commit 937b209a3c14857bea09a692545c59ac1a241275 Author: Travis Bradshaw Date: Tue Jan 31 14:59:39 2012 -0600 The Return to Castle Wolfenstein Multiplayer sources as originally released under the GPL license on August 12, 2010. diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..6fc52e5 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,643 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + + +ADDITIONAL TERMS APPLICABLE TO THE RETURN TO CASTLE WOLFENSTEIN MULTIPLAYER GPL SOURCE CODE. + + The following additional terms (“Additional Terms”) supplement and modify the GNU General Public License, Version 3 (“GPL”) applicable to the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). In addition to the terms and conditions of the GPL, the RTCW MP Source Code is subject to the further restrictions below. + +1. Replacement of Section 15. Section 15 of the GPL shall be deleted in its entirety and replaced with the following: + +“15. Disclaimer of Warranty. + +THE PROGRAM IS PROVIDED WITHOUT ANY WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, TITLE AND MERCHANTABILITY. THE PROGRAM IS BEING DELIVERED OR MADE AVAILABLE “AS IS”, “WITH ALL FAULTS” AND WITHOUT WARRANTY OR REPRESENTATION. 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.” + +2. Replacement of Section 16. Section 16 of the GPL shall be deleted in its entirety and replaced with the following: + +“16. LIMITATION OF LIABILITY. + +UNDER NO CIRCUMSTANCES SHALL ANY COPYRIGHT HOLDER OR ITS AFFILIATES, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, FOR ANY DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, DIRECT, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL OR PUNITIVE DAMAGES ARISING FROM, OUT OF OR IN CONNECTION WITH THE USE OR INABILITY TO USE THE PROGRAM OR OTHER DEALINGS WITH 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), WHETHER OR NOT ANY COPYRIGHT HOLDER OR SUCH OTHER PARTY RECEIVES NOTICE OF ANY SUCH DAMAGES AND WHETHER OR NOT SUCH DAMAGES COULD HAVE BEEN FORESEEN.” + +3. LEGAL NOTICES; NO TRADEMARK LICENSE; ORIGIN. You must reproduce faithfully all trademark, copyright and other proprietary and legal notices on any copies of the Program or any other required author attributions. This license does not grant you rights to use any copyright holder or any other party’s name, logo, or trademarks. Neither the name of the copyright holder or its affiliates, or any other party who modifies and/or conveys the Program may be used to endorse or promote products derived from this software without specific prior written permission. The origin of the Program must not be misrepresented; you must not claim that you wrote the original Program. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original Program. + +4. INDEMNIFICATION. IF YOU CONVEY A COVERED WORK AND AGREE WITH ANY RECIPIENT OF THAT COVERED WORK THAT YOU WILL ASSUME ANY LIABILITY FOR THAT COVERED WORK, YOU HEREBY AGREE TO INDEMNIFY, DEFEND AND HOLD HARMLESS THE OTHER LICENSORS AND AUTHORS OF THAT COVERED WORK FOR ANY DAMAEGS, DEMANDS, CLAIMS, LOSSES, CAUSES OF ACTION, LAWSUITS, JUDGMENTS EXPENSES (INCLUDING WITHOUT LIMITATION REASONABLE ATTORNEYS' FEES AND EXPENSES) OR ANY OTHER LIABLITY ARISING FROM, RELATED TO OR IN CONNECTION WITH YOUR ASSUMPTIONS OF LIABILITY. diff --git a/MAIN/UI/menudef.h b/MAIN/UI/menudef.h new file mode 100644 index 0000000..b93fcc2 --- /dev/null +++ b/MAIN/UI/menudef.h @@ -0,0 +1,395 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#define ITEM_TYPE_TEXT 0 // simple text +#define ITEM_TYPE_BUTTON 1 // button, basically text with a border +#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped +#define ITEM_TYPE_CHECKBOX 3 // check box +#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar +#define ITEM_TYPE_COMBO 5 // drop down list +#define ITEM_TYPE_LISTBOX 6 // scrollable list +#define ITEM_TYPE_MODEL 7 // model +#define ITEM_TYPE_OWNERDRAW 8 // owner draw, name specs what it is +#define ITEM_TYPE_NUMERICFIELD 9 // editable text, associated with a cvar +#define ITEM_TYPE_SLIDER 10 // mouse speed, volume, etc. +#define ITEM_TYPE_YESNO 11 // yes no cvar setting +#define ITEM_TYPE_MULTI 12 // multiple list setting, enumerated +#define ITEM_TYPE_BIND 13 // multiple list setting, enumerated +#define ITEM_TYPE_MENUMODEL 14 // special menu model + +#define ITEM_ALIGN_LEFT 0 // left alignment +#define ITEM_ALIGN_CENTER 1 // center alignment +#define ITEM_ALIGN_RIGHT 2 // right alignment + +#define ITEM_TEXTSTYLE_NORMAL 0 // normal text +#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking +#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing +#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this ) + +#define WINDOW_BORDER_NONE 0 // no border +#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel ) +#define WINDOW_BORDER_HORZ 2 // horizontal borders only +#define WINDOW_BORDER_VERT 3 // vertical borders only +#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars + +#define WINDOW_STYLE_EMPTY 0 // no background +#define WINDOW_STYLE_FILLED 1 // filled with background color +#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color +#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color +#define WINDOW_STYLE_TEAMCOLOR 4 // team color +#define WINDOW_STYLE_CINEMATIC 5 // cinematic + +#define MENU_TRUE 1 // uh.. true +#define MENU_FALSE 0 // and false + +#define HUD_VERTICAL 0x00 +#define HUD_HORIZONTAL 0x01 + +#define RANGETYPE_ABSOLUTE 0 +#define RANGETYPE_RELATIVE 1 + +// list box element types +#define LISTBOX_TEXT 0x00 +#define LISTBOX_IMAGE 0x01 + +// list feeders +#define FEEDER_HEADS 0x00 // model heads +#define FEEDER_MAPS 0x01 // text maps based on game type +#define FEEDER_SERVERS 0x02 // servers +#define FEEDER_CLANS 0x03 // clan names +#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format +#define FEEDER_REDTEAM_LIST 0x05 // red team members +#define FEEDER_BLUETEAM_LIST 0x06 // blue team members +#define FEEDER_PLAYER_LIST 0x07 // players +#define FEEDER_TEAM_LIST 0x08 // team members for team voting +#define FEEDER_MODS 0x09 // team members for team voting +#define FEEDER_DEMOS 0x0a // team members for team voting +#define FEEDER_SCOREBOARD 0x0b // team members for team voting +#define FEEDER_Q3HEADS 0x0c // model heads +#define FEEDER_SERVERSTATUS 0x0d // server status +#define FEEDER_FINDPLAYER 0x0e // find player +#define FEEDER_CINEMATICS 0x0f // cinematics +#define FEEDER_SAVEGAMES 0x10 // savegames +#define FEEDER_PICKSPAWN 0x11 // NERVE - SMF - wolf mp pick spawn point +#define FEEDER_SOLDIERWEAP 0x12 // NERVE - SMF - wolf mp soldier weapon list +#define FEEDER_LIEUTWEAP 0x13 // NERVE - SMF - wolf mp lieutenant weapon list + +// display flags +#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG 0x00000001 +#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG 0x00000002 +#define CG_SHOW_ANYTEAMGAME 0x00000004 +#define CG_SHOW_HARVESTER 0x00000008 +#define CG_SHOW_ONEFLAG 0x00000010 +#define CG_SHOW_CTF 0x00000020 +#define CG_SHOW_OBELISK 0x00000040 +#define CG_SHOW_HEALTHCRITICAL 0x00000080 +#define CG_SHOW_SINGLEPLAYER 0x00000100 +#define CG_SHOW_TOURNAMENT 0x00000200 +#define CG_SHOW_DURINGINCOMINGVOICE 0x00000400 +#define CG_SHOW_IF_PLAYER_HAS_FLAG 0x00000800 +#define CG_SHOW_LANPLAYONLY 0x00001000 +#define CG_SHOW_MINED 0x00002000 +#define CG_SHOW_HEALTHOK 0x00004000 +#define CG_SHOW_TEAMINFO 0x00008000 +#define CG_SHOW_NOTEAMINFO 0x00010000 +#define CG_SHOW_OTHERTEAMHASFLAG 0x00020000 +#define CG_SHOW_YOURTEAMHASENEMYFLAG 0x00040000 +#define CG_SHOW_ANYNONTEAMGAME 0x00080000 +//(SA) +#define CG_SHOW_TEXTASINT 0x00200000 +#define CG_SHOW_HIGHLIGHTED 0x00100000 + +#define CG_SHOW_NOT_V_BINOC 0x00200000 //----(SA) added // hide on binoc huds +#define CG_SHOW_NOT_V_SNIPER 0x00400000 //----(SA) added // hide on sniper huds +#define CG_SHOW_NOT_V_SNOOPER 0x00800000 //----(SA) added // hide on snooper huds +#define CG_SHOW_NOT_V_FGSCOPE 0x01000000 //----(SA) added // hide on fg42 scope huds +#define CG_SHOW_NOT_V_CLEAR 0x02000000 //----(SA) added // hide on normal, full-view huds + +#define CG_SHOW_2DONLY 0x10000000 + + +#define UI_SHOW_LEADER 0x00000001 +#define UI_SHOW_NOTLEADER 0x00000002 +#define UI_SHOW_FAVORITESERVERS 0x00000004 +#define UI_SHOW_ANYNONTEAMGAME 0x00000008 +#define UI_SHOW_ANYTEAMGAME 0x00000010 +#define UI_SHOW_NEWHIGHSCORE 0x00000020 +#define UI_SHOW_DEMOAVAILABLE 0x00000040 +#define UI_SHOW_NEWBESTTIME 0x00000080 +#define UI_SHOW_FFA 0x00000100 +#define UI_SHOW_NOTFFA 0x00000200 +#define UI_SHOW_NETANYNONTEAMGAME 0x00000400 +#define UI_SHOW_NETANYTEAMGAME 0x00000800 +#define UI_SHOW_NOTFAVORITESERVERS 0x00001000 + + + +// owner draw types +// ideally these should be done outside of this file but +// this makes it much easier for the macro expansion to +// convert them for the designers ( from the .menu files ) +#define CG_OWNERDRAW_BASE 1 +#define CG_PLAYER_ARMOR_ICON 1 +#define CG_PLAYER_ARMOR_VALUE 2 +#define CG_PLAYER_HEAD 3 +#define CG_PLAYER_HEALTH 4 +#define CG_PLAYER_AMMO_ICON 5 +#define CG_PLAYER_AMMO_VALUE 6 +#define CG_SELECTEDPLAYER_HEAD 7 +#define CG_SELECTEDPLAYER_NAME 8 +#define CG_SELECTEDPLAYER_LOCATION 9 +#define CG_SELECTEDPLAYER_STATUS 10 +#define CG_SELECTEDPLAYER_WEAPON 11 +#define CG_SELECTEDPLAYER_POWERUP 12 + +#define CG_FLAGCARRIER_HEAD 13 +#define CG_FLAGCARRIER_NAME 14 +#define CG_FLAGCARRIER_LOCATION 15 +#define CG_FLAGCARRIER_STATUS 16 +#define CG_FLAGCARRIER_WEAPON 17 +#define CG_FLAGCARRIER_POWERUP 18 + +#define CG_PLAYER_ITEM 19 +#define CG_PLAYER_SCORE 20 + +#define CG_BLUE_FLAGHEAD 21 +#define CG_BLUE_FLAGSTATUS 22 +#define CG_BLUE_FLAGNAME 23 +#define CG_RED_FLAGHEAD 24 +#define CG_RED_FLAGSTATUS 25 +#define CG_RED_FLAGNAME 26 + +#define CG_BLUE_SCORE 27 +#define CG_RED_SCORE 28 +#define CG_RED_NAME 29 +#define CG_BLUE_NAME 30 +#define CG_HARVESTER_SKULLS 31 // only shows in harvester +#define CG_ONEFLAG_STATUS 32 // only shows in one flag +#define CG_PLAYER_LOCATION 33 +#define CG_TEAM_COLOR 34 +#define CG_CTF_POWERUP 35 + +#define CG_AREA_POWERUP 36 +#define CG_AREA_LAGOMETER 37 // painted with old system +#define CG_PLAYER_HASFLAG 38 +#define CG_GAME_TYPE 39 // not done + +#define CG_SELECTEDPLAYER_ARMOR 40 +#define CG_SELECTEDPLAYER_HEALTH 41 +#define CG_PLAYER_STATUS 42 +#define CG_FRAGGED_MSG 43 // painted with old system +#define CG_PROXMINED_MSG 44 // painted with old system +#define CG_AREA_FPSINFO 45 // painted with old system +#define CG_AREA_SYSTEMCHAT 46 // painted with old system +#define CG_AREA_TEAMCHAT 47 // painted with old system +#define CG_AREA_CHAT 48 // painted with old system +#define CG_GAME_STATUS 49 +#define CG_KILLER 50 +#define CG_PLAYER_ARMOR_ICON2D 51 +#define CG_PLAYER_AMMO_ICON2D 52 +#define CG_ACCURACY 53 +#define CG_ASSISTS 54 +#define CG_DEFEND 55 +#define CG_EXCELLENT 56 +#define CG_IMPRESSIVE 57 +#define CG_PERFECT 58 +#define CG_GAUNTLET 59 +#define CG_SPECTATORS 60 +#define CG_TEAMINFO 61 +#define CG_VOICE_HEAD 62 +#define CG_VOICE_NAME 63 +#define CG_PLAYER_HASFLAG2D 64 +#define CG_HARVESTER_SKULLS2D 65 // only shows in harvester +#define CG_CAPFRAGLIMIT 66 +#define CG_1STPLACE 67 +#define CG_2NDPLACE 68 +#define CG_CAPTURES 69 + +// (SA) adding +#define CG_PLAYER_AMMOCLIP_VALUE 70 +#define CG_PLAYER_WEAPON_ICON2D 71 +#define CG_CURSORHINT 72 +#define CG_STAMINA 73 +#define CG_PLAYER_WEAPON_HEAT 74 +#define CG_PLAYER_POWERUP 75 +#define CG_PLAYER_HOLDABLE 76 +#define CG_PLAYER_INVENTORY 77 +#define CG_AREA_WEAPON 78 // draw weapons here +#define CG_AREA_HOLDABLE 79 +#define CG_CURSORHINT_STATUS 80 // like 'health' bar when pointing at a func_explosive +#define CG_PLAYER_WEAPON_STABILITY 81 // shows aimSpreadScale value +#define CG_PLAYER_WEAPON_RECHARGE 82 // DHM - Nerve :: For various multiplayer weapons that have recharge times + +#define UI_OWNERDRAW_BASE 200 +#define UI_HANDICAP 200 +#define UI_EFFECTS 201 +#define UI_PLAYERMODEL 202 +#define UI_CLANNAME 203 +#define UI_CLANLOGO 204 +#define UI_GAMETYPE 205 +#define UI_MAPPREVIEW 206 +#define UI_SKILL 207 +#define UI_BLUETEAMNAME 208 +#define UI_REDTEAMNAME 209 +#define UI_BLUETEAM1 210 +#define UI_BLUETEAM2 211 +#define UI_BLUETEAM3 212 +#define UI_BLUETEAM4 213 +#define UI_BLUETEAM5 214 +#define UI_REDTEAM1 215 +#define UI_REDTEAM2 216 +#define UI_REDTEAM3 217 +#define UI_REDTEAM4 218 +#define UI_REDTEAM5 219 +#define UI_NETSOURCE 220 +#define UI_NETMAPPREVIEW 221 +#define UI_NETFILTER 222 +#define UI_TIER 223 +#define UI_OPPONENTMODEL 224 +#define UI_TIERMAP1 225 +#define UI_TIERMAP2 226 +#define UI_TIERMAP3 227 +#define UI_PLAYERLOGO 228 +#define UI_OPPONENTLOGO 229 +#define UI_PLAYERLOGO_METAL 230 +#define UI_OPPONENTLOGO_METAL 231 +#define UI_PLAYERLOGO_NAME 232 +#define UI_OPPONENTLOGO_NAME 233 +#define UI_TIER_MAPNAME 234 +#define UI_TIER_GAMETYPE 235 +#define UI_ALLMAPS_SELECTION 236 +#define UI_OPPONENT_NAME 237 +#define UI_VOTE_KICK 238 +#define UI_BOTNAME 239 +#define UI_BOTSKILL 240 +#define UI_REDBLUE 241 +#define UI_CROSSHAIR 242 +#define UI_SELECTEDPLAYER 243 +#define UI_MAPCINEMATIC 244 +#define UI_NETGAMETYPE 245 +#define UI_NETMAPCINEMATIC 246 +#define UI_SERVERREFRESHDATE 247 +#define UI_SERVERMOTD 248 +#define UI_GLINFO 249 +#define UI_KEYBINDSTATUS 250 +#define UI_CLANCINEMATIC 251 +#define UI_MAP_TIMETOBEAT 252 +#define UI_JOINGAMETYPE 253 +#define UI_PREVIEWCINEMATIC 254 +#define UI_STARTMAPCINEMATIC 255 +#define UI_MAPS_SELECTION 256 + +//----(SA) added +#define UI_MENUMODEL 257 +#define UI_SAVEGAME_SHOT 258 +//----(SA) end + +// NERVE - SMF +#define UI_LIMBOCHAT 259 +// -NERVE - SMF + +#define VOICECHAT_GETFLAG "getflag" // command someone to get the flag +#define VOICECHAT_OFFENSE "offense" // command someone to go on offense +#define VOICECHAT_DEFEND "defend" // command someone to go on defense +#define VOICECHAT_DEFENDFLAG "defendflag" // command someone to defend the flag +#define VOICECHAT_PATROL "patrol" // command someone to go on patrol (roam) +#define VOICECHAT_CAMP "camp" // command someone to camp (we don't have sounds for this one) +#define VOICECHAT_FOLLOWME "followme" // command someone to follow you +#define VOICECHAT_RETURNFLAG "returnflag" // command someone to return our flag +#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier" // command someone to follow the flag carrier +#define VOICECHAT_YES "yes" // yes, affirmative, etc. +#define VOICECHAT_NO "no" // no, negative, etc. +#define VOICECHAT_ONGETFLAG "ongetflag" // I'm getting the flag +#define VOICECHAT_ONOFFENSE "onoffense" // I'm on offense +#define VOICECHAT_ONDEFENSE "ondefense" // I'm on defense +#define VOICECHAT_ONPATROL "onpatrol" // I'm on patrol (roaming) +#define VOICECHAT_ONCAMPING "oncamp" // I'm camping somewhere +#define VOICECHAT_ONFOLLOW "onfollow" // I'm following +#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier" // I'm following the flag carrier +#define VOICECHAT_ONRETURNFLAG "onreturnflag" // I'm returning our flag +#define VOICECHAT_INPOSITION "inposition" // I'm in position +#define VOICECHAT_IHAVEFLAG "ihaveflag" // I have the flag +#define VOICECHAT_BASEATTACK "baseattack" // the base is under attack +#define VOICECHAT_ENEMYHASFLAG "enemyhasflag" // the enemy has our flag (CTF) +#define VOICECHAT_STARTLEADER "startleader" // I'm the leader +#define VOICECHAT_STOPLEADER "stopleader" // I resign leadership +#define VOICECHAT_WHOISLEADER "whoisleader" // who is the team leader +#define VOICECHAT_WANTONDEFENSE "wantondefense" // I want to be on defense +#define VOICECHAT_WANTONOFFENSE "wantonoffense" // I want to be on offense +#define VOICECHAT_KILLINSULT "kill_insult" // I just killed you +#define VOICECHAT_TAUNT "taunt" // I want to taunt you +#define VOICECHAT_DEATHINSULT "death_insult" // you just killed me +#define VOICECHAT_KILLGAUNTLET "kill_gauntlet" // I just killed you with the gauntlet +#define VOICECHAT_PRAISE "praise" // you did something good + +// NERVE - SMF - wolf multiplayer class/item selection mechanism +#define WM_START_SELECT 0 + +#define WM_SELECT_TEAM 1 +#define WM_SELECT_CLASS 2 +#define WM_SELECT_WEAPON 3 +#define WM_SELECT_PISTOL 4 +#define WM_SELECT_GRENADE 5 +#define WM_SELECT_ITEM1 6 + +#define WM_AXIS 1 +#define WM_ALLIES 2 +#define WM_SPECTATOR 3 + +#define WM_SOLDIER 1 +#define WM_MEDIC 2 +#define WM_LIEUTENANT 3 +#define WM_ENGINEER 4 + +#define WM_PISTOL_1911 1 +#define WM_PISTOL_LUGER 2 + +#define WM_WEAPON_MP40 3 +#define WM_WEAPON_THOMPSON 4 +#define WM_WEAPON_STEN 5 +#define WM_WEAPON_MAUSER 6 +//#define WM_WEAPON_GARAND 7 +#define WM_WEAPON_PANZERFAUST 8 +#define WM_WEAPON_VENOM 9 +#define WM_WEAPON_FLAMETHROWER 10 + +#define WM_PINEAPPLE_GRENADE 11 +#define WM_STICK_GRENADE 12 +// -NERVE - SMF + +// NERVE - SMF - ui font stuff +#define UI_FONT_DEFAULT 0 +#define UI_FONT_NORMAL 1 +#define UI_FONT_BIG 2 +#define UI_FONT_SMALL 3 +#define UI_FONT_HANDWRITING 4 +// -NERVE - SMF diff --git a/MAIN/ui_mp/menudef.h b/MAIN/ui_mp/menudef.h new file mode 100644 index 0000000..994c69f --- /dev/null +++ b/MAIN/ui_mp/menudef.h @@ -0,0 +1,396 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#define ITEM_TYPE_TEXT 0 // simple text +#define ITEM_TYPE_BUTTON 1 // button, basically text with a border +#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped +#define ITEM_TYPE_CHECKBOX 3 // check box +#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar +#define ITEM_TYPE_COMBO 5 // drop down list +#define ITEM_TYPE_LISTBOX 6 // scrollable list +#define ITEM_TYPE_MODEL 7 // model +#define ITEM_TYPE_OWNERDRAW 8 // owner draw, name specs what it is +#define ITEM_TYPE_NUMERICFIELD 9 // editable text, associated with a cvar +#define ITEM_TYPE_SLIDER 10 // mouse speed, volume, etc. +#define ITEM_TYPE_YESNO 11 // yes no cvar setting +#define ITEM_TYPE_MULTI 12 // multiple list setting, enumerated +#define ITEM_TYPE_BIND 13 // multiple list setting, enumerated +#define ITEM_TYPE_MENUMODEL 14 // special menu model + +#define ITEM_ALIGN_LEFT 0 // left alignment +#define ITEM_ALIGN_CENTER 1 // center alignment +#define ITEM_ALIGN_RIGHT 2 // right alignment +#define ITEM_ALIGN_CENTER2 3 // center alignment + +#define ITEM_TEXTSTYLE_NORMAL 0 // normal text +#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking +#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing +#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this ) + +#define WINDOW_BORDER_NONE 0 // no border +#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel ) +#define WINDOW_BORDER_HORZ 2 // horizontal borders only +#define WINDOW_BORDER_VERT 3 // vertical borders only +#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars + +#define WINDOW_STYLE_EMPTY 0 // no background +#define WINDOW_STYLE_FILLED 1 // filled with background color +#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color +#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color +#define WINDOW_STYLE_TEAMCOLOR 4 // team color +#define WINDOW_STYLE_CINEMATIC 5 // cinematic + +#define MENU_TRUE 1 // uh.. true +#define MENU_FALSE 0 // and false + +#define HUD_VERTICAL 0x00 +#define HUD_HORIZONTAL 0x01 + +#define RANGETYPE_ABSOLUTE 0 +#define RANGETYPE_RELATIVE 1 + +// list box element types +#define LISTBOX_TEXT 0x00 +#define LISTBOX_IMAGE 0x01 + +// list feeders +#define FEEDER_HEADS 0x00 // model heads +#define FEEDER_MAPS 0x01 // text maps based on game type +#define FEEDER_SERVERS 0x02 // servers +#define FEEDER_CLANS 0x03 // clan names +#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format +#define FEEDER_REDTEAM_LIST 0x05 // red team members +#define FEEDER_BLUETEAM_LIST 0x06 // blue team members +#define FEEDER_PLAYER_LIST 0x07 // players +#define FEEDER_TEAM_LIST 0x08 // team members for team voting +#define FEEDER_MODS 0x09 // team members for team voting +#define FEEDER_DEMOS 0x0a // team members for team voting +#define FEEDER_SCOREBOARD 0x0b // team members for team voting +#define FEEDER_Q3HEADS 0x0c // model heads +#define FEEDER_SERVERSTATUS 0x0d // server status +#define FEEDER_FINDPLAYER 0x0e // find player +#define FEEDER_CINEMATICS 0x0f // cinematics +#define FEEDER_SAVEGAMES 0x10 // savegames +#define FEEDER_PICKSPAWN 0x11 // NERVE - SMF - wolf mp pick spawn point +#define FEEDER_SOLDIERWEAP 0x12 // NERVE - SMF - wolf mp soldier weapon list +#define FEEDER_LIEUTWEAP 0x13 // NERVE - SMF - wolf mp lieutenant weapon list + +// display flags +#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG 0x00000001 +#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG 0x00000002 +#define CG_SHOW_ANYTEAMGAME 0x00000004 +#define CG_SHOW_HARVESTER 0x00000008 +#define CG_SHOW_ONEFLAG 0x00000010 +#define CG_SHOW_CTF 0x00000020 +#define CG_SHOW_OBELISK 0x00000040 +#define CG_SHOW_HEALTHCRITICAL 0x00000080 +#define CG_SHOW_SINGLEPLAYER 0x00000100 +#define CG_SHOW_TOURNAMENT 0x00000200 +#define CG_SHOW_DURINGINCOMINGVOICE 0x00000400 +#define CG_SHOW_IF_PLAYER_HAS_FLAG 0x00000800 +#define CG_SHOW_LANPLAYONLY 0x00001000 +#define CG_SHOW_MINED 0x00002000 +#define CG_SHOW_HEALTHOK 0x00004000 +#define CG_SHOW_TEAMINFO 0x00008000 +#define CG_SHOW_NOTEAMINFO 0x00010000 +#define CG_SHOW_OTHERTEAMHASFLAG 0x00020000 +#define CG_SHOW_YOURTEAMHASENEMYFLAG 0x00040000 +#define CG_SHOW_ANYNONTEAMGAME 0x00080000 +//(SA) +#define CG_SHOW_TEXTASINT 0x00200000 +#define CG_SHOW_HIGHLIGHTED 0x00100000 + +#define CG_SHOW_NOT_V_BINOC 0x00200000 //----(SA) added // hide on binoc huds +#define CG_SHOW_NOT_V_SNIPER 0x00400000 //----(SA) added // hide on sniper huds +#define CG_SHOW_NOT_V_SNOOPER 0x00800000 //----(SA) added // hide on snooper huds +#define CG_SHOW_NOT_V_FGSCOPE 0x01000000 //----(SA) added // hide on fg42 scope huds +#define CG_SHOW_NOT_V_CLEAR 0x02000000 //----(SA) added // hide on normal, full-view huds + +#define CG_SHOW_2DONLY 0x10000000 + + +#define UI_SHOW_LEADER 0x00000001 +#define UI_SHOW_NOTLEADER 0x00000002 +#define UI_SHOW_FAVORITESERVERS 0x00000004 +#define UI_SHOW_ANYNONTEAMGAME 0x00000008 +#define UI_SHOW_ANYTEAMGAME 0x00000010 +#define UI_SHOW_NEWHIGHSCORE 0x00000020 +#define UI_SHOW_DEMOAVAILABLE 0x00000040 +#define UI_SHOW_NEWBESTTIME 0x00000080 +#define UI_SHOW_FFA 0x00000100 +#define UI_SHOW_NOTFFA 0x00000200 +#define UI_SHOW_NETANYNONTEAMGAME 0x00000400 +#define UI_SHOW_NETANYTEAMGAME 0x00000800 +#define UI_SHOW_NOTFAVORITESERVERS 0x00001000 + + + +// owner draw types +// ideally these should be done outside of this file but +// this makes it much easier for the macro expansion to +// convert them for the designers ( from the .menu files ) +#define CG_OWNERDRAW_BASE 1 +#define CG_PLAYER_ARMOR_ICON 1 +#define CG_PLAYER_ARMOR_VALUE 2 +#define CG_PLAYER_HEAD 3 +#define CG_PLAYER_HEALTH 4 +#define CG_PLAYER_AMMO_ICON 5 +#define CG_PLAYER_AMMO_VALUE 6 +#define CG_SELECTEDPLAYER_HEAD 7 +#define CG_SELECTEDPLAYER_NAME 8 +#define CG_SELECTEDPLAYER_LOCATION 9 +#define CG_SELECTEDPLAYER_STATUS 10 +#define CG_SELECTEDPLAYER_WEAPON 11 +#define CG_SELECTEDPLAYER_POWERUP 12 + +#define CG_FLAGCARRIER_HEAD 13 +#define CG_FLAGCARRIER_NAME 14 +#define CG_FLAGCARRIER_LOCATION 15 +#define CG_FLAGCARRIER_STATUS 16 +#define CG_FLAGCARRIER_WEAPON 17 +#define CG_FLAGCARRIER_POWERUP 18 + +#define CG_PLAYER_ITEM 19 +#define CG_PLAYER_SCORE 20 + +#define CG_BLUE_FLAGHEAD 21 +#define CG_BLUE_FLAGSTATUS 22 +#define CG_BLUE_FLAGNAME 23 +#define CG_RED_FLAGHEAD 24 +#define CG_RED_FLAGSTATUS 25 +#define CG_RED_FLAGNAME 26 + +#define CG_BLUE_SCORE 27 +#define CG_RED_SCORE 28 +#define CG_RED_NAME 29 +#define CG_BLUE_NAME 30 +#define CG_HARVESTER_SKULLS 31 // only shows in harvester +#define CG_ONEFLAG_STATUS 32 // only shows in one flag +#define CG_PLAYER_LOCATION 33 +#define CG_TEAM_COLOR 34 +#define CG_CTF_POWERUP 35 + +#define CG_AREA_POWERUP 36 +#define CG_AREA_LAGOMETER 37 // painted with old system +#define CG_PLAYER_HASFLAG 38 +#define CG_GAME_TYPE 39 // not done + +#define CG_SELECTEDPLAYER_ARMOR 40 +#define CG_SELECTEDPLAYER_HEALTH 41 +#define CG_PLAYER_STATUS 42 +#define CG_FRAGGED_MSG 43 // painted with old system +#define CG_PROXMINED_MSG 44 // painted with old system +#define CG_AREA_FPSINFO 45 // painted with old system +#define CG_AREA_SYSTEMCHAT 46 // painted with old system +#define CG_AREA_TEAMCHAT 47 // painted with old system +#define CG_AREA_CHAT 48 // painted with old system +#define CG_GAME_STATUS 49 +#define CG_KILLER 50 +#define CG_PLAYER_ARMOR_ICON2D 51 +#define CG_PLAYER_AMMO_ICON2D 52 +#define CG_ACCURACY 53 +#define CG_ASSISTS 54 +#define CG_DEFEND 55 +#define CG_EXCELLENT 56 +#define CG_IMPRESSIVE 57 +#define CG_PERFECT 58 +#define CG_GAUNTLET 59 +#define CG_SPECTATORS 60 +#define CG_TEAMINFO 61 +#define CG_VOICE_HEAD 62 +#define CG_VOICE_NAME 63 +#define CG_PLAYER_HASFLAG2D 64 +#define CG_HARVESTER_SKULLS2D 65 // only shows in harvester +#define CG_CAPFRAGLIMIT 66 +#define CG_1STPLACE 67 +#define CG_2NDPLACE 68 +#define CG_CAPTURES 69 + +// (SA) adding +#define CG_PLAYER_AMMOCLIP_VALUE 70 +#define CG_PLAYER_WEAPON_ICON2D 71 +#define CG_CURSORHINT 72 +#define CG_STAMINA 73 +#define CG_PLAYER_WEAPON_HEAT 74 +#define CG_PLAYER_POWERUP 75 +#define CG_PLAYER_HOLDABLE 76 +#define CG_PLAYER_INVENTORY 77 +#define CG_AREA_WEAPON 78 // draw weapons here +#define CG_AREA_HOLDABLE 79 +#define CG_CURSORHINT_STATUS 80 // like 'health' bar when pointing at a func_explosive +#define CG_PLAYER_WEAPON_STABILITY 81 // shows aimSpreadScale value +#define CG_PLAYER_WEAPON_RECHARGE 82 // DHM - Nerve :: For various multiplayer weapons that have recharge times + +#define UI_OWNERDRAW_BASE 200 +#define UI_HANDICAP 200 +#define UI_EFFECTS 201 +#define UI_PLAYERMODEL 202 +#define UI_CLANNAME 203 +#define UI_CLANLOGO 204 +#define UI_GAMETYPE 205 +#define UI_MAPPREVIEW 206 +#define UI_SKILL 207 +#define UI_BLUETEAMNAME 208 +#define UI_REDTEAMNAME 209 +#define UI_BLUETEAM1 210 +#define UI_BLUETEAM2 211 +#define UI_BLUETEAM3 212 +#define UI_BLUETEAM4 213 +#define UI_BLUETEAM5 214 +#define UI_REDTEAM1 215 +#define UI_REDTEAM2 216 +#define UI_REDTEAM3 217 +#define UI_REDTEAM4 218 +#define UI_REDTEAM5 219 +#define UI_NETSOURCE 220 +#define UI_NETMAPPREVIEW 221 +#define UI_NETFILTER 222 +#define UI_TIER 223 +#define UI_OPPONENTMODEL 224 +#define UI_TIERMAP1 225 +#define UI_TIERMAP2 226 +#define UI_TIERMAP3 227 +#define UI_PLAYERLOGO 228 +#define UI_OPPONENTLOGO 229 +#define UI_PLAYERLOGO_METAL 230 +#define UI_OPPONENTLOGO_METAL 231 +#define UI_PLAYERLOGO_NAME 232 +#define UI_OPPONENTLOGO_NAME 233 +#define UI_TIER_MAPNAME 234 +#define UI_TIER_GAMETYPE 235 +#define UI_ALLMAPS_SELECTION 236 +#define UI_OPPONENT_NAME 237 +#define UI_VOTE_KICK 238 +#define UI_BOTNAME 239 +#define UI_BOTSKILL 240 +#define UI_REDBLUE 241 +#define UI_CROSSHAIR 242 +#define UI_SELECTEDPLAYER 243 +#define UI_MAPCINEMATIC 244 +#define UI_NETGAMETYPE 245 +#define UI_NETMAPCINEMATIC 246 +#define UI_SERVERREFRESHDATE 247 +#define UI_SERVERMOTD 248 +#define UI_GLINFO 249 +#define UI_KEYBINDSTATUS 250 +#define UI_CLANCINEMATIC 251 +#define UI_MAP_TIMETOBEAT 252 +#define UI_JOINGAMETYPE 253 +#define UI_PREVIEWCINEMATIC 254 +#define UI_STARTMAPCINEMATIC 255 +#define UI_MAPS_SELECTION 256 + +//----(SA) added +#define UI_MENUMODEL 257 +#define UI_SAVEGAME_SHOT 258 +//----(SA) end + +// NERVE - SMF +#define UI_LIMBOCHAT 259 +// -NERVE - SMF + +#define VOICECHAT_GETFLAG "getflag" // command someone to get the flag +#define VOICECHAT_OFFENSE "offense" // command someone to go on offense +#define VOICECHAT_DEFEND "defend" // command someone to go on defense +#define VOICECHAT_DEFENDFLAG "defendflag" // command someone to defend the flag +#define VOICECHAT_PATROL "patrol" // command someone to go on patrol (roam) +#define VOICECHAT_CAMP "camp" // command someone to camp (we don't have sounds for this one) +#define VOICECHAT_FOLLOWME "followme" // command someone to follow you +#define VOICECHAT_RETURNFLAG "returnflag" // command someone to return our flag +#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier" // command someone to follow the flag carrier +#define VOICECHAT_YES "yes" // yes, affirmative, etc. +#define VOICECHAT_NO "no" // no, negative, etc. +#define VOICECHAT_ONGETFLAG "ongetflag" // I'm getting the flag +#define VOICECHAT_ONOFFENSE "onoffense" // I'm on offense +#define VOICECHAT_ONDEFENSE "ondefense" // I'm on defense +#define VOICECHAT_ONPATROL "onpatrol" // I'm on patrol (roaming) +#define VOICECHAT_ONCAMPING "oncamp" // I'm camping somewhere +#define VOICECHAT_ONFOLLOW "onfollow" // I'm following +#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier" // I'm following the flag carrier +#define VOICECHAT_ONRETURNFLAG "onreturnflag" // I'm returning our flag +#define VOICECHAT_INPOSITION "inposition" // I'm in position +#define VOICECHAT_IHAVEFLAG "ihaveflag" // I have the flag +#define VOICECHAT_BASEATTACK "baseattack" // the base is under attack +#define VOICECHAT_ENEMYHASFLAG "enemyhasflag" // the enemy has our flag (CTF) +#define VOICECHAT_STARTLEADER "startleader" // I'm the leader +#define VOICECHAT_STOPLEADER "stopleader" // I resign leadership +#define VOICECHAT_WHOISLEADER "whoisleader" // who is the team leader +#define VOICECHAT_WANTONDEFENSE "wantondefense" // I want to be on defense +#define VOICECHAT_WANTONOFFENSE "wantonoffense" // I want to be on offense +#define VOICECHAT_KILLINSULT "kill_insult" // I just killed you +#define VOICECHAT_TAUNT "taunt" // I want to taunt you +#define VOICECHAT_DEATHINSULT "death_insult" // you just killed me +#define VOICECHAT_KILLGAUNTLET "kill_gauntlet" // I just killed you with the gauntlet +#define VOICECHAT_PRAISE "praise" // you did something good + +// NERVE - SMF - wolf multiplayer class/item selection mechanism +#define WM_START_SELECT 0 + +#define WM_SELECT_TEAM 1 +#define WM_SELECT_CLASS 2 +#define WM_SELECT_WEAPON 3 +#define WM_SELECT_PISTOL 4 +#define WM_SELECT_GRENADE 5 +#define WM_SELECT_ITEM1 6 + +#define WM_AXIS 1 +#define WM_ALLIES 2 +#define WM_SPECTATOR 3 + +#define WM_SOLDIER 1 +#define WM_MEDIC 2 +#define WM_LIEUTENANT 3 +#define WM_ENGINEER 4 + +#define WM_PISTOL_1911 1 +#define WM_PISTOL_LUGER 2 + +#define WM_WEAPON_MP40 3 +#define WM_WEAPON_THOMPSON 4 +#define WM_WEAPON_STEN 5 +#define WM_WEAPON_MAUSER 6 +//#define WM_WEAPON_GARAND 7 +#define WM_WEAPON_PANZERFAUST 8 +#define WM_WEAPON_VENOM 9 +#define WM_WEAPON_FLAMETHROWER 10 + +#define WM_PINEAPPLE_GRENADE 11 +#define WM_STICK_GRENADE 12 +// -NERVE - SMF + +// NERVE - SMF - ui font stuff +#define UI_FONT_DEFAULT 0 +#define UI_FONT_NORMAL 1 +#define UI_FONT_BIG 2 +#define UI_FONT_SMALL 3 +#define UI_FONT_HANDWRITING 4 +// -NERVE - SMF diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..6d1966e --- /dev/null +++ b/README.txt @@ -0,0 +1,133 @@ +Return to Castle Wolfenstein multiplayer GPL source release +=========================================================== + +This file contains the following sections: + +GENERAL NOTES +LICENSE + +GENERAL NOTES +============= + +Game data and patching: +----------------------- + +This source release does not contain any game data, the game data is still +covered by the original EULA and must be obeyed as usual. + +You must patch the game to the latest version. + +Note that RTCW is available from the Steam store at +http://store.steampowered.com/app/9010/ + +Linux note: due to the game CD containing only a Windows version of the game, +you must install and update the game using WINE to get the game data. + +Compiling on win32: +------------------- + +A Visual C++ 2008 project is provided in src\wolf.sln. +The solution file is compatible with the Express release of Visual C++. + +In order to test your binaries, backup and remove Main\mp_bin.pk3, +then replace WolfMP.exe, Main\qagame_mp_x86.dll, Main\cgame_mp_x86.dll, Main\ui_mp_x86.dll. +When starting the server make sure to specify Pure Server: No (sv_pure 0). + +Compiling on GNU/Linux x86: +--------------------------- + +Go to the src/unix directory, and run the cons script +(cons was an old precursor to scons which we had been using in earlier projects) + +Run ./cons -h to review build options. Use ./cons -- release to compile in release mode. + +If any problems occur, consult the internet. + +Other platforms, updated source code, security issues: +------------------------------------------------------ + +If you have obtained this source code several weeks after the time of release +(August 2010), it is likely that you can find modified and improved +versions of the engine in various open source projects across the internet. +Depending what is your interest with the source code, those may be a better +starting point. + + +LICENSE +======= + +See COPYING.txt for the GNU GENERAL PUBLIC LICENSE + +ADDITIONAL TERMS: The Return to Castle Wolfenstein multiplayer GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU GPL which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +EXCLUDED CODE: The code described below and contained in the Return to Castle Wolfenstein multiplayer GPL Source Code release is not part of the Program covered by the GPL and is expressly excluded from its terms. You are solely responsible for obtaining from the copyright holder a license for such code and complying with the applicable license terms. + +IO on .zip files using portions of zlib +--------------------------------------------------------------------------- +lines file(s) +4301 src/qcommon/unzip.c +Copyright (C) 1998 Gilles Vollant +zlib is Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + + 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. + +MD4 Message-Digest Algorithm +----------------------------------------------------------------------------- +lines file(s) +289 src/qcommon/md4.c +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. + +License to copy and use this software is granted provided that it is identified +as the <93>RSA Data Security, Inc. MD4 Message-Digest Algorithm<94> in all mater +ial mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such work +s are identified as <93>derived from the RSA Data Security, Inc. MD4 Message-Dig +est Algorithm<94> in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchanta +bility of this software or the suitability of this software for any particular p +urpose. It is provided <93>as is<94> without express or implied warranty of any +kind. + +JPEG library +----------------------------------------------------------------------------- +src/jpeg-6 +Copyright (C) 1991-1995, Thomas G. Lane + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +NOTE: unfortunately the README that came with our copy of the library has +been lost, so the one from release 6b is included instead. There are a few +'glue type' modifications to the library to make it easier to use from +the engine, but otherwise the dependency can be easily cleaned up to a +better release of the library. + diff --git a/src/botai/ai_chat.c b/src/botai/ai_chat.c new file mode 100644 index 0000000..ade057d --- /dev/null +++ b/src/botai/ai_chat.c @@ -0,0 +1,1313 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_chat.c + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +#include "../game/g_local.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../botai/botai.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + + +/* +================== +BotNumActivePlayers +================== +*/ +int BotNumActivePlayers( void ) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + num = 0; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + //if no config string or no name + if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) { + continue; + } + //skip spectators + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + continue; + } + // + num++; + } + return num; +} + +/* +================== +BotIsFirstInRankings +================== +*/ +int BotIsFirstInRankings( bot_state_t *bs ) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclients; + playerState_t ps; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + score = bs->cur_ps.persistant[PERS_SCORE]; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + //if no config string or no name + if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) { + continue; + } + //skip spectators + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + continue; + } + // + BotAI_GetClientState( i, &ps ); + if ( score < ps.persistant[PERS_SCORE] ) { + return qfalse; + } + } + return qtrue; +} + +/* +================== +BotIsLastInRankings +================== +*/ +int BotIsLastInRankings( bot_state_t *bs ) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclients; + playerState_t ps; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + score = bs->cur_ps.persistant[PERS_SCORE]; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + //if no config string or no name + if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) { + continue; + } + //skip spectators + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + continue; + } + // + BotAI_GetClientState( i, &ps ); + if ( score > ps.persistant[PERS_SCORE] ) { + return qfalse; + } + } + return qtrue; +} + +/* +================== +BotFirstClientInRankings +================== +*/ +char *BotFirstClientInRankings( void ) { + int i, bestscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclients; + playerState_t ps; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + bestscore = -999999; + bestclient = 0; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + //if no config string or no name + if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) { + continue; + } + //skip spectators + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + continue; + } + // + BotAI_GetClientState( i, &ps ); + if ( ps.persistant[PERS_SCORE] > bestscore ) { + bestscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName( bestclient, name, 32 ); + return name; +} + +/* +================== +BotLastClientInRankings +================== +*/ +char *BotLastClientInRankings( void ) { + int i, worstscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclients; + playerState_t ps; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + worstscore = 999999; + bestclient = 0; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + //if no config string or no name + if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) { + continue; + } + //skip spectators + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + continue; + } + // + BotAI_GetClientState( i, &ps ); + if ( ps.persistant[PERS_SCORE] < worstscore ) { + worstscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName( bestclient, name, 32 ); + return name; +} + +/* +================== +BotRandomOpponentName +================== +*/ +char *BotRandomOpponentName( bot_state_t *bs ) { + int i, count; + char buf[MAX_INFO_STRING]; + int opponents[MAX_CLIENTS], numopponents; + static int maxclients; + static char name[32]; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + numopponents = 0; + opponents[0] = 0; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + if ( i == bs->client ) { + continue; + } + // + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + //if no config string or no name + if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) { + continue; + } + //skip spectators + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + continue; + } + //skip team mates + if ( BotSameTeam( bs, i ) ) { + continue; + } + // + opponents[numopponents] = i; + numopponents++; + } + count = random() * numopponents; + for ( i = 0; i < numopponents; i++ ) { + count--; + if ( count <= 0 ) { + EasyClientName( opponents[i], name, sizeof( name ) ); + return name; + } + } + EasyClientName( opponents[0], name, sizeof( name ) ); + return name; +} + +/* +================== +BotMapTitle +================== +*/ + +char *BotMapTitle( void ) { + char info[1024]; + static char mapname[128]; + + trap_GetServerinfo( info, sizeof( info ) ); + + strncpy( mapname, Info_ValueForKey( info, "mapname" ), sizeof( mapname ) - 1 ); + mapname[sizeof( mapname ) - 1] = '\0'; + + return mapname; +} + + +/* +================== +BotWeaponNameForMeansOfDeath +================== +*/ + +char *BotWeaponNameForMeansOfDeath( int mod ) { + switch ( mod ) { + case MOD_SHOTGUN: return "Shotgun"; + case MOD_GAUNTLET: return "Gauntlet"; + case MOD_MACHINEGUN: return "Machinegun"; + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: return "Grenade Launcher"; + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: return "Rocket Launcher"; + case MOD_RAILGUN: return "Railgun"; + case MOD_LIGHTNING: return "Lightning Gun"; + case MOD_BFG: + case MOD_BFG_SPLASH: return "BFG10K"; + case MOD_GRAPPLE: return "Grapple"; + default: return "[unknown weapon]"; + } +} + +/* +================== +BotRandomWeaponName +================== +*/ +char *BotRandomWeaponName( void ) { + int rnd; + + rnd = random() * 8.9; + switch ( rnd ) { + case 0: return "Gauntlet"; + case 1: return "Shotgun"; + case 2: return "Machinegun"; + case 3: return "Grenade Launcher"; + case 4: return "Rocket Launcher"; + case 5: return "Plasmagun"; + case 6: return "Railgun"; + case 7: return "Lightning Gun"; + default: return "BFG10K"; + } +} + +/* +================== +BotValidChatPosition +================== +*/ +int BotValidChatPosition( bot_state_t *bs ) { + vec3_t point, start, end, mins, maxs; + bsp_trace_t trace; + + //if the bot is dead all positions are valid + if ( BotIsDead( bs ) ) { + return qtrue; + } + //must be on the ground + //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; + //do not chat if in lava or slime + VectorCopy( bs->origin, point ); + point[2] -= 24; + if ( trap_PointContents( point,bs->entitynum ) & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) { + return qfalse; + } + //do not chat if under water + VectorCopy( bs->origin, point ); + point[2] += 32; + if ( trap_PointContents( point,bs->entitynum ) & MASK_WATER ) { + return qfalse; + } + //must be standing on the world entity + VectorCopy( bs->origin, start ); + VectorCopy( bs->origin, end ); + start[2] += 1; + end[2] -= 10; + trap_AAS_PresenceTypeBoundingBox( PRESENCE_CROUCH, mins, maxs ); + BotAI_Trace( &trace, start, mins, maxs, end, bs->client, MASK_SOLID ); + if ( trace.ent != ENTITYNUM_WORLD ) { + return qfalse; + } + //the bot is in a position where it can chat + return qtrue; +} + +/* +================== +BotChat_EnterGame +================== +*/ +int BotChat_EnterGame( bot_state_t *bs ) { + char name[32]; + float rnd; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1 ); + if ( !bot_fastchat.integer ) { + if ( random() > rnd ) { + return qfalse; + } + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + if ( !BotValidChatPosition( bs ) ) { + return qfalse; + } + BotAI_BotInitialChat( bs, "game_enter", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL ); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_ExitGame +================== +*/ +int BotChat_ExitGame( bot_state_t *bs ) { + char name[32]; + float rnd; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1 ); + if ( !bot_fastchat.integer ) { + if ( random() > rnd ) { + return qfalse; + } + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + // + BotAI_BotInitialChat( bs, "game_exit", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL ); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_StartLevel +================== +*/ +int BotChat_StartLevel( bot_state_t *bs ) { + char name[32]; + float rnd; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( BotIsObserver( bs ) ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1 ); + if ( !bot_fastchat.integer ) { + if ( random() > rnd ) { + return qfalse; + } + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + BotAI_BotInitialChat( bs, "level_start", + EasyClientName( bs->client, name, 32 ), // 0 + NULL ); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_EndLevel +================== +*/ +int BotChat_EndLevel( bot_state_t *bs ) { + char name[32]; + float rnd; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( BotIsObserver( bs ) ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1 ); + if ( !bot_fastchat.integer ) { + if ( random() > rnd ) { + return qfalse; + } + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + // + if ( BotIsFirstInRankings( bs ) ) { + BotAI_BotInitialChat( bs, "level_end_victory", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + "[invalid var]", // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL ); + } else if ( BotIsLastInRankings( bs ) ) { + BotAI_BotInitialChat( bs, "level_end_lose", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + BotFirstClientInRankings(), // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL ); + } else { + BotAI_BotInitialChat( bs, "level_end", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL ); + } + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Death +================== +*/ +int BotChat_Death( bot_state_t *bs ) { + char name[32]; + float rnd; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1 ); + //if fast chatting is off + if ( !bot_fastchat.integer ) { + if ( random() > rnd ) { + return qfalse; + } + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + // + if ( bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS ) { + EasyClientName( bs->lastkilledby, name, 32 ); + } else { + strcpy( name, "[world]" ); + } + // + if ( TeamPlayIsOn() && BotSameTeam( bs, bs->lastkilledby ) ) { + if ( bs->lastkilledby == bs->client ) { + return qfalse; + } + BotAI_BotInitialChat( bs, "death_teammate", name, NULL ); + bs->chatto = CHAT_TEAM; + } else + { + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + // + if ( bs->botdeathtype == MOD_WATER ) { + BotAI_BotInitialChat( bs, "death_drown", BotRandomOpponentName( bs ), NULL ); + } else if ( bs->botdeathtype == MOD_SLIME ) { + BotAI_BotInitialChat( bs, "death_slime", BotRandomOpponentName( bs ), NULL ); + } else if ( bs->botdeathtype == MOD_LAVA ) { + BotAI_BotInitialChat( bs, "death_lava", BotRandomOpponentName( bs ), NULL ); + } else if ( bs->botdeathtype == MOD_FALLING ) { + BotAI_BotInitialChat( bs, "death_cratered", BotRandomOpponentName( bs ), NULL ); + } else if ( bs->botsuicide || //all other suicides by own weapon + bs->botdeathtype == MOD_CRUSH || + bs->botdeathtype == MOD_SUICIDE || + bs->botdeathtype == MOD_TARGET_LASER || + bs->botdeathtype == MOD_TRIGGER_HURT || + bs->botdeathtype == MOD_UNKNOWN ) { + BotAI_BotInitialChat( bs, "death_suicide", BotRandomOpponentName( bs ), NULL ); + } else if ( bs->botdeathtype == MOD_TELEFRAG ) { + BotAI_BotInitialChat( bs, "death_telefrag", name, NULL ); + } else { + if ( ( bs->botdeathtype == MOD_GAUNTLET || + bs->botdeathtype == MOD_RAILGUN || + bs->botdeathtype == MOD_BFG || + bs->botdeathtype == MOD_BFG_SPLASH ) && random() < 0.5 ) { + + if ( bs->botdeathtype == MOD_GAUNTLET ) { + BotAI_BotInitialChat( bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + } else if ( bs->botdeathtype == MOD_RAILGUN ) { + BotAI_BotInitialChat( bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + } else { + BotAI_BotInitialChat( bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + } + } + //choose between insult and praise + else if ( random() < trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1 ) ) { + BotAI_BotInitialChat( bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + } else { + BotAI_BotInitialChat( bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + } + } + bs->chatto = CHAT_ALL; + } + bs->lastchat_time = trap_AAS_Time(); + return qtrue; +} + +/* +================== +BotChat_Kill +================== +*/ +int BotChat_Kill( bot_state_t *bs ) { + char name[32]; + float rnd; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1 ); + //if fast chat is off + if ( !bot_fastchat.integer ) { + if ( random() > rnd ) { + return qfalse; + } + } + if ( bs->lastkilledplayer == bs->client ) { + return qfalse; + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + if ( !BotValidChatPosition( bs ) ) { + return qfalse; + } + // + EasyClientName( bs->lastkilledplayer, name, 32 ); + // + bs->chatto = CHAT_ALL; + if ( TeamPlayIsOn() && BotSameTeam( bs, bs->lastkilledplayer ) ) { + BotAI_BotInitialChat( bs, "kill_teammate", name, NULL ); + bs->chatto = CHAT_TEAM; + } else + { + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + // + if ( bs->enemydeathtype == MOD_GAUNTLET ) { + BotAI_BotInitialChat( bs, "kill_gauntlet", name, NULL ); + } else if ( bs->enemydeathtype == MOD_RAILGUN ) { + BotAI_BotInitialChat( bs, "kill_rail", name, NULL ); + } else if ( bs->enemydeathtype == MOD_TELEFRAG ) { + BotAI_BotInitialChat( bs, "kill_telefrag", name, NULL ); + } + //choose between insult and praise + else if ( random() < trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1 ) ) { + BotAI_BotInitialChat( bs, "kill_insult", name, NULL ); + } else { + BotAI_BotInitialChat( bs, "kill_praise", name, NULL ); + } + } + bs->lastchat_time = trap_AAS_Time(); + return qtrue; +} + +/* +================== +BotChat_EnemySuicide +================== +*/ +int BotChat_EnemySuicide( bot_state_t *bs ) { + char name[32]; + float rnd; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + // + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1 ); + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + //if fast chat is off + if ( !bot_fastchat.integer ) { + if ( random() > rnd ) { + return qfalse; + } + } + if ( !BotValidChatPosition( bs ) ) { + return qfalse; + } + // + if ( bs->enemy >= 0 ) { + EasyClientName( bs->enemy, name, 32 ); + } else { strcpy( name, "" );} + BotAI_BotInitialChat( bs, "enemy_suicide", name, NULL ); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitTalking +================== +*/ +int BotChat_HitTalking( bot_state_t *bs ) { + char name[32], *weap; + int lasthurt_client; + float rnd; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if ( !lasthurt_client ) { + return qfalse; + } + if ( lasthurt_client == bs->client ) { + return qfalse; + } + // + if ( lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS ) { + return qfalse; + } + // + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1 ); + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + //if fast chat is off + if ( !bot_fastchat.integer ) { + if ( random() > rnd * 0.5 ) { + return qfalse; + } + } + if ( !BotValidChatPosition( bs ) ) { + return qfalse; + } + // + ClientName( g_entities[bs->client].client->lasthurt_client, name, sizeof( name ) ); + weap = BotWeaponNameForMeansOfDeath( g_entities[bs->client].client->lasthurt_client ); + // + BotAI_BotInitialChat( bs, "hit_talking", name, weap, NULL ); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoDeath +================== +*/ +int BotChat_HitNoDeath( bot_state_t *bs ) { + char name[32], *weap; + float rnd; + int lasthurt_client; + aas_entityinfo_t entinfo; + + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if ( !lasthurt_client ) { + return qfalse; + } + if ( lasthurt_client == bs->client ) { + return qfalse; + } + // + if ( lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS ) { + return qfalse; + } + // + if ( bot_nochat.integer ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1 ); + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + //if fast chat is off + if ( !bot_fastchat.integer ) { + if ( random() > rnd * 0.5 ) { + return qfalse; + } + } + if ( !BotValidChatPosition( bs ) ) { + return qfalse; + } + //if the enemy is visible + if ( BotEntityVisible( bs->client, bs->eye, bs->viewangles, 360, bs->enemy ) ) { + return qfalse; + } + // + BotEntityInfo( bs->enemy, &entinfo ); + if ( EntityIsShooting( &entinfo ) ) { + return qfalse; + } + // + ClientName( lasthurt_client, name, sizeof( name ) ); + weap = BotWeaponNameForMeansOfDeath( g_entities[bs->client].client->lasthurt_mod ); + // + BotAI_BotInitialChat( bs, "hit_nodeath", name, weap, NULL ); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoKill +================== +*/ +int BotChat_HitNoKill( bot_state_t *bs ) { + char name[32], *weap; + float rnd; + aas_entityinfo_t entinfo; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1 ); + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + //if fast chat is off + if ( !bot_fastchat.integer ) { + if ( random() > rnd * 0.5 ) { + return qfalse; + } + } + if ( !BotValidChatPosition( bs ) ) { + return qfalse; + } + //if the enemy is visible + if ( BotEntityVisible( bs->client, bs->eye, bs->viewangles, 360, bs->enemy ) ) { + return qfalse; + } + // + BotEntityInfo( bs->enemy, &entinfo ); + if ( EntityIsShooting( &entinfo ) ) { + return qfalse; + } + // + ClientName( bs->enemy, name, sizeof( name ) ); + weap = BotWeaponNameForMeansOfDeath( g_entities[bs->enemy].client->lasthurt_mod ); + // + BotAI_BotInitialChat( bs, "hit_nokill", name, weap, NULL ); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Random +================== +*/ +int BotChat_Random( bot_state_t *bs ) { + float rnd; + char name[32]; + + if ( bot_nochat.integer ) { + return qfalse; + } + if ( BotIsObserver( bs ) ) { + return qfalse; + } + if ( bs->lastchat_time > trap_AAS_Time() - 3 ) { + return qfalse; + } + //don't chat in teamplay + if ( TeamPlayIsOn() ) { + return qfalse; + } + //don't chat when doing something important :) + if ( bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_RUSHBASE ) { + return qfalse; + } + // + rnd = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1 ); + if ( random() > bs->thinktime * 0.1 ) { + return qfalse; + } + if ( !bot_fastchat.integer ) { + if ( random() > rnd ) { + return qfalse; + } + if ( random() > 0.25 ) { + return qfalse; + } + } + if ( BotNumActivePlayers() <= 1 ) { + return qfalse; + } + if ( !BotValidChatPosition( bs ) ) { + return qfalse; + } + // + if ( bs->lastkilledplayer == bs->client ) { + strcpy( name, BotRandomOpponentName( bs ) ); + } else { + EasyClientName( bs->lastkilledplayer, name, sizeof( name ) ); + } + // + if ( random() < trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1 ) ) { + BotAI_BotInitialChat( bs, "random_misc", + BotRandomOpponentName( bs ), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL ); + } else { + BotAI_BotInitialChat( bs, "random_insult", + BotRandomOpponentName( bs ), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL ); + } + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChatTime +================== +*/ +float BotChatTime( bot_state_t *bs ) { + int cpm; + + cpm = trap_Characteristic_BInteger( bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000 ); + + return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm; +} + +/* +================== +BotChatTest +================== +*/ +void BotChatTest( bot_state_t *bs ) { + + char name[32]; + char *weap; + int num, i; + + num = trap_BotNumInitialChats( bs->cs, "game_enter" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "game_enter", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "game_exit" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "game_exit", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "level_start" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "level_start", + EasyClientName( bs->client, name, 32 ), // 0 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "level_end_victory" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "level_end_victory", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "level_end_lose" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "level_end_lose", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "level_end" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "level_end", + EasyClientName( bs->client, name, 32 ), // 0 + BotRandomOpponentName( bs ), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + EasyClientName( bs->lastkilledby, name, sizeof( name ) ); + num = trap_BotNumInitialChats( bs->cs, "death_drown" ); + for ( i = 0; i < num; i++ ) + { + // + BotAI_BotInitialChat( bs, "death_drown", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_slime" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_slime", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_lava" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_lava", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_cratered" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_cratered", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_suicide" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_suicide", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_telefrag" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_telefrag", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_gauntlet" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_rail" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_bfg" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_insult" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "death_praise" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath( bs->botdeathtype ), // 1 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + // + EasyClientName( bs->lastkilledplayer, name, 32 ); + // + num = trap_BotNumInitialChats( bs->cs, "kill_gauntlet" ); + for ( i = 0; i < num; i++ ) + { + // + BotAI_BotInitialChat( bs, "kill_gauntlet", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "kill_rail" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "kill_rail", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "kill_telefrag" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "kill_telefrag", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "kill_insult" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "kill_insult", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "kill_praise" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "kill_praise", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "enemy_suicide" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "enemy_suicide", name, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + ClientName( g_entities[bs->client].client->lasthurt_client, name, sizeof( name ) ); + weap = BotWeaponNameForMeansOfDeath( g_entities[bs->client].client->lasthurt_client ); + num = trap_BotNumInitialChats( bs->cs, "hit_talking" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "hit_talking", name, weap, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "hit_nodeath" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "hit_nodeath", name, weap, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "hit_nokill" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "hit_nokill", name, weap, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + // + if ( bs->lastkilledplayer == bs->client ) { + strcpy( name, BotRandomOpponentName( bs ) ); + } else { + EasyClientName( bs->lastkilledplayer, name, sizeof( name ) ); + } + // + num = trap_BotNumInitialChats( bs->cs, "random_misc" ); + for ( i = 0; i < num; i++ ) + { + // + BotAI_BotInitialChat( bs, "random_misc", + BotRandomOpponentName( bs ), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + num = trap_BotNumInitialChats( bs->cs, "random_insult" ); + for ( i = 0; i < num; i++ ) + { + BotAI_BotInitialChat( bs, "random_insult", + BotRandomOpponentName( bs ), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } +} diff --git a/src/botai/ai_chat.h b/src/botai/ai_chat.h new file mode 100644 index 0000000..9ad2a23 --- /dev/null +++ b/src/botai/ai_chat.h @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_chat.h + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +// +int BotChat_EnterGame( bot_state_t *bs ); +// +int BotChat_ExitGame( bot_state_t *bs ); +// +int BotChat_StartLevel( bot_state_t *bs ); +// +int BotChat_EndLevel( bot_state_t *bs ); +// +int BotChat_HitTalking( bot_state_t *bs ); +// +int BotChat_HitNoDeath( bot_state_t *bs ); +// +int BotChat_HitNoKill( bot_state_t *bs ); +// +int BotChat_Death( bot_state_t *bs ); +// +int BotChat_Kill( bot_state_t *bs ); +// +int BotChat_EnemySuicide( bot_state_t *bs ); +// +int BotChat_Random( bot_state_t *bs ); +// time the selected chat takes to type in +float BotChatTime( bot_state_t *bs ); +// returns true if the bot can chat at the current position +int BotValidChatPosition( bot_state_t *bs ); +// test the initial bot chats +void BotChatTest( bot_state_t *bs ); + diff --git a/src/botai/ai_cmd.c b/src/botai/ai_cmd.c new file mode 100644 index 0000000..e9ccace --- /dev/null +++ b/src/botai/ai_cmd.c @@ -0,0 +1,1645 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_cmd.c + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +#include "../game/g_local.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../botai/botai.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + + +#ifdef DEBUG +/* +================== +BotPrintTeamGoal +================== +*/ +void BotPrintTeamGoal( bot_state_t *bs ) { + char netname[MAX_NETNAME]; + float t; + + ClientName( bs->client, netname, sizeof( netname ) ); + t = bs->teamgoal_time - trap_AAS_Time(); + switch ( bs->ltgtype ) { + case LTG_TEAMHELP: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t ); + break; + } + case LTG_TEAMACCOMPANY: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t ); + break; + } + case LTG_GETFLAG: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t ); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t ); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t ); + break; + } + case LTG_DEFENDKEYAREA: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t ); + break; + } + case LTG_GETITEM: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t ); + break; + } + case LTG_KILL: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t ); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t ); + break; + } + case LTG_PATROL: + { + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t ); + break; + } + default: + { + if ( bs->ctfroam_time > trap_AAS_Time() ) { + t = bs->ctfroam_time - trap_AAS_Time(); + BotAI_Print( PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t ); + } else { + BotAI_Print( PRT_MESSAGE, "%s: I've got a regular goal\n", netname ); + } + } + } +} +#endif //DEBUG + +/* +================== +BotGetItemTeamGoal + +FIXME: add stuff like "upper rocket launcher" +"the rl near the railgun", "lower grenade launcher" etc. +================== +*/ +int BotGetItemTeamGoal( char *goalname, bot_goal_t *goal ) { + int i; + + if ( !strlen( goalname ) ) { + return qfalse; + } + i = -1; + do { + i = trap_BotGetLevelItemGoal( i, goalname, goal ); + if ( i > 0 ) { // && !AvoidGoalTime(&bs->gs, goal.number)) + return qtrue; + } + } while ( i > 0 ); + return qfalse; +} + +/* +================== +BotGetMessageTeamGoal +================== +*/ +int BotGetMessageTeamGoal( bot_state_t *bs, char *goalname, bot_goal_t *goal ) { + bot_waypoint_t *cp; + + if ( BotGetItemTeamGoal( goalname, goal ) ) { + return qtrue; + } + + cp = BotFindWayPoint( bs->checkpoints, goalname ); + if ( cp ) { + memcpy( goal, &cp->goal, sizeof( bot_goal_t ) ); + return qtrue; + } + return qfalse; +} + +/* +================== +BotGetTime +================== +*/ +float BotGetTime( bot_match_t *match ) { + bot_match_t timematch; + char timestring[MAX_MESSAGE_SIZE]; + float t; + + //if the matched string has a time + if ( match->subtype & ST_TIME ) { + //get the time string + trap_BotMatchVariable( match, TIME, timestring, MAX_MESSAGE_SIZE ); + //match it to find out if the time is in seconds or minutes + if ( trap_BotFindMatch( timestring, &timematch, MTCONTEXT_TIME ) ) { + if ( timematch.type == MSG_FOREVER ) { + t = 99999999; + } else { + trap_BotMatchVariable( &timematch, TIME, timestring, MAX_MESSAGE_SIZE ); + if ( timematch.type == MSG_MINUTES ) { + t = atof( timestring ) * 60; + } else if ( timematch.type == MSG_SECONDS ) { + t = atof( timestring ); + } else { t = 0;} + } + //if there's a valid time + if ( t > 0 ) { + return trap_AAS_Time() + t; + } + } + } + return 0; +} + +/* +================== +FindClientByName +================== +*/ +int FindClientByName( char *name ) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + ClientName( i, buf, sizeof( buf ) ); + if ( !Q_stricmp( buf, name ) ) { + return i; + } + } + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + ClientName( i, buf, sizeof( buf ) ); + if ( stristr( buf, name ) ) { + return i; + } + } + return -1; +} + +/* +================== +FindEnemyByName +================== +*/ +int FindEnemyByName( bot_state_t *bs, char *name ) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + if ( BotSameTeam( bs, i ) ) { + continue; + } + ClientName( i, buf, sizeof( buf ) ); + if ( !Q_stricmp( buf, name ) ) { + return i; + } + } + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + if ( BotSameTeam( bs, i ) ) { + continue; + } + ClientName( i, buf, sizeof( buf ) ); + if ( stristr( buf, name ) ) { + return i; + } + } + return -1; +} + +/* +================== +NumPlayersOnSameTeam +================== +*/ +int NumPlayersOnSameTeam( bot_state_t *bs ) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + num = 0; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, MAX_INFO_STRING ); + if ( strlen( buf ) ) { + if ( BotSameTeam( bs, i + 1 ) ) { + num++; + } + } + } + return num; +} + +/* +================== +TeamPlayIsOn +================== +*/ +int BotGetPatrolWaypoints( bot_state_t *bs, bot_match_t *match ) { + char keyarea[MAX_MESSAGE_SIZE]; + int patrolflags; + bot_waypoint_t *wp, *newwp, *newpatrolpoints; + bot_match_t keyareamatch; + bot_goal_t goal; + + newpatrolpoints = NULL; + patrolflags = 0; + // + trap_BotMatchVariable( match, KEYAREA, keyarea, MAX_MESSAGE_SIZE ); + // + while ( 1 ) { + if ( !trap_BotFindMatch( keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA ) ) { + trap_EA_SayTeam( bs->client, "what do you say?" ); + BotFreeWaypoints( newpatrolpoints ); + bs->patrolpoints = NULL; + return qfalse; + } + trap_BotMatchVariable( &keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE ); + if ( !BotGetMessageTeamGoal( bs, keyarea, &goal ) ) { + //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + BotFreeWaypoints( newpatrolpoints ); + bs->patrolpoints = NULL; + return qfalse; + } + //create a new waypoint + newwp = BotCreateWayPoint( keyarea, goal.origin, goal.areanum ); + //add the waypoint to the patrol points + newwp->next = NULL; + for ( wp = newpatrolpoints; wp && wp->next; wp = wp->next ) ; + if ( !wp ) { + newpatrolpoints = newwp; + newwp->prev = NULL; + } else { + wp->next = newwp; + newwp->prev = wp; + } + // + if ( keyareamatch.subtype & ST_BACK ) { + patrolflags = PATROL_LOOP; + break; + } else if ( keyareamatch.subtype & ST_REVERSE ) { + patrolflags = PATROL_REVERSE; + break; + } else if ( keyareamatch.subtype & ST_MORE ) { + trap_BotMatchVariable( &keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE ); + } else { + break; + } + } + // + if ( !newpatrolpoints || !newpatrolpoints->next ) { + trap_EA_SayTeam( bs->client, "I need more key points to patrol\n" ); + BotFreeWaypoints( newpatrolpoints ); + newpatrolpoints = NULL; + return qfalse; + } + // + BotFreeWaypoints( bs->patrolpoints ); + bs->patrolpoints = newpatrolpoints; + // + bs->curpatrolpoint = bs->patrolpoints; + bs->patrolflags = patrolflags; + // + return qtrue; +} + +/* +================== +BotAddressedToBot +================== +*/ +int BotAddressedToBot( bot_state_t *bs, bot_match_t *match ) { + char addressedto[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char name[MAX_MESSAGE_SIZE]; + char botname[128]; + int client; + bot_match_t addresseematch; + + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + client = ClientFromName( netname ); + if ( client < 0 ) { + return qfalse; + } + if ( !BotSameTeam( bs, client ) ) { + return qfalse; + } + //if the message is addressed to someone + if ( match->subtype & ST_ADDRESSED ) { + trap_BotMatchVariable( match, ADDRESSEE, addressedto, sizeof( addressedto ) ); + //the name of this bot + ClientName( bs->client, botname, 128 ); + // + while ( trap_BotFindMatch( addressedto, &addresseematch, MTCONTEXT_ADDRESSEE ) ) { + if ( addresseematch.type == MSG_EVERYONE ) { + return qtrue; + } else if ( addresseematch.type == MSG_MULTIPLENAMES ) { + trap_BotMatchVariable( &addresseematch, TEAMMATE, name, sizeof( name ) ); + if ( strlen( name ) ) { + if ( stristr( botname, name ) ) { + return qtrue; + } + if ( stristr( bs->subteam, name ) ) { + return qtrue; + } + } + trap_BotMatchVariable( &addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE ); + } else { + trap_BotMatchVariable( &addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE ); + if ( strlen( name ) ) { + if ( stristr( botname, name ) ) { + return qtrue; + } + if ( stristr( bs->subteam, name ) ) { + return qtrue; + } + } + break; + } + } + //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); + //trap_EA_Say(bs->client, buf); + return qfalse; + } else { + //make sure not everyone reacts to this message + if ( random() > (float ) 1.0 / ( NumPlayersOnSameTeam( bs ) - 1 ) ) { + return qfalse; + } + } + return qtrue; +} + +/* +================== +BotGPSToPosition +================== +*/ +int BotGPSToPosition( char *buf, vec3_t position ) { + int i, j = 0; + int num, sign; + + for ( i = 0; i < 3; i++ ) { + num = 0; + while ( buf[j] == ' ' ) j++; + if ( buf[j] == '-' ) { + j++; + sign = -1; + } else { + sign = 1; + } + while ( buf[j] ) { + if ( buf[j] >= '0' && buf[j] <= '9' ) { + num = num * 10 + buf[j] - '0'; + j++; + } else { + j++; + break; + } + } + BotAI_Print( PRT_MESSAGE, "%d\n", sign * num ); + position[i] = (float) sign * num; + } + return qtrue; +} + +/* +================== +BotMatch_HelpAccompany +================== +*/ +void BotMatch_HelpAccompany( bot_state_t *bs, bot_match_t *match ) { + int client, other, areanum; + char teammate[MAX_MESSAGE_SIZE], netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + bot_match_t teammatematch; + aas_entityinfo_t entinfo; + + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //get the team mate name + trap_BotMatchVariable( match, TEAMMATE, teammate, sizeof( teammate ) ); + //get the client to help + if ( trap_BotFindMatch( teammate, &teammatematch, MTCONTEXT_TEAMMATE ) && + //if someone asks for him or herself + teammatematch.type == MSG_ME ) { + //get the netname + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + client = ClientFromName( netname ); + other = qfalse; + } else { + //asked for someone else + client = FindClientByName( teammate ); + //if this is the bot self + if ( client == bs->client ) { + other = qfalse; + } else if ( !BotSameTeam( bs, client ) ) { + //FIXME: say "I don't help the enemy" + return; + } else { + other = qtrue; + } + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if ( client < 0 ) { + if ( other ) { + BotAI_BotInitialChat( bs, "whois", teammate, NULL ); + } else { BotAI_BotInitialChat( bs, "whois", netname, NULL );} + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + return; + } + //don't help or accompany yourself + if ( client == bs->client ) { + return; + } + // + bs->teamgoal.entitynum = -1; + BotEntityInfo( client, &entinfo ); + //if info is valid (in PVS) + if ( entinfo.valid ) { + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy( entinfo.origin, bs->teamgoal.origin ); + VectorSet( bs->teamgoal.mins, -8, -8, -8 ); + VectorSet( bs->teamgoal.maxs, 8, 8, 8 ); + } + } + //if no teamgoal yet + if ( bs->teamgoal.entitynum < 0 ) { + //if near an item + if ( match->subtype & ST_NEARITEM ) { + //get the match variable + trap_BotMatchVariable( match, ITEM, itemname, sizeof( itemname ) ); + // + if ( !BotGetMessageTeamGoal( bs, itemname, &bs->teamgoal ) ) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + } + } + // + if ( bs->teamgoal.entitynum < 0 ) { + if ( other ) { + BotAI_BotInitialChat( bs, "whereis", teammate, NULL ); + } else { BotAI_BotInitialChat( bs, "whereareyou", netname, NULL );} + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + return; + } + //the team mate + bs->teammate = client; + //last time the team mate was assumed visible + bs->teammatevisible_time = trap_AAS_Time(); + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //get the team goal time + bs->teamgoal_time = BotGetTime( match ); + //set the ltg type + if ( match->type == MSG_HELP ) { + bs->ltgtype = LTG_TEAMHELP; + if ( !bs->teamgoal_time ) { + bs->teamgoal_time = trap_AAS_Time() + TEAM_HELP_TIME; + } + } else { + bs->ltgtype = LTG_TEAMACCOMPANY; + if ( !bs->teamgoal_time ) { + bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME; + } + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + } +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +/* +================== +BotMatch_DefendKeyArea +================== +*/ +void BotMatch_DefendKeyArea( bot_state_t *bs, bot_match_t *match ) { + char itemname[MAX_MESSAGE_SIZE]; + + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //get the match variable + trap_BotMatchVariable( match, KEYAREA, itemname, sizeof( itemname ) ); + // + if ( !BotGetMessageTeamGoal( bs, itemname, &bs->teamgoal ) ) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = BotGetTime( match ); + //set the team goal time + if ( !bs->teamgoal_time ) { + bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME; + } + //away from defending + bs->defendaway_time = 0; +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +/* +================== +BotMatch_GetItem +================== +*/ +void BotMatch_GetItem( bot_state_t *bs, bot_match_t *match ) { + char itemname[MAX_MESSAGE_SIZE]; + + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //get the match variable + trap_BotMatchVariable( match, ITEM, itemname, sizeof( itemname ) ); + // + if ( !BotGetMessageTeamGoal( bs, itemname, &bs->teamgoal ) ) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETITEM; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + TEAM_GETITEM_TIME; +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +/* +================== +BotMatch_Camp +================== +*/ +void BotMatch_Camp( bot_state_t *bs, bot_match_t *match ) { + int client, areanum; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + aas_entityinfo_t entinfo; + + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + // + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + //asked for someone else + client = FindClientByName( netname ); + //if there's no valid client with this name + if ( client < 0 ) { + BotAI_BotInitialChat( bs, "whois", netname, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + return; + } + //get the match variable + trap_BotMatchVariable( match, KEYAREA, itemname, sizeof( itemname ) ); + //in CTF it could be the base + if ( match->subtype & ST_THERE ) { + //camp at the spot the bot is currently standing + bs->teamgoal.entitynum = bs->entitynum; + bs->teamgoal.areanum = bs->areanum; + VectorCopy( bs->origin, bs->teamgoal.origin ); + VectorSet( bs->teamgoal.mins, -8, -8, -8 ); + VectorSet( bs->teamgoal.maxs, 8, 8, 8 ); + } else if ( match->subtype & ST_HERE ) { + //if this is the bot self + if ( client == bs->client ) { + return; + } + // + bs->teamgoal.entitynum = -1; + BotEntityInfo( client, &entinfo ); + //if info is valid (in PVS) + if ( entinfo.valid ) { + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + //NOTE: just cheat and assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy( entinfo.origin, bs->teamgoal.origin ); + VectorSet( bs->teamgoal.mins, -8, -8, -8 ); + VectorSet( bs->teamgoal.maxs, 8, 8, 8 ); + //} + } + } + //if the other is not visible + if ( bs->teamgoal.entitynum < 0 ) { + BotAI_BotInitialChat( bs, "whereareyou", netname, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + return; + } + } else if ( !BotGetMessageTeamGoal( bs, itemname, &bs->teamgoal ) ) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = BotGetTime( match ); + //set the team goal time + if ( !bs->teamgoal_time ) { + bs->teamgoal_time = trap_AAS_Time() + TEAM_CAMP_TIME; + } + //the teammate that requested the camping + bs->teammate = client; + //not arrived yet + bs->arrive_time = 0; + // +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +/* +================== +BotMatch_Patrol +================== +*/ +void BotMatch_Patrol( bot_state_t *bs, bot_match_t *match ) { + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //get the patrol waypoints + if ( !BotGetPatrolWaypoints( bs, match ) ) { + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_PATROL; + //get the team goal time + bs->teamgoal_time = BotGetTime( match ); + //set the team goal time if not set already + if ( !bs->teamgoal_time ) { + bs->teamgoal_time = trap_AAS_Time() + TEAM_PATROL_TIME; + } + // +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +/* +================== +BotMatch_GetFlag +================== +*/ +void BotMatch_GetFlag( bot_state_t *bs, bot_match_t *match ) { + //if not in CTF mode + if ( gametype != GT_CTF || !ctf_redflag.areanum || !ctf_blueflag.areanum ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME; +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +/* +================== +BotMatch_RushBase +================== +*/ +void BotMatch_RushBase( bot_state_t *bs, bot_match_t *match ) { + //if not in CTF mode + if ( gametype != GT_CTF || !ctf_redflag.areanum || !ctf_blueflag.areanum ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RUSHBASE; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + + +/* +================== +BotMatch_ReturnFlag +================== +*/ +void BotMatch_ReturnFlag( bot_state_t *bs, bot_match_t *match ) { + //if not in CTF mode + if ( gametype != GT_CTF ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +/* +================== +BotMatch_JoinSubteam +================== +*/ +void BotMatch_JoinSubteam( bot_state_t *bs, bot_match_t *match ) { + char teammate[MAX_MESSAGE_SIZE]; + + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //get the sub team name + trap_BotMatchVariable( match, TEAMNAME, teammate, MAX_MESSAGE_SIZE ); + //set the sub team name + strncpy( bs->subteam, teammate, 32 ); + bs->subteam[31] = '\0'; + // + BotAI_BotInitialChat( bs, "joinedteam", teammate, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_LeaveSubteam( bot_state_t *bs, bot_match_t *match ) { + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + // + if ( strlen( bs->subteam ) ) { + BotAI_BotInitialChat( bs, "leftteam", bs->subteam, NULL ); + } //end if + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + strcpy( bs->subteam, "" ); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_WhichTeam( bot_state_t *bs, bot_match_t *match ) { + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + // + if ( strlen( bs->subteam ) ) { + BotAI_BotInitialChat( bs, "inteam", bs->subteam, NULL ); + } else { + BotAI_BotInitialChat( bs, "noteam", NULL ); + } + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); +} + +/* +================== +BotMatch_CheckPoint +================== +*/ +void BotMatch_CheckPoint( bot_state_t *bs, bot_match_t *match ) { + int areanum; + char buf[MAX_MESSAGE_SIZE]; + vec3_t position; + bot_waypoint_t *cp; + + if ( !TeamPlayIsOn() ) { + return; + } + // + trap_BotMatchVariable( match, POSITION, buf, MAX_MESSAGE_SIZE ); + VectorClear( position ); + //BotGPSToPosition(buf, position); + sscanf( buf, "%f %f %f", &position[0], &position[1], &position[2] ); + position[2] += 0.5; + areanum = BotPointAreaNum( position ); + if ( !areanum ) { + if ( BotAddressedToBot( bs, match ) ) { + BotAI_BotInitialChat( bs, "checkpoint_invalid", NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + } + return; + } + // + trap_BotMatchVariable( match, NAME, buf, MAX_MESSAGE_SIZE ); + //check if there already exists a checkpoint with this name + cp = BotFindWayPoint( bs->checkpoints, buf ); + if ( cp ) { + if ( cp->next ) { + cp->next->prev = cp->prev; + } + if ( cp->prev ) { + cp->prev->next = cp->next; + } else { bs->checkpoints = cp->next;} + cp->inuse = qfalse; + } + //create a new check point + cp = BotCreateWayPoint( buf, position, areanum ); + //add the check point to the bot's known chech points + cp->next = bs->checkpoints; + if ( bs->checkpoints ) { + bs->checkpoints->prev = cp; + } + bs->checkpoints = cp; + // + if ( BotAddressedToBot( bs, match ) ) { + Com_sprintf( buf, sizeof( buf ), "%1.0f %1.0f %1.0f", cp->goal.origin[0], + cp->goal.origin[1], + cp->goal.origin[2] ); + + BotAI_BotInitialChat( bs, "checkpoint_confirm", cp->name, buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + } +} + +/* +================== +BotMatch_FormationSpace +================== +*/ +void BotMatch_FormationSpace( bot_state_t *bs, bot_match_t *match ) { + char buf[MAX_MESSAGE_SIZE]; + float space; + + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + // + trap_BotMatchVariable( match, NUMBER, buf, MAX_MESSAGE_SIZE ); + //if it's the distance in feet + if ( match->subtype & ST_FEET ) { + space = 0.3048 * 32 * atof( buf ); + } + //else it's in meters + else {space = 32 * atof( buf );} + //check if the formation intervening space is valid + if ( space < 48 || space > 500 ) { + space = 100; + } + bs->formation_dist = space; +} + +/* +================== +BotMatch_Dismiss +================== +*/ +void BotMatch_Dismiss( bot_state_t *bs, bot_match_t *match ) { + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + // + bs->ltgtype = 0; + bs->lead_time = 0; + // + BotAI_BotInitialChat( bs, "dismissed", NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); +} + +/* +================== +BotMatch_StartTeamLeaderShip +================== +*/ +void BotMatch_StartTeamLeaderShip( bot_state_t *bs, bot_match_t *match ) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + + if ( !TeamPlayIsOn() ) { + return; + } + //if chats for him or herself + if ( match->subtype & ST_I ) { + //get the team mate that will be the team leader + trap_BotMatchVariable( match, NETNAME, teammate, sizeof( teammate ) ); + strncpy( bs->teamleader, teammate, sizeof( bs->teamleader ) ); + bs->teamleader[sizeof( bs->teamleader )] = '\0'; + } + //chats for someone else + else { + //get the team mate that will be the team leader + trap_BotMatchVariable( match, TEAMMATE, teammate, sizeof( teammate ) ); + client = FindClientByName( teammate ); + if ( client >= 0 ) { + ClientName( client, bs->teamleader, sizeof( bs->teamleader ) ); + } + } +} + +/* +================== +BotMatch_StopTeamLeaderShip +================== +*/ +void BotMatch_StopTeamLeaderShip( bot_state_t *bs, bot_match_t *match ) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + + if ( !TeamPlayIsOn() ) { + return; + } + //get the team mate that stops being the team leader + trap_BotMatchVariable( match, TEAMMATE, teammate, sizeof( teammate ) ); + //if chats for him or herself + if ( match->subtype & ST_I ) { + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + client = FindClientByName( netname ); + } + //chats for someone else + else { + client = FindClientByName( teammate ); + } //end else + if ( client >= 0 ) { + if ( !Q_stricmp( bs->teamleader, ClientName( client, netname, sizeof( netname ) ) ) ) { + bs->teamleader[0] = '\0'; + } + } +} + +/* +================== +BotMatch_WhoIsTeamLeader +================== +*/ +void BotMatch_WhoIsTeamLeader( bot_state_t *bs, bot_match_t *match ) { + char netname[MAX_MESSAGE_SIZE]; + + if ( !TeamPlayIsOn() ) { + return; + } + + ClientName( bs->client, netname, sizeof( netname ) ); + //if this bot IS the team leader + if ( !Q_stricmp( netname, bs->teamleader ) ) { + trap_EA_SayTeam( bs->client, "I'm the team leader\n" ); + } +} + +/* +================== +BotMatch_WhatAreYouDoing +================== +*/ +void BotMatch_WhatAreYouDoing( bot_state_t *bs, bot_match_t *match ) { + char netname[MAX_MESSAGE_SIZE]; + char goalname[MAX_MESSAGE_SIZE]; + + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + // + switch ( bs->ltgtype ) { + case LTG_TEAMHELP: + { + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + EasyClientName( bs->teammate, netname, MAX_MESSAGE_SIZE ); + BotAI_BotInitialChat( bs, "helping", netname, NULL ); + break; + } + case LTG_TEAMACCOMPANY: + { + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + EasyClientName( bs->teammate, netname, MAX_MESSAGE_SIZE ); + BotAI_BotInitialChat( bs, "accompanying", netname, NULL ); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName( bs->teamgoal.number, goalname, sizeof( goalname ) ); + BotAI_BotInitialChat( bs, "defending", goalname, NULL ); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName( bs->teamgoal.number, goalname, sizeof( goalname ) ); + BotAI_BotInitialChat( bs, "gettingitem", goalname, NULL ); + break; + } + case LTG_KILL: + { + ClientName( bs->teamgoal.entitynum, netname, sizeof( netname ) ); + BotAI_BotInitialChat( bs, "killing", netname, NULL ); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_BotInitialChat( bs, "camping", NULL ); + break; + } + case LTG_PATROL: + { + BotAI_BotInitialChat( bs, "patrolling", NULL ); + break; + } + case LTG_GETFLAG: + { + BotAI_BotInitialChat( bs, "capturingflag", NULL ); + break; + } + case LTG_RUSHBASE: + { + BotAI_BotInitialChat( bs, "rushingbase", NULL ); + break; + } + case LTG_RETURNFLAG: + { + BotAI_BotInitialChat( bs, "returningflag", NULL ); + break; + } + default: + { + BotAI_BotInitialChat( bs, "roaming", NULL ); + break; + } + } + //chat what the bot is doing + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); +} + +/* +================== +BotMatch_WhatIsMyCommand +================== +*/ +void BotMatch_WhatIsMyCommand( bot_state_t *bs, bot_match_t *match ) { + char netname[MAX_NETNAME]; + + ClientName( bs->client, netname, sizeof( netname ) ); + if ( Q_stricmp( netname, bs->teamleader ) != 0 ) { + return; + } + bs->forceorders = qtrue; +} + +/* +================== +BotNearestVisibleItem +================== +*/ +float BotNearestVisibleItem( bot_state_t *bs, char *itemname, bot_goal_t *goal ) { + int i; + char name[64]; + bot_goal_t tmpgoal; + float dist, bestdist; + vec3_t dir; + bsp_trace_t trace; + + bestdist = 999999; + i = -1; + do { + i = trap_BotGetLevelItemGoal( i, itemname, &tmpgoal ); + trap_BotGoalName( tmpgoal.number, name, sizeof( name ) ); + if ( Q_stricmp( itemname, name ) != 0 ) { + continue; + } + VectorSubtract( tmpgoal.origin, bs->origin, dir ); + dist = VectorLength( dir ); + if ( dist < bestdist ) { + //trace from start to end + BotAI_Trace( &trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + if ( trace.fraction >= 1.0 ) { + bestdist = dist; + memcpy( goal, &tmpgoal, sizeof( bot_goal_t ) ); + } + } + } while ( i > 0 ); + return bestdist; +} + +/* +================== +BotMatch_WhereAreYou +================== +*/ +void BotMatch_WhereAreYou( bot_state_t *bs, bot_match_t *match ) { + float dist, bestdist; + int i, bestitem, redflagtt, blueflagtt, redtobluett; + bot_goal_t goal; + char *nearbyitems[] = { + "Shotgun", + "Grenade Launcher", + "Rocket Launcher", + "Plasmagun", + "Railgun", + "Lightning Gun", + "BFG10K", + "Quad Damage", + "Regeneration", + "Battle Suit", + "Speed", + "Invisibility", + "Flight", + "Armor", + "Heavy Armor", + "Red Flag", + "Blue Flag", + NULL + }; + // + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + + bestitem = -1; + bestdist = 999999; + for ( i = 0; nearbyitems[i]; i++ ) { + dist = BotNearestVisibleItem( bs, nearbyitems[i], &goal ); + if ( dist < bestdist ) { + bestdist = dist; + bestitem = i; + } + } + if ( bestitem != -1 ) { + if ( gametype == GT_CTF ) { + redflagtt = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT ); + blueflagtt = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT ); + redtobluett = trap_AAS_AreaTravelTimeToGoalArea( ctf_redflag.areanum, ctf_redflag.origin, ctf_blueflag.areanum, TFL_DEFAULT ); + if ( redflagtt < ( redflagtt + blueflagtt ) * 0.4 ) { + BotAI_BotInitialChat( bs, "ctflocation", nearbyitems[bestitem], "red", NULL ); + } else if ( blueflagtt < ( redflagtt + blueflagtt ) * 0.4 ) { + BotAI_BotInitialChat( bs, "ctflocation", nearbyitems[bestitem], "blue", NULL ); + } else { + BotAI_BotInitialChat( bs, "location", nearbyitems[bestitem], NULL ); + } + } else { + BotAI_BotInitialChat( bs, "location", nearbyitems[bestitem], NULL ); + } + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + } +} + +/* +================== +BotMatch_LeadTheWay +================== +*/ +void BotMatch_LeadTheWay( bot_state_t *bs, bot_match_t *match ) { + aas_entityinfo_t entinfo; + char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; + int client, areanum, other; + + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + //if someone asks for someone else + if ( match->subtype & ST_SOMEONE ) { + //get the team mate name + trap_BotMatchVariable( match, TEAMMATE, teammate, sizeof( teammate ) ); + client = FindClientByName( teammate ); + //if this is the bot self + if ( client == bs->client ) { + other = qfalse; + } else if ( !BotSameTeam( bs, client ) ) { + //FIXME: say "I don't help the enemy" + return; + } else { + other = qtrue; + } + } else { + //get the netname + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + client = ClientFromName( netname ); + other = qfalse; + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if ( client < 0 ) { + BotAI_BotInitialChat( bs, "whois", netname, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + return; + } + // + bs->lead_teamgoal.entitynum = -1; + BotEntityInfo( client, &entinfo ); + //if info is valid (in PVS) + if ( entinfo.valid ) { + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + bs->lead_teamgoal.entitynum = client; + bs->lead_teamgoal.areanum = areanum; + VectorCopy( entinfo.origin, bs->lead_teamgoal.origin ); + VectorSet( bs->lead_teamgoal.mins, -8, -8, -8 ); + VectorSet( bs->lead_teamgoal.maxs, 8, 8, 8 ); + } + } + + if ( bs->teamgoal.entitynum < 0 ) { + if ( other ) { + BotAI_BotInitialChat( bs, "whereis", teammate, NULL ); + } else { BotAI_BotInitialChat( bs, "whereareyou", netname, NULL );} + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + return; + } + bs->lead_teammate = client; + bs->lead_time = trap_AAS_Time() + TEAM_LEAD_TIME; + bs->leadvisible_time = 0; + bs->leadmessage_time = -( trap_AAS_Time() + 2 * random() ); +} + +/* +================== +BotMatch_Kill +================== +*/ +void BotMatch_Kill( bot_state_t *bs, bot_match_t *match ) { + char enemy[MAX_MESSAGE_SIZE]; + int client; + + if ( !TeamPlayIsOn() ) { + return; + } + //if not addressed to this bot + if ( !BotAddressedToBot( bs, match ) ) { + return; + } + + trap_BotMatchVariable( match, ENEMY, enemy, sizeof( enemy ) ); + // + client = FindEnemyByName( bs, enemy ); + if ( client < 0 ) { + BotAI_BotInitialChat( bs, "whois", enemy, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + return; + } + bs->teamgoal.entitynum = client; + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_KILL; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + TEAM_KILL_SOMEONE; +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +/* +================== +BotMatch_CTF +================== +*/ +void BotMatch_CTF( bot_state_t *bs, bot_match_t *match ) { + + char flag[128], netname[MAX_NETNAME]; + + trap_BotMatchVariable( match, FLAG, flag, sizeof( flag ) ); + if ( match->subtype & ST_GOTFLAG ) { + if ( !Q_stricmp( flag, "red" ) ) { + bs->redflagstatus = 1; + if ( BotCTFTeam( bs ) == CTF_TEAM_BLUE ) { + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + bs->flagcarrier = ClientFromName( netname ); + } + } else { + bs->blueflagstatus = 1; + if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) { + trap_BotMatchVariable( match, NETNAME, netname, sizeof( netname ) ); + bs->flagcarrier = ClientFromName( netname ); + } + } + bs->flagstatuschanged = 1; + } else if ( match->subtype & ST_CAPTUREDFLAG ) { + bs->redflagstatus = 0; + bs->blueflagstatus = 0; + bs->flagcarrier = 0; + bs->flagstatuschanged = 1; + } else if ( match->subtype & ST_RETURNEDFLAG ) { + if ( !Q_stricmp( flag, "red" ) ) { + bs->redflagstatus = 0; + } else { bs->blueflagstatus = 0;} + bs->flagstatuschanged = 1; + } +} + +/* +================== +BotMatchMessage +================== +*/ +int BotMatchMessage( bot_state_t *bs, char *message ) { + bot_match_t match; + + match.type = 0; + //if it is an unknown message + if ( !trap_BotFindMatch( message, &match, MTCONTEXT_ENTERGAME + | MTCONTEXT_INITIALTEAMCHAT + | MTCONTEXT_CTF ) ) { + return qfalse; + } + //react to the found message + switch ( match.type ) { + case MSG_HELP: //someone calling for help + case MSG_ACCOMPANY: //someone calling for company + { + BotMatch_HelpAccompany( bs, &match ); + break; + } + case MSG_DEFENDKEYAREA: //teamplay defend a key area + { + BotMatch_DefendKeyArea( bs, &match ); + break; + } + case MSG_CAMP: //camp somewhere + { + BotMatch_Camp( bs, &match ); + break; + } + case MSG_PATROL: //patrol between several key areas + { + BotMatch_Patrol( bs, &match ); + break; + } +#ifdef CTF + case MSG_GETFLAG: //ctf get the enemy flag + { + BotMatch_GetFlag( bs, &match ); + break; + } + case MSG_RUSHBASE: //ctf rush to the base + { + BotMatch_RushBase( bs, &match ); + break; + } + case MSG_RETURNFLAG: + { + BotMatch_ReturnFlag( bs, &match ); + break; + } +#endif //CTF + case MSG_GETITEM: + { + BotMatch_GetItem( bs, &match ); + break; + } + case MSG_JOINSUBTEAM: //join a sub team + { + BotMatch_JoinSubteam( bs, &match ); + break; + } + case MSG_LEAVESUBTEAM: //leave a sub team + { + BotMatch_LeaveSubteam( bs, &match ); + break; + } + case MSG_WHICHTEAM: + { + BotMatch_WhichTeam( bs, &match ); + break; + } + case MSG_CHECKPOINT: //remember a check point + { + BotMatch_CheckPoint( bs, &match ); + break; + } + case MSG_CREATENEWFORMATION: //start the creation of a new formation + { + trap_EA_SayTeam( bs->client, "the part of my brain to create formations has been damaged" ); + break; + } + case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation + { + trap_EA_SayTeam( bs->client, "the part of my brain to create formations has been damaged" ); + break; + } + case MSG_FORMATIONSPACE: //set the formation space + { + BotMatch_FormationSpace( bs, &match ); + break; + } + case MSG_DOFORMATION: //form a certain formation + { + break; + } + case MSG_DISMISS: //dismiss someone + { + BotMatch_Dismiss( bs, &match ); + break; + } + case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader + { + BotMatch_StartTeamLeaderShip( bs, &match ); + break; + } + case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader + { + BotMatch_StopTeamLeaderShip( bs, &match ); + break; + } + case MSG_WHOISTEAMLAEDER: + { + BotMatch_WhoIsTeamLeader( bs, &match ); + break; + } + case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing + { + BotMatch_WhatAreYouDoing( bs, &match ); + break; + } + case MSG_WHATISMYCOMMAND: + { + BotMatch_WhatIsMyCommand( bs, &match ); + break; + } + case MSG_WHEREAREYOU: + { + BotMatch_WhereAreYou( bs, &match ); + break; + } + case MSG_LEADTHEWAY: + { + BotMatch_LeadTheWay( bs, &match ); + break; + } + case MSG_KILL: + { + BotMatch_Kill( bs, &match ); + break; + } + case MSG_ENTERGAME: //someone entered the game + { + //NOTE: eliza chats will catch this + //BotMatchVariable(&match, NETNAME, netname); + //Com_sprintf(buf, sizeof(buf), "heya %s", netname); + //EA_Say(bs->client, buf); + break; + } + case MSG_CTF: + { + BotMatch_CTF( bs, &match ); + break; + } + case MSG_WAIT: + { + break; + } + default: + { + BotAI_Print( PRT_MESSAGE, "unknown match type\n" ); + break; + } + } + return qtrue; +} diff --git a/src/botai/ai_cmd.h b/src/botai/ai_cmd.h new file mode 100644 index 0000000..47cb2d0 --- /dev/null +++ b/src/botai/ai_cmd.h @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_cmd.h + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +int BotMatchMessage( bot_state_t *bs, char *message ); +void BotPrintTeamGoal( bot_state_t *bs ); + diff --git a/src/botai/ai_dmnet.c b/src/botai/ai_dmnet.c new file mode 100644 index 0000000..5d0f0b7 --- /dev/null +++ b/src/botai/ai_dmnet.c @@ -0,0 +1,2045 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_dmnet.c + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +#include "../game/g_local.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../botai/botai.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +//data file headers +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +//goal flag, see be_ai_goal.h for the other GFL_* +#define GFL_AIR 16 + +int numnodeswitches; +char nodeswitch[MAX_NODESWITCHES + 1][144]; + +#define LOOKAHEAD_DISTANCE 300 + +/* +================== +BotResetNodeSwitches +================== +*/ +void BotResetNodeSwitches( void ) { + numnodeswitches = 0; +} + +/* +================== +BotDumpNodeSwitches +================== +*/ +void BotDumpNodeSwitches( bot_state_t *bs ) { + int i; + char netname[MAX_NETNAME]; + + ClientName( bs->client, netname, sizeof( netname ) ); + BotAI_Print( PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, trap_AAS_Time(), MAX_NODESWITCHES ); + for ( i = 0; i < numnodeswitches; i++ ) { + BotAI_Print( PRT_MESSAGE, nodeswitch[i] ); + } + BotAI_Print( PRT_FATAL, "" ); +} + +/* +================== +BotRecordNodeSwitch +================== +*/ +void BotRecordNodeSwitch( bot_state_t *bs, char *node, char *str ) { + char netname[MAX_NETNAME]; + + ClientName( bs->client, netname, sizeof( netname ) ); + Com_sprintf( nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s\n", netname, trap_AAS_Time(), node, str ); +#ifdef DEBUG + if ( 0 ) { + BotAI_Print( PRT_MESSAGE, nodeswitch[numnodeswitches] ); + } +#endif //DEBUG + numnodeswitches++; +} + +/* +================== +BotGetAirGoal +================== +*/ +int BotGetAirGoal( bot_state_t *bs, bot_goal_t *goal ) { + bsp_trace_t bsptrace; + vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; + int areanum; + + //trace up until we hit solid + VectorCopy( bs->origin, end ); + end[2] += 1000; + BotAI_Trace( &bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + //trace down until we hit water + VectorCopy( bsptrace.endpos, end ); + BotAI_Trace( &bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ); + //if we found the water surface + if ( bsptrace.fraction > 0 ) { + areanum = BotPointAreaNum( bsptrace.endpos ); + if ( areanum ) { + VectorCopy( bsptrace.endpos, goal->origin ); + goal->origin[2] -= 2; + goal->areanum = areanum; + goal->mins[0] = -15; + goal->mins[1] = -15; + goal->mins[2] = -1; + goal->maxs[0] = 15; + goal->maxs[1] = 15; + goal->maxs[2] = 1; + goal->flags = GFL_AIR; + goal->number = 0; + goal->iteminfo = 0; + goal->entitynum = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGoForAir +================== +*/ +int BotGoForAir( bot_state_t *bs, int tfl, bot_goal_t *ltg, float range ) { + bot_goal_t goal; + + //if the bot needs air + if ( bs->lastair_time < trap_AAS_Time() - 6 ) { + // +#ifdef DEBUG + //BotAI_Print(PRT_MESSAGE, "going for air\n"); +#endif //DEBUG + //if we can find an air goal + if ( BotGetAirGoal( bs, &goal ) ) { + trap_BotPushGoal( bs->gs, &goal ); + return qtrue; + } else { + //get a nearby goal outside the water + while ( trap_BotChooseNBGItem( bs->gs, bs->origin, bs->inventory, tfl, ltg, range ) ) { + trap_BotGetTopGoal( bs->gs, &goal ); + //if the goal is not in water + if ( !( trap_AAS_PointContents( goal.origin ) & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + return qtrue; + } + trap_BotPopGoal( bs->gs ); + } + trap_BotResetAvoidGoals( bs->gs ); + } + } + return qfalse; +} + +/* +================== +BotNearbyGoal +================== +*/ +int BotNearbyGoal( bot_state_t *bs, int tfl, bot_goal_t *ltg, float range ) { + int ret; + + if ( BotGoForAir( bs, tfl, ltg, range ) ) { + return qtrue; + } + + ret = trap_BotChooseNBGItem( bs->gs, bs->origin, bs->inventory, tfl, ltg, range ); + /* + if (ret) + { + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, &goal); + trap_BotGoalName(goal.number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", trap_AAS_Time(), buf); + } + */ + return ret; +} + +/* +================== +BotReachedGoal +================== +*/ +int BotReachedGoal( bot_state_t *bs, bot_goal_t *goal ) { + if ( goal->flags & GFL_ITEM ) { + //if touching the goal + if ( trap_BotTouchingGoal( bs->origin, goal ) ) { + return qtrue; + } + //if the goal isn't there + if ( trap_BotItemGoalInVisButNotVisible( bs->entitynum, bs->eye, bs->viewangles, goal ) ) { + return qtrue; + } + //if in the goal area and below or above the goal and not swimming + if ( bs->areanum == goal->areanum ) { + if ( bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0] ) { + if ( bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1] ) { + if ( !trap_AAS_Swimming( bs->origin ) ) { + return qtrue; + } + } + } + } + } else if ( goal->flags & GFL_AIR ) { + //if touching the goal + if ( trap_BotTouchingGoal( bs->origin, goal ) ) { + return qtrue; + } + //if the bot got air + if ( bs->lastair_time > trap_AAS_Time() - 1 ) { + return qtrue; + } + } else { + //if touching the goal + if ( trap_BotTouchingGoal( bs->origin, goal ) ) { + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGetItemLongTermGoal +================== +*/ +int BotGetItemLongTermGoal( bot_state_t *bs, int tfl, bot_goal_t *goal ) { + //if the bot has no goal + if ( !trap_BotGetTopGoal( bs->gs, goal ) ) { + //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); + bs->ltg_time = 0; + } + //if the bot touches the current goal + else if ( BotReachedGoal( bs, goal ) ) { + BotChooseWeapon( bs ); + bs->ltg_time = 0; + } + //if it is time to find a new long term goal + if ( bs->ltg_time < trap_AAS_Time() ) { + //pop the current goal from the stack + trap_BotPopGoal( bs->gs ); + //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); + //choose a new goal + //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", trap_AAS_Time(), bs->client); + if ( trap_BotChooseLTGItem( bs->gs, bs->origin, bs->inventory, tfl ) ) { + /* + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, goal); + trap_BotGoalName(goal->number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", trap_AAS_Time(), buf); + */ + bs->ltg_time = trap_AAS_Time() + 20; + } else { //the bot gets sorta stuck with all the avoid timings, shouldn't happen though + // +#ifdef DEBUG + char netname[128]; + + BotAI_Print( PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName( bs->client, netname, sizeof( netname ) ) ); +#endif + //trap_BotDumpAvoidGoals(bs->gs); + //reset the avoid goals and the avoid reach + trap_BotResetAvoidGoals( bs->gs ); + trap_BotResetAvoidReach( bs->ms ); + } + //get the goal at the top of the stack + return trap_BotGetTopGoal( bs->gs, goal ); + } + return qtrue; +} + +/* +================== +BotGetLongTermGoal + +we could also create a seperate AI node for every long term goal type +however this saves us a lot of code +================== +*/ +int BotGetLongTermGoal( bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal ) { + vec3_t target, dir; + char netname[MAX_NETNAME]; + char buf[MAX_MESSAGE_SIZE]; + int areanum; + float croucher; + aas_entityinfo_t entinfo; + bot_waypoint_t *wp; + + if ( bs->ltgtype == LTG_TEAMHELP && !retreat ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + BotAI_BotInitialChat( bs, "help_start", EasyClientName( bs->teammate, netname, sizeof( netname ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->teammessage_time = 0; + } + //if trying to help the team mate for more than a minute + if ( bs->teamgoal_time < trap_AAS_Time() ) { + bs->ltgtype = 0; + } + //if the team mate IS visible for quite some time + if ( bs->teammatevisible_time < trap_AAS_Time() - 10 ) { + bs->ltgtype = 0; + } + //get entity information of the companion + BotEntityInfo( bs->teammate, &entinfo ); + //if the team mate is visible + if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate ) ) { + //if close just stand still there + VectorSubtract( entinfo.origin, bs->origin, dir ); + if ( VectorLength( dir ) < 100 ) { + trap_BotResetAvoidReach( bs->ms ); + return qfalse; + } + } else { + //last time the bot was NOT visible + bs->teammatevisible_time = trap_AAS_Time(); + } + //if the entity information is valid (entity in PVS) + if ( entinfo.valid ) { + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy( entinfo.origin, bs->teamgoal.origin ); + VectorSet( bs->teamgoal.mins, -8, -8, -8 ); + VectorSet( bs->teamgoal.maxs, 8, 8, 8 ); + } + } + memcpy( goal, &bs->teamgoal, sizeof( bot_goal_t ) ); + return qtrue; + } + //if the bot accompanies someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !retreat ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + BotAI_BotInitialChat( bs, "accompany_start", EasyClientName( bs->teammate, netname, sizeof( netname ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->teammessage_time = 0; + } + //if accompanying the companion for 3 minutes + if ( bs->teamgoal_time < trap_AAS_Time() ) { + BotAI_BotInitialChat( bs, "accompany_stop", EasyClientName( bs->teammate, netname, sizeof( netname ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->ltgtype = 0; + } + //get entity information of the companion + BotEntityInfo( bs->teammate, &entinfo ); + //if the companion is visible + if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate ) ) { + //update visible time + bs->teammatevisible_time = trap_AAS_Time(); + VectorSubtract( entinfo.origin, bs->origin, dir ); + if ( VectorLength( dir ) < bs->formation_dist ) { + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if ( bs->attackcrouch_time < trap_AAS_Time() - 5 ) { + croucher = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CROUCHER, 0, 1 ); + if ( random() < bs->thinktime * croucher ) { + bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15; + } + } + //don't crouch when swimming + if ( trap_AAS_Swimming( bs->origin ) ) { + bs->attackcrouch_time = trap_AAS_Time() - 1; + } + //if not arrived yet or arived some time ago + if ( bs->arrive_time < trap_AAS_Time() - 2 ) { + //if not arrived yet + if ( !bs->arrive_time ) { + trap_EA_Gesture( bs->client ); + BotAI_BotInitialChat( bs, "accompany_arrive", EasyClientName( bs->teammate, netname, sizeof( netname ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->arrive_time = trap_AAS_Time(); + } + //if the bot wants to crouch + else if ( bs->attackcrouch_time > trap_AAS_Time() ) { + trap_EA_Crouch( bs->client ); + } + //else do some model taunts + else if ( random() < bs->thinktime * 0.3 ) { + //do a gesture :) + trap_EA_Gesture( bs->client ); + } + } + //if just arrived look at the companion + if ( bs->arrive_time > trap_AAS_Time() - 2 ) { + VectorSubtract( entinfo.origin, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + bs->ideal_viewangles[2] *= 0.5; + } + //else look strategically around for enemies + else if ( random() < bs->thinktime * 0.8 ) { + BotRoamGoal( bs, target ); + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to go for air + if ( BotGoForAir( bs, bs->tfl, &bs->teamgoal, 400 ) ) { + trap_BotResetLastAvoidReach( bs->ms ); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = trap_AAS_Time() + 8; + AIEnter_Seek_NBG( bs ); + return qfalse; + } + // + trap_BotResetAvoidReach( bs->ms ); + return qfalse; + } + } + //if the entity information is valid (entity in PVS) + if ( entinfo.valid ) { + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + //update team goal so bot will accompany + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy( entinfo.origin, bs->teamgoal.origin ); + VectorSet( bs->teamgoal.mins, -8, -8, -8 ); + VectorSet( bs->teamgoal.maxs, 8, 8, 8 ); + } + } + //the goal the bot should go for + memcpy( goal, &bs->teamgoal, sizeof( bot_goal_t ) ); + //if the companion is NOT visible for too long + if ( bs->teammatevisible_time < trap_AAS_Time() - 60 ) { + BotAI_BotInitialChat( bs, "accompany_cannotfind", EasyClientName( bs->teammate, netname, sizeof( netname ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->ltgtype = 0; + } + return qtrue; + } + // + if ( bs->ltgtype == LTG_DEFENDKEYAREA ) { + if ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT ) > bs->defendaway_range ) { + bs->defendaway_time = 0; + } + } + //if defending a key area + if ( bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && + bs->defendaway_time < trap_AAS_Time() ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + trap_BotGoalName( bs->teamgoal.number, buf, sizeof( buf ) ); + BotAI_BotInitialChat( bs, "defend_start", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy( goal, &bs->teamgoal, sizeof( bot_goal_t ) ); + //stop after 2 minutes + if ( bs->teamgoal_time < trap_AAS_Time() ) { + trap_BotGoalName( bs->teamgoal.number, buf, sizeof( buf ) ); + BotAI_BotInitialChat( bs, "defend_stop", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->ltgtype = 0; + } + //if very close... go away for some time + VectorSubtract( goal->origin, bs->origin, dir ); + if ( VectorLength( dir ) < 70 ) { + trap_BotResetAvoidReach( bs->ms ); + bs->defendaway_time = trap_AAS_Time() + 2 + 5 * random(); + bs->defendaway_range = 300; + } + return qtrue; + } + //going to kill someone + if ( bs->ltgtype == LTG_KILL && !retreat ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + EasyClientName( bs->teamgoal.entitynum, buf, sizeof( buf ) ); + BotAI_BotInitialChat( bs, "kill_start", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->teammessage_time = 0; + } + // + if ( bs->lastkilledplayer == bs->teamgoal.entitynum ) { + EasyClientName( bs->teamgoal.entitynum, buf, sizeof( buf ) ); + BotAI_BotInitialChat( bs, "kill_done", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->lastkilledplayer = -1; + bs->ltgtype = 0; + } + // + if ( bs->teamgoal_time < trap_AAS_Time() ) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal( bs, tfl, goal ); + } + //get an item + if ( bs->ltgtype == LTG_GETITEM && !retreat ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + trap_BotGoalName( bs->teamgoal.number, buf, sizeof( buf ) ); + BotAI_BotInitialChat( bs, "getitem_start", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy( goal, &bs->teamgoal, sizeof( bot_goal_t ) ); + //stop after some time + if ( bs->teamgoal_time < trap_AAS_Time() ) { + bs->ltgtype = 0; + } + // + if ( trap_BotItemGoalInVisButNotVisible( bs->entitynum, bs->eye, bs->viewangles, goal ) ) { + trap_BotGoalName( bs->teamgoal.number, buf, sizeof( buf ) ); + BotAI_BotInitialChat( bs, "getitem_notthere", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->ltgtype = 0; + } else if ( BotReachedGoal( bs, goal ) ) { + trap_BotGoalName( bs->teamgoal.number, buf, sizeof( buf ) ); + BotAI_BotInitialChat( bs, "getitem_gotit", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->ltgtype = 0; + } + return qtrue; + } + //if camping somewhere + if ( ( bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER ) && !retreat ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + if ( bs->ltgtype == LTG_CAMPORDER ) { + BotAI_BotInitialChat( bs, "camp_start", EasyClientName( bs->teammate, netname, sizeof( netname ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + } + bs->teammessage_time = 0; + } + //set the bot goal + memcpy( goal, &bs->teamgoal, sizeof( bot_goal_t ) ); + // + if ( bs->teamgoal_time < trap_AAS_Time() ) { + if ( bs->ltgtype == LTG_CAMPORDER ) { + BotAI_BotInitialChat( bs, "camp_stop", NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + } + bs->ltgtype = 0; + } + //if really near the camp spot + VectorSubtract( goal->origin, bs->origin, dir ); + if ( VectorLength( dir ) < 60 ) { + //if not arrived yet + if ( !bs->arrive_time ) { + if ( bs->ltgtype == LTG_CAMPORDER ) { + BotAI_BotInitialChat( bs, "camp_arrive", EasyClientName( bs->teammate, netname, sizeof( netname ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + } + bs->arrive_time = trap_AAS_Time(); + } + //look strategically around for enemies + if ( random() < bs->thinktime * 0.8 ) { + BotRoamGoal( bs, target ); + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if ( bs->attackcrouch_time < trap_AAS_Time() - 5 ) { + croucher = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CROUCHER, 0, 1 ); + if ( random() < bs->thinktime * croucher ) { + bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15; + } + } + //if the bot wants to crouch + if ( bs->attackcrouch_time > trap_AAS_Time() ) { + trap_EA_Crouch( bs->client ); + } + //don't crouch when swimming + if ( trap_AAS_Swimming( bs->origin ) ) { + bs->attackcrouch_time = trap_AAS_Time() - 1; + } + //make sure the bot is not gonna drown + if ( trap_PointContents( bs->eye,bs->entitynum ) & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( bs->ltgtype == LTG_CAMPORDER ) { + BotAI_BotInitialChat( bs, "camp_stop", NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + } + bs->ltgtype = 0; + } + // + if ( bs->camp_range > 0 ) { + //FIXME: move around a bit + } + // + trap_BotResetAvoidReach( bs->ms ); + return qfalse; + } + return qtrue; + } + //patrolling along several waypoints + if ( bs->ltgtype == LTG_PATROL && !retreat ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + strcpy( buf, "" ); + for ( wp = bs->patrolpoints; wp; wp = wp->next ) { + strcat( buf, wp->name ); + if ( wp->next ) { + strcat( buf, " to " ); + } + } + BotAI_BotInitialChat( bs, "patrol_start", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->teammessage_time = 0; + } + // + if ( !bs->curpatrolpoint ) { + bs->ltgtype = 0; + return qfalse; + } + //if the bot touches the current goal + if ( trap_BotTouchingGoal( bs->origin, &bs->curpatrolpoint->goal ) ) { + if ( bs->patrolflags & PATROL_BACK ) { + if ( bs->curpatrolpoint->prev ) { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + } else { + bs->curpatrolpoint = bs->curpatrolpoint->next; + bs->patrolflags &= ~PATROL_BACK; + } + } else { + if ( bs->curpatrolpoint->next ) { + bs->curpatrolpoint = bs->curpatrolpoint->next; + } else { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + bs->patrolflags |= PATROL_BACK; + } + } + } + //stop after 5 minutes + if ( bs->teamgoal_time < trap_AAS_Time() ) { + BotAI_BotInitialChat( bs, "patrol_stop", NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->ltgtype = 0; + } + if ( !bs->curpatrolpoint ) { + bs->ltgtype = 0; + return qfalse; + } + memcpy( goal, &bs->curpatrolpoint->goal, sizeof( bot_goal_t ) ); + return qtrue; + } +#ifdef CTF + //if going for enemy flag + if ( bs->ltgtype == LTG_GETFLAG ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + BotAI_BotInitialChat( bs, "captureflag_start", NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->teammessage_time = 0; + } + // + switch ( BotCTFTeam( bs ) ) { + case CTF_TEAM_RED: *goal = ctf_blueflag; break; + case CTF_TEAM_BLUE: *goal = ctf_redflag; break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if ( trap_BotTouchingGoal( bs->origin, goal ) ) { + bs->ltgtype = 0; + } + //stop after 3 minutes + if ( bs->teamgoal_time < trap_AAS_Time() ) { +#ifdef DEBUG + BotAI_Print( PRT_MESSAGE, "%s: I quit getting the flag\n", ClientName( bs->client, netname, sizeof( netname ) ) ); +#endif //DEBUG + bs->ltgtype = 0; + } + return qtrue; + } + //if rushing to the base + if ( bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < trap_AAS_Time() ) { + switch ( BotCTFTeam( bs ) ) { + case CTF_TEAM_RED: *goal = ctf_redflag; break; + case CTF_TEAM_BLUE: *goal = ctf_blueflag; break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if ( bs->teamgoal_time < trap_AAS_Time() ) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if ( trap_BotTouchingGoal( bs->origin, goal ) ) { + //if the bot is still carrying the enemy flag then the + //base flag is gone, now just walk near the base a bit + if ( BotCTFCarryingFlag( bs ) ) { + trap_BotResetAvoidReach( bs->ms ); + bs->rushbaseaway_time = trap_AAS_Time() + 5 + 10 * random(); + //FIXME: add chat to tell the others to get back the flag + } else { + bs->ltgtype = 0; + } + } + return qtrue; + } + //returning flag + if ( bs->ltgtype == LTG_RETURNFLAG ) { + //check for bot typing status message + if ( bs->teammessage_time && bs->teammessage_time < trap_AAS_Time() ) { + EasyClientName( bs->teamgoal.entitynum, buf, sizeof( buf ) ); + BotAI_BotInitialChat( bs, "returnflag_start", buf, NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->teammessage_time = 0; + } + // + if ( bs->teamgoal_time < trap_AAS_Time() ) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal( bs, tfl, goal ); + } +#endif //CTF + //normal goal stuff + return BotGetItemLongTermGoal( bs, tfl, goal ); +} + +/* +================== +BotLongTermGoal +================== +*/ +int BotLongTermGoal( bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal ) { + aas_entityinfo_t entinfo; + char teammate[MAX_MESSAGE_SIZE]; + float dist; + int areanum; + vec3_t dir; + + //FIXME: also have air long term goals? + // + //if the bot is leading someone and not retreating + if ( bs->lead_time > 0 && !retreat ) { + if ( bs->lead_time < trap_AAS_Time() ) { + //FIXME: add chat to tell the team mate that he/she's on his/her own + bs->lead_time = 0; + return BotGetLongTermGoal( bs, tfl, retreat, goal ); + } + // + if ( bs->leadmessage_time < 0 && -bs->leadmessage_time < trap_AAS_Time() ) { + BotAI_BotInitialChat( bs, "followme", EasyClientName( bs->lead_teammate, teammate, sizeof( teammate ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->leadmessage_time = trap_AAS_Time(); + } + //get entity information of the companion + BotEntityInfo( bs->lead_teammate, &entinfo ); + // + if ( entinfo.valid ) { + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + //update team goal + bs->lead_teamgoal.entitynum = bs->lead_teammate; + bs->lead_teamgoal.areanum = areanum; + VectorCopy( entinfo.origin, bs->lead_teamgoal.origin ); + VectorSet( bs->lead_teamgoal.mins, -8, -8, -8 ); + VectorSet( bs->lead_teamgoal.maxs, 8, 8, 8 ); + } + } + //if the team mate is visible + if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate ) ) { + bs->leadvisible_time = trap_AAS_Time(); + } + //if the team mate is not visible for 1 seconds + if ( bs->leadvisible_time < trap_AAS_Time() - 1 ) { + bs->leadbackup_time = trap_AAS_Time() + 2; + } + //distance towards the team mate + VectorSubtract( bs->origin, bs->lead_teamgoal.origin, dir ); + dist = VectorLength( dir ); + //if backing up towards the team mate + if ( bs->leadbackup_time > trap_AAS_Time() ) { + if ( bs->leadmessage_time < trap_AAS_Time() - 20 ) { + BotAI_BotInitialChat( bs, "followme", EasyClientName( bs->lead_teammate, teammate, sizeof( teammate ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->leadmessage_time = trap_AAS_Time(); + } + //if very close to the team mate + if ( dist < 100 ) { + bs->leadbackup_time = 0; + } + //the bot should go back to the team mate + memcpy( goal, &bs->lead_teamgoal, sizeof( bot_goal_t ) ); + return qtrue; + } else { + //if quite distant from the team mate + if ( dist > 500 ) { + if ( bs->leadmessage_time < trap_AAS_Time() - 20 ) { + BotAI_BotInitialChat( bs, "followme", EasyClientName( bs->lead_teammate, teammate, sizeof( teammate ) ), NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->leadmessage_time = trap_AAS_Time(); + } + //look at the team mate + VectorSubtract( entinfo.origin, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + bs->ideal_viewangles[2] *= 0.5; + //just wait for the team mate + return qfalse; + } + } + } + return BotGetLongTermGoal( bs, tfl, retreat, goal ); +} + +/* +================== +AIEnter_Intermission +================== +*/ +void AIEnter_Intermission( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "intermission", "" ); + //reset the bot state + BotResetState( bs ); + //check for end level chat + if ( BotChat_EndLevel( bs ) ) { + trap_BotEnterChat( bs->cs, bs->client, bs->chatto ); + } + bs->ainode = AINode_Intermission; +} + +/* +================== +AINode_Intermission +================== +*/ +int AINode_Intermission( bot_state_t *bs ) { + //if the intermission ended + if ( !BotIntermission( bs ) ) { + if ( BotChat_StartLevel( bs ) ) { + bs->stand_time = trap_AAS_Time() + BotChatTime( bs ); + } else { + bs->stand_time = trap_AAS_Time() + 2; + } + AIEnter_Stand( bs ); + } + return qtrue; +} + +/* +================== +AIEnter_Observer +================== +*/ +void AIEnter_Observer( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "observer", "" ); + //reset the bot state + BotResetState( bs ); + bs->ainode = AINode_Observer; +} + +/* +================== +AINode_Observer +================== +*/ +int AINode_Observer( bot_state_t *bs ) { + //if the bot left observer mode + if ( !BotIsObserver( bs ) ) { + AIEnter_Stand( bs ); + } + return qtrue; +} + +/* +================== +AIEnter_Stand +================== +*/ +void AIEnter_Stand( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "stand", "" ); + bs->standfindenemy_time = trap_AAS_Time() + 1; + bs->ainode = AINode_Stand; +} + +/* +================== +AINode_Stand +================== +*/ +int AINode_Stand( bot_state_t *bs ) { + + //if the bot's health decreased + if ( bs->lastframe_health > bs->inventory[INVENTORY_HEALTH] ) { + if ( BotChat_HitTalking( bs ) ) { + bs->standfindenemy_time = trap_AAS_Time() + BotChatTime( bs ) + 0.1; + bs->stand_time = trap_AAS_Time() + BotChatTime( bs ) + 0.1; + } + } + if ( bs->standfindenemy_time < trap_AAS_Time() ) { + if ( BotFindEnemy( bs, -1 ) ) { + AIEnter_Battle_Fight( bs ); + return qfalse; + } + bs->standfindenemy_time = trap_AAS_Time() + 1; + } + trap_EA_Talk( bs->client ); + if ( bs->stand_time < trap_AAS_Time() ) { + trap_BotEnterChat( bs->cs, bs->client, bs->chatto ); + AIEnter_Seek_LTG( bs ); + return qfalse; + } + // + return qtrue; +} + +/* +================== +AIEnter_Respawn +================== +*/ +void AIEnter_Respawn( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "respawn", "" ); + //reset some states + trap_BotResetMoveState( bs->ms ); + trap_BotResetGoalState( bs->gs ); + trap_BotResetAvoidGoals( bs->gs ); + trap_BotResetAvoidReach( bs->ms ); + //if the bot wants to chat + if ( BotChat_Death( bs ) ) { + bs->respawn_time = trap_AAS_Time() + BotChatTime( bs ); + bs->respawnchat_time = trap_AAS_Time(); + } else { + bs->respawn_time = trap_AAS_Time() + 1 + random(); + bs->respawnchat_time = 0; + } + //set respawn state + bs->respawn_wait = qfalse; + bs->ainode = AINode_Respawn; +} + +/* +================== +AINode_Respawn +================== +*/ +int AINode_Respawn( bot_state_t *bs ) { + if ( bs->respawn_wait ) { + if ( !BotIsDead( bs ) ) { + AIEnter_Seek_LTG( bs ); + } else { + trap_EA_Respawn( bs->client ); + } + } else if ( bs->respawn_time < trap_AAS_Time() ) { + //wait until respawned + bs->respawn_wait = qtrue; + //elementary action respawn + trap_EA_Respawn( bs->client ); + // + if ( bs->respawnchat_time ) { + trap_BotEnterChat( bs->cs, bs->client, bs->chatto ); + bs->enemy = -1; + } + } + if ( bs->respawnchat_time && bs->respawnchat_time < trap_AAS_Time() - 0.5 ) { + trap_EA_Talk( bs->client ); + } + // + return qtrue; +} + +/* +================== +AIEnter_Seek_ActivateEntity +================== +*/ +void AIEnter_Seek_ActivateEntity( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "activate entity", "" ); + bs->ainode = AINode_Seek_ActivateEntity; +} + +/* +================== +AINode_Seek_Activate_Entity +================== +*/ +int AINode_Seek_ActivateEntity( bot_state_t *bs ) { + bot_goal_t *goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + + if ( BotIsObserver( bs ) ) { + AIEnter_Observer( bs ); + return qfalse; + } + //if in the intermission + if ( BotIntermission( bs ) ) { + AIEnter_Intermission( bs ); + return qfalse; + } + //respawn if dead + if ( BotIsDead( bs ) ) { + AIEnter_Respawn( bs ); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if ( bot_grapple.integer ) { + bs->tfl |= TFL_GRAPPLEHOOK; + } + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + bs->tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + bs->tfl |= TFL_SLIME; + } + //map specific code + BotMapScripts( bs ); + //no enemy + bs->enemy = -1; + // + goal = &bs->activategoal; + //if the bot has no goal + if ( !goal ) { + bs->activate_time = 0; + } + //if the bot touches the current goal + else if ( trap_BotTouchingGoal( bs->origin, goal ) ) { + BotChooseWeapon( bs ); +#ifdef DEBUG + BotAI_Print( PRT_MESSAGE, "touched button or trigger\n" ); +#endif //DEBUG + bs->activate_time = 0; + } + // + if ( bs->activate_time < trap_AAS_Time() ) { + AIEnter_Seek_NBG( bs ); + return qfalse; + } + //initialize the movement state + BotSetupForMovement( bs ); + //move towards the goal + trap_BotMoveToGoal( &moveresult, bs->ms, goal, bs->tfl ); + //if the movement failed + if ( moveresult.failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + bs->nbg_time = 0; + } + //check if the bot is blocked + BotAIBlocked( bs, &moveresult, qtrue ); + // + if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { + VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); + } + //if waiting for something + else if ( moveresult.flags & MOVERESULT_WAITING ) { + if ( random() < bs->thinktime * 0.8 ) { + BotRoamGoal( bs, target ); + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + bs->ideal_viewangles[2] *= 0.5; + } + } else if ( !( bs->flags & BFL_IDEALVIEWSET ) ) { + if ( trap_BotMovementViewTarget( bs->ms, goal, bs->tfl, 300, target ) ) { + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + } else { + //vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { + bs->weaponnum = moveresult.weapon; + } + //if there is an enemy + if ( BotFindEnemy( bs, -1 ) ) { + if ( BotWantsToRetreat( bs ) ) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG( bs ); + } else { + trap_BotResetLastAvoidReach( bs->ms ); + //empty the goal stack + trap_BotEmptyGoalStack( bs->gs ); + //go fight + AIEnter_Battle_Fight( bs ); + } + } + return qtrue; +} + +/* +================== +AIEnter_Seek_NBG +================== +*/ +void AIEnter_Seek_NBG( bot_state_t *bs ) { + bot_goal_t goal; + char buf[144]; + + if ( trap_BotGetTopGoal( bs->gs, &goal ) ) { + trap_BotGoalName( goal.number, buf, 144 ); + BotRecordNodeSwitch( bs, "seek NBG", buf ); + } else { + BotRecordNodeSwitch( bs, "seek NBG", "no goal" ); + } + bs->ainode = AINode_Seek_NBG; +} + +/* +================== +AINode_Seek_NBG +================== +*/ +int AINode_Seek_NBG( bot_state_t *bs ) { + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + + if ( BotIsObserver( bs ) ) { + AIEnter_Observer( bs ); + return qfalse; + } + //if in the intermission + if ( BotIntermission( bs ) ) { + AIEnter_Intermission( bs ); + return qfalse; + } + //respawn if dead + if ( BotIsDead( bs ) ) { + AIEnter_Respawn( bs ); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if ( bot_grapple.integer ) { + bs->tfl |= TFL_GRAPPLEHOOK; + } + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + bs->tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + bs->tfl |= TFL_SLIME; + } + // + if ( BotCanAndWantsToRocketJump( bs ) ) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts( bs ); + //no enemy + bs->enemy = -1; + //if the bot has no goal + if ( !trap_BotGetTopGoal( bs->gs, &goal ) ) { + bs->nbg_time = 0; + } + //if the bot touches the current goal + else if ( BotReachedGoal( bs, &goal ) ) { + BotChooseWeapon( bs ); + bs->nbg_time = 0; + } + // + if ( bs->nbg_time < trap_AAS_Time() ) { + //pop the current goal from the stack + trap_BotPopGoal( bs->gs ); + //check for new nearby items right away + //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches + bs->check_time = trap_AAS_Time() + 0.05; + //go back to seek ltg + AIEnter_Seek_LTG( bs ); + return qfalse; + } + //initialize the movement state + BotSetupForMovement( bs ); + //move towards the goal + trap_BotMoveToGoal( &moveresult, bs->ms, &goal, bs->tfl ); + //if the movement failed + if ( moveresult.failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + bs->nbg_time = 0; + } + //check if the bot is blocked + BotAIBlocked( bs, &moveresult, qtrue ); + //if the viewangles are used for the movement + if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { + VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); + } + //if waiting for something + else if ( moveresult.flags & MOVERESULT_WAITING ) { + if ( random() < bs->thinktime * 0.8 ) { + BotRoamGoal( bs, target ); + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + bs->ideal_viewangles[2] *= 0.5; + } + } else if ( !( bs->flags & BFL_IDEALVIEWSET ) ) { + if ( !trap_BotGetSecondGoal( bs->gs, &goal ) ) { + trap_BotGetTopGoal( bs->gs, &goal ); + } + if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + } + //FIXME: look at cluster portals? + else {vectoangles( moveresult.movedir, bs->ideal_viewangles );} + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { + bs->weaponnum = moveresult.weapon; + } + //if there is an enemy + if ( BotFindEnemy( bs, -1 ) ) { + if ( BotWantsToRetreat( bs ) ) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG( bs ); + } else { + trap_BotResetLastAvoidReach( bs->ms ); + //empty the goal stack + trap_BotEmptyGoalStack( bs->gs ); + //go fight + AIEnter_Battle_Fight( bs ); + } + } + return qtrue; +} + +/* +================== +AIEnter_Seek_LTG +================== +*/ +void AIEnter_Seek_LTG( bot_state_t *bs ) { + bot_goal_t goal; + char buf[144]; + + if ( trap_BotGetTopGoal( bs->gs, &goal ) ) { + trap_BotGoalName( goal.number, buf, 144 ); + BotRecordNodeSwitch( bs, "seek LTG", buf ); + } else { + BotRecordNodeSwitch( bs, "seek LTG", "no goal" ); + } + bs->ainode = AINode_Seek_LTG; +} + +/* +================== +AINode_Seek_LTG +================== +*/ +int AINode_Seek_LTG( bot_state_t *bs ) { + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + int range; + //char buf[128]; + //bot_goal_t tmpgoal; + + if ( BotIsObserver( bs ) ) { + AIEnter_Observer( bs ); + return qfalse; + } + //if in the intermission + if ( BotIntermission( bs ) ) { + AIEnter_Intermission( bs ); + return qfalse; + } + //respawn if dead + if ( BotIsDead( bs ) ) { + AIEnter_Respawn( bs ); + return qfalse; + } + // + if ( BotChat_Random( bs ) ) { + bs->stand_time = trap_AAS_Time() + BotChatTime( bs ); + AIEnter_Stand( bs ); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if ( bot_grapple.integer ) { + bs->tfl |= TFL_GRAPPLEHOOK; + } + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + bs->tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + bs->tfl |= TFL_SLIME; + } + // + if ( BotCanAndWantsToRocketJump( bs ) ) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts( bs ); + //no enemy + bs->enemy = -1; + // + if ( bs->killedenemy_time > trap_AAS_Time() - 2 ) { + if ( random() < bs->thinktime * 1 ) { + trap_EA_Gesture( bs->client ); + } + } + //if there is an enemy + if ( BotFindEnemy( bs, -1 ) ) { + if ( BotWantsToRetreat( bs ) ) { + //keep the current long term goal and retreat + AIEnter_Battle_Retreat( bs ); + return qfalse; + } else { + trap_BotResetLastAvoidReach( bs->ms ); + //empty the goal stack + trap_BotEmptyGoalStack( bs->gs ); + //go fight + AIEnter_Battle_Fight( bs ); + return qfalse; + } + } +#ifdef CTF + if ( gametype == GT_CTF ) { + //decide what to do in CTF mode + BotCTFSeekGoals( bs ); + } +#endif //CTF + //get the current long term goal + if ( !BotLongTermGoal( bs, bs->tfl, qfalse, &goal ) ) { + return qtrue; + } + //check for nearby goals periodicly + if ( bs->check_time < trap_AAS_Time() ) { + bs->check_time = trap_AAS_Time() + 0.5; + //check if the bot wants to camp + BotWantsToCamp( bs ); + // + if ( bs->ltgtype == LTG_DEFENDKEYAREA ) { + range = 400; + } else { range = 150;} + // +#ifdef CTF + //if carrying a flag the bot shouldn't be distracted too much + if ( BotCTFCarryingFlag( bs ) ) { + range = 50; + } +#endif //CTF + // + if ( BotNearbyGoal( bs, bs->tfl, &goal, range ) ) { + trap_BotResetLastAvoidReach( bs->ms ); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = trap_AAS_Time() + 4 + range * 0.01; + AIEnter_Seek_NBG( bs ); + return qfalse; + } + } + //initialize the movement state + BotSetupForMovement( bs ); + //move towards the goal + trap_BotMoveToGoal( &moveresult, bs->ms, &goal, bs->tfl ); + //if the movement failed + if ( moveresult.failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked( bs, &moveresult, qtrue ); + //if the viewangles are used for the movement + if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { + VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); + } + //if waiting for something + else if ( moveresult.flags & MOVERESULT_WAITING ) { + if ( random() < bs->thinktime * 0.8 ) { + BotRoamGoal( bs, target ); + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + bs->ideal_viewangles[2] *= 0.5; + } + } else if ( !( bs->flags & BFL_IDEALVIEWSET ) ) { + if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + } + //FIXME: look at cluster portals? + else if ( VectorLength( moveresult.movedir ) ) { + vectoangles( moveresult.movedir, bs->ideal_viewangles ); + } else if ( random() < bs->thinktime * 0.8 ) { + BotRoamGoal( bs, target ); + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + bs->ideal_viewangles[2] *= 0.5; + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { + bs->weaponnum = moveresult.weapon; + } + // + return qtrue; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_Fight( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "battle fight", "" ); + trap_BotResetLastAvoidReach( bs->ms ); + bs->ainode = AINode_Battle_Fight; +} + +/* +================== +AINode_Battle_Fight +================== +*/ +int AINode_Battle_Fight( bot_state_t *bs ) { + int areanum; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + + if ( BotIsObserver( bs ) ) { + AIEnter_Observer( bs ); + return qfalse; + } + //if in the intermission + if ( BotIntermission( bs ) ) { + AIEnter_Intermission( bs ); + return qfalse; + } + //respawn if dead + if ( BotIsDead( bs ) ) { + AIEnter_Respawn( bs ); + return qfalse; + } + //if no enemy + if ( bs->enemy < 0 ) { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + // + BotEntityInfo( bs->enemy, &entinfo ); + //if the enemy is dead + if ( bs->enemydeath_time ) { + if ( bs->enemydeath_time < trap_AAS_Time() - 1.5 ) { + bs->enemydeath_time = 0; + if ( bs->enemysuicide ) { + BotChat_EnemySuicide( bs ); + } + if ( bs->lastkilledplayer == bs->enemy && BotChat_Kill( bs ) ) { + bs->stand_time = trap_AAS_Time() + BotChatTime( bs ); + AIEnter_Stand( bs ); + } else { + bs->ltg_time = 0; + AIEnter_Seek_LTG( bs ); + } + return qfalse; + } + } else { + if ( EntityIsDead( &entinfo ) ) { + bs->enemydeath_time = trap_AAS_Time(); + } + } + //if the enemy is invisible and not shooting the bot looses track easily + if ( EntityIsInvisible( &entinfo ) && !EntityIsShooting( &entinfo ) ) { + if ( random() < 0.2 ) { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + VectorCopy( entinfo.origin, bs->lastenemyorigin ); + bs->lastenemyareanum = areanum; + } + //update the attack inventory values + BotUpdateBattleInventory( bs, bs->enemy ); + //if the bot's health decreased + if ( bs->lastframe_health > bs->inventory[INVENTORY_HEALTH] ) { + if ( BotChat_HitNoDeath( bs ) ) { + bs->stand_time = trap_AAS_Time() + BotChatTime( bs ); + AIEnter_Stand( bs ); + return qfalse; + } + } + //if the bot hit someone + if ( bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount ) { + if ( BotChat_HitNoKill( bs ) ) { + bs->stand_time = trap_AAS_Time() + BotChatTime( bs ); + AIEnter_Stand( bs ); + return qfalse; + } + } + //if the enemy is not visible + if ( !BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy ) ) { + if ( BotWantsToChase( bs ) ) { + AIEnter_Battle_Chase( bs ); + return qfalse; + } else { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + } + //use holdable items + BotBattleUseItems( bs ); + // + bs->tfl = TFL_DEFAULT; + if ( bot_grapple.integer ) { + bs->tfl |= TFL_GRAPPLEHOOK; + } + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + bs->tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + bs->tfl |= TFL_SLIME; + } + // + if ( BotCanAndWantsToRocketJump( bs ) ) { + bs->tfl |= TFL_ROCKETJUMP; + } + //choose the best weapon to fight with + BotChooseWeapon( bs ); + //do attack movements + moveresult = BotAttackMove( bs, bs->tfl ); + //if the movement failed + if ( moveresult.failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked( bs, &moveresult, qfalse ); + //aim at the enemy + BotAimAtEnemy( bs ); + //attack the enemy if possible + BotCheckAttack( bs ); + //if the bot wants to retreat + if ( BotWantsToRetreat( bs ) ) { + AIEnter_Battle_Retreat( bs ); + return qtrue; + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Chase +================== +*/ +void AIEnter_Battle_Chase( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "battle chase", "" ); + bs->chase_time = trap_AAS_Time(); + bs->ainode = AINode_Battle_Chase; +} + +/* +================== +AINode_Battle_Chase +================== +*/ +int AINode_Battle_Chase( bot_state_t *bs ) { + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + float range; + + if ( BotIsObserver( bs ) ) { + AIEnter_Observer( bs ); + return qfalse; + } + //if in the intermission + if ( BotIntermission( bs ) ) { + AIEnter_Intermission( bs ); + return qfalse; + } + //respawn if dead + if ( BotIsDead( bs ) ) { + AIEnter_Respawn( bs ); + return qfalse; + } + //if no enemy + if ( bs->enemy < 0 ) { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + //if the enemy is visible + if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy ) ) { + AIEnter_Battle_Fight( bs ); + return qfalse; + } + //if there is another enemy + if ( BotFindEnemy( bs, -1 ) ) { + AIEnter_Battle_Fight( bs ); + return qfalse; + } + //there is no last enemy area + if ( !bs->lastenemyareanum ) { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if ( bot_grapple.integer ) { + bs->tfl |= TFL_GRAPPLEHOOK; + } + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + bs->tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + bs->tfl |= TFL_SLIME; + } + // + if ( BotCanAndWantsToRocketJump( bs ) ) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts( bs ); + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy( bs->lastenemyorigin, goal.origin ); + VectorSet( goal.mins, -8, -8, -8 ); + VectorSet( goal.maxs, 8, 8, 8 ); + //if the last seen enemy spot is reached the enemy could not be found + if ( trap_BotTouchingGoal( bs->origin, &goal ) ) { + bs->chase_time = 0; + } + //if there's no chase time left + if ( !bs->chase_time || bs->chase_time < trap_AAS_Time() - 10 ) { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + //check for nearby goals periodicly + if ( bs->check_time < trap_AAS_Time() ) { + bs->check_time = trap_AAS_Time() + 1; + range = 150; + // + if ( BotNearbyGoal( bs, bs->tfl, &goal, range ) ) { + //the bot gets 5 seconds to pick up the nearby goal item + bs->nbg_time = trap_AAS_Time() + 0.1 * range + 1; + trap_BotResetLastAvoidReach( bs->ms ); + AIEnter_Battle_NBG( bs ); + return qfalse; + } + } + // + BotUpdateBattleInventory( bs, bs->enemy ); + //initialize the movement state + BotSetupForMovement( bs ); + //move towards the goal + trap_BotMoveToGoal( &moveresult, bs->ms, &goal, bs->tfl ); + //if the movement failed + if ( moveresult.failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked( bs, &moveresult, qfalse ); + // + if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { + VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); + } else if ( !( bs->flags & BFL_IDEALVIEWSET ) ) { + if ( bs->chase_time > trap_AAS_Time() - 2 ) { + BotAimAtEnemy( bs ); + } else { + if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + } else { + vectoangles( moveresult.movedir, bs->ideal_viewangles ); + } + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { + bs->weaponnum = moveresult.weapon; + } + //if the bot is in the area the enemy was last seen in + if ( bs->areanum == bs->lastenemyareanum ) { + bs->chase_time = 0; + } + //if the bot wants to retreat (the bot could have been damage during the chase) + if ( BotWantsToRetreat( bs ) ) { + AIEnter_Battle_Retreat( bs ); + return qtrue; + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Retreat +================== +*/ +void AIEnter_Battle_Retreat( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "battle retreat", "" ); + bs->ainode = AINode_Battle_Retreat; +} + +/* +================== +AINode_Battle_Retreat +================== +*/ +int AINode_Battle_Retreat( bot_state_t *bs ) { + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + vec3_t target, dir; + float attack_skill, range; + int areanum; + + if ( BotIsObserver( bs ) ) { + AIEnter_Observer( bs ); + return qfalse; + } + //if in the intermission + if ( BotIntermission( bs ) ) { + AIEnter_Intermission( bs ); + return qfalse; + } + //respawn if dead + if ( BotIsDead( bs ) ) { + AIEnter_Respawn( bs ); + return qfalse; + } + //if no enemy + if ( bs->enemy < 0 ) { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + // + BotEntityInfo( bs->enemy, &entinfo ); + if ( EntityIsDead( &entinfo ) ) { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if ( bot_grapple.integer ) { + bs->tfl |= TFL_GRAPPLEHOOK; + } + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + bs->tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + bs->tfl |= TFL_SLIME; + } + //map specific code + BotMapScripts( bs ); + //update the attack inventory values + BotUpdateBattleInventory( bs, bs->enemy ); + //if the bot doesn't want to retreat anymore... probably picked up some nice items + if ( BotWantsToChase( bs ) ) { + //empty the goal stack, when chasing, only the enemy is the goal + trap_BotEmptyGoalStack( bs->gs ); + //go chase the enemy + AIEnter_Battle_Chase( bs ); + return qfalse; + } + //update the last time the enemy was visible + if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy ) ) { + bs->enemyvisible_time = trap_AAS_Time(); + //update the reachability area and origin if possible + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + VectorCopy( entinfo.origin, bs->lastenemyorigin ); + bs->lastenemyareanum = areanum; + } + } + //if the enemy is NOT visible for 4 seconds + if ( bs->enemyvisible_time < trap_AAS_Time() - 4 ) { + AIEnter_Seek_LTG( bs ); + return qfalse; + } + //else if the enemy is NOT visible + else if ( bs->enemyvisible_time < trap_AAS_Time() ) { + //if there is another enemy + if ( BotFindEnemy( bs, -1 ) ) { + AIEnter_Battle_Fight( bs ); + return qfalse; + } + } + // +#ifdef CTF + if ( gametype == GT_CTF ) { + BotCTFRetreatGoals( bs ); + } +#endif //CTF + //use holdable items + BotBattleUseItems( bs ); + //get the current long term goal while retreating + if ( !BotLongTermGoal( bs, bs->tfl, qtrue, &goal ) ) { + return qtrue; + } + //check for nearby goals periodicly + if ( bs->check_time < trap_AAS_Time() ) { + bs->check_time = trap_AAS_Time() + 1; + range = 150; +#ifdef CTF + //if carrying a flag the bot shouldn't be distracted too much + if ( BotCTFCarryingFlag( bs ) ) { + range = 100; + } +#endif //CTF + // + if ( BotNearbyGoal( bs, bs->tfl, &goal, range ) ) { + trap_BotResetLastAvoidReach( bs->ms ); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = trap_AAS_Time() + range / 100 + 1; + AIEnter_Battle_NBG( bs ); + return qfalse; + } + } + //initialize the movement state + BotSetupForMovement( bs ); + //move towards the goal + trap_BotMoveToGoal( &moveresult, bs->ms, &goal, bs->tfl ); + //if the movement failed + if ( moveresult.failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked( bs, &moveresult, qfalse ); + //choose the best weapon to fight with + BotChooseWeapon( bs ); + //if the view is fixed for the movement + if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEW + //|MOVERESULT_SWIMVIEW + ) ) { + VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); + } else if ( !( moveresult.flags & MOVERESULT_MOVEMENTVIEWSET ) + && !( bs->flags & BFL_IDEALVIEWSET ) ) { + attack_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1 ); + //if the bot is skilled anough + if ( attack_skill > 0.3 ) { + BotAimAtEnemy( bs ); + } else { + if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + } else { + vectoangles( moveresult.movedir, bs->ideal_viewangles ); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { + bs->weaponnum = moveresult.weapon; + } + //attack the enemy if possible + BotCheckAttack( bs ); + // + return qtrue; +} + +/* +================== +AIEnter_Battle_NBG +================== +*/ +void AIEnter_Battle_NBG( bot_state_t *bs ) { + BotRecordNodeSwitch( bs, "battle NBG", "" ); + bs->ainode = AINode_Battle_NBG; +} + +/* +================== +AINode_Battle_NBG +================== +*/ +int AINode_Battle_NBG( bot_state_t *bs ) { + int areanum; + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + float attack_skill; + vec3_t target, dir; + + if ( BotIsObserver( bs ) ) { + AIEnter_Observer( bs ); + return qfalse; + } + //if in the intermission + if ( BotIntermission( bs ) ) { + AIEnter_Intermission( bs ); + return qfalse; + } + //respawn if dead + if ( BotIsDead( bs ) ) { + AIEnter_Respawn( bs ); + return qfalse; + } + //if no enemy + if ( bs->enemy < 0 ) { + AIEnter_Seek_NBG( bs ); + return qfalse; + } + // + BotEntityInfo( bs->enemy, &entinfo ); + if ( EntityIsDead( &entinfo ) ) { + AIEnter_Seek_NBG( bs ); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if ( bot_grapple.integer ) { + bs->tfl |= TFL_GRAPPLEHOOK; + } + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + bs->tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + bs->tfl |= TFL_SLIME; + } + // + if ( BotCanAndWantsToRocketJump( bs ) ) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts( bs ); + //update the last time the enemy was visible + if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy ) ) { + bs->enemyvisible_time = trap_AAS_Time(); + //update the reachability area and origin if possible + areanum = BotPointAreaNum( entinfo.origin ); + if ( areanum && trap_AAS_AreaReachability( areanum ) ) { + VectorCopy( entinfo.origin, bs->lastenemyorigin ); + bs->lastenemyareanum = areanum; + } + } + //if the bot has no goal or touches the current goal + if ( !trap_BotGetTopGoal( bs->gs, &goal ) ) { + bs->nbg_time = 0; + } else if ( trap_BotTouchingGoal( bs->origin, &goal ) ) { + bs->nbg_time = 0; + } + // + if ( bs->nbg_time < trap_AAS_Time() ) { + //pop the current goal from the stack + trap_BotPopGoal( bs->gs ); + //if the bot still has a goal + if ( trap_BotGetTopGoal( bs->gs, &goal ) ) { + AIEnter_Battle_Retreat( bs ); + } else { AIEnter_Battle_Fight( bs );} + // + return qfalse; + } + //initialize the movement state + BotSetupForMovement( bs ); + //move towards the goal + trap_BotMoveToGoal( &moveresult, bs->ms, &goal, bs->tfl ); + //if the movement failed + if ( moveresult.failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->nbg_time = 0; + } + // + BotAIBlocked( bs, &moveresult, qfalse ); + //update the attack inventory values + BotUpdateBattleInventory( bs, bs->enemy ); + //choose the best weapon to fight with + BotChooseWeapon( bs ); + //if the view is fixed for the movement + if ( moveresult.flags & MOVERESULT_MOVEMENTVIEW ) { + VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); + } else if ( !( moveresult.flags & MOVERESULT_MOVEMENTVIEWSET ) + && !( bs->flags & BFL_IDEALVIEWSET ) ) { + attack_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1 ); + //if the bot is skilled anough and the enemy is visible + if ( attack_skill > 0.3 ) { + //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) + BotAimAtEnemy( bs ); + } else { + if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { + VectorSubtract( target, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + } else { + vectoangles( moveresult.movedir, bs->ideal_viewangles ); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { + bs->weaponnum = moveresult.weapon; + } + //attack the enemy if possible + BotCheckAttack( bs ); + // + return qtrue; +} diff --git a/src/botai/ai_dmnet.h b/src/botai/ai_dmnet.h new file mode 100644 index 0000000..3a54772 --- /dev/null +++ b/src/botai/ai_dmnet.h @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_dmnet.h + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +#define MAX_NODESWITCHES 50 + +void AIEnter_Intermission( bot_state_t *bs ); +void AIEnter_Observer( bot_state_t *bs ); +void AIEnter_Respawn( bot_state_t *bs ); +void AIEnter_Stand( bot_state_t *bs ); +void AIEnter_Seek_ActivateEntity( bot_state_t *bs ); +void AIEnter_Seek_NBG( bot_state_t *bs ); +void AIEnter_Seek_LTG( bot_state_t *bs ); +void AIEnter_Seek_Camp( bot_state_t *bs ); +void AIEnter_Battle_Fight( bot_state_t *bs ); +void AIEnter_Battle_Chase( bot_state_t *bs ); +void AIEnter_Battle_Retreat( bot_state_t *bs ); +void AIEnter_Battle_NBG( bot_state_t *bs ); +int AINode_Intermission( bot_state_t *bs ); +int AINode_Observer( bot_state_t *bs ); +int AINode_Respawn( bot_state_t *bs ); +int AINode_Stand( bot_state_t *bs ); +int AINode_Seek_ActivateEntity( bot_state_t *bs ); +int AINode_Seek_NBG( bot_state_t *bs ); +int AINode_Seek_LTG( bot_state_t *bs ); +int AINode_Battle_Fight( bot_state_t *bs ); +int AINode_Battle_Chase( bot_state_t *bs ); +int AINode_Battle_Retreat( bot_state_t *bs ); +int AINode_Battle_NBG( bot_state_t *bs ); + +void BotResetNodeSwitches( void ); +void BotDumpNodeSwitches( bot_state_t *bs ); + diff --git a/src/botai/ai_dmq3.c b/src/botai/ai_dmq3.c new file mode 100644 index 0000000..3417c8c --- /dev/null +++ b/src/botai/ai_dmq3.c @@ -0,0 +1,2905 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_dmq3.c + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + + +#include "../game/g_local.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../botai/botai.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +#define IDEAL_ATTACKDIST 140 +#define WEAPONINDEX_MACHINEGUN 2 + +#define MAX_WAYPOINTS 128 + +////////////////// +// from aasfile.h +#define AREACONTENTS_MOVER 1024 +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM ( AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT ) +////////////////// +// +bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; +bot_waypoint_t *botai_freewaypoints; + +//NOTE: not using a cvar which can be updated because the game should be reloaded anyway +int gametype; //game type + +// Rafael gameskill +int gameskill; + +vmCvar_t bot_grapple; +vmCvar_t bot_rocketjump; +vmCvar_t bot_fastchat; +vmCvar_t bot_nochat; +vmCvar_t bot_testrchat; + +vec3_t lastteleport_origin; +float lastteleport_time; +//true when the map changed +int max_bspmodelindex; //maximum BSP model index + +//CTF flag goals +bot_goal_t ctf_redflag; +bot_goal_t ctf_blueflag; + +#ifdef CTF +/* +================== +BotCTFCarryingFlag +================== +*/ +int BotCTFCarryingFlag( bot_state_t *bs ) { + if ( gametype != GT_CTF ) { + return CTF_FLAG_NONE; + } + + if ( bs->inventory[INVENTORY_REDFLAG] > 0 ) { + return CTF_FLAG_RED; + } else if ( bs->inventory[INVENTORY_BLUEFLAG] > 0 ) { + return CTF_FLAG_BLUE; + } + return CTF_FLAG_NONE; +} + +/* +================== +BotCTFTeam +================== +*/ +int BotCTFTeam( bot_state_t *bs ) { + char skin[128], *p; + + if ( gametype != GT_CTF ) { + return CTF_TEAM_NONE; + } + ClientSkin( bs->client, skin, sizeof( skin ) ); + p = strchr( skin, '/' ); + if ( !p ) { + p = skin; + } else { p++;} + if ( Q_stricmp( p, CTF_SKIN_REDTEAM ) == 0 ) { + return CTF_TEAM_RED; + } + if ( Q_stricmp( p, CTF_SKIN_BLUETEAM ) == 0 ) { + return CTF_TEAM_BLUE; + } + return CTF_TEAM_NONE; +} + +/* +================== +BotCTFRetreatGoals +================== +*/ +void BotCTFRetreatGoals( bot_state_t *bs ) { + //when carrying a flag in ctf the bot should rush to the base + if ( BotCTFCarryingFlag( bs ) ) { + //if not already rushing to the base + if ( bs->ltgtype != LTG_RUSHBASE ) { + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + } + } +} + +/* +================== +BotCTFSeekGoals +================== +*/ +void BotCTFSeekGoals( bot_state_t *bs ) { + float rnd; + + //when carrying a flag in ctf the bot should rush to the base + if ( BotCTFCarryingFlag( bs ) ) { + //if not already rushing to the base + if ( bs->ltgtype != LTG_RUSHBASE ) { + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + } + return; + } + //if the bot is roaming + if ( bs->ctfroam_time > trap_AAS_Time() ) { + return; + } + //if already a CTF or team goal + if ( bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL ) { + return; + } + //if the bot has anough aggression to decide what to do + if ( BotAggression( bs ) < 50 ) { + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //get the flag or defend the base + rnd = random(); + if ( rnd < 0.33 && ctf_redflag.areanum && ctf_blueflag.areanum ) { + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME; + } else if ( rnd < 0.66 && ctf_redflag.areanum && ctf_blueflag.areanum ) { + //FIXME: do not always use the base flag + if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) { + memcpy( &bs->teamgoal, &ctf_redflag, sizeof( bot_goal_t ) ); + } else { memcpy( &bs->teamgoal, &ctf_blueflag, sizeof( bot_goal_t ) );} + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stop defending the base + bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + } else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = trap_AAS_Time() + CTF_ROAM_TIME; + } +#ifdef DEBUG + BotPrintTeamGoal( bs ); +#endif //DEBUG +} + +#endif //CTF + +/* +================== +BotPointAreaNum +================== +*/ +int BotPointAreaNum( vec3_t origin ) { + int areanum, numareas, areas[10]; + vec3_t end, ofs; + #define BOTAREA_JIGGLE_DIST 32 + + areanum = trap_AAS_PointAreaNum( origin ); + if ( areanum ) { + return areanum; + } + VectorCopy( origin, end ); + end[2] += 10; + numareas = trap_AAS_TraceAreas( origin, end, areas, NULL, 10 ); + if ( numareas > 0 ) { + return areas[0]; + } + + // Ridah, jiggle them around to look for a fuzzy area, helps LARGE characters reach destinations that are against walls + ofs[2] = 10; + for ( ofs[0] = -BOTAREA_JIGGLE_DIST; ofs[0] <= BOTAREA_JIGGLE_DIST; ofs[0] += BOTAREA_JIGGLE_DIST * 2 ) + for ( ofs[1] = -BOTAREA_JIGGLE_DIST; ofs[1] <= BOTAREA_JIGGLE_DIST; ofs[1] += BOTAREA_JIGGLE_DIST * 2 ) { + VectorAdd( origin, ofs, end ); + numareas = trap_AAS_TraceAreas( origin, end, areas, NULL, 10 ); + if ( numareas > 0 ) { + return areas[0]; + } + } + + return 0; +} + +/* +================== +ClientName +================== +*/ +char *ClientName( int client, char *name, int size ) { + char buf[MAX_INFO_STRING]; + + if ( client < 0 || client >= MAX_CLIENTS ) { + BotAI_Print( PRT_ERROR, "ClientName: client out of range\n" ); + return "[client out of range]"; + } + trap_GetConfigstring( CS_PLAYERS + client, buf, sizeof( buf ) ); + strncpy( name, Info_ValueForKey( buf, "n" ), size - 1 ); + name[size - 1] = '\0'; + Q_CleanStr( name ); + return name; +} + +/* +================== +ClientSkin +================== +*/ +char *ClientSkin( int client, char *skin, int size ) { + char buf[MAX_INFO_STRING]; + + if ( client < 0 || client >= MAX_CLIENTS ) { + BotAI_Print( PRT_ERROR, "ClientSkin: client out of range\n" ); + return "[client out of range]"; + } + trap_GetConfigstring( CS_PLAYERS + client, buf, sizeof( buf ) ); + strncpy( skin, Info_ValueForKey( buf, "model" ), size - 1 ); + skin[size - 1] = '\0'; + return skin; +} + +/* +================== +ClientFromName +================== +*/ +int ClientFromName( char *name ) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + Q_CleanStr( buf ); + if ( !Q_stricmp( Info_ValueForKey( buf, "n" ), name ) ) { + return i; + } + } + return -1; +} + +/* +================== +stristr +================== +*/ +char *stristr( char *str, char *charset ) { + int i; + + while ( *str ) { + for ( i = 0; charset[i] && str[i]; i++ ) { + if ( toupper( charset[i] ) != toupper( str[i] ) ) { + break; + } + } + if ( !charset[i] ) { + return str; + } + str++; + } + return NULL; +} + +/* +================== +EasyClientName +================== +*/ +char *EasyClientName( int client, char *buf, int size ) { + int i; + char *str1, *str2, *ptr, c; + char name[128]; + + strcpy( name, ClientName( client, name, sizeof( name ) ) ); + for ( i = 0; name[i]; i++ ) name[i] &= 127; + //remove all spaces + for ( ptr = strstr( name, " " ); ptr; ptr = strstr( name, " " ) ) { + memmove( ptr, ptr + 1, strlen( ptr + 1 ) + 1 ); + } + //check for [x] and ]x[ clan names + str1 = strstr( name, "[" ); + str2 = strstr( name, "]" ); + if ( str1 && str2 ) { + if ( str2 > str1 ) { + memmove( str1, str2 + 1, strlen( str2 + 1 ) + 1 ); + } else { memmove( str2, str1 + 1, strlen( str1 + 1 ) + 1 );} + } + //remove Mr prefix + if ( ( name[0] == 'm' || name[0] == 'M' ) && + ( name[1] == 'r' || name[1] == 'R' ) ) { + memmove( name, name + 2, strlen( name + 2 ) + 1 ); + } + //only allow lower case alphabet characters + ptr = name; + while ( *ptr ) { + c = *ptr; + if ( ( c >= 'a' && c <= 'z' ) || + ( c >= '0' && c <= '9' ) || c == '_' ) { + ptr++; + } else if ( c >= 'A' && c <= 'Z' ) { + *ptr += 'a' - 'A'; + ptr++; + } else { + memmove( ptr, ptr + 1, strlen( ptr + 1 ) + 1 ); + } + } + strncpy( buf, name, size - 1 ); + buf[size - 1] = '\0'; + return buf; +} + +/* +================== +BotChooseWeapon +================== +*/ +void BotChooseWeapon( bot_state_t *bs ) { + int newweaponnum; + + if ( bs->cur_ps.weaponstate == WEAPON_RAISING || + bs->cur_ps.weaponstate == WEAPON_DROPPING ) { + trap_EA_SelectWeapon( bs->client, bs->weaponnum ); + } else { + newweaponnum = trap_BotChooseBestFightWeapon( bs->ws, bs->inventory ); + if ( bs->weaponnum != newweaponnum ) { + bs->weaponchange_time = trap_AAS_Time(); + } + bs->weaponnum = newweaponnum; + //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); + trap_EA_SelectWeapon( bs->client, bs->weaponnum ); + } +} + +/* +================== +BotSetupForMovement +================== +*/ +void BotSetupForMovement( bot_state_t *bs ) { + bot_initmove_t initmove; + + memset( &initmove, 0, sizeof( bot_initmove_t ) ); + VectorCopy( bs->cur_ps.origin, initmove.origin ); + VectorCopy( bs->cur_ps.velocity, initmove.velocity ); + VectorCopy( bs->cur_ps.origin, initmove.viewoffset ); + initmove.viewoffset[2] += bs->cur_ps.viewheight; + initmove.entitynum = bs->entitynum; + initmove.client = bs->client; + initmove.thinktime = bs->thinktime; + //set the onground flag + if ( bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) { + initmove.or_moveflags |= MFL_ONGROUND; + } + //set the teleported flag + if ( ( bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK ) && ( bs->cur_ps.pm_time > 0 ) ) { + initmove.or_moveflags |= MFL_TELEPORTED; + } + //set the waterjump flag + if ( ( bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP ) && ( bs->cur_ps.pm_time > 0 ) ) { + initmove.or_moveflags |= MFL_WATERJUMP; + } + //set presence type + if ( bs->cur_ps.pm_flags & PMF_DUCKED ) { + initmove.presencetype = PRESENCE_CROUCH; + } else { initmove.presencetype = PRESENCE_NORMAL;} + // + if ( bs->walker > 0.5 ) { + initmove.or_moveflags |= MFL_WALK; + } + // + VectorCopy( bs->viewangles, initmove.viewangles ); + // + trap_BotInitMoveState( bs->ms, &initmove ); +} + +/* +================== +BotUpdateInventory +================== +*/ +void BotUpdateInventory( bot_state_t *bs ) { + //armor + bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; + //weapons + bs->inventory[INVENTORY_LUGER] = COM_BitCheck( bs->cur_ps.weapons, ( WP_LUGER ) ); + bs->inventory[INVENTORY_MAUSER] = COM_BitCheck( bs->cur_ps.weapons, ( WP_MAUSER ) ); + bs->inventory[INVENTORY_MP40] = COM_BitCheck( bs->cur_ps.weapons, ( WP_MP40 ) ); + bs->inventory[INVENTORY_ROCKETLAUNCHER] = COM_BitCheck( bs->cur_ps.weapons, ( WP_ROCKET_LAUNCHER ) ); + bs->inventory[INVENTORY_GRENADELAUNCHER] = COM_BitCheck( bs->cur_ps.weapons, ( WP_GRENADE_LAUNCHER ) ); + bs->inventory[INVENTORY_VENOM] = COM_BitCheck( bs->cur_ps.weapons, ( WP_VENOM ) ); + bs->inventory[INVENTORY_FLAMETHROWER] = COM_BitCheck( bs->cur_ps.weapons, ( WP_FLAMETHROWER ) ); + bs->inventory[INVENTORY_CROSS] = COM_BitCheck( bs->cur_ps.weapons, ( WP_CROSS ) ); + bs->inventory[INVENTORY_GAUNTLET] = COM_BitCheck( bs->cur_ps.weapons, ( WP_GAUNTLET ) ); + + // ammo + bs->inventory[INVENTORY_9MM] = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_MP40 )]; + bs->inventory[INVENTORY_792MM] = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_MAUSER )]; + bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_ROCKET_LAUNCHER )]; + bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_GRENADE_LAUNCHER )]; + bs->inventory[INVENTORY_127MM] = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_VENOM )]; + bs->inventory[INVENTORY_FUEL] = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_FLAMETHROWER )]; + bs->inventory[INVENTORY_CHARGES] = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_CROSS )]; + + //powerups + bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; + bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; + bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; + bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; + bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; + bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; + bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; + bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; + bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; + bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; + bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; + // +} + +/* +================== +BotUpdateBattleInventory +================== +*/ +void BotUpdateBattleInventory( bot_state_t *bs, int enemy ) { + vec3_t dir; + aas_entityinfo_t entinfo; + + BotEntityInfo( enemy, &entinfo ); + VectorSubtract( entinfo.origin, bs->origin, dir ); + bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; + dir[2] = 0; + bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength( dir ); + //FIXME: add num visible enemies and num visible team mates to the inventory +} + +/* +================== +BotBattleUseItems +================== +*/ +void BotBattleUseItems( bot_state_t *bs ) { + if ( bs->inventory[INVENTORY_HEALTH] < 40 ) { + if ( bs->inventory[INVENTORY_TELEPORTER] > 0 ) { + trap_EA_Use( bs->client ); + } + if ( bs->inventory[INVENTORY_MEDKIT] > 0 ) { + trap_EA_Use( bs->client ); + } + } +} + +/* +================== +BotSetTeleportTime +================== +*/ +void BotSetTeleportTime( bot_state_t *bs ) { + if ( ( bs->cur_ps.eFlags ^ bs->last_eFlags ) & EF_TELEPORT_BIT ) { + bs->teleport_time = trap_AAS_Time(); + } + bs->last_eFlags = bs->cur_ps.eFlags; +} + +/* +================== +BotIsDead +================== +*/ +qboolean BotIsDead( bot_state_t *bs ) { + return ( bs->cur_ps.pm_type == PM_DEAD ); +} + +/* +================== +BotIsObserver +================== +*/ +qboolean BotIsObserver( bot_state_t *bs ) { + char buf[MAX_INFO_STRING]; + if ( bs->cur_ps.pm_type == PM_SPECTATOR ) { + return qtrue; + } + trap_GetConfigstring( CS_PLAYERS + bs->client, buf, sizeof( buf ) ); + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + return qtrue; + } + return qfalse; +} + +/* +================== +BotIntermission +================== +*/ +qboolean BotIntermission( bot_state_t *bs ) { + //NOTE: we shouldn't look at the game code... + if ( level.intermissiontime ) { + return qtrue; + } + return ( bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION ); +} + + +/* +============== +BotInLava +============== +*/ +qboolean BotInLava( bot_state_t *bs ) { + vec3_t feet; + + VectorCopy( bs->origin, feet ); + feet[2] -= 23; + return ( trap_AAS_PointContents( feet ) & CONTENTS_LAVA ); +} + +/* +============== +BotInSlime +============== +*/ +qboolean BotInSlime( bot_state_t *bs ) { + vec3_t feet; + + VectorCopy( bs->origin, feet ); + feet[2] -= 23; + return ( trap_AAS_PointContents( feet ) & CONTENTS_SLIME ); +} + +/* +================== +EntityIsDead +================== +*/ +qboolean EntityIsDead( aas_entityinfo_t *entinfo ) { + playerState_t ps; + + if ( entinfo->number >= 0 && entinfo->number < MAX_CLIENTS ) { + //retrieve the current client state + BotAI_GetClientState( entinfo->number, &ps ); + if ( ps.pm_type != PM_NORMAL ) { + return qtrue; + } + } + return qfalse; +} + +/* +================== +EntityIsInvisible +================== +*/ +qboolean EntityIsInvisible( aas_entityinfo_t *entinfo ) { + if ( entinfo->powerups & ( 1 << PW_INVIS ) ) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsShooting +================== +*/ +qboolean EntityIsShooting( aas_entityinfo_t *entinfo ) { + if ( entinfo->flags & EF_FIRING ) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsChatting +================== +*/ +qboolean EntityIsChatting( aas_entityinfo_t *entinfo ) { + if ( entinfo->flags & EF_TALK ) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityHasQuad +================== +*/ +qboolean EntityHasQuad( aas_entityinfo_t *entinfo ) { + if ( entinfo->powerups & ( 1 << PW_QUAD ) ) { + return qtrue; + } + return qfalse; +} + +/* +================== +BotCreateWayPoint +================== +*/ +bot_waypoint_t *BotCreateWayPoint( char *name, vec3_t origin, int areanum ) { + bot_waypoint_t *wp; + vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; + + wp = botai_freewaypoints; + if ( !wp ) { + BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); + return NULL; + } + botai_freewaypoints = botai_freewaypoints->next; + + Q_strncpyz( wp->name, name, sizeof( wp->name ) ); + VectorCopy( origin, wp->goal.origin ); + VectorCopy( waypointmins, wp->goal.mins ); + VectorCopy( waypointmaxs, wp->goal.maxs ); + wp->goal.areanum = areanum; + wp->next = NULL; + wp->prev = NULL; + return wp; +} + +/* +================== +BotFindWayPoint +================== +*/ +bot_waypoint_t *BotFindWayPoint( bot_waypoint_t *waypoints, char *name ) { + bot_waypoint_t *wp; + + for ( wp = waypoints; wp; wp = wp->next ) { + if ( !Q_stricmp( wp->name, name ) ) { + return wp; + } + } + return NULL; +} + +/* +================== +BotFreeWaypoints +================== +*/ +void BotFreeWaypoints( bot_waypoint_t *wp ) { + bot_waypoint_t *nextwp; + + for (; wp; wp = nextwp ) { + nextwp = wp->next; + wp->next = botai_freewaypoints; + botai_freewaypoints = wp; + } +} + +/* +================== +BotInitWaypoints +================== +*/ +void BotInitWaypoints( void ) { + int i; + + botai_freewaypoints = NULL; + for ( i = 0; i < MAX_WAYPOINTS; i++ ) { + botai_waypoints[i].next = botai_freewaypoints; + botai_freewaypoints = &botai_waypoints[i]; + } +} + +/* +================== +TeamPlayIsOn +================== +*/ +int TeamPlayIsOn( void ) { + return ( gametype == GT_TEAM || gametype == GT_CTF ); +} + +/* +================== +BotAggression + +FIXME: move this to external fuzzy logic + + NOTE!!: I made no changes to this code for wolf weapon awareness. (SA) +================== +*/ +float BotAggression( bot_state_t *bs ) { + //if the bot has quad + if ( bs->inventory[INVENTORY_QUAD] ) { + //if the bot is not holding the gauntlet or the enemy is really nearby + if ( bs->weaponnum != WP_GAUNTLET || + bs->inventory[ENEMY_HORIZONTAL_DIST] < 80 ) { + return 70; + } + } + //if the enemy is located way higher than the bot + if ( bs->inventory[ENEMY_HEIGHT] > 200 ) { + return 0; + } + //if the bot is very low on health + if ( bs->inventory[INVENTORY_HEALTH] < 60 ) { + return 0; + } + //if the bot is low on health + if ( bs->inventory[INVENTORY_HEALTH] < 80 ) { + //if the bot has insufficient armor + if ( bs->inventory[INVENTORY_ARMOR] < 40 ) { + return 0; + } + } +// //if the bot can use the bfg +// if (bs->inventory[INVENTORY_BFG10K] > 0 && +// bs->inventory[INVENTORY_BFGAMMO] > 7) return 100; +// //if the bot can use the railgun +// if (bs->inventory[INVENTORY_RAILGUN] > 0 && +// bs->inventory[INVENTORY_SLUGS] > 5) return 95; + //if the bot can use the lightning gun + if ( bs->inventory[INVENTORY_FLAMETHROWER] > 0 && + bs->inventory[INVENTORY_FUEL] > 50 ) { + return 90; + } + //if the bot can use the rocketlauncher + if ( bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_ROCKETS] > 5 ) { + return 90; + } + //if the bot can use the SP5 + if ( bs->inventory[INVENTORY_SP5] > 0 && + bs->inventory[INVENTORY_SP5AMMO] > 40 ) { + return 85; + } + //if the bot can use the grenade launcher + if ( bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && + bs->inventory[INVENTORY_GRENADES] > 10 ) { + return 80; + } +// //if the bot can use the shotgun +// if (bs->inventory[INVENTORY_SHOTGUN] > 0 && +// bs->inventory[INVENTORY_SHELLS] > 10) return 50; + //otherwise the bot is not feeling too good + return 0; +} + +/* +================== +BotWantsToRetreat +================== +*/ +int BotWantsToRetreat( bot_state_t *bs ) { +#ifdef CTF + //always retreat when carrying a CTF flag + if ( BotCTFCarryingFlag( bs ) ) { + return qtrue; + } + //if the bot is getting the flag + if ( bs->ltgtype == LTG_GETFLAG ) { + return qtrue; + } +#endif //CTF + if ( BotAggression( bs ) < 50 ) { + return qtrue; + } + return qfalse; +} + +/* +================== +BotWantsToChase +================== +*/ +int BotWantsToChase( bot_state_t *bs ) { +#ifdef CTF + //always retreat when carrying a CTF flag + if ( BotCTFCarryingFlag( bs ) ) { + return qfalse; + } + //if the bot is getting the flag + if ( bs->ltgtype == LTG_GETFLAG ) { + return qfalse; + } +#endif //CTF + if ( BotAggression( bs ) > 50 ) { + return qtrue; + } + return qfalse; +} + +/* +================== +BotWantsToHelp +================== +*/ +int BotWantsToHelp( bot_state_t *bs ) { + return qtrue; +} + +/* +================== +BotCanAndWantsToRocketJump +================== +*/ +int BotCanAndWantsToRocketJump( bot_state_t *bs ) { + float rocketjumper; + + //if rocket jumping is disabled + if ( !bot_rocketjump.integer ) { + return qfalse; + } + //if no rocket launcher + if ( bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 ) { + return qfalse; + } + //if low on rockets + if ( bs->inventory[INVENTORY_ROCKETS] < 3 ) { + return qfalse; + } + //never rocket jump with the Quad + if ( bs->inventory[INVENTORY_QUAD] ) { + return qfalse; + } + //if low on health + if ( bs->inventory[INVENTORY_HEALTH] < 60 ) { + return qfalse; + } + //if not full health + if ( bs->inventory[INVENTORY_HEALTH] < 90 ) { + //if the bot has insufficient armor + if ( bs->inventory[INVENTORY_ARMOR] < 40 ) { + return qfalse; + } + } + rocketjumper = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1 ); + if ( rocketjumper < 0.5 ) { + return qfalse; + } + return qtrue; +} + +/* +================== +BotGoCamp +================== +*/ +void BotGoCamp( bot_state_t *bs, bot_goal_t *goal ) { + float camper; + + //set message time to zero so bot will NOT show any message + bs->teammessage_time = 0; + //set the ltg type + bs->ltgtype = LTG_CAMP; + //set the team goal + memcpy( &bs->teamgoal, goal, sizeof( bot_goal_t ) ); + //get the team goal time + camper = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CAMPER, 0, 1 ); + if ( camper > 0.99 ) { + bs->teamgoal_time = 99999; + } else { bs->teamgoal_time = 120 + 180 * camper + random() * 15;} + //set the last time the bot started camping + bs->camp_time = trap_AAS_Time(); + //the teammate that requested the camping + bs->teammate = 0; + //do NOT type arrive message + bs->arrive_time = 1; +} + +/* +================== +BotWantsToCamp +================== +*/ +int BotWantsToCamp( bot_state_t *bs ) { + float camper; + int cs, traveltime, besttraveltime; + bot_goal_t goal, bestgoal; + + camper = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CAMPER, 0, 1 ); + if ( camper < 0.1 ) { + return qfalse; + } + //if the bot has a team goal + if ( bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMP || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL ) { + return qfalse; + } + //if camped recently + if ( bs->camp_time > trap_AAS_Time() - 60 + 300 * ( 1 - camper ) ) { + return qfalse; + } + // + if ( random() > camper ) { + bs->camp_time = trap_AAS_Time(); + return qfalse; + } + //if the bot isn't healthy anough + if ( BotAggression( bs ) < 50 ) { + return qfalse; + } + //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo + if ( ( bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10] ) +// && (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) +// && (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10) + ) { + return qfalse; + } + //find the closest camp spot + besttraveltime = 99999; + for ( cs = trap_BotGetNextCampSpotGoal( 0, &goal ); cs; cs = trap_BotGetNextCampSpotGoal( cs, &goal ) ) { + traveltime = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT ); + if ( traveltime && traveltime < besttraveltime ) { + besttraveltime = traveltime; + memcpy( &bestgoal, &goal, sizeof( bot_goal_t ) ); + } + } + if ( besttraveltime > 150 ) { + return qfalse; + } + //ok found a camp spot, go camp there + BotGoCamp( bs, &bestgoal ); + // + return qtrue; +} + +/* +================== +BotDontAvoid +================== +*/ +void BotDontAvoid( bot_state_t *bs, char *itemname ) { + bot_goal_t goal; + int num; + + num = trap_BotGetLevelItemGoal( -1, itemname, &goal ); + while ( num >= 0 ) { + trap_BotRemoveFromAvoidGoals( bs->gs, goal.number ); + num = trap_BotGetLevelItemGoal( num, itemname, &goal ); + } +} + +/* +================== +BotGoForPowerups +================== +*/ +void BotGoForPowerups( bot_state_t *bs ) { + + //don't avoid any of the powerups anymore + BotDontAvoid( bs, "Quad Damage" ); + BotDontAvoid( bs, "Regeneration" ); + BotDontAvoid( bs, "Battle Suit" ); + BotDontAvoid( bs, "Speed" ); + BotDontAvoid( bs, "Invisibility" ); + //BotDontAvoid(bs, "Flight"); + //reset the long term goal time so the bot will go for the powerup + //NOTE: the long term goal type doesn't change + bs->ltg_time = 0; +} + +/* +================== +BotRoamGoal +================== +*/ +void BotRoamGoal( bot_state_t *bs, vec3_t goal ) { + float len, r1, r2, sign, n; + int pc; + vec3_t dir, bestorg, belowbestorg; + bsp_trace_t trace; + + for ( n = 0; n < 10; n++ ) { + //start at the bot origin + VectorCopy( bs->origin, bestorg ); + r1 = random(); + if ( r1 < 0.8 ) { + //add a random value to the x-coordinate + r2 = random(); + if ( r2 < 0.5 ) { + sign = -1; + } else { sign = 1;} + bestorg[0] += sign * 700 * random() + 50; + } + if ( r1 > 0.2 ) { + //add a random value to the y-coordinate + r2 = random(); + if ( r2 < 0.5 ) { + sign = -1; + } else { sign = 1;} + bestorg[1] += sign * 700 * random() + 50; + } + //add a random value to the z-coordinate (NOTE: 48 = maxjump?) + bestorg[2] += 3 * 48 * random() - 2 * 48 - 1; + //trace a line from the origin to the roam target + BotAI_Trace( &trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID ); + //direction and length towards the roam target + VectorSubtract( bestorg, bs->origin, dir ); + len = VectorNormalize( dir ); + //if the roam target is far away anough + if ( len > 200 ) { + //the roam target is in the given direction before walls + VectorScale( dir, len * trace.fraction - 40, dir ); + VectorAdd( bs->origin, dir, bestorg ); + //get the coordinates of the floor below the roam target + belowbestorg[0] = bestorg[0]; + belowbestorg[1] = bestorg[1]; + belowbestorg[2] = bestorg[2] - 800; + BotAI_Trace( &trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID ); + // + if ( !trace.startsolid ) { + trace.endpos[2]++; + pc = trap_PointContents( trace.endpos,bs->entitynum ); + if ( !( pc & CONTENTS_LAVA ) ) { //----(SA) modified since slime is no longer deadly +// if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { + VectorCopy( bestorg, goal ); + return; + } + } + } + } + VectorCopy( bestorg, goal ); +} + +/* +================== +BotAttackMove +================== +*/ +bot_moveresult_t BotAttackMove( bot_state_t *bs, int tfl ) { + int movetype, i; + float attack_skill, jumper, croucher, dist, strafechange_time; + float attack_dist, attack_range; + vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + bot_goal_t goal; + + if ( bs->attackchase_time > trap_AAS_Time() ) { + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy( bs->lastenemyorigin, goal.origin ); + VectorSet( goal.mins, -8, -8, -8 ); + VectorSet( goal.maxs, 8, 8, 8 ); + //initialize the movement state + BotSetupForMovement( bs ); + //move towards the goal + trap_BotMoveToGoal( &moveresult, bs->ms, &goal, tfl ); + return moveresult; + } + // + memset( &moveresult, 0, sizeof( bot_moveresult_t ) ); + // + attack_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1 ); + jumper = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_JUMPER, 0, 1 ); + croucher = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CROUCHER, 0, 1 ); + //if the bot is really stupid + if ( attack_skill < 0.2 ) { + return moveresult; + } + //initialize the movement state + BotSetupForMovement( bs ); + //get the enemy entity info + BotEntityInfo( bs->enemy, &entinfo ); + //direction towards the enemy + VectorSubtract( entinfo.origin, bs->origin, forward ); + //the distance towards the enemy + dist = VectorNormalize( forward ); + VectorNegate( forward, backward ); + //walk, crouch or jump + movetype = MOVE_WALK; + // + if ( bs->attackcrouch_time < trap_AAS_Time() - 1 ) { + if ( random() < jumper ) { + movetype = MOVE_JUMP; + } + //wait at least one second before crouching again + else if ( bs->attackcrouch_time < trap_AAS_Time() - 1 && random() < croucher ) { + bs->attackcrouch_time = trap_AAS_Time() + croucher * 5; + } + } + if ( bs->attackcrouch_time > trap_AAS_Time() ) { + movetype = MOVE_CROUCH; + } + //if the bot should jump + if ( movetype == MOVE_JUMP ) { + //if jumped last frame + if ( bs->attackjump_time > trap_AAS_Time() ) { + movetype = MOVE_WALK; + } else { + bs->attackjump_time = trap_AAS_Time() + 1; + } + } + if ( bs->cur_ps.weapon == WP_GAUNTLET ) { + attack_dist = 0; + attack_range = 0; + } else { + attack_dist = IDEAL_ATTACKDIST; + attack_range = 40; + } + //if the bot is stupid + if ( attack_skill <= 0.4 ) { + //just walk to or away from the enemy + if ( dist > attack_dist + attack_range ) { + if ( trap_BotMoveInDirection( bs->ms, forward, 400, movetype ) ) { + return moveresult; + } + } + if ( dist < attack_dist - attack_range ) { + if ( trap_BotMoveInDirection( bs->ms, backward, 400, movetype ) ) { + return moveresult; + } + } + return moveresult; + } + //increase the strafe time + bs->attackstrafe_time += bs->thinktime; + //get the strafe change time + strafechange_time = 0.4 + ( 1 - attack_skill ) * 0.2; + if ( attack_skill > 0.7 ) { + strafechange_time += crandom() * 0.2; + } + //if the strafe direction should be changed + if ( bs->attackstrafe_time > strafechange_time ) { + //some magic number :) + if ( random() > 0.935 ) { + //flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + } + // + for ( i = 0; i < 2; i++ ) { + hordir[0] = forward[0]; + hordir[1] = forward[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + //get the sideward vector + CrossProduct( hordir, up, sideward ); + //reverse the vector depending on the strafe direction + if ( bs->flags & BFL_STRAFERIGHT ) { + VectorNegate( sideward, sideward ); + } + //randomly go back a little + if ( random() > 0.9 ) { + VectorAdd( sideward, backward, sideward ); + } else { + //walk forward or backward to get at the ideal attack distance + if ( dist > attack_dist + attack_range ) { + VectorAdd( sideward, forward, sideward ); + } else if ( dist < attack_dist - attack_range ) { + VectorAdd( sideward, backward, sideward ); + } + } + //perform the movement + if ( trap_BotMoveInDirection( bs->ms, sideward, 400, movetype ) ) { + return moveresult; + } + //movement failed, flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + //bot couldn't do any usefull movement +// bs->attackchase_time = AAS_Time() + 6; + return moveresult; +} + +/* +================== +BotSameTeam +================== +*/ +int BotSameTeam( bot_state_t *bs, int entnum ) { + char info1[128], info2[128]; + + if ( bs->client < 0 || bs->client >= MAX_CLIENTS ) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if ( entnum < 0 || entnum >= MAX_CLIENTS ) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if ( gametype == GT_TEAM || gametype == GT_CTF ) { + trap_GetConfigstring( CS_PLAYERS + bs->client, info1, sizeof( info1 ) ); + trap_GetConfigstring( CS_PLAYERS + entnum, info2, sizeof( info2 ) ); + // + if ( atoi( Info_ValueForKey( info1, "t" ) ) == atoi( Info_ValueForKey( info2, "t" ) ) ) { + return qtrue; + } + } + return qfalse; +} + +/* +================== +InFieldOfVision +================== +*/ +qboolean InFieldOfVision( vec3_t viewangles, float fov, vec3_t angles ) { + int i; + float diff, angle; + + for ( i = 0; i < 2; i++ ) { + angle = AngleMod( viewangles[i] ); + angles[i] = AngleMod( angles[i] ); + diff = angles[i] - angle; + if ( angles[i] > angle ) { + if ( diff > 180.0 ) { + diff -= 360.0; + } + } else { + if ( diff < -180.0 ) { + diff += 360.0; + } + } + if ( diff > 0 ) { + if ( diff > fov * 0.5 ) { + return qfalse; + } + } else { + if ( diff < -fov * 0.5 ) { + return qfalse; + } + } + } + return qtrue; +} + +/* +================== +BotEntityVisible + +returns visibility in the range [0, 1] taking fog and water surfaces into account +================== +*/ +float BotEntityVisible( int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent ) { + int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; + float fogdist, waterfactor, vis, bestvis; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t dir, entangles, start, end, middle; + + //calculate middle of bounding box + BotEntityInfo( ent, &entinfo ); + VectorAdd( entinfo.mins, entinfo.maxs, middle ); + VectorScale( middle, 0.5, middle ); + VectorAdd( entinfo.origin, middle, middle ); + //check if entity is within field of vision + VectorSubtract( middle, eye, dir ); + vectoangles( dir, entangles ); + if ( !InFieldOfVision( viewangles, fov, entangles ) ) { + return 0; + } + // + pc = trap_AAS_PointContents( eye ); + infog = ( pc & CONTENTS_SOLID ); + inwater = ( pc & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ); + // + bestvis = 0; + for ( i = 0; i < 3; i++ ) { + //if the point is not in potential visible sight + //if (!AAS_inPVS(eye, middle)) continue; + // + contents_mask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + passent = viewer; + hitent = ent; + VectorCopy( eye, start ); + VectorCopy( middle, end ); + //if the entity is in water, lava or slime + if ( trap_AAS_PointContents( middle ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { + contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); + } + //if eye is in water, lava or slime + if ( inwater ) { + if ( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { + passent = ent; + hitent = viewer; + VectorCopy( middle, start ); + VectorCopy( eye, end ); + } + contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); + } + //trace from start to end + BotAI_Trace( &trace, start, NULL, NULL, end, passent, contents_mask ); + //if water was hit + waterfactor = 1.0; + if ( trace.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { + //if the water surface is translucent + if ( 1 ) { + //trace through the water + contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); + BotAI_Trace( &trace, trace.endpos, NULL, NULL, end, passent, contents_mask ); + waterfactor = 0.5; + } + } + //if a full trace or the hitent was hit + if ( trace.fraction >= 1 || trace.ent == hitent ) { + //check for fog, assuming there's only one fog brush where + //either the viewer or the entity is in or both are in + otherinfog = ( trap_AAS_PointContents( middle ) & CONTENTS_FOG ); + if ( infog && otherinfog ) { + VectorSubtract( trace.endpos, eye, dir ); + fogdist = VectorLength( dir ); + } else if ( infog ) { + VectorCopy( trace.endpos, start ); + BotAI_Trace( &trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG ); + VectorSubtract( eye, trace.endpos, dir ); + fogdist = VectorLength( dir ); + } else if ( otherinfog ) { + VectorCopy( trace.endpos, end ); + BotAI_Trace( &trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG ); + VectorSubtract( end, trace.endpos, dir ); + fogdist = VectorLength( dir ); + } else { + //if the entity and the viewer are not in fog assume there's no fog in between + fogdist = 0; + } + //decrease visibility with the view distance through fog + vis = 1 / ( ( fogdist * fogdist * 0.001 ) < 1 ? 1 : ( fogdist * fogdist * 0.001 ) ); + //if entering water visibility is reduced + vis *= waterfactor; + // + if ( vis > bestvis ) { + bestvis = vis; + } + //if pretty much no fog + if ( bestvis >= 0.95 ) { + return bestvis; + } + } + //check bottom and top of bounding box as well + if ( i == 0 ) { + middle[2] += entinfo.mins[2]; + } else if ( i == 1 ) { + middle[2] += entinfo.maxs[2] - entinfo.mins[2]; + } + } + return bestvis; +} + +/* +================== +BotFindEnemy +================== +*/ +int BotFindEnemy( bot_state_t *bs, int curenemy ) { + int i, healthdecrease; + float fov, dist, curdist, alertness, easyfragger, vis; + aas_entityinfo_t entinfo, curenemyinfo; + vec3_t dir, angles; + + alertness = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_ALERTNESS, 0, 1 ); + easyfragger = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1 ); + //check if the health decreased + healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; + //remember the current health value + bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; + // + if ( curenemy >= 0 ) { + BotEntityInfo( curenemy, &curenemyinfo ); + VectorSubtract( curenemyinfo.origin, bs->origin, dir ); + curdist = VectorLength( dir ); + } else { + curdist = 0; + } + // + for ( i = 0; i < MAX_CLIENTS; i++ ) { + + if ( i == bs->client ) { + continue; + } + //if it's the current enemy + if ( i == curenemy ) { + continue; + } + // + BotEntityInfo( i, &entinfo ); + // + if ( !entinfo.valid ) { + continue; + } + //if the enemy isn't dead and the enemy isn't the bot self + if ( EntityIsDead( &entinfo ) || entinfo.number == bs->entitynum ) { + continue; + } + //if the enemy is invisible and not shooting + if ( EntityIsInvisible( &entinfo ) && !EntityIsShooting( &entinfo ) ) { + continue; + } + //if not an easy fragger don't shoot at chatting players + if ( easyfragger < 0.5 && EntityIsChatting( &entinfo ) ) { + continue; + } + // + if ( lastteleport_time > trap_AAS_Time() - 3 ) { + VectorSubtract( entinfo.origin, lastteleport_origin, dir ); + if ( VectorLength( dir ) < 70 ) { + continue; + } + } + //calculate the distance towards the enemy + VectorSubtract( entinfo.origin, bs->origin, dir ); + dist = VectorLength( dir ); + //if this enemy is further away than the current one + if ( curenemy >= 0 && dist > curdist ) { + continue; + } + //if the bot has no + if ( dist > 900 + alertness * 4000 ) { + continue; + } + //if on the same team + if ( BotSameTeam( bs, i ) ) { + continue; + } + //if the bot's health decreased or the enemy is shooting + if ( curenemy < 0 && ( healthdecrease || EntityIsShooting( &entinfo ) ) ) { + fov = 360; + } else { fov = 90 + 270 - ( 270 - ( dist > 810 ? 810 : dist ) / 3 );} + //check if the enemy visibility + vis = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, fov, i ); + if ( vis <= 0 ) { + continue; + } + //if the enemy is quite far away, not shooting and the bot is not damaged + if ( curenemy < 0 && dist > 200 && !healthdecrease && !EntityIsShooting( &entinfo ) ) { + //check if we can avoid this enemy + VectorSubtract( bs->origin, entinfo.origin, dir ); + vectoangles( dir, angles ); + //if the bot isn't in the fov of the enemy + if ( !InFieldOfVision( entinfo.angles, 120, angles ) ) { + //update some stuff for this enemy + BotUpdateBattleInventory( bs, i ); + //if the bot doesn't really want to fight + if ( BotWantsToRetreat( bs ) ) { + continue; + } + } + } + //found an enemy + bs->enemy = entinfo.number; + if ( curenemy >= 0 ) { + bs->enemysight_time = trap_AAS_Time() - 2; + } else { bs->enemysight_time = trap_AAS_Time();} + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + return qtrue; + } + return qfalse; +} + +/* +================== +BotAimAtEnemy +================== +*/ +void BotAimAtEnemy( bot_state_t *bs ) { + int i, enemyvisible; + float dist, f, aim_skill, aim_accuracy, speed, reactiontime; + vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; + vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; + weaponinfo_t wi; + aas_entityinfo_t entinfo; + bot_goal_t goal; + bsp_trace_t trace; + vec3_t target; + + //if the bot has no enemy + if ( bs->enemy < 0 ) { + return; + } + // + //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); + // + aim_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1 ); + aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1 ); + // + if ( aim_skill > 0.95 ) { + //don't aim too early + reactiontime = 0.5 * trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 ); + if ( bs->enemysight_time > trap_AAS_Time() - reactiontime ) { + return; + } + if ( bs->teleport_time > trap_AAS_Time() - reactiontime ) { + return; + } + } + + //get the weapon information + trap_BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi ); + //get the weapon specific aim accuracy and or aim skill +//----(SA) commented out the weapons that aren't ours. +//----(SA) if we're not using this routine at all and my changes are irrelivant, please let me know. +// if (wi.number == WP_MACHINEGUN) { +// aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); +// } +// if (wi.number == WP_SHOTGUN) { +// aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); +// } + if ( wi.number == WP_GRENADE_LAUNCHER ) { + aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1 ); + aim_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1 ); + } + if ( wi.number == WP_ROCKET_LAUNCHER ) { + aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1 ); + aim_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1 ); + } + if ( wi.number == WP_FLAMETHROWER ) { + aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1 ); + } +// if (wi.number == WP_RAILGUN) { +// aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); +// } + if ( wi.number == WP_SILENCER ) { + aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_SP5, 0, 1 ); + aim_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_SP5, 0, 1 ); + } +// if (wi.number == WP_BFG) { +// aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); +// aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); +// } + // + if ( aim_accuracy <= 0 ) { + aim_accuracy = 0.0001; + } + //get the enemy entity information + BotEntityInfo( bs->enemy, &entinfo ); + //if the enemy is invisible then shoot crappy most of the time + if ( EntityIsInvisible( &entinfo ) ) { + if ( random() > 0.1 ) { + aim_accuracy *= 0.4; + } + } + // + VectorSubtract( entinfo.origin, entinfo.lastvisorigin, enemyvelocity ); + VectorScale( enemyvelocity, 1 / entinfo.update_time, enemyvelocity ); + //enemy origin and velocity is remembered every 0.5 seconds + if ( bs->enemyposition_time < trap_AAS_Time() ) { + // + bs->enemyposition_time = trap_AAS_Time() + 0.5; + VectorCopy( enemyvelocity, bs->enemyvelocity ); + VectorCopy( entinfo.origin, bs->enemyorigin ); + } + //if not extremely skilled + if ( aim_skill < 0.9 ) { + VectorSubtract( entinfo.origin, bs->enemyorigin, dir ); + //if the enemy moved a bit + if ( VectorLength( dir ) > 48 ) { + //if the enemy changed direction + if ( DotProduct( bs->enemyvelocity, enemyvelocity ) < 0 ) { + //aim accuracy should be worse now + aim_accuracy *= 0.7; + } + } + } + //check visibility of enemy + enemyvisible = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy ); + //if the enemy is visible + if ( enemyvisible ) { + // + VectorCopy( entinfo.origin, bestorigin ); + bestorigin[2] += 8; + //get the start point shooting from + //NOTE: the x and y projectile start offsets are ignored + VectorCopy( bs->origin, start ); + start[2] += bs->cur_ps.viewheight; + start[2] += wi.offset[2]; + // + BotAI_Trace( &trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT ); + //if the enemy is NOT hit + if ( trace.fraction <= 1 && trace.ent != entinfo.number ) { + bestorigin[2] += 16; + } + //if it is not an instant hit weapon the bot might want to predict the enemy + if ( wi.speed ) { + // + VectorSubtract( bestorigin, bs->origin, dir ); + dist = VectorLength( dir ); + VectorSubtract( entinfo.origin, bs->enemyorigin, dir ); + //if the enemy is NOT pretty far away and strafing just small steps left and right + if ( !( dist > 100 && VectorLength( dir ) < 32 ) ) { + //if skilled anough do exact prediction + if ( aim_skill > 0.8 && + //if the weapon is ready to fire + bs->cur_ps.weaponstate == WEAPON_READY ) { + aas_clientmove_t move; + vec3_t origin; + + VectorSubtract( entinfo.origin, bs->origin, dir ); + //distance towards the enemy + dist = VectorLength( dir ); + //direction the enemy is moving in + VectorSubtract( entinfo.origin, entinfo.lastvisorigin, dir ); + // + VectorScale( dir, 1 / entinfo.update_time, dir ); + // + VectorCopy( entinfo.origin, origin ); + origin[2] += 1; + // + VectorClear( cmdmove ); + //AAS_ClearShownDebugLines(); + trap_AAS_PredictClientMovement( &move, bs->enemy, origin, + PRESENCE_CROUCH, qfalse, + dir, cmdmove, 0, + dist * 10 / wi.speed, 0.1, 0, 0, qfalse ); + VectorCopy( move.endpos, bestorigin ); + //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", trap_AAS_Time(), VectorLength(dir), dist * 10 / wi.speed); + } + //if not that skilled do linear prediction + else if ( aim_skill > 0.4 ) { + VectorSubtract( entinfo.origin, bs->origin, dir ); + //distance towards the enemy + dist = VectorLength( dir ); + //direction the enemy is moving in + VectorSubtract( entinfo.origin, entinfo.lastvisorigin, dir ); + dir[2] = 0; + // + speed = VectorNormalize( dir ) / entinfo.update_time; + //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); + //best spot to aim at + VectorMA( entinfo.origin, ( dist / wi.speed ) * speed, dir, bestorigin ); + } + } + } + //if the projectile does radial damage + if ( aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL ) { + //if the enemy isn't standing significantly higher than the bot + if ( entinfo.origin[2] < bs->origin[2] + 16 ) { + //try to aim at the ground in front of the enemy + VectorCopy( entinfo.origin, end ); + end[2] -= 64; + BotAI_Trace( &trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT ); + // + VectorCopy( bestorigin, groundtarget ); + if ( trace.startsolid ) { + groundtarget[2] = entinfo.origin[2] - 16; + } else { groundtarget[2] = trace.endpos[2] - 8;} + //trace a line from projectile start to ground target + BotAI_Trace( &trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT ); + //if hitpoint is not vertically too far from the ground target + if ( fabs( trace.endpos[2] - groundtarget[2] ) < 50 ) { + VectorSubtract( trace.endpos, groundtarget, dir ); + //if the hitpoint is near anough the ground target + if ( VectorLength( dir ) < 60 ) { + VectorSubtract( trace.endpos, start, dir ); + //if the hitpoint is far anough from the bot + if ( VectorLength( dir ) > 100 ) { + //check if the bot is visible from the ground target + trace.endpos[2] += 1; + BotAI_Trace( &trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT ); + if ( trace.fraction >= 1 ) { + //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); + VectorCopy( groundtarget, bestorigin ); + } + } + } + } + } + } + bestorigin[0] += 20 * crandom() * ( 1 - aim_accuracy ); + bestorigin[1] += 20 * crandom() * ( 1 - aim_accuracy ); + bestorigin[2] += 10 * crandom() * ( 1 - aim_accuracy ); + } else { + // + VectorCopy( bs->lastenemyorigin, bestorigin ); + bestorigin[2] += 8; + //if the bot is skilled anough + if ( aim_skill > 0.5 ) { + //do prediction shots around corners +// if (wi.number == WP_BFG || //----(SA) removing old weapon references + if ( wi.number == WP_ROCKET_LAUNCHER || + wi.number == WP_GRENADE_LAUNCHER ) { + //create the chase goal + goal.entitynum = bs->client; + goal.areanum = bs->areanum; + VectorCopy( bs->eye, goal.origin ); + VectorSet( goal.mins, -8, -8, -8 ); + VectorSet( goal.maxs, 8, 8, 8 ); + // + if ( trap_BotPredictVisiblePosition( bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target ) ) { + VectorCopy( target, bestorigin ); + bestorigin[2] -= 20; + } + aim_accuracy = 1; + } + } + } + // + if ( enemyvisible ) { + BotAI_Trace( &trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT ); + VectorCopy( trace.endpos, bs->aimtarget ); + } else { + VectorCopy( bestorigin, bs->aimtarget ); + } + //get aim direction + VectorSubtract( bestorigin, bs->eye, dir ); + // + if ( wi.number == WP_FLAMETHROWER ) { +// if (wi.number == WP_MACHINEGUN || //----(SA) removing old weapon references +// wi.number == WP_SHOTGUN || +// wi.number == WP_RAILGUN) { + //distance towards the enemy + dist = VectorLength( dir ); + if ( dist > 150 ) { + dist = 150; + } + f = 0.6 + dist / 150 * 0.4; + aim_accuracy *= f; + } + //add some random stuff to the aim direction depending on the aim accuracy + if ( aim_accuracy < 0.8 ) { + VectorNormalize( dir ); + for ( i = 0; i < 3; i++ ) dir[i] += 0.3 * crandom() * ( 1 - aim_accuracy ); + } + //set the ideal view angles + vectoangles( dir, bs->ideal_viewangles ); + //take the weapon spread into account for lower skilled bots + bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * ( 1 - aim_accuracy ); + bs->ideal_viewangles[PITCH] = AngleMod( bs->ideal_viewangles[PITCH] ); + bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * ( 1 - aim_accuracy ); + bs->ideal_viewangles[YAW] = AngleMod( bs->ideal_viewangles[YAW] ); + //if the bot is really accurate and has the enemy in view for some time + if ( aim_accuracy > 0.9 && bs->enemysight_time < trap_AAS_Time() - 1 ) { + //set the view angles directly + if ( bs->ideal_viewangles[PITCH] > 180 ) { + bs->ideal_viewangles[PITCH] -= 360; + } + VectorCopy( bs->ideal_viewangles, bs->viewangles ); + trap_EA_View( bs->client, bs->viewangles ); + } +} + +/* +================== +BotCheckAttack +================== +*/ +void BotCheckAttack( bot_state_t *bs ) { + float points, reactiontime, fov, firethrottle; + bsp_trace_t bsptrace; + //float selfpreservation; + vec3_t forward, right, start, end, dir, angles; + weaponinfo_t wi; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + if ( bs->enemy < 0 ) { + return; + } + // + reactiontime = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 ); + if ( bs->enemysight_time > trap_AAS_Time() - reactiontime ) { + return; + } + if ( bs->teleport_time > trap_AAS_Time() - reactiontime ) { + return; + } + //if changing weapons + if ( bs->weaponchange_time > trap_AAS_Time() - 0.1 ) { + return; + } + //check fire throttle characteristic + if ( bs->firethrottlewait_time > trap_AAS_Time() ) { + return; + } + firethrottle = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1 ); + if ( bs->firethrottleshoot_time < trap_AAS_Time() ) { + if ( random() > firethrottle ) { + bs->firethrottlewait_time = trap_AAS_Time() + firethrottle; + bs->firethrottleshoot_time = 0; + } else { + bs->firethrottleshoot_time = trap_AAS_Time() + 1 - firethrottle; + bs->firethrottlewait_time = 0; + } + } + // + BotEntityInfo( bs->enemy, &entinfo ); + VectorSubtract( entinfo.origin, bs->eye, dir ); + // + if ( VectorLength( dir ) < 100 ) { + fov = 120; + } else { fov = 50;} + /* + //if the enemy isn't visible + if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, bs->enemy)) { + //botimport.Print(PRT_MESSAGE, "enemy not visible\n"); + return; + }*/ + vectoangles( dir, angles ); + if ( !InFieldOfVision( bs->viewangles, fov, angles ) ) { + return; + } + BotAI_Trace( &bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + if ( bsptrace.fraction < 1 && bsptrace.ent != bs->enemy ) { + return; + } + + //get the weapon info + trap_BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi ); + //get the start point shooting from + VectorCopy( bs->origin, start ); + start[2] += bs->cur_ps.viewheight; + AngleVectors( bs->viewangles, forward, right, NULL ); + start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; + start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; + start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; + //end point aiming at + VectorMA( start, 1000, forward, end ); + //a little back to make sure not inside a very close enemy + VectorMA( start, -12, forward, start ); + BotAI_Trace( &trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT ); //----(SA) should this maybe check the weapon type and adjust the clipflag? it seems like this is probably fine as-is, but I thought I'd note it. + //if won't hit the enemy + if ( trace.ent != bs->enemy ) { + //if the entity is a client + if ( trace.ent > 0 && trace.ent <= MAX_CLIENTS ) { + //if a teammate is hit + if ( BotSameTeam( bs, trace.ent ) ) { + return; + } + } + //if the projectile does a radial damage + if ( wi.proj.damagetype & DAMAGETYPE_RADIAL ) { + if ( trace.fraction * 1000 < wi.proj.radius ) { + points = ( wi.proj.damage - 0.5 * trace.fraction * 1000 ) * 0.5; + if ( points > 0 ) { +// selfpreservation = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_SELFPRESERVATION, 0, 1); +// if (random() < selfpreservation) return; + return; + } + } + //FIXME: check if a teammate gets radial damage + } + } + //if fire has to be release to activate weapon + if ( wi.flags & WFL_FIRERELEASED ) { + if ( bs->flags & BFL_ATTACKED ) { + trap_EA_Attack( bs->client ); + } + } else { + trap_EA_Attack( bs->client ); + } + bs->flags ^= BFL_ATTACKED; +} + +/* +================== +BotMapScripts +================== +*/ +void BotMapScripts( bot_state_t *bs ) { + char info[1024]; + char mapname[128]; + int i, shootbutton; + float aim_accuracy; + aas_entityinfo_t entinfo; + vec3_t dir; + + trap_GetServerinfo( info, sizeof( info ) ); + + strncpy( mapname, Info_ValueForKey( info, "mapname" ), sizeof( mapname ) - 1 ); + mapname[sizeof( mapname ) - 1] = '\0'; + + if ( !Q_stricmp( mapname, "q3tourney6" ) ) { + vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; + vec3_t buttonorg = {304, 352, 920}; + //NOTE: NEVER use the func_bobbing in q3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + //if the bot is below the bounding box + if ( bs->origin[0] > mins[0] && bs->origin[0] < maxs[0] ) { + if ( bs->origin[1] > mins[1] && bs->origin[1] < maxs[1] ) { + if ( bs->origin[2] < mins[2] ) { + return; + } + } + } + shootbutton = qfalse; + //if an enemy is below this bounding box then shoot the button + for ( i = 0; i < MAX_CLIENTS; i++ ) { + + if ( i == bs->client ) { + continue; + } + // + BotEntityInfo( i, &entinfo ); + // + if ( !entinfo.valid ) { + continue; + } + //if the enemy isn't dead and the enemy isn't the bot self + if ( EntityIsDead( &entinfo ) || entinfo.number == bs->entitynum ) { + continue; + } + // + if ( entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0] ) { + if ( entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1] ) { + if ( entinfo.origin[2] < mins[2] ) { + //if there's a team mate below the crusher + if ( BotSameTeam( bs, i ) ) { + shootbutton = qfalse; + break; + } else { + shootbutton = qtrue; + } + } + } + } + } + if ( shootbutton ) { + bs->flags |= BFL_IDEALVIEWSET; + VectorSubtract( buttonorg, bs->eye, dir ); + vectoangles( dir, bs->ideal_viewangles ); + aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1 ); + bs->ideal_viewangles[PITCH] += 8 * crandom() * ( 1 - aim_accuracy ); + bs->ideal_viewangles[PITCH] = AngleMod( bs->ideal_viewangles[PITCH] ); + bs->ideal_viewangles[YAW] += 8 * crandom() * ( 1 - aim_accuracy ); + bs->ideal_viewangles[YAW] = AngleMod( bs->ideal_viewangles[YAW] ); + // + if ( InFieldOfVision( bs->viewangles, 20, bs->ideal_viewangles ) ) { + trap_EA_Attack( bs->client ); + } + } + } +} + +/* +================== +BotCheckButtons +================== +*/ +/* +void CheckButtons(void) +{ + int modelindex, i, numbuttons = 0; + char *classname, *model; + float lip, health, dist; + bsp_entity_t *ent; + vec3_t mins, maxs, size, origin, angles, movedir, goalorigin; + vec3_t start, end, bboxmins, bboxmaxs; + aas_trace_t trace; + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (!strcmp(classname, "func_button")) + { + //create a bot goal towards the button + model = AAS_ValueForBSPEpairKey(ent, "model"); + modelindex = AAS_IndexFromModel(model); + //if the model is not loaded + if (!modelindex) modelindex = atoi(model+1); + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(modelindex - 1, angles, mins, maxs, NULL); + //get the lip of the button + lip = AAS_FloatForBSPEpairKey(ent, "lip"); + if (!lip) lip = 4; + //get the move direction from the angle + VectorSet(angles, 0, AAS_FloatForBSPEpairKey(ent, "angle"), 0); + AAS_SetMovedir(angles, movedir); + //button size + VectorSubtract(maxs, mins, size); + //button origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + //touch distance of the button + dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2];// - lip; + dist *= 0.5; + // + health = AAS_FloatForBSPEpairKey(ent, "health"); + //if the button is shootable + if (health) + { + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + AAS_DrawPermanentCross(goalorigin, 4, LINECOLOR_BLUE); + } //end if + else + { + //add bounding box size to the dist + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } //end for + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorSet(end, start[0], start[1], start[2] - 100); + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, goalorigin); + } //end if + // + AAS_DrawPermanentCross(goalorigin, 4, LINECOLOR_YELLOW); + // + VectorSubtract(mins, origin, mins); + VectorSubtract(maxs, origin, maxs); + // + VectorAdd(mins, origin, start); + AAS_DrawPermanentCross(start, 4, LINECOLOR_BLUE); + VectorAdd(maxs, origin, start); + AAS_DrawPermanentCross(start, 4, LINECOLOR_BLUE); + } //end else + if (++numbuttons > 5) return; + } //end if + } //end for +} //end of the function CheckButtons +*/ + +/* +================== +BotEntityToActivate +================== +*/ +//#define OBSTACLEDEBUG + +int BotEntityToActivate( int entitynum ) { + int i, ent, cur_entities[10]; + char model[MAX_INFO_STRING], tmpmodel[128]; + char target[128], classname[128]; + float health; + char targetname[10][128]; + aas_entityinfo_t entinfo; + + BotEntityInfo( entitynum, &entinfo ); + Com_sprintf( model, sizeof( model ), "*%d", entinfo.modelindex ); + for ( ent = trap_AAS_NextBSPEntity( 0 ); ent; ent = trap_AAS_NextBSPEntity( ent ) ) { + if ( !trap_AAS_ValueForBSPEpairKey( ent, "model", tmpmodel, sizeof( tmpmodel ) ) ) { + continue; + } + if ( !strcmp( model, tmpmodel ) ) { + break; + } + } + if ( !ent ) { + BotAI_Print( PRT_ERROR, "BotEntityToActivate: no entity found with model %s\n", model ); + return 0; + } + trap_AAS_ValueForBSPEpairKey( ent, "classname", classname, sizeof( classname ) ); + if ( !classname ) { + BotAI_Print( PRT_ERROR, "BotEntityToActivate: entity with model %s has no classname\n", model ); + return 0; + } + //if it is a door + if ( !strcmp( classname, "func_door" ) ) { + if ( trap_AAS_FloatForBSPEpairKey( ent, "health", &health ) ) { + //if health the door must be shot to open + if ( health ) { + return ent; + } + } + } + //get the targetname so we can find an entity with a matching target + if ( !trap_AAS_ValueForBSPEpairKey( ent, "targetname", targetname[0], sizeof( targetname[0] ) ) ) { +#ifdef OBSTACLEDEBUG + BotAI_Print( PRT_ERROR, "BotEntityToActivate: entity with model \"%s\" has no targetname\n", model ); +#endif //OBSTACLEDEBUG + return 0; + } + cur_entities[0] = trap_AAS_NextBSPEntity( 0 ); + for ( i = 0; i >= 0 && i < 10; ) { + for ( ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity( ent ) ) { + if ( !trap_AAS_ValueForBSPEpairKey( ent, "target", target, sizeof( target ) ) ) { + continue; + } + if ( !strcmp( targetname[i], target ) ) { + cur_entities[i] = trap_AAS_NextBSPEntity( ent ); + break; + } + } + if ( !ent ) { + BotAI_Print( PRT_ERROR, "BotEntityToActivate: no entity with target \"%s\"\n", targetname[i] ); + i--; + continue; + } + if ( !trap_AAS_ValueForBSPEpairKey( ent, "classname", classname, sizeof( classname ) ) ) { + BotAI_Print( PRT_ERROR, "BotEntityToActivate: entity with target \"%s\" has no classname\n", targetname[i] ); + continue; + } + if ( !strcmp( classname, "func_button" ) ) { + //BSP button model + return ent; + } else if ( !strcmp( classname, "trigger_multiple" ) ) { + //invisible trigger multiple box + return ent; + } else { + i--; + } + } + BotAI_Print( PRT_ERROR, "BotEntityToActivate: unknown activator with classname \"%s\"\n", classname ); + return 0; +} + +/* +================== +BotSetMovedir +================== +*/ +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void BotSetMovedir( vec3_t angles, vec3_t movedir ) { + if ( VectorCompare( angles, VEC_UP ) ) { + VectorCopy( MOVEDIR_UP, movedir ); + } else if ( VectorCompare( angles, VEC_DOWN ) ) { + VectorCopy( MOVEDIR_DOWN, movedir ); + } else { + AngleVectors( angles, movedir, NULL, NULL ); + } +} + +void BotModelMinsMaxs( int modelindex, vec3_t mins, vec3_t maxs ) { + gentity_t *ent; + int i; + + ent = &g_entities[0]; + for ( i = 0; i < level.num_entities; i++, ent++ ) { + if ( !ent->inuse ) { + continue; + } + if ( ent->s.modelindex == modelindex ) { + VectorCopy( ent->r.mins, mins ); + VectorCopy( ent->r.maxs, maxs ); + return; + } + } + VectorClear( mins ); + VectorClear( maxs ); +} + +/* +================== +BotAIBlocked +================== +*/ +void BotAIBlocked( bot_state_t *bs, bot_moveresult_t *moveresult, int activate ) { + int movetype, ent, i, areas[10], numareas, modelindex; + char classname[128], model[128]; +#ifdef OBSTACLEDEBUG + char buf[128]; +#endif + float lip, dist, health, angle; + vec3_t hordir, size, start, end, mins, maxs, sideward, angles; + vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; + vec3_t up = {0, 0, 1}, extramins = {-1, -1, -1}, extramaxs = {1, 1, 1}; + aas_entityinfo_t entinfo; +/* + bsp_trace_t bsptrace; +*/ +#ifdef OBSTACLEDEBUG + char netname[MAX_NETNAME]; +#endif + + if ( !moveresult->blocked ) { + return; + } + // + BotEntityInfo( moveresult->blockentity, &entinfo ); +#ifdef OBSTACLEDEBUG + ClientName( bs->client, netname, sizeof( netname ) ); + BotAI_Print( PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex ); +#endif //OBSTACLEDEBUG + //if blocked by a bsp model and the bot wants to activate it if possible + if ( entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex && activate ) { + //find the bsp entity which should be activated in order to remove + //the blocking entity + ent = BotEntityToActivate( entinfo.number ); + if ( !ent ) { + strcpy( classname, "" ); +#ifdef OBSTACLEDEBUG + BotAI_Print( PRT_MESSAGE, "%s: can't find activator for blocking entity\n", ClientName( bs->client, netname, sizeof( netname ) ) ); +#endif //OBSTACLEDEBUG + } else { + trap_AAS_ValueForBSPEpairKey( ent, "classname", classname, sizeof( classname ) ); +#ifdef OBSTACLEDEBUG + ClientName( bs->client, netname, sizeof( netname ) ); + BotAI_Print( PRT_MESSAGE, "%s: I should activate %s\n", netname, classname ); +#endif //OBSTACLEDEBUG + } +#ifdef OBSTACLEDEBUG +// ClientName(bs->client, netname, sizeof(netname)); +// BotAI_Print(PRT_MESSAGE, "%s: I've got no brain cells for activating entities\n", netname); +#endif //OBSTACLEDEBUG + /* + //the bot should now activate one of the following entities + //"func_button", "trigger_multiple", "func_door" + //all these activators use BSP models, so it should be a matter of + //finding where this model is located using AAS and then activating + //by walking against the model it or shooting at it + // + //if it is a door we should shoot at + if (!strcmp(classname, "func_door")) + { + //get the door model + model = AAS_ValueForBSPEpairKey(ent, "model"); + modelindex = AAS_IndexFromModel(model); + //if the model is not loaded + if (!modelindex) return; + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(modelindex - 1, angles, mins, maxs, NULL); + //get a goal to shoot at + VectorAdd(maxs, mins, goalorigin); + VectorScale(goalorigin, 0.5, goalorigin); + VectorSubtract(goalorigin, bs->origin, movedir); + // + vectoangles(movedir, moveresult->ideal_viewangles); + moveresult->flags |= MOVERESULT_MOVEMENTVIEW; + //select the blaster + EA_UseItem(bs->client, "Blaster"); + //shoot + EA_Attack(bs->client); + // + return; + } //end if*/ + if ( !strcmp( classname, "func_button" ) ) { + //create a bot goal towards the button + trap_AAS_ValueForBSPEpairKey( ent, "model", model, sizeof( model ) ); + modelindex = atoi( model + 1 ); + //if the model is not loaded + if ( !modelindex ) { + return; + } + VectorClear( angles ); + BotModelMinsMaxs( modelindex, mins, maxs ); + //get the lip of the button + trap_AAS_FloatForBSPEpairKey( ent, "lip", &lip ); + if ( !lip ) { + lip = 4; + } + //get the move direction from the angle + trap_AAS_FloatForBSPEpairKey( ent, "angle", &angle ); + VectorSet( angles, 0, angle, 0 ); + BotSetMovedir( angles, movedir ); + //button size + VectorSubtract( maxs, mins, size ); + //button origin + VectorAdd( mins, maxs, origin ); + VectorScale( origin, 0.5, origin ); + //touch distance of the button + dist = fabs( movedir[0] ) * size[0] + fabs( movedir[1] ) * size[1] + fabs( movedir[2] ) * size[2]; + dist *= 0.5; + // + trap_AAS_FloatForBSPEpairKey( ent, "health", &health ); + //if the button is shootable + if ( health ) { + //calculate the goal origin + VectorMA( origin, -dist, movedir, goalorigin ); + // + //AAS_ClearShownDebugLines(); + //AAS_DrawArrow(bs->origin, goalorigin, LINECOLOR_BLUE, LINECOLOR_YELLOW); + // + VectorSubtract( goalorigin, bs->origin, movedir ); + vectoangles( movedir, moveresult->ideal_viewangles ); + moveresult->flags |= MOVERESULT_MOVEMENTVIEW; + //select the blaster + trap_EA_SelectWeapon( bs->client, WEAPONINDEX_MACHINEGUN ); + //shoot + trap_EA_Attack( bs->client ); + return; + } //end if + else + { + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox( PRESENCE_CROUCH, bboxmins, bboxmaxs ); + for ( i = 0; i < 3; i++ ) + { + if ( movedir[i] < 0 ) { + dist += fabs( movedir[i] ) * fabs( bboxmaxs[i] ); + } else { dist += fabs( movedir[i] ) * fabs( bboxmins[i] );} + } //end for + //calculate the goal origin + VectorMA( origin, -dist, movedir, goalorigin ); + // + VectorCopy( goalorigin, start ); + start[2] += 24; + VectorCopy( start, end ); + end[2] -= 100; + numareas = trap_AAS_TraceAreas( start, end, areas, NULL, 10 ); + // + for ( i = 0; i < numareas; i++ ) { + if ( trap_AAS_AreaReachability( areas[i] ) ) { + break; + } + } + if ( i < numareas ) { + // +#ifdef OBSTACLEDEBUG + if ( bs->activatemessage_time < trap_AAS_Time() ) { + Com_sprintf( buf, sizeof( buf ), "I have to activate a button at %1.1f %1.1f %1.1f in area %d\n", + goalorigin[0], goalorigin[1], goalorigin[2], areas[i] ); + trap_EA_Say( bs->client, buf ); + bs->activatemessage_time = trap_AAS_Time() + 5; + } //end if +#endif //OBSTACLEDEBUG + // + //VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy( origin, bs->activategoal.origin ); + bs->activategoal.areanum = areas[i]; + VectorSubtract( mins, origin, bs->activategoal.mins ); + VectorSubtract( maxs, origin, bs->activategoal.maxs ); + // + VectorAdd( bs->activategoal.mins, extramins, bs->activategoal.mins ); + VectorAdd( bs->activategoal.maxs, extramaxs, bs->activategoal.maxs ); + // + bs->activategoal.entitynum = entinfo.number; + bs->activategoal.number = 0; + bs->activategoal.flags = 0; + bs->activate_time = trap_AAS_Time() + 10; + AIEnter_Seek_ActivateEntity( bs ); + } //end if + else + { +#ifdef OBSTACLEDEBUG + BotAI_Print( PRT_MESSAGE, "button area has no reachabilities\n" ); +#endif //OBSTACLEDEBUG + if ( bs->ainode == AINode_Seek_NBG ) { + bs->nbg_time = 0; + } else if ( bs->ainode == AINode_Seek_LTG ) { + bs->ltg_time = 0; + } + } //end else + } //end else + } //end if + /* + if (!strcmp(classname, "trigger_multiple")) + { + //create a bot goal towards the trigger + model = AAS_ValueForBSPEpairKey(ent, "model"); + modelindex = AAS_IndexFromModel(model); + //if the model is not precached (bad thing but happens) assume model is "*X" + if (!modelindex) modelindex = atoi(model+1); + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(modelindex - 1, angles, mins, maxs, NULL); + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + VectorCopy(mid, start); + start[2] = maxs[2] + 24; + VectorSet(end, start[0], start[1], start[2] - 100); + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) return; + //trace.endpos is now the goal origin + VectorCopy(trace.endpos, goalorigin); + // + #ifdef OBSTACLEDEBUG + if (bs->activatemessage_time < AAS_Time()) + { + Com_sprintf(buf, sizeof(buf), "I have to activate a trigger at %1.1f %1.1f %1.1f in area %d\n", + goalorigin[0], goalorigin[1], goalorigin[2], AAS_PointAreaNum(goalorigin)); + EA_Say(bs->client, buf); + bs->activatemessage_time = AAS_Time() + 5; + } //end if* / + #endif //OBSTACLEDEBUG + // + VectorCopy(mid, bs->activategoal.origin); + bs->activategoal.areanum = AAS_PointAreaNum(goalorigin); + VectorSubtract(mins, mid, bs->activategoal.mins); + VectorSubtract(maxs, mid, bs->activategoal.maxs); + bs->activategoal.entitynum = entinfo.number; + bs->activategoal.number = 0; + bs->activategoal.flags = 0; + bs->activate_time = AAS_Time() + 10; + if (!AAS_AreaReachability(bs->activategoal.areanum)) + { + #ifdef OBSTACLEDEBUG + botimport.Print(PRT_MESSAGE, "trigger area has no reachabilities\n"); + #endif //OBSTACLEDEBUG + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } //end if + else + { + AIEnter_Seek_ActivateEntity(bs); + } //end else + return; + } //end if*/ + } + //just some basic dynamic obstacle avoidance code + hordir[0] = moveresult->movedir[0]; + hordir[1] = moveresult->movedir[1]; + hordir[2] = 0; + //if no direction just take a random direction + if ( VectorNormalize( hordir ) < 0.1 ) { + VectorSet( angles, 0, 360 * random(), 0 ); + AngleVectors( angles, hordir, NULL, NULL ); + } + // +// if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; +// else + movetype = MOVE_WALK; + //if there's an obstacle at the bot's feet and head then + //the bot might be able to crouch through + VectorCopy( bs->origin, start ); + start[2] += 18; + VectorMA( start, 5, hordir, end ); + VectorSet( mins, -16, -16, -24 ); + VectorSet( maxs, 16, 16, 4 ); + // +// bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); +// if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; + //get the sideward vector + CrossProduct( hordir, up, sideward ); + // + if ( bs->flags & BFL_AVOIDRIGHT ) { + VectorNegate( sideward, sideward ); + } + //try to crouch straight forward? + if ( movetype != MOVE_CROUCH || !trap_BotMoveInDirection( bs->ms, hordir, 400, movetype ) ) { + //perform the movement + if ( !trap_BotMoveInDirection( bs->ms, sideward, 400, movetype ) ) { + //flip the avoid direction flag + bs->flags ^= BFL_AVOIDRIGHT; + //flip the direction + VectorNegate( sideward, sideward ); + //move in the other direction + trap_BotMoveInDirection( bs->ms, sideward, 400, movetype ); + } + } + //just reset goals and hope the bot will go into another direction + //still needed?? + if ( bs->ainode == AINode_Seek_NBG ) { + bs->nbg_time = 0; + } else if ( bs->ainode == AINode_Seek_LTG ) { + bs->ltg_time = 0; + } +} + +/* +================== +BotCheckConsoleMessages +================== +*/ +void BotCheckConsoleMessages( bot_state_t *bs ) { + char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME]; + float chat_reply; + int context, handle; + bot_consolemessage_t m; + bot_match_t match; + + //the name of this bot + ClientName( bs->client, botname, sizeof( botname ) ); + // + while ( ( handle = trap_BotNextConsoleMessage( bs->cs, &m ) ) != 0 ) { + //if the chat state is flooded with messages the bot will read them quickly + if ( trap_BotNumConsoleMessages( bs->cs ) < 10 ) { + //if it is a chat message the bot needs some time to read it + if ( m.type == CMS_CHAT && m.time > trap_AAS_Time() - ( 1 + random() ) ) { + break; + } + } + //unify the white spaces in the message + trap_UnifyWhiteSpaces( m.message ); + //replace synonyms in the right context + context = CONTEXT_NORMAL | CONTEXT_NEARBYITEM | CONTEXT_NAMES; + if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) { + context |= CONTEXT_CTFREDTEAM; + } else { context |= CONTEXT_CTFBLUETEAM;} + trap_BotReplaceSynonyms( m.message, context ); + //if there's no match + if ( !BotMatchMessage( bs, m.message ) ) { + //if it is a chat message + if ( m.type == CMS_CHAT && !bot_nochat.integer ) { + // + if ( !trap_BotFindMatch( m.message, &match, MTCONTEXT_REPLYCHAT ) ) { + trap_BotRemoveConsoleMessage( bs->cs, handle ); + continue; + } + //don't use eliza chats with team messages + if ( match.subtype & ST_TEAM ) { + trap_BotRemoveConsoleMessage( bs->cs, handle ); + continue; + } + // + trap_BotMatchVariable( &match, NETNAME, netname, sizeof( netname ) ); + trap_BotMatchVariable( &match, MESSAGE, message, sizeof( message ) ); + //if this is a message from the bot self + if ( !Q_stricmp( netname, botname ) ) { + trap_BotRemoveConsoleMessage( bs->cs, handle ); + continue; + } + //unify the message + trap_UnifyWhiteSpaces( message ); + // + trap_Cvar_Update( &bot_testrchat ); + if ( bot_testrchat.integer ) { + // + trap_BotLibVarSet( "bot_testrchat", "1" ); + //if bot replies with a chat message + if ( trap_BotReplyChat( bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname ) ) { + BotAI_Print( PRT_MESSAGE, "------------------------\n" ); + } else { + BotAI_Print( PRT_MESSAGE, "**** no valid reply ****\n" ); + } + } + //if at a valid chat position and not chatting already + else if ( bs->ainode != AINode_Stand && BotValidChatPosition( bs ) ) { + chat_reply = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1 ); + if ( random() < 1.5 / ( NumBots() + 1 ) && random() < chat_reply ) { + //if bot replies with a chat message + if ( trap_BotReplyChat( bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname ) ) { + //remove the console message + trap_BotRemoveConsoleMessage( bs->cs, handle ); + bs->stand_time = trap_AAS_Time() + BotChatTime( bs ); + AIEnter_Stand( bs ); + //EA_Say(bs->client, bs->cs.chatmessage); + break; + } + } + } + } + } + //remove the console message + trap_BotRemoveConsoleMessage( bs->cs, handle ); + } +} + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckEvents( bot_state_t *bs, entityState_t *state ) { + int event; + char buf[128]; + // + //this sucks, we're accessing the gentity_t directly but there's no other fast way + //to do it right now + if ( bs->entityeventTime[state->number] == g_entities[state->number].eventTime ) { + return; + } + bs->entityeventTime[state->number] = g_entities[state->number].eventTime; + //if it's an event only entity + if ( state->eType > ET_EVENTS ) { + event = ( state->eType - ET_EVENTS ) & ~EV_EVENT_BITS; + } else { + event = state->event & ~EV_EVENT_BITS; + } + // + switch ( event ) { + //client obituary event + case EV_OBITUARY: + { + int target, attacker, mod; + + target = state->otherEntityNum; + attacker = state->otherEntityNum2; + mod = state->eventParm; + // + if ( target == bs->client ) { + bs->botdeathtype = mod; + bs->lastkilledby = attacker; + // + if ( target == attacker ) { + bs->botsuicide = qtrue; + } else { bs->botsuicide = qfalse;} + // + bs->num_deaths++; + } + //else if this client was killed by the bot + else if ( attacker == bs->client ) { + bs->enemydeathtype = mod; + bs->lastkilledplayer = target; + bs->killedenemy_time = trap_AAS_Time(); + // + bs->num_kills++; + } else if ( attacker == bs->enemy && target == attacker ) { + bs->enemysuicide = qtrue; + } + break; + } + case EV_GLOBAL_SOUND: + { + if ( state->eventParm < 0 || state->eventParm > MAX_SOUNDS ) { + BotAI_Print( PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm ); + break; + } + trap_GetConfigstring( CS_SOUNDS + state->eventParm, buf, sizeof( buf ) ); + if ( !strcmp( buf, "sound/teamplay/flagret_red.wav" ) ) { + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + } else if ( !strcmp( buf, "sound/teamplay/flagret_blu.wav" ) ) { + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + } else if ( !strcmp( buf, "sound/items/poweruprespawn.wav" ) ) { + //powerup respawned... go get it + BotGoForPowerups( bs ); + } + break; + } + case EV_PLAYER_TELEPORT_IN: + { + VectorCopy( state->origin, lastteleport_origin ); + lastteleport_time = trap_AAS_Time(); + break; + } + case EV_GENERAL_SOUND: + { + //if this sound is played on the bot + if ( state->number == bs->client ) { + if ( state->eventParm < 0 || state->eventParm > MAX_SOUNDS ) { + BotAI_Print( PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm ); + break; + } + //check out the sound + trap_GetConfigstring( CS_SOUNDS + state->eventParm, buf, sizeof( buf ) ); + //if falling into a death pit + if ( !strcmp( buf, "*falling1.wav" ) ) { + //if the bot has a personal teleporter + if ( bs->inventory[INVENTORY_TELEPORTER] > 0 ) { + //use the holdable item + trap_EA_Use( bs->client ); + } + } + } + break; + } + } +} + +/* +================== +BotCheckSnapshot +================== +*/ +void BotCheckSnapshot( bot_state_t *bs ) { + int ent; + entityState_t state; + + // + ent = 0; + while ( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { + //check the entity state for events + BotCheckEvents( bs, &state ); + } + //check the player state for events + BotAI_GetEntityState( bs->client, &state ); + //copy the player state events to the entity state + //state.event = bs->cur_ps.externalEvent; + //state.eventParm = bs->cur_ps.externalEventParm; + // + BotCheckEvents( bs, &state ); +} + +/* +================== +BotCheckAir +================== +*/ +void BotCheckAir( bot_state_t *bs ) { + if ( bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0 ) { + if ( trap_AAS_PointContents( bs->eye ) & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + } + bs->lastair_time = trap_AAS_Time(); +} + +/* +================== +BotDeathmatchAI +================== +*/ +void BotDeathmatchAI( bot_state_t *bs, float thinktime ) { + char gender[144], name[144], buf[144]; + char userinfo[MAX_INFO_STRING]; + int i; + + //if the bot has just been setup + if ( bs->setupcount > 0 ) { + bs->setupcount--; + if ( bs->setupcount > 0 ) { + return; + } + //get the gender characteristic + trap_Characteristic_String( bs->character, CHARACTERISTIC_GENDER, gender, sizeof( gender ) ); + //set the bot gender + trap_GetUserinfo( bs->client, userinfo, sizeof( userinfo ) ); + Info_SetValueForKey( userinfo, "sex", gender ); + trap_SetUserinfo( bs->client, userinfo ); + //set the team + if ( g_gametype.integer != GT_TOURNAMENT ) { + Com_sprintf( buf, sizeof( buf ), "team %s", bs->settings.team ); + trap_EA_Command( bs->client, buf ); + } + //set the chat gender + if ( gender[0] == 'm' ) { + trap_BotSetChatGender( bs->cs, CHAT_GENDERMALE ); + } else if ( gender[0] == 'f' ) { + trap_BotSetChatGender( bs->cs, CHAT_GENDERFEMALE ); + } else { trap_BotSetChatGender( bs->cs, CHAT_GENDERLESS );} + //set the chat name + ClientName( bs->client, name, sizeof( name ) ); + trap_BotSetChatName( bs->cs, name ); + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; + // + bs->setupcount = 0; + } + //no ideal view set + bs->flags &= ~BFL_IDEALVIEWSET; + //set the teleport time + BotSetTeleportTime( bs ); + //update some inventory values + BotUpdateInventory( bs ); + //check the console messages + BotCheckConsoleMessages( bs ); + //check out the snapshot + BotCheckSnapshot( bs ); + //check for air + BotCheckAir( bs ); + //if not in the intermission and not in observer mode + if ( !BotIntermission( bs ) && !BotIsObserver( bs ) ) { + //do team AI + BotTeamAI( bs ); + } + //if the bot has no ai node + if ( !bs->ainode ) { + AIEnter_Seek_LTG( bs ); + } + //if the bot entered the game less than 8 seconds ago + if ( !bs->entergamechat && bs->entergame_time > trap_AAS_Time() - 8 ) { + if ( BotChat_EnterGame( bs ) ) { + bs->stand_time = trap_AAS_Time() + BotChatTime( bs ); + AIEnter_Stand( bs ); + } + bs->entergamechat = qtrue; + } + //reset the node switches from the previous frame + BotResetNodeSwitches(); + //execute AI nodes + for ( i = 0; i < MAX_NODESWITCHES; i++ ) { + if ( bs->ainode( bs ) ) { + break; + } + } + //if the bot removed itself :) + if ( !bs->inuse ) { + return; + } + //if the bot executed too many AI nodes + if ( i >= MAX_NODESWITCHES ) { + trap_BotDumpGoalStack( bs->gs ); + trap_BotDumpAvoidGoals( bs->gs ); + BotDumpNodeSwitches( bs ); + ClientName( bs->client, name, sizeof( name ) ); + BotAI_Print( PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, trap_AAS_Time(), MAX_NODESWITCHES ); + } + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; +} + +/* +================== +BotSetupDeathmatchAI +================== +*/ +void BotSetupDeathmatchAI( void ) { + int ent, modelnum; + char model[128]; + + gametype = trap_Cvar_VariableIntegerValue( "g_gametype" ); + + // Rafael gameskill + gameskill = trap_Cvar_VariableIntegerValue( "g_gameskill" ); + // done + + trap_Cvar_Register( &bot_rocketjump, "bot_rocketjump", "1", 0 ); + trap_Cvar_Register( &bot_grapple, "bot_grapple", "0", 0 ); + trap_Cvar_Register( &bot_fastchat, "bot_fastchat", "0", 0 ); + trap_Cvar_Register( &bot_nochat, "bot_nochat", "0", 0 ); + trap_Cvar_Register( &bot_testrchat, "bot_testrchat", "0", 0 ); + // + if ( gametype == GT_CTF ) { + if ( trap_BotGetLevelItemGoal( -1, "Red Flag", &ctf_redflag ) < 0 ) { + BotAI_Print( PRT_WARNING, "CTF without Red Flag\n" ); + } + if ( trap_BotGetLevelItemGoal( -1, "Blue Flag", &ctf_blueflag ) < 0 ) { + BotAI_Print( PRT_WARNING, "CTF without Blue Flag\n" ); + } + } + + max_bspmodelindex = 0; + for ( ent = trap_AAS_NextBSPEntity( 0 ); ent; ent = trap_AAS_NextBSPEntity( ent ) ) { + if ( !trap_AAS_ValueForBSPEpairKey( ent, "model", model, sizeof( model ) ) ) { + continue; + } + if ( model[0] == '*' ) { + modelnum = atoi( model + 1 ); + if ( modelnum > max_bspmodelindex ) { + max_bspmodelindex = modelnum; + } + } + } + //initialize the waypoint heap + BotInitWaypoints(); +} + +/* +================== +BotShutdownDeathmatchAI +================== +*/ +void BotShutdownDeathmatchAI( void ) { +} diff --git a/src/botai/ai_dmq3.h b/src/botai/ai_dmq3.h new file mode 100644 index 0000000..3f29dfd --- /dev/null +++ b/src/botai/ai_dmq3.h @@ -0,0 +1,156 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_dmq3.h + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +//setup the deathmatch AI +void BotSetupDeathmatchAI( void ); +//shutdown the deathmatch AI +void BotShutdownDeathmatchAI( void ); +//let the bot live within it's deathmatch AI net +void BotDeathmatchAI( bot_state_t *bs, float thinktime ); +//free waypoints +void BotFreeWaypoints( bot_waypoint_t *wp ); +//choose a weapon +void BotChooseWeapon( bot_state_t *bs ); +//setup movement stuff +void BotSetupForMovement( bot_state_t *bs ); +//update the inventory +void BotUpdateInventory( bot_state_t *bs ); +//update the inventory during battle +void BotUpdateBattleInventory( bot_state_t *bs, int enemy ); +//use holdable items during battle +void BotBattleUseItems( bot_state_t *bs ); +//return true if the bot is dead +qboolean BotIsDead( bot_state_t *bs ); +//returns true if the bot is in observer mode +qboolean BotIsObserver( bot_state_t *bs ); +//returns true if the bot is in the intermission +qboolean BotIntermission( bot_state_t *bs ); +//returns true if the bot is in lava +qboolean BotInLava( bot_state_t *bs ); +//returns true if the bot is in slime +qboolean BotInSlime( bot_state_t *bs ); +//returns true if the entity is dead +qboolean EntityIsDead( aas_entityinfo_t *entinfo ); +//returns true if the entity is invisible +qboolean EntityIsInvisible( aas_entityinfo_t *entinfo ); +//returns true if the entity is shooting +qboolean EntityIsShooting( aas_entityinfo_t *entinfo ); +//returns the name of the client +char *ClientName( int client, char *name, int size ); +//returns an simplyfied client name +char *EasyClientName( int client, char *name, int size ); +//returns the skin used by the client +char *ClientSkin( int client, char *skin, int size ); +//returns the aggression of the bot in the range [0, 100] +float BotAggression( bot_state_t *bs ); +//returns true if the bot wants to retreat +int BotWantsToRetreat( bot_state_t *bs ); +//returns true if the bot wants to chase +int BotWantsToChase( bot_state_t *bs ); +//returns true if the bot wants to help +int BotWantsToHelp( bot_state_t *bs ); +//returns true if the bot can and wants to rocketjump +int BotCanAndWantsToRocketJump( bot_state_t *bs ); +//returns true if the bot wants to and goes camping +int BotWantsToCamp( bot_state_t *bs ); +//the bot will perform attack movements +bot_moveresult_t BotAttackMove( bot_state_t *bs, int tfl ); +//returns true if the bot and the entity are in the same team +int BotSameTeam( bot_state_t *bs, int entnum ); +//returns true if teamplay is on +int TeamPlayIsOn( void ); +//returns true and sets the .enemy field when an enemy is found +int BotFindEnemy( bot_state_t *bs, int curenemy ); +//returns a roam goal +void BotRoamGoal( bot_state_t *bs, vec3_t goal ); +//returns entity visibility in the range [0, 1] +float BotEntityVisible( int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent ); +//the bot will aim at the current enemy +void BotAimAtEnemy( bot_state_t *bs ); +//check if the bot should attack +void BotCheckAttack( bot_state_t *bs ); +//AI when the bot is blocked +void BotAIBlocked( bot_state_t *bs, bot_moveresult_t *moveresult, int activate ); +//returns the CTF team the bot is in +int BotCTFTeam( bot_state_t *bs ); +//returns the flag the bot is carrying (CTFFLAG_?) +int BotCTFCarryingFlag( bot_state_t *bs ); +//set ctf goals (defend base, get enemy flag) during seek +void BotCTFSeekGoals( bot_state_t *bs ); +//set ctf goals (defend base, get enemy flag) during retreat +void BotCTFRetreatGoals( bot_state_t *bs ); +//create a new waypoint +bot_waypoint_t *BotCreateWayPoint( char *name, vec3_t origin, int areanum ); +//find a waypoint with the given name +bot_waypoint_t *BotFindWayPoint( bot_waypoint_t *waypoints, char *name ); +//strstr but case insensitive +char *stristr( char *str, char *charset ); +//returns the number of the client with the given name +int ClientFromName( char *name ); +// +int BotPointAreaNum( vec3_t origin ); +// +void BotMapScripts( bot_state_t *bs ); + +//ctf flags +#define CTF_FLAG_NONE 0 +#define CTF_FLAG_RED 1 +#define CTF_FLAG_BLUE 2 +//CTF skins +#define CTF_SKIN_REDTEAM "red" +#define CTF_SKIN_BLUETEAM "blue" +//CTF teams +#define CTF_TEAM_NONE 0 +#define CTF_TEAM_RED 1 +#define CTF_TEAM_BLUE 2 + +extern int dmflags; //deathmatch flags +extern int gametype; //game type + +// Rafael gameskill +extern int gameskill; +// done + +extern vmCvar_t bot_grapple; +extern vmCvar_t bot_rocketjump; +extern vmCvar_t bot_fastchat; +extern vmCvar_t bot_nochat; +extern vmCvar_t bot_testrchat; + +extern bot_goal_t ctf_redflag; +extern bot_goal_t ctf_blueflag; + diff --git a/src/botai/ai_main.c b/src/botai/ai_main.c new file mode 100644 index 0000000..6bdc777 --- /dev/null +++ b/src/botai/ai_main.c @@ -0,0 +1,1205 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_main.c + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +// +#include "chars.h" +#include "inv.h" +#include "syn.h" + +#define MAX_PATH 144 + +//bot states +bot_state_t *botstates[MAX_CLIENTS]; +//number of bots +int numbots; +//time to do a regular update +float regularupdate_time; +// +vmCvar_t bot_thinktime; +vmCvar_t memorydump; + + +/* +================== +BotAI_Print +================== +*/ +void QDECL BotAI_Print( int type, char *fmt, ... ) { + char str[2048]; + va_list ap; + + va_start( ap, fmt ); + vsprintf( str, fmt, ap ); + va_end( ap ); + + switch ( type ) { + case PRT_MESSAGE: { + G_Printf( "%s", str ); + break; + } + case PRT_WARNING: { + G_Printf( S_COLOR_YELLOW "Warning: %s", str ); + break; + } + case PRT_ERROR: { + G_Printf( S_COLOR_RED "Error: %s", str ); + break; + } + case PRT_FATAL: { + G_Printf( S_COLOR_RED "Fatal: %s", str ); + break; + } + case PRT_EXIT: { + G_Error( S_COLOR_RED "Exit: %s", str ); + break; + } + default: { + G_Printf( "unknown print type\n" ); + break; + } + } +} + +/* +================== +BotAI_Trace +================== +*/ +void BotAI_Trace( bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask ) { + trace_t trace; + + trap_Trace( &trace, start, mins, maxs, end, passent, contentmask ); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy( trace.endpos, bsptrace->endpos ); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy( trace.plane.normal, bsptrace->plane.normal ); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + +/* +================== +BotAI_GetClientState +================== +*/ +int BotAI_GetClientState( int clientNum, playerState_t *state ) { + gentity_t *ent; + + ent = &g_entities[clientNum]; + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->client ) { + return qfalse; + } + + memcpy( state, &ent->client->ps, sizeof( playerState_t ) ); + return qtrue; +} + +/* +================== +BotAI_GetEntityState +================== +*/ +int BotAI_GetEntityState( int entityNum, entityState_t *state ) { + gentity_t *ent; + + ent = &g_entities[entityNum]; + memset( state, 0, sizeof( entityState_t ) ); + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->r.linked ) { + return qfalse; + } + if ( ent->r.svFlags & SVF_NOCLIENT ) { + return qfalse; + } + memcpy( state, &ent->s, sizeof( entityState_t ) ); + return qtrue; +} + +/* +================== +BotAI_GetSnapshotEntity +================== +*/ +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { + int entNum; + + entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); + if ( entNum == -1 ) { + memset( state, 0, sizeof( entityState_t ) ); + return -1; + } + + BotAI_GetEntityState( entNum, state ); + + return sequence + 1; +} + +/* +================== +BotAI_BotInitialChat +================== +*/ +void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { + int i, mcontext; + va_list ap; + char *p; + char *vars[MAX_MATCHVARIABLES]; + + memset( vars, 0, sizeof( vars ) ); + va_start( ap, type ); + p = va_arg( ap, char * ); + for ( i = 0; i < MAX_MATCHVARIABLES; i++ ) { + if ( !p ) { + break; + } + vars[i] = p; + p = va_arg( ap, char * ); + } + va_end( ap ); + + mcontext = CONTEXT_NORMAL | CONTEXT_NEARBYITEM | CONTEXT_NAMES; + if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) { + mcontext |= CONTEXT_CTFREDTEAM; + } else { mcontext |= CONTEXT_CTFBLUETEAM;} + + trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); +} + +/* +============== +BotInterbreeding +============== +*/ +void BotInterbreeding( void ) { + float ranks[MAX_CLIENTS]; + int parent1, parent2, child; + int i; + + // get rankings for all the bots + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( botstates[i] && botstates[i]->inuse ) { + ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } else { + ranks[i] = -1; + } + } + + if ( trap_GeneticParentsAndChildSelection( MAX_CLIENTS, ranks, &parent1, &parent2, &child ) ) { + trap_BotInterbreedGoalFuzzyLogic( botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs ); + trap_BotMutateGoalFuzzyLogic( botstates[child]->gs, 1 ); + } + // reset the kills and deaths + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( botstates[i] && botstates[i]->inuse ) { + botstates[i]->num_kills = 0; + botstates[i]->num_deaths = 0; + } + } +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo( int entnum, aas_entityinfo_t *info ) { + trap_AAS_EntityInfo( entnum, info ); +} + +/* +============== +NumBots +============== +*/ +int NumBots( void ) { + return numbots; +} + +/* +============== +AngleDifference +============== +*/ +float AngleDifference( float ang1, float ang2 ) { + float diff; + + diff = ang1 - ang2; + if ( ang1 > ang2 ) { + if ( diff > 180.0 ) { + diff -= 360.0; + } + } else { + if ( diff < -180.0 ) { + diff += 360.0; + } + } + return diff; +} + +/* +============== +BotChangeViewAngle +============== +*/ +float BotChangeViewAngle( float angle, float ideal_angle, float speed ) { + float move; + + angle = AngleMod( angle ); + ideal_angle = AngleMod( ideal_angle ); + if ( angle == ideal_angle ) { + return angle; + } + move = ideal_angle - angle; + if ( ideal_angle > angle ) { + if ( move > 180.0 ) { + move -= 360.0; + } + } else { + if ( move < -180.0 ) { + move += 360.0; + } + } + if ( move > 0 ) { + if ( move > speed ) { + move = speed; + } + } else { + if ( move < -speed ) { + move = -speed; + } + } + return AngleMod( angle + move ); +} + +/* +============== +BotChangeViewAngles +============== +*/ +void BotChangeViewAngles( bot_state_t *bs, float thinktime ) { + float diff, factor, maxchange, anglespeed; + int i; + + if ( bs->ideal_viewangles[PITCH] > 180 ) { + bs->ideal_viewangles[PITCH] -= 360; + } + // + if ( bs->enemy >= 0 ) { + factor = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01, 1 ); + maxchange = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800 ); + } else { + factor = 0.25; + maxchange = 300; + } + maxchange *= thinktime; + for ( i = 0; i < 2; i++ ) { + diff = abs( AngleDifference( bs->viewangles[i], bs->ideal_viewangles[i] ) ); + anglespeed = diff * factor; + if ( anglespeed > maxchange ) { + anglespeed = maxchange; + } + bs->viewangles[i] = BotChangeViewAngle( bs->viewangles[i], + bs->ideal_viewangles[i], anglespeed ); + //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` + //bs->viewangles[i] = bs->ideal_viewangles[i]; + } + if ( bs->viewangles[PITCH] > 180 ) { + bs->viewangles[PITCH] -= 360; + } + //elementary action: view + trap_EA_View( bs->client, bs->viewangles ); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand( bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time ) { + vec3_t angles, forward, right; + short temp; + int j; + + //clear the whole structure + memset( ucmd, 0, sizeof( usercmd_t ) ); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = time; + // + if ( bi->actionflags & ACTION_DELAYEDJUMP ) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + //set the buttons + if ( bi->actionflags & ACTION_RESPAWN ) { + ucmd->buttons = BUTTON_ATTACK; + } + if ( bi->actionflags & ACTION_ATTACK ) { + ucmd->buttons |= BUTTON_ATTACK; + } + if ( bi->actionflags & ACTION_TALK ) { + ucmd->buttons |= BUTTON_TALK; + } + if ( bi->actionflags & ACTION_GESTURE ) { + ucmd->buttons |= BUTTON_GESTURE; + } + if ( bi->actionflags & ACTION_USE ) { + ucmd->buttons |= BUTTON_USE_HOLDABLE; + } + if ( bi->actionflags & ACTION_WALK ) { + ucmd->buttons |= BUTTON_WALKING; + } + ucmd->weapon = bi->weapon; + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT( bi->viewangles[PITCH] ); + ucmd->angles[YAW] = ANGLE2SHORT( bi->viewangles[YAW] ); + ucmd->angles[ROLL] = ANGLE2SHORT( bi->viewangles[ROLL] ); + //subtract the delta angles + for ( j = 0; j < 3; j++ ) { + temp = ucmd->angles[j] - delta_angles[j]; + /*NOTE: disabled because temp should be mod first + if ( j == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) temp = 16000; + else if ( temp < -16000 ) temp = -16000; + } + */ + ucmd->angles[j] = temp; + } + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if ( bi->dir[2] ) { + angles[PITCH] = bi->viewangles[PITCH]; + } else { angles[PITCH] = 0;} + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors( angles, forward, right, NULL ); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / 400; + //set the view independent movement + ucmd->forwardmove = DotProduct( forward, bi->dir ) * bi->speed; + ucmd->rightmove = DotProduct( right, bi->dir ) * bi->speed; + ucmd->upmove = abs( forward[2] ) * bi->dir[2] * bi->speed; + //normal keyboard movement + if ( bi->actionflags & ACTION_MOVEFORWARD ) { + ucmd->forwardmove += 127; + } + if ( bi->actionflags & ACTION_MOVEBACK ) { + ucmd->forwardmove -= 127; + } + if ( bi->actionflags & ACTION_MOVELEFT ) { + ucmd->rightmove -= 127; + } + if ( bi->actionflags & ACTION_MOVERIGHT ) { + ucmd->rightmove += 127; + } + //jump/moveup + if ( bi->actionflags & ACTION_JUMP ) { + ucmd->upmove += 127; + } + //crouch/movedown + if ( bi->actionflags & ACTION_CROUCH ) { + ucmd->upmove -= 127; + } + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); + //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); +} + +/* +============== +BotUpdateInput +============== +*/ +void BotUpdateInput( bot_state_t *bs, int time ) { + bot_input_t bi; + int j; + + //add the delta angles to the bot's current view angles + for ( j = 0; j < 3; j++ ) { + bs->viewangles[j] = AngleMod( bs->viewangles[j] + SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); + } + // + BotChangeViewAngles( bs, (float) time / 1000 ); + trap_EA_GetInput( bs->client, (float) time / 1000, &bi ); + //respawn hack + if ( bi.actionflags & ACTION_RESPAWN ) { + if ( bs->lastucmd.buttons & BUTTON_ATTACK ) { + bi.actionflags &= ~( ACTION_RESPAWN | ACTION_ATTACK ); + } + } + // + BotInputToUserCommand( &bi, &bs->lastucmd, bs->cur_ps.delta_angles, time ); + bs->lastucmd.serverTime = time; + //subtract the delta angles + for ( j = 0; j < 3; j++ ) { + bs->viewangles[j] = AngleMod( bs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); + } +} + +/* +============== +BotAIRegularUpdate +============== +*/ +void BotAIRegularUpdate( void ) { + if ( regularupdate_time < trap_AAS_Time() ) { + trap_BotUpdateEntityItems(); + regularupdate_time = trap_AAS_Time() + 1; + } +} + +/* +============== +BotAI +============== +*/ +int BotAI( int client, float thinktime ) { + bot_state_t *bs; + char buf[1024], *args; + int j; + + trap_EA_ResetInput( client, NULL ); + // + bs = botstates[client]; + if ( !bs || !bs->inuse ) { + BotAI_Print( PRT_FATAL, "client %d hasn't been setup\n", client ); + return BLERR_AICLIENTNOTSETUP; + } + + //retrieve the current client state + BotAI_GetClientState( client, &bs->cur_ps ); + + //retrieve any waiting console messages + while ( trap_BotGetServerCommand( client, buf, sizeof( buf ) ) ) { + //have buf point to the command and args to the command arguments + args = strchr( buf, ' ' ); + if ( !args ) { + continue; + } + *args++ = '\0'; + + //remove color espace sequences from the arguments + Q_CleanStr( args ); + + //botai_import.Print(PRT_MESSAGE, "ConsoleMessage: \"%s\"\n", buf); + if ( !Q_stricmp( buf, "cp " ) ) { /*CenterPrintf*/ + } else if ( !Q_stricmp( buf, "cs" ) ) { /*ConfigStringModified*/ + } else if ( !Q_stricmp( buf, "print" ) ) { + trap_BotQueueConsoleMessage( bs->cs, CMS_NORMAL, args ); + } else if ( !Q_stricmp( buf, "chat" ) ) { + trap_BotQueueConsoleMessage( bs->cs, CMS_CHAT, args ); + } else if ( !Q_stricmp( buf, "tchat" ) ) { + trap_BotQueueConsoleMessage( bs->cs, CMS_CHAT, args ); + } else if ( !Q_stricmp( buf, "scores" ) ) { /*FIXME: parse scores?*/ + } else if ( !Q_stricmp( buf, "clientLevelShot" ) ) { /*ignore*/ + } + } + //add the delta angles to the bot's current view angles + for ( j = 0; j < 3; j++ ) { + bs->viewangles[j] = AngleMod( bs->viewangles[j] + SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); + } + //increase the local time of the bot + bs->ltime += thinktime; + // + bs->thinktime = thinktime; + //origin of the bot + VectorCopy( bs->cur_ps.origin, bs->origin ); + //eye coordinates of the bot + VectorCopy( bs->cur_ps.origin, bs->eye ); + bs->eye[2] += bs->cur_ps.viewheight; + //get the area the bot is in + bs->areanum = BotPointAreaNum( bs->origin ); + //the real AI + BotDeathmatchAI( bs, thinktime ); + //set the weapon selection every AI frame + trap_EA_SelectWeapon( bs->client, bs->weaponnum ); + //subtract the delta angles + for ( j = 0; j < 3; j++ ) { + bs->viewangles[j] = AngleMod( bs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); + } + //everything was ok + return BLERR_NOERROR; +} + +/* +================== +BotScheduleBotThink +================== +*/ +void BotScheduleBotThink( void ) { + int i, botnum; + + botnum = 0; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + //initialize the bot think residual time + botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots; + botnum++; + } +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient( int client, struct bot_settings_s *settings ) { + char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; + bot_state_t *bs; + int errnum; + + if ( !botstates[client] ) { + botstates[client] = G_Alloc( sizeof( bot_state_t ) ); + } + bs = botstates[client]; + + if ( bs && bs->inuse ) { + BotAI_Print( PRT_FATAL, "client %d already setup\n", client ); + return qfalse; + } + + if ( !trap_AAS_Initialized() ) { + BotAI_Print( PRT_FATAL, "AAS not initialized\n" ); + return qfalse; + } + + //load the bot character + bs->character = trap_BotLoadCharacter( settings->characterfile, settings->skill ); + if ( !bs->character ) { + BotAI_Print( PRT_FATAL, "couldn't load skill %d from %s\n", settings->skill, settings->characterfile ); + return qfalse; + } + //copy the settings + memcpy( &bs->settings, settings, sizeof( bot_settings_t ) ); + //allocate a goal state + bs->gs = trap_BotAllocGoalState( client ); + //load the item weights + trap_Characteristic_String( bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH ); + errnum = trap_BotLoadItemWeights( bs->gs, filename ); + if ( errnum != BLERR_NOERROR ) { + trap_BotFreeGoalState( bs->gs ); + return qfalse; + } + //allocate a weapon state + bs->ws = trap_BotAllocWeaponState(); + //load the weapon weights + trap_Characteristic_String( bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH ); + errnum = trap_BotLoadWeaponWeights( bs->ws, filename ); + if ( errnum != BLERR_NOERROR ) { + trap_BotFreeGoalState( bs->gs ); + trap_BotFreeWeaponState( bs->ws ); + return qfalse; + } + //allocate a chat state + bs->cs = trap_BotAllocChatState(); + //load the chat file + trap_Characteristic_String( bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH ); + trap_Characteristic_String( bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH ); + errnum = trap_BotLoadChatFile( bs->cs, filename, name ); + if ( errnum != BLERR_NOERROR ) { + trap_BotFreeChatState( bs->cs ); + trap_BotFreeGoalState( bs->gs ); + trap_BotFreeWeaponState( bs->ws ); + return qfalse; + } + //get the gender characteristic + trap_Characteristic_String( bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH ); + //set the chat gender + if ( *gender == 'f' || *gender == 'F' ) { + trap_BotSetChatGender( bs->cs, CHAT_GENDERFEMALE ); + } else if ( *gender == 'm' || *gender == 'M' ) { + trap_BotSetChatGender( bs->cs, CHAT_GENDERMALE ); + } else { trap_BotSetChatGender( bs->cs, CHAT_GENDERLESS );} + + bs->inuse = qtrue; + bs->client = client; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = trap_AAS_Time(); + bs->ms = trap_BotAllocMoveState(); + bs->walker = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_WALKER, 0, 1 ); + numbots++; + + if ( trap_Cvar_VariableIntegerValue( "bot_testichat" ) ) { + trap_BotLibVarSet( "bot_testichat", "1" ); + BotChatTest( bs ); + } + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + // + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient( int client ) { + bot_state_t *bs; + + // Wolfenstein + if ( g_entities[client].r.svFlags & SVF_CASTAI ) { + AICast_ShutdownClient( client ); + return BLERR_NOERROR; + } + // done. + + bs = botstates[client]; + if ( !bs || !bs->inuse ) { + // BotAI_Print(PRT_ERROR, "client %d already shutdown\n", client); + return BLERR_AICLIENTALREADYSHUTDOWN; + } + + if ( BotChat_ExitGame( bs ) ) { + trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL ); + } + + trap_BotFreeMoveState( bs->ms ); + //free the goal state + trap_BotFreeGoalState( bs->gs ); + //free the chat file + trap_BotFreeChatState( bs->cs ); + //free the weapon weights + trap_BotFreeWeaponState( bs->ws ); + //free the bot character + trap_BotFreeCharacter( bs->character ); + // + BotFreeWaypoints( bs->checkpoints ); + BotFreeWaypoints( bs->patrolpoints ); + //clear the bot state + memset( bs, 0, sizeof( bot_state_t ) ); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //there's one bot less + numbots--; + //everything went ok + return BLERR_NOERROR; +} + +/* +============== +BotResetState + +called when a bot enters the intermission or observer mode and +when the level is changed +============== +*/ +void BotResetState( bot_state_t *bs ) { + int client, entitynum, inuse; + int movestate, goalstate, chatstate, weaponstate; + bot_settings_t settings; + int character; + playerState_t ps; //current player state + float entergame_time; + + //save some things that should not be reset here + memcpy( &settings, &bs->settings, sizeof( bot_settings_t ) ); + memcpy( &ps, &bs->cur_ps, sizeof( playerState_t ) ); + inuse = bs->inuse; + client = bs->client; + entitynum = bs->entitynum; + character = bs->character; + movestate = bs->ms; + goalstate = bs->gs; + chatstate = bs->cs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //free checkpoints and patrol points + BotFreeWaypoints( bs->checkpoints ); + BotFreeWaypoints( bs->patrolpoints ); + //reset the whole state + memset( bs, 0, sizeof( bot_state_t ) ); + //copy back some state stuff that should not be reset + bs->ms = movestate; + bs->gs = goalstate; + bs->cs = chatstate; + bs->ws = weaponstate; + memcpy( &bs->cur_ps, &ps, sizeof( playerState_t ) ); + memcpy( &bs->settings, &settings, sizeof( bot_settings_t ) ); + bs->inuse = inuse; + bs->client = client; + bs->entitynum = entitynum; + bs->character = character; + bs->entergame_time = entergame_time; + //reset several states + if ( bs->ms ) { + trap_BotResetMoveState( bs->ms ); + } + if ( bs->gs ) { + trap_BotResetGoalState( bs->gs ); + } + if ( bs->ws ) { + trap_BotResetWeaponState( bs->ws ); + } + if ( bs->gs ) { + trap_BotResetAvoidGoals( bs->gs ); + } + if ( bs->ms ) { + trap_BotResetAvoidReach( bs->ms ); + } +} + +/* +============== +BotAILoadMap +============== +*/ +int BotAILoadMap( int restart ) { + int i; + vmCvar_t mapname; + + if ( !restart ) { + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + trap_BotLibLoadMap( mapname.string ); + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( botstates[i] && botstates[i]->inuse ) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + BotSetupDeathmatchAI(); + + return BLERR_NOERROR; +} + +/* +================== +BotAIStartFrame +================== +*/ +int BotAIStartFrame( int time ) { + int i; + gentity_t *ent; + bot_entitystate_t state; + //entityState_t entitystate; + //vec3_t mins = {-15, -15, -24}, maxs = {15, 15, 32}; + int elapsed_time, thinktime; + static int local_time; + static int botlib_residual; + static int lastbotthink_time; + + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + G_CheckBotSpawn(); + } + + trap_Cvar_Update( &bot_rocketjump ); + trap_Cvar_Update( &bot_grapple ); + trap_Cvar_Update( &bot_fastchat ); + trap_Cvar_Update( &bot_nochat ); + trap_Cvar_Update( &bot_testrchat ); + trap_Cvar_Update( &bot_thinktime ); + // Ridah, set the default AAS world + trap_AAS_SetCurrentWorld( 0 ); + trap_Cvar_Update( &memorydump ); + + if ( memorydump.integer ) { + trap_BotLibVarSet( "memorydump", "1" ); + trap_Cvar_Set( "memorydump", "0" ); + } + + //if the bot think time changed we should reschedule the bots + if ( bot_thinktime.integer != lastbotthink_time ) { + lastbotthink_time = bot_thinktime.integer; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + botlib_residual += elapsed_time; + + if ( elapsed_time > bot_thinktime.integer ) { + thinktime = elapsed_time; + } else { thinktime = bot_thinktime.integer;} + + // update the bot library + if ( botlib_residual >= thinktime ) { + botlib_residual -= thinktime; + + trap_BotLibStartFrame( (float) time / 1000 ); + + // Ridah, only check the default world + trap_AAS_SetCurrentWorld( 0 ); + + if ( !trap_AAS_Initialized() ) { + return BLERR_NOERROR; + } + + //update entities in the botlib + for ( i = 0; i < MAX_GENTITIES; i++ ) { + + // Ridah, in single player, we only need client entity information + if ( g_gametype.integer == GT_SINGLE_PLAYER && i > level.maxclients ) { + break; + } + + ent = &g_entities[i]; + if ( !ent->inuse ) { + continue; + } + if ( !ent->r.linked ) { + continue; + } + if ( ent->r.svFlags & SVF_NOCLIENT ) { + continue; + } + // + memset( &state, 0, sizeof( bot_entitystate_t ) ); + // + VectorCopy( ent->r.currentOrigin, state.origin ); + VectorCopy( ent->r.currentAngles, state.angles ); + VectorCopy( ent->s.origin2, state.old_origin ); + VectorCopy( ent->r.mins, state.mins ); + VectorCopy( ent->r.maxs, state.maxs ); + state.type = ent->s.eType; + state.flags = ent->s.eFlags; + if ( ent->r.bmodel ) { + state.solid = SOLID_BSP; + } else { state.solid = SOLID_BBOX;} + state.groundent = ent->s.groundEntityNum; + state.modelindex = ent->s.modelindex; + state.modelindex2 = ent->s.modelindex2; + state.frame = ent->s.frame; + //state.event = ent->s.event; + //state.eventParm = ent->s.eventParm; + state.powerups = ent->s.powerups; + state.legsAnim = ent->s.legsAnim; + state.torsoAnim = ent->s.torsoAnim; +// state.weapAnim = ent->s.weapAnim; //----(SA) +//----(SA) didn't want to comment in as I wasn't sure of any implications of changing the aas_entityinfo_t and bot_entitystate_t structures. + state.weapon = ent->s.weapon; + /* + if (!BotAI_GetEntityState(i, &entitystate)) continue; + // + memset(&state, 0, sizeof(bot_entitystate_t)); + // + VectorCopy(entitystate.pos.trBase, state.origin); + VectorCopy(entitystate.angles, state.angles); + VectorCopy(ent->s.origin2, state.old_origin); + //VectorCopy(ent->r.mins, state.mins); + //VectorCopy(ent->r.maxs, state.maxs); + state.type = entitystate.eType; + state.flags = entitystate.eFlags; + if (ent->r.bmodel) state.solid = SOLID_BSP; + else state.solid = SOLID_BBOX; + state.modelindex = entitystate.modelindex; + state.modelindex2 = entitystate.modelindex2; + state.frame = entitystate.frame; + state.event = entitystate.event; + state.eventParm = entitystate.eventParm; + state.powerups = entitystate.powerups; + state.legsAnim = entitystate.legsAnim; + state.torsoAnim = entitystate.torsoAnim; + state.weapon = entitystate.weapon; + */ + // + trap_BotLibUpdateEntity( i, &state ); + } + + BotAIRegularUpdate(); + + } + + // Ridah, in single player, don't need bot's thinking + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return BLERR_NOERROR; + } + + // execute scheduled bot AI + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + // Ridah + if ( g_entities[i].r.svFlags & SVF_CASTAI ) { + continue; + } + // done. + // + botstates[i]->botthink_residual += elapsed_time; + // + if ( botstates[i]->botthink_residual >= thinktime ) { + botstates[i]->botthink_residual -= thinktime; + + if ( !trap_AAS_Initialized() ) { + return BLERR_NOERROR; + } + + if ( g_entities[i].client->pers.connected == CON_CONNECTED ) { + BotAI( i, (float) thinktime / 1000 ); + } + } + } + + + // execute bot user commands every frame + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + // Ridah + if ( g_entities[i].r.svFlags & SVF_CASTAI ) { + continue; + } + // done. + if ( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + + BotUpdateInput( botstates[i], time ); + trap_BotUserCommand( botstates[i]->client, &botstates[i]->lastucmd ); + } + + return BLERR_NOERROR; +} + +/* +============== +BotInitLibrary +============== +*/ +int BotInitLibrary( void ) { + char buf[144]; + + //set the maxclients and maxentities library variables before calling BotSetupLibrary + trap_Cvar_VariableStringBuffer( "sv_maxclients", buf, sizeof( buf ) ); + if ( !strlen( buf ) ) { + strcpy( buf, "8" ); + } + trap_BotLibVarSet( "maxclients", buf ); + Com_sprintf( buf, sizeof( buf ), "%d", MAX_GENTITIES ); + trap_BotLibVarSet( "maxentities", buf ); + //bsp checksum + trap_Cvar_VariableStringBuffer( "sv_mapChecksum", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "sv_mapChecksum", buf ); + } + //maximum number of aas links + trap_Cvar_VariableStringBuffer( "max_aaslinks", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "max_aaslinks", buf ); + } + //maximum number of items in a level + trap_Cvar_VariableStringBuffer( "max_levelitems", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "max_levelitems", buf ); + } + //automatically launch WinBSPC if AAS file not available + trap_Cvar_VariableStringBuffer( "autolaunchbspc", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "autolaunchbspc", "1" ); + } + // + trap_Cvar_VariableStringBuffer( "g_gametype", buf, sizeof( buf ) ); + if ( !strlen( buf ) ) { + strcpy( buf, "0" ); + } + trap_BotLibVarSet( "g_gametype", buf ); + // + // Rafael gameskill + trap_Cvar_VariableStringBuffer( "g_gameskill", buf, sizeof( buf ) ); + if ( !strlen( buf ) ) { + strcpy( buf, "0" ); + } + trap_BotLibVarSet( "g_gamekill", buf ); + // done + // + trap_Cvar_VariableStringBuffer( "bot_developer", buf, sizeof( buf ) ); + if ( !strlen( buf ) ) { + strcpy( buf, "0" ); + } + trap_BotLibVarSet( "bot_developer", buf ); + //log file + trap_Cvar_VariableStringBuffer( "bot_developer", buf, sizeof( buf ) ); + if ( !strlen( buf ) ) { + strcpy( buf, "0" ); + } + trap_BotLibVarSet( "log", buf ); + //no chatting + trap_Cvar_VariableStringBuffer( "bot_nochat", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "nochat", "0" ); + } + //forced clustering calculations + trap_Cvar_VariableStringBuffer( "forceclustering", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "forceclustering", buf ); + } + //forced reachability calculations + trap_Cvar_VariableStringBuffer( "forcereachability", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "forcereachability", buf ); + } + //force writing of AAS to file + trap_Cvar_VariableStringBuffer( "forcewrite", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "forcewrite", buf ); + } + //no AAS optimization + trap_Cvar_VariableStringBuffer( "nooptimize", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "nooptimize", buf ); + } + //number of reachabilities to calculate each frame + trap_Cvar_VariableStringBuffer( "framereachability", buf, sizeof( buf ) ); + if ( !strlen( buf ) ) { + strcpy( buf, "20" ); + } + trap_BotLibVarSet( "framereachability", buf ); + // + trap_Cvar_VariableStringBuffer( "bot_reloadcharacters", buf, sizeof( buf ) ); + if ( !strlen( buf ) ) { + strcpy( buf, "0" ); + } + trap_BotLibVarSet( "bot_reloadcharacters", buf ); + //base directory + trap_Cvar_VariableStringBuffer( "fs_basepath", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "basedir", buf ); + } + //game directory + trap_Cvar_VariableStringBuffer( "fs_game", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "gamedir", buf ); + } + //cd directory + trap_Cvar_VariableStringBuffer( "fs_cdpath", buf, sizeof( buf ) ); + if ( strlen( buf ) ) { + trap_BotLibVarSet( "cddir", buf ); + } + //setup the bot library + return trap_BotLibSetup(); +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + int errnum; + +#ifdef RANDOMIZE + srand( (unsigned)time( NULL ) ); +#endif //RANDOMIZE + + trap_Cvar_Register( &bot_thinktime, "bot_thinktime", "100", 0 ); + trap_Cvar_Register( &memorydump, "memorydump", "0", 0 ); + + //if the game is restarted for a tournament + if ( restart ) { + return BLERR_NOERROR; + } + + //initialize the bot states + memset( botstates, 0, sizeof( botstates ) ); + + trap_Cvar_Register( &bot_thinktime, "bot_thinktime", "100", 0 ); + + errnum = BotInitLibrary(); + if ( errnum != BLERR_NOERROR ) { + return qfalse; + } + return BLERR_NOERROR; +} + +/* +============== +BotAIShutdown +============== +*/ +int BotAIShutdown( int restart ) { + + int i; + + //if the game is restarted for a tournament + if ( restart ) { + //shutdown all the bots in the botlib + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( botstates[i] && botstates[i]->inuse ) { + BotAIShutdownClient( botstates[i]->client ); + } + } + //don't shutdown the bot library + } else { + trap_BotLibShutdown(); + } + return qtrue; +} + diff --git a/src/botai/ai_main.h b/src/botai/ai_main.h new file mode 100644 index 0000000..97f8d0c --- /dev/null +++ b/src/botai/ai_main.h @@ -0,0 +1,242 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_main.h + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +//#define DEBUG +#define CTF + +#define MAX_ITEMS 256 +//bot flags +#define BFL_STRAFERIGHT 1 //strafe to the right +#define BFL_ATTACKED 2 //bot has attacked last ai frame +#define BFL_ATTACKJUMPED 4 //bot jumped during attack last frame +#define BFL_AIMATENEMY 8 //bot aimed at the enemy this frame +#define BFL_AVOIDRIGHT 16 //avoid obstacles by going to the right +#define BFL_IDEALVIEWSET 32 //bot has ideal view angles set +//long term goal types +#define LTG_TEAMHELP 1 //help a team mate +#define LTG_TEAMACCOMPANY 2 //accompany a team mate +#define LTG_DEFENDKEYAREA 3 //defend a key area +#define LTG_GETFLAG 4 //get the enemy flag +#define LTG_RUSHBASE 5 //rush to the base +#define LTG_RETURNFLAG 6 //return the flag +#define LTG_CAMP 7 //camp somewhere +#define LTG_CAMPORDER 8 //ordered to camp somewhere +#define LTG_PATROL 9 //patrol +#define LTG_GETITEM 10 //get an item +#define LTG_KILL 11 //kill someone +//some goal dedication times +#define TEAM_HELP_TIME 60 //1 minute teamplay help time +#define TEAM_ACCOMPANY_TIME 600 //10 minutes teamplay accompany time +#define TEAM_DEFENDKEYAREA_TIME 240 //4 minutes ctf defend base time +#define TEAM_CAMP_TIME 600 //10 minutes camping time +#define TEAM_PATROL_TIME 600 //10 minutes patrolling time +#define TEAM_LEAD_TIME 600 //10 minutes taking the lead +#define TEAM_GETITEM_TIME 60 //1 minute +#define TEAM_KILL_SOMEONE 180 //3 minute to kill someone +#define CTF_GETFLAG_TIME 240 //4 minutes ctf get flag time +#define CTF_RUSHBASE_TIME 120 //2 minutes ctf rush base time +#define CTF_RETURNFLAG_TIME 180 //3 minutes to return the flag +#define CTF_ROAM_TIME 60 //1 minute ctf roam time +//patrol flags +#define PATROL_LOOP 1 +#define PATROL_REVERSE 2 +#define PATROL_BACK 4 +//copied from the aas file header +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 + +//check points +typedef struct bot_waypoint_s +{ + int inuse; + char name[32]; + bot_goal_t goal; + struct bot_waypoint_s *next, *prev; +} bot_waypoint_t; + +//bot state +typedef struct bot_state_s +{ + int inuse; //true if this state is used by a bot client + int botthink_residual; //residual for the bot thinks + int client; //client number of the bot + int entitynum; //entity number of the bot + playerState_t cur_ps; //current player state + int last_eFlags; //last ps flags + usercmd_t lastucmd; //usercmd from last frame + int entityeventTime[1024]; //last entity event time + // + bot_settings_t settings; //several bot settings + int ( *ainode )( struct bot_state_s *bs ); //current AI node + float thinktime; //time the bot thinks this frame + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + int presencetype; //presence type of the bot + vec3_t eye; //eye coordinates of the bot + int areanum; //the number of the area the bot is in + int inventory[MAX_ITEMS]; //string with items amounts the bot has + int tfl; //the travel flags the bot uses + int flags; //several flags + int respawn_wait; //wait until respawned + int lasthealth; //health value previous frame + int lastkilledplayer; //last killed player + int lastkilledby; //player that last killed this bot + int botdeathtype; //the death type of the bot + int enemydeathtype; //the death type of the enemy + int botsuicide; //true when the bot suicides + int enemysuicide; //true when the enemy of the bot suicides + int setupcount; //true when the bot has just been setup + int entergamechat; //true when the bot used an enter game chat + int num_deaths; //number of time this bot died + int num_kills; //number of kills of this bot + int revenge_enemy; //the revenge enemy + int revenge_kills; //number of kills the enemy made + int lastframe_health; //health value the last frame + int lasthitcount; //number of hits last frame + int chatto; //chat to all or team + float walker; //walker charactertic + float ltime; //local bot time + float entergame_time; //time the bot entered the game + float ltg_time; //long term goal time + float nbg_time; //nearby goal time + float respawn_time; //time the bot takes to respawn + float respawnchat_time; //time the bot started a chat during respawn + float chase_time; //time the bot will chase the enemy + float enemyvisible_time; //time the enemy was last visible + float check_time; //time to check for nearby items + float stand_time; //time the bot is standing still + float lastchat_time; //time the bot last selected a chat + float standfindenemy_time; //time to find enemy while standing + float attackstrafe_time; //time the bot is strafing in one dir + float attackcrouch_time; //time the bot will stop crouching + float attackchase_time; //time the bot chases during actual attack + float attackjump_time; //time the bot jumped during attack + float enemysight_time; //time before reacting to enemy + float enemydeath_time; //time the enemy died + float enemyposition_time; //time the position and velocity of the enemy were stored + float activate_time; //time to activate something + float activatemessage_time; //time to show activate message + float defendaway_time; //time away while defending + float defendaway_range; //max travel time away from defend area + float rushbaseaway_time; //time away from rushing to the base + float ctfroam_time; //time the bot is roaming in ctf + float killedenemy_time; //time the bot killed the enemy + float arrive_time; //time arrived (at companion) + float lastair_time; //last time the bot had air + float teleport_time; //last time the bot teleported + float camp_time; //last time camped + float camp_range; //camp range + float weaponchange_time; //time the bot started changing weapons + float firethrottlewait_time; //amount of time to wait + float firethrottleshoot_time; //amount of time to shoot + vec3_t aimtarget; + vec3_t enemyvelocity; //enemy velocity 0.5 secs ago during battle + vec3_t enemyorigin; //enemy origin 0.5 secs ago during battle + // + int character; //the bot character + int ms; //move state of the bot + int gs; //goal state of the bot + int cs; //chat state of the bot + int ws; //weapon state of the bot + // + int enemy; //enemy entity number + int lastenemyareanum; //last reachability area the enemy was in + vec3_t lastenemyorigin; //last origin of the enemy in the reachability area + int weaponnum; //current weapon number + vec3_t viewangles; //current view angles + vec3_t ideal_viewangles; //ideal view angles + // + int ltgtype; //long term goal type + // + int teammate; //team mate + bot_goal_t teamgoal; //the team goal + float teammessage_time; //time to message team mates what the bot is doing + float teamgoal_time; //time to stop helping team mate + float teammatevisible_time; //last time the team mate was NOT visible + // + int lead_teammate; //team mate the bot is leading + bot_goal_t lead_teamgoal; //team goal while leading + float lead_time; //time leading someone + float leadvisible_time; //last time the team mate was visible + float leadmessage_time; //last time a messaged was sent to the team mate + float leadbackup_time; //time backing up towards team mate + // + char teamleader[32]; //netname of the team leader + float askteamleader_time; //time asked for team leader + float becometeamleader_time; //time the bot will become the team leader + float teamgiveorders_time; //time to give team orders + int numteammates; //number of team mates + int redflagstatus; //0 = at base, 1 = not at base + int blueflagstatus; //0 = at base, 1 = not at base + int flagstatuschanged; //flag status changed + int forceorders; //true if forced to give orders + int flagcarrier; //team mate carrying the enemy flag + char subteam[32]; //sub team name + float formation_dist; //formation team mate intervening space + char formation_teammate[16]; //netname of the team mate the bot uses for relative positioning + float formation_angle; //angle relative to the formation team mate + vec3_t formation_dir; //the direction the formation is moving in + vec3_t formation_origin; //origin the bot uses for relative positioning + bot_goal_t formation_goal; //formation goal + bot_goal_t activategoal; //goal to activate (buttons etc.) + bot_waypoint_t *checkpoints; //check points + bot_waypoint_t *patrolpoints; //patrol points + bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for + int patrolflags; //patrol flags +} bot_state_t; + +//resets the whole bot state +void BotResetState( bot_state_t *bs ); +//returns the number of bots in the game +int NumBots( void ); +//returns info about the entity +void BotEntityInfo( int entnum, aas_entityinfo_t *info ); + +// Ridah, defines for AI Cast system +int AICast_ShutdownClient( int client ); +void AICast_Init( void ); +void AICast_StartFrame( int time ); +// done. + +// from the game source +void QDECL BotAI_Print( int type, char *fmt, ... ); +void QDECL QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ); +void BotAI_Trace( bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask ); +int BotAI_GetClientState( int clientNum, playerState_t *state ); +int BotAI_GetEntityState( int entityNum, entityState_t *state ); +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); diff --git a/src/botai/ai_team.c b/src/botai/ai_team.c new file mode 100644 index 0000000..2b455c8 --- /dev/null +++ b/src/botai/ai_team.c @@ -0,0 +1,612 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_team.c + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +#include "../game/g_local.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../botai/botai.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" + + +/* +================== +BotValidTeamLeader +================== +*/ +int BotValidTeamLeader( bot_state_t *bs ) { + if ( !strlen( bs->teamleader ) ) { + return qfalse; + } + if ( ClientFromName( bs->teamleader ) == -1 ) { + return qfalse; + } + return qtrue; +} + +/* +================== +BotNumTeamMates +================== +*/ +int BotNumTeamMates( bot_state_t *bs ) { + int i, numplayers; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + numplayers = 0; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + //if no config string or no name + if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) { + continue; + } + //skip spectators + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + continue; + } + // + if ( BotSameTeam( bs, i ) ) { + numplayers++; + } + } + return numplayers; +} + +/* +================== +BotClientTravelTimeToGoal +================== +*/ +int BotClientTravelTimeToGoal( int client, bot_goal_t *goal ) { + playerState_t ps; + int areanum; + + BotAI_GetClientState( client, &ps ); + areanum = BotPointAreaNum( ps.origin ); + if ( !areanum ) { + return 1; + } + return trap_AAS_AreaTravelTimeToGoalArea( areanum, ps.origin, goal->areanum, TFL_DEFAULT ); +} + +/* +================== +BotSortTeamMatesByBaseTravelTime +================== +*/ +int BotSortTeamMatesByBaseTravelTime( bot_state_t *bs, int *teammates, int maxteammates ) { + + int i, j, k, numteammates, traveltime; + char buf[MAX_INFO_STRING]; + static int maxclients; + int traveltimes[MAX_CLIENTS]; + bot_goal_t *goal; + + if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) { + goal = &ctf_redflag; + } else { goal = &ctf_blueflag;} + + if ( !maxclients ) { + maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + } + + numteammates = 0; + for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) { + trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) ); + //if no config string or no name + if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) { + continue; + } + //skip spectators + if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) { + continue; + } + // + if ( BotSameTeam( bs, i ) ) { + // + traveltime = BotClientTravelTimeToGoal( i, goal ); + // + for ( j = 0; j < numteammates; j++ ) { + if ( traveltime < traveltimes[j] ) { + for ( k = numteammates; k > j; k-- ) { + traveltimes[k] = traveltimes[k - 1]; + teammates[k] = teammates[k - 1]; + } + traveltimes[j] = traveltime; + teammates[j] = i; + break; + } + } + if ( j >= numteammates ) { + traveltimes[j] = traveltime; + teammates[j] = i; + } + numteammates++; + if ( numteammates >= maxteammates ) { + break; + } + } + } + return numteammates; +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrder( bot_state_t *bs, int toclient ) { + char teamchat[MAX_MESSAGE_SIZE]; + char buf[MAX_MESSAGE_SIZE]; + char name[MAX_NETNAME]; + + //if the bot is talking to itself + if ( bs->client == toclient ) { + //don't show the message just put it in the console message queue + trap_BotGetChatMessage( bs->cs, buf, sizeof( buf ) ); + ClientName( bs->client, name, sizeof( name ) ); + Com_sprintf( teamchat, sizeof( teamchat ), "(%s): %s", name, buf ); + trap_BotQueueConsoleMessage( bs->cs, CMS_CHAT, teamchat ); + } else { + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsNotAtBase( bot_state_t *bs ) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime( bs, teammates, sizeof( teammates ) ); + //different orders based on the number of team mates + switch ( bs->numteammates ) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if ( teammates[0] != bs->flagcarrier ) { + other = teammates[0]; + } else { other = teammates[1];} + ClientName( other, name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, other ); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to accompany the flag carrier + if ( teammates[0] != bs->flagcarrier ) { + other = teammates[0]; + } else { other = teammates[1];} + ClientName( other, name, sizeof( name ) ); + ClientName( bs->flagcarrier, carriername, sizeof( carriername ) ); + if ( bs->flagcarrier == bs->client ) { + BotAI_BotInitialChat( bs, "cmd_accompanyme", name, NULL ); + } else { + BotAI_BotInitialChat( bs, "cmd_accompany", name, carriername, NULL ); + } + BotSayTeamOrder( bs, other ); + //tell the one furthest from the the base not carrying the flag to get the enemy flag + if ( teammates[2] != bs->flagcarrier ) { + other = teammates[2]; + } else { other = teammates[1];} + ClientName( other, name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, other ); + break; + } + default: + { + defenders = (int) ( float ) numteammates * 0.4 + 0.5; + attackers = (int) ( float ) numteammates * 0.5 + 0.5; + ClientName( bs->flagcarrier, carriername, sizeof( carriername ) ); + for ( i = 0; i < defenders; i++ ) { + // + if ( teammates[i] == bs->flagcarrier ) { + continue; + } + // + ClientName( teammates[i], name, sizeof( name ) ); + if ( bs->flagcarrier == bs->client ) { + BotAI_BotInitialChat( bs, "cmd_accompanyme", name, NULL ); + } else { + BotAI_BotInitialChat( bs, "cmd_accompany", name, carriername, NULL ); + } + BotSayTeamOrder( bs, teammates[i] ); + } + for ( i = 0; i < attackers; i++ ) { + // + if ( teammates[numteammates - i - 1] == bs->flagcarrier ) { + continue; + } + // + ClientName( teammates[numteammates - i - 1], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, teammates[numteammates - i - 1] ); + } + // + break; + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_FlagNotAtBase( bot_state_t *bs ) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime( bs, teammates, sizeof( teammates ) ); + //different orders based on the number of team mates + switch ( bs->numteammates ) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName( teammates[0], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, teammates[0] ); + //the other will get the flag + ClientName( teammates[1], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, teammates[1] ); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName( teammates[0], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, teammates[0] ); + //the other two get the flag + ClientName( teammates[1], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, teammates[1] ); + // + ClientName( teammates[2], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, teammates[2] ); + break; + } + default: + { + defenders = (int) ( float ) numteammates * 0.3 + 0.5; + attackers = (int) ( float ) numteammates * 0.5 + 0.5; + for ( i = 0; i < defenders; i++ ) { + // + ClientName( teammates[i], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, teammates[i] ); + } + for ( i = 0; i < attackers; i++ ) { + // + ClientName( teammates[numteammates - i - 1], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, teammates[numteammates - i - 1] ); + } + // + break; + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_EnemyFlagNotAtBase( bot_state_t *bs ) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime( bs, teammates, sizeof( teammates ) ); + //different orders based on the number of team mates + switch ( numteammates ) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if ( teammates[0] == bs->flagcarrier ) { + other = teammates[1]; + } else { other = teammates[0];} + ClientName( other, name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, other ); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if ( teammates[0] != bs->flagcarrier ) { + other = teammates[0]; + } else { other = teammates[1];} + ClientName( other, name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, other ); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if ( teammates[2] != bs->flagcarrier ) { + other = teammates[2]; + } else { other = teammates[1];} + ClientName( other, name, sizeof( name ) ); + ClientName( bs->flagcarrier, carriername, sizeof( carriername ) ); + if ( bs->flagcarrier == bs->client ) { + BotAI_BotInitialChat( bs, "cmd_accompanyme", name, NULL ); + } else { + BotAI_BotInitialChat( bs, "cmd_accompany", name, carriername, NULL ); + } + BotSayTeamOrder( bs, other ); + break; + } + default: + { + //40% will defend the base + defenders = (int) ( float ) numteammates * 0.4 + 0.5; + //50% accompanies the flag carrier + attackers = (int) ( float ) numteammates * 0.5 + 0.5; + for ( i = 0; i < defenders; i++ ) { + // + if ( teammates[i] == bs->flagcarrier ) { + continue; + } + ClientName( teammates[i], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, teammates[i] ); + } + ClientName( bs->flagcarrier, carriername, sizeof( carriername ) ); + for ( i = 0; i < attackers; i++ ) { + // + if ( teammates[numteammates - i - 1] == bs->flagcarrier ) { + continue; + } + // + ClientName( teammates[numteammates - i - 1], name, sizeof( name ) ); + if ( bs->flagcarrier == bs->client ) { + BotAI_BotInitialChat( bs, "cmd_accompanyme", name, NULL ); + } else { + BotAI_BotInitialChat( bs, "cmd_accompany", name, carriername, NULL ); + } + BotSayTeamOrder( bs, teammates[numteammates - i - 1] ); + } + // + break; + } + } +} + + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsAtBase( bot_state_t *bs ) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; +// char buf[MAX_MESSAGE_SIZE]; + + numteammates = BotSortTeamMatesByBaseTravelTime( bs, teammates, sizeof( teammates ) ); + //different orders based on the number of team mates + switch ( numteammates ) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName( teammates[0], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, teammates[0] ); + //the other will get the flag + ClientName( teammates[1], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, teammates[1] ); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName( teammates[0], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, teammates[0] ); + //the second one closest to the base will defend the base + ClientName( teammates[1], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, teammates[1] ); + //the other will get the flag + ClientName( teammates[2], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, teammates[2] ); + break; + } + default: + { + defenders = (int) ( float ) numteammates * 0.5 + 0.5; + attackers = (int) ( float ) numteammates * 0.3 + 0.5; + for ( i = 0; i < defenders; i++ ) { + // + ClientName( teammates[i], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL ); + BotSayTeamOrder( bs, teammates[i] ); + } + for ( i = 0; i < attackers; i++ ) { + // + ClientName( teammates[numteammates - i - 1], name, sizeof( name ) ); + BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL ); + BotSayTeamOrder( bs, teammates[numteammates - i - 1] ); + } + // + break; + } + } +} + + +/* +================== +BotTeamOrders +================== +*/ +void BotTeamOrders( bot_state_t *bs ) { + //no teamplay orders at this time +} + + +/* +================== +BotTeamAI +================== +*/ +void BotTeamAI( bot_state_t *bs ) { + int numteammates, flagstatus; + char netname[MAX_NETNAME]; + + // + if ( gametype != GT_TEAM && gametype != GT_CTF ) { + return; + } + //make sure we've got a valid team leader + if ( !BotValidTeamLeader( bs ) ) { + // + if ( !bs->askteamleader_time && !bs->becometeamleader_time ) { + if ( bs->entergame_time + 10 > trap_AAS_Time() ) { + bs->askteamleader_time = trap_AAS_Time() + 5 + random() * 10; + } else { + bs->becometeamleader_time = trap_AAS_Time() + 5 + random() * 10; + } + } + if ( bs->askteamleader_time && bs->askteamleader_time < trap_AAS_Time() ) { + //if asked for a team leader and no repsonse + BotAI_BotInitialChat( bs, "whoisteamleader", NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + bs->askteamleader_time = 0; + bs->becometeamleader_time = trap_AAS_Time() + 15 + random() * 10; + } + if ( bs->becometeamleader_time && bs->becometeamleader_time < trap_AAS_Time() ) { + BotAI_BotInitialChat( bs, "iamteamleader", NULL ); + trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM ); + ClientName( bs->client, netname, sizeof( netname ) ); + strncpy( bs->teamleader, netname, sizeof( bs->teamleader ) ); + bs->teamleader[sizeof( bs->teamleader )] = '\0'; + bs->becometeamleader_time = 0; + } + return; + } + bs->askteamleader_time = 0; + bs->becometeamleader_time = 0; + + //return if this bot is NOT the team leader + ClientName( bs->client, netname, sizeof( netname ) ); + if ( Q_stricmp( netname, bs->teamleader ) != 0 ) { + return; + } + // + //if the game starts OR a new player comes onto the team OR a player leaves the team + // + numteammates = BotNumTeamMates( bs ); + //give orders + switch ( gametype ) { + case GT_TEAM: + { + if ( bs->numteammates != numteammates || bs->forceorders ) { + bs->teamgiveorders_time = trap_AAS_Time(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if ( bs->teamgiveorders_time < trap_AAS_Time() - 5 ) { + BotTeamOrders( bs ); + // + bs->teamgiveorders_time = 0; + } + break; + } + case GT_CTF: + { + // + if ( bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders ) { + bs->teamgiveorders_time = trap_AAS_Time(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if it's time to give orders + if ( bs->teamgiveorders_time && bs->teamgiveorders_time < trap_AAS_Time() - 3 ) { + // + if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) { + flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + } else { flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus;} + // + switch ( flagstatus ) { + case 0: BotCTFOrders_BothFlagsAtBase( bs ); break; + case 1: BotCTFOrders_EnemyFlagNotAtBase( bs ); break; + case 2: BotCTFOrders_FlagNotAtBase( bs ); break; + case 3: BotCTFOrders_BothFlagsNotAtBase( bs ); break; + } + // + bs->teamgiveorders_time = 0; + } + break; + } + } +} + + diff --git a/src/botai/ai_team.h b/src/botai/ai_team.h new file mode 100644 index 0000000..a560406 --- /dev/null +++ b/src/botai/ai_team.h @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: ai_team.h + * + * desc: Quake3 bot AI + * + * + *****************************************************************************/ + +void BotTeamAI( bot_state_t *bs ); + + diff --git a/src/botai/botai.h b/src/botai/botai.h new file mode 100644 index 0000000..fbd9d11 --- /dev/null +++ b/src/botai/botai.h @@ -0,0 +1,110 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: botai.h +// Function: bot AI +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-08-18 +// Tab Size: 3 +//=========================================================================== + +//debug line colors +#define LINECOLOR_NONE -1 +#define LINECOLOR_RED 1 +#define LINECOLOR_GREEN 2 +#define LINECOLOR_BLUE 3 +#define LINECOLOR_YELLOW 4 +#define LINECOLOR_ORANGE 5 + +//Print types +#define PRT_MESSAGE 1 +#define PRT_WARNING 2 +#define PRT_ERROR 3 +#define PRT_FATAL 4 +#define PRT_EXIT 5 + +//console message types +#define CMS_NORMAL 0 +#define CMS_CHAT 1 + +//some maxs +#define MAX_NETNAME 36 +#define MAX_CLIENTSKINNAME 128 +#define MAX_FILEPATH 144 +#define MAX_CHARACTERNAME 144 + +#ifndef BSPTRACE + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//remove the bsp_trace_s structure definition l8r on +//a trace is returned when a box is swept through the world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // the hit point surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; + +#define BSPTRACE +#endif // BSPTRACE + +// +// imported functions used for the BotAI +// + + +// from the server +/* +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ); +void trap_Cvar_Update( vmCvar_t *cvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +int trap_Cvar_VariableIntegerValue( const char *var_name ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +void trap_GetConfigstring( int num, char *buffer, int bufferSize ); +void trap_GetServerinfo( char *buffer, int bufferSize ); +int trap_PointContents( const vec3_t point, int passEntityNum ); +qboolean trap_InPVS( const vec3_t p1, const vec3_t p2 ); +int trap_BotAllocateClient( void ); +void trap_BotFreeClient( int clientNum ); +*/ diff --git a/src/botai/chars.h b/src/botai/chars.h new file mode 100644 index 0000000..1722730 --- /dev/null +++ b/src/botai/chars.h @@ -0,0 +1,150 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: chars.h +// Function: bot characteristics +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-09-08 +// Tab Size: 4 (real tabs) +//=========================================================================== + + +//======================================================== +//======================================================== +//name +#define CHARACTERISTIC_NAME 0 //string +//gender of the bot +#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") +//attack skill +// > 0.0 && < 0.2 = don't move +// > 0.3 && < 1.0 = aim at enemy during retreat +// > 0.0 && < 0.4 = only move forward/backward +// >= 0.4 && < 1.0 = circle strafing +// > 0.7 && < 1.0 = random strafe direction change +#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] +//weapon weight file +#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string +//view angle difference to angle change factor +#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] +//maximum view angle change +#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] +//reaction time in seconds +#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] +//accuracy when aiming +#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] +//weapon specific aim accuracy +#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 +#define CHARACTERISTIC_AIM_ACCURACY_SP5 13 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 +#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] +//skill when aiming +// > 0.0 && < 0.9 = aim is affected by enemy movement +// > 0.4 && <= 0.8 = enemy linear leading +// > 0.8 && <= 1.0 = enemy exact movement leading +// > 0.5 && <= 1.0 = prediction shots when enemy is not visible +// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry +#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] +//weapon specific aim skill +#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_SP5 19 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] +//======================================================== +//chat +//======================================================== +//file with chats +#define CHARACTERISTIC_CHAT_FILE 21 //string +//name of the chat character +#define CHARACTERISTIC_CHAT_NAME 22 //string +//characters per minute type speed +#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] +//tendency to insult/praise +#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] +//tendency to chat misc +#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] +//tendency to chat at start or end of level +#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] +//tendency to chat entering or exiting the game +#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] +//tendency to chat when killed someone +#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] +//tendency to chat when died +#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] +//tendency to chat when enemy suicides +#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] +//tendency to chat when hit while talking +#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] +//tendency to chat when bot was hit but didn't dye +#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] +//tendency to chat when bot hit the enemy but enemy didn't dye +#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] +//tendency to randomly chat +#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] +//tendency to reply +#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] +//======================================================== +//movement +//======================================================== +//tendency to crouch +#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] +//tendency to jump +#define CHARACTERISTIC_JUMPER 37 //float [0, 1] +//tendency to walk +#define CHARACTERISTIC_WALKER 48 //float [0, 1] +//tendency to jump using a weapon +#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] +//tendency to use the grapple hook when available +#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! +//======================================================== +//goal +//======================================================== +//item weight file +#define CHARACTERISTIC_ITEMWEIGHTS 40 //string +//the aggression of the bot +#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] +//the self preservation of the bot (rockets near walls etc.) +#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] +//how likely the bot is to take revenge +#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! +//tendency to camp +#define CHARACTERISTIC_CAMPER 44 //float [0, 1] +//======================================================== +//======================================================== +//tendency to get easy frags +#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] +//how alert the bot is (view distance) +#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] +//how much the bot fires it's weapon +#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] + diff --git a/src/botai/inv.h b/src/botai/inv.h new file mode 100644 index 0000000..219d520 --- /dev/null +++ b/src/botai/inv.h @@ -0,0 +1,128 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: inv.h + * + * desc: + * +*/ + + +#define INVENTORY_NONE 0 +//armor +#define INVENTORY_ARMOR 1 +//weapons +#define INVENTORY_LUGER 4 +#define INVENTORY_MAUSER 5 +#define INVENTORY_MP40 6 +#define INVENTORY_SP5 7 +#define INVENTORY_ROCKETLAUNCHER 8 +#define INVENTORY_GRENADELAUNCHER 9 +#define INVENTORY_VENOM 10 +#define INVENTORY_FLAMETHROWER 11 +#define INVENTORY_CROSS 12 + +#define INVENTORY_GAUNTLET 13 + + +// please leave these open up to 27 (INVENTORY_9MM) (and double check defines when merging) +// the inventory max (MAX_ITEMS) is 256, so we aren't too concerned about running out of space + +//ammo +#define INVENTORY_9MM 27 +#define INVENTORY_792MM 28 +#define INVENTORY_SP5AMMO 29 +#define INVENTORY_ROCKETS 30 +#define INVENTORY_GRENADES 31 +#define INVENTORY_127MM 32 +#define INVENTORY_FUEL 33 +#define INVENTORY_CHARGES 34 + +// please leave these open up to 48 (INVENTORY_HEALTH) (and double check defines when merging) +// the inventory max (MAX_ITEMS) is 256, so we aren't too concerned about running out of space + +//powerups +#define INVENTORY_HEALTH 48 +#define INVENTORY_TELEPORTER 49 +#define INVENTORY_MEDKIT 50 +#define INVENTORY_QUAD 51 +#define INVENTORY_ENVIRONMENTSUIT 52 +#define INVENTORY_HASTE 53 +#define INVENTORY_INVISIBILITY 54 +#define INVENTORY_REGEN 55 +#define INVENTORY_FLIGHT 56 +#define INVENTORY_REDFLAG 57 +#define INVENTORY_BLUEFLAG 58 +//enemy stuff +#define ENEMY_HORIZONTAL_DIST 200 +#define ENEMY_HEIGHT 201 +#define NUM_VISIBLE_ENEMIES 202 +#define NUM_VISIBLE_TEAMMATES 203 + +//item numbers (make sure they are in sync with bg_itemlist in bg_misc.c) +#define MODELINDEX_ARMORSHARD 1 +#define MODELINDEX_ARMORCOMBAT 2 +#define MODELINDEX_ARMORBODY 3 +#define MODELINDEX_HEALTHSMALL 4 +#define MODELINDEX_HEALTH 5 +#define MODELINDEX_HEALTHLARGE 6 +#define MODELINDEX_HEALTHMEGA 7 + +#define MODELINDEX_GAUNTLET 8 +#define MODELINDEX_SHOTGUN 9 +#define MODELINDEX_MACHINEGUN 10 +#define MODELINDEX_GRENADELAUNCHER 11 +#define MODELINDEX_ROCKETLAUNCHER 12 +#define MODELINDEX_LIGHTNING 13 +#define MODELINDEX_RAILGUN 14 +#define MODELINDEX_SP5 15 +#define MODELINDEX_BFG10K 16 +#define MODELINDEX_GRAPPLINGHOOK 17 + +#define MODELINDEX_SHELLS 18 +#define MODELINDEX_BULLETS 19 +#define MODELINDEX_GRENADES 20 +#define MODELINDEX_CELLS 21 +#define MODELINDEX_LIGHTNINGAMMO 22 +#define MODELINDEX_ROCKETS 23 +#define MODELINDEX_SLUGS 24 +#define MODELINDEX_BFGAMMO 25 + +#define MODELINDEX_TELEPORTER 26 +#define MODELINDEX_MEDKIT 27 +#define MODELINDEX_QUAD 28 +#define MODELINDEX_ENVIRONMENTSUIT 29 +#define MODELINDEX_HASTE 30 +#define MODELINDEX_INVISIBILITY 31 +#define MODELINDEX_REGEN 32 +#define MODELINDEX_FLIGHT 33 +#define MODELINDEX_REDFLAG 34 +#define MODELINDEX_BLUEFLAG 35 + + diff --git a/src/botai/match.h b/src/botai/match.h new file mode 100644 index 0000000..10b3d01 --- /dev/null +++ b/src/botai/match.h @@ -0,0 +1,134 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: match.h +// Function: match template defines +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-10-01 +// Tab Size: 4 (real tabs) +// +//=========================================================================== + +//match template contexts +#define MTCONTEXT_ENTERGAME 2 +#define MTCONTEXT_INITIALTEAMCHAT 4 +#define MTCONTEXT_TIME 8 +#define MTCONTEXT_TEAMMATE 16 +#define MTCONTEXT_ADDRESSEE 32 +#define MTCONTEXT_PATROLKEYAREA 64 +#define MTCONTEXT_REPLYCHAT 128 +#define MTCONTEXT_CTF 256 + +//message types +#define MSG_ENTERGAME 2 //enter game message +#define MSG_HELP 3 //help someone +#define MSG_ACCOMPANY 4 //accompany someone +#define MSG_DEFENDKEYAREA 5 //defend a key area +#define MSG_RUSHBASE 6 //everyone rush to base +#define MSG_GETFLAG 7 //get the enemy flag +#define MSG_STARTTEAMLEADERSHIP 8 //someone wants to become the team leader +#define MSG_STOPTEAMLEADERSHIP 9 //someone wants to stop being the team leader +#define MSG_WHOISTEAMLAEDER 10 //who is the team leader +#define MSG_WAIT 11 //wait for someone +#define MSG_WHATAREYOUDOING 12 //what are you doing? +#define MSG_JOINSUBTEAM 13 //join a sub-team +#define MSG_LEAVESUBTEAM 14 //leave a sub-team +#define MSG_CREATENEWFORMATION 15 //create a new formation +#define MSG_FORMATIONPOSITION 16 //tell someone his/her position in a formation +#define MSG_FORMATIONSPACE 17 //set the formation intervening space +#define MSG_DOFORMATION 18 //form a known formation +#define MSG_DISMISS 19 //dismiss commanded team mates +#define MSG_CAMP 20 //camp somewhere +#define MSG_CHECKPOINT 21 //remember a check point +#define MSG_PATROL 22 //patrol between certain keypoints +#define MSG_LEADTHEWAY 23 //lead the way +#define MSG_GETITEM 24 //get an item +#define MSG_KILL 25 //kill someone +#define MSG_WHEREAREYOU 26 //where is someone +#define MSG_RETURNFLAG 27 //return the flag +#define MSG_WHATISMYCOMMAND 28 //ask the team leader what to do +#define MSG_WHICHTEAM 29 //ask which team a bot is in +// +#define MSG_ME 100 +#define MSG_EVERYONE 101 +#define MSG_MULTIPLENAMES 102 +#define MSG_NAME 103 +#define MSG_PATROLKEYAREA 104 +#define MSG_MINUTES 105 +#define MSG_SECONDS 106 +#define MSG_FOREVER 107 +// +#define MSG_CHATALL 200 +#define MSG_CHATTEAM 201 +// +#define MSG_CTF 300 //ctf message + +//command sub types +#define ST_SOMEWHERE 0 +#define ST_NEARITEM 1 +#define ST_ADDRESSED 2 +#define ST_METER 4 +#define ST_FEET 8 +#define ST_TIME 16 +#define ST_HERE 32 +#define ST_THERE 64 +#define ST_I 128 +#define ST_MORE 256 +#define ST_BACK 512 +#define ST_REVERSE 1024 +#define ST_SOMEONE 2048 +#define ST_GOTFLAG 4096 +#define ST_CAPTUREDFLAG 8192 +#define ST_RETURNEDFLAG 16384 +#define ST_TEAM 32768 + + +//word replacement variables +#define THE_ENEMY 7 +#define THE_TEAM 7 +//team message variables +#define NETNAME 0 +#define PLACE 1 +#define FLAG 1 +#define MESSAGE 2 +#define ADDRESSEE 2 +#define ITEM 3 +#define TEAMMATE 4 +#define TEAMNAME 4 +#define ENEMY 4 +#define KEYAREA 5 +#define FORMATION 5 +#define POSITION 5 +#define NUMBER 5 +#define TIME 6 +#define NAME 6 +#define MORE 6 + + diff --git a/src/botai/syn.h b/src/botai/syn.h new file mode 100644 index 0000000..5a8f3ec --- /dev/null +++ b/src/botai/syn.h @@ -0,0 +1,46 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: syn.h +// Function: synonyms +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-09-08 +// Tab Size: 4 (real tabs) +// Notes: - +//=========================================================================== + +#define CONTEXT_ALL 0xFFFFFFFF +#define CONTEXT_NORMAL 1 +#define CONTEXT_NEARBYITEM 2 +#define CONTEXT_CTFREDTEAM 4 +#define CONTEXT_CTFBLUETEAM 8 +#define CONTEXT_REPLY 16 + +#define CONTEXT_NAMES 1024 diff --git a/src/botlib/aasfile.h b/src/botlib/aasfile.h new file mode 100644 index 0000000..6c51a95 --- /dev/null +++ b/src/botlib/aasfile.h @@ -0,0 +1,276 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +//NOTE: int = default signed +// default long + +#define AASID ( ( 'S' << 24 ) + ( 'A' << 16 ) + ( 'A' << 8 ) + 'E' ) +#define AASVERSION 8 + +//presence types +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 + +//travel types +#define MAX_TRAVELTYPES 32 +#define TRAVEL_INVALID 1 //temporary not possible +#define TRAVEL_WALK 2 //walking +#define TRAVEL_CROUCH 3 //crouching +#define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier +#define TRAVEL_JUMP 5 //jumping +#define TRAVEL_LADDER 6 //climbing a ladder +#define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge +#define TRAVEL_SWIM 8 //swimming +#define TRAVEL_WATERJUMP 9 //jump out of the water +#define TRAVEL_TELEPORT 10 //teleportation +#define TRAVEL_ELEVATOR 11 //travel by elevator +#define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel +#define TRAVEL_BFGJUMP 13 //bfg jumping required for travel +#define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel +#define TRAVEL_DOUBLEJUMP 15 //double jump +#define TRAVEL_RAMPJUMP 16 //ramp jump +#define TRAVEL_STRAFEJUMP 17 //strafe jump +#define TRAVEL_JUMPPAD 18 //jump pad +#define TRAVEL_FUNCBOB 19 //func bob + +//additional travel flags +#define TRAVELTYPE_MASK 0xFFFFFF +#define TRAVELFLAG_NOTTEAM1 ( 1 << 24 ) +#define TRAVELFLAG_NOTTEAM2 ( 2 << 24 ) + +//face flags +#define FACE_SOLID 1 //just solid at the other side +#define FACE_LADDER 2 //ladder +#define FACE_GROUND 4 //standing on ground when in this face +#define FACE_GAP 8 //gap in the ground +#define FACE_LIQUID 16 +#define FACE_LIQUIDSURFACE 32 + +//area contents +#define AREACONTENTS_WATER 1 +#define AREACONTENTS_LAVA 2 +#define AREACONTENTS_SLIME 4 +#define AREACONTENTS_CLUSTERPORTAL 8 +#define AREACONTENTS_TELEPORTAL 16 +#define AREACONTENTS_ROUTEPORTAL 32 +#define AREACONTENTS_TELEPORTER 64 +#define AREACONTENTS_JUMPPAD 128 +#define AREACONTENTS_DONOTENTER 256 +#define AREACONTENTS_VIEWPORTAL 512 +// Rafael - nopass +#define AREACONTENTS_DONOTENTER_LARGE 1024 +#define AREACONTENTS_MOVER 2048 + +//number of model of the mover inside this area +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM ( AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT ) + +//area flags +#define AREA_GROUNDED 1 //bot can stand on the ground +#define AREA_LADDER 2 //area contains one or more ladder faces +#define AREA_LIQUID 4 //area contains a liquid +// Ridah +#define AREA_DISABLED 8 +#define AREA_USEFORROUTING 1024 + +//aas file header lumps +#define AAS_LUMPS 14 +#define AASLUMP_BBOXES 0 +#define AASLUMP_VERTEXES 1 +#define AASLUMP_PLANES 2 +#define AASLUMP_EDGES 3 +#define AASLUMP_EDGEINDEX 4 +#define AASLUMP_FACES 5 +#define AASLUMP_FACEINDEX 6 +#define AASLUMP_AREAS 7 +#define AASLUMP_AREASETTINGS 8 +#define AASLUMP_REACHABILITY 9 +#define AASLUMP_NODES 10 +#define AASLUMP_PORTALS 11 +#define AASLUMP_PORTALINDEX 12 +#define AASLUMP_CLUSTERS 13 + +//========== bounding box ========= + +//bounding box +typedef struct aas_bbox_s +{ + int presencetype; + int flags; + vec3_t mins, maxs; +} aas_bbox_t; + +//============ settings =========== + +//reachability to another area +typedef struct aas_reachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime; //travel time of the inter area movement +} aas_reachability_t; + +//area settings +typedef struct aas_areasettings_s +{ + //could also add all kind of statistic fields + int contents; //contents of the convex area + int areaflags; //several area flags + int presencetype; //how a bot can be present in this convex area + int cluster; //cluster the area belongs to, if negative it's a portal + int clusterareanum; //number of the area in the cluster + int numreachableareas; //number of reachable areas from this one + int firstreachablearea; //first reachable area in the reachable area index + // Ridah, add a ground steepness stat, so we can avoid terrain when we can take a close-by flat route + float groundsteepness; // 0 = flat, 1 = steep +} aas_areasettings_t; + +//cluster portal +typedef struct aas_portal_s +{ + int areanum; //area that is the actual portal + int frontcluster; //cluster at front of portal + int backcluster; //cluster at back of portal + int clusterareanum[2]; //number of the area in the front and back cluster +} aas_portal_t; + +//cluster portal index +typedef int aas_portalindex_t; + +//cluster +typedef struct aas_cluster_s +{ + int numareas; //number of areas in the cluster + int numreachabilityareas; //number of areas with reachabilities + int numportals; //number of cluster portals + int firstportal; //first cluster portal in the index +} aas_cluster_t; + +//============ 3d definition ============ + +typedef vec3_t aas_vertex_t; + +//just a plane in the third dimension +typedef struct aas_plane_s +{ + vec3_t normal; //normal vector of the plane + float dist; //distance of the plane (normal vector * distance = point in plane) + int type; +} aas_plane_t; + +//edge +typedef struct aas_edge_s +{ + int v[2]; //numbers of the vertexes of this edge +} aas_edge_t; + +//edge index, negative if vertexes are reversed +typedef int aas_edgeindex_t; + +//a face bounds a convex area, often it will also seperate two convex areas +typedef struct aas_face_s +{ + int planenum; //number of the plane this face is in + int faceflags; //face flags (no use to create face settings for just this field) + int numedges; //number of edges in the boundary of the face + int firstedge; //first edge in the edge index + int frontarea; //convex area at the front of this face + int backarea; //convex area at the back of this face +} aas_face_t; + +//face index, stores a negative index if backside of face +typedef int aas_faceindex_t; + +//convex area with a boundary of faces +typedef struct aas_area_s +{ + int areanum; //number of this area + //3d definition + int numfaces; //number of faces used for the boundary of the convex area + int firstface; //first face in the face index used for the boundary of the convex area + vec3_t mins; //mins of the convex area + vec3_t maxs; //maxs of the convex area + vec3_t center; //'center' of the convex area +} aas_area_t; + +//nodes of the bsp tree +typedef struct aas_node_s +{ + int planenum; + int children[2]; //child nodes of this node, or convex areas as leaves when negative + //when a child is zero it's a solid leaf +} aas_node_t; + +//=========== aas file =============== + +//header lump +typedef struct +{ + int fileofs; + int filelen; +} aas_lump_t; + +//aas file header +typedef struct aas_header_s +{ + int ident; + int version; + int bspchecksum; + //data entries + aas_lump_t lumps[AAS_LUMPS]; +} aas_header_t; + + +//====== additional information ====== +/* + +- when a node child is a solid leaf the node child number is zero +- two adjacent areas (sharing a plane at opposite sides) share a face + this face is a portal between the areas +- when an area uses a face from the faceindex with a positive index + then the face plane normal points into the area +- the face edges are stored counter clockwise using the edgeindex +- two adjacent convex areas (sharing a face) only share One face + this is a simple result of the areas being convex +- the convex areas can't have a mixture of ground and gap faces + other mixtures of faces in one area are allowed +- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have + cluster number zero +- edge zero is a dummy +- face zero is a dummy +- area zero is a dummy +- node zero is a dummy +*/ diff --git a/src/botlib/be_aas_bsp.h b/src/botlib/be_aas_bsp.h new file mode 100644 index 0000000..33e44e3 --- /dev/null +++ b/src/botlib/be_aas_bsp.h @@ -0,0 +1,95 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_bsp.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +//loads the given BSP file +int AAS_LoadBSPFile( void ); +//dump the loaded BSP data +void AAS_DumpBSPData( void ); +//unlink the given entity from the bsp tree leaves +void AAS_UnlinkFromBSPLeaves( bsp_link_t *leaves ); +//link the given entity to the bsp tree leaves of the given model +bsp_link_t *AAS_BSPLinkEntity( vec3_t absmins, + vec3_t absmaxs, + int entnum, + int modelnum ); + +//calculates collision with given entity +qboolean AAS_EntityCollision( int entnum, + vec3_t start, + vec3_t boxmins, + vec3_t boxmaxs, + vec3_t end, + int contentmask, + bsp_trace_t *trace ); +//for debugging +void AAS_PrintFreeBSPLinks( char *str ); +// +#endif //AASINTERN + +#define MAX_EPAIRKEY 128 + +//trace through the world +bsp_trace_t AAS_Trace( vec3_t start, + vec3_t mins, + vec3_t maxs, + vec3_t end, + int passent, + int contentmask ); +//returns the contents at the given point +int AAS_PointContents( vec3_t point ); +//returns true when p2 is in the PVS of p1 +qboolean AAS_inPVS( vec3_t p1, vec3_t p2 ); +//returns true when p2 is in the PHS of p1 +qboolean AAS_inPHS( vec3_t p1, vec3_t p2 ); +//returns true if the given areas are connected +qboolean AAS_AreasConnected( int area1, int area2 ); +//creates a list with entities totally or partly within the given box +int AAS_BoxEntities( vec3_t absmins, vec3_t absmaxs, int *list, int maxcount ); +//gets the mins, maxs and origin of a BSP model +void AAS_BSPModelMinsMaxsOrigin( int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin ); +//handle to the next bsp entity +int AAS_NextBSPEntity( int ent ); +//return the value of the BSP epair key +int AAS_ValueForBSPEpairKey( int ent, char *key, char *value, int size ); +//get a vector for the BSP epair key +int AAS_VectorForBSPEpairKey( int ent, char *key, vec3_t v ); +//get a float for the BSP epair key +int AAS_FloatForBSPEpairKey( int ent, char *key, float *value ); +//get an integer for the BSP epair key +int AAS_IntForBSPEpairKey( int ent, char *key, int *value ); + diff --git a/src/botlib/be_aas_bspq3.c b/src/botlib/be_aas_bspq3.c new file mode 100644 index 0000000..a6fc5cc --- /dev/null +++ b/src/botlib/be_aas_bspq3.c @@ -0,0 +1,525 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_bspq3.c + * + * desc: BSP, Environment Sampling + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +//#define TRACE_DEBUG + +#define ON_EPSILON 0.005 +//#define DEG2RAD( a ) (( a * M_PI ) / 180.0F) + +#define MAX_BSPENTITIES 2048 + +typedef struct rgb_s +{ + int red; + int green; + int blue; +} rgb_t; + +//bsp entity epair +typedef struct bsp_epair_s +{ + char *key; + char *value; + struct bsp_epair_s *next; +} bsp_epair_t; + +//bsp data entity +typedef struct bsp_entity_s +{ + bsp_epair_t *epairs; +} bsp_entity_t; + +//id Sofware BSP data +typedef struct bsp_s +{ + //true when bsp file is loaded + int loaded; + //entity data + int entdatasize; + char *dentdata; + //bsp entities + int numentities; + bsp_entity_t entities[MAX_BSPENTITIES]; + //memory used for strings and epairs + byte *ebuffer; +} bsp_t; + +//global bsp +bsp_t bspworld; + + +#ifdef BSP_DEBUG +typedef struct cname_s +{ + int value; + char *name; +} cname_t; + +cname_t contentnames[] = +{ + {CONTENTS_SOLID,"CONTENTS_SOLID"}, + {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, + {CONTENTS_AUX,"CONTENTS_AUX"}, + {CONTENTS_LAVA,"CONTENTS_LAVA"}, + {CONTENTS_SLIME,"CONTENTS_SLIME"}, + {CONTENTS_WATER,"CONTENTS_WATER"}, + {CONTENTS_MIST,"CONTENTS_MIST"}, + {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, + + {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, + {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, + {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, + {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, + {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, + {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, + {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, + {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, + {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, + {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, + {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, + {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, + {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, + {CONTENTS_TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, + {CONTENTS_LADDER,"CONTENTS_LADDER"}, + {0, 0} +}; + +void PrintContents( int contents ) { + int i; + + for ( i = 0; contentnames[i].value; i++ ) + { + if ( contents & contentnames[i].value ) { + botimport.Print( PRT_MESSAGE, "%s\n", contentnames[i].name ); + } //end if + } //end for +} //end of the function PrintContents + +#endif BSP_DEBUG +//=========================================================================== +// traces axial boxes of any size through the world +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_trace_t AAS_Trace( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask ) { + bsp_trace_t bsptrace; + botimport.Trace( &bsptrace, start, mins, maxs, end, passent, contentmask ); + return bsptrace; +} //end of the function AAS_Trace +//=========================================================================== +// returns the contents at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointContents( vec3_t point ) { + return botimport.PointContents( point ); +} //end of the function AAS_PointContents +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_EntityCollision( int entnum, + vec3_t start, vec3_t boxmins, vec3_t boxmaxs, vec3_t end, + int contentmask, bsp_trace_t *trace ) { + bsp_trace_t enttrace; + + botimport.EntityTrace( &enttrace, start, boxmins, boxmaxs, end, entnum, contentmask ); + if ( enttrace.fraction < trace->fraction ) { + memcpy( trace, &enttrace, sizeof( bsp_trace_t ) ); + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_EntityCollision +//=========================================================================== +// returns true if in Potentially Hearable Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPVS( vec3_t p1, vec3_t p2 ) { + return botimport.inPVS( p1, p2 ); +} //end of the function AAS_InPVS +//=========================================================================== +// returns true if in Potentially Visible Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPHS( vec3_t p1, vec3_t p2 ) { + return qtrue; +} //end of the function AAS_inPHS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_BSPModelMinsMaxsOrigin( int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin ) { + botimport.BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, origin ); +} //end of the function AAS_BSPModelMinsMaxs +//=========================================================================== +// unlinks the entity from all leaves +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromBSPLeaves( bsp_link_t *leaves ) { +} //end of the function AAS_UnlinkFromBSPLeaves +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_link_t *AAS_BSPLinkEntity( vec3_t absmins, vec3_t absmaxs, int entnum, int modelnum ) { + return NULL; +} //end of the function AAS_BSPLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxEntities( vec3_t absmins, vec3_t absmaxs, int *list, int maxcount ) { + return 0; +} //end of the function AAS_BoxEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextBSPEntity( int ent ) { + ent++; + if ( ent >= 1 && ent < bspworld.numentities ) { + return ent; + } + return 0; +} //end of the function AAS_NextBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPEntityInRange( int ent ) { + if ( ent <= 0 || ent >= bspworld.numentities ) { + botimport.Print( PRT_MESSAGE, "bsp entity out of range\n" ); + return qfalse; + } //end if + return qtrue; +} //end of the function AAS_BSPEntityInRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ValueForBSPEpairKey( int ent, char *key, char *value, int size ) { + bsp_epair_t *epair; + + value[0] = '\0'; + if ( !AAS_BSPEntityInRange( ent ) ) { + return qfalse; + } + for ( epair = bspworld.entities[ent].epairs; epair; epair = epair->next ) + { + if ( !strcmp( epair->key, key ) ) { + strncpy( value, epair->value, size - 1 ); + value[size - 1] = '\0'; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_FindBSPEpair +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_VectorForBSPEpairKey( int ent, char *key, vec3_t v ) { + char buf[MAX_EPAIRKEY]; + double v1, v2, v3; + + VectorClear( v ); + if ( !AAS_ValueForBSPEpairKey( ent, key, buf, MAX_EPAIRKEY ) ) { + return qfalse; + } + //scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf( buf, "%lf %lf %lf", &v1, &v2, &v3 ); + v[0] = v1; + v[1] = v2; + v[2] = v3; + return qtrue; +} //end of the function AAS_VectorForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloatForBSPEpairKey( int ent, char *key, float *value ) { + char buf[MAX_EPAIRKEY]; + + *value = 0; + if ( !AAS_ValueForBSPEpairKey( ent, key, buf, MAX_EPAIRKEY ) ) { + return qfalse; + } + *value = atof( buf ); + return qtrue; +} //end of the function AAS_FloatForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IntForBSPEpairKey( int ent, char *key, int *value ) { + char buf[MAX_EPAIRKEY]; + + *value = 0; + if ( !AAS_ValueForBSPEpairKey( ent, key, buf, MAX_EPAIRKEY ) ) { + return qfalse; + } + *value = atoi( buf ); + return qtrue; +} //end of the function AAS_IntForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeBSPEntities( void ) { +// RF, optimized memory allocation +/* + int i; + bsp_entity_t *ent; + bsp_epair_t *epair, *nextepair; + + for (i = 1; i < bspworld.numentities; i++) + { + ent = &bspworld.entities[i]; + for (epair = ent->epairs; epair; epair = nextepair) + { + nextepair = epair->next; + // + if (epair->key) FreeMemory(epair->key); + if (epair->value) FreeMemory(epair->value); + FreeMemory(epair); + } //end for + } //end for +*/ + if ( bspworld.ebuffer ) { + FreeMemory( bspworld.ebuffer ); + } + bspworld.numentities = 0; +} //end of the function AAS_FreeBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ParseBSPEntities( void ) { + script_t *script; + token_t token; + bsp_entity_t *ent; + bsp_epair_t *epair; + byte *buffer, *buftrav; + int bufsize; + + // RF, modified this, so that it first gathers up memory requirements, then allocates a single chunk, + // and places the strings all in there + + bspworld.ebuffer = NULL; + + script = LoadScriptMemory( bspworld.dentdata, bspworld.entdatasize, "entdata" ); + SetScriptFlags( script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS ); //SCFL_PRIMITIVE); + + bufsize = 0; + + while ( PS_ReadToken( script, &token ) ) + { + if ( strcmp( token.string, "{" ) ) { + ScriptError( script, "invalid %s\n", token.string ); + AAS_FreeBSPEntities(); + FreeScript( script ); + return; + } //end if + if ( bspworld.numentities >= MAX_BSPENTITIES ) { + botimport.Print( PRT_MESSAGE, "too many entities in BSP file\n" ); + break; + } //end if + while ( PS_ReadToken( script, &token ) ) + { + if ( !strcmp( token.string, "}" ) ) { + break; + } + bufsize += sizeof( bsp_epair_t ); + if ( token.type != TT_STRING ) { + ScriptError( script, "invalid %s\n", token.string ); + AAS_FreeBSPEntities(); + FreeScript( script ); + return; + } //end if + StripDoubleQuotes( token.string ); + bufsize += strlen( token.string ) + 1; + if ( !PS_ExpectTokenType( script, TT_STRING, 0, &token ) ) { + AAS_FreeBSPEntities(); + FreeScript( script ); + return; + } //end if + StripDoubleQuotes( token.string ); + bufsize += strlen( token.string ) + 1; + } //end while + if ( strcmp( token.string, "}" ) ) { + ScriptError( script, "missing }\n" ); + AAS_FreeBSPEntities(); + FreeScript( script ); + return; + } //end if + } //end while + FreeScript( script ); + + buffer = (byte *)GetClearedHunkMemory( bufsize ); + buftrav = buffer; + bspworld.ebuffer = buffer; + + // RF, now parse the entities into memory + // RF, NOTE: removed error checks for speed, no need to do them twice + + script = LoadScriptMemory( bspworld.dentdata, bspworld.entdatasize, "entdata" ); + SetScriptFlags( script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS ); //SCFL_PRIMITIVE); + + bspworld.numentities = 1; + + while ( PS_ReadToken( script, &token ) ) + { + ent = &bspworld.entities[bspworld.numentities]; + bspworld.numentities++; + ent->epairs = NULL; + while ( PS_ReadToken( script, &token ) ) + { + if ( !strcmp( token.string, "}" ) ) { + break; + } + epair = (bsp_epair_t *) buftrav; buftrav += sizeof( bsp_epair_t ); + epair->next = ent->epairs; + ent->epairs = epair; + StripDoubleQuotes( token.string ); + epair->key = (char *) buftrav; buftrav += ( strlen( token.string ) + 1 ); + strcpy( epair->key, token.string ); + if ( !PS_ExpectTokenType( script, TT_STRING, 0, &token ) ) { + AAS_FreeBSPEntities(); + FreeScript( script ); + return; + } //end if + StripDoubleQuotes( token.string ); + epair->value = (char *) buftrav; buftrav += ( strlen( token.string ) + 1 ); + strcpy( epair->value, token.string ); + } //end while + } //end while + FreeScript( script ); +} //end of the function AAS_ParseBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPTraceLight( vec3_t start, vec3_t end, vec3_t endpos, int *red, int *green, int *blue ) { + return 0; +} //end of the function AAS_BSPTraceLight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpBSPData( void ) { + AAS_FreeBSPEntities(); + + if ( bspworld.dentdata ) { + FreeMemory( bspworld.dentdata ); + } + bspworld.dentdata = NULL; + bspworld.entdatasize = 0; + // + bspworld.loaded = qfalse; + memset( &bspworld, 0, sizeof( bspworld ) ); +} //end of the function AAS_DumpBSPData +//=========================================================================== +// load an bsp file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadBSPFile( void ) { + AAS_DumpBSPData(); + bspworld.entdatasize = strlen( botimport.BSPEntityData() ) + 1; + bspworld.dentdata = (char *) GetClearedHunkMemory( bspworld.entdatasize ); + memcpy( bspworld.dentdata, botimport.BSPEntityData(), bspworld.entdatasize ); + AAS_ParseBSPEntities(); + bspworld.loaded = qtrue; + return BLERR_NOERROR; +} //end of the function AAS_LoadBSPFile diff --git a/src/botlib/be_aas_cluster.c b/src/botlib/be_aas_cluster.c new file mode 100644 index 0000000..562f89a --- /dev/null +++ b/src/botlib/be_aas_cluster.c @@ -0,0 +1,1631 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_cluster.c + * + * desc: area clustering + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +#define AAS_MAX_PORTALS 65536 +#define AAS_MAX_PORTALINDEXSIZE 65536 +#define AAS_MAX_CLUSTERS 65536 +// +#define MAX_PORTALAREAS 1024 + +// do not flood through area faces, only use reachabilities +int nofaceflood = qtrue; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveClusterAreas( void ) { + int i; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + ( *aasworld ).areasettings[i].cluster = 0; + } //end for +} //end of the function AAS_RemoveClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearCluster( int clusternum ) { + int i; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( ( *aasworld ).areasettings[i].cluster == clusternum ) { + ( *aasworld ).areasettings[i].cluster = 0; + } //end if + } //end for +} //end of the function AAS_ClearCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemovePortalsClusterReference( int clusternum ) { + int portalnum; + + for ( portalnum = 1; portalnum < ( *aasworld ).numportals; portalnum++ ) + { + if ( ( *aasworld ).portals[portalnum].frontcluster == clusternum ) { + ( *aasworld ).portals[portalnum].frontcluster = 0; + } //end if + if ( ( *aasworld ).portals[portalnum].backcluster == clusternum ) { + ( *aasworld ).portals[portalnum].backcluster = 0; + } //end if + } //end for +} //end of the function AAS_RemovePortalsClusterReference +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdatePortal( int areanum, int clusternum ) { + int portalnum; + aas_portal_t *portal; + aas_cluster_t *cluster; + + //find the portal of the area + for ( portalnum = 1; portalnum < ( *aasworld ).numportals; portalnum++ ) + { + if ( ( *aasworld ).portals[portalnum].areanum == areanum ) { + break; + } + } //end for + // + if ( portalnum == ( *aasworld ).numportals ) { + AAS_Error( "no portal of area %d", areanum ); + return qtrue; + } //end if + // + portal = &( *aasworld ).portals[portalnum]; + //if the portal is already fully updated + if ( portal->frontcluster == clusternum ) { + return qtrue; + } + if ( portal->backcluster == clusternum ) { + return qtrue; + } + //if the portal has no front cluster yet + if ( !portal->frontcluster ) { + portal->frontcluster = clusternum; + } //end if + //if the portal has no back cluster yet + else if ( !portal->backcluster ) { + portal->backcluster = clusternum; + } //end else if + else + { + Log_Write( "portal using area %d is seperating more than two clusters\r\n", areanum ); + //remove the cluster portal flag contents + ( *aasworld ).areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + return qfalse; + } //end else + if ( ( *aasworld ).portalindexsize >= AAS_MAX_PORTALINDEXSIZE ) { + AAS_Error( "AAS_MAX_PORTALINDEXSIZE" ); + return qtrue; + } //end if + //set the area cluster number to the negative portal number + ( *aasworld ).areasettings[areanum].cluster = -portalnum; + //add the portal to the cluster using the portal index + cluster = &( *aasworld ).clusters[clusternum]; + ( *aasworld ).portalindex[cluster->firstportal + cluster->numportals] = portalnum; + ( *aasworld ).portalindexsize++; + cluster->numportals++; + return qtrue; +} //end of the function AAS_UpdatePortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreas_r( int areanum, int clusternum ) { + aas_area_t *area; + aas_face_t *face; + int facenum, i; + + // + if ( areanum <= 0 || areanum >= ( *aasworld ).numareas ) { + AAS_Error( "AAS_FloodClusterAreas_r: areanum out of range" ); + return qfalse; + } //end if + //if the area is already part of a cluster + if ( ( *aasworld ).areasettings[areanum].cluster > 0 ) { + if ( ( *aasworld ).areasettings[areanum].cluster == clusternum ) { + return qtrue; + } + // + //there's a reachability going from one cluster to another only in one direction + // + AAS_Error( "cluster %d touched cluster %d at area %d\r\n", + clusternum, ( *aasworld ).areasettings[areanum].cluster, areanum ); + return qfalse; + } //end if + //don't add the cluster portal areas to the clusters + if ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + return AAS_UpdatePortal( areanum, clusternum ); + } //end if + //set the area cluster number + ( *aasworld ).areasettings[areanum].cluster = clusternum; + ( *aasworld ).areasettings[areanum].clusterareanum = + ( *aasworld ).clusters[clusternum].numareas; + //the cluster has an extra area + ( *aasworld ).clusters[clusternum].numareas++; + + area = &( *aasworld ).areas[areanum]; + //use area faces to flood into adjacent areas + if ( !nofaceflood ) { + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + i] ); + face = &( *aasworld ).faces[facenum]; + if ( face->frontarea == areanum ) { + if ( face->backarea ) { + if ( !AAS_FloodClusterAreas_r( face->backarea, clusternum ) ) { + return qfalse; + } + } + } //end if + else + { + if ( face->frontarea ) { + if ( !AAS_FloodClusterAreas_r( face->frontarea, clusternum ) ) { + return qfalse; + } + } + } //end else + } //end for + } + //use the reachabilities to flood into other areas + for ( i = 0; i < ( *aasworld ).areasettings[areanum].numreachableareas; i++ ) + { + if ( !( *aasworld ).reachability[ + ( *aasworld ).areasettings[areanum].firstreachablearea + i].areanum ) { + continue; + } //end if + if ( !AAS_FloodClusterAreas_r( ( *aasworld ).reachability[ + ( *aasworld ).areasettings[areanum].firstreachablearea + i].areanum, clusternum ) ) { + return qfalse; + } + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreas_r +//=========================================================================== +// try to flood from all areas without cluster into areas with a cluster set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreasUsingReachabilities( int clusternum ) { + int i, j, areanum; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + //if this area already has a cluster set + if ( ( *aasworld ).areasettings[i].cluster ) { + continue; + } + //if this area is a cluster portal + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + //loop over the reachable areas from this area + for ( j = 0; j < ( *aasworld ).areasettings[i].numreachableareas; j++ ) + { + //the reachable area + areanum = ( *aasworld ).reachability[( *aasworld ).areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + //if this area has a cluster set + if ( ( *aasworld ).areasettings[areanum].cluster ) { + if ( !AAS_FloodClusterAreas_r( i, clusternum ) ) { + return qfalse; + } + i = 0; + break; + } //end if + } //end for + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreasUsingReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterPortals( int clusternum ) { + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + cluster = &( *aasworld ).clusters[clusternum]; + for ( i = 0; i < cluster->numportals; i++ ) + { + portalnum = ( *aasworld ).portalindex[cluster->firstportal + i]; + portal = &( *aasworld ).portals[portalnum]; + if ( portal->frontcluster == clusternum ) { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterAreas( int clusternum ) { + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + ( *aasworld ).clusters[clusternum].numareas = 0; + ( *aasworld ).clusters[clusternum].numreachabilityareas = 0; + //number all areas in this cluster WITH reachabilities + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + // + if ( ( *aasworld ).areasettings[i].cluster != clusternum ) { + continue; + } + // + if ( !AAS_AreaReachability( i ) ) { + continue; + } + // + ( *aasworld ).areasettings[i].clusterareanum = ( *aasworld ).clusters[clusternum].numareas; + //the cluster has an extra area + ( *aasworld ).clusters[clusternum].numareas++; + ( *aasworld ).clusters[clusternum].numreachabilityareas++; + } //end for + //number all portals in this cluster WITH reachabilities + cluster = &( *aasworld ).clusters[clusternum]; + for ( i = 0; i < cluster->numportals; i++ ) + { + portalnum = ( *aasworld ).portalindex[cluster->firstportal + i]; + portal = &( *aasworld ).portals[portalnum]; + if ( !AAS_AreaReachability( portal->areanum ) ) { + continue; + } + if ( portal->frontcluster == clusternum ) { + portal->clusterareanum[0] = cluster->numareas++; + ( *aasworld ).clusters[clusternum].numreachabilityareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + ( *aasworld ).clusters[clusternum].numreachabilityareas++; + } //end else + } //end for + //number all areas in this cluster WITHOUT reachabilities + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + // + if ( ( *aasworld ).areasettings[i].cluster != clusternum ) { + continue; + } + // + if ( AAS_AreaReachability( i ) ) { + continue; + } + // + ( *aasworld ).areasettings[i].clusterareanum = ( *aasworld ).clusters[clusternum].numareas; + //the cluster has an extra area + ( *aasworld ).clusters[clusternum].numareas++; + } //end for + //number all portals in this cluster WITHOUT reachabilities + cluster = &( *aasworld ).clusters[clusternum]; + for ( i = 0; i < cluster->numportals; i++ ) + { + portalnum = ( *aasworld ).portalindex[cluster->firstportal + i]; + portal = &( *aasworld ).portals[portalnum]; + if ( AAS_AreaReachability( portal->areanum ) ) { + continue; + } + if ( portal->frontcluster == clusternum ) { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindClusters( void ) { + int i; + aas_cluster_t *cluster; + + AAS_RemoveClusterAreas(); + // + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + //if the area is already part of a cluster + if ( ( *aasworld ).areasettings[i].cluster ) { + continue; + } + // if not flooding through faces only use areas that have reachabilities + if ( nofaceflood ) { + if ( !( *aasworld ).areasettings[i].numreachableareas ) { + continue; + } + } //end if + //if the area is a cluster portal + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + if ( ( *aasworld ).numclusters >= AAS_MAX_CLUSTERS ) { + AAS_Error( "AAS_MAX_CLUSTERS" ); + return qfalse; + } //end if + cluster = &( *aasworld ).clusters[( *aasworld ).numclusters]; + cluster->numareas = 0; + cluster->numreachabilityareas = 0; + cluster->firstportal = ( *aasworld ).portalindexsize; + cluster->numportals = 0; + //flood the areas in this cluster + if ( !AAS_FloodClusterAreas_r( i, ( *aasworld ).numclusters ) ) { + return qfalse; + } + if ( !AAS_FloodClusterAreasUsingReachabilities( ( *aasworld ).numclusters ) ) { + return qfalse; + } + //number the cluster areas + //AAS_NumberClusterPortals((*aasworld).numclusters); + AAS_NumberClusterAreas( ( *aasworld ).numclusters ); + //Log_Write("cluster %d has %d areas\r\n", (*aasworld).numclusters, cluster->numareas); + ( *aasworld ).numclusters++; + } //end for + return qtrue; +} //end of the function AAS_FindClusters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreatePortals( void ) { + int i; + aas_portal_t *portal; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + //if the area is a cluster portal + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL ) { + if ( ( *aasworld ).numportals >= AAS_MAX_PORTALS ) { + AAS_Error( "AAS_MAX_PORTALS" ); + return; + } //end if + portal = &( *aasworld ).portals[( *aasworld ).numportals]; + portal->areanum = i; + portal->frontcluster = 0; + portal->backcluster = 0; + Log_Write( "portal %d: area %d\r\n", ( *aasworld ).numportals, portal->areanum ); + ( *aasworld ).numportals++; + } //end if + } //end for +} //end of the function AAS_CreatePortals +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MapContainsTeleporters(void) +{ + bsp_entity_t *entities, *ent; + char *classname; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + AAS_FreeBSPEntities(entities); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_MapContainsTeleporters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NonConvexFaces(aas_face_t *face1, aas_face_t *face2, int side1, int side2) +{ + int i, j, edgenum; + aas_plane_t *plane1, *plane2; + aas_edge_t *edge; + + + plane1 = &(*aasworld).planes[face1->planenum ^ side1]; + plane2 = &(*aasworld).planes[face2->planenum ^ side2]; + + //check if one of the points of face1 is at the back of the plane of face2 + for (i = 0; i < face1->numedges; i++) + { + edgenum = abs((*aasworld).edgeindex[face1->firstedge + i]); + edge = &(*aasworld).edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane2->normal, (*aasworld).vertexes[edge->v[j]]) - + plane2->dist < -0.01) return qtrue; + } //end for + } //end for + for (i = 0; i < face2->numedges; i++) + { + edgenum = abs((*aasworld).edgeindex[face2->firstedge + i]); + edge = &(*aasworld).edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane1->normal, (*aasworld).vertexes[edge->v[j]]) - + plane1->dist < -0.01) return qtrue; + } //end for + } //end for + + return qfalse; +} //end of the function AAS_NonConvexFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeAreas(int *areanums, int numareas) +{ + int i, j, s, face1num, face2num, side1, side2, fn1, fn2; + aas_face_t *face1, *face2; + aas_area_t *area1, *area2; + + for (i = 0; i < numareas; i++) + { + area1 = &(*aasworld).areas[areanums[i]]; + for (fn1 = 0; fn1 < area1->numfaces; fn1++) + { + face1num = abs((*aasworld).faceindex[area1->firstface + fn1]); + face1 = &(*aasworld).faces[face1num]; + side1 = face1->frontarea != areanums[i]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == i) continue; + if (face1->frontarea == s || face1->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + for (j = 0; j < numareas; j++) + { + if (j == i) continue; + area2 = &(*aasworld).areas[areanums[j]]; + for (fn2 = 0; fn2 < area2->numfaces; fn2++) + { + face2num = abs((*aasworld).faceindex[area2->firstface + fn2]); + face2 = &(*aasworld).faces[face2num]; + side2 = face2->frontarea != areanums[j]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == j) continue; + if (face2->frontarea == s || face2->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + if (AAS_NonConvexFaces(face1, face2, side1, side2)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_NonConvexEdges(aas_edge_t *edge1, aas_edge_t *edge2, int side1, int side2, int planenum) +{ + int i; + vec3_t edgevec1, edgevec2, normal1, normal2; + float dist1, dist2; + aas_plane_t *plane; + + plane = &(*aasworld).planes[planenum]; + VectorSubtract((*aasworld).vertexes[edge1->v[1]], (*aasworld).vertexes[edge1->v[0]], edgevec1); + VectorSubtract((*aasworld).vertexes[edge2->v[1]], (*aasworld).vertexes[edge2->v[0]], edgevec2); + if (side1) VectorInverse(edgevec1); + if (side2) VectorInverse(edgevec2); + // + CrossProduct(edgevec1, plane->normal, normal1); + dist1 = DotProduct(normal1, (*aasworld).vertexes[edge1->v[0]]); + CrossProduct(edgevec2, plane->normal, normal2); + dist2 = DotProduct(normal2, (*aasworld).vertexes[edge2->v[0]]); + + for (i = 0; i < 2; i++) + { + if (DotProduct((*aasworld).vertexes[edge1->v[i]], normal2) - dist2 < -0.01) return qfalse; + } //end for + for (i = 0; i < 2; i++) + { + if (DotProduct((*aasworld).vertexes[edge2->v[i]], normal1) - dist1 < -0.01) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_NonConvexEdges +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeFaces(int *facenums, int numfaces, int planenum) +{ + int i, j, s, edgenum1, edgenum2, side1, side2, en1, en2, ens; + aas_face_t *face1, *face2, *otherface; + aas_edge_t *edge1, *edge2; + + for (i = 0; i < numfaces; i++) + { + face1 = &(*aasworld).faces[facenums[i]]; + for (en1 = 0; en1 < face1->numedges; en1++) + { + edgenum1 = (*aasworld).edgeindex[face1->firstedge + en1]; + side1 = (edgenum1 < 0) ^ (face1->planenum != planenum); + edgenum1 = abs(edgenum1); + edge1 = &(*aasworld).edges[edgenum1]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &(*aasworld).faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum1 == abs((*aasworld).edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + for (j = 0; j < numfaces; j++) + { + if (j == i) continue; + face2 = &(*aasworld).faces[facenums[j]]; + for (en2 = 0; en2 < face2->numedges; en2++) + { + edgenum2 = (*aasworld).edgeindex[face2->firstedge + en2]; + side2 = (edgenum2 < 0) ^ (face2->planenum != planenum); + edgenum2 = abs(edgenum2); + edge2 = &(*aasworld).edges[edgenum2]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &(*aasworld).faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum2 == abs((*aasworld).edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + if (AAS_NonConvexEdges(edge1, edge2, side1, side2, planenum)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeFaces*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ConnectedAreas_r( int *areanums, int numareas, int *connectedareas, int curarea ) { + int i, j, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + connectedareas[curarea] = qtrue; + area = &( *aasworld ).areas[areanums[curarea]]; + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + i] ); + face = &( *aasworld ).faces[facenum]; + //if the face is solid + if ( face->faceflags & FACE_SOLID ) { + continue; + } + //get the area at the other side of the face + if ( face->frontarea != areanums[curarea] ) { + otherareanum = face->frontarea; + } else { otherareanum = face->backarea;} + //check if the face is leading to one of the other areas + for ( j = 0; j < numareas; j++ ) + { + if ( areanums[j] == otherareanum ) { + break; + } + } //end for + //if the face isn't leading to one of the other areas + if ( j == numareas ) { + continue; + } + //if the other area is already connected + if ( connectedareas[j] ) { + continue; + } + //recursively proceed with the other area + AAS_ConnectedAreas_r( areanums, numareas, connectedareas, j ); + } //end for +} //end of the function AAS_ConnectedAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ConnectedAreas( int *areanums, int numareas ) { + int connectedareas[MAX_PORTALAREAS], i; + + memset( connectedareas, 0, sizeof( connectedareas ) ); + if ( numareas < 1 ) { + return qfalse; + } + if ( numareas == 1 ) { + return qtrue; + } + AAS_ConnectedAreas_r( areanums, numareas, connectedareas, 0 ); + for ( i = 0; i < numareas; i++ ) + { + if ( !connectedareas[i] ) { + return qfalse; + } + } //end for + return qtrue; +} //end of the function AAS_ConnectedAreas +//=========================================================================== +// gets adjacent areas with less presence types recursively +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetAdjacentAreasWithLessPresenceTypes_r( int *areanums, int numareas, int curareanum ) { + int i, j, presencetype, otherpresencetype, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + areanums[numareas++] = curareanum; + area = &( *aasworld ).areas[curareanum]; + presencetype = ( *aasworld ).areasettings[curareanum].presencetype; + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + i] ); + face = &( *aasworld ).faces[facenum]; + //if the face is solid + if ( face->faceflags & FACE_SOLID ) { + continue; + } + //the area at the other side of the face + if ( face->frontarea != curareanum ) { + otherareanum = face->frontarea; + } else { otherareanum = face->backarea;} + // + otherpresencetype = ( *aasworld ).areasettings[otherareanum].presencetype; + //if the other area has less presence types + if ( ( presencetype & ~otherpresencetype ) && + !( otherpresencetype & ~presencetype ) ) { + //check if the other area isn't already in the list + for ( j = 0; j < numareas; j++ ) + { + if ( otherareanum == areanums[j] ) { + break; + } + } //end for + //if the other area isn't already in the list + if ( j == numareas ) { + if ( numareas >= MAX_PORTALAREAS ) { + AAS_Error( "MAX_PORTALAREAS" ); + return numareas; + } //end if + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r( areanums, numareas, otherareanum ); + } //end if + } //end if + } //end for + return numareas; +} //end of the function AAS_GetAdjacentAreasWithLessPresenceTypes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CheckAreaForPossiblePortals( int areanum ) { + int i, j, k, fen, ben, frontedgenum, backedgenum, facenum; + int areanums[MAX_PORTALAREAS], numareas, otherareanum; + int numareafrontfaces[MAX_PORTALAREAS], numareabackfaces[MAX_PORTALAREAS]; + int frontfacenums[MAX_PORTALAREAS], backfacenums[MAX_PORTALAREAS]; + int numfrontfaces, numbackfaces; + int frontareanums[MAX_PORTALAREAS], backareanums[MAX_PORTALAREAS]; + int numfrontareas, numbackareas; + int frontplanenum, backplanenum, faceplanenum; + aas_area_t *area; + aas_face_t *frontface, *backface, *face; + + //if it isn't already a portal + if ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + return 0; + } + //it must be a grounded area + if ( !( ( *aasworld ).areasettings[areanum].areaflags & AREA_GROUNDED ) ) { + return 0; + } + // + memset( numareafrontfaces, 0, sizeof( numareafrontfaces ) ); + memset( numareabackfaces, 0, sizeof( numareabackfaces ) ); + numareas = numfrontfaces = numbackfaces = 0; + numfrontareas = numbackareas = 0; + frontplanenum = backplanenum = -1; + //add any adjacent areas with less presence types + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r( areanums, 0, areanum ); + // + for ( i = 0; i < numareas; i++ ) + { + area = &( *aasworld ).areas[areanums[i]]; + for ( j = 0; j < area->numfaces; j++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + j] ); + face = &( *aasworld ).faces[facenum]; + //if the face is solid + if ( face->faceflags & FACE_SOLID ) { + continue; + } + //check if the face is shared with one of the other areas + for ( k = 0; k < numareas; k++ ) + { + if ( k == i ) { + continue; + } + if ( face->frontarea == areanums[k] || face->backarea == areanums[k] ) { + break; + } + } //end for + //if the face is shared + if ( k != numareas ) { + continue; + } + //the number of the area at the other side of the face + if ( face->frontarea == areanums[i] ) { + otherareanum = face->backarea; + } else { otherareanum = face->frontarea;} + //if the other area already is a cluter portal + if ( ( *aasworld ).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + return 0; + } + //number of the plane of the area + faceplanenum = face->planenum & ~1; + // + if ( frontplanenum < 0 || faceplanenum == frontplanenum ) { + frontplanenum = faceplanenum; + frontfacenums[numfrontfaces++] = facenum; + for ( k = 0; k < numfrontareas; k++ ) + { + if ( frontareanums[k] == otherareanum ) { + break; + } + } //end for + if ( k == numfrontareas ) { + frontareanums[numfrontareas++] = otherareanum; + } + numareafrontfaces[i]++; + } //end if + else if ( backplanenum < 0 || faceplanenum == backplanenum ) { + backplanenum = faceplanenum; + backfacenums[numbackfaces++] = facenum; + for ( k = 0; k < numbackareas; k++ ) + { + if ( backareanums[k] == otherareanum ) { + break; + } + } //end for + if ( k == numbackareas ) { + backareanums[numbackareas++] = otherareanum; + } + numareabackfaces[i]++; + } //end else + else + { + return 0; + } //end else + } //end for + } //end for + //every area should have at least one front face and one back face + for ( i = 0; i < numareas; i++ ) + { + if ( !numareafrontfaces[i] || !numareabackfaces[i] ) { + return 0; + } + } //end for + //the front areas should all be connected + if ( !AAS_ConnectedAreas( frontareanums, numfrontareas ) ) { + return 0; + } + //the back areas should all be connected + if ( !AAS_ConnectedAreas( backareanums, numbackareas ) ) { + return 0; + } + //none of the front faces should have a shared edge with a back face + for ( i = 0; i < numfrontfaces; i++ ) + { + frontface = &( *aasworld ).faces[frontfacenums[i]]; + for ( fen = 0; fen < frontface->numedges; fen++ ) + { + frontedgenum = abs( ( *aasworld ).edgeindex[frontface->firstedge + fen] ); + for ( j = 0; j < numbackfaces; j++ ) + { + backface = &( *aasworld ).faces[backfacenums[j]]; + for ( ben = 0; ben < backface->numedges; ben++ ) + { + backedgenum = abs( ( *aasworld ).edgeindex[backface->firstedge + ben] ); + if ( frontedgenum == backedgenum ) { + break; + } + } //end for + if ( ben != backface->numedges ) { + break; + } + } //end for + if ( j != numbackfaces ) { + break; + } + } //end for + if ( fen != frontface->numedges ) { + break; + } + } //end for + if ( i != numfrontfaces ) { + return 0; + } + //set the cluster portal contents + for ( i = 0; i < numareas; i++ ) + { + ( *aasworld ).areasettings[areanums[i]].contents |= AREACONTENTS_CLUSTERPORTAL; + //this area can be used as a route portal + ( *aasworld ).areasettings[areanums[i]].contents |= AREACONTENTS_ROUTEPORTAL; + Log_Write( "possible portal: %d\r\n", areanums[i] ); + } //end for + // + return numareas; +} //end of the function AAS_CheckAreaForPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FindPossiblePortals( void ) { + int i, numpossibleportals; + + numpossibleportals = 0; + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + numpossibleportals += AAS_CheckAreaForPossiblePortals( i ); + } //end for + botimport.Print( PRT_MESSAGE, "\r%6d possible portals\n", numpossibleportals ); +} //end of the function AAS_FindPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveAllPortals( void ) { + int i; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + ( *aasworld ).areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + } //end for +} //end of the function AAS_RemoveAllPortals + +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodCluster_r( int areanum, int clusternum ) { + int i, otherareanum; + aas_face_t *face; + aas_area_t *area; + + //set cluster mark + ( *aasworld ).areasettings[areanum].cluster = clusternum; + //if the area is a portal + //if ((*aasworld).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return; + // + area = &( *aasworld ).areas[areanum]; + //use area faces to flood into adjacent areas + for ( i = 0; i < area->numfaces; i++ ) + { + face = &( *aasworld ).faces[abs( ( *aasworld ).faceindex[area->firstface + i] )]; + // + if ( face->frontarea != areanum ) { + otherareanum = face->frontarea; + } else { otherareanum = face->backarea;} + //if there's no area at the other side + if ( !otherareanum ) { + continue; + } + //if the area is a portal + if ( ( *aasworld ).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + //if the area is already marked + if ( ( *aasworld ).areasettings[otherareanum].cluster ) { + continue; + } + // + AAS_FloodCluster_r( otherareanum, clusternum ); + } //end for + //use the reachabilities to flood into other areas + for ( i = 0; i < ( *aasworld ).areasettings[areanum].numreachableareas; i++ ) + { + otherareanum = ( *aasworld ).reachability[ + ( *aasworld ).areasettings[areanum].firstreachablearea + i].areanum; + if ( !otherareanum ) { + continue; + AAS_Error( "reachability %d has zero area\n", ( *aasworld ).areasettings[areanum].firstreachablearea + i ); + } //end if + //if the area is a portal + if ( ( *aasworld ).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + //if the area is already marked + if ( ( *aasworld ).areasettings[otherareanum].cluster ) { + continue; + } + // + AAS_FloodCluster_r( otherareanum, clusternum ); + } //end for +} //end of the function AAS_FloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveTeleporterPortals( void ) { + int i, j, areanum; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + for ( j = 0; j < ( *aasworld ).areasettings[i].numreachableareas; j++ ) + { + areanum = ( *aasworld ).reachability[( *aasworld ).areasettings[i].firstreachablearea + j].areanum; + if ( ( *aasworld ).reachability[( *aasworld ).areasettings[i].firstreachablearea + j].traveltype == TRAVEL_TELEPORT ) { + ( *aasworld ).areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + ( *aasworld ).areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_RemoveTeleporterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodClusterReachabilities( int clusternum ) { + int i, j, areanum; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + //if this area already has a cluster set + if ( ( *aasworld ).areasettings[i].cluster ) { + continue; + } + //if this area is a cluster portal + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + //loop over the reachable areas from this area + for ( j = 0; j < ( *aasworld ).areasettings[i].numreachableareas; j++ ) + { + //the reachable area + areanum = ( *aasworld ).reachability[( *aasworld ).areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + //if this area has a cluster set + if ( ( *aasworld ).areasettings[areanum].cluster == clusternum ) { + AAS_FloodCluster_r( i, clusternum ); + i = 0; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_FloodClusterReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_RemoveNotClusterClosingPortals( void ) { + int i, j, k, facenum, otherareanum, nonclosingportals; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( !( ( *aasworld ).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL ) ) { + continue; + } + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &( *aasworld ).areas[i]; + for ( j = 0; j < area->numfaces; j++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + j] ); + face = &( *aasworld ).faces[facenum]; + // + if ( face->frontarea != i ) { + otherareanum = face->frontarea; + } else { otherareanum = face->backarea;} + // + if ( !otherareanum ) { + continue; + } + // + if ( ( *aasworld ).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } //end if + //reset all cluster fields + AAS_RemoveClusterAreas(); + // + AAS_FloodCluster_r( otherareanum, 1 ); + AAS_FloodClusterReachabilities( 1 ); + //check if all adjacent non-portal areas have a cluster set + for ( k = 0; k < area->numfaces; k++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + k] ); + face = &( *aasworld ).faces[facenum]; + // + if ( face->frontarea != i ) { + otherareanum = face->frontarea; + } else { otherareanum = face->backarea;} + // + if ( !otherareanum ) { + continue; + } + // + if ( ( *aasworld ).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } //end if + // + if ( !( *aasworld ).areasettings[otherareanum].cluster ) { + break; + } + } //end for + //if all adjacent non-portal areas have a cluster set then the portal + //didn't seal a cluster + if ( k >= area->numfaces ) { + ( *aasworld ).areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + break; + } //end if + } //end for + } //end for + botimport.Print( PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals ); +} //end of the function AAS_RemoveNotClusterClosingPortals*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_RemoveNotClusterClosingPortals( void ) { + int i, j, facenum, otherareanum, nonclosingportals, numseperatedclusters; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( !( ( *aasworld ).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL ) ) { + continue; + } + // + numseperatedclusters = 0; + //reset all cluster fields + AAS_RemoveClusterAreas(); + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &( *aasworld ).areas[i]; + for ( j = 0; j < area->numfaces; j++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + j] ); + face = &( *aasworld ).faces[facenum]; + // + if ( face->frontarea != i ) { + otherareanum = face->frontarea; + } else { otherareanum = face->backarea;} + //if not solid at the other side of the face + if ( !otherareanum ) { + continue; + } + //don't flood into other portals + if ( ( *aasworld ).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + //if the area already has a cluster set + if ( ( *aasworld ).areasettings[otherareanum].cluster ) { + continue; + } + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r( otherareanum, numseperatedclusters ); + AAS_FloodClusterReachabilities( numseperatedclusters ); + } //end for + //use the reachabilities to flood into other areas + for ( j = 0; j < ( *aasworld ).areasettings[i].numreachableareas; j++ ) + { + otherareanum = ( *aasworld ).reachability[ + ( *aasworld ).areasettings[i].firstreachablearea + j].areanum; + //this should never be qtrue but we check anyway + if ( !otherareanum ) { + continue; + } + //don't flood into other portals + if ( ( *aasworld ).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } + //if the area already has a cluster set + if ( ( *aasworld ).areasettings[otherareanum].cluster ) { + continue; + } + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r( otherareanum, numseperatedclusters ); + AAS_FloodClusterReachabilities( numseperatedclusters ); + } //end for + //a portal must seperate no more and no less than 2 clusters + if ( numseperatedclusters != 2 ) { + ( *aasworld ).areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + } //end if + } //end for + botimport.Print( PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals ); +} //end of the function AAS_RemoveNotClusterClosingPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_AddTeleporterPortals( void ) { + int j, area2num, facenum, otherareanum; + char *target, *targetname, *classname; + bsp_entity_t *entities, *ent, *dest; + vec3_t origin, destorigin, mins, maxs, end; + vec3_t bbmins, bbmaxs; + aas_area_t *area; + aas_face_t *face; + aas_trace_t trace; + aas_link_t *areas, *link; + + entities = AAS_ParseBSPEntities(); + + for ( ent = entities; ent; ent = ent->next ) + { + classname = AAS_ValueForBSPEpairKey( ent, "classname" ); + if ( classname && !strcmp( classname, "misc_teleporter" ) ) { + if ( !AAS_VectorForBSPEpairKey( ent, "origin", origin ) ) { + botimport.Print( PRT_ERROR, "teleporter (%s) without origin\n", target ); + continue; + } //end if + // + target = AAS_ValueForBSPEpairKey( ent, "target" ); + if ( !target ) { + botimport.Print( PRT_ERROR, "teleporter (%s) without target\n", target ); + continue; + } //end if + for ( dest = entities; dest; dest = dest->next ) + { + classname = AAS_ValueForBSPEpairKey( dest, "classname" ); + if ( classname && !strcmp( classname, "misc_teleporter_dest" ) ) { + targetname = AAS_ValueForBSPEpairKey( dest, "targetname" ); + if ( targetname && !strcmp( targetname, target ) ) { + break; + } //end if + } //end if + } //end for + if ( !dest ) { + botimport.Print( PRT_ERROR, "teleporter without destination (%s)\n", target ); + continue; + } //end if + if ( !AAS_VectorForBSPEpairKey( dest, "origin", destorigin ) ) { + botimport.Print( PRT_ERROR, "teleporter destination (%s) without origin\n", target ); + continue; + } //end if + destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground + VectorCopy( destorigin, end ); + end[2] -= 100; + trace = AAS_TraceClientBBox( destorigin, end, PRESENCE_CROUCH, -1 ); + if ( trace.startsolid ) { + botimport.Print( PRT_ERROR, "teleporter destination (%s) in solid\n", target ); + continue; + } //end if + VectorCopy( trace.endpos, destorigin ); + area2num = AAS_PointAreaNum( destorigin ); + //reset all cluster fields + for ( j = 0; j < ( *aasworld ).numareas; j++ ) + { + ( *aasworld ).areasettings[j].cluster = 0; + } //end for + // + VectorSet( mins, -8, -8, 8 ); + VectorSet( maxs, 8, 8, 24 ); + // + AAS_PresenceTypeBoundingBox( PRESENCE_CROUCH, bbmins, bbmaxs ); + // + VectorAdd( origin, mins, mins ); + VectorAdd( origin, maxs, maxs ); + //add bounding box size + VectorSubtract( mins, bbmaxs, mins ); + VectorSubtract( maxs, bbmins, maxs ); + //link an invalid (-1) entity + areas = AAS_AASLinkEntity( mins, maxs, -1 ); + // + for ( link = areas; link; link = link->next_area ) + { + if ( !AAS_AreaGrounded( link->areanum ) ) { + continue; + } + //add the teleporter portal mark + ( *aasworld ).areasettings[link->areanum].contents |= AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL; + } //end for + // + for ( link = areas; link; link = link->next_area ) + { + if ( !AAS_AreaGrounded( link->areanum ) ) { + continue; + } + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &( *aasworld ).areas[link->areanum]; + for ( j = 0; j < area->numfaces; j++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + j] ); + face = &( *aasworld ).faces[facenum]; + // + if ( face->frontarea != link->areanum ) { + otherareanum = face->frontarea; + } else { otherareanum = face->backarea;} + // + if ( !otherareanum ) { + continue; + } + // + if ( ( *aasworld ).areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + continue; + } //end if + // + AAS_FloodCluster_r( otherareanum, 1 ); + } //end for + } //end for + //if the teleport destination IS in the same cluster + if ( ( *aasworld ).areasettings[area2num].cluster ) { + for ( link = areas; link; link = link->next_area ) + { + if ( !AAS_AreaGrounded( link->areanum ) ) { + continue; + } + //add the teleporter portal mark + ( *aasworld ).areasettings[link->areanum].contents &= ~( AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL ); + } //end for + } //end if + } //end if + } //end for + AAS_FreeBSPEntities( entities ); +} //end of the function AAS_AddTeleporterPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddTeleporterPortals( void ) { + int i, j, areanum; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + for ( j = 0; j < ( *aasworld ).areasettings[i].numreachableareas; j++ ) + { + if ( ( *aasworld ).reachability[( *aasworld ).areasettings[i].firstreachablearea + j].traveltype != TRAVEL_TELEPORT ) { + continue; + } + areanum = ( *aasworld ).reachability[( *aasworld ).areasettings[i].firstreachablearea + j].areanum; + ( *aasworld ).areasettings[areanum].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end for + } //end for +} //end of the function AAS_AddTeleporterPortals*/ +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TestPortals( void ) { + int i; + aas_portal_t *portal; + + for ( i = 1; i < ( *aasworld ).numportals; i++ ) + { + portal = &( *aasworld ).portals[i]; + if ( !portal->frontcluster ) { + ( *aasworld ).areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write( "portal area %d has no front cluster\r\n", portal->areanum ); + return qfalse; + } //end if + if ( !portal->backcluster ) { + ( *aasworld ).areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write( "portal area %d has no back cluster\r\n", portal->areanum ); + return qfalse; + } //end if + } //end for + return qtrue; +} //end of the function +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CountForcedClusterPortals( void ) { + int num, i; + + num = 0; + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL ) { + num++; + } //end if + } //end for + botimport.Print( PRT_MESSAGE, "%6d forced portals\n", num ); +} //end of the function AAS_CountForcedClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateViewPortals( void ) { + int i; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL ) { + ( *aasworld ).areasettings[i].contents |= AREACONTENTS_VIEWPORTAL; + } //end if + } //end for +} //end of the function AAS_CreateViewPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetViewPortalsAsClusterPortals( void ) { + int i; + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_VIEWPORTAL ) { + ( *aasworld ).areasettings[i].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end if + } //end for +} //end of the function AAS_SetViewPortalsAsClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClustering( void ) { + int i, removedPortalAreas; + int n, total, numreachabilityareas; + + if ( !( *aasworld ).loaded ) { + return; + } + //if there are clusters + if ( ( *aasworld ).numclusters >= 1 ) { +#ifndef BSPC + //if clustering isn't forced + if ( !( (int)LibVarGetValue( "forceclustering" ) ) && + !( (int)LibVarGetValue( "forcereachability" ) ) ) { + return; + } +#else + return; +#endif + } //end if + // + AAS_CountForcedClusterPortals(); + //remove all the existing portals + //AAS_RemoveAllPortals(); + //remove all area cluster marks + AAS_RemoveClusterAreas(); + //find possible cluster portals + AAS_FindPossiblePortals(); + //craete portals to for the bot view + AAS_CreateViewPortals(); + //remove all portals that are not closing a cluster + //AAS_RemoveNotClusterClosingPortals(); + //initialize portal memory + if ( ( *aasworld ).portals ) { + FreeMemory( ( *aasworld ).portals ); + } + ( *aasworld ).portals = (aas_portal_t *) GetClearedMemory( AAS_MAX_PORTALS * sizeof( aas_portal_t ) ); + //initialize portal index memory + if ( ( *aasworld ).portalindex ) { + FreeMemory( ( *aasworld ).portalindex ); + } + ( *aasworld ).portalindex = (aas_portalindex_t *) GetClearedMemory( AAS_MAX_PORTALINDEXSIZE * sizeof( aas_portalindex_t ) ); + //initialize cluster memory + if ( ( *aasworld ).clusters ) { + FreeMemory( ( *aasworld ).clusters ); + } + ( *aasworld ).clusters = (aas_cluster_t *) GetClearedMemory( AAS_MAX_CLUSTERS * sizeof( aas_cluster_t ) ); + // + removedPortalAreas = 0; + botimport.Print( PRT_MESSAGE, "\r%6d removed portal areas", removedPortalAreas ); + while ( 1 ) + { + botimport.Print( PRT_MESSAGE, "\r%6d", removedPortalAreas ); + //initialize the number of portals and clusters + ( *aasworld ).numportals = 1; //portal 0 is a dummy + ( *aasworld ).portalindexsize = 0; + ( *aasworld ).numclusters = 1; //cluster 0 is a dummy + //create the portals from the portal areas + AAS_CreatePortals(); + // + removedPortalAreas++; + //find the clusters + if ( !AAS_FindClusters() ) { + continue; + } + //test the portals + if ( !AAS_TestPortals() ) { + continue; + } + // + break; + } //end while + botimport.Print( PRT_MESSAGE, "\n" ); + //the AAS file should be saved + ( *aasworld ).savefile = qtrue; + // report cluster info + botimport.Print( PRT_MESSAGE, "%6d portals created\n", ( *aasworld ).numportals ); + botimport.Print( PRT_MESSAGE, "%6d clusters created\n", ( *aasworld ).numclusters ); + for ( i = 1; i < ( *aasworld ).numclusters; i++ ) + { + botimport.Print( PRT_MESSAGE, "cluster %d has %d reachability areas\n", i, + ( *aasworld ).clusters[i].numreachabilityareas ); + } //end for + // report AAS file efficiency + numreachabilityareas = 0; + total = 0; + for ( i = 0; i < ( *aasworld ).numclusters; i++ ) { + n = ( *aasworld ).clusters[i].numreachabilityareas; + numreachabilityareas += n; + total += n * n; + } + total += numreachabilityareas * ( *aasworld ).numportals; + // + botimport.Print( PRT_MESSAGE, "%6i total reachability areas\n", numreachabilityareas ); + botimport.Print( PRT_MESSAGE, "%6i AAS memory/CPU usage (the lower the better)\n", total * 3 ); +} //end of the function AAS_InitClustering diff --git a/src/botlib/be_aas_cluster.h b/src/botlib/be_aas_cluster.h new file mode 100644 index 0000000..01c26ef --- /dev/null +++ b/src/botlib/be_aas_cluster.h @@ -0,0 +1,42 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_cluster.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize the AAS clustering +void AAS_InitClustering( void ); +#endif //AASINTERN + diff --git a/src/botlib/be_aas_debug.c b/src/botlib/be_aas_debug.c new file mode 100644 index 0000000..766bede --- /dev/null +++ b/src/botlib/be_aas_debug.c @@ -0,0 +1,692 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_debug.c + * + * desc: AAS debug code + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_interface.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +#define MAX_DEBUGLINES 1024 +#define MAX_DEBUGPOLYGONS 128 + +int debuglines[MAX_DEBUGLINES]; +int debuglinevisible[MAX_DEBUGLINES]; +int numdebuglines; + +static int debugpolygons[MAX_DEBUGPOLYGONS]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownPolygons( void ) { + int i; +//* + for ( i = 0; i < MAX_DEBUGPOLYGONS; i++ ) + { + if ( debugpolygons[i] ) { + botimport.DebugPolygonDelete( debugpolygons[i] ); + } + debugpolygons[i] = 0; + } //end for +//*/ +/* + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + botimport.DebugPolygonDelete(i); + debugpolygons[i] = 0; + } //end for +*/ +} //end of the function AAS_ClearShownPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowPolygon( int color, int numpoints, vec3_t *points ) { + int i; + + for ( i = 0; i < MAX_DEBUGPOLYGONS; i++ ) + { + if ( !debugpolygons[i] ) { + debugpolygons[i] = botimport.DebugPolygonCreate( color, numpoints, points ); + break; + } //end if + } //end for +} //end of the function AAS_ShowPolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownDebugLines( void ) { + int i; + + //make all lines invisible + for ( i = 0; i < MAX_DEBUGLINES; i++ ) + { + if ( debuglines[i] ) { + //botimport.DebugLineShow(debuglines[i], NULL, NULL, LINECOLOR_NONE); + botimport.DebugLineDelete( debuglines[i] ); + debuglines[i] = 0; + debuglinevisible[i] = qfalse; + } //end if + } //end for +} //end of the function AAS_ClearShownDebugLines +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DebugLine( vec3_t start, vec3_t end, int color ) { + int line; + + for ( line = 0; line < MAX_DEBUGLINES; line++ ) + { + if ( !debuglines[line] ) { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if ( !debuglinevisible[line] ) { + botimport.DebugLineShow( debuglines[line], start, end, color ); + debuglinevisible[line] = qtrue; + return; + } //end else + } //end for +} //end of the function AAS_DebugLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PermanentLine( vec3_t start, vec3_t end, int color ) { + int line; + + line = botimport.DebugLineCreate(); + botimport.DebugLineShow( line, start, end, color ); +} //end of the function AAS_PermenentLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPermanentCross( vec3_t origin, float size, int color ) { + int i, debugline; + vec3_t start, end; + + for ( i = 0; i < 3; i++ ) + { + VectorCopy( origin, start ); + start[i] += size; + VectorCopy( origin, end ); + end[i] -= size; + AAS_DebugLine( start, end, color ); + debugline = botimport.DebugLineCreate(); + botimport.DebugLineShow( debugline, start, end, color ); + } //end for +} //end of the function AAS_DrawPermanentCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPlaneCross( vec3_t point, vec3_t normal, float dist, int type, int color ) { + int n0, n1, n2, j, line, lines[2]; + vec3_t start1, end1, start2, end2; + + //make a cross in the hit plane at the hit point + VectorCopy( point, start1 ); + VectorCopy( point, end1 ); + VectorCopy( point, start2 ); + VectorCopy( point, end2 ); + + n0 = type % 3; + n1 = ( type + 1 ) % 3; + n2 = ( type + 2 ) % 3; + start1[n1] -= 6; + start1[n2] -= 6; + end1[n1] += 6; + end1[n2] += 6; + start2[n1] += 6; + start2[n2] -= 6; + end2[n1] -= 6; + end2[n2] += 6; + + start1[n0] = ( dist - ( start1[n1] * normal[n1] + + start1[n2] * normal[n2] ) ) / normal[n0]; + end1[n0] = ( dist - ( end1[n1] * normal[n1] + + end1[n2] * normal[n2] ) ) / normal[n0]; + start2[n0] = ( dist - ( start2[n1] * normal[n1] + + start2[n2] * normal[n2] ) ) / normal[n0]; + end2[n0] = ( dist - ( end2[n1] * normal[n1] + + end2[n2] * normal[n2] ) ) / normal[n0]; + + for ( j = 0, line = 0; j < 2 && line < MAX_DEBUGLINES; line++ ) + { + if ( !debuglines[line] ) { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if ( !debuglinevisible[line] ) { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + botimport.DebugLineShow( lines[0], start1, end1, color ); + botimport.DebugLineShow( lines[1], start2, end2, color ); +} //end of the function AAS_DrawPlaneCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs ) { + vec3_t bboxcorners[8]; + int lines[3]; + int i, j, line; + + //upper corners + bboxcorners[0][0] = origin[0] + maxs[0]; + bboxcorners[0][1] = origin[1] + maxs[1]; + bboxcorners[0][2] = origin[2] + maxs[2]; + // + bboxcorners[1][0] = origin[0] + mins[0]; + bboxcorners[1][1] = origin[1] + maxs[1]; + bboxcorners[1][2] = origin[2] + maxs[2]; + // + bboxcorners[2][0] = origin[0] + mins[0]; + bboxcorners[2][1] = origin[1] + mins[1]; + bboxcorners[2][2] = origin[2] + maxs[2]; + // + bboxcorners[3][0] = origin[0] + maxs[0]; + bboxcorners[3][1] = origin[1] + mins[1]; + bboxcorners[3][2] = origin[2] + maxs[2]; + //lower corners + memcpy( bboxcorners[4], bboxcorners[0], sizeof( vec3_t ) * 4 ); + for ( i = 0; i < 4; i++ ) bboxcorners[4 + i][2] = origin[2] + mins[2]; + //draw bounding box + for ( i = 0; i < 4; i++ ) + { + for ( j = 0, line = 0; j < 3 && line < MAX_DEBUGLINES; line++ ) + { + if ( !debuglines[line] ) { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if ( !debuglinevisible[line] ) { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + //top plane + botimport.DebugLineShow( lines[0], bboxcorners[i], + bboxcorners[( i + 1 ) & 3], LINECOLOR_RED ); + //bottom plane + botimport.DebugLineShow( lines[1], bboxcorners[4 + i], + bboxcorners[4 + ( ( i + 1 ) & 3 )], LINECOLOR_RED ); + //vertical lines + botimport.DebugLineShow( lines[2], bboxcorners[i], + bboxcorners[4 + i], LINECOLOR_RED ); + } //end for +} //end of the function AAS_ShowBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFace( int facenum ) { + int i, color, edgenum; + aas_edge_t *edge; + aas_face_t *face; + aas_plane_t *plane; + vec3_t start, end; + + color = LINECOLOR_YELLOW; + //check if face number is in range + if ( facenum >= ( *aasworld ).numfaces ) { + botimport.Print( PRT_ERROR, "facenum %d out of range\n", facenum ); + } //end if + face = &( *aasworld ).faces[facenum]; + //walk through the edges of the face + for ( i = 0; i < face->numedges; i++ ) + { + //edge number + edgenum = abs( ( *aasworld ).edgeindex[face->firstedge + i] ); + //check if edge number is in range + if ( edgenum >= ( *aasworld ).numedges ) { + botimport.Print( PRT_ERROR, "edgenum %d out of range\n", edgenum ); + } //end if + edge = &( *aasworld ).edges[edgenum]; + if ( color == LINECOLOR_RED ) { + color = LINECOLOR_GREEN; + } else if ( color == LINECOLOR_GREEN ) { + color = LINECOLOR_BLUE; + } else if ( color == LINECOLOR_BLUE ) { + color = LINECOLOR_YELLOW; + } else { color = LINECOLOR_RED;} + AAS_DebugLine( ( *aasworld ).vertexes[edge->v[0]], + ( *aasworld ).vertexes[edge->v[1]], + color ); + } //end for + plane = &( *aasworld ).planes[face->planenum]; + edgenum = abs( ( *aasworld ).edgeindex[face->firstedge] ); + edge = &( *aasworld ).edges[edgenum]; + VectorCopy( ( *aasworld ).vertexes[edge->v[0]], start ); + VectorMA( start, 20, plane->normal, end ); + AAS_DebugLine( start, end, LINECOLOR_RED ); +} //end of the function AAS_ShowFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFacePolygon( int facenum, int color, int flip ) { + int i, edgenum, numpoints; + vec3_t points[128]; + aas_edge_t *edge; + aas_face_t *face; + + //check if face number is in range + if ( facenum >= ( *aasworld ).numfaces ) { + botimport.Print( PRT_ERROR, "facenum %d out of range\n", facenum ); + } //end if + face = &( *aasworld ).faces[facenum]; + //walk through the edges of the face + numpoints = 0; + if ( flip ) { + for ( i = face->numedges - 1; i >= 0; i-- ) + { + //edge number + edgenum = ( *aasworld ).edgeindex[face->firstedge + i]; + edge = &( *aasworld ).edges[abs( edgenum )]; + VectorCopy( ( *aasworld ).vertexes[edge->v[edgenum < 0]], points[numpoints] ); + numpoints++; + } //end for + } //end if + else + { + for ( i = 0; i < face->numedges; i++ ) + { + //edge number + edgenum = ( *aasworld ).edgeindex[face->firstedge + i]; + edge = &( *aasworld ).edges[abs( edgenum )]; + VectorCopy( ( *aasworld ).vertexes[edge->v[edgenum < 0]], points[numpoints] ); + numpoints++; + } //end for + } //end else + AAS_ShowPolygon( color, numpoints, points ); +} //end of the function AAS_ShowFacePolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowArea( int areanum, int groundfacesonly ) { + int areaedges[MAX_DEBUGLINES]; + int numareaedges, i, j, n, color = 0, line; + int facenum, edgenum; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + + // + numareaedges = 0; + // + if ( areanum < 0 || areanum >= ( *aasworld ).numareas ) { + botimport.Print( PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, ( *aasworld ).numareas ); + return; + } //end if + //pointer to the convex area + area = &( *aasworld ).areas[areanum]; + //walk through the faces of the area + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + i] ); + //check if face number is in range + if ( facenum >= ( *aasworld ).numfaces ) { + botimport.Print( PRT_ERROR, "facenum %d out of range\n", facenum ); + } //end if + face = &( *aasworld ).faces[facenum]; + //ground faces only + if ( groundfacesonly ) { + if ( !( face->faceflags & ( FACE_GROUND | FACE_LADDER ) ) ) { + continue; + } + } //end if + //walk through the edges of the face + for ( j = 0; j < face->numedges; j++ ) + { + //edge number + edgenum = abs( ( *aasworld ).edgeindex[face->firstedge + j] ); + //check if edge number is in range + if ( edgenum >= ( *aasworld ).numedges ) { + botimport.Print( PRT_ERROR, "edgenum %d out of range\n", edgenum ); + } //end if + //check if the edge is stored already + for ( n = 0; n < numareaedges; n++ ) + { + if ( areaedges[n] == edgenum ) { + break; + } + } //end for + if ( n == numareaedges && numareaedges < MAX_DEBUGLINES ) { + areaedges[numareaedges++] = edgenum; + } //end if + } //end for + //AAS_ShowFace(facenum); + } //end for + //draw all the edges + for ( n = 0; n < numareaedges; n++ ) + { + for ( line = 0; line < MAX_DEBUGLINES; line++ ) + { + if ( !debuglines[line] ) { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if ( !debuglinevisible[line] ) { + break; + } //end else + } //end for + if ( line >= MAX_DEBUGLINES ) { + return; + } + edge = &( *aasworld ).edges[areaedges[n]]; + if ( color == LINECOLOR_RED ) { + color = LINECOLOR_BLUE; + } else if ( color == LINECOLOR_BLUE ) { + color = LINECOLOR_GREEN; + } else if ( color == LINECOLOR_GREEN ) { + color = LINECOLOR_YELLOW; + } else { color = LINECOLOR_RED;} + botimport.DebugLineShow( debuglines[line], + ( *aasworld ).vertexes[edge->v[0]], + ( *aasworld ).vertexes[edge->v[1]], + color ); + debuglinevisible[line] = qtrue; + } //end for*/ +} //end of the function AAS_ShowArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowAreaPolygons( int areanum, int color, int groundfacesonly ) { + int i, facenum; + aas_area_t *area; + aas_face_t *face; + + // + if ( areanum < 0 || areanum >= ( *aasworld ).numareas ) { + botimport.Print( PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, ( *aasworld ).numareas ); + return; + } //end if + //pointer to the convex area + area = &( *aasworld ).areas[areanum]; + //walk through the faces of the area + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + i] ); + //check if face number is in range + if ( facenum >= ( *aasworld ).numfaces ) { + botimport.Print( PRT_ERROR, "facenum %d out of range\n", facenum ); + } //end if + face = &( *aasworld ).faces[facenum]; + //ground faces only + if ( groundfacesonly ) { + if ( !( face->faceflags & ( FACE_GROUND | FACE_LADDER ) ) ) { + continue; + } + } //end if + AAS_ShowFacePolygon( facenum, color, face->frontarea != areanum ); + } //end for +} //end of the function AAS_ShowAreaPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawCross( vec3_t origin, float size, int color ) { + int i; + vec3_t start, end; + + for ( i = 0; i < 3; i++ ) + { + VectorCopy( origin, start ); + start[i] += size; + VectorCopy( origin, end ); + end[i] -= size; + AAS_DebugLine( start, end, color ); + } //end for +} //end of the function AAS_DrawCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PrintTravelType( int traveltype ) { +#ifdef DEBUG + char *str; + // + switch ( traveltype ) + { + case TRAVEL_INVALID: str = "TRAVEL_INVALID"; break; + case TRAVEL_WALK: str = "TRAVEL_WALK"; break; + case TRAVEL_CROUCH: str = "TRAVEL_CROUCH"; break; + case TRAVEL_BARRIERJUMP: str = "TRAVEL_BARRIERJUMP"; break; + case TRAVEL_JUMP: str = "TRAVEL_JUMP"; break; + case TRAVEL_LADDER: str = "TRAVEL_LADDER"; break; + case TRAVEL_WALKOFFLEDGE: str = "TRAVEL_WALKOFFLEDGE"; break; + case TRAVEL_SWIM: str = "TRAVEL_SWIM"; break; + case TRAVEL_WATERJUMP: str = "TRAVEL_WATERJUMP"; break; + case TRAVEL_TELEPORT: str = "TRAVEL_TELEPORT"; break; + case TRAVEL_ELEVATOR: str = "TRAVEL_ELEVATOR"; break; + case TRAVEL_ROCKETJUMP: str = "TRAVEL_ROCKETJUMP"; break; + case TRAVEL_BFGJUMP: str = "TRAVEL_BFGJUMP"; break; + case TRAVEL_GRAPPLEHOOK: str = "TRAVEL_GRAPPLEHOOK"; break; + case TRAVEL_JUMPPAD: str = "TRAVEL_JUMPPAD"; break; + case TRAVEL_FUNCBOB: str = "TRAVEL_FUNCBOB"; break; + default: str = "UNKNOWN TRAVEL TYPE"; break; + } //end switch + botimport.Print( PRT_MESSAGE, "%s", str ); +#endif //DEBUG +} //end of the function AAS_PrintTravelType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawArrow( vec3_t start, vec3_t end, int linecolor, int arrowcolor ) { + vec3_t dir, cross, p1, p2, up = {0, 0, 1}; + float dot; + + VectorSubtract( end, start, dir ); + VectorNormalize( dir ); + dot = DotProduct( dir, up ); + if ( dot > 0.99 || dot < -0.99 ) { + VectorSet( cross, 1, 0, 0 ); + } else { CrossProduct( dir, up, cross );} + + VectorMA( end, -6, dir, p1 ); + VectorCopy( p1, p2 ); + VectorMA( p1, 6, cross, p1 ); + VectorMA( p2, -6, cross, p2 ); + + AAS_DebugLine( start, end, linecolor ); + AAS_DebugLine( p1, end, arrowcolor ); + AAS_DebugLine( p2, end, arrowcolor ); +} //end of the function AAS_DrawArrow +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachability( aas_reachability_t *reach ) { + vec3_t dir, cmdmove, velocity; + float speed, zvel; + aas_clientmove_t move; + + AAS_ShowAreaPolygons( reach->areanum, 5, qtrue ); + //AAS_ShowArea(reach->areanum, qtrue); + AAS_DrawArrow( reach->start, reach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW ); + // + if ( reach->traveltype == TRAVEL_JUMP || reach->traveltype == TRAVEL_WALKOFFLEDGE ) { + AAS_HorizontalVelocityForJump( aassettings.sv_jumpvel, reach->start, reach->end, &speed ); + // + VectorSubtract( reach->end, reach->start, dir ); + dir[2] = 0; + VectorNormalize( dir ); + //set the velocity + VectorScale( dir, speed, velocity ); + //set the command movement + VectorClear( cmdmove ); + cmdmove[2] = aassettings.sv_jumpvel; + // + AAS_PredictClientMovement( &move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1, + SE_HITGROUND | SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE, 0, qtrue ); + // + if ( reach->traveltype == TRAVEL_JUMP ) { + AAS_JumpReachRunStart( reach, dir ); + AAS_DrawCross( dir, 4, LINECOLOR_BLUE ); + } //end if + } //end if + else if ( reach->traveltype == TRAVEL_ROCKETJUMP ) { + zvel = AAS_RocketJumpZVelocity( reach->start ); + AAS_HorizontalVelocityForJump( zvel, reach->start, reach->end, &speed ); + // + VectorSubtract( reach->end, reach->start, dir ); + dir[2] = 0; + VectorNormalize( dir ); + //get command movement + VectorScale( dir, speed, cmdmove ); + VectorSet( velocity, 0, 0, zvel ); + // + AAS_PredictClientMovement( &move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | + SE_TOUCHJUMPPAD | SE_HITGROUNDAREA, reach->areanum, qtrue ); + } //end else if + else if ( reach->traveltype == TRAVEL_JUMPPAD ) { + VectorSet( cmdmove, 0, 0, 0 ); + // + VectorSubtract( reach->end, reach->start, dir ); + dir[2] = 0; + VectorNormalize( dir ); + //set the velocity + //NOTE: the edgenum is the horizontal velocity + VectorScale( dir, reach->edgenum, velocity ); + //NOTE: the facenum is the Z velocity + velocity[2] = reach->facenum; + // + AAS_PredictClientMovement( &move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | + SE_TOUCHJUMPPAD | SE_HITGROUNDAREA, reach->areanum, qtrue ); + } //end else if +} //end of the function AAS_ShowReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachableAreas( int areanum ) { + aas_areasettings_t *settings; + static aas_reachability_t reach; + static int index, lastareanum; + static float lasttime; + + if ( areanum != lastareanum ) { + index = 0; + lastareanum = areanum; + } //end if + settings = &( *aasworld ).areasettings[areanum]; + // + if ( !settings->numreachableareas ) { + return; + } + // + if ( index >= settings->numreachableareas ) { + index = 0; + } + // + if ( AAS_Time() - lasttime > 1.5 ) { + memcpy( &reach, &( *aasworld ).reachability[settings->firstreachablearea + index], sizeof( aas_reachability_t ) ); + index++; + lasttime = AAS_Time(); + AAS_PrintTravelType( reach.traveltype ); + botimport.Print( PRT_MESSAGE, "(traveltime: %i)\n", reach.traveltime ); + } //end if + AAS_ShowReachability( &reach ); +} //end of the function ShowReachableAreas diff --git a/src/botlib/be_aas_debug.h b/src/botlib/be_aas_debug.h new file mode 100644 index 0000000..c833b87 --- /dev/null +++ b/src/botlib/be_aas_debug.h @@ -0,0 +1,68 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_debug.h + * + * desc: AAS + * + * + *****************************************************************************/ + +//clear the shown debug lines +void AAS_ClearShownDebugLines( void ); +// +void AAS_ClearShownPolygons( void ); +//show a debug line +void AAS_DebugLine( vec3_t start, vec3_t end, int color ); +//show a permenent line +void AAS_PermanentLine( vec3_t start, vec3_t end, int color ); +//show a permanent cross +void AAS_DrawPermanentCross( vec3_t origin, float size, int color ); +//draw a cross in the plane +void AAS_DrawPlaneCross( vec3_t point, vec3_t normal, float dist, int type, int color ); +//show a bounding box +void AAS_ShowBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs ); +//show a face +void AAS_ShowFace( int facenum ); +//show an area +void AAS_ShowArea( int areanum, int groundfacesonly ); +// +void AAS_ShowAreaPolygons( int areanum, int color, int groundfacesonly ); +//draw a cros +void AAS_DrawCross( vec3_t origin, float size, int color ); +//print the travel type +void AAS_PrintTravelType( int traveltype ); +//draw an arrow +void AAS_DrawArrow( vec3_t start, vec3_t end, int linecolor, int arrowcolor ); +//visualize the given reachability +void AAS_ShowReachability( struct aas_reachability_s *reach ); +//show the reachable areas from the given area +void AAS_ShowReachableAreas( int areanum ); + diff --git a/src/botlib/be_aas_def.h b/src/botlib/be_aas_def.h new file mode 100644 index 0000000..fb12d86 --- /dev/null +++ b/src/botlib/be_aas_def.h @@ -0,0 +1,294 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_def.h + * + * desc: AAS + * + * + *****************************************************************************/ + +//debugging on +#define AAS_DEBUG + +//#define MAX_CLIENTS 128 +//#define MAX_MODELS 256 // these are sent over the net as 8 bits +//#define MAX_SOUNDS 256 // so they cannot be blindly increased +//#define MAX_CONFIGSTRINGS 1024 +#define MAX_CONFIGSTRINGS 2048 //----(SA) upped + +//#define CS_SCORES 32 +//#define CS_MODELS (CS_SCORES+MAX_CLIENTS) +//#define CS_SOUNDS (CS_MODELS+MAX_MODELS) + +#define DF_AASENTNUMBER( x ) ( x - ( *aasworlds ).entities ) +#define DF_NUMBERAASENT( x ) ( &( *aasworlds ).entities[x] ) +#define DF_AASENTCLIENT( x ) ( x - ( *aasworlds ).entities - 1 ) +#define DF_CLIENTAASENT( x ) ( &( *aasworlds ).entities[x + 1] ) + +#ifndef MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +//string index (for model, sound and image index) +typedef struct aas_stringindex_s +{ + int numindexes; + char **index; +} aas_stringindex_t; + +//structure to link entities to areas and areas to entities +typedef struct aas_link_s +{ + int entnum; + int areanum; + struct aas_link_s *next_ent, *prev_ent; + struct aas_link_s *next_area, *prev_area; +} aas_link_t; + +//structure to link entities to leaves and leaves to entities +typedef struct bsp_link_s +{ + int entnum; + int leafnum; + struct bsp_link_s *next_ent, *prev_ent; + struct bsp_link_s *next_leaf, *prev_leaf; +} bsp_link_t; + +typedef struct bsp_entdata_s +{ + vec3_t origin; + vec3_t angles; + vec3_t absmins; + vec3_t absmaxs; + int solid; + int modelnum; +} bsp_entdata_t; + +//entity +typedef struct aas_entity_s +{ + //entity info + aas_entityinfo_t i; + //links into the AAS areas + aas_link_t *areas; + //links into the BSP leaves + bsp_link_t *leaves; +} aas_entity_t; + +typedef struct aas_settings_s +{ + float sv_friction; + float sv_stopspeed; + float sv_gravity; + float sv_waterfriction; + float sv_watergravity; + float sv_maxvelocity; + float sv_maxwalkvelocity; + float sv_maxcrouchvelocity; + float sv_maxswimvelocity; + float sv_walkaccelerate; + float sv_airaccelerate; + float sv_swimaccelerate; + float sv_maxstep; + float sv_maxsteepness; + float sv_maxwaterjump; + float sv_maxbarrier; + float sv_jumpvel; +} aas_settings_t; + +//routing cache +typedef struct aas_routingcache_s +{ + int size; //size of the routing cache + float time; //last time accessed or updated + int cluster; //cluster the cache is for + int areanum; //area the cache is created for + vec3_t origin; //origin within the area + float starttraveltime; //travel time to start with + int travelflags; //combinations of the travel flags + struct aas_routingcache_s *prev, *next; + unsigned char *reachabilities; //reachabilities used for routing + unsigned short int traveltimes[1]; //travel time for every area (variable sized) +} aas_routingcache_t; + +//fields for the routing algorithm +typedef struct aas_routingupdate_s +{ + int cluster; + int areanum; //area number of the update + vec3_t start; //start point the area was entered + unsigned short int tmptraveltime; //temporary travel time + unsigned short int *areatraveltimes; //travel times within the area + qboolean inlist; //true if the update is in the list + struct aas_routingupdate_s *next; + struct aas_routingupdate_s *prev; +} aas_routingupdate_t; + +//reversed reachability link +typedef struct aas_reversedlink_s +{ + int linknum; //the aas_areareachability_t + int areanum; //reachable from this area + struct aas_reversedlink_s *next; //next link +} aas_reversedlink_t; + +//reversed area reachability +typedef struct aas_reversedreachability_s +{ + int numlinks; + aas_reversedlink_t *first; +} aas_reversedreachability_t; + +// Ridah, route-tables +#include "be_aas_routetable.h" +// done. + +typedef struct aas_s +{ + int loaded; //true when an AAS file is loaded + int initialized; //true when AAS has been initialized + int savefile; //set true when file should be saved + int bspchecksum; + //current time + float time; + int numframes; + //name of the aas file + char filename[MAX_PATH]; + char mapname[MAX_PATH]; + //bounding boxes + int numbboxes; + aas_bbox_t *bboxes; + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //planes + int numplanes; + aas_plane_t *planes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + //convex area settings + int numareasettings; + aas_areasettings_t *areasettings; + //reachablity list + int reachabilitysize; + aas_reachability_t *reachability; + //nodes of the bsp tree + int numnodes; + aas_node_t *nodes; + //cluster portals + int numportals; + aas_portal_t *portals; + //cluster portal index + int portalindexsize; + aas_portalindex_t *portalindex; + //clusters + int numclusters; + aas_cluster_t *clusters; + // + int reachabilityareas; + float reachabilitytime; + //enities linked in the areas + aas_link_t *linkheap; //heap with link structures + int linkheapsize; //size of the link heap + aas_link_t *freelinks; //first free link + aas_link_t **arealinkedentities; //entities linked into areas + //entities + int maxentities; + int maxclients; + aas_entity_t *entities; + //string indexes + char *configstrings[MAX_CONFIGSTRINGS]; + int indexessetup; + //index to retrieve travel flag for a travel type + int travelflagfortype[MAX_TRAVELTYPES]; + //routing update + aas_routingupdate_t *areaupdate; + aas_routingupdate_t *portalupdate; + //number of routing updates during a frame (reset every frame) + int frameroutingupdates; + //reversed reachability links + aas_reversedreachability_t *reversedreachability; + //travel times within the areas + unsigned short ***areatraveltimes; + //array of size numclusters with cluster cache + aas_routingcache_t ***clusterareacache; + aas_routingcache_t **portalcache; + //maximum travel time through portals + int *portalmaxtraveltimes; + // Ridah, pointer to Route-Table information + aas_rt_t *routetable; + //hide travel times + unsigned short int *hidetraveltimes; + //vis data + byte *decompressedvis; + int decompressedvisarea; + byte **areavisibility; + // done. + // Ridah, store the area's waypoint for hidepos calculations (center traced downwards) + vec3_t *areawaypoints; + // Ridah, so we can cache the areas that have already been tested for visibility/attackability + byte *visCache; +} aas_t; + +#define AASINTERN + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +// Ridah, route-tables +#include "be_aas_routetable.h" + +#endif //BSPCINCLUDE diff --git a/src/botlib/be_aas_entity.c b/src/botlib/be_aas_entity.c new file mode 100644 index 0000000..8d798ee --- /dev/null +++ b/src/botlib/be_aas_entity.c @@ -0,0 +1,498 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_entity.c + * + * desc: AAS entities + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define MASK_SOLID CONTENTS_PLAYERCLIP + +// Ridah, always use the default world for entities +extern aas_t aasworlds[2]; + +aas_t *defaultaasworld = aasworlds; + +//FIXME: these might change +enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdateEntity( int entnum, bot_entitystate_t *state ) { + int relink; + aas_entity_t *ent; + vec3_t absmins, absmaxs; + + if ( !( *defaultaasworld ).loaded ) { + botimport.Print( PRT_MESSAGE, "AAS_UpdateEntity: not loaded\n" ); + return BLERR_NOAASFILE; + } //end if + + ent = &( *defaultaasworld ).entities[entnum]; + + ent->i.update_time = AAS_Time() - ent->i.ltime; + ent->i.type = state->type; + ent->i.flags = state->flags; + ent->i.ltime = AAS_Time(); + VectorCopy( ent->i.origin, ent->i.lastvisorigin ); + VectorCopy( state->old_origin, ent->i.old_origin ); + ent->i.solid = state->solid; + ent->i.groundent = state->groundent; + ent->i.modelindex = state->modelindex; + ent->i.modelindex2 = state->modelindex2; + ent->i.frame = state->frame; + //ent->i.event = state->event; + ent->i.eventParm = state->eventParm; + ent->i.powerups = state->powerups; + ent->i.weapon = state->weapon; + ent->i.legsAnim = state->legsAnim; + ent->i.torsoAnim = state->torsoAnim; + +// ent->i.weapAnim = state->weapAnim; //----(SA) +//----(SA) didn't want to comment in as I wasn't sure of any implications of changing the aas_entityinfo_t and bot_entitystate_t structures. + + //number of the entity + ent->i.number = entnum; + //updated so set valid flag + ent->i.valid = qtrue; + //link everything the first frame + + if ( ( *defaultaasworld ).numframes == 1 ) { + relink = qtrue; + } else { relink = qfalse;} + + // + if ( ent->i.solid == SOLID_BSP ) { + //if the angles of the model changed + if ( !VectorCompare( state->angles, ent->i.angles ) ) { + VectorCopy( state->angles, ent->i.angles ); + relink = qtrue; + } //end if + //get the mins and maxs of the model + //FIXME: rotate mins and maxs + AAS_BSPModelMinsMaxsOrigin( ent->i.modelindex, ent->i.angles, ent->i.mins, ent->i.maxs, NULL ); + } //end if + else if ( ent->i.solid == SOLID_BBOX ) { + //if the bounding box size changed + if ( !VectorCompare( state->mins, ent->i.mins ) || + !VectorCompare( state->maxs, ent->i.maxs ) ) { + VectorCopy( state->mins, ent->i.mins ); + VectorCopy( state->maxs, ent->i.maxs ); + relink = qtrue; + } //end if + } //end if + //if the origin changed + if ( !VectorCompare( state->origin, ent->i.origin ) ) { + VectorCopy( state->origin, ent->i.origin ); + relink = qtrue; + } //end if + //if the entity should be relinked + if ( relink ) { + //don't link the world model + if ( entnum != ENTITYNUM_WORLD ) { + //absolute mins and maxs + VectorAdd( ent->i.mins, ent->i.origin, absmins ); + VectorAdd( ent->i.maxs, ent->i.origin, absmaxs ); + //unlink the entity + AAS_UnlinkFromAreas( ent->areas ); + //relink the entity to the AAS areas (use the larges bbox) + ent->areas = AAS_LinkEntityClientBBox( absmins, absmaxs, entnum, PRESENCE_NORMAL ); + //unlink the entity from the BSP leaves + AAS_UnlinkFromBSPLeaves( ent->leaves ); + //link the entity to the world BSP tree + ent->leaves = AAS_BSPLinkEntity( absmins, absmaxs, entnum, 0 ); + } //end if + } //end if + return BLERR_NOERROR; +} //end of the function AAS_UpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityInfo( int entnum, aas_entityinfo_t *info ) { + if ( !( *defaultaasworld ).initialized ) { + botimport.Print( PRT_FATAL, "AAS_EntityInfo: (*defaultaasworld) not initialized\n" ); + memset( info, 0, sizeof( aas_entityinfo_t ) ); + return; + } //end if + + if ( entnum < 0 || entnum >= ( *defaultaasworld ).maxentities ) { + botimport.Print( PRT_FATAL, "AAS_EntityInfo: entnum %d out of range\n", entnum ); + memset( info, 0, sizeof( aas_entityinfo_t ) ); + return; + } //end if + + memcpy( info, &( *defaultaasworld ).entities[entnum].i, sizeof( aas_entityinfo_t ) ); +} //end of the function AAS_EntityInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityOrigin( int entnum, vec3_t origin ) { + if ( entnum < 0 || entnum >= ( *defaultaasworld ).maxentities ) { + botimport.Print( PRT_FATAL, "AAS_EntityOrigin: entnum %d out of range\n", entnum ); + VectorClear( origin ); + return; + } //end if + + VectorCopy( ( *defaultaasworld ).entities[entnum].i.origin, origin ); +} //end of the function AAS_EntityOrigin +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelindex( int entnum ) { + if ( entnum < 0 || entnum >= ( *defaultaasworld ).maxentities ) { + botimport.Print( PRT_FATAL, "AAS_EntityModelindex: entnum %d out of range\n", entnum ); + return 0; + } //end if + return ( *defaultaasworld ).entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelindex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityType( int entnum ) { + if ( !( *defaultaasworld ).initialized ) { + return 0; + } + + if ( entnum < 0 || entnum >= ( *defaultaasworld ).maxentities ) { + botimport.Print( PRT_FATAL, "AAS_EntityType: entnum %d out of range\n", entnum ); + return 0; + } //end if + return ( *defaultaasworld ).entities[entnum].i.type; +} //end of the AAS_EntityType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelNum( int entnum ) { + if ( !( *defaultaasworld ).initialized ) { + return 0; + } + + if ( entnum < 0 || entnum >= ( *defaultaasworld ).maxentities ) { + botimport.Print( PRT_FATAL, "AAS_EntityModelNum: entnum %d out of range\n", entnum ); + return 0; + } //end if + return ( *defaultaasworld ).entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OriginOfEntityWithModelNum( int modelnum, vec3_t origin ) { + int i; + aas_entity_t *ent; + + for ( i = 0; i < ( *defaultaasworld ).maxentities; i++ ) + { + ent = &( *defaultaasworld ).entities[i]; + if ( ent->i.type == ET_MOVER ) { + if ( ent->i.modelindex == modelnum ) { + VectorCopy( ent->i.origin, origin ); + return qtrue; + } //end if + } + } //end for + return qfalse; +} //end of the function AAS_OriginOfEntityWithModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntitySize( int entnum, vec3_t mins, vec3_t maxs ) { + aas_entity_t *ent; + + if ( !( *defaultaasworld ).initialized ) { + return; + } + + if ( entnum < 0 || entnum >= ( *defaultaasworld ).maxentities ) { + botimport.Print( PRT_FATAL, "AAS_EntitySize: entnum %d out of range\n", entnum ); + return; + } //end if + + ent = &( *defaultaasworld ).entities[entnum]; + VectorCopy( ent->i.mins, mins ); + VectorCopy( ent->i.maxs, maxs ); +} //end of the function AAS_EntitySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityBSPData( int entnum, bsp_entdata_t *entdata ) { + aas_entity_t *ent; + + ent = &( *defaultaasworld ).entities[entnum]; + VectorCopy( ent->i.origin, entdata->origin ); + VectorCopy( ent->i.angles, entdata->angles ); + VectorAdd( ent->i.origin, ent->i.mins, entdata->absmins ); + VectorAdd( ent->i.origin, ent->i.maxs, entdata->absmaxs ); + entdata->solid = ent->i.solid; + entdata->modelnum = ent->i.modelindex - 1; +} //end of the function AAS_EntityBSPData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ResetEntityLinks( void ) { + int i; + for ( i = 0; i < ( *defaultaasworld ).maxentities; i++ ) + { + ( *defaultaasworld ).entities[i].areas = NULL; + ( *defaultaasworld ).entities[i].leaves = NULL; + } //end for +} //end of the function AAS_ResetEntityLinks +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InvalidateEntities( void ) { + int i; + for ( i = 0; i < ( *defaultaasworld ).maxentities; i++ ) + { + ( *defaultaasworld ).entities[i].i.valid = qfalse; + ( *defaultaasworld ).entities[i].i.number = i; + } //end for +} //end of the function AAS_InvalidateEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearestEntity( vec3_t origin, int modelindex ) { + int i, bestentnum; + float dist, bestdist; + aas_entity_t *ent; + vec3_t dir; + + bestentnum = 0; + bestdist = 99999; + for ( i = 0; i < ( *defaultaasworld ).maxentities; i++ ) + { + ent = &( *defaultaasworld ).entities[i]; + if ( ent->i.modelindex != modelindex ) { + continue; + } + VectorSubtract( ent->i.origin, origin, dir ); + if ( abs( dir[0] ) < 40 ) { + if ( abs( dir[1] ) < 40 ) { + dist = VectorLength( dir ); + if ( dist < bestdist ) { + bestdist = dist; + bestentnum = i; + } //end if + } //end if + } //end if + } //end for + return bestentnum; +} //end of the function AAS_NearestEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableEntityArea( int entnum ) { + aas_entity_t *ent; + + ent = &( *defaultaasworld ).entities[entnum]; + return AAS_BestReachableLinkArea( ent->areas ); +} //end of the function AAS_BestReachableEntityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextEntity( int entnum ) { + if ( !( *defaultaasworld ).loaded ) { + return 0; + } + + if ( entnum < 0 ) { + entnum = -1; + } + while ( ++entnum < ( *defaultaasworld ).maxentities ) + { + if ( ( *defaultaasworld ).entities[entnum].i.valid ) { + return entnum; + } + } //end while + return 0; +} //end of the function AAS_NextEntity + +// Ridah, used to find out if there is an entity touching the given area, if so, try and avoid it +/* +============ +AAS_EntityInArea +============ +*/ +int AAS_IsEntityInArea( int entnumIgnore, int entnumIgnore2, int areanum ) { + aas_link_t *link; + aas_entity_t *ent; +// int i; + + for ( link = ( *aasworld ).arealinkedentities[areanum]; link; link = link->next_ent ) + { + //ignore the pass entity + if ( link->entnum == entnumIgnore ) { + continue; + } + if ( link->entnum == entnumIgnore2 ) { + continue; + } + // + ent = &( *defaultaasworld ).entities[link->entnum]; + if ( !ent->i.valid ) { + continue; + } + if ( !ent->i.solid ) { + continue; + } + return qtrue; + } +/* + ent = (*defaultaasworld).entities; + for (i = 0; i < (*defaultaasworld).maxclients; i++, ent++) + { + if (!ent->i.valid) + continue; + if (!ent->i.solid) + continue; + if (i == entnumIgnore) + continue; + if (i == entnumIgnore2) + continue; + for (link = ent->areas; link; link = link->next_area) + { + if (link->areanum == areanum) + { + return qtrue; + } //end if + } //end for + } +*/ + return qfalse; +} + +/* +============= +AAS_SetAASBlockingEntity +============= +*/ +int AAS_EnableRoutingArea( int areanum, int enable ); +void AAS_SetAASBlockingEntity( vec3_t absmin, vec3_t absmax, qboolean blocking ) { + int areas[128]; + int numareas, i, w; + // + // check for resetting AAS blocking + // TTimo WTF?? qboolean blocking + // warning: comparison is always false due to limited range of data type + // if (VectorCompare( absmin, absmax ) && blocking < 0) { + if ( VectorCompare( absmin, absmax ) && !blocking ) { + for ( w = 0; w < MAX_AAS_WORLDS; w++ ) { + AAS_SetCurrentWorld( w ); + // + if ( !( *aasworld ).loaded ) { + continue; + } + // now clear blocking status + for ( i = 1; i < ( *aasworld ).numareas; i++ ) { + AAS_EnableRoutingArea( i, qtrue ); + } + } + // + return; + } + // + for ( w = 0; w < MAX_AAS_WORLDS; w++ ) { + AAS_SetCurrentWorld( w ); + // + if ( !( *aasworld ).loaded ) { + continue; + } + // grab the list of areas + numareas = AAS_BBoxAreas( absmin, absmax, areas, 128 ); + // now set their blocking status + for ( i = 0; i < numareas; i++ ) { + AAS_EnableRoutingArea( areas[i], !blocking ); + } + } +} diff --git a/src/botlib/be_aas_entity.h b/src/botlib/be_aas_entity.h new file mode 100644 index 0000000..90db7dd --- /dev/null +++ b/src/botlib/be_aas_entity.h @@ -0,0 +1,68 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_entity.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +//invalidates all entity infos +void AAS_InvalidateEntities( void ); +//resets the entity AAS and BSP links (sets areas and leaves pointers to NULL) +void AAS_ResetEntityLinks( void ); +//updates an entity +int AAS_UpdateEntity( int ent, bot_entitystate_t *state ); +//gives the entity data used for collision detection +void AAS_EntityBSPData( int entnum, bsp_entdata_t *entdata ); +#endif //AASINTERN + +//returns the size of the entity bounding box in mins and maxs +void AAS_EntitySize( int entnum, vec3_t mins, vec3_t maxs ); +//returns the BSP model number of the entity +int AAS_EntityModelNum( int entnum ); +//returns the origin of an entity with the given model number +int AAS_OriginOfEntityWithModelNum( int modelnum, vec3_t origin ); +//returns the best reachable area the entity is situated in +int AAS_BestReachableEntityArea( int entnum ); +//returns the info of the given entity +void AAS_EntityInfo( int entnum, aas_entityinfo_t *info ); +//returns the next entity +int AAS_NextEntity( int entnum ); +//returns the origin of the entity +void AAS_EntityOrigin( int entnum, vec3_t origin ); +//returns the entity type +int AAS_EntityType( int entnum ); +//returns the model index of the entity +int AAS_EntityModelindex( int entnum ); +// Ridah +int AAS_IsEntityInArea( int entnumIgnore, int entnumIgnore2, int areanum ); diff --git a/src/botlib/be_aas_file.c b/src/botlib/be_aas_file.c new file mode 100644 index 0000000..d8cf64c --- /dev/null +++ b/src/botlib/be_aas_file.c @@ -0,0 +1,662 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_file.c + * + * desc: AAS file loading/writing + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +//#define AASFILEDEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SwapAASData( void ) { + int i, j; + + // Ridah, no need to do anything if this OS doesn't need byte swapping + if ( LittleLong( 1 ) == 1 ) { + return; + } + // done. + + //bounding boxes + for ( i = 0; i < ( *aasworld ).numbboxes; i++ ) + { + ( *aasworld ).bboxes[i].presencetype = LittleLong( ( *aasworld ).bboxes[i].presencetype ); + ( *aasworld ).bboxes[i].flags = LittleLong( ( *aasworld ).bboxes[i].flags ); + for ( j = 0; j < 3; j++ ) + { + ( *aasworld ).bboxes[i].mins[j] = LittleLong( ( *aasworld ).bboxes[i].mins[j] ); + ( *aasworld ).bboxes[i].maxs[j] = LittleLong( ( *aasworld ).bboxes[i].maxs[j] ); + } //end for + } //end for + //vertexes + for ( i = 0; i < ( *aasworld ).numvertexes; i++ ) + { + for ( j = 0; j < 3; j++ ) + ( *aasworld ).vertexes[i][j] = LittleFloat( ( *aasworld ).vertexes[i][j] ); + } //end for + //planes + for ( i = 0; i < ( *aasworld ).numplanes; i++ ) + { + for ( j = 0; j < 3; j++ ) + ( *aasworld ).planes[i].normal[j] = LittleFloat( ( *aasworld ).planes[i].normal[j] ); + ( *aasworld ).planes[i].dist = LittleFloat( ( *aasworld ).planes[i].dist ); + ( *aasworld ).planes[i].type = LittleLong( ( *aasworld ).planes[i].type ); + } //end for + //edges + for ( i = 0; i < ( *aasworld ).numedges; i++ ) + { + ( *aasworld ).edges[i].v[0] = LittleLong( ( *aasworld ).edges[i].v[0] ); + ( *aasworld ).edges[i].v[1] = LittleLong( ( *aasworld ).edges[i].v[1] ); + } //end for + //edgeindex + for ( i = 0; i < ( *aasworld ).edgeindexsize; i++ ) + { + ( *aasworld ).edgeindex[i] = LittleLong( ( *aasworld ).edgeindex[i] ); + } //end for + //faces + for ( i = 0; i < ( *aasworld ).numfaces; i++ ) + { + ( *aasworld ).faces[i].planenum = LittleLong( ( *aasworld ).faces[i].planenum ); + ( *aasworld ).faces[i].faceflags = LittleLong( ( *aasworld ).faces[i].faceflags ); + ( *aasworld ).faces[i].numedges = LittleLong( ( *aasworld ).faces[i].numedges ); + ( *aasworld ).faces[i].firstedge = LittleLong( ( *aasworld ).faces[i].firstedge ); + ( *aasworld ).faces[i].frontarea = LittleLong( ( *aasworld ).faces[i].frontarea ); + ( *aasworld ).faces[i].backarea = LittleLong( ( *aasworld ).faces[i].backarea ); + } //end for + //face index + for ( i = 0; i < ( *aasworld ).faceindexsize; i++ ) + { + ( *aasworld ).faceindex[i] = LittleLong( ( *aasworld ).faceindex[i] ); + } //end for + //convex areas + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + ( *aasworld ).areas[i].areanum = LittleLong( ( *aasworld ).areas[i].areanum ); + ( *aasworld ).areas[i].numfaces = LittleLong( ( *aasworld ).areas[i].numfaces ); + ( *aasworld ).areas[i].firstface = LittleLong( ( *aasworld ).areas[i].firstface ); + for ( j = 0; j < 3; j++ ) + { + ( *aasworld ).areas[i].mins[j] = LittleFloat( ( *aasworld ).areas[i].mins[j] ); + ( *aasworld ).areas[i].maxs[j] = LittleFloat( ( *aasworld ).areas[i].maxs[j] ); + ( *aasworld ).areas[i].center[j] = LittleFloat( ( *aasworld ).areas[i].center[j] ); + } //end for + } //end for + //area settings + for ( i = 0; i < ( *aasworld ).numareasettings; i++ ) + { + ( *aasworld ).areasettings[i].contents = LittleLong( ( *aasworld ).areasettings[i].contents ); + ( *aasworld ).areasettings[i].areaflags = LittleLong( ( *aasworld ).areasettings[i].areaflags ); + ( *aasworld ).areasettings[i].presencetype = LittleLong( ( *aasworld ).areasettings[i].presencetype ); + ( *aasworld ).areasettings[i].cluster = LittleLong( ( *aasworld ).areasettings[i].cluster ); + ( *aasworld ).areasettings[i].clusterareanum = LittleLong( ( *aasworld ).areasettings[i].clusterareanum ); + ( *aasworld ).areasettings[i].numreachableareas = LittleLong( ( *aasworld ).areasettings[i].numreachableareas ); + ( *aasworld ).areasettings[i].firstreachablearea = LittleLong( ( *aasworld ).areasettings[i].firstreachablearea ); + ( *aasworld ).areasettings[i].groundsteepness = LittleLong( ( *aasworld ).areasettings[i].groundsteepness ); + } //end for + //area reachability + for ( i = 0; i < ( *aasworld ).reachabilitysize; i++ ) + { + ( *aasworld ).reachability[i].areanum = LittleLong( ( *aasworld ).reachability[i].areanum ); + ( *aasworld ).reachability[i].facenum = LittleLong( ( *aasworld ).reachability[i].facenum ); + ( *aasworld ).reachability[i].edgenum = LittleLong( ( *aasworld ).reachability[i].edgenum ); + for ( j = 0; j < 3; j++ ) + { + ( *aasworld ).reachability[i].start[j] = LittleFloat( ( *aasworld ).reachability[i].start[j] ); + ( *aasworld ).reachability[i].end[j] = LittleFloat( ( *aasworld ).reachability[i].end[j] ); + } //end for + ( *aasworld ).reachability[i].traveltype = LittleLong( ( *aasworld ).reachability[i].traveltype ); + ( *aasworld ).reachability[i].traveltime = LittleShort( ( *aasworld ).reachability[i].traveltime ); + } //end for + //nodes + for ( i = 0; i < ( *aasworld ).numnodes; i++ ) + { + ( *aasworld ).nodes[i].planenum = LittleLong( ( *aasworld ).nodes[i].planenum ); + ( *aasworld ).nodes[i].children[0] = LittleLong( ( *aasworld ).nodes[i].children[0] ); + ( *aasworld ).nodes[i].children[1] = LittleLong( ( *aasworld ).nodes[i].children[1] ); + } //end for + //cluster portals + for ( i = 0; i < ( *aasworld ).numportals; i++ ) + { + ( *aasworld ).portals[i].areanum = LittleLong( ( *aasworld ).portals[i].areanum ); + ( *aasworld ).portals[i].frontcluster = LittleLong( ( *aasworld ).portals[i].frontcluster ); + ( *aasworld ).portals[i].backcluster = LittleLong( ( *aasworld ).portals[i].backcluster ); + ( *aasworld ).portals[i].clusterareanum[0] = LittleLong( ( *aasworld ).portals[i].clusterareanum[0] ); + ( *aasworld ).portals[i].clusterareanum[1] = LittleLong( ( *aasworld ).portals[i].clusterareanum[1] ); + } //end for + //cluster portal index + for ( i = 0; i < ( *aasworld ).portalindexsize; i++ ) + { + ( *aasworld ).portalindex[i] = LittleLong( ( *aasworld ).portalindex[i] ); + } //end for + //cluster + for ( i = 0; i < ( *aasworld ).numclusters; i++ ) + { + ( *aasworld ).clusters[i].numareas = LittleLong( ( *aasworld ).clusters[i].numareas ); + ( *aasworld ).clusters[i].numreachabilityareas = LittleLong( ( *aasworld ).clusters[i].numreachabilityareas ); + ( *aasworld ).clusters[i].numportals = LittleLong( ( *aasworld ).clusters[i].numportals ); + ( *aasworld ).clusters[i].firstportal = LittleLong( ( *aasworld ).clusters[i].firstportal ); + } //end for +} //end of the function AAS_SwapAASData +//=========================================================================== +// dump the current loaded aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpAASData( void ) { + ( *aasworld ).numbboxes = 0; + if ( ( *aasworld ).bboxes ) { + FreeMemory( ( *aasworld ).bboxes ); + } + ( *aasworld ).bboxes = NULL; + ( *aasworld ).numvertexes = 0; + if ( ( *aasworld ).vertexes ) { + FreeMemory( ( *aasworld ).vertexes ); + } + ( *aasworld ).vertexes = NULL; + ( *aasworld ).numplanes = 0; + if ( ( *aasworld ).planes ) { + FreeMemory( ( *aasworld ).planes ); + } + ( *aasworld ).planes = NULL; + ( *aasworld ).numedges = 0; + if ( ( *aasworld ).edges ) { + FreeMemory( ( *aasworld ).edges ); + } + ( *aasworld ).edges = NULL; + ( *aasworld ).edgeindexsize = 0; + if ( ( *aasworld ).edgeindex ) { + FreeMemory( ( *aasworld ).edgeindex ); + } + ( *aasworld ).edgeindex = NULL; + ( *aasworld ).numfaces = 0; + if ( ( *aasworld ).faces ) { + FreeMemory( ( *aasworld ).faces ); + } + ( *aasworld ).faces = NULL; + ( *aasworld ).faceindexsize = 0; + if ( ( *aasworld ).faceindex ) { + FreeMemory( ( *aasworld ).faceindex ); + } + ( *aasworld ).faceindex = NULL; + ( *aasworld ).numareas = 0; + if ( ( *aasworld ).areas ) { + FreeMemory( ( *aasworld ).areas ); + } + ( *aasworld ).areas = NULL; + ( *aasworld ).numareasettings = 0; + if ( ( *aasworld ).areasettings ) { + FreeMemory( ( *aasworld ).areasettings ); + } + ( *aasworld ).areasettings = NULL; + ( *aasworld ).reachabilitysize = 0; + if ( ( *aasworld ).reachability ) { + FreeMemory( ( *aasworld ).reachability ); + } + ( *aasworld ).reachability = NULL; + ( *aasworld ).numnodes = 0; + if ( ( *aasworld ).nodes ) { + FreeMemory( ( *aasworld ).nodes ); + } + ( *aasworld ).nodes = NULL; + ( *aasworld ).numportals = 0; + if ( ( *aasworld ).portals ) { + FreeMemory( ( *aasworld ).portals ); + } + ( *aasworld ).portals = NULL; + ( *aasworld ).numportals = 0; + if ( ( *aasworld ).portalindex ) { + FreeMemory( ( *aasworld ).portalindex ); + } + ( *aasworld ).portalindex = NULL; + ( *aasworld ).portalindexsize = 0; + if ( ( *aasworld ).clusters ) { + FreeMemory( ( *aasworld ).clusters ); + } + ( *aasworld ).clusters = NULL; + ( *aasworld ).numclusters = 0; + // + ( *aasworld ).loaded = qfalse; + ( *aasworld ).initialized = qfalse; + ( *aasworld ).savefile = qfalse; +} //end of the function AAS_DumpAASData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef AASFILEDEBUG +void AAS_FileInfo( void ) { + int i, n, optimized; + + botimport.Print( PRT_MESSAGE, "version = %d\n", AASVERSION ); + botimport.Print( PRT_MESSAGE, "numvertexes = %d\n", ( *aasworld ).numvertexes ); + botimport.Print( PRT_MESSAGE, "numplanes = %d\n", ( *aasworld ).numplanes ); + botimport.Print( PRT_MESSAGE, "numedges = %d\n", ( *aasworld ).numedges ); + botimport.Print( PRT_MESSAGE, "edgeindexsize = %d\n", ( *aasworld ).edgeindexsize ); + botimport.Print( PRT_MESSAGE, "numfaces = %d\n", ( *aasworld ).numfaces ); + botimport.Print( PRT_MESSAGE, "faceindexsize = %d\n", ( *aasworld ).faceindexsize ); + botimport.Print( PRT_MESSAGE, "numareas = %d\n", ( *aasworld ).numareas ); + botimport.Print( PRT_MESSAGE, "numareasettings = %d\n", ( *aasworld ).numareasettings ); + botimport.Print( PRT_MESSAGE, "reachabilitysize = %d\n", ( *aasworld ).reachabilitysize ); + botimport.Print( PRT_MESSAGE, "numnodes = %d\n", ( *aasworld ).numnodes ); + botimport.Print( PRT_MESSAGE, "numportals = %d\n", ( *aasworld ).numportals ); + botimport.Print( PRT_MESSAGE, "portalindexsize = %d\n", ( *aasworld ).portalindexsize ); + botimport.Print( PRT_MESSAGE, "numclusters = %d\n", ( *aasworld ).numclusters ); + // + for ( n = 0, i = 0; i < ( *aasworld ).numareasettings; i++ ) + { + if ( ( *aasworld ).areasettings[i].areaflags & AREA_GROUNDED ) { + n++; + } + } //end for + botimport.Print( PRT_MESSAGE, "num grounded areas = %d\n", n ); + // + botimport.Print( PRT_MESSAGE, "planes size %d bytes\n", ( *aasworld ).numplanes * sizeof( aas_plane_t ) ); + botimport.Print( PRT_MESSAGE, "areas size %d bytes\n", ( *aasworld ).numareas * sizeof( aas_area_t ) ); + botimport.Print( PRT_MESSAGE, "areasettings size %d bytes\n", ( *aasworld ).numareasettings * sizeof( aas_areasettings_t ) ); + botimport.Print( PRT_MESSAGE, "nodes size %d bytes\n", ( *aasworld ).numnodes * sizeof( aas_node_t ) ); + botimport.Print( PRT_MESSAGE, "reachability size %d bytes\n", ( *aasworld ).reachabilitysize * sizeof( aas_reachability_t ) ); + botimport.Print( PRT_MESSAGE, "portals size %d bytes\n", ( *aasworld ).numportals * sizeof( aas_portal_t ) ); + botimport.Print( PRT_MESSAGE, "clusters size %d bytes\n", ( *aasworld ).numclusters * sizeof( aas_cluster_t ) ); + + optimized = ( *aasworld ).numplanes * sizeof( aas_plane_t ) + + ( *aasworld ).numareas * sizeof( aas_area_t ) + + ( *aasworld ).numareasettings * sizeof( aas_areasettings_t ) + + ( *aasworld ).numnodes * sizeof( aas_node_t ) + + ( *aasworld ).reachabilitysize * sizeof( aas_reachability_t ) + + ( *aasworld ).numportals * sizeof( aas_portal_t ) + + ( *aasworld ).numclusters * sizeof( aas_cluster_t ); + botimport.Print( PRT_MESSAGE, "optimzed size %d KB\n", optimized >> 10 ); +} //end of the function AAS_FileInfo +#endif //AASFILEDEBUG +//=========================================================================== +// allocate memory and read a lump of a AAS file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_LoadAASLump( fileHandle_t fp, int offset, int length, int *lastoffset ) { + char *buf; + // + if ( !length ) { + return NULL; + } + //seek to the data + if ( offset != *lastoffset ) { + botimport.Print( PRT_WARNING, "AAS file not sequentially read\n" ); + if ( botimport.FS_Seek( fp, offset, FS_SEEK_SET ) ) { + AAS_Error( "can't seek to aas lump\n" ); + AAS_DumpAASData(); + botimport.FS_FCloseFile( fp ); + return 0; + } //end if + } //end if + //allocate memory + buf = (char *) GetClearedHunkMemory( length + 1 ); + //read the data + if ( length ) { + botimport.FS_Read( buf, length, fp ); + *lastoffset += length; + } //end if + return buf; +} //end of the function AAS_LoadAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DData( unsigned char *data, int size ) { + int i; + + for ( i = 0; i < size; i++ ) + { + data[i] ^= (unsigned char) i * 119; + } //end for +} //end of the function AAS_DData +//=========================================================================== +// load an aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadAASFile( char *filename ) { + fileHandle_t fp; + aas_header_t header; + int offset, length, lastoffset; + + botimport.Print( PRT_MESSAGE, "trying to load %s\n", filename ); + //dump current loaded aas file + AAS_DumpAASData(); + //open the file + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if ( !fp ) { + AAS_Error( "can't open %s\n", filename ); + return BLERR_CANNOTOPENAASFILE; + } //end if + //read the header + botimport.FS_Read( &header, sizeof( aas_header_t ), fp ); + lastoffset = sizeof( aas_header_t ); + //check header identification + header.ident = LittleLong( header.ident ); + if ( header.ident != AASID ) { + AAS_Error( "%s is not an AAS file\n", filename ); + botimport.FS_FCloseFile( fp ); + return BLERR_WRONGAASFILEID; + } //end if + //check the version + header.version = LittleLong( header.version ); + // + if ( header.version != AASVERSION ) { + AAS_Error( "aas file %s is version %i, not %i\n", filename, header.version, AASVERSION ); + botimport.FS_FCloseFile( fp ); + return BLERR_WRONGAASFILEVERSION; + } //end if + // + if ( header.version == AASVERSION ) { + AAS_DData( (unsigned char *) &header + 8, sizeof( aas_header_t ) - 8 ); + } //end if + // + ( *aasworld ).bspchecksum = atoi( LibVarGetString( "sv_mapChecksum" ) ); + if ( LittleLong( header.bspchecksum ) != ( *aasworld ).bspchecksum ) { + AAS_Error( "aas file %s is out of date\n", filename ); + botimport.FS_FCloseFile( fp ); + return BLERR_WRONGAASFILEVERSION; + } //end if + //load the lumps: + //bounding boxes + offset = LittleLong( header.lumps[AASLUMP_BBOXES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_BBOXES].filelen ); + ( *aasworld ).bboxes = (aas_bbox_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numbboxes = length / sizeof( aas_bbox_t ); + if ( ( *aasworld ).numbboxes && !( *aasworld ).bboxes ) { + return BLERR_CANNOTREADAASLUMP; + } + //vertexes + offset = LittleLong( header.lumps[AASLUMP_VERTEXES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_VERTEXES].filelen ); + ( *aasworld ).vertexes = (aas_vertex_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numvertexes = length / sizeof( aas_vertex_t ); + if ( ( *aasworld ).numvertexes && !( *aasworld ).vertexes ) { + return BLERR_CANNOTREADAASLUMP; + } + //planes + offset = LittleLong( header.lumps[AASLUMP_PLANES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_PLANES].filelen ); + ( *aasworld ).planes = (aas_plane_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numplanes = length / sizeof( aas_plane_t ); + if ( ( *aasworld ).numplanes && !( *aasworld ).planes ) { + return BLERR_CANNOTREADAASLUMP; + } + //edges + offset = LittleLong( header.lumps[AASLUMP_EDGES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_EDGES].filelen ); + ( *aasworld ).edges = (aas_edge_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numedges = length / sizeof( aas_edge_t ); + if ( ( *aasworld ).numedges && !( *aasworld ).edges ) { + return BLERR_CANNOTREADAASLUMP; + } + //edgeindex + offset = LittleLong( header.lumps[AASLUMP_EDGEINDEX].fileofs ); + length = LittleLong( header.lumps[AASLUMP_EDGEINDEX].filelen ); + ( *aasworld ).edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).edgeindexsize = length / sizeof( aas_edgeindex_t ); + if ( ( *aasworld ).edgeindexsize && !( *aasworld ).edgeindex ) { + return BLERR_CANNOTREADAASLUMP; + } + //faces + offset = LittleLong( header.lumps[AASLUMP_FACES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_FACES].filelen ); + ( *aasworld ).faces = (aas_face_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numfaces = length / sizeof( aas_face_t ); + if ( ( *aasworld ).numfaces && !( *aasworld ).faces ) { + return BLERR_CANNOTREADAASLUMP; + } + //faceindex + offset = LittleLong( header.lumps[AASLUMP_FACEINDEX].fileofs ); + length = LittleLong( header.lumps[AASLUMP_FACEINDEX].filelen ); + ( *aasworld ).faceindex = (aas_faceindex_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).faceindexsize = length / sizeof( int ); + if ( ( *aasworld ).faceindexsize && !( *aasworld ).faceindex ) { + return BLERR_CANNOTREADAASLUMP; + } + //convex areas + offset = LittleLong( header.lumps[AASLUMP_AREAS].fileofs ); + length = LittleLong( header.lumps[AASLUMP_AREAS].filelen ); + ( *aasworld ).areas = (aas_area_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numareas = length / sizeof( aas_area_t ); + if ( ( *aasworld ).numareas && !( *aasworld ).areas ) { + return BLERR_CANNOTREADAASLUMP; + } + //area settings + offset = LittleLong( header.lumps[AASLUMP_AREASETTINGS].fileofs ); + length = LittleLong( header.lumps[AASLUMP_AREASETTINGS].filelen ); + ( *aasworld ).areasettings = (aas_areasettings_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numareasettings = length / sizeof( aas_areasettings_t ); + if ( ( *aasworld ).numareasettings && !( *aasworld ).areasettings ) { + return BLERR_CANNOTREADAASLUMP; + } + //reachability list + offset = LittleLong( header.lumps[AASLUMP_REACHABILITY].fileofs ); + length = LittleLong( header.lumps[AASLUMP_REACHABILITY].filelen ); + ( *aasworld ).reachability = (aas_reachability_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).reachabilitysize = length / sizeof( aas_reachability_t ); + if ( ( *aasworld ).reachabilitysize && !( *aasworld ).reachability ) { + return BLERR_CANNOTREADAASLUMP; + } + //nodes + offset = LittleLong( header.lumps[AASLUMP_NODES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_NODES].filelen ); + ( *aasworld ).nodes = (aas_node_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numnodes = length / sizeof( aas_node_t ); + if ( ( *aasworld ).numnodes && !( *aasworld ).nodes ) { + return BLERR_CANNOTREADAASLUMP; + } + //cluster portals + offset = LittleLong( header.lumps[AASLUMP_PORTALS].fileofs ); + length = LittleLong( header.lumps[AASLUMP_PORTALS].filelen ); + ( *aasworld ).portals = (aas_portal_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numportals = length / sizeof( aas_portal_t ); + if ( ( *aasworld ).numportals && !( *aasworld ).portals ) { + return BLERR_CANNOTREADAASLUMP; + } + //cluster portal index + offset = LittleLong( header.lumps[AASLUMP_PORTALINDEX].fileofs ); + length = LittleLong( header.lumps[AASLUMP_PORTALINDEX].filelen ); + ( *aasworld ).portalindex = (aas_portalindex_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).portalindexsize = length / sizeof( aas_portalindex_t ); + if ( ( *aasworld ).portalindexsize && !( *aasworld ).portalindex ) { + return BLERR_CANNOTREADAASLUMP; + } + //clusters + offset = LittleLong( header.lumps[AASLUMP_CLUSTERS].fileofs ); + length = LittleLong( header.lumps[AASLUMP_CLUSTERS].filelen ); + ( *aasworld ).clusters = (aas_cluster_t *) AAS_LoadAASLump( fp, offset, length, &lastoffset ); + ( *aasworld ).numclusters = length / sizeof( aas_cluster_t ); + if ( ( *aasworld ).numclusters && !( *aasworld ).clusters ) { + return BLERR_CANNOTREADAASLUMP; + } + //swap everything + AAS_SwapAASData(); + //aas file is loaded + ( *aasworld ).loaded = qtrue; + //close the file + botimport.FS_FCloseFile( fp ); + // +#ifdef AASFILEDEBUG + AAS_FileInfo(); +#endif //AASFILEDEBUG + // + return BLERR_NOERROR; +} //end of the function AAS_LoadAASFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int AAS_WriteAASLump_offset; + +int AAS_WriteAASLump( fileHandle_t fp, aas_header_t *h, int lumpnum, void *data, int length ) { + aas_lump_t *lump; + + lump = &h->lumps[lumpnum]; + + lump->fileofs = LittleLong( AAS_WriteAASLump_offset ); //LittleLong(ftell(fp)); + lump->filelen = LittleLong( length ); + + if ( length > 0 ) { + botimport.FS_Write( data, length, fp ); + } //end if + + AAS_WriteAASLump_offset += length; + + return qtrue; +} //end of the function AAS_WriteAASLump +//=========================================================================== +// aas data is useless after writing to file because it is byte swapped +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_WriteAASFile( char *filename ) { + aas_header_t header; + fileHandle_t fp; + + botimport.Print( PRT_MESSAGE, "writing %s\n", filename ); + //swap the aas data + AAS_SwapAASData(); + //initialize the file header + memset( &header, 0, sizeof( aas_header_t ) ); + header.ident = LittleLong( AASID ); + header.version = LittleLong( AASVERSION ); + header.bspchecksum = LittleLong( ( *aasworld ).bspchecksum ); + //open a new file + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if ( !fp ) { + botimport.Print( PRT_ERROR, "error opening %s\n", filename ); + return qfalse; + } //end if + //write the header + botimport.FS_Write( &header, sizeof( aas_header_t ), fp ); + AAS_WriteAASLump_offset = sizeof( aas_header_t ); + //add the data lumps to the file + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_BBOXES, ( *aasworld ).bboxes, + ( *aasworld ).numbboxes * sizeof( aas_bbox_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_VERTEXES, ( *aasworld ).vertexes, + ( *aasworld ).numvertexes * sizeof( aas_vertex_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_PLANES, ( *aasworld ).planes, + ( *aasworld ).numplanes * sizeof( aas_plane_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_EDGES, ( *aasworld ).edges, + ( *aasworld ).numedges * sizeof( aas_edge_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_EDGEINDEX, ( *aasworld ).edgeindex, + ( *aasworld ).edgeindexsize * sizeof( aas_edgeindex_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_FACES, ( *aasworld ).faces, + ( *aasworld ).numfaces * sizeof( aas_face_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_FACEINDEX, ( *aasworld ).faceindex, + ( *aasworld ).faceindexsize * sizeof( aas_faceindex_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_AREAS, ( *aasworld ).areas, + ( *aasworld ).numareas * sizeof( aas_area_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_AREASETTINGS, ( *aasworld ).areasettings, + ( *aasworld ).numareasettings * sizeof( aas_areasettings_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_REACHABILITY, ( *aasworld ).reachability, + ( *aasworld ).reachabilitysize * sizeof( aas_reachability_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_NODES, ( *aasworld ).nodes, + ( *aasworld ).numnodes * sizeof( aas_node_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_PORTALS, ( *aasworld ).portals, + ( *aasworld ).numportals * sizeof( aas_portal_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_PORTALINDEX, ( *aasworld ).portalindex, + ( *aasworld ).portalindexsize * sizeof( aas_portalindex_t ) ) ) { + return qfalse; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_CLUSTERS, ( *aasworld ).clusters, + ( *aasworld ).numclusters * sizeof( aas_cluster_t ) ) ) { + return qfalse; + } + //rewrite the header with the added lumps + botimport.FS_Seek( fp, 0, FS_SEEK_SET ); + botimport.FS_Write( &header, sizeof( aas_header_t ), fp ); + //close the file + botimport.FS_FCloseFile( fp ); + return qtrue; +} //end of the function AAS_WriteAASFile + diff --git a/src/botlib/be_aas_file.h b/src/botlib/be_aas_file.h new file mode 100644 index 0000000..102b795 --- /dev/null +++ b/src/botlib/be_aas_file.h @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_file.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +//loads the AAS file with the given name +int AAS_LoadAASFile( char *filename ); +//writes an AAS file with the given name +qboolean AAS_WriteAASFile( char *filename ); +//dumps the loaded AAS data +void AAS_DumpAASData( void ); +//print AAS file information +void AAS_FileInfo( void ); +#endif //AASINTERN + diff --git a/src/botlib/be_aas_funcs.h b/src/botlib/be_aas_funcs.h new file mode 100644 index 0000000..ffd9546 --- /dev/null +++ b/src/botlib/be_aas_funcs.h @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_funcs.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +// Ridah, route-tables +#include "be_aas_routetable.h" + +#endif //BSPCINCLUDE diff --git a/src/botlib/be_aas_main.c b/src/botlib/be_aas_main.c new file mode 100644 index 0000000..925f221 --- /dev/null +++ b/src/botlib/be_aas_main.c @@ -0,0 +1,486 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_main.c + * + * desc: AAS + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +aas_t aasworlds[MAX_AAS_WORLDS]; + +aas_t *aasworld; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL AAS_Error( char *fmt, ... ) { + char str[1024]; + va_list arglist; + + va_start( arglist, fmt ); + vsprintf( str, fmt, arglist ); + va_end( arglist ); + botimport.Print( PRT_FATAL, str ); +} //end of the function AAS_Error + +// Ridah, multiple AAS worlds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetCurrentWorld( int index ) { + if ( index >= MAX_AAS_WORLDS || index < 0 ) { + AAS_Error( "AAS_SetCurrentWorld: index out of range\n" ); + return; + } + + // set the current world pointer + aasworld = &aasworlds[index]; +} +// done. + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_StringFromIndex( char *indexname, char *stringindex[], int numindexes, int index ) { + if ( !( *aasworld ).indexessetup ) { + botimport.Print( PRT_ERROR, "%s: index %d not setup\n", indexname, index ); + return ""; + } //end if + if ( index < 0 || index >= numindexes ) { + botimport.Print( PRT_ERROR, "%s: index %d out of range\n", indexname, index ); + return ""; + } //end if + if ( !stringindex[index] ) { + if ( index ) { + botimport.Print( PRT_ERROR, "%s: reference to unused index %d\n", indexname, index ); + } //end if + return ""; + } //end if + return stringindex[index]; +} //end of the function AAS_StringFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromString( char *indexname, char *stringindex[], int numindexes, char *string ) { + int i; + if ( !( *aasworld ).indexessetup ) { + botimport.Print( PRT_ERROR, "%s: index not setup \"%s\"\n", indexname, string ); + return 0; + } //end if + for ( i = 0; i < numindexes; i++ ) + { + if ( !stringindex[i] ) { + continue; + } + if ( !Q_stricmp( stringindex[i], string ) ) { + return i; + } + } //end for + return 0; +} //end of the function AAS_IndexFromString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_ModelFromIndex( int index ) { +// return AAS_StringFromIndex("ModelFromIndex", &(*aasworld).configstrings[CS_MODELS], MAX_MODELS, index); + return 0; // removed so the CS_ defines could be removed from be_aas_def.h +} //end of the function AAS_ModelFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromModel( char *modelname ) { +// return AAS_IndexFromString("IndexFromModel", &(*aasworld).configstrings[CS_MODELS], MAX_MODELS, modelname); + return 0; // removed so the CS_ defines could be removed from be_aas_def.h +} //end of the function AAS_IndexFromModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateStringIndexes( int numconfigstrings, char *configstrings[] ) { + int i; + //set string pointers and copy the strings + for ( i = 0; i < numconfigstrings; i++ ) + { + if ( configstrings[i] ) { + //if ((*aasworld).configstrings[i]) FreeMemory((*aasworld).configstrings[i]); + ( *aasworld ).configstrings[i] = (char *) GetMemory( strlen( configstrings[i] ) + 1 ); + strcpy( ( *aasworld ).configstrings[i], configstrings[i] ); + } //end if + } //end for + ( *aasworld ).indexessetup = qtrue; +} //end of the function AAS_UpdateStringIndexes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Loaded( void ) { + return ( *aasworld ).loaded; +} //end of the function AAS_Loaded +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Initialized( void ) { + return ( *aasworld ).initialized; +} //end of the function AAS_Initialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetInitialized( void ) { + ( *aasworld ).initialized = qtrue; + botimport.Print( PRT_MESSAGE, "AAS initialized.\n" ); +#ifdef DEBUG + //create all the routing cache + //AAS_CreateAllRoutingCache(); + // + //AAS_RoutingInfo(); +#endif + + // Ridah, build/load the route-table + AAS_RT_BuildRouteTable(); + // done. + +} //end of the function AAS_SetInitialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ContinueInit( float time ) { + //if no AAS file loaded + if ( !( *aasworld ).loaded ) { + return; + } + //if AAS is already initialized + if ( ( *aasworld ).initialized ) { + return; + } + //calculate reachability, if not finished return + if ( AAS_ContinueInitReachability( time ) ) { + return; + } + //initialize clustering for the new map + AAS_InitClustering(); + //if reachability has been calculated and an AAS file should be written + //or there is a forced data optimization + if ( ( *aasworld ).savefile || ( (int)LibVarGetValue( "forcewrite" ) ) ) { + //optimize the AAS data + if ( !( (int)LibVarValue( "nooptimize", "1" ) ) ) { + AAS_Optimize(); + } + //save the AAS file + if ( AAS_WriteAASFile( ( *aasworld ).filename ) ) { + botimport.Print( PRT_MESSAGE, "%s written succesfully\n", ( *aasworld ).filename ); + } //end if + else + { + botimport.Print( PRT_ERROR, "couldn't write %s\n", ( *aasworld ).filename ); + } //end else + } //end if + //initialize the routing + AAS_InitRouting(); + //at this point AAS is initialized + AAS_SetInitialized(); +} //end of the function AAS_ContinueInit +//=========================================================================== +// called at the start of every frame +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StartFrame( float time ) { + // Ridah, do each of the aasworlds + int i; + + for ( i = 0; i < MAX_AAS_WORLDS; i++ ) + { + AAS_SetCurrentWorld( i ); + + ( *aasworld ).time = time; + //invalidate the entities + AAS_InvalidateEntities(); + //initialize AAS + AAS_ContinueInit( time ); + // + ( *aasworld ).frameroutingupdates = 0; + // + /* Ridah, disabled for speed + if (LibVarGetValue("showcacheupdates")) + { + AAS_RoutingInfo(); + LibVarSet("showcacheupdates", "0"); + } //end if + if (LibVarGetValue("showmemoryusage")) + { + PrintUsedMemorySize(); + LibVarSet("showmemoryusage", "0"); + } //end if + if (LibVarGetValue("memorydump")) + { + PrintMemoryLabels(); + LibVarSet("memorydump", "0"); + } //end if + */ + } //end if + ( *aasworld ).numframes++; + return BLERR_NOERROR; +} //end of the function AAS_StartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_Time( void ) { + return ( *aasworld ).time; +} //end of the function AAS_Time +//=========================================================================== +// basedir = Quake2 console basedir +// gamedir = Quake2 console gamedir +// mapname = name of the map without extension (.bsp) +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadFiles( const char *mapname ) { + int errnum; + char aasfile[MAX_PATH]; +// char bspfile[MAX_PATH]; + + strcpy( ( *aasworld ).mapname, mapname ); + //NOTE: first reset the entity links into the AAS areas and BSP leaves + // the AAS link heap and BSP link heap are reset after respectively the + // AAS file and BSP file are loaded + AAS_ResetEntityLinks(); + // + + // load bsp info + AAS_LoadBSPFile(); + + //load the aas file + Com_sprintf( aasfile, MAX_PATH, "maps/%s.aas", mapname ); + errnum = AAS_LoadAASFile( aasfile ); + if ( errnum != BLERR_NOERROR ) { + return errnum; + } + + botimport.Print( PRT_MESSAGE, "loaded %s\n", aasfile ); + strncpy( ( *aasworld ).filename, aasfile, MAX_PATH ); + return BLERR_NOERROR; +} //end of the function AAS_LoadFiles +//=========================================================================== +// called everytime a map changes +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +// Ridah, modified this for multiple AAS files + +int AAS_LoadMap( const char *mapname ) { + int errnum; + int i; + char this_mapname[256], intstr[4]; + qboolean loaded = qfalse; + int missingErrNum = 0; + + for ( i = 0; i < MAX_AAS_WORLDS; i++ ) + { + AAS_SetCurrentWorld( i ); + + strncpy( this_mapname, mapname, 256 ); + strncat( this_mapname, "_b", 256 ); + sprintf( intstr, "%i", i ); + strncat( this_mapname, intstr, 256 ); + + //if no mapname is provided then the string indexes are updated + if ( !mapname ) { + return 0; + } //end if + // + ( *aasworld ).initialized = qfalse; + //NOTE: free the routing caches before loading a new map because + // to free the caches the old number of areas, number of clusters + // and number of areas in a clusters must be available + AAS_FreeRoutingCaches(); + //load the map + errnum = AAS_LoadFiles( this_mapname ); + if ( errnum != BLERR_NOERROR ) { + ( *aasworld ).loaded = qfalse; + // RF, we are allowed to skip one of the files, but not both + //return errnum; + missingErrNum = errnum; + continue; + } //end if + // + loaded = qtrue; + // + AAS_InitSettings(); + //initialize the AAS link heap for the new map + AAS_InitAASLinkHeap(); + //initialize the AAS linked entities for the new map + AAS_InitAASLinkedEntities(); + //initialize reachability for the new map + AAS_InitReachability(); + //initialize the alternative routing + AAS_InitAlternativeRouting(); + } + + if ( !loaded ) { + return missingErrNum; + } + + //everything went ok + return 0; +} //end of the function AAS_LoadMap + +// done. + +//=========================================================================== +// called when the library is first loaded +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Setup( void ) { + // Ridah, just use the default world for entities + AAS_SetCurrentWorld( 0 ); + + ( *aasworlds ).maxclients = (int) LibVarValue( "maxclients", "128" ); + ( *aasworlds ).maxentities = (int) LibVarValue( "maxentities", "1024" ); + //allocate memory for the entities + if ( ( *aasworld ).entities ) { + FreeMemory( ( *aasworld ).entities ); + } + ( *aasworld ).entities = (aas_entity_t *) GetClearedHunkMemory( ( *aasworld ).maxentities * sizeof( aas_entity_t ) ); + //invalidate all the entities + AAS_InvalidateEntities(); + + //force some recalculations + //LibVarSet("forceclustering", "1"); //force clustering calculation + //LibVarSet("forcereachability", "1"); //force reachability calculation + ( *aasworld ).numframes = 0; + return BLERR_NOERROR; +} //end of the function AAS_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Shutdown( void ) { + // Ridah, do each of the worlds + int i; + + for ( i = 0; i < MAX_AAS_WORLDS; i++ ) + { + AAS_SetCurrentWorld( i ); + + // Ridah, kill the route-table data + AAS_RT_ShutdownRouteTable(); + + AAS_ShutdownAlternativeRouting(); + AAS_DumpBSPData(); + //free routing caches + AAS_FreeRoutingCaches(); + //free aas link heap + AAS_FreeAASLinkHeap(); + //free aas linked entities + AAS_FreeAASLinkedEntities(); + //free the aas data + AAS_DumpAASData(); + + if ( i == 0 ) { + //free the entities + if ( ( *aasworld ).entities ) { + FreeMemory( ( *aasworld ).entities ); + } + } + + //clear the (*aasworld) structure + memset( &( *aasworld ), 0, sizeof( aas_t ) ); + //aas has not been initialized + ( *aasworld ).initialized = qfalse; + } + + //NOTE: as soon as a new .bsp file is loaded the .bsp file memory is + // freed an reallocated, so there's no need to free that memory here + //print shutdown + botimport.Print( PRT_MESSAGE, "AAS shutdown.\n" ); +} //end of the function AAS_Shutdown diff --git a/src/botlib/be_aas_main.h b/src/botlib/be_aas_main.h new file mode 100644 index 0000000..f66699d --- /dev/null +++ b/src/botlib/be_aas_main.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_main.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN + +extern aas_t( *aasworld ); + +//AAS error message +void QDECL AAS_Error( char *fmt, ... ); +//set AAS initialized +void AAS_SetInitialized( void ); +//setup AAS with the given number of entities and clients +int AAS_Setup( void ); +//shutdown AAS +void AAS_Shutdown( void ); +//start a new map +int AAS_LoadMap( const char *mapname ); +//start a new time frame +int AAS_StartFrame( float time ); +#endif //AASINTERN + +//returns true if AAS is initialized +int AAS_Initialized( void ); +//returns true if the AAS file is loaded +int AAS_Loaded( void ); +//returns the model name from the given index +char *AAS_ModelFromIndex( int index ); +//returns the index from the given model name +int AAS_IndexFromModel( char *modelname ); +//returns the current time +float AAS_Time( void ); + +// Ridah +void AAS_SetCurrentWorld( int index ); +// done. diff --git a/src/botlib/be_aas_move.c b/src/botlib/be_aas_move.c new file mode 100644 index 0000000..a12d4ae --- /dev/null +++ b/src/botlib/be_aas_move.c @@ -0,0 +1,911 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_move.c + * + * desc: AAS + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +//#define BSPC + +extern botlib_import_t botimport; + +aas_settings_t aassettings; + +//#define AAS_MOVE_DEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_DropToFloor( vec3_t origin, vec3_t mins, vec3_t maxs ) { + vec3_t end; + bsp_trace_t trace; + + VectorCopy( origin, end ); + end[2] -= 100; + trace = AAS_Trace( origin, mins, maxs, end, 0, CONTENTS_SOLID ); + if ( trace.startsolid ) { + return qfalse; + } + VectorCopy( trace.endpos, origin ); + return qtrue; +} //end of the function AAS_DropToFloor +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitSettings( void ) { + aassettings.sv_friction = 6; + aassettings.sv_stopspeed = 100; + aassettings.sv_gravity = 800; + aassettings.sv_waterfriction = 1; + aassettings.sv_watergravity = 400; + aassettings.sv_maxvelocity = 320; + aassettings.sv_maxwalkvelocity = 300; + aassettings.sv_maxcrouchvelocity = 100; + aassettings.sv_maxswimvelocity = 150; + aassettings.sv_walkaccelerate = 10; + aassettings.sv_airaccelerate = 1; + aassettings.sv_swimaccelerate = 4; + aassettings.sv_maxstep = 18; + aassettings.sv_maxsteepness = 0.7; + aassettings.sv_maxwaterjump = 17; + // Ridah, calculate maxbarrier according to jumpvel and gravity + aassettings.sv_jumpvel = 270; + aassettings.sv_maxbarrier = -0.8 + ( 0.5 * aassettings.sv_gravity * ( aassettings.sv_jumpvel / aassettings.sv_gravity ) * ( aassettings.sv_jumpvel / aassettings.sv_gravity ) ); + // done. +} //end of the function AAS_InitSettings +//=========================================================================== +// returns qtrue if the bot is against a ladder +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AgainstLadder( vec3_t origin, int ms_areanum ) { + int areanum, i, facenum, side; + vec3_t org; + aas_plane_t *plane; + aas_face_t *face; + aas_area_t *area; + + VectorCopy( origin, org ); + areanum = AAS_PointAreaNum( org ); + if ( !areanum ) { + org[0] += 1; + areanum = AAS_PointAreaNum( org ); + if ( !areanum ) { + org[1] += 1; + areanum = AAS_PointAreaNum( org ); + if ( !areanum ) { + org[0] -= 2; + areanum = AAS_PointAreaNum( org ); + if ( !areanum ) { + org[1] -= 2; + areanum = AAS_PointAreaNum( org ); + } //end if + } //end if + } //end if + } //end if + //if in solid... wrrr shouldn't happen + //if (!areanum) return qfalse; + // RF, it does if they're in a monsterclip brush + if ( !areanum ) { + areanum = ms_areanum; + } + //if not in a ladder area + if ( !( ( *aasworld ).areasettings[areanum].areaflags & AREA_LADDER ) ) { + return qfalse; + } + //if a crouch only area + if ( !( ( *aasworld ).areasettings[areanum].presencetype & PRESENCE_NORMAL ) ) { + return qfalse; + } + // + area = &( *aasworld ).areas[areanum]; + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = ( *aasworld ).faceindex[area->firstface + i]; + side = facenum < 0; + face = &( *aasworld ).faces[abs( facenum )]; + //if the face isn't a ladder face + if ( !( face->faceflags & FACE_LADDER ) ) { + continue; + } + //get the plane the face is in + plane = &( *aasworld ).planes[face->planenum ^ side]; + //if the origin is pretty close to the plane + if ( abs( DotProduct( plane->normal, origin ) - plane->dist ) < 3 ) { + if ( AAS_PointInsideFace( abs( facenum ), origin, 0.1 ) ) { + return qtrue; + } + } //end if + } //end for + return qfalse; +} //end of the function AAS_AgainstLadder +//=========================================================================== +// returns qtrue if the bot is on the ground +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OnGround( vec3_t origin, int presencetype, int passent ) { + aas_trace_t trace; + vec3_t end, up = {0, 0, 1}; + aas_plane_t *plane; + + VectorCopy( origin, end ); + end[2] -= 10; + + trace = AAS_TraceClientBBox( origin, end, presencetype, passent ); + + //if in solid + if ( trace.startsolid ) { + return qfalse; + } + //if nothing hit at all + if ( trace.fraction >= 1.0 ) { + return qfalse; + } + //if too far from the hit plane + if ( origin[2] - trace.endpos[2] > 10 ) { + return qfalse; + } + //check if the plane isn't too steep + plane = AAS_PlaneFromNum( trace.planenum ); + if ( DotProduct( plane->normal, up ) < aassettings.sv_maxsteepness ) { + return qfalse; + } + //the bot is on the ground + return qtrue; +} //end of the function AAS_OnGround +//=========================================================================== +// returns qtrue if a bot at the given position is swimming +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Swimming( vec3_t origin ) { + vec3_t testorg; + + VectorCopy( origin, testorg ); + testorg[2] -= 2; + if ( AAS_PointContents( testorg ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { + return qtrue; + } + return qfalse; +} //end of the function AAS_Swimming +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void AAS_SetMovedir( vec3_t angles, vec3_t movedir ) { + if ( VectorCompare( angles, VEC_UP ) ) { + VectorCopy( MOVEDIR_UP, movedir ); + } //end if + else if ( VectorCompare( angles, VEC_DOWN ) ) { + VectorCopy( MOVEDIR_DOWN, movedir ); + } //end else if + else + { + AngleVectors( angles, movedir, NULL, NULL ); + } //end else +} //end of the function AAS_SetMovedir +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_JumpReachRunStart( aas_reachability_t *reach, vec3_t runstart ) { + vec3_t hordir, start, cmdmove; + aas_clientmove_t move; + + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + //start point + VectorCopy( reach->start, start ); + start[2] += 1; + //get command movement + VectorScale( hordir, 400, cmdmove ); + // + AAS_PredictClientMovement( &move, -1, start, PRESENCE_NORMAL, qtrue, + vec3_origin, cmdmove, 1, 2, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | SE_ENTERLAVA | + SE_HITGROUNDDAMAGE | SE_GAP, 0, qfalse ); + VectorCopy( move.endpos, runstart ); + //don't enter slime or lava and don't fall from too high + if ( move.stopevent & ( SE_ENTERLAVA | SE_HITGROUNDDAMAGE ) ) { //----(SA) modified since slime is no longer deadly +// if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + VectorCopy( start, runstart ); + } //end if +} //end of the function AAS_JumpReachRunStart +//=========================================================================== +// returns the Z velocity when rocket jumping at the origin +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_WeaponJumpZVelocity( vec3_t origin, float radiusdamage ) { + vec3_t kvel, v, start, end, forward, right, viewangles, dir; + float mass, knockback, points; + vec3_t rocketoffset = {8, 8, -8}; + vec3_t botmins = {-16, -16, -24}; + vec3_t botmaxs = {16, 16, 32}; + bsp_trace_t bsptrace; + + //look down (90 degrees) + viewangles[PITCH] = 90; + viewangles[YAW] = 0; + viewangles[ROLL] = 0; + //get the start point shooting from + VectorCopy( origin, start ); + start[2] += 8; //view offset Z + AngleVectors( viewangles, forward, right, NULL ); + start[0] += forward[0] * rocketoffset[0] + right[0] * rocketoffset[1]; + start[1] += forward[1] * rocketoffset[0] + right[1] * rocketoffset[1]; + start[2] += forward[2] * rocketoffset[0] + right[2] * rocketoffset[1] + rocketoffset[2]; + //end point of the trace + VectorMA( start, 500, forward, end ); + //trace a line to get the impact point + bsptrace = AAS_Trace( start, NULL, NULL, end, 1, CONTENTS_SOLID ); + //calculate the damage the bot will get from the rocket impact + VectorAdd( botmins, botmaxs, v ); + VectorMA( origin, 0.5, v, v ); + VectorSubtract( bsptrace.endpos, v, v ); + // + points = radiusdamage - 0.5 * VectorLength( v ); + if ( points < 0 ) { + points = 0; + } + //the owner of the rocket gets half the damage + points *= 0.5; + //mass of the bot (p_client.c: PutClientInServer) + mass = 200; + //knockback is the same as the damage points + knockback = points; + //direction of the damage (from trace.endpos to bot origin) + VectorSubtract( origin, bsptrace.endpos, dir ); + VectorNormalize( dir ); + //damage velocity + VectorScale( dir, 1600.0 * (float)knockback / mass, kvel ); //the rocket jump hack... + //rocket impact velocity + jump velocity + return kvel[2] + aassettings.sv_jumpvel; +} //end of the function AAS_WeaponJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_RocketJumpZVelocity( vec3_t origin ) { + //rocket radius damage is 120 (p_weapon.c: Weapon_RocketLauncher_Fire) + return AAS_WeaponJumpZVelocity( origin, 120 ); +} //end of the function AAS_RocketJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_BFGJumpZVelocity( vec3_t origin ) { + //bfg radius damage is 1000 (p_weapon.c: weapon_bfg_fire) + return AAS_WeaponJumpZVelocity( origin, 120 ); +} //end of the function AAS_BFGJumpZVelocity +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Accelerate( vec3_t velocity, float frametime, vec3_t wishdir, float wishspeed, float accel ) { + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct( velocity, wishdir ); + addspeed = wishspeed - currentspeed; + if ( addspeed <= 0 ) { + return; + } + accelspeed = accel * frametime * wishspeed; + if ( accelspeed > addspeed ) { + accelspeed = addspeed; + } + + for ( i = 0 ; i < 3 ; i++ ) { + velocity[i] += accelspeed * wishdir[i]; + } +} //end of the function AAS_Accelerate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AirControl( vec3_t start, vec3_t end, vec3_t velocity, vec3_t cmdmove ) { + vec3_t dir; + + VectorSubtract( end, start, dir ); +} //end of the function AAS_AirControl +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ApplyFriction( vec3_t vel, float friction, float stopspeed, + float frametime ) { + float speed, control, newspeed; + + //horizontal speed + speed = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ); + if ( speed ) { + control = speed < stopspeed ? stopspeed : speed; + newspeed = speed - frametime * control * friction; + if ( newspeed < 0 ) { + newspeed = 0; + } + newspeed /= speed; + vel[0] *= newspeed; + vel[1] *= newspeed; + } //end if +} //end of the function AAS_ApplyFriction +//=========================================================================== +// predicts the movement +// assumes regular bounding box sizes +// NOTE: out of water jumping is not included +// NOTE: grappling hook is not included +// +// Parameter: origin : origin to start with +// presencetype : presence type to start with +// velocity : velocity to start with +// cmdmove : client command movement +// cmdframes : number of frame cmdmove is valid +// maxframes : maximum number of predicted frames +// frametime : duration of one predicted frame +// stopevent : events that stop the prediction +// stopareanum : stop as soon as entered this area +// Returns: aas_clientmove_t +// Changes Globals: - +//=========================================================================== +int AAS_PredictClientMovement( struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize ) { + float sv_friction, sv_stopspeed, sv_gravity, sv_waterfriction; + float sv_watergravity; + float sv_walkaccelerate, sv_airaccelerate, sv_swimaccelerate; + float sv_maxwalkvelocity, sv_maxcrouchvelocity, sv_maxswimvelocity; + float sv_maxstep, sv_maxsteepness, sv_jumpvel, friction; + float gravity, delta, maxvel, wishspeed, accelerate; + //float velchange, newvel; + int n, i, j, pc, step, swimming, ax, crouch, event, jump_frame, areanum; + int areas[20], numareas; + vec3_t points[20]; + vec3_t org, end, feet, start, stepend, lastorg, wishdir; + vec3_t frame_test_vel, old_frame_test_vel, left_test_vel; + vec3_t up = {0, 0, 1}; + aas_plane_t *plane, *plane2; + aas_trace_t trace, steptrace; + + if ( frametime <= 0 ) { + frametime = 0.1; + } + // + sv_friction = aassettings.sv_friction; + sv_stopspeed = aassettings.sv_stopspeed; + sv_gravity = aassettings.sv_gravity; + sv_waterfriction = aassettings.sv_waterfriction; + sv_watergravity = aassettings.sv_watergravity; + sv_maxwalkvelocity = aassettings.sv_maxwalkvelocity; // * frametime; + sv_maxcrouchvelocity = aassettings.sv_maxcrouchvelocity; // * frametime; + sv_maxswimvelocity = aassettings.sv_maxswimvelocity; // * frametime; + sv_walkaccelerate = aassettings.sv_walkaccelerate; + sv_airaccelerate = aassettings.sv_airaccelerate; + sv_swimaccelerate = aassettings.sv_swimaccelerate; + sv_maxstep = aassettings.sv_maxstep; + sv_maxsteepness = aassettings.sv_maxsteepness; + sv_jumpvel = aassettings.sv_jumpvel * frametime; + // + memset( move, 0, sizeof( aas_clientmove_t ) ); + memset( &trace, 0, sizeof( aas_trace_t ) ); + //start at the current origin + VectorCopy( origin, org ); + org[2] += 0.25; + //velocity to test for the first frame + VectorScale( velocity, frametime, frame_test_vel ); + // + jump_frame = -1; + //predict a maximum of 'maxframes' ahead + for ( n = 0; n < maxframes; n++ ) + { + swimming = AAS_Swimming( org ); + //get gravity depending on swimming or not + gravity = swimming ? sv_watergravity : sv_gravity; + //apply gravity at the START of the frame + frame_test_vel[2] = frame_test_vel[2] - ( gravity * 0.1 * frametime ); + //if on the ground or swimming + if ( onground || swimming ) { + friction = swimming ? sv_friction : sv_waterfriction; + //apply friction + VectorScale( frame_test_vel, 1 / frametime, frame_test_vel ); + AAS_ApplyFriction( frame_test_vel, friction, sv_stopspeed, frametime ); + VectorScale( frame_test_vel, frametime, frame_test_vel ); + } //end if + crouch = qfalse; + //apply command movement + if ( n < cmdframes ) { + ax = 0; + maxvel = sv_maxwalkvelocity; + accelerate = sv_airaccelerate; + VectorCopy( cmdmove, wishdir ); + if ( onground ) { + if ( cmdmove[2] < -300 ) { + crouch = qtrue; + maxvel = sv_maxcrouchvelocity; + } //end if + //if not swimming and upmove is positive then jump + if ( !swimming && cmdmove[2] > 1 ) { + //jump velocity minus the gravity for one frame + 5 for safety + frame_test_vel[2] = sv_jumpvel - ( gravity * 0.1 * frametime ) + 5; + jump_frame = n; + //jumping so air accelerate + accelerate = sv_airaccelerate; + } //end if + else + { + accelerate = sv_walkaccelerate; + } //end else + ax = 2; + } //end if + if ( swimming ) { + maxvel = sv_maxswimvelocity; + accelerate = sv_swimaccelerate; + ax = 3; + } //end if + else + { + wishdir[2] = 0; + } //end else + // + wishspeed = VectorNormalize( wishdir ); + if ( wishspeed > maxvel ) { + wishspeed = maxvel; + } + VectorScale( frame_test_vel, 1 / frametime, frame_test_vel ); + AAS_Accelerate( frame_test_vel, frametime, wishdir, wishspeed, accelerate ); + VectorScale( frame_test_vel, frametime, frame_test_vel ); + /* + for (i = 0; i < ax; i++) + { + velchange = (cmdmove[i] * frametime) - frame_test_vel[i]; + if (velchange > sv_maxacceleration) velchange = sv_maxacceleration; + else if (velchange < -sv_maxacceleration) velchange = -sv_maxacceleration; + newvel = frame_test_vel[i] + velchange; + // + if (frame_test_vel[i] <= maxvel && newvel > maxvel) frame_test_vel[i] = maxvel; + else if (frame_test_vel[i] >= -maxvel && newvel < -maxvel) frame_test_vel[i] = -maxvel; + else frame_test_vel[i] = newvel; + } //end for + */ + } //end if + if ( crouch ) { + presencetype = PRESENCE_CROUCH; + } //end if + else if ( presencetype == PRESENCE_CROUCH ) { + if ( AAS_PointPresenceType( org ) & PRESENCE_NORMAL ) { + presencetype = PRESENCE_NORMAL; + } //end if + } //end else + //save the current origin + VectorCopy( org, lastorg ); + //move linear during one frame + VectorCopy( frame_test_vel, left_test_vel ); + j = 0; + do + { + VectorAdd( org, left_test_vel, end ); + //trace a bounding box + trace = AAS_TraceClientBBox( org, end, presencetype, entnum ); + // +//#ifdef AAS_MOVE_DEBUG + if ( visualize ) { + if ( trace.startsolid ) { + botimport.Print( PRT_MESSAGE, "PredictMovement: start solid\n" ); + } + AAS_DebugLine( org, trace.endpos, LINECOLOR_RED ); + } //end if +//#endif //AAS_MOVE_DEBUG + // + if ( stopevent & SE_ENTERAREA ) { + numareas = AAS_TraceAreas( org, trace.endpos, areas, points, 20 ); + for ( i = 0; i < numareas; i++ ) + { + if ( areas[i] == stopareanum ) { + VectorCopy( points[i], move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->trace = trace; + move->stopevent = SE_ENTERAREA; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end for + } //end if + //move the entity to the trace end point + VectorCopy( trace.endpos, org ); + //if there was a collision + if ( trace.fraction < 1.0 ) { + //get the plane the bounding box collided with + plane = AAS_PlaneFromNum( trace.planenum ); + // + if ( stopevent & SE_HITGROUNDAREA ) { + if ( DotProduct( plane->normal, up ) > sv_maxsteepness ) { + VectorCopy( org, start ); + start[2] += 0.5; + if ( AAS_PointAreaNum( start ) == stopareanum ) { + VectorCopy( start, move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->trace = trace; + move->stopevent = SE_HITGROUNDAREA; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + //assume there's no step + step = qfalse; + //if it is a vertical plane and the bot didn't jump recently + if ( plane->normal[2] == 0 && ( jump_frame < 0 || n - jump_frame > 2 ) ) { + //check for a step + VectorMA( org, -0.25, plane->normal, start ); + VectorCopy( start, stepend ); + start[2] += sv_maxstep; + steptrace = AAS_TraceClientBBox( start, stepend, presencetype, entnum ); + // + if ( !steptrace.startsolid ) { + plane2 = AAS_PlaneFromNum( steptrace.planenum ); + if ( DotProduct( plane2->normal, up ) > sv_maxsteepness ) { + VectorSubtract( end, steptrace.endpos, left_test_vel ); + left_test_vel[2] = 0; + frame_test_vel[2] = 0; +//#ifdef AAS_MOVE_DEBUG + if ( visualize ) { + if ( steptrace.endpos[2] - org[2] > 0.125 ) { + VectorCopy( org, start ); + start[2] = steptrace.endpos[2]; + AAS_DebugLine( org, start, LINECOLOR_BLUE ); + } //end if + } //end if +//#endif //AAS_MOVE_DEBUG + org[2] = steptrace.endpos[2]; + step = qtrue; + } //end if + } //end if + } //end if + // + if ( !step ) { + //velocity left to test for this frame is the projection + //of the current test velocity into the hit plane + VectorMA( left_test_vel, -DotProduct( left_test_vel, plane->normal ), + plane->normal, left_test_vel ); + //store the old velocity for landing check + VectorCopy( frame_test_vel, old_frame_test_vel ); + //test velocity for the next frame is the projection + //of the velocity of the current frame into the hit plane + VectorMA( frame_test_vel, -DotProduct( frame_test_vel, plane->normal ), + plane->normal, frame_test_vel ); + //check for a landing on an almost horizontal floor + if ( DotProduct( plane->normal, up ) > sv_maxsteepness ) { + onground = qtrue; + } //end if + if ( stopevent & SE_HITGROUNDDAMAGE ) { + delta = 0; + if ( old_frame_test_vel[2] < 0 && + frame_test_vel[2] > old_frame_test_vel[2] && + !onground ) { + delta = old_frame_test_vel[2]; + } //end if + else if ( onground ) { + delta = frame_test_vel[2] - old_frame_test_vel[2]; + } //end else + if ( delta ) { + delta = delta * 10; + delta = delta * delta * 0.0001; + if ( swimming ) { + delta = 0; + } + // never take falling damage if completely underwater + /* + if (ent->waterlevel == 3) return; + if (ent->waterlevel == 2) delta *= 0.25; + if (ent->waterlevel == 1) delta *= 0.5; + */ + if ( delta > 40 ) { + VectorCopy( org, move->endpos ); + VectorCopy( frame_test_vel, move->velocity ); + move->trace = trace; + move->stopevent = SE_HITGROUNDDAMAGE; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end if + } //end if + //extra check to prevent endless loop + if ( ++j > 20 ) { + return qfalse; + } + //while there is a plane hit + } while ( trace.fraction < 1.0 ); + //if going down + if ( frame_test_vel[2] <= 10 ) { + //check for a liquid at the feet of the bot + VectorCopy( org, feet ); + feet[2] -= 22; + pc = AAS_PointContents( feet ); + //get event from pc + event = SE_NONE; + if ( pc & CONTENTS_LAVA ) { + event |= SE_ENTERLAVA; + } + if ( pc & CONTENTS_SLIME ) { + event |= SE_ENTERSLIME; + } + if ( pc & CONTENTS_WATER ) { + event |= SE_ENTERWATER; + } + // + areanum = AAS_PointAreaNum( org ); + if ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_LAVA ) { + event |= SE_ENTERLAVA; + } + if ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_SLIME ) { + event |= SE_ENTERSLIME; + } + if ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_WATER ) { + event |= SE_ENTERWATER; + } + //if in lava or slime + if ( event & stopevent ) { + VectorCopy( org, move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->stopevent = event & stopevent; + move->presencetype = presencetype; + move->endcontents = pc; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + // + onground = AAS_OnGround( org, presencetype, entnum ); + //if onground and on the ground for at least one whole frame + if ( onground ) { + if ( stopevent & SE_HITGROUND ) { + VectorCopy( org, move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->trace = trace; + move->stopevent = SE_HITGROUND; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + else if ( stopevent & SE_LEAVEGROUND ) { + VectorCopy( org, move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->trace = trace; + move->stopevent = SE_LEAVEGROUND; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end else if + else if ( stopevent & SE_GAP ) { + aas_trace_t gaptrace; + + VectorCopy( org, start ); + VectorCopy( start, end ); + end[2] -= 48 + aassettings.sv_maxbarrier; + gaptrace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, -1 ); + //if solid is found the bot cannot walk any further and will not fall into a gap + if ( !gaptrace.startsolid ) { + //if it is a gap (lower than one step height) + if ( gaptrace.endpos[2] < org[2] - aassettings.sv_maxstep - 1 ) { + if ( !( AAS_PointContents( end ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) ) { //----(SA) modified since slime is no longer deadly +// if (!(AAS_PointContents(end) & CONTENTS_WATER)) + VectorCopy( lastorg, move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->trace = trace; + move->stopevent = SE_GAP; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end else if + if ( stopevent & SE_TOUCHJUMPPAD ) { + if ( ( *aasworld ).areasettings[AAS_PointAreaNum( org )].contents & AREACONTENTS_JUMPPAD ) { + VectorCopy( org, move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->trace = trace; + move->stopevent = SE_TOUCHJUMPPAD; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + if ( stopevent & SE_TOUCHTELEPORTER ) { + if ( ( *aasworld ).areasettings[AAS_PointAreaNum( org )].contents & AREACONTENTS_TELEPORTER ) { + VectorCopy( org, move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->trace = trace; + move->stopevent = SE_TOUCHTELEPORTER; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end for + // + VectorCopy( org, move->endpos ); + VectorScale( frame_test_vel, 1 / frametime, move->velocity ); + move->stopevent = SE_NONE; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + // + return qtrue; +} //end of the function AAS_PredictClientMovement +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction( int entnum, vec3_t origin, vec3_t dir ) { + vec3_t velocity, cmdmove; + aas_clientmove_t move; + + VectorClear( velocity ); + if ( !AAS_Swimming( origin ) ) { + dir[2] = 0; + } + VectorNormalize( dir ); + VectorScale( dir, 400, cmdmove ); + cmdmove[2] = 224; + AAS_ClearShownDebugLines(); + AAS_PredictClientMovement( &move, entnum, origin, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 13, 13, 0.1, SE_HITGROUND, 0, qtrue ); //SE_LEAVEGROUND); + if ( move.stopevent & SE_LEAVEGROUND ) { + botimport.Print( PRT_MESSAGE, "leave ground\n" ); + } //end if +} //end of the function TestMovementPrediction +//=========================================================================== +// calculates the horizontal velocity needed to perform a jump from start +// to end +// +// Parameter: zvel : z velocity for jump +// start : start position of jump +// end : end position of jump +// *speed : returned speed for jump +// Returns: qfalse if too high or too far from start to end +// Changes Globals: - +//=========================================================================== +int AAS_HorizontalVelocityForJump( float zvel, vec3_t start, vec3_t end, float *velocity ) { + float sv_gravity, sv_maxvelocity; + float maxjump, height2fall, t, top; + vec3_t dir; + + sv_gravity = aassettings.sv_gravity; + sv_maxvelocity = aassettings.sv_maxvelocity; + + //maximum height a player can jump with the given initial z velocity + maxjump = 0.5 * sv_gravity * ( zvel / sv_gravity ) * ( zvel / sv_gravity ); + //top of the parabolic jump + top = start[2] + maxjump; + //height the bot will fall from the top + height2fall = top - end[2]; + //if the goal is to high to jump to + if ( height2fall < 0 ) { + *velocity = sv_maxvelocity; + return 0; + } //end if + //time a player takes to fall the height + t = sqrt( height2fall / ( 0.5 * sv_gravity ) ); + //direction from start to end + VectorSubtract( end, start, dir ); + //calculate horizontal speed + *velocity = sqrt( dir[0] * dir[0] + dir[1] * dir[1] ) / ( t + zvel / sv_gravity ); + //the horizontal speed must be lower than the max speed + if ( *velocity > sv_maxvelocity ) { + *velocity = sv_maxvelocity; + return 0; + } //end if + return 1; +} //end of the function AAS_HorizontalVelocityForJump diff --git a/src/botlib/be_aas_move.h b/src/botlib/be_aas_move.h new file mode 100644 index 0000000..a3f3b9d --- /dev/null +++ b/src/botlib/be_aas_move.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_move.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +extern aas_settings_t aassettings; +#endif //AASINTERN + +//movement prediction +int AAS_PredictClientMovement( struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize ); +//returns true if on the ground at the given origin +int AAS_OnGround( vec3_t origin, int presencetype, int passent ); +//returns true if swimming at the given origin +int AAS_Swimming( vec3_t origin ); +//returns the jump reachability run start point +void AAS_JumpReachRunStart( struct aas_reachability_s *reach, vec3_t runstart ); +//returns true if against a ladder at the given origin +int AAS_AgainstLadder( vec3_t origin, int ms_areanum ); +//rocket jump Z velocity when rocket-jumping at origin +float AAS_RocketJumpZVelocity( vec3_t origin ); +//bfg jump Z velocity when bfg-jumping at origin +float AAS_BFGJumpZVelocity( vec3_t origin ); +//calculates the horizontal velocity needed for a jump and returns true this velocity could be calculated +int AAS_HorizontalVelocityForJump( float zvel, vec3_t start, vec3_t end, float *velocity ); +// +void AAS_SetMovedir( vec3_t angles, vec3_t movedir ); +// +int AAS_DropToFloor( vec3_t origin, vec3_t mins, vec3_t maxs ); +// +void AAS_InitSettings( void ); diff --git a/src/botlib/be_aas_optimize.c b/src/botlib/be_aas_optimize.c new file mode 100644 index 0000000..e196980 --- /dev/null +++ b/src/botlib/be_aas_optimize.c @@ -0,0 +1,337 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_optimize.c + * + * desc: decreases the .aas file size after the reachabilities have + * been calculated, just dumps all the faces, edges and vertexes + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_libvar.h" +//#include "l_utils.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +typedef struct optimized_s +{ + //vertixes + int numvertexes; + aas_vertex_t *vertexes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + // + int *vertexoptimizeindex; + int *edgeoptimizeindex; + int *faceoptimizeindex; +} optimized_t; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepEdge( aas_edge_t *edge ) { + return 1; +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeEdge( optimized_t *optimized, int edgenum ) { + int i, optedgenum; + aas_edge_t *edge, *optedge; + + edge = &( *aasworld ).edges[abs( edgenum )]; + if ( !AAS_KeepEdge( edge ) ) { + return 0; + } + + optedgenum = optimized->edgeoptimizeindex[abs( edgenum )]; + if ( optedgenum ) { + //keep the edge reversed sign + if ( edgenum > 0 ) { + return optedgenum; + } else { return -optedgenum;} + } //end if + + optedge = &optimized->edges[optimized->numedges]; + + for ( i = 0; i < 2; i++ ) + { + if ( optimized->vertexoptimizeindex[edge->v[i]] ) { + optedge->v[i] = optimized->vertexoptimizeindex[edge->v[i]]; + } //end if + else + { + VectorCopy( ( *aasworld ).vertexes[edge->v[i]], optimized->vertexes[optimized->numvertexes] ); + optedge->v[i] = optimized->numvertexes; + optimized->vertexoptimizeindex[edge->v[i]] = optimized->numvertexes; + optimized->numvertexes++; + } //end else + } //end for + optimized->edgeoptimizeindex[abs( edgenum )] = optimized->numedges; + optedgenum = optimized->numedges; + optimized->numedges++; + //keep the edge reversed sign + if ( edgenum > 0 ) { + return optedgenum; + } else { return -optedgenum;} +} //end of the function AAS_OptimizeEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepFace( aas_face_t *face ) { + if ( !( face->faceflags & FACE_LADDER ) ) { + return 0; + } else { return 1;} +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeFace( optimized_t *optimized, int facenum ) { + int i, edgenum, optedgenum, optfacenum; + aas_face_t *face, *optface; + + face = &( *aasworld ).faces[abs( facenum )]; + if ( !AAS_KeepFace( face ) ) { + return 0; + } + + optfacenum = optimized->faceoptimizeindex[abs( facenum )]; + if ( optfacenum ) { + //keep the face side sign + if ( facenum > 0 ) { + return optfacenum; + } else { return -optfacenum;} + } //end if + + optface = &optimized->faces[optimized->numfaces]; + memcpy( optface, face, sizeof( aas_face_t ) ); + + optface->numedges = 0; + optface->firstedge = optimized->edgeindexsize; + for ( i = 0; i < face->numedges; i++ ) + { + edgenum = ( *aasworld ).edgeindex[face->firstedge + i]; + optedgenum = AAS_OptimizeEdge( optimized, edgenum ); + if ( optedgenum ) { + optimized->edgeindex[optface->firstedge + optface->numedges] = optedgenum; + optface->numedges++; + optimized->edgeindexsize++; + } //end if + } //end for + optimized->faceoptimizeindex[abs( facenum )] = optimized->numfaces; + optfacenum = optimized->numfaces; + optimized->numfaces++; + //keep the face side sign + if ( facenum > 0 ) { + return optfacenum; + } else { return -optfacenum;} +} //end of the function AAS_OptimizeFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeArea( optimized_t *optimized, int areanum ) { + int i, facenum, optfacenum; + aas_area_t *area, *optarea; + + area = &( *aasworld ).areas[areanum]; + optarea = &optimized->areas[areanum]; + memcpy( optarea, area, sizeof( aas_area_t ) ); + + optarea->numfaces = 0; + optarea->firstface = optimized->faceindexsize; + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = ( *aasworld ).faceindex[area->firstface + i]; + optfacenum = AAS_OptimizeFace( optimized, facenum ); + if ( optfacenum ) { + optimized->faceindex[optarea->firstface + optarea->numfaces] = optfacenum; + optarea->numfaces++; + optimized->faceindexsize++; + } //end if + } //end for +} //end of the function AAS_OptimizeArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeAlloc( optimized_t *optimized ) { + optimized->vertexes = (aas_vertex_t *) GetClearedMemory( ( *aasworld ).numvertexes * sizeof( aas_vertex_t ) ); + optimized->numvertexes = 0; + optimized->edges = (aas_edge_t *) GetClearedMemory( ( *aasworld ).numedges * sizeof( aas_edge_t ) ); + optimized->numedges = 1; //edge zero is a dummy + optimized->edgeindex = (aas_edgeindex_t *) GetClearedMemory( ( *aasworld ).edgeindexsize * sizeof( aas_edgeindex_t ) ); + optimized->edgeindexsize = 0; + optimized->faces = (aas_face_t *) GetClearedMemory( ( *aasworld ).numfaces * sizeof( aas_face_t ) ); + optimized->numfaces = 1; //face zero is a dummy + optimized->faceindex = (aas_faceindex_t *) GetClearedMemory( ( *aasworld ).faceindexsize * sizeof( aas_faceindex_t ) ); + optimized->faceindexsize = 0; + optimized->areas = (aas_area_t *) GetClearedMemory( ( *aasworld ).numareas * sizeof( aas_area_t ) ); + optimized->numareas = ( *aasworld ).numareas; + // + optimized->vertexoptimizeindex = (int *) GetClearedMemory( ( *aasworld ).numvertexes * sizeof( int ) ); + optimized->edgeoptimizeindex = (int *) GetClearedMemory( ( *aasworld ).numedges * sizeof( int ) ); + optimized->faceoptimizeindex = (int *) GetClearedMemory( ( *aasworld ).numfaces * sizeof( int ) ); +} //end of the function AAS_OptimizeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeStore( optimized_t *optimized ) { + //store the optimized vertexes + if ( ( *aasworld ).vertexes ) { + FreeMemory( ( *aasworld ).vertexes ); + } + ( *aasworld ).vertexes = optimized->vertexes; + ( *aasworld ).numvertexes = optimized->numvertexes; + //store the optimized edges + if ( ( *aasworld ).edges ) { + FreeMemory( ( *aasworld ).edges ); + } + ( *aasworld ).edges = optimized->edges; + ( *aasworld ).numedges = optimized->numedges; + //store the optimized edge index + if ( ( *aasworld ).edgeindex ) { + FreeMemory( ( *aasworld ).edgeindex ); + } + ( *aasworld ).edgeindex = optimized->edgeindex; + ( *aasworld ).edgeindexsize = optimized->edgeindexsize; + //store the optimized faces + if ( ( *aasworld ).faces ) { + FreeMemory( ( *aasworld ).faces ); + } + ( *aasworld ).faces = optimized->faces; + ( *aasworld ).numfaces = optimized->numfaces; + //store the optimized face index + if ( ( *aasworld ).faceindex ) { + FreeMemory( ( *aasworld ).faceindex ); + } + ( *aasworld ).faceindex = optimized->faceindex; + ( *aasworld ).faceindexsize = optimized->faceindexsize; + //store the optimized areas + if ( ( *aasworld ).areas ) { + FreeMemory( ( *aasworld ).areas ); + } + ( *aasworld ).areas = optimized->areas; + ( *aasworld ).numareas = optimized->numareas; + //free optimize indexes + FreeMemory( optimized->vertexoptimizeindex ); + FreeMemory( optimized->edgeoptimizeindex ); + FreeMemory( optimized->faceoptimizeindex ); +} //end of the function AAS_OptimizeStore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Optimize( void ) { + int i, sign; + optimized_t optimized; + + AAS_OptimizeAlloc( &optimized ); + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + AAS_OptimizeArea( &optimized, i ); + } //end for + //reset the reachability face pointers + for ( i = 0; i < ( *aasworld ).reachabilitysize; i++ ) + { + //NOTE: for TRAVEL_ELEVATOR the facenum is the model number of + // the elevator + if ( ( *aasworld ).reachability[i].traveltype == TRAVEL_ELEVATOR ) { + continue; + } + //NOTE: for TRAVEL_JUMPPAD the facenum is the Z velocity and the edgenum is the hor velocity + if ( ( *aasworld ).reachability[i].traveltype == TRAVEL_JUMPPAD ) { + continue; + } + //NOTE: for TRAVEL_FUNCBOB the facenum and edgenum contain other coded information + if ( ( *aasworld ).reachability[i].traveltype == TRAVEL_FUNCBOB ) { + continue; + } + // + sign = ( *aasworld ).reachability[i].facenum; + ( *aasworld ).reachability[i].facenum = optimized.faceoptimizeindex[abs( ( *aasworld ).reachability[i].facenum )]; + if ( sign < 0 ) { + ( *aasworld ).reachability[i].facenum = -( *aasworld ).reachability[i].facenum; + } + sign = ( *aasworld ).reachability[i].edgenum; + ( *aasworld ).reachability[i].edgenum = optimized.edgeoptimizeindex[abs( ( *aasworld ).reachability[i].edgenum )]; + if ( sign < 0 ) { + ( *aasworld ).reachability[i].edgenum = -( *aasworld ).reachability[i].edgenum; + } + } //end for + //store the optimized AAS data into (*aasworld) + AAS_OptimizeStore( &optimized ); + //print some nice stuff :) + botimport.Print( PRT_MESSAGE, "AAS data optimized.\n" ); +} //end of the function AAS_Optimize diff --git a/src/botlib/be_aas_optimize.h b/src/botlib/be_aas_optimize.h new file mode 100644 index 0000000..edbb941 --- /dev/null +++ b/src/botlib/be_aas_optimize.h @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_optimize.h + * + * desc: AAS + * + * + *****************************************************************************/ + +void AAS_Optimize( void ); + diff --git a/src/botlib/be_aas_reach.c b/src/botlib/be_aas_reach.c new file mode 100644 index 0000000..e25a551 --- /dev/null +++ b/src/botlib/be_aas_reach.c @@ -0,0 +1,4471 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +/***************************************************************************** + * name: be_aas_reach.c + * + * desc: reachability calculations + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_libvar.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern int Sys_MilliSeconds( void ); + +//#include "../../../gladiator/bspc/aas_store.h" + +extern botlib_import_t botimport; + +//#define REACHDEBUG + +//NOTE: all travel times are in hundreth of a second +//maximum fall delta before getting damaged + +// Ridah, tweaked for Wolf AI +#define FALLDELTA_5DAMAGE 25 //40 +#define FALLDELTA_10DAMAGE 40 //60 +// done. + +//maximum number of reachability links +#define AAS_MAX_REACHABILITYSIZE 65536 +//number of areas reachability is calculated for each frame +#define REACHABILITYAREASPERCYCLE 15 +//number of units reachability points are placed inside the areas +#define INSIDEUNITS 2 + +// Ridah, tweaked this, routing issues around small areas +#define INSIDEUNITS_WALKEND 5 // original +//#define INSIDEUNITS_WALKEND 0.2 // new + +// Ridah, added this for better walking off ledges +#define INSIDEUNITS_WALKOFFLEDGEEND 15 + +#define INSIDEUNITS_WALKSTART 0.1 +#define INSIDEUNITS_WATERJUMP 15 +//travel times in hundreth of a second + +// Ridah, tweaked these for Wolf AI +#define WATERJUMP_TIME 700 //7 seconds +#define TELEPORT_TIME 50 //0.5 seconds +#define BARRIERJUMP_TIME 900 //fixed value? +#define STARTCROUCH_TIME 300 //3 sec to start crouching +#define STARTGRAPPLE_TIME 500 //using the grapple costs a lot of time +#define STARTWALKOFFLEDGE_TIME 300 //3 seconds +#define STARTJUMP_TIME 500 //3 seconds for jumping + +#define FALLDAMAGE_5_TIME 400 //extra travel time when falling hurts +#define FALLDAMAGE_10_TIME 900 //extra travel time when falling hurts +// done. + +//maximum height the bot may fall down when jumping +#define MAX_JUMPFALLHEIGHT 450 +//area flag used for weapon jumping +#define AREA_WEAPONJUMP 8192 //valid area to weapon jump to +//number of reachabilities of each type +int reach_swim; //swim +int reach_equalfloor; //walk on floors with equal height +int reach_step; //step up +int reach_walk; //walk of step +int reach_barrier; //jump up to a barrier +int reach_waterjump; //jump out of water +int reach_walkoffledge; //walk of a ledge +int reach_jump; //jump +int reach_ladder; //climb or descent a ladder +int reach_teleport; //teleport +int reach_elevator; //use an elevator +int reach_funcbob; //use a func bob +int reach_grapple; //grapple hook +int reach_doublejump; //double jump +int reach_rampjump; //ramp jump +int reach_strafejump; //strafe jump (just normal jump but further) +int reach_rocketjump; //rocket jump +int reach_bfgjump; //bfg jump +int reach_jumppad; //jump pads +//if true grapple reachabilities are skipped +int calcgrapplereach; +//linked reachability +typedef struct aas_lreachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime; //travel time of the inter area movement + // + struct aas_lreachability_s *next; +} aas_lreachability_t; +//temporary reachabilities +aas_lreachability_t *reachabilityheap; //heap with reachabilities +aas_lreachability_t *nextreachability; //next free reachability from the heap +aas_lreachability_t **areareachability; //reachability links for every area +int numlreachabilities; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableLinkArea( aas_link_t *areas ) { + aas_link_t *link; + + for ( link = areas; link; link = link->next_area ) + { + if ( AAS_AreaGrounded( link->areanum ) || AAS_AreaSwim( link->areanum ) ) { + return link->areanum; + } //end if + } //end for + // + for ( link = areas; link; link = link->next_area ) + { + if ( link->areanum ) { + return link->areanum; + } + //FIXME: cannot enable next line right now because the reachability + // does not have to be calculated when the level items are loaded + //if (AAS_AreaReachability(link->areanum)) return link->areanum; + } //end for + return 0; +} //end of the function AAS_BestReachableLinkArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableArea( vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin ) { + int areanum, i, j, k, l; + aas_link_t *areas; + vec3_t absmins, absmaxs; + //vec3_t bbmins, bbmaxs; + vec3_t start, end; + aas_trace_t trace; + + if ( !( *aasworld ).loaded ) { + botimport.Print( PRT_ERROR, "AAS_BestReachableArea: aas not loaded\n" ); + return 0; + } //end if + //find a point in an area + VectorCopy( origin, start ); + areanum = AAS_PointAreaNum( start ); + //while no area found fudge around a little + for ( i = 0; i < 5 && !areanum; i++ ) + { + for ( j = 0; j < 5 && !areanum; j++ ) + { + for ( k = -1; k <= 1 && !areanum; k++ ) + { + for ( l = -1; l <= 1 && !areanum; l++ ) + { + VectorCopy( origin, start ); + start[0] += (float) j * 4 * k; + start[1] += (float) j * 4 * l; + start[2] += (float) i * 4; + areanum = AAS_PointAreaNum( start ); + } //end for + } //end for + } //end for + } //end for + //if an area was found + if ( areanum ) { + //drop client bbox down and try again + VectorCopy( start, end ); + start[2] += 0.25; + end[2] -= 50; + trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, -1 ); + if ( !trace.startsolid ) { + areanum = AAS_PointAreaNum( trace.endpos ); + VectorCopy( trace.endpos, goalorigin ); + //FIXME: cannot enable next line right now because the reachability + // does not have to be calculated when the level items are loaded + //if the origin is in an area with reachability + //if (AAS_AreaReachability(areanum)) return areanum; + if ( areanum ) { + return areanum; + } + } //end if + else + { + //it can very well happen that the AAS_PointAreaNum function tells that + //a point is in an area and that starting a AAS_TraceClientBBox from that + //point will return trace.startsolid qtrue + /* + if (AAS_PointAreaNum(start)) + { + Log_Write("point %f %f %f in area %d but trace startsolid", start[0], start[1], start[2], areanum); + AAS_DrawPermanentCross(start, 4, LINECOLOR_RED); + } //end if + botimport.Print(PRT_MESSAGE, "AAS_BestReachableArea: start solid\n"); + */ + VectorCopy( start, goalorigin ); + return areanum; + } //end else + } //end if + // + //AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + //NOTE: the goal origin does not have to be in the goal area + // because the bot will have to move towards the item origin anyway + VectorCopy( origin, goalorigin ); + // + VectorAdd( origin, mins, absmins ); + VectorAdd( origin, maxs, absmaxs ); + //add bounding box size + //VectorSubtract(absmins, bbmaxs, absmins); + //VectorSubtract(absmaxs, bbmins, absmaxs); + //link an invalid (-1) entity + areas = AAS_AASLinkEntity( absmins, absmaxs, -1 ); + //get the reachable link arae + areanum = AAS_BestReachableLinkArea( areas ); + //unlink the invalid entity + AAS_UnlinkFromAreas( areas ); + // + return areanum; +} //end of the function AAS_BestReachableArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetupReachabilityHeap( void ) { + int i; + + reachabilityheap = (aas_lreachability_t *) GetClearedMemory( + AAS_MAX_REACHABILITYSIZE * sizeof( aas_lreachability_t ) ); + for ( i = 0; i < AAS_MAX_REACHABILITYSIZE - 1; i++ ) + { + reachabilityheap[i].next = &reachabilityheap[i + 1]; + } //end for + reachabilityheap[AAS_MAX_REACHABILITYSIZE - 1].next = NULL; + nextreachability = reachabilityheap; + numlreachabilities = 0; +} //end of the function AAS_InitReachabilityHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutDownReachabilityHeap( void ) { + FreeMemory( reachabilityheap ); + numlreachabilities = 0; +} //end of the function AAS_ShutDownReachabilityHeap +//=========================================================================== +// returns a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_AllocReachability( void ) { + aas_lreachability_t *r; + + if ( !nextreachability ) { + return NULL; + } + //make sure the error message only shows up once + if ( !nextreachability->next ) { + AAS_Error( "AAS_MAX_REACHABILITYSIZE" ); + } + // + r = nextreachability; + nextreachability = nextreachability->next; + numlreachabilities++; + return r; +} //end of the function AAS_AllocReachability +//=========================================================================== +// frees a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeReachability( aas_lreachability_t *lreach ) { + memset( lreach, 0, sizeof( aas_lreachability_t ) ); + + lreach->next = nextreachability; + nextreachability = lreach; + numlreachabilities--; +} //end of the function AAS_FreeReachability +//=========================================================================== +// returns qtrue if the area has reachability links +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachability( int areanum ) { + if ( areanum < 0 || areanum >= ( *aasworld ).numareas ) { + AAS_Error( "AAS_AreaReachability: areanum %d out of range", areanum ); + return 0; + } //end if + return ( *aasworld ).areasettings[areanum].numreachableareas; +} //end of the function AAS_AreaReachability +//=========================================================================== +// returns the surface area of the given face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FaceArea( aas_face_t *face ) { + int i, edgenum, side; + float total; + vec_t *v; + vec3_t d1, d2, cross; + aas_edge_t *edge; + + edgenum = ( *aasworld ).edgeindex[face->firstedge]; + side = edgenum < 0; + edge = &( *aasworld ).edges[abs( edgenum )]; + v = ( *aasworld ).vertexes[edge->v[side]]; + + total = 0; + for ( i = 1; i < face->numedges - 1; i++ ) + { + edgenum = ( *aasworld ).edgeindex[face->firstedge + i]; + side = edgenum < 0; + edge = &( *aasworld ).edges[abs( edgenum )]; + VectorSubtract( ( *aasworld ).vertexes[edge->v[side]], v, d1 ); + VectorSubtract( ( *aasworld ).vertexes[edge->v[!side]], v, d2 ); + CrossProduct( d1, d2, cross ); + total += 0.5 * VectorLength( cross ); + } //end for + return total; +} //end of the function AAS_FaceArea +//=========================================================================== +// returns the volume of an area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaVolume( int areanum ) { + int i, edgenum, facenum; + vec_t d, a, volume; + vec3_t corner; + aas_plane_t *plane; + aas_edge_t *edge; + aas_face_t *face; + aas_area_t *area; + + area = &( *aasworld ).areas[areanum]; + facenum = ( *aasworld ).faceindex[area->firstface]; + face = &( *aasworld ).faces[abs( facenum )]; + edgenum = ( *aasworld ).edgeindex[face->firstedge]; + edge = &( *aasworld ).edges[abs( edgenum )]; + // + VectorCopy( ( *aasworld ).vertexes[edge->v[0]], corner ); + + //make tetrahedrons to all other faces + volume = 0; + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = abs( ( *aasworld ).faceindex[area->firstface + i] ); + face = &( *aasworld ).faces[facenum]; + plane = &( *aasworld ).planes[face->planenum]; + d = -( DotProduct( corner, plane->normal ) - plane->dist ); + a = AAS_FaceArea( face ); + volume += d * a; + } //end for + + volume /= 3; + return volume; +} //end of the function AAS_AreaVolume +//=========================================================================== +// returns the surface area of all ground faces together of the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaGroundFaceArea( int areanum ) { + int i; + float total; + aas_area_t *area; + aas_face_t *face; + + total = 0; + area = &( *aasworld ).areas[areanum]; + for ( i = 0; i < area->numfaces; i++ ) + { + face = &( *aasworld ).faces[abs( ( *aasworld ).faceindex[area->firstface + i] )]; + if ( !( face->faceflags & FACE_GROUND ) ) { + continue; + } + // + total += AAS_FaceArea( face ); + } //end for + return total; +} //end of the function AAS_AreaGroundFaceArea +//=========================================================================== +// returns the center of a face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FaceCenter( int facenum, vec3_t center ) { + int i; + float scale; + aas_face_t *face; + aas_edge_t *edge; + + face = &( *aasworld ).faces[facenum]; + + VectorClear( center ); + for ( i = 0; i < face->numedges; i++ ) + { + edge = &( *aasworld ).edges[abs( ( *aasworld ).edgeindex[face->firstedge + i] )]; + VectorAdd( center, ( *aasworld ).vertexes[edge->v[0]], center ); + VectorAdd( center, ( *aasworld ).vertexes[edge->v[1]], center ); + } //end for + scale = 0.5 / face->numedges; + VectorScale( center, scale, center ); +} //end of the function AAS_FaceCenter +//=========================================================================== +// returns the maximum distance a player can fall before being damaged +// damage = deltavelocity*deltavelocity * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FallDamageDistance( void ) { + float maxzvelocity, gravity, t; + + maxzvelocity = sqrt( 30 * 10000 ); + gravity = aassettings.sv_gravity; + t = maxzvelocity / gravity; + return 0.5 * gravity * t * t; +} //end of the function AAS_FallDamageDistance +//=========================================================================== +// distance = 0.5 * gravity * t * t +// vel = t * gravity +// damage = vel * vel * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FallDelta( float distance ) { + float t, delta, gravity; + + gravity = aassettings.sv_gravity; + t = sqrt( fabs( distance ) * 2 / gravity ); + delta = t * gravity; + return delta * delta * 0.0001; +} //end of the function AAS_FallDelta +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpHeight( float sv_jumpvel ) { + float sv_gravity; + + sv_gravity = aassettings.sv_gravity; + //maximum height a player can jump with the given initial z velocity + return 0.5 * sv_gravity * ( sv_jumpvel / sv_gravity ) * ( sv_jumpvel / sv_gravity ); +} //end of the function MaxJumpHeight +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpDistance( float sv_jumpvel ) { + float sv_gravity, sv_maxvelocity, t; + + sv_gravity = aassettings.sv_gravity; + sv_maxvelocity = aassettings.sv_maxvelocity; + //time a player takes to fall the height + t = sqrt( MAX_JUMPFALLHEIGHT / ( 0.5 * sv_gravity ) ); + //maximum distance + return sv_maxvelocity * ( t + sv_jumpvel / sv_gravity ); +} //end of the function AAS_MaxJumpDistance +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCrouch( int areanum ) { + if ( !( ( *aasworld ).areasettings[areanum].presencetype & PRESENCE_NORMAL ) ) { + return qtrue; + } else { return qfalse;} +} //end of the function AAS_AreaCrouch +//=========================================================================== +// returns qtrue if it is possible to swim in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSwim( int areanum ) { + if ( ( *aasworld ).areasettings[areanum].areaflags & AREA_LIQUID ) { + return qtrue; + } else { return qfalse;} +} //end of the function AAS_AreaSwim +//=========================================================================== +// returns qtrue if the area contains a liquid +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLiquid( int areanum ) { + if ( ( *aasworld ).areasettings[areanum].areaflags & AREA_LIQUID ) { + return qtrue; + } else { return qfalse;} +} //end of the function AAS_AreaLiquid +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLava( int areanum ) { + return ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_LAVA ); +} //end of the function AAS_AreaLava +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSlime( int areanum ) { + return ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_SLIME ); +} //end of the function AAS_AreaSlime +//=========================================================================== +// returns qtrue if the area contains ground faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaGrounded( int areanum ) { + return ( ( *aasworld ).areasettings[areanum].areaflags & AREA_GROUNDED ); +} //end of the function AAS_AreaGround +//=========================================================================== +// returns true if the area contains ladder faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLadder( int areanum ) { + return ( ( *aasworld ).areasettings[areanum].areaflags & AREA_LADDER ); +} //end of the function AAS_AreaLadder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaJumpPad( int areanum ) { + return ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_JUMPPAD ); +} //end of the function AAS_AreaJumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTeleporter( int areanum ) { + return ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_TELEPORTER ); +} //end of the function AAS_AreaTeleporter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaDoNotEnter( int areanum ) { + return ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_DONOTENTER ); +} //end of the function AAS_AreaDoNotEnter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaDoNotEnterLarge( int areanum ) { + return ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_DONOTENTER_LARGE ); +} //end of the function AAS_AreaDoNotEnter +//=========================================================================== +// returns the time it takes perform a barrier jump +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_BarrierJumpTravelTime( void ) { + return aassettings.sv_jumpvel / ( aassettings.sv_gravity * 0.1 ); +} //end op the function AAS_BarrierJumpTravelTime +//=========================================================================== +// returns true if there already exists a reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ReachabilityExists( int area1num, int area2num ) { + aas_lreachability_t *r; + + for ( r = areareachability[area1num]; r; r = r->next ) + { + if ( r->areanum == area2num ) { + return qtrue; + } + } //end for + return qfalse; +} //end of the function AAS_ReachabilityExists +//=========================================================================== +// returns true if there is a solid just after the end point when going +// from start to end +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearbySolidOrGap( vec3_t start, vec3_t end ) { + vec3_t dir, testpoint; + int areanum; + + VectorSubtract( end, start, dir ); + dir[2] = 0; + VectorNormalize( dir ); + VectorMA( end, 48, dir, testpoint ); + + areanum = AAS_PointAreaNum( testpoint ); + if ( !areanum ) { + testpoint[2] += 16; + areanum = AAS_PointAreaNum( testpoint ); + if ( !areanum ) { + return qtrue; + } + } //end if + VectorMA( end, 64, dir, testpoint ); + areanum = AAS_PointAreaNum( testpoint ); + if ( areanum ) { + if ( !AAS_AreaSwim( areanum ) && !AAS_AreaGrounded( areanum ) ) { + return qtrue; + } + } //end if + return qfalse; +} //end of the function AAS_SolidGapTime +//=========================================================================== +// searches for swim reachabilities between adjacent areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Swim( int area1num, int area2num ) { + int i, j, face1num, face2num, side1; + aas_area_t *area1, *area2; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_face_t *face1; + aas_plane_t *plane; + vec3_t start; + + if ( !AAS_AreaSwim( area1num ) || !AAS_AreaSwim( area2num ) ) { + return qfalse; + } + //if the second area is crouch only + if ( !( ( *aasworld ).areasettings[area2num].presencetype & PRESENCE_NORMAL ) ) { + return qfalse; + } + + area1 = &( *aasworld ).areas[area1num]; + area2 = &( *aasworld ).areas[area2num]; + + //if the areas are not near anough + for ( i = 0; i < 3; i++ ) + { + if ( area1->mins[i] > area2->maxs[i] + 10 ) { + return qfalse; + } + if ( area1->maxs[i] < area2->mins[i] - 10 ) { + return qfalse; + } + } //end for + //find a shared face and create a reachability link + for ( i = 0; i < area1->numfaces; i++ ) + { + face1num = ( *aasworld ).faceindex[area1->firstface + i]; + side1 = face1num < 0; + face1num = abs( face1num ); + // + for ( j = 0; j < area2->numfaces; j++ ) + { + face2num = abs( ( *aasworld ).faceindex[area2->firstface + j] ); + // + if ( face1num == face2num ) { + AAS_FaceCenter( face1num, start ); + // + if ( AAS_PointContents( start ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { + // + face1 = &( *aasworld ).faces[face1num]; + areasettings = &( *aasworld ).areasettings[area1num]; + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = face1num; + lreach->edgenum = 0; + VectorCopy( start, lreach->start ); + plane = &( *aasworld ).planes[face1->planenum ^ side1]; + VectorMA( lreach->start, INSIDEUNITS, plane->normal, lreach->end ); + lreach->traveltype = TRAVEL_SWIM; + lreach->traveltime = 1; + //if the volume of the area is rather small + if ( AAS_AreaVolume( area2num ) < 800 ) { + lreach->traveltime += 200; + } + //if (!(AAS_PointContents(start) & MASK_WATER)) lreach->traveltime += 500; + //link the reachability + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + reach_swim++; + return qtrue; + } //end if + } //end if + } //end for + } //end for + return qfalse; +} //end of the function AAS_Reachability_Swim +//=========================================================================== +// searches for reachabilities between adjacent areas with equal floor +// heights +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_EqualFloorHeight( int area1num, int area2num ) { + int i, j, edgenum, edgenum1, edgenum2, foundreach, side; + float height, bestheight, length, bestlength; + vec3_t dir, start, end, normal, invgravity, gravitydirection = {0, 0, -1}; + vec3_t edgevec; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge; + aas_plane_t *plane2; + aas_lreachability_t lr, *lreach; + + if ( !AAS_AreaGrounded( area1num ) || !AAS_AreaGrounded( area2num ) ) { + return qfalse; + } + + area1 = &( *aasworld ).areas[area1num]; + area2 = &( *aasworld ).areas[area2num]; + //if the areas are not near anough in the x-y direction + for ( i = 0; i < 2; i++ ) + { + if ( area1->mins[i] > area2->maxs[i] + 10 ) { + return qfalse; + } + if ( area1->maxs[i] < area2->mins[i] - 10 ) { + return qfalse; + } + } //end for + //if area 2 is too high above area 1 + if ( area2->mins[2] > area1->maxs[2] ) { + return qfalse; + } + // + VectorCopy( gravitydirection, invgravity ); + VectorInverse( invgravity ); + // + bestheight = 99999; + bestlength = 0; + foundreach = qfalse; + memset( &lr, 0, sizeof( aas_lreachability_t ) ); //make the compiler happy + // + //check if the areas have ground faces with a common edge + //if existing use the lowest common edge for a reachability link + for ( i = 0; i < area1->numfaces; i++ ) + { + face1 = &( *aasworld ).faces[abs( ( *aasworld ).faceindex[area1->firstface + i] )]; + if ( !( face1->faceflags & FACE_GROUND ) ) { + continue; + } + // + for ( j = 0; j < area2->numfaces; j++ ) + { + face2 = &( *aasworld ).faces[abs( ( *aasworld ).faceindex[area2->firstface + j] )]; + if ( !( face2->faceflags & FACE_GROUND ) ) { + continue; + } + //if there is a common edge + for ( edgenum1 = 0; edgenum1 < face1->numedges; edgenum1++ ) + { + for ( edgenum2 = 0; edgenum2 < face2->numedges; edgenum2++ ) + { + if ( abs( ( *aasworld ).edgeindex[face1->firstedge + edgenum1] ) != + abs( ( *aasworld ).edgeindex[face2->firstedge + edgenum2] ) ) { + continue; + } + edgenum = ( *aasworld ).edgeindex[face1->firstedge + edgenum1]; + side = edgenum < 0; + edge = &( *aasworld ).edges[abs( edgenum )]; + //get the length of the edge + VectorSubtract( ( *aasworld ).vertexes[edge->v[1]], + ( *aasworld ).vertexes[edge->v[0]], dir ); + length = VectorLength( dir ); + //get the start point + VectorAdd( ( *aasworld ).vertexes[edge->v[0]], + ( *aasworld ).vertexes[edge->v[1]], start ); + VectorScale( start, 0.5, start ); + VectorCopy( start, end ); + //get the end point several units inside area2 + //and the start point several units inside area1 + //NOTE: normal is pointing into area2 because the + //face edges are stored counter clockwise + VectorSubtract( ( *aasworld ).vertexes[edge->v[side]], + ( *aasworld ).vertexes[edge->v[!side]], edgevec ); + plane2 = &( *aasworld ).planes[face2->planenum]; + CrossProduct( edgevec, plane2->normal, normal ); + VectorNormalize( normal ); + // + //VectorMA(start, -1, normal, start); + VectorMA( end, INSIDEUNITS_WALKEND, normal, end ); + VectorMA( start, INSIDEUNITS_WALKSTART, normal, start ); + end[2] += 0.125; + // + height = DotProduct( invgravity, start ); + //NOTE: if there's nearby solid or a gap area after this area + //disabled this crap + //if (AAS_NearbySolidOrGap(start, end)) height += 200; + //NOTE: disabled because it disables reachabilities to very small areas + //if (AAS_PointAreaNum(end) != area2num) continue; + //get the longest lowest edge + if ( height < bestheight || + ( height < bestheight + 1 && length > bestlength ) ) { + bestheight = height; + bestlength = length; + //create a new reachability link + lr.areanum = area2num; + lr.facenum = 0; + lr.edgenum = edgenum; + VectorCopy( start, lr.start ); + VectorCopy( end, lr.end ); + lr.traveltype = TRAVEL_WALK; + lr.traveltime = 1; + foundreach = qtrue; + } //end if + } //end for + } //end for + } //end for + } //end for + if ( foundreach ) { + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = lr.areanum; + lreach->facenum = lr.facenum; + lreach->edgenum = lr.edgenum; + VectorCopy( lr.start, lreach->start ); + VectorCopy( lr.end, lreach->end ); + lreach->traveltype = lr.traveltype; + lreach->traveltime = lr.traveltime; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //if going into a crouch area + if ( !AAS_AreaCrouch( area1num ) && AAS_AreaCrouch( area2num ) ) { + lreach->traveltime += STARTCROUCH_TIME; + } //end if + /* + //NOTE: if there's nearby solid or a gap area after this area + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_equalfloor++; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_Reachability_EqualFloorHeight +//=========================================================================== +// searches step, barrier, waterjump and walk off ledge reachabilities +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge( int area1num, int area2num ) { + int i, j, k, l, edge1num, edge2num; + int ground_bestarea2groundedgenum, ground_foundreach; + int water_bestarea2groundedgenum, water_foundreach; + int side1, area1swim, faceside1, groundface1num; + float dist, dist1, dist2, diff, invgravitydot, ortdot; + float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y; + float length, ground_bestlength, water_bestlength, ground_bestdist, water_bestdist; + vec3_t v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2; + vec3_t normal, ort, edgevec, start, end, dir; + vec3_t ground_beststart, ground_bestend, ground_bestnormal; + vec3_t water_beststart, water_bestend, water_bestnormal; + vec3_t invgravity = {0, 0, 1}; + vec3_t testpoint; + aas_plane_t *plane; + aas_area_t *area1, *area2; + aas_face_t *groundface1, *groundface2, *ground_bestface1, *water_bestface1; + aas_edge_t *edge1, *edge2; + aas_lreachability_t *lreach; + aas_trace_t trace; + + //must be able to walk or swim in the first area + if ( !AAS_AreaGrounded( area1num ) && !AAS_AreaSwim( area1num ) ) { + return qfalse; + } + // + if ( !AAS_AreaGrounded( area2num ) && !AAS_AreaSwim( area2num ) ) { + return qfalse; + } + // + area1 = &( *aasworld ).areas[area1num]; + area2 = &( *aasworld ).areas[area2num]; + //if the first area contains a liquid + area1swim = AAS_AreaSwim( area1num ); + //if the areas are not near anough in the x-y direction + for ( i = 0; i < 2; i++ ) + { + if ( area1->mins[i] > area2->maxs[i] + 10 ) { + return qfalse; + } + if ( area1->maxs[i] < area2->mins[i] - 10 ) { + return qfalse; + } + } //end for + // + ground_foundreach = qfalse; + ground_bestdist = 99999; + ground_bestlength = 0; + ground_bestarea2groundedgenum = 0; + // + water_foundreach = qfalse; + water_bestdist = 99999; + water_bestlength = 0; + water_bestarea2groundedgenum = 0; + // + for ( i = 0; i < area1->numfaces; i++ ) + { + groundface1num = ( *aasworld ).faceindex[area1->firstface + i]; + faceside1 = groundface1num < 0; + groundface1 = &( *aasworld ).faces[abs( groundface1num )]; + //if this isn't a ground face + if ( !( groundface1->faceflags & FACE_GROUND ) ) { + //if we can swim in the first area + if ( area1swim ) { + //face plane must be more or less horizontal + plane = &( *aasworld ).planes[groundface1->planenum ^ ( !faceside1 )]; + if ( DotProduct( plane->normal, invgravity ) < 0.7 ) { + continue; + } + } //end if + else + { + //if we can't swim in the area it must be a ground face + continue; + } //end else + } //end if + // + for ( k = 0; k < groundface1->numedges; k++ ) + { + edge1num = ( *aasworld ).edgeindex[groundface1->firstedge + k]; + side1 = ( edge1num < 0 ); + //NOTE: for water faces we must take the side area 1 is + // on into account because the face is shared and doesn't + // have to be oriented correctly + if ( !( groundface1->faceflags & FACE_GROUND ) ) { + side1 = ( side1 == faceside1 ); + } + edge1num = abs( edge1num ); + edge1 = &( *aasworld ).edges[edge1num]; + //vertexes of the edge + VectorCopy( ( *aasworld ).vertexes[edge1->v[!side1]], v1 ); + VectorCopy( ( *aasworld ).vertexes[edge1->v[side1]], v2 ); + //get a vertical plane through the edge + //NOTE: normal is pointing into area 2 because the + //face edges are stored counter clockwise + VectorSubtract( v2, v1, edgevec ); + CrossProduct( edgevec, invgravity, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, v1 ); + //check the faces from the second area + for ( j = 0; j < area2->numfaces; j++ ) + { + groundface2 = &( *aasworld ).faces[abs( ( *aasworld ).faceindex[area2->firstface + j] )]; + //must be a ground face + if ( !( groundface2->faceflags & FACE_GROUND ) ) { + continue; + } + //check the edges of this ground face + for ( l = 0; l < groundface2->numedges; l++ ) + { + edge2num = abs( ( *aasworld ).edgeindex[groundface2->firstedge + l] ); + edge2 = &( *aasworld ).edges[edge2num]; + //vertexes of the edge + VectorCopy( ( *aasworld ).vertexes[edge2->v[0]], v3 ); + VectorCopy( ( *aasworld ).vertexes[edge2->v[1]], v4 ); + //check the distance between the two points and the vertical plane + //through the edge of area1 + diff = DotProduct( normal, v3 ) - dist; + if ( diff < -0.1 || diff > 0.1 ) { + continue; + } + diff = DotProduct( normal, v4 ) - dist; + if ( diff < -0.1 || diff > 0.1 ) { + continue; + } + // + //project the two ground edges into the step side plane + //and calculate the shortest distance between the two + //edges if they overlap in the direction orthogonal to + //the gravity direction + CrossProduct( invgravity, normal, ort ); + invgravitydot = DotProduct( invgravity, invgravity ); + ortdot = DotProduct( ort, ort ); + //projection into the step plane + //NOTE: since gravity is vertical this is just the z coordinate + y1 = v1[2]; //DotProduct(v1, invgravity) / invgravitydot; + y2 = v2[2]; //DotProduct(v2, invgravity) / invgravitydot; + y3 = v3[2]; //DotProduct(v3, invgravity) / invgravitydot; + y4 = v4[2]; //DotProduct(v4, invgravity) / invgravitydot; + // + x1 = DotProduct( v1, ort ) / ortdot; + x2 = DotProduct( v2, ort ) / ortdot; + x3 = DotProduct( v3, ort ) / ortdot; + x4 = DotProduct( v4, ort ) / ortdot; + // + if ( x1 > x2 ) { + tmp = x1; x1 = x2; x2 = tmp; + tmp = y1; y1 = y2; y2 = tmp; + VectorCopy( v1, tmpv ); VectorCopy( v2, v1 ); VectorCopy( tmpv, v2 ); + } //end if + if ( x3 > x4 ) { + tmp = x3; x3 = x4; x4 = tmp; + tmp = y3; y3 = y4; y4 = tmp; + VectorCopy( v3, tmpv ); VectorCopy( v4, v3 ); VectorCopy( tmpv, v4 ); + } //end if + //if the two projected edge lines have no overlap + if ( x2 <= x3 || x4 <= x1 ) { +// Log_Write("lines no overlap: from area %d to %d\r\n", area1num, area2num); + continue; + } //end if + //if the two lines fully overlap + if ( ( x1 - 0.5 < x3 && x4 < x2 + 0.5 ) && + ( x3 - 0.5 < x1 && x2 < x4 + 0.5 ) ) { + dist1 = y3 - y1; + dist2 = y4 - y2; + VectorCopy( v1, p1area1 ); + VectorCopy( v2, p2area1 ); + VectorCopy( v3, p1area2 ); + VectorCopy( v4, p2area2 ); + } //end if + else + { + //if the points are equal + if ( x1 > x3 - 0.1 && x1 < x3 + 0.1 ) { + dist1 = y3 - y1; + VectorCopy( v1, p1area1 ); + VectorCopy( v3, p1area2 ); + } //end if + else if ( x1 < x3 ) { + y = y1 + ( x3 - x1 ) * ( y2 - y1 ) / ( x2 - x1 ); + dist1 = y3 - y; + VectorCopy( v3, p1area1 ); + p1area1[2] = y; + VectorCopy( v3, p1area2 ); + } //end if + else + { + y = y3 + ( x1 - x3 ) * ( y4 - y3 ) / ( x4 - x3 ); + dist1 = y - y1; + VectorCopy( v1, p1area1 ); + VectorCopy( v1, p1area2 ); + p1area2[2] = y; + } //end if + //if the points are equal + if ( x2 > x4 - 0.1 && x2 < x4 + 0.1 ) { + dist2 = y4 - y2; + VectorCopy( v2, p2area1 ); + VectorCopy( v4, p2area2 ); + } //end if + else if ( x2 < x4 ) { + y = y3 + ( x2 - x3 ) * ( y4 - y3 ) / ( x4 - x3 ); + dist2 = y - y2; + VectorCopy( v2, p2area1 ); + VectorCopy( v2, p2area2 ); + p2area2[2] = y; + } //end if + else + { + y = y1 + ( x4 - x1 ) * ( y2 - y1 ) / ( x2 - x1 ); + dist2 = y4 - y; + VectorCopy( v4, p2area1 ); + p2area1[2] = y; + VectorCopy( v4, p2area2 ); + } //end else + } //end else + //if both distances are pretty much equal + //then we take the middle of the points + if ( dist1 > dist2 - 1 && dist1 < dist2 + 1 ) { + dist = dist1; + VectorAdd( p1area1, p2area1, start ); + VectorScale( start, 0.5, start ); + VectorAdd( p1area2, p2area2, end ); + VectorScale( end, 0.5, end ); + } //end if + else if ( dist1 < dist2 ) { + dist = dist1; + VectorCopy( p1area1, start ); + VectorCopy( p1area2, end ); + } //end else if + else + { + dist = dist2; + VectorCopy( p2area1, start ); + VectorCopy( p2area2, end ); + } //end else + //get the length of the overlapping part of the edges of the two areas + VectorSubtract( p2area2, p1area2, dir ); + length = VectorLength( dir ); + // + if ( groundface1->faceflags & FACE_GROUND ) { + //if the vertical distance is smaller + if ( dist < ground_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + ( dist < ground_bestdist + 1 && length > ground_bestlength ) ) { + ground_bestdist = dist; + ground_bestlength = length; + ground_foundreach = qtrue; + ground_bestarea2groundedgenum = edge1num; + ground_bestface1 = groundface1; + //best point towards area1 + VectorCopy( start, ground_beststart ); + //normal is pointing into area2 + VectorCopy( normal, ground_bestnormal ); + //best point towards area2 + VectorCopy( end, ground_bestend ); + } //end if + } //end if + else + { + //if the vertical distance is smaller + if ( dist < water_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + ( dist < water_bestdist + 1 && length > water_bestlength ) ) { + water_bestdist = dist; + water_bestlength = length; + water_foundreach = qtrue; + water_bestarea2groundedgenum = edge1num; + water_bestface1 = groundface1; + //best point towards area1 + VectorCopy( start, water_beststart ); + //normal is pointing into area2 + VectorCopy( normal, water_bestnormal ); + //best point towards area2 + VectorCopy( end, water_bestend ); + } //end if + } //end else + } //end for + } //end for + } //end for + } //end for + // + // NOTE: swim reachabilities are already filtered out + // + // Steps + // + // --------- + // | step height -> TRAVEL_WALK + //--------| + // + // --------- + //~~~~~~~~| step height and low water -> TRAVEL_WALK + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | step height and low water up to the step -> TRAVEL_WALK + //--------| + // + //check for a step reachability + if ( ground_foundreach ) { + //if area2 is higher but lower than the maximum step height + //NOTE: ground_bestdist >= 0 also catches equal floor reachabilities + if ( ground_bestdist >= 0 && ground_bestdist < aassettings.sv_maxstep ) { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA( ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start ); + VectorMA( ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end ); + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 0; //1; + //if going into a crouch area + if ( !AAS_AreaCrouch( area1num ) && AAS_AreaCrouch( area2num ) ) { + lreach->traveltime += STARTCROUCH_TIME; + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //NOTE: if there's nearby solid or a gap area after this area + /* + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_step++; + return qtrue; + } //end if + } //end if + // + // Water Jumps + // + // --------- + // | + //~~~~~~~~| + // | + // | higher than step height and water up to waterjump height -> TRAVEL_WATERJUMP + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | + // | + // | + // | higher than step height and low water up to the step -> TRAVEL_WATERJUMP + //--------| + // + //check for a waterjump reachability + if ( water_foundreach ) { + //get a test point a little bit towards area1 + VectorMA( water_bestend, -INSIDEUNITS, water_bestnormal, testpoint ); + //go down the maximum waterjump height + testpoint[2] -= aassettings.sv_maxwaterjump; + //if there IS water the sv_maxwaterjump height below the bestend point + if ( ( *aasworld ).areasettings[AAS_PointAreaNum( testpoint )].areaflags & AREA_LIQUID ) { + //don't create rediculous water jump reachabilities from areas very far below + //the water surface + if ( water_bestdist < aassettings.sv_maxwaterjump + 24 ) { + //waterjumping from or towards a crouch only area is not possible in Quake2 + if ( ( ( *aasworld ).areasettings[area1num].presencetype & PRESENCE_NORMAL ) && + ( ( *aasworld ).areasettings[area2num].presencetype & PRESENCE_NORMAL ) ) { + //create water jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = water_bestarea2groundedgenum; + VectorCopy( water_beststart, lreach->start ); + VectorMA( water_bestend, INSIDEUNITS_WATERJUMP, water_bestnormal, lreach->end ); + lreach->traveltype = TRAVEL_WATERJUMP; + lreach->traveltime = WATERJUMP_TIME; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another waterjump reachability + reach_waterjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Barrier Jumps + // + // --------- + // | + // | + // | + // | higher than step height lower than barrier height -> TRAVEL_BARRIERJUMP + //--------| + // + // --------- + // | + // | + // | + //~~~~~~~~| higher than step height lower than barrier height + //--------| and a thin layer of water in the area to jump from -> TRAVEL_BARRIERJUMP + // + //check for a barrier jump reachability + if ( ground_foundreach ) { + //if area2 is higher but lower than the maximum barrier jump height + if ( ground_bestdist > 0 && ground_bestdist < aassettings.sv_maxbarrier ) { + //if no water in area1 or a very thin layer of water on the ground + if ( !water_foundreach || ( ground_bestdist - water_bestdist < 16 ) ) { + //cannot perform a barrier jump towards or from a crouch area in Quake2 + if ( !AAS_AreaCrouch( area1num ) && !AAS_AreaCrouch( area2num ) ) { + //create barrier jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA( ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start ); + VectorMA( ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end ); + lreach->traveltype = TRAVEL_BARRIERJUMP; + lreach->traveltime = BARRIERJUMP_TIME; //AAS_BarrierJumpTravelTime(); + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another barrierjump reachability + reach_barrier++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Walk and Walk Off Ledge + // + //--------| + // | can walk or step back -> TRAVEL_WALK + // --------- + // + //--------| + // | + // | + // | + // | cannot walk/step back -> TRAVEL_WALKOFFLEDGE + // --------- + // + //--------| + // | + // |~~~~~~~~ + // | + // | cannot step back but can waterjump back -> TRAVEL_WALKOFFLEDGE + // --------- FIXME: create TRAVEL_WALK reach?? + // + //check for a walk or walk off ledge reachability + if ( ground_foundreach ) { + if ( ground_bestdist < 0 ) { + if ( ground_bestdist > -aassettings.sv_maxstep ) { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA( ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start ); + + // Ridah +// VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + VectorMA( ground_bestend, INSIDEUNITS_WALKOFFLEDGEEND, ground_bestnormal, lreach->end ); + + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 1; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another walk reachability + reach_walk++; + return qtrue; + } //end if + //trace a bounding box vertically to check for solids + VectorMA( ground_bestend, INSIDEUNITS, ground_bestnormal, ground_bestend ); + VectorCopy( ground_bestend, start ); + start[2] = ground_beststart[2]; + VectorCopy( ground_bestend, end ); + end[2] += 4; + trace = AAS_TraceClientBBox( start, end, PRESENCE_NORMAL, -1 ); + //if no solids were found + if ( !trace.startsolid && trace.fraction >= 1.0 ) { + //the trace end point must be in the goal area + trace.endpos[2] += 1; + if ( AAS_PointAreaNum( trace.endpos ) == area2num ) { + //create a walk off ledge reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorCopy( ground_beststart, lreach->start ); + VectorCopy( ground_bestend, lreach->end ); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = STARTWALKOFFLEDGE_TIME + fabs( ground_bestdist ) * 50 / aassettings.sv_gravity; + //if falling from too high and not falling into water + if ( !AAS_AreaSwim( area2num ) && !AAS_AreaJumpPad( area2num ) ) { + if ( AAS_FallDelta( ground_bestdist ) > FALLDELTA_5DAMAGE ) { + lreach->traveltime += FALLDAMAGE_5_TIME; + } //end if + if ( AAS_FallDelta( ground_bestdist ) > FALLDELTA_10DAMAGE ) { + lreach->traveltime += FALLDAMAGE_10_TIME; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_walkoffledge++; + //NOTE: don't create a weapon (rl, bfg) jump reachability here + //because it interferes with other reachabilities + //like the ladder reachability + return qtrue; + } //end if + } //end if + } //end else + } //end if + return qfalse; +} //end of the function AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge +//=========================================================================== +// returns the distance between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* Ridah, moved to q_math.c +float VectorDistance(vec3_t v1, vec3_t v2) +{ + vec3_t dir; + + VectorSubtract(v2, v1, dir); + return VectorLength(dir); +} //end of the function VectorDistance +*/ +//=========================================================================== +// returns true if the first vector is between the last two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int VectorBetweenVectors( vec3_t v, vec3_t v1, vec3_t v2 ) { + vec3_t dir1, dir2; + + VectorSubtract( v, v1, dir1 ); + VectorSubtract( v, v2, dir2 ); + return ( DotProduct( dir1, dir2 ) <= 0 ); +} //end of the function VectorBetweenVectors +//=========================================================================== +// returns the mid point between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void VectorMiddle( vec3_t v1, vec3_t v2, vec3_t middle ) { + VectorAdd( v1, v2, middle ); + VectorScale( middle, 0.5, middle ); +} //end of the function VectorMiddle +//=========================================================================== +// calculate a range of points closest to each other on both edges +// +// Parameter: beststart1 start of the range of points on edge v1-v2 +// beststart2 end of the range of points on edge v1-v2 +// bestend1 start of the range of points on edge v3-v4 +// bestend2 end of the range of points on edge v3-v4 +// bestdist best distance so far +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart, vec3_t bestend, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v1, beststart); + VectorMiddle(bestend, p1, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(p1, bestend); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v2, beststart); + VectorMiddle(bestend, p2, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(p2, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p3, beststart); + VectorMiddle(bestend, v3, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart); + VectorCopy(v3, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p4, beststart); + VectorMiddle(bestend, v4, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart); + VectorCopy(v4, bestend); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v4, bestend); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v4, bestend); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints*/ + +float AAS_ClosestEdgePoints( vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart1, vec3_t bestend1, + vec3_t beststart2, vec3_t bestend2, float bestdist ) { + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist, dist1, dist2; + int founddist; + + //edge vectors + VectorSubtract( v2, v1, dir1 ); + VectorSubtract( v4, v3, dir2 ); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if ( dir2[0] ) { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = ( DotProduct( v1, dir2 ) - ( a2 * dir2[0] + b2 * dir2[1] ) ) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = ( DotProduct( v2, dir2 ) - ( a2 * dir2[0] + b2 * dir2[1] ) ) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if ( dir1[0] ) { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = ( DotProduct( v3, dir1 ) - ( a1 * dir1[0] + b1 * dir1[1] ) ) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = ( DotProduct( v4, dir1 ) - ( a1 * dir1[0] + b1 * dir1[1] ) ) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = ( plane2->dist - DotProduct( plane2->normal, p1 ) ) / plane2->normal[2]; + p2[2] = ( plane2->dist - DotProduct( plane2->normal, p2 ) ) / plane2->normal[2]; + p3[2] = ( plane1->dist - DotProduct( plane1->normal, p3 ) ) / plane1->normal[2]; + p4[2] = ( plane1->dist - DotProduct( plane1->normal, p4 ) ) / plane1->normal[2]; + // + founddist = qfalse; + // + if ( VectorBetweenVectors( p1, v3, v4 ) ) { + dist = VectorDistance( v1, p1 ); + if ( dist > bestdist - 0.5 && dist < bestdist + 0.5 ) { + dist1 = VectorDistance( beststart1, v1 ); + dist2 = VectorDistance( beststart2, v1 ); + if ( dist1 > dist2 ) { + if ( dist1 > VectorDistance( beststart1, beststart2 ) ) { + VectorCopy( v1, beststart2 ); + } + } //end if + else + { + if ( dist2 > VectorDistance( beststart1, beststart2 ) ) { + VectorCopy( v1, beststart1 ); + } + } //end else + dist1 = VectorDistance( bestend1, p1 ); + dist2 = VectorDistance( bestend2, p1 ); + if ( dist1 > dist2 ) { + if ( dist1 > VectorDistance( bestend1, bestend2 ) ) { + VectorCopy( p1, bestend2 ); + } + } //end if + else + { + if ( dist2 > VectorDistance( bestend1, bestend2 ) ) { + VectorCopy( p1, bestend1 ); + } + } //end else + } //end if + else if ( dist < bestdist ) { + bestdist = dist; + VectorCopy( v1, beststart1 ); + VectorCopy( v1, beststart2 ); + VectorCopy( p1, bestend1 ); + VectorCopy( p1, bestend2 ); + } //end if + founddist = qtrue; + } //end if + if ( VectorBetweenVectors( p2, v3, v4 ) ) { + dist = VectorDistance( v2, p2 ); + if ( dist > bestdist - 0.5 && dist < bestdist + 0.5 ) { + dist1 = VectorDistance( beststart1, v2 ); + dist2 = VectorDistance( beststart2, v2 ); + if ( dist1 > dist2 ) { + if ( dist1 > VectorDistance( beststart1, beststart2 ) ) { + VectorCopy( v2, beststart2 ); + } + } //end if + else + { + if ( dist2 > VectorDistance( beststart1, beststart2 ) ) { + VectorCopy( v2, beststart1 ); + } + } //end else + dist1 = VectorDistance( bestend1, p2 ); + dist2 = VectorDistance( bestend2, p2 ); + if ( dist1 > dist2 ) { + if ( dist1 > VectorDistance( bestend1, bestend2 ) ) { + VectorCopy( p2, bestend2 ); + } + } //end if + else + { + if ( dist2 > VectorDistance( bestend1, bestend2 ) ) { + VectorCopy( p2, bestend1 ); + } + } //end else + } //end if + else if ( dist < bestdist ) { + bestdist = dist; + VectorCopy( v2, beststart1 ); + VectorCopy( v2, beststart2 ); + VectorCopy( p2, bestend1 ); + VectorCopy( p2, bestend2 ); + } //end if + founddist = qtrue; + } //end else if + if ( VectorBetweenVectors( p3, v1, v2 ) ) { + dist = VectorDistance( v3, p3 ); + if ( dist > bestdist - 0.5 && dist < bestdist + 0.5 ) { + dist1 = VectorDistance( beststart1, p3 ); + dist2 = VectorDistance( beststart2, p3 ); + if ( dist1 > dist2 ) { + if ( dist1 > VectorDistance( beststart1, beststart2 ) ) { + VectorCopy( p3, beststart2 ); + } + } //end if + else + { + if ( dist2 > VectorDistance( beststart1, beststart2 ) ) { + VectorCopy( p3, beststart1 ); + } + } //end else + dist1 = VectorDistance( bestend1, v3 ); + dist2 = VectorDistance( bestend2, v3 ); + if ( dist1 > dist2 ) { + if ( dist1 > VectorDistance( bestend1, bestend2 ) ) { + VectorCopy( v3, bestend2 ); + } + } //end if + else + { + if ( dist2 > VectorDistance( bestend1, bestend2 ) ) { + VectorCopy( v3, bestend1 ); + } + } //end else + } //end if + else if ( dist < bestdist ) { + bestdist = dist; + VectorCopy( p3, beststart1 ); + VectorCopy( p3, beststart2 ); + VectorCopy( v3, bestend1 ); + VectorCopy( v3, bestend2 ); + } //end if + founddist = qtrue; + } //end else if + if ( VectorBetweenVectors( p4, v1, v2 ) ) { + dist = VectorDistance( v4, p4 ); + if ( dist > bestdist - 0.5 && dist < bestdist + 0.5 ) { + dist1 = VectorDistance( beststart1, p4 ); + dist2 = VectorDistance( beststart2, p4 ); + if ( dist1 > dist2 ) { + if ( dist1 > VectorDistance( beststart1, beststart2 ) ) { + VectorCopy( p4, beststart2 ); + } + } //end if + else + { + if ( dist2 > VectorDistance( beststart1, beststart2 ) ) { + VectorCopy( p4, beststart1 ); + } + } //end else + dist1 = VectorDistance( bestend1, v4 ); + dist2 = VectorDistance( bestend2, v4 ); + if ( dist1 > dist2 ) { + if ( dist1 > VectorDistance( bestend1, bestend2 ) ) { + VectorCopy( v4, bestend2 ); + } + } //end if + else + { + if ( dist2 > VectorDistance( bestend1, bestend2 ) ) { + VectorCopy( v4, bestend1 ); + } + } //end else + } //end if + else if ( dist < bestdist ) { + bestdist = dist; + VectorCopy( p4, beststart1 ); + VectorCopy( p4, beststart2 ); + VectorCopy( v4, bestend1 ); + VectorCopy( v4, bestend2 ); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if ( !founddist ) { + dist = VectorDistance( v1, v3 ); + if ( dist < bestdist ) { + bestdist = dist; + VectorCopy( v1, beststart1 ); + VectorCopy( v1, beststart2 ); + VectorCopy( v3, bestend1 ); + VectorCopy( v3, bestend2 ); + } //end if + dist = VectorDistance( v1, v4 ); + if ( dist < bestdist ) { + bestdist = dist; + VectorCopy( v1, beststart1 ); + VectorCopy( v1, beststart2 ); + VectorCopy( v4, bestend1 ); + VectorCopy( v4, bestend2 ); + } //end if + dist = VectorDistance( v2, v3 ); + if ( dist < bestdist ) { + bestdist = dist; + VectorCopy( v2, beststart1 ); + VectorCopy( v2, beststart2 ); + VectorCopy( v3, bestend1 ); + VectorCopy( v3, bestend2 ); + } //end if + dist = VectorDistance( v2, v4 ); + if ( dist < bestdist ) { + bestdist = dist; + VectorCopy( v2, beststart1 ); + VectorCopy( v2, beststart2 ); + VectorCopy( v4, bestend1 ); + VectorCopy( v4, bestend2 ); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints +//=========================================================================== +// creates possible jump reachabilities between the areas +// +// The two closest points on the ground of the areas are calculated +// One of the points will be on an edge of a ground face of area1 and +// one on an edge of a ground face of area2. +// If there is a range of closest points the point in the middle of this range +// is selected. +// Between these two points there must be one or more gaps. +// If the gaps exist a potential jump is predicted. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Jump( int area1num, int area2num ) { + int i, j, k, l, face1num, face2num, edge1num, edge2num, traveltype; + float sv_jumpvel, maxjumpdistance, maxjumpheight, height, bestdist, speed; + vec_t *v1, *v2, *v3, *v4; + vec3_t beststart, beststart2, bestend, bestend2; + vec3_t teststart, testend, dir, velocity, cmdmove, up = {0, 0, 1}; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge1, *edge2; + aas_plane_t *plane1, *plane2, *plane; + aas_trace_t trace; + aas_clientmove_t move; + aas_lreachability_t *lreach; + + if ( !AAS_AreaGrounded( area1num ) || !AAS_AreaGrounded( area2num ) ) { + return qfalse; + } + //cannot jump from or to a crouch area + if ( AAS_AreaCrouch( area1num ) || AAS_AreaCrouch( area2num ) ) { + return qfalse; + } + // + area1 = &( *aasworld ).areas[area1num]; + area2 = &( *aasworld ).areas[area2num]; + // + sv_jumpvel = aassettings.sv_jumpvel; + //maximum distance a player can jump + maxjumpdistance = 2 * AAS_MaxJumpDistance( sv_jumpvel ); + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight( sv_jumpvel ); + + //if the areas are not near anough in the x-y direction + for ( i = 0; i < 2; i++ ) + { + if ( area1->mins[i] > area2->maxs[i] + maxjumpdistance ) { + return qfalse; + } + if ( area1->maxs[i] < area2->mins[i] - maxjumpdistance ) { + return qfalse; + } + } //end for + //if area2 is way to high to jump up to + if ( area2->mins[2] > area1->maxs[2] + maxjumpheight ) { + return qfalse; + } + // + bestdist = 999999; + // + for ( i = 0; i < area1->numfaces; i++ ) + { + face1num = ( *aasworld ).faceindex[area1->firstface + i]; + face1 = &( *aasworld ).faces[abs( face1num )]; + //if not a ground face + if ( !( face1->faceflags & FACE_GROUND ) ) { + continue; + } + // + for ( j = 0; j < area2->numfaces; j++ ) + { + face2num = ( *aasworld ).faceindex[area2->firstface + j]; + face2 = &( *aasworld ).faces[abs( face2num )]; + //if not a ground face + if ( !( face2->faceflags & FACE_GROUND ) ) { + continue; + } + // + for ( k = 0; k < face1->numedges; k++ ) + { + edge1num = abs( ( *aasworld ).edgeindex[face1->firstedge + k] ); + edge1 = &( *aasworld ).edges[edge1num]; + for ( l = 0; l < face2->numedges; l++ ) + { + edge2num = abs( ( *aasworld ).edgeindex[face2->firstedge + l] ); + edge2 = &( *aasworld ).edges[edge2num]; + //calculate the minimum distance between the two edges + v1 = ( *aasworld ).vertexes[edge1->v[0]]; + v2 = ( *aasworld ).vertexes[edge1->v[1]]; + v3 = ( *aasworld ).vertexes[edge2->v[0]]; + v4 = ( *aasworld ).vertexes[edge2->v[1]]; + //get the ground planes + plane1 = &( *aasworld ).planes[face1->planenum]; + plane2 = &( *aasworld ).planes[face2->planenum]; + // + bestdist = AAS_ClosestEdgePoints( v1, v2, v3, v4, plane1, plane2, + beststart, bestend, + beststart2, bestend2, bestdist ); + } //end for + } //end for + } //end for + } //end for + VectorMiddle( beststart, beststart2, beststart ); + VectorMiddle( bestend, bestend2, bestend ); + if ( bestdist > 4 && bestdist < maxjumpdistance ) { +// Log_Write("shortest distance between %d and %d is %f\r\n", area1num, area2num, bestdist); + //if the fall would damage the bot + // + if ( AAS_HorizontalVelocityForJump( 0, beststart, bestend, &speed ) ) { + //FIXME: why multiply with 1.2??? + speed *= 1.2; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end if + else if ( bestdist <= 48 && fabs( beststart[2] - bestend[2] ) < 8 ) { + speed = 400; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end else if + else + { + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + if ( !AAS_HorizontalVelocityForJump( sv_jumpvel, beststart, bestend, &speed ) ) { + return qfalse; + } + traveltype = TRAVEL_JUMP; + // + //NOTE: test if the horizontal distance isn't too small + VectorSubtract( bestend, beststart, dir ); + dir[2] = 0; + if ( VectorLength( dir ) < 10 ) { + return qfalse; + } + } //end if + // + VectorSubtract( bestend, beststart, dir ); + VectorNormalize( dir ); + VectorMA( beststart, 1, dir, teststart ); + // + VectorCopy( teststart, testend ); + testend[2] -= 100; + trace = AAS_TraceClientBBox( teststart, testend, PRESENCE_NORMAL, -1 ); + // + if ( trace.startsolid ) { + return qfalse; + } + if ( trace.fraction < 1 ) { + plane = &( *aasworld ).planes[trace.planenum]; + if ( DotProduct( plane->normal, up ) >= 0.7 ) { + if ( !( AAS_PointContents( trace.endpos ) & CONTENTS_LAVA ) ) { //----(SA) modified since slime is no longer deadly +// if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) + if ( teststart[2] - trace.endpos[2] <= aassettings.sv_maxbarrier ) { + return qfalse; + } + } //end if + } //end if + } //end if + // + VectorMA( bestend, -1, dir, teststart ); + // + VectorCopy( teststart, testend ); + testend[2] -= 100; + trace = AAS_TraceClientBBox( teststart, testend, PRESENCE_NORMAL, -1 ); + // + if ( trace.startsolid ) { + return qfalse; + } + if ( trace.fraction < 1 ) { + plane = &( *aasworld ).planes[trace.planenum]; + if ( DotProduct( plane->normal, up ) >= 0.7 ) { + if ( !( AAS_PointContents( trace.endpos ) & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) { + if ( teststart[2] - trace.endpos[2] <= aassettings.sv_maxbarrier ) { + return qfalse; + } + } //end if + } //end if + } //end if + // + VectorSubtract( bestend, beststart, dir ); + dir[2] = 0; + VectorNormalize( dir ); + // + VectorScale( dir, speed, velocity ); + //get command movement + VectorClear( cmdmove ); + if ( traveltype == TRAVEL_JUMP ) { + cmdmove[2] = aassettings.sv_jumpvel; + } else { cmdmove[2] = 0;} + // + AAS_PredictClientMovement( &move, -1, beststart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1, + SE_HITGROUND | SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE, 0, qfalse ); + //if prediction time wasn't enough to fully predict the movement + if ( move.frames >= 30 ) { + return qfalse; + } + //don't enter slime or lava and don't fall from too high + if ( move.stopevent & SE_ENTERLAVA ) { + return qfalse; //----(SA) modified since slime is no longer deadly + } +// if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) return qfalse; + //the end position should be in area2, also test a little bit back + //because the predicted jump could have rushed through the area + for ( i = 0; i <= 32; i += 8 ) + { + VectorMA( move.endpos, -i, dir, teststart ); + teststart[2] += 0.125; + if ( AAS_PointAreaNum( teststart ) == area2num ) { + break; + } + } //end for + if ( i > 32 ) { + return qfalse; + } + // +#ifdef REACHDEBUG + //create the reachability + Log_Write( "jump reachability between %d and %d\r\n", area1num, area2num ); +#endif //REACHDEBUG + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy( beststart, lreach->start ); + VectorCopy( bestend, lreach->end ); + lreach->traveltype = traveltype; + + VectorSubtract( bestend, beststart, dir ); + height = dir[2]; + dir[2] = 0; + if ( traveltype == TRAVEL_WALKOFFLEDGE && height > VectorLength( dir ) ) { + lreach->traveltime = STARTWALKOFFLEDGE_TIME + height * 50 / aassettings.sv_gravity; + } else + { + lreach->traveltime = STARTJUMP_TIME + VectorDistance( bestend, beststart ) * 240 / aassettings.sv_maxwalkvelocity; + } //end if + // + if ( !AAS_AreaJumpPad( area2num ) ) { + if ( AAS_FallDelta( beststart[2] - bestend[2] ) > FALLDELTA_5DAMAGE ) { + lreach->traveltime += FALLDAMAGE_5_TIME; + } //end if + else if ( AAS_FallDelta( beststart[2] - bestend[2] ) > FALLDELTA_10DAMAGE ) { + lreach->traveltime += FALLDAMAGE_10_TIME; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + if ( traveltype == TRAVEL_JUMP ) { + reach_jump++; + } else { reach_walkoffledge++;} + } //end if + return qfalse; +} //end of the function AAS_Reachability_Jump +//=========================================================================== +// create a possible ladder reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Ladder( int area1num, int area2num ) { + int i, j, k, l, edge1num, edge2num, sharededgenum, lowestedgenum; + int face1num, face2num, ladderface1num, ladderface2num; + int ladderface1vertical, ladderface2vertical, firstv; + float face1area, face2area, bestface1area, bestface2area; + float sv_jumpvel, maxjumpheight; + vec3_t area1point, area2point, v1, v2, up = {0, 0, 1}; + vec3_t mid, lowestpoint, start, end, sharededgevec, dir; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2, *ladderface1, *ladderface2; + aas_plane_t *plane1, *plane2; + aas_edge_t *sharededge, *edge1; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if ( !AAS_AreaLadder( area1num ) || !AAS_AreaLadder( area2num ) ) { + return qfalse; + } + // + sv_jumpvel = aassettings.sv_jumpvel; + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight( sv_jumpvel ); + + area1 = &( *aasworld ).areas[area1num]; + area2 = &( *aasworld ).areas[area2num]; + // + ladderface1 = NULL; + ladderface2 = NULL; + ladderface1num = 0; //make compiler happy + ladderface2num = 0; //make compiler happy + bestface1area = -9999; + bestface2area = -9999; + sharededgenum = 0; //make compiler happy + lowestedgenum = 0; //make compiler happy + // + for ( i = 0; i < area1->numfaces; i++ ) + { + face1num = ( *aasworld ).faceindex[area1->firstface + i]; + face1 = &( *aasworld ).faces[abs( face1num )]; + //if not a ladder face + if ( !( face1->faceflags & FACE_LADDER ) ) { + continue; + } + // + for ( j = 0; j < area2->numfaces; j++ ) + { + face2num = ( *aasworld ).faceindex[area2->firstface + j]; + face2 = &( *aasworld ).faces[abs( face2num )]; + //if not a ladder face + if ( !( face2->faceflags & FACE_LADDER ) ) { + continue; + } + //check if the faces share an edge + for ( k = 0; k < face1->numedges; k++ ) + { + edge1num = ( *aasworld ).edgeindex[face1->firstedge + k]; + for ( l = 0; l < face2->numedges; l++ ) + { + edge2num = ( *aasworld ).edgeindex[face2->firstedge + l]; + if ( abs( edge1num ) == abs( edge2num ) ) { + //get the face with the largest area + face1area = AAS_FaceArea( face1 ); + face2area = AAS_FaceArea( face2 ); + if ( face1area > bestface1area && face2area > bestface2area ) { + bestface1area = face1area; + bestface2area = face2area; + ladderface1 = face1; + ladderface2 = face2; + ladderface1num = face1num; + ladderface2num = face2num; + sharededgenum = edge1num; + } //end if + break; + } //end if + } //end for + if ( l != face2->numedges ) { + break; + } + } //end for + } //end for + } //end for + // + if ( ladderface1 && ladderface2 ) { + //get the middle of the shared edge + sharededge = &( *aasworld ).edges[abs( sharededgenum )]; + firstv = sharededgenum < 0; + // + VectorCopy( ( *aasworld ).vertexes[sharededge->v[firstv]], v1 ); + VectorCopy( ( *aasworld ).vertexes[sharededge->v[!firstv]], v2 ); + VectorAdd( v1, v2, area1point ); + VectorScale( area1point, 0.5, area1point ); + VectorCopy( area1point, area2point ); + // + //if the face plane in area 1 is pretty much vertical + plane1 = &( *aasworld ).planes[ladderface1->planenum ^ ( ladderface1num < 0 )]; + plane2 = &( *aasworld ).planes[ladderface2->planenum ^ ( ladderface2num < 0 )]; + // + //get the points really into the areas + VectorSubtract( v2, v1, sharededgevec ); + CrossProduct( plane1->normal, sharededgevec, dir ); + VectorNormalize( dir ); + //NOTE: 32 because that's larger than 16 (bot bbox x,y) + VectorMA( area1point, -32, dir, area1point ); + VectorMA( area2point, 32, dir, area2point ); + // + ladderface1vertical = abs( DotProduct( plane1->normal, up ) ) < 0.1; + ladderface2vertical = abs( DotProduct( plane2->normal, up ) ) < 0.1; + //there's only reachability between vertical ladder faces + if ( !ladderface1vertical && !ladderface2vertical ) { + return qfalse; + } + //if both vertical ladder faces + if ( ladderface1vertical && ladderface2vertical + //and the ladder faces do not make a sharp corner + && DotProduct( plane1->normal, plane2->normal ) > 0.7 + //and the shared edge is not too vertical + && abs( DotProduct( sharededgevec, up ) ) < 0.7 ) { + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs( sharededgenum ); + VectorCopy( area1point, lreach->start ); + //VectorCopy(area2point, lreach->end); + VectorMA( area2point, -3, plane1->normal, lreach->end ); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs( sharededgenum ); + VectorCopy( area2point, lreach->start ); + //VectorCopy(area1point, lreach->end); + VectorMA( area1point, -3, plane1->normal, lreach->end ); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_ladder++; + // + return qtrue; + } //end if + //if the second ladder face is also a ground face + //create ladder end (just ladder) reachability and + //walk off a ladder (ledge) reachability + if ( ladderface1vertical && ( ladderface2->faceflags & FACE_GROUND ) ) { + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs( sharededgenum ); + VectorCopy( area1point, lreach->start ); + VectorCopy( area2point, lreach->end ); + lreach->end[2] += 16; + VectorMA( lreach->end, -15, plane1->normal, lreach->end ); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs( sharededgenum ); + VectorCopy( area2point, lreach->start ); + VectorCopy( area1point, lreach->end ); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_walkoffledge++; + // + return qtrue; + } //end if + // + if ( ladderface1vertical ) { + //find lowest edge of the ladder face + lowestpoint[2] = 99999; + for ( i = 0; i < ladderface1->numedges; i++ ) + { + edge1num = abs( ( *aasworld ).edgeindex[ladderface1->firstedge + i] ); + edge1 = &( *aasworld ).edges[edge1num]; + // + VectorCopy( ( *aasworld ).vertexes[edge1->v[0]], v1 ); + VectorCopy( ( *aasworld ).vertexes[edge1->v[1]], v2 ); + // + VectorAdd( v1, v2, mid ); + VectorScale( mid, 0.5, mid ); + // + if ( mid[2] < lowestpoint[2] ) { + VectorCopy( mid, lowestpoint ); + lowestedgenum = edge1num; + } //end if + } //end for + // + plane1 = &( *aasworld ).planes[ladderface1->planenum]; + //trace down in the middle of this edge + VectorMA( lowestpoint, 5, plane1->normal, start ); + VectorCopy( start, end ); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox( start, end, PRESENCE_NORMAL, -1 ); + // + // +#ifdef REACHDEBUG + if ( trace.startsolid ) { + Log_Write( "trace from area %d started in solid\r\n", area1num ); + } //end if +#endif //REACHDEBUG + // + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum( trace.endpos ); + // + area2 = &( *aasworld ).areas[area2num]; + for ( i = 0; i < area2->numfaces; i++ ) + { + face2num = ( *aasworld ).faceindex[area2->firstface + i]; + face2 = &( *aasworld ).faces[abs( face2num )]; + // + if ( face2->faceflags & FACE_LADDER ) { + plane2 = &( *aasworld ).planes[face2->planenum]; + if ( abs( DotProduct( plane2->normal, up ) ) < 0.1 ) { + break; + } + } //end if + } //end for + //if from another area without vertical ladder faces + if ( i >= area2->numfaces && area2num != area1num && + //the reachabilities shouldn't exist already + !AAS_ReachabilityExists( area1num, area2num ) && + !AAS_ReachabilityExists( area2num, area1num ) ) { + //if the height is jumpable + if ( start[2] - trace.endpos[2] < maxjumpheight ) { + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy( lowestpoint, lreach->start ); + VectorCopy( trace.endpos, lreach->end ); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy( trace.endpos, lreach->start ); + //get the end point a little bit into the ladder + VectorMA( lowestpoint, -5, plane1->normal, lreach->end ); + //get the end point a little higher + lreach->end[2] += 10; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + return qtrue; +#ifdef REACHDEBUG + Log_Write( "jump up to ladder reach between %d and %d\r\n", area2num, area1num ); +#endif //REACHDEBUG + } //end if +#ifdef REACHDEBUG + else {Log_Write( "jump too high between area %d and %d\r\n", area2num, area1num );} +#endif //REACHDEBUG + } //end if + /*//if slime or lava below the ladder + //try jump reachability from far towards the ladder + if ((*aasworld).areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + for (i = 20; i <= 120; i += 20) + { + //trace down in the middle of this edge + VectorMA(lowestpoint, i, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) break; + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + if (area2num == area1num) continue; + // + if (start[2] - trace.endpos[2] > maxjumpheight) continue; + if ((*aasworld).areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) continue; + // + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + VectorCopy(lowestpoint, lreach->end); + lreach->end[2] += 5; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + Log_Write("jump far to ladder reach between %d and %d\r\n", area2num, area1num); + // + break; + } //end for + } //end if*/ + } //end if + } //end if + return qfalse; +} //end of the function AAS_Reachability_Ladder +//=========================================================================== +// create possible teleporter reachabilities +// this is very game dependent.... :( +// +// classname = trigger_multiple or trigger_teleport +// target = "t1" +// +// classname = target_teleporter +// targetname = "t1" +// target = "t2" +// +// classname = misc_teleporter_dest +// targetname = "t2" +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_Teleport( void ) { + int area1num, area2num; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + int ent, dest; + vec3_t origin, destorigin, mins, maxs, end, angles = {0, 0, 0}; + vec3_t mid; + aas_lreachability_t *lreach; + aas_trace_t trace; + aas_link_t *areas, *link; + + for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + if ( !strcmp( classname, "trigger_multiple" ) ) { + AAS_ValueForBSPEpairKey( ent, "model", model, MAX_EPAIRKEY ); +//#ifdef REACHDEBUG + botimport.Print( PRT_MESSAGE, "trigger_multiple model = \"%s\"\n", model ); +//#endif REACHDEBUG + AAS_BSPModelMinsMaxsOrigin( atoi( model + 1 ), angles, mins, maxs, origin ); + // + if ( !AAS_ValueForBSPEpairKey( ent, "target", target, MAX_EPAIRKEY ) ) { + botimport.Print( PRT_ERROR, "trigger_multiple at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2] ); + continue; + } //end if + for ( dest = AAS_NextBSPEntity( 0 ); dest; dest = AAS_NextBSPEntity( dest ) ) + { + if ( !AAS_ValueForBSPEpairKey( dest, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + if ( !strcmp( classname, "target_teleporter" ) ) { + if ( !AAS_ValueForBSPEpairKey( dest, "targetname", targetname, MAX_EPAIRKEY ) ) { + continue; + } + if ( !strcmp( targetname, target ) ) { + break; + } //end if + } //end if + } //end for + if ( !dest ) { + continue; + } //end if + if ( !AAS_ValueForBSPEpairKey( dest, "target", target, MAX_EPAIRKEY ) ) { + botimport.Print( PRT_ERROR, "target_teleporter without target\n" ); + continue; + } //end if + } //end else + else if ( !strcmp( classname, "trigger_teleport" ) ) { + AAS_ValueForBSPEpairKey( ent, "model", model, MAX_EPAIRKEY ); +//#ifdef REACHDEBUG + botimport.Print( PRT_MESSAGE, "trigger_teleport model = \"%s\"\n", model ); +//#endif REACHDEBUG + AAS_BSPModelMinsMaxsOrigin( atoi( model + 1 ), angles, mins, maxs, origin ); + // + if ( !AAS_ValueForBSPEpairKey( ent, "target", target, MAX_EPAIRKEY ) ) { + botimport.Print( PRT_ERROR, "trigger_teleport at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2] ); + continue; + } //end if + } //end if + else + { + continue; + } //end else + // + for ( dest = AAS_NextBSPEntity( 0 ); dest; dest = AAS_NextBSPEntity( dest ) ) + { + //classname should be misc_teleporter_dest + //but I've also seen target_position and actually any + //entity could be used... burp + if ( AAS_ValueForBSPEpairKey( dest, "targetname", targetname, MAX_EPAIRKEY ) ) { + if ( !strcmp( targetname, target ) ) { + break; + } //end if + } //end if + } //end for + if ( !dest ) { + botimport.Print( PRT_ERROR, "teleporter without misc_teleporter_dest (%s)\n", target ); + continue; + } //end if + if ( !AAS_VectorForBSPEpairKey( dest, "origin", destorigin ) ) { + botimport.Print( PRT_ERROR, "teleporter destination (%s) without origin\n", target ); + continue; + } //end if + // + area2num = AAS_PointAreaNum( destorigin ); + //if not teleported into a teleporter or into a jumppad + if ( !AAS_AreaTeleporter( area2num ) && !AAS_AreaJumpPad( area2num ) ) { + destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground + VectorCopy( destorigin, end ); + end[2] -= 100; + trace = AAS_TraceClientBBox( destorigin, end, PRESENCE_CROUCH, -1 ); + if ( trace.startsolid ) { + botimport.Print( PRT_ERROR, "teleporter destination (%s) in solid\n", target ); + continue; + } //end if + VectorCopy( trace.endpos, destorigin ); + area2num = AAS_PointAreaNum( destorigin ); + } //end if + // + //botimport.Print(PRT_MESSAGE, "teleporter brush origin at %f %f %f\n", origin[0], origin[1], origin[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush mins = %f %f %f\n", mins[0], mins[1], mins[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush maxs = %f %f %f\n", maxs[0], maxs[1], maxs[2]); + VectorAdd( origin, mins, mins ); + VectorAdd( origin, maxs, maxs ); + // + VectorAdd( mins, maxs, mid ); + VectorScale( mid, 0.5, mid ); + //link an invalid (-1) entity + areas = AAS_LinkEntityClientBBox( mins, maxs, -1, PRESENCE_CROUCH ); + if ( !areas ) { + botimport.Print( PRT_MESSAGE, "trigger_multiple not in any area\n" ); + } + // + for ( link = areas; link; link = link->next_area ) + { + //if (!AAS_AreaGrounded(link->areanum)) continue; + if ( !AAS_AreaTeleporter( link->areanum ) ) { + continue; + } + // + area1num = link->areanum; + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + break; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy( mid, lreach->start ); + VectorCopy( destorigin, lreach->end ); + lreach->traveltype = TRAVEL_TELEPORT; + lreach->traveltime = TELEPORT_TIME; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_teleport++; + } //end for + //unlink the invalid entity + AAS_UnlinkFromAreas( areas ); + } //end for +} //end of the function AAS_Reachability_Teleport +//=========================================================================== +// create possible elevator (func_plat) reachabilities +// this is very game dependent.... :( +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define REACHDEBUG +void AAS_Reachability_Elevator( void ) { + int area1num, area2num, modelnum, i, j, k, l, n, p; + float lip, height, speed; + char model[MAX_EPAIRKEY], classname[MAX_EPAIRKEY]; + int ent; + vec3_t mins, maxs, origin, angles = {0, 0, 0}; + vec3_t pos1, pos2, mids, platbottom, plattop; + vec3_t bottomorg, toporg, start, end, dir; + vec_t xvals[8], yvals[8], xvals_top[8], yvals_top[8]; + aas_lreachability_t *lreach; + aas_trace_t trace; + +#ifdef REACHDEBUG + Log_Write( "AAS_Reachability_Elevator\r\n" ); +#endif //REACHDEBUG + for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + if ( !strcmp( classname, "func_plat" ) ) { +#ifdef REACHDEBUG + Log_Write( "found func plat\r\n" ); +#endif //REACHDEBUG + if ( !AAS_ValueForBSPEpairKey( ent, "model", model, MAX_EPAIRKEY ) ) { + botimport.Print( PRT_ERROR, "func_plat without model\n" ); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi( model + 1 ); + if ( modelnum <= 0 ) { + botimport.Print( PRT_ERROR, "func_plat with invalid model number\n" ); + continue; + } //end if + //get the mins, maxs and origin of the model + //NOTE: the origin is usually (0,0,0) and the mins and maxs + // are the absolute mins and maxs + AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, origin ); + // + AAS_VectorForBSPEpairKey( ent, "origin", origin ); + //pos1 is the top position, pos2 is the bottom + VectorCopy( origin, pos1 ); + VectorCopy( origin, pos2 ); + //get the lip of the plat + AAS_FloatForBSPEpairKey( ent, "lip", &lip ); + if ( !lip ) { + lip = 8; + } + //get the movement height of the plat + AAS_FloatForBSPEpairKey( ent, "height", &height ); + if ( !height ) { + height = ( maxs[2] - mins[2] ) - lip; + } + //get the speed of the plat + AAS_FloatForBSPEpairKey( ent, "speed", &speed ); + if ( !speed ) { + speed = 200; + } + //get bottom position below pos1 + pos2[2] -= height; + // + botimport.Print( PRT_MESSAGE, "pos2[2] = %1.1f pos1[2] = %1.1f\n", pos2[2], pos1[2] ); + //get a point just above the plat in the bottom position + VectorAdd( mins, maxs, mids ); + VectorMA( pos2, 0.5, mids, platbottom ); + platbottom[2] = maxs[2] - ( pos1[2] - pos2[2] ) + 2; + //get a point just above the plat in the top position + VectorAdd( mins, maxs, mids ); + VectorMA( pos2, 0.5, mids, plattop ); + plattop[2] = maxs[2] + 2; + // + /*if (!area1num) + { + Log_Write("no grounded area near plat bottom\r\n"); + continue; + } //end if*/ + //get the mins and maxs a little larger + for ( i = 0; i < 3; i++ ) + { + mins[i] -= 1; + maxs[i] += 1; + } //end for + // + botimport.Print( PRT_MESSAGE, "platbottom[2] = %1.1f plattop[2] = %1.1f\n", platbottom[2], plattop[2] ); + // + VectorAdd( mins, maxs, mids ); + VectorScale( mids, 0.5, mids ); + // + xvals[0] = mins[0]; xvals[1] = mids[0]; xvals[2] = maxs[0]; xvals[3] = mids[0]; + yvals[0] = mids[1]; yvals[1] = maxs[1]; yvals[2] = mids[1]; yvals[3] = mins[1]; + // + xvals[4] = mins[0]; xvals[5] = maxs[0]; xvals[6] = maxs[0]; xvals[7] = mins[0]; + yvals[4] = maxs[1]; yvals[5] = maxs[1]; yvals[6] = mins[1]; yvals[7] = mins[1]; + //find adjacent areas around the bottom of the plat + for ( i = 0; i < 9; i++ ) + { + if ( i < 8 ) { //check at the sides of the plat + bottomorg[0] = origin[0] + xvals[i]; + bottomorg[1] = origin[1] + yvals[i]; + bottomorg[2] = platbottom[2] + 16; + //get a grounded or swim area near the plat in the bottom position + area1num = AAS_PointAreaNum( bottomorg ); + for ( k = 0; k < 16; k++ ) + { + if ( area1num ) { + if ( AAS_AreaGrounded( area1num ) || AAS_AreaSwim( area1num ) ) { + break; + } + } //end if + bottomorg[2] += 4; + area1num = AAS_PointAreaNum( bottomorg ); + } //end if + //if in solid + if ( k >= 16 ) { + continue; + } //end if + } //end if + else //at the middle of the plat + { + VectorCopy( plattop, bottomorg ); + bottomorg[2] += 24; + area1num = AAS_PointAreaNum( bottomorg ); + if ( !area1num ) { + continue; + } + VectorCopy( platbottom, bottomorg ); + bottomorg[2] += 24; + } //end else + //look at adjacent areas around the top of the plat + //make larger steps to outside the plat everytime + for ( n = 0; n < 3; n++ ) + { + for ( k = 0; k < 3; k++ ) + { + mins[k] -= 4; + maxs[k] += 4; + } //end for + xvals_top[0] = mins[0]; xvals_top[1] = mids[0]; xvals_top[2] = maxs[0]; xvals_top[3] = mids[0]; + yvals_top[0] = mids[1]; yvals_top[1] = maxs[1]; yvals_top[2] = mids[1]; yvals_top[3] = mins[1]; + // + xvals_top[4] = mins[0]; xvals_top[5] = maxs[0]; xvals_top[6] = maxs[0]; xvals_top[7] = mins[0]; + yvals_top[4] = maxs[1]; yvals_top[5] = maxs[1]; yvals_top[6] = mins[1]; yvals_top[7] = mins[1]; + // + for ( j = 0; j < 8; j++ ) + { + toporg[0] = origin[0] + xvals_top[j]; + toporg[1] = origin[1] + yvals_top[j]; + toporg[2] = plattop[2] + 16; + //get a grounded or swim area near the plat in the top position + area2num = AAS_PointAreaNum( toporg ); + for ( l = 0; l < 16; l++ ) + { + if ( area2num ) { + if ( AAS_AreaGrounded( area2num ) || AAS_AreaSwim( area2num ) ) { + VectorCopy( plattop, start ); + start[2] += 32; + VectorCopy( toporg, end ); + end[2] += 1; + trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, -1 ); + if ( trace.fraction >= 1 ) { + break; + } + } //end if + } //end if + toporg[2] += 4; + area2num = AAS_PointAreaNum( toporg ); + } //end if + //if in solid + if ( l >= 16 ) { + continue; + } + //never create a reachability in the same area + if ( area2num == area1num ) { + continue; + } + //if the area isn't grounded + if ( !AAS_AreaGrounded( area2num ) ) { + continue; + } + //if there already exists reachability between the areas + if ( AAS_ReachabilityExists( area1num, area2num ) ) { + continue; + } + //if the reachability start is within the elevator bounding box + VectorSubtract( bottomorg, platbottom, dir ); + VectorNormalize( dir ); + dir[0] = bottomorg[0] + 24 * dir[0]; + dir[1] = bottomorg[1] + 24 * dir[1]; + dir[2] = bottomorg[2]; + // + for ( p = 0; p < 3; p++ ) + if ( dir[p] < origin[p] + mins[p] || dir[p] > origin[p] + maxs[p] ) { + break; + } + if ( p >= 3 ) { + continue; + } + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + continue; + } + lreach->areanum = area2num; + //the facenum is the model number + lreach->facenum = modelnum; + //the edgenum is the height + lreach->edgenum = (int) height; + // + VectorCopy( dir, lreach->start ); + VectorCopy( toporg, lreach->end ); + lreach->traveltype = TRAVEL_ELEVATOR; + lreach->traveltime = height * 100 / speed; + if ( !lreach->traveltime ) { + lreach->traveltime = 50; + } + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //don't go any further to the outside + n = 9999; + // +#ifdef REACHDEBUG + Log_Write( "elevator reach from %d to %d\r\n", area1num, area2num ); +#endif //REACHDEBUG + // + reach_elevator++; + } //end for + } //end for + } //end for + } //end if + } //end for +} //end of the function AAS_Reachability_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_FindFaceReachabilities( vec3_t *facepoints, int numpoints, aas_plane_t *plane, int towardsface ) { + int i, j, k, l; + int facenum, edgenum, bestfacenum; + float *v1, *v2, *v3, *v4; + float bestdist, speed, hordist, dist; + vec3_t beststart, beststart2, bestend, bestend2, tmp, hordir, testpoint; + aas_lreachability_t *lreach, *lreachabilities; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + aas_plane_t *faceplane, *bestfaceplane; + + // + lreachabilities = NULL; + bestfacenum = 0; + bestfaceplane = NULL; + // + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + area = &( *aasworld ).areas[i]; + // get the shortest distance between one of the func_bob start edges and + // one of the face edges of area1 + bestdist = 999999; + for ( j = 0; j < area->numfaces; j++ ) + { + facenum = ( *aasworld ).faceindex[area->firstface + j]; + face = &( *aasworld ).faces[abs( facenum )]; + //if not a ground face + if ( !( face->faceflags & FACE_GROUND ) ) { + continue; + } + //get the ground planes + faceplane = &( *aasworld ).planes[face->planenum]; + // + for ( k = 0; k < face->numedges; k++ ) + { + edgenum = abs( ( *aasworld ).edgeindex[face->firstedge + k] ); + edge = &( *aasworld ).edges[edgenum]; + //calculate the minimum distance between the two edges + v1 = ( *aasworld ).vertexes[edge->v[0]]; + v2 = ( *aasworld ).vertexes[edge->v[1]]; + // + for ( l = 0; l < numpoints; l++ ) + { + v3 = facepoints[l]; + v4 = facepoints[( l + 1 ) % numpoints]; + dist = AAS_ClosestEdgePoints( v1, v2, v3, v4, faceplane, plane, + beststart, bestend, + beststart2, bestend2, bestdist ); + if ( dist < bestdist ) { + bestfacenum = facenum; + bestfaceplane = faceplane; + bestdist = dist; + } //end if + } //end for + } //end for + } //end for + // + if ( bestdist > 192 ) { + continue; + } + // + VectorMiddle( beststart, beststart2, beststart ); + VectorMiddle( bestend, bestend2, bestend ); + // + if ( !towardsface ) { + VectorCopy( beststart, tmp ); + VectorCopy( bestend, beststart ); + VectorCopy( tmp, bestend ); + } //end if + // + VectorSubtract( bestend, beststart, hordir ); + hordir[2] = 0; + hordist = VectorLength( hordir ); + // + if ( hordist > 2 * AAS_MaxJumpDistance( aassettings.sv_jumpvel ) ) { + continue; + } + //the end point should not be significantly higher than the start point + if ( bestend[2] - 32 > beststart[2] ) { + continue; + } + //don't fall down too far + if ( bestend[2] < beststart[2] - 128 ) { + continue; + } + //the distance should not be too far + if ( hordist > 32 ) { + //check for walk off ledge + if ( !AAS_HorizontalVelocityForJump( 0, beststart, bestend, &speed ) ) { + continue; + } + } //end if + // + beststart[2] += 1; + bestend[2] += 1; + // + if ( towardsface ) { + VectorCopy( bestend, testpoint ); + } else { VectorCopy( beststart, testpoint );} + testpoint[2] = 0; + testpoint[2] = ( bestfaceplane->dist - DotProduct( bestfaceplane->normal, testpoint ) ) / bestfaceplane->normal[2]; + // + if ( !AAS_PointInsideFace( bestfacenum, testpoint, 0.1 ) ) { + //if the faces are not overlapping then only go down + if ( bestend[2] - 16 > beststart[2] ) { + continue; + } + } //end if + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return lreachabilities; + } + lreach->areanum = i; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy( beststart, lreach->start ); + VectorCopy( bestend, lreach->end ); + lreach->traveltype = 0; + lreach->traveltime = 0; + lreach->next = lreachabilities; + lreachabilities = lreach; +#ifndef BSPC + if ( towardsface ) { + AAS_PermanentLine( lreach->start, lreach->end, 1 ); + } else { AAS_PermanentLine( lreach->start, lreach->end, 2 );} +#endif + } //end for + return lreachabilities; +} //end of the function AAS_FindFaceReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_FuncBobbing( void ) { + int ent, spawnflags, modelnum, axis; + int i, numareas, areas[10]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + vec3_t origin, move_end, move_start, move_start_top, move_end_top; + vec3_t mins, maxs, angles = {0, 0, 0}; + vec3_t start_edgeverts[4], end_edgeverts[4], mid; + vec3_t org, start, end, dir, points[10]; + float height; + aas_plane_t start_plane, end_plane; + aas_lreachability_t *startreach, *endreach, *nextstartreach, *nextendreach, *lreach; + aas_lreachability_t *firststartreach, *firstendreach; + + for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + if ( strcmp( classname, "func_bobbing" ) ) { + continue; + } + AAS_FloatForBSPEpairKey( ent, "height", &height ); + if ( !height ) { + height = 32; + } + // + if ( !AAS_ValueForBSPEpairKey( ent, "model", model, MAX_EPAIRKEY ) ) { + botimport.Print( PRT_ERROR, "func_bobbing without model\n" ); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi( model + 1 ); + if ( modelnum <= 0 ) { + botimport.Print( PRT_ERROR, "func_bobbing with invalid model number\n" ); + continue; + } //end if + // + AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, NULL ); + // + VectorAdd( mins, maxs, mid ); + VectorScale( mid, 0.5, mid ); + //VectorAdd(mid, origin, mid); + VectorCopy( mid, origin ); + // + VectorCopy( origin, move_end ); + VectorCopy( origin, move_start ); + // + AAS_IntForBSPEpairKey( ent, "spawnflags", &spawnflags ); + // set the axis of bobbing + if ( spawnflags & 1 ) { + axis = 0; + } else if ( spawnflags & 2 ) { + axis = 1; + } else { axis = 2;} + // + move_start[axis] -= height; + move_end[axis] += height; + // + Log_Write( "funcbob model %d, start = {%1.1f, %1.1f, %1.1f} end = {%1.1f, %1.1f, %1.1f}\n", + modelnum, move_start[0], move_start[1], move_start[2], move_end[0], move_end[1], move_end[2] ); + // +#ifndef BSPC + /* + AAS_DrawPermanentCross(move_start, 4, 1); + AAS_DrawPermanentCross(move_end, 4, 2); + */ +#endif + // + for ( i = 0; i < 4; i++ ) + { + VectorCopy( move_start, start_edgeverts[i] ); + start_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + start_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + start_edgeverts[0][0] += maxs[0] - mid[0]; + start_edgeverts[0][1] += maxs[1] - mid[1]; + start_edgeverts[1][0] += maxs[0] - mid[0]; + start_edgeverts[1][1] += mins[1] - mid[1]; + start_edgeverts[2][0] += mins[0] - mid[0]; + start_edgeverts[2][1] += mins[1] - mid[1]; + start_edgeverts[3][0] += mins[0] - mid[0]; + start_edgeverts[3][1] += maxs[1] - mid[1]; + // + start_plane.dist = start_edgeverts[0][2]; + VectorSet( start_plane.normal, 0, 0, 1 ); + // + for ( i = 0; i < 4; i++ ) + { + VectorCopy( move_end, end_edgeverts[i] ); + end_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + end_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + end_edgeverts[0][0] += maxs[0] - mid[0]; + end_edgeverts[0][1] += maxs[1] - mid[1]; + end_edgeverts[1][0] += maxs[0] - mid[0]; + end_edgeverts[1][1] += mins[1] - mid[1]; + end_edgeverts[2][0] += mins[0] - mid[0]; + end_edgeverts[2][1] += mins[1] - mid[1]; + end_edgeverts[3][0] += mins[0] - mid[0]; + end_edgeverts[3][1] += maxs[1] - mid[1]; + // + end_plane.dist = end_edgeverts[0][2]; + VectorSet( end_plane.normal, 0, 0, 1 ); + // +#ifndef BSPC + /* + for (i = 0; i < 4; i++) + { + AAS_PermanentLine(start_edgeverts[i], start_edgeverts[(i+1)%4], 1); + AAS_PermanentLine(end_edgeverts[i], end_edgeverts[(i+1)%4], 1); + } //end for + */ +#endif + VectorCopy( move_start, move_start_top ); + move_start_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + VectorCopy( move_end, move_end_top ); + move_end_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + // + if ( !AAS_PointAreaNum( move_start_top ) ) { + continue; + } + if ( !AAS_PointAreaNum( move_end_top ) ) { + continue; + } + // + for ( i = 0; i < 2; i++ ) + { + firststartreach = firstendreach = NULL; + // + if ( i == 0 ) { + firststartreach = AAS_FindFaceReachabilities( start_edgeverts, 4, &start_plane, qtrue ); + firstendreach = AAS_FindFaceReachabilities( end_edgeverts, 4, &end_plane, qfalse ); + } //end if + else + { + firststartreach = AAS_FindFaceReachabilities( end_edgeverts, 4, &end_plane, qtrue ); + firstendreach = AAS_FindFaceReachabilities( start_edgeverts, 4, &start_plane, qfalse ); + } //end else + // + //create reachabilities from start to end + for ( startreach = firststartreach; startreach; startreach = nextstartreach ) + { + nextstartreach = startreach->next; + // + //trace = AAS_TraceClientBBox(startreach->start, move_start_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + for ( endreach = firstendreach; endreach; endreach = nextendreach ) + { + nextendreach = endreach->next; + // + //trace = AAS_TraceClientBBox(endreach->end, move_end_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + Log_Write( "funcbob reach from area %d to %d\n", startreach->areanum, endreach->areanum ); + // + // + if ( i == 0 ) { + VectorCopy( move_start_top, org ); + } else { VectorCopy( move_end_top, org );} + VectorSubtract( startreach->start, org, dir ); + dir[2] = 0; + VectorNormalize( dir ); + VectorCopy( startreach->start, start ); + VectorMA( startreach->start, 1, dir, start ); + start[2] += 1; + VectorMA( startreach->start, 16, dir, end ); + end[2] += 1; + // + numareas = AAS_TraceAreas( start, end, areas, points, 10 ); + if ( numareas <= 0 ) { + continue; + } + if ( numareas > 1 ) { + VectorCopy( points[1], startreach->start ); + } else { VectorCopy( end, startreach->start );} + // + if ( !AAS_PointAreaNum( startreach->start ) ) { + continue; + } + if ( !AAS_PointAreaNum( endreach->end ) ) { + continue; + } + // + lreach = AAS_AllocReachability(); + lreach->areanum = endreach->areanum; + if ( i == 0 ) { + lreach->edgenum = ( (int)move_start[axis] << 16 ) | ( (int) move_end[axis] & 0x0000ffff ); + } else { lreach->edgenum = ( (int)move_end[axis] << 16 ) | ( (int) move_start[axis] & 0x0000ffff );} + lreach->facenum = ( spawnflags << 16 ) | modelnum; + VectorCopy( startreach->start, lreach->start ); + VectorCopy( endreach->end, lreach->end ); +#ifndef BSPC +// AAS_DrawArrow(lreach->start, lreach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); +// AAS_PermanentLine(lreach->start, lreach->end, 1); +#endif + lreach->traveltype = TRAVEL_FUNCBOB; + lreach->traveltime = 300; + reach_funcbob++; + lreach->next = areareachability[startreach->areanum]; + areareachability[startreach->areanum] = lreach; + // + } //end for + } //end for + for ( startreach = firststartreach; startreach; startreach = nextstartreach ) + { + nextstartreach = startreach->next; + AAS_FreeReachability( startreach ); + } //end for + for ( endreach = firstendreach; endreach; endreach = nextendreach ) + { + nextendreach = endreach->next; + AAS_FreeReachability( endreach ); + } //end for + //only go up with func_bobbing entities that go up and down + if ( !( spawnflags & 1 ) && !( spawnflags & 2 ) ) { + break; + } + } //end for + } //end for +} //end of the function AAS_Reachability_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_JumpPad( void ) { + int face2num, i, ret, modelnum, area2num, visualize; + float speed, zvel, hordist, dist, time, height, gravity, forward; + aas_face_t *face2; + aas_area_t *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, dir, cmdmove, teststart; + vec3_t velocity, origin, ent2origin, angles, absmins, absmaxs; + aas_clientmove_t move; + aas_trace_t trace; + int ent, ent2; + aas_link_t *areas, *link; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + + for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + if ( strcmp( classname, "trigger_push" ) ) { + continue; + } + // + AAS_FloatForBSPEpairKey( ent, "speed", &speed ); + if ( !speed ) { + speed = 1000; + } +// AAS_VectorForBSPEpairKey(ent, "angles", angles); +// AAS_SetMovedir(angles, velocity); +// VectorScale(velocity, speed, velocity); + VectorClear( angles ); + //get the mins, maxs and origin of the model + AAS_ValueForBSPEpairKey( ent, "model", model, MAX_EPAIRKEY ); + if ( model[0] ) { + modelnum = atoi( model + 1 ); + } else { modelnum = 0;} + AAS_BSPModelMinsMaxsOrigin( modelnum, angles, absmins, absmaxs, origin ); + VectorAdd( origin, absmins, absmins ); + VectorAdd( origin, absmaxs, absmaxs ); + // +#ifdef REACHDEBUG + botimport.Print( PRT_MESSAGE, "absmins = %f %f %f\n", absmins[0], absmins[1], absmins[2] ); + botimport.Print( PRT_MESSAGE, "absmaxs = %f %f %f\n", absmaxs[0], absmaxs[1], absmaxs[2] ); +#endif REACHDEBUG + VectorAdd( absmins, absmaxs, origin ); + VectorScale( origin, 0.5, origin ); + + //get the start areas + VectorCopy( origin, teststart ); + teststart[2] += 64; + trace = AAS_TraceClientBBox( teststart, origin, PRESENCE_CROUCH, -1 ); + if ( trace.startsolid ) { + botimport.Print( PRT_MESSAGE, "trigger_push start solid\n" ); + VectorCopy( origin, areastart ); + } //end if + else + { + VectorCopy( trace.endpos, areastart ); + } //end else + areastart[2] += 0.125; + // + //AAS_DrawPermanentCross(origin, 4, 4); + //get the target entity + AAS_ValueForBSPEpairKey( ent, "target", target, MAX_EPAIRKEY ); + for ( ent2 = AAS_NextBSPEntity( 0 ); ent2; ent2 = AAS_NextBSPEntity( ent2 ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent2, "targetname", targetname, MAX_EPAIRKEY ) ) { + continue; + } + if ( !strcmp( targetname, target ) ) { + break; + } + } //end for + if ( !ent2 ) { + botimport.Print( PRT_MESSAGE, "trigger_push without target entity %s\n", target ); + continue; + } //end if + AAS_VectorForBSPEpairKey( ent2, "origin", ent2origin ); + // + height = ent2origin[2] - origin[2]; + gravity = aassettings.sv_gravity; + time = sqrt( height / ( 0.5 * gravity ) ); + if ( !time ) { + botimport.Print( PRT_MESSAGE, "trigger_push without time\n" ); + continue; + } //end if + // set s.origin2 to the push velocity + VectorSubtract( ent2origin, origin, velocity ); + dist = VectorNormalize( velocity ); + forward = dist / time; + //FIXME: why multiply by 1.1 + forward *= 1.1; + VectorScale( velocity, forward, velocity ); + velocity[2] = time * gravity; + //get the areas the jump pad brush is in + areas = AAS_LinkEntityClientBBox( absmins, absmaxs, -1, PRESENCE_CROUCH ); + //* + for ( link = areas; link; link = link->next_area ) + { + if ( link->areanum == 5772 ) { + ret = qfalse; + } + } //*/ + for ( link = areas; link; link = link->next_area ) + { + if ( AAS_AreaJumpPad( link->areanum ) ) { + break; + } + } //end for + if ( !link ) { + botimport.Print( PRT_MESSAGE, "trigger_multiple not in any jump pad area\n" ); + AAS_UnlinkFromAreas( areas ); + continue; + } //end if + // + botimport.Print( PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2] ); + //if there is a horizontal velocity check for a reachability without air control + if ( velocity[0] || velocity[1] ) { + VectorSet( cmdmove, 0, 0, 0 ); + //VectorCopy(velocity, cmdmove); + //cmdmove[2] = 0; + memset( &move, 0, sizeof( aas_clientmove_t ) ); + area2num = 0; + for ( i = 0; i < 20; i++ ) + { + AAS_PredictClientMovement( &move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1, + SE_HITGROUND | SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | SE_TOUCHJUMPPAD | SE_TOUCHTELEPORTER, 0, qfalse ); //qtrue); + area2num = AAS_PointAreaNum( move.endpos ); + for ( link = areas; link; link = link->next_area ) + { + if ( !AAS_AreaJumpPad( link->areanum ) ) { + continue; + } + if ( link->areanum == area2num ) { + break; + } + } //end if + if ( !link ) { + break; + } + VectorCopy( move.endpos, areastart ); + VectorCopy( move.velocity, velocity ); + } //end for + if ( area2num && i < 20 ) { + for ( link = areas; link; link = link->next_area ) + { + if ( !AAS_AreaJumpPad( link->areanum ) ) { + continue; + } + if ( AAS_ReachabilityExists( link->areanum, area2num ) ) { + continue; + } + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if ( !lreach ) { + AAS_UnlinkFromAreas( areas ); + return; + } //end if + lreach->areanum = area2num; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt( velocity[0] * velocity[0] + velocity[1] * velocity[1] ); + VectorCopy( areastart, lreach->start ); + VectorCopy( move.endpos, lreach->end ); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltime = 200; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } //end if + } //end if + // + if ( fabs( velocity[0] ) > 100 || fabs( velocity[1] ) > 100 ) { + continue; + } + //check for areas we can reach with air control + for ( area2num = 1; area2num < ( *aasworld ).numareas; area2num++ ) + { + visualize = qfalse; + /* + if (area2num == 3568) + { + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 3380) + { + visualize = qtrue; + botimport.Print(PRT_MESSAGE, "bah\n"); + } //end if + } //end for + } //end if*/ + //never try to go back to one of the original jumppad areas + //and don't create reachabilities if they already exist + for ( link = areas; link; link = link->next_area ) + { + if ( AAS_ReachabilityExists( link->areanum, area2num ) ) { + break; + } + if ( AAS_AreaJumpPad( link->areanum ) ) { + if ( link->areanum == area2num ) { + break; + } + } //end if + } //end if + if ( link ) { + continue; + } + // + area2 = &( *aasworld ).areas[area2num]; + for ( i = 0; i < area2->numfaces; i++ ) + { + face2num = ( *aasworld ).faceindex[area2->firstface + i]; + face2 = &( *aasworld ).faces[abs( face2num )]; + //if it is not a ground face + if ( !( face2->faceflags & FACE_GROUND ) ) { + continue; + } + //get the center of the face + AAS_FaceCenter( face2num, facecenter ); + //only go higher up + if ( facecenter[2] < areastart[2] ) { + continue; + } + //get the jumppad jump z velocity + zvel = velocity[2]; + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + ret = AAS_HorizontalVelocityForJump( zvel, areastart, facecenter, &speed ); + if ( ret && speed < 150 ) { + //direction towards the face center + VectorSubtract( facecenter, areastart, dir ); + dir[2] = 0; + hordist = VectorNormalize( dir ); + //if (hordist < 1.6 * facecenter[2] - areastart[2]) + { + //get command movement + VectorScale( dir, speed, cmdmove ); + // + AAS_PredictClientMovement( &move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 30, 30, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | + SE_TOUCHJUMPPAD | SE_TOUCHTELEPORTER | SE_HITGROUNDAREA, area2num, visualize ); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if ( move.frames < 30 && + !( move.stopevent & ( SE_ENTERSLIME | SE_ENTERLAVA | SE_HITGROUNDDAMAGE ) ) + && ( move.stopevent & ( SE_HITGROUNDAREA | SE_TOUCHJUMPPAD | SE_TOUCHTELEPORTER ) ) ) { + for ( link = areas; link; link = link->next_area ) + { + if ( !AAS_AreaJumpPad( link->areanum ) ) { + continue; + } + if ( AAS_ReachabilityExists( link->areanum, area2num ) ) { + continue; + } + //create a jumppad reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if ( !lreach ) { + AAS_UnlinkFromAreas( areas ); + return; + } //end if + lreach->areanum = area2num; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt( cmdmove[0] * cmdmove[0] + cmdmove[1] * cmdmove[1] ); + VectorCopy( areastart, lreach->start ); + VectorCopy( facecenter, lreach->end ); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltime = 250; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } //end if + } //end if + } //end for + } //end for + } //end for + AAS_UnlinkFromAreas( areas ); + } //end for +} //end of the function AAS_Reachability_JumpPad +//=========================================================================== +// never point at ground faces +// always a higher and pretty far area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Grapple( int area1num, int area2num ) { + int face2num, i, j, areanum, numareas, areas[20]; + float mingrappleangle, z, hordist; + bsp_trace_t bsptrace; + aas_trace_t trace; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, down = {0, 0, -1}; + vec_t *v; + + //only grapple when on the ground or swimming + if ( !AAS_AreaGrounded( area1num ) && !AAS_AreaSwim( area1num ) ) { + return qfalse; + } + //don't grapple from a crouch area + if ( !( AAS_AreaPresenceType( area1num ) & PRESENCE_NORMAL ) ) { + return qfalse; + } + //NOTE: disabled area swim it doesn't work right + if ( AAS_AreaSwim( area1num ) ) { + return qfalse; + } + // + area1 = &( *aasworld ).areas[area1num]; + area2 = &( *aasworld ).areas[area2num]; + //don't grapple towards way lower areas + if ( area2->maxs[2] < area1->mins[2] ) { + return qfalse; + } + // + VectorCopy( ( *aasworld ).areas[area1num].center, start ); + //if not a swim area + if ( !AAS_AreaSwim( area1num ) ) { + if ( !AAS_PointAreaNum( start ) ) { + Log_Write( "area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2] ); + } + VectorCopy( start, end ); + end[2] -= 1000; + trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, -1 ); + if ( trace.startsolid ) { + return qfalse; + } + VectorCopy( trace.endpos, areastart ); + } //end if + else + { + if ( !( AAS_PointContents( start ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { + return qfalse; + } + } //end else + // + //start is now the start point + // + for ( i = 0; i < area2->numfaces; i++ ) + { + face2num = ( *aasworld ).faceindex[area2->firstface + i]; + face2 = &( *aasworld ).faces[abs( face2num )]; + //if it is not a solid face + if ( !( face2->faceflags & FACE_SOLID ) ) { + continue; + } + //direction towards the first vertex of the face + v = ( *aasworld ).vertexes[( *aasworld ).edges[abs( ( *aasworld ).edgeindex[face2->firstedge] )].v[0]]; + VectorSubtract( v, areastart, dir ); + //if the face plane is facing away + if ( DotProduct( ( *aasworld ).planes[face2->planenum].normal, dir ) > 0 ) { + continue; + } + //get the center of the face + AAS_FaceCenter( face2num, facecenter ); + //only go higher up with the grapple + if ( facecenter[2] < areastart[2] + 64 ) { + continue; + } + //only use vertical faces or downward facing faces + if ( DotProduct( ( *aasworld ).planes[face2->planenum].normal, down ) < 0 ) { + continue; + } + //direction towards the face center + VectorSubtract( facecenter, areastart, dir ); + // + z = dir[2]; + dir[2] = 0; + hordist = VectorLength( dir ); + if ( !hordist ) { + continue; + } + //if too far + if ( hordist > 2000 ) { + continue; + } + //check the minimal angle of the movement + mingrappleangle = 15; //15 degrees + if ( z / hordist < tan( 2 * M_PI * mingrappleangle / 360 ) ) { + continue; + } + // + VectorCopy( facecenter, start ); + VectorMA( facecenter, -500, ( *aasworld ).planes[face2->planenum].normal, end ); + // + bsptrace = AAS_Trace( start, NULL, NULL, end, 0, CONTENTS_SOLID ); + //the grapple won't stick to the sky and the grapple point should be near the AAS wall + if ( ( bsptrace.surface.flags & SURF_SKY ) || ( bsptrace.fraction * 500 > 32 ) ) { + continue; + } + //trace a full bounding box from the area center on the ground to + //the center of the face + VectorSubtract( facecenter, areastart, dir ); + VectorNormalize( dir ); + VectorMA( areastart, 4, dir, start ); + VectorCopy( bsptrace.endpos, end ); + trace = AAS_TraceClientBBox( start, end, PRESENCE_NORMAL, -1 ); + VectorSubtract( trace.endpos, facecenter, dir ); + if ( VectorLength( dir ) > 24 ) { + continue; + } + // + VectorCopy( trace.endpos, start ); + VectorCopy( trace.endpos, end ); + end[2] -= AAS_FallDamageDistance(); + trace = AAS_TraceClientBBox( start, end, PRESENCE_NORMAL, -1 ); + if ( trace.fraction >= 1 ) { + continue; + } + //area to end in + areanum = AAS_PointAreaNum( trace.endpos ); + //if not in lava or slime + if ( ( *aasworld ).areasettings[areanum].contents & AREACONTENTS_LAVA ) { //----(SA) modified since slime is no longer deadly +// if ((*aasworld).areasettings[areanum].contents & (AREACONTENTS_SLIME|AREACONTENTS_LAVA)) + continue; + } //end if + //do not go the the source area + if ( areanum == area1num ) { + continue; + } + //don't create reachabilities if they already exist + if ( AAS_ReachabilityExists( area1num, areanum ) ) { + continue; + } + //only end in areas we can stand + if ( !AAS_AreaGrounded( areanum ) ) { + continue; + } + //never go through cluster portals!! + numareas = AAS_TraceAreas( areastart, bsptrace.endpos, areas, NULL, 20 ); + if ( numareas >= 20 ) { + continue; + } + for ( j = 0; j < numareas; j++ ) + { + if ( ( *aasworld ).areasettings[areas[j]].contents & AREACONTENTS_CLUSTERPORTAL ) { + break; + } + } //end for + if ( j < numareas ) { + continue; + } + //create a new reachability link + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = areanum; + lreach->facenum = face2num; + lreach->edgenum = 0; + VectorCopy( areastart, lreach->start ); + //VectorCopy(facecenter, lreach->end); + VectorCopy( bsptrace.endpos, lreach->end ); + lreach->traveltype = TRAVEL_GRAPPLEHOOK; + VectorSubtract( lreach->end, lreach->start, dir ); + lreach->traveltime = STARTGRAPPLE_TIME + VectorLength( dir ) * 0.25; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_grapple++; + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetWeaponJumpAreaFlags( void ) { + int ent, i; + vec3_t mins = {-15, -15, -15}, maxs = {15, 15, 15}; + vec3_t origin; + int areanum, weaponjumpareas, spawnflags; + char classname[MAX_EPAIRKEY]; + + weaponjumpareas = 0; + for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + if ( + !strcmp( classname, "item_armor_body" ) || + !strcmp( classname, "item_armor_combat" ) || + !strcmp( classname, "item_health_mega" ) || + !strcmp( classname, "weapon_grenadelauncher" ) || + !strcmp( classname, "weapon_rocketlauncher" ) || + !strcmp( classname, "weapon_lightning" ) || + !strcmp( classname, "weapon_sp5" ) || + !strcmp( classname, "weapon_railgun" ) || + !strcmp( classname, "weapon_bfg" ) || + !strcmp( classname, "item_quad" ) || + !strcmp( classname, "item_regen" ) || + !strcmp( classname, "item_invulnerability" ) ) { + if ( AAS_VectorForBSPEpairKey( ent, "origin", origin ) ) { + spawnflags = 0; + AAS_IntForBSPEpairKey( ent, "spawnflags", &spawnflags ); + //if not a stationary item + if ( !( spawnflags & 1 ) ) { + if ( !AAS_DropToFloor( origin, mins, maxs ) ) { + botimport.Print( PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2] ); + } //end if + } //end if + //areanum = AAS_PointAreaNum(origin); + areanum = AAS_BestReachableArea( origin, mins, maxs, origin ); + //the bot may rocket jump towards this area + ( *aasworld ).areasettings[areanum].areaflags |= AREA_WEAPONJUMP; + // + if ( !AAS_AreaGrounded( areanum ) ) { + botimport.Print( PRT_MESSAGE, "area not grounded\n" ); + } + // + weaponjumpareas++; + } //end if + } //end if + } //end for + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_JUMPPAD ) { + ( *aasworld ).areasettings[i].areaflags |= AREA_WEAPONJUMP; + weaponjumpareas++; + } //end if + } //end for + botimport.Print( PRT_MESSAGE, "%d weapon jump areas\n", weaponjumpareas ); +} //end of the function AAS_SetWeaponJumpAreaFlags +//=========================================================================== +// create a possible weapon jump reachability from area1 to area2 +// +// check if there's a cool item in the second area +// check if area1 is lower than area2 +// check if the bot can rocketjump from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_WeaponJump( int area1num, int area2num ) { + int face2num, i, n, ret; + float speed, zvel, hordist; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, cmdmove; // teststart; + vec3_t velocity; + aas_clientmove_t move; + aas_trace_t trace; + + if ( !AAS_AreaGrounded( area1num ) || AAS_AreaSwim( area1num ) ) { + return qfalse; + } + if ( !AAS_AreaGrounded( area2num ) ) { + return qfalse; + } + //NOTE: only weapon jump towards areas with an interesting item in it?? + if ( !( ( *aasworld ).areasettings[area2num].areaflags & AREA_WEAPONJUMP ) ) { + return qfalse; + } + // + area1 = &( *aasworld ).areas[area1num]; + area2 = &( *aasworld ).areas[area2num]; + //don't weapon jump towards way lower areas + if ( area2->maxs[2] < area1->mins[2] ) { + return qfalse; + } + // + VectorCopy( ( *aasworld ).areas[area1num].center, start ); + //if not a swim area + if ( !AAS_PointAreaNum( start ) ) { + Log_Write( "area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2] ); + } + VectorCopy( start, end ); + end[2] -= 1000; + trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, -1 ); + if ( trace.startsolid ) { + return qfalse; + } + VectorCopy( trace.endpos, areastart ); + // + //areastart is now the start point + // + for ( i = 0; i < area2->numfaces; i++ ) + { + face2num = ( *aasworld ).faceindex[area2->firstface + i]; + face2 = &( *aasworld ).faces[abs( face2num )]; + //if it is not a solid face + if ( !( face2->faceflags & FACE_GROUND ) ) { + continue; + } + //get the center of the face + AAS_FaceCenter( face2num, facecenter ); + //only go higher up with weapon jumps + if ( facecenter[2] < areastart[2] + 64 ) { + continue; + } + //NOTE: set to 2 to allow bfg jump reachabilities + for ( n = 0; n < 1 /*2*/; n++ ) + { + //get the rocket jump z velocity + if ( n ) { + zvel = AAS_BFGJumpZVelocity( areastart ); + } else { zvel = AAS_RocketJumpZVelocity( areastart );} + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + ret = AAS_HorizontalVelocityForJump( zvel, areastart, facecenter, &speed ); + if ( ret && speed < 270 ) { + //direction towards the face center + VectorSubtract( facecenter, areastart, dir ); + dir[2] = 0; + hordist = VectorNormalize( dir ); + //if (hordist < 1.6 * (facecenter[2] - areastart[2])) + { + //get command movement + VectorScale( dir, speed, cmdmove ); + VectorSet( velocity, 0, 0, zvel ); + /* + //get command movement + VectorScale(dir, speed, velocity); + velocity[2] = zvel; + VectorSet(cmdmove, 0, 0, 0); + */ + // + AAS_PredictClientMovement( &move, -1, areastart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1, + SE_ENTERWATER | SE_ENTERSLIME | + SE_ENTERLAVA | SE_HITGROUNDDAMAGE | + SE_TOUCHJUMPPAD | SE_HITGROUNDAREA, area2num, qfalse ); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if ( move.frames < 30 && + !( move.stopevent & ( SE_ENTERSLIME | SE_ENTERLAVA | SE_HITGROUNDDAMAGE ) ) + && ( move.stopevent & ( SE_HITGROUNDAREA | SE_TOUCHJUMPPAD ) ) ) { + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if ( !lreach ) { + return qfalse; + } + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy( areastart, lreach->start ); + VectorCopy( facecenter, lreach->end ); + if ( n ) { + lreach->traveltype = TRAVEL_BFGJUMP; + } else { lreach->traveltype = TRAVEL_ROCKETJUMP;} + lreach->traveltime = 300; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_rocketjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end for + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_WeaponJump +//=========================================================================== +// calculates additional walk off ledge reachabilities for the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_WalkOffLedge( int areanum ) { + int i, j, k, l, m, n; + int face1num, face2num, face3num, edge1num, edge2num, edge3num; + int otherareanum, gap, reachareanum, side; + aas_area_t *area, *area2; + aas_face_t *face1, *face2, *face3; + aas_edge_t *edge; + aas_plane_t *plane; + vec_t *v1, *v2; + vec3_t sharededgevec, mid, dir, testend; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if ( !AAS_AreaGrounded( areanum ) || AAS_AreaSwim( areanum ) ) { + return; + } + // + area = &( *aasworld ).areas[areanum]; + // + for ( i = 0; i < area->numfaces; i++ ) + { + face1num = ( *aasworld ).faceindex[area->firstface + i]; + face1 = &( *aasworld ).faces[abs( face1num )]; + //face 1 must be a ground face + if ( !( face1->faceflags & FACE_GROUND ) ) { + continue; + } + //go through all the edges of this ground face + for ( k = 0; k < face1->numedges; k++ ) + { + edge1num = ( *aasworld ).edgeindex[face1->firstedge + k]; + //find another not ground face using this same edge + for ( j = 0; j < area->numfaces; j++ ) + { + face2num = ( *aasworld ).faceindex[area->firstface + j]; + face2 = &( *aasworld ).faces[abs( face2num )]; + //face 2 may not be a ground face + if ( face2->faceflags & FACE_GROUND ) { + continue; + } + //compare all the edges + for ( l = 0; l < face2->numedges; l++ ) + { + edge2num = ( *aasworld ).edgeindex[face2->firstedge + l]; + if ( abs( edge1num ) == abs( edge2num ) ) { + //get the area at the other side of the face + if ( face2->frontarea == areanum ) { + otherareanum = face2->backarea; + } else { otherareanum = face2->frontarea;} + // + area2 = &( *aasworld ).areas[otherareanum]; + //if the other area is grounded! + if ( ( *aasworld ).areasettings[otherareanum].areaflags & AREA_GROUNDED ) { + //check for a possible gap + gap = qfalse; + for ( n = 0; n < area2->numfaces; n++ ) + { + face3num = ( *aasworld ).faceindex[area2->firstface + n]; + //may not be the shared face of the two areas + if ( abs( face3num ) == abs( face2num ) ) { + continue; + } + // + face3 = &( *aasworld ).faces[abs( face3num )]; + //find an edge shared by all three faces + for ( m = 0; m < face3->numedges; m++ ) + { + edge3num = ( *aasworld ).edgeindex[face3->firstedge + m]; + //but the edge should be shared by all three faces + if ( abs( edge3num ) == abs( edge1num ) ) { + if ( !( face3->faceflags & FACE_SOLID ) ) { + gap = qtrue; + break; + } //end if + // + if ( face3->faceflags & FACE_GROUND ) { + gap = qfalse; + break; + } //end if + //FIXME: there are more situations to be handled + gap = qtrue; + break; + } //end if + } //end for + if ( m < face3->numedges ) { + break; + } + } //end for + if ( !gap ) { + break; + } + } //end if + //check for a walk off ledge reachability + edge = &( *aasworld ).edges[abs( edge1num )]; + side = edge1num < 0; + // + v1 = ( *aasworld ).vertexes[edge->v[side]]; + v2 = ( *aasworld ).vertexes[edge->v[!side]]; + // + plane = &( *aasworld ).planes[face1->planenum]; + //get the points really into the areas + VectorSubtract( v2, v1, sharededgevec ); + CrossProduct( plane->normal, sharededgevec, dir ); + VectorNormalize( dir ); + // + VectorAdd( v1, v2, mid ); + VectorScale( mid, 0.5, mid ); + VectorMA( mid, 8, dir, mid ); + // + VectorCopy( mid, testend ); + testend[2] -= 1000; + trace = AAS_TraceClientBBox( mid, testend, PRESENCE_CROUCH, -1 ); + // + if ( trace.startsolid ) { + //Log_Write("area %d: trace.startsolid\r\n", areanum); + break; + } //end if + reachareanum = AAS_PointAreaNum( trace.endpos ); + if ( reachareanum == areanum ) { + //Log_Write("area %d: same area\r\n", areanum); + break; + } //end if + if ( AAS_ReachabilityExists( areanum, reachareanum ) ) { + //Log_Write("area %d: reachability already exists\r\n", areanum); + break; + } //end if + if ( !AAS_AreaGrounded( reachareanum ) && !AAS_AreaSwim( reachareanum ) ) { + //Log_Write("area %d, reach area %d: not grounded and not swim\r\n", areanum, reachareanum); + break; + } //end if + // + if ( ( *aasworld ).areasettings[reachareanum].contents & AREACONTENTS_LAVA ) { //----(SA) modified since slime is no longer deadly +// if ((*aasworld).areasettings[reachareanum].contents & (AREACONTENTS_SLIME | AREACONTENTS_LAVA)) + //Log_Write("area %d, reach area %d: lava or slime\r\n", areanum, reachareanum); + break; + } //end if + lreach = AAS_AllocReachability(); + if ( !lreach ) { + break; + } + lreach->areanum = reachareanum; + lreach->facenum = 0; + lreach->edgenum = edge1num; + VectorCopy( mid, lreach->start ); + VectorCopy( trace.endpos, lreach->end ); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = STARTWALKOFFLEDGE_TIME + fabs( mid[2] - trace.endpos[2] ) * 50 / aassettings.sv_gravity; + if ( !AAS_AreaSwim( reachareanum ) && !AAS_AreaJumpPad( reachareanum ) ) { + if ( AAS_FallDelta( mid[2] - trace.endpos[2] ) > FALLDELTA_5DAMAGE ) { + lreach->traveltime += FALLDAMAGE_5_TIME; + } //end if + else if ( AAS_FallDelta( mid[2] - trace.endpos[2] ) > FALLDELTA_10DAMAGE ) { + lreach->traveltime += FALLDAMAGE_10_TIME; + } //end if + } //end if + lreach->next = areareachability[areanum]; + areareachability[areanum] = lreach; + //we've got another walk off ledge reachability + reach_walkoffledge++; + } //end if + } //end for + } //end for + } //end for + } //end for +} //end of the function AAS_Reachability_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreReachability( void ) { + int i; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_reachability_t *reach; + + if ( ( *aasworld ).reachability ) { + FreeMemory( ( *aasworld ).reachability ); + } + ( *aasworld ).reachability = (aas_reachability_t *) GetClearedMemory( ( numlreachabilities + 10 ) * sizeof( aas_reachability_t ) ); + ( *aasworld ).reachabilitysize = 1; + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + areasettings = &( *aasworld ).areasettings[i]; + areasettings->firstreachablearea = ( *aasworld ).reachabilitysize; + areasettings->numreachableareas = 0; + for ( lreach = areareachability[i]; lreach; lreach = lreach->next ) + { + reach = &( *aasworld ).reachability[areasettings->firstreachablearea + + areasettings->numreachableareas]; + reach->areanum = lreach->areanum; + reach->facenum = lreach->facenum; + reach->edgenum = lreach->edgenum; + VectorCopy( lreach->start, reach->start ); + VectorCopy( lreach->end, reach->end ); + reach->traveltype = lreach->traveltype; + reach->traveltime = lreach->traveltime; + // + areasettings->numreachableareas++; + } //end for + ( *aasworld ).reachabilitysize += areasettings->numreachableareas; + } //end for +} //end of the function AAS_StoreReachability +//=========================================================================== +// +// TRAVEL_WALK 100% equal floor height + steps +// TRAVEL_CROUCH 100% +// TRAVEL_BARRIERJUMP 100% +// TRAVEL_JUMP 80% +// TRAVEL_LADDER 100% + fall down from ladder + jump up to ladder +// TRAVEL_WALKOFFLEDGE 90% walk off very steep walls? +// TRAVEL_SWIM 100% +// TRAVEL_WATERJUMP 100% +// TRAVEL_TELEPORT 100% +// TRAVEL_ELEVATOR 100% +// TRAVEL_GRAPPLEHOOK 100% +// TRAVEL_DOUBLEJUMP 0% +// TRAVEL_RAMPJUMP 0% +// TRAVEL_STRAFEJUMP 0% +// TRAVEL_ROCKETJUMP 100% (currently limited towards areas with items) +// TRAVEL_BFGJUMP 0% (currently disabled) +// TRAVEL_JUMPPAD 100% +// TRAVEL_FUNCBOB 100% +// +// Parameter: - +// Returns: true if NOT finished +// Changes Globals: - +//=========================================================================== +int AAS_ContinueInitReachability( float time ) { + int i, j, todo, start_time; + static float framereachability, reachability_delay; + static int lastpercentage; + + if ( !( *aasworld ).loaded ) { + return qfalse; + } + //if reachability is calculated for all areas + if ( ( *aasworld ).reachabilityareas >= ( *aasworld ).numareas + 2 ) { + return qfalse; + } + //if starting with area 1 (area 0 is a dummy) + if ( ( *aasworld ).reachabilityareas == 1 ) { + botimport.Print( PRT_MESSAGE, "calculating reachability...\n" ); + lastpercentage = 0; + framereachability = 2000; + reachability_delay = 1000; + } //end if + //number of areas to calculate reachability for this cycle + todo = ( *aasworld ).reachabilityareas + (int) framereachability; + start_time = Sys_MilliSeconds(); + //loop over the areas + for ( i = ( *aasworld ).reachabilityareas; i < ( *aasworld ).numareas && i < todo; i++ ) + { + ( *aasworld ).reachabilityareas++; + //only create jumppad reachabilities from jumppad areas + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_JUMPPAD ) { + continue; + } //end if + //loop over the areas + for ( j = 1; j < ( *aasworld ).numareas; j++ ) + { + if ( i == j ) { + continue; + } + //never create reachabilities from teleporter or jumppad areas to regular areas + if ( ( *aasworld ).areasettings[i].contents & ( AREACONTENTS_TELEPORTER | AREACONTENTS_JUMPPAD ) ) { + if ( !( ( *aasworld ).areasettings[j].contents & ( AREACONTENTS_TELEPORTER | AREACONTENTS_JUMPPAD ) ) ) { + continue; + } //end if + } //end if + //if there already is a reachability link from area i to j + if ( AAS_ReachabilityExists( i, j ) ) { + continue; + } + //check for a swim reachability + if ( AAS_Reachability_Swim( i, j ) ) { + continue; + } + //check for a simple walk on equal floor height reachability + if ( AAS_Reachability_EqualFloorHeight( i, j ) ) { + continue; + } + //check for step, barrier, waterjump and walk off ledge reachabilities + if ( AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge( i, j ) ) { + continue; + } + //check for ladder reachabilities + if ( AAS_Reachability_Ladder( i, j ) ) { + continue; + } + //check for a jump reachability + if ( AAS_Reachability_Jump( i, j ) ) { + continue; + } + } //end for + //never create these reachabilities from teleporter or jumppad areas + if ( ( *aasworld ).areasettings[i].contents & ( AREACONTENTS_TELEPORTER | AREACONTENTS_JUMPPAD ) ) { + continue; + } //end if + //loop over the areas + for ( j = 1; j < ( *aasworld ).numareas; j++ ) + { + if ( i == j ) { + continue; + } + // + if ( AAS_ReachabilityExists( i, j ) ) { + continue; + } + //check for a grapple hook reachability +// Ridah, no grapple +// AAS_Reachability_Grapple(i, j); + //check for a weapon jump reachability +// Ridah, no weapon jumping +// AAS_Reachability_WeaponJump(i, j); + } //end for + //if the calculation took more time than the max reachability delay + if ( Sys_MilliSeconds() - start_time > (int) reachability_delay ) { + break; + } + // + if ( ( *aasworld ).reachabilityareas * 1000 / ( *aasworld ).numareas > lastpercentage ) { + break; + } + } //end for + // + if ( ( *aasworld ).reachabilityareas == ( *aasworld ).numareas ) { + botimport.Print( PRT_MESSAGE, "\r%6.1f%%", (float) 100.0 ); + botimport.Print( PRT_MESSAGE, "\nplease wait while storing reachability...\n" ); + ( *aasworld ).reachabilityareas++; + } //end if + //if this is the last step in the reachability calculations + else if ( ( *aasworld ).reachabilityareas == ( *aasworld ).numareas + 1 ) { + //create additional walk off ledge reachabilities for every area + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + //only create jumppad reachabilities from jumppad areas + if ( ( *aasworld ).areasettings[i].contents & AREACONTENTS_JUMPPAD ) { + continue; + } //end if + AAS_Reachability_WalkOffLedge( i ); + } //end for + //create jump pad reachabilities + AAS_Reachability_JumpPad(); + //create teleporter reachabilities + AAS_Reachability_Teleport(); + //create elevator (func_plat) reachabilities + AAS_Reachability_Elevator(); + //create func_bobbing reachabilities + AAS_Reachability_FuncBobbing(); + // +//#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "%6d reach swim\n", reach_swim ); + botimport.Print( PRT_MESSAGE, "%6d reach equal floor\n", reach_equalfloor ); + botimport.Print( PRT_MESSAGE, "%6d reach step\n", reach_step ); + botimport.Print( PRT_MESSAGE, "%6d reach barrier\n", reach_barrier ); + botimport.Print( PRT_MESSAGE, "%6d reach waterjump\n", reach_waterjump ); + botimport.Print( PRT_MESSAGE, "%6d reach walkoffledge\n", reach_walkoffledge ); + botimport.Print( PRT_MESSAGE, "%6d reach jump\n", reach_jump ); + botimport.Print( PRT_MESSAGE, "%6d reach ladder\n", reach_ladder ); + botimport.Print( PRT_MESSAGE, "%6d reach walk\n", reach_walk ); + botimport.Print( PRT_MESSAGE, "%6d reach teleport\n", reach_teleport ); + botimport.Print( PRT_MESSAGE, "%6d reach funcbob\n", reach_funcbob ); + botimport.Print( PRT_MESSAGE, "%6d reach elevator\n", reach_elevator ); + botimport.Print( PRT_MESSAGE, "%6d reach grapple\n", reach_grapple ); + botimport.Print( PRT_MESSAGE, "%6d reach rocketjump\n", reach_rocketjump ); + botimport.Print( PRT_MESSAGE, "%6d reach jumppad\n", reach_jumppad ); +//#endif + //*/ + //store all the reachabilities + AAS_StoreReachability(); + //free the reachability link heap + AAS_ShutDownReachabilityHeap(); + // + FreeMemory( areareachability ); + // + ( *aasworld ).reachabilityareas++; + // + botimport.Print( PRT_MESSAGE, "calculating clusters...\n" ); + } //end if + else + { + lastpercentage = ( *aasworld ).reachabilityareas * 1000 / ( *aasworld ).numareas; + botimport.Print( PRT_MESSAGE, "\r%6.1f%%", (float) lastpercentage / 10 ); + } //end else + //not yet finished + return qtrue; +} //end of the function AAS_ContinueInitReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitReachability( void ) { + if ( !( *aasworld ).loaded ) { + return; + } + + if ( ( *aasworld ).reachabilitysize ) { +#ifndef BSPC + if ( !( (int)LibVarGetValue( "forcereachability" ) ) ) { + ( *aasworld ).reachabilityareas = ( *aasworld ).numareas + 2; + return; + } //end if +#else + ( *aasworld ).reachabilityareas = ( *aasworld ).numareas + 2; + return; +#endif //BSPC + } //end if + ( *aasworld ).savefile = qtrue; + //start with area 1 because area zero is a dummy + ( *aasworld ).reachabilityareas = 1; + //setup the heap with reachability links + AAS_SetupReachabilityHeap(); + //allocate area reachability link array + areareachability = (aas_lreachability_t **) GetClearedMemory( + ( *aasworld ).numareas * sizeof( aas_lreachability_t * ) ); + // + AAS_SetWeaponJumpAreaFlags(); +} //end of the function AAS_InitReachable diff --git a/src/botlib/be_aas_reach.h b/src/botlib/be_aas_reach.h new file mode 100644 index 0000000..e7a20ff --- /dev/null +++ b/src/botlib/be_aas_reach.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_reach.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize calculating the reachabilities +void AAS_InitReachability( void ); +//continue calculating the reachabilities +int AAS_ContinueInitReachability( float time ); +// +int AAS_BestReachableLinkArea( aas_link_t *areas ); +#endif //AASINTERN + +//returns true if the are has reachabilities to other areas +int AAS_AreaReachability( int areanum ); +//returns the best reachable area and goal origin for a bounding box at the given origin +int AAS_BestReachableArea( vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin ); +//returns the next reachability using the given model +int AAS_NextModelReachability( int num, int modelnum ); +//returns the total area of the ground faces of the given area +float AAS_AreaGroundFaceArea( int areanum ); +//returns true if the area is crouch only +int AAS_AreaCrouch( int areanum ); +//returns true if a player can swim in this area +int AAS_AreaSwim( int areanum ); +//returns true if the area is filled with a liquid +int AAS_AreaLiquid( int areanum ); +//returns true if the area contains lava +int AAS_AreaLava( int areanum ); +//returns true if the area contains slime +int AAS_AreaSlime( int areanum ); +//returns true if the area has one or more ground faces +int AAS_AreaGrounded( int areanum ); +//returns true if the area has one or more ladder faces +int AAS_AreaLadder( int areanum ); +//returns true if the area is a jump pad +int AAS_AreaJumpPad( int areanum ); +//returns true if the area is donotenter +int AAS_AreaDoNotEnter( int areanum ); +//returns true if the area is donotenterlarge +int AAS_AreaDoNotEnterLarge( int areanum ); diff --git a/src/botlib/be_aas_route.c b/src/botlib/be_aas_route.c new file mode 100644 index 0000000..f364314 --- /dev/null +++ b/src/botlib/be_aas_route.c @@ -0,0 +1,2485 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_route.c + * + * desc: AAS + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_crc.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ROUTING_DEBUG + +//travel time in hundreths of a second = distance * 100 / speed +#define DISTANCEFACTOR_CROUCH 1.3 //crouch speed = 100 +#define DISTANCEFACTOR_SWIM 1 //should be 0.66, swim speed = 150 +#define DISTANCEFACTOR_WALK 0.33 //walk speed = 300 + +// Ridah, scale traveltimes with ground steepness of area +#define GROUNDSTEEPNESS_TIMESCALE 20 // this is the maximum scale, 1 being the usual for a flat ground + +//cache refresh time +#define CACHE_REFRESHTIME 15.0 //15 seconds refresh time + +//maximum number of routing updates each frame +#define MAX_FRAMEROUTINGUPDATES 100 + + +/* + + area routing cache: + stores the distances within one cluster to a specific goal area + this goal area is in this same cluster and could be a cluster portal + for every cluster there's a list with routing cache for every area + in that cluster (including the portals of that cluster) + area cache stores (*aasworld).clusters[?].numreachabilityareas travel times + + portal routing cache: + stores the distances of all portals to a specific goal area + this goal area could be in any cluster and could also be a cluster portal + for every area ((*aasworld).numareas) the portal cache stores + (*aasworld).numportals travel times + +*/ + +#ifdef ROUTING_DEBUG +int numareacacheupdates; +int numportalcacheupdates; +#endif //ROUTING_DEBUG + +int routingcachesize; +int max_routingcachesize; + +// Ridah, routing memory calls go here, so we can change between Hunk/Zone easily +void *AAS_RoutingGetMemory( int size ) { + return GetClearedMemory( size ); +} + +void AAS_RoutingFreeMemory( void *ptr ) { + FreeMemory( ptr ); +} +// done. + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef ROUTING_DEBUG +void AAS_RoutingInfo( void ) { + botimport.Print( PRT_MESSAGE, "%d area cache updates\n", numareacacheupdates ); + botimport.Print( PRT_MESSAGE, "%d portal cache updates\n", numportalcacheupdates ); + botimport.Print( PRT_MESSAGE, "%d bytes routing cache\n", routingcachesize ); +} //end of the function AAS_RoutingInfo +#endif //ROUTING_DEBUG +//=========================================================================== +// returns the number of the area in the cluster +// assumes the given area is in the given cluster or a portal of the cluster +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline int AAS_ClusterAreaNum( int cluster, int areanum ) { + int side, areacluster; + + areacluster = ( *aasworld ).areasettings[areanum].cluster; + if ( areacluster > 0 ) { + return ( *aasworld ).areasettings[areanum].clusterareanum; + } else + { +/*#ifdef ROUTING_DEBUG + if ((*aasworld).portals[-areacluster].frontcluster != cluster && + (*aasworld).portals[-areacluster].backcluster != cluster) + { + botimport.Print(PRT_ERROR, "portal %d: does not belong to cluster %d\n" + , -areacluster, cluster); + } //end if +#endif //ROUTING_DEBUG*/ + side = ( *aasworld ).portals[-areacluster].frontcluster != cluster; + return ( *aasworld ).portals[-areacluster].clusterareanum[side]; + } //end else +} //end of the function AAS_ClusterAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitTravelFlagFromType( void ) { + int i; + + for ( i = 0; i < MAX_TRAVELTYPES; i++ ) + { + ( *aasworld ).travelflagfortype[i] = TFL_INVALID; + } //end for + ( *aasworld ).travelflagfortype[TRAVEL_INVALID] = TFL_INVALID; + ( *aasworld ).travelflagfortype[TRAVEL_WALK] = TFL_WALK; + ( *aasworld ).travelflagfortype[TRAVEL_CROUCH] = TFL_CROUCH; + ( *aasworld ).travelflagfortype[TRAVEL_BARRIERJUMP] = TFL_BARRIERJUMP; + ( *aasworld ).travelflagfortype[TRAVEL_JUMP] = TFL_JUMP; + ( *aasworld ).travelflagfortype[TRAVEL_LADDER] = TFL_LADDER; + ( *aasworld ).travelflagfortype[TRAVEL_WALKOFFLEDGE] = TFL_WALKOFFLEDGE; + ( *aasworld ).travelflagfortype[TRAVEL_SWIM] = TFL_SWIM; + ( *aasworld ).travelflagfortype[TRAVEL_WATERJUMP] = TFL_WATERJUMP; + ( *aasworld ).travelflagfortype[TRAVEL_TELEPORT] = TFL_TELEPORT; + ( *aasworld ).travelflagfortype[TRAVEL_ELEVATOR] = TFL_ELEVATOR; + ( *aasworld ).travelflagfortype[TRAVEL_ROCKETJUMP] = TFL_ROCKETJUMP; + ( *aasworld ).travelflagfortype[TRAVEL_BFGJUMP] = TFL_BFGJUMP; + ( *aasworld ).travelflagfortype[TRAVEL_GRAPPLEHOOK] = TFL_GRAPPLEHOOK; + ( *aasworld ).travelflagfortype[TRAVEL_DOUBLEJUMP] = TFL_DOUBLEJUMP; + ( *aasworld ).travelflagfortype[TRAVEL_RAMPJUMP] = TFL_RAMPJUMP; + ( *aasworld ).travelflagfortype[TRAVEL_STRAFEJUMP] = TFL_STRAFEJUMP; + ( *aasworld ).travelflagfortype[TRAVEL_JUMPPAD] = TFL_JUMPPAD; + ( *aasworld ).travelflagfortype[TRAVEL_FUNCBOB] = TFL_FUNCBOB; +} //end of the function AAS_InitTravelFlagFromType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TravelFlagForType( int traveltype ) { + if ( traveltype < 0 || traveltype >= MAX_TRAVELTYPES ) { + return TFL_INVALID; + } + return ( *aasworld ).travelflagfortype[traveltype]; +} //end of the function AAS_TravelFlagForType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline float AAS_RoutingTime( void ) { + return AAS_Time(); +} //end of the function AAS_RoutingTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCache( aas_routingcache_t *cache ) { + routingcachesize -= cache->size; + AAS_RoutingFreeMemory( cache ); +} //end of the function AAS_FreeRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheInCluster( int clusternum ) { + int i; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + if ( !( *aasworld ).clusterareacache ) { + return; + } + cluster = &( *aasworld ).clusters[clusternum]; + for ( i = 0; i < cluster->numareas; i++ ) + { + for ( cache = ( *aasworld ).clusterareacache[clusternum][i]; cache; cache = nextcache ) + { + nextcache = cache->next; + AAS_FreeRoutingCache( cache ); + } //end for + ( *aasworld ).clusterareacache[clusternum][i] = NULL; + } //end for +} //end of the function AAS_RemoveRoutingCacheInCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheUsingArea( int areanum ) { + int i, clusternum; + aas_routingcache_t *cache, *nextcache; + + clusternum = ( *aasworld ).areasettings[areanum].cluster; + if ( clusternum > 0 ) { + //remove all the cache in the cluster the area is in + AAS_RemoveRoutingCacheInCluster( clusternum ); + } //end if + else + { + // if this is a portal remove all cache in both the front and back cluster + AAS_RemoveRoutingCacheInCluster( ( *aasworld ).portals[-clusternum].frontcluster ); + AAS_RemoveRoutingCacheInCluster( ( *aasworld ).portals[-clusternum].backcluster ); + } //end else + // remove all portal cache + if ( ( *aasworld ).portalcache ) { + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + //refresh portal cache + for ( cache = ( *aasworld ).portalcache[i]; cache; cache = nextcache ) + { + nextcache = cache->next; + AAS_FreeRoutingCache( cache ); + } //end for + ( *aasworld ).portalcache[i] = NULL; + } //end for + } +} //end of the function AAS_RemoveRoutingCacheUsingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EnableRoutingArea( int areanum, int enable ) { + int flags; + + if ( areanum <= 0 || areanum >= ( *aasworld ).numareas ) { + if ( bot_developer ) { + botimport.Print( PRT_ERROR, "AAS_EnableRoutingArea: areanum %d out of range\n", areanum ); + } //end if + return 0; + } //end if + flags = ( *aasworld ).areasettings[areanum].areaflags & AREA_DISABLED; + if ( enable < 0 ) { + return !flags; + } + + if ( enable ) { + ( *aasworld ).areasettings[areanum].areaflags &= ~AREA_DISABLED; + } else { + ( *aasworld ).areasettings[areanum].areaflags |= AREA_DISABLED; + } + // if the status of the area changed + if ( ( flags & AREA_DISABLED ) != ( ( *aasworld ).areasettings[areanum].areaflags & AREA_DISABLED ) ) { + //remove all routing cache involving this area + AAS_RemoveRoutingCacheUsingArea( areanum ); + } //end if + return !flags; +} //end of the function AAS_EnableRoutingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateReversedReachability( void ) { + int i, n; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + char *ptr; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif + //free reversed links that have already been created + if ( ( *aasworld ).reversedreachability ) { + AAS_RoutingFreeMemory( ( *aasworld ).reversedreachability ); + } + //allocate memory for the reversed reachability links + ptr = (char *) AAS_RoutingGetMemory( ( *aasworld ).numareas * sizeof( aas_reversedreachability_t ) + + ( *aasworld ).reachabilitysize * sizeof( aas_reversedlink_t ) ); + // + ( *aasworld ).reversedreachability = (aas_reversedreachability_t *) ptr; + //pointer to the memory for the reversed links + ptr += ( *aasworld ).numareas * sizeof( aas_reversedreachability_t ); + //check all other areas for reachability links to the area + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + //settings of the area + settings = &( *aasworld ).areasettings[i]; + //check the reachability links + for ( n = 0; n < settings->numreachableareas; n++ ) + { + //reachability link + reach = &( *aasworld ).reachability[settings->firstreachablearea + n]; + // + revlink = (aas_reversedlink_t *) ptr; + ptr += sizeof( aas_reversedlink_t ); + // + revlink->areanum = i; + revlink->linknum = settings->firstreachablearea + n; + revlink->next = ( *aasworld ).reversedreachability[reach->areanum].first; + ( *aasworld ).reversedreachability[reach->areanum].first = revlink; + ( *aasworld ).reversedreachability[reach->areanum].numlinks++; + } //end for + } //end for +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "reversed reachability %d msec\n", Sys_MilliSeconds() - starttime ); +#endif //DEBUG +} //end of the function AAS_CreateReversedReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaGroundSteepnessScale( int areanum ) { + return ( 1.0 + ( *aasworld ).areasettings[areanum].groundsteepness * (float)( GROUNDSTEEPNESS_TIMESCALE - 1 ) ); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_AreaTravelTime( int areanum, vec3_t start, vec3_t end ) { + int intdist; + float dist; + vec3_t dir; + + VectorSubtract( start, end, dir ); + dist = VectorLength( dir ); + // Ridah, factor in the groundsteepness now + dist *= AAS_AreaGroundSteepnessScale( areanum ); + + //if crouch only area + if ( AAS_AreaCrouch( areanum ) ) { + dist *= DISTANCEFACTOR_CROUCH; + } + //if swim area + else if ( AAS_AreaSwim( areanum ) ) { + dist *= DISTANCEFACTOR_SWIM; + } + //normal walk area + else {dist *= DISTANCEFACTOR_WALK;} + // + intdist = (int) dist; + //make sure the distance isn't zero + if ( intdist <= 0 ) { + intdist = 1; + } + return intdist; +} //end of the function AAS_AreaTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CalculateAreaTravelTimes( void ) { + int i, l, n, size; + char *ptr; + vec3_t end; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + int starttime; + + starttime = Sys_MilliSeconds(); + //if there are still area travel times, free the memory + if ( ( *aasworld ).areatraveltimes ) { + AAS_RoutingFreeMemory( ( *aasworld ).areatraveltimes ); + } + //get the total size of all the area travel times + size = ( *aasworld ).numareas * sizeof( unsigned short ** ); + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + revreach = &( *aasworld ).reversedreachability[i]; + //settings of the area + settings = &( *aasworld ).areasettings[i]; + // + size += settings->numreachableareas * sizeof( unsigned short * ); + // + size += settings->numreachableareas * revreach->numlinks * sizeof( unsigned short ); + } //end for + //allocate memory for the area travel times + ptr = (char *) AAS_RoutingGetMemory( size ); + ( *aasworld ).areatraveltimes = (unsigned short ***) ptr; + ptr += ( *aasworld ).numareas * sizeof( unsigned short ** ); + //calcluate the travel times for all the areas + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + //reversed reachabilities of this area + revreach = &( *aasworld ).reversedreachability[i]; + //settings of the area + settings = &( *aasworld ).areasettings[i]; + // + ( *aasworld ).areatraveltimes[i] = (unsigned short **) ptr; + ptr += settings->numreachableareas * sizeof( unsigned short * ); + // + reach = &( *aasworld ).reachability[settings->firstreachablearea]; + for ( l = 0; l < settings->numreachableareas; l++, reach++ ) + { + ( *aasworld ).areatraveltimes[i][l] = (unsigned short *) ptr; + ptr += revreach->numlinks * sizeof( unsigned short ); + //reachability link + // + for ( n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++ ) + { + VectorCopy( ( *aasworld ).reachability[revlink->linknum].end, end ); + // + ( *aasworld ).areatraveltimes[i][l][n] = AAS_AreaTravelTime( i, end, reach->start ); + } //end for + } //end for + } //end for +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "area travel times %d msec\n", Sys_MilliSeconds() - starttime ); +#endif //DEBUG +} //end of the function AAS_CalculateAreaTravelTimes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PortalMaxTravelTime( int portalnum ) { + int l, n, t, maxt; + aas_portal_t *portal; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_areasettings_t *settings; + + portal = &( *aasworld ).portals[portalnum]; + //reversed reachabilities of this portal area + revreach = &( *aasworld ).reversedreachability[portal->areanum]; + //settings of the portal area + settings = &( *aasworld ).areasettings[portal->areanum]; + // + maxt = 0; + for ( l = 0; l < settings->numreachableareas; l++ ) + { + for ( n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++ ) + { + t = ( *aasworld ).areatraveltimes[portal->areanum][l][n]; + if ( t > maxt ) { + maxt = t; + } //end if + } //end for + } //end for + return maxt; +} //end of the function AAS_PortalMaxTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalMaxTravelTimes( void ) { + int i; + + if ( ( *aasworld ).portalmaxtraveltimes ) { + AAS_RoutingFreeMemory( ( *aasworld ).portalmaxtraveltimes ); + } + + ( *aasworld ).portalmaxtraveltimes = (int *) AAS_RoutingGetMemory( ( *aasworld ).numportals * sizeof( int ) ); + + for ( i = 0; i < ( *aasworld ).numportals; i++ ) + { + ( *aasworld ).portalmaxtraveltimes[i] = AAS_PortalMaxTravelTime( i ); + //botimport.Print(PRT_MESSAGE, "portal %d max tt = %d\n", i, (*aasworld).portalmaxtraveltimes[i]); + } //end for +} //end of the function AAS_InitPortalMaxTravelTimes +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkCache(aas_routingcache_t *cache) +{ + if (cache->time_next) cache->time_next->time_prev = cache->time_prev; + else newestcache = cache->time_prev; + if (cache->time_prev) cache->time_prev->time_next = cache->time_next; + else oldestcache = cache->time_next; + cache->time_next = NULL; + cache->time_prev = NULL; +} //end of the function AAS_UnlinkCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_LinkCache(aas_routingcache_t *cache) +{ + if (newestcache) + { + newestcache->time_next = cache; + cache->time_prev = cache; + } //end if + else + { + oldestcache = cache; + cache->time_prev = NULL; + } //end else + cache->time_next = NULL; + newestcache = cache; +} //end of the function AAS_LinkCache*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FreeOldestCache( void ) { + int i, j, bestcluster, bestarea, freed; + float besttime; + aas_routingcache_t *cache, *bestcache; + + freed = qfalse; + besttime = 999999999; + bestcache = NULL; + bestcluster = 0; + bestarea = 0; + //refresh cluster cache + for ( i = 0; i < ( *aasworld ).numclusters; i++ ) + { + for ( j = 0; j < ( *aasworld ).clusters[i].numareas; j++ ) + { + for ( cache = ( *aasworld ).clusterareacache[i][j]; cache; cache = cache->next ) + { + //never remove cache leading towards a portal + if ( ( *aasworld ).areasettings[cache->areanum].cluster < 0 ) { + continue; + } + //if this cache is older than the cache we found so far + if ( cache->time < besttime ) { + bestcache = cache; + bestcluster = i; + bestarea = j; + besttime = cache->time; + } //end if + } //end for + } //end for + } //end for + if ( bestcache ) { + cache = bestcache; + if ( cache->prev ) { + cache->prev->next = cache->next; + } else { ( *aasworld ).clusterareacache[bestcluster][bestarea] = cache->next;} + if ( cache->next ) { + cache->next->prev = cache->prev; + } + AAS_FreeRoutingCache( cache ); + freed = qtrue; + } //end if + besttime = 999999999; + bestcache = NULL; + bestarea = 0; + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + //refresh portal cache + for ( cache = ( *aasworld ).portalcache[i]; cache; cache = cache->next ) + { + if ( cache->time < besttime ) { + bestcache = cache; + bestarea = i; + besttime = cache->time; + } //end if + } //end for + } //end for + if ( bestcache ) { + cache = bestcache; + if ( cache->prev ) { + cache->prev->next = cache->next; + } else { ( *aasworld ).portalcache[bestarea] = cache->next;} + if ( cache->next ) { + cache->next->prev = cache->prev; + } + AAS_FreeRoutingCache( cache ); + freed = qtrue; + } //end if + return freed; +} //end of the function AAS_FreeOldestCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_AllocRoutingCache( int numtraveltimes ) { + aas_routingcache_t *cache; + int size; + + // + size = sizeof( aas_routingcache_t ) + + numtraveltimes * sizeof( unsigned short int ) + + numtraveltimes * sizeof( unsigned char ); + // + routingcachesize += size; + // + cache = (aas_routingcache_t *) AAS_RoutingGetMemory( size ); + cache->reachabilities = (unsigned char *) cache + sizeof( aas_routingcache_t ) + + numtraveltimes * sizeof( unsigned short int ); + cache->size = size; + return cache; +} //end of the function AAS_AllocRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllClusterAreaCache( void ) { + int i, j; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + //free all cluster cache if existing + if ( !( *aasworld ).clusterareacache ) { + return; + } + //free caches + for ( i = 0; i < ( *aasworld ).numclusters; i++ ) + { + cluster = &( *aasworld ).clusters[i]; + for ( j = 0; j < cluster->numareas; j++ ) + { + for ( cache = ( *aasworld ).clusterareacache[i][j]; cache; cache = nextcache ) + { + nextcache = cache->next; + AAS_FreeRoutingCache( cache ); + } //end for + ( *aasworld ).clusterareacache[i][j] = NULL; + } //end for + } //end for + //free the cluster cache array + AAS_RoutingFreeMemory( ( *aasworld ).clusterareacache ); + ( *aasworld ).clusterareacache = NULL; +} //end of the function AAS_FreeAllClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClusterAreaCache( void ) { + int i, size; + char *ptr; + + // + for ( size = 0, i = 0; i < ( *aasworld ).numclusters; i++ ) + { + size += ( *aasworld ).clusters[i].numareas; + } //end for + //two dimensional array with pointers for every cluster to routing cache + //for every area in that cluster + ptr = (char *) AAS_RoutingGetMemory( + ( *aasworld ).numclusters * sizeof( aas_routingcache_t * * ) + + size * sizeof( aas_routingcache_t * ) ); + ( *aasworld ).clusterareacache = (aas_routingcache_t ***) ptr; + ptr += ( *aasworld ).numclusters * sizeof( aas_routingcache_t * * ); + for ( i = 0; i < ( *aasworld ).numclusters; i++ ) + { + ( *aasworld ).clusterareacache[i] = (aas_routingcache_t **) ptr; + ptr += ( *aasworld ).clusters[i].numareas * sizeof( aas_routingcache_t * ); + } //end for +} //end of the function AAS_InitClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllPortalCache( void ) { + int i; + aas_routingcache_t *cache, *nextcache; + + //free all portal cache if existing + if ( !( *aasworld ).portalcache ) { + return; + } + //free portal caches + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + for ( cache = ( *aasworld ).portalcache[i]; cache; cache = nextcache ) + { + nextcache = cache->next; + AAS_FreeRoutingCache( cache ); + } //end for + ( *aasworld ).portalcache[i] = NULL; + } //end for + AAS_RoutingFreeMemory( ( *aasworld ).portalcache ); + ( *aasworld ).portalcache = NULL; +} //end of the function AAS_FreeAllPortalCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalCache( void ) { + // + ( *aasworld ).portalcache = (aas_routingcache_t **) AAS_RoutingGetMemory( + ( *aasworld ).numareas * sizeof( aas_routingcache_t * ) ); +} //end of the function AAS_InitPortalCache +// +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAreaVisibility( void ) { + int i; + + if ( ( *aasworld ).areavisibility ) { + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + if ( ( *aasworld ).areavisibility[i] ) { + FreeMemory( ( *aasworld ).areavisibility[i] ); + } + } + } + if ( ( *aasworld ).areavisibility ) { + FreeMemory( ( *aasworld ).areavisibility ); + } + ( *aasworld ).areavisibility = NULL; + if ( ( *aasworld ).decompressedvis ) { + FreeMemory( ( *aasworld ).decompressedvis ); + } + ( *aasworld ).decompressedvis = NULL; +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitRoutingUpdate( void ) { +// int i, maxreachabilityareas; + + //free routing update fields if already existing + if ( ( *aasworld ).areaupdate ) { + AAS_RoutingFreeMemory( ( *aasworld ).areaupdate ); + } + // +// Ridah, had to change it to numareas for hidepos checking +/* + maxreachabilityareas = 0; + for (i = 0; i < (*aasworld).numclusters; i++) + { + if ((*aasworld).clusters[i].numreachabilityareas > maxreachabilityareas) + { + maxreachabilityareas = (*aasworld).clusters[i].numreachabilityareas; + } //end if + } //end for + //allocate memory for the routing update fields + (*aasworld).areaupdate = (aas_routingupdate_t *) AAS_RoutingGetMemory( + maxreachabilityareas * sizeof(aas_routingupdate_t)); +*/ + ( *aasworld ).areaupdate = (aas_routingupdate_t *) AAS_RoutingGetMemory( + ( *aasworld ).numareas * sizeof( aas_routingupdate_t ) ); + // + if ( ( *aasworld ).portalupdate ) { + AAS_RoutingFreeMemory( ( *aasworld ).portalupdate ); + } + //allocate memory for the portal update fields + ( *aasworld ).portalupdate = (aas_routingupdate_t *) AAS_RoutingGetMemory( + ( ( *aasworld ).numportals + 1 ) * sizeof( aas_routingupdate_t ) ); +} //end of the function AAS_InitRoutingUpdate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_CreateAllRoutingCache( void ) { + int i, j, k, t, tfl, numroutingareas; + aas_areasettings_t *areasettings; + aas_reachability_t *reach; + + numroutingareas = 0; + tfl = TFL_DEFAULT & ~( TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA ); //----(SA) modified since slime is no longer deadly +// tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); + botimport.Print( PRT_MESSAGE, "AAS_CreateAllRoutingCache\n" ); + // + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( !AAS_AreaReachability( i ) ) { + continue; + } + areasettings = &( *aasworld ).areasettings[i]; + for ( k = 0; k < areasettings->numreachableareas; k++ ) + { + reach = &( *aasworld ).reachability[areasettings->firstreachablearea + k]; + if ( ( *aasworld ).travelflagfortype[reach->traveltype] & tfl ) { + break; + } + } + if ( k >= areasettings->numreachableareas ) { + continue; + } + ( *aasworld ).areasettings[i].areaflags |= AREA_USEFORROUTING; + numroutingareas++; + } + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( !( ( *aasworld ).areasettings[i].areaflags & AREA_USEFORROUTING ) ) { + continue; + } + for ( j = 1; j < ( *aasworld ).numareas; j++ ) + { + if ( i == j ) { + continue; + } + if ( !( ( *aasworld ).areasettings[j].areaflags & AREA_USEFORROUTING ) ) { + continue; + } + t = AAS_AreaTravelTimeToGoalArea( j, ( *aasworld ).areawaypoints[j], i, tfl ); + //if (t) break; + //Log_Write("traveltime from %d to %d is %d", i, j, t); + } //end for + } //end for +} //end of the function AAS_CreateAllRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_ProcessString( unsigned char *data, int length ); + +//the route cache header +//this header is followed by numportalcache + numareacache aas_routingcache_t +//structures that store routing cache +typedef struct routecacheheader_s +{ + int ident; + int version; + int numareas; + int numclusters; + int areacrc; + int clustercrc; + int reachcrc; + int numportalcache; + int numareacache; +} routecacheheader_t; + +#define RCID ( ( 'C' << 24 ) + ( 'R' << 16 ) + ( 'E' << 8 ) + 'M' ) +#define RCVERSION 12 + +void AAS_DecompressVis( byte *in, int numareas, byte *decompressed ); +int AAS_CompressVis( byte *vis, int numareas, byte *dest ); + +void AAS_WriteRouteCache( void ) { + int i, j, numportalcache, numareacache, size; + aas_routingcache_t *cache; + aas_cluster_t *cluster; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + byte *buf; + + buf = (byte *) GetClearedMemory( ( *aasworld ).numareas * 2 * sizeof( byte ) ); // in case it ends up bigger than the decompressedvis, which is rare but possible + + numportalcache = 0; + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + for ( cache = ( *aasworld ).portalcache[i]; cache; cache = cache->next ) + { + numportalcache++; + } //end for + } //end for + numareacache = 0; + for ( i = 0; i < ( *aasworld ).numclusters; i++ ) + { + cluster = &( *aasworld ).clusters[i]; + for ( j = 0; j < cluster->numareas; j++ ) + { + for ( cache = ( *aasworld ).clusterareacache[i][j]; cache; cache = cache->next ) + { + numareacache++; + } //end for + } //end for + } //end for + // open the file for writing + Com_sprintf( filename, MAX_QPATH, "maps/%s.rcd", ( *aasworld ).mapname ); + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if ( !fp ) { + AAS_Error( "Unable to open file: %s\n", filename ); + return; + } //end if + //create the header + routecacheheader.ident = RCID; + routecacheheader.version = RCVERSION; + routecacheheader.numareas = ( *aasworld ).numareas; + routecacheheader.numclusters = ( *aasworld ).numclusters; + routecacheheader.areacrc = CRC_ProcessString( (unsigned char *)( *aasworld ).areas, sizeof( aas_area_t ) * ( *aasworld ).numareas ); + routecacheheader.clustercrc = CRC_ProcessString( (unsigned char *)( *aasworld ).clusters, sizeof( aas_cluster_t ) * ( *aasworld ).numclusters ); + routecacheheader.reachcrc = CRC_ProcessString( (unsigned char *)( *aasworld ).reachability, sizeof( aas_reachability_t ) * ( *aasworld ).reachabilitysize ); + routecacheheader.numportalcache = numportalcache; + routecacheheader.numareacache = numareacache; + //write the header + botimport.FS_Write( &routecacheheader, sizeof( routecacheheader_t ), fp ); + //write all the cache + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + for ( cache = ( *aasworld ).portalcache[i]; cache; cache = cache->next ) + { + botimport.FS_Write( cache, cache->size, fp ); + } //end for + } //end for + for ( i = 0; i < ( *aasworld ).numclusters; i++ ) + { + cluster = &( *aasworld ).clusters[i]; + for ( j = 0; j < cluster->numareas; j++ ) + { + for ( cache = ( *aasworld ).clusterareacache[i][j]; cache; cache = cache->next ) + { + botimport.FS_Write( cache, cache->size, fp ); + } //end for + } //end for + } //end for + // write the visareas + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + if ( !( *aasworld ).areavisibility[i] ) { + size = 0; + botimport.FS_Write( &size, sizeof( int ), fp ); + continue; + } + AAS_DecompressVis( ( *aasworld ).areavisibility[i], ( *aasworld ).numareas, ( *aasworld ).decompressedvis ); + size = AAS_CompressVis( ( *aasworld ).decompressedvis, ( *aasworld ).numareas, buf ); + botimport.FS_Write( &size, sizeof( int ), fp ); + botimport.FS_Write( buf, size, fp ); + } + // write the waypoints + botimport.FS_Write( ( *aasworld ).areawaypoints, sizeof( vec3_t ) * ( *aasworld ).numareas, fp ); + // + botimport.FS_FCloseFile( fp ); + botimport.Print( PRT_MESSAGE, "\nroute cache written to %s\n", filename ); +} //end of the function AAS_WriteRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_ReadCache( fileHandle_t fp ) { + int size, i; + aas_routingcache_t *cache; + + botimport.FS_Read( &size, sizeof( size ), fp ); + cache = (aas_routingcache_t *) AAS_RoutingGetMemory( size ); + cache->size = size; + botimport.FS_Read( (unsigned char *)cache + sizeof( size ), size - sizeof( size ), fp ); +// cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - sizeof(unsigned short) + +// (size - sizeof(aas_routingcache_t) + sizeof(unsigned short)) / 3 * 2; + cache->reachabilities = (unsigned char *) cache + sizeof( aas_routingcache_t ) + + ( ( size - sizeof( aas_routingcache_t ) ) / 3 ) * 2; + + //DAJ BUGFIX for missing byteswaps for traveltimes + size = ( size - sizeof( aas_routingcache_t ) ) / 3 + 1; + for ( i = 0; i < size; i++ ) { + cache->traveltimes[i] = LittleShort( cache->traveltimes[i] ); + } + return cache; +} //end of the function AAS_ReadCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ReadRouteCache( void ) { + int i, clusterareanum, size; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + aas_routingcache_t *cache; + + Com_sprintf( filename, MAX_QPATH, "maps/%s.rcd", ( *aasworld ).mapname ); + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if ( !fp ) { + return qfalse; + } //end if + botimport.FS_Read( &routecacheheader, sizeof( routecacheheader_t ), fp ); + if ( routecacheheader.ident != RCID ) { + botimport.FS_FCloseFile( fp ); + AAS_Error( "%s is not a route cache dump\n" ); + return qfalse; + } //end if + if ( routecacheheader.version != RCVERSION ) { + botimport.FS_FCloseFile( fp ); + AAS_Error( "route cache dump has wrong version %d, should be %d", routecacheheader.version, RCVERSION ); + return qfalse; + } //end if + if ( routecacheheader.numareas != ( *aasworld ).numareas ) { + botimport.FS_FCloseFile( fp ); + //AAS_Error("route cache dump has wrong number of areas\n"); + return qfalse; + } //end if + if ( routecacheheader.numclusters != ( *aasworld ).numclusters ) { + botimport.FS_FCloseFile( fp ); + //AAS_Error("route cache dump has wrong number of clusters\n"); + return qfalse; + } //end if +#if defined( MACOSX ) + // the crc table stuff is endian orientated.... +#else + if ( routecacheheader.areacrc != + CRC_ProcessString( (unsigned char *)( *aasworld ).areas, sizeof( aas_area_t ) * ( *aasworld ).numareas ) ) { + botimport.FS_FCloseFile( fp ); + //AAS_Error("route cache dump area CRC incorrect\n"); + return qfalse; + } //end if + if ( routecacheheader.clustercrc != + CRC_ProcessString( (unsigned char *)( *aasworld ).clusters, sizeof( aas_cluster_t ) * ( *aasworld ).numclusters ) ) { + botimport.FS_FCloseFile( fp ); + //AAS_Error("route cache dump cluster CRC incorrect\n"); + return qfalse; + } //end if + if ( routecacheheader.reachcrc != + CRC_ProcessString( (unsigned char *)( *aasworld ).reachability, sizeof( aas_reachability_t ) * ( *aasworld ).reachabilitysize ) ) { + botimport.FS_FCloseFile( fp ); + //AAS_Error("route cache dump reachability CRC incorrect\n"); + return qfalse; + } //end if +#endif + //read all the portal cache + for ( i = 0; i < routecacheheader.numportalcache; i++ ) + { + cache = AAS_ReadCache( fp ); + cache->next = ( *aasworld ).portalcache[cache->areanum]; + cache->prev = NULL; + if ( ( *aasworld ).portalcache[cache->areanum] ) { + ( *aasworld ).portalcache[cache->areanum]->prev = cache; + } + ( *aasworld ).portalcache[cache->areanum] = cache; + } //end for + //read all the cluster area cache + for ( i = 0; i < routecacheheader.numareacache; i++ ) + { + cache = AAS_ReadCache( fp ); + clusterareanum = AAS_ClusterAreaNum( cache->cluster, cache->areanum ); + cache->next = ( *aasworld ).clusterareacache[cache->cluster][clusterareanum]; + cache->prev = NULL; + if ( ( *aasworld ).clusterareacache[cache->cluster][clusterareanum] ) { + ( *aasworld ).clusterareacache[cache->cluster][clusterareanum]->prev = cache; + } + ( *aasworld ).clusterareacache[cache->cluster][clusterareanum] = cache; + } //end for + // read the visareas + ( *aasworld ).areavisibility = (byte **) GetClearedMemory( ( *aasworld ).numareas * sizeof( byte * ) ); + ( *aasworld ).decompressedvis = (byte *) GetClearedMemory( ( *aasworld ).numareas * sizeof( byte ) ); + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + botimport.FS_Read( &size, sizeof( size ), fp ); + if ( size ) { + ( *aasworld ).areavisibility[i] = (byte *) GetMemory( size ); + botimport.FS_Read( ( *aasworld ).areavisibility[i], size, fp ); + } + } + // read the area waypoints + ( *aasworld ).areawaypoints = (vec3_t *) GetClearedMemory( ( *aasworld ).numareas * sizeof( vec3_t ) ); + botimport.FS_Read( ( *aasworld ).areawaypoints, ( *aasworld ).numareas * sizeof( vec3_t ), fp ); + // + botimport.FS_FCloseFile( fp ); + return qtrue; +} //end of the function AAS_ReadRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateVisibility( void ); +void AAS_InitRouting( void ) { + AAS_InitTravelFlagFromType(); + //initialize the routing update fields + AAS_InitRoutingUpdate(); + //create reversed reachability links used by the routing update algorithm + AAS_CreateReversedReachability(); + //initialize the cluster cache + AAS_InitClusterAreaCache(); + //initialize portal cache + AAS_InitPortalCache(); + //initialize the area travel times + AAS_CalculateAreaTravelTimes(); + //calculate the maximum travel times through portals + AAS_InitPortalMaxTravelTimes(); + // +#ifdef ROUTING_DEBUG + numareacacheupdates = 0; + numportalcacheupdates = 0; +#endif //ROUTING_DEBUG + // + routingcachesize = 0; + max_routingcachesize = 1024 * (int) LibVarValue( "max_routingcache", "4096" ); + // + // Ridah, load or create the routing cache + if ( !AAS_ReadRouteCache() ) { + ( *aasworld ).initialized = qtrue; // Hack, so routing can compute traveltimes + AAS_CreateVisibility(); + AAS_CreateAllRoutingCache(); + ( *aasworld ).initialized = qfalse; + + AAS_WriteRouteCache(); // save it so we don't have to create it again + } + // done. +} //end of the function AAS_InitRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCaches( void ) { + // free all the existing cluster area cache + AAS_FreeAllClusterAreaCache(); + // free all the existing portal cache + AAS_FreeAllPortalCache(); + // free all the existing area visibility data + AAS_FreeAreaVisibility(); + // free cached travel times within areas + if ( ( *aasworld ).areatraveltimes ) { + AAS_RoutingFreeMemory( ( *aasworld ).areatraveltimes ); + } + ( *aasworld ).areatraveltimes = NULL; + // free cached maximum travel time through cluster portals + if ( ( *aasworld ).portalmaxtraveltimes ) { + AAS_RoutingFreeMemory( ( *aasworld ).portalmaxtraveltimes ); + } + ( *aasworld ).portalmaxtraveltimes = NULL; + // free reversed reachability links + if ( ( *aasworld ).reversedreachability ) { + AAS_RoutingFreeMemory( ( *aasworld ).reversedreachability ); + } + ( *aasworld ).reversedreachability = NULL; + // free routing algorithm memory + if ( ( *aasworld ).areaupdate ) { + AAS_RoutingFreeMemory( ( *aasworld ).areaupdate ); + } + ( *aasworld ).areaupdate = NULL; + if ( ( *aasworld ).portalupdate ) { + AAS_RoutingFreeMemory( ( *aasworld ).portalupdate ); + } + ( *aasworld ).portalupdate = NULL; + // free area waypoints + if ( ( *aasworld ).areawaypoints ) { + FreeMemory( ( *aasworld ).areawaypoints ); + } + ( *aasworld ).areawaypoints = NULL; +} //end of the function AAS_FreeRoutingCaches +//=========================================================================== +// this function could be replaced by a bubble sort or for even faster +// routing by a B+ tree +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline void AAS_AddUpdateToList( aas_routingupdate_t **updateliststart, + aas_routingupdate_t **updatelistend, + aas_routingupdate_t *update ) { + if ( !update->inlist ) { + if ( *updatelistend ) { + ( *updatelistend )->next = update; + } else { *updateliststart = update;} + update->prev = *updatelistend; + update->next = NULL; + *updatelistend = update; + update->inlist = qtrue; + } //end if +} //end of the function AAS_AddUpdateToList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaContentsTravelFlag( int areanum ) { + int contents, tfl; + + contents = ( *aasworld ).areasettings[areanum].contents; + tfl = 0; + if ( contents & AREACONTENTS_WATER ) { + return tfl |= TFL_WATER; + } else if ( contents & AREACONTENTS_SLIME ) { + return tfl |= TFL_SLIME; + } else if ( contents & AREACONTENTS_LAVA ) { + return tfl |= TFL_LAVA; + } else { tfl |= TFL_AIR;} + if ( contents & AREACONTENTS_DONOTENTER_LARGE ) { + tfl |= TFL_DONOTENTER_LARGE; + } + if ( contents & AREACONTENTS_DONOTENTER ) { + return tfl |= TFL_DONOTENTER; + } + return tfl; +} //end of the function AAS_AreaContentsTravelFlag +//=========================================================================== +// update the given routing cache +// +// Parameter: areacache : routing cache to update +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateAreaRoutingCache( aas_routingcache_t *areacache ) { + int i, nextareanum, cluster, badtravelflags, clusterareanum, linknum; + int numreachabilityareas; + unsigned short int t, startareatraveltimes[128]; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + +#ifdef ROUTING_DEBUG + numareacacheupdates++; +#endif //ROUTING_DEBUG + //number of reachability areas within this cluster + numreachabilityareas = ( *aasworld ).clusters[areacache->cluster].numreachabilityareas; + // + //clear the routing update fields +// memset((*aasworld).areaupdate, 0, (*aasworld).numareas * sizeof(aas_routingupdate_t)); + // + badtravelflags = ~areacache->travelflags; + // + clusterareanum = AAS_ClusterAreaNum( areacache->cluster, areacache->areanum ); + if ( clusterareanum >= numreachabilityareas ) { + return; + } + // + memset( startareatraveltimes, 0, sizeof( startareatraveltimes ) ); + // + curupdate = &( *aasworld ).areaupdate[clusterareanum]; + curupdate->areanum = areacache->areanum; + //VectorCopy(areacache->origin, curupdate->start); + curupdate->areatraveltimes = ( *aasworld ).areatraveltimes[areacache->areanum][0]; + curupdate->tmptraveltime = areacache->starttraveltime; + // + areacache->traveltimes[clusterareanum] = areacache->starttraveltime; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while ( updateliststart ) + { + curupdate = updateliststart; + // + if ( curupdate->next ) { + curupdate->next->prev = NULL; + } else { updatelistend = NULL;} + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + revreach = &( *aasworld ).reversedreachability[curupdate->areanum]; + // + for ( i = 0, revlink = revreach->first; revlink; revlink = revlink->next, i++ ) + { + linknum = revlink->linknum; + reach = &( *aasworld ).reachability[linknum]; + //if there is used an undesired travel type + if ( ( *aasworld ).travelflagfortype[reach->traveltype] & badtravelflags ) { + continue; + } + //if not allowed to enter the next area + if ( ( *aasworld ).areasettings[reach->areanum].areaflags & AREA_DISABLED ) { + continue; + } + //if the next area has a not allowed travel flag + if ( AAS_AreaContentsTravelFlag( reach->areanum ) & badtravelflags ) { + continue; + } + //number of the area the reversed reachability leads to + nextareanum = revlink->areanum; + //get the cluster number of the area + cluster = ( *aasworld ).areasettings[nextareanum].cluster; + //don't leave the cluster + if ( cluster > 0 && cluster != areacache->cluster ) { + continue; + } + //get the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum( areacache->cluster, nextareanum ); + if ( clusterareanum >= numreachabilityareas ) { + continue; + } + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + //AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->end) + + curupdate->areatraveltimes[i] + + reach->traveltime; + // + ( *aasworld ).frameroutingupdates++; + // + if ( !areacache->traveltimes[clusterareanum] || + areacache->traveltimes[clusterareanum] > t ) { + areacache->traveltimes[clusterareanum] = t; + areacache->reachabilities[clusterareanum] = linknum - ( *aasworld ).areasettings[nextareanum].firstreachablearea; + nextupdate = &( *aasworld ).areaupdate[clusterareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //VectorCopy(reach->start, nextupdate->start); + nextupdate->areatraveltimes = ( *aasworld ).areatraveltimes[nextareanum][linknum - + ( *aasworld ).areasettings[nextareanum].firstreachablearea]; + if ( !nextupdate->inlist ) { + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if ( updatelistend ) { + updatelistend->next = nextupdate; + } else { updateliststart = nextupdate;} + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdateAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetAreaRoutingCache( int clusternum, int areanum, int travelflags, qboolean forceUpdate ) { + int clusterareanum; + aas_routingcache_t *cache, *clustercache; + + //number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum( clusternum, areanum ); + //pointer to the cache for the area in the cluster + clustercache = ( *aasworld ).clusterareacache[clusternum][clusterareanum]; + //find the cache without undesired travel flags + for ( cache = clustercache; cache; cache = cache->next ) + { + //if there aren't used any undesired travel types for the cache + if ( cache->travelflags == travelflags ) { + break; + } + } //end for + //if there was no cache + if ( !cache ) { + //NOTE: the number of routing updates is limited per frame + if ( !forceUpdate && ( ( *aasworld ).frameroutingupdates > MAX_FRAMEROUTINGUPDATES ) ) { + return NULL; + } //end if + + cache = AAS_AllocRoutingCache( ( *aasworld ).clusters[clusternum].numreachabilityareas ); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy( ( *aasworld ).areas[areanum].center, cache->origin ); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + cache->prev = NULL; + cache->next = clustercache; + if ( clustercache ) { + clustercache->prev = cache; + } + ( *aasworld ).clusterareacache[clusternum][clusterareanum] = cache; + AAS_UpdateAreaRoutingCache( cache ); + } //end if + //the cache has been accessed + cache->time = AAS_RoutingTime(); + return cache; +} //end of the function AAS_GetAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdatePortalRoutingCache( aas_routingcache_t *portalcache ) { + int i, portalnum, clusterareanum, clusternum; + unsigned short int t; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *cache; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + +#ifdef ROUTING_DEBUG + numportalcacheupdates++; +#endif //ROUTING_DEBUG + //clear the routing update fields +// memset((*aasworld).portalupdate, 0, ((*aasworld).numportals+1) * sizeof(aas_routingupdate_t)); + // + curupdate = &( *aasworld ).portalupdate[( *aasworld ).numportals]; + curupdate->cluster = portalcache->cluster; + curupdate->areanum = portalcache->areanum; + curupdate->tmptraveltime = portalcache->starttraveltime; + //if the start area is a cluster portal, store the travel time for that portal + clusternum = ( *aasworld ).areasettings[portalcache->areanum].cluster; + if ( clusternum < 0 ) { + portalcache->traveltimes[-clusternum] = portalcache->starttraveltime; + } //end if + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while ( updateliststart ) + { + curupdate = updateliststart; + //remove the current update from the list + if ( curupdate->next ) { + curupdate->next->prev = NULL; + } else { updatelistend = NULL;} + updateliststart = curupdate->next; + //current update is removed from the list + curupdate->inlist = qfalse; + // + cluster = &( *aasworld ).clusters[curupdate->cluster]; + // + cache = AAS_GetAreaRoutingCache( curupdate->cluster, + curupdate->areanum, portalcache->travelflags, qtrue ); + //take all portals of the cluster + for ( i = 0; i < cluster->numportals; i++ ) + { + portalnum = ( *aasworld ).portalindex[cluster->firstportal + i]; + portal = &( *aasworld ).portals[portalnum]; + //if this is the portal of the current update continue + if ( portal->areanum == curupdate->areanum ) { + continue; + } + // + clusterareanum = AAS_ClusterAreaNum( curupdate->cluster, portal->areanum ); + if ( clusterareanum >= cluster->numreachabilityareas ) { + continue; + } + // + t = cache->traveltimes[clusterareanum]; + if ( !t ) { + continue; + } + t += curupdate->tmptraveltime; + // + if ( !portalcache->traveltimes[portalnum] || + portalcache->traveltimes[portalnum] > t ) { + portalcache->traveltimes[portalnum] = t; + portalcache->reachabilities[portalnum] = cache->reachabilities[clusterareanum]; + nextupdate = &( *aasworld ).portalupdate[portalnum]; + if ( portal->frontcluster == curupdate->cluster ) { + nextupdate->cluster = portal->backcluster; + } //end if + else + { + nextupdate->cluster = portal->frontcluster; + } //end else + nextupdate->areanum = portal->areanum; + //add travel time through actual portal area for the next update + nextupdate->tmptraveltime = t + ( *aasworld ).portalmaxtraveltimes[portalnum]; + if ( !nextupdate->inlist ) { + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if ( updatelistend ) { + updatelistend->next = nextupdate; + } else { updateliststart = nextupdate;} + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdatePortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetPortalRoutingCache( int clusternum, int areanum, int travelflags ) { + aas_routingcache_t *cache; + + //find the cached portal routing if existing + for ( cache = ( *aasworld ).portalcache[areanum]; cache; cache = cache->next ) + { + if ( cache->travelflags == travelflags ) { + break; + } + } //end for + //if the portal routing isn't cached + if ( !cache ) { + cache = AAS_AllocRoutingCache( ( *aasworld ).numportals ); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy( ( *aasworld ).areas[areanum].center, cache->origin ); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + //add the cache to the cache list + cache->prev = NULL; + cache->next = ( *aasworld ).portalcache[areanum]; + if ( ( *aasworld ).portalcache[areanum] ) { + ( *aasworld ).portalcache[areanum]->prev = cache; + } + ( *aasworld ).portalcache[areanum] = cache; + //update the cache + AAS_UpdatePortalRoutingCache( cache ); + } //end if + //the cache has been accessed + cache->time = AAS_RoutingTime(); + return cache; +} //end of the function AAS_GetPortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum ) { + int clusternum, goalclusternum, portalnum, i, clusterareanum, bestreachnum; + unsigned short int t, besttime; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *areacache, *portalcache; + aas_reachability_t *reach; + aas_portalindex_t *pPortalnum; + + if ( !( *aasworld ).initialized ) { + return qfalse; + } + + if ( areanum == goalareanum ) { + *traveltime = 1; + *reachnum = 0; + return qtrue; + } //end if + // + if ( areanum <= 0 || areanum >= ( *aasworld ).numareas ) { + if ( bot_developer ) { + botimport.Print( PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: areanum %d out of range\n", areanum ); + } //end if + return qfalse; + } //end if + if ( goalareanum <= 0 || goalareanum >= ( *aasworld ).numareas ) { + if ( bot_developer ) { + botimport.Print( PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: goalareanum %d out of range\n", goalareanum ); + } //end if + return qfalse; + } //end if + + //make sure the routing cache doesn't grow to large + while ( routingcachesize > max_routingcachesize ) { + if ( !AAS_FreeOldestCache() ) { + break; + } + } + // + if ( AAS_AreaDoNotEnter( areanum ) || AAS_AreaDoNotEnter( goalareanum ) ) { + travelflags |= TFL_DONOTENTER; + } //end if + if ( AAS_AreaDoNotEnterLarge( areanum ) || AAS_AreaDoNotEnterLarge( goalareanum ) ) { + travelflags |= TFL_DONOTENTER_LARGE; + } //end if + //NOTE: the number of routing updates is limited per frame + /* + if ((*aasworld).frameroutingupdates > MAX_FRAMEROUTINGUPDATES) + { + #ifdef DEBUG + //Log_Write("WARNING: AAS_AreaTravelTimeToGoalArea: frame routing updates overflowed"); + #endif + return 0; + } //end if + */ + // + clusternum = ( *aasworld ).areasettings[areanum].cluster; + goalclusternum = ( *aasworld ).areasettings[goalareanum].cluster; + //check if the area is a portal of the goal area cluster + if ( clusternum < 0 && goalclusternum > 0 ) { + portal = &( *aasworld ).portals[-clusternum]; + if ( portal->frontcluster == goalclusternum || + portal->backcluster == goalclusternum ) { + clusternum = goalclusternum; + } //end if + } //end if + //check if the goalarea is a portal of the area cluster + else if ( clusternum > 0 && goalclusternum < 0 ) { + portal = &( *aasworld ).portals[-goalclusternum]; + if ( portal->frontcluster == clusternum || + portal->backcluster == clusternum ) { + goalclusternum = clusternum; + } //end if + } //end if + //if both areas are in the same cluster + //NOTE: there might be a shorter route via another cluster!!! but we don't care + if ( clusternum > 0 && goalclusternum > 0 && clusternum == goalclusternum ) { + // + areacache = AAS_GetAreaRoutingCache( clusternum, goalareanum, travelflags, qfalse ); + // RF, note that the routing cache might be NULL now since we are restricting + // the updates per frame, hopefully rejected cache's will be requested again + // when things have settled down + if ( !areacache ) { + return qfalse; + } + //the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum( clusternum, areanum ); + //the cluster the area is in + cluster = &( *aasworld ).clusters[clusternum]; + //if the area is NOT a reachability area + if ( clusterareanum >= cluster->numreachabilityareas ) { + return qfalse; + } + //if it is possible to travel to the goal area through this cluster + if ( areacache->traveltimes[clusterareanum] != 0 ) { + *reachnum = ( *aasworld ).areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + // + if ( !origin ) { + *traveltime = areacache->traveltimes[clusterareanum]; + return qtrue; + } + // + reach = &( *aasworld ).reachability[*reachnum]; + *traveltime = areacache->traveltimes[clusterareanum] + + AAS_AreaTravelTime( areanum, origin, reach->start ); + return qtrue; + } //end if + } //end if + // + clusternum = ( *aasworld ).areasettings[areanum].cluster; + goalclusternum = ( *aasworld ).areasettings[goalareanum].cluster; + //if the goal area is a portal + if ( goalclusternum < 0 ) { + //just assume the goal area is part of the front cluster + portal = &( *aasworld ).portals[-goalclusternum]; + goalclusternum = portal->frontcluster; + } //end if + //get the portal routing cache + portalcache = AAS_GetPortalRoutingCache( goalclusternum, goalareanum, travelflags ); + //if the area is a cluster portal, read directly from the portal cache + if ( clusternum < 0 ) { + *traveltime = portalcache->traveltimes[-clusternum]; + *reachnum = ( *aasworld ).areasettings[areanum].firstreachablearea + + portalcache->reachabilities[-clusternum]; + return qtrue; + } + // + besttime = 0; + bestreachnum = -1; + //the cluster the area is in + cluster = &( *aasworld ).clusters[clusternum]; + //current area inside the current cluster + clusterareanum = AAS_ClusterAreaNum( clusternum, areanum ); + //if the area is NOT a reachability area + if ( clusterareanum >= cluster->numreachabilityareas ) { + return qfalse; + } + // + pPortalnum = ( *aasworld ).portalindex + cluster->firstportal; + //find the portal of the area cluster leading towards the goal area + for ( i = 0; i < cluster->numportals; i++, pPortalnum++ ) + { + portalnum = *pPortalnum; + //if the goal area isn't reachable from the portal + if ( !portalcache->traveltimes[portalnum] ) { + continue; + } + // + portal = ( *aasworld ).portals + portalnum; + // if the area in disabled + if ( ( *aasworld ).areasettings[portal->areanum].areaflags & AREA_DISABLED ) { + continue; + } + //get the cache of the portal area + areacache = AAS_GetAreaRoutingCache( clusternum, portal->areanum, travelflags, qfalse ); + // RF, this may be NULL if we were unable to calculate the cache this frame + if ( !areacache ) { + return qfalse; + } + //if the portal is NOT reachable from this area + if ( !areacache->traveltimes[clusterareanum] ) { + continue; + } + //total travel time is the travel time the portal area is from + //the goal area plus the travel time towards the portal area + t = portalcache->traveltimes[portalnum] + areacache->traveltimes[clusterareanum]; + //FIXME: add the exact travel time through the actual portal area + //NOTE: for now we just add the largest travel time through the area portal + // because we can't directly calculate the exact travel time + // to be more specific we don't know which reachability is used to travel + // into the portal area when coming from the current area + t += ( *aasworld ).portalmaxtraveltimes[portalnum]; + // + // Ridah, needs to be up here + *reachnum = ( *aasworld ).areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + +//botimport.Print(PRT_MESSAGE, "portal reachability: %i\n", (int)areacache->reachabilities[clusterareanum] ); + + if ( origin ) { + reach = ( *aasworld ).reachability + *reachnum; + t += AAS_AreaTravelTime( areanum, origin, reach->start ); + } //end if + //if the time is better than the one already found + if ( !besttime || t < besttime ) { + bestreachnum = *reachnum; + besttime = t; + } //end if + } //end for + // Ridah, check a route was found + if ( bestreachnum < 0 ) { + return qfalse; + } + *reachnum = bestreachnum; + *traveltime = besttime; + return qtrue; +} //end of the function AAS_AreaRouteToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTravelTimeToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags ) { + int traveltime, reachnum; + + if ( AAS_AreaRouteToGoalArea( areanum, origin, goalareanum, travelflags, &traveltime, &reachnum ) ) { + return traveltime; + } + return 0; +} //end of the function AAS_AreaTravelTimeToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachabilityToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags ) { + int traveltime, reachnum; + + if ( AAS_AreaRouteToGoalArea( areanum, origin, goalareanum, travelflags, &traveltime, &reachnum ) ) { + return reachnum; + } + return 0; +} //end of the function AAS_AreaReachabilityToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ReachabilityFromNum( int num, struct aas_reachability_s *reach ) { + if ( !( *aasworld ).initialized ) { + memset( reach, 0, sizeof( aas_reachability_t ) ); + return; + } //end if + if ( num < 0 || num >= ( *aasworld ).reachabilitysize ) { + memset( reach, 0, sizeof( aas_reachability_t ) ); + return; + } //end if + memcpy( reach, &( *aasworld ).reachability[num], sizeof( aas_reachability_t ) );; +} //end of the function AAS_ReachabilityFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextAreaReachability( int areanum, int reachnum ) { + aas_areasettings_t *settings; + + if ( !( *aasworld ).initialized ) { + return 0; + } + + if ( areanum <= 0 || areanum >= ( *aasworld ).numareas ) { + botimport.Print( PRT_ERROR, "AAS_NextAreaReachability: areanum %d out of range\n", areanum ); + return 0; + } //end if + + settings = &( *aasworld ).areasettings[areanum]; + if ( !reachnum ) { + return settings->firstreachablearea; + } //end if + if ( reachnum < settings->firstreachablearea ) { + botimport.Print( PRT_FATAL, "AAS_NextAreaReachability: reachnum < settings->firstreachableara" ); + return 0; + } //end if + reachnum++; + if ( reachnum >= settings->firstreachablearea + settings->numreachableareas ) { + return 0; + } //end if + return reachnum; +} //end of the function AAS_NextAreaReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextModelReachability( int num, int modelnum ) { + int i; + + if ( num <= 0 ) { + num = 1; + } else if ( num >= ( *aasworld ).reachabilitysize ) { + return 0; + } else { num++;} + // + for ( i = num; i < ( *aasworld ).reachabilitysize; i++ ) + { + if ( ( *aasworld ).reachability[i].traveltype == TRAVEL_ELEVATOR ) { + if ( ( *aasworld ).reachability[i].facenum == modelnum ) { + return i; + } + } //end if + else if ( ( *aasworld ).reachability[i].traveltype == TRAVEL_FUNCBOB ) { + if ( ( ( *aasworld ).reachability[i].facenum & 0x0000FFFF ) == modelnum ) { + return i; + } + } //end if + } //end for + return 0; +} //end of the function AAS_NextModelReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_RandomGoalArea( int areanum, int travelflags, int *goalareanum, vec3_t goalorigin ) { + int i, n, t; + vec3_t start, end; + aas_trace_t trace; + + //if the area has no reachabilities + if ( !AAS_AreaReachability( areanum ) ) { + return qfalse; + } + // + n = ( *aasworld ).numareas * random(); + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + if ( n <= 0 ) { + n = 1; + } + if ( n >= ( *aasworld ).numareas ) { + n = 1; + } + if ( AAS_AreaReachability( n ) ) { + t = AAS_AreaTravelTimeToGoalArea( areanum, ( *aasworld ).areas[areanum].center, n, travelflags ); + //if the goal is reachable + if ( t > 0 ) { + if ( AAS_AreaSwim( n ) ) { + *goalareanum = n; + VectorCopy( ( *aasworld ).areas[n].center, goalorigin ); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + VectorCopy( ( *aasworld ).areas[n].center, start ); + if ( !AAS_PointAreaNum( start ) ) { + Log_Write( "area %d center %f %f %f in solid?", n, + start[0], start[1], start[2] ); + } + VectorCopy( start, end ); + end[2] -= 300; + trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, -1 ); + if ( !trace.startsolid && AAS_PointAreaNum( trace.endpos ) == n ) { + if ( AAS_AreaGroundFaceArea( n ) > 300 ) { + *goalareanum = n; + VectorCopy( trace.endpos, goalorigin ); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + } //end if + } //end if + } //end if + n++; + } //end for + return qfalse; +} //end of the function AAS_RandomGoalArea +//=========================================================================== +// run-length compression on zeros +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CompressVis( byte *vis, int numareas, byte *dest ) { + int j; + int rep; + //int visrow; + byte *dest_p; + byte check; + + // + dest_p = dest; + //visrow = (numareas + 7)>>3; + + for ( j = 0 ; j < numareas /*visrow*/ ; j++ ) + { + *dest_p++ = vis[j]; + check = vis[j]; + //if (vis[j]) + // continue; + + rep = 1; + for ( j++; j < numareas /*visrow*/ ; j++ ) + if ( vis[j] != check || rep == 255 ) { + break; + } else { + rep++; + } + *dest_p++ = rep; + j--; + } + return dest_p - dest; +} //end of the function AAS_CompressVis +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DecompressVis( byte *in, int numareas, byte *decompressed ) { + byte c; + byte *out; + //int row; + byte *end; + + // initialize the vis data, only set those that are visible + memset( decompressed, 0, numareas ); + + //row = (numareas+7)>>3; + out = decompressed; + end = ( byte * )( (int)decompressed + numareas ); + + do + { + /* + if (*in) + { + *out++ = *in++; + continue; + } + */ + + c = in[1]; + if ( !c ) { + AAS_Error( "DecompressVis: 0 repeat" ); + } + if ( *in ) { // we need to set these bits + memset( out, 1, c ); + } + in += 2; + /* + while (c) + { + *out++ = 0; + c--; + } + */ + out += c; + } while ( out < end ); +} //end of the function AAS_DecompressVis +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaVisible( int srcarea, int destarea ) { + if ( srcarea != ( *aasworld ).decompressedvisarea ) { + if ( !( *aasworld ).areavisibility[srcarea] ) { + return qfalse; + } + AAS_DecompressVis( ( *aasworld ).areavisibility[srcarea], + ( *aasworld ).numareas, ( *aasworld ).decompressedvis ); + ( *aasworld ).decompressedvisarea = srcarea; + } + return ( *aasworld ).decompressedvis[destarea]; +} //end of the function AAS_AreaVisible +//=========================================================================== +// just center to center visibility checking... +// FIXME: implement a correct full vis +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateVisibility( void ) { + int i, j, size, totalsize; + vec3_t endpos, mins, maxs; + bsp_trace_t trace; + byte *buf; + byte *validareas; + int numvalid = 0; + + buf = (byte *) GetClearedMemory( ( *aasworld ).numareas * 2 * sizeof( byte ) ); // in case it ends up bigger than the decompressedvis, which is rare but possible + validareas = (byte *) GetClearedMemory( ( *aasworld ).numareas * sizeof( byte ) ); + + ( *aasworld ).areavisibility = (byte **) GetClearedMemory( ( *aasworld ).numareas * sizeof( byte * ) ); + ( *aasworld ).decompressedvis = (byte *) GetClearedMemory( ( *aasworld ).numareas * sizeof( byte ) ); + ( *aasworld ).areawaypoints = (vec3_t *) GetClearedMemory( ( *aasworld ).numareas * sizeof( vec3_t ) ); + totalsize = ( *aasworld ).numareas * sizeof( byte * ); + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( !AAS_AreaReachability( i ) ) { + continue; + } + + // find the waypoint + VectorCopy( ( *aasworld ).areas[i].center, endpos ); + endpos[2] -= 256; + AAS_PresenceTypeBoundingBox( PRESENCE_NORMAL, mins, maxs ); +// maxs[2] = 0; + trace = AAS_Trace( ( *aasworld ).areas[i].center, mins, maxs, endpos, -1, CONTENTS_SOLID ); + if ( !trace.startsolid && trace.fraction < 1 && AAS_PointAreaNum( trace.endpos ) == i ) { + VectorCopy( trace.endpos, ( *aasworld ).areawaypoints[i] ); + validareas[i] = 1; + numvalid++; + } else { + continue; + } + } + + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( !validareas[i] ) { + continue; + } + + if ( !AAS_AreaReachability( i ) ) { + continue; + } + + for ( j = 1; j < ( *aasworld ).numareas; j++ ) + { + if ( i == j ) { + ( *aasworld ).decompressedvis[j] = 1; + continue; + } + if ( !validareas[j] || !AAS_AreaReachability( j ) ) { + ( *aasworld ).decompressedvis[j] = 0; + continue; + } //end if + + // Ridah, this always returns false?! + //if (AAS_inPVS( (*aasworld).areawaypoints[i], (*aasworld).areawaypoints[j] )) + trace = AAS_Trace( ( *aasworld ).areawaypoints[i], NULL, NULL, ( *aasworld ).areawaypoints[j], -1, CONTENTS_SOLID ); + if ( trace.fraction >= 1 ) { + //if (botimport.inPVS( (*aasworld).areawaypoints[i], (*aasworld).areawaypoints[j] )) + ( *aasworld ).decompressedvis[j] = 1; + } //end if + else + { + ( *aasworld ).decompressedvis[j] = 0; + } //end else + } //end for + size = AAS_CompressVis( ( *aasworld ).decompressedvis, ( *aasworld ).numareas, buf ); + ( *aasworld ).areavisibility[i] = (byte *) GetMemory( size ); + memcpy( ( *aasworld ).areavisibility[i], buf, size ); + totalsize += size; + } //end for + botimport.Print( PRT_MESSAGE, "AAS_CreateVisibility: compressed vis size = %i\n", totalsize ); +} //end of the function AAS_CreateVisibility +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float VectorDistance( vec3_t v1, vec3_t v2 ); +extern void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) ; +int AAS_NearestHideArea( int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags ) { + int i, j, nextareanum, badtravelflags, numreach, bestarea; + unsigned short int t, besttraveltime, enemytraveltime; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + float dist1, dist2; + float enemytraveldist; + vec3_t enemyVec; + qboolean startVisible; + vec3_t v1, v2, p; + int count = 0; + #define MAX_HIDEAREA_LOOPS 4000 + // + // don't run this more than once per frame + static float lastTime; + if ( lastTime == AAS_Time() ) { + return 0; + } + lastTime = AAS_Time(); + // + if ( !( *aasworld ).hidetraveltimes ) { + ( *aasworld ).hidetraveltimes = (unsigned short int *) GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); + } else { + memset( ( *aasworld ).hidetraveltimes, 0, ( *aasworld ).numareas * sizeof( unsigned short int ) ); + } //end else + // + if ( !( *aasworld ).visCache ) { + ( *aasworld ).visCache = (byte *) GetClearedMemory( ( *aasworld ).numareas * sizeof( byte ) ); + } else { + memset( ( *aasworld ).visCache, 0, ( *aasworld ).numareas * sizeof( byte ) ); + } //end else + besttraveltime = 0; + bestarea = 0; + if ( enemyareanum ) { + enemytraveltime = AAS_AreaTravelTimeToGoalArea( areanum, origin, enemyareanum, travelflags ); + } + VectorSubtract( enemyorigin, origin, enemyVec ); + enemytraveldist = VectorNormalize( enemyVec ); + startVisible = botimport.AICast_VisibleFromPos( enemyorigin, enemynum, origin, srcnum, qfalse ); + // + badtravelflags = ~travelflags; + // + curupdate = &( *aasworld ).areaupdate[areanum]; + curupdate->areanum = areanum; + VectorCopy( origin, curupdate->start ); + curupdate->areatraveltimes = ( *aasworld ).areatraveltimes[areanum][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while ( updateliststart ) + { + curupdate = updateliststart; + // + if ( curupdate->next ) { + curupdate->next->prev = NULL; + } else { updatelistend = NULL;} + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = ( *aasworld ).areasettings[curupdate->areanum].numreachableareas; + reach = &( *aasworld ).reachability[( *aasworld ).areasettings[curupdate->areanum].firstreachablearea]; + // + for ( i = 0; i < numreach; i++, reach++ ) + { + //if an undesired travel type is used + if ( ( *aasworld ).travelflagfortype[reach->traveltype] & badtravelflags ) { + continue; + } + // + if ( AAS_AreaContentsTravelFlag( reach->areanum ) & badtravelflags ) { + continue; + } + // + if ( ( *aasworld ).areasettings[reach->areanum].areaflags & AREA_DISABLED ) { + continue; + } + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if this moves us into the enemies area, skip it + if ( nextareanum == enemyareanum ) { + continue; + } + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + AAS_AreaTravelTime( curupdate->areanum, curupdate->start, reach->start ) + + reach->traveltime; + // if this isn't the fastest route to this area, ignore + if ( ( *aasworld ).hidetraveltimes[nextareanum] && ( *aasworld ).hidetraveltimes[nextareanum] < t ) { + continue; + } + ( *aasworld ).hidetraveltimes[nextareanum] = t; + // if the bestarea is this area, then it must be a longer route, so ignore it + if ( bestarea == nextareanum ) { + bestarea = 0; + besttraveltime = 0; + } + // do this test now, so we can reject the route if it starts out too long + if ( besttraveltime && t >= besttraveltime ) { + continue; + } + // + //avoid going near the enemy + ProjectPointOntoVector( enemyorigin, curupdate->start, reach->end, p ); + for ( j = 0; j < 3; j++ ) { + if ( ( p[j] > curupdate->start[j] + 0.1 && p[j] > reach->end[j] + 0.1 ) || + ( p[j] < curupdate->start[j] - 0.1 && p[j] < reach->end[j] - 0.1 ) ) { + break; + } + } + if ( j < 3 ) { + VectorSubtract( enemyorigin, reach->end, v2 ); + } //end if + else + { + VectorSubtract( enemyorigin, p, v2 ); + } //end else + dist2 = VectorLength( v2 ); + //never go through the enemy + if ( enemytraveldist > 32 && dist2 < enemytraveldist && dist2 < 256 ) { + continue; + } + // + VectorSubtract( reach->end, origin, v2 ); + if ( enemytraveldist > 32 && DotProduct( v2, enemyVec ) > enemytraveldist / 2 ) { + continue; + } + // + VectorSubtract( enemyorigin, curupdate->start, v1 ); + dist1 = VectorLength( v1 ); + // + if ( enemytraveldist > 32 && dist2 < dist1 ) { + t += ( dist1 - dist2 ) * 10; + // test it again after modifying it + if ( besttraveltime && t >= besttraveltime ) { + continue; + } + } + // make sure the hide area doesn't have anyone else in it + if ( AAS_IsEntityInArea( srcnum, -1, nextareanum ) ) { + t += 1000; // avoid this path/area + //continue; + } + // + // if we weren't visible when starting, make sure we don't move into their view + if ( enemyareanum && !startVisible && AAS_AreaVisible( enemyareanum, nextareanum ) ) { + continue; + //t += 1000; + } + // + if ( !besttraveltime || besttraveltime > t ) { + // + // if this area doesn't have a vis list, ignore it + if ( ( *aasworld ).areavisibility[nextareanum] ) { + //if the nextarea is not visible from the enemy area + if ( !AAS_AreaVisible( enemyareanum, nextareanum ) ) { // now last of all, check that this area is a safe hiding spot + if ( ( ( *aasworld ).visCache[nextareanum] == 2 ) || + ( !( *aasworld ).visCache[nextareanum] && !botimport.AICast_VisibleFromPos( enemyorigin, enemynum, ( *aasworld ).areawaypoints[nextareanum], srcnum, qfalse ) ) ) { + ( *aasworld ).visCache[nextareanum] = 2; + besttraveltime = t; + bestarea = nextareanum; + } else { + ( *aasworld ).visCache[nextareanum] = 1; + } + } //end if + } + // + // getting down to here is bad for cpu usage + if ( count++ > MAX_HIDEAREA_LOOPS ) { + //botimport.Print(PRT_MESSAGE, "AAS_NearestHideArea: exceeded max loops, aborting\n" ); + continue; + } + // + // otherwise, add this to the list so we check is reachables + // disabled, this should only store the raw traveltime, not the adjusted time + //(*aasworld).hidetraveltimes[nextareanum] = t; + nextupdate = &( *aasworld ).areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //remember where we entered this area + VectorCopy( reach->end, nextupdate->start ); + //if this update is not in the list yet + if ( !nextupdate->inlist ) { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if ( updatelistend ) { + updatelistend->next = nextupdate; + } else { updateliststart = nextupdate;} + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while + //botimport.Print(PRT_MESSAGE, "AAS_NearestHideArea: hidearea: %i, %i loops\n", bestarea, count ); + return bestarea; +} //end of the function AAS_NearestHideArea + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindAttackSpotWithinRange( int srcnum, int rangenum, int enemynum, float rangedist, int travelflags, float *outpos ) { + int i, nextareanum, badtravelflags, numreach, bestarea; + unsigned short int t, besttraveltime, enemytraveltime; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + vec3_t srcorg, rangeorg, enemyorg; + int srcarea, rangearea, enemyarea; + unsigned short int srctraveltime; + int count = 0; + #define MAX_ATTACKAREA_LOOPS 200 + // + // don't run this more than once per frame + static float lastTime; + if ( lastTime == AAS_Time() ) { + return 0; + } + lastTime = AAS_Time(); + // + if ( !( *aasworld ).hidetraveltimes ) { + ( *aasworld ).hidetraveltimes = (unsigned short int *) GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); + } else { + memset( ( *aasworld ).hidetraveltimes, 0, ( *aasworld ).numareas * sizeof( unsigned short int ) ); + } //end else + // + if ( !( *aasworld ).visCache ) { + ( *aasworld ).visCache = (byte *) GetClearedMemory( ( *aasworld ).numareas * sizeof( byte ) ); + } else { + memset( ( *aasworld ).visCache, 0, ( *aasworld ).numareas * sizeof( byte ) ); + } //end else + // + srcarea = AAS_BestReachableEntityArea( srcnum ); + rangearea = AAS_BestReachableEntityArea( rangenum ); + enemyarea = AAS_BestReachableEntityArea( enemynum ); + // + AAS_EntityOrigin( srcnum, srcorg ); + AAS_EntityOrigin( rangenum, rangeorg ); + AAS_EntityOrigin( enemynum, enemyorg ); + // + besttraveltime = 0; + bestarea = 0; + enemytraveltime = AAS_AreaTravelTimeToGoalArea( srcarea, srcorg, enemyarea, travelflags ); + // + badtravelflags = ~travelflags; + // + curupdate = &( *aasworld ).areaupdate[rangearea]; + curupdate->areanum = rangearea; + VectorCopy( rangeorg, curupdate->start ); + curupdate->areatraveltimes = ( *aasworld ).areatraveltimes[srcarea][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list, flip the lists + while ( updateliststart ) + { + curupdate = updateliststart; + // + if ( curupdate->next ) { + curupdate->next->prev = NULL; + } else { updatelistend = NULL;} + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = ( *aasworld ).areasettings[curupdate->areanum].numreachableareas; + reach = &( *aasworld ).reachability[( *aasworld ).areasettings[curupdate->areanum].firstreachablearea]; + // + for ( i = 0; i < numreach; i++, reach++ ) + { + //if an undesired travel type is used + if ( ( *aasworld ).travelflagfortype[reach->traveltype] & badtravelflags ) { + continue; + } + // + if ( AAS_AreaContentsTravelFlag( reach->areanum ) & badtravelflags ) { + continue; + } + // + if ( ( *aasworld ).areasettings[reach->areanum].areaflags & AREA_DISABLED ) { + continue; + } + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if this moves us into the enemies area, skip it + if ( nextareanum == enemyarea ) { + continue; + } + // if we've already been to this area + if ( ( *aasworld ).hidetraveltimes[nextareanum] ) { + continue; + } + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + if ( count++ > MAX_ATTACKAREA_LOOPS ) { + //botimport.Print(PRT_MESSAGE, "AAS_FindAttackSpotWithinRange: exceeded max loops, aborting\n" ); + if ( bestarea ) { + VectorCopy( ( *aasworld ).areawaypoints[bestarea], outpos ); + } + return bestarea; + } + t = curupdate->tmptraveltime + + AAS_AreaTravelTime( curupdate->areanum, curupdate->start, reach->start ) + + reach->traveltime; + // + // if it's too far from rangenum, ignore + if ( Distance( rangeorg, ( *aasworld ).areawaypoints[nextareanum] ) > rangedist ) { + continue; + } + // + // find the traveltime from srcnum + srctraveltime = AAS_AreaTravelTimeToGoalArea( srcarea, srcorg, nextareanum, travelflags ); + // do this test now, so we can reject the route if it starts out too long + if ( besttraveltime && srctraveltime >= besttraveltime ) { + continue; + } + // + // if this area doesn't have a vis list, ignore it + if ( ( *aasworld ).areavisibility[nextareanum] ) { + //if the nextarea can see the enemy area + if ( AAS_AreaVisible( enemyarea, nextareanum ) ) { // now last of all, check that this area is a good attacking spot + if ( ( ( *aasworld ).visCache[nextareanum] == 2 ) || + ( !( *aasworld ).visCache[nextareanum] && + ( count += 10 ) && // we are about to use lots of CPU time + botimport.AICast_CheckAttackAtPos( srcnum, enemynum, ( *aasworld ).areawaypoints[nextareanum], qfalse, qfalse ) ) ) { + ( *aasworld ).visCache[nextareanum] = 2; + besttraveltime = srctraveltime; + bestarea = nextareanum; + } else { + ( *aasworld ).visCache[nextareanum] = 1; + } + } //end if + } + ( *aasworld ).hidetraveltimes[nextareanum] = t; + nextupdate = &( *aasworld ).areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //remember where we entered this area + VectorCopy( reach->end, nextupdate->start ); + //if this update is not in the list yet + if ( !nextupdate->inlist ) { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if ( updatelistend ) { + updatelistend->next = nextupdate; + } else { updateliststart = nextupdate;} + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end for + } //end while +//botimport.Print(PRT_MESSAGE, "AAS_NearestHideArea: hidearea: %i, %i loops\n", bestarea, count ); + if ( bestarea ) { + VectorCopy( ( *aasworld ).areawaypoints[bestarea], outpos ); + } + return bestarea; +} //end of the function AAS_NearestHideArea diff --git a/src/botlib/be_aas_route.h b/src/botlib/be_aas_route.h new file mode 100644 index 0000000..33e8575 --- /dev/null +++ b/src/botlib/be_aas_route.h @@ -0,0 +1,65 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_route.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize the AAS routing +void AAS_InitRouting( void ); +//free the AAS routing caches +void AAS_FreeRoutingCaches( void ); +//returns the travel time from start to end in the given area +unsigned short int AAS_AreaTravelTime( int areanum, vec3_t start, vec3_t end ); +// +void AAS_CreateAllRoutingCache( void ); +// +void AAS_RoutingInfo( void ); +#endif //AASINTERN + +//returns the travel flag for the given travel type +int AAS_TravelFlagForType( int traveltype ); +// +int AAS_AreaContentsTravelFlag( int areanum ); +//returns the index of the next reachability for the given area +int AAS_NextAreaReachability( int areanum, int reachnum ); +//returns the reachability with the given index +void AAS_ReachabilityFromNum( int num, struct aas_reachability_s *reach ); +//returns a random goal area and goal origin +int AAS_RandomGoalArea( int areanum, int travelflags, int *goalareanum, vec3_t goalorigin ); +//returns the travel time within the given area from start to end +unsigned short int AAS_AreaTravelTime( int areanum, vec3_t start, vec3_t end ); +//returns the travel time from the area to the goal area using the given travel flags +int AAS_AreaTravelTimeToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags ); + diff --git a/src/botlib/be_aas_routealt.c b/src/botlib/be_aas_routealt.c new file mode 100644 index 0000000..606c1a6 --- /dev/null +++ b/src/botlib/be_aas_routealt.c @@ -0,0 +1,271 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_routealt.c + * + * desc: AAS + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +//#define ENABLE_ALTROUTING + +typedef struct midrangearea_s +{ + int valid; + unsigned short starttime; + unsigned short goaltime; +} midrangearea_t; + +midrangearea_t *midrangeareas; +int *clusterareas; +int numclusterareas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AltRoutingFloodCluster_r( int areanum ) { + int i, otherareanum; + aas_area_t *area; + aas_face_t *face; + + //add the current area to the areas of the current cluster + clusterareas[numclusterareas] = areanum; + numclusterareas++; + //remove the area from the mid range areas + midrangeareas[areanum].valid = qfalse; + //flood to other areas through the faces of this area + area = &( *aasworld ).areas[areanum]; + for ( i = 0; i < area->numfaces; i++ ) + { + face = &( *aasworld ).faces[abs( ( *aasworld ).faceindex[area->firstface + i] )]; + //get the area at the other side of the face + if ( face->frontarea == areanum ) { + otherareanum = face->backarea; + } else { otherareanum = face->frontarea;} + //if there is an area at the other side of this face + if ( !otherareanum ) { + continue; + } + //if the other area is not a midrange area + if ( !midrangeareas[otherareanum].valid ) { + continue; + } + // + AAS_AltRoutingFloodCluster_r( otherareanum ); + } //end for +} //end of the function AAS_AltRoutingFloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AlternativeRouteGoals( vec3_t start, vec3_t goal, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int color ) { +#ifndef ENABLE_ALTROUTING + return 0; +#else + int i, j, startareanum, goalareanum, bestareanum; + int numaltroutegoals, nummidrangeareas; + int starttime, goaltime, goaltraveltime; + float dist, bestdist; + vec3_t mid, dir; +#ifdef DEBUG + int startmillisecs; + + startmillisecs = Sys_MilliSeconds(); +#endif + + startareanum = AAS_PointAreaNum( start ); + if ( !startareanum ) { + return 0; + } + goalareanum = AAS_PointAreaNum( goal ); + if ( !goalareanum ) { + return 0; + } + //travel time towards the goal area + goaltraveltime = AAS_AreaTravelTimeToGoalArea( startareanum, start, goalareanum, travelflags ); + //clear the midrange areas + memset( midrangeareas, 0, ( *aasworld ).numareas * sizeof( midrangearea_t ) ); + numaltroutegoals = 0; + // + nummidrangeareas = 0; + // + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + // + if ( !( ( *aasworld ).areasettings[i].contents & AREACONTENTS_ROUTEPORTAL ) ) { + continue; + } + //if the area has no reachabilities + if ( !AAS_AreaReachability( i ) ) { + continue; + } + //tavel time from the area to the start area + starttime = AAS_AreaTravelTimeToGoalArea( startareanum, start, i, travelflags ); + if ( !starttime ) { + continue; + } + //if the travel time from the start to the area is greater than the shortest goal travel time + if ( starttime > 1.5 * goaltraveltime ) { + continue; + } + //travel time from the area to the goal area + goaltime = AAS_AreaTravelTimeToGoalArea( i, NULL, goalareanum, travelflags ); + if ( !goaltime ) { + continue; + } + //if the travel time from the area to the goal is greater than the shortest goal travel time + if ( goaltime > 1.5 * goaltraveltime ) { + continue; + } + //this is a mid range area + midrangeareas[i].valid = qtrue; + midrangeareas[i].starttime = starttime; + midrangeareas[i].goaltime = goaltime; + Log_Write( "%d midrange area %d", nummidrangeareas, i ); + nummidrangeareas++; + } //end for + // + for ( i = 1; i < ( *aasworld ).numareas; i++ ) + { + if ( !midrangeareas[i].valid ) { + continue; + } + //get the areas in one cluster + numclusterareas = 0; + AAS_AltRoutingFloodCluster_r( i ); + //now we've got a cluster with areas through which an alternative route could go + //get the 'center' of the cluster + VectorClear( mid ); + for ( j = 0; j < numclusterareas; j++ ) + { + VectorAdd( mid, ( *aasworld ).areas[clusterareas[j]].center, mid ); + } //end for + VectorScale( mid, 1.0 / numclusterareas, mid ); + //get the area closest to the center of the cluster + bestdist = 999999; + bestareanum = 0; + for ( j = 0; j < numclusterareas; j++ ) + { + VectorSubtract( mid, ( *aasworld ).areas[clusterareas[j]].center, dir ); + dist = VectorLength( dir ); + if ( dist < bestdist ) { + bestdist = dist; + bestareanum = clusterareas[j]; + } //end if + } //end for + //now we've got an area for an alternative route + //FIXME: add alternative goal origin + VectorCopy( ( *aasworld ).areas[bestareanum].center, altroutegoals[numaltroutegoals].origin ); + altroutegoals[numaltroutegoals].areanum = bestareanum; + altroutegoals[numaltroutegoals].starttraveltime = midrangeareas[bestareanum].starttime; + altroutegoals[numaltroutegoals].goaltraveltime = midrangeareas[bestareanum].goaltime; + altroutegoals[numaltroutegoals].extratraveltime = + ( midrangeareas[bestareanum].starttime + midrangeareas[bestareanum].goaltime ) - + goaltraveltime; + numaltroutegoals++; + // +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "alternative route goal area %d, numclusterareas = %d\n", bestareanum, numclusterareas ); + if ( color ) { + AAS_DrawPermanentCross( ( *aasworld ).areas[bestareanum].center, 10, color ); + } //end if + //AAS_ShowArea(bestarea, qtrue); +#endif + //don't return more than the maximum alternative route goals + if ( numaltroutegoals >= maxaltroutegoals ) { + break; + } + } //end for + botimport.Print( PRT_MESSAGE, "%d alternative route goals\n", numaltroutegoals ); +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "alternative route goals in %d msec\n", Sys_MilliSeconds() - startmillisecs ); +#endif + return numaltroutegoals; +#endif +} //end of the function AAS_AlternativeRouteGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAlternativeRouting( void ) { +#ifdef ENABLE_ALTROUTING + if ( midrangeareas ) { + FreeMemory( midrangeareas ); + } + midrangeareas = (midrangearea_t *) GetMemory( ( *aasworld ).numareas * sizeof( midrangearea_t ) ); + if ( clusterareas ) { + FreeMemory( clusterareas ); + } + clusterareas = (int *) GetMemory( aasworld.numareas * sizeof( int ) ); +#endif +} //end of the function AAS_InitAlternativeRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutdownAlternativeRouting( void ) { +#ifdef ENABLE_ALTROUTING + if ( midrangeareas ) { + FreeMemory( midrangeareas ); + } + midrangeareas = NULL; + if ( clusterareas ) { + FreeMemory( clusterareas ); + } + clusterareas = NULL; + numclusterareas = 0; +#endif +} diff --git a/src/botlib/be_aas_routealt.h b/src/botlib/be_aas_routealt.h new file mode 100644 index 0000000..1f9b3de --- /dev/null +++ b/src/botlib/be_aas_routealt.h @@ -0,0 +1,46 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_routealt.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +void AAS_InitAlternativeRouting( void ); +void AAS_ShutdownAlternativeRouting( void ); +#endif //AASINTERN + + +int AAS_AlternativeRouteGoals( vec3_t start, vec3_t goal, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int color ); diff --git a/src/botlib/be_aas_routetable.c b/src/botlib/be_aas_routetable.c new file mode 100644 index 0000000..8b33930 --- /dev/null +++ b/src/botlib/be_aas_routetable.c @@ -0,0 +1,1450 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: be_aas_routetable.c +// Function: Area Awareness System, Route-table defines +// Programmer: Ridah +// Tab Size: 3 +//=========================================================================== + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_interface.h" +#include "be_aas_def.h" + +// ugly hack to turn off route-tables, can't find a way to check cvar's +int disable_routetable = 0; + +// this must be enabled for the route-tables to work, but it's not fully operational yet +#define CHECK_TRAVEL_TIMES +//#define DEBUG_ROUTETABLE +#define FILTERAREAS + +// enable this to use the built-in route-cache system to find the routes +#define USE_ROUTECACHE + +// enable this to disable Rocket/BFG Jumping, Grapple Hook +#define FILTER_TRAVEL + +// hmm, is there a cleaner way of finding out memory usage? +extern int totalmemorysize; +static int memorycount, cachememory; + +// globals to reduce function parameters +static unsigned short int *filtered_areas, childcount, num_parents; +static unsigned short int *rev_filtered_areas; + +// misc defines +unsigned short CRC_ProcessString( unsigned char *data, int length ); + + +//=========================================================================== +// Memory debugging/optimization + +void *AAS_RT_GetClearedMemory( unsigned long size ) { + void *ptr; + + memorycount += size; + + // ptr = GetClearedMemory(size); + //ptr = GetClearedHunkMemory(size); + // Ryan - 01102k, need to use this, since the routetable calculations use up a lot of memory + // this will be a non-issue once we transfer the remnants of the routetable over to the aasworld + ptr = malloc( size ); + memset( ptr, 0, size ); + + return ptr; +} + +void AAS_RT_FreeMemory( void *ptr ) { + int before; + + before = totalmemorysize; + + // FreeMemory( ptr ); + // Ryan - 01102k + free( ptr ); + + memorycount -= before - totalmemorysize; +} + +void AAS_RT_PrintMemoryUsage() { +#ifdef AAS_RT_MEMORY_USAGE + + botimport.Print( PRT_MESSAGE, "\n" ); + + // TODO: print the usage from each of the aas_rt_t lumps + +#endif +} +//=========================================================================== + + +//=========================================================================== +// return the number of unassigned areas that are in the given area's visible list +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_RT_GetValidVisibleAreasCount( aas_area_buildlocalinfo_t *localinfo, aas_area_childlocaldata_t **childlocaldata ) { + int i, cnt; + + cnt = 1; // assume it can reach itself + + for ( i = 0; i < localinfo->numvisible; i++ ) + { + if ( childlocaldata[localinfo->visible[i]] ) { + continue; + } + + cnt++; + } + + return cnt; +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static aas_rt_route_t **routetable; + +int AAS_AreaRouteToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum ); + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_RT_CalcTravelTimesToGoalArea( int goalarea ) { + int i; + // TTimo: unused +// static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_LAVA); //----(SA) modified since slime is no longer deadly +// static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); + aas_rt_route_t *rt; + int reach, travel; + + for ( i = 0; i < childcount; i++ ) { + rt = &routetable[i][-1 + rev_filtered_areas[goalarea]]; + if ( AAS_AreaRouteToGoalArea( filtered_areas[i], ( *aasworld ).areas[filtered_areas[i]].center, goalarea, ~RTB_BADTRAVELFLAGS, &travel, &reach ) ) { + rt->reachable_index = reach; + rt->travel_time = travel; + } else { + rt->reachable_index = -1; + rt->travel_time = 0; + } + } +} +//=========================================================================== +// calculate the initial route-table for each filtered area to all other areas +// +// FIXME: this isn't fully operational yet, for some reason not all routes are found +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_CalculateRouteTable( aas_rt_route_t **parmroutetable ) { + int i; + + routetable = parmroutetable; + + for ( i = 0; i < childcount; i++ ) + { + AAS_RT_CalcTravelTimesToGoalArea( filtered_areas[i] ); + } +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_AddParentLink( aas_area_childlocaldata_t *child, int parentindex, int childindex ) { + aas_parent_link_t *oldparentlink; + + oldparentlink = child->parentlink; + + child->parentlink = (aas_parent_link_t *) AAS_RT_GetClearedMemory( sizeof( aas_parent_link_t ) ); + + child->parentlink->childindex = (unsigned short int)childindex; + child->parentlink->parent = (unsigned short int)parentindex; + child->parentlink->next = oldparentlink; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_WriteShort( unsigned short int si, fileHandle_t fp ) { + unsigned short int lsi; + + lsi = LittleShort( si ); + botimport.FS_Write( &lsi, sizeof( lsi ), fp ); +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_WriteByte( int si, fileHandle_t fp ) { + unsigned char uc; + + uc = si; + botimport.FS_Write( &uc, sizeof( uc ), fp ); +} + +//=========================================================================== +// writes the current route-table data to a .rtb file in tne maps folder +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_WriteRouteTable() { + int ident, version; + unsigned short crc_aas; + fileHandle_t fp; + char filename[MAX_QPATH]; + + // open the file for writing + Com_sprintf( filename, MAX_QPATH, "maps/%s.rtb", ( *aasworld ).mapname ); + botimport.Print( PRT_MESSAGE, "\nsaving route-table to %s\n", filename ); + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if ( !fp ) { + AAS_Error( "Unable to open file: %s\n", filename ); + return; + } + + // ident + ident = LittleLong( RTBID ); + botimport.FS_Write( &ident, sizeof( ident ), fp ); + + // version + version = LittleLong( RTBVERSION ); + botimport.FS_Write( &version, sizeof( version ), fp ); + + // crc + crc_aas = CRC_ProcessString( (unsigned char *)( *aasworld ).areas, sizeof( aas_area_t ) * ( *aasworld ).numareas ); + botimport.FS_Write( &crc_aas, sizeof( crc_aas ), fp ); + + // save the table data + + // children + botimport.FS_Write( &( *aasworld ).routetable->numChildren, sizeof( int ), fp ); + botimport.FS_Write( ( *aasworld ).routetable->children, ( *aasworld ).routetable->numChildren * sizeof( aas_rt_child_t ), fp ); + + // parents + botimport.FS_Write( &( *aasworld ).routetable->numParents, sizeof( int ), fp ); + botimport.FS_Write( ( *aasworld ).routetable->parents, ( *aasworld ).routetable->numParents * sizeof( aas_rt_parent_t ), fp ); + + // parentChildren + botimport.FS_Write( &( *aasworld ).routetable->numParentChildren, sizeof( int ), fp ); + botimport.FS_Write( ( *aasworld ).routetable->parentChildren, ( *aasworld ).routetable->numParentChildren * sizeof( unsigned short int ), fp ); + + // visibleParents + botimport.FS_Write( &( *aasworld ).routetable->numVisibleParents, sizeof( int ), fp ); + botimport.FS_Write( ( *aasworld ).routetable->visibleParents, ( *aasworld ).routetable->numVisibleParents * sizeof( unsigned short int ), fp ); + + // parentLinks + botimport.FS_Write( &( *aasworld ).routetable->numParentLinks, sizeof( int ), fp ); + botimport.FS_Write( ( *aasworld ).routetable->parentLinks, ( *aasworld ).routetable->numParentLinks * sizeof( aas_rt_parent_link_t ), fp ); + + botimport.FS_FCloseFile( fp ); + return; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_DBG_Read( void *buf, int size, int fp ) { + botimport.FS_Read( buf, size, fp ); +} + +//=========================================================================== +// reads the given file, and creates the structures required for the route-table system +// +// Parameter: - +// Returns: qtrue if succesful, qfalse if not +// Changes Globals: - +//=========================================================================== +#define DEBUG_READING_TIME +qboolean AAS_RT_ReadRouteTable( fileHandle_t fp ) { + int ident, version, i; + unsigned short int crc, crc_aas; + aas_rt_t *routetable; + aas_rt_child_t *child; + aas_rt_parent_t *parent; + aas_rt_parent_link_t *plink; + unsigned short int *psi; + + qboolean doswap; + +#ifdef DEBUG_READING_TIME + int pretime; + + pretime = Sys_MilliSeconds(); +#endif + + routetable = ( *aasworld ).routetable; + + doswap = ( LittleLong( 1 ) != 1 ); + + // check ident + AAS_RT_DBG_Read( &ident, sizeof( ident ), fp ); + ident = LittleLong( ident ); + + if ( ident != RTBID ) { + AAS_Error( "File is not an RTB file\n" ); + botimport.FS_FCloseFile( fp ); + return qfalse; + } + + // check version + AAS_RT_DBG_Read( &version, sizeof( version ), fp ); + version = LittleLong( version ); + + if ( version != RTBVERSION ) { + AAS_Error( "File is version %i not %i\n", version, RTBVERSION ); + botimport.FS_FCloseFile( fp ); + return qfalse; + } + + // read the CRC check on the AAS data + AAS_RT_DBG_Read( &crc, sizeof( crc ), fp ); + crc = LittleShort( crc ); + + // calculate a CRC on the AAS areas + crc_aas = CRC_ProcessString( (unsigned char *)( *aasworld ).areas, sizeof( aas_area_t ) * ( *aasworld ).numareas ); + + if ( crc != crc_aas ) { + AAS_Error( "Route-table is from different AAS file, ignoring.\n" ); + botimport.FS_FCloseFile( fp ); + return qfalse; + } + + // read the route-table + + // children + botimport.FS_Read( &routetable->numChildren, sizeof( int ), fp ); + routetable->numChildren = LittleLong( routetable->numChildren ); + routetable->children = (aas_rt_child_t *) AAS_RT_GetClearedMemory( routetable->numChildren * sizeof( aas_rt_child_t ) ); + botimport.FS_Read( routetable->children, routetable->numChildren * sizeof( aas_rt_child_t ), fp ); + child = &routetable->children[0]; + if ( doswap ) { + for ( i = 0; i < routetable->numChildren; i++, child++ ) { + child->areanum = LittleShort( child->areanum ); + child->numParentLinks = LittleLong( child->numParentLinks ); + child->startParentLinks = LittleLong( child->startParentLinks ); + } + } + + // parents + botimport.FS_Read( &routetable->numParents, sizeof( int ), fp ); + routetable->numParents = LittleLong( routetable->numParents ); + routetable->parents = (aas_rt_parent_t *) AAS_RT_GetClearedMemory( routetable->numParents * sizeof( aas_rt_parent_t ) ); + botimport.FS_Read( routetable->parents, routetable->numParents * sizeof( aas_rt_parent_t ), fp ); + parent = &routetable->parents[0]; + if ( doswap ) { + for ( i = 0; i < routetable->numParents; i++, parent++ ) { + parent->areanum = LittleShort( parent->areanum ); + parent->numParentChildren = LittleLong( parent->numParentChildren ); + parent->startParentChildren = LittleLong( parent->startParentChildren ); + parent->numVisibleParents = LittleLong( parent->numVisibleParents ); + parent->startVisibleParents = LittleLong( parent->startVisibleParents ); + } + } + + // parentChildren + botimport.FS_Read( &routetable->numParentChildren, sizeof( int ), fp ); + routetable->numParentChildren = LittleLong( routetable->numParentChildren ); + routetable->parentChildren = (unsigned short int *) AAS_RT_GetClearedMemory( routetable->numParentChildren * sizeof( unsigned short int ) ); + botimport.FS_Read( routetable->parentChildren, routetable->numParentChildren * sizeof( unsigned short int ), fp ); + psi = &routetable->parentChildren[0]; + if ( doswap ) { + for ( i = 0; i < routetable->numParentChildren; i++, psi++ ) { + *psi = LittleShort( *psi ); + } + } + + // visibleParents + botimport.FS_Read( &routetable->numVisibleParents, sizeof( int ), fp ); + routetable->numVisibleParents = LittleLong( routetable->numVisibleParents ); + routetable->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory( routetable->numVisibleParents * sizeof( unsigned short int ) ); + botimport.FS_Read( routetable->visibleParents, routetable->numVisibleParents * sizeof( unsigned short int ), fp ); + psi = &routetable->visibleParents[0]; + if ( doswap ) { + for ( i = 0; i < routetable->numVisibleParents; i++, psi++ ) { + *psi = LittleShort( *psi ); + } + } + + // parentLinks + botimport.FS_Read( &routetable->numParentLinks, sizeof( int ), fp ); + routetable->numParentLinks = LittleLong( routetable->numParentLinks ); + routetable->parentLinks = (aas_rt_parent_link_t *) AAS_RT_GetClearedMemory( routetable->numParentLinks * sizeof( aas_rt_parent_link_t ) ); + botimport.FS_Read( routetable->parentLinks, routetable->numParentLinks * sizeof( aas_parent_link_t ), fp ); + plink = &routetable->parentLinks[0]; + if ( doswap ) { + for ( i = 0; i < routetable->numParentLinks; i++, plink++ ) { + plink->childIndex = LittleShort( plink->childIndex ); + plink->parent = LittleShort( plink->parent ); + } + } + + // build the areaChildIndexes + routetable->areaChildIndexes = (unsigned short int *) AAS_RT_GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); + child = routetable->children; + for ( i = 0; i < routetable->numChildren; i++, child++ ) { + routetable->areaChildIndexes[child->areanum] = i + 1; + } + + botimport.Print( PRT_MESSAGE, "Total Parents: %d\n", routetable->numParents ); + botimport.Print( PRT_MESSAGE, "Total Children: %d\n", routetable->numChildren ); + botimport.Print( PRT_MESSAGE, "Total Memory Used: %d\n", memorycount ); + +#ifdef DEBUG_READING_TIME + botimport.Print( PRT_MESSAGE, "Route-Table read time: %i\n", Sys_MilliSeconds() - pretime ); +#endif + + botimport.FS_FCloseFile( fp ); + return qtrue; +} + +int AAS_RT_NumParentLinks( aas_area_childlocaldata_t *child ) { + aas_parent_link_t *plink; + int i; + + i = 0; + plink = child->parentlink; + while ( plink ) + { + i++; + plink = plink->next; + } + + return i; +} + +//=========================================================================== +// main routine to build the route-table +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAllRoutingCache( void ); + +void AAS_RT_BuildRouteTable( void ) { + int i,j,k; + aas_area_t *srcarea; + aas_areasettings_t *srcsettings; +// vec3_t vec; + unsigned int totalcount; + unsigned int noroutecount; + + aas_area_buildlocalinfo_t **area_localinfos; + aas_area_buildlocalinfo_t *localinfo; + + aas_area_childlocaldata_t **area_childlocaldata; + aas_area_childlocaldata_t *child; + + aas_area_parent_t *area_parents[MAX_PARENTS]; + aas_area_parent_t *thisparent; + + int bestchild, bestcount, bestparent, cnt; + + int memoryend; + + unsigned short int *visibleParents; + +#ifdef CHECK_TRAVEL_TIMES + aas_rt_route_t **filteredroutetable; + unsigned short int traveltime; +#endif + + fileHandle_t fp; + char filename[MAX_QPATH]; + +// not used anymore + return; + + // create the routetable in this aasworld + aasworld->routetable = (aas_rt_t *) AAS_RT_GetClearedMemory( sizeof( aas_rt_t ) ); + + // Try to load in a prepared route-table + Com_sprintf( filename, MAX_QPATH, "maps/%s.rtb", ( *aasworld ).mapname ); + botimport.Print( PRT_MESSAGE, "\n---------------------------------\n" ); + botimport.Print( PRT_MESSAGE, "\ntrying to load %s\n", filename ); + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if ( fp ) { + // read in the table.. + if ( AAS_RT_ReadRouteTable( fp ) ) { + AAS_RT_PrintMemoryUsage(); + + botimport.Print( PRT_MESSAGE, "\nAAS Route-Table loaded.\n" ); + botimport.Print( PRT_MESSAGE, "---------------------------------\n\n" ); + return; + } else + { + botimport.Print( PRT_MESSAGE, "\nUnable to load %s, building route-table..\n", filename ); + } + } else + { + botimport.Print( PRT_MESSAGE, "file not found, building route-table\n\n" ); + } + + + botimport.Print( PRT_MESSAGE, "\n-------------------------------------\nRoute-table memory usage figures..\n\n" ); + + totalcount = 0; + childcount = 0; + noroutecount = 0; + childcount = 0; + num_parents = 0; + + memorycount = 0; + cachememory = 0; + + filtered_areas = (unsigned short int *) AAS_RT_GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); + rev_filtered_areas = (unsigned short int *) AAS_RT_GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); + + // to speed things up, build a list of FILTERED areas first + // do this so we can check for filtered areas + AAS_CreateAllRoutingCache(); + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + srcarea = &( *aasworld ).areas[i]; + srcsettings = &( *aasworld ).areasettings[i]; + +#ifdef FILTERAREAS + if ( !( srcsettings->areaflags & ( AREA_USEFORROUTING ) ) ) { + continue; + } + if ( !( srcsettings->areaflags & ( AREA_GROUNDED | AREA_LIQUID | AREA_LADDER ) ) ) { + continue; + } +#endif + + rev_filtered_areas[i] = childcount + 1; + filtered_areas[childcount++] = (unsigned short int)i; + } + +#ifdef CHECK_TRAVEL_TIMES + // allocate and calculate the travel times + filteredroutetable = (aas_rt_route_t **) AAS_RT_GetClearedMemory( childcount * sizeof( aas_rt_route_t * ) ); + for ( i = 0; i < childcount; i++ ) + filteredroutetable[i] = (aas_rt_route_t *) AAS_RT_GetClearedMemory( childcount * sizeof( aas_rt_route_t ) ); + + AAS_RT_CalculateRouteTable( filteredroutetable ); + +#endif // CHECK_TRAVEL_TIMES + + // allocate for the temporary build local data + area_localinfos = (aas_area_buildlocalinfo_t **) AAS_RT_GetClearedMemory( childcount * sizeof( aas_area_buildlocalinfo_t * ) ); + + for ( i = 0; i < childcount; i++ ) + { + srcarea = &( *aasworld ).areas[filtered_areas[i]]; + srcsettings = &( *aasworld ).areasettings[filtered_areas[i]]; + + // allocate memory for this area + area_localinfos[i] = (aas_area_buildlocalinfo_t *) AAS_RT_GetClearedMemory( sizeof( aas_area_buildlocalinfo_t ) ); + localinfo = area_localinfos[i]; + + for ( j = 0; j < childcount; j++ ) + { + if ( i == j ) { + continue; + } + +#ifdef CHECK_TRAVEL_TIMES + + // make sure travel time is reasonable + // Get the travel time from i to j + traveltime = (int)filteredroutetable[i][j].travel_time; + + if ( !traveltime ) { + noroutecount++; + continue; + } + if ( traveltime > MAX_LOCALTRAVELTIME ) { + continue; + } + +#endif // CHECK_TRAVEL_TIMES + + // Add it to the list + localinfo->visible[localinfo->numvisible++] = j; + totalcount++; + + if ( localinfo->numvisible >= MAX_VISIBLE_AREAS ) { + botimport.Print( PRT_MESSAGE, "MAX_VISIBLE_AREAS exceeded, lower MAX_VISIBLE_RANGE\n" ); + break; + } + } + } + + // now calculate the best list of locale's + + // allocate for the long-term child data + area_childlocaldata = (aas_area_childlocaldata_t **) AAS_RT_GetClearedMemory( childcount * sizeof( aas_area_childlocaldata_t * ) ); + + for ( i = 0; i < childcount; i++ ) + { + area_childlocaldata[i] = (aas_area_childlocaldata_t *) AAS_RT_GetClearedMemory( sizeof( aas_area_childlocaldata_t ) ); + area_childlocaldata[i]->areanum = filtered_areas[i]; + } + + while ( 1 ) + { + bestchild = -1; + bestcount = 99999; + + // find the area with the least number of visible areas + for ( i = 0; i < childcount; i++ ) + { + if ( area_childlocaldata[i]->parentlink ) { + continue; // already has been allocated to a parent + + } + cnt = AAS_RT_GetValidVisibleAreasCount( area_localinfos[i], area_childlocaldata ); + + if ( cnt < bestcount ) { + bestcount = area_localinfos[i]->numvisible; + bestchild = i; + } + } + + if ( bestchild < 0 ) { + break; // our job is done + + + } + localinfo = area_localinfos[bestchild]; + + + // look through this area's list of visible areas, and pick the one with the most VALID visible areas + bestparent = bestchild; + + for ( i = 0; i < localinfo->numvisible; i++ ) + { + if ( area_childlocaldata[localinfo->visible[i]]->parentlink ) { + continue; // already has been allocated to a parent + + } + // calculate how many of children are valid + cnt = AAS_RT_GetValidVisibleAreasCount( area_localinfos[localinfo->visible[i]], area_childlocaldata ); + + if ( cnt > bestcount ) { + bestcount = cnt; + bestparent = localinfo->visible[i]; + } + } + + // now setup this parent, and assign all it's children + localinfo = area_localinfos[bestparent]; + + // we use all children now, not just valid ones + bestcount = localinfo->numvisible; + + area_parents[num_parents] = (aas_area_parent_t *) AAS_RT_GetClearedMemory( sizeof( aas_area_parent_t ) ); + thisparent = area_parents[num_parents]; + + thisparent->areanum = filtered_areas[bestparent]; + thisparent->children = (unsigned short int *) AAS_RT_GetClearedMemory( ( localinfo->numvisible + 1 ) * sizeof( unsigned short int ) ); + + // first, add itself to the list (yes, a parent is a child of itself) + child = area_childlocaldata[bestparent]; + AAS_RT_AddParentLink( child, num_parents, thisparent->numchildren ); + thisparent->children[thisparent->numchildren++] = filtered_areas[bestparent]; + + // loop around all the parent's visible list, and make them children if they're aren't already assigned to a parent + for ( i = 0; i < localinfo->numvisible; i++ ) + { + // create the childlocaldata + child = area_childlocaldata[localinfo->visible[i]]; + + // Ridah, only one parent per child in the new system + if ( child->parentlink ) { + continue; // already has been allocated to a parent + + } + if ( child->areanum != thisparent->areanum ) { + AAS_RT_AddParentLink( child, num_parents, thisparent->numchildren ); + thisparent->children[thisparent->numchildren++] = filtered_areas[localinfo->visible[i]]; + } + } + + // now setup the list of children and the route-tables + for ( i = 0; i < thisparent->numchildren; i++ ) + { + child = area_childlocaldata[-1 + rev_filtered_areas[thisparent->children[i]]]; + localinfo = area_localinfos[-1 + rev_filtered_areas[thisparent->children[i]]]; + + child->parentlink->routeindexes = (unsigned short int *) AAS_RT_GetClearedMemory( thisparent->numchildren * sizeof( unsigned short int ) ); + + // now setup the indexes + for ( j = 0; j < thisparent->numchildren; j++ ) + { + // find this child in our list of visibles + if ( j == child->parentlink->childindex ) { + continue; + } + + for ( k = 0; k < localinfo->numvisible; k++ ) + { + if ( thisparent->children[j] == filtered_areas[localinfo->visible[k]] ) { // found a match + child->parentlink->routeindexes[j] = (unsigned short int)k; + break; + } + } + + if ( k == localinfo->numvisible ) { // didn't find it, so add it to our list + if ( localinfo->numvisible >= MAX_VISIBLE_AREAS ) { + botimport.Print( PRT_MESSAGE, "MAX_VISIBLE_AREAS exceeded, lower MAX_VISIBLE_RANGE\n" ); + } else + { + localinfo->visible[localinfo->numvisible] = -1 + rev_filtered_areas[thisparent->children[j]]; + child->parentlink->routeindexes[j] = (unsigned short int)localinfo->numvisible; + localinfo->numvisible++; + } + } + } + } + + num_parents++; + } + + // place all the visible areas from each child, into their childlocaldata route-table + for ( i = 0; i < childcount; i++ ) + { + localinfo = area_localinfos[i]; + child = area_childlocaldata[i]; + + child->numlocal = localinfo->numvisible; + child->localroutes = (aas_rt_route_t *) AAS_RT_GetClearedMemory( localinfo->numvisible * sizeof( aas_rt_route_t ) ); + + for ( j = 0; j < localinfo->numvisible; j++ ) + { + child->localroutes[j] = filteredroutetable[i][localinfo->visible[j]]; + } + + child->parentroutes = (aas_rt_route_t *) AAS_RT_GetClearedMemory( num_parents * sizeof( aas_rt_route_t ) ); + + for ( j = 0; j < num_parents; j++ ) + { + child->parentroutes[j] = filteredroutetable[i][-1 + rev_filtered_areas[area_parents[j]->areanum]]; + } + } + + // build the visibleParents lists + visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory( num_parents * sizeof( unsigned short int ) ); + for ( i = 0; i < num_parents; i++ ) + { + area_parents[i]->numVisibleParents = 0; + + for ( j = 0; j < num_parents; j++ ) + { + if ( i == j ) { + continue; + } + + if ( !AAS_inPVS( ( *aasworld ).areas[area_parents[i]->areanum].center, ( *aasworld ).areas[area_parents[j]->areanum].center ) ) { + continue; + } + + visibleParents[area_parents[i]->numVisibleParents] = j; + area_parents[i]->numVisibleParents++; + } + + // now copy the list over to the current src area + area_parents[i]->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory( area_parents[i]->numVisibleParents * sizeof( unsigned short int ) ); + memcpy( area_parents[i]->visibleParents, visibleParents, area_parents[i]->numVisibleParents * sizeof( unsigned short int ) ); + + } + AAS_RT_FreeMemory( visibleParents ); + + // before we free the main childlocaldata, go through and assign the aas_area's to their appropriate childlocaldata + // this would require modification of the aas_area_t structure, so for now, we'll just place them in a global array, for external reference + +// aasworld->routetable->area_childlocaldata_list = (aas_area_childlocaldata_t **) AAS_RT_GetClearedMemory( (*aasworld).numareas * sizeof(aas_area_childlocaldata_t *) ); +// for (i=0; iroutetable->area_childlocaldata_list[filtered_areas[i]] = area_childlocaldata[i]; +// } + + // copy the list of parents to a global structure for now (should eventually go into the (*aasworld) structure +// aasworld->routetable->area_parents_global = (aas_area_parent_t **) AAS_RT_GetClearedMemory( num_parents * sizeof(aas_area_parent_t *) ); +// memcpy( aasworld->routetable->area_parents_global, area_parents, num_parents * sizeof(aas_area_parent_t *) ); + + // ................................................ + // Convert the data into the correct format + { + aas_rt_t *rt; + aas_rt_child_t *child; + aas_rt_parent_t *parent; + aas_rt_parent_link_t *plink; + unsigned short int *psi; + + aas_area_childlocaldata_t *chloc; + aas_area_parent_t *apar; + aas_parent_link_t *oplink; + + int localRoutesCount, parentRoutesCount, parentChildrenCount, visibleParentsCount, parentLinkCount, routeIndexesCount; + + rt = ( *aasworld ).routetable; + localRoutesCount = 0; + parentRoutesCount = 0; + parentChildrenCount = 0; + visibleParentsCount = 0; + parentLinkCount = 0; + routeIndexesCount = 0; + + // areaChildIndexes + rt->areaChildIndexes = (unsigned short int *) AAS_RT_GetClearedMemory( ( *aasworld ).numareas * sizeof( unsigned short int ) ); + for ( i = 0; i < childcount; i++ ) + { + rt->areaChildIndexes[filtered_areas[i]] = i + 1; + } + + // children + rt->numChildren = childcount; + rt->children = (aas_rt_child_t *) AAS_RT_GetClearedMemory( rt->numChildren * sizeof( aas_rt_child_t ) ); + child = rt->children; + for ( i = 0; i < childcount; i++, child++ ) + { + chloc = area_childlocaldata[i]; + + child->areanum = chloc->areanum; + child->numParentLinks = AAS_RT_NumParentLinks( chloc ); + + child->startParentLinks = parentLinkCount; + + parentLinkCount += child->numParentLinks; + } + + // parents + rt->numParents = num_parents; + rt->parents = (aas_rt_parent_t *) AAS_RT_GetClearedMemory( rt->numParents * sizeof( aas_rt_parent_t ) ); + parent = rt->parents; + for ( i = 0; i < num_parents; i++, parent++ ) + { + apar = area_parents[i]; + + parent->areanum = apar->areanum; + parent->numParentChildren = apar->numchildren; + parent->numVisibleParents = apar->numVisibleParents; + + parent->startParentChildren = parentChildrenCount; + parent->startVisibleParents = visibleParentsCount; + + parentChildrenCount += parent->numParentChildren; + visibleParentsCount += parent->numVisibleParents; + } + + // parentChildren + rt->numParentChildren = parentChildrenCount; + rt->parentChildren = (unsigned short int *) AAS_RT_GetClearedMemory( parentChildrenCount * sizeof( unsigned short int ) ); + psi = rt->parentChildren; + for ( i = 0; i < num_parents; i++ ) + { + apar = area_parents[i]; + for ( j = 0; j < apar->numchildren; j++, psi++ ) + { + *psi = apar->children[j]; + } + } + + // visibleParents + rt->numVisibleParents = visibleParentsCount; + rt->visibleParents = (unsigned short int *) AAS_RT_GetClearedMemory( rt->numVisibleParents * sizeof( unsigned short int ) ); + psi = rt->visibleParents; + for ( i = 0; i < num_parents; i++ ) + { + apar = area_parents[i]; + for ( j = 0; j < apar->numVisibleParents; j++, psi++ ) + { + *psi = apar->visibleParents[j]; + } + } + + // parentLinks + rt->numParentLinks = parentLinkCount; + rt->parentLinks = (aas_rt_parent_link_t *) AAS_RT_GetClearedMemory( parentLinkCount * sizeof( aas_rt_parent_link_t ) ); + plink = rt->parentLinks; + for ( i = 0; i < childcount; i++ ) + { + chloc = area_childlocaldata[i]; + for ( oplink = chloc->parentlink; oplink; plink++, oplink = oplink->next ) + { + plink->childIndex = oplink->childindex; + plink->parent = oplink->parent; + } + } + + } + // ................................................ + + // write the newly created table + AAS_RT_WriteRouteTable(); + + + botimport.Print( PRT_MESSAGE, "Child Areas: %i\nTotal Parents: %i\nAverage VisAreas: %i\n", (int)childcount, num_parents, (int)( childcount / num_parents ) ); + botimport.Print( PRT_MESSAGE, "NoRoute Ratio: %i%%\n", (int)( ( 100.0 * noroutecount ) / ( 1.0 * childcount * childcount ) ) ); + + memoryend = memorycount; + + // clear allocated memory + +// causes crashes in route-caching +//#ifdef USE_ROUTECACHE +// AAS_FreeRoutingCaches(); +//#endif + + for ( i = 0; i < childcount; i++ ) + { + AAS_RT_FreeMemory( area_localinfos[i] ); +#ifdef CHECK_TRAVEL_TIMES + AAS_RT_FreeMemory( filteredroutetable[i] ); +#endif + } + + { + aas_parent_link_t *next, *trav; + + // kill the client areas + for ( i = 0; i < childcount; i++ ) + { + // kill the parent links + next = area_childlocaldata[i]->parentlink; + // TTimo gcc: suggests () around assignment used as truth value + while ( ( trav = next ) ) + { + next = next->next; + + AAS_RT_FreeMemory( trav->routeindexes ); + AAS_RT_FreeMemory( trav ); + + } + + AAS_RT_FreeMemory( area_childlocaldata[i]->localroutes ); + AAS_RT_FreeMemory( area_childlocaldata[i]->parentroutes ); + AAS_RT_FreeMemory( area_childlocaldata[i] ); + } + + // kill the parents + for ( i = 0; i < num_parents; i++ ) + { + AAS_RT_FreeMemory( area_parents[i]->children ); + AAS_RT_FreeMemory( area_parents[i]->visibleParents ); + AAS_RT_FreeMemory( area_parents[i] ); + } + } + + AAS_RT_FreeMemory( area_localinfos ); + AAS_RT_FreeMemory( area_childlocaldata ); + AAS_RT_FreeMemory( filtered_areas ); + AAS_RT_FreeMemory( rev_filtered_areas ); +#ifdef CHECK_TRAVEL_TIMES + AAS_RT_FreeMemory( filteredroutetable ); +#endif + + // check how much memory we've used, and intend to keep + AAS_RT_PrintMemoryUsage(); + + botimport.Print( PRT_MESSAGE, "Route-Table Permanent Memory Usage: %i\n", memorycount ); + botimport.Print( PRT_MESSAGE, "Route-Table Calculation Usage: %i\n", memoryend + cachememory ); + botimport.Print( PRT_MESSAGE, "---------------------------------\n" ); +} + +//=========================================================================== +// free permanent memory used by route-table system +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RT_ShutdownRouteTable( void ) { + if ( !aasworld->routetable ) { + return; + } + + // free the dynamic lists + AAS_RT_FreeMemory( aasworld->routetable->areaChildIndexes ); + AAS_RT_FreeMemory( aasworld->routetable->children ); + AAS_RT_FreeMemory( aasworld->routetable->parents ); + AAS_RT_FreeMemory( aasworld->routetable->parentChildren ); + AAS_RT_FreeMemory( aasworld->routetable->visibleParents ); +// AAS_RT_FreeMemory( aasworld->routetable->localRoutes ); +// AAS_RT_FreeMemory( aasworld->routetable->parentRoutes ); + AAS_RT_FreeMemory( aasworld->routetable->parentLinks ); +// AAS_RT_FreeMemory( aasworld->routetable->routeIndexes ); +// AAS_RT_FreeMemory( aasworld->routetable->parentTravelTimes ); + + // kill the table + AAS_RT_FreeMemory( aasworld->routetable ); + aasworld->routetable = NULL; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_rt_parent_link_t *AAS_RT_GetFirstParentLink( aas_rt_child_t *child ) { + return &aasworld->routetable->parentLinks[child->startParentLinks]; +} + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_rt_child_t *AAS_RT_GetChild( int areanum ) { + int i; + + i = (int)aasworld->routetable->areaChildIndexes[areanum] - 1; + + if ( i >= 0 ) { + return &aasworld->routetable->children[i]; + } else { + return NULL; + } +} + +//=========================================================================== +// returns a route between the areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum ); +aas_rt_route_t *AAS_RT_GetRoute( int srcnum, vec3_t origin, int destnum ) { + #define GETROUTE_NUMROUTES 64 + static aas_rt_route_t routes[GETROUTE_NUMROUTES]; // cycle through these, so we don't overlap + static int routeIndex = 0; + aas_rt_route_t *thisroute; + int reach, traveltime; + aas_rt_t *rt; + static int tfl = TFL_DEFAULT & ~( TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA ); //----(SA) modified since slime is no longer deadly +// static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); + + if ( !( rt = aasworld->routetable ) ) { // no route table present + return NULL; + } + + if ( disable_routetable ) { + return NULL; + } + + if ( ++routeIndex >= GETROUTE_NUMROUTES ) { + routeIndex = 0; + } + + thisroute = &routes[routeIndex]; + + if ( AAS_AreaRouteToGoalArea( srcnum, origin, destnum, tfl, &traveltime, &reach ) ) { + thisroute->reachable_index = reach; + thisroute->travel_time = traveltime; + return thisroute; + } else { + return NULL; + } +} + +//=========================================================================== +// draws the route-table from src to dest +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#include "../game/be_ai_goal.h" +int BotGetReachabilityToGoal( vec3_t origin, int areanum, int entnum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags ); + +void AAS_RT_ShowRoute( vec3_t srcpos, int srcnum, int destnum ) { +#ifdef DEBUG +#define MAX_RT_AVOID_REACH 1 + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_ShowAreaPolygons( srcnum, 1, qtrue ); + AAS_ShowAreaPolygons( destnum, 4, qtrue ); + { + static int lastgoalareanum, lastareanum; + static int avoidreach[MAX_RT_AVOID_REACH]; + static float avoidreachtimes[MAX_RT_AVOID_REACH]; + static int avoidreachtries[MAX_RT_AVOID_REACH]; + int reachnum; + bot_goal_t goal; + aas_reachability_t reach; + + goal.areanum = destnum; + VectorCopy( botlibglobals.goalorigin, goal.origin ); + reachnum = BotGetReachabilityToGoal( srcpos, srcnum, -1, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT | TFL_FUNCBOB, TFL_DEFAULT | TFL_FUNCBOB ); + AAS_ReachabilityFromNum( reachnum, &reach ); + AAS_ShowReachability( &reach ); + } +#endif +} + +/* +================= +AAS_RT_GetHidePos + + "src" is hiding ent, "dest" is the enemy +================= +*/ +int AAS_NearestHideArea( int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags ); +qboolean AAS_RT_GetHidePos( vec3_t srcpos, int srcnum, int srcarea, vec3_t destpos, int destnum, int destarea, vec3_t returnPos ) { + static int tfl = TFL_DEFAULT & ~( TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA ); //----(SA) modified since slime is no longer deadly +// static int tfl = TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA); + +#if 1 + // use MrE's breadth first method + int hideareanum; +// int pretime; + + // disabled this so grenade hiding works + //if (!srcarea || !destarea) + // return qfalse; + +// pretime = -Sys_MilliSeconds(); + + hideareanum = AAS_NearestHideArea( srcnum, srcpos, srcarea, destnum, destpos, destarea, tfl ); + if ( !hideareanum ) { +// botimport.Print(PRT_MESSAGE, "Breadth First HidePos FAILED: %i ms\n", pretime + Sys_MilliSeconds()); + return qfalse; + } + // we found a valid hiding area + VectorCopy( ( *aasworld ).areawaypoints[hideareanum], returnPos ); + +// botimport.Print(PRT_MESSAGE, "Breadth First HidePos: %i ms\n", pretime + Sys_MilliSeconds()); + + return qtrue; + +#else + // look around at random parent areas, if any of them have a center point + // that isn't visible from "destpos", then return it's position in returnPos + + int i, j, pathArea, dir; + unsigned short int destTravelTime; + aas_rt_parent_t *srcParent, *travParent, *destParent; + aas_rt_child_t *srcChild, *destChild, *travChild; + aas_rt_route_t *route; + vec3_t destVec; + float destTravelDist; + static float lastTime; + static int frameCount, maxPerFrame = 2; + int firstreach; + aas_reachability_t *reachability, *reach; + qboolean startVisible; + unsigned short int bestTravelTime, thisTravelTime, elapsedTravelTime; + #define MAX_HIDE_TRAVELTIME 1000 // 10 seconds is a very long way away + unsigned char destVisLookup[MAX_PARENTS]; + unsigned short int *destVisTrav; + + aas_rt_t *rt; + + const int MAX_CHECK_VISPARENTS = 100; + int visparents_count, total_parents_checked; + int thisParentIndex; + int pretime; + + if ( !( rt = aasworld->routetable ) ) { // no route table present + return qfalse; + } +/* + if (lastTime > (AAS_Time() - 0.1)) { + if (frameCount++ > maxPerFrame) { + return qfalse; + } + } else { + frameCount = 0; + lastTime = AAS_Time(); + } +*/ + pretime = -Sys_MilliSeconds(); + + // is the src area grounded? + if ( !( srcChild = AAS_RT_GetChild( srcarea ) ) ) { + return qfalse; + } + // does it have a parent? +// all valid areas have a parent +// if (!srcChild->numParentLinks) { +// return qfalse; +// } + // get the dest (enemy) area + if ( !( destChild = AAS_RT_GetChild( destarea ) ) ) { + return qfalse; + } + destParent = &rt->parents[ rt->parentLinks[destChild->startParentLinks].parent ]; + // + // populate the destVisAreas + memset( destVisLookup, 0, sizeof( destVisLookup ) ); + destVisTrav = rt->visibleParents + destParent->startVisibleParents; + for ( i = 0; i < destParent->numVisibleParents; i++, destVisTrav++ ) { + destVisLookup[*destVisTrav] = 1; + } + // + // use the first parent to source the vis areas from + srcParent = &rt->parents[ rt->parentLinks[srcChild->startParentLinks].parent ]; + // + // set the destTravelTime + if ( route = AAS_RT_GetRoute( srcarea, srcpos, destarea ) ) { + destTravelTime = route->travel_time; + } else { + destTravelTime = 0; + } + bestTravelTime = MAX_HIDE_TRAVELTIME; // ignore any routes longer than 10 seconds away + // set the destVec + VectorSubtract( destpos, srcpos, destVec ); + destTravelDist = VectorNormalize( destVec ); + // + // randomize the direction we traverse the list, so the hiding spot isn't always the same + if ( rand() % 2 ) { + dir = 1; // forward + } else { + dir = -1; // reverse + } + // randomize the starting area + if ( srcParent->numVisibleParents ) { + i = rand() % srcParent->numVisibleParents; + } else { // prevent divide by zero + i = 0; + } + // + // setup misc stuff + reachability = ( *aasworld ).reachability; + startVisible = botimport.AICast_VisibleFromPos( destpos, destnum, srcpos, srcnum, qfalse ); + // + // set the firstreach to prevent having to do an array and pointer lookup for each destination + firstreach = ( *aasworld ).areasettings[srcarea].firstreachablearea; + // + // just check random parent areas, traversing the route until we find an area that can't be seen from the dest area + for ( visparents_count = 0, total_parents_checked = 0; visparents_count < MAX_CHECK_VISPARENTS && total_parents_checked < rt->numParents; total_parents_checked++ ) { + thisParentIndex = rand() % rt->numParents; + travParent = &rt->parents[ thisParentIndex ]; + // + // never go to the enemy's areas + if ( travParent->areanum == destarea ) { + continue; + } + // + // if it's visible from dest, ignore it + if ( destVisLookup[thisParentIndex] ) { + continue; + } + // + visparents_count++; + // they might be visible, check to see if the path to the area, takes us towards the + // enemy we are trying to hide from + { + qboolean invalidRoute; + vec3_t curPos, lastVec; + #define GETHIDE_MAX_CHECK_PATHS 15 + // + invalidRoute = qfalse; + // initialize the pathArea + pathArea = srcarea; + VectorCopy( srcpos, curPos ); + // now evaluate the path + for ( j = 0; j < GETHIDE_MAX_CHECK_PATHS; j++ ) { + // get the reachability to the travParent + if ( !( route = AAS_RT_GetRoute( pathArea, curPos, travParent->areanum ) ) ) { + // we can't get to the travParent, so don't bother checking it + invalidRoute = qtrue; + break; + } + // set the pathArea + reach = &reachability[route->reachable_index]; + // how far have we travelled so far? + elapsedTravelTime = AAS_AreaTravelTimeToGoalArea( pathArea, curPos, reach->areanum, tfl ); + // add the travel to the center of the area + elapsedTravelTime += AAS_AreaTravelTime( reach->areanum, reach->end, ( *aasworld ).areas[reach->areanum].center ); + // have we gone too far already? + if ( elapsedTravelTime > bestTravelTime ) { + invalidRoute = qtrue; + break; + } else { + thisTravelTime = route->travel_time; + } + // + // if this travel would have us do something wierd + if ( ( reach->traveltype == TRAVEL_WALKOFFLEDGE ) && ( reach->traveltime > 500 ) ) { + invalidRoute = qtrue; + break; + } + // + pathArea = reach->areanum; + VectorCopy( reach->end, curPos ); + // + // if this moves us into the enemies area, skip it + if ( pathArea == destarea ) { + invalidRoute = qtrue; + break; + } + // if we are very close, don't get any closer under any circumstances + { + vec3_t vec; + float dist; + // + VectorSubtract( destpos, reachability[firstreach + route->reachable_index].end, vec ); + dist = VectorNormalize( vec ); + // + if ( destTravelTime < 400 ) { + if ( dist < destTravelDist ) { + invalidRoute = qtrue; + break; + } + if ( DotProduct( destVec, vec ) < 0.2 ) { + invalidRoute = qtrue; + break; + } + } else { + if ( dist < destTravelDist * 0.7 ) { + invalidRoute = qtrue; + break; + } + } + // + // check the directions to make sure we're not trying to run through them + if ( j > 0 ) { + if ( DotProduct( vec, lastVec ) < 0.2 ) { + invalidRoute = qtrue; + break; + } + } else if ( DotProduct( destVec, vec ) < 0.2 ) { + invalidRoute = qtrue; + break; + } + // + VectorCopy( vec, lastVec ); + } + // + // if this area isn't in the visible list for the enemy's area, it's a good hiding spot + if ( !( travChild = AAS_RT_GetChild( pathArea ) ) ) { + invalidRoute = qtrue; + break; + } + if ( !destVisLookup[rt->parentLinks[travChild->startParentLinks].parent] ) { + // success ? + if ( !botimport.AICast_VisibleFromPos( destpos, destnum, ( *aasworld ).areas[pathArea].center, srcnum, qfalse ) ) { + // SUCESS !! + travParent = &rt->parents[rt->parentLinks[travChild->startParentLinks].parent]; + break; + } + } else { + // if we weren't visible when starting, make sure we don't move into their view + if ( !startVisible ) { //botimport.AICast_VisibleFromPos( destpos, destnum, reachability[firstreach + route->reachable_index].end, srcnum, qfalse )) { + invalidRoute = qtrue; + break; + } + } + // + // if this is the travParent, then stop checking + if ( pathArea == travParent->areanum ) { + invalidRoute = qtrue; // we didn't find a hiding spot + break; + } + } // end for areas in route + // + // if the route is invalid, skip this travParent + if ( invalidRoute ) { + continue; + } + } + // + // now last of all, check that this area is a safe hiding spot +// if (botimport.AICast_VisibleFromPos( destpos, destnum, (*aasworld).areas[travParent->areanum].center, srcnum, qfalse )) { +// continue; +// } + // + // we've found a good hiding spot, so use it + VectorCopy( ( *aasworld ).areas[travParent->areanum].center, returnPos ); + bestTravelTime = elapsedTravelTime; + // + if ( thisTravelTime < 300 ) { + botimport.Print( PRT_MESSAGE, "Fuzzy RT HidePos: %i ms\n", pretime + Sys_MilliSeconds() ); + return qtrue; + } + } + // + // did we find something? + if ( bestTravelTime < MAX_HIDE_TRAVELTIME ) { + botimport.Print( PRT_MESSAGE, "Fuzzy RT HidePos: %i ms\n", pretime + Sys_MilliSeconds() ); + return qtrue; + } + // + // couldn't find anything + botimport.Print( PRT_MESSAGE, "Fuzzy RT HidePos FAILED: %i ms\n", pretime + Sys_MilliSeconds() ); + return qfalse; +#endif +} + +/* +================= +AAS_RT_GetReachabilityIndex +================= +*/ +int AAS_RT_GetReachabilityIndex( int areanum, int reachIndex ) { +// return (*aasworld).areasettings[areanum].firstreachablearea + reachIndex; + return reachIndex; +} diff --git a/src/botlib/be_aas_routetable.h b/src/botlib/be_aas_routetable.h new file mode 100644 index 0000000..0210962 --- /dev/null +++ b/src/botlib/be_aas_routetable.h @@ -0,0 +1,171 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: be_aas_routetable.h +// Function: Area Awareness System, Route-table defines +// Programmer: Ridah +// Tab Size: 3 +//=========================================================================== + +#ifndef RT_DEFINED + +#define RT_DEFINED + +#define RTBID ( ( 'B' << 24 ) + ( 'T' << 16 ) + ( 'R' << 8 ) + 'X' ) +#define RTBVERSION 17 + +#define RTB_BADTRAVELFLAGS ( TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA ) //----(SA) modified since slime is no longer deadly +//#define RTB_BADTRAVELFLAGS (TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA) + +#define MAX_VISIBLE_AREAS 1024 // going over this limit will result in excessive memory usage, try and keep RANGE low enough so this limit won't be reached +#define MAX_LOCALTRAVELTIME 60 // use this to tweak memory usage (reduces parent count, increases local count (and cpu usage) - find a balance) +#define MAX_PARENTS 8192 + +extern int disable_routetable; + +//.................................................................... +// Permanent structures (in order of highest to lowest count) +typedef struct +{ + unsigned short int reachable_index; // reachability index (from this area's first reachability link in the world) to head for to get to the destination + unsigned short int travel_time; // travel time (!) +} aas_rt_route_t; + +typedef struct +{ + unsigned short int parent; // parent we belong to + unsigned short int childIndex; // our index in the parent's list of children +// unsigned short int numRouteIndexes; +// int startRouteIndexes; +} aas_rt_parent_link_t; + +typedef struct +{ + unsigned short int areanum; +// int numLocalRoutes; +// int startLocalRoutes; +// int numParentRoutes; +// int startParentRoutes; + int numParentLinks; + int startParentLinks; +} aas_rt_child_t; + +typedef struct +{ + unsigned short int areanum; // out area number in the global list + int numParentChildren; + int startParentChildren; + int numVisibleParents; + int startVisibleParents; // list of other parents that we can see (used for fast hide/retreat checks) +// int startParentTravelTimes; +} aas_rt_parent_t; + +// this is what each aasworld attaches itself to +typedef struct +{ + unsigned short int *areaChildIndexes; // each aas area that is part of the Route-Table has a pointer here to their position in the list of children + + int numChildren; + aas_rt_child_t *children; + + int numParents; + aas_rt_parent_t *parents; + + int numParentChildren; + unsigned short int *parentChildren; + + int numVisibleParents; + unsigned short int *visibleParents; + +// int numLocalRoutes; +// aas_rt_route_t *localRoutes; // the list of routes to all other local areas + +// int numParentRoutes; +// unsigned char *parentRoutes; // reachability to each other parent, as an offset from our first reachability + + int numParentLinks; + aas_rt_parent_link_t *parentLinks; // links from each child to the parent's it belongs to + +// int numParentTravelTimes; +// unsigned short int *parentTravelTimes; // travel times between all parent areas + +// int numRouteIndexes; +// unsigned short int *routeIndexes; // each parentLink has a list within here, which + // contains the local indexes of each child that + // belongs to the parent, within the source child's + // localroutes +} aas_rt_t; + +//.................................................................... +// Temp structures used only during route-table contruction +typedef struct +{ + unsigned short int numvisible; // number of areas that are visible and within range + unsigned short int + visible[MAX_VISIBLE_AREAS]; // list of area indexes of visible and within range areas +} aas_area_buildlocalinfo_t; + +typedef struct aas_parent_link_s +{ + unsigned short int parent; // parent we belong to + unsigned short int childindex; // our index in the parent's list of children + unsigned short int *routeindexes; // for this parent link, list the children that fall under that parent, and their associated indexes in our localroutes table + struct aas_parent_link_s *next; +} aas_parent_link_t; + +typedef struct +{ + unsigned short int areanum; + unsigned short int numlocal; + aas_parent_link_t *parentlink; // linked list of parents that we belong to + aas_rt_route_t *localroutes; // the list of routes to all other local areas + aas_rt_route_t *parentroutes; // the list of routes to all other parent areas +} aas_area_childlocaldata_t; + +typedef struct +{ + unsigned short int areanum; // out area number in the global list + unsigned short int numchildren; + unsigned short int *children; + unsigned short int numVisibleParents; + unsigned short int *visibleParents; // list of other parents that we can see (used for fast hide/retreat checks) +} aas_area_parent_t; + +#endif // RT_DEFINED + +//.................................................................... + +void AAS_RT_BuildRouteTable( void ); +void AAS_RT_ShowRoute( vec3_t srcpos, int srcnum, int destnum ); +aas_rt_route_t *AAS_RT_GetRoute( int srcnum, vec3_t origin, int destnum ); +void AAS_RT_ShutdownRouteTable( void ); +qboolean AAS_RT_GetHidePos( vec3_t srcpos, int srcnum, int srcarea, vec3_t destpos, int destnum, int destarea, vec3_t returnPos ); +int AAS_RT_GetReachabilityIndex( int areanum, int reachIndex ); + diff --git a/src/botlib/be_aas_sample.c b/src/botlib/be_aas_sample.c new file mode 100644 index 0000000..1b38a25 --- /dev/null +++ b/src/botlib/be_aas_sample.c @@ -0,0 +1,1274 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_sample.c + * + * desc: AAS environment sampling + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +//#define AAS_SAMPLE_DEBUG + +#define BBOX_NORMAL_EPSILON 0.001 + +#define ON_EPSILON 0 //0.0005 + +#define TRACEPLANE_EPSILON 0.125 + +typedef struct aas_tracestack_s +{ + vec3_t start; //start point of the piece of line to trace + vec3_t end; //end point of the piece of line to trace + int planenum; //last plane used as splitter + int nodenum; //node found after splitting with planenum +} aas_tracestack_t; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PresenceTypeBoundingBox( int presencetype, vec3_t mins, vec3_t maxs ) { + int index; + //bounding box size for each presence type + vec3_t boxmins[3] = {{0, 0, 0}, {-15, -15, -24}, {-15, -15, -24}}; + vec3_t boxmaxs[3] = {{0, 0, 0}, { 15, 15, 32}, { 15, 15, 8}}; + + if ( presencetype == PRESENCE_NORMAL ) { + index = 1; + } else if ( presencetype == PRESENCE_CROUCH ) { + index = 2; + } else + { + botimport.Print( PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n" ); + index = 2; + } //end if + VectorCopy( boxmins[index], mins ); + VectorCopy( boxmaxs[index], maxs ); +} //end of the function AAS_PresenceTypeBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkHeap( void ) { + int i, max_aaslinks; + + max_aaslinks = ( *aasworld ).linkheapsize; + //if there's no link heap present + if ( !( *aasworld ).linkheap ) { + max_aaslinks = (int) 4096; //LibVarValue("max_aaslinks", "4096"); + if ( max_aaslinks < 0 ) { + max_aaslinks = 0; + } + ( *aasworld ).linkheapsize = max_aaslinks; + ( *aasworld ).linkheap = (aas_link_t *) GetHunkMemory( max_aaslinks * sizeof( aas_link_t ) ); + } //end if + //link the links on the heap + ( *aasworld ).linkheap[0].prev_ent = NULL; + ( *aasworld ).linkheap[0].next_ent = &( *aasworld ).linkheap[1]; + for ( i = 1; i < max_aaslinks - 1; i++ ) + { + ( *aasworld ).linkheap[i].prev_ent = &( *aasworld ).linkheap[i - 1]; + ( *aasworld ).linkheap[i].next_ent = &( *aasworld ).linkheap[i + 1]; + } //end for + ( *aasworld ).linkheap[max_aaslinks - 1].prev_ent = &( *aasworld ).linkheap[max_aaslinks - 2]; + ( *aasworld ).linkheap[max_aaslinks - 1].next_ent = NULL; + //pointer to the first free link + ( *aasworld ).freelinks = &( *aasworld ).linkheap[0]; +} //end of the function AAS_InitAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkHeap( void ) { + if ( ( *aasworld ).linkheap ) { + FreeMemory( ( *aasworld ).linkheap ); + } + ( *aasworld ).linkheap = NULL; + ( *aasworld ).linkheapsize = 0; +} //end of the function AAS_FreeAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_AllocAASLink( void ) { + aas_link_t *link; + + link = ( *aasworld ).freelinks; + if ( !link ) { + botimport.Print( PRT_FATAL, "empty aas link heap\n" ); + return NULL; + } //end if + if ( ( *aasworld ).freelinks ) { + ( *aasworld ).freelinks = ( *aasworld ).freelinks->next_ent; + } + if ( ( *aasworld ).freelinks ) { + ( *aasworld ).freelinks->prev_ent = NULL; + } + return link; +} //end of the function AAS_AllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DeAllocAASLink( aas_link_t *link ) { + if ( ( *aasworld ).freelinks ) { + ( *aasworld ).freelinks->prev_ent = link; + } + link->prev_ent = NULL; + link->next_ent = ( *aasworld ).freelinks; + link->prev_area = NULL; + link->next_area = NULL; + ( *aasworld ).freelinks = link; +} //end of the function AAS_DeAllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkedEntities( void ) { + if ( !( *aasworld ).loaded ) { + return; + } + if ( ( *aasworld ).arealinkedentities ) { + FreeMemory( ( *aasworld ).arealinkedentities ); + } + ( *aasworld ).arealinkedentities = (aas_link_t **) GetClearedHunkMemory( + ( *aasworld ).numareas * sizeof( aas_link_t * ) ); +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkedEntities( void ) { + if ( ( *aasworld ).arealinkedentities ) { + FreeMemory( ( *aasworld ).arealinkedentities ); + } + ( *aasworld ).arealinkedentities = NULL; +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// returns the AAS area the point is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointAreaNum( vec3_t point ) { + int nodenum; + vec_t dist; + aas_node_t *node; + aas_plane_t *plane; + + if ( !( *aasworld ).loaded ) { + botimport.Print( PRT_ERROR, "AAS_PointAreaNum: aas not loaded\n" ); + return 0; + } //end if + + //start with node 1 because node zero is a dummy used for solid leafs + nodenum = 1; + while ( nodenum > 0 ) + { +// botimport.Print(PRT_MESSAGE, "[%d]", nodenum); +#ifdef AAS_SAMPLE_DEBUG + if ( nodenum >= ( *aasworld ).numnodes ) { + botimport.Print( PRT_ERROR, "nodenum = %d >= (*aasworld).numnodes = %d\n", nodenum, ( *aasworld ).numnodes ); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + node = &( *aasworld ).nodes[nodenum]; +#ifdef AAS_SAMPLE_DEBUG + if ( node->planenum < 0 || node->planenum >= ( *aasworld ).numplanes ) { + botimport.Print( PRT_ERROR, "node->planenum = %d >= (*aasworld).numplanes = %d\n", node->planenum, ( *aasworld ).numplanes ); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + plane = &( *aasworld ).planes[node->planenum]; + dist = DotProduct( point, plane->normal ) - plane->dist; + if ( dist > 0 ) { + nodenum = node->children[0]; + } else { nodenum = node->children[1];} + } //end while + if ( !nodenum ) { +#ifdef AAS_SAMPLE_DEBUG + botimport.Print( PRT_MESSAGE, "in solid\n" ); +#endif //AAS_SAMPLE_DEBUG + return 0; + } //end if + return -nodenum; +} //end of the function AAS_PointAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCluster( int areanum ) { + if ( areanum <= 0 || areanum >= ( *aasworld ).numareas ) { + botimport.Print( PRT_ERROR, "AAS_AreaCluster: invalid area number\n" ); + return 0; + } //end if + return ( *aasworld ).areasettings[areanum].cluster; +} //end of the function AAS_AreaCluster +//=========================================================================== +// returns the presence types of the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaPresenceType( int areanum ) { + if ( !( *aasworld ).loaded ) { + return 0; + } + if ( areanum <= 0 || areanum >= ( *aasworld ).numareas ) { + botimport.Print( PRT_ERROR, "AAS_AreaPresenceType: invalid area number\n" ); + return 0; + } //end if + return ( *aasworld ).areasettings[areanum].presencetype; +} //end of the function AAS_AreaPresenceType +//=========================================================================== +// returns the presence type at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointPresenceType( vec3_t point ) { + int areanum; + + if ( !( *aasworld ).loaded ) { + return 0; + } + + areanum = AAS_PointAreaNum( point ); + if ( !areanum ) { + return PRESENCE_NONE; + } + return ( *aasworld ).areasettings[areanum].presencetype; +} //end of the function AAS_PointPresenceType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_AreaEntityCollision( int areanum, vec3_t start, vec3_t end, + int presencetype, int passent, aas_trace_t *trace ) { + int collision; + vec3_t boxmins, boxmaxs; + aas_link_t *link; + bsp_trace_t bsptrace; + + AAS_PresenceTypeBoundingBox( presencetype, boxmins, boxmaxs ); + + memset( &bsptrace, 0, sizeof( bsp_trace_t ) ); //make compiler happy + //assume no collision + bsptrace.fraction = 1; + collision = qfalse; + for ( link = ( *aasworld ).arealinkedentities[areanum]; link; link = link->next_ent ) + { + //ignore the pass entity + if ( link->entnum == passent ) { + continue; + } + // + if ( AAS_EntityCollision( link->entnum, start, boxmins, boxmaxs, end, + CONTENTS_SOLID | CONTENTS_PLAYERCLIP, &bsptrace ) ) { + collision = qtrue; + } //end if + } //end for + if ( collision ) { + trace->startsolid = bsptrace.startsolid; + trace->ent = bsptrace.ent; + VectorCopy( bsptrace.endpos, trace->endpos ); + trace->area = 0; + trace->planenum = 0; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_AreaEntityCollision +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_trace_t AAS_TraceClientBBox( vec3_t start, vec3_t end, int presencetype, + int passent ) { + int side, nodenum, tmpplanenum; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid, v1, v2; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_trace_t trace; + + //clear the trace structure + memset( &trace, 0, sizeof( aas_trace_t ) ); + + if ( !( *aasworld ).loaded ) { + return trace; + } + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy( start, tstack_p->start ); + VectorCopy( end, tstack_p->end ); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while ( 1 ) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if ( tstack_p < tracestack ) { + tstack_p++; + //nothing was hit + trace.startsolid = qfalse; + trace.fraction = 1.0; + //endpos is the end of the line + VectorCopy( end, trace.endpos ); + //nothing hit + trace.ent = 0; + trace.area = 0; + trace.planenum = 0; + return trace; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if ( nodenum < 0 ) { +#ifdef AAS_SAMPLE_DEBUG + if ( -nodenum > ( *aasworld ).numareasettings ) { + botimport.Print( PRT_ERROR, "AAS_TraceBoundingBox: -nodenum out of range\n" ); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + //if can't enter the area because it hasn't got the right presence type + if ( !( ( *aasworld ).areasettings[-nodenum].presencetype & presencetype ) ) { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if ( tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2] ) { + trace.startsolid = qtrue; + trace.fraction = 0.0; + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract( end, start, v1 ); + VectorSubtract( tstack_p->start, start, v2 ); + trace.fraction = VectorLength( v2 ) / VectorNormalize( v1 ); + VectorMA( tstack_p->start, -0.125, v1, tstack_p->start ); + } //end else + VectorCopy( tstack_p->start, trace.endpos ); + trace.ent = 0; + trace.area = -nodenum; +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &( *aasworld ).planes[trace.planenum]; + if ( DotProduct( v1, plane->normal ) > 0 ) { + trace.planenum ^= 1; + } + return trace; + } //end if + else + { + if ( passent >= 0 ) { + if ( AAS_AreaEntityCollision( -nodenum, tstack_p->start, + tstack_p->end, presencetype, passent, + &trace ) ) { + if ( !trace.startsolid ) { + VectorSubtract( end, start, v1 ); + VectorSubtract( trace.endpos, start, v2 ); + trace.fraction = VectorLength( v2 ) / VectorLength( v1 ); + } //end if + return trace; + } //end if + } //end if + } //end else + trace.lastarea = -nodenum; + continue; + } //end if + //if it is a solid leaf + if ( !nodenum ) { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if ( tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2] ) { + trace.startsolid = qtrue; + trace.fraction = 0.0; + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract( end, start, v1 ); + VectorSubtract( tstack_p->start, start, v2 ); + trace.fraction = VectorLength( v2 ) / VectorNormalize( v1 ); + VectorMA( tstack_p->start, -0.125, v1, tstack_p->start ); + } //end else + VectorCopy( tstack_p->start, trace.endpos ); + trace.ent = 0; + trace.area = 0; //hit solid leaf +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &( *aasworld ).planes[trace.planenum]; + if ( DotProduct( v1, plane->normal ) > 0 ) { + trace.planenum ^= 1; + } + return trace; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if ( nodenum > ( *aasworld ).numnodes ) { + botimport.Print( PRT_ERROR, "AAS_TraceBoundingBox: nodenum out of range\n" ); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &( *aasworld ).nodes[nodenum]; + //start point of current line to test against node + VectorCopy( tstack_p->start, cur_start ); + //end point of the current line to test against node + VectorCopy( tstack_p->end, cur_end ); + //the current node plane + plane = &( *aasworld ).planes[aasnode->planenum]; + + switch ( plane->type ) + {/*FIXME: wtf doesn't this work? obviously the axial node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ + default: //gee it's not an axial plane + { + front = DotProduct( cur_start, plane->normal ) - plane->dist; + back = DotProduct( cur_end, plane->normal ) - plane->dist; + break; + } //end default + } //end switch + + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if ( front < 0 ) { + frac = ( front + TRACEPLANE_EPSILON ) / ( front - back ); + } else { frac = ( front - TRACEPLANE_EPSILON ) / ( front - back );} + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if ( ( front >= -ON_EPSILON && back >= -ON_EPSILON ) ) { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if ( tstack_p >= &tracestack[127] ) { + botimport.Print( PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n" ); + return trace; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if ( ( front < ON_EPSILON && back < ON_EPSILON ) ) { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if ( tstack_p >= &tracestack[127] ) { + botimport.Print( PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n" ); + return trace; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + // + if ( frac < 0 ) { + frac = 0.001; //0 + } else if ( frac > 1 ) { + frac = 0.999; //1 + } + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + ( cur_end[0] - cur_start[0] ) * frac; + cur_mid[1] = cur_start[1] + ( cur_end[1] - cur_start[1] ) * frac; + cur_mid[2] = cur_start[2] + ( cur_end[2] - cur_start[2] ) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy( cur_mid, tstack_p->start ); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if ( tstack_p >= &tracestack[127] ) { + botimport.Print( PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n" ); + return trace; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy( cur_start, tstack_p->start ); + VectorCopy( cur_mid, tstack_p->end ); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if ( tstack_p >= &tracestack[127] ) { + botimport.Print( PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n" ); + return trace; + } //end if + } //end else + } //end while +// return trace; +} //end of the function AAS_TraceClientBBox +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TraceAreas( vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas ) { + int side, nodenum, tmpplanenum; + int numareas; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + + numareas = 0; + areas[0] = 0; + if ( !( *aasworld ).loaded ) { + return numareas; + } + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy( start, tstack_p->start ); + VectorCopy( end, tstack_p->end ); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while ( 1 ) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if ( tstack_p < tracestack ) { + return numareas; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if ( nodenum < 0 ) { +#ifdef AAS_SAMPLE_DEBUG + if ( -nodenum > ( *aasworld ).numareasettings ) { + botimport.Print( PRT_ERROR, "AAS_TraceAreas: -nodenum = %d out of range\n", -nodenum ); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + areas[numareas] = -nodenum; + if ( points ) { + VectorCopy( tstack_p->start, points[numareas] ); + } + numareas++; + if ( numareas >= maxareas ) { + return numareas; + } + continue; + } //end if + //if it is a solid leaf + if ( !nodenum ) { + continue; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if ( nodenum > ( *aasworld ).numnodes ) { + botimport.Print( PRT_ERROR, "AAS_TraceAreas: nodenum out of range\n" ); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &( *aasworld ).nodes[nodenum]; + //start point of current line to test against node + VectorCopy( tstack_p->start, cur_start ); + //end point of the current line to test against node + VectorCopy( tstack_p->end, cur_end ); + //the current node plane + plane = &( *aasworld ).planes[aasnode->planenum]; + + switch ( plane->type ) + {/*FIXME: wtf doesn't this work? obviously the node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ + default: //gee it's not an axial plane + { + front = DotProduct( cur_start, plane->normal ) - plane->dist; + back = DotProduct( cur_end, plane->normal ) - plane->dist; + break; + } //end default + } //end switch + + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if ( front > 0 && back > 0 ) { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if ( tstack_p >= &tracestack[127] ) { + botimport.Print( PRT_ERROR, "AAS_TraceAreas: stack overflow\n" ); + return numareas; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if ( front <= 0 && back <= 0 ) { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if ( tstack_p >= &tracestack[127] ) { + botimport.Print( PRT_ERROR, "AAS_TraceAreas: stack overflow\n" ); + return numareas; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if ( front < 0 ) { + frac = ( front ) / ( front - back ); + } else { frac = ( front ) / ( front - back );} + if ( frac < 0 ) { + frac = 0; + } else if ( frac > 1 ) { + frac = 1; + } + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + ( cur_end[0] - cur_start[0] ) * frac; + cur_mid[1] = cur_start[1] + ( cur_end[1] - cur_start[1] ) * frac; + cur_mid[2] = cur_start[2] + ( cur_end[2] - cur_start[2] ) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy( cur_mid, tstack_p->start ); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if ( tstack_p >= &tracestack[127] ) { + botimport.Print( PRT_ERROR, "AAS_TraceAreas: stack overflow\n" ); + return numareas; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy( cur_start, tstack_p->start ); + VectorCopy( cur_mid, tstack_p->end ); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if ( tstack_p >= &tracestack[127] ) { + botimport.Print( PRT_ERROR, "AAS_TraceAreas: stack overflow\n" ); + return numareas; + } //end if + } //end else + } //end while +// return numareas; +} //end of the function AAS_TraceAreas +//=========================================================================== +// a simple cross product +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// void AAS_OrthogonalToVectors(vec3_t v1, vec3_t v2, vec3_t res) +#define AAS_OrthogonalToVectors( v1, v2, res ) \ + ( res )[0] = ( ( v1 )[1] * ( v2 )[2] ) - ( ( v1 )[2] * ( v2 )[1] ); \ + ( res )[1] = ( ( v1 )[2] * ( v2 )[0] ) - ( ( v1 )[0] * ( v2 )[2] ); \ + ( res )[2] = ( ( v1 )[0] * ( v2 )[1] ) - ( ( v1 )[1] * ( v2 )[0] ); +//=========================================================================== +// tests if the given point is within the face boundaries +// +// Parameter: face : face to test if the point is in it +// pnormal : normal of the plane to use for the face +// point : point to test if inside face boundaries +// Returns: qtrue if the point is within the face boundaries +// Changes Globals: - +//=========================================================================== +qboolean AAS_InsideFace( aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon ) { + int i, firstvertex, edgenum; + vec3_t v0; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; +#ifdef AAS_SAMPLE_DEBUG + int lastvertex = 0; +#endif //AAS_SAMPLE_DEBUG + + if ( !( *aasworld ).loaded ) { + return qfalse; + } + + for ( i = 0; i < face->numedges; i++ ) + { + edgenum = ( *aasworld ).edgeindex[face->firstedge + i]; + edge = &( *aasworld ).edges[abs( edgenum )]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + VectorCopy( ( *aasworld ).vertexes[edge->v[firstvertex]], v0 ); + //edge vector + VectorSubtract( ( *aasworld ).vertexes[edge->v[!firstvertex]], v0, edgevec ); + // +#ifdef AAS_SAMPLE_DEBUG + if ( lastvertex && lastvertex != edge->v[firstvertex] ) { + botimport.Print( PRT_MESSAGE, "winding not counter clockwise\n" ); + } //end if + lastvertex = edge->v[!firstvertex]; +#endif //AAS_SAMPLE_DEBUG + //vector from first edge point to point possible in face + VectorSubtract( point, v0, pointvec ); + //get a vector pointing inside the face orthogonal to both the + //edge vector and the normal vector of the plane the face is in + //this vector defines a plane through the origin (first vertex of + //edge) and through both the edge vector and the normal vector + //of the plane + AAS_OrthogonalToVectors( edgevec, pnormal, sepnormal ); + //check on wich side of the above plane the point is + //this is done by checking the sign of the dot product of the + //vector orthogonal vector from above and the vector from the + //origin (first vertex of edge) to the point + //if the dotproduct is smaller than zero the point is outside the face + if ( DotProduct( pointvec, sepnormal ) < -epsilon ) { + return qfalse; + } + } //end for + return qtrue; +} //end of the function AAS_InsideFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_PointInsideFace( int facenum, vec3_t point, float epsilon ) { + int i, firstvertex, edgenum; + vec_t *v1, *v2; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; + aas_plane_t *plane; + aas_face_t *face; + + if ( !( *aasworld ).loaded ) { + return qfalse; + } + + face = &( *aasworld ).faces[facenum]; + plane = &( *aasworld ).planes[face->planenum]; + // + for ( i = 0; i < face->numedges; i++ ) + { + edgenum = ( *aasworld ).edgeindex[face->firstedge + i]; + edge = &( *aasworld ).edges[abs( edgenum )]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + v1 = ( *aasworld ).vertexes[edge->v[firstvertex]]; + v2 = ( *aasworld ).vertexes[edge->v[!firstvertex]]; + //edge vector + VectorSubtract( v2, v1, edgevec ); + //vector from first edge point to point possible in face + VectorSubtract( point, v1, pointvec ); + // + CrossProduct( edgevec, plane->normal, sepnormal ); + // + if ( DotProduct( pointvec, sepnormal ) < -epsilon ) { + return qfalse; + } + } //end for + return qtrue; +} //end of the function AAS_PointInsideFace +//=========================================================================== +// returns the ground face the given point is above in the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_AreaGroundFace( int areanum, vec3_t point ) { + int i, facenum; + vec3_t up = {0, 0, 1}; + vec3_t normal; + aas_area_t *area; + aas_face_t *face; + + if ( !( *aasworld ).loaded ) { + return NULL; + } + + area = &( *aasworld ).areas[areanum]; + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = ( *aasworld ).faceindex[area->firstface + i]; + face = &( *aasworld ).faces[abs( facenum )]; + //if this is a ground face + if ( face->faceflags & FACE_GROUND ) { + //get the up or down normal + if ( ( *aasworld ).planes[face->planenum].normal[2] < 0 ) { + VectorNegate( up, normal ); + } else { VectorCopy( up, normal );} + //check if the point is in the face + if ( AAS_InsideFace( face, normal, point, 0.01 ) ) { + return face; + } + } //end if + } //end for + return NULL; +} //end of the function AAS_AreaGroundFace +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FacePlane( int facenum, vec3_t normal, float *dist ) { + aas_plane_t *plane; + + plane = &( *aasworld ).planes[( *aasworld ).faces[facenum].planenum]; + VectorCopy( plane->normal, normal ); + *dist = plane->dist; +} //end of the function AAS_FacePlane +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_TraceEndFace( aas_trace_t *trace ) { + int i, facenum; + aas_area_t *area; + aas_face_t *face, *firstface = NULL; + + if ( !( *aasworld ).loaded ) { + return NULL; + } + + //if started in solid no face was hit + if ( trace->startsolid ) { + return NULL; + } + //trace->lastarea is the last area the trace was in + area = &( *aasworld ).areas[trace->lastarea]; + //check which face the trace.endpos was in + for ( i = 0; i < area->numfaces; i++ ) + { + facenum = ( *aasworld ).faceindex[area->firstface + i]; + face = &( *aasworld ).faces[abs( facenum )]; + //if the face is in the same plane as the trace end point + if ( ( face->planenum & ~1 ) == ( trace->planenum & ~1 ) ) { + //firstface is used for optimization, if theres only one + //face in the plane then it has to be the good one + //if there are more faces in the same plane then always + //check the one with the fewest edges first +/* if (firstface) + { + if (firstface->numedges < face->numedges) + { + if (AAS_InsideFace(firstface, + (*aasworld).planes[face->planenum].normal, trace->endpos)) + { + return firstface; + } //end if + firstface = face; + } //end if + else + { + if (AAS_InsideFace(face, + (*aasworld).planes[face->planenum].normal, trace->endpos)) + { + return face; + } //end if + } //end else + } //end if + else + { + firstface = face; + } //end else*/ + if ( AAS_InsideFace( face, + ( *aasworld ).planes[face->planenum].normal, trace->endpos, 0.01 ) ) { + return face; + } + } //end if + } //end for + return firstface; +} //end of the function AAS_TraceEndFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxOnPlaneSide2( vec3_t absmins, vec3_t absmaxs, aas_plane_t *p ) { + int i, sides; + float dist1, dist2; + vec3_t corners[2]; + + for ( i = 0; i < 3; i++ ) + { + if ( p->normal[i] < 0 ) { + corners[0][i] = absmins[i]; + corners[1][i] = absmaxs[i]; + } //end if + else + { + corners[1][i] = absmins[i]; + corners[0][i] = absmaxs[i]; + } //end else + } //end for + dist1 = DotProduct( p->normal, corners[0] ) - p->dist; + dist2 = DotProduct( p->normal, corners[1] ) - p->dist; + sides = 0; + if ( dist1 >= 0 ) { + sides = 1; + } + if ( dist2 < 0 ) { + sides |= 2; + } + + return sides; +} //end of the function AAS_BoxOnPlaneSide2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//int AAS_BoxOnPlaneSide(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +#define AAS_BoxOnPlaneSide( absmins, absmaxs, p ) ( \ + ( ( p )->type < 3 ) ? \ + ( \ + ( ( p )->dist <= ( absmins )[( p )->type] ) ? \ + ( \ + 1 \ + ) \ + : \ + ( \ + ( ( p )->dist >= ( absmaxs )[( p )->type] ) ? \ + ( \ + 2 \ + ) \ + : \ + ( \ + 3 \ + ) \ + ) \ + ) \ + : \ + ( \ + AAS_BoxOnPlaneSide2( ( absmins ), ( absmaxs ), ( p ) ) \ + ) \ + ) //end of the function AAS_BoxOnPlaneSide +//=========================================================================== +// remove the links to this entity from all areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromAreas( aas_link_t *areas ) { + aas_link_t *link, *nextlink; + + for ( link = areas; link; link = nextlink ) + { + //next area the entity is linked in + nextlink = link->next_area; + //remove the entity from the linked list of this area + if ( link->prev_ent ) { + link->prev_ent->next_ent = link->next_ent; + } else { ( *aasworld ).arealinkedentities[link->areanum] = link->next_ent;} + if ( link->next_ent ) { + link->next_ent->prev_ent = link->prev_ent; + } + //deallocate the link structure + AAS_DeAllocAASLink( link ); + } //end for +} //end of the function AAS_UnlinkFromAreas +//=========================================================================== +// link the entity to the areas the bounding box is totally or partly +// situated in. This is done with recursion down the tree using the +// bounding box to test for plane sides +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +typedef struct +{ + int nodenum; //node found after splitting +} aas_linkstack_t; + +aas_link_t *AAS_AASLinkEntity( vec3_t absmins, vec3_t absmaxs, int entnum ) { + int side, nodenum; + aas_linkstack_t linkstack[128]; + aas_linkstack_t *lstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_link_t *link, *areas; + + if ( !( *aasworld ).loaded ) { + botimport.Print( PRT_ERROR, "AAS_LinkEntity: aas not loaded\n" ); + return NULL; + } //end if + + areas = NULL; + // + lstack_p = linkstack; + //we start with the whole line on the stack + //start with node 1 because node zero is a dummy used for solid leafs + lstack_p->nodenum = 1; //starting at the root of the tree + lstack_p++; + + while ( 1 ) + { + //pop up the stack + lstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if ( lstack_p < linkstack ) { + break; + } + //number of the current node to test the line against + nodenum = lstack_p->nodenum; + //if it is an area + if ( nodenum < 0 ) { + //NOTE: the entity might have already been linked into this area + // because several node children can point to the same area + for ( link = ( *aasworld ).arealinkedentities[-nodenum]; link; link = link->next_ent ) + { + if ( link->entnum == entnum ) { + break; + } + } //end for + if ( link ) { + continue; + } + // + link = AAS_AllocAASLink(); + if ( !link ) { + return areas; + } + link->entnum = entnum; + link->areanum = -nodenum; + //put the link into the double linked area list of the entity + link->prev_area = NULL; + link->next_area = areas; + if ( areas ) { + areas->prev_area = link; + } + areas = link; + //put the link into the double linked entity list of the area + link->prev_ent = NULL; + link->next_ent = ( *aasworld ).arealinkedentities[-nodenum]; + if ( ( *aasworld ).arealinkedentities[-nodenum] ) { + ( *aasworld ).arealinkedentities[-nodenum]->prev_ent = link; + } + ( *aasworld ).arealinkedentities[-nodenum] = link; + // + continue; + } //end if + //if solid leaf + if ( !nodenum ) { + continue; + } + //the node to test against + aasnode = &( *aasworld ).nodes[nodenum]; + //the current node plane + plane = &( *aasworld ).planes[aasnode->planenum]; + //get the side(s) the box is situated relative to the plane + side = AAS_BoxOnPlaneSide2( absmins, absmaxs, plane ); + //if on the front side of the node + if ( side & 1 ) { + lstack_p->nodenum = aasnode->children[0]; + lstack_p++; + } //end if + if ( lstack_p >= &linkstack[127] ) { + botimport.Print( PRT_ERROR, "AAS_LinkEntity: stack overflow\n" ); + break; + } //end if + //if on the back side of the node + if ( side & 2 ) { + lstack_p->nodenum = aasnode->children[1]; + lstack_p++; + } //end if + if ( lstack_p >= &linkstack[127] ) { + botimport.Print( PRT_ERROR, "AAS_LinkEntity: stack overflow\n" ); + break; + } //end if + } //end while + return areas; +} //end of the function AAS_AASLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_LinkEntityClientBBox( vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype ) { + vec3_t mins, maxs; + vec3_t newabsmins, newabsmaxs; + + AAS_PresenceTypeBoundingBox( presencetype, mins, maxs ); + VectorSubtract( absmins, maxs, newabsmins ); + VectorSubtract( absmaxs, mins, newabsmaxs ); + //relink the entity + return AAS_AASLinkEntity( newabsmins, newabsmaxs, entnum ); +} //end of the function AAS_LinkEntityClientBBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_plane_t *AAS_PlaneFromNum( int planenum ) { + if ( !( *aasworld ).loaded ) { + return 0; + } + + return &( *aasworld ).planes[planenum]; +} //end of the function AAS_PlaneFromNum + +/* +============= +AAS_BBoxAreas +============= +*/ +int AAS_BBoxAreas( vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas ) { + aas_link_t *linkedareas, *link; + int num; + + linkedareas = AAS_AASLinkEntity( absmins, absmaxs, -1 ); + num = 0; + for ( link = linkedareas; link; link = link->next_area ) + { + areas[num] = link->areanum; + num++; + if ( num >= maxareas ) { + break; + } + } //end for + AAS_UnlinkFromAreas( linkedareas ); + return num; +} //end of the function AAS_BBoxAreas diff --git a/src/botlib/be_aas_sample.h b/src/botlib/be_aas_sample.h new file mode 100644 index 0000000..7e7a961 --- /dev/null +++ b/src/botlib/be_aas_sample.h @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas_sample.h + * + * desc: AAS + * + * + *****************************************************************************/ + +#ifdef AASINTERN +void AAS_InitAASLinkHeap( void ); +void AAS_InitAASLinkedEntities( void ); +void AAS_FreeAASLinkHeap( void ); +void AAS_FreeAASLinkedEntities( void ); +aas_face_t *AAS_AreaGroundFace( int areanum, vec3_t point ); +aas_face_t *AAS_TraceEndFace( aas_trace_t *trace ); +aas_plane_t *AAS_PlaneFromNum( int planenum ); +aas_link_t *AAS_AASLinkEntity( vec3_t absmins, vec3_t absmaxs, int entnum ); +aas_link_t *AAS_LinkEntityClientBBox( vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype ); +qboolean AAS_PointInsideFace( int facenum, vec3_t point, float epsilon ); +qboolean AAS_InsideFace( aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon ); +void AAS_UnlinkFromAreas( aas_link_t *areas ); +#endif //AASINTERN + +//returns the mins and maxs of the bounding box for the given presence type +void AAS_PresenceTypeBoundingBox( int presencetype, vec3_t mins, vec3_t maxs ); +//returns the cluster the area is in (negative portal number if the area is a portal) +int AAS_AreaCluster( int areanum ); +//returns the presence type(s) of the area +int AAS_AreaPresenceType( int areanum ); +//returns the presence type(s) at the given point +int AAS_PointPresenceType( vec3_t point ); +//returns the result of the trace of a client bbox +aas_trace_t AAS_TraceClientBBox( vec3_t start, vec3_t end, int presencetype, int passent ); +//stores the areas the trace went through and returns the number of passed areas +int AAS_TraceAreas( vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas ); +//returns the area the point is in +int AAS_PointAreaNum( vec3_t point ); +//returns the plane the given face is in +void AAS_FacePlane( int facenum, vec3_t normal, float *dist ); + +int AAS_BBoxAreas( vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas ); diff --git a/src/botlib/be_ai_char.c b/src/botlib/be_ai_char.c new file mode 100644 index 0000000..6e98266 --- /dev/null +++ b/src/botlib/be_ai_char.c @@ -0,0 +1,765 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_char.c + * + * desc: bot characters + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ai_char.h" + +#define MAX_CHARACTERISTICS 80 + +#define CT_INTEGER 1 +#define CT_FLOAT 2 +#define CT_STRING 3 + +#define DEFAULT_CHARACTER "bots/default_c.c" + +//characteristic value +union cvalue +{ + int integer; + float _float; + char *string; +}; +//a characteristic +typedef struct bot_characteristic_s +{ + char type; //characteristic type + union cvalue value; //characteristic value +} bot_characteristic_t; + +//a bot character +typedef struct bot_character_s +{ + char filename[MAX_QPATH]; + int skill; + bot_characteristic_t c[1]; //variable sized +} bot_character_t; + +bot_character_t *botcharacters[MAX_CLIENTS + 1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_character_t *BotCharacterFromHandle( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "character handle %d out of range\n", handle ); + return NULL; + } //end if + if ( !botcharacters[handle] ) { + botimport.Print( PRT_FATAL, "invalid character %d\n", handle ); + return NULL; + } //end if + return botcharacters[handle]; +} //end of the function BotCharacterFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpCharacter( bot_character_t *ch ) { + int i; + + Log_Write( "%s", ch->filename ); + Log_Write( "skill %d\n", ch->skill ); + Log_Write( "{\n" ); + for ( i = 0; i < MAX_CHARACTERISTICS; i++ ) + { + switch ( ch->c[i].type ) + { + case CT_INTEGER: Log_Write( " %4d %d\n", i, ch->c[i].value.integer ); break; + case CT_FLOAT: Log_Write( " %4d %f\n", i, ch->c[i].value._float ); break; + case CT_STRING: Log_Write( " %4d %s\n", i, ch->c[i].value.string ); break; + } //end case + } //end for + Log_Write( "}\n" ); +} //end of the function BotDumpCharacter +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacterStrings( bot_character_t *ch ) { + int i; + + for ( i = 0; i < MAX_CHARACTERISTICS; i++ ) + { + if ( ch->c[i].type == CT_STRING ) { + FreeMemory( ch->c[i].value.string ); + } //end if + } //end for +} //end of the function BotFreeCharacterStrings +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter2( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "character handle %d out of range\n", handle ); + return; + } //end if + if ( !botcharacters[handle] ) { + botimport.Print( PRT_FATAL, "invalid character %d\n", handle ); + return; + } //end if + BotFreeCharacterStrings( botcharacters[handle] ); + FreeMemory( botcharacters[handle] ); + botcharacters[handle] = NULL; +} //end of the function BotFreeCharacter2 +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter( int handle ) { + if ( !LibVarGetValue( "bot_reloadcharacters" ) ) { + return; + } + BotFreeCharacter2( handle ); +} //end of the function BotFreeCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDefaultCharacteristics( bot_character_t *ch, bot_character_t *defaultch ) { + int i; + + for ( i = 0; i < MAX_CHARACTERISTICS; i++ ) + { + if ( ch->c[i].type ) { + continue; + } + // + if ( defaultch->c[i].type == CT_FLOAT ) { + ch->c[i].type = CT_FLOAT; + ch->c[i].value._float = defaultch->c[i].value._float; + } //end if + else if ( defaultch->c[i].type == CT_INTEGER ) { + ch->c[i].type = CT_INTEGER; + ch->c[i].value.integer = defaultch->c[i].value.integer; + } //end else if + else if ( defaultch->c[i].type == CT_STRING ) { + ch->c[i].type = CT_STRING; + ch->c[i].value.string = (char *) GetMemory( strlen( defaultch->c[i].value.string ) + 1 ); + strcpy( ch->c[i].value.string, defaultch->c[i].value.string ); + } //end else if + } //end for +} //end of the function BotDefaultCharacteristics +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_character_t *BotLoadCharacterFromFile( char *charfile, int skill ) { + int indent, index, foundcharacter; + bot_character_t *ch; + source_t *source; + token_t token; + + foundcharacter = qfalse; + //a bot character is parsed in two phases + source = LoadSourceFile( charfile ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", charfile ); + return NULL; + } //end if + ch = (bot_character_t *) GetClearedMemory( sizeof( bot_character_t ) + + MAX_CHARACTERISTICS * sizeof( bot_characteristic_t ) ); + strcpy( ch->filename, charfile ); + while ( PC_ReadToken( source, &token ) ) + { + if ( !strcmp( token.string, "skill" ) ) { + if ( !PC_ExpectTokenType( source, TT_NUMBER, 0, &token ) ) { + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end if + if ( !PC_ExpectTokenString( source, "{" ) ) { + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end if + //if it's the correct skill + if ( skill < 0 || token.intvalue == skill ) { + foundcharacter = qtrue; + ch->skill = token.intvalue; + while ( PC_ExpectAnyToken( source, &token ) ) + { + if ( !strcmp( token.string, "}" ) ) { + break; + } + if ( token.type != TT_NUMBER || !( token.subtype & TT_INTEGER ) ) { + SourceError( source, "expected integer index, found %s\n", token.string ); + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end if + index = token.intvalue; + if ( index < 0 || index > MAX_CHARACTERISTICS ) { + SourceError( source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS ); + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end if + if ( ch->c[index].type ) { + SourceError( source, "characteristic %d already initialized\n", index ); + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end if + if ( !PC_ExpectAnyToken( source, &token ) ) { + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end if + if ( token.type == TT_NUMBER ) { + if ( token.subtype & TT_FLOAT ) { + ch->c[index].value._float = token.floatvalue; + ch->c[index].type = CT_FLOAT; + } //end if + else + { + ch->c[index].value.integer = token.intvalue; + ch->c[index].type = CT_INTEGER; + } //end else + } //end if + else if ( token.type == TT_STRING ) { + StripDoubleQuotes( token.string ); + ch->c[index].value.string = GetMemory( strlen( token.string ) + 1 ); + strcpy( ch->c[index].value.string, token.string ); + ch->c[index].type = CT_STRING; + } //end else if + else + { + SourceError( source, "expected integer, float or string, found %s\n", token.string ); + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end else + } //end if + break; + } //end if + else + { + indent = 1; + while ( indent ) + { + if ( !PC_ExpectAnyToken( source, &token ) ) { + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end if + if ( !strcmp( token.string, "{" ) ) { + indent++; + } else if ( !strcmp( token.string, "}" ) ) { + indent--; + } + } //end while + } //end else + } //end if + else + { + SourceError( source, "unknown definition %s\n", token.string ); + FreeSource( source ); + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end else + } //end while + FreeSource( source ); + // + if ( !foundcharacter ) { + BotFreeCharacterStrings( ch ); + FreeMemory( ch ); + return NULL; + } //end if + return ch; +} //end of the function BotLoadCharacterFromFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindCachedCharacter( char *charfile, int skill ) { + int handle; + + for ( handle = 1; handle <= MAX_CLIENTS; handle++ ) + { + if ( !botcharacters[handle] ) { + continue; + } + if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 && + ( skill < 0 || botcharacters[handle]->skill == skill ) ) { + return handle; + } //end if + } //end for + return 0; +} //end of the function BotFindCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCachedCharacter( char *charfile, int skill, int reload ) { + int handle, cachedhandle; + bot_character_t *ch = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + //find a free spot for a character + for ( handle = 1; handle <= MAX_CLIENTS; handle++ ) + { + if ( !botcharacters[handle] ) { + break; + } + } //end for + if ( handle > MAX_CLIENTS ) { + return 0; + } + //try to load a cached character with the given skill + if ( !reload ) { + cachedhandle = BotFindCachedCharacter( charfile, skill ); + if ( cachedhandle ) { + botimport.Print( PRT_MESSAGE, "loaded cached skill %d from %s\n", skill, charfile ); + return cachedhandle; + } //end if + } //end else + //try to load the character with the given skill + ch = BotLoadCharacterFromFile( charfile, skill ); + if ( ch ) { + botcharacters[handle] = ch; + // + botimport.Print( PRT_MESSAGE, "loaded skill %d from %s\n", skill, charfile ); +#ifdef DEBUG + if ( bot_developer ) { + botimport.Print( PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", skill, Sys_MilliSeconds() - starttime, charfile ); + } //end if +#endif //DEBUG + return handle; + } //end if + // + botimport.Print( PRT_WARNING, "couldn't find skill %d in %s\n", skill, charfile ); + // + if ( !reload ) { + //try to load a cached default character with the given skill + cachedhandle = BotFindCachedCharacter( "bots/default_c.c", skill ); + if ( cachedhandle ) { + botimport.Print( PRT_MESSAGE, "loaded cached default skill %d from %s\n", skill, charfile ); + return cachedhandle; + } //end if + } //end if + //try to load the default character with the given skill + ch = BotLoadCharacterFromFile( DEFAULT_CHARACTER, skill ); + if ( ch ) { + botcharacters[handle] = ch; + botimport.Print( PRT_MESSAGE, "loaded default skill %d from %s\n", skill, charfile ); + return handle; + } //end if + // + if ( !reload ) { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter( charfile, -1 ); + if ( cachedhandle ) { + botimport.Print( PRT_MESSAGE, "loaded cached skill %d from %s\n", botcharacters[cachedhandle]->skill, charfile ); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile( charfile, -1 ); + if ( ch ) { + botcharacters[handle] = ch; + botimport.Print( PRT_MESSAGE, "loaded skill %d from %s\n", ch->skill, charfile ); + return handle; + } //end if + // + if ( !reload ) { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter( DEFAULT_CHARACTER, -1 ); + if ( cachedhandle ) { + botimport.Print( PRT_MESSAGE, "loaded cached default skill %d from %s\n", botcharacters[cachedhandle]->skill, charfile ); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile( DEFAULT_CHARACTER, -1 ); + if ( ch ) { + botcharacters[handle] = ch; + botimport.Print( PRT_MESSAGE, "loaded default skill %d from %s\n", ch->skill, charfile ); + return handle; + } //end if + // + botimport.Print( PRT_WARNING, "couldn't load any skill from %s\n", charfile ); + //couldn't load any character + return 0; +} //end of the function BotLoadCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacterSkill( char *charfile, int skill ) { + int ch, defaultch; + + defaultch = BotLoadCachedCharacter( DEFAULT_CHARACTER, skill, qfalse ); + ch = BotLoadCachedCharacter( charfile, skill, LibVarGetValue( "bot_reloadcharacters" ) ); + + if ( defaultch && ch ) { + BotDefaultCharacteristics( botcharacters[ch], botcharacters[defaultch] ); + } //end if + + return ch; +} //end of the function BotLoadCharacterSkill +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotInterpolateCharacters( int handle1, int handle2, int desiredskill ) { + bot_character_t *ch1, *ch2, *out; + int i, handle; + float scale; + + ch1 = BotCharacterFromHandle( handle1 ); + ch2 = BotCharacterFromHandle( handle2 ); + if ( !ch1 || !ch2 ) { + return 0; + } + //find a free spot for a character + for ( handle = 1; handle <= MAX_CLIENTS; handle++ ) + { + if ( !botcharacters[handle] ) { + break; + } + } //end for + if ( handle > MAX_CLIENTS ) { + return 0; + } + out = (bot_character_t *) GetClearedMemory( sizeof( bot_character_t ) + + MAX_CHARACTERISTICS * sizeof( bot_characteristic_t ) ); + out->skill = desiredskill; + strcpy( out->filename, ch1->filename ); + botcharacters[handle] = out; + + scale = (float) ( desiredskill - 1 ) / ( ch2->skill - ch1->skill ); + for ( i = 0; i < MAX_CHARACTERISTICS; i++ ) + { + // + if ( ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT ) { + out->c[i].type = CT_FLOAT; + out->c[i].value._float = ch1->c[i].value._float + + ( ch2->c[i].value._float - ch1->c[i].value._float ) * scale; + } //end if + else if ( ch1->c[i].type == CT_INTEGER ) { + out->c[i].type = CT_INTEGER; + out->c[i].value.integer = ch1->c[i].value.integer; + } //end else if + else if ( ch1->c[i].type == CT_STRING ) { + out->c[i].type = CT_STRING; + out->c[i].value.string = (char *) GetMemory( strlen( ch1->c[i].value.string ) + 1 ); + strcpy( out->c[i].value.string, ch1->c[i].value.string ); + } //end else if + } //end for + return handle; +} //end of the function BotInterpolateCharacters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacter( char *charfile, int skill ) { + int skill1, skill4, handle; + + //make sure the skill is in the valid range + if ( skill < 1 ) { + skill = 1; + } else if ( skill > 5 ) { + skill = 5; + } + //skill 1, 4 and 5 should be available in the character files + if ( skill == 1 || skill == 4 || skill == 5 ) { + return BotLoadCharacterSkill( charfile, skill ); + } //end if + //check if there's a cached skill 2 or 3 + handle = BotFindCachedCharacter( charfile, skill ); + if ( handle ) { + botimport.Print( PRT_MESSAGE, "loaded cached skill %d from %s\n", skill, charfile ); + return handle; + } //end if + //load skill 1 and 4 + skill1 = BotLoadCharacterSkill( charfile, 1 ); + if ( !skill1 ) { + return 0; + } + skill4 = BotLoadCharacterSkill( charfile, 4 ); + if ( !skill4 ) { + return skill1; + } + //interpolate between 1 and 4 to create skill 2 or 3 + handle = BotInterpolateCharacters( skill1, skill4, skill ); + if ( !handle ) { + return 0; + } + //write the character to the log file + BotDumpCharacter( botcharacters[handle] ); + // + return handle; +} //end of the function BotLoadCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CheckCharacteristicIndex( int character, int index ) { + bot_character_t *ch; + + ch = BotCharacterFromHandle( character ); + if ( !ch ) { + return qfalse; + } + if ( index < 0 || index >= MAX_CHARACTERISTICS ) { + botimport.Print( PRT_ERROR, "characteristic %d does not exist\n", index ); + return qfalse; + } //end if + if ( !ch->c[index].type ) { + botimport.Print( PRT_ERROR, "characteristic %d is not initialized\n", index ); + return qfalse; + } //end if + return qtrue; +} //end of the function CheckCharacteristicIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_Float( int character, int index ) { + bot_character_t *ch; + + ch = BotCharacterFromHandle( character ); + if ( !ch ) { + return 0; + } + //check if the index is in range + if ( !CheckCharacteristicIndex( character, index ) ) { + return 0; + } + //an integer will be converted to a float + if ( ch->c[index].type == CT_INTEGER ) { + return (float) ch->c[index].value.integer; + } //end if + //floats are just returned + else if ( ch->c[index].type == CT_FLOAT ) { + return ch->c[index].value._float; + } //end else if + //cannot convert a string pointer to a float + else + { + botimport.Print( PRT_ERROR, "characteristic %d is not a float\n", index ); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Float +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_BFloat( int character, int index, float min, float max ) { + float value; + bot_character_t *ch; + + ch = BotCharacterFromHandle( character ); + if ( !ch ) { + return 0; + } + if ( min > max ) { + botimport.Print( PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max ); + return 0; + } //end if + value = Characteristic_Float( character, index ); + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} //end of the function Characteristic_BFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_Integer( int character, int index ) { + bot_character_t *ch; + + ch = BotCharacterFromHandle( character ); + if ( !ch ) { + return 0; + } + //check if the index is in range + if ( !CheckCharacteristicIndex( character, index ) ) { + return 0; + } + //an integer will just be returned + if ( ch->c[index].type == CT_INTEGER ) { + return ch->c[index].value.integer; + } //end if + //floats are casted to integers + else if ( ch->c[index].type == CT_FLOAT ) { + return (int) ch->c[index].value._float; + } //end else if + else + { + botimport.Print( PRT_ERROR, "characteristic %d is not a integer\n", index ); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Integer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_BInteger( int character, int index, int min, int max ) { + int value; + bot_character_t *ch; + + ch = BotCharacterFromHandle( character ); + if ( !ch ) { + return 0; + } + if ( min > max ) { + botimport.Print( PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max ); + return 0; + } //end if + value = Characteristic_Integer( character, index ); + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} //end of the function Characteristic_BInteger +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Characteristic_String( int character, int index, char *buf, int size ) { + bot_character_t *ch; + + ch = BotCharacterFromHandle( character ); + if ( !ch ) { + return; + } + //check if the index is in range + if ( !CheckCharacteristicIndex( character, index ) ) { + return; + } + //an integer will be converted to a float + if ( ch->c[index].type == CT_STRING ) { + strncpy( buf, ch->c[index].value.string, size - 1 ); + buf[size - 1] = '\0'; + return; + } //end if + else + { + botimport.Print( PRT_ERROR, "characteristic %d is not a string\n", index ); + return; + } //end else if + return; +} //end of the function Characteristic_String +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownCharacters( void ) { + int handle; + + for ( handle = 1; handle <= MAX_CLIENTS; handle++ ) + { + if ( botcharacters[handle] ) { + BotFreeCharacter2( handle ); + } //end if + } //end for +} //end of the function BotShutdownCharacters diff --git a/src/botlib/be_ai_chat.c b/src/botlib/be_ai_chat.c new file mode 100644 index 0000000..3e730a1 --- /dev/null +++ b/src/botlib/be_ai_chat.c @@ -0,0 +1,2884 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_chat.c + * + * desc: bot chat AI + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +//#include "../server/server.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ea.h" +#include "../game/be_ai_chat.h" + + +//escape character +#define ESCAPE_CHAR 0x01 //'_' +// +// "hi ", people, " ", 0, " entered the game" +//becomes: +// "hi _rpeople_ _v0_ entered the game" +// + +//match piece types +#define MT_VARIABLE 1 //variable match piece +#define MT_STRING 2 //string match piece +//reply chat key flags +#define RCKFL_AND 1 //key must be present +#define RCKFL_NOT 2 //key must be absent +#define RCKFL_NAME 4 //name of bot must be present +#define RCKFL_STRING 8 //key is a string +#define RCKFL_VARIABLES 16 //key is a match template +#define RCKFL_BOTNAMES 32 //key is a series of botnames +#define RCKFL_GENDERFEMALE 64 //bot must be female +#define RCKFL_GENDERMALE 128 //bot must be male +#define RCKFL_GENDERLESS 256 //bot must be genderless +//time to ignore a chat message after using it +#define CHATMESSAGE_RECENTTIME 20 + +//the actuall chat messages +typedef struct bot_chatmessage_s +{ + char *chatmessage; //chat message string + float time; //last time used + struct bot_chatmessage_s *next; //next chat message in a list +} bot_chatmessage_t; +//bot chat type with chat lines +typedef struct bot_chattype_s +{ + char name[MAX_CHATTYPE_NAME]; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_chattype_s *next; +} bot_chattype_t; +//bot chat lines +typedef struct bot_chat_s +{ + bot_chattype_t *types; +} bot_chat_t; + +//random string +typedef struct bot_randomstring_s +{ + char *string; + struct bot_randomstring_s *next; +} bot_randomstring_t; +//list with random strings +typedef struct bot_randomlist_s +{ + char *string; + int numstrings; + bot_randomstring_t *firstrandomstring; + struct bot_randomlist_s *next; +} bot_randomlist_t; + +//synonym +typedef struct bot_synonym_s +{ + char *string; + float weight; + struct bot_synonym_s *next; +} bot_synonym_t; +//list with synonyms +typedef struct bot_synonymlist_s +{ + unsigned long int context; + float totalweight; + bot_synonym_t *firstsynonym; + struct bot_synonymlist_s *next; +} bot_synonymlist_t; + +//fixed match string +typedef struct bot_matchstring_s +{ + char *string; + struct bot_matchstring_s *next; +} bot_matchstring_t; + +//piece of a match template +typedef struct bot_matchpiece_s +{ + int type; + bot_matchstring_t *firststring; + int variable; + struct bot_matchpiece_s *next; +} bot_matchpiece_t; +//match template +typedef struct bot_matchtemplate_s +{ + unsigned long int context; + int type; + int subtype; + bot_matchpiece_t *first; + struct bot_matchtemplate_s *next; +} bot_matchtemplate_t; + +//reply chat key +typedef struct bot_replychatkey_s +{ + int flags; + char *string; + bot_matchpiece_t *match; + struct bot_replychatkey_s *next; +} bot_replychatkey_t; +//reply chat +typedef struct bot_replychat_s +{ + bot_replychatkey_t *keys; + float priority; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_replychat_s *next; +} bot_replychat_t; + +//string list +typedef struct bot_stringlist_s +{ + char *string; + struct bot_stringlist_s *next; +} bot_stringlist_t; + +//chat state of a bot +typedef struct bot_chatstate_s +{ + int gender; //0=it, 1=female, 2=male + char name[32]; //name of the bot + char chatmessage[MAX_MESSAGE_SIZE]; + int handle; + //the console messages visible to the bot + bot_consolemessage_t *firstmessage; //first message is the first typed message + bot_consolemessage_t *lastmessage; //last message is the last typed message, bottom of console + //number of console messages stored in the state + int numconsolemessages; + //the bot chat lines + bot_chat_t *chat; +} bot_chatstate_t; + +typedef struct { + bot_chat_t *chat; + int inuse; + char filename[MAX_QPATH]; + char chatname[MAX_QPATH]; +} bot_ichatdata_t; + +bot_ichatdata_t ichatdata[MAX_CLIENTS]; + +bot_chatstate_t *botchatstates[MAX_CLIENTS + 1]; +//console message heap +bot_consolemessage_t *consolemessageheap = NULL; +bot_consolemessage_t *freeconsolemessages = NULL; +//list with match strings +bot_matchtemplate_t *matchtemplates = NULL; +//list with synonyms +bot_synonymlist_t *synonyms = NULL; +//list with random strings +bot_randomlist_t *randomstrings = NULL; +//reply chats +bot_replychat_t *replychats = NULL; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_chatstate_t *BotChatStateFromHandle( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "chat state handle %d out of range\n", handle ); + return NULL; + } //end if + if ( !botchatstates[handle] ) { + botimport.Print( PRT_FATAL, "invalid chat state %d\n", handle ); + return NULL; + } //end if + return botchatstates[handle]; +} //end of the function BotChatStateFromHandle +//=========================================================================== +// initialize the heap with unused console messages +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitConsoleMessageHeap( void ) { + int i, max_messages; + + if ( consolemessageheap ) { + FreeMemory( consolemessageheap ); + } + // + max_messages = (int) LibVarValue( "max_messages", "1024" ); + consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory( max_messages * + sizeof( bot_consolemessage_t ) ); + consolemessageheap[0].prev = NULL; + consolemessageheap[0].next = &consolemessageheap[1]; + for ( i = 1; i < max_messages - 1; i++ ) + { + consolemessageheap[i].prev = &consolemessageheap[i - 1]; + consolemessageheap[i].next = &consolemessageheap[i + 1]; + } //end for + consolemessageheap[max_messages - 1].prev = &consolemessageheap[max_messages - 2]; + consolemessageheap[max_messages - 1].next = NULL; + //pointer to the free console messages + freeconsolemessages = consolemessageheap; +} //end of the function InitConsoleMessageHeap +//=========================================================================== +// allocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_consolemessage_t *AllocConsoleMessage( void ) { + bot_consolemessage_t *message; + message = freeconsolemessages; + if ( freeconsolemessages ) { + freeconsolemessages = freeconsolemessages->next; + } + if ( freeconsolemessages ) { + freeconsolemessages->prev = NULL; + } + return message; +} //end of the function AllocConsoleMessage +//=========================================================================== +// deallocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeConsoleMessage( bot_consolemessage_t *message ) { + if ( freeconsolemessages ) { + freeconsolemessages->prev = message; + } + message->prev = NULL; + message->next = freeconsolemessages; + freeconsolemessages = message; +} //end of the function FreeConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveConsoleMessage( int chatstate, int handle ) { + bot_consolemessage_t *m, *nextm; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return; + } + + for ( m = cs->firstmessage; m; m = nextm ) + { + nextm = m->next; + if ( m->handle == handle ) { + if ( m->next ) { + m->next->prev = m->prev; + } else { cs->lastmessage = m->prev;} + if ( m->prev ) { + m->prev->next = m->next; + } else { cs->firstmessage = m->next;} + + FreeConsoleMessage( m ); + cs->numconsolemessages--; + break; + } //end if + } //end for +} //end of the function BotRemoveConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotQueueConsoleMessage( int chatstate, int type, char *message ) { + bot_consolemessage_t *m; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return; + } + + m = AllocConsoleMessage(); + if ( !m ) { + botimport.Print( PRT_ERROR, "empty console message heap\n" ); + return; + } //end if + cs->handle++; + if ( cs->handle <= 0 || cs->handle > 8192 ) { + cs->handle = 1; + } + m->handle = cs->handle; + m->time = AAS_Time(); + m->type = type; + strncpy( m->message, message, MAX_MESSAGE_SIZE ); + m->next = NULL; + if ( cs->lastmessage ) { + cs->lastmessage->next = m; + m->prev = cs->lastmessage; + cs->lastmessage = m; + } //end if + else + { + cs->lastmessage = m; + cs->firstmessage = m; + m->prev = NULL; + } //end if + cs->numconsolemessages++; +} //end of the function BotQueueConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNextConsoleMessage( int chatstate, bot_consolemessage_t *cm ) { + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return 0; + } + if ( cs->firstmessage ) { + memcpy( cm, cs->firstmessage, sizeof( bot_consolemessage_t ) ); + cm->next = cm->prev = NULL; + return cm->handle; + } //end if + return 0; +} //end of the function BotConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumConsoleMessages( int chatstate ) { + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return 0; + } + return cs->numconsolemessages; +} //end of the function BotNumConsoleMessages +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int IsWhiteSpace( char c ) { + if ( ( c >= 'a' && c <= 'z' ) + || ( c >= 'A' && c <= 'Z' ) + || ( c >= '0' && c <= '9' ) + || c == '(' || c == ')' + || c == '?' || c == '\'' + || c == ':' || c == ',' + || c == '[' || c == ']' + || c == '-' || c == '_' + || c == '+' || c == '=' ) { + return qfalse; + } + return qtrue; +} //end of the function IsWhiteSpace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveTildes( char *message ) { + int i; + + //remove all tildes from the chat message + for ( i = 0; message[i]; i++ ) + { + if ( message[i] == '~' ) { + memmove( &message[i], &message[i + 1], strlen( &message[i + 1] ) + 1 ); + } //end if + } //end for +} //end of the function BotRemoveTildes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnifyWhiteSpaces( char *string ) { + char *ptr, *oldptr; + + for ( ptr = oldptr = string; *ptr; oldptr = ptr ) + { + while ( *ptr && IsWhiteSpace( *ptr ) ) ptr++; + if ( ptr > oldptr ) { + //if not at the start and not at the end of the string + //write only one space + if ( oldptr > string && *ptr ) { + *oldptr++ = ' '; + } + //remove all other white spaces + if ( ptr > oldptr ) { + memmove( oldptr, ptr, strlen( ptr ) + 1 ); + } + } //end if + while ( *ptr && !IsWhiteSpace( *ptr ) ) ptr++; + } //end while +} //end of the function UnifyWhiteSpaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringContains( char *str1, char *str2, int casesensitive ) { + int len, i, j, index; + + if ( str1 == NULL || str2 == NULL ) { + return -1; + } + + len = strlen( str1 ) - strlen( str2 ); + index = 0; + for ( i = 0; i <= len; i++, str1++, index++ ) + { + for ( j = 0; str2[j]; j++ ) + { + if ( casesensitive ) { + if ( str1[j] != str2[j] ) { + break; + } + } //end if + else + { + if ( toupper( str1[j] ) != toupper( str2[j] ) ) { + break; + } + } //end else + } //end for + if ( !str2[j] ) { + return index; + } + } //end for + return -1; +} //end of the function StringContains +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *StringContainsWord( char *str1, char *str2, int casesensitive ) { + int len, i, j; + + len = strlen( str1 ) - strlen( str2 ); + for ( i = 0; i <= len; i++, str1++ ) + { + //if not at the start of the string + if ( i ) { + //skip to the start of the next word + while ( *str1 && *str1 != ' ' ) str1++; + if ( !*str1 ) { + break; + } + str1++; + } //end for + //compare the word + for ( j = 0; str2[j]; j++ ) + { + if ( casesensitive ) { + if ( str1[j] != str2[j] ) { + break; + } + } //end if + else + { + if ( toupper( str1[j] ) != toupper( str2[j] ) ) { + break; + } + } //end else + } //end for + //if there was a word match + if ( !str2[j] ) { + //if the first string has an end of word + if ( !str1[j] || str1[j] == ' ' ) { + return str1; + } + } //end if + } //end for + return NULL; +} //end of the function StringContainsWord +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void StringReplaceWords( char *string, char *synonym, char *replacement ) { + char *str, *str2; + + //find the synonym in the string + str = StringContainsWord( string, synonym, qfalse ); + //if the synonym occured in the string + while ( str ) + { + //if the synonym isn't part of the replacement which is already in the string + //usefull for abreviations + str2 = StringContainsWord( string, replacement, qfalse ); + while ( str2 ) + { + if ( str2 <= str && str < str2 + strlen( replacement ) ) { + break; + } + str2 = StringContainsWord( str2 + 1, replacement, qfalse ); + } //end while + if ( !str2 ) { + memmove( str + strlen( replacement ), str + strlen( synonym ), strlen( str + strlen( synonym ) ) + 1 ); + //append the synonum replacement + memcpy( str, replacement, strlen( replacement ) ); + } //end if + //find the next synonym in the string + str = StringContainsWord( str + strlen( replacement ), synonym, qfalse ); + } //end if +} //end of the function StringReplaceWords +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpSynonymList( bot_synonymlist_t *synlist ) { + FILE *fp; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + fp = Log_FilePointer(); + if ( !fp ) { + return; + } + for ( syn = synlist; syn; syn = syn->next ) + { + fprintf( fp, "%ld : [", syn->context ); + for ( synonym = syn->firstsynonym; synonym; synonym = synonym->next ) + { + fprintf( fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight ); + if ( synonym->next ) { + fprintf( fp, ", " ); + } + } //end for + fprintf( fp, "]\n" ); + } //end for +} //end of the function BotDumpSynonymList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_synonymlist_t *BotLoadSynonyms( char *filename ) { + int pass, size, contextlevel, numsynonyms; + unsigned long int context, contextstack[32]; + char *ptr = NULL; + source_t *source; + token_t token; + bot_synonymlist_t *synlist, *lastsyn, *syn; + bot_synonym_t *synonym, *lastsynonym; + + size = 0; + synlist = NULL; //make compiler happy + syn = NULL; //make compiler happy + synonym = NULL; //make compiler happy + //the synonyms are parsed in two phases + for ( pass = 0; pass < 2; pass++ ) + { + // + if ( pass && size ) { + ptr = (char *) GetClearedHunkMemory( size ); + } + // + source = LoadSourceFile( filename ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", filename ); + return NULL; + } //end if + // + context = 0; + contextlevel = 0; + synlist = NULL; //list synonyms + lastsyn = NULL; //last synonym in the list + // + while ( PC_ReadToken( source, &token ) ) + { + if ( token.type == TT_NUMBER ) { + context |= token.intvalue; + contextstack[contextlevel] = token.intvalue; + contextlevel++; + if ( contextlevel >= 32 ) { + SourceError( source, "more than 32 context levels" ); + FreeSource( source ); + return NULL; + } //end if + if ( !PC_ExpectTokenString( source, "{" ) ) { + FreeSource( source ); + return NULL; + } //end if + } //end if + else if ( token.type == TT_PUNCTUATION ) { + if ( !strcmp( token.string, "}" ) ) { + contextlevel--; + if ( contextlevel < 0 ) { + SourceError( source, "too many }" ); + FreeSource( source ); + return NULL; + } //end if + context &= ~contextstack[contextlevel]; + } //end if + else if ( !strcmp( token.string, "[" ) ) { + size += sizeof( bot_synonymlist_t ); + if ( pass ) { + syn = (bot_synonymlist_t *) ptr; + ptr += sizeof( bot_synonymlist_t ); + syn->context = context; + syn->firstsynonym = NULL; + syn->next = NULL; + if ( lastsyn ) { + lastsyn->next = syn; + } else { synlist = syn;} + lastsyn = syn; + } //end if + numsynonyms = 0; + lastsynonym = NULL; + while ( 1 ) + { + if ( !PC_ExpectTokenString( source, "(" ) || + !PC_ExpectTokenType( source, TT_STRING, 0, &token ) ) { + FreeSource( source ); + return NULL; + } //end if + StripDoubleQuotes( token.string ); + if ( strlen( token.string ) <= 0 ) { + SourceError( source, "empty string", token.string ); + FreeSource( source ); + return NULL; + } //end if + size += sizeof( bot_synonym_t ) + strlen( token.string ) + 1; + if ( pass ) { + synonym = (bot_synonym_t *) ptr; + ptr += sizeof( bot_synonym_t ); + synonym->string = ptr; + ptr += strlen( token.string ) + 1; + strcpy( synonym->string, token.string ); + // + if ( lastsynonym ) { + lastsynonym->next = synonym; + } else { syn->firstsynonym = synonym;} + lastsynonym = synonym; + } //end if + numsynonyms++; + if ( !PC_ExpectTokenString( source, "," ) || + !PC_ExpectTokenType( source, TT_NUMBER, 0, &token ) || + !PC_ExpectTokenString( source, ")" ) ) { + FreeSource( source ); + return NULL; + } //end if + if ( pass ) { + synonym->weight = token.floatvalue; + syn->totalweight += synonym->weight; + } //end if + if ( PC_CheckTokenString( source, "]" ) ) { + break; + } + if ( !PC_ExpectTokenString( source, "," ) ) { + FreeSource( source ); + return NULL; + } //end if + } //end while + if ( numsynonyms < 2 ) { + SourceError( source, "synonym must have at least two entries\n" ); + FreeSource( source ); + return NULL; + } //end if + } //end else + else + { + SourceError( source, "unexpected %s", token.string ); + FreeSource( source ); + return NULL; + } //end if + } //end else if + } //end while + // + FreeSource( source ); + // + if ( contextlevel > 0 ) { + SourceError( source, "missing }" ); + return NULL; + } //end if + } //end for + botimport.Print( PRT_MESSAGE, "loaded %s\n", filename ); + // + //BotDumpSynonymList(synlist); + // + return synlist; +} //end of the function BotLoadSynonyms +//=========================================================================== +// replace all the synonyms in the string +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceSynonyms( char *string, unsigned long int context ) { + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for ( syn = synonyms; syn; syn = syn->next ) + { + if ( !( syn->context & context ) ) { + continue; + } + for ( synonym = syn->firstsynonym->next; synonym; synonym = synonym->next ) + { + StringReplaceWords( string, synonym->string, syn->firstsynonym->string ); + } //end for + } //end for +} //end of the function BotReplaceSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceWeightedSynonyms( char *string, unsigned long int context ) { + bot_synonymlist_t *syn; + bot_synonym_t *synonym, *replacement; + float weight, curweight; + + for ( syn = synonyms; syn; syn = syn->next ) + { + if ( !( syn->context & context ) ) { + continue; + } + //choose a weighted random replacement synonym + weight = random() * syn->totalweight; + if ( !weight ) { + continue; + } + curweight = 0; + for ( replacement = syn->firstsynonym; replacement; replacement = replacement->next ) + { + curweight += replacement->weight; + if ( weight < curweight ) { + break; + } + } //end for + if ( !replacement ) { + continue; + } + //replace all synonyms with the replacement + for ( synonym = syn->firstsynonym; synonym; synonym = synonym->next ) + { + if ( synonym == replacement ) { + continue; + } + StringReplaceWords( string, synonym->string, replacement->string ); + } //end for + } //end for +} //end of the function BotReplaceWeightedSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceReplySynonyms( char *string, unsigned long int context ) { + char *str1, *str2, *replacement; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for ( str1 = string; *str1; ) + { + //go to the start of the next word + while ( *str1 && *str1 <= ' ' ) str1++; + if ( !*str1 ) { + break; + } + // + for ( syn = synonyms; syn; syn = syn->next ) + { + if ( !( syn->context & context ) ) { + continue; + } + for ( synonym = syn->firstsynonym->next; synonym; synonym = synonym->next ) + { + str2 = synonym->string; + //if the synonym is not at the front of the string continue + str2 = StringContainsWord( str1, synonym->string, qfalse ); + if ( !str2 || str2 != str1 ) { + continue; + } + // + replacement = syn->firstsynonym->string; + //if the replacement IS in front of the string continue + str2 = StringContainsWord( str1, replacement, qfalse ); + if ( str2 && str2 == str1 ) { + continue; + } + // + memmove( str1 + strlen( replacement ), str1 + strlen( synonym->string ), + strlen( str1 + strlen( synonym->string ) ) + 1 ); + //append the synonum replacement + memcpy( str1, replacement, strlen( replacement ) ); + // + break; + } //end for + //if a synonym has been replaced + if ( synonym ) { + break; + } + } //end for + //skip over this word + while ( *str1 && *str1 > ' ' ) str1++; + if ( !*str1 ) { + break; + } + } //end while +} //end of the function BotReplaceReplySynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatMessage( source_t *source, char *chatmessagestring ) { + char *ptr; + token_t token; + + ptr = chatmessagestring; + *ptr = 0; + // + while ( 1 ) + { + if ( !PC_ExpectAnyToken( source, &token ) ) { + return qfalse; + } + //fixed string + if ( token.type == TT_STRING ) { + StripDoubleQuotes( token.string ); + if ( strlen( ptr ) + strlen( token.string ) + 1 > MAX_MESSAGE_SIZE ) { + SourceError( source, "chat message too long\n" ); + return qfalse; + } //end if + strcat( ptr, token.string ); + } //end else if + //variable string + else if ( token.type == TT_NUMBER && ( token.subtype & TT_INTEGER ) ) { + if ( strlen( ptr ) + 7 > MAX_MESSAGE_SIZE ) { + SourceError( source, "chat message too long\n" ); + return qfalse; + } //end if + sprintf( &ptr[strlen( ptr )], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR ); + } //end if + //random string + else if ( token.type == TT_NAME ) { + if ( strlen( ptr ) + 7 > MAX_MESSAGE_SIZE ) { + SourceError( source, "chat message too long\n" ); + return qfalse; + } //end if + sprintf( &ptr[strlen( ptr )], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR ); + } //end else if + else + { + SourceError( source, "unknown message component %s\n", token.string ); + return qfalse; + } //end else + if ( PC_CheckTokenString( source, ";" ) ) { + break; + } + if ( !PC_ExpectTokenString( source, "," ) ) { + return qfalse; + } + } //end while + // + return qtrue; +} //end of the function BotLoadChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpRandomStringList( bot_randomlist_t *randomlist ) { + FILE *fp; + bot_randomlist_t *random; + bot_randomstring_t *rs; + + fp = Log_FilePointer(); + if ( !fp ) { + return; + } + for ( random = randomlist; random; random = random->next ) + { + fprintf( fp, "%s = {", random->string ); + for ( rs = random->firstrandomstring; rs; rs = rs->next ) + { + fprintf( fp, "\"%s\"", rs->string ); + if ( rs->next ) { + fprintf( fp, ", " ); + } else { fprintf( fp, "}\n" );} + } //end for + } //end for +} //end of the function BotDumpRandomStringList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_randomlist_t *BotLoadRandomStrings( char *filename ) { + int pass, size; + char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_randomlist_t *randomlist, *lastrandom, *random; + bot_randomstring_t *randomstring; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + size = 0; + randomlist = NULL; + random = NULL; + //the synonyms are parsed in two phases + for ( pass = 0; pass < 2; pass++ ) + { + // + if ( pass && size ) { + ptr = (char *) GetClearedHunkMemory( size ); + } + // + source = LoadSourceFile( filename ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", filename ); + return NULL; + } //end if + // + randomlist = NULL; //list + lastrandom = NULL; //last + // + while ( PC_ReadToken( source, &token ) ) + { + if ( token.type != TT_NAME ) { + SourceError( source, "unknown random %s", token.string ); + FreeSource( source ); + return NULL; + } //end if + size += sizeof( bot_randomlist_t ) + strlen( token.string ) + 1; + if ( pass ) { + random = (bot_randomlist_t *) ptr; + ptr += sizeof( bot_randomlist_t ); + random->string = ptr; + ptr += strlen( token.string ) + 1; + strcpy( random->string, token.string ); + random->firstrandomstring = NULL; + random->numstrings = 0; + // + if ( lastrandom ) { + lastrandom->next = random; + } else { randomlist = random;} + lastrandom = random; + } //end if + if ( !PC_ExpectTokenString( source, "=" ) || + !PC_ExpectTokenString( source, "{" ) ) { + FreeSource( source ); + return NULL; + } //end if + while ( !PC_CheckTokenString( source, "}" ) ) + { + if ( !BotLoadChatMessage( source, chatmessagestring ) ) { + FreeSource( source ); + return NULL; + } //end if + size += sizeof( bot_randomstring_t ) + strlen( chatmessagestring ) + 1; + if ( pass ) { + randomstring = (bot_randomstring_t *) ptr; + ptr += sizeof( bot_randomstring_t ); + randomstring->string = ptr; + ptr += strlen( chatmessagestring ) + 1; + strcpy( randomstring->string, chatmessagestring ); + // + random->numstrings++; + randomstring->next = random->firstrandomstring; + random->firstrandomstring = randomstring; + } //end if + } //end while + } //end while + //free the source after one pass + FreeSource( source ); + } //end for + botimport.Print( PRT_MESSAGE, "loaded %s\n", filename ); + // +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime ); + //BotDumpRandomStringList(randomlist); +#endif //DEBUG + // + return randomlist; +} //end of the function BotLoadRandomStrings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *RandomString( char *name ) { + bot_randomlist_t *random; + bot_randomstring_t *rs; + int i; + + for ( random = randomstrings; random; random = random->next ) + { + if ( !strcmp( random->string, name ) ) { + i = random() * random->numstrings; + for ( rs = random->firstrandomstring; rs; rs = rs->next ) + { + if ( --i < 0 ) { + break; + } + } //end for + if ( rs ) { + return rs->string; + } //end if + } //end for + } //end for + return NULL; +} //end of the function RandomString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpMatchTemplates( bot_matchtemplate_t *matches ) { + FILE *fp; + bot_matchtemplate_t *mt; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + fp = Log_FilePointer(); + if ( !fp ) { + return; + } + for ( mt = matches; mt; mt = mt->next ) + { + // TTimo ? + // fprintf(fp, "%8d { "); + for ( mp = mt->first; mp; mp = mp->next ) + { + if ( mp->type == MT_STRING ) { + for ( ms = mp->firststring; ms; ms = ms->next ) + { + fprintf( fp, "\"%s\"", ms->string ); + if ( ms->next ) { + fprintf( fp, "|" ); + } + } //end for + } //end if + else if ( mp->type == MT_VARIABLE ) { + fprintf( fp, "%d", mp->variable ); + } //end else if + if ( mp->next ) { + fprintf( fp, ", " ); + } + } //end for + fprintf( fp, " = (%d, %d);}\n", mt->type, mt->subtype ); + } //end for +} //end of the function BotDumpMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchPieces( bot_matchpiece_t *matchpieces ) { + bot_matchpiece_t *mp, *nextmp; + bot_matchstring_t *ms, *nextms; + + for ( mp = matchpieces; mp; mp = nextmp ) + { + nextmp = mp->next; + if ( mp->type == MT_STRING ) { + for ( ms = mp->firststring; ms; ms = nextms ) + { + nextms = ms->next; + FreeMemory( ms ); + } //end for + } //end if + FreeMemory( mp ); + } //end for +} //end of the function BotFreeMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchpiece_t *BotLoadMatchPieces( source_t *source, char *endtoken ) { + int lastwasvariable, emptystring; + token_t token; + bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece; + bot_matchstring_t *matchstring, *lastmatchstring; + + firstpiece = NULL; + lastpiece = NULL; + // + lastwasvariable = qfalse; + // + while ( PC_ReadToken( source, &token ) ) + { + if ( token.type == TT_NUMBER && ( token.subtype & TT_INTEGER ) ) { + if ( token.intvalue < 0 || token.intvalue >= MAX_MATCHVARIABLES ) { + SourceError( source, "can't have more than %d match variables\n", MAX_MATCHVARIABLES ); + FreeSource( source ); + BotFreeMatchPieces( firstpiece ); + return NULL; + } //end if + if ( lastwasvariable ) { + SourceError( source, "not allowed to have adjacent variables\n" ); + FreeSource( source ); + BotFreeMatchPieces( firstpiece ); + return NULL; + } //end if + lastwasvariable = qtrue; + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory( sizeof( bot_matchpiece_t ) ); + matchpiece->type = MT_VARIABLE; + matchpiece->variable = token.intvalue; + matchpiece->next = NULL; + if ( lastpiece ) { + lastpiece->next = matchpiece; + } else { firstpiece = matchpiece;} + lastpiece = matchpiece; + } //end if + else if ( token.type == TT_STRING ) { + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory( sizeof( bot_matchpiece_t ) ); + matchpiece->firststring = NULL; + matchpiece->type = MT_STRING; + matchpiece->variable = 0; + matchpiece->next = NULL; + if ( lastpiece ) { + lastpiece->next = matchpiece; + } else { firstpiece = matchpiece;} + lastpiece = matchpiece; + // + lastmatchstring = NULL; + emptystring = qfalse; + // + do + { + if ( matchpiece->firststring ) { + if ( !PC_ExpectTokenType( source, TT_STRING, 0, &token ) ) { + FreeSource( source ); + BotFreeMatchPieces( firstpiece ); + return NULL; + } //end if + } //end if + StripDoubleQuotes( token.string ); + matchstring = (bot_matchstring_t *) GetClearedHunkMemory( sizeof( bot_matchstring_t ) + strlen( token.string ) + 1 ); + matchstring->string = (char *) matchstring + sizeof( bot_matchstring_t ); + strcpy( matchstring->string, token.string ); + if ( !strlen( token.string ) ) { + emptystring = qtrue; + } + matchstring->next = NULL; + if ( lastmatchstring ) { + lastmatchstring->next = matchstring; + } else { matchpiece->firststring = matchstring;} + lastmatchstring = matchstring; + } while ( PC_CheckTokenString( source, "|" ) ); + //if there was no empty string found + if ( !emptystring ) { + lastwasvariable = qfalse; + } + } //end if + else + { + SourceError( source, "invalid token %s\n", token.string ); + FreeSource( source ); + BotFreeMatchPieces( firstpiece ); + return NULL; + } //end else + if ( PC_CheckTokenString( source, endtoken ) ) { + break; + } + if ( !PC_ExpectTokenString( source, "," ) ) { + FreeSource( source ); + BotFreeMatchPieces( firstpiece ); + return NULL; + } //end if + } //end while + return firstpiece; +} //end of the function BotLoadMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchTemplates( bot_matchtemplate_t *mt ) { + bot_matchtemplate_t *nextmt; + + for (; mt; mt = nextmt ) + { + nextmt = mt->next; + BotFreeMatchPieces( mt->first ); + FreeMemory( mt ); + } //end for +} //end of the function BotFreeMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchtemplate_t *BotLoadMatchTemplates( char *matchfile ) { + source_t *source; + token_t token; + bot_matchtemplate_t *matchtemplate, *matches, *lastmatch; + unsigned long int context; + + source = LoadSourceFile( matchfile ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", matchfile ); + return NULL; + } //end if + // + matches = NULL; //list with matches + lastmatch = NULL; //last match in the list + + while ( PC_ReadToken( source, &token ) ) + { + if ( token.type != TT_NUMBER || !( token.subtype & TT_INTEGER ) ) { + SourceError( source, "expected integer, found %s\n", token.string ); + BotFreeMatchTemplates( matches ); + FreeSource( source ); + return NULL; + } //end if + //the context + context = token.intvalue; + // + if ( !PC_ExpectTokenString( source, "{" ) ) { + BotFreeMatchTemplates( matches ); + FreeSource( source ); + return NULL; + } //end if + // + while ( PC_ReadToken( source, &token ) ) + { + if ( !strcmp( token.string, "}" ) ) { + break; + } + // + PC_UnreadLastToken( source ); + // + matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory( sizeof( bot_matchtemplate_t ) ); + matchtemplate->context = context; + matchtemplate->next = NULL; + //add the match template to the list + if ( lastmatch ) { + lastmatch->next = matchtemplate; + } else { matches = matchtemplate;} + lastmatch = matchtemplate; + //load the match template + matchtemplate->first = BotLoadMatchPieces( source, "=" ); + if ( !matchtemplate->first ) { + BotFreeMatchTemplates( matches ); + return NULL; + } //end if + //read the match type + if ( !PC_ExpectTokenString( source, "(" ) || + !PC_ExpectTokenType( source, TT_NUMBER, TT_INTEGER, &token ) ) { + BotFreeMatchTemplates( matches ); + FreeSource( source ); + return NULL; + } //end if + matchtemplate->type = token.intvalue; + //read the match subtype + if ( !PC_ExpectTokenString( source, "," ) || + !PC_ExpectTokenType( source, TT_NUMBER, TT_INTEGER, &token ) ) { + BotFreeMatchTemplates( matches ); + FreeSource( source ); + return NULL; + } //end if + matchtemplate->subtype = token.intvalue; + //read trailing punctuations + if ( !PC_ExpectTokenString( source, ")" ) || + !PC_ExpectTokenString( source, ";" ) ) { + BotFreeMatchTemplates( matches ); + FreeSource( source ); + return NULL; + } //end if + } //end while + } //end while + //free the source + FreeSource( source ); + botimport.Print( PRT_MESSAGE, "loaded %s\n", matchfile ); + // + //BotDumpMatchTemplates(matches); + // + return matches; +} //end of the function BotLoadMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringsMatch( bot_matchpiece_t *pieces, bot_match_t *match ) { + int lastvariable, index; + char *strptr, *newstrptr; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + //no last variable + lastvariable = -1; + //pointer to the string to compare the match string with + strptr = match->string; + //Log_Write("match: %s", strptr); + //compare the string with the current match string + for ( mp = pieces; mp; mp = mp->next ) + { + //if it is a piece of string + if ( mp->type == MT_STRING ) { + newstrptr = NULL; + for ( ms = mp->firststring; ms; ms = ms->next ) + { + if ( !strlen( ms->string ) ) { + newstrptr = strptr; + break; + } //end if + //Log_Write("MT_STRING: %s", mp->string); + index = StringContains( strptr, ms->string, qfalse ); + if ( index >= 0 ) { + newstrptr = strptr + index; + if ( lastvariable >= 0 ) { + match->variables[lastvariable].length = + newstrptr - match->variables[lastvariable].ptr; + lastvariable = -1; + break; + } //end if + else if ( index == 0 ) { + break; + } //end else + newstrptr = NULL; + } //end if + } //end for + if ( !newstrptr ) { + return qfalse; + } + strptr = newstrptr + strlen( ms->string ); + } //end if + //if it is a variable piece of string + else if ( mp->type == MT_VARIABLE ) { + //Log_Write("MT_VARIABLE"); + match->variables[mp->variable].ptr = strptr; + lastvariable = mp->variable; + } //end else if + } //end for + //if a match was found + if ( !mp && ( lastvariable >= 0 || !strlen( strptr ) ) ) { + //if the last piece was a variable string + if ( lastvariable >= 0 ) { + match->variables[lastvariable].length = strlen( match->variables[lastvariable].ptr ); + } //end if + return qtrue; + } //end if + return qfalse; +} //end of the function StringsMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindMatch( char *str, bot_match_t *match, unsigned long int context ) { + int i; + bot_matchtemplate_t *ms; + + strncpy( match->string, str, MAX_MESSAGE_SIZE ); + //remove any trailing enters + while ( strlen( match->string ) && + match->string[strlen( match->string ) - 1] == '\n' ) + { + match->string[strlen( match->string ) - 1] = '\0'; + } //end while + //compare the string with all the match strings + for ( ms = matchtemplates; ms; ms = ms->next ) + { + if ( !( ms->context & context ) ) { + continue; + } + //reset the match variable pointers + for ( i = 0; i < MAX_MATCHVARIABLES; i++ ) match->variables[i].ptr = NULL; + // + if ( StringsMatch( ms->first, match ) ) { + match->type = ms->type; + match->subtype = ms->subtype; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotFindMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMatchVariable( bot_match_t *match, int variable, char *buf, int size ) { + if ( variable < 0 || variable >= MAX_MATCHVARIABLES ) { + botimport.Print( PRT_FATAL, "BotMatchVariable: variable out of range\n" ); + strcpy( buf, "" ); + return; + } //end if + + if ( match->variables[variable].ptr ) { + if ( match->variables[variable].length < size ) { + size = match->variables[variable].length + 1; + } + strncpy( buf, match->variables[variable].ptr, size - 1 ); + buf[size - 1] = '\0'; + } //end if + else + { + strcpy( buf, "" ); + } //end else + return; +} //end of the function BotMatchVariable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotFindStringInList( bot_stringlist_t *list, char *string ) { + bot_stringlist_t *s; + + for ( s = list; s; s = s->next ) + { + if ( !strcmp( s->string, string ) ) { + return s; + } + } //end for + return NULL; +} //end of the function BotFindStringInList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotCheckChatMessageIntegrety( char *message, bot_stringlist_t *stringlist ) { + int i; + char *msgptr; + char temp[MAX_MESSAGE_SIZE]; + bot_stringlist_t *s; + + msgptr = message; + // + while ( *msgptr ) + { + if ( *msgptr == ESCAPE_CHAR ) { + msgptr++; + switch ( *msgptr ) + { + case 'v': //variable + { + //step over the 'v' + msgptr++; + while ( *msgptr && *msgptr != ESCAPE_CHAR ) msgptr++; + //step over the trailing escape char + if ( *msgptr ) { + msgptr++; + } + break; + } //end case + case 'r': //random + { + //step over the 'r' + msgptr++; + for ( i = 0; ( *msgptr && *msgptr != ESCAPE_CHAR ); i++ ) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if ( *msgptr ) { + msgptr++; + } + //find the random keyword + if ( !RandomString( temp ) ) { + if ( !BotFindStringInList( stringlist, temp ) ) { + Log_Write( "%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp ); + s = GetClearedMemory( sizeof( bot_stringlist_t ) + strlen( temp ) + 1 ); + s->string = (char *) s + sizeof( bot_stringlist_t ); + strcpy( s->string, temp ); + s->next = stringlist; + stringlist = s; + } //end if + } //end if + break; + } //end case + default: + { + botimport.Print( PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message ); + break; + } //end default + } //end switch + } //end if + else + { + msgptr++; + } //end else + } //end while + return stringlist; +} //end of the function BotCheckChatMessageIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckReplyChatIntegrety( bot_replychat_t *replychat ) { + bot_replychat_t *rp; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for ( rp = replychat; rp; rp = rp->next ) + { + for ( cm = rp->firstchatmessage; cm; cm = cm->next ) + { + stringlist = BotCheckChatMessageIntegrety( cm->chatmessage, stringlist ); + } //end for + } //end for + for ( s = stringlist; s; s = nexts ) + { + nexts = s->next; + FreeMemory( s ); + } //end for +} //end of the function BotCheckReplyChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckInitialChatIntegrety( bot_chat_t *chat ) { + bot_chattype_t *t; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for ( t = chat->types; t; t = t->next ) + { + for ( cm = t->firstchatmessage; cm; cm = cm->next ) + { + stringlist = BotCheckChatMessageIntegrety( cm->chatmessage, stringlist ); + } //end for + } //end for + for ( s = stringlist; s; s = nexts ) + { + nexts = s->next; + FreeMemory( s ); + } //end for +} //end of the function BotCheckInitialChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpReplyChat( bot_replychat_t *replychat ) { + FILE *fp; + bot_replychat_t *rp; + bot_replychatkey_t *key; + bot_chatmessage_t *cm; + bot_matchpiece_t *mp; + + fp = Log_FilePointer(); + if ( !fp ) { + return; + } + fprintf( fp, "BotDumpReplyChat:\n" ); + for ( rp = replychat; rp; rp = rp->next ) + { + fprintf( fp, "[" ); + for ( key = rp->keys; key; key = key->next ) + { + if ( key->flags & RCKFL_AND ) { + fprintf( fp, "&" ); + } else if ( key->flags & RCKFL_NOT ) { + fprintf( fp, "!" ); + } + // + if ( key->flags & RCKFL_NAME ) { + fprintf( fp, "name" ); + } else if ( key->flags & RCKFL_GENDERFEMALE ) { + fprintf( fp, "female" ); + } else if ( key->flags & RCKFL_GENDERMALE ) { + fprintf( fp, "male" ); + } else if ( key->flags & RCKFL_GENDERLESS ) { + fprintf( fp, "it" ); + } else if ( key->flags & RCKFL_VARIABLES ) { + fprintf( fp, "(" ); + for ( mp = key->match; mp; mp = mp->next ) + { + if ( mp->type == MT_STRING ) { + fprintf( fp, "\"%s\"", mp->firststring->string ); + } else { fprintf( fp, "%d", mp->variable );} + if ( mp->next ) { + fprintf( fp, ", " ); + } + } //end for + fprintf( fp, ")" ); + } //end if + else if ( key->flags & RCKFL_STRING ) { + fprintf( fp, "\"%s\"", key->string ); + } //end if + if ( key->next ) { + fprintf( fp, ", " ); + } else { fprintf( fp, "] = %1.0f\n", rp->priority );} + } //end for + fprintf( fp, "{\n" ); + for ( cm = rp->firstchatmessage; cm; cm = cm->next ) + { + fprintf( fp, "\t\"%s\";\n", cm->chatmessage ); + } //end for + fprintf( fp, "}\n" ); + } //end for +} //end of the function BotDumpReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeReplyChat( bot_replychat_t *replychat ) { + bot_replychat_t *rp, *nextrp; + bot_replychatkey_t *key, *nextkey; + bot_chatmessage_t *cm, *nextcm; + + for ( rp = replychat; rp; rp = nextrp ) + { + nextrp = rp->next; + for ( key = rp->keys; key; key = nextkey ) + { + nextkey = key->next; + if ( key->match ) { + BotFreeMatchPieces( key->match ); + } + if ( key->string ) { + FreeMemory( key->string ); + } + FreeMemory( key ); + } //end for + for ( cm = rp->firstchatmessage; cm; cm = nextcm ) + { + nextcm = cm->next; + FreeMemory( cm ); + } //end for + FreeMemory( rp ); + } //end for +} //end of the function BotFreeReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_replychat_t *BotLoadReplyChat( char *filename ) { + char chatmessagestring[MAX_MESSAGE_SIZE]; + char namebuffer[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chatmessage_t *chatmessage = NULL; + bot_replychat_t *replychat, *replychatlist; + bot_replychatkey_t *key; + + source = LoadSourceFile( filename ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", filename ); + return NULL; + } //end if + // + replychatlist = NULL; + // + while ( PC_ReadToken( source, &token ) ) + { + if ( strcmp( token.string, "[" ) ) { + SourceError( source, "expected [, found %s", token.string ); + BotFreeReplyChat( replychatlist ); + FreeSource( source ); + return NULL; + } //end if + // + replychat = GetClearedHunkMemory( sizeof( bot_replychat_t ) ); + replychat->keys = NULL; + replychat->next = replychatlist; + replychatlist = replychat; + //read the keys, there must be at least one key + do + { + //allocate a key + key = (bot_replychatkey_t *) GetClearedHunkMemory( sizeof( bot_replychatkey_t ) ); + key->flags = 0; + key->string = NULL; + key->match = NULL; + key->next = replychat->keys; + replychat->keys = key; + //check for MUST BE PRESENT and MUST BE ABSENT keys + if ( PC_CheckTokenString( source, "&" ) ) { + key->flags |= RCKFL_AND; + } else if ( PC_CheckTokenString( source, "!" ) ) { + key->flags |= RCKFL_NOT; + } + //special keys + if ( PC_CheckTokenString( source, "name" ) ) { + key->flags |= RCKFL_NAME; + } else if ( PC_CheckTokenString( source, "female" ) ) { + key->flags |= RCKFL_GENDERFEMALE; + } else if ( PC_CheckTokenString( source, "male" ) ) { + key->flags |= RCKFL_GENDERMALE; + } else if ( PC_CheckTokenString( source, "it" ) ) { + key->flags |= RCKFL_GENDERLESS; + } else if ( PC_CheckTokenString( source, "(" ) ) { //match key + key->flags |= RCKFL_VARIABLES; + key->match = BotLoadMatchPieces( source, ")" ); + if ( !key->match ) { + BotFreeReplyChat( replychatlist ); + return NULL; + } //end if + } //end else if + else if ( PC_CheckTokenString( source, "<" ) ) { //bot names + key->flags |= RCKFL_BOTNAMES; + strcpy( namebuffer, "" ); + do + { + if ( !PC_ExpectTokenType( source, TT_STRING, 0, &token ) ) { + BotFreeReplyChat( replychatlist ); + FreeSource( source ); + return NULL; + } //end if + StripDoubleQuotes( token.string ); + if ( strlen( namebuffer ) ) { + strcat( namebuffer, "\\" ); + } + strcat( namebuffer, token.string ); + } while ( PC_CheckTokenString( source, "," ) ); + if ( !PC_ExpectTokenString( source, ">" ) ) { + BotFreeReplyChat( replychatlist ); + FreeSource( source ); + return NULL; + } //end if + key->string = (char *) GetClearedHunkMemory( strlen( namebuffer ) + 1 ); + strcpy( key->string, namebuffer ); + } //end else if + else //normal string key + { + key->flags |= RCKFL_STRING; + if ( !PC_ExpectTokenType( source, TT_STRING, 0, &token ) ) { + BotFreeReplyChat( replychatlist ); + FreeSource( source ); + return NULL; + } //end if + StripDoubleQuotes( token.string ); + key->string = (char *) GetClearedHunkMemory( strlen( token.string ) + 1 ); + strcpy( key->string, token.string ); + } //end else + // + PC_CheckTokenString( source, "," ); + } while ( !PC_CheckTokenString( source, "]" ) ); + //read the = sign and the priority + if ( !PC_ExpectTokenString( source, "=" ) || + !PC_ExpectTokenType( source, TT_NUMBER, 0, &token ) ) { + BotFreeReplyChat( replychatlist ); + FreeSource( source ); + return NULL; + } //end if + replychat->priority = token.floatvalue; + //read the leading { + if ( !PC_ExpectTokenString( source, "{" ) ) { + BotFreeReplyChat( replychatlist ); + FreeSource( source ); + return NULL; + } //end if + replychat->numchatmessages = 0; + //while the trailing } is not found + while ( !PC_CheckTokenString( source, "}" ) ) + { + if ( !BotLoadChatMessage( source, chatmessagestring ) ) { + BotFreeReplyChat( replychatlist ); + FreeSource( source ); + return NULL; + } //end if + chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory( sizeof( bot_chatmessage_t ) + strlen( chatmessagestring ) + 1 ); + chatmessage->chatmessage = (char *) chatmessage + sizeof( bot_chatmessage_t ); + strcpy( chatmessage->chatmessage, chatmessagestring ); + chatmessage->time = -2 * CHATMESSAGE_RECENTTIME; + chatmessage->next = replychat->firstchatmessage; + //add the chat message to the reply chat + replychat->firstchatmessage = chatmessage; + replychat->numchatmessages++; + } //end while + } //end while + FreeSource( source ); + botimport.Print( PRT_MESSAGE, "loaded %s\n", filename ); + // + //BotDumpReplyChat(replychatlist); + if ( bot_developer ) { + BotCheckReplyChatIntegrety( replychatlist ); + } //end if + // + if ( !replychatlist ) { + botimport.Print( PRT_MESSAGE, "no rchats\n" ); + } + // + return replychatlist; +} //end of the function BotLoadReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpInitialChat( bot_chat_t *chat ) { + bot_chattype_t *t; + bot_chatmessage_t *m; + + Log_Write( "{" ); + for ( t = chat->types; t; t = t->next ) + { + Log_Write( " type \"%s\"", t->name ); + Log_Write( " {" ); + Log_Write( " numchatmessages = %d", t->numchatmessages ); + for ( m = t->firstchatmessage; m; m = m->next ) + { + Log_Write( " \"%s\"", m->chatmessage ); + } //end for + Log_Write( " }" ); + } //end for + Log_Write( "}" ); +} //end of the function BotDumpInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_chat_t *BotLoadInitialChat( char *chatfile, char *chatname ) { + int pass, foundchat, indent, size; + char *ptr = NULL; + char chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chat_t *chat = NULL; + bot_chattype_t *chattype = NULL; + bot_chatmessage_t *chatmessage = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + // + size = 0; + foundchat = qfalse; + //a bot chat is parsed in two phases + for ( pass = 0; pass < 2; pass++ ) + { + //allocate memory + if ( pass && size ) { + ptr = (char *) GetClearedMemory( size ); + } + //load the source file + source = LoadSourceFile( chatfile ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", chatfile ); + return NULL; + } //end if + //chat structure + if ( pass ) { + chat = (bot_chat_t *) ptr; + ptr += sizeof( bot_chat_t ); + } //end if + size = sizeof( bot_chat_t ); + // + while ( PC_ReadToken( source, &token ) ) + { + if ( !strcmp( token.string, "chat" ) ) { + if ( !PC_ExpectTokenType( source, TT_STRING, 0, &token ) ) { + FreeSource( source ); + return NULL; + } //end if + StripDoubleQuotes( token.string ); + //after the chat name we expect a opening brace + if ( !PC_ExpectTokenString( source, "{" ) ) { + FreeSource( source ); + return NULL; + } //end if + //if the chat name is found + if ( !Q_stricmp( token.string, chatname ) ) { + foundchat = qtrue; + //read the chat types + while ( 1 ) + { + if ( !PC_ExpectAnyToken( source, &token ) ) { + FreeSource( source ); + return NULL; + } //end if + if ( !strcmp( token.string, "}" ) ) { + break; + } + if ( strcmp( token.string, "type" ) ) { + SourceError( source, "expected type found %s\n", token.string ); + FreeSource( source ); + return NULL; + } //end if + //expect the chat type name + if ( !PC_ExpectTokenType( source, TT_STRING, 0, &token ) || + !PC_ExpectTokenString( source, "{" ) ) { + FreeSource( source ); + return NULL; + } //end if + StripDoubleQuotes( token.string ); + if ( pass ) { + chattype = (bot_chattype_t *) ptr; + strncpy( chattype->name, token.string, MAX_CHATTYPE_NAME ); + chattype->firstchatmessage = NULL; + //add the chat type to the chat + chattype->next = chat->types; + chat->types = chattype; + // + ptr += sizeof( bot_chattype_t ); + } //end if + size += sizeof( bot_chattype_t ); + //read the chat messages + while ( !PC_CheckTokenString( source, "}" ) ) + { + if ( !BotLoadChatMessage( source, chatmessagestring ) ) { + FreeSource( source ); + return NULL; + } //end if + if ( pass ) { + chatmessage = (bot_chatmessage_t *) ptr; + chatmessage->time = -2 * CHATMESSAGE_RECENTTIME; + //put the chat message in the list + chatmessage->next = chattype->firstchatmessage; + chattype->firstchatmessage = chatmessage; + //store the chat message + ptr += sizeof( bot_chatmessage_t ); + chatmessage->chatmessage = ptr; + strcpy( chatmessage->chatmessage, chatmessagestring ); + ptr += strlen( chatmessagestring ) + 1; + //the number of chat messages increased + chattype->numchatmessages++; + } //end if + size += sizeof( bot_chatmessage_t ) + strlen( chatmessagestring ) + 1; + } //end if + } //end while + } //end if + else //skip the bot chat + { + indent = 1; + while ( indent ) + { + if ( !PC_ExpectAnyToken( source, &token ) ) { + FreeSource( source ); + return NULL; + } //end if + if ( !strcmp( token.string, "{" ) ) { + indent++; + } else if ( !strcmp( token.string, "}" ) ) { + indent--; + } + } //end while + } //end else + } //end if + else + { + SourceError( source, "unknown definition %s\n", token.string ); + FreeSource( source ); + return NULL; + } //end else + } //end while + //free the source + FreeSource( source ); + //if the requested character is not found + if ( !foundchat ) { + botimport.Print( PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile ); + return NULL; + } //end if + } //end for + // + botimport.Print( PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile ); + // + //BotDumpInitialChat(chat); + if ( bot_developer ) { + BotCheckInitialChatIntegrety( chat ); + } //end if +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime ); +#endif //DEBUG + //character was read succesfully + return chat; +} //end of the function BotLoadInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeChatFile( int chatstate ) { + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return; + } + if ( cs->chat ) { + FreeMemory( cs->chat ); + } + cs->chat = NULL; +} //end of the function BotFreeChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatFile( int chatstate, char *chatfile, char *chatname ) { + bot_chatstate_t *cs; + int n, avail = 0; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return BLERR_CANNOTLOADICHAT; + } + BotFreeChatFile( chatstate ); + + if ( !LibVarGetValue( "bot_reloadcharacters" ) ) { + avail = -1; + for ( n = 0; n < MAX_CLIENTS; n++ ) { + if ( !ichatdata[n].inuse ) { + if ( avail == -1 ) { + avail = n; + } + continue; + } + if ( strcmp( chatfile, ichatdata[n].filename ) != 0 ) { + continue; + } + if ( strcmp( chatname, ichatdata[n].chatname ) != 0 ) { + continue; + } + cs->chat = ichatdata[n].chat; + // botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile ); + return BLERR_NOERROR; + } + + if ( avail == -1 ) { + botimport.Print( PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile ); + return BLERR_CANNOTLOADICHAT; + } + } + + cs->chat = BotLoadInitialChat( chatfile, chatname ); + if ( !cs->chat ) { + botimport.Print( PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile ); + return BLERR_CANNOTLOADICHAT; + } //end if + if ( !LibVarGetValue( "bot_reloadcharacters" ) ) { + ichatdata[avail].chat = cs->chat; + Q_strncpyz( ichatdata[avail].chatname, chatname, sizeof( ichatdata[avail].chatname ) ); + Q_strncpyz( ichatdata[avail].filename, chatfile, sizeof( ichatdata[avail].filename ) ); + ichatdata[avail].inuse = qtrue; + } //end if + + return BLERR_NOERROR; +} //end of the function BotLoadChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotExpandChatMessage( char *outmessage, char *message, unsigned long mcontext, + bot_matchvariable_t *variables, unsigned long vcontext, int reply ) { + int num, len, i, expansion; + char *outputbuf, *ptr, *msgptr; + char temp[MAX_MESSAGE_SIZE]; + + expansion = qfalse; + msgptr = message; + outputbuf = outmessage; + len = 0; + // + while ( *msgptr ) + { + if ( *msgptr == ESCAPE_CHAR ) { + msgptr++; + switch ( *msgptr ) + { + case 'v': //variable + { + msgptr++; + num = 0; + while ( *msgptr && *msgptr != ESCAPE_CHAR ) + { + num = num * 10 + ( *msgptr++ ) - '0'; + } //end while + //step over the trailing escape char + if ( *msgptr ) { + msgptr++; + } + if ( num > MAX_MATCHVARIABLES ) { + botimport.Print( PRT_ERROR, "BotConstructChat: message %s variable %d out of range\n", message, num ); + return qfalse; + } //end if + ptr = variables[num].ptr; + if ( ptr ) { + for ( i = 0; i < variables[num].length; i++ ) + { + temp[i] = ptr[i]; + } //end for + temp[i] = 0; + //if it's a reply message + if ( reply ) { + //replace the reply synonyms in the variables + BotReplaceReplySynonyms( temp, vcontext ); + } //end if + else + { + //replace synonyms in the variable context + BotReplaceSynonyms( temp, vcontext ); + } //end else + // + if ( len + strlen( temp ) >= MAX_MESSAGE_SIZE ) { + botimport.Print( PRT_ERROR, "BotConstructChat: message %s too long\n", message ); + return qfalse; + } //end if + strcpy( &outputbuf[len], temp ); + len += strlen( temp ); + } //end if + break; + } //end case + case 'r': //random + { + msgptr++; + for ( i = 0; ( *msgptr && *msgptr != ESCAPE_CHAR ); i++ ) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if ( *msgptr ) { + msgptr++; + } + //find the random keyword + ptr = RandomString( temp ); + if ( !ptr ) { + botimport.Print( PRT_ERROR, "BotConstructChat: unknown random string %s\n", temp ); + return qfalse; + } //end if + if ( len + strlen( ptr ) >= MAX_MESSAGE_SIZE ) { + botimport.Print( PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message ); + return qfalse; + } //end if + strcpy( &outputbuf[len], ptr ); + len += strlen( ptr ); + expansion = qtrue; + break; + } //end case + default: + { + botimport.Print( PRT_FATAL, "BotConstructChat: message \"%s\" invalid escape char\n", message ); + break; + } //end default + } //end switch + } //end if + else + { + outputbuf[len++] = *msgptr++; + if ( len >= MAX_MESSAGE_SIZE ) { + botimport.Print( PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message ); + break; + } //end if + } //end else + } //end while + outputbuf[len] = '\0'; + //replace synonyms weighted in the message context + BotReplaceWeightedSynonyms( outputbuf, mcontext ); + //return true if a random was expanded + return expansion; +} //end of the function BotExpandChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotConstructChatMessage( bot_chatstate_t *chatstate, char *message, unsigned long mcontext, + bot_matchvariable_t *variables, unsigned long vcontext, int reply ) { + int i; + char srcmessage[MAX_MESSAGE_SIZE]; + + strcpy( srcmessage, message ); + for ( i = 0; i < 10; i++ ) + { + if ( !BotExpandChatMessage( chatstate->chatmessage, srcmessage, mcontext, variables, vcontext, reply ) ) { + break; + } //end if + strcpy( srcmessage, chatstate->chatmessage ); + } //end for + if ( i >= 10 ) { + botimport.Print( PRT_WARNING, "too many expansions in chat message\n" ); + botimport.Print( PRT_WARNING, "%s\n", chatstate->chatmessage ); + } //end if +} //end of the function BotConstructChatMessage +//=========================================================================== +// randomly chooses one of the chat message of the given type +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *BotChooseInitialChatMessage( bot_chatstate_t *cs, char *type ) { + int n, numchatmessages; + float besttime; + bot_chattype_t *t; + bot_chatmessage_t *m, *bestchatmessage; + bot_chat_t *chat; + + chat = cs->chat; + for ( t = chat->types; t; t = t->next ) + { + if ( !Q_stricmp( t->name, type ) ) { + numchatmessages = 0; + for ( m = t->firstchatmessage; m; m = m->next ) + { + if ( m->time > AAS_Time() ) { + continue; + } + numchatmessages++; + } //end if + //if all chat messages have been used recently + if ( numchatmessages <= 0 ) { + besttime = 0; + bestchatmessage = NULL; + for ( m = t->firstchatmessage; m; m = m->next ) + { + if ( !besttime || m->time < besttime ) { + bestchatmessage = m; + besttime = m->time; + } //end if + } //end for + if ( bestchatmessage ) { + return bestchatmessage->chatmessage; + } + } //end if + else //choose a chat message randomly + { + n = random() * numchatmessages; + for ( m = t->firstchatmessage; m; m = m->next ) + { + if ( m->time > AAS_Time() ) { + continue; + } + if ( --n < 0 ) { + m->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + return m->chatmessage; + } //end if + } //end for + } //end else + return NULL; + } //end if + } //end for + return NULL; +} //end of the function BotChooseInitialChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumInitialChats( int chatstate, char *type ) { + bot_chatstate_t *cs; + bot_chattype_t *t; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return 0; + } + + for ( t = cs->chat->types; t; t = t->next ) + { + if ( !Q_stricmp( t->name, type ) ) { + if ( LibVarGetValue( "bot_testichat" ) ) { + botimport.Print( PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages ); + botimport.Print( PRT_MESSAGE, "-------------------\n" ); + } + return t->numchatmessages; + } //end if + } //end for + return 0; +} //end of the function BotNumInitialChats +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitialChat( int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ) { + char *message; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return; + } + //if no chat file is loaded + if ( !cs->chat ) { + return; + } + //choose a chat message randomly of the given type + message = BotChooseInitialChatMessage( cs, type ); + //if there's no message of the given type + if ( !message ) { +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "no chat messages of type %s\n", type ); +#endif //DEBUG + return; + } //end if + // + memset( variables, 0, sizeof( variables ) ); + if ( var0 ) { + variables[0].ptr = var0; + variables[0].length = strlen( var0 ); + } + if ( var1 ) { + variables[1].ptr = var1; + variables[1].length = strlen( var1 ); + } + if ( var2 ) { + variables[2].ptr = var2; + variables[2].length = strlen( var2 ); + } + if ( var3 ) { + variables[3].ptr = var3; + variables[3].length = strlen( var3 ); + } + if ( var4 ) { + variables[4].ptr = var4; + variables[4].length = strlen( var4 ); + } + if ( var5 ) { + variables[5].ptr = var5; + variables[5].length = strlen( var5 ); + } + if ( var6 ) { + variables[6].ptr = var6; + variables[6].length = strlen( var6 ); + } + if ( var7 ) { + variables[7].ptr = var7; + variables[7].length = strlen( var7 ); + } + // + BotConstructChatMessage( cs, message, mcontext, variables, 0, qfalse ); +} //end of the function BotInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPrintReplyChatKeys( bot_replychat_t *replychat ) { + bot_replychatkey_t *key; + bot_matchpiece_t *mp; + + botimport.Print( PRT_MESSAGE, "[" ); + for ( key = replychat->keys; key; key = key->next ) + { + if ( key->flags & RCKFL_AND ) { + botimport.Print( PRT_MESSAGE, "&" ); + } else if ( key->flags & RCKFL_NOT ) { + botimport.Print( PRT_MESSAGE, "!" ); + } + // + if ( key->flags & RCKFL_NAME ) { + botimport.Print( PRT_MESSAGE, "name" ); + } else if ( key->flags & RCKFL_GENDERFEMALE ) { + botimport.Print( PRT_MESSAGE, "female" ); + } else if ( key->flags & RCKFL_GENDERMALE ) { + botimport.Print( PRT_MESSAGE, "male" ); + } else if ( key->flags & RCKFL_GENDERLESS ) { + botimport.Print( PRT_MESSAGE, "it" ); + } else if ( key->flags & RCKFL_VARIABLES ) { + botimport.Print( PRT_MESSAGE, "(" ); + for ( mp = key->match; mp; mp = mp->next ) + { + if ( mp->type == MT_STRING ) { + botimport.Print( PRT_MESSAGE, "\"%s\"", mp->firststring->string ); + } else { botimport.Print( PRT_MESSAGE, "%d", mp->variable );} + if ( mp->next ) { + botimport.Print( PRT_MESSAGE, ", " ); + } + } //end for + botimport.Print( PRT_MESSAGE, ")" ); + } //end if + else if ( key->flags & RCKFL_STRING ) { + botimport.Print( PRT_MESSAGE, "\"%s\"", key->string ); + } //end if + if ( key->next ) { + botimport.Print( PRT_MESSAGE, ", " ); + } else { botimport.Print( PRT_MESSAGE, "] = %1.0f\n", replychat->priority );} + } //end for + botimport.Print( PRT_MESSAGE, "{\n" ); +} //end of the function BotPrintReplyChatKeys +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReplyChat( int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ) { + bot_replychat_t *rchat, *bestrchat; + bot_replychatkey_t *key; + bot_chatmessage_t *m, *bestchatmessage; + bot_match_t match, bestmatch; + int bestpriority, num, found, res, numchatmessages; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return qfalse; + } + memset( &match, 0, sizeof( bot_match_t ) ); + strcpy( match.string, message ); + bestpriority = -1; + bestchatmessage = NULL; + bestrchat = NULL; + //go through all the reply chats + for ( rchat = replychats; rchat; rchat = rchat->next ) + { + found = qfalse; + for ( key = rchat->keys; key; key = key->next ) + { + res = qfalse; + //get the match result + if ( key->flags & RCKFL_NAME ) { + res = ( StringContains( message, cs->name, qfalse ) != -1 ); + } else if ( key->flags & RCKFL_BOTNAMES ) { + res = ( StringContains( key->string, cs->name, qfalse ) != -1 ); + } else if ( key->flags & RCKFL_GENDERFEMALE ) { + res = ( cs->gender == CHAT_GENDERFEMALE ); + } else if ( key->flags & RCKFL_GENDERMALE ) { + res = ( cs->gender == CHAT_GENDERMALE ); + } else if ( key->flags & RCKFL_GENDERLESS ) { + res = ( cs->gender == CHAT_GENDERLESS ); + } else if ( key->flags & RCKFL_VARIABLES ) { + res = StringsMatch( key->match, &match ); + } else if ( key->flags & RCKFL_STRING ) { + res = ( StringContainsWord( message, key->string, qfalse ) != NULL ); + } + //if the key must be present + if ( key->flags & RCKFL_AND ) { + if ( !res ) { + found = qfalse; + break; + } //end if + //botnames is an exception + //if (!(key->flags & RCKFL_BOTNAMES)) found = qtrue; + } //end else if + //if the key must be absent + else if ( key->flags & RCKFL_NOT ) { + if ( res ) { + found = qfalse; + break; + } //end if + } //end if + else if ( res ) { + found = qtrue; + } //end else + } //end for + // + if ( found ) { + if ( rchat->priority > bestpriority ) { + numchatmessages = 0; + for ( m = rchat->firstchatmessage; m; m = m->next ) + { + if ( m->time > AAS_Time() ) { + continue; + } + numchatmessages++; + } //end if + num = random() * numchatmessages; + for ( m = rchat->firstchatmessage; m; m = m->next ) + { + if ( --num < 0 ) { + break; + } + if ( m->time > AAS_Time() ) { + continue; + } + } //end for + //if the reply chat has a message + if ( m ) { + memcpy( &bestmatch, &match, sizeof( bot_match_t ) ); + bestchatmessage = m; + bestrchat = rchat; + bestpriority = rchat->priority; + } //end if + } //end if + } //end if + } //end for + if ( bestchatmessage ) { + if ( var0 ) { + bestmatch.variables[0].ptr = var0; + bestmatch.variables[0].length = strlen( var0 ); + } + if ( var1 ) { + bestmatch.variables[1].ptr = var1; + bestmatch.variables[1].length = strlen( var1 ); + } + if ( var2 ) { + bestmatch.variables[2].ptr = var2; + bestmatch.variables[2].length = strlen( var2 ); + } + if ( var3 ) { + bestmatch.variables[3].ptr = var3; + bestmatch.variables[3].length = strlen( var3 ); + } + if ( var4 ) { + bestmatch.variables[4].ptr = var4; + bestmatch.variables[4].length = strlen( var4 ); + } + if ( var5 ) { + bestmatch.variables[5].ptr = var5; + bestmatch.variables[5].length = strlen( var5 ); + } + if ( var6 ) { + bestmatch.variables[6].ptr = var6; + bestmatch.variables[6].length = strlen( var6 ); + } + if ( var7 ) { + bestmatch.variables[7].ptr = var7; + bestmatch.variables[7].length = strlen( var7 ); + } + if ( LibVarGetValue( "bot_testrchat" ) ) { + for ( m = bestrchat->firstchatmessage; m; m = m->next ) + { + BotConstructChatMessage( cs, m->chatmessage, mcontext, bestmatch.variables, vcontext, qtrue ); + BotRemoveTildes( cs->chatmessage ); + botimport.Print( PRT_MESSAGE, "%s\n", cs->chatmessage ); + } //end if + } //end if + else + { + bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + BotConstructChatMessage( cs, bestchatmessage->chatmessage, mcontext, bestmatch.variables, vcontext, qtrue ); + } //end else + return qtrue; + } //end if + return qfalse; +} //end of the function BotReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChatLength( int chatstate ) { + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return 0; + } + return strlen( cs->chatmessage ); +} //end of the function BotChatLength +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEnterChat( int chatstate, int client, int sendto ) { + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return; + } + + if ( strlen( cs->chatmessage ) ) { + BotRemoveTildes( cs->chatmessage ); + if ( LibVarGetValue( "bot_testichat" ) ) { + botimport.Print( PRT_MESSAGE, "%s\n", cs->chatmessage ); + } else { + if ( sendto == CHAT_TEAM ) { + EA_SayTeam( client, cs->chatmessage ); + } else { EA_Say( client, cs->chatmessage );} + } + //clear the chat message from the state + strcpy( cs->chatmessage, "" ); + } //end if +} //end of the function BotEnterChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetChatMessage( int chatstate, char *buf, int size ) { + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return; + } + + BotRemoveTildes( cs->chatmessage ); + strncpy( buf, cs->chatmessage, size - 1 ); + buf[size - 1] = '\0'; + //clear the chat message from the state + strcpy( cs->chatmessage, "" ); +} //end of the function BotGetChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatGender( int chatstate, int gender ) { + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return; + } + switch ( gender ) + { + case CHAT_GENDERFEMALE: cs->gender = CHAT_GENDERFEMALE; break; + case CHAT_GENDERMALE: cs->gender = CHAT_GENDERMALE; break; + default: cs->gender = CHAT_GENDERLESS; break; + } //end switch +} //end of the function BotSetChatGender +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatName( int chatstate, char *name ) { + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle( chatstate ); + if ( !cs ) { + return; + } + memset( cs->name, 0, sizeof( cs->name ) ); + strncpy( cs->name, name, sizeof( cs->name ) ); + cs->name[sizeof( cs->name ) - 1] = '\0'; +} //end of the function BotSetChatName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetChatAI( void ) { + bot_replychat_t *rchat; + bot_chatmessage_t *m; + + for ( rchat = replychats; rchat; rchat = rchat->next ) + { + for ( m = rchat->firstchatmessage; m; m = m->next ) + { + m->time = 0; + } //end for + } //end for +} //end of the function BotResetChatAI +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocChatState( void ) { + int i; + + for ( i = 1; i <= MAX_CLIENTS; i++ ) + { + if ( !botchatstates[i] ) { + botchatstates[i] = GetClearedMemory( sizeof( bot_chatstate_t ) ); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocChatState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeChatState( int handle ) { + bot_chatstate_t *cs; + bot_consolemessage_t m; + int h; + + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "chat state handle %d out of range\n", handle ); + return; + } //end if + if ( !botchatstates[handle] ) { + botimport.Print( PRT_FATAL, "invalid chat state %d\n", handle ); + return; + } //end if + cs = botchatstates[handle]; + if ( LibVarGetValue( "bot_reloadcharacters" ) ) { + BotFreeChatFile( handle ); + } //end if + //free all the console messages left in the chat state + for ( h = BotNextConsoleMessage( handle, &m ); h; h = BotNextConsoleMessage( handle, &m ) ) + { + //remove the console message + BotRemoveConsoleMessage( handle, h ); + } //end for + FreeMemory( botchatstates[handle] ); + botchatstates[handle] = NULL; +} //end of the function BotFreeChatState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupChatAI( void ) { + char *file; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + file = LibVarString( "synfile", "syn.c" ); + synonyms = BotLoadSynonyms( file ); + file = LibVarString( "rndfile", "rnd.c" ); + randomstrings = BotLoadRandomStrings( file ); + file = LibVarString( "matchfile", "match.c" ); + matchtemplates = BotLoadMatchTemplates( file ); + // + if ( !LibVarValue( "nochat", "0" ) ) { + file = LibVarString( "rchatfile", "rchat.c" ); + replychats = BotLoadReplyChat( file ); + } //end if + + InitConsoleMessageHeap(); + +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime ); +#endif //DEBUG + return BLERR_NOERROR; +} //end of the function BotSetupChatAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownChatAI( void ) { + int i; + + //free all remaining chat states + for ( i = 0; i < MAX_CLIENTS; i++ ) + { + if ( botchatstates[i] ) { + BotFreeChatState( i ); + } //end if + } //end for + //free all cached chats + for ( i = 0; i < MAX_CLIENTS; i++ ) + { + if ( ichatdata[i].inuse ) { + FreeMemory( ichatdata[i].chat ); + ichatdata[i].inuse = qfalse; + } //end if + } //end for + if ( consolemessageheap ) { + FreeMemory( consolemessageheap ); + } + consolemessageheap = NULL; + if ( matchtemplates ) { + BotFreeMatchTemplates( matchtemplates ); + } + matchtemplates = NULL; + if ( randomstrings ) { + FreeMemory( randomstrings ); + } + randomstrings = NULL; + if ( synonyms ) { + FreeMemory( synonyms ); + } + synonyms = NULL; + if ( replychats ) { + BotFreeReplyChat( replychats ); + } + replychats = NULL; +} //end of the function BotShutdownChatAI diff --git a/src/botlib/be_ai_gen.c b/src/botlib/be_ai_gen.c new file mode 100644 index 0000000..bf6b4ac --- /dev/null +++ b/src/botlib/be_ai_gen.c @@ -0,0 +1,151 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_gen.c + * + * desc: genetic selection + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ai_gen.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticSelection( int numranks, float *rankings ) { + float sum, select; + int i, index; + + sum = 0; + for ( i = 0; i < numranks; i++ ) + { + if ( rankings[i] < 0 ) { + continue; + } + sum += rankings[i]; + } //end for + if ( sum > 0 ) { + //select a bot where the ones with the higest rankings have + //the highest chance of being selected + select = random() * sum; + for ( i = 0; i < numranks; i++ ) + { + if ( rankings[i] < 0 ) { + continue; + } + sum -= rankings[i]; + if ( sum <= 0 ) { + return i; + } + } //end for + } //end if + //select a bot randomly + index = random() * numranks; + for ( i = 0; i < numranks; i++ ) + { + if ( rankings[index] >= 0 ) { + return index; + } + index = ( index + 1 ) % numranks; + } //end for + return 0; +} //end of the function GeneticSelection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticParentsAndChildSelection( int numranks, float *ranks, int *parent1, int *parent2, int *child ) { + float rankings[256], max; + int i; + + if ( numranks > 256 ) { + botimport.Print( PRT_WARNING, "GeneticParentsAndChildSelection: too many bots\n" ); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + for ( max = 0, i = 0; i < numranks; i++ ) + { + if ( ranks[i] < 0 ) { + continue; + } + max++; + } //end for + if ( max < 3 ) { + botimport.Print( PRT_WARNING, "GeneticParentsAndChildSelection: too few valid bots\n" ); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + memcpy( rankings, ranks, sizeof( float ) * numranks ); + //select first parent + *parent1 = GeneticSelection( numranks, rankings ); + rankings[*parent1] = -1; + //select second parent + *parent2 = GeneticSelection( numranks, rankings ); + rankings[*parent2] = -1; + //reverse the rankings + max = 0; + for ( i = 0; i < numranks; i++ ) + { + if ( rankings[i] < 0 ) { + continue; + } + if ( rankings[i] > max ) { + max = rankings[i]; + } + } //end for + for ( i = 0; i < numranks; i++ ) + { + if ( rankings[i] < 0 ) { + continue; + } + rankings[i] = max - rankings[i]; + } //end for + //select child + *child = GeneticSelection( numranks, rankings ); + return qtrue; +} //end of the function GeneticParentsAndChildSelection diff --git a/src/botlib/be_ai_goal.c b/src/botlib/be_ai_goal.c new file mode 100644 index 0000000..e6be65f --- /dev/null +++ b/src/botlib/be_ai_goal.c @@ -0,0 +1,1630 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_goal.c + * + * desc: goal AI + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_libvar.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" + +//#define DEBUG_AI_GOAL +#ifdef RANDOMIZE +#define UNDECIDEDFUZZY +#endif //RANDOMIZE +#define DROPPEDWEIGHT +//avoid goal time +#define AVOID_TIME 30 +//avoid dropped goal time +#define AVOIDDROPPED_TIME 5 +// +#define TRAVELTIME_SCALE 0.01 + +//location in the map "target_location" +typedef struct maplocation_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + struct maplocation_s *next; +} maplocation_t; + +//camp spots "info_camp" +typedef struct campspot_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + float range; + float weight; + float wait; + float random; + struct campspot_s *next; +} campspot_t; + +//FIXME: these are game specific +typedef enum { + GT_FFA, // free for all + GT_TOURNAMENT, // one on one tournament + GT_SINGLE_PLAYER, // single player tournament + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_CTF, // capture the flag + + GT_MAX_GAME_TYPE +} gametype_t; + +// Rafael gameskill +typedef enum { + GSKILL_EASY, + GSKILL_MEDIUM, + GSKILL_MEDIUMHARD, // normal default level + GSKILL_HARD +} gameskill_t; + +typedef struct levelitem_s +{ + int number; //number of the level item + int iteminfo; //index into the item info + int notteam; //true if not in teamplay + int notfree; //true if not in ffa + int notsingle; //true if not in single + vec3_t origin; //origin of the item + int goalareanum; //area the item is in + vec3_t goalorigin; //goal origin within the area + int entitynum; //entity number + float timeout; //item is removed after this time + struct levelitem_s *prev, *next; +} levelitem_t; + +typedef struct iteminfo_s +{ + char classname[32]; //classname of the item + char name[MAX_STRINGFIELD]; //name of the item + char model[MAX_STRINGFIELD]; //model of the item + int modelindex; //model index + int type; //item type + int index; //index in the inventory + float respawntime; //respawn time + vec3_t mins; //mins of the item + vec3_t maxs; //maxs of the item + int number; //number of the item info +} iteminfo_t; + +#define ITEMINFO_OFS( x ) (int)&( ( (iteminfo_t *)0 )->x ) + +fielddef_t iteminfo_fields[] = +{ + {"name", ITEMINFO_OFS( name ), FT_STRING}, + {"model", ITEMINFO_OFS( model ), FT_STRING}, + {"modelindex", ITEMINFO_OFS( modelindex ), FT_INT}, + {"type", ITEMINFO_OFS( type ), FT_INT}, + {"index", ITEMINFO_OFS( index ), FT_INT}, + {"respawntime", ITEMINFO_OFS( respawntime ), FT_FLOAT}, + {"mins", ITEMINFO_OFS( mins ), FT_FLOAT | FT_ARRAY, 3}, + {"maxs", ITEMINFO_OFS( maxs ), FT_FLOAT | FT_ARRAY, 3}, + {0, 0, 0} +}; + +structdef_t iteminfo_struct = +{ + sizeof( iteminfo_t ), iteminfo_fields +}; + +typedef struct itemconfig_s +{ + int numiteminfo; + iteminfo_t *iteminfo; +} itemconfig_t; + +//goal state +typedef struct bot_goalstate_s +{ + struct weightconfig_s *itemweightconfig; //weight config + int *itemweightindex; //index from item to weight + // + int client; //client using this goal state + int lastreachabilityarea; //last area with reachabilities the bot was in + // + bot_goal_t goalstack[MAX_GOALSTACK]; //goal stack + int goalstacktop; //the top of the goal stack + // + int avoidgoals[MAX_AVOIDGOALS]; //goals to avoid + float avoidgoaltimes[MAX_AVOIDGOALS]; //times to avoid the goals +} bot_goalstate_t; + +bot_goalstate_t *botgoalstates[MAX_CLIENTS + 1]; +//item configuration +itemconfig_t *itemconfig = NULL; +//level items +levelitem_t *levelitemheap = NULL; +levelitem_t *freelevelitems = NULL; +levelitem_t *levelitems = NULL; +int numlevelitems = 0; +//map locations +maplocation_t *maplocations = NULL; +//camp spots +campspot_t *campspots = NULL; +//the game type +int g_gametype; + +// Rafael gameskill +int g_gameskill; +// done + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_goalstate_t *BotGoalStateFromHandle( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "goal state handle %d out of range\n", handle ); + return NULL; + } //end if + if ( !botgoalstates[handle] ) { + botimport.Print( PRT_FATAL, "invalid goal state %d\n", handle ); + return NULL; + } //end if + return botgoalstates[handle]; +} //end of the function BotGoalStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInterbreedGoalFuzzyLogic( int parent1, int parent2, int child ) { + bot_goalstate_t *p1, *p2, *c; + + p1 = BotGoalStateFromHandle( parent1 ); + p2 = BotGoalStateFromHandle( parent2 ); + c = BotGoalStateFromHandle( child ); + + InterbreedWeightConfigs( p1->itemweightconfig, p2->itemweightconfig, + c->itemweightconfig ); +} //end of the function BotInterbreedingGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSaveGoalFuzzyLogic( int goalstate, char *filename ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + + //WriteWeightConfig(filename, gs->itemweightconfig); +} //end of the function BotSaveGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMutateGoalFuzzyLogic( int goalstate, float range ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + + EvolveWeightConfig( gs->itemweightconfig ); +} //end of the function BotMutateGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +itemconfig_t *LoadItemConfig( char *filename ) { + int max_iteminfo; + token_t token; + char path[MAX_PATH]; + source_t *source; + itemconfig_t *ic; + iteminfo_t *ii; + + max_iteminfo = (int) LibVarValue( "max_iteminfo", "256" ); + if ( max_iteminfo < 0 ) { + botimport.Print( PRT_ERROR, "max_iteminfo = %d\n", max_iteminfo ); + max_iteminfo = 128; + LibVarSet( "max_iteminfo", "128" ); + } + + strncpy( path, filename, MAX_PATH ); + source = LoadSourceFile( path ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", path ); + return NULL; + } //end if + //initialize item config + ic = (itemconfig_t *) GetClearedHunkMemory( sizeof( itemconfig_t ) + + max_iteminfo * sizeof( iteminfo_t ) ); + ic->iteminfo = ( iteminfo_t * )( (char *) ic + sizeof( itemconfig_t ) ); + ic->numiteminfo = 0; + //parse the item config file + while ( PC_ReadToken( source, &token ) ) + { + if ( !strcmp( token.string, "iteminfo" ) ) { + if ( ic->numiteminfo >= max_iteminfo ) { + SourceError( source, "more than %d item info defined\n", max_iteminfo ); + FreeMemory( ic ); + FreeSource( source ); + return NULL; + } //end if + ii = &ic->iteminfo[ic->numiteminfo]; + memset( ii, 0, sizeof( iteminfo_t ) ); + if ( !PC_ExpectTokenType( source, TT_STRING, 0, &token ) ) { + FreeMemory( ic ); + FreeMemory( source ); + return NULL; + } //end if + StripDoubleQuotes( token.string ); + strncpy( ii->classname, token.string, sizeof( ii->classname ) - 1 ); + if ( !ReadStructure( source, &iteminfo_struct, (char *) ii ) ) { + FreeMemory( ic ); + FreeSource( source ); + return NULL; + } //end if + ii->number = ic->numiteminfo; + ic->numiteminfo++; + } //end if + else + { + SourceError( source, "unknown definition %s\n", token.string ); + FreeMemory( ic ); + FreeSource( source ); + return NULL; + } //end else + } //end while + FreeSource( source ); + // + if ( !ic->numiteminfo ) { + botimport.Print( PRT_WARNING, "no item info loaded\n" ); + } + botimport.Print( PRT_MESSAGE, "loaded %s\n", path ); + return ic; +} //end of the function LoadItemConfig +//=========================================================================== +// index to find the weight function of an iteminfo +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *ItemWeightIndex( weightconfig_t *iwc, itemconfig_t *ic ) { + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory( sizeof( int ) * ic->numiteminfo ); + + for ( i = 0; i < ic->numiteminfo; i++ ) + { + index[i] = FindFuzzyWeight( iwc, ic->iteminfo[i].classname ); + if ( index[i] < 0 ) { + Log_Write( "item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname ); + } //end if + } //end for + return index; +} //end of the function ItemWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitLevelItemHeap( void ) { + int i, max_levelitems; + + if ( levelitemheap ) { + FreeMemory( levelitemheap ); + } + + max_levelitems = (int) LibVarValue( "max_levelitems", "256" ); + levelitemheap = (levelitem_t *) GetMemory( max_levelitems * sizeof( levelitem_t ) ); + + for ( i = 0; i < max_levelitems - 2; i++ ) + { + levelitemheap[i].next = &levelitemheap[i + 1]; + } //end for + levelitemheap[max_levelitems - 1].next = NULL; + // + freelevelitems = levelitemheap; +} //end of the function InitLevelItemHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +levelitem_t *AllocLevelItem( void ) { + levelitem_t *li; + + li = freelevelitems; + if ( !li ) { + botimport.Print( PRT_FATAL, "out of level items\n" ); + return NULL; + } //end if + // + freelevelitems = freelevelitems->next; + memset( li, 0, sizeof( levelitem_t ) ); + return li; +} //end of the function AllocLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeLevelItem( levelitem_t *li ) { + li->next = freelevelitems; + freelevelitems = li; +} //end of the function FreeLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddLevelItemToList( levelitem_t *li ) { + if ( levelitems ) { + levelitems->prev = li; + } + li->prev = NULL; + li->next = levelitems; + levelitems = li; +} //end of the function AddLevelItemToList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveLevelItemFromList( levelitem_t *li ) { + if ( li->prev ) { + li->prev->next = li->next; + } else { levelitems = li->next;} + if ( li->next ) { + li->next->prev = li->prev; + } +} //end of the function RemoveLevelItemFromList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeInfoEntities( void ) { + maplocation_t *ml, *nextml; + campspot_t *cs, *nextcs; + + for ( ml = maplocations; ml; ml = nextml ) + { + nextml = ml->next; + FreeMemory( ml ); + } //end for + maplocations = NULL; + for ( cs = campspots; cs; cs = nextcs ) + { + nextcs = cs->next; + FreeMemory( cs ); + } //end for + campspots = NULL; +} //end of the function BotFreeInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitInfoEntities( void ) { + char classname[MAX_EPAIRKEY]; + maplocation_t *ml; + campspot_t *cs; + int ent, numlocations, numcampspots; + + BotFreeInfoEntities(); + // + numlocations = 0; + numcampspots = 0; + for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + + //map locations + if ( !strcmp( classname, "target_location" ) ) { + ml = (maplocation_t *) GetClearedMemory( sizeof( maplocation_t ) ); + AAS_VectorForBSPEpairKey( ent, "origin", ml->origin ); + AAS_ValueForBSPEpairKey( ent, "message", ml->name, sizeof( ml->name ) ); + ml->areanum = AAS_PointAreaNum( ml->origin ); + ml->next = maplocations; + maplocations = ml; + numlocations++; + } //end if + //camp spots + else if ( !strcmp( classname, "info_camp" ) ) { + cs = (campspot_t *) GetClearedMemory( sizeof( campspot_t ) ); + AAS_VectorForBSPEpairKey( ent, "origin", cs->origin ); + //cs->origin[2] += 16; + AAS_ValueForBSPEpairKey( ent, "message", cs->name, sizeof( cs->name ) ); + AAS_FloatForBSPEpairKey( ent, "range", &cs->range ); + AAS_FloatForBSPEpairKey( ent, "weight", &cs->weight ); + AAS_FloatForBSPEpairKey( ent, "wait", &cs->wait ); + AAS_FloatForBSPEpairKey( ent, "random", &cs->random ); + cs->areanum = AAS_PointAreaNum( cs->origin ); + if ( !cs->areanum ) { + botimport.Print( PRT_MESSAGE, "camp spot at %1.1f %1.1f %1.1f in solid\n", cs->origin[0], cs->origin[1], cs->origin[2] ); + FreeMemory( cs ); + continue; + } //end if + cs->next = campspots; + campspots = cs; + //AAS_DrawPermanentCross(cs->origin, 4, LINECOLOR_YELLOW); + numcampspots++; + } //end else if + } //end for + if ( bot_developer ) { + botimport.Print( PRT_MESSAGE, "%d map locations\n", numlocations ); + botimport.Print( PRT_MESSAGE, "%d camp spots\n", numcampspots ); + } //end if +} //end of the function BotInitInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitLevelItems( void ) { + int i, spawnflags; + char classname[MAX_EPAIRKEY]; + vec3_t origin; + int ent; + itemconfig_t *ic; + levelitem_t *li; + + //initialize the map locations and camp spots + BotInitInfoEntities(); + + //initialize the level item heap + InitLevelItemHeap(); + levelitems = NULL; + numlevelitems = 0; + // + ic = itemconfig; + if ( !ic ) { + return; + } + + //if there's no AAS file loaded + if ( !AAS_Loaded() ) { + return; + } + + //update the modelindexes of the item info + for ( i = 0; i < ic->numiteminfo; i++ ) + { + //ic->iteminfo[i].modelindex = AAS_IndexFromModel(ic->iteminfo[i].model); + if ( !ic->iteminfo[i].modelindex ) { + Log_Write( "item %s has modelindex 0", ic->iteminfo[i].classname ); + } //end if + } //end for + + for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + // + spawnflags = 0; + AAS_IntForBSPEpairKey( ent, "spawnflags", &spawnflags ); + //FIXME: don't do this + // for now skip all floating entities + if ( spawnflags & 1 ) { + continue; + } + // + for ( i = 0; i < ic->numiteminfo; i++ ) + { + if ( !strcmp( classname, ic->iteminfo[i].classname ) ) { + //get the origin of the item + if ( AAS_VectorForBSPEpairKey( ent, "origin", origin ) ) { + li = AllocLevelItem(); + if ( !li ) { + return; + } + // + li->number = ++numlevelitems; + li->timeout = 0; + li->entitynum = 0; + // + AAS_IntForBSPEpairKey( ent, "notfree", &li->notfree ); + AAS_IntForBSPEpairKey( ent, "notteam", &li->notteam ); + AAS_IntForBSPEpairKey( ent, "notsingle", &li->notsingle ); + //if not a stationary item + if ( !( spawnflags & 1 ) ) { + if ( !AAS_DropToFloor( origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs ) ) { + botimport.Print( PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2] ); + } //end if + } //end if + //item info of the level item + li->iteminfo = i; + //origin of the item + VectorCopy( origin, li->origin ); + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea( origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin ); + // + AddLevelItemToList( li ); + } //end if + else + { + botimport.Print( PRT_ERROR, "item %s without origin\n", classname ); + } //end else + break; + } //end if + } //end for + if ( i >= ic->numiteminfo ) { + Log_Write( "entity %s unknown item\r\n", classname ); + } //end if + } //end for + botimport.Print( PRT_MESSAGE, "found %d level items\n", numlevelitems ); +} //end of the function BotInitLevelItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGoalName( int number, char *name, int size ) { + levelitem_t *li; + + if ( !itemconfig ) { + return; + } + // + for ( li = levelitems; li; li = li->next ) + { + if ( li->number == number ) { + strncpy( name, itemconfig->iteminfo[li->iteminfo].name, size - 1 ); + name[size - 1] = '\0'; + return; + } //end for + } //end for + strcpy( name, "" ); + return; +} //end of the function BotGoalName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidGoals( int goalstate ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + memset( gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof( int ) ); + memset( gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof( float ) ); +} //end of the function BotResetAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpAvoidGoals( int goalstate ) { + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + for ( i = 0; i < MAX_AVOIDGOALS; i++ ) + { + if ( gs->avoidgoaltimes[i] >= AAS_Time() ) { + BotGoalName( gs->avoidgoals[i], name, 32 ); + Log_Write( "avoid goal %s, number %d for %f seconds", name, + gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time() ); + } //end if + } //end for +} //end of the function BotDumpAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidGoals( bot_goalstate_t *gs, int number, float avoidtime ) { + int i; + + for ( i = 0; i < MAX_AVOIDGOALS; i++ ) + { + //if this avoid goal has expired + if ( gs->avoidgoaltimes[i] < AAS_Time() ) { + gs->avoidgoals[i] = number; + gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveFromAvoidGoals( int goalstate, int number ) { + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + //don't use the goals the bot wants to avoid + for ( i = 0; i < MAX_AVOIDGOALS; i++ ) + { + if ( gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time() ) { + gs->avoidgoaltimes[i] = 0; + return; + } //end if + } //end for +} //end of the function BotRemoveFromAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotAvoidGoalTime( int goalstate, int number ) { + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return 0; + } + //don't use the goals the bot wants to avoid + for ( i = 0; i < MAX_AVOIDGOALS; i++ ) + { + if ( gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time() ) { + return gs->avoidgoaltimes[i] - AAS_Time(); + } //end if + } //end for + return 0; +} //end of the function BotAvoidGoalTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetLevelItemGoal( int index, char *name, bot_goal_t *goal ) { + levelitem_t *li; + + if ( !itemconfig ) { + return -1; + } + for ( li = levelitems; li; li = li->next ) + { + if ( li->number <= index ) { + continue; + } + // + if ( g_gametype == GT_SINGLE_PLAYER ) { + if ( li->notsingle ) { + continue; + } + } else if ( g_gametype >= GT_TEAM ) { + if ( li->notteam ) { + continue; + } + } else { + if ( li->notfree ) { + continue; + } + } + // + if ( !Q_stricmp( name, itemconfig->iteminfo[li->iteminfo].name ) ) { + goal->areanum = li->goalareanum; + VectorCopy( li->goalorigin, goal->origin ); + goal->entitynum = li->entitynum; + VectorCopy( itemconfig->iteminfo[li->iteminfo].mins, goal->mins ); + VectorCopy( itemconfig->iteminfo[li->iteminfo].maxs, goal->maxs ); + goal->number = li->number; + //botimport.Print(PRT_MESSAGE, "found li %s\n", itemconfig->iteminfo[li->iteminfo].name); + return li->number; + } //end if + } //end for + return -1; +} //end of the function BotGetLevelItemGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetMapLocationGoal( char *name, bot_goal_t *goal ) { + maplocation_t *ml; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + for ( ml = maplocations; ml; ml = ml->next ) + { + if ( !Q_stricmp( ml->name, name ) ) { + goal->areanum = ml->areanum; + VectorCopy( ml->origin, goal->origin ); + goal->entitynum = 0; + VectorCopy( mins, goal->mins ); + VectorCopy( maxs, goal->maxs ); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotGetMapLocationGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetNextCampSpotGoal( int num, bot_goal_t *goal ) { + int i; + campspot_t *cs; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + if ( num < 0 ) { + num = 0; + } + i = num; + for ( cs = campspots; cs; cs = cs->next ) + { + if ( --i < 0 ) { + goal->areanum = cs->areanum; + VectorCopy( cs->origin, goal->origin ); + goal->entitynum = 0; + VectorCopy( mins, goal->mins ); + VectorCopy( maxs, goal->maxs ); + return num + 1; + } //end if + } //end for + return 0; +} //end of the function BotGetNextCampSpotGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +//NOTE: enum entityType_t in bg_public.h +#define ET_ITEM 2 + +void BotUpdateEntityItems( void ) { + int ent, i, modelindex; + vec3_t dir; + levelitem_t *li, *nextli; + aas_entityinfo_t entinfo; + itemconfig_t *ic; + + //timeout current entity items if necessary + for ( li = levelitems; li; li = nextli ) + { + nextli = li->next; + //if it is a item that will time out + if ( li->timeout ) { + //timeout the item + if ( li->timeout < AAS_Time() ) { + RemoveLevelItemFromList( li ); + FreeLevelItem( li ); + } //end if + } //end if + } //end for + //find new entity items + ic = itemconfig; + if ( !itemconfig ) { + return; + } + // + for ( ent = AAS_NextEntity( 0 ); ent; ent = AAS_NextEntity( ent ) ) + { + if ( AAS_EntityType( ent ) != ET_ITEM ) { + continue; + } + //get the model index of the entity + modelindex = AAS_EntityModelindex( ent ); + // + if ( !modelindex ) { + continue; + } + //get info about the entity + AAS_EntityInfo( ent, &entinfo ); + //FIXME: don't do this + //skip all floating items for now + if ( entinfo.groundent != ENTITYNUM_WORLD ) { + continue; + } + //if the entity is still moving + if ( entinfo.origin[0] != entinfo.lastvisorigin[0] || + entinfo.origin[1] != entinfo.lastvisorigin[1] || + entinfo.origin[2] != entinfo.lastvisorigin[2] ) { + continue; + } + //check if the level item isn't already stored + for ( li = levelitems; li; li = li->next ) + { + //if the model of the level item and the entity are different + if ( ic->iteminfo[li->iteminfo].modelindex != modelindex ) { + continue; + } + //if the level item is linked to an entity + if ( li->entitynum ) { + if ( li->entitynum == ent ) { + VectorCopy( entinfo.origin, li->origin ); + break; + } //end if + } //end if + else + { + //check if the entity is very close + VectorSubtract( li->origin, entinfo.origin, dir ); + if ( VectorLength( dir ) < 30 ) { + //found an entity for this level item + li->entitynum = ent; + //keep updating the entity origin + VectorCopy( entinfo.origin, li->origin ); + //also update the goal area number + li->goalareanum = AAS_BestReachableArea( li->origin, + ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, + li->goalorigin ); + //Log_Write("found item %s entity", ic->iteminfo[li->iteminfo].classname); + break; + } //end if + //else botimport.Print(PRT_MESSAGE, "item %s has no attached entity\n", + // ic->iteminfo[li->iteminfo].name); + } //end else + } //end for + if ( li ) { + continue; + } + //check if the model is from a known item + for ( i = 0; i < ic->numiteminfo; i++ ) + { + if ( ic->iteminfo[i].modelindex == modelindex ) { + break; + } //end if + } //end for + //if the model is not from a known item + if ( i >= ic->numiteminfo ) { + continue; + } + //allocate a new level item + li = AllocLevelItem(); + // + if ( !li ) { + continue; + } + //entity number of the level item + li->entitynum = ent; + //number for the level item + li->number = numlevelitems + ent; + //set the item info index for the level item + li->iteminfo = i; + //origin of the item + VectorCopy( entinfo.origin, li->origin ); + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea( li->origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin ); + // + if ( AAS_AreaJumpPad( li->goalareanum ) ) { + FreeLevelItem( li ); + continue; + } //end if + //time this item out after 30 seconds + //dropped items disappear after 30 seconds + li->timeout = AAS_Time() + 30; + //add the level item to the list + AddLevelItemToList( li ); + //botimport.Print(PRT_MESSAGE, "found new level item %s\n", ic->iteminfo[i].classname); + } //end for +} //end of the function BotUpdateEntityItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpGoalStack( int goalstate ) { + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + for ( i = 1; i <= gs->goalstacktop; i++ ) + { + BotGoalName( gs->goalstack[i].number, name, 32 ); + Log_Write( "%d: %s", i, name ); + } //end for +} //end of the function BotDumpGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPushGoal( int goalstate, bot_goal_t *goal ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + if ( gs->goalstacktop >= MAX_GOALSTACK - 1 ) { + botimport.Print( PRT_ERROR, "goal heap overflow\n" ); + BotDumpGoalStack( goalstate ); + return; + } //end if + gs->goalstacktop++; + memcpy( &gs->goalstack[gs->goalstacktop], goal, sizeof( bot_goal_t ) ); +} //end of the function BotPushGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPopGoal( int goalstate ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + if ( gs->goalstacktop > 0 ) { + gs->goalstacktop--; + } +} //end of the function BotPopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEmptyGoalStack( int goalstate ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + gs->goalstacktop = 0; +} //end of the function BotEmptyGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetTopGoal( int goalstate, bot_goal_t *goal ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return qfalse; + } + if ( !gs->goalstacktop ) { + return qfalse; + } + memcpy( goal, &gs->goalstack[gs->goalstacktop], sizeof( bot_goal_t ) ); + return qtrue; +} //end of the function BotGetTopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetSecondGoal( int goalstate, bot_goal_t *goal ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return qfalse; + } + if ( gs->goalstacktop <= 1 ) { + return qfalse; + } + memcpy( goal, &gs->goalstack[gs->goalstacktop - 1], sizeof( bot_goal_t ) ); + return qtrue; +} //end of the function BotGetSecondGoal +//=========================================================================== +// pops a new long term goal on the goal stack in the goalstate +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseLTGItem( int goalstate, vec3_t origin, int *inventory, int travelflags ) { + int areanum, t, weightnum; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return qfalse; + } + if ( !gs->itemweightconfig ) { + return qfalse; + } + //get the area the bot is in + areanum = BotReachabilityArea( origin, gs->client ); + //if the bot is in solid or if the area the bot is in has no reachability links + if ( !areanum || !AAS_AreaReachability( areanum ) ) { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if ( !areanum ) { + return qfalse; + } + //the item configuration + ic = itemconfig; + if ( !itemconfig ) { + return qfalse; + } + //best weight and item so far + bestweight = 0; + bestitem = NULL; + memset( &goal, 0, sizeof( bot_goal_t ) ); + //go through the items in the level + for ( li = levelitems; li; li = li->next ) + { + if ( g_gametype == GT_SINGLE_PLAYER ) { + if ( li->notsingle ) { + continue; + } + } else if ( g_gametype >= GT_TEAM ) { + if ( li->notteam ) { + continue; + } + } else { + if ( li->notfree ) { + continue; + } + } + //if the item is not in a possible goal area + if ( !li->goalareanum ) { + continue; + } + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if ( weightnum < 0 ) { + continue; + } + //if this goal is in the avoid goals + if ( BotAvoidGoalTime( goalstate, li->number ) > 0 ) { + continue; + } + +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum ); +#else + weight = FuzzyWeight( inventory, gs->itemweightconfig, weightnum ); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if ( li->timeout ) { + weight += 1000; + } +#endif //DROPPEDWEIGHT + if ( weight > 0 ) { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea( areanum, origin, li->goalareanum, travelflags ); + //if the goal is reachable + if ( t > 0 ) { + weight /= (float) t * TRAVELTIME_SCALE; + // + if ( weight > bestweight ) { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if ( !bestitem ) { + /* + //if not in lava or slime + if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum)) + { + if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin)) + { + VectorSet(goal.mins, -15, -15, -15); + VectorSet(goal.maxs, 15, 15, 15); + goal.entitynum = 0; + goal.number = 0; + goal.flags = GFL_ROAM; + goal.iteminfo = 0; + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "chosen roam goal area %d\n", goal.areanum); +#endif //DEBUG + return qtrue; + } //end if + } //end if + */ + return qfalse; + } //end if + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy( bestitem->goalorigin, goal.origin ); + VectorCopy( iteminfo->mins, goal.mins ); + VectorCopy( iteminfo->maxs, goal.maxs ); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + goal.iteminfo = bestitem->iteminfo; + //add the chosen goal to the goals to avoid for a while + avoidtime = iteminfo->respawntime * 0.5; + if ( avoidtime < 10 ) { + avoidtime = AVOID_TIME; + } + //if it's a dropped item + if ( bestitem->timeout ) { + avoidtime = AVOIDDROPPED_TIME; + } + BotAddToAvoidGoals( gs, bestitem->number, avoidtime ); + //push the goal on the stack + BotPushGoal( goalstate, &goal ); + // +#ifdef DEBUG_AI_GOAL + if ( bestitem->timeout ) { + botimport.Print( PRT_MESSAGE, "new ltg dropped item %s\n", ic->iteminfo[bestitem->iteminfo].classname ); + } //end if + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + botimport.Print( PRT_MESSAGE, "new ltg \"%s\"\n", iteminfo->classname ); +#endif //DEBUG_AI_GOAL + return qtrue; +} //end of the function BotChooseLTGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseNBGItem( int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime ) { + int areanum, t, weightnum, ltg_time; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return qfalse; + } + if ( !gs->itemweightconfig ) { + return qfalse; + } + //get the area the bot is in + areanum = BotReachabilityArea( origin, gs->client ); + //if the bot is in solid or if the area the bot is in has no reachability links + if ( !areanum || !AAS_AreaReachability( areanum ) ) { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if ( !areanum ) { + return qfalse; + } + // + if ( ltg ) { + ltg_time = AAS_AreaTravelTimeToGoalArea( areanum, origin, ltg->areanum, travelflags ); + } else { ltg_time = 99999;} + //the item configuration + ic = itemconfig; + if ( !itemconfig ) { + return qfalse; + } + //best weight and item so far + bestweight = 0; + bestitem = NULL; + memset( &goal, 0, sizeof( bot_goal_t ) ); + //go through the items in the level + for ( li = levelitems; li; li = li->next ) + { + if ( g_gametype == GT_SINGLE_PLAYER ) { + if ( li->notsingle ) { + continue; + } + } else if ( g_gametype >= GT_TEAM ) { + if ( li->notteam ) { + continue; + } + } else { + if ( li->notfree ) { + continue; + } + } + //if the item is in a possible goal area + if ( !li->goalareanum ) { + continue; + } + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if ( weightnum < 0 ) { + continue; + } + //if this goal is in the avoid goals + if ( BotAvoidGoalTime( goalstate, li->number ) > 0 ) { + continue; + } + // +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum ); +#else + weight = FuzzyWeight( inventory, gs->itemweightconfig, weightnum ); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if ( li->timeout ) { + weight += 1000; + } +#endif //DROPPEDWEIGHT + if ( weight > 0 ) { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea( areanum, origin, li->goalareanum, travelflags ); + //if the goal is reachable + if ( t > 0 && t < maxtime ) { + weight /= (float) t * TRAVELTIME_SCALE; + // + if ( weight > bestweight ) { + t = 0; + if ( ltg && !li->timeout ) { + //get the travel time from the goal to the long term goal + t = AAS_AreaTravelTimeToGoalArea( li->goalareanum, li->goalorigin, ltg->areanum, travelflags ); + } //end if + //if the travel back is possible and doesn't take too long + if ( t <= ltg_time ) { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if ( !bestitem ) { + return qfalse; + } + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy( bestitem->goalorigin, goal.origin ); + VectorCopy( iteminfo->mins, goal.mins ); + VectorCopy( iteminfo->maxs, goal.maxs ); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + goal.iteminfo = bestitem->iteminfo; + //add the chosen goal to the goals to avoid for a while + avoidtime = iteminfo->respawntime * 0.5; + if ( avoidtime < 10 ) { + avoidtime = AVOID_TIME; + } + //if it's a dropped item + if ( bestitem->timeout ) { + avoidtime = AVOIDDROPPED_TIME; + } + BotAddToAvoidGoals( gs, bestitem->number, avoidtime ); + //push the goal on the stack + BotPushGoal( goalstate, &goal ); + // +#ifdef DEBUG_AI_GOAL + if ( bestitem->timeout ) { + botimport.Print( PRT_MESSAGE, "new nbg dropped item %s\n", ic->iteminfo[bestitem->iteminfo].classname ); + } //end if + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + botimport.Print( PRT_MESSAGE, "new nbg \"%s\"\n", iteminfo->classname ); +#endif //DEBUG_AI_GOAL + return qtrue; +} //end of the function BotChooseNBGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotTouchingGoal( vec3_t origin, bot_goal_t *goal ) { + int i; + vec3_t boxmins, boxmaxs; + vec3_t absmins, absmaxs; + vec3_t safety_maxs = {0, 0, 0}; //{4, 4, 10}; + vec3_t safety_mins = {0, 0, 0}; //{-4, -4, 0}; + + AAS_PresenceTypeBoundingBox( PRESENCE_NORMAL, boxmins, boxmaxs ); + VectorSubtract( goal->mins, boxmaxs, absmins ); + VectorSubtract( goal->maxs, boxmins, absmaxs ); + VectorAdd( absmins, goal->origin, absmins ); + VectorAdd( absmaxs, goal->origin, absmaxs ); + //make the box a little smaller for safety + VectorSubtract( absmaxs, safety_maxs, absmaxs ); + VectorSubtract( absmins, safety_mins, absmins ); + + for ( i = 0; i < 3; i++ ) + { + if ( origin[i] < absmins[i] || origin[i] > absmaxs[i] ) { + return qfalse; + } + } //end for + return qtrue; +} //end of the function BotTouchingGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotItemGoalInVisButNotVisible( int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal ) { + aas_entityinfo_t entinfo; + bsp_trace_t trace; + vec3_t middle; + + if ( !( goal->flags & GFL_ITEM ) ) { + return qfalse; + } + // + VectorAdd( goal->mins, goal->mins, middle ); + VectorScale( middle, 0.5, middle ); + VectorAdd( goal->origin, middle, middle ); + // + trace = AAS_Trace( eye, NULL, NULL, middle, viewer, CONTENTS_SOLID ); + //if the goal middle point is visible + if ( trace.fraction >= 1 ) { + //the goal entity number doesn't have to be valid + //just assume it's valid + if ( goal->entitynum <= 0 ) { + return qfalse; + } + // + //if the entity data isn't valid + AAS_EntityInfo( goal->entitynum, &entinfo ); + //NOTE: for some wacko reason entities are sometimes + // not updated + //if (!entinfo.valid) return qtrue; + if ( entinfo.ltime < AAS_Time() - 0.5 ) { + return qtrue; + } + } //end if + return qfalse; +} //end of the function BotItemGoalInVisButNotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGoalState( int goalstate ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + memset( gs->goalstack, 0, MAX_GOALSTACK * sizeof( bot_goal_t ) ); + gs->goalstacktop = 0; + BotResetAvoidGoals( goalstate ); +} //end of the function BotResetGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadItemWeights( int goalstate, char *filename ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return BLERR_CANNOTLOADITEMWEIGHTS; + } + //load the weight configuration + gs->itemweightconfig = ReadWeightConfig( filename ); + if ( !gs->itemweightconfig ) { + botimport.Print( PRT_FATAL, "couldn't load weights\n" ); + return BLERR_CANNOTLOADITEMWEIGHTS; + } //end if + //if there's no item configuration + if ( !itemconfig ) { + return BLERR_CANNOTLOADITEMWEIGHTS; + } + //create the item weight index + gs->itemweightindex = ItemWeightIndex( gs->itemweightconfig, itemconfig ); + //everything went ok + return BLERR_NOERROR; +} //end of the function BotLoadItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeItemWeights( int goalstate ) { + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle( goalstate ); + if ( !gs ) { + return; + } + if ( gs->itemweightconfig ) { + FreeWeightConfig( gs->itemweightconfig ); + } + if ( gs->itemweightindex ) { + FreeMemory( gs->itemweightindex ); + } +} //end of the function BotFreeItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAllocGoalState( int client ) { + int i; + + for ( i = 1; i <= MAX_CLIENTS; i++ ) + { + if ( !botgoalstates[i] ) { + botgoalstates[i] = GetClearedMemory( sizeof( bot_goalstate_t ) ); + botgoalstates[i]->client = client; + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocGoalState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeGoalState( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "goal state handle %d out of range\n", handle ); + return; + } //end if + if ( !botgoalstates[handle] ) { + botimport.Print( PRT_FATAL, "invalid goal state handle %d\n", handle ); + return; + } //end if + BotFreeItemWeights( handle ); + FreeMemory( botgoalstates[handle] ); + botgoalstates[handle] = NULL; +} //end of the function BotFreeGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupGoalAI( void ) { + char *filename; + + //check if teamplay is on + g_gametype = LibVarValue( "g_gametype", "0" ); + //item configuration file + filename = LibVarString( "itemconfig", "items.c" ); + //load the item configuration + itemconfig = LoadItemConfig( filename ); + if ( !itemconfig ) { + botimport.Print( PRT_FATAL, "couldn't load item config\n" ); + return BLERR_CANNOTLOADITEMCONFIG; + } //end if + //everything went ok + return BLERR_NOERROR; +} //end of the function BotSetupGoalAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownGoalAI( void ) { + int i; + + if ( itemconfig ) { + FreeMemory( itemconfig ); + } + itemconfig = NULL; + if ( levelitemheap ) { + FreeMemory( levelitemheap ); + } + levelitemheap = NULL; + freelevelitems = NULL; + levelitems = NULL; + numlevelitems = 0; + + BotFreeInfoEntities(); + + for ( i = 1; i <= MAX_CLIENTS; i++ ) + { + if ( botgoalstates[i] ) { + BotFreeGoalState( i ); + } //end if + } //end for +} //end of the function BotShutdownGoalAI diff --git a/src/botlib/be_ai_move.c b/src/botlib/be_ai_move.c new file mode 100644 index 0000000..c239cf5 --- /dev/null +++ b/src/botlib/be_ai_move.c @@ -0,0 +1,3632 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_move.c + * + * desc: bot movement AI + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" + +#include "../game/be_ea.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" + + +//#define DEBUG_AI_MOVE +//#define DEBUG_ELEVATOR +//#define DEBUG_GRAPPLE +//movement state + +//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP must be set outside the movement code +typedef struct bot_movestate_s +{ + //input vars (all set outside the movement code) + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + //state vars + int areanum; //area the bot is in + int lastareanum; //last area the bot was in + int lastgoalareanum; //last goal area number + int lastreachnum; //last reachability number + vec3_t lastorigin; //origin previous cycle + float lasttime; + int reachareanum; //area number of the reachabilty + int moveflags; //movement flags + int jumpreach; //set when jumped + float grapplevisible_time; //last time the grapple was visible + float lastgrappledist; //last distance to the grapple end + float reachability_time; //time to use current reachability + int avoidreach[MAX_AVOIDREACH]; //reachabilities to avoid + float avoidreachtimes[MAX_AVOIDREACH]; //times to avoid the reachabilities + int avoidreachtries[MAX_AVOIDREACH]; //number of tries before avoiding +} bot_movestate_t; + +//used to avoid reachability links for some time after being used +// Ridah, disabled this to prevent wierd navigational behaviour (mostly by Zombie, since it's so slow) +//#define AVOIDREACH +#define AVOIDREACH_TIME 6 //avoid links for 6 seconds after use +#define AVOIDREACH_TRIES 4 +//prediction times +#define PREDICTIONTIME_JUMP 3 //in seconds +#define PREDICTIONTIME_MOVE 2 //in seconds +//hook commands +#define CMD_HOOKOFF "hookoff" +#define CMD_HOOKON "hookon" +//weapon indexes for weapon jumping +#define WEAPONINDEX_ROCKET_LAUNCHER 5 +#define WEAPONINDEX_BFG 9 + +#define MODELTYPE_FUNC_PLAT 1 +#define MODELTYPE_FUNC_BOB 2 + +float sv_maxstep; +float sv_maxbarrier; +float sv_gravity; +//type of model, func_plat or func_bobbing +int modeltypes[MAX_MODELS]; + +bot_movestate_t *botmovestates[MAX_CLIENTS + 1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocMoveState( void ) { + int i; + + for ( i = 1; i <= MAX_CLIENTS; i++ ) + { + if ( !botmovestates[i] ) { + botmovestates[i] = GetClearedMemory( sizeof( bot_movestate_t ) ); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeMoveState( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "move state handle %d out of range\n", handle ); + return; + } //end if + if ( !botmovestates[handle] ) { + botimport.Print( PRT_FATAL, "invalid move state %d\n", handle ); + return; + } //end if + FreeMemory( botmovestates[handle] ); + botmovestates[handle] = NULL; +} //end of the function BotFreeMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_movestate_t *BotMoveStateFromHandle( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "move state handle %d out of range\n", handle ); + return NULL; + } //end if + if ( !botmovestates[handle] ) { + botimport.Print( PRT_FATAL, "invalid move state %d\n", handle ); + return NULL; + } //end if + return botmovestates[handle]; +} //end of the function BotMoveStateFromHandle + +// Ridah, provide a means of resetting the avoidreach, so if a bot stops moving, they don't avoid the area they were heading for +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotInitAvoidReach( int handle ) { + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle( handle ); + if ( !ms ) { + return; + } + + memset( ms->avoidreach, 0, sizeof( ms->avoidreach ) ); + memset( ms->avoidreachtries, 0, sizeof( ms->avoidreachtries ) ); + memset( ms->avoidreachtimes, 0, sizeof( ms->avoidreachtimes ) ); +} +// done. + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotInitMoveState( int handle, bot_initmove_t *initmove ) { + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle( handle ); + if ( !ms ) { + return; + } + VectorCopy( initmove->origin, ms->origin ); + VectorCopy( initmove->velocity, ms->velocity ); + VectorCopy( initmove->viewoffset, ms->viewoffset ); + ms->entitynum = initmove->entitynum; + ms->client = initmove->client; + ms->thinktime = initmove->thinktime; + ms->presencetype = initmove->presencetype; + VectorCopy( initmove->viewangles, ms->viewangles ); + // + ms->moveflags &= ~MFL_ONGROUND; + if ( initmove->or_moveflags & MFL_ONGROUND ) { + ms->moveflags |= MFL_ONGROUND; + } + ms->moveflags &= ~MFL_TELEPORTED; + if ( initmove->or_moveflags & MFL_TELEPORTED ) { + ms->moveflags |= MFL_TELEPORTED; + } + ms->moveflags &= ~MFL_WATERJUMP; + if ( initmove->or_moveflags & MFL_WATERJUMP ) { + ms->moveflags |= MFL_WATERJUMP; + } + ms->moveflags &= ~MFL_WALK; + if ( initmove->or_moveflags & MFL_WALK ) { + ms->moveflags |= MFL_WALK; + } +} //end of the function BotInitMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +float AngleDiff( float ang1, float ang2 ) { + float diff; + + diff = ang1 - ang2; + if ( ang1 > ang2 ) { + if ( diff > 180.0 ) { + diff -= 360.0; + } + } //end if + else + { + if ( diff < -180.0 ) { + diff += 360.0; + } + } //end else + return diff; +} //end of the function AngleDiff +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFuzzyPointReachabilityArea( vec3_t origin ) { + int firstareanum, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t points[10], v, end; + + firstareanum = 0; + areanum = AAS_PointAreaNum( origin ); + if ( areanum ) { + firstareanum = areanum; + if ( AAS_AreaReachability( areanum ) ) { + return areanum; + } + } //end if + VectorCopy( origin, end ); + end[2] += 4; + numareas = AAS_TraceAreas( origin, end, areas, points, 10 ); + for ( j = 0; j < numareas; j++ ) + { + if ( AAS_AreaReachability( areas[j] ) ) { + return areas[j]; + } + } //end for + bestdist = 999999; + bestareanum = 0; + for ( z = 1; z >= -1; z -= 1 ) + { + for ( x = 1; x >= -1; x -= 1 ) + { + for ( y = 1; y >= -1; y -= 1 ) + { + VectorCopy( origin, end ); + // Ridah, increased this for Wolf larger bounding boxes + end[0] += x * 16; //8; + end[1] += y * 16; //8; + end[2] += z * 24; //12; + numareas = AAS_TraceAreas( origin, end, areas, points, 10 ); + for ( j = 0; j < numareas; j++ ) + { + if ( AAS_AreaReachability( areas[j] ) ) { + VectorSubtract( points[j], origin, v ); + dist = VectorLength( v ); + if ( dist < bestdist ) { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + if ( !firstareanum ) { + firstareanum = areas[j]; + } + } //end for + } //end for + } //end for + if ( bestareanum ) { + return bestareanum; + } + } //end for + return firstareanum; +} //end of the function BotFuzzyPointReachabilityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityArea( vec3_t origin, int client ) { + int modelnum, modeltype, reachnum, areanum; + aas_reachability_t reach; + vec3_t org, end, mins, maxs, up = {0, 0, 1}; + bsp_trace_t bsptrace; + aas_trace_t trace; + + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox( PRESENCE_CROUCH, mins, maxs ); + VectorMA( origin, -3, up, end ); + bsptrace = AAS_Trace( origin, mins, maxs, end, client, CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + if ( !bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE ) { + //if standing on the world the bot should be in a valid area + if ( bsptrace.ent == ENTITYNUM_WORLD ) { + return BotFuzzyPointReachabilityArea( origin ); + } //end if + + modelnum = AAS_EntityModelindex( bsptrace.ent ); + modeltype = modeltypes[modelnum]; + + //if standing on a func_plat or func_bobbing then the bot is assumed to be + //in the area the reachability points to + if ( modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB ) { + reachnum = AAS_NextModelReachability( 0, modelnum ); + if ( reachnum ) { + AAS_ReachabilityFromNum( reachnum, &reach ); + return reach.areanum; + } //end if + } //end else if + + //if the bot is swimming the bot should be in a valid area + if ( AAS_Swimming( origin ) ) { + return BotFuzzyPointReachabilityArea( origin ); + } //end if + // + areanum = BotFuzzyPointReachabilityArea( origin ); + //if the bot is in an area with reachabilities + if ( areanum && AAS_AreaReachability( areanum ) ) { + return areanum; + } + //trace down till the ground is hit because the bot is standing on some other entity + VectorCopy( origin, org ); + VectorCopy( org, end ); + end[2] -= 800; + trace = AAS_TraceClientBBox( org, end, PRESENCE_CROUCH, -1 ); + if ( !trace.startsolid ) { + VectorCopy( trace.endpos, org ); + } //end if + // + return BotFuzzyPointReachabilityArea( org ); + } //end if + // + return BotFuzzyPointReachabilityArea( origin ); +} //end of the function BotReachabilityArea +//=========================================================================== +// returns the reachability area the bot is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int BotReachabilityArea(vec3_t origin, int testground) +{ + int firstareanum, i, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t org, end, points[10], v; + aas_trace_t trace; + + firstareanum = 0; + for (i = 0; i < 2; i++) + { + VectorCopy(origin, org); + //if test at the ground (used when bot is standing on an entity) + if (i > 0) + { + VectorCopy(origin, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + } //end if + + firstareanum = 0; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(org, end); + end[0] += x * 8; + end[1] += y * 8; + end[2] += z * 12; + numareas = AAS_TraceAreas(org, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], org, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + if (!testground) break; + } //end for +//#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "no reachability area\n"); +//#endif //DEBUG + return firstareanum; +} //end of the function BotReachabilityArea*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnMover( vec3_t origin, int entnum, aas_reachability_t *reach ) { + int i, modelnum; + vec3_t mins, maxs, modelorigin, org, end; + vec3_t angles = {0, 0, 0}; + vec3_t boxmins = {-16, -16, -8}, boxmaxs = {16, 16, 8}; + bsp_trace_t trace; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, NULL ); + // + if ( !AAS_OriginOfEntityWithModelNum( modelnum, modelorigin ) ) { + botimport.Print( PRT_MESSAGE, "no entity with model %d\n", modelnum ); + return qfalse; + } //end if + // + for ( i = 0; i < 2; i++ ) + { + if ( origin[i] > modelorigin[i] + maxs[i] + 16 ) { + return qfalse; + } + if ( origin[i] < modelorigin[i] + mins[i] - 16 ) { + return qfalse; + } + } //end for + // + VectorCopy( origin, org ); + org[2] += 24; + VectorCopy( origin, end ); + end[2] -= 48; + // + trace = AAS_Trace( org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + if ( !trace.startsolid && !trace.allsolid ) { + //NOTE: the reachability face number is the model number of the elevator + if ( trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum( trace.ent ) == modelnum ) { + return qtrue; + } //end if + } //end if + return qfalse; +} //end of the function BotOnMover +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MoverDown( aas_reachability_t *reach ) { + int modelnum; + vec3_t mins, maxs, origin; + vec3_t angles = {0, 0, 0}; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, origin ); + // + if ( !AAS_OriginOfEntityWithModelNum( modelnum, origin ) ) { + botimport.Print( PRT_MESSAGE, "no entity with model %d\n", modelnum ); + return qfalse; + } //end if + //if the top of the plat is below the reachability start point + if ( origin[2] + maxs[2] < reach->start[2] ) { + return qtrue; + } + return qfalse; +} //end of the function MoverDown +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotSetBrushModelTypes( void ) { + int ent, modelnum; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + + memset( modeltypes, 0, MAX_MODELS * sizeof( int ) ); + // + for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) ) + { + if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) { + continue; + } + if ( !AAS_ValueForBSPEpairKey( ent, "model", model, MAX_EPAIRKEY ) ) { + continue; + } + if ( model[0] ) { + modelnum = atoi( model + 1 ); + } else { modelnum = 0;} + + if ( modelnum < 0 || modelnum > MAX_MODELS ) { + botimport.Print( PRT_MESSAGE, "entity %s model number out of range\n", classname ); + continue; + } //end if + + if ( !strcmp( classname, "func_bobbing" ) ) { + modeltypes[modelnum] = MODELTYPE_FUNC_BOB; + } else if ( !strcmp( classname, "func_plat" ) ) { + modeltypes[modelnum] = MODELTYPE_FUNC_PLAT; + } + } //end for +} //end of the function BotSetBrushModelTypes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnTopOfEntity( bot_movestate_t *ms ) { + vec3_t mins, maxs, end, up = {0, 0, 1}; + bsp_trace_t trace; + + AAS_PresenceTypeBoundingBox( ms->presencetype, mins, maxs ); + VectorMA( ms->origin, -3, up, end ); + trace = AAS_Trace( ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + if ( !trace.startsolid && ( trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE ) ) { + return trace.ent; + } //end if + return -1; +} //end of the function BotOnTopOfEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotValidTravel( vec3_t origin, aas_reachability_t *reach, int travelflags ) { + //if the reachability uses an unwanted travel type + if ( AAS_TravelFlagForType( reach->traveltype ) & ~travelflags ) { + return qfalse; + } + //don't go into areas with bad travel types + if ( AAS_AreaContentsTravelFlag( reach->areanum ) & ~travelflags ) { + return qfalse; + } + return qtrue; +} //end of the function BotValidTravel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidReach( bot_movestate_t *ms, int number, float avoidtime ) { + int i; + + for ( i = 0; i < MAX_AVOIDREACH; i++ ) + { + if ( ms->avoidreach[i] == number ) { + if ( ms->avoidreachtimes[i] > AAS_Time() ) { + ms->avoidreachtries[i]++; + } else { ms->avoidreachtries[i] = 1;} + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for + //add the reachability to the reachabilities to avoid for a while + for ( i = 0; i < MAX_AVOIDREACH; i++ ) + { + if ( ms->avoidreachtimes[i] < AAS_Time() ) { + ms->avoidreach[i] = number; + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + ms->avoidreachtries[i] = 1; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//__inline int AAS_AreaContentsTravelFlag(int areanum); + +int BotGetReachabilityToGoal( vec3_t origin, int areanum, int entnum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags ) { + int t, besttime, bestreachnum, reachnum; + aas_reachability_t reach; + + //if not in a valid area + if ( !areanum ) { + return 0; + } + // + if ( AAS_AreaDoNotEnter( areanum ) || AAS_AreaDoNotEnter( goal->areanum ) ) { + travelflags |= TFL_DONOTENTER; + movetravelflags |= TFL_DONOTENTER; + } //end if + if ( AAS_AreaDoNotEnterLarge( areanum ) || AAS_AreaDoNotEnterLarge( goal->areanum ) ) { + travelflags |= TFL_DONOTENTER_LARGE; + movetravelflags |= TFL_DONOTENTER_LARGE; + } //end if + //use the routing to find the next area to go to + besttime = 0; + bestreachnum = 0; + // + for ( reachnum = AAS_NextAreaReachability( areanum, 0 ); reachnum; + reachnum = AAS_NextAreaReachability( areanum, reachnum ) ) + { +#ifdef AVOIDREACH + int i; + //check if it isn't an reachability to avoid + for ( i = 0; i < MAX_AVOIDREACH; i++ ) + { + if ( avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time() ) { + break; + } + } //end for + if ( i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES ) { +#ifdef DEBUG + if ( bot_developer ) { + botimport.Print( PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i] ); + } //end if +#endif //DEBUG + continue; + } //end if +#endif //AVOIDREACH + //get the reachability from the number + AAS_ReachabilityFromNum( reachnum, &reach ); + //NOTE: do not go back to the previous area if the goal didn't change + //NOTE: is this actually avoidance of local routing minima between two areas??? + if ( lastgoalareanum == goal->areanum && reach.areanum == lastareanum ) { + continue; + } + //if (AAS_AreaContentsTravelFlag(reach.areanum) & ~travelflags) continue; + //if the travel isn't valid + if ( !BotValidTravel( origin, &reach, movetravelflags ) ) { + continue; + } + //get the travel time + t = AAS_AreaTravelTimeToGoalArea( reach.areanum, reach.end, goal->areanum, travelflags ); + //if the goal area isn't reachable from the reachable area + if ( !t ) { + continue; + } + + // Ridah, if this sends us to a looped route, ignore it +// if (AAS_AreaTravelTimeToGoalArea(areanum, reach.start, goal->areanum, travelflags) + reach.traveltime == t) +// continue; + + //add the travel time towards the area + // Ridah, not sure why this was disabled, but it causes looped links in the route-cache + //t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start); + t += reach.traveltime + AAS_AreaTravelTime( areanum, origin, reach.start ); + + // Ridah, if there exists other entities in this area, avoid it +// if (reach.areanum != goal->areanum && AAS_IsEntityInArea( entnum, goal->entitynum, reach.areanum )) { +// t += 50; +// } + + //if the travel time is better than the ones already found + if ( !besttime || t < besttime ) { + besttime = t; + bestreachnum = reachnum; + } //end if + } //end for + // + return bestreachnum; +} //end of the function BotGetReachabilityToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAddToTarget( vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target ) { + vec3_t dir; + float curdist; + + VectorSubtract( end, start, dir ); + curdist = VectorNormalize( dir ); + if ( *dist + curdist < maxdist ) { + VectorCopy( end, target ); + *dist += curdist; + return qfalse; + } //end if + else + { + VectorMA( start, maxdist - *dist, dir, target ); + *dist = maxdist; + return qtrue; + } //end else +} //end of the function BotAddToTarget + +int BotMovementViewTarget( int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target ) { + aas_reachability_t reach; + int reachnum, lastareanum; + bot_movestate_t *ms; + vec3_t end; + float dist; + + ms = BotMoveStateFromHandle( movestate ); + if ( !ms ) { + return qfalse; + } + reachnum = 0; + //if the bot has no goal or no last reachability + if ( !ms->lastreachnum || !goal ) { + return qfalse; + } + + reachnum = ms->lastreachnum; + VectorCopy( ms->origin, end ); + lastareanum = ms->lastareanum; + dist = 0; + while ( reachnum && dist < lookahead ) + { + AAS_ReachabilityFromNum( reachnum, &reach ); + if ( BotAddToTarget( end, reach.start, lookahead, &dist, target ) ) { + return qtrue; + } + //never look beyond teleporters + if ( reach.traveltype == TRAVEL_TELEPORT ) { + return qtrue; + } + //don't add jump pad distances + if ( reach.traveltype != TRAVEL_JUMPPAD && + reach.traveltype != TRAVEL_ELEVATOR && + reach.traveltype != TRAVEL_FUNCBOB ) { + if ( BotAddToTarget( reach.start, reach.end, lookahead, &dist, target ) ) { + return qtrue; + } + } //end if + reachnum = BotGetReachabilityToGoal( reach.end, reach.areanum, -1, + ms->lastgoalareanum, lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags ); + VectorCopy( reach.end, end ); + lastareanum = reach.areanum; + if ( lastareanum == goal->areanum ) { + BotAddToTarget( reach.end, goal->origin, lookahead, &dist, target ); + return qtrue; + } //end if + } //end while + // + return qfalse; +} //end of the function BotMovementViewTarget +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotVisible( int ent, vec3_t eye, vec3_t target ) { + bsp_trace_t trace; + + trace = AAS_Trace( eye, NULL, NULL, target, ent, CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + if ( trace.fraction >= 1 ) { + return qtrue; + } + return qfalse; +} //end of the function BotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotPredictVisiblePosition( vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target ) { + aas_reachability_t reach; + int reachnum, lastgoalareanum, lastareanum, i; + int avoidreach[MAX_AVOIDREACH]; + float avoidreachtimes[MAX_AVOIDREACH]; + int avoidreachtries[MAX_AVOIDREACH]; + vec3_t end; + + //if the bot has no goal or no last reachability + if ( !goal ) { + return qfalse; + } + //if the areanum is not valid + if ( !areanum ) { + return qfalse; + } + //if the goal areanum is not valid + if ( !goal->areanum ) { + return qfalse; + } + + memset( avoidreach, 0, MAX_AVOIDREACH * sizeof( int ) ); + lastgoalareanum = goal->areanum; + lastareanum = areanum; + VectorCopy( origin, end ); + //only do 20 hops + for ( i = 0; i < 20 && ( areanum != goal->areanum ); i++ ) + { + // + reachnum = BotGetReachabilityToGoal( end, areanum, -1, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + goal, travelflags, travelflags ); + if ( !reachnum ) { + return qfalse; + } + AAS_ReachabilityFromNum( reachnum, &reach ); + // + if ( BotVisible( goal->entitynum, goal->origin, reach.start ) ) { + VectorCopy( reach.start, target ); + return qtrue; + } //end if + // + if ( BotVisible( goal->entitynum, goal->origin, reach.end ) ) { + VectorCopy( reach.end, target ); + return qtrue; + } //end if + // + if ( reach.areanum == goal->areanum ) { + VectorCopy( reach.end, target ); + return qtrue; + } //end if + // + lastareanum = areanum; + areanum = reach.areanum; + VectorCopy( reach.end, end ); + // + } //end while + // + return qfalse; +} //end of the function BotPredictVisiblePosition +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MoverBottomCenter( aas_reachability_t *reach, vec3_t bottomcenter ) { + int modelnum; + vec3_t mins, maxs, origin, mids; + vec3_t angles = {0, 0, 0}; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, origin ); + // + if ( !AAS_OriginOfEntityWithModelNum( modelnum, origin ) ) { + botimport.Print( PRT_MESSAGE, "no entity with model %d\n", modelnum ); + } //end if + //get a point just above the plat in the bottom position + VectorAdd( mins, maxs, mids ); + VectorMA( origin, 0.5, mids, bottomcenter ); + bottomcenter[2] = reach->start[2]; +} //end of the function MoverBottomCenter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotGapDistance( vec3_t origin, vec3_t hordir, int entnum ) { + float dist, startz; + vec3_t start, end; + aas_trace_t trace; + + //do gap checking + startz = origin[2]; + //this enables walking down stairs more fluidly + { + VectorCopy( origin, start ); + VectorCopy( origin, end ); + end[2] -= 60; + trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, entnum ); + if ( trace.fraction >= 1 ) { + return 1; + } + startz = trace.endpos[2] + 1; + } + // + for ( dist = 8; dist <= 100; dist += 8 ) + { + VectorMA( origin, dist, hordir, start ); + start[2] = startz + 24; + VectorCopy( start, end ); + end[2] -= 48 + sv_maxbarrier; + trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, entnum ); + //if solid is found the bot can't walk any further and fall into a gap + if ( !trace.startsolid ) { + //if it is a gap + if ( trace.endpos[2] < startz - sv_maxstep - 8 ) { + VectorCopy( trace.endpos, end ); + end[2] -= 20; + if ( AAS_PointContents( end ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) { + break; //----(SA) modified since slime is no longer deadly + } +// if (AAS_PointContents(end) & CONTENTS_WATER) break; + //if a gap is found slow down + //botimport.Print(PRT_MESSAGE, "gap at %f\n", dist); + return dist; + } //end if + startz = trace.endpos[2]; + } //end if + } //end for + return 0; +} //end of the function BotGapDistance +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotCheckBarrierJump( bot_movestate_t *ms, vec3_t dir, float speed ) { + vec3_t start, hordir, end; + aas_trace_t trace; + + VectorCopy( ms->origin, end ); + end[2] += sv_maxbarrier; + //trace right up + trace = AAS_TraceClientBBox( ms->origin, end, PRESENCE_NORMAL, ms->entitynum ); + //this shouldn't happen... but we check anyway + if ( trace.startsolid ) { + return qfalse; + } + //if very low ceiling it isn't possible to jump up to a barrier + if ( trace.endpos[2] - ms->origin[2] < sv_maxstep ) { + return qfalse; + } + // + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + VectorMA( ms->origin, ms->thinktime * speed * 0.5, hordir, end ); + VectorCopy( trace.endpos, start ); + end[2] = trace.endpos[2]; + //trace from previous trace end pos horizontally in the move direction + trace = AAS_TraceClientBBox( start, end, PRESENCE_NORMAL, ms->entitynum ); + //again this shouldn't happen + if ( trace.startsolid ) { + return qfalse; + } + // + VectorCopy( trace.endpos, start ); + VectorCopy( trace.endpos, end ); + end[2] = ms->origin[2]; + //trace down from the previous trace end pos + trace = AAS_TraceClientBBox( start, end, PRESENCE_NORMAL, ms->entitynum ); + //if solid + if ( trace.startsolid ) { + return qfalse; + } + //if no obstacle at all + if ( trace.fraction >= 1.0 ) { + return qfalse; + } + //if less than the maximum step height + if ( trace.endpos[2] - ms->origin[2] < sv_maxstep ) { + return qfalse; + } + // + EA_Jump( ms->client ); + EA_Move( ms->client, hordir, speed ); + ms->moveflags |= MFL_BARRIERJUMP; + //there is a barrier + return qtrue; +} //end of the function BotCheckBarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSwimInDirection( bot_movestate_t *ms, vec3_t dir, float speed, int type ) { + vec3_t normdir; + + VectorCopy( dir, normdir ); + VectorNormalize( normdir ); + EA_Move( ms->client, normdir, speed ); + return qtrue; +} //end of the function BotSwimInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotWalkInDirection( bot_movestate_t *ms, vec3_t dir, float speed, int type ) { + vec3_t hordir, cmdmove, velocity, tmpdir, origin; + int presencetype, maxframes, cmdframes, stopevent; + aas_clientmove_t move; + float dist; + + //if the bot is on the ground + if ( ms->moveflags & MFL_ONGROUND ) { + //if there is a barrier the bot can jump on + if ( BotCheckBarrierJump( ms, dir, speed ) ) { + return qtrue; + } + //remove barrier jump flag + ms->moveflags &= ~MFL_BARRIERJUMP; + //get the presence type for the movement + if ( ( type & MOVE_CROUCH ) && !( type & MOVE_JUMP ) ) { + presencetype = PRESENCE_CROUCH; + } else { presencetype = PRESENCE_NORMAL;} + //horizontal direction + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + //if the bot is not supposed to jump + if ( !( type & MOVE_JUMP ) ) { + //if there is a gap, try to jump over it + if ( BotGapDistance( ms->origin, hordir, ms->entitynum ) > 0 ) { + type |= MOVE_JUMP; + } + } //end if + //get command movement + VectorScale( hordir, speed, cmdmove ); + VectorCopy( ms->velocity, velocity ); + // + if ( type & MOVE_JUMP ) { + //botimport.Print(PRT_MESSAGE, "trying jump\n"); + cmdmove[2] = 400; + maxframes = PREDICTIONTIME_JUMP / 0.1; + cmdframes = 1; + stopevent = SE_HITGROUND | SE_HITGROUNDDAMAGE | + SE_ENTERWATER | SE_ENTERSLIME | SE_ENTERLAVA; + } //end if + else + { + maxframes = 2; + cmdframes = 2; + stopevent = SE_HITGROUNDDAMAGE | + SE_ENTERWATER | SE_ENTERSLIME | SE_ENTERLAVA; + } //end else + //AAS_ClearShownDebugLines(); + // + VectorCopy( ms->origin, origin ); + origin[2] += 0.5; + AAS_PredictClientMovement( &move, ms->entitynum, origin, presencetype, qtrue, + velocity, cmdmove, cmdframes, maxframes, 0.1, + stopevent, 0, qfalse ); //qtrue); + //if prediction time wasn't enough to fully predict the movement + if ( move.frames >= maxframes && ( type & MOVE_JUMP ) ) { + //botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client); + return qfalse; + } //end if + //don't enter slime or lava and don't fall from too high + if ( move.stopevent & ( SE_ENTERLAVA | SE_HITGROUNDDAMAGE ) ) { //----(SA) modified since slime is no longer deadly +// if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + //botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client); + //if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n"); + //if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n"); + //if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n"); + return qfalse; + } //end if + //if ground was hit + if ( move.stopevent & SE_HITGROUND ) { + //check for nearby gap + VectorNormalize2( move.velocity, tmpdir ); + dist = BotGapDistance( move.endpos, tmpdir, ms->entitynum ); + if ( dist > 0 ) { + return qfalse; + } + // + dist = BotGapDistance( move.endpos, hordir, ms->entitynum ); + if ( dist > 0 ) { + return qfalse; + } + } //end if + //get horizontal movement + tmpdir[0] = move.endpos[0] - ms->origin[0]; + tmpdir[1] = move.endpos[1] - ms->origin[1]; + tmpdir[2] = 0; + // + //AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE); + //the bot is blocked by something + if ( VectorLength( tmpdir ) < speed * ms->thinktime * 0.5 ) { + return qfalse; + } + //perform the movement + if ( type & MOVE_JUMP ) { + EA_Jump( ms->client ); + } + if ( type & MOVE_CROUCH ) { + EA_Crouch( ms->client ); + } + EA_Move( ms->client, hordir, speed ); + //movement was succesfull + return qtrue; + } //end if + else + { + if ( ms->moveflags & MFL_BARRIERJUMP ) { + //if near the top or going down + if ( ms->velocity[2] < 50 ) { + EA_Move( ms->client, dir, speed ); + } //end if + } //end if + //FIXME: do air control to avoid hazards + return qtrue; + } //end else +} //end of the function BotWalkInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotMoveInDirection( int movestate, vec3_t dir, float speed, int type ) { + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle( movestate ); + if ( !ms ) { + return qfalse; + } + //if swimming + if ( AAS_Swimming( ms->origin ) ) { + return BotSwimInDirection( ms, dir, speed, type ); + } //end if + else + { + return BotWalkInDirection( ms, dir, speed, type ); + } //end else +} //end of the function BotMoveInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Intersection( vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out ) { + float x1, dx1, dy1, x2, dx2, dy2, d; + + dx1 = p2[0] - p1[0]; + dy1 = p2[1] - p1[1]; + dx2 = p4[0] - p3[0]; + dy2 = p4[1] - p3[1]; + + d = dy1 * dx2 - dx1 * dy2; + if ( d != 0 ) { + x1 = p1[1] * dx1 - p1[0] * dy1; + x2 = p3[1] * dx2 - p3[0] * dy2; + out[0] = (int) ( ( dx1 * x2 - dx2 * x1 ) / d ); + out[1] = (int) ( ( dy1 * x2 - dy2 * x1 ) / d ); + return qtrue; + } //end if + else + { + return qfalse; + } //end else +} //end of the function Intersection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckBlocked( bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result ) { + vec3_t mins, maxs, end, up = {0, 0, 1}; + bsp_trace_t trace; + + //test for entities obstructing the bot's path + AAS_PresenceTypeBoundingBox( ms->presencetype, mins, maxs ); + // + if ( fabs( DotProduct( dir, up ) ) < 0.7 ) { + mins[2] += sv_maxstep; //if the bot can step on + maxs[2] -= 10; //a little lower to avoid low ceiling + } //end if + VectorMA( ms->origin, 3, dir, end ); + trace = AAS_Trace( ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY ); + //if not started in solid and not hitting the world entity + if ( !trace.startsolid && ( trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE ) ) { + result->blocked = qtrue; + result->blockentity = trace.ent; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + //if not in an area with reachability + else if ( checkbottom && !AAS_AreaReachability( ms->areanum ) ) { + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox( ms->presencetype, mins, maxs ); + VectorMA( ms->origin, -3, up, end ); + trace = AAS_Trace( ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP ); + if ( !trace.startsolid && ( trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE ) ) { + result->blocked = qtrue; + result->blockentity = trace.ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + } //end else +} //end of the function BotCheckBlocked +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotClearMoveResult( bot_moveresult_t *moveresult ) { + moveresult->failure = qfalse; + moveresult->type = 0; + moveresult->blocked = qfalse; + moveresult->blockentity = 0; + moveresult->traveltype = 0; + moveresult->flags = 0; +} //end of the function BotClearMoveResult +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Walk( bot_movestate_t *ms, aas_reachability_t *reach ) { + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize( hordir ); + // + BotCheckBlocked( ms, hordir, qtrue, &result ); + // + // Ridah, tweaked this +// if (dist < 10) + if ( dist < 32 ) { + //walk straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize( hordir ); + } //end if + //if going towards a crouch area + + // Ridah, some areas have a 0 presence (!?!) +// if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL)) + if ( ( AAS_AreaPresenceType( reach->areanum ) & PRESENCE_CROUCH ) && + !( AAS_AreaPresenceType( reach->areanum ) & PRESENCE_NORMAL ) ) { + //if pretty close to the reachable area + if ( dist < 20 ) { + EA_Crouch( ms->client ); + } + } //end if + // + dist = BotGapDistance( ms->origin, hordir, ms->entitynum ); + // + if ( ms->moveflags & MFL_WALK ) { + if ( dist > 0 ) { + speed = 200 - ( 180 - 1 * dist ); + } else { speed = 200;} + EA_Walk( ms->client ); + } //end if + else + { + if ( dist > 0 ) { + speed = 400 - ( 360 - 2 * dist ); + } else { speed = 400;} + } //end else + //elemantary action move in direction + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Walk( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //if not on the ground and changed areas... don't walk back!! + //(doesn't seem to help) + /* + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + if (ms->areanum == reach->areanum) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n"); +#endif //DEBUG + return result; + } //end if*/ + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize( hordir ); + // + if ( dist > 100 ) { + dist = 100; + } + speed = 400 - ( 400 - 3 * dist ); + // + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotFinishTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Crouch( bot_movestate_t *ms, aas_reachability_t *reach ) { + float speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + // + speed = 400; + //walk straight to reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + // + BotCheckBlocked( ms, hordir, qtrue, &result ); + //elemantary actions + EA_Crouch( ms->client ); + EA_Move( ms->client, hordir, speed ); + // + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotTravel_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BarrierJump( bot_movestate_t *ms, aas_reachability_t *reach ) { + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //walk straight to reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize( hordir ); + // + BotCheckBlocked( ms, hordir, qtrue, &result ); + //if pretty close to the barrier + if ( dist < 9 ) { + EA_Jump( ms->client ); + + // Ridah, do the movement also, so we have momentum to get onto the barrier + hordir[0] = reach->end[0] - reach->start[0]; + hordir[1] = reach->end[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + + dist = 60; + speed = 360 - ( 360 - 6 * dist ); + EA_Move( ms->client, hordir, speed ); + // done. + } //end if + else + { + if ( dist > 60 ) { + dist = 60; + } + speed = 360 - ( 360 - 6 * dist ); + EA_Move( ms->client, hordir, speed ); + } //end else + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_BarrierJump( bot_movestate_t *ms, aas_reachability_t *reach ) { + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //if near the top or going down + if ( ms->velocity[2] < 250 ) { + // Ridah, extend the end point a bit, so we strive to get over the ledge more + vec3_t end; + + VectorSubtract( reach->end, reach->start, end ); + end[2] = 0; + VectorNormalize( end ); + VectorMA( reach->end, 32, end, end ); + hordir[0] = end[0] - ms->origin[0]; + hordir[1] = end[1] - ms->origin[1]; +// hordir[0] = reach->end[0] - ms->origin[0]; +// hordir[1] = reach->end[1] - ms->origin[1]; + // done. + hordir[2] = 0; + dist = VectorNormalize( hordir ); + // + BotCheckBlocked( ms, hordir, qtrue, &result ); + // + if ( dist > 60 ) { + dist = 60; + } + speed = 400 - ( 400 - 6 * dist ); + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + } //end if + // + return result; +} //end of the function BotFinishTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Swim( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t dir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //swim straight to reachability end + VectorSubtract( reach->start, ms->origin, dir ); + VectorNormalize( dir ); + // + BotCheckBlocked( ms, dir, qtrue, &result ); + //elemantary actions + EA_Move( ms->client, dir, 400 ); + // + VectorCopy( dir, result.movedir ); + Vector2Angles( dir, result.ideal_viewangles ); + result.flags |= MOVERESULT_SWIMVIEW; + // + return result; +} //end of the function BotTravel_Swim +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WaterJump( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t dir, hordir; + float dist; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //swim straight to reachability end + VectorSubtract( reach->end, ms->origin, dir ); + VectorCopy( dir, hordir ); + hordir[2] = 0; + dir[2] += 15 + crandom() * 40; + //botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]); + VectorNormalize( dir ); + dist = VectorNormalize( hordir ); + //elemantary actions + //EA_Move(ms->client, dir, 400); + EA_MoveForward( ms->client ); + //move up if close to the actual out of water jump spot + if ( dist < 40 ) { + EA_MoveUp( ms->client ); + } + //set the ideal view angles + Vector2Angles( dir, result.ideal_viewangles ); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy( dir, result.movedir ); + // + return result; +} //end of the function BotTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WaterJump( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t dir, pnt; + float dist; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n"); + BotClearMoveResult( &result ); + //if waterjumping there's nothing to do + if ( ms->moveflags & MFL_WATERJUMP ) { + return result; + } + //if not touching any water anymore don't do anything + //otherwise the bot sometimes keeps jumping? + VectorCopy( ms->origin, pnt ); + pnt[2] -= 32; //extra for q2dm4 near red armor/mega health + if ( !( AAS_PointContents( pnt ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { + return result; + } + //swim straight to reachability end + VectorSubtract( reach->end, ms->origin, dir ); + dir[0] += crandom() * 10; + dir[1] += crandom() * 10; + dir[2] += 70 + crandom() * 10; + dist = VectorNormalize( dir ); + //elemantary actions + EA_Move( ms->client, dir, 400 ); + //set the ideal view angles + Vector2Angles( dir, result.ideal_viewangles ); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy( dir, result.movedir ); + // + return result; +} //end of the function BotFinishTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WalkOffLedge( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t hordir, dir; + float dist, speed, reachhordist; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //check if the bot is blocked by anything + VectorSubtract( reach->start, ms->origin, dir ); + VectorNormalize( dir ); + BotCheckBlocked( ms, dir, qtrue, &result ); + //if the reachability start and end are practially above each other + VectorSubtract( reach->end, reach->start, dir ); + dir[2] = 0; + reachhordist = VectorLength( dir ); + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize( hordir ); + //if pretty close to the start focus on the reachability end + + // Ridah, tweaked this +#if 0 + if ( dist < 48 ) { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; +#else + if ( ( dist < 72 ) && ( DotProduct( dir, hordir ) < 0 ) ) { // walk in the direction of start -> end + //hordir[0] = reach->end[0] - ms->origin[0]; + //hordir[1] = reach->end[1] - ms->origin[1]; + //VectorNormalize( dir ); + //VectorMA( hordir, 48, dir, hordir ); + //hordir[2] = 0; + + VectorCopy( dir, hordir ); +#endif + VectorNormalize( hordir ); + // + if ( reachhordist < 20 ) { + speed = 100; + } //end if + else if ( !AAS_HorizontalVelocityForJump( 0, reach->start, reach->end, &speed ) ) { + speed = 400; + } //end if + // looks better crouching off a ledge + EA_Crouch( ms->client ); + } //end if + else + { + if ( reachhordist < 20 ) { + if ( dist > 64 ) { + dist = 64; + } + speed = 400 - ( 256 - 4 * dist ); + } //end if + else + { + speed = 400; + // Ridah, tweaked this + if ( dist < 128 ) { + speed *= ( dist / 128 ); + } + } //end else + } //end else + // + BotCheckBlocked( ms, hordir, qtrue, &result ); + //elemantary action + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAirControl( vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed ) { + vec3_t org, vel; + float dist; + int i; + + VectorCopy( origin, org ); + VectorScale( velocity, 0.1, vel ); + for ( i = 0; i < 50; i++ ) + { + vel[2] -= sv_gravity * 0.01; + //if going down and next position would be below the goal + if ( vel[2] < 0 && org[2] + vel[2] < goal[2] ) { + VectorScale( vel, ( goal[2] - org[2] ) / vel[2], vel ); + VectorAdd( org, vel, org ); + VectorSubtract( goal, org, dir ); + dist = VectorNormalize( dir ); + if ( dist > 32 ) { + dist = 32; + } + *speed = 400 - ( 400 - 13 * dist ); + return qtrue; + } //end if + else + { + VectorAdd( org, vel, org ); + } //end else + } //end for + VectorSet( dir, 0, 0, 0 ); + *speed = 400; + return qfalse; +} //end of the function BotAirControl +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WalkOffLedge( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t dir, hordir, end, v; + float dist, speed; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + // + VectorSubtract( reach->end, ms->origin, dir ); + BotCheckBlocked( ms, dir, qtrue, &result ); + // + VectorSubtract( reach->end, ms->origin, v ); + v[2] = 0; + dist = VectorNormalize( v ); + if ( dist > 16 ) { + VectorMA( reach->end, 16, v, end ); + } else { VectorCopy( reach->end, end );} + // + if ( !BotAirControl( ms->origin, ms->velocity, end, hordir, &speed ) ) { + //go straight to the reachability end + VectorCopy( dir, hordir ); + hordir[2] = 0; + // + dist = VectorNormalize( hordir ); + speed = 400; + } //end if + // + // looks better crouching off a ledge + EA_Crouch( ms->client ); + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotFinishTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, gapdist, speed, horspeed, sv_jumpvel; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + sv_jumpvel = botlibglobals.sv_jumpvel->value; + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + speed = 350; + // + gapdist = BotGapDistance(ms, hordir, ms->entitynum); + //if pretty close to the start focus on the reachability end + if (dist < 50 || (gapdist && gapdist < 50)) + { + //NOTE: using max speed (400) works best + //if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed)) + //{ + // speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + //} //end if + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + // + ms->jumpreach = ms->lastreachnum; + speed = 600; + } //end if + else + { + if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed)) + { + speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + } //end if + } //end else + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, mins, maxs, start, end; + float dist1, dist2, speed; + bot_moveresult_t result; + bsp_trace_t trace; + + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + //minus back the bouding box size plus 16 + VectorMA(reach->start, 80, hordir, end); + // + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); + //check for solids + trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID); + if (trace.startsolid) VectorCopy(start, trace.endpos); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (dist1 < 80) VectorMA(reach->start, dist1, hordir, trace.endpos); +// dist1 = BotGapDistance(start, hordir, ms->entitynum); +// if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, trace.endpos, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run to point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + //botimport.Print(PRT_MESSAGE, "going towards run to point\n"); + hordir[0] = trace.endpos[0] - ms->origin[0]; + hordir[1] = trace.endpos[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//* +bot_moveresult_t BotTravel_Jump( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t hordir, dir1, dir2, start, end, runstart; +// vec3_t runstart, dir1, dir2, hordir; + float dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + // + AAS_JumpReachRunStart( reach, runstart ); + //* + hordir[0] = runstart[0] - reach->start[0]; + hordir[1] = runstart[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + // + VectorCopy( reach->start, start ); + start[2] += 1; + VectorMA( reach->start, 80, hordir, runstart ); + //check for a gap + for ( dist1 = 0; dist1 < 80; dist1 += 10 ) + { + VectorMA( start, dist1 + 10, hordir, end ); + end[2] += 1; + if ( AAS_PointAreaNum( end ) != ms->reachareanum ) { + break; + } + } //end for + if ( dist1 < 80 ) { + VectorMA( reach->start, dist1, hordir, runstart ); + } + // + VectorSubtract( ms->origin, reach->start, dir1 ); + dir1[2] = 0; + dist1 = VectorNormalize( dir1 ); + VectorSubtract( ms->origin, runstart, dir2 ); + dir2[2] = 0; + dist2 = VectorNormalize( dir2 ); + //if just before the reachability start + if ( DotProduct( dir1, dir2 ) < -0.8 || dist2 < 5 ) { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + //elemantary action jump + if ( dist1 < 24 ) { + EA_Jump( ms->client ); + } else if ( dist1 < 32 ) { + EA_DelayedJump( ms->client ); + } + EA_Move( ms->client, hordir, 600 ); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { +// botimport.Print(PRT_MESSAGE, "going towards run start point\n"); + hordir[0] = runstart[0] - ms->origin[0]; + hordir[1] = runstart[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + // + if ( dist2 > 80 ) { + dist2 = 80; + } + speed = 400 - ( 400 - 5 * dist2 ); + EA_Move( ms->client, hordir, speed ); + } //end else + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotTravel_Jump*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Jump( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t hordir, hordir2; + float speed, dist; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //if not jumped yet + if ( !ms->jumpreach ) { + return result; + } + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize( hordir ); + // + hordir2[0] = reach->end[0] - reach->start[0]; + hordir2[1] = reach->end[1] - reach->start[1]; + hordir2[2] = 0; + VectorNormalize( hordir2 ); + // + if ( DotProduct( hordir, hordir2 ) < -0.5 && dist < 24 ) { + return result; + } + //always use max speed when traveling through the air + speed = 800; + // + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotFinishTravel_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Ladder( bot_movestate_t *ms, aas_reachability_t *reach ) { + //float dist, speed; + vec3_t dir, viewdir, hordir, pos; + vec3_t origin = {0, 0, 0}; +// vec3_t up = {0, 0, 1}; + bot_moveresult_t result; + float dist, speed; + + // RF, heavily modified, wolf has different ladder movement + + BotClearMoveResult( &result ); + // + if ( ( ms->moveflags & MFL_AGAINSTLADDER ) + //NOTE: not a good idea for ladders starting in water + || !( ms->moveflags & MFL_ONGROUND ) ) { + //botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n"); + // RF, wolf has different ladder movement + VectorSubtract( reach->end, reach->start, dir ); + VectorNormalize( dir ); + //set the ideal view angles, facing the ladder up or down + viewdir[0] = dir[0]; + viewdir[1] = dir[1]; + viewdir[2] = dir[2]; + if ( dir[2] < 0 ) { // going down, so face the other way + VectorInverse( viewdir ); + } else { + viewdir[2] = 0; // straight forward goes up + } + Vector2Angles( viewdir, result.ideal_viewangles ); + //elemantary action + EA_Move( ms->client, origin, 0 ); + EA_MoveForward( ms->client ); + //set movement view flag so the AI can see the view is focussed + result.flags |= MOVERESULT_MOVEMENTVIEW; + } //end if + else + { + //botimport.Print(PRT_MESSAGE, "moving towards ladder base\n"); + // find a postion back away from the base of the ladder + VectorSubtract( reach->end, reach->start, hordir ); + hordir[2] = 0; + VectorNormalize( hordir ); + VectorMA( reach->start, -24, hordir, pos ); + VectorSubtract( pos, ms->origin, dir ); + //make sure the horizontal movement is large anough + VectorCopy( dir, hordir ); + hordir[2] = 0; + dist = VectorNormalize( hordir ); + if ( dist < 32 ) { // within range, go for the end + //botimport.Print(PRT_MESSAGE, "found base, moving towards ladder top\n"); + VectorSubtract( reach->end, ms->origin, dir ); + //make sure the horizontal movement is large anough + VectorCopy( dir, hordir ); + hordir[2] = 0; + dist = VectorNormalize( hordir ); + } + // + dir[0] = hordir[0]; + dir[1] = hordir[1]; +// if (dist < 48) { +// if (dir[2] > 0) dir[2] = 1; +// else dir[2] = -1; +// } else { + dir[2] = 0; +// } + if ( dist > 50 ) { + dist = 50; + } + speed = 400 - ( 200 - 4 * dist ); + EA_Move( ms->client, dir, speed ); + } //end else + //save the movement direction + VectorCopy( dir, result.movedir ); + // + return result; +} //end of the function BotTravel_Ladder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Teleport( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t hordir; + float dist; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //if the bot is being teleported + if ( ms->moveflags & MFL_TELEPORTED ) { + return result; + } + + //walk straight to center of the teleporter + VectorSubtract( reach->start, ms->origin, hordir ); + if ( !( ms->moveflags & MFL_SWIMMING ) ) { + hordir[2] = 0; + } + dist = VectorNormalize( hordir ); + // + BotCheckBlocked( ms, hordir, qtrue, &result ); + + if ( dist < 30 ) { + EA_Move( ms->client, hordir, 200 ); + } else { EA_Move( ms->client, hordir, 400 );} + + if ( ms->moveflags & MFL_SWIMMING ) { + result.flags |= MOVERESULT_SWIMVIEW; + } + + VectorCopy( hordir, result.movedir ); + return result; +} //end of the function BotTravel_Teleport +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Elevator( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t dir, dir1, dir2, hordir, bottomcenter; + float dist, dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //if standing on the plat + if ( BotOnMover( ms->origin, ms->entitynum, reach ) ) { +#ifdef DEBUG_ELEVATOR + botimport.Print( PRT_MESSAGE, "bot on elevator\n" ); +#endif //DEBUG_ELEVATOR + //if vertically not too far from the end point + if ( abs( ms->origin[2] - reach->end[2] ) < sv_maxbarrier ) { +#ifdef DEBUG_ELEVATOR + botimport.Print( PRT_MESSAGE, "bot moving to end\n" ); +#endif //DEBUG_ELEVATOR + //move to the end point + VectorSubtract( reach->end, ms->origin, hordir ); + hordir[2] = 0; + VectorNormalize( hordir ); + if ( !BotCheckBarrierJump( ms, hordir, 100 ) ) { + EA_Move( ms->client, hordir, 400 ); + } //end if + VectorCopy( hordir, result.movedir ); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter( reach, bottomcenter ); + VectorSubtract( bottomcenter, ms->origin, hordir ); + hordir[2] = 0; + dist = VectorNormalize( hordir ); + // + if ( dist > 10 ) { +#ifdef DEBUG_ELEVATOR + botimport.Print( PRT_MESSAGE, "bot moving to center\n" ); +#endif //DEBUG_ELEVATOR + //move to the center of the plat + if ( dist > 100 ) { + dist = 100; + } + speed = 400 - ( 400 - 4 * dist ); + // + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_ELEVATOR + botimport.Print( PRT_MESSAGE, "bot not on elevator\n" ); +#endif //DEBUG_ELEVATOR + //if very near the reachability end + VectorSubtract( reach->end, ms->origin, dir ); + dist = VectorLength( dir ); + if ( dist < 64 ) { + if ( dist > 60 ) { + dist = 60; + } + speed = 360 - ( 360 - 6 * dist ); + // + if ( ( ms->moveflags & MFL_SWIMMING ) || !BotCheckBarrierJump( ms, dir, 50 ) ) { + if ( speed > 5 ) { + EA_Move( ms->client, dir, speed ); + } + } //end if + VectorCopy( dir, result.movedir ); + // + if ( ms->moveflags & MFL_SWIMMING ) { + result.flags |= MOVERESULT_SWIMVIEW; + } + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract( reach->start, ms->origin, dir1 ); + if ( !( ms->moveflags & MFL_SWIMMING ) ) { + dir1[2] = 0; + } + dist1 = VectorNormalize( dir1 ); + //if the elevator isn't down + if ( !MoverDown( reach ) ) { +#ifdef DEBUG_ELEVATOR + botimport.Print( PRT_MESSAGE, "elevator not down\n" ); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy( dir1, dir ); + // + BotCheckBlocked( ms, dir, qfalse, &result ); + // + if ( dist > 60 ) { + dist = 60; + } + speed = 360 - ( 360 - 6 * dist ); + // + if ( !( ms->moveflags & MFL_SWIMMING ) && !BotCheckBarrierJump( ms, dir, 50 ) ) { + if ( speed > 5 ) { + EA_Move( ms->client, dir, speed ); + } + } //end if + VectorCopy( dir, result.movedir ); + // + if ( ms->moveflags & MFL_SWIMMING ) { + result.flags |= MOVERESULT_SWIMVIEW; + } + //this isn't a failure... just wait till the elevator comes down + //result.failure = qtrue; + result.type = RESULTTYPE_ELEVATORUP; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to elevator bottom center + MoverBottomCenter( reach, bottomcenter ); + VectorSubtract( bottomcenter, ms->origin, dir2 ); + if ( !( ms->moveflags & MFL_SWIMMING ) ) { + dir2[2] = 0; + } + dist2 = VectorNormalize( dir2 ); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and elevator center + if ( dist1 < 20 || dist2 < dist1 || DotProduct( dir1, dir2 ) < 0 ) { +#ifdef DEBUG_ELEVATOR + botimport.Print( PRT_MESSAGE, "bot moving to center\n" ); +#endif //DEBUG_ELEVATOR + dist = dist2; + VectorCopy( dir2, dir ); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_ELEVATOR + botimport.Print( PRT_MESSAGE, "bot moving to start\n" ); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy( dir1, dir ); + } //end else + // + BotCheckBlocked( ms, dir, qfalse, &result ); + // + if ( dist > 60 ) { + dist = 60; + } + speed = 400 - ( 400 - 6 * dist ); + // + if ( !( ms->moveflags & MFL_SWIMMING ) && !BotCheckBarrierJump( ms, dir, 50 ) ) { + EA_Move( ms->client, dir, speed ); + } //end if + VectorCopy( dir, result.movedir ); + // + if ( ms->moveflags & MFL_SWIMMING ) { + result.flags |= MOVERESULT_SWIMVIEW; + } + } //end else + return result; +} //end of the function BotTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Elevator( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t bottomcenter, bottomdir, topdir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + // + MoverBottomCenter( reach, bottomcenter ); + VectorSubtract( bottomcenter, ms->origin, bottomdir ); + // + VectorSubtract( reach->end, ms->origin, topdir ); + // + if ( fabs( bottomdir[2] ) < fabs( topdir[2] ) ) { + VectorNormalize( bottomdir ); + EA_Move( ms->client, bottomdir, 300 ); + } //end if + else + { + VectorNormalize( topdir ); + EA_Move( ms->client, topdir, 300 ); + } //end else + return result; +} //end of the function BotFinishTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFuncBobStartEnd( aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin ) { + int spawnflags, modelnum; + vec3_t mins, maxs, mid, angles = {0, 0, 0}; + int num0, num1; + + modelnum = reach->facenum & 0x0000FFFF; + if ( !AAS_OriginOfEntityWithModelNum( modelnum, origin ) ) { + botimport.Print( PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum ); + VectorSet( start, 0, 0, 0 ); + VectorSet( end, 0, 0, 0 ); + return; + } //end if + AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, NULL ); + VectorAdd( mins, maxs, mid ); + VectorScale( mid, 0.5, mid ); + VectorCopy( mid, start ); + VectorCopy( mid, end ); + spawnflags = reach->facenum >> 16; + num0 = reach->edgenum >> 16; + if ( num0 > 0x00007FFF ) { + num0 |= 0xFFFF0000; + } + num1 = reach->edgenum & 0x0000FFFF; + if ( num1 > 0x00007FFF ) { + num1 |= 0xFFFF0000; + } + if ( spawnflags & 1 ) { + start[0] = num0; + end[0] = num1; + // + origin[0] += mid[0]; + origin[1] = mid[1]; + origin[2] = mid[2]; + } //end if + else if ( spawnflags & 2 ) { + start[1] = num0; + end[1] = num1; + // + origin[0] = mid[0]; + origin[1] += mid[1]; + origin[2] = mid[2]; + } //end else if + else + { + start[2] = num0; + end[2] = num1; + // + origin[0] = mid[0]; + origin[1] = mid[1]; + origin[2] += mid[2]; + } //end else +} //end of the function BotFuncBobStartEnd +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_FuncBobbing( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin; + float dist, dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + // + BotFuncBobStartEnd( reach, bob_start, bob_end, bob_origin ); + //if standing ontop of the func_bobbing + if ( BotOnMover( ms->origin, ms->entitynum, reach ) ) { +#ifdef DEBUG_FUNCBOB + botimport.Print( PRT_MESSAGE, "bot on func_bobbing\n" ); +#endif + //if near end point of reachability + VectorSubtract( bob_origin, bob_end, dir ); + if ( VectorLength( dir ) < 24 ) { +#ifdef DEBUG_FUNCBOB + botimport.Print( PRT_MESSAGE, "bot moving to reachability end\n" ); +#endif + //move to the end point + VectorSubtract( reach->end, ms->origin, hordir ); + hordir[2] = 0; + VectorNormalize( hordir ); + if ( !BotCheckBarrierJump( ms, hordir, 100 ) ) { + EA_Move( ms->client, hordir, 400 ); + } //end if + VectorCopy( hordir, result.movedir ); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter( reach, bottomcenter ); + VectorSubtract( bottomcenter, ms->origin, hordir ); + hordir[2] = 0; + dist = VectorNormalize( hordir ); + // + if ( dist > 10 ) { +#ifdef DEBUG_FUNCBOB + botimport.Print( PRT_MESSAGE, "bot moving to func_bobbing center\n" ); +#endif + //move to the center of the plat + if ( dist > 100 ) { + dist = 100; + } + speed = 400 - ( 400 - 4 * dist ); + // + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_FUNCBOB + botimport.Print( PRT_MESSAGE, "bot not ontop of func_bobbing\n" ); +#endif + //if very near the reachability end + VectorSubtract( reach->end, ms->origin, dir ); + dist = VectorLength( dir ); + if ( dist < 64 ) { +#ifdef DEBUG_FUNCBOB + botimport.Print( PRT_MESSAGE, "bot moving to end\n" ); +#endif + if ( dist > 60 ) { + dist = 60; + } + speed = 360 - ( 360 - 6 * dist ); + //if swimming or no barrier jump + if ( ( ms->moveflags & MFL_SWIMMING ) || !BotCheckBarrierJump( ms, dir, 50 ) ) { + if ( speed > 5 ) { + EA_Move( ms->client, dir, speed ); + } + } //end if + VectorCopy( dir, result.movedir ); + // + if ( ms->moveflags & MFL_SWIMMING ) { + result.flags |= MOVERESULT_SWIMVIEW; + } + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract( reach->start, ms->origin, dir1 ); + if ( !( ms->moveflags & MFL_SWIMMING ) ) { + dir1[2] = 0; + } + dist1 = VectorNormalize( dir1 ); + //if func_bobbing is Not it's start position + VectorSubtract( bob_origin, bob_start, dir ); + if ( VectorLength( dir ) > 16 ) { +#ifdef DEBUG_FUNCBOB + botimport.Print( PRT_MESSAGE, "func_bobbing not at start\n" ); +#endif + dist = dist1; + VectorCopy( dir1, dir ); + // + BotCheckBlocked( ms, dir, qfalse, &result ); + // + if ( dist > 60 ) { + dist = 60; + } + speed = 360 - ( 360 - 6 * dist ); + // + if ( !( ms->moveflags & MFL_SWIMMING ) && !BotCheckBarrierJump( ms, dir, 50 ) ) { + if ( speed > 5 ) { + EA_Move( ms->client, dir, speed ); + } + } //end if + VectorCopy( dir, result.movedir ); + // + if ( ms->moveflags & MFL_SWIMMING ) { + result.flags |= MOVERESULT_SWIMVIEW; + } + //this isn't a failure... just wait till the func_bobbing arrives + result.type = RESULTTYPE_ELEVATORUP; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to func_bob bottom center + MoverBottomCenter( reach, bottomcenter ); + VectorSubtract( bottomcenter, ms->origin, dir2 ); + if ( !( ms->moveflags & MFL_SWIMMING ) ) { + dir2[2] = 0; + } + dist2 = VectorNormalize( dir2 ); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and func_bobbing center + if ( dist1 < 20 || dist2 < dist1 || DotProduct( dir1, dir2 ) < 0 ) { +#ifdef DEBUG_FUNCBOB + botimport.Print( PRT_MESSAGE, "bot moving to func_bobbing center\n" ); +#endif + dist = dist2; + VectorCopy( dir2, dir ); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_FUNCBOB + botimport.Print( PRT_MESSAGE, "bot moving to reachability start\n" ); +#endif + dist = dist1; + VectorCopy( dir1, dir ); + } //end else + // + BotCheckBlocked( ms, dir, qfalse, &result ); + // + if ( dist > 60 ) { + dist = 60; + } + speed = 400 - ( 400 - 6 * dist ); + // + if ( !( ms->moveflags & MFL_SWIMMING ) && !BotCheckBarrierJump( ms, dir, 50 ) ) { + EA_Move( ms->client, dir, speed ); + } //end if + VectorCopy( dir, result.movedir ); + // + if ( ms->moveflags & MFL_SWIMMING ) { + result.flags |= MOVERESULT_SWIMVIEW; + } + } //end else + return result; +} //end of the function BotTravel_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_FuncBobbing( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter; + bot_moveresult_t result; + float dist, speed; + + BotClearMoveResult( &result ); + // + BotFuncBobStartEnd( reach, bob_start, bob_end, bob_origin ); + // + VectorSubtract( bob_origin, bob_end, dir ); + dist = VectorLength( dir ); + //if the func_bobbing is near the end + if ( dist < 16 ) { + VectorSubtract( reach->end, ms->origin, hordir ); + if ( !( ms->moveflags & MFL_SWIMMING ) ) { + hordir[2] = 0; + } + dist = VectorNormalize( hordir ); + // + if ( dist > 60 ) { + dist = 60; + } + speed = 360 - ( 360 - 6 * dist ); + // + if ( speed > 5 ) { + EA_Move( ms->client, dir, speed ); + } + VectorCopy( dir, result.movedir ); + // + if ( ms->moveflags & MFL_SWIMMING ) { + result.flags |= MOVERESULT_SWIMVIEW; + } + } //end if + else + { + MoverBottomCenter( reach, bottomcenter ); + VectorSubtract( bottomcenter, ms->origin, hordir ); + if ( !( ms->moveflags & MFL_SWIMMING ) ) { + hordir[2] = 0; + } + dist = VectorNormalize( hordir ); + // + if ( dist > 5 ) { + //move to the center of the plat + if ( dist > 100 ) { + dist = 100; + } + speed = 400 - ( 400 - 4 * dist ); + // + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + } //end if + } //end else + return result; +} //end of the function BotFinishTravel_FuncBobbing +//=========================================================================== +// 0 no valid grapple hook visible +// 1 the grapple hook is still flying +// 2 the grapple hooked into a wall +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach) +{ + static int grapplemodelindex; + int i; + vec3_t dir; + aas_entityinfo_t entinfo; + + if (!grapplemodelindex) + { + grapplemodelindex = AAS_IndexFromModel("models/weapons/grapple/hook/tris.md2"); + } //end if + for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i)) + { + if (AAS_EntityModelindex(i) == grapplemodelindex) + { + AAS_EntityInfo(i, &entinfo); + if (VectorCompare(entinfo.origin, entinfo.old_origin)) + { + VectorSubtract(entinfo.origin, reach->end, dir); + //if hooked near the reachability end + if (VectorLength(dir) < 32) return 2; + } //end if + else + { + //still shooting hook + return 1; + } //end else + } //end if + } //end if + //no valid grapple at all + return 0; +} //end of the function GrappleState*/ +//=========================================================================== +// 0 no valid grapple hook visible +// 1 the grapple hook is still flying +// 2 the grapple hooked into a wall +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GrappleState( bot_movestate_t *ms, aas_reachability_t *reach ) { + static int grapplemodelindex; + static libvar_t *laserhook; + int i; + vec3_t dir; + aas_entityinfo_t entinfo; + + if ( !laserhook ) { + laserhook = LibVar( "laserhook", "0" ); + } + if ( !laserhook->value && !grapplemodelindex ) { + grapplemodelindex = AAS_IndexFromModel( "models/weapons/grapple/hook/tris.md2" ); + } //end if + for ( i = AAS_NextEntity( 0 ); i; i = AAS_NextEntity( i ) ) + { + if ( ( !laserhook->value && AAS_EntityModelindex( i ) == grapplemodelindex ) +// || (laserhook->value && (AAS_EntityRenderFX(i) & RF_BEAM)) + ) { + AAS_EntityInfo( i, &entinfo ); + //if the origin is equal to the last visible origin + if ( VectorCompare( entinfo.origin, entinfo.lastvisorigin ) ) { + VectorSubtract( entinfo.origin, reach->end, dir ); + //if hooked near the reachability end + if ( VectorLength( dir ) < 32 ) { + return 2; + } + } //end if + else + { + //still shooting hook + return 1; + } //end else + } //end if + } //end for + //no valid grapple at all + return 0; +} //end of the function GrappleState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGrapple( bot_movestate_t *ms ) { + aas_reachability_t reach; + + AAS_ReachabilityFromNum( ms->lastreachnum, &reach ); + //if not using the grapple hook reachability anymore + if ( reach.traveltype != TRAVEL_GRAPPLEHOOK ) { + if ( ( ms->moveflags & MFL_ACTIVEGRAPPLE ) || ms->grapplevisible_time ) { + EA_Command( ms->client, CMD_HOOKOFF ); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->grapplevisible_time = 0; +#ifdef DEBUG_GRAPPLE + botimport.Print( PRT_MESSAGE, "reset grapple\n" ); +#endif //DEBUG_GRAPPLE + } //end if + } //end if +} //end of the function BotResetGrapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Grapple( bot_movestate_t *ms, aas_reachability_t *reach ) { + bot_moveresult_t result; + float dist, speed; + vec3_t dir, viewdir, org; + int state, areanum; + +#ifdef DEBUG_GRAPPLE + static int debugline; + if ( !debugline ) { + debugline = botimport.DebugLineCreate(); + } + botimport.DebugLineShow( debugline, reach->start, reach->end, LINECOLOR_BLUE ); +#endif //DEBUG_GRAPPLE + + BotClearMoveResult( &result ); + // + if ( ms->moveflags & MFL_GRAPPLERESET ) { + EA_Command( ms->client, CMD_HOOKOFF ); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + return result; + } //end if + // + if ( ms->moveflags & MFL_ACTIVEGRAPPLE ) { +#ifdef DEBUG_GRAPPLE + botimport.Print( PRT_MESSAGE, "BotTravel_Grapple: active grapple\n" ); +#endif //DEBUG_GRAPPLE + // + state = GrappleState( ms, reach ); + // + VectorSubtract( reach->end, ms->origin, dir ); + dir[2] = 0; + dist = VectorLength( dir ); + //if very close to the grapple end or + //the grappled is hooked and the bot doesn't get any closer + if ( state && dist < 48 ) { + if ( ms->lastgrappledist - dist < 1 ) { + EA_Command( ms->client, CMD_HOOKOFF ); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability +#ifdef DEBUG_GRAPPLE + botimport.Print( PRT_ERROR, "grapple normal end\n" ); +#endif //DEBUG_GRAPPLE + } //end if + } //end if + //if no valid grapple at all, or the grapple hooked and the bot + //isn't moving anymore + else if ( !state || ( state == 2 && dist > ms->lastgrappledist - 2 ) ) { + if ( ms->grapplevisible_time < AAS_Time() - 0.4 ) { +#ifdef DEBUG_GRAPPLE + botimport.Print( PRT_ERROR, "grapple not visible\n" ); +#endif //DEBUG_GRAPPLE + EA_Command( ms->client, CMD_HOOKOFF ); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability + //result.failure = qtrue; + //result.type = RESULTTYPE_INVISIBLEGRAPPLE; + return result; + } //end if + } //end if + else + { + ms->grapplevisible_time = AAS_Time(); + } //end else + //remember the current grapple distance + ms->lastgrappledist = dist; + } //end if + else + { +#ifdef DEBUG_GRAPPLE + botimport.Print( PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n" ); +#endif //DEBUG_GRAPPLE + // + ms->grapplevisible_time = AAS_Time(); + // + VectorSubtract( reach->start, ms->origin, dir ); + if ( !( ms->moveflags & MFL_SWIMMING ) ) { + dir[2] = 0; + } + VectorAdd( ms->origin, ms->viewoffset, org ); + VectorSubtract( reach->end, org, viewdir ); + // + dist = VectorNormalize( dir ); + Vector2Angles( viewdir, result.ideal_viewangles ); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + if ( dist < 5 && + fabs( AngleDiff( result.ideal_viewangles[0], ms->viewangles[0] ) ) < 2 && + fabs( AngleDiff( result.ideal_viewangles[1], ms->viewangles[1] ) ) < 2 ) { +#ifdef DEBUG_GRAPPLE + botimport.Print( PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n" ); +#endif //DEBUG_GRAPPLE + EA_Command( ms->client, CMD_HOOKON ); + ms->moveflags |= MFL_ACTIVEGRAPPLE; + ms->lastgrappledist = 999999; + } //end if + else + { + if ( dist < 70 ) { + speed = 300 - ( 300 - 4 * dist ); + } else { speed = 400;} + // + BotCheckBlocked( ms, dir, qtrue, &result ); + //elemantary action move in direction + EA_Move( ms->client, dir, speed ); + VectorCopy( dir, result.movedir ); + } //end else + //if in another area before actually grappling + areanum = AAS_PointAreaNum( ms->origin ); + if ( areanum && areanum != ms->reachareanum ) { + ms->reachability_time = 0; + } + } //end else + return result; +} //end of the function BotTravel_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_RocketJump( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); + BotClearMoveResult( &result ); + // + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + dist = VectorNormalize( hordir ); + // + if ( dist < 5 ) { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + //elemantary action jump + EA_Jump( ms->client ); + EA_Attack( ms->client ); + EA_Move( ms->client, hordir, 800 ); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + if ( dist > 80 ) { + dist = 80; + } + speed = 400 - ( 400 - 5 * dist ); + EA_Move( ms->client, hordir, speed ); + } //end else + // +/* + vec3_t hordir, dir1, dir2, start, end, runstart; + float dist1, dist2, speed; + bot_moveresult_t result; + + botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); + BotClearMoveResult(&result); + AAS_JumpReachRunStart(reach, runstart); + // + hordir[0] = runstart[0] - reach->start[0]; + hordir[1] = runstart[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + VectorMA(reach->start, 80, hordir, runstart); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (dist1 < 80) VectorMA(reach->start, dist1, hordir, runstart); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, runstart, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { +// botimport.Print(PRT_MESSAGE, "going towards run start point\n"); + hordir[0] = runstart[0] - ms->origin[0]; + hordir[1] = runstart[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + */ + //look in the movement direction + Vector2Angles( hordir, result.ideal_viewangles ); + //look straight down + result.ideal_viewangles[PITCH] = 90; + //set the view angles directly + EA_View( ms->client, result.ideal_viewangles ); + //view is important for the movment + result.flags |= MOVERESULT_MOVEMENTVIEWSET; + //select the rocket launcher + EA_SelectWeapon( ms->client, WEAPONINDEX_ROCKET_LAUNCHER ); + //weapon is used for movement + result.weapon = WEAPONINDEX_ROCKET_LAUNCHER; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + // + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotTravel_RocketJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BFGJump( bot_movestate_t *ms, aas_reachability_t *reach ) { + bot_moveresult_t result; + + BotClearMoveResult( &result ); + // + return result; +} //end of the function BotTravel_BFGJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WeaponJump( bot_movestate_t *ms, aas_reachability_t *reach ) { + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //if not jumped yet + if ( !ms->jumpreach ) { + return result; + } + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + //always use max speed when traveling through the air + EA_Move( ms->client, hordir, 800 ); + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotFinishTravel_WeaponJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_JumpPad( bot_movestate_t *ms, aas_reachability_t *reach ) { + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize( hordir ); + // + BotCheckBlocked( ms, hordir, qtrue, &result ); + speed = 400; + //elemantary action move in direction + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotTravel_JumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_JumpPad( bot_movestate_t *ms, aas_reachability_t *reach ) { + float speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult( &result ); + if ( !BotAirControl( ms->origin, ms->velocity, reach->end, hordir, &speed ) ) { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize( hordir ); + speed = 400; + } //end if + BotCheckBlocked( ms, hordir, qtrue, &result ); + //elemantary action move in direction + EA_Move( ms->client, hordir, speed ); + VectorCopy( hordir, result.movedir ); + // + return result; +} //end of the function BotFinishTravel_JumpPad +//=========================================================================== +// time before the reachability times out +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityTime( aas_reachability_t *reach ) { + switch ( reach->traveltype ) + { + case TRAVEL_WALK: return 5; + case TRAVEL_CROUCH: return 5; + case TRAVEL_BARRIERJUMP: return 5; + case TRAVEL_LADDER: return 6; + case TRAVEL_WALKOFFLEDGE: return 5; + case TRAVEL_JUMP: return 5; + case TRAVEL_SWIM: return 5; + case TRAVEL_WATERJUMP: return 5; + case TRAVEL_TELEPORT: return 5; + case TRAVEL_ELEVATOR: return 10; + case TRAVEL_GRAPPLEHOOK: return 8; + case TRAVEL_ROCKETJUMP: return 6; + //case TRAVEL_BFGJUMP: return 6; + case TRAVEL_JUMPPAD: return 10; + case TRAVEL_FUNCBOB: return 10; + default: + { + botimport.Print( PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype ); + return 8; + } //end case + } //end switch +} //end of the function BotReachabilityTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotMoveInGoalArea( bot_movestate_t *ms, bot_goal_t *goal ) { + bot_moveresult_t result; + vec3_t dir; + float dist, speed; + +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1)); + //AAS_ClearShownDebugLines(); + //AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED); +#endif //DEBUG + BotClearMoveResult( &result ); + //walk straight to the goal origin + dir[0] = goal->origin[0] - ms->origin[0]; + dir[1] = goal->origin[1] - ms->origin[1]; + if ( ms->moveflags & MFL_SWIMMING ) { + dir[2] = goal->origin[2] - ms->origin[2]; + result.traveltype = TRAVEL_SWIM; + } //end if + else + { + dir[2] = 0; + result.traveltype = TRAVEL_WALK; + } //endif + // + dist = VectorNormalize( dir ); + if ( dist > 100 || ( goal->flags & GFL_NOSLOWAPPROACH ) ) { + dist = 100; + } + speed = 400 - ( 400 - 4 * dist ); + if ( speed < 10 ) { + speed = 0; + } + // + BotCheckBlocked( ms, dir, qtrue, &result ); + //elemantary action move in direction + EA_Move( ms->client, dir, speed ); + VectorCopy( dir, result.movedir ); + // + if ( ms->moveflags & MFL_SWIMMING ) { + Vector2Angles( dir, result.ideal_viewangles ); + result.flags |= MOVERESULT_SWIMVIEW; + } //end if + //if (!debugline) debugline = botimport.DebugLineCreate(); + //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE); + // + ms->lastreachnum = 0; + ms->lastareanum = 0; + ms->lastgoalareanum = goal->areanum; + VectorCopy( ms->origin, ms->lastorigin ); + ms->lasttime = AAS_Time(); + // + return result; +} //end of the function BotMoveInGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum ); +extern float VectorDistance( vec3_t v1, vec3_t v2 ); +void BotMoveToGoal( bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags ) { + int reachnum = 0; // TTimo (might be used uninitialized in this function) + int lastreachnum, foundjumppad, ent; + aas_reachability_t reach, lastreach; + bot_movestate_t *ms; + //vec3_t mins, maxs, up = {0, 0, 1}; + //bsp_trace_t trace; + //static int debugline; + + BotClearMoveResult( result ); + // + ms = BotMoveStateFromHandle( movestate ); + if ( !ms ) { + return; + } + //reset the grapple before testing if the bot has a valid goal + //because the bot could loose all it's goals when stuck to a wall + BotResetGrapple( ms ); + // + if ( !goal ) { +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client ); +#endif //DEBUG + result->failure = qtrue; + return; + } //end if + //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach); + //remove some of the move flags + ms->moveflags &= ~( MFL_SWIMMING | MFL_AGAINSTLADDER ); + //set some of the move flags + //NOTE: the MFL_ONGROUND flag is also set in the higher AI + if ( AAS_OnGround( ms->origin, ms->presencetype, ms->entitynum ) ) { + ms->moveflags |= MFL_ONGROUND; + } + // + + if ( ms->moveflags & MFL_ONGROUND ) { + int modeltype, modelnum; + + ent = BotOnTopOfEntity( ms ); + + if ( ent != -1 ) { + modelnum = AAS_EntityModelindex( ent ); + if ( modelnum >= 0 && modelnum < MAX_MODELS ) { + modeltype = modeltypes[modelnum]; + + if ( modeltype == MODELTYPE_FUNC_PLAT ) { + AAS_ReachabilityFromNum( ms->lastreachnum, &reach ); + //if the bot is Not using the elevator + if ( reach.traveltype != TRAVEL_ELEVATOR || + //NOTE: the face number is the plat model number + ( reach.facenum & 0x0000FFFF ) != modelnum ) { + reachnum = AAS_NextModelReachability( 0, modelnum ); + if ( reachnum ) { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client); + AAS_ReachabilityFromNum( reachnum, &reach ); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime( &reach ); + } //end if + else + { + if ( bot_developer ) { + botimport.Print( PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client ); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_ELEVATOR; + } //end if + else if ( modeltype == MODELTYPE_FUNC_BOB ) { + AAS_ReachabilityFromNum( ms->lastreachnum, &reach ); + //if the bot is Not using the func bobbing + if ( reach.traveltype != TRAVEL_FUNCBOB || + //NOTE: the face number is the func_bobbing model number + ( reach.facenum & 0x0000FFFF ) != modelnum ) { + reachnum = AAS_NextModelReachability( 0, modelnum ); + if ( reachnum ) { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client); + AAS_ReachabilityFromNum( reachnum, &reach ); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime( &reach ); + } //end if + else + { + if ( bot_developer ) { + botimport.Print( PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client ); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_FUNCBOB; + } //end if + /* Ridah, disabled this, or standing on little fragments causes problems + else + { + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + */ + } //end if + } //end if + } //end if + //if swimming + if ( AAS_Swimming( ms->origin ) ) { + ms->moveflags |= MFL_SWIMMING; + } + //if against a ladder + if ( AAS_AgainstLadder( ms->origin, ms->areanum ) ) { + ms->moveflags |= MFL_AGAINSTLADDER; + } + //if the bot is on the ground, swimming or against a ladder + if ( ms->moveflags & ( MFL_ONGROUND | MFL_SWIMMING | MFL_AGAINSTLADDER ) ) { + //botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + // + AAS_ReachabilityFromNum( ms->lastreachnum, &lastreach ); + //reachability area the bot is in + //ms->areanum = BotReachabilityArea(ms->origin, (lastreach.traveltype != TRAVEL_ELEVATOR)); + ms->areanum = BotFuzzyPointReachabilityArea( ms->origin ); + //if the bot is in the goal area + if ( ms->areanum == goal->areanum ) { + *result = BotMoveInGoalArea( ms, goal ); + return; + } //end if + //assume we can use the reachability from the last frame + reachnum = ms->lastreachnum; + //if there is a last reachability + +// Ridah, best calc it each frame, especially for Zombie, which moves so slow that we can't afford to take a long route +// reachnum = 0; +// done. + if ( reachnum ) { + AAS_ReachabilityFromNum( reachnum, &reach ); + //check if the reachability is still valid + if ( !( AAS_TravelFlagForType( reach.traveltype ) & travelflags ) ) { + reachnum = 0; + } //end if + //special grapple hook case + else if ( reach.traveltype == TRAVEL_GRAPPLEHOOK ) { + if ( ms->reachability_time < AAS_Time() || + ( ms->moveflags & MFL_GRAPPLERESET ) ) { + reachnum = 0; + } //end if + } //end if + //special elevator case + else if ( reach.traveltype == TRAVEL_ELEVATOR || reach.traveltype == TRAVEL_FUNCBOB ) { + if ( ( result->flags & MOVERESULT_ONTOPOF_FUNCBOB ) || + ( result->flags & MOVERESULT_ONTOPOF_FUNCBOB ) ) { + ms->reachability_time = AAS_Time() + 5; + } //end if + //if the bot was going for an elevator and reached the reachability area + if ( ms->areanum == reach.areanum || + ms->reachability_time < AAS_Time() ) { + reachnum = 0; + } //end if + } //end if + else + { +#ifdef DEBUG + if ( bot_developer ) { + if ( ms->reachability_time < AAS_Time() ) { + botimport.Print( PRT_MESSAGE, "client %d: reachability timeout in ", ms->client ); + AAS_PrintTravelType( reach.traveltype ); + botimport.Print( PRT_MESSAGE, "\n" ); + } //end if + /* + if (ms->lastareanum != ms->areanum) + { + botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum); + } //end if*/ + } //end if +#endif //DEBUG + //if the goal area changed or the reachability timed out + //or the area changed + if ( ms->lastgoalareanum != goal->areanum || + ms->reachability_time < AAS_Time() || + ms->lastareanum != ms->areanum || + ( ( ms->lasttime > ( AAS_Time() - 0.5 ) ) && ( VectorDistance( ms->origin, ms->lastorigin ) < 20 * ( AAS_Time() - ms->lasttime ) ) ) ) { + reachnum = 0; + //botimport.Print(PRT_MESSAGE, "area change or timeout\n"); + } //end else if + } //end else + } //end if + //if the bot needs a new reachability + if ( !reachnum ) { + //if the area has no reachability links + if ( !AAS_AreaReachability( ms->areanum ) ) { +#ifdef DEBUG + if ( bot_developer ) { + botimport.Print( PRT_MESSAGE, "area %d no reachability\n", ms->areanum ); + } //end if +#endif //DEBUG + } //end if + //get a new reachability leading towards the goal + reachnum = BotGetReachabilityToGoal( ms->origin, ms->areanum, ms->entitynum, + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags ); + //the area number the reachability starts in + ms->reachareanum = ms->areanum; + //reset some state variables + ms->jumpreach = 0; //for TRAVEL_JUMP + ms->moveflags &= ~MFL_GRAPPLERESET; //for TRAVEL_GRAPPLEHOOK + //if there is a reachability to the goal + if ( reachnum ) { + AAS_ReachabilityFromNum( reachnum, &reach ); + //set a timeout for this reachability + ms->reachability_time = AAS_Time() + BotReachabilityTime( &reach ); + // +#ifdef AVOIDREACH + //add the reachability to the reachabilities to avoid for a while + BotAddToAvoidReach( ms, reachnum, AVOIDREACH_TIME ); +#endif //AVOIDREACH + } //end if +#ifdef DEBUG + + else if ( bot_developer ) { + botimport.Print( PRT_MESSAGE, "goal not reachable\n" ); + memset( &reach, 0, sizeof( aas_reachability_t ) ); //make compiler happy + } //end else + if ( bot_developer ) { + //if still going for the same goal + if ( ms->lastgoalareanum == goal->areanum ) { + if ( ms->lastareanum == reach.areanum ) { + botimport.Print( PRT_MESSAGE, "same goal, going back to previous area\n" ); + } //end if + } //end if + } //end if +#endif //DEBUG + } //end else + // + ms->lastreachnum = reachnum; + ms->lastgoalareanum = goal->areanum; + ms->lastareanum = ms->areanum; + //if the bot has a reachability + if ( reachnum ) { + //get the reachability from the number + AAS_ReachabilityFromNum( reachnum, &reach ); + result->traveltype = reach.traveltype; + // +#ifdef DEBUG_AI_MOVE + AAS_ClearShownDebugLines(); + AAS_PrintTravelType( reach.traveltype ); + AAS_ShowReachability( &reach ); +#endif //DEBUG_AI_MOVE + // +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client); + //AAS_PrintTravelType(reach.traveltype); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + switch ( reach.traveltype ) + { + case TRAVEL_WALK: *result = BotTravel_Walk( ms, &reach ); break; + case TRAVEL_CROUCH: *result = BotTravel_Crouch( ms, &reach ); break; + case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump( ms, &reach ); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder( ms, &reach ); break; + case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge( ms, &reach ); break; + case TRAVEL_JUMP: *result = BotTravel_Jump( ms, &reach ); break; + case TRAVEL_SWIM: *result = BotTravel_Swim( ms, &reach ); break; + case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump( ms, &reach ); break; + case TRAVEL_TELEPORT: *result = BotTravel_Teleport( ms, &reach ); break; + case TRAVEL_ELEVATOR: *result = BotTravel_Elevator( ms, &reach ); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple( ms, &reach ); break; + case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump( ms, &reach ); break; + //case TRAVEL_BFGJUMP: + case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad( ms, &reach ); break; + case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing( ms, &reach ); break; + default: + { + botimport.Print( PRT_FATAL, "travel type %d not implemented yet\n", reach.traveltype ); + break; + } //end case + } //end switch + } //end if + else + { + result->failure = qtrue; + memset( &reach, 0, sizeof( aas_reachability_t ) ); + } //end else +#ifdef DEBUG + if ( bot_developer ) { + if ( result->failure ) { + botimport.Print( PRT_MESSAGE, "client %d: movement failure in ", ms->client ); + AAS_PrintTravelType( reach.traveltype ); + botimport.Print( PRT_MESSAGE, "\n" ); + } //end if + } //end if +#endif //DEBUG + } //end if + else + { + int i, numareas, areas[16]; + vec3_t end; + + //special handling of jump pads when the bot uses a jump pad without knowing it + foundjumppad = qfalse; + VectorMA( ms->origin, -2 * ms->thinktime, ms->velocity, end ); + numareas = AAS_TraceAreas( ms->origin, end, areas, NULL, 16 ); + for ( i = numareas - 1; i >= 0; i-- ) + { + if ( AAS_AreaJumpPad( areas[i] ) ) { + //botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]); + foundjumppad = qtrue; + lastreachnum = BotGetReachabilityToGoal( end, areas[i], ms->entitynum, + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, TFL_JUMPPAD ); + if ( lastreachnum ) { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability\n"); + break; + } //end if + else + { + for ( lastreachnum = AAS_NextAreaReachability( areas[i], 0 ); lastreachnum; + lastreachnum = AAS_NextAreaReachability( areas[i], lastreachnum ) ) + { + //get the reachability from the number + AAS_ReachabilityFromNum( lastreachnum, &reach ); + if ( reach.traveltype == TRAVEL_JUMPPAD ) { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n"); + break; + } //end if + } //end for + if ( lastreachnum ) { + break; + } + } //end else + } //end if + } //end for + if ( bot_developer ) { + //if a jumppad is found with the trace but no reachability is found + if ( foundjumppad && !ms->lastreachnum ) { + botimport.Print( PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client ); + } //end if + } //end if + // + if ( ms->lastreachnum ) { + //botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + AAS_ReachabilityFromNum( ms->lastreachnum, &reach ); + result->traveltype = reach.traveltype; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client); + //AAS_PrintTravelType(reach.traveltype); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + // + switch ( reach.traveltype ) + { + case TRAVEL_WALK: *result = BotTravel_Walk( ms, &reach ); break; //BotFinishTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: /*do nothing*/ break; + case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump( ms, &reach ); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder( ms, &reach ); break; + case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge( ms, &reach ); break; + case TRAVEL_JUMP: *result = BotFinishTravel_Jump( ms, &reach ); break; + case TRAVEL_SWIM: *result = BotTravel_Swim( ms, &reach ); break; + case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump( ms, &reach ); break; + case TRAVEL_TELEPORT: /*do nothing*/ break; + case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator( ms, &reach ); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple( ms, &reach ); break; + case TRAVEL_ROCKETJUMP: *result = BotFinishTravel_WeaponJump( ms, &reach ); break; + //case TRAVEL_BFGJUMP: + case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad( ms, &reach ); break; + case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing( ms, &reach ); break; + default: + { + botimport.Print( PRT_FATAL, "(last) travel type %d not implemented yet\n", reach.traveltype ); + break; + } //end case + } //end switch +#ifdef DEBUG + if ( bot_developer ) { + if ( result->failure ) { + botimport.Print( PRT_MESSAGE, "client %d: movement failure in finish ", ms->client ); + AAS_PrintTravelType( reach.traveltype ); + botimport.Print( PRT_MESSAGE, "\n" ); + } //end if + } //end if +#endif //DEBUG + } //end if + } //end else + //FIXME: is it right to do this here? + if ( result->blocked ) { + ms->reachability_time -= 10 * ms->thinktime; + } + //copy the last origin + VectorCopy( ms->origin, ms->lastorigin ); + ms->lasttime = AAS_Time(); + + // RF, try to look in the direction we will be moving ahead of time + if ( reachnum > 0 && !( result->flags & ( MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) ) { + vec3_t dir; + int ftraveltime, freachnum; + + AAS_ReachabilityFromNum( reachnum, &reach ); + if ( reach.areanum != goal->areanum ) { + if ( AAS_AreaRouteToGoalArea( reach.areanum, reach.end, goal->areanum, travelflags, &ftraveltime, &freachnum ) ) { + AAS_ReachabilityFromNum( freachnum, &reach ); + VectorSubtract( reach.end, ms->origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, result->ideal_viewangles ); + result->flags |= MOVERESULT_FUTUREVIEW; + } + } else { + VectorSubtract( goal->origin, ms->origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, result->ideal_viewangles ); + result->flags |= MOVERESULT_FUTUREVIEW; + } + } + + //return the movement result + return; +} //end of the function BotMoveToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidReach( int movestate ) { + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle( movestate ); + if ( !ms ) { + return; + } + memset( ms->avoidreach, 0, MAX_AVOIDREACH * sizeof( int ) ); + memset( ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof( float ) ); + memset( ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof( int ) ); + + // RF, also clear movestate stuff + ms->lastareanum = 0; + ms->lastgoalareanum = 0; + ms->lastreachnum = 0; +} //end of the function BotResetAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetLastAvoidReach( int movestate ) { + int i, latest; + float latesttime; + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle( movestate ); + if ( !ms ) { + return; + } + latesttime = 0; + latest = 0; + for ( i = 0; i < MAX_AVOIDREACH; i++ ) + { + if ( ms->avoidreachtimes[i] > latesttime ) { + latesttime = ms->avoidreachtimes[i]; + latest = i; + } //end if + } //end for + if ( latesttime ) { + ms->avoidreachtimes[latest] = 0; + if ( ms->avoidreachtries[i] > 0 ) { + ms->avoidreachtries[latest]--; + } + } //end if +} //end of the function BotResetLastAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetMoveState( int movestate ) { + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle( movestate ); + if ( !ms ) { + return; + } + memset( ms, 0, sizeof( bot_movestate_t ) ); +} //end of the function BotResetMoveState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupMoveAI( void ) { + BotSetBrushModelTypes(); + sv_maxstep = LibVarValue( "sv_step", "18" ); + sv_maxbarrier = LibVarValue( "sv_maxbarrier", "32" ); + sv_gravity = LibVarValue( "sv_gravity", "800" ); + return BLERR_NOERROR; +} //end of the function BotSetupMoveAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownMoveAI( void ) { + int i; + + for ( i = 1; i <= MAX_CLIENTS; i++ ) + { + if ( botmovestates[i] ) { + FreeMemory( botmovestates[i] ); + botmovestates[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownMoveAI diff --git a/src/botlib/be_ai_weap.c b/src/botlib/be_ai_weap.c new file mode 100644 index 0000000..82471bd --- /dev/null +++ b/src/botlib/be_ai_weap.c @@ -0,0 +1,547 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_weap.c + * + * desc: weapon AI + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_libvar.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" //fuzzy weights +#include "../game/be_ai_weap.h" + +//#define DEBUG_AI_WEAP + +//structure field offsets +#define WEAPON_OFS( x ) (int)&( ( (weaponinfo_t *)0 )->x ) +#define PROJECTILE_OFS( x ) (int)&( ( (projectileinfo_t *)0 )->x ) + +//weapon definition +fielddef_t weaponinfo_fields[] = +{ + {"number", WEAPON_OFS( number ), FT_INT}, //weapon number + {"name", WEAPON_OFS( name ), FT_STRING}, //name of the weapon + {"level", WEAPON_OFS( level ), FT_INT}, + {"model", WEAPON_OFS( model ), FT_STRING}, //model of the weapon + {"weaponindex", WEAPON_OFS( weaponindex ), FT_INT}, //index of weapon in inventory + {"flags", WEAPON_OFS( flags ), FT_INT}, //special flags + {"projectile", WEAPON_OFS( projectile ), FT_STRING}, //projectile used by the weapon + {"numprojectiles", WEAPON_OFS( numprojectiles ), FT_INT}, //number of projectiles + {"hspread", WEAPON_OFS( hspread ), FT_FLOAT}, //horizontal spread of projectiles (degrees from middle) + {"vspread", WEAPON_OFS( vspread ), FT_FLOAT}, //vertical spread of projectiles (degrees from middle) + {"speed", WEAPON_OFS( speed ), FT_FLOAT}, //speed of the projectile (0 = instant hit) + {"acceleration", WEAPON_OFS( acceleration ), FT_FLOAT}, //"acceleration" * time (in seconds) + "speed" = projectile speed + {"recoil", WEAPON_OFS( recoil ), FT_FLOAT | FT_ARRAY, 3}, //amount of recoil the player gets from the weapon + {"offset", WEAPON_OFS( offset ), FT_FLOAT | FT_ARRAY, 3}, //projectile start offset relative to eye and view angles + {"angleoffset", WEAPON_OFS( angleoffset ), FT_FLOAT | FT_ARRAY, 3}, //offset of the shoot angles relative to the view angles + {"extrazvelocity", WEAPON_OFS( extrazvelocity ), FT_FLOAT}, //extra z velocity the projectile gets + {"ammoamount", WEAPON_OFS( ammoamount ), FT_INT}, //ammo amount used per shot + {"ammoindex", WEAPON_OFS( ammoindex ), FT_INT}, //index of ammo in inventory + {"activate", WEAPON_OFS( activate ), FT_FLOAT}, //time it takes to select the weapon + {"reload", WEAPON_OFS( reload ), FT_FLOAT}, //time it takes to reload the weapon + {"spinup", WEAPON_OFS( spinup ), FT_FLOAT}, //time it takes before first shot + {"spindown", WEAPON_OFS( spindown ), FT_FLOAT}, //time it takes before weapon stops firing + {NULL, 0, 0, 0} +}; + +//projectile definition +fielddef_t projectileinfo_fields[] = +{ + {"name", PROJECTILE_OFS( name ), FT_STRING}, //name of the projectile + {"model", WEAPON_OFS( model ), FT_STRING}, //model of the projectile + {"flags", PROJECTILE_OFS( flags ), FT_INT}, //special flags + {"gravity", PROJECTILE_OFS( gravity ), FT_FLOAT}, //amount of gravity applied to the projectile [0,1] + {"damage", PROJECTILE_OFS( damage ), FT_INT}, //damage of the projectile + {"radius", PROJECTILE_OFS( radius ), FT_FLOAT}, //radius of damage + {"visdamage", PROJECTILE_OFS( visdamage ), FT_INT}, //damage of the projectile to visible entities + {"damagetype", PROJECTILE_OFS( damagetype ), FT_INT}, //type of damage (combination of the DAMAGETYPE_? flags) + {"healthinc", PROJECTILE_OFS( healthinc ), FT_INT}, //health increase the owner gets + {"push", PROJECTILE_OFS( push ), FT_FLOAT}, //amount a player is pushed away from the projectile impact + {"detonation", PROJECTILE_OFS( detonation ), FT_FLOAT}, //time before projectile explodes after fire pressed + {"bounce", PROJECTILE_OFS( bounce ), FT_FLOAT}, //amount the projectile bounces + {"bouncefric", PROJECTILE_OFS( bouncefric ), FT_FLOAT}, //amount the bounce decreases per bounce + {"bouncestop", PROJECTILE_OFS( bouncestop ), FT_FLOAT}, //minimum bounce value before bouncing stops +//recurive projectile definition?? + {NULL, 0, 0, 0} +}; + +structdef_t weaponinfo_struct = +{ + sizeof( weaponinfo_t ), weaponinfo_fields +}; +structdef_t projectileinfo_struct = +{ + sizeof( projectileinfo_t ), projectileinfo_fields +}; + +//weapon configuration: set of weapons with projectiles +typedef struct weaponconfig_s +{ + int numweapons; + int numprojectiles; + projectileinfo_t *projectileinfo; + weaponinfo_t *weaponinfo; +} weaponconfig_t; + +//the bot weapon state +typedef struct bot_weaponstate_s +{ + struct weightconfig_s *weaponweightconfig; //weapon weight configuration + int *weaponweightindex; //weapon weight index +} bot_weaponstate_t; + +bot_weaponstate_t *botweaponstates[MAX_CLIENTS + 1]; +weaponconfig_t *weaponconfig; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotValidWeaponNumber( int weaponnum ) { + if ( weaponnum <= 0 || weaponnum > weaponconfig->numweapons ) { + botimport.Print( PRT_ERROR, "weapon number out of range\n" ); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidWeaponNumber +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_weaponstate_t *BotWeaponStateFromHandle( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "move state handle %d out of range\n", handle ); + return NULL; + } //end if + if ( !botweaponstates[handle] ) { + botimport.Print( PRT_FATAL, "invalid move state %d\n", handle ); + return NULL; + } //end if + return botweaponstates[handle]; +} //end of the function BotWeaponStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef DEBUG_AI_WEAP +void DumpWeaponConfig( weaponconfig_t *wc ) { + FILE *fp; + int i; + + fp = Log_FileStruct(); + if ( !fp ) { + return; + } + for ( i = 0; i < wc->numprojectiles; i++ ) + { + WriteStructure( fp, &projectileinfo_struct, (char *) &wc->projectileinfo[i] ); + Log_Flush(); + } //end for + for ( i = 0; i < wc->numweapons; i++ ) + { + WriteStructure( fp, &weaponinfo_struct, (char *) &wc->weaponinfo[i] ); + Log_Flush(); + } //end for +} //end of the function DumpWeaponConfig +#endif //DEBUG_AI_WEAP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weaponconfig_t *LoadWeaponConfig( char *filename ) { + int max_weaponinfo, max_projectileinfo; + token_t token; + char path[MAX_PATH]; + int i, j; + source_t *source; + weaponconfig_t *wc; + weaponinfo_t weaponinfo; + + max_weaponinfo = (int) LibVarValue( "max_weaponinfo", "32" ); + if ( max_weaponinfo < 0 ) { + botimport.Print( PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo ); + max_weaponinfo = 32; + LibVarSet( "max_weaponinfo", "32" ); + } //end if + max_projectileinfo = (int) LibVarValue( "max_projectileinfo", "32" ); + if ( max_projectileinfo < 0 ) { + botimport.Print( PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo ); + max_projectileinfo = 32; + LibVarSet( "max_projectileinfo", "32" ); + } //end if + strncpy( path, filename, MAX_PATH ); + source = LoadSourceFile( path ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", path ); + return NULL; + } //end if + //initialize weapon config + wc = (weaponconfig_t *) GetClearedHunkMemory( sizeof( weaponconfig_t ) + + max_weaponinfo * sizeof( weaponinfo_t ) + + max_projectileinfo * sizeof( projectileinfo_t ) ); + wc->weaponinfo = ( weaponinfo_t * )( (char *) wc + sizeof( weaponconfig_t ) ); + wc->projectileinfo = ( projectileinfo_t * )( (char *) wc->weaponinfo + + max_weaponinfo * sizeof( weaponinfo_t ) ); + wc->numweapons = max_weaponinfo; + wc->numprojectiles = 0; + //parse the source file + while ( PC_ReadToken( source, &token ) ) + { + if ( !strcmp( token.string, "weaponinfo" ) ) { + memset( &weaponinfo, 0, sizeof( weaponinfo_t ) ); + if ( !ReadStructure( source, &weaponinfo_struct, (char *) &weaponinfo ) ) { + FreeMemory( wc ); + FreeSource( source ); + return NULL; + } //end if + if ( weaponinfo.number < 0 || weaponinfo.number >= max_weaponinfo ) { + botimport.Print( PRT_ERROR, "weapon info number %d out of range in %s\n", weaponinfo.number, path ); + FreeMemory( wc ); + FreeSource( source ); + return NULL; + } //end if + memcpy( &wc->weaponinfo[weaponinfo.number], &weaponinfo, sizeof( weaponinfo_t ) ); + wc->weaponinfo[weaponinfo.number].valid = qtrue; + } //end if + else if ( !strcmp( token.string, "projectileinfo" ) ) { + if ( wc->numprojectiles >= max_projectileinfo ) { + botimport.Print( PRT_ERROR, "more than %d projectiles defined in %s\n", max_projectileinfo, path ); + FreeMemory( wc ); + FreeSource( source ); + return NULL; + } //end if + memset( &wc->projectileinfo[wc->numprojectiles], 0, sizeof( projectileinfo_t ) ); + if ( !ReadStructure( source, &projectileinfo_struct, (char *) &wc->projectileinfo[wc->numprojectiles] ) ) { + FreeMemory( wc ); + FreeSource( source ); + return NULL; + } //end if + wc->numprojectiles++; + } //end if + else + { + botimport.Print( PRT_ERROR, "unknown definition %s in %s\n", token.string, path ); + FreeMemory( wc ); + FreeSource( source ); + return NULL; + } //end else + } //end while + FreeSource( source ); + //fix up weapons + for ( i = 0; i < wc->numweapons; i++ ) + { + if ( !wc->weaponinfo[i].valid ) { + continue; + } + if ( !wc->weaponinfo[i].name[0] ) { + botimport.Print( PRT_ERROR, "weapon %d has no name in %s\n", i, path ); + FreeMemory( wc ); + return NULL; + } //end if + if ( !wc->weaponinfo[i].projectile[0] ) { + botimport.Print( PRT_ERROR, "weapon %s has no projectile in %s\n", wc->weaponinfo[i].name, path ); + FreeMemory( wc ); + return NULL; + } //end if + //find the projectile info and copy it to the weapon info + for ( j = 0; j < wc->numprojectiles; j++ ) + { + if ( !strcmp( wc->projectileinfo[j].name, wc->weaponinfo[i].projectile ) ) { + memcpy( &wc->weaponinfo[i].proj, &wc->projectileinfo[j], sizeof( projectileinfo_t ) ); + break; + } //end if + } //end for + if ( j == wc->numprojectiles ) { + botimport.Print( PRT_ERROR, "weapon %s uses undefined projectile in %s\n", wc->weaponinfo[i].name, path ); + FreeMemory( wc ); + return NULL; + } //end if + } //end for + if ( !wc->numweapons ) { + botimport.Print( PRT_WARNING, "no weapon info loaded\n" ); + } + botimport.Print( PRT_MESSAGE, "loaded %s\n", path ); + return wc; +} //end of the function LoadWeaponConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *WeaponWeightIndex( weightconfig_t *wwc, weaponconfig_t *wc ) { + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory( sizeof( int ) * wc->numweapons ); + + for ( i = 0; i < wc->numweapons; i++ ) + { + index[i] = FindFuzzyWeight( wwc, wc->weaponinfo[i].name ); + } //end for + return index; +} //end of the function WeaponWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeWeaponWeights( int weaponstate ) { + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle( weaponstate ); + if ( !ws ) { + return; + } + if ( ws->weaponweightconfig ) { + FreeWeightConfig( ws->weaponweightconfig ); + } + if ( ws->weaponweightindex ) { + FreeMemory( ws->weaponweightindex ); + } +} //end of the function BotFreeWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadWeaponWeights( int weaponstate, char *filename ) { + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle( weaponstate ); + if ( !ws ) { + return BLERR_CANNOTLOADWEAPONWEIGHTS; + } + BotFreeWeaponWeights( weaponstate ); + // + ws->weaponweightconfig = ReadWeightConfig( filename ); + if ( !ws->weaponweightconfig ) { + botimport.Print( PRT_FATAL, "couldn't load weapon config %s\n", filename ); + return BLERR_CANNOTLOADWEAPONWEIGHTS; + } //end if + if ( !weaponconfig ) { + return BLERR_CANNOTLOADWEAPONCONFIG; + } + ws->weaponweightindex = WeaponWeightIndex( ws->weaponweightconfig, weaponconfig ); + return BLERR_NOERROR; +} //end of the function BotLoadWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetWeaponInfo( int weaponstate, int weapon, weaponinfo_t *weaponinfo ) { + bot_weaponstate_t *ws; + + if ( !BotValidWeaponNumber( weapon ) ) { + return; + } + ws = BotWeaponStateFromHandle( weaponstate ); + if ( !ws ) { + return; + } + if ( !weaponconfig ) { + return; + } + memcpy( weaponinfo, &weaponconfig->weaponinfo[weapon], sizeof( weaponinfo_t ) ); +} //end of the function BotGetWeaponInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseBestFightWeapon( int weaponstate, int *inventory ) { + int i, index, bestweapon; + float weight, bestweight; + weaponconfig_t *wc; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle( weaponstate ); + if ( !ws ) { + return 0; + } + wc = weaponconfig; + if ( !weaponconfig ) { + return 0; + } + + //if the bot has no weapon weight configuration + if ( !ws->weaponweightconfig ) { + return 0; + } + + bestweight = 0; + bestweapon = 0; + for ( i = 0; i < wc->numweapons; i++ ) + { + if ( !wc->weaponinfo[i].valid ) { + continue; + } + index = ws->weaponweightindex[i]; + if ( index < 0 ) { + continue; + } + weight = FuzzyWeight( inventory, ws->weaponweightconfig, index ); + if ( weight > bestweight ) { + bestweight = weight; + bestweapon = i; + } //end if + } //end for + return bestweapon; +} //end of the function BotChooseBestFightWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetWeaponState( int weaponstate ) { + struct weightconfig_s *weaponweightconfig; + int *weaponweightindex; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle( weaponstate ); + if ( !ws ) { + return; + } + weaponweightconfig = ws->weaponweightconfig; + weaponweightindex = ws->weaponweightindex; + + //memset(ws, 0, sizeof(bot_weaponstate_t)); + ws->weaponweightconfig = weaponweightconfig; + ws->weaponweightindex = weaponweightindex; +} //end of the function BotResetWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocWeaponState( void ) { + int i; + + for ( i = 1; i <= MAX_CLIENTS; i++ ) + { + if ( !botweaponstates[i] ) { + botweaponstates[i] = GetClearedMemory( sizeof( bot_weaponstate_t ) ); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeWeaponState( int handle ) { + if ( handle <= 0 || handle > MAX_CLIENTS ) { + botimport.Print( PRT_FATAL, "move state handle %d out of range\n", handle ); + return; + } //end if + if ( !botweaponstates[handle] ) { + botimport.Print( PRT_FATAL, "invalid move state %d\n", handle ); + return; + } //end if + BotFreeWeaponWeights( handle ); + FreeMemory( botweaponstates[handle] ); + botweaponstates[handle] = NULL; +} //end of the function BotFreeWeaponState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupWeaponAI( void ) { + char *file; + + file = LibVarString( "weaponconfig", "weapons.c" ); + weaponconfig = LoadWeaponConfig( file ); + if ( !weaponconfig ) { + botimport.Print( PRT_FATAL, "couldn't load the weapon config\n" ); + return BLERR_CANNOTLOADWEAPONCONFIG; + } //end if + +#ifdef DEBUG_AI_WEAP + DumpWeaponConfig( weaponconfig ); +#endif //DEBUG_AI_WEAP + // + return BLERR_NOERROR; +} //end of the function BotSetupWeaponAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeaponAI( void ) { + int i; + + if ( weaponconfig ) { + FreeMemory( weaponconfig ); + } + weaponconfig = NULL; + + for ( i = 1; i <= MAX_CLIENTS; i++ ) + { + if ( botweaponstates[i] ) { + BotFreeWeaponState( i ); + } //end if + } //end for +} //end of the function BotShutdownWeaponAI + diff --git a/src/botlib/be_ai_weight.c b/src/botlib/be_ai_weight.c new file mode 100644 index 0000000..2121ac8 --- /dev/null +++ b/src/botlib/be_ai_weight.c @@ -0,0 +1,972 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_weight.c + * + * desc: fuzzy logic + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" + +#define MAX_INVENTORYVALUE 999999 +#define EVALUATERECURSIVELY + +#define MAX_WEIGHT_FILES 128 +weightconfig_t *weightFileList[MAX_WEIGHT_FILES]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadValue( source_t *source, float *value ) { + token_t token; + + if ( !PC_ExpectAnyToken( source, &token ) ) { + return qfalse; + } + if ( !strcmp( token.string, "-" ) ) { + SourceWarning( source, "negative value set to zero\n" ); + if ( !PC_ExpectTokenType( source, TT_NUMBER, 0, &token ) ) { + return qfalse; + } + } //end if + if ( token.type != TT_NUMBER ) { + SourceError( source, "invalid return value %s\n", token.string ); + return qfalse; + } //end if + *value = token.floatvalue; + return qtrue; +} //end of the function ReadValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadFuzzyWeight( source_t *source, fuzzyseperator_t *fs ) { + if ( PC_CheckTokenString( source, "balance" ) ) { + fs->type = WT_BALANCE; + if ( !PC_ExpectTokenString( source, "(" ) ) { + return qfalse; + } + if ( !ReadValue( source, &fs->weight ) ) { + return qfalse; + } + if ( !PC_ExpectTokenString( source, "," ) ) { + return qfalse; + } + if ( !ReadValue( source, &fs->minweight ) ) { + return qfalse; + } + if ( !PC_ExpectTokenString( source, "," ) ) { + return qfalse; + } + if ( !ReadValue( source, &fs->maxweight ) ) { + return qfalse; + } + if ( !PC_ExpectTokenString( source, ")" ) ) { + return qfalse; + } + } //end if + else + { + fs->type = 0; + if ( !ReadValue( source, &fs->weight ) ) { + return qfalse; + } + fs->minweight = fs->weight; + fs->maxweight = fs->weight; + } //end if + if ( !PC_ExpectTokenString( source, ";" ) ) { + return qfalse; + } + return qtrue; +} //end of the function ReadFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeFuzzySeperators_r( fuzzyseperator_t *fs ) { + if ( !fs ) { + return; + } + if ( fs->child ) { + FreeFuzzySeperators_r( fs->child ); + } + if ( fs->next ) { + FreeFuzzySeperators_r( fs->next ); + } + FreeMemory( fs ); +} //end of the function FreeFuzzySeperators +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig2( weightconfig_t *config ) { + int i; + + for ( i = 0; i < config->numweights; i++ ) + { + FreeFuzzySeperators_r( config->weights[i].firstseperator ); + if ( config->weights[i].name ) { + FreeMemory( config->weights[i].name ); + } + } //end for + FreeMemory( config ); +} //end of the function FreeWeightConfig2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig( weightconfig_t *config ) { + if ( !LibVarGetValue( "bot_reloadcharacters" ) ) { + return; + } + FreeWeightConfig2( config ); +} //end of the function FreeWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fuzzyseperator_t *ReadFuzzySeperators_r( source_t *source ) { + int newindent, index, def, founddefault; + token_t token; + fuzzyseperator_t *fs, *lastfs, *firstfs; + + founddefault = qfalse; + firstfs = NULL; + lastfs = NULL; + if ( !PC_ExpectTokenString( source, "(" ) ) { + return NULL; + } + if ( !PC_ExpectTokenType( source, TT_NUMBER, TT_INTEGER, &token ) ) { + return NULL; + } + index = token.intvalue; + if ( !PC_ExpectTokenString( source, ")" ) ) { + return NULL; + } + if ( !PC_ExpectTokenString( source, "{" ) ) { + return NULL; + } + if ( !PC_ExpectAnyToken( source, &token ) ) { + return NULL; + } + do + { + def = !strcmp( token.string, "default" ); + if ( def || !strcmp( token.string, "case" ) ) { + fs = (fuzzyseperator_t *) GetClearedMemory( sizeof( fuzzyseperator_t ) ); + fs->index = index; + if ( lastfs ) { + lastfs->next = fs; + } else { firstfs = fs;} + lastfs = fs; + if ( def ) { + if ( founddefault ) { + SourceError( source, "switch already has a default\n" ); + FreeFuzzySeperators_r( firstfs ); + return NULL; + } //end if + fs->value = MAX_INVENTORYVALUE; + founddefault = qtrue; + } //end if + else + { + if ( !PC_ExpectTokenType( source, TT_NUMBER, TT_INTEGER, &token ) ) { + FreeFuzzySeperators_r( firstfs ); + return NULL; + } //end if + fs->value = token.intvalue; + } //end else + if ( !PC_ExpectTokenString( source, ":" ) || !PC_ExpectAnyToken( source, &token ) ) { + FreeFuzzySeperators_r( firstfs ); + return NULL; + } //end if + newindent = qfalse; + if ( !strcmp( token.string, "{" ) ) { + newindent = qtrue; + if ( !PC_ExpectAnyToken( source, &token ) ) { + FreeFuzzySeperators_r( firstfs ); + return NULL; + } //end if + } //end if + if ( !strcmp( token.string, "return" ) ) { + if ( !ReadFuzzyWeight( source, fs ) ) { + FreeFuzzySeperators_r( firstfs ); + return NULL; + } //end if + } //end if + else if ( !strcmp( token.string, "switch" ) ) { + fs->child = ReadFuzzySeperators_r( source ); + if ( !fs->child ) { + FreeFuzzySeperators_r( firstfs ); + return NULL; + } //end if + } //end else if + else + { + SourceError( source, "invalid name %s\n", token.string ); + return NULL; + } //end else + if ( newindent ) { + if ( !PC_ExpectTokenString( source, "}" ) ) { + FreeFuzzySeperators_r( firstfs ); + return NULL; + } //end if + } //end if + } //end if + else + { + FreeFuzzySeperators_r( firstfs ); + SourceError( source, "invalid name %s\n", token.string ); + return NULL; + } //end else + if ( !PC_ExpectAnyToken( source, &token ) ) { + FreeFuzzySeperators_r( firstfs ); + return NULL; + } //end if + } while ( strcmp( token.string, "}" ) ); + // + if ( !founddefault ) { + SourceWarning( source, "switch without default\n" ); + fs = (fuzzyseperator_t *) GetClearedMemory( sizeof( fuzzyseperator_t ) ); + fs->index = index; + fs->value = MAX_INVENTORYVALUE; + fs->weight = 0; + fs->next = NULL; + fs->child = NULL; + if ( lastfs ) { + lastfs->next = fs; + } else { firstfs = fs;} + lastfs = fs; + } //end if + // + return firstfs; +} //end of the function ReadFuzzySeperators_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weightconfig_t *ReadWeightConfig( char *filename ) { + int newindent, avail = 0, n; + token_t token; + source_t *source; + fuzzyseperator_t *fs; + weightconfig_t *config = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + if ( !LibVarGetValue( "bot_reloadcharacters" ) ) { + avail = -1; + for ( n = 0; n < MAX_WEIGHT_FILES; n++ ) + { + config = weightFileList[n]; + if ( !config ) { + if ( avail == -1 ) { + avail = n; + } //end if + continue; + } //end if + if ( strcmp( filename, config->filename ) == 0 ) { + //botimport.Print( PRT_MESSAGE, "retained %s\n", filename ); + return config; + } //end if + } //end for + + if ( avail == -1 ) { + botimport.Print( PRT_ERROR, "weightFileList was full trying to load %s\n", filename ); + return NULL; + } //end if + } //end if + + source = LoadSourceFile( filename ); + if ( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", filename ); + return NULL; + } //end if + // + config = (weightconfig_t *) GetClearedMemory( sizeof( weightconfig_t ) ); + config->numweights = 0; + Q_strncpyz( config->filename, filename, sizeof( config->filename ) ); + //parse the item config file + while ( PC_ReadToken( source, &token ) ) + { + if ( !strcmp( token.string, "weight" ) ) { + if ( config->numweights >= MAX_WEIGHTS ) { + SourceWarning( source, "too many fuzzy weights\n" ); + break; + } //end if + if ( !PC_ExpectTokenType( source, TT_STRING, 0, &token ) ) { + FreeWeightConfig( config ); + FreeSource( source ); + return NULL; + } //end if + StripDoubleQuotes( token.string ); + config->weights[config->numweights].name = (char *) GetClearedMemory( strlen( token.string ) + 1 ); + strcpy( config->weights[config->numweights].name, token.string ); + if ( !PC_ExpectAnyToken( source, &token ) ) { + FreeWeightConfig( config ); + FreeSource( source ); + return NULL; + } //end if + newindent = qfalse; + if ( !strcmp( token.string, "{" ) ) { + newindent = qtrue; + if ( !PC_ExpectAnyToken( source, &token ) ) { + FreeWeightConfig( config ); + FreeSource( source ); + return NULL; + } //end if + } //end if + if ( !strcmp( token.string, "switch" ) ) { + fs = ReadFuzzySeperators_r( source ); + if ( !fs ) { + FreeWeightConfig( config ); + FreeSource( source ); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end if + else if ( !strcmp( token.string, "return" ) ) { + fs = (fuzzyseperator_t *) GetClearedMemory( sizeof( fuzzyseperator_t ) ); + fs->index = 0; + fs->value = MAX_INVENTORYVALUE; + fs->next = NULL; + fs->child = NULL; + if ( !ReadFuzzyWeight( source, fs ) ) { + FreeMemory( fs ); + FreeWeightConfig( config ); + FreeSource( source ); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end else if + else + { + SourceError( source, "invalid name %s\n", token.string ); + FreeWeightConfig( config ); + FreeSource( source ); + return NULL; + } //end else + if ( newindent ) { + if ( !PC_ExpectTokenString( source, "}" ) ) { + FreeWeightConfig( config ); + FreeSource( source ); + return NULL; + } //end if + } //end if + config->numweights++; + } //end if + else + { + SourceError( source, "invalid name %s\n", token.string ); + FreeWeightConfig( config ); + FreeSource( source ); + return NULL; + } //end else + } //end while + //free the source at the end of a pass + FreeSource( source ); + //if the file was located in a pak file + botimport.Print( PRT_MESSAGE, "loaded %s\n", filename ); +#ifdef DEBUG + if ( bot_developer ) { + botimport.Print( PRT_MESSAGE, "weights loaded in %d msec\n", Sys_MilliSeconds() - starttime ); + } //end if +#endif //DEBUG + // + if ( !LibVarGetValue( "bot_reloadcharacters" ) ) { + weightFileList[avail] = config; + } //end if + // + return config; +} //end of the function ReadWeightConfig +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzyWeight( FILE *fp, fuzzyseperator_t *fs ) { + if ( fs->type == WT_BALANCE ) { + if ( fprintf( fp, " return balance(" ) < 0 ) { + return qfalse; + } + if ( !WriteFloat( fp, fs->weight ) ) { + return qfalse; + } + if ( fprintf( fp, "," ) < 0 ) { + return qfalse; + } + if ( !WriteFloat( fp, fs->minweight ) ) { + return qfalse; + } + if ( fprintf( fp, "," ) < 0 ) { + return qfalse; + } + if ( !WriteFloat( fp, fs->maxweight ) ) { + return qfalse; + } + if ( fprintf( fp, ");\n" ) < 0 ) { + return qfalse; + } + } //end if + else + { + if ( fprintf( fp, " return " ) < 0 ) { + return qfalse; + } + if ( !WriteFloat( fp, fs->weight ) ) { + return qfalse; + } + if ( fprintf( fp, ";\n" ) < 0 ) { + return qfalse; + } + } //end else + return qtrue; +} //end of the function WriteFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzySeperators_r( FILE *fp, fuzzyseperator_t *fs, int indent ) { + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fprintf( fp, "switch(%d)\n", fs->index ) < 0 ) { + return qfalse; + } + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fprintf( fp, "{\n" ) < 0 ) { + return qfalse; + } + indent++; + do + { + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fs->next ) { + if ( fprintf( fp, "case %d:", fs->value ) < 0 ) { + return qfalse; + } + } //end if + else + { + if ( fprintf( fp, "default:" ) < 0 ) { + return qfalse; + } + } //end else + if ( fs->child ) { + if ( fprintf( fp, "\n" ) < 0 ) { + return qfalse; + } + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fprintf( fp, "{\n" ) < 0 ) { + return qfalse; + } + if ( !WriteFuzzySeperators_r( fp, fs->child, indent + 1 ) ) { + return qfalse; + } + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fs->next ) { + if ( fprintf( fp, "} //end case\n" ) < 0 ) { + return qfalse; + } + } //end if + else + { + if ( fprintf( fp, "} //end default\n" ) < 0 ) { + return qfalse; + } + } //end else + } //end if + else + { + if ( !WriteFuzzyWeight( fp, fs ) ) { + return qfalse; + } + } //end else + fs = fs->next; + } while ( fs ); + indent--; + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fprintf( fp, "} //end switch\n" ) < 0 ) { + return qfalse; + } + return qtrue; +} //end of the function WriteItemFuzzyWeights_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteWeightConfig( char *filename, weightconfig_t *config ) { + int i; + FILE *fp; + weight_t *ifw; + + fp = fopen( filename, "wb" ); + if ( !fp ) { + return qfalse; + } + + for ( i = 0; i < config->numweights; i++ ) + { + ifw = &config->weights[i]; + if ( fprintf( fp, "\nweight \"%s\"\n", ifw->name ) < 0 ) { + return qfalse; + } + if ( fprintf( fp, "{\n" ) < 0 ) { + return qfalse; + } + if ( ifw->firstseperator->index > 0 ) { + if ( !WriteFuzzySeperators_r( fp, ifw->firstseperator, 1 ) ) { + return qfalse; + } + } //end if + else + { + if ( !WriteIndent( fp, 1 ) ) { + return qfalse; + } + if ( !WriteFuzzyWeight( fp, ifw->firstseperator ) ) { + return qfalse; + } + } //end else + if ( fprintf( fp, "} //end weight\n" ) < 0 ) { + return qfalse; + } + } //end for + fclose( fp ); + return qtrue; +} //end of the function WriteWeightConfig +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindFuzzyWeight( weightconfig_t *wc, char *name ) { + int i; + + for ( i = 0; i < wc->numweights; i++ ) + { + if ( !strcmp( wc->weights[i].name, name ) ) { + return i; + } //end if + } //end if + return -1; +} //end of the function FindFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight_r( int *inventory, fuzzyseperator_t *fs ) { + float scale, w1, w2; + + if ( inventory[fs->index] < fs->value ) { + if ( fs->child ) { + return FuzzyWeight_r( inventory, fs->child ); + } else { return fs->weight;} + } //end if + else if ( fs->next ) { + if ( inventory[fs->index] < fs->next->value ) { + //first weight + if ( fs->child ) { + w1 = FuzzyWeight_r( inventory, fs->child ); + } else { w1 = fs->weight;} + //second weight + if ( fs->next->child ) { + w2 = FuzzyWeight_r( inventory, fs->next->child ); + } else { w2 = fs->next->weight;} + //the scale factor + scale = ( inventory[fs->index] - fs->value ) / ( fs->next->value - fs->value ); + //scale between the two weights + return scale * w1 + ( 1 - scale ) * w2; + } //end if + return FuzzyWeight_r( inventory, fs->next ); + } //end else if + return fs->weight; +} //end of the function FuzzyWeight_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided_r( int *inventory, fuzzyseperator_t *fs ) { + float scale, w1, w2; + + if ( inventory[fs->index] < fs->value ) { + if ( fs->child ) { + return FuzzyWeightUndecided_r( inventory, fs->child ); + } else { return fs->minweight + random() * ( fs->maxweight - fs->minweight );} + } //end if + else if ( fs->next ) { + if ( inventory[fs->index] < fs->next->value ) { + //first weight + if ( fs->child ) { + w1 = FuzzyWeightUndecided_r( inventory, fs->child ); + } else { w1 = fs->minweight + random() * ( fs->maxweight - fs->minweight );} + //second weight + if ( fs->next->child ) { + w2 = FuzzyWeight_r( inventory, fs->next->child ); + } else { w2 = fs->next->minweight + random() * ( fs->next->maxweight - fs->next->minweight );} + //the scale factor + scale = ( inventory[fs->index] - fs->value ) / ( fs->next->value - fs->value ); + //scale between the two weights + return scale * w1 + ( 1 - scale ) * w2; + } //end if + return FuzzyWeightUndecided_r( inventory, fs->next ); + } //end else if + return fs->weight; +} //end of the function FuzzyWeightUndecided_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight( int *inventory, weightconfig_t *wc, int weightnum ) { +#ifdef EVALUATERECURSIVELY + return FuzzyWeight_r( inventory, wc->weights[weightnum].firstseperator ); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if ( !s ) { + return 0; + } + while ( 1 ) + { + if ( inventory[s->index] < s->value ) { + if ( s->child ) { + s = s->child; + } else { return s->weight;} + } //end if + else + { + if ( s->next ) { + s = s->next; + } else { return s->weight;} + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided( int *inventory, weightconfig_t *wc, int weightnum ) { +#ifdef EVALUATERECURSIVELY + return FuzzyWeightUndecided_r( inventory, wc->weights[weightnum].firstseperator ); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if ( !s ) { + return 0; + } + while ( 1 ) + { + if ( inventory[s->index] < s->value ) { + if ( s->child ) { + s = s->child; + } else { return s->minweight + random() * ( s->maxweight - s->minweight );} + } //end if + else + { + if ( s->next ) { + s = s->next; + } else { return s->minweight + random() * ( s->maxweight - s->minweight );} + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeightUndecided +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveFuzzySeperator_r( fuzzyseperator_t *fs ) { + if ( fs->child ) { + EvolveFuzzySeperator_r( fs->child ); + } //end if + else if ( fs->type == WT_BALANCE ) { + //every once in a while an evolution leap occurs, mutation + if ( random() < 0.01 ) { + fs->weight += crandom() * ( fs->maxweight - fs->minweight ); + } else { fs->weight += crandom() * ( fs->maxweight - fs->minweight ) * 0.5;} + //modify bounds if necesary because of mutation + if ( fs->weight < fs->minweight ) { + fs->minweight = fs->weight; + } else if ( fs->weight > fs->maxweight ) { + fs->maxweight = fs->weight; + } + } //end else if + if ( fs->next ) { + EvolveFuzzySeperator_r( fs->next ); + } +} //end of the function EvolveFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveWeightConfig( weightconfig_t *config ) { + int i; + + for ( i = 0; i < config->numweights; i++ ) + { + EvolveFuzzySeperator_r( config->weights[i].firstseperator ); + } //end for +} //end of the function EvolveWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperator_r( fuzzyseperator_t *fs, float scale ) { + if ( fs->child ) { + ScaleFuzzySeperator_r( fs->child, scale ); + } //end if + else if ( fs->type == WT_BALANCE ) { + // + fs->weight = ( fs->maxweight + fs->minweight ) * scale; + //get the weight between bounds + if ( fs->weight < fs->minweight ) { + fs->weight = fs->minweight; + } else if ( fs->weight > fs->maxweight ) { + fs->weight = fs->maxweight; + } + } //end else if + if ( fs->next ) { + ScaleFuzzySeperator_r( fs->next, scale ); + } +} //end of the function ScaleFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleWeight( weightconfig_t *config, char *name, float scale ) { + int i; + + if ( scale < 0 ) { + scale = 0; + } else if ( scale > 1 ) { + scale = 1; + } + for ( i = 0; i < config->numweights; i++ ) + { + if ( !strcmp( name, config->weights[i].name ) ) { + ScaleFuzzySeperator_r( config->weights[i].firstseperator, scale ); + break; + } //end if + } //end for +} //end of the function ScaleWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperatorBalanceRange_r( fuzzyseperator_t *fs, float scale ) { + if ( fs->child ) { + ScaleFuzzySeperatorBalanceRange_r( fs->child, scale ); + } //end if + else if ( fs->type == WT_BALANCE ) { + float mid = ( fs->minweight + fs->maxweight ) * 0.5; + //get the weight between bounds + fs->maxweight = mid + ( fs->maxweight - mid ) * scale; + fs->minweight = mid + ( fs->minweight - mid ) * scale; + if ( fs->maxweight < fs->minweight ) { + fs->maxweight = fs->minweight; + } //end if + } //end else if + if ( fs->next ) { + ScaleFuzzySeperatorBalanceRange_r( fs->next, scale ); + } +} //end of the function ScaleFuzzySeperatorBalanceRange_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzyBalanceRange( weightconfig_t *config, float scale ) { + int i; + + if ( scale < 0 ) { + scale = 0; + } else if ( scale > 100 ) { + scale = 100; + } + for ( i = 0; i < config->numweights; i++ ) + { + ScaleFuzzySeperatorBalanceRange_r( config->weights[i].firstseperator, scale ); + } //end for +} //end of the function ScaleFuzzyBalanceRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int InterbreedFuzzySeperator_r( fuzzyseperator_t *fs1, fuzzyseperator_t *fs2, + fuzzyseperator_t *fsout ) { + if ( fs1->child ) { + if ( !fs2->child || !fsout->child ) { + botimport.Print( PRT_ERROR, "cannot interbreed weight configs, unequal child\n" ); + return qfalse; + } //end if + if ( !InterbreedFuzzySeperator_r( fs2->child, fs2->child, fsout->child ) ) { + return qfalse; + } //end if + } //end if + else if ( fs1->type == WT_BALANCE ) { + if ( fs2->type != WT_BALANCE || fsout->type != WT_BALANCE ) { + botimport.Print( PRT_ERROR, "cannot interbreed weight configs, unequal balance\n" ); + return qfalse; + } //end if + fsout->weight = ( fs1->weight + fs2->weight ) / 2; + if ( fsout->weight > fsout->maxweight ) { + fsout->maxweight = fsout->weight; + } + if ( fsout->weight > fsout->minweight ) { + fsout->minweight = fsout->weight; + } + } //end else if + if ( fs1->next ) { + if ( !fs2->next || !fsout->next ) { + botimport.Print( PRT_ERROR, "cannot interbreed weight configs, unequal next\n" ); + return qfalse; + } //end if + if ( !InterbreedFuzzySeperator_r( fs1->next, fs2->next, fsout->next ) ) { + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function InterbreedFuzzySeperator_r +//=========================================================================== +// config1 and config2 are interbreeded and stored in configout +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InterbreedWeightConfigs( weightconfig_t *config1, weightconfig_t *config2, + weightconfig_t *configout ) { + int i; + + if ( config1->numweights != config2->numweights || + config1->numweights != configout->numweights ) { + botimport.Print( PRT_ERROR, "cannot interbreed weight configs, unequal numweights\n" ); + return; + } //end if + for ( i = 0; i < config1->numweights; i++ ) + { + InterbreedFuzzySeperator_r( config1->weights[i].firstseperator, + config2->weights[i].firstseperator, + configout->weights[i].firstseperator ); + } //end for +} //end of the function InterbreedWeightConfigs +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeights( void ) { + int i; + + for ( i = 0; i < MAX_WEIGHT_FILES; i++ ) + { + if ( weightFileList[i] ) { + FreeWeightConfig2( weightFileList[i] ); + weightFileList[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownWeights diff --git a/src/botlib/be_ai_weight.h b/src/botlib/be_ai_weight.h new file mode 100644 index 0000000..c6ffbb5 --- /dev/null +++ b/src/botlib/be_ai_weight.h @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_weight.h + * + * desc: fuzzy weights + * + * + *****************************************************************************/ + +#define WT_BALANCE 1 +#define MAX_WEIGHTS 128 + +//fuzzy seperator +typedef struct fuzzyseperator_s +{ + int index; + int value; + int type; + float weight; + float minweight; + float maxweight; + struct fuzzyseperator_s *child; + struct fuzzyseperator_s *next; +} fuzzyseperator_t; + +//fuzzy weight +typedef struct weight_s +{ + char *name; + struct fuzzyseperator_s *firstseperator; +} weight_t; + +//weight configuration +typedef struct weightconfig_s +{ + int numweights; + weight_t weights[MAX_WEIGHTS]; + char filename[MAX_QPATH]; +} weightconfig_t; + +//reads a weight configuration +weightconfig_t *ReadWeightConfig( char *filename ); +//free a weight configuration +void FreeWeightConfig( weightconfig_t *config ); +//writes a weight configuration, returns true if successfull +qboolean WriteWeightConfig( char *filename, weightconfig_t *config ); +//find the fuzzy weight with the given name +int FindFuzzyWeight( weightconfig_t *wc, char *name ); +//returns the fuzzy weight for the given inventory and weight +float FuzzyWeight( int *inventory, weightconfig_t *wc, int weightnum ); +float FuzzyWeightUndecided( int *inventory, weightconfig_t *wc, int weightnum ); +//scales the weight with the given name +void ScaleWeight( weightconfig_t *config, char *name, float scale ); +//scale the balance range +void ScaleBalanceRange( weightconfig_t *config, float scale ); +//evolves the weight configuration +void EvolveWeightConfig( weightconfig_t *config ); +//interbreed the weight configurations and stores the interbreeded one in configout +void InterbreedWeightConfigs( weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout ); +//frees cached weight configurations +void BotShutdownWeights( void ); diff --git a/src/botlib/be_ea.c b/src/botlib/be_ea.c new file mode 100644 index 0000000..1777519 --- /dev/null +++ b/src/botlib/be_ea.c @@ -0,0 +1,480 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ea.c + * + * desc: elementary actions + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "../game/botlib.h" +#include "be_interface.h" + +#define MAX_USERMOVE 400 +#define MAX_COMMANDARGUMENTS 10 +#define ACTION_JUMPEDLASTFRAME 128 + +bot_input_t *botinputs; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Say( int client, char *str ) { + botimport.BotClientCommand( client, va( "say %s", str ) ); +} //end of the function EA_Say +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_SayTeam( int client, char *str ) { + botimport.BotClientCommand( client, va( "say_team %s", str ) ); +} //end of the function EA_SayTeam +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_UseItem( int client, char *it ) { + botimport.BotClientCommand( client, va( "use %s", it ) ); +} //end of the function EA_UseItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DropItem( int client, char *it ) { + botimport.BotClientCommand( client, va( "drop %s", it ) ); +} //end of the function EA_DropItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_UseInv( int client, char *inv ) { + botimport.BotClientCommand( client, va( "invuse %s", inv ) ); +} //end of the function EA_UseInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DropInv( int client, char *inv ) { + botimport.BotClientCommand( client, va( "invdrop %s", inv ) ); +} //end of the function EA_DropInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Gesture( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_GESTURE; +} //end of the function EA_Gesture +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Command( int client, char *command ) { + botimport.BotClientCommand( client, command ); +} //end of the function EA_Command +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_SelectWeapon( int client, int weapon ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->weapon = weapon; +} //end of the function EA_SelectWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Attack( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_ATTACK; +} //end of the function EA_Attack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Reload( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_RELOAD; +} //end of the function EA_Attack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Talk( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_TALK; +} //end of the function EA_Talk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Use( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_USE; +} //end of the function EA_Use +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Respawn( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_RESPAWN; +} //end of the function EA_Respawn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Jump( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + if ( bi->actionflags & ACTION_JUMPEDLASTFRAME ) { + bi->actionflags &= ~ACTION_JUMP; + } //end if + else + { + bi->actionflags |= ACTION_JUMP; + } //end if +} //end of the function EA_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DelayedJump( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + if ( bi->actionflags & ACTION_JUMPEDLASTFRAME ) { + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } //end if + else + { + bi->actionflags |= ACTION_DELAYEDJUMP; + } //end if +} //end of the function EA_DelayedJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Crouch( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_CROUCH; +} //end of the function EA_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Walk( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_WALK; +} //end of the function EA_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveUp( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEUP; +} //end of the function EA_MoveUp +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveDown( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEDOWN; +} //end of the function EA_MoveDown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveForward( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEFORWARD; +} //end of the function EA_MoveForward +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveBack( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEBACK; +} //end of the function EA_MoveBack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveLeft( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVELEFT; +} //end of the function EA_MoveLeft +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveRight( int client ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVERIGHT; +} //end of the function EA_MoveRight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Move( int client, vec3_t dir, float speed ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy( dir, bi->dir ); + //cap speed + if ( speed > MAX_USERMOVE ) { + speed = MAX_USERMOVE; + } else if ( speed < -MAX_USERMOVE ) { + speed = -MAX_USERMOVE; + } + bi->speed = speed; +} //end of the function EA_Move +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_View( int client, vec3_t viewangles ) { + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy( viewangles, bi->viewangles ); +} //end of the function EA_View +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_EndRegular( int client, float thinktime ) { +/* + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + + bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = thinktime; + botimport.BotInput(client, bi); + + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; +*/ +} //end of the function EA_EndRegular +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_GetInput( int client, float thinktime, bot_input_t *input ) { + bot_input_t *bi; +// int jumped = qfalse; + + bi = &botinputs[client]; + +// bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = thinktime; + memcpy( input, bi, sizeof( bot_input_t ) ); + + /* + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; + */ +} //end of the function EA_GetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_ResetInput( int client, bot_input_t *init ) { + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = 0; + VectorClear( bi->dir ); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if ( jumped ) { + bi->actionflags |= ACTION_JUMPEDLASTFRAME; + } + + if ( init ) { + memcpy( bi, init, sizeof( bot_input_t ) ); + } +} //end of the function EA_ResetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int EA_Setup( void ) { + //initialize the bot inputs + botinputs = (bot_input_t *) GetClearedHunkMemory( + botlibglobals.maxclients * sizeof( bot_input_t ) ); + return BLERR_NOERROR; +} //end of the function EA_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Shutdown( void ) { + FreeMemory( botinputs ); + botinputs = NULL; +} //end of the function EA_Shutdown diff --git a/src/botlib/be_interface.c b/src/botlib/be_interface.c new file mode 100644 index 0000000..208bf5a --- /dev/null +++ b/src/botlib/be_interface.c @@ -0,0 +1,903 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_interface.c + * + * desc: bot library interface + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" +#include "be_interface.h" + +#include "../game/be_ea.h" +#include "be_ai_weight.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_gen.h" + +//library globals in a structure +botlib_globals_t botlibglobals; + +botlib_export_t be_botlib_export; +botlib_import_t botimport; +// +int bot_developer; +//qtrue if the library is setup +int botlibsetup = qfalse; + +//=========================================================================== +// +// several functions used by the exported functions +// +//=========================================================================== + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// Ridah, faster Win32 code +#ifdef _WIN32 +#undef MAX_PATH // this is an ugly hack, to temporarily ignore the current definition, since it's also defined in windows.h +#include +#undef MAX_PATH +#define MAX_PATH MAX_QPATH +#endif + +int Sys_MilliSeconds( void ) { +// Ridah, faster Win32 code +#ifdef _WIN32 + int sys_curtime; + static qboolean initialized = qfalse; + static int sys_timeBase; + + if ( !initialized ) { + sys_timeBase = timeGetTime(); + initialized = qtrue; + } + sys_curtime = timeGetTime() - sys_timeBase; + + return sys_curtime; +#else + return clock() * 1000 / CLOCKS_PER_SEC; +#endif +} //end of the function Sys_MilliSeconds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidClientNumber( int num, char *str ) { + if ( num < 0 || num > botlibglobals.maxclients ) { + //weird: the disabled stuff results in a crash + botimport.Print( PRT_ERROR, "%s: invalid client number %d, [0, %d]\n", + str, num, botlibglobals.maxclients ); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidEntityNumber( int num, char *str ) { + if ( num < 0 || num > botlibglobals.maxentities ) { + botimport.Print( PRT_ERROR, "%s: invalid entity number %d, [0, %d]\n", + str, num, botlibglobals.maxentities ); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BotLibSetup( char *str ) { +// return qtrue; + + if ( !botlibglobals.botlibsetup ) { + botimport.Print( PRT_ERROR, "%s: bot library used before being setup\n", str ); + return qfalse; + } //end if + return qtrue; +} //end of the function BotLibSetup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibSetup( void ) { + int errnum; + + bot_developer = LibVarGetValue( "bot_developer" ); + //initialize byte swapping (litte endian etc.) + Swap_Init(); + Log_Open( "botlib.log" ); + // + botimport.Print( PRT_MESSAGE, "------- BotLib Initialization -------\n" ); + // + botlibglobals.maxclients = (int) LibVarValue( "maxclients", "128" ); + botlibglobals.maxentities = (int) LibVarValue( "maxentities", "1024" ); + + errnum = AAS_Setup(); //be_aas_main.c + if ( errnum != BLERR_NOERROR ) { + return errnum; + } + errnum = EA_Setup(); //be_ea.c + if ( errnum != BLERR_NOERROR ) { + return errnum; + } +// errnum = BotSetupWeaponAI(); //be_ai_weap.c +// if (errnum != BLERR_NOERROR)return errnum; +// errnum = BotSetupGoalAI(); //be_ai_goal.c +// if (errnum != BLERR_NOERROR) return errnum; +// errnum = BotSetupChatAI(); //be_ai_chat.c +// if (errnum != BLERR_NOERROR) return errnum; +// errnum = BotSetupMoveAI(); //be_ai_move.c +// if (errnum != BLERR_NOERROR) return errnum; + + botlibsetup = qtrue; + botlibglobals.botlibsetup = qtrue; + + return BLERR_NOERROR; +} //end of the function Export_BotLibSetup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibShutdown( void ) { + static int recursive = 0; + + if ( !BotLibSetup( "BotLibShutdown" ) ) { + return BLERR_LIBRARYNOTSETUP; + } + // + if ( recursive ) { + return BLERR_NOERROR; + } + recursive = 1; + // shutdown all AI subsystems + BotShutdownChatAI(); //be_ai_chat.c + BotShutdownMoveAI(); //be_ai_move.c + BotShutdownGoalAI(); //be_ai_goal.c + BotShutdownWeaponAI(); //be_ai_weap.c + BotShutdownWeights(); //be_ai_weight.c + BotShutdownCharacters(); //be_ai_char.c + // shutdown AAS + AAS_Shutdown(); + // shutdown bot elemantary actions + EA_Shutdown(); + // free all libvars + LibVarDeAllocAll(); + // remove all global defines from the pre compiler + PC_RemoveAllGlobalDefines(); + // shut down library log file + Log_Shutdown(); + // + botlibsetup = qfalse; + botlibglobals.botlibsetup = qfalse; + recursive = 0; + // print any files still open + PC_CheckOpenSourceHandles(); + // +#ifdef _DEBUG + Log_AlwaysOpen( "memory.log" ); + PrintMemoryLabels(); + Log_Shutdown(); +#endif + return BLERR_NOERROR; +} //end of the function Export_BotLibShutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarSet( char *var_name, char *value ) { + LibVarSet( var_name, value ); + return BLERR_NOERROR; +} //end of the function Export_BotLibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarGet( char *var_name, char *value, int size ) { + char *varvalue; + + varvalue = LibVarGetString( var_name ); + strncpy( value, varvalue, size - 1 ); + value[size - 1] = '\0'; + return BLERR_NOERROR; +} //end of the function Export_BotLibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibStartFrame( float time ) { + if ( !BotLibSetup( "BotStartFrame" ) ) { + return BLERR_LIBRARYNOTSETUP; + } + return AAS_StartFrame( time ); +} //end of the function Export_BotLibStartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibLoadMap( const char *mapname ) { +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif + int errnum; + + if ( !BotLibSetup( "BotLoadMap" ) ) { + return BLERR_LIBRARYNOTSETUP; + } + // + botimport.Print( PRT_MESSAGE, "------------ Map Loading ------------\n" ); + //startup AAS for the current map, model and sound index + errnum = AAS_LoadMap( mapname ); + if ( errnum != BLERR_NOERROR ) { + return errnum; + } + //initialize the items in the level + BotInitLevelItems(); //be_ai_goal.h + BotSetBrushModelTypes(); //be_ai_move.h + // + botimport.Print( PRT_MESSAGE, "-------------------------------------\n" ); +#ifdef DEBUG + botimport.Print( PRT_MESSAGE, "map loaded in %d msec\n", Sys_MilliSeconds() - starttime ); +#endif + // + return BLERR_NOERROR; +} //end of the function Export_BotLibLoadMap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibUpdateEntity( int ent, bot_entitystate_t *state ) { + if ( !BotLibSetup( "BotUpdateEntity" ) ) { + return BLERR_LIBRARYNOTSETUP; + } + if ( !ValidEntityNumber( ent, "BotUpdateEntity" ) ) { + return BLERR_INVALIDENTITYNUMBER; + } + + return AAS_UpdateEntity( ent, state ); +} //end of the function Export_BotLibUpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction( int entnum, vec3_t origin, vec3_t dir ); +void ElevatorBottomCenter( aas_reachability_t *reach, vec3_t bottomcenter ); +int BotGetReachabilityToGoal( vec3_t origin, int areanum, int entnum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags ); + +int AAS_PointLight( vec3_t origin, int *red, int *green, int *blue ); + +int AAS_TraceAreas( vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas ); + +int AAS_Reachability_WeaponJump( int area1num, int area2num ); + +int BotFuzzyPointReachabilityArea( vec3_t origin ); + +float BotGapDistance( vec3_t origin, vec3_t hordir, int entnum ); + +int AAS_NearestHideArea( int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags ); + +int AAS_FindAttackSpotWithinRange( int srcnum, int rangenum, int enemynum, float rangedist, int travelflags, float *outpos ); + +void AAS_SetAASBlockingEntity( vec3_t absmin, vec3_t absmax, qboolean blocking ); + +int BotExportTest( int parm0, char *parm1, vec3_t parm2, vec3_t parm3 ) { + +// return AAS_PointLight(parm2, NULL, NULL, NULL); + +#ifdef DEBUG + static int area = -1; + static int line[2]; + int newarea, i, highlightarea, bot_testhidepos, hideposarea; +// int reachnum; + vec3_t eye, forward, right, end, origin; +// vec3_t bottomcenter; +// aas_trace_t trace; +// aas_face_t *face; +// aas_entity_t *ent; +// bsp_trace_t bsptrace; +// aas_reachability_t reach; +// bot_goal_t goal; + + +// clock_t start_time, end_time; + // + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + // +// int areas[10], numareas; + + + //return 0; + + if ( !( *aasworld ).loaded ) { + return 0; + } + AAS_SetCurrentWorld( 0 ); + + for ( i = 0; i < 2; i++ ) if ( !line[i] ) { + line[i] = botimport.DebugLineCreate(); + } + +// AAS_ClearShownDebugLines(); + bot_testhidepos = LibVarGetValue( "bot_testhidepos" ); + if ( bot_testhidepos ) { + VectorCopy( parm2, origin ); + newarea = BotFuzzyPointReachabilityArea( origin ); + if ( parm0 & 1 ) { + botlibglobals.goalareanum = newarea; + VectorCopy( origin, botlibglobals.goalorigin ); + botimport.Print( PRT_MESSAGE, "new enemy position %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea ); + } //end if + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + hideposarea = AAS_NearestHideArea( 0, origin, AAS_PointAreaNum( origin ), 0, + botlibglobals.goalorigin, botlibglobals.goalareanum, TFL_DEFAULT ); + + //area we are currently in + AAS_ShowAreaPolygons( newarea, 1, qtrue ); + //enemy position + AAS_ShowAreaPolygons( botlibglobals.goalareanum, 2, qtrue ); + //area we should go hide + AAS_ShowAreaPolygons( hideposarea, 4, qtrue ); + return 0; + } + + //if (AAS_AgainstLadder(parm2)) botimport.Print(PRT_MESSAGE, "against ladder\n"); + //BotOnGround(parm2, PRESENCE_NORMAL, 1, &newarea, &newarea); + //botimport.Print(PRT_MESSAGE, "%f %f %f\n", parm2[0], parm2[1], parm2[2]); + //* + highlightarea = LibVarGetValue( "bot_highlightarea" ); + if ( highlightarea > 0 ) { + newarea = highlightarea; + } //end if + else + { + VectorCopy( parm2, origin ); + origin[2] += 0.5; + //newarea = AAS_PointAreaNum(origin); + newarea = BotFuzzyPointReachabilityArea( origin ); + } //end else + + botimport.Print( PRT_MESSAGE, "\rtravel time to goal (%d) = %d ", botlibglobals.goalareanum, + AAS_AreaTravelTimeToGoalArea( newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT ) ); + //newarea = BotReachabilityArea(origin, qtrue); + if ( newarea != area ) { + botimport.Print( PRT_MESSAGE, "origin = %f, %f, %f\n", origin[0], origin[1], origin[2] ); + area = newarea; + botimport.Print( PRT_MESSAGE, "new area %d, cluster %d, presence type %d\n", + area, AAS_AreaCluster( area ), AAS_PointPresenceType( origin ) ); + if ( ( *aasworld ).areasettings[area].areaflags & AREA_LIQUID ) { + botimport.Print( PRT_MESSAGE, "liquid area\n" ); + } //end if + botimport.Print( PRT_MESSAGE, "area contents: " ); + if ( ( *aasworld ).areasettings[area].contents & AREACONTENTS_WATER ) { + botimport.Print( PRT_MESSAGE, "water " ); + } //end if + if ( ( *aasworld ).areasettings[area].contents & AREACONTENTS_LAVA ) { + botimport.Print( PRT_MESSAGE, "lava " ); + } //end if + if ( ( *aasworld ).areasettings[area].contents & AREACONTENTS_SLIME ) { +// botimport.Print(PRT_MESSAGE, "slime "); + botimport.Print( PRT_MESSAGE, "slag " ); + } //end if + if ( ( *aasworld ).areasettings[area].contents & AREACONTENTS_JUMPPAD ) { + botimport.Print( PRT_MESSAGE, "jump pad " ); + } //end if + if ( ( *aasworld ).areasettings[area].contents & AREACONTENTS_CLUSTERPORTAL ) { + botimport.Print( PRT_MESSAGE, "cluster portal " ); + } //end if + if ( ( *aasworld ).areasettings[area].contents & AREACONTENTS_DONOTENTER ) { + botimport.Print( PRT_MESSAGE, "do not enter " ); + } //end if + if ( ( *aasworld ).areasettings[area].contents & AREACONTENTS_DONOTENTER_LARGE ) { + botimport.Print( PRT_MESSAGE, "do not enter large " ); + } //end if + if ( !( *aasworld ).areasettings[area].contents ) { + botimport.Print( PRT_MESSAGE, "empty " ); + } //end if + if ( ( *aasworld ).areasettings[area].areaflags & AREA_DISABLED ) { + botimport.Print( PRT_MESSAGE, "DISABLED" ); + } //end if + botimport.Print( PRT_MESSAGE, "\n" ); + botimport.Print( PRT_MESSAGE, "travel time to goal (%d) = %d\n", botlibglobals.goalareanum, + AAS_AreaTravelTimeToGoalArea( newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT | TFL_ROCKETJUMP ) ); + /* + VectorCopy(origin, end); + end[2] += 5; + numareas = AAS_TraceAreas(origin, end, areas, NULL, 10); + AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + botimport.Print(PRT_MESSAGE, "num areas = %d, area = %d\n", numareas, areas[0]); + */ + /* + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea); + */ + } //end if + //* + if ( parm0 & 1 ) { + botlibglobals.goalareanum = newarea; + VectorCopy( parm2, botlibglobals.goalorigin ); + botimport.Print( PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea ); + } //end if*/ +// if (parm0 & BUTTON_USE) +// { +// botlibglobals.runai = !botlibglobals.runai; +// if (botlibglobals.runai) botimport.Print(PRT_MESSAGE, "started AI\n"); +// else botimport.Print(PRT_MESSAGE, "stopped AI\n"); + //* / + /* + goal.areanum = botlibglobals.goalareanum; + reachnum = BotGetReachabilityToGoal(parm2, newarea, 1, + ms.avoidreach, ms.avoidreachtimes, + &goal, TFL_DEFAULT); + if (!reachnum) + { + botimport.Print(PRT_MESSAGE, "goal not reachable\n"); + } //end if + else + { + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ClearShownDebugLines(); + AAS_ShowArea(area, qtrue); + AAS_ShowArea(reach.areanum, qtrue); + AAS_DrawCross(reach.start, 6, LINECOLOR_BLUE); + AAS_DrawCross(reach.end, 6, LINECOLOR_RED); + // + if (reach.traveltype == TRAVEL_ELEVATOR) + { + ElevatorBottomCenter(&reach, bottomcenter); + AAS_DrawCross(bottomcenter, 10, LINECOLOR_GREEN); + } //end if + } //end else*/ +// botimport.Print(PRT_MESSAGE, "travel time to goal = %d\n", +// AAS_AreaTravelTimeToGoalArea(area, origin, botlibglobals.goalareanum, TFL_DEFAULT)); +// botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); +// AAS_Reachability_WeaponJump(703, 716); +// } //end if*/ + +/* face = AAS_AreaGroundFace(newarea, parm2); + if (face) + { + AAS_ShowFace(face - (*aasworld).faces); + } //end if*/ + /* + AAS_ClearShownDebugLines(); + AAS_ShowArea(newarea, parm0 & BUTTON_USE); + AAS_ShowReachableAreas(area); + // + */ + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_ShowAreaPolygons( newarea, 1, parm0 & 4 ); + if ( parm0 & 2 ) { + AAS_ShowReachableAreas( area ); + } else + { + static int lastgoalareanum, lastareanum; + static int avoidreach[MAX_AVOIDREACH]; + static float avoidreachtimes[MAX_AVOIDREACH]; + static int avoidreachtries[MAX_AVOIDREACH]; + int reachnum; + bot_goal_t goal; + aas_reachability_t reach; + + goal.areanum = botlibglobals.goalareanum; + VectorCopy( botlibglobals.goalorigin, goal.origin ); + reachnum = BotGetReachabilityToGoal( origin, newarea, -1, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT | TFL_FUNCBOB, TFL_DEFAULT | TFL_FUNCBOB ); + AAS_ReachabilityFromNum( reachnum, &reach ); + AAS_ShowReachability( &reach ); + } //end else + VectorClear( forward ); + //BotGapDistance(origin, forward, 0); + /* + if (parm0 & BUTTON_USE) + { + botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); + AAS_Reachability_WeaponJump(703, 716); + } //end if*/ + + AngleVectors( parm3, forward, right, NULL ); + //get the eye 16 units to the right of the origin + VectorMA( parm2, 8, right, eye ); + //get the eye 24 units up + eye[2] += 24; + //get the end point for the line to be traced + VectorMA( eye, 800, forward, end ); + +// AAS_TestMovementPrediction(1, parm2, forward); +/* //trace the line to find the hit point + trace = AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); + if (!line[0]) line[0] = botimport.DebugLineCreate(); + botimport.DebugLineShow(line[0], eye, trace.endpos, LINECOLOR_BLUE); + // + AAS_ClearShownDebugLines(); + if (trace.ent) + { + ent = &(*aasworld).entities[trace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if*/ + +/* + start_time = clock(); + for (i = 0; i < 2000; i++) + { + AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); +// AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); + } //end for + end_time = clock(); + botimport.Print(PRT_MESSAGE, "me %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); + start_time = clock(); + for (i = 0; i < 2000; i++) + { + AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + } //end for + end_time = clock(); + botimport.Print(PRT_MESSAGE, "id %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); +// +*/ + + /* + AAS_ClearShownDebugLines(); + //bsptrace = AAS_Trace(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); + bsptrace = AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + if (!line[0]) line[0] = botimport.DebugLineCreate(); + botimport.DebugLineShow(line[0], eye, bsptrace.endpos, LINECOLOR_YELLOW); + if (bsptrace.fraction < 1.0) + { + face = AAS_TraceEndFace(&trace); + if (face) + { + AAS_ShowFace(face - (*aasworld).faces); + } //end if*/ + /* + AAS_DrawPlaneCross(bsptrace.endpos, + bsptrace.plane.normal, + bsptrace.plane.dist + bsptrace.exp_dist, + bsptrace.plane.type, LINECOLOR_GREEN); + if (trace.ent) + { + ent = &(*aasworld).entities[trace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if* / +} //end if*/ + /*/ + //bsptrace = AAS_Trace2(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); + bsptrace = AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + botimport.DebugLineShow(line[1], eye, bsptrace.endpos, LINECOLOR_BLUE); + if (bsptrace.fraction < 1.0) + { + AAS_DrawPlaneCross(bsptrace.endpos, + bsptrace.plane.normal, + bsptrace.plane.dist,// + bsptrace.exp_dist, + bsptrace.plane.type, LINECOLOR_RED); + if (bsptrace.ent) + { + ent = &(*aasworld).entities[bsptrace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if* / + } //end if*/ + + //*/ +#endif + return 0; +} //end of the function BotExportTest + + +/* +============ +Init_AAS_Export +============ +*/ +static void Init_AAS_Export( aas_export_t *aas ) { + //-------------------------------------------- + // be_aas_entity.c + //-------------------------------------------- + aas->AAS_EntityInfo = AAS_EntityInfo; + //-------------------------------------------- + // be_aas_main.c + //-------------------------------------------- + aas->AAS_Initialized = AAS_Initialized; + aas->AAS_PresenceTypeBoundingBox = AAS_PresenceTypeBoundingBox; + aas->AAS_Time = AAS_Time; + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + aas->AAS_PointAreaNum = AAS_PointAreaNum; + aas->AAS_TraceAreas = AAS_TraceAreas; + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + aas->AAS_PointContents = AAS_PointContents; + aas->AAS_NextBSPEntity = AAS_NextBSPEntity; + aas->AAS_ValueForBSPEpairKey = AAS_ValueForBSPEpairKey; + aas->AAS_VectorForBSPEpairKey = AAS_VectorForBSPEpairKey; + aas->AAS_FloatForBSPEpairKey = AAS_FloatForBSPEpairKey; + aas->AAS_IntForBSPEpairKey = AAS_IntForBSPEpairKey; + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + aas->AAS_AreaReachability = AAS_AreaReachability; + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + aas->AAS_AreaTravelTimeToGoalArea = AAS_AreaTravelTimeToGoalArea; + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + aas->AAS_Swimming = AAS_Swimming; + aas->AAS_PredictClientMovement = AAS_PredictClientMovement; + + // Ridah, route-tables + //-------------------------------------------- + // be_aas_routetable.c + //-------------------------------------------- + aas->AAS_RT_ShowRoute = AAS_RT_ShowRoute; + aas->AAS_RT_GetHidePos = AAS_RT_GetHidePos; + aas->AAS_FindAttackSpotWithinRange = AAS_FindAttackSpotWithinRange; + aas->AAS_SetAASBlockingEntity = AAS_SetAASBlockingEntity; + // done. + + // Ridah, multiple AAS files + aas->AAS_SetCurrentWorld = AAS_SetCurrentWorld; + // done. + +} + + +/* +============ +Init_EA_Export +============ +*/ +static void Init_EA_Export( ea_export_t *ea ) { + //ClientCommand elementary actions + ea->EA_Say = EA_Say; + ea->EA_SayTeam = EA_SayTeam; + ea->EA_UseItem = EA_UseItem; + ea->EA_DropItem = EA_DropItem; + ea->EA_UseInv = EA_UseInv; + ea->EA_DropInv = EA_DropInv; + ea->EA_Gesture = EA_Gesture; + ea->EA_Command = EA_Command; + ea->EA_SelectWeapon = EA_SelectWeapon; + ea->EA_Talk = EA_Talk; + ea->EA_Attack = EA_Attack; + ea->EA_Reload = EA_Reload; + ea->EA_Use = EA_Use; + ea->EA_Respawn = EA_Respawn; + ea->EA_Jump = EA_Jump; + ea->EA_DelayedJump = EA_DelayedJump; + ea->EA_Crouch = EA_Crouch; + ea->EA_MoveUp = EA_MoveUp; + ea->EA_MoveDown = EA_MoveDown; + ea->EA_MoveForward = EA_MoveForward; + ea->EA_MoveBack = EA_MoveBack; + ea->EA_MoveLeft = EA_MoveLeft; + ea->EA_MoveRight = EA_MoveRight; + ea->EA_Move = EA_Move; + ea->EA_View = EA_View; + ea->EA_GetInput = EA_GetInput; + ea->EA_EndRegular = EA_EndRegular; + ea->EA_ResetInput = EA_ResetInput; +} + + +/* +============ +Init_AI_Export +============ +*/ +static void Init_AI_Export( ai_export_t *ai ) { + //----------------------------------- + // be_ai_char.h + //----------------------------------- + ai->BotLoadCharacter = BotLoadCharacter; + ai->BotFreeCharacter = BotFreeCharacter; + ai->Characteristic_Float = Characteristic_Float; + ai->Characteristic_BFloat = Characteristic_BFloat; + ai->Characteristic_Integer = Characteristic_Integer; + ai->Characteristic_BInteger = Characteristic_BInteger; + ai->Characteristic_String = Characteristic_String; + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + ai->BotAllocChatState = BotAllocChatState; + ai->BotFreeChatState = BotFreeChatState; + ai->BotQueueConsoleMessage = BotQueueConsoleMessage; + ai->BotRemoveConsoleMessage = BotRemoveConsoleMessage; + ai->BotNextConsoleMessage = BotNextConsoleMessage; + ai->BotNumConsoleMessages = BotNumConsoleMessages; + ai->BotInitialChat = BotInitialChat; + ai->BotNumInitialChats = BotNumInitialChats; + ai->BotReplyChat = BotReplyChat; + ai->BotChatLength = BotChatLength; + ai->BotEnterChat = BotEnterChat; + ai->BotGetChatMessage = BotGetChatMessage; + ai->StringContains = StringContains; + ai->BotFindMatch = BotFindMatch; + ai->BotMatchVariable = BotMatchVariable; + ai->UnifyWhiteSpaces = UnifyWhiteSpaces; + ai->BotReplaceSynonyms = BotReplaceSynonyms; + ai->BotLoadChatFile = BotLoadChatFile; + ai->BotSetChatGender = BotSetChatGender; + ai->BotSetChatName = BotSetChatName; + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + ai->BotResetGoalState = BotResetGoalState; + ai->BotResetAvoidGoals = BotResetAvoidGoals; + ai->BotRemoveFromAvoidGoals = BotRemoveFromAvoidGoals; + ai->BotPushGoal = BotPushGoal; + ai->BotPopGoal = BotPopGoal; + ai->BotEmptyGoalStack = BotEmptyGoalStack; + ai->BotDumpAvoidGoals = BotDumpAvoidGoals; + ai->BotDumpGoalStack = BotDumpGoalStack; + ai->BotGoalName = BotGoalName; + ai->BotGetTopGoal = BotGetTopGoal; + ai->BotGetSecondGoal = BotGetSecondGoal; + ai->BotChooseLTGItem = BotChooseLTGItem; + ai->BotChooseNBGItem = BotChooseNBGItem; + ai->BotTouchingGoal = BotTouchingGoal; + ai->BotItemGoalInVisButNotVisible = BotItemGoalInVisButNotVisible; + ai->BotGetLevelItemGoal = BotGetLevelItemGoal; + ai->BotGetNextCampSpotGoal = BotGetNextCampSpotGoal; + ai->BotGetMapLocationGoal = BotGetMapLocationGoal; + ai->BotAvoidGoalTime = BotAvoidGoalTime; + ai->BotInitLevelItems = BotInitLevelItems; + ai->BotUpdateEntityItems = BotUpdateEntityItems; + ai->BotLoadItemWeights = BotLoadItemWeights; + ai->BotFreeItemWeights = BotFreeItemWeights; + ai->BotInterbreedGoalFuzzyLogic = BotInterbreedGoalFuzzyLogic; + ai->BotSaveGoalFuzzyLogic = BotSaveGoalFuzzyLogic; + ai->BotMutateGoalFuzzyLogic = BotMutateGoalFuzzyLogic; + ai->BotAllocGoalState = BotAllocGoalState; + ai->BotFreeGoalState = BotFreeGoalState; + //----------------------------------- + // be_ai_move.h + //----------------------------------- + ai->BotResetMoveState = BotResetMoveState; + ai->BotMoveToGoal = BotMoveToGoal; + ai->BotMoveInDirection = BotMoveInDirection; + ai->BotResetAvoidReach = BotResetAvoidReach; + ai->BotResetLastAvoidReach = BotResetLastAvoidReach; + ai->BotReachabilityArea = BotReachabilityArea; + ai->BotMovementViewTarget = BotMovementViewTarget; + ai->BotPredictVisiblePosition = BotPredictVisiblePosition; + ai->BotAllocMoveState = BotAllocMoveState; + ai->BotFreeMoveState = BotFreeMoveState; + ai->BotInitMoveState = BotInitMoveState; + // Ridah + ai->BotInitAvoidReach = BotInitAvoidReach; + // done. + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + ai->BotChooseBestFightWeapon = BotChooseBestFightWeapon; + ai->BotGetWeaponInfo = BotGetWeaponInfo; + ai->BotLoadWeaponWeights = BotLoadWeaponWeights; + ai->BotAllocWeaponState = BotAllocWeaponState; + ai->BotFreeWeaponState = BotFreeWeaponState; + ai->BotResetWeaponState = BotResetWeaponState; + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + ai->GeneticParentsAndChildSelection = GeneticParentsAndChildSelection; +} + + +/* +============ +GetBotLibAPI +============ +*/ +botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ) { + botimport = *import; + + memset( &be_botlib_export, 0, sizeof( be_botlib_export ) ); + + if ( apiVersion != BOTLIB_API_VERSION ) { + botimport.Print( PRT_ERROR, "Mismatched BOTLIB_API_VERSION: expected %i, got %i\n", BOTLIB_API_VERSION, apiVersion ); + return NULL; + } + + Init_AAS_Export( &be_botlib_export.aas ); + Init_EA_Export( &be_botlib_export.ea ); + Init_AI_Export( &be_botlib_export.ai ); + + be_botlib_export.BotLibSetup = Export_BotLibSetup; + be_botlib_export.BotLibShutdown = Export_BotLibShutdown; + be_botlib_export.BotLibVarSet = Export_BotLibVarSet; + be_botlib_export.BotLibVarGet = Export_BotLibVarGet; + be_botlib_export.PC_AddGlobalDefine = PC_AddGlobalDefine; + be_botlib_export.PC_LoadSourceHandle = PC_LoadSourceHandle; + be_botlib_export.PC_FreeSourceHandle = PC_FreeSourceHandle; + be_botlib_export.PC_ReadTokenHandle = PC_ReadTokenHandle; + be_botlib_export.PC_SourceFileAndLine = PC_SourceFileAndLine; + + be_botlib_export.BotLibStartFrame = Export_BotLibStartFrame; + be_botlib_export.BotLibLoadMap = Export_BotLibLoadMap; + be_botlib_export.BotLibUpdateEntity = Export_BotLibUpdateEntity; + be_botlib_export.Test = BotExportTest; + + return &be_botlib_export; +} diff --git a/src/botlib/be_interface.h b/src/botlib/be_interface.h new file mode 100644 index 0000000..42fc564 --- /dev/null +++ b/src/botlib/be_interface.h @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_interface.h + * + * desc: botlib interface + * + * + *****************************************************************************/ + +/* +"Do not go where the path leads, rather go where there's no track and leave a trail." + +"AAS (Area Awareness System)" + +"Part of the Gladiator is BoGuS (Bot Guidance System)" + +"ANSI (Advanced Navigational System Interface)" + +"to make things work the only thing you really have to do is think things work." + +"a madman is just someone living in another reality which isn't shared among many people" +*/ + +//#define DEBUG //debug code +#define RANDOMIZE //randomize bot behaviour +#if defined( WIN32 ) || defined( _WIN32 ) +#define AASZIP //allow reading directly from aasX.zip files +#endif +#define QUAKE2 //bot for Quake2 +//#define HALFLIFE //bot for Half-Life + +//========================================================== +// +// global variable structures +// +//========================================================== + +//FIXME: get rid of this global structure +typedef struct botlib_globals_s +{ + int botlibsetup; //true when the bot library has been setup + int maxentities; //maximum number of entities + int maxclients; //maximum number of clients + float time; //the global time +#ifdef DEBUG + qboolean debug; //true if debug is on + int goalareanum; + vec3_t goalorigin; + int runai; +#endif +} botlib_globals_t; + +//========================================================== +// +// global variables +// +//========================================================== + +extern botlib_globals_t botlibglobals; +extern botlib_import_t botimport; +extern int bot_developer; //true if developer is on + +// +int Sys_MilliSeconds( void ); + diff --git a/src/botlib/botlib.vcproj b/src/botlib/botlib.vcproj new file mode 100644 index 0000000..aab6a91 --- /dev/null +++ b/src/botlib/botlib.vcproj @@ -0,0 +1,1024 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/botlib/l_crc.c b/src/botlib/l_crc.c new file mode 100644 index 0000000..7b613ce --- /dev/null +++ b/src/botlib/l_crc.c @@ -0,0 +1,155 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_crc.c + * + * desc: CRC calculation + * + * + *****************************************************************************/ + +#include +#include +#include + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" //for botimport.Print + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +unsigned short crctable[257] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, + 0x0000 // code reaches element 256 +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_Init( unsigned short *crcvalue ) { + *crcvalue = CRC_INIT_VALUE; +} //end of the function CRC_Init +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_ProcessByte( unsigned short *crcvalue, byte data ) { + *crcvalue = ( *crcvalue << 8 ) ^ crctable[( *crcvalue >> 8 ) ^ data]; +} //end of the function CRC_ProcessByte +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_Value( unsigned short crcvalue ) { + return crcvalue ^ CRC_XOR_VALUE; +} //end of the function CRC_Value +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_ProcessString( unsigned char *data, int length ) { + unsigned short crcvalue; + int i, ind; + + CRC_Init( &crcvalue ); + + for ( i = 0; i < length; i++ ) + { + ind = ( crcvalue >> 8 ) ^ data[i]; + if ( ind < 0 || ind > 256 ) { + ind = 0; + } + crcvalue = ( crcvalue << 8 ) ^ crctable[ind]; + } //end for + return CRC_Value( crcvalue ); +} //end of the function CRC_ProcessString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_ContinueProcessString( unsigned short *crc, char *data, int length ) { + int i; + + for ( i = 0; i < length; i++ ) + { + *crc = ( *crc << 8 ) ^ crctable[( *crc >> 8 ) ^ data[i]]; + } //end for +} //end of the function CRC_ProcessString diff --git a/src/botlib/l_crc.h b/src/botlib/l_crc.h new file mode 100644 index 0000000..935cbc8 --- /dev/null +++ b/src/botlib/l_crc.h @@ -0,0 +1,44 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_crc.h +// Function: for CRC checks +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-31 +// Tab Size: 3 +//=========================================================================== + +typedef unsigned short crc_t; + +void CRC_Init( unsigned short *crcvalue ); +void CRC_ProcessByte( unsigned short *crcvalue, byte data ); +unsigned short CRC_Value( unsigned short crcvalue ); +unsigned short CRC_ProcessString( unsigned char *data, int length ); +void CRC_ContinueProcessString( unsigned short *crc, char *data, int length ); diff --git a/src/botlib/l_libvar.c b/src/botlib/l_libvar.c new file mode 100644 index 0000000..c71487e --- /dev/null +++ b/src/botlib/l_libvar.c @@ -0,0 +1,282 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_libvar.c + * + * desc: bot library variables + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" + +//list with library variables +libvar_t *libvarlist; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarStringValue( char *string ) { + int dotfound = 0; + float value = 0; + + while ( *string ) + { + if ( *string < '0' || *string > '9' ) { + if ( dotfound || *string != '.' ) { + return 0; + } //end if + else + { + dotfound = 10; + string++; + } //end if + } //end if + if ( dotfound ) { + value = value + (float) ( *string - '0' ) / (float) dotfound; + dotfound *= 10; + } //end if + else + { + value = value * 10.0 + (float) ( *string - '0' ); + } //end else + string++; + } //end while + return value; +} //end of the function LibVarStringValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVarAlloc( char *var_name ) { + libvar_t *v; + + v = (libvar_t *) GetMemory( sizeof( libvar_t ) + strlen( var_name ) + 1 ); + memset( v, 0, sizeof( libvar_t ) ); + v->name = (char *) v + sizeof( libvar_t ); + strcpy( v->name, var_name ); + //add the variable in the list + v->next = libvarlist; + libvarlist = v; + return v; +} //end of the function LibVarAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarDeAlloc( libvar_t *v ) { + if ( v->string ) { + FreeMemory( v->string ); + } + FreeMemory( v ); +} //end of the function LibVarDeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarDeAllocAll( void ) { + libvar_t *v; + + for ( v = libvarlist; v; v = libvarlist ) + { + libvarlist = libvarlist->next; + LibVarDeAlloc( v ); + } //end for + libvarlist = NULL; +} //end of the function LibVarDeAllocAll +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVarGet( char *var_name ) { + libvar_t *v; + + for ( v = libvarlist; v; v = v->next ) + { + if ( !Q_stricmp( v->name, var_name ) ) { + return v; + } //end if + } //end for + return NULL; +} //end of the function LibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *LibVarGetString( char *var_name ) { + libvar_t *v; + + v = LibVarGet( var_name ); + if ( v ) { + return v->string; + } //end if + else + { + return ""; + } //end else +} //end of the function LibVarGetString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarGetValue( char *var_name ) { + libvar_t *v; + + v = LibVarGet( var_name ); + if ( v ) { + return v->value; + } //end if + else + { + return 0; + } //end else +} //end of the function LibVarGetValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVar( char *var_name, char *value ) { + libvar_t *v; + v = LibVarGet( var_name ); + if ( v ) { + return v; + } + //create new variable + v = LibVarAlloc( var_name ); + //variable string + v->string = (char *) GetMemory( strlen( value ) + 1 ); + strcpy( v->string, value ); + //the value + v->value = LibVarStringValue( v->string ); + //variable is modified + v->modified = qtrue; + // + return v; +} //end of the function LibVar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *LibVarString( char *var_name, char *value ) { + libvar_t *v; + + v = LibVar( var_name, value ); + return v->string; +} //end of the function LibVarString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarValue( char *var_name, char *value ) { + libvar_t *v; + + v = LibVar( var_name, value ); + return v->value; +} //end of the function LibVarValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarSet( char *var_name, char *value ) { + libvar_t *v; + + v = LibVarGet( var_name ); + if ( v ) { + FreeMemory( v->string ); + } //end if + else + { + v = LibVarAlloc( var_name ); + } //end else + //variable string + v->string = (char *) GetMemory( strlen( value ) + 1 ); + strcpy( v->string, value ); + //the value + v->value = LibVarStringValue( v->string ); + //variable is modified + v->modified = qtrue; +} //end of the function LibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean LibVarChanged( char *var_name ) { + libvar_t *v; + + v = LibVarGet( var_name ); + if ( v ) { + return v->modified; + } //end if + else + { + return qfalse; + } //end else +} //end of the function LibVarChanged +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarSetNotModified( char *var_name ) { + libvar_t *v; + + v = LibVarGet( var_name ); + if ( v ) { + v->modified = qfalse; + } //end if +} //end of the function LibVarSetNotModified diff --git a/src/botlib/l_libvar.h b/src/botlib/l_libvar.h new file mode 100644 index 0000000..d6a26cb --- /dev/null +++ b/src/botlib/l_libvar.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_libvar.h + * + * desc: botlib vars + * + * + *****************************************************************************/ + +//library variable +typedef struct libvar_s +{ + char *name; + char *string; + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct libvar_s *next; +} libvar_t; + +//removes all library variables +void LibVarDeAllocAll( void ); +//gets the library variable with the given name +libvar_t *LibVarGet( char *var_name ); +//gets the string of the library variable with the given name +char *LibVarGetString( char *var_name ); +//gets the value of the library variable with the given name +float LibVarGetValue( char *var_name ); +//creates the library variable if not existing already and returns it +libvar_t *LibVar( char *var_name, char *value ); +//creates the library variable if not existing already and returns the value +float LibVarValue( char *var_name, char *value ); +//creates the library variable if not existing already and returns the value string +char *LibVarString( char *var_name, char *value ); +//sets the library variable +void LibVarSet( char *var_name, char *value ); +//returns true if the library variable has been modified +qboolean LibVarChanged( char *var_name ); +//sets the library variable to unmodified +void LibVarSetNotModified( char *var_name ); + diff --git a/src/botlib/l_log.c b/src/botlib/l_log.c new file mode 100644 index 0000000..81fddd1 --- /dev/null +++ b/src/botlib/l_log.c @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_log.c + * + * desc: log file + * + * + *****************************************************************************/ + +#include +#include +#include + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" //for botimport.Print +#include "l_libvar.h" + +#define MAX_LOGFILENAMESIZE 1024 + +typedef struct logfile_s +{ + char filename[MAX_LOGFILENAMESIZE]; + FILE *fp; + int numwrites; +} logfile_t; + +static logfile_t logfile; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_AlwaysOpen( char *filename ) { + if ( !filename || !strlen( filename ) ) { + botimport.Print( PRT_MESSAGE, "openlog \n" ); + return; + } //end if + if ( logfile.fp ) { + botimport.Print( PRT_ERROR, "log file %s is already opened\n", logfile.filename ); + return; + } //end if + logfile.fp = fopen( filename, "wb" ); + if ( !logfile.fp ) { + botimport.Print( PRT_ERROR, "can't open the log file %s\n", filename ); + return; + } //end if + strncpy( logfile.filename, filename, MAX_LOGFILENAMESIZE ); + botimport.Print( PRT_MESSAGE, "Opened log %s\n", logfile.filename ); +} //end of the function Log_Create +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Open( char *filename ) { + if ( !LibVarValue( "log", "0" ) ) { + return; + } + Log_AlwaysOpen( filename ); + +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Close( void ) { + if ( !logfile.fp ) { + return; + } + if ( fclose( logfile.fp ) ) { + botimport.Print( PRT_ERROR, "can't close log file %s\n", logfile.filename ); + return; + } //end if + logfile.fp = NULL; + botimport.Print( PRT_MESSAGE, "Closed log %s\n", logfile.filename ); +} //end of the function Log_Close +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Shutdown( void ) { + if ( logfile.fp ) { + Log_Close(); + } +} //end of the function Log_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL Log_Write( char *fmt, ... ) { + va_list ap; + + if ( !logfile.fp ) { + return; + } + va_start( ap, fmt ); + vfprintf( logfile.fp, fmt, ap ); + va_end( ap ); + //fprintf(logfile.fp, "\r\n"); + fflush( logfile.fp ); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL Log_WriteTimeStamped( char *fmt, ... ) { + va_list ap; + + if ( !logfile.fp ) { + return; + } + fprintf( logfile.fp, "%d %02d:%02d:%02d:%02d ", + logfile.numwrites, + (int) ( botlibglobals.time / 60 / 60 ), + (int) ( botlibglobals.time / 60 ), + (int) ( botlibglobals.time ), + (int) ( (int) ( botlibglobals.time * 100 ) ) - + ( (int) botlibglobals.time ) * 100 ); + va_start( ap, fmt ); + vfprintf( logfile.fp, fmt, ap ); + va_end( ap ); + fprintf( logfile.fp, "\r\n" ); + logfile.numwrites++; + fflush( logfile.fp ); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +FILE *Log_FilePointer( void ) { + return logfile.fp; +} //end of the function Log_FilePointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Flush( void ) { + if ( logfile.fp ) { + fflush( logfile.fp ); + } +} //end of the function Log_Flush + diff --git a/src/botlib/l_log.h b/src/botlib/l_log.h new file mode 100644 index 0000000..f9c5915 --- /dev/null +++ b/src/botlib/l_log.h @@ -0,0 +1,54 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_log.h + * + * desc: log file + * + * + *****************************************************************************/ + +//open a log file +void Log_Open( char *filename ); +// +void Log_AlwaysOpen( char *filename ); +//close the current log file +void Log_Close( void ); +//close log file if present +void Log_Shutdown( void ); +//write to the current opened log file +void QDECL Log_Write( char *fmt, ... ); +//write to the current opened log file with a time stamp +void QDECL Log_WriteTimeStamped( char *fmt, ... ); +//returns a pointer to the log file +FILE *Log_FilePointer( void ); +//flush log file +void Log_Flush( void ); + diff --git a/src/botlib/l_memory.c b/src/botlib/l_memory.c new file mode 100644 index 0000000..d0d4139 --- /dev/null +++ b/src/botlib/l_memory.c @@ -0,0 +1,446 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_memory.c + * + * desc: memory allocation + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "l_log.h" +#include "be_interface.h" + +#ifdef _DEBUG + #define MEMDEBUG + #define MEMORYMANEGER +#endif + +#define MEM_ID 0x12345678l +#define HUNK_ID 0x87654321l + +int allocatedmemory; +int totalmemorysize; +int numblocks; + +#ifdef MEMORYMANEGER + +typedef struct memoryblock_s +{ + unsigned long int id; + void *ptr; + int size; +#ifdef MEMDEBUG + char *label; + char *file; + int line; +#endif //MEMDEBUG + struct memoryblock_s *prev, *next; +} memoryblock_t; + +memoryblock_t *memory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LinkMemoryBlock( memoryblock_t *block ) { + block->prev = NULL; + block->next = memory; + if ( memory ) { + memory->prev = block; + } + memory = block; +} //end of the function LinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnlinkMemoryBlock( memoryblock_t *block ) { + if ( block->prev ) { + block->prev->next = block->next; + } else { memory = block->next;} + if ( block->next ) { + block->next->prev = block->prev; + } +} //end of the function UnlinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = botimport.GetMemory( size + sizeof( memoryblock_t ) ); + block = (memoryblock_t *) ptr; + block->id = MEM_ID; + block->ptr = (char *) ptr + sizeof( memoryblock_t ); + block->size = size + sizeof( memoryblock_t ); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock( block ); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof( memoryblock_t ); + numblocks++; + return block->ptr; +} //end of the function GetMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug( size, label, file, line ); +#else + ptr = GetMemory( size ); +#endif //MEMDEBUG + memset( ptr, 0, size ); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetHunkMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = botimport.HunkAlloc( size + sizeof( memoryblock_t ) ); + block = (memoryblock_t *) ptr; + block->id = HUNK_ID; + block->ptr = (char *) ptr + sizeof( memoryblock_t ); + block->size = size + sizeof( memoryblock_t ); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock( block ); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof( memoryblock_t ); + numblocks++; + return block->ptr; +} //end of the function GetHunkMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedHunkMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug( size, label, file, line ); +#else + ptr = GetHunkMemory( size ); +#endif //MEMDEBUG + memset( ptr, 0, size ); + return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +memoryblock_t *BlockFromPointer( void *ptr, char *str ) { + memoryblock_t *block; + + if ( !ptr ) { +#ifdef MEMDEBUG + //char *crash = (char *) NULL; + //crash[0] = 1; + botimport.Print( PRT_FATAL, "%s: NULL pointer\n", str ); +#endif MEMDEBUG + return NULL; + } //end if + block = ( memoryblock_t * )( (char *) ptr - sizeof( memoryblock_t ) ); + if ( block->id != MEM_ID && block->id != HUNK_ID ) { + botimport.Print( PRT_FATAL, "%s: invalid memory block\n", str ); + return NULL; + } //end if + if ( block->ptr != ptr ) { + botimport.Print( PRT_FATAL, "%s: memory block pointer invalid\n", str ); + return NULL; + } //end if + return block; +} //end of the function BlockFromPointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory( void *ptr ) { + memoryblock_t *block; + + block = BlockFromPointer( ptr, "FreeMemory" ); + if ( !block ) { + return; + } + UnlinkMemoryBlock( block ); + allocatedmemory -= block->size; + totalmemorysize -= block->size + sizeof( memoryblock_t ); + numblocks--; + // + if ( block->id == MEM_ID ) { + botimport.FreeMemory( block ); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemoryByteSize( void *ptr ) { + memoryblock_t *block; + + block = BlockFromPointer( ptr, "MemoryByteSize" ); + if ( !block ) { + return 0; + } + return block->size; +} //end of the function MemoryByteSize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize( void ) { + botimport.Print( PRT_MESSAGE, "total allocated memory: %d KB\n", allocatedmemory >> 10 ); + botimport.Print( PRT_MESSAGE, "total botlib memory: %d KB\n", totalmemorysize >> 10 ); + botimport.Print( PRT_MESSAGE, "total memory blocks: %d\n", numblocks ); +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels( void ) { + memoryblock_t *block; + int i; + + PrintUsedMemorySize(); + i = 0; + Log_Write( "\r\n" ); + for ( block = memory; block; block = block->next ) + { +#ifdef MEMDEBUG + if ( block->id == HUNK_ID ) { + Log_Write( "%6d, hunk %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label ); + } //end if + else + { + Log_Write( "%6d, %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label ); + } //end else +#endif //MEMDEBUG + i++; + } //end for +} //end of the function PrintMemoryLabels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DumpMemory( void ) { + memoryblock_t *block; + + for ( block = memory; block; block = memory ) + { + FreeMemory( block->ptr ); + } //end for + totalmemorysize = 0; + allocatedmemory = 0; +} //end of the function DumpMemory + +#else + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = botimport.GetMemory( size + sizeof( unsigned long int ) ); + if ( !ptr ) { + return NULL; + } + memid = (unsigned long int *) ptr; + *memid = MEM_ID; + return (unsigned long int *) ( (char *) ptr + sizeof( unsigned long int ) ); +} //end of the function GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug( size, label, file, line ); +#else +ptr = GetMemory( size ); +#endif //MEMDEBUG +memset( ptr, 0, size ); +return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetHunkMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = botimport.HunkAlloc( size + sizeof( unsigned long int ) ); + if ( !ptr ) { + return NULL; + } + memid = (unsigned long int *) ptr; + *memid = HUNK_ID; + return (unsigned long int *) ( (char *) ptr + sizeof( unsigned long int ) ); +} //end of the function GetHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedHunkMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug( size, label, file, line ); +#else +ptr = GetHunkMemory( size ); +#endif //MEMDEBUG +memset( ptr, 0, size ); +return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory( void *ptr ) { + unsigned long int *memid; + + memid = (unsigned long int *) ( (char *) ptr - sizeof( unsigned long int ) ); + + if ( *memid == MEM_ID ) { + botimport.FreeMemory( memid ); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize( void ) { +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels( void ) { +} //end of the function PrintMemoryLabels + +#endif diff --git a/src/botlib/l_memory.h b/src/botlib/l_memory.h new file mode 100644 index 0000000..4675786 --- /dev/null +++ b/src/botlib/l_memory.h @@ -0,0 +1,82 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_memory.h + * + * desc: memory management + * + * + *****************************************************************************/ + +#ifdef _DEBUG + #define MEMDEBUG +#endif + +#ifdef MEMDEBUG +#define GetMemory( size ) GetMemoryDebug( size, # size, __FILE__, __LINE__ ); +#define GetClearedMemory( size ) GetClearedMemoryDebug( size, # size, __FILE__, __LINE__ ); +//allocate a memory block of the given size +void *GetMemoryDebug( unsigned long size, char *label, char *file, int line ); +//allocate a memory block of the given size and clear it +void *GetClearedMemoryDebug( unsigned long size, char *label, char *file, int line ); +// +#define GetHunkMemory( size ) GetHunkMemoryDebug( size, # size, __FILE__, __LINE__ ); +#define GetClearedHunkMemory( size ) GetClearedHunkMemoryDebug( size, # size, __FILE__, __LINE__ ); +//allocate a memory block of the given size +void *GetHunkMemoryDebug( unsigned long size, char *label, char *file, int line ); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemoryDebug( unsigned long size, char *label, char *file, int line ); +#else +//allocate a memory block of the given size +void *GetMemory( unsigned long size ); +//allocate a memory block of the given size and clear it +void *GetClearedMemory( unsigned long size ); +// +#ifdef BSPC +#define GetHunkMemory GetMemory +#define GetClearedHunkMemory GetClearedMemory +#else +//allocate a memory block of the given size +void *GetHunkMemory( unsigned long size ); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemory( unsigned long size ); +#endif +#endif + +//free the given memory block +void FreeMemory( void *ptr ); +//prints the total used memory size +void PrintUsedMemorySize( void ); +//print all memory blocks with label +void PrintMemoryLabels( void ); +//returns the size of the memory block in bytes +int MemoryByteSize( void *ptr ); +//free all allocated memory +void DumpMemory( void ); diff --git a/src/botlib/l_precomp.c b/src/botlib/l_precomp.c new file mode 100644 index 0000000..8c15197 --- /dev/null +++ b/src/botlib/l_precomp.c @@ -0,0 +1,3233 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_precomp.c + * + * desc: pre compiler + * + * + *****************************************************************************/ + +//Notes: fix: PC_StringizeTokens + +//#define SCREWUP +//#define BOTLIB +//#define QUAKE +//#define QUAKEC +//#define MEQCC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" + +typedef enum {qfalse, qtrue} qboolean; +#endif //SCREWUP + +#ifdef BOTLIB +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" +#endif //BOTLIB + +#ifdef MEQCC +#include "qcc.h" +#include "time.h" //time & ctime +#include "math.h" //fabs +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" + +#define qtrue true +#define qfalse false +#define Q_stricmp stricmp + +#define MAX_TOKENLENGTH 1024 + +typedef struct pc_token_s +{ + int type; + int subtype; + int intvalue; + float floatvalue; + char string[MAX_TOKENLENGTH]; +} pc_token_t; +#endif //BSPC + +#if defined( QUAKE ) && !defined( BSPC ) +#include "l_utils.h" +#endif //QUAKE + +//#define DEBUG_EVAL + +#define MAX_DEFINEPARMS 128 + +#define DEFINEHASHING 1 + +//directive name with parse function +typedef struct directive_s +{ + char *name; + int ( *func )( source_t *source ); +} directive_t; + +#define DEFINEHASHSIZE 1024 + +#define TOKEN_HEAP_SIZE 4096 + +int numtokens; +/* +int tokenheapinitialized; //true when the token heap is initialized +token_t token_heap[TOKEN_HEAP_SIZE]; //heap with tokens +token_t *freetokens; //free tokens from the heap +*/ + +//list with global defines added to every source loaded +define_t *globaldefines; + +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void QDECL SourceError( source_t *source, char *str, ... ) { + char text[1024]; + va_list ap; + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); +#ifdef BOTLIB + botimport.Print( PRT_ERROR, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //BOTLIB +#ifdef MEQCC + printf( "error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //MEQCC +#ifdef BSPC + Log_Print( "error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //BSPC +} //end of the function SourceError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL SourceWarning( source_t *source, char *str, ... ) { + char text[1024]; + va_list ap; + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); +#ifdef BOTLIB + botimport.Print( PRT_WARNING, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //BOTLIB +#ifdef MEQCC + printf( "warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //MEQCC +#ifdef BSPC + Log_Print( "warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //BSPC +} //end of the function ScriptWarning +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushIndent( source_t *source, int type, int skip ) { + indent_t *indent; + + indent = (indent_t *) GetMemory( sizeof( indent_t ) ); + indent->type = type; + indent->script = source->scriptstack; + indent->skip = ( skip != 0 ); + source->skip += indent->skip; + indent->next = source->indentstack; + source->indentstack = indent; +} //end of the function PC_PushIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PopIndent( source_t *source, int *type, int *skip ) { + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = source->indentstack; + if ( !indent ) { + return; + } + + //must be an indent from the current script + if ( source->indentstack->script != source->scriptstack ) { + return; + } + + *type = indent->type; + *skip = indent->skip; + source->indentstack = source->indentstack->next; + source->skip -= indent->skip; + FreeMemory( indent ); +} //end of the function PC_PopIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushScript( source_t *source, script_t *script ) { + script_t *s; + + for ( s = source->scriptstack; s; s = s->next ) + { + if ( !Q_stricmp( s->filename, script->filename ) ) { + SourceError( source, "%s recursively included", script->filename ); + return; + } //end if + } //end for + //push the script on the script stack + script->next = source->scriptstack; + source->scriptstack = script; +} //end of the function PC_PushScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_InitTokenHeap( void ) { + /* + int i; + + if (tokenheapinitialized) return; + freetokens = NULL; + for (i = 0; i < TOKEN_HEAP_SIZE; i++) + { + token_heap[i].next = freetokens; + freetokens = &token_heap[i]; + } //end for + tokenheapinitialized = qtrue; + */ +} //end of the function PC_InitTokenHeap +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +token_t *PC_CopyToken( token_t *token ) { + token_t *t; + +// t = (token_t *) malloc(sizeof(token_t)); + t = (token_t *) GetMemory( sizeof( token_t ) ); +// t = freetokens; + if ( !t ) { +#ifdef BSPC + Error( "out of token space\n" ); +#else + Com_Error( ERR_FATAL, "out of token space\n" ); +#endif + return NULL; + } //end if +// freetokens = freetokens->next; + memcpy( t, token, sizeof( token_t ) ); + t->next = NULL; + numtokens++; + return t; +} //end of the function PC_CopyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeToken( token_t *token ) { + //free(token); + FreeMemory( token ); +// token->next = freetokens; +// freetokens = token; + numtokens--; +} //end of the function PC_FreeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadSourceToken( source_t *source, token_t *token ) { + token_t *t; + script_t *script; + int type, skip; + + //if there's no token already available + while ( !source->tokens ) + { + //if there's a token to read from the script + if ( PS_ReadToken( source->scriptstack, token ) ) { + return qtrue; + } + //if at the end of the script + if ( EndOfScript( source->scriptstack ) ) { + //remove all indents of the script + while ( source->indentstack && + source->indentstack->script == source->scriptstack ) + { + SourceWarning( source, "missing #endif" ); + PC_PopIndent( source, &type, &skip ); + } //end if + } //end if + //if this was the initial script + if ( !source->scriptstack->next ) { + return qfalse; + } + //remove the script and return to the last one + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript( script ); + } //end while + //copy the already available token + memcpy( token, source->tokens, sizeof( token_t ) ); + //free the read token + t = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken( t ); + return qtrue; +} //end of the function PC_ReadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_UnreadSourceToken( source_t *source, token_t *token ) { + token_t *t; + + t = PC_CopyToken( token ); + t->next = source->tokens; + source->tokens = t; + return qtrue; +} //end of the function PC_UnreadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadDefineParms( source_t *source, define_t *define, token_t **parms, int maxparms ) { + token_t token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "define %s missing parms", define->name ); + return qfalse; + } //end if + // + if ( define->numparms > maxparms ) { + SourceError( source, "define with more than %d parameters", maxparms ); + return qfalse; + } //end if + // + for ( i = 0; i < define->numparms; i++ ) parms[i] = NULL; + //if no leading "(" + if ( strcmp( token.string, "(" ) ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "define %s missing parms", define->name ); + return qfalse; + } //end if + //read the define parameters + for ( done = 0, numparms = 0, indent = 0; !done; ) + { + if ( numparms >= maxparms ) { + SourceError( source, "define %s with too many parms", define->name ); + return qfalse; + } //end if + if ( numparms >= define->numparms ) { + SourceWarning( source, "define %s has too many parms", define->name ); + return qfalse; + } //end if + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while ( !done ) + { + // + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "define %s incomplete", define->name ); + return qfalse; + } //end if + // + if ( !strcmp( token.string, "," ) ) { + if ( indent <= 0 ) { + if ( lastcomma ) { + SourceWarning( source, "too many comma's" ); + } + lastcomma = 1; + break; + } //end if + } //end if + lastcomma = 0; + // + if ( !strcmp( token.string, "(" ) ) { + indent++; + continue; + } //end if + else if ( !strcmp( token.string, ")" ) ) { + if ( --indent <= 0 ) { + if ( !parms[define->numparms - 1] ) { + SourceWarning( source, "too few define parms" ); + } //end if + done = 1; + break; + } //end if + } //end if + // + if ( numparms < define->numparms ) { + // + t = PC_CopyToken( &token ); + t->next = NULL; + if ( last ) { + last->next = t; + } else { parms[numparms] = t;} + last = t; + } //end if + } //end while + numparms++; + } //end for + return qtrue; +} //end of the function PC_ReadDefineParms +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_StringizeTokens( token_t *tokens, token_t *token ) { + token_t *t; + + token->type = TT_STRING; + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->string[0] = '\0'; + strcat( token->string, "\"" ); + for ( t = tokens; t; t = t->next ) + { + strncat( token->string, t->string, MAX_TOKEN - strlen( token->string ) ); + } //end for + strncat( token->string, "\"", MAX_TOKEN - strlen( token->string ) ); + return qtrue; +} //end of the function PC_StringizeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_MergeTokens( token_t *t1, token_t *t2 ) { + //merging of a name with a name or number + if ( t1->type == TT_NAME && ( t2->type == TT_NAME || t2->type == TT_NUMBER ) ) { + strcat( t1->string, t2->string ); + return qtrue; + } //end if + //merging of two strings + if ( t1->type == TT_STRING && t2->type == TT_STRING ) { + //remove trailing double quote + t1->string[strlen( t1->string ) - 1] = '\0'; + //concat without leading double quote + strcat( t1->string, &t2->string[1] ); + return qtrue; + } //end if + //FIXME: merging of two number of the same sub type + return qfalse; +} //end of the function PC_MergeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +/* +void PC_PrintDefine(define_t *define) +{ + printf("define->name = %s\n", define->name); + printf("define->flags = %d\n", define->flags); + printf("define->builtin = %d\n", define->builtin); + printf("define->numparms = %d\n", define->numparms); +// token_t *parms; //define parameters +// token_t *tokens; //macro tokens (possibly containing parm tokens) +// struct define_s *next; //next defined macro in a list +} //end of the function PC_PrintDefine*/ +#if DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PrintDefineHashTable( define_t **definehash ) { + int i; + define_t *d; + + for ( i = 0; i < DEFINEHASHSIZE; i++ ) + { + Log_Write( "%4d:", i ); + for ( d = definehash[i]; d; d = d->hashnext ) + { + Log_Write( " %s", d->name ); + } //end for + Log_Write( "\n" ); + } //end for +} //end of the function PC_PrintDefineHashTable +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; + +int PC_NameHash( char *name ) { + int register hash, i; + + hash = 0; + for ( i = 0; name[i] != '\0'; i++ ) + { + hash += name[i] * ( 119 + i ); + //hash += (name[i] << 7) + i; + //hash += (name[i] << (i&15)); + } //end while + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ) & ( DEFINEHASHSIZE - 1 ); + return hash; +} //end of the function PC_NameHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddDefineToHash( define_t *define, define_t **definehash ) { + int hash; + + hash = PC_NameHash( define->name ); + define->hashnext = definehash[hash]; + definehash[hash] = define; +} //end of the function PC_AddDefineToHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindHashedDefine( define_t **definehash, char *name ) { + define_t *d; + int hash; + + hash = PC_NameHash( name ); + for ( d = definehash[hash]; d; d = d->hashnext ) + { + if ( !strcmp( d->name, name ) ) { + return d; + } + } //end for + return NULL; +} //end of the function PC_FindHashedDefine +#endif //DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindDefine( define_t *defines, char *name ) { + define_t *d; + + for ( d = defines; d; d = d->next ) + { + if ( !strcmp( d->name, name ) ) { + return d; + } + } //end for + return NULL; +} //end of the function PC_FindDefine +//============================================================================ +// +// Parameter: - +// Returns: number of the parm +// if no parm found with the given name -1 is returned +// Changes Globals: - +//============================================================================ +int PC_FindDefineParm( define_t *define, char *name ) { + token_t *p; + int i; + + i = 0; + for ( p = define->parms; p; p = p->next ) + { + if ( !strcmp( p->string, name ) ) { + return i; + } + i++; + } //end for + return -1; +} //end of the function PC_FindDefineParm +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeDefine( define_t *define ) { + token_t *t, *next; + + //free the define parameters + for ( t = define->parms; t; t = next ) + { + next = t->next; + PC_FreeToken( t ); + } //end for + //free the define tokens + for ( t = define->tokens; t; t = next ) + { + next = t->next; + PC_FreeToken( t ); + } //end for + //free the define + FreeMemory( define ); +} //end of the function PC_FreeDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddBuiltinDefines( source_t *source ) { + int i; + define_t *define; + struct builtin + { + char *string; + int builtin; + } builtin[] = { + { "__LINE__", BUILTIN_LINE }, + { "__FILE__", BUILTIN_FILE }, + { "__DATE__", BUILTIN_DATE }, + { "__TIME__", BUILTIN_TIME }, +// { "__STDC__", BUILTIN_STDC }, + { NULL, 0 } + }; + + for ( i = 0; builtin[i].string; i++ ) + { + define = (define_t *) GetMemory( sizeof( define_t ) + strlen( builtin[i].string ) + 1 ); + memset( define, 0, sizeof( define_t ) ); + define->name = (char *) define + sizeof( define_t ); + strcpy( define->name, builtin[i].string ); + define->flags |= DEFINE_FIXED; + define->builtin = builtin[i].builtin; + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash( define, source->definehash ); +#else + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + } //end for +} //end of the function PC_AddBuiltinDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandBuiltinDefine( source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken ) { + token_t *token; + unsigned long t; // time_t t; //to prevent LCC warning + char *curtime; + + token = PC_CopyToken( deftoken ); + switch ( define->builtin ) + { + case BUILTIN_LINE: + { + sprintf( token->string, "%d", deftoken->line ); +#ifdef NUMBERVALUE + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; +#endif //NUMBERVALUE + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER; + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_FILE: + { + strcpy( token->string, source->scriptstack->filename ); + token->type = TT_NAME; + token->subtype = strlen( token->string ); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_DATE: + { + t = time( NULL ); + curtime = ctime( &t ); + strcpy( token->string, "\"" ); + strncat( token->string, curtime + 4, 7 ); + strncat( token->string + 7, curtime + 20, 4 ); + strcat( token->string, "\"" ); + free( curtime ); + token->type = TT_NAME; + token->subtype = strlen( token->string ); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_TIME: + { + t = time( NULL ); + curtime = ctime( &t ); + strcpy( token->string, "\"" ); + strncat( token->string, curtime + 11, 8 ); + strcat( token->string, "\"" ); + free( curtime ); + token->type = TT_NAME; + token->subtype = strlen( token->string ); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_STDC: + default: + { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } //end case + } //end switch + return qtrue; +} //end of the function PC_ExpandBuiltinDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefine( source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken ) { + token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + token_t *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + //if it is a builtin define + if ( define->builtin ) { + return PC_ExpandBuiltinDefine( source, deftoken, define, firsttoken, lasttoken ); + } //end if + //if the define has parameters + if ( define->numparms ) { + if ( !PC_ReadDefineParms( source, define, parms, MAX_DEFINEPARMS ) ) { + return qfalse; + } +#ifdef DEBUG_EVAL + for ( i = 0; i < define->numparms; i++ ) + { + Log_Write( "define parms %d:", i ); + for ( pt = parms[i]; pt; pt = pt->next ) + { + Log_Write( "%s", pt->string ); + } //end for + } //end for +#endif //DEBUG_EVAL + } //end if + //empty list at first + first = NULL; + last = NULL; + //create a list with tokens of the expanded define + for ( dt = define->tokens; dt; dt = dt->next ) + { + parmnum = -1; + //if the token is a name, it could be a define parameter + if ( dt->type == TT_NAME ) { + parmnum = PC_FindDefineParm( define, dt->string ); + } //end if + //if it is a define parameter + if ( parmnum >= 0 ) { + for ( pt = parms[parmnum]; pt; pt = pt->next ) + { + t = PC_CopyToken( pt ); + //add the token to the list + t->next = NULL; + if ( last ) { + last->next = t; + } else { first = t;} + last = t; + } //end for + } //end if + else + { + //if stringizing operator + if ( dt->string[0] == '#' && dt->string[1] == '\0' ) { + //the stringizing operator must be followed by a define parameter + if ( dt->next ) { + parmnum = PC_FindDefineParm( define, dt->next->string ); + } else { parmnum = -1;} + // + if ( parmnum >= 0 ) { + //step over the stringizing operator + dt = dt->next; + //stringize the define parameter tokens + if ( !PC_StringizeTokens( parms[parmnum], &token ) ) { + SourceError( source, "can't stringize tokens" ); + return qfalse; + } //end if + t = PC_CopyToken( &token ); + } //end if + else + { + SourceWarning( source, "stringizing operator without define parameter" ); + continue; + } //end if + } //end if + else + { + t = PC_CopyToken( dt ); + } //end else + //add the token to the list + t->next = NULL; + if ( last ) { + last->next = t; + } else { first = t;} + last = t; + } //end else + } //end for + //check for the merging operator + for ( t = first; t; ) + { + if ( t->next ) { + //if the merging operator + if ( t->next->string[0] == '#' && t->next->string[1] == '#' ) { + t1 = t; + t2 = t->next->next; + if ( t2 ) { + if ( !PC_MergeTokens( t1, t2 ) ) { + SourceError( source, "can't merge %s with %s", t1->string, t2->string ); + return qfalse; + } //end if + PC_FreeToken( t1->next ); + t1->next = t2->next; + if ( t2 == last ) { + last = t1; + } + PC_FreeToken( t2 ); + continue; + } //end if + } //end if + } //end if + t = t->next; + } //end for + //store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + //free all the parameter tokens + for ( i = 0; i < define->numparms; i++ ) + { + for ( pt = parms[i]; pt; pt = nextpt ) + { + nextpt = pt->next; + PC_FreeToken( pt ); + } //end for + } //end for + // + return qtrue; +} //end of the function PC_ExpandDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefineIntoSource( source_t *source, token_t *deftoken, define_t *define ) { + token_t *firsttoken, *lasttoken; + + if ( !PC_ExpandDefine( source, deftoken, define, &firsttoken, &lasttoken ) ) { + return qfalse; + } + + if ( firsttoken && lasttoken ) { + lasttoken->next = source->tokens; + source->tokens = firsttoken; + return qtrue; + } //end if + return qfalse; +} //end of the function PC_ExpandDefineIntoSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ConvertPath( char *path ) { + char *ptr; + + //remove double path seperators + for ( ptr = path; *ptr; ) + { + if ( ( *ptr == '\\' || *ptr == '/' ) && + ( *( ptr + 1 ) == '\\' || *( ptr + 1 ) == '/' ) ) { + strcpy( ptr, ptr + 1 ); + } //end if + else + { + ptr++; + } //end else + } //end while + //set OS dependent path seperators + for ( ptr = path; *ptr; ) + { + if ( *ptr == '/' || *ptr == '\\' ) { + *ptr = PATHSEPERATOR_CHAR; + } + ptr++; + } //end while +} //end of the function PC_ConvertPath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_include( source_t *source ) { + script_t *script; + token_t token; + char path[_MAX_PATH]; +#ifdef QUAKE + foundfile_t file; +#endif //QUAKE + + if ( source->skip > 0 ) { + return qtrue; + } + // + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "#include without file name" ); + return qfalse; + } //end if + if ( token.linescrossed > 0 ) { + SourceError( source, "#include without file name" ); + return qfalse; + } //end if + if ( token.type == TT_STRING ) { + StripDoubleQuotes( token.string ); + PC_ConvertPath( token.string ); + script = LoadScriptFile( token.string ); + if ( !script ) { + strcpy( path, source->includepath ); + strcat( path, token.string ); + script = LoadScriptFile( path ); + } //end if + } //end if + else if ( token.type == TT_PUNCTUATION && *token.string == '<' ) { + strcpy( path, source->includepath ); + while ( PC_ReadSourceToken( source, &token ) ) + { + if ( token.linescrossed > 0 ) { + PC_UnreadSourceToken( source, &token ); + break; + } //end if + if ( token.type == TT_PUNCTUATION && *token.string == '>' ) { + break; + } + strncat( path, token.string, _MAX_PATH ); + } //end while + if ( *token.string != '>' ) { + SourceWarning( source, "#include missing trailing >" ); + } //end if + if ( !strlen( path ) ) { + SourceError( source, "#include without file name between < >" ); + return qfalse; + } //end if + PC_ConvertPath( path ); + script = LoadScriptFile( path ); + } //end if + else + { + SourceError( source, "#include without file name" ); + return qfalse; + } //end else +#ifdef QUAKE + if ( !script ) { + memset( &file, 0, sizeof( foundfile_t ) ); + script = LoadScriptFile( path ); + if ( script ) { + strncpy( script->filename, path, _MAX_PATH ); + } + } //end if +#endif //QUAKE + if ( !script ) { +#ifdef SCREWUP + SourceWarning( source, "file %s not found", path ); + return qtrue; +#else + SourceError( source, "file %s not found", path ); + return qfalse; +#endif //SCREWUP + } //end if + PC_PushScript( source, script ); + return qtrue; +} //end of the function PC_Directive_include +//============================================================================ +// reads a token from the current line, continues reading on the next +// line only if a backslash '\' is encountered. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadLine( source_t *source, token_t *token ) { + int crossline; + + crossline = 0; + do + { + if ( !PC_ReadSourceToken( source, token ) ) { + return qfalse; + } + + if ( token->linescrossed > crossline ) { + PC_UnreadSourceToken( source, token ); + return qfalse; + } //end if + crossline = 1; + } while ( !strcmp( token->string, "\\" ) ); + return qtrue; +} //end of the function PC_ReadLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_WhiteSpaceBeforeToken( token_t *token ) { + return token->endwhitespace_p - token->whitespace_p > 0; +} //end of the function PC_WhiteSpaceBeforeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ClearTokenWhiteSpace( token_t *token ) { + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->linescrossed = 0; +} //end of the function PC_ClearTokenWhiteSpace +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_undef( source_t *source ) { + token_t token; + define_t *define, *lastdefine; + int hash; + + if ( source->skip > 0 ) { + return qtrue; + } + // + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "undef without name" ); + return qfalse; + } //end if + if ( token.type != TT_NAME ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "expected name, found %s", token.string ); + return qfalse; + } //end if +#if DEFINEHASHING + + hash = PC_NameHash( token.string ); + for ( lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext ) + { + if ( !strcmp( define->name, token.string ) ) { + if ( define->flags & DEFINE_FIXED ) { + SourceWarning( source, "can't undef %s", token.string ); + } //end if + else + { + if ( lastdefine ) { + lastdefine->hashnext = define->hashnext; + } else { source->definehash[hash] = define->hashnext;} + PC_FreeDefine( define ); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#else //DEFINEHASHING + for ( lastdefine = NULL, define = source->defines; define; define = define->next ) + { + if ( !strcmp( define->name, token.string ) ) { + if ( define->flags & DEFINE_FIXED ) { + SourceWarning( source, "can't undef %s", token.string ); + } //end if + else + { + if ( lastdefine ) { + lastdefine->next = define->next; + } else { source->defines = define->next;} + PC_FreeDefine( define ); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_Directive_undef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_define( source_t *source ) { + token_t token, *t, *last; + define_t *define; + + if ( source->skip > 0 ) { + return qtrue; + } + // + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "#define without name" ); + return qfalse; + } //end if + if ( token.type != TT_NAME ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "expected name after #define, found %s", token.string ); + return qfalse; + } //end if + //check if the define already exists +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token.string ); +#else + define = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + if ( define ) { + if ( define->flags & DEFINE_FIXED ) { + SourceError( source, "can't redefine %s", token.string ); + return qfalse; + } //end if + SourceWarning( source, "redefinition of %s", token.string ); + //unread the define name before executing the #undef directive + PC_UnreadSourceToken( source, &token ); + if ( !PC_Directive_undef( source ) ) { + return qfalse; + } + //if the define was not removed (define->flags & DEFINE_FIXED) +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token.string ); +#else + define = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + } //end if + //allocate define + define = (define_t *) GetMemory( sizeof( define_t ) + strlen( token.string ) + 1 ); + memset( define, 0, sizeof( define_t ) ); + define->name = (char *) define + sizeof( define_t ); + strcpy( define->name, token.string ); + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash( define, source->definehash ); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + //if nothing is defined, just return + if ( !PC_ReadLine( source, &token ) ) { + return qtrue; + } + //if it is a define with parameters + if ( !PC_WhiteSpaceBeforeToken( &token ) && !strcmp( token.string, "(" ) ) { + //read the define parameters + last = NULL; + if ( !PC_CheckTokenString( source, ")" ) ) { + while ( 1 ) + { + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "expected define parameter" ); + return qfalse; + } //end if + //if it isn't a name + if ( token.type != TT_NAME ) { + SourceError( source, "invalid define parameter" ); + return qfalse; + } //end if + // + if ( PC_FindDefineParm( define, token.string ) >= 0 ) { + SourceError( source, "two the same define parameters" ); + return qfalse; + } //end if + //add the define parm + t = PC_CopyToken( &token ); + PC_ClearTokenWhiteSpace( t ); + t->next = NULL; + if ( last ) { + last->next = t; + } else { define->parms = t;} + last = t; + define->numparms++; + //read next token + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "define parameters not terminated" ); + return qfalse; + } //end if + // + if ( !strcmp( token.string, ")" ) ) { + break; + } + //then it must be a comma + if ( strcmp( token.string, "," ) ) { + SourceError( source, "define not terminated" ); + return qfalse; + } //end if + } //end while + } //end if + if ( !PC_ReadLine( source, &token ) ) { + return qtrue; + } + } //end if + //read the defined stuff + last = NULL; + do + { + t = PC_CopyToken( &token ); + if ( t->type == TT_NAME && !strcmp( t->string, define->name ) ) { + SourceError( source, "recursive define (removed recursion)" ); + continue; + } //end if + PC_ClearTokenWhiteSpace( t ); + t->next = NULL; + if ( last ) { + last->next = t; + } else { define->tokens = t;} + last = t; + } while ( PC_ReadLine( source, &token ) ); + // + if ( last ) { + //check for merge operators at the beginning or end + if ( !strcmp( define->tokens->string, "##" ) || + !strcmp( last->string, "##" ) ) { + SourceError( source, "define with misplaced ##" ); + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function PC_Directive_define +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_DefineFromString( char *string ) { + script_t *script; + source_t src; + token_t *t; + int res, i; + define_t *def; + + PC_InitTokenHeap(); + + script = LoadScriptMemory( string, strlen( string ), "*extern" ); + //create a new source + memset( &src, 0, sizeof( source_t ) ); + strncpy( src.filename, "*extern", _MAX_PATH ); + src.scriptstack = script; +#if DEFINEHASHING + src.definehash = GetClearedMemory( DEFINEHASHSIZE * sizeof( define_t * ) ); +#endif //DEFINEHASHING + //create a define from the source + res = PC_Directive_define( &src ); + //free any tokens if left + for ( t = src.tokens; t; t = src.tokens ) + { + src.tokens = src.tokens->next; + PC_FreeToken( t ); + } //end for +#ifdef DEFINEHASHING + def = NULL; + for ( i = 0; i < DEFINEHASHSIZE; i++ ) + { + if ( src.definehash[i] ) { + def = src.definehash[i]; + break; + } //end if + } //end for +#else + def = src.defines; +#endif //DEFINEHASHING + // +#if DEFINEHASHING + FreeMemory( src.definehash ); +#endif //DEFINEHASHING + // + FreeScript( script ); + //if the define was created succesfully + if ( res > 0 ) { + return def; + } + //free the define if created + if ( src.defines ) { + PC_FreeDefine( def ); + } + // + return NULL; +} //end of the function PC_DefineFromString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddDefine( source_t *source, char *string ) { + define_t *define; + + define = PC_DefineFromString( string ); + if ( !define ) { + return qfalse; + } +#if DEFINEHASHING + PC_AddDefineToHash( define, source->definehash ); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_AddDefine +//============================================================================ +// add a globals define that will be added to all opened sources +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddGlobalDefine( char *string ) { + define_t *define; + + define = PC_DefineFromString( string ); + if ( !define ) { + return qfalse; + } + define->next = globaldefines; + globaldefines = define; + return qtrue; +} //end of the function PC_AddGlobalDefine +//============================================================================ +// remove the given global define +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_RemoveGlobalDefine( char *name ) { + define_t *define; + + define = PC_FindDefine( globaldefines, name ); + if ( define ) { + PC_FreeDefine( define ); + return qtrue; + } //end if + return qfalse; +} //end of the function PC_RemoveGlobalDefine +//============================================================================ +// remove all globals defines +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_RemoveAllGlobalDefines( void ) { + define_t *define; + + for ( define = globaldefines; define; define = globaldefines ) + { + globaldefines = globaldefines->next; + PC_FreeDefine( define ); + } //end for +} //end of the function PC_RemoveAllGlobalDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_CopyDefine( source_t *source, define_t *define ) { + define_t *newdefine; + token_t *token, *newtoken, *lasttoken; + + newdefine = (define_t *) GetMemory( sizeof( define_t ) + strlen( define->name ) + 1 ); + //copy the define name + newdefine->name = (char *) newdefine + sizeof( define_t ); + strcpy( newdefine->name, define->name ); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for ( lasttoken = NULL, token = define->tokens; token; token = token->next ) + { + newtoken = PC_CopyToken( token ); + newtoken->next = NULL; + if ( lasttoken ) { + lasttoken->next = newtoken; + } else { newdefine->tokens = newtoken;} + lasttoken = newtoken; + } //end for + //copy the define parameters + newdefine->parms = NULL; + for ( lasttoken = NULL, token = define->parms; token; token = token->next ) + { + newtoken = PC_CopyToken( token ); + newtoken->next = NULL; + if ( lasttoken ) { + lasttoken->next = newtoken; + } else { newdefine->parms = newtoken;} + lasttoken = newtoken; + } //end for + return newdefine; +} //end of the function PC_CopyDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddGlobalDefinesToSource( source_t *source ) { + define_t *define, *newdefine; + + for ( define = globaldefines; define; define = define->next ) + { + newdefine = PC_CopyDefine( source, define ); +#if DEFINEHASHING + PC_AddDefineToHash( newdefine, source->definehash ); +#else //DEFINEHASHING + newdefine->next = source->defines; + source->defines = newdefine; +#endif //DEFINEHASHING + } //end for +} //end of the function PC_AddGlobalDefinesToSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if_def( source_t *source, int type ) { + token_t token; + define_t *d; + int skip; + + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "#ifdef without name" ); + return qfalse; + } //end if + if ( token.type != TT_NAME ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "expected name after #ifdef, found %s", token.string ); + return qfalse; + } //end if +#if DEFINEHASHING + d = PC_FindHashedDefine( source->definehash, token.string ); +#else + d = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + skip = ( type == INDENT_IFDEF ) == ( d == NULL ); + PC_PushIndent( source, type, skip ); + return qtrue; +} //end of the function PC_Directiveif_def +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifdef( source_t *source ) { + return PC_Directive_if_def( source, INDENT_IFDEF ); +} //end of the function PC_Directive_ifdef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifndef( source_t *source ) { + return PC_Directive_if_def( source, INDENT_IFNDEF ); +} //end of the function PC_Directive_ifndef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_else( source_t *source ) { + int type, skip; + + PC_PopIndent( source, &type, &skip ); + if ( !type ) { + SourceError( source, "misplaced #else" ); + return qfalse; + } //end if + if ( type == INDENT_ELSE ) { + SourceError( source, "#else after #else" ); + return qfalse; + } //end if + PC_PushIndent( source, INDENT_ELSE, !skip ); + return qtrue; +} //end of the function PC_Directive_else +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_endif( source_t *source ) { + int type, skip; + + PC_PopIndent( source, &type, &skip ); + if ( !type ) { + SourceError( source, "misplaced #endif" ); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_Directive_endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +typedef struct operator_s +{ + int operator; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +int PC_OperatorPriority( int op ) { + switch ( op ) + { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } //end switch + return qfalse; +} //end of the function PC_OperatorPriority + +//#define AllocValue() GetClearedMemory(sizeof(value_t)); +//#define FreeValue(val) FreeMemory(val) +//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); +//#define FreeOperator(op) FreeMemory(op); + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 +#define AllocValue( val ) \ + if ( numvalues >= MAX_VALUES ) { \ + SourceError( source, "out of value space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + val = &value_heap[numvalues++];} +#define FreeValue( val ) +// +#define AllocOperator( op ) \ + if ( numoperators >= MAX_OPERATORS ) { \ + SourceError( source, "out of operator space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + op = &operator_heap[numoperators++];} +#define FreeOperator( op ) + +int PC_EvaluateTokens( source_t *source, token_t *tokens, signed long int *intvalue, + double *floatvalue, int integer ) { + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + token_t *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = qfalse; + int lastoperatortype = 0; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if ( intvalue ) { + *intvalue = 0; + } + if ( floatvalue ) { + *floatvalue = 0; + } + for ( t = tokens; t; t = t->next ) + { + switch ( t->type ) + { + case TT_NAME: + { + if ( lastwasvalue || negativevalue ) { + SourceError( source, "syntax error in #if/#elif" ); + error = 1; + break; + } //end if + if ( strcmp( t->string, "defined" ) ) { + SourceError( source, "undefined name %s in #if/#elif", t->string ); + error = 1; + break; + } //end if + t = t->next; + if ( !strcmp( t->string, "(" ) ) { + brace = qtrue; + t = t->next; + } //end if + if ( !t || t->type != TT_NAME ) { + SourceError( source, "defined without name in #if/#elif" ); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue( v ); +#if DEFINEHASHING + if ( PC_FindHashedDefine( source->definehash, t->string ) ) +#else + if ( PC_FindDefine( source->defines, t->string ) ) +#endif //DEFINEHASHING + { + v->intvalue = 1; + v->floatvalue = 1; + } //end if + else + { + v->intvalue = 0; + v->floatvalue = 0; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if ( lastvalue ) { + lastvalue->next = v; + } else { firstvalue = v;} + lastvalue = v; + if ( brace ) { + t = t->next; + if ( !t || strcmp( t->string, ")" ) ) { + SourceError( source, "defined without ) in #if/#elif" ); + error = 1; + break; + } //end if + } //end if + brace = qfalse; + // defined() creates a value + lastwasvalue = 1; + break; + } //end case + case TT_NUMBER: + { + if ( lastwasvalue ) { + SourceError( source, "syntax error in #if/#elif" ); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue( v ); + if ( negativevalue ) { + v->intvalue = -(signed int) t->intvalue; + v->floatvalue = -t->floatvalue; + } //end if + else + { + v->intvalue = t->intvalue; + v->floatvalue = t->floatvalue; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if ( lastvalue ) { + lastvalue->next = v; + } else { firstvalue = v;} + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } //end case + case TT_PUNCTUATION: + { + if ( negativevalue ) { + SourceError( source, "misplaced minus sign in #if/#elif" ); + error = 1; + break; + } //end if + if ( t->subtype == P_PARENTHESESOPEN ) { + parentheses++; + break; + } //end if + else if ( t->subtype == P_PARENTHESESCLOSE ) { + parentheses--; + if ( parentheses < 0 ) { + SourceError( source, "too many ) in #if/#elsif" ); + error = 1; + } //end if + break; + } //end else if + //check for invalid operators on floating point values + if ( !integer ) { + if ( t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR ) { + SourceError( source, "illigal operator %s on floating point operands\n", t->string ); + error = 1; + break; + } //end if + } //end if + switch ( t->subtype ) + { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if ( lastwasvalue ) { + SourceError( source, "! or ~ after value in #if/#elif" ); + error = 1; + break; + } //end if + break; + } //end case + case P_INC: + case P_DEC: + { + SourceError( source, "++ or -- used in #if/#elif" ); + break; + } //end case + case P_SUB: + { + if ( !lastwasvalue ) { + negativevalue = 1; + break; + } //end if + } //end case + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if ( !lastwasvalue ) { + SourceError( source, "operator %s after operator in #if/#elif", t->string ); + error = 1; + break; + } //end if + break; + } //end case + default: + { + SourceError( source, "invalid operator %s in #if/#elif", t->string ); + error = 1; + break; + } //end default + } //end switch + if ( !error && !negativevalue ) { + //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); + AllocOperator( o ); + o->operator = t->subtype; + o->priority = PC_OperatorPriority( t->subtype ); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if ( lastoperator ) { + lastoperator->next = o; + } else { firstoperator = o;} + lastoperator = o; + lastwasvalue = 0; + } //end if + break; + } //end case + default: + { + SourceError( source, "unknown %s in #if/#elif", t->string ); + error = 1; + break; + } //end default + } //end switch + if ( error ) { + break; + } + } //end for + if ( !error ) { + if ( !lastwasvalue ) { + SourceError( source, "trailing operator in #if/#elif" ); + error = 1; + } //end if + else if ( parentheses ) { + SourceError( source, "too many ( in #if/#elif" ); + error = 1; + } //end else if + } //end if + // + gotquestmarkvalue = qfalse; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while ( !error && firstoperator ) + { + v = firstvalue; + for ( o = firstoperator; o->next; o = o->next ) + { + //if the current operator is nested deeper in parentheses + //than the next operator + if ( o->parentheses > o->next->parentheses ) { + break; + } + //if the current and next operator are nested equally deep in parentheses + if ( o->parentheses == o->next->parentheses ) { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if ( o->priority >= o->next->priority ) { + break; + } + } //end if + //if the arity of the operator isn't equal to 1 + if ( o->operator != P_LOGIC_NOT + && o->operator != P_BIN_NOT ) { + v = v->next; + } + //if there's no value or no next value + if ( !v ) { + SourceError( source, "mising values in #if/#elif" ); + error = 1; + break; + } //end if + } //end for + if ( error ) { + break; + } + v1 = v; + v2 = v->next; +#ifdef DEBUG_EVAL + if ( integer ) { + Log_Write( "operator %s, value1 = %d", PunctuationFromNum( source->scriptstack, o->operator ), v1->intvalue ); + if ( v2 ) { + Log_Write( "value2 = %d", v2->intvalue ); + } + } //end if + else + { + Log_Write( "operator %s, value1 = %f", PunctuationFromNum( source->scriptstack, o->operator ), v1->floatvalue ); + if ( v2 ) { + Log_Write( "value2 = %f", v2->floatvalue ); + } + } //end else +#endif //DEBUG_EVAL + switch ( o->operator ) + { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if ( !v2->intvalue || !v2->floatvalue ) { + SourceError( source, "divide by zero in #if/#elif\n" ); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if ( !v2->intvalue ) { + SourceError( source, "divide by zero in #if/#elif\n" ); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if ( !gotquestmarkvalue ) { + SourceError( source, ": without ? in #if/#elif" ); + error = 1; + break; + } //end if + if ( integer ) { + if ( !questmarkintvalue ) { + v1->intvalue = v2->intvalue; + } + } //end if + else + { + if ( !questmarkfloatvalue ) { + v1->floatvalue = v2->floatvalue; + } + } //end else + gotquestmarkvalue = qfalse; + break; + } //end case + case P_QUESTIONMARK: + { + if ( gotquestmarkvalue ) { + SourceError( source, "? after ? in #if/#elif" ); + error = 1; + break; + } //end if + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = qtrue; + break; + } //end if + } //end switch +#ifdef DEBUG_EVAL + if ( integer ) { + Log_Write( "result value = %d", v1->intvalue ); + } else { Log_Write( "result value = %f", v1->floatvalue );} +#endif //DEBUG_EVAL + if ( error ) { + break; + } + lastoperatortype = o->operator; + //if not an operator with arity 1 + if ( o->operator !=P_LOGIC_NOT + && o->operator !=P_BIN_NOT ) { + //remove the second value if not question mark operator + if ( o->operator != P_QUESTIONMARK ) {v = v->next;} + // + if ( v->prev ) { + v->prev->next = v->next; + } else { firstvalue = v->next;} + if ( v->next ) { + v->next->prev = v->prev; + } else { lastvalue = v->prev;} + //FreeMemory(v); + FreeValue( v ); + } //end if + //remove the operator + if ( o->prev ) { + o->prev->next = o->next; + } else { firstoperator = o->next;} + if ( o->next ) { + o->next->prev = o->prev; + } else { lastoperator = o->prev;} + //FreeMemory(o); + FreeOperator( o ); + } //end while + if ( firstvalue ) { + if ( intvalue ) { + *intvalue = firstvalue->intvalue; + } + if ( floatvalue ) { + *floatvalue = firstvalue->floatvalue; + } + } //end if + for ( o = firstoperator; o; o = lastoperator ) + { + lastoperator = o->next; + //FreeMemory(o); + FreeOperator( o ); + } //end for + for ( v = firstvalue; v; v = lastvalue ) + { + lastvalue = v->next; + //FreeMemory(v); + FreeValue( v ); + } //end for + if ( !error ) { + return qtrue; + } + if ( intvalue ) { + *intvalue = 0; + } + if ( floatvalue ) { + *floatvalue = 0; + } + return qfalse; +} //end of the function PC_EvaluateTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Evaluate( source_t *source, signed long int *intvalue, + double *floatvalue, int integer ) { + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + int defined = qfalse; + + if ( intvalue ) { + *intvalue = 0; + } + if ( floatvalue ) { + *floatvalue = 0; + } + // + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "no value after #if/#elif" ); + return qfalse; + } //end if + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if ( token.type == TT_NAME ) { + if ( defined ) { + defined = qfalse; + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end if + else if ( !strcmp( token.string, "defined" ) ) { + defined = qtrue; + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token.string ); +#else + define = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + if ( !define ) { + SourceError( source, "can't evaluate %s, not defined", token.string ); + return qfalse; + } //end if + if ( !PC_ExpandDefineIntoSource( source, &token, define ) ) { + return qfalse; + } + } //end else + } //end if + //if the token is a number or a punctuation + else if ( token.type == TT_NUMBER || token.type == TT_PUNCTUATION ) { + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError( source, "can't evaluate %s", token.string ); + return qfalse; + } //end else + } while ( PC_ReadLine( source, &token ) ); + // + if ( !PC_EvaluateTokens( source, firsttoken, intvalue, floatvalue, integer ) ) { + return qfalse; + } + // +#ifdef DEBUG_EVAL + Log_Write( "eval:" ); +#endif //DEBUG_EVAL + for ( t = firsttoken; t; t = nexttoken ) + { +#ifdef DEBUG_EVAL + Log_Write( " %s", t->string ); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken( t ); + } //end for +#ifdef DEBUG_EVAL + if ( integer ) { + Log_Write( "eval result: %d", *intvalue ); + } else { Log_Write( "eval result: %f", *floatvalue );} +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_Evaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarEvaluate( source_t *source, signed long int *intvalue, + double *floatvalue, int integer ) { + int indent, defined = qfalse; + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + + if ( intvalue ) { + *intvalue = 0; + } + if ( floatvalue ) { + *floatvalue = 0; + } + // + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "no leading ( after $evalint/$evalfloat" ); + return qfalse; + } //end if + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "nothing to evaluate" ); + return qfalse; + } //end if + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if ( token.type == TT_NAME ) { + if ( defined ) { + defined = qfalse; + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end if + else if ( !strcmp( token.string, "defined" ) ) { + defined = qtrue; + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token.string ); +#else + define = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + if ( !define ) { + SourceError( source, "can't evaluate %s, not defined", token.string ); + return qfalse; + } //end if + if ( !PC_ExpandDefineIntoSource( source, &token, define ) ) { + return qfalse; + } + } //end else + } //end if + //if the token is a number or a punctuation + else if ( token.type == TT_NUMBER || token.type == TT_PUNCTUATION ) { + if ( *token.string == '(' ) { + indent++; + } else if ( *token.string == ')' ) { + indent--; + } + if ( indent <= 0 ) { + break; + } + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError( source, "can't evaluate %s", token.string ); + return qfalse; + } //end else + } while ( PC_ReadSourceToken( source, &token ) ); + // + if ( !PC_EvaluateTokens( source, firsttoken, intvalue, floatvalue, integer ) ) { + return qfalse; + } + // +#ifdef DEBUG_EVAL + Log_Write( "$eval:" ); +#endif //DEBUG_EVAL + for ( t = firsttoken; t; t = nexttoken ) + { +#ifdef DEBUG_EVAL + Log_Write( " %s", t->string ); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken( t ); + } //end for +#ifdef DEBUG_EVAL + if ( integer ) { + Log_Write( "$eval result: %d", *intvalue ); + } else { Log_Write( "$eval result: %f", *floatvalue );} +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_DollarEvaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_elif( source_t *source ) { + signed long int value; + int type, skip; + + PC_PopIndent( source, &type, &skip ); + if ( !type || type == INDENT_ELSE ) { + SourceError( source, "misplaced #elif" ); + return qfalse; + } //end if + if ( !PC_Evaluate( source, &value, NULL, qtrue ) ) { + return qfalse; + } + skip = ( value == 0 ); + PC_PushIndent( source, INDENT_ELIF, skip ); + return qtrue; +} //end of the function PC_Directive_elif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if( source_t *source ) { + signed long int value; + int skip; + + if ( !PC_Evaluate( source, &value, NULL, qtrue ) ) { + return qfalse; + } + skip = ( value == 0 ); + PC_PushIndent( source, INDENT_IF, skip ); + return qtrue; +} //end of the function PC_Directive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_line( source_t *source ) { + SourceError( source, "#line directive not supported" ); + return qfalse; +} //end of the function PC_Directive_line +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_error( source_t *source ) { + token_t token; + + strcpy( token.string, "" ); + PC_ReadSourceToken( source, &token ); + SourceError( source, "#error directive: %s", token.string ); + return qfalse; +} //end of the function PC_Directive_error +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_pragma( source_t *source ) { + token_t token; + + SourceWarning( source, "#pragma directive not supported" ); + while ( PC_ReadLine( source, &token ) ) ; + return qtrue; +} //end of the function PC_Directive_pragma +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void UnreadSignToken( source_t *source ) { + token_t token; + + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + strcpy( token.string, "-" ); + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + PC_UnreadSourceToken( source, &token ); +} //end of the function UnreadSignToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_eval( source_t *source ) { + signed long int value; + token_t token; + + if ( !PC_Evaluate( source, &value, NULL, qtrue ) ) { + return qfalse; + } + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf( token.string, "%d", abs( value ) ); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER | TT_LONG | TT_DECIMAL; + PC_UnreadSourceToken( source, &token ); + if ( value < 0 ) { + UnreadSignToken( source ); + } + return qtrue; +} //end of the function PC_Directive_eval +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_evalfloat( source_t *source ) { + double value; + token_t token; + + if ( !PC_Evaluate( source, NULL, &value, qfalse ) ) { + return qfalse; + } + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf( token.string, "%1.2f", fabs( value ) ); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT | TT_LONG | TT_DECIMAL; + PC_UnreadSourceToken( source, &token ); + if ( value < 0 ) { + UnreadSignToken( source ); + } + return qtrue; +} //end of the function PC_Directive_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t directives[20] = +{ + {"if", PC_Directive_if}, + {"ifdef", PC_Directive_ifdef}, + {"ifndef", PC_Directive_ifndef}, + {"elif", PC_Directive_elif}, + {"else", PC_Directive_else}, + {"endif", PC_Directive_endif}, + {"include", PC_Directive_include}, + {"define", PC_Directive_define}, + {"undef", PC_Directive_undef}, + {"line", PC_Directive_line}, + {"error", PC_Directive_error}, + {"pragma", PC_Directive_pragma}, + {"eval", PC_Directive_eval}, + {"evalfloat", PC_Directive_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDirective( source_t *source ) { + token_t token; + int i; + + //read the directive name + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "found # without name" ); + return qfalse; + } //end if + //directive name must be on the same line + if ( token.linescrossed > 0 ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "found # at end of line" ); + return qfalse; + } //end if + //if if is a name + if ( token.type == TT_NAME ) { + //find the precompiler directive + for ( i = 0; directives[i].name; i++ ) + { + if ( !strcmp( directives[i].name, token.string ) ) { + return directives[i].func( source ); + } //end if + } //end for + } //end if + SourceError( source, "unknown precompiler directive %s", token.string ); + return qfalse; +} //end of the function PC_ReadDirective +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalint( source_t *source ) { + signed long int value; + token_t token; + + if ( !PC_DollarEvaluate( source, &value, NULL, qtrue ) ) { + return qfalse; + } + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf( token.string, "%d", abs( value ) ); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER | TT_LONG | TT_DECIMAL; +#ifdef NUMBERVALUE + token.intvalue = value; + token.floatvalue = value; +#endif //NUMBERVALUE + PC_UnreadSourceToken( source, &token ); + if ( value < 0 ) { + UnreadSignToken( source ); + } + return qtrue; +} //end of the function PC_DollarDirective_evalint +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalfloat( source_t *source ) { + double value; + token_t token; + + if ( !PC_DollarEvaluate( source, NULL, &value, qfalse ) ) { + return qfalse; + } + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf( token.string, "%1.2f", fabs( value ) ); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT | TT_LONG | TT_DECIMAL; +#ifdef NUMBERVALUE + token.intvalue = (unsigned long) value; + token.floatvalue = value; +#endif //NUMBERVALUE + PC_UnreadSourceToken( source, &token ); + if ( value < 0 ) { + UnreadSignToken( source ); + } + return qtrue; +} //end of the function PC_DollarDirective_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t dollardirectives[20] = +{ + {"evalint", PC_DollarDirective_evalint}, + {"evalfloat", PC_DollarDirective_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDollarDirective( source_t *source ) { + token_t token; + int i; + + //read the directive name + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "found $ without name" ); + return qfalse; + } //end if + //directive name must be on the same line + if ( token.linescrossed > 0 ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "found $ at end of line" ); + return qfalse; + } //end if + //if if is a name + if ( token.type == TT_NAME ) { + //find the precompiler directive + for ( i = 0; dollardirectives[i].name; i++ ) + { + if ( !strcmp( dollardirectives[i].name, token.string ) ) { + return dollardirectives[i].func( source ); + } //end if + } //end for + } //end if + PC_UnreadSourceToken( source, &token ); + SourceError( source, "unknown precompiler directive %s", token.string ); + return qfalse; +} //end of the function PC_ReadDirective + +#ifdef QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int BuiltinFunction( source_t *source ) { + token_t token; + + if ( !PC_ReadSourceToken( source, &token ) ) { + return qfalse; + } + if ( token.type == TT_NUMBER ) { + PC_UnreadSourceToken( source, &token ); + return qtrue; + } //end if + else + { + PC_UnreadSourceToken( source, &token ); + return qfalse; + } //end else +} //end of the function BuiltinFunction +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int QuakeCMacro( source_t *source ) { + int i; + token_t token; + + if ( !PC_ReadSourceToken( source, &token ) ) { + return qtrue; + } + if ( token.type != TT_NAME ) { + PC_UnreadSourceToken( source, &token ); + return qtrue; + } //end if + //find the precompiler directive + for ( i = 0; dollardirectives[i].name; i++ ) + { + if ( !strcmp( dollardirectives[i].name, token.string ) ) { + PC_UnreadSourceToken( source, &token ); + return qfalse; + } //end if + } //end for + PC_UnreadSourceToken( source, &token ); + return qtrue; +} //end of the function QuakeCMacro +#endif //QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadToken( source_t *source, token_t *token ) { + define_t *define; + + while ( 1 ) + { + if ( !PC_ReadSourceToken( source, token ) ) { + return qfalse; + } + //check for precompiler directives + if ( token->type == TT_PUNCTUATION && *token->string == '#' ) { +#ifdef QUAKEC + if ( !BuiltinFunction( source ) ) +#endif //QUAKC + { + //read the precompiler directive + if ( !PC_ReadDirective( source ) ) { + return qfalse; + } + continue; + } //end if + } //end if + if ( token->type == TT_PUNCTUATION && *token->string == '$' ) { +#ifdef QUAKEC + if ( !QuakeCMacro( source ) ) +#endif //QUAKEC + { + //read the precompiler directive + if ( !PC_ReadDollarDirective( source ) ) { + return qfalse; + } + continue; + } //end if + } //end if + // recursively concatenate strings that are behind each other still resolving defines + if ( token->type == TT_STRING ) { + token_t newtoken; + if ( PC_ReadToken( source, &newtoken ) ) { + if ( newtoken.type == TT_STRING ) { + token->string[strlen( token->string ) - 1] = '\0'; + if ( strlen( token->string ) + strlen( newtoken.string + 1 ) + 1 >= MAX_TOKEN ) { + SourceError( source, "string longer than MAX_TOKEN %d\n", MAX_TOKEN ); + return qfalse; + } + strcat( token->string, newtoken.string + 1 ); + } else + { + PC_UnreadToken( source, &newtoken ); + } + } + } //end if + //if skipping source because of conditional compilation + if ( source->skip ) { + continue; + } + //if the token is a name + if ( token->type == TT_NAME ) { + //check if the name is a define macro +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token->string ); +#else + define = PC_FindDefine( source->defines, token->string ); +#endif //DEFINEHASHING + //if it is a define macro + if ( define ) { + //expand the defined macro + if ( !PC_ExpandDefineIntoSource( source, token, define ) ) { + return qfalse; + } + continue; + } //end if + } //end if + //copy token for unreading + memcpy( &source->token, token, sizeof( token_t ) ); + //found a token + return qtrue; + } //end while +} //end of the function PC_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenString( source_t *source, char *string ) { + token_t token; + + if ( !PC_ReadToken( source, &token ) ) { + SourceError( source, "couldn't find expected %s", string ); + return qfalse; + } //end if + + if ( strcmp( token.string, string ) ) { + SourceError( source, "expected %s, found %s", string, token.string ); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_ExpectTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenType( source_t *source, int type, int subtype, token_t *token ) { + char str[MAX_TOKEN]; + + if ( !PC_ReadToken( source, token ) ) { + SourceError( source, "couldn't read expected token" ); + return qfalse; + } //end if + + if ( token->type != type ) { + strcpy( str, "" ); + if ( type == TT_STRING ) { + strcpy( str, "string" ); + } + if ( type == TT_LITERAL ) { + strcpy( str, "literal" ); + } + if ( type == TT_NUMBER ) { + strcpy( str, "number" ); + } + if ( type == TT_NAME ) { + strcpy( str, "name" ); + } + if ( type == TT_PUNCTUATION ) { + strcpy( str, "punctuation" ); + } + SourceError( source, "expected a %s, found %s", str, token->string ); + return qfalse; + } //end if + if ( token->type == TT_NUMBER ) { + if ( ( token->subtype & subtype ) != subtype ) { + if ( subtype & TT_DECIMAL ) { + strcpy( str, "decimal" ); + } + if ( subtype & TT_HEX ) { + strcpy( str, "hex" ); + } + if ( subtype & TT_OCTAL ) { + strcpy( str, "octal" ); + } + if ( subtype & TT_BINARY ) { + strcpy( str, "binary" ); + } + if ( subtype & TT_LONG ) { + strcat( str, " long" ); + } + if ( subtype & TT_UNSIGNED ) { + strcat( str, " unsigned" ); + } + if ( subtype & TT_FLOAT ) { + strcat( str, " float" ); + } + if ( subtype & TT_INTEGER ) { + strcat( str, " integer" ); + } + SourceError( source, "expected %s, found %s", str, token->string ); + return qfalse; + } //end if + } //end if + else if ( token->type == TT_PUNCTUATION ) { + if ( token->subtype != subtype ) { + SourceError( source, "found %s", token->string ); + return qfalse; + } //end if + } //end else if + return qtrue; +} //end of the function PC_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectAnyToken( source_t *source, token_t *token ) { + if ( !PC_ReadToken( source, token ) ) { + SourceError( source, "couldn't read expected token" ); + return qfalse; + } //end if + else + { + return qtrue; + } //end else +} //end of the function PC_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenString( source_t *source, char *string ) { + token_t tok; + + if ( !PC_ReadToken( source, &tok ) ) { + return qfalse; + } + //if the token is available + if ( !strcmp( tok.string, string ) ) { + return qtrue; + } + // + PC_UnreadSourceToken( source, &tok ); + return qfalse; +} //end of the function PC_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenType( source_t *source, int type, int subtype, token_t *token ) { + token_t tok; + + if ( !PC_ReadToken( source, &tok ) ) { + return qfalse; + } + //if the type matches + if ( tok.type == type && + ( tok.subtype & subtype ) == subtype ) { + memcpy( token, &tok, sizeof( token_t ) ); + return qtrue; + } //end if + // + PC_UnreadSourceToken( source, &tok ); + return qfalse; +} //end of the function PC_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SkipUntilString( source_t *source, char *string ) { + token_t token; + + while ( PC_ReadToken( source, &token ) ) + { + if ( !strcmp( token.string, string ) ) { + return qtrue; + } + } //end while + return qfalse; +} //end of the function PC_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadLastToken( source_t *source ) { + PC_UnreadSourceToken( source, &source->token ); +} //end of the function PC_UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadToken( source_t *source, token_t *token ) { + PC_UnreadSourceToken( source, token ); +} //end of the function PC_UnreadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetIncludePath( source_t *source, char *path ) { + strncpy( source->includepath, path, _MAX_PATH ); + //add trailing path seperator + if ( source->includepath[strlen( source->includepath ) - 1] != '\\' && + source->includepath[strlen( source->includepath ) - 1] != '/' ) { + strcat( source->includepath, PATHSEPERATOR_STR ); + } //end if +} //end of the function PC_SetIncludePath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetPunctuations( source_t *source, punctuation_t *p ) { + source->punctuations = p; +} //end of the function PC_SetPunctuations +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceFile( const char *filename ) { + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + + script = LoadScriptFile( filename ); + if ( !script ) { + return NULL; + } + + script->next = NULL; + + source = (source_t *) GetMemory( sizeof( source_t ) ); + memset( source, 0, sizeof( source_t ) ); + + strncpy( source->filename, filename, _MAX_PATH ); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = GetClearedMemory( DEFINEHASHSIZE * sizeof( define_t * ) ); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource( source ); + return source; +} //end of the function LoadSourceFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceMemory( char *ptr, int length, char *name ) { + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + + script = LoadScriptMemory( ptr, length, name ); + if ( !script ) { + return NULL; + } + script->next = NULL; + + source = (source_t *) GetMemory( sizeof( source_t ) ); + memset( source, 0, sizeof( source_t ) ); + + strncpy( source->filename, name, _MAX_PATH ); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = GetClearedMemory( DEFINEHASHSIZE * sizeof( define_t * ) ); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource( source ); + return source; +} //end of the function LoadSourceMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeSource( source_t *source ) { + script_t *script; + token_t *token; + define_t *define; + indent_t *indent; + int i; + + //PC_PrintDefineHashTable(source->definehash); + //free all the scripts + while ( source->scriptstack ) + { + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript( script ); + } //end for + //free all the tokens + while ( source->tokens ) + { + token = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken( token ); + } //end for +#if DEFINEHASHING + for ( i = 0; i < DEFINEHASHSIZE; i++ ) + { + while ( source->definehash[i] ) + { + define = source->definehash[i]; + source->definehash[i] = source->definehash[i]->hashnext; + PC_FreeDefine( define ); + } //end while + } //end for +#else //DEFINEHASHING + //free all defines + while ( source->defines ) + { + define = source->defines; + source->defines = source->defines->next; + PC_FreeDefine( define ); + } //end for +#endif //DEFINEHASHING + //free all indents + while ( source->indentstack ) + { + indent = source->indentstack; + source->indentstack = source->indentstack->next; + FreeMemory( indent ); + } //end for +#if DEFINEHASHING + // + if ( source->definehash ) { + FreeMemory( source->definehash ); + } +#endif //DEFINEHASHING + //free the source itself + FreeMemory( source ); +} //end of the function FreeSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ + +#define MAX_SOURCEFILES 64 + +source_t *sourceFiles[MAX_SOURCEFILES]; + +int PC_LoadSourceHandle( const char *filename ) { + source_t *source; + int i; + + for ( i = 1; i < MAX_SOURCEFILES; i++ ) + { + if ( !sourceFiles[i] ) { + break; + } + } //end for + if ( i >= MAX_SOURCEFILES ) { + return 0; + } + PS_SetBaseFolder( "" ); + source = LoadSourceFile( filename ); + if ( !source ) { + return 0; + } + sourceFiles[i] = source; + return i; +} //end of the function PC_LoadSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_FreeSourceHandle( int handle ) { + if ( handle < 1 || handle >= MAX_SOURCEFILES ) { + return qfalse; + } + if ( !sourceFiles[handle] ) { + return qfalse; + } + + FreeSource( sourceFiles[handle] ); + sourceFiles[handle] = NULL; + return qtrue; +} //end of the function PC_FreeSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadTokenHandle( int handle, pc_token_t *pc_token ) { + token_t token; + int ret; + + if ( handle < 1 || handle >= MAX_SOURCEFILES ) { + return 0; + } + if ( !sourceFiles[handle] ) { + return 0; + } + + ret = PC_ReadToken( sourceFiles[handle], &token ); + strcpy( pc_token->string, token.string ); + pc_token->type = token.type; + pc_token->subtype = token.subtype; + pc_token->intvalue = token.intvalue; + pc_token->floatvalue = token.floatvalue; + if ( pc_token->type == TT_STRING ) { + StripDoubleQuotes( pc_token->string ); + } + return ret; +} //end of the function PC_ReadTokenHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SourceFileAndLine( int handle, char *filename, int *line ) { + if ( handle < 1 || handle >= MAX_SOURCEFILES ) { + return qfalse; + } + if ( !sourceFiles[handle] ) { + return qfalse; + } + + strcpy( filename, sourceFiles[handle]->filename ); + if ( sourceFiles[handle]->scriptstack ) { + *line = sourceFiles[handle]->scriptstack->line; + } else { + *line = 0; + } + return qtrue; +} //end of the function PC_SourceFileAndLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetBaseFolder( char *path ) { + PS_SetBaseFolder( path ); +} //end of the function PC_SetBaseFolder +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_CheckOpenSourceHandles( void ) { + int i; + + for ( i = 1; i < MAX_SOURCEFILES; i++ ) + { + if ( sourceFiles[i] ) { +#ifdef BOTLIB + botimport.Print( PRT_ERROR, "file %s still open in precompiler\n", sourceFiles[i]->scriptstack->filename ); +#endif //BOTLIB + } //end if + } //end for +} //end of the function PC_CheckOpenSourceHandles diff --git a/src/botlib/l_precomp.h b/src/botlib/l_precomp.h new file mode 100644 index 0000000..0aa33e6 --- /dev/null +++ b/src/botlib/l_precomp.h @@ -0,0 +1,166 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_precomp.h + * + * desc: pre compiler + * + * + *****************************************************************************/ + +#ifndef _MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +#ifndef PATH_SEPERATORSTR + #if defined( WIN32 ) | defined( _WIN32 ) | defined( __NT__ ) | defined( __WINDOWS__ ) | defined( __WINDOWS_386__ ) + #define PATHSEPERATOR_STR "\\" + #else + #define PATHSEPERATOR_STR "/" + #endif +#endif +#ifndef PATH_SEPERATORCHAR + #if defined( WIN32 ) | defined( _WIN32 ) | defined( __NT__ ) | defined( __WINDOWS__ ) | defined( __WINDOWS_386__ ) + #define PATHSEPERATOR_CHAR '\\' + #else + #define PATHSEPERATOR_CHAR '/' + #endif +#endif + + +#define DEFINE_FIXED 0x0001 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +//macro definitions +typedef struct define_s +{ + char *name; //define name + int flags; //define flags + int builtin; // > 0 if builtin define + int numparms; //number of define parameters + token_t *parms; //define parameters + token_t *tokens; //macro tokens (possibly containing parm tokens) + struct define_s *next; //next defined macro in a list + struct define_s *hashnext; //next define in the hash chain +} define_t; + +//indents +//used for conditional compilation directives: +//#if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s +{ + int type; //indent type + int skip; //true if skipping current indent + script_t *script; //script the indent was in + struct indent_s *next; //next indent on the indent stack +} indent_t; + +//source file +typedef struct source_s +{ + char filename[_MAX_PATH]; //file name of the script + char includepath[_MAX_PATH]; //path to include files + punctuation_t *punctuations; //punctuations to use + script_t *scriptstack; //stack with scripts of the source + token_t *tokens; //tokens to read first + define_t *defines; //list with macro definitions + define_t **definehash; //hash chain with defines + indent_t *indentstack; //stack with indents + int skip; // > 0 if skipping conditional code + token_t token; //last read token +} source_t; + + +//read a token from the source +int PC_ReadToken( source_t *source, token_t *token ); +//expect a certain token +int PC_ExpectTokenString( source_t *source, char *string ); +//expect a certain token type +int PC_ExpectTokenType( source_t *source, int type, int subtype, token_t *token ); +//expect a token +int PC_ExpectAnyToken( source_t *source, token_t *token ); +//returns true when the token is available +int PC_CheckTokenString( source_t *source, char *string ); +//returns true an reads the token when a token with the given type is available +int PC_CheckTokenType( source_t *source, int type, int subtype, token_t *token ); +//skip tokens until the given token string is read +int PC_SkipUntilString( source_t *source, char *string ); +//unread the last token read from the script +void PC_UnreadLastToken( source_t *source ); +//unread the given token +void PC_UnreadToken( source_t *source, token_t *token ); +//read a token only if on the same line, lines are concatenated with a slash +int PC_ReadLine( source_t *source, token_t *token ); +//returns true if there was a white space in front of the token +int PC_WhiteSpaceBeforeToken( token_t *token ); +//add a define to the source +int PC_AddDefine( source_t *source, char *string ); +//add a globals define that will be added to all opened sources +int PC_AddGlobalDefine( char *string ); +//remove the given global define +int PC_RemoveGlobalDefine( char *name ); +//remove all globals defines +void PC_RemoveAllGlobalDefines( void ); +//add builtin defines +void PC_AddBuiltinDefines( source_t *source ); +//set the source include path +void PC_SetIncludePath( source_t *source, char *path ); +//set the punction set +void PC_SetPunctuations( source_t *source, punctuation_t *p ); +//set the base folder to load files from +void PC_SetBaseFolder( char *path ); +//load a source file +source_t *LoadSourceFile( const char *filename ); +//load a source from memory +source_t *LoadSourceMemory( char *ptr, int length, char *name ); +//free the given source +void FreeSource( source_t *source ); +//print a source error +void QDECL SourceError( source_t *source, char *str, ... ); +//print a source warning +void QDECL SourceWarning( source_t *source, char *str, ... ); + +// +int PC_LoadSourceHandle( const char *filename ); +int PC_FreeSourceHandle( int handle ); +int PC_ReadTokenHandle( int handle, struct pc_token_s *pc_token ); +int PC_SourceFileAndLine( int handle, char *filename, int *line ); +void PC_CheckOpenSourceHandles( void ); diff --git a/src/botlib/l_script.c b/src/botlib/l_script.c new file mode 100644 index 0000000..a2b2e02 --- /dev/null +++ b/src/botlib/l_script.c @@ -0,0 +1,1443 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_script.c + * + * desc: lexicographical parser + * + * + *****************************************************************************/ + +//#define SCREWUP +//#define BOTLIB +//#define MEQCC +//#define BSPC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include "../botlib/l_memory.h" +#include "../botlib/l_script.h" + +typedef enum {qfalse, qtrue} qboolean; + +#endif //SCREWUP + +#ifdef BOTLIB +//include files for usage in the bot library +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#endif //BOTLIB + +#ifdef MEQCC +//include files for usage in MrElusive's QuakeC Compiler +#include "qcc.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + + +#define PUNCTABLE + +//longer punctuations first +punctuation_t default_punctuations[] = +{ + //binary operators + {">>=",P_RSHIFT_ASSIGN, NULL}, + {"<<=",P_LSHIFT_ASSIGN, NULL}, + // + {"...",P_PARMS, NULL}, + //define merge operator + {"##",P_PRECOMPMERGE, NULL}, + //logic operators + {"&&",P_LOGIC_AND, NULL}, + {"||",P_LOGIC_OR, NULL}, + {">=",P_LOGIC_GEQ, NULL}, + {"<=",P_LOGIC_LEQ, NULL}, + {"==",P_LOGIC_EQ, NULL}, + {"!=",P_LOGIC_UNEQ, NULL}, + //arithmatic operators + {"*=",P_MUL_ASSIGN, NULL}, + {"/=",P_DIV_ASSIGN, NULL}, + {"%=",P_MOD_ASSIGN, NULL}, + {"+=",P_ADD_ASSIGN, NULL}, + {"-=",P_SUB_ASSIGN, NULL}, + {"++",P_INC, NULL}, + {"--",P_DEC, NULL}, + //binary operators + {"&=",P_BIN_AND_ASSIGN, NULL}, + {"|=",P_BIN_OR_ASSIGN, NULL}, + {"^=",P_BIN_XOR_ASSIGN, NULL}, + {">>",P_RSHIFT, NULL}, + {"<<",P_LSHIFT, NULL}, + //reference operators + {"->",P_POINTERREF, NULL}, + //C++ + {"::",P_CPP1, NULL}, + {".*",P_CPP2, NULL}, + //arithmatic operators + {"*",P_MUL, NULL}, + {"/",P_DIV, NULL}, + {"%",P_MOD, NULL}, + {"+",P_ADD, NULL}, + {"-",P_SUB, NULL}, + {"=",P_ASSIGN, NULL}, + //binary operators + {"&",P_BIN_AND, NULL}, + {"|",P_BIN_OR, NULL}, + {"^",P_BIN_XOR, NULL}, + {"~",P_BIN_NOT, NULL}, + //logic operators + {"!",P_LOGIC_NOT, NULL}, + {">",P_LOGIC_GREATER, NULL}, + {"<",P_LOGIC_LESS, NULL}, + //reference operator + {".",P_REF, NULL}, + //seperators + {",",P_COMMA, NULL}, + {";",P_SEMICOLON, NULL}, + //label indication + {":",P_COLON, NULL}, + //if statement + {"?",P_QUESTIONMARK, NULL}, + //embracements + {"(",P_PARENTHESESOPEN, NULL}, + {")",P_PARENTHESESCLOSE, NULL}, + {"{",P_BRACEOPEN, NULL}, + {"}",P_BRACECLOSE, NULL}, + {"[",P_SQBRACKETOPEN, NULL}, + {"]",P_SQBRACKETCLOSE, NULL}, + // + {"\\",P_BACKSLASH, NULL}, + //precompiler operator + {"#",P_PRECOMP, NULL}, +#ifdef DOLLAR + {"$",P_DOLLAR, NULL}, +#endif //DOLLAR + {NULL, 0} +}; + +#ifdef BSPC +char basefolder[MAX_PATH]; +#else +char basefolder[MAX_QPATH]; +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PS_CreatePunctuationTable( script_t *script, punctuation_t *punctuations ) { + int i; + punctuation_t *p, *lastp, *newp; + + //get memory for the table + if ( !script->punctuationtable ) { + script->punctuationtable = (punctuation_t **) + GetMemory( 256 * sizeof( punctuation_t * ) ); + } + memset( script->punctuationtable, 0, 256 * sizeof( punctuation_t * ) ); + //add the punctuations in the list to the punctuation table + for ( i = 0; punctuations[i].p; i++ ) + { + newp = &punctuations[i]; + lastp = NULL; + //sort the punctuations in this table entry on length (longer punctuations first) + for ( p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next ) + { + if ( strlen( p->p ) < strlen( newp->p ) ) { + newp->next = p; + if ( lastp ) { + lastp->next = newp; + } else { script->punctuationtable[(unsigned int) newp->p[0]] = newp;} + break; + } //end if + lastp = p; + } //end for + if ( !p ) { + newp->next = NULL; + if ( lastp ) { + lastp->next = newp; + } else { script->punctuationtable[(unsigned int) newp->p[0]] = newp;} + } //end if + } //end for +} //end of the function PS_CreatePunctuationTable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *PunctuationFromNum( script_t *script, int num ) { + int i; + + for ( i = 0; script->punctuations[i].p; i++ ) + { + if ( script->punctuations[i].n == num ) { + return script->punctuations[i].p; + } + } //end for + return "unkown punctuation"; +} //end of the function PunctuationFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptError( script_t *script, char *str, ... ) { + char text[1024]; + va_list ap; + + if ( script->flags & SCFL_NOERRORS ) { + return; + } + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); +#ifdef BOTLIB + botimport.Print( PRT_ERROR, "file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //BOTLIB +#ifdef MEQCC + printf( "error: file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //MEQCC +#ifdef BSPC + Log_Print( "error: file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //BSPC +} //end of the function ScriptError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptWarning( script_t *script, char *str, ... ) { + char text[1024]; + va_list ap; + + if ( script->flags & SCFL_NOWARNINGS ) { + return; + } + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); +#ifdef BOTLIB + botimport.Print( PRT_WARNING, "file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //BOTLIB +#ifdef MEQCC + printf( "warning: file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //MEQCC +#ifdef BSPC + Log_Print( "warning: file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //BSPC +} //end of the function ScriptWarning +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetScriptPunctuations( script_t *script, punctuation_t *p ) { +#ifdef PUNCTABLE + if ( p ) { + PS_CreatePunctuationTable( script, p ); + } else { PS_CreatePunctuationTable( script, default_punctuations );} +#endif //PUNCTABLE + if ( p ) { + script->punctuations = p; + } else { script->punctuations = default_punctuations;} +} //end of the function SetScriptPunctuations +//============================================================================ +// Reads spaces, tabs, C-like comments etc. +// When a newline character is found the scripts line counter is increased. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadWhiteSpace( script_t *script ) { + while ( 1 ) + { + //skip white space + while ( *script->script_p <= ' ' ) + { + if ( !*script->script_p ) { + return 0; + } + if ( *script->script_p == '\n' ) { + script->line++; + } + script->script_p++; + } //end while + //skip comments + if ( *script->script_p == '/' ) { + //comments // + if ( *( script->script_p + 1 ) == '/' ) { + script->script_p++; + do + { + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + } //end do + while ( *script->script_p != '\n' ); + script->line++; + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + continue; + } //end if + //comments /* */ + else if ( *( script->script_p + 1 ) == '*' ) { + script->script_p++; + do + { + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + if ( *script->script_p == '\n' ) { + script->line++; + } + } //end do + while ( !( *script->script_p == '*' && *( script->script_p + 1 ) == '/' ) ); + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + continue; + } //end if + } //end if + break; + } //end while + return 1; +} //end of the function PS_ReadWhiteSpace +//============================================================================ +// Reads an escape character. +// +// Parameter: script : script to read from +// ch : place to store the read escape character +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadEscapeCharacter( script_t *script, char *ch ) { + int c, val, i; + + //step over the leading '\\' + script->script_p++; + //determine the escape character + switch ( *script->script_p ) + { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + script->script_p++; + for ( i = 0, val = 0; ; i++, script->script_p++ ) + { + c = *script->script_p; + if ( c >= '0' && c <= '9' ) { + c = c - '0'; + } else if ( c >= 'A' && c <= 'Z' ) { + c = c - 'A' + 10; + } else if ( c >= 'a' && c <= 'z' ) { + c = c - 'a' + 10; + } else { break;} + val = ( val << 4 ) + c; + } //end for + script->script_p--; + if ( val > 0xFF ) { + ScriptWarning( script, "too large value in escape character" ); + val = 0xFF; + } //end if + c = val; + break; + } //end case + default: //NOTE: decimal ASCII code, NOT octal + { + if ( *script->script_p < '0' || *script->script_p > '9' ) { + ScriptError( script, "unknown escape char" ); + } + for ( i = 0, val = 0; ; i++, script->script_p++ ) + { + c = *script->script_p; + if ( c >= '0' && c <= '9' ) { + c = c - '0'; + } else { break;} + val = val * 10 + c; + } //end for + script->script_p--; + if ( val > 0xFF ) { + ScriptWarning( script, "too large value in escape character" ); + val = 0xFF; + } //end if + c = val; + break; + } //end default + } //end switch + //step over the escape character or the last digit of the number + script->script_p++; + //store the escape character + *ch = c; + //succesfully read escape character + return 1; +} //end of the function PS_ReadEscapeCharacter +//============================================================================ +// Reads C-like string. Escape characters are interpretted. +// Quotes are included with the string. +// Reads two strings with a white space between them as one string. +// +// Parameter: script : script to read from +// token : buffer to store the string +// Returns: qtrue when a string was read succesfully +// Changes Globals: - +//============================================================================ +int PS_ReadString( script_t *script, token_t *token, int quote ) { + int len, tmpline; + char *tmpscript_p; + + if ( quote == '\"' ) { + token->type = TT_STRING; + } else { token->type = TT_LITERAL;} + + len = 0; + //leading quote + token->string[len++] = *script->script_p++; + // + while ( 1 ) + { + //minus 2 because trailing double quote and zero have to be appended + if ( len >= MAX_TOKEN - 2 ) { + ScriptError( script, "string longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + //if there is an escape character and + //if escape characters inside a string are allowed + if ( *script->script_p == '\\' && !( script->flags & SCFL_NOSTRINGESCAPECHARS ) ) { + if ( !PS_ReadEscapeCharacter( script, &token->string[len] ) ) { + token->string[len] = 0; + return 0; + } //end if + len++; + } //end if + //if a trailing quote + else if ( *script->script_p == quote ) { + //step over the double quote + script->script_p++; + //if white spaces in a string are not allowed + if ( script->flags & SCFL_NOSTRINGWHITESPACES ) { + break; + } + // + tmpscript_p = script->script_p; + tmpline = script->line; + //read unusefull stuff between possible two following strings + if ( !PS_ReadWhiteSpace( script ) ) { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //if there's no leading double qoute + if ( *script->script_p != quote ) { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //step over the new leading double quote + script->script_p++; + } //end if + else + { + if ( *script->script_p == '\0' ) { + token->string[len] = 0; + ScriptError( script, "missing trailing quote" ); + return 0; + } //end if + if ( *script->script_p == '\n' ) { + token->string[len] = 0; + ScriptError( script, "newline inside string %s", token->string ); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end else + } //end while + //trailing quote + token->string[len++] = quote; + //end string with a zero + token->string[len] = '\0'; + //the sub type is the length of the string + token->subtype = len; + return 1; +} //end of the function PS_ReadString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadName( script_t *script, token_t *token ) { + int len = 0; + char c; + + token->type = TT_NAME; + do + { + token->string[len++] = *script->script_p++; + if ( len >= MAX_TOKEN ) { + ScriptError( script, "name longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + c = *script->script_p; + } while ( ( c >= 'a' && c <= 'z' ) || + ( c >= 'A' && c <= 'Z' ) || + ( c >= '0' && c <= '9' ) || + c == '_' ); + token->string[len] = '\0'; + //the sub type is the length of the name + token->subtype = len; + return 1; +} //end of the function PS_ReadName +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void NumberValue( char *string, int subtype, unsigned long int *intvalue, + long double *floatvalue ) { + unsigned long int dotfound = 0; + + *intvalue = 0; + *floatvalue = 0; + //floating point number + if ( subtype & TT_FLOAT ) { + while ( *string ) + { + if ( *string == '.' ) { + if ( dotfound ) { + return; + } + dotfound = 10; + string++; + } //end if + if ( dotfound ) { + *floatvalue = *floatvalue + ( long double )( *string - '0' ) / + (long double) dotfound; + dotfound *= 10; + } //end if + else + { + *floatvalue = *floatvalue * 10.0 + ( long double )( *string - '0' ); + } //end else + string++; + } //end while + *intvalue = (unsigned long) *floatvalue; + } //end if + else if ( subtype & TT_DECIMAL ) { + while ( *string ) *intvalue = *intvalue * 10 + ( *string++ - '0' ); + *floatvalue = *intvalue; + } //end else if + else if ( subtype & TT_HEX ) { + //step over the leading 0x or 0X + string += 2; + while ( *string ) + { + *intvalue <<= 4; + if ( *string >= 'a' && *string <= 'f' ) { + *intvalue += *string - 'a' + 10; + } else if ( *string >= 'A' && *string <= 'F' ) { + *intvalue += *string - 'A' + 10; + } else { *intvalue += *string - '0';} + string++; + } //end while + *floatvalue = *intvalue; + } //end else if + else if ( subtype & TT_OCTAL ) { + //step over the first zero + string += 1; + while ( *string ) *intvalue = ( *intvalue << 3 ) + ( *string++ - '0' ); + *floatvalue = *intvalue; + } //end else if + else if ( subtype & TT_BINARY ) { + //step over the leading 0b or 0B + string += 2; + while ( *string ) *intvalue = ( *intvalue << 1 ) + ( *string++ - '0' ); + *floatvalue = *intvalue; + } //end else if +} //end of the function NumberValue +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadNumber( script_t *script, token_t *token ) { + int len = 0, i; + int octal, dot; + char c; +// unsigned long int intvalue = 0; +// long double floatvalue = 0; + + token->type = TT_NUMBER; + //check for a hexadecimal number + if ( *script->script_p == '0' && + ( *( script->script_p + 1 ) == 'x' || + *( script->script_p + 1 ) == 'X' ) ) { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while ( ( c >= '0' && c <= '9' ) || + ( c >= 'a' && c <= 'f' ) || + ( c >= 'A' && c <= 'A' ) ) + { + token->string[len++] = *script->script_p++; + if ( len >= MAX_TOKEN ) { + ScriptError( script, "hexadecimal number longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_HEX; + } //end if +#ifdef BINARYNUMBERS + //check for a binary number + else if ( *script->script_p == '0' && + ( *( script->script_p + 1 ) == 'b' || + *( script->script_p + 1 ) == 'B' ) ) { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while ( c == '0' || c == '1' ) + { + token->string[len++] = *script->script_p++; + if ( len >= MAX_TOKEN ) { + ScriptError( script, "binary number longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_BINARY; + } //end if +#endif //BINARYNUMBERS + else //decimal or octal integer or floating point number + { + octal = qfalse; + dot = qfalse; + if ( *script->script_p == '0' ) { + octal = qtrue; + } + while ( 1 ) + { + c = *script->script_p; + if ( c == '.' ) { + dot = qtrue; + } else if ( c == '8' || c == '9' ) { + octal = qfalse; + } else if ( c < '0' || c > '9' ) { + break; + } + token->string[len++] = *script->script_p++; + if ( len >= MAX_TOKEN - 1 ) { + ScriptError( script, "number longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + } //end while + if ( octal ) { + token->subtype |= TT_OCTAL; + } else { token->subtype |= TT_DECIMAL;} + if ( dot ) { + token->subtype |= TT_FLOAT; + } + } //end else + for ( i = 0; i < 2; i++ ) + { + c = *script->script_p; + //check for a LONG number + if ( ( c == 'l' || c == 'L' ) && + !( token->subtype & TT_LONG ) ) { + script->script_p++; + token->subtype |= TT_LONG; + } //end if + //check for an UNSIGNED number + else if ( ( c == 'u' || c == 'U' ) && + !( token->subtype & ( TT_UNSIGNED | TT_FLOAT ) ) ) { + script->script_p++; + token->subtype |= TT_UNSIGNED; + } //end if + } //end for + token->string[len] = '\0'; +#ifdef NUMBERVALUE + NumberValue( token->string, token->subtype, &token->intvalue, &token->floatvalue ); +#endif //NUMBERVALUE + if ( !( token->subtype & TT_FLOAT ) ) { + token->subtype |= TT_INTEGER; + } + return 1; +} //end of the function PS_ReadNumber +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadLiteral( script_t *script, token_t *token ) { + token->type = TT_LITERAL; + //first quote + token->string[0] = *script->script_p++; + //check for end of file + if ( !*script->script_p ) { + ScriptError( script, "end of file before trailing \'" ); + return 0; + } //end if + //if it is an escape character + if ( *script->script_p == '\\' ) { + if ( !PS_ReadEscapeCharacter( script, &token->string[1] ) ) { + return 0; + } + } //end if + else + { + token->string[1] = *script->script_p++; + } //end else + //check for trailing quote + if ( *script->script_p != '\'' ) { + ScriptWarning( script, "too many characters in literal, ignored" ); + while ( *script->script_p && + *script->script_p != '\'' && + *script->script_p != '\n' ) + { + script->script_p++; + } //end while + if ( *script->script_p == '\'' ) { + script->script_p++; + } + } //end if + //store the trailing quote + token->string[2] = *script->script_p++; + //store trailing zero to end the string + token->string[3] = '\0'; + //the sub type is the integer literal value + token->subtype = token->string[1]; + // + return 1; +} //end of the function PS_ReadLiteral +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPunctuation( script_t *script, token_t *token ) { + int len; + char *p; + punctuation_t *punc; + +#ifdef PUNCTABLE + for ( punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next ) + { +#else + int i; + + for ( i = 0; script->punctuations[i].p; i++ ) + { + punc = &script->punctuations[i]; +#endif //PUNCTABLE + p = punc->p; + len = strlen( p ); + //if the script contains at least as much characters as the punctuation + if ( script->script_p + len <= script->end_p ) { + //if the script contains the punctuation + if ( !strncmp( script->script_p, p, len ) ) { + strncpy( token->string, p, MAX_TOKEN ); + script->script_p += len; + token->type = TT_PUNCTUATION; + //sub type is the number of the punctuation + token->subtype = punc->n; + return 1; + } //end if + } //end if + } //end for + return 0; +} //end of the function PS_ReadPunctuation +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPrimitive( script_t *script, token_t *token ) { + int len; + + len = 0; + while ( *script->script_p > ' ' && *script->script_p != ';' ) + { + if ( len >= MAX_TOKEN ) { + ScriptError( script, "primitive token longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end while + token->string[len] = 0; + //copy the token into the script structure + memcpy( &script->token, token, sizeof( token_t ) ); + //primitive reading successfull + return 1; +} //end of the function PS_ReadPrimitive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadToken( script_t *script, token_t *token ) { + //if there is a token available (from UnreadToken) + if ( script->tokenavailable ) { + script->tokenavailable = 0; + memcpy( token, &script->token, sizeof( token_t ) ); + return 1; + } //end if + //save script pointer + script->lastscript_p = script->script_p; + //save line counter + script->lastline = script->line; + //clear the token stuff + memset( token, 0, sizeof( token_t ) ); + //start of the white space + script->whitespace_p = script->script_p; + token->whitespace_p = script->script_p; + //read unusefull stuff + if ( !PS_ReadWhiteSpace( script ) ) { + return 0; + } + //end of the white space + script->endwhitespace_p = script->script_p; + token->endwhitespace_p = script->script_p; + //line the token is on + token->line = script->line; + //number of lines crossed before token + token->linescrossed = script->line - script->lastline; + //if there is a leading double quote + if ( *script->script_p == '\"' ) { + if ( !PS_ReadString( script, token, '\"' ) ) { + return 0; + } + } //end if + //if an literal + else if ( *script->script_p == '\'' ) { + //if (!PS_ReadLiteral(script, token)) return 0; + if ( !PS_ReadString( script, token, '\'' ) ) { + return 0; + } + } //end if + //if there is a number + else if ( ( *script->script_p >= '0' && *script->script_p <= '9' ) || + ( *script->script_p == '.' && + ( *( script->script_p + 1 ) >= '0' && *( script->script_p + 1 ) <= '9' ) ) ) { + if ( !PS_ReadNumber( script, token ) ) { + return 0; + } + } //end if + //if this is a primitive script + else if ( script->flags & SCFL_PRIMITIVE ) { + return PS_ReadPrimitive( script, token ); + } //end else if + //if there is a name + else if ( ( *script->script_p >= 'a' && *script->script_p <= 'z' ) || + ( *script->script_p >= 'A' && *script->script_p <= 'Z' ) || + *script->script_p == '_' ) { + if ( !PS_ReadName( script, token ) ) { + return 0; + } + } //end if + //check for punctuations + else if ( !PS_ReadPunctuation( script, token ) ) { + ScriptError( script, "can't read token" ); + return 0; + } //end if + //copy the token into the script structure + memcpy( &script->token, token, sizeof( token_t ) ); + //succesfully read a token + return 1; +} //end of the function PS_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenString( script_t *script, char *string ) { + token_t token; + + if ( !PS_ReadToken( script, &token ) ) { + ScriptError( script, "couldn't find expected %s", string ); + return 0; + } //end if + + if ( strcmp( token.string, string ) ) { + ScriptError( script, "expected %s, found %s", string, token.string ); + return 0; + } //end if + return 1; +} //end of the function PS_ExpectToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenType( script_t *script, int type, int subtype, token_t *token ) { + char str[MAX_TOKEN]; + + if ( !PS_ReadToken( script, token ) ) { + ScriptError( script, "couldn't read expected token" ); + return 0; + } //end if + + if ( token->type != type ) { + if ( type == TT_STRING ) { + strcpy( str, "string" ); + } + if ( type == TT_LITERAL ) { + strcpy( str, "literal" ); + } + if ( type == TT_NUMBER ) { + strcpy( str, "number" ); + } + if ( type == TT_NAME ) { + strcpy( str, "name" ); + } + if ( type == TT_PUNCTUATION ) { + strcpy( str, "punctuation" ); + } + ScriptError( script, "expected a %s, found %s", str, token->string ); + return 0; + } //end if + if ( token->type == TT_NUMBER ) { + if ( ( token->subtype & subtype ) != subtype ) { + if ( subtype & TT_DECIMAL ) { + strcpy( str, "decimal" ); + } + if ( subtype & TT_HEX ) { + strcpy( str, "hex" ); + } + if ( subtype & TT_OCTAL ) { + strcpy( str, "octal" ); + } + if ( subtype & TT_BINARY ) { + strcpy( str, "binary" ); + } + if ( subtype & TT_LONG ) { + strcat( str, " long" ); + } + if ( subtype & TT_UNSIGNED ) { + strcat( str, " unsigned" ); + } + if ( subtype & TT_FLOAT ) { + strcat( str, " float" ); + } + if ( subtype & TT_INTEGER ) { + strcat( str, " integer" ); + } + ScriptError( script, "expected %s, found %s", str, token->string ); + return 0; + } //end if + } //end if + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + ScriptError( script, "BUG: wrong punctuation subtype" ); + return 0; + } //end if + if ( token->subtype != subtype ) { + ScriptError( script, "expected %s, found %s", + script->punctuations[subtype], token->string ); + return 0; + } //end if + } //end else if + return 1; +} //end of the function PS_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectAnyToken( script_t *script, token_t *token ) { + if ( !PS_ReadToken( script, token ) ) { + ScriptError( script, "couldn't read expected token" ); + return 0; + } //end if + else + { + return 1; + } //end else +} //end of the function PS_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenString( script_t *script, char *string ) { + token_t tok; + + if ( !PS_ReadToken( script, &tok ) ) { + return 0; + } + //if the token is available + if ( !strcmp( tok.string, string ) ) { + return 1; + } + //token not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenType( script_t *script, int type, int subtype, token_t *token ) { + token_t tok; + + if ( !PS_ReadToken( script, &tok ) ) { + return 0; + } + //if the type matches + if ( tok.type == type && + ( tok.subtype & subtype ) == subtype ) { + memcpy( token, &tok, sizeof( token_t ) ); + return 1; + } //end if + //token is not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_SkipUntilString( script_t *script, char *string ) { + token_t token; + + while ( PS_ReadToken( script, &token ) ) + { + if ( !strcmp( token.string, string ) ) { + return 1; + } + } //end while + return 0; +} //end of the function PS_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadLastToken( script_t *script ) { + script->tokenavailable = 1; +} //end of the function UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadToken( script_t *script, token_t *token ) { + memcpy( &script->token, token, sizeof( token_t ) ); + script->tokenavailable = 1; +} //end of the function UnreadToken +//============================================================================ +// returns the next character of the read white space, returns NULL if none +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +char PS_NextWhiteSpaceChar( script_t *script ) { + if ( script->whitespace_p != script->endwhitespace_p ) { + return *script->whitespace_p++; + } //end if + else + { + return 0; + } //end else +} //end of the function PS_NextWhiteSpaceChar +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripDoubleQuotes( char *string ) { + if ( *string == '\"' ) { + strcpy( string, string + 1 ); + } //end if + if ( string[strlen( string ) - 1] == '\"' ) { + string[strlen( string ) - 1] = '\0'; + } //end if +} //end of the function StripDoubleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripSingleQuotes( char *string ) { + if ( *string == '\'' ) { + strcpy( string, string + 1 ); + } //end if + if ( string[strlen( string ) - 1] == '\'' ) { + string[strlen( string ) - 1] = '\0'; + } //end if +} //end of the function StripSingleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +long double ReadSignedFloat( script_t *script ) { + token_t token; + long double sign = 1; + + PS_ExpectAnyToken( script, &token ); + if ( !strcmp( token.string, "-" ) ) { + sign = -1; + PS_ExpectTokenType( script, TT_NUMBER, 0, &token ); + } //end if + else if ( token.type != TT_NUMBER ) { + ScriptError( script, "expected float value, found %s\n", token.string ); + } //end else if + return sign * token.floatvalue; +} //end of the function ReadSignedFloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +signed long int ReadSignedInt( script_t *script ) { + token_t token; + signed long int sign = 1; + + PS_ExpectAnyToken( script, &token ); + if ( !strcmp( token.string, "-" ) ) { + sign = -1; + PS_ExpectTokenType( script, TT_NUMBER, TT_INTEGER, &token ); + } //end if + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + ScriptError( script, "expected integer value, found %s\n", token.string ); + } //end else if + return sign * token.intvalue; +} //end of the function ReadSignedInt +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void SetScriptFlags( script_t *script, int flags ) { + script->flags = flags; +} //end of the function SetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int GetScriptFlags( script_t *script ) { + return script->flags; +} //end of the function GetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void ResetScript( script_t *script ) { + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //begin of white space + script->whitespace_p = NULL; + //end of white space + script->endwhitespace_p = NULL; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + //clear the saved token + memset( &script->token, 0, sizeof( token_t ) ); +} //end of the function ResetScript +//============================================================================ +// returns true if at the end of the script +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int EndOfScript( script_t *script ) { + return script->script_p >= script->end_p; +} //end of the function EndOfScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int NumLinesCrossed( script_t *script ) { + return script->line - script->lastline; +} //end of the function NumLinesCrossed +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int ScriptSkipTo( script_t *script, char *value ) { + int len; + char firstchar; + + firstchar = *value; + len = strlen( value ); + do + { + if ( !PS_ReadWhiteSpace( script ) ) { + return 0; + } + if ( *script->script_p == firstchar ) { + if ( !strncmp( script->script_p, value, len ) ) { + return 1; + } //end if + } //end if + script->script_p++; + } while ( 1 ); +} //end of the function ScriptSkipTo +#ifndef BOTLIB +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int FileLength( FILE *fp ) { + int pos; + int end; + + pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + end = ftell( fp ); + fseek( fp, pos, SEEK_SET ); + + return end; +} //end of the function FileLength +#endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptFile( const char *filename ) { +#ifdef BOTLIB + fileHandle_t fp; + char pathname[MAX_QPATH]; +#else + FILE *fp; +#endif + int length; + void *buffer; + script_t *script; + +#ifdef BOTLIB + if ( strlen( basefolder ) ) { + Com_sprintf( pathname, sizeof( pathname ), "%s/%s", basefolder, filename ); + } else { + Com_sprintf( pathname, sizeof( pathname ), "%s", filename ); + } + length = botimport.FS_FOpenFile( pathname, &fp, FS_READ ); + if ( !fp ) { + return NULL; + } +#else + fp = fopen( filename, "rb" ); + if ( !fp ) { + return NULL; + } + + length = FileLength( fp ); +#endif + + buffer = GetClearedMemory( sizeof( script_t ) + length + 1 ); + script = (script_t *) buffer; + memset( script, 0, sizeof( script_t ) ); + strcpy( script->filename, filename ); + script->buffer = (char *) buffer + sizeof( script_t ); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations( script, NULL ); + // +#ifdef BOTLIB + botimport.FS_Read( script->buffer, length, fp ); + botimport.FS_FCloseFile( fp ); +#else + if ( fread( script->buffer, length, 1, fp ) != 1 ) { + FreeMemory( buffer ); + script = NULL; + } //end if + fclose( fp ); +#endif + // + script->length = COM_Compress( script->buffer ); + + return script; +} //end of the function LoadScriptFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptMemory( char *ptr, int length, char *name ) { + void *buffer; + script_t *script; + + buffer = GetClearedMemory( sizeof( script_t ) + length + 1 ); + script = (script_t *) buffer; + memset( script, 0, sizeof( script_t ) ); + strcpy( script->filename, name ); + script->buffer = (char *) buffer + sizeof( script_t ); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations( script, NULL ); + // + memcpy( script->buffer, ptr, length ); + // + return script; +} //end of the function LoadScriptMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeScript( script_t *script ) { +#ifdef PUNCTABLE + if ( script->punctuationtable ) { + FreeMemory( script->punctuationtable ); + } +#endif //PUNCTABLE + FreeMemory( script ); +} //end of the function FreeScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_SetBaseFolder( char *path ) { +#ifdef BSPC + sprintf( basefolder, path ); +#else + Com_sprintf( basefolder, sizeof( basefolder ), path ); +#endif +} //end of the function PS_SetBaseFolder diff --git a/src/botlib/l_script.h b/src/botlib/l_script.h new file mode 100644 index 0000000..7cfb04f --- /dev/null +++ b/src/botlib/l_script.h @@ -0,0 +1,267 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_script.h + * + * desc: lexicographical parser + * + * + *****************************************************************************/ + +// Ridah, can't get it to compile without this +#ifndef QDECL + +// for windows fastcall option +#define QDECL +//======================= WIN32 DEFINES ================================= +#ifdef WIN32 +#undef QDECL +#define QDECL __cdecl +#endif +#endif +// done. + +//undef if binary numbers of the form 0b... or 0B... are not allowed +#define BINARYNUMBERS +//undef if not using the token.intvalue and token.floatvalue +#define NUMBERVALUE +//use dollar sign also as punctuation +#define DOLLAR + +//maximum token length +#define MAX_TOKEN 1024 +//maximum path length +#ifndef _MAX_PATH + #define _MAX_PATH MAX_QPATH +#endif + + +//script flags +#define SCFL_NOERRORS 0x0001 +#define SCFL_NOWARNINGS 0x0002 +#define SCFL_NOSTRINGWHITESPACES 0x0004 +#define SCFL_NOSTRINGESCAPECHARS 0x0008 +#define SCFL_PRIMITIVE 0x0010 +#define SCFL_NOBINARYNUMBERS 0x0020 +#define SCFL_NONUMBERVALUES 0x0040 + +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +//string sub type +//--------------- +// the length of the string +//literal sub type +//---------------- +// the ASCII code of the literal +//number sub type +//--------------- +#define TT_DECIMAL 0x0008 // decimal number +#define TT_HEX 0x0100 // hexadecimal number +#define TT_OCTAL 0x0200 // octal number +#ifdef BINARYNUMBERS +#define TT_BINARY 0x0400 // binary number +#endif //BINARYNUMBERS +#define TT_FLOAT 0x0800 // floating point number +#define TT_INTEGER 0x1000 // integer number +#define TT_LONG 0x2000 // long number +#define TT_UNSIGNED 0x4000 // unsigned number +//punctuation sub type +//-------------------- +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 +//name sub type +//------------- +// the length of the name + +//punctuation +typedef struct punctuation_s +{ + char *p; //punctuation character(s) + int n; //punctuation indication + struct punctuation_s *next; //next punctuation +} punctuation_t; + +//token +typedef struct token_s +{ + char string[MAX_TOKEN]; //available token + int type; //last read token type + int subtype; //last read token sub type +#ifdef NUMBERVALUE + unsigned long int intvalue; //integer value + long double floatvalue; //floating point value +#endif //NUMBERVALUE + char *whitespace_p; //start of white space before token + char *endwhitespace_p; //start of white space before token + int line; //line the token was on + int linescrossed; //lines crossed in white space + struct token_s *next; //next token in chain +} token_t; + +//script file +typedef struct script_s +{ + char filename[_MAX_PATH]; //file name of the script + char *buffer; //buffer containing the script + char *script_p; //current pointer in the script + char *end_p; //pointer to the end of the script + char *lastscript_p; //script pointer before reading token + char *whitespace_p; //begin of the white space + char *endwhitespace_p; //end of the white space + int length; //length of the script in bytes + int line; //current line in script + int lastline; //line before reading token + int tokenavailable; //set by UnreadLastToken + int flags; //several script flags + punctuation_t *punctuations; //the punctuations used in the script + punctuation_t **punctuationtable; + token_t token; //available token + struct script_s *next; //next script in a chain +} script_t; + +//read a token from the script +int PS_ReadToken( script_t *script, token_t *token ); +//expect a certain token +int PS_ExpectTokenString( script_t *script, char *string ); +//expect a certain token type +int PS_ExpectTokenType( script_t *script, int type, int subtype, token_t *token ); +//expect a token +int PS_ExpectAnyToken( script_t *script, token_t *token ); +//returns true when the token is available +int PS_CheckTokenString( script_t *script, char *string ); +//returns true an reads the token when a token with the given type is available +int PS_CheckTokenType( script_t *script, int type, int subtype, token_t *token ); +//skip tokens until the given token string is read +int PS_SkipUntilString( script_t *script, char *string ); +//unread the last token read from the script +void PS_UnreadLastToken( script_t *script ); +//unread the given token +void PS_UnreadToken( script_t *script, token_t *token ); +//returns the next character of the read white space, returns NULL if none +char PS_NextWhiteSpaceChar( script_t *script ); +//remove any leading and trailing double quotes from the token +void StripDoubleQuotes( char *string ); +//remove any leading and trailing single quotes from the token +void StripSingleQuotes( char *string ); +//read a possible signed integer +signed long int ReadSignedInt( script_t *script ); +//read a possible signed floating point number +long double ReadSignedFloat( script_t *script ); +//set an array with punctuations, NULL restores default C/C++ set +void SetScriptPunctuations( script_t *script, punctuation_t *p ); +//set script flags +void SetScriptFlags( script_t *script, int flags ); +//get script flags +int GetScriptFlags( script_t *script ); +//reset a script +void ResetScript( script_t *script ); +//returns true if at the end of the script +int EndOfScript( script_t *script ); +//returns a pointer to the punctuation with the given number +char *PunctuationFromNum( script_t *script, int num ); +//load a script from the given file at the given offset with the given length +script_t *LoadScriptFile( const char *filename ); +//load a script from the given memory with the given length +script_t *LoadScriptMemory( char *ptr, int length, char *name ); +//free a script +void FreeScript( script_t *script ); +//set the base folder to load files from +void PS_SetBaseFolder( char *path ); +//print a script error with filename and line number +void QDECL ScriptError( script_t *script, char *str, ... ); +//print a script warning with filename and line number +void QDECL ScriptWarning( script_t *script, char *str, ... ); + + + diff --git a/src/botlib/l_struct.c b/src/botlib/l_struct.c new file mode 100644 index 0000000..4670913 --- /dev/null +++ b/src/botlib/l_struct.c @@ -0,0 +1,507 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_struct.c + * + * desc: structure reading / writing + * + * + *****************************************************************************/ + +#ifdef BOTLIB +#include "../game/q_shared.h" +#include "../game/botlib.h" //for the include of be_interface.h +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "be_interface.h" +#endif //BOTLIB + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" +#include "l_struct.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fielddef_t *FindField( fielddef_t *defs, char *name ) { + int i; + + for ( i = 0; defs[i].name; i++ ) + { + if ( !strcmp( defs[i].name, name ) ) { + return &defs[i]; + } + } //end for + return NULL; +} //end of the function FindField +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ReadNumber( source_t *source, fielddef_t *fd, void *p ) { + token_t token; + int negative = qfalse; + long int intval, intmin = 0, intmax = 0; + double floatval; + + if ( !PC_ExpectAnyToken( source, &token ) ) { + return 0; + } + + //check for minus sign + if ( token.type == TT_PUNCTUATION ) { + if ( fd->type & FT_UNSIGNED ) { + SourceError( source, "expected unsigned value, found %s", token.string ); + return 0; + } //end if + //if not a minus sign + if ( strcmp( token.string, "-" ) ) { + SourceError( source, "unexpected punctuation %s", token.string ); + return 0; + } //end if + negative = qtrue; + //read the number + if ( !PC_ExpectAnyToken( source, &token ) ) { + return 0; + } + } //end if + //check if it is a number + if ( token.type != TT_NUMBER ) { + SourceError( source, "expected number, found %s", token.string ); + return 0; + } //end if + //check for a float value + if ( token.subtype & TT_FLOAT ) { + if ( ( fd->type & FT_TYPE ) != FT_FLOAT ) { + SourceError( source, "unexpected float" ); + return 0; + } //end if + floatval = token.floatvalue; + if ( negative ) { + floatval = -floatval; + } + if ( fd->type & FT_BOUNDED ) { + if ( floatval < fd->floatmin || floatval > fd->floatmax ) { + SourceError( source, "float out of range [%f, %f]", fd->floatmin, fd->floatmax ); + return 0; + } //end if + } //end if + *(float *) p = (float) floatval; + return 1; + } //end if + // + intval = token.intvalue; + if ( negative ) { + intval = -intval; + } + //check bounds + if ( ( fd->type & FT_TYPE ) == FT_CHAR ) { + if ( fd->type & FT_UNSIGNED ) { + intmin = 0; intmax = 255; + } else {intmin = -128; intmax = 127;} + } //end if + if ( ( fd->type & FT_TYPE ) == FT_INT ) { + if ( fd->type & FT_UNSIGNED ) { + intmin = 0; intmax = 65535; + } else {intmin = -32768; intmax = 32767;} + } //end else if + if ( ( fd->type & FT_TYPE ) == FT_CHAR || ( fd->type & FT_TYPE ) == FT_INT ) { + if ( fd->type & FT_BOUNDED ) { + intmin = Maximum( intmin, fd->floatmin ); + intmax = Minimum( intmax, fd->floatmax ); + } //end if + if ( intval < intmin || intval > intmax ) { + SourceError( source, "value %d out of range [%d, %d]", intval, intmin, intmax ); + return 0; + } //end if + } //end if + else if ( ( fd->type & FT_TYPE ) == FT_FLOAT ) { + if ( fd->type & FT_BOUNDED ) { + if ( intval < fd->floatmin || intval > fd->floatmax ) { + SourceError( source, "value %d out of range [%f, %f]", intval, fd->floatmin, fd->floatmax ); + return 0; + } //end if + } //end if + } //end else if + //store the value + if ( ( fd->type & FT_TYPE ) == FT_CHAR ) { + if ( fd->type & FT_UNSIGNED ) { + *(unsigned char *) p = (unsigned char) intval; + } else { *(char *) p = (char) intval;} + } //end if + else if ( ( fd->type & FT_TYPE ) == FT_INT ) { + if ( fd->type & FT_UNSIGNED ) { + *(unsigned int *) p = (unsigned int) intval; + } else { *(int *) p = (int) intval;} + } //end else + else if ( ( fd->type & FT_TYPE ) == FT_FLOAT ) { + *(float *) p = (float) intval; + } //end else + return 1; +} //end of the function ReadNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ReadChar( source_t *source, fielddef_t *fd, void *p ) { + token_t token; + + if ( !PC_ExpectAnyToken( source, &token ) ) { + return 0; + } + + //take literals into account + if ( token.type == TT_LITERAL ) { + StripSingleQuotes( token.string ); + *(char *) p = token.string[0]; + } //end if + else + { + PC_UnreadLastToken( source ); + if ( !ReadNumber( source, fd, p ) ) { + return 0; + } + } //end if + return 1; +} //end of the function ReadChar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadString( source_t *source, fielddef_t *fd, void *p ) { + token_t token; + + if ( !PC_ExpectTokenType( source, TT_STRING, 0, &token ) ) { + return 0; + } + //remove the double quotes + StripDoubleQuotes( token.string ); + //copy the string + strncpy( (char *) p, token.string, MAX_STRINGFIELD ); + //make sure the string is closed with a zero + ( (char *)p )[MAX_STRINGFIELD - 1] = '\0'; + // + return 1; +} //end of the function ReadString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadStructure( source_t *source, structdef_t *def, char *structure ) { + token_t token; + fielddef_t *fd; + void *p; + int num; + + if ( !PC_ExpectTokenString( source, "{" ) ) { + return 0; + } + while ( 1 ) + { + if ( !PC_ExpectAnyToken( source, &token ) ) { + return qfalse; + } + //if end of structure + if ( !strcmp( token.string, "}" ) ) { + break; + } + //find the field with the name + fd = FindField( def->fields, token.string ); + if ( !fd ) { + SourceError( source, "unknown structure field %s", token.string ); + return qfalse; + } //end if + if ( fd->type & FT_ARRAY ) { + num = fd->maxarray; + if ( !PC_ExpectTokenString( source, "{" ) ) { + return qfalse; + } + } //end if + else + { + num = 1; + } //end else + p = ( void * )( structure + fd->offset ); + while ( num-- > 0 ) + { + if ( fd->type & FT_ARRAY ) { + if ( PC_CheckTokenString( source, "}" ) ) { + break; + } + } //end if + switch ( fd->type & FT_TYPE ) + { + case FT_CHAR: + { + if ( !ReadChar( source, fd, p ) ) { + return qfalse; + } + p = (char *) p + sizeof( char ); + break; + } //end case + case FT_INT: + { + if ( !ReadNumber( source, fd, p ) ) { + return qfalse; + } + p = (char *) p + sizeof( int ); + break; + } //end case + case FT_FLOAT: + { + if ( !ReadNumber( source, fd, p ) ) { + return qfalse; + } + p = (char *) p + sizeof( float ); + break; + } //end case + case FT_STRING: + { + if ( !ReadString( source, fd, p ) ) { + return qfalse; + } + p = (char *) p + MAX_STRINGFIELD; + break; + } //end case + case FT_STRUCT: + { + if ( !fd->substruct ) { + SourceError( source, "BUG: no sub structure defined" ); + return qfalse; + } //end if + ReadStructure( source, fd->substruct, (char *) p ); + p = (char *) p + fd->substruct->size; + break; + } //end case + } //end switch + if ( fd->type & FT_ARRAY ) { + if ( !PC_ExpectAnyToken( source, &token ) ) { + return qfalse; + } + if ( !strcmp( token.string, "}" ) ) { + break; + } + if ( strcmp( token.string, "," ) ) { + SourceError( source, "expected a comma, found %s", token.string ); + return qfalse; + } //end if + } //end if + } //end while + } //end while + return qtrue; +} //end of the function ReadStructure +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteIndent( FILE *fp, int indent ) { + while ( indent-- > 0 ) + { + if ( fprintf( fp, "\t" ) < 0 ) { + return qfalse; + } + } //end while + return qtrue; +} //end of the function WriteIndent +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteFloat( FILE *fp, float value ) { + char buf[128]; + int l; + + sprintf( buf, "%f", value ); + l = strlen( buf ); + //strip any trailing zeros + while ( l-- > 1 ) + { + if ( buf[l] != '0' && buf[l] != '.' ) { + break; + } + if ( buf[l] == '.' ) { + buf[l] = 0; + break; + } //end if + buf[l] = 0; + } //end while + //write the float to file + if ( fprintf( fp, "%s", buf ) < 0 ) { + return 0; + } + return 1; +} //end of the function WriteFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteStructWithIndent( FILE *fp, structdef_t *def, char *structure, int indent ) { + int i, num; + void *p; + fielddef_t *fd; + + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fprintf( fp, "{\r\n" ) < 0 ) { + return qfalse; + } + + indent++; + for ( i = 0; def->fields[i].name; i++ ) + { + fd = &def->fields[i]; + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fprintf( fp, "%s\t", fd->name ) < 0 ) { + return qfalse; + } + p = ( void * )( structure + fd->offset ); + if ( fd->type & FT_ARRAY ) { + num = fd->maxarray; + if ( fprintf( fp, "{" ) < 0 ) { + return qfalse; + } + } //end if + else + { + num = 1; + } //end else + while ( num-- > 0 ) + { + switch ( fd->type & FT_TYPE ) + { + case FT_CHAR: + { + if ( fprintf( fp, "%d", *(char *) p ) < 0 ) { + return qfalse; + } + p = (char *) p + sizeof( char ); + break; + } //end case + case FT_INT: + { + if ( fprintf( fp, "%d", *(int *) p ) < 0 ) { + return qfalse; + } + p = (char *) p + sizeof( int ); + break; + } //end case + case FT_FLOAT: + { + if ( !WriteFloat( fp, *(float *)p ) ) { + return qfalse; + } + p = (char *) p + sizeof( float ); + break; + } //end case + case FT_STRING: + { + if ( fprintf( fp, "\"%s\"", (char *) p ) < 0 ) { + return qfalse; + } + p = (char *) p + MAX_STRINGFIELD; + break; + } //end case + case FT_STRUCT: + { + if ( !WriteStructWithIndent( fp, fd->substruct, structure, indent ) ) { + return qfalse; + } + p = (char *) p + fd->substruct->size; + break; + } //end case + } //end switch + if ( fd->type & FT_ARRAY ) { + if ( num > 0 ) { + if ( fprintf( fp, "," ) < 0 ) { + return qfalse; + } + } //end if + else + { + if ( fprintf( fp, "}" ) < 0 ) { + return qfalse; + } + } //end else + } //end if + } //end while + if ( fprintf( fp, "\r\n" ) < 0 ) { + return qfalse; + } + } //end for + indent--; + + if ( !WriteIndent( fp, indent ) ) { + return qfalse; + } + if ( fprintf( fp, "}\r\n" ) < 0 ) { + return qfalse; + } + return qtrue; +} //end of the function WriteStructWithIndent +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteStructure( FILE *fp, structdef_t *def, char *structure ) { + return WriteStructWithIndent( fp, def, structure, 0 ); +} //end of the function WriteStructure + diff --git a/src/botlib/l_struct.h b/src/botlib/l_struct.h new file mode 100644 index 0000000..68fb9ae --- /dev/null +++ b/src/botlib/l_struct.h @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_struct.h + * + * desc: structure reading/writing + * + * + *****************************************************************************/ + + +#define MAX_STRINGFIELD 80 +//field types +#define FT_CHAR 1 // char +#define FT_INT 2 // int +#define FT_FLOAT 3 // float +#define FT_STRING 4 // char [MAX_STRINGFIELD] +#define FT_STRUCT 6 // struct (sub structure) +//type only mask +#define FT_TYPE 0x00FF // only type, clear subtype +//sub types +#define FT_ARRAY 0x0100 // array of type +#define FT_BOUNDED 0x0200 // bounded value +#define FT_UNSIGNED 0x0400 + +//structure field definition +typedef struct fielddef_s +{ + char *name; //name of the field + int offset; //offset in the structure + int type; //type of the field + //type specific fields + int maxarray; //maximum array size + float floatmin, floatmax; //float min and max + struct structdef_s *substruct; //sub structure +} fielddef_t; + +//structure definition +typedef struct structdef_s +{ + int size; + fielddef_t *fields; +} structdef_t; + +//read a structure from a script +int ReadStructure( source_t *source, structdef_t *def, char *structure ); +//write a structure to a file +int WriteStructure( FILE *fp, structdef_t *def, char *structure ); +//writes indents +int WriteIndent( FILE *fp, int indent ); +//writes a float without traling zeros +int WriteFloat( FILE *fp, float value ); + + diff --git a/src/botlib/l_utils.h b/src/botlib/l_utils.h new file mode 100644 index 0000000..7f8106e --- /dev/null +++ b/src/botlib/l_utils.h @@ -0,0 +1,41 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_util.h + * + * desc: utils + * + * + *****************************************************************************/ + +#define Vector2Angles( v,a ) vectoangles( v,a ) +#define MAX_PATH MAX_QPATH +#define Maximum( x,y ) ( x > y ? x : y ) +#define Minimum( x,y ) ( x < y ? x : y ) diff --git a/src/bspc/_files.c b/src/bspc/_files.c new file mode 100644 index 0000000..710f8a8 --- /dev/null +++ b/src/bspc/_files.c @@ -0,0 +1,90 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: _files.c +// Function: +// Programmer: Mr Elusive +// Last update: 1999-12-02 +// Tab Size: 4 +//=========================================================================== + +/* + +aas_areamerging.c //AAS area merging +aas_cfg.c //AAS configuration for different games +aas_create.c //AAS creating +aas_edgemelting.c //AAS edge melting +aas_facemerging.c //AAS face merging +aas_file.c //AAS file writing +aas_gsubdiv.c //AAS gravitational and ladder subdivision +aas_map.c //AAS map brush creation +aas_prunenodes.c //AAS node pruning +aas_store.c //AAS file storing + +map.c //map file loading and writing +map_hl.c //Half-Life map loading +map_q1.c //Quake1 map loading +map_q2.c //Quake2 map loading +map_q3.c //Quake3 map loading +map_sin.c //Sin map loading +tree.c //BSP tree management + node pruning (*) +brushbsp.c //brush bsp creation (*) +portals.c //BSP portal creation and leaf filling (*) +csg.c //Constructive Solid Geometry brush chopping (*) +leakfile.c //leak file writing (*) +textures.c //Quake2 BSP textures (*) + +l_bsp_ent.c //BSP entity parsing +l_bsp_q1.c //Quake1 BSP loading and writing +l_bsp_q2.c //Quake2 BSP loading and writing +l_bsp_q3.c //Quake2 BSP loading and writing +l_bsp_sin.c //Sin BSP loading and writing +l_cmd.c //cmd library +l_log.c //log file library +l_math.c //math library +l_mem.c //memory management library +l_poly.c //polygon (winding) library +l_script.c //script file parsing library +l_threads.c //multi-threading library +l_utils.c //utility library +l_qfiles.c //loading of quake files + +gldraw.c //GL drawing (*) +glfile.c //GL file writing (*) +nodraw.c //no draw module (*) + +bspc.c //BSPC Win32 console version +winbspc.c //WinBSPC Win32 GUI version +win32_terminal.c //Win32 terminal output +win32_qfiles.c //Win32 game file management (also .pak .sin) +win32_font.c //Win32 fonts +win32_folder.c //Win32 folder dialogs + +*/ diff --git a/src/bspc/aas_areamerging.c b/src/bspc/aas_areamerging.c new file mode 100644 index 0000000..33a5c27 --- /dev/null +++ b/src/bspc/aas_areamerging.c @@ -0,0 +1,424 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_areamerging.c +// Function: Merging of Areas +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "../botlib/aasfile.h" +#include "aas_create.h" +#include "aas_store.h" + +#define CONVEX_EPSILON 0.3 + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_RefreshMergedTree_r( tmp_node_t *tmpnode ) { + tmp_area_t *tmparea; + + //if this is a solid leaf + if ( !tmpnode ) { + return NULL; + } + //if this is an area leaf + if ( tmpnode->tmparea ) { + tmparea = tmpnode->tmparea; + while ( tmparea->mergedarea ) tmparea = tmparea->mergedarea; + tmpnode->tmparea = tmparea; + return tmpnode; + } //end if + //do the children recursively + tmpnode->children[0] = AAS_RefreshMergedTree_r( tmpnode->children[0] ); + tmpnode->children[1] = AAS_RefreshMergedTree_r( tmpnode->children[1] ); + return tmpnode; +} //end of the function AAS_RefreshMergedTree_r +//=========================================================================== +// returns true if the two given faces would create a non-convex area at +// the given sides, otherwise false is returned +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NonConvex( tmp_face_t *face1, tmp_face_t *face2, int side1, int side2 ) { + int i; + winding_t *w1, *w2; + plane_t *plane1, *plane2; + + w1 = face1->winding; + w2 = face2->winding; + + plane1 = &mapplanes[face1->planenum ^ side1]; + plane2 = &mapplanes[face2->planenum ^ side2]; + + //check if one of the points of face1 is at the back of the plane of face2 + for ( i = 0; i < w1->numpoints; i++ ) + { + if ( DotProduct( plane2->normal, w1->p[i] ) - plane2->dist < -CONVEX_EPSILON ) { + return true; + } + } //end for + //check if one of the points of face2 is at the back of the plane of face1 + for ( i = 0; i < w2->numpoints; i++ ) + { + if ( DotProduct( plane1->normal, w2->p[i] ) - plane1->dist < -CONVEX_EPSILON ) { + return true; + } + } //end for + + return false; +} //end of the function NonConvex +//=========================================================================== +// try to merge the areas at both sides of the given face +// +// Parameter: seperatingface : face that seperates two areas +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TryMergeFaceAreas( tmp_face_t *seperatingface ) { + int side1, side2, area1faceflags, area2faceflags; + tmp_area_t *tmparea1, *tmparea2, *newarea; + tmp_face_t *face1, *face2, *nextface1, *nextface2; + + tmparea1 = seperatingface->frontarea; + tmparea2 = seperatingface->backarea; + + //areas must have the same presence type + if ( tmparea1->presencetype != tmparea2->presencetype ) { + return false; + } + //areas must have the same area contents + if ( tmparea1->contents != tmparea2->contents ) { + return false; + } + //areas must have the same bsp model inside (or both none) + if ( tmparea1->modelnum != tmparea2->modelnum ) { + return false; + } + + area1faceflags = 0; + area2faceflags = 0; + for ( face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side1] ) + { + side1 = ( face1->frontarea != tmparea1 ); + //debug: check if the area belongs to the area + if ( face1->frontarea != tmparea1 && + face1->backarea != tmparea1 ) { + Error( "face does not belong to area1" ); + } + //just continue if the face is seperating the two areas + //NOTE: a result of this is that ground and gap areas can + // be merged if the seperating face is the gap + if ( ( face1->frontarea == tmparea1 && + face1->backarea == tmparea2 ) || + ( face1->frontarea == tmparea2 && + face1->backarea == tmparea1 ) ) { + continue; + } + //get area1 face flags + area1faceflags |= face1->faceflags; + if ( AAS_GapFace( face1, side1 ) ) { + area1faceflags |= FACE_GAP; + } + // + for ( face2 = tmparea2->tmpfaces; face2; face2 = face2->next[side2] ) + { + side2 = ( face2->frontarea != tmparea2 ); + //debug: check if the area belongs to the area + if ( face2->frontarea != tmparea2 && + face2->backarea != tmparea2 ) { + Error( "face does not belong to area2" ); + } + //just continue if the face is seperating the two areas + //NOTE: a result of this is that ground and gap areas can + // be merged if the seperating face is the gap + if ( ( face2->frontarea == tmparea1 && + face2->backarea == tmparea2 ) || + ( face2->frontarea == tmparea2 && + face2->backarea == tmparea1 ) ) { + continue; + } + //get area2 face flags + area2faceflags |= face2->faceflags; + if ( AAS_GapFace( face2, side2 ) ) { + area2faceflags |= FACE_GAP; + } + //if the two faces would create a non-convex area + if ( NonConvex( face1, face2, side1, side2 ) ) { + return false; + } + } //end for + } //end for + //if one area has gap faces (that aren't seperating the two areas) + //and the other has ground faces (that aren't seperating the two areas), + //the areas can't be merged + if ( ( ( area1faceflags & FACE_GROUND ) && ( area2faceflags & FACE_GAP ) ) || + ( ( area2faceflags & FACE_GROUND ) && ( area1faceflags & FACE_GAP ) ) ) { +// Log_Print(" can't merge: ground/gap\n"); + return false; + } //end if + +// Log_Print("merged area %d & %d to %d with %d faces\n", tmparea1->areanum, tmparea2->areanum, newarea->areanum, numfaces); +// return false; + // + //AAS_CheckArea(tmparea1); + //AAS_CheckArea(tmparea2); + //create the new area + newarea = AAS_AllocTmpArea(); + newarea->presencetype = tmparea1->presencetype; + newarea->contents = tmparea1->contents; + newarea->modelnum = tmparea1->modelnum; + newarea->tmpfaces = NULL; + + //add all the faces (except the seperating ones) from the first area + //to the new area + for ( face1 = tmparea1->tmpfaces; face1; face1 = nextface1 ) + { + side1 = ( face1->frontarea != tmparea1 ); + nextface1 = face1->next[side1]; + //don't add seperating faces + if ( ( face1->frontarea == tmparea1 && + face1->backarea == tmparea2 ) || + ( face1->frontarea == tmparea2 && + face1->backarea == tmparea1 ) ) { + continue; + } //end if + // + AAS_RemoveFaceFromArea( face1, tmparea1 ); + AAS_AddFaceSideToArea( face1, side1, newarea ); + } //end for + //add all the faces (except the seperating ones) from the second area + //to the new area + for ( face2 = tmparea2->tmpfaces; face2; face2 = nextface2 ) + { + side2 = ( face2->frontarea != tmparea2 ); + nextface2 = face2->next[side2]; + //don't add seperating faces + if ( ( face2->frontarea == tmparea1 && + face2->backarea == tmparea2 ) || + ( face2->frontarea == tmparea2 && + face2->backarea == tmparea1 ) ) { + continue; + } //end if + // + AAS_RemoveFaceFromArea( face2, tmparea2 ); + AAS_AddFaceSideToArea( face2, side2, newarea ); + } //end for + //free all shared faces + for ( face1 = tmparea1->tmpfaces; face1; face1 = nextface1 ) + { + side1 = ( face1->frontarea != tmparea1 ); + nextface1 = face1->next[side1]; + // + AAS_RemoveFaceFromArea( face1, face1->frontarea ); + AAS_RemoveFaceFromArea( face1, face1->backarea ); + AAS_FreeTmpFace( face1 ); + } //end for + // + tmparea1->mergedarea = newarea; + tmparea1->invalid = true; + tmparea2->mergedarea = newarea; + tmparea2->invalid = true; + // + AAS_CheckArea( newarea ); + AAS_FlipAreaFaces( newarea ); +// Log_Print("merged area %d & %d to %d with %d faces\n", tmparea1->areanum, tmparea2->areanum, newarea->areanum); + return true; +} //end of the function AAS_TryMergeFaceAreas +//=========================================================================== +// try to merge areas +// merged areas are added to the end of the convex area list so merging +// will be tried for those areas as well +// +// Parameter: - +// Returns: - +// Changes Globals: tmpaasworld +//=========================================================================== +/* +void AAS_MergeAreas(void) +{ + int side, nummerges; + tmp_area_t *tmparea, *othertmparea; + tmp_face_t *face; + + nummerges = 0; + Log_Write("AAS_MergeAreas\r\n"); + qprintf("%6d areas merged", 1); + //first merge grounded areas only + //NOTE: this is useless because the area settings aren't available yet + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { +// Log_Print("checking area %d\n", i); + //if the area is invalid + if (tmparea->invalid) + { +// Log_Print(" area invalid\n"); + continue; + } //end if + // +// if (!(tmparea->settings->areaflags & AREA_GROUNDED)) continue; + // + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = (face->frontarea != tmparea); + //if the face has both a front and back area + if (face->frontarea && face->backarea) + { + // + if (face->frontarea == tmparea) othertmparea = face->backarea; + else othertmparea = face->frontarea; +// if (!(othertmparea->settings->areaflags & AREA_GROUNDED)) continue; +// Log_Print(" checking area %d with %d\n", face->frontarea, face->backarea); + if (AAS_TryMergeFaceAreas(face)) + { + qprintf("\r%6d", ++nummerges); + break; + } //end if + } //end if + } //end for + } //end for + //merge all areas + for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) + { +// Log_Print("checking area %d\n", i); + //if the area is invalid + if (tmparea->invalid) + { +// Log_Print(" area invalid\n"); + continue; + } //end if + // + for (face = tmparea->tmpfaces; face; face = face->next[side]) + { + side = (face->frontarea != tmparea); + //if the face has both a front and back area + if (face->frontarea && face->backarea) + { +// Log_Print(" checking area %d with %d\n", face->frontarea, face->backarea); + if (AAS_TryMergeFaceAreas(face)) + { + qprintf("\r%6d", ++nummerges); + break; + } //end if + } //end if + } //end for + } //end for + Log_Print("\r%6d areas merged\n", nummerges); + //refresh the merged tree + AAS_RefreshMergedTree_r(tmpaasworld.nodes); +} //end of the function AAS_MergeAreas*/ + +int AAS_GroundArea( tmp_area_t *tmparea ) { + tmp_face_t *face; + int side; + + for ( face = tmparea->tmpfaces; face; face = face->next[side] ) + { + side = ( face->frontarea != tmparea ); + if ( face->faceflags & FACE_GROUND ) { + return true; + } + } //end for + return false; +} //end of the function AAS_GroundArea + +void AAS_MergeAreas( void ) { + int side, nummerges, merges, groundfirst; + tmp_area_t *tmparea, *othertmparea; + tmp_face_t *face; + + nummerges = 0; + Log_Write( "AAS_MergeAreas\r\n" ); + qprintf( "%6d areas merged", 1 ); + // + groundfirst = true; + //for (i = 0; i < 4 || merges; i++) + while ( 1 ) + { + //if (i < 2) groundfirst = true; + //else groundfirst = false; + // + merges = 0; + //first merge grounded areas only + for ( tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next ) + { + //if the area is invalid + if ( tmparea->invalid ) { + continue; + } //end if + // + if ( groundfirst ) { + if ( !AAS_GroundArea( tmparea ) ) { + continue; + } + } //end if + // + for ( face = tmparea->tmpfaces; face; face = face->next[side] ) + { + side = ( face->frontarea != tmparea ); + //if the face has both a front and back area + if ( face->frontarea && face->backarea ) { + // + if ( face->frontarea == tmparea ) { + othertmparea = face->backarea; + } else { othertmparea = face->frontarea;} + // + if ( groundfirst ) { + if ( !AAS_GroundArea( othertmparea ) ) { + continue; + } + } //end if + if ( AAS_TryMergeFaceAreas( face ) ) { + qprintf( "\r%6d", ++nummerges ); + merges++; + break; + } //end if + } //end if + } //end for + } //end for + if ( !merges ) { + if ( groundfirst ) { + groundfirst = false; + } else { break;} + } //end if + } //end for + qprintf( "\n" ); + Log_Write( "%6d areas merged\r\n", nummerges ); + //refresh the merged tree + AAS_RefreshMergedTree_r( tmpaasworld.nodes ); +} //end of the function AAS_MergeAreas diff --git a/src/bspc/aas_areamerging.h b/src/bspc/aas_areamerging.h new file mode 100644 index 0000000..9fa6968 --- /dev/null +++ b/src/bspc/aas_areamerging.h @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_areamerging.h +// Function: Merging of Areas +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + + +void AAS_MergeAreas( void ); + diff --git a/src/bspc/aas_cfg.c b/src/bspc/aas_cfg.c new file mode 100644 index 0000000..3a48096 --- /dev/null +++ b/src/bspc/aas_cfg.c @@ -0,0 +1,312 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: cfg.c +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "float.h" +#include "..\botlib\aasfile.h" +#include "aas_store.h" +#include "aas_cfg.h" +#include "../botlib/l_precomp.h" +#include "../botlib/l_struct.h" +#include "../botlib/l_libvar.h" + +/////////////////////////////////// +extern void LibVarSet( char *var_name, char *value ); +/////////////////////////////////// + +//structure field offsets +#define BBOX_OFS( x ) (int)&( ( (aas_bbox_t *)0 )->x ) +#define CFG_OFS( x ) (int)&( ( (cfg_t *)0 )->x ) + +//bounding box definition +fielddef_t bbox_fields[] = +{ + {"presencetype", BBOX_OFS( presencetype ), FT_INT}, + {"flags", BBOX_OFS( flags ), FT_INT}, + {"mins", BBOX_OFS( mins ), FT_FLOAT | FT_ARRAY, 3}, + {"maxs", BBOX_OFS( maxs ), FT_FLOAT | FT_ARRAY, 3}, + {NULL, 0, 0, 0} +}; + +fielddef_t cfg_fields[] = +{ + {"phys_gravitydirection", CFG_OFS( phys_gravitydirection ), FT_FLOAT | FT_ARRAY, 3}, + {"phys_friction", CFG_OFS( phys_friction ), FT_FLOAT}, + {"phys_stopspeed", CFG_OFS( phys_stopspeed ), FT_FLOAT}, + {"phys_gravity", CFG_OFS( phys_gravity ), FT_FLOAT}, + {"phys_waterfriction", CFG_OFS( phys_waterfriction ), FT_FLOAT}, + {"phys_watergravity", CFG_OFS( phys_watergravity ), FT_FLOAT}, + {"phys_maxvelocity", CFG_OFS( phys_maxvelocity ), FT_FLOAT}, + {"phys_maxwalkvelocity", CFG_OFS( phys_maxwalkvelocity ), FT_FLOAT}, + {"phys_maxcrouchvelocity", CFG_OFS( phys_maxcrouchvelocity ), FT_FLOAT}, + {"phys_maxswimvelocity", CFG_OFS( phys_maxswimvelocity ), FT_FLOAT}, + {"phys_walkaccelerate", CFG_OFS( phys_walkaccelerate ), FT_FLOAT}, + {"phys_airaccelerate", CFG_OFS( phys_airaccelerate ), FT_FLOAT}, + {"phys_swimaccelerate", CFG_OFS( phys_swimaccelerate ), FT_FLOAT}, + {"phys_maxstep", CFG_OFS( phys_maxstep ), FT_FLOAT}, + {"phys_maxsteepness", CFG_OFS( phys_maxsteepness ), FT_FLOAT}, + {"phys_maxwaterjump", CFG_OFS( phys_maxwaterjump ), FT_FLOAT}, +// Ridah, calculated from velocity and gravity +// {"sv_maxbarrier", CFG_OFS(sv_maxbarrier), FT_FLOAT}, +// {"sv_jumpvel", CFG_OFS(sv_jumpvel), FT_FLOAT}, + {"phys_maxbarrier", CFG_OFS( phys_maxbarrier ), FT_FLOAT}, + {"phys_jumpvel", CFG_OFS( phys_jumpvel ), FT_FLOAT}, + {"phys_falldelta5", CFG_OFS( phys_falldelta5 ), FT_FLOAT}, + {"phys_falldelta10", CFG_OFS( phys_falldelta10 ), FT_FLOAT}, + {"rs_waterjump", CFG_OFS( rs_waterjump ), FT_FLOAT}, + {"rs_teleport", CFG_OFS( rs_teleport ), FT_FLOAT}, + {"rs_barrierjump", CFG_OFS( rs_barrierjump ), FT_FLOAT}, + {"rs_startcrouch", CFG_OFS( rs_startcrouch ), FT_FLOAT}, + {"rs_startgrapple", CFG_OFS( rs_startgrapple ), FT_FLOAT}, + {"rs_startwalkoffledge", CFG_OFS( rs_startwalkoffledge ), FT_FLOAT}, + {"rs_startjump", CFG_OFS( rs_startjump ), FT_FLOAT}, + {"rs_rocketjump", CFG_OFS( rs_rocketjump ), FT_FLOAT}, + {"rs_bfgjump", CFG_OFS( rs_bfgjump ), FT_FLOAT}, + {"rs_jumppad", CFG_OFS( rs_jumppad ), FT_FLOAT}, + {"rs_aircontrolledjumppad", CFG_OFS( rs_aircontrolledjumppad ), FT_FLOAT}, + {"rs_funcbob", CFG_OFS( rs_funcbob ), FT_FLOAT}, + {"rs_startelevator", CFG_OFS( rs_startelevator ), FT_FLOAT}, + {"rs_falldamage5", CFG_OFS( rs_falldamage5 ), FT_FLOAT}, + {"rs_falldamage10", CFG_OFS( rs_falldamage10 ), FT_FLOAT}, + {"rs_maxjumpfallheight", CFG_OFS( rs_maxjumpfallheight ), FT_FLOAT}, + {NULL, 0, 0, 0} +}; + +structdef_t bbox_struct = +{ + sizeof( aas_bbox_t ), bbox_fields +}; +structdef_t cfg_struct = +{ + sizeof( cfg_t ), cfg_fields +}; + +//global cfg +cfg_t cfg; + +#if 0 +//=========================================================================== +// the default Q3A configuration +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DefaultCfg( void ) { + int i; + + // default all float values to infinite + for ( i = 0; cfg_fields[i].name; i++ ) + { + if ( ( cfg_fields[i].type & FT_TYPE ) == FT_FLOAT ) { + *( float * )( ( (char*)&cfg ) + cfg_fields[i].offset ) = FLT_MAX; + } + } //end for + // + cfg.numbboxes = 2; + //bbox 0 + cfg.bboxes[0].presencetype = PRESENCE_NORMAL; + cfg.bboxes[0].flags = 0; + cfg.bboxes[0].mins[0] = -18; + cfg.bboxes[0].mins[1] = -18; + cfg.bboxes[0].mins[2] = -24; + cfg.bboxes[0].maxs[0] = 18; + cfg.bboxes[0].maxs[1] = 18; + cfg.bboxes[0].maxs[2] = 48; + //bbox 1 + cfg.bboxes[1].presencetype = PRESENCE_CROUCH; + cfg.bboxes[1].flags = 1; + cfg.bboxes[1].mins[0] = -18; + cfg.bboxes[1].mins[1] = -18; + cfg.bboxes[1].mins[2] = -24; + cfg.bboxes[1].maxs[0] = 18; + cfg.bboxes[1].maxs[1] = 18; + cfg.bboxes[1].maxs[2] = 24; + // + cfg.allpresencetypes = PRESENCE_NORMAL | PRESENCE_CROUCH; + cfg.phys_gravitydirection[0] = 0; + cfg.phys_gravitydirection[1] = 0; + cfg.phys_gravitydirection[2] = -1; + cfg.phys_maxsteepness = 0.7; + +// cfg.phys_maxbarrier = -999;//32; // RIDAH: this is calculated at run-time now, from the gravity and jump velocity settings +} //end of the function DefaultCfg +#else +//=========================================================================== +// the default Q3A configuration +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DefaultCfg( void ) { + int i; + + // default all float values to infinite + for ( i = 0; cfg_fields[i].name; i++ ) + { + if ( ( cfg_fields[i].type & FT_TYPE ) == FT_FLOAT ) { + *( float * )( ( (char*)&cfg ) + cfg_fields[i].offset ) = FLT_MAX; + } + } //end for + // + cfg.numbboxes = 2; + //bbox 0 + cfg.bboxes[0].presencetype = PRESENCE_NORMAL; + cfg.bboxes[0].flags = 0; + cfg.bboxes[0].mins[0] = -15; + cfg.bboxes[0].mins[1] = -15; + cfg.bboxes[0].mins[2] = -24; + cfg.bboxes[0].maxs[0] = 15; + cfg.bboxes[0].maxs[1] = 15; + cfg.bboxes[0].maxs[2] = 32; + //bbox 1 + cfg.bboxes[1].presencetype = PRESENCE_CROUCH; + cfg.bboxes[1].flags = 1; + cfg.bboxes[1].mins[0] = -15; + cfg.bboxes[1].mins[1] = -15; + cfg.bboxes[1].mins[2] = -24; + cfg.bboxes[1].maxs[0] = 15; + cfg.bboxes[1].maxs[1] = 15; + cfg.bboxes[1].maxs[2] = 16; + // + cfg.allpresencetypes = PRESENCE_NORMAL | PRESENCE_CROUCH; + cfg.phys_gravitydirection[0] = 0; + cfg.phys_gravitydirection[1] = 0; + cfg.phys_gravitydirection[2] = -1; + cfg.phys_maxsteepness = 0.7; + +// cfg.phys_maxbarrier = -999;//32; // RIDAH: this is calculated at run-time now, from the gravity and jump velocity settings +} //end of the function DefaultCfg +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char * QDECL va( char *format, ... ) { + va_list argptr; + static char string[2][32000]; // in case va is called by nested functions + static int index = 0; + char *buf; + + buf = string[index & 1]; + index++; + + va_start( argptr, format ); + vsprintf( buf, format,argptr ); + va_end( argptr ); + + return buf; +} //end of the function va +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetCfgLibVars( void ) { + int i; + float value; + + for ( i = 0; cfg_fields[i].name; i++ ) + { + if ( ( cfg_fields[i].type & FT_TYPE ) == FT_FLOAT ) { + value = *( float * )( ( (char*)&cfg ) + cfg_fields[i].offset ); + if ( value != FLT_MAX ) { + LibVarSet( cfg_fields[i].name, va( "%f", value ) ); + } //end if + } //end if + } //end for +} //end of the function SetCfgLibVars +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int LoadCfgFile( char *filename ) { + source_t *source; + token_t token; + int settingsdefined; + + source = LoadSourceFile( filename ); + if ( !source ) { + Log_Print( "couldn't open cfg file %s\n", filename ); + return false; + } //end if + + settingsdefined = false; + memset( &cfg, 0, sizeof( cfg_t ) ); + + while ( PC_ReadToken( source, &token ) ) + { + if ( !stricmp( token.string, "bbox" ) ) { + if ( cfg.numbboxes >= AAS_MAX_BBOXES ) { + SourceError( source, "too many bounding box volumes defined" ); + } //end if + if ( !ReadStructure( source, &bbox_struct, (char *) &cfg.bboxes[cfg.numbboxes] ) ) { + FreeSource( source ); + return false; + } //end if + cfg.allpresencetypes |= cfg.bboxes[cfg.numbboxes].presencetype; + cfg.numbboxes++; + } //end if + else if ( !stricmp( token.string, "settings" ) ) { + if ( settingsdefined ) { + SourceWarning( source, "settings already defined\n" ); + } //end if + settingsdefined = true; + if ( !ReadStructure( source, &cfg_struct, (char *) &cfg ) ) { + FreeSource( source ); + return false; + } //end if + } //end else if + } //end while + if ( VectorLength( cfg.phys_gravitydirection ) < 0.9 || VectorLength( cfg.phys_gravitydirection ) > 1.1 ) { + SourceError( source, "invalid gravity direction specified" ); + } //end if + if ( cfg.numbboxes <= 0 ) { + SourceError( source, "no bounding volumes specified" ); + } //end if + FreeSource( source ); + SetCfgLibVars(); + Log_Print( "using cfg file %s\n", filename ); + return true; +} //end of the function LoadCfgFile diff --git a/src/bspc/aas_cfg.h b/src/bspc/aas_cfg.h new file mode 100644 index 0000000..d71a77e --- /dev/null +++ b/src/bspc/aas_cfg.h @@ -0,0 +1,88 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: cfg.h +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#define BBOXFL_GROUNDED 1 //bounding box only valid when on ground +#define BBOXFL_NOTGROUNDED 2 //bounding box only valid when NOT on ground + +typedef struct cfg_s +{ + int numbboxes; //number of bounding boxes + aas_bbox_t bboxes[AAS_MAX_BBOXES]; //all the bounding boxes + int allpresencetypes; //or of all presence types + // aas settings + vec3_t phys_gravitydirection; + float phys_friction; + float phys_stopspeed; + float phys_gravity; + float phys_waterfriction; + float phys_watergravity; + float phys_maxvelocity; + float phys_maxwalkvelocity; + float phys_maxcrouchvelocity; + float phys_maxswimvelocity; + float phys_walkaccelerate; + float phys_airaccelerate; + float phys_swimaccelerate; + float phys_maxstep; + float phys_maxsteepness; + float phys_maxwaterjump; + float phys_maxbarrier; + float phys_jumpvel; + float phys_falldelta5; + float phys_falldelta10; + float rs_waterjump; + float rs_teleport; + float rs_barrierjump; + float rs_startcrouch; + float rs_startgrapple; + float rs_startwalkoffledge; + float rs_startjump; + float rs_rocketjump; + float rs_bfgjump; + float rs_jumppad; + float rs_aircontrolledjumppad; + float rs_funcbob; + float rs_startelevator; + float rs_falldamage5; + float rs_falldamage10; + float rs_maxjumpfallheight; +} cfg_t; + +extern cfg_t cfg; + +void DefaultCfg( void ); +int LoadCfgFile( char *filename ); diff --git a/src/bspc/aas_create.c b/src/bspc/aas_create.c new file mode 100644 index 0000000..db72b20 --- /dev/null +++ b/src/bspc/aas_create.c @@ -0,0 +1,1182 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_create.c +// Function: Creation of AAS +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "..\botlib\aasfile.h" +#include "aas_create.h" +#include "aas_store.h" +#include "aas_gsubdiv.h" +#include "aas_facemerging.h" +#include "aas_areamerging.h" +#include "aas_edgemelting.h" +#include "aas_prunenodes.h" +#include "aas_cfg.h" +#include "..\game\surfaceflags.h" + +//#define AW_DEBUG +//#define L_DEBUG + +#define AREAONFACESIDE( face, area ) ( face->frontarea != area ) + +tmp_aas_t tmpaasworld; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitTmpAAS( void ) { + //tmp faces + tmpaasworld.numfaces = 0; + tmpaasworld.facenum = 0; + tmpaasworld.faces = NULL; + //tmp convex areas + tmpaasworld.numareas = 0; + tmpaasworld.areanum = 0; + tmpaasworld.areas = NULL; + //tmp nodes + tmpaasworld.numnodes = 0; + tmpaasworld.nodes = NULL; + // + tmpaasworld.nodebuffer = NULL; +} //end of the function AAS_InitTmpAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeTmpAAS( void ) { + tmp_face_t *f, *nextf; + tmp_area_t *a, *nexta; + tmp_nodebuf_t *nb, *nextnb; + + //free all the faces + for ( f = tmpaasworld.faces; f; f = nextf ) + { + nextf = f->l_next; + if ( f->winding ) { + FreeWinding( f->winding ); + } + FreeMemory( f ); + } //end if + //free all tmp areas + for ( a = tmpaasworld.areas; a; a = nexta ) + { + nexta = a->l_next; + if ( a->settings ) { + FreeMemory( a->settings ); + } + FreeMemory( a ); + } //end for + //free all the tmp nodes + for ( nb = tmpaasworld.nodebuffer; nb; nb = nextnb ) + { + nextnb = nb->next; + FreeMemory( nb ); + } //end for +} //end of the function AAS_FreeTmpAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_face_t *AAS_AllocTmpFace( void ) { + tmp_face_t *tmpface; + + tmpface = (tmp_face_t *) GetClearedMemory( sizeof( tmp_face_t ) ); + tmpface->num = tmpaasworld.facenum++; + tmpface->l_prev = NULL; + tmpface->l_next = tmpaasworld.faces; + if ( tmpaasworld.faces ) { + tmpaasworld.faces->l_prev = tmpface; + } + tmpaasworld.faces = tmpface; + tmpaasworld.numfaces++; + return tmpface; +} //end of the function AAS_AllocTmpFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeTmpFace( tmp_face_t *tmpface ) { + if ( tmpface->l_next ) { + tmpface->l_next->l_prev = tmpface->l_prev; + } + if ( tmpface->l_prev ) { + tmpface->l_prev->l_next = tmpface->l_next; + } else { tmpaasworld.faces = tmpface->l_next;} + //free the winding + if ( tmpface->winding ) { + FreeWinding( tmpface->winding ); + } + //free the face + FreeMemory( tmpface ); + tmpaasworld.numfaces--; +} //end of the function AAS_FreeTmpFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_area_t *AAS_AllocTmpArea( void ) { + tmp_area_t *tmparea; + + tmparea = (tmp_area_t *) GetClearedMemory( sizeof( tmp_area_t ) ); + tmparea->areanum = tmpaasworld.areanum++; + tmparea->l_prev = NULL; + tmparea->l_next = tmpaasworld.areas; + if ( tmpaasworld.areas ) { + tmpaasworld.areas->l_prev = tmparea; + } + tmpaasworld.areas = tmparea; + tmpaasworld.numareas++; + return tmparea; +} //end of the function AAS_AllocTmpArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeTmpArea( tmp_area_t *tmparea ) { + if ( tmparea->l_next ) { + tmparea->l_next->l_prev = tmparea->l_prev; + } + if ( tmparea->l_prev ) { + tmparea->l_prev->l_next = tmparea->l_next; + } else { tmpaasworld.areas = tmparea->l_next;} + if ( tmparea->settings ) { + FreeMemory( tmparea->settings ); + } + FreeMemory( tmparea ); + tmpaasworld.numareas--; +} //end of the function AAS_FreeTmpArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_AllocTmpNode( void ) { + tmp_nodebuf_t *nodebuf; + + if ( !tmpaasworld.nodebuffer || + tmpaasworld.nodebuffer->numnodes >= NODEBUF_SIZE ) { + nodebuf = (tmp_nodebuf_t *) GetClearedMemory( sizeof( tmp_nodebuf_t ) ); + nodebuf->next = tmpaasworld.nodebuffer; + nodebuf->numnodes = 0; + tmpaasworld.nodebuffer = nodebuf; + } //end if + tmpaasworld.numnodes++; + return &tmpaasworld.nodebuffer->nodes[tmpaasworld.nodebuffer->numnodes++]; +} //end of the function AAS_AllocTmpNode +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeTmpNode( tmp_node_t *tmpnode ) { + tmpaasworld.numnodes--; +} //end of the function AAS_FreeTmpNode +//=========================================================================== +// returns true if the face is a gap from the given side +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GapFace( tmp_face_t *tmpface, int side ) { + vec3_t invgravity; + + //if the face is a solid or ground face it can't be a gap + if ( tmpface->faceflags & ( FACE_GROUND | FACE_SOLID ) ) { + return 0; + } + + VectorCopy( cfg.phys_gravitydirection, invgravity ); + VectorInverse( invgravity ); + + return ( DotProduct( invgravity, mapplanes[tmpface->planenum ^ side].normal ) > cfg.phys_maxsteepness ); +} //end of the function AAS_GapFace +//=========================================================================== +// returns true if the face is a ground face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GroundFace( tmp_face_t *tmpface ) { + vec3_t invgravity; + + //must be a solid face + if ( !( tmpface->faceflags & FACE_SOLID ) ) { + return 0; + } + + VectorCopy( cfg.phys_gravitydirection, invgravity ); + VectorInverse( invgravity ); + + return ( DotProduct( invgravity, mapplanes[tmpface->planenum].normal ) > cfg.phys_maxsteepness ); +} //end of the function AAS_GroundFace +//=========================================================================== +// adds the side of a face to an area +// +// side : 0 = front side +// 1 = back side +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddFaceSideToArea( tmp_face_t *tmpface, int side, tmp_area_t *tmparea ) { + int tmpfaceside; + + if ( side ) { + if ( tmpface->backarea ) { + Error( "AAS_AddFaceSideToArea: already a back area\n" ); + } + } //end if + else + { + if ( tmpface->frontarea ) { + Error( "AAS_AddFaceSideToArea: already a front area\n" ); + } + } //end else + + if ( side ) { + tmpface->backarea = tmparea; + } else { tmpface->frontarea = tmparea;} + + if ( tmparea->tmpfaces ) { + tmpfaceside = tmparea->tmpfaces->frontarea != tmparea; + tmparea->tmpfaces->prev[tmpfaceside] = tmpface; + } //end if + tmpface->next[side] = tmparea->tmpfaces; + tmpface->prev[side] = NULL; + tmparea->tmpfaces = tmpface; +} //end of the function AAS_AddFaceSideToArea +//=========================================================================== +// remove (a side of) a face from an area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveFaceFromArea( tmp_face_t *tmpface, tmp_area_t *tmparea ) { + int side, prevside, nextside; + + if ( tmpface->frontarea != tmparea && + tmpface->backarea != tmparea ) { + Error( "AAS_RemoveFaceFromArea: face not part of the area" ); + } //end if + side = tmpface->frontarea != tmparea; + if ( tmpface->prev[side] ) { + prevside = tmpface->prev[side]->frontarea != tmparea; + tmpface->prev[side]->next[prevside] = tmpface->next[side]; + } //end if + else + { + tmparea->tmpfaces = tmpface->next[side]; + } //end else + if ( tmpface->next[side] ) { + nextside = tmpface->next[side]->frontarea != tmparea; + tmpface->next[side]->prev[nextside] = tmpface->prev[side]; + } //end if + //remove the area number from the face depending on the side + if ( side ) { + tmpface->backarea = NULL; + } else { tmpface->frontarea = NULL;} + tmpface->prev[side] = NULL; + tmpface->next[side] = NULL; +} //end of the function AAS_RemoveFaceFromArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckArea( tmp_area_t *tmparea ) { + int side; + tmp_face_t *face; + plane_t *plane; + vec3_t wcenter, acenter = {0, 0, 0}; + vec3_t normal; + float n, dist; + + if ( tmparea->invalid ) { + Log_Print( "AAS_CheckArea: invalid area\n" ); + } + for ( n = 0, face = tmparea->tmpfaces; face; face = face->next[side] ) + { + //side of the face the area is on + side = face->frontarea != tmparea; + WindingCenter( face->winding, wcenter ); + VectorAdd( acenter, wcenter, acenter ); + n++; + } //end for + n = 1 / n; + VectorScale( acenter, n, acenter ); + for ( face = tmparea->tmpfaces; face; face = face->next[side] ) + { + //side of the face the area is on + side = face->frontarea != tmparea; + +#ifdef L_DEBUG + if ( WindingError( face->winding ) ) { + Log_Write( "AAS_CheckArea: area %d face %d: %s\r\n", tmparea->areanum, + face->num, WindingErrorString() ); + } //end if +#endif L_DEBUG + + plane = &mapplanes[face->planenum ^ side]; + + if ( DotProduct( plane->normal, acenter ) - plane->dist < 0 ) { + Log_Print( "AAS_CheckArea: area %d face %d is flipped\n", tmparea->areanum, face->num ); + Log_Print( "AAS_CheckArea: area %d center is %f %f %f\n", tmparea->areanum, acenter[0], acenter[1], acenter[2] ); + } //end if + //check if the winding plane is the same as the face plane + WindingPlane( face->winding, normal, &dist ); + plane = &mapplanes[face->planenum]; +#ifdef L_DEBUG + if ( fabs( dist - plane->dist ) > 0.4 || + fabs( normal[0] - plane->normal[0] ) > 0.0001 || + fabs( normal[1] - plane->normal[1] ) > 0.0001 || + fabs( normal[2] - plane->normal[2] ) > 0.0001 ) { + Log_Write( "AAS_CheckArea: area %d face %d winding plane unequal to face plane\r\n", + tmparea->areanum, face->num ); + } //end if +#endif L_DEBUG + } //end for +} //end of the function AAS_CheckArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckFaceWindingPlane( tmp_face_t *face ) { + float dist, sign1, sign2; + vec3_t normal; + plane_t *plane; + winding_t *w; + + //check if the winding plane is the same as the face plane + WindingPlane( face->winding, normal, &dist ); + plane = &mapplanes[face->planenum]; + // + sign1 = DotProduct( plane->normal, normal ); + // + if ( fabs( dist - plane->dist ) > 0.4 || + fabs( normal[0] - plane->normal[0] ) > 0.0001 || + fabs( normal[1] - plane->normal[1] ) > 0.0001 || + fabs( normal[2] - plane->normal[2] ) > 0.0001 ) { + VectorInverse( normal ); + dist = -dist; + if ( fabs( dist - plane->dist ) > 0.4 || + fabs( normal[0] - plane->normal[0] ) > 0.0001 || + fabs( normal[1] - plane->normal[1] ) > 0.0001 || + fabs( normal[2] - plane->normal[2] ) > 0.0001 ) { + Log_Write( "AAS_CheckFaceWindingPlane: face %d winding plane unequal to face plane\r\n", + face->num ); + // + sign2 = DotProduct( plane->normal, normal ); + if ( ( sign1 < 0 && sign2 > 0 ) || + ( sign1 > 0 && sign2 < 0 ) ) { + Log_Write( "AAS_CheckFaceWindingPlane: face %d winding reversed\r\n", + face->num ); + w = face->winding; + face->winding = ReverseWinding( w ); + FreeWinding( w ); + } //end if + } //end if + else + { + Log_Write( "AAS_CheckFaceWindingPlane: face %d winding reversed\r\n", + face->num ); + w = face->winding; + face->winding = ReverseWinding( w ); + FreeWinding( w ); + } //end else + } //end if +} //end of the function AAS_CheckFaceWindingPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckAreaWindingPlanes( void ) { + int side; + tmp_area_t *tmparea; + tmp_face_t *face; + + Log_Write( "AAS_CheckAreaWindingPlanes:\r\n" ); + for ( tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next ) + { + if ( tmparea->invalid ) { + continue; + } + for ( face = tmparea->tmpfaces; face; face = face->next[side] ) + { + side = face->frontarea != tmparea; + AAS_CheckFaceWindingPlane( face ); + } //end for + } //end for +} //end of the function AAS_CheckAreaWindingPlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FlipAreaFaces( tmp_area_t *tmparea ) { + int side; + tmp_face_t *face; + plane_t *plane; + vec3_t wcenter, acenter = {0, 0, 0}; + //winding_t *w; + float n; + + for ( n = 0, face = tmparea->tmpfaces; face; face = face->next[side] ) + { + if ( !face->frontarea ) { + Error( "face %d has no front area\n", face->num ); + } + //side of the face the area is on + side = face->frontarea != tmparea; + WindingCenter( face->winding, wcenter ); + VectorAdd( acenter, wcenter, acenter ); + n++; + } //end for + n = 1 / n; + VectorScale( acenter, n, acenter ); + for ( face = tmparea->tmpfaces; face; face = face->next[side] ) + { + //side of the face the area is on + side = face->frontarea != tmparea; + + plane = &mapplanes[face->planenum ^ side]; + + if ( DotProduct( plane->normal, acenter ) - plane->dist < 0 ) { + Log_Print( "area %d face %d flipped: front area %d, back area %d\n", tmparea->areanum, face->num, + face->frontarea ? face->frontarea->areanum : 0, + face->backarea ? face->backarea->areanum : 0 ); + /* + face->planenum = face->planenum ^ 1; + w = face->winding; + face->winding = ReverseWinding(w); + FreeWinding(w); + */ + } //end if +#ifdef L_DEBUG + { + float dist; + vec3_t normal; + + //check if the winding plane is the same as the face plane + WindingPlane( face->winding, normal, &dist ); + plane = &mapplanes[face->planenum]; + if ( fabs( dist - plane->dist ) > 0.4 || + fabs( normal[0] - plane->normal[0] ) > 0.0001 || + fabs( normal[1] - plane->normal[1] ) > 0.0001 || + fabs( normal[2] - plane->normal[2] ) > 0.0001 ) { + Log_Write( "area %d face %d winding plane unequal to face plane\r\n", + tmparea->areanum, face->num ); + } //end if + } +#endif + } //end for +} //end of the function AAS_FlipAreaFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveAreaFaceColinearPoints( void ) { + int side; + tmp_face_t *face; + tmp_area_t *tmparea; + + //FIXME: loop over the faces instead of area->faces + for ( tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next ) + { + for ( face = tmparea->tmpfaces; face; face = face->next[side] ) + { + side = face->frontarea != tmparea; + RemoveColinearPoints( face->winding ); +// RemoveEqualPoints(face->winding, 0.1); + } //end for + } //end for +} //end of the function AAS_RemoveAreaFaceColinearPoints +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveTinyFaces( void ) { + int side, num; + tmp_face_t *face, *nextface; + tmp_area_t *tmparea; + + //FIXME: loop over the faces instead of area->faces + Log_Write( "AAS_RemoveTinyFaces\r\n" ); + num = 0; + for ( tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next ) + { + for ( face = tmparea->tmpfaces; face; face = nextface ) + { + side = face->frontarea != tmparea; + nextface = face->next[side]; + // + if ( WindingArea( face->winding ) < 1 ) { + if ( face->frontarea ) { + AAS_RemoveFaceFromArea( face, face->frontarea ); + } + if ( face->backarea ) { + AAS_RemoveFaceFromArea( face, face->backarea ); + } + AAS_FreeTmpFace( face ); + //Log_Write("area %d face %d is tiny\r\n", tmparea->areanum, face->num); + num++; + } //end if + } //end for + } //end for + Log_Write( "%d tiny faces removed\r\n", num ); +} //end of the function AAS_RemoveTinyFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAreaSettings( void ) { + int i, flags, side, numgrounded, numladderareas, numliquidareas; + tmp_face_t *face; + tmp_area_t *tmparea; + int count; + + numgrounded = 0; + numladderareas = 0; + numliquidareas = 0; + Log_Write( "AAS_CreateAreaSettings\r\n" ); + i = 0; + qprintf( "%6d areas provided with settings", i ); + for ( tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next ) + { + //if the area is invalid there no need to create settings for it + if ( tmparea->invalid ) { + continue; + } + + tmparea->settings = (tmp_areasettings_t *) GetClearedMemory( sizeof( tmp_areasettings_t ) ); + tmparea->settings->contents = tmparea->contents; + tmparea->settings->modelnum = tmparea->modelnum; + flags = 0; + count = 0; + tmparea->settings->groundsteepness = 0.0; + for ( face = tmparea->tmpfaces; face; face = face->next[side] ) + { + side = face->frontarea != tmparea; + flags |= face->faceflags; + // Ridah, add this face's steepness + if ( face->faceflags & FACE_GROUND ) { + tmparea->settings->groundsteepness += ( 1.0 - mapplanes[face->planenum ^ side].normal[2] ); + count++; + } + } //end for + tmparea->settings->groundsteepness /= (float)count; + if ( tmparea->settings->groundsteepness > 1.0 ) { + tmparea->settings->groundsteepness = 1.0; + } + if ( tmparea->settings->groundsteepness < 0.0 ) { + tmparea->settings->groundsteepness = 0.0; + } + tmparea->settings->areaflags = 0; + if ( flags & FACE_GROUND ) { + tmparea->settings->areaflags |= AREA_GROUNDED; + numgrounded++; + } //end if + if ( flags & FACE_LADDER ) { + tmparea->settings->areaflags |= AREA_LADDER; + numladderareas++; + } //end if + if ( tmparea->contents & ( AREACONTENTS_WATER | + AREACONTENTS_SLIME | + AREACONTENTS_LAVA ) ) { + tmparea->settings->areaflags |= AREA_LIQUID; + numliquidareas++; + } //end if + //presence type of the area + tmparea->settings->presencetype = tmparea->presencetype; + // + qprintf( "\r%6d", ++i ); + } //end for + qprintf( "\n" ); +#ifdef AASINFO + Log_Print( "%6d grounded areas\n", numgrounded ); + Log_Print( "%6d ladder areas\n", numladderareas ); + Log_Print( "%6d liquid areas\n", numliquidareas ); +#endif //AASINFO +} //end of the function AAS_CreateAreaSettings +//=========================================================================== +// create a tmp AAS area from a leaf node +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_CreateArea( node_t *node ) { + int pside; + int areafaceflags; + portal_t *p; + tmp_face_t *tmpface; + tmp_area_t *tmparea; + tmp_node_t *tmpnode; + vec3_t up = {0, 0, 1}; + + //create an area from this leaf + tmparea = AAS_AllocTmpArea(); + tmparea->tmpfaces = NULL; + //clear the area face flags + areafaceflags = 0; + //make aas faces from the portals + for ( p = node->portals; p; p = p->next[pside] ) + { + pside = ( p->nodes[1] == node ); + //don't create faces from very small portals +// if (WindingArea(p->winding) < 1) continue; + //if there's already a face created for this portal + if ( p->tmpface ) { + //add the back side of the face to the area + AAS_AddFaceSideToArea( p->tmpface, 1, tmparea ); + } //end if + else + { + tmpface = AAS_AllocTmpFace(); + //set the face pointer at the portal so we can see from + //the portal there's a face created for it + p->tmpface = tmpface; + //FIXME: test this change + //tmpface->planenum = (p->planenum & ~1) | pside; + tmpface->planenum = p->planenum ^ pside; + if ( pside ) { + tmpface->winding = ReverseWinding( p->winding ); + } else { tmpface->winding = CopyWinding( p->winding );} +#ifdef L_DEBUG + // + AAS_CheckFaceWindingPlane( tmpface ); +#endif //L_DEBUG + //if there's solid at the other side of the portal + if ( p->nodes[!pside]->contents & ( CONTENTS_SOLID | CONTENTS_PLAYERCLIP ) ) { + tmpface->faceflags |= FACE_SOLID; + } //end if + //else there is no solid at the other side and if there + //is a liquid at this side + else if ( node->contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + tmpface->faceflags |= FACE_LIQUID; + //if there's no liquid at the other side + if ( !( p->nodes[!pside]->contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + tmpface->faceflags |= FACE_LIQUIDSURFACE; + } //end if + } //end else + //if there's ladder contents at other side of the portal + if ( ( p->nodes[pside]->contents & CONTENTS_LADDER ) || + ( p->nodes[!pside]->contents & CONTENTS_LADDER ) ) { + + //NOTE: doesn't have to be solid at the other side because + // when standing one can use a crouch area (which is not solid) + // as a ladder + // imagine a ladder one can walk underthrough, + // under the ladder against the ladder is a crouch area + // the (vertical) sides of this crouch area area also used as + // ladder sides when standing (not crouched) + tmpface->faceflags |= FACE_LADDER; + } //end if + //if it is possible to stand on the face + if ( AAS_GroundFace( tmpface ) ) { + tmpface->faceflags |= FACE_GROUND; + } //end if + // + areafaceflags |= tmpface->faceflags; + //no aas face number yet (zero is a dummy in the aasworld faces) + tmpface->aasfacenum = 0; + //add the front side of the face to the area + AAS_AddFaceSideToArea( tmpface, 0, tmparea ); + } //end else + } //end for + qprintf( "\r%6d", tmparea->areanum ); + //presence type in the area + tmparea->presencetype = ~node->expansionbboxes & cfg.allpresencetypes; + // + tmparea->contents = 0; + if ( node->contents & CONTENTS_CLUSTERPORTAL ) { + tmparea->contents |= AREACONTENTS_CLUSTERPORTAL; + } + if ( node->contents & CONTENTS_MOVER ) { + tmparea->contents |= AREACONTENTS_MOVER; + } + if ( node->contents & CONTENTS_TELEPORTER ) { + tmparea->contents |= AREACONTENTS_TELEPORTER; + } + if ( node->contents & CONTENTS_JUMPPAD ) { + tmparea->contents |= AREACONTENTS_JUMPPAD; + } + if ( node->contents & CONTENTS_DONOTENTER ) { + tmparea->contents |= AREACONTENTS_DONOTENTER; + } + if ( node->contents & CONTENTS_DONOTENTER_LARGE ) { + tmparea->contents |= AREACONTENTS_DONOTENTER_LARGE; + } + if ( node->contents & CONTENTS_WATER ) { + tmparea->contents |= AREACONTENTS_WATER; + } + if ( node->contents & CONTENTS_LAVA ) { + tmparea->contents |= AREACONTENTS_LAVA; + } + if ( node->contents & CONTENTS_SLIME ) { + tmparea->contents |= AREACONTENTS_SLIME; + } + + //store the bsp model that's inside this node + tmparea->modelnum = node->modelnum; + //sorta check for flipped area faces (remove??) + AAS_FlipAreaFaces( tmparea ); + //check if the area is ok (remove??) + AAS_CheckArea( tmparea ); + // + tmpnode = AAS_AllocTmpNode(); + tmpnode->planenum = 0; + tmpnode->children[0] = 0; + tmpnode->children[1] = 0; + tmpnode->tmparea = tmparea; + // + return tmpnode; +} //end of the function AAS_CreateArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_CreateAreas_r( node_t *node ) { + tmp_node_t *tmpnode; + + //recurse down to leafs + if ( node->planenum != PLANENUM_LEAF ) { + //the first tmp node is a dummy + tmpnode = AAS_AllocTmpNode(); + tmpnode->planenum = node->planenum; + tmpnode->children[0] = AAS_CreateAreas_r( node->children[0] ); + tmpnode->children[1] = AAS_CreateAreas_r( node->children[1] ); + return tmpnode; + } //end if + //areas won't be created for solid leafs + if ( node->contents & CONTENTS_SOLID ) { + //just return zero for a solid leaf (in tmp AAS NULL is a solid leaf) + return NULL; + } //end if + + return AAS_CreateArea( node ); +} //end of the function AAS_CreateAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAreas( node_t *node ) { + Log_Write( "AAS_CreateAreas\r\n" ); + qprintf( "%6d areas created", 0 ); + tmpaasworld.nodes = AAS_CreateAreas_r( node ); + qprintf( "\n" ); + Log_Write( "%6d areas created\r\n", tmpaasworld.numareas ); +} //end of the function AAS_CreateAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PrintNumGroundFaces( void ) { + tmp_face_t *tmpface; + int numgroundfaces = 0; + + for ( tmpface = tmpaasworld.faces; tmpface; tmpface = tmpface->l_next ) + { + if ( tmpface->faceflags & FACE_GROUND ) { + numgroundfaces++; + } //end if + } //end for + qprintf( "%6d ground faces\n", numgroundfaces ); +} //end of the function AAS_PrintNumGroundFaces +//=========================================================================== +// checks the number of shared faces between the given two areas +// since areas are convex they should only have ONE shared face +// however due to crappy face merging there are sometimes several +// shared faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckAreaSharedFaces( tmp_area_t *tmparea1, tmp_area_t *tmparea2 ) { + int numsharedfaces, side; + tmp_face_t *face1, *sharedface; + + if ( tmparea1->invalid || tmparea2->invalid ) { + return; + } + + sharedface = NULL; + numsharedfaces = 0; + for ( face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side] ) + { + side = face1->frontarea != tmparea1; + if ( face1->backarea == tmparea2 || face1->frontarea == tmparea2 ) { + sharedface = face1; + numsharedfaces++; + } //end if + } //end if + if ( !sharedface ) { + return; + } + //the areas should only have one shared face + if ( numsharedfaces > 1 ) { + Log_Write( "---- tmp area %d and %d have %d shared faces\r\n", + tmparea1->areanum, tmparea2->areanum, numsharedfaces ); + for ( face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side] ) + { + side = face1->frontarea != tmparea1; + if ( face1->backarea == tmparea2 || face1->frontarea == tmparea2 ) { + Log_Write( "face %d, planenum = %d, face->frontarea = %d face->backarea = %d\r\n", + face1->num, face1->planenum, face1->frontarea->areanum, face1->backarea->areanum ); + } //end if + } //end if + } //end if +} //end of the function AAS_CheckAreaSharedFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CheckSharedFaces( void ) { + tmp_area_t *tmparea1, *tmparea2; + + for ( tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next ) + { + for ( tmparea2 = tmpaasworld.areas; tmparea2; tmparea2 = tmparea2->l_next ) + { + if ( tmparea1 == tmparea2 ) { + continue; + } + AAS_CheckAreaSharedFaces( tmparea1, tmparea2 ); + } //end for + } //end for +} //end of the function AAS_CheckSharedFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FlipFace( tmp_face_t *face ) { + tmp_area_t *frontarea, *backarea; + winding_t *w; + + frontarea = face->frontarea; + backarea = face->backarea; + //must have an area at both sides before flipping is allowed + if ( !frontarea || !backarea ) { + return; + } + //flip the face winding + w = face->winding; + face->winding = ReverseWinding( w ); + FreeWinding( w ); + //flip the face plane + face->planenum ^= 1; + //flip the face areas + AAS_RemoveFaceFromArea( face, frontarea ); + AAS_RemoveFaceFromArea( face, backarea ); + AAS_AddFaceSideToArea( face, 1, frontarea ); + AAS_AddFaceSideToArea( face, 0, backarea ); +} //end of the function AAS_FlipFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void AAS_FlipAreaSharedFaces(tmp_area_t *tmparea1, tmp_area_t *tmparea2) +{ + int numsharedfaces, side, area1facing, area2facing; + tmp_face_t *face1, *sharedface; + + if (tmparea1->invalid || tmparea2->invalid) return; + + sharedface = NULL; + numsharedfaces = 0; + area1facing = 0; //number of shared faces facing towards area 1 + area2facing = 0; //number of shared faces facing towards area 2 + for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) + { + side = face1->frontarea != tmparea1; + if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) + { + sharedface = face1; + numsharedfaces++; + if (face1->frontarea == tmparea1) area1facing++; + else area2facing++; + } //end if + } //end if + if (!sharedface) return; + //if there's only one shared face + if (numsharedfaces <= 1) return; + //if all the shared faces are facing to the same area + if (numsharedfaces == area1facing || numsharedfaces == area2facing) return; + // + do + { + for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) + { + side = face1->frontarea != tmparea1; + if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) + { + if (face1->frontarea != tmparea1) + { + AAS_FlipFace(face1); + break; + } //end if + } //end if + } //end for + } while(face1); +} //end of the function AAS_FlipAreaSharedFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FlipSharedFaces(void) +{ + int i; + tmp_area_t *tmparea1, *tmparea2; + + i = 0; + qprintf("%6d areas checked for shared face flipping", i); + for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) + { + if (tmparea1->invalid) continue; + for (tmparea2 = tmpaasworld.areas; tmparea2; tmparea2 = tmparea2->l_next) + { + if (tmparea2->invalid) continue; + if (tmparea1 == tmparea2) continue; + AAS_FlipAreaSharedFaces(tmparea1, tmparea2); + } //end for + qprintf("\r%6d", ++i); + } //end for + Log_Print("\r%6d areas checked for shared face flipping\n", i); +} //end of the function AAS_FlipSharedFaces +*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FlipSharedFaces( void ) { + int i, side1, side2; + tmp_area_t *tmparea1; + tmp_face_t *face1, *face2; + + i = 0; + qprintf( "%6d areas checked for shared face flipping", i ); + for ( tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next ) + { + if ( tmparea1->invalid ) { + continue; + } + for ( face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side1] ) + { + side1 = face1->frontarea != tmparea1; + if ( !face1->frontarea || !face1->backarea ) { + continue; + } + // + for ( face2 = face1->next[side1]; face2; face2 = face2->next[side2] ) + { + side2 = face2->frontarea != tmparea1; + if ( !face2->frontarea || !face2->backarea ) { + continue; + } + // + if ( face1->frontarea == face2->backarea && + face1->backarea == face2->frontarea ) { + AAS_FlipFace( face2 ); + } //end if + //recheck side + side2 = face2->frontarea != tmparea1; + } //end for + } //end for + qprintf( "\r%6d", ++i ); + } //end for + qprintf( "\n" ); + Log_Print( "%6d areas checked for shared face flipping\r\n", i ); +} //end of the function AAS_FlipSharedFaces +//=========================================================================== +// creates an .AAS file with the given name +// a MAP should be loaded before calling this +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Create( char *aasfile ) { + entity_t *e; + tree_t *tree; + double start_time; + + //for a possible leak file + strcpy( source, aasfile ); + StripExtension( source ); + //the time started + start_time = I_FloatTime(); + //set the default number of threads (depends on number of processors) + ThreadSetDefault(); + //set the global entity number to the world model + entity_num = 0; + //the world entity + e = &entities[entity_num]; + //process the whole world + tree = ProcessWorldBrushes( e->firstbrush, e->firstbrush + e->numbrushes ); + //if the conversion is cancelled + if ( cancelconversion ) { + Tree_Free( tree ); + return; + } //end if + //display BSP tree creation time + Log_Print( "BSP tree created in %5.0f seconds\n", I_FloatTime() - start_time ); + //prune the bsp tree + Tree_PruneNodes( tree->headnode ); + //if the conversion is cancelled + if ( cancelconversion ) { + Tree_Free( tree ); + return; + } //end if + //create the tree portals + MakeTreePortals( tree ); + //if the conversion is cancelled + if ( cancelconversion ) { + Tree_Free( tree ); + return; + } //end if + //Marks all nodes that can be reached by entites + if ( FloodEntities( tree ) ) { + //fill out nodes that can't be reached + FillOutside( tree->headnode ); + } //end if + else + { + LeakFile( tree ); + Error( "**** leaked ****\n" ); + return; + } //end else + //create AAS from the BSP tree + //========================================== + //initialize tmp aas + AAS_InitTmpAAS(); + //create the convex areas from the leaves + AAS_CreateAreas( tree->headnode ); + //free the BSP tree because it isn't used anymore + if ( freetree ) { + Tree_Free( tree ); + } + //try to merge area faces + AAS_MergeAreaFaces(); + //do gravitational subdivision + AAS_GravitationalSubdivision(); + //merge faces if possible + AAS_MergeAreaFaces(); + AAS_RemoveAreaFaceColinearPoints(); + //merge areas if possible + AAS_MergeAreas(); + //NOTE: prune nodes directly after area merging + AAS_PruneNodes(); + //flip shared faces so they are all facing to the same area + AAS_FlipSharedFaces(); + AAS_RemoveAreaFaceColinearPoints(); + //merge faces if possible + AAS_MergeAreaFaces(); + //merge area faces in the same plane + AAS_MergeAreaPlaneFaces(); + //do ladder subdivision + AAS_LadderSubdivision(); + //FIXME: melting is buggy + AAS_MeltAreaFaceWindings(); + //remove tiny faces + AAS_RemoveTinyFaces(); + //create area settings + AAS_CreateAreaSettings(); + //check if the winding plane is equal to the face plane + //AAS_CheckAreaWindingPlanes(); + // + //AAS_CheckSharedFaces(); + //========================================== + //if the conversion is cancelled + if ( cancelconversion ) { + Tree_Free( tree ); + AAS_FreeTmpAAS(); + return; + } //end if + //store the created AAS stuff in the AAS file format and write the file + AAS_StoreFile( aasfile ); + //free the temporary AAS memory + AAS_FreeTmpAAS(); + //display creation time + Log_Print( "\nAAS created in %5.0f seconds\n", I_FloatTime() - start_time ); +} //end of the function AAS_Create diff --git a/src/bspc/aas_create.h b/src/bspc/aas_create.h new file mode 100644 index 0000000..2809a4f --- /dev/null +++ b/src/bspc/aas_create.h @@ -0,0 +1,153 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_create.h +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#define AREA_PORTAL 1 + +//temporary AAS face +typedef struct tmp_face_s +{ + int num; //face number + int planenum; //number of the plane the face is in + winding_t *winding; //winding of the face + struct tmp_area_s *frontarea; //area at the front of the face + struct tmp_area_s *backarea; //area at the back of the face + int faceflags; //flags of this face + int aasfacenum; //the number of the aas face used for this face + //double link list pointers for front and back area + struct tmp_face_s *prev[2], *next[2]; + //links in the list with faces + struct tmp_face_s *l_prev, *l_next; +} tmp_face_t; + +//temporary AAS area settings +typedef struct tmp_areasettings_s +{ + //could also add all kind of statistic fields + int contents; //contents of the area + int modelnum; //bsp model inside this area + int areaflags; //area flags + int presencetype; //how a bot can be present in this area + int numreachableareas; //number of reachable areas from this one + int firstreachablearea; //first reachable area in the reachable area index + // Ridah, steepness + float groundsteepness; +} tmp_areasettings_t; + +//temporary AAS area +typedef struct tmp_area_s +{ + int areanum; //number of the area + struct tmp_face_s *tmpfaces; //the faces of the area + int presencetype; //presence type of the area + int contents; //area contents + int modelnum; //bsp model inside this area + int invalid; //true if the area is invalid + tmp_areasettings_t *settings; //area settings + struct tmp_area_s *mergedarea; //points to the new area after merging + //when mergedarea != 0 the area has only the + //seperating face of the merged areas + int aasareanum; //number of the aas area created for this tmp area + //links in the list with areas + struct tmp_area_s *l_prev, *l_next; +} tmp_area_t; + +//temporary AAS node +typedef struct tmp_node_s +{ + int planenum; //node plane number + struct tmp_area_s *tmparea; //points to an area if this node is an area + struct tmp_node_s *children[2]; //child nodes of this node +} tmp_node_t; + +#define NODEBUF_SIZE 128 +//node buffer +typedef struct tmp_nodebuf_s +{ + int numnodes; + struct tmp_nodebuf_s *next; + tmp_node_t nodes[NODEBUF_SIZE]; +} tmp_nodebuf_t; + +//the whole temorary AAS +typedef struct tmp_aas_s +{ + //faces + int numfaces; + int facenum; + tmp_face_t *faces; + //areas + int numareas; + int areanum; + tmp_area_t *areas; + //area settings + int numareasettings; + tmp_areasettings_t *areasettings; + //nodes + int numnodes; + tmp_node_t *nodes; + //node buffer + tmp_nodebuf_t *nodebuffer; +} tmp_aas_t; + +extern tmp_aas_t tmpaasworld; + +//creates a .AAS file with the given name from an already loaded map +void AAS_Create( char *aasfile ); +//adds a face side to an area +void AAS_AddFaceSideToArea( tmp_face_t *tmpface, int side, tmp_area_t *tmparea ); +//remvoes a face from an area +void AAS_RemoveFaceFromArea( tmp_face_t *tmpface, tmp_area_t *tmparea ); +//allocate a tmp face +tmp_face_t *AAS_AllocTmpFace( void ); +//free the tmp face +void AAS_FreeTmpFace( tmp_face_t *tmpface ); +//allocate a tmp area +tmp_area_t *AAS_AllocTmpArea( void ); +//free a tmp area +void AAS_FreeTmpArea( tmp_area_t *tmparea ); +//allocate a tmp node +tmp_node_t *AAS_AllocTmpNode( void ); +//free a tmp node +void AAS_FreeTmpNode( tmp_node_t *node ); +//checks if an area is ok +void AAS_CheckArea( tmp_area_t *tmparea ); +//flips the area faces where needed +void AAS_FlipAreaFaces( tmp_area_t *tmparea ); +//returns true if the face is a gap seen from the given side +int AAS_GapFace( tmp_face_t *tmpface, int side ); +//returns true if the face is a ground face +int AAS_GroundFace( tmp_face_t *tmpface ); diff --git a/src/bspc/aas_edgemelting.c b/src/bspc/aas_edgemelting.c new file mode 100644 index 0000000..7c4c9c4 --- /dev/null +++ b/src/bspc/aas_edgemelting.c @@ -0,0 +1,125 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_edgemelting.c +// Function: Melting of Edges +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "..\botlib\aasfile.h" +#include "aas_create.h" + +//=========================================================================== +// try to melt the windings of the two faces +// FIXME: this is buggy +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MeltFaceWinding( tmp_face_t *face1, tmp_face_t *face2 ) { + int i, n; + int splits = 0; + winding_t *w2, *neww; + plane_t *plane1; + +#ifdef DEBUG + if ( !face1->winding ) { + Error( "face1 %d without winding", face1->num ); + } + if ( !face2->winding ) { + Error( "face2 %d without winding", face2->num ); + } +#endif //DEBUG + w2 = face2->winding; + plane1 = &mapplanes[face1->planenum]; + for ( i = 0; i < w2->numpoints; i++ ) + { + if ( PointOnWinding( face1->winding, plane1->normal, plane1->dist, w2->p[i], &n ) ) { + neww = AddWindingPoint( face1->winding, w2->p[i], n ); + FreeWinding( face1->winding ); + face1->winding = neww; + + splits++; + } //end if + } //end for + return splits; +} //end of the function AAS_MeltFaceWinding +//=========================================================================== +// melt the windings of the area faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MeltFaceWindingsOfArea( tmp_area_t *tmparea ) { + int side1, side2, num_windingsplits = 0; + tmp_face_t *face1, *face2; + + for ( face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1] ) + { + side1 = face1->frontarea != tmparea; + for ( face2 = tmparea->tmpfaces; face2; face2 = face2->next[side2] ) + { + side2 = face2->frontarea != tmparea; + if ( face1 == face2 ) { + continue; + } + num_windingsplits += AAS_MeltFaceWinding( face1, face2 ); + } //end for + } //end for + return num_windingsplits; +} //end of the function AAS_MeltFaceWindingsOfArea +//=========================================================================== +// melt the windings of the faces of all areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_MeltAreaFaceWindings( void ) { + tmp_area_t *tmparea; + int num_windingsplits = 0; + + Log_Write( "AAS_MeltAreaFaceWindings\r\n" ); + qprintf( "%6d edges melted", num_windingsplits ); + //NOTE: first convex area (zero) is a dummy + for ( tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next ) + { + num_windingsplits += AAS_MeltFaceWindingsOfArea( tmparea ); + qprintf( "\r%6d", num_windingsplits ); + } //end for + qprintf( "\n" ); + Log_Write( "%6d edges melted\r\n", num_windingsplits ); +} //end of the function AAS_MeltAreaFaceWindings + diff --git a/src/bspc/aas_edgemelting.h b/src/bspc/aas_edgemelting.h new file mode 100644 index 0000000..931c530 --- /dev/null +++ b/src/bspc/aas_edgemelting.h @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_edgemelting.h +// Function: Melting of Edges +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + + +void AAS_MeltAreaFaceWindings( void ); + diff --git a/src/bspc/aas_facemerging.c b/src/bspc/aas_facemerging.c new file mode 100644 index 0000000..42a6bda --- /dev/null +++ b/src/bspc/aas_facemerging.c @@ -0,0 +1,312 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_facemerging.c +// Function: Merging of Faces +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "..\botlib\aasfile.h" +#include "aas_create.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TryMergeFaces( tmp_face_t *face1, tmp_face_t *face2 ) { + winding_t *neww; + +#ifdef DEBUG + if ( !face1->winding ) { + Error( "face1 %d without winding", face1->num ); + } + if ( !face2->winding ) { + Error( "face2 %d without winding", face2->num ); + } +#endif //DEBUG + // + if ( face1->faceflags != face2->faceflags ) { + return false; + } + //NOTE: if the front or back area is zero this doesn't mean there's + //a real area. It means there's solid at that side of the face + //if both faces have the same front area + if ( face1->frontarea == face2->frontarea ) { + //if both faces have the same back area + if ( face1->backarea == face2->backarea ) { + //if the faces are in the same plane + if ( face1->planenum == face2->planenum ) { + //if they have both a front and a back area (no solid on either side) + if ( face1->frontarea && face1->backarea ) { + neww = MergeWindings( face1->winding, face2->winding, + mapplanes[face1->planenum].normal ); + } //end if + else + { + //this function is to be found in l_poly.c + neww = TryMergeWinding( face1->winding, face2->winding, + mapplanes[face1->planenum].normal ); + } //end else + if ( neww ) { + FreeWinding( face1->winding ); + face1->winding = neww; + if ( face2->frontarea ) { + AAS_RemoveFaceFromArea( face2, face2->frontarea ); + } + if ( face2->backarea ) { + AAS_RemoveFaceFromArea( face2, face2->backarea ); + } + AAS_FreeTmpFace( face2 ); + return true; + } //end if + } //end if + else if ( ( face1->planenum & ~1 ) == ( face2->planenum & ~1 ) ) { + Log_Write( "face %d and %d, same front and back area but flipped planes\r\n", + face1->num, face2->num ); + } //end if + } //end if + } //end if + return false; +} //end of the function AAS_TryMergeFaces +/* +int AAS_TryMergeFaces(tmp_face_t *face1, tmp_face_t *face2) +{ + winding_t *neww; + +#ifdef DEBUG + if (!face1->winding) Error("face1 %d without winding", face1->num); + if (!face2->winding) Error("face2 %d without winding", face2->num); +#endif //DEBUG + //if the faces are in the same plane + if ((face1->planenum & ~1) != (face2->planenum & ~1)) return false; +// if (face1->planenum != face2->planenum) return false; + //NOTE: if the front or back area is zero this doesn't mean there's + //a real area. It means there's solid at that side of the face + //if both faces have the same front area + if (face1->frontarea != face2->frontarea || + face1->backarea != face2->backarea) + { + if (!face1->frontarea || !face1->backarea || + !face2->frontarea || !face2->backarea) return false; + else if (face1->frontarea != face2->backarea || + face1->backarea != face2->frontarea) return false; +// return false; + } //end if + //this function is to be found in l_poly.c + neww = TryMergeWinding(face1->winding, face2->winding, + mapplanes[face1->planenum].normal); + if (!neww) return false; + // + FreeWinding(face1->winding); + face1->winding = neww; + //remove face2 + if (face2->frontarea) + AAS_RemoveFaceFromArea(face2, &tmpaasworld.areas[face2->frontarea]); + if (face2->backarea) + AAS_RemoveFaceFromArea(face2, &tmpaasworld.areas[face2->backarea]); + return true; +} //end of the function AAS_TryMergeFaces*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_MergeAreaFaces( void ) { + int num_facemerges = 0; + int side1, side2, restart; + tmp_area_t *tmparea, *lasttmparea; + tmp_face_t *face1, *face2; + + Log_Write( "AAS_MergeAreaFaces\r\n" ); + qprintf( "%6d face merges", num_facemerges ); + //NOTE: first convex area is a dummy + lasttmparea = tmpaasworld.areas; + for ( tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next ) + { + restart = false; + // + if ( tmparea->invalid ) { + continue; + } + // + for ( face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1] ) + { + side1 = face1->frontarea != tmparea; + for ( face2 = face1->next[side1]; face2; face2 = face2->next[side2] ) + { + side2 = face2->frontarea != tmparea; + //if succesfully merged + if ( AAS_TryMergeFaces( face1, face2 ) ) { + //start over again after merging two faces + restart = true; + num_facemerges++; + qprintf( "\r%6d", num_facemerges ); + AAS_CheckArea( tmparea ); + break; + } //end if + } //end for + if ( restart ) { + tmparea = lasttmparea; + break; + } //end if + } //end for + lasttmparea = tmparea; + } //end for + qprintf( "\n" ); + Log_Write( "%6d face merges\r\n", num_facemerges ); +} //end of the function AAS_MergeAreaFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_MergePlaneFaces( tmp_area_t *tmparea, int planenum ) { + tmp_face_t *face1, *face2, *nextface2; + winding_t *neww; + int side1, side2; + + for ( face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1] ) + { + side1 = face1->frontarea != tmparea; + if ( face1->planenum != planenum ) { + continue; + } + // + for ( face2 = face1->next[side1]; face2; face2 = nextface2 ) + { + side2 = face2->frontarea != tmparea; + nextface2 = face2->next[side2]; + // + if ( ( face2->planenum & ~1 ) != ( planenum & ~1 ) ) { + continue; + } + // + neww = MergeWindings( face1->winding, face2->winding, + mapplanes[face1->planenum].normal ); + FreeWinding( face1->winding ); + face1->winding = neww; + if ( face2->frontarea ) { + AAS_RemoveFaceFromArea( face2, face2->frontarea ); + } + if ( face2->backarea ) { + AAS_RemoveFaceFromArea( face2, face2->backarea ); + } + AAS_FreeTmpFace( face2 ); + // + nextface2 = face1->next[side1]; + } //end for + } //end for +} //end of the function AAS_MergePlaneFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CanMergePlaneFaces( tmp_area_t *tmparea, int planenum ) { + tmp_area_t *frontarea, *backarea; + tmp_face_t *face1; + int side1, merge, faceflags; + + frontarea = backarea = NULL; + merge = false; + for ( face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1] ) + { + side1 = face1->frontarea != tmparea; + if ( ( face1->planenum & ~1 ) != ( planenum & ~1 ) ) { + continue; + } + if ( !frontarea && !backarea ) { + frontarea = face1->frontarea; + backarea = face1->backarea; + faceflags = face1->faceflags; + } //end if + else + { + if ( frontarea != face1->frontarea ) { + return false; + } + if ( backarea != face1->backarea ) { + return false; + } + if ( faceflags != face1->faceflags ) { + return false; + } + merge = true; + } //end else + } //end for + return merge; +} //end of the function AAS_CanMergePlaneFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_MergeAreaPlaneFaces( void ) { + int num_facemerges = 0; + int side1; + tmp_area_t *tmparea, *nexttmparea; + tmp_face_t *face1; + + Log_Write( "AAS_MergePlaneFaces\r\n" ); + qprintf( "%6d plane face merges", num_facemerges ); + //NOTE: first convex area is a dummy + for ( tmparea = tmpaasworld.areas; tmparea; tmparea = nexttmparea ) + { + nexttmparea = tmparea->l_next; + // + if ( tmparea->invalid ) { + continue; + } + // + for ( face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1] ) + { + side1 = face1->frontarea != tmparea; + // + if ( AAS_CanMergePlaneFaces( tmparea, face1->planenum ) ) { + AAS_MergePlaneFaces( tmparea, face1->planenum ); + nexttmparea = tmparea; + num_facemerges++; + qprintf( "\r%6d", num_facemerges ); + break; + } //end if + } //end for + } //end for + qprintf( "\n" ); + Log_Write( "%6d plane face merges\r\n", num_facemerges ); +} //end of the function AAS_MergeAreaPlaneFaces diff --git a/src/bspc/aas_facemerging.h b/src/bspc/aas_facemerging.h new file mode 100644 index 0000000..182e26e --- /dev/null +++ b/src/bspc/aas_facemerging.h @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_facemerging.h +// Function: Merging of Faces +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +void AAS_MergeAreaFaces( void ); +void AAS_MergeAreaPlaneFaces( void ); diff --git a/src/bspc/aas_file.c b/src/bspc/aas_file.c new file mode 100644 index 0000000..e859d15 --- /dev/null +++ b/src/bspc/aas_file.c @@ -0,0 +1,603 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_file.c +// Function: AAS file loading and writing +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "..\botlib\aasfile.h" +#include "aas_file.h" +#include "aas_store.h" +#include "aas_create.h" + +#define AAS_Error Error + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SwapAASData( void ) { + int i, j; + //bounding boxes + for ( i = 0; i < ( *aasworld ).numbboxes; i++ ) + { + ( *aasworld ).bboxes[i].presencetype = LittleLong( ( *aasworld ).bboxes[i].presencetype ); + ( *aasworld ).bboxes[i].flags = LittleLong( ( *aasworld ).bboxes[i].flags ); + for ( j = 0; j < 3; j++ ) + { + ( *aasworld ).bboxes[i].mins[j] = LittleLong( ( *aasworld ).bboxes[i].mins[j] ); + ( *aasworld ).bboxes[i].maxs[j] = LittleLong( ( *aasworld ).bboxes[i].maxs[j] ); + } //end for + } //end for + //vertexes + for ( i = 0; i < ( *aasworld ).numvertexes; i++ ) + { + for ( j = 0; j < 3; j++ ) + ( *aasworld ).vertexes[i][j] = LittleFloat( ( *aasworld ).vertexes[i][j] ); + } //end for + //planes + for ( i = 0; i < ( *aasworld ).numplanes; i++ ) + { + for ( j = 0; j < 3; j++ ) + ( *aasworld ).planes[i].normal[j] = LittleFloat( ( *aasworld ).planes[i].normal[j] ); + ( *aasworld ).planes[i].dist = LittleFloat( ( *aasworld ).planes[i].dist ); + ( *aasworld ).planes[i].type = LittleLong( ( *aasworld ).planes[i].type ); + } //end for + //edges + for ( i = 0; i < ( *aasworld ).numedges; i++ ) + { + ( *aasworld ).edges[i].v[0] = LittleLong( ( *aasworld ).edges[i].v[0] ); + ( *aasworld ).edges[i].v[1] = LittleLong( ( *aasworld ).edges[i].v[1] ); + } //end for + //edgeindex + for ( i = 0; i < ( *aasworld ).edgeindexsize; i++ ) + { + ( *aasworld ).edgeindex[i] = LittleLong( ( *aasworld ).edgeindex[i] ); + } //end for + //faces + for ( i = 0; i < ( *aasworld ).numfaces; i++ ) + { + ( *aasworld ).faces[i].planenum = LittleLong( ( *aasworld ).faces[i].planenum ); + ( *aasworld ).faces[i].faceflags = LittleLong( ( *aasworld ).faces[i].faceflags ); + ( *aasworld ).faces[i].numedges = LittleLong( ( *aasworld ).faces[i].numedges ); + ( *aasworld ).faces[i].firstedge = LittleLong( ( *aasworld ).faces[i].firstedge ); + ( *aasworld ).faces[i].frontarea = LittleLong( ( *aasworld ).faces[i].frontarea ); + ( *aasworld ).faces[i].backarea = LittleLong( ( *aasworld ).faces[i].backarea ); + } //end for + //face index + for ( i = 0; i < ( *aasworld ).faceindexsize; i++ ) + { + ( *aasworld ).faceindex[i] = LittleLong( ( *aasworld ).faceindex[i] ); + } //end for + //convex areas + for ( i = 0; i < ( *aasworld ).numareas; i++ ) + { + ( *aasworld ).areas[i].areanum = LittleLong( ( *aasworld ).areas[i].areanum ); + ( *aasworld ).areas[i].numfaces = LittleLong( ( *aasworld ).areas[i].numfaces ); + ( *aasworld ).areas[i].firstface = LittleLong( ( *aasworld ).areas[i].firstface ); + for ( j = 0; j < 3; j++ ) + { + ( *aasworld ).areas[i].mins[j] = LittleFloat( ( *aasworld ).areas[i].mins[j] ); + ( *aasworld ).areas[i].maxs[j] = LittleFloat( ( *aasworld ).areas[i].maxs[j] ); + ( *aasworld ).areas[i].center[j] = LittleFloat( ( *aasworld ).areas[i].center[j] ); + } //end for + } //end for + //area settings + for ( i = 0; i < ( *aasworld ).numareasettings; i++ ) + { + ( *aasworld ).areasettings[i].contents = LittleLong( ( *aasworld ).areasettings[i].contents ); + ( *aasworld ).areasettings[i].areaflags = LittleLong( ( *aasworld ).areasettings[i].areaflags ); + ( *aasworld ).areasettings[i].presencetype = LittleLong( ( *aasworld ).areasettings[i].presencetype ); + ( *aasworld ).areasettings[i].cluster = LittleLong( ( *aasworld ).areasettings[i].cluster ); + ( *aasworld ).areasettings[i].clusterareanum = LittleLong( ( *aasworld ).areasettings[i].clusterareanum ); + ( *aasworld ).areasettings[i].numreachableareas = LittleLong( ( *aasworld ).areasettings[i].numreachableareas ); + ( *aasworld ).areasettings[i].firstreachablearea = LittleLong( ( *aasworld ).areasettings[i].firstreachablearea ); + // Ridah + ( *aasworld ).areasettings[i].groundsteepness = LittleFloat( ( *aasworld ).areasettings[i].groundsteepness ); + } //end for + //area reachability + for ( i = 0; i < ( *aasworld ).reachabilitysize; i++ ) + { + ( *aasworld ).reachability[i].areanum = LittleLong( ( *aasworld ).reachability[i].areanum ); + ( *aasworld ).reachability[i].facenum = LittleLong( ( *aasworld ).reachability[i].facenum ); + ( *aasworld ).reachability[i].edgenum = LittleLong( ( *aasworld ).reachability[i].edgenum ); + for ( j = 0; j < 3; j++ ) + { + ( *aasworld ).reachability[i].start[j] = LittleFloat( ( *aasworld ).reachability[i].start[j] ); + ( *aasworld ).reachability[i].end[j] = LittleFloat( ( *aasworld ).reachability[i].end[j] ); + } //end for + ( *aasworld ).reachability[i].traveltype = LittleLong( ( *aasworld ).reachability[i].traveltype ); + ( *aasworld ).reachability[i].traveltime = LittleShort( ( *aasworld ).reachability[i].traveltime ); + } //end for + //nodes + for ( i = 0; i < ( *aasworld ).numnodes; i++ ) + { + ( *aasworld ).nodes[i].planenum = LittleLong( ( *aasworld ).nodes[i].planenum ); + ( *aasworld ).nodes[i].children[0] = LittleLong( ( *aasworld ).nodes[i].children[0] ); + ( *aasworld ).nodes[i].children[1] = LittleLong( ( *aasworld ).nodes[i].children[1] ); + } //end for + //cluster portals + for ( i = 0; i < ( *aasworld ).numportals; i++ ) + { + ( *aasworld ).portals[i].areanum = LittleLong( ( *aasworld ).portals[i].areanum ); + ( *aasworld ).portals[i].frontcluster = LittleLong( ( *aasworld ).portals[i].frontcluster ); + ( *aasworld ).portals[i].backcluster = LittleLong( ( *aasworld ).portals[i].backcluster ); + ( *aasworld ).portals[i].clusterareanum[0] = LittleLong( ( *aasworld ).portals[i].clusterareanum[0] ); + ( *aasworld ).portals[i].clusterareanum[1] = LittleLong( ( *aasworld ).portals[i].clusterareanum[1] ); + } //end for + //cluster portal index + for ( i = 0; i < ( *aasworld ).portalindexsize; i++ ) + { + ( *aasworld ).portalindex[i] = LittleLong( ( *aasworld ).portalindex[i] ); + } //end for + //cluster + for ( i = 0; i < ( *aasworld ).numclusters; i++ ) + { + ( *aasworld ).clusters[i].numareas = LittleLong( ( *aasworld ).clusters[i].numareas ); + ( *aasworld ).clusters[i].numportals = LittleLong( ( *aasworld ).clusters[i].numportals ); + ( *aasworld ).clusters[i].firstportal = LittleLong( ( *aasworld ).clusters[i].firstportal ); + } //end for +} //end of the function AAS_SwapAASData +//=========================================================================== +// dump the current loaded aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpAASData( void ) { + /* + if ((*aasworld).vertexes) FreeMemory((*aasworld).vertexes); + (*aasworld).vertexes = NULL; + if ((*aasworld).planes) FreeMemory((*aasworld).planes); + (*aasworld).planes = NULL; + if ((*aasworld).edges) FreeMemory((*aasworld).edges); + (*aasworld).edges = NULL; + if ((*aasworld).edgeindex) FreeMemory((*aasworld).edgeindex); + (*aasworld).edgeindex = NULL; + if ((*aasworld).faces) FreeMemory((*aasworld).faces); + (*aasworld).faces = NULL; + if ((*aasworld).faceindex) FreeMemory((*aasworld).faceindex); + (*aasworld).faceindex = NULL; + if ((*aasworld).areas) FreeMemory((*aasworld).areas); + (*aasworld).areas = NULL; + if ((*aasworld).areasettings) FreeMemory((*aasworld).areasettings); + (*aasworld).areasettings = NULL; + if ((*aasworld).reachability) FreeMemory((*aasworld).reachability); + (*aasworld).reachability = NULL; + */ + ( *aasworld ).loaded = false; +} //end of the function AAS_DumpAASData +//=========================================================================== +// allocate memory and read a lump of a AAS file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_LoadAASLump( FILE *fp, int offset, int length, void *buf ) { + if ( !length ) { + printf( "lump size 0\n" ); + return buf; + } //end if + //seek to the data + if ( fseek( fp, offset, SEEK_SET ) ) { + AAS_Error( "can't seek to lump\n" ); + AAS_DumpAASData(); + fclose( fp ); + return 0; + } //end if + //allocate memory + if ( !buf ) { + buf = (void *) GetClearedMemory( length ); + } + //read the data + if ( fread( (char *) buf, 1, length, fp ) != length ) { + AAS_Error( "can't read lump\n" ); + FreeMemory( buf ); + AAS_DumpAASData(); + fclose( fp ); + return NULL; + } //end if + return buf; +} //end of the function AAS_LoadAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DData( unsigned char *data, int size ) { + int i; + + for ( i = 0; i < size; i++ ) + { + data[i] ^= (unsigned char) i * 119; + } //end for +} //end of the function AAS_DData +//=========================================================================== +// load an aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_LoadAASFile( char *filename, int fpoffset, int fplength ) { + FILE *fp; + aas_header_t header; + int offset, length; + + //dump current loaded aas file + AAS_DumpAASData(); + //open the file + fp = fopen( filename, "rb" ); + if ( !fp ) { + AAS_Error( "can't open %s\n", filename ); + return false; + } //end if + //seek to the correct position (in the pak file) + if ( fseek( fp, fpoffset, SEEK_SET ) ) { + AAS_Error( "can't seek to file %s\n" ); + fclose( fp ); + return false; + } //end if + //read the header + if ( fread( &header, sizeof( aas_header_t ), 1, fp ) != 1 ) { + AAS_Error( "can't read header of file %s\n", filename ); + fclose( fp ); + return false; + } //end if + //check header identification + header.ident = LittleLong( header.ident ); + if ( header.ident != AASID ) { + AAS_Error( "%s is not an AAS file\n", filename ); + fclose( fp ); + return false; + } //end if + //check the version + header.version = LittleLong( header.version ); + if ( header.version != AASVERSION ) { + AAS_Error( "%s is version %i, not %i\n", filename, header.version, AASVERSION ); + fclose( fp ); + return false; + } //end if + // + if ( header.version == AASVERSION ) { + AAS_DData( (unsigned char *) &header + 8, sizeof( aas_header_t ) - 8 ); + } //end if + ( *aasworld ).bspchecksum = LittleLong( header.bspchecksum ); + //load the lumps: + //bounding boxes + offset = fpoffset + LittleLong( header.lumps[AASLUMP_BBOXES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_BBOXES].filelen ); + ( *aasworld ).bboxes = (aas_bbox_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).bboxes ); + if ( !( *aasworld ).bboxes ) { + return false; + } + ( *aasworld ).numbboxes = length / sizeof( aas_bbox_t ); + //vertexes + offset = fpoffset + LittleLong( header.lumps[AASLUMP_VERTEXES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_VERTEXES].filelen ); + ( *aasworld ).vertexes = (aas_vertex_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).vertexes ); + if ( !( *aasworld ).vertexes ) { + return false; + } + ( *aasworld ).numvertexes = length / sizeof( aas_vertex_t ); + //planes + offset = fpoffset + LittleLong( header.lumps[AASLUMP_PLANES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_PLANES].filelen ); + ( *aasworld ).planes = (aas_plane_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).planes ); + if ( !( *aasworld ).planes ) { + return false; + } + ( *aasworld ).numplanes = length / sizeof( aas_plane_t ); + //edges + offset = fpoffset + LittleLong( header.lumps[AASLUMP_EDGES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_EDGES].filelen ); + ( *aasworld ).edges = (aas_edge_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).edges ); + if ( !( *aasworld ).edges ) { + return false; + } + ( *aasworld ).numedges = length / sizeof( aas_edge_t ); + //edgeindex + offset = fpoffset + LittleLong( header.lumps[AASLUMP_EDGEINDEX].fileofs ); + length = LittleLong( header.lumps[AASLUMP_EDGEINDEX].filelen ); + ( *aasworld ).edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).edgeindex ); + if ( !( *aasworld ).edgeindex ) { + return false; + } + ( *aasworld ).edgeindexsize = length / sizeof( aas_edgeindex_t ); + //faces + offset = fpoffset + LittleLong( header.lumps[AASLUMP_FACES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_FACES].filelen ); + ( *aasworld ).faces = (aas_face_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).faces ); + if ( !( *aasworld ).faces ) { + return false; + } + ( *aasworld ).numfaces = length / sizeof( aas_face_t ); + //faceindex + offset = fpoffset + LittleLong( header.lumps[AASLUMP_FACEINDEX].fileofs ); + length = LittleLong( header.lumps[AASLUMP_FACEINDEX].filelen ); + ( *aasworld ).faceindex = (aas_faceindex_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).faceindex ); + if ( !( *aasworld ).faceindex ) { + return false; + } + ( *aasworld ).faceindexsize = length / sizeof( int ); + //convex areas + offset = fpoffset + LittleLong( header.lumps[AASLUMP_AREAS].fileofs ); + length = LittleLong( header.lumps[AASLUMP_AREAS].filelen ); + ( *aasworld ).areas = (aas_area_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).areas ); + if ( !( *aasworld ).areas ) { + return false; + } + ( *aasworld ).numareas = length / sizeof( aas_area_t ); + //area settings + offset = fpoffset + LittleLong( header.lumps[AASLUMP_AREASETTINGS].fileofs ); + length = LittleLong( header.lumps[AASLUMP_AREASETTINGS].filelen ); + ( *aasworld ).areasettings = (aas_areasettings_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).areasettings ); + if ( !( *aasworld ).areasettings ) { + return false; + } + ( *aasworld ).numareasettings = length / sizeof( aas_areasettings_t ); + //reachability list + offset = fpoffset + LittleLong( header.lumps[AASLUMP_REACHABILITY].fileofs ); + length = LittleLong( header.lumps[AASLUMP_REACHABILITY].filelen ); + ( *aasworld ).reachability = (aas_reachability_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).reachability ); + if ( length && !( *aasworld ).reachability ) { + return false; + } + ( *aasworld ).reachabilitysize = length / sizeof( aas_reachability_t ); + //nodes + offset = fpoffset + LittleLong( header.lumps[AASLUMP_NODES].fileofs ); + length = LittleLong( header.lumps[AASLUMP_NODES].filelen ); + ( *aasworld ).nodes = (aas_node_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).nodes ); + if ( !( *aasworld ).nodes ) { + return false; + } + ( *aasworld ).numnodes = length / sizeof( aas_node_t ); + //cluster portals + offset = fpoffset + LittleLong( header.lumps[AASLUMP_PORTALS].fileofs ); + length = LittleLong( header.lumps[AASLUMP_PORTALS].filelen ); + ( *aasworld ).portals = (aas_portal_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).portals ); + if ( length && !( *aasworld ).portals ) { + return false; + } + ( *aasworld ).numportals = length / sizeof( aas_portal_t ); + //cluster portal index + offset = fpoffset + LittleLong( header.lumps[AASLUMP_PORTALINDEX].fileofs ); + length = LittleLong( header.lumps[AASLUMP_PORTALINDEX].filelen ); + ( *aasworld ).portalindex = (aas_portalindex_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).portalindex ); + if ( length && !( *aasworld ).portalindex ) { + return false; + } + ( *aasworld ).portalindexsize = length / sizeof( aas_portalindex_t ); + //clusters + offset = fpoffset + LittleLong( header.lumps[AASLUMP_CLUSTERS].fileofs ); + length = LittleLong( header.lumps[AASLUMP_CLUSTERS].filelen ); + ( *aasworld ).clusters = (aas_cluster_t *) AAS_LoadAASLump( fp, offset, length, ( *aasworld ).clusters ); + if ( length && !( *aasworld ).clusters ) { + return false; + } + ( *aasworld ).numclusters = length / sizeof( aas_cluster_t ); + //swap everything + AAS_SwapAASData(); + //aas file is loaded + ( *aasworld ).loaded = true; + //close the file + fclose( fp ); + return true; +} //end of the function AAS_LoadAASFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_WriteAASLump( FILE *fp, aas_header_t *h, int lumpnum, void *data, int length ) { + aas_lump_t *lump; + + lump = &h->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell( fp ) ); + lump->filelen = LittleLong( length ); + + if ( length > 0 ) { + if ( fwrite( data, length, 1, fp ) < 1 ) { + Log_Print( "error writing lump %s\n", lumpnum ); + fclose( fp ); + return false; + } //end if + } //end if + return true; +} //end of the function AAS_WriteAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowNumReachabilities( int tt, char *name ) { + int i, num; + + num = 0; + for ( i = 0; i < ( *aasworld ).reachabilitysize; i++ ) + { + if ( ( ( *aasworld ).reachability[i].traveltype & TRAVELTYPE_MASK ) == tt ) { + num++; + } + } //end for + Log_Print( "%6d %s\n", num, name ); +} //end of the function AAS_ShowNumReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowTotals( void ) { + Log_Print( "numvertexes = %d\r\n", ( *aasworld ).numvertexes ); + Log_Print( "numplanes = %d\r\n", ( *aasworld ).numplanes ); + Log_Print( "numedges = %d\r\n", ( *aasworld ).numedges ); + Log_Print( "edgeindexsize = %d\r\n", ( *aasworld ).edgeindexsize ); + Log_Print( "numfaces = %d\r\n", ( *aasworld ).numfaces ); + Log_Print( "faceindexsize = %d\r\n", ( *aasworld ).faceindexsize ); + Log_Print( "numareas = %d\r\n", ( *aasworld ).numareas ); + Log_Print( "numareasettings = %d\r\n", ( *aasworld ).numareasettings ); + Log_Print( "reachabilitysize = %d\r\n", ( *aasworld ).reachabilitysize ); + Log_Print( "numnodes = %d\r\n", ( *aasworld ).numnodes ); + Log_Print( "numportals = %d\r\n", ( *aasworld ).numportals ); + Log_Print( "portalindexsize = %d\r\n", ( *aasworld ).portalindexsize ); + Log_Print( "numclusters = %d\r\n", ( *aasworld ).numclusters ); + AAS_ShowNumReachabilities( TRAVEL_WALK, "walk" ); + AAS_ShowNumReachabilities( TRAVEL_CROUCH, "crouch" ); + AAS_ShowNumReachabilities( TRAVEL_BARRIERJUMP, "barrier jump" ); + AAS_ShowNumReachabilities( TRAVEL_JUMP, "jump" ); + AAS_ShowNumReachabilities( TRAVEL_LADDER, "ladder" ); + AAS_ShowNumReachabilities( TRAVEL_WALKOFFLEDGE, "walk off ledge" ); + AAS_ShowNumReachabilities( TRAVEL_SWIM, "swim" ); + AAS_ShowNumReachabilities( TRAVEL_WATERJUMP, "water jump" ); + AAS_ShowNumReachabilities( TRAVEL_TELEPORT, "teleport" ); + AAS_ShowNumReachabilities( TRAVEL_ELEVATOR, "elevator" ); + AAS_ShowNumReachabilities( TRAVEL_ROCKETJUMP, "rocket jump" ); + AAS_ShowNumReachabilities( TRAVEL_BFGJUMP, "bfg jump" ); + AAS_ShowNumReachabilities( TRAVEL_GRAPPLEHOOK, "grapple hook" ); + AAS_ShowNumReachabilities( TRAVEL_DOUBLEJUMP, "double jump" ); + AAS_ShowNumReachabilities( TRAVEL_RAMPJUMP, "ramp jump" ); + AAS_ShowNumReachabilities( TRAVEL_STRAFEJUMP, "strafe jump" ); + AAS_ShowNumReachabilities( TRAVEL_JUMPPAD, "jump pad" ); + AAS_ShowNumReachabilities( TRAVEL_FUNCBOB, "func bob" ); + +} //end of the function AAS_ShowTotals +//=========================================================================== +// aas data is useless after writing to file because it is byte swapped +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_WriteAASFile( char *filename ) { + aas_header_t header; + FILE *fp; + + Log_Print( "writing %s\n", filename ); + AAS_ShowTotals(); + //swap the aas data + AAS_SwapAASData(); + //initialize the file header + memset( &header, 0, sizeof( aas_header_t ) ); + header.ident = LittleLong( AASID ); + header.version = LittleLong( AASVERSION ); + header.bspchecksum = LittleLong( ( *aasworld ).bspchecksum ); + //open a new file + fp = fopen( filename, "wb" ); + if ( !fp ) { + Log_Print( "error opening %s\n", filename ); + return false; + } //end if + //write the header + if ( fwrite( &header, sizeof( aas_header_t ), 1, fp ) < 1 ) { + fclose( fp ); + return false; + } //end if + //add the data lumps to the file + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_BBOXES, ( *aasworld ).bboxes, + ( *aasworld ).numbboxes * sizeof( aas_bbox_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_VERTEXES, ( *aasworld ).vertexes, + ( *aasworld ).numvertexes * sizeof( aas_vertex_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_PLANES, ( *aasworld ).planes, + ( *aasworld ).numplanes * sizeof( aas_plane_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_EDGES, ( *aasworld ).edges, + ( *aasworld ).numedges * sizeof( aas_edge_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_EDGEINDEX, ( *aasworld ).edgeindex, + ( *aasworld ).edgeindexsize * sizeof( aas_edgeindex_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_FACES, ( *aasworld ).faces, + ( *aasworld ).numfaces * sizeof( aas_face_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_FACEINDEX, ( *aasworld ).faceindex, + ( *aasworld ).faceindexsize * sizeof( aas_faceindex_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_AREAS, ( *aasworld ).areas, + ( *aasworld ).numareas * sizeof( aas_area_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_AREASETTINGS, ( *aasworld ).areasettings, + ( *aasworld ).numareasettings * sizeof( aas_areasettings_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_REACHABILITY, ( *aasworld ).reachability, + ( *aasworld ).reachabilitysize * sizeof( aas_reachability_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_NODES, ( *aasworld ).nodes, + ( *aasworld ).numnodes * sizeof( aas_node_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_PORTALS, ( *aasworld ).portals, + ( *aasworld ).numportals * sizeof( aas_portal_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_PORTALINDEX, ( *aasworld ).portalindex, + ( *aasworld ).portalindexsize * sizeof( aas_portalindex_t ) ) ) { + return false; + } + if ( !AAS_WriteAASLump( fp, &header, AASLUMP_CLUSTERS, ( *aasworld ).clusters, + ( *aasworld ).numclusters * sizeof( aas_cluster_t ) ) ) { + return false; + } + //rewrite the header with the added lumps + fseek( fp, 0, SEEK_SET ); + AAS_DData( (unsigned char *) &header + 8, sizeof( aas_header_t ) - 8 ); + if ( fwrite( &header, sizeof( aas_header_t ), 1, fp ) < 1 ) { + fclose( fp ); + return false; + } //end if + //close the file + fclose( fp ); + return true; +} //end of the function AAS_WriteAASFile + diff --git a/src/bspc/aas_file.h b/src/bspc/aas_file.h new file mode 100644 index 0000000..6544f2b --- /dev/null +++ b/src/bspc/aas_file.h @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_write.h +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +qboolean AAS_WriteAASFile( char *filename ); +qboolean AAS_LoadAASFile( char *filename, int fpoffset, int fplength ); + diff --git a/src/bspc/aas_gsubdiv.c b/src/bspc/aas_gsubdiv.c new file mode 100644 index 0000000..0acfa99 --- /dev/null +++ b/src/bspc/aas_gsubdiv.c @@ -0,0 +1,688 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_gsubdiv.c +// Function: Gravitational Subdivision +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "..\botlib\aasfile.h" +#include "aas_create.h" +#include "aas_store.h" +#include "aas_cfg.h" + +#define FACECLIP_EPSILON 0.2 +#define FACE_EPSILON 1.0 + +int numgravitationalsubdivisions = 0; +int numladdersubdivisions = 0; + +//NOTE: only do gravitational subdivision BEFORE area merging!!!!!!! +// because the bsp tree isn't refreshes like with ladder subdivision + +//=========================================================================== +// NOTE: the original face is invalid after splitting +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SplitFace( tmp_face_t *face, vec3_t normal, float dist, + tmp_face_t **frontface, tmp_face_t **backface ) { + winding_t *frontw, *backw; + + // + *frontface = *backface = NULL; + + ClipWindingEpsilon( face->winding, normal, dist, FACECLIP_EPSILON, &frontw, &backw ); + +#ifdef DEBUG + // + if ( frontw ) { + if ( WindingIsTiny( frontw ) ) { + Log_Write( "AAS_SplitFace: tiny back face\r\n" ); + FreeWinding( frontw ); + frontw = NULL; + } //end if + } //end if + if ( backw ) { + if ( WindingIsTiny( backw ) ) { + Log_Write( "AAS_SplitFace: tiny back face\r\n" ); + FreeWinding( backw ); + backw = NULL; + } //end if + } //end if +#endif //DEBUG + //if the winding was split + if ( frontw ) { + //check bounds + ( *frontface ) = AAS_AllocTmpFace(); + ( *frontface )->planenum = face->planenum; + ( *frontface )->winding = frontw; + ( *frontface )->faceflags = face->faceflags; + } //end if + if ( backw ) { + //check bounds + ( *backface ) = AAS_AllocTmpFace(); + ( *backface )->planenum = face->planenum; + ( *backface )->winding = backw; + ( *backface )->faceflags = face->faceflags; + } //end if +} //end of the function AAS_SplitFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *AAS_SplitWinding( tmp_area_t *tmparea, int planenum ) { + tmp_face_t *face; + plane_t *plane; + int side; + winding_t *splitwinding; + + // + plane = &mapplanes[planenum]; + //create a split winding, first base winding for plane + splitwinding = BaseWindingForPlane( plane->normal, plane->dist ); + //chop with all the faces of the area + for ( face = tmparea->tmpfaces; face && splitwinding; face = face->next[side] ) + { + //side of the face the original area was on + side = face->frontarea != tmparea; + plane = &mapplanes[face->planenum ^ side]; + ChopWindingInPlace( &splitwinding, plane->normal, plane->dist, 0 ); // PLANESIDE_EPSILON); + } //end for + return splitwinding; +} //end of the function AAS_SplitWinding +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TestSplitPlane( tmp_area_t *tmparea, vec3_t normal, float dist, + int *facesplits, int *groundsplits, int *epsilonfaces ) { + int j, side, front, back, planenum; + float d, d_front, d_back; + tmp_face_t *face; + winding_t *w; + + *facesplits = *groundsplits = *epsilonfaces = 0; + + planenum = FindFloatPlane( normal, dist ); + + w = AAS_SplitWinding( tmparea, planenum ); + if ( !w ) { + return false; + } + FreeWinding( w ); + // + for ( face = tmparea->tmpfaces; face; face = face->next[side] ) + { + //side of the face the area is on + side = face->frontarea != tmparea; + + if ( ( face->planenum & ~1 ) == ( planenum & ~1 ) ) { + Log_Print( "AAS_TestSplitPlane: tried face plane as splitter\n" ); + return false; + } //end if + w = face->winding; + //reset distance at front and back side of plane + d_front = d_back = 0; + //reset front and back flags + front = back = 0; + for ( j = 0; j < w->numpoints; j++ ) + { + d = DotProduct( w->p[j], normal ) - dist; + if ( d > d_front ) { + d_front = d; + } + if ( d < d_back ) { + d_back = d; + } + + if ( d > 0.4 ) { // PLANESIDE_EPSILON) + front = 1; + } + if ( d < -0.4 ) { // PLANESIDE_EPSILON) + back = 1; + } + } //end for + //check for an epsilon face + if ( ( d_front > FACECLIP_EPSILON && d_front < FACE_EPSILON ) + || ( d_back < -FACECLIP_EPSILON && d_back > -FACE_EPSILON ) ) { + ( *epsilonfaces )++; + } //end if + //if the face has points at both sides of the plane + if ( front && back ) { + ( *facesplits )++; + if ( face->faceflags & FACE_GROUND ) { + ( *groundsplits )++; + } //end if + } //end if + } //end for + return true; +} //end of the function AAS_TestSplitPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SplitArea( tmp_area_t *tmparea, int planenum, tmp_area_t **frontarea, tmp_area_t **backarea ) { + int side; + tmp_area_t *facefrontarea, *facebackarea, *faceotherarea; + tmp_face_t *face, *frontface, *backface, *splitface, *nextface; + winding_t *splitwinding; + plane_t *splitplane; + +/* +#ifdef AW_DEBUG + int facesplits, groundsplits, epsilonface; + Log_Print("\n----------------------\n"); + Log_Print("splitting area %d\n", areanum); + Log_Print("with normal = \'%f %f %f\', dist = %f\n", normal[0], normal[1], normal[2], dist); + AAS_TestSplitPlane(areanum, normal, dist, + &facesplits, &groundsplits, &epsilonface); + Log_Print("face splits = %d\nground splits = %d\n", facesplits, groundsplits); + if (epsilonface) Log_Print("aaahh epsilon face\n"); +#endif //AW_DEBUG*/ + //the original area + + AAS_FlipAreaFaces( tmparea ); + AAS_CheckArea( tmparea ); + // + splitplane = &mapplanes[planenum]; +/* //create a split winding, first base winding for plane + splitwinding = BaseWindingForPlane(splitplane->normal, splitplane->dist); + //chop with all the faces of the area + for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side]) + { + //side of the face the original area was on + side = face->frontarea != tmparea->areanum; + plane = &mapplanes[face->planenum ^ side]; + ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON); + } //end for*/ + splitwinding = AAS_SplitWinding( tmparea, planenum ); + if ( !splitwinding ) { +/* +#ifdef DEBUG + AAS_TestSplitPlane(areanum, normal, dist, + &facesplits, &groundsplits, &epsilonface); + Log_Print("\nface splits = %d\nground splits = %d\n", facesplits, groundsplits); + if (epsilonface) Log_Print("aaahh epsilon face\n"); +#endif //DEBUG*/ + Error( "AAS_SplitArea: no split winding when splitting area %d\n", tmparea->areanum ); + } //end if + //create a split face + splitface = AAS_AllocTmpFace(); + //get the map plane + splitface->planenum = planenum; + //store the split winding + splitface->winding = splitwinding; + //the new front area + ( *frontarea ) = AAS_AllocTmpArea(); + ( *frontarea )->presencetype = tmparea->presencetype; + ( *frontarea )->contents = tmparea->contents; + ( *frontarea )->modelnum = tmparea->modelnum; + ( *frontarea )->tmpfaces = NULL; + //the new back area + ( *backarea ) = AAS_AllocTmpArea(); + ( *backarea )->presencetype = tmparea->presencetype; + ( *backarea )->contents = tmparea->contents; + ( *backarea )->modelnum = tmparea->modelnum; + ( *backarea )->tmpfaces = NULL; + //add the split face to the new areas + AAS_AddFaceSideToArea( splitface, 0, ( *frontarea ) ); + AAS_AddFaceSideToArea( splitface, 1, ( *backarea ) ); + + //split all the faces of the original area + for ( face = tmparea->tmpfaces; face; face = nextface ) + { + //side of the face the original area was on + side = face->frontarea != tmparea; + //next face of the original area + nextface = face->next[side]; + //front area of the face + facefrontarea = face->frontarea; + //back area of the face + facebackarea = face->backarea; + //remove the face from both the front and back areas + if ( facefrontarea ) { + AAS_RemoveFaceFromArea( face, facefrontarea ); + } + if ( facebackarea ) { + AAS_RemoveFaceFromArea( face, facebackarea ); + } + //split the face + AAS_SplitFace( face, splitplane->normal, splitplane->dist, &frontface, &backface ); + //free the original face + AAS_FreeTmpFace( face ); + //get the number of the area at the other side of the face + if ( side ) { + faceotherarea = facefrontarea; + } else { faceotherarea = facebackarea;} + //if there is an area at the other side of the original face + if ( faceotherarea ) { + if ( frontface ) { + AAS_AddFaceSideToArea( frontface, !side, faceotherarea ); + } + if ( backface ) { + AAS_AddFaceSideToArea( backface, !side, faceotherarea ); + } + } //end if + //add the front and back part left after splitting the original face to the new areas + if ( frontface ) { + AAS_AddFaceSideToArea( frontface, side, ( *frontarea ) ); + } + if ( backface ) { + AAS_AddFaceSideToArea( backface, side, ( *backarea ) ); + } + } //end for + + if ( !( *frontarea )->tmpfaces ) { + Log_Print( "AAS_SplitArea: front area without faces\n" ); + } + if ( !( *backarea )->tmpfaces ) { + Log_Print( "AAS_SplitArea: back area without faces\n" ); + } + + tmparea->invalid = true; +/* +#ifdef AW_DEBUG + for (i = 0, face = frontarea->tmpfaces; face; face = face->next[side]) + { + side = face->frontarea != frontarea->areanum; + i++; + } //end for + Log_Print("created front area %d with %d faces\n", frontarea->areanum, i); + + for (i = 0, face = backarea->tmpfaces; face; face = face->next[side]) + { + side = face->frontarea != backarea->areanum; + i++; + } //end for + Log_Print("created back area %d with %d faces\n", backarea->areanum, i); +#endif //AW_DEBUG*/ + + AAS_FlipAreaFaces( ( *frontarea ) ); + AAS_FlipAreaFaces( ( *backarea ) ); + // + AAS_CheckArea( ( *frontarea ) ); + AAS_CheckArea( ( *backarea ) ); +} //end of the function AAS_SplitArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindBestAreaSplitPlane( tmp_area_t *tmparea, vec3_t normal, float *dist ) { + int side1, side2; + int foundsplitter, facesplits, groundsplits, epsilonfaces, bestepsilonfaces; + float bestvalue, value; + tmp_face_t *face1, *face2; + vec3_t tmpnormal, invgravity; + float tmpdist; + + //get inverse of gravity direction + VectorCopy( cfg.phys_gravitydirection, invgravity ); + VectorInverse( invgravity ); + + foundsplitter = false; + bestvalue = -999999; + bestepsilonfaces = 0; + // +#ifdef AW_DEBUG + Log_Print( "finding split plane for area %d\n", tmparea->areanum ); +#endif //AW_DEBUG + for ( face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1] ) + { + //side of the face the area is on + side1 = face1->frontarea != tmparea; + // + if ( WindingIsTiny( face1->winding ) ) { + Log_Write( "gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum ); + continue; + } //end if + //if the face isn't a gap or ground there's no split edge + if ( !( face1->faceflags & FACE_GROUND ) && !AAS_GapFace( face1, side1 ) ) { + continue; + } + // + for ( face2 = face1->next[side1]; face2; face2 = face2->next[side2] ) + { + //side of the face the area is on + side2 = face2->frontarea != tmparea; + // + if ( WindingIsTiny( face1->winding ) ) { + Log_Write( "gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum ); + continue; + } //end if + //if the face isn't a gap or ground there's no split edge + if ( !( face2->faceflags & FACE_GROUND ) && !AAS_GapFace( face2, side2 ) ) { + continue; + } + //only split between gaps and ground + if ( !( ( ( face1->faceflags & FACE_GROUND ) && AAS_GapFace( face2, side2 ) ) || + ( ( face2->faceflags & FACE_GROUND ) && AAS_GapFace( face1, side1 ) ) ) ) { + continue; + } + //find a plane seperating the windings of the faces + if ( !FindPlaneSeperatingWindings( face1->winding, face2->winding, invgravity, + tmpnormal, &tmpdist ) ) { + continue; + } +#ifdef AW_DEBUG + Log_Print( "normal = \'%f %f %f\', dist = %f\n", + tmpnormal[0], tmpnormal[1], tmpnormal[2], tmpdist ); +#endif //AW_DEBUG + //get metrics for this vertical plane + if ( !AAS_TestSplitPlane( tmparea, tmpnormal, tmpdist, + &facesplits, &groundsplits, &epsilonfaces ) ) { + continue; + } //end if +#ifdef AW_DEBUG + Log_Print( "face splits = %d\nground splits = %d\n", + facesplits, groundsplits ); +#endif //AW_DEBUG + value = 100 - facesplits - 2 * groundsplits; + //avoid epsilon faces + value += epsilonfaces * -1000; + if ( value > bestvalue ) { + VectorCopy( tmpnormal, normal ); + *dist = tmpdist; + bestvalue = value; + bestepsilonfaces = epsilonfaces; + foundsplitter = true; + } //end if + } //end for + } //end for + if ( bestepsilonfaces ) { + Log_Write( "found %d epsilon faces trying to split area %d\r\n", + epsilonfaces, tmparea->areanum ); + } //end else + return foundsplitter; +} //end of the function AAS_FindBestAreaSplitPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_SubdivideArea_r( tmp_node_t *tmpnode ) { + int planenum; + tmp_area_t *frontarea, *backarea; + tmp_node_t *tmpnode1, *tmpnode2; + vec3_t normal; + float dist; + + if ( AAS_FindBestAreaSplitPlane( tmpnode->tmparea, normal, &dist ) ) { + qprintf( "\r%6d", ++numgravitationalsubdivisions ); + // + planenum = FindFloatPlane( normal, dist ); + //split the area + AAS_SplitArea( tmpnode->tmparea, planenum, &frontarea, &backarea ); + // + tmpnode->tmparea = NULL; + tmpnode->planenum = FindFloatPlane( normal, dist ); + // + tmpnode1 = AAS_AllocTmpNode(); + tmpnode1->planenum = 0; + tmpnode1->tmparea = frontarea; + // + tmpnode2 = AAS_AllocTmpNode(); + tmpnode2->planenum = 0; + tmpnode2->tmparea = backarea; + //subdivide the areas created by splitting recursively + tmpnode->children[0] = AAS_SubdivideArea_r( tmpnode1 ); + tmpnode->children[1] = AAS_SubdivideArea_r( tmpnode2 ); + } //end if + return tmpnode; +} //end of the function AAS_SubdivideArea_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_GravitationalSubdivision_r( tmp_node_t *tmpnode ) { + //if this is a solid leaf + if ( !tmpnode ) { + return NULL; + } + //negative so it's an area + if ( tmpnode->tmparea ) { + return AAS_SubdivideArea_r( tmpnode ); + } + //do the children recursively + tmpnode->children[0] = AAS_GravitationalSubdivision_r( tmpnode->children[0] ); + tmpnode->children[1] = AAS_GravitationalSubdivision_r( tmpnode->children[1] ); + return tmpnode; +} //end of the function AAS_GravitationalSubdivision_r +//=========================================================================== +// NOTE: merge faces and melt edges first +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_GravitationalSubdivision( void ) { + Log_Write( "AAS_GravitationalSubdivision\r\n" ); + numgravitationalsubdivisions = 0; + qprintf( "%6i gravitational subdivisions", numgravitationalsubdivisions ); + //start with the head node + AAS_GravitationalSubdivision_r( tmpaasworld.nodes ); + qprintf( "\n" ); + Log_Write( "%6i gravitational subdivisions\r\n", numgravitationalsubdivisions ); +} //end of the function AAS_GravitationalSubdivision +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_RefreshLadderSubdividedTree_r( tmp_node_t *tmpnode, tmp_area_t *tmparea, + tmp_node_t *tmpnode1, tmp_node_t *tmpnode2, int planenum ) { + //if this is a solid leaf + if ( !tmpnode ) { + return NULL; + } + //negative so it's an area + if ( tmpnode->tmparea ) { + if ( tmpnode->tmparea == tmparea ) { + tmpnode->tmparea = NULL; + tmpnode->planenum = planenum; + tmpnode->children[0] = tmpnode1; + tmpnode->children[1] = tmpnode2; + } //end if + return tmpnode; + } //end if + //do the children recursively + tmpnode->children[0] = AAS_RefreshLadderSubdividedTree_r( tmpnode->children[0], + tmparea, tmpnode1, tmpnode2, planenum ); + tmpnode->children[1] = AAS_RefreshLadderSubdividedTree_r( tmpnode->children[1], + tmparea, tmpnode1, tmpnode2, planenum ); + return tmpnode; +} //end of the function AAS_RefreshLadderSubdividedTree_r +//=========================================================================== +// find an area with ladder faces and ground faces that are not connected +// split the area with a horizontal plane at the lowest vertex of all +// ladder faces in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_LadderSubdivideArea_r( tmp_node_t *tmpnode ) { + int side1, i, planenum; + int foundladderface, foundgroundface; + float dist; + tmp_area_t *tmparea, *frontarea, *backarea; + tmp_face_t *face1; + tmp_node_t *tmpnode1, *tmpnode2; + vec3_t lowestpoint, normal = {0, 0, 1}; + plane_t *plane; + winding_t *w; + + tmparea = tmpnode->tmparea; + //skip areas with a liquid + if ( tmparea->contents & ( AREACONTENTS_WATER + | AREACONTENTS_LAVA + | AREACONTENTS_SLIME ) ) { + return tmpnode; + } + //must be possible to stand in the area + if ( !( tmparea->presencetype & PRESENCE_NORMAL ) ) { + return tmpnode; + } + // + foundladderface = false; + foundgroundface = false; + lowestpoint[2] = 99999; + // + for ( face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1] ) + { + //side of the face the area is on + side1 = face1->frontarea != tmparea; + //if the face is a ladder face + if ( face1->faceflags & FACE_LADDER ) { + plane = &mapplanes[face1->planenum]; + //the ladder face plane should be pretty much vertical + if ( DotProduct( plane->normal, normal ) > -0.1 ) { + foundladderface = true; + //find lowest point + for ( i = 0; i < face1->winding->numpoints; i++ ) + { + if ( face1->winding->p[i][2] < lowestpoint[2] ) { + VectorCopy( face1->winding->p[i], lowestpoint ); + } //end if + } //end for + } //end if + } //end if + else if ( face1->faceflags & FACE_GROUND ) { + foundgroundface = true; + } //end else if + } //end for + // + if ( ( !foundladderface ) || ( !foundgroundface ) ) { + return tmpnode; + } + // + for ( face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1] ) + { + //side of the face the area is on + side1 = face1->frontarea != tmparea; + //if the face isn't a ground face + if ( !( face1->faceflags & FACE_GROUND ) ) { + continue; + } + //the ground plane + plane = &mapplanes[face1->planenum]; + //get the difference between the ground plane and the lowest point + dist = DotProduct( plane->normal, lowestpoint ) - plane->dist; + //if the lowest point is very near one of the ground planes + if ( dist > -1 && dist < 1 ) { + return tmpnode; + } //end if + } //end for + // + dist = DotProduct( normal, lowestpoint ); + planenum = FindFloatPlane( normal, dist ); + // + w = AAS_SplitWinding( tmparea, planenum ); + if ( !w ) { + return tmpnode; + } + FreeWinding( w ); + //split the area with a horizontal plane through the lowest point + qprintf( "\r%6d", ++numladdersubdivisions ); + // + AAS_SplitArea( tmparea, planenum, &frontarea, &backarea ); + // + tmpnode->tmparea = NULL; + tmpnode->planenum = planenum; + // + tmpnode1 = AAS_AllocTmpNode(); + tmpnode1->planenum = 0; + tmpnode1->tmparea = frontarea; + // + tmpnode2 = AAS_AllocTmpNode(); + tmpnode2->planenum = 0; + tmpnode2->tmparea = backarea; + //subdivide the areas created by splitting recursively + tmpnode->children[0] = AAS_LadderSubdivideArea_r( tmpnode1 ); + tmpnode->children[1] = AAS_LadderSubdivideArea_r( tmpnode2 ); + //refresh the tree + AAS_RefreshLadderSubdividedTree_r( tmpaasworld.nodes, tmparea, tmpnode1, tmpnode2, planenum ); + // + return tmpnode; +} //end of the function AAS_LadderSubdivideArea_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_LadderSubdivision_r( tmp_node_t *tmpnode ) { + //if this is a solid leaf + if ( !tmpnode ) { + return 0; + } + //negative so it's an area + if ( tmpnode->tmparea ) { + return AAS_LadderSubdivideArea_r( tmpnode ); + } + //do the children recursively + tmpnode->children[0] = AAS_LadderSubdivision_r( tmpnode->children[0] ); + tmpnode->children[1] = AAS_LadderSubdivision_r( tmpnode->children[1] ); + return tmpnode; +} //end of the function AAS_LadderSubdivision_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_LadderSubdivision( void ) { + Log_Write( "AAS_LadderSubdivision\r\n" ); + numladdersubdivisions = 0; + qprintf( "%6i ladder subdivisions", numladdersubdivisions ); + //start with the head node + AAS_LadderSubdivision_r( tmpaasworld.nodes ); + // + qprintf( "\n" ); + Log_Write( "%6i ladder subdivisions\r\n", numladdersubdivisions ); +} //end of the function AAS_LadderSubdivision + diff --git a/src/bspc/aas_gsubdiv.h b/src/bspc/aas_gsubdiv.h new file mode 100644 index 0000000..7038d45 --- /dev/null +++ b/src/bspc/aas_gsubdiv.h @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_gsubdiv.h +// Function: Gravitational subdivision +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +//works with the global tmpaasworld +void AAS_GravitationalSubdivision( void ); +void AAS_LadderSubdivision( void ); diff --git a/src/bspc/aas_map.c b/src/bspc/aas_map.c new file mode 100644 index 0000000..370c1c2 --- /dev/null +++ b/src/bspc/aas_map.c @@ -0,0 +1,838 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_map.c +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-03 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "l_mem.h" +#include "..\botlib\aasfile.h" //aas_bbox_t +#include "aas_store.h" //AAS_MAX_BBOXES +#include "aas_cfg.h" +#include "..\game\surfaceflags.h" + +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + +#define BBOX_NORMAL_EPSILON 0.0001 + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t BoxOriginDistanceFromPlane( vec3_t normal, vec3_t mins, vec3_t maxs, int side ) { + vec3_t v1, v2; + int i; + + if ( side ) { + for ( i = 0; i < 3; i++ ) + { + if ( normal[i] > BBOX_NORMAL_EPSILON ) { + v1[i] = maxs[i]; + } else if ( normal[i] < -BBOX_NORMAL_EPSILON ) { + v1[i] = mins[i]; + } else { v1[i] = 0;} + } //end for + } //end if + else + { + for ( i = 0; i < 3; i++ ) + { + if ( normal[i] > BBOX_NORMAL_EPSILON ) { + v1[i] = mins[i]; + } else if ( normal[i] < -BBOX_NORMAL_EPSILON ) { + v1[i] = maxs[i]; + } else { v1[i] = 0;} + } //end for + } //end else + VectorCopy( normal, v2 ); + VectorInverse( v2 ); + return DotProduct( v1, v2 ); +} //end of the function BoxOriginDistanceFromPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t CapsuleOriginDistanceFromPlane( vec3_t normal, vec3_t mins, vec3_t maxs ) { + float offset_up, offset_down, width, radius; + + width = maxs[0] - mins[0]; + // if the box is less high then it is wide + if ( maxs[2] - mins[2] < width ) { + width = maxs[2] - mins[2]; + } + radius = width * 0.5; + // offset to upper and lower sphere + offset_up = maxs[2] - radius; + offset_down = -mins[2] - radius; + + // if normal points upward + if ( normal[2] > 0 ) { + // touches lower sphere first + return normal[2] * offset_down + radius; + } else { + // touched upper sphere first + return -normal[2] * offset_up + radius; + } +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ExpandMapBrush( mapbrush_t *brush, vec3_t mins, vec3_t maxs ) { + int sn; + float dist; + side_t *s; + plane_t *plane; + + for ( sn = 0; sn < brush->numsides; sn++ ) + { + s = brush->original_sides + sn; + plane = &mapplanes[s->planenum]; + dist = plane->dist; + if ( capsule_collision ) { + dist += CapsuleOriginDistanceFromPlane( plane->normal, mins, maxs ); + } else { + dist += BoxOriginDistanceFromPlane( plane->normal, mins, maxs, 0 ); + } + s->planenum = FindFloatPlane( plane->normal, dist ); + //the side isn't a bevel after expanding + s->flags &= ~SFL_BEVEL; + //don't skip the surface + s->surf &= ~SURF_SKIP; + //make sure the texinfo is not TEXINFO_NODE + //when player clip contents brushes are read from the bsp tree + //they have the texinfo field set to TEXINFO_NODE + //s->texinfo = 0; + } //end for +} //end of the function AAS_ExpandMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetTexinfo( mapbrush_t *brush ) { + int n; + side_t *side; + + if ( brush->contents & ( CONTENTS_LADDER + | CONTENTS_AREAPORTAL + | CONTENTS_CLUSTERPORTAL + | CONTENTS_TELEPORTER + | CONTENTS_JUMPPAD + | CONTENTS_DONOTENTER + | CONTENTS_DONOTENTER_LARGE + | CONTENTS_WATER + | CONTENTS_LAVA + | CONTENTS_SLIME + | CONTENTS_WINDOW + | CONTENTS_PLAYERCLIP ) ) { + //we just set texinfo to 0 because these brush sides MUST be used as + //bsp splitters textured or not textured + for ( n = 0; n < brush->numsides; n++ ) + { + side = brush->original_sides + n; + //side->flags |= SFL_TEXTURED|SFL_VISIBLE; + side->texinfo = 0; + } //end for + } //end if + else + { + //only use brush sides as splitters if they are textured + //texinfo of non-textured sides will be set to TEXINFO_NODE + for ( n = 0; n < brush->numsides; n++ ) + { + side = brush->original_sides + n; + //don't use side as splitter (set texinfo to TEXINFO_NODE) if not textured + if ( side->flags & ( SFL_TEXTURED | SFL_BEVEL ) ) { + side->texinfo = 0; + } else { side->texinfo = TEXINFO_NODE;} + } //end for + } //end else +} //end of the function AAS_SetTexinfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeBrushWindings( mapbrush_t *brush ) { + int n; + side_t *side; + // + for ( n = 0; n < brush->numsides; n++ ) + { + side = brush->original_sides + n; + // + if ( side->winding ) { + FreeWinding( side->winding ); + } + } //end for +} //end of the function FreeBrushWindings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddMapBrushSide( mapbrush_t *brush, int planenum ) { + side_t *side; + // + if ( nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES ) { + Error( "MAX_MAPFILE_BRUSHSIDES" ); + } + // + side = brush->original_sides + brush->numsides; + side->original = NULL; + side->winding = NULL; + side->contents = brush->contents; + side->flags &= ~( SFL_BEVEL | SFL_VISIBLE ); + side->surf = 0; + side->planenum = planenum; + side->texinfo = 0; + // + nummapbrushsides++; + brush->numsides++; +} //end of the function AAS_AddMapBrushSide +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FixMapBrush( mapbrush_t *brush ) { + int i, j, planenum; + float dist; + winding_t *w; + plane_t *plane, *plane1, *plane2; + side_t *side; + vec3_t normal; + + //calculate the brush bounds + ClearBounds( brush->mins, brush->maxs ); + for ( i = 0; i < brush->numsides; i++ ) + { + plane = &mapplanes[brush->original_sides[i].planenum]; + w = BaseWindingForPlane( plane->normal, plane->dist ); + for ( j = 0; j < brush->numsides && w; j++ ) + { + if ( i == j ) { + continue; + } + //there are no brush bevels marked but who cares :) + if ( brush->original_sides[j].flags & SFL_BEVEL ) { + continue; + } + plane = &mapplanes[brush->original_sides[j].planenum ^ 1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON); + } //end for + + side = &brush->original_sides[i]; + side->winding = w; + if ( w ) { + for ( j = 0; j < w->numpoints; j++ ) + { + AddPointToBounds( w->p[j], brush->mins, brush->maxs ); + } //end for + } //end if + } //end for + // + for ( i = 0; i < brush->numsides; i++ ) + { + for ( j = 0; j < brush->numsides; j++ ) + { + if ( i == j ) { + continue; + } + plane1 = &mapplanes[brush->original_sides[i].planenum]; + plane2 = &mapplanes[brush->original_sides[j].planenum]; + if ( WindingsNonConvex( brush->original_sides[i].winding, + brush->original_sides[j].winding, + plane1->normal, plane2->normal, + plane1->dist, plane2->dist ) ) { + Log_Print( "non convex brush" ); + } //end if + } //end for + } //end for + + //NOW close the fucking brush!! + for ( i = 0; i < 3; i++ ) + { + if ( brush->mins[i] < -MAX_MAP_BOUNDS ) { + VectorClear( normal ); + normal[i] = -1; + dist = MAX_MAP_BOUNDS - 10; + planenum = FindFloatPlane( normal, dist ); + // + Log_Print( "mins out of range: added extra brush side\n" ); + AAS_AddMapBrushSide( brush, planenum ); + } //end if + if ( brush->maxs[i] > MAX_MAP_BOUNDS ) { + VectorClear( normal ); + normal[i] = 1; + dist = MAX_MAP_BOUNDS - 10; + planenum = FindFloatPlane( normal, dist ); + // + Log_Print( "maxs out of range: added extra brush side\n" ); + AAS_AddMapBrushSide( brush, planenum ); + } //end if + if ( brush->mins[i] > MAX_MAP_BOUNDS || brush->maxs[i] < -MAX_MAP_BOUNDS ) { + Log_Print( "entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum ); + } //end if + } //end for + //free all the windings + FreeBrushWindings( brush ); +} //end of the function AAS_FixMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_MakeBrushWindings( mapbrush_t *ob ) { + int i, j; + winding_t *w; + side_t *side; + plane_t *plane, *plane1, *plane2; + + ClearBounds( ob->mins, ob->maxs ); + + for ( i = 0; i < ob->numsides; i++ ) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane( plane->normal, plane->dist ); + for ( j = 0; j < ob->numsides && w; j++ ) + { + if ( i == j ) { + continue; + } + if ( ob->original_sides[j].flags & SFL_BEVEL ) { + continue; + } + plane = &mapplanes[ob->original_sides[j].planenum ^ 1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON); + } + + side = &ob->original_sides[i]; + side->winding = w; + if ( w ) { + side->flags |= SFL_VISIBLE; + for ( j = 0; j < w->numpoints; j++ ) + AddPointToBounds( w->p[j], ob->mins, ob->maxs ); + } + } + //check if the brush is convex + for ( i = 0; i < ob->numsides; i++ ) + { + for ( j = 0; j < ob->numsides; j++ ) + { + if ( i == j ) { + continue; + } + plane1 = &mapplanes[ob->original_sides[i].planenum]; + plane2 = &mapplanes[ob->original_sides[j].planenum]; + if ( WindingsNonConvex( ob->original_sides[i].winding, + ob->original_sides[j].winding, + plane1->normal, plane2->normal, + plane1->dist, plane2->dist ) ) { + Log_Print( "non convex brush" ); + } //end if + } //end for + } //end for + //check for out of bound brushes + for ( i = 0; i < 3; i++ ) + { + //IDBUG: all the indexes into the mins and maxs were zero (not using i) + if ( ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS ) { + Log_Print( "entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum ); + Log_Print( "ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, ob->mins[i], i, ob->maxs[i] ); + ob->numsides = 0; //remove the brush + break; + } //end if + if ( ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS ) { + Log_Print( "entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum ); + Log_Print( "ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, ob->mins[i], i, ob->maxs[i] ); + ob->numsides = 0; //remove the brush + break; + } //end if + } //end for + return true; +} //end of the function AAS_MakeBrushWindings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +mapbrush_t *AAS_CopyMapBrush( mapbrush_t *brush, entity_t *mapent ) { + int n; + mapbrush_t *newbrush; + side_t *side, *newside; + + if ( nummapbrushes >= MAX_MAPFILE_BRUSHES ) { + Error( "MAX_MAPFILE_BRUSHES" ); + } + + newbrush = &mapbrushes[nummapbrushes]; + newbrush->original_sides = &brushsides[nummapbrushsides]; + newbrush->entitynum = brush->entitynum; + newbrush->brushnum = nummapbrushes - mapent->firstbrush; + newbrush->numsides = brush->numsides; + newbrush->contents = brush->contents; + + //copy the sides + for ( n = 0; n < brush->numsides; n++ ) + { + if ( nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES ) { + Error( "MAX_MAPFILE_BRUSHSIDES" ); + } + side = brush->original_sides + n; + + newside = newbrush->original_sides + n; + newside->original = NULL; + newside->winding = NULL; + newside->contents = side->contents; + newside->flags = side->flags; + newside->surf = side->surf; + newside->planenum = side->planenum; + newside->texinfo = side->texinfo; + nummapbrushsides++; + } //end for + // + nummapbrushes++; + mapent->numbrushes++; + return newbrush; +} //end of the function AAS_CopyMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AlwaysTriggered( char *targetname ) { + int i; + + if ( !strlen( targetname ) ) { + return false; + } + // + for ( i = 0; i < num_entities; i++ ) + { + //if the entity will activate the given targetname + if ( !strcmp( targetname, ValueForKey( &entities[i], "target" ) ) ) { + //if this activator is present in deathmatch + if ( !( atoi( ValueForKey( &entities[i], "spawnflags" ) ) & SPAWNFLAG_NOT_DEATHMATCH ) ) { + //if it is a trigger_always entity + if ( !strcmp( "trigger_always", ValueForKey( &entities[i], "classname" ) ) ) { + return true; + } //end if + //check for possible trigger_always entities activating this entity + if ( AAS_AlwaysTriggered( ValueForKey( &entities[i], "targetname" ) ) ) { + return true; + } //end if + } //end if + } //end if + } //end for + return false; +} //end of the function AAS_AlwaysTriggered +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ValidEntity( entity_t *mapent ) { + int i; + char target[1024]; + + //all world brushes are used for AAS + if ( mapent == &entities[0] ) { + return true; + } //end if + //some of the func_wall brushes are also used for AAS + else if ( !strcmp( "func_wall", ValueForKey( mapent, "classname" ) ) ) { + //Log_Print("found func_wall entity %d\n", mapent - entities); + //if the func wall is used in deathmatch + //if (!(atoi(ValueForKey(mapent, "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) + { + //Log_Print("func_wall USED in deathmatch mode %d\n", atoi(ValueForKey(mapent, "spawnflags"))); + return true; + } //end if + } //end else if + else if ( !strcmp( "func_door_rotating", ValueForKey( mapent, "classname" ) ) || + !strcmp( "func_door", ValueForKey( mapent, "classname" ) ) || + !strcmp( "func_invisible_user", ValueForKey( mapent, "classname" ) ) ) { + //if the func_door_rotating is present in deathmatch + //if (!(atoi(ValueForKey(mapent, "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) + { + //if the func_door_rotating is always activated in deathmatch + if ( AAS_AlwaysTriggered( ValueForKey( mapent, "targetname" ) ) ) { + //Log_Print("found func_door_rotating in deathmatch\ntargetname %s\n", ValueForKey(mapent, "targetname")); + return true; + } //end if + } //end if + } //end else if + else if ( !strcmp( "trigger_hurt", ValueForKey( mapent, "classname" ) ) ) { + // RF, spawnflag & 1 is for delayed spawn, so ignore it + if ( atoi( ValueForKey( mapent, "spawnflags" ) ) & 1 ) { + return false; + } + + //"dmg" is the damage, for instance: "dmg" "666" + return true; + } //end else if + else if ( !strcmp( "trigger_push", ValueForKey( mapent, "classname" ) ) ) { + return true; + } //end else if + else if ( !strcmp( "trigger_multiple", ValueForKey( mapent, "classname" ) ) ) { + //find out if the trigger_multiple is pointing to a target_teleporter + strcpy( target, ValueForKey( mapent, "target" ) ); + for ( i = 0; i < num_entities; i++ ) + { + //if the entity will activate the given targetname + if ( !strcmp( target, ValueForKey( &entities[i], "targetname" ) ) ) { + if ( !strcmp( "target_teleporter", ValueForKey( &entities[i], "classname" ) ) ) { + return true; + } //end if + } //end if + } //end for + } //end else if + else if ( !strcmp( "trigger_teleport", ValueForKey( mapent, "classname" ) ) ) { + return true; + } //end else if + else if ( !strcmp( "func_tramcar", ValueForKey( mapent, "classname" ) ) ) { + return true; + } //end else if + else if ( !strcmp( "func_invisible_user", ValueForKey( mapent, "classname" ) ) ) { + return true; + } + /* + else if (!strcmp("func_static", ValueForKey(mapent, "classname"))) + { + //FIXME: easy/medium/hard/deathmatch specific? + return true; + } //end else if + */ + return false; +} //end of the function AAS_ValidEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TransformPlane( int planenum, vec3_t origin, vec3_t angles ) { + float newdist, matrix[3][3]; + vec3_t normal; + + //rotate the node plane + VectorCopy( mapplanes[planenum].normal, normal ); + CreateRotationMatrix( angles, matrix ); + RotatePoint( normal, matrix ); + newdist = mapplanes[planenum].dist + DotProduct( normal, origin ); + return FindFloatPlane( normal, newdist ); +} //end of the function AAS_TransformPlane +//=========================================================================== +// this function sets the func_rotating_door in it's final position +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PositionFuncRotatingBrush( entity_t *mapent, mapbrush_t *brush ) { + int spawnflags, i; + float distance; + vec3_t movedir, angles, pos1, pos2; + side_t *s; + + spawnflags = FloatForKey( mapent, "spawnflags" ); + VectorClear( movedir ); + if ( spawnflags & DOOR_X_AXIS ) { + movedir[2] = 1.0; //roll + } else if ( spawnflags & DOOR_Y_AXIS ) { + movedir[0] = 1.0; //pitch + } else { // Z_AXIS + movedir[1] = 1.0; //yaw + + } + // check for reverse rotation + if ( spawnflags & DOOR_REVERSE ) { + VectorInverse( movedir ); + } + + distance = FloatForKey( mapent, "distance" ); + if ( !distance ) { + distance = 90; + } + + GetVectorForKey( mapent, "angles", angles ); + VectorCopy( angles, pos1 ); + VectorMA( angles, -distance, movedir, pos2 ); + // if it starts open, switch the positions + if ( spawnflags & DOOR_START_OPEN ) { + VectorCopy( pos2, angles ); + VectorCopy( pos1, pos2 ); + VectorCopy( angles, pos1 ); + VectorInverse( movedir ); + } //end if + // + for ( i = 0; i < brush->numsides; i++ ) + { + s = &brush->original_sides[i]; + s->planenum = AAS_TransformPlane( s->planenum, mapent->origin, pos2 ); + } //end for + // + FreeBrushWindings( brush ); + AAS_MakeBrushWindings( brush ); + AddBrushBevels( brush ); + FreeBrushWindings( brush ); +} //end of the function AAS_PositionFuncRotatingBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PositionBrush( entity_t *mapent, mapbrush_t *brush ) { + side_t *s; + float newdist; + int i; + char *model; + + if ( !strcmp( ValueForKey( mapent, "classname" ), "func_door_rotating" ) ) { + AAS_PositionFuncRotatingBrush( mapent, brush ); + } //end if + else + { + if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) { + for ( i = 0; i < brush->numsides; i++ ) + { + s = &brush->original_sides[i]; + newdist = mapplanes[s->planenum].dist + + DotProduct( mapplanes[s->planenum].normal, mapent->origin ); + s->planenum = FindFloatPlane( mapplanes[s->planenum].normal, newdist ); + } //end for + } //end if + //if it's a trigger hurt + if ( !strcmp( "trigger_hurt", ValueForKey( mapent, "classname" ) ) ) { + //set the lava contents + brush->contents |= CONTENTS_LAVA; + //Log_Print("found trigger_hurt brush\n"); + } //end if + // + else if ( !strcmp( "trigger_push", ValueForKey( mapent, "classname" ) ) ) { + //set the jumppad contents + brush->contents = CONTENTS_JUMPPAD; + //Log_Print("found trigger_push brush\n"); + } //end if + // + else if ( !strcmp( "trigger_multiple", ValueForKey( mapent, "classname" ) ) ) { + //set teleporter contents + brush->contents = CONTENTS_TELEPORTER; + //Log_Print("found trigger_multiple teleporter brush\n"); + } //end if + // + else if ( !strcmp( "trigger_teleport", ValueForKey( mapent, "classname" ) ) ) { + //set teleporter contents + brush->contents = CONTENTS_TELEPORTER; + //Log_Print("found trigger_teleport teleporter brush\n"); + } //end if + else if ( !strcmp( "func_door", ValueForKey( mapent, "classname" ) ) ) { + //set mover contents + brush->contents = CONTENTS_MOVER; + //get the model number + model = ValueForKey( mapent, "model" ); + brush->modelnum = atoi( model + 1 ); + } //end if + else if ( !strcmp( "func_invisible_user", ValueForKey( mapent, "classname" ) ) ) { + //set mover contents + brush->contents = CONTENTS_TRIGGER; + } //end if + + } //end else +} //end of the function AAS_PositionBrush +//=========================================================================== +// uses the global cfg_t cfg +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateMapBrushes( mapbrush_t *brush, entity_t *mapent, int addbevels ) { + int i; + //side_t *s; + mapbrush_t *bboxbrushes[16]; + + //if the brushes are not from an entity used for AAS + if ( !AAS_ValidEntity( mapent ) ) { + nummapbrushsides -= brush->numsides; + brush->numsides = 0; + return; + } //end if + // + AAS_PositionBrush( mapent, brush ); + //from all normal solid brushes only the textured brush sides will + //be used as bsp splitters, so set the right texinfo reference here + AAS_SetTexinfo( brush ); + //remove contents detail flag, otherwise player clip contents won't be + //bsped correctly for AAS! + brush->contents &= ~CONTENTS_DETAIL; + //if the brush has contents area portal it should be the only contents + if ( brush->contents & ( CONTENTS_AREAPORTAL | CONTENTS_CLUSTERPORTAL ) ) { + brush->contents = CONTENTS_CLUSTERPORTAL; + brush->leafnum = -1; + } //end if + //window and playerclip are used for player clipping, make them solid + if ( brush->contents & ( CONTENTS_WINDOW | CONTENTS_PLAYERCLIP ) ) { + // + brush->contents &= ~( CONTENTS_WINDOW | CONTENTS_PLAYERCLIP ); + brush->contents |= CONTENTS_SOLID; + brush->leafnum = -1; + } //end if + // +// Rafael TBD: no flag to support CONTENTS_BOTCLIP +/* + if (brush->contents & CONTENTS_BOTCLIP) + { + brush->contents = CONTENTS_SOLID; + brush->leafnum = -1; + } // end if +*/ + // + //Log_Write("brush %d contents = ", brush->brushnum); + //PrintContents(brush->contents); + //Log_Write("\r\n"); + //if not one of the following brushes then the brush is NOT used for AAS + if ( !( brush->contents & ( CONTENTS_SOLID + | CONTENTS_LADDER + | CONTENTS_CLUSTERPORTAL + | CONTENTS_DONOTENTER + | CONTENTS_DONOTENTER_LARGE + | CONTENTS_TELEPORTER + | CONTENTS_JUMPPAD + | CONTENTS_WATER + | CONTENTS_LAVA + | CONTENTS_SLIME + | CONTENTS_MOVER + ) ) ) { + nummapbrushsides -= brush->numsides; + brush->numsides = 0; + return; + } //end if + //fix the map brush + //AAS_FixMapBrush(brush); + //if brush bevels should be added (for real map brushes, not bsp map brushes) + if ( addbevels ) { + //NOTE: we first have to get the mins and maxs of the brush before + // creating the brush bevels... the mins and maxs are used to + // create them. so we call MakeBrushWindings to get the mins + // and maxs and then after creating the bevels we free the + // windings because they are created for all sides (including + // bevels) a little later + AAS_MakeBrushWindings( brush ); + AddBrushBevels( brush ); + FreeBrushWindings( brush ); + } //end if + //NOTE: add the brush to the WORLD entity!!! + mapent = &entities[0]; + //there's at least one new brush for now + nummapbrushes++; + mapent->numbrushes++; + //liquid brushes are expanded for the maximum possible bounding box + if ( brush->contents & ( CONTENTS_WATER + | CONTENTS_LAVA + | CONTENTS_SLIME + | CONTENTS_TELEPORTER + | CONTENTS_JUMPPAD + | CONTENTS_DONOTENTER + | CONTENTS_DONOTENTER_LARGE + | CONTENTS_MOVER + ) ) { + brush->expansionbbox = 0; + //NOTE: the first bounding box is the max + //FIXME: use max bounding box created from all bboxes + AAS_ExpandMapBrush( brush, cfg.bboxes[0].mins, cfg.bboxes[0].maxs ); + AAS_MakeBrushWindings( brush ); + } //end if + //area portal brushes are NOT expanded + else if ( brush->contents & CONTENTS_CLUSTERPORTAL ) { + brush->expansionbbox = 0; + //NOTE: the first bounding box is the max + //FIXME: use max bounding box created from all bboxes + AAS_ExpandMapBrush( brush, cfg.bboxes[0].mins, cfg.bboxes[0].maxs ); + AAS_MakeBrushWindings( brush ); + } //end if + //all solid brushes are expanded for all bounding boxes + else if ( brush->contents & ( CONTENTS_SOLID + | CONTENTS_LADDER + ) ) { + //brush for the first bounding box + bboxbrushes[0] = brush; + //make a copy for the other bounding boxes + for ( i = 1; i < cfg.numbboxes; i++ ) + { + bboxbrushes[i] = AAS_CopyMapBrush( brush, mapent ); + } //end for + //expand every brush for it's bounding box and create windings + for ( i = 0; i < cfg.numbboxes; i++ ) + { + AAS_ExpandMapBrush( bboxbrushes[i], cfg.bboxes[i].mins, cfg.bboxes[i].maxs ); + bboxbrushes[i]->expansionbbox = cfg.bboxes[i].presencetype; + AAS_MakeBrushWindings( bboxbrushes[i] ); + } //end for + } //end else +} //end of the function AAS_CreateMapBrushes diff --git a/src/bspc/aas_map.h b/src/bspc/aas_map.h new file mode 100644 index 0000000..8866eba --- /dev/null +++ b/src/bspc/aas_map.h @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_map.h +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +void AAS_CreateMapBrushes( mapbrush_t *brush, entity_t *mapent, int addbevels ); diff --git a/src/bspc/aas_prunenodes.c b/src/bspc/aas_prunenodes.c new file mode 100644 index 0000000..f9d28c3 --- /dev/null +++ b/src/bspc/aas_prunenodes.c @@ -0,0 +1,103 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_prunenodes.c +// Function: Prune Nodes +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "..\botlib\aasfile.h" +#include "aas_create.h" + +int c_numprunes; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tmp_node_t *AAS_PruneNodes_r( tmp_node_t *tmpnode ) { + tmp_area_t *tmparea1, *tmparea2; + + //if it is a solid leaf + if ( !tmpnode ) { + return NULL; + } + // + if ( tmpnode->tmparea ) { + return tmpnode; + } + //process the children first + tmpnode->children[0] = AAS_PruneNodes_r( tmpnode->children[0] ); + tmpnode->children[1] = AAS_PruneNodes_r( tmpnode->children[1] ); + //if both children are areas + if ( tmpnode->children[0] && tmpnode->children[1] && + tmpnode->children[0]->tmparea && tmpnode->children[1]->tmparea ) { + tmparea1 = tmpnode->children[0]->tmparea; + while ( tmparea1->mergedarea ) tmparea1 = tmparea1->mergedarea; + + tmparea2 = tmpnode->children[1]->tmparea; + while ( tmparea2->mergedarea ) tmparea2 = tmparea2->mergedarea; + + if ( tmparea1 == tmparea2 ) { + c_numprunes++; + tmpnode->tmparea = tmparea1; + tmpnode->planenum = 0; + AAS_FreeTmpNode( tmpnode->children[0] ); + AAS_FreeTmpNode( tmpnode->children[1] ); + tmpnode->children[0] = NULL; + tmpnode->children[1] = NULL; + return tmpnode; + } //end if + } //end if + //if both solid leafs + if ( !tmpnode->children[0] && !tmpnode->children[1] ) { + c_numprunes++; + AAS_FreeTmpNode( tmpnode ); + return NULL; + } //end if + // + return tmpnode; +} //end of the function AAS_PruneNodes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PruneNodes( void ) { + Log_Write( "AAS_PruneNodes\r\n" ); + AAS_PruneNodes_r( tmpaasworld.nodes ); + Log_Print( "%6d nodes pruned\r\n", c_numprunes ); +} //end of the function AAS_PruneNodes diff --git a/src/bspc/aas_prunenodes.h b/src/bspc/aas_prunenodes.h new file mode 100644 index 0000000..9ffa9b2 --- /dev/null +++ b/src/bspc/aas_prunenodes.h @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_prunenodes.h +// Function: Prune Nodes +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +void AAS_PruneNodes( void ); + diff --git a/src/bspc/aas_store.c b/src/bspc/aas_store.c new file mode 100644 index 0000000..62d7bf7 --- /dev/null +++ b/src/bspc/aas_store.c @@ -0,0 +1,1121 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_store.c +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "..\botlib\aasfile.h" +#include "aas_file.h" +#include "aas_store.h" +#include "aas_create.h" +#include "aas_cfg.h" + + +//#define NOTHREEVERTEXFACES + +#define STOREPLANESDOUBLE + +#define VERTEX_EPSILON 0.1 //NOTE: changed from 0.5 +//NOTE: changed from 0.9 +#define DIST_EPSILON 0.05 +//NOTE: changed from 0.005 +#define NORMAL_EPSILON 0.0001 + +#define INTEGRAL_EPSILON 0.01 +#define VEREX_EPSILON 0.1 //NOTE: changed from 0.5 +#define VERTEX_HASHING +#define VERTEX_HASH_SHIFT 7 +#define VERTEX_HASH_SIZE ( ( MAX_MAP_BOUNDS >> ( VERTEX_HASH_SHIFT - 1 ) ) + 1 ) //was 64 +// +#define PLANE_HASHING +#define PLANE_HASH_SIZE 1024 //must be power of 2 +// +#define EDGE_HASHING +#define EDGE_HASH_SIZE 1024 //must be power of 2 + +// Ridah +aas_t aasworlds[1]; +aas_t( *aasworld ); +// done. + +//vertex hash +int *aas_vertexchain; // the next vertex in a hash chain +int aas_hashverts[VERTEX_HASH_SIZE * VERTEX_HASH_SIZE]; // a vertex number, or 0 for no verts +//plane hash +int *aas_planechain; +int aas_hashplanes[PLANE_HASH_SIZE]; +//edge hash +int *aas_edgechain; +int aas_hashedges[EDGE_HASH_SIZE]; + +int allocatedaasmem = 0; + +int groundfacesonly = false; //true; +// +typedef struct max_aas_s +{ + int max_bboxes; + int max_vertexes; + int max_planes; + int max_edges; + int max_edgeindexsize; + int max_faces; + int max_faceindexsize; + int max_areas; + int max_areasettings; + int max_reachabilitysize; + int max_nodes; + int max_portals; + int max_portalindexsize; + int max_clusters; +} max_aas_t; +//maximums of everything +max_aas_t max_aas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CountTmpNodes( tmp_node_t *tmpnode ) { + if ( !tmpnode ) { + return 0; + } + return AAS_CountTmpNodes( tmpnode->children[0] ) + + AAS_CountTmpNodes( tmpnode->children[1] ) + 1; +} //end of the function AAS_CountTmpNodes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitMaxAAS( void ) { + int numfaces, numpoints, numareas; + tmp_face_t *f; + tmp_area_t *a; + + numpoints = 0; + numfaces = 0; + for ( f = tmpaasworld.faces; f; f = f->l_next ) + { + numfaces++; + if ( f->winding ) { + numpoints += f->winding->numpoints; + } + } //end for + // + numareas = 0; + for ( a = tmpaasworld.areas; a; a = a->l_next ) + { + numareas++; + } //end for + max_aas.max_bboxes = AAS_MAX_BBOXES; + max_aas.max_vertexes = numpoints + 1; + max_aas.max_planes = nummapplanes; + max_aas.max_edges = numpoints + 1; + max_aas.max_edgeindexsize = ( numpoints + 1 ) * 3; + max_aas.max_faces = numfaces + 10; + max_aas.max_faceindexsize = ( numfaces + 10 ) * 2; + max_aas.max_areas = numareas + 10; + max_aas.max_areasettings = numareas + 10; + max_aas.max_reachabilitysize = 0; + max_aas.max_nodes = AAS_CountTmpNodes( tmpaasworld.nodes ) + 10; + max_aas.max_portals = 0; + max_aas.max_portalindexsize = 0; + max_aas.max_clusters = 0; +} //end of the function AAS_InitMaxAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AllocMaxAAS( void ) { + int i; + + AAS_InitMaxAAS(); + //bounding boxes + ( *aasworld ).numbboxes = 0; + ( *aasworld ).bboxes = (aas_bbox_t *) GetClearedMemory( max_aas.max_bboxes * sizeof( aas_bbox_t ) ); + allocatedaasmem += max_aas.max_bboxes * sizeof( aas_bbox_t ); + //vertexes + ( *aasworld ).numvertexes = 0; + ( *aasworld ).vertexes = (aas_vertex_t *) GetClearedMemory( max_aas.max_vertexes * sizeof( aas_vertex_t ) ); + allocatedaasmem += max_aas.max_vertexes * sizeof( aas_vertex_t ); + //planes + ( *aasworld ).numplanes = 0; + ( *aasworld ).planes = (aas_plane_t *) GetClearedMemory( max_aas.max_planes * sizeof( aas_plane_t ) ); + allocatedaasmem += max_aas.max_planes * sizeof( aas_plane_t ); + //edges + ( *aasworld ).numedges = 0; + ( *aasworld ).edges = (aas_edge_t *) GetClearedMemory( max_aas.max_edges * sizeof( aas_edge_t ) ); + allocatedaasmem += max_aas.max_edges * sizeof( aas_edge_t ); + //edge index + ( *aasworld ).edgeindexsize = 0; + ( *aasworld ).edgeindex = (aas_edgeindex_t *) GetClearedMemory( max_aas.max_edgeindexsize * sizeof( aas_edgeindex_t ) ); + allocatedaasmem += max_aas.max_edgeindexsize * sizeof( aas_edgeindex_t ); + //faces + ( *aasworld ).numfaces = 0; + ( *aasworld ).faces = (aas_face_t *) GetClearedMemory( max_aas.max_faces * sizeof( aas_face_t ) ); + allocatedaasmem += max_aas.max_faces * sizeof( aas_face_t ); + //face index + ( *aasworld ).faceindexsize = 0; + ( *aasworld ).faceindex = (aas_faceindex_t *) GetClearedMemory( max_aas.max_faceindexsize * sizeof( aas_faceindex_t ) ); + allocatedaasmem += max_aas.max_faceindexsize * sizeof( aas_faceindex_t ); + //convex areas + ( *aasworld ).numareas = 0; + ( *aasworld ).areas = (aas_area_t *) GetClearedMemory( max_aas.max_areas * sizeof( aas_area_t ) ); + allocatedaasmem += max_aas.max_areas * sizeof( aas_area_t ); + //convex area settings + ( *aasworld ).numareasettings = 0; + ( *aasworld ).areasettings = (aas_areasettings_t *) GetClearedMemory( max_aas.max_areasettings * sizeof( aas_areasettings_t ) ); + allocatedaasmem += max_aas.max_areasettings * sizeof( aas_areasettings_t ); + //reachablity list + ( *aasworld ).reachabilitysize = 0; + ( *aasworld ).reachability = (aas_reachability_t *) GetClearedMemory( max_aas.max_reachabilitysize * sizeof( aas_reachability_t ) ); + allocatedaasmem += max_aas.max_reachabilitysize * sizeof( aas_reachability_t ); + //nodes of the bsp tree + ( *aasworld ).numnodes = 0; + ( *aasworld ).nodes = (aas_node_t *) GetClearedMemory( max_aas.max_nodes * sizeof( aas_node_t ) ); + allocatedaasmem += max_aas.max_nodes * sizeof( aas_node_t ); + //cluster portals + ( *aasworld ).numportals = 0; + ( *aasworld ).portals = (aas_portal_t *) GetClearedMemory( max_aas.max_portals * sizeof( aas_portal_t ) ); + allocatedaasmem += max_aas.max_portals * sizeof( aas_portal_t ); + //cluster portal index + ( *aasworld ).portalindexsize = 0; + ( *aasworld ).portalindex = (aas_portalindex_t *) GetClearedMemory( max_aas.max_portalindexsize * sizeof( aas_portalindex_t ) ); + allocatedaasmem += max_aas.max_portalindexsize * sizeof( aas_portalindex_t ); + //cluster + ( *aasworld ).numclusters = 0; + ( *aasworld ).clusters = (aas_cluster_t *) GetClearedMemory( max_aas.max_clusters * sizeof( aas_cluster_t ) ); + allocatedaasmem += max_aas.max_clusters * sizeof( aas_cluster_t ); + // + Log_Print( "allocated " ); + PrintMemorySize( allocatedaasmem ); + Log_Print( " of AAS memory\n" ); + //reset the has stuff + aas_vertexchain = (int *) GetClearedMemory( max_aas.max_vertexes * sizeof( int ) ); + aas_planechain = (int *) GetClearedMemory( max_aas.max_planes * sizeof( int ) ); + aas_edgechain = (int *) GetClearedMemory( max_aas.max_edges * sizeof( int ) ); + // + for ( i = 0; i < max_aas.max_vertexes; i++ ) aas_vertexchain[i] = -1; + for ( i = 0; i < VERTEX_HASH_SIZE * VERTEX_HASH_SIZE; i++ ) aas_hashverts[i] = -1; + // + for ( i = 0; i < max_aas.max_planes; i++ ) aas_planechain[i] = -1; + for ( i = 0; i < PLANE_HASH_SIZE; i++ ) aas_hashplanes[i] = -1; + // + for ( i = 0; i < max_aas.max_edges; i++ ) aas_edgechain[i] = -1; + for ( i = 0; i < EDGE_HASH_SIZE; i++ ) aas_hashedges[i] = -1; +} //end of the function AAS_AllocMaxAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeMaxAAS( void ) { + //bounding boxes + if ( ( *aasworld ).bboxes ) { + FreeMemory( ( *aasworld ).bboxes ); + } + ( *aasworld ).bboxes = NULL; + ( *aasworld ).numbboxes = 0; + //vertexes + if ( ( *aasworld ).vertexes ) { + FreeMemory( ( *aasworld ).vertexes ); + } + ( *aasworld ).vertexes = NULL; + ( *aasworld ).numvertexes = 0; + //planes + if ( ( *aasworld ).planes ) { + FreeMemory( ( *aasworld ).planes ); + } + ( *aasworld ).planes = NULL; + ( *aasworld ).numplanes = 0; + //edges + if ( ( *aasworld ).edges ) { + FreeMemory( ( *aasworld ).edges ); + } + ( *aasworld ).edges = NULL; + ( *aasworld ).numedges = 0; + //edge index + if ( ( *aasworld ).edgeindex ) { + FreeMemory( ( *aasworld ).edgeindex ); + } + ( *aasworld ).edgeindex = NULL; + ( *aasworld ).edgeindexsize = 0; + //faces + if ( ( *aasworld ).faces ) { + FreeMemory( ( *aasworld ).faces ); + } + ( *aasworld ).faces = NULL; + ( *aasworld ).numfaces = 0; + //face index + if ( ( *aasworld ).faceindex ) { + FreeMemory( ( *aasworld ).faceindex ); + } + ( *aasworld ).faceindex = NULL; + ( *aasworld ).faceindexsize = 0; + //convex areas + if ( ( *aasworld ).areas ) { + FreeMemory( ( *aasworld ).areas ); + } + ( *aasworld ).areas = NULL; + ( *aasworld ).numareas = 0; + //convex area settings + if ( ( *aasworld ).areasettings ) { + FreeMemory( ( *aasworld ).areasettings ); + } + ( *aasworld ).areasettings = NULL; + ( *aasworld ).numareasettings = 0; + //reachablity list + if ( ( *aasworld ).reachability ) { + FreeMemory( ( *aasworld ).reachability ); + } + ( *aasworld ).reachability = NULL; + ( *aasworld ).reachabilitysize = 0; + //nodes of the bsp tree + if ( ( *aasworld ).nodes ) { + FreeMemory( ( *aasworld ).nodes ); + } + ( *aasworld ).nodes = NULL; + ( *aasworld ).numnodes = 0; + //cluster portals + if ( ( *aasworld ).portals ) { + FreeMemory( ( *aasworld ).portals ); + } + ( *aasworld ).portals = NULL; + ( *aasworld ).numportals = 0; + //cluster portal index + if ( ( *aasworld ).portalindex ) { + FreeMemory( ( *aasworld ).portalindex ); + } + ( *aasworld ).portalindex = NULL; + ( *aasworld ).portalindexsize = 0; + //clusters + if ( ( *aasworld ).clusters ) { + FreeMemory( ( *aasworld ).clusters ); + } + ( *aasworld ).clusters = NULL; + ( *aasworld ).numclusters = 0; + + Log_Print( "freed " ); + PrintMemorySize( allocatedaasmem ); + Log_Print( " of AAS memory\n" ); + allocatedaasmem = 0; + // + if ( aas_vertexchain ) { + FreeMemory( aas_vertexchain ); + } + aas_vertexchain = NULL; + if ( aas_planechain ) { + FreeMemory( aas_planechain ); + } + aas_planechain = NULL; + if ( aas_edgechain ) { + FreeMemory( aas_edgechain ); + } + aas_edgechain = NULL; +} //end of the function AAS_FreeMaxAAS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned AAS_HashVec( vec3_t vec ) { + int x, y; + + x = ( MAX_MAP_BOUNDS + (int)( vec[0] + 0.5 ) ) >> VERTEX_HASH_SHIFT; + y = ( MAX_MAP_BOUNDS + (int)( vec[1] + 0.5 ) ) >> VERTEX_HASH_SHIFT; + + if ( x < 0 || x >= VERTEX_HASH_SIZE || y < 0 || y >= VERTEX_HASH_SIZE ) { + Log_Print( "WARNING! HashVec: point %f %f %f outside valid range\n", vec[0], vec[1], vec[2] ); + Log_Print( "This should never happen!\n" ); + return -1; + } //end if + + return y * VERTEX_HASH_SIZE + x; +} //end of the function AAS_HashVec +//=========================================================================== +// returns true if the vertex was found in the list +// stores the vertex number in *vnum +// stores a new vertex if not stored already +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetVertex( vec3_t v, int *vnum ) { + int i; +#ifndef VERTEX_HASHING + float diff; +#endif //VERTEX_HASHING + +#ifdef VERTEX_HASHING + int h, vn; + vec3_t vert; + + for ( i = 0; i < 3; i++ ) + { + if ( fabs( v[i] - Q_rint( v[i] ) ) < INTEGRAL_EPSILON ) { + vert[i] = Q_rint( v[i] ); + } else { + vert[i] = v[i]; + } + } //end for + + h = AAS_HashVec( vert ); + //if the vertex was outside the valid range + if ( h == -1 ) { + *vnum = -1; + return true; + } //end if + + for ( vn = aas_hashverts[h]; vn >= 0; vn = aas_vertexchain[vn] ) + { + if ( fabs( ( *aasworld ).vertexes[vn][0] - vert[0] ) < VEREX_EPSILON + && fabs( ( *aasworld ).vertexes[vn][1] - vert[1] ) < VEREX_EPSILON + && fabs( ( *aasworld ).vertexes[vn][2] - vert[2] ) < VEREX_EPSILON ) { + *vnum = vn; + return true; + } //end if + } //end for +#else //VERTEX_HASHING + //check if the vertex is already stored + //stupid linear search + for ( i = 0; i < ( *aasworld ).numvertexes; i++ ) + { + diff = vert[0] - ( *aasworld ).vertexes[i][0]; + if ( diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON ) { + diff = vert[1] - ( *aasworld ).vertexes[i][1]; + if ( diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON ) { + diff = vert[2] - ( *aasworld ).vertexes[i][2]; + if ( diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON ) { + *vnum = i; + return true; + } //end if + } //end if + } //end if + } //end for +#endif //VERTEX_HASHING + + if ( ( *aasworld ).numvertexes >= max_aas.max_vertexes ) { + Error( "AAS_MAX_VERTEXES = %d", max_aas.max_vertexes ); + } //end if + VectorCopy( vert, ( *aasworld ).vertexes[( *aasworld ).numvertexes] ); + *vnum = ( *aasworld ).numvertexes; + +#ifdef VERTEX_HASHING + aas_vertexchain[( *aasworld ).numvertexes] = aas_hashverts[h]; + aas_hashverts[h] = ( *aasworld ).numvertexes; +#endif //VERTEX_HASHING + + ( *aasworld ).numvertexes++; + return false; +} //end of the function AAS_GetVertex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned AAS_HashEdge( int v1, int v2 ) { + int vnum1, vnum2; + // + if ( v1 < v2 ) { + vnum1 = v1; + vnum2 = v2; + } //end if + else + { + vnum1 = v2; + vnum2 = v1; + } //end else + return ( vnum1 + vnum2 ) & ( EDGE_HASH_SIZE - 1 ); +} //end of the function AAS_HashVec +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddEdgeToHash( int edgenum ) { + int hash; + aas_edge_t *edge; + + edge = &( *aasworld ).edges[edgenum]; + + hash = AAS_HashEdge( edge->v[0], edge->v[1] ); + + aas_edgechain[edgenum] = aas_hashedges[hash]; + aas_hashedges[hash] = edgenum; +} //end of the function AAS_AddEdgeToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_FindHashedEdge( int v1num, int v2num, int *edgenum ) { + int e, hash; + aas_edge_t *edge; + + hash = AAS_HashEdge( v1num, v2num ); + for ( e = aas_hashedges[hash]; e >= 0; e = aas_edgechain[e] ) + { + edge = &( *aasworld ).edges[e]; + if ( edge->v[0] == v1num ) { + if ( edge->v[1] == v2num ) { + *edgenum = e; + return true; + } //end if + } //end if + else if ( edge->v[1] == v1num ) { + if ( edge->v[0] == v2num ) { + //negative for a reversed edge + *edgenum = -e; + return true; + } //end if + } //end else + } //end for + return false; +} //end of the function AAS_FindHashedPlane +//=========================================================================== +// returns true if the edge was found +// stores the edge number in *edgenum (negative if reversed edge) +// stores new edge if not stored already +// returns zero when the edge is degenerate +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetEdge( vec3_t v1, vec3_t v2, int *edgenum ) { + int v1num, v2num; + qboolean found; + + //the first edge is a dummy + if ( ( *aasworld ).numedges == 0 ) { + ( *aasworld ).numedges = 1; + } + + found = AAS_GetVertex( v1, &v1num ); + found &= AAS_GetVertex( v2, &v2num ); + //if one of the vertexes was outside the valid range + if ( v1num == -1 || v2num == -1 ) { + *edgenum = 0; + return true; + } //end if + //if both vertexes are the same or snapped onto each other + if ( v1num == v2num ) { + *edgenum = 0; + return true; + } //end if + //if both vertexes where already stored + if ( found ) { +#ifdef EDGE_HASHING + if ( AAS_FindHashedEdge( v1num, v2num, edgenum ) ) { + return true; + } +#else + int i; + for ( i = 1; i < ( *aasworld ).numedges; i++ ) + { + if ( ( *aasworld ).edges[i].v[0] == v1num ) { + if ( ( *aasworld ).edges[i].v[1] == v2num ) { + *edgenum = i; + return true; + } //end if + } //end if + else if ( ( *aasworld ).edges[i].v[1] == v1num ) { + if ( ( *aasworld ).edges[i].v[0] == v2num ) { + //negative for a reversed edge + *edgenum = -i; + return true; + } //end if + } //end else + } //end for +#endif //EDGE_HASHING + } //end if + if ( ( *aasworld ).numedges >= max_aas.max_edges ) { + Error( "AAS_MAX_EDGES = %d", max_aas.max_edges ); + } //end if + ( *aasworld ).edges[( *aasworld ).numedges].v[0] = v1num; + ( *aasworld ).edges[( *aasworld ).numedges].v[1] = v2num; + *edgenum = ( *aasworld ).numedges; +#ifdef EDGE_HASHING + AAS_AddEdgeToHash( *edgenum ); +#endif //EDGE_HASHING + ( *aasworld ).numedges++; + return false; +} //end of the function AAS_GetEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PlaneTypeForNormal( vec3_t normal ) { + vec_t ax, ay, az; + + //NOTE: epsilon used + if ( ( normal[0] >= 1.0 - NORMAL_EPSILON ) || + ( normal[0] <= -1.0 + NORMAL_EPSILON ) ) { + return PLANE_X; + } + if ( ( normal[1] >= 1.0 - NORMAL_EPSILON ) || + ( normal[1] <= -1.0 + NORMAL_EPSILON ) ) { + return PLANE_Y; + } + if ( ( normal[2] >= 1.0 - NORMAL_EPSILON ) || + ( normal[2] <= -1.0 + NORMAL_EPSILON ) ) { + return PLANE_Z; + } + + ax = fabs( normal[0] ); + ay = fabs( normal[1] ); + az = fabs( normal[2] ); + + if ( ax >= ay && ax >= az ) { + return PLANE_ANYX; + } + if ( ay >= ax && ay >= az ) { + return PLANE_ANYY; + } + return PLANE_ANYZ; +} //end of the function AAS_PlaneTypeForNormal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddPlaneToHash( int planenum ) { + int hash; + aas_plane_t *plane; + + plane = &( *aasworld ).planes[planenum]; + + hash = (int)fabs( plane->dist ) / 8; + hash &= ( PLANE_HASH_SIZE - 1 ); + + aas_planechain[planenum] = aas_hashplanes[hash]; + aas_hashplanes[hash] = planenum; +} //end of the function AAS_AddPlaneToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PlaneEqual( vec3_t normal, float dist, int planenum ) { + float diff; + + diff = dist - ( *aasworld ).planes[planenum].dist; + if ( diff > -DIST_EPSILON && diff < DIST_EPSILON ) { + diff = normal[0] - ( *aasworld ).planes[planenum].normal[0]; + if ( diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON ) { + diff = normal[1] - ( *aasworld ).planes[planenum].normal[1]; + if ( diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON ) { + diff = normal[2] - ( *aasworld ).planes[planenum].normal[2]; + if ( diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON ) { + return true; + } //end if + } //end if + } //end if + } //end if + return false; +} //end of the function AAS_PlaneEqual +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_FindPlane( vec3_t normal, float dist, int *planenum ) { + int i; + + for ( i = 0; i < ( *aasworld ).numplanes; i++ ) + { + if ( AAS_PlaneEqual( normal, dist, i ) ) { + *planenum = i; + return true; + } //end if + } //end for + return false; +} //end of the function AAS_FindPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_FindHashedPlane( vec3_t normal, float dist, int *planenum ) { + int i, p; + aas_plane_t *plane; + int hash, h; + + hash = (int)fabs( dist ) / 8; + hash &= ( PLANE_HASH_SIZE - 1 ); + + //search the border bins as well + for ( i = -1; i <= 1; i++ ) + { + h = ( hash + i ) & ( PLANE_HASH_SIZE - 1 ); + for ( p = aas_hashplanes[h]; p >= 0; p = aas_planechain[p] ) + { + plane = &( *aasworld ).planes[p]; + if ( AAS_PlaneEqual( normal, dist, p ) ) { + *planenum = p; + return true; + } //end if + } //end for + } //end for + return false; +} //end of the function AAS_FindHashedPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetPlane( vec3_t normal, vec_t dist, int *planenum ) { + aas_plane_t *plane, temp; + + //if (AAS_FindPlane(normal, dist, planenum)) return true; + if ( AAS_FindHashedPlane( normal, dist, planenum ) ) { + return true; + } + + if ( ( *aasworld ).numplanes >= max_aas.max_planes - 1 ) { + Error( "AAS_MAX_PLANES = %d", max_aas.max_planes ); + } //end if + +#ifdef STOREPLANESDOUBLE + plane = &( *aasworld ).planes[( *aasworld ).numplanes]; + VectorCopy( normal, plane->normal ); + plane->dist = dist; + plane->type = ( plane + 1 )->type = PlaneTypeForNormal( plane->normal ); + + VectorCopy( normal, ( plane + 1 )->normal ); + VectorNegate( ( plane + 1 )->normal, ( plane + 1 )->normal ); + ( plane + 1 )->dist = -dist; + + ( *aasworld ).numplanes += 2; + + //allways put axial planes facing positive first + if ( plane->type < 3 ) { + if ( plane->normal[0] < 0 || plane->normal[1] < 0 || plane->normal[2] < 0 ) { + // flip order + temp = *plane; + *plane = *( plane + 1 ); + *( plane + 1 ) = temp; + *planenum = ( *aasworld ).numplanes - 1; + return false; + } //end if + } //end if + *planenum = ( *aasworld ).numplanes - 2; + //add the planes to the hash + AAS_AddPlaneToHash( ( *aasworld ).numplanes - 1 ); + AAS_AddPlaneToHash( ( *aasworld ).numplanes - 2 ); + return false; +#else + plane = &( *aasworld ).planes[( *aasworld ).numplanes]; + VectorCopy( normal, plane->normal ); + plane->dist = dist; + plane->type = AAS_PlaneTypeForNormal( normal ); + + *planenum = ( *aasworld ).numplanes; + ( *aasworld ).numplanes++; + //add the plane to the hash + AAS_AddPlaneToHash( ( *aasworld ).numplanes - 1 ); + return false; +#endif //STOREPLANESDOUBLE +} //end of the function AAS_GetPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_GetFace( winding_t *w, plane_t *p, int side, int *facenum ) { + int edgenum, i, j; + aas_face_t *face; + + //face zero is a dummy, because of the face index with negative numbers + if ( ( *aasworld ).numfaces == 0 ) { + ( *aasworld ).numfaces = 1; + } + + if ( ( *aasworld ).numfaces >= max_aas.max_faces ) { + Error( "AAS_MAX_FACES = %d", max_aas.max_faces ); + } //end if + face = &( *aasworld ).faces[( *aasworld ).numfaces]; + AAS_GetPlane( p->normal, p->dist, &face->planenum ); + face->faceflags = 0; + face->firstedge = ( *aasworld ).edgeindexsize; + face->frontarea = 0; + face->backarea = 0; + face->numedges = 0; + for ( i = 0; i < w->numpoints; i++ ) + { + if ( ( *aasworld ).edgeindexsize >= max_aas.max_edgeindexsize ) { + Error( "AAS_MAX_EDGEINDEXSIZE = %d", max_aas.max_edgeindexsize ); + } //end if + j = ( i + 1 ) % w->numpoints; + AAS_GetEdge( w->p[i], w->p[j], &edgenum ); + //if the edge wasn't degenerate + if ( edgenum ) { + ( *aasworld ).edgeindex[( *aasworld ).edgeindexsize++] = edgenum; + face->numedges++; + } //end if + else if ( verbose ) { + Log_Write( "AAS_GetFace: face %d had degenerate edge %d-%d\r\n", + ( *aasworld ).numfaces, i, j ); + } //end else + } //end for + if ( face->numedges < 1 +#ifdef NOTHREEVERTEXFACES + || face->numedges < 3 +#endif //NOTHREEVERTEXFACES + ) { + memset( &( *aasworld ).faces[( *aasworld ).numfaces], 0, sizeof( aas_face_t ) ); + Log_Write( "AAS_GetFace: face %d was tiny\r\n", ( *aasworld ).numfaces ); + return false; + } //end if + *facenum = ( *aasworld ).numfaces; + ( *aasworld ).numfaces++; + return true; +} //end of the function AAS_GetFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +qboolean AAS_GetFace(winding_t *w, plane_t *p, int side, int *facenum) +{ + aas_edgeindex_t edges[1024]; + int planenum, numedges, i; + int j, edgenum; + qboolean foundplane, foundedges; + aas_face_t *face; + + //face zero is a dummy, because of the face index with negative numbers + if ((*aasworld).numfaces == 0) (*aasworld).numfaces = 1; + + foundplane = AAS_GetPlane(p->normal, p->dist, &planenum); + + foundedges = true; + numedges = w->numpoints; + for (i = 0; i < w->numpoints; i++) + { + if (i >= 1024) Error("AAS_GetFace: more than %d edges\n", 1024); + foundedges &= AAS_GetEdge(w->p[i], w->p[(i+1 >= w->numpoints ? 0 : i+1)], &edges[i]); + } //end for + + //FIXME: use portal number instead of a search + //if the plane and all edges already existed + if (foundplane && foundedges) + { + for (i = 0; i < (*aasworld).numfaces; i++) + { + face = &(*aasworld).faces[i]; + if (planenum == face->planenum) + { + if (numedges == face->numedges) + { + for (j = 0; j < numedges; j++) + { + edgenum = abs((*aasworld).edgeindex[face->firstedge + j]); + if (abs(edges[i]) != edgenum) break; + } //end for + if (j == numedges) + { + //jippy found the face + *facenum = -i; + return true; + } //end if + } //end if + } //end if + } //end for + } //end if + if ((*aasworld).numfaces >= max_aas.max_faces) + { + Error("AAS_MAX_FACES = %d", max_aas.max_faces); + } //end if + face = &(*aasworld).faces[(*aasworld).numfaces]; + face->planenum = planenum; + face->faceflags = 0; + face->numedges = numedges; + face->firstedge = (*aasworld).edgeindexsize; + face->frontarea = 0; + face->backarea = 0; + for (i = 0; i < numedges; i++) + { + if ((*aasworld).edgeindexsize >= max_aas.max_edgeindexsize) + { + Error("AAS_MAX_EDGEINDEXSIZE = %d", max_aas.max_edgeindexsize); + } //end if + (*aasworld).edgeindex[(*aasworld).edgeindexsize++] = edges[i]; + } //end for + *facenum = (*aasworld).numfaces; + (*aasworld).numfaces++; + return false; +} //end of the function AAS_GetFace*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreAreaSettings( tmp_areasettings_t *tmpareasettings ) { + aas_areasettings_t *areasettings; + + if ( ( *aasworld ).numareasettings == 0 ) { + ( *aasworld ).numareasettings = 1; + } + areasettings = &( *aasworld ).areasettings[( *aasworld ).numareasettings++]; + areasettings->areaflags = tmpareasettings->areaflags; + areasettings->presencetype = tmpareasettings->presencetype; + areasettings->contents = tmpareasettings->contents; + areasettings->groundsteepness = tmpareasettings->groundsteepness; // Ridah + if ( tmpareasettings->modelnum > AREACONTENTS_MAXMODELNUM ) { + Log_Print( "WARNING: more than %d mover models\n", AREACONTENTS_MAXMODELNUM ); + } + areasettings->contents |= ( tmpareasettings->modelnum & AREACONTENTS_MAXMODELNUM ) << AREACONTENTS_MODELNUMSHIFT; +} //end of the function AAS_StoreAreaSettings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StoreArea( tmp_area_t *tmparea ) { + int side, edgenum, i; + plane_t *plane; + tmp_face_t *tmpface; + aas_area_t *aasarea; + aas_edge_t *edge; + aas_face_t *aasface; + aas_faceindex_t aasfacenum; + vec3_t facecenter; + winding_t *w; + + //when the area is merged go to the merged area + //FIXME: this isn't necessary anymore because the tree + // is refreshed after area merging + while ( tmparea->mergedarea ) tmparea = tmparea->mergedarea; + // + if ( tmparea->invalid ) { + Error( "AAS_StoreArea: tried to store invalid area" ); + } + //if there is an aas area already stored for this tmp area + if ( tmparea->aasareanum ) { + return -tmparea->aasareanum; + } + // + if ( ( *aasworld ).numareas >= max_aas.max_areas ) { + Error( "AAS_MAX_AREAS = %d", max_aas.max_areas ); + } //end if + //area zero is a dummy + if ( ( *aasworld ).numareas == 0 ) { + ( *aasworld ).numareas = 1; + } + //create an area from this leaf + aasarea = &( *aasworld ).areas[( *aasworld ).numareas]; + aasarea->areanum = ( *aasworld ).numareas; + aasarea->numfaces = 0; + aasarea->firstface = ( *aasworld ).faceindexsize; + ClearBounds( aasarea->mins, aasarea->maxs ); + VectorClear( aasarea->center ); + // +// Log_Write("tmparea %d became aasarea %d\r\n", tmparea->areanum, aasarea->areanum); + //store the aas area number at the tmp area + tmparea->aasareanum = aasarea->areanum; + // + for ( tmpface = tmparea->tmpfaces; tmpface; tmpface = tmpface->next[side] ) + { + side = tmpface->frontarea != tmparea; + //if there's an aas face created for the tmp face already + if ( tmpface->aasfacenum ) { + //we're at the back of the face so use a negative index + aasfacenum = -tmpface->aasfacenum; +#ifdef DEBUG + if ( tmpface->aasfacenum < 0 || tmpface->aasfacenum > max_aas.max_faces ) { + Error( "AAS_CreateTree_r: face number out of range" ); + } //end if +#endif //DEBUG + aasface = &( *aasworld ).faces[tmpface->aasfacenum]; + aasface->backarea = aasarea->areanum; + } //end if + else + { + plane = &mapplanes[tmpface->planenum ^ side]; + if ( side ) { + w = tmpface->winding; + tmpface->winding = ReverseWinding( tmpface->winding ); + } //end if + if ( !AAS_GetFace( tmpface->winding, plane, 0, &aasfacenum ) ) { + continue; + } + if ( side ) { + FreeWinding( tmpface->winding ); + tmpface->winding = w; + } //end if + aasface = &( *aasworld ).faces[aasfacenum]; + aasface->frontarea = aasarea->areanum; + aasface->backarea = 0; + aasface->faceflags = tmpface->faceflags; + //set the face number at the tmp face + tmpface->aasfacenum = aasfacenum; + } //end else + //add face points to the area bounds and + //calculate the face 'center' + VectorClear( facecenter ); + for ( edgenum = 0; edgenum < aasface->numedges; edgenum++ ) + { + edge = &( *aasworld ).edges[abs( ( *aasworld ).edgeindex[aasface->firstedge + edgenum] )]; + for ( i = 0; i < 2; i++ ) + { + AddPointToBounds( ( *aasworld ).vertexes[edge->v[i]], aasarea->mins, aasarea->maxs ); + VectorAdd( ( *aasworld ).vertexes[edge->v[i]], facecenter, facecenter ); + } //end for + } //end for + VectorScale( facecenter, 1.0 / ( aasface->numedges * 2.0 ), facecenter ); + //add the face 'center' to the area 'center' + VectorAdd( aasarea->center, facecenter, aasarea->center ); + // + if ( ( *aasworld ).faceindexsize >= max_aas.max_faceindexsize ) { + Error( "AAS_MAX_FACEINDEXSIZE = %d", max_aas.max_faceindexsize ); + } //end if + ( *aasworld ).faceindex[( *aasworld ).faceindexsize++] = aasfacenum; + aasarea->numfaces++; + } //end for + //if the area has no faces at all (return 0, = solid leaf) + if ( !aasarea->numfaces ) { + return 0; + } + // + VectorScale( aasarea->center, 1.0 / aasarea->numfaces, aasarea->center ); + //Log_Write("area %d center %f %f %f\r\n", (*aasworld).numareas, + // aasarea->center[0], aasarea->center[1], aasarea->center[2]); + //store the area settings + AAS_StoreAreaSettings( tmparea->settings ); + // + //Log_Write("tmp area %d became aas area %d\r\n", tmpareanum, aasarea->areanum); + qprintf( "\r%6d", aasarea->areanum ); + // + if ( ( *aasworld ).areasettings[aasarea->areanum].contents & AREACONTENTS_CLUSTERPORTAL ) { + static int num; + Log_Write( "***** area %d is a cluster portal %d\n", aasarea->areanum, num++ ); + } //end if + // + ( *aasworld ).numareas++; + return -( ( *aasworld ).numareas - 1 ); +} //end of the function AAS_StoreArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StoreTree_r( tmp_node_t *tmpnode ) { + int aasnodenum; + plane_t *plane; + aas_node_t *aasnode; + + //if it is a solid leaf + if ( !tmpnode ) { + return 0; + } + //negative so it's an area + if ( tmpnode->tmparea ) { + return AAS_StoreArea( tmpnode->tmparea ); + } + //it's another node + //the first node is a dummy + if ( ( *aasworld ).numnodes == 0 ) { + ( *aasworld ).numnodes = 1; + } + if ( ( *aasworld ).numnodes >= max_aas.max_nodes ) { + Error( "AAS_MAX_NODES = %d", max_aas.max_nodes ); + } //end if + aasnodenum = ( *aasworld ).numnodes; + aasnode = &( *aasworld ).nodes[( *aasworld ).numnodes++]; + plane = &mapplanes[tmpnode->planenum]; + AAS_GetPlane( plane->normal, plane->dist, &aasnode->planenum ); + aasnode->children[0] = AAS_StoreTree_r( tmpnode->children[0] ); + aasnode->children[1] = AAS_StoreTree_r( tmpnode->children[1] ); + return aasnodenum; +} //end of the function AAS_StoreTree_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreBoundingBoxes( void ) { + if ( cfg.numbboxes > max_aas.max_bboxes ) { + Error( "more than %d bounding boxes", max_aas.max_bboxes ); + } //end if + ( *aasworld ).numbboxes = cfg.numbboxes; + memcpy( ( *aasworld ).bboxes, cfg.bboxes, cfg.numbboxes * sizeof( aas_bbox_t ) ); +} //end of the function AAS_StoreBoundingBoxes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreFile( char *filename ) { + AAS_AllocMaxAAS(); + + Log_Write( "AAS_StoreFile\r\n" ); + // + AAS_StoreBoundingBoxes(); + // + qprintf( "%6d areas stored", 0 ); + //start with node 1 because node zero is a dummy + AAS_StoreTree_r( tmpaasworld.nodes ); + qprintf( "\n" ); + Log_Write( "%6d areas stored\r\n", ( *aasworld ).numareas ); + ( *aasworld ).loaded = true; +} //end of the function AAS_StoreFile diff --git a/src/bspc/aas_store.h b/src/bspc/aas_store.h new file mode 100644 index 0000000..1269451 --- /dev/null +++ b/src/bspc/aas_store.h @@ -0,0 +1,125 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: aas_store.h +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#define AAS_MAX_BBOXES 5 +#define AAS_MAX_VERTEXES 512000 +#define AAS_MAX_PLANES 65536 +#define AAS_MAX_EDGES 512000 +#define AAS_MAX_EDGEINDEXSIZE 512000 +#define AAS_MAX_FACES 512000 +#define AAS_MAX_FACEINDEXSIZE 512000 +#define AAS_MAX_AREAS 65536 +#define AAS_MAX_AREASETTINGS 65536 +#define AAS_MAX_REACHABILITYSIZE 65536 +#define AAS_MAX_NODES 256000 +#define AAS_MAX_PORTALS 65536 +#define AAS_MAX_PORTALINDEXSIZE 65536 +#define AAS_MAX_CLUSTERS 65536 + +#define BSPCINCLUDE +#include "../botlib/be_aas.h" +#include "../botlib/be_aas_def.h" + +/* +typedef struct bspc_aas_s +{ + int loaded; + int initialized; //true when AAS has been initialized + int savefile; //set true when file should be saved + //bounding boxes + int numbboxes; + aas_bbox_t *bboxes; + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //planes + int numplanes; + aas_plane_t *planes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + //convex area settings + int numareasettings; + aas_areasettings_t *areasettings; + //reachablity list + int reachabilitysize; + aas_reachability_t *reachability; + //nodes of the bsp tree + int numnodes; + aas_node_t *nodes; + //cluster portals + int numportals; + aas_portal_t *portals; + //cluster portal index + int portalindexsize; + aas_portalindex_t *portalindex; + //clusters + int numclusters; + aas_cluster_t *clusters; + // + int numreachabilityareas; + float reachabilitytime; +} bspc_aas_t; + +extern bspc_aas_t aasworld; +//*/ + +// Ridah +extern aas_t aasworlds[1]; +extern aas_t *aasworld; +// done. + +//stores the AAS file from the temporary AAS +void AAS_StoreFile( char *filename ); +//returns a number of the given plane +qboolean AAS_FindPlane( vec3_t normal, float dist, int *planenum ); +//allocates the maximum AAS memory for storage +void AAS_AllocMaxAAS( void ); +//frees the maximum AAS memory for storage +void AAS_FreeMaxAAS( void ); diff --git a/src/bspc/be_aas_bspc.c b/src/bspc/be_aas_bspc.c new file mode 100644 index 0000000..ba2fe22 --- /dev/null +++ b/src/bspc/be_aas_bspc.c @@ -0,0 +1,314 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: b_aas_bspc.c +// Function: Area Awareness System +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-09-14 +// Tab Size: 3 +//=========================================================================== + +#include "../game/q_shared.h" +#include "../bspc/l_log.h" +#include "../bspc/l_qfiles.h" +#include "../botlib/l_memory.h" +#include "../botlib/l_script.h" +#include "../botlib/l_precomp.h" +#include "../botlib/l_struct.h" +#include "../botlib/aasfile.h" +#include "../botlib/botlib.h" +#include "../botlib/be_aas.h" +#include "../botlib/be_aas_def.h" +#include "../qcommon/cm_public.h" + +//#define BSPC + +extern botlib_import_t botimport; +extern qboolean capsule_collision; + +//#define AAS_MOVE_DEBUG + +botlib_import_t botimport; +clipHandle_t worldmodel; + +void Error( char *error, ... ); + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Error( char *fmt, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + Error( text ); +} //end of the function AAS_Error +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sys_MilliSeconds( void ) { + return clock() * 1000 / CLOCKS_PER_SEC; +} //end of the function Sys_MilliSeconds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DebugLine( vec3_t start, vec3_t end, int color ) { +} //end of the function AAS_DebugLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownDebugLines( void ) { +} //end of the function AAS_ClearShownDebugLines +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *BotImport_BSPEntityData( void ) { + return CM_EntityString(); +} //end of the function AAS_GetEntityData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotImport_Trace( bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask ) { + trace_t result; + + CM_BoxTrace( &result, start, end, mins, maxs, worldmodel, contentmask, capsule_collision ); + + bsptrace->allsolid = result.allsolid; + bsptrace->contents = result.contents; + VectorCopy( result.endpos, bsptrace->endpos ); + bsptrace->ent = result.entityNum; + bsptrace->fraction = result.fraction; + bsptrace->exp_dist = 0; + bsptrace->plane.dist = result.plane.dist; + VectorCopy( result.plane.normal, bsptrace->plane.normal ); + bsptrace->plane.signbits = result.plane.signbits; + bsptrace->plane.type = result.plane.type; + bsptrace->sidenum = 0; + bsptrace->startsolid = result.startsolid; + bsptrace->surface.flags = result.surfaceFlags; +} //end of the function BotImport_Trace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotImport_PointContents( vec3_t p ) { + return CM_PointContents( p, worldmodel ); +} //end of the function BotImport_PointContents +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *BotImport_GetMemory( int size ) { + return GetMemory( size ); +} //end of the function BotImport_GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotImport_Print( int type, char *fmt, ... ) { + va_list argptr; + char buf[1024]; + + va_start( argptr, fmt ); + vsprintf( buf, fmt, argptr ); + printf( buf ); + if ( buf[0] != '\r' ) { + Log_Write( buf ); + } + va_end( argptr ); +} //end of the function BotImport_Print +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotImport_BSPModelMinsMaxsOrigin( int modelnum, vec3_t angles, vec3_t outmins, vec3_t outmaxs, vec3_t origin ) { + clipHandle_t h; + vec3_t mins, maxs; + float max; + int i; + + h = CM_InlineModel( modelnum ); + CM_ModelBounds( h, mins, maxs ); + //if the model is rotated + if ( ( angles[0] || angles[1] || angles[2] ) ) { // expand for rotation + + max = RadiusFromBounds( mins, maxs ); + for ( i = 0; i < 3; i++ ) + { + mins[i] = ( mins[i] + maxs[i] ) * 0.5 - max; + maxs[i] = ( mins[i] + maxs[i] ) * 0.5 + max; + } //end for + } //end if + if ( outmins ) { + VectorCopy( mins, outmins ); + } + if ( outmaxs ) { + VectorCopy( maxs, outmaxs ); + } + if ( origin ) { + VectorClear( origin ); + } +} //end of the function BotImport_BSPModelMinsMaxsOrigin +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Com_DPrintf( char *fmt, ... ) { + va_list argptr; + char buf[1024]; + + va_start( argptr, fmt ); + vsprintf( buf, fmt, argptr ); + printf( buf ); + if ( buf[0] != '\r' ) { + Log_Write( buf ); + } + va_end( argptr ); +} //end of the function Com_DPrintf +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int COM_Compress( char *data_p ) { + return strlen( data_p ); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Com_Memset( void* dest, const int val, const size_t count ) { + memset( dest, val, count ); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Com_Memcpy( void* dest, const void* src, const size_t count ) { + memcpy( dest, src, count ); +} +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitBotImport( void ) { + botimport.BSPEntityData = BotImport_BSPEntityData; + botimport.GetMemory = BotImport_GetMemory; + botimport.FreeMemory = FreeMemory; + botimport.Trace = BotImport_Trace; + botimport.PointContents = BotImport_PointContents; + botimport.Print = BotImport_Print; + botimport.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin; +} //end of the function AAS_InitBotImport +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_CalcReachAndClusters( struct quakefile_s *qf ) { + float time; + + Log_Print( "loading collision map...\n" ); + // + if ( !qf->pakfile[0] ) { + strcpy( qf->pakfile, qf->filename ); + } + //load the map + CM_LoadMap( (char *) qf, qfalse, &( *aasworld ).bspchecksum ); + //get a handle to the world model + worldmodel = CM_InlineModel( 0 ); // 0 = world, 1 + are bmodels + //initialize bot import structure + AAS_InitBotImport(); + //load the BSP entity string + AAS_LoadBSPFile(); + //init physics settings + AAS_InitSettings(); + //initialize AAS link heap + AAS_InitAASLinkHeap(); + //initialize the AAS linked entities for the new map + AAS_InitAASLinkedEntities(); + //reset all reachabilities and clusters + ( *aasworld ).reachabilitysize = 0; + ( *aasworld ).numclusters = 0; + //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) + AAS_SetViewPortalsAsClusterPortals(); + //calculate reachabilities + AAS_InitReachability(); + time = 0; + while ( AAS_ContinueInitReachability( time ) ) time++; + //calculate clusters + AAS_InitClustering(); +} //end of the function AAS_CalcReachAndClusters + +// Ridah +void AAS_SetWorldPointer( aas_t *newaasworld ) { + aasworld = newaasworld; +} +// done. diff --git a/src/bspc/be_aas_bspc.h b/src/bspc/be_aas_bspc.h new file mode 100644 index 0000000..4ff1f18 --- /dev/null +++ b/src/bspc/be_aas_bspc.h @@ -0,0 +1,42 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: b_aas_bspc.h +// Function: Area Awareness System +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1999-02-28 +// Tab Size: 3 +//=========================================================================== + +void AAS_CalcReachAndClusters( struct quakefile_s *qf ); + +// Ridah +void AAS_SetWorldPointer( aas_t *newaasworld ); +// done. diff --git a/src/bspc/brushbsp.c b/src/bspc/brushbsp.c new file mode 100644 index 0000000..52644b3 --- /dev/null +++ b/src/bspc/brushbsp.c @@ -0,0 +1,1923 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: BrushBSP +// Function: Build a BSP tree from a set of brushes +// Programmer: id Software & Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "l_mem.h" +#include "..\botlib\aasfile.h" +#include "aas_store.h" +#include "aas_cfg.h" + +#include + +/* +each side has a count of the other sides it splits + +the best split will be the one that minimizes the total split counts +of all remaining sides + +precalc side on plane table + +evaluate split side +{ +cost = 0 +for all sides + for all sides + get + if side splits side and splitside is on same child + cost++; +} +*/ + +int c_nodes; +int c_nonvis; +int c_active_brushes; +int c_solidleafnodes; +int c_totalsides; +int c_brushmemory; +int c_peak_brushmemory; +int c_nodememory; +int c_peak_totalbspmemory; + +// if a brush just barely pokes onto the other side, +// let it slide by without chopping +#define PLANESIDE_EPSILON 0.001 +//0.1 + +//#ifdef DEBUG +typedef struct cname_s +{ + int value; + char *name; +} cname_t; + +cname_t contentnames[] = +{ + {CONTENTS_SOLID,"CONTENTS_SOLID"}, + {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, + {CONTENTS_AUX,"CONTENTS_AUX"}, + {CONTENTS_LAVA,"CONTENTS_LAVA"}, + {CONTENTS_SLIME,"CONTENTS_SLIME"}, + {CONTENTS_WATER,"CONTENTS_WATER"}, + {CONTENTS_MIST,"CONTENTS_MIST"}, + {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, + + {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, + {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, + {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, + {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, + {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, + {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, + {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, + {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, + {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, + {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, + {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, + {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, + {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, + {CONTENTS_Q2TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, + {CONTENTS_LADDER,"CONTENTS_LADDER"}, + {0, 0} +}; + +void PrintContents( int contents ) { + int i; + + for ( i = 0; contentnames[i].value; i++ ) + { + if ( contents & contentnames[i].value ) { + Log_Write( "%s,", contentnames[i].name ); + } //end if + } //end for +} //end of the function PrintContents + +//#endif DEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ResetBrushBSP( void ) { + c_nodes = 0; + c_nonvis = 0; + c_active_brushes = 0; + c_solidleafnodes = 0; + c_totalsides = 0; + c_brushmemory = 0; + c_peak_brushmemory = 0; + c_nodememory = 0; + c_peak_totalbspmemory = 0; +} //end of the function ResetBrushBSP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FindBrushInTree( node_t *node, int brushnum ) { + bspbrush_t *b; + + if ( node->planenum == PLANENUM_LEAF ) { + for ( b = node->brushlist ; b ; b = b->next ) + if ( b->original->brushnum == brushnum ) { + Log_Print( "here\n" ); + } + return; + } + FindBrushInTree( node->children[0], brushnum ); + FindBrushInTree( node->children[1], brushnum ); +} //end of the function FindBrushInTree +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DrawBrushList( bspbrush_t *brush, node_t *node ) { + int i; + side_t *s; + + GLS_BeginScene(); + for ( ; brush ; brush = brush->next ) + { + for ( i = 0 ; i < brush->numsides ; i++ ) + { + s = &brush->sides[i]; + if ( !s->winding ) { + continue; + } + if ( s->texinfo == TEXINFO_NODE ) { + GLS_Winding( s->winding, 1 ); + } else if ( !( s->flags & SFL_VISIBLE ) ) { + GLS_Winding( s->winding, 2 ); + } else { + GLS_Winding( s->winding, 0 ); + } + } + } + GLS_EndScene(); +} //end of the function DrawBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WriteBrushList( char *name, bspbrush_t *brush, qboolean onlyvis ) { + int i; + side_t *s; + FILE *f; + + qprintf( "writing %s\n", name ); + f = SafeOpenWrite( name ); + + for ( ; brush ; brush = brush->next ) + { + for ( i = 0 ; i < brush->numsides ; i++ ) + { + s = &brush->sides[i]; + if ( !s->winding ) { + continue; + } + if ( onlyvis && !( s->flags & SFL_VISIBLE ) ) { + continue; + } + OutputWinding( brush->sides[i].winding, f ); + } + } + + fclose( f ); +} //end of the function WriteBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintBrush( bspbrush_t *brush ) { + int i; + + printf( "brush: %p\n", brush ); + for ( i = 0; i < brush->numsides ; i++ ) + { + pw( brush->sides[i].winding ); + printf( "\n" ); + } //end for +} //end of the function PrintBrush +//=========================================================================== +// Sets the mins/maxs based on the windings +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BoundBrush( bspbrush_t *brush ) { + int i, j; + winding_t *w; + + ClearBounds( brush->mins, brush->maxs ); + for ( i = 0 ; i < brush->numsides ; i++ ) + { + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + for ( j = 0 ; j < w->numpoints ; j++ ) + AddPointToBounds( w->p[j], brush->mins, brush->maxs ); + } +} //end of the function BoundBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CreateBrushWindings( bspbrush_t *brush ) { + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + for ( i = 0 ; i < brush->numsides ; i++ ) + { + side = &brush->sides[i]; + plane = &mapplanes[side->planenum]; + w = BaseWindingForPlane( plane->normal, plane->dist ); + for ( j = 0 ; j < brush->numsides && w; j++ ) + { + if ( i == j ) { + continue; + } + if ( brush->sides[j].flags & SFL_BEVEL ) { + continue; + } + plane = &mapplanes[brush->sides[j].planenum ^ 1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON); + } + + side->winding = w; + } + + BoundBrush( brush ); +} //end of the function CreateBrushWindings +//=========================================================================== +// Creates a new axial brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *BrushFromBounds( vec3_t mins, vec3_t maxs ) { + bspbrush_t *b; + int i; + vec3_t normal; + vec_t dist; + + b = AllocBrush( 6 ); + b->numsides = 6; + for ( i = 0 ; i < 3 ; i++ ) + { + VectorClear( normal ); + normal[i] = 1; + dist = maxs[i]; + b->sides[i].planenum = FindFloatPlane( normal, dist ); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3 + i].planenum = FindFloatPlane( normal, dist ); + } + + CreateBrushWindings( b ); + + return b; +} //end of the function BrushFromBounds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BrushOutOfBounds( bspbrush_t *brush, vec3_t mins, vec3_t maxs, float epsilon ) { + int i, j, n; + winding_t *w; + side_t *side; + + for ( i = 0; i < brush->numsides; i++ ) + { + side = &brush->sides[i]; + w = side->winding; + for ( j = 0; j < w->numpoints; j++ ) + { + for ( n = 0; n < 3; n++ ) + { + if ( w->p[j][n] < ( mins[n] + epsilon ) || w->p[j][n] > ( maxs[n] - epsilon ) ) { + return true; + } + } //end for + } //end for + } //end for + return false; +} //end of the function BrushOutOfBounds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t BrushVolume( bspbrush_t *brush ) { + int i; + winding_t *w; + vec3_t corner; + vec_t d, area, volume; + plane_t *plane; + + if ( !brush ) { + return 0; + } + + // grab the first valid point as the corner + w = NULL; + for ( i = 0; i < brush->numsides; i++ ) + { + w = brush->sides[i].winding; + if ( w ) { + break; + } + } //end for + if ( !w ) { + return 0; + } + VectorCopy( w->p[0], corner ); + + // make tetrahedrons to all other faces + volume = 0; + for ( ; i < brush->numsides; i++ ) + { + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + plane = &mapplanes[brush->sides[i].planenum]; + d = -( DotProduct( corner, plane->normal ) - plane->dist ); + area = WindingArea( w ); + volume += d * area; + } //end for + + volume /= 3; + return volume; +} //end of the function BrushVolume +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CountBrushList( bspbrush_t *brushes ) { + int c; + + c = 0; + for ( ; brushes; brushes = brushes->next ) c++; + return c; +} //end of the function CountBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *AllocNode( void ) { + node_t *node; + + node = GetMemory( sizeof( *node ) ); + memset( node, 0, sizeof( *node ) ); + if ( numthreads == 1 ) { + c_nodememory += MemorySize( node ); + } //end if + return node; +} //end of the function AllocNode +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *AllocBrush( int numsides ) { + bspbrush_t *bb; + int c; + + c = (int)&( ( (bspbrush_t *)0 )->sides[numsides] ); + bb = GetMemory( c ); + memset( bb, 0, c ); + if ( numthreads == 1 ) { + c_active_brushes++; + c_brushmemory += MemorySize( bb ); + if ( c_brushmemory > c_peak_brushmemory ) { + c_peak_brushmemory = c_brushmemory; + } + } //end if + return bb; +} //end of the function AllocBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeBrush( bspbrush_t *brushes ) { + int i; + + for ( i = 0 ; i < brushes->numsides ; i++ ) + if ( brushes->sides[i].winding ) { + FreeWinding( brushes->sides[i].winding ); + } + if ( numthreads == 1 ) { + c_active_brushes--; + c_brushmemory -= MemorySize( brushes ); + if ( c_brushmemory < 0 ) { + c_brushmemory = 0; + } + } //end if + FreeMemory( brushes ); +} //end of the function FreeBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeBrushList( bspbrush_t *brushes ) { + bspbrush_t *next; + + for ( ; brushes; brushes = next ) + { + next = brushes->next; + + FreeBrush( brushes ); + } //end for +} //end of the function FreeBrushList +//=========================================================================== +// Duplicates the brush, the sides, and the windings +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *CopyBrush( bspbrush_t *brush ) { + bspbrush_t *newbrush; + int size; + int i; + + size = (int)&( ( (bspbrush_t *)0 )->sides[brush->numsides] ); + + newbrush = AllocBrush( brush->numsides ); + memcpy( newbrush, brush, size ); + + for ( i = 0 ; i < brush->numsides ; i++ ) + { + if ( brush->sides[i].winding ) { + newbrush->sides[i].winding = CopyWinding( brush->sides[i].winding ); + } + } + + return newbrush; +} //end of the function CopyBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *PointInLeaf( node_t *node, vec3_t point ) { + vec_t d; + plane_t *plane; + + while ( node->planenum != PLANENUM_LEAF ) + { + plane = &mapplanes[node->planenum]; + d = DotProduct( point, plane->normal ) - plane->dist; + if ( d > 0 ) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + + return node; +} //end of the function PointInLeaf +//=========================================================================== +// Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#if 0 +int BoxOnPlaneSide( vec3_t mins, vec3_t maxs, plane_t *plane ) { + int side; + int i; + vec3_t corners[2]; + vec_t dist1, dist2; + + // axial planes are easy + if ( plane->type < 3 ) { + side = 0; + if ( maxs[plane->type] > plane->dist + PLANESIDE_EPSILON ) { + side |= PSIDE_FRONT; + } + if ( mins[plane->type] < plane->dist - PLANESIDE_EPSILON ) { + side |= PSIDE_BACK; + } + return side; + } + + // create the proper leading and trailing verts for the box + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( plane->normal[i] < 0 ) { + corners[0][i] = mins[i]; + corners[1][i] = maxs[i]; + } else + { + corners[1][i] = mins[i]; + corners[0][i] = maxs[i]; + } + } + + dist1 = DotProduct( plane->normal, corners[0] ) - plane->dist; + dist2 = DotProduct( plane->normal, corners[1] ) - plane->dist; + side = 0; + if ( dist1 >= PLANESIDE_EPSILON ) { + side = PSIDE_FRONT; + } + if ( dist2 < PLANESIDE_EPSILON ) { + side |= PSIDE_BACK; + } + + return side; +} +#else +int BoxOnPlaneSide( vec3_t emins, vec3_t emaxs, plane_t *p ) { + float dist1, dist2; + int sides; + + // axial planes are easy + if ( p->type < 3 ) { + sides = 0; + if ( emaxs[p->type] > p->dist + PLANESIDE_EPSILON ) { + sides |= PSIDE_FRONT; + } + if ( emins[p->type] < p->dist - PLANESIDE_EPSILON ) { + sides |= PSIDE_BACK; + } + return sides; + } //end if + +// 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 +// assert( 0 ); + break; + } + + sides = 0; + if ( dist1 - p->dist >= PLANESIDE_EPSILON ) { + sides = PSIDE_FRONT; + } + if ( dist2 - p->dist < PLANESIDE_EPSILON ) { + sides |= PSIDE_BACK; + } + +// assert(sides != 0); + + return sides; +} +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int QuickTestBrushToPlanenum( bspbrush_t *brush, int planenum, int *numsplits ) { + int i, num; + plane_t *plane; + int s; + + *numsplits = 0; + + plane = &mapplanes[planenum]; + +#ifdef ME + //fast axial cases + if ( plane->type < 3 ) { + if ( plane->dist + PLANESIDE_EPSILON < brush->mins[plane->type] ) { + return PSIDE_FRONT; + } + if ( plane->dist - PLANESIDE_EPSILON > brush->maxs[plane->type] ) { + return PSIDE_BACK; + } + } //end if +#endif //ME*/ + + // if the brush actually uses the planenum, + // we can tell the side for sure + for ( i = 0; i < brush->numsides; i++ ) + { + num = brush->sides[i].planenum; + if ( num >= MAX_MAPFILE_PLANES ) { + Error( "bad planenum" ); + } + if ( num == planenum ) { + return PSIDE_BACK | PSIDE_FACING; + } + if ( num == ( planenum ^ 1 ) ) { + return PSIDE_FRONT | PSIDE_FACING; + } + + } + + // box on plane side + s = BoxOnPlaneSide( brush->mins, brush->maxs, plane ); + + // if both sides, count the visible faces split + if ( s == PSIDE_BOTH ) { + *numsplits += 3; + } + + return s; +} //end of the function QuickTestBrushToPlanenum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TestBrushToPlanenum( bspbrush_t *brush, int planenum, + int *numsplits, qboolean *hintsplit, int *epsilonbrush ) { + int i, j, num; + plane_t *plane; + int s = 0; + winding_t *w; + vec_t d, d_front, d_back; + int front, back; + int type; + float dist; + + *numsplits = 0; + *hintsplit = false; + + plane = &mapplanes[planenum]; + +#ifdef ME +// fast axial cases + type = plane->type; + if ( type < 3 ) { + dist = plane->dist; + if ( dist + PLANESIDE_EPSILON < brush->mins[type] ) { + return PSIDE_FRONT; + } + if ( dist - PLANESIDE_EPSILON > brush->maxs[type] ) { + return PSIDE_BACK; + } + if ( brush->mins[type] < dist - PLANESIDE_EPSILON && + brush->maxs[type] > dist + PLANESIDE_EPSILON ) { + s = PSIDE_BOTH; + } + } //end if + + if ( s != PSIDE_BOTH ) +#endif //ME + { + // if the brush actually uses the planenum, + // we can tell the side for sure + for ( i = 0; i < brush->numsides; i++ ) + { + num = brush->sides[i].planenum; + if ( num >= MAX_MAPFILE_PLANES ) { + Error( "bad planenum" ); + } + if ( num == planenum ) { + //we don't need to test this side plane again + brush->sides[i].flags |= SFL_TESTED; + return PSIDE_BACK | PSIDE_FACING; + } //end if + if ( num == ( planenum ^ 1 ) ) { + //we don't need to test this side plane again + brush->sides[i].flags |= SFL_TESTED; + return PSIDE_FRONT | PSIDE_FACING; + } //end if + } //end for + + // box on plane side + s = BoxOnPlaneSide( brush->mins, brush->maxs, plane ); + + if ( s != PSIDE_BOTH ) { + return s; + } + } //end if + +// if both sides, count the visible faces split + d_front = d_back = 0; + + for ( i = 0; i < brush->numsides; i++ ) + { + if ( brush->sides[i].texinfo == TEXINFO_NODE ) { + continue; // on node, don't worry about splits + } + if ( !( brush->sides[i].flags & SFL_VISIBLE ) ) { + continue; // we don't care about non-visible + } + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + front = back = 0; + for ( j = 0; j < w->numpoints; j++ ) + { + d = DotProduct( w->p[j], plane->normal ) - plane->dist; + if ( d > d_front ) { + d_front = d; + } + if ( d < d_back ) { + d_back = d; + } + if ( d > 0.1 ) { // PLANESIDE_EPSILON) + front = 1; + } + if ( d < -0.1 ) { // PLANESIDE_EPSILON) + back = 1; + } + } //end for + if ( front && back ) { + if ( !( brush->sides[i].surf & SURF_SKIP ) ) { + ( *numsplits )++; + if ( brush->sides[i].surf & SURF_HINT ) { + *hintsplit = true; + } //end if + } //end if + } //end if + } //end for + + if ( ( d_front > 0.0 && d_front < 1.0 ) + || ( d_back < 0.0 && d_back > -1.0 ) ) { + ( *epsilonbrush )++; + } + +#if 0 + if ( *numsplits == 0 ) { // didn't really need to be split + if ( front ) { + s = PSIDE_FRONT; + } else if ( back ) { + s = PSIDE_BACK; + } else { s = 0;} + } +#endif + + return s; +} //end of the function TestBrushToPlanenum +//=========================================================================== +// Returns true if the winding would be crunched out of +// existance by the vertex snapping. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define EDGE_LENGTH 0.2 +qboolean WindingIsTiny( winding_t *w ) { +#if 0 + if ( WindingArea( w ) < 1 ) { + return true; + } + return false; +#else + int i, j; + vec_t len; + vec3_t delta; + int edges; + + edges = 0; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + j = i == w->numpoints - 1 ? 0 : i + 1; + VectorSubtract( w->p[j], w->p[i], delta ); + len = VectorLength( delta ); + if ( len > EDGE_LENGTH ) { + if ( ++edges == 3 ) { + return false; + } + } + } + return true; +#endif +} +//=========================================================================== +// Returns true if the winding still has one of the points +// from basewinding for plane +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsHuge( winding_t *w ) { + int i, j; + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + if ( w->p[i][j] < -BOGUS_RANGE + 1 || w->p[i][j] > BOGUS_RANGE - 1 ) { + return true; + } + } + return false; +} //end of the function WindingIsHuge +//=========================================================================== +// creates a leaf out of the given nodes with the given brushes +// +// FIXME: use the origin->expansionbbox to find areas with different +// presence types +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LeafNode( node_t *node, bspbrush_t *brushes ) { + bspbrush_t *b; + int i; + + node->side = NULL; + node->planenum = PLANENUM_LEAF; + node->contents = 0; + + for ( b = brushes; b; b = b->next ) + { + // if the brush is solid and all of its sides are on nodes, + // it eats everything + if ( b->original->contents & CONTENTS_SOLID ) { + for ( i = 0 ; i < b->numsides ; i++ ) + if ( b->sides[i].texinfo != TEXINFO_NODE ) { + break; + } + if ( i == b->numsides ) { + node->contents = CONTENTS_SOLID; + break; + } //end if + } //end if + node->contents |= b->original->contents; + } //end for + + if ( create_aas ) { + node->expansionbboxes = 0; + node->contents = 0; + for ( b = brushes; b; b = b->next ) + { + node->expansionbboxes |= b->original->expansionbbox; + node->contents |= b->original->contents; + if ( b->original->modelnum ) { + node->modelnum = b->original->modelnum; + } + } //end for + if ( node->contents & CONTENTS_SOLID ) { + if ( node->expansionbboxes != cfg.allpresencetypes ) { + node->contents &= ~CONTENTS_SOLID; + } //end if + } //end if + } //end if + + node->brushlist = brushes; +} //end of the function LeafNode +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CheckPlaneAgainstParents( int pnum, node_t *node ) { + node_t *p; + + for ( p = node->parent; p; p = p->parent ) + { + if ( p->planenum == pnum ) { + Error( "Tried parent" ); + } + } //end for +} //end of the function CheckPlaneAgainstParants +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean CheckPlaneAgainstVolume( int pnum, node_t *node ) { + bspbrush_t *front, *back; + qboolean good; + + SplitBrush( node->volume, pnum, &front, &back ); + + good = ( front && back ); + + if ( front ) { + FreeBrush( front ); + } + if ( back ) { + FreeBrush( back ); + } + + return good; +} //end of the function CheckPlaneAgaintsVolume +//=========================================================================== +// Using a hueristic, choses one of the sides out of the brushlist +// to partition the brushes with. +// Returns NULL if there are no valid planes to split with.. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +/* + + //perfect for fact2: + + value = 5*facing - 7*splits - abs(front-back); + if (mapplanes[pnum].type < 3) + value+=3; // axial is better +*/ + +side_t *SelectSplitSide( bspbrush_t *brushes, node_t *node ) { + int value, bestvalue; + bspbrush_t *brush, *test; + side_t *side, *bestside; + int i, pass, numpasses; + //int j; + int pnum; + int s; + int front, back, both, facing, splits; + int bsplits; + int bestsplits; + int epsilonbrush; + qboolean hintsplit = false; + + bestside = NULL; + bestvalue = -99999; + bestsplits = 0; + + // the search order goes: visible-structural, visible-detail, + // nonvisible-structural, nonvisible-detail. + // If any valid plane is available in a pass, no further + // passes will be tried. + numpasses = 2; + for ( pass = 0; pass < numpasses; pass++ ) + { + for ( brush = brushes; brush; brush = brush->next ) + { + // only check detail the second pass +// if ( (pass & 1) && !(brush->original->contents & CONTENTS_DETAIL) ) +// continue; +// if ( !(pass & 1) && (brush->original->contents & CONTENTS_DETAIL) ) +// continue; + for ( i = 0; i < brush->numsides; i++ ) + { + side = brush->sides + i; +// if (side->flags & SFL_BEVEL) +// continue; // never use a bevel as a spliter + if ( !side->winding ) { + continue; // nothing visible, so it can't split + } + if ( side->texinfo == TEXINFO_NODE ) { + continue; // allready a node splitter + } + if ( side->flags & SFL_TESTED ) { + continue; // we allready have metrics for this plane + } +// if (side->surf & SURF_SKIP) +// continue; // skip surfaces are never chosen +// if (!(side->flags & SFL_VISIBLE) && (pass < 2)) +// continue; // only check visible faces on first pass + if ( ( side->flags & SFL_CURVE ) && ( pass < 1 ) ) { + continue; // only check curves on the second pass + + } + pnum = side->planenum; + pnum &= ~1; // allways use positive facing plane + + CheckPlaneAgainstParents( pnum, node ); + + if ( !CheckPlaneAgainstVolume( pnum, node ) ) { + continue; // would produce a tiny volume + + } + front = 0; + back = 0; + both = 0; + facing = 0; + splits = 0; + epsilonbrush = 0; + + //inner loop: optimize + for ( test = brushes; test; test = test->next ) + { + s = TestBrushToPlanenum( test, pnum, &bsplits, &hintsplit, &epsilonbrush ); + + splits += bsplits; +// if (bsplits && (s&PSIDE_FACING) ) +// Error ("PSIDE_FACING with splits"); + + test->testside = s; + // + if ( s & PSIDE_FACING ) { + facing++; + } + if ( s & PSIDE_FRONT ) { + front++; + } + if ( s & PSIDE_BACK ) { + back++; + } + if ( s == PSIDE_BOTH ) { + both++; + } + } //end for + + // give a value estimate for using this plane + value = 5 * facing - 5 * splits - abs( front - back ); +// value = -5*splits; +// value = 5*facing - 5*splits; + if ( mapplanes[pnum].type < 3 ) { + value += 5; // axial is better + + } + value -= epsilonbrush * 1000; // avoid! + + // never split a hint side except with another hint + if ( hintsplit && !( side->surf & SURF_HINT ) ) { + value = -9999999; + } + + // save off the side test so we don't need + // to recalculate it when we actually seperate + // the brushes + if ( value > bestvalue ) { + bestvalue = value; + bestside = side; + bestsplits = splits; + for ( test = brushes; test ; test = test->next ) + test->side = test->testside; + } //end if + } //end for + } //end for (brush = brushes; + + // if we found a good plane, don't bother trying any + // other passes + if ( bestside ) { + if ( pass > 1 ) { + if ( numthreads == 1 ) { + c_nonvis++; + } + } + if ( pass > 0 ) { + node->detail_seperator = true; // not needed for vis + } + break; + } //end if + } //end for (pass = 0; + + // + // clear all the tested flags we set + // + for ( brush = brushes ; brush ; brush = brush->next ) + { + for ( i = 0; i < brush->numsides; i++ ) + { + brush->sides[i].flags &= ~SFL_TESTED; + } //end for + } //end for + + return bestside; +} //end of the function SelectSplitSide +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BrushMostlyOnSide( bspbrush_t *brush, plane_t *plane ) { + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for ( i = 0 ; i < brush->numsides ; i++ ) + { + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + for ( j = 0 ; j < w->numpoints ; j++ ) + { + d = DotProduct( w->p[j], plane->normal ) - plane->dist; + if ( d > max ) { + max = d; + side = PSIDE_FRONT; + } + if ( -d > max ) { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} //end of the function BrushMostlyOnSide +//=========================================================================== +// Generates two new brushes, leaving the original +// unchanged +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SplitBrush( bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back ) { + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for ( i = 0 ; i < brush->numsides ; i++ ) + { + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + for ( j = 0 ; j < w->numpoints ; j++ ) + { + d = DotProduct( w->p[j], plane->normal ) - plane->dist; + if ( d > 0 && d > d_front ) { + d_front = d; + } + if ( d < 0 && d < d_back ) { + d_back = d; + } + } + } + + if ( d_front < 0.2 ) { // PLANESIDE_EPSILON) + // only on back + *back = CopyBrush( brush ); + return; + } + if ( d_back > -0.2 ) { // PLANESIDE_EPSILON) + // only on front + *front = CopyBrush( brush ); + return; + } + + // create a new winding from the split plane + + w = BaseWindingForPlane( plane->normal, plane->dist ); + for ( i = 0 ; i < brush->numsides && w ; i++ ) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace( &w, plane2->normal, plane2->dist, 0 ); // PLANESIDE_EPSILON); + } + + if ( !w || WindingIsTiny( w ) ) { // the brush isn't really split + int side; + + side = BrushMostlyOnSide( brush, plane ); + if ( side == PSIDE_FRONT ) { + *front = CopyBrush( brush ); + } + if ( side == PSIDE_BACK ) { + *back = CopyBrush( brush ); + } + //free a possible winding + if ( w ) { + FreeWinding( w ); + } + return; + } + + if ( WindingIsHuge( w ) ) { + Log_Write( "WARNING: huge winding\n" ); + } + + midwinding = w; + + // split it for real + + for ( i = 0 ; i < 2 ; i++ ) + { + b[i] = AllocBrush( brush->numsides + 1 ); + b[i]->original = brush->original; + } + + // split all the current windings + + for ( i = 0 ; i < brush->numsides ; i++ ) + { + s = &brush->sides[i]; + w = s->winding; + if ( !w ) { + continue; + } + ClipWindingEpsilon( w, plane->normal, plane->dist, + 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1] ); + for ( j = 0 ; j < 2 ; j++ ) + { + if ( !cw[j] ) { + continue; + } +#if 0 + if ( WindingIsTiny( cw[j] ) ) { + FreeWinding( cw[j] ); + continue; + } +#endif + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; +// cs->planenum = s->planenum; +// cs->texinfo = s->texinfo; +// cs->original = s->original; + cs->winding = cw[j]; + cs->flags &= ~SFL_TESTED; + } + } + + + // see if we have valid polygons on both sides + + for ( i = 0 ; i < 2 ; i++ ) + { + BoundBrush( b[i] ); + for ( j = 0 ; j < 3 ; j++ ) + { + if ( b[i]->mins[j] < -MAX_MAP_BOUNDS || b[i]->maxs[j] > MAX_MAP_BOUNDS ) { + Log_Write( "bogus brush after clip" ); + break; + } + } + + if ( b[i]->numsides < 3 || j < 3 ) { + FreeBrush( b[i] ); + b[i] = NULL; + } + } + + if ( !( b[0] && b[1] ) ) { + if ( !b[0] && !b[1] ) { + Log_Write( "split removed brush\r\n" ); + } else { + Log_Write( "split not on both sides\r\n" ); + } + if ( b[0] ) { + FreeBrush( b[0] ); + *front = CopyBrush( brush ); + } + if ( b[1] ) { + FreeBrush( b[1] ); + *back = CopyBrush( brush ); + } + return; + } + + // add the midwinding to both sides + for ( i = 0 ; i < 2 ; i++ ) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum ^ i ^ 1; + cs->texinfo = TEXINFO_NODE; //never use these sides as splitters + cs->flags &= ~SFL_VISIBLE; + cs->flags &= ~SFL_TESTED; + if ( i == 0 ) { + cs->winding = CopyWinding( midwinding ); + } else { + cs->winding = midwinding; + } + } + + { + vec_t v1; + int i; + + for ( i = 0; i < 2; i++ ) + { + v1 = BrushVolume( b[i] ); + if ( v1 < 1.0 ) { + FreeBrush( b[i] ); + b[i] = NULL; + //Log_Write("tiny volume after clip"); + } + } + if ( !b[0] && !b[1] ) { + Log_Write( "two tiny brushes\r\n" ); + } //end if + } + + *front = b[0]; + *back = b[1]; +} //end of the function SplitBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SplitBrushList( bspbrush_t *brushes, + node_t *node, bspbrush_t **front, bspbrush_t **back ) { + bspbrush_t *brush, *newbrush, *newbrush2; + side_t *side; + int sides; + int i; + + *front = *back = NULL; + + for ( brush = brushes ; brush ; brush = brush->next ) + { + sides = brush->side; + + if ( sides == PSIDE_BOTH ) { // split into two brushes + SplitBrush( brush, node->planenum, &newbrush, &newbrush2 ); + if ( newbrush ) { + newbrush->next = *front; + *front = newbrush; + } //end if + if ( newbrush2 ) { + newbrush2->next = *back; + *back = newbrush2; + } //end if + continue; + } //end if + + newbrush = CopyBrush( brush ); + + // if the planenum is actualy a part of the brush + // find the plane and flag it as used so it won't be tried + // as a splitter again + if ( sides & PSIDE_FACING ) { + for ( i = 0 ; i < newbrush->numsides ; i++ ) + { + side = newbrush->sides + i; + if ( ( side->planenum & ~1 ) == node->planenum ) { + side->texinfo = TEXINFO_NODE; + } + } //end for + } //end if + + if ( sides & PSIDE_FRONT ) { + newbrush->next = *front; + *front = newbrush; + continue; + } //end if + if ( sides & PSIDE_BACK ) { + newbrush->next = *back; + *back = newbrush; + continue; + } //end if + } //end for +} //end of the function SplitBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CheckBrushLists( bspbrush_t *brushlist1, bspbrush_t *brushlist2 ) { + bspbrush_t *brush1, *brush2; + + for ( brush1 = brushlist1; brush1; brush1 = brush1->next ) + { + for ( brush2 = brushlist2; brush2; brush2 = brush2->next ) + { + assert( brush1 != brush2 ); + } //end for + } //end for +} //end of the function CheckBrushLists +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int numrecurse = 0; + +node_t *BuildTree_r( node_t *node, bspbrush_t *brushes ) { + node_t *newnode; + side_t *bestside; + int i, totalmem; + bspbrush_t *children[2]; + + qprintf( "\r%6d", numrecurse ); + numrecurse++; + + if ( numthreads == 1 ) { + totalmem = WindingMemory() + c_nodememory + c_brushmemory; + if ( totalmem > c_peak_totalbspmemory ) { + c_peak_totalbspmemory = totalmem; + } + c_nodes++; + } //endif + + if ( drawflag ) { + DrawBrushList( brushes, node ); + } + + // find the best plane to use as a splitter + bestside = SelectSplitSide( brushes, node ); + if ( !bestside ) { + // leaf node + node->side = NULL; + node->planenum = -1; + LeafNode( node, brushes ); + if ( node->contents & CONTENTS_SOLID ) { + c_solidleafnodes++; + } + if ( create_aas ) { + //free up memory!!! + FreeBrushList( node->brushlist ); + node->brushlist = NULL; + //free the node volume brush + if ( node->volume ) { + FreeBrush( node->volume ); + node->volume = NULL; + } //end if + } //end if + return node; + } //end if + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; // always use front facing + + //split the brush list in two for both children + SplitBrushList( brushes, node, &children[0], &children[1] ); + //free the old brush list + FreeBrushList( brushes ); + + // allocate children before recursing + for ( i = 0; i < 2; i++ ) + { + newnode = AllocNode(); + newnode->parent = node; + node->children[i] = newnode; + } //end for + + //split the volume brush of the node for the children + SplitBrush( node->volume, node->planenum, &node->children[0]->volume, + &node->children[1]->volume ); + + if ( create_aas ) { + //free the volume brush + if ( node->volume ) { + FreeBrush( node->volume ); + node->volume = NULL; + } //end if + } //end if + // recursively process children + for ( i = 0; i < 2; i++ ) + { + node->children[i] = BuildTree_r( node->children[i], children[i] ); + } //end for + + return node; +} //end of the function BuildTree_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *firstnode; //first node in the list +node_t *lastnode; //last node in the list +int nodelistsize; //number of nodes in the list +int use_nodequeue = 0; //use nodequeue, otherwise a node stack is used +int numwaiting = 0; + +void ( *AddNodeToList )( node_t *node ); + +//add the node to the front of the node list +//(effectively using a node stack) +void AddNodeToStack( node_t *node ) { + ThreadLock(); + + node->next = firstnode; + firstnode = node; + if ( !lastnode ) { + lastnode = node; + } + nodelistsize++; + + ThreadUnlock(); + // + ThreadSemaphoreIncrease( 1 ); +} //end of the function AddNodeToStack +//add the node to the end of the node list +//(effectively using a node queue) +void AddNodeToQueue( node_t *node ) { + ThreadLock(); + + node->next = NULL; + if ( lastnode ) { + lastnode->next = node; + } else { firstnode = node;} + lastnode = node; + nodelistsize++; + + ThreadUnlock(); + // + ThreadSemaphoreIncrease( 1 ); +} //end of the function AddNodeToQueue +//get the first node from the front of the node list +node_t *NextNodeFromList( void ) { + node_t *node; + + ThreadLock(); + numwaiting++; + if ( !firstnode ) { + if ( numwaiting >= GetNumThreads() ) { + ThreadSemaphoreIncrease( GetNumThreads() ); + } + } //end if + ThreadUnlock(); + + ThreadSemaphoreWait(); + + ThreadLock(); + + numwaiting--; + + node = firstnode; + if ( firstnode ) { + firstnode = firstnode->next; + nodelistsize--; + } //end if + if ( !firstnode ) { + lastnode = NULL; + } + + ThreadUnlock(); + + return node; +} //end of the function NextNodeFromList +//returns the size of the node list +int NodeListSize( void ) { + int size; + + ThreadLock(); + size = nodelistsize; + ThreadUnlock(); + + return size; +} //end of the function NodeListSize +// +void IncreaseNodeCounter( void ) { + ThreadLock(); + //if (verbose) printf("\r%6d", numrecurse++); + qprintf( "\r%6d", numrecurse++ ); + //qprintf("\r%6d %d, %5d ", numrecurse++, GetNumThreads(), nodelistsize); + ThreadUnlock(); +} //end of the function IncreaseNodeCounter +//thread function, gets nodes from the nodelist and processes them +void BuildTreeThread( int threadid ) { + node_t *newnode, *node; + side_t *bestside; + int i, totalmem; + bspbrush_t *brushes; + + for ( node = NextNodeFromList(); node; ) + { + //if the nodelist isn't empty try to add another thread + //if (NodeListSize() > 10) AddThread(BuildTreeThread); + //display the number of nodes processed so far + if ( numthreads == 1 ) { + IncreaseNodeCounter(); + } + + brushes = node->brushlist; + + if ( numthreads == 1 ) { + totalmem = WindingMemory() + c_nodememory + c_brushmemory; + if ( totalmem > c_peak_totalbspmemory ) { + c_peak_totalbspmemory = totalmem; + } //end if + c_nodes++; + } //endif + + if ( drawflag ) { + DrawBrushList( brushes, node ); + } //end if + + if ( cancelconversion ) { + bestside = NULL; + } //end if + else + { + // find the best plane to use as a splitter + bestside = SelectSplitSide( brushes, node ); + } //end else + //if there's no split side left + if ( !bestside ) { + //create a leaf out of the node + LeafNode( node, brushes ); + if ( node->contents & CONTENTS_SOLID ) { + c_solidleafnodes++; + } + + if ( create_aas ) { + //free up memory!!! + FreeBrushList( node->brushlist ); + node->brushlist = NULL; + } //end if + //free the node volume brush (it is not used anymore) + if ( node->volume ) { + FreeBrush( node->volume ); + node->volume = NULL; + } //end if + node = NextNodeFromList(); + continue; + } //end if + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; //always use front facing + + //allocate children + for ( i = 0; i < 2; i++ ) + { + newnode = AllocNode(); + newnode->parent = node; + node->children[i] = newnode; + } //end for + + //split the brush list in two for both children + SplitBrushList( brushes, node, &node->children[0]->brushlist, &node->children[1]->brushlist ); + + CheckBrushLists( node->children[0]->brushlist, node->children[1]->brushlist ); + //free the old brush list + FreeBrushList( brushes ); + node->brushlist = NULL; + + //split the volume brush of the node for the children + SplitBrush( node->volume, node->planenum, &node->children[0]->volume, + &node->children[1]->volume ); + + if ( !node->children[0]->volume || !node->children[1]->volume ) { + Error( "child without volume brush" ); + } //end if + + //free the volume brush + if ( node->volume ) { + FreeBrush( node->volume ); + node->volume = NULL; + } //end if + + //add both children to the node list + //AddNodeToList(node->children[0]); + AddNodeToList( node->children[1] ); + node = node->children[0]; + } //end while + RemoveThread( threadid ); +} //end of the function BuildTreeThread +//=========================================================================== +// build the bsp tree using a node list +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BuildTree( tree_t *tree ) { + int i; + + firstnode = NULL; + lastnode = NULL; + //use a node queue or node stack + if ( use_nodequeue ) { + AddNodeToList = AddNodeToQueue; + } else { AddNodeToList = AddNodeToStack;} + //setup thread locking + ThreadSetupLock(); + ThreadSetupSemaphore(); + numwaiting = 0; + // + Log_Print( "%6d threads max\n", numthreads ); + if ( use_nodequeue ) { + Log_Print( "breadth first bsp building\n" ); + } else { Log_Print( "depth first bsp building\n" );} + qprintf( "%6d splits", 0 ); + //add the first node to the list + AddNodeToList( tree->headnode ); + //start the threads + for ( i = 0; i < numthreads; i++ ) + AddThread( BuildTreeThread ); + //wait for all added threads to be finished + WaitForAllThreadsFinished(); + //shutdown the thread locking + ThreadShutdownLock(); + ThreadShutdownSemaphore(); +} //end of the function BuildTree +//=========================================================================== +// The incoming brush list will be freed before exiting +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tree_t *BrushBSP( bspbrush_t *brushlist, vec3_t mins, vec3_t maxs ) { + int i, c_faces, c_nonvisfaces, c_brushes; + bspbrush_t *b; + node_t *node; + tree_t *tree; + vec_t volume; +// vec3_t point; + + Log_Print( "-------- Brush BSP ---------\n" ); + + tree = Tree_Alloc(); + + c_faces = 0; + c_nonvisfaces = 0; + c_brushes = 0; + c_totalsides = 0; + for ( b = brushlist; b; b = b->next ) + { + c_brushes++; + + volume = BrushVolume( b ); + if ( volume < microvolume ) { + Log_Print( "WARNING: entity %i, brush %i: microbrush\n", + b->original->entitynum, b->original->brushnum ); + } //end if + + for ( i = 0 ; i < b->numsides ; i++ ) + { + if ( b->sides[i].flags & SFL_BEVEL ) { + continue; + } + if ( !b->sides[i].winding ) { + continue; + } + if ( b->sides[i].texinfo == TEXINFO_NODE ) { + continue; + } + if ( b->sides[i].flags & SFL_VISIBLE ) { + c_faces++; + } //end if + else + { + c_nonvisfaces++; + //if (create_aas) b->sides[i].texinfo = TEXINFO_NODE; + } //end if + } //end for + c_totalsides += b->numsides; + + AddPointToBounds( b->mins, tree->mins, tree->maxs ); + AddPointToBounds( b->maxs, tree->mins, tree->maxs ); + } //end for + + Log_Print( "%6i brushes\n", c_brushes ); + Log_Print( "%6i visible faces\n", c_faces ); + Log_Print( "%6i nonvisible faces\n", c_nonvisfaces ); + Log_Print( "%6i total sides\n", c_totalsides ); + + c_active_brushes = c_brushes; + c_nodememory = 0; + c_brushmemory = 0; + c_peak_brushmemory = 0; + + c_nodes = 0; + c_nonvis = 0; + node = AllocNode(); + + //volume of first node (head node) + node->volume = BrushFromBounds( mins, maxs ); + // + tree->headnode = node; + //just get some statistics and the mins/maxs of the node + numrecurse = 0; +// qprintf("%6d splits", numrecurse); + + tree->headnode->brushlist = brushlist; + BuildTree( tree ); + + //build the bsp tree with the start node from the brushlist +// node = BuildTree_r(node, brushlist); + + //if the conversion is cancelled + if ( cancelconversion ) { + return tree; + } + + qprintf( "\n" ); + Log_Write( "%6d splits\r\n", numrecurse ); +// Log_Print("%6i visible nodes\n", c_nodes/2 - c_nonvis); +// Log_Print("%6i nonvis nodes\n", c_nonvis); +// Log_Print("%6i leaves\n", (c_nodes+1)/2); +// Log_Print("%6i solid leaf nodes\n", c_solidleafnodes); +// Log_Print("%6i active brushes\n", c_active_brushes); + if ( numthreads == 1 ) { +// Log_Print("%6i KB of node memory\n", c_nodememory >> 10); +// Log_Print("%6i KB of brush memory\n", c_brushmemory >> 10); +// Log_Print("%6i KB of peak brush memory\n", c_peak_brushmemory >> 10); +// Log_Print("%6i KB of winding memory\n", WindingMemory() >> 10); +// Log_Print("%6i KB of peak winding memory\n", WindingPeakMemory() >> 10); + Log_Print( "%6i KB of peak total bsp memory\n", c_peak_totalbspmemory >> 10 ); + } //end if + + /* + point[0] = 1485; + point[1] = 956.125; + point[2] = 352.125; + node = PointInLeaf(tree->headnode, point); + if (node->planenum != PLANENUM_LEAF) + { + Log_Print("node not a leaf\n"); + } //end if + Log_Print("at %f %f %f:\n", point[0], point[1], point[2]); + PrintContents(node->contents); + Log_Print("node->expansionbboxes = %d\n", node->expansionbboxes); + //*/ + return tree; +} //end of the function BrushBSP + diff --git a/src/bspc/bspc.c b/src/bspc/bspc.c new file mode 100644 index 0000000..6d2bc6e --- /dev/null +++ b/src/bspc/bspc.c @@ -0,0 +1,1101 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: BSP tool +// Function: +// Programmer: id Software & Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +// Notes: Microsoft Visual C++ optimizations: +// "global optimization" or "full optimization" results +// in micro brushes?? +//=========================================================================== + +#if defined( WIN32 ) || defined( _WIN32 ) +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#include "qbsp.h" +#include "l_mem.h" +//#include "l_qfiles.h" +#include "..\botlib\aasfile.h" +#include "..\botlib\be_aas_cluster.h" +#include "..\botlib\be_aas_optimize.h" +#include "aas_create.h" +#include "aas_store.h" +#include "aas_file.h" +#include "aas_cfg.h" +#include "be_aas_bspc.h" + +#define BSPC_VERSION "2.1c" + +extern int use_nodequeue; //brushbsp.c +extern int calcgrapplereach; //be_aas_reach.c + +float subdivide_size = 240; +char source[1024]; +char name[1024]; +vec_t microvolume = 1.0; +char outbase[32]; +int entity_num; +aas_settings_t aassettings; + +qboolean noprune; //don't prune nodes (bspc.c) +qboolean glview; //create a gl view +qboolean nodetail; //don't use detail brushes (map.c) +qboolean fulldetail; //use but don't mark detail brushes (map.c) +qboolean onlyents; //only process the entities (bspc.c) +qboolean nomerge; //don't merge bsp node faces (faces.c) +qboolean nowater; //don't use the water brushes (map.c) +qboolean nocsg; //don't carve intersecting brushes (bspc.c) +qboolean noweld; //use unique face vertexes (faces.c) +qboolean noshare; //don't share bsp edges (faces.c) +qboolean nosubdiv; //don't subdivide bsp node faces (faces.c) +qboolean notjunc; //don't create tjunctions (edge melting) (faces.c) +qboolean optimize; //enable optimisation +qboolean leaktest; //perform a leak test +qboolean verboseentities; +qboolean freetree; //free the bsp tree when not needed anymore +qboolean create_aas; //create an .AAS file +qboolean nobrushmerge; //don't merge brushes +qboolean lessbrushes; //create less brushes instead of correct texture placement +qboolean cancelconversion; //true if the conversion is being cancelled +qboolean noliquids; //no liquids when writing map file +qboolean forcesidesvisible; //force all brush sides to be visible when loaded from bsp +// qboolean capsule_collision = true; //use capsule collision +qboolean capsule_collision = false; // capsule collision// Ridah, allow to specify an extension for multiple AAS files per map + +char aas_extension[64]; +// done. + +float VectorDistance( vec3_t v1, vec3_t v2 ) { + vec3_t dir; + + VectorSubtract( v2, v1, dir ); + return VectorLength( dir ); +} + +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ProcessWorldModel (void) +{ + entity_t *e; + tree_t *tree; + qboolean leaked; + int brush_start, brush_end; + + e = &entities[entity_num]; + + brush_start = e->firstbrush; + brush_end = brush_start + e->numbrushes; + leaked = false; + + //process the whole world in one time + tree = ProcessWorldBrushes(brush_start, brush_end); + //create the bsp tree portals + MakeTreePortals(tree); + //mark all leafs that can be reached by entities + if (FloodEntities(tree)) + { + FillOutside(tree->headnode); + } //end if + else + { + Log_Print("**** leaked ****\n"); + leaked = true; + LeakFile(tree); + if (leaktest) + { + Log_Print("--- MAP LEAKED ---\n"); + exit(0); + } //end if + } //end else + + MarkVisibleSides (tree, brush_start, brush_end); + + FloodAreas (tree); + +#ifndef ME + if (glview) WriteGLView(tree, source); +#endif + MakeFaces(tree->headnode); + FixTjuncs(tree->headnode); + + //NOTE: Never prune the nodes because the portals + // are screwed when prunning is done and as + // a result portal writing will crash + //if (!noprune) PruneNodes(tree->headnode); + + WriteBSP(tree->headnode); + + if (!leaked) WritePortalFile(tree); + + Tree_Free(tree); +} //end of the function ProcessWorldModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ProcessSubModel (void) +{ + entity_t *e; + int start, end; + tree_t *tree; + bspbrush_t *list; + vec3_t mins, maxs; + + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + + mins[0] = mins[1] = mins[2] = -4096; + maxs[0] = maxs[1] = maxs[2] = 4096; + list = MakeBspBrushList(start, end, mins, maxs); + if (!nocsg) list = ChopBrushes (list); + tree = BrushBSP (list, mins, maxs); + MakeTreePortals (tree); + MarkVisibleSides (tree, start, end); + MakeFaces (tree->headnode); + FixTjuncs (tree->headnode); + WriteBSP (tree->headnode); + Tree_Free (tree); +} //end of the function ProcessSubModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ProcessModels (void) +{ + BeginBSPFile(); + + for (entity_num = 0; entity_num < num_entities; entity_num++) + { + if (!entities[entity_num].numbrushes) + continue; + + Log_Print("############### model %i ###############\n", nummodels); + BeginModel(); + if (entity_num == 0) ProcessWorldModel(); + else ProcessSubModel(); + EndModel(); + + if (!verboseentities) + verbose = false; // don't bother printing submodels + } //end for + EndBSPFile(); +} //end of the function ProcessModels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Win_Map2Bsp(char *bspfilename) +{ + double start, end; + char path[1024]; + + start = I_FloatTime(); + + ThreadSetDefault(); + //yeah sure Carmack + //numthreads = 1; // multiple threads aren't helping... + + strcpy(source, ExpandArg(bspfilename)); + StripExtension(source); + + //delete portal and line files + sprintf(path, "%s.prt", source); + remove(path); + sprintf(path, "%s.lin", source); + remove(path); + + strcpy(name, ExpandArg(bspfilename)); + DefaultExtension(name, ".map"); // might be .reg + + Q2_AllocMaxBSP(); + // + SetModelNumbers(); + SetLightStyles(); + ProcessModels(); + //write the BSP + Q2_WriteBSPFile(bspfilename); + + Q2_FreeMaxBSP(); + + end = I_FloatTime(); + Log_Print("%5.0f seconds elapsed\n", end-start); +} //end of the function Win_Map2Bsp +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Map2Bsp(char *mapfilename, char *outputfilename) +{ + double start, end; + char path[1024]; + + start = I_FloatTime (); + + ThreadSetDefault (); + //yeah sure Carmack + //numthreads = 1; //multiple threads aren't helping... + //SetQdirFromPath(bspfilename); + + strcpy(source, ExpandArg(mapfilename)); + StripExtension(source); + + // delete portal and line files + sprintf(path, "%s.prt", source); + remove(path); + sprintf(path, "%s.lin", source); + remove(path); + + strcpy(name, ExpandArg(mapfilename)); + DefaultExtension(name, ".map"); // might be .reg + + // + // if onlyents, just grab the entites and resave + // + if (onlyents) + { + char out[1024]; + + Q2_AllocMaxBSP(); + sprintf (out, "%s.bsp", source); + Q2_LoadBSPFile(out, 0, 0); + num_entities = 0; + + Q2_LoadMapFile(name); + SetModelNumbers(); + SetLightStyles(); + + Q2_UnparseEntities(); + + Q2_WriteBSPFile(out); + // + Q2_FreeMaxBSP(); + } //end if + else + { + // + // start from scratch + // + Q2_AllocMaxBSP(); + //load the map + Q2_LoadMapFile(name); + //create the .bsp file + SetModelNumbers(); + SetLightStyles(); + ProcessModels(); + //write the BSP + Q2_WriteBSPFile(outputfilename); + // + Q2_FreeMaxBSP(); + } //end else + + end = I_FloatTime(); + Log_Print("%5.0f seconds elapsed\n", end-start); +} //end of the function Map2Bsp +*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AASOuputFile( quakefile_t *qf, char *outputpath, char *filename ) { + char ext[MAX_PATH]; + + // + if ( strlen( outputpath ) ) { + strcpy( filename, outputpath ); + //append the bsp file base + AppendPathSeperator( filename, MAX_PATH ); + ExtractFileBase( qf->origname, &filename[strlen( filename )] ); + + // Ridah, add extension + strcat( filename, aas_extension ); + // done. + + //append .aas + strcat( filename, ".aas" ); + return; + } //end if + // + ExtractFileExtension( qf->filename, ext ); + if ( !stricmp( ext, "pk3" ) || !stricmp( ext, "pak" ) || !stricmp( ext, "sin" ) ) { + strcpy( filename, qf->filename ); + while ( strlen( filename ) && + filename[strlen( filename ) - 1] != '\\' && + filename[strlen( filename ) - 1] != '/' ) + { + filename[strlen( filename ) - 1] = '\0'; + } //end while + strcat( filename, "maps" ); + if ( access( filename, 0x04 ) ) { + CreatePath( filename ); + } + //append the bsp file base + AppendPathSeperator( filename, MAX_PATH ); + ExtractFileBase( qf->origname, &filename[strlen( filename )] ); + + // Ridah, add extension + strcat( filename, aas_extension ); + // done. + + //append .aas + strcat( filename, ".aas" ); + } //end if + else + { + strcpy( filename, qf->filename ); + while ( strlen( filename ) && + filename[strlen( filename ) - 1] != '.' ) + { + filename[strlen( filename ) - 1] = '\0'; + } //end while + + // Ridah, add extension + strcat( filename, aas_extension ); + // done. + + strcat( filename, "aas" ); + } //end else +} //end of the function AASOutputFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CreateAASFilesForAllBSPFiles( char *quakepath ) { +#if defined( WIN32 ) | defined( _WIN32 ) + WIN32_FIND_DATA filedata; + HWND handle; + struct _stat statbuf; +#else + glob_t globbuf; + struct stat statbuf; + int j; +#endif + int done; + char filter[_MAX_PATH], bspfilter[_MAX_PATH], aasfilter[_MAX_PATH]; + char aasfile[_MAX_PATH], buf[_MAX_PATH], foldername[_MAX_PATH]; + quakefile_t *qf, *qf2, *files, *bspfiles, *aasfiles; + + strcpy( filter, quakepath ); + AppendPathSeperator( filter, sizeof( filter ) ); + strcat( filter, "*" ); + +#if defined( WIN32 ) | defined( _WIN32 ) + handle = FindFirstFile( filter, &filedata ); + done = ( handle == INVALID_HANDLE_VALUE ); + while ( !done ) + { + _splitpath( filter, foldername, NULL, NULL, NULL ); + _splitpath( filter, NULL, &foldername[strlen( foldername )], NULL, NULL ); + AppendPathSeperator( foldername, _MAX_PATH ); + strcat( foldername, filedata.cFileName ); + _stat( foldername, &statbuf ); +#else + glob( filter, 0, NULL, &globbuf ); + for ( j = 0; j < globbuf.gl_pathc; j++ ) + { + strcpy( foldername, globbuf.gl_pathv[j] ); + stat( foldername, &statbuf ); +#endif + //if it is a folder + if ( statbuf.st_mode & S_IFDIR ) { + // + AppendPathSeperator( foldername, sizeof( foldername ) ); + //get all the bsp files + strcpy( bspfilter, foldername ); + strcat( bspfilter, "maps/*.bsp" ); + files = FindQuakeFiles( bspfilter ); + strcpy( bspfilter, foldername ); + strcat( bspfilter, "*.pk3/maps/*.bsp" ); + bspfiles = FindQuakeFiles( bspfilter ); + for ( qf = bspfiles; qf; qf = qf->next ) if ( !qf->next ) { + break; + } + if ( qf ) { + qf->next = files; + } else { bspfiles = files;} + //get all the aas files + strcpy( aasfilter, foldername ); + strcat( aasfilter, "maps/*.aas" ); + files = FindQuakeFiles( aasfilter ); + strcpy( aasfilter, foldername ); + strcat( aasfilter, "*.pk3/maps/*.aas" ); + aasfiles = FindQuakeFiles( aasfilter ); + for ( qf = aasfiles; qf; qf = qf->next ) if ( !qf->next ) { + break; + } + if ( qf ) { + qf->next = files; + } else { aasfiles = files;} + // + for ( qf = bspfiles; qf; qf = qf->next ) + { + sprintf( aasfile, "%s/%s", qf->pakfile, qf->origname ); + Log_Print( "found %s\n", aasfile ); + strcpy( &aasfile[strlen( aasfile ) - strlen( ".bsp" )], ".aas" ); + for ( qf2 = aasfiles; qf2; qf2 = qf2->next ) + { + sprintf( buf, "%s/%s", qf2->pakfile, qf2->origname ); + if ( !stricmp( aasfile, buf ) ) { + Log_Print( "found %s\n", buf ); + break; + } //end if + } //end for + } //end for + } //end if +#if defined( WIN32 ) | defined( _WIN32 ) + //find the next file + done = !FindNextFile( handle, &filedata ); + } //end while +#else + } //end for + globfree( &globbuf ); +#endif +} //end of the function CreateAASFilesForAllBSPFiles +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *GetArgumentFiles( int argc, char *argv[], int *i, char *ext ) { + quakefile_t *qfiles, *lastqf, *qf; + int j; + char buf[1024]; + + qfiles = NULL; + lastqf = NULL; + for (; ( *i ) + 1 < argc && argv[( *i ) + 1][0] != '-'; ( *i )++ ) + { + strcpy( buf, argv[( *i ) + 1] ); + for ( j = strlen( buf ) - 1; j >= strlen( buf ) - 4; j-- ) + if ( buf[j] == '.' ) { + break; + } + if ( j >= strlen( buf ) - 4 ) { + strcpy( &buf[j + 1], ext ); + } + qf = FindQuakeFiles( buf ); + if ( !qf ) { + continue; + } + if ( lastqf ) { + lastqf->next = qf; + } else { qfiles = qf;} + lastqf = qf; + while ( lastqf->next ) lastqf = lastqf->next; + } //end for + return qfiles; +} //end of the function GetArgumentFiles +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +#define COMP_BSP2MAP 1 +#define COMP_BSP2AAS 2 +#define COMP_REACH 3 +#define COMP_CLUSTER 4 +#define COMP_AASOPTIMIZE 5 +#define COMP_AASINFO 6 +#define COMP_TETRA 7 + + +int main( int argc, char **argv ) { + int i, comp = 0; + char outputpath[MAX_PATH] = ""; + char filename[MAX_PATH] = "unknown"; + quakefile_t *qfiles, *qf; + + // Ridah, allow to specify an extension for multiple AAS files per map + int has_ext = 0; + // done. + + // Ridah, set the world pointer up for reachabilities + aasworld = aasworlds; + AAS_SetWorldPointer( &( *aasworld ) ); + // done. + + myargc = argc; + myargv = argv; + + Log_Open( "bspc.log" ); //open a log file + Log_Print( "BSPC version " BSPC_VERSION ", %s %s by Mr Elusive\n", __DATE__, __TIME__ ); + + DefaultCfg(); + for ( i = 1; i < argc; i++ ) + { + if ( !stricmp( argv[i],"-threads" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + numthreads = atoi( argv[++i] ); + Log_Print( "threads = %d\n", numthreads ); + } //end if + else if ( !stricmp( argv[i], "-noverbose" ) ) { + Log_Print( "verbose = false\n" ); + verbose = false; + } //end else if + else if ( !stricmp( argv[i], "-nocsg" ) ) { + Log_Print( "nocsg = true\n" ); + nocsg = true; + } //end else if + else if ( !stricmp( argv[i], "-optimize" ) ) { + Log_Print( "optimize = true\n" ); + optimize = true; + } //end else if + /* + else if (!stricmp(argv[i],"-glview")) + { + glview = true; + } //end else if + else if (!stricmp(argv[i], "-draw")) + { + Log_Print("drawflag = true\n"); + drawflag = true; + } //end else if + else if (!stricmp(argv[i], "-noweld")) + { + Log_Print("noweld = true\n"); + noweld = true; + } //end else if + else if (!stricmp(argv[i], "-noshare")) + { + Log_Print("noshare = true\n"); + noshare = true; + } //end else if + else if (!stricmp(argv[i], "-notjunc")) + { + Log_Print("notjunc = true\n"); + notjunc = true; + } //end else if + else if (!stricmp(argv[i], "-nowater")) + { + Log_Print("nowater = true\n"); + nowater = true; + } //end else if + else if (!stricmp(argv[i], "-noprune")) + { + Log_Print("noprune = true\n"); + noprune = true; + } //end else if + else if (!stricmp(argv[i], "-nomerge")) + { + Log_Print("nomerge = true\n"); + nomerge = true; + } //end else if + else if (!stricmp(argv[i], "-nosubdiv")) + { + Log_Print("nosubdiv = true\n"); + nosubdiv = true; + } //end else if + else if (!stricmp(argv[i], "-nodetail")) + { + Log_Print("nodetail = true\n"); + nodetail = true; + } //end else if + else if (!stricmp(argv[i], "-fulldetail")) + { + Log_Print("fulldetail = true\n"); + fulldetail = true; + } //end else if + else if (!stricmp(argv[i], "-onlyents")) + { + Log_Print("onlyents = true\n"); + onlyents = true; + } //end else if + else if (!stricmp(argv[i], "-micro")) + { + if (i + 1 >= argc) {i = 0; break;} + microvolume = atof(argv[++i]); + Log_Print("microvolume = %f\n", microvolume); + } //end else if + else if (!stricmp(argv[i], "-leaktest")) + { + Log_Print("leaktest = true\n"); + leaktest = true; + } //end else if + else if (!stricmp(argv[i], "-verboseentities")) + { + Log_Print("verboseentities = true\n"); + verboseentities = true; + } //end else if + else if (!stricmp(argv[i], "-chop")) + { + if (i + 1 >= argc) {i = 0; break;} + subdivide_size = atof(argv[++i]); + Log_Print("subdivide_size = %f\n", subdivide_size); + } //end else if + else if (!stricmp (argv[i], "-tmpout")) + { + strcpy (outbase, "/tmp"); + Log_Print("temp output\n"); + } //end else if + */ +#ifdef ME + else if ( !stricmp( argv[i], "-freetree" ) ) { + freetree = true; + Log_Print( "freetree = true\n" ); + } //end else if + else if ( !stricmp( argv[i], "-grapplereach" ) ) { + calcgrapplereach = true; + Log_Print( "grapplereach = true\n" ); + } //end else if + else if ( !stricmp( argv[i], "-nobrushmerge" ) ) { + nobrushmerge = true; + Log_Print( "nobrushmerge = true\n" ); + } //end else if + else if ( !stricmp( argv[i], "-noliquids" ) ) { + noliquids = true; + Log_Print( "noliquids = true\n" ); + } //end else if + else if ( !stricmp( argv[i], "-forcesidesvisible" ) ) { + forcesidesvisible = true; + Log_Print( "forcesidesvisible = true\n" ); + } //end else if + else if ( !stricmp( argv[i], "-output" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + if ( access( argv[i + 1], 0x04 ) ) { + Warning( "the folder %s does not exist", argv[i + 1] ); + } + strcpy( outputpath, argv[++i] ); + } //end else if + else if ( !stricmp( argv[i], "-breadthfirst" ) ) { + use_nodequeue = true; + Log_Print( "breadthfirst = true\n" ); + } //end else if + else if ( !stricmp( argv[i], "-cfg" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + if ( !LoadCfgFile( argv[++i] ) ) { + exit( 0 ); + } + } //end else if + else if ( !stricmp( argv[i], "-bsp2map" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + comp = COMP_BSP2MAP; + qfiles = GetArgumentFiles( argc, argv, &i, "bsp" ); + } //end else if + else if ( !stricmp( argv[i], "-bsp2aas" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + comp = COMP_BSP2AAS; + qfiles = GetArgumentFiles( argc, argv, &i, "bsp" ); + } //end else if + else if ( !stricmp( argv[i], "-aasall" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + CreateAASFilesForAllBSPFiles( argv[++i] ); + } //end else if + else if ( !stricmp( argv[i], "-reach" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + comp = COMP_REACH; + qfiles = GetArgumentFiles( argc, argv, &i, "bsp" ); + } //end else if + else if ( !stricmp( argv[i], "-cluster" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + comp = COMP_CLUSTER; + qfiles = GetArgumentFiles( argc, argv, &i, "bsp" ); + } //end else if + else if ( !stricmp( argv[i], "-aasinfo" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + comp = COMP_AASINFO; + qfiles = GetArgumentFiles( argc, argv, &i, "aas" ); + } //end else if + else if ( !stricmp( argv[i], "-aasopt" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + comp = COMP_AASOPTIMIZE; + qfiles = GetArgumentFiles( argc, argv, &i, "aas" ); + } //end else if + else if ( !stricmp( argv[i], "-tetra" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + comp = COMP_TETRA; + qfiles = GetArgumentFiles( argc, argv, &i, "aas" ); + } //end else if + // Ridah, allow to specify an extension for multiple AAS files per map + else if ( !stricmp( argv[i], "-ext" ) ) { + if ( i + 1 >= argc ) { + i = 0; break; + } + strcpy( aas_extension, argv[++i] ); + has_ext = 1; + + } //end else if + // done. + +#endif //ME + else + { + Log_Print( "unknown parameter %s\n", argv[i] ); + break; + } //end else + } //end for + + //if there are parameters and there's no mismatch in one of the parameters + if ( argc > 1 && i == argc ) { + switch ( comp ) + { + case COMP_BSP2MAP: + { + if ( !qfiles ) { + Log_Print( "no files found\n" ); + } + for ( qf = qfiles; qf; qf = qf->next ) + { + //copy the output path + strcpy( filename, outputpath ); + //append the bsp file base + AppendPathSeperator( filename, MAX_PATH ); + ExtractFileBase( qf->origname, &filename[strlen( filename )] ); + //append .map + strcat( filename, ".map" ); + // + Log_Print( "bsp2map: %s to %s\n", qf->origname, filename ); + if ( qf->type != QFILETYPE_BSP ) { + Warning( "%s is probably not a BSP file\n", qf->origname ); + } + // + LoadMapFromBSP( qf ); + //write the map file + WriteMapFile( filename ); + } //end for + break; + } //end case + case COMP_BSP2AAS: + { + if ( !qfiles ) { + Log_Print( "no files found\n" ); + } + for ( qf = qfiles; qf; qf = qf->next ) + { + AASOuputFile( qf, outputpath, filename ); + // + Log_Print( "bsp2aas: %s to %s\n", qf->origname, filename ); + if ( qf->type != QFILETYPE_BSP ) { + Warning( "%s is probably not a BSP file\n", qf->origname ); + } + //set before map loading + create_aas = 1; + LoadMapFromBSP( qf ); + //create the AAS file + AAS_Create( filename ); + //if it's a Quake3 map calculate the reachabilities and clusters + if ( loadedmaptype == MAPTYPE_QUAKE3 ) { + AAS_CalcReachAndClusters( qf ); + } + // + if ( optimize ) { + AAS_Optimize(); + } + // + //write out the stored AAS file + if ( !AAS_WriteAASFile( filename ) ) { + Error( "error writing %s\n", filename ); + } //end if + //deallocate memory + AAS_FreeMaxAAS(); + } //end for + break; + } //end case + case COMP_REACH: + { + if ( !qfiles ) { + Log_Print( "no files found\n" ); + } + for ( qf = qfiles; qf; qf = qf->next ) + { + AASOuputFile( qf, outputpath, filename ); + // + Log_Print( "reach: %s to %s\n", qf->origname, filename ); + if ( qf->type != QFILETYPE_BSP ) { + Warning( "%s is probably not a BSP file\n", qf->origname ); + } + //if the AAS file exists in the output directory + if ( !access( filename, 0x04 ) ) { + if ( !AAS_LoadAASFile( filename, 0, 0 ) ) { + Error( "error loading aas file %s\n", filename ); + } //end if + //assume it's a Quake3 BSP file + loadedmaptype = MAPTYPE_QUAKE3; + } //end if + else + { + Warning( "AAS file %s not found in output folder\n", filename ); + Log_Print( "creating %s...\n", filename ); + //set before map loading + create_aas = 1; + LoadMapFromBSP( qf ); + //create the AAS file + AAS_Create( filename ); + } //end else + //if it's a Quake3 map calculate the reachabilities and clusters + if ( loadedmaptype == MAPTYPE_QUAKE3 ) { + AAS_CalcReachAndClusters( qf ); + } //end if + // + if ( optimize ) { + AAS_Optimize(); + } + //write out the stored AAS file + if ( !AAS_WriteAASFile( filename ) ) { + Error( "error writing %s\n", filename ); + } //end if + //deallocate memory + AAS_FreeMaxAAS(); + } //end for + break; + } //end case + case COMP_CLUSTER: + { + if ( !qfiles ) { + Log_Print( "no files found\n" ); + } + for ( qf = qfiles; qf; qf = qf->next ) + { + AASOuputFile( qf, outputpath, filename ); + // + Log_Print( "cluster: %s to %s\n", qf->origname, filename ); + if ( qf->type != QFILETYPE_BSP ) { + Warning( "%s is probably not a BSP file\n", qf->origname ); + } + //if the AAS file exists in the output directory + if ( !access( filename, 0x04 ) ) { + if ( !AAS_LoadAASFile( filename, 0, 0 ) ) { + Error( "error loading aas file %s\n", filename ); + } //end if + //assume it's a Quake3 BSP file + loadedmaptype = MAPTYPE_QUAKE3; + //if it's a Quake3 map calculate the clusters + if ( loadedmaptype == MAPTYPE_QUAKE3 ) { + ( *aasworld ).numclusters = 0; + AAS_InitBotImport(); + AAS_InitClustering(); + } //end if + } //end if + else + { + Warning( "AAS file %s not found in output folder\n", filename ); + Log_Print( "creating %s...\n", filename ); + //set before map loading + create_aas = 1; + LoadMapFromBSP( qf ); + //create the AAS file + AAS_Create( filename ); + //if it's a Quake3 map calculate the reachabilities and clusters + if ( loadedmaptype == MAPTYPE_QUAKE3 ) { + AAS_CalcReachAndClusters( qf ); + } + } //end else + // + if ( optimize ) { + AAS_Optimize(); + } + //write out the stored AAS file + if ( !AAS_WriteAASFile( filename ) ) { + Error( "error writing %s\n", filename ); + } //end if + //deallocate memory + AAS_FreeMaxAAS(); + } //end for + break; + } //end case + case COMP_AASOPTIMIZE: + { + if ( !qfiles ) { + Log_Print( "no files found\n" ); + } + for ( qf = qfiles; qf; qf = qf->next ) + { + AASOuputFile( qf, outputpath, filename ); + // + Log_Print( "optimizing: %s to %s\n", qf->origname, filename ); + if ( qf->type != QFILETYPE_AAS ) { + Warning( "%s is probably not a AAS file\n", qf->origname ); + } + // + AAS_InitBotImport(); + // + if ( !AAS_LoadAASFile( qf->filename, qf->offset, qf->length ) ) { + Error( "error loading aas file %s\n", qf->filename ); + } //end if + AAS_Optimize(); + //write out the stored AAS file + if ( !AAS_WriteAASFile( filename ) ) { + Error( "error writing %s\n", filename ); + } //end if + //deallocate memory + AAS_FreeMaxAAS(); + } //end for + break; + } //end case + case COMP_AASINFO: + { + if ( !qfiles ) { + Log_Print( "no files found\n" ); + } + for ( qf = qfiles; qf; qf = qf->next ) + { + AASOuputFile( qf, outputpath, filename ); + // + Log_Print( "aas info for: %s\n", filename ); + if ( qf->type != QFILETYPE_AAS ) { + Warning( "%s is probably not a AAS file\n", qf->origname ); + } + // + AAS_InitBotImport(); + // + if ( !AAS_LoadAASFile( qf->filename, qf->offset, qf->length ) ) { + Error( "error loading aas file %s\n", qf->filename ); + } //end if + AAS_ShowTotals(); + } //end for + } //end case + case COMP_TETRA: + { + if ( !qfiles ) { + Log_Print( "no files found\n" ); + } + for ( qf = qfiles; qf; qf = qf->next ) + { + //TH_AASToTetrahedrons(qf->filename); + } //end for + break; + } //end case + default: + { + Log_Print( "don't know what to do\n" ); + break; + } //end default + } //end switch + } //end if + else + { + Log_Print( "Usage: bspc [- [- ...]]\n" +#if defined( WIN32 ) || defined( _WIN32 ) + "Example 1: bspc -bsp2aas d:\\quake3\\baseq3\\maps\\mymap?.bsp\n" + "Example 2: bspc -bsp2aas d:\\quake3\\baseq3\\pak0.pk3\\maps/q3dm*.bsp\n" +#else + "Example 1: bspc -bsp2aas /quake3/baseq3/maps/mymap?.bsp\n" + "Example 2: bspc -bsp2aas /quake3/baseq3/pak0.pk3/maps/q3dm*.bsp\n" +#endif + "\n" + "Switches:\n" + //" bsp2map <[pakfilter/]filter.bsp> = convert BSP to MAP\n" + " bsp2aas <[pakfilter/]filter.bsp> = convert BSP to AAS\n" + //" aasall = create AAS files for all BSPs\n" + " reach = compute reachability & clusters\n" + " cluster = compute clusters\n" + " aasopt = optimize aas file\n" + //" tetra = tetrahedral decomposition\n" + " output = set output path\n" + " threads = set number of threads to X\n" + " cfg = use this cfg file\n" + " optimize = enable optimization\n" + " noverbose = disable verbose output\n" + " breadthfirst = breadth first bsp building\n" + " nobrushmerge = don't merge brushes\n" + " noliquids = don't write liquids to map\n" + " freetree = free the bsp tree\n" + " nocsg = disables brush chopping\n" + " forcesidesvisible = force all sides to be visible\n" + " grapplereach = calculate grapple reachabilities\n" + +/* " glview = output a GL view\n" + " draw = enables drawing\n" + " noweld = disables weld\n" + " noshare = disables sharing\n" + " notjunc = disables juncs\n" + " nowater = disables water brushes\n" + " noprune = disables node prunes\n" + " nomerge = disables face merging\n" + " nosubdiv = disables subdeviding\n" + " nodetail = disables detail brushes\n" + " fulldetail = enables full detail\n" + " onlyents = only compile entities with bsp\n" + " micro \n" + " = sets the micro volume to the given float\n" + " leaktest = perform a leak test\n" + " verboseentities\n" + " = enable entity verbose mode\n" + " chop \n" + " = sets the subdivide size to the given float\n"*/ + "\n" ); + } //end else + Log_Close(); //close the log file + return 0; +} //end of the function main + + diff --git a/src/bspc/csg.c b/src/bspc/csg.c new file mode 100644 index 0000000..63fe36a --- /dev/null +++ b/src/bspc/csg.c @@ -0,0 +1,1053 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: Brush CSG +// Function: +// Programmer(s): id Software & Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +// Notes: Microsoft Visual C++ optimizations: +// "global optimization" or "full optimization" results +// in micro brushes?? +//=========================================================================== + +#include "qbsp.h" + +/* + +tag all brushes with original contents +brushes may contain multiple contents +there will be no brush overlap after csg phase + +*/ + +int minplanenums[3]; +int maxplanenums[3]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CheckBSPBrush( bspbrush_t *brush ) { + int i, j; + plane_t *plane1, *plane2; + + //check if the brush is convex... flipped planes make a brush non-convex + for ( i = 0; i < brush->numsides; i++ ) + { + for ( j = 0; j < brush->numsides; j++ ) + { + if ( i == j ) { + continue; + } + plane1 = &mapplanes[brush->sides[i].planenum]; + plane2 = &mapplanes[brush->sides[j].planenum]; + // + if ( WindingsNonConvex( brush->sides[i].winding, + brush->sides[j].winding, + plane1->normal, plane2->normal, + plane1->dist, plane2->dist ) ) { + Log_Print( "non convex brush" ); + break; + } //end if + } //end for + } //end for + BoundBrush( brush ); + //check for out of bound brushes + for ( i = 0; i < 3; i++ ) + { + if ( brush->mins[i] < -MAX_MAP_BOUNDS || brush->maxs[i] > MAX_MAP_BOUNDS ) { + Log_Print( "brush: bounds out of range\n" ); + Log_Print( "ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, brush->mins[i], i, brush->maxs[i] ); + break; + } //end if + if ( brush->mins[i] > MAX_MAP_BOUNDS || brush->maxs[i] < -MAX_MAP_BOUNDS ) { + Log_Print( "brush: no visible sides on brush\n" ); + Log_Print( "ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, brush->mins[i], i, brush->maxs[i] ); + break; + } //end if + } //end for +} //end of the function CheckBSPBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BSPBrushWindings( bspbrush_t *brush ) { + int i, j; + winding_t *w; + plane_t *plane; + + for ( i = 0; i < brush->numsides; i++ ) + { + plane = &mapplanes[brush->sides[i].planenum]; + w = BaseWindingForPlane( plane->normal, plane->dist ); + for ( j = 0; j < brush->numsides && w; j++ ) + { + if ( i == j ) { + continue; + } + plane = &mapplanes[brush->sides[j].planenum ^ 1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON); + } //end for + brush->sides[i].winding = w; + } //end for +} //end of the function BSPBrushWindings +//=========================================================================== +// NOTE: can't keep brush->original intact +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *TryMergeBrushes( bspbrush_t *brush1, bspbrush_t *brush2 ) { + int i, j, k, n, shared; + side_t *side1, *side2, *cs; + plane_t *plane1, *plane2; + bspbrush_t *newbrush; + + //check for bounding box overlapp + for ( i = 0; i < 3; i++ ) + { + if ( brush1->mins[i] > brush2->maxs[i] + 2 + || brush1->maxs[i] < brush2->mins[i] - 2 ) { + return NULL; + } //end if + } //end for + // + shared = 0; + //check if the brush is convex... flipped planes make a brush non-convex + for ( i = 0; i < brush1->numsides; i++ ) + { + side1 = &brush1->sides[i]; + //don't check the "shared" sides + for ( k = 0; k < brush2->numsides; k++ ) + { + side2 = &brush2->sides[k]; + if ( side1->planenum == ( side2->planenum ^ 1 ) ) { + shared++; + //there may only be ONE shared side + if ( shared > 1 ) { + return NULL; + } + break; + } //end if + } //end for + if ( k < brush2->numsides ) { + continue; + } + // + for ( j = 0; j < brush2->numsides; j++ ) + { + side2 = &brush2->sides[j]; + //don't check the "shared" sides + for ( n = 0; n < brush1->numsides; n++ ) + { + side1 = &brush1->sides[n]; + if ( side1->planenum == ( side2->planenum ^ 1 ) ) { + break; + } + } //end for + if ( n < brush1->numsides ) { + continue; + } + // + side1 = &brush1->sides[i]; + //if the side is in the same plane + //* + if ( side1->planenum == side2->planenum ) { + if ( side1->texinfo != TEXINFO_NODE && + side2->texinfo != TEXINFO_NODE && + side1->texinfo != side2->texinfo ) { + return NULL; + } + continue; + } //end if + // + plane1 = &mapplanes[side1->planenum]; + plane2 = &mapplanes[side2->planenum]; + // + if ( WindingsNonConvex( side1->winding, side2->winding, + plane1->normal, plane2->normal, + plane1->dist, plane2->dist ) ) { + return NULL; + } //end if + } //end for + } //end for + newbrush = AllocBrush( brush1->numsides + brush2->numsides ); + newbrush->original = brush1->original; + newbrush->numsides = 0; + //newbrush->side = brush1->side; //brush contents + //fix texinfos for sides lying in the same plane + for ( i = 0; i < brush1->numsides; i++ ) + { + side1 = &brush1->sides[i]; + // + for ( n = 0; n < brush2->numsides; n++ ) + { + side2 = &brush2->sides[n]; + //if both sides are in the same plane get the texinfo right + if ( side1->planenum == side2->planenum ) { + if ( side1->texinfo == TEXINFO_NODE ) { + side1->texinfo = side2->texinfo; + } + if ( side2->texinfo == TEXINFO_NODE ) { + side2->texinfo = side1->texinfo; + } + } //end if + } //end for + } //end for + // + for ( i = 0; i < brush1->numsides; i++ ) + { + side1 = &brush1->sides[i]; + //don't add the "shared" sides + for ( n = 0; n < brush2->numsides; n++ ) + { + side2 = &brush2->sides[n]; + if ( side1->planenum == ( side2->planenum ^ 1 ) ) { + break; + } + } //end for + if ( n < brush2->numsides ) { + continue; + } + // + for ( n = 0; n < newbrush->numsides; n++ ) + { + cs = &newbrush->sides[n]; + if ( cs->planenum == side1->planenum ) { + Log_Print( "brush duplicate plane\n" ); + break; + } //end if + } //end if + if ( n < newbrush->numsides ) { + continue; + } + //add this side + cs = &newbrush->sides[newbrush->numsides]; + newbrush->numsides++; + *cs = *side1; + } //end for + for ( j = 0; j < brush2->numsides; j++ ) + { + side2 = &brush2->sides[j]; + for ( n = 0; n < brush1->numsides; n++ ) + { + side1 = &brush1->sides[n]; + //if the side is in the same plane + if ( side2->planenum == side1->planenum ) { + break; + } + //don't add the "shared" sides + if ( side2->planenum == ( side1->planenum ^ 1 ) ) { + break; + } + } //end for + if ( n < brush1->numsides ) { + continue; + } + // + for ( n = 0; n < newbrush->numsides; n++ ) + { + cs = &newbrush->sides[n]; + if ( cs->planenum == side2->planenum ) { + Log_Print( "brush duplicate plane\n" ); + break; + } //end if + } //end if + if ( n < newbrush->numsides ) { + continue; + } + //add this side + cs = &newbrush->sides[newbrush->numsides]; + newbrush->numsides++; + *cs = *side2; + } //end for + BSPBrushWindings( newbrush ); + BoundBrush( newbrush ); + CheckBSPBrush( newbrush ); + return newbrush; +} //end of the function TryMergeBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *MergeBrushes( bspbrush_t *brushlist ) { + int nummerges, merged; + bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; + bspbrush_t *lastb2; + + if ( !brushlist ) { + return NULL; + } + + qprintf( "%5d brushes merged", nummerges = 0 ); + do + { + for ( tail = brushlist; tail; tail = tail->next ) + { + if ( !tail->next ) { + break; + } + } //end for + merged = 0; + newbrushlist = NULL; + for ( b1 = brushlist; b1; b1 = brushlist ) + { + lastb2 = b1; + for ( b2 = b1->next; b2; b2 = b2->next ) + { + //if the brushes don't have the same contents + if ( b1->original->contents != b2->original->contents || + b1->original->expansionbbox != b2->original->expansionbbox ) { + newbrush = NULL; + } else { newbrush = TryMergeBrushes( b1, b2 );} + if ( newbrush ) { + tail->next = newbrush; + lastb2->next = b2->next; + brushlist = brushlist->next; + FreeBrush( b1 ); + FreeBrush( b2 ); + for ( tail = brushlist; tail; tail = tail->next ) + { + if ( !tail->next ) { + break; + } + } //end for + merged++; + qprintf( "\r%5d", nummerges++ ); + break; + } //end if + lastb2 = b2; + } //end for + //if b1 can't be merged with any of the other brushes + if ( !b2 ) { + brushlist = brushlist->next; + //keep b1 + b1->next = newbrushlist; + newbrushlist = b1; + } //end else + } //end for + brushlist = newbrushlist; + } while ( merged ); + qprintf( "\n" ); + return newbrushlist; +} //end of the function MergeBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SplitBrush2( bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back ) { + SplitBrush( brush, planenum, front, back ); +#if 0 + if ( *front && ( *front )->sides[( *front )->numsides - 1].texinfo == -1 ) { + ( *front )->sides[( *front )->numsides - 1].texinfo = ( *front )->sides[0].texinfo; // not -1 + } + if ( *back && ( *back )->sides[( *back )->numsides - 1].texinfo == -1 ) { + ( *back )->sides[( *back )->numsides - 1].texinfo = ( *back )->sides[0].texinfo; // not -1 + } +#endif +} //end of the function SplitBrush2 +//=========================================================================== +// Returns a list of brushes that remain after B is subtracted from A. +// May by empty if A is contained inside B. +// The originals are undisturbed. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *SubtractBrush( bspbrush_t *a, bspbrush_t *b ) { // a - b = out (list) + int i; + bspbrush_t *front, *back; + bspbrush_t *out, *in; + + in = a; + out = NULL; + for ( i = 0; i < b->numsides && in; i++ ) + { + SplitBrush2( in, b->sides[i].planenum, &front, &back ); + if ( in != a ) { + FreeBrush( in ); + } + if ( front ) { // add to list + front->next = out; + out = front; + } //end if + in = back; + } //end for + if ( in ) { + FreeBrush( in ); + } //end if + else + { // didn't really intersect + FreeBrushList( out ); + return a; + } //end else + return out; +} //end of the function SubtractBrush +//=========================================================================== +// Returns a single brush made up by the intersection of the +// two provided brushes, or NULL if they are disjoint. +// +// The originals are undisturbed. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *IntersectBrush( bspbrush_t *a, bspbrush_t *b ) { + int i; + bspbrush_t *front, *back; + bspbrush_t *in; + + in = a; + for ( i = 0 ; i < b->numsides && in ; i++ ) + { + SplitBrush2( in, b->sides[i].planenum, &front, &back ); + if ( in != a ) { + FreeBrush( in ); + } + if ( front ) { + FreeBrush( front ); + } + in = back; + } //end for + + if ( in == a ) { + return NULL; + } + + in->next = NULL; + return in; +} //end of the function IntersectBrush +//=========================================================================== +// Returns true if the two brushes definately do not intersect. +// There will be false negatives for some non-axial combinations. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BrushesDisjoint( bspbrush_t *a, bspbrush_t *b ) { + int i, j; + + // check bounding boxes + for ( i = 0 ; i < 3 ; i++ ) + if ( a->mins[i] >= b->maxs[i] + || a->maxs[i] <= b->mins[i] ) { + return true; + } // bounding boxes don't overlap + + // check for opposing planes + for ( i = 0 ; i < a->numsides ; i++ ) + { + for ( j = 0 ; j < b->numsides ; j++ ) + { + if ( a->sides[i].planenum == + ( b->sides[j].planenum ^ 1 ) ) { + return true; // opposite planes, so not touching + } + } + } + + return false; // might intersect +} //end of the function BrushesDisjoint +//=========================================================================== +// Returns a content word for the intersection of two brushes. +// Some combinations will generate a combination (water + clip), +// but most will be the stronger of the two contents. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int IntersectionContents( int c1, int c2 ) { + int out; + + out = c1 | c2; + + if ( out & CONTENTS_SOLID ) { + out = CONTENTS_SOLID; + } + + return out; +} //end of the function IntersectionContents +//=========================================================================== +// Any planes shared with the box edge will be set to no texinfo +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *ClipBrushToBox( bspbrush_t *brush, vec3_t clipmins, vec3_t clipmaxs ) { + int i, j; + bspbrush_t *front, *back; + int p; + + for ( j = 0 ; j < 2 ; j++ ) + { + if ( brush->maxs[j] > clipmaxs[j] ) { + SplitBrush( brush, maxplanenums[j], &front, &back ); + if ( front ) { + FreeBrush( front ); + } + brush = back; + if ( !brush ) { + return NULL; + } + } + if ( brush->mins[j] < clipmins[j] ) { + SplitBrush( brush, minplanenums[j], &front, &back ); + if ( back ) { + FreeBrush( back ); + } + brush = front; + if ( !brush ) { + return NULL; + } + } + } + + // remove any colinear faces + + for ( i = 0 ; i < brush->numsides ; i++ ) + { + p = brush->sides[i].planenum & ~1; + if ( p == maxplanenums[0] || p == maxplanenums[1] + || p == minplanenums[0] || p == minplanenums[1] ) { + brush->sides[i].texinfo = TEXINFO_NODE; + brush->sides[i].flags &= ~SFL_VISIBLE; + } + } + return brush; +} //end of the function ClipBrushToBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *MakeBspBrushList( int startbrush, int endbrush, + vec3_t clipmins, vec3_t clipmaxs ) { + mapbrush_t *mb; + bspbrush_t *brushlist, *newbrush; + int i, j; + int c_faces; + int c_brushes; + int numsides; + int vis; + vec3_t normal; + float dist; + + for ( i = 0 ; i < 2 ; i++ ) + { + VectorClear( normal ); + normal[i] = 1; + dist = clipmaxs[i]; + maxplanenums[i] = FindFloatPlane( normal, dist ); + dist = clipmins[i]; + minplanenums[i] = FindFloatPlane( normal, dist ); + } + + brushlist = NULL; + c_faces = 0; + c_brushes = 0; + + for ( i = startbrush ; i < endbrush ; i++ ) + { + mb = &mapbrushes[i]; + + numsides = mb->numsides; + if ( !numsides ) { + continue; + } + + // make sure the brush has at least one face showing + vis = 0; + for ( j = 0 ; j < numsides ; j++ ) + if ( ( mb->original_sides[j].flags & SFL_VISIBLE ) && mb->original_sides[j].winding ) { + vis++; + } +#if 0 + if ( !vis ) { + continue; // no faces at all + } +#endif + // if the brush is outside the clip area, skip it + for ( j = 0 ; j < 3 ; j++ ) + if ( mb->mins[j] >= clipmaxs[j] + || mb->maxs[j] <= clipmins[j] ) { + break; + } + if ( j != 3 ) { + continue; + } + + // + // make a copy of the brush + // + newbrush = AllocBrush( mb->numsides ); + newbrush->original = mb; + newbrush->numsides = mb->numsides; + memcpy( newbrush->sides, mb->original_sides, numsides * sizeof( side_t ) ); + for ( j = 0 ; j < numsides ; j++ ) + { + if ( newbrush->sides[j].winding ) { + newbrush->sides[j].winding = CopyWinding( newbrush->sides[j].winding ); + } + if ( newbrush->sides[j].surf & SURF_HINT ) { + newbrush->sides[j].flags |= SFL_VISIBLE; // hints are always visible + } + } + VectorCopy( mb->mins, newbrush->mins ); + VectorCopy( mb->maxs, newbrush->maxs ); + + // + // carve off anything outside the clip box + // + newbrush = ClipBrushToBox( newbrush, clipmins, clipmaxs ); + if ( !newbrush ) { + continue; + } + + c_faces += vis; + c_brushes++; + + newbrush->next = brushlist; + brushlist = newbrush; + } + + return brushlist; +} //end of the function MakeBspBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *AddBrushListToTail( bspbrush_t *list, bspbrush_t *tail ) { + bspbrush_t *walk, *next; + + for ( walk = list ; walk ; walk = next ) + { // add to end of list + next = walk->next; + walk->next = NULL; + tail->next = walk; + tail = walk; + } //end for + return tail; +} //end of the function AddBrushListToTail +//=========================================================================== +// Builds a new list that doesn't hold the given brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *CullList( bspbrush_t *list, bspbrush_t *skip1 ) { + bspbrush_t *newlist; + bspbrush_t *next; + + newlist = NULL; + + for ( ; list ; list = next ) + { + next = list->next; + if ( list == skip1 ) { + FreeBrush( list ); + continue; + } + list->next = newlist; + newlist = list; + } + return newlist; +} //end of the function CullList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void WriteBrushMap(char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + + Log_Print("writing %s\n", name); + f = fopen (name, "wb"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "{\n"); + for (i=0,s=list->sides ; inumsides ; i++,s++) + { + w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); +} //end of the function WriteBrushMap +*/ +//=========================================================================== +// Returns true if b1 is allowed to bite b2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BrushGE( bspbrush_t *b1, bspbrush_t *b2 ) { +#ifdef ME + if ( create_aas ) { + if ( b1->original->expansionbbox != b2->original->expansionbbox ) { + return false; + } //end if + //never have something else bite a ladder brush + //never have a ladder brush bite something else + if ( ( b1->original->contents & CONTENTS_LADDER ) + && !( b2->original->contents & CONTENTS_LADDER ) ) { + return false; + } //end if + } //end if +#endif //ME + // detail brushes never bite structural brushes + if ( ( b1->original->contents & CONTENTS_DETAIL ) + && !( b2->original->contents & CONTENTS_DETAIL ) ) { + return false; + } //end if + if ( b1->original->contents & CONTENTS_SOLID ) { + return true; + } //end if + return false; +} //end of the function BrushGE +//=========================================================================== +// Carves any intersecting solid brushes into the minimum number +// of non-intersecting brushes. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *ChopBrushes( bspbrush_t *head ) { + bspbrush_t *b1, *b2, *next; + bspbrush_t *tail; + bspbrush_t *keep; + bspbrush_t *sub, *sub2; + int c1, c2; + int num_csg_iterations; + + Log_Print( "-------- Brush CSG ---------\n" ); + Log_Print( "%6d original brushes\n", CountBrushList( head ) ); + + num_csg_iterations = 0; + qprintf( "%6d output brushes", num_csg_iterations ); + +#if 0 + if ( startbrush == 0 ) { + WriteBrushList( "before.gl", head, false ); + } +#endif + keep = NULL; + +newlist: + // find tail + if ( !head ) { + return NULL; + } + + for ( tail = head; tail->next; tail = tail->next ) + ; + + for ( b1 = head ; b1 ; b1 = next ) + { + next = b1->next; + + //if the conversion is cancelled + if ( cancelconversion ) { + b1->next = keep; + keep = b1; + continue; + } //end if + + for ( b2 = b1->next; b2; b2 = b2->next ) + { + if ( BrushesDisjoint( b1, b2 ) ) { + continue; + } + + sub = NULL; + sub2 = NULL; + c1 = 999999; + c2 = 999999; + + if ( BrushGE( b2, b1 ) ) { + sub = SubtractBrush( b1, b2 ); + if ( sub == b1 ) { + continue; // didn't really intersect + } //end if + if ( !sub ) { // b1 is swallowed by b2 + head = CullList( b1, b1 ); + goto newlist; + } + c1 = CountBrushList( sub ); + } + + if ( BrushGE( b1, b2 ) ) { + sub2 = SubtractBrush( b2, b1 ); + if ( sub2 == b2 ) { + continue; // didn't really intersect + } + if ( !sub2 ) { // b2 is swallowed by b1 + FreeBrushList( sub ); + head = CullList( b1, b2 ); + goto newlist; + } + c2 = CountBrushList( sub2 ); + } + + if ( !sub && !sub2 ) { + continue; // neither one can bite + + } + // only accept if it didn't fragment + // (commenting this out allows full fragmentation) + if ( c1 > 1 && c2 > 1 ) { + if ( sub2 ) { + FreeBrushList( sub2 ); + } + if ( sub ) { + FreeBrushList( sub ); + } + continue; + } + + if ( c1 < c2 ) { + if ( sub2 ) { + FreeBrushList( sub2 ); + } + tail = AddBrushListToTail( sub, tail ); + head = CullList( b1, b1 ); + goto newlist; + } //end if + else + { + if ( sub ) { + FreeBrushList( sub ); + } + tail = AddBrushListToTail( sub2, tail ); + head = CullList( b1, b2 ); + goto newlist; + } //end else + } //end for + + if ( !b2 ) { // b1 is no longer intersecting anything, so keep it + b1->next = keep; + keep = b1; + } //end if + num_csg_iterations++; + qprintf( "\r%6d", num_csg_iterations ); + } //end for + + if ( cancelconversion ) { + return keep; + } + // + qprintf( "\n" ); + Log_Write( "%6d output brushes\r\n", num_csg_iterations ); + +#if 0 + { + WriteBrushList( "after.gl", keep, false ); + WriteBrushMap( "after.map", keep ); + } +#endif + + return keep; +} //end of the function ChopBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *InitialBrushList( bspbrush_t *list ) { + bspbrush_t *b; + bspbrush_t *out, *newb; + int i; + + // only return brushes that have visible faces + out = NULL; + for ( b = list ; b ; b = b->next ) + { +#if 0 + for ( i = 0 ; i < b->numsides ; i++ ) + if ( b->sides[i].flags & SFL_VISIBLE ) { + break; + } + if ( i == b->numsides ) { + continue; + } +#endif + newb = CopyBrush( b ); + newb->next = out; + out = newb; + + // clear visible, so it must be set by MarkVisibleFaces_r + // to be used in the optimized list + for ( i = 0 ; i < b->numsides ; i++ ) + { + newb->sides[i].original = &b->sides[i]; +// newb->sides[i].visible = true; + b->sides[i].flags &= ~SFL_VISIBLE; + } + } + + return out; +} //end of the function InitialBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *OptimizedBrushList( bspbrush_t *list ) { + bspbrush_t *b; + bspbrush_t *out, *newb; + int i; + + // only return brushes that have visible faces + out = NULL; + for ( b = list ; b ; b = b->next ) + { + for ( i = 0 ; i < b->numsides ; i++ ) + if ( b->sides[i].flags & SFL_VISIBLE ) { + break; + } + if ( i == b->numsides ) { + continue; + } + newb = CopyBrush( b ); + newb->next = out; + out = newb; + } //end for + +// WriteBrushList ("vis.gl", out, true); + return out; +} //end of the function OptimizeBrushList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tree_t *ProcessWorldBrushes( int brush_start, int brush_end ) { + bspbrush_t *brushes; + tree_t *tree; + node_t *node; + vec3_t mins, maxs; + + //take the whole world + mins[0] = map_mins[0] - 8; + mins[1] = map_mins[1] - 8; + mins[2] = map_mins[2] - 8; + + maxs[0] = map_maxs[0] + 8; + maxs[1] = map_maxs[1] + 8; + maxs[2] = map_maxs[2] + 8; + + //reset the brush bsp + ResetBrushBSP(); + + // the makelist and chopbrushes could be cached between the passes... + + //create a list with brushes that are within the given mins/maxs + //some brushes will be cut and only the part that falls within the + //mins/maxs will be in the bush list + brushes = MakeBspBrushList( brush_start, brush_end, mins, maxs ); + // + + if ( !brushes ) { + node = AllocNode(); + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + + tree = Tree_Alloc(); + tree->headnode = node; + VectorCopy( mins, tree->mins ); + VectorCopy( maxs, tree->maxs ); + } //end if + else + { + //Carves any intersecting solid brushes into the minimum number + //of non-intersecting brushes. + if ( !nocsg ) { + brushes = ChopBrushes( brushes ); + /* + if (create_aas) + { + brushes = MergeBrushes(brushes); + } //end if*/ + } //end if + //if the conversion is cancelled + if ( cancelconversion ) { + FreeBrushList( brushes ); + return NULL; + } //end if + //create the actual bsp tree + tree = BrushBSP( brushes, mins, maxs ); + } //end else + //return the tree + return tree; +} //end of the function ProcessWorldBrushes diff --git a/src/bspc/faces.c b/src/bspc/faces.c new file mode 100644 index 0000000..7952688 --- /dev/null +++ b/src/bspc/faces.c @@ -0,0 +1,1002 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// NO LONGER USED +// faces.c +#if 0 +#include "qbsp.h" +#include "l_mem.h" + + +extern dvertex_t *dvertexes; +extern int numvertexes; +extern int numedges; +extern dedge_t *dedges; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; + +/* + + some faces will be removed before saving, but still form nodes: + + the insides of sky volumes + meeting planes of different water current volumes + +*/ + +// undefine for dumb linear searches +#define USE_HASHING + +#define INTEGRAL_EPSILON 0.01 +#define POINT_EPSILON 0.5 +#define OFF_EPSILON 0.5 + +int c_merge; +int c_subdivide; + +int c_totalverts; +int c_uniqueverts; +int c_degenerate; +int c_tjunctions; +int c_faceoverflows; +int c_facecollapse; +int c_badstartverts; + +#define MAX_SUPERVERTS 512 +int superverts[MAX_SUPERVERTS]; +int numsuperverts; + +face_t *edgefaces[MAX_MAP_EDGES][2]; +int firstmodeledge = 1; +int firstmodelface; + +int c_tryedges; + +vec3_t edge_dir; +vec3_t edge_start; +vec_t edge_len; + +int num_edge_verts; +int edge_verts[MAX_MAP_VERTS]; + + +face_t *NewFaceFromFace( face_t *f ); + +//=========================================================================== + +typedef struct hashvert_s +{ + struct hashvert_s *next; + int num; +} hashvert_t; + + +#define HASH_SIZE 64 + + +int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain +int hashverts[HASH_SIZE * HASH_SIZE]; // a vertex number, or 0 for no verts + +face_t *edgefaces[MAX_MAP_EDGES][2]; + +//============================================================================ + + +unsigned HashVec( vec3_t vec ) { + int x, y; + + x = ( 4096 + (int)( vec[0] + 0.5 ) ) >> 7; + y = ( 4096 + (int)( vec[1] + 0.5 ) ) >> 7; + + if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) { + Error( "HashVec: point outside valid range" ); + } + + return y * HASH_SIZE + x; +} + +#ifdef USE_HASHING +/* +============= +GetVertex + +Uses hashing +============= +*/ +int GetVertexnum( vec3_t in ) { + int h; + int i; + float *p; + vec3_t vert; + int vnum; + + c_totalverts++; + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( fabs( in[i] - Q_rint( in[i] ) ) < INTEGRAL_EPSILON ) { + vert[i] = Q_rint( in[i] ); + } else { + vert[i] = in[i]; + } + } + + h = HashVec( vert ); + + for ( vnum = hashverts[h] ; vnum ; vnum = vertexchain[vnum] ) + { + p = dvertexes[vnum].point; + if ( fabs( p[0] - vert[0] ) < POINT_EPSILON + && fabs( p[1] - vert[1] ) < POINT_EPSILON + && fabs( p[2] - vert[2] ) < POINT_EPSILON ) { + return vnum; + } + } + +// emit a vertex + if ( numvertexes == MAX_MAP_VERTS ) { + Error( "numvertexes == MAX_MAP_VERTS" ); + } + + dvertexes[numvertexes].point[0] = vert[0]; + dvertexes[numvertexes].point[1] = vert[1]; + dvertexes[numvertexes].point[2] = vert[2]; + + vertexchain[numvertexes] = hashverts[h]; + hashverts[h] = numvertexes; + + c_uniqueverts++; + + numvertexes++; + + return numvertexes - 1; +} +#else +/* +================== +GetVertexnum + +Dumb linear search +================== +*/ +int GetVertexnum( vec3_t v ) { + int i, j; + dvertex_t *dv; + vec_t d; + + c_totalverts++; + + // make really close values exactly integral + for ( i = 0 ; i < 3 ; i++ ) + { + if ( fabs( v[i] - (int)( v[i] + 0.5 ) ) < INTEGRAL_EPSILON ) { + v[i] = (int)( v[i] + 0.5 ); + } + if ( v[i] < -4096 || v[i] > 4096 ) { + Error( "GetVertexnum: outside +/- 4096" ); + } + } + + // search for an existing vertex match + for ( i = 0, dv = dvertexes ; i < numvertexes ; i++, dv++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + d = v[j] - dv->point[j]; + if ( d > POINT_EPSILON || d < -POINT_EPSILON ) { + break; + } + } + if ( j == 3 ) { + return i; // a match + } + } + + // new point + if ( numvertexes == MAX_MAP_VERTS ) { + Error( "MAX_MAP_VERTS" ); + } + VectorCopy( v, dv->point ); + numvertexes++; + c_uniqueverts++; + + return numvertexes - 1; +} +#endif + + +/* +================== +FaceFromSuperverts + +The faces vertexes have been added to the superverts[] array, +and there may be more there than can be held in a face (MAXEDGES). + +If less, the faces vertexnums[] will be filled in, otherwise +face will reference a tree of split[] faces until all of the +vertexnums can be added. + +superverts[base] will become face->vertexnums[0], and the others +will be circularly filled in. +================== +*/ +void FaceFromSuperverts( node_t *node, face_t *f, int base ) { + face_t *newf; + int remaining; + int i; + + remaining = numsuperverts; + while ( remaining > MAXEDGES ) + { // must split into two faces, because of vertex overload + c_faceoverflows++; + + newf = f->split[0] = NewFaceFromFace( f ); + newf = f->split[0]; + newf->next = node->faces; + node->faces = newf; + + newf->numpoints = MAXEDGES; + for ( i = 0 ; i < MAXEDGES ; i++ ) + newf->vertexnums[i] = superverts[( i + base ) % numsuperverts]; + + f->split[1] = NewFaceFromFace( f ); + f = f->split[1]; + f->next = node->faces; + node->faces = f; + + remaining -= ( MAXEDGES - 2 ); + base = ( base + MAXEDGES - 1 ) % numsuperverts; + } + + // copy the vertexes back to the face + f->numpoints = remaining; + for ( i = 0 ; i < remaining ; i++ ) + f->vertexnums[i] = superverts[( i + base ) % numsuperverts]; +} + + +/* +================== +EmitFaceVertexes +================== +*/ +void EmitFaceVertexes( node_t *node, face_t *f ) { + winding_t *w; + int i; + + if ( f->merged || f->split[0] || f->split[1] ) { + return; + } + + w = f->w; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + if ( noweld ) { // make every point unique + if ( numvertexes == MAX_MAP_VERTS ) { + Error( "MAX_MAP_VERTS" ); + } + superverts[i] = numvertexes; + VectorCopy( w->p[i], dvertexes[numvertexes].point ); + numvertexes++; + c_uniqueverts++; + c_totalverts++; + } else { + superverts[i] = GetVertexnum( w->p[i] ); + } + } + numsuperverts = w->numpoints; + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts( node, f, 0 ); +} + +/* +================== +EmitVertexes_r +================== +*/ +void EmitVertexes_r( node_t *node ) { + int i; + face_t *f; + + if ( node->planenum == PLANENUM_LEAF ) { + return; + } + + for ( f = node->faces ; f ; f = f->next ) + { + EmitFaceVertexes( node, f ); + } + + for ( i = 0 ; i < 2 ; i++ ) + EmitVertexes_r( node->children[i] ); +} + + +#ifdef USE_HASHING +/* +========== +FindEdgeVerts + +Uses the hash tables to cut down to a small number +========== +*/ +void FindEdgeVerts( vec3_t v1, vec3_t v2 ) { + int x1, x2, y1, y2, t; + int x, y; + int vnum; + +#if 0 + { + int i; + num_edge_verts = numvertexes - 1; + for ( i = 0 ; i < numvertexes - 1 ; i++ ) + edge_verts[i] = i + 1; + } +#endif + + x1 = ( 4096 + (int)( v1[0] + 0.5 ) ) >> 7; + y1 = ( 4096 + (int)( v1[1] + 0.5 ) ) >> 7; + x2 = ( 4096 + (int)( v2[0] + 0.5 ) ) >> 7; + y2 = ( 4096 + (int)( v2[1] + 0.5 ) ) >> 7; + + if ( x1 > x2 ) { + t = x1; + x1 = x2; + x2 = t; + } + if ( y1 > y2 ) { + t = y1; + y1 = y2; + y2 = t; + } +#if 0 + x1--; + x2++; + y1--; + y2++; + if ( x1 < 0 ) { + x1 = 0; + } + if ( x2 >= HASH_SIZE ) { + x2 = HASH_SIZE; + } + if ( y1 < 0 ) { + y1 = 0; + } + if ( y2 >= HASH_SIZE ) { + y2 = HASH_SIZE; + } +#endif + num_edge_verts = 0; + for ( x = x1 ; x <= x2 ; x++ ) + { + for ( y = y1 ; y <= y2 ; y++ ) + { + for ( vnum = hashverts[y * HASH_SIZE + x] ; vnum ; vnum = vertexchain[vnum] ) + { + edge_verts[num_edge_verts++] = vnum; + } + } + } +} + +#else +/* +========== +FindEdgeVerts + +Forced a dumb check of everything +========== +*/ +void FindEdgeVerts( vec3_t v1, vec3_t v2 ) { + int i; + + num_edge_verts = numvertexes - 1; + for ( i = 0 ; i < num_edge_verts ; i++ ) + edge_verts[i] = i + 1; +} +#endif + +/* +========== +TestEdge + +Can be recursively reentered +========== +*/ +void TestEdge( vec_t start, vec_t end, int p1, int p2, int startvert ) { + int j, k; + vec_t dist; + vec3_t delta; + vec3_t exact; + vec3_t off; + vec_t error; + vec3_t p; + + if ( p1 == p2 ) { + c_degenerate++; + return; // degenerate edge + } + + for ( k = startvert ; k < num_edge_verts ; k++ ) + { + j = edge_verts[k]; + if ( j == p1 || j == p2 ) { + continue; + } + + VectorCopy( dvertexes[j].point, p ); + + VectorSubtract( p, edge_start, delta ); + dist = DotProduct( delta, edge_dir ); + if ( dist <= start || dist >= end ) { + continue; // off an end + } + VectorMA( edge_start, dist, edge_dir, exact ); + VectorSubtract( p, exact, off ); + error = VectorLength( off ); + + if ( fabs( error ) > OFF_EPSILON ) { + continue; // not on the edge + + } + // break the edge + c_tjunctions++; + TestEdge( start, dist, p1, j, k + 1 ); + TestEdge( dist, end, j, p2, k + 1 ); + return; + } + + // the edge p1 to p2 is now free of tjunctions + if ( numsuperverts >= MAX_SUPERVERTS ) { + Error( "MAX_SUPERVERTS" ); + } + superverts[numsuperverts] = p1; + numsuperverts++; +} + +/* +================== +FixFaceEdges + +================== +*/ +void FixFaceEdges( node_t *node, face_t *f ) { + int p1, p2; + int i; + vec3_t e2; + vec_t len; + int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; + int base; + + if ( f->merged || f->split[0] || f->split[1] ) { + return; + } + + numsuperverts = 0; + + for ( i = 0 ; i < f->numpoints ; i++ ) + { + p1 = f->vertexnums[i]; + p2 = f->vertexnums[( i + 1 ) % f->numpoints]; + + VectorCopy( dvertexes[p1].point, edge_start ); + VectorCopy( dvertexes[p2].point, e2 ); + + FindEdgeVerts( edge_start, e2 ); + + VectorSubtract( e2, edge_start, edge_dir ); + len = VectorNormalize( edge_dir ); + + start[i] = numsuperverts; + TestEdge( 0, len, p1, p2, 0 ); + + count[i] = numsuperverts - start[i]; + } + + if ( numsuperverts < 3 ) { // entire face collapsed + f->numpoints = 0; + c_facecollapse++; + return; + } + + // we want to pick a vertex that doesn't have tjunctions + // on either side, which can cause artifacts on trifans, + // especially underwater + for ( i = 0 ; i < f->numpoints ; i++ ) + { + if ( count[i] == 1 && count[( i + f->numpoints - 1 ) % f->numpoints] == 1 ) { + break; + } + } + if ( i == f->numpoints ) { + f->badstartvert = true; + c_badstartverts++; + base = 0; + } else + { // rotate the vertex order + base = start[i]; + } + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts( node, f, base ); +} + +/* +================== +FixEdges_r +================== +*/ +void FixEdges_r( node_t *node ) { + int i; + face_t *f; + + if ( node->planenum == PLANENUM_LEAF ) { + return; + } + + for ( f = node->faces ; f ; f = f->next ) + FixFaceEdges( node, f ); + + for ( i = 0 ; i < 2 ; i++ ) + FixEdges_r( node->children[i] ); +} + +/* +=========== +FixTjuncs + +=========== +*/ +void FixTjuncs( node_t *headnode ) { + // snap and merge all vertexes + qprintf( "---- snap verts ----\n" ); + memset( hashverts, 0, sizeof( hashverts ) ); + c_totalverts = 0; + c_uniqueverts = 0; + c_faceoverflows = 0; + EmitVertexes_r( headnode ); + qprintf( "%i unique from %i\n", c_uniqueverts, c_totalverts ); + + // break edges on tjunctions + qprintf( "---- tjunc ----\n" ); + c_tryedges = 0; + c_degenerate = 0; + c_facecollapse = 0; + c_tjunctions = 0; + if ( !notjunc ) { + FixEdges_r( headnode ); + } + qprintf( "%5i edges degenerated\n", c_degenerate ); + qprintf( "%5i faces degenerated\n", c_facecollapse ); + qprintf( "%5i edges added by tjunctions\n", c_tjunctions ); + qprintf( "%5i faces added by tjunctions\n", c_faceoverflows ); + qprintf( "%5i bad start verts\n", c_badstartverts ); +} + + +//======================================================== + +int c_faces; + +face_t *AllocFace( void ) { + face_t *f; + + f = GetMemory( sizeof( *f ) ); + memset( f, 0, sizeof( *f ) ); + c_faces++; + + return f; +} + +face_t *NewFaceFromFace( face_t *f ) { + face_t *newf; + + newf = AllocFace(); + *newf = *f; + newf->merged = NULL; + newf->split[0] = newf->split[1] = NULL; + newf->w = NULL; + return newf; +} + +void FreeFace( face_t *f ) { + if ( f->w ) { + FreeWinding( f->w ); + } + FreeMemory( f ); + c_faces--; +} + +//======================================================== + +/* +================== +GetEdge + +Called by writebsp. +Don't allow four way edges +================== +*/ +int GetEdge2( int v1, int v2, face_t *f ) { + dedge_t *edge; + int i; + + c_tryedges++; + + if ( !noshare ) { + for ( i = firstmodeledge ; i < numedges ; i++ ) + { + edge = &dedges[i]; + if ( v1 == edge->v[1] && v2 == edge->v[0] + && edgefaces[i][0]->contents == f->contents ) { + if ( edgefaces[i][1] ) { + // printf ("WARNING: multiple backward edge\n"); + continue; + } + edgefaces[i][1] = f; + return -i; + } + #if 0 + if ( v1 == edge->v[0] && v2 == edge->v[1] ) { + printf( "WARNING: multiple forward edge\n" ); + return i; + } + #endif + } + } + +// emit an edge + if ( numedges >= MAX_MAP_EDGES ) { + Error( "numedges == MAX_MAP_EDGES" ); + } + edge = &dedges[numedges]; + numedges++; + edge->v[0] = v1; + edge->v[1] = v2; + edgefaces[numedges - 1][0] = f; + + return numedges - 1; +} + +/* +=========================================================================== + +FACE MERGING + +=========================================================================== +*/ + +/* +============= +TryMerge + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +face_t *TryMerge( face_t *f1, face_t *f2, vec3_t planenormal ) { + face_t *newf; + winding_t *nw; + + if ( !f1->w || !f2->w ) { + return NULL; + } + if ( f1->texinfo != f2->texinfo ) { + return NULL; + } + if ( f1->planenum != f2->planenum ) { // on front and back sides + return NULL; + } + if ( f1->contents != f2->contents ) { + return NULL; + } + + + nw = TryMergeWinding( f1->w, f2->w, planenormal ); + if ( !nw ) { + return NULL; + } + + c_merge++; + newf = NewFaceFromFace( f1 ); + newf->w = nw; + + f1->merged = newf; + f2->merged = newf; + + return newf; +} + +/* +=============== +MergeNodeFaces +=============== +*/ +void MergeNodeFaces( node_t *node ) { + face_t *f1, *f2, *end; + face_t *merged; + plane_t *plane; + + plane = &mapplanes[node->planenum]; + merged = NULL; + + for ( f1 = node->faces ; f1 ; f1 = f1->next ) + { + if ( f1->merged || f1->split[0] || f1->split[1] ) { + continue; + } + + for ( f2 = node->faces ; f2 != f1 ; f2 = f2->next ) + { + if ( f2->merged || f2->split[0] || f2->split[1] ) { + continue; + } + + //IDBUG: always passes the face's node's normal to TryMerge() + //regardless of which side the face is on. Approximately 50% of + //the time the face will be on the other side of node, and thus + //the result of the convex/concave test in TryMergeWinding(), + //which depends on the normal, is flipped. This causes faces + //that shouldn't be merged to be merged and faces that + //should be merged to not be merged. + //the following added line fixes this bug + //thanks to: Alexander Malmberg + plane = &mapplanes[f1->planenum]; + // + merged = TryMerge( f1, f2, plane->normal ); + if ( !merged ) { + continue; + } + + // add merged to the end of the node face list + // so it will be checked against all the faces again + for ( end = node->faces ; end->next ; end = end->next ) + ; + merged->next = NULL; + end->next = merged; + break; + } + } +} + +//===================================================================== + +/* +=============== +SubdivideFace + +Chop up faces that are larger than we want in the surface cache +=============== +*/ +void SubdivideFace( node_t *node, face_t *f ) { + float mins, maxs; + vec_t v; + int axis, i; + texinfo_t *tex; + vec3_t temp; + vec_t dist; + winding_t *w, *frontw, *backw; + + if ( f->merged ) { + return; + } + +// special (non-surface cached) faces don't need subdivision + tex = &texinfo[f->texinfo]; + + if ( tex->flags & ( SURF_WARP | SURF_SKY ) ) { + return; + } + + for ( axis = 0 ; axis < 2 ; axis++ ) + { + while ( 1 ) + { + mins = 999999; + maxs = -999999; + + VectorCopy( tex->vecs[axis], temp ); + w = f->w; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + v = DotProduct( w->p[i], temp ); + if ( v < mins ) { + mins = v; + } + if ( v > maxs ) { + maxs = v; + } + } +#if 0 + if ( maxs - mins <= 0 ) { + Error( "zero extents" ); + } +#endif + if ( axis == 2 ) { // allow double high walls + if ( maxs - mins <= subdivide_size /* *2 */ ) { + break; + } + } else if ( maxs - mins <= subdivide_size ) { + break; + } + + // split it + c_subdivide++; + + v = VectorNormalize( temp ); + + dist = ( mins + subdivide_size - 16 ) / v; + + ClipWindingEpsilon( w, temp, dist, ON_EPSILON, &frontw, &backw ); + if ( !frontw || !backw ) { + Error( "SubdivideFace: didn't split the polygon" ); + } + + f->split[0] = NewFaceFromFace( f ); + f->split[0]->w = frontw; + f->split[0]->next = node->faces; + node->faces = f->split[0]; + + f->split[1] = NewFaceFromFace( f ); + f->split[1]->w = backw; + f->split[1]->next = node->faces; + node->faces = f->split[1]; + + SubdivideFace( node, f->split[0] ); + SubdivideFace( node, f->split[1] ); + return; + } + } +} + +void SubdivideNodeFaces( node_t *node ) { + face_t *f; + + for ( f = node->faces ; f ; f = f->next ) + { + SubdivideFace( node, f ); + } +} + +//=========================================================================== + +int c_nodefaces; + + +/* +============ +FaceFromPortal + +============ +*/ +face_t *FaceFromPortal( portal_t *p, int pside ) { + face_t *f; + side_t *side; + + side = p->side; + if ( !side ) { + return NULL; // portal does not bridge different visible contents + + } + f = AllocFace(); + + f->texinfo = side->texinfo; + f->planenum = ( side->planenum & ~1 ) | pside; + f->portal = p; + + if ( ( p->nodes[pside]->contents & CONTENTS_WINDOW ) + && VisibleContents( p->nodes[!pside]->contents ^ p->nodes[pside]->contents ) == CONTENTS_WINDOW ) { + return NULL; // don't show insides of windows + + } + if ( pside ) { + f->w = ReverseWinding( p->winding ); + f->contents = p->nodes[1]->contents; + } else + { + f->w = CopyWinding( p->winding ); + f->contents = p->nodes[0]->contents; + } + return f; +} + + +/* +=============== +MakeFaces_r + +If a portal will make a visible face, +mark the side that originally created it + + solid / empty : solid + solid / water : solid + water / empty : water + water / water : none +=============== +*/ +void MakeFaces_r( node_t *node ) { + portal_t *p; + int s; + + // recurse down to leafs + if ( node->planenum != PLANENUM_LEAF ) { + MakeFaces_r( node->children[0] ); + MakeFaces_r( node->children[1] ); + + // merge together all visible faces on the node + if ( !nomerge ) { + MergeNodeFaces( node ); + } + if ( !nosubdiv ) { + SubdivideNodeFaces( node ); + } + + return; + } + + // solid leafs never have visible faces + if ( node->contents & CONTENTS_SOLID ) { + return; + } + + // see which portals are valid + for ( p = node->portals ; p ; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + + p->face[s] = FaceFromPortal( p, s ); + if ( p->face[s] ) { + c_nodefaces++; + p->face[s]->next = p->onnode->faces; + p->onnode->faces = p->face[s]; + } + } +} + +/* +============ +MakeFaces +============ +*/ +void MakeFaces( node_t *node ) { + qprintf( "--- MakeFaces ---\n" ); + c_merge = 0; + c_subdivide = 0; + c_nodefaces = 0; + + MakeFaces_r( node ); + + qprintf( "%5i makefaces\n", c_nodefaces ); + qprintf( "%5i merged\n", c_merge ); + qprintf( "%5i subdivided\n", c_subdivide ); +} +#endif \ No newline at end of file diff --git a/src/bspc/glfile.c b/src/bspc/glfile.c new file mode 100644 index 0000000..67a3747 --- /dev/null +++ b/src/bspc/glfile.c @@ -0,0 +1,157 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "qbsp.h" + +int c_glfaces; + +int PortalVisibleSides( portal_t *p ) { + int fcon, bcon; + + if ( !p->onnode ) { + return 0; // outside + + } + fcon = p->nodes[0]->contents; + bcon = p->nodes[1]->contents; + + // same contents never create a face + if ( fcon == bcon ) { + return 0; + } + + // FIXME: is this correct now? + if ( !fcon ) { + return 1; + } + if ( !bcon ) { + return 2; + } + return 0; +} + +void OutputWinding( winding_t *w, FILE *glview ) { + static int level = 128; + vec_t light; + int i; + + fprintf( glview, "%i\n", w->numpoints ); + level += 28; + light = ( level & 255 ) / 255.0; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + fprintf( glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + light, + light, + light ); + } + fprintf( glview, "\n" ); +} + +/* +============= +OutputPortal +============= +*/ +void OutputPortal( portal_t *p, FILE *glview ) { + winding_t *w; + int sides; + + sides = PortalVisibleSides( p ); + if ( !sides ) { + return; + } + + c_glfaces++; + + w = p->winding; + + if ( sides == 2 ) { // back side + w = ReverseWinding( w ); + } + + OutputWinding( w, glview ); + + if ( sides == 2 ) { + FreeWinding( w ); + } +} + +/* +============= +WriteGLView_r +============= +*/ +void WriteGLView_r( node_t *node, FILE *glview ) { + portal_t *p, *nextp; + + if ( node->planenum != PLANENUM_LEAF ) { + WriteGLView_r( node->children[0], glview ); + WriteGLView_r( node->children[1], glview ); + return; + } + + // write all the portals + for ( p = node->portals ; p ; p = nextp ) + { + if ( p->nodes[0] == node ) { + OutputPortal( p, glview ); + nextp = p->next[0]; + } else { + nextp = p->next[1]; + } + } +} + +/* +============= +WriteGLView +============= +*/ +void WriteGLView( tree_t *tree, char *source ) { + char name[1024]; + FILE *glview; + + c_glfaces = 0; + sprintf( name, "%s%s.gl",outbase, source ); + printf( "Writing %s\n", name ); + + glview = fopen( name, "w" ); + if ( !glview ) { + Error( "Couldn't open %s", name ); + } + WriteGLView_r( tree->headnode, glview ); + fclose( glview ); + + printf( "%5i c_glfaces\n", c_glfaces ); +} + diff --git a/src/bspc/l_bsp_ent.c b/src/bspc/l_bsp_ent.c new file mode 100644 index 0000000..647b24c --- /dev/null +++ b/src/bspc/l_bsp_ent.c @@ -0,0 +1,195 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_bsp_ent.c +// Function: bsp entity parsing +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1999-04-05 +// Tab Size: 3 +// Notes: - +//=========================================================================== + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "../botlib/l_script.h" +#include "l_bsp_ent.h" + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing( char *e ) { + char *s; + + s = e + strlen( e ) - 1; + while ( s >= e && *s <= 32 ) + { + *s = 0; + s--; + } +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair( script_t *script ) { + epair_t *e; + token_t token; + + e = GetMemory( sizeof( epair_t ) ); + memset( e, 0, sizeof( epair_t ) ); + + PS_ExpectAnyToken( script, &token ); + StripDoubleQuotes( token.string ); + if ( strlen( token.string ) >= MAX_KEY - 1 ) { + Error( "ParseEpair: token %s too long", token.string ); + } + e->key = copystring( token.string ); + PS_ExpectAnyToken( script, &token ); + StripDoubleQuotes( token.string ); + if ( strlen( token.string ) >= MAX_VALUE - 1 ) { + Error( "ParseEpair: token %s too long", token.string ); + } + e->value = copystring( token.string ); + + // strip trailing spaces + StripTrailing( e->key ); + StripTrailing( e->value ); + + return e; +} //end of the function ParseEpair + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity( script_t *script ) { + epair_t *e; + entity_t *mapent; + token_t token; + + if ( !PS_ReadToken( script, &token ) ) { + return false; + } + + if ( strcmp( token.string, "{" ) ) { + Error( "ParseEntity: { not found" ); + } + + if ( num_entities == MAX_MAP_ENTITIES ) { + Error( "num_entities == MAX_MAP_ENTITIES" ); + } + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if ( !PS_ReadToken( script, &token ) ) { + Error( "ParseEntity: EOF without closing brace" ); + } + if ( !strcmp( token.string, "}" ) ) { + break; + } + PS_UnreadLastToken( script ); + e = ParseEpair( script ); + e->next = mapent->epairs; + mapent->epairs = e; + } while ( 1 ); + + return true; +} //end of the function ParseEntity + +void PrintEntity( entity_t *ent ) { + epair_t *ep; + + printf( "------- entity %p -------\n", ent ); + for ( ep = ent->epairs ; ep ; ep = ep->next ) + { + printf( "%s = %s\n", ep->key, ep->value ); + } + +} + +void SetKeyValue( entity_t *ent, char *key, char *value ) { + epair_t *ep; + + for ( ep = ent->epairs ; ep ; ep = ep->next ) + if ( !strcmp( ep->key, key ) ) { + FreeMemory( ep->value ); + ep->value = copystring( value ); + return; + } + ep = GetMemory( sizeof( *ep ) ); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring( key ); + ep->value = copystring( value ); +} + +char *ValueForKey( entity_t *ent, char *key ) { + epair_t *ep; + + for ( ep = ent->epairs ; ep ; ep = ep->next ) + if ( !strcmp( ep->key, key ) ) { + return ep->value; + } + return ""; +} + +vec_t FloatForKey( entity_t *ent, char *key ) { + char *k; + + k = ValueForKey( ent, key ); + return atof( k ); +} + +void GetVectorForKey( entity_t *ent, char *key, vec3_t vec ) { + char *k; + double v1, v2, v3; + + k = ValueForKey( ent, key ); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 ); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + + diff --git a/src/bspc/l_bsp_ent.h b/src/bspc/l_bsp_ent.h new file mode 100644 index 0000000..9eb95cf --- /dev/null +++ b/src/bspc/l_bsp_ent.h @@ -0,0 +1,65 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#ifndef MAX_MAP_ENTITIES +#define MAX_MAP_ENTITIES 2048 +#endif + +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; + // only valid for func_areaportals + int areaportalnum; + int portalareas[2]; + int modelnum; //for bsp 2 map conversion + qboolean wasdetail; //for SIN +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing( char *e ); +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 ); +qboolean ParseEntity( script_t *script ); +epair_t *ParseEpair( script_t *script ); +void PrintEntity( entity_t *ent ); + diff --git a/src/bspc/l_bsp_q1.c b/src/bspc/l_bsp_q1.c new file mode 100644 index 0000000..b9348b9 --- /dev/null +++ b/src/bspc/l_bsp_q1.c @@ -0,0 +1,608 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "../botlib/l_script.h" +#include "l_bsp_q1.h" +#include "l_bsp_ent.h" + +//============================================================================= + +int q1_nummodels; +q1_dmodel_t *q1_dmodels; //[MAX_MAP_MODELS]; + +int q1_visdatasize; +byte *q1_dvisdata; //[MAX_MAP_VISIBILITY]; + +int q1_lightdatasize; +byte *q1_dlightdata; //[MAX_MAP_LIGHTING]; + +int q1_texdatasize; +byte *q1_dtexdata; //[MAX_MAP_MIPTEX]; // (dmiptexlump_t) + +int q1_entdatasize; +char *q1_dentdata; //[MAX_MAP_ENTSTRING]; + +int q1_numleafs; +q1_dleaf_t *q1_dleafs; //[MAX_MAP_LEAFS]; + +int q1_numplanes; +q1_dplane_t *q1_dplanes; //[MAX_MAP_PLANES]; + +int q1_numvertexes; +q1_dvertex_t *q1_dvertexes; //[MAX_MAP_VERTS]; + +int q1_numnodes; +q1_dnode_t *q1_dnodes; //[MAX_MAP_NODES]; + +int q1_numtexinfo; +q1_texinfo_t *q1_texinfo; //[MAX_MAP_TEXINFO]; + +int q1_numfaces; +q1_dface_t *q1_dfaces; //[MAX_MAP_FACES]; + +int q1_numclipnodes; +q1_dclipnode_t *q1_dclipnodes; //[MAX_MAP_CLIPNODES]; + +int q1_numedges; +q1_dedge_t *q1_dedges; //[MAX_MAP_EDGES]; + +int q1_nummarksurfaces; +unsigned short *q1_dmarksurfaces; //[MAX_MAP_MARKSURFACES]; + +int q1_numsurfedges; +int *q1_dsurfedges; //[MAX_MAP_SURFEDGES]; + +//============================================================================= + +int q1_bspallocated = false; +int q1_allocatedbspmem = 0; + +void Q1_AllocMaxBSP( void ) { + //models + q1_nummodels = 0; + q1_dmodels = (q1_dmodel_t *) GetMemory( Q1_MAX_MAP_MODELS * sizeof( q1_dmodel_t ) ); + q1_allocatedbspmem = Q1_MAX_MAP_MODELS * sizeof( q1_dmodel_t ); + //visibility + q1_visdatasize = 0; + q1_dvisdata = (byte *) GetMemory( Q1_MAX_MAP_VISIBILITY * sizeof( byte ) ); + q1_allocatedbspmem += Q1_MAX_MAP_VISIBILITY * sizeof( byte ); + //light data + q1_lightdatasize = 0; + q1_dlightdata = (byte *) GetMemory( Q1_MAX_MAP_LIGHTING * sizeof( byte ) ); + q1_allocatedbspmem += Q1_MAX_MAP_LIGHTING * sizeof( byte ); + //texture data + q1_texdatasize = 0; + q1_dtexdata = (byte *) GetMemory( Q1_MAX_MAP_MIPTEX * sizeof( byte ) ); // (dmiptexlump_t) + q1_allocatedbspmem += Q1_MAX_MAP_MIPTEX * sizeof( byte ); + //entities + q1_entdatasize = 0; + q1_dentdata = (char *) GetMemory( Q1_MAX_MAP_ENTSTRING * sizeof( char ) ); + q1_allocatedbspmem += Q1_MAX_MAP_ENTSTRING * sizeof( char ); + //leaves + q1_numleafs = 0; + q1_dleafs = (q1_dleaf_t *) GetMemory( Q1_MAX_MAP_LEAFS * sizeof( q1_dleaf_t ) ); + q1_allocatedbspmem += Q1_MAX_MAP_LEAFS * sizeof( q1_dleaf_t ); + //planes + q1_numplanes = 0; + q1_dplanes = (q1_dplane_t *) GetMemory( Q1_MAX_MAP_PLANES * sizeof( q1_dplane_t ) ); + q1_allocatedbspmem += Q1_MAX_MAP_PLANES * sizeof( q1_dplane_t ); + //vertexes + q1_numvertexes = 0; + q1_dvertexes = (q1_dvertex_t *) GetMemory( Q1_MAX_MAP_VERTS * sizeof( q1_dvertex_t ) ); + q1_allocatedbspmem += Q1_MAX_MAP_VERTS * sizeof( q1_dvertex_t ); + //nodes + q1_numnodes = 0; + q1_dnodes = (q1_dnode_t *) GetMemory( Q1_MAX_MAP_NODES * sizeof( q1_dnode_t ) ); + q1_allocatedbspmem += Q1_MAX_MAP_NODES * sizeof( q1_dnode_t ); + //texture info + q1_numtexinfo = 0; + q1_texinfo = (q1_texinfo_t *) GetMemory( Q1_MAX_MAP_TEXINFO * sizeof( q1_texinfo_t ) ); + q1_allocatedbspmem += Q1_MAX_MAP_TEXINFO * sizeof( q1_texinfo_t ); + //faces + q1_numfaces = 0; + q1_dfaces = (q1_dface_t *) GetMemory( Q1_MAX_MAP_FACES * sizeof( q1_dface_t ) ); + q1_allocatedbspmem += Q1_MAX_MAP_FACES * sizeof( q1_dface_t ); + //clip nodes + q1_numclipnodes = 0; + q1_dclipnodes = (q1_dclipnode_t *) GetMemory( Q1_MAX_MAP_CLIPNODES * sizeof( q1_dclipnode_t ) ); + q1_allocatedbspmem += Q1_MAX_MAP_CLIPNODES * sizeof( q1_dclipnode_t ); + //edges + q1_numedges = 0; + q1_dedges = (q1_dedge_t *) GetMemory( Q1_MAX_MAP_EDGES * sizeof( q1_dedge_t ) ); + q1_allocatedbspmem += Q1_MAX_MAP_EDGES, sizeof( q1_dedge_t ); + //mark surfaces + q1_nummarksurfaces = 0; + q1_dmarksurfaces = (unsigned short *) GetMemory( Q1_MAX_MAP_MARKSURFACES * sizeof( unsigned short ) ); + q1_allocatedbspmem += Q1_MAX_MAP_MARKSURFACES * sizeof( unsigned short ); + //surface edges + q1_numsurfedges = 0; + q1_dsurfedges = (int *) GetMemory( Q1_MAX_MAP_SURFEDGES * sizeof( int ) ); + q1_allocatedbspmem += Q1_MAX_MAP_SURFEDGES * sizeof( int ); + //print allocated memory + Log_Print( "allocated " ); + PrintMemorySize( q1_allocatedbspmem ); + Log_Print( " of BSP memory\n" ); +} //end of the function Q1_AllocMaxBSP + +void Q1_FreeMaxBSP( void ) { + //models + q1_nummodels = 0; + FreeMemory( q1_dmodels ); + q1_dmodels = NULL; + //visibility + q1_visdatasize = 0; + FreeMemory( q1_dvisdata ); + q1_dvisdata = NULL; + //light data + q1_lightdatasize = 0; + FreeMemory( q1_dlightdata ); + q1_dlightdata = NULL; + //texture data + q1_texdatasize = 0; + FreeMemory( q1_dtexdata ); + q1_dtexdata = NULL; + //entities + q1_entdatasize = 0; + FreeMemory( q1_dentdata ); + q1_dentdata = NULL; + //leaves + q1_numleafs = 0; + FreeMemory( q1_dleafs ); + q1_dleafs = NULL; + //planes + q1_numplanes = 0; + FreeMemory( q1_dplanes ); + q1_dplanes = NULL; + //vertexes + q1_numvertexes = 0; + FreeMemory( q1_dvertexes ); + q1_dvertexes = NULL; + //nodes + q1_numnodes = 0; + FreeMemory( q1_dnodes ); + q1_dnodes = NULL; + //texture info + q1_numtexinfo = 0; + FreeMemory( q1_texinfo ); + q1_texinfo = NULL; + //faces + q1_numfaces = 0; + FreeMemory( q1_dfaces ); + q1_dfaces = NULL; + //clip nodes + q1_numclipnodes = 0; + FreeMemory( q1_dclipnodes ); + q1_dclipnodes = NULL; + //edges + q1_numedges = 0; + FreeMemory( q1_dedges ); + q1_dedges = NULL; + //mark surfaces + q1_nummarksurfaces = 0; + FreeMemory( q1_dmarksurfaces ); + q1_dmarksurfaces = NULL; + //surface edges + q1_numsurfedges = 0; + FreeMemory( q1_dsurfedges ); + q1_dsurfedges = NULL; + // + Log_Print( "freed " ); + PrintMemorySize( q1_allocatedbspmem ); + Log_Print( " of BSP memory\n" ); + q1_allocatedbspmem = 0; +} //end of the function Q1_FreeMaxBSP +//#endif //ME + +/* +============= +Q1_SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void Q1_SwapBSPFile( qboolean todisk ) { + int i, j, c; + q1_dmodel_t *d; + q1_dmiptexlump_t *mtl; + + +// models + for ( i = 0 ; i < q1_nummodels ; i++ ) + { + d = &q1_dmodels[i]; + + for ( j = 0 ; j < Q1_MAX_MAP_HULLS ; j++ ) + d->headnode[j] = LittleLong( d->headnode[j] ); + + d->visleafs = LittleLong( d->visleafs ); + d->firstface = LittleLong( d->firstface ); + d->numfaces = LittleLong( d->numfaces ); + + for ( j = 0 ; j < 3 ; j++ ) + { + d->mins[j] = LittleFloat( d->mins[j] ); + d->maxs[j] = LittleFloat( d->maxs[j] ); + d->origin[j] = LittleFloat( d->origin[j] ); + } + } + +// +// vertexes +// + for ( i = 0 ; i < q1_numvertexes ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + q1_dvertexes[i].point[j] = LittleFloat( q1_dvertexes[i].point[j] ); + } + +// +// planes +// + for ( i = 0 ; i < q1_numplanes ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + q1_dplanes[i].normal[j] = LittleFloat( q1_dplanes[i].normal[j] ); + q1_dplanes[i].dist = LittleFloat( q1_dplanes[i].dist ); + q1_dplanes[i].type = LittleLong( q1_dplanes[i].type ); + } + +// +// texinfos +// + for ( i = 0 ; i < q1_numtexinfo ; i++ ) + { + for ( j = 0 ; j < 8 ; j++ ) + q1_texinfo[i].vecs[0][j] = LittleFloat( q1_texinfo[i].vecs[0][j] ); + q1_texinfo[i].miptex = LittleLong( q1_texinfo[i].miptex ); + q1_texinfo[i].flags = LittleLong( q1_texinfo[i].flags ); + } + +// +// faces +// + for ( i = 0 ; i < q1_numfaces ; i++ ) + { + q1_dfaces[i].texinfo = LittleShort( q1_dfaces[i].texinfo ); + q1_dfaces[i].planenum = LittleShort( q1_dfaces[i].planenum ); + q1_dfaces[i].side = LittleShort( q1_dfaces[i].side ); + q1_dfaces[i].lightofs = LittleLong( q1_dfaces[i].lightofs ); + q1_dfaces[i].firstedge = LittleLong( q1_dfaces[i].firstedge ); + q1_dfaces[i].numedges = LittleShort( q1_dfaces[i].numedges ); + } + +// +// nodes +// + for ( i = 0 ; i < q1_numnodes ; i++ ) + { + q1_dnodes[i].planenum = LittleLong( q1_dnodes[i].planenum ); + for ( j = 0 ; j < 3 ; j++ ) + { + q1_dnodes[i].mins[j] = LittleShort( q1_dnodes[i].mins[j] ); + q1_dnodes[i].maxs[j] = LittleShort( q1_dnodes[i].maxs[j] ); + } + q1_dnodes[i].children[0] = LittleShort( q1_dnodes[i].children[0] ); + q1_dnodes[i].children[1] = LittleShort( q1_dnodes[i].children[1] ); + q1_dnodes[i].firstface = LittleShort( q1_dnodes[i].firstface ); + q1_dnodes[i].numfaces = LittleShort( q1_dnodes[i].numfaces ); + } + +// +// leafs +// + for ( i = 0 ; i < q1_numleafs ; i++ ) + { + q1_dleafs[i].contents = LittleLong( q1_dleafs[i].contents ); + for ( j = 0 ; j < 3 ; j++ ) + { + q1_dleafs[i].mins[j] = LittleShort( q1_dleafs[i].mins[j] ); + q1_dleafs[i].maxs[j] = LittleShort( q1_dleafs[i].maxs[j] ); + } + + q1_dleafs[i].firstmarksurface = LittleShort( q1_dleafs[i].firstmarksurface ); + q1_dleafs[i].nummarksurfaces = LittleShort( q1_dleafs[i].nummarksurfaces ); + q1_dleafs[i].visofs = LittleLong( q1_dleafs[i].visofs ); + } + +// +// clipnodes +// + for ( i = 0 ; i < q1_numclipnodes ; i++ ) + { + q1_dclipnodes[i].planenum = LittleLong( q1_dclipnodes[i].planenum ); + q1_dclipnodes[i].children[0] = LittleShort( q1_dclipnodes[i].children[0] ); + q1_dclipnodes[i].children[1] = LittleShort( q1_dclipnodes[i].children[1] ); + } + +// +// miptex +// + if ( q1_texdatasize ) { + mtl = (q1_dmiptexlump_t *)q1_dtexdata; + if ( todisk ) { + c = mtl->nummiptex; + } else { + c = LittleLong( mtl->nummiptex ); + } + mtl->nummiptex = LittleLong( mtl->nummiptex ); + for ( i = 0 ; i < c ; i++ ) + mtl->dataofs[i] = LittleLong( mtl->dataofs[i] ); + } + +// +// marksurfaces +// + for ( i = 0 ; i < q1_nummarksurfaces ; i++ ) + q1_dmarksurfaces[i] = LittleShort( q1_dmarksurfaces[i] ); + +// +// surfedges +// + for ( i = 0 ; i < q1_numsurfedges ; i++ ) + q1_dsurfedges[i] = LittleLong( q1_dsurfedges[i] ); + +// +// edges +// + for ( i = 0 ; i < q1_numedges ; i++ ) + { + q1_dedges[i].v[0] = LittleShort( q1_dedges[i].v[0] ); + q1_dedges[i].v[1] = LittleShort( q1_dedges[i].v[1] ); + } +} + + +q1_dheader_t *q1_header; + +int Q1_CopyLump( int lump, void *dest, int size ) { + int length, ofs; + + length = q1_header->lumps[lump].filelen; + ofs = q1_header->lumps[lump].fileofs; + + if ( length % size ) { + Error( "Q1_LoadBSPFile: odd lump size" ); + } + + memcpy( dest, (byte *)q1_header + ofs, length ); + + return length / size; +} + +/* +============= +Q1_LoadBSPFile +============= +*/ +void Q1_LoadBSPFile( char *filename, int offset, int length ) { + int i; + +// +// load the file header +// + LoadFile( filename, (void **)&q1_header, offset, length ); + +// swap the header + for ( i = 0 ; i < sizeof( q1_dheader_t ) / 4 ; i++ ) + ( (int *)q1_header )[i] = LittleLong( ( (int *)q1_header )[i] ); + + if ( q1_header->version != Q1_BSPVERSION ) { + Error( "%s is version %i, not %i", filename, i, Q1_BSPVERSION ); + } + + q1_nummodels = Q1_CopyLump( Q1_LUMP_MODELS, q1_dmodels, sizeof( q1_dmodel_t ) ); + q1_numvertexes = Q1_CopyLump( Q1_LUMP_VERTEXES, q1_dvertexes, sizeof( q1_dvertex_t ) ); + q1_numplanes = Q1_CopyLump( Q1_LUMP_PLANES, q1_dplanes, sizeof( q1_dplane_t ) ); + q1_numleafs = Q1_CopyLump( Q1_LUMP_LEAFS, q1_dleafs, sizeof( q1_dleaf_t ) ); + q1_numnodes = Q1_CopyLump( Q1_LUMP_NODES, q1_dnodes, sizeof( q1_dnode_t ) ); + q1_numtexinfo = Q1_CopyLump( Q1_LUMP_TEXINFO, q1_texinfo, sizeof( q1_texinfo_t ) ); + q1_numclipnodes = Q1_CopyLump( Q1_LUMP_CLIPNODES, q1_dclipnodes, sizeof( q1_dclipnode_t ) ); + q1_numfaces = Q1_CopyLump( Q1_LUMP_FACES, q1_dfaces, sizeof( q1_dface_t ) ); + q1_nummarksurfaces = Q1_CopyLump( Q1_LUMP_MARKSURFACES, q1_dmarksurfaces, sizeof( q1_dmarksurfaces[0] ) ); + q1_numsurfedges = Q1_CopyLump( Q1_LUMP_SURFEDGES, q1_dsurfedges, sizeof( q1_dsurfedges[0] ) ); + q1_numedges = Q1_CopyLump( Q1_LUMP_EDGES, q1_dedges, sizeof( q1_dedge_t ) ); + + q1_texdatasize = Q1_CopyLump( Q1_LUMP_TEXTURES, q1_dtexdata, 1 ); + q1_visdatasize = Q1_CopyLump( Q1_LUMP_VISIBILITY, q1_dvisdata, 1 ); + q1_lightdatasize = Q1_CopyLump( Q1_LUMP_LIGHTING, q1_dlightdata, 1 ); + q1_entdatasize = Q1_CopyLump( Q1_LUMP_ENTITIES, q1_dentdata, 1 ); + + FreeMemory( q1_header ); // everything has been copied out + +// +// swap everything +// + Q1_SwapBSPFile( false ); +} + +//============================================================================ + +FILE *q1_wadfile; +q1_dheader_t q1_outheader; + +void Q1_AddLump( int lumpnum, void *data, int len ) { + q1_lump_t *lump; + + lump = &q1_header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell( q1_wadfile ) ); + lump->filelen = LittleLong( len ); + SafeWrite( q1_wadfile, data, ( len + 3 ) & ~3 ); +} + +/* +============= +Q1_WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void Q1_WriteBSPFile( char *filename ) { + q1_header = &q1_outheader; + memset( q1_header, 0, sizeof( q1_dheader_t ) ); + + Q1_SwapBSPFile( true ); + + q1_header->version = LittleLong( Q1_BSPVERSION ); + + q1_wadfile = SafeOpenWrite( filename ); + SafeWrite( q1_wadfile, q1_header, sizeof( q1_dheader_t ) ); // overwritten later + + Q1_AddLump( Q1_LUMP_PLANES, q1_dplanes, q1_numplanes * sizeof( q1_dplane_t ) ); + Q1_AddLump( Q1_LUMP_LEAFS, q1_dleafs, q1_numleafs * sizeof( q1_dleaf_t ) ); + Q1_AddLump( Q1_LUMP_VERTEXES, q1_dvertexes, q1_numvertexes * sizeof( q1_dvertex_t ) ); + Q1_AddLump( Q1_LUMP_NODES, q1_dnodes, q1_numnodes * sizeof( q1_dnode_t ) ); + Q1_AddLump( Q1_LUMP_TEXINFO, q1_texinfo, q1_numtexinfo * sizeof( q1_texinfo_t ) ); + Q1_AddLump( Q1_LUMP_FACES, q1_dfaces, q1_numfaces * sizeof( q1_dface_t ) ); + Q1_AddLump( Q1_LUMP_CLIPNODES, q1_dclipnodes, q1_numclipnodes * sizeof( q1_dclipnode_t ) ); + Q1_AddLump( Q1_LUMP_MARKSURFACES, q1_dmarksurfaces, q1_nummarksurfaces * sizeof( q1_dmarksurfaces[0] ) ); + Q1_AddLump( Q1_LUMP_SURFEDGES, q1_dsurfedges, q1_numsurfedges * sizeof( q1_dsurfedges[0] ) ); + Q1_AddLump( Q1_LUMP_EDGES, q1_dedges, q1_numedges * sizeof( q1_dedge_t ) ); + Q1_AddLump( Q1_LUMP_MODELS, q1_dmodels, q1_nummodels * sizeof( q1_dmodel_t ) ); + + Q1_AddLump( Q1_LUMP_LIGHTING, q1_dlightdata, q1_lightdatasize ); + Q1_AddLump( Q1_LUMP_VISIBILITY, q1_dvisdata, q1_visdatasize ); + Q1_AddLump( Q1_LUMP_ENTITIES, q1_dentdata, q1_entdatasize ); + Q1_AddLump( Q1_LUMP_TEXTURES, q1_dtexdata, q1_texdatasize ); + + fseek( q1_wadfile, 0, SEEK_SET ); + SafeWrite( q1_wadfile, q1_header, sizeof( q1_dheader_t ) ); + fclose( q1_wadfile ); +} + +//============================================================================ + +/* +============= +Q1_PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void Q1_PrintBSPFileSizes( void ) { + printf( "%5i planes %6i\n" + ,q1_numplanes, (int)( q1_numplanes * sizeof( q1_dplane_t ) ) ); + printf( "%5i vertexes %6i\n" + ,q1_numvertexes, (int)( q1_numvertexes * sizeof( q1_dvertex_t ) ) ); + printf( "%5i nodes %6i\n" + ,q1_numnodes, (int)( q1_numnodes * sizeof( q1_dnode_t ) ) ); + printf( "%5i texinfo %6i\n" + ,q1_numtexinfo, (int)( q1_numtexinfo * sizeof( q1_texinfo_t ) ) ); + printf( "%5i faces %6i\n" + ,q1_numfaces, (int)( q1_numfaces * sizeof( q1_dface_t ) ) ); + printf( "%5i clipnodes %6i\n" + ,q1_numclipnodes, (int)( q1_numclipnodes * sizeof( q1_dclipnode_t ) ) ); + printf( "%5i leafs %6i\n" + ,q1_numleafs, (int)( q1_numleafs * sizeof( q1_dleaf_t ) ) ); + printf( "%5i marksurfaces %6i\n" + ,q1_nummarksurfaces, (int)( q1_nummarksurfaces * sizeof( q1_dmarksurfaces[0] ) ) ); + printf( "%5i surfedges %6i\n" + ,q1_numsurfedges, (int)( q1_numsurfedges * sizeof( q1_dmarksurfaces[0] ) ) ); + printf( "%5i edges %6i\n" + ,q1_numedges, (int)( q1_numedges * sizeof( q1_dedge_t ) ) ); + if ( !q1_texdatasize ) { + printf( " 0 textures 0\n" ); + } else { + printf( "%5i textures %6i\n",( (q1_dmiptexlump_t*)q1_dtexdata )->nummiptex, q1_texdatasize ); + } + printf( " lightdata %6i\n", q1_lightdatasize ); + printf( " visdata %6i\n", q1_visdatasize ); + printf( " entdata %6i\n", q1_entdatasize ); +} //end of the function Q1_PrintBSPFileSizes + + +/* +================ +Q1_ParseEntities + +Parses the dentdata string into entities +================ +*/ +void Q1_ParseEntities( void ) { + script_t *script; + + num_entities = 0; + script = LoadScriptMemory( q1_dentdata, q1_entdatasize, "*Quake1 bsp file" ); + SetScriptFlags( script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS ); + + while ( ParseEntity( script ) ) + { + } //end while + + FreeScript( script ); +} //end of the function Q1_ParseEntities + + +/* +================ +Q1_UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void Q1_UnparseEntities( void ) { + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + + buf = q1_dentdata; + end = buf; + *end = 0; + + for ( i = 0 ; i < num_entities ; i++ ) + { + ep = entities[i].epairs; + if ( !ep ) { + continue; // ent got removed + + } + strcat( end,"{\n" ); + end += 2; + + for ( ep = entities[i].epairs ; ep ; ep = ep->next ) + { + sprintf( line, "\"%s\" \"%s\"\n", ep->key, ep->value ); + strcat( end, line ); + end += strlen( line ); + } + strcat( end,"}\n" ); + end += 2; + + if ( end > buf + Q1_MAX_MAP_ENTSTRING ) { + Error( "Entity text too long" ); + } + } + q1_entdatasize = end - buf + 1; +} //end of the function Q1_UnparseEntities diff --git a/src/bspc/l_bsp_q1.h b/src/bspc/l_bsp_q1.h new file mode 100644 index 0000000..aacd783 --- /dev/null +++ b/src/bspc/l_bsp_q1.h @@ -0,0 +1,282 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +// upper design bounds + +#define Q1_MAX_MAP_HULLS 4 + +#define Q1_MAX_MAP_MODELS 256 +#define Q1_MAX_MAP_BRUSHES 4096 +#define Q1_MAX_MAP_ENTITIES 1024 +#define Q1_MAX_MAP_ENTSTRING 65536 + +#define Q1_MAX_MAP_PLANES 8192 +#define Q1_MAX_MAP_NODES 32767 // because negative shorts are contents +#define Q1_MAX_MAP_CLIPNODES 32767 // +#define Q1_MAX_MAP_LEAFS 32767 // +#define Q1_MAX_MAP_VERTS 65535 +#define Q1_MAX_MAP_FACES 65535 +#define Q1_MAX_MAP_MARKSURFACES 65535 +#define Q1_MAX_MAP_TEXINFO 4096 +#define Q1_MAX_MAP_EDGES 256000 +#define Q1_MAX_MAP_SURFEDGES 512000 +#define Q1_MAX_MAP_MIPTEX 0x200000 +#define Q1_MAX_MAP_LIGHTING 0x100000 +#define Q1_MAX_MAP_VISIBILITY 0x100000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + + +#define Q1_BSPVERSION 29 + +typedef struct +{ + int fileofs, filelen; +} q1_lump_t; + +#define Q1_LUMP_ENTITIES 0 +#define Q1_LUMP_PLANES 1 +#define Q1_LUMP_TEXTURES 2 +#define Q1_LUMP_VERTEXES 3 +#define Q1_LUMP_VISIBILITY 4 +#define Q1_LUMP_NODES 5 +#define Q1_LUMP_TEXINFO 6 +#define Q1_LUMP_FACES 7 +#define Q1_LUMP_LIGHTING 8 +#define Q1_LUMP_CLIPNODES 9 +#define Q1_LUMP_LEAFS 10 +#define Q1_LUMP_MARKSURFACES 11 +#define Q1_LUMP_EDGES 12 +#define Q1_LUMP_SURFEDGES 13 +#define Q1_LUMP_MODELS 14 + +#define Q1_HEADER_LUMPS 15 + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[Q1_MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} q1_dmodel_t; + +typedef struct +{ + int version; + q1_lump_t lumps[Q1_HEADER_LUMPS]; +} q1_dheader_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} q1_dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct q1_miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} q1_miptex_t; + + +typedef struct +{ + float point[3]; +} q1_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 +} q1_dplane_t; + + + +#define Q1_CONTENTS_EMPTY -1 +#define Q1_CONTENTS_SOLID -2 +#define Q1_CONTENTS_WATER -3 +#define Q1_CONTENTS_SLIME -4 +#define Q1_CONTENTS_LAVA -5 +#define Q1_CONTENTS_SKY -6 + +// !!! 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 +} q1_dnode_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} q1_dclipnode_t; + + +typedef struct q1_texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} q1_texinfo_t; +#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} q1_dedge_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 +} q1_dface_t; + + + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic Q1_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]; +} q1_dleaf_t; + +//============================================================================ + +#ifndef QUAKE_GAME + +// the utilities get to be lazy and just use large static arrays + +extern int q1_nummodels; +extern q1_dmodel_t *q1_dmodels; //[MAX_MAP_MODELS]; + +extern int q1_visdatasize; +extern byte *q1_dvisdata; //[MAX_MAP_VISIBILITY]; + +extern int q1_lightdatasize; +extern byte *q1_dlightdata; //[MAX_MAP_LIGHTING]; + +extern int q1_texdatasize; +extern byte *q1_dtexdata; //[MAX_MAP_MIPTEX]; // (dmiptexlump_t) + +extern int q1_entdatasize; +extern char *q1_dentdata; //[MAX_MAP_ENTSTRING]; + +extern int q1_numleafs; +extern q1_dleaf_t *q1_dleafs; //[MAX_MAP_LEAFS]; + +extern int q1_numplanes; +extern q1_dplane_t *q1_dplanes; //[MAX_MAP_PLANES]; + +extern int q1_numvertexes; +extern q1_dvertex_t *q1_dvertexes; //[MAX_MAP_VERTS]; + +extern int q1_numnodes; +extern q1_dnode_t *q1_dnodes; //[MAX_MAP_NODES]; + +extern int q1_numtexinfo; +extern q1_texinfo_t *q1_texinfo; //[MAX_MAP_TEXINFO]; + +extern int q1_numfaces; +extern q1_dface_t *q1_dfaces; //[MAX_MAP_FACES]; + +extern int q1_numclipnodes; +extern q1_dclipnode_t *q1_dclipnodes; //[MAX_MAP_CLIPNODES]; + +extern int q1_numedges; +extern q1_dedge_t *q1_dedges; //[MAX_MAP_EDGES]; + +extern int q1_nummarksurfaces; +extern unsigned short *q1_dmarksurfaces; //[MAX_MAP_MARKSURFACES]; + +extern int q1_numsurfedges; +extern int *q1_dsurfedges; //[MAX_MAP_SURFEDGES]; + + +void Q1_AllocMaxBSP( void ); +void Q1_FreeMaxBSP( void ); +void Q1_LoadBSPFile( char *filename, int offset, int length ); +void Q1_WriteBSPFile( char *filename ); +void Q1_PrintBSPFileSizes( void ); +void Q1_ParseEntities( void ); +void Q1_UnparseEntities( void ); + +#endif diff --git a/src/bspc/l_bsp_q2.c b/src/bspc/l_bsp_q2.c new file mode 100644 index 0000000..ac85dec --- /dev/null +++ b/src/bspc/l_bsp_q2.c @@ -0,0 +1,1137 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "l_poly.h" +#include "../botlib/l_script.h" +#include "q2files.h" +#include "l_bsp_q2.h" +#include "l_bsp_ent.h" + +#define q2_dmodel_t dmodel_t +#define q2_lump_t lump_t +#define q2_dheader_t dheader_t +#define q2_dmodel_t dmodel_t +#define q2_dvertex_t dvertex_t +#define q2_dplane_t dplane_t +#define q2_dnode_t dnode_t +#define q2_texinfo_t texinfo_t +#define q2_dedge_t dedge_t +#define q2_dface_t dface_t +#define q2_dleaf_t dleaf_t +#define q2_dbrushside_t dbrushside_t +#define q2_dbrush_t dbrush_t +#define q2_dvis_t dvis_t +#define q2_dareaportal_t dareaportal_t +#define q2_darea_t darea_t + +#define q2_nummodels nummodels +#define q2_dmodels dmodels +#define q2_numleafs numleafs +#define q2_dleafs dleafs +#define q2_numplanes numplanes +#define q2_dplanes dplanes +#define q2_numvertexes numvertexes +#define q2_dvertexes dvertexes +#define q2_numnodes numnodes +#define q2_dnodes dnodes +#define q2_numtexinfo numtexinfo +#define q2_texinfo texinfo +#define q2_numfaces numfaces +#define q2_dfaces dfaces +#define q2_numedges numedges +#define q2_dedges dedges +#define q2_numleaffaces numleaffaces +#define q2_dleaffaces dleaffaces +#define q2_numleafbrushes numleafbrushes +#define q2_dleafbrushes dleafbrushes +#define q2_dsurfedges dsurfedges +#define q2_numbrushes numbrushes +#define q2_dbrushes dbrushes +#define q2_numbrushsides numbrushsides +#define q2_dbrushsides dbrushsides +#define q2_numareas numareas +#define q2_dareas dareas +#define q2_numareaportals numareaportals +#define q2_dareaportals dareaportals + +void GetLeafNums( void ); + +//============================================================================= + +int nummodels; +dmodel_t *dmodels; //[MAX_MAP_MODELS]; + +int visdatasize; +byte *dvisdata; //[MAX_MAP_VISIBILITY]; +dvis_t *dvis; // = (dvis_t *)dvisdata; + +int lightdatasize; +byte *dlightdata; //[MAX_MAP_LIGHTING]; + +int entdatasize; +char *dentdata; //[MAX_MAP_ENTSTRING]; + +int numleafs; +dleaf_t *dleafs; //[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t *dplanes; //[MAX_MAP_PLANES]; + +int numvertexes; +dvertex_t *dvertexes; //[MAX_MAP_VERTS]; + +int numnodes; +dnode_t *dnodes; //[MAX_MAP_NODES]; + +//NOTE: must be static for q2 .map to q2 .bsp +int numtexinfo; +texinfo_t texinfo[MAX_MAP_TEXINFO]; + +int numfaces; +dface_t *dfaces; //[MAX_MAP_FACES]; + +int numedges; +dedge_t *dedges; //[MAX_MAP_EDGES]; + +int numleaffaces; +unsigned short *dleaffaces; //[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +unsigned short *dleafbrushes; //[MAX_MAP_LEAFBRUSHES]; + +int numsurfedges; +int *dsurfedges; //[MAX_MAP_SURFEDGES]; + +int numbrushes; +dbrush_t *dbrushes; //[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t *dbrushsides; //[MAX_MAP_BRUSHSIDES]; + +int numareas; +darea_t *dareas; //[MAX_MAP_AREAS]; + +int numareaportals; +dareaportal_t *dareaportals; //[MAX_MAP_AREAPORTALS]; + +#define MAX_MAP_DPOP 256 +byte dpop[MAX_MAP_DPOP]; + +// +char brushsidetextured[MAX_MAP_BRUSHSIDES]; + +//#ifdef ME + +int bspallocated = false; +int allocatedbspmem = 0; + +void Q2_AllocMaxBSP( void ) { + //models + nummodels = 0; + dmodels = (dmodel_t *) GetClearedMemory( MAX_MAP_MODELS * sizeof( dmodel_t ) ); + allocatedbspmem += MAX_MAP_MODELS * sizeof( dmodel_t ); + //vis data + visdatasize = 0; + dvisdata = (byte *) GetClearedMemory( MAX_MAP_VISIBILITY * sizeof( byte ) ); + dvis = (dvis_t *) dvisdata; + allocatedbspmem += MAX_MAP_VISIBILITY * sizeof( byte ); + //light data + lightdatasize = 0; + dlightdata = (byte *) GetClearedMemory( MAX_MAP_LIGHTING * sizeof( byte ) ); + allocatedbspmem += MAX_MAP_LIGHTING * sizeof( byte ); + //entity data + entdatasize = 0; + dentdata = (char *) GetClearedMemory( MAX_MAP_ENTSTRING * sizeof( char ) ); + allocatedbspmem += MAX_MAP_ENTSTRING * sizeof( char ); + //leafs + numleafs = 0; + dleafs = (dleaf_t *) GetClearedMemory( MAX_MAP_LEAFS * sizeof( dleaf_t ) ); + allocatedbspmem += MAX_MAP_LEAFS * sizeof( dleaf_t ); + //planes + numplanes = 0; + dplanes = (dplane_t *) GetClearedMemory( MAX_MAP_PLANES * sizeof( dplane_t ) ); + allocatedbspmem += MAX_MAP_PLANES * sizeof( dplane_t ); + //vertexes + numvertexes = 0; + dvertexes = (dvertex_t *) GetClearedMemory( MAX_MAP_VERTS * sizeof( dvertex_t ) ); + allocatedbspmem += MAX_MAP_VERTS * sizeof( dvertex_t ); + //nodes + numnodes = 0; + dnodes = (dnode_t *) GetClearedMemory( MAX_MAP_NODES * sizeof( dnode_t ) ); + allocatedbspmem += MAX_MAP_NODES * sizeof( dnode_t ); + /* + //texture info + numtexinfo = 0; + texinfo = (texinfo_t *) GetClearedMemory(MAX_MAP_TEXINFO * sizeof(texinfo_t)); + allocatedbspmem += MAX_MAP_TEXINFO * sizeof(texinfo_t); + //*/ + //faces + numfaces = 0; + dfaces = (dface_t *) GetClearedMemory( MAX_MAP_FACES * sizeof( dface_t ) ); + allocatedbspmem += MAX_MAP_FACES * sizeof( dface_t ); + //edges + numedges = 0; + dedges = (dedge_t *) GetClearedMemory( MAX_MAP_EDGES * sizeof( dedge_t ) ); + allocatedbspmem += MAX_MAP_EDGES * sizeof( dedge_t ); + //leaf faces + numleaffaces = 0; + dleaffaces = (unsigned short *) GetClearedMemory( MAX_MAP_LEAFFACES * sizeof( unsigned short ) ); + allocatedbspmem += MAX_MAP_LEAFFACES * sizeof( unsigned short ); + //leaf brushes + numleafbrushes = 0; + dleafbrushes = (unsigned short *) GetClearedMemory( MAX_MAP_LEAFBRUSHES * sizeof( unsigned short ) ); + allocatedbspmem += MAX_MAP_LEAFBRUSHES * sizeof( unsigned short ); + //surface edges + numsurfedges = 0; + dsurfedges = (int *) GetClearedMemory( MAX_MAP_SURFEDGES * sizeof( int ) ); + allocatedbspmem += MAX_MAP_SURFEDGES * sizeof( int ); + //brushes + numbrushes = 0; + dbrushes = (dbrush_t *) GetClearedMemory( MAX_MAP_BRUSHES * sizeof( dbrush_t ) ); + allocatedbspmem += MAX_MAP_BRUSHES * sizeof( dbrush_t ); + //brushsides + numbrushsides = 0; + dbrushsides = (dbrushside_t *) GetClearedMemory( MAX_MAP_BRUSHSIDES * sizeof( dbrushside_t ) ); + allocatedbspmem += MAX_MAP_BRUSHSIDES * sizeof( dbrushside_t ); + //areas + numareas = 0; + dareas = (darea_t *) GetClearedMemory( MAX_MAP_AREAS * sizeof( darea_t ) ); + allocatedbspmem += MAX_MAP_AREAS * sizeof( darea_t ); + //area portals + numareaportals = 0; + dareaportals = (dareaportal_t *) GetClearedMemory( MAX_MAP_AREAPORTALS * sizeof( dareaportal_t ) ); + allocatedbspmem += MAX_MAP_AREAPORTALS * sizeof( dareaportal_t ); + //print allocated memory + Log_Print( "allocated " ); + PrintMemorySize( allocatedbspmem ); + Log_Print( " of BSP memory\n" ); +} //end of the function Q2_AllocMaxBSP + +void Q2_FreeMaxBSP( void ) { + //models + nummodels = 0; + FreeMemory( dmodels ); + dmodels = NULL; + //vis data + visdatasize = 0; + FreeMemory( dvisdata ); + dvisdata = NULL; + dvis = NULL; + //light data + lightdatasize = 0; + FreeMemory( dlightdata ); + dlightdata = NULL; + //entity data + entdatasize = 0; + FreeMemory( dentdata ); + dentdata = NULL; + //leafs + numleafs = 0; + FreeMemory( dleafs ); + dleafs = NULL; + //planes + numplanes = 0; + FreeMemory( dplanes ); + dplanes = NULL; + //vertexes + numvertexes = 0; + FreeMemory( dvertexes ); + dvertexes = NULL; + //nodes + numnodes = 0; + FreeMemory( dnodes ); + dnodes = NULL; + /* + //texture info + numtexinfo = 0; + FreeMemory(texinfo); + texinfo = NULL; + //*/ + //faces + numfaces = 0; + FreeMemory( dfaces ); + dfaces = NULL; + //edges + numedges = 0; + FreeMemory( dedges ); + dedges = NULL; + //leaf faces + numleaffaces = 0; + FreeMemory( dleaffaces ); + dleaffaces = NULL; + //leaf brushes + numleafbrushes = 0; + FreeMemory( dleafbrushes ); + dleafbrushes = NULL; + //surface edges + numsurfedges = 0; + FreeMemory( dsurfedges ); + dsurfedges = NULL; + //brushes + numbrushes = 0; + FreeMemory( dbrushes ); + dbrushes = NULL; + //brushsides + numbrushsides = 0; + FreeMemory( dbrushsides ); + dbrushsides = NULL; + //areas + numareas = 0; + FreeMemory( dareas ); + dareas = NULL; + //area portals + numareaportals = 0; + FreeMemory( dareaportals ); + dareaportals = NULL; + // + Log_Print( "freed " ); + PrintMemorySize( allocatedbspmem ); + Log_Print( " of BSP memory\n" ); + allocatedbspmem = 0; +} //end of the function Q2_FreeMaxBSP + +#define WCONVEX_EPSILON 0.5 + +int InsideWinding( winding_t *w, vec3_t point, int planenum ) { + int i; + float dist; + vec_t *v1, *v2; + vec3_t normal, edgevec; + dplane_t *plane; + + for ( i = 1; i <= w->numpoints; i++ ) + { + v1 = w->p[i % w->numpoints]; + v2 = w->p[( i + 1 ) % w->numpoints]; + + VectorSubtract( v2, v1, edgevec ); + plane = &dplanes[planenum]; + CrossProduct( plane->normal, edgevec, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, v1 ); + // + if ( DotProduct( normal, point ) - dist > WCONVEX_EPSILON ) { + return false; + } + } //end for + return true; +} //end of the function InsideWinding + +int InsideFace( dface_t *face, vec3_t point ) { + int i, edgenum, side; + float dist; + vec_t *v1, *v2; + vec3_t normal, edgevec; + dplane_t *plane; + + for ( i = 0; i < face->numedges; i++ ) + { + //get the first and second vertex of the edge + edgenum = dsurfedges[face->firstedge + i]; + side = edgenum < 0; + v1 = dvertexes[dedges[abs( edgenum )].v[side]].point; + v2 = dvertexes[dedges[abs( edgenum )].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract( v1, v2, edgevec ); + plane = &dplanes[face->planenum]; + CrossProduct( plane->normal, edgevec, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, v1 ); + // + if ( DotProduct( normal, point ) - dist > WCONVEX_EPSILON ) { + return false; + } + } //end for + return true; +} //end of the function InsideFace +//=========================================================================== +// returns the amount the face and the winding overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Q2_FaceOnWinding( q2_dface_t *face, winding_t *winding ) { + int i, edgenum, side; + float dist, area; + q2_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + // + w = CopyWinding( winding ); + memcpy( &plane, &q2_dplanes[face->planenum], sizeof( q2_dplane_t ) ); + //check on which side of the plane the face is + if ( face->side ) { + VectorNegate( plane.normal, plane.normal ); + plane.dist = -plane.dist; + } //end if + for ( i = 0; i < face->numedges && w; i++ ) + { + //get the first and second vertex of the edge + edgenum = q2_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = q2_dvertexes[q2_dedges[abs( edgenum )].v[side]].point; + v2 = q2_dvertexes[q2_dedges[abs( edgenum )].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing inward + VectorSubtract( v1, v2, edgevec ); + CrossProduct( edgevec, plane.normal, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, v1 ); + // + ChopWindingInPlace( &w, normal, dist, -0.1 ); //CLIP_EPSILON + } //end for + if ( w ) { + area = WindingArea( w ); + FreeWinding( w ); + return area; + } //end if + return 0; +} //end of the function Q2_FaceOnWinding +//=========================================================================== +// creates a winding for the given brush side on the given brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *Q2_BrushSideWinding( dbrush_t *brush, dbrushside_t *baseside ) { + int i; + dplane_t *baseplane, *plane; + winding_t *w; + dbrushside_t *side; + + //create a winding for the brush side with the given planenumber + baseplane = &dplanes[baseside->planenum]; + w = BaseWindingForPlane( baseplane->normal, baseplane->dist ); + for ( i = 0; i < brush->numsides && w; i++ ) + { + side = &dbrushsides[brush->firstside + i]; + //don't chop with the base plane + if ( side->planenum == baseside->planenum ) { + continue; + } + //also don't use planes that are almost equal + plane = &dplanes[side->planenum]; + if ( DotProduct( baseplane->normal, plane->normal ) > 0.999 + && fabs( baseplane->dist - plane->dist ) < 0.01 ) { + continue; + } + // + plane = &dplanes[side->planenum ^ 1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, -0.1 ); //CLIP_EPSILON); + } //end for + return w; +} //end of the function Q2_BrushSideWinding +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Q2_HintSkipBrush( dbrush_t *brush ) { + int j; + dbrushside_t *brushside; + + for ( j = 0; j < brush->numsides; j++ ) + { + brushside = &dbrushsides[brush->firstside + j]; + if ( brushside->texinfo > 0 ) { + if ( texinfo[brushside->texinfo].flags & ( SURF_SKIP | SURF_HINT ) ) { + return true; + } //end if + } //end if + } //end for + return false; +} //end of the function Q2_HintSkipBrush +//=========================================================================== +// fix screwed brush texture references +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsTiny( winding_t *w ); + +void Q2_FixTextureReferences( void ) { + int i, j, k, we; + dbrushside_t *brushside; + dbrush_t *brush; + dface_t *face; + winding_t *w; + + memset( brushsidetextured, false, MAX_MAP_BRUSHSIDES ); + //go over all the brushes + for ( i = 0; i < numbrushes; i++ ) + { + brush = &dbrushes[i]; + //hint brushes are not textured + if ( Q2_HintSkipBrush( brush ) ) { + continue; + } + //go over all the sides of the brush + for ( j = 0; j < brush->numsides; j++ ) + { + brushside = &dbrushsides[brush->firstside + j]; + // + w = Q2_BrushSideWinding( brush, brushside ); + if ( !w ) { + brushsidetextured[brush->firstside + j] = true; + continue; + } //end if + else + { + //RemoveEqualPoints(w, 0.2); + if ( WindingIsTiny( w ) ) { + FreeWinding( w ); + brushsidetextured[brush->firstside + j] = true; + continue; + } //end if + else + { + we = WindingError( w ); + if ( we == WE_NOTENOUGHPOINTS + || we == WE_SMALLAREA + || we == WE_POINTBOGUSRANGE +// || we == WE_NONCONVEX + ) { + FreeWinding( w ); + brushsidetextured[brush->firstside + j] = true; + continue; + } //end if + } //end else + } //end else + if ( WindingArea( w ) < 20 ) { + brushsidetextured[brush->firstside + j] = true; + } //end if + //find a face for texturing this brush + for ( k = 0; k < numfaces; k++ ) + { + face = &dfaces[k]; + //if the face is in the same plane as the brush side + if ( ( face->planenum & ~1 ) != ( brushside->planenum & ~1 ) ) { + continue; + } + //if the face is partly or totally on the brush side + if ( Q2_FaceOnWinding( face, w ) ) { + brushside->texinfo = face->texinfo; + brushsidetextured[brush->firstside + j] = true; + break; + } //end if + } //end for + FreeWinding( w ); + } //end for + } //end for +} //end of the function Q2_FixTextureReferences*/ + +//#endif //ME + + +/* +=============== +CompressVis + +=============== +*/ +int Q2_CompressVis( byte *vis, byte *dest ) { + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = ( dvis->numclusters + 7 ) >> 3; + + for ( j = 0 ; j < visrow ; j++ ) + { + *dest_p++ = vis[j]; + if ( vis[j] ) { + continue; + } + + rep = 1; + for ( j++; j < visrow ; j++ ) + if ( vis[j] || rep == 255 ) { + break; + } else { + rep++; + } + *dest_p++ = rep; + j--; + } + + return dest_p - dest; +} + + +/* +=================== +DecompressVis +=================== +*/ +void Q2_DecompressVis( byte *in, byte *decompressed ) { + int c; + byte *out; + int row; + +// row = (r_numvisleafs+7)>>3; + row = ( dvis->numclusters + 7 ) >> 3; + out = decompressed; + + do + { + if ( *in ) { + *out++ = *in++; + continue; + } + + c = in[1]; + if ( !c ) { + Error( "DecompressVis: 0 repeat" ); + } + in += 2; + while ( c ) + { + *out++ = 0; + c--; + } + } while ( out - decompressed < row ); +} + +//============================================================================= + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void Q2_SwapBSPFile( qboolean todisk ) { + int i, j; + dmodel_t *d; + + +// models + for ( i = 0 ; i < nummodels ; i++ ) + { + d = &dmodels[i]; + + d->firstface = LittleLong( d->firstface ); + d->numfaces = LittleLong( d->numfaces ); + d->headnode = LittleLong( d->headnode ); + + for ( j = 0 ; j < 3 ; j++ ) + { + d->mins[j] = LittleFloat( d->mins[j] ); + d->maxs[j] = LittleFloat( d->maxs[j] ); + d->origin[j] = LittleFloat( d->origin[j] ); + } + } + +// +// vertexes +// + for ( i = 0 ; i < numvertexes ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + dvertexes[i].point[j] = LittleFloat( dvertexes[i].point[j] ); + } + +// +// planes +// + for ( i = 0 ; i < numplanes ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + dplanes[i].normal[j] = LittleFloat( dplanes[i].normal[j] ); + dplanes[i].dist = LittleFloat( dplanes[i].dist ); + dplanes[i].type = LittleLong( dplanes[i].type ); + } + +// +// texinfos +// + for ( i = 0 ; i < numtexinfo ; i++ ) + { + for ( j = 0 ; j < 8 ; j++ ) + texinfo[i].vecs[0][j] = LittleFloat( texinfo[i].vecs[0][j] ); + texinfo[i].flags = LittleLong( texinfo[i].flags ); + texinfo[i].value = LittleLong( texinfo[i].value ); + texinfo[i].nexttexinfo = LittleLong( texinfo[i].nexttexinfo ); + } + +// +// faces +// + for ( i = 0 ; i < numfaces ; i++ ) + { + dfaces[i].texinfo = LittleShort( dfaces[i].texinfo ); + dfaces[i].planenum = LittleShort( dfaces[i].planenum ); + dfaces[i].side = LittleShort( dfaces[i].side ); + dfaces[i].lightofs = LittleLong( dfaces[i].lightofs ); + dfaces[i].firstedge = LittleLong( dfaces[i].firstedge ); + dfaces[i].numedges = LittleShort( dfaces[i].numedges ); + } + +// +// nodes +// + for ( i = 0 ; i < numnodes ; i++ ) + { + dnodes[i].planenum = LittleLong( dnodes[i].planenum ); + for ( j = 0 ; j < 3 ; j++ ) + { + dnodes[i].mins[j] = LittleShort( dnodes[i].mins[j] ); + dnodes[i].maxs[j] = LittleShort( dnodes[i].maxs[j] ); + } + dnodes[i].children[0] = LittleLong( dnodes[i].children[0] ); + dnodes[i].children[1] = LittleLong( dnodes[i].children[1] ); + dnodes[i].firstface = LittleShort( dnodes[i].firstface ); + dnodes[i].numfaces = LittleShort( dnodes[i].numfaces ); + } + +// +// leafs +// + for ( i = 0 ; i < numleafs ; i++ ) + { + dleafs[i].contents = LittleLong( dleafs[i].contents ); + dleafs[i].cluster = LittleShort( dleafs[i].cluster ); + dleafs[i].area = LittleShort( dleafs[i].area ); + for ( j = 0 ; j < 3 ; j++ ) + { + dleafs[i].mins[j] = LittleShort( dleafs[i].mins[j] ); + dleafs[i].maxs[j] = LittleShort( dleafs[i].maxs[j] ); + } + + dleafs[i].firstleafface = LittleShort( dleafs[i].firstleafface ); + dleafs[i].numleaffaces = LittleShort( dleafs[i].numleaffaces ); + dleafs[i].firstleafbrush = LittleShort( dleafs[i].firstleafbrush ); + dleafs[i].numleafbrushes = LittleShort( dleafs[i].numleafbrushes ); + } + +// +// leaffaces +// + for ( i = 0 ; i < numleaffaces ; i++ ) + dleaffaces[i] = LittleShort( dleaffaces[i] ); + +// +// leafbrushes +// + for ( i = 0 ; i < numleafbrushes ; i++ ) + dleafbrushes[i] = LittleShort( dleafbrushes[i] ); + +// +// surfedges +// + for ( i = 0 ; i < numsurfedges ; i++ ) + dsurfedges[i] = LittleLong( dsurfedges[i] ); + +// +// edges +// + for ( i = 0 ; i < numedges ; i++ ) + { + dedges[i].v[0] = LittleShort( dedges[i].v[0] ); + dedges[i].v[1] = LittleShort( dedges[i].v[1] ); + } + +// +// brushes +// + for ( i = 0 ; i < numbrushes ; i++ ) + { + dbrushes[i].firstside = LittleLong( dbrushes[i].firstside ); + dbrushes[i].numsides = LittleLong( dbrushes[i].numsides ); + dbrushes[i].contents = LittleLong( dbrushes[i].contents ); + } + +// +// areas +// + for ( i = 0 ; i < numareas ; i++ ) + { + dareas[i].numareaportals = LittleLong( dareas[i].numareaportals ); + dareas[i].firstareaportal = LittleLong( dareas[i].firstareaportal ); + } + +// +// areasportals +// + for ( i = 0 ; i < numareaportals ; i++ ) + { + dareaportals[i].portalnum = LittleLong( dareaportals[i].portalnum ); + dareaportals[i].otherarea = LittleLong( dareaportals[i].otherarea ); + } + +// +// brushsides +// + for ( i = 0 ; i < numbrushsides ; i++ ) + { + dbrushsides[i].planenum = LittleShort( dbrushsides[i].planenum ); + dbrushsides[i].texinfo = LittleShort( dbrushsides[i].texinfo ); + } + +// +// visibility +// + if ( todisk ) { + j = dvis->numclusters; + } else { + j = LittleLong( dvis->numclusters ); + } + dvis->numclusters = LittleLong( dvis->numclusters ); + for ( i = 0 ; i < j ; i++ ) + { + dvis->bitofs[i][0] = LittleLong( dvis->bitofs[i][0] ); + dvis->bitofs[i][1] = LittleLong( dvis->bitofs[i][1] ); + } +} //end of the function Q2_SwapBSPFile + + +dheader_t *header; + +int Q2_CopyLump( int lump, void *dest, int size, int maxsize ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if ( length % size ) { + Error( "LoadBSPFile: odd lump size" ); + } + + if ( ( length / size ) > maxsize ) { + Error( "Q2_LoadBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lump, ( length / size ), maxsize ); + } + + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} //end of the function Q2_CopyLump + +/* +============= +LoadBSPFile +============= +*/ +void Q2_LoadBSPFile( char *filename, int offset, int length ) { + int i; + +// +// load the file header +// + LoadFile( filename, (void **)&header, offset, length ); + +// swap the header + for ( i = 0 ; i < sizeof( dheader_t ) / 4 ; i++ ) + ( (int *)header )[i] = LittleLong( ( (int *)header )[i] ); + + if ( header->ident != IDBSPHEADER ) { + Error( "%s is not a IBSP file", filename ); + } + if ( header->version != BSPVERSION ) { + Error( "%s is version %i, not %i", filename, header->version, BSPVERSION ); + } + + nummodels = Q2_CopyLump( LUMP_MODELS, dmodels, sizeof( dmodel_t ), MAX_MAP_MODELS ); + numvertexes = Q2_CopyLump( LUMP_VERTEXES, dvertexes, sizeof( dvertex_t ), MAX_MAP_VERTS ); + numplanes = Q2_CopyLump( LUMP_PLANES, dplanes, sizeof( dplane_t ), MAX_MAP_PLANES ); + numleafs = Q2_CopyLump( LUMP_LEAFS, dleafs, sizeof( dleaf_t ), MAX_MAP_LEAFS ); + numnodes = Q2_CopyLump( LUMP_NODES, dnodes, sizeof( dnode_t ), MAX_MAP_NODES ); + numtexinfo = Q2_CopyLump( LUMP_TEXINFO, texinfo, sizeof( texinfo_t ), MAX_MAP_TEXINFO ); + numfaces = Q2_CopyLump( LUMP_FACES, dfaces, sizeof( dface_t ), MAX_MAP_FACES ); + numleaffaces = Q2_CopyLump( LUMP_LEAFFACES, dleaffaces, sizeof( dleaffaces[0] ), MAX_MAP_LEAFFACES ); + numleafbrushes = Q2_CopyLump( LUMP_LEAFBRUSHES, dleafbrushes, sizeof( dleafbrushes[0] ), MAX_MAP_LEAFBRUSHES ); + numsurfedges = Q2_CopyLump( LUMP_SURFEDGES, dsurfedges, sizeof( dsurfedges[0] ), MAX_MAP_SURFEDGES ); + numedges = Q2_CopyLump( LUMP_EDGES, dedges, sizeof( dedge_t ), MAX_MAP_EDGES ); + numbrushes = Q2_CopyLump( LUMP_BRUSHES, dbrushes, sizeof( dbrush_t ), MAX_MAP_BRUSHES ); + numbrushsides = Q2_CopyLump( LUMP_BRUSHSIDES, dbrushsides, sizeof( dbrushside_t ), MAX_MAP_BRUSHSIDES ); + numareas = Q2_CopyLump( LUMP_AREAS, dareas, sizeof( darea_t ), MAX_MAP_AREAS ); + numareaportals = Q2_CopyLump( LUMP_AREAPORTALS, dareaportals, sizeof( dareaportal_t ), MAX_MAP_AREAPORTALS ); + + visdatasize = Q2_CopyLump( LUMP_VISIBILITY, dvisdata, 1, MAX_MAP_VISIBILITY ); + lightdatasize = Q2_CopyLump( LUMP_LIGHTING, dlightdata, 1, MAX_MAP_LIGHTING ); + entdatasize = Q2_CopyLump( LUMP_ENTITIES, dentdata, 1, MAX_MAP_ENTSTRING ); + + Q2_CopyLump( LUMP_POP, dpop, 1, MAX_MAP_DPOP ); + + FreeMemory( header ); // everything has been copied out + +// +// swap everything +// + Q2_SwapBSPFile( false ); + + Q2_FixTextureReferences(); +} //end of the function Q2_LoadBSPFile + + +/* +============= +LoadBSPFileTexinfo + +Only loads the texinfo lump, so qdata can scan for textures +============= +*/ +void Q2_LoadBSPFileTexinfo( char *filename ) { + int i; + FILE *f; + int length, ofs; + + header = GetMemory( sizeof( dheader_t ) ); + + f = fopen( filename, "rb" ); + fread( header, sizeof( dheader_t ), 1, f ); + +// swap the header + for ( i = 0 ; i < sizeof( dheader_t ) / 4 ; i++ ) + ( (int *)header )[i] = LittleLong( ( (int *)header )[i] ); + + if ( header->ident != IDBSPHEADER ) { + Error( "%s is not a IBSP file", filename ); + } + if ( header->version != BSPVERSION ) { + Error( "%s is version %i, not %i", filename, header->version, BSPVERSION ); + } + + + length = header->lumps[LUMP_TEXINFO].filelen; + ofs = header->lumps[LUMP_TEXINFO].fileofs; + + fseek( f, ofs, SEEK_SET ); + fread( texinfo, length, 1, f ); + fclose( f ); + + numtexinfo = length / sizeof( texinfo_t ); + + FreeMemory( header ); // everything has been copied out + + Q2_SwapBSPFile( false ); +} //end of the function Q2_LoadBSPFileTexinfo + + +//============================================================================ + +FILE *wadfile; +dheader_t outheader; + +void Q2_AddLump( int lumpnum, void *data, int len ) { + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell( wadfile ) ); + lump->filelen = LittleLong( len ); + SafeWrite( wadfile, data, ( len + 3 ) & ~3 ); +} //end of the function Q2_AddLump + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void Q2_WriteBSPFile( char *filename ) { + header = &outheader; + memset( header, 0, sizeof( dheader_t ) ); + + Q2_SwapBSPFile( true ); + + header->ident = LittleLong( IDBSPHEADER ); + header->version = LittleLong( BSPVERSION ); + + wadfile = SafeOpenWrite( filename ); + SafeWrite( wadfile, header, sizeof( dheader_t ) ); // overwritten later + + Q2_AddLump( LUMP_PLANES, dplanes, numplanes * sizeof( dplane_t ) ); + Q2_AddLump( LUMP_LEAFS, dleafs, numleafs * sizeof( dleaf_t ) ); + Q2_AddLump( LUMP_VERTEXES, dvertexes, numvertexes * sizeof( dvertex_t ) ); + Q2_AddLump( LUMP_NODES, dnodes, numnodes * sizeof( dnode_t ) ); + Q2_AddLump( LUMP_TEXINFO, texinfo, numtexinfo * sizeof( texinfo_t ) ); + Q2_AddLump( LUMP_FACES, dfaces, numfaces * sizeof( dface_t ) ); + Q2_AddLump( LUMP_BRUSHES, dbrushes, numbrushes * sizeof( dbrush_t ) ); + Q2_AddLump( LUMP_BRUSHSIDES, dbrushsides, numbrushsides * sizeof( dbrushside_t ) ); + Q2_AddLump( LUMP_LEAFFACES, dleaffaces, numleaffaces * sizeof( dleaffaces[0] ) ); + Q2_AddLump( LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes * sizeof( dleafbrushes[0] ) ); + Q2_AddLump( LUMP_SURFEDGES, dsurfedges, numsurfedges * sizeof( dsurfedges[0] ) ); + Q2_AddLump( LUMP_EDGES, dedges, numedges * sizeof( dedge_t ) ); + Q2_AddLump( LUMP_MODELS, dmodels, nummodels * sizeof( dmodel_t ) ); + Q2_AddLump( LUMP_AREAS, dareas, numareas * sizeof( darea_t ) ); + Q2_AddLump( LUMP_AREAPORTALS, dareaportals, numareaportals * sizeof( dareaportal_t ) ); + + Q2_AddLump( LUMP_LIGHTING, dlightdata, lightdatasize ); + Q2_AddLump( LUMP_VISIBILITY, dvisdata, visdatasize ); + Q2_AddLump( LUMP_ENTITIES, dentdata, entdatasize ); + Q2_AddLump( LUMP_POP, dpop, sizeof( dpop ) ); + + fseek( wadfile, 0, SEEK_SET ); + SafeWrite( wadfile, header, sizeof( dheader_t ) ); + fclose( wadfile ); +} //end of the function Q2_WriteBSPFile + +//============================================================================ + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void Q2_PrintBSPFileSizes( void ) { + if ( !num_entities ) { + Q2_ParseEntities(); + } + + printf( "%6i models %7i\n" + ,nummodels, (int)( nummodels * sizeof( dmodel_t ) ) ); + printf( "%6i brushes %7i\n" + ,numbrushes, (int)( numbrushes * sizeof( dbrush_t ) ) ); + printf( "%6i brushsides %7i\n" + ,numbrushsides, (int)( numbrushsides * sizeof( dbrushside_t ) ) ); + printf( "%6i planes %7i\n" + ,numplanes, (int)( numplanes * sizeof( dplane_t ) ) ); + printf( "%6i texinfo %7i\n" + ,numtexinfo, (int)( numtexinfo * sizeof( texinfo_t ) ) ); + printf( "%6i entdata %7i\n", num_entities, entdatasize ); + + printf( "\n" ); + + printf( "%6i vertexes %7i\n" + ,numvertexes, (int)( numvertexes * sizeof( dvertex_t ) ) ); + printf( "%6i nodes %7i\n" + ,numnodes, (int)( numnodes * sizeof( dnode_t ) ) ); + printf( "%6i faces %7i\n" + ,numfaces, (int)( numfaces * sizeof( dface_t ) ) ); + printf( "%6i leafs %7i\n" + ,numleafs, (int)( numleafs * sizeof( dleaf_t ) ) ); + printf( "%6i leaffaces %7i\n" + ,numleaffaces, (int)( numleaffaces * sizeof( dleaffaces[0] ) ) ); + printf( "%6i leafbrushes %7i\n" + ,numleafbrushes, (int)( numleafbrushes * sizeof( dleafbrushes[0] ) ) ); + printf( "%6i surfedges %7i\n" + ,numsurfedges, (int)( numsurfedges * sizeof( dsurfedges[0] ) ) ); + printf( "%6i edges %7i\n" + ,numedges, (int)( numedges * sizeof( dedge_t ) ) ); +//NEW + printf( "%6i areas %7i\n" + ,numareas, (int)( numareas * sizeof( darea_t ) ) ); + printf( "%6i areaportals %7i\n" + ,numareaportals, (int)( numareaportals * sizeof( dareaportal_t ) ) ); +//ENDNEW + printf( " lightdata %7i\n", lightdatasize ); + printf( " visdata %7i\n", visdatasize ); +} //end of the function Q2_PrintBSPFileSizes + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void Q2_ParseEntities( void ) { + script_t *script; + + num_entities = 0; + script = LoadScriptMemory( dentdata, entdatasize, "*Quake2 bsp file" ); + SetScriptFlags( script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS ); + + while ( ParseEntity( script ) ) + { + } //end while + + FreeScript( script ); +} //end of the function Q2_ParseEntities + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void Q2_UnparseEntities( void ) { + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + buf = dentdata; + end = buf; + *end = 0; + + for ( i = 0 ; i < num_entities ; i++ ) + { + ep = entities[i].epairs; + if ( !ep ) { + continue; // ent got removed + + } + strcat( end,"{\n" ); + end += 2; + + for ( ep = entities[i].epairs ; ep ; ep = ep->next ) + { + strcpy( key, ep->key ); + StripTrailing( key ); + strcpy( value, ep->value ); + StripTrailing( value ); + + sprintf( line, "\"%s\" \"%s\"\n", key, value ); + strcat( end, line ); + end += strlen( line ); + } + strcat( end,"}\n" ); + end += 2; + + if ( end > buf + MAX_MAP_ENTSTRING ) { + Error( "Entity text too long" ); + } + } + entdatasize = end - buf + 1; +} //end of the function Q2_UnparseEntities + diff --git a/src/bspc/l_bsp_q2.h b/src/bspc/l_bsp_q2.h new file mode 100644 index 0000000..5b52a24 --- /dev/null +++ b/src/bspc/l_bsp_q2.h @@ -0,0 +1,104 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef ME +#define ME +#endif //ME + +extern int nummodels; +extern dmodel_t *dmodels; //[MAX_MAP_MODELS]; + +extern int visdatasize; +extern byte *dvisdata; //[MAX_MAP_VISIBILITY]; +extern dvis_t *dvis; + +extern int lightdatasize; +extern byte *dlightdata; //[MAX_MAP_LIGHTING]; + +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 numedges; +extern dedge_t *dedges; //[MAX_MAP_EDGES]; + +extern int numleaffaces; +extern unsigned short *dleaffaces; //[MAX_MAP_LEAFFACES]; + +extern int numleafbrushes; +extern unsigned short *dleafbrushes; //[MAX_MAP_LEAFBRUSHES]; + +extern int numsurfedges; +extern int *dsurfedges; //[MAX_MAP_SURFEDGES]; + +extern int numareas; +extern darea_t *dareas; //[MAX_MAP_AREAS]; + +extern int numareaportals; +extern dareaportal_t *dareaportals; //[MAX_MAP_AREAPORTALS]; + +extern int numbrushes; +extern dbrush_t *dbrushes; //[MAX_MAP_BRUSHES]; + +extern int numbrushsides; +extern dbrushside_t *dbrushsides; //[MAX_MAP_BRUSHSIDES]; + +extern byte dpop[256]; + +extern char brushsidetextured[MAX_MAP_BRUSHSIDES]; + +void Q2_AllocMaxBSP( void ); +void Q2_FreeMaxBSP( void ); + +void Q2_DecompressVis( byte *in, byte *decompressed ); +int Q2_CompressVis( byte *vis, byte *dest ); + +void Q2_LoadBSPFile( char *filename, int offset, int length ); +void Q2_LoadBSPFileTexinfo( char *filename ); // just for qdata +void Q2_WriteBSPFile( char *filename ); +void Q2_PrintBSPFileSizes( void ); +void Q2_ParseEntities( void ); +void Q2_UnparseEntities( void ); + diff --git a/src/bspc/l_bsp_q3.c b/src/bspc/l_bsp_q3.c new file mode 100644 index 0000000..1ef927b --- /dev/null +++ b/src/bspc/l_bsp_q3.c @@ -0,0 +1,838 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "l_poly.h" +#include "../botlib/l_script.h" +#include "l_qfiles.h" +#include "l_bsp_q3.h" +#include "l_bsp_ent.h" + +void Q3_ParseEntities( void ); +void Q3_PrintBSPFileSizes( void ); + +void GetLeafNums( void ); + +//============================================================================= + +#define WCONVEX_EPSILON 0.5 + +int q3_nummodels; +q3_dmodel_t *q3_dmodels; //[MAX_MAP_MODELS]; + +int q3_numShaders; +q3_dshader_t *q3_dshaders; //[Q3_MAX_MAP_SHADERS]; + +int q3_entdatasize; +char *q3_dentdata; //[Q3_MAX_MAP_ENTSTRING]; + +int q3_numleafs; +q3_dleaf_t *q3_dleafs; //[Q3_MAX_MAP_LEAFS]; + +int q3_numplanes; +q3_dplane_t *q3_dplanes; //[Q3_MAX_MAP_PLANES]; + +int q3_numnodes; +q3_dnode_t *q3_dnodes; //[Q3_MAX_MAP_NODES]; + +int q3_numleafsurfaces; +int *q3_dleafsurfaces; //[Q3_MAX_MAP_LEAFFACES]; + +int q3_numleafbrushes; +int *q3_dleafbrushes; //[Q3_MAX_MAP_LEAFBRUSHES]; + +int q3_numbrushes; +q3_dbrush_t *q3_dbrushes; //[Q3_MAX_MAP_BRUSHES]; + +int q3_numbrushsides; +q3_dbrushside_t *q3_dbrushsides; //[Q3_MAX_MAP_BRUSHSIDES]; + +int q3_numLightBytes; +byte *q3_lightBytes; //[Q3_MAX_MAP_LIGHTING]; + +int q3_numGridPoints; +byte *q3_gridData; //[Q3_MAX_MAP_LIGHTGRID]; + +int q3_numVisBytes; +byte *q3_visBytes; //[Q3_MAX_MAP_VISIBILITY]; + +int q3_numDrawVerts; +q3_drawVert_t *q3_drawVerts; //[Q3_MAX_MAP_DRAW_VERTS]; + +int q3_numDrawIndexes; +int *q3_drawIndexes; //[Q3_MAX_MAP_DRAW_INDEXES]; + +int q3_numDrawSurfaces; +q3_dsurface_t *q3_drawSurfaces; //[Q3_MAX_MAP_DRAW_SURFS]; + +int q3_numFogs; +q3_dfog_t *q3_dfogs; //[Q3_MAX_MAP_FOGS]; + +char q3_dbrushsidetextured[Q3_MAX_MAP_BRUSHSIDES]; + +extern qboolean forcesidesvisible; + + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_FreeMaxBSP( void ) { + if ( q3_dmodels ) { + FreeMemory( q3_dmodels ); + } + q3_dmodels = NULL; + q3_nummodels = 0; + if ( q3_dshaders ) { + FreeMemory( q3_dshaders ); + } + q3_dshaders = NULL; + q3_numShaders = 0; + if ( q3_dentdata ) { + FreeMemory( q3_dentdata ); + } + q3_dentdata = NULL; + q3_entdatasize = 0; + if ( q3_dleafs ) { + FreeMemory( q3_dleafs ); + } + q3_dleafs = NULL; + q3_numleafs = 0; + if ( q3_dplanes ) { + FreeMemory( q3_dplanes ); + } + q3_dplanes = NULL; + q3_numplanes = 0; + if ( q3_dnodes ) { + FreeMemory( q3_dnodes ); + } + q3_dnodes = NULL; + q3_numnodes = 0; + if ( q3_dleafsurfaces ) { + FreeMemory( q3_dleafsurfaces ); + } + q3_dleafsurfaces = NULL; + q3_numleafsurfaces = 0; + if ( q3_dleafbrushes ) { + FreeMemory( q3_dleafbrushes ); + } + q3_dleafbrushes = NULL; + q3_numleafbrushes = 0; + if ( q3_dbrushes ) { + FreeMemory( q3_dbrushes ); + } + q3_dbrushes = NULL; + q3_numbrushes = 0; + if ( q3_dbrushsides ) { + FreeMemory( q3_dbrushsides ); + } + q3_dbrushsides = NULL; + q3_numbrushsides = 0; + if ( q3_lightBytes ) { + FreeMemory( q3_lightBytes ); + } + q3_lightBytes = NULL; + q3_numLightBytes = 0; + if ( q3_gridData ) { + FreeMemory( q3_gridData ); + } + q3_gridData = NULL; + q3_numGridPoints = 0; + if ( q3_visBytes ) { + FreeMemory( q3_visBytes ); + } + q3_visBytes = NULL; + q3_numVisBytes = 0; + if ( q3_drawVerts ) { + FreeMemory( q3_drawVerts ); + } + q3_drawVerts = NULL; + q3_numDrawVerts = 0; + if ( q3_drawIndexes ) { + FreeMemory( q3_drawIndexes ); + } + q3_drawIndexes = NULL; + q3_numDrawIndexes = 0; + if ( q3_drawSurfaces ) { + FreeMemory( q3_drawSurfaces ); + } + q3_drawSurfaces = NULL; + q3_numDrawSurfaces = 0; + if ( q3_dfogs ) { + FreeMemory( q3_dfogs ); + } + q3_dfogs = NULL; + q3_numFogs = 0; +} //end of the function Q3_FreeMaxBSP + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_PlaneFromPoints( vec3_t p0, vec3_t p1, vec3_t p2, vec3_t normal, float *dist ) { + vec3_t t1, t2; + + VectorSubtract( p0, p1, t1 ); + VectorSubtract( p2, p1, t2 ); + CrossProduct( t1, t2, normal ); + VectorNormalize( normal ); + + *dist = DotProduct( p0, normal ); +} //end of the function PlaneFromPoints +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_SurfacePlane( q3_dsurface_t *surface, vec3_t normal, float *dist ) { + int i; + float *p0, *p1, *p2; + vec3_t t1, t2; + + p0 = q3_drawVerts[surface->firstVert].xyz; + for ( i = 1; i < surface->numVerts - 1; i++ ) + { + p1 = q3_drawVerts[surface->firstVert + ( ( i ) % surface->numVerts )].xyz; + p2 = q3_drawVerts[surface->firstVert + ( ( i + 1 ) % surface->numVerts )].xyz; + VectorSubtract( p0, p1, t1 ); + VectorSubtract( p2, p1, t2 ); + CrossProduct( t1, t2, normal ); + VectorNormalize( normal ); + if ( VectorLength( normal ) ) { + break; + } + } //end for*/ +/* + float dot; + for (i = 0; i < surface->numVerts; i++) + { + p0 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; + p1 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; + p2 = q3_drawVerts[surface->firstVert + ((i+2) % surface->numVerts)].xyz; + VectorSubtract(p0, p1, t1); + VectorSubtract(p2, p1, t2); + VectorNormalize(t1); + VectorNormalize(t2); + dot = DotProduct(t1, t2); + if (dot > -0.9 && dot < 0.9 && + VectorLength(t1) > 0.1 && VectorLength(t2) > 0.1) break; + } //end for + CrossProduct(t1, t2, normal); + VectorNormalize(normal); +*/ + if ( VectorLength( normal ) < 0.9 ) { + printf( "surface %d bogus normal vector %f %f %f\n", surface - q3_drawSurfaces, normal[0], normal[1], normal[2] ); + printf( "t1 = %f %f %f, t2 = %f %f %f\n", t1[0], t1[1], t1[2], t2[0], t2[1], t2[2] ); + for ( i = 0; i < surface->numVerts; i++ ) + { + p1 = q3_drawVerts[surface->firstVert + ( ( i ) % surface->numVerts )].xyz; + Log_Print( "p%d = %f %f %f\n", i, p1[0], p1[1], p1[2] ); + } //end for + } //end if + *dist = DotProduct( p0, normal ); +} //end of the function Q3_SurfacePlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +q3_dplane_t *q3_surfaceplanes; + +void Q3_CreatePlanarSurfacePlanes( void ) { + int i; + q3_dsurface_t *surface; + + Log_Print( "creating planar surface planes...\n" ); + q3_surfaceplanes = (q3_dplane_t *) GetClearedMemory( q3_numDrawSurfaces * sizeof( q3_dplane_t ) ); + + for ( i = 0; i < q3_numDrawSurfaces; i++ ) + { + surface = &q3_drawSurfaces[i]; + if ( surface->surfaceType != MST_PLANAR ) { + continue; + } + Q3_SurfacePlane( surface, q3_surfaceplanes[i].normal, &q3_surfaceplanes[i].dist ); + //Log_Print("normal = %f %f %f, dist = %f\n", q3_surfaceplanes[i].normal[0], + // q3_surfaceplanes[i].normal[1], + // q3_surfaceplanes[i].normal[2], q3_surfaceplanes[i].dist); + } //end for +} //end of the function Q3_CreatePlanarSurfacePlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void Q3_SurfacePlane(q3_dsurface_t *surface, vec3_t normal, float *dist) +{ + //take the plane information from the lightmap vector + //VectorCopy(surface->lightmapVecs[2], normal); + //calculate plane dist with first surface vertex + //*dist = DotProduct(q3_drawVerts[surface->firstVert].xyz, normal); + Q3_PlaneFromPoints(q3_drawVerts[surface->firstVert].xyz, + q3_drawVerts[surface->firstVert+1].xyz, + q3_drawVerts[surface->firstVert+2].xyz, normal, dist); +} //end of the function Q3_SurfacePlane*/ +//=========================================================================== +// returns the amount the face and the winding overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Q3_FaceOnWinding( q3_dsurface_t *surface, winding_t *winding ) { + int i; + float dist, area; + q3_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + //copy the winding before chopping + w = CopyWinding( winding ); + //retrieve the surface plane + Q3_SurfacePlane( surface, plane.normal, &plane.dist ); + //chop the winding with the surface edge planes + for ( i = 0; i < surface->numVerts && w; i++ ) + { + v1 = q3_drawVerts[surface->firstVert + ( ( i ) % surface->numVerts )].xyz; + v2 = q3_drawVerts[surface->firstVert + ( ( i + 1 ) % surface->numVerts )].xyz; + //create a plane through the edge from v1 to v2, orthogonal to the + //surface plane and with the normal vector pointing inward + VectorSubtract( v2, v1, edgevec ); + CrossProduct( edgevec, plane.normal, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, v1 ); + // + ChopWindingInPlace( &w, normal, dist, -0.1 ); //CLIP_EPSILON + } //end for + if ( w ) { + area = WindingArea( w ); + FreeWinding( w ); + return area; + } //end if + return 0; +} //end of the function Q3_FaceOnWinding +//=========================================================================== +// creates a winding for the given brush side on the given brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *Q3_BrushSideWinding( q3_dbrush_t *brush, q3_dbrushside_t *baseside ) { + int i; + q3_dplane_t *baseplane, *plane; + winding_t *w; + q3_dbrushside_t *side; + + //create a winding for the brush side with the given planenumber + baseplane = &q3_dplanes[baseside->planeNum]; + w = BaseWindingForPlane( baseplane->normal, baseplane->dist ); + for ( i = 0; i < brush->numSides && w; i++ ) + { + side = &q3_dbrushsides[brush->firstSide + i]; + //don't chop with the base plane + if ( side->planeNum == baseside->planeNum ) { + continue; + } + //also don't use planes that are almost equal + plane = &q3_dplanes[side->planeNum]; + if ( DotProduct( baseplane->normal, plane->normal ) > 0.999 + && fabs( baseplane->dist - plane->dist ) < 0.01 ) { + continue; + } + // + plane = &q3_dplanes[side->planeNum ^ 1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, -0.1 ); //CLIP_EPSILON); + } //end for + return w; +} //end of the function Q3_BrushSideWinding +//=========================================================================== +// fix screwed brush texture references +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsTiny( winding_t *w ); + +void Q3_FindVisibleBrushSides( void ) { + int i, j, k, we, numtextured, numsides; + float dot; + q3_dplane_t *plane; + q3_dbrushside_t *brushside; + q3_dbrush_t *brush; + q3_dsurface_t *surface; + winding_t *w; + + memset( q3_dbrushsidetextured, false, Q3_MAX_MAP_BRUSHSIDES ); + // + numsides = 0; + //create planes for the planar surfaces + Q3_CreatePlanarSurfacePlanes(); + Log_Print( "searching visible brush sides...\n" ); + Log_Print( "%6d brush sides", numsides ); + //go over all the brushes + for ( i = 0; i < q3_numbrushes; i++ ) + { + brush = &q3_dbrushes[i]; + //go over all the sides of the brush + for ( j = 0; j < brush->numSides; j++ ) + { + qprintf( "\r%6d", numsides++ ); + brushside = &q3_dbrushsides[brush->firstSide + j]; + // + w = Q3_BrushSideWinding( brush, brushside ); + if ( !w ) { + q3_dbrushsidetextured[brush->firstSide + j] = true; + continue; + } //end if + else + { + //RemoveEqualPoints(w, 0.2); + if ( WindingIsTiny( w ) ) { + FreeWinding( w ); + q3_dbrushsidetextured[brush->firstSide + j] = true; + continue; + } //end if + else + { + we = WindingError( w ); + if ( we == WE_NOTENOUGHPOINTS + || we == WE_SMALLAREA + || we == WE_POINTBOGUSRANGE +// || we == WE_NONCONVEX + ) { + FreeWinding( w ); + q3_dbrushsidetextured[brush->firstSide + j] = true; + continue; + } //end if + } //end else + } //end else + if ( WindingArea( w ) < 20 ) { + q3_dbrushsidetextured[brush->firstSide + j] = true; + continue; + } //end if + //find a face for texturing this brush + for ( k = 0; k < q3_numDrawSurfaces; k++ ) + { + surface = &q3_drawSurfaces[k]; + if ( surface->surfaceType != MST_PLANAR ) { + continue; + } + // + //Q3_SurfacePlane(surface, plane.normal, &plane.dist); + plane = &q3_surfaceplanes[k]; + //the surface plane and the brush side plane should be pretty much the same + if ( fabs( fabs( plane->dist ) - fabs( q3_dplanes[brushside->planeNum].dist ) ) > 5 ) { + continue; + } + dot = DotProduct( plane->normal, q3_dplanes[brushside->planeNum].normal ); + if ( dot > -0.9 && dot < 0.9 ) { + continue; + } + //if the face is partly or totally on the brush side + if ( Q3_FaceOnWinding( surface, w ) ) { + q3_dbrushsidetextured[brush->firstSide + j] = true; + //Log_Write("Q3_FaceOnWinding"); + break; + } //end if + } //end for + FreeWinding( w ); + } //end for + } //end for + qprintf( "\r%6d brush sides\n", numsides ); + numtextured = 0; + for ( i = 0; i < q3_numbrushsides; i++ ) + { + if ( forcesidesvisible ) { + q3_dbrushsidetextured[i] = true; + } + if ( q3_dbrushsidetextured[i] ) { + numtextured++; + } + } //end for + Log_Print( "%d brush sides textured out of %d\n", numtextured, q3_numbrushsides ); +} //end of the function Q3_FindVisibleBrushSides + +/* +============= +Q3_SwapBlock + +If all values are 32 bits, this can be used to swap everything +============= +*/ +void Q3_SwapBlock( int *block, int sizeOfBlock ) { + int i; + + sizeOfBlock >>= 2; + for ( i = 0 ; i < sizeOfBlock ; i++ ) { + block[i] = LittleLong( block[i] ); + } +} //end of the function Q3_SwapBlock + +/* +============= +Q3_SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void Q3_SwapBSPFile( void ) { + int i; + + // models + Q3_SwapBlock( (int *)q3_dmodels, q3_nummodels * sizeof( q3_dmodels[0] ) ); + + // shaders (don't swap the name) + for ( i = 0 ; i < q3_numShaders ; i++ ) { + q3_dshaders[i].contentFlags = LittleLong( q3_dshaders[i].contentFlags ); + q3_dshaders[i].surfaceFlags = LittleLong( q3_dshaders[i].surfaceFlags ); + } + + // planes + Q3_SwapBlock( (int *)q3_dplanes, q3_numplanes * sizeof( q3_dplanes[0] ) ); + + // nodes + Q3_SwapBlock( (int *)q3_dnodes, q3_numnodes * sizeof( q3_dnodes[0] ) ); + + // leafs + Q3_SwapBlock( (int *)q3_dleafs, q3_numleafs * sizeof( q3_dleafs[0] ) ); + + // leaffaces + Q3_SwapBlock( (int *)q3_dleafsurfaces, q3_numleafsurfaces * sizeof( q3_dleafsurfaces[0] ) ); + + // leafbrushes + Q3_SwapBlock( (int *)q3_dleafbrushes, q3_numleafbrushes * sizeof( q3_dleafbrushes[0] ) ); + + // brushes + Q3_SwapBlock( (int *)q3_dbrushes, q3_numbrushes * sizeof( q3_dbrushes[0] ) ); + + // brushsides + Q3_SwapBlock( (int *)q3_dbrushsides, q3_numbrushsides * sizeof( q3_dbrushsides[0] ) ); + + // vis + ( (int *)&q3_visBytes )[0] = LittleLong( ( (int *)&q3_visBytes )[0] ); + ( (int *)&q3_visBytes )[1] = LittleLong( ( (int *)&q3_visBytes )[1] ); + + // drawverts (don't swap colors ) + for ( i = 0 ; i < q3_numDrawVerts ; i++ ) { + q3_drawVerts[i].lightmap[0] = LittleFloat( q3_drawVerts[i].lightmap[0] ); + q3_drawVerts[i].lightmap[1] = LittleFloat( q3_drawVerts[i].lightmap[1] ); + q3_drawVerts[i].st[0] = LittleFloat( q3_drawVerts[i].st[0] ); + q3_drawVerts[i].st[1] = LittleFloat( q3_drawVerts[i].st[1] ); + q3_drawVerts[i].xyz[0] = LittleFloat( q3_drawVerts[i].xyz[0] ); + q3_drawVerts[i].xyz[1] = LittleFloat( q3_drawVerts[i].xyz[1] ); + q3_drawVerts[i].xyz[2] = LittleFloat( q3_drawVerts[i].xyz[2] ); + q3_drawVerts[i].normal[0] = LittleFloat( q3_drawVerts[i].normal[0] ); + q3_drawVerts[i].normal[1] = LittleFloat( q3_drawVerts[i].normal[1] ); + q3_drawVerts[i].normal[2] = LittleFloat( q3_drawVerts[i].normal[2] ); + } + + // drawindexes + Q3_SwapBlock( (int *)q3_drawIndexes, q3_numDrawIndexes * sizeof( q3_drawIndexes[0] ) ); + + // drawsurfs + Q3_SwapBlock( (int *)q3_drawSurfaces, q3_numDrawSurfaces * sizeof( q3_drawSurfaces[0] ) ); + + // fogs + for ( i = 0 ; i < q3_numFogs ; i++ ) { + q3_dfogs[i].brushNum = LittleLong( q3_dfogs[i].brushNum ); + } +} + + + +/* +============= +Q3_CopyLump +============= +*/ +int Q3_CopyLump( q3_dheader_t *header, int lump, void **dest, int size ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if ( length % size ) { + Error( "Q3_LoadBSPFile: odd lump size" ); + } + + *dest = GetMemory( length ); + + memcpy( *dest, (byte *)header + ofs, length ); + + return length / size; +} + +/* +============= +Q3_LoadBSPFile +============= +*/ +void Q3_LoadBSPFile( struct quakefile_s *qf ) { + q3_dheader_t *header; + + // load the file header + //LoadFile(filename, (void **)&header, offset, length); + // + LoadQuakeFile( qf, (void **)&header ); + + // swap the header + Q3_SwapBlock( (int *)header, sizeof( *header ) ); + + if ( header->ident != Q3_BSP_IDENT ) { + Error( "%s is not a IBSP file", qf->filename ); + } + if ( header->version != Q3_BSP_VERSION ) { + Error( "%s is version %i, not %i", qf->filename, header->version, Q3_BSP_VERSION ); + } + + q3_numShaders = Q3_CopyLump( header, Q3_LUMP_SHADERS, (void *) &q3_dshaders, sizeof( q3_dshader_t ) ); + q3_nummodels = Q3_CopyLump( header, Q3_LUMP_MODELS, (void *) &q3_dmodels, sizeof( q3_dmodel_t ) ); + q3_numplanes = Q3_CopyLump( header, Q3_LUMP_PLANES, (void *) &q3_dplanes, sizeof( q3_dplane_t ) ); + q3_numleafs = Q3_CopyLump( header, Q3_LUMP_LEAFS, (void *) &q3_dleafs, sizeof( q3_dleaf_t ) ); + q3_numnodes = Q3_CopyLump( header, Q3_LUMP_NODES, (void *) &q3_dnodes, sizeof( q3_dnode_t ) ); + q3_numleafsurfaces = Q3_CopyLump( header, Q3_LUMP_LEAFSURFACES, (void *) &q3_dleafsurfaces, sizeof( q3_dleafsurfaces[0] ) ); + q3_numleafbrushes = Q3_CopyLump( header, Q3_LUMP_LEAFBRUSHES, (void *) &q3_dleafbrushes, sizeof( q3_dleafbrushes[0] ) ); + q3_numbrushes = Q3_CopyLump( header, Q3_LUMP_BRUSHES, (void *) &q3_dbrushes, sizeof( q3_dbrush_t ) ); + q3_numbrushsides = Q3_CopyLump( header, Q3_LUMP_BRUSHSIDES, (void *) &q3_dbrushsides, sizeof( q3_dbrushside_t ) ); + q3_numDrawVerts = Q3_CopyLump( header, Q3_LUMP_DRAWVERTS, (void *) &q3_drawVerts, sizeof( q3_drawVert_t ) ); + q3_numDrawSurfaces = Q3_CopyLump( header, Q3_LUMP_SURFACES, (void *) &q3_drawSurfaces, sizeof( q3_dsurface_t ) ); + q3_numFogs = Q3_CopyLump( header, Q3_LUMP_FOGS, (void *) &q3_dfogs, sizeof( q3_dfog_t ) ); + q3_numDrawIndexes = Q3_CopyLump( header, Q3_LUMP_DRAWINDEXES, (void *) &q3_drawIndexes, sizeof( q3_drawIndexes[0] ) ); + + q3_numVisBytes = Q3_CopyLump( header, Q3_LUMP_VISIBILITY, (void *) &q3_visBytes, 1 ); + q3_numLightBytes = Q3_CopyLump( header, Q3_LUMP_LIGHTMAPS, (void *) &q3_lightBytes, 1 ); + q3_entdatasize = Q3_CopyLump( header, Q3_LUMP_ENTITIES, (void *) &q3_dentdata, 1 ); + + q3_numGridPoints = Q3_CopyLump( header, Q3_LUMP_LIGHTGRID, (void *) &q3_gridData, 8 ); + + + FreeMemory( header ); // everything has been copied out + + // swap everything + Q3_SwapBSPFile(); + + Q3_FindVisibleBrushSides(); + + //Q3_PrintBSPFileSizes(); +} + + +//============================================================================ + +/* +============= +Q3_AddLump +============= +*/ +void Q3_AddLump( FILE *bspfile, q3_dheader_t *header, int lumpnum, void *data, int len ) { + q3_lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell( bspfile ) ); + lump->filelen = LittleLong( len ); + SafeWrite( bspfile, data, ( len + 3 ) & ~3 ); +} + +/* +============= +Q3_WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void Q3_WriteBSPFile( char *filename ) { + q3_dheader_t outheader, *header; + FILE *bspfile; + + header = &outheader; + memset( header, 0, sizeof( q3_dheader_t ) ); + + Q3_SwapBSPFile(); + + header->ident = LittleLong( Q3_BSP_IDENT ); + header->version = LittleLong( Q3_BSP_VERSION ); + + bspfile = SafeOpenWrite( filename ); + SafeWrite( bspfile, header, sizeof( q3_dheader_t ) ); // overwritten later + + Q3_AddLump( bspfile, header, Q3_LUMP_SHADERS, q3_dshaders, q3_numShaders * sizeof( q3_dshader_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_PLANES, q3_dplanes, q3_numplanes * sizeof( q3_dplane_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_LEAFS, q3_dleafs, q3_numleafs * sizeof( q3_dleaf_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_NODES, q3_dnodes, q3_numnodes * sizeof( q3_dnode_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_BRUSHES, q3_dbrushes, q3_numbrushes * sizeof( q3_dbrush_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_BRUSHSIDES, q3_dbrushsides, q3_numbrushsides * sizeof( q3_dbrushside_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_LEAFSURFACES, q3_dleafsurfaces, q3_numleafsurfaces * sizeof( q3_dleafsurfaces[0] ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_LEAFBRUSHES, q3_dleafbrushes, q3_numleafbrushes * sizeof( q3_dleafbrushes[0] ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_MODELS, q3_dmodels, q3_nummodels * sizeof( q3_dmodel_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_DRAWVERTS, q3_drawVerts, q3_numDrawVerts * sizeof( q3_drawVert_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_SURFACES, q3_drawSurfaces, q3_numDrawSurfaces * sizeof( q3_dsurface_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_VISIBILITY, q3_visBytes, q3_numVisBytes ); + Q3_AddLump( bspfile, header, Q3_LUMP_LIGHTMAPS, q3_lightBytes, q3_numLightBytes ); + Q3_AddLump( bspfile, header, Q3_LUMP_LIGHTGRID, q3_gridData, 8 * q3_numGridPoints ); + Q3_AddLump( bspfile, header, Q3_LUMP_ENTITIES, q3_dentdata, q3_entdatasize ); + Q3_AddLump( bspfile, header, Q3_LUMP_FOGS, q3_dfogs, q3_numFogs * sizeof( q3_dfog_t ) ); + Q3_AddLump( bspfile, header, Q3_LUMP_DRAWINDEXES, q3_drawIndexes, q3_numDrawIndexes * sizeof( q3_drawIndexes[0] ) ); + + fseek( bspfile, 0, SEEK_SET ); + SafeWrite( bspfile, header, sizeof( q3_dheader_t ) ); + fclose( bspfile ); +} + +//============================================================================ + +/* +============= +Q3_PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void Q3_PrintBSPFileSizes( void ) { + if ( !num_entities ) { + Q3_ParseEntities(); + } + + Log_Print( "%6i models %7i\n" + ,q3_nummodels, (int)( q3_nummodels * sizeof( q3_dmodel_t ) ) ); + Log_Print( "%6i shaders %7i\n" + ,q3_numShaders, (int)( q3_numShaders * sizeof( q3_dshader_t ) ) ); + Log_Print( "%6i brushes %7i\n" + ,q3_numbrushes, (int)( q3_numbrushes * sizeof( q3_dbrush_t ) ) ); + Log_Print( "%6i brushsides %7i\n" + ,q3_numbrushsides, (int)( q3_numbrushsides * sizeof( q3_dbrushside_t ) ) ); + Log_Print( "%6i fogs %7i\n" + ,q3_numFogs, (int)( q3_numFogs * sizeof( q3_dfog_t ) ) ); + Log_Print( "%6i planes %7i\n" + ,q3_numplanes, (int)( q3_numplanes * sizeof( q3_dplane_t ) ) ); + Log_Print( "%6i entdata %7i\n", num_entities, q3_entdatasize ); + + Log_Print( "\n" ); + + Log_Print( "%6i nodes %7i\n" + ,q3_numnodes, (int)( q3_numnodes * sizeof( q3_dnode_t ) ) ); + Log_Print( "%6i leafs %7i\n" + ,q3_numleafs, (int)( q3_numleafs * sizeof( q3_dleaf_t ) ) ); + Log_Print( "%6i leafsurfaces %7i\n" + ,q3_numleafsurfaces, (int)( q3_numleafsurfaces * sizeof( q3_dleafsurfaces[0] ) ) ); + Log_Print( "%6i leafbrushes %7i\n" + ,q3_numleafbrushes, (int)( q3_numleafbrushes * sizeof( q3_dleafbrushes[0] ) ) ); + Log_Print( "%6i drawverts %7i\n" + ,q3_numDrawVerts, (int)( q3_numDrawVerts * sizeof( q3_drawVerts[0] ) ) ); + Log_Print( "%6i drawindexes %7i\n" + ,q3_numDrawIndexes, (int)( q3_numDrawIndexes * sizeof( q3_drawIndexes[0] ) ) ); + Log_Print( "%6i drawsurfaces %7i\n" + ,q3_numDrawSurfaces, (int)( q3_numDrawSurfaces * sizeof( q3_drawSurfaces[0] ) ) ); + + Log_Print( "%6i lightmaps %7i\n" + ,q3_numLightBytes / ( LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3 ), q3_numLightBytes ); + Log_Print( " visibility %7i\n" + , q3_numVisBytes ); +} + +/* +================ +Q3_ParseEntities + +Parses the q3_dentdata string into entities +================ +*/ +void Q3_ParseEntities( void ) { + script_t *script; + + num_entities = 0; + script = LoadScriptMemory( q3_dentdata, q3_entdatasize, "*Quake3 bsp file" ); + SetScriptFlags( script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS ); + + while ( ParseEntity( script ) ) + { + } //end while + + FreeScript( script ); +} //end of the function Q3_ParseEntities + + +/* +================ +Q3_UnparseEntities + +Generates the q3_dentdata string from all the entities +================ +*/ +void Q3_UnparseEntities( void ) { + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + + buf = q3_dentdata; + end = buf; + *end = 0; + + for ( i = 0 ; i < num_entities ; i++ ) + { + ep = entities[i].epairs; + if ( !ep ) { + continue; // ent got removed + + } + strcat( end,"{\n" ); + end += 2; + + for ( ep = entities[i].epairs ; ep ; ep = ep->next ) + { + sprintf( line, "\"%s\" \"%s\"\n", ep->key, ep->value ); + strcat( end, line ); + end += strlen( line ); + } + strcat( end,"}\n" ); + end += 2; + + if ( end > buf + Q3_MAX_MAP_ENTSTRING ) { + Error( "Entity text too long" ); + } + } + q3_entdatasize = end - buf + 1; +} //end of the function Q3_UnparseEntities + diff --git a/src/bspc/l_bsp_q3.h b/src/bspc/l_bsp_q3.h new file mode 100644 index 0000000..0a3180c --- /dev/null +++ b/src/bspc/l_bsp_q3.h @@ -0,0 +1,88 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "q3files.h" +//#include "surfaceflags.h" + +extern int q3_nummodels; +extern q3_dmodel_t *q3_dmodels; //[MAX_MAP_MODELS]; + +extern int q3_numShaders; +extern q3_dshader_t *q3_dshaders; //[Q3_MAX_MAP_SHADERS]; + +extern int q3_entdatasize; +extern char *q3_dentdata; //[Q3_MAX_MAP_ENTSTRING]; + +extern int q3_numleafs; +extern q3_dleaf_t *q3_dleafs; //[Q3_MAX_MAP_LEAFS]; + +extern int q3_numplanes; +extern q3_dplane_t *q3_dplanes; //[Q3_MAX_MAP_PLANES]; + +extern int q3_numnodes; +extern q3_dnode_t *q3_dnodes; //[Q3_MAX_MAP_NODES]; + +extern int q3_numleafsurfaces; +extern int *q3_dleafsurfaces; //[Q3_MAX_MAP_LEAFFACES]; + +extern int q3_numleafbrushes; +extern int *q3_dleafbrushes; //[Q3_MAX_MAP_LEAFBRUSHES]; + +extern int q3_numbrushes; +extern q3_dbrush_t *q3_dbrushes; //[Q3_MAX_MAP_BRUSHES]; + +extern int q3_numbrushsides; +extern q3_dbrushside_t *q3_dbrushsides; //[Q3_MAX_MAP_BRUSHSIDES]; + +extern int q3_numLightBytes; +extern byte *q3_lightBytes; //[Q3_MAX_MAP_LIGHTING]; + +extern int q3_numGridPoints; +extern byte *q3_gridData; //[Q3_MAX_MAP_LIGHTGRID]; + +extern int q3_numVisBytes; +extern byte *q3_visBytes; //[Q3_MAX_MAP_VISIBILITY]; + +extern int q3_numDrawVerts; +extern q3_drawVert_t *q3_drawVerts; //[Q3_MAX_MAP_DRAW_VERTS]; + +extern int q3_numDrawIndexes; +extern int *q3_drawIndexes; //[Q3_MAX_MAP_DRAW_INDEXES]; + +extern int q3_numDrawSurfaces; +extern q3_dsurface_t *q3_drawSurfaces; //[Q3_MAX_MAP_DRAW_SURFS]; + +extern int q3_numFogs; +extern q3_dfog_t *q3_dfogs; //[Q3_MAX_MAP_FOGS]; + +extern char q3_dbrushsidetextured[Q3_MAX_MAP_BRUSHSIDES]; + +void Q3_LoadBSPFile( struct quakefile_s *qf ); +void Q3_FreeMaxBSP( void ); +void Q3_ParseEntities( void ); diff --git a/src/bspc/l_bsp_sin.c b/src/bspc/l_bsp_sin.c new file mode 100644 index 0000000..b837a0a --- /dev/null +++ b/src/bspc/l_bsp_sin.c @@ -0,0 +1,1186 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "l_cmd.h" +#include "l_math.h" +#include "l_mem.h" +#include "l_log.h" +#include "l_poly.h" +#include "../botlib/l_script.h" +#include "l_bsp_ent.h" +#include "l_bsp_sin.h" + +void GetLeafNums( void ); + +//============================================================================= + +int sin_nummodels; +sin_dmodel_t *sin_dmodels; //[SIN_MAX_MAP_MODELS]; + +int sin_visdatasize; +byte *sin_dvisdata; //[SIN_MAX_MAP_VISIBILITY]; +sin_dvis_t *sin_dvis; // = (sin_dvis_t *)sin_sin_dvisdata; + +int sin_lightdatasize; +byte *sin_dlightdata; //[SIN_MAX_MAP_LIGHTING]; + +int sin_entdatasize; +char *sin_dentdata; //[SIN_MAX_MAP_ENTSTRING]; + +int sin_numleafs; +sin_dleaf_t *sin_dleafs; //[SIN_MAX_MAP_LEAFS]; + +int sin_numplanes; +sin_dplane_t *sin_dplanes; //[SIN_MAX_MAP_PLANES]; + +int sin_numvertexes; +sin_dvertex_t *sin_dvertexes; //[SIN_MAX_MAP_VERTS]; + +int sin_numnodes; +sin_dnode_t *sin_dnodes; //[SIN_MAX_MAP_NODES]; + +int sin_numtexinfo; +sin_texinfo_t *sin_texinfo; //[SIN_MAX_MAP_sin_texinfo]; + +int sin_numfaces; +sin_dface_t *sin_dfaces; //[SIN_MAX_MAP_FACES]; + +int sin_numedges; +sin_dedge_t *sin_dedges; //[SIN_MAX_MAP_EDGES]; + +int sin_numleaffaces; +unsigned short *sin_dleaffaces; //[SIN_MAX_MAP_LEAFFACES]; + +int sin_numleafbrushes; +unsigned short *sin_dleafbrushes; //[SIN_MAX_MAP_LEAFBRUSHES]; + +int sin_numsurfedges; +int *sin_dsurfedges; //[SIN_MAX_MAP_SURFEDGES]; + +int sin_numbrushes; +sin_dbrush_t *sin_dbrushes; //[SIN_MAX_MAP_BRUSHES]; + +int sin_numbrushsides; +sin_dbrushside_t *sin_dbrushsides; //[SIN_MAX_MAP_BRUSHSIDES]; + +int sin_numareas; +sin_darea_t *sin_dareas; //[SIN_MAX_MAP_AREAS]; + +int sin_numareaportals; +sin_dareaportal_t *sin_dareaportals; //[SIN_MAX_MAP_AREAPORTALS]; + +int sin_numlightinfo; +sin_lightvalue_t *sin_lightinfo; //[SIN_MAX_MAP_LIGHTINFO]; + +byte sin_dpop[256]; + +char sin_dbrushsidetextured[SIN_MAX_MAP_BRUSHSIDES]; + +int sin_bspallocated = false; +int sin_allocatedbspmem = 0; + +void Sin_AllocMaxBSP( void ) { + //models + sin_nummodels = 0; + sin_dmodels = (sin_dmodel_t *) GetClearedMemory( SIN_MAX_MAP_MODELS * sizeof( sin_dmodel_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_MODELS * sizeof( sin_dmodel_t ); + //vis data + sin_visdatasize = 0; + sin_dvisdata = (byte *) GetClearedMemory( SIN_MAX_MAP_VISIBILITY * sizeof( byte ) ); + sin_dvis = (sin_dvis_t *) sin_dvisdata; + sin_allocatedbspmem += SIN_MAX_MAP_VISIBILITY * sizeof( byte ); + //light data + sin_lightdatasize = 0; + sin_dlightdata = (byte *) GetClearedMemory( SIN_MAX_MAP_LIGHTING * sizeof( byte ) ); + sin_allocatedbspmem += SIN_MAX_MAP_LIGHTING * sizeof( byte ); + //entity data + sin_entdatasize = 0; + sin_dentdata = (char *) GetClearedMemory( SIN_MAX_MAP_ENTSTRING * sizeof( char ) ); + sin_allocatedbspmem += SIN_MAX_MAP_ENTSTRING * sizeof( char ); + //leafs + sin_numleafs = 0; + sin_dleafs = (sin_dleaf_t *) GetClearedMemory( SIN_MAX_MAP_LEAFS * sizeof( sin_dleaf_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_LEAFS * sizeof( sin_dleaf_t ); + //planes + sin_numplanes = 0; + sin_dplanes = (sin_dplane_t *) GetClearedMemory( SIN_MAX_MAP_PLANES * sizeof( sin_dplane_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_PLANES * sizeof( sin_dplane_t ); + //vertexes + sin_numvertexes = 0; + sin_dvertexes = (sin_dvertex_t *) GetClearedMemory( SIN_MAX_MAP_VERTS * sizeof( sin_dvertex_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_VERTS * sizeof( sin_dvertex_t ); + //nodes + sin_numnodes = 0; + sin_dnodes = (sin_dnode_t *) GetClearedMemory( SIN_MAX_MAP_NODES * sizeof( sin_dnode_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_NODES * sizeof( sin_dnode_t ); + //texture info + sin_numtexinfo = 0; + sin_texinfo = (sin_texinfo_t *) GetClearedMemory( SIN_MAX_MAP_TEXINFO * sizeof( sin_texinfo_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_TEXINFO * sizeof( sin_texinfo_t ); + //faces + sin_numfaces = 0; + sin_dfaces = (sin_dface_t *) GetClearedMemory( SIN_MAX_MAP_FACES * sizeof( sin_dface_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_FACES * sizeof( sin_dface_t ); + //edges + sin_numedges = 0; + sin_dedges = (sin_dedge_t *) GetClearedMemory( SIN_MAX_MAP_EDGES * sizeof( sin_dedge_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_EDGES * sizeof( sin_dedge_t ); + //leaf faces + sin_numleaffaces = 0; + sin_dleaffaces = (unsigned short *) GetClearedMemory( SIN_MAX_MAP_LEAFFACES * sizeof( unsigned short ) ); + sin_allocatedbspmem += SIN_MAX_MAP_LEAFFACES * sizeof( unsigned short ); + //leaf brushes + sin_numleafbrushes = 0; + sin_dleafbrushes = (unsigned short *) GetClearedMemory( SIN_MAX_MAP_LEAFBRUSHES * sizeof( unsigned short ) ); + sin_allocatedbspmem += SIN_MAX_MAP_LEAFBRUSHES * sizeof( unsigned short ); + //surface edges + sin_numsurfedges = 0; + sin_dsurfedges = (int *) GetClearedMemory( SIN_MAX_MAP_SURFEDGES * sizeof( int ) ); + sin_allocatedbspmem += SIN_MAX_MAP_SURFEDGES * sizeof( int ); + //brushes + sin_numbrushes = 0; + sin_dbrushes = (sin_dbrush_t *) GetClearedMemory( SIN_MAX_MAP_BRUSHES * sizeof( sin_dbrush_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_BRUSHES * sizeof( sin_dbrush_t ); + //brushsides + sin_numbrushsides = 0; + sin_dbrushsides = (sin_dbrushside_t *) GetClearedMemory( SIN_MAX_MAP_BRUSHSIDES * sizeof( sin_dbrushside_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_BRUSHSIDES * sizeof( sin_dbrushside_t ); + //areas + sin_numareas = 0; + sin_dareas = (sin_darea_t *) GetClearedMemory( SIN_MAX_MAP_AREAS * sizeof( sin_darea_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_AREAS * sizeof( sin_darea_t ); + //area portals + sin_numareaportals = 0; + sin_dareaportals = (sin_dareaportal_t *) GetClearedMemory( SIN_MAX_MAP_AREAPORTALS * sizeof( sin_dareaportal_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_AREAPORTALS * sizeof( sin_dareaportal_t ); + //light info + sin_numlightinfo = 0; + sin_lightinfo = (sin_lightvalue_t *) GetClearedMemory( SIN_MAX_MAP_LIGHTINFO * sizeof( sin_lightvalue_t ) ); + sin_allocatedbspmem += SIN_MAX_MAP_LIGHTINFO * sizeof( sin_lightvalue_t ); + //print allocated memory + Log_Print( "allocated " ); + PrintMemorySize( sin_allocatedbspmem ); + Log_Print( " of BSP memory\n" ); +} //end of the function Sin_AllocMaxBSP + +void Sin_FreeMaxBSP( void ) { + //models + sin_nummodels = 0; + FreeMemory( sin_dmodels ); + sin_dmodels = NULL; + //vis data + sin_visdatasize = 0; + FreeMemory( sin_dvisdata ); + sin_dvisdata = NULL; + sin_dvis = NULL; + //light data + sin_lightdatasize = 0; + FreeMemory( sin_dlightdata ); + sin_dlightdata = NULL; + //entity data + sin_entdatasize = 0; + FreeMemory( sin_dentdata ); + sin_dentdata = NULL; + //leafs + sin_numleafs = 0; + FreeMemory( sin_dleafs ); + sin_dleafs = NULL; + //planes + sin_numplanes = 0; + FreeMemory( sin_dplanes ); + sin_dplanes = NULL; + //vertexes + sin_numvertexes = 0; + FreeMemory( sin_dvertexes ); + sin_dvertexes = NULL; + //nodes + sin_numnodes = 0; + FreeMemory( sin_dnodes ); + sin_dnodes = NULL; + //texture info + sin_numtexinfo = 0; + FreeMemory( sin_texinfo ); + sin_texinfo = NULL; + //faces + sin_numfaces = 0; + FreeMemory( sin_dfaces ); + sin_dfaces = NULL; + //edges + sin_numedges = 0; + FreeMemory( sin_dedges ); + sin_dedges = NULL; + //leaf faces + sin_numleaffaces = 0; + FreeMemory( sin_dleaffaces ); + sin_dleaffaces = NULL; + //leaf brushes + sin_numleafbrushes = 0; + FreeMemory( sin_dleafbrushes ); + sin_dleafbrushes = NULL; + //surface edges + sin_numsurfedges = 0; + FreeMemory( sin_dsurfedges ); + sin_dsurfedges = NULL; + //brushes + sin_numbrushes = 0; + FreeMemory( sin_dbrushes ); + sin_dbrushes = NULL; + //brushsides + sin_numbrushsides = 0; + FreeMemory( sin_dbrushsides ); + sin_dbrushsides = NULL; + //areas + sin_numareas = 0; + FreeMemory( sin_dareas ); + sin_dareas = NULL; + //area portals + sin_numareaportals = 0; + FreeMemory( sin_dareaportals ); + sin_dareaportals = NULL; + //light info + sin_numlightinfo = 0; + FreeMemory( sin_lightinfo ); + sin_lightinfo = NULL; + // + Log_Print( "freed " ); + PrintMemorySize( sin_allocatedbspmem ); + Log_Print( " of BSP memory\n" ); + sin_allocatedbspmem = 0; +} //end of the function Sin_FreeMaxBSP + +#define WCONVEX_EPSILON 0.5 + +//=========================================================================== +// returns the amount the face and the winding overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Sin_FaceOnWinding( sin_dface_t *face, winding_t *winding ) { + int i, edgenum, side; + float dist, area; + sin_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + // + w = CopyWinding( winding ); + memcpy( &plane, &sin_dplanes[face->planenum], sizeof( sin_dplane_t ) ); + //check on which side of the plane the face is + if ( face->side ) { + VectorNegate( plane.normal, plane.normal ); + plane.dist = -plane.dist; + } //end if + for ( i = 0; i < face->numedges && w; i++ ) + { + //get the first and second vertex of the edge + edgenum = sin_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = sin_dvertexes[sin_dedges[abs( edgenum )].v[side]].point; + v2 = sin_dvertexes[sin_dedges[abs( edgenum )].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract( v1, v2, edgevec ); + CrossProduct( edgevec, plane.normal, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, v1 ); + // + ChopWindingInPlace( &w, normal, dist, 0.9 ); //CLIP_EPSILON + } //end for + if ( w ) { + area = WindingArea( w ); + FreeWinding( w ); + return area; + } //end if + return 0; +} //end of the function Sin_FaceOnWinding +//=========================================================================== +// creates a winding for the given brush side on the given brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *Sin_BrushSideWinding( sin_dbrush_t *brush, sin_dbrushside_t *baseside ) { + int i; + sin_dplane_t *baseplane, *plane; + sin_dbrushside_t *side; + winding_t *w; + + //create a winding for the brush side with the given planenumber + baseplane = &sin_dplanes[baseside->planenum]; + w = BaseWindingForPlane( baseplane->normal, baseplane->dist ); + for ( i = 0; i < brush->numsides && w; i++ ) + { + side = &sin_dbrushsides[brush->firstside + i]; + //don't chop with the base plane + if ( side->planenum == baseside->planenum ) { + continue; + } + //also don't use planes that are almost equal + plane = &sin_dplanes[side->planenum]; + if ( DotProduct( baseplane->normal, plane->normal ) > 0.999 + && fabs( baseplane->dist - plane->dist ) < 0.01 ) { + continue; + } + // + plane = &sin_dplanes[side->planenum ^ 1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON); + } //end for + return w; +} //end of the function Sin_BrushSideWinding +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sin_HintSkipBrush( sin_dbrush_t *brush ) { + int j; + sin_dbrushside_t *brushside; + + for ( j = 0; j < brush->numsides; j++ ) + { + brushside = &sin_dbrushsides[brush->firstside + j]; + if ( brushside->texinfo > 0 ) { + if ( sin_texinfo[brushside->texinfo].flags & ( SURF_SKIP | SURF_HINT ) ) { + return true; + } //end if + } //end if + } //end for + return false; +} //end of the function Sin_HintSkipBrush +//=========================================================================== +// fix screwed brush texture references +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsTiny( winding_t *w ); + +void Sin_FixTextureReferences( void ) { + int i, j, k, we; + sin_dbrushside_t *brushside; + sin_dbrush_t *brush; + sin_dface_t *face; + winding_t *w; + + memset( sin_dbrushsidetextured, false, SIN_MAX_MAP_BRUSHSIDES ); + //go over all the brushes + for ( i = 0; i < sin_numbrushes; i++ ) + { + brush = &sin_dbrushes[i]; + //hint brushes are not textured + if ( Sin_HintSkipBrush( brush ) ) { + continue; + } + //go over all the sides of the brush + for ( j = 0; j < brush->numsides; j++ ) + { + brushside = &sin_dbrushsides[brush->firstside + j]; + // + w = Sin_BrushSideWinding( brush, brushside ); + if ( !w ) { + sin_dbrushsidetextured[brush->firstside + j] = true; + continue; + } //end if + else + { + //RemoveEqualPoints(w, 0.2); + if ( WindingIsTiny( w ) ) { + FreeWinding( w ); + sin_dbrushsidetextured[brush->firstside + j] = true; + continue; + } //end if + else + { + we = WindingError( w ); + if ( we == WE_NOTENOUGHPOINTS + || we == WE_SMALLAREA + || we == WE_POINTBOGUSRANGE +// || we == WE_NONCONVEX + ) { + FreeWinding( w ); + sin_dbrushsidetextured[brush->firstside + j] = true; + continue; + } //end if + } //end else + } //end else + if ( WindingArea( w ) < 20 ) { + sin_dbrushsidetextured[brush->firstside + j] = true; + } //end if + //find a face for texturing this brush + for ( k = 0; k < sin_numfaces; k++ ) + { + face = &sin_dfaces[k]; + //if the face is in the same plane as the brush side + if ( ( face->planenum & ~1 ) != ( brushside->planenum & ~1 ) ) { + continue; + } + //if the face is partly or totally on the brush side + if ( Sin_FaceOnWinding( face, w ) ) { + brushside->texinfo = face->texinfo; + sin_dbrushsidetextured[brush->firstside + j] = true; + break; + } //end if + } //end for + FreeWinding( w ); + } //end for + } //end for +} //end of the function Sin_FixTextureReferences*/ + +/* +=============== +CompressVis + +=============== +*/ +int Sin_CompressVis( byte *vis, byte *dest ) { + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = ( sin_dvis->numclusters + 7 ) >> 3; + + for ( j = 0 ; j < visrow ; j++ ) + { + *dest_p++ = vis[j]; + if ( vis[j] ) { + continue; + } + + rep = 1; + for ( j++; j < visrow ; j++ ) + if ( vis[j] || rep == 255 ) { + break; + } else { + rep++; + } + *dest_p++ = rep; + j--; + } + + return dest_p - dest; +} //end of the function Sin_CompressVis + + +/* +=================== +DecompressVis +=================== +*/ +void Sin_DecompressVis( byte *in, byte *decompressed ) { + int c; + byte *out; + int row; + +// row = (r_numvisleafs+7)>>3; + row = ( sin_dvis->numclusters + 7 ) >> 3; + out = decompressed; + + do + { + if ( *in ) { + *out++ = *in++; + continue; + } + + c = in[1]; + if ( !c ) { + Error( "DecompressVis: 0 repeat" ); + } + in += 2; + while ( c ) + { + *out++ = 0; + c--; + } + } while ( out - decompressed < row ); +} //end of the function Sin_DecompressVis + +//============================================================================= + +/* +============= +Sin_SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void Sin_SwapBSPFile( qboolean todisk ) { + int i, j; + sin_dmodel_t *d; + + +// models + for ( i = 0 ; i < sin_nummodels ; i++ ) + { + d = &sin_dmodels[i]; + + d->firstface = LittleLong( d->firstface ); + d->numfaces = LittleLong( d->numfaces ); + d->headnode = LittleLong( d->headnode ); + + for ( j = 0 ; j < 3 ; j++ ) + { + d->mins[j] = LittleFloat( d->mins[j] ); + d->maxs[j] = LittleFloat( d->maxs[j] ); + d->origin[j] = LittleFloat( d->origin[j] ); + } + } + +// +// vertexes +// + for ( i = 0 ; i < sin_numvertexes ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + sin_dvertexes[i].point[j] = LittleFloat( sin_dvertexes[i].point[j] ); + } + +// +// planes +// + for ( i = 0 ; i < sin_numplanes ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + sin_dplanes[i].normal[j] = LittleFloat( sin_dplanes[i].normal[j] ); + sin_dplanes[i].dist = LittleFloat( sin_dplanes[i].dist ); + sin_dplanes[i].type = LittleLong( sin_dplanes[i].type ); + } + +// +// sin_texinfos +// + for ( i = 0; i < sin_numtexinfo; i++ ) + { + for ( j = 0 ; j < 8 ; j++ ) + sin_texinfo[i].vecs[0][j] = LittleFloat( sin_texinfo[i].vecs[0][j] ); +#ifdef SIN + sin_texinfo[i].trans_mag = LittleFloat( sin_texinfo[i].trans_mag ); + sin_texinfo[i].trans_angle = LittleLong( sin_texinfo[i].trans_angle ); + sin_texinfo[i].animtime = LittleFloat( sin_texinfo[i].animtime ); + sin_texinfo[i].nonlit = LittleFloat( sin_texinfo[i].nonlit ); + sin_texinfo[i].translucence = LittleFloat( sin_texinfo[i].translucence ); + sin_texinfo[i].friction = LittleFloat( sin_texinfo[i].friction ); + sin_texinfo[i].restitution = LittleFloat( sin_texinfo[i].restitution ); + sin_texinfo[i].flags = LittleUnsigned( sin_texinfo[i].flags ); +#else + sin_texinfo[i].value = LittleLong( sin_texinfo[i].value ); + sin_texinfo[i].flags = LittleLong( sin_texinfo[i].flags ); +#endif + sin_texinfo[i].nexttexinfo = LittleLong( sin_texinfo[i].nexttexinfo ); + } + +#ifdef SIN +// +// lightinfos +// + for ( i = 0; i < sin_numlightinfo; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + sin_lightinfo[i].color[j] = LittleFloat( sin_lightinfo[i].color[j] ); + } + sin_lightinfo[i].value = LittleLong( sin_lightinfo[i].value ); + sin_lightinfo[i].direct = LittleFloat( sin_lightinfo[i].direct ); + sin_lightinfo[i].directangle = LittleFloat( sin_lightinfo[i].directangle ); + sin_lightinfo[i].directstyle = LittleFloat( sin_lightinfo[i].directstyle ); + } +#endif + +// +// faces +// + for ( i = 0 ; i < sin_numfaces ; i++ ) + { + sin_dfaces[i].texinfo = LittleShort( sin_dfaces[i].texinfo ); +#ifdef SIN + sin_dfaces[i].lightinfo = LittleLong( sin_dfaces[i].lightinfo ); + sin_dfaces[i].planenum = LittleUnsignedShort( sin_dfaces[i].planenum ); +#else + sin_dfaces[i].planenum = LittleShort( sin_dfaces[i].planenum ); +#endif + sin_dfaces[i].side = LittleShort( sin_dfaces[i].side ); + sin_dfaces[i].lightofs = LittleLong( sin_dfaces[i].lightofs ); + sin_dfaces[i].firstedge = LittleLong( sin_dfaces[i].firstedge ); + sin_dfaces[i].numedges = LittleShort( sin_dfaces[i].numedges ); + } + +// +// nodes +// + for ( i = 0 ; i < sin_numnodes ; i++ ) + { + sin_dnodes[i].planenum = LittleLong( sin_dnodes[i].planenum ); + for ( j = 0 ; j < 3 ; j++ ) + { + sin_dnodes[i].mins[j] = LittleShort( sin_dnodes[i].mins[j] ); + sin_dnodes[i].maxs[j] = LittleShort( sin_dnodes[i].maxs[j] ); + } + sin_dnodes[i].children[0] = LittleLong( sin_dnodes[i].children[0] ); + sin_dnodes[i].children[1] = LittleLong( sin_dnodes[i].children[1] ); +#ifdef SIN + sin_dnodes[i].firstface = LittleUnsignedShort( sin_dnodes[i].firstface ); + sin_dnodes[i].numfaces = LittleUnsignedShort( sin_dnodes[i].numfaces ); +#else + sin_dnodes[i].firstface = LittleShort( sin_dnodes[i].firstface ); + sin_dnodes[i].numfaces = LittleShort( sin_dnodes[i].numfaces ); +#endif + } + +// +// leafs +// + for ( i = 0 ; i < sin_numleafs ; i++ ) + { + sin_dleafs[i].contents = LittleLong( sin_dleafs[i].contents ); + sin_dleafs[i].cluster = LittleShort( sin_dleafs[i].cluster ); + sin_dleafs[i].area = LittleShort( sin_dleafs[i].area ); + for ( j = 0 ; j < 3 ; j++ ) + { + sin_dleafs[i].mins[j] = LittleShort( sin_dleafs[i].mins[j] ); + sin_dleafs[i].maxs[j] = LittleShort( sin_dleafs[i].maxs[j] ); + } +#ifdef SIN + sin_dleafs[i].firstleafface = LittleUnsignedShort( sin_dleafs[i].firstleafface ); + sin_dleafs[i].numleaffaces = LittleUnsignedShort( sin_dleafs[i].numleaffaces ); + sin_dleafs[i].firstleafbrush = LittleUnsignedShort( sin_dleafs[i].firstleafbrush ); + sin_dleafs[i].numleafbrushes = LittleUnsignedShort( sin_dleafs[i].numleafbrushes ); +#else + sin_dleafs[i].firstleafface = LittleShort( sin_dleafs[i].firstleafface ); + sin_dleafs[i].numleaffaces = LittleShort( sin_dleafs[i].numleaffaces ); + sin_dleafs[i].firstleafbrush = LittleShort( sin_dleafs[i].firstleafbrush ); + sin_dleafs[i].numleafbrushes = LittleShort( sin_dleafs[i].numleafbrushes ); +#endif + } + +// +// leaffaces +// + for ( i = 0 ; i < sin_numleaffaces ; i++ ) + sin_dleaffaces[i] = LittleShort( sin_dleaffaces[i] ); + +// +// leafbrushes +// + for ( i = 0 ; i < sin_numleafbrushes ; i++ ) + sin_dleafbrushes[i] = LittleShort( sin_dleafbrushes[i] ); + +// +// surfedges +// + for ( i = 0 ; i < sin_numsurfedges ; i++ ) + sin_dsurfedges[i] = LittleLong( sin_dsurfedges[i] ); + +// +// edges +// + for ( i = 0 ; i < sin_numedges ; i++ ) + { +#ifdef SIN + sin_dedges[i].v[0] = LittleUnsignedShort( sin_dedges[i].v[0] ); + sin_dedges[i].v[1] = LittleUnsignedShort( sin_dedges[i].v[1] ); +#else + sin_dedges[i].v[0] = LittleShort( sin_dedges[i].v[0] ); + sin_dedges[i].v[1] = LittleShort( sin_dedges[i].v[1] ); +#endif + } + +// +// brushes +// + for ( i = 0 ; i < sin_numbrushes ; i++ ) + { + sin_dbrushes[i].firstside = LittleLong( sin_dbrushes[i].firstside ); + sin_dbrushes[i].numsides = LittleLong( sin_dbrushes[i].numsides ); + sin_dbrushes[i].contents = LittleLong( sin_dbrushes[i].contents ); + } + +// +// areas +// + for ( i = 0 ; i < sin_numareas ; i++ ) + { + sin_dareas[i].numareaportals = LittleLong( sin_dareas[i].numareaportals ); + sin_dareas[i].firstareaportal = LittleLong( sin_dareas[i].firstareaportal ); + } + +// +// areasportals +// + for ( i = 0 ; i < sin_numareaportals ; i++ ) + { + sin_dareaportals[i].portalnum = LittleLong( sin_dareaportals[i].portalnum ); + sin_dareaportals[i].otherarea = LittleLong( sin_dareaportals[i].otherarea ); + } + +// +// brushsides +// + for ( i = 0 ; i < sin_numbrushsides ; i++ ) + { +#ifdef SIN + sin_dbrushsides[i].planenum = LittleUnsignedShort( sin_dbrushsides[i].planenum ); +#else + sin_dbrushsides[i].planenum = LittleShort( sin_dbrushsides[i].planenum ); +#endif + sin_dbrushsides[i].texinfo = LittleShort( sin_dbrushsides[i].texinfo ); +#ifdef SIN + sin_dbrushsides[i].lightinfo = LittleLong( sin_dbrushsides[i].lightinfo ); +#endif + } + +// +// visibility +// + if ( todisk ) { + j = sin_dvis->numclusters; + } else { + j = LittleLong( sin_dvis->numclusters ); + } + sin_dvis->numclusters = LittleLong( sin_dvis->numclusters ); + for ( i = 0 ; i < j ; i++ ) + { + sin_dvis->bitofs[i][0] = LittleLong( sin_dvis->bitofs[i][0] ); + sin_dvis->bitofs[i][1] = LittleLong( sin_dvis->bitofs[i][1] ); + } +} //end of the function Sin_SwapBSPFile + + +sin_dheader_t *header; +#ifdef SIN +int Sin_CopyLump( int lump, void *dest, int size, int maxsize ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if ( length % size ) { + Error( "Sin_LoadBSPFile: odd lump size" ); + } + + if ( ( length / size ) > maxsize ) { + Error( "Sin_LoadBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lump, ( length / size ), maxsize ); + } + + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} +#else +int Sin_CopyLump( int lump, void *dest, int size ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if ( length % size ) { + Error( "Sin_LoadBSPFile: odd lump size" ); + } + + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} +#endif + +/* +============= +Sin_LoadBSPFile +============= +*/ +void Sin_LoadBSPFile( char *filename, int offset, int length ) { + int i; + +// +// load the file header +// + LoadFile( filename, (void **)&header, offset, length ); + +// swap the header + for ( i = 0 ; i < sizeof( sin_dheader_t ) / 4 ; i++ ) + ( (int *)header )[i] = LittleLong( ( (int *)header )[i] ); + + if ( header->ident != SIN_BSPHEADER && header->ident != SINGAME_BSPHEADER ) { + Error( "%s is not a IBSP file", filename ); + } + if ( header->version != SIN_BSPVERSION && header->version != SINGAME_BSPVERSION ) { + Error( "%s is version %i, not %i", filename, header->version, SIN_BSPVERSION ); + } + +#ifdef SIN + sin_nummodels = Sin_CopyLump( SIN_LUMP_MODELS, sin_dmodels, sizeof( sin_dmodel_t ), SIN_MAX_MAP_MODELS ); + sin_numvertexes = Sin_CopyLump( SIN_LUMP_VERTEXES, sin_dvertexes, sizeof( sin_dvertex_t ), SIN_MAX_MAP_VERTS ); + sin_numplanes = Sin_CopyLump( SIN_LUMP_PLANES, sin_dplanes, sizeof( sin_dplane_t ), SIN_MAX_MAP_PLANES ); + sin_numleafs = Sin_CopyLump( SIN_LUMP_LEAFS, sin_dleafs, sizeof( sin_dleaf_t ), SIN_MAX_MAP_LEAFS ); + sin_numnodes = Sin_CopyLump( SIN_LUMP_NODES, sin_dnodes, sizeof( sin_dnode_t ), SIN_MAX_MAP_NODES ); + sin_numtexinfo = Sin_CopyLump( SIN_LUMP_TEXINFO, sin_texinfo, sizeof( sin_texinfo_t ), SIN_MAX_MAP_TEXINFO ); + sin_numfaces = Sin_CopyLump( SIN_LUMP_FACES, sin_dfaces, sizeof( sin_dface_t ), SIN_MAX_MAP_FACES ); + sin_numleaffaces = Sin_CopyLump( SIN_LUMP_LEAFFACES, sin_dleaffaces, sizeof( sin_dleaffaces[0] ), SIN_MAX_MAP_LEAFFACES ); + sin_numleafbrushes = Sin_CopyLump( SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sizeof( sin_dleafbrushes[0] ), SIN_MAX_MAP_LEAFBRUSHES ); + sin_numsurfedges = Sin_CopyLump( SIN_LUMP_SURFEDGES, sin_dsurfedges, sizeof( sin_dsurfedges[0] ), SIN_MAX_MAP_SURFEDGES ); + sin_numedges = Sin_CopyLump( SIN_LUMP_EDGES, sin_dedges, sizeof( sin_dedge_t ), SIN_MAX_MAP_EDGES ); + sin_numbrushes = Sin_CopyLump( SIN_LUMP_BRUSHES, sin_dbrushes, sizeof( sin_dbrush_t ), SIN_MAX_MAP_BRUSHES ); + sin_numbrushsides = Sin_CopyLump( SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sizeof( sin_dbrushside_t ), SIN_MAX_MAP_BRUSHSIDES ); + sin_numareas = Sin_CopyLump( SIN_LUMP_AREAS, sin_dareas, sizeof( sin_darea_t ), SIN_MAX_MAP_AREAS ); + sin_numareaportals = Sin_CopyLump( SIN_LUMP_AREAPORTALS, sin_dareaportals, sizeof( sin_dareaportal_t ), SIN_MAX_MAP_AREAPORTALS ); + sin_numlightinfo = Sin_CopyLump( SIN_LUMP_LIGHTINFO, sin_lightinfo, sizeof( sin_lightvalue_t ), SIN_MAX_MAP_LIGHTINFO ); + + sin_visdatasize = Sin_CopyLump( SIN_LUMP_VISIBILITY, sin_dvisdata, 1, SIN_MAX_MAP_VISIBILITY ); + sin_lightdatasize = Sin_CopyLump( SIN_LUMP_LIGHTING, sin_dlightdata, 1, SIN_MAX_MAP_LIGHTING ); + sin_entdatasize = Sin_CopyLump( SIN_LUMP_ENTITIES, sin_dentdata, 1, SIN_MAX_MAP_ENTSTRING ); + + Sin_CopyLump( SIN_LUMP_POP, sin_dpop, 1, sizeof( sin_dpop ) ); +#else + sin_nummodels = Sin_CopyLump( SIN_LUMP_MODELS, sin_dmodels, sizeof( sin_dmodel_t ) ); + sin_numvertexes = Sin_CopyLump( SIN_LUMP_VERTEXES, sin_dvertexes, sizeof( sin_dvertex_t ) ); + sin_numplanes = Sin_CopyLump( SIN_LUMP_PLANES, sin_dplanes, sizeof( sin_dplane_t ) ); + sin_numleafs = Sin_CopyLump( SIN_LUMP_LEAFS, sin_dleafs, sizeof( sin_dleaf_t ) ); + sin_numnodes = Sin_CopyLump( SIN_LUMP_NODES, sin_dnodes, sizeof( sin_dnode_t ) ); + sin_numtexinfo = Sin_CopyLump( SIN_LUMP_TEXINFO, sin_texinfo, sizeof( sin_texinfo_t ) ); + sin_numfaces = Sin_CopyLump( SIN_LUMP_FACES, sin_dfaces, sizeof( sin_dface_t ) ); + sin_numleaffaces = Sin_CopyLump( SIN_LUMP_LEAFFACES, sin_dleaffaces, sizeof( sin_dleaffaces[0] ) ); + sin_numleafbrushes = Sin_CopyLump( SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sizeof( sin_dleafbrushes[0] ) ); + sin_numsurfedges = Sin_CopyLump( SIN_LUMP_SURFEDGES, sin_dsurfedges, sizeof( sin_dsurfedges[0] ) ); + sin_numedges = Sin_CopyLump( SIN_LUMP_EDGES, sin_dedges, sizeof( sin_dedge_t ) ); + sin_numbrushes = Sin_CopyLump( SIN_LUMP_BRUSHES, sin_dbrushes, sizeof( sin_dbrush_t ) ); + sin_numbrushsides = Sin_CopyLump( SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sizeof( sin_dbrushside_t ) ); + sin_numareas = Sin_CopyLump( SIN_LUMP_AREAS, sin_dareas, sizeof( sin_darea_t ) ); + sin_numareaportals = Sin_CopyLump( SIN_LUMP_AREAPORTALS, sin_dareaportals, sizeof( sin_dareaportal_t ) ); + + sin_visdatasize = Sin_CopyLump( SIN_LUMP_VISIBILITY, sin_dvisdata, 1 ); + sin_lightdatasize = Sin_CopyLump( SIN_LUMP_LIGHTING, sin_dlightdata, 1 ); + sin_entdatasize = Sin_CopyLump( SIN_LUMP_ENTITIES, sin_dentdata, 1 ); + + Sin_CopyLump( SIN_LUMP_POP, sin_dpop, 1 ); +#endif + + FreeMemory( header ); // everything has been copied out + +// +// swap everything +// + Sin_SwapBSPFile( false ); +} //end of the function Sin_LoadBSPFile + +/* +============= +Sin_LoadBSPFilesTexinfo + +Only loads the sin_texinfo lump, so qdata can scan for textures +============= +*/ +void Sin_LoadBSPFileTexinfo( char *filename ) { + int i; + FILE *f; + int length, ofs; + + header = GetMemory( sizeof( sin_dheader_t ) ); + + f = fopen( filename, "rb" ); + fread( header, sizeof( sin_dheader_t ), 1, f ); + +// swap the header + for ( i = 0 ; i < sizeof( sin_dheader_t ) / 4 ; i++ ) + ( (int *)header )[i] = LittleLong( ( (int *)header )[i] ); + + if ( header->ident != SIN_BSPHEADER && header->ident != SINGAME_BSPHEADER ) { + Error( "%s is not a IBSP file", filename ); + } + if ( header->version != SIN_BSPVERSION && header->version != SINGAME_BSPVERSION ) { + Error( "%s is version %i, not %i", filename, header->version, SIN_BSPVERSION ); + } + + + length = header->lumps[SIN_LUMP_TEXINFO].filelen; + ofs = header->lumps[SIN_LUMP_TEXINFO].fileofs; + + fseek( f, ofs, SEEK_SET ); + fread( sin_texinfo, length, 1, f ); + fclose( f ); + + sin_numtexinfo = length / sizeof( sin_texinfo_t ); + + FreeMemory( header ); // everything has been copied out + + Sin_SwapBSPFile( false ); +} //end of the function Sin_LoadBSPFilesTexinfo + + +//============================================================================ + +FILE *wadfile; +sin_dheader_t outheader; + +#ifdef SIN +void Sin_AddLump( int lumpnum, void *data, int len, int size, int maxsize ) { + sin_lump_t *lump; + int totallength; + + totallength = len * size; + + if ( len > maxsize ) { + Error( "Sin_WriteBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lumpnum, len, maxsize ); + } + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell( wadfile ) ); + lump->filelen = LittleLong( totallength ); + SafeWrite( wadfile, data, ( totallength + 3 ) & ~3 ); +} +#else +void Sin_AddLump( int lumpnum, void *data, int len ) { + sin_lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell( wadfile ) ); + lump->filelen = LittleLong( len ); + SafeWrite( wadfile, data, ( len + 3 ) & ~3 ); +} +#endif +/* +============= +Sin_WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void Sin_WriteBSPFile( char *filename ) { + header = &outheader; + memset( header, 0, sizeof( sin_dheader_t ) ); + + Sin_SwapBSPFile( true ); + + header->ident = LittleLong( SIN_BSPHEADER ); + header->version = LittleLong( SIN_BSPVERSION ); + + wadfile = SafeOpenWrite( filename ); + SafeWrite( wadfile, header, sizeof( sin_dheader_t ) ); // overwritten later + +#ifdef SIN + Sin_AddLump( SIN_LUMP_PLANES, sin_dplanes, sin_numplanes, sizeof( sin_dplane_t ), SIN_MAX_MAP_PLANES ); + Sin_AddLump( SIN_LUMP_LEAFS, sin_dleafs, sin_numleafs, sizeof( sin_dleaf_t ), SIN_MAX_MAP_LEAFS ); + Sin_AddLump( SIN_LUMP_VERTEXES, sin_dvertexes, sin_numvertexes, sizeof( sin_dvertex_t ), SIN_MAX_MAP_VERTS ); + Sin_AddLump( SIN_LUMP_NODES, sin_dnodes, sin_numnodes, sizeof( sin_dnode_t ), SIN_MAX_MAP_NODES ); + Sin_AddLump( SIN_LUMP_TEXINFO, sin_texinfo, sin_numtexinfo, sizeof( sin_texinfo_t ), SIN_MAX_MAP_TEXINFO ); + Sin_AddLump( SIN_LUMP_FACES, sin_dfaces, sin_numfaces, sizeof( sin_dface_t ), SIN_MAX_MAP_FACES ); + Sin_AddLump( SIN_LUMP_BRUSHES, sin_dbrushes, sin_numbrushes, sizeof( sin_dbrush_t ), SIN_MAX_MAP_BRUSHES ); + Sin_AddLump( SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sin_numbrushsides, sizeof( sin_dbrushside_t ), SIN_MAX_MAP_BRUSHSIDES ); + Sin_AddLump( SIN_LUMP_LEAFFACES, sin_dleaffaces, sin_numleaffaces, sizeof( sin_dleaffaces[0] ), SIN_MAX_MAP_LEAFFACES ); + Sin_AddLump( SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sin_numleafbrushes, sizeof( sin_dleafbrushes[0] ), SIN_MAX_MAP_LEAFBRUSHES ); + Sin_AddLump( SIN_LUMP_SURFEDGES, sin_dsurfedges, sin_numsurfedges, sizeof( sin_dsurfedges[0] ), SIN_MAX_MAP_SURFEDGES ); + Sin_AddLump( SIN_LUMP_EDGES, sin_dedges, sin_numedges, sizeof( sin_dedge_t ), SIN_MAX_MAP_EDGES ); + Sin_AddLump( SIN_LUMP_MODELS, sin_dmodels, sin_nummodels, sizeof( sin_dmodel_t ), SIN_MAX_MAP_MODELS ); + Sin_AddLump( SIN_LUMP_AREAS, sin_dareas, sin_numareas, sizeof( sin_darea_t ), SIN_MAX_MAP_AREAS ); + Sin_AddLump( SIN_LUMP_AREAPORTALS, sin_dareaportals, sin_numareaportals, sizeof( sin_dareaportal_t ), SIN_MAX_MAP_AREAPORTALS ); + Sin_AddLump( SIN_LUMP_LIGHTINFO, sin_lightinfo, sin_numlightinfo, sizeof( sin_lightvalue_t ), SIN_MAX_MAP_LIGHTINFO ); + + Sin_AddLump( SIN_LUMP_LIGHTING, sin_dlightdata, sin_lightdatasize, 1, SIN_MAX_MAP_LIGHTING ); + Sin_AddLump( SIN_LUMP_VISIBILITY, sin_dvisdata, sin_visdatasize, 1, SIN_MAX_MAP_VISIBILITY ); + Sin_AddLump( SIN_LUMP_ENTITIES, sin_dentdata, sin_entdatasize, 1, SIN_MAX_MAP_ENTSTRING ); + Sin_AddLump( SIN_LUMP_POP, sin_dpop, sizeof( sin_dpop ), 1, sizeof( sin_dpop ) ); +#else + Sin_AddLump( SIN_LUMP_PLANES, sin_dplanes, sin_numplanes * sizeof( sin_dplane_t ) ); + Sin_AddLump( SIN_LUMP_LEAFS, sin_dleafs, sin_numleafs * sizeof( sin_dleaf_t ) ); + Sin_AddLump( SIN_LUMP_VERTEXES, sin_dvertexes, sin_numvertexes * sizeof( sin_dvertex_t ) ); + Sin_AddLump( SIN_LUMP_NODES, sin_dnodes, sin_numnodes * sizeof( sin_dnode_t ) ); + Sin_AddLump( SIN_LUMP_TEXINFO, sin_texinfo, sin_numtexinfo * sizeof( sin_texinfo_t ) ); + Sin_AddLump( SIN_LUMP_FACES, sin_dfaces, sin_numfaces * sizeof( sin_dface_t ) ); + Sin_AddLump( SIN_LUMP_BRUSHES, sin_dbrushes, sin_numbrushes * sizeof( sin_dbrush_t ) ); + Sin_AddLump( SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sin_numbrushsides * sizeof( sin_dbrushside_t ) ); + Sin_AddLump( SIN_LUMP_LEAFFACES, sin_dleaffaces, sin_numleaffaces * sizeof( sin_dleaffaces[0] ) ); + Sin_AddLump( SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sin_numleafbrushes * sizeof( sin_dleafbrushes[0] ) ); + Sin_AddLump( SIN_LUMP_SURFEDGES, sin_dsurfedges, sin_numsurfedges * sizeof( sin_dsurfedges[0] ) ); + Sin_AddLump( SIN_LUMP_EDGES, sin_dedges, sin_numedges * sizeof( sin_dedge_t ) ); + Sin_AddLump( SIN_LUMP_MODELS, sin_dmodels, sin_nummodels * sizeof( sin_dmodel_t ) ); + Sin_AddLump( SIN_LUMP_AREAS, sin_dareas, sin_numareas * sizeof( sin_darea_t ) ); + Sin_AddLump( SIN_LUMP_AREAPORTALS, sin_dareaportals, sin_numareaportals * sizeof( sin_dareaportal_t ) ); + + Sin_AddLump( SIN_LUMP_LIGHTING, sin_dlightdata, sin_lightdatasize ); + Sin_AddLump( SIN_LUMP_VISIBILITY, sin_dvisdata, sin_visdatasize ); + Sin_AddLump( SIN_LUMP_ENTITIES, sin_dentdata, sin_entdatasize ); + Sin_AddLump( SIN_LUMP_POP, sin_dpop, sizeof( sin_dpop ) ); +#endif + + fseek( wadfile, 0, SEEK_SET ); + SafeWrite( wadfile, header, sizeof( sin_dheader_t ) ); + fclose( wadfile ); +} + +//============================================================================ + + +//============================================ + +/* +================ +ParseEntities + +Parses the sin_dentdata string into entities +================ +*/ +void Sin_ParseEntities( void ) { + script_t *script; + + num_entities = 0; + script = LoadScriptMemory( sin_dentdata, sin_entdatasize, "*sin bsp file" ); + SetScriptFlags( script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS ); + + while ( ParseEntity( script ) ) + { + } //end while + + FreeScript( script ); +} //end of the function Sin_ParseEntities + + +/* +================ +UnparseEntities + +Generates the sin_dentdata string from all the entities +================ +*/ +void Sin_UnparseEntities( void ) { + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + buf = sin_dentdata; + end = buf; + *end = 0; + + for ( i = 0 ; i < num_entities ; i++ ) + { + ep = entities[i].epairs; + if ( !ep ) { + continue; // ent got removed + + } + strcat( end,"{\n" ); + end += 2; + + for ( ep = entities[i].epairs ; ep ; ep = ep->next ) + { + strcpy( key, ep->key ); + StripTrailing( key ); + strcpy( value, ep->value ); + StripTrailing( value ); + + sprintf( line, "\"%s\" \"%s\"\n", key, value ); + strcat( end, line ); + end += strlen( line ); + } + strcat( end,"}\n" ); + end += 2; + + if ( end > buf + SIN_MAX_MAP_ENTSTRING ) { + Error( "Entity text too long" ); + } + } + sin_entdatasize = end - buf + 1; +} //end of the function Sin_UnparseEntities + +#ifdef SIN +void FreeValueKeys( entity_t *ent ) { + epair_t *ep,*next; + + for ( ep = ent->epairs ; ep ; ep = next ) + { + next = ep->next; + FreeMemory( ep->value ); + FreeMemory( ep->key ); + FreeMemory( ep ); + } + ent->epairs = NULL; +} +#endif + +/* +============= +Sin_PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void Sin_PrintBSPFileSizes( void ) { + if ( !num_entities ) { + Sin_ParseEntities(); + } + + Log_Print( "%6i models %7i\n" + ,sin_nummodels, (int)( sin_nummodels * sizeof( sin_dmodel_t ) ) ); + Log_Print( "%6i brushes %7i\n" + ,sin_numbrushes, (int)( sin_numbrushes * sizeof( sin_dbrush_t ) ) ); + Log_Print( "%6i brushsides %7i\n" + ,sin_numbrushsides, (int)( sin_numbrushsides * sizeof( sin_dbrushside_t ) ) ); + Log_Print( "%6i planes %7i\n" + ,sin_numplanes, (int)( sin_numplanes * sizeof( sin_dplane_t ) ) ); + Log_Print( "%6i texinfo %7i\n" + ,sin_numtexinfo, (int)( sin_numtexinfo * sizeof( sin_texinfo_t ) ) ); +#ifdef SIN + Log_Print( "%6i lightinfo %7i\n" + ,sin_numlightinfo, (int)( sin_numlightinfo * sizeof( sin_lightvalue_t ) ) ); +#endif + Log_Print( "%6i entdata %7i\n", num_entities, sin_entdatasize ); + + Log_Print( "\n" ); + + Log_Print( "%6i vertexes %7i\n" + ,sin_numvertexes, (int)( sin_numvertexes * sizeof( sin_dvertex_t ) ) ); + Log_Print( "%6i nodes %7i\n" + ,sin_numnodes, (int)( sin_numnodes * sizeof( sin_dnode_t ) ) ); + Log_Print( "%6i faces %7i\n" + ,sin_numfaces, (int)( sin_numfaces * sizeof( sin_dface_t ) ) ); + Log_Print( "%6i leafs %7i\n" + ,sin_numleafs, (int)( sin_numleafs * sizeof( sin_dleaf_t ) ) ); + Log_Print( "%6i leaffaces %7i\n" + ,sin_numleaffaces, (int)( sin_numleaffaces * sizeof( sin_dleaffaces[0] ) ) ); + Log_Print( "%6i leafbrushes %7i\n" + ,sin_numleafbrushes, (int)( sin_numleafbrushes * sizeof( sin_dleafbrushes[0] ) ) ); + Log_Print( "%6i surfedges %7i\n" + ,sin_numsurfedges, (int)( sin_numsurfedges * sizeof( sin_dsurfedges[0] ) ) ); + Log_Print( "%6i edges %7i\n" + ,sin_numedges, (int)( sin_numedges * sizeof( sin_dedge_t ) ) ); + Log_Print( " lightdata %7i\n", sin_lightdatasize ); + Log_Print( " visdata %7i\n", sin_visdatasize ); +} diff --git a/src/bspc/l_bsp_sin.h b/src/bspc/l_bsp_sin.h new file mode 100644 index 0000000..0195a8d --- /dev/null +++ b/src/bspc/l_bsp_sin.h @@ -0,0 +1,113 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "sinfiles.h" + +#define SINGAME_BSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'R' ) //RBSP +#define SINGAME_BSPVERSION 1 + +#define SIN_BSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) //IBSP +#define SIN_BSPVERSION 41 + + +extern int sin_nummodels; +extern sin_dmodel_t *sin_dmodels; //[MAX_MAP_MODELS]; + +extern int sin_visdatasize; +extern byte *sin_dvisdata; //[MAX_MAP_VISIBILITY]; +extern sin_dvis_t *sin_dvis; // = (dvis_t *)sin_sin_dvisdata; + +extern int sin_lightdatasize; +extern byte *sin_dlightdata; //[MAX_MAP_LIGHTING]; + +extern int sin_entdatasize; +extern char *sin_dentdata; //[MAX_MAP_ENTSTRING]; + +extern int sin_numleafs; +extern sin_dleaf_t *sin_dleafs; //[MAX_MAP_LEAFS]; + +extern int sin_numplanes; +extern sin_dplane_t *sin_dplanes; //[MAX_MAP_PLANES]; + +extern int sin_numvertexes; +extern sin_dvertex_t *sin_dvertexes; //[MAX_MAP_VERTS]; + +extern int sin_numnodes; +extern sin_dnode_t *sin_dnodes; //[MAX_MAP_NODES]; + +extern int sin_numtexinfo; +extern sin_texinfo_t *sin_texinfo; //[MAX_MAP_sin_texinfo]; + +extern int sin_numfaces; +extern sin_dface_t *sin_dfaces; //[MAX_MAP_FACES]; + +extern int sin_numedges; +extern sin_dedge_t *sin_dedges; //[MAX_MAP_EDGES]; + +extern int sin_numleaffaces; +extern unsigned short *sin_dleaffaces; //[MAX_MAP_LEAFFACES]; + +extern int sin_numleafbrushes; +extern unsigned short *sin_dleafbrushes; //[MAX_MAP_LEAFBRUSHES]; + +extern int sin_numsurfedges; +extern int *sin_dsurfedges; //[MAX_MAP_SURFEDGES]; + +extern int sin_numbrushes; +extern sin_dbrush_t *sin_dbrushes; //[MAX_MAP_BRUSHES]; + +extern int sin_numbrushsides; +extern sin_dbrushside_t *sin_dbrushsides; //[MAX_MAP_BRUSHSIDES]; + +extern int sin_numareas; +extern sin_darea_t *sin_dareas; //[MAX_MAP_AREAS]; + +extern int sin_numareaportals; +extern sin_dareaportal_t *sin_dareaportals; //[MAX_MAP_AREAPORTALS]; + +extern int sin_numlightinfo; +extern sin_lightvalue_t *sin_lightinfo; //[MAX_MAP_LIGHTINFO]; + +extern byte sin_dpop[256]; + +extern char sin_dbrushsidetextured[SIN_MAX_MAP_BRUSHSIDES]; + +void Sin_AllocMaxBSP( void ); +void Sin_FreeMaxBSP( void ); + +void Sin_DecompressVis( byte *in, byte *decompressed ); +int Sin_CompressVis( byte *vis, byte *dest ); + +void Sin_LoadBSPFile( char *filename, int offset, int length ); +void Sin_LoadBSPFileTexinfo( char *filename ); // just for qdata +void Sin_WriteBSPFile( char *filename ); +void Sin_PrintBSPFileSizes( void ); +void Sin_ParseEntities( void ); +void Sin_UnparseEntities( void ); + diff --git a/src/bspc/l_cmd.c b/src/bspc/l_cmd.c new file mode 100644 index 0000000..4836da1 --- /dev/null +++ b/src/bspc/l_cmd.c @@ -0,0 +1,1182 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// cmdlib.c + +#include "l_cmd.h" +#include "l_log.h" +#include "l_mem.h" +#include +#include + +#ifndef SIN +#define SIN +#endif //SIN + +#if defined( WIN32 ) || defined( _WIN32 ) +#include +#else +#include +#endif + +#ifdef NeXT +#include +#endif + +#define BASEDIRNAME "wolf" +#define PATHSEPERATOR '/' + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; +qboolean com_eof; + +qboolean archive; +char archivedir[1024]; + + +/* +=================== +ExpandWildcards + +Mimic unix command line expansion +=================== +*/ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#ifdef _WIN32 +#include "io.h" +void ExpandWildcards( int *argc, char ***argv ) { + struct _finddata_t fileinfo; + int handle; + int i; + char filename[1024]; + char filebase[1024]; + char *path; + + ex_argc = 0; + for ( i = 0 ; i < *argc ; i++ ) + { + path = ( *argv )[i]; + if ( path[0] == '-' + || ( !strstr( path, "*" ) && !strstr( path, "?" ) ) ) { + ex_argv[ex_argc++] = path; + continue; + } + + handle = _findfirst( path, &fileinfo ); + if ( handle == -1 ) { + return; + } + + ExtractFilePath( path, filebase ); + + do + { + sprintf( filename, "%s%s", filebase, fileinfo.name ); + ex_argv[ex_argc++] = copystring( filename ); + } while ( _findnext( handle, &fileinfo ) != -1 ); + + _findclose( handle ); + } + + *argc = ex_argc; + *argv = ex_argv; +} +#else +void ExpandWildcards( int *argc, char ***argv ) { +} +#endif + +#ifdef WINBSPC + +#include + +HWND program_hwnd; + +void SetProgramHandle( HWND hwnd ) { + program_hwnd = hwnd; +} //end of the function SetProgramHandle + +/* +================= +Error + +For abnormal program terminations in windowed apps +================= +*/ +void Error( char *error, ... ) { + va_list argptr; + char text[1024]; + char text2[1024]; + int err; + + err = GetLastError(); + + va_start( argptr, error ); + vsprintf( text, error, argptr ); + va_end( argptr ); + + sprintf( text2, "%s\nGetLastError() = %i", text, err ); + MessageBox( program_hwnd, text2, "Error", 0 /* MB_OK */ ); + + Log_Write( text ); + Log_Close(); + + exit( 1 ); +} //end of the function Error + +void Warning( char *szFormat, ... ) { + char szBuffer[256]; + va_list argptr; + + va_start( argptr, szFormat ); + vsprintf( szBuffer, szFormat, argptr ); + va_end( argptr ); + + MessageBox( program_hwnd, szBuffer, "Warning", MB_OK ); + + Log_Write( szBuffer ); +} //end of the function Warning + + +#else +/* +================= +Error + +For abnormal program terminations in console apps +================= +*/ +void Error( char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, error ); + vsprintf( text, error, argptr ); + va_end( argptr ); + printf( "ERROR: %s\n", text ); + + Log_Write( text ); + Log_Close(); + + exit( 1 ); +} //end of the function Error + +void Warning( char *warning, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, warning ); + vsprintf( text, warning, argptr ); + va_end( argptr ); + printf( "WARNING: %s\n", text ); + + Log_Write( text ); +} //end of the function Warning + +#endif + +//only printf if in verbose mode +qboolean verbose = true; + +void qprintf( char *format, ... ) { + va_list argptr; +#ifdef WINBSPC + char buf[2048]; +#endif //WINBSPC + + if ( !verbose ) { + return; + } + + va_start( argptr,format ); +#ifdef WINBSPC + vsprintf( buf, format, argptr ); + WinBSPCPrint( buf ); +#else + vprintf( format, argptr ); +#endif //WINBSPC + va_end( argptr ); +} //end of the function qprintf + +void Com_Error( int level, char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, error ); + vsprintf( text, error, argptr ); + va_end( argptr ); + Error( text ); +} //end of the funcion Com_Error + +void Com_Printf( const char *fmt, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + Log_Print( text ); +} //end of the funcion Com_Printf + +/* + +qdir will hold the path up to the quake directory, including the slash + + f:\quake \ + /raid/quake/ + +gamedir will hold qdir + the game directory (id1, id2, etc) + + */ + +char qdir[1024]; +char gamedir[1024]; + +void SetQdirFromPath( char *path ) { + char temp[1024]; + char *c; + int len; + + if ( !( path[0] == '/' || path[0] == '\\' || path[1] == ':' ) ) { // path is partial + Q_getwd( temp ); + strcat( temp, path ); + path = temp; + } + + // search for "quake2" in path + + len = strlen( BASEDIRNAME ); + for ( c = path + strlen( path ) - 1 ; c != path ; c-- ) + if ( !Q_strncasecmp( c, BASEDIRNAME, len ) ) { + strncpy( qdir, path, c + len + 1 - path ); + qprintf( "qdir: %s\n", qdir ); + c += len + 1; + while ( *c ) + { + if ( *c == '/' || *c == '\\' ) { + strncpy( gamedir, path, c + 1 - path ); + qprintf( "gamedir: %s\n", gamedir ); + return; + } + c++; + } + Error( "No gamedir in %s", path ); + return; + } + Error( "SetQdirFromPath: no '%s' in %s", BASEDIRNAME, path ); +} + +char *ExpandArg( char *path ) { + static char full[1024]; + + if ( path[0] != '/' && path[0] != '\\' && path[1] != ':' ) { + Q_getwd( full ); + strcat( full, path ); + } else { + strcpy( full, path ); + } + return full; +} + +char *ExpandPath( char *path ) { + static char full[1024]; + if ( !qdir ) { + Error( "ExpandPath called without qdir set" ); + } + if ( path[0] == '/' || path[0] == '\\' || path[1] == ':' ) { + return path; + } + sprintf( full, "%s%s", qdir, path ); + return full; +} + +char *ExpandPathAndArchive( char *path ) { + char *expanded; + char archivename[1024]; + + expanded = ExpandPath( path ); + + if ( archive ) { + sprintf( archivename, "%s/%s", archivedir, path ); + QCopyFile( expanded, archivename ); + } + return expanded; +} + + +char *copystring( char *s ) { + char *b; + b = GetMemory( strlen( s ) + 1 ); + strcpy( b, s ); + return b; +} + + + +/* +================ +I_FloatTime +================ +*/ +double I_FloatTime( void ) { + time_t t; + + time( &t ); + + return t; +#if 0 +// more precise, less portable + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday( &tp, &tzp ); + + if ( !secbase ) { + secbase = tp.tv_sec; + return tp.tv_usec / 1000000.0; + } + + return ( tp.tv_sec - secbase ) + tp.tv_usec / 1000000.0; +#endif +} + +void Q_getwd( char *out ) { +#if defined( WIN32 ) || defined( _WIN32 ) + getcwd( out, 256 ); + strcat( out, "\\" ); +#else + getwd( out ); + strcat( out, "/" ); +#endif +} + + +void Q_mkdir( char *path ) { +#ifdef WIN32 + if ( _mkdir( path ) != -1 ) { + return; + } +#else + if ( mkdir( path, 0777 ) != -1 ) { + return; + } +#endif + if ( errno != EEXIST ) { + Error( "mkdir %s: %s",path, strerror( errno ) ); + } +} + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime( char *path ) { + struct stat buf; + + if ( stat( path,&buf ) == -1 ) { + return -1; + } + + return buf.st_mtime; +} + + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse( 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 ) { + com_eof = true; + return NULL; // end of file; + } + data++; + } + +// skip // comments + if ( c == '/' && data[1] == '/' ) { + while ( *data && *data != '\n' ) + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if ( c == '\"' ) { + data++; + do + { + c = *data++; + if ( c == '\"' ) { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while ( 1 ); + } + +// 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; + if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) { + break; + } + } while ( c > 32 ); + + com_token[len] = 0; + return data; +} + + +int Q_strncasecmp( char *s1, char *s2, int n ) { + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + + } + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + if ( c1 != c2 ) { + return -1; // strings not equal + } + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_strcasecmp( char *s1, char *s2 ) { + return Q_strncasecmp( s1, s2, 99999 ); +} + +int Q_stricmp( char *s1, char *s2 ) { + return Q_strncasecmp( s1, s2, 99999 ); +} + +void Q_strncpyz( char *dest, const char *src, int destsize ) { + strncpy( dest, src, destsize - 1 ); + dest[destsize - 1] = 0; +} + +char *strupr( char *start ) { + char *in; + in = start; + while ( *in ) + { + *in = toupper( *in ); + in++; + } + return start; +} + +char *strlower( char *start ) { + char *in; + in = start; + while ( *in ) + { + *in = tolower( *in ); + in++; + } + return start; +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm( char *check ) { + int i; + + for ( i = 1; i < myargc; i++ ) + { + if ( !Q_strcasecmp( check, myargv[i] ) ) { + return i; + } + } + + return 0; +} + + + +/* +================ +Q_filelength +================ +*/ +int Q_filelength( FILE *f ) { + int pos; + int end; + + pos = ftell( f ); + fseek( f, 0, SEEK_END ); + end = ftell( f ); + fseek( f, pos, SEEK_SET ); + + return end; +} + + +FILE *SafeOpenWrite( char *filename ) { + FILE *f; + + f = fopen( filename, "wb" ); + + if ( !f ) { + Error( "Error opening %s: %s",filename,strerror( errno ) ); + } + + return f; +} + +FILE *SafeOpenRead( char *filename ) { + FILE *f; + + f = fopen( filename, "rb" ); + + if ( !f ) { + Error( "Error opening %s: %s",filename,strerror( errno ) ); + } + + return f; +} + + +void SafeRead( FILE *f, void *buffer, int count ) { + if ( fread( buffer, 1, count, f ) != (size_t)count ) { + Error( "File read failure" ); + } +} + + +void SafeWrite( FILE *f, void *buffer, int count ) { + if ( fwrite( buffer, 1, count, f ) != (size_t)count ) { + Error( "File write failure" ); + } +} + + +/* +============== +FileExists +============== +*/ +qboolean FileExists( char *filename ) { + FILE *f; + + f = fopen( filename, "r" ); + if ( !f ) { + return false; + } + fclose( f ); + return true; +} + +/* +============== +LoadFile +============== +*/ +int LoadFile( char *filename, void **bufferptr, int offset, int length ) { + FILE *f; + void *buffer; + + f = SafeOpenRead( filename ); + fseek( f, offset, SEEK_SET ); + if ( !length ) { + length = Q_filelength( f ); + } + buffer = GetMemory( length + 1 ); + ( (char *)buffer )[length] = 0; + SafeRead( f, buffer, length ); + fclose( f ); + + *bufferptr = buffer; + return length; +} + + +/* +============== +TryLoadFile + +Allows failure +============== +*/ +int TryLoadFile( char *filename, void **bufferptr ) { + FILE *f; + int length; + void *buffer; + + *bufferptr = NULL; + + f = fopen( filename, "rb" ); + if ( !f ) { + return -1; + } + length = Q_filelength( f ); + buffer = GetMemory( length + 1 ); + ( (char *)buffer )[length] = 0; + SafeRead( f, buffer, length ); + fclose( f ); + + *bufferptr = buffer; + return length; +} + + +/* +============== +SaveFile +============== +*/ +void SaveFile( char *filename, void *buffer, int count ) { + FILE *f; + + f = SafeOpenWrite( filename ); + SafeWrite( f, buffer, count ); + fclose( f ); +} + + + +void DefaultExtension( char *path, char *extension ) { + char *src; +// +// if path doesnt have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen( path ) - 1; + + while ( *src != PATHSEPERATOR && src != path ) + { + if ( *src == '.' ) { + return; // it has an extension + } + src--; + } + + strcat( path, extension ); +} + + +void DefaultPath( char *path, char *basepath ) { + char temp[128]; + + if ( path[0] == PATHSEPERATOR ) { + return; // absolute path location + } + strcpy( temp,path ); + strcpy( path,basepath ); + strcat( path,temp ); +} + + +void StripFilename( char *path ) { + int length; + + length = strlen( path ) - 1; + while ( length > 0 && path[length] != PATHSEPERATOR ) + length--; + path[length] = 0; +} + +void StripExtension( char *path ) { + int length; + + length = strlen( path ) - 1; + while ( length > 0 && path[length] != '.' ) + { + length--; + if ( path[length] == '/' ) { + return; // no extension + } + } + if ( length ) { + path[length] = 0; + } +} + + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash +void ExtractFilePath( char *path, char *dest ) { + char *src; + + src = path + strlen( path ) - 1; + +// +// back up until a \ or the start +// + while ( src != path && *( src - 1 ) != '\\' && *( src - 1 ) != '/' ) + src--; + + memcpy( dest, path, src - path ); + dest[src - path] = 0; +} + +void ExtractFileBase( char *path, char *dest ) { + char *src; + + src = path + strlen( path ) - 1; + +// +// back up until a \ or the start +// + while ( src != path && *( src - 1 ) != '\\' && *( src - 1 ) != '/' ) + src--; + + while ( *src && *src != '.' ) + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension( char *path, char *dest ) { + char *src; + + src = path + strlen( path ) - 1; + +// +// back up until a . or the start +// + while ( src != path && *( src - 1 ) != '.' ) + src--; + if ( src == path ) { + *dest = 0; // no extension + return; + } + + strcpy( dest,src ); +} + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex( char *hex ) { + char *str; + int num; + + num = 0; + str = hex; + + while ( *str ) + { + num <<= 4; + if ( *str >= '0' && *str <= '9' ) { + num += *str - '0'; + } else if ( *str >= 'a' && *str <= 'f' ) { + num += 10 + *str - 'a'; + } else if ( *str >= 'A' && *str <= 'F' ) { + num += 10 + *str - 'A'; + } else { + Error( "Bad hex number: %s",hex ); + } + str++; + } + + return num; +} + + +int ParseNum( char *str ) { + if ( str[0] == '$' ) { + return ParseHex( str + 1 ); + } + if ( str[0] == '0' && str[1] == 'x' ) { + return ParseHex( str + 2 ); + } + return atol( str ); +} + + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ + +short LittleShort( short l ) { + byte b1,b2; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + + return ( b1 << 8 ) + b2; +} + +short BigShort( short l ) { + return l; +} + + +int LittleLong( 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 BigLong( int l ) { + return l; +} + + +float LittleFloat( float l ) { + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat( float l ) { + return l; +} + +#ifdef SIN +unsigned short LittleUnsignedShort( unsigned short l ) { + byte b1,b2; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + + return ( b1 << 8 ) + b2; +} + +unsigned short BigUnsignedShort( unsigned short l ) { + return l; +} + +unsigned LittleUnsigned( unsigned l ) { + byte b1,b2,b3,b4; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + b3 = ( l >> 16 ) & 255; + b4 = ( l >> 24 ) & 255; + + return ( (unsigned)b1 << 24 ) + ( (unsigned)b2 << 16 ) + ( (unsigned)b3 << 8 ) + b4; +} + +unsigned BigUnsigned( unsigned l ) { + return l; +} +#endif + + +#else + + +short BigShort( short l ) { + byte b1,b2; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + + return ( b1 << 8 ) + b2; +} + +short LittleShort( short l ) { + return l; +} + + +int BigLong( 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 LittleLong( int l ) { + return l; +} + +float BigFloat( float l ) { + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat( float l ) { + return l; +} + +#ifdef SIN +unsigned short BigUnsignedShort( unsigned short l ) { + byte b1,b2; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + + return ( b1 << 8 ) + b2; +} + +unsigned short LittleUnsignedShort( unsigned short l ) { + return l; +} + + +unsigned BigUnsigned( unsigned l ) { + byte b1,b2,b3,b4; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + b3 = ( l >> 16 ) & 255; + b4 = ( l >> 24 ) & 255; + + return ( (unsigned)b1 << 24 ) + ( (unsigned)b2 << 16 ) + ( (unsigned)b3 << 8 ) + b4; +} + +unsigned LittleUnsigned( unsigned l ) { + return l; +} +#endif + + +#endif + + +//======================================================= + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[257] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, + 0x0000 // code reaches element 256 +}; + +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; +} +//============================================================================= + +/* +============ +CreatePath +============ +*/ +void CreatePath( char *path ) { + char *ofs, c; + + if ( path[1] == ':' ) { + path += 2; + } + + for ( ofs = path + 1 ; *ofs ; ofs++ ) + { + c = *ofs; + if ( c == '/' || c == '\\' ) { // create the directory + *ofs = 0; + Q_mkdir( path ); + *ofs = c; + } + } +} + + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile( char *from, char *to ) { + void *buffer; + int length; + + length = LoadFile( from, &buffer, 0, 0 ); + CreatePath( to ); + SaveFile( to, buffer, length ); + FreeMemory( buffer ); +} + +void FS_FreeFile( void *buf ) { + FreeMemory( buf ); +} //end of the function FS_FreeFile + +int FS_ReadFileAndCache( const char *qpath, void **buffer ) { + return LoadFile( (char *) qpath, buffer, 0, 0 ); +} //end of the function FS_ReadFileAndCache + +int FS_FOpenFileRead( const char *filename, FILE **file, qboolean uniqueFILE ) { + *file = fopen( filename, "rb" ); + return ( *file != NULL ); +} //end of the function FS_FOpenFileRead diff --git a/src/bspc/l_cmd.h b/src/bspc/l_cmd.h new file mode 100644 index 0000000..2f9f1b6 --- /dev/null +++ b/src/bspc/l_cmd.h @@ -0,0 +1,164 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cmdlib.h + +#ifndef SIN +#define SIN +#endif //SIN + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#ifdef _WIN32 +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncate from double to float +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifndef __BYTEBOOL__ +#define __BYTEBOOL__ +typedef enum {false, true} qboolean; +typedef unsigned char byte; +#endif + +// the dec offsetof macro doesnt work very well... +#define myoffsetof( type,identifier ) ( (size_t)&( (type *)0 )->identifier ) + + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +char *strupr( char *in ); +char *strlower( char *in ); +int Q_strncasecmp( char *s1, char *s2, int n ); +int Q_strcasecmp( char *s1, char *s2 ); +void Q_getwd( char *out ); + +int Q_filelength( FILE *f ); +int FileTime( char *path ); + +void Q_mkdir( char *path ); + +extern char qdir[1024]; +extern char gamedir[1024]; +void SetQdirFromPath( char *path ); +char *ExpandArg( char *path ); // from cmd line +char *ExpandPath( char *path ); // from scripts +char *ExpandPathAndArchive( char *path ); + + +double I_FloatTime( void ); + +void Error( char *error, ... ); +void Warning( char *warning, ... ); + +int CheckParm( char *check ); + +FILE *SafeOpenWrite( char *filename ); +FILE *SafeOpenRead( char *filename ); +void SafeRead( FILE *f, void *buffer, int count ); +void SafeWrite( FILE *f, void *buffer, int count ); + +int LoadFile( char *filename, void **bufferptr, int offset, int length ); +int TryLoadFile( char *filename, void **bufferptr ); +void SaveFile( char *filename, void *buffer, int count ); +qboolean FileExists( char *filename ); + +void DefaultExtension( char *path, char *extension ); +void DefaultPath( char *path, char *basepath ); +void StripFilename( char *path ); +void StripExtension( char *path ); + +void ExtractFilePath( char *path, char *dest ); +void ExtractFileBase( char *path, char *dest ); +void ExtractFileExtension( char *path, char *dest ); + +int ParseNum( char *str ); + +short BigShort( short l ); +short LittleShort( short l ); +int BigLong( int l ); +int LittleLong( int l ); +float BigFloat( float l ); +float LittleFloat( float l ); + +#ifdef SIN +unsigned short BigUnsignedShort( unsigned short l ); +unsigned short LittleUnsignedShort( unsigned short l ); +unsigned BigUnsigned( unsigned l ); +unsigned LittleUnsigned( unsigned l ); +#endif + + +char *COM_Parse( char *data ); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring( char *s ); + + +void CRC_Init( unsigned short *crcvalue ); +void CRC_ProcessByte( unsigned short *crcvalue, byte data ); +unsigned short CRC_Value( unsigned short crcvalue ); + +void CreatePath( char *path ); +void QCopyFile( char *from, char *to ); + +extern qboolean archive; +extern char archivedir[1024]; + + +extern qboolean verbose; +void qprintf( char *format, ... ); + +void ExpandWildcards( int *argc, char ***argv ); + + +// for compression routines +typedef struct +{ + byte *data; + int count; +} cblock_t; + +#endif + diff --git a/src/bspc/l_log.c b/src/bspc/l_log.c new file mode 100644 index 0000000..3e64445 --- /dev/null +++ b/src/bspc/l_log.c @@ -0,0 +1,220 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_log.c +// Function: log file stuff +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-31 +// Tab Size: 3 +//=========================================================================== + +#include +#include +#include + +#include "qbsp.h" + +#define MAX_LOGFILENAMESIZE 1024 + +typedef struct logfile_s +{ + char filename[MAX_LOGFILENAMESIZE]; + FILE *fp; + int numwrites; +} logfile_t; + +logfile_t logfile; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Open( char *filename ) { + if ( !filename || !strlen( filename ) ) { + printf( "openlog \n" ); + return; + } //end if + if ( logfile.fp ) { + printf( "log file %s is already opened\n", logfile.filename ); + return; + } //end if + logfile.fp = fopen( filename, "wb" ); + if ( !logfile.fp ) { + printf( "can't open the log file %s\n", filename ); + return; + } //end if + strncpy( logfile.filename, filename, MAX_LOGFILENAMESIZE ); + printf( "Opened log %s\n", logfile.filename ); +} //end of the function Log_Create +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Close( void ) { + if ( !logfile.fp ) { + printf( "no log file to close\n" ); + return; + } //end if + if ( fclose( logfile.fp ) ) { + printf( "can't close log file %s\n", logfile.filename ); + return; + } //end if + logfile.fp = NULL; + printf( "Closed log %s\n", logfile.filename ); +} //end of the function Log_Close +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Shutdown( void ) { + if ( logfile.fp ) { + Log_Close(); + } +} //end of the function Log_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_UnifyEndOfLine( char *buf ) { + int i; + + for ( i = 0; buf[i]; i++ ) + { + if ( buf[i] == '\n' ) { + if ( i <= 0 || buf[i - 1] != '\r' ) { + memmove( &buf[i + 1], &buf[i], strlen( &buf[i] ) + 1 ); + buf[i] = '\r'; + i++; + } //end if + } //end if + } //end for +} //end of the function Log_UnifyEndOfLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Print( char *fmt, ... ) { + va_list ap; + char buf[2048]; + + va_start( ap, fmt ); + vsprintf( buf, fmt, ap ); + va_end( ap ); + + if ( verbose ) { +#ifdef WINBSPC + WinBSPCPrint( buf ); +#else + printf( "%s", buf ); +#endif //WINBSPS + } //end if + + if ( logfile.fp ) { + Log_UnifyEndOfLine( buf ); + fprintf( logfile.fp, "%s", buf ); + fflush( logfile.fp ); + } //end if +} //end of the function Log_Print +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Write( char *fmt, ... ) { + va_list ap; + char buf[2048]; + + if ( !logfile.fp ) { + return; + } + va_start( ap, fmt ); + vsprintf( buf, fmt, ap ); + va_end( ap ); + Log_UnifyEndOfLine( buf ); + fprintf( logfile.fp, "%s", buf ); + fflush( logfile.fp ); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_WriteTimeStamped( char *fmt, ... ) { + va_list ap; + + if ( !logfile.fp ) { + return; + } +/* fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", + logfile.numwrites, + (int) (botlibglobals.time / 60 / 60), + (int) (botlibglobals.time / 60), + (int) (botlibglobals.time), + (int) ((int) (botlibglobals.time * 100)) - + ((int) botlibglobals.time) * 100);*/ + va_start( ap, fmt ); + vfprintf( logfile.fp, fmt, ap ); + va_end( ap ); + logfile.numwrites++; + fflush( logfile.fp ); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +FILE *Log_FileStruct( void ) { + return logfile.fp; +} //end of the function Log_FileStruct +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Flush( void ) { + if ( logfile.fp ) { + fflush( logfile.fp ); + } +} //end of the function Log_Flush + diff --git a/src/bspc/l_log.h b/src/bspc/l_log.h new file mode 100644 index 0000000..9180c67 --- /dev/null +++ b/src/bspc/l_log.h @@ -0,0 +1,57 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_log.h +// Function: log file stuff +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-31 +// Tab Size: 3 +//=========================================================================== + +//open a log file +void Log_Open( char *filename ); +//close the current log file +void Log_Close( void ); +//close log file if present +void Log_Shutdown( void ); +//print on stdout and write to the current opened log file +void Log_Print( char *fmt, ... ); +//write to the current opened log file +void Log_Write( char *fmt, ... ); +//write to the current opened log file with a time stamp +void Log_WriteTimeStamped( char *fmt, ... ); +//returns the log file structure +FILE *Log_FileStruct( void ); +//flush log file +void Log_Flush( void ); + +#ifdef WINBSPC +void WinBSPCPrint( char *str ); +#endif //WINBSPC diff --git a/src/bspc/l_math.c b/src/bspc/l_math.c new file mode 100644 index 0000000..3591c07 --- /dev/null +++ b/src/bspc/l_math.c @@ -0,0 +1,279 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// mathlib.c -- math primitives + +#include "l_cmd.h" +#include "l_math.h" + +vec3_t vec3_origin = {0,0,0}; + +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ) { + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + 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 ); + + if ( forward ) { + forward[0] = cp * cy; + forward[1] = cp * sy; + forward[2] = -sp; + } + if ( right ) { + right[0] = ( -1 * sr * sp * cy + - 1 * cr * -sy ); + right[1] = ( -1 * sr * sp * sy + - 1 * cr * cy ); + right[2] = -1 * sr * cp; + } + if ( up ) { + up[0] = ( cr * sp * cy + - sr * -sy ); + up[1] = ( cr * sp * sy + - sr * cy ); + up[2] = cr * cp; + } +} + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) { + int i; + vec3_t corner; + float a, b; + + for ( i = 0 ; i < 3 ; i++ ) { + a = fabs( mins[i] ); + b = fabs( maxs[i] ); + corner[i] = a > b ? a : b; + } + + return VectorLength( corner ); +} + +/* +================ +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]; +} + +void AxisClear( vec3_t axis[3] ) { + axis[0][0] = 1; + axis[0][1] = 0; + axis[0][2] = 0; + axis[1][0] = 0; + axis[1][1] = 1; + axis[1][2] = 0; + axis[2][0] = 0; + axis[2][1] = 0; + axis[2][2] = 1; +} + +double VectorLength( vec3_t v ) { + int i; + double length; + + length = 0; + for ( i = 0 ; i < 3 ; i++ ) + length += v[i] * v[i]; + length = sqrt( length ); // FIXME + + return length; +} + +float VectorLengthSquared( vec3_t v ) { + return DotProduct( v, v ); +} + +qboolean VectorCompare( vec3_t v1, vec3_t v2 ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) + if ( fabs( v1[i] - v2[i] ) > EQUAL_EPSILON ) { + return false; + } + + return true; +} + +vec_t Q_rint( vec_t in ) { + return floor( in + 0.5 ); +} + +void CrossProduct( const vec3_t v1, const 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]; +} + +void _VectorMA( vec3_t va, double scale, vec3_t vb, vec3_t vc ) { + vc[0] = va[0] + scale * vb[0]; + vc[1] = va[1] + scale * vb[1]; + vc[2] = va[2] + scale * vb[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 va, vec3_t vb, vec3_t out ) { + out[0] = va[0] - vb[0]; + out[1] = va[1] - vb[1]; + out[2] = va[2] - vb[2]; +} + +void _VectorAdd( vec3_t va, vec3_t vb, vec3_t out ) { + out[0] = va[0] + vb[0]; + out[1] = va[1] + vb[1]; + out[2] = va[2] + vb[2]; +} + +void _VectorCopy( vec3_t in, vec3_t out ) { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale( vec3_t v, vec_t scale, vec3_t out ) { + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; +} + +vec_t VectorNormalize( vec3_t inout ) { + vec_t length, ilength; + + length = sqrt( inout[0] * inout[0] + inout[1] * inout[1] + inout[2] * inout[2] ); + if ( length == 0 ) { + VectorClear( inout ); + return 0; + } + + ilength = 1.0 / length; + inout[0] = inout[0] * ilength; + inout[1] = inout[1] * ilength; + inout[2] = inout[2] * ilength; + + return length; +} + +vec_t VectorNormalize2( const vec3_t in, vec3_t out ) { + vec_t length, ilength; + + length = sqrt( in[0] * in[0] + in[1] * in[1] + in[2] * in[2] ); + if ( length == 0 ) { + VectorClear( out ); + return 0; + } + + ilength = 1.0 / length; + out[0] = in[0] * ilength; + out[1] = in[1] * ilength; + out[2] = in[2] * ilength; + + return length; +} + +vec_t ColorNormalize( vec3_t in, vec3_t out ) { + float max, scale; + + max = in[0]; + if ( in[1] > max ) { + max = in[1]; + } + if ( in[2] > max ) { + max = in[2]; + } + + if ( max == 0 ) { + return 0; + } + + scale = 1.0 / max; + + VectorScale( in, scale, out ); + + return max; +} + + + +void VectorInverse( vec3_t v ) { + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void ClearBounds( vec3_t mins, vec3_t maxs ) { + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) { + int i; + vec_t val; + + for ( i = 0 ; i < 3 ; i++ ) + { + val = v[i]; + if ( val < mins[i] ) { + mins[i] = val; + } + if ( val > maxs[i] ) { + maxs[i] = val; + } + } +} diff --git a/src/bspc/l_math.h b/src/bspc/l_math.h new file mode 100644 index 0000000..a4a5a1e --- /dev/null +++ b/src/bspc/l_math.h @@ -0,0 +1,100 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#define PITCH 0 +#define YAW 1 +#define ROLL 2 + +#define Q_PI 3.14159265358979323846 + +#define DEG2RAD( a ) ( a * M_PI ) / 180.0F + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern vec3_t vec3_origin; + +#define EQUAL_EPSILON 0.001 + +qboolean VectorCompare( vec3_t v1, vec3_t v2 ); + +#define DotProduct( x,y ) ( x[0] * y[0] + x[1] * y[1] + 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];} +#define Vector4Copy( a,b ) {b[0] = a[0]; b[1] = a[1]; b[2] = a[2]; b[3] = a[3];} +#define VectorScale( v, s, o ) ( ( o )[0] = ( v )[0] * ( s ),( o )[1] = ( v )[1] * ( s ),( o )[2] = ( v )[2] * ( s ) ) +#define VectorClear( x ) {x[0] = x[1] = x[2] = 0;} +#define VectorNegate( x, y ) {y[0] = -x[0]; y[1] = -x[1]; y[2] = -x[2];} +#define VectorMA( v, s, b, o ) ( ( o )[0] = ( v )[0] + ( b )[0] * ( s ),( o )[1] = ( v )[1] + ( b )[1] * ( s ),( o )[2] = ( v )[2] + ( b )[2] * ( s ) ) + +vec_t Q_rint( vec_t in ); +vec_t _DotProduct( vec3_t v1, vec3_t v2 ); +void _VectorSubtract( vec3_t va, vec3_t vb, vec3_t out ); +void _VectorAdd( vec3_t va, vec3_t vb, vec3_t out ); +void _VectorCopy( vec3_t in, vec3_t out ); +void _VectorScale( vec3_t v, vec_t scale, vec3_t out ); +void _VectorMA( vec3_t va, double scale, vec3_t vb, vec3_t vc ); + +double VectorLength( vec3_t v ); +void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ); +vec_t VectorNormalize( vec3_t inout ); +vec_t ColorNormalize( vec3_t in, vec3_t out ); +vec_t VectorNormalize2( const vec3_t v, vec3_t out ); +void VectorInverse( vec3_t v ); + +void ClearBounds( vec3_t mins, vec3_t maxs ); +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); + +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ); +void R_ConcatRotations( float in1[3][3], float in2[3][3], float out[3][3] ); +void RotatePoint( vec3_t point, float matrix[3][3] ); +void CreateRotationMatrix( vec3_t angles, float matrix[3][3] ); + +#endif diff --git a/src/bspc/l_mem.c b/src/bspc/l_mem.c new file mode 100644 index 0000000..6edafee --- /dev/null +++ b/src/bspc/l_mem.c @@ -0,0 +1,484 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_mem.c +// Function: +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1999-06-02 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "l_log.h" + +int allocedmemory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemorySize( unsigned long size ) { + unsigned long number1, number2, number3; + number1 = size >> 20; + number2 = ( size & 0xFFFFF ) >> 10; + number3 = ( size & 0x3FF ); + if ( number1 ) { + Log_Print( "%ld MB", number1 ); + } + if ( number1 && number2 ) { + Log_Print( " and " ); + } + if ( number2 ) { + Log_Print( "%ld KB", number2 ); + } + if ( number2 && number3 ) { + Log_Print( " and " ); + } + if ( number3 ) { + Log_Print( "%ld bytes", number3 ); + } +} //end of the function PrintFileSize + +#ifndef MEMDEBUG +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemorySize( void *ptr ) { +#if defined( WIN32 ) || defined( _WIN32 ) + #ifdef __WATCOMC__ + //Intel 32 bits memory addressing, 16 bytes aligned + return ( _msize( ptr ) + 15 ) >> 4 << 4; + #else + return _msize( ptr ); + #endif +#else + return 0; +#endif +} //end of the function MemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *GetClearedMemory( int size ) { + void *ptr; + + ptr = (void *) malloc( size ); + if ( !ptr ) { + Error( "out of memory" ); + } + memset( ptr, 0, size ); + allocedmemory += MemorySize( ptr ); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *GetMemory( unsigned long size ) { + void *ptr; + ptr = malloc( size ); + if ( !ptr ) { + Error( "out of memory" ); + } + allocedmemory += MemorySize( ptr ); + return ptr; +} //end of the function GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int fmemsize; +void FreeMemory( void *ptr ) { + // RF, modified this for better memory trash testing + fmemsize = MemorySize( ptr ); + allocedmemory -= fmemsize; + + // RF, somehow this crashes windows if size is less than or equal 8 + if ( fmemsize <= 8 ) { + return; + } + + // RF, set this memory to something that will cause problems if accessed again + memset( ptr, 0xAA, fmemsize ); + + free( ptr ); +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TotalAllocatedMemory( void ) { + return allocedmemory; +} //end of the function TotalAllocatedMemory + +#else + +#define MEM_ID 0x12345678l + +int totalmemorysize; +int numblocks; + +typedef struct memoryblock_s +{ + unsigned long int id; + void *ptr; + int size; +#ifdef MEMDEBUG + char *label; + char *file; + int line; +#endif //MEMDEBUG + struct memoryblock_s *prev, *next; +} memoryblock_t; + +memoryblock_t *memory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LinkMemoryBlock( memoryblock_t * block ) +{ + block->prev = NULL; + block->next = memory; + if ( memory ) { + memory->prev = block; + } + memory = block; +} //end of the function LinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnlinkMemoryBlock( memoryblock_t * block ) +{ + if ( block->prev ) { + block->prev->next = block->next; + } else { memory = block->next;} + if ( block->next ) { + block->next->prev = block->prev; + } +} //end of the function UnlinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = malloc( size + sizeof( memoryblock_t ) ); + block = (memoryblock_t *) ptr; + block->id = MEM_ID; + block->ptr = (char *) ptr + sizeof( memoryblock_t ); + block->size = size + sizeof( memoryblock_t ); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock( block ); + totalmemorysize += block->size; + numblocks++; + return block->ptr; +} //end of the function GetMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug( size, label, file, line ); +#else + ptr = GetMemory( size ); +#endif //MEMDEBUG + memset( ptr, 0, size ); + return ptr; +} //end of the function GetClearedMemoryLabelled +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *GetClearedHunkMemory( unsigned long size ) +{ + return GetClearedMemory( size ); +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *GetHunkMemory( unsigned long size ) +{ + return GetMemory( size ); +} //end of the function GetHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +memoryblock_t *BlockFromPointer( void *ptr, char *str ) +{ + memoryblock_t *block; + + if ( !ptr ) { +#ifdef MEMDEBUG + //char *crash = (char *) NULL; + //crash[0] = 1; + Error( "%s: NULL pointer\n", str ); +#endif MEMDEBUG + return NULL; + } //end if + block = ( memoryblock_t * )( (char *) ptr - sizeof( memoryblock_t ) ); + if ( block->id != MEM_ID ) { + Error( "%s: invalid memory block\n", str ); + } //end if + if ( block->ptr != ptr ) { + + Error( "%s: memory block pointer invalid\n", str ); + } //end if + return block; +} //end of the function BlockFromPointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory( void *ptr ) +{ + memoryblock_t *block; + + block = BlockFromPointer( ptr, "FreeMemory" ); + if ( !block ) { + return; + } + UnlinkMemoryBlock( block ); + totalmemorysize -= block->size; + numblocks--; + // + free( block ); +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemoryByteSize( void *ptr ) +{ + memoryblock_t *block; + + block = BlockFromPointer( ptr, "MemoryByteSize" ); + if ( !block ) { + return 0; + } + return block->size; +} //end of the function MemoryByteSize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemorySize( void *ptr ) +{ + return MemoryByteSize( ptr ); +} //end of the function MemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize( void ) +{ + printf( "total botlib memory: %d KB\n", totalmemorysize >> 10 ); + printf( "total memory blocks: %d\n", numblocks ); +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels( void ) +{ + memoryblock_t *block; + int i; + + PrintUsedMemorySize(); + i = 0; + for ( block = memory; block; block = block->next ) + { +#ifdef MEMDEBUG + Log_Write( "%6d, %p, %8d: %24s line %6d: %s", i, block->ptr, block->size, block->file, block->line, block->label ); +#endif //MEMDEBUG + i++; + } //end for +} //end of the function PrintMemoryLabels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DumpMemory( void ) +{ + memoryblock_t *block; + + for ( block = memory; block; block = memory ) + { + FreeMemory( block->ptr ); + } //end for + totalmemorysize = 0; +} //end of the function DumpMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TotalAllocatedMemory( void ) +{ + return totalmemorysize; +} //end of the function TotalAllocatedMemory +#endif + +//=========================================================================== +// Q3 Hunk and Z_ memory management +//=========================================================================== + +typedef struct memhunk_s +{ + void *ptr; + struct memhunk_s *next; +} memhunk_t; + +memhunk_t *memhunk_high; +memhunk_t *memhunk_low; +int memhunk_high_size = 16 * 1024 * 1024; +int memhunk_low_size = 0; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Hunk_ClearHigh( void ) +{ + memhunk_t *h, *nexth; + + for ( h = memhunk_high; h; h = nexth ) + { + nexth = h->next; + FreeMemory( h ); + } //end for + memhunk_high = NULL; + memhunk_high_size = 16 * 1024 * 1024; +} //end of the function Hunk_ClearHigh +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *Hunk_Alloc( int size ) +{ + memhunk_t *h; + + if ( !size ) { + return (void *) memhunk_high_size; + } + // + h = GetClearedMemory( size + sizeof( memhunk_t ) ); + h->ptr = (char *) h + sizeof( memhunk_t ); + h->next = memhunk_high; + memhunk_high = h; + memhunk_high_size -= size; + return h->ptr; +} //end of the function Hunk_Alloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void *Z_Malloc( int size ) +{ + return GetClearedMemory( size ); +} //end of the function Z_Malloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Z_Free( void *ptr ) +{ + FreeMemory( ptr ); +} //end of the function Z_Free diff --git a/src/bspc/l_mem.h b/src/bspc/l_mem.h new file mode 100644 index 0000000..398a60f --- /dev/null +++ b/src/bspc/l_mem.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +//============================================================================= + +// memory.h +//#define MEMDEBUG +#undef MEMDEBUG + +#ifndef MEMDEBUG + +void *GetClearedMemory( int size ); +void *GetMemory( unsigned long size ); + +#else + +#define GetMemory( size ) GetMemoryDebug( size, # size, __FILE__, __LINE__ ); +#define GetClearedMemory( size ) GetClearedMemoryDebug( size, # size, __FILE__, __LINE__ ); +//allocate a memory block of the given size +void *GetMemoryDebug( unsigned long size, char *label, char *file, int line ); +//allocate a memory block of the given size and clear it +void *GetClearedMemoryDebug( unsigned long size, char *label, char *file, int line ); +// +void PrintMemoryLabels( void ); +#endif //MEMDEBUG + +void FreeMemory( void *ptr ); +int MemorySize( void *ptr ); +void PrintMemorySize( unsigned long size ); +int TotalAllocatedMemory( void ); + diff --git a/src/bspc/l_poly.c b/src/bspc/l_poly.c new file mode 100644 index 0000000..2b28733 --- /dev/null +++ b/src/bspc/l_poly.c @@ -0,0 +1,1433 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_poly.c +// Function: +// Programmer: id Sofware +// Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +#include +#include "l_cmd.h" +#include "l_math.h" +#include "l_poly.h" +#include "l_log.h" +#include "l_mem.h" + +#define BOGUS_RANGE 8192 + +extern int numthreads; + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; +int c_windingmemory; +int c_peak_windingmemory; + +char windingerror[1024]; + +void pw( winding_t *w ) { + int i; + for ( i = 0 ; i < w->numpoints ; i++ ) + printf( "(%5.3f, %5.3f, %5.3f)\n",w->p[i][0], w->p[i][1],w->p[i][2] ); +} + + +void ResetWindings( void ) { + c_active_windings = 0; + c_peak_windings = 0; + c_winding_allocs = 0; + c_winding_points = 0; + c_windingmemory = 0; + c_peak_windingmemory = 0; + + strcpy( windingerror, "" ); +} //end of the function ResetWindings +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding( int points ) { + winding_t *w; + int s; + + s = sizeof( vec_t ) * 3 * points + sizeof( int ); + w = GetMemory( s ); + memset( w, 0, s ); + + if ( numthreads == 1 ) { + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if ( c_active_windings > c_peak_windings ) { + c_peak_windings = c_active_windings; + } + c_windingmemory += MemorySize( w ); + if ( c_windingmemory > c_peak_windingmemory ) { + c_peak_windingmemory = c_windingmemory; + } + } //end if + return w; +} //end of the function AllocWinding + +void FreeWinding( winding_t *w ) { + if ( *(unsigned *)w == 0xdeaddead ) { + Error( "FreeWinding: freed a freed winding" ); + } + + if ( numthreads == 1 ) { + c_active_windings--; + c_windingmemory -= MemorySize( w ); + } //end if + + *(unsigned *)w = 0xdeaddead; + + FreeMemory( w ); +} //end of the function FreeWinding + +int WindingMemory( void ) { + return c_windingmemory; +} //end of the function WindingMemory + +int WindingPeakMemory( void ) { + return c_peak_windingmemory; +} //end of the function WindingPeakMemory + +int ActiveWindings( void ) { + return c_active_windings; +} //end of the function ActiveWindings +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints( winding_t *w ) { + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + j = ( i + 1 ) % w->numpoints; + k = ( i + w->numpoints - 1 ) % w->numpoints; + VectorSubtract( w->p[j], w->p[i], v1 ); + VectorSubtract( w->p[i], w->p[k], v2 ); + VectorNormalize( v1 ); + VectorNormalize( v2 ); + if ( DotProduct( v1, v2 ) < 0.999 ) { + if ( nump >= MAX_POINTS_ON_WINDING ) { + Error( "RemoveColinearPoints: MAX_POINTS_ON_WINDING" ); + } + VectorCopy( w->p[i], p[nump] ); + nump++; + } + } + + if ( nump == w->numpoints ) { + return; + } + + if ( numthreads == 1 ) { + c_removed += w->numpoints - nump; + } + w->numpoints = nump; + memcpy( w->p, p, nump * sizeof( p[0] ) ); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ) { + vec3_t v1, v2; + int i; + + //find two vectors each longer than 0.5 units + for ( i = 0; i < w->numpoints; i++ ) + { + VectorSubtract( w->p[( i + 1 ) % w->numpoints], w->p[i], v1 ); + VectorSubtract( w->p[( i + 2 ) % w->numpoints], w->p[i], v2 ); + if ( VectorLength( v1 ) > 0.5 && VectorLength( v2 ) > 0.5 ) { + break; + } + } //end for + CrossProduct( v2, v1, normal ); + VectorNormalize( normal ); + *dist = DotProduct( w->p[0], normal ); +} //end of the function WindingPlane + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea( winding_t *w ) { + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for ( i = 2 ; i < w->numpoints ; i++ ) + { + VectorSubtract( w->p[i - 1], w->p[0], d1 ); + VectorSubtract( w->p[i], w->p[0], d2 ); + CrossProduct( d1, d2, cross ); + total += 0.5 * VectorLength( cross ); + } + return total; +} + +void WindingBounds( winding_t *w, vec3_t mins, vec3_t maxs ) { + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + v = w->p[i][j]; + if ( v < mins[j] ) { + mins[j] = v; + } + if ( v > maxs[j] ) { + maxs[j] = v; + } + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter( winding_t *w, vec3_t center ) { + int i; + float scale; + + VectorCopy( vec3_origin, center ); + for ( i = 0 ; i < w->numpoints ; i++ ) + VectorAdd( w->p[i], center, center ); + + scale = 1.0 / w->numpoints; + VectorScale( center, scale, center ); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane( vec3_t normal, vec_t dist ) { + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for ( i = 0 ; i < 3; i++ ) + { + v = fabs( normal[i] ); + if ( v > max ) { + x = i; + max = v; + } + } + if ( x == -1 ) { + Error( "BaseWindingForPlane: no axis found" ); + } + + VectorCopy( vec3_origin, vup ); + switch ( x ) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct( vup, normal ); + VectorMA( vup, -v, normal, vup ); + VectorNormalize( vup ); + + VectorScale( normal, dist, org ); + + CrossProduct( vup, normal, vright ); + + VectorScale( vup, BOGUS_RANGE, vup ); + VectorScale( vright, BOGUS_RANGE, vright ); + +// project a really big axis aligned box onto the plane + w = AllocWinding( 4 ); + + VectorSubtract( org, vright, w->p[0] ); + VectorAdd( w->p[0], vup, w->p[0] ); + + VectorAdd( org, vright, w->p[1] ); + VectorAdd( w->p[1], vup, w->p[1] ); + + VectorAdd( org, vright, w->p[2] ); + VectorSubtract( w->p[2], vup, w->p[2] ); + + VectorSubtract( org, vright, w->p[3] ); + VectorSubtract( w->p[3], vup, w->p[3] ); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding( winding_t *w ) { + int size; + winding_t *c; + + c = AllocWinding( w->numpoints ); + size = (int)( (winding_t *)0 )->p[w->numpoints]; + memcpy( c, w, size ); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding( winding_t *w ) { + int i; + winding_t *c; + + c = AllocWinding( w->numpoints ); + for ( i = 0 ; i < w->numpoints ; i++ ) + { + VectorCopy( w->p[w->numpoints - 1 - i], c->p[i] ); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon( winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back ) { + vec_t dists[MAX_POINTS_ON_WINDING + 4]; + int sides[MAX_POINTS_ON_WINDING + 4]; + int counts[3]; + //MrElusive: DOH can't use statics when unsing multithreading!!! + vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for ( i = 0 ; i < in->numpoints ; i++ ) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if ( !counts[0] ) { + *back = CopyWinding( in ); + return; + } + if ( !counts[1] ) { + *front = CopyWinding( in ); + return; + } + + maxpts = in->numpoints + 4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding( maxpts ); + *back = b = AllocWinding( maxpts ); + + for ( i = 0 ; i < in->numpoints ; i++ ) + { + p1 = in->p[i]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + VectorCopy( p1, b->p[b->numpoints] ); + b->numpoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + if ( sides[i] == SIDE_BACK ) { + VectorCopy( p1, b->p[b->numpoints] ); + b->numpoints++; + } + + if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = in->p[( i + 1 ) % in->numpoints]; + + dot = dists[i] / ( dists[i] - dists[i + 1] ); + for ( j = 0 ; j < 3 ; j++ ) + { // avoid round off error when possible + if ( normal[j] == 1 ) { + mid[j] = dist; + } else if ( normal[j] == -1 ) { + mid[j] = -dist; + } else { + mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + } + + VectorCopy( mid, f->p[f->numpoints] ); + f->numpoints++; + VectorCopy( mid, b->p[b->numpoints] ); + b->numpoints++; + } + + if ( f->numpoints > maxpts || b->numpoints > maxpts ) { + Error( "ClipWinding: points exceeded estimate" ); + } + if ( f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING ) { + Error( "ClipWinding: MAX_POINTS_ON_WINDING" ); + } +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace( winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon ) { + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING + 4]; + int sides[MAX_POINTS_ON_WINDING + 4]; + int counts[3]; + //MrElusive: DOH can't use statics when unsing multithreading!!! + vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for ( i = 0 ; i < in->numpoints ; i++ ) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if ( !counts[0] ) { + FreeWinding( in ); + *inout = NULL; + return; + } + if ( !counts[1] ) { + return; // inout stays the same + + } + maxpts = in->numpoints + 4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding( maxpts ); + + for ( i = 0 ; i < in->numpoints ; i++ ) + { + p1 = in->p[i]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + + if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = in->p[( i + 1 ) % in->numpoints]; + + dot = dists[i] / ( dists[i] - dists[i + 1] ); + for ( j = 0 ; j < 3 ; j++ ) + { // avoid round off error when possible + if ( normal[j] == 1 ) { + mid[j] = dist; + } else if ( normal[j] == -1 ) { + mid[j] = -dist; + } else { + mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + } + + VectorCopy( mid, f->p[f->numpoints] ); + f->numpoints++; + } + + if ( f->numpoints > maxpts ) { + Error( "ClipWinding: points exceeded estimate" ); + } + if ( f->numpoints > MAX_POINTS_ON_WINDING ) { + Error( "ClipWinding: MAX_POINTS_ON_WINDING" ); + } + + FreeWinding( in ); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding( winding_t *in, vec3_t normal, vec_t dist ) { + winding_t *f, *b; + + ClipWindingEpsilon( in, normal, dist, ON_EPSILON, &f, &b ); + FreeWinding( in ); + if ( b ) { + FreeWinding( b ); + } + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding( winding_t *w ) { + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if ( w->numpoints < 3 ) { + Error( "CheckWinding: %i points",w->numpoints ); + } + + area = WindingArea( w ); + if ( area < 1 ) { + Error( "CheckWinding: %f area", area ); + } + + WindingPlane( w, facenormal, &facedist ); + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + p1 = w->p[i]; + + for ( j = 0 ; j < 3 ; j++ ) + if ( p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE ) { + Error( "CheckWinding: BUGUS_RANGE: %f",p1[j] ); + } + + j = i + 1 == w->numpoints ? 0 : i + 1; + + // check the point is on the face plane + d = DotProduct( p1, facenormal ) - facedist; + if ( d < -ON_EPSILON || d > ON_EPSILON ) { + Error( "CheckWinding: point off plane" ); + } + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract( p2, p1, dir ); + + if ( VectorLength( dir ) < ON_EPSILON ) { + Error( "CheckWinding: degenerate edge" ); + } + + CrossProduct( facenormal, dir, edgenormal ); + VectorNormalize( edgenormal ); + edgedist = DotProduct( p1, edgenormal ); + edgedist += ON_EPSILON; + + // all other points must be on front side + for ( j = 0 ; j < w->numpoints ; j++ ) + { + if ( j == i ) { + continue; + } + d = DotProduct( w->p[j], edgenormal ); + if ( d > edgedist ) { + Error( "CheckWinding: non-convex" ); + } + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide( winding_t *w, vec3_t normal, vec_t dist ) { + qboolean front, back; + int i; + vec_t d; + + front = false; + back = false; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + d = DotProduct( w->p[i], normal ) - dist; + if ( d < -ON_EPSILON ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + if ( d > ON_EPSILON ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +//#ifdef ME + #define CONTINUOUS_EPSILON 0.005 +//#else +// #define CONTINUOUS_EPSILON 0.001 +//#endif + +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ + +winding_t *TryMergeWinding( winding_t *f1, winding_t *f2, vec3_t planenormal ) { + vec_t *p1, *p2, *p3, *p4, *back; + winding_t *newf; + int i, j, k, l; + vec3_t normal, delta; + vec_t dot; + qboolean keep1, keep2; + + + // + // find a common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; // + + for ( i = 0; i < f1->numpoints; i++ ) + { + p1 = f1->p[i]; + p2 = f1->p[( i + 1 ) % f1->numpoints]; + for ( j = 0; j < f2->numpoints; j++ ) + { + p3 = f2->p[j]; + p4 = f2->p[( j + 1 ) % f2->numpoints]; + for ( k = 0; k < 3; k++ ) + { + if ( fabs( p1[k] - p4[k] ) > 0.1 ) { //EQUAL_EPSILON) //ME + break; + } + if ( fabs( p2[k] - p3[k] ) > 0.1 ) { //EQUAL_EPSILON) //ME + break; + } + } //end for + if ( k == 3 ) { + break; + } + } //end for + if ( j < f2->numpoints ) { + break; + } + } //end for + + if ( i == f1->numpoints ) { + return NULL; // no matching edges + + } + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = f1->p[( i + f1->numpoints - 1 ) % f1->numpoints]; + VectorSubtract( p1, back, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + + back = f2->p[( j + 2 ) % f2->numpoints]; + VectorSubtract( back, p1, delta ); + dot = DotProduct( delta, normal ); + if ( dot > CONTINUOUS_EPSILON ) { + return NULL; // not a convex polygon + } + keep1 = (qboolean)( dot < -CONTINUOUS_EPSILON ); + + back = f1->p[( i + 2 ) % f1->numpoints]; + VectorSubtract( back, p2, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + + back = f2->p[( j + f2->numpoints - 1 ) % f2->numpoints]; + VectorSubtract( back, p2, delta ); + dot = DotProduct( delta, normal ); + if ( dot > CONTINUOUS_EPSILON ) { + return NULL; // not a convex polygon + } + keep2 = (qboolean)( dot < -CONTINUOUS_EPSILON ); + + // + // build the new polygon + // + newf = AllocWinding( f1->numpoints + f2->numpoints ); + + // copy first polygon + for ( k = ( i + 1 ) % f1->numpoints ; k != i ; k = ( k + 1 ) % f1->numpoints ) + { + if ( k == ( i + 1 ) % f1->numpoints && !keep2 ) { + continue; + } + + VectorCopy( f1->p[k], newf->p[newf->numpoints] ); + newf->numpoints++; + } + + // copy second polygon + for ( l = ( j + 1 ) % f2->numpoints ; l != j ; l = ( l + 1 ) % f2->numpoints ) + { + if ( l == ( j + 1 ) % f2->numpoints && !keep1 ) { + continue; + } + VectorCopy( f2->p[l], newf->p[newf->numpoints] ); + newf->numpoints++; + } + + return newf; +} + +//#ifdef ME +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +winding_t *MergeWindings( winding_t *w1, winding_t *w2, vec3_t planenormal ) { + winding_t *neww; + float dist; + int i, j, n, found, insertafter; + int sides[MAX_POINTS_ON_WINDING + 4]; + vec3_t newp[MAX_POINTS_ON_WINDING + 4]; + int numpoints; + vec3_t edgevec, sepnormal, v; + + RemoveEqualPoints( w1, 0.2 ); + numpoints = w1->numpoints; + memcpy( newp, w1->p, w1->numpoints * sizeof( vec3_t ) ); + // + for ( i = 0; i < w2->numpoints; i++ ) + { + VectorCopy( w2->p[i], v ); + for ( j = 0; j < numpoints; j++ ) + { + VectorSubtract( newp[( j + 1 ) % numpoints], + newp[( j ) % numpoints], edgevec ); + CrossProduct( edgevec, planenormal, sepnormal ); + VectorNormalize( sepnormal ); + if ( VectorLength( sepnormal ) < 0.9 ) { + //remove the point from the new winding + for ( n = j; n < numpoints - 1; n++ ) + { + VectorCopy( newp[n + 1], newp[n] ); + sides[n] = sides[n + 1]; + } //end for + numpoints--; + j--; + Log_Print( "MergeWindings: degenerate edge on winding %f %f %f\n", sepnormal[0], + sepnormal[1], + sepnormal[2] ); + continue; + } //end if + dist = DotProduct( newp[( j ) % numpoints], sepnormal ); + if ( DotProduct( v, sepnormal ) - dist < -0.1 ) { + sides[j] = SIDE_BACK; + } else { sides[j] = SIDE_FRONT;} + } //end for + //remove all unnecesary points + for ( j = 0; j < numpoints; ) + { + if ( sides[j] == SIDE_BACK + && sides[( j + 1 ) % numpoints] == SIDE_BACK ) { + //remove the point from the new winding + for ( n = ( j + 1 ) % numpoints; n < numpoints - 1; n++ ) + { + VectorCopy( newp[n + 1], newp[n] ); + sides[n] = sides[n + 1]; + } //end for + numpoints--; + } //end if + else + { + j++; + } //end else + } //end for + // + found = false; + for ( j = 0; j < numpoints; j++ ) + { + if ( sides[j] == SIDE_FRONT + && sides[( j + 1 ) % numpoints] == SIDE_BACK ) { + if ( found ) { + Log_Print( "Warning: MergeWindings: front to back found twice\n" ); + } + found = true; + } //end if + } //end for + // + for ( j = 0; j < numpoints; j++ ) + { + if ( sides[j] == SIDE_FRONT + && sides[( j + 1 ) % numpoints] == SIDE_BACK ) { + insertafter = ( j + 1 ) % numpoints; + //insert the new point after j+1 + for ( n = numpoints - 1; n > insertafter; n-- ) + { + VectorCopy( newp[n], newp[n + 1] ); + } //end for + numpoints++; + VectorCopy( v, newp[( insertafter + 1 ) % numpoints] ); + break; + } //end if + } //end for + } //end for + neww = AllocWinding( numpoints ); + neww->numpoints = numpoints; + memcpy( neww->p, newp, numpoints * sizeof( vec3_t ) ); + RemoveColinearPoints( neww ); + return neww; +} //end of the function MergeWindings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WindingErrorString( void ) { + return windingerror; +} //end of the function WindingErrorString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WindingError( winding_t *w ) { + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if ( w->numpoints < 3 ) { + sprintf( windingerror, "winding %i points", w->numpoints ); + return WE_NOTENOUGHPOINTS; + } //end if + + area = WindingArea( w ); + if ( area < 1 ) { + sprintf( windingerror, "winding %f area", area ); + return WE_SMALLAREA; + } //end if + + WindingPlane( w, facenormal, &facedist ); + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + p1 = w->p[i]; + + for ( j = 0 ; j < 3 ; j++ ) + { + if ( p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE ) { + sprintf( windingerror, "winding point %d BUGUS_RANGE \'%f %f %f\'", j, p1[0], p1[1], p1[2] ); + return WE_POINTBOGUSRANGE; + } //end if + } //end for + + j = i + 1 == w->numpoints ? 0 : i + 1; + + // check the point is on the face plane + d = DotProduct( p1, facenormal ) - facedist; + if ( d < -ON_EPSILON || d > ON_EPSILON ) { + sprintf( windingerror, "winding point %d off plane", i ); + return WE_POINTOFFPLANE; + } //end if + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract( p2, p1, dir ); + + if ( VectorLength( dir ) < ON_EPSILON ) { + sprintf( windingerror, "winding degenerate edge %d-%d", i, j ); + return WE_DEGENERATEEDGE; + } //end if + + CrossProduct( facenormal, dir, edgenormal ); + VectorNormalize( edgenormal ); + edgedist = DotProduct( p1, edgenormal ); + edgedist += ON_EPSILON; + + // all other points must be on front side + for ( j = 0 ; j < w->numpoints ; j++ ) + { + if ( j == i ) { + continue; + } + d = DotProduct( w->p[j], edgenormal ); + if ( d > edgedist ) { + sprintf( windingerror, "winding non-convex" ); + return WE_NONCONVEX; + } //end if + } //end for + } //end for + return WE_NONE; +} //end of the function WindingError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveEqualPoints( winding_t *w, float epsilon ) { + int i, nump; + vec3_t v; + vec3_t p[MAX_POINTS_ON_WINDING]; + + VectorCopy( w->p[0], p[0] ); + nump = 1; + for ( i = 1; i < w->numpoints; i++ ) + { + VectorSubtract( w->p[i], p[nump - 1], v ); + if ( VectorLength( v ) > epsilon ) { + if ( nump >= MAX_POINTS_ON_WINDING ) { + Error( "RemoveColinearPoints: MAX_POINTS_ON_WINDING" ); + } + VectorCopy( w->p[i], p[nump] ); + nump++; + } //end if + } //end for + + if ( nump == w->numpoints ) { + return; + } + + w->numpoints = nump; + memcpy( w->p, p, nump * sizeof( p[0] ) ); +} //end of the function RemoveEqualPoints +//=========================================================================== +// adds the given point to a winding at the given spot +// (for instance when spot is zero then the point is added at position zero) +// the original winding is NOT freed +// +// Parameter: - +// Returns: the new winding with the added point +// Changes Globals: - +//=========================================================================== +winding_t *AddWindingPoint( winding_t *w, vec3_t point, int spot ) { + int i, j; + winding_t *neww; + + if ( spot > w->numpoints ) { + Error( "AddWindingPoint: num > w->numpoints" ); + } //end if + if ( spot < 0 ) { + Error( "AddWindingPoint: num < 0" ); + } //end if + neww = AllocWinding( w->numpoints + 1 ); + neww->numpoints = w->numpoints + 1; + for ( i = 0, j = 0; i < neww->numpoints; i++ ) + { + if ( i == spot ) { + VectorCopy( point, neww->p[i] ); + } //end if + else + { + VectorCopy( w->p[j], neww->p[i] ); + j++; + } //end else + } //end for + return neww; +} //end of the function AddWindingPoint +//=========================================================================== +// the position where the new point should be added in the winding is +// stored in *spot +// +// Parameter: - +// Returns: true if the point is on the winding +// Changes Globals: - +//=========================================================================== +#define MELT_ON_EPSILON 0.2 + +int PointOnWinding( winding_t *w, vec3_t normal, float dist, vec3_t point, int *spot ) { + int i, j; + vec3_t v1, v2; + vec3_t edgenormal, edgevec; + float edgedist, dot; + + *spot = 0; + //the point must be on the winding plane + dot = DotProduct( point, normal ) - dist; + if ( dot < -MELT_ON_EPSILON || dot > MELT_ON_EPSILON ) { + return false; + } + // + for ( i = 0; i < w->numpoints; i++ ) + { + j = ( i + 1 ) % w->numpoints; + //get a plane orthogonal to the winding plane through the edge + VectorSubtract( w->p[j], w->p[i], edgevec ); + CrossProduct( normal, edgevec, edgenormal ); + VectorNormalize( edgenormal ); + edgedist = DotProduct( edgenormal, w->p[i] ); + //point must be not too far from the plane + dot = DotProduct( point, edgenormal ) - edgedist; + if ( dot < -MELT_ON_EPSILON || dot > MELT_ON_EPSILON ) { + continue; + } + //vector from first point of winding to the point to test + VectorSubtract( point, w->p[i], v1 ); + //vector from second point of winding to the point to test + VectorSubtract( point, w->p[j], v2 ); + //if the length of the vector is not larger than 0.5 units then + //the point is assumend to be the same as one of the winding points + if ( VectorNormalize( v1 ) < 0.5 ) { + return false; + } + if ( VectorNormalize( v2 ) < 0.5 ) { + return false; + } + //point must be between the two winding points + //(the two vectors must be directed towards each other, and on the + //same straight line) + if ( DotProduct( v1, v2 ) < -0.99 ) { + *spot = i + 1; + return true; + } //end if + } //end for + return false; +} //end of the function PointOnWinding +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindPlaneSeperatingWindings( winding_t *w1, winding_t *w2, vec3_t dir, + vec3_t normal, float *dist ) { + int i, i2, j, j2, n; + int sides1[3], sides2[3]; + float dist1, dist2, dot, diff; + vec3_t normal1, normal2; + vec3_t v1, v2; + + for ( i = 0; i < w1->numpoints; i++ ) + { + i2 = ( i + 1 ) % w1->numpoints; + // + VectorSubtract( w1->p[i2], w1->p[i], v1 ); + if ( VectorLength( v1 ) < 0.1 ) { + //Log_Write("FindPlaneSeperatingWindings: winding1 with degenerate edge\r\n"); + continue; + } //end if + CrossProduct( v1, dir, normal1 ); + VectorNormalize( normal1 ); + dist1 = DotProduct( normal1, w1->p[i] ); + // + for ( j = 0; j < w2->numpoints; j++ ) + { + j2 = ( j + 1 ) % w2->numpoints; + // + VectorSubtract( w2->p[j2], w2->p[j], v2 ); + if ( VectorLength( v2 ) < 0.1 ) { + //Log_Write("FindPlaneSeperatingWindings: winding2 with degenerate edge\r\n"); + continue; + } //end if + CrossProduct( v2, dir, normal2 ); + VectorNormalize( normal2 ); + dist2 = DotProduct( normal2, w2->p[j] ); + // + diff = dist1 - dist2; + if ( diff < -0.1 || diff > 0.1 ) { + dist2 = -dist2; + VectorNegate( normal2, normal2 ); + diff = dist1 - dist2; + if ( diff < -0.1 || diff > 0.1 ) { + continue; + } + } //end if + //check if the normal vectors are equal + for ( n = 0; n < 3; n++ ) + { + diff = normal1[n] - normal2[n]; + if ( diff < -0.0001 || diff > 0.0001 ) { + break; + } + } //end for + if ( n != 3 ) { + continue; + } + //check on which side of the seperating plane the points of + //the first winding are + sides1[0] = sides1[1] = sides1[2] = 0; + for ( n = 0; n < w1->numpoints; n++ ) + { + dot = DotProduct( w1->p[n], normal1 ) - dist1; + if ( dot > 0.1 ) { + sides1[0]++; + } else if ( dot < -0.1 ) { + sides1[1]++; + } else { sides1[2]++;} + } //end for + //check on which side of the seperating plane the points of + //the second winding are + sides2[0] = sides2[1] = sides2[2] = 0; + for ( n = 0; n < w2->numpoints; n++ ) + { + //used normal1 and dist1 (they are equal to normal2 and dist2) + dot = DotProduct( w2->p[n], normal1 ) - dist1; + if ( dot > 0.1 ) { + sides2[0]++; + } else if ( dot < -0.1 ) { + sides2[1]++; + } else { sides2[2]++;} + } //end for + //if the first winding has points at both sides + if ( sides1[0] && sides1[1] ) { + Log_Write( "FindPlaneSeperatingWindings: winding1 non-convex\r\n" ); + continue; + } //end if + //if the second winding has points at both sides + if ( sides2[0] && sides2[1] ) { + Log_Write( "FindPlaneSeperatingWindings: winding2 non-convex\r\n" ); + continue; + } //end if + // + if ( ( !sides1[0] && !sides1[1] ) || ( !sides2[0] && !sides2[1] ) ) { + //don't use one of the winding planes as the seperating plane + continue; + } //end if + //the windings must be at different sides of the seperating plane + if ( ( !sides1[0] && !sides2[1] ) || ( !sides1[1] && !sides2[0] ) ) { + VectorCopy( normal1, normal ); + *dist = dist1; + return true; + } //end if + } //end for + } //end for + return false; +} //end of the function FindPlaneSeperatingWindings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define WCONVEX_EPSILON 0.2 + +int WindingsNonConvex( winding_t *w1, winding_t *w2, + vec3_t normal1, vec3_t normal2, + float dist1, float dist2 ) { + int i; + + if ( !w1 || !w2 ) { + return false; + } + + //check if one of the points of face1 is at the back of the plane of face2 + for ( i = 0; i < w1->numpoints; i++ ) + { + if ( DotProduct( normal2, w1->p[i] ) - dist2 > WCONVEX_EPSILON ) { + return true; + } + } //end for + //check if one of the points of face2 is at the back of the plane of face1 + for ( i = 0; i < w2->numpoints; i++ ) + { + if ( DotProduct( normal1, w2->p[i] ) - dist1 > WCONVEX_EPSILON ) { + return true; + } + } //end for + + return false; +} //end of the function WindingsNonConvex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +#define VERTEX_EPSILON 0.5 + +qboolean EqualVertexes(vec3_t v1, vec3_t v2) +{ + float diff; + + diff = v1[0] - v2[0]; + if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) + { + diff = v1[1] - v2[1]; + if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) + { + diff = v1[2] - v2[2]; + if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) + { + return true; + } //end if + } //end if + } //end if + return false; +} //end of the function EqualVertexes + +#define CONTINUOUS_EPSILON 0.001 + +winding_t *AAS_MergeWindings(winding_t *w1, winding_t *w2, vec3_t windingnormal) +{ + int n, i, k; + vec3_t normal, delta; + winding_t *winding, *neww; + float dist, dot; + int p1, p2; + int points[2][64]; + int numpoints[2] = {0, 0}; + int newnumpoints; + int keep[2]; + + if (!FindPlaneSeperatingWindings(w1, w2, windingnormal, normal, &dist)) return NULL; + + //for both windings + for (n = 0; n < 2; n++) + { + if (n == 0) winding = w1; + else winding = w2; + //get the points of the winding which are on the seperating plane + for (i = 0; i < winding->numpoints; i++) + { + dot = DotProduct(winding->p[i], normal) - dist; + if (dot > -ON_EPSILON && dot < ON_EPSILON) + { + //don't allow more than 64 points on the seperating plane + if (numpoints[n] >= 64) Error("AAS_MergeWindings: more than 64 points on seperating plane\n"); + points[n][numpoints[n]++] = i; + } //end if + } //end for + //there must be at least two points of each winding on the seperating plane + if (numpoints[n] < 2) return NULL; + } //end for + + //if the first point of winding1 (which is on the seperating plane) is unequal + //to the last point of winding2 (which is on the seperating plane) + if (!EqualVertexes(w1->p[points[0][0]], w2->p[points[1][numpoints[1]-1]])) + { + return NULL; + } //end if + //if the last point of winding1 (which is on the seperating plane) is unequal + //to the first point of winding2 (which is on the seperating plane) + if (!EqualVertexes(w1->p[points[0][numpoints[0]-1]], w2->p[points[1][0]])) + { + return NULL; + } //end if + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + //first point of winding1 which is on the seperating plane + p1 = points[0][0]; + //point before p1 + p2 = (p1 + w1->numpoints - 1) % w1->numpoints; + VectorSubtract(w1->p[p1], w1->p[p2], delta); + CrossProduct(windingnormal, delta, normal); + VectorNormalize(normal, normal); + + //last point of winding2 which is on the seperating plane + p1 = points[1][numpoints[1]-1]; + //point after p1 + p2 = (p1 + 1) % w2->numpoints; + VectorSubtract(w2->p[p2], w2->p[p1], delta); + dot = DotProduct(delta, normal); + if (dot > CONTINUOUS_EPSILON) return NULL; //merging would create a non-convex polygon + keep[0] = (qboolean)(dot < -CONTINUOUS_EPSILON); + + //first point of winding2 which is on the seperating plane + p1 = points[1][0]; + //point before p1 + p2 = (p1 + w2->numpoints - 1) % w2->numpoints; + VectorSubtract(w2->p[p1], w2->p[p2], delta); + CrossProduct(windingnormal, delta, normal); + VectorNormalize(normal, normal); + + //last point of winding1 which is on the seperating plane + p1 = points[0][numpoints[0]-1]; + //point after p1 + p2 = (p1 + 1) % w1->numpoints; + VectorSubtract(w1->p[p2], w1->p[p1], delta); + dot = DotProduct(delta, normal); + if (dot > CONTINUOUS_EPSILON) return NULL; //merging would create a non-convex polygon + keep[1] = (qboolean)(dot < -CONTINUOUS_EPSILON); + + //number of points on the new winding + newnumpoints = w1->numpoints - numpoints[0] + w2->numpoints - numpoints[1] + 2; + //allocate the winding + neww = AllocWinding(newnumpoints); + neww->numpoints = newnumpoints; + //copy all the points + k = 0; + //for both windings + for (n = 0; n < 2; n++) + { + if (n == 0) winding = w1; + else winding = w2; + //copy the points of the winding starting with the last point on the + //seperating plane and ending before the first point on the seperating plane + for (i = points[n][numpoints[n]-1]; i != points[n][0]; i = (i+1)%winding->numpoints) + { + if (k >= newnumpoints) + { + Log_Print("numpoints[0] = %d\n", numpoints[0]); + Log_Print("numpoints[1] = %d\n", numpoints[1]); + Error("AAS_MergeWindings: k = %d >= newnumpoints = %d\n", k, newnumpoints); + } //end if + VectorCopy(winding->p[i], neww->p[k]); + k++; + } //end for + } //end for + RemoveEqualPoints(neww); + if (!WindingIsOk(neww, 1)) + { + Log_Print("AAS_MergeWindings: winding not ok after merging\n"); + FreeWinding(neww); + return NULL; + } //end if + return neww; +} //end of the function AAS_MergeWindings*/ +//#endif //ME diff --git a/src/bspc/l_poly.h b/src/bspc/l_poly.h new file mode 100644 index 0000000..9cb9784 --- /dev/null +++ b/src/bspc/l_poly.h @@ -0,0 +1,136 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_poly.h +// Function: +// Programmer: id Sofware +// Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-04 +// Tab Size: 3 +//=========================================================================== + +//a winding gives the bounding points of a convex polygon +typedef struct +{ + int numpoints; + vec3_t p[4]; //variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 96 + +//you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1 +#endif +//winding errors +#define WE_NONE 0 +#define WE_NOTENOUGHPOINTS 1 +#define WE_SMALLAREA 2 +#define WE_POINTBOGUSRANGE 3 +#define WE_POINTOFFPLANE 4 +#define WE_DEGENERATEEDGE 5 +#define WE_NONCONVEX 6 + +//allocates a winding +winding_t *AllocWinding( int points ); +//returns the area of the winding +vec_t WindingArea( winding_t *w ); +//gives the center of the winding +void WindingCenter( winding_t *w, vec3_t center ); +//clips the given winding to the given plane and gives the front +//and back part of the clipped winding +void ClipWindingEpsilon( winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back ); +//returns the fragment of the given winding that is on the front +//side of the cliping plane. The original is freed. +winding_t *ChopWinding( winding_t *in, vec3_t normal, vec_t dist ); +//returns a copy of the given winding +winding_t *CopyWinding( winding_t *w ); +//returns the reversed winding of the given one +winding_t *ReverseWinding( winding_t *w ); +//returns a base winding for the given plane +winding_t *BaseWindingForPlane( vec3_t normal, vec_t dist ); +//checks the winding for errors +void CheckWinding( winding_t *w ); +//returns the plane normal and dist the winding is in +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ); +//removes colinear points from the winding +void RemoveColinearPoints( winding_t *w ); +//returns on which side of the plane the winding is situated +int WindingOnPlaneSide( winding_t *w, vec3_t normal, vec_t dist ); +//frees the winding +void FreeWinding( winding_t *w ); +//gets the bounds of the winding +void WindingBounds( winding_t *w, vec3_t mins, vec3_t maxs ); +//chops the winding with the given plane, the original winding is freed if clipped +void ChopWindingInPlace( winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon ); +//prints the winding points on STDOUT +void pw( winding_t *w ); +//try to merge the two windings which are in the given plane +//the original windings are undisturbed +//the merged winding is returned when merging was possible +//NULL is returned otherwise +winding_t *TryMergeWinding( winding_t *f1, winding_t *f2, vec3_t planenormal ); +//brute force winding merging... creates a convex winding out of +//the two whatsoever +winding_t *MergeWindings( winding_t *w1, winding_t *w2, vec3_t planenormal ); + +//#ifdef ME +void ResetWindings( void ); +//returns the amount of winding memory +int WindingMemory( void ); +int WindingPeakMemory( void ); +int ActiveWindings( void ); +//returns the winding error string +char *WindingErrorString( void ); +//returns one of the WE_ flags when the winding has errors +int WindingError( winding_t *w ); +//removes equal points from the winding +void RemoveEqualPoints( winding_t *w, float epsilon ); +//returns a winding with a point added at the given spot to the +//given winding, original winding is NOT freed +winding_t *AddWindingPoint( winding_t *w, vec3_t point, int spot ); +//returns true if the point is on one of the winding 'edges' +//when the point is on one of the edged the number of the first +//point of the edge is stored in 'spot' +int PointOnWinding( winding_t *w, vec3_t normal, float dist, vec3_t point, int *spot ); +//find a plane seperating the two windings +//true is returned when the windings area adjacent +//the seperating plane normal and distance area stored in 'normal' and 'dist' +//this plane will contain both the piece of common edge of the two windings +//and the vector 'dir' +int FindPlaneSeperatingWindings( winding_t *w1, winding_t *w2, vec3_t dir, + vec3_t normal, float *dist ); +// +int WindingsNonConvex( winding_t *w1, winding_t *w2, + vec3_t normal1, vec3_t normal2, + float dist1, float dist2 ); +//#endif //ME + diff --git a/src/bspc/l_qfiles.c b/src/bspc/l_qfiles.c new file mode 100644 index 0000000..30ba73a --- /dev/null +++ b/src/bspc/l_qfiles.c @@ -0,0 +1,701 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_qfiles.h +// Function: - +// Programmer: Mr Elusive +// Last update: 1999-11-29 +// Tab Size: 3 +//=========================================================================== + +#if defined( WIN32 ) | defined( _WIN32 ) +#include +#include +#include +#else +#include +#include +#include +#endif + +#include "qbsp.h" + +//file extensions with their type +typedef struct qfile_exttype_s +{ + char *extension; + int type; +} qfile_exttyp_t; + +qfile_exttyp_t quakefiletypes[] = +{ + {QFILEEXT_UNKNOWN, QFILETYPE_UNKNOWN}, + {QFILEEXT_PAK, QFILETYPE_PAK}, + {QFILEEXT_PK3, QFILETYPE_PK3}, + {QFILEEXT_SIN, QFILETYPE_PAK}, + {QFILEEXT_BSP, QFILETYPE_BSP}, + {QFILEEXT_MAP, QFILETYPE_MAP}, + {QFILEEXT_MDL, QFILETYPE_MDL}, + {QFILEEXT_MD2, QFILETYPE_MD2}, + {QFILEEXT_MD3, QFILETYPE_MD3}, + {QFILEEXT_WAL, QFILETYPE_WAL}, + {QFILEEXT_WAV, QFILETYPE_WAV}, + {QFILEEXT_AAS, QFILETYPE_AAS}, + {NULL, 0} +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int QuakeFileExtensionType( char *extension ) { + int i; + + for ( i = 0; quakefiletypes[i].extension; i++ ) + { + if ( !stricmp( extension, quakefiletypes[i].extension ) ) { + return quakefiletypes[i].type; + } //end if + } //end for + return QFILETYPE_UNKNOWN; +} //end of the function QuakeFileExtensionType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *QuakeFileTypeExtension( int type ) { + int i; + + for ( i = 0; quakefiletypes[i].extension; i++ ) + { + if ( quakefiletypes[i].type == type ) { + return quakefiletypes[i].extension; + } //end if + } //end for + return QFILEEXT_UNKNOWN; +} //end of the function QuakeFileExtension +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int QuakeFileType( char *filename ) { + char ext[_MAX_PATH] = "."; + + ExtractFileExtension( filename, ext + 1 ); + return QuakeFileExtensionType( ext ); +} //end of the function QuakeFileTypeFromFileName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *StringContains( char *str1, char *str2, int casesensitive ) { + int len, i, j; + + len = strlen( str1 ) - strlen( str2 ); + for ( i = 0; i <= len; i++, str1++ ) + { + for ( j = 0; str2[j]; j++ ) + { + if ( casesensitive ) { + if ( str1[j] != str2[j] ) { + break; + } + } //end if + else + { + if ( toupper( str1[j] ) != toupper( str2[j] ) ) { + break; + } + } //end else + } //end for + if ( !str2[j] ) { + return str1; + } + } //end for + return NULL; +} //end of the function StringContains +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FileFilter( char *filter, char *filename, int casesensitive ) { + char buf[1024]; + char *ptr; + int i, found; + + while ( *filter ) + { + if ( *filter == '*' ) { + filter++; + for ( i = 0; *filter; i++ ) + { + if ( *filter == '*' || *filter == '?' ) { + break; + } + buf[i] = *filter; + filter++; + } //end for + buf[i] = '\0'; + if ( strlen( buf ) ) { + ptr = StringContains( filename, buf, casesensitive ); + if ( !ptr ) { + return false; + } + filename = ptr + strlen( buf ); + } //end if + } //end if + else if ( *filter == '?' ) { + filter++; + filename++; + } //end else if + else if ( *filter == '[' && *( filter + 1 ) == '[' ) { + filter++; + } //end if + else if ( *filter == '[' ) { + filter++; + found = false; + while ( *filter && !found ) + { + if ( *filter == ']' && *( filter + 1 ) != ']' ) { + break; + } + if ( *( filter + 1 ) == '-' && *( filter + 2 ) && ( *( filter + 2 ) != ']' || *( filter + 3 ) == ']' ) ) { + if ( casesensitive ) { + if ( *filename >= *filter && *filename <= *( filter + 2 ) ) { + found = true; + } + } //end if + else + { + if ( toupper( *filename ) >= toupper( *filter ) && + toupper( *filename ) <= toupper( *( filter + 2 ) ) ) { + found = true; + } + } //end else + filter += 3; + } //end if + else + { + if ( casesensitive ) { + if ( *filter == *filename ) { + found = true; + } + } //end if + else + { + if ( toupper( *filter ) == toupper( *filename ) ) { + found = true; + } + } //end else + filter++; + } //end else + } //end while + if ( !found ) { + return false; + } + while ( *filter ) + { + if ( *filter == ']' && *( filter + 1 ) != ']' ) { + break; + } + filter++; + } //end while + filter++; + filename++; + } //end else if + else + { + if ( casesensitive ) { + if ( *filter != *filename ) { + return false; + } + } //end if + else + { + if ( toupper( *filter ) != toupper( *filename ) ) { + return false; + } + } //end else + filter++; + filename++; + } //end else + } //end while + return true; +} //end of the function FileFilter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *FindQuakeFilesInZip( char *zipfile, char *filter ) { + unzFile uf; + int err; + unz_global_info gi; + char filename_inzip[MAX_PATH]; + unz_file_info file_info; + int i; + quakefile_t *qfiles, *lastqf, *qf; + + uf = unzOpen( zipfile ); + err = unzGetGlobalInfo( uf, &gi ); + + if ( err != UNZ_OK ) { + return NULL; + } + + unzGoToFirstFile( uf ); + + qfiles = NULL; + lastqf = NULL; + for ( i = 0; i < gi.number_entry; i++ ) + { + err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL,0,NULL,0 ); + if ( err != UNZ_OK ) { + break; + } + + ConvertPath( filename_inzip ); + if ( FileFilter( filter, filename_inzip, false ) ) { + qf = malloc( sizeof( quakefile_t ) ); + if ( !qf ) { + Error( "out of memory" ); + } + memset( qf, 0, sizeof( quakefile_t ) ); + strcpy( qf->pakfile, zipfile ); + strcpy( qf->filename, zipfile ); + strcpy( qf->origname, filename_inzip ); + qf->zipfile = true; + //memcpy( &buildBuffer[i].zipfileinfo, (unz_s*)uf, sizeof(unz_s)); + memcpy( &qf->zipinfo, (unz_s*)uf, sizeof( unz_s ) ); + qf->offset = 0; + qf->length = file_info.uncompressed_size; + qf->type = QuakeFileType( filename_inzip ); + //add the file ot the list + qf->next = NULL; + if ( lastqf ) { + lastqf->next = qf; + } else { qfiles = qf;} + lastqf = qf; + } //end if + unzGoToNextFile( uf ); + } //end for + + unzClose( uf ); + + return qfiles; +} //end of the function FindQuakeFilesInZip +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *FindQuakeFilesInPak( char *pakfile, char *filter ) { + FILE *fp; + dpackheader_t packheader; + dsinpackfile_t *packfiles; + dpackfile_t *idpackfiles; + quakefile_t *qfiles, *lastqf, *qf; + int numpackdirs, i; + + qfiles = NULL; + lastqf = NULL; + //open the pak file + fp = fopen( pakfile, "rb" ); + if ( !fp ) { + Warning( "can't open pak file %s", pakfile ); + return NULL; + } //end if + //read pak header, check for valid pak id and seek to the dir entries + if ( ( fread( &packheader, 1, sizeof( dpackheader_t ), fp ) != sizeof( dpackheader_t ) ) + || ( packheader.ident != IDPAKHEADER && packheader.ident != SINPAKHEADER ) + || ( fseek( fp, LittleLong( packheader.dirofs ), SEEK_SET ) ) + ) { + fclose( fp ); + Warning( "invalid pak file %s", pakfile ); + return NULL; + } //end if + //if it is a pak file from id software + if ( packheader.ident == IDPAKHEADER ) { + //number of dir entries in the pak file + numpackdirs = LittleLong( packheader.dirlen ) / sizeof( dpackfile_t ); + idpackfiles = (dpackfile_t *) malloc( numpackdirs * sizeof( dpackfile_t ) ); + if ( !idpackfiles ) { + Error( "out of memory" ); + } + //read the dir entry + if ( fread( idpackfiles, sizeof( dpackfile_t ), numpackdirs, fp ) != numpackdirs ) { + fclose( fp ); + free( idpackfiles ); + Warning( "can't read the Quake pak file dir entries from %s", pakfile ); + return NULL; + } //end if + fclose( fp ); + //convert to sin pack files + packfiles = (dsinpackfile_t *) malloc( numpackdirs * sizeof( dsinpackfile_t ) ); + if ( !packfiles ) { + Error( "out of memory" ); + } + for ( i = 0; i < numpackdirs; i++ ) + { + strcpy( packfiles[i].name, idpackfiles[i].name ); + packfiles[i].filepos = LittleLong( idpackfiles[i].filepos ); + packfiles[i].filelen = LittleLong( idpackfiles[i].filelen ); + } //end for + free( idpackfiles ); + } //end if + else //its a Sin pack file + { + //number of dir entries in the pak file + numpackdirs = LittleLong( packheader.dirlen ) / sizeof( dsinpackfile_t ); + packfiles = (dsinpackfile_t *) malloc( numpackdirs * sizeof( dsinpackfile_t ) ); + if ( !packfiles ) { + Error( "out of memory" ); + } + //read the dir entry + if ( fread( packfiles, sizeof( dsinpackfile_t ), numpackdirs, fp ) != numpackdirs ) { + fclose( fp ); + free( packfiles ); + Warning( "can't read the Sin pak file dir entries from %s", pakfile ); + return NULL; + } //end if + fclose( fp ); + for ( i = 0; i < numpackdirs; i++ ) + { + packfiles[i].filepos = LittleLong( packfiles[i].filepos ); + packfiles[i].filelen = LittleLong( packfiles[i].filelen ); + } //end for + } //end else + // + for ( i = 0; i < numpackdirs; i++ ) + { + ConvertPath( packfiles[i].name ); + if ( FileFilter( filter, packfiles[i].name, false ) ) { + qf = malloc( sizeof( quakefile_t ) ); + if ( !qf ) { + Error( "out of memory" ); + } + memset( qf, 0, sizeof( quakefile_t ) ); + strcpy( qf->pakfile, pakfile ); + strcpy( qf->filename, pakfile ); + strcpy( qf->origname, packfiles[i].name ); + qf->zipfile = false; + qf->offset = packfiles[i].filepos; + qf->length = packfiles[i].filelen; + qf->type = QuakeFileType( packfiles[i].name ); + //add the file ot the list + qf->next = NULL; + if ( lastqf ) { + lastqf->next = qf; + } else { qfiles = qf;} + lastqf = qf; + } //end if + } //end for + free( packfiles ); + return qfiles; +} //end of the function FindQuakeFilesInPak +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *FindQuakeFilesWithPakFilter( char *pakfilter, char *filter ) { +#if defined( WIN32 ) | defined( _WIN32 ) + WIN32_FIND_DATA filedata; + HWND handle; + struct _stat statbuf; +#else + glob_t globbuf; + struct stat statbuf; + int j; +#endif + quakefile_t *qfiles, *lastqf, *qf; + char pakfile[_MAX_PATH], filename[_MAX_PATH], *str; + int done; + + qfiles = NULL; + lastqf = NULL; + if ( pakfilter && strlen( pakfilter ) ) { +#if defined( WIN32 ) | defined( _WIN32 ) + handle = FindFirstFile( pakfilter, &filedata ); + done = ( handle == INVALID_HANDLE_VALUE ); + while ( !done ) + { + _splitpath( pakfilter, pakfile, NULL, NULL, NULL ); + _splitpath( pakfilter, NULL, &pakfile[strlen( pakfile )], NULL, NULL ); + AppendPathSeperator( pakfile, _MAX_PATH ); + strcat( pakfile, filedata.cFileName ); + _stat( pakfile, &statbuf ); +#else + glob( pakfilter, 0, NULL, &globbuf ); + for ( j = 0; j < globbuf.gl_pathc; j++ ) + { + strcpy( pakfile, globbuf.gl_pathv[j] ); + stat( pakfile, &statbuf ); +#endif + //if the file with .pak or .pk3 is a folder + if ( statbuf.st_mode & S_IFDIR ) { + strcpy( filename, pakfilter ); + AppendPathSeperator( filename, _MAX_PATH ); + strcat( filename, filter ); + qf = FindQuakeFilesWithPakFilter( NULL, filename ); + if ( lastqf ) { + lastqf->next = qf; + } else { qfiles = qf;} + lastqf = qf; + while ( lastqf->next ) lastqf = lastqf->next; + } //end if + else + { +#if defined( WIN32 ) | defined( _WIN32 ) + str = StringContains( pakfile, ".pk3", false ); +#else + str = StringContains( pakfile, ".pk3", true ); +#endif + if ( str && str == pakfile + strlen( pakfile ) - strlen( ".pk3" ) ) { + qf = FindQuakeFilesInZip( pakfile, filter ); + } //end if + else + { + qf = FindQuakeFilesInPak( pakfile, filter ); + } //end else + // + if ( qf ) { + if ( lastqf ) { + lastqf->next = qf; + } else { qfiles = qf;} + lastqf = qf; + while ( lastqf->next ) lastqf = lastqf->next; + } //end if + } //end else + // +#if defined( WIN32 ) | defined( _WIN32 ) + //find the next file + done = !FindNextFile( handle, &filedata ); + } //end while +#else + } //end for + globfree( &globbuf ); +#endif + } //end if + else + { +#if defined( WIN32 ) | defined( _WIN32 ) + handle = FindFirstFile( filter, &filedata ); + done = ( handle == INVALID_HANDLE_VALUE ); + while ( !done ) + { + _splitpath( filter, filename, NULL, NULL, NULL ); + _splitpath( filter, NULL, &filename[strlen( filename )], NULL, NULL ); + AppendPathSeperator( filename, _MAX_PATH ); + strcat( filename, filedata.cFileName ); +#else + glob( filter, 0, NULL, &globbuf ); + for ( j = 0; j < globbuf.gl_pathc; j++ ) + { + strcpy( filename, globbuf.gl_pathv[j] ); +#endif + // + qf = malloc( sizeof( quakefile_t ) ); + if ( !qf ) { + Error( "out of memory" ); + } + memset( qf, 0, sizeof( quakefile_t ) ); + strcpy( qf->pakfile, "" ); + strcpy( qf->filename, filename ); + strcpy( qf->origname, filename ); + qf->offset = 0; + qf->length = 0; + qf->type = QuakeFileType( filename ); + //add the file ot the list + qf->next = NULL; + if ( lastqf ) { + lastqf->next = qf; + } else { qfiles = qf;} + lastqf = qf; +#if defined( WIN32 ) | defined( _WIN32 ) + //find the next file + done = !FindNextFile( handle, &filedata ); + } //end while +#else + } //end for + globfree( &globbuf ); +#endif + } //end else + return qfiles; +} //end of the function FindQuakeFilesWithPakFilter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +quakefile_t *FindQuakeFiles( char *filter ) { + char *str; + char newfilter[_MAX_PATH]; + char pakfilter[_MAX_PATH]; + char filefilter[_MAX_PATH]; + + strcpy( newfilter, filter ); + ConvertPath( newfilter ); + strcpy( pakfilter, newfilter ); + + str = StringContains( pakfilter, ".pak", false ); + if ( !str ) { + str = StringContains( pakfilter, ".pk3", false ); + } + + if ( str ) { + str += strlen( ".pak" ); + if ( *str ) { + *str++ = '\0'; + while ( *str == '\\' || *str == '/' ) str++; + strcpy( filefilter, str ); + return FindQuakeFilesWithPakFilter( pakfilter, filefilter ); + } //end if + } //end else + return FindQuakeFilesWithPakFilter( NULL, newfilter ); +} //end of the function FindQuakeFiles +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int LoadQuakeFile( quakefile_t *qf, void **bufferptr ) { + FILE *fp; + void *buffer; + int length; + unzFile zf; + + if ( qf->zipfile ) { + //open the zip file + zf = unzOpen( qf->pakfile ); + //set the file pointer + qf->zipinfo.file = ( (unz_s *) zf )->file; + //open the Quake file in the zip file + unzOpenCurrentFile( &qf->zipinfo ); + //allocate memory for the buffer + length = qf->length; + buffer = GetMemory( length + 1 ); + //read the Quake file from the zip file + length = unzReadCurrentFile( &qf->zipinfo, buffer, length ); + //close the Quake file in the zip file + unzCloseCurrentFile( &qf->zipinfo ); + //close the zip file + unzClose( zf ); + + *bufferptr = buffer; + return length; + } //end if + else + { + fp = SafeOpenRead( qf->filename ); + if ( qf->offset ) { + fseek( fp, qf->offset, SEEK_SET ); + } + length = qf->length; + if ( !length ) { + length = Q_filelength( fp ); + } + buffer = GetMemory( length + 1 ); + ( (char *)buffer )[length] = 0; + SafeRead( fp, buffer, length ); + fclose( fp ); + + *bufferptr = buffer; + return length; + } //end else +} //end of the function LoadQuakeFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadQuakeFile( quakefile_t *qf, void *buffer, int offset, int length ) { + FILE *fp; + int read; + unzFile zf; + char tmpbuf[1024]; + + if ( qf->zipfile ) { + //open the zip file + zf = unzOpen( qf->pakfile ); + //set the file pointer + qf->zipinfo.file = ( (unz_s *) zf )->file; + //open the Quake file in the zip file + unzOpenCurrentFile( &qf->zipinfo ); + // + while ( offset > 0 ) + { + read = offset; + if ( read > sizeof( tmpbuf ) ) { + read = sizeof( tmpbuf ); + } + unzReadCurrentFile( &qf->zipinfo, tmpbuf, read ); + offset -= read; + } //end while + //read the Quake file from the zip file + length = unzReadCurrentFile( &qf->zipinfo, buffer, length ); + //close the Quake file in the zip file + unzCloseCurrentFile( &qf->zipinfo ); + //close the zip file + unzClose( zf ); + + return length; + } //end if + else + { + fp = SafeOpenRead( qf->filename ); + if ( qf->offset ) { + fseek( fp, qf->offset, SEEK_SET ); + } + if ( offset ) { + fseek( fp, offset, SEEK_CUR ); + } + SafeRead( fp, buffer, length ); + fclose( fp ); + + return length; + } //end else +} //end of the function ReadQuakeFile diff --git a/src/bspc/l_qfiles.h b/src/bspc/l_qfiles.h new file mode 100644 index 0000000..fa88267 --- /dev/null +++ b/src/bspc/l_qfiles.h @@ -0,0 +1,106 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_qfiles.h +// Function: - +// Programmer: Mr Elusive +// Last update: 1999-12-01 +// Tab Size: 3 +//=========================================================================== + +#include "../qcommon/unzip.h" + +#define QFILETYPE_UNKNOWN 0x8000 +#define QFILETYPE_PAK 0x0001 +#define QFILETYPE_PK3 0x0002 +#define QFILETYPE_BSP 0x0004 +#define QFILETYPE_MAP 0x0008 +#define QFILETYPE_MDL 0x0010 +#define QFILETYPE_MD2 0x0020 +#define QFILETYPE_MD3 0x0040 +#define QFILETYPE_WAL 0x0080 +#define QFILETYPE_WAV 0x0100 +#define QFILETYPE_AAS 0x4000 + +#define QFILEEXT_UNKNOWN "" +#define QFILEEXT_PAK ".PAK" +#define QFILEEXT_PK3 ".PK3" +#define QFILEEXT_SIN ".SIN" +#define QFILEEXT_BSP ".BSP" +#define QFILEEXT_MAP ".MAP" +#define QFILEEXT_MDL ".MDL" +#define QFILEEXT_MD2 ".MD2" +#define QFILEEXT_MD3 ".MD3" +#define QFILEEXT_WAL ".WAL" +#define QFILEEXT_WAV ".WAV" +#define QFILEEXT_AAS ".AAS" + +//maximum path length +#ifndef _MAX_PATH + #define _MAX_PATH 1024 +#endif + +//for Sin packs +#define MAX_PAK_FILENAME_LENGTH 120 +#define SINPAKHEADER ( ( 'K' << 24 ) + ( 'A' << 16 ) + ( 'P' << 8 ) + 'S' ) + +typedef struct +{ + char name[MAX_PAK_FILENAME_LENGTH]; + int filepos, filelen; +} dsinpackfile_t; + +typedef struct quakefile_s +{ + char pakfile[_MAX_PATH]; + char filename[_MAX_PATH]; + char origname[_MAX_PATH]; + int zipfile; + int type; + int offset; + int length; + unz_s zipinfo; + struct quakefile_s *next; +} quakefile_t; + +//returns the file extension for the given type +char *QuakeFileTypeExtension( int type ); +//returns the file type for the given extension +int QuakeFileExtensionType( char *extension ); +//return the Quake file type for the given file +int QuakeFileType( char *filename ); +//returns true if the filename complies to the filter +int FileFilter( char *filter, char *filename, int casesensitive ); +//find Quake files using the given filter +quakefile_t *FindQuakeFiles( char *filter ); +//load the given Quake file, returns the length of the file +int LoadQuakeFile( quakefile_t *qf, void **bufferptr ); +//read part of a Quake file into the buffer +int ReadQuakeFile( quakefile_t *qf, void *buffer, int offset, int length ); diff --git a/src/bspc/l_threads.c b/src/bspc/l_threads.c new file mode 100644 index 0000000..b090453 --- /dev/null +++ b/src/bspc/l_threads.c @@ -0,0 +1,1528 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_threads.c +// Function: multi-threading +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1999-05-14 +// Tab Size: 3 +//=========================================================================== + +#include "l_cmd.h" +#include "l_threads.h" +#include "l_log.h" +#include "l_mem.h" + +#define MAX_THREADS 64 + +//#define THREAD_DEBUG + +int dispatch; +int workcount; +int oldf; +qboolean pacifier; +qboolean threaded; +void ( *workfunction )( int ); + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetThreadWork( void ) { + int r; + int f; + + ThreadLock(); + + if ( dispatch == workcount ) { + ThreadUnlock(); + return -1; + } + + f = 10 * dispatch / workcount; + if ( f != oldf ) { + oldf = f; + if ( pacifier ) { + printf( "%i...", f ); + } + } //end if + + r = dispatch; + dispatch++; + ThreadUnlock(); + + return r; +} //end of the function GetThreadWork +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadWorkerFunction( int threadnum ) { + int work; + + while ( 1 ) + { + work = GetThreadWork(); + if ( work == -1 ) { + break; + } +//printf ("thread %i, work %i\n", threadnum, work); + workfunction( work ); + } //end while +} //end of the function ThreadWorkerFunction +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOnIndividual( int workcnt, qboolean showpacifier, void ( *func )(int) ) { + if ( numthreads == -1 ) { + ThreadSetDefault(); + } + workfunction = func; + RunThreadsOn( workcnt, showpacifier, ThreadWorkerFunction ); +} //end of the function RunThreadsOnIndividual + + +//=================================================================== +// +// WIN32 +// +//=================================================================== + +#if defined( WIN32 ) || defined( _WIN32 ) + +#define USED + +#include + +typedef struct thread_s +{ + HANDLE handle; + int threadid; + int id; + struct thread_s *next; +} thread_t; + +thread_t *firstthread; +thread_t *lastthread; +int currentnumthreads; +int currentthreadid; + +int numthreads = 1; +CRITICAL_SECTION crit; +HANDLE semaphore; +static int enter; +static int numwaitingthreads = 0; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault( void ) { + SYSTEM_INFO info; + + if ( numthreads == -1 ) { // not set manually + GetSystemInfo( &info ); + numthreads = info.dwNumberOfProcessors; + if ( numthreads < 1 || numthreads > 32 ) { + numthreads = 1; + } + } //end if + qprintf( "%i threads\n", numthreads ); +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock( void ) { + if ( !threaded ) { + Error( "ThreadLock: !threaded" ); + return; + } //end if + EnterCriticalSection( &crit ); + if ( enter ) { + Error( "Recursive ThreadLock\n" ); + } + enter = 1; +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock( void ) { + if ( !threaded ) { + Error( "ThreadUnlock: !threaded" ); + return; + } //end if + if ( !enter ) { + Error( "ThreadUnlock without lock\n" ); + } + enter = 0; + LeaveCriticalSection( &crit ); +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock( void ) { + Log_Print( "Win32 multi-threading\n" ); + InitializeCriticalSection( &crit ); + threaded = true; //Stupid me... forgot this!!! + currentnumthreads = 0; + currentthreadid = 0; +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock( void ) { + DeleteCriticalSection( &crit ); + threaded = false; //Stupid me... forgot this!!! +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupSemaphore( void ) { + semaphore = CreateSemaphore( NULL, 0, 99999999, "bspc" ); +} //end of the function ThreadSetupSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownSemaphore( void ) { +} //end of the function ThreadShutdownSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreWait( void ) { + WaitForSingleObject( semaphore, INFINITE ); +} //end of the function ThreadSemaphoreWait +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreIncrease( int count ) { + ReleaseSemaphore( semaphore, count, NULL ); +} //end of the function ThreadSemaphoreIncrease +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )(int) ) { + int threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + int start, end; + + Log_Print( "Win32 multi-threading\n" ); + start = I_FloatTime(); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if ( numthreads == -1 ) { + ThreadSetDefault(); + } + + if ( numthreads < 1 || numthreads > MAX_THREADS ) { + numthreads = 1; + } + // + // run threads in parallel + // + InitializeCriticalSection( &crit ); + + numwaitingthreads = 0; + + if ( numthreads == 1 ) { // use same thread + func( 0 ); + } //end if + else + { +// printf("starting %d threads\n", numthreads); + for ( i = 0; i < numthreads; i++ ) + { + threadhandle[i] = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, + (LPVOID)i, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &threadid[i] ); +// printf("started thread %d\n", i); + } //end for + + for ( i = 0; i < numthreads; i++ ) + WaitForSingleObject( threadhandle[i], INFINITE ); + } //end else + DeleteCriticalSection( &crit ); + + threaded = false; + end = I_FloatTime(); + if ( pacifier ) { + printf( " (%i)\n", end - start ); + } +} //end of the function RunThreadsOn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddThread( void ( *func )(int) ) { + thread_t *thread; + + if ( numthreads == 1 ) { + if ( currentnumthreads >= numthreads ) { + return; + } + currentnumthreads++; + func( -1 ); + currentnumthreads--; + } //end if + else + { + ThreadLock(); + if ( currentnumthreads >= numthreads ) { + ThreadUnlock(); + return; + } //end if + //allocate new thread + thread = GetMemory( sizeof( thread_t ) ); + if ( !thread ) { + Error( "can't allocate memory for thread\n" ); + } + + // + thread->threadid = currentthreadid; + thread->handle = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, + (LPVOID) thread->threadid, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &thread->id ); + + //add the thread to the end of the list + thread->next = NULL; + if ( lastthread ) { + lastthread->next = thread; + } else { firstthread = thread;} + lastthread = thread; + // +#ifdef THREAD_DEBUG + qprintf( "added thread with id %d\n", thread->threadid ); +#endif //THREAD_DEBUG + // + currentnumthreads++; + currentthreadid++; + // + ThreadUnlock(); + } //end else +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread( int threadid ) { + thread_t *thread, *last; + + //if a single thread + if ( threadid == -1 ) { + return; + } + // + ThreadLock(); + last = NULL; + for ( thread = firstthread; thread; thread = thread->next ) + { + if ( thread->threadid == threadid ) { + if ( last ) { + last->next = thread->next; + } else { firstthread = thread->next;} + if ( !thread->next ) { + lastthread = last; + } + // + FreeMemory( thread ); + currentnumthreads--; +#ifdef THREAD_DEBUG + qprintf( "removed thread with id %d\n", threadid ); +#endif //THREAD_DEBUG + break; + } //end if + last = thread; + } //end if + if ( !thread ) { + Error( "couldn't find thread with id %d", threadid ); + } + ThreadUnlock(); +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished( void ) { + HANDLE handle; + + ThreadLock(); + while ( firstthread ) + { + handle = firstthread->handle; + ThreadUnlock(); + + WaitForSingleObject( handle, INFINITE ); + + ThreadLock(); + } //end while + ThreadUnlock(); +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads( void ) { + return currentnumthreads; +} //end of the function GetNumThreads + +#endif + + +//=================================================================== +// +// OSF1 +// +//=================================================================== + +#if defined( __osf__ ) + +#define USED + +#include + +typedef struct thread_s +{ + pthread_t thread; + int threadid; + int id; + struct thread_s *next; +} thread_t; + +thread_t *firstthread; +thread_t *lastthread; +int currentnumthreads; +int currentthreadid; + +int numthreads = 1; +pthread_mutex_t my_mutex; +pthread_attr_t attrib; +static int enter; +static int numwaitingthreads = 0; + + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault( void ) { + if ( numthreads == -1 ) { // not set manually + numthreads = 1; + } //end if + qprintf( "%i threads\n", numthreads ); +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock( void ) { + if ( !threaded ) { + Error( "ThreadLock: !threaded" ); + return; + } //end if + if ( my_mutex ) { + pthread_mutex_lock( my_mutex ); + } //end if + if ( enter ) { + Error( "Recursive ThreadLock\n" ); + } + enter = 1; +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock( void ) { + if ( !threaded ) { + Error( "ThreadUnlock: !threaded" ); + return; + } //end if + if ( !enter ) { + Error( "ThreadUnlock without lock\n" ); + } + enter = 0; + if ( my_mutex ) { + pthread_mutex_unlock( my_mutex ); + } //end if +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock( void ) { + pthread_mutexattr_t mattrib; + + Log_Print( "pthread multi-threading\n" ); + + if ( !my_mutex ) { + my_mutex = GetMemory( sizeof( *my_mutex ) ); + if ( pthread_mutexattr_create( &mattrib ) == -1 ) { + Error( "pthread_mutex_attr_create failed" ); + } + if ( pthread_mutexattr_setkind_np( &mattrib, MUTEX_FAST_NP ) == -1 ) { + Error( "pthread_mutexattr_setkind_np failed" ); + } + if ( pthread_mutex_init( my_mutex, mattrib ) == -1 ) { + Error( "pthread_mutex_init failed" ); + } + } + + if ( pthread_attr_create( &attrib ) == -1 ) { + Error( "pthread_attr_create failed" ); + } + if ( pthread_attr_setstacksize( &attrib, 0x100000 ) == -1 ) { + Error( "pthread_attr_setstacksize failed" ); + } + + threaded = true; + currentnumthreads = 0; + currentthreadid = 0; +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock( void ) { + threaded = false; +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )(int) ) { + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + Log_Print( "pthread multi-threading\n" ); + + start = I_FloatTime(); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if ( numthreads < 1 || numthreads > MAX_THREADS ) { + numthreads = 1; + } + + if ( pacifier ) { + setbuf( stdout, NULL ); + } + + if ( !my_mutex ) { + my_mutex = GetMemory( sizeof( *my_mutex ) ); + if ( pthread_mutexattr_create( &mattrib ) == -1 ) { + Error( "pthread_mutex_attr_create failed" ); + } + if ( pthread_mutexattr_setkind_np( &mattrib, MUTEX_FAST_NP ) == -1 ) { + Error( "pthread_mutexattr_setkind_np failed" ); + } + if ( pthread_mutex_init( my_mutex, mattrib ) == -1 ) { + Error( "pthread_mutex_init failed" ); + } + } + + if ( pthread_attr_create( &attrib ) == -1 ) { + Error( "pthread_attr_create failed" ); + } + if ( pthread_attr_setstacksize( &attrib, 0x100000 ) == -1 ) { + Error( "pthread_attr_setstacksize failed" ); + } + + for ( i = 0 ; i < numthreads ; i++ ) + { + if ( pthread_create( &work_threads[i], attrib + , (pthread_startroutine_t)func, (pthread_addr_t)i ) == -1 ) { + Error( "pthread_create failed" ); + } + } + + for ( i = 0 ; i < numthreads ; i++ ) + { + if ( pthread_join( work_threads[i], &status ) == -1 ) { + Error( "pthread_join failed" ); + } + } + + threaded = false; + + end = I_FloatTime(); + if ( pacifier ) { + printf( " (%i)\n", end - start ); + } +} //end of the function RunThreadsOn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddThread( void ( *func )(int) ) { + thread_t *thread; + + if ( numthreads == 1 ) { + if ( currentnumthreads >= numthreads ) { + return; + } + currentnumthreads++; + func( -1 ); + currentnumthreads--; + } //end if + else + { + ThreadLock(); + if ( currentnumthreads >= numthreads ) { + ThreadUnlock(); + return; + } //end if + //allocate new thread + thread = GetMemory( sizeof( thread_t ) ); + if ( !thread ) { + Error( "can't allocate memory for thread\n" ); + } + // + thread->threadid = currentthreadid; + + if ( pthread_create( &thread->thread, attrib, (pthread_startroutine_t)func, (pthread_addr_t)thread->threadid ) == -1 ) { + Error( "pthread_create failed" ); + } + + //add the thread to the end of the list + thread->next = NULL; + if ( lastthread ) { + lastthread->next = thread; + } else { firstthread = thread;} + lastthread = thread; + // +#ifdef THREAD_DEBUG + qprintf( "added thread with id %d\n", thread->threadid ); +#endif //THREAD_DEBUG + // + currentnumthreads++; + currentthreadid++; + // + ThreadUnlock(); + } //end else +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread( int threadid ) { + thread_t *thread, *last; + + //if a single thread + if ( threadid == -1 ) { + return; + } + // + ThreadLock(); + last = NULL; + for ( thread = firstthread; thread; thread = thread->next ) + { + if ( thread->threadid == threadid ) { + if ( last ) { + last->next = thread->next; + } else { firstthread = thread->next;} + if ( !thread->next ) { + lastthread = last; + } + // + FreeMemory( thread ); + currentnumthreads--; +#ifdef THREAD_DEBUG + qprintf( "removed thread with id %d\n", threadid ); +#endif //THREAD_DEBUG + break; + } //end if + last = thread; + } //end if + if ( !thread ) { + Error( "couldn't find thread with id %d", threadid ); + } + ThreadUnlock(); +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished( void ) { + pthread_t *thread; + pthread_addr_t status; + + ThreadLock(); + while ( firstthread ) + { + thread = &firstthread->thread; + ThreadUnlock(); + + if ( pthread_join( *thread, &status ) == -1 ) { + Error( "pthread_join failed" ); + } + + ThreadLock(); + } //end while + ThreadUnlock(); +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads( void ) { + return currentnumthreads; +} //end of the function GetNumThreads + +#endif + +//=================================================================== +// +// LINUX +// +//=================================================================== + +#if defined( LINUX ) + +#define USED + +#include +#include + +typedef struct thread_s +{ + pthread_t thread; + int threadid; + int id; + struct thread_s *next; +} thread_t; + +thread_t *firstthread; +thread_t *lastthread; +int currentnumthreads; +int currentthreadid; + +int numthreads = 1; +pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_attr_t attrib; +sem_t semaphore; +static int enter; +static int numwaitingthreads = 0; + + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault( void ) { + if ( numthreads == -1 ) { // not set manually + numthreads = 1; + } //end if + qprintf( "%i threads\n", numthreads ); +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock( void ) { + if ( !threaded ) { + Error( "ThreadLock: !threaded" ); + return; + } //end if + pthread_mutex_lock( &my_mutex ); + if ( enter ) { + Error( "Recursive ThreadLock\n" ); + } + enter = 1; +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock( void ) { + if ( !threaded ) { + Error( "ThreadUnlock: !threaded" ); + return; + } //end if + if ( !enter ) { + Error( "ThreadUnlock without lock\n" ); + } + enter = 0; + pthread_mutex_unlock( &my_mutex ); +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock( void ) { + pthread_mutexattr_t mattrib; + + Log_Print( "pthread multi-threading\n" ); + + threaded = true; + currentnumthreads = 0; + currentthreadid = 0; +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock( void ) { + threaded = false; +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupSemaphore( void ) { + sem_init( &semaphore, 0, 0 ); +} //end of the function ThreadSetupSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownSemaphore( void ) { + sem_destroy( &semaphore ); +} //end of the function ThreadShutdownSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreWait( void ) { + sem_wait( &semaphore ); +} //end of the function ThreadSemaphoreWait +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreIncrease( int count ) { + int i; + + for ( i = 0; i < count; i++ ) + { + sem_post( &semaphore ); + } //end for +} //end of the function ThreadSemaphoreIncrease +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )(int) ) { + int i; + pthread_t work_threads[MAX_THREADS]; + void *pthread_return; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + Log_Print( "pthread multi-threading\n" ); + + start = I_FloatTime(); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if ( numthreads < 1 || numthreads > MAX_THREADS ) { + numthreads = 1; + } + + if ( pacifier ) { + setbuf( stdout, NULL ); + } + + for ( i = 0 ; i < numthreads ; i++ ) + { + if ( pthread_create( &work_threads[i], NULL, (void *)func, (void *)i ) == -1 ) { + Error( "pthread_create failed" ); + } + } + + for ( i = 0 ; i < numthreads ; i++ ) + { + if ( pthread_join( work_threads[i], &pthread_return ) == -1 ) { + Error( "pthread_join failed" ); + } + } + + threaded = false; + + end = I_FloatTime(); + if ( pacifier ) { + printf( " (%i)\n", end - start ); + } +} //end of the function RunThreadsOn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddThread( void ( *func )(int) ) { + thread_t *thread; + + if ( numthreads == 1 ) { + if ( currentnumthreads >= numthreads ) { + return; + } + currentnumthreads++; + func( -1 ); + currentnumthreads--; + } //end if + else + { + ThreadLock(); + if ( currentnumthreads >= numthreads ) { + ThreadUnlock(); + return; + } //end if + //allocate new thread + thread = GetMemory( sizeof( thread_t ) ); + if ( !thread ) { + Error( "can't allocate memory for thread\n" ); + } + // + thread->threadid = currentthreadid; + + if ( pthread_create( &thread->thread, NULL, (void *)func, (void *)thread->threadid ) == -1 ) { + Error( "pthread_create failed" ); + } + + //add the thread to the end of the list + thread->next = NULL; + if ( lastthread ) { + lastthread->next = thread; + } else { firstthread = thread;} + lastthread = thread; + // +#ifdef THREAD_DEBUG + qprintf( "added thread with id %d\n", thread->threadid ); +#endif //THREAD_DEBUG + // + currentnumthreads++; + currentthreadid++; + // + ThreadUnlock(); + } //end else +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread( int threadid ) { + thread_t *thread, *last; + + //if a single thread + if ( threadid == -1 ) { + return; + } + // + ThreadLock(); + last = NULL; + for ( thread = firstthread; thread; thread = thread->next ) + { + if ( thread->threadid == threadid ) { + if ( last ) { + last->next = thread->next; + } else { firstthread = thread->next;} + if ( !thread->next ) { + lastthread = last; + } + // + FreeMemory( thread ); + currentnumthreads--; +#ifdef THREAD_DEBUG + qprintf( "removed thread with id %d\n", threadid ); +#endif //THREAD_DEBUG + break; + } //end if + last = thread; + } //end if + if ( !thread ) { + Error( "couldn't find thread with id %d", threadid ); + } + ThreadUnlock(); +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished( void ) { + pthread_t *thread; + void *pthread_return; + + ThreadLock(); + while ( firstthread ) + { + thread = &firstthread->thread; + ThreadUnlock(); + + if ( pthread_join( *thread, &pthread_return ) == -1 ) { + Error( "pthread_join failed" ); + } + + ThreadLock(); + } //end while + ThreadUnlock(); +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads( void ) { + return currentnumthreads; +} //end of the function GetNumThreads + +#endif //LINUX + + +//=================================================================== +// +// IRIX +// +//=================================================================== + +#ifdef _MIPS_ISA + +#define USED + +#include +#include +#include +#include + +typedef struct thread_s +{ + int threadid; + int id; + struct thread_s *next; +} thread_t; + +thread_t *firstthread; +thread_t *lastthread; +int currentnumthreads; +int currentthreadid; + +int numthreads = 1; +static int enter; +static int numwaitingthreads = 0; + +abilock_t lck; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault( void ) { + if ( numthreads == -1 ) { + numthreads = prctl( PR_MAXPPROCS ); + } + printf( "%i threads\n", numthreads ); +//@@ + usconfig( CONF_INITUSERS, numthreads ); +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock( void ) { + spin_lock( &lck ); +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock( void ) { + release_lock( &lck ); +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock( void ) { + init_lock( &lck ); + + Log_Print( "IRIX multi-threading\n" ); + + threaded = true; + currentnumthreads = 0; + currentthreadid = 0; +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock( void ) { + threaded = false; +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )(int) ) { + int i; + int pid[MAX_THREADS]; + int start, end; + + start = I_FloatTime(); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if ( numthreads < 1 || numthreads > MAX_THREADS ) { + numthreads = 1; + } + + if ( pacifier ) { + setbuf( stdout, NULL ); + } + + init_lock( &lck ); + + for ( i = 0 ; i < numthreads - 1 ; i++ ) + { + pid[i] = sprocsp( ( void( * ) ( void *, size_t ) )func, PR_SALL, (void *)i + , NULL, 0x100000 ); +// pid[i] = sprocsp ( (void (*)(void *, size_t))func, PR_SALL, (void *)i +// , NULL, 0x80000); + if ( pid[i] == -1 ) { + perror( "sproc" ); + Error( "sproc failed" ); + } + } + + func( i ); + + for ( i = 0 ; i < numthreads - 1 ; i++ ) + wait( NULL ); + + threaded = false; + + end = I_FloatTime(); + if ( pacifier ) { + printf( " (%i)\n", end - start ); + } +} //end of the function RunThreadsOn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddThread( void ( *func )(int) ) { + thread_t *thread; + + if ( numthreads == 1 ) { + if ( currentnumthreads >= numthreads ) { + return; + } + currentnumthreads++; + func( -1 ); + currentnumthreads--; + } //end if + else + { + ThreadLock(); + if ( currentnumthreads >= numthreads ) { + ThreadUnlock(); + return; + } //end if + //allocate new thread + thread = GetMemory( sizeof( thread_t ) ); + if ( !thread ) { + Error( "can't allocate memory for thread\n" ); + } + // + thread->threadid = currentthreadid; + + thread->id = sprocsp( ( void( * ) ( void *, size_t ) )func, PR_SALL, (void *)thread->threadid, NULL, 0x100000 ); + if ( thread->id == -1 ) { + perror( "sproc" ); + Error( "sproc failed" ); + } + + //add the thread to the end of the list + thread->next = NULL; + if ( lastthread ) { + lastthread->next = thread; + } else { firstthread = thread;} + lastthread = thread; + // +#ifdef THREAD_DEBUG + qprintf( "added thread with id %d\n", thread->threadid ); +#endif //THREAD_DEBUG + // + currentnumthreads++; + currentthreadid++; + // + ThreadUnlock(); + } //end else +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread( int threadid ) { + thread_t *thread, *last; + + //if a single thread + if ( threadid == -1 ) { + return; + } + // + ThreadLock(); + last = NULL; + for ( thread = firstthread; thread; thread = thread->next ) + { + if ( thread->threadid == threadid ) { + if ( last ) { + last->next = thread->next; + } else { firstthread = thread->next;} + if ( !thread->next ) { + lastthread = last; + } + // + FreeMemory( thread ); + currentnumthreads--; +#ifdef THREAD_DEBUG + qprintf( "removed thread with id %d\n", threadid ); +#endif //THREAD_DEBUG + break; + } //end if + last = thread; + } //end if + if ( !thread ) { + Error( "couldn't find thread with id %d", threadid ); + } + ThreadUnlock(); +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished( void ) { + ThreadLock(); + while ( firstthread ) + { + ThreadUnlock(); + + //wait (NULL); + + ThreadLock(); + } //end while + ThreadUnlock(); +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads( void ) { + return currentnumthreads; +} //end of the function GetNumThreads + +#endif //_MIPS_ISA + + +//======================================================================= +// +// SINGLE THREAD +// +//======================================================================= + +#ifndef USED + +int numthreads = 1; +int currentnumthreads = 0; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetDefault( void ) { + numthreads = 1; +} //end of the function ThreadSetDefault +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadLock( void ) { +} //end of the function ThreadLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadUnlock( void ) { +} //end of the function ThreadUnlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupLock( void ) { + Log_Print( "no multi-threading\n" ); +} //end of the function ThreadInitLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownLock( void ) { +} //end of the function ThreadShutdownLock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSetupSemaphore( void ) { +} //end of the function ThreadSetupSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadShutdownSemaphore( void ) { +} //end of the function ThreadShutdownSemaphore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreWait( void ) { +} //end of the function ThreadSemaphoreWait +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ThreadSemaphoreIncrease( int count ) { +} //end of the function ThreadSemaphoreIncrease +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )(int) ) { + int start, end; + + Log_Print( "no multi-threading\n" ); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + start = I_FloatTime(); +#ifdef NeXT + if ( pacifier ) { + setbuf( stdout, NULL ); + } +#endif + func( 0 ); + + end = I_FloatTime(); + if ( pacifier ) { + printf( " (%i)\n", end - start ); + } +} //end of the function RunThreadsOn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddThread( void ( *func )(int) ) { + if ( currentnumthreads >= numthreads ) { + return; + } + currentnumthreads++; + func( -1 ); + currentnumthreads--; +} //end of the function AddThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveThread( int threadid ) { +} //end of the function RemoveThread +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WaitForAllThreadsFinished( void ) { +} //end of the function WaitForAllThreadsFinished +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GetNumThreads( void ) { + return currentnumthreads; +} //end of the function GetNumThreads + +#endif //USED diff --git a/src/bspc/l_threads.h b/src/bspc/l_threads.h new file mode 100644 index 0000000..e4891e5 --- /dev/null +++ b/src/bspc/l_threads.h @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +extern int numthreads; + +void ThreadSetDefault( void ); +int GetThreadWork( void ); +void RunThreadsOnIndividual( int workcnt, qboolean showpacifier, void ( *func )( int ) ); +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )( int ) ); + +//mutex +void ThreadSetupLock( void ); +void ThreadShutdownLock( void ); +void ThreadLock( void ); +void ThreadUnlock( void ); +//semaphore +void ThreadSetupSemaphore( void ); +void ThreadShutdownSemaphore( void ); +void ThreadSemaphoreWait( void ); +void ThreadSemaphoreIncrease( int count ); +//add/remove threads +void AddThread( void ( *func )( int ) ); +void RemoveThread( int threadid ); +void WaitForAllThreadsFinished( void ); +int GetNumThreads( void ); + diff --git a/src/bspc/l_utils.c b/src/bspc/l_utils.c new file mode 100644 index 0000000..99c296e --- /dev/null +++ b/src/bspc/l_utils.c @@ -0,0 +1,262 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_utils.c +// Function: several utils +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-31 +// Tab Size: 3 +//=========================================================================== + +//#ifndef BOTLIB +//#define BOTLIB +//#endif //BOTLIB + +#ifdef BOTLIB +#include "q_shared.h" +#include "qfiles.h" +#include "botlib.h" +#include "l_log.h" +#include "l_libvar.h" +#include "l_memory.h" +//#include "l_utils.h" +#include "be_interface.h" +#else //BOTLIB +#include "qbsp.h" +#include "l_mem.h" +#endif //BOTLIB + +#ifdef BOTLIB +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void Vector2Angles( vec3_t value1, vec3_t angles ) { + float forward; + float yaw, pitch; + + if ( value1[1] == 0 && value1[0] == 0 ) { + yaw = 0; + if ( value1[2] > 0 ) { + pitch = 90; + } else { pitch = 270;} + } //end if + 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; + } + } //end else + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} //end of the function Vector2Angles +#endif //BOTLIB +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ConvertPath( char *path ) { + while ( *path ) + { + if ( *path == '/' || *path == '\\' ) { + *path = PATHSEPERATOR_CHAR; + } + path++; + } //end while +} //end of the function ConvertPath +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AppendPathSeperator( char *path, int length ) { + int pathlen = strlen( path ); + + if ( strlen( path ) && length - pathlen > 1 && path[pathlen - 1] != '/' && path[pathlen - 1] != '\\' ) { + path[pathlen] = PATHSEPERATOR_CHAR; + path[pathlen + 1] = '\0'; + } //end if +} //end of the function AppenPathSeperator +//=========================================================================== +// returns pointer to file handle +// sets offset to and length of 'filename' in the pak file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean FindFileInPak( char *pakfile, char *filename, foundfile_t *file ) { + FILE *fp; + dpackheader_t packheader; + dpackfile_t *packfiles; + int numdirs, i; + char path[MAX_PATH]; + + //open the pak file + fp = fopen( pakfile, "rb" ); + if ( !fp ) { + return false; + } //end if + //read pak header, check for valid pak id and seek to the dir entries + if ( ( fread( &packheader, 1, sizeof( dpackheader_t ), fp ) != sizeof( dpackheader_t ) ) + || ( packheader.ident != IDPAKHEADER ) + || ( fseek( fp, LittleLong( packheader.dirofs ), SEEK_SET ) ) + ) { + fclose( fp ); + return false; + } //end if + //number of dir entries in the pak file + numdirs = LittleLong( packheader.dirlen ) / sizeof( dpackfile_t ); + packfiles = (dpackfile_t *) GetMemory( numdirs * sizeof( dpackfile_t ) ); + //read the dir entry + if ( fread( packfiles, sizeof( dpackfile_t ), numdirs, fp ) != numdirs ) { + fclose( fp ); + FreeMemory( packfiles ); + return false; + } //end if + fclose( fp ); + // + strcpy( path, filename ); + ConvertPath( path ); + //find the dir entry in the pak file + for ( i = 0; i < numdirs; i++ ) + { + //convert the dir entry name + ConvertPath( packfiles[i].name ); + //compare the dir entry name with the filename + if ( Q_strcasecmp( packfiles[i].name, path ) == 0 ) { + strcpy( file->filename, pakfile ); + file->offset = LittleLong( packfiles[i].filepos ); + file->length = LittleLong( packfiles[i].filelen ); + FreeMemory( packfiles ); + return true; + } //end if + } //end for + FreeMemory( packfiles ); + return false; +} //end of the function FindFileInPak +//=========================================================================== +// find a Quake2 file +// returns full path in 'filename' +// sets offset and length of the file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean FindQuakeFile2( char *basedir, char *gamedir, char *filename, foundfile_t *file ) { + int dir, i; + //NOTE: 3 is necessary (LCC bug???) + char gamedirs[3][MAX_PATH] = {"","",""}; + char filedir[MAX_PATH] = ""; + + // + if ( gamedir ) { + strncpy( gamedirs[0], gamedir, MAX_PATH ); + } + strncpy( gamedirs[1], "baseq2", MAX_PATH ); + // + //find the file in the two game directories + for ( dir = 0; dir < 2; dir++ ) + { + //check if the file is in a directory + filedir[0] = 0; + if ( basedir && strlen( basedir ) ) { + strncpy( filedir, basedir, MAX_PATH ); + AppendPathSeperator( filedir, MAX_PATH ); + } //end if + if ( strlen( gamedirs[dir] ) ) { + strncat( filedir, gamedirs[dir], MAX_PATH - strlen( filedir ) ); + AppendPathSeperator( filedir, MAX_PATH ); + } //end if + strncat( filedir, filename, MAX_PATH - strlen( filedir ) ); + ConvertPath( filedir ); + Log_Write( "accessing %s", filedir ); + if ( !access( filedir, 0x04 ) ) { + strcpy( file->filename, filedir ); + file->length = 0; + file->offset = 0; + return true; + } //end if + //check if the file is in a pak?.pak + for ( i = 0; i < 10; i++ ) + { + filedir[0] = 0; + if ( basedir && strlen( basedir ) ) { + strncpy( filedir, basedir, MAX_PATH ); + AppendPathSeperator( filedir, MAX_PATH ); + } //end if + if ( strlen( gamedirs[dir] ) ) { + strncat( filedir, gamedirs[dir], MAX_PATH - strlen( filedir ) ); + AppendPathSeperator( filedir, MAX_PATH ); + } //end if + sprintf( &filedir[strlen( filedir )], "pak%d.pak\0", i ); + if ( !access( filedir, 0x04 ) ) { + Log_Write( "searching %s in %s", filename, filedir ); + if ( FindFileInPak( filedir, filename, file ) ) { + return true; + } + } //end if + } //end for + } //end for + file->offset = 0; + file->length = 0; + return false; +} //end of the function FindQuakeFile2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef BOTLIB +qboolean FindQuakeFile( char *filename, foundfile_t *file ) { + return FindQuakeFile2( LibVarGetString( "basedir" ), + LibVarGetString( "gamedir" ), filename, file ); +} //end of the function FindQuakeFile +#else //BOTLIB +qboolean FindQuakeFile( char *basedir, char *gamedir, char *filename, foundfile_t *file ) { + return FindQuakeFile2( basedir, gamedir, filename, file ); +} //end of the function FindQuakeFile +#endif //BOTLIB diff --git a/src/bspc/l_utils.h b/src/bspc/l_utils.h new file mode 100644 index 0000000..5c26901 --- /dev/null +++ b/src/bspc/l_utils.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_utils.h +// Function: several utils +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-31 +// Tab Size: 3 +//=========================================================================== + +#ifndef MAX_PATH + #define MAX_PATH 64 +#endif + +#ifndef PATH_SEPERATORSTR + #if defined( WIN32 ) | defined( _WIN32 ) | defined( __NT__ ) | defined( __WINDOWS__ ) | defined( __WINDOWS_386__ ) + #define PATHSEPERATOR_STR "\\" + #else + #define PATHSEPERATOR_STR "/" + #endif +#endif +#ifndef PATH_SEPERATORCHAR + #if defined( WIN32 ) | defined( _WIN32 ) | defined( __NT__ ) | defined( __WINDOWS__ ) | defined( __WINDOWS_386__ ) + #define PATHSEPERATOR_CHAR '\\' + #else + #define PATHSEPERATOR_CHAR '/' + #endif +#endif + +//random in the range [0, 1] +#define random() ( ( rand() & 0x7fff ) / ( (float)0x7fff ) ) +//random in the range [-1, 1] +#define crandom() ( 2.0 * ( random() - 0.5 ) ) +//min and max +#define Maximum( x,y ) ( x > y ? x : y ) +#define Minimum( x,y ) ( x < y ? x : y ) +//absolute value +#define FloatAbs( x ) ( *(float *) &( ( *(int *) &( x ) ) & 0x7FFFFFFF ) ) +#define IntAbs( x ) ( ~( x ) ) +//coordinates +#define _X 0 +#define _Y 1 +#define _Z 2 + +typedef struct foundfile_s +{ + int offset; + int length; + char filename[MAX_PATH]; //screw LCC, array must be at end of struct +} foundfile_t; + +void Vector2Angles( vec3_t value1, vec3_t angles ); +//set the correct path seperators +void ConvertPath( char *path ); +//append a path seperator to the given path not exceeding the length +void AppendPathSeperator( char *path, int length ); +//find a file in a pak file +qboolean FindFileInPak( char *pakfile, char *filename, foundfile_t *file ); +//find a quake file +#ifdef BOTLIB +qboolean FindQuakeFile( char *filename, foundfile_t *file ); +#else //BOTLIB +qboolean FindQuakeFile( char *basedir, char *gamedir, char *filename, foundfile_t *file ); +#endif //BOTLIB + + + diff --git a/src/bspc/leakfile.c b/src/bspc/leakfile.c new file mode 100644 index 0000000..c93d862 --- /dev/null +++ b/src/bspc/leakfile.c @@ -0,0 +1,108 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "qbsp.h" + +/* +============================================================================== + +LEAF FILE GENERATION + +Save out name.line for qe3 to read +============================================================================== +*/ + + +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf +============= +*/ +void LeakFile( tree_t *tree ) { + vec3_t mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + if ( !tree->outside_node.occupied ) { + return; + } + + qprintf( "--- LeakFile ---\n" ); + + // + // write the points to the file + // + sprintf( filename, "%s.lin", source ); + qprintf( "%s\n", filename ); + linefile = fopen( filename, "w" ); + if ( !linefile ) { + Error( "Couldn't open %s\n", filename ); + } + + count = 0; + node = &tree->outside_node; + while ( node->occupied > 1 ) + { + int next; + portal_t *p, *nextportal; + node_t *nextnode; + int s; + + // find the best portal exit + next = node->occupied; + for ( p = node->portals ; p ; p = p->next[!s] ) + { + s = ( p->nodes[0] == node ); + if ( p->nodes[s]->occupied + && p->nodes[s]->occupied < next ) { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter( nextportal->winding, mid ); + fprintf( linefile, "%f %f %f\n", mid[0], mid[1], mid[2] ); + count++; + } + // add the occupant center + GetVectorForKey( node->occupant, "origin", mid ); + + fprintf( linefile, "%f %f %f\n", mid[0], mid[1], mid[2] ); + qprintf( "%5i point linefile\n", count + 1 ); + + fclose( linefile ); +} + diff --git a/src/bspc/map.c b/src/bspc/map.c new file mode 100644 index 0000000..a8702ee --- /dev/null +++ b/src/bspc/map.c @@ -0,0 +1,1351 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: map.c +// Function: map loading and writing +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-03 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "l_bsp_q1.h" +#include "l_bsp_q2.h" +#include "l_bsp_q3.h" +#include "l_bsp_sin.h" +#include "l_mem.h" +#include "..\botlib\aasfile.h" //aas_bbox_t +#include "aas_store.h" //AAS_MAX_BBOXES +#include "aas_cfg.h" + +#define Sign( x ) ( x < 0 ? 1 : 0 ) + +int nummapbrushes; +mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; + +int nummapbrushsides; +side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; +brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; + +int nummapplanes; +plane_t mapplanes[MAX_MAPFILE_PLANES]; +int mapplaneusers[MAX_MAPFILE_PLANES]; + +#define PLANE_HASHES 1024 +plane_t *planehash[PLANE_HASHES]; +vec3_t map_mins, map_maxs; + +#ifdef SIN +textureref_t side_newrefs[MAX_MAPFILE_BRUSHSIDES]; +#endif + +map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; +int map_numtexinfo; +int loadedmaptype; //loaded map type + +// undefine to make plane finding use linear sort +#define USE_HASHING + +int c_boxbevels; +int c_edgebevels; +int c_areaportals; +int c_clipbrushes; +int c_squattbrushes; +int c_writtenbrushes; + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ + + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int PlaneSignBits( vec3_t normal ) { + int i, signbits; + + signbits = 0; + for ( i = 2; i >= 0; i-- ) + { + signbits = ( signbits << 1 ) + Sign( normal[i] ); + } //end for + return signbits; +} //end of the function PlaneSignBits +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int PlaneTypeForNormal( vec3_t normal ) { + vec_t ax, ay, az; + +// NOTE: should these have an epsilon around 1.0? + if ( normal[0] == 1.0 || normal[0] == -1.0 ) { + return PLANE_X; + } + if ( normal[1] == 1.0 || normal[1] == -1.0 ) { + return PLANE_Y; + } + if ( normal[2] == 1.0 || normal[2] == -1.0 ) { + return PLANE_Z; + } + + ax = fabs( normal[0] ); + ay = fabs( normal[1] ); + az = fabs( normal[2] ); + + if ( ax >= ay && ax >= az ) { + return PLANE_ANYX; + } + if ( ay >= ax && ay >= az ) { + return PLANE_ANYY; + } + return PLANE_ANYZ; +} //end of the function PlaneTypeForNormal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//ME NOTE: changed from 0.00001 +#define NORMAL_EPSILON 0.0001 +//ME NOTE: changed from 0.01 +#define DIST_EPSILON 0.02 +qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ) { +#if 1 + if ( + fabs( p->normal[0] - normal[0] ) < NORMAL_EPSILON + && fabs( p->normal[1] - normal[1] ) < NORMAL_EPSILON + && fabs( p->normal[2] - normal[2] ) < NORMAL_EPSILON + && fabs( p->dist - dist ) < DIST_EPSILON ) { + return true; + } +#else + if ( p->normal[0] == normal[0] + && p->normal[1] == normal[1] + && p->normal[2] == normal[2] + && p->dist == dist ) { + return true; + } +#endif + return false; +} //end of the function PlaneEqual +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddPlaneToHash( plane_t *p ) { + int hash; + + hash = (int)fabs( p->dist ) / 8; + hash &= ( PLANE_HASHES - 1 ); + + p->hash_chain = planehash[hash]; + planehash[hash] = p; +} //end of the function AddPlaneToHash +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CreateNewFloatPlane( vec3_t normal, vec_t dist ) { + plane_t *p, temp; + + if ( VectorLength( normal ) < 0.5 ) { + Error( "FloatPlane: bad normal" ); + } + // create a new plane + if ( nummapplanes + 2 > MAX_MAPFILE_PLANES ) { + Error( "MAX_MAPFILE_PLANES" ); + } + + p = &mapplanes[nummapplanes]; + VectorCopy( normal, p->normal ); + p->dist = dist; + p->type = ( p + 1 )->type = PlaneTypeForNormal( p->normal ); + p->signbits = PlaneSignBits( p->normal ); + + VectorSubtract( vec3_origin, normal, ( p + 1 )->normal ); + ( p + 1 )->dist = -dist; + ( p + 1 )->signbits = PlaneSignBits( ( p + 1 )->normal ); + + nummapplanes += 2; + + // allways put axial planes facing positive first + if ( p->type < 3 ) { + if ( p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0 ) { + // flip order + temp = *p; + *p = *( p + 1 ); + *( p + 1 ) = temp; + + AddPlaneToHash( p ); + AddPlaneToHash( p + 1 ); + return nummapplanes - 1; + } + } + + AddPlaneToHash( p ); + AddPlaneToHash( p + 1 ); + return nummapplanes - 2; +} //end of the function CreateNewFloatPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SnapVector( vec3_t normal ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( fabs( normal[i] - 1 ) < NORMAL_EPSILON ) { + VectorClear( normal ); + normal[i] = 1; + break; + } + if ( fabs( normal[i] - -1 ) < NORMAL_EPSILON ) { + VectorClear( normal ); + normal[i] = -1; + break; + } + } +} //end of the function SnapVector +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SnapPlane( vec3_t normal, vec_t *dist ) { + SnapVector( normal ); + + if ( fabs( *dist - Q_rint( *dist ) ) < DIST_EPSILON ) { + *dist = Q_rint( *dist ); + } +} //end of the function SnapPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifndef USE_HASHING +int FindFloatPlane( vec3_t normal, vec_t dist ) { + int i; + plane_t *p; + + SnapPlane( normal, &dist ); + for ( i = 0, p = mapplanes; i < nummapplanes; i++, p++ ) + { + if ( PlaneEqual( p, normal, dist ) ) { + mapplaneusers[i]++; + return i; + } //end if + } //end for + i = CreateNewFloatPlane( normal, dist ); + mapplaneusers[i]++; + return i; +} //end of the function FindFloatPlane +#else +int FindFloatPlane( vec3_t normal, vec_t dist ) { + int i; + plane_t *p; + int hash, h; + + SnapPlane( normal, &dist ); + hash = (int)fabs( dist ) / 8; + hash &= ( PLANE_HASHES - 1 ); + + // search the border bins as well + for ( i = -1; i <= 1; i++ ) + { + h = ( hash + i ) & ( PLANE_HASHES - 1 ); + for ( p = planehash[h]; p; p = p->hash_chain ) + { + if ( PlaneEqual( p, normal, dist ) ) { + mapplaneusers[p - mapplanes]++; + return p - mapplanes; + } //end if + } //end for + } //end for + i = CreateNewFloatPlane( normal, dist ); + mapplaneusers[i]++; + return i; +} //end of the function FindFloatPlane +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int PlaneFromPoints( int *p0, int *p1, int *p2 ) { + vec3_t t1, t2, normal; + vec_t dist; + + VectorSubtract( p0, p1, t1 ); + VectorSubtract( p2, p1, t2 ); + CrossProduct( t1, t2, normal ); + VectorNormalize( normal ); + + dist = DotProduct( p0, normal ); + + return FindFloatPlane( normal, dist ); +} //end of the function PlaneFromPoints +//=========================================================================== +// Adds any additional planes necessary to allow the brush to be expanded +// against axial bounding boxes +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddBrushBevels( mapbrush_t *b ) { + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + brush_texture_t tdtemp; +#ifdef SIN + textureref_t trtemp; +#endif + side_t *s, *s2; + vec3_t normal; + float dist; + winding_t *w, *w2; + vec3_t vec, vec2; + float d; + + // + // add the axial planes + // + order = 0; + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) + { + // see if the plane is allready present + for ( i = 0, s = b->original_sides ; i < b->numsides ; i++,s++ ) + { + if ( mapplanes[s->planenum].normal[axis] == dir ) { + break; + } + } + + if ( i == b->numsides ) { // add a new side + if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) { + Error( "MAX_MAP_BRUSHSIDES" ); + } + nummapbrushsides++; + b->numsides++; + VectorClear( normal ); + normal[axis] = dir; + if ( dir == 1 ) { + dist = b->maxs[axis]; + } else { + dist = -b->mins[axis]; + } + s->planenum = FindFloatPlane( normal, dist ); + s->texinfo = b->original_sides[0].texinfo; +#ifdef SIN + s->lightinfo = b->original_sides[0].lightinfo; +#endif + s->contents = b->original_sides[0].contents; + s->flags |= SFL_BEVEL; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if ( i != order ) { + sidetemp = b->original_sides[order]; + b->original_sides[order] = b->original_sides[i]; + b->original_sides[i] = sidetemp; + + j = b->original_sides - brushsides; + tdtemp = side_brushtextures[j + order]; + side_brushtextures[j + order] = side_brushtextures[j + i]; + side_brushtextures[j + i] = tdtemp; + +#ifdef SIN + trtemp = side_newrefs[j + order]; + side_newrefs[j + order] = side_newrefs[j + i]; + side_newrefs[j + i] = trtemp; +#endif + } + } + } + + // + // add the edge bevels + // + if ( b->numsides == 6 ) { + return; // pure axial + + } + // test the non-axial plane edges + for ( i = 6 ; i < b->numsides ; i++ ) + { + s = b->original_sides + i; + w = s->winding; + if ( !w ) { + continue; + } + for ( j = 0 ; j < w->numpoints ; j++ ) + { + k = ( j + 1 ) % w->numpoints; + VectorSubtract( w->p[j], w->p[k], vec ); + if ( VectorNormalize( vec ) < 0.5 ) { + continue; + } + SnapVector( vec ); + for ( k = 0 ; k < 3 ; k++ ) + if ( vec[k] == -1 || vec[k] == 1 ) { + break; + } // axial + if ( k != 3 ) { + continue; // only test non-axial edges + + } + // try the six possible slanted axials from this edge + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + // construct a plane + VectorClear( vec2 ); + vec2[axis] = dir; + CrossProduct( vec, vec2, normal ); + if ( VectorNormalize( normal ) < 0.5 ) { + continue; + } + dist = DotProduct( w->p[j], normal ); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for ( k = 0 ; k < b->numsides ; k++ ) + { + // if this plane has allready been used, skip it + if ( PlaneEqual( &mapplanes[b->original_sides[k].planenum] + , normal, dist ) ) { + break; + } + + w2 = b->original_sides[k].winding; + if ( !w2 ) { + continue; + } + for ( l = 0 ; l < w2->numpoints ; l++ ) + { + d = DotProduct( w2->p[l], normal ) - dist; + if ( d > 0.1 ) { + break; // point in front + } + } + if ( l != w2->numpoints ) { + break; + } + } + + if ( k != b->numsides ) { + continue; // wasn't part of the outer hull + } + // add this plane + if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) { + Error( "MAX_MAP_BRUSHSIDES" ); + } + nummapbrushsides++; + s2 = &b->original_sides[b->numsides]; + s2->planenum = FindFloatPlane( normal, dist ); + s2->texinfo = b->original_sides[0].texinfo; +#ifdef SIN + s2->lightinfo = b->original_sides[0].lightinfo; +#endif + s2->contents = b->original_sides[0].contents; + s2->flags |= SFL_BEVEL; + c_edgebevels++; + b->numsides++; + } + } + } + } +} //end of the function AddBrushBevels +//=========================================================================== +// creates windigs for sides and mins / maxs for the brush +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean MakeBrushWindings( mapbrush_t *ob ) { + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + ClearBounds( ob->mins, ob->maxs ); + + for ( i = 0; i < ob->numsides; i++ ) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane( plane->normal, plane->dist ); + for ( j = 0; j < ob->numsides && w; j++ ) + { + if ( i == j ) { + continue; + } + if ( ob->original_sides[j].flags & SFL_BEVEL ) { + continue; + } + plane = &mapplanes[ob->original_sides[j].planenum ^ 1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON); + } + + side = &ob->original_sides[i]; + side->winding = w; + if ( w ) { + side->flags |= SFL_VISIBLE; + for ( j = 0; j < w->numpoints; j++ ) + AddPointToBounds( w->p[j], ob->mins, ob->maxs ); + } + } + + for ( i = 0; i < 3; i++ ) + { + //IDBUG: all the indexes into the mins and maxs were zero (not using i) + if ( ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS ) { + Log_Print( "entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum ); + ob->numsides = 0; //remove the brush + break; + } //end if + if ( ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS ) { + Log_Print( "entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum ); + ob->numsides = 0; //remove the brush + break; + } //end if + } //end for + return true; +} //end of the function MakeBrushWindings +//=========================================================================== +// FIXME: currently doesn't mark all bevels +// NOTE: when one brush bevel is found the remaining sides of the brush +// are bevels as well (when the brush isn't expanded for AAS :)) +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MarkBrushBevels( mapbrush_t *brush ) { + int i; + int we; + side_t *s; + + //check all the sides of the brush + for ( i = 0; i < brush->numsides; i++ ) + { + s = brush->original_sides + i; + //if the side has no winding + if ( !s->winding ) { + Log_Write( "MarkBrushBevels: brush %d no winding", brush->brushnum ); + s->flags |= SFL_BEVEL; + } //end if + //if the winding is tiny + else if ( WindingIsTiny( s->winding ) ) { + s->flags |= SFL_BEVEL; + Log_Write( "MarkBrushBevels: brush %d tiny winding", brush->brushnum ); + } //end else if + //if the winding has errors + else + { + we = WindingError( s->winding ); + if ( we == WE_NOTENOUGHPOINTS + || we == WE_SMALLAREA + || we == WE_POINTBOGUSRANGE +// || we == WE_NONCONVEX + ) { + Log_Write( "MarkBrushBevels: brush %d %s", brush->brushnum, WindingErrorString() ); + s->flags |= SFL_BEVEL; + } //end else if + } //end else + if ( s->flags & SFL_BEVEL ) { + s->flags &= ~SFL_VISIBLE; + //if the side has a valid plane + if ( s->planenum > 0 && s->planenum < nummapplanes ) { + //if it is an axial plane + if ( mapplanes[s->planenum].type < 3 ) { + c_boxbevels++; + } else { c_edgebevels++;} + } //end if + } //end if + } //end for +} //end of the function MarkBrushBevels +//=========================================================================== +// returns true if the map brush already exists +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BrushExists( mapbrush_t *brush ) { + int i, s1, s2; + side_t *side1, *side2; + mapbrush_t *brush1, *brush2; + + for ( i = 0; i < nummapbrushes; i++ ) + { + brush1 = brush; + brush2 = &mapbrushes[i]; + //compare the brushes + if ( brush1->entitynum != brush2->entitynum ) { + continue; + } + //if (brush1->contents != brush2->contents) continue; + if ( brush1->numsides != brush2->numsides ) { + continue; + } + for ( s1 = 0; s1 < brush1->numsides; s1++ ) + { + side1 = brush1->original_sides + s1; + // + for ( s2 = 0; s2 < brush2->numsides; s2++ ) + { + side2 = brush2->original_sides + s2; + // + if ( ( side1->planenum & ~1 ) == ( side2->planenum & ~1 ) +// && side1->texinfo == side2->texinfo +// && side1->contents == side2->contents +// && side1->surf == side2->surf + ) { + break; + } + } //end if + if ( s2 >= brush2->numsides ) { + break; + } + } //end for + if ( s1 >= brush1->numsides ) { + return true; + } + } //end for + return false; +} //end of the function BrushExists +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteMapBrush( FILE *fp, mapbrush_t *brush, vec3_t origin ) { + int sn, rotate, shift[2], sv, tv, planenum, p1, i, j; + float scale[2], originshift[2], ang1, ang2, newdist; + vec3_t vecs[2], axis[2]; + map_texinfo_t *ti; + winding_t *w; + side_t *s; + plane_t *plane; + + if ( noliquids ) { + if ( brush->contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return true; + } //end if + } //end if + //if the brush has no contents + if ( !brush->contents ) { + return true; + } + //print the leading { + if ( fprintf( fp, " { //brush %d\n", brush->brushnum ) < 0 ) { + return false; + } + //write brush sides + for ( sn = 0; sn < brush->numsides; sn++ ) + { + s = brush->original_sides + sn; + //don't write out bevels + if ( !( s->flags & SFL_BEVEL ) ) { + //if the entity has an origin set + if ( origin[0] || origin[1] || origin[2] ) { + newdist = mapplanes[s->planenum].dist + + DotProduct( mapplanes[s->planenum].normal, origin ); + planenum = FindFloatPlane( mapplanes[s->planenum].normal, newdist ); + } //end if + else + { + planenum = s->planenum; + } //end else + //always take the first plane, then flip the points if necesary + plane = &mapplanes[planenum & ~1]; + w = BaseWindingForPlane( plane->normal, plane->dist ); + // + for ( i = 0; i < 3; i++ ) + { + for ( j = 0; j < 3; j++ ) + { + if ( fabs( w->p[i][j] ) < 0.2 ) { + w->p[i][j] = 0; + } else if ( fabs( (int)w->p[i][j] - w->p[i][j] ) < 0.3 ) { + w->p[i][j] = (int) w->p[i][j]; + } + //w->p[i][j] = (int) (w->p[i][j] + 0.2); + } //end for + } //end for + //three non-colinear points to define the plane + if ( planenum & 1 ) { + p1 = 1; + } else { p1 = 0;} + if ( fprintf( fp," ( %5i %5i %5i ) ", (int)w->p[p1][0], (int)w->p[p1][1], (int)w->p[p1][2] ) < 0 ) { + return false; + } + if ( fprintf( fp,"( %5i %5i %5i ) ", (int)w->p[!p1][0], (int)w->p[!p1][1], (int)w->p[!p1][2] ) < 0 ) { + return false; + } + if ( fprintf( fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] ) < 0 ) { + return false; + } + //free the winding + FreeWinding( w ); + // + if ( s->texinfo == TEXINFO_NODE ) { + if ( brush->contents & CONTENTS_PLAYERCLIP ) { + //player clip + if ( loadedmaptype == MAPTYPE_SIN ) { + if ( fprintf( fp, "generic/misc/clip 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end if + else if ( loadedmaptype == MAPTYPE_QUAKE2 ) { //FIXME: don't always use e1u1 + if ( fprintf( fp, "e1u1/clip 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end else + else if ( loadedmaptype == MAPTYPE_QUAKE3 ) { + if ( fprintf( fp, "e1u1/clip 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end else if + else + { + if ( fprintf( fp, "clip 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end else + } //end if + else if ( brush->contents == CONTENTS_MONSTERCLIP ) { + //monster clip + if ( loadedmaptype == MAPTYPE_SIN ) { + if ( fprintf( fp, "generic/misc/monster 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end if + else if ( loadedmaptype == MAPTYPE_QUAKE2 ) { + if ( fprintf( fp, "e1u1/clip_mon 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end else + else + { + if ( fprintf( fp, "clip 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end else + } //end else + else + { + if ( fprintf( fp, "clip 0 0 0 1 1" ) < 0 ) { + return false; + } + Log_Write( "brush->contents = %d\n", brush->contents ); + } //end else + } //end if + else if ( loadedmaptype == MAPTYPE_SIN && s->texinfo == 0 ) { + if ( brush->contents & CONTENTS_DUMMYFENCE ) { + if ( fprintf( fp, "generic/misc/fence 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end if + else if ( brush->contents & CONTENTS_MIST ) { + if ( fprintf( fp, "generic/misc/volumetric_base 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end if + else //unknown so far + { + if ( fprintf( fp, "generic/misc/red 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end else + } //end if + else if ( loadedmaptype == MAPTYPE_QUAKE3 ) { + //always use the same texture + if ( fprintf( fp, "e2u3/floor1_2 0 0 0 1 1 1 0 0" ) < 0 ) { + return false; + } + } //end else if + else + { + //* + ti = &map_texinfo[s->texinfo]; + //the scaling of the texture + scale[0] = 1 / VectorNormalize2( ti->vecs[0], vecs[0] ); + scale[1] = 1 / VectorNormalize2( ti->vecs[1], vecs[1] ); + // + TextureAxisFromPlane( plane, axis[0], axis[1] ); + //calculate texture shift done by entity origin + originshift[0] = DotProduct( origin, axis[0] ); + originshift[1] = DotProduct( origin, axis[1] ); + //the texture shift without origin shift + shift[0] = ti->vecs[0][3] - originshift[0]; + shift[1] = ti->vecs[1][3] - originshift[1]; + // + if ( axis[0][0] ) { + sv = 0; + } else if ( axis[0][1] ) { + sv = 1; + } else { sv = 2;} + if ( axis[1][0] ) { + tv = 0; + } else if ( axis[1][1] ) { + tv = 1; + } else { tv = 2;} + //calculate rotation of texture + if ( vecs[0][tv] == 0 ) { + ang1 = vecs[0][sv] > 0 ? 90.0 : -90.0; + } else { ang1 = atan2( vecs[0][sv], vecs[0][tv] ) * 180 / Q_PI;} + if ( ang1 < 0 ) { + ang1 += 360; + } + if ( ang1 >= 360 ) { + ang1 -= 360; + } + if ( axis[0][tv] == 0 ) { + ang2 = axis[0][sv] > 0 ? 90.0 : -90.0; + } else { ang2 = atan2( axis[0][sv], axis[0][tv] ) * 180 / Q_PI;} + if ( ang2 < 0 ) { + ang2 += 360; + } + if ( ang2 >= 360 ) { + ang2 -= 360; + } + rotate = ang2 - ang1; + if ( rotate < 0 ) { + rotate += 360; + } + if ( rotate >= 360 ) { + rotate -= 360; + } + //write the texture info + if ( fprintf( fp, "%s %d %d %d", ti->texture, shift[0], shift[1], rotate ) < 0 ) { + return false; + } + if ( fabs( scale[0] - ( (int) scale[0] ) ) < 0.001 ) { + if ( fprintf( fp, " %d", (int) scale[0] ) < 0 ) { + return false; + } + } //end if + else + { + if ( fprintf( fp, " %4f", scale[0] ) < 0 ) { + return false; + } + } //end if + if ( fabs( scale[1] - ( (int) scale[1] ) ) < 0.001 ) { + if ( fprintf( fp, " %d", (int) scale[1] ) < 0 ) { + return false; + } + } //end if + else + { + if ( fprintf( fp, " %4f", scale[1] ) < 0 ) { + return false; + } + } //end else + //write the extra brush side info + if ( loadedmaptype == MAPTYPE_QUAKE2 ) { + if ( fprintf( fp, " %ld %ld %ld", s->contents, ti->flags, ti->value ) < 0 ) { + return false; + } + } //end if + //*/ + } //end else + if ( fprintf( fp, "\n" ) < 0 ) { + return false; + } + } //end if + } //end if + if ( fprintf( fp, " }\n" ) < 0 ) { + return false; + } + c_writtenbrushes++; + return true; +} //end of the function WriteMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteOriginBrush( FILE *fp, vec3_t origin ) { + vec3_t normal; + float dist; + int i, s; + winding_t *w; + + if ( fprintf( fp, " {\n" ) < 0 ) { + return false; + } + // + for ( i = 0; i < 3; i++ ) + { + for ( s = -1; s <= 1; s += 2 ) + { + // + VectorClear( normal ); + normal[i] = s; + dist = origin[i] * s + 16; + // + w = BaseWindingForPlane( normal, dist ); + //three non-colinear points to define the plane + if ( fprintf( fp," ( %5i %5i %5i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2] ) < 0 ) { + return false; + } + if ( fprintf( fp,"( %5i %5i %5i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2] ) < 0 ) { + return false; + } + if ( fprintf( fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] ) < 0 ) { + return false; + } + //free the winding + FreeWinding( w ); + //write origin texture: + // CONTENTS_ORIGIN = 16777216 + // SURF_NODRAW = 128 + if ( loadedmaptype == MAPTYPE_SIN ) { + if ( fprintf( fp, "generic/misc/origin 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end if + else if ( loadedmaptype == MAPTYPE_HALFLIFE ) { + if ( fprintf( fp, "origin 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end if + else + { + if ( fprintf( fp, "e1u1/origin 0 0 0 1 1" ) < 0 ) { + return false; + } + } //end else + //Quake2 extra brush side info + if ( loadedmaptype == MAPTYPE_QUAKE2 ) { + //if (fprintf(fp, " 16777216 128 0") < 0) return false; + } //end if + if ( fprintf( fp, "\n" ) < 0 ) { + return false; + } + } //end for + } //end for + if ( fprintf( fp, " }\n" ) < 0 ) { + return false; + } + c_writtenbrushes++; + return true; +} //end of the function WriteOriginBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +mapbrush_t *GetAreaPortalBrush( entity_t *mapent ) { + int portalnum, bn; + mapbrush_t *brush; + + //the area portal number + portalnum = mapent->areaportalnum; + //find the area portal brush in the world brushes + for ( bn = 0; bn < nummapbrushes && portalnum; bn++ ) + { + brush = &mapbrushes[bn]; + //must be in world entity + if ( brush->entitynum == 0 ) { + if ( brush->contents & CONTENTS_AREAPORTAL ) { + portalnum--; + } //end if + } //end if + } //end for + if ( bn < nummapbrushes ) { + return brush; + } //end if + else + { + Log_Print( "area portal %d brush not found\n", mapent->areaportalnum ); + return NULL; + } //end else +} //end of the function GetAreaPortalBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteMapFileSafe( FILE *fp ) { + char key[1024], value[1024]; + int i, bn, entitybrushes; + epair_t *ep; + mapbrush_t *brush; + entity_t *mapent; + //vec3_t vec_origin = {0, 0, 0}; + + // + if ( fprintf( fp,"//=====================================================\n" + "//\n" + "// map file created with BSPC v1.6\n" + "//\n" + "// BSPC is created by Mr Elusive\n" + "//\n" ) < 0 ) { + return false; + } + if ( loadedmaptype == MAPTYPE_SIN ) { + if ( fprintf( fp, + "// generic/misc/red is used for unknown textures\n" ) < 0 ) { + return false; + } + } //end if + if ( fprintf( fp,"//\n" + "//=====================================================\n" ) < 0 ) { + return false; + } + //write out all the entities + for ( i = 0; i < num_entities; i++ ) + { + mapent = &entities[i]; + if ( !mapent->epairs ) { + continue; + } //end if + if ( fprintf( fp, "{\n" ) < 0 ) { + return false; + } + // + if ( loadedmaptype == MAPTYPE_QUAKE3 ) { + if ( !stricmp( ValueForKey( mapent, "classname" ), "light" ) ) { + SetKeyValue( mapent, "light", "10000" ); + } //end if + } //end if + //write epairs + for ( ep = mapent->epairs; ep; ep = ep->next ) + { + strcpy( key, ep->key ); + StripTrailing( key ); + strcpy( value, ep->value ); + StripTrailing( value ); + // + if ( loadedmaptype == MAPTYPE_QUAKE2 || + loadedmaptype == MAPTYPE_SIN ) { + //don't write an origin for BSP models + if ( mapent->modelnum >= 0 && !strcmp( key, "origin" ) ) { + continue; + } + } //end if + //don't write BSP model numbers + if ( mapent->modelnum >= 0 && !strcmp( key, "model" ) && value[0] == '*' ) { + continue; + } + // + if ( fprintf( fp, " \"%s\" \"%s\"\n", key, value ) < 0 ) { + return false; + } + } //end for + // + if ( ValueForKey( mapent, "origin" ) ) { + GetVectorForKey( mapent, "origin", mapent->origin ); + } else { mapent->origin[0] = mapent->origin[1] = mapent->origin[2] = 0;} + //if this is an area portal entity + if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) { + brush = GetAreaPortalBrush( mapent ); + if ( !brush ) { + return false; + } + if ( !WriteMapBrush( fp, brush, mapent->origin ) ) { + return false; + } + } //end if + else + { + entitybrushes = false; + //write brushes + for ( bn = 0; bn < nummapbrushes; bn++ ) + { + brush = &mapbrushes[bn]; + //if the brush is part of this entity + if ( brush->entitynum == i ) { + //don't write out area portal brushes in the world + if ( !( ( brush->contents & CONTENTS_AREAPORTAL ) && brush->entitynum == 0 ) ) { + /* + if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname"))) + { + AAS_PositionFuncRotatingBrush(mapent, brush); + if (!WriteMapBrush(fp, brush, vec_origin)) return false; + } //end if + else //*/ + { + if ( !WriteMapBrush( fp, brush, mapent->origin ) ) { + return false; + } + } //end else + entitybrushes = true; + } //end if + } //end if + } //end for + //if the entity had brushes + if ( entitybrushes ) { + //if the entity has an origin set + if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) { + if ( !WriteOriginBrush( fp, mapent->origin ) ) { + return false; + } + } //end if + } //end if + } //end else + if ( fprintf( fp, "}\n" ) < 0 ) { + return false; + } + } //end for + if ( fprintf( fp, "//total of %d brushes\n", c_writtenbrushes ) < 0 ) { + return false; + } + return true; +} //end of the function WriteMapFileSafe +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WriteMapFile( char *filename ) { + FILE *fp; + double start_time; + + c_writtenbrushes = 0; + //the time started + start_time = I_FloatTime(); + // + Log_Print( "writing %s\n", filename ); + fp = fopen( filename, "wb" ); + if ( !fp ) { + Log_Print( "can't open %s\n", filename ); + return; + } //end if + if ( !WriteMapFileSafe( fp ) ) { + fclose( fp ); + Log_Print( "error writing map file %s\n", filename ); + return; + } //end if + fclose( fp ); + //display creation time + Log_Print( "written %d brushes\n", c_writtenbrushes ); + Log_Print( "map file written in %5.0f seconds\n", I_FloatTime() - start_time ); +} //end of the function WriteMapFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMapInfo( void ) { + Log_Print( "\n" ); + Log_Print( "%6i brushes\n", nummapbrushes ); + Log_Print( "%6i brush sides\n", nummapbrushsides ); +// Log_Print("%6i clipbrushes\n", c_clipbrushes); +// Log_Print("%6i total sides\n", nummapbrushsides); +// Log_Print("%6i boxbevels\n", c_boxbevels); +// Log_Print("%6i edgebevels\n", c_edgebevels); +// Log_Print("%6i entities\n", num_entities); +// Log_Print("%6i planes\n", nummapplanes); +// Log_Print("%6i areaportals\n", c_areaportals); +// Log_Print("%6i squatt brushes\n", c_squattbrushes); +// Log_Print("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], +// map_maxs[0],map_maxs[1],map_maxs[2]); +} //end of the function PrintMapInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ResetMapLoading( void ) { + int i; + epair_t *ep, *nextep; + + Q2_ResetMapLoading(); + Sin_ResetMapLoading(); + + //free all map brush side windings + for ( i = 0; i < nummapbrushsides; i++ ) + { + if ( brushsides[i].winding ) { + FreeWinding( brushsides[i].winding ); + } //end for + } //end for + + //reset regular stuff + nummapbrushes = 0; + memset( mapbrushes, 0, MAX_MAPFILE_BRUSHES * sizeof( mapbrush_t ) ); + // + nummapbrushsides = 0; + memset( brushsides, 0, MAX_MAPFILE_BRUSHSIDES * sizeof( side_t ) ); + memset( side_brushtextures, 0, MAX_MAPFILE_BRUSHSIDES * sizeof( brush_texture_t ) ); + // + nummapplanes = 0; + memset( mapplanes, 0, MAX_MAPFILE_PLANES * sizeof( plane_t ) ); + // + memset( planehash, 0, PLANE_HASHES * sizeof( plane_t * ) ); + // + memset( map_texinfo, 0, MAX_MAPFILE_TEXINFO * sizeof( map_texinfo_t ) ); + map_numtexinfo = 0; + // + VectorClear( map_mins ); + VectorClear( map_maxs ); + // + c_boxbevels = 0; + c_edgebevels = 0; + c_areaportals = 0; + c_clipbrushes = 0; + c_writtenbrushes = 0; + //clear the entities + for ( i = 0; i < num_entities; i++ ) + { + for ( ep = entities[i].epairs; ep; ep = nextep ) + { + nextep = ep->next; + FreeMemory( ep->key ); + FreeMemory( ep->value ); + FreeMemory( ep ); + } //end for + } //end for + num_entities = 0; + memset( entities, 0, MAX_MAP_ENTITIES * sizeof( entity_t ) ); +} //end of the function ResetMapLoading +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifndef Q1_BSPVERSION +#define Q1_BSPVERSION 29 +#endif +#ifndef HL_BSPVERSION +#define HL_BSPVERSION 30 +#endif + +#define Q2_BSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) //IBSP +#define Q2_BSPVERSION 38 + +#define SINGAME_BSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'R' ) //RBSP +#define SINGAME_BSPVERSION 1 + +#define SIN_BSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) //IBSP +#define SIN_BSPVERSION 41 + +typedef struct +{ + int ident; + int version; +} idheader_t; + +int LoadMapFromBSP( struct quakefile_s *qf ) { + idheader_t idheader; + + if ( ReadQuakeFile( qf, &idheader, 0, sizeof( idheader_t ) ) != sizeof( idheader_t ) ) { + return false; + } //end if + + idheader.ident = LittleLong( idheader.ident ); + idheader.version = LittleLong( idheader.version ); + //Quake3 BSP file + if ( idheader.ident == Q3_BSP_IDENT && idheader.version == Q3_BSP_VERSION ) { + ResetMapLoading(); + Q3_LoadMapFromBSP( qf ); + Q3_FreeMaxBSP(); + } //end if + //Quake2 BSP file + else if ( idheader.ident == Q2_BSPHEADER && idheader.version == Q2_BSPVERSION ) { + ResetMapLoading(); + Q2_AllocMaxBSP(); + Q2_LoadMapFromBSP( qf->filename, qf->offset, qf->length ); + Q2_FreeMaxBSP(); + } //endif + //Sin BSP file + else if ( ( idheader.ident == SIN_BSPHEADER && idheader.version == SIN_BSPVERSION ) || + //the dorks gave the same format another ident and verions + ( idheader.ident == SINGAME_BSPHEADER && idheader.version == SINGAME_BSPVERSION ) ) { + ResetMapLoading(); + Sin_AllocMaxBSP(); + Sin_LoadMapFromBSP( qf->filename, qf->offset, qf->length ); + Sin_FreeMaxBSP(); + } //end if + //the Quake1 bsp files don't have a ident only a version + else if ( idheader.ident == Q1_BSPVERSION ) { + ResetMapLoading(); + Q1_AllocMaxBSP(); + Q1_LoadMapFromBSP( qf->filename, qf->offset, qf->length ); + Q1_FreeMaxBSP(); + } //end if + //Half-Life also only uses a version number + else if ( idheader.ident == HL_BSPVERSION ) { + ResetMapLoading(); + HL_AllocMaxBSP(); + HL_LoadMapFromBSP( qf->filename, qf->offset, qf->length ); + HL_FreeMaxBSP(); + } //end if + else + { + Error( "unknown BSP format %c%c%c%c, version %d\n", + ( idheader.ident & 0xFF ), + ( ( idheader.ident >> 8 ) & 0xFF ), + ( ( idheader.ident >> 16 ) & 0xFF ), + ( ( idheader.ident >> 24 ) & 0xFF ), idheader.version ); + return false; + } //end if + // + return true; +} //end of the function LoadMapFromBSP diff --git a/src/bspc/map_q1.c b/src/bspc/map_q1.c new file mode 100644 index 0000000..740e022 --- /dev/null +++ b/src/bspc/map_q1.c @@ -0,0 +1,1205 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: map_q1.c +// Function: map loading and writing +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-03 +// Tab Size: 3 +// NOTES: the recursive splitting of the huge brush sometimes still +// creates bad brushes +//=========================================================================== + +#include "qbsp.h" +#include "l_bsp_q1.h" +#include "aas_map.h" //AAS_CreateMapBrushes + +int q1_numbrushes; +int q1_numclipbrushes; + +//#define Q1_PRINT + +//=========================================================================== +// water, slime and lava brush textures names always start with a * +// followed by the type: "slime", "lava" or otherwise water +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Q1_TextureContents( char *name ) { + if ( !Q_strcasecmp( name, "clip" ) ) { + return CONTENTS_SOLID; + } + if ( name[0] == '*' ) { + if ( !Q_strncasecmp( name + 1,"lava",4 ) ) { + return CONTENTS_LAVA; + } else if ( !Q_strncasecmp( name + 1,"slime",5 ) ) { + return CONTENTS_SLIME; + } else { return CONTENTS_WATER;} + } //end if + else if ( !Q_strncasecmp( name, "sky", 3 ) ) { + return CONTENTS_SOLID; + } else { return CONTENTS_SOLID;} +} //end of the function Q1_TextureContents +//=========================================================================== +// Generates two new brushes, leaving the original +// unchanged +// +// modified for Half-Life because there are quite a lot of tiny node leaves +// in the Half-Life bsps +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_SplitBrush( bspbrush_t *brush, int planenum, int nodenum, + bspbrush_t **front, bspbrush_t **back ) { + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for ( i = 0 ; i < brush->numsides ; i++ ) + { + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + for ( j = 0 ; j < w->numpoints ; j++ ) + { + d = DotProduct( w->p[j], plane->normal ) - plane->dist; + if ( d > 0 && d > d_front ) { + d_front = d; + } + if ( d < 0 && d < d_back ) { + d_back = d; + } + } //end for + } //end for + + if ( d_front < 0.1 ) { // PLANESIDE_EPSILON) + // only on back + *back = CopyBrush( brush ); + Log_Print( "Q1_SplitBrush: only on back\n" ); + return; + } //end if + if ( d_back > -0.1 ) { // PLANESIDE_EPSILON) + // only on front + *front = CopyBrush( brush ); + Log_Print( "Q1_SplitBrush: only on front\n" ); + return; + } //end if + + // create a new winding from the split plane + + w = BaseWindingForPlane( plane->normal, plane->dist ); + for ( i = 0; i < brush->numsides && w; i++ ) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace( &w, plane2->normal, plane2->dist, 0 ); // PLANESIDE_EPSILON); + } //end for + + if ( !w || WindingIsTiny( w ) ) { // the brush isn't really split + int side; + + Log_Print( "Q1_SplitBrush: no split winding\n" ); + side = BrushMostlyOnSide( brush, plane ); + if ( side == PSIDE_FRONT ) { + *front = CopyBrush( brush ); + } + if ( side == PSIDE_BACK ) { + *back = CopyBrush( brush ); + } + return; + } + + if ( WindingIsHuge( w ) ) { + Log_Print( "Q1_SplitBrush: WARNING huge split winding\n" ); + } //end of + + midwinding = w; + + // split it for real + + for ( i = 0; i < 2; i++ ) + { + b[i] = AllocBrush( brush->numsides + 1 ); + b[i]->original = brush->original; + } //end for + + // split all the current windings + + for ( i = 0 ; i < brush->numsides ; i++ ) + { + s = &brush->sides[i]; + w = s->winding; + if ( !w ) { + continue; + } + ClipWindingEpsilon( w, plane->normal, plane->dist, + 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1] ); + for ( j = 0 ; j < 2 ; j++ ) + { + if ( !cw[j] ) { + continue; + } +#if 0 + if ( WindingIsTiny( cw[j] ) ) { + FreeWinding( cw[j] ); + continue; + } +#endif + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; +// cs->planenum = s->planenum; +// cs->texinfo = s->texinfo; +// cs->visible = s->visible; +// cs->original = s->original; + cs->winding = cw[j]; + cs->flags &= ~SFL_TESTED; + } //end for + } //end for + + + // see if we have valid polygons on both sides + + for ( i = 0 ; i < 2 ; i++ ) + { + BoundBrush( b[i] ); + for ( j = 0 ; j < 3 ; j++ ) + { + if ( b[i]->mins[j] < -4096 || b[i]->maxs[j] > 4096 ) { + Log_Print( "Q1_SplitBrush: bogus brush after clip\n" ); + break; + } //end if + } //end for + + if ( b[i]->numsides < 3 || j < 3 ) { + FreeBrush( b[i] ); + b[i] = NULL; + Log_Print( "Q1_SplitBrush: numsides < 3\n" ); + } //end if + } //end for + + if ( !( b[0] && b[1] ) ) { + if ( !b[0] && !b[1] ) { + Log_Print( "Q1_SplitBrush: split removed brush\n" ); + } else { + Log_Print( "Q1_SplitBrush: split not on both sides\n" ); + } + if ( b[0] ) { + FreeBrush( b[0] ); + *front = CopyBrush( brush ); + } //end if + if ( b[1] ) { + FreeBrush( b[1] ); + *back = CopyBrush( brush ); + } //end if + return; + } //end if + + // add the midwinding to both sides + for ( i = 0; i < 2; i++ ) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum ^ i ^ 1; + cs->texinfo = 0; + //store the node number in the surf to find the texinfo later on + cs->surf = nodenum; + // + cs->flags &= ~SFL_VISIBLE; + cs->flags &= ~SFL_TESTED; + cs->flags &= ~SFL_TEXTURED; + if ( i == 0 ) { + cs->winding = CopyWinding( midwinding ); + } else { + cs->winding = midwinding; + } + } //end for + + + { + vec_t v1; + int i; + + for ( i = 0 ; i < 2 ; i++ ) + { + v1 = BrushVolume( b[i] ); + if ( v1 < 1 ) { + FreeBrush( b[i] ); + b[i] = NULL; + Log_Print( "Q1_SplitBrush: tiny volume after clip\n" ); + } //end if + } //end for + } //*/ + + *front = b[0]; + *back = b[1]; +} //end of the function Q1_SplitBrush +//=========================================================================== +// returns true if the tree starting at nodenum has only solid leaves +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Q1_SolidTree_r( int nodenum ) { + if ( nodenum < 0 ) { + switch ( q1_dleafs[( -nodenum ) - 1].contents ) + { + case Q1_CONTENTS_EMPTY: + { + return false; + } //end case + case Q1_CONTENTS_SOLID: +#ifdef HLCONTENTS + case Q1_CONTENTS_CLIP: +#endif HLCONTENTS + case Q1_CONTENTS_SKY: +#ifdef HLCONTENTS + case Q1_CONTENTS_TRANSLUCENT: +#endif HLCONTENTS + { + return true; + } //end case + case Q1_CONTENTS_WATER: + case Q1_CONTENTS_SLIME: + case Q1_CONTENTS_LAVA: +#ifdef HLCONTENTS + //these contents should not be found in the BSP + case Q1_CONTENTS_ORIGIN: + case Q1_CONTENTS_CURRENT_0: + case Q1_CONTENTS_CURRENT_90: + case Q1_CONTENTS_CURRENT_180: + case Q1_CONTENTS_CURRENT_270: + case Q1_CONTENTS_CURRENT_UP: + case Q1_CONTENTS_CURRENT_DOWN: +#endif HLCONTENTS + default: + { + return false; + } //end default + } //end switch + return false; + } //end if + if ( !Q1_SolidTree_r( q1_dnodes[nodenum].children[0] ) ) { + return false; + } + if ( !Q1_SolidTree_r( q1_dnodes[nodenum].children[1] ) ) { + return false; + } + return true; +} //end of the function Q1_SolidTree_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_CreateBrushes_r( bspbrush_t *brush, int nodenum ) { + int planenum; + bspbrush_t *front, *back; + q1_dleaf_t *leaf; + + //if it is a leaf + if ( nodenum < 0 ) { + leaf = &q1_dleafs[( -nodenum ) - 1]; + if ( leaf->contents != Q1_CONTENTS_EMPTY ) { +#ifdef Q1_PRINT + qprintf( "\r%5i", ++q1_numbrushes ); +#endif //Q1_PRINT + } //end if + switch ( leaf->contents ) + { + case Q1_CONTENTS_EMPTY: + { + FreeBrush( brush ); + return NULL; + } //end case + case Q1_CONTENTS_SOLID: +#ifdef HLCONTENTS + case Q1_CONTENTS_CLIP: +#endif HLCONTENTS + case Q1_CONTENTS_SKY: +#ifdef HLCONTENTS + case Q1_CONTENTS_TRANSLUCENT: +#endif HLCONTENTS + { + brush->side = CONTENTS_SOLID; + return brush; + } //end case + case Q1_CONTENTS_WATER: + { + brush->side = CONTENTS_WATER; + return brush; + } //end case + case Q1_CONTENTS_SLIME: + { + brush->side = CONTENTS_SLIME; + return brush; + } //end case + case Q1_CONTENTS_LAVA: + { + brush->side = CONTENTS_LAVA; + return brush; + } //end case +#ifdef HLCONTENTS + //these contents should not be found in the BSP + case Q1_CONTENTS_ORIGIN: + case Q1_CONTENTS_CURRENT_0: + case Q1_CONTENTS_CURRENT_90: + case Q1_CONTENTS_CURRENT_180: + case Q1_CONTENTS_CURRENT_270: + case Q1_CONTENTS_CURRENT_UP: + case Q1_CONTENTS_CURRENT_DOWN: + { + Error( "Q1_CreateBrushes_r: found contents %d in Half-Life BSP", leaf->contents ); + return NULL; + } //end case +#endif HLCONTENTS + default: + { + Error( "Q1_CreateBrushes_r: unknown contents %d in Half-Life BSP", leaf->contents ); + return NULL; + } //end default + } //end switch + return NULL; + } //end if + //if the rest of the tree is solid + /*if (Q1_SolidTree_r(nodenum)) + { + brush->side = CONTENTS_SOLID; + return brush; + } //end if*/ + // + planenum = q1_dnodes[nodenum].planenum; + planenum = FindFloatPlane( q1_dplanes[planenum].normal, q1_dplanes[planenum].dist ); + //split the brush with the node plane + Q1_SplitBrush( brush, planenum, nodenum, &front, &back ); + //free the original brush + FreeBrush( brush ); + //every node must split the brush in two + if ( !front || !back ) { + Log_Print( "Q1_CreateBrushes_r: WARNING node not splitting brush\n" ); + //return NULL; + } //end if + //create brushes recursively + if ( front ) { + front = Q1_CreateBrushes_r( front, q1_dnodes[nodenum].children[0] ); + } + if ( back ) { + back = Q1_CreateBrushes_r( back, q1_dnodes[nodenum].children[1] ); + } + //link the brushes if possible and return them + if ( front ) { + for ( brush = front; brush->next; brush = brush->next ) ; + brush->next = back; + return front; + } //end if + else + { + return back; + } //end else +} //end of the function Q1_CreateBrushes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_CreateBrushesFromBSP( int modelnum ) { + bspbrush_t *brushlist; + bspbrush_t *brush; + q1_dnode_t *headnode; + vec3_t mins, maxs; + int i; + + // + headnode = &q1_dnodes[q1_dmodels[modelnum].headnode[0]]; + //get the mins and maxs of the world + VectorCopy( headnode->mins, mins ); + VectorCopy( headnode->maxs, maxs ); + //enlarge these mins and maxs + for ( i = 0; i < 3; i++ ) + { + mins[i] -= 8; + maxs[i] += 8; + } //end for + //NOTE: have to add the BSP tree mins and maxs to the MAP mins and maxs + AddPointToBounds( mins, map_mins, map_maxs ); + AddPointToBounds( maxs, map_mins, map_maxs ); + // + if ( !modelnum ) { + Log_Print( "brush size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", + map_mins[0], map_mins[1], map_mins[2], + map_maxs[0], map_maxs[1], map_maxs[2] ); + } //end if + //create one huge brush containing the whole world + brush = BrushFromBounds( mins, maxs ); + VectorCopy( mins, brush->mins ); + VectorCopy( maxs, brush->maxs ); + // +#ifdef Q1_PRINT + qprintf( "creating Quake brushes\n" ); + qprintf( "%5d brushes", q1_numbrushes = 0 ); +#endif //Q1_PRINT + //create the brushes + brushlist = Q1_CreateBrushes_r( brush, q1_dmodels[modelnum].headnode[0] ); + // +#ifdef Q1_PRINT + qprintf( "\n" ); +#endif //Q1_PRINT + //now we've got a list with brushes! + return brushlist; +} //end of the function Q1_CreateBrushesFromBSP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +q1_dleaf_t *Q1_PointInLeaf( int startnode, vec3_t point ) { + int nodenum; + vec_t dist; + q1_dnode_t *node; + q1_dplane_t *plane; + + nodenum = startnode; + while ( nodenum >= 0 ) + { + node = &q1_dnodes[nodenum]; + plane = &q1_dplanes[node->planenum]; + dist = DotProduct( point, plane->normal ) - plane->dist; + if ( dist > 0 ) { + nodenum = node->children[0]; + } else { + nodenum = node->children[1]; + } + } //end while + + return &q1_dleafs[-nodenum - 1]; +} //end of the function Q1_PointInLeaf +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Q1_FaceArea( q1_dface_t *face ) { + int i; + float total; + vec_t *v; + vec3_t d1, d2, cross; + q1_dedge_t *edge; + + edge = &q1_dedges[face->firstedge]; + v = q1_dvertexes[edge->v[0]].point; + + total = 0; + for ( i = 1; i < face->numedges - 1; i++ ) + { + edge = &q1_dedges[face->firstedge + i]; + VectorSubtract( q1_dvertexes[edge->v[0]].point, v, d1 ); + VectorSubtract( q1_dvertexes[edge->v[1]].point, v, d2 ); + CrossProduct( d1, d2, cross ); + total += 0.5 * VectorLength( cross ); + } //end for + return total; +} //end of the function AAS_FaceArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_FacePlane( q1_dface_t *face, vec3_t normal, float *dist ) { + vec_t *v1, *v2, *v3; + vec3_t vec1, vec2; + int side, edgenum; + + edgenum = q1_dsurfedges[face->firstedge]; + side = edgenum < 0; + v1 = q1_dvertexes[q1_dedges[abs( edgenum )].v[side]].point; + v2 = q1_dvertexes[q1_dedges[abs( edgenum )].v[!side]].point; + edgenum = q1_dsurfedges[face->firstedge + 1]; + side = edgenum < 0; + v3 = q1_dvertexes[q1_dedges[abs( edgenum )].v[!side]].point; + // + VectorSubtract( v2, v1, vec1 ); + VectorSubtract( v3, v1, vec2 ); + + CrossProduct( vec1, vec2, normal ); + VectorNormalize( normal ); + *dist = DotProduct( v1, normal ); +} //end of the function Q1_FacePlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_MergeBrushes( bspbrush_t *brushlist, int modelnum ) { + int nummerges, merged; + bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; + bspbrush_t *lastb2; + + if ( !brushlist ) { + return NULL; + } + + if ( !modelnum ) { + qprintf( "%5d brushes merged", nummerges = 0 ); + } + do + { + for ( tail = brushlist; tail; tail = tail->next ) + { + if ( !tail->next ) { + break; + } + } //end for + merged = 0; + newbrushlist = NULL; + for ( b1 = brushlist; b1; b1 = brushlist ) + { + lastb2 = b1; + for ( b2 = b1->next; b2; b2 = b2->next ) + { + //can't merge brushes with different contents + if ( b1->side != b2->side ) { + newbrush = NULL; + } else { newbrush = TryMergeBrushes( b1, b2 );} + //if a merged brush is created + if ( newbrush ) { + //copy the brush contents + newbrush->side = b1->side; + //add the new brush to the end of the list + tail->next = newbrush; + //remove the second brush from the list + lastb2->next = b2->next; + //remove the first brush from the list + brushlist = brushlist->next; + //free the merged brushes + FreeBrush( b1 ); + FreeBrush( b2 ); + //get a new tail brush + for ( tail = brushlist; tail; tail = tail->next ) + { + if ( !tail->next ) { + break; + } + } //end for + merged++; + if ( !modelnum ) { + qprintf( "\r%5d", nummerges++ ); + } + break; + } //end if + lastb2 = b2; + } //end for + //if b1 can't be merged with any of the other brushes + if ( !b2 ) { + brushlist = brushlist->next; + //keep b1 + b1->next = newbrushlist; + newbrushlist = b1; + } //end else + } //end for + brushlist = newbrushlist; + } while ( merged ); + if ( !modelnum ) { + qprintf( "\n" ); + } + return newbrushlist; +} //end of the function Q1_MergeBrushes +//=========================================================================== +// returns the amount the face and the winding overlap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Q1_FaceOnWinding( q1_dface_t *face, winding_t *winding ) { + int i, edgenum, side; + float dist, area; + q1_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + winding_t *w; + + // + w = CopyWinding( winding ); + memcpy( &plane, &q1_dplanes[face->planenum], sizeof( q1_dplane_t ) ); + //check on which side of the plane the face is + if ( face->side ) { + VectorNegate( plane.normal, plane.normal ); + plane.dist = -plane.dist; + } //end if + for ( i = 0; i < face->numedges && w; i++ ) + { + //get the first and second vertex of the edge + edgenum = q1_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = q1_dvertexes[q1_dedges[abs( edgenum )].v[side]].point; + v2 = q1_dvertexes[q1_dedges[abs( edgenum )].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract( v1, v2, edgevec ); + CrossProduct( edgevec, plane.normal, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, v1 ); + // + ChopWindingInPlace( &w, normal, dist, 0.9 ); //CLIP_EPSILON + } //end for + if ( w ) { + area = WindingArea( w ); + FreeWinding( w ); + return area; + } //end if + return 0; +} //end of the function Q1_FaceOnWinding +//=========================================================================== +// returns a list with brushes created by splitting the given brush with +// planes that go through the face edges and are orthogonal to the face plane +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_SplitBrushWithFace( bspbrush_t *brush, q1_dface_t *face ) { + int i, edgenum, side, planenum, splits; + float dist; + q1_dplane_t plane; + vec_t *v1, *v2; + vec3_t normal, edgevec; + bspbrush_t *front, *back, *brushlist; + + memcpy( &plane, &q1_dplanes[face->planenum], sizeof( q1_dplane_t ) ); + //check on which side of the plane the face is + if ( face->side ) { + VectorNegate( plane.normal, plane.normal ); + plane.dist = -plane.dist; + } //end if + splits = 0; + brushlist = NULL; + for ( i = 0; i < face->numedges; i++ ) + { + //get the first and second vertex of the edge + edgenum = q1_dsurfedges[face->firstedge + i]; + side = edgenum > 0; + //if the face plane is flipped + v1 = q1_dvertexes[q1_dedges[abs( edgenum )].v[side]].point; + v2 = q1_dvertexes[q1_dedges[abs( edgenum )].v[!side]].point; + //create a plane through the edge vector, orthogonal to the face plane + //and with the normal vector pointing out of the face + VectorSubtract( v1, v2, edgevec ); + CrossProduct( edgevec, plane.normal, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, v1 ); + // + planenum = FindFloatPlane( normal, dist ); + //split the current brush + SplitBrush( brush, planenum, &front, &back ); + //if there is a back brush just put it in the list + if ( back ) { + //copy the brush contents + back->side = brush->side; + // + back->next = brushlist; + brushlist = back; + splits++; + } //end if + if ( !front ) { + Log_Print( "Q1_SplitBrushWithFace: no new brush\n" ); + FreeBrushList( brushlist ); + return NULL; + } //end if + //copy the brush contents + front->side = brush->side; + //continue splitting the front brush + brush = front; + } //end for + if ( !splits ) { + FreeBrush( front ); + return NULL; + } //end if + front->next = brushlist; + brushlist = front; + return brushlist; +} //end of the function Q1_SplitBrushWithFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bspbrush_t *Q1_TextureBrushes( bspbrush_t *brushlist, int modelnum ) { + float area, largestarea; + int i, n, texinfonum, sn, numbrushes; + int bestfacenum, sidenodenum; + side_t *side; + q1_dmiptexlump_t *miptexlump; + q1_miptex_t *miptex; + bspbrush_t *brush, *nextbrush, *prevbrush, *newbrushes, *brushlistend; + vec_t defaultvec[4] = {1, 0, 0, 0}; + + if ( !modelnum ) { + qprintf( "texturing brushes\n" ); + } + if ( !modelnum ) { + qprintf( "%5d brushes", numbrushes = 0 ); + } + //get a pointer to the last brush in the list + for ( brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next ) + { + if ( !brushlistend->next ) { + break; + } + } //end for + //there's no previous brush when at the start of the list + prevbrush = NULL; + //go over the brush list + for ( brush = brushlist; brush; brush = nextbrush ) + { + nextbrush = brush->next; + //find a texinfo for every brush side + for ( sn = 0; sn < brush->numsides; sn++ ) + { + side = &brush->sides[sn]; + // + if ( side->flags & SFL_TEXTURED ) { + continue; + } + //number of the node that created this brush side + sidenodenum = side->surf; //see midwinding in Q1_SplitBrush + //no face found yet + bestfacenum = -1; + //minimum face size + largestarea = 1; + //if optimizing the texture placement and not going for the + //least number of brushes + if ( !lessbrushes ) { + for ( i = 0; i < q1_numfaces; i++ ) + { + //the face must be in the same plane as the node plane that created + //this brush side + if ( q1_dfaces[i].planenum == q1_dnodes[sidenodenum].planenum ) { + //get the area the face and the brush side overlap + area = Q1_FaceOnWinding( &q1_dfaces[i], side->winding ); + //if this face overlaps the brush side winding more than previous faces + if ( area > largestarea ) { + //if there already was a face for texturing this brush side with + //a different texture + if ( bestfacenum >= 0 && + ( q1_dfaces[bestfacenum].texinfo != q1_dfaces[i].texinfo ) ) { + //split the brush to fit the texture + newbrushes = Q1_SplitBrushWithFace( brush, &q1_dfaces[i] ); + //if new brushes where created + if ( newbrushes ) { + //remove the current brush from the list + if ( prevbrush ) { + prevbrush->next = brush->next; + } else { brushlist = brush->next;} + if ( brushlistend == brush ) { + brushlistend = prevbrush; + nextbrush = newbrushes; + } //end if + //add the new brushes to the end of the list + if ( brushlistend ) { + brushlistend->next = newbrushes; + } else { brushlist = newbrushes;} + //free the current brush + FreeBrush( brush ); + //don't forget about the prevbrush pointer at the bottom of + //the outer loop + brush = prevbrush; + //find the end of the list + for ( brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next ) + { + if ( !brushlistend->next ) { + break; + } + } //end for + break; + } //end if + else + { + Log_Write( "brush %d: no real texture split", numbrushes ); + } //end else + } //end if + else + { + //best face for texturing this brush side + bestfacenum = i; + } //end else + } //end if + } //end if + } //end for + //if the brush was split the original brush is removed + //and we just continue with the next one in the list + if ( i < q1_numfaces ) { + break; + } + } //end if + else + { + //find the face with the largest overlap with this brush side + //for texturing the brush side + for ( i = 0; i < q1_numfaces; i++ ) + { + //the face must be in the same plane as the node plane that created + //this brush side + if ( q1_dfaces[i].planenum == q1_dnodes[sidenodenum].planenum ) { + //get the area the face and the brush side overlap + area = Q1_FaceOnWinding( &q1_dfaces[i], side->winding ); + //if this face overlaps the brush side winding more than previous faces + if ( area > largestarea ) { + largestarea = area; + bestfacenum = i; + } //end if + } //end if + } //end for + } //end else + //if a face was found for texturing this brush side + if ( bestfacenum >= 0 ) { + //set the MAP texinfo values + texinfonum = q1_dfaces[bestfacenum].texinfo; + for ( n = 0; n < 4; n++ ) + { + map_texinfo[texinfonum].vecs[0][n] = q1_texinfo[texinfonum].vecs[0][n]; + map_texinfo[texinfonum].vecs[1][n] = q1_texinfo[texinfonum].vecs[1][n]; + } //end for + //make sure the two vectors aren't of zero length otherwise use the default + //vector to prevent a divide by zero in the map writing + if ( VectorLength( map_texinfo[texinfonum].vecs[0] ) < 0.01 ) { + memcpy( map_texinfo[texinfonum].vecs[0], defaultvec, sizeof( defaultvec ) ); + } + if ( VectorLength( map_texinfo[texinfonum].vecs[1] ) < 0.01 ) { + memcpy( map_texinfo[texinfonum].vecs[1], defaultvec, sizeof( defaultvec ) ); + } + // + map_texinfo[texinfonum].flags = q1_texinfo[texinfonum].flags; + map_texinfo[texinfonum].value = 0; //Q1 and HL texinfos don't have a value + //the mip texture + miptexlump = (q1_dmiptexlump_t *) q1_dtexdata; + miptex = ( q1_miptex_t * )( (byte *)miptexlump + miptexlump->dataofs[q1_texinfo[texinfonum].miptex] ); + //get the mip texture name + strcpy( map_texinfo[texinfonum].texture, miptex->name ); + //no animations in Quake1 and Half-Life mip textures + map_texinfo[texinfonum].nexttexinfo = -1; + //store the texinfo number + side->texinfo = texinfonum; + // + if ( texinfonum > map_numtexinfo ) { + map_numtexinfo = texinfonum; + } + //this side is textured + side->flags |= SFL_TEXTURED; + } //end if + else + { + //no texture for this side + side->texinfo = TEXINFO_NODE; + //this side is textured + side->flags |= SFL_TEXTURED; + } //end if + } //end for + // + if ( !modelnum && prevbrush != brush ) { + qprintf( "\r%5d", ++numbrushes ); + } + //previous brush in the list + prevbrush = brush; + } //end for + if ( !modelnum ) { + qprintf( "\n" ); + } + //return the new list with brushes + return brushlist; +} //end of the function Q1_TextureBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_FixContentsTextures( bspbrush_t *brushlist ) { + int i, texinfonum; + bspbrush_t *brush; + + for ( brush = brushlist; brush; brush = brush->next ) + { + //only fix the textures of water, slime and lava brushes + if ( brush->side != CONTENTS_WATER && + brush->side != CONTENTS_SLIME && + brush->side != CONTENTS_LAVA ) { + continue; + } + // + for ( i = 0; i < brush->numsides; i++ ) + { + texinfonum = brush->sides[i].texinfo; + if ( Q1_TextureContents( map_texinfo[texinfonum].texture ) == brush->side ) { + break; + } + } //end for + //if no specific contents texture was found + if ( i >= brush->numsides ) { + texinfonum = -1; + for ( i = 0; i < map_numtexinfo; i++ ) + { + if ( Q1_TextureContents( map_texinfo[i].texture ) == brush->side ) { + texinfonum = i; + break; + } //end if + } //end for + } //end if + // + if ( texinfonum >= 0 ) { + //give all the brush sides this contents texture + for ( i = 0; i < brush->numsides; i++ ) + { + brush->sides[i].texinfo = texinfonum; + } //end for + } //end if + else {Log_Print( "brush contents %d with wrong textures\n", brush->side );} + // + } //end for + /* + for (brush = brushlist; brush; brush = brush->next) + { + //give all the brush sides this contents texture + for (i = 0; i < brush->numsides; i++) + { + if (Q1_TextureContents(map_texinfo[texinfonum].texture) != brush->side) + { + Error("brush contents %d with wrong contents textures %s\n", brush->side, + Q1_TextureContents(map_texinfo[texinfonum].texture)); + } //end if + } //end for + } //end for*/ +} //end of the function Q1_FixContentsTextures +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_BSPBrushToMapBrush( bspbrush_t *bspbrush, entity_t *mapent ) { + mapbrush_t *mapbrush; + side_t *side; + int i, besttexinfo; + + CheckBSPBrush( bspbrush ); + + if ( nummapbrushes >= MAX_MAPFILE_BRUSHES ) { + Error( "nummapbrushes == MAX_MAPFILE_BRUSHES" ); + } + + mapbrush = &mapbrushes[nummapbrushes]; + mapbrush->original_sides = &brushsides[nummapbrushsides]; + mapbrush->entitynum = mapent - entities; + mapbrush->brushnum = nummapbrushes - mapent->firstbrush; + mapbrush->leafnum = -1; + mapbrush->numsides = 0; + + besttexinfo = TEXINFO_NODE; + for ( i = 0; i < bspbrush->numsides; i++ ) + { + if ( !bspbrush->sides[i].winding ) { + continue; + } + // + if ( nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES ) { + Error( "MAX_MAPFILE_BRUSHSIDES" ); + } + side = &brushsides[nummapbrushsides]; + //the contents of the bsp brush is stored in the side variable + side->contents = bspbrush->side; + side->surf = 0; + side->planenum = bspbrush->sides[i].planenum; + side->texinfo = bspbrush->sides[i].texinfo; + if ( side->texinfo != TEXINFO_NODE ) { + //this brush side is textured + side->flags |= SFL_TEXTURED; + besttexinfo = side->texinfo; + } //end if + // + nummapbrushsides++; + mapbrush->numsides++; + } //end for + // + if ( besttexinfo == TEXINFO_NODE ) { + mapbrush->numsides = 0; + q1_numclipbrushes++; + return; + } //end if + //set the texinfo for all the brush sides without texture + for ( i = 0; i < mapbrush->numsides; i++ ) + { + if ( mapbrush->original_sides[i].texinfo == TEXINFO_NODE ) { + mapbrush->original_sides[i].texinfo = besttexinfo; + } //end if + } //end for + //contents of the brush + mapbrush->contents = bspbrush->side; + // + if ( create_aas ) { + //create the AAS brushes from this brush, add brush bevels + AAS_CreateMapBrushes( mapbrush, mapent, true ); + return; + } //end if + //create windings for sides and bounds for brush + MakeBrushWindings( mapbrush ); + //add brush bevels + AddBrushBevels( mapbrush ); + //a new brush has been created + nummapbrushes++; + mapent->numbrushes++; +} //end of the function Q1_BSPBrushToMapBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_CreateMapBrushes( entity_t *mapent, int modelnum ) { + bspbrush_t *brushlist, *brush, *nextbrush; + int i; + + //create brushes from the model BSP tree + brushlist = Q1_CreateBrushesFromBSP( modelnum ); + //texture the brushes and split them when necesary + brushlist = Q1_TextureBrushes( brushlist, modelnum ); + //fix the contents textures of all brushes + Q1_FixContentsTextures( brushlist ); + // + if ( !nobrushmerge ) { + brushlist = Q1_MergeBrushes( brushlist, modelnum ); + //brushlist = Q1_MergeBrushes(brushlist, modelnum); + } //end if + // + if ( !modelnum ) { + qprintf( "converting brushes to map brushes\n" ); + } + if ( !modelnum ) { + qprintf( "%5d brushes", i = 0 ); + } + for ( brush = brushlist; brush; brush = nextbrush ) + { + nextbrush = brush->next; + Q1_BSPBrushToMapBrush( brush, mapent ); + brush->next = NULL; + FreeBrush( brush ); + if ( !modelnum ) { + qprintf( "\r%5d", ++i ); + } + } //end for + if ( !modelnum ) { + qprintf( "\n" ); + } +} //end of the function Q1_CreateMapBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_ResetMapLoading( void ) { +} //end of the function Q1_ResetMapLoading +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q1_LoadMapFromBSP( char *filename, int offset, int length ) { + int i, modelnum; + char *model, *classname; + + Log_Print( "-- Q1_LoadMapFromBSP --\n" ); + //the loaded map type + loadedmaptype = MAPTYPE_QUAKE1; + // + qprintf( "loading map from %s at %d\n", filename, offset ); + //load the Half-Life BSP file + Q1_LoadBSPFile( filename, offset, length ); + // + q1_numclipbrushes = 0; + //CreatePath(path); + //Q1_CreateQ2WALFiles(path); + //parse the entities from the BSP + Q1_ParseEntities(); + //clear the map mins and maxs + ClearBounds( map_mins, map_maxs ); + // + qprintf( "creating Quake1 brushes\n" ); + if ( lessbrushes ) { + qprintf( "creating minimum number of brushes\n" ); + } else { qprintf( "placing textures correctly\n" );} + // + for ( i = 0; i < num_entities; i++ ) + { + entities[i].firstbrush = nummapbrushes; + entities[i].numbrushes = 0; + // + classname = ValueForKey( &entities[i], "classname" ); + if ( classname && !strcmp( classname, "worldspawn" ) ) { + modelnum = 0; + } //end if + else + { + // + model = ValueForKey( &entities[i], "model" ); + if ( !model || *model != '*' ) { + continue; + } + model++; + modelnum = atoi( model ); + } //end else + //create map brushes for the entity + Q1_CreateMapBrushes( &entities[i], modelnum ); + } //end for + // + qprintf( "%5d map brushes\n", nummapbrushes ); + qprintf( "%5d clip brushes\n", q1_numclipbrushes ); +} //end of the function Q1_LoadMapFromBSP diff --git a/src/bspc/map_q2.c b/src/bspc/map_q2.c new file mode 100644 index 0000000..e4d5d8f --- /dev/null +++ b/src/bspc/map_q2.c @@ -0,0 +1,1149 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: map_q2.c +// Function: map loading and writing +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-03 +// Tab Size: 3 +//=========================================================================== + +//=========================================================================== +// ANSI, Area Navigational System Interface +// AAS, Area Awareness System +//=========================================================================== + +#include "qbsp.h" +#include "l_mem.h" +#include "..\botlib\aasfile.h" //aas_bbox_t +#include "aas_store.h" //AAS_MAX_BBOXES +#include "aas_cfg.h" +#include "aas_map.h" //AAS_CreateMapBrushes +#include "l_bsp_q2.h" + +#ifdef ME + +#define NODESTACKSIZE 1024 + +int nodestack[NODESTACKSIZE]; +int *nodestackptr; +int nodestacksize = 0; +int brushmodelnumbers[MAX_MAPFILE_BRUSHES]; +int dbrushleafnums[MAX_MAPFILE_BRUSHES]; +int dplanes2mapplanes[MAX_MAPFILE_PLANES]; + +#endif //ME + +//==================================================================== + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_CreateMapTexinfo( void ) { + int i; + + for ( i = 0; i < numtexinfo; i++ ) + { + memcpy( map_texinfo[i].vecs, texinfo[i].vecs, sizeof( float ) * 2 * 4 ); + map_texinfo[i].flags = texinfo[i].flags; + map_texinfo[i].value = texinfo[i].value; + strcpy( map_texinfo[i].texture, texinfo[i].texture ); + map_texinfo[i].nexttexinfo = 0; + } //end for +} //end of the function Q2_CreateMapTexinfo + +/* +=========== +Q2_BrushContents +=========== +*/ +int Q2_BrushContents( mapbrush_t *b ) { + int contents; + side_t *s; + int i; + int trans; + + s = &b->original_sides[0]; + contents = s->contents; + trans = texinfo[s->texinfo].flags; + for ( i = 1; i < b->numsides; i++, s++ ) + { + s = &b->original_sides[i]; + trans |= texinfo[s->texinfo].flags; + if ( s->contents != contents ) { + Log_Print( "Entity %i, Brush %i: mixed face contents\n" + , b->entitynum, b->brushnum ); + Log_Print( "texture name = %s\n", texinfo[s->texinfo].texture ); + break; + } + } + + // if any side is translucent, mark the contents + // and change solid to window + if ( trans & ( SURF_TRANS33 | SURF_TRANS66 ) ) { + contents |= CONTENTS_Q2TRANSLUCENT; + if ( contents & CONTENTS_SOLID ) { + contents &= ~CONTENTS_SOLID; + contents |= CONTENTS_WINDOW; + } + } + + return contents; +} + +#ifdef ME + +#define BBOX_NORMAL_EPSILON 0.0001 + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MakeAreaPortalBrush( mapbrush_t *brush ) { + int sn; + side_t *s; + + brush->contents = CONTENTS_AREAPORTAL; + + for ( sn = 0; sn < brush->numsides; sn++ ) + { + s = brush->original_sides + sn; + //make sure the surfaces are not hint or skip + s->surf &= ~( SURF_HINT | SURF_SKIP ); + // + s->texinfo = 0; + s->contents = CONTENTS_AREAPORTAL; + } //end for +} //end of the function MakeAreaPortalBrush +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DPlanes2MapPlanes( void ) { + int i; + + for ( i = 0; i < numplanes; i++ ) + { + dplanes2mapplanes[i] = FindFloatPlane( dplanes[i].normal, dplanes[i].dist ); + } //end for +} //end of the function DPlanes2MapPlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MarkVisibleBrushSides( mapbrush_t *brush ) { + int n, i, planenum; + side_t *side; + dface_t *face; + // + for ( n = 0; n < brush->numsides; n++ ) + { + side = brush->original_sides + n; + //if this side is a bevel or the leaf number of the brush is unknown + if ( ( side->flags & SFL_BEVEL ) || brush->leafnum < 0 ) { + //this side is a valid splitter + side->flags |= SFL_VISIBLE; + continue; + } //end if + //assum this side will not be used as a splitter + side->flags &= ~SFL_VISIBLE; + //check if the side plane is used by a visible face + for ( i = 0; i < numfaces; i++ ) + { + face = &dfaces[i]; + planenum = dplanes2mapplanes[face->planenum]; + if ( ( planenum & ~1 ) == ( side->planenum & ~1 ) ) { + //this side is a valid splitter + side->flags |= SFL_VISIBLE; + } //end if + } //end for + } //end for +} //end of the function MarkVisibleBrushSides + +#endif //ME + +/* +================= +Q2_ParseBrush +================= +*/ +void Q2_ParseBrush( script_t *script, entity_t *mapent ) { + mapbrush_t *b; + int i, j, k; + int mt; + side_t *side, *s2; + int planenum; + brush_texture_t td; + int planepts[3][3]; + token_t token; + + if ( nummapbrushes >= MAX_MAPFILE_BRUSHES ) { + Error( "nummapbrushes == MAX_MAPFILE_BRUSHES" ); + } + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities - 1; + b->brushnum = nummapbrushes - mapent->firstbrush; + b->leafnum = -1; + + do + { + if ( !PS_ReadToken( script, &token ) ) { + break; + } + if ( !strcmp( token.string, "}" ) ) { + break; + } + + //IDBUG: mixed use of MAX_MAPFILE_? and MAX_MAP_? this could + // lead to out of bound indexing of the arrays + if ( nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES ) { + Error( "MAX_MAPFILE_BRUSHSIDES" ); + } + side = &brushsides[nummapbrushsides]; + + //read the three point plane definition + for ( i = 0; i < 3; i++ ) + { + if ( i != 0 ) { + PS_ExpectTokenString( script, "(" ); + } + for ( j = 0; j < 3; j++ ) + { + PS_ExpectAnyToken( script, &token ); + planepts[i][j] = atof( token.string ); + } //end for + PS_ExpectTokenString( script, ")" ); + } //end for + + // + //read the texturedef + // + PS_ExpectAnyToken( script, &token ); + strcpy( td.name, token.string ); + + PS_ExpectAnyToken( script, &token ); + td.shift[0] = atol( token.string ); + PS_ExpectAnyToken( script, &token ); + td.shift[1] = atol( token.string ); + PS_ExpectAnyToken( script, &token ); + td.rotate = atol( token.string ); + PS_ExpectAnyToken( script, &token ); + td.scale[0] = atof( token.string ); + PS_ExpectAnyToken( script, &token ); + td.scale[1] = atof( token.string ); + + //find default flags and values + mt = FindMiptex( td.name ); + td.flags = textureref[mt].flags; + td.value = textureref[mt].value; + side->contents = textureref[mt].contents; + side->surf = td.flags = textureref[mt].flags; + + //check if there's a number available + if ( PS_CheckTokenType( script, TT_NUMBER, 0, &token ) ) { + side->contents = token.intvalue; + PS_ExpectTokenType( script, TT_NUMBER, 0, &token ); + side->surf = td.flags = token.intvalue; + PS_ExpectTokenType( script, TT_NUMBER, 0, &token ); + td.value = token.intvalue; + } + + // translucent objects are automatically classified as detail + if ( side->surf & ( SURF_TRANS33 | SURF_TRANS66 ) ) { + side->contents |= CONTENTS_DETAIL; + } + if ( side->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) { + side->contents |= CONTENTS_DETAIL; + } + if ( fulldetail ) { + side->contents &= ~CONTENTS_DETAIL; + } + if ( !( side->contents & ( ( LAST_VISIBLE_CONTENTS - 1 ) + | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP | CONTENTS_MIST ) ) ) { + side->contents |= CONTENTS_SOLID; + } + + // hints and skips are never detail, and have no content + if ( side->surf & ( SURF_HINT | SURF_SKIP ) ) { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + } + +#ifdef ME + //for creating AAS... this side is textured + side->flags |= SFL_TEXTURED; +#endif //ME + // + // find the plane number + // + planenum = PlaneFromPoints( planepts[0], planepts[1], planepts[2] ); + if ( planenum == -1 ) { + Log_Print( "Entity %i, Brush %i: plane with no normal\n" + , b->entitynum, b->brushnum ); + continue; + } + + // + // see if the plane has been used already + // + for ( k = 0 ; k < b->numsides ; k++ ) + { + s2 = b->original_sides + k; + if ( s2->planenum == planenum ) { + Log_Print( "Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum ); + break; + } + if ( s2->planenum == ( planenum ^ 1 ) ) { + Log_Print( "Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum ); + break; + } + } + if ( k != b->numsides ) { + continue; // duplicated + + } + // + // keep this side + // + + side = b->original_sides + b->numsides; + side->planenum = planenum; + side->texinfo = TexinfoForBrushTexture( &mapplanes[planenum], + &td, vec3_origin ); + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } while ( 1 ); + + // get the content for the entire brush + b->contents = Q2_BrushContents( b ); + +#ifdef ME + if ( BrushExists( b ) ) { + c_squattbrushes++; + b->numsides = 0; + return; + } //end if + + if ( create_aas ) { + //create AAS brushes, and add brush bevels + AAS_CreateMapBrushes( b, mapent, true ); + //NOTE: if we return here then duplicate plane errors occur for the non world entities + return; + } //end if +#endif //ME + + // allow detail brushes to be removed + if ( nodetail && ( b->contents & CONTENTS_DETAIL ) ) { + b->numsides = 0; + return; + } + + // allow water brushes to be removed + if ( nowater && ( b->contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { + b->numsides = 0; + return; + } + + // create windings for sides and bounds for brush + MakeBrushWindings( b ); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if ( b->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) { + c_clipbrushes++; + for ( i = 0 ; i < b->numsides ; i++ ) + b->original_sides[i].texinfo = TEXINFO_NODE; + } + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + if ( b->contents & CONTENTS_ORIGIN ) { + char string[32]; + vec3_t origin; + + if ( num_entities == 1 ) { + Error( "Entity %i, Brush %i: origin brushes not allowed in world" + , b->entitynum, b->brushnum ); + return; + } + + VectorAdd( b->mins, b->maxs, origin ); + VectorScale( origin, 0.5, origin ); + + sprintf( string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2] ); + SetKeyValue( &entities[b->entitynum], "origin", string ); + + VectorCopy( origin, entities[b->entitynum].origin ); + + // don't keep this brush + b->numsides = 0; + + return; + } + + AddBrushBevels( b ); + + nummapbrushes++; + mapent->numbrushes++; +} + +/* +================ +Q2_MoveBrushesToWorld + +Takes all of the brushes from the current entity and +adds them to the world's brush list. + +Used by func_group and func_areaportal +================ +*/ +void Q2_MoveBrushesToWorld( entity_t *mapent ) { + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = GetMemory( newbrushes * sizeof( mapbrush_t ) ); + memcpy( temp, mapbrushes + mapent->firstbrush, newbrushes * sizeof( mapbrush_t ) ); + +#if 0 // let them keep their original brush numbers + for ( i = 0 ; i < newbrushes ; i++ ) + temp[i].entitynum = 0; +#endif + + // make space to move the brushes (overlapped copy) + memmove( mapbrushes + worldbrushes + newbrushes, + mapbrushes + worldbrushes, + sizeof( mapbrush_t ) * ( nummapbrushes - worldbrushes - newbrushes ) ); + + // copy the new brushes down + memcpy( mapbrushes + worldbrushes, temp, sizeof( mapbrush_t ) * newbrushes ); + + // fix up indexes + entities[0].numbrushes += newbrushes; + for ( i = 1 ; i < num_entities ; i++ ) + entities[i].firstbrush += newbrushes; + FreeMemory( temp ); + + mapent->numbrushes = 0; +} + +/* +================ +Q2_ParseMapEntity +================ +*/ +qboolean Q2_ParseMapEntity( script_t *script ) { + entity_t *mapent; + epair_t *e; + side_t *s; + int i, j; + int startbrush, startsides; + vec_t newdist; + mapbrush_t *b; + token_t token; + + if ( !PS_ReadToken( script, &token ) ) { + return false; + } + + if ( strcmp( token.string, "{" ) ) { + Error( "ParseEntity: { not found" ); + } + + if ( num_entities == MAX_MAP_ENTITIES ) { + Error( "num_entities == MAX_MAP_ENTITIES" ); + } + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[num_entities]; + num_entities++; + memset( mapent, 0, sizeof( *mapent ) ); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; +// mapent->portalareas[0] = -1; +// mapent->portalareas[1] = -1; + + do + { + if ( !PS_ReadToken( script, &token ) ) { + Error( "ParseEntity: EOF without closing brace" ); + } //end if + if ( !strcmp( token.string, "}" ) ) { + break; + } + if ( !strcmp( token.string, "{" ) ) { + Q2_ParseBrush( script, mapent ); + } //end if + else + { + PS_UnreadLastToken( script ); + e = ParseEpair( script ); + e->next = mapent->epairs; + mapent->epairs = e; + } //end else + } while ( 1 ); + + GetVectorForKey( mapent, "origin", mapent->origin ); + + // + // if there was an origin brush, offset all of the planes and texinfo + // + if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) { + for ( i = 0 ; i < mapent->numbrushes ; i++ ) + { + b = &mapbrushes[mapent->firstbrush + i]; + for ( j = 0 ; j < b->numsides ; j++ ) + { + s = &b->original_sides[j]; + newdist = mapplanes[s->planenum].dist - + DotProduct( mapplanes[s->planenum].normal, mapent->origin ); + s->planenum = FindFloatPlane( mapplanes[s->planenum].normal, newdist ); + s->texinfo = TexinfoForBrushTexture( &mapplanes[s->planenum], + &side_brushtextures[s - brushsides], mapent->origin ); + } + MakeBrushWindings( b ); + } + } + + // group entities are just for editor convenience + // toss all brushes into the world entity + if ( !strcmp( "func_group", ValueForKey( mapent, "classname" ) ) ) { + Q2_MoveBrushesToWorld( mapent ); + mapent->numbrushes = 0; + return true; + } + + // areaportal entities move their brushes, but don't eliminate + // the entity + if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) { + char str[128]; + + if ( mapent->numbrushes != 1 ) { + Error( "Entity %i: func_areaportal can only be a single brush", num_entities - 1 ); + } + + b = &mapbrushes[nummapbrushes - 1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + // set the portal number as "style" + sprintf( str, "%i", c_areaportals ); + SetKeyValue( mapent, "style", str ); + Q2_MoveBrushesToWorld( mapent ); + return true; + } + + return true; +} + +//=================================================================== + +/* +================ +LoadMapFile +================ +*/ +void Q2_LoadMapFile( char *filename ) { + int i; + script_t *script; + + Log_Print( "-- Q2_LoadMapFile --\n" ); +#ifdef ME + //loaded map type + loadedmaptype = MAPTYPE_QUAKE2; + //reset the map loading + ResetMapLoading(); +#endif //ME + + script = LoadScriptFile( filename ); + if ( !script ) { + Log_Print( "couldn't open %s\n", filename ); + return; + } //end if + //white spaces and escape characters inside a string are not allowed + SetScriptFlags( script, SCFL_NOSTRINGWHITESPACES | + SCFL_NOSTRINGESCAPECHARS | + SCFL_PRIMITIVE ); + + nummapbrushsides = 0; + num_entities = 0; + + while ( Q2_ParseMapEntity( script ) ) + { + } + + ClearBounds( map_mins, map_maxs ); + for ( i = 0 ; i < entities[0].numbrushes ; i++ ) + { + if ( mapbrushes[i].mins[0] > 4096 ) { + continue; // no valid points + } + AddPointToBounds( mapbrushes[i].mins, map_mins, map_maxs ); + AddPointToBounds( mapbrushes[i].maxs, map_mins, map_maxs ); + } //end for + + PrintMapInfo(); + + //free the script + FreeScript( script ); +// TestExpandBrushes (); + // + Q2_CreateMapTexinfo(); +} //end of the function Q2_LoadMapFile + +#ifdef ME //Begin MAP loading from BSP file +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_SetLeafBrushesModelNumbers( int leafnum, int modelnum ) { + int i, brushnum; + dleaf_t *leaf; + + leaf = &dleafs[leafnum]; + for ( i = 0; i < leaf->numleafbrushes; i++ ) + { + brushnum = dleafbrushes[leaf->firstleafbrush + i]; + brushmodelnumbers[brushnum] = modelnum; + dbrushleafnums[brushnum] = leafnum; + } //end for +} //end of the function Q2_SetLeafBrushesModelNumbers +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_InitNodeStack( void ) { + nodestackptr = nodestack; + nodestacksize = 0; +} //end of the function Q2_InitNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_PushNodeStack( int num ) { + *nodestackptr = num; + nodestackptr++; + nodestacksize++; + // + if ( nodestackptr >= &nodestack[NODESTACKSIZE] ) { + Error( "Q2_PushNodeStack: stack overflow\n" ); + } //end if +} //end of the function Q2_PushNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Q2_PopNodeStack( void ) { + //if the stack is empty + if ( nodestackptr <= nodestack ) { + return -1; + } + //decrease stack pointer + nodestackptr--; + nodestacksize--; + //return the top value from the stack + return *nodestackptr; +} //end of the function Q2_PopNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_SetBrushModelNumbers( entity_t *mapent ) { + int n, pn; + int leafnum; + + // + Q2_InitNodeStack(); + //head node (root) of the bsp tree + n = dmodels[mapent->modelnum].headnode; + pn = 0; + + do + { + //if we are in a leaf (negative node number) + if ( n < 0 ) { + //number of the leaf + leafnum = ( -n ) - 1; + //set the brush numbers + Q2_SetLeafBrushesModelNumbers( leafnum, mapent->modelnum ); + //walk back into the tree to find a second child to continue with + for ( pn = Q2_PopNodeStack(); pn >= 0; n = pn, pn = Q2_PopNodeStack() ) + { + //if we took the first child at the parent node + if ( dnodes[pn].children[0] == n ) { + break; + } + } //end for + //if the stack wasn't empty (if not processed whole tree) + if ( pn >= 0 ) { + //push the parent node again + Q2_PushNodeStack( pn ); + //we proceed with the second child of the parent node + n = dnodes[pn].children[1]; + } //end if + } //end if + else + { + //push the current node onto the stack + Q2_PushNodeStack( n ); + //walk forward into the tree to the first child + n = dnodes[n].children[0]; + } //end else + } while ( pn >= 0 ); +} //end of the function Q2_SetBrushModelNumbers +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_BSPBrushToMapBrush( dbrush_t *bspbrush, entity_t *mapent ) { + mapbrush_t *b; + int i, k, n; + side_t *side, *s2; + int planenum; + dbrushside_t *bspbrushside; + dplane_t *bspplane; + + if ( nummapbrushes >= MAX_MAPFILE_BRUSHES ) { + Error( "nummapbrushes >= MAX_MAPFILE_BRUSHES" ); + } + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = mapent - entities; + b->brushnum = nummapbrushes - mapent->firstbrush; + b->leafnum = dbrushleafnums[bspbrush - dbrushes]; + + for ( n = 0; n < bspbrush->numsides; n++ ) + { + //pointer to the bsp brush side + bspbrushside = &dbrushsides[bspbrush->firstside + n]; + + if ( nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES ) { + Error( "MAX_MAPFILE_BRUSHSIDES" ); + } //end if + //pointer to the map brush side + side = &brushsides[nummapbrushsides]; + //if the BSP brush side is textured + if ( brushsidetextured[bspbrush->firstside + n] ) { + side->flags |= SFL_TEXTURED; + } else { side->flags &= ~SFL_TEXTURED;} + //ME: can get side contents and surf directly from BSP file + side->contents = bspbrush->contents; + //if the texinfo is TEXINFO_NODE + if ( bspbrushside->texinfo < 0 ) { + side->surf = 0; + } else { side->surf = texinfo[bspbrushside->texinfo].flags;} + + // translucent objects are automatically classified as detail + if ( side->surf & ( SURF_TRANS33 | SURF_TRANS66 ) ) { + side->contents |= CONTENTS_DETAIL; + } + if ( side->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) { + side->contents |= CONTENTS_DETAIL; + } + if ( fulldetail ) { + side->contents &= ~CONTENTS_DETAIL; + } + if ( !( side->contents & ( ( LAST_VISIBLE_CONTENTS - 1 ) + | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP | CONTENTS_MIST ) ) ) { + side->contents |= CONTENTS_SOLID; + } + + // hints and skips are never detail, and have no content + if ( side->surf & ( SURF_HINT | SURF_SKIP ) ) { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + } + + //ME: get a plane for this side + bspplane = &dplanes[bspbrushside->planenum]; + planenum = FindFloatPlane( bspplane->normal, bspplane->dist ); + // + // see if the plane has been used already + // + //ME: this really shouldn't happen!!! + //ME: otherwise the bsp file is corrupted?? + //ME: still it seems to happen, maybe Johny Boy's + //ME: brush bevel adding is crappy ? + for ( k = 0; k < b->numsides; k++ ) + { + s2 = b->original_sides + k; +// if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 +// && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) + + if ( s2->planenum == planenum ) { + Log_Print( "Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum ); + break; + } + if ( s2->planenum == ( planenum ^ 1 ) ) { + Log_Print( "Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum ); + break; + } + } + if ( k != b->numsides ) { + continue; // duplicated + + } + // + // keep this side + // + //ME: reset pointer to side, why? hell I dunno (pointer is set above already) + side = b->original_sides + b->numsides; + //ME: store the plane number + side->planenum = planenum; + //ME: texinfo is already stored when bsp is loaded + //NOTE: check for TEXINFO_NODE, otherwise crash in Q2_BrushContents + if ( bspbrushside->texinfo < 0 ) { + side->texinfo = 0; + } else { side->texinfo = bspbrushside->texinfo;} + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + // ME: don't need to recalculate because it's already done + // (for non-world entities) in the BSP file +// side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } //end for + + // get the content for the entire brush + b->contents = bspbrush->contents; + Q2_BrushContents( b ); + + if ( BrushExists( b ) ) { + c_squattbrushes++; + b->numsides = 0; + return; + } //end if + + //if we're creating AAS + if ( create_aas ) { + //create the AAS brushes from this brush, don't add brush bevels + AAS_CreateMapBrushes( b, mapent, false ); + return; + } //end if + + // allow detail brushes to be removed + if ( nodetail && ( b->contents & CONTENTS_DETAIL ) ) { + b->numsides = 0; + return; + } //end if + + // allow water brushes to be removed + if ( nowater && ( b->contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { + b->numsides = 0; + return; + } //end if + + // create windings for sides and bounds for brush + MakeBrushWindings( b ); + + //mark brushes without winding or with a tiny window as bevels + MarkBrushBevels( b ); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if ( b->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) { + c_clipbrushes++; + for ( i = 0; i < b->numsides; i++ ) + b->original_sides[i].texinfo = TEXINFO_NODE; + } //end for + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + //ME: not needed because the entities in the BSP file already + // have an origin set +// if (b->contents & CONTENTS_ORIGIN) +// { +// char string[32]; +// vec3_t origin; +// +// if (num_entities == 1) +// { +// Error ("Entity %i, Brush %i: origin brushes not allowed in world" +// , b->entitynum, b->brushnum); +// return; +// } +// +// VectorAdd (b->mins, b->maxs, origin); +// VectorScale (origin, 0.5, origin); +// +// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); +// SetKeyValue (&entities[b->entitynum], "origin", string); +// +// VectorCopy (origin, entities[b->entitynum].origin); +// +// // don't keep this brush +// b->numsides = 0; +// +// return; +// } + + //ME: the bsp brushes already have bevels, so we won't try to + // add them again (especially since Johny Boy's bevel adding might + // be crappy) +// AddBrushBevels(b); + + nummapbrushes++; + mapent->numbrushes++; +} //end of the function Q2_BSPBrushToMapBrush +//=========================================================================== +//=========================================================================== +void Q2_ParseBSPBrushes( entity_t *mapent ) { + int i; + + //give all the brushes that belong to this entity the number of the + //BSP model used by this entity + Q2_SetBrushModelNumbers( mapent ); + //now parse all the brushes with the correct mapent->modelnum + for ( i = 0; i < numbrushes; i++ ) + { + if ( brushmodelnumbers[i] == mapent->modelnum ) { + Q2_BSPBrushToMapBrush( &dbrushes[i], mapent ); + } //end if + } //end for +} //end of the function Q2_ParseBSPBrushes +//=========================================================================== +//=========================================================================== +qboolean Q2_ParseBSPEntity( int entnum ) { + entity_t *mapent; + char *model; + int startbrush, startsides; + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[entnum]; //num_entities]; + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + mapent->modelnum = -1; //-1 = no model + + model = ValueForKey( mapent, "model" ); + if ( model && strlen( model ) ) { + if ( *model != '*' ) { + Error( "Q2_ParseBSPEntity: model number without leading *" ); + } //end if + //get the model number of this entity (skip the leading *) + mapent->modelnum = atoi( &model[1] ); + } //end if + + GetVectorForKey( mapent, "origin", mapent->origin ); + + //if this is the world entity it has model number zero + //the world entity has no model key + if ( !strcmp( "worldspawn", ValueForKey( mapent, "classname" ) ) ) { + mapent->modelnum = 0; + } //end if + //if the map entity has a BSP model (a modelnum of -1 is used for + //entities that aren't using a BSP model) + if ( mapent->modelnum >= 0 ) { + //parse the bsp brushes + Q2_ParseBSPBrushes( mapent ); + } //end if + // + //the origin of the entity is already taken into account + // + //func_group entities can't be in the bsp file + // + //check out the func_areaportal entities + if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) { + c_areaportals++; + mapent->areaportalnum = c_areaportals; + return true; + } //end if + return true; +} //end of the function Q2_ParseBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q2_LoadMapFromBSP( char *filename, int offset, int length ) { + int i; + + Log_Print( "-- Q2_LoadMapFromBSP --\n" ); + //loaded map type + loadedmaptype = MAPTYPE_QUAKE2; + + Log_Print( "Loading map from %s...\n", filename ); + //load the bsp file + Q2_LoadBSPFile( filename, offset, length ); + + //create an index from bsp planes to map planes + //DPlanes2MapPlanes(); + //clear brush model numbers + for ( i = 0; i < MAX_MAPFILE_BRUSHES; i++ ) + brushmodelnumbers[i] = -1; + + nummapbrushsides = 0; + num_entities = 0; + + Q2_ParseEntities(); + // + for ( i = 0; i < num_entities; i++ ) + { + Q2_ParseBSPEntity( i ); + } //end for + + //get the map mins and maxs from the world model + ClearBounds( map_mins, map_maxs ); + for ( i = 0; i < entities[0].numbrushes; i++ ) + { + if ( mapbrushes[i].mins[0] > 4096 ) { + continue; //no valid points + } + AddPointToBounds( mapbrushes[i].mins, map_mins, map_maxs ); + AddPointToBounds( mapbrushes[i].maxs, map_mins, map_maxs ); + } //end for + + PrintMapInfo(); + // + Q2_CreateMapTexinfo(); +} //end of the function Q2_LoadMapFromBSP + +void Q2_ResetMapLoading( void ) { + //reset for map loading from bsp + memset( nodestack, 0, NODESTACKSIZE * sizeof( int ) ); + nodestackptr = NULL; + nodestacksize = 0; + memset( brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof( int ) ); +} //end of the function Q2_ResetMapLoading + +//End MAP loading from BSP file +#endif //ME + +//==================================================================== + +/* +================ +TestExpandBrushes + +Expands all the brush planes and saves a new map out +================ +*/ +void TestExpandBrushes( void ) { + FILE *f; + side_t *s; + int i, j, bn; + winding_t *w; + char *name = "expanded.map"; + mapbrush_t *brush; + vec_t dist; + + Log_Print( "writing %s\n", name ); + f = fopen( name, "wb" ); + if ( !f ) { + Error( "Can't write %s\n", name ); + } + + fprintf( f, "{\n\"classname\" \"worldspawn\"\n" ); + + for ( bn = 0 ; bn < nummapbrushes ; bn++ ) + { + brush = &mapbrushes[bn]; + fprintf( f, "{\n" ); + for ( i = 0 ; i < brush->numsides ; i++ ) + { + s = brush->original_sides + i; + dist = mapplanes[s->planenum].dist; + for ( j = 0 ; j < 3 ; j++ ) + dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); + + w = BaseWindingForPlane( mapplanes[s->planenum].normal, dist ); + + fprintf( f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2] ); + fprintf( f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2] ); + fprintf( f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] ); + + fprintf( f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture ); + FreeWinding( w ); + } + fprintf( f, "}\n" ); + } + fprintf( f, "}\n" ); + + fclose( f ); + + Error( "can't proceed after expanding brushes" ); +} //end of the function TestExpandBrushes + diff --git a/src/bspc/map_q3.c b/src/bspc/map_q3.c new file mode 100644 index 0000000..96fbdf4 --- /dev/null +++ b/src/bspc/map_q3.c @@ -0,0 +1,711 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: map_q3.c +// Function: map loading +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1999-07-02 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "l_mem.h" +#include "..\botlib\aasfile.h" //aas_bbox_t +#include "aas_store.h" //AAS_MAX_BBOXES +#include "aas_cfg.h" +#include "aas_map.h" //AAS_CreateMapBrushes +#include "l_bsp_q3.h" +#include "..\qcommon\cm_patch.h" +#include "..\game\surfaceflags.h" + +#define NODESTACKSIZE 1024 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintContents( int contents ); + +int Q3_BrushContents( mapbrush_t *b ) { + int contents, i, mixed, hint; + side_t *s; + + s = &b->original_sides[0]; + contents = s->contents; + // + mixed = false; + hint = false; + for ( i = 1; i < b->numsides; i++ ) + { + s = &b->original_sides[i]; + if ( s->contents != contents ) { + mixed = true; + } + if ( s->surf & ( SURF_HINT | SURF_SKIP ) ) { + hint = true; + } + contents |= s->contents; + } //end for + // + if ( hint ) { + if ( contents ) { + Log_Write( "WARNING: hint brush with contents: " ); + PrintContents( contents ); + Log_Write( "\r\n" ); + // + Log_Write( "brush contents is: " ); + PrintContents( b->contents ); + Log_Write( "\r\n" ); + } //end if + return 0; + } //end if + //Log_Write("brush %d contents ", nummapbrushes); + //PrintContents(contents); + //Log_Write("\r\n"); + //remove ladder and fog contents + contents &= ~( CONTENTS_LADDER | CONTENTS_FOG ); + // + if ( mixed ) { + Log_Write( "Entity %i, Brush %i: mixed face contents " + , b->entitynum, b->brushnum ); + PrintContents( contents ); + Log_Write( "\r\n" ); + // + Log_Write( "brush contents is: " ); + PrintContents( b->contents ); + Log_Write( "\r\n" ); + // + if ( contents & CONTENTS_DONOTENTER ) { + return CONTENTS_DONOTENTER; //Log_Print("mixed contents with donotenter\n"); + } + /* + Log_Print("contents:"); PrintContents(contents); + Log_Print("\ncontents:"); PrintContents(s->contents); + Log_Print("\n"); + Log_Print("texture name = %s\n", texinfo[s->texinfo].texture); + */ + //if liquid brush + if ( contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { + return ( contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ); + } //end if + if ( contents & CONTENTS_PLAYERCLIP ) { + return ( contents & CONTENTS_PLAYERCLIP ); + } + return ( contents & CONTENTS_SOLID ); + } //end if + /* + if (contents & CONTENTS_AREAPORTAL) + { + static int num; + Log_Write("Entity %i, Brush %i: area portal %d\r\n", b->entitynum, b->brushnum, num++); + } //end if*/ + if ( contents == ( contents & CONTENTS_STRUCTURAL ) ) { + //Log_Print("brush %i is only structural\n", b->brushnum); + contents = 0; + } //end if + if ( contents & CONTENTS_DONOTENTER ) { + Log_Print( "brush %i is a donotenter brush, c = %X\n", b->brushnum, contents ); + } //end if + return contents; +} //end of the function Q3_BrushContents +#define BBOX_NORMAL_EPSILON 0.0001 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_DPlanes2MapPlanes( void ) { + int i; + + for ( i = 0; i < q3_numplanes; i++ ) + { + dplanes2mapplanes[i] = FindFloatPlane( q3_dplanes[i].normal, q3_dplanes[i].dist ); + } //end for +} //end of the function Q3_DPlanes2MapPlanes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_BSPBrushToMapBrush( q3_dbrush_t *bspbrush, entity_t *mapent ) { + mapbrush_t *b; + int i, k, n; + side_t *side, *s2; + int planenum; + q3_dbrushside_t *bspbrushside; + q3_dplane_t *bspplane; + int contentFlags = 0; + + if ( nummapbrushes >= MAX_MAPFILE_BRUSHES ) { + Error( "nummapbrushes >= MAX_MAPFILE_BRUSHES" ); + } + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = mapent - entities; + b->brushnum = nummapbrushes - mapent->firstbrush; + b->leafnum = dbrushleafnums[bspbrush - q3_dbrushes]; + + for ( n = 0; n < bspbrush->numSides; n++ ) + { + //pointer to the bsp brush side + bspbrushside = &q3_dbrushsides[bspbrush->firstSide + n]; + + if ( nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES ) { + Error( "MAX_MAPFILE_BRUSHSIDES" ); + } //end if + //pointer to the map brush side + side = &brushsides[nummapbrushsides]; + //if the BSP brush side is textured + if ( q3_dbrushsidetextured[bspbrush->firstSide + n] ) { + side->flags |= SFL_TEXTURED | SFL_VISIBLE; + } else { side->flags &= ~SFL_TEXTURED;} + //NOTE: all Quake3 sides are assumed textured + //side->flags |= SFL_TEXTURED|SFL_VISIBLE; + // + if ( bspbrushside->shaderNum < 0 ) { + side->contents = 0; + side->surf = 0; + } //end if + else + { + side->contents = q3_dshaders[bspbrushside->shaderNum].contentFlags; + side->surf = q3_dshaders[bspbrushside->shaderNum].surfaceFlags; + if ( strstr( q3_dshaders[bspbrushside->shaderNum].shader, "common/hint" ) ) { + //Log_Print("found hint side\n"); + side->surf |= SURF_HINT; + } //end if + + // Ridah, mark ladder brushes + if ( strstr( q3_dshaders[bspbrushside->shaderNum].shader, "common/ladder" ) ) { + //Log_Print("found ladder side\n"); + side->contents |= CONTENTS_LADDER; + contentFlags |= CONTENTS_LADDER; + } //end if + // done. + + } //end else + // + + if ( !( strstr( q3_dshaders[bspbrushside->shaderNum].shader, "common/slip" ) ) ) { + side->flags |= SFL_VISIBLE; + } else if ( side->surf & SURF_NODRAW ) { + side->flags |= SFL_TEXTURED | SFL_VISIBLE; + } //end if + /* + if (side->contents & (CONTENTS_TRANSLUCENT|CONTENTS_STRUCTURAL)) + { + side->flags |= SFL_TEXTURED|SFL_VISIBLE; + } //end if*/ + + // hints and skips are never detail, and have no content + if ( side->surf & ( SURF_HINT | SURF_SKIP ) ) { + side->contents = 0; + //Log_Print("found hint brush side\n"); + } + /* + if ((side->surf & SURF_NODRAW) && (side->surf & SURF_NOIMPACT)) + { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + Log_Print("probably found hint brush in a BSP without hints being used\n"); + } //end if*/ + + //ME: get a plane for this side + bspplane = &q3_dplanes[bspbrushside->planeNum]; + planenum = FindFloatPlane( bspplane->normal, bspplane->dist ); + // + // see if the plane has been used already + // + //ME: this really shouldn't happen!!! + //ME: otherwise the bsp file is corrupted?? + //ME: still it seems to happen, maybe Johny Boy's + //ME: brush bevel adding is crappy ? + for ( k = 0; k < b->numsides; k++ ) + { + s2 = b->original_sides + k; +// if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 +// && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) + + if ( s2->planenum == planenum ) { + Log_Print( "Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum ); + break; + } + if ( s2->planenum == ( planenum ^ 1 ) ) { + Log_Print( "Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum ); + break; + } + } + if ( k != b->numsides ) { + continue; // duplicated + + } + // + // keep this side + // + //ME: reset pointer to side, why? hell I dunno (pointer is set above already) + side = b->original_sides + b->numsides; + //ME: store the plane number + side->planenum = planenum; + //ME: texinfo is already stored when bsp is loaded + //NOTE: check for TEXINFO_NODE, otherwise crash in Q3_BrushContents + //if (bspbrushside->texinfo < 0) side->texinfo = 0; + //else side->texinfo = bspbrushside->texinfo; + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + // ME: don't need to recalculate because it's already done + // (for non-world entities) in the BSP file +// side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } //end for + + // get the content for the entire brush + //Quake3 bsp brushes don't have a contents + b->contents = q3_dshaders[bspbrush->shaderNum].contentFlags | contentFlags; + // Ridah, Wolf has ladders (if we call Q3_BrushContents(), we'll get the solid area bug + b->contents &= ~( /*CONTENTS_LADDER|*/ CONTENTS_FOG | CONTENTS_STRUCTURAL ); + //b->contents = Q3_BrushContents(b); + // + // Ridah, CONTENTS_MOSTERCLIP should prevent AAS from being created, but not clip players/AI in the game + if ( b->contents & CONTENTS_MONSTERCLIP ) { + b->contents |= CONTENTS_PLAYERCLIP; + } + + // func_explosive's not solid + if ( !strcmp( "func_explosive", ValueForKey( &entities[b->entitynum], "classname" ) ) || + !strcmp( "func_invisible_user", ValueForKey( &entities[b->entitynum], "classname" ) ) || + !strcmp( "func_static", ValueForKey( &entities[b->entitynum], "classname" ) ) ) { + Log_Print( "Ignoring %s brush..\n", ValueForKey( &entities[b->entitynum], "classname" ) ); + b->numsides = 0; + b->contents = 0; + return; + } + + if ( BrushExists( b ) ) { + c_squattbrushes++; + b->numsides = 0; + return; + } //end if + + //if we're creating AAS + if ( create_aas ) { + //create the AAS brushes from this brush, don't add brush bevels + AAS_CreateMapBrushes( b, mapent, false ); + return; + } //end if + + // allow detail brushes to be removed + if ( nodetail && ( b->contents & CONTENTS_DETAIL ) ) { + b->numsides = 0; + return; + } //end if + + // allow water brushes to be removed + if ( nowater && ( b->contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { + b->numsides = 0; + return; + } //end if + + + // create windings for sides and bounds for brush + MakeBrushWindings( b ); + + //mark brushes without winding or with a tiny window as bevels + MarkBrushBevels( b ); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if ( b->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) { + c_clipbrushes++; + for ( i = 0; i < b->numsides; i++ ) + b->original_sides[i].texinfo = TEXINFO_NODE; + } //end for + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + //ME: not needed because the entities in the BSP file already + // have an origin set +// if (b->contents & CONTENTS_ORIGIN) +// { +// char string[32]; +// vec3_t origin; +// +// if (num_entities == 1) +// { +// Error ("Entity %i, Brush %i: origin brushes not allowed in world" +// , b->entitynum, b->brushnum); +// return; +// } +// +// VectorAdd (b->mins, b->maxs, origin); +// VectorScale (origin, 0.5, origin); +// +// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); +// SetKeyValue (&entities[b->entitynum], "origin", string); +// +// VectorCopy (origin, entities[b->entitynum].origin); +// +// // don't keep this brush +// b->numsides = 0; +// +// return; +// } + + //ME: the bsp brushes already have bevels, so we won't try to + // add them again (especially since Johny Boy's bevel adding might + // be crappy) +// AddBrushBevels(b); + + nummapbrushes++; + mapent->numbrushes++; +} //end of the function Q3_BSPBrushToMapBrush +//=========================================================================== +//=========================================================================== +void Q3_ParseBSPBrushes( entity_t *mapent ) { + int i; + + /* + //give all the brushes that belong to this entity the number of the + //BSP model used by this entity + Q3_SetBrushModelNumbers(mapent); + //now parse all the brushes with the correct mapent->modelnum + for (i = 0; i < q3_numbrushes; i++) + { + if (brushmodelnumbers[i] == mapent->modelnum) + { + Q3_BSPBrushToMapBrush(&q3_dbrushes[i], mapent); + } //end if + } //end for + */ + for ( i = 0; i < q3_dmodels[mapent->modelnum].numBrushes; i++ ) + { + Q3_BSPBrushToMapBrush( &q3_dbrushes[q3_dmodels[mapent->modelnum].firstBrush + i], mapent ); + } //end for +} //end of the function Q3_ParseBSPBrushes +//=========================================================================== +//=========================================================================== +qboolean Q3_ParseBSPEntity( int entnum ) { + entity_t *mapent; + char *model; + int startbrush, startsides; + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[entnum]; //num_entities]; + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + mapent->modelnum = -1; //-1 = no BSP model + + model = ValueForKey( mapent, "model" ); + if ( model && strlen( model ) ) { + if ( *model == '*' ) { + //get the model number of this entity (skip the leading *) + mapent->modelnum = atoi( &model[1] ); + } //end if + } //end if + + GetVectorForKey( mapent, "origin", mapent->origin ); + + //if this is the world entity it has model number zero + //the world entity has no model key + if ( !strcmp( "worldspawn", ValueForKey( mapent, "classname" ) ) ) { + mapent->modelnum = 0; + } //end if + //if the map entity has a BSP model (a modelnum of -1 is used for + //entities that aren't using a BSP model) + if ( mapent->modelnum >= 0 ) { + //parse the bsp brushes + Q3_ParseBSPBrushes( mapent ); + } //end if + // + //the origin of the entity is already taken into account + // + //func_group entities can't be in the bsp file + // + //check out the func_areaportal entities + if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) { + c_areaportals++; + mapent->areaportalnum = c_areaportals; + return true; + } //end if + return true; +} //end of the function Q3_ParseBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define MAX_PATCH_VERTS 1024 + +void AAS_CreateCurveBrushes( void ) { + int i, j, n, planenum, numcurvebrushes = 0; + q3_dsurface_t *surface; + q3_drawVert_t *dv_p; + vec3_t points[MAX_PATCH_VERTS]; + int width, height, c; + patchCollide_t *pc; + facet_t *facet; + mapbrush_t *brush; + side_t *side; + entity_t *mapent; + winding_t *winding; + + qprintf( "nummapbrushsides = %d\n", nummapbrushsides ); + mapent = &entities[0]; + for ( i = 0; i < q3_numDrawSurfaces; i++ ) + { + surface = &q3_drawSurfaces[i]; + if ( !surface->patchWidth ) { + continue; + } + //if the curve is not solid + if ( !( q3_dshaders[surface->shaderNum].contentFlags & ( CONTENTS_SOLID | CONTENTS_PLAYERCLIP ) ) ) { + //Log_Print("skipped non-solid curve\n"); + continue; + } //end if + // + width = surface->patchWidth; + height = surface->patchHeight; + c = width * height; + if ( c > MAX_PATCH_VERTS ) { + Error( "ParseMesh: MAX_PATCH_VERTS" ); + } //end if + + dv_p = q3_drawVerts + surface->firstVert; + for ( j = 0 ; j < c ; j++, dv_p++ ) + { + points[j][0] = dv_p->xyz[0]; + points[j][1] = dv_p->xyz[1]; + points[j][2] = dv_p->xyz[2]; + } //end for + // create the internal facet structure + pc = CM_GeneratePatchCollide( width, height, points ); + // + for ( j = 0; j < pc->numFacets; j++ ) + { + facet = &pc->facets[j]; + // + brush = &mapbrushes[nummapbrushes]; + brush->original_sides = &brushsides[nummapbrushsides]; + brush->entitynum = 0; + brush->brushnum = nummapbrushes - mapent->firstbrush; + // + brush->numsides = facet->numBorders + 2; + nummapbrushsides += brush->numsides; + brush->contents = CONTENTS_SOLID; + // + //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); + qprintf( "\r%6d curve brushes", ++numcurvebrushes ); + // + planenum = FindFloatPlane( pc->planes[facet->surfacePlane].plane, pc->planes[facet->surfacePlane].plane[3] ); + // + side = &brush->original_sides[0]; + side->planenum = planenum; + side->contents = CONTENTS_SOLID; + side->flags |= SFL_TEXTURED | SFL_VISIBLE | SFL_CURVE; + side->surf = 0; + // + side = &brush->original_sides[1]; + if ( create_aas ) { + //the plane is expanded later so it's not a problem that + //these first two opposite sides are coplanar + side->planenum = planenum ^ 1; + } //end if + else + { + side->planenum = FindFloatPlane( mapplanes[planenum ^ 1].normal, mapplanes[planenum ^ 1].dist + 1 ); + side->flags |= SFL_TEXTURED | SFL_VISIBLE; + } //end else + side->contents = CONTENTS_SOLID; + side->flags |= SFL_CURVE; + side->surf = 0; + // + winding = BaseWindingForPlane( mapplanes[side->planenum].normal, mapplanes[side->planenum].dist ); + for ( n = 0; n < facet->numBorders; n++ ) + { + //never use the surface plane as a border + if ( facet->borderPlanes[n] == facet->surfacePlane ) { + continue; + } + // + side = &brush->original_sides[2 + n]; + side->planenum = FindFloatPlane( pc->planes[facet->borderPlanes[n]].plane, pc->planes[facet->borderPlanes[n]].plane[3] ); + if ( facet->borderInward[n] ) { + side->planenum ^= 1; + } + side->contents = CONTENTS_SOLID; + side->flags |= SFL_TEXTURED | SFL_CURVE; + side->surf = 0; + //chop the winding in place + if ( winding ) { + ChopWindingInPlace( &winding, mapplanes[side->planenum ^ 1].normal, mapplanes[side->planenum ^ 1].dist, 0.1 ); //CLIP_EPSILON); + } + } //end for + //VectorCopy(pc->bounds[0], brush->mins); + //VectorCopy(pc->bounds[1], brush->maxs); + if ( !winding ) { + Log_Print( "WARNING: AAS_CreateCurveBrushes: no winding\n" ); + brush->numsides = 0; + continue; + } //end if + brush->original_sides[0].winding = winding; + WindingBounds( winding, brush->mins, brush->maxs ); + for ( n = 0; n < 3; n++ ) + { + //IDBUG: all the indexes into the mins and maxs were zero (not using i) + if ( brush->mins[n] < -MAX_MAP_BOUNDS || brush->maxs[n] > MAX_MAP_BOUNDS ) { + Log_Print( "entity %i, brush %i: bounds out of range\n", brush->entitynum, brush->brushnum ); + Log_Print( "brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n] ); + brush->numsides = 0; //remove the brush + break; + } //end if + if ( brush->mins[n] > MAX_MAP_BOUNDS || brush->maxs[n] < -MAX_MAP_BOUNDS ) { + Log_Print( "entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum ); + Log_Print( "brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n] ); + brush->numsides = 0; //remove the brush + break; + } //end if + } //end for + if ( create_aas ) { + //NOTE: brush bevels now already added + //AddBrushBevels(brush); + AAS_CreateMapBrushes( brush, mapent, false ); + } //end if + else + { + // create windings for sides and bounds for brush + MakeBrushWindings( brush ); + AddBrushBevels( brush ); + nummapbrushes++; + mapent->numbrushes++; + } //end else + } //end for + } //end for + //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); + qprintf( "\r%6d curve brushes\n", numcurvebrushes ); +} //end of the function AAS_CreateCurveBrushes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ExpandMapBrush( mapbrush_t *brush, vec3_t mins, vec3_t maxs ); + +void Q3_LoadMapFromBSP( struct quakefile_s *qf ) { + int i; + vec3_t mins = {-1,-1,-1}, maxs = {1, 1, 1}; + + Log_Print( "-- Q3_LoadMapFromBSP --\n" ); + //loaded map type + loadedmaptype = MAPTYPE_QUAKE3; + + Log_Print( "Loading map from %s...\n", qf->filename ); + //load the bsp file + Q3_LoadBSPFile( qf ); + + //create an index from bsp planes to map planes + //DPlanes2MapPlanes(); + //clear brush model numbers + for ( i = 0; i < MAX_MAPFILE_BRUSHES; i++ ) + brushmodelnumbers[i] = -1; + + nummapbrushsides = 0; + num_entities = 0; + + Q3_ParseEntities(); + // + for ( i = 0; i < num_entities; i++ ) + { + Q3_ParseBSPEntity( i ); + } //end for + + AAS_CreateCurveBrushes(); + //get the map mins and maxs from the world model + ClearBounds( map_mins, map_maxs ); + for ( i = 0; i < entities[0].numbrushes; i++ ) + { + if ( mapbrushes[i].numsides <= 0 ) { + continue; + } + if ( mapbrushes[i].mins[0] > 4096 ) { + continue; //no valid points + } + AddPointToBounds( mapbrushes[i].mins, map_mins, map_maxs ); + AddPointToBounds( mapbrushes[i].maxs, map_mins, map_maxs ); + } //end for + /*/ + for (i = 0; i < nummapbrushes; i++) + { + //if (!mapbrushes[i].original_sides) continue; + //AddBrushBevels(&mapbrushes[i]); + //AAS_ExpandMapBrush(&mapbrushes[i], mins, maxs); + } //end for*/ + /* + for (i = 0; i < nummapbrushsides; i++) + { + Log_Write("side %d flags = %d", i, brushsides[i].flags); + } //end for + for (i = 0; i < nummapbrushes; i++) + { + Log_Write("brush contents: "); + PrintContents(mapbrushes[i].contents); + Log_Print("\n"); + } //end for*/ +} //end of the function Q3_LoadMapFromBSP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Q3_ResetMapLoading( void ) { + //reset for map loading from bsp + memset( nodestack, 0, NODESTACKSIZE * sizeof( int ) ); + nodestackptr = NULL; + nodestacksize = 0; + memset( brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof( int ) ); +} //end of the function Q3_ResetMapLoading + diff --git a/src/bspc/map_sin.c b/src/bspc/map_sin.c new file mode 100644 index 0000000..97e5fcf --- /dev/null +++ b/src/bspc/map_sin.c @@ -0,0 +1,1199 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//----------------------------------------------------------------------------- +// +// $Logfile:: /Wolfenstein MP/src/bspc/map_sin.c $ + +#include "qbsp.h" +#include "l_bsp_sin.h" +#include "aas_map.h" //AAS_CreateMapBrushes + + +//==================================================================== + + +/* +=========== +Sin_BrushContents +=========== +*/ + +int Sin_BrushContents( mapbrush_t *b ) { + int contents; + side_t *s; + int i; +#ifdef SIN + float trans = 0; +#else + int trans; +#endif + + s = &b->original_sides[0]; + contents = s->contents; + +#ifdef SIN + trans = sin_texinfo[s->texinfo].translucence; +#else + trans = texinfo[s->texinfo].flags; +#endif + for ( i = 1 ; i < b->numsides ; i++, s++ ) + { + s = &b->original_sides[i]; +#ifdef SIN + trans += sin_texinfo[s->texinfo].translucence; +#else + trans |= texinfo[s->texinfo].flags; +#endif + if ( s->contents != contents ) { +#ifdef SIN + if ( + ( s->contents & CONTENTS_DETAIL && !( contents & CONTENTS_DETAIL ) ) || + ( !( s->contents & CONTENTS_DETAIL ) && contents & CONTENTS_DETAIL ) + ) { + s->contents |= CONTENTS_DETAIL; + contents |= CONTENTS_DETAIL; + continue; + } +#endif + printf( "Entity %i, Brush %i: mixed face contents\n" + , b->entitynum, b->brushnum ); + break; + } + } + + +#ifdef SIN + if ( contents & CONTENTS_FENCE ) { +// contents |= CONTENTS_TRANSLUCENT; + contents |= CONTENTS_DETAIL; + contents |= CONTENTS_DUMMYFENCE; + contents &= ~CONTENTS_SOLID; + contents &= ~CONTENTS_FENCE; + contents |= CONTENTS_WINDOW; + } +#endif + + // if any side is translucent, mark the contents + // and change solid to window +#ifdef SIN + if ( trans > 0 ) +#else + if ( trans & ( SURF_TRANS33 | SURF_TRANS66 ) ) +#endif + { + contents |= CONTENTS_Q2TRANSLUCENT; + if ( contents & CONTENTS_SOLID ) { + contents &= ~CONTENTS_SOLID; + contents |= CONTENTS_WINDOW; + } + } + + return contents; +} //*/ + + +//============================================================================ + + + +/* +================= +ParseBrush +================= +* / +void ParseBrush (entity_t *mapent) +{ + mapbrush_t *b; + int i,j, k; + int mt; + side_t *side, *s2; + int planenum; + brush_texture_t td; +#ifdef SIN + textureref_t newref; +#endif + int planepts[3][3]; + + if (nummapbrushes == MAX_MAP_BRUSHES) + Error ("nummapbrushes == MAX_MAP_BRUSHES"); + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities-1; + b->brushnum = nummapbrushes - mapent->firstbrush; + + do + { + if (!GetToken (true)) + break; + if (!strcmp (token, "}") ) + break; + + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + side = &brushsides[nummapbrushsides]; + + // read the three point plane definition + for (i=0 ; i<3 ; i++) + { + if (i != 0) + GetToken (true); + if (strcmp (token, "(") ) + Error ("parsing brush"); + + for (j=0 ; j<3 ; j++) + { + GetToken (false); + planepts[i][j] = atoi(token); + } + + GetToken (false); + if (strcmp (token, ")") ) + Error ("parsing brush"); + + } + + + // + // read the texturedef + // + GetToken (false); + strcpy (td.name, token); + + GetToken (false); + td.shift[0] = atoi(token); + GetToken (false); + td.shift[1] = atoi(token); + GetToken (false); +#ifdef SIN + td.rotate = atof(token); +#else + td.rotate = atoi(token); +#endif + GetToken (false); + td.scale[0] = atof(token); + GetToken (false); + td.scale[1] = atof(token); + + // find default flags and values + mt = FindMiptex (td.name); +#ifdef SIN + // clear out the masks on newref + memset(&newref,0,sizeof(newref)); + // copy over the name + strcpy( newref.name, td.name ); + + ParseSurfaceInfo( &newref ); + MergeRefs( &bsp_textureref[mt], &newref, &td.tref ); + side->contents = td.tref.contents; + side->surf = td.tref.flags; +#else + td.flags = textureref[mt].flags; + td.value = textureref[mt].value; + side->contents = textureref[mt].contents; + side->surf = td.flags = textureref[mt].flags; + + if (TokenAvailable()) + { + GetToken (false); + side->contents = atoi(token); + GetToken (false); + side->surf = td.flags = atoi(token); + GetToken (false); + td.value = atoi(token); + } +#endif + + // translucent objects are automatically classified as detail +#ifdef SIN + if ( td.tref.translucence > 0 ) +#else + if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) +#endif + side->contents |= CONTENTS_DETAIL; + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + side->contents |= CONTENTS_DETAIL; + if (fulldetail) + side->contents &= ~CONTENTS_DETAIL; + if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) + | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) + side->contents |= CONTENTS_SOLID; + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; +#ifndef SIN // I think this is a bug of some kind + side->surf &= ~CONTENTS_DETAIL; +#endif + } + + // + // find the plane number + // + planenum = PlaneFromPoints (planepts[0], planepts[1], planepts[2]); + if (planenum == -1) + { + printf ("Entity %i, Brush %i: plane with no normal\n" + , b->entitynum, b->brushnum); + continue; + } + + // + // see if the plane has been used already + // + for (k=0 ; knumsides ; k++) + { + s2 = b->original_sides + k; + if (s2->planenum == planenum) + { + printf ("Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum); + break; + } + if ( s2->planenum == (planenum^1) ) + { + printf ("Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum); + break; + } + } + if (k != b->numsides) + continue; // duplicated + + // + // keep this side + // + + side = b->original_sides + b->numsides; + side->planenum = planenum; +#ifdef SIN + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], + &td, vec3_origin, &newref); + // + // save off lightinfo + // + side->lightinfo = LightinfoForBrushTexture ( &td ); +#else + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], + &td, vec3_origin); + +#endif + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + side_brushtextures[nummapbrushsides] = td; +#ifdef SIN + // save off the merged tref for animating textures + side_newrefs[nummapbrushsides] = newref; +#endif + + nummapbrushsides++; + b->numsides++; + } while (1); + + // get the content for the entire brush + b->contents = Sin_BrushContents (b); + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) ) + { + b->numsides = 0; + return; + } + + // allow water brushes to be removed + if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) + { + b->numsides = 0; + return; + } + + // create windings for sides and bounds for brush + MakeBrushWindings (b); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + c_clipbrushes++; + for (i=0 ; inumsides ; i++) + b->original_sides[i].texinfo = TEXINFO_NODE; + } + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + if (b->contents & CONTENTS_ORIGIN) + { + char string[32]; + vec3_t origin; + + if (num_entities == 1) + { + Error ("Entity %i, Brush %i: origin brushes not allowed in world" + , b->entitynum, b->brushnum); + return; + } + + VectorAdd (b->mins, b->maxs, origin); + VectorScale (origin, 0.5, origin); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue (&entities[b->entitynum], "origin", string); + + VectorCopy (origin, entities[b->entitynum].origin); + + // don't keep this brush + b->numsides = 0; + + return; + } + + AddBrushBevels (b); + + nummapbrushes++; + mapent->numbrushes++; +} //*/ + +/* +================ +MoveBrushesToWorld + +Takes all of the brushes from the current entity and +adds them to the world's brush list. + +Used by func_group and func_areaportal +================ +* / +void MoveBrushesToWorld (entity_t *mapent) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; inumbrushes = 0; +} //*/ + +/* +================ +ParseMapEntity +================ +* / +qboolean Sin_ParseMapEntity (void) +{ + entity_t *mapent; + epair_t *e; + side_t *s; + int i, j; + int startbrush, startsides; + vec_t newdist; + mapbrush_t *b; + + if (!GetToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[num_entities]; + num_entities++; + memset (mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; +// mapent->portalareas[0] = -1; +// mapent->portalareas[1] = -1; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + if (!strcmp (token, "{") ) + ParseBrush (mapent); + else + { + e = ParseEpair (); +#ifdef SIN + //HACK HACK HACK + // MED Gotta do this here + if ( !stricmp(e->key, "surfacefile") ) + { + if (!surfacefile[0]) + { + strcpy( surfacefile, e->value ); + } + printf ("--- ParseSurfaceFile ---\n"); + printf ("Surface script: %s\n", surfacefile); + if (!ParseSurfaceFile(surfacefile)) + { + Error ("Script file not found: %s\n", surfacefile); + } + } +#endif + e->next = mapent->epairs; + mapent->epairs = e; + } + } while (1); + +#ifdef SIN + if (!(strlen(ValueForKey(mapent, "origin"))) && ((num_entities-1) != 0)) + { + mapbrush_t *brush; + vec3_t origin; + char string[32]; + vec3_t mins, maxs; + int start, end; + // Calculate bounds + + start = mapent->firstbrush; + end = start + mapent->numbrushes; + ClearBounds (mins, maxs); + + for (j=start ; jnumsides) + continue; // not a real brush (origin brush) - shouldn't happen + AddPointToBounds (brush->mins, mins, maxs); + AddPointToBounds (brush->maxs, mins, maxs); + } + + // Set the origin to be the centroid of the entity. + VectorAdd ( mins, maxs, origin); + VectorScale( origin, 0.5f, origin ); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue ( mapent, "origin", string); +// qprintf("Setting origin to %s\n",string); + } +#endif + + GetVectorForKey (mapent, "origin", mapent->origin); + +#ifdef SIN + if ( + (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) || + (!strcmp ("func_group", ValueForKey (mapent, "classname"))) || + (!strcmp ("detail", ValueForKey (mapent, "classname")) && !entitydetails) + ) + { + VectorClear( mapent->origin ); + } +#endif + + // + // if there was an origin brush, offset all of the planes and texinfo + // + if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) + { + for (i=0 ; inumbrushes ; i++) + { + b = &mapbrushes[mapent->firstbrush + i]; + for (j=0 ; jnumsides ; j++) + { + s = &b->original_sides[j]; + newdist = mapplanes[s->planenum].dist - + DotProduct (mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); +#ifdef SIN + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], + &side_brushtextures[s-brushsides], mapent->origin, &side_newrefs[s-brushsides]); + // + // save off lightinfo + // + s->lightinfo = LightinfoForBrushTexture ( &side_brushtextures[s-brushsides] ); +#else + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], + &side_brushtextures[s-brushsides], mapent->origin); +#endif + } + MakeBrushWindings (b); + } + } + + // group entities are just for editor convenience + // toss all brushes into the world entity + if (!strcmp ("func_group", ValueForKey (mapent, "classname"))) + { + MoveBrushesToWorld (mapent); + mapent->numbrushes = 0; + mapent->wasdetail = true; + FreeValueKeys( mapent ); + return true; + } +#ifdef SIN + // detail entities are just for editor convenience + // toss all brushes into the world entity as detail brushes + if (!strcmp ("detail", ValueForKey (mapent, "classname")) && !entitydetails) + { + for (i=0 ; inumbrushes ; i++) + { + int j; + side_t * s; + b = &mapbrushes[mapent->firstbrush + i]; + if (nodetail) + { + b->numsides = 0; + continue; + } + if (!fulldetail) + { + // set the contents for the entire brush + b->contents |= CONTENTS_DETAIL; + // set the contents in the sides as well + for (j=0, s=b->original_sides ; jnumsides ; j++,s++) + { + s->contents |= CONTENTS_DETAIL; + } + } + else + { + // set the contents for the entire brush + b->contents |= CONTENTS_SOLID; + // set the contents in the sides as well + for (j=0, s=b->original_sides ; jnumsides ; j++,s++) + { + s->contents |= CONTENTS_SOLID; + } + } + } + MoveBrushesToWorld (mapent); + mapent->wasdetail = true; + FreeValueKeys( mapent ); + // kill off the entity + // num_entities--; + return true; + } +#endif + + // areaportal entities move their brushes, but don't eliminate + // the entity + if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) + { + char str[128]; + + if (mapent->numbrushes != 1) + Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); + + b = &mapbrushes[nummapbrushes-1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + // set the portal number as "style" + sprintf (str, "%i", c_areaportals); + SetKeyValue (mapent, "style", str); + MoveBrushesToWorld (mapent); + return true; + } + + return true; +} //end of the function Sin_ParseMapEntity */ + +//=================================================================== + +/* +================ +LoadMapFile +================ +* / +void Sin_LoadMapFile (char *filename) +{ + int i; +#ifdef SIN + int num_detailsides=0; + int num_detailbrushes=0; + int num_worldsides=0; + int num_worldbrushes=0; + int j,k; +#endif + + qprintf ("--- LoadMapFile ---\n"); + + LoadScriptFile (filename); + + nummapbrushsides = 0; + num_entities = 0; + + while (ParseMapEntity ()) + { + } + + ClearBounds (map_mins, map_maxs); + for (i=0 ; i 4096) + continue; // no valid points + AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); + AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); + } +#ifdef SIN + for (j=0; jnumsides && b->contents & CONTENTS_DETAIL) + num_detailbrushes++; + else if (b->numsides) + num_worldbrushes++; + for (k=0, s=b->original_sides ; knumsides ; k++,s++) + { + if (s->contents & CONTENTS_DETAIL) + num_detailsides++; + else + num_worldsides++; + } + } + } +#endif + + qprintf ("%5i brushes\n", nummapbrushes); + qprintf ("%5i clipbrushes\n", c_clipbrushes); + qprintf ("%5i total sides\n", nummapbrushsides); + qprintf ("%5i boxbevels\n", c_boxbevels); + qprintf ("%5i edgebevels\n", c_edgebevels); + qprintf ("%5i entities\n", num_entities); + qprintf ("%5i planes\n", nummapplanes); + qprintf ("%5i areaportals\n", c_areaportals); + qprintf ("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], + map_maxs[0],map_maxs[1],map_maxs[2]); +#ifdef SIN + qprintf ("%5i detailbrushes\n", num_detailbrushes); + qprintf ("%5i worldbrushes\n", num_worldbrushes); + qprintf ("%5i detailsides\n", num_detailsides); + qprintf ("%5i worldsides\n", num_worldsides); +#endif + +} //end of the function Sin_LoadMap */ + + +#ifdef ME //Begin MAP loading from BSP file +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_CreateMapTexinfo( void ) { + int i; + vec_t defaultvec[4] = {1, 0, 0, 0}; + + memcpy( map_texinfo[0].vecs[0], defaultvec, sizeof( defaultvec ) ); + memcpy( map_texinfo[0].vecs[1], defaultvec, sizeof( defaultvec ) ); + map_texinfo[0].flags = 0; + map_texinfo[0].value = 0; + strcpy( map_texinfo[0].texture, "generic/misc/red" ); //no texture + map_texinfo[0].nexttexinfo = -1; + for ( i = 1; i < sin_numtexinfo; i++ ) + { + memcpy( map_texinfo[i].vecs, sin_texinfo[i].vecs, sizeof( float ) * 2 * 4 ); + map_texinfo[i].flags = sin_texinfo[i].flags; + map_texinfo[i].value = 0; + strcpy( map_texinfo[i].texture, sin_texinfo[i].texture ); + map_texinfo[i].nexttexinfo = -1; + } //end for +} //end of the function Sin_CreateMapTexinfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_SetLeafBrushesModelNumbers( int leafnum, int modelnum ) { + int i, brushnum; + sin_dleaf_t *leaf; + + leaf = &sin_dleafs[leafnum]; + for ( i = 0; i < leaf->numleafbrushes; i++ ) + { + brushnum = sin_dleafbrushes[leaf->firstleafbrush + i]; + brushmodelnumbers[brushnum] = modelnum; + dbrushleafnums[brushnum] = leafnum; + } //end for +} //end of the function Sin_SetLeafBrushesModelNumbers +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_InitNodeStack( void ) { + nodestackptr = nodestack; + nodestacksize = 0; +} //end of the function Sin_InitNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_PushNodeStack( int num ) { + *nodestackptr = num; + nodestackptr++; + nodestacksize++; + // + if ( nodestackptr >= &nodestack[NODESTACKSIZE] ) { + Error( "Sin_PushNodeStack: stack overflow\n" ); + } //end if +} //end of the function Sin_PushNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sin_PopNodeStack( void ) { + //if the stack is empty + if ( nodestackptr <= nodestack ) { + return -1; + } + //decrease stack pointer + nodestackptr--; + nodestacksize--; + //return the top value from the stack + return *nodestackptr; +} //end of the function Sin_PopNodeStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_SetBrushModelNumbers( entity_t *mapent ) { + int n, pn; + int leafnum; + + // + Sin_InitNodeStack(); + //head node (root) of the bsp tree + n = sin_dmodels[mapent->modelnum].headnode; + pn = 0; + + do + { + //if we are in a leaf (negative node number) + if ( n < 0 ) { + //number of the leaf + leafnum = ( -n ) - 1; + //set the brush numbers + Sin_SetLeafBrushesModelNumbers( leafnum, mapent->modelnum ); + //walk back into the tree to find a second child to continue with + for ( pn = Sin_PopNodeStack(); pn >= 0; n = pn, pn = Sin_PopNodeStack() ) + { + //if we took the first child at the parent node + if ( sin_dnodes[pn].children[0] == n ) { + break; + } + } //end for + //if the stack wasn't empty (if not processed whole tree) + if ( pn >= 0 ) { + //push the parent node again + Sin_PushNodeStack( pn ); + //we proceed with the second child of the parent node + n = sin_dnodes[pn].children[1]; + } //end if + } //end if + else + { + //push the current node onto the stack + Sin_PushNodeStack( n ); + //walk forward into the tree to the first child + n = sin_dnodes[n].children[0]; + } //end else + } while ( pn >= 0 ); +} //end of the function Sin_SetBrushModelNumbers +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_BSPBrushToMapBrush( sin_dbrush_t *bspbrush, entity_t *mapent ) { + mapbrush_t *b; + int i, k, n; + side_t *side, *s2; + int planenum; + sin_dbrushside_t *bspbrushside; + sin_dplane_t *bspplane; + + if ( nummapbrushes >= MAX_MAPFILE_BRUSHES ) { + Error( "nummapbrushes >= MAX_MAPFILE_BRUSHES" ); + } + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = mapent - entities; + b->brushnum = nummapbrushes - mapent->firstbrush; + b->leafnum = dbrushleafnums[bspbrush - sin_dbrushes]; + + for ( n = 0; n < bspbrush->numsides; n++ ) + { + //pointer to the bsp brush side + bspbrushside = &sin_dbrushsides[bspbrush->firstside + n]; + + if ( nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES ) { + Error( "MAX_MAPFILE_BRUSHSIDES" ); + } //end if + //pointer to the map brush side + side = &brushsides[nummapbrushsides]; + //if the BSP brush side is textured + if ( sin_dbrushsidetextured[bspbrush->firstside + n] ) { + side->flags |= SFL_TEXTURED; + } else { side->flags &= ~SFL_TEXTURED;} + //ME: can get side contents and surf directly from BSP file + side->contents = bspbrush->contents; + //if the texinfo is TEXINFO_NODE + if ( bspbrushside->texinfo < 0 ) { + side->surf = 0; + } else { side->surf = sin_texinfo[bspbrushside->texinfo].flags;} + + // translucent objects are automatically classified as detail + if ( side->surf & ( SURF_TRANS33 | SURF_TRANS66 ) ) { + side->contents |= CONTENTS_DETAIL; + } + if ( side->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) { + side->contents |= CONTENTS_DETAIL; + } + if ( fulldetail ) { + side->contents &= ~CONTENTS_DETAIL; + } + if ( !( side->contents & ( ( LAST_VISIBLE_CONTENTS - 1 ) + | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP | CONTENTS_MIST ) ) ) { + side->contents |= CONTENTS_SOLID; + } + + // hints and skips are never detail, and have no content + if ( side->surf & ( SURF_HINT | SURF_SKIP ) ) { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + } + + //ME: get a plane for this side + bspplane = &sin_dplanes[bspbrushside->planenum]; + planenum = FindFloatPlane( bspplane->normal, bspplane->dist ); + // + // see if the plane has been used already + // + //ME: this really shouldn't happen!!! + //ME: otherwise the bsp file is corrupted?? + //ME: still it seems to happen, maybe Johny Boy's + //ME: brush bevel adding is crappy ? + for ( k = 0; k < b->numsides; k++ ) + { + s2 = b->original_sides + k; + if ( s2->planenum == planenum ) { + Log_Print( "Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum ); + break; + } + if ( s2->planenum == ( planenum ^ 1 ) ) { + Log_Print( "Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum ); + break; + } + } + if ( k != b->numsides ) { + continue; // duplicated + + } + // + // keep this side + // + //ME: reset pointer to side, why? hell I dunno (pointer is set above already) + side = b->original_sides + b->numsides; + //ME: store the plane number + side->planenum = planenum; + //ME: texinfo is already stored when bsp is loaded + //NOTE: check for TEXINFO_NODE, otherwise crash in Sin_BrushContents + if ( bspbrushside->texinfo < 0 ) { + side->texinfo = 0; + } else { side->texinfo = bspbrushside->texinfo;} + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + // ME: don't need to recalculate because it's already done + // (for non-world entities) in the BSP file +// side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } //end for + + // get the content for the entire brush + b->contents = bspbrush->contents; + Sin_BrushContents( b ); + + if ( BrushExists( b ) ) { + c_squattbrushes++; + b->numsides = 0; + return; + } //end if + + //if we're creating AAS + if ( create_aas ) { + //create the AAS brushes from this brush, don't add brush bevels + AAS_CreateMapBrushes( b, mapent, false ); + return; + } //end if + + // allow detail brushes to be removed + if ( nodetail && ( b->contents & CONTENTS_DETAIL ) ) { + b->numsides = 0; + return; + } //end if + + // allow water brushes to be removed + if ( nowater && ( b->contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { + b->numsides = 0; + return; + } //end if + + // create windings for sides and bounds for brush + MakeBrushWindings( b ); + + //mark brushes without winding or with a tiny window as bevels + MarkBrushBevels( b ); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if ( b->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) { + c_clipbrushes++; + for ( i = 0; i < b->numsides; i++ ) + b->original_sides[i].texinfo = TEXINFO_NODE; + } //end for + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + //ME: not needed because the entities in the BSP file already + // have an origin set +// if (b->contents & CONTENTS_ORIGIN) +// { +// char string[32]; +// vec3_t origin; +// +// if (num_entities == 1) +// { +// Error ("Entity %i, Brush %i: origin brushes not allowed in world" +// , b->entitynum, b->brushnum); +// return; +// } +// +// VectorAdd (b->mins, b->maxs, origin); +// VectorScale (origin, 0.5, origin); +// +// sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); +// SetKeyValue (&entities[b->entitynum], "origin", string); +// +// VectorCopy (origin, entities[b->entitynum].origin); +// +// // don't keep this brush +// b->numsides = 0; +// +// return; +// } + + //ME: the bsp brushes already have bevels, so we won't try to + // add them again (especially since Johny Boy's bevel adding might + // be crappy) +// AddBrushBevels(b); + + nummapbrushes++; + mapent->numbrushes++; +} //end of the function Sin_BSPBrushToMapBrush +//=========================================================================== +//=========================================================================== +void Sin_ParseBSPBrushes( entity_t *mapent ) { + int i, testnum = 0; + + //give all the brushes that belong to this entity the number of the + //BSP model used by this entity + Sin_SetBrushModelNumbers( mapent ); + //now parse all the brushes with the correct mapent->modelnum + for ( i = 0; i < sin_numbrushes; i++ ) + { + if ( brushmodelnumbers[i] == mapent->modelnum ) { + testnum++; + Sin_BSPBrushToMapBrush( &sin_dbrushes[i], mapent ); + } //end if + } //end for +} //end of the function Sin_ParseBSPBrushes +//=========================================================================== +//=========================================================================== +qboolean Sin_ParseBSPEntity( int entnum ) { + entity_t *mapent; + char *model; + int startbrush, startsides; + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[entnum]; //num_entities]; + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + mapent->modelnum = -1; //-1 = no model + + model = ValueForKey( mapent, "model" ); + if ( model && *model == '*' ) { + mapent->modelnum = atoi( &model[1] ); + //Log_Print("model = %s\n", model); + //Log_Print("mapent->modelnum = %d\n", mapent->modelnum); + } //end if + + GetVectorForKey( mapent, "origin", mapent->origin ); + + //if this is the world entity it has model number zero + //the world entity has no model key + if ( !strcmp( "worldspawn", ValueForKey( mapent, "classname" ) ) ) { + mapent->modelnum = 0; + } //end if + //if the map entity has a BSP model (a modelnum of -1 is used for + //entities that aren't using a BSP model) + if ( mapent->modelnum >= 0 ) { + //parse the bsp brushes + Sin_ParseBSPBrushes( mapent ); + } //end if + // + //the origin of the entity is already taken into account + // + //func_group entities can't be in the bsp file + // + //check out the func_areaportal entities + if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) { + c_areaportals++; + mapent->areaportalnum = c_areaportals; + return true; + } //end if + return true; +} //end of the function Sin_ParseBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Sin_LoadMapFromBSP( char *filename, int offset, int length ) { + int i; + + Log_Print( "-- Sin_LoadMapFromBSP --\n" ); + //loaded map type + loadedmaptype = MAPTYPE_SIN; + + Log_Print( "Loading map from %s...\n", filename ); + //load the bsp file + Sin_LoadBSPFile( filename, offset, length ); + + //create an index from bsp planes to map planes + //DPlanes2MapPlanes(); + //clear brush model numbers + for ( i = 0; i < MAX_MAPFILE_BRUSHES; i++ ) + brushmodelnumbers[i] = -1; + + nummapbrushsides = 0; + num_entities = 0; + + Sin_ParseEntities(); + // + for ( i = 0; i < num_entities; i++ ) + { + Sin_ParseBSPEntity( i ); + } //end for + + //get the map mins and maxs from the world model + ClearBounds( map_mins, map_maxs ); + for ( i = 0; i < entities[0].numbrushes; i++ ) + { + if ( mapbrushes[i].mins[0] > 4096 ) { + continue; //no valid points + } + AddPointToBounds( mapbrushes[i].mins, map_mins, map_maxs ); + AddPointToBounds( mapbrushes[i].maxs, map_mins, map_maxs ); + } //end for + // + Sin_CreateMapTexinfo(); +} //end of the function Sin_LoadMapFromBSP + +void Sin_ResetMapLoading( void ) { + //reset for map loading from bsp + memset( nodestack, 0, NODESTACKSIZE * sizeof( int ) ); + nodestackptr = NULL; + nodestacksize = 0; + memset( brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof( int ) ); +} //end of the function Sin_ResetMapLoading + +//End MAP loading from BSP file + +#endif //ME diff --git a/src/bspc/nodraw.c b/src/bspc/nodraw.c new file mode 100644 index 0000000..a6117f0 --- /dev/null +++ b/src/bspc/nodraw.c @@ -0,0 +1,50 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "qbsp.h" + +vec3_t draw_mins, draw_maxs; +qboolean drawflag; + +void Draw_ClearWindow( void ) { +} + +//============================================================ + +#define GLSERV_PORT 25001 + + +void GLS_BeginScene( void ) { +} + +void GLS_Winding( winding_t *w, int code ) { +} + +void GLS_EndScene( void ) { +} diff --git a/src/bspc/portals.c b/src/bspc/portals.c new file mode 100644 index 0000000..f182fd1 --- /dev/null +++ b/src/bspc/portals.c @@ -0,0 +1,1308 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: Portals +// Function: partalizing a bsp tree, flooding through portals +// Programmer: id Software & Mr Elusive (MrElusive@worldentity.com) +// Last update: 1999-08-10 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "l_mem.h" + +int c_active_portals; +int c_peak_portals; +int c_boundary; +int c_boundary_sides; +int c_portalmemory; + +//portal_t *portallist = NULL; +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +portal_t *AllocPortal( void ) { + portal_t *p; + + p = GetMemory( sizeof( portal_t ) ); + memset( p, 0, sizeof( portal_t ) ); + + if ( numthreads == 1 ) { + c_active_portals++; + if ( c_active_portals > c_peak_portals ) { + c_peak_portals = c_active_portals; + } //end if + c_portalmemory += MemorySize( p ); + } //end if + +// p->nextportal = portallist; +// portallist = p; + + return p; +} //end of the function AllocPortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreePortal( portal_t *p ) { + if ( p->winding ) { + FreeWinding( p->winding ); + } + if ( numthreads == 1 ) { + c_active_portals--; + c_portalmemory -= MemorySize( p ); + } //end if + FreeMemory( p ); +} //end of the function FreePortal +//=========================================================================== +// Returns the single content bit of the +// strongest visible content present +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int VisibleContents( int contents ) { + int i; + + for ( i = 1 ; i <= LAST_VISIBLE_CONTENTS ; i <<= 1 ) + if ( contents & i ) { + return i; + } + + return 0; +} //end of the function VisibleContents +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ClusterContents( node_t *node ) { + int c1, c2, c; + + if ( node->planenum == PLANENUM_LEAF ) { + return node->contents; + } + + c1 = ClusterContents( node->children[0] ); + c2 = ClusterContents( node->children[1] ); + c = c1 | c2; + + // a cluster may include some solid detail areas, but + // still be seen into + if ( !( c1 & CONTENTS_SOLID ) || !( c2 & CONTENTS_SOLID ) ) { + c &= ~CONTENTS_SOLID; + } + return c; +} //end of the function ClusterContents + +//=========================================================================== +// Returns true if the portal is empty or translucent, allowing +// the PVS calculation to see through it. +// The nodes on either side of the portal may actually be clusters, +// not leaves, so all contents should be ored together +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean Portal_VisFlood( portal_t *p ) { + int c1, c2; + + if ( !p->onnode ) { + return false; // to global outsideleaf + + } + c1 = ClusterContents( p->nodes[0] ); + c2 = ClusterContents( p->nodes[1] ); + + if ( !VisibleContents( c1 ^ c2 ) ) { + return true; + } + + if ( c1 & ( CONTENTS_Q2TRANSLUCENT | CONTENTS_DETAIL ) ) { + c1 = 0; + } + if ( c2 & ( CONTENTS_Q2TRANSLUCENT | CONTENTS_DETAIL ) ) { + c2 = 0; + } + + if ( ( c1 | c2 ) & CONTENTS_SOLID ) { + return false; // can't see through solid + + } + if ( !( c1 ^ c2 ) ) { + return true; // identical on both sides + + } + if ( !VisibleContents( c1 ^ c2 ) ) { + return true; + } + return false; +} //end of the function Portal_VisFlood +//=========================================================================== +// The entity flood determines which areas are +// "outside" on the map, which are then filled in. +// Flowing from side s to side !s +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean Portal_EntityFlood( portal_t *p, int s ) { + if ( p->nodes[0]->planenum != PLANENUM_LEAF + || p->nodes[1]->planenum != PLANENUM_LEAF ) { + Error( "Portal_EntityFlood: not a leaf" ); + } + + // can never cross to a solid + if ( ( p->nodes[0]->contents & CONTENTS_SOLID ) + || ( p->nodes[1]->contents & CONTENTS_SOLID ) ) { + return false; + } + + // can flood through everything else + return true; +} + + +//============================================================================= + +int c_tinyportals; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddPortalToNodes( portal_t *p, node_t *front, node_t *back ) { + if ( p->nodes[0] || p->nodes[1] ) { + Error( "AddPortalToNode: allready included" ); + } + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} //end of the function AddPortalToNodes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemovePortalFromNode( portal_t *portal, node_t *l ) { + portal_t **pp, *t; + + int s, i, n; + portal_t *p; + portal_t *portals[4096]; + +// remove reference to the current portal + pp = &l->portals; + while ( 1 ) + { + t = *pp; + if ( !t ) { + Error( "RemovePortalFromNode: portal not in leaf" ); + } + + if ( t == portal ) { + break; + } + + if ( t->nodes[0] == l ) { + pp = &t->next[0]; + } else if ( t->nodes[1] == l ) { + pp = &t->next[1]; + } else { + Error( "RemovePortalFromNode: portal not bounding leaf" ); + } + } + + if ( portal->nodes[0] == l ) { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } //end if + else if ( portal->nodes[1] == l ) { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } //end else if + else + { + Error( "RemovePortalFromNode: mislinked portal" ); + } //end else +//#ifdef ME + n = 0; + for ( p = l->portals; p; p = p->next[s] ) + { + for ( i = 0; i < n; i++ ) + { + if ( p == portals[i] ) { + Error( "RemovePortalFromNode: circular linked\n" ); + } + } //end for + if ( p->nodes[0] != l && p->nodes[1] != l ) { + Error( "RemovePortalFromNodes: portal does not belong to node\n" ); + } //end if + portals[n] = p; + s = ( p->nodes[1] == l ); +// if (++n >= 4096) Error("RemovePortalFromNode: more than 4096 portals\n"); + } //end for +//#endif +} //end of the function RemovePortalFromNode +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintPortal( portal_t *p ) { + int i; + winding_t *w; + + w = p->winding; + for ( i = 0 ; i < w->numpoints ; i++ ) + printf( "(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] + , w->p[i][1], w->p[i][2] ); +} //end of the function PrintPortal +//=========================================================================== +// The created portals will face the global outside_node +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define SIDESPACE 8 + +void MakeHeadnodePortals( tree_t *tree ) { + vec3_t bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leaves + for ( i = 0 ; i < 3 ; i++ ) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.contents = 0; + + for ( i = 0 ; i < 3 ; i++ ) + for ( j = 0 ; j < 2 ; j++ ) + { + n = j * 3 + i; + + p = AllocPortal(); + portals[n] = p; + + pl = &bplanes[n]; + memset( pl, 0, sizeof( *pl ) ); + if ( j ) { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane( pl->normal, pl->dist ); + AddPortalToNodes( p, node, &tree->outside_node ); + } + +// clip the basewindings by all the other planes + for ( i = 0 ; i < 6 ; i++ ) + { + for ( j = 0 ; j < 6 ; j++ ) + { + if ( j == i ) { + continue; + } + ChopWindingInPlace( &portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON ); + } //end for + } //end for +} //end of the function MakeHeadNodePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define BASE_WINDING_EPSILON 0.001 +#define SPLIT_WINDING_EPSILON 0.001 + +winding_t *BaseWindingForNode( node_t *node ) { + winding_t *w; + node_t *n; + plane_t *plane; + vec3_t normal; + vec_t dist; + + w = BaseWindingForPlane( mapplanes[node->planenum].normal + , mapplanes[node->planenum].dist ); + + // clip by all the parents + for ( n = node->parent ; n && w ; ) + { + plane = &mapplanes[n->planenum]; + + if ( n->children[0] == node ) { // take front + ChopWindingInPlace( &w, plane->normal, plane->dist, BASE_WINDING_EPSILON ); + } else + { // take back + VectorSubtract( vec3_origin, plane->normal, normal ); + dist = -plane->dist; + ChopWindingInPlace( &w, normal, dist, BASE_WINDING_EPSILON ); + } + node = n; + n = n->parent; + } + + return w; +} //end of the function BaseWindingForNode +//=========================================================================== +// create the new portal by taking the full plane winding for the cutting +// plane and clipping it by all of parents of this node +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WindingIsTiny( winding_t *w ); + +void MakeNodePortal( node_t *node ) { + portal_t *new_portal, *p; + winding_t *w; + vec3_t normal; + float dist; + int side; + + w = BaseWindingForNode( node ); + + // clip the portal by all the other portals in the node + for ( p = node->portals; p && w; p = p->next[side] ) + { + if ( p->nodes[0] == node ) { + side = 0; + VectorCopy( p->plane.normal, normal ); + dist = p->plane.dist; + } //end if + else if ( p->nodes[1] == node ) { + side = 1; + VectorSubtract( vec3_origin, p->plane.normal, normal ); + dist = -p->plane.dist; + } //end else if + else + { + Error( "MakeNodePortal: mislinked portal" ); + } //end else + ChopWindingInPlace( &w, normal, dist, 0.1 ); + } //end for + + if ( !w ) { + return; + } //end if + + if ( WindingIsTiny( w ) ) { + c_tinyportals++; + FreeWinding( w ); + return; + } //end if + +#ifdef DEBUG +/* //NOTE: don't use this winding ok check + // all the invalid windings only have a degenerate edge + if (WindingError(w)) + { + Log_Print("MakeNodePortal: %s\n", WindingErrorString()); + FreeWinding(w); + return; + } //end if*/ +#endif //DEBUG + + + new_portal = AllocPortal(); + new_portal->plane = mapplanes[node->planenum]; + +#ifdef ME + new_portal->planenum = node->planenum; +#endif //ME + + new_portal->onnode = node; + new_portal->winding = w; + AddPortalToNodes( new_portal, node->children[0], node->children[1] ); +} //end of the function MakeNodePortal +//=========================================================================== +// Move or split the portals that bound node so that the node's +// children have portals instead of node. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SplitNodePortals( node_t *node ) { + portal_t *p, *next_portal, *new_portal; + node_t *f, *b, *other_node; + int side; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for ( p = node->portals ; p ; p = next_portal ) + { + if ( p->nodes[0] == node ) { + side = 0; + } else if ( p->nodes[1] == node ) { + side = 1; + } else { Error( "SplitNodePortals: mislinked portal" );} + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode( p, p->nodes[0] ); + RemovePortalFromNode( p, p->nodes[1] ); + +// +// cut the portal into two portals, one on each side of the cut plane +// + ClipWindingEpsilon( p->winding, plane->normal, plane->dist, + SPLIT_WINDING_EPSILON, &frontwinding, &backwinding ); + + if ( frontwinding && WindingIsTiny( frontwinding ) ) { + FreeWinding( frontwinding ); + frontwinding = NULL; + c_tinyportals++; + } //end if + + if ( backwinding && WindingIsTiny( backwinding ) ) { + FreeWinding( backwinding ); + backwinding = NULL; + c_tinyportals++; + } //end if + +#ifdef DEBUG +/* //NOTE: don't use this winding ok check + // all the invalid windings only have a degenerate edge + if (frontwinding && WindingError(frontwinding)) + { + Log_Print("SplitNodePortals: front %s\n", WindingErrorString()); + FreeWinding(frontwinding); + frontwinding = NULL; + } //end if + if (backwinding && WindingError(backwinding)) + { + Log_Print("SplitNodePortals: back %s\n", WindingErrorString()); + FreeWinding(backwinding); + backwinding = NULL; + } //end if*/ +#endif //DEBUG + + if ( !frontwinding && !backwinding ) { // tiny windings on both sides + continue; + } + + if ( !frontwinding ) { + FreeWinding( backwinding ); + if ( side == 0 ) { + AddPortalToNodes( p, b, other_node ); + } else { AddPortalToNodes( p, other_node, b );} + continue; + } + if ( !backwinding ) { + FreeWinding( frontwinding ); + if ( side == 0 ) { + AddPortalToNodes( p, f, other_node ); + } else { AddPortalToNodes( p, other_node, f );} + continue; + } + + // the winding is split + new_portal = AllocPortal(); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding( p->winding ); + p->winding = frontwinding; + + if ( side == 0 ) { + AddPortalToNodes( p, f, other_node ); + AddPortalToNodes( new_portal, b, other_node ); + } //end if + else + { + AddPortalToNodes( p, other_node, f ); + AddPortalToNodes( new_portal, other_node, b ); + } //end else + } + + node->portals = NULL; +} //end of the function SplitNodePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CalcNodeBounds( node_t *node ) { + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leaves and nodes + ClearBounds( node->mins, node->maxs ); + for ( p = node->portals ; p ; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + for ( i = 0 ; i < p->winding->numpoints ; i++ ) + AddPointToBounds( p->winding->p[i], node->mins, node->maxs ); + } +} //end of the function CalcNodeBounds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int c_numportalizednodes; + +void MakeTreePortals_r( node_t *node ) { + int i; + +#ifdef ME + qprintf( "\r%6d", ++c_numportalizednodes ); + if ( cancelconversion ) { + return; + } +#endif //ME + + CalcNodeBounds( node ); + if ( node->mins[0] >= node->maxs[0] ) { + Log_Print( "WARNING: node without a volume\n" ); + } + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( node->mins[i] < -MAX_MAP_BOUNDS || node->maxs[i] > MAX_MAP_BOUNDS ) { + Log_Print( "WARNING: node with unbounded volume\n" ); + break; + } + } + if ( node->planenum == PLANENUM_LEAF ) { + return; + } + + MakeNodePortal( node ); + SplitNodePortals( node ); + + MakeTreePortals_r( node->children[0] ); + MakeTreePortals_r( node->children[1] ); +} //end of the function MakeTreePortals_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MakeTreePortals( tree_t *tree ) { + +#ifdef ME + Log_Print( "---- Node Portalization ----\n" ); + c_numportalizednodes = 0; + c_portalmemory = 0; + qprintf( "%6d nodes portalized", c_numportalizednodes ); +#endif //ME + + MakeHeadnodePortals( tree ); + MakeTreePortals_r( tree->headnode ); + +#ifdef ME + qprintf( "\n" ); + Log_Write( "\r%6d nodes portalized\r\n", c_numportalizednodes ); + Log_Print( "%6d tiny portals\r\n", c_tinyportals ); + Log_Print( "%6d KB of portal memory\r\n", c_portalmemory >> 10 ); + Log_Print( "%6i KB of winding memory\r\n", WindingMemory() >> 10 ); +#endif //ME +} //end of the function MakeTreePortals + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ +//#define P_NODESTACK + +node_t *p_firstnode; +node_t *p_lastnode; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef P_NODESTACK +void P_AddNodeToList( node_t *node ) { + node->next = p_firstnode; + p_firstnode = node; + if ( !p_lastnode ) { + p_lastnode = node; + } +} //end of the function P_AddNodeToList +#else //it's a node queue +//add the node to the end of the node list +void P_AddNodeToList( node_t *node ) { + node->next = NULL; + if ( p_lastnode ) { + p_lastnode->next = node; + } else { p_firstnode = node;} + p_lastnode = node; +} //end of the function P_AddNodeToList +#endif //P_NODESTACK +//=========================================================================== +// get the first node from the front of the node list +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *P_NextNodeFromList( void ) { + node_t *node; + + node = p_firstnode; + if ( p_firstnode ) { + p_firstnode = p_firstnode->next; + } + if ( !p_firstnode ) { + p_lastnode = NULL; + } + return node; +} //end of the function P_NextNodeFromList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FloodPortals( node_t *firstnode ) { + node_t *node; + portal_t *p; + int s; + + firstnode->occupied = 1; + P_AddNodeToList( firstnode ); + + for ( node = P_NextNodeFromList(); node; node = P_NextNodeFromList() ) + { + for ( p = node->portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + //if the node at the other side of the portal is occupied already + if ( p->nodes[!s]->occupied ) { + continue; + } + //if it isn't possible to flood through this portal + if ( !Portal_EntityFlood( p, s ) ) { + continue; + } + // + p->nodes[!s]->occupied = node->occupied + 1; + // + P_AddNodeToList( p->nodes[!s] ); + } //end for + } //end for +} //end of the function FloodPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int numrec; + +void FloodPortals_r( node_t *node, int dist ) { + portal_t *p; + int s; +// int i; + + Log_Print( "\r%6d", ++numrec ); + + if ( node->occupied ) { + Error( "FloodPortals_r: node already occupied\n" ); + } + if ( !node ) { + Error( "FloodPortals_r: NULL node\n" ); + } //end if*/ + node->occupied = dist; + + for ( p = node->portals; p; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + //if the node at the other side of the portal is occupied already + if ( p->nodes[!s]->occupied ) { + continue; + } + //if it isn't possible to flood through this portal + if ( !Portal_EntityFlood( p, s ) ) { + continue; + } + //flood recursively through the current portal + FloodPortals_r( p->nodes[!s], dist + 1 ); + } //end for + Log_Print( "\r%6d", --numrec ); +} //end of the function FloodPortals_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean PlaceOccupant( node_t *headnode, vec3_t origin, entity_t *occupant ) { + node_t *node; + vec_t d; + plane_t *plane; + + //find the leaf to start in + node = headnode; + while ( node->planenum != PLANENUM_LEAF ) + { + if ( node->planenum < 0 || node->planenum > nummapplanes ) { + Error( "PlaceOccupant: invalid node->planenum\n" ); + } //end if + plane = &mapplanes[node->planenum]; + d = DotProduct( origin, plane->normal ) - plane->dist; + if ( d >= 0 ) { + node = node->children[0]; + } else { node = node->children[1];} + if ( !node ) { + Error( "PlaceOccupant: invalid child %d\n", d < 0 ); + } //end if + } //end while + //don't start in solid +// if (node->contents == CONTENTS_SOLID) + //ME: replaced because in LeafNode in brushbsp.c + // some nodes have contents solid with other contents + if ( node->contents & CONTENTS_SOLID ) { + return false; + } + //if the node is already occupied + if ( node->occupied ) { + return false; + } + + //place the occupant in the first leaf + node->occupant = occupant; + + numrec = 0; +// Log_Print("%6d recurses", numrec); +// FloodPortals_r(node, 1); +// Log_Print("\n"); + FloodPortals( node ); + + return true; +} //end of the function PlaceOccupant +//=========================================================================== +// Marks all nodes that can be reached by entites +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean FloodEntities( tree_t *tree ) { + int i; + int x, y; + vec3_t origin; + char *cl; + qboolean inside; + node_t *headnode; + + headnode = tree->headnode; + Log_Print( "------ FloodEntities -------\n" ); + inside = false; + tree->outside_node.occupied = 0; + + //start at entity 1 not the world ( = 0) + for ( i = 1; i < num_entities; i++ ) + { + GetVectorForKey( &entities[i], "origin", origin ); + if ( VectorCompare( origin, vec3_origin ) ) { + continue; + } + + cl = ValueForKey( &entities[i], "classname" ); + origin[2] += 1; //so objects on floor are ok + +// Log_Print("flooding from entity %d: %s\n", i, cl); + //nudge playerstart around if needed so clipping hulls allways + //have a valid point + if ( !strcmp( cl, "info_player_start" ) ) { + for ( x = -16; x <= 16; x += 16 ) + { + for ( y = -16; y <= 16; y += 16 ) + { + origin[0] += x; + origin[1] += y; + if ( PlaceOccupant( headnode, origin, &entities[i] ) ) { + inside = true; + x = 999; //stop for this info_player_start + break; + } //end if + origin[0] -= x; + origin[1] -= y; + } //end for + } //end for + } //end if + else + { + if ( PlaceOccupant( headnode, origin, &entities[i] ) ) { + inside = true; + } //end if + } //end else + } //end for + + if ( !inside ) { + Log_Print( "WARNING: no entities inside\n" ); + } //end if + else if ( tree->outside_node.occupied ) { + Log_Print( "WARNING: entity reached from outside\n" ); + } //end else if + + return (qboolean)( inside && !tree->outside_node.occupied ); +} //end of the function FloodEntities + +/* +========================================================= + +FILL OUTSIDE + +========================================================= +*/ + +int c_outside; +int c_inside; +int c_solid; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FillOutside_r( node_t *node ) { + if ( node->planenum != PLANENUM_LEAF ) { + FillOutside_r( node->children[0] ); + FillOutside_r( node->children[1] ); + return; + } //end if + // anything not reachable by an entity + // can be filled away (by setting solid contents) + if ( !node->occupied ) { + if ( !( node->contents & CONTENTS_SOLID ) ) { + c_outside++; + node->contents |= CONTENTS_SOLID; + } //end if + else + { + c_solid++; + } //end else + } //end if + else + { + c_inside++; + } //end else +} //end of the function FillOutside_r +//=========================================================================== +// Fill all nodes that can't be reached by entities +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FillOutside( node_t *headnode ) { + c_outside = 0; + c_inside = 0; + c_solid = 0; + Log_Print( "------- FillOutside --------\n" ); + FillOutside_r( headnode ); + Log_Print( "%5i solid leaves\n", c_solid ); + Log_Print( "%5i leaves filled\n", c_outside ); + Log_Print( "%5i inside leaves\n", c_inside ); +} //end of the function FillOutside + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +int c_areas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FloodAreas_r( node_t *node ) { + portal_t *p; + int s; + bspbrush_t *b; + entity_t *e; + + if ( node->contents == CONTENTS_AREAPORTAL ) { + // this node is part of an area portal + b = node->brushlist; + e = &entities[b->original->entitynum]; + + // if the current area has allready touched this + // portal, we are done + if ( e->portalareas[0] == c_areas || e->portalareas[1] == c_areas ) { + return; + } + + // note the current area as bounding the portal + if ( e->portalareas[1] ) { + Log_Print( "WARNING: areaportal entity %i touches > 2 areas\n", b->original->entitynum ); + return; + } + if ( e->portalareas[0] ) { + e->portalareas[1] = c_areas; + } else { + e->portalareas[0] = c_areas; + } + + return; + } //end if + + if ( node->area ) { + return; // allready got it + } + node->area = c_areas; + + for ( p = node->portals ; p ; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); +#if 0 + if ( p->nodes[!s]->occupied ) { + continue; + } +#endif + if ( !Portal_EntityFlood( p, s ) ) { + continue; + } + + FloodAreas_r( p->nodes[!s] ); + } //end for +} //end of the function FloodAreas_r +//=========================================================================== +// Just decend the tree, and for each node that hasn't had an +// area set, flood fill out from there +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FindAreas_r( node_t *node ) { + if ( node->planenum != PLANENUM_LEAF ) { + FindAreas_r( node->children[0] ); + FindAreas_r( node->children[1] ); + return; + } + + if ( node->area ) { + return; // allready got it + + } + if ( node->contents & CONTENTS_SOLID ) { + return; + } + + if ( !node->occupied ) { + return; // not reachable by entities + + } + // area portals are allways only flooded into, never + // out of + if ( node->contents == CONTENTS_AREAPORTAL ) { + return; + } + + c_areas++; + FloodAreas_r( node ); +} //end of the function FindAreas_r +//=========================================================================== +// Just decend the tree, and for each node that hasn't had an +// area set, flood fill out from there +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetAreaPortalAreas_r( node_t *node ) { + bspbrush_t *b; + entity_t *e; + + if ( node->planenum != PLANENUM_LEAF ) { + SetAreaPortalAreas_r( node->children[0] ); + SetAreaPortalAreas_r( node->children[1] ); + return; + } //end if + + if ( node->contents == CONTENTS_AREAPORTAL ) { + if ( node->area ) { + return; // allready set + + } + b = node->brushlist; + e = &entities[b->original->entitynum]; + node->area = e->portalareas[0]; + if ( !e->portalareas[1] ) { + Log_Print( "WARNING: areaportal entity %i doesn't touch two areas\n", b->original->entitynum ); + return; + } //end if + } //end if +} //end of the function SetAreaPortalAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void EmitAreaPortals(node_t *headnode) +{ + int i, j; + entity_t *e; + dareaportal_t *dp; + + if (c_areas > MAX_MAP_AREAS) + Error ("MAX_MAP_AREAS"); + numareas = c_areas+1; + numareaportals = 1; // leave 0 as an error + + for (i=1 ; i<=c_areas ; i++) + { + dareas[i].firstareaportal = numareaportals; + for (j=0 ; jareaportalnum) + continue; + dp = &dareaportals[numareaportals]; + if (e->portalareas[0] == i) + { + dp->portalnum = e->areaportalnum; + dp->otherarea = e->portalareas[1]; + numareaportals++; + } //end if + else if (e->portalareas[1] == i) + { + dp->portalnum = e->areaportalnum; + dp->otherarea = e->portalareas[0]; + numareaportals++; + } //end else if + } //end for + dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal; + } //end for + + Log_Print("%5i numareas\n", numareas); + Log_Print("%5i numareaportals\n", numareaportals); +} //end of the function EmitAreaPortals +*/ +//=========================================================================== +// Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FloodAreas( tree_t *tree ) { + Log_Print( "--- FloodAreas ---\n" ); + FindAreas_r( tree->headnode ); + SetAreaPortalAreas_r( tree->headnode ); + Log_Print( "%5i areas\n", c_areas ); +} //end of the function FloodAreas +//=========================================================================== +// Finds a brush side to use for texturing the given portal +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FindPortalSide( portal_t *p ) { + int viscontents; + bspbrush_t *bb; + mapbrush_t *brush; + node_t *n; + int i,j; + int planenum; + side_t *side, *bestside; + float dot, bestdot; + plane_t *p1, *p2; + + // decide which content change is strongest + // solid > lava > water, etc + viscontents = VisibleContents( p->nodes[0]->contents ^ p->nodes[1]->contents ); + if ( !viscontents ) { + return; + } + + planenum = p->onnode->planenum; + bestside = NULL; + bestdot = 0; + + for ( j = 0 ; j < 2 ; j++ ) + { + n = p->nodes[j]; + p1 = &mapplanes[p->onnode->planenum]; + for ( bb = n->brushlist ; bb ; bb = bb->next ) + { + brush = bb->original; + if ( !( brush->contents & viscontents ) ) { + continue; + } + for ( i = 0 ; i < brush->numsides ; i++ ) + { + side = &brush->original_sides[i]; + if ( side->flags & SFL_BEVEL ) { + continue; + } + if ( side->texinfo == TEXINFO_NODE ) { + continue; // non-visible + } + if ( ( side->planenum & ~1 ) == planenum ) { // exact match + bestside = &brush->original_sides[i]; + goto gotit; + } //end if + // see how close the match is + p2 = &mapplanes[side->planenum & ~1]; + dot = DotProduct( p1->normal, p2->normal ); + if ( dot > bestdot ) { + bestdot = dot; + bestside = side; + } //end if + } //end for + } //end for + } //end for + +gotit: + if ( !bestside ) { + Log_Print( "WARNING: side not found for portal\n" ); + } + + p->sidefound = true; + p->side = bestside; +} //end of the function FindPortalSide +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MarkVisibleSides_r( node_t *node ) { + portal_t *p; + int s; + + if ( node->planenum != PLANENUM_LEAF ) { + MarkVisibleSides_r( node->children[0] ); + MarkVisibleSides_r( node->children[1] ); + return; + } //end if + + // empty leaves are never boundary leaves + if ( !node->contents ) { + return; + } + + // see if there is a visible face + for ( p = node->portals ; p ; p = p->next[!s] ) + { + s = ( p->nodes[0] == node ); + if ( !p->onnode ) { + continue; // edge of world + } + if ( !p->sidefound ) { + FindPortalSide( p ); + } + if ( p->side ) { + p->side->flags |= SFL_VISIBLE; + } + } //end for +} //end of the function MarkVisibleSides_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MarkVisibleSides( tree_t *tree, int startbrush, int endbrush ) { + int i, j; + mapbrush_t *mb; + int numsides; + + Log_Print( "--- MarkVisibleSides ---\n" ); + + // clear all the visible flags + for ( i = startbrush ; i < endbrush ; i++ ) + { + mb = &mapbrushes[i]; + + numsides = mb->numsides; + for ( j = 0 ; j < numsides ; j++ ) + mb->original_sides[j].flags &= ~SFL_VISIBLE; + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r( tree->headnode ); +} //end of the function MarkVisibleSides + diff --git a/src/bspc/prtfile.c b/src/bspc/prtfile.c new file mode 100644 index 0000000..30d4a2a --- /dev/null +++ b/src/bspc/prtfile.c @@ -0,0 +1,287 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// NO LONGER USED +#if 0 +#include "qbsp.h" + +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +/* +============================================================================== + +PORTAL FILE GENERATION + +Save out name.prt for qvis to read +============================================================================== +*/ + + +#define PORTALFILE "PRT1" + +FILE *pf; +int num_visclusters; // clusters the player can be in +int num_visportals; + +void WriteFloat2( FILE *f, vec_t v ) { + if ( fabs( v - Q_rint( v ) ) < 0.001 ) { + fprintf( f,"%i ",(int)Q_rint( v ) ); + } else { + fprintf( f,"%f ",v ); + } +} + +/* +================= +WritePortalFile_r +================= +*/ +void WritePortalFile_r( node_t *node ) { + int i, s; + portal_t *p; + winding_t *w; + vec3_t normal; + vec_t dist; + + // decision node + if ( node->planenum != PLANENUM_LEAF && !node->detail_seperator ) { + WritePortalFile_r( node->children[0] ); + WritePortalFile_r( node->children[1] ); + return; + } + + if ( node->contents & CONTENTS_SOLID ) { + return; + } + + for ( p = node->portals ; p ; p = p->next[s] ) + { + w = p->winding; + s = ( p->nodes[1] == node ); + if ( w && p->nodes[0] == node ) { + if ( !Portal_VisFlood( p ) ) { + continue; + } + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + // FIXME: is this still relevent? + WindingPlane( w, normal, &dist ); + if ( DotProduct( p->plane.normal, normal ) < 0.99 ) { // backwards... + fprintf( pf,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster ); + } else { + fprintf( pf,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster ); + } + for ( i = 0 ; i < w->numpoints ; i++ ) + { + fprintf( pf,"(" ); + WriteFloat2( pf, w->p[i][0] ); + WriteFloat2( pf, w->p[i][1] ); + WriteFloat2( pf, w->p[i][2] ); + fprintf( pf,") " ); + } + fprintf( pf,"\n" ); + } + } + +} + +/* +================ +FillLeafNumbers_r + +All of the leafs under node will have the same cluster +================ +*/ +void FillLeafNumbers_r( node_t *node, int num ) { + if ( node->planenum == PLANENUM_LEAF ) { + if ( node->contents & CONTENTS_SOLID ) { + node->cluster = -1; + } else { + node->cluster = num; + } + return; + } + node->cluster = num; + FillLeafNumbers_r( node->children[0], num ); + FillLeafNumbers_r( node->children[1], num ); +} + +/* +================ +NumberLeafs_r +================ +*/ +void NumberLeafs_r( node_t *node ) { + portal_t *p; + + if ( node->planenum != PLANENUM_LEAF && !node->detail_seperator ) { // decision node + node->cluster = -99; + NumberLeafs_r( node->children[0] ); + NumberLeafs_r( node->children[1] ); + return; + } + + // either a leaf or a detail cluster + + if ( node->contents & CONTENTS_SOLID ) { // solid block, viewpoint never inside + node->cluster = -1; + return; + } + + FillLeafNumbers_r( node, num_visclusters ); + num_visclusters++; + + // count the portals + for ( p = node->portals ; p ; ) + { + if ( p->nodes[0] == node ) { // only write out from first leaf + if ( Portal_VisFlood( p ) ) { + num_visportals++; + } + p = p->next[0]; + } else { + p = p->next[1]; + } + } + +} + + +/* +================ +CreateVisPortals_r +================ +*/ +void CreateVisPortals_r( node_t *node ) { + // stop as soon as we get to a detail_seperator, which + // means that everything below is in a single cluster + if ( node->planenum == PLANENUM_LEAF || node->detail_seperator ) { + return; + } + + MakeNodePortal( node ); + SplitNodePortals( node ); + + CreateVisPortals_r( node->children[0] ); + CreateVisPortals_r( node->children[1] ); +} + +/* +================ +FinishVisPortals_r +================ +*/ +void FinishVisPortals2_r( node_t *node ) { + if ( node->planenum == PLANENUM_LEAF ) { + return; + } + + MakeNodePortal( node ); + SplitNodePortals( node ); + + FinishVisPortals2_r( node->children[0] ); + FinishVisPortals2_r( node->children[1] ); +} + +void FinishVisPortals_r( node_t *node ) { + if ( node->planenum == PLANENUM_LEAF ) { + return; + } + + if ( node->detail_seperator ) { + FinishVisPortals2_r( node ); + return; + } + + FinishVisPortals_r( node->children[0] ); + FinishVisPortals_r( node->children[1] ); +} + + +int clusterleaf; +void SaveClusters_r( node_t *node ) { + if ( node->planenum == PLANENUM_LEAF ) { + dleafs[clusterleaf++].cluster = node->cluster; + return; + } + SaveClusters_r( node->children[0] ); + SaveClusters_r( node->children[1] ); +} + +/* +================ +WritePortalFile +================ +*/ +void WritePortalFile( tree_t *tree ) { + char filename[1024]; + node_t *headnode; + + qprintf( "--- WritePortalFile ---\n" ); + + headnode = tree->headnode; + num_visclusters = 0; + num_visportals = 0; + + Tree_FreePortals_r( headnode ); + + MakeHeadnodePortals( tree ); + + CreateVisPortals_r( headnode ); + +// set the cluster field in every leaf and count the total number of portals + + NumberLeafs_r( headnode ); + +// write the file + sprintf( filename, "%s.prt", source ); + printf( "writing %s\n", filename ); + pf = fopen( filename, "w" ); + if ( !pf ) { + Error( "Error opening %s", filename ); + } + + fprintf( pf, "%s\n", PORTALFILE ); + fprintf( pf, "%i\n", num_visclusters ); + fprintf( pf, "%i\n", num_visportals ); + + qprintf( "%5i visclusters\n", num_visclusters ); + qprintf( "%5i visportals\n", num_visportals ); + + WritePortalFile_r( headnode ); + + fclose( pf ); + + // we need to store the clusters out now because ordering + // issues made us do this after writebsp... + clusterleaf = 1; + SaveClusters_r( headnode ); +} +#endif diff --git a/src/bspc/q2files.h b/src/bspc/q2files.h new file mode 100644 index 0000000..b930fdd --- /dev/null +++ b/src/bspc/q2files.h @@ -0,0 +1,494 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#define IDPAKHEADER ( ( 'K' << 24 ) + ( 'C' << 16 ) + ( 'A' << 8 ) + 'P' ) + +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + int ident; // == IDPAKHEADER + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 4096 + + +/* +======================================================================== + +PCX files are used for as many images as possible + +======================================================================== +*/ + +typedef struct +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +#define IDALIASHEADER ( ( '2' << 24 ) + ( 'P' << 16 ) + ( 'D' << 8 ) + 'I' ) +#define ALIAS_VERSION 8 + +#define MAX_TRIANGLES 4096 +#define MAX_VERTS 2048 +#define MAX_FRAMES 512 +#define MAX_MD2SKINS 32 +#define MAX_SKINNAME 64 + +typedef struct +{ + short s; + short t; +} dstvert_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} dtriangle_t; + +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} dtrivertx_t; + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + dtrivertx_t verts[1]; // variable sized +} daliasframe_t; + + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file + +} dmdl_t; + +/* +======================================================================== + +.SP2 sprite file format + +======================================================================== +*/ + +#define IDSPRITEHEADER ( ( '2' << 24 ) + ( 'S' << 16 ) + ( 'D' << 8 ) + 'I' ) +// little-endian "IDS2" +#define SPRITE_VERSION 2 + +typedef struct +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[MAX_SKINNAME]; // name of pcx file +} dsprframe_t; + +typedef struct { + int ident; + int version; + int numframes; + dsprframe_t frames[1]; // variable sized +} dsprite_t; + +/* +============================================================================== + + .WAL texture file format + +============================================================================== +*/ + + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[32]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} miptex_t; + + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define IDBSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) +// little-endian "IBSP" + +#define BSPVERSION 38 + + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define MAX_MAP_MODELS 1024 +#define MAX_MAP_BRUSHES 8192 +#define MAX_MAP_ENTITIES 2048 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_TEXINFO 8192 + +#define MAX_MAP_AREAS 256 +#define MAX_MAP_AREAPORTALS 1024 +#define MAX_MAP_PLANES 65536 +#define MAX_MAP_NODES 65536 +#define MAX_MAP_BRUSHSIDES 65536 +#define MAX_MAP_LEAFS 65536 +#define MAX_MAP_VERTS 65536 +#define MAX_MAP_FACES 65536 +#define MAX_MAP_LEAFFACES 65536 +#define MAX_MAP_LEAFBRUSHES 65536 +#define MAX_MAP_PORTALS 65536 +#define MAX_MAP_EDGES 128000 +#define MAX_MAP_SURFEDGES 256000 +#define MAX_MAP_LIGHTING 0x320000 +#define MAX_MAP_VISIBILITY 0x280000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_VERTEXES 2 +#define LUMP_VISIBILITY 3 +#define LUMP_NODES 4 +#define LUMP_TEXINFO 5 +#define LUMP_FACES 6 +#define LUMP_LIGHTING 7 +#define LUMP_LEAFS 8 +#define LUMP_LEAFFACES 9 +#define LUMP_LEAFBRUSHES 10 +#define LUMP_EDGES 11 +#define LUMP_SURFEDGES 12 +#define LUMP_MODELS 13 +#define LUMP_BRUSHES 14 +#define LUMP_BRUSHSIDES 15 +#define LUMP_POP 16 +#define LUMP_AREAS 17 +#define LUMP_AREAPORTALS 18 +#define HEADER_LUMPS 19 + +typedef struct +{ + int ident; + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} dmodel_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 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +//renamed because it's in conflict with the Q3A translucent contents +#define CONTENTS_Q2TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes + + + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} texinfo_t; + + +// 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 +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + unsigned 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 +} dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} dareaportal_t; + +typedef struct +{ + int numareaportals; + int firstareaportal; +} darea_t; diff --git a/src/bspc/q3files.h b/src/bspc/q3files.h new file mode 100644 index 0000000..ea8a107 --- /dev/null +++ b/src/bspc/q3files.h @@ -0,0 +1,381 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES ( 6 * SHADER_MAX_VERTEXES ) + + +// the maximum size of game reletive pathnames +#define MAX_QPATH 64 + + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +* + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +* + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + +*/ + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT ( ( '3' << 24 ) + ( 'P' << 16 ) + ( 'D' << 8 ) + 'I' ) +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 4 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE ( 1.0 / 64 ) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ + +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_QPATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + + +#define Q3_BSP_IDENT ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) +// little-endian "IBSP" + +#define Q3_BSP_VERSION 47 + + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define Q3_MAX_MAP_MODELS 0x400 +#define Q3_MAX_MAP_BRUSHES 0x8000 +#define Q3_MAX_MAP_ENTITIES 0x800 +#define Q3_MAX_MAP_ENTSTRING 0x10000 +#define Q3_MAX_MAP_SHADERS 0x400 + +#define Q3_MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define Q3_MAX_MAP_FOGS 0x100 +#define Q3_MAX_MAP_PLANES 0x10000 +#define Q3_MAX_MAP_NODES 0x10000 +#define Q3_MAX_MAP_BRUSHSIDES 0x10000 +#define Q3_MAX_MAP_LEAFS 0x10000 +#define Q3_MAX_MAP_LEAFFACES 0x10000 +#define Q3_MAX_MAP_LEAFBRUSHES 0x10000 +#define Q3_MAX_MAP_PORTALS 0x10000 +#define Q3_MAX_MAP_LIGHTING 0x400000 +#define Q3_MAX_MAP_LIGHTGRID 0x400000 +#define Q3_MAX_MAP_VISIBILITY 0x200000 + +#define Q3_MAX_MAP_DRAW_SURFS 0x20000 +#define Q3_MAX_MAP_DRAW_VERTS 0x80000 +#define Q3_MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define Q3_MAX_KEY 32 +#define Q3_MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + + +//============================================================================= + + +typedef struct { + int fileofs, filelen; +} q3_lump_t; + +#define Q3_LUMP_ENTITIES 0 +#define Q3_LUMP_SHADERS 1 +#define Q3_LUMP_PLANES 2 +#define Q3_LUMP_NODES 3 +#define Q3_LUMP_LEAFS 4 +#define Q3_LUMP_LEAFSURFACES 5 +#define Q3_LUMP_LEAFBRUSHES 6 +#define Q3_LUMP_MODELS 7 +#define Q3_LUMP_BRUSHES 8 +#define Q3_LUMP_BRUSHSIDES 9 +#define Q3_LUMP_DRAWVERTS 10 +#define Q3_LUMP_DRAWINDEXES 11 +#define Q3_LUMP_FOGS 12 +#define Q3_LUMP_SURFACES 13 +#define Q3_LUMP_LIGHTMAPS 14 +#define Q3_LUMP_LIGHTGRID 15 +#define Q3_LUMP_VISIBILITY 16 +#define Q3_HEADER_LUMPS 17 + +typedef struct { + int ident; + int version; + + q3_lump_t lumps[Q3_HEADER_LUMPS]; +} q3_dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} q3_dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} q3_dshader_t; + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct { + float normal[3]; + float dist; +} q3_dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} q3_dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} q3_dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; +} q3_dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} q3_dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} q3_dfog_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; +} q3_drawVert_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} q3_mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} q3_dsurface_t; + + +#endif diff --git a/src/bspc/qbsp.h b/src/bspc/qbsp.h new file mode 100644 index 0000000..f8d3218 --- /dev/null +++ b/src/bspc/qbsp.h @@ -0,0 +1,496 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +#if defined( WIN32 ) || defined( _WIN32 ) +#include +#endif +#include +#include "l_cmd.h" +#include "l_math.h" +#include "l_poly.h" +#include "l_threads.h" +#include "../botlib/l_script.h" +#include "l_bsp_ent.h" +#include "q2files.h" +#include "l_mem.h" +#include "l_utils.h" +#include "l_log.h" +#include "l_qfiles.h" + +//Mr Elusive shit +#define ME +#define DEBUG +#define NODELIST +#define SIN + +#define MAX_BRUSH_SIDES 128 //maximum number of sides per brush +#define CLIP_EPSILON 0.1 +#define MAX_MAP_BOUNDS 65535 +#define BOGUS_RANGE ( MAX_MAP_BOUNDS + 128 ) //somewhere outside the map + +#define TEXINFO_NODE -1 //side is allready on a node +#define PLANENUM_LEAF -1 //used for leaf nodes +#define MAXEDGES 20 //maximum number of face edges +#define MAX_NODE_BRUSHES 8 //maximum brushes in a node +//side flags +#define SFL_TESTED 1 +#define SFL_VISIBLE 2 +#define SFL_BEVEL 4 +#define SFL_TEXTURED 8 +#define SFL_CURVE 16 + +//map plane +typedef struct plane_s +{ + vec3_t normal; + vec_t dist; + int type; + int signbits; + struct plane_s *hash_chain; +} plane_t; +//brush texture +typedef struct +{ + vec_t shift[2]; + vec_t rotate; + vec_t scale[2]; + char name[32]; + int flags; + int value; +} brush_texture_t; +//brush side +typedef struct side_s +{ + int planenum; // map plane this side is in + int texinfo; // texture reference + winding_t *winding; // winding of this side + struct side_s *original; // bspbrush_t sides will reference the mapbrush_t sides + int lightinfo; // for SIN only + int contents; // from miptex + int surf; // from miptex + unsigned short flags; // side flags +} side_t; //sizeof(side_t) = 36 +//map brush +typedef struct mapbrush_s +{ + int entitynum; + int brushnum; + + int contents; +#ifdef ME + int expansionbbox; //bbox used for expansion of the brush + int leafnum; + int modelnum; +#endif + + vec3_t mins, maxs; + + int numsides; + side_t *original_sides; +} mapbrush_t; +//bsp face +typedef struct face_s +{ + struct face_s *next; // on node + + // the chain of faces off of a node can be merged or split, + // but each face_t along the way will remain in the chain + // until the entire tree is freed + struct face_s *merged; // if set, this face isn't valid anymore + struct face_s *split[2]; // if set, this face isn't valid anymore + + struct portal_s *portal; + int texinfo; +#ifdef SIN + int lightinfo; +#endif + int planenum; + int contents; // faces in different contents can't merge + int outputnumber; + winding_t *w; + int numpoints; + qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex + int vertexnums[MAXEDGES]; +} face_t; +//bsp brush +typedef struct bspbrush_s +{ + struct bspbrush_s *next; + vec3_t mins, maxs; + int side, testside; // side of node during construction + mapbrush_t *original; + int numsides; + side_t sides[6]; // variably sized +} bspbrush_t; //sizeof(bspbrush_t) = 44 + numsides * sizeof(side_t) +//bsp node +typedef struct node_s +{ + //both leafs and nodes + int planenum; // -1 = leaf node + struct node_s *parent; + vec3_t mins, maxs; // valid after portalization + bspbrush_t *volume; // one for each leaf/node + + // nodes only + qboolean detail_seperator; // a detail brush caused the split + side_t *side; // the side that created the node + struct node_s *children[2]; + face_t *faces; + + // leafs only + bspbrush_t *brushlist; // fragments of all brushes in this leaf + int contents; // OR of all brush contents + int occupied; // 1 or greater can reach entity + entity_t *occupant; // for leak file testing + int cluster; // for portalfile writing + int area; // for areaportals + struct portal_s *portals; // also on nodes during construction +#ifdef NODELIST + struct node_s *next; //next node in the nodelist +#endif +#ifdef ME + int expansionbboxes; //OR of all bboxes used for expansion of the brushes + int modelnum; +#endif +} node_t; //sizeof(node_t) = 80 bytes +//bsp portal +typedef struct portal_s +{ + plane_t plane; + node_t *onnode; // NULL = outside box + node_t *nodes[2]; // [0] = front side of plane + struct portal_s *next[2]; + winding_t *winding; + + qboolean sidefound; // false if ->side hasn't been checked + side_t *side; // NULL = non-visible + face_t *face[2]; // output face in bsp file +#ifdef ME + struct tmp_face_s *tmpface; //pointer to the tmpface created for this portal + int planenum; //number of the map plane used by the portal +#endif +} portal_t; +//bsp tree +typedef struct +{ + node_t *headnode; + node_t outside_node; + vec3_t mins, maxs; +} tree_t; + +//============================================================================= +// bspc.c +//============================================================================= + +extern qboolean noprune; +extern qboolean nodetail; +extern qboolean fulldetail; +extern qboolean nomerge; +extern qboolean nosubdiv; +extern qboolean nowater; +extern qboolean noweld; +extern qboolean noshare; +extern qboolean notjunc; +extern qboolean onlyents; +#ifdef ME +extern qboolean nocsg; +extern qboolean create_aas; +extern qboolean freetree; +extern qboolean lessbrushes; +extern qboolean nobrushmerge; +extern qboolean cancelconversion; +extern qboolean noliquids; +extern qboolean capsule_collision; +#endif //ME + +extern float subdivide_size; +extern vec_t microvolume; + +extern char outbase[32]; +extern char source[1024]; + +//============================================================================= +// map.c +//============================================================================= + +#define MAX_MAPFILE_PLANES 128000 +#define MAX_MAPFILE_BRUSHES 65535 //16384 +#define MAX_MAPFILE_BRUSHSIDES ( MAX_MAPFILE_BRUSHES * 8 ) +#define MAX_MAPFILE_TEXINFO 8192 + +extern int entity_num; + +extern plane_t mapplanes[MAX_MAPFILE_PLANES]; +extern int nummapplanes; +extern int mapplaneusers[MAX_MAPFILE_PLANES]; + +extern int nummapbrushes; +extern mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; + +extern vec3_t map_mins, map_maxs; + +extern int nummapbrushsides; +extern side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; +extern brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; + +#ifdef ME + +typedef struct +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; + char texture[64]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} map_texinfo_t; + +extern map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; +extern int map_numtexinfo; +#define NODESTACKSIZE 1024 + +#define MAPTYPE_QUAKE1 1 +#define MAPTYPE_QUAKE2 2 +#define MAPTYPE_QUAKE3 3 +#define MAPTYPE_HALFLIFE 4 +#define MAPTYPE_SIN 5 + +extern int nodestack[NODESTACKSIZE]; +extern int *nodestackptr; +extern int nodestacksize; +extern int brushmodelnumbers[MAX_MAPFILE_BRUSHES]; +extern int dbrushleafnums[MAX_MAPFILE_BRUSHES]; +extern int dplanes2mapplanes[MAX_MAPFILE_PLANES]; + +extern int loadedmaptype; +#endif //ME + +extern int c_boxbevels; +extern int c_edgebevels; +extern int c_areaportals; +extern int c_clipbrushes; +extern int c_squattbrushes; + +//finds a float plane for the given normal and distance +int FindFloatPlane( vec3_t normal, vec_t dist ); +//returns the plane type for the given normal +int PlaneTypeForNormal( vec3_t normal ); +//returns the plane defined by the three given points +int PlaneFromPoints( int *p0, int *p1, int *p2 ); +//add bevels to the map brush +void AddBrushBevels( mapbrush_t *b ); +//makes brush side windings for the brush +qboolean MakeBrushWindings( mapbrush_t *ob ); +//marks brush bevels of the brush as bevel +void MarkBrushBevels( mapbrush_t *brush ); +//returns true if the map brush already exists +int BrushExists( mapbrush_t *brush ); +//loads a map from a bsp file +int LoadMapFromBSP( struct quakefile_s *qf ); +//resets map loading +void ResetMapLoading( void ); +//print some map info +void PrintMapInfo( void ); +//writes a map file (type depending on loaded map type) +void WriteMapFile( char *filename ); + +//============================================================================= +// map_q2.c +//============================================================================= + +void Q2_ResetMapLoading( void ); +//loads a Quake2 map file +void Q2_LoadMapFile( char *filename ); +//loads a map from a Quake2 bsp file +void Q2_LoadMapFromBSP( char *filename, int offset, int length ); + +//============================================================================= +// map_q1.c +//============================================================================= + +void Q1_ResetMapLoading( void ); +//loads a Quake2 map file +void Q1_LoadMapFile( char *filename ); +//loads a map from a Quake1 bsp file +void Q1_LoadMapFromBSP( char *filename, int offset, int length ); + +//============================================================================= +// map_q3.c +//============================================================================= +void Q3_ResetMapLoading( void ); +//loads a map from a Quake3 bsp file +void Q3_LoadMapFromBSP( struct quakefile_s *qf ); + +//============================================================================= +// map_sin.c +//============================================================================= + +void Sin_ResetMapLoading( void ); +//loads a Sin map file +void Sin_LoadMapFile( char *filename ); +//loads a map from a Sin bsp file +void Sin_LoadMapFromBSP( char *filename, int offset, int length ); + +//============================================================================= +// map_hl.c +//============================================================================= + +void HL_ResetMapLoading( void ); +//loads a Half-Life map file +void HL_LoadMapFile( char *filename ); +//loads a map from a Half-Life bsp file +void HL_LoadMapFromBSP( char *filename, int offset, int length ); + +//============================================================================= +// textures.c +//============================================================================= + +typedef struct +{ + char name[64]; + int flags; + int value; + int contents; + char animname[64]; +} textureref_t; + +#define MAX_MAP_TEXTURES 1024 + +extern textureref_t textureref[MAX_MAP_TEXTURES]; + +int FindMiptex( char *name ); +int TexinfoForBrushTexture( plane_t *plane, brush_texture_t *bt, vec3_t origin ); +void TextureAxisFromPlane( plane_t *pln, vec3_t xv, vec3_t yv ); + +//============================================================================= +// csg +//============================================================================= + +bspbrush_t *MakeBspBrushList( int startbrush, int endbrush, vec3_t clipmins, vec3_t clipmaxs ); +bspbrush_t *ChopBrushes( bspbrush_t *head ); +bspbrush_t *InitialBrushList( bspbrush_t *list ); +bspbrush_t *OptimizedBrushList( bspbrush_t *list ); +void WriteBrushMap( char *name, bspbrush_t *list ); +void CheckBSPBrush( bspbrush_t *brush ); +void BSPBrushWindings( bspbrush_t *brush ); +bspbrush_t *TryMergeBrushes( bspbrush_t *brush1, bspbrush_t *brush2 ); +tree_t *ProcessWorldBrushes( int brush_start, int brush_end ); + +//============================================================================= +// brushbsp +//============================================================================= + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH ( PSIDE_FRONT | PSIDE_BACK ) +#define PSIDE_FACING 4 + +void WriteBrushList( char *name, bspbrush_t *brush, qboolean onlyvis ); +bspbrush_t *CopyBrush( bspbrush_t *brush ); +void SplitBrush( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ); +node_t *AllocNode( void ); +bspbrush_t *AllocBrush( int numsides ); +int CountBrushList( bspbrush_t *brushes ); +void FreeBrush( bspbrush_t *brushes ); +vec_t BrushVolume( bspbrush_t *brush ); +void BoundBrush( bspbrush_t *brush ); +void FreeBrushList( bspbrush_t *brushes ); +tree_t *BrushBSP( bspbrush_t *brushlist, vec3_t mins, vec3_t maxs ); + +bspbrush_t *BrushFromBounds( vec3_t mins, vec3_t maxs ); +int BrushMostlyOnSide( bspbrush_t *brush, plane_t *plane ); +qboolean WindingIsHuge( winding_t *w ); +qboolean WindingIsTiny( winding_t *w ); +void ResetBrushBSP( void ); + +//============================================================================= +// portals.c +//============================================================================= + +int VisibleContents( int contents ); +void MakeHeadnodePortals( tree_t *tree ); +void MakeNodePortal( node_t *node ); +void SplitNodePortals( node_t *node ); +qboolean Portal_VisFlood( portal_t *p ); +qboolean FloodEntities( tree_t *tree ); +void FillOutside( node_t *headnode ); +void FloodAreas( tree_t *tree ); +void MarkVisibleSides( tree_t *tree, int start, int end ); +void FreePortal( portal_t *p ); +void EmitAreaPortals( node_t *headnode ); +void MakeTreePortals( tree_t *tree ); + +//============================================================================= +// glfile.c +//============================================================================= + +void OutputWinding( winding_t *w, FILE *glview ); +void WriteGLView( tree_t *tree, char *source ); + +//============================================================================= +// gldraw.c +//============================================================================= + +extern vec3_t draw_mins, draw_maxs; +extern qboolean drawflag; + +void Draw_ClearWindow( void ); +void DrawWinding( winding_t *w ); +void GLS_BeginScene( void ); +void GLS_Winding( winding_t *w, int code ); +void GLS_EndScene( void ); + +//============================================================================= +// leakfile.c +//============================================================================= + +void LeakFile( tree_t *tree ); + +//============================================================================= +// tree.c +//============================================================================= + +tree_t *Tree_Alloc( void ); +void Tree_Free( tree_t *tree ); +void Tree_Free_r( node_t *node ); +void Tree_Print_r( node_t *node, int depth ); +void Tree_FreePortals_r( node_t *node ); +void Tree_PruneNodes_r( node_t *node ); +void Tree_PruneNodes( node_t *node ); + +//============================================================================= +// faces.c +//============================================================================= + +face_t *AllocFace( void ); +void FreeFace( face_t *f ); +void MakeFaces( node_t *headnode ); +void FixTjuncs( node_t *headnode ); +int GetEdge2( int v1, int v2, face_t *f ); +void MergeNodeFaces( node_t *node ); diff --git a/src/bspc/qfiles.h b/src/bspc/qfiles.h new file mode 100644 index 0000000..0800712 --- /dev/null +++ b/src/bspc/qfiles.h @@ -0,0 +1,495 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#define IDPAKHEADER ( ( 'K' << 24 ) + ( 'C' << 16 ) + ( 'A' << 8 ) + 'P' ) + +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + int ident; // == IDPAKHEADER + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 4096 + + +/* +======================================================================== + +PCX files are used for as many images as possible + +======================================================================== +*/ + +typedef struct +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +#define IDALIASHEADER ( ( '2' << 24 ) + ( 'P' << 16 ) + ( 'D' << 8 ) + 'I' ) +#define ALIAS_VERSION 8 + +#define MAX_TRIANGLES 4096 +#define MAX_VERTS 2048 +#define MAX_FRAMES 512 +#define MAX_MD2SKINS 32 +#define MAX_SKINNAME 64 + +typedef struct +{ + short s; + short t; +} dstvert_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} dtriangle_t; + +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} dtrivertx_t; + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + dtrivertx_t verts[1]; // variable sized +} daliasframe_t; + + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file + +} dmdl_t; + +/* +======================================================================== + +.SP2 sprite file format + +======================================================================== +*/ + +#define IDSPRITEHEADER ( ( '2' << 24 ) + ( 'S' << 16 ) + ( 'D' << 8 ) + 'I' ) +// little-endian "IDS2" +#define SPRITE_VERSION 2 + +typedef struct +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[MAX_SKINNAME]; // name of pcx file +} dsprframe_t; + +typedef struct { + int ident; + int version; + int numframes; + dsprframe_t frames[1]; // variable sized +} dsprite_t; + +/* +============================================================================== + + .WAL texture file format + +============================================================================== +*/ + + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[32]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} miptex_t; + + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define IDBSPHEADER ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) +// little-endian "IBSP" + +#define BSPVERSION 38 + + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define MAX_MAP_MODELS 1024 +#define MAX_MAP_BRUSHES 8192 +#define MAX_MAP_ENTITIES 2048 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_TEXINFO 8192 + +#define MAX_MAP_AREAS 256 +#define MAX_MAP_AREAPORTALS 1024 +#define MAX_MAP_PLANES 65536 +#define MAX_MAP_NODES 65536 +#define MAX_MAP_BRUSHSIDES 65536 +#define MAX_MAP_LEAFS 65536 +#define MAX_MAP_VERTS 65536 +#define MAX_MAP_FACES 65536 +#define MAX_MAP_LEAFFACES 65536 +#define MAX_MAP_LEAFBRUSHES 65536 +#define MAX_MAP_PORTALS 65536 +#define MAX_MAP_EDGES 128000 +#define MAX_MAP_SURFEDGES 256000 +#define MAX_MAP_LIGHTING 0x320000 +#define MAX_MAP_VISIBILITY 0x280000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_VERTEXES 2 +#define LUMP_VISIBILITY 3 +#define LUMP_NODES 4 +#define LUMP_TEXINFO 5 +#define LUMP_FACES 6 +#define LUMP_LIGHTING 7 +#define LUMP_LEAFS 8 +#define LUMP_LEAFFACES 9 +#define LUMP_LEAFBRUSHES 10 +#define LUMP_EDGES 11 +#define LUMP_SURFEDGES 12 +#define LUMP_MODELS 13 +#define LUMP_BRUSHES 14 +#define LUMP_BRUSHSIDES 15 +#define LUMP_POP 16 +#define LUMP_AREAS 17 +#define LUMP_AREAPORTALS 18 +#define HEADER_LUMPS 19 + +typedef struct +{ + int ident; + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} dmodel_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 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +//renamed because it's in conflict with the Q3A translucent contents +#define CONTENTS_Q2TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes + +#define SURF_MONSTERSLICK 0x4000000 // slick surf that only affects ai's + + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} texinfo_t; + + +// 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 +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + unsigned 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 +} dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} dareaportal_t; + +typedef struct +{ + int numareaportals; + int firstareaportal; +} darea_t; diff --git a/src/bspc/sinfiles.h b/src/bspc/sinfiles.h new file mode 100644 index 0000000..5401f61 --- /dev/null +++ b/src/bspc/sinfiles.h @@ -0,0 +1,372 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define SIN + +#define SINBSPVERSION 41 + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define SIN_MAX_MAP_MODELS 1024 +#define SIN_MAX_MAP_BRUSHES 8192 +#define SIN_MAX_MAP_ENTITIES 2048 +#define SIN_MAX_MAP_ENTSTRING 0x40000 +#define SIN_MAX_MAP_TEXINFO 8192 + +#define SIN_MAX_MAP_AREAS 256 +#define SIN_MAX_MAP_AREAPORTALS 1024 +#define SIN_MAX_MAP_PLANES 65536 +#define SIN_MAX_MAP_NODES 65536 +#define SIN_MAX_MAP_BRUSHSIDES 65536 +#define SIN_MAX_MAP_LEAFS 65536 +#define SIN_MAX_MAP_VERTS 65536 +#define SIN_MAX_MAP_FACES 65536 +#define SIN_MAX_MAP_LEAFFACES 65536 +#define SIN_MAX_MAP_LEAFBRUSHES 65536 +#define SIN_MAX_MAP_PORTALS 65536 +#define SIN_MAX_MAP_EDGES 128000 +#define SIN_MAX_MAP_SURFEDGES 256000 +#define SIN_MAX_MAP_LIGHTING 0x320000 +#define SIN_MAX_MAP_VISIBILITY 0x280000 + +#ifdef SIN +#define SIN_MAX_MAP_LIGHTINFO 8192 +#endif + +#ifdef SIN +#undef SIN_MAX_MAP_LIGHTING //undef the Quake2 bsp version +#define SIN_MAX_MAP_LIGHTING 0x300000 +#endif + +#ifdef SIN +#undef SIN_MAX_MAP_VISIBILITY //undef the Quake2 bsp version +#define SIN_MAX_MAP_VISIBILITY 0x280000 +#endif + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} sin_lump_t; + +#define SIN_LUMP_ENTITIES 0 +#define SIN_LUMP_PLANES 1 +#define SIN_LUMP_VERTEXES 2 +#define SIN_LUMP_VISIBILITY 3 +#define SIN_LUMP_NODES 4 +#define SIN_LUMP_TEXINFO 5 +#define SIN_LUMP_FACES 6 +#define SIN_LUMP_LIGHTING 7 +#define SIN_LUMP_LEAFS 8 +#define SIN_LUMP_LEAFFACES 9 +#define SIN_LUMP_LEAFBRUSHES 10 +#define SIN_LUMP_EDGES 11 +#define SIN_LUMP_SURFEDGES 12 +#define SIN_LUMP_MODELS 13 +#define SIN_LUMP_BRUSHES 14 +#define SIN_LUMP_BRUSHSIDES 15 +#define SIN_LUMP_POP 16 +#define SIN_LUMP_AREAS 17 +#define SIN_LUMP_AREAPORTALS 18 + +#ifdef SIN +#define SIN_LUMP_LIGHTINFO 19 +#define SINHEADER_LUMPS 20 +#endif + +typedef struct +{ + int ident; + int version; + sin_lump_t lumps[SINHEADER_LUMPS]; +} sin_dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} sin_dmodel_t; + +typedef struct +{ + float point[3]; +} sin_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 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} sin_dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#ifdef SIN +#define CONTENTS_FENCE 4 +#endif +// remaining contents are non-visible, and don't eat brushes + +#ifdef SIN +#define CONTENTS_DUMMYFENCE 0x1000 +#endif + +#ifdef SIN +#define SURF_MASKED 0x2 // surface texture is masked +#endif + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp + +#ifdef SIN +#define SURF_NONLIT 0x10 // surface is not lit +#define SURF_NOFILTER 0x20 // surface is not bi-linear filtered +#endif + +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes + +#ifdef SIN +#define SURF_CONVEYOR 0x40 // surface is not lit +#endif + +#ifdef SIN +#define SURF_WAVY 0x400 // surface has waves +#define SURF_RICOCHET 0x800 // projectiles bounce literally bounce off this surface +#define SURF_PRELIT 0x1000 // surface has intensity information for pre-lighting +#define SURF_MIRROR 0x2000 // surface is a mirror +#define SURF_CONSOLE 0x4000 // surface is a console +#define SURF_USECOLOR 0x8000 // surface is lit with non-lit * color +#define SURF_HARDWAREONLY 0x10000 // surface has been damaged +#define SURF_DAMAGE 0x20000 // surface can be damaged +#define SURF_WEAK 0x40000 // surface has weak hit points +#define SURF_NORMAL 0x80000 // surface has normal hit points +#define SURF_ADD 0x100000 // surface will be additive +#define SURF_ENVMAPPED 0x200000 // surface is envmapped +#define SURF_RANDOMANIMATE 0x400000 // surface start animating on a random frame +#define SURF_ANIMATE 0x800000 // surface animates +#define SURF_RNDTIME 0x1000000 // time between animations is random +#define SURF_TRANSLATE 0x2000000 // surface translates +#define SURF_NOMERGE 0x4000000 // surface is not merged in csg phase +#define SURF_TYPE_BIT0 0x8000000 // 0 bit of surface type +#define SURF_TYPE_BIT1 0x10000000 // 1 bit of surface type +#define SURF_TYPE_BIT2 0x20000000 // 2 bit of surface type +#define SURF_TYPE_BIT3 0x40000000 // 3 bit of surface type + +#define SURF_START_BIT 27 +#define SURFACETYPE_FROM_FLAGS( x ) ( ( x >> ( SURF_START_BIT ) ) & 0xf ) + + +#define SURF_TYPE_SHIFT( x ) ( ( x ) << ( SURF_START_BIT ) ) // macro for getting proper bit mask + +#define SURF_TYPE_NONE SURF_TYPE_SHIFT( 0 ) +#define SURF_TYPE_WOOD SURF_TYPE_SHIFT( 1 ) +#define SURF_TYPE_METAL SURF_TYPE_SHIFT( 2 ) +#define SURF_TYPE_STONE SURF_TYPE_SHIFT( 3 ) +#define SURF_TYPE_CONCRETE SURF_TYPE_SHIFT( 4 ) +#define SURF_TYPE_DIRT SURF_TYPE_SHIFT( 5 ) +#define SURF_TYPE_FLESH SURF_TYPE_SHIFT( 6 ) +#define SURF_TYPE_GRILL SURF_TYPE_SHIFT( 7 ) +#define SURF_TYPE_GLASS SURF_TYPE_SHIFT( 8 ) +#define SURF_TYPE_FABRIC SURF_TYPE_SHIFT( 9 ) +#define SURF_TYPE_MONITOR SURF_TYPE_SHIFT( 10 ) +#define SURF_TYPE_GRAVEL SURF_TYPE_SHIFT( 11 ) +#define SURF_TYPE_VEGETATION SURF_TYPE_SHIFT( 12 ) +#define SURF_TYPE_PAPER SURF_TYPE_SHIFT( 13 ) +#define SURF_TYPE_DUCT SURF_TYPE_SHIFT( 14 ) +#define SURF_TYPE_WATER SURF_TYPE_SHIFT( 15 ) +#endif + + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} sin_dnode_t; + +#ifdef SIN + +typedef struct sin_lightvalue_s +{ + int value; // light emission, etc + vec3_t color; + float direct; + float directangle; + float directstyle; + char directstylename[32]; +} sin_lightvalue_t; + +typedef struct sin_texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + char texture[64]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain + float trans_mag; + int trans_angle; + int base_angle; + float animtime; + float nonlit; + float translucence; + float friction; + float restitution; + vec3_t color; + char groupname[32]; +} sin_texinfo_t; + +#endif //SIN + +// 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 +} sin_dedge_t; + +#ifdef MAXLIGHTMAPS +#undef MAXLIGHTMAPS +#endif +#define MAXLIGHTMAPS 16 +typedef struct +{ + unsigned 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 +#ifdef SIN + int lightinfo; +#endif +} sin_dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} sin_dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +#ifdef SIN + int lightinfo; +#endif +} sin_dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} sin_dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} sin_dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} sin_dareaportal_t; + +typedef struct +{ + int numareaportals; + int firstareaportal; +} sin_darea_t; diff --git a/src/bspc/textures.c b/src/bspc/textures.c new file mode 100644 index 0000000..e288524 --- /dev/null +++ b/src/bspc/textures.c @@ -0,0 +1,249 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: textures.c +// Function: textures +// Programmer: Mr Elusive (MrElusive@worldentity.com) +// Last update: 1999-08-10 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" +#include "l_bsp_q2.h" + + +int nummiptex; +textureref_t textureref[MAX_MAP_TEXTURES]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindMiptex( char *name ) { + int i; + char path[1024]; + miptex_t *mt; + + for ( i = 0; i < nummiptex; i++ ) + { + if ( !strcmp( name, textureref[i].name ) ) { + return i; + } //end if + } //end for + if ( nummiptex == MAX_MAP_TEXTURES ) { + Error( "MAX_MAP_TEXTURES" ); + } + strcpy( textureref[i].name, name ); + + // load the miptex to get the flags and values + sprintf( path, "%stextures/%s.wal", gamedir, name ); + if ( TryLoadFile( path, (void **)&mt ) != -1 ) { + textureref[i].value = LittleLong( mt->value ); + textureref[i].flags = LittleLong( mt->flags ); + textureref[i].contents = LittleLong( mt->contents ); + strcpy( textureref[i].animname, mt->animname ); + FreeMemory( mt ); + } //end if + nummiptex++; + + if ( textureref[i].animname[0] ) { + FindMiptex( textureref[i].animname ); + } + + return i; +} //end of the function FindMipTex +//=========================================================================== +//textureAxisFromPlane +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec3_t baseaxis[18] = +{ + {0,0,1}, {1,0,0}, {0,-1,0}, // floor + {0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling + {1,0,0}, {0,1,0}, {0,0,-1}, // west wall + {-1,0,0}, {0,1,0}, {0,0,-1}, // east wall + {0,1,0}, {1,0,0}, {0,0,-1}, // south wall + {0,-1,0}, {1,0,0}, {0,0,-1} // north wall +}; + +void TextureAxisFromPlane( plane_t *pln, vec3_t xv, vec3_t yv ) { + int bestaxis; + vec_t dot,best; + int i; + + best = 0; + bestaxis = 0; + + for ( i = 0 ; i < 6 ; i++ ) + { + dot = DotProduct( pln->normal, baseaxis[i * 3] ); + if ( dot > best ) { + best = dot; + bestaxis = i; + } + } + + VectorCopy( baseaxis[bestaxis * 3 + 1], xv ); + VectorCopy( baseaxis[bestaxis * 3 + 2], yv ); +} //end of the function TextureAxisFromPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int TexinfoForBrushTexture( plane_t *plane, brush_texture_t *bt, vec3_t origin ) { + vec3_t vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + texinfo_t tx, *tc; + int i, j, k; + float shift[2]; + brush_texture_t anim; + int mt; + + if ( !bt->name[0] ) { + return 0; + } + + memset( &tx, 0, sizeof( tx ) ); + strcpy( tx.texture, bt->name ); + + TextureAxisFromPlane( plane, vecs[0], vecs[1] ); + + shift[0] = DotProduct( origin, vecs[0] ); + shift[1] = DotProduct( origin, vecs[1] ); + + if ( !bt->scale[0] ) { + bt->scale[0] = 1; + } + if ( !bt->scale[1] ) { + bt->scale[1] = 1; + } + + +// rotate axis + if ( bt->rotate == 0 ) { + sinv = 0 ; cosv = 1; + } else if ( bt->rotate == 90 ) { + sinv = 1 ; cosv = 0; + } else if ( bt->rotate == 180 ) { + sinv = 0 ; cosv = -1; + } else if ( bt->rotate == 270 ) { + sinv = -1 ; cosv = 0; + } else + { + ang = bt->rotate / 180 * Q_PI; + sinv = sin( ang ); + cosv = cos( ang ); + } + + if ( vecs[0][0] ) { + sv = 0; + } else if ( vecs[0][1] ) { + sv = 1; + } else { + sv = 2; + } + + if ( vecs[1][0] ) { + tv = 0; + } else if ( vecs[1][1] ) { + tv = 1; + } else { + tv = 2; + } + + for ( i = 0 ; i < 2 ; i++ ) + { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for ( i = 0 ; i < 2 ; i++ ) + for ( j = 0 ; j < 3 ; j++ ) + tx.vecs[i][j] = vecs[i][j] / bt->scale[i]; + + tx.vecs[0][3] = bt->shift[0] + shift[0]; + tx.vecs[1][3] = bt->shift[1] + shift[1]; + tx.flags = bt->flags; + tx.value = bt->value; + + // + // find the texinfo + // + tc = texinfo; + for ( i = 0 ; i < numtexinfo ; i++, tc++ ) + { + if ( tc->flags != tx.flags ) { + continue; + } + if ( tc->value != tx.value ) { + continue; + } + for ( j = 0 ; j < 2 ; j++ ) + { + if ( strcmp( tc->texture, tx.texture ) ) { + goto skip; + } + for ( k = 0 ; k < 4 ; k++ ) + { + if ( tc->vecs[j][k] != tx.vecs[j][k] ) { + goto skip; + } + } + } + return i; +skip:; + } + *tc = tx; + numtexinfo++; + + // load the next animation + mt = FindMiptex( bt->name ); + if ( textureref[mt].animname[0] ) { + anim = *bt; + strcpy( anim.name, textureref[mt].animname ); + tc->nexttexinfo = TexinfoForBrushTexture( plane, &anim, origin ); + } else { + tc->nexttexinfo = -1; + } + + + return i; +} //end of the function TexinfoForBrushTexture diff --git a/src/bspc/tree.c b/src/bspc/tree.c new file mode 100644 index 0000000..4e6fbc2 --- /dev/null +++ b/src/bspc/tree.c @@ -0,0 +1,298 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: textures.c +// Function: textures +// Programmer: Mr Elusive (MrElusive@worldentity.com) +// Last update: 1999-08-10 +// Tab Size: 3 +//=========================================================================== + +#include "qbsp.h" + +extern int c_nodes; +int c_pruned; +int freedtreemem = 0; + +void RemovePortalFromNode( portal_t *portal, node_t *l ); + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +node_t *NodeForPoint( node_t *node, vec3_t origin ) { + plane_t *plane; + vec_t d; + + while ( node->planenum != PLANENUM_LEAF ) + { + plane = &mapplanes[node->planenum]; + d = DotProduct( origin, plane->normal ) - plane->dist; + if ( d >= 0 ) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + return node; +} //end of the function NodeForPoint +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_FreePortals_r( node_t *node ) { + portal_t *p, *nextp; + int s; + + // free children + if ( node->planenum != PLANENUM_LEAF ) { + Tree_FreePortals_r( node->children[0] ); + Tree_FreePortals_r( node->children[1] ); + } + + // free portals + for ( p = node->portals; p; p = nextp ) + { + s = ( p->nodes[1] == node ); + nextp = p->next[s]; + + RemovePortalFromNode( p, p->nodes[!s] ); +#ifdef ME + if ( p->winding ) { + freedtreemem += MemorySize( p->winding ); + } + freedtreemem += MemorySize( p ); +#endif //ME + FreePortal( p ); + } + node->portals = NULL; +} //end of the function Tree_FreePortals_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_Free_r( node_t *node ) { +// face_t *f, *nextf; + bspbrush_t *brush, *nextbrush; + + //free children + if ( node->planenum != PLANENUM_LEAF ) { + Tree_Free_r( node->children[0] ); + Tree_Free_r( node->children[1] ); + } //end if + //free bspbrushes +// FreeBrushList (node->brushlist); + for ( brush = node->brushlist; brush; brush = nextbrush ) + { + nextbrush = brush->next; +#ifdef ME + freedtreemem += MemorySize( brush ); +#endif //ME + FreeBrush( brush ); + } //end for + node->brushlist = NULL; + + /* + NOTE: only used when creating Q2 bsp + // free faces + for (f = node->faces; f; f = nextf) + { + nextf = f->next; +#ifdef ME + if (f->w) freedtreemem += MemorySize(f->w); + freedtreemem += sizeof(face_t); +#endif //ME + FreeFace(f); + } //end for + */ + + // free the node + if ( node->volume ) { +#ifdef ME + freedtreemem += MemorySize( node->volume ); +#endif //ME + FreeBrush( node->volume ); + } //end if + + if ( numthreads == 1 ) { + c_nodes--; + } +#ifdef ME + freedtreemem += MemorySize( node ); +#endif //ME + FreeMemory( node ); +} //end of the function Tree_Free_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_Free( tree_t *tree ) { + //if no tree just return + if ( !tree ) { + return; + } + // + freedtreemem = 0; + // + Tree_FreePortals_r( tree->headnode ); + Tree_Free_r( tree->headnode ); +#ifdef ME + freedtreemem += MemorySize( tree ); +#endif //ME + FreeMemory( tree ); +#ifdef ME + Log_Print( "freed " ); + PrintMemorySize( freedtreemem ); + Log_Print( " of tree memory\n" ); +#endif //ME +} //end of the function Tree_Free +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +tree_t *Tree_Alloc( void ) { + tree_t *tree; + + tree = GetMemory( sizeof( *tree ) ); + memset( tree, 0, sizeof( *tree ) ); + ClearBounds( tree->mins, tree->maxs ); + + return tree; +} //end of the function Tree_Alloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_Print_r( node_t *node, int depth ) { + int i; + plane_t *plane; + bspbrush_t *bb; + + for ( i = 0 ; i < depth ; i++ ) + printf( " " ); + if ( node->planenum == PLANENUM_LEAF ) { + if ( !node->brushlist ) { + printf( "NULL\n" ); + } else + { + for ( bb = node->brushlist ; bb ; bb = bb->next ) + printf( "%i ", bb->original->brushnum ); + printf( "\n" ); + } + return; + } + + plane = &mapplanes[node->planenum]; + printf( "#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, + plane->normal[0], plane->normal[1], plane->normal[2], + plane->dist ); + Tree_Print_r( node->children[0], depth + 1 ); + Tree_Print_r( node->children[1], depth + 1 ); +} //end of the function Tree_Print_r +//=========================================================================== +// NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_PruneNodes_r( node_t *node ) { + bspbrush_t *b, *next; + + if ( node->planenum == PLANENUM_LEAF ) { + return; + } + + Tree_PruneNodes_r( node->children[0] ); + Tree_PruneNodes_r( node->children[1] ); + + if ( create_aas ) { + if ( ( node->children[0]->contents & CONTENTS_LADDER ) || + ( node->children[1]->contents & CONTENTS_LADDER ) ) { + return; + } + } + + if ( ( node->children[0]->contents & CONTENTS_SOLID ) + && ( node->children[1]->contents & CONTENTS_SOLID ) ) { + if ( node->faces ) { + Error( "node->faces seperating CONTENTS_SOLID" ); + } + if ( node->children[0]->faces || node->children[1]->faces ) { + Error( "!node->faces with children" ); + } + // FIXME: free stuff + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + node->detail_seperator = false; + + if ( node->brushlist ) { + Error( "PruneNodes: node->brushlist" ); + } + // combine brush lists + node->brushlist = node->children[1]->brushlist; + + for ( b = node->children[0]->brushlist; b; b = next ) + { + next = b->next; + b->next = node->brushlist; + node->brushlist = b; + } //end for + //free the child nodes + FreeMemory( node->children[0] ); + FreeMemory( node->children[1] ); + //two nodes are cut away + c_pruned += 2; + } //end if +} //end of the function Tree_PruneNodes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Tree_PruneNodes( node_t *node ) { + Log_Print( "------- Prune Nodes --------\n" ); + c_pruned = 0; + Tree_PruneNodes_r( node ); + Log_Print( "%5i pruned nodes\n", c_pruned ); +} //end of the function Tree_PruneNodes diff --git a/src/bspc/writebsp.c b/src/bspc/writebsp.c new file mode 100644 index 0000000..ae622dd --- /dev/null +++ b/src/bspc/writebsp.c @@ -0,0 +1,632 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// NO LONGER IN PROJECT +#if 0 +#include "qbsp.h" + +int c_nofaces; +int c_facenodes; + + +extern int numplanes; +extern int numfaces; +extern int numleaffaces; +extern int numleafs; +extern int numleafbrushes; +extern int numsurfedges; +extern int numnodes; + +extern int nummodels; +extern int numbrushsides; +extern int numbrushes; +extern int numvertexes; +extern int numedges; + +extern dplane_t dplanes[MAX_MAP_PLANES]; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +extern dface_t dfaces[MAX_MAP_FACES]; +extern unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; +extern unsigned short dleaffaces[MAX_MAP_LEAFFACES]; +extern int dsurfedges[MAX_MAP_SURFEDGES]; +extern dnode_t dnodes[MAX_MAP_NODES]; +extern dmodel_t dmodels[MAX_MAP_MODELS]; +extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; +extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +/* +========================================================= + +ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES + +========================================================= +*/ + +int planeused[MAX_MAP_PLANES]; + +/* +============ +EmitPlanes + +There is no oportunity to discard planes, because all of the original +brushes will be saved in the map. +============ +*/ +void EmitPlanes( void ) { + int i; + dplane_t *dp; + plane_t *mp; + //ME: this causes a crash?? +// int planetranslate[MAX_MAP_PLANES]; + + mp = mapplanes; + for ( i = 0 ; i < nummapplanes ; i++, mp++ ) + { + dp = &dplanes[numplanes]; +// planetranslate[i] = numplanes; + VectorCopy( mp->normal, dp->normal ); + dp->dist = mp->dist; + dp->type = mp->type; + numplanes++; + if ( numplanes >= MAX_MAP_PLANES ) { + Error( "MAX_MAP_PLANES" ); + } + } +} + + +//======================================================== + +void EmitMarkFace( dleaf_t *leaf_p, face_t *f ) { + int i; + int facenum; + + while ( f->merged ) + f = f->merged; + + if ( f->split[0] ) { + EmitMarkFace( leaf_p, f->split[0] ); + EmitMarkFace( leaf_p, f->split[1] ); + return; + } + + facenum = f->outputnumber; + if ( facenum == -1 ) { + return; // degenerate face + + } + if ( facenum < 0 || facenum >= numfaces ) { + Error( "Bad leafface" ); + } + for ( i = leaf_p->firstleafface ; i < numleaffaces ; i++ ) + if ( dleaffaces[i] == facenum ) { + break; + } // merged out face + if ( i == numleaffaces ) { + if ( numleaffaces >= MAX_MAP_LEAFFACES ) { + Error( "MAX_MAP_LEAFFACES" ); + } + + dleaffaces[numleaffaces] = facenum; + numleaffaces++; + } + +} + + +/* +================== +EmitLeaf +================== +*/ +void EmitLeaf( node_t *node ) { + dleaf_t *leaf_p; + portal_t *p; + int s; + face_t *f; + bspbrush_t *b; + int i; + int brushnum; + + // emit a leaf + if ( numleafs >= MAX_MAP_LEAFS ) { + Error( "MAX_MAP_LEAFS" ); + } + + leaf_p = &dleafs[numleafs]; + numleafs++; + + leaf_p->contents = node->contents; + leaf_p->cluster = node->cluster; + leaf_p->area = node->area; + + // + // write bounding box info + // + VectorCopy( node->mins, leaf_p->mins ); + VectorCopy( node->maxs, leaf_p->maxs ); + + // + // write the leafbrushes + // + leaf_p->firstleafbrush = numleafbrushes; + for ( b = node->brushlist ; b ; b = b->next ) + { + if ( numleafbrushes >= MAX_MAP_LEAFBRUSHES ) { + Error( "MAX_MAP_LEAFBRUSHES" ); + } + + brushnum = b->original - mapbrushes; + for ( i = leaf_p->firstleafbrush ; i < numleafbrushes ; i++ ) + if ( dleafbrushes[i] == brushnum ) { + break; + } + if ( i == numleafbrushes ) { + dleafbrushes[numleafbrushes] = brushnum; + numleafbrushes++; + } + } + leaf_p->numleafbrushes = numleafbrushes - leaf_p->firstleafbrush; + + // + // write the leaffaces + // + if ( leaf_p->contents & CONTENTS_SOLID ) { + return; // no leaffaces in solids + + } + leaf_p->firstleafface = numleaffaces; + + for ( p = node->portals ; p ; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + f = p->face[s]; + if ( !f ) { + continue; // not a visible portal + + } + EmitMarkFace( leaf_p, f ); + } + + leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; +} + + +/* +================== +EmitFace +================== +*/ +void EmitFace( face_t *f ) { + dface_t *df; + int i; + int e; + + f->outputnumber = -1; + + if ( f->numpoints < 3 ) { + return; // degenerated + } + if ( f->merged || f->split[0] || f->split[1] ) { + return; // not a final face + } + + // save output number so leaffaces can use + f->outputnumber = numfaces; + + if ( numfaces >= MAX_MAP_FACES ) { + Error( "numfaces == MAX_MAP_FACES" ); + } + df = &dfaces[numfaces]; + numfaces++; + + // planenum is used by qlight, but not quake + df->planenum = f->planenum & ( ~1 ); + df->side = f->planenum & 1; + + df->firstedge = numsurfedges; + df->numedges = f->numpoints; + df->texinfo = f->texinfo; + for ( i = 0 ; i < f->numpoints ; i++ ) + { +// e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); + e = GetEdge2( f->vertexnums[i], f->vertexnums[( i + 1 ) % f->numpoints], f ); + if ( numsurfedges >= MAX_MAP_SURFEDGES ) { + Error( "numsurfedges == MAX_MAP_SURFEDGES" ); + } + dsurfedges[numsurfedges] = e; + numsurfedges++; + } +} + +/* +============ +EmitDrawingNode_r +============ +*/ +int EmitDrawNode_r( node_t *node ) { + dnode_t *n; + face_t *f; + int i; + + if ( node->planenum == PLANENUM_LEAF ) { + EmitLeaf( node ); + return -numleafs; + } + + // emit a node + if ( numnodes == MAX_MAP_NODES ) { + Error( "MAX_MAP_NODES" ); + } + n = &dnodes[numnodes]; + numnodes++; + + VectorCopy( node->mins, n->mins ); + VectorCopy( node->maxs, n->maxs ); + + planeused[node->planenum]++; + planeused[node->planenum ^ 1]++; + + if ( node->planenum & 1 ) { + Error( "WriteDrawNodes_r: odd planenum" ); + } + n->planenum = node->planenum; + n->firstface = numfaces; + + if ( !node->faces ) { + c_nofaces++; + } else { + c_facenodes++; + } + + for ( f = node->faces ; f ; f = f->next ) + EmitFace( f ); + + n->numfaces = numfaces - n->firstface; + + + // + // recursively output the other nodes + // + for ( i = 0 ; i < 2 ; i++ ) + { + if ( node->children[i]->planenum == PLANENUM_LEAF ) { + n->children[i] = -( numleafs + 1 ); + EmitLeaf( node->children[i] ); + } else + { + n->children[i] = numnodes; + EmitDrawNode_r( node->children[i] ); + } + } + + return n - dnodes; +} + +//========================================================= + + +/* +============ +WriteBSP +============ +*/ +void WriteBSP( node_t *headnode ) { + int oldfaces; + + c_nofaces = 0; + c_facenodes = 0; + + qprintf( "--- WriteBSP ---\n" ); + + oldfaces = numfaces; + dmodels[nummodels].headnode = EmitDrawNode_r( headnode ); +// EmitAreaPortals (headnode); + + qprintf( "%5i nodes with faces\n", c_facenodes ); + qprintf( "%5i nodes without faces\n", c_nofaces ); + qprintf( "%5i faces\n", numfaces - oldfaces ); +} + +//=========================================================== + +/* +============ +SetModelNumbers +============ +*/ +void SetModelNumbers( void ) { + int i; + int models; + char value[10]; + + models = 1; + for ( i = 1 ; i < num_entities ; i++ ) + { + if ( entities[i].numbrushes ) { + sprintf( value, "*%i", models ); + models++; + SetKeyValue( &entities[i], "model", value ); + } + } + +} + +/* +============ +SetLightStyles +============ +*/ +#define MAX_SWITCHED_LIGHTS 32 +void SetLightStyles( void ) { + int stylenum; + char *t; + entity_t *e; + int i, j; + char value[10]; + char lighttargets[MAX_SWITCHED_LIGHTS][64]; + + + // any light that is controlled (has a targetname) + // must have a unique style number generated for it + + stylenum = 0; + for ( i = 1 ; i < num_entities ; i++ ) + { + e = &entities[i]; + + t = ValueForKey( e, "classname" ); + if ( Q_strncasecmp( t, "light", 5 ) ) { + continue; + } + t = ValueForKey( e, "targetname" ); + if ( !t[0] ) { + continue; + } + + // find this targetname + for ( j = 0 ; j < stylenum ; j++ ) + if ( !strcmp( lighttargets[j], t ) ) { + break; + } + if ( j == stylenum ) { + if ( stylenum == MAX_SWITCHED_LIGHTS ) { + Error( "stylenum == MAX_SWITCHED_LIGHTS" ); + } + strcpy( lighttargets[j], t ); + stylenum++; + } + sprintf( value, "%i", 32 + j ); + SetKeyValue( e, "style", value ); + } + +} + +//=========================================================== + +/* +============ +EmitBrushes +============ +*/ +void EmitBrushes( void ) { + int i, j, bnum, s, x; + dbrush_t *db; + mapbrush_t *b; + dbrushside_t *cp; + vec3_t normal; + vec_t dist; + int planenum; + + numbrushsides = 0; + numbrushes = nummapbrushes; + + for ( bnum = 0 ; bnum < nummapbrushes ; bnum++ ) + { + b = &mapbrushes[bnum]; + db = &dbrushes[bnum]; + + db->contents = b->contents; + db->firstside = numbrushsides; + db->numsides = b->numsides; + for ( j = 0 ; j < b->numsides ; j++ ) + { + if ( numbrushsides == MAX_MAP_BRUSHSIDES ) { + Error( "MAX_MAP_BRUSHSIDES" ); + } + cp = &dbrushsides[numbrushsides]; + numbrushsides++; + cp->planenum = b->original_sides[j].planenum; + cp->texinfo = b->original_sides[j].texinfo; + } + +#ifdef ME + //for collision detection, bounding boxes are axial :) + //brushes are convex so just add dot or line touching planes on the sides of + //the brush parallell to the axis planes +#endif + // add any axis planes not contained in the brush to bevel off corners + for ( x = 0 ; x < 3 ; x++ ) + for ( s = -1 ; s <= 1 ; s += 2 ) + { + // add the plane + VectorCopy( vec3_origin, normal ); + normal[x] = s; + if ( s == -1 ) { + dist = -b->mins[x]; + } else { + dist = b->maxs[x]; + } + planenum = FindFloatPlane( normal, dist ); + for ( i = 0 ; i < b->numsides ; i++ ) + if ( b->original_sides[i].planenum == planenum ) { + break; + } + if ( i == b->numsides ) { + if ( numbrushsides >= MAX_MAP_BRUSHSIDES ) { + Error( "MAX_MAP_BRUSHSIDES" ); + } + + dbrushsides[numbrushsides].planenum = planenum; + dbrushsides[numbrushsides].texinfo = + dbrushsides[numbrushsides - 1].texinfo; + numbrushsides++; + db->numsides++; + } + } + + } + +} + +//=========================================================== + +/* +================== +BeginBSPFile +================== +*/ +void BeginBSPFile( void ) { + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + nummodels = 0; + numfaces = 0; + numnodes = 0; + numbrushsides = 0; + numvertexes = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + + // edge 0 is not used, because 0 can't be negated + numedges = 1; + + // leave vertex 0 as an error + numvertexes = 1; + + // leave leaf 0 as an error + numleafs = 1; + dleafs[0].contents = CONTENTS_SOLID; +} + + +/* +============ +EndBSPFile +============ +*/ +void EndBSPFile( void ) { +#if 0 + char path[1024]; + int len; + byte *buf; +#endif + + + EmitBrushes(); + EmitPlanes(); + Q2_UnparseEntities(); + + // load the pop +#if 0 + sprintf( path, "%s/pics/pop.lmp", gamedir ); + len = LoadFile( path, &buf ); + memcpy( dpop, buf, sizeof( dpop ) ); + FreeMemory( buf ); +#endif +} + + +/* +================== +BeginModel +================== +*/ +int firstmodleaf; +extern int firstmodeledge; +extern int firstmodelface; +void BeginModel( void ) { + dmodel_t *mod; + int start, end; + mapbrush_t *b; + int j; + entity_t *e; + vec3_t mins, maxs; + + if ( nummodels == MAX_MAP_MODELS ) { + Error( "MAX_MAP_MODELS" ); + } + mod = &dmodels[nummodels]; + + mod->firstface = numfaces; + + firstmodleaf = numleafs; + firstmodeledge = numedges; + firstmodelface = numfaces; + + // + // bound the brushes + // + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + ClearBounds( mins, maxs ); + + for ( j = start ; j < end ; j++ ) + { + b = &mapbrushes[j]; + if ( !b->numsides ) { + continue; // not a real brush (origin brush) + } + AddPointToBounds( b->mins, mins, maxs ); + AddPointToBounds( b->maxs, mins, maxs ); + } + + VectorCopy( mins, mod->mins ); + VectorCopy( maxs, mod->maxs ); +} + + +/* +================== +EndModel +================== +*/ +void EndModel( void ) { + dmodel_t *mod; + + mod = &dmodels[nummodels]; + + mod->numfaces = numfaces - mod->firstface; + + nummodels++; +} + +#endif \ No newline at end of file diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c new file mode 100644 index 0000000..1b1d4b0 --- /dev/null +++ b/src/cgame/cg_consolecmds.c @@ -0,0 +1,680 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: cg_consolecmds.c + * + * desc: text commands typed in at the local console, or executed by a key binding + * +*/ + + +#include "cg_local.h" +#include "../ui/ui_shared.h" + + + +void CG_TargetCommand_f( void ) { + int targetNum; + char test[4]; + + targetNum = CG_CrosshairPlayer(); + if ( !targetNum ) { + return; + } + + trap_Argv( 1, test, 4 ); + trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); +} + + + +/* +================= +CG_SizeUp_f + +Keybinding command +================= +*/ +static void CG_SizeUp_f( void ) { + trap_Cvar_Set( "cg_viewsize", va( "%i",(int)( cg_viewsize.integer + 10 ) ) ); +} + + +/* +================= +CG_SizeDown_f + +Keybinding command +================= +*/ +static void CG_SizeDown_f( void ) { + trap_Cvar_Set( "cg_viewsize", va( "%i",(int)( cg_viewsize.integer - 10 ) ) ); +} + + +/* +============= +CG_Viewpos_f + +Debugging command to print the current position +============= +*/ +static void CG_Viewpos_f( void ) { + CG_Printf( "(%i %i %i) : %i\n", (int)cg.refdef.vieworg[0], + (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], + (int)cg.refdefViewAngles[YAW] ); +} + + +static void CG_ScoresDown_f( void ) { + if ( cg.scoresRequestTime + 2000 < cg.time ) { + // the scores are more than two seconds out of data, + // so request new ones + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + + // leave the current scores up if they were already + // displayed, but if this is the first hit, clear them out + if ( !cg.showScores ) { + cg.showScores = qtrue; + cg.numScores = 0; + } + } else { + // show the cached contents even if they just pressed if it + // is within two seconds + cg.showScores = qtrue; + } +} + +static void CG_ScoresUp_f( void ) { + if ( cg.showScores ) { + cg.showScores = qfalse; + cg.scoreFadeTime = cg.time; + } +} + + +extern menuDef_t *menuScoreboard; +void Menu_Reset(); // FIXME: add to right include file + +static void CG_LoadHud_f( void ) { + char buff[1024]; + const char *hudSet; + memset( buff, 0, sizeof( buff ) ); + + String_Init(); + Menu_Reset(); + +// trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); +// hudSet = buff; +// if (hudSet[0] == '\0') { + hudSet = "ui_mp/hud.txt"; +// } + + CG_LoadMenus( hudSet ); + menuScoreboard = NULL; +} + +/* +// TTimo: defined but not used +static void CG_scrollScoresDown_f( void) { + if (menuScoreboard && cg.scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qtrue); + } +} +*/ + +/* +// TTimo: defined but not used +static void CG_scrollScoresUp_f( void) { + if (menuScoreboard && cg.scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qfalse); + } +} +*/ + +/* +// TTimo: defined but not used +static void CG_spWin_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); +// CG_AddBufferedSound(cgs.media.winnerSound); + //trap_S_StartLocalSound(cgs.media.winnerSound, CHAN_ANNOUNCER); + CG_CenterPrint("YOU WIN!", SCREEN_HEIGHT * .30, 0); +} +*/ + +/* +// TTimo: defined but not used +static void CG_spLose_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); +// CG_AddBufferedSound(cgs.media.loserSound); + //trap_S_StartLocalSound(cgs.media.loserSound, CHAN_ANNOUNCER); + CG_CenterPrint("YOU LOSE...", SCREEN_HEIGHT * .30, 0); +} +*/ + +//----(SA) item (key/pickup) drawing +static void CG_InventoryDown_f( void ) { + cg.showItems = qtrue; +} + +static void CG_InventoryUp_f( void ) { + cg.showItems = qfalse; + cg.itemFadeTime = cg.time; +} + +//----(SA) end + +static void CG_TellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_TellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +/* +// TTimo: defined but not used +static void CG_NextTeamMember_f( void ) { + CG_SelectNextPlayer(); +} +*/ + +/* +// TTimo: defined but not used +static void CG_PrevTeamMember_f( void ) { + CG_SelectPrevPlayer(); +} +*/ + +/////////// cameras + +#define MAX_CAMERAS 64 // matches define in splines.cpp +qboolean cameraInuse[MAX_CAMERAS]; + +int CG_LoadCamera( const char *name ) { + int i; + for ( i = 1; i < MAX_CAMERAS; i++ ) { // start at '1' since '0' is always taken by the cutscene camera + if ( !cameraInuse[i] ) { + if ( trap_loadCamera( i, name ) ) { + cameraInuse[i] = qtrue; + return i; + } + } + } + return -1; +} + +void CG_FreeCamera( int camNum ) { + cameraInuse[camNum] = qfalse; +} + +/* +============== +CG_StartCamera +============== +*/ +void CG_StartCamera( const char *name, qboolean startBlack ) { + char lname[MAX_QPATH]; + + if ( cgs.gametype != GT_SINGLE_PLAYER ) { + return; + } + + COM_StripExtension( name, lname ); //----(SA) added + strcat( lname, ".camera" ); + + if ( trap_loadCamera( CAM_PRIMARY, va( "cameras/%s", lname ) ) ) { + cg.cameraMode = qtrue; + if ( startBlack ) { + CG_Fade( 0, 0, 0, 255, 0 ); // go black + } + trap_Cvar_Set( "cg_letterbox", "1" ); // go letterbox + trap_SendClientCommand( "startCamera" ); + trap_startCamera( CAM_PRIMARY, cg.time ); + } else { + + //----(SA) temp until radiant stores cameras in own directory + // check cameras dir then main dir + if ( trap_loadCamera( CAM_PRIMARY, name ) ) { + cg.cameraMode = qtrue; + trap_SendClientCommand( "startCamera" ); + trap_startCamera( CAM_PRIMARY, cg.time ); + return; + } + //----(SA) end (remove when radiant stores cameras...) + + trap_SendClientCommand( "stopCamera" ); + CG_Fade( 0, 0, 0, 0, 0 ); // ensure fadeup + trap_Cvar_Set( "cg_letterbox", "0" ); + cg.cameraMode = qfalse; + CG_Printf( "Unable to load camera %s\n",lname ); + } +} + +/* +// TTimo: defined but not used +static void CG_Camera_f( void ) { + char name[MAX_QPATH]; + + if ( cgs.gametype != GT_SINGLE_PLAYER ) + return; + + trap_Argv( 1, name, sizeof(name)); + + CG_StartCamera(name, qfalse ); +} +*/ + +static void CG_Fade_f( void ) { + int r, g, b, a; + float time; + + if ( trap_Argc() < 6 ) { + return; + } + + r = atof( CG_Argv( 1 ) ); + g = atof( CG_Argv( 2 ) ); + b = atof( CG_Argv( 3 ) ); + a = atof( CG_Argv( 4 ) ); + + time = atof( CG_Argv( 5 ) ) * 1000; + + CG_Fade( r, g, b, a, time ); +} + +// NERVE - SMF +static void CG_QuickMessage_f( void ) { + if ( cg_quickMessageAlt.integer ) { + trap_UI_Popup( "UIMENU_WM_QUICKMESSAGEALT" ); + } else { + trap_UI_Popup( "UIMENU_WM_QUICKMESSAGE" ); + } +} + +static void CG_OpenLimbo_f( void ) { + int currentTeam; + char buf[32]; + + // set correct team, also set current team to detect if its changed + if ( cg.snap ) { + currentTeam = cg.snap->ps.persistant[PERS_TEAM] - 1; + } else { + currentTeam = 0; + } + + if ( currentTeam > 2 ) { + currentTeam = 2; + } + + // Arnout - don't set currentteam when following as it won't be the actual currentteam + if ( currentTeam != mp_team.integer && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { + trap_Cvar_Set( "mp_team", va( "%d", currentTeam ) ); + } + + if ( currentTeam != mp_currentTeam.integer && !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { + trap_Cvar_Set( "mp_currentTeam", va( "%d", currentTeam ) ); + } + + // set current player type + if ( mp_currentPlayerType.integer != cg.snap->ps.stats[ STAT_PLAYER_CLASS ] ) { + trap_Cvar_Set( "mp_currentPlayerType", va( "%i", cg.snap->ps.stats[ STAT_PLAYER_CLASS ] ) ); + } + + // set isSpectator + trap_Cvar_VariableStringBuffer( "ui_isSpectator", buf, 32 ); + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && cg.snap->ps.pm_type != PM_INTERMISSION ) { + trap_SendConsoleCommand( "+scores\n" ); // NERVE - SMF - blah + + if ( !atoi( buf ) ) { + trap_Cvar_Set( "ui_isSpectator", "1" ); + } + } else { + if ( atoi( buf ) ) { + trap_Cvar_Set( "ui_isSpectator", "0" ); + } + } + + trap_UI_Popup( "UIMENU_WM_LIMBO" ); +} + +static void CG_CloseLimbo_f( void ) { + trap_UI_ClosePopup( "UIMENU_WM_LIMBO" ); +} + +static void CG_LimboMessage_f( void ) { + char teamStr[80], classStr[80], weapStr[80]; + + Q_strncpyz( teamStr, CG_TranslateString( CG_Argv( 1 ) ), 80 ); + Q_strncpyz( classStr, CG_TranslateString( CG_Argv( 2 ) ), 80 ); + Q_strncpyz( weapStr, CG_TranslateString( CG_Argv( 3 ) ), 80 ); + + CG_PriorityCenterPrint( va( "%s %s %s %s %s.", CG_TranslateString( "You will spawn as an" ), + teamStr, classStr, CG_TranslateString( "with a" ), weapStr ), SCREEN_HEIGHT - ( SCREEN_HEIGHT * 0.25 ), SMALLCHAR_WIDTH, -1 ); +} + +static void CG_VoiceChat_f( void ) { + char chatCmd[64]; + + if ( cgs.gametype < GT_WOLF || trap_Argc() != 2 ) { + return; + } + + // NERVE - SMF - don't let spectators voice chat + // NOTE - This cg.snap will be the person you are following, but its just for intermission test + if ( cg.snap && ( cg.snap->ps.pm_type != PM_INTERMISSION ) ) { + if ( cgs.clientinfo[cg.clientNum].team == TEAM_SPECTATOR || cgs.clientinfo[cg.clientNum].team == TEAM_FREE ) { + CG_Printf( CG_TranslateString( "Can't voice chat as a spectator.\n" ) ); + return; + } + } + + trap_Argv( 1, chatCmd, 64 ); + + trap_SendConsoleCommand( va( "cmd vsay %s\n", chatCmd ) ); +} + +static void CG_TeamVoiceChat_f( void ) { + char chatCmd[64]; + + if ( cgs.gametype < GT_WOLF || trap_Argc() != 2 ) { + return; + } + + // NERVE - SMF - don't let spectators voice chat + // NOTE - This cg.snap will be the person you are following, but its just for intermission test + if ( cg.snap && ( cg.snap->ps.pm_type != PM_INTERMISSION ) ) { + if ( cgs.clientinfo[cg.clientNum].team == TEAM_SPECTATOR || cgs.clientinfo[cg.clientNum].team == TEAM_FREE ) { + CG_Printf( CG_TranslateString( "Can't team voice chat as a spectator.\n" ) ); + return; + } + } + + trap_Argv( 1, chatCmd, 64 ); + + trap_SendConsoleCommand( va( "cmd vsay_team %s\n", chatCmd ) ); +} + +static void CG_SetWeaponCrosshair_f( void ) { + char crosshair[64]; + + trap_Argv( 1, crosshair, 64 ); + cg.newCrosshairIndex = atoi( crosshair ) + 1; +} +// -NERVE - SMF + +/* +=================== +CG_DumpLocation_f + +Dump a target_location definition to a file +=================== +*/ +static void CG_DumpLocation_f( void ) { + char locfilename[MAX_QPATH]; + char locname[MAX_STRING_CHARS]; + char *extptr, *buffptr; + fileHandle_t f; + + // Check for argument + if ( trap_Argc() < 2 ) { + CG_Printf( "Usage: dumploc \n" ); + return; + } + trap_Args( locname, sizeof( locname ) ); + + // Open locations file + Q_strncpyz( locfilename, cgs.mapname, sizeof( locfilename ) ); + extptr = locfilename + strlen( locfilename ) - 4; + if ( extptr < locfilename || Q_stricmp( extptr, ".bsp" ) ) { + CG_Printf( "Unable to dump, unknown map name?\n" ); + return; + } + Q_strncpyz( extptr, ".loc", 5 ); + trap_FS_FOpenFile( locfilename, &f, FS_APPEND_SYNC ); + if ( !f ) { + CG_Printf( "Failed to open '%s' for writing.\n", locfilename ); + return; + } + + // Strip bad characters out + for ( buffptr = locname; *buffptr; buffptr++ ) + { + if ( *buffptr == '\n' ) { + *buffptr = ' '; + } else if ( *buffptr == '"' ) { + *buffptr = '\''; + } + } + // Kill any trailing space as well + if ( *( buffptr - 1 ) == ' ' ) { + *( buffptr - 1 ) = 0; + } + + // Build the entity definition + buffptr = va( "{\n\"classname\" \"target_location\"\n\"origin\" \"%i %i %i\"\n\"message\" \"%s\"\n}\n\n", + (int) cg.snap->ps.origin[0], (int) cg.snap->ps.origin[1], (int) cg.snap->ps.origin[2], locname ); + + // And write out/acknowledge + trap_FS_Write( buffptr, strlen( buffptr ), f ); + trap_FS_FCloseFile( f ); + CG_Printf( "Entity dumped to '%s' (%i %i %i).\n", locfilename, + (int) cg.snap->ps.origin[0], (int) cg.snap->ps.origin[1], (int) cg.snap->ps.origin[2] ); +} + + +typedef struct { + char *cmd; + void ( *function )( void ); +} consoleCommand_t; + +static consoleCommand_t commands[] = { + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, + { "nextframe", CG_TestModelNextFrame_f }, + { "prevframe", CG_TestModelPrevFrame_f }, + { "nextskin", CG_TestModelNextSkin_f }, + { "prevskin", CG_TestModelPrevSkin_f }, + { "viewpos", CG_Viewpos_f }, + { "+scores", CG_ScoresDown_f }, + { "-scores", CG_ScoresUp_f }, + { "+inventory", CG_InventoryDown_f }, + { "-inventory", CG_InventoryUp_f }, +// { "+zoom", CG_ZoomDown_f }, // (SA) zoom moved to a wbutton so server can determine weapon firing based on zoom status +// { "-zoom", CG_ZoomUp_f }, + { "zoomin", CG_ZoomIn_f }, + { "zoomout", CG_ZoomOut_f }, + { "sizeup", CG_SizeUp_f }, + { "sizedown", CG_SizeDown_f }, + { "weaplastused", CG_LastWeaponUsed_f }, + { "weapnextinbank", CG_NextWeaponInBank_f }, + { "weapprevinbank", CG_PrevWeaponInBank_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "weapalt", CG_AltWeapon_f }, + { "weapon", CG_Weapon_f }, + { "weaponbank", CG_WeaponBank_f }, + { "itemnext", CG_NextItem_f }, + { "itemprev", CG_PrevItem_f }, + { "item", CG_Item_f }, + { "tell_target", CG_TellTarget_f }, + { "tell_attacker", CG_TellAttacker_f }, + { "tcmd", CG_TargetCommand_f }, + { "loadhud", CG_LoadHud_f }, + { "loaddeferred", CG_LoadDeferredPlayers }, // spelling fixed (SA) +// { "camera", CG_Camera_f }, // duffy + { "fade", CG_Fade_f }, // duffy + { "loadhud", CG_LoadHud_f }, + + // NERVE - SMF + { "mp_QuickMessage", CG_QuickMessage_f }, + { "OpenLimboMenu", CG_OpenLimbo_f }, + { "CloseLimboMenu", CG_CloseLimbo_f }, + { "LimboMessage", CG_LimboMessage_f }, + { "VoiceChat", CG_VoiceChat_f }, + { "VoiceTeamChat", CG_TeamVoiceChat_f }, + { "SetWeaponCrosshair", CG_SetWeaponCrosshair_f }, + // -NERVE - SMF + + // Arnout + { "dumploc", CG_DumpLocation_f }, +}; + + +/* +================= +CG_ConsoleCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_ConsoleCommand( void ) { + const char *cmd; + int i; + + // Arnout - don't allow console commands until a snapshot is present + if ( !cg.snap ) { + return qfalse; + } + + cmd = CG_Argv( 0 ); + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + if ( !Q_stricmp( cmd, commands[i].cmd ) ) { + commands[i].function(); + return qtrue; + } + } + + return qfalse; +} + + +/* +================= +CG_InitConsoleCommands + +Let the client system know about all of our commands +so it can perform tab completion +================= +*/ +void CG_InitConsoleCommands( void ) { + int i; + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + trap_AddCommand( commands[i].cmd ); + } + + // + // the game server will interpret these commands, which will be automatically + // forwarded to the server after they are not recognized locally + // + trap_AddCommand( "kill" ); + trap_AddCommand( "say" ); + trap_AddCommand( "say_team" ); + trap_AddCommand( "say_limbo" ); // NERVE - SMF + trap_AddCommand( "tell" ); +// trap_AddCommand ("vsay"); +// trap_AddCommand ("vsay_team"); +// trap_AddCommand ("vtell"); +// trap_AddCommand ("vtaunt"); +// trap_AddCommand ("vosay"); +// trap_AddCommand ("vosay_team"); +// trap_AddCommand ("votell"); + trap_AddCommand( "give" ); + trap_AddCommand( "god" ); + trap_AddCommand( "notarget" ); + trap_AddCommand( "noclip" ); + trap_AddCommand( "team" ); + trap_AddCommand( "follow" ); + trap_AddCommand( "levelshot" ); + trap_AddCommand( "addbot" ); + trap_AddCommand( "setviewpos" ); + trap_AddCommand( "callvote" ); + trap_AddCommand( "vote" ); +// trap_AddCommand ("callteamvote"); +// trap_AddCommand ("teamvote"); + trap_AddCommand( "stats" ); +// trap_AddCommand ("teamtask"); + trap_AddCommand( "loaddeferred" ); // spelling fixed (SA) + +// trap_AddCommand ("startCamera"); +// trap_AddCommand ("stopCamera"); +// trap_AddCommand ("setCameraOrigin"); + + // Rafael + trap_AddCommand( "nofatigue" ); + + // NERVE - SMF + trap_AddCommand( "setspawnpt" ); + trap_AddCommand( "follownext" ); + trap_AddCommand( "followprev" ); + + trap_AddCommand( "start_match" ); + trap_AddCommand( "reset_match" ); + trap_AddCommand( "swap_teams" ); + // -NERVE - SMF +} diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c new file mode 100644 index 0000000..4ca9e40 --- /dev/null +++ b/src/cgame/cg_draw.c @@ -0,0 +1,3639 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + +#include "cg_local.h" +#include "../ui/ui_shared.h" + +//----(SA) added to make it easier to raise/lower our statsubar by only changing one thing +#define STATUSBARHEIGHT 452 +//----(SA) end + +extern displayContextDef_t cgDC; +menuDef_t *menuScoreboard = NULL; + +int sortedTeamPlayers[TEAM_MAXOVERLAY]; +int numSortedTeamPlayers; + +char systemChat[256]; +char teamChat1[256]; +char teamChat2[256]; + +// NERVE - SMF +void Controls_GetKeyAssignment( char *command, int *twokeys ); +char* BindingFromName( const char *cvar ); +void Controls_GetConfig( void ); +// -NERVE - SMF + +//////////////////////// +//////////////////////// +////// new hud stuff +/////////////////////// +/////////////////////// + +int CG_Text_Width( const char *text, float scale, int limit ) { + int count,len; + float out; + glyphInfo_t *glyph; + float useScale; + const char *s = text; + fontInfo_t *font = &cgDC.Assets.textFont; + if ( scale <= cg_smallFont.value ) { + font = &cgDC.Assets.smallFont; + } else if ( scale > cg_bigFont.value ) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + out = 0; + if ( text ) { + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(int)*s]; + out += glyph->xSkip; + s++; + count++; + } + } + } + return out * useScale; +} + +int CG_Text_Height( const char *text, float scale, int limit ) { + int len, count; + float max; + glyphInfo_t *glyph; + float useScale; + const char *s = text; + fontInfo_t *font = &cgDC.Assets.textFont; + if ( scale <= cg_smallFont.value ) { + font = &cgDC.Assets.smallFont; + } else if ( scale > cg_bigFont.value ) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + max = 0; + if ( text ) { + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(int)*s]; + if ( max < glyph->height ) { + max = glyph->height; + } + s++; + count++; + } + } + } + return max * useScale; +} + +void CG_Text_PaintChar( float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader ) { + float w, h; + w = width * scale; + h = height * scale; + CG_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); +} + +void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + float useScale; + fontInfo_t *font = &cgDC.Assets.textFont; + if ( scale <= cg_smallFont.value ) { + font = &cgDC.Assets.smallFont; + } else if ( scale > cg_bigFont.value ) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + + color[3] *= cg_hudAlpha.value; // (SA) adjust for cg_hudalpha + + if ( text ) { + const char *s = text; + trap_R_SetColor( color ); + memcpy( &newColor[0], &color[0], sizeof( vec4_t ) ); + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + glyph = &font->glyphs[(int)*s]; + //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; + //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if ( style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE ) { + int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; + colorBlack[3] = newColor[3]; + trap_R_SetColor( colorBlack ); + CG_Text_PaintChar( x + ofs, y - yadj + ofs, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + colorBlack[3] = 1.0; + trap_R_SetColor( newColor ); + } + CG_Text_PaintChar( x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + // CG_DrawPic(x, y - yadj, scale * cgDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * cgDC.Assets.textFont.glyphs[text[i]].imageHeight, cgDC.Assets.textFont.glyphs[text[i]].glyph); + x += ( glyph->xSkip * useScale ) + adjust; + s++; + count++; + } + } + trap_R_SetColor( NULL ); + } +} + +// NERVE - SMF - added back in +int CG_DrawFieldWidth( int x, int y, int width, int value, int charWidth, int charHeight ) { + char num[16], *ptr; + int l; + int frame; + int totalwidth = 0; + + if ( width < 1 ) { + return 0; + } + + // draw number string + if ( width > 5 ) { + width = 5; + } + + switch ( width ) { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf( num, sizeof( num ), "%i", value ); + l = strlen( num ); + if ( l > width ) { + l = width; + } + + ptr = num; + while ( *ptr && l ) + { + if ( *ptr == '-' ) { + frame = STAT_MINUS; + } else { + frame = *ptr - '0'; + } + + totalwidth += charWidth; + ptr++; + l--; + } + + return totalwidth; +} + +int CG_DrawField( int x, int y, int width, int value, int charWidth, int charHeight, qboolean dodrawpic, qboolean leftAlign ) { + char num[16], *ptr; + int l; + int frame; + int startx; + + if ( width < 1 ) { + return 0; + } + + // draw number string + if ( width > 5 ) { + width = 5; + } + + switch ( width ) { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf( num, sizeof( num ), "%i", value ); + l = strlen( num ); + if ( l > width ) { + l = width; + } + + // NERVE - SMF + if ( !leftAlign ) { + x -= 2 + charWidth * ( l ); + } + + startx = x; + + ptr = num; + while ( *ptr && l ) + { + if ( *ptr == '-' ) { + frame = STAT_MINUS; + } else { + frame = *ptr - '0'; + } + + if ( dodrawpic ) { + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[frame] ); + } + x += charWidth; + ptr++; + l--; + } + + return startx; +} +// -NERVE - SMF + +/* +================ +CG_Draw3DModel + +================ +*/ +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) { + refdef_t refdef; + refEntity_t ent; + + if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { + return; + } + + CG_AdjustFrom640( &x, &y, &w, &h ); + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.hModel = model; + ent.customSkin = skin; + ent.renderfx = RF_NOSHADOW; // no stencil shadows + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.fov_x = 30; + refdef.fov_y = 30; + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.time = cg.time; + + trap_R_ClearScene(); + trap_R_AddRefEntityToScene( &ent ); + trap_R_RenderScene( &refdef ); +} + +/* +================ +CG_DrawHead + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { + clipHandle_t cm; + clientInfo_t *ci; + float len; + vec3_t origin; + vec3_t mins, maxs; + + ci = &cgs.clientinfo[ clientNum ]; + + if ( cg_draw3dIcons.integer ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->modelInfo->headOffset, origin ); + + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles ); +// } else if ( cg_drawIcons.integer ) { +// CG_DrawPic( x, y, w, h, ci->modelIcon ); + } + + // if they are deferred, draw a cross out + if ( ci->deferred ) { + CG_DrawPic( x, y, w, h, cgs.media.deferShader ); + } +} + +/* +================ +CG_DrawFlagModel + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawFlagModel( float x, float y, float w, float h, int team ) { + qhandle_t cm; + float len; + vec3_t origin, angles; + vec3_t mins, maxs; + + VectorClear( angles ); + + cm = cgs.media.redFlagModel; + + // offset the origin y and z to center the flag + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the flag nearly fills the box + // assume heads are taller than wide + len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + angles[YAW] = 60 * sin( cg.time / 2000.0 );; + + CG_Draw3DModel( x, y, w, h, + team == TEAM_RED ? cgs.media.redFlagModel : cgs.media.blueFlagModel, + 0, origin, angles ); +} + + +/* +============== +CG_DrawKeyModel +============== +*/ +void CG_DrawKeyModel( int keynum, float x, float y, float w, float h, int fadetime ) { + qhandle_t cm; + float len; + vec3_t origin, angles; + vec3_t mins, maxs; + + VectorClear( angles ); + + cm = cg_items[keynum].models[0]; + + // offset the origin y and z to center the model + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + +// len = 0.5 * ( maxs[2] - mins[2] ); + len = 0.75 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + angles[YAW] = 30 * sin( cg.time / 2000.0 );; + + CG_Draw3DModel( x, y, w, h, cg_items[keynum].models[0], 0, origin, angles ); +} + +/* +================ +CG_DrawTeamBackground + +================ +*/ +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) { + vec4_t hcolor; + + hcolor[3] = alpha; + if ( team == TEAM_RED ) { + hcolor[0] = 1; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( team == TEAM_BLUE ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 1; + } else { + return; + } + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); +} + +/* +=========================================================================================== + + UPPER RIGHT CORNER + +=========================================================================================== +*/ + +#define UPPERRIGHT_X 500 +/* +================== +CG_DrawSnapshot +================== +*/ +static float CG_DrawSnapshot( float y ) { + char *s; + int w; + + s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, + cg.latestSnapshotNum, cgs.serverCommandSequence ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( UPPERRIGHT_X - w, y + 2, s, 1.0F ); + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 4 +static float CG_DrawFPS( float y ) { + char *s; + int w; + static int previousTimes[FPS_FRAMES]; + static int index; + int i, total; + int fps; + static int previous; + int t, frameTime; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = trap_Milliseconds(); + frameTime = t - previous; + previous = t; + + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + if ( index > FPS_FRAMES ) { + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + fps = 1000 * FPS_FRAMES / total; + + s = va( "%ifps", fps ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( UPPERRIGHT_X - w, y + 2, s, 1.0F ); + } + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================= +CG_DrawTimer +================= +*/ +static float CG_DrawTimer( float y ) { + char *s; + int w; + int mins, seconds, tens; + int msec; + + // NERVE - SMF - draw time remaining in multiplayer + if ( cgs.gametype >= GT_WOLF ) { + msec = ( cgs.timelimit * 60.f * 1000.f ) - ( cg.time - cgs.levelStartTime ); + } else { + msec = cg.time - cgs.levelStartTime; + } + // -NERVE - SMF + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + if ( msec < 0 ) { + s = va( "Sudden Death" ); + } else { + s = va( "%i:%i%i", mins, tens, seconds ); + } + + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( UPPERRIGHT_X - w, y + 2, s, 1.0F ); + + return y + BIGCHAR_HEIGHT + 4; +} + + +/* +================= +CG_DrawTeamOverlay +================= +*/ + +int maxCharsBeforeOverlay; + +// set in CG_ParseTeamInfo +int sortedTeamPlayers[TEAM_MAXOVERLAY]; +int numSortedTeamPlayers; + +#define TEAM_OVERLAY_MAXNAME_WIDTH 16 +#define TEAM_OVERLAY_MAXLOCATION_WIDTH 20 + +static float CG_DrawTeamOverlay( float y ) { + int x, w, h, xx; + int i, len; + const char *p; + vec4_t hcolor; + int pwidth, lwidth; + int plyrs; + char st[16]; + clientInfo_t *ci; + // NERVE - SMF + char classType[2] = { 0, 0 }; + int val; + vec4_t deathcolor, damagecolor; // JPW NERVE + float *pcolor; + // -NERVE - SMF + + deathcolor[0] = 1; + deathcolor[1] = 0; + deathcolor[2] = 0; + deathcolor[3] = cg_hudAlpha.value; + damagecolor[0] = 1; + damagecolor[1] = 1; + damagecolor[2] = 0; + damagecolor[3] = cg_hudAlpha.value; + maxCharsBeforeOverlay = 80; + + if ( !cg_drawTeamOverlay.integer ) { + return y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && + cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { + return y; // Not on any team + + } + plyrs = 0; + + // max player name width + pwidth = 0; + for ( i = 0; i < numSortedTeamPlayers; i++ ) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM] ) { + plyrs++; + len = CG_DrawStrlen( ci->name ); + if ( len > pwidth ) { + pwidth = len; + } + } + } + + if ( !plyrs ) { + return y; + } + + if ( pwidth > TEAM_OVERLAY_MAXNAME_WIDTH ) { + pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; + } + +#if 1 + // max location name width + lwidth = 0; + if ( cg_drawTeamOverlay.integer > 1 ) { + for ( i = 0; i < numSortedTeamPlayers; i++ ) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && + ci->team == cg.snap->ps.persistant[PERS_TEAM] && + CG_ConfigString( CS_LOCATIONS + ci->location ) ) { + len = CG_DrawStrlen( CG_TranslateString( CG_ConfigString( CS_LOCATIONS + ci->location ) ) ); + if ( len > lwidth ) { + lwidth = len; + } + } + } + } +#else + // max location name width + lwidth = 0; + for ( i = 1; i < MAX_LOCATIONS; i++ ) { + p = CG_TranslateString( CG_ConfigString( CS_LOCATIONS + i ) ); + if ( p && *p ) { + len = CG_DrawStrlen( p ); + if ( len > lwidth ) { + lwidth = len; + } + } + } +#endif + + if ( lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH ) { + lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; + } + + if ( cg_drawTeamOverlay.integer > 1 ) { + w = ( pwidth + lwidth + 3 + 7 ) * TINYCHAR_WIDTH; // JPW NERVE was +4+7 + } else { + w = ( pwidth + lwidth + 8 ) * TINYCHAR_WIDTH; // JPW NERVE was +4+7 + + } + x = 640 - w - 4; // JPW was -32 + h = plyrs * TINYCHAR_HEIGHT; + + // DHM - Nerve :: Set the max characters that can be printed before the left edge + maxCharsBeforeOverlay = ( x / TINYCHAR_WIDTH ) - 1; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 0.5f; // was 0.38 instead of 0.5 JPW NERVE + hcolor[1] = 0.25f; + hcolor[2] = 0.25f; + hcolor[3] = 0.25f * cg_hudAlpha.value; + } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) + hcolor[0] = 0.25f; + hcolor[1] = 0.25f; + hcolor[2] = 0.5f; + hcolor[3] = 0.25f * cg_hudAlpha.value; + } + + CG_FillRect( x,y,w,h,hcolor ); + VectorSet( hcolor, 0.4f, 0.4f, 0.4f ); + hcolor[3] = cg_hudAlpha.value; + CG_DrawRect( x - 1, y, w + 2, h + 2, 1, hcolor ); + + + for ( i = 0; i < numSortedTeamPlayers; i++ ) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM] ) { + + // NERVE - SMF + // determine class type + val = cg_entities[ ci->clientNum ].currentState.teamNum; + if ( val == 0 ) { + classType[0] = 'S'; + } else if ( val == 1 ) { + classType[0] = 'M'; + } else if ( val == 2 ) { + classType[0] = 'E'; + } else if ( val == 3 ) { + classType[0] = 'L'; + } else { + classType[0] = 'S'; + } + + Com_sprintf( st, sizeof( st ), "%s", CG_TranslateString( classType ) ); + + xx = x + TINYCHAR_WIDTH; + + hcolor[0] = hcolor[1] = 1.0; + hcolor[2] = 0.0; + hcolor[3] = cg_hudAlpha.value; + + CG_DrawStringExt( xx, y, + st, hcolor, qtrue, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 1 ); + + hcolor[0] = hcolor[1] = hcolor[2] = 1.0; + hcolor[3] = cg_hudAlpha.value; + + xx = x + 3 * TINYCHAR_WIDTH; + + // JPW NERVE + if ( ci->health > 80 ) { + pcolor = hcolor; + } else if ( ci->health > 0 ) { + pcolor = damagecolor; + } else { + pcolor = deathcolor; + } + // jpw + + CG_DrawStringExt( xx, y, + ci->name, pcolor, qtrue, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH ); + + if ( lwidth ) { + p = CG_ConfigString( CS_LOCATIONS + ci->location ); + if ( !p || !*p ) { + p = "unknown"; + } + p = CG_TranslateString( p ); + len = CG_DrawStrlen( p ); + if ( len > lwidth ) { + len = lwidth; + } + + xx = x + TINYCHAR_WIDTH * 5 + TINYCHAR_WIDTH * pwidth + + ( ( lwidth / 2 - len / 2 ) * TINYCHAR_WIDTH ); + CG_DrawStringExt( xx, y, + p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + TEAM_OVERLAY_MAXLOCATION_WIDTH ); + } + + + Com_sprintf( st, sizeof( st ), "%3i", ci->health ); // JPW NERVE pulled class stuff since it's at top now + + if ( cg_drawTeamOverlay.integer > 1 ) { + xx = x + TINYCHAR_WIDTH * 6 + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; + } else { + xx = x + TINYCHAR_WIDTH * 4 + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; + } + + CG_DrawStringExt( xx, y, + st, pcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 3 ); + + y += TINYCHAR_HEIGHT; + } + } + + return y; +} + + +/* +===================== +CG_DrawUpperRight + +===================== +*/ +static void CG_DrawUpperRight( void ) { + float y; + + y = 0; // JPW NERVE move team overlay below obits, even with timer on left + + if ( cgs.gametype >= GT_TEAM ) { + y = CG_DrawTeamOverlay( y ); + } + if ( cg_drawSnapshot.integer ) { + y = CG_DrawSnapshot( y ); + } + if ( cg_drawFPS.integer ) { + y = CG_DrawFPS( y ); + } + if ( cg_drawTimer.integer ) { + y = CG_DrawTimer( y ); + } +// (SA) disabling drawattacker for the time being +// if ( cg_drawAttacker.integer ) { +// y = CG_DrawAttacker( y ); +// } +//----(SA) end +} + +/* +=========================================================================================== + + LOWER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================= +CG_DrawTeamInfo +================= +*/ +static void CG_DrawTeamInfo( void ) { + int w, h; + int i, len; + vec4_t hcolor; + int chatHeight; + float alphapercent; + +#define CHATLOC_Y 385 // bottom end +#define CHATLOC_X 0 + + if ( cg_teamChatHeight.integer < TEAMCHAT_HEIGHT ) { + chatHeight = cg_teamChatHeight.integer; + } else { + chatHeight = TEAMCHAT_HEIGHT; + } + if ( chatHeight <= 0 ) { + return; // disabled + + } + if ( cgs.teamLastChatPos != cgs.teamChatPos ) { + if ( cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer ) { + cgs.teamLastChatPos++; + } + + h = ( cgs.teamChatPos - cgs.teamLastChatPos ) * TINYCHAR_HEIGHT; + + w = 0; + + for ( i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++ ) { + len = CG_DrawStrlen( cgs.teamChatMsgs[i % chatHeight] ); + if ( len > w ) { + w = len; + } + } + w *= TINYCHAR_WIDTH; + w += TINYCHAR_WIDTH * 2; + +// JPW NERVE rewritten to support first pass at fading chat messages + for ( i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i-- ) { + alphapercent = 1.0f - ( cg.time - cgs.teamChatMsgTimes[i % chatHeight] ) / (float)( cg_teamChatTime.integer ); + if ( alphapercent > 1.0f ) { + alphapercent = 1.0f; + } else if ( alphapercent < 0.f ) { + alphapercent = 0.f; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1; + hcolor[1] = 0; + hcolor[2] = 0; +// hcolor[3] = 0.33; + } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 1; +// hcolor[3] = 0.33; + } else { + hcolor[0] = 0; + hcolor[1] = 1; + hcolor[2] = 0; +// hcolor[3] = 0.33; + } + + hcolor[3] = 0.33f * alphapercent; + + trap_R_SetColor( hcolor ); + CG_DrawPic( CHATLOC_X, CHATLOC_Y - ( cgs.teamChatPos - i ) * TINYCHAR_HEIGHT, 640, TINYCHAR_HEIGHT, cgs.media.teamStatusBar ); + + hcolor[0] = hcolor[1] = hcolor[2] = 1.0; + hcolor[3] = alphapercent; + trap_R_SetColor( hcolor ); + + CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH, + CHATLOC_Y - ( cgs.teamChatPos - i ) * TINYCHAR_HEIGHT, + cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); +// CG_DrawSmallString( CHATLOC_X + SMALLCHAR_WIDTH, +// CHATLOC_Y - (cgs.teamChatPos - i)*SMALLCHAR_HEIGHT, +// cgs.teamChatMsgs[i % TEAMCHAT_HEIGHT], 1.0F ); + } +// jpw + } +} + +//----(SA) modified +/* +=================== +CG_DrawPickupItem +=================== +*/ +static void CG_DrawPickupItem( void ) { + int value; + float *fadeColor; + char pickupText[256]; + float color[4]; + const char *s; + + value = cg.itemPickup; + if ( value ) { + fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); + if ( fadeColor ) { + CG_RegisterItemVisuals( value ); + + //----(SA) so we don't pick up all sorts of items and have it print "0 " + if ( bg_itemlist[ value ].giType == IT_AMMO || bg_itemlist[ value ].giType == IT_HEALTH || bg_itemlist[value].giType == IT_POWERUP ) { + if ( bg_itemlist[ value ].world_model[2] ) { // this is a multi-stage item + // FIXME: print the correct amount for multi-stage + Com_sprintf( pickupText, sizeof( pickupText ), "%s", bg_itemlist[ value ].pickup_name ); + } else { + Com_sprintf( pickupText, sizeof( pickupText ), "%i %s", bg_itemlist[ value ].gameskillnumber[( cg_gameSkill.integer ) - 1], bg_itemlist[ value ].pickup_name ); + } + } else { + if ( !Q_stricmp( "Blue Flag", bg_itemlist[ value ].pickup_name ) ) { + Com_sprintf( pickupText, sizeof( pickupText ), "%s", "Objective" ); + } else { + Com_sprintf( pickupText, sizeof( pickupText ), "%s", bg_itemlist[ value ].pickup_name ); + } + } + + s = CG_TranslateString( pickupText ); + + color[0] = color[1] = color[2] = 1.0; + color[3] = fadeColor[0]; + CG_DrawStringExt2( 34, 388, s, color, qfalse, qtrue, 10, 10, 0 ); // JPW NERVE moved per atvi req + + trap_R_SetColor( NULL ); + } + } +} +//----(SA) end + +/* +================= +CG_DrawNotify +================= +*/ +#define NOTIFYLOC_Y 42 // bottom end +#define NOTIFYLOC_X 0 + +static void CG_DrawNotify( void ) { + int w, h; + int i, len; + vec4_t hcolor; + int chatHeight; + float alphapercent; + char var[MAX_TOKEN_CHARS]; + float notifytime = 1.0f; + + trap_Cvar_VariableStringBuffer( "con_notifytime", var, sizeof( var ) ); + notifytime = atof( var ) * 1000; + + if ( notifytime <= 100.f ) { + notifytime = 100.0f; + } + + chatHeight = NOTIFY_HEIGHT; + + if ( cgs.notifyLastPos != cgs.notifyPos ) { + if ( cg.time - cgs.notifyMsgTimes[cgs.notifyLastPos % chatHeight] > notifytime ) { + cgs.notifyLastPos++; + } + + h = ( cgs.notifyPos - cgs.notifyLastPos ) * TINYCHAR_HEIGHT; + + w = 0; + + for ( i = cgs.notifyLastPos; i < cgs.notifyPos; i++ ) { + len = CG_DrawStrlen( cgs.notifyMsgs[i % chatHeight] ); + if ( len > w ) { + w = len; + } + } + w *= TINYCHAR_WIDTH; + w += TINYCHAR_WIDTH * 2; + + if ( maxCharsBeforeOverlay <= 0 ) { + maxCharsBeforeOverlay = 80; + } + + for ( i = cgs.notifyPos - 1; i >= cgs.notifyLastPos; i-- ) { + alphapercent = 1.0f - ( ( cg.time - cgs.notifyMsgTimes[i % chatHeight] ) / notifytime ); + if ( alphapercent > 0.5f ) { + alphapercent = 1.0f; + } else { + alphapercent *= 2; + } + + if ( alphapercent < 0.f ) { + alphapercent = 0.f; + } + + hcolor[0] = hcolor[1] = hcolor[2] = 1.0; + hcolor[3] = alphapercent; + trap_R_SetColor( hcolor ); + + CG_DrawStringExt( NOTIFYLOC_X + TINYCHAR_WIDTH, + NOTIFYLOC_Y - ( cgs.notifyPos - i ) * TINYCHAR_HEIGHT, + cgs.notifyMsgs[i % chatHeight], hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, maxCharsBeforeOverlay ); + } + } +} + +/* +=============================================================================== + +LAGOMETER + +=============================================================================== +*/ + +#define LAG_SAMPLES 128 + + +typedef struct { + int frameSamples[LAG_SAMPLES]; + int frameCount; + int snapshotFlags[LAG_SAMPLES]; + int snapshotSamples[LAG_SAMPLES]; + int snapshotCount; +} lagometer_t; + +lagometer_t lagometer; + +/* +============== +CG_AddLagometerFrameInfo + +Adds the current interpolate / extrapolate bar for this frame +============== +*/ +void CG_AddLagometerFrameInfo( void ) { + int offset; + + offset = cg.time - cg.latestSnapshotTime; + lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1 ) ] = offset; + lagometer.frameCount++; +} + +/* +============== +CG_AddLagometerSnapshotInfo + +Each time a snapshot is received, log its ping time and +the number of snapshots that were dropped before it. + +Pass NULL for a dropped packet. +============== +*/ +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { + // dropped packet + if ( !snap ) { + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = -1; + lagometer.snapshotCount++; + return; + } + + // add this snapshot's info + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->ping; + lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->snapFlags; + lagometer.snapshotCount++; +} + +/* +============== +CG_DrawDisconnect + +Should we draw something differnet for long lag vs no packets? +============== +*/ +static void CG_DrawDisconnect( void ) { + float x, y; + int cmdNum; + usercmd_t cmd; + const char *s; + int w; // bk010215 - FIXME char message[1024]; + + // draw the phone jack if we are completely past our buffers + cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &cmd ); + if ( cmd.serverTime <= cg.snap->ps.commandTime + || cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME + return; + } + + // also add text in center of screen + s = CG_TranslateString( "Connection Interrupted" ); // bk 010215 - FIXME + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w / 2, 100, s, 1.0F ); + + // blink the icon + if ( ( cg.time >> 9 ) & 1 ) { + return; + } + + x = 640 - 72; + y = 480 - 52; + + CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader( "gfx/2d/net.tga" ) ); +} + + +#define MAX_LAGOMETER_PING 900 +#define MAX_LAGOMETER_RANGE 300 + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( void ) { + int a, x, y, i; + float v; + float ax, ay, aw, ah, mid, range; + int color; + float vscale; + + if ( !cg_lagometer.integer || cgs.localServer ) { +// if(0) { + CG_DrawDisconnect(); + return; + } + + // + // draw the graph + // + x = 640 - 55; + y = 480 - 140; + + trap_R_SetColor( NULL ); + CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); + + ax = x; + ay = y; + aw = 48; + ah = 48; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + color = -1; + range = ah / 3; + mid = ay + range; + + vscale = range / MAX_LAGOMETER_RANGE; + + // draw the frame interpoalte / extrapolate graph + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.frameCount - 1 - a ) & ( LAG_SAMPLES - 1 ); + v = lagometer.frameSamples[i]; + v *= vscale; + if ( v > 0 ) { + if ( color != 1 ) { + color = 1; + trap_R_SetColor( g_color_table[ColorIndex( COLOR_YELLOW )] ); + } + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 2 ) { + color = 2; + trap_R_SetColor( g_color_table[ColorIndex( COLOR_BLUE )] ); + } + v = -v; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + // draw the snapshot latency / drop graph + range = ah / 2; + vscale = range / MAX_LAGOMETER_PING; + + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.snapshotCount - 1 - a ) & ( LAG_SAMPLES - 1 ); + v = lagometer.snapshotSamples[i]; + if ( v > 0 ) { + if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { + if ( color != 5 ) { + color = 5; // YELLOW for rate delay + trap_R_SetColor( g_color_table[ColorIndex( COLOR_YELLOW )] ); + } + } else { + if ( color != 3 ) { + color = 3; + trap_R_SetColor( g_color_table[ColorIndex( COLOR_GREEN )] ); + } + } + v = v * vscale; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 4 ) { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ColorIndex( COLOR_RED )] ); + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_DrawBigString( ax, ay, "snc", 1.0 ); + } + + CG_DrawDisconnect(); +} + + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + + +/* +============== +CG_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +#define CP_LINEWIDTH 55 // NERVE - SMF + +void CG_CenterPrint( const char *str, int y, int charWidth ) { + char *s; + int i, len; // NERVE - SMF + qboolean neednewline = qfalse; // NERVE - SMF + int priority = 0; + + // NERVE - SMF - don't draw if this print message is less important + if ( cg.centerPrintTime && priority < cg.centerPrintPriority ) { + return; + } + + Q_strncpyz( cg.centerPrint, str, sizeof( cg.centerPrint ) ); + cg.centerPrintPriority = priority; // NERVE - SMF + + // NERVE - SMF - turn spaces into newlines, if we've run over the linewidth + len = strlen( cg.centerPrint ); + for ( i = 0; i < len; i++ ) { + + // NOTE: subtract a few chars here so long words still get displayed properly + if ( i % ( CP_LINEWIDTH - 20 ) == 0 && i > 0 ) { + neednewline = qtrue; + } + if ( cg.centerPrint[i] == ' ' && neednewline ) { + cg.centerPrint[i] = '\n'; + neednewline = qfalse; + } + } + // -NERVE - SMF + + cg.centerPrintTime = cg.time; + cg.centerPrintY = y; + cg.centerPrintCharWidth = charWidth; + + // count the number of lines for centering + cg.centerPrintLines = 1; + s = cg.centerPrint; + while ( *s ) { + if ( *s == '\n' ) { + cg.centerPrintLines++; + } + s++; + } +} + +// NERVE - SMF +/* +============== +CG_PriorityCenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_PriorityCenterPrint( const char *str, int y, int charWidth, int priority ) { + char *s; + int i, len; // NERVE - SMF + qboolean neednewline = qfalse; // NERVE - SMF + + // NERVE - SMF - don't draw if this print message is less important + if ( cg.centerPrintTime && priority < cg.centerPrintPriority ) { + return; + } + + Q_strncpyz( cg.centerPrint, str, sizeof( cg.centerPrint ) ); + cg.centerPrintPriority = priority; // NERVE - SMF + + // NERVE - SMF - turn spaces into newlines, if we've run over the linewidth + len = strlen( cg.centerPrint ); + for ( i = 0; i < len; i++ ) { + + // NOTE: subtract a few chars here so long words still get displayed properly + if ( i % ( CP_LINEWIDTH - 20 ) == 0 && i > 0 ) { + neednewline = qtrue; + } + if ( cg.centerPrint[i] == ' ' && neednewline ) { + cg.centerPrint[i] = '\n'; + neednewline = qfalse; + } + } + // -NERVE - SMF + + cg.centerPrintTime = cg.time + 2000; + cg.centerPrintY = y; + cg.centerPrintCharWidth = charWidth; + + // count the number of lines for centering + cg.centerPrintLines = 1; + s = cg.centerPrint; + while ( *s ) { + if ( *s == '\n' ) { + cg.centerPrintLines++; + } + s++; + } +} +// -NERVE - SMF + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) { + char *start; + int l; + int x, y, w; + float *color; + + if ( !cg.centerPrintTime ) { + return; + } + + color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); + if ( !color ) { + cg.centerPrintTime = 0; + cg.centerPrintPriority = 0; + return; + } + + trap_R_SetColor( color ); + + start = cg.centerPrint; + + y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; + + while ( 1 ) { + char linebuffer[1024]; + + for ( l = 0; l < CP_LINEWIDTH; l++ ) { // NERVE - SMF - added CP_LINEWIDTH + if ( !start[l] || start[l] == '\n' ) { + break; + } + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + + w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer ); + + x = ( SCREEN_WIDTH - w ) / 2; + + CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, + cg.centerPrintCharWidth, (int)( cg.centerPrintCharWidth * 1.5 ), 0 ); + + y += cg.centerPrintCharWidth * 1.5; + + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIRS + +================================================================================ +*/ + +/* +============== +CG_DrawWeapReticle +============== +*/ +static void CG_DrawWeapReticle( void ) { + qboolean snooper, sniper, fg; + vec4_t color = {0, 0, 0, 1}; + + // DHM - Nerve :: So that we will draw reticle + if ( cgs.gametype >= GT_WOLF && ( ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || cg.demoPlayback ) ) { + sniper = (qboolean)( cg.snap->ps.weapon == WP_SNIPERRIFLE ); + snooper = (qboolean)( cg.snap->ps.weapon == WP_SNOOPERSCOPE ); + fg = (qboolean)( cg.snap->ps.weapon == WP_FG42SCOPE ); + } else { + sniper = (qboolean)( cg.weaponSelect == WP_SNIPERRIFLE ); + snooper = (qboolean)( cg.weaponSelect == WP_SNOOPERSCOPE ); + fg = (qboolean)( cg.weaponSelect == WP_FG42SCOPE ); + } + + if ( sniper ) { + if ( cg_reticles.integer ) { + + // sides + CG_FillRect( 0, 0, 80, 480, color ); + CG_FillRect( 560, 0, 80, 480, color ); + + // center + if ( cgs.media.reticleShaderSimple ) { + CG_DrawPic( 80, 0, 480, 480, cgs.media.reticleShaderSimple ); + } + + // hairs + CG_FillRect( 84, 239, 177, 2, color ); // left + CG_FillRect( 320, 242, 1, 58, color ); // center top + CG_FillRect( 319, 300, 2, 178, color ); // center bot + CG_FillRect( 380, 239, 177, 2, color ); // right + } + } else if ( snooper ) { + if ( cg_reticles.integer ) { + + // sides + CG_FillRect( 0, 0, 80, 480, color ); + CG_FillRect( 560, 0, 80, 480, color ); + + // center + +//----(SA) added + // DM didn't like how bright it gets + { + vec4_t hcolor = {0.7, .8, 0.7, 0}; // greenish + float brt; + + brt = cg_reticleBrightness.value; + Com_Clamp( 0.0f, 1.0f, brt ); + hcolor[0] *= brt; + hcolor[1] *= brt; + hcolor[2] *= brt; + trap_R_SetColor( hcolor ); + } +//----(SA) end + + if ( cgs.media.snooperShaderSimple ) { + CG_DrawPic( 80, 0, 480, 480, cgs.media.snooperShaderSimple ); + } + + // hairs + + CG_FillRect( 310, 120, 20, 1, color ); // ----- + CG_FillRect( 300, 160, 40, 1, color ); // ------------- + CG_FillRect( 310, 200, 20, 1, color ); // ----- + + CG_FillRect( 140, 239, 360, 2, color ); // horiz --------------------------- + + CG_FillRect( 310, 280, 20, 1, color ); // ----- + CG_FillRect( 300, 320, 40, 1, color ); // ------------- + CG_FillRect( 310, 360, 20, 1, color ); // ----- + + + + CG_FillRect( 400, 220, 1, 40, color ); // l + + CG_FillRect( 319, 60, 2, 360, color ); // vert + + CG_FillRect( 240, 220, 1, 40, color ); // r + } + } +} + +/* +============== +CG_DrawBinocReticle +============== +*/ +static void CG_DrawBinocReticle( void ) { + if ( cg_reticles.integer ) { + if ( cg_reticleType.integer == 0 ) { + if ( cgs.media.binocShader ) { + CG_DrawPic( 0, 0, 640, 480, cgs.media.binocShader ); + } + } else if ( cg_reticleType.integer == 1 ) { + // an alternative. This gives nice sharp lines at the expense of a few extra polys + vec4_t color; + color[0] = color[1] = color[2] = 0; + color[3] = 1; + + if ( cgs.media.binocShaderSimple ) { + CG_DrawPic( 0, 0, 640, 480, cgs.media.binocShaderSimple ); + } + + CG_FillRect( 146, 239, 348, 1, color ); + + CG_FillRect( 188, 234, 1, 13, color ); // ll + CG_FillRect( 234, 226, 1, 29, color ); // l + CG_FillRect( 274, 234, 1, 13, color ); // lr + CG_FillRect( 320, 213, 1, 55, color ); // center + CG_FillRect( 360, 234, 1, 13, color ); // rl + CG_FillRect( 406, 226, 1, 29, color ); // r + CG_FillRect( 452, 234, 1, 13, color ); // rr + } + } +} + +void CG_FinishWeaponChange( int lastweap, int newweap ); // JPW NERVE + + +/* +================= +CG_DrawCrosshair +================= +*/ +static void CG_DrawCrosshair( void ) { + float w, h; + qhandle_t hShader; + float f; + float x, y; + int weapnum; // DHM - Nerve + + if ( cg.renderingThirdPerson ) { + return; + } + + // DHM - Nerve :: show reticle in limbo and spectator + if ( cgs.gametype >= GT_WOLF && ( ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || cg.demoPlayback ) ) { + weapnum = cg.snap->ps.weapon; + } else { + weapnum = cg.weaponSelect; + } + + switch ( weapnum ) { + + // weapons that get no reticle + case WP_NONE: // no weapon, no crosshair + if ( cg.zoomedBinoc ) { + CG_DrawBinocReticle(); + } + + if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + return; + } + break; + + // special reticle for weapon + case WP_SNIPERRIFLE: + if ( !( cg.snap->ps.eFlags & EF_MG42_ACTIVE ) ) { + + // JPW NERVE -- don't let players run with rifles -- speed 80 == crouch, 128 == walk, 256 == run + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) { + if ( VectorLength( cg.snap->ps.velocity ) > 127.0f ) { + if ( cg.snap->ps.weapon == WP_SNIPERRIFLE ) { + CG_FinishWeaponChange( WP_SNIPERRIFLE, WP_MAUSER ); + } + if ( cg.snap->ps.weapon == WP_SNOOPERSCOPE ) { + CG_FinishWeaponChange( WP_SNOOPERSCOPE, WP_GARAND ); + } + } + } + // jpw + + CG_DrawWeapReticle(); + return; + } + break; + default: + break; + } + + // using binoculars + if ( cg.zoomedBinoc ) { + CG_DrawBinocReticle(); + return; + } + + if ( cg_drawCrosshair.integer < 0 ) { //----(SA) moved down so it doesn't keep the scoped weaps from drawing reticles + return; + } + + if ( cg.snap->ps.leanf ) { // no crosshair while leaning + return; + } + + // set color based on health + if ( cg_crosshairHealth.integer ) { + vec4_t hcolor; + + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + } else { + trap_R_SetColor( NULL ); + } + + w = h = cg_crosshairSize.value; + + // RF, crosshair size represents aim spread + f = (float)cg.snap->ps.aimSpreadScale / 255.0; + w *= ( 1 + f * 2.0 ); + h *= ( 1 + f * 2.0 ); + + x = cg_crosshairX.integer; + y = cg_crosshairY.integer; + CG_AdjustFrom640( &x, &y, &w, &h ); + + hShader = cgs.media.crosshairShader[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ]; + + // NERVE - SMF - modified, fixes crosshair offset in shifted/scaled 3d views + if ( cg.limboMenu ) { // JPW NERVE + trap_R_DrawStretchPic( x /*+ cg.refdef.x*/ + 0.5 * ( cg.refdef.width - w ), + y /*+ cg.refdef.y*/ + 0.5 * ( cg.refdef.height - h ), + w, h, 0, 0, 1, 1, hShader ); + } else { + trap_R_DrawStretchPic( x + 0.5 * ( cgs.glconfig.vidWidth - w ), // JPW NERVE for scaled-down main windows + y + 0.5 * ( cgs.glconfig.vidHeight - h ), + w, h, 0, 0, 1, 1, hShader ); + } + // NERVE - SMF + if ( cg.crosshairShaderAlt[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ] ) { + w = h = cg_crosshairSize.value; + x = cg_crosshairX.integer; + y = cg_crosshairY.integer; + CG_AdjustFrom640( &x, &y, &w, &h ); + + if ( cg.limboMenu ) { // JPW NERVE + trap_R_DrawStretchPic( x + 0.5 * ( cg.refdef.width - w ), y + 0.5 * ( cg.refdef.height - h ), + w, h, 0, 0, 1, 1, cg.crosshairShaderAlt[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ] ); + } else { + trap_R_DrawStretchPic( x + 0.5 * ( cgs.glconfig.vidWidth - w ), y + 0.5 * ( cgs.glconfig.vidHeight - h ), // JPW NERVE fix for small main windows (dunno why people still do this, but it's supported) + w, h, 0, 0, 1, 1, cg.crosshairShaderAlt[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ] ); + } + } + // -NERVE - SMF +} + + + +/* +================= +CG_ScanForCrosshairEntity +================= +*/ +static void CG_ScanForCrosshairEntity( void ) { + trace_t trace; +// gentity_t *traceEnt; + vec3_t start, end; + int content; + + // DHM - Nerve :: We want this in multiplayer + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + return; //----(SA) don't use any scanning at the moment. + + } + VectorCopy( cg.refdef.vieworg, start ); + VectorMA( start, 8192, cg.refdef.viewaxis[0], end ); //----(SA) changed from 8192 + + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + cg.snap->ps.clientNum, CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_ITEM ); + + if ( trace.entityNum >= MAX_CLIENTS ) { + return; + } + +// traceEnt = &g_entities[trace.entityNum]; + + + // if the player is in fog, don't show it + content = trap_CM_PointContents( trace.endpos, 0 ); + if ( content & CONTENTS_FOG ) { + return; + } + + // if the player is invisible, don't show it + if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) { + return; + } + + // update the fade timer + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; + if ( cg.crosshairClientNum != cg.identifyClientNum && cg.crosshairClientNum != ENTITYNUM_WORLD ) { + cg.identifyClientRequest = cg.crosshairClientNum; + } +} + + + +/* +============== +CG_DrawDynamiteStatus +============== +*/ +static void CG_DrawDynamiteStatus( void ) { + float color[4]; + char *name; + int timeleft; + float w; + + if ( cg.snap->ps.weapon != WP_DYNAMITE && cg.snap->ps.weapon != WP_DYNAMITE2 ) { + return; + } + + if ( cg.snap->ps.grenadeTimeLeft <= 0 ) { + return; + } + + timeleft = cg.snap->ps.grenadeTimeLeft; + +// color = g_color_table[ColorIndex(COLOR_RED)]; + color[0] = color[3] = 1.0f; + + // fade red as it pulses past seconds + color[1] = color[2] = 1.0f - ( (float)( timeleft % 1000 ) * 0.001f ); + + if ( timeleft < 300 ) { // fade up the text + color[3] = (float)timeleft / 300.0f; + } + + trap_R_SetColor( color ); + + timeleft *= 5; + timeleft -= ( timeleft % 5000 ); + timeleft += 5000; + timeleft /= 1000; + +// name = va("Timer: %d", timeleft); + name = va( "Timer: 30" ); + w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; + + color[3] *= cg_hudAlpha.value; + CG_DrawBigStringColor( 320 - w / 2, 170, name, color ); + + trap_R_SetColor( NULL ); +} + + +#define CH_KNIFE_DIST 48 // from g_weapon.c +#define CH_LADDER_DIST 100 +#define CH_WATER_DIST 100 +#define CH_BREAKABLE_DIST 64 +#define CH_DOOR_DIST 96 + +#define CH_DIST 100 //128 // use the largest value from above + +/* +============== +CG_CheckForCursorHints + concept in progress... +============== +*/ +void CG_CheckForCursorHints( void ) { + trace_t trace; + vec3_t start, end; + centity_t *tracent; + vec3_t pforward, eforward; + float dist; + + + if ( cg.renderingThirdPerson ) { + return; + } + + if ( cg.snap->ps.serverCursorHint ) { // server is dictating a cursor hint, use it. + cg.cursorHintTime = cg.time; + cg.cursorHintFade = 500; // fade out time + cg.cursorHintIcon = cg.snap->ps.serverCursorHint; + cg.cursorHintValue = cg.snap->ps.serverCursorHintVal; + return; + } + + + // From here on it's client-side cursor hints. So if the server isn't sending that info (as an option) + // then it falls into here and you can get basic cursorhint info if you want, but not the detailed info + // the server sends. + + // the trace + VectorCopy( cg.refdef.vieworg, start ); + VectorMA( start, CH_DIST, cg.refdef.viewaxis[0], end ); + +// CG_Trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_ALL &~CONTENTS_MONSTERCLIP); + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_PLAYERSOLID ); + + if ( trace.fraction == 1 ) { + return; + } + + dist = trace.fraction * CH_DIST; + + tracent = &cg_entities[ trace.entityNum ]; + + // + // world + // + if ( trace.entityNum == ENTITYNUM_WORLD ) { + // if looking into water, warn if you don't have a breather + if ( ( trace.contents & CONTENTS_WATER ) && !( cg.snap->ps.powerups[PW_BREATHER] ) ) { + if ( dist <= CH_WATER_DIST ) { + cg.cursorHintIcon = HINT_WATER; + cg.cursorHintTime = cg.time; + cg.cursorHintFade = 500; + } + } + // ladder + else if ( ( trace.surfaceFlags & SURF_LADDER ) && !( cg.snap->ps.pm_flags & PMF_LADDER ) ) { + if ( dist <= CH_LADDER_DIST ) { + cg.cursorHintIcon = HINT_LADDER; + cg.cursorHintTime = cg.time; + cg.cursorHintFade = 500; + } + } + + + } + // + // people + // + else if ( trace.entityNum < MAX_CLIENTS ) { + + // knife + if ( trace.entityNum < MAX_CLIENTS && ( cg.snap->ps.weapon == WP_KNIFE || cg.snap->ps.weapon == WP_KNIFE2 ) ) { + if ( dist <= CH_KNIFE_DIST ) { + + AngleVectors( cg.snap->ps.viewangles, pforward, NULL, NULL ); + AngleVectors( tracent->lerpAngles, eforward, NULL, NULL ); + + if ( DotProduct( eforward, pforward ) > 0.9f ) { // from behind + cg.cursorHintIcon = HINT_KNIFE; + cg.cursorHintTime = cg.time; + cg.cursorHintFade = 100; + } + } + } + } + // + // other entities + // + else { + if ( tracent->currentState.dmgFlags ) { + switch ( tracent->currentState.dmgFlags ) { + case HINT_DOOR: + if ( dist < CH_DOOR_DIST ) { + cg.cursorHintIcon = HINT_DOOR; + cg.cursorHintTime = cg.time; + cg.cursorHintFade = 500; + } + break; + //case HINT_CHAIR: + case HINT_MG42: + if ( dist < CH_DOOR_DIST && !( cg.snap->ps.eFlags & EF_MG42_ACTIVE ) ) { + cg.cursorHintIcon = HINT_ACTIVATE; + cg.cursorHintTime = cg.time; + cg.cursorHintFade = 500; + } + break; + } + } else { + + if ( tracent->currentState.eType == ET_EXPLOSIVE ) { + if ( ( dist < CH_BREAKABLE_DIST ) && ( cg.cursorHintIcon != HINT_FORCENONE ) ) { // JPW NERVE so we don't get breakables in trigger_objective_info fields for wrong team (see code chunk in g_main.c) + cg.cursorHintIcon = HINT_BREAKABLE; + cg.cursorHintTime = cg.time; + cg.cursorHintFade = 500; + } + + } + } + } +} + + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( void ) { + float *color; + char *name; + float w; + // NERVE - SMF + const char *s, *playerClass; + int playerHealth, val; + vec4_t c; + float barFrac; + // -NERVE - SMF + + if ( cg_drawCrosshair.integer < 0 ) { + return; + } + if ( !cg_drawCrosshairNames.integer ) { + return; + } + if ( cg.renderingThirdPerson ) { + return; + } + + // NERVE - SMF - we don't want to do this in warmup + if ( cgs.gamestate != GS_PLAYING && cgs.gametype == GT_WOLF_STOPWATCH ) { + return; + } + + // Ridah + if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { + return; + } + // done. + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity(); + + // draw the name of the player being looked at + color = CG_FadeColor( cg.crosshairClientTime, 1000 ); + + if ( !color ) { + trap_R_SetColor( NULL ); + return; + } + + // NERVE - SMF + if ( cg.crosshairClientNum > MAX_CLIENTS ) { + return; + } + + // we only want to see players on our team + if ( cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR + && cgs.clientinfo[ cg.crosshairClientNum ].team != cgs.clientinfo[cg.snap->ps.clientNum].team ) { + return; + } + + // determine player class + val = cg_entities[ cg.crosshairClientNum ].currentState.teamNum; + if ( val == 0 ) { + playerClass = "S"; + } else if ( val == 1 ) { + playerClass = "M"; + } else if ( val == 2 ) { + playerClass = "E"; + } else if ( val == 3 ) { + playerClass = "L"; + } else { + playerClass = ""; + } + + name = cgs.clientinfo[ cg.crosshairClientNum ].name; + + s = va( "[%s] %s", CG_TranslateString( playerClass ), name ); + if ( !s ) { + return; + } + w = CG_DrawStrlen( s ) * SMALLCHAR_WIDTH; + + // draw the name and class + CG_DrawSmallStringColor( 320 - w / 2, 170, s, color ); + + // draw the health bar + playerHealth = cg.identifyClientHealth; + + if ( cg.crosshairClientNum == cg.identifyClientNum ) { + barFrac = (float)playerHealth / 100; + + if ( barFrac > 1.0 ) { + barFrac = 1.0; + } else if ( barFrac < 0 ) { + barFrac = 0; + } + + c[0] = 1.0f; + c[1] = c[2] = barFrac; + c[3] = 0.25 + barFrac * 0.5 * color[3]; + + CG_FilledBar( 320 - w / 2, 190, 110, 10, c, NULL, NULL, barFrac, 16 ); + } + // -NERVE - SMF + + trap_R_SetColor( NULL ); +} + + + +//============================================================================== + +/* +================= +CG_DrawSpectator +================= +*/ +static void CG_DrawSpectator( void ) { + CG_DrawBigString( 320 - 9 * 8, 440, CG_TranslateString( "SPECTATOR" ), 1.0F ); + if ( cgs.gametype == GT_TOURNAMENT ) { + CG_DrawBigString( 320 - 15 * 8, 460, "waiting to play", 1.0F ); + } + if ( cgs.gametype == GT_TEAM || cgs.gametype == GT_CTF ) { + CG_DrawBigString( 320 - 25 * 8, 460, "use the TEAM menu to play", 1.0F ); + } +} + +/* +================= +CG_DrawVote +================= +*/ +static void CG_DrawVote( void ) { + char *s; + char str1[32], str2[32]; + float color[4] = { 1, 1, 0, 1 }; + int sec; + + if ( cgs.complaintEndTime > cg.time ) { + + if ( cgs.complaintClient == -1 ) { + s = "Your complaint has been filed"; + CG_DrawStringExt( 8, 200, CG_TranslateString( s ), color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 80 ); + return; + } + if ( cgs.complaintClient == -2 ) { + s = "Complaint dismissed"; + CG_DrawStringExt( 8, 200, CG_TranslateString( s ), color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 80 ); + return; + } + if ( cgs.complaintClient == -3 ) { + s = "Server Host cannot be complained against"; + CG_DrawStringExt( 8, 200, CG_TranslateString( s ), color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 80 ); + return; + } + if ( cgs.complaintClient == -4 ) { + s = "You were team-killed by the Server Host"; + CG_DrawStringExt( 8, 200, CG_TranslateString( s ), color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 80 ); + return; + } + + Q_strncpyz( str1, BindingFromName( "vote yes" ), 32 ); + if ( !Q_stricmp( str1, "???" ) ) { + Q_strncpyz( str1, "vote yes", 32 ); + } + Q_strncpyz( str2, BindingFromName( "vote no" ), 32 ); + if ( !Q_stricmp( str2, "???" ) ) { + Q_strncpyz( str2, "vote no", 32 ); + } + + s = va( CG_TranslateString( "File complaint against %s for team-killing?" ), cgs.clientinfo[cgs.complaintClient].name ); + CG_DrawStringExt( 8, 200, s, color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 80 ); + + s = va( CG_TranslateString( "Press '%s' for YES, or '%s' for No" ), str1, str2 ); + CG_DrawStringExt( 8, 214, s, color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 80 ); + return; + } + + if ( !cgs.voteTime ) { + return; + } + + Q_strncpyz( str1, BindingFromName( "vote yes" ), 32 ); + if ( !Q_stricmp( str1, "???" ) ) { + Q_strncpyz( str1, "vote yes", 32 ); + } + Q_strncpyz( str2, BindingFromName( "vote no" ), 32 ); + if ( !Q_stricmp( str2, "???" ) ) { + Q_strncpyz( str2, "vote no", 32 ); + } + + // play a talk beep whenever it is modified + if ( cgs.voteModified ) { + cgs.voteModified = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + + if ( !( cg.snap->ps.eFlags & EF_VOTED ) ) { + s = va( CG_TranslateString( "VOTE(%i):%s" ), sec, cgs.voteString ); + CG_DrawStringExt( 8, 200, s, color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 60 ); + + s = va( CG_TranslateString( "YES(%s):%i, NO(%s):%i" ), str1, cgs.voteYes, str2, cgs.voteNo ); + CG_DrawStringExt( 8, 214, s, color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 60 ); + } else { + s = va( CG_TranslateString( "Y:%i, N:%i" ), cgs.voteYes, cgs.voteNo ); + CG_DrawStringExt( 8, 214, s, color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 20 ); + } +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) { + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + CG_DrawCenterString(); + return; + } + + cg.scoreFadeTime = cg.time; + CG_DrawScoreboard(); +} + +/* +================= +CG_ActivateLimboMenu + +NERVE - SMF +================= +*/ +static void CG_ActivateLimboMenu( void ) { + // ATVI Wolfenstein Misc #339 + // track when the UI would disable limbo, that leaves us in an inconsistent latch state + // the inconsistent state is a good thing most of the time, except when game sends us back to free fly + // that had the bad effect of triggering limbo again + static qboolean ui_disable = qfalse; + // track when we see cgame conditions change and need to emit a new limbo console command + static qboolean latch = qfalse; + qboolean test; + char buf[32]; + + if ( cgs.gametype < GT_WOLF ) { + return; + } + + // a test to detect when UI closes the limbo + trap_Cvar_VariableStringBuffer( "ui_limboMode", buf, sizeof( buf ) ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && atoi( buf ) == 0 && latch == 1 ) { + ui_disable = qtrue; + } + + // should we open the limbo menu + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + test = qfalse; + } else { + test = cg.snap->ps.pm_flags & PMF_LIMBO || cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg.warmup; + } + + // auto open/close limbo mode + if ( cg_popupLimboMenu.integer ) { + // we don't want to trigger limbo in this very particular case + if ( ui_disable && cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && test && !latch ) { + // ATVI Wolfenstein Misc #413 + // we manually update this, otherwise there's a chance team selections won't work + trap_Cvar_Set( "mp_currentTeam", "2" ); + latch = 1; + ui_disable = qfalse; + } + if ( test && !latch ) { + trap_SendConsoleCommand( "OpenLimboMenu\n" ); + latch = qtrue; + ui_disable = qfalse; + } else if ( !test && latch ) { + trap_SendConsoleCommand( "CloseLimboMenu\n" ); + latch = qfalse; + } + } + + if ( atoi( buf ) ) { + cg.limboMenu = qtrue; + } else { + cg.limboMenu = qfalse; + } +} + +/* +================= +CG_DrawSpectatorMessage +================= +*/ +static void CG_DrawSpectatorMessage( void ) { + float color[4] = { 1, 1, 1, 1 }; + const char *str, *str2; + float x, y; + char buf[32]; + + if ( cgs.gametype < GT_WOLF ) { + return; + } + + if ( !cg_descriptiveText.integer ) { + return; + } + + if ( !( cg.snap->ps.pm_flags & PMF_LIMBO || cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) ) { + return; + } + + trap_Cvar_VariableStringBuffer( "ui_limboMode", buf, sizeof( buf ) ); + if ( atoi( buf ) ) { + return; + } + + Controls_GetConfig(); + + x = 80; + y = 408; + + str2 = BindingFromName( "OpenLimboMenu" ); + if ( !Q_stricmp( str2, "???" ) ) { + str2 = "ESCAPE"; + } + str = va( CG_TranslateString( "- Press %s to open Limbo Menu" ), str2 ); + CG_DrawStringExt( x, y, str, color, qtrue, 0, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + y += TINYCHAR_HEIGHT; + + str2 = BindingFromName( "mp_QuickMessage" ); + str = va( CG_TranslateString( "- Press %s to open quick message menu" ), str2 ); + CG_DrawStringExt( x, y, str, color, qtrue, 0, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + y += TINYCHAR_HEIGHT; + + str2 = BindingFromName( "+attack" ); + str = va( CG_TranslateString( "- Press %s to follow next player" ), str2 ); + CG_DrawStringExt( x, y, str, color, qtrue, 0, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + y += TINYCHAR_HEIGHT; +} + +/* +================= +CG_DrawLimboMessage +================= +*/ + +#define INFOTEXT_STARTX 42 + +static void CG_DrawLimboMessage( void ) { + float color[4] = { 1, 1, 1, 1 }; + const char *str; + playerState_t *ps; + //int w; + + if ( cgs.gametype < GT_WOLF ) { + return; + } + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] > 0 ) { + return; + } + + if ( cg.snap->ps.pm_flags & PMF_LIMBO || cgs.clientinfo[cg.clientNum].team == TEAM_SPECTATOR ) { + return; + } + + color[3] *= cg_hudAlpha.value; + + if ( cg_descriptiveText.integer ) { + str = CG_TranslateString( "You are wounded and waiting for a medic." ); + CG_DrawSmallStringColor( INFOTEXT_STARTX, 68, str, color ); + + str = CG_TranslateString( "Press JUMP to go into reinforcement queue." ); + CG_DrawSmallStringColor( INFOTEXT_STARTX, 86, str, color ); + } + + // JPW NERVE + if ( cg.snap->ps.persistant[PERS_RESPAWNS_LEFT] == 0 ) { + str = CG_TranslateString( "No more reinforcements this round." ); + } else if ( cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_RED ) { + str = va( CG_TranslateString( "Reinforcements deploy in %d seconds." ), + (int)( 1 + (float)( cg_redlimbotime.integer - ( cg.time % cg_redlimbotime.integer ) ) * 0.001f ) ); + } else { + str = va( CG_TranslateString( "Reinforcements deploy in %d seconds." ), + (int)( 1 + (float)( cg_bluelimbotime.integer - ( cg.time % cg_bluelimbotime.integer ) ) * 0.001f ) ); + } + + CG_DrawSmallStringColor( INFOTEXT_STARTX, 104, str, color ); + // jpw + + trap_R_SetColor( NULL ); +} +// -NERVE - SMF + +/* +================= +CG_DrawFollow +================= +*/ +static qboolean CG_DrawFollow( void ) { + //float x; + vec4_t color; + const char *name; + char deploytime[128]; // JPW NERVE + + if ( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { + return qfalse; + } + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + // JPW NERVE -- if in limbo, show different follow message + if ( cg.snap->ps.pm_flags & PMF_LIMBO ) { + color[1] = 0.0; + color[2] = 0.0; + if ( cg.snap->ps.persistant[PERS_RESPAWNS_LEFT] == 0 ) { + sprintf( deploytime, CG_TranslateString( "No more deployments this round" ) ); + } else if ( cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_RED ) { + sprintf( deploytime, CG_TranslateString( "Deploying in %d seconds" ), + (int)( 1 + (float)( cg_redlimbotime.integer - ( cg.time % cg_redlimbotime.integer ) ) * 0.001f ) ); + } else { + sprintf( deploytime, CG_TranslateString( "Deploying in %d seconds" ), + (int)( 1 + (float)( cg_bluelimbotime.integer - ( cg.time % cg_bluelimbotime.integer ) ) * 0.001f ) ); + } + + CG_DrawStringExt( INFOTEXT_STARTX, 68, deploytime, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 80 ); + + // DHM - Nerve :: Don't display if you're following yourself + if ( cg.snap->ps.clientNum != cg.clientNum ) { + sprintf( deploytime,"(%s %s)", CG_TranslateString( "Following" ), cgs.clientinfo[ cg.snap->ps.clientNum ].name ); + CG_DrawStringExt( INFOTEXT_STARTX, 86, deploytime, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 80 ); + } + } else { + // jpw + CG_DrawSmallString( INFOTEXT_STARTX, 68, CG_TranslateString( "following" ), 1.0F ); + + name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; + + CG_DrawStringExt( 120, 68, name, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); + } // JPW NERVE + return qtrue; +} + + +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) { + int w; + int sec; + int cw; + const char *s, *s1, *s2; + + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + return; // (SA) don't bother with this stuff in sp + } + + sec = cg.warmup; + if ( !sec ) { + if ( cgs.gamestate == GS_WAITING_FOR_PLAYERS ) { + cw = 10; + + s = CG_TranslateString( "Game Stopped - Waiting for more players" ); + + w = CG_DrawStrlen( s ); + CG_DrawStringExt( 320 - w * 6, 120, s, colorWhite, qfalse, qtrue, 12, 18, 0 ); + + + s1 = va( CG_TranslateString( "Waiting for %i players" ), cgs.minclients ); + s2 = CG_TranslateString( "or call a vote to start match" ); + + w = CG_DrawStrlen( s1 ); + CG_DrawStringExt( 320 - w * cw / 2, 160, s1, colorWhite, + qfalse, qtrue, cw, (int)( cw * 1.5 ), 0 ); + + w = CG_DrawStrlen( s2 ); + CG_DrawStringExt( 320 - w * cw / 2, 180, s2, colorWhite, + qfalse, qtrue, cw, (int)( cw * 1.5 ), 0 ); + + return; + } + + return; + } + + sec = ( sec - cg.time ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + + if ( cgs.gametype == GT_WOLF_STOPWATCH ) { + s = va( "%s %i", CG_TranslateString( "(WARMUP) Match begins in:" ), sec + 1 ); + } else { + s = va( "%s %i", CG_TranslateString( "(WARMUP) Match begins in:" ), sec + 1 ); + } + + w = CG_DrawStrlen( s ); + CG_DrawStringExt( 320 - w * 6, 120, s, colorWhite, qfalse, qtrue, 12, 18, 0 ); + + // NERVE - SMF - stopwatch stuff + s1 = ""; + s2 = ""; + + if ( cgs.gametype == GT_WOLF_STOPWATCH ) { + const char *cs; + int defender; + + s = va( "%s %i", CG_TranslateString( "Stopwatch Round" ), cgs.currentRound + 1 ); + + cs = CG_ConfigString( CS_MULTI_INFO ); + defender = atoi( Info_ValueForKey( cs, "defender" ) ); + + if ( !defender ) { + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + if ( cgs.currentRound == 1 ) { + s1 = "You have been switched to the Axis team"; + s2 = "Keep the Allies from beating the clock!"; + } else { + s1 = "You are on the Axis team"; + } + } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + if ( cgs.currentRound == 1 ) { + s1 = "You have been switched to the Allied team"; + s2 = "Try to beat the clock!"; + } else { + s1 = "You are on the Allied team"; + } + } + } else { + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + if ( cgs.currentRound == 1 ) { + s1 = "You have been switched to the Axis team"; + s2 = "Try to beat the clock!"; + } else { + s1 = "You are on the Axis team"; + } + } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + if ( cgs.currentRound == 1 ) { + s1 = "You have been switched to the Allied team"; + s2 = "Keep the Axis from beating the clock!"; + } else { + s1 = "You are on the Allied team"; + } + } + } + + if ( strlen( s1 ) ) { + s1 = CG_TranslateString( s1 ); + } + + if ( strlen( s2 ) ) { + s2 = CG_TranslateString( s2 ); + } + + cw = 10; + + w = CG_DrawStrlen( s ); + CG_DrawStringExt( 320 - w * cw / 2, 140, s, colorWhite, + qfalse, qtrue, cw, (int)( cw * 1.5 ), 0 ); + + w = CG_DrawStrlen( s1 ); + CG_DrawStringExt( 320 - w * cw / 2, 160, s1, colorWhite, + qfalse, qtrue, cw, (int)( cw * 1.5 ), 0 ); + + w = CG_DrawStrlen( s2 ); + CG_DrawStringExt( 320 - w * cw / 2, 180, s2, colorWhite, + qfalse, qtrue, cw, (int)( cw * 1.5 ), 0 ); + } +} + +//================================================================================== + +/* +================= +CG_DrawFlashFade +================= +*/ +static void CG_DrawFlashFade( void ) { + static int lastTime; + int elapsed, time; + vec4_t col; + + if ( cgs.fadeStartTime + cgs.fadeDuration < cg.time ) { + cgs.fadeAlphaCurrent = cgs.fadeAlpha; + } else if ( cgs.fadeAlphaCurrent != cgs.fadeAlpha ) { + elapsed = ( time = trap_Milliseconds() ) - lastTime; // we need to use trap_Milliseconds() here since the cg.time gets modified upon reloading + lastTime = time; + if ( elapsed < 500 && elapsed > 0 ) { + if ( cgs.fadeAlphaCurrent > cgs.fadeAlpha ) { + cgs.fadeAlphaCurrent -= ( (float)elapsed / (float)cgs.fadeDuration ); + if ( cgs.fadeAlphaCurrent < cgs.fadeAlpha ) { + cgs.fadeAlphaCurrent = cgs.fadeAlpha; + } + } else { + cgs.fadeAlphaCurrent += ( (float)elapsed / (float)cgs.fadeDuration ); + if ( cgs.fadeAlphaCurrent > cgs.fadeAlpha ) { + cgs.fadeAlphaCurrent = cgs.fadeAlpha; + } + } + } + } + // now draw the fade + if ( cgs.fadeAlphaCurrent > 0.0 ) { + VectorClear( col ); + col[3] = cgs.fadeAlphaCurrent; + CG_FillRect( -10, -10, 650, 490, col ); + } +} + + + +/* +============== +CG_DrawFlashZoomTransition + hide the snap transition from regular view to/from zoomed + + FIXME: TODO: use cg_fade? +============== +*/ +static void CG_DrawFlashZoomTransition( void ) { + vec4_t color; + float frac; + int fadeTime; + + if ( !cg.snap ) { + return; + } + + if ( cg.snap->ps.eFlags & EF_MG42_ACTIVE ) { // don't draw when on mg_42 + // keep the timer fresh so when you remove yourself from the mg42, it'll fade + cg.zoomTime = cg.time; + return; + } + + if ( cgs.gametype != GT_SINGLE_PLAYER ) { // JPW NERVE + fadeTime = 400; + } else { + if ( cg.zoomedScope ) { + fadeTime = cg.zoomedScope; //----(SA) + } else { + fadeTime = 300; + } + } + // jpw + + frac = cg.time - cg.zoomTime; + + if ( frac < fadeTime ) { + frac = frac / (float)fadeTime; + + if ( cg.weaponSelect == WP_SNOOPERSCOPE ) { +// Vector4Set( color, 0.7f, 0.3f, 0.7f, 1.0f - frac ); +// Vector4Set( color, 1, 0.5, 1, 1.0f - frac ); +// Vector4Set( color, 0.5f, 0.3f, 0.5f, 1.0f - frac ); + Vector4Set( color, 0.7f, 0.6f, 0.7f, 1.0f - frac ); + } else { + Vector4Set( color, 0, 0, 0, 1.0f - frac ); + } + + CG_FillRect( -10, -10, 650, 490, color ); + } +} + + + +/* +================= +CG_DrawFlashDamage +================= +*/ +static void CG_DrawFlashDamage( void ) { + vec4_t col; + float redFlash; + + if ( !cg.snap ) { + return; + } + + if ( cg.v_dmg_time > cg.time ) { + redFlash = fabs( cg.v_dmg_pitch * ( ( cg.v_dmg_time - cg.time ) / DAMAGE_TIME ) ); + + // blend the entire screen red + if ( redFlash > 5 ) { + redFlash = 5; + } + + VectorSet( col, 0.2, 0, 0 ); + col[3] = 0.7 * ( redFlash / 5.0 ); + + CG_FillRect( -10, -10, 650, 490, col ); + } +} + + +/* +================= +CG_DrawFlashFire +================= +*/ +static void CG_DrawFlashFire( void ) { + vec4_t col = {1,1,1,1}; + float alpha, max, f; + + if ( !cg.snap ) { + return; + } + + if ( cg.renderingThirdPerson ) { + return; + } + + if ( !cg.snap->ps.onFireStart ) { + cg.v_noFireTime = cg.time; + return; + } + + alpha = (float)( ( FIRE_FLASH_TIME - 1000 ) - ( cg.time - cg.snap->ps.onFireStart ) ) / ( FIRE_FLASH_TIME - 1000 ); + if ( alpha > 0 ) { + if ( alpha >= 1.0 ) { + alpha = 1.0; + } + + // fade in? + f = (float)( cg.time - cg.v_noFireTime ) / FIRE_FLASH_FADEIN_TIME; + if ( f >= 0.0 && f < 1.0 ) { + alpha = f; + } + + max = 0.5 + 0.5 * sin( (float)( ( cg.time / 10 ) % 1000 ) / 1000.0 ); + if ( alpha > max ) { + alpha = max; + } + col[0] = alpha; + col[1] = alpha; + col[2] = alpha; + col[3] = alpha; + trap_R_SetColor( col ); + CG_DrawPic( -10, -10, 650, 490, cgs.media.viewFlashFire[( cg.time / 50 ) % 16] ); + trap_R_SetColor( NULL ); + + trap_S_AddLoopingSound( cg.snap->ps.clientNum, cg.snap->ps.origin, vec3_origin, cgs.media.flameSound, (int)( 255.0 * alpha ) ); + trap_S_AddLoopingSound( cg.snap->ps.clientNum, cg.snap->ps.origin, vec3_origin, cgs.media.flameCrackSound, (int)( 255.0 * alpha ) ); + } else { + cg.v_noFireTime = cg.time; + } +} + +/* +================= +CG_DrawFlashLightning +================= +*/ +static void CG_DrawFlashLightning( void ) { + float alpha; + centity_t *cent; + qhandle_t shader; + + if ( !cg.snap ) { + return; + } + + if ( cg_thirdPerson.integer ) { + return; + } + + cent = &cg_entities[cg.snap->ps.clientNum]; + + if ( !cent->pe.teslaDamagedTime || ( cent->pe.teslaDamagedTime > cg.time ) ) { + return; + } + + alpha = 1.0 - (float)( cg.time - cent->pe.teslaDamagedTime ) / LIGHTNING_FLASH_TIME; + if ( alpha > 0 ) { + if ( alpha >= 1.0 ) { + alpha = 1.0; + } + + if ( ( cg.time / 50 ) % ( 2 + ( cg.time % 2 ) ) == 0 ) { + shader = cgs.media.viewTeslaAltDamageEffectShader; + } else { + shader = cgs.media.viewTeslaDamageEffectShader; + } + + CG_DrawPic( -10, -10, 650, 490, shader ); + } +} + + + +/* +============== +CG_DrawFlashBlendBehindHUD + screen flash stuff drawn first (on top of world, behind HUD) +============== +*/ +static void CG_DrawFlashBlendBehindHUD( void ) { + CG_DrawFlashZoomTransition(); +} + + +/* +================= +CG_DrawFlashBlend + screen flash stuff drawn last (on top of everything) +================= +*/ +static void CG_DrawFlashBlend( void ) { + CG_DrawFlashLightning(); + CG_DrawFlashFire(); + CG_DrawFlashDamage(); + CG_DrawFlashFade(); +} + +// NERVE - SMF +/* +================= +CG_DrawObjectiveInfo +================= +*/ +#define OID_LEFT 10 +#define OID_TOP 360 + +void CG_ObjectivePrint( const char *str, int charWidth ) { + char *s; + int i, len; // NERVE - SMF + qboolean neednewline = qfalse; // NERVE - SMF + + s = CG_TranslateString( str ); + + Q_strncpyz( cg.oidPrint, s, sizeof( cg.oidPrint ) ); + + // NERVE - SMF - turn spaces into newlines, if we've run over the linewidth + len = strlen( cg.oidPrint ); + for ( i = 0; i < len; i++ ) { + + // NOTE: subtract a few chars here so long words still get displayed properly + if ( i % ( CP_LINEWIDTH - 20 ) == 0 && i > 0 ) { + neednewline = qtrue; + } + if ( cg.oidPrint[i] == ' ' && neednewline ) { + cg.oidPrint[i] = '\n'; + neednewline = qfalse; + } + } + // -NERVE - SMF + + cg.oidPrintTime = cg.time; + cg.oidPrintY = OID_TOP; + cg.oidPrintCharWidth = charWidth; + + // count the number of lines for oiding + cg.oidPrintLines = 1; + s = cg.oidPrint; + while ( *s ) { + if ( *s == '\n' ) { + cg.oidPrintLines++; + } + s++; + } +} + +static void CG_DrawObjectiveInfo( void ) { + char *start; + int l; + int x, y, w,h; + int x1, y1, x2, y2; + float *color; + vec4_t backColor; + backColor[0] = 0.2f; + backColor[1] = 0.2f; + backColor[2] = 0.2f; + backColor[2] = cg_hudAlpha.value; + + if ( !cg.oidPrintTime ) { + return; + } + + color = CG_FadeColor( cg.oidPrintTime, 250 ); + if ( !color ) { + cg.oidPrintTime = 0; + return; + } + + trap_R_SetColor( color ); + + start = cg.oidPrint; + +// JPW NERVE + // y = cg.oidPrintY - cg.oidPrintLines * BIGCHAR_HEIGHT / 2; + y = 415 - cg.oidPrintLines * BIGCHAR_HEIGHT / 2; + + x1 = 319; + y1 = y - 2; + x2 = 321; +// jpw + + // first just find the bounding rect + while ( 1 ) { + char linebuffer[1024]; + + for ( l = 0; l < CP_LINEWIDTH; l++ ) { + if ( !start[l] || start[l] == '\n' ) { + break; + } + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + + w = cg.oidPrintCharWidth * CG_DrawStrlen( linebuffer ) + 10; +// JPW NERVE + if ( 320 - w / 2 < x1 ) { + x1 = 320 - w / 2; + x2 = 320 + w / 2; + } + +/* + if ( x1 + w > x2 ) + x2 = x1 + w; +*/ + x = 320 - w / 2; +// jpw + y += cg.oidPrintCharWidth * 1.5; + + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + x2 = x2 + 4; + y2 = y - cg.oidPrintCharWidth * 1.5 + 4; + + h = y2 - y1; // JPW NERVE + + VectorCopy( color, backColor ); + backColor[3] = 0.5 * color[3]; + trap_R_SetColor( backColor ); + + CG_DrawPic( x1, y1, x2 - x1, y2 - y1, cgs.media.teamStatusBar ); + + VectorSet( backColor, 0, 0, 0 ); + CG_DrawRect( x1, y1, x2 - x1, y2 - y1, 1, backColor ); + + trap_R_SetColor( color ); + + // do the actual drawing + start = cg.oidPrint; +// y = cg.oidPrintY - cg.oidPrintLines * BIGCHAR_HEIGHT / 2; + y = 415 - cg.oidPrintLines * BIGCHAR_HEIGHT / 2; // JPW NERVE + + + while ( 1 ) { + char linebuffer[1024]; + + for ( l = 0; l < CP_LINEWIDTH; l++ ) { + if ( !start[l] || start[l] == '\n' ) { + break; + } + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + + w = cg.oidPrintCharWidth * CG_DrawStrlen( linebuffer ); + if ( x1 + w > x2 ) { + x2 = x1 + w; + } + + x = 320 - w / 2; // JPW NERVE + + CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, + cg.oidPrintCharWidth, (int)( cg.oidPrintCharWidth * 1.5 ), 0 ); + + y += cg.oidPrintCharWidth * 1.5; + + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + +void CG_DrawObjectiveIcons() { + float x, y, w, h, xx, fade; // JPW NERVE + float startColor[4]; + const char *s, *buf; + char teamstr[32]; + float captureHoldFract; + int i, num, status,barheight; + vec4_t hcolor = { 0.2f, 0.2f, 0.2f, 1.f }; + int msec, mins, seconds, tens; // JPW NERVE + +// JPW NERVE added round timer + y = 48; + x = 5; + msec = ( cgs.timelimit * 60.f * 1000.f ) - ( cg.time - cgs.levelStartTime ); + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + if ( msec < 0 ) { + fade = fabs( sin( cg.time * 0.002 ) ) * cg_hudAlpha.value; + s = va( "0:00" ); + } else { + s = va( "%i:%i%i", mins, tens, seconds ); // float cast to line up with reinforce time + fade = cg_hudAlpha.value; + } + + CG_DrawSmallString( x,y,s,fade ); + +// jpw + + x = 5; + y = 68; + w = 24; + h = 14; + + // draw the stopwatch + if ( cgs.gametype == GT_WOLF_STOPWATCH ) { + if ( cgs.currentRound == 0 ) { + CG_DrawPic( 3, y, 30, 30, trap_R_RegisterShader( "sprites/stopwatch1.tga" ) ); + } else { + CG_DrawPic( 3, y, 30, 30, trap_R_RegisterShader( "sprites/stopwatch2.tga" ) ); + } + y += 34; + } + + // determine character's team + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + strcpy( teamstr, "axis_desc" ); + } else { + strcpy( teamstr, "allied_desc" ); + } + + s = CG_ConfigString( CS_MULTI_INFO ); + buf = Info_ValueForKey( s, "numobjectives" ); + + if ( buf && atoi( buf ) ) { + num = atoi( buf ); + + VectorSet( hcolor, 0.3f, 0.3f, 0.3f ); + hcolor[3] = 0.7f * cg_hudAlpha.value; // JPW NERVE + CG_DrawRect( x - 1, y - 1, w + 2, ( h + 4 ) * num - 4 + 2, 1, hcolor ); + + VectorSet( hcolor, 1.0f, 1.0f, 1.0f ); + hcolor[3] = 0.2f * cg_hudAlpha.value; // JPW NERVE + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, ( h + 4 ) * num - 4, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + for ( i = 0; i < num; i++ ) { + s = CG_ConfigString( CS_MULTI_OBJECTIVE1 + i ); + buf = Info_ValueForKey( s, teamstr ); + + xx = x; + + hcolor[0] = 0.25f; + hcolor[1] = 0.38f; + hcolor[2] = 0.38f; + hcolor[3] = 0.5f * cg_hudAlpha.value; // JPW NERVE + trap_R_SetColor( hcolor ); + CG_DrawPic( xx, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + // draw text + VectorSet( hcolor, 1.f, 1.f, 1.f ); + hcolor[3] = cg_hudAlpha.value; // JPW NERVE +// CG_DrawStringExt( xx, y, va( "%i", i+1 ), hcolor, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); // JPW NERVE pulled per id request + +// xx += 10; + + // draw status flags + s = CG_ConfigString( CS_MULTI_OBJ1_STATUS + i ); + status = atoi( Info_ValueForKey( s, "status" ) ); + + VectorSet( hcolor, 1, 1, 1 ); + hcolor[3] = 0.7f * cg_hudAlpha.value; // JPW NERVE + trap_R_SetColor( hcolor ); + + if ( status == 0 ) { + CG_DrawPic( xx, y, w, h, trap_R_RegisterShaderNoMip( "ui_mp/assets/ger_flag.tga" ) ); + } else if ( status == 1 ) { + CG_DrawPic( xx, y, w, h, trap_R_RegisterShaderNoMip( "ui_mp/assets/usa_flag.tga" ) ); + } + + VectorSet( hcolor, 0.2f, 0.2f, 0.2f ); + hcolor[3] = cg_hudAlpha.value; // JPW NERVE + trap_R_SetColor( hcolor ); + +// CG_DrawRect( xx, y, w, h, 2, hcolor ); + + y += h + 4; + } + } + +// JPW NERVE compute & draw hold bar + if ( cgs.gametype == GT_WOLF_CPH ) { + if ( cg.snap->ps.stats[STAT_CAPTUREHOLD_RED] || cg.snap->ps.stats[STAT_CAPTUREHOLD_BLUE] ) { + captureHoldFract = (float)cg.snap->ps.stats[STAT_CAPTUREHOLD_RED] / + (float)( cg.snap->ps.stats[STAT_CAPTUREHOLD_RED] + cg.snap->ps.stats[STAT_CAPTUREHOLD_BLUE] ); + } else { + captureHoldFract = 0.5; + } + + barheight = y - 72; // (68+4) + + + startColor[0] = 1; + startColor[1] = 1; + startColor[2] = 1; + startColor[3] = 1; + if ( captureHoldFract > 0.5 ) { + startColor[3] = cg_hudAlpha.value; + } else { + startColor[3] = cg_hudAlpha.value * ( ( captureHoldFract ) + 0.15 ); + } +// startColor[3] = 0.25; + trap_R_SetColor( startColor ); + CG_DrawPic( 32,68,5,barheight * captureHoldFract,cgs.media.redColorBar ); + + if ( captureHoldFract < 0.5 ) { + startColor[3] = cg_hudAlpha.value; + } else { + startColor[3] = cg_hudAlpha.value * ( 0.15 + ( 1.0f - captureHoldFract ) ); + } +// startColor[3] = 0.25; + trap_R_SetColor( startColor ); + CG_DrawPic( 32,68 + barheight * captureHoldFract,5,barheight - barheight * captureHoldFract,cgs.media.blueColorBar ); + } +// jpw + + + // draw treasure icon if we have the flag + y += 4; + + VectorSet( hcolor, 1, 1, 1 ); + hcolor[3] = cg_hudAlpha.value; + trap_R_SetColor( hcolor ); + if ( cgs.clientinfo[cg.snap->ps.clientNum].powerups & ( 1 << PW_REDFLAG ) || + cgs.clientinfo[cg.snap->ps.clientNum].powerups & ( 1 << PW_BLUEFLAG ) ) { + CG_DrawPic( -7, y, 48, 48, trap_R_RegisterShader( "models/multiplayer/treasure/treasure" ) ); + y += 50; + } +} +// -NERVE - SMF + +//================================================================================== + + +void CG_DrawTimedMenus() { + if ( cg.voiceTime ) { + int t = cg.time - cg.voiceTime; + if ( t > 2500 ) { + Menus_CloseByName( "voiceMenu" ); + trap_Cvar_Set( "cl_conXOffset", "0" ); + cg.voiceTime = 0; + } + } +} + + +/* +================= +CG_Fade +================= +*/ +void CG_Fade( int r, int g, int b, int a, float time ) { + if ( time <= 0 ) { // do instantly + cg.fadeRate = 1; + cg.fadeTime = cg.time - 1; // set cg.fadeTime behind cg.time so it will start out 'done' + } else { + cg.fadeRate = 1.0f / time; + cg.fadeTime = cg.time + time; + } + + cg.fadeColor2[ 0 ] = ( float )r / 255.0f; + cg.fadeColor2[ 1 ] = ( float )g / 255.0f; + cg.fadeColor2[ 2 ] = ( float )b / 255.0f; + cg.fadeColor2[ 3 ] = ( float )a / 255.0f; +} + + +/* +================= +CG_ScreenFade +================= +*/ +static void CG_ScreenFade( void ) { + int msec; + int i; + float t, invt; + vec4_t color; + + if ( !cg.fadeRate ) { + return; + } + + msec = cg.fadeTime - cg.time; + if ( msec <= 0 ) { + cg.fadeColor1[ 0 ] = cg.fadeColor2[ 0 ]; + cg.fadeColor1[ 1 ] = cg.fadeColor2[ 1 ]; + cg.fadeColor1[ 2 ] = cg.fadeColor2[ 2 ]; + cg.fadeColor1[ 3 ] = cg.fadeColor2[ 3 ]; + + if ( !cg.fadeColor1[ 3 ] ) { + cg.fadeRate = 0; + return; + } + + CG_FillRect( 0, 0, 640, 480, cg.fadeColor1 ); + + } else { + t = ( float )msec * cg.fadeRate; + invt = 1.0f - t; + + for ( i = 0; i < 4; i++ ) { + color[ i ] = cg.fadeColor1[ i ] * t + cg.fadeColor2[ i ] * invt; + } + + if ( color[ 3 ] ) { + CG_FillRect( 0, 0, 640, 480, color ); + } + } +} + +// JPW NERVE +void CG_Draw2D2( void ) { + qhandle_t weapon; + vec4_t hcolor; + + VectorSet( hcolor, 1.f,1.f,1.f ); +// VectorSet(hcolor, cg_hudAlpha.value, cg_hudAlpha.value, cg_hudAlpha.value); + hcolor[3] = cg_hudAlpha.value; + trap_R_SetColor( hcolor ); + + CG_DrawPic( 0,480, 640, -70, cgs.media.hud1Shader ); + + if ( !( cg.snap->ps.eFlags & EF_MG42_ACTIVE ) ) { + switch ( cg.snap->ps.weapon ) { + case WP_COLT: + case WP_LUGER: + weapon = cgs.media.hud2Shader; + break; + case WP_KNIFE: + weapon = cgs.media.hud5Shader; + break; + case WP_VENOM: + weapon = cgs.media.hud4Shader; + break; + default: + weapon = cgs.media.hud3Shader; + } + CG_DrawPic( 220,410, 200,-200,weapon ); + } +} +// jpw + +/* +================= +CG_DrawCompassIcon + +NERVE - SMF +================= +*/ +void CG_DrawCompassIcon( int x, int y, int w, int h, vec3_t origin, vec3_t dest, qhandle_t shader ) { + float angle, pi2 = M_PI * 2; + vec3_t v1, angles; + float len; + + VectorCopy( dest, v1 ); + VectorSubtract( origin, v1, v1 ); + len = VectorLength( v1 ); + VectorNormalize( v1 ); + vectoangles( v1, angles ); + + if ( v1[0] == 0 && v1[1] == 0 && v1[2] == 0 ) { + return; + } + + angles[YAW] = AngleSubtract( cg.snap->ps.viewangles[YAW], angles[YAW] ); + + angle = ( ( angles[YAW] + 180.f ) / 360.f - ( 0.50 / 2.f ) ) * pi2; + + w /= 2; + h /= 2; + + x += w; + y += h; + + w = sqrt( ( w * w ) + ( h * h ) ) / 3.f * 2.f * 0.9f; + + x = x + ( cos( angle ) * w ); + y = y + ( sin( angle ) * w ); + + len = 1 - min( 1.f, len / 2000.f ); + + CG_DrawPic( x - ( 14 * len + 4 ) / 2, y - ( 14 * len + 4 ) / 2, 14 * len + 4, 14 * len + 4, shader ); +} + +/* +================= +CG_DrawCompass + +NERVE - SMF +================= +*/ +static void CG_DrawCompass( void ) { + float basex = 290, basey = 420; + float basew = 60, baseh = 60; + snapshot_t *snap; + vec4_t hcolor; + float angle; + int i; + + if ( cgs.gametype < GT_WOLF ) { + return; + } + + if ( cg.snap->ps.pm_flags & PMF_LIMBO || cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + angle = ( cg.snap->ps.viewangles[YAW] + 180.f ) / 360.f - ( 0.25 / 2.f ); + + VectorSet( hcolor, 1.f,1.f,1.f ); + hcolor[3] = cg_hudAlpha.value; + trap_R_SetColor( hcolor ); + + CG_DrawRotatedPic( basex, basey, basew, baseh, trap_R_RegisterShader( "gfx/2d/compass2.tga" ), angle ); + CG_DrawPic( basex, basey, basew, baseh, trap_R_RegisterShader( "gfx/2d/compass.tga" ) ); + + // draw voice chats + for ( i = 0; i < MAX_CLIENTS; i++ ) { + centity_t *cent = &cg_entities[i]; + + if ( cg.snap->ps.clientNum == i || !cgs.clientinfo[i].infoValid || cg.snap->ps.persistant[PERS_TEAM] != cgs.clientinfo[i].team ) { + continue; + } + + // also draw revive icons if cent is dead and player is a medic + if ( cent->voiceChatSpriteTime < cg.time ) { + continue; + } + + if ( cgs.clientinfo[i].health <= 0 ) { + // reset + cent->voiceChatSpriteTime = cg.time; + continue; + } + + CG_DrawCompassIcon( basex, basey, basew, baseh, cg.snap->ps.origin, cent->lerpOrigin, cent->voiceChatSprite ); + } + + // draw explosives if an engineer + if ( cg.snap->ps.stats[ STAT_PLAYER_CLASS ] == PC_ENGINEER ) { + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + snap = cg.nextSnap; + } else { + snap = cg.snap; + } + + for ( i = 0; i < snap->numEntities; i++ ) { + centity_t *cent = &cg_entities[ snap->entities[ i ].number ]; + + if ( cent->currentState.eType != ET_EXPLOSIVE_INDICATOR ) { + continue; + } + + if ( cent->currentState.teamNum == 1 && cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + continue; + } else if ( cent->currentState.teamNum == 2 && cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + continue; + } + + CG_DrawCompassIcon( basex, basey, basew, baseh, cg.snap->ps.origin, cent->lerpOrigin, trap_R_RegisterShader( "sprites/destroy.tga" ) ); + } + } + + // draw revive medic icons + if ( cg.snap->ps.stats[ STAT_PLAYER_CLASS ] == PC_MEDIC ) { + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + snap = cg.nextSnap; + } else { + snap = cg.snap; + } + + for ( i = 0; i < snap->numEntities; i++ ) { + entityState_t *ent = &snap->entities[i]; + + if ( ent->eType != ET_PLAYER ) { + continue; + } + + if ( ( ent->eFlags & EF_DEAD ) && ent->number == ent->clientNum ) { + if ( !cgs.clientinfo[ent->clientNum].infoValid || cg.snap->ps.persistant[PERS_TEAM] != cgs.clientinfo[ent->clientNum].team ) { + continue; + } + + CG_DrawCompassIcon( basex, basey, basew, baseh, cg.snap->ps.origin, ent->pos.trBase, cgs.media.medicReviveShader ); + } + } + } +} +// -NERVE - SMF + +/* +================= +CG_Draw2D +================= +*/ +static void CG_Draw2D( void ) { + + // if we are taking a levelshot for the menu, don't draw anything + if ( cg.levelShot ) { + return; + } + + if ( cg_draw2D.integer == 0 ) { + return; + } + + CG_ScreenFade(); + + if ( cg.cameraMode ) { //----(SA) no 2d when in camera view + return; + } + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + CG_DrawIntermission(); + return; + } + + CG_DrawFlashBlendBehindHUD(); + +#ifndef PRE_RELEASE_DEMO + if ( cg_uselessNostalgia.integer ) { + CG_DrawCrosshair(); + CG_Draw2D2(); + return; + } +#endif + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + CG_DrawSpectator(); + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + + // NERVE - SMF - we need to do this for spectators as well + if ( cgs.gametype >= GT_TEAM ) { + CG_DrawTeamInfo(); + } + } else { + // don't draw any status if dead + if ( cg.snap->ps.stats[STAT_HEALTH] > 0 || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { + + CG_DrawCrosshair(); + + CG_DrawDynamiteStatus(); + CG_DrawCrosshairNames(); + + // DHM - Nerve :: Don't draw icon in upper-right when switching weapons + //CG_DrawWeaponSelect(); + + CG_DrawPickupItem(); + } + if ( cgs.gametype >= GT_TEAM ) { + CG_DrawTeamInfo(); + } + if ( cg_drawStatus.integer ) { + Menu_PaintAll(); + CG_DrawTimedMenus(); + } + } + + CG_DrawVote(); + + CG_DrawLagometer(); + + if ( !cg_paused.integer ) { + CG_DrawUpperRight(); + } + + // don't draw center string if scoreboard is up + if ( !CG_DrawScoreboard() ) { + CG_DrawCenterString(); + + CG_DrawFollow(); + CG_DrawWarmup(); + + //if ( cg_drawNotifyText.integer ) + CG_DrawNotify(); + + // NERVE - SMF + if ( cg_drawCompass.integer ) { + CG_DrawCompass(); + } + + CG_DrawObjectiveInfo(); + CG_DrawObjectiveIcons(); + + CG_DrawSpectatorMessage(); + + CG_DrawLimboMessage(); + // -NERVE - SMF + } + + // Ridah, draw flash blends now + CG_DrawFlashBlend(); +} + +// NERVE - SMF +void CG_StartShakeCamera( float p ) { + cg.cameraShakeScale = p; + + cg.cameraShakeLength = 1000 * ( p * p ); + cg.cameraShakeTime = cg.time + cg.cameraShakeLength; + cg.cameraShakePhase = crandom() * M_PI; // start chain in random dir +} + +void CG_ShakeCamera() { + float x, val; + + if ( cg.time > cg.cameraShakeTime ) { + cg.cameraShakeScale = 0; // JPW NERVE all pending explosions resolved, so reset shakescale + return; + } + + // JPW NERVE starts at 1, approaches 0 over time + x = ( cg.cameraShakeTime - cg.time ) / cg.cameraShakeLength; + + // up/down + val = sin( M_PI * 8 * x + cg.cameraShakePhase ) * x * 18.0f * cg.cameraShakeScale; + cg.refdefViewAngles[0] += val; + + // left/right + val = sin( M_PI * 15 * x + cg.cameraShakePhase ) * x * 16.0f * cg.cameraShakeScale; + cg.refdefViewAngles[1] += val; + + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); +} +// -NERVE - SMF + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) { + float separation; + vec3_t baseOrg; + + // optionally draw the info screen instead + if ( !cg.snap ) { + CG_DrawInformation(); + return; + } + + // if they are waiting at the mission stats screen, show the stats + if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { + if ( strlen( cg_missionStats.string ) > 1 ) { + trap_Cvar_Set( "com_expectedhunkusage", "-2" ); + CG_DrawInformation(); + return; + } + } + + // optionally draw the tournement scoreboard instead + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && + ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { + CG_DrawTourneyScoreboard(); + return; + } + + switch ( stereoView ) { + case STEREO_CENTER: + separation = 0; + break; + case STEREO_LEFT: + separation = -cg_stereoSeparation.value / 2; + break; + case STEREO_RIGHT: + separation = cg_stereoSeparation.value / 2; + break; + default: + separation = 0; + CG_Error( "CG_DrawActive: Undefined stereoView" ); + } + + + // clear around the rendered view if sized down + CG_TileClear(); + + // offset vieworg appropriately if we're doing stereo separation + VectorCopy( cg.refdef.vieworg, baseOrg ); + if ( separation != 0 ) { + VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); + } + + cg.refdef.glfog.registered = 0; // make sure it doesn't use fog from another scene + + // NERVE - SMF - activate limbo menu and draw small 3d window + CG_ActivateLimboMenu(); + + if ( cg.limboMenu ) { + float x, y, w, h; + x = LIMBO_3D_X; + y = LIMBO_3D_Y; + w = LIMBO_3D_W; + h = LIMBO_3D_H; + + cg.refdef.width = 0; + CG_AdjustFrom640( &x, &y, &w, &h ); + + cg.refdef.x = x; + cg.refdef.y = y; + cg.refdef.width = w; + cg.refdef.height = h; + } + // -NERVE - SMF + + CG_ShakeCamera(); // NERVE - SMF + + trap_R_RenderScene( &cg.refdef ); + + // restore original viewpoint if running stereo + if ( separation != 0 ) { + VectorCopy( baseOrg, cg.refdef.vieworg ); + } + + // draw status bar and other floating elements + CG_Draw2D(); +} + + diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c new file mode 100644 index 0000000..e9f7d61 --- /dev/null +++ b/src/cgame/cg_drawtools.c @@ -0,0 +1,1316 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc +#include "cg_local.h" + +/* +================ +CG_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { +#if 0 + // adjust for wide screens + if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + + // NERVE - SMF - hack to make images display properly in small view / limbo mode + if ( cg.limboMenu && cg.refdef.width ) { + float xscale = ( ( cg.refdef.width / cgs.screenXScale ) / 640.f ); + float yscale = ( ( cg.refdef.height / cgs.screenYScale ) / 480.f ); + + ( *x ) = ( *x ) * xscale + ( cg.refdef.x / cgs.screenXScale ); + ( *y ) = ( *y ) * yscale + ( cg.refdef.y / cgs.screenYScale ); + ( *w ) *= xscale; + ( *h ) *= yscale; + } + // -NERVE - SMF + + // scale for screen sizes + *x *= cgs.screenXScale; + *y *= cgs.screenYScale; + *w *= cgs.screenXScale; + *h *= cgs.screenYScale; +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 1, cgs.media.whiteShader ); + + trap_R_SetColor( NULL ); +} + +/* +============== +CG_FillRectGradient +============== +*/ +void CG_FillRectGradient( float x, float y, float width, float height, const float *color, const float *gradcolor, int gradientType ) { + trap_R_SetColor( color ); + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPicGradient( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader, gradcolor, gradientType ); + + trap_R_SetColor( NULL ); +} + + +/* +============== +CG_HorizontalPercentBar + Generic routine for pretty much all status indicators that show a fractional + value to the palyer by virtue of how full a drawn box is. + +flags: + left - 1 + center - 2 // direction is 'right' by default and orientation is 'horizontal' + vert - 4 + nohudalpha - 8 // don't adjust bar's alpha value by the cg_hudalpha value + bg - 16 // background contrast box (bg set with bgColor of 'NULL' means use default bg color (1,1,1,0.25) + spacing - 32 // some bars use different sorts of spacing when drawing both an inner and outer box + + lerp color - 256 // use an average of the start and end colors to set the fill color +============== +*/ + + +// TODO: these flags will be shared, but it was easier to work on stuff if I wasn't changing header files a lot +#define BAR_LEFT 0x0001 +#define BAR_CENTER 0x0002 +#define BAR_VERT 0x0004 +#define BAR_NOHUDALPHA 0x0008 +#define BAR_BG 0x0010 +// different spacing modes for use w/ BAR_BG +#define BAR_BGSPACING_X0Y5 0x0020 +#define BAR_BGSPACING_X0Y0 0x0040 + +#define BAR_LERP_COLOR 0x0100 + +#define BAR_BORDERSIZE 2 + +void CG_FilledBar( float x, float y, float w, float h, float *startColor, float *endColor, const float *bgColor, float frac, int flags ) { + vec4_t backgroundcolor = {1, 1, 1, 0.25f}, colorAtPos; // colorAtPos is the lerped color if necessary + int indent = BAR_BORDERSIZE; + + if ( ( flags & BAR_BG ) && bgColor ) { // BAR_BG set, and color specified, use specified bg color + Vector4Copy( bgColor, backgroundcolor ); + } + + // hud alpha + if ( !( flags & BAR_NOHUDALPHA ) ) { + startColor[3] *= cg_hudAlpha.value; + if ( endColor ) { + endColor[3] *= cg_hudAlpha.value; + } + if ( backgroundcolor ) { + backgroundcolor[3] *= cg_hudAlpha.value; + } + } + + if ( flags & BAR_LERP_COLOR ) { + Vector4Average( startColor, endColor, frac, colorAtPos ); + } + + // background + if ( ( flags & BAR_BG ) ) { + // draw background at full size and shrink the remaining box to fit inside with a border. (alternate border may be specified by a BAR_BGSPACING_xx) + CG_FillRect( x, + y, + w, + h, + backgroundcolor ); + + if ( flags & BAR_BGSPACING_X0Y0 ) { // fill the whole box (no border) + + } else if ( flags & BAR_BGSPACING_X0Y5 ) { // spacing created for weapon heat + indent *= 3; + y += indent; + h -= ( 2 * indent ); + + } else { // default spacing of 2 units on each side + x += indent; + y += indent; + w -= ( 2 * indent ); + h -= ( 2 * indent ); + } + } + + + // adjust for horiz/vertical and draw the fractional box + if ( flags & BAR_VERT ) { + if ( flags & BAR_LEFT ) { // TODO: remember to swap colors on the ends here + y += ( h * ( 1 - frac ) ); + } else if ( flags & BAR_CENTER ) { + y += ( h * ( 1 - frac ) / 2 ); + } + + if ( flags & BAR_LERP_COLOR ) { + CG_FillRect( x, y, w, h * frac, colorAtPos ); + } else { +// CG_FillRectGradient ( x, y, w, h * frac, startColor, endColor, 0 ); + CG_FillRect( x, y, w, h * frac, startColor ); + } + + } else { + + if ( flags & BAR_LEFT ) { // TODO: remember to swap colors on the ends here + x += ( w * ( 1 - frac ) ); + } else if ( flags & BAR_CENTER ) { + x += ( w * ( 1 - frac ) / 2 ); + } + + if ( flags & BAR_LERP_COLOR ) { + CG_FillRect( x, y, w * frac, h, colorAtPos ); + } else { +// CG_FillRectGradient ( x, y, w * frac, h, startColor, endColor, 0 ); + CG_FillRect( x, y, w * frac, h, startColor ); + } + } + +} + + +/* +================= +CG_HorizontalPercentBar +================= +*/ +void CG_HorizontalPercentBar( float x, float y, float width, float height, float percent ) { + vec4_t bgcolor = {0.5f, 0.5f, 0.5f, 0.3f}, + color = {1.0f, 1.0f, 1.0f, 0.3f}; + CG_FilledBar( x, y, width, height, color, NULL, bgcolor, percent, BAR_BG | BAR_NOHUDALPHA ); +} + +/* +================= +CG_DrawMotd +================= +*/ +void CG_DrawMotd() { + const char *s; + vec4_t color = { 0.5f, 0.5f, 0.5f, 0.3f }; + int len; + + s = CG_ConfigString( CS_MOTD ); + if ( s[0] ) { + CG_FillRect( 0, 448, 640, 14, color ); + len = (int)( (float)UI_ProportionalStringWidth( s ) * UI_ProportionalSizeScale( UI_EXSMALLFONT ) / 2 ); + CG_DrawStringExt( 320 - len, 445, s, colorWhite, qfalse, qtrue, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); + } +} + +/* +================ +CG_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides( float x, float y, float w, float h, float size ) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenXScale; + trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); +} + +void CG_DrawTopBottom( float x, float y, float w, float h, float size ) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenYScale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); +} + +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { + vec4_t hudAlphaColor; + + Vector4Copy( color, hudAlphaColor ); + hudAlphaColor[3] *= cg_hudAlpha.value; + + trap_R_SetColor( hudAlphaColor ); + + CG_DrawTopBottom( x, y, width, height, size ); + CG_DrawSides( x, y, width, height, size ); + + trap_R_SetColor( NULL ); +} + + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +// NERVE - SMF +/* +================ +CG_DrawRotatedPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRotatedPic( float x, float y, float width, float height, qhandle_t hShader, float angle ) { + + CG_AdjustFrom640( &x, &y, &width, &height ); + + trap_R_DrawRotatedPic( x, y, width, height, 0, 0, 1, 1, hShader, angle ); +} +// -NERVE - SMF + +/* +=============== +CG_DrawChar + +Coordinates and size in 640*480 virtual screen size +=============== +*/ +void CG_DrawChar( int x, int y, int width, int height, int ch ) { + int row, col; + float frow, fcol; + float size; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + ax = x; + ay = y; + aw = width; + ah = height; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625; + fcol = col * 0.0625; + size = 0.0625; + + trap_R_DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cgs.media.charsetShader ); +} + +/* +=============== +CG_DrawChar2 + +Coordinates and size in 640*480 virtual screen size +=============== +*/ +void CG_DrawChar2( int x, int y, int width, int height, int ch ) { + int row, col; + float frow, fcol; + float size; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + ax = x; + ay = y; + aw = width; + ah = height; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625; + fcol = col * 0.0625; + size = 0.0625; + + trap_R_DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cgs.media.menucharsetShader ); +} + +// JOSEPH 4-25-00 +/* +================== +CG_DrawStringExt + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { + vec4_t color; + const char *s; + int xx; + int cnt; + + if ( maxChars <= 0 ) { + maxChars = 32767; // do them all! + + } + // draw the drop shadow + if ( shadow ) { + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + trap_R_SetColor( color ); + s = string; + xx = x; + cnt = 0; + while ( *s && cnt < maxChars ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); + cnt++; + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + cnt = 0; + trap_R_SetColor( setColor ); + while ( *s && cnt < maxChars ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( color ) ); + color[3] = setColor[3]; + trap_R_SetColor( color ); + } + s += 2; + continue; + } + CG_DrawChar( xx, y, charWidth, charHeight, *s ); + xx += charWidth; + cnt++; + s++; + } + trap_R_SetColor( NULL ); +} + +/*================== +CG_DrawStringExt2 + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void CG_DrawStringExt2( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { + vec4_t color; + const char *s; + int xx; + int cnt; + + if ( maxChars <= 0 ) { + maxChars = 32767; // do them all! + + } + // draw the drop shadow + if ( shadow ) { + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + trap_R_SetColor( color ); + s = string; + xx = x; + cnt = 0; + while ( *s && cnt < maxChars ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar2( xx + 2, y + 2, charWidth, charHeight, *s ); + cnt++; + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + cnt = 0; + trap_R_SetColor( setColor ); + while ( *s && cnt < maxChars ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( color ) ); + color[3] = setColor[3]; + trap_R_SetColor( color ); + } + s += 2; + continue; + } + CG_DrawChar2( xx, y, charWidth, charHeight, *s ); + xx += charWidth; + cnt++; + s++; + } + trap_R_SetColor( NULL ); +} + +/*================== +CG_DrawStringExt3 + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void CG_DrawStringExt3( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { + vec4_t color; + const char *s; + int xx; + int cnt; + + if ( maxChars <= 0 ) { + maxChars = 32767; // do them all! + + } + s = string; + xx = 0; + + while ( *s ) { + xx += charWidth; + s++; + } + + x -= xx; + + s = string; + xx = x; + + // draw the drop shadow + if ( shadow ) { + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + trap_R_SetColor( color ); + s = string; + xx = x; + cnt = 0; + while ( *s && cnt < maxChars ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar2( xx + 2, y + 2, charWidth, charHeight, *s ); + cnt++; + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + cnt = 0; + trap_R_SetColor( setColor ); + while ( *s && cnt < maxChars ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( color ) ); + color[3] = setColor[3]; + trap_R_SetColor( color ); + } + s += 2; + continue; + } + CG_DrawChar2( xx, y, charWidth, charHeight, *s ); + xx += charWidth; + cnt++; + s++; + } + trap_R_SetColor( NULL ); +} + +/* +================== +CG_DrawStringExt2 + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +/*void CG_DrawStringExt2( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { + vec4_t color; + const char *s; + int xx; + int cnt; + + if (maxChars <= 0) + maxChars = 32767; // do them all! + + // draw the drop shadow + if (shadow) { + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + trap_R_SetColor( color ); + s = string; + xx = x; + cnt = 0; + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar2( xx + 2, y + 2, charWidth, charHeight, *s ); + cnt++; + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + cnt = 0; + trap_R_SetColor( setColor ); + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + trap_R_SetColor( color ); + } + s += 2; + continue; + } + CG_DrawChar2( xx, y, charWidth, charHeight, *s ); + xx += charWidth; + cnt++; + s++; + } + trap_R_SetColor( NULL ); +}*/ + +void CG_DrawBigString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + //CG_DrawStringExt( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); + CG_DrawStringExt2( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { + //CG_DrawStringExt( x, y, s, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); + CG_DrawStringExt2( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} +// END JOSEPH + +// JOSEPH 4-25-00 +void CG_DrawBigString2( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt3( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawBigStringColor2( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt3( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} +// END JOSEPH + +void CG_DrawSmallString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +/* +================= +CG_DrawStrlen + +Returns character count, skiping color escape codes +================= +*/ +int CG_DrawStrlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { + float s1, t1, s2, t2; + s1 = x / 64.0; + t1 = y / 64.0; + s2 = ( x + w ) / 64.0; + t2 = ( y + h ) / 64.0; + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) { + int top, bottom, left, right; + int w, h; + + w = cgs.glconfig.vidWidth; + h = cgs.glconfig.vidHeight; + + if ( cg.refdef.x == 0 && cg.refdef.y == 0 && + cg.refdef.width == w && cg.refdef.height == h ) { + return; // full screen rendering + } + + top = cg.refdef.y; + bottom = top + cg.refdef.height - 1; + left = cg.refdef.x; + right = left + cg.refdef.width - 1; + + // clear above view screen + CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + + // clear below view screen + CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + + // clear left of view screen + CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + + // clear right of view screen + CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +} + + + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) { + static vec4_t color; + int t; + + if ( startMsec == 0 ) { + return NULL; + } + + t = cg.time - startMsec; + + if ( t >= totalMsec ) { + return NULL; + } + + // fade out + if ( totalMsec - t < FADE_TIME ) { + color[3] = ( totalMsec - t ) * 1.0 / FADE_TIME; + } else { + color[3] = 1.0; + } + color[0] = color[1] = color[2] = 1; + + color[3] *= cg_hudAlpha.value; // NERVE - SMF - make this work like everything else + + return color; +} + + +/* +================ +CG_TeamColor +================ +*/ +float *CG_TeamColor( int team ) { + static vec4_t red = {1, 0.2, 0.2, 1}; + static vec4_t blue = {0.2, 0.2, 1, 1}; + static vec4_t other = {1, 1, 1, 1}; + static vec4_t spectator = {0.7, 0.7, 0.7, 1}; + + switch ( team ) { + case TEAM_RED: + return red; + case TEAM_BLUE: + return blue; + case TEAM_SPECTATOR: + return spectator; + default: + return other; + } +} + + +/* +================= +CG_GetColorForHealth +================= +*/ +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + if ( health <= 0 ) { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + count = armor; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) { + count = max; + } + health += count; + + // set the color based on health + hcolor[0] = 1.0; + hcolor[3] = 1.0; + if ( health >= 100 ) { + hcolor[2] = 1.0; + } else if ( health < 66 ) { + hcolor[2] = 0; + } else { + hcolor[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) { + hcolor[1] = 1.0; + } else if ( health < 30 ) { + hcolor[1] = 0; + } else { + hcolor[1] = ( health - 30 ) / 30.0; + } +} + +/* +================= +CG_ColorForHealth +================= +*/ +void CG_ColorForHealth( vec4_t hcolor ) { + int health; + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + health = cg.snap->ps.stats[STAT_HEALTH]; + if ( health <= 0 ) { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + count = cg.snap->ps.stats[STAT_ARMOR]; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) { + count = max; + } + health += count; + + + // set the color based on health + hcolor[0] = 1.0; + hcolor[3] = 1.0; + if ( health >= 100 ) { + hcolor[2] = 1.0; + } else if ( health < 66 ) { + hcolor[2] = 0; + } else { + hcolor[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) { + hcolor[1] = 1.0; + } else if ( health < 30 ) { + hcolor[1] = 0; + } else { + hcolor[1] = ( health - 30 ) / 30.0; + } +} + + + + +/* +================= +UI_DrawProportionalString2 +================= +*/ +static int propMap[128][3] = { + {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + + {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + + {0, 0, PROP_SPACE_WIDTH}, // SPACE + {11, 122, 7}, // ! + {154, 181, 14}, // " + {55, 122, 17}, // # + {79, 122, 18}, // $ + {101, 122, 23}, // % + {153, 122, 18}, // & + {9, 93, 7}, // ' + {207, 122, 8}, // ( + {230, 122, 9}, // ) + {177, 122, 18}, // * + {30, 152, 18}, // + + {85, 181, 7}, // , + {34, 93, 11}, // - + {110, 181, 6}, // . + {130, 152, 14}, // / + + {22, 64, 17}, // 0 + {41, 64, 12}, // 1 + {58, 64, 17}, // 2 + {78, 64, 18}, // 3 + {98, 64, 19}, // 4 + {120, 64, 18}, // 5 + {141, 64, 18}, // 6 + {204, 64, 16}, // 7 + {162, 64, 17}, // 8 + {182, 64, 18}, // 9 + {59, 181, 7}, // : + {35,181, 7}, // ; + {203, 152, 14}, // < + {56, 93, 14}, // = + {228, 152, 14}, // > + {177, 181, 18}, // ? + + {28, 122, 22}, // @ + {5, 4, 18}, // A + {27, 4, 18}, // B + {48, 4, 18}, // C + {69, 4, 17}, // D + {90, 4, 13}, // E + {106, 4, 13}, // F + {121, 4, 18}, // G + {143, 4, 17}, // H + {164, 4, 8}, // I + {175, 4, 16}, // J + {195, 4, 18}, // K + {216, 4, 12}, // L + {230, 4, 23}, // M + {6, 34, 18}, // N + {27, 34, 18}, // O + + {48, 34, 18}, // P + {68, 34, 18}, // Q + {90, 34, 17}, // R + {110, 34, 18}, // S + {130, 34, 14}, // T + {146, 34, 18}, // U + {166, 34, 19}, // V + {185, 34, 29}, // W + {215, 34, 18}, // X + {234, 34, 18}, // Y + {5, 64, 14}, // Z + {60, 152, 7}, // [ + {106, 151, 13}, // '\' + {83, 152, 7}, // ] + {128, 122, 17}, // ^ + {4, 152, 21}, // _ + + {134, 181, 5}, // ' + {5, 4, 18}, // A + {27, 4, 18}, // B + {48, 4, 18}, // C + {69, 4, 17}, // D + {90, 4, 13}, // E + {106, 4, 13}, // F + {121, 4, 18}, // G + {143, 4, 17}, // H + {164, 4, 8}, // I + {175, 4, 16}, // J + {195, 4, 18}, // K + {216, 4, 12}, // L + {230, 4, 23}, // M + {6, 34, 18}, // N + {27, 34, 18}, // O + + {48, 34, 18}, // P + {68, 34, 18}, // Q + {90, 34, 17}, // R + {110, 34, 18}, // S + {130, 34, 14}, // T + {146, 34, 18}, // U + {166, 34, 19}, // V + {185, 34, 29}, // W + {215, 34, 18}, // X + {234, 34, 18}, // Y + {5, 64, 14}, // Z + {153, 152, 13}, // { + {11, 181, 5}, // | + {180, 152, 13}, // } + {79, 93, 17}, // ~ + {0, 0, -1} // DEL +}; + +static int propMapB[26][3] = { + {11, 12, 33}, + {49, 12, 31}, + {85, 12, 31}, + {120, 12, 30}, + {156, 12, 21}, + {183, 12, 21}, + {207, 12, 32}, + + {13, 55, 30}, + {49, 55, 13}, + {66, 55, 29}, + {101, 55, 31}, + {135, 55, 21}, + {158, 55, 40}, + {204, 55, 32}, + + {12, 97, 31}, + {48, 97, 31}, + {82, 97, 30}, + {118, 97, 30}, + {153, 97, 30}, + {185, 97, 25}, + {213, 97, 30}, + + {11, 139, 32}, + {42, 139, 51}, + {93, 139, 32}, + {126, 139, 31}, + {158, 139, 25}, +}; + +#define PROPB_GAP_WIDTH 4 +#define PROPB_SPACE_WIDTH 12 +#define PROPB_HEIGHT 36 + +/* +================= +UI_DrawBannerString +================= +*/ +static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) { + const char* s; + unsigned char ch; + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenXScale; + + s = str; + while ( *s ) + { + ch = *s & 127; + if ( ch == ' ' ) { + ax += ( (float)PROPB_SPACE_WIDTH + (float)PROPB_GAP_WIDTH ) * cgs.screenXScale; + } else if ( ch >= 'A' && ch <= 'Z' ) { + ch -= 'A'; + fcol = (float)propMapB[ch][0] / 256.0f; + frow = (float)propMapB[ch][1] / 256.0f; + fwidth = (float)propMapB[ch][2] / 256.0f; + fheight = (float)PROPB_HEIGHT / 256.0f; + aw = (float)propMapB[ch][2] * cgs.screenXScale; + ah = (float)PROPB_HEIGHT * cgs.screenXScale; + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol + fwidth, frow + fheight, cgs.media.charsetPropB ); + ax += ( aw + (float)PROPB_GAP_WIDTH * cgs.screenXScale ); + } + s++; + } + + trap_R_SetColor( NULL ); +} + +void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ) { + const char * s; + int ch; + int width; + vec4_t drawcolor; + + // find the width of the drawn text + s = str; + width = 0; + while ( *s ) { + ch = *s; + if ( ch == ' ' ) { + width += PROPB_SPACE_WIDTH; + } else if ( ch >= 'A' && ch <= 'Z' ) { + width += propMapB[ch - 'A'][2] + PROPB_GAP_WIDTH; + } + s++; + } + width -= PROPB_GAP_WIDTH; + + switch ( style & UI_FORMATMASK ) { + case UI_CENTER: + x -= width / 2; + break; + + case UI_RIGHT: + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawBannerString2( x + 2, y + 2, str, drawcolor ); + } + + UI_DrawBannerString2( x, y, str, color ); +} + + +int UI_ProportionalStringWidth( const char* str ) { + const char * s; + int ch; + int charWidth; + int width; + + s = str; + width = 0; + while ( *s ) { + ch = *s & 127; + charWidth = propMap[ch][2]; + if ( charWidth != -1 ) { + width += charWidth; + width += PROP_GAP_WIDTH; + } + s++; + } + + width -= PROP_GAP_WIDTH; + return width; +} + +static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t color, float sizeScale, qhandle_t charset ) { + const char* s; + unsigned char ch; + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenXScale; + + s = str; + while ( *s ) + { + ch = *s & 127; + if ( ch == ' ' ) { + aw = (float)PROP_SPACE_WIDTH * cgs.screenXScale * sizeScale; + } else if ( propMap[ch][2] != -1 ) { + fcol = (float)propMap[ch][0] / 256.0f; + frow = (float)propMap[ch][1] / 256.0f; + fwidth = (float)propMap[ch][2] / 256.0f; + fheight = (float)PROP_HEIGHT / 256.0f; + aw = (float)propMap[ch][2] * cgs.screenXScale * sizeScale; + ah = (float)PROP_HEIGHT * cgs.screenXScale * sizeScale; + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol + fwidth, frow + fheight, charset ); + } else { + aw = 0; + } + + ax += ( aw + (float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale ); + s++; + } + + trap_R_SetColor( NULL ); +} + +/* +================= +UI_ProportionalSizeScale +================= +*/ +float UI_ProportionalSizeScale( int style ) { + if ( style & UI_SMALLFONT ) { + return 0.75; + } + if ( style & UI_EXSMALLFONT ) { + return 0.4; + } + + return 1.00; +} + + +/* +================= +UI_DrawProportionalString +================= +*/ +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) { + vec4_t drawcolor; + int width; + float sizeScale; + + sizeScale = UI_ProportionalSizeScale( style ); + + switch ( style & UI_FORMATMASK ) { + case UI_CENTER: + width = UI_ProportionalStringWidth( str ) * sizeScale; + x -= width / 2; + break; + + case UI_RIGHT: + width = UI_ProportionalStringWidth( str ) * sizeScale; + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x + 2, y + 2, str, drawcolor, sizeScale, cgs.media.charsetProp ); + } + + if ( style & UI_INVERSE ) { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetProp ); + return; + } + + // JOSEPH 12-29-99 + if ( style & UI_PULSE ) { + //drawcolor[0] = color[0] * 0.8; + //drawcolor[1] = color[1] * 0.8; + //drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); + + drawcolor[0] = color[0]; + drawcolor[1] = color[1]; + drawcolor[2] = color[2]; + drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetPropGlow ); + return; + } + // END JOSEPH + + UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); +} + +char* CG_TranslateString( const char *string ) { + // dont even make the call if we're in english + return trap_TranslateString( string ); +} diff --git a/src/cgame/cg_effects.c b/src/cgame/cg_effects.c new file mode 100644 index 0000000..667c796 --- /dev/null +++ b/src/cgame/cg_effects.c @@ -0,0 +1,1508 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_effects.c -- these functions generate localentities, usually as a result +// of event processing + +#include "cg_local.h" + + +/* +================== +CG_BubbleTrail + +Bullets shot underwater +================== +*/ +void CG_BubbleTrail( vec3_t start, vec3_t end, float size, float spacing ) { + vec3_t move; + vec3_t vec; + float len; + int i; + + VectorCopy( start, move ); + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // advance a random amount first + i = rand() % (int)spacing; + VectorMA( move, i, vec, move ); + + VectorScale( vec, spacing, vec ); + + for ( ; i < len; i += spacing ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + 1000 + random() * 250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_SPRITE; + re->rotation = 0; +// re->radius = 3; + re->radius = size; // (SA) + re->customShader = cgs.media.waterBubbleShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + + le->color[3] = 1.0; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + VectorCopy( move, le->pos.trBase ); + le->pos.trDelta[0] = crandom() * 3; + le->pos.trDelta[1] = crandom() * 3; +// le->pos.trDelta[2] = crandom()*5 + 6; + le->pos.trDelta[2] = crandom() * 5 + 20; // (SA) + + VectorAdd( move, vec, move ); + } +} + +/* +===================== +CG_SmokePuff + +Adds a smoke puff or blood trail localEntity. + +(SA) boy, it would be nice to have an acceleration vector for this as well. + big velocity vector with a negative acceleration for deceleration, etc. + (breath could then come out of a guys mouth at the rate he's walking/running and it + would slow down once it's created) +===================== +*/ + +//----(SA) modified +localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ) { + static int seed = 0x92; + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = leFlags; + le->radius = radius; + + re = &le->refEntity; + re->rotation = Q_random( &seed ) * 360; + re->radius = radius; + re->shaderTime = startTime / 1000.0f; + + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = startTime; + le->endTime = startTime + duration; + le->fadeInTime = fadeInTime; + if ( fadeInTime > startTime ) { + le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); + } else { + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + } + le->color[0] = r; + le->color[1] = g; + le->color[2] = b; + le->color[3] = a; + + + le->pos.trType = TR_LINEAR; + le->pos.trTime = startTime; + VectorCopy( vel, le->pos.trDelta ); + VectorCopy( p, le->pos.trBase ); + + VectorCopy( p, re->origin ); + re->customShader = hShader; + + // rage pro can't alpha fade, so use a different shader + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + re->customShader = cgs.media.smokePuffRageProShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + } else { + re->shaderRGBA[0] = le->color[0] * 0xff; + re->shaderRGBA[1] = le->color[1] * 0xff; + re->shaderRGBA[2] = le->color[2] * 0xff; + re->shaderRGBA[3] = 0xff; + } +// JPW NERVE + if ( cg_fxflags & 1 ) { + re->customShader = getTestShader(); + re->rotation = 180; + } +// jpw + + re->reType = RT_SPRITE; + re->radius = le->radius; + + return le; +} + +/* +================== +CG_SpawnEffect + +Player teleporting in or out +================== +*/ +void CG_SpawnEffect( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + + return; // (SA) don't play spawn in effect right now + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + 500; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->customShader = cgs.media.teleportEffectShader; + re->hModel = cgs.media.teleportEffectModel; + AxisClear( re->axis ); + + VectorCopy( org, re->origin ); + re->origin[2] -= 24; +} + +qhandle_t getTestShader( void ) { + switch ( rand() % 2 ) { + case 0: + return cgs.media.nerveTestShader; + break; + case 1: + default: + return cgs.media.idTestShader; + break; + } +} + +/* +==================== +CG_MakeExplosion +==================== +*/ +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, + int msec, qboolean isSprite ) { + float ang; + localEntity_t *ex; + int offset; + vec3_t tmpVec, newOrigin; + + if ( msec <= 0 ) { + CG_Error( "CG_MakeExplosion: msec = %i", msec ); + } + + // skew the time a bit so they aren't all in sync + offset = rand() & 63; + + ex = CG_AllocLocalEntity(); + if ( isSprite ) { + ex->leType = LE_SPRITE_EXPLOSION; + + // randomly rotate sprite orientation + ex->refEntity.rotation = rand() % 360; + VectorScale( dir, 16, tmpVec ); + VectorAdd( tmpVec, origin, newOrigin ); + } else { + ex->leType = LE_EXPLOSION; + VectorCopy( origin, newOrigin ); + + // set axis with random rotate + if ( !dir ) { + AxisClear( ex->refEntity.axis ); + } else { + ang = rand() % 360; + VectorCopy( dir, ex->refEntity.axis[0] ); + RotateAroundDirection( ex->refEntity.axis, ang ); + } + } + + ex->startTime = cg.time - offset; + ex->endTime = ex->startTime + msec; + + // bias the time so all shader effects start correctly + ex->refEntity.shaderTime = ex->startTime / 1000.0f; + + ex->refEntity.hModel = hModel; + ex->refEntity.customShader = shader; + + // set origin + VectorCopy( newOrigin, ex->refEntity.origin ); + VectorCopy( newOrigin, ex->refEntity.oldorigin ); + + // Ridah, move away from the wall as the sprite expands + ex->pos.trType = TR_LINEAR; + ex->pos.trTime = cg.time; + VectorCopy( newOrigin, ex->pos.trBase ); + VectorScale( dir, 48, ex->pos.trDelta ); + // done. + + ex->color[0] = ex->color[1] = ex->color[2] = 1.0; + + return ex; +} + +/* +================= +CG_AddBloodTrails +================= +*/ +void CG_AddBloodTrails( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity; + int i; + + for ( i = 0; i < count; i++ ) { + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + VectorSet( velocity, dir[0] + crandom() * randScale, dir[1] + crandom() * randScale, dir[2] + crandom() * randScale ); + VectorScale( velocity, (float)speed, velocity ); + + le->leType = LE_BLOOD; + le->startTime = cg.time; + le->endTime = le->startTime + duration; // DHM - Nerve :: (removed) - (int)(0.5 * random() * duration); + le->lastTrailTime = cg.time; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + + le->pos.trType = TR_GRAVITY_LOW; + VectorCopy( origin, le->pos.trBase ); + VectorMA( le->pos.trBase, 2 + random() * 4, dir, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->bounceFactor = 0.9; + } +} + +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, int entityNum ) { +#define BLOOD_SPURT_COUNT 4 + int i,j; +// localEntity_t *ex; + centity_t *cent; +// vec3_t dir; + + if ( !cg_blood.integer ) { + return; + } + + cent = &cg_entities[entityNum]; + + if ( cent->currentState.aiChar == AICHAR_ZOMBIE ) { + CG_ParticleBloodCloudZombie( cent, origin, vec3_origin ); + return; + } + + // Ridah, blood spurts + if ( entityNum != cg.snap->ps.clientNum ) { + vec3_t vhead, vlegs, vtorso, bOrigin, dir, vec, pvec, ndir; + + CG_GetBleedOrigin( vhead, vtorso, vlegs, entityNum ); + + // project the impact point onto the vector defined by torso -> head + ProjectPointOntoVector( origin, vtorso, vhead, bOrigin ); + + // if it's below the waste, or above the head, clamp + VectorSubtract( vhead, vtorso, vec ); + VectorSubtract( bOrigin, vtorso, pvec ); + if ( DotProduct( pvec, vec ) < 0 ) { + VectorCopy( vtorso, bOrigin ); + } else { + VectorSubtract( bOrigin, vhead, pvec ); + if ( DotProduct( pvec, vec ) > 0 ) { + VectorCopy( vhead, bOrigin ); + } + } + + // spawn some blood trails, heading out towards the impact point + VectorSubtract( origin, bOrigin, dir ); + VectorNormalize( dir ); + + { + float len; + vec3_t vec; + + VectorSubtract( bOrigin, vhead, vec ); + len = VectorLength( vec ); + + if ( len > 8 ) { + VectorMA( bOrigin, 8, dir, bOrigin ); + } + } + + // DHM - Nerve :: Made minor adjustments + for ( i = 0; i < BLOOD_SPURT_COUNT; i++ ) { + VectorCopy( dir, ndir ); + for ( j = 0; j < 3; j++ ) ndir[j] += crandom() * 0.3; + VectorNormalize( ndir ); + CG_AddBloodTrails( bOrigin, ndir, + 100, // speed + 450 + (int)( crandom() * 50 ), // duration + 2 + rand() % 2, // count + 0.1 ); // rand scale + } + + } + // done. +} + + + +/* +================== +CG_LaunchGib +================== +*/ +void CG_LaunchGib( centity_t *cent, vec3_t origin, vec3_t angles, vec3_t velocity, qhandle_t hModel, float sizeScale, int breakCount ) { + localEntity_t *le; + refEntity_t *re; + int i; + + if ( !cg_blood.integer ) { + return; + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + // le->endTime = le->startTime + 60000 + random() * 60000; + le->endTime = le->startTime + 20000 + ( crandom() * 5000 ); + le->breakCount = breakCount; + le->sizeScale = sizeScale; + + VectorCopy( angles, le->angles.trBase ); + VectorCopy( origin, re->origin ); + AnglesToAxis( angles, re->axis ); + if ( sizeScale != 1.0 ) { + for ( i = 0; i < 3; i++ ) VectorScale( re->axis[i], sizeScale, re->axis[i] ); + } + re->hModel = hModel; + + // re->fadeStartTime = le->endTime - 3000; + re->fadeStartTime = le->endTime - 1000; + re->fadeEndTime = le->endTime; + + switch ( cent->currentState.aiChar ) { + case AICHAR_ZOMBIE: + le->pos.trType = TR_GRAVITY_LOW; + le->angles.trDelta[0] = 400 * crandom(); + le->angles.trDelta[1] = 400 * crandom(); + le->angles.trDelta[2] = 400 * crandom(); + + le->leBounceSoundType = LEBS_BONE; + + le->bounceFactor = 0.5; + break; + default: + le->leBounceSoundType = LEBS_BLOOD; + le->leMarkType = LEMT_BLOOD; + le->pos.trType = TR_GRAVITY; + + le->angles.trDelta[0] = ( 10 + ( rand() & 50 ) ) - 30; + // le->angles.trDelta[0] = (100 + (rand()&500)) - 300; // pitch + le->angles.trDelta[1] = ( 100 + ( rand() & 500 ) ) - 300; // (SA) this is the safe one right now (yaw) turn the others up when I have tumbling things landing properly + le->angles.trDelta[2] = ( 10 + ( rand() & 50 ) ) - 30; + // le->angles.trDelta[2] = (100 + (rand()&500)) - 300; // roll + + le->bounceFactor = 0.3; + break; + } + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + + le->angles.trType = TR_LINEAR; + + le->angles.trTime = cg.time; + + le->ownerNum = cent->currentState.number; + + // Ridah, if the player is on fire, then spawn some flaming gibs + if ( cent && CG_EntOnFire( cent ) ) { + le->onFireStart = cent->currentState.onFireStart; + le->onFireEnd = re->fadeEndTime + 1000; + } else if ( ( cent->currentState.aiChar == AICHAR_ZOMBIE ) && IS_FLAMING_ZOMBIE( cent->currentState ) ) { + le->onFireStart = cg.time - 1000; + le->onFireEnd = re->fadeEndTime + 1000; + } +} + +//#define GIB_VELOCITY 250 +//#define GIB_JUMP 250 + +#define GIB_VELOCITY 75 +#define GIB_JUMP 250 + + +/* +============== +CG_LoseHat +============== +*/ +void CG_LoseHat( centity_t *cent, vec3_t dir ) { + clientInfo_t *ci; + int clientNum; +// int i, count, tagIndex, gibIndex; + int tagIndex; + vec3_t origin, velocity; + + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity" ); + } + ci = &cgs.clientinfo[ clientNum ]; + + if ( !ci->accModels[ACC_HAT] ) { // don't launch anything if they don't have one + return; + } + + tagIndex = CG_GetOriginForTag( cent, ¢->pe.headRefEnt, "tag_mouth", 0, origin, NULL ); + + velocity[0] = dir[0] * ( 0.75 + random() ) * GIB_VELOCITY; + velocity[1] = dir[1] * ( 0.75 + random() ) * GIB_VELOCITY; + velocity[2] = GIB_JUMP - 50 + dir[2] * ( 0.5 + random() ) * GIB_VELOCITY; + + { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 20000 + ( crandom() * 5000 ); + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = ci->accModels[ACC_HAT]; + + re->fadeStartTime = le->endTime - 1000; + re->fadeEndTime = le->endTime; + + // (SA) FIXME: origin of hat md3 is offset from center. need to center the origin when you toss it + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + // spin it a bit + le->angles.trType = TR_LINEAR; + VectorCopy( tv( 0, 0, 0 ), le->angles.trBase ); + le->angles.trDelta[0] = 0; + le->angles.trDelta[1] = ( 100 + ( rand() & 500 ) ) - 300; +// le->angles.trDelta[2] = 0; + le->angles.trDelta[2] = 400; // (SA) this is set with a very particular value to try to get it + // to flip exactly once before landing (based on player alive + // (standing) and on level ground) and will be unnecessary when + // I have things landing properly on their own + + le->angles.trTime = cg.time; + + le->bounceFactor = 0.2; + + // Ridah, if the player is on fire, then make the hat on fire + if ( cent && CG_EntOnFire( cent ) ) { + le->onFireStart = cent->currentState.onFireStart; + le->onFireEnd = cent->currentState.onFireEnd + 4000; + } + } +} +/* +============== +CG_GibHead + + FIXME: accept the cent as parameter, and use it to grab the head position + from the model TAG's +============== +*/ +void CG_GibHead( vec3_t headOrigin ) { + vec3_t origin, velocity; + + VectorCopy( headOrigin, origin ); + velocity[0] = crandom() * GIB_VELOCITY; + velocity[1] = crandom() * GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY; + CG_LaunchGib( NULL, origin, vec3_origin, velocity, cgs.media.gibSkull, 1.0, 0 ); + + VectorCopy( headOrigin, origin ); + velocity[0] = crandom() * GIB_VELOCITY; + velocity[1] = crandom() * GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom() * GIB_VELOCITY; + CG_LaunchGib( NULL, origin, vec3_origin, velocity, cgs.media.gibBrain, 1.0, 0 ); + +} + +/* +====================== +CG_GetOriginForTag + + places the position of the tag into "org" + + returns the index of the tag it used, so we can cycle through tag's with the same name +====================== +*/ +int CG_GetOriginForTag( centity_t *cent, refEntity_t *parent, char *tagName, int startIndex, vec3_t org, vec3_t axis[3] ) { + int i; + orientation_t lerped; + int retval; + + // lerp the tag + retval = trap_R_LerpTag( &lerped, parent, tagName, startIndex ); + + if ( retval < 0 ) { + return retval; + } + + VectorCopy( parent->origin, org ); + + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( org, lerped.origin[i], parent->axis[i], org ); + } + + if ( axis ) { + // had to cast away the const to avoid compiler problems... + MatrixMultiply( lerped.axis, ( (refEntity_t *)parent )->axis, axis ); + } + + return retval; +} + +/* +=================== +CG_GibPlayer + +Generated a bunch of gibs launching out from the bodies location +=================== +*/ +#define MAXJUNCTIONS 8 + +void CG_GibPlayer( centity_t *cent, vec3_t playerOrigin, vec3_t gdir ) { + vec3_t origin, velocity, dir; + int i, count = 0, tagIndex, gibIndex; + trace_t trace; + qboolean foundtag; + + clientInfo_t *ci; + int clientNum; + + // Rafael + // BloodCloud + qboolean newjunction[MAXJUNCTIONS]; + vec3_t junctionOrigin[MAXJUNCTIONS]; + int junction; + int j; + // TTimo: unused + //float size; + vec3_t axis[3], angles; + + char *JunctiongibTags[] = { + // leg tag + "tag_footright", + "tag_footleft", + "tag_legright", + "tag_legleft", + + // torsotags + "tag_armright", + "tag_armleft", + + "tag_torso", + "tag_chest" + }; + + char *ConnectTags[] = { + // legs tags + "tag_legright", + "tag_legleft", + "tag_torso", + "tag_torso", + + // torso tags + "tag_chest", + "tag_chest", + + "tag_chest", + "tag_torso", + }; + + char *gibTags[] = { + // tags in the legs + "tag_footright", + "tag_footleft", + "tag_legright", + "tag_legleft", + "tag_torso", + + // tags in the torso + "tag_chest", + "tag_armright", + "tag_armleft", + "tag_head", + NULL + }; + + if ( cg_blood.integer ) { + // Rafael + for ( i = 0; i < MAXJUNCTIONS; i++ ) + newjunction[i] = qfalse; + + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity" ); + } + ci = &cgs.clientinfo[ clientNum ]; + + // Ridah, fetch the various positions of the tag_gib*'s + // and spawn the gibs from the correct places (especially the head) + for ( gibIndex = 0, count = 0, foundtag = qtrue; foundtag && gibIndex < MAX_GIB_MODELS && gibTags[gibIndex]; gibIndex++ ) { + + refEntity_t *re = 0; + + foundtag = qfalse; + + if ( !ci->gibModels[gibIndex] ) { + continue; + } + + re = ¢->pe.torsoRefEnt; + + for ( tagIndex = 0; ( tagIndex = CG_GetOriginForTag( cent, re, gibTags[gibIndex], tagIndex, origin, axis ) ) >= 0; count++, tagIndex++ ) { + + foundtag = qtrue; + + VectorSubtract( origin, re->origin, dir ); + VectorNormalize( dir ); + + // spawn a gib + velocity[0] = dir[0] * ( 0.5 + random() ) * GIB_VELOCITY * 0.3; + velocity[1] = dir[1] * ( 0.5 + random() ) * GIB_VELOCITY * 0.3; + velocity[2] = GIB_JUMP + dir[2] * ( 0.5 + random() ) * GIB_VELOCITY * 0.5; + + VectorMA( velocity, GIB_VELOCITY, gdir, velocity ); + AxisToAngles( axis, angles ); + + CG_LaunchGib( cent, origin, angles, velocity, ci->gibModels[gibIndex], 1.0, 0 ); + + for ( junction = 0; junction < MAXJUNCTIONS; junction++ ) + { + if ( !Q_stricmp( gibTags[gibIndex], JunctiongibTags[junction] ) ) { + VectorCopy( origin, junctionOrigin[junction] ); + newjunction[junction] = qtrue; + } + } + } + } + + for ( i = 0; i < MAXJUNCTIONS; i++ ) + { + if ( newjunction[i] == qtrue ) { + for ( j = 0; j < MAXJUNCTIONS; j++ ) + { + if ( !Q_stricmp( JunctiongibTags[j], ConnectTags[i] ) ) { + if ( newjunction[j] == qtrue ) { + // spawn a blood cloud somewhere on the vec from + VectorSubtract( junctionOrigin[i], junctionOrigin[j], dir ); + CG_ParticleBloodCloud( cent, junctionOrigin[i], dir ); + } + } + } + } + } + + // Ridah, spawn a bunch of blood dots around the place + #define GIB_BLOOD_DOTS 3 + for ( i = 0, count = 0; i < GIB_BLOOD_DOTS * 2; i++ ) { + // TTimo: unused + //static vec3_t mins = {-10,-10,-10}; + //static vec3_t maxs = { 10, 10, 10}; + + if ( i > 0 ) { + velocity[0] = ( ( i % 2 ) * 2 - 1 ) * ( 40 + 40 * random() ); + velocity[1] = ( ( ( i / 2 ) % 2 ) * 2 - 1 ) * ( 40 + 40 * random() ); + velocity[2] = ( ( ( i < GIB_BLOOD_DOTS ) * 2 ) - 1 ) * 40; + } else { + VectorClear( velocity ); + velocity[2] = -64; + } + + VectorAdd( playerOrigin, velocity, origin ); + + CG_Trace( &trace, playerOrigin, NULL, NULL, origin, -1, CONTENTS_SOLID ); + if ( trace.fraction < 1.0 ) { + BG_GetMarkDir( velocity, trace.plane.normal, velocity ); + CG_ImpactMark( cgs.media.bloodDotShaders[rand() % 5], trace.endpos, velocity, random() * 360, + 1,1,1,1, qtrue, 30, qfalse, cg_bloodTime.integer * 1000 ); + if ( count++ > GIB_BLOOD_DOTS ) { + break; + } + } + } + } + + if ( !( cent->currentState.eFlags & EF_HEADSHOT ) ) { // (SA) already lost hat while living + CG_LoseHat( cent, tv( 0, 0, 1 ) ); + } +} + + +/* +============== +CG_SparklerSparks +============== +*/ +void CG_SparklerSparks( vec3_t origin, int count ) { +// these effect the look of the, umm, effect + int FUSE_SPARK_LIFE = 100; + int FUSE_SPARK_LENGTH = 30; +// these are calculated from the above + int FUSE_SPARK_SPEED = ( FUSE_SPARK_LENGTH * 1000 / FUSE_SPARK_LIFE ); + + int i; + localEntity_t *le; + refEntity_t *re; + + for ( i = 0; i < count; i++ ) { + + // spawn the spark + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FUSE_SPARK; + le->startTime = cg.time; + le->endTime = cg.time + FUSE_SPARK_LIFE; + le->lastTrailTime = cg.time; + + VectorCopy( origin, re->origin ); + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorSet( le->pos.trDelta, crandom(), crandom(), crandom() ); + VectorNormalize( le->pos.trDelta ); + VectorScale( le->pos.trDelta, FUSE_SPARK_SPEED, le->pos.trDelta ); + le->pos.trTime = cg.time; + + } +} + +// just a bunch of numbers we can use for pseudo-randomizing based on time +#define NUMRANDTABLE 257 +unsigned short randtable[NUMRANDTABLE] = +{ + 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 +}; + +#define LT_MS 100 // random number will change every LT_MS millseconds +#define LT_RANDMAX ( (unsigned short)0xffff ) + +float lt_random( int thisrandseed, int t ) { + return (float)randtable[( thisrandseed + t + ( cg.time / LT_MS ) * ( cg.time / LT_MS ) ) % NUMRANDTABLE] / (float)LT_RANDMAX; +} + +float lt_crandom( int thisrandseed, int t ) { + return ( ( 2.0 * ( (float)randtable[( thisrandseed + t + ( cg.time / LT_MS ) * ( cg.time / LT_MS ) ) % NUMRANDTABLE] / (float)LT_RANDMAX ) ) - 1.0 ); +} + +/* +=============== +CG_DynamicLightningBolt +=============== +*/ +void CG_DynamicLightningBolt( qhandle_t shader, vec3_t start, vec3_t pend, int numBolts, float maxWidth, qboolean fade, float startAlpha, int recursion, int randseed ) { + int i,j; + float segMin, segMax, length; + float thisSeg, distLeft, thisWidth; + vec3_t pos, vec, end; + int curJunc; + vec3_t c, bc = {0.8,0.9,1}; + const float rndSize = 12.0; + const float maxSTscale = 30.0; + float stScale; + float alpha, viewDist; + const int trailLife = 1; + int forks = 0; + #define STYPE_LIGHTNING STYPE_REPEAT // ST mapping for trail + #define FORK_CHANCE 0.5 + #define VIEW_SCALE_DIST 128 + + VectorCopy( pend, end ); // need this so recursive calls don't override stacked endpoints + + // HACK, updated sprite, so downscale all widths + maxWidth *= 0.6; + + length = Distance( start, end ); + //if (length > 128) { + segMin = length / 10.0; + if ( segMin < 8 ) { + segMin = 8; + } + segMax = segMin * 1.2; + //segMax = length / 30.0; + //} else { + // segMin = length / 3.0; + // segMax = length / 1.5; + //} + + if ( startAlpha > 1.0 ) { + startAlpha = 1.0; + } + alpha = startAlpha; // change only if fading + + for ( i = 0; i < numBolts; i++ ) + { + distLeft = length; + VectorCopy( start, pos ); + // drop a start junction + stScale = maxSTscale * ( 0.5 + lt_random( randseed,i + 1 ) * 0.5 ); + if ( fade ) { + if ( startAlpha == 1.0 ) { + alpha = startAlpha * ( distLeft / length ); + } else { + alpha = 1.0 - 1.0 * fabs( ( 1.0 - ( distLeft / length ) ) - startAlpha ); + if ( alpha < 0 ) { + alpha = 0; + } + if ( alpha > 1 ) { + alpha = 1; + } + } + } + thisWidth = maxWidth * ( 0.5 + 0.5 * alpha ); + if ( ( viewDist = VectorDistance( pos, cg.refdef.vieworg ) ) < VIEW_SCALE_DIST ) { + thisWidth *= 0.5 + ( 0.5 * ( viewDist / VIEW_SCALE_DIST ) ); + if ( thisWidth < 4.0 && thisWidth < maxWidth ) { + thisWidth = 4.0; + } + } else { // scale it wider with distance so it remains visible + thisWidth *= 0.5 + ( 0.5 * ( viewDist / VIEW_SCALE_DIST ) ); + if ( thisWidth > maxWidth * 2 ) { + // thisWidth > maxWidth*2; + thisWidth = maxWidth * 2; + } + } + // + VectorScale( bc, alpha, c ); + c[2] *= 1.0 + ( 1.0 - alpha ); + if ( c[2] > 1.0 ) { + c[2] = 1.0; + } + // + curJunc = CG_AddTrailJunc( 0, shader, cg.time, STYPE_LIGHTNING, pos, trailLife, 1, 1, thisWidth, thisWidth, TJFL_NOCULL, c, c, stScale, 20.0 ); + while ( distLeft > 0 ) + { // create this bolt + thisSeg = segMin + ( segMax - segMin ) * lt_random( randseed,2 ); + thisWidth = maxWidth * ( 0.5 + 0.5 * alpha ); + if ( thisSeg >= distLeft - rndSize ) { // go directly to the end point + VectorCopy( end, pos ); + thisWidth *= 0.6; + } else if ( (float)distLeft / length < 0.3 ) { + VectorSubtract( end, pos, vec ); + VectorNormalize( vec ); + VectorMA( pos, thisSeg, vec, pos ); + // randomize the position a bit + thisSeg *= 0.05; + if ( thisSeg > rndSize ) { + thisSeg = rndSize; + } + for ( j = 0; j < 3; j++ ) { + viewDist = lt_crandom( randseed * randseed,j * j + i * i + 3 ); + if ( fabs( viewDist ) < 0.5 ) { + if ( viewDist > 0 ) { + viewDist = 0.5; + } else { viewDist = -0.5;} + } + pos[j] += viewDist * thisSeg; + } + } else { + VectorSubtract( end, pos, vec ); + VectorNormalize( vec ); + VectorMA( pos, thisSeg, vec, pos ); + // randomize the position a bit + thisSeg *= 0.25; + if ( thisSeg > rndSize ) { + thisSeg = rndSize; + } + for ( j = 0; j < 3; j++ ) { + viewDist = lt_crandom( randseed,j * j + i * i + 3 ); + if ( fabs( viewDist ) < 0.5 ) { + if ( viewDist > 0 ) { + viewDist = 0.5; + } else { viewDist = -0.5;} + } + pos[j] += viewDist * thisSeg; + } + } + + distLeft = Distance( pos, end ); + if ( fade ) { + if ( startAlpha == 1.0 ) { + alpha = startAlpha * ( distLeft / length ); + } else { + alpha = 1.0 - 1.0 * fabs( ( 1.0 - ( distLeft / length ) ) - startAlpha ); + if ( alpha < 0 ) { + alpha = 0; + } + if ( alpha > 1 ) { + alpha = 1; + } + } + } + //thisWidth *= alpha; + if ( ( viewDist = VectorDistance( pos, cg.refdef.vieworg ) ) < VIEW_SCALE_DIST ) { + thisWidth *= 0.5 + ( 0.5 * ( viewDist / VIEW_SCALE_DIST ) ); + if ( thisWidth < 4.0 && thisWidth < maxWidth ) { + thisWidth = 4.0; + } + } else { // scale it wider with distance so it remains visible + thisWidth *= 0.5 + ( 0.5 * ( viewDist / VIEW_SCALE_DIST ) ); + if ( thisWidth > maxWidth * 2 ) { + // thisWidth > maxWidth*2; + thisWidth = maxWidth * 2; + } + + } + // + VectorScale( bc, alpha, c ); + c[2] *= 1.0 + ( 1.0 - alpha ); + if ( c[2] > 1.0 ) { + c[2] = 1.0; + } + // + //stScale = maxSTscale * (0.4 + lt_random(randseed,1)*0.6); + curJunc = CG_AddTrailJunc( curJunc, shader, cg.time, STYPE_LIGHTNING, pos, trailLife, 1, 1, thisWidth, thisWidth, TJFL_NOCULL, c, c, stScale, 20.0 ); + + // fork from here? + if ( thisWidth < 4 && distLeft > 10 && recursion < 3 && forks < 3 && lt_random( randseed,383 + i + forks ) < FORK_CHANCE ) { + vec3_t fend; + forks++; + VectorSet( fend, + distLeft * 0.3 * lt_crandom( randseed,56 + i + forks ), + distLeft * 0.3 * lt_crandom( randseed,160 + i + forks ), + distLeft * 0.3 * lt_crandom( randseed,190 + i + forks ) ); + VectorAdd( fend, end, fend ); + VectorSubtract( fend, pos, fend ); + VectorMA( pos, 0.2 + 0.7 * lt_random( randseed,6 + i + forks ), fend, fend ); + + //if (recursion > 0 && recursion < 2) { + // CG_DynamicLightningBolt( cgs.media.lightningBoltShader, pos, fend, 1, maxWidth, qtrue, alpha, recursion, randseed ); + // return; // divert bolt rather than split + //} else { + CG_DynamicLightningBolt( shader, pos, fend, 1, maxWidth, qtrue, alpha, recursion + 1, randseed + 765 ); + //} + } + + randseed++; + + } + } +} + + + +/* +================ +CG_ProjectedSpotLight +================ +*/ +void CG_ProjectedSpotLight( vec3_t start, vec3_t dir ) { + vec3_t end, proj; + trace_t tr; + float alpha, radius; + + VectorMA( start, 1000, dir, end ); + CG_Trace( &tr, start, NULL, NULL, end, -1, CONTENTS_SOLID ); + if ( tr.fraction == 1.0 ) { + return; + } + // + alpha = ( 1.0 - tr.fraction ); + if ( alpha > 1.0 ) { + alpha = 1.0; + } + // + radius = 32 + 64 * tr.fraction; + VectorNegate( dir, proj ); + CG_ImpactMark( cgs.media.spotLightShader, tr.endpos, proj, 0, alpha, alpha, alpha, 1.0, qfalse, radius, qtrue, -2 ); +} + + +#define MAX_SPOT_SEGS 20 +#define MAX_SPOT_RANGE 2000 +/* +============== +CG_Spotlight + segs: number of sides on tube. - 999 is a valid value and just means, 'cap to max' (MAX_SPOT_SEGS) or use lod scheme + range: effective range of beam + startWidth: will be optimized for '0' as a value (true cone) to not use quads and not cap the start circle + + -- flags -- + SL_NOTRACE - don't do a trace check for shortening the beam, always draw at full 'range' length + SL_TRACEWORLDONLY - go through everything but the world + SL_NODLIGHT - don't put a dlight at the end + SL_NOSTARTCAP - dont' cap the start circle + SL_LOCKTRACETORANGE - only trace out as far as the specified range (rather than to max spot range) + SL_NOFLARE - don't draw a flare when the light is pointing at the camera + SL_NOIMPACT - don't draw the impact mark on hit surfaces + SL_LOCKUV - lock the texture coordinates at the 'true' length of the requested beam. + SL_NOCORE - don't draw the center 'core' beam + + + + + + + I know, this is a bit kooky right now. It evolved big, but now that I know what it should do, it'll get + crunched down to a bunch of table driven stuff. once it works, I'll make it work well... + +============== +*/ + +void CG_Spotlight( centity_t *cent, float *color, vec3_t realstart, vec3_t lightDir, int segs, float range, int startWidth, float coneAngle, int flags ) { + int i, j; + vec3_t start, traceEnd, proj; + vec3_t right, up; + vec3_t v1, v2; + vec3_t startvec, endvec; // the vectors to rotate around lightDir to create the circles + vec3_t conevec; + vec3_t start_points[MAX_SPOT_SEGS], end_points[MAX_SPOT_SEGS]; + vec3_t coreright; + polyVert_t verts[MAX_SPOT_SEGS * 4]; // x4 for 4 verts per poly + polyVert_t plugVerts[MAX_SPOT_SEGS]; + vec3_t endCenter; + polyVert_t coreverts[4]; + trace_t tr; + float alpha; + float radius = 0.0; // TTimo might be used uninitialized + float coreEndRadius; + qboolean capStart = qtrue; + float hitDist; // the actual distance of the trace impact (0 is no hit) + float beamLen; // actual distance of the drawn beam + float endAlpha = 0.0; + vec4_t colorNorm; // normalized color vector + refEntity_t ent; + vec3_t angles; + + VectorCopy( realstart, start ); + + // normalize color + colorNorm[3] = 0; // store normalize multiplier in alpha index + for ( i = 0; i < 3; i++ ) { + if ( color[i] > colorNorm[3] ) { + colorNorm[3] = color[i]; // find largest color value in RGB + } + } + + if ( colorNorm[3] != 1 ) { // it needs to be boosted + VectorMA( color, 1.0 / colorNorm[3], color, colorNorm ); // FIXME: div by 0 + } else { + VectorCopy( color, colorNorm ); + } + colorNorm[3] = color[3]; + + + if ( flags & SL_NOSTARTCAP ) { + capStart = qfalse; + } + + if ( startWidth == 0 ) { // cone, not cylinder + capStart = qfalse; + } + + if ( flags & SL_LOCKTRACETORANGE ) { + VectorMA( start, range, lightDir, traceEnd ); // trace out to 'range' + } else { + VectorMA( start, MAX_SPOT_RANGE, lightDir, traceEnd ); // trace all the way out to max dist + } + + // first trace to see if anything is hit + if ( flags & SL_NOTRACE ) { + tr.fraction = 1.0; // force no hit + } else { + if ( flags & SL_TRACEWORLDONLY ) { + CG_Trace( &tr, start, NULL, NULL, traceEnd, -1, CONTENTS_SOLID ); + } else { + CG_Trace( &tr, start, NULL, NULL, traceEnd, -1, MASK_SHOT ); + } +// CG_Trace( &tr, start, NULL, NULL, traceEnd, -1, MASK_ALL &~(CONTENTS_MONSTERCLIP|CONTENTS_AREAPORTAL|CONTENTS_CLUSTERPORTAL)); + } + + + if ( tr.fraction < 1.0 ) { + hitDist = beamLen = MAX_SPOT_RANGE * tr.fraction; + if ( beamLen > range ) { + beamLen = range; + } + } else { + hitDist = 0; + beamLen = range; + } + + + if ( flags & SL_LOCKUV ) { + if ( beamLen < range ) { + endAlpha = 255.0f * ( color[3] - ( color[3] * beamLen / range ) ); + } + } + + + if ( segs >= MAX_SPOT_SEGS ) { + segs = MAX_SPOT_SEGS - 1; + } + + // TODO: adjust segs based on r_lodbias + // TODO: move much of this to renderer + + +// model at base + if ( cent->currentState.modelindex ) { + memset( &ent, 0, sizeof( ent ) ); + ent.frame = 0; + ent.oldframe = 0; + ent.backlerp = 0; + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + ent.hModel = cgs.gameModels[cent->currentState.modelindex]; + // AnglesToAxis( cent->lerpAngles, ent.axis ); + vectoangles( lightDir, angles ); + AnglesToAxis( angles, ent.axis ); + trap_R_AddRefEntityToScene( &ent ); + memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); + + // push start out a bit so the beam fits to the front of the base model + VectorMA( start, 14, lightDir, start ); + } + +//// BEAM + + PerpendicularVector( up, lightDir ); + CrossProduct( lightDir, up, right ); + + // find first vert of the start + VectorScale( right, startWidth, startvec ); + // find the first vert of the end + RotatePointAroundVector( conevec, up, lightDir, -coneAngle ); + VectorMA( startvec, beamLen, conevec, endvec ); // this applies the offset of the start diameter before finding the end points + + VectorScale( lightDir, beamLen, endCenter ); + VectorSubtract( endCenter, endvec, coreverts[3].xyz ); // get a vector of the radius out at the end for the core to use + coreEndRadius = VectorLength( coreverts[3].xyz ); +#define CORESCALE 0.6f + +// +// generate the flat beam 'core' +// + if ( !( flags & SL_NOCORE ) ) { + VectorSubtract( start, cg.refdef.vieworg, v1 ); + VectorNormalize( v1 ); + VectorSubtract( traceEnd, cg.refdef.vieworg, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, coreright ); + VectorNormalize( coreright ); + + memset( &coreverts[0], 0, 4 * sizeof( polyVert_t ) ); + VectorMA( start, startWidth * 0.5f, coreright, coreverts[0].xyz ); + VectorMA( start, -startWidth * 0.5f, coreright, coreverts[1].xyz ); + VectorMA( endCenter, -coreEndRadius * CORESCALE, coreright, coreverts[2].xyz ); + VectorAdd( start, coreverts[2].xyz, coreverts[2].xyz ); + VectorMA( endCenter, coreEndRadius * CORESCALE, coreright, coreverts[3].xyz ); + VectorAdd( start, coreverts[3].xyz, coreverts[3].xyz ); + + for ( i = 0; i < 4; i++ ) { + coreverts[i].modulate[0] = color[0] * 200.0f; + coreverts[i].modulate[1] = color[1] * 200.0f; + coreverts[i].modulate[2] = color[2] * 200.0f; + coreverts[i].modulate[3] = color[3] * 200.0f; + if ( i > 1 ) { + coreverts[i].modulate[3] = 0; + } + } + + trap_R_AddPolyToScene( cgs.media.spotLightBeamShader, 4, &coreverts[0] ); + } + + +// +// generate the beam cylinder +// + + + + for ( i = 0; i <= segs; i++ ) { + RotatePointAroundVector( start_points[i], lightDir, startvec, ( 360.0f / (float)segs ) * i ); + VectorAdd( start_points[i], start, start_points[i] ); + + RotatePointAroundVector( end_points[i], lightDir, endvec, ( 360.0f / (float)segs ) * i ); + VectorAdd( end_points[i], start, end_points[i] ); + } + + for ( i = 0; i < segs; i++ ) { + + j = ( i * 4 ); + + VectorCopy( start_points[i], verts[( i * 4 )].xyz ); + verts[j].st[0] = 0; + verts[j].st[1] = 1; + verts[j].modulate[0] = color[0] * 255.0f; + verts[j].modulate[1] = color[1] * 255.0f; + verts[j].modulate[2] = color[2] * 255.0f; + verts[j].modulate[3] = color[3] * 255.0f; + j++; + + VectorCopy( end_points[i], verts[j].xyz ); + verts[j].st[0] = 0; + verts[j].st[1] = 0; + verts[j].modulate[0] = color[0] * 255.0f; + verts[j].modulate[1] = color[1] * 255.0f; + verts[j].modulate[2] = color[2] * 255.0f; + verts[j].modulate[3] = endAlpha; + j++; + + VectorCopy( end_points[i + 1], verts[j].xyz ); + verts[j].st[0] = 1; + verts[j].st[1] = 0; + verts[j].modulate[0] = color[0] * 255.0f; + verts[j].modulate[1] = color[1] * 255.0f; + verts[j].modulate[2] = color[2] * 255.0f; + verts[j].modulate[3] = endAlpha; + j++; + + VectorCopy( start_points[i + 1], verts[j].xyz ); + verts[j].st[0] = 1; + verts[j].st[1] = 1; + verts[j].modulate[0] = color[0] * 255.0f; + verts[j].modulate[1] = color[1] * 255.0f; + verts[j].modulate[2] = color[2] * 255.0f; + verts[j].modulate[3] = color[3] * 255.0f; + + if ( capStart ) { + VectorCopy( start_points[i], plugVerts[i].xyz ); + plugVerts[i].st[0] = 0; + plugVerts[i].st[1] = 0; + plugVerts[i].modulate[0] = color[0] * 255.0f; + plugVerts[i].modulate[1] = color[1] * 255.0f; + plugVerts[i].modulate[2] = color[2] * 255.0f; + plugVerts[i].modulate[3] = color[3] * 255.0f; + } + } + + trap_R_AddPolysToScene( cgs.media.spotLightBeamShader, 4, &verts[0], segs ); + + + // plug up the start circle + if ( capStart ) { + trap_R_AddPolyToScene( cgs.media.spotLightBeamShader, segs, &plugVerts[0] ); + } + + + // show the endpoint + + if ( !( flags & SL_NOIMPACT ) ) { + if ( hitDist ) { + VectorMA( startvec, hitDist, conevec, endvec ); + + alpha = 0.3f; + radius = coreEndRadius * ( hitDist / beamLen ); + + VectorNegate( lightDir, proj ); + CG_ImpactMark( cgs.media.spotLightShader, tr.endpos, proj, 0, colorNorm[0], colorNorm[1], colorNorm[2], alpha, qfalse, radius, qtrue, -1 ); + } + } + + + + // add d light at end + if ( !( flags & SL_NODLIGHT ) ) { + vec3_t dlightLoc; +// VectorMA(tr.endpos, -60, lightDir, dlightLoc); // back away from the hit +// trap_R_AddLightToScene(dlightLoc, 200, colorNorm[0], colorNorm[1], colorNorm[2], 0); // ,REF_JUNIOR_DLIGHT); + VectorMA( tr.endpos, 0, lightDir, dlightLoc ); // back away from the hit +// trap_R_AddLightToScene(dlightLoc, radius*2, colorNorm[0], colorNorm[1], colorNorm[2], 0); // ,REF_JUNIOR_DLIGHT); + trap_R_AddLightToScene( dlightLoc, radius * 2, 0.3, 0.3, 0.3, 0 ); // ,REF_JUNIOR_DLIGHT); + } + + + + // draw flare at source + if ( !( flags & SL_NOFLARE ) ) { + qboolean lightInEyes = qfalse; + vec3_t camloc, dirtolight; + float dot, deg, dist; + float flarescale = 0.0; // TTimo: might be used uninitialized + + // get camera position and direction to lightsource + VectorCopy( cg.snap->ps.origin, camloc ); + camloc[2] += cg.snap->ps.viewheight; + VectorSubtract( start, camloc, dirtolight ); + dist = VectorNormalize( dirtolight ); + + // first use dot to determine if it's facing the camera + dot = DotProduct( lightDir, dirtolight ); + + // it's facing the camera, find out how closely and trace to see if the source can be seen + + deg = RAD2DEG( M_PI - acos( dot ) ); + if ( deg <= 35 ) { // start flare a bit before the camera gets inside the cylinder + lightInEyes = qtrue; + flarescale = 1 - ( deg / 35 ); + } + + if ( lightInEyes ) { // the dot check succeeded, now do a trace + CG_Trace( &tr, start, NULL, NULL, camloc, -1, MASK_ALL & ~( CONTENTS_MONSTERCLIP | CONTENTS_AREAPORTAL | CONTENTS_CLUSTERPORTAL ) ); + if ( tr.fraction != 1 ) { + lightInEyes = qfalse; + } + + } + + if ( lightInEyes ) { + float coronasize = flarescale; + if ( dist < 512 ) { // make even bigger if you're close enough + coronasize *= ( 512.0f / dist ); + } + + trap_R_AddCoronaToScene( start, colorNorm[0], colorNorm[1], colorNorm[2], coronasize, cent->currentState.number, qtrue ); + } else { + // even though it's off, still need to add it, but turned off so it can fade in/out properly + trap_R_AddCoronaToScene( start, colorNorm[0], colorNorm[1], colorNorm[2], 0, cent->currentState.number, qfalse ); + } + } + +} + + + +/* +============== +CG_RumbleEfx +============== +*/ +void CG_RumbleEfx( float pitch, float yaw ) { + float pitchRecoilAdd, pitchAdd; + float yawRandom; + vec3_t recoil; + + // + pitchRecoilAdd = 0; + pitchAdd = 0; + yawRandom = 0; + // + + if ( pitch < 1 ) { + pitch = 1; + } + + pitchRecoilAdd = pow( random(),8 ) * ( 10 + VectorLength( cg.snap->ps.velocity ) / 5 ); + pitchAdd = ( rand() % (int)pitch ) - ( pitch * 0.5 ); //5 + yawRandom = yaw; //2 + + pitchRecoilAdd *= 0.5; + pitchAdd *= 0.5; + yawRandom *= 0.5; + + // calc the recoil + recoil[YAW] = crandom() * yawRandom; + recoil[ROLL] = -recoil[YAW]; // why not + recoil[PITCH] = -pitchAdd; + // scale it up a bit (easier to modify this while tweaking) + VectorScale( recoil, 30, recoil ); + // set the recoil + VectorCopy( recoil, cg.kickAVel ); + // set the recoil + cg.recoilPitch -= pitchRecoilAdd; +} + + + diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c new file mode 100644 index 0000000..5b9039d --- /dev/null +++ b/src/cgame/cg_ents.c @@ -0,0 +1,2179 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: cg_ents.c + * + * desc: present snapshot entities, happens every single frame + * +*/ + + +#include "cg_local.h" + +/////////////////////// +extern int propellerModel; +/////////////////////// + +/* +====================== +CG_PositionEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + char *tagName, int startIndex, vec3_t *offset ) { + int i; + orientation_t lerped; + + // lerp the tag + trap_R_LerpTag( &lerped, parent, tagName, startIndex ); + + // FIXME: allow origin offsets along tag? //----(SA) Yes! Adding. + + VectorCopy( parent->origin, entity->origin ); + + if ( offset ) { + VectorAdd( lerped.origin, *offset, lerped.origin ); + } +//----(SA) end + + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( lerped.axis, ( (refEntity_t *)parent )->axis, entity->axis ); + // Ridah, not sure why this was here.. causes jittery torso animation, since the torso might have + // different frame/oldFrame + //entity->backlerp = parent->backlerp; +} + + +/* +====================== +CG_PositionRotatedEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + char *tagName ) { + int i; + orientation_t lerped; + vec3_t tempAxis[3]; + +//AxisClear( entity->axis ); + // lerp the tag + trap_R_LerpTag( &lerped, parent, tagName, 0 ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( entity->axis, lerped.axis, tempAxis ); + MatrixMultiply( tempAxis, ( (refEntity_t *)parent )->axis, entity->axis ); +} + + +//----(SA) added +/* +============== +CG_LoseArmor + maybe better in cg_localents.c +============== +*/ +void CG_LoseArmor( centity_t *cent, int index ) { +} + +/* +============== +CG_AttachedPartChange +============== +*/ +void CG_AttachedPartChange( centity_t *cent ) { +} + +//----(SA) end + +/* +========================================================================== + +FUNCTIONS CALLED EACH FRAME + +========================================================================== +*/ + +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +void CG_SetEntitySoundPosition( centity_t *cent ) { + if ( cent->currentState.solid == SOLID_BMODEL ) { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_UpdateEntityPosition( cent->currentState.number, origin ); + } else { + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + } +} + + + + +#define LS_FRAMETIME 100 // (ms) cycle through lightstyle characters at 10fps + + +/* +============== +CG_SetDlightIntensity + +============== +*/ +void CG_AddLightstyle( centity_t *cent ) { + float lightval; + int cl; + int r, g, b; + int stringlength; + float offset; + int offsetwhole; + int otime; + int lastch, nextch; + + if ( !cent->dl_stylestring ) { + return; + } + + otime = cg.time - cent->dl_time; + stringlength = strlen( cent->dl_stylestring ); + + // it's been a long time since you were updated, lets assume a reset + if ( otime > 2 * LS_FRAMETIME ) { + otime = 0; + cent->dl_frame = cent->dl_oldframe = 0; + cent->dl_backlerp = 0; + } + + cent->dl_time = cg.time; + + offset = ( (float)otime ) / LS_FRAMETIME; + offsetwhole = (int)offset; + + cent->dl_backlerp += offset; + + + if ( cent->dl_backlerp > 1 ) { // we're moving on to the next frame + cent->dl_oldframe = cent->dl_oldframe + (int)cent->dl_backlerp; + cent->dl_frame = cent->dl_oldframe + 1; + if ( cent->dl_oldframe >= stringlength ) { + cent->dl_oldframe = ( cent->dl_oldframe ) % stringlength; + if ( cent->dl_oldframe < 3 && cent->dl_sound ) { // < 3 so if an alarm comes back into the pvs it will only start a sound if it's going to be closely synced with the light, otherwise wait till the next cycle + trap_S_StartSound( NULL, cent->currentState.number, CHAN_AUTO, cgs.gameSounds[cent->dl_sound] ); + } + } + + if ( cent->dl_frame >= stringlength ) { + cent->dl_frame = ( cent->dl_frame ) % stringlength; + } + + cent->dl_backlerp = cent->dl_backlerp - (int)cent->dl_backlerp; + } + + + lastch = cent->dl_stylestring[cent->dl_oldframe] - 'a'; + nextch = cent->dl_stylestring[cent->dl_frame] - 'a'; + + lightval = ( lastch * ( 1.0 - cent->dl_backlerp ) ) + ( nextch * cent->dl_backlerp ); + + lightval = ( lightval * ( 1000.0f / 24.0f ) ) - 200.0f; // they want 'm' as the "middle" value as 300 + + lightval = max( 0.0f, lightval ); + lightval = min( 1000.0f, lightval ); + + cl = cent->currentState.constantLight; + r = cl & 255; + g = ( cl >> 8 ) & 255; + b = ( cl >> 16 ) & 255; + + trap_R_AddLightToScene( cent->lerpOrigin, lightval, (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, 0 ); // overdraw forced to 0 for now +} + + +void CG_GetWindVector( vec3_t dir ); // JPW NERVE + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) { + static vec3_t dir; + // update sound origins + CG_SetEntitySoundPosition( cent ); + + // add loop sound + if ( cent->currentState.loopSound ) { + //----(SA) hmm, the above (CG_SetEntitySoundPosition()) sets s_entityPosition[entityNum] with a valid + // location, but the looping sound for a bmodel will never get it since that sound is + // started with the lerpOriging right here. \/ \/ How do looping sounds ever work for bmodels? + // Or have they always been broken and we just never used them? + + if ( cent->currentState.eType == ET_SPEAKER ) { + /*if(cent->currentState.density == 1) { // NO_PVS + trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ] ); + } + else*/if ( cent->currentState.dmgFlags ) { // range is set + trap_S_AddRangedLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], cent->currentState.dmgFlags ); + } else { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], 255 ); + } + } else if ( cent->currentState.solid == SOLID_BMODEL ) { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_AddLoopingSound( cent->currentState.number, origin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], 255 ); + } else { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ], 255 ); + } + } /*else { + // stop NO_PVS speakers if they've been turned off + if(cent->currentState.eType == ET_SPEAKER) { + if(cent->currentState.density == 1) { + trap_S_StopLoopingSound(cent->currentState.number); + } + } + }*/ + + + // constant light glow + if ( cent->currentState.constantLight ) { + int cl; + int i, r, g, b; + + + if ( cent->dl_stylestring[0] != 0 ) { // it's probably a dlight + CG_AddLightstyle( cent ); + } else + { + cl = cent->currentState.constantLight; + r = cl & 255; + g = ( cl >> 8 ) & 255; + b = ( cl >> 16 ) & 255; + i = ( ( cl >> 24 ) & 255 ) * 4; + + trap_R_AddLightToScene( cent->lerpOrigin, i, (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, 0 ); + } + } + + // Ridah, flaming sounds + if ( CG_EntOnFire( cent ) ) { + // play a flame blow sound when moving + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flameBlowSound, (int)( 255.0 * ( 1.0 - fabs( cent->fireRiseDir[2] ) ) ) ); + // play a burning sound when not moving + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flameSound, (int)( 0.3 * 255.0 * ( pow( cent->fireRiseDir[2],2 ) ) ) ); + } + + + // DHM - Nerve :: If EF_SMOKING is set, emit smoke + if ( cgs.gametype >= GT_WOLF && cent->currentState.eFlags & EF_SMOKING ) { + float rnd = random(); + + if ( cent->lastTrailTime < cg.time ) { + cent->lastTrailTime = cg.time + 100; + +// JPW NERVE -- use wind vector for smoke + CG_GetWindVector( dir ); + VectorScale( dir,20,dir ); // was 75, before that 55 + if ( dir[2] < 10 ) { + dir[2] += 10; + } +// dir[0] = crandom() * 10; +// dir[1] = crandom() * 10; +// dir[2] = 10 + rnd * 30; +// jpw + CG_SmokePuff( cent->lerpOrigin, dir, 15 + ( random() * 10 ), + 0.3 + rnd, 0.3 + rnd, 0.3 + rnd, 0.4, 1500 + ( rand() % 500 ), + cg.time, cg.time + 500, 0, cgs.media.smokePuffShader ); + } + } + // dhm - end +// JPW NERVE same thing but for smoking barrels instead of nasty server-side effect from single player + if ( cgs.gametype >= GT_WOLF && cent->currentState.eFlags & EF_SMOKINGBLACK ) { + float rnd = random(); + + if ( cent->lastTrailTime < cg.time ) { + cent->lastTrailTime = cg.time + 75; + + CG_GetWindVector( dir ); + VectorScale( dir,50,dir ); // was 75, before that 55 + if ( dir[2] < 50 ) { + dir[2] += 50; + } + + CG_SmokePuff( cent->lerpOrigin, dir, 40 + random() * 70, //40+(rnd*40), + rnd * 0.1, rnd * 0.1, rnd * 0.1, 1, 2800 + ( rand() % 4000 ), //2500+(random()*1500), + cg.time, 0, 0, cgs.media.smokePuffShader ); + } + } +// jpw + + + +} + + +/* +================== +CG_General +================== +*/ +static void CG_General( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // if set to invisible, skip + if ( !s1->modelindex ) { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + // set frame + + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + + if ( ent.frame ) { + + ent.oldframe -= 1; + ent.backlerp = 1 - cg.frameInterpolation; + + if ( cent->currentState.time ) { + ent.fadeStartTime = cent->currentState.time; + ent.fadeEndTime = cent->currentState.time2; + } + + } + + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + + ent.hModel = cgs.gameModels[s1->modelindex]; + + // player model + if ( s1->number == cg.snap->ps.clientNum ) { + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + } + + if ( cent->currentState.eType == ET_MG42_BARREL ) { + // grab angles from first person user or self if not + // ATVI Wolfenstein Misc #469 - don't track until viewlocked + if ( cent->currentState.otherEntityNum == cg.snap->ps.clientNum && cg.snap->ps.viewlocked ) { + AnglesToAxis( cg.predictedPlayerState.viewangles, ent.axis ); + } else { + AnglesToAxis( cent->lerpAngles, ent.axis ); + } + } else { + // convert angles to axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + } + + // scale gamemodels + if ( cent->currentState.eType == ET_GAMEMODEL ) { + VectorScale( ent.axis[0], cent->currentState.angles2[0], ent.axis[0] ); + VectorScale( ent.axis[1], cent->currentState.angles2[1], ent.axis[1] ); + VectorScale( ent.axis[2], cent->currentState.angles2[2], ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + +//----(SA) testing + if ( cent->currentState.apos.trType ) { + ent.reFlags |= REFLAG_ORIENT_LOD; + } +//----(SA) end + } + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); + + memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) { + if ( !cent->currentState.clientNum ) { // FIXME: use something other than clientNum... + return; // not auto triggering + } + + if ( cg.time < cent->miscTime ) { + return; + } + + trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); + + // ent->s.frame = ent->wait * 10; + // ent->s.clientNum = ent->random * 10; + cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); +} + + + +/* +============== +CG_DrawHoldableSelect + + This, of course, will all change when we've got a hud, but for now it makes the holdable items usable and not bad looking +============== +*/ +void CG_DrawHoldableSelect( void ) { +/* + int bits; + int count; + int amount; + int i, x, y, w; + float *color; + char *name; + gitem_t *item; + + // don't display if dead + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + color = CG_FadeColor( cg.holdableSelectTime, HOLDABLE_SELECT_TIME ); + if ( !color ) { + return; + } + trap_R_SetColor( color ); + + // showing select clears pickup item display, but not the blend blob + cg.itemPickupTime = 0; + + //----(SA) removed + + // count the number of weapons owned + bits = cg.snap->ps.stats[ STAT_HOLDABLE_ITEM ]; + count = 0; + + for ( i = 1 ; i <= HI_BOOK3; i++ ) { + if ( bits & ( 1 << i ) ) { + if(cg.predictedPlayerState.holdable[i]) // don't show ones we're out of + count++; + } + } + + x = 320 - count * 20; + y = 380; + + + for ( i = 1 ; i <= HI_BOOK3 ; i++ ) { + if ( !( bits & ( 1 << i ) ) ) { + continue; + } + + amount = cg.predictedPlayerState.holdable[i]; + + if(!amount) + continue; + + item = BG_FindItemForHoldable(i); + if(!item) + continue; + + CG_RegisterItemVisuals(item - bg_itemlist); + + // draw icon + if(i == HI_WINE) { + // wine icons have three stages since each bottle has three uses (as opposed to others so far where there's only 1 use) + int wine = amount; + if(wine > 3) wine = 3; + CG_DrawPic( x, y, 32, 32, cg_items[item - bg_itemlist].icons[ 2 - (wine - 1) ] ) ; + } + else { + CG_DrawPic( x, y, 32, 32, cg_items[item - bg_itemlist].icons[0]); + } + + // draw remaining uses if there's more than one + if(amount > 1) + CG_DrawBigStringColor(x, y + 34, va("%d", amount), color); + + // draw selection marker + if ( i == cg.holdableSelect) { + CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); + } + + x += 40; + } + + // draw the selected name + if(cg.holdableSelect) { + item = BG_FindItemForHoldable(cg.holdableSelect); + if(item) { + name = item->pickup_name; + if ( name ) { + //----(SA) trying smaller text +// w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; + w = CG_DrawStrlen( name ) * 10; + x = ( SCREEN_WIDTH - w ) / 2; +// CG_DrawBigStringColor(x, y - 22, name, color); + CG_DrawStringExt2( x, y + 60, name, color, qfalse, qtrue, 10, 10, 0 ); + } + } + } + + trap_R_SetColor( NULL ); +*/ +} + + +/* +============== +CG_NextItem_f +============== +*/ + +void CG_NextItem_f( void ) { +/* + int i; + int original, next; + + if ( !cg.snap ) { + return; + } + + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.holdableSelectTime = cg.time; + cg.weaponSelectTime = 0; // (SA) clear weapon selection drawing + + next = original = cg.holdableSelect; + + for ( i = 0 ; i < HI_NUM_HOLDABLE ; i++ ) { + next++; + + if(next == HI_NUM_HOLDABLE) + next = 0; + + if(cg.predictedPlayerState.holdable[next]) { //----(SA) + break; + } + } + + if ( i == HI_NUM_HOLDABLE ) { + next = original; + } + + cg.holdableSelect = next; +*/ +} + +/* +============== +CG_PrevItem_f +============== +*/ +void CG_PrevItem_f( void ) { +/* + int i; + int original, next; + + if ( !cg.snap ) { + return; + } + + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = 0; // (SA) clear weapon selection drawing + cg.holdableSelectTime = cg.time; + + next = original = cg.holdableSelect; + + for ( i = 0 ; i < HI_NUM_HOLDABLE ; i++ ) { + next--; + + if(next == -1) + next = HI_NUM_HOLDABLE - 1; + + if(cg.predictedPlayerState.holdable[next]) { //----(SA) + break; + } + } + + if ( i == HI_NUM_HOLDABLE ) { + next = original; + } + + cg.holdableSelect = next; +*/ +} + +/* +============== +CG_Item_f +============== +*/ +void CG_Item_f( void ) { + //int num; + //num = atoi( CG_Argv( 1 ) ); + + //cg.holdableSelectTime = cg.time; + + //CG_Printf ("Item set to: d\n", num); +} + + + +//----(SA) added +/* +============== +CG_HoldableUsedupChange +============== +*/ +void CG_HoldableUsedupChange( void ) { + int holding; + + holding = cg.holdableSelect; + + CG_NextItem_f(); + + if ( cg.holdableSelect == holding ) { // nothing else to go to + cg.holdableSelect = 0; + cg.weaponSelectTime = 0; + return; + } +} +//----(SA) end + + + +qboolean CG_PlayerSeesItem( playerState_t *ps, entityState_t *item, int atTime, int itemType ) { + vec3_t vorigin, eorigin, viewa, dir; + float dot, dist, foo; + trace_t tr; + + BG_EvaluateTrajectory( &item->pos, atTime, eorigin ); + + VectorCopy( ps->origin, vorigin ); + vorigin[2] += ps->viewheight; // get the view loc up to the viewheight +// eorigin[2] += 8; // and subtract the item's offset (that is used to place it on the ground) + + + VectorSubtract( vorigin, eorigin, dir ); + + dist = VectorNormalize( dir ); // dir is now the direction from the item to the player + + if ( dist > 255 ) { + return qfalse; // only run the remaining stuff on items that are close enough + + } + // (SA) FIXME: do this without AngleVectors. + // It'd be nice if the angle vectors for the player + // have already been figured at this point and I can + // just pick them up. (if anybody is storing this somewhere, + // for the current frame please let me know so I don't + // have to do redundant calcs) + AngleVectors( ps->viewangles, viewa, 0, 0 ); + dot = DotProduct( viewa, dir ); + + // give more range based on distance (the hit area is wider when closer) + +// foo = -0.94f - (dist/255.0f) * 0.057f; // (ranging from -0.94 to -0.997) (it happened to be a pretty good range) + foo = -0.94f - ( dist * ( 1.0f / 255.0f ) ) * 0.057f; // (ranging from -0.94 to -0.997) (it happened to be a pretty good range) + +/// Com_Printf("test: if(%f > %f) return qfalse (dot > foo)\n", dot, foo); + if ( dot > foo ) { + return qfalse; + } + + // (SA) okay, everything else is okay, so do a bloody trace. (so coronas on treasure doesn't show through walls) + if ( itemType == IT_TREASURE ) { + CG_Trace( &tr, vorigin, NULL, NULL, eorigin, -1, MASK_SOLID ); + + if ( tr.fraction != 1 ) { + return qfalse; + } + } + + return qtrue; + +} + + +/* +================== +CG_Item +================== +*/ +static void CG_Item( centity_t *cent ) { + refEntity_t ent; + entityState_t *es; + gitem_t *item; + float scale; + qboolean hasStand, highlight; + float highlightFadeScale = 1.0f; + + es = ¢->currentState; + + hasStand = qfalse; + highlight = qfalse; + + // (item index is stored in es->modelindex for item) + + if ( es->modelindex >= bg_numItems ) { + CG_Error( "Bad item index %i on entity", es->modelindex ); + } + + // if set to invisible, skip + if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { + return; + } + + item = &bg_itemlist[ es->modelindex ]; + + if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.radius = 14; + ent.customShader = cg_items[es->modelindex].icons[0]; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); + return; + } + + scale = 0.005 + cent->currentState.number * 0.00001; + + memset( &ent, 0, sizeof( ent ) ); + + ent.nonNormalizedAxes = qfalse; + + if ( item->giType == IT_WEAPON ) { + weaponInfo_t *weaponInfo = &cg_weapons[item->giTag]; + + if ( weaponInfo->standModel ) { + hasStand = qtrue; + } + + if ( hasStand ) { // first try to put the weapon on it's 'stand' + refEntity_t stand; + + memset( &stand, 0, sizeof( stand ) ); + stand.hModel = weaponInfo->standModel; + + if ( es->eFlags & EF_SPINNING ) { + if ( es->groundEntityNum == -1 || !es->groundEntityNum ) { // (SA) spinning with a stand will spin the stand and the attached weap (only when in the air) + VectorCopy( cg.autoAnglesSlow, cent->lerpAngles ); + VectorCopy( cg.autoAnglesSlow, cent->lastLerpAngles ); + } else { + VectorCopy( cent->lastLerpAngles, cent->lerpAngles ); // make a tossed weapon sit on the ground in a position that matches how it was yawed + } + } + + AnglesToAxis( cent->lerpAngles, stand.axis ); + VectorCopy( cent->lerpOrigin, stand.origin ); + + // scale the stand to match the weapon scale ( the weapon will also be scaled inside CG_PositionEntityOnTag() ) + VectorScale( stand.axis[0], 1.5, stand.axis[0] ); + VectorScale( stand.axis[1], 1.5, stand.axis[1] ); + VectorScale( stand.axis[2], 1.5, stand.axis[2] ); + +//----(SA) modified + if ( cent->currentState.frame ) { + CG_PositionEntityOnTag( &ent, &stand, va( "tag_stand%d", cent->currentState.frame ), 0, NULL ); + } else { + CG_PositionEntityOnTag( &ent, &stand, "tag_stand", 0, NULL ); + } +//----(SA) end + + VectorCopy( ent.origin, ent.oldorigin ); + ent.nonNormalizedAxes = qtrue; + + } else { // then default to laying it on it's side + if ( !cg_items[es->modelindex].models[2] ) { + cent->lerpAngles[2] += 90; + } + + AnglesToAxis( cent->lerpAngles, ent.axis ); + + // increase the size of the weapons when they are presented as items + VectorScale( ent.axis[0], 1.5, ent.axis[0] ); + VectorScale( ent.axis[1], 1.5, ent.axis[1] ); + VectorScale( ent.axis[2], 1.5, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + + if ( es->eFlags & EF_SPINNING ) { // spinning will override the angles set by a stand + if ( es->groundEntityNum == -1 || !es->groundEntityNum ) { // (SA) spinning with a stand will spin the stand and the attached weap (only when in the air) + VectorCopy( cg.autoAnglesSlow, cent->lerpAngles ); + VectorCopy( cg.autoAnglesSlow, cent->lastLerpAngles ); + } else { + VectorCopy( cent->lastLerpAngles, cent->lerpAngles ); // make a tossed weapon sit on the ground in a position that matches how it was yawed + } + } + } + + } else { + AnglesToAxis( cent->lerpAngles, ent.axis ); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + + if ( es->eFlags & EF_SPINNING ) { // spinning will override the angles set by a stand + VectorCopy( cg.autoAnglesSlow, cent->lerpAngles ); + AxisCopy( cg.autoAxisSlow, ent.axis ); + } + } + + + if ( es->modelindex2 ) { // modelindex2 was specified for the ent, meaning it probably has an alternate model (as opposed to the one in the itemlist) + // try to load it first, and if it fails, default to the itemlist model + ent.hModel = cgs.gameModels[ es->modelindex2 ]; + } else { + if ( item->giType == IT_WEAPON && cg_items[es->modelindex].models[2] ) { // check if there's a specific model for weapon pickup placement + ent.hModel = cg_items[es->modelindex].models[2]; + } else if ( item->giType == IT_HEALTH || item->giType == IT_AMMO || item->giType == IT_POWERUP ) { + if ( es->density < ( 1 << 9 ) ) { // (10 bits of data transmission for density) + ent.hModel = cg_items[es->modelindex].models[es->density]; // multi-state powerups store their state in 'density' + } else { + ent.hModel = cg_items[es->modelindex].models[0]; + } + } else { + ent.hModel = cg_items[es->modelindex].models[0]; + } + } + + //----(SA) find midpoint for highlight corona. + // Can't do it when item is registered since it wouldn't know about replacement model + if ( !( cent->usehighlightOrigin ) ) { + vec3_t mins, maxs, offset; + int i; + + trap_R_ModelBounds( ent.hModel, mins, maxs ); // get bounds + + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); // find object-space center + } + + VectorCopy( cent->lerpOrigin, cent->highlightOrigin ); // set 'midpoint' to origin + + for ( i = 0 ; i < 3 ; i++ ) { // adjust midpoint by offset and orientation + cent->highlightOrigin[i] += offset[0] * ent.axis[0][i] + + offset[1] * ent.axis[1][i] + + offset[2] * ent.axis[2][i]; + } + + cent->usehighlightOrigin = qtrue; + } + + // items without glow textures need to keep a minimum light value so they are always visible +// if ( ( item->giType == IT_WEAPON ) || ( item->giType == IT_ARMOR ) ) { + ent.renderfx |= RF_MINLIGHT; +// } + + // highlighting items the player looks at + if ( cg_drawCrosshairPickups.integer ) { + + + if ( cg_drawCrosshairPickups.integer == 2 ) { // '2' is 'force highlights' + highlight = qtrue; + } + + if ( CG_PlayerSeesItem( &cg.predictedPlayerState, es, cg.time, item->giType ) ) { + highlight = qtrue; + + if ( item->giType == IT_TREASURE ) { + trap_R_AddCoronaToScene( cent->highlightOrigin, 1, 0.85, 0.5, 2, cent->currentState.number, qtrue ); //----(SA) add corona to treasure + } + } else { + if ( item->giType == IT_TREASURE ) { + trap_R_AddCoronaToScene( cent->highlightOrigin, 1, 0.85, 0.5, 2, cent->currentState.number, qfalse ); //----(SA) "empty corona" for proper fades + } + } + +//----(SA) added fixed item highlight fading + + if ( highlight ) { + if ( !cent->highlighted ) { + cent->highlighted = qtrue; + cent->highlightTime = cg.time; + } + ent.hilightIntensity = ( ( cg.time - cent->highlightTime ) / 250.0f ) * highlightFadeScale; // .25 sec to brighten up + } else { + if ( cent->highlighted ) { + cent->highlighted = qfalse; + cent->highlightTime = cg.time; + } + ent.hilightIntensity = 1.0f - ( ( cg.time - cent->highlightTime ) / 1000.0f ) * highlightFadeScale; // 1 sec to dim down (diff in time causes problems if you quickly flip to/away from looking at the item) + } + + if ( ent.hilightIntensity < 0.25f ) { // leave a minlight + ent.hilightIntensity = 0.25f; + } + if ( ent.hilightIntensity > 1 ) { + ent.hilightIntensity = 1.0; + } + } +//----(SA) end + + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + +//============================================================================ + +/* +=============== +CG_Smoker +=============== +*/ +static void CG_Smoker( centity_t *cent ) { + // this ent has some special setting up + // time = speed + // time2 = duration + // angles2[0] = start_size + // angles2[1] = end_size + // angles2[2] = wait + // dl_intensity = health + // constantLight = delay + // origin2 = normal to emit particles along + + if ( cg.time - cent->highlightTime > cent->currentState.constantLight ) { + // FIXME: make this framerate independant? + cent->highlightTime = cg.time; // fire a particle this frame + + if ( cent->currentState.density == 3 ) { // cannon + CG_ParticleSmoke( cgs.media.smokePuffShaderdirty, cent ); + } else if ( !( cent->currentState.density ) ) { + CG_ParticleSmoke( cgs.media.smokePuffShader, cent ); + } else { + CG_ParticleSmoke( cgs.media.smokePuffShader, cent ); + } + } + + cent->lastTrailTime = cg.time; // time we were last received at the client +} + +/* +=============== +CG_Missile +=============== +*/ + +extern void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ); + +static void CG_Missile( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles ); + + // add trails + if ( cent->currentState.eType == ET_FP_PARTS + || cent->currentState.eType == ET_FIRE_COLUMN + || cent->currentState.eType == ET_FIRE_COLUMN_SMOKE + || cent->currentState.eType == ET_RAMJET ) { + CG_RocketTrail( cent, NULL ); + } else if ( weapon->missileTrailFunc ) { + weapon->missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene( cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2], 0 ); + } + +//----(SA) whoops, didn't mean to check it in with the missile flare + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound, 255 ); + } + + // DHM - Nerve :: Don't tick until armed + if ( cgs.gametype >= GT_WOLF && cent->currentState.weapon == WP_DYNAMITE ) { + if ( cent->currentState.teamNum < 4 ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->spindownSound, 255 ); + } + } + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + +//----(SA) removed plasma gun code as sp5 is taking that spot + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + + if ( cent->currentState.eType == ET_FP_PARTS ) { + ent.hModel = cgs.gameModels[cent->currentState.modelindex]; + } else if ( cent->currentState.eType == ET_EXPLO_PART ) { + ent.hModel = cgs.gameModels[cent->currentState.modelindex]; + } else if ( cent->currentState.eType == ET_FLAMEBARREL ) { + ent.hModel = cgs.media.flamebarrel; + } else if ( cent->currentState.eType == ET_FIRE_COLUMN || cent->currentState.eType == ET_FIRE_COLUMN_SMOKE ) { + // it may have a model sometime in the future + ent.hModel = 0; + } else if ( cent->currentState.eType == ET_RAMJET ) { + ent.hModel = 0; + } + // ent.hModel = cgs.gameModels[cent->currentState.modelindex]; + else { + ent.hModel = weapon->missileModel; + } + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + // spin as it moves + if ( s1->pos.trType != TR_STATIONARY ) { + RotateAroundDirection( ent.axis, cg.time / 4 ); + } else { + RotateAroundDirection( ent.axis, s1->time ); + } + + // Rafael + // Added this since it may be a propExlosion + if ( ent.hModel ) { + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1->powerups, TEAM_FREE, s1, vec3_origin ); + } + +} + +/* +=============== +CG_Bat +=============== +*/ +static void CG_Bat( centity_t *cent ) { + CG_ParticleBat( cent ); + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.batsFlyingLoopSound, 5 ); +} + +//----(SA) animation_t struct changed, so changes are to keep this working +static animation_t grabberAnims[] = { + {"", 0, 6, 6, 1000 / 5, 1000 / 5 }, // (main idle) + {"", 5, 21, 21, 1000 / 7, 1000 / 7 }, // (random idle) + {"", 25, 11, 0, 1000 / 15, 1000 / 15 }, // (attack big swipe) + {"", 35, 16, 0, 1000 / 15, 1000 / 15 }, // (attack small swipe) + {"", 50, 16, 0, 1000 / 15, 1000 / 15 }, // (attack grab) + {"", 66, 1, 0, 1000 / 15, 1000 / 15 } // (starting position) +}; + +//----(SA) added +static animation_t footlockerAnims[] = { + {"", 0, 1, 1, 1000 / 5, 1000 / 5 }, // (main idle) + {"", 0, 5, 5, 1000 / 5, 1000 / 5 }, // (lock rattle) + {"", 5, 6, 0, 1000 / 5, 1000 / 5 } // (break open) +}; + +//----(SA) end + +// DHM - Nerve :: capture and hold flag + +static animation_t multi_flagpoleAnims[] = { + {"", 0, 1, 0, 1000 / 15, 1000 / 15 }, // (no flags, idle) + {"", 0, 15, 0, 1000 / 15, 1000 / 15 }, // (axis flag rising) + {"", 490, 15, 0, 1000 / 15, 1000 / 15 }, // (american flag rising) + {"", 20, 211, 211, 1000 / 15, 1000 / 15 }, // (axis flag raised) + {"", 255, 211, 211, 1000 / 15, 1000 / 15 }, // (american flag raised) + {"", 235, 15, 0, 1000 / 15, 1000 / 15 }, // (axis switching to american) + {"", 470, 15, 0, 1000 / 15, 1000 / 15 }, // (american switching to axis) + {"", 510, 15, 0, 1000 / 15, 1000 / 15 }, // (axis flag falling) + {"", 530, 15, 0, 1000 / 15, 1000 / 15 } // (american flag falling) +}; + +// dhm - end + +extern void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ); + + +/* +============== +CG_TrapSetAnim +============== +*/ +static void CG_TrapSetAnim( centity_t *cent, lerpFrame_t *lf, int newAnim ) { + // transition animation + lf->animationNumber = cent->currentState.frame; + + if ( 0 ) { + lf->animation = &grabberAnims[cent->currentState.frame]; + } else { + lf->animation = &footlockerAnims[cent->currentState.frame]; + } + + // DHM - Nerve :: teamNum specifies which set of animations to use (only 1 exists right now) + if ( cgs.gametype >= GT_WOLF ) { + switch ( cent->currentState.teamNum ) { + + case 1: + lf->animation = &multi_flagpoleAnims[ cent->currentState.frame ]; + break; + default: + // Keep what was set above + break; + } + } + // dhm - end + + lf->animationTime = lf->frameTime + lf->animation->initialLerp; +} + +/* +============== +CG_Trap + // TODO: change from 'trap' to something else. 'trap' is a misnomer. it's actually used for other stuff too +============== +*/ +static void CG_Trap( centity_t *cent ) { + refEntity_t ent; + entityState_t *cs; + lerpFrame_t *traplf; + + memset( &ent, 0, sizeof( ent ) ); + + cs = ¢->currentState; + + traplf = ¢->lerpFrame; + + // initial setup + if ( !traplf->oldFrameTime ) { + traplf->frameTime = + traplf->oldFrameTime = cg.time; + + CG_TrapSetAnim( cent, traplf, cs->frame ); + + traplf->frame = + traplf->oldFrame = traplf->animation->firstFrame; + } + + // transition to new anim if requested + if ( ( traplf->animationNumber != cs->frame ) || !traplf->animation ) { + CG_TrapSetAnim( cent, traplf, cs->frame ); + } + + CG_RunLerpFrame( NULL, traplf, 0, 1 ); // use existing lerp code rather than re-writing + + ent.frame = traplf->frame; + ent.oldframe = traplf->oldFrame; + ent.backlerp = traplf->backlerp; + + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + + ent.hModel = cgs.gameModels[cs->modelindex]; + + AnglesToAxis( cent->lerpAngles, ent.axis ); + + trap_R_AddRefEntityToScene( &ent ); + + memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); +} +//----(SA) end + + +/* +============== +CG_Corona +============== +*/ +static void CG_Corona( centity_t *cent ) { + trace_t tr; + int r, g, b; + int dli; + qboolean visible = qfalse, + behind = qfalse, + toofar = qfalse; + + float dot, dist; + vec3_t dir; + + if ( cg_coronas.integer == 0 ) { // if set to '0' no coronas + return; + } + + dli = cent->currentState.dl_intensity; + r = dli & 255; + g = ( dli >> 8 ) & 255; + b = ( dli >> 16 ) & 255; + + // only coronas that are in your PVS are being added + + VectorSubtract( cg.refdef.vieworg, cent->lerpOrigin, dir ); + + dist = VectorNormalize2( dir, dir ); + if ( dist > cg_coronafardist.integer ) { // performance variable cg_coronafardist will keep down super long traces + toofar = qtrue; + } + + dot = DotProduct( dir, cg.refdef.viewaxis[0] ); + if ( dot >= -0.6 ) { // assumes ~90 deg fov (SA) changed value to 0.6 (screen corner at 90 fov) + behind = qtrue; // use the dot to at least do trivial removal of those behind you. + } + // yeah, I could calc side planes to clip against, but would that be worth it? (much better than dumb dot>= thing?) + +// CG_Printf("dot: %f\n", dot); + + if ( cg_coronas.integer == 2 ) { // if set to '2' trace everything + behind = qfalse; + toofar = qfalse; + } + + + if ( !behind && !toofar ) { + CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, cent->lerpOrigin, -1, MASK_SOLID | CONTENTS_BODY ); // added blockage by players. not sure how this is going to be since this is their bb, not their model (too much blockage) + + if ( tr.fraction == 1 ) { + visible = qtrue; + } + + trap_R_AddCoronaToScene( cent->lerpOrigin, (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, (float)cent->currentState.density / 255.0f, cent->currentState.number, visible ); + } +} + + +/* +============== +CG_Efx +============== +*/ +static void CG_Efx( centity_t *cent ) { + int i; + float rnd; + trace_t trace; + vec3_t perpvec; + vec3_t stickPoint; + float movePerUpdate; + + if ( cent->currentState.eType == ET_EF_TESLA ) { + rnd = cent->currentState.angles2[0]; + + for ( i = 0; i < MAX_TESLA_BOLTS; i++ ) { + if ( cent->boltTimes[i] < cg.time ) { + VectorSet( cent->boltLocs[i], crandom(), crandom(), crandom() ); + VectorNormalize2( cent->boltLocs[i], cent->boltLocs[i] ); + VectorMA( cent->currentState.origin2, rnd, cent->boltLocs[i], cent->boltLocs[i] ); + + cent->boltTimes[i] = cg.time + rand() % cent->currentState.time2; // hold this position for ~1 second ('stickytime' value is stored in .time2) + + // cut the bolt short if it collides w/ something + CG_Trace( &trace, cent->currentState.origin, NULL, NULL, cent->boltLocs[i], -1, MASK_SOLID | CONTENTS_BODY ); + + if ( trace.fraction < 1 ) { + // take damage + if ( trace.entityNum != ENTITYNUM_WORLD ) { +// CG_ClientDamage(trace.entityNum, cent->currentState.number, CLDMG_TESLA); +// cg_entities[trace.entityNum].pe.teslaDamagedTime = cg.time; + } + + VectorCopy( trace.endpos, cent->boltLocs[i] ); + + // store perpendicular vector so end can 'crawl' + PerpendicularVector( perpvec, trace.plane.normal ); + + RotatePointAroundVector( stickPoint, trace.plane.normal, perpvec, crandom() * 360 ); + + // scale it so it won't move too far with bolts that have long boltTimer's + movePerUpdate = 1.0f / (float)( cent->boltTimes[i] - cg.time ); + + // move a max of 64 away from the 'original' target location + VectorScale( stickPoint, movePerUpdate * trace.fraction * 64.0f, cent->boltCrawlDirs[i] ); + + } else { + VectorSet( cent->boltCrawlDirs[i], 0, 0, 0 ); + } + + } + } + + for ( i = 0; i < MAX_TESLA_BOLTS; i++ ) { + + if ( cent->boltCrawlDirs[0] || cent->boltCrawlDirs[1] || cent->boltCrawlDirs[2] ) { + VectorMA( cent->boltLocs[i], cent->boltTimes[i] - cg.time, cent->boltCrawlDirs[i], perpvec ); + } else { + VectorCopy( cent->boltLocs[i], perpvec ); + } + + CG_DynamicLightningBolt( cgs.media.lightningBoltShader, // shader + cent->currentState.origin, // start + perpvec, // end + cent->currentState.density, // numBolts + cent->currentState.frame, // maxWidth + qtrue, // fade + 1.0, // startAlpha + 0, // recursion + i * i * 2 ); // randseed + } + + // add dlight + if ( cent->currentState.dl_intensity ) { + int r, g, b; + int dli; + int randomness = cent->currentState.time2; + + if ( ( cg.time / 50 ) % ( randomness + ( cg.time % randomness ) ) == 0 ) { + dli = cent->currentState.dl_intensity; + r = dli & 255; + g = ( dli >> 8 ) & 255; + b = ( dli >> 16 ) & 255; + trap_R_AddLightToScene( cent->currentState.origin, cent->currentState.time, (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, 0 ); + } + } + } else if ( cent->currentState.eType == ET_EF_SPOTLIGHT ) { + + vec3_t targetpos, normalized_direction, direction; + float dist, fov = 90; + vec4_t color = {1, 1, 1, .1}; + int splinetarget = 0; + char *cs; + + VectorCopy( cent->currentState.origin2, targetpos ); + + splinetarget = cent->overheatTime; + + if ( !splinetarget ) { + cs = (char *)CG_ConfigString( CS_SPLINES + cent->currentState.density ); + cent->overheatTime = splinetarget = CG_LoadCamera( va( "cameras/%s.camera", cs ) ); + if ( splinetarget != -1 ) { + trap_startCamera( splinetarget, cg.time ); + } + } else { + vec3_t angles; + if ( splinetarget != -1 ) { + if ( trap_getCameraInfo( splinetarget, cg.time, &targetpos, &angles, &fov ) ) { + + } else { // loop + trap_startCamera( splinetarget, cg.time ); + trap_getCameraInfo( splinetarget, cg.time, &targetpos, &angles, &fov ); + } + } + } + + + normalized_direction[0] = direction[0] = targetpos[0] - cent->currentState.origin[0]; + normalized_direction[1] = direction[1] = targetpos[1] - cent->currentState.origin[1]; + normalized_direction[2] = direction[2] = targetpos[2] - cent->currentState.origin[2]; + + dist = VectorNormalize( normalized_direction ); + + if ( dist == 0 ) { + return; + } + + CG_Spotlight( cent, color, cent->currentState.origin, normalized_direction, 999, 2048, 10, fov, 0 ); + } +} + + +//----(SA) adding func_explosive + +/* +=============== +CG_Explosive + This is currently almost exactly the same as CG_Mover + It's split out so that any changes or experiments are + unattached to anything else. +=============== +*/ +static void CG_Explosive( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); +// VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); +// VectorCopy( ent.origin, cent->lerpOrigin); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + // get the model, either as a bmodel or a modelindex + if ( s1->solid == SOLID_BMODEL ) { + ent.hModel = cgs.inlineDrawModel[s1->modelindex]; + } else { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + // add to refresh list + // trap_R_AddRefEntityToScene(&ent); + + // add the secondary model + if ( s1->modelindex2 ) { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + trap_R_AddRefEntityToScene( &ent ); + } else { + trap_R_AddRefEntityToScene( &ent ); + } + +} + +//----(SA) done + +// declaration for add bullet particles (might as well stick this one in a .h file I think) +extern void CG_AddBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ); + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + // flicker between two skins (FIXME?) + ent.skinNum = 0; + + // get the model, either as a bmodel or a modelindex + if ( s1->solid == SOLID_BMODEL ) { + ent.hModel = cgs.inlineDrawModel[s1->modelindex]; + } else { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + // Rafael + // testing for mike to get movers to scale + if ( cent->currentState.density == ET_MOVERSCALED ) { + VectorScale( ent.axis[0], cent->currentState.angles2[0], ent.axis[0] ); + VectorScale( ent.axis[1], cent->currentState.angles2[1], ent.axis[1] ); + VectorScale( ent.axis[2], cent->currentState.angles2[2], ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } + + +//----(SA) added + if ( cent->currentState.eType == ET_ALARMBOX ) { + ent.renderfx |= RF_MINLIGHT; + } +//----(SA) end + + + // add the secondary model + if ( s1->modelindex2 ) { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + ent.frame = s1->frame; + trap_R_AddRefEntityToScene( &ent ); + memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); + } else { + trap_R_AddRefEntityToScene( &ent ); + } + + // add propeller and sfx to me109 + if ( cent->currentState.density == 7 || cent->currentState.density == 8 ) { + refEntity_t propeller; + vec3_t angles; + + memset( &propeller, 0, sizeof( propeller ) ); + VectorCopy( ent.lightingOrigin, propeller.lightingOrigin ); + propeller.shadowPlane = ent.shadowPlane; + propeller.renderfx = ent.renderfx; + + propeller.hModel = propellerModel; + + angles[PITCH] = cg.time % 16; + + AnglesToAxis( angles, propeller.axis ); + + CG_PositionRotatedEntityOnTag( &propeller, &ent, "tag_prop" ); + + trap_R_AddRefEntityToScene( &propeller ); + + if ( cent->currentState.density == 8 ) { + refEntity_t flash; + vec3_t angles; + + angles[YAW] = 90; + angles[ROLL] = random() * 90; + + memset( &flash, 0, sizeof( flash ) ); + flash.renderfx = ent.shadowPlane; + //flash.hModel = cgs.media.mg42muzzleflash; + flash.hModel = cgs.media.planemuzzleflash; + + AnglesToAxis( angles, flash.axis ); + CG_PositionRotatedEntityOnTag( &flash, &ent, "tag_gun1" ); + + trap_R_AddRefEntityToScene( &flash ); + trap_R_AddLightToScene( flash.origin, 200 + ( rand() & 31 ),1.0, 0.6, 0.23, 0 ); + + memset( &flash, 0, sizeof( flash ) ); + flash.renderfx = ent.shadowPlane; + //flash.hModel = cgs.media.mg42muzzleflash; + flash.hModel = cgs.media.planemuzzleflash; + + AnglesToAxis( angles, flash.axis ); + CG_PositionRotatedEntityOnTag( &flash, &ent, "tag_gun02" ); + + trap_R_AddRefEntityToScene( &flash ); + trap_R_AddLightToScene( flash.origin, 200 + ( rand() & 31 ),1.0, 0.6, 0.23, 0 ); + } + } + + // alarm box spark effects + + if ( cent->currentState.eType == ET_ALARMBOX ) { + if ( cent->currentState.frame == 2 ) { // i'm dead + if ( rand() % 50 == 1 ) { + vec3_t angNorm; // normalized angles + VectorNormalize2( cent->lerpAngles, angNorm ); + // (origin, dir, speed, duration, count, 'randscale') + CG_AddBulletParticles( cent->lerpOrigin, angNorm, 2, 800, 4, 16.0f ); + trap_S_StartSound( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.sparkSounds[0] ); + } + } + } +} + +/* +=============== +CG_Beam + +Also called as an event +=============== +*/ +void CG_Beam( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( s1->pos.trBase, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + + AxisClear( ent.axis ); + ent.reType = RT_BEAM; + + ent.renderfx = RF_NOSHADOW; + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + ByteToDir( s1->eventParm, ent.axis[0] ); + PerpendicularVector( ent.axis[1], ent.axis[0] ); + + // negating this tends to get the directions like they want + // we really should have a camera roll value + VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); + + CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); + ent.reType = RT_PORTALSURFACE; + ent.frame = s1->frame; // rotation speed + ent.skinNum = s1->clientNum / 256.0 * 360; // roll offset + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_Prop +=============== +*/ +static void CG_Prop( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + vec3_t angles; + float scale; + + s1 = ¢->currentState; + + // create the render entity + memset( &ent, 0, sizeof( ent ) ); + + if ( cg.renderingThirdPerson ) { + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + } else + { + VectorCopy( cg.refdef.vieworg, ent.origin ); + VectorCopy( cg.refdefViewAngles, angles ); + + if ( cg.bobcycle & 1 ) { + scale = -cg.xyspeed; + } else { + scale = cg.xyspeed; + } + + // modify angles from bobbing + angles[ROLL] += scale * cg.bobfracsin * 0.005; + angles[YAW] += scale * cg.bobfracsin * 0.01; + angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; + + VectorCopy( angles, cent->lerpAngles ); + + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + + if ( cent->currentState.density ) { + ent.frame = s1->frame + cent->currentState.density; + ent.oldframe = ent.frame - 1; + ent.backlerp = 1 - cg.frameInterpolation; + ent.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; + + //CG_Printf ("frame %d oldframe %d\n", ent.frame, ent.oldframe); + } else if ( ent.frame ) { + ent.oldframe -= 1; + ent.backlerp = 1 - cg.frameInterpolation; + } else + { + ent.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; + } + } + + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx |= RF_NOSHADOW; + + // flicker between two skins (FIXME?) + ent.skinNum = ( cg.time >> 6 ) & 1; + + // get the model, either as a bmodel or a modelindex + if ( s1->solid == SOLID_BMODEL ) { + ent.hModel = cgs.inlineDrawModel[s1->modelindex]; + } else { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + // add the secondary model + if ( s1->modelindex2 ) { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + ent.frame = s1->frame; + trap_R_AddRefEntityToScene( &ent ); + memcpy( ¢->refEnt, &ent, sizeof( refEntity_t ) ); + } else { + trap_R_AddRefEntityToScene( &ent ); + } + +} + + +/* +========================= +CG_AdjustPositionForMover + +Also called by client movement prediction code +========================= +*/ +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out, vec3_t outDeltaAngles ) { + centity_t *cent; + vec3_t oldOrigin, origin, deltaOrigin; + vec3_t oldAngles, angles, deltaAngles; + + if ( outDeltaAngles ) { + VectorClear( outDeltaAngles ); + } + + if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { + VectorCopy( in, out ); + return; + } + + cent = &cg_entities[ moverNum ]; + + if ( cent->currentState.eType != ET_MOVER ) { + VectorCopy( in, out ); + return; + } + + BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); + + BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); + BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); + + VectorSubtract( origin, oldOrigin, deltaOrigin ); + VectorSubtract( angles, oldAngles, deltaAngles ); + + VectorAdd( in, deltaOrigin, out ); + if ( outDeltaAngles ) { + VectorCopy( deltaAngles, outDeltaAngles ); + } + + // FIXME: origin change when on a rotating object +} + + +/* +============================= +CG_InterpolateEntityPosition +============================= +*/ +static void CG_InterpolateEntityPosition( centity_t *cent ) { + vec3_t current, next; + float f; + + // it would be an internal error to find an entity that interpolates without + // a snapshot ahead of the current one + if ( cg.nextSnap == NULL ) { + // DHM - Nerve :: FIXME? There are some cases when in Limbo mode during a map restart + // that were tripping this error. + //CG_Error( "CG_InterpolateEntityPosition: cg.nextSnap == NULL" ); + //CG_Printf("CG_InterpolateEntityPosition: cg.nextSnap == NULL"); + return; + } + + f = cg.frameInterpolation; + + // this will linearize a sine or parabolic curve, but it is important + // to not extrapolate player positions if more recent data is available + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + + cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); + cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); + cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); + + BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); + + cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); + cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); + cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); + +} + +/* +=============== +CG_CalcEntityLerpPositions + +=============== +*/ +static void CG_CalcEntityLerpPositions( centity_t *cent ) { + if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { + CG_InterpolateEntityPosition( cent ); + return; + } + + // NERVE - SMF - fix for jittery clients in multiplayer + if ( cgs.gametype != GT_SINGLE_PLAYER ) { + // first see if we can interpolate between two snaps for + // linear extrapolated clients + if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && + cent->currentState.number < MAX_CLIENTS ) { + CG_InterpolateEntityPosition( cent ); + return; + } + } + // -NERVE - SMF + + // just use the current frame and evaluate as best we can + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if ( cent != &cg.predictedPlayerEntity ) { + CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, + cg.snap->serverTime, cg.time, cent->lerpOrigin, NULL ); + } +} + +/* +=============== +CG_ProcessEntity +=============== +*/ +static void CG_ProcessEntity( centity_t *cent ) { + switch ( cent->currentState.eType ) { + default: + CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + break; + case ET_CONCUSSIVE_TRIGGER: + case ET_CAMERA: + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + case ET_TELEPORT_TRIGGER: + case ET_OID_TRIGGER: + case ET_AI_EFFECT: + case ET_EXPLOSIVE_INDICATOR: // NERVE - SMF + break; + case ET_SPEAKER: + CG_Speaker( cent ); + break; + case ET_GAMEMODEL: + if ( !cg_drawGamemodels.integer ) { + break; + } + case ET_MG42_BARREL: + case ET_FOOTLOCKER: + case ET_GENERAL: + CG_General( cent ); + break; + case ET_CORPSE: + case ET_PLAYER: + CG_Player( cent ); + break; + case ET_ITEM: + CG_Item( cent ); + break; + case ET_MISSILE: + case ET_FLAMEBARREL: + case ET_FP_PARTS: + case ET_FIRE_COLUMN: + case ET_FIRE_COLUMN_SMOKE: + case ET_EXPLO_PART: + case ET_RAMJET: + CG_Missile( cent ); + break; + case ET_EF_TESLA: + case ET_EF_SPOTLIGHT: + case ET_EFFECT3: + CG_Efx( cent ); + break; + case ET_EXPLOSIVE: + CG_Explosive( cent ); + break; + case ET_TRAP: + CG_Trap( cent ); + break; + case ET_ALARMBOX: + case ET_MOVER: + CG_Mover( cent ); + break; + case ET_PROP: + CG_Prop( cent ); + break; + case ET_BEAM: + CG_Beam( cent ); + break; + case ET_PORTAL: + CG_Portal( cent ); + break; + case ET_CORONA: + CG_Corona( cent ); + break; + case ET_BAT: + CG_Bat( cent ); + break; + case ET_SMOKER: + CG_Smoker( cent ); + break; + } +} + +/* +=============== +CG_AddCEntity + +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) { + // event-only entities will have been dealt with already + if ( cent->currentState.eType >= ET_EVENTS ) { + return; + } + + cent->processedFrame = cg.clientFrame; + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + // add automatic effects + CG_EntityEffects( cent ); + + // call the appropriate function which will add this entity to the view accordingly + CG_ProcessEntity( cent ); +} + +/* +================== +CG_AddEntityToTag +================== +*/ +static void CG_AddEntityToTag( centity_t *cent ) { + entityState_t *s1; + centity_t *centParent; + entityState_t *sParent; + refEntity_t ent; + char *cs, *token = NULL; + int i, pi; + vec3_t ang; + + memset( &ent, 0, sizeof( ent ) ); + + // event-only entities will have been dealt with already + if ( cent->currentState.eType >= ET_EVENTS ) { + return; + } + + if ( cent->processedFrame == cg.clientFrame ) { + // already processed this frame + return; + } + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + s1 = ¢->currentState; + + // find us in the list of tagged entities + sParent = NULL; + centParent = NULL; + for ( i = CS_TAGCONNECTS + 1; i < CS_TAGCONNECTS + MAX_TAGCONNECTS; i++ ) { // NOTE: +1 since G_FindConfigStringIndex() starts at index 1 rather than 0 (not sure why) + cs = (char *)CG_ConfigString( i ); + token = COM_Parse( &cs ); + if ( !token[0] ) { + break; + } + if ( atoi( token ) == s1->number ) { + token = COM_Parse( &cs ); + if ( !token[0] ) { + CG_Error( "CG_EntityTagConnected: missing parameter in configstring" ); + } + pi = atoi( token ); + if ( pi < 0 || pi >= MAX_GENTITIES ) { + CG_Error( "CG_EntityTagConnected: parent out of range" ); + } + centParent = &cg_entities[pi]; + sParent = &( cg_entities[pi].currentState ); + token = COM_Parse( &cs ); + if ( !token[0] ) { + CG_Error( "CG_EntityTagConnected: missing parameter in configstring" ); + } + + // NOTE: token is now the tag name to attach to + + break; + } + } + + if ( !sParent ) { + CG_Error( "CG_EntityTagConnected: unable to find configstring to perform connection" ); + } + + // if parent isn't visible, then don't draw us + if ( !centParent->currentValid ) { + return; + } + + // make sure all parents are added first + if ( centParent->processedFrame != cg.clientFrame ) { + if ( sParent->eFlags & EF_TAGCONNECT ) { + CG_AddEntityToTag( centParent ); + } + } + + // if there was a higher ranking parent not added to the scene, then don't add us + if ( centParent->processedFrame != cg.clientFrame ) { + return; + } + + cent->processedFrame = cg.clientFrame; + + // start with default axis + AnglesToAxis( vec3_origin, ent.axis ); + + // get the tag position from parent + CG_PositionEntityOnTag( &ent, ¢Parent->refEnt, token, 0, NULL ); + + VectorCopy( ent.origin, cent->lerpOrigin ); + // we need to add the child's angles to the tag angles + if ( !cent->currentState.density ) { // this entity should rotate with it's parent, but can turn around using it's own angles + AxisToAngles( ent.axis, ang ); + VectorAdd( cent->lerpAngles, ang, cent->lerpAngles ); + } else { // face our angles exactly + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + } + + // add automatic effects + CG_EntityEffects( cent ); + + // call the appropriate function which will add this entity to the view accordingly + CG_ProcessEntity( cent ); +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( void ) { + int num; + centity_t *cent; + playerState_t *ps; + //int clcount; + + // set cg.frameInterpolation + if ( cg.nextSnap ) { + int delta; + + delta = ( cg.nextSnap->serverTime - cg.snap->serverTime ); + if ( delta == 0 ) { + cg.frameInterpolation = 0; + } else { + cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; + } + } else { + cg.frameInterpolation = 0; // actually, it should never be used, because + // no entities should be marked as interpolating + } + + // the auto-rotating items will all have the same axis + cg.autoAnglesSlow[0] = 0; + cg.autoAnglesSlow[1] = ( cg.time & 4095 ) * 360 / 4095.0; + cg.autoAnglesSlow[2] = 0; + + cg.autoAngles[0] = 0; + cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; + cg.autoAngles[2] = 0; + + cg.autoAnglesFast[0] = 0; + cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; + cg.autoAnglesFast[2] = 0; + + AnglesToAxis( cg.autoAnglesSlow, cg.autoAxisSlow ); + AnglesToAxis( cg.autoAngles, cg.autoAxis ); + AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + + // generate and add the entity from the playerstate + ps = &cg.predictedPlayerState; + BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); + CG_AddCEntity( &cg.predictedPlayerEntity ); + + // lerp the non-predicted value for lightning gun origins + CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + + // add each entity sent over by the server + + // NON TAG-CONNECTED ENTITIES + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + if ( !( cent->currentState.eFlags & EF_TAGCONNECT ) ) { + CG_AddCEntity( cent ); + } + } + + // TAG-CONNECTED ENTITIES (connected to NON TAG-CONNECTED ENTITIES) + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + if ( cent->currentState.eFlags & EF_TAGCONNECT ) { + CG_AddEntityToTag( cent ); + } + } + + // Ridah, add the flamethrower sounds + CG_UpdateFlamethrowerSounds(); +} diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c new file mode 100644 index 0000000..b3168a5 --- /dev/null +++ b/src/cgame/cg_event.c @@ -0,0 +1,2737 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +// cg_event.c -- handle entity events at snapshot or playerstate transitions + +#include "cg_local.h" + +extern int hWeaponSnd; +extern int hWeaponEchoSnd; // JPW NERVE nasty kludge, referenced from cg_weapons.c + +extern void CG_Tracer( vec3_t source, vec3_t dest, int sparks ); +//========================================================================== + +/* +=================== +CG_PlaceString + +Also called by scoreboard drawing +=================== +*/ +const char *CG_PlaceString( int rank ) { + static char str[64]; + char *s, *t; + + if ( rank & RANK_TIED_FLAG ) { + rank &= ~RANK_TIED_FLAG; + t = "Tied for "; + } else { + t = ""; + } + + if ( rank == 1 ) { + s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue + } else if ( rank == 2 ) { + s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red + } else if ( rank == 3 ) { + s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow + } else if ( rank == 11 ) { + s = "11th"; + } else if ( rank == 12 ) { + s = "12th"; + } else if ( rank == 13 ) { + s = "13th"; + } else if ( rank % 10 == 1 ) { + s = va( "%ist", rank ); + } else if ( rank % 10 == 2 ) { + s = va( "%ind", rank ); + } else if ( rank % 10 == 3 ) { + s = va( "%ird", rank ); + } else { + s = va( "%ith", rank ); + } + + Com_sprintf( str, sizeof( str ), "%s%s", t, s ); + return str; +} + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) { + int mod; + int target, attacker; + int killtype = 0; // DHM - Nerve :: 0==Axis; 1==Allied; 2==your kill + char *message; + char *message2; + const char *targetInfo; + const char *attackerInfo; + char targetName[32]; + char attackerName[32]; + clientInfo_t *ci, *ca; // JPW NERVE ca = attacker + + // Ridah, no obituaries in single player + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + return; + } + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm; + + if ( target < 0 || target >= MAX_CLIENTS ) { + CG_Error( "CG_Obituary: target out of range" ); + } + ci = &cgs.clientinfo[target]; + ca = &cgs.clientinfo[attacker]; + + if ( attacker < 0 || attacker >= MAX_CLIENTS ) { + attacker = ENTITYNUM_WORLD; + attackerInfo = NULL; + } else { + attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); + } + + targetInfo = CG_ConfigString( CS_PLAYERS + target ); + if ( !targetInfo ) { + return; + } + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName ) - 2 ); + strcat( targetName, S_COLOR_WHITE ); + + message2 = ""; + + // DHM - Nerve :: Set killtype + if ( attacker == cg.snap->ps.clientNum ) { + killtype = 2; + } else if ( ci->team == TEAM_BLUE ) { + killtype = 1; + } else { + killtype = 0; + } + + // check for single client messages + + switch ( mod ) { + case MOD_SUICIDE: + message = "committed suicide"; + break; + case MOD_FALLING: + message = "fell to his death"; + break; + case MOD_CRUSH: + message = "was crushed"; + break; + case MOD_WATER: + message = "drowned"; + break; + case MOD_SLIME: + message = "died by toxic materials"; + break; + //case MOD_LAVA: + //message = "does a back flip into the lava"; + //break; + //case MOD_TARGET_LASER: + //message = "saw the light"; + //break; + case MOD_TRIGGER_HURT: + message = "was killed"; + break; + default: + message = NULL; + break; + } + + if ( attacker == target ) { + switch ( mod ) { +// JPW NERVE per atvi req + case MOD_DYNAMITE: + case MOD_DYNAMITE_SPLASH: + message = "dynamited himself to pieces"; + break; +// jpw + case MOD_GRENADE_SPLASH: + message = "dove on his own grenade"; + break; + case MOD_ROCKET_SPLASH: + message = "vaporized himself"; + break; + case MOD_AIRSTRIKE: + message = "obliterated himself"; + break; + //case MOD_BFG_SPLASH: + //message = "should have used a smaller gun"; + //break; + case MOD_EXPLOSIVE: + message = "died in his own explosion"; + break; + default: + message = "killed himself"; + break; + } + } + if ( message ) { + message = CG_TranslateString( message ); + CG_Printf( "[cgnotify]%s %s.\n", targetName, message ); + return; + } + + // check for kill messages from the current clientNum + if ( attacker == cg.snap->ps.clientNum ) { + char *s; + + if ( cgs.gametype < GT_TEAM ) { + s = va( "You killed %s\n%s place with %i", targetName, + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + } else { + if ( ci->team == ca->team ) { + s = va( "%s %s", CG_TranslateString( "You killed ^1TEAMMATE^7" ), targetName ); + } else { + s = va( "%s %s", CG_TranslateString( "You killed" ), targetName ); + } + } + CG_PriorityCenterPrint( s, SCREEN_HEIGHT * 0.75, BIGCHAR_WIDTH * 0.6, 1 ); + // print the text message as well + } + + // check for double client messages + if ( !attackerInfo ) { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } else { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName ) - 2 ); + strcat( attackerName, S_COLOR_WHITE ); + // check for kill messages about the current clientNum + if ( target == cg.snap->ps.clientNum ) { + Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); + } + } + + if ( attacker != ENTITYNUM_WORLD ) { + switch ( mod ) { + + // TODO: put real text here. these are just placeholders + + case MOD_KNIFE_STEALTH: + case MOD_KNIFE: + case MOD_KNIFE2: + //message = "was stabbed to death by"; + message = "was stabbed by"; + message2 = "'s knife"; + break; + case MOD_LUGER: + //message = "borrowed some bullets from "; + //message2 = "'s Luger Parabellum 9mm"; + message = "was killed by"; + message2 = "'s Luger 9mm"; + break; + case MOD_COLT: + //message = "'s face was the unwilling recipient of"; + //message2 = "'s .45ACP 1911 rounds"; + message = "was killed by"; + message2 = " 's .45ACP 1911"; + break; + case MOD_MP40: + //message = "was force fed a magazine of"; + //message2 = "'s MP40 slugs"; + message = "was killed by"; + message2 = "'s MP40"; + break; + case MOD_THOMPSON: + //message = "was hit by a fusillade from"; + //message2 = "'s Thompson"; + message = "was killed by"; + message2 = "'s Thompson"; + break; + case MOD_STEN: + //message = "was quietly zapped by"; + //message2 = "'s Sten"; + message = "was killed by"; + message2 = "'s Sten"; + break; + case MOD_MAUSER: + //message = "took a rifle gut shot from"; + message = "was killed by"; + message2 = "'s Mauser"; + break; + case MOD_SNIPERRIFLE: + //message = "caught a sniper round in the teeth from"; + message = "was killed by"; + message2 = "'s sniper rifle"; + break; + case MOD_GARAND: + //message = "was killed (garand) by"; + break; + case MOD_SNOOPERSCOPE: + //message = "was killed (snooper) by"; + break; + case MOD_AKIMBO: + //message = "was killed (dual colts) by"; + break; +// JPW NERVE - per atvi req + case MOD_DYNAMITE: + case MOD_DYNAMITE_SPLASH: + message = "was blasted by"; + message2 = "'s dynamite"; + break; +// jpw + case MOD_ROCKET_LAUNCHER: + case MOD_ROCKET_SPLASH: + //message = "was vaporized by"; + //message2 = "'s Panzerfaust"; + message = "was blasted by"; + message2 = "'s Panzerfaust"; + break; + case MOD_GRENADE_LAUNCHER: + case MOD_GRENADE_SPLASH: + case MOD_GRENADE_PINEAPPLE: + //message = "was blown to bits by"; + //message2 = "'s potato masher"; + message = "was exploded by"; + message2 = "'s grenade"; + break; + case MOD_VENOM: + //message = "was ventilated by"; + message = "was ventilated by"; + message2 = "'s Venom"; + break; + case MOD_VENOM_FULL: + //message = "was killed (venom shot) by"; + break; + case MOD_FLAMETHROWER: + //message = "was roasted on the barbie by"; + message = "was cooked by"; + message2 = "'s flamethrower"; + break; + case MOD_TESLA: + //message = "was killed (tesla) by"; + break; + case MOD_SPEARGUN: + //message = "was killed (spear) by"; + break; + case MOD_SPEARGUN_CO2: + //message = "was killed (co2 spear) by"; + break; + case MOD_MACHINEGUN: + //message = "was perforated by"; + //message2 = "'s crew-served mg42"; + message = "was perforated by"; + message2 = "'s crew-served MG42"; + break; + case MOD_CROSS: + //message = "was killed (cross) by"; + break; +// JPW NERVE + case MOD_AIRSTRIKE: + //message = "stood under"; + //message2 = "'s air strike"; + message = "was blasted by"; + message2 = "'s support fire"; // JPW NERVE changed since it gets called for both air strikes and artillery + break; +// jpw +// (SA) leaving a sample of two part obit's +// case MOD_ROCKET: +// message = "ate"; +// message2 = "'s rocket"; +// break; +// case MOD_ROCKET_SPLASH: +// message = "almost dodged"; +// message2 = "'s rocket"; +// break; + default: + message = "was killed by"; + break; + } + +// JPW NERVE if attacker != target but on same team + if ( ci->team == ca->team ) { + message = "^1WAS KILLED BY TEAMMATE^7"; + message2 = ""; + } +// jpw + + if ( message ) { + message = CG_TranslateString( message ); + if ( message2 ) { + message2 = CG_TranslateString( message2 ); + } + CG_Printf( "[cgnotify]%s %s %s%s\n", targetName, message, attackerName, message2 ); + return; + } + } + + // we don't know what it was +// JPW NERVE added mod check for machinegun (prolly mortar here too) + switch ( mod ) { + case MOD_MACHINEGUN: + CG_Printf( "[cgnotify]%s was riddled by machinegun fire\n",targetName ); + break; + default: + CG_Printf( "[cgnotify]%s died.\n", targetName ); + break; + } +// jpw +} + +//========================================================================== + +/* +=============== +CG_UseItem +=============== +*/ +static void CG_UseItem( centity_t *cent ) { +/* + int itemNum; + gitem_t *item; + entityState_t *es; + + es = ¢->currentState; + + // itemNum = es->event - EV_USE_ITEM0; + // JCash bluesnews reported fix + itemNum = (es->event &~EV_EVENT_BITS) - EV_USE_ITEM0; + + if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { + itemNum = 0; + } + + // print a message if the local player + if ( es->number == cg.snap->ps.clientNum ) { + if ( !itemNum ) { + CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + } else { + item = BG_FindItemForHoldable( itemNum ); + + if(item) { + cg.holdableSelectTime = cg.time; // show remaining items + + switch(itemNum) { + case HI_BOOK1: + case HI_BOOK2: + case HI_BOOK3: + break; + case HI_WINE: + CG_CenterPrint( "You drank the wine", SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + break; + default: + CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + break; + } + } + } + } + + switch ( itemNum ) { + default: + case HI_NONE: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + break; + + case HI_MEDKIT: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.medkitSound ); + break; + + case HI_WINE: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.wineSound ); + break; + } +*/ +} + +// from cg_weapons.c +extern int CG_WeaponIndex( int weapnum, int *bank, int *cycle ); + + +/* +================ +CG_ItemPickup + +A new item was picked up this frame +================ +*/ +static void CG_ItemPickup( int itemNum ) { + int itemid; + int wpbank_cur, wpbank_pickup; + + itemid = bg_itemlist[itemNum].giTag; + + cg.itemPickup = itemNum; + cg.itemPickupTime = cg.time; + cg.itemPickupBlendTime = cg.time; + + // see if it should be the grabbed weapon + if ( bg_itemlist[itemNum].giType == IT_WEAPON ) { + + if ( cg_autoswitch.integer && cg.predictedPlayerState.weaponstate != WEAPON_RELOADING ) { + + // 0 - "Off" + // 1 - "Always Switch" + // 2 - "If New" + // 3 - "If Better" + // 4 - "New or Better" + + // don't ever autoswitch to secondary fire weapons + if ( itemid != WP_SNIPERRIFLE && itemid != WP_SNOOPERSCOPE && itemid != WP_VENOM_FULL && itemid != WP_FG42SCOPE && itemid != WP_AMMO ) { //----(SA) modified + + // no weap currently selected, always just select the new one + if ( !cg.weaponSelect ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = itemid; + } + // 1 - always switch to new weap (Q3A default) + else if ( cg_autoswitch.integer == 1 ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = itemid; + } else { + + // 2 - switch to weap if it's not already in the player's inventory (Wolf default) + // 4 - both 2 and 3 + + // FIXME: this works fine for predicted pickups (when you walk over the weapon), but not for + // manual pickups (activate item) + if ( cg_autoswitch.integer == 2 || cg_autoswitch.integer == 4 ) { + if ( !COM_BitCheck( cg.snap->ps.weapons, itemid ) ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = itemid; + } + } // end 2 + + // 3 - switch to weap if it's in a bank greater than the current weap + // 4 - both 2 and 3 + if ( cg_autoswitch.integer == 3 || cg_autoswitch.integer == 4 ) { + // switch away only if a primary weapon is selected (read: don't switch away if current weap is a secondary mode) + if ( CG_WeaponIndex( cg.weaponSelect, &wpbank_cur, NULL ) ) { + if ( CG_WeaponIndex( itemid, &wpbank_pickup, NULL ) ) { + if ( wpbank_pickup > wpbank_cur ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = itemid; + } + } + } + } // end 3 + + } // end cg_autoswitch.integer != 1 + + } // end itemid != WP_SNIPERRIFLE && ... + + } // end cg_autoswitch.integer + + } // end bg_itemlist[itemNum].giType == IT_WEAPON + + + //if (bg_itemlist[itemNum].giType == IT_HOLDABLE ) { + //cg.holdableSelectTime = cg.time; // show holdables when a new one is picked up + //cg.holdableSelect = itemid; // and select the new one + //} + +} + + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +typedef struct { + char *tag; + int refEntOfs; + int anim; +} painAnimForTag_t; + +#define PEFOFS( x ) ( (int)&( ( (playerEntity_t *)0 )->x ) ) + +void CG_PainEvent( centity_t *cent, int health, qboolean crouching ) { + char *snd; + + #define STUNNED_ANIM BOTH_PAIN8 + painAnimForTag_t tagAnims[] = { + {"tag_head", PEFOFS( torsoRefEnt ), BOTH_PAIN1}, + {"tag_chest", PEFOFS( torsoRefEnt ), BOTH_PAIN2}, + {"tag_groin", PEFOFS( legsRefEnt ), BOTH_PAIN3}, + {"tag_armright",PEFOFS( torsoRefEnt ), BOTH_PAIN4}, + {"tag_armleft", PEFOFS( torsoRefEnt ), BOTH_PAIN5}, + {"tag_legright",PEFOFS( legsRefEnt ), BOTH_PAIN6}, + {"tag_legleft", PEFOFS( legsRefEnt ), BOTH_PAIN7}, + {NULL,0,0}, + }; + vec3_t tagOrg; + int tagIndex, bestTag, oldPainAnim; + float bestDist, dist; + + // Rafael + if ( cent->currentState.aiChar && cgs.gametype == GT_SINGLE_PLAYER ) { + + if ( cent->pe.painTime > cg.time - 1000 ) { + oldPainAnim = cent->pe.painAnimTorso; + } else { + oldPainAnim = -1; + } + + // Ridah, health is actually time to spend playing the animation + cent->pe.painTime = cg.time; + cent->pe.painDuration = health << 4; + cent->pe.painDirection ^= 1; + cent->pe.painAnimLegs = -1; + cent->pe.painAnimTorso = -1; + + if ( VectorLength( cent->currentState.origin2 ) > 1 ) { + // find a correct animation to play, based on the body orientation at previous frame + for ( tagIndex = 0, bestDist = 0, bestTag = -1; tagAnims[tagIndex].tag; tagIndex++ ) { + if ( oldPainAnim >= 0 && tagAnims[tagIndex].anim == oldPainAnim ) { + continue; + } + // grab the tag with this name + if ( CG_GetOriginForTag( cent, ( refEntity_t * )( ( (byte *)¢->pe ) + tagAnims[tagIndex].refEntOfs ), tagAnims[tagIndex].tag, 0, tagOrg, NULL ) >= 0 ) { + dist = VectorDistance( tagOrg, cent->currentState.origin2 ); + if ( !bestDist || dist < bestDist ) { + bestTag = tagIndex; + bestDist = dist; + } + } + } + + if ( bestTag >= 0 ) { + if ( !crouching ) { + cent->pe.painAnimLegs = tagAnims[bestTag].anim; + } + cent->pe.painAnimTorso = tagAnims[bestTag].anim; + } + } + + if ( cent->pe.painAnimTorso < 0 && cent->pe.painDuration > 1000 ) { // stunned + if ( !crouching ) { + cent->pe.painAnimLegs = STUNNED_ANIM; + } + cent->pe.painAnimTorso = STUNNED_ANIM; + } + + if ( cent->pe.painAnimTorso < 0 ) { + // pick a random anim + for ( tagIndex = 0; tagAnims[tagIndex].tag; tagIndex++ ) {}; + bestTag = rand() % tagIndex; + if ( !crouching ) { + cent->pe.painAnimLegs = tagAnims[bestTag].anim; + } + cent->pe.painAnimTorso = tagAnims[bestTag].anim; + } + + // adjust the animation speed + { + animation_t *anim; + clientInfo_t *ci; + + ci = &cgs.clientinfo[ cent->currentState.number ]; + anim = &ci->modelInfo->animations[ cent->pe.painAnimTorso ]; + + cent->pe.animSpeed = ( anim->frameLerp * anim->numFrames ) / (float)cent->pe.painDuration; + } + + return; + } + + // don't do more than two pain sounds a second + if ( cg.time - cent->pe.painTime < 500 ) { + return; + } + + if ( health < 25 ) { + snd = "*pain25_1.wav"; + } else if ( health < 50 ) { + snd = "*pain50_1.wav"; + } else if ( health < 75 ) { + snd = "*pain75_1.wav"; + } else { + snd = "*pain100_1.wav"; + } + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, snd ) ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg.time; + cent->pe.painDirection ^= 1; +} + + + + + +/* +============== +CG_Explode + + + if (cent->currentState.angles2[0] || cent->currentState.angles2[1] || cent->currentState.angles2[2]) + +============== +*/ + +#define POSSIBLE_PIECES 6 + + + + +void CG_Explodef( vec3_t origin, vec3_t dir, int mass, int type, qhandle_t sound, int forceLowGrav, qhandle_t shader ); + +/* +============== +CG_Explode + the old cent-based explode calls will still work with this pass-through +============== +*/ +void CG_Explode( centity_t *cent, vec3_t origin, vec3_t dir, qhandle_t shader ) { + + qhandle_t inheritmodel = 0; + + // inherit shader + // (SA) FIXME: do this at spawn time rather than explode time so any new necessary shaders are created earlier + if ( cent->currentState.eFlags & EF_INHERITSHADER ) { + if ( !shader ) { +// inheritmodel = cent->currentState.modelindex; + inheritmodel = cgs.inlineDrawModel[cent->currentState.modelindex]; // okay, this should be better. + if ( inheritmodel ) { + shader = trap_R_GetShaderFromModel( inheritmodel, 0, 0 ); + } + } + } + + + CG_Explodef( origin, + dir, + cent->currentState.density, // mass + cent->currentState.frame, // type + cent->currentState.dl_intensity, // sound + cent->currentState.weapon, // forceLowGrav + shader + ); + +} + + +/* +============== +CG_Explodef + made this more generic for spawning hits and breaks without needing a *cent +============== +*/ +void CG_Explodef( vec3_t origin, vec3_t dir, int mass, int type, qhandle_t sound, int forceLowGrav, qhandle_t shader ) { + int i; + localEntity_t *le; + refEntity_t *re; + int howmany, total; + int pieces[6]; // how many of each piece + qhandle_t modelshader = 0; + float materialmul = 1; // multiplier for different types + + memset( &pieces, 0, sizeof( pieces ) ); + + pieces[5] = (int)( mass / 250.0f ); + pieces[4] = (int)( mass / 76.0f ); + pieces[3] = (int)( mass / 37.0f ); // so 2 per 75 + pieces[2] = (int)( mass / 15.0f ); + pieces[1] = (int)( mass / 10.0f ); + pieces[0] = (int)( mass / 5.0f ); + + if ( pieces[0] > 20 ) { + pieces[0] = 20; // cap some of the smaller bits so they don't get out of control + } + if ( pieces[1] > 15 ) { + pieces[1] = 15; + } + if ( pieces[2] > 10 ) { + pieces[2] = 10; + } + + if ( type == 0 ) { // cap wood even more since it's often grouped, and the small splinters can add up + if ( pieces[0] > 10 ) { + pieces[0] = 10; + } + if ( pieces[1] > 10 ) { + pieces[1] = 10; + } + if ( pieces[2] > 10 ) { + pieces[2] = 10; + } + } + + total = pieces[5] + pieces[4] + pieces[3] + pieces[2] + pieces[1] + pieces[0]; + + if ( sound ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.gameSounds[sound] ); + } + + if ( shader ) { // shader passed in to use + modelshader = shader; + } + + for ( i = 0; i < POSSIBLE_PIECES; i++ ) { + leBounceSoundType_t snd = LEBS_NONE; + int hmodel = 0; + float scale; + int endtime; + for ( howmany = 0; howmany < pieces[i]; howmany++ ) { + + scale = 1.0f; + endtime = 0; // set endtime offset for faster/slower fadeouts + + switch ( type ) { + case 0: // "wood" + snd = LEBS_WOOD; + hmodel = cgs.media.debWood[i]; + + if ( i == 0 ) { + scale = 0.5f; + } else if ( i == 1 ) { + scale = 0.6f; + } else if ( i == 2 ) { + scale = 0.7f; + } else if ( i == 3 ) { + scale = 0.5f; + } + // else + // scale = cg_forceModel.value; + // else goto pass; + + if ( i < 3 ) { + endtime = -3000; // small bits live 3 sec shorter than normal + } + break; + + case 1: // "glass" + snd = LEBS_NONE; + if ( i == 5 ) { + hmodel = cgs.media.shardGlass1; + } else if ( i == 4 ) { + hmodel = cgs.media.shardGlass2; + } else if ( i == 2 ) { + hmodel = cgs.media.shardGlass2; + } else if ( i == 1 ) { + hmodel = cgs.media.shardGlass2; + scale = 0.5f; + } else {goto pass;} + break; + + case 2: // "metal" + snd = LEBS_BRASS; + if ( i == 5 ) { + hmodel = cgs.media.shardMetal1; + } else if ( i == 4 ) { + hmodel = cgs.media.shardMetal2; + } else if ( i == 2 ) { + hmodel = cgs.media.shardMetal2; + } else if ( i == 1 ) { + hmodel = cgs.media.shardMetal2; + scale = 0.5f; + } else {goto pass;} + break; + + case 3: // "gibs" + snd = LEBS_BLOOD; + if ( i == 5 ) { + hmodel = cgs.media.gibIntestine; + } else if ( i == 4 ) { + hmodel = cgs.media.gibLeg; + } else if ( i == 2 ) { + hmodel = cgs.media.gibChest; + } else { goto pass;} + break; + + case 4: // "brick" + snd = LEBS_ROCK; + hmodel = cgs.media.debBlock[i]; + break; + + case 5: // "rock" + snd = LEBS_ROCK; + if ( i == 5 ) { + hmodel = cgs.media.debRock[2]; // temporarily use the next smallest rock piece + } else if ( i == 4 ) { + hmodel = cgs.media.debRock[2]; + } else if ( i == 3 ) { + hmodel = cgs.media.debRock[1]; + } else if ( i == 2 ) { + hmodel = cgs.media.debRock[0]; + } else if ( i == 1 ) { + hmodel = cgs.media.debBlock[1]; // temporarily use the small block pieces + } else { hmodel = cgs.media.debBlock[0]; // temporarily use the small block pieces + } + if ( i <= 2 ) { + endtime = -2000; // small bits live 2 sec shorter than normal + } + break; + + case 6: // "fabric" + if ( i == 5 ) { + hmodel = cgs.media.debFabric[0]; + } else if ( i == 4 ) { + hmodel = cgs.media.debFabric[1]; + } else if ( i == 2 ) { + hmodel = cgs.media.debFabric[2]; + } else if ( i == 1 ) { + hmodel = cgs.media.debFabric[2]; + scale = 0.5; + } else {goto pass; // (only do 5, 4, 2 and 1) + } + break; + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + + le->endTime = ( le->startTime + 5000 + random() * 5000 ) + endtime; + + // as it turns out, i'm not sure if setting the re->axis here will actually do anything + // AxisClear(re->axis); + // re->axis[0][0] = + // re->axis[1][1] = + // re->axis[2][2] = scale; + // + // if(scale != 1.0) + // re->nonNormalizedAxes = qtrue; + + le->sizeScale = scale; + + if ( type == 1 ) { // glass + // Rafael added this because glass looks funky when it fades out + // TBD: need to look into this so that they fade out correctly + re->fadeStartTime = le->endTime; + re->fadeEndTime = le->endTime; + } else { + re->fadeStartTime = le->endTime - 4000; + re->fadeEndTime = le->endTime; + } + + + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + le->leFlags = LEF_TUMBLE; + le->leMarkType = 0; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + + le->leBounceSoundType = snd; + re->hModel = hmodel; + + // inherit shader + if ( modelshader ) { + re->customShader = modelshader; + } + + re->radius = 1000; + + // trying to make this a little more interesting + if ( type == 6 ) { // "fabric" + le->pos.trType = TR_GRAVITY_FLOAT; // the fabric stuff will change to use something that looks better + } else { + if ( !forceLowGrav && rand() & 1 ) { // if low gravity is not forced and die roll goes our way use regular grav + le->pos.trType = TR_GRAVITY; + } else { + le->pos.trType = TR_GRAVITY_LOW; + } + } + + switch ( type ) { + case 6: // fabric + le->bounceFactor = 0.0; + materialmul = 0.3; // rotation speed + break; + default: + le->bounceFactor = 0.4; + break; + } + + + // rotation + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand() & 31; + le->angles.trBase[1] = rand() & 31; + le->angles.trBase[2] = rand() & 31; + le->angles.trDelta[0] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul; + le->angles.trDelta[1] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul; + le->angles.trDelta[2] = ( ( 100 + ( rand() & 500 ) ) - 300 ) * materialmul; + + + // if(type == 6) // fabric + // materialmul = 1; // translation speed + + + VectorCopy( origin, le->pos.trBase ); + VectorNormalize( dir ); + le->pos.trTime = cg.time; + + // (SA) hoping that was just intended to represent randomness + // if (cent->currentState.angles2[0] || cent->currentState.angles2[1] || cent->currentState.angles2[2]) + if ( le->angles.trBase[0] == 1 || le->angles.trBase[1] == 1 || le->angles.trBase[2] == 1 ) { + le->pos.trType = TR_GRAVITY; + VectorScale( dir, 10 * 8, le->pos.trDelta ); + le->pos.trDelta[0] += ( ( random() * 100 ) - 50 ); + le->pos.trDelta[1] += ( ( random() * 100 ) - 50 ); + le->pos.trDelta[2] = ( random() * 200 ) + 200; + + } else { + // location + VectorScale( dir, 200 + mass, le->pos.trDelta ); + le->pos.trDelta[0] += ( ( random() * 100 ) - 50 ); + le->pos.trDelta[1] += ( ( random() * 100 ) - 50 ); + + if ( dir[2] ) { + le->pos.trDelta[2] = random() * 200 * materialmul; // randomize sort of a lot so they don't all land together + } else { + le->pos.trDelta[2] = random() * 20; + } + } + } +pass: + continue; + } + +} + + +/* +============== +CG_Effect + Quake ed -> target_effect (0 .5 .8) (-6 -6 -6) (6 6 6) fire explode smoke debris gore lowgrav +============== +*/ +void CG_Effect( centity_t *cent, vec3_t origin, vec3_t dir ) { + localEntity_t *le; + refEntity_t *re; +// int howmany; + int mass; +// int large, small; + + VectorSet( dir, 0, 0, 1 ); // straight up. + + mass = cent->currentState.density; + +// 1 large per 100, 1 small per 24 +// large = (int)(mass / 100); +// small = (int)(mass / 24) + 1; + + if ( cent->currentState.eventParm & 1 ) { // fire + } + + // (SA) right now force smoke on any explosions +// if(cent->currentState.eventParm & 4) // smoke + if ( cent->currentState.eventParm & 6 ) { + int i, j; + vec3_t sprVel, sprOrg; + // explosion sprite animation + VectorScale( dir, 16, sprVel ); + for ( i = 0; i < 5; i++ ) { + for ( j = 0; j < 3; j++ ) + sprOrg[j] = origin[j] + 64 * dir[j] + 24 * crandom(); + sprVel[2] += rand() % 50; +// CG_ParticleExplosion( 2, sprOrg, sprVel, 1000+rand()%250, 20, 40+rand()%60 ); + CG_ParticleExplosion( "blacksmokeanim", sprOrg, sprVel, 3500 + rand() % 250, 10, 250 + rand() % 60 ); // JPW NERVE was smokeanimb + } + } + + + if ( cent->currentState.eventParm & 2 ) { // explode + vec3_t sprVel, sprOrg; + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.sfx_rockexp ); + + // new explode (from rl) + VectorMA( origin, 16, dir, sprOrg ); + VectorScale( dir, 100, sprVel ); + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 500, 20, 160 ); + //CG_ParticleExplosion( "blueexp", sprOrg, sprVel, 1200, 9, 300 ); + + // (SA) this is done only if the level designer has it marked in the entity. + // (see "cent->currentState.eventParm & 64" below) + + // RF, throw some debris +// CG_AddDebris( origin, dir, +// 280, // speed +// 1400, // duration +// // 15 + rand()%5 ); // count +// 7 + rand()%2 ); // count + + CG_ImpactMark( cgs.media.burnMarkShader, origin, dir, random() * 360, 1,1,1,1, qfalse, 64, qfalse, 0xffffffff ); + } + + + if ( cent->currentState.eventParm & 8 ) { // rubble + // share the cg_explode code with func_explosives + const char *s; + qhandle_t sh = 0; // shader handle + + vec3_t newdir = {0, 0, 0}; + + if ( cent->currentState.angles2[0] || cent->currentState.angles2[1] || cent->currentState.angles2[2] ) { + VectorCopy( cent->currentState.angles2, newdir ); + } + + s = CG_ConfigString( CS_TARGETEFFECT ); // see if ent has a shader specified + if ( s && strlen( s ) > 0 ) { + sh = trap_R_RegisterShader( va( "textures/%s", s ) ); // FIXME: don't do this here. only for testing + + } + cent->currentState.eFlags &= ~EF_INHERITSHADER; // don't try to inherit shader + cent->currentState.dl_intensity = 0; // no sound + CG_Explode( cent, origin, newdir, sh ); +//void CG_Explodef(vec3_t origin, vec3_t dir, int mass, int type, qhandle_t sound, int forceLowGrav, qhandle_t shader); + } + + + if ( cent->currentState.eventParm & 16 ) { // gore + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 5000 + random() * 3000; +//----(SA) fading out + re->fadeStartTime = le->endTime - 4000; + re->fadeEndTime = le->endTime; +//----(SA) end + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + // re->hModel = hModel; + re->hModel = cgs.media.gibIntestine; + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + + // VectorCopy( velocity, le->pos.trDelta ); + VectorNormalize( dir ); + VectorMA( dir, 200, dir, le->pos.trDelta ); + + le->pos.trTime = cg.time; + + le->bounceFactor = 0.3; + + le->leBounceSoundType = LEBS_BLOOD; + le->leMarkType = LEMT_BLOOD; + } + + + if ( cent->currentState.eventParm & 64 ) { // debris trails (the black strip that Ryan did) + CG_AddDebris( origin, dir, + 280, // speed + 1400, // duration + // 15 + rand()%5 ); // count + 7 + rand() % 2 ); // count + } +} + + + + + + +/* +CG_Shard + + We should keep this separate since there will be considerable differences + in the physical properties of shard vrs debris. not to mention the fact + there is no way we can quantify what type of effects the designers will + potentially desire. If it is still possible to merge the functionality of + cg_shard into cg_explode at a latter time I would have no problem with that + but for now I want to keep it separate +*/ +void CG_Shard( centity_t *cent, vec3_t origin, vec3_t dir ) { + localEntity_t *le; + refEntity_t *re; + int type; + int howmany; + int i; + int rval; + + qboolean isflyingdebris = qfalse; + + type = cent->currentState.density; + howmany = cent->currentState.frame; + + for ( i = 0; i < howmany; i++ ) + { + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 5000 + random() * 5000; + +//----(SA) fading out + re->fadeStartTime = le->endTime - 1000; + re->fadeEndTime = le->endTime; +//----(SA) end + + if ( type == 999 ) { + le->startTime = cg.time; + le->endTime = le->startTime + 100; + re->fadeStartTime = le->endTime - 100; + re->fadeEndTime = le->endTime; + type = 1; + + isflyingdebris = qtrue; + } + + + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + le->leFlags = LEF_TUMBLE; + le->bounceFactor = 0.4; + // le->leBounceSoundType = LEBS_WOOD; + le->leMarkType = 0; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + + rval = rand() % 2; + + if ( type == 0 ) { // glass + if ( rval ) { + re->hModel = cgs.media.shardGlass1; + } else { + re->hModel = cgs.media.shardGlass2; + } + } else if ( type == 1 ) { // wood + if ( rval ) { + re->hModel = cgs.media.shardWood1; + } else { + re->hModel = cgs.media.shardWood2; + } + } else if ( type == 2 ) { // metal + if ( rval ) { + re->hModel = cgs.media.shardMetal1; + } else { + re->hModel = cgs.media.shardMetal2; + } + } else if ( type == 3 ) { // ceramic + if ( rval ) { + re->hModel = cgs.media.shardCeramic1; + } else { + re->hModel = cgs.media.shardCeramic2; + } + } else if ( type == 4 ) { // rubble + rval = rand() % 3; + + if ( rval == 1 ) { + re->hModel = cgs.media.shardRubble1; + } else if ( rval == 2 ) { + re->hModel = cgs.media.shardRubble2; + } else { + re->hModel = cgs.media.shardRubble3; + } + + } else { + CG_Printf( "CG_Debris has an unknown type\n" ); + } + + // location + if ( isflyingdebris ) { + le->pos.trType = TR_GRAVITY_LOW; + } else { + le->pos.trType = TR_GRAVITY; + } + + VectorCopy( origin, le->pos.trBase ); + VectorNormalize( dir ); + VectorScale( dir, 10 * howmany, le->pos.trDelta ); + le->pos.trTime = cg.time; + le->pos.trDelta[0] += ( ( random() * 100 ) - 50 ); + le->pos.trDelta[1] += ( ( random() * 100 ) - 50 ); + if ( type ) { + le->pos.trDelta[2] = ( random() * 200 ) + 100; // randomize sort of a lot so they don't all land together + } else { // glass + le->pos.trDelta[2] = ( random() * 100 ) + 50; // randomize sort of a lot so they don't all land together + + } + // rotation + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand() & 31; + le->angles.trBase[1] = rand() & 31; + le->angles.trBase[2] = rand() & 31; + le->angles.trDelta[0] = ( 100 + ( rand() & 500 ) ) - 300; + le->angles.trDelta[1] = ( 100 + ( rand() & 500 ) ) - 300; + le->angles.trDelta[2] = ( 100 + ( rand() & 500 ) ) - 300; + + } + +} + + +void CG_ShardJunk( centity_t *cent, vec3_t origin, vec3_t dir ) { + localEntity_t *le; + refEntity_t *re; + int type; + + type = cent->currentState.density; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 5000 + random() * 5000; + + re->fadeStartTime = le->endTime - 1000; + re->fadeEndTime = le->endTime; + + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + le->leFlags = LEF_TUMBLE; + le->bounceFactor = 0.4; + le->leMarkType = 0; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + + re->hModel = cgs.media.shardJunk[rand() % MAX_LOCKER_DEBRIS]; + + le->pos.trType = TR_GRAVITY; + + VectorCopy( origin, le->pos.trBase ); + VectorNormalize( dir ); + VectorScale( dir, 10 * 8, le->pos.trDelta ); + le->pos.trTime = cg.time; + le->pos.trDelta[0] += ( ( random() * 100 ) - 50 ); + le->pos.trDelta[1] += ( ( random() * 100 ) - 50 ); + + le->pos.trDelta[2] = ( random() * 100 ) + 50; // randomize sort of a lot so they don't all land together + + // rotation + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + //le->angles.trBase[0] = rand()&31; + //le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand() & 31; + + //le->angles.trDelta[0] = (100 + (rand()&500)) - 300; + //le->angles.trDelta[1] = (100 + (rand()&500)) - 300; + le->angles.trDelta[2] = ( 100 + ( rand() & 500 ) ) - 300; + +} + +void CG_BatDeath( centity_t *cent ) { + CG_ParticleExplosion( "blood", cent->lerpOrigin, vec3_origin, 400, 20, 30 ); +} + + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +extern void CG_AddBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ); +// JPW NERVE +void CG_MachineGunEjectBrass( centity_t *cent ); +void CG_MachineGunEjectBrassNew( centity_t *cent ); +// jpw +#define DEBUGNAME( x ) if ( cg_debugEvents.integer ) {CG_Printf( x "\n" );} +void CG_EntityEvent( centity_t *cent, vec3_t position ) { + entityState_t *es; + int event; + vec3_t dir; + const char *s; + int clientNum; + clientInfo_t *ci; + char tempStr[MAX_QPATH]; + +// JPW NERVE copied here for mg42 SFX event + vec3_t porg, gorg, norm; // player/gun origin + float gdist; +// jpw + + static int footstepcnt = 0; + static int splashfootstepcnt = 0; + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + + if ( cg_debugEvents.integer ) { + CG_Printf( "ent:%3i event:%3i ", es->number, event ); + } + + if ( !event ) { + DEBUGNAME( "ZEROEVENT" ); + return; + } + + clientNum = es->clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + if ( cgs.gametype == GT_SINGLE_PLAYER && !ci->modelInfo ) { // not ready yet? + return; + } + + switch ( event ) { + // + // movement generated events + // + case EV_FOOTSTEP: + DEBUGNAME( "EV_FOOTSTEP" ); + if ( cg_footsteps.integer ) { + if ( cent->currentState.aiChar == AICHAR_ELITEGUARD ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_ELITE_STEP ][footstepcnt] ); + } else if ( cent->currentState.aiChar == AICHAR_ZOMBIE ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_ZOMBIE_STEP ][footstepcnt] ); + } else if ( cent->currentState.aiChar == AICHAR_LOPER ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_LOPER_STEP ][footstepcnt] ); + } else if ( cent->currentState.aiChar >= AICHAR_STIMSOLDIER1 && cent->currentState.aiChar <= AICHAR_STIMSOLDIER3 ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SUPERSOLDIER_STEP ][footstepcnt] ); + } else { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ ci->modelInfo->footsteps ][footstepcnt] ); + } + } + break; + case EV_FOOTSTEP_METAL: + DEBUGNAME( "EV_FOOTSTEP_METAL" ); + if ( cg_footsteps.integer ) { + if ( cent->currentState.aiChar == AICHAR_ELITEGUARD ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_ELITE_METAL ][footstepcnt] ); + } else if ( cent->currentState.aiChar == AICHAR_LOPER ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_LOPER_METAL ][footstepcnt] ); + } else if ( cent->currentState.aiChar >= AICHAR_STIMSOLDIER1 && cent->currentState.aiChar <= AICHAR_STIMSOLDIER3 ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SUPERSOLDIER_METAL ][footstepcnt] ); + } else { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_METAL ][footstepcnt] ); + } + } + break; + case EV_FOOTSTEP_WOOD: + DEBUGNAME( "EV_FOOTSTEP_WOOD" ); + if ( cg_footsteps.integer ) { + if ( cent->currentState.aiChar == AICHAR_ELITEGUARD ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_ELITE_WOOD ][footstepcnt] ); + } else if ( cent->currentState.aiChar == AICHAR_ZOMBIE ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_ZOMBIE_WOOD ][footstepcnt] ); + } else if ( cent->currentState.aiChar == AICHAR_LOPER ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_LOPER_WOOD ][footstepcnt] ); + } else if ( cent->currentState.aiChar >= AICHAR_STIMSOLDIER1 && cent->currentState.aiChar <= AICHAR_STIMSOLDIER3 ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SUPERSOLDIER_WOOD ][footstepcnt] ); + } else { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_WOOD ][footstepcnt] ); + } + + } + break; + case EV_FOOTSTEP_GRASS: + DEBUGNAME( "EV_FOOTSTEP_GRASS" ); + if ( cg_footsteps.integer ) { + //if (cent->currentState.aiChar == AICHAR_ELITEGUARD) + // trap_S_StartSound (NULL, es->number, CHAN_BODY, + // cgs.media.footsteps[ FOOTSTEP_ELITE_STEP ][footstepcnt] ); + //else + if ( cent->currentState.aiChar >= AICHAR_STIMSOLDIER1 && cent->currentState.aiChar <= AICHAR_STIMSOLDIER3 ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SUPERSOLDIER_GRASS ][footstepcnt] ); + } else { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_GRASS ][footstepcnt] ); + } + } + break; + case EV_FOOTSTEP_GRAVEL: + DEBUGNAME( "EV_FOOTSTEP_GRAVEL" ); + if ( cg_footsteps.integer ) { + if ( cent->currentState.aiChar == AICHAR_ELITEGUARD ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_ELITE_GRAVEL ][footstepcnt] ); + } else if ( cent->currentState.aiChar == AICHAR_ZOMBIE ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_ZOMBIE_GRAVEL ][footstepcnt] ); + } else if ( cent->currentState.aiChar >= AICHAR_STIMSOLDIER1 && cent->currentState.aiChar <= AICHAR_STIMSOLDIER3 ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SUPERSOLDIER_GRAVEL][footstepcnt] ); + } else { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_GRAVEL ][footstepcnt] ); + } + } + break; + + case EV_FOOTSTEP_ROOF: // tile sound + DEBUGNAME( "EV_FOOTSTEP_ROOF" ); + if ( cg_footsteps.integer ) { + //if (cent->currentState.aiChar == AICHAR_ELITEGUARD) + // trap_S_StartSound (NULL, es->number, CHAN_BODY, + // cgs.media.footsteps[ FOOTSTEP_ELITE_ROOF ][footstepcnt] ); + //else + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_ROOF ][footstepcnt] ); + } + break; + case EV_FOOTSTEP_SNOW: + DEBUGNAME( "EV_FOOTSTEP_SNOW" ); + if ( cg_footsteps.integer ) { + //if (cent->currentState.aiChar == AICHAR_ELITEGUARD) + // trap_S_StartSound (NULL, es->number, CHAN_BODY, + // cgs.media.footsteps[ FOOTSTEP_ELITE_STEP ][footstepcnt] ); + //else + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SNOW ][footstepcnt] ); + } + break; + +//----(SA) added + case EV_FOOTSTEP_CARPET: + DEBUGNAME( "EV_FOOTSTEP_CARPET" ); + if ( cg_footsteps.integer ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_CARPET ][footstepcnt] ); + } + break; + + +//----(SA) end + + case EV_FOOTSPLASH: + DEBUGNAME( "EV_FOOTSPLASH" ); + if ( cg_footsteps.integer ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][splashfootstepcnt] ); + } + break; + case EV_FOOTWADE: + DEBUGNAME( "EV_FOOTWADE" ); + if ( cg_footsteps.integer ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][splashfootstepcnt] ); + } + break; + case EV_SWIM: + DEBUGNAME( "EV_SWIM" ); + if ( cg_footsteps.integer ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][footstepcnt] ); + } + break; + + + case EV_FALL_SHORT: + DEBUGNAME( "EV_FALL_SHORT" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; +/* + case EV_FALL_MEDIUM: + DEBUGNAME("EV_FALL_MEDIUM"); + // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + case EV_FALL_FAR: + DEBUGNAME("EV_FALL_FAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; +*/ + case EV_FALL_DMG_10: + DEBUGNAME( "EV_FALL_DMG_10" ); + // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "sound/multiplayer/land_hurt.wav" ) ); // JPW NERVE + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + case EV_FALL_DMG_15: + DEBUGNAME( "EV_FALL_DMG_15" ); + // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "sound/multiplayer/land_hurt.wav" ) ); // JPW NERVE + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + case EV_FALL_DMG_25: + DEBUGNAME( "EV_FALL_DMG_25" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "sound/multiplayer/land_hurt.wav" ) ); // JPW NERVE + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; +/* + case EV_FALL_DMG_30: + DEBUGNAME("EV_FALL_DMG_30"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; +*/ + case EV_FALL_DMG_50: + DEBUGNAME( "EV_FALL_DMG_50" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "sound/multiplayer/land_hurt.wav" ) ); // JPW NERVE + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; +/* + case EV_FALL_DMG_75: + DEBUGNAME("EV_FALL_DMG_75"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; +*/ + case EV_FALL_NDIE: + DEBUGNAME( "EV_FALL_NDIE" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "sound/multiplayer/land_hurt.wav" ) ); // JPW NERVE + cent->pe.painTime = cg.time; // don't play a pain sound right after this + // splat + break; + + case EV_EXERT1: + DEBUGNAME( "EV_EXERT1" ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*exert1.wav" ) ); + break; + case EV_EXERT2: + DEBUGNAME( "EV_EXERT2" ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*exert2.wav" ) ); + break; + case EV_EXERT3: + DEBUGNAME( "EV_EXERT3" ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*exert3.wav" ) ); + break; + + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + DEBUGNAME( "EV_STEP" ); + { + float oldStep; + int delta; + int step; + + if ( clientNum != cg.predictedPlayerState.clientNum ) { + break; + } + // if we are interpolating, we don't need to smooth steps + if ( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || + cg_nopredict.integer || cg_synchronousClients.integer ) { + break; + } + // check for stepping up before a previous step is completed + delta = cg.time - cg.stepTime; + if ( delta < STEP_TIME ) { + oldStep = cg.stepChange * ( STEP_TIME - delta ) / STEP_TIME; + } else { + oldStep = 0; + } + + // add this amount + step = 4 * ( event - EV_STEP_4 + 1 ); + cg.stepChange = oldStep + step; + if ( cg.stepChange > MAX_STEP_CHANGE ) { + cg.stepChange = MAX_STEP_CHANGE; + } + cg.stepTime = cg.time; + break; + } + + case EV_JUMP_PAD: + DEBUGNAME( "EV_JUMP_PAD" ); + // boing sound at origin, jump sound on player + trap_S_StartSound( cent->lerpOrigin, -1, CHAN_VOICE, cgs.media.jumpPadSound ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + + case EV_JUMP: + DEBUGNAME( "EV_JUMP" ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + case EV_TAUNT: + DEBUGNAME( "EV_TAUNT" ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); + break; + case EV_WATER_TOUCH: + DEBUGNAME( "EV_WATER_TOUCH" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); + break; + case EV_WATER_LEAVE: + DEBUGNAME( "EV_WATER_LEAVE" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); + break; + case EV_WATER_UNDER: + DEBUGNAME( "EV_WATER_UNDER" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); +//----(SA) this fog stuff for underwater is really just a test for feasibility of creating the under-water effect that way. +//----(SA) the related issues of load/savegames, death underwater, etc. are not handled at all. +//----(SA) the actual problem, of course, is doing underwater stuff when the water is very turbulant and you can't simply +//----(SA) do things based on the players head being above/below the water brushes top surface. (since the waves can potentially be /way/ above/below that) + + // DHM - Nerve :: causes problems in multiplayer... + if ( cgs.gametype == GT_SINGLE_PLAYER && clientNum == cg.predictedPlayerState.clientNum ) { +// trap_R_SetFog(FOG_WATER, 0, 400, .1, .1, .1, 111); + trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_WATER, 200, 0, 0, 0, 0 ); + } + break; + case EV_WATER_CLEAR: + DEBUGNAME( "EV_WATER_CLEAR" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + + // DHM - Nerve :: causes problems in multiplayer... + if ( cgs.gametype == GT_SINGLE_PLAYER && clientNum == cg.predictedPlayerState.clientNum ) { + trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP, 400,0,0,0,0 ); + } + break; + + case EV_ITEM_PICKUP: + case EV_ITEM_PICKUP_QUIET: + DEBUGNAME( "EV_ITEM_PICKUP" ); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + if ( event == EV_ITEM_PICKUP ) { // not quiet + // powerups and team items will have a separate global sound, this one + // will be played at prediction time + if ( item->giType == IT_POWERUP || item->giType == IT_TEAM ) { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( "sound/misc/w_pkup.wav" ) ); + } else { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound ) ); + } + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + +//----(SA) draw the HUD items for a sec since this is a special item + if ( item->giType == IT_KEY ) { + cg.itemFadeTime = cg.time + 1000; + } + + } + break; + + case EV_GLOBAL_ITEM_PICKUP: + DEBUGNAME( "EV_GLOBAL_ITEM_PICKUP" ); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + // powerup pickups are global + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound ) ); + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + // + // weapon events + // + case EV_VENOM: + DEBUGNAME( "EV_VENOM" ); + CG_VenomFire( es, qfalse ); + break; + case EV_VENOMFULL: + DEBUGNAME( "EV_VENOMFULL" ); + CG_VenomFire( es, qtrue ); + break; + + case EV_NOITEM: + DEBUGNAME( "EV_NOITEM" ); + if ( es->number == cg.snap->ps.clientNum ) { + CG_HoldableUsedupChange(); + } + break; + + case EV_WEAP_OVERHEAT: + DEBUGNAME( "EV_WEAP_OVERHEAT" ); + + // start weapon idle animation + if ( es->number == cg.snap->ps.clientNum ) { + cg.predictedPlayerState.weapAnim = ( ( cg.predictedPlayerState.weapAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | WEAP_IDLE1; + cent->overheatTime = cg.time; // used to make the barrels smoke when overheated + } + + if ( cg_weapons[es->weapon].overheatSound ) { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cg_weapons[es->weapon].overheatSound ); + } + break; + +// JPW NERVE + case EV_SPINUP: + DEBUGNAME( "EV_SPINUP" ); + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cg_weapons[es->weapon].spinupSound ); + } + break; +// jpw + case EV_EMPTYCLIP: + DEBUGNAME( "EV_EMPTYCLIP" ); + break; + + case EV_FILL_CLIP: + DEBUGNAME( "EV_FILL_CLIP" ); + if ( cg_weapons[es->weapon].reloadSound ) { + trap_S_StartSound( NULL, es->number, CHAN_WEAPON, cg_weapons[es->weapon].reloadSound ); // JPW NERVE following sherman's SP fix, should allow killing reload sound when player dies + } + break; + +// JPW NERVE play a sound when engineer fixes MG42 + case EV_MG42_FIXED: + DEBUGNAME( "EV_MG42_FIXED" ); + trap_S_StartSound( NULL,es->number,CHAN_WEAPON,cg_weapons[WP_MAUSER].reloadSound ); + break; +// jpw + + case EV_NOAMMO: + DEBUGNAME( "EV_NOAMMO" ); + if ( ( es->weapon != WP_GRENADE_LAUNCHER ) && ( es->weapon != WP_GRENADE_PINEAPPLE ) && ( es->weapon != WP_DYNAMITE ) && ( es->weapon != WP_DYNAMITE2 ) ) { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); + } + if ( es->number == cg.snap->ps.clientNum ) { + CG_OutOfAmmoChange(); + } + break; + case EV_CHANGE_WEAPON: + { + + int newweap = 0; + + DEBUGNAME( "EV_CHANGE_WEAPON" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + + // client will get this message if reloading while using an alternate weapon + // client should voluntarily switch back to primary at that point + switch ( es->weapon ) { + case WP_SNOOPERSCOPE: + newweap = WP_GARAND; + break; + case WP_SNIPERRIFLE: + newweap = WP_MAUSER; + break; + case WP_FG42SCOPE: + newweap = WP_FG42; + break; + default: + break; + } + + if ( ( newweap ) && ( cgs.gametype < GT_WOLF ) ) { // NERVE - SMF - we don't want this in multiplayer + CG_FinishWeaponChange( es->weapon, newweap ); + } + + } + break; + + case EV_FIRE_WEAPON_MG42: + //trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, hWeaponSnd ); +// JPW NERVE -- nasty kludge because there's no WP_MG42 struct to hold echosound, so we pull it from GM's predefined globals hweaponSnd & hEchoweaponsnd + VectorCopy( cent->currentState.pos.trBase, gorg ); + VectorCopy( cg.refdef.vieworg, porg ); + VectorSubtract( gorg, porg, norm ); + gdist = VectorNormalize( norm ); + if ( gdist > 512 && gdist < 4096 ) { + VectorMA( cg.refdef.vieworg, 64, norm, gorg ); + trap_S_StartSoundEx( gorg, cent->currentState.number, CHAN_WEAPON, hWeaponEchoSnd, SND_NOCUT ); + } +// jpw + DEBUGNAME( "EV_FIRE_WEAPON" ); + CG_FireWeapon( cent ); + break; + case EV_FIRE_WEAPON: +// JPW NERVE + if ( cg.snap->ps.eFlags & EF_ZOOMING ) { // to stop airstrike sfx + break; + } +// jpw + case EV_FIRE_WEAPONB: + DEBUGNAME( "EV_FIRE_WEAPON" ); + CG_FireWeapon( cent ); + break; + case EV_FIRE_WEAPON_LASTSHOT: + DEBUGNAME( "EV_FIRE_WEAPON_LASTSHOT" ); + CG_FireWeapon( cent ); + break; + +//----(SA) added + case EV_FIRE_QUICKGREN: + // testing. no client side effect yet + break; +//----(SA) end +//----(SA) added + case EV_NOFIRE_UNDERWATER: + DEBUGNAME( "EV_NOFIRE_UNDERWATER" ); + if ( cgs.media.noFireUnderwater ) { + trap_S_StartSound( NULL, es->number, CHAN_WEAPON, cgs.media.noFireUnderwater ); + } + break; +//----(SA) end + case EV_USE_ITEM0: + DEBUGNAME( "EV_USE_ITEM0" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM1: + DEBUGNAME( "EV_USE_ITEM1" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM2: + DEBUGNAME( "EV_USE_ITEM2" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM3: + DEBUGNAME( "EV_USE_ITEM3" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM4: + DEBUGNAME( "EV_USE_ITEM4" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM5: + DEBUGNAME( "EV_USE_ITEM5" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM6: + DEBUGNAME( "EV_USE_ITEM6" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM7: + DEBUGNAME( "EV_USE_ITEM7" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM8: + DEBUGNAME( "EV_USE_ITEM8" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM9: + DEBUGNAME( "EV_USE_ITEM9" ); + CG_UseItem( cent ); + break; +// JPW NERVE -- this looks reasonable + case EV_TESTID1: + cg_fxflags |= 2; + break; + case EV_TESTID2: + cg_fxflags |= 1; + break; + case EV_ENDTEST: + cg_fxflags = 0; + break; +// jpw + case EV_USE_ITEM10: + DEBUGNAME( "EV_USE_ITEM10" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM11: + DEBUGNAME( "EV_USE_ITEM11" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM12: + DEBUGNAME( "EV_USE_ITEM12" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM13: + DEBUGNAME( "EV_USE_ITEM13" ); + CG_UseItem( cent ); + break; + case EV_USE_ITEM14: + DEBUGNAME( "EV_USE_ITEM14" ); + CG_UseItem( cent ); + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + DEBUGNAME( "EV_PLAYER_TELEPORT_IN" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + CG_SpawnEffect( position ); + break; + + case EV_PLAYER_TELEPORT_OUT: + DEBUGNAME( "EV_PLAYER_TELEPORT_OUT" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + CG_SpawnEffect( position ); + break; + + case EV_ITEM_POP: + DEBUGNAME( "EV_ITEM_POP" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + case EV_ITEM_RESPAWN: + DEBUGNAME( "EV_ITEM_RESPAWN" ); + cent->miscTime = cg.time; // scale up from this + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + + case EV_GRENADE_BOUNCE: + DEBUGNAME( "EV_GRENADE_BOUNCE" ); + + // DYNAMITE + if ( es->weapon == WP_DYNAMITE || es->weapon == WP_DYNAMITE2 ) { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.dynamitebounce1 ); + } else { + // GRENADES + if ( rand() & 1 ) { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.grenadebounce1 ); + } else { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.grenadebounce2 ); + } + } + break; + + case EV_FLAMEBARREL_BOUNCE: + DEBUGNAME( "EV_FLAMEBARREL_BOUNCE" ); + if ( rand() & 1 ) { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.fbarrelexp1 ); + } else { + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.fbarrelexp2 ); + } + break; + + case EV_RAILTRAIL: + // ev_railtrail is now sent standalone rather than by a player entity + CG_RailTrail( &cgs.clientinfo[ es->otherEntityNum2 ], es->origin2, es->pos.trBase, es->dmgFlags ); //----(SA) added 'type' field + break; + // + // missile impacts + // + case EV_MISSILE_HIT: + DEBUGNAME( "EV_MISSILE_HIT" ); + ByteToDir( es->eventParm, dir ); + CG_MissileHitPlayer( cent, es->weapon, position, dir, es->otherEntityNum ); + break; + + case EV_MISSILE_MISS_SMALL: + DEBUGNAME( "EV_MISSILE_MISS" ); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWallSmall( es->weapon, 0, position, dir ); + break; + + case EV_MISSILE_MISS: + DEBUGNAME( "EV_MISSILE_MISS" ); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, 0 ); // (SA) modified to send missilehitwall surface parameters + break; + + case EV_MISSILE_MISS_LARGE: + DEBUGNAME( "EV_MISSILE_MISS_LARGE" ); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( VERYBIGEXPLOSION, 0, position, dir, 0 ); // (SA) modified to send missilehitwall surface parameters + break; + + case EV_SPIT_MISS: + case EV_SPIT_HIT: + DEBUGNAME( "EV_SPIT_MISS" ); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, 0 ); // (SA) modified to send missilehitwall surface parameters + break; + + case EV_MG42BULLET_HIT_WALL: + DEBUGNAME( "EV_MG42BULLET_HIT_WALL" ); + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, qfalse, es->otherEntityNum2, es->effect1Time ); + break; + + case EV_MG42BULLET_HIT_FLESH: + DEBUGNAME( "EV_MG42BULLET_HIT_FLESH" ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, qfalse, es->otherEntityNum2, es->effect1Time ); + break; + + case EV_BULLET_HIT_WALL: + DEBUGNAME( "EV_BULLET_HIT_WALL" ); + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, qfalse, es->otherEntityNum2, 0 ); +// CG_MachineGunEjectBrass(cent); // JPW NERVE + break; + + case EV_BULLET_HIT_FLESH: + DEBUGNAME( "EV_BULLET_HIT_FLESH" ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, qfalse, es->otherEntityNum2, 0 ); + break; + + case EV_WOLFKICK_HIT_WALL: + DEBUGNAME( "EV_WOLFKICK_HIT_WALL" ); + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, qtrue, es->otherEntityNum2, 0 ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.fkickwall ); + break; + + case EV_WOLFKICK_HIT_FLESH: + DEBUGNAME( "EV_WOLFKICK_HIT_FLESH" ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, qtrue, es->otherEntityNum2, 0 ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.fkickflesh ); + break; + + case EV_WOLFKICK_MISS: + DEBUGNAME( "EV_WOLFKICK_MISS" ); + trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.fkickmiss ); + break; + + case EV_POPUPBOOK: + trap_UI_Popup( va( "hbook%d", es->eventParm ) ); + break; + + case EV_POPUP: + s = CG_ConfigString( CS_CLIPBOARDS + es->eventParm ); + // 's' is now the name of the menu script to run + trap_Cvar_Set( "cg_clipboardName", s ); // store new current page name for the ui to pick up + trap_UI_Popup( s ); + break; + + case EV_GIVEPAGE: + { + int havepages = cg_notebookpages.integer; + havepages |= es->eventParm; + trap_Cvar_Set( "cg_notebookpages", va( "%d", havepages ) ); // store new current page name for the ui to pick up + } + break; + + case EV_GENERAL_SOUND: + DEBUGNAME( "EV_GENERAL_SOUND" ); + // Ridah, check for a sound script + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + if ( !strstr( s, ".wav" ) ) { + if ( CG_SoundPlaySoundScript( s, NULL, es->number ) ) { + break; + } + // try with .wav + Q_strncpyz( tempStr, s, sizeof( tempStr ) ); + Q_strcat( tempStr, sizeof( tempStr ), ".wav" ); + s = tempStr; + } + // done. + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes + DEBUGNAME( "EV_GLOBAL_SOUND" ); + // Ridah, check for a sound script + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + if ( !strstr( s, ".wav" ) ) { + if ( CG_SoundPlaySoundScript( s, NULL, es->number ) ) { + break; + } + // try with .wav + Q_strncpyz( tempStr, s, sizeof( tempStr ) ); + Q_strcat( tempStr, sizeof( tempStr ), ".wav" ); + s = tempStr; + } + // done. + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); + } + break; + + // DHM - Nerve + case EV_GLOBAL_CLIENT_SOUND: + DEBUGNAME( "EV_GLOBAL_CLIENT_SOUND" ); + + if ( cg.snap->ps.clientNum == es->teamNum ) { + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); + } + } + + break; + // dhm - end + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME( "EV_PAIN" ); + if ( cent->currentState.number != cg.snap->ps.clientNum ) { + CG_PainEvent( cent, es->eventParm, qfalse ); + } + break; + + case EV_CROUCH_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME( "EV_PAIN" ); + if ( cent->currentState.number != cg.snap->ps.clientNum ) { + CG_PainEvent( cent, es->eventParm, qtrue ); + } + break; + + case EV_DEATH1: + case EV_DEATH2: + case EV_DEATH3: + DEBUGNAME( "EV_DEATHx" ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, + CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) ); + break; + + + case EV_OBITUARY: + DEBUGNAME( "EV_OBITUARY" ); + CG_Obituary( es ); + break; + +// JPW NERVE -- swiped from SP/Sherman + case EV_STOPSTREAMINGSOUND: + DEBUGNAME( "EV_STOPLOOPINGSOUND" ); +// trap_S_StopStreamingSound( es->number ); + trap_S_StartSoundEx( NULL, es->number, CHAN_WEAPON, 0, SND_CUTOFF_ALL ); // kill weapon sound (could be reloading) + break; +// jpw + + // + // powerup events + // + case EV_POWERUP_QUAD: + DEBUGNAME( "EV_POWERUP_QUAD" ); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_QUAD; + cg.powerupTime = cg.time; + } + trap_S_StartSound( NULL, es->number, CHAN_ITEM, cgs.media.quadSound ); + break; + case EV_POWERUP_BATTLESUIT: + DEBUGNAME( "EV_POWERUP_BATTLESUIT" ); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_BATTLESUIT; + cg.powerupTime = cg.time; + } + trap_S_StartSound( NULL, es->number, CHAN_ITEM, trap_S_RegisterSound( "sound/items/protect3.wav" ) ); + break; + case EV_POWERUP_REGEN: + DEBUGNAME( "EV_POWERUP_REGEN" ); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_REGEN; + cg.powerupTime = cg.time; + } + trap_S_StartSound( NULL, es->number, CHAN_ITEM, trap_S_RegisterSound( "sound/items/regen.wav" ) ); + break; + + case EV_LOSE_HAT: + DEBUGNAME( "EV_LOSE_HAT" ); + ByteToDir( es->eventParm, dir ); + // (SA) okay, some events not getting through, so I'm still testing. Works except for that tho. +// CG_Printf("lose had dir: %2.4f %2.4f %2.4f\n", dir[0], dir[1], dir[2]); + CG_LoseHat( cent, dir ); + break; + + case EV_GIB_HEAD: + DEBUGNAME( "EV_GIB_HEAD" ); + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); + { + vec3_t vhead, vtorso, vlegs; + CG_GetBleedOrigin( vhead, vtorso, vlegs, cent->currentState.clientNum ); + CG_GibHead( vhead ); + } + break; + + case EV_GIB_PLAYER: + DEBUGNAME( "EV_GIB_PLAYER" ); + if ( es->aiChar == AICHAR_ZOMBIE ) { + trap_S_StartSound( es->pos.trBase, -1, CHAN_AUTO, cgs.media.zombieDeathSound ); + } else { + trap_S_StartSound( es->pos.trBase, -1, CHAN_AUTO, cgs.media.gibSound ); + } + ByteToDir( es->eventParm, dir ); + CG_GibPlayer( cent, cent->lerpOrigin, dir ); + break; + + case EV_STOPLOOPINGSOUND: + DEBUGNAME( "EV_STOPLOOPINGSOUND" ); + trap_S_StopLoopingSound( es->number ); + es->loopSound = 0; + break; + + case EV_DEBUG_LINE: + DEBUGNAME( "EV_DEBUG_LINE" ); + CG_Beam( cent ); + break; + + // Rafael particles + case EV_SMOKE: + DEBUGNAME( "EV_SMOKE" ); + if ( cent->currentState.density == 3 ) { // cannon + CG_ParticleSmoke( cgs.media.smokePuffShaderdirty, cent ); + } else if ( !( cent->currentState.density ) ) { + CG_ParticleSmoke( cgs.media.smokePuffShader, cent ); + } else { + CG_ParticleSmoke( cgs.media.smokePuffShader, cent ); + } + break; + // done. + + case EV_FLAMETHROWER_EFFECT: + { + int old; + old = cent->currentState.aiChar; + cent->currentState.aiChar = AICHAR_ZOMBIE; + + // shoot this only in bursts + + // (SA) this first one doesn't seem to do anything. ? + +// if ((cg.time+cent->currentState.number*100)%1000 > 200) { +// CG_FireFlameChunks( cent, cent->currentState.origin, cent->lerpAngles, 0.1, qfalse ); +// CG_FireFlameChunks( cent, cent->currentState.origin, cent->currentState.apos.trBase, 0.1, qfalse ); +// } +// else +// CG_FireFlameChunks( cent, cent->currentState.origin, cent->lerpAngles, 0.6, 2 ); + CG_FireFlameChunks( cent, cent->currentState.origin, cent->currentState.apos.trBase, 0.6, 2 ); + + cent->currentState.aiChar = old; + } + break; + + case EV_DUST: + CG_ParticleDust( cent, cent->currentState.origin, cent->currentState.angles ); + break; + + case EV_RUMBLE_EFX: + { + float pitch, yaw; + pitch = cent->currentState.angles[0]; + yaw = cent->currentState.angles[1]; + CG_RumbleEfx( pitch, yaw ); + } + break; + + case EV_CONCUSSIVE: + CG_Concussive( cent ); + break; + +//----(SA) added // generic particle emitter that uses client-side particle scripts + case EV_EMITTER: + { + localEntity_t *le; + le = CG_AllocLocalEntity(); + le->leType = LE_EMITTER; + le->startTime = cg.time; + le->endTime = le->startTime + 20000; + le->pos.trType = TR_STATIONARY; + VectorCopy( cent->currentState.origin, le->pos.trBase ); + VectorCopy( cent->currentState.origin2, le->angles.trBase ); + le->ownerNum = 0; + } + break; +//----(SA) + + case EV_OILPARTICLES: + CG_Particle_OilParticle( cgs.media.oilParticle, cent->currentState.origin, cent->currentState.origin2, cent->currentState.time, cent->currentState.density ); + break; + case EV_OILSLICK: + CG_Particle_OilSlick( cgs.media.oilSlick, cent ); + break; + case EV_OILSLICKREMOVE: + CG_OilSlickRemove( cent ); + break; + + case EV_MG42EFX: + CG_MG42EFX( cent ); + break; + + case EV_FLAKGUN1: + CG_FLAKEFX( cent, 1 ); + break; + case EV_FLAKGUN2: + CG_FLAKEFX( cent, 2 ); + break; + case EV_FLAKGUN3: + CG_FLAKEFX( cent, 3 ); + break; + case EV_FLAKGUN4: + CG_FLAKEFX( cent, 4 ); + break; + + case EV_SPARKS_ELECTRIC: + case EV_SPARKS: + { + int numsparks; + int i; + int duration; + float x,y; + float speed; + vec3_t source, dest; + + if ( !( cent->currentState.density ) ) { + cent->currentState.density = 1; + } + numsparks = rand() % cent->currentState.density; + duration = cent->currentState.frame; + x = cent->currentState.angles2[0]; + y = cent->currentState.angles2[1]; + speed = cent->currentState.angles2[2]; + + if ( !numsparks ) { + numsparks = 1; + } + for ( i = 0; i < numsparks; i++ ) + { + + if ( event == EV_SPARKS_ELECTRIC ) { + VectorCopy( cent->currentState.origin, source ); + + VectorCopy( source, dest ); + dest[0] += ( ( rand() & 31 ) - 16 ); + dest[1] += ( ( rand() & 31 ) - 16 ); + dest[2] += ( ( rand() & 31 ) - 16 ); + + CG_Tracer( source, dest, 1 ); + } else { + CG_ParticleSparks( cent->currentState.origin, cent->currentState.angles, duration, x, y, speed ); + } + + } + + } + break; + + case EV_GUNSPARKS: + { + int numsparks; + int speed; + //int count; + + numsparks = cent->currentState.density; + speed = cent->currentState.angles2[2]; + + CG_AddBulletParticles( cent->currentState.origin, cent->currentState.angles, speed, 800, numsparks, 1.0f ); + + } + break; + + // Rafael bats + case EV_BATS: + { + int i; + for ( i = 0; i < cent->currentState.density; i++ ) + CG_ParticleBats( cgs.media.bats[0], cent ); + } + break; + + case EV_BATS_UPDATEPOSITION: + CG_BatsUpdatePosition( cent ); + break; + + case EV_BATS_DEATH: + CG_BatDeath( cent ); + break; + + // Rafael snow pvs check + case EV_SNOW_ON: + CG_SnowLink( cent, qtrue ); + break; + + case EV_SNOW_OFF: + CG_SnowLink( cent, qfalse ); + break; + + + case EV_SNOWFLURRY: + CG_ParticleSnowFlurry( cgs.media.snowShader, cent ); + break; + + //----(SA) + + // for func_exploding + case EV_EXPLODE: + DEBUGNAME( "EV_EXPLODE" ); + ByteToDir( es->eventParm, dir ); + CG_Explode( cent, position, dir, 0 ); + break; + + // for target_effect + case EV_EFFECT: + DEBUGNAME( "EV_EFFECT" ); +// ByteToDir( es->eventParm, dir ); + CG_Effect( cent, position, dir ); + break; + + //----(SA) done + + case EV_MORTAREFX: // mortar firing + DEBUGNAME( "EV_MORTAREFX" ); + CG_MortarEFX( cent ); + break; + + case EV_SHARD: + ByteToDir( es->eventParm, dir ); + CG_Shard( cent, position, dir ); + break; + + case EV_JUNK: + ByteToDir( es->eventParm, dir ); + { + int i; + int rval; + + rval = rand() % 3 + 3; + + for ( i = 0; i < rval; i++ ) + CG_ShardJunk( cent, position, dir ); + } + break; + + case EV_SNIPER_SOUND: + // trap_S_StartSound( es->pos.trBase, -1, CHAN_AUTO, cgs.media.snipersound ); + // trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.snipersound ); + trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cgs.media.snipersound ); + break; + + default: + DEBUGNAME( "UNKNOWN" ); + CG_Error( "Unknown event: %i", event ); + break; + } + + + { + int rval; + + rval = rand() & 3; + + if ( splashfootstepcnt != rval ) { + splashfootstepcnt = rval; + } else { + splashfootstepcnt++; + } + + if ( splashfootstepcnt > 3 ) { + splashfootstepcnt = 0; + } + + + if ( footstepcnt != rval ) { + footstepcnt = rval; + } else { + footstepcnt++; + } + + if ( footstepcnt > 3 ) { + footstepcnt = 0; + } + } +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) { + int i, event; + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); + CG_SetEntitySoundPosition( cent ); + + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) { + if ( cent->previousEvent ) { + //goto skipEvent; + return; // already fired + } + // if this is a player event set the entity number of the client entity number +//(SA) note: EF_PLAYER_EVENT never set +// if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { +// cent->currentState.number = cent->currentState.otherEntityNum; +// } + + cent->previousEvent = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + } else { + + // DHM - Nerve :: Entities that make it here are Not TempEntities. + // As far as we could tell, for all non-TempEntities, the + // circular 'events' list contains the valid events. So we + // skip processing the single 'event' field and go straight + // to the circular list. + + goto skipEvent; + /* + // check for events riding with another entity + if ( cent->currentState.event == cent->previousEvent ) { + goto skipEvent; + //return; + } + cent->previousEvent = cent->currentState.event; + if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { + goto skipEvent; + //return; + } + */ + // dhm - end + } + + CG_EntityEvent( cent, cent->lerpOrigin ); + // DHM - Nerve :: Temp ents return after processing + return; + +skipEvent: + + // check the sequencial list + // if we've added more events than can fit into the list, make sure we only add them once + if ( cent->currentState.eventSequence < cent->previousEventSequence ) { + cent->previousEventSequence -= ( 1 << 8 ); // eventSequence is sent as an 8-bit through network stream + } + if ( cent->currentState.eventSequence - cent->previousEventSequence > MAX_EVENTS ) { + cent->previousEventSequence = cent->currentState.eventSequence - MAX_EVENTS; + } + for ( i = cent->previousEventSequence ; i != cent->currentState.eventSequence; i++ ) { + event = cent->currentState.events[ i & ( MAX_EVENTS - 1 ) ]; + + cent->currentState.event = event; + cent->currentState.eventParm = cent->currentState.eventParms[ i & ( MAX_EVENTS - 1 ) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + cent->previousEventSequence = cent->currentState.eventSequence; + + // set the event back so we don't think it's changed next frame (unless it really has) + cent->currentState.event = cent->previousEvent; +} + + +/* +void CG_CheckEvents( centity_t *cent ) { + int i, event; + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); + CG_SetEntitySoundPosition( cent ); + + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) { + if ( !cent->previousEvent ) { + cent->previousEvent = 1; + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + } else { + // check for events riding with another entity + if ( cent->currentState.event != cent->previousEvent ) { + cent->previousEvent = cent->currentState.event; + if ( cent->currentState.event & ~EV_EVENT_BITS ) { + CG_EntityEvent( cent, cent->lerpOrigin ); + } + } + } + + // check the sequencial list + // if we've added more events than can fit into the list, make sure we only add them once + if (cent->currentState.eventSequence < cent->previousEventSequence) { + cent->previousEventSequence -= (1 << 8); // eventSequence is sent as an 8-bit through network stream + } + if (cent->currentState.eventSequence - cent->previousEventSequence > MAX_EVENTS) { + cent->previousEventSequence = cent->currentState.eventSequence - MAX_EVENTS; + } + for ( i = cent->previousEventSequence ; i != cent->currentState.eventSequence; i++ ) { + event = cent->currentState.events[ i & (MAX_EVENTS-1) ]; + + cent->currentState.event = event; + cent->currentState.eventParm = cent->currentState.eventParms[ i & (MAX_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + cent->previousEventSequence = cent->currentState.eventSequence; + + // set the event back so we don't think it's changed next frame (unless it really has) + cent->currentState.event = cent->previousEvent; +} +*/ diff --git a/src/cgame/cg_flamethrower.c b/src/cgame/cg_flamethrower.c new file mode 100644 index 0000000..f94e49b --- /dev/null +++ b/src/cgame/cg_flamethrower.c @@ -0,0 +1,1418 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// cg_flamethrower.c - special code for the flamethrower effects +// +// the flameChunks behave similarly to the trailJunc's, except they are rendered differently, and +// also interact with the environment +// +// NOTE: some AI's are treated different, mostly for aesthetical reasons. + +#include "cg_local.h" + +// a flameChunk is a ball or section of fuel which goes from fuel->blue ignition->flame ball +// optimization is necessary, since lots of these will be spawned, but as they grow, they can be +// merged so that less overdraw occurs +typedef struct flameChunk_s +{ + struct flameChunk_s *nextGlobal, *prevGlobal; // next junction in the global list it is in (free or used) + struct flameChunk_s *nextFlameChunk; // next junction in the trail + struct flameChunk_s *nextHead, *prevHead; // next head junc in the world + + qboolean inuse; + qboolean dead; // set when a chunk is effectively inactive, but waiting to be freed + int ownerCent; // cent that spawned us + + int timeStart, timeEnd; + float sizeMax; // start small, increase if we slow down + float sizeRand; + float sizeRate; // rate per ms, variable according to speed (larger if moving slower) + vec3_t baseOrg; + int baseOrgTime; + vec3_t velDir; + float velSpeed; // flame chunks should start with a fast velocity, then slow down if there is nothing behind them pushing them along + float rollAngle; + qboolean ignitionOnly; + int blueLife; + float gravity; + vec3_t startVelDir; + float speedScale; + + // current variables + vec3_t org; + float size; + float lifeFrac; // 0.0 (baby) -> 1.0 (aged) + + int lastFriction, lastFrictionTake; + vec3_t parentFwd; +} flameChunk_t; + +// DHM - Nerve :: lowered this from 2048. Still allows 6-9 people flaming. +#define MAX_FLAME_CHUNKS 1024 +static flameChunk_t flameChunks[MAX_FLAME_CHUNKS]; +static flameChunk_t *freeFlameChunks, *activeFlameChunks, *headFlameChunks; + +static qboolean initFlameChunks = qfalse; + +static int numFlameChunksInuse; + +// this structure stores information relevant to each cent in the game, this way we keep +// the flamethrower data seperate to the rest of the code, which helps if we decide against +// using this weapon in the game +typedef struct centFlameInfo_s +{ + int lastClientFrame; // client frame that we last fired the flamethrower + vec3_t lastAngles; // angles at last firing + vec3_t lastOrigin; // origin at last firing + flameChunk_t + *lastFlameChunk; // flame chunk we last spawned + int lastSoundUpdate; + + qboolean lastFiring; + + int lastDmgUpdate; // time we last told server about this ent's flame damage + int lastDmgCheck; // only check once per 100ms + int lastDmgEnemy; // entity that inflicted the damage +} centFlameInfo_t; + +static centFlameInfo_t centFlameInfo[MAX_GENTITIES]; + +typedef struct +{ +// float fireVolume; // not needed, since we add individual loop sources for the flame, so it gets spacialized + float blowVolume; + float streamVolume; +} flameSoundStatus_t; + +static flameSoundStatus_t centFlameStatus[MAX_GENTITIES]; + +// procedure defs +flameChunk_t *CG_SpawnFlameChunk( flameChunk_t *headFlameChunk ); +void CG_FlameCalcOrg( flameChunk_t *f, int time, vec3_t outOrg ); +void CG_FlameGetMuzzlePoint( vec3_t org, vec3_t fwd, vec3_t right, vec3_t up, vec3_t outPos ); + +// these must be globals, since they cannot expand or contract, since that might result in them getting +// stuck in geometry. therefore when a chunk hits a surface, we should deflect it away from the surface +// slightly, rather than running along it, so that as the chink grows, the sprites don't sink into the +// wall too much. +static vec3_t flameChunkMins = {-4, -4, -4}; +static vec3_t flameChunkMaxs = { 4, 4, 4}; + +// these define how the flame looks +#define FLAME_START_SIZE 1.0 +#define FLAME_START_MAX_SIZE 140.0 // when the flame is spawned, it should endevour to reach this size +#define FLAME_START_MAX_SIZE_RAND 60.0 +#define FLAME_MAX_SIZE 200.0 // flame sprites cannot be larger than this +#define FLAME_MIN_MAXSIZE 40.0 // don't ever let the sizeMax go less than this +#define FLAME_START_SPEED 1200.0 //1200.0 // speed of flame as it leaves the nozzle +#define FLAME_MIN_SPEED 60.0 //200.0 +#define FLAME_CHUNK_DIST 4.0 // space in between chunks when fired + +#define FLAME_BLUE_LENGTH 130.0 +#define FLAME_BLUE_MAX_ALPHA 1.0 + +#define FLAME_FUEL_LENGTH 48.0 +#define FLAME_FUEL_MAX_ALPHA 0.35 +#define FLAME_FUEL_MIN_WIDTH 1.0 + +// these are calculated (don't change) +#define FLAME_LENGTH ( FLAMETHROWER_RANGE + 50.0 ) // NOTE: only modify the range, since this should always reflect that range + +#define FLAME_LIFETIME (int)( ( FLAME_LENGTH / FLAME_START_SPEED ) * 1000 ) // life duration in milliseconds +#define FLAME_FRICTION_PER_SEC ( 2.0 * FLAME_START_SPEED ) +#define FLAME_BLUE_LIFE (int)( ( FLAME_BLUE_LENGTH / FLAME_START_SPEED ) * 1000 ) +#define FLAME_FUEL_LIFE (int)( ( FLAME_FUEL_LENGTH / FLAME_START_SPEED ) * 1000 ) +#define FLAME_FUEL_FADEIN_TIME ( 0.2 * FLAME_FUEL_LIFE ) + +#define FLAME_BLUE_FADEIN_TIME( x ) ( 0.2 * x ) +#define FLAME_BLUE_FADEOUT_TIME( x ) ( 0.05 * x ) +#define GET_FLAME_BLUE_SIZE_SPEED( x ) ( ( (float)x / FLAME_LIFETIME ) / 1.0 ) // x is the current sizeMax +#define GET_FLAME_SIZE_SPEED( x ) ( ( (float)x / FLAME_LIFETIME ) / 0.3 ) // x is the current sizeMax + +//#define FLAME_MIN_DRAWSIZE 20 + +// enable this for the fuel stream +//#define FLAME_ENABLE_FUEL_STREAM + +// enable this for dynamic lighting around flames +//#define FLAMETHROW_LIGHTS + +// disable this to stop rotating flames (this is variable so we can change it at run-time) +int rotatingFlames = qtrue; + +/* +=============== +CG_FlameLerpVec +=============== +*/ +void CG_FlameLerpVec( const vec3_t oldV, const vec3_t newV, float backLerp, vec3_t outV ) { + VectorScale( newV, ( 1.0 - backLerp ), outV ); + VectorMA( outV, backLerp, oldV, outV ); +} + +/* +=============== +CG_FlameAdjustSpeed +=============== +*/ +void CG_FlameAdjustSpeed( flameChunk_t *f, float change ) { + if ( !f->velSpeed && !change ) { + return; + } + + f->velSpeed += change; + if ( f->velSpeed < FLAME_MIN_SPEED ) { + f->velSpeed = FLAME_MIN_SPEED; + } +} + +/* +=============== +CG_FireFlameChunks + + The given entity is firing a flamethrower +=============== +*/ +void CG_FireFlameChunks( centity_t *cent, vec3_t origin, vec3_t angles, float speedScale, qboolean firing ) { + centFlameInfo_t *centInfo; + flameChunk_t *f, *of; + vec3_t lastFwd, thisFwd, fwd; + vec3_t lastUp, thisUp, up; + vec3_t lastRight, thisRight, right; + vec3_t thisOrg, lastOrg, org; + double timeInc, backLerp, fracInc; + int t, numFrameChunks; + double ft; + trace_t trace; + vec3_t parentFwd; + + centInfo = ¢FlameInfo[cent->currentState.number]; + + // for any other character or in 3rd person view, use entity angles for friction + if ( cent->currentState.number != cg.snap->ps.clientNum || cg_thirdPerson.integer ) { + AngleVectors( cent->currentState.angles, parentFwd, NULL, NULL ); + } else { + AngleVectors( angles, parentFwd, NULL, NULL ); + } + + AngleVectors( angles, thisFwd, thisRight, thisUp ); + VectorCopy( origin, thisOrg ); + + // if this entity was firing last frame, interpolate the angles as we spawn the chunks that + // fired over the last frame + if ( ( centInfo->lastClientFrame == cent->currentState.frame ) && + ( centInfo->lastFlameChunk && centInfo->lastFiring == firing ) ) { + AngleVectors( centInfo->lastAngles, lastFwd, lastRight, lastUp ); + VectorCopy( centInfo->lastOrigin, lastOrg ); + centInfo->lastFiring = firing; + + of = centInfo->lastFlameChunk; + timeInc = 1000.0 * ( firing ? 1.0 : 0.5 ) * ( FLAME_CHUNK_DIST / ( FLAME_START_SPEED * speedScale ) ); + ft = ( (double)of->timeStart + timeInc ); + t = (int)ft; + fracInc = timeInc / (double)( cg.time - of->timeStart ); + backLerp = 1.0 - fracInc; + + numFrameChunks = 0; // CHANGE: id + + while ( t <= cg.time ) { + // spawn a new chunk + CG_FlameLerpVec( lastOrg, thisOrg, backLerp, org ); + + CG_Trace( &trace, org, flameChunkMins, flameChunkMaxs, org, cent->currentState.number, MASK_SHOT | MASK_WATER ); // JPW NERVE water fixes + + if ( trace.startsolid ) { + return; // don't spawn inside a wall + + } + f = CG_SpawnFlameChunk( of ); + + if ( !f ) { + //CG_Printf( "Out of flame chunks\n" ); + // CHANGE: id + // to make sure we do not keep trying to add more and more chunks + centInfo->lastFlameChunk->timeStart = cg.time; + // end CHANGE: id + return; + } + + CG_FlameLerpVec( lastFwd, thisFwd, backLerp, fwd ); + VectorNormalize( fwd ); + CG_FlameLerpVec( lastRight, thisRight, backLerp, right ); + VectorNormalize( right ); + CG_FlameLerpVec( lastUp, thisUp, backLerp, up ); + VectorNormalize( up ); + + f->timeStart = t; + f->timeEnd = t + FLAME_LIFETIME * ( 1.0 / ( 0.5 + 0.5 * speedScale ) ); + f->size = FLAME_START_SIZE * speedScale; + f->sizeMax = speedScale * ( FLAME_START_MAX_SIZE + f->sizeRand * ( firing ? 1.0 : 0.0 ) ); + f->sizeRand = 0; + + if ( f->sizeMax > FLAME_MAX_SIZE ) { + f->sizeMax = FLAME_MAX_SIZE; + } + + f->sizeRate = GET_FLAME_BLUE_SIZE_SPEED( f->sizeMax * speedScale * ( 1.0 + ( 0.5 * (float)!firing ) ) ); + VectorCopy( org, f->baseOrg ); + f->baseOrgTime = t; + VectorCopy( fwd, f->velDir ); + VectorCopy( fwd, f->startVelDir ); + f->speedScale = speedScale; + + VectorNormalize( f->velDir ); + f->velSpeed = FLAME_START_SPEED * ( 0.5 + 0.5 * speedScale ) * ( firing ? 1.0 : 4.5 ); + f->ownerCent = cent->currentState.number; + f->rollAngle = crandom() * 179; + f->ignitionOnly = !firing; + + if ( !firing ) { + f->gravity = -150; + f->blueLife = FLAME_BLUE_LIFE * 0.1; + } else { + f->gravity = 0; + f->blueLife = FLAME_BLUE_LIFE; + } + f->lastFriction = cg.time; + f->lastFrictionTake = cg.time; + VectorCopy( parentFwd, f->parentFwd ); + + ft += timeInc; + // always spawn a chunk right on the current time + if ( (int)ft > cg.time && t < cg.time ) { + ft = (double)cg.time; + backLerp = fracInc; // so it'll get set to zero a few lines down + } + t = (int)ft; + backLerp -= fracInc; + centInfo->lastFlameChunk = of = f; + // CHANGE: id + // don't spawn too many chunks each frame + if ( ++numFrameChunks > 50 ) { + // to make sure we do not keep trying to add more and more chunks + centInfo->lastFlameChunk->timeStart = cg.time; + break; + } + // end CHANGE: id + } + } else { + centInfo->lastFiring = firing; + + // just fire a single chunk to get us started + f = CG_SpawnFlameChunk( NULL ); + + if ( !f ) { + //CG_Printf( "Out of flame chunks\n" ); + return; + } + + VectorCopy( thisOrg, org ); + VectorCopy( thisFwd, fwd ); + VectorCopy( thisUp, up ); + VectorCopy( thisRight, right ); + + f->timeStart = cg.time; + f->timeEnd = cg.time + FLAME_LIFETIME * ( 1.0 / ( 0.5 + 0.5 * speedScale ) ); + f->size = FLAME_START_SIZE * speedScale; + f->sizeMax = FLAME_START_MAX_SIZE * speedScale; + if ( f->sizeMax > FLAME_MAX_SIZE ) { + f->sizeMax = FLAME_MAX_SIZE; + } + + f->sizeRand = 0; + f->sizeRate = GET_FLAME_BLUE_SIZE_SPEED( f->sizeMax * speedScale ); + VectorCopy( org, f->baseOrg ); + f->baseOrgTime = cg.time; + VectorCopy( fwd, f->velDir ); + VectorCopy( fwd, f->startVelDir ); + f->velSpeed = FLAME_START_SPEED * ( 0.5 + 0.5 * speedScale ); + f->ownerCent = cent->currentState.number; + f->rollAngle = crandom() * 179; + f->ignitionOnly = !firing; + f->speedScale = speedScale; + if ( !firing ) { + f->gravity = -100; + f->blueLife = (int)( 0.3 * ( 1.0 / speedScale ) * (float)FLAME_BLUE_LIFE ); + } else { + f->gravity = 0; + f->blueLife = FLAME_BLUE_LIFE; + } + f->lastFriction = cg.time; + f->lastFrictionTake = cg.time; + VectorCopy( parentFwd, f->parentFwd ); + + centInfo->lastFlameChunk = f; + } + + // push them along + /* + f = centInfo->lastFlameChunk; + while (f) { + + if (f->lastFriction < cg.time - 50) { + frametime = (float)(cg.time - f->lastFriction) / 1000.0; + f->lastFriction = cg.time; + dot = DotProduct(parentFwd, f->parentFwd); + if (dot >= 0.99) { + dot -= 0.99; + dot *= (1.0/(1.0-0.99)); + CG_FlameAdjustSpeed( f, 0.5 * frametime * FLAME_FRICTION_PER_SEC * pow(dot,4) ); + } + } + + f = f->nextFlameChunk; + } + */ + + VectorCopy( angles, centInfo->lastAngles ); + VectorCopy( origin, centInfo->lastOrigin ); + centInfo->lastClientFrame = cent->currentState.frame; +} + +/* +=============== +CG_ClearFlameChunks +=============== +*/ +void CG_ClearFlameChunks( void ) { + int i; + + memset( flameChunks, 0, sizeof( flameChunks ) ); + memset( centFlameInfo, 0, sizeof( centFlameInfo ) ); + + freeFlameChunks = flameChunks; + activeFlameChunks = NULL; + headFlameChunks = NULL; + + for ( i = 0 ; i < MAX_FLAME_CHUNKS ; i++ ) + { + flameChunks[i].nextGlobal = &flameChunks[i + 1]; + + if ( i > 0 ) { + flameChunks[i].prevGlobal = &flameChunks[i - 1]; + } else { + flameChunks[i].prevGlobal = NULL; + } + + flameChunks[i].inuse = qfalse; + } + flameChunks[MAX_FLAME_CHUNKS - 1].nextGlobal = NULL; + + initFlameChunks = qtrue; + numFlameChunksInuse = 0; +} + +/* +=============== +CG_SpawnFlameChunk +=============== +*/ +flameChunk_t *CG_SpawnFlameChunk( flameChunk_t *headFlameChunk ) { + flameChunk_t *f; + + if ( !freeFlameChunks ) { + return NULL; + } + + if ( headFlameChunks && headFlameChunks->dead ) { + headFlameChunks = NULL; + } + + // select the first free trail, and remove it from the list + f = freeFlameChunks; + freeFlameChunks = f->nextGlobal; + if ( freeFlameChunks ) { + freeFlameChunks->prevGlobal = NULL; + } + + f->nextGlobal = activeFlameChunks; + if ( activeFlameChunks ) { + activeFlameChunks->prevGlobal = f; + } + activeFlameChunks = f; + f->prevGlobal = NULL; + f->inuse = qtrue; + f->dead = qfalse; + + // if this owner has a headJunc, add us to the start + if ( headFlameChunk ) { + // remove the headJunc from the list of heads + if ( headFlameChunk == headFlameChunks ) { + headFlameChunks = headFlameChunks->nextHead; + if ( headFlameChunks ) { + headFlameChunks->prevHead = NULL; + } + } else { + if ( headFlameChunk->nextHead ) { + headFlameChunk->nextHead->prevHead = headFlameChunk->prevHead; + } + if ( headFlameChunk->prevHead ) { + headFlameChunk->prevHead->nextHead = headFlameChunk->nextHead; + } + } + headFlameChunk->prevHead = NULL; + headFlameChunk->nextHead = NULL; + } + // make us the headTrail + if ( headFlameChunks ) { + headFlameChunks->prevHead = f; + } + f->nextHead = headFlameChunks; + f->prevHead = NULL; + headFlameChunks = f; + + f->nextFlameChunk = headFlameChunk; // if headJunc is NULL, then we'll just be the end of the list + + numFlameChunksInuse++; + + // debugging +// if ( cg_drawRewards.integer > 1 ) +// CG_Printf( "NumFlameChunks: %i\n", numFlameChunksInuse ); + + return f; +} + +/* +=========== +CG_FreeFlameChunk +=========== +*/ +void CG_FreeFlameChunk( flameChunk_t *f ) { + // kill any juncs after us, so they aren't left hanging + if ( f->nextFlameChunk ) { + CG_FreeFlameChunk( f->nextFlameChunk ); + f->nextFlameChunk = NULL; + } + + // make it non-active + f->inuse = qfalse; + f->dead = qfalse; + if ( f->nextGlobal ) { + f->nextGlobal->prevGlobal = f->prevGlobal; + } + if ( f->prevGlobal ) { + f->prevGlobal->nextGlobal = f->nextGlobal; + } + if ( f == activeFlameChunks ) { + activeFlameChunks = f->nextGlobal; + } + + // if it's a head, remove it + if ( f == headFlameChunks ) { + headFlameChunks = f->nextHead; + } + if ( f->nextHead ) { + f->nextHead->prevHead = f->prevHead; + } + if ( f->prevHead ) { + f->prevHead->nextHead = f->nextHead; + } + f->nextHead = NULL; + f->prevHead = NULL; + + // stick it in the free list + f->prevGlobal = NULL; + f->nextGlobal = freeFlameChunks; + if ( freeFlameChunks ) { + freeFlameChunks->prevGlobal = f; + } + freeFlameChunks = f; + + numFlameChunksInuse--; +} + +/* +=============== +CG_MergeFlameChunks + + Assumes f1 comes before f2 +=============== +*/ +void CG_MergeFlameChunks( flameChunk_t *f1, flameChunk_t *f2 ) { + if ( f1->nextFlameChunk != f2 ) { + CG_Error( "CG_MergeFlameChunks: f2 doesn't follow f1, cannot merge\n" ); + } + + f1->nextFlameChunk = f2->nextFlameChunk; + f2->nextFlameChunk = NULL; + + VectorCopy( f2->velDir, f1->velDir ); + + VectorCopy( f2->baseOrg, f1->baseOrg ); + f1->baseOrgTime = f2->baseOrgTime; + + f1->velSpeed = f2->velSpeed; + f1->sizeMax = f2->sizeMax; + f1->size = f2->size; + f1->timeStart = f2->timeStart; + f1->timeEnd = f2->timeEnd; + + CG_FreeFlameChunk( f2 ); +} + +/* +=============== +CG_FlameCalcOrg +=============== +*/ +void CG_FlameCalcOrg( flameChunk_t *f, int time, vec3_t outOrg ) { + VectorMA( f->baseOrg, f->velSpeed * ( (float)( time - f->baseOrgTime ) / 1000 ), f->velDir, outOrg ); + //outOrg[2] -= f->gravity * ((float)(time - f->timeStart)/1000.0) * ((float)(time - f->timeStart)/1000.0); +} + +/* +=============== +CG_MoveFlameChunk +=============== +*/ +void CG_MoveFlameChunk( flameChunk_t *f ) { + vec3_t newOrigin, sOrg; + trace_t trace; + int jiggleCount; + float dot; + // TTimo: unused + //static vec3_t umins = {-1,-1,-1}; + //static vec3_t umaxs = { 1, 1, 1}; + + // subtract friction from speed + if ( f->velSpeed > 1 && f->lastFrictionTake < cg.time - 50 ) { + CG_FlameAdjustSpeed( f, -( (float)( cg.time - f->lastFrictionTake ) / 1000.0 ) * FLAME_FRICTION_PER_SEC ); + f->lastFrictionTake = cg.time; + } + + // adjust size + if ( f->size < f->sizeMax ) { + if ( ( cg.time - f->timeStart ) < f->blueLife ) { + f->sizeRate = GET_FLAME_BLUE_SIZE_SPEED( FLAME_START_MAX_SIZE ); // use a constant so the blue flame doesn't distort + } else { + f->sizeRate = GET_FLAME_SIZE_SPEED( f->sizeMax ); + } + + f->size += f->sizeRate * (float)( cg.time - f->baseOrgTime ); + if ( f->size > f->sizeMax ) { + f->size = f->sizeMax; + } + } + + jiggleCount = 0; + VectorCopy( f->baseOrg, sOrg ); + while ( f->velSpeed > 1 ) { + CG_FlameCalcOrg( f, cg.time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, sOrg, flameChunkMins, flameChunkMaxs, newOrigin, f->ownerCent, MASK_SHOT | MASK_WATER ); // JPW NERVE water fixes + + if ( trace.startsolid ) { + f->velSpeed = 0; + f->dead = 1; // JPW NERVE water fixes + break; + } + + if ( trace.surfaceFlags & SURF_NOIMPACT ) { + break; + } + + // moved some distance + VectorCopy( trace.endpos, f->baseOrg ); + f->baseOrgTime += (int)( (float)( cg.time - f->baseOrgTime ) * trace.fraction ); + + if ( trace.fraction == 1.0 ) { + // check for hitting client + if ( ( f->ownerCent != cg.snap->ps.clientNum ) && !( cg.snap->ps.eFlags & EF_DEAD ) && VectorDistance( newOrigin, cg.snap->ps.origin ) < 32 ) { + VectorNegate( f->velDir, trace.plane.normal ); + } else { + break; + } + } + + // reflect off surface + dot = DotProduct( f->velDir, trace.plane.normal ); + VectorMA( f->velDir, -2 * dot, trace.plane.normal, f->velDir ); + VectorNormalize( f->velDir ); + // subtract some speed + f->velSpeed *= 0.5 * ( 0.25 + 0.75 * ( ( dot + 1.0 ) * 0.5 ) ); + VectorCopy( f->velDir, f->parentFwd ); + + VectorCopy( f->baseOrg, sOrg ); + } + + CG_FlameCalcOrg( f, cg.time, f->org ); + f->baseOrgTime = cg.time; // incase we skipped the movement +} + +/* +=============== +CG_AddFlameSpriteToScene +=============== +*/ +static vec3_t vright, vup; +static vec3_t rright, rup; + +#ifdef _DEBUG // just in case we forget about it, but it should be disabled at all times (only enabled to generate updated shaders) +#ifdef ALLOW_GEN_SHADERS // secondary security measure + +//#define GEN_FLAME_SHADER + +#endif // ALLOW_GEN_SHADERS +#endif // _DEBUG + +#define FLAME_BLEND_SRC "GL_ONE" +#define FLAME_BLEND_DST "GL_ONE_MINUS_SRC_COLOR" + +#define NUM_FLAME_SPRITES 45 +#define FLAME_SPRITE_DIR "twiltb2" + +#define NUM_NOZZLE_SPRITES 8 + +static qhandle_t flameShaders[NUM_FLAME_SPRITES]; +static qhandle_t nozzleShaders[NUM_NOZZLE_SPRITES]; +static qboolean initFlameShaders = qtrue; + +#define MAX_CLIPPED_FLAMES 8 // dont draw more than this many per frame +static int numClippedFlames; + + +void CG_AddFlameSpriteToScene( flameChunk_t *f, float lifeFrac, float alpha ) { + vec3_t point, p2, projVec, sProj; + polyVert_t verts[4]; + float radius, sdist, x; + int frameNum; + vec3_t vec, rotate_ang; + unsigned char alphaChar; + vec2_t fovRadius; + vec2_t rdist, rST; + int rollAngleClamped; + static vec3_t lastPos; + + // DHM - Nerve :: No client side damage + //CG_FlameDamage( f->ownerCent, f->org, f->size ); + + if ( alpha < 0 ) { + return; // we dont want to see this + + } + radius = ( f->size / 2.0 ); + if ( radius < 6 ) { + radius = 6; + } + rST[0] = radius * 1.0; + rST[1] = radius * 1.0 / 1.481; + alphaChar = ( unsigned char )( 255.0 * alpha ); + + verts[0].modulate[0] = alphaChar; + verts[0].modulate[1] = alphaChar; + verts[0].modulate[2] = alphaChar; + verts[0].modulate[3] = alphaChar; + verts[1] = verts[0]; + verts[2] = verts[0]; + verts[3] = verts[0]; + + // find the projected distance from the eye to the projection of the flame origin + // onto the view direction vector + VectorMA( cg.refdef.vieworg, 1024, cg.refdef.viewaxis[0], p2 ); + ProjectPointOntoVector( f->org, cg.refdef.vieworg, p2, sProj ); + + // make sure its infront of us + VectorSubtract( sProj, cg.refdef.vieworg, vec ); + sdist = VectorNormalize( vec ); + if ( !sdist || DotProduct( vec, cg.refdef.viewaxis[0] ) < 0 ) { + return; + } + + // if we are "inside" this sprite, clip it to our view frustum + if ( sdist < f->size * 0.6 ) { + // clip the sprite to the viewport, avoiding rendering off-screen pixels which + // is the main cause of slow-downs + + if ( ( sdist < f->size * 0.6 ) && ( numClippedFlames++ > MAX_CLIPPED_FLAMES ) ) { + return; + } + + // OPTIMIZATION: clamp to 90 degree rotations + rollAngleClamped = 0; + if ( ( rotatingFlames ) && ( !( cg_fxflags & 1 ) ) ) { // JPW NERVE no rotate for alt flame shaders + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += ( rollAngleClamped = ( (int)( f->rollAngle ) / 90 ) * 90 ); + AngleVectors( rotate_ang, NULL, rright, rup ); + } else { + VectorCopy( vright, rright ); + VectorCopy( vup, rup ); + } + + VectorSubtract( sProj, f->org, projVec ); + + // find the distances from the sprite origin to the projection along the refdef axis + rdist[0] = -1.0 * DotProduct( rright, projVec ); + rdist[1] = -1.0 * DotProduct( rup, projVec ); + + if ( fabs( rdist[0] ) > radius || fabs( rdist[1] ) > radius ) { + return; // completely off-screen + + } + // now set the bounds for clipping + fovRadius[0] = tan( DEG2RAD( ( rollAngleClamped % 2 == 0 ? cg.refdef.fov_x : cg.refdef.fov_x ) * 0.52 ) ) * sdist; + fovRadius[1] = tan( DEG2RAD( ( rollAngleClamped % 2 == 0 ? cg.refdef.fov_x : cg.refdef.fov_x ) * 0.52 ) ) * sdist; + + // BOTTOM LEFT + x = ( -radius + rdist[0] ); + if ( x < -fovRadius[0] ) { // clip + VectorMA( f->org, -radius + ( -fovRadius[0] - x ), rright, point ); + verts[0].st[0] = 0.0 + ( -fovRadius[0] - x ) / ( radius * 2 ); + } else { + VectorMA( f->org, -radius, rright, point ); + verts[0].st[0] = 0.0; + } + x = ( -radius + rdist[1] ); + if ( x < -fovRadius[1] ) { + VectorMA( point, -radius + ( -fovRadius[1] - x ), rup, point ); + verts[0].st[1] = 0.0 + ( -fovRadius[1] - x ) / ( radius * 2 ); + } else { + VectorMA( point, -radius, rup, point ); + verts[0].st[1] = 0.0; + } + VectorCopy( point, verts[0].xyz ); + + // TOP LEFT + x = ( -radius + rdist[0] ); + if ( x < -fovRadius[0] ) { // clip + VectorMA( f->org, -radius + ( -fovRadius[0] - x ), rright, point ); + verts[1].st[0] = 0.0 + ( -fovRadius[0] - x ) / ( radius * 2 ); + } else { + VectorMA( f->org, -radius, rright, point ); + verts[1].st[0] = 0.0; + } + x = ( radius + rdist[1] ); + if ( x > fovRadius[1] ) { + VectorMA( point, radius + ( fovRadius[1] - x ), rup, point ); + verts[1].st[1] = 1.0 + ( fovRadius[1] - x ) / ( radius * 2 ); + } else { + VectorMA( point, radius, rup, point ); + verts[1].st[1] = 1.0; + } + VectorCopy( point, verts[1].xyz ); + + // TOP RIGHT + x = ( radius + rdist[0] ); + if ( x > fovRadius[0] ) { + VectorMA( f->org, radius + ( fovRadius[0] - x ), rright, point ); + verts[2].st[0] = 1.0 + ( fovRadius[0] - x ) / ( radius * 2 ); + } else { + VectorMA( f->org, radius, rright, point ); + verts[2].st[0] = 1.0; + } + x = ( radius + rdist[1] ); + if ( x > fovRadius[1] ) { + VectorMA( point, radius + ( fovRadius[1] - x ), rup, point ); + verts[2].st[1] = 1.0 + ( fovRadius[1] - x ) / ( radius * 2 ); + } else { + VectorMA( point, radius, rup, point ); + verts[2].st[1] = 1.0; + } + VectorCopy( point, verts[2].xyz ); + + // BOTTOM RIGHT + x = ( radius + rdist[0] ); + if ( x > fovRadius[0] ) { + VectorMA( f->org, radius + ( fovRadius[0] - x ), rright, point ); + verts[3].st[0] = 1.0 + ( fovRadius[0] - x ) / ( radius * 2 ); + } else { + VectorMA( f->org, radius, rright, point ); + verts[3].st[0] = 1.0; + } + x = ( -radius + rdist[1] ); + if ( x < -fovRadius[1] ) { + VectorMA( point, -radius + ( -fovRadius[1] - x ), rup, point ); + verts[3].st[1] = 0.0 + ( -fovRadius[1] - x ) / ( radius * 2 ); + } else { + VectorMA( point, -radius, rup, point ); + verts[3].st[1] = 0.0; + } + VectorCopy( point, verts[3].xyz ); + + } else { // set default values for no clipping + + if ( ( rotatingFlames ) && ( !( cg_fxflags & 1 ) ) ) { // JPW NERVE no rotate for alt flame shaders { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += f->rollAngle; + AngleVectors( rotate_ang, NULL, rright, rup ); + } else { + VectorCopy( vright, rright ); + VectorCopy( vup, rup ); + } + + VectorMA( f->org, -rST[1], rup, point ); + VectorMA( point, -rST[0], rright, point ); + VectorCopy( point, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + + VectorMA( point, rST[1] * 2, rup, point ); + VectorCopy( point, verts[1].xyz ); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + + VectorMA( point, rST[0] * 2, rright, point ); + VectorCopy( point, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + + VectorMA( point, -rST[1] * 2, rup, point ); + VectorCopy( point, verts[3].xyz ); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + } + + frameNum = (int)floor( lifeFrac * NUM_FLAME_SPRITES ); + if ( frameNum < 0 ) { + frameNum = 0; + } else if ( frameNum > NUM_FLAME_SPRITES - 1 ) { + frameNum = NUM_FLAME_SPRITES - 1; + } + + +// JPW NERVE alternate FT shaders + if ( cg_fxflags & 1 ) { +/* + p->roll = 0; + p->pshader = getTestShader(); + rotate_ang[ROLL]=90; +*/ + trap_R_AddPolyToScene( getTestShader(),4,verts ); + } else { + trap_R_AddPolyToScene( flameShaders[frameNum], 4, verts ); + } +// jpw + VectorCopy( f->org, lastPos ); +} + +static int nextFlameLight = 0; +static int lastFlameOwner = -1; +/* +=============== +CG_AddFlameToScene +=============== +*/ +void CG_AddFlameToScene( flameChunk_t *fHead ) { + flameChunk_t *f, *fNext; + int blueTrailHead = 0, fuelTrailHead = 0; + static vec3_t whiteColor = {1,1,1}; + vec3_t c; + float alpha; + float lived; + int headTimeStart; + // qboolean firstFuel = qtrue; // TTimo: unused + float vdist, bdot; +#define FLAME_SOUND_RANGE 1024.0 + //flameChunk_t *lastSoundFlameChunk=NULL; // TTimo: unused + flameChunk_t *lastBlowChunk = NULL; + qboolean isClientFlame, firing; + int shader; + flameChunk_t *lastBlueChunk = NULL; + qboolean skip = qfalse, droppedTrail; + vec3_t v; + vec3_t lightOrg; // origin to place light at + float lightSize; + float lightFlameCount; + float lastFuelAlpha; + + isClientFlame = ( fHead == centFlameInfo[fHead->ownerCent].lastFlameChunk ); + + if ( ( cg_entities[fHead->ownerCent].currentState.eFlags & EF_FIRING ) && ( centFlameInfo[fHead->ownerCent].lastFlameChunk == fHead ) ) { + headTimeStart = fHead->timeStart; + firing = qtrue; + } else { + headTimeStart = cg.time; + firing = qfalse; + } + + VectorClear( lightOrg ); + lightSize = 0; + lightFlameCount = 0; + + lastFuelAlpha = 1.0; + + f = fHead; + while ( f ) { + + if ( f->nextFlameChunk && f->nextFlameChunk->dead ) { + // kill it + CG_FreeFlameChunk( f->nextFlameChunk ); + f->nextFlameChunk = NULL; + } + + // draw this chunk + + fNext = f->nextFlameChunk; + lived = (float)( headTimeStart - f->timeStart ); + + // update the "blow" sound volume (louder as we sway it) + vdist = Distance( cg.refdef.vieworg, f->org ); // NOTE: this needs to be here or the flameSound code further below won't work + if ( lastBlowChunk && ( centFlameStatus[f->ownerCent].blowVolume < 1.0 ) && + ( ( bdot = DotProduct( lastBlowChunk->startVelDir, f->startVelDir ) ) < 1.0 ) ) { + if ( vdist < FLAME_SOUND_RANGE ) { + centFlameStatus[f->ownerCent].blowVolume += 500.0 * ( 1.0 - bdot ) * ( 1.0 - ( vdist / FLAME_SOUND_RANGE ) ); + if ( centFlameStatus[f->ownerCent].blowVolume > 1.0 ) { + centFlameStatus[f->ownerCent].blowVolume = 1.0; + } + } + } + lastBlowChunk = f; + + VectorMA( lightOrg, f->size / 20.0, f->org, lightOrg ); + lightSize += f->size; + lightFlameCount += f->size / 20.0; + + droppedTrail = qfalse; + + // is it a stream chunk? (no special handling) + if ( !f->ignitionOnly && f->velSpeed < 1 ) { + CG_AddFlameSpriteToScene( f, f->lifeFrac, 1.0 ); + + // is it in the blue ignition section of the flame? + } else if ( isClientFlame && f->blueLife > ( lived / 2.0 ) ) { + + skip = qfalse; + + // if this is backwards from the last chunk, then skip it + if ( fNext && f != fHead && lastBlueChunk ) { + VectorSubtract( f->org, lastBlueChunk->org, v ); + if ( VectorNormalize( v ) < f->size / 2 ) { + skip = qtrue; + } else if ( DotProduct( v, f->velDir ) < 0 ) { + skip = qtrue; + } + } + + // stream sound + if ( !f->ignitionOnly ) { + centFlameStatus[f->ownerCent].streamVolume += 0.05; + if ( centFlameStatus[f->ownerCent].streamVolume > 1.0 ) { + centFlameStatus[f->ownerCent].streamVolume = 1.0; + } + } + + + if ( !skip ) { + + // just call this for damage checking + //if (!f->ignitionOnly) + //CG_AddFlameSpriteToScene( f, f->lifeFrac, -1 ); + + lastBlueChunk = f; + + alpha = 1.0; // new nozzle sprite + VectorScale( whiteColor, alpha, c ); + + if ( f->blueLife > lived * ( f->ignitionOnly ? 3.0 : 3.0 ) ) { + + shader = nozzleShaders[( cg.time / 50 + ( cg.time / 50 >> 1 ) ) % NUM_NOZZLE_SPRITES]; + + blueTrailHead = CG_AddTrailJunc( blueTrailHead, + shader, + cg.time, + STYPE_STRETCH, + f->org, + 1, + alpha, alpha, + f->size * ( f->ignitionOnly /*&& (cg.snap->ps.clientNum != f->ownerCent || cg_thirdPerson.integer)*/ ? 2.0 : 1.0 ), + FLAME_MAX_SIZE, + TJFL_NOCULL | TJFL_FIXDISTORT, + c, c, 1.0, 5.0 ); + } + + // fire stream + if ( !f->ignitionOnly ) { + float bscale; + qboolean fskip = qfalse; + + bscale = 1.0; + + if ( !f->nextFlameChunk ) { + alpha = 0; + } else if ( lived / 1.3 < bscale * FLAME_BLUE_FADEIN_TIME( f->blueLife ) ) { + alpha = FLAME_BLUE_MAX_ALPHA * ( ( lived / 1.3 ) / ( bscale * FLAME_BLUE_FADEIN_TIME( f->blueLife ) ) ); + } else if ( lived / 1.3 < ( f->blueLife - FLAME_BLUE_FADEOUT_TIME( f->blueLife ) ) ) { + alpha = FLAME_BLUE_MAX_ALPHA; + } else { + alpha = FLAME_BLUE_MAX_ALPHA * ( 1.0 - ( ( lived / 1.3 - ( f->blueLife - FLAME_BLUE_FADEOUT_TIME( f->blueLife ) ) ) / ( FLAME_BLUE_FADEOUT_TIME( f->blueLife ) ) ) ); + } + if ( alpha <= 0.0 ) { + alpha = 0.0; + if ( lastFuelAlpha <= 0.0 ) { + fskip = qtrue; + } + } + + if ( !fskip ) { + lastFuelAlpha = alpha; + + VectorScale( whiteColor, alpha, c ); + + droppedTrail = qtrue; + + fuelTrailHead = CG_AddTrailJunc( fuelTrailHead, + cgs.media.flamethrowerFireStream, + cg.time, + ( f->ignitionOnly ? STYPE_STRETCH : STYPE_REPEAT ), + f->org, + 1, + alpha, alpha, + ( f->size / 2 < f->sizeMax / 4 ? f->size / 2 : f->sizeMax / 4 ), + FLAME_MAX_SIZE, + TJFL_NOCULL | TJFL_FIXDISTORT | TJFL_CROSSOVER, + c, c, 0.5, 1.5 ); + } + } + } + } + +#define FLAME_SPRITE_START_BLUE_SCALE 0.2 + + if ( !f->ignitionOnly && + ( (float)( FLAME_SPRITE_START_BLUE_SCALE * f->blueLife ) < (float)lived ) ) { + + float alpha, lifeFrac; + qboolean skip = qfalse; + + // should we merge it with the next sprite? + while ( fNext && !droppedTrail ) { + if ( ( Distance( f->org, fNext->org ) < ( ( 0.1 + 0.9 * f->lifeFrac ) * f->size * 0.35 ) ) + && ( fabs( f->size - fNext->size ) < ( 40.0 ) ) + && ( fabs( f->timeStart - fNext->timeStart ) < 100 ) + && ( DotProduct( f->velDir, fNext->velDir ) > 0.99 ) + ) { + if ( !droppedTrail ) { + CG_MergeFlameChunks( f, fNext ); + fNext = f->nextFlameChunk; // it may have changed + } else { + skip = qtrue; + break; + } + } else { + break; + } + } + + lifeFrac = ( lived - FLAME_SPRITE_START_BLUE_SCALE * f->blueLife ) / ( FLAME_LIFETIME - FLAME_SPRITE_START_BLUE_SCALE * f->blueLife ); + + alpha = ( 1.0 - lifeFrac ) * 1.4; + if ( alpha > 1.0 ) { + alpha = 1.0; + } + + if ( !skip ) { + // draw the sprite + CG_AddFlameSpriteToScene( f, lifeFrac, alpha ); + } + // update the sizeRate + f->sizeRate = GET_FLAME_SIZE_SPEED( f->sizeMax ); + } + + f = fNext; + } + + if ( lastFlameOwner == fHead->ownerCent && nextFlameLight == cg.clientFrame ) { + return; + } + + if ( !fHead->ignitionOnly ) { + nextFlameLight = cg.clientFrame; + lastFlameOwner = fHead->ownerCent; + } + + if ( lightSize < 80 ) { + lightSize = 80; + } + + if ( lightSize > 500 ) { + lightSize = 500; + } + lightSize *= 1.0 + 0.2 * ( sin( 1.0 * cg.time / 50.0 ) * cos( 1.0 * cg.time / 43.0 ) ); + // set the alpha + alpha = lightSize / 500.0; + if ( alpha > 1.0 ) { + alpha = 1.0; + } + VectorScale( lightOrg, 1.0 / lightFlameCount, lightOrg ); + // if it's only a nozzle, make it blue + if ( fHead->ignitionOnly ) { + if ( lightSize > 80 ) { + lightSize = 80; + } + trap_R_AddLightToScene( lightOrg, 90 + lightSize, 0, 0, alpha, 0 + isClientFlame * ( fHead->ownerCent == cg.snap->ps.clientNum ) ); + } else if ( isClientFlame || ( fHead->ownerCent == cg.snap->ps.clientNum ) ) { + trap_R_AddLightToScene( lightOrg, 90 + lightSize, 1.000000 * alpha, 0.603922 * alpha, 0.207843 * alpha, 0 ); + } +} + +/* +============= +CG_GenerateShaders + + A util to create a bunch of shaders in a unique shader file, which represent an animation +============= +*/ +void CG_GenerateShaders( char *filename, char *shaderName, char *dir, int numFrames, char *srcBlend, char *dstBlend, char *extras, qboolean compressedVersionAvailable, qboolean nomipmap ) { + fileHandle_t f; + int b, c, d, lastNumber; + char str[512]; + int i; + + trap_FS_FOpenFile( filename, &f, FS_WRITE ); + for ( i = 0; i < numFrames; i++ ) { + lastNumber = i; + b = lastNumber / 100; + lastNumber -= b * 100; + c = lastNumber / 10; + lastNumber -= c * 10; + d = lastNumber; + + if ( compressedVersionAvailable ) { + Com_sprintf( str, sizeof( str ), "%s%i\n{\n\tnofog%s\n\tallowCompress\n\tcull none\n\t{\n\t\tmapcomp sprites/%s_lg/spr%i%i%i.tga\n\t\tmapnocomp sprites/%s/spr%i%i%i.tga\n\t\tblendFunc %s %s\n%s\t}\n}\n", shaderName, i + 1, nomipmap ? "\n\tnomipmaps" : "", dir, b, c, d, dir, b, c, d, srcBlend, dstBlend, extras ); + } else { + Com_sprintf( str, sizeof( str ), "%s%i\n{\n\tnofog%s\n\tallowCompress\n\tcull none\n\t{\n\t\tmap sprites/%s/spr%i%i%i.tga\n\t\tblendFunc %s %s\n%s\t}\n}\n", shaderName, i + 1, nomipmap ? "\n\tnomipmap" : "", dir, b, c, d, srcBlend, dstBlend, extras ); + } + trap_FS_Write( str, strlen( str ), f ); + } + trap_FS_FCloseFile( f ); +} + +/* +=============== +CG_InitFlameChunks +=============== +*/ +void CG_InitFlameChunks( void ) { + int i; + char filename[MAX_QPATH]; + + CG_ClearFlameChunks(); + +#ifdef GEN_FLAME_SHADER + CG_GenerateShaders( "scripts/flamethrower.shader", + "flamethrowerFire", + FLAME_SPRITE_DIR, + NUM_FLAME_SPRITES, + FLAME_BLEND_SRC, + FLAME_BLEND_DST, + "", + qtrue, qtrue ); + + CG_GenerateShaders( "scripts/blacksmokeanim.shader", + "blacksmokeanim", + "explode1", + 23, + "GL_ZERO", + "GL_ONE_MINUS_SRC_ALPHA", + "\t\talphaGen const 0.2\n", + qfalse, qfalse ); + + CG_GenerateShaders( "scripts/viewflames.shader", + "viewFlashFire", + "clnfire", + 16, + "GL_ONE", + "GL_ONE", + "\t\talphaGen vertex\n\t\trgbGen vertex\n", + qtrue, qtrue ); + + CG_GenerateShaders( "scripts/twiltb.shader", + "twiltb", + "twiltb", + 42, + "GL_SRC_ALPHA", + "GL_ONE_MINUS_SRC_COLOR", + "", + qtrue, qfalse ); + + CG_GenerateShaders( "scripts/twiltb2.shader", + "twiltb2", + "twiltb2", + 45, + "GL_ONE", + "GL_ONE_MINUS_SRC_COLOR", + "", + qtrue, qfalse ); +/* + CG_GenerateShaders( "scripts/expblue.shader", + "expblue", + "expblue", + 25, + "GL_ONE", + "GL_ONE_MINUS_SRC_COLOR", + "", + qfalse, qfalse ); +*/ + CG_GenerateShaders( "scripts/firest.shader", + "firest", + "firest", + 36, + "GL_ONE", + "GL_ONE_MINUS_SRC_COLOR", + "", + qtrue, qfalse ); + + CG_GenerateShaders( "scripts/explode1.shader", + "explode1", + "explode1", + 23, + "GL_ONE", + "GL_ONE_MINUS_SRC_COLOR", + "", + qtrue, qfalse ); + + CG_GenerateShaders( "scripts/funnel.shader", + "funnel", + "funnel", + 21, + "GL_ONE", + "GL_ONE_MINUS_SRC_COLOR", + "", + qfalse, qfalse ); +#endif + + for ( i = 0; i < NUM_FLAME_SPRITES; i++ ) { + Com_sprintf( filename, MAX_QPATH, "flamethrowerFire%i", i + 1 ); + flameShaders[i] = trap_R_RegisterShader( filename ); + } + for ( i = 0; i < NUM_NOZZLE_SPRITES; i++ ) { + Com_sprintf( filename, MAX_QPATH, "nozzleFlame%i", i + 1 ); + nozzleShaders[i] = trap_R_RegisterShader( filename ); + } + initFlameShaders = qfalse; +} + +/* +=============== +CG_AddFlameChunks +=============== +*/ +void CG_AddFlameChunks( void ) { + flameChunk_t *f, *fNext; + + //AngleVectors( cg.refdef.viewangles, NULL, vright, vup ); + VectorCopy( cg.refdef.viewaxis[1], vright ); + VectorCopy( cg.refdef.viewaxis[2], vup ); + + // clear out the volumes so we can rebuild them + memset( centFlameStatus, 0, sizeof( centFlameStatus ) ); + + numClippedFlames = 0; + + // age them + f = activeFlameChunks; + while ( f ) { + if ( !f->dead ) { + if ( cg.time > f->timeEnd ) { + f->dead = qtrue; + } else if ( f->ignitionOnly && ( f->blueLife < ( cg.time - f->timeStart ) ) ) { + f->dead = qtrue; + } else { + CG_MoveFlameChunk( f ); + f->lifeFrac = (float)( cg.time - f->timeStart ) / (float)( f->timeEnd - f->timeStart ); + } + } + f = f->nextGlobal; + } + + // draw each of the headFlameChunk's + f = headFlameChunks; + while ( f ) { + fNext = f->nextHead; // in case it gets removed + if ( f->dead ) { + if ( centFlameInfo[f->ownerCent].lastFlameChunk == f ) { + centFlameInfo[f->ownerCent].lastFlameChunk = NULL; + centFlameInfo[f->ownerCent].lastClientFrame = 0; + } + CG_FreeFlameChunk( f ); + } else if ( !f->ignitionOnly || ( centFlameInfo[f->ownerCent].lastFlameChunk == f ) ) { // don't draw the ignition flame after we start firing + CG_AddFlameToScene( f ); + } + f = fNext; + } +} + +/* +=============== +CG_UpdateFlamethrowerSounds +=============== +*/ +void CG_UpdateFlamethrowerSounds( void ) { + flameChunk_t *f, *trav; // , *lastSoundFlameChunk=NULL; // TTimo: unused + #define MIN_BLOW_VOLUME 30 + + // draw each of the headFlameChunk's + f = headFlameChunks; + while ( f ) { + // update this entity? + if ( centFlameInfo[f->ownerCent].lastSoundUpdate != cg.time ) { + // blow/ignition sound + if ( centFlameStatus[f->ownerCent].blowVolume * 255.0 > MIN_BLOW_VOLUME ) { + trap_S_AddLoopingSound( f->ownerCent, f->org, vec3_origin, cgs.media.flameBlowSound, (int)( 255.0 * centFlameStatus[f->ownerCent].blowVolume ) ); // JPW NERVE + } else { + trap_S_AddLoopingSound( f->ownerCent, f->org, vec3_origin, cgs.media.flameBlowSound, MIN_BLOW_VOLUME ); // JPW NERVE + } + + if ( centFlameStatus[f->ownerCent].streamVolume ) { + if ( cg_entities[f->ownerCent].currentState.aiChar != AICHAR_ZOMBIE ) { + trap_S_AddLoopingSound( f->ownerCent, f->org, vec3_origin, cgs.media.flameStreamSound, (int)( 255.0 * centFlameStatus[f->ownerCent].streamVolume ) ); // JPW NERVE + } else { + trap_S_AddLoopingSound( f->ownerCent, f->org, vec3_origin, cgs.media.flameCrackSound, (int)( 255.0 * centFlameStatus[f->ownerCent].streamVolume ) ); // JPW NERVE + } + } + + centFlameInfo[f->ownerCent].lastSoundUpdate = cg.time; + } + + // traverse the chunks, spawning flame sound sources as we go + for ( trav = f; trav; trav = trav->nextFlameChunk ) { + // update the sound volume + if ( trav->blueLife + 100 < ( cg.time - trav->timeStart ) ) { + trap_S_AddLoopingSound( trav->ownerCent, trav->org, vec3_origin, cgs.media.flameSound, (int)( 255.0 * ( 0.2 * ( trav->size / FLAME_MAX_SIZE ) ) ) ); + } + } + + f = f->nextHead; + } + + // DHM - Nerve :: No more client side damage + // send client damage updates if required + /* + for (cent=cg_entities, i=0; i centFlameInfo[i].lastDmgUpdate && + centFlameInfo[i].lastDmgUpdate < cg.time - 50 ) // JPW NERVE (cgs.gametype == GT_SINGLE_PLAYER ? 50 : 50)) -- sean changed clientdamage so this isn't a saturation issue any longer + { + if ((cg.snap->ps.pm_flags & PMF_LIMBO) || ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR )) // JPW NERVE + return; // JPW NERVE don't do flame damage to guys in limbo or spectator, they drop out of the game + CG_ClientDamage( i, centFlameInfo[i].lastDmgEnemy, CLDMG_FLAMETHROWER ); + centFlameInfo[i].lastDmgUpdate = cg.time; + } + } + */ +} diff --git a/src/cgame/cg_info.c b/src/cgame/cg_info.c new file mode 100644 index 0000000..aa3664c --- /dev/null +++ b/src/cgame/cg_info.c @@ -0,0 +1,522 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_info.c -- display information while data is being loading + +#include "cg_local.h" + +#define MAX_LOADING_PLAYER_ICONS 16 +#define MAX_LOADING_ITEM_ICONS 26 + +static int loadingPlayerIconCount; +static int loadingItemIconCount; +static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; +static qhandle_t loadingItemIcons[MAX_LOADING_ITEM_ICONS]; + + +/* +=================== +CG_DrawLoadingIcons +=================== +*/ +static void CG_DrawLoadingIcons( void ) { + int n; + int x, y; + + // JOSEPH 5-2-00 Per MAXX + return; + + for ( n = 0; n < loadingPlayerIconCount; n++ ) { + x = 16 + n * 78; + y = 324; + CG_DrawPic( x, y, 64, 64, loadingPlayerIcons[n] ); + } + + for ( n = 0; n < loadingItemIconCount; n++ ) { + y = 400; + if ( n >= 13 ) { + y += 40; + } + x = 16 + n % 13 * 48; + CG_DrawPic( x, y, 32, 32, loadingItemIcons[n] ); + } +} + + +/* +====================== +CG_LoadingString + +====================== +*/ +void CG_LoadingString( const char *s ) { + Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); + + if ( s && s[0] != 0 ) { + CG_Printf( va( "LOADING... %s\n",s ) ); //----(SA) added so you can see from the console what's going on + + } + trap_UpdateScreen(); +} + +/* +=================== +CG_LoadingItem +=================== +*/ +void CG_LoadingItem( int itemNum ) { + gitem_t *item; + + item = &bg_itemlist[itemNum]; + + if ( item->giType == IT_KEY ) { // do not show keys at level startup //----(SA) + return; + } + +//----(SA) Max Kaufman request that we don't show any pacifier stuff for items + return; +//----(SA) end + + + if ( item->icon && loadingItemIconCount < MAX_LOADING_ITEM_ICONS ) { + loadingItemIcons[loadingItemIconCount++] = trap_R_RegisterShaderNoMip( item->icon ); + } + + CG_LoadingString( item->pickup_name ); +} + +/* +=================== +CG_LoadingClient +=================== +*/ +void CG_LoadingClient( int clientNum ) { + const char *info; + char *skin; + char personality[MAX_QPATH]; + char model[MAX_QPATH]; + char iconName[MAX_QPATH]; + + if ( cgs.gametype == GT_SINGLE_PLAYER && clientNum > 0 ) { // for now only show the player's icon in SP games + return; + } + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + + Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } else { + skin = "default"; + } + + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); + +// (SA) ignore player icons for the moment + if ( !( cg_entities[clientNum].currentState.aiChar ) ) { +// if ( loadingPlayerIconCount < MAX_LOADING_PLAYER_ICONS ) { +// loadingPlayerIcons[loadingPlayerIconCount++] = trap_R_RegisterShaderNoMip( iconName ); +// } + } + + Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof( personality ) ); + Q_CleanStr( personality ); + + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality ) ); + } + + CG_LoadingString( personality ); +} + +/* +==================== +CG_DrawStats +==================== +*/ + +typedef struct { + char *label; + int YOfs; + int labelX; + int labelFlags; + vec4_t *labelColor; + + char *format; + int formatX; + int formatFlags; + vec4_t *formatColor; + + int numVars; +} statsItem_t; + +// this defines the layout of the mission stats +// NOTE: these must match the stats sent in AICast_ScriptAction_ChangeLevel() +static statsItem_t statsItems[] = { + //{ "MISSION STATS", 110, 40, UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, &colorWhite, "", 600, UI_SMALLFONT|UI_DROPSHADOW|UI_RIGHT, &colorWhite, 0 }, + + { "Kills", 170, 40, UI_SMALLFONT | UI_DROPSHADOW, &colorWhite, "%3i/%3i", 600, UI_SMALLFONT | UI_DROPSHADOW | UI_RIGHT, &colorWhite, 2 }, + { " Nazis", 40, 40, UI_EXSMALLFONT | UI_DROPSHADOW, &colorWhite, "%3i/%3i", 600, UI_EXSMALLFONT | UI_DROPSHADOW | UI_RIGHT, &colorWhite, 2 }, + { " Monsters", 15, 40, UI_EXSMALLFONT | UI_DROPSHADOW, &colorWhite, "%3i/%3i", 600, UI_EXSMALLFONT | UI_DROPSHADOW | UI_RIGHT, &colorWhite, 2 }, + + { "Time", 30, 40, UI_SMALLFONT | UI_DROPSHADOW, &colorWhite, "%2ih %2im %2is", 600, UI_SMALLFONT | UI_DROPSHADOW | UI_RIGHT, &colorWhite, 3 }, + + { "Secrets", 30, 40, UI_SMALLFONT | UI_DROPSHADOW, &colorWhite, "%i/%i", 600, UI_SMALLFONT | UI_DROPSHADOW | UI_RIGHT, &colorWhite, 2 }, + + { "Attempts", 30, 40, UI_SMALLFONT | UI_DROPSHADOW, &colorWhite, "%i", 600, UI_SMALLFONT | UI_DROPSHADOW | UI_RIGHT, &colorWhite, 1 }, + + { NULL } +}; + +void CG_DrawStats( char *stats ) { + int i, y, v, j; + #define MAX_STATS_VARS 64 + int vars[MAX_STATS_VARS]; + char *str, *token; + char *formatStr = NULL; // TTimo: init + int varIndex; + char string[MAX_QPATH]; + + UI_DrawProportionalString( 320, 120, "MISSION STATS", + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + + Q_strncpyz( string, stats, sizeof( string ) ); + str = string; + // convert commas to spaces + for ( i = 0; str[i]; i++ ) { + if ( str[i] == ',' ) { + str[i] = ' '; + } + } + + for ( i = 0, y = 0, v = 0; statsItems[i].label; i++ ) { + y += statsItems[i].YOfs; + + UI_DrawProportionalString( statsItems[i].labelX, y, statsItems[i].label, + statsItems[i].labelFlags, *statsItems[i].labelColor ); + + if ( statsItems[i].numVars ) { + varIndex = v; + for ( j = 0; j < statsItems[i].numVars; j++ ) { + token = COM_Parse( &str ); + if ( !token || !token[0] ) { + CG_Error( "error parsing mission stats\n" ); + return; + } + + vars[v++] = atoi( token ); + } + + // build the formatStr + switch ( statsItems[i].numVars ) { + case 1: + formatStr = va( statsItems[i].format, vars[varIndex] ); + break; + case 2: + formatStr = va( statsItems[i].format, vars[varIndex], vars[varIndex + 1] ); + break; + case 3: + formatStr = va( statsItems[i].format, vars[varIndex], vars[varIndex + 1], vars[varIndex + 2] ); + break; + case 4: + formatStr = va( statsItems[i].format, vars[varIndex], vars[varIndex + 1], vars[varIndex + 2], vars[varIndex + 3] ); + break; + } + + UI_DrawProportionalString( statsItems[i].formatX, y, formatStr, + statsItems[i].formatFlags, *statsItems[i].formatColor ); + } + } +} + +/* +==================== +CG_DrawInformation + +Draw all the status / pacifier stuff during level loading +==================== +*/ +void CG_DrawInformation( void ) { + const char *s; + const char *info; + const char *sysInfo; + int y; + int value; + qhandle_t levelshot = 0; // TTimo: init +// qhandle_t detail; + char buf[1024]; + static int lastDraw = 0; // Ridah, so we don't draw the screen more often than we need to + int ms; + static int callCount = 0; + float percentDone; + + int expectedHunk; + char hunkBuf[MAX_QPATH]; + + vec4_t color; + + if ( cg.snap && ( strlen( cg_missionStats.string ) <= 1 ) ) { + return; // we are in the world, no need to draw information + } + + if ( callCount ) { // reject recursive calls + return; + } + + ms = trap_Milliseconds(); + if ( ( lastDraw <= ms ) && ( lastDraw > ms - 100 ) ) { + return; + } + lastDraw = ms; + + callCount++; + + info = CG_ConfigString( CS_SERVERINFO ); + sysInfo = CG_ConfigString( CS_SYSTEMINFO ); + + trap_Cvar_VariableStringBuffer( "com_expectedhunkusage", hunkBuf, MAX_QPATH ); + expectedHunk = atoi( hunkBuf ); + + + s = Info_ValueForKey( info, "mapname" ); + if ( s && s[0] != 0 ) { // there is often no 's' + if ( strlen( cg_missionStats.string ) > 1 && cg_missionStats.string[0] == 's' ) { + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/pre_%s_stats.tga", s ) ); + } else { // show briefing screen + if ( s && s[0] != 0 ) { // there is often no 's' + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); + } + } + } + if ( !levelshot ) { + levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ); + } + trap_R_SetColor( NULL ); + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); + + // blend a detail texture over it + //detail = trap_R_RegisterShader( "levelShotDetail" ); + //trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 2.5, 2, detail ); + + +// (SA) commented out for Drew +// UI_DrawProportionalString( 320, 16, va( "Loading %s", Info_ValueForKey( info, "mapname" ) ), UI_SMALLFONT|UI_CENTER|UI_DROPSHADOW, colorWhite ); + + // show the loading progress + VectorSet( color, 0.8, 0.8, 0.8 ); + color[3] = 0.8; + + if ( strlen( cg_missionStats.string ) > 1 && cg_missionStats.string[0] == 's' ) { + vec2_t xy = { 190, 470 }; + vec2_t wh = { 260, 10 }; + + // draw the mission stats while loading + if ( !Q_strncmp( cg_missionStats.string, "s=", 2 ) ) { + CG_DrawStats( cg_missionStats.string + 2 ); + } + + if ( cg_waitForFire.integer == 1 ) { + // waiting for server to finish loading the map + UI_DrawProportionalString( 320, xy[1] + wh[1] - 10, "press fire to continue", + UI_CENTER | UI_EXSMALLFONT | UI_DROPSHADOW, color ); + } else if ( expectedHunk > 0 ) { + percentDone = (float)( cg_hunkUsed.integer + cg_soundAdjust.integer ) / (float)( expectedHunk ); + if ( percentDone > 0.97 ) { // never actually show 100%, since we are not in the game yet + percentDone = 0.97; + } + CG_HorizontalPercentBar( xy[0] + 10, xy[1] + wh[1] - 10, wh[0] - 20, 10, percentDone ); + } else if ( expectedHunk == -2 ) { + // we're ready, press a key to start playing + if ( ( ms % 1000 ) < 700 ) { // flashing to get our attention + UI_DrawProportionalString( 320, xy[1] + wh[1] - 10, "press fire to begin", + UI_CENTER | UI_EXSMALLFONT | UI_DROPSHADOW, color ); + } + } else { + UI_DrawProportionalString( 320, xy[1] + wh[1] - 10, "please wait", + UI_CENTER | UI_EXSMALLFONT | UI_DROPSHADOW, color ); + } + + trap_UpdateScreen(); + callCount--; + return; + } + + // Ridah, in single player, cheats disabled, don't show unnecessary information + // DHM - Nerve :: maybe temp, but we want to display the same as single player right now + if ( cgs.gametype == GT_SINGLE_PLAYER || cgs.gametype >= GT_WOLF ) { + vec2_t xy = { 200, 468 }; + vec2_t wh = { 240, 10 }; + + // show the server motd + CG_DrawMotd(); + + // show the percent complete bar + if ( expectedHunk > 0 ) { + percentDone = (float)( cg_hunkUsed.integer + cg_soundAdjust.integer ) / (float)( expectedHunk ); + if ( percentDone > 0.97 ) { + percentDone = 0.97; + } + CG_HorizontalPercentBar( xy[0], xy[1], wh[0], wh[1], percentDone ); + } else if ( expectedHunk == -2 ) { + // we're ready, press a key to start playing + if ( ( ms % 1000 ) < 700 ) { // flashing to get our attention + UI_DrawProportionalString( 320, xy[1] - 2, "press fire to begin", + UI_CENTER | UI_EXSMALLFONT | UI_DROPSHADOW, color ); + } + } + + trap_UpdateScreen(); + callCount--; + return; + } + // done. + + // draw the icons of thiings as they are loaded + CG_DrawLoadingIcons(); + + // the first 150 rows are reserved for the client connection + // screen to write into + if ( cg.infoScreenText[0] ) { + UI_DrawProportionalString( 320, 128, va( "Loading... %s", cg.infoScreenText ), + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + } else { + UI_DrawProportionalString( 320, 128, "Awaiting snapshot...", + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + } + + // draw info string information + y = 180; + + // don't print server lines if playing a local game + trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); + if ( !atoi( buf ) ) { + // server hostname + s = Info_ValueForKey( info, "sv_hostname" ); + UI_DrawProportionalString( 320, y, s, + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + + // server-specific message of the day + s = CG_ConfigString( CS_MOTD ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // some extra space after hostname and motd + y += 10; + } + + // map-specific message (long map name) + s = CG_ConfigString( CS_MESSAGE ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // cheats warning + s = Info_ValueForKey( sysInfo, "sv_cheats" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( 320, y, "CHEATS ARE ENABLED", + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // game type + switch ( cgs.gametype ) { + case GT_FFA: + s = "Free For All"; + break; + case GT_SINGLE_PLAYER: + s = "Single Player"; + break; + case GT_TOURNAMENT: + s = "Tournament"; + break; + case GT_TEAM: + s = "Team Deathmatch"; + break; + case GT_CTF: + s = "Capture The Flag"; + break; + // JPW NERVE + case GT_WOLF: + s = "Wolfenstein Multiplayer"; + break; + // jpw + // NERVE - SMF + case GT_WOLF_STOPWATCH: + s = "WolfMP Stopwatch"; + break; + case GT_WOLF_CP: + s = "WolfMP Checkpoint"; + break; + // -NERVE - SMF + // JPW NERVE + case GT_WOLF_CPH: + s = "WolfMP Capture & Hold"; + break; + // jpw + default: + s = "Unknown Gametype"; + break; + } + UI_DrawProportionalString( 320, y, s, + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + + value = atoi( Info_ValueForKey( info, "timelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "timelimit %i", value ), + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + if ( cgs.gametype != GT_CTF && cgs.gametype != GT_SINGLE_PLAYER ) { + value = atoi( Info_ValueForKey( info, "fraglimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "fraglimit %i", value ), + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + } + + if ( cgs.gametype == GT_CTF ) { + value = atoi( Info_ValueForKey( info, "capturelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "capturelimit %i", value ), + UI_CENTER | UI_SMALLFONT | UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + } + + callCount--; +} diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h new file mode 100644 index 0000000..e7fc885 --- /dev/null +++ b/src/cgame/cg_local.h @@ -0,0 +1,2469 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: cg_local.h + * + * desc: The entire cgame module is unloaded and reloaded on each level change, + * so there is NO persistant data between levels on the client side. + * If you absolutely need something stored, it can either be kept + * by the server in the server stored userinfos, or stashed in a cvar. + + * +*/ + +#include "../game/q_shared.h" +#include "tr_types.h" +#include "../game/bg_public.h" +#include "cg_public.h" + + +#define POWERUP_BLINKS 5 + +#define POWERUP_BLINK_TIME 1000 +#define FADE_TIME 200 +#define PULSE_TIME 200 +#define DAMAGE_DEFLECT_TIME 100 +#define DAMAGE_RETURN_TIME 400 +#define DAMAGE_TIME 500 +#define LAND_DEFLECT_TIME 150 +#define LAND_RETURN_TIME 300 +#define STEP_TIME 200 +#define DUCK_TIME 100 +#define PAIN_TWITCH_TIME 200 +#define WEAPON_SELECT_TIME 1400 +#define HOLDABLE_SELECT_TIME 1400 //----(SA) for drawing holdable icons +#define ITEM_SCALEUP_TIME 1000 +#define ZOOM_TIME 150 +#define ITEM_BLOB_TIME 200 +#define MUZZLE_FLASH_TIME 30 //----(SA) +#define SINK_TIME 1000 // time for fragments to sink into ground before going away +#define ATTACKER_HEAD_TIME 10000 +#define REWARD_TIME 3000 + +#define PULSE_SCALE 1.5 // amount to scale up the icons when activating + +#define MAX_STEP_CHANGE 32 + +#define MAX_VERTS_ON_POLY 10 +#define MAX_MARK_POLYS 256 // JPW NERVE was 1024 + +#define STAT_MINUS 10 // num frame for '-' stats digit + +#define ICON_SIZE 48 +#define CHAR_WIDTH 32 +#define CHAR_HEIGHT 48 +#define TEXT_ICON_SPACE 4 + +#define TEAMCHAT_WIDTH 80 +#define TEAMCHAT_HEIGHT 8 + +#define NOTIFY_WIDTH 80 +#define NOTIFY_HEIGHT 5 + +// very large characters +#define GIANT_WIDTH 32 +#define GIANT_HEIGHT 48 + +#define NUM_CROSSHAIRS 10 + +// Ridah, trails +#define STYPE_STRETCH 0 +#define STYPE_REPEAT 1 + +#define TJFL_FADEIN ( 1 << 0 ) +#define TJFL_CROSSOVER ( 1 << 1 ) +#define TJFL_NOCULL ( 1 << 2 ) +#define TJFL_FIXDISTORT ( 1 << 3 ) +#define TJFL_SPARKHEADFLARE ( 1 << 4 ) +#define TJFL_NOPOLYMERGE ( 1 << 5 ) +// done. + +// NERVE - SMF - limbo mode 3d view position +#define LIMBO_3D_X 10 +#define LIMBO_3D_Y 158 +#define LIMBO_3D_W 420 +#define LIMBO_3D_H 312 +// -NERVE - SMF + +//================================================= + +// player entities need to track more information +// than any other type of entity. + +// note that not every player entity is a client entity, +// because corpses after respawn are outside the normal +// client numbering range + +// when changing animation, set animationTime to frameTime + lerping time +// The current lerp will finish out, then it will lerp to the new animation +typedef struct { + int oldFrame; + int oldFrameTime; // time when ->oldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + int animationNumber; // may include ANIM_TOGGLEBIT + int oldAnimationNumber; // may include ANIM_TOGGLEBIT + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact + + // Ridah, variable speed anims + vec3_t oldFramePos; + float animSpeedScale; + int oldFrameSnapshotTime; + headAnimation_t *headAnim; + // done. + +} lerpFrame_t; + +// Ridah, effect defines +#define MAX_ZOMBIE_SPIRITS 1 // JPW NERVE was 4 +#define MAX_ZOMBIE_DEATH_TRAILS 1 // JPW NERVE was 16 + +#define MAX_LOPER_LIGHTNING_POINTS 1 // JPW NERVE was 24 + +#define MAX_TESLA_BOLTS 1 // JPW NERVE was 4 + +#define MAX_EFFECT_ENTS 20 + +typedef struct { + lerpFrame_t legs, torso; + + // Ridah, talking animations + lerpFrame_t head; + // done. + + lerpFrame_t weap; //----(SA) autonomous weapon animations + + int painTime; + int painDuration; + int painDirection; // flip from 0 to 1 + int painAnimTorso; + int painAnimLegs; + int lightningFiring; + + // railgun trail spawning + vec3_t railgunImpact; + qboolean railgunFlash; + + // machinegun spinning + float barrelAngle; + int barrelTime; + qboolean barrelSpinning; + + //----(SA) machinegun bolt sliding + float boltPosition; + int boltTime; + int boltSliding; + //----(SA) end + + //----(SA) 'spinner' spinning (body part) + float spinnerAngle; + int spinnerTime; + qboolean spinnerSpinning; + //----(SA) end + + // Ridah, so we can do fast tag grabbing + refEntity_t legsRefEnt, torsoRefEnt, headRefEnt, gunRefEnt; + int gunRefEntFrame; + + float animSpeed; // for manual adjustment + + // Zombie spirit effect + // !!FIXME: these effects will be restarted by a *_restart command, can we save this data somehow? + qboolean cueZombieSpirit; // if this is qfalse, and the zombie effect flag is set, then we need to start a new attack + int zombieSpiritStartTime; // time the effect was started, so we can fade things in + int zombieSpiritTrailHead[MAX_ZOMBIE_SPIRITS]; + int zombieSpiritRotationTimes[MAX_ZOMBIE_SPIRITS]; + int zombieSpiritRadiusCycleTimes[MAX_ZOMBIE_SPIRITS]; + int lastZombieSpirit; + int nextZombieSpiritSound; + int zombieSpiritEndTime; // time the effect was disabled + vec3_t zombieSpiritPos[MAX_ZOMBIE_SPIRITS]; + vec3_t zombieSpiritDir[MAX_ZOMBIE_SPIRITS]; + float zombieSpiritSpeed[MAX_ZOMBIE_SPIRITS]; + int zombieSpiritStartTimes[MAX_ZOMBIE_SPIRITS]; + + // Zombie death effect + // !!FIXME: these effects will be restarted by a *_restart command, can we save this data somehow? + qboolean cueZombieDeath; // if this is qfalse, and the zombie effect flag is set, then we need to start a new attack + int zombieDeathStartTime; // time the effect was started, so we can fade things in + int zombieDeathEndTime; // time the effect was disabled + int lastZombieDeath; + int zombieDeathFadeStart; + int zombieDeathFadeEnd; + int zombieDeathTrailHead[MAX_ZOMBIE_DEATH_TRAILS]; + int zombieDeathRotationTimes[MAX_ZOMBIE_DEATH_TRAILS]; + int zombieDeathRadiusCycleTimes[MAX_ZOMBIE_DEATH_TRAILS]; + + // loper effects + int loperLastGroundChargeTime; + byte loperGroundChargeToggle; + int loperGroundValidTime; + + vec3_t headLookIdeal; + vec3_t headLookOffset; + float headLookSpeed; + int headLookStopTime; + float headLookSpeedMax; + + // tesla coil effects + vec3_t teslaEndPoints[MAX_TESLA_BOLTS]; + int teslaEndPointTimes[MAX_TESLA_BOLTS]; // time the bolt stays valid + vec3_t teslaOffsetDirs[MAX_TESLA_BOLTS]; // bending direction from center or direct beam + float teslaOffsets[MAX_TESLA_BOLTS]; // amount to offset from center + int teslaOffsetTimes[MAX_TESLA_BOLTS]; // time the offset stays valid + int teslaEnemy[MAX_TESLA_BOLTS]; + int teslaDamageApplyTime; + + int teslaDamagedTime; // time we were last hit by a tesla bolt + + // misc effects + int effectEnts[MAX_EFFECT_ENTS]; + int numEffectEnts; + int effect1EndTime; + vec3_t lightningPoints[MAX_LOPER_LIGHTNING_POINTS]; + int lightningTimes[MAX_LOPER_LIGHTNING_POINTS]; + int lightningSoundTime; + + qboolean forceLOD; + +} playerEntity_t; + +//----(SA) +typedef struct { + char type[MAX_QPATH]; // md3_lower, md3_lbelt, md3_rbelt, etc. + char model[MAX_QPATH]; // lower.md3, belt1.md3, etc. +} skinModel_t; +//----(SA) end + + +//================================================= + + + +// centity_t have a direct corespondence with gentity_t in the game, but +// only the entityState_t is directly communicated to the cgame +typedef struct centity_s { + entityState_t currentState; // from cg.frame + entityState_t nextState; // from cg.nextFrame, if available + qboolean interpolate; // true if next is valid to interpolate to + qboolean currentValid; // true if cg.frame holds this entity + + int muzzleFlashTime; // move to playerEntity? + int overheatTime; + int previousEvent; + int previousEventSequence; // Ridah + int teleportFlag; + + int trailTime; // so missile trails can handle dropped initial packets + int miscTime; + + playerEntity_t pe; + + int errorTime; // decay the error from this time + vec3_t errorOrigin; + vec3_t errorAngles; + + qboolean extrapolated; // false if origin / angles is an interpolation + vec3_t rawOrigin; + vec3_t rawAngles; + + vec3_t beamEnd; + + // exact interpolated position of entity on this frame + vec3_t lerpOrigin; + vec3_t lerpAngles; + + vec3_t lastLerpAngles; // (SA) for remembering the last position when a state changes + + // Ridah, trail effects + int headJuncIndex, headJuncIndex2; + int lastTrailTime; + // done. + + // Ridah + float loopSoundVolume; + vec3_t fireRiseDir; // if standing still this will be up, otherwise it'll point away from movement dir + int lastWeaponClientFrame; + int lastFuseSparkTime; + vec3_t lastFuseSparkOrg; + + // client side dlights + int dl_frame; + int dl_oldframe; + float dl_backlerp; + int dl_time; + char dl_stylestring[64]; + int dl_sound; + int dl_atten; + + lerpFrame_t lerpFrame; //----(SA) added + vec3_t highlightOrigin; // center of the geometry. for things like corona placement on treasure + qboolean usehighlightOrigin; + + refEntity_t refEnt; + int processedFrame; // frame we were last added to the scene + + int voiceChatSprite; // DHM - Nerve + int voiceChatSpriteTime; // DHM - Nerve + + // client-side lightning + int boltTimes[MAX_TESLA_BOLTS]; + vec3_t boltLocs[MAX_TESLA_BOLTS]; + vec3_t boltCrawlDirs[MAX_TESLA_BOLTS]; + + // item highlighting + + int highlightTime; + qboolean highlighted; +} centity_t; + + +//====================================================================== + +// local entities are created as a result of events or predicted actions, +// and live independantly from all server transmitted entities + +typedef struct markPoly_s { + struct markPoly_s *prevMark, *nextMark; + int time; + qhandle_t markShader; + qboolean alphaFade; // fade alpha instead of rgb + float color[4]; + poly_t poly; + polyVert_t verts[MAX_VERTS_ON_POLY]; + + int duration; // Ridah +} markPoly_t; + +//----(SA) moved in from cg_view.c +typedef enum { + ZOOM_NONE, + ZOOM_BINOC, + ZOOM_SNIPER, + ZOOM_SNOOPER, + ZOOM_FG42SCOPE, + ZOOM_MG42, + ZOOM_MAX_ZOOMS +} EZoom_t; + +typedef enum { + ZOOM_OUT, // widest angle + ZOOM_IN // tightest angle (approaching 0) +} EZoomInOut_t; + +extern float zoomTable[ZOOM_MAX_ZOOMS][2]; + +//----(SA) end + +typedef enum { + LE_MARK, + LE_EXPLOSION, + LE_SPRITE_EXPLOSION, + LE_FRAGMENT, + LE_MOVE_SCALE_FADE, + LE_FALL_SCALE_FADE, + LE_FADE_RGB, + LE_SCALE_FADE, + LE_SPARK, + LE_DEBRIS, + LE_BLOOD, + LE_FUSE_SPARK, + LE_ZOMBIE_SPIRIT, + LE_ZOMBIE_BAT, + LE_MOVING_TRACER, + LE_EMITTER +} leType_t; + +typedef enum { + LEF_PUFF_DONT_SCALE = 0x0001 // do not scale size over time + ,LEF_TUMBLE = 0x0002 // tumble over time, used for ejecting shells + ,LEF_NOFADEALPHA = 0x0004 // Ridah, sparks + ,LEF_SMOKING = 0x0008 // (SA) smoking +} leFlag_t; + +typedef enum { + LEMT_NONE, + LEMT_BLOOD +} leMarkType_t; // fragment local entities can leave marks on walls + +typedef enum { + LEBS_NONE, + LEBS_BLOOD, + LEBS_ROCK, + LEBS_WOOD, + LEBS_BRASS, + LEBS_BONE +} leBounceSoundType_t; // fragment local entities can make sounds on impacts + +typedef struct localEntity_s { + struct localEntity_s *prev, *next; + leType_t leType; + int leFlags; + + int startTime; + int endTime; + int fadeInTime; + + float lifeRate; // 1.0 / (endTime - startTime) + + trajectory_t pos; + trajectory_t angles; + + float bounceFactor; // 0.0 = no bounce, 1.0 = perfect + + float color[4]; + + float radius; + + float light; + vec3_t lightColor; + + leMarkType_t leMarkType; // mark to leave on fragment impact + leBounceSoundType_t leBounceSoundType; + + refEntity_t refEntity; + + // Ridah + int lightOverdraw; + int lastTrailTime; + int headJuncIndex, headJuncIndex2; + float effectWidth; + int effectFlags; + struct localEntity_s *chain; // used for grouping entities (like for flamethrower junctions) + int onFireStart, onFireEnd; + int ownerNum; + int lastSpiritDmgTime; + + int loopingSound; + + int breakCount; // break-up this many times before we can break no more + float sizeScale; + // done. + +} localEntity_t; + +//====================================================================== + + +typedef struct { + int client; + int score; + int ping; + int time; + int scoreFlags; + int powerUps; + int accuracy; + int impressiveCount; + int excellentCount; + int guantletCount; + int defendCount; + int assistCount; + int captures; + qboolean perfect; + int team; + int playerClass; // NERVE - SMF + int respawnsLeft; // NERVE - SMF +} score_t; + + +typedef enum { + ACC_BELT_LEFT, // belt left (lower) + ACC_BELT_RIGHT, // belt right (lower) + ACC_BELT, // belt (upper) + ACC_BACK, // back (upper) + ACC_WEAPON, // weapon (upper) + ACC_WEAPON2, // weapon2 (upper) + ACC_HAT, // hat (head) + ACC_MOUTH2, // + ACC_MOUTH3, // + // + ACC_MAX // this is bound by network limits, must change network stream to increase this + // (SA) No, really? that's not true is it? isn't this client-side only? +} accType_t; + +#define ACC_NUM_MOUTH 3 // matches the above count + + + + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a client's configstring changes, +// usually as a result of a userinfo (name, model, etc) change +#define MAX_CUSTOM_SOUNDS 32 +#define MAX_GIB_MODELS 16 +typedef struct { + qboolean infoValid; + + int clientNum; + + char name[MAX_QPATH]; + team_t team; + + int botSkill; // 0 = not bot, 1-5 = bot + + vec3_t color; + + int score; // updated by score servercmds + int location; // location index for team mode + int health; // you only get this info about your teammates + int armor; + int curWeapon; + + int handicap; + int wins, losses; // in tourney mode + + int powerups; // so can display quad/flag status + + int breathPuffTime; + + // when clientinfo is changed, the loading of models/skins/sounds + // can be deferred until you are dead, to prevent hitches in + // gameplay + char modelName[MAX_QPATH]; + char skinName[MAX_QPATH]; + char hSkinName[MAX_QPATH]; + qboolean deferred; + + qhandle_t legsModel; + qhandle_t legsSkin; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + + qboolean isSkeletal; + + //----(SA) added accessory models/skins for belts/backpacks/etc. + qhandle_t accModels[ACC_MAX]; // see def of ACC_MAX for index descriptions + qhandle_t accSkins[ACC_MAX]; // FIXME: put the #define for number of accessory models somewhere. (SA) + + //----(SA) additional parts for specialized characters (the loper's spinning trunk for example) + qhandle_t partModels[1]; // expand to [2], [3], etc as necessary + //----(SA) end + + qhandle_t headModel; + qhandle_t headSkin; + + qhandle_t modelIcon; + + // RF, may be shared by multiple clients/characters + animModelInfo_t *modelInfo; + + sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; + + qhandle_t gibModels[MAX_GIB_MODELS]; + + vec3_t playermodelScale; //----(SA) set in the skin. client-side only + + int blinkTime; //----(SA) + + // NERVE - SMF + char headModelName[MAX_QPATH]; + gender_t gender; // from model + // -NERVE - SMF +} clientInfo_t; + + + +typedef enum { + W_PART_1, + W_PART_2, + W_PART_3, + W_PART_4, + W_PART_5, + W_PART_6, + W_PART_7, + W_MAX_PARTS +} barrelType_t; + +typedef enum { + W_TP_MODEL, // third person model + W_FP_MODEL, // first person model + W_PU_MODEL, // pickup model + W_FP_MODEL_SWAP, // swap out model + W_SKTP_MODEL, // SKELETAL version third person model + W_NUM_TYPES +} modelViewType_t; + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s { + qboolean registered; + gitem_t *item; + +//----(SA) weapon animation sequences loaded from the weapon.cfg + animation_t weapAnimations[MAX_WP_ANIMATIONS]; +//----(SA) end + + qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + + qhandle_t standModel; // not drawn. tags used for positioning weapons for pickup + +//----(SA) mod for 1st/3rd person weap views + qhandle_t weaponModel[W_NUM_TYPES]; + qhandle_t partModels[W_NUM_TYPES][W_MAX_PARTS]; + qhandle_t flashModel[W_NUM_TYPES]; + qhandle_t modModel[W_NUM_TYPES]; // like the scope for the rifles +//----(SA) end + + pose_t position; // wolf locations (high, low, knife, pistol, shoulder, throw) defines are WPOS_HIGH, WPOS_LOW, WPOS_KNIFE, WPOS_PISTOL, WPOS_SHOULDER, WPOS_THROW + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + float flashDlight; + vec3_t flashDlightColor; + sfxHandle_t flashSound[4]; // fast firing weapons randomly choose + sfxHandle_t flashEchoSound[4]; //----(SA) added - distant gun firing sound + sfxHandle_t lastShotSound[4]; // sound of the last shot can be different (mauser doesn't have bolt action on last shot for example) + + qhandle_t weaponIcon[2]; //----(SA) [0] is weap icon, [1] is highlight icon + qhandle_t ammoIcon; + + qhandle_t ammoModel; + + qhandle_t missileModel; + sfxHandle_t missileSound; + void ( *missileTrailFunc )( centity_t *, const struct weaponInfo_s *wi ); + float missileDlight; + vec3_t missileDlightColor; + int missileRenderfx; + + void ( *ejectBrassFunc )( centity_t * ); + + float trailRadius; + float wiTrailTime; + + sfxHandle_t readySound; // an amibient sound the weapon makes when it's /not/ firing + sfxHandle_t firingSound; + sfxHandle_t overheatSound; + sfxHandle_t reloadSound; + + sfxHandle_t spinupSound; //----(SA) added // sound started when fire button goes down, and stepped on when the first fire event happens + sfxHandle_t spindownSound; //----(SA) added // sound called if the above is running but player doesn't follow through and fire +} weaponInfo_t; + + +// each IT_* item has an associated itemInfo_t +// that constains media references necessary to present the +// item and its effects +typedef struct { + qboolean registered; + qhandle_t models[MAX_ITEM_MODELS]; + qhandle_t icons[MAX_ITEM_ICONS]; +} itemInfo_t; + + +typedef struct { + int itemNum; +} powerupInfo_t; + +#define MAX_VIEWDAMAGE 8 +typedef struct { + int damageTime, damageDuration; + float damageX, damageY, damageValue; +} viewDamage_t; + +//====================================================================== + +// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action +// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after + +#define MAX_PREDICTED_EVENTS 16 + +#define MAX_SPAWN_VARS 64 +#define MAX_SPAWN_VARS_CHARS 2048 + +typedef struct { + int clientFrame; // incremented each frame + + int clientNum; + + qboolean demoPlayback; + qboolean levelShot; // taking a level menu screenshot + int deferredPlayerLoading; + qboolean loading; // don't defer players at initial startup + qboolean intermissionStarted; // don't play voice rewards, because game will end shortly + + // there are only one or two snapshot_t that are relevent at a time + int latestSnapshotNum; // the number of snapshots the client system has received + int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet + + snapshot_t *snap; // cg.snap->serverTime <= cg.time + snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL + snapshot_t activeSnapshots[2]; + + float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) + + qboolean thisFrameTeleport; + qboolean nextFrameTeleport; + + int frametime; // cg.time - cg.oldTime + + int time; // this is the time value that the client + // is rendering at. + int oldTime; // time at last frame, used for missile trails and prediction checking + + int physicsTime; // either cg.snap->time or cg.nextSnap->time + + int timelimitWarnings; // 5 min, 1 min, overtime + int fraglimitWarnings; + + qboolean mapRestart; // set on a map restart to set back the weapon + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + centity_t predictedPlayerEntity; + qboolean validPPS; // clear until the first call to CG_PredictPlayerState + int predictedErrorTime; + vec3_t predictedError; + + int eventSequence; + int predictableEvents[MAX_PREDICTED_EVENTS]; + + float stepChange; // for stair up smoothing + int stepTime; + + float duckChange; // for duck viewheight smoothing + int duckTime; + + float landChange; // for landing hard + int landTime; + + // input state sent to server + int weaponSelect; + int holdableSelect; // (SA) which holdable item is currently held ("selected"). When the client is ready to use it, send "use item " + + // auto rotating items + vec3_t autoAnglesSlow; + vec3_t autoAxisSlow[3]; + vec3_t autoAngles; + vec3_t autoAxis[3]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[3]; + + // view rendering + refdef_t refdef; + vec3_t refdefViewAngles; // will be converted to refdef.viewaxis + + // zoom key + qboolean zoomed; + qboolean zoomedBinoc; + int zoomedScope; //----(SA) changed to int + int zoomTime; + float zoomSensitivity; + float zoomval; + + + // information screen text during loading + char infoScreenText[MAX_STRING_CHARS]; + + // scoreboard + int scoresRequestTime; + int numScores; + int selectedScore; + int teamScores[2]; + int teamPlayers[TEAM_NUM_TEAMS]; // JPW NERVE for scoreboard + score_t scores[MAX_CLIENTS]; + qboolean showScores; + qboolean scoreBoardShowing; + int scoreFadeTime; + char killerName[MAX_NAME_LENGTH]; + char spectatorList[MAX_STRING_CHARS]; // list of names + int spectatorLen; // length of list + float spectatorWidth; // width in device units + int spectatorTime; // next time to offset + int spectatorPaintX; // current paint x + int spectatorPaintX2; // current paint x + int spectatorOffset; // current offset from start + int spectatorPaintLen; // current offset from start + + qboolean showItems; + int itemFadeTime; + + qboolean lightstylesInited; + + // centerprinting + int centerPrintTime; + int centerPrintCharWidth; + int centerPrintY; + char centerPrint[1024]; + int centerPrintLines; + int centerPrintPriority; // NERVE - SMF + + // fade in/out + int fadeTime; + float fadeRate; + vec4_t fadeColor1; + vec4_t fadeColor2; + + // low ammo warning state + int lowAmmoWarning; // 1 = low, 2 = empty + + // kill timers for carnage reward + int lastKillTime; + + // crosshair client ID + int crosshairClientNum; + int crosshairClientTime; + + int crosshairPowerupNum; + int crosshairPowerupTime; + + int identifyClientNum; // NERVE - SMF + int identifyClientHealth; // NERVE - SMF + int identifyNextTime; // NERVE - SMF + int identifyClientRequest; // NERVE - SMF + +//----(SA) added + // cursorhints + int cursorHintIcon; + int cursorHintTime; + int cursorHintFade; + int cursorHintValue; +//----(SA) end + + // powerup active flashing + int powerupActive; + int powerupTime; + + // attacking player + int attackerTime; + int voiceTime; + + // reward medals + int rewardTime; + int rewardCount; + qhandle_t rewardShader; + + // warmup countdown + int warmup; + int warmupCount; + + //========================== + + int itemPickup; + int itemPickupTime; + int itemPickupBlendTime; // the pulse around the crosshair is timed seperately + + int holdableSelectTime; //----(SA) for holdable item icon drawing + + int weaponSelectTime; + int weaponAnimation; + int weaponAnimationTime; + + // blend blobs + viewDamage_t viewDamage[MAX_VIEWDAMAGE]; + float damageTime; // last time any kind of damage was recieved + int damageIndex; // slot that was filled in + float damageX, damageY, damageValue; + + int grenLastTime; + + int switchbackWeapon; + int lastFiredWeapon; + int lastWeapSelInBank[MAX_WEAP_BANKS]; // remember which weapon was last selected in a bank for 'weaponbank' commands //----(SA) added +// JPW FIXME NOTE: max_weap_banks > max_weap_banks_mp so this should be OK, but if that changes, change this too + + // status bar head + float headYaw; + float headEndPitch; + float headEndYaw; + int headEndTime; + float headStartPitch; + float headStartYaw; + int headStartTime; + + // view movement + float v_dmg_time; + float v_dmg_pitch; + float v_dmg_roll; + + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + + // RF, view flames when getting burnt + int v_fireTime, v_noFireTime; + vec3_t v_fireRiseDir; + + // temp working variables for player view + float bobfracsin; + int bobcycle; + float xyspeed; + int nextOrbitTime; + + // development tool + refEntity_t testModelEntity; + char testModelName[MAX_QPATH]; + qboolean testGun; + + // RF, new kick angles + vec3_t kickAVel; // for damage feedback, weapon recoil, etc + // This is the angular velocity, to give a smooth + // rotational feedback, rather than sudden jerks + vec3_t kickAngles; // for damage feedback, weapon recoil, etc + // NOTE: this is not transmitted through MSG.C stream + // since weapon kicks are client-side, and damage feedback + // is rare enough that we can transmit that as an event + float recoilPitch, recoilPitchAngle; + + // Duffy + qboolean cameraMode; // if rendering from a camera + // Duffy end + + // NERVE - SMF - Objective info display + qboolean limboMenu; + + int oidTeam; + int oidPrintTime; + int oidPrintCharWidth; + int oidPrintY; + char oidPrint[1024]; + int oidPrintLines; + + // for voice chat buffer + int voiceChatTime; + int voiceChatBufferIn; + int voiceChatBufferOut; + + int newCrosshairIndex; + qhandle_t crosshairShaderAlt[NUM_CROSSHAIRS]; + + int cameraShakeTime; + float cameraShakePhase; + float cameraShakeScale; + float cameraShakeLength; + + qboolean latchVictorySound; + // -NERVE - SMF + + // spawn variables + qboolean spawning; // the CG_Spawn*() functions are valid + int numSpawnVars; + char *spawnVars[MAX_SPAWN_VARS][2]; // key / value pairs + int numSpawnVarChars; + char spawnVarChars[MAX_SPAWN_VARS_CHARS]; + + // Arnout: allow overriding of countdown sounds + char twoMinuteSound_g[MAX_QPATH]; + char twoMinuteSound_a[MAX_QPATH]; + char thirtySecondSound_g[MAX_QPATH]; + char thirtySecondSound_a[MAX_QPATH]; + + pmoveExt_t pmext; + +} cg_t; + +#define NUM_FUNNEL_SPRITES 21 + +#define MAX_LOCKER_DEBRIS 5 + +// all of the model, shader, and sound references that are +// loaded at gamestate time are stored in cgMedia_t +// Other media that can be tied to clients, weapons, or items are +// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t +typedef struct { + qhandle_t charsetShader; + // JOSEPH 4-17-00 + qhandle_t menucharsetShader; + // END JOSEPH + qhandle_t charsetProp; + qhandle_t charsetPropGlow; + qhandle_t charsetPropB; + qhandle_t whiteShader; + + qhandle_t redFlagModel; + qhandle_t blueFlagModel; + + qhandle_t armorModel; + +// JPW NERVE + qhandle_t hudSprintBar; + qhandle_t hudPowerBar; + qhandle_t hudAxisHelmet; + qhandle_t hudAlliedHelmet; + qhandle_t redColorBar; + qhandle_t blueColorBar; +// jpw + qhandle_t teamStatusBar; + + qhandle_t deferShader; + + // gib explosions + qhandle_t gibAbdomen; + qhandle_t gibArm; + qhandle_t gibChest; + qhandle_t gibFist; + qhandle_t gibFoot; + qhandle_t gibForearm; + qhandle_t gibIntestine; + qhandle_t gibLeg; + qhandle_t gibSkull; + qhandle_t gibBrain; + + // debris + qhandle_t debBlock[6]; + qhandle_t debRock[3]; + qhandle_t debFabric[3]; + qhandle_t debWood[6]; + + qhandle_t targetEffectExplosionShader; + + qhandle_t machinegunBrassModel; + qhandle_t panzerfaustBrassModel; //----(SA) added + + // Rafael + qhandle_t smallgunBrassModel; + + qhandle_t shotgunBrassModel; + + qhandle_t railRingsShader; + qhandle_t railCoreShader; + + qhandle_t lightningShader; + + qhandle_t friendShader; + + qhandle_t spawnInvincibleShader; + qhandle_t scoreEliminatedShader; + + qhandle_t voiceChatShader; + qhandle_t medicReviveShader; + qhandle_t balloonShader; + qhandle_t connectionShader; + + qhandle_t aiStateShaders[MAX_AISTATES]; + + qhandle_t selectShader; + qhandle_t viewBloodShader; + qhandle_t tracerShader; + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + qhandle_t lagometerShader; + qhandle_t backTileShader; + qhandle_t noammoShader; + + qhandle_t reticleShader; + qhandle_t reticleShaderSimple; + qhandle_t snooperShader; + qhandle_t snooperShaderSimple; + qhandle_t binocShader; + qhandle_t binocShaderSimple; +// JPW NERVE + qhandle_t fleshSmokePuffShader; // JPW NERVE for bullet hit flesh smoke puffs + qhandle_t nerveTestShader; + qhandle_t idTestShader; + qhandle_t hud1Shader; + qhandle_t hud2Shader; + qhandle_t hud3Shader; + qhandle_t hud4Shader; + qhandle_t hud5Shader; +// jpw + qhandle_t smokePuffShader; + qhandle_t smokePuffRageProShader; + qhandle_t shotgunSmokePuffShader; + qhandle_t waterBubbleShader; + qhandle_t bloodTrailShader; + + qhandle_t nailPuffShader; + +//----(SA) cursor hints + // would be nice to specify these in the menu scripts instead of permanent handles... + qhandle_t usableHintShader; + qhandle_t notUsableHintShader; + qhandle_t doorHintShader; + qhandle_t doorRotateHintShader; + qhandle_t doorLockHintShader; + qhandle_t doorRotateLockHintShader; + qhandle_t mg42HintShader; + qhandle_t breakableHintShader; + qhandle_t chairHintShader; + qhandle_t alarmHintShader; + qhandle_t healthHintShader; + qhandle_t treasureHintShader; + qhandle_t knifeHintShader; + qhandle_t ladderHintShader; + qhandle_t buttonHintShader; + qhandle_t waterHintShader; + qhandle_t cautionHintShader; + qhandle_t dangerHintShader; + qhandle_t secretHintShader; + qhandle_t qeustionHintShader; + qhandle_t exclamationHintShader; + qhandle_t clipboardHintShader; + qhandle_t weaponHintShader; + qhandle_t ammoHintShader; + qhandle_t armorHintShader; + qhandle_t powerupHintShader; + qhandle_t holdableHintShader; + qhandle_t inventoryHintShader; + qhandle_t exitHintShader; + + qhandle_t hintPlrFriendShader; + qhandle_t hintPlrNeutralShader; + qhandle_t hintPlrEnemyShader; + qhandle_t hintPlrUnknownShader; + + // DHM - Nerve :: Multiplayer hints + qhandle_t buildHintShader; + qhandle_t disarmHintShader; + qhandle_t reviveHintShader; + qhandle_t dynamiteHintShader; + // dhm - end +//----(SA) end + + // Rafael + qhandle_t snowShader; + qhandle_t oilParticle; + qhandle_t oilSlick; + // done. + + // Rafael - cannon + qhandle_t smokePuffShaderdirty; + qhandle_t smokePuffShaderb1; + qhandle_t smokePuffShaderb2; + qhandle_t smokePuffShaderb3; + qhandle_t smokePuffShaderb4; + qhandle_t smokePuffShaderb5; + // done + + // Rafael - blood pool + qhandle_t bloodPool; + + // Ridah, viewscreen blood animation + qhandle_t viewBloodAni[5]; + qhandle_t viewFlashBlood; + qhandle_t viewFlashFire[16]; + // done + + // Rafael bats + qhandle_t bats[10]; + // done + + // Rafael shards + qhandle_t shardGlass1; + qhandle_t shardGlass2; + qhandle_t shardWood1; + qhandle_t shardWood2; + qhandle_t shardMetal1; + qhandle_t shardMetal2; + qhandle_t shardCeramic1; + qhandle_t shardCeramic2; + // done + + qhandle_t shardRubble1; + qhandle_t shardRubble2; + qhandle_t shardRubble3; + + + qhandle_t shardJunk[MAX_LOCKER_DEBRIS]; + + qhandle_t numberShaders[11]; + + qhandle_t shadowMarkShader; + qhandle_t shadowFootShader; + qhandle_t shadowTorsoShader; + + qhandle_t botSkillShaders[5]; + + // wall mark shaders + qhandle_t wakeMarkShader; + qhandle_t wakeMarkShaderAnim; + qhandle_t bloodMarkShaders[5]; + qhandle_t bloodDotShaders[5]; + qhandle_t bulletMarkShader; + qhandle_t bulletMarkShaderMetal; + qhandle_t bulletMarkShaderWood; + qhandle_t bulletMarkShaderCeramic; + qhandle_t bulletMarkShaderGlass; + qhandle_t burnMarkShader; + qhandle_t holeMarkShader; + qhandle_t energyMarkShader; + + // powerup shaders + qhandle_t quadShader; + qhandle_t redQuadShader; + qhandle_t quadWeaponShader; + qhandle_t invisShader; + qhandle_t regenShader; + qhandle_t battleSuitShader; + qhandle_t battleWeaponShader; + qhandle_t hastePuffShader; + + // weapon effect models + qhandle_t spearModel; //----(SA) + + qhandle_t bulletFlashModel; + qhandle_t ringFlashModel; + qhandle_t dishFlashModel; + qhandle_t lightningExplosionModel; + + qhandle_t zombieLoogie; + qhandle_t flamebarrel; + qhandle_t mg42muzzleflash; + //qhandle_t mg42muzzleflashgg; + qhandle_t planemuzzleflash; + + // Rafael + qhandle_t crowbar; + + qhandle_t waterSplashModel; + qhandle_t waterSplashShader; + + qhandle_t thirdPersonBinocModel; //----(SA) added + + qhandle_t batModel; + qhandle_t spiritSkullModel; + + // weapon effect shaders + qhandle_t railExplosionShader; + qhandle_t bulletExplosionShader; + qhandle_t rocketExplosionShader; + qhandle_t grenadeExplosionShader; + qhandle_t bfgExplosionShader; + qhandle_t bloodExplosionShader; + + qhandle_t flameThrowerhitShader; + + // special effects models + qhandle_t teleportEffectModel; + qhandle_t teleportEffectShader; + + // scoreboard headers + qhandle_t scoreboardName; + qhandle_t scoreboardPing; + qhandle_t scoreboardScore; + qhandle_t scoreboardTime; + // Ridah + qhandle_t bloodCloudShader; + qhandle_t sparkParticleShader; + qhandle_t smokeTrailShader; + qhandle_t fireTrailShader; + qhandle_t lightningBoltShader; + qhandle_t lightningBoss1Shader; + qhandle_t flamethrowerFireStream; + qhandle_t flamethrowerBlueStream; + qhandle_t flamethrowerFuelStream; + qhandle_t flamethrowerFuelShader; + qhandle_t onFireShader, onFireShader2; + //qhandle_t dripWetShader, dripWetShader2; + qhandle_t viewFadeBlack; + qhandle_t sparkFlareShader; + qhandle_t funnelFireShader[NUM_FUNNEL_SPRITES]; + qhandle_t spotLightShader; + qhandle_t spotLightBeamShader; + qhandle_t lightningHitWallShader; + qhandle_t lightningWaveShader; + qhandle_t bulletParticleTrailShader; + qhandle_t smokeParticleShader; + + // DHM - Nerve :: bullet hitting dirt + qhandle_t dirtParticle1Shader; + qhandle_t dirtParticle2Shader; + qhandle_t dirtParticle3Shader; + + qhandle_t zombieSpiritWallShader; + qhandle_t zombieSpiritTrailShader; + qhandle_t zombieSpiritSkullShader; + qhandle_t zombieDeathDustShader; + qhandle_t zombieBodyFadeShader; + qhandle_t zombieHeadFadeShader; + + qhandle_t skeletonSkinShader; + qhandle_t skeletonLegsModel; + qhandle_t skeletonTorsoModel; + qhandle_t skeletonHeadModel; + qhandle_t skeletonLegsSkin; + qhandle_t skeletonTorsoSkin; + qhandle_t skeletonHeadSkin; + + qhandle_t loperGroundChargeShader; + + qhandle_t teslaDamageEffectShader; + qhandle_t teslaAltDamageEffectShader; + qhandle_t viewTeslaDamageEffectShader; + qhandle_t viewTeslaAltDamageEffectShader; + // done. + +//----(SA) + // proto/super/heini armor parts + qhandle_t protoArmor[9 * 3]; // 9 parts, 3 sections each (nodam, dam1, dam2) + qhandle_t superArmor[16 * 3]; // 14 parts, 3 sections each + qhandle_t heinrichArmor[22 * 3]; // 20 parts, 3 sections each +//----(SA) end + + // medals shown during gameplay + qhandle_t medalImpressive; + qhandle_t medalExcellent; + qhandle_t medalGauntlet; + + // sounds + sfxHandle_t n_health; + sfxHandle_t noFireUnderwater; + sfxHandle_t snipersound; + sfxHandle_t quadSound; + sfxHandle_t tracerSound; + sfxHandle_t selectSound; + sfxHandle_t useNothingSound; + sfxHandle_t wearOffSound; + sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; + sfxHandle_t sfx_lghit1; + sfxHandle_t sfx_lghit2; + sfxHandle_t sfx_lghit3; + sfxHandle_t sfx_ric1; + sfxHandle_t sfx_ric2; + sfxHandle_t sfx_ric3; + sfxHandle_t sfx_railg; + sfxHandle_t sfx_rockexp; + sfxHandle_t sfx_rockexpDist; // JPW NERVE + sfxHandle_t sfx_dynamiteexp; + sfxHandle_t sfx_dynamiteexpDist; //----(SA) added + sfxHandle_t sfx_spearhit; + sfxHandle_t sfx_knifehit[5]; + sfxHandle_t sfx_bullet_metalhit[3]; + sfxHandle_t sfx_bullet_woodhit[3]; + sfxHandle_t sfx_bullet_roofhit[3]; + sfxHandle_t sfx_bullet_ceramichit[3]; + sfxHandle_t sfx_bullet_glasshit[3]; + sfxHandle_t gibSound; + sfxHandle_t gibBounce1Sound; + sfxHandle_t gibBounce2Sound; + sfxHandle_t gibBounce3Sound; + sfxHandle_t teleInSound; + sfxHandle_t teleOutSound; + sfxHandle_t noAmmoSound; + sfxHandle_t respawnSound; + sfxHandle_t talkSound; + sfxHandle_t landSound; + sfxHandle_t fallSound; + sfxHandle_t jumpPadSound; + +// JPW NERVE +// sfxHandle_t oneMinuteSound; +// sfxHandle_t fiveMinuteSound; +// sfxHandle_t suddenDeathSound; + + sfxHandle_t twoMinuteSound_g, twoMinuteSound_a; + sfxHandle_t thirtySecondSound_g, thirtySecondSound_a; +// jpw + sfxHandle_t threeFragSound; + sfxHandle_t twoFragSound; + sfxHandle_t oneFragSound; + + sfxHandle_t hitSound; + sfxHandle_t hitTeamSound; + sfxHandle_t impressiveSound; + sfxHandle_t excellentSound; + sfxHandle_t deniedSound; + sfxHandle_t humiliationSound; + + sfxHandle_t takenLeadSound; + sfxHandle_t tiedLeadSound; + sfxHandle_t lostLeadSound; + + sfxHandle_t watrInSound; + sfxHandle_t watrOutSound; + sfxHandle_t watrUnSound; + +// sfxHandle_t flightSound; + sfxHandle_t underWaterSound; + sfxHandle_t medkitSound; + sfxHandle_t wineSound; + sfxHandle_t elecSound; + sfxHandle_t fireSound; + sfxHandle_t waterSound; + + // teamplay sounds + sfxHandle_t redLeadsSound; + sfxHandle_t blueLeadsSound; + sfxHandle_t teamsTiedSound; + + // tournament sounds + sfxHandle_t count3Sound; + sfxHandle_t count2Sound; + sfxHandle_t count1Sound; + sfxHandle_t countFightSound; + sfxHandle_t countPrepareSound; + + //----(SA) added + sfxHandle_t debBounce1Sound; + sfxHandle_t debBounce2Sound; + sfxHandle_t debBounce3Sound; + //----(SA) end + + //----(SA) added + sfxHandle_t grenadePulseSound4; + sfxHandle_t grenadePulseSound3; + sfxHandle_t grenadePulseSound2; + sfxHandle_t grenadePulseSound1; + //----(SA) + +//----(SA) added + sfxHandle_t sparkSounds[2]; +//----(SA) + + // Ridah + sfxHandle_t flameSound; + sfxHandle_t flameBlowSound; + sfxHandle_t flameStartSound; + sfxHandle_t flameStreamSound; + sfxHandle_t lightningSounds[3]; + sfxHandle_t lightningZap; + sfxHandle_t flameCrackSound; + sfxHandle_t boneBounceSound; + + sfxHandle_t zombieSpiritSound; + sfxHandle_t zombieSpiritLoopSound; + sfxHandle_t zombieDeathSound; + + sfxHandle_t loperLightningSounds[3]; + sfxHandle_t loperLightningZap; + + sfxHandle_t lightningClap[5]; + + sfxHandle_t batsFlyingLoopSound; + + sfxHandle_t grenadebounce1; + sfxHandle_t grenadebounce2; + + sfxHandle_t dynamitebounce1; //----(SA) added + + sfxHandle_t fbarrelexp1; + sfxHandle_t fbarrelexp2; + + sfxHandle_t fkickwall; + sfxHandle_t fkickflesh; + sfxHandle_t fkickmiss; + + int bulletHitFleshScript; + + int teslaZapScript; + sfxHandle_t teslaLoopSound; + // done. + + qhandle_t cursor; + qhandle_t selectCursor; + qhandle_t sizeCursor; + +} cgMedia_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate. It will NOT +// be cleared when a tournement restart is done, allowing +// all clients to begin playing instantly +typedef struct { + gameState_t gameState; // gamestate from server + glconfig_t glconfig; // rendering configuration + float screenXScale; // derived from glconfig + float screenYScale; + float screenXBias; + + int serverCommandSequence; // reliable command stream counter + int processedSnapshotNum; // the number of snapshots cgame has requested + + qboolean localServer; // detected on startup by checking sv_running + + // parsed from serverinfo + gametype_t gametype; + int antilag; + + // Rafael gameskill + gameskill_t gameskill; + // done + + int dmflags; + int teamflags; + int fraglimit; + int capturelimit; + float timelimit; // NERVE - SMF - made this a float + int maxclients; + char mapname[MAX_QPATH]; + char redTeam[MAX_QPATH]; // A team + char blueTeam[MAX_QPATH]; // B team + + int voteTime; + int voteYes; + int voteNo; + qboolean voteModified; // beep whenever changed + char voteString[MAX_STRING_TOKENS]; + + int teamVoteTime[2]; + int teamVoteYes[2]; + int teamVoteNo[2]; + qboolean teamVoteModified[2]; // beep whenever changed + char teamVoteString[2][MAX_STRING_TOKENS]; + + int levelStartTime; + + int scores1, scores2; // from configstrings + + // + // locally derived information from gamestate + // + qhandle_t gameModels[MAX_MODELS]; + sfxHandle_t gameSounds[MAX_SOUNDS]; + + int numInlineModels; + qhandle_t inlineDrawModel[MAX_MODELS]; + vec3_t inlineModelMidpoints[MAX_MODELS]; + + clientInfo_t clientinfo[MAX_CLIENTS]; + + // teamchat width is *3 because of embedded color codes + char teamChatMsgs[TEAMCHAT_HEIGHT][TEAMCHAT_WIDTH * 3 + 1]; + int teamChatMsgTimes[TEAMCHAT_HEIGHT]; + int teamChatPos; + int teamLastChatPos; + + // New notify mechanism for obits + char notifyMsgs[NOTIFY_HEIGHT][NOTIFY_WIDTH * 3 + 1]; + int notifyMsgTimes[NOTIFY_HEIGHT]; + int notifyPos; + int notifyLastPos; + + int cursorX; + int cursorY; + qboolean eventHandling; + qboolean mouseCaptured; + qboolean sizingHud; + void *capturedItem; + qhandle_t activeCursor; + + // screen fading + float fadeAlpha, fadeAlphaCurrent; + int fadeStartTime; + int fadeDuration; + + // media + cgMedia_t media; + + // player/AI model scripting (client repository) + animScriptData_t animScriptData; + + // NERVE - SMF + int currentVoiceClient; + int currentRound; + float nextTimeLimit; + int minclients; + gamestate_t gamestate; + // -NERVE - SMF + + int complaintClient; // DHM - Nerve + int complaintEndTime; // DHM - Nerve + float smokeWindDir; // JPW NERVE for smoke puffs & wind (arty, airstrikes, bullet impacts) +} cgs_t; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t cg; +extern centity_t cg_entities[MAX_GENTITIES]; +extern weaponInfo_t cg_weapons[MAX_WEAPONS]; +extern itemInfo_t cg_items[MAX_ITEMS]; +extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; + +extern vmCvar_t cg_centertime; +extern vmCvar_t cg_runpitch; +extern vmCvar_t cg_runroll; +extern vmCvar_t cg_bobup; +extern vmCvar_t cg_bobpitch; +extern vmCvar_t cg_bobroll; +extern vmCvar_t cg_swingSpeed; +extern vmCvar_t cg_shadows; +extern vmCvar_t cg_gibs; +extern vmCvar_t cg_drawTimer; +extern vmCvar_t cg_drawFPS; +extern vmCvar_t cg_drawSnapshot; +extern vmCvar_t cg_draw3dIcons; +extern vmCvar_t cg_drawIcons; +extern vmCvar_t cg_drawAmmoWarning; +extern vmCvar_t cg_drawCrosshair; +extern vmCvar_t cg_drawCrosshairNames; +extern vmCvar_t cg_drawCrosshairPickups; +extern vmCvar_t cg_hudAlpha; +extern vmCvar_t cg_useWeapsForZoom; +extern vmCvar_t cg_weaponCycleDelay; //----(SA) added +extern vmCvar_t cg_cycleAllWeaps; +extern vmCvar_t cg_drawAllWeaps; +extern vmCvar_t cg_drawRewards; +extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_uselessNostalgia; // JPW NERVE +extern vmCvar_t cg_crosshairX; +extern vmCvar_t cg_crosshairY; +extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_crosshairHealth; +extern vmCvar_t cg_drawStatus; +extern vmCvar_t cg_draw2D; +extern vmCvar_t cg_drawFrags; +extern vmCvar_t cg_animSpeed; +extern vmCvar_t cg_debugAnim; +extern vmCvar_t cg_debugPosition; +extern vmCvar_t cg_debugEvents; +extern vmCvar_t cg_drawSpreadScale; +extern vmCvar_t cg_railTrailTime; +extern vmCvar_t cg_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_noPlayerAnims; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_markTime; +extern vmCvar_t cg_brassTime; +extern vmCvar_t cg_gun_frame; +extern vmCvar_t cg_gun_x; +extern vmCvar_t cg_gun_y; +extern vmCvar_t cg_gun_z; +extern vmCvar_t cg_drawGun; +extern vmCvar_t cg_drawFPGun; +extern vmCvar_t cg_drawGamemodels; +extern vmCvar_t cg_cursorHints; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_letterbox; //----(SA) added +extern vmCvar_t cg_tracerChance; +extern vmCvar_t cg_tracerWidth; +extern vmCvar_t cg_tracerLength; +extern vmCvar_t cg_tracerSpeed; +extern vmCvar_t cg_autoswitch; +extern vmCvar_t cg_ignore; +extern vmCvar_t cg_simpleItems; +extern vmCvar_t cg_fov; +extern vmCvar_t cg_zoomFov; +extern vmCvar_t cg_zoomDefaultBinoc; +extern vmCvar_t cg_zoomDefaultSniper; +extern vmCvar_t cg_zoomDefaultFG; +extern vmCvar_t cg_zoomDefaultSnooper; +extern vmCvar_t cg_zoomStepBinoc; +extern vmCvar_t cg_zoomStepSniper; +extern vmCvar_t cg_zoomStepSnooper; +extern vmCvar_t cg_zoomStepFG; +extern vmCvar_t cg_reticles; +extern vmCvar_t cg_reticleType; +extern vmCvar_t cg_reticleBrightness; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_stereoSeparation; +extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_drawAttacker; +extern vmCvar_t cg_synchronousClients; +extern vmCvar_t cg_teamChatTime; +extern vmCvar_t cg_teamChatHeight; +extern vmCvar_t cg_stats; +extern vmCvar_t cg_forceModel; +extern vmCvar_t cg_coronafardist; +extern vmCvar_t cg_coronas; +extern vmCvar_t cg_buildScript; +extern vmCvar_t cg_paused; +extern vmCvar_t cg_blood; +extern vmCvar_t cg_predictItems; +extern vmCvar_t cg_deferPlayers; +extern vmCvar_t cg_teamChatsOnly; +extern vmCvar_t cg_noVoiceChats; // NERVE - SMF +extern vmCvar_t cg_noVoiceText; // NERVE - SMF +extern vmCvar_t cg_enableBreath; +extern vmCvar_t cg_autoactivate; +extern vmCvar_t cg_emptyswitch; +extern vmCvar_t cg_smoothClients; +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; + +extern vmCvar_t cg_cameraOrbit; +extern vmCvar_t cg_cameraOrbitDelay; +extern vmCvar_t cg_timescaleFadeEnd; +extern vmCvar_t cg_timescaleFadeSpeed; +extern vmCvar_t cg_timescale; +extern vmCvar_t cg_cameraMode; +extern vmCvar_t cg_smallFont; +extern vmCvar_t cg_bigFont; +extern vmCvar_t cg_noTaunt; // NERVE - SMF +extern vmCvar_t cg_voiceSpriteTime; // DHM - Nerve + +extern vmCvar_t cg_blinktime; //----(SA) added + +extern vmCvar_t cg_currentSelectedPlayer; +extern vmCvar_t cg_currentSelectedPlayerName; + +// Rafael - particle switch +extern vmCvar_t cg_wolfparticles; +// done + +// Ridah +extern vmCvar_t cg_gameType; +extern vmCvar_t cg_bloodTime; +extern vmCvar_t cg_norender; +extern vmCvar_t cg_skybox; + +// Rafael gameskill +extern vmCvar_t cg_gameSkill; +// done + +// JPW NERVE +extern vmCvar_t cg_medicChargeTime; +extern vmCvar_t cg_engineerChargeTime; +extern vmCvar_t cg_LTChargeTime; +extern vmCvar_t cg_soldierChargeTime; +extern vmCvar_t cg_redlimbotime; +extern vmCvar_t cg_bluelimbotime; +// jpw + +extern vmCvar_t cg_hunkUsed; +extern vmCvar_t cg_soundAdjust; +extern vmCvar_t cg_expectedhunkusage; + +extern vmCvar_t cg_showAIState; + +extern vmCvar_t cg_notebook; +extern vmCvar_t cg_notebookpages; // bitflags for the currently accessable pages. if they wanna cheat, let 'em. Most won't, or will wait 'til they actually play it. + +extern vmCvar_t cg_animState; +extern vmCvar_t cg_missionStats; +extern vmCvar_t cg_waitForFire; + +// NERVE - SMF - Wolf multiplayer configuration cvars +extern vmCvar_t mp_playerType; +extern vmCvar_t mp_currentPlayerType; +extern vmCvar_t mp_team; +extern vmCvar_t mp_currentTeam; +extern vmCvar_t mp_weapon; +extern vmCvar_t mp_item1; + +extern vmCvar_t cg_drawCompass; +extern vmCvar_t cg_drawNotifyText; +extern vmCvar_t cg_quickMessageAlt; +extern vmCvar_t cg_popupLimboMenu; +extern vmCvar_t cg_descriptiveText; +// -NERVE - SMF + +// TTimo +extern vmCvar_t cg_autoReload; +extern vmCvar_t cg_antilag; + +// +// cg_main.c +// +const char *CG_ConfigString( int index ); +const char *CG_Argv( int arg ); + +float CG_Cvar_Get( const char *cvar ); + +void QDECL CG_Printf( const char *msg, ... ); +void QDECL CG_Error( const char *msg, ... ); + +void CG_StartMusic( void ); + +void CG_UpdateCvars( void ); + +int CG_CrosshairPlayer( void ); +int CG_LastAttacker( void ); +void CG_LoadMenus( const char *menuFile ); +void CG_KeyEvent( int key, qboolean down ); +void CG_MouseEvent( int x, int y ); +void CG_EventHandling( int type ); + +qboolean CG_GetTag( int clientNum, char *tagname, orientation_t * or ); +qboolean CG_GetWeaponTag( int clientNum, char *tagname, orientation_t * or ); + +qboolean CG_CheckCenterView(); + +// +// cg_view.c +// +void CG_TestModel_f( void ); +void CG_TestGun_f( void ); +void CG_TestModelNextFrame_f( void ); +void CG_TestModelPrevFrame_f( void ); +void CG_TestModelNextSkin_f( void ); +void CG_TestModelPrevSkin_f( void ); +void CG_ZoomDown_f( void ); +void CG_ZoomIn_f( void ); +void CG_ZoomOut_f( void ); +void CG_ZoomUp_f( void ); + +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + +void CG_Concussive( centity_t *cent ); +// +// cg_drawtools.c +// +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); +void CG_FillRect( float x, float y, float width, float height, const float *color ); +void CG_HorizontalPercentBar( float x, float y, float width, float height, float percent ); +void CG_DrawMotd(); +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void CG_DrawRotatedPic( float x, float y, float width, float height, qhandle_t hShader, float angle ); // NERVE - SMF +void CG_FilledBar( float x, float y, float w, float h, float *startColor, float *endColor, const float *bgColor, float frac, int flags ); +// JOSEPH 10-26-99 +void CG_DrawStretchPic( float x, float y, float width, float height, qhandle_t hShader ); +// END JOSEPH +void CG_DrawString( float x, float y, const char *string, + float charWidth, float charHeight, const float *modulate ); + + +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); +// JOSEPH 4-17-00 +void CG_DrawStringExt2( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); +// END JOSEPH +void CG_DrawBigString( int x, int y, const char *s, float alpha ); +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); +void CG_DrawSmallString( int x, int y, const char *s, float alpha ); +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); +// JOSEPH 4-25-00 +void CG_DrawBigString2( int x, int y, const char *s, float alpha ); +void CG_DrawBigStringColor2( int x, int y, const char *s, vec4_t color ); +// END JOSEPH +int CG_DrawStrlen( const char *str ); + +float *CG_FadeColor( int startMsec, int totalMsec ); +float *CG_TeamColor( int team ); +void CG_TileClear( void ); +void CG_ColorForHealth( vec4_t hcolor ); +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); + +float UI_ProportionalSizeScale( int style ); +int UI_ProportionalStringWidth( const char* str ); +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); + +// new hud stuff +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); +void CG_DrawSides( float x, float y, float w, float h, float size ); +void CG_DrawTopBottom( float x, float y, float w, float h, float size ); + +// NERVE - SMF - localization functions +void CG_InitTranslation(); +char* CG_TranslateString( const char *string ); +void CG_SaveTransTable(); +void CG_ReloadTranslation(); +// -NERVE - SMF + +// +// cg_draw.c, cg_newDraw.c +// +extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; +extern int numSortedTeamPlayers; +extern int drawTeamOverlayModificationCount; +extern char systemChat[256]; +extern char teamChat1[256]; +extern char teamChat2[256]; +extern char cg_fxflags; // JPW NERVE + +void CG_AddLagometerFrameInfo( void ); +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_CenterPrint( const char *str, int y, int charWidth ); +void CG_PriorityCenterPrint( const char *str, int y, int charWidth, int priority ); // NERVE - SMF +void CG_ObjectivePrint( const char *str, int charWidth ); // NERVE - SMF +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); +void CG_DrawActive( stereoFrame_t stereoView ); +void CG_DrawFlagModel( float x, float y, float w, float h, int team ); + +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ); +void CG_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle ); +void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ); +int CG_Text_Width( const char *text, float scale, int limit ); +int CG_Text_Height( const char *text, float scale, int limit ); +void CG_SelectPrevPlayer(); +void CG_SelectNextPlayer(); +float CG_GetValue( int ownerDraw, int type ); // 'type' is relative or absolute (fractional-'0.5' or absolute- '50' health) +qboolean CG_OwnerDrawVisible( int flags ); +void CG_RunMenuScript( char **args ); +void CG_ShowResponseHead(); +void CG_SetPrintString( int type, const char *p ); +void CG_InitTeamChat(); +void CG_GetTeamColor( vec4_t *color ); +const char *CG_GetGameStatusText(); +const char *CG_GetKillerText(); +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ); +void CG_Text_PaintChar( float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader ); +void CG_CheckOrderPending(); +const char *CG_GameTypeString(); +qboolean CG_YourTeamHasFlag(); +qboolean CG_OtherTeamHasFlag(); +qhandle_t CG_StatusHandle( int task ); +void CG_Fade( int r, int g, int b, int a, float time ); + + + + +// +// cg_player.c +// +qboolean CG_EntOnFire( centity_t *cent ); // Ridah +void CG_Player( centity_t *cent ); +void CG_ResetPlayerEntity( centity_t *cent ); +void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int team, entityState_t *es, const vec3_t fireRiseDir ); +void CG_NewClientInfo( int clientNum ); +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); + +// Rafael particles +extern qboolean initparticles; +int CG_NewParticleArea( int num ); + +// +// cg_predict.c +// +void CG_BuildSolidList( void ); +int CG_PointContents( const vec3_t point, int passEntityNum ); +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ); +void CG_PredictPlayerState( void ); +void CG_LoadDeferredPlayers( void ); + + +// +// cg_events.c +// +void CG_CheckEvents( centity_t *cent ); +const char *CG_PlaceString( int rank ); +void CG_EntityEvent( centity_t *cent, vec3_t position ); +void CG_PainEvent( centity_t *cent, int health, qboolean crouching ); + + +// +// cg_ents.c +// +void CG_SetEntitySoundPosition( centity_t *cent ); +void CG_AddPacketEntities( void ); +void CG_Beam( centity_t *cent ); +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out, vec3_t outDeltaAngles ); + +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + char *tagName, int startIndex, vec3_t *offset ); +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, char *tagName ); + + +//----(SA) +void CG_AttachedPartChange( centity_t *cent ); +void CG_NextItem_f( void ); +void CG_PrevItem_f( void ); +void CG_Item_f( void ); +//----(SA) end + + +// +// cg_weapons.c +// +void CG_LastWeaponUsed_f( void ); //----(SA) added +void CG_NextWeaponInBank_f( void ); //----(SA) added +void CG_PrevWeaponInBank_f( void ); //----(SA) added +void CG_AltWeapon_f( void ); +void CG_NextWeapon_f( void ); +void CG_PrevWeapon_f( void ); +void CG_Weapon_f( void ); +void CG_WeaponBank_f( void ); + +void CG_FinishWeaponChange( int lastweap, int newweap ); + +void CG_RegisterWeapon( int weaponNum ); +void CG_RegisterItemVisuals( int itemNum ); + +void CG_FireWeapon( centity_t *cent ); //----(SA) modified. +//void CG_EndFireWeapon( centity_t *cent, int firemode ); //----(SA) added +void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, int surfaceFlags ); // (SA) modified to send missilehitwall surface parameters + +void CG_MissileHitWallSmall( int weapon, int clientNum, vec3_t origin, vec3_t dir ); +void CG_DrawTracer( vec3_t start, vec3_t finish ); + +// Rafael +void CG_MG42EFX( centity_t *cent ); + +void CG_FLAKEFX( centity_t *cent, int whichgun ); + +void CG_MortarEFX( centity_t *cent ); + +// Ridah +qboolean CG_MonsterUsingWeapon( centity_t *cent, int aiChar, int weaponNum ); + +// Rafael +void CG_MissileHitWall2( int weapon, int clientNum, vec3_t origin, vec3_t dir ); +// done + +void CG_MissileHitPlayer( centity_t *cent, int weapon, vec3_t origin, vec3_t dir, int entityNum ); +//----(SA) +void CG_VenomFire( entityState_t *es, qboolean fullmode ); +//----(SA) +void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum, qboolean wolfkick, int otherEntNum2, int seed ); + +void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end, int type ); //----(SA) added 'type' +void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ); +void CG_AddViewWeapon( playerState_t *ps ); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ); +void CG_DrawWeaponSelect( void ); +void CG_DrawHoldableSelect( void ); + +void CG_OutOfAmmoChange( void ); +void CG_HoldableUsedupChange( void ); //----(SA) added + +//----(SA) added to header to access from outside cg_weapons.c +void CG_AddDebris( vec3_t origin, vec3_t dir, int speed, int duration, int count ); +//----(SA) done + +void CG_ClientDamage( int entnum, int enemynum, int id ); + +// +// cg_marks.c +// +void CG_InitMarkPolys( void ); +void CG_AddMarks( void ); +void CG_ImpactMark( qhandle_t markShader, + const vec3_t origin, const vec3_t dir, + float orientation, + float r, float g, float b, float a, + qboolean alphaFade, + float radius, qboolean temporary, int duration ); + +// Rafael particles +// +// cg_particles.c +// +void CG_ClearParticles( void ); +void CG_AddParticles( void ); +void CG_ParticleSnow( qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum ); +void CG_ParticleSmoke( qhandle_t pshader, centity_t *cent ); +void CG_AddParticleShrapnel( localEntity_t *le ); +void CG_ParticleSnowFlurry( qhandle_t pshader, centity_t *cent ); +void CG_ParticleBulletDebris( vec3_t org, vec3_t vel, int duration ); +void CG_ParticleDirtBulletDebris( vec3_t org, vec3_t vel, int duration ); // DHM - Nerve +void CG_ParticleDirtBulletDebris_Core( vec3_t org, vec3_t vel, int duration, float width, float height, float alpha, char *shadername ); // NERVE - SMF // JPW addtnl params +void CG_ParticleSparks( vec3_t org, vec3_t vel, int duration, float x, float y, float speed ); +void CG_ParticleDust( centity_t *cent, vec3_t origin, vec3_t dir ); +void CG_ParticleMisc( qhandle_t pshader, vec3_t origin, int size, int duration, float alpha ); + +// Ridah +void CG_ParticleExplosion( char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd ); + +// Rafael snow pvs check +void CG_SnowLink( centity_t *cent, qboolean particleOn ); +// done. + +// Rafael bats +void CG_ParticleBat( centity_t *cent ); +void CG_ParticleBats( qhandle_t pshader, centity_t *cent ); +void CG_BatsUpdatePosition( centity_t *cent ); +void CG_ParticleImpactSmokePuff( qhandle_t pshader, vec3_t origin ); +void CG_ParticleImpactSmokePuffExtended( qhandle_t pshader, vec3_t origin, int lifetime, int vel, int acc, int maxroll, float alpha ); // (SA) so I can add more parameters without screwing up the one that's there +void CG_Particle_Bleed( qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration ); +void CG_GetBleedOrigin( vec3_t head_origin, vec3_t torso_origin, vec3_t legs_origin, int fleshEntityNum ); +void CG_Particle_OilParticle( qhandle_t pshader, vec3_t origin, vec3_t origin2, int ptime, int snum ); +void CG_Particle_OilSlick( qhandle_t pshader, centity_t *cent ); +void CG_OilSlickRemove( centity_t *cent ); +void CG_BloodPool( localEntity_t *le, qhandle_t pshader, trace_t *tr ); +void CG_ParticleBloodCloudZombie( centity_t *cent, vec3_t origin, vec3_t dir ); +void CG_ParticleBloodCloud( centity_t *cent, vec3_t origin, vec3_t dir ); +// done + +// Ridah, trails +// +// cg_trails.c +// +int CG_AddTrailJunc( int headJuncIndex, qhandle_t shader, int spawnTime, int sType, vec3_t pos, int trailLife, float alphaStart, float alphaEnd, float startWidth, float endWidth, int flags, vec3_t colorStart, vec3_t colorEnd, float sRatio, float animSpeed ); +int CG_AddSparkJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, float alphaStart, float alphaEnd, float startWidth, float endWidth ); +int CG_AddSmokeJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, float alpha, float startWidth, float endWidth ); +int CG_AddFireJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, float alpha, float startWidth, float endWidth ); +void CG_AddTrails( void ); +void CG_ClearTrails( void ); +// done. + +// Ridah, sound scripting +int CG_SoundScriptPrecache( const char *name ); +qboolean CG_SoundPlaySoundScript( const char *name, vec3_t org, int entnum ); +// TTimo: prototype must match animScriptData_t::playSound +void CG_SoundPlayIndexedScript( int index, vec3_t org, int entnum ); +void CG_SoundInit( void ); +// done. + +// Ridah, flamethrower +void CG_FireFlameChunks( centity_t *cent, vec3_t origin, vec3_t angles, float speedScale, qboolean firing ); +void CG_InitFlameChunks( void ); +void CG_AddFlameChunks( void ); +void CG_UpdateFlamethrowerSounds( void ); +void CG_FlameDamage( int owner, vec3_t org, float radius ); +// done. + +// +// cg_localents.c +// +void CG_InitLocalEntities( void ); +localEntity_t *CG_AllocLocalEntity( void ); +void CG_AddLocalEntities( void ); + +// +// cg_effects.c +// +int CG_GetOriginForTag( centity_t * cent, refEntity_t * parent, char *tagName, int startIndex, vec3_t org, vec3_t axis[3] ); +localEntity_t *CG_SmokePuff( const vec3_t p, + const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ); + +void CG_BubbleTrail( vec3_t start, vec3_t end, float size, float spacing ); +void CG_SpawnEffect( vec3_t org ); +void CG_GibPlayer( centity_t *cent, vec3_t playerOrigin, vec3_t gdir ); +void CG_LoseHat( centity_t *cent, vec3_t dir ); //----(SA) added +void CG_GibHead( vec3_t headOrigin ); + +void CG_Bleed( vec3_t origin, int entityNum ); + +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, int msec, + qboolean isSprite ); +// Ridah +void CG_DynamicLightningBolt( qhandle_t shader, vec3_t start, vec3_t pend, int numBolts, float maxWidth, qboolean fade, float startAlpha, int recursion, int randseed ); +void CG_SparklerSparks( vec3_t origin, int count ); +void CG_ClearFlameChunks( void ); +void CG_ProjectedSpotLight( vec3_t start, vec3_t dir ); +// done. + +//----(SA) +void CG_Spotlight( centity_t *cent, float *color, vec3_t start, vec3_t dir, int segs, float range, int startWidth, float coneAngle, int flags ); +#define SL_NOTRACE 0x001 // don't do a trace check for shortening the beam, always draw at full 'range' length +#define SL_NODLIGHT 0x002 // don't put a dlight at the end +#define SL_NOSTARTCAP 0x004 // dont' cap the start circle +#define SL_LOCKTRACETORANGE 0x010 // only trace out as far as the specified range (rather than to max spot range) +#define SL_NOFLARE 0x020 // don't draw a flare when the light is pointing at the camera +#define SL_NOIMPACT 0x040 // don't draw the impact mark +#define SL_LOCKUV 0x080 // lock the texture coordinates at the 'true' length of the requested beam. +#define SL_NOCORE 0x100 // don't draw the center 'core' beam +#define SL_TRACEWORLDONLY 0x200 +//----(SA) done + +void CG_RumbleEfx( float pitch, float yaw ); + +// +// cg_snapshot.c +// +void CG_ProcessSnapshots( void ); + +// +// cg_spawn.c +// +qboolean CG_SpawnString( const char *key, const char *defaultString, char **out ); +qboolean CG_SpawnFloat( const char *key, const char *defaultString, float *out ); +qboolean CG_SpawnInt( const char *key, const char *defaultString, int *out ); +qboolean CG_SpawnVector( const char *key, const char *defaultString, float *out ); +void CG_ParseEntitiesFromString( void ); + +// +// cg_info.c +// +void CG_LoadingString( const char *s ); +void CG_LoadingItem( int itemNum ); +void CG_LoadingClient( int clientNum ); +void CG_DrawInformation( void ); + +// +// cg_scoreboard.c +// +qboolean CG_DrawScoreboard( void ); +void CG_DrawTourneyScoreboard( void ); + +// +// cg_consolecmds.c +// +qboolean CG_ConsoleCommand( void ); +void CG_InitConsoleCommands( void ); + +// +// cg_servercmds.c +// +void CG_ExecuteNewServerCommands( int latestSequence ); +void CG_ParseServerinfo( void ); +void CG_ParseWolfinfo( void ); // NERVE - SMF +void CG_SetConfigValues( void ); +void CG_ShaderStateChanged( void ); +void CG_SendMoveSpeed( animation_t *animList, int numAnims, char *modelName ); +void CG_LoadVoiceChats(); // NERVE - SMF +void CG_PlayBufferedVoiceChats(); // NERVE - SMF +void CG_AddToNotify( const char *str ); + +// +// cg_playerstate.c +// +void CG_Respawn( void ); +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); + + +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + +// print message on the local console +void trap_Print( const char *fmt ); + +// abort the game +void trap_Error( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related. Get time from the CG_DrawActiveFrame parameter +int trap_Milliseconds( void ); + +// console variable interaction +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +// ServerCommand and ConsoleCommand parameter access +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); + +// filesystem access +// returns length of file +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); + +// add commands to the local console as if they were typed in +// for map changing, etc. The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +void trap_SendConsoleCommand( const char *text ); + +// register a command name so the console can perform command completion. +// FIXME: replace this with a normal console command "defineCommand"? +void trap_AddCommand( const char *cmdName ); + +// send a string to the server over the network +void trap_SendClientCommand( const char *s ); + +// force a screen update, only used during gamestate load +void trap_UpdateScreen( void ); + +// model collision +void trap_CM_LoadMap( const char *mapname ); +int trap_CM_NumInlineModels( void ); +clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ); +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ); +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ); + +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ); +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ); + +// Returns the projection of a polygon onto the solid brushes in the world +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ); + +// normal sounds will have their volume dynamically changed as their entity +// moves and the listener moves +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +void trap_S_StartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx, int flags ); +void trap_S_StopLoopingSound( int entnum ); + +// a local sound is always played full volume +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void trap_S_ClearLoopingSounds( qboolean killall ); +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx, int volume ); +void trap_S_AddRangedLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx, int range ); +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +// Ridah, talking animations +int trap_S_GetVoiceAmplitude( int entityNum ); +// done. + +// repatialize recalculates the volumes of sound as they should be heard by the +// given entityNum and position +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); +sfxHandle_t trap_S_RegisterSound( const char *sample ); // returns buzz if not found +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music +void trap_S_StopBackgroundTrack( void ); +void trap_S_StartStreamingSound( const char *intro, const char *loop, int entnum, int channel, int attenuation ); + +void trap_R_LoadWorldMap( const char *mapname ); + +// all media should be registered during level startup to prevent +// hitches during gameplay +qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found + +qboolean trap_R_GetSkinModel( qhandle_t skinid, const char *type, char *name ); //----(SA) added +qhandle_t trap_R_GetShaderFromModel( qhandle_t modelid, int surfnum, int withlightmap ); //----(SA) added + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); + +// polys are intended for simple wall marks, not really for doing +// significant construction +void trap_R_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts ); +// Ridah +void trap_R_AddPolysToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ); +// done. +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b, int overdraw ); +void trap_R_AddCoronaToScene( const vec3_t org, float r, float g, float b, float scale, int id, qboolean visible ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_DrawRotatedPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, float angle ); // NERVE - SMF +void trap_R_DrawStretchPicGradient( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, const float *gradientColor, int gradientType ); + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +int trap_R_LerpTag( orientation_t *tag, const refEntity_t *refent, const char *tagName, int startIndex ); +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +//----(SA) +void trap_R_SetFog( int fogvar, int var1, int var2, float r, float g, float b, float density ); + +//----(SA) + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in. The time is returned seperately so that +// snapshot latency can be calculated. +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int trap_GetCurrentCmdNumber( void ); + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon/holdable select and zoom +void trap_SetUserCmdValue( int stateValue, int holdValue, float sensitivityScale, int mpSetup, int mpIdentClient ); +void trap_SetClientLerpOrigin( float x, float y, float z ); // DHM - Nerve + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +int trap_MemoryRemaining( void ); +void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ); +qboolean trap_Key_IsDown( int keynum ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +int trap_Key_GetKey( const char *binding ); + +// RF +void trap_SendMoveSpeedsToGame( int entnum, char *movespeeds ); + +typedef enum { + SYSTEM_PRINT, + CHAT_PRINT, + TEAMCHAT_PRINT +} q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration + +void trap_UI_Popup( const char *arg0 ); //----(SA) added + +// NERVE - SMF +qhandle_t getTestShader( void ); // JPW NERVE shhh +void trap_UI_ClosePopup( const char *arg0 ); +void trap_UI_LimboChat( const char *arg0 ); +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ); +void trap_Key_SetBinding( int keynum, const char *binding ); +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); +// -NERVE - SMF + +char* trap_TranslateString( const char *string ); // NERVE - SMF - localization + +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ); +e_status trap_CIN_StopCinematic( int handle ); +e_status trap_CIN_RunCinematic( int handle ); +void trap_CIN_DrawCinematic( int handle ); +void trap_CIN_SetExtents( int handle, int x, int y, int w, int h ); + +void trap_SnapVector( float *v ); + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); + +// Duffy, camera stuff +#define CAM_PRIMARY 0 // the main camera for cutscenes, etc. +qboolean trap_loadCamera( int camNum, const char *name ); +void trap_startCamera( int camNum, int time ); +qboolean trap_getCameraInfo( int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov ); +void CG_StartCamera( const char *name, qboolean startBlack ); + +//----(SA) added +int CG_LoadCamera( const char *name ); +void CG_FreeCamera( int camNum ); +//----(SA) end diff --git a/src/cgame/cg_localents.c b/src/cgame/cg_localents.c new file mode 100644 index 0000000..37f31ef --- /dev/null +++ b/src/cgame/cg_localents.c @@ -0,0 +1,1587 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// cg_localents.c -- every frame, generate renderer commands for locally +// processed entities, like smoke puffs, gibs, shells, etc. + +#include "cg_local.h" + +// Ridah, increased this +//#define MAX_LOCAL_ENTITIES 512 +#define MAX_LOCAL_ENTITIES 768 // renderer can only handle 1024 entities max, so we should avoid + // overwriting game entities +// done. + +localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; +localEntity_t cg_activeLocalEntities; // double linked list +localEntity_t *cg_freeLocalEntities; // single linked list + +// Ridah, debugging +int localEntCount = 0; + +/* +=================== +CG_InitLocalEntities + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitLocalEntities( void ) { + int i; + + memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); + cg_activeLocalEntities.next = &cg_activeLocalEntities; + cg_activeLocalEntities.prev = &cg_activeLocalEntities; + cg_freeLocalEntities = cg_localEntities; + for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { + cg_localEntities[i].next = &cg_localEntities[i + 1]; + } + + // Ridah, debugging + localEntCount = 0; +} + + +/* +================== +CG_FreeLocalEntity +================== +*/ +void CG_FreeLocalEntity( localEntity_t *le ) { + if ( !le->prev ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // Ridah, debugging + localEntCount--; +// trap_Print( va("FreeLocalEntity: locelEntCount = %d\n", localEntCount) ); + // done. + + // remove from the doubly linked active list + le->prev->next = le->next; + le->next->prev = le->prev; + + // the free list is only singly linked + le->next = cg_freeLocalEntities; + cg_freeLocalEntities = le; +} + +/* +=================== +CG_AllocLocalEntity + +Will allways succeed, even if it requires freeing an old active entity +=================== +*/ +localEntity_t *CG_AllocLocalEntity( void ) { + localEntity_t *le; + + if ( !cg_freeLocalEntities ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + CG_FreeLocalEntity( cg_activeLocalEntities.prev ); + } + + // Ridah, debugging + localEntCount++; +// trap_Print( va("AllocLocalEntity: locelEntCount = %d\n", localEntCount) ); + // done. + + le = cg_freeLocalEntities; + cg_freeLocalEntities = cg_freeLocalEntities->next; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->next = cg_activeLocalEntities.next; + le->prev = &cg_activeLocalEntities; + cg_activeLocalEntities.next->prev = le; + cg_activeLocalEntities.next = le; + return le; +} + + +/* +==================================================================================== + +FRAGMENT PROCESSING + +A fragment localentity interacts with the environment in some way (hitting walls), +or generates more localentities along a trail. + +==================================================================================== +*/ + +/* +================ +CG_BloodTrail + +Leave expanding blood puffs behind gibs +================ +*/ +// use this to change between particle and trail code +//#define BLOOD_PARTICLE_TRAIL +void CG_BloodTrail( localEntity_t *le ) { + int t; + int t2; + int step; + vec3_t newOrigin; + +#ifndef BLOOD_PARTICLE_TRAIL + static vec3_t col = {1,1,1}; +#endif + + centity_t *cent; + cent = &cg_entities[le->ownerNum]; + + if ( !cg_blood.integer ) { + return; + } + + // step = 150; +#ifdef BLOOD_PARTICLE_TRAIL + step = 10; +#else + // time it takes to move 3 units + step = ( 1000 * 3 ) / VectorLength( le->pos.trDelta ); +#endif + + if ( cent && cent->currentState.aiChar == AICHAR_ZOMBIE ) { + step = 30; + } + + t = step * ( ( cg.time - cg.frametime + step ) / step ); + t2 = step * ( cg.time / step ); + + for ( ; t <= t2; t += step ) { + BG_EvaluateTrajectory( &le->pos, t, newOrigin ); + +#ifdef BLOOD_PARTICLE_TRAIL + CG_Particle_Bleed( cgs.media.smokePuffShader, newOrigin, vec3_origin, 0, 500 + rand() % 200 ); +#else + + + if ( cent && cent->currentState.aiChar == AICHAR_ZOMBIE ) { + CG_Particle_Bleed( cgs.media.smokePuffShader, newOrigin, vec3_origin, 1, 500 + rand() % 200 ); + } else { + // Ridah, blood trail using trail code (should be faster since we don't have to spawn as many) + le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex, + cgs.media.bloodTrailShader, + t, + STYPE_STRETCH, + newOrigin, + 180, + 1.0, // start alpha + 0.0, // end alpha + 12.0, + 12.0, + TJFL_NOCULL, + col, col, + 0, 0 ); + } +#endif + + } +} + + +/* +================ +CG_FragmentBounceMark +================ +*/ +void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { + int radius; + + if ( le->leMarkType == LEMT_BLOOD ) { + static int lastBloodMark; + + // don't drop too many blood marks + if ( !( lastBloodMark > cg.time || lastBloodMark > cg.time - 100 ) ) { + radius = 16 + ( rand() & 31 ); + CG_ImpactMark( cgs.media.bloodDotShaders[rand() % 5], trace->endpos, trace->plane.normal, random() * 360, + 1,1,1,1, qtrue, radius, qfalse, cg_bloodTime.integer * 1000 ); + + lastBloodMark = cg.time; + } + } + + // don't allow a fragment to make multiple marks, or they + // pile up while settling + le->leMarkType = LEMT_NONE; +} + +/* +================ +CG_FragmentBounceSound +================ +*/ +void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { + if ( le->leBounceSoundType == LEBS_BLOOD ) { + // half the gibs will make splat sounds + if ( rand() & 1 ) { + int r = rand() & 3; + sfxHandle_t s; + + if ( r < 2 ) { + s = cgs.media.gibBounce1Sound; + } else if ( r == 2 ) { + s = cgs.media.gibBounce2Sound; + } else { + s = cgs.media.gibBounce3Sound; + } + trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); + } + } else if ( le->leBounceSoundType == LEBS_BRASS ) { + +//----(SA) added + } else if ( le->leBounceSoundType == LEBS_ROCK ) { + // half the hits will make thunk sounds (this is just to start since we don't even have the sound yet... (SA)) + if ( rand() & 1 ) { + int r = rand() & 3; + sfxHandle_t s; + + if ( r < 2 ) { + s = cgs.media.debBounce1Sound; + } else if ( r == 2 ) { + s = cgs.media.debBounce2Sound; + } else { + s = cgs.media.debBounce3Sound; + } + trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); + } +//----(SA) end + + } else if ( le->leBounceSoundType == LEBS_BONE ) { + + trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.boneBounceSound ); + + } + + // don't allow a fragment to make multiple bounce sounds, + // or it gets too noisy as they settle + le->leBounceSoundType = LEBS_NONE; +} + + +/* +================ +CG_ReflectVelocity +================ +*/ +void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; + BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2 * dot, trace->plane.normal, le->pos.trDelta ); + + VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); + + VectorCopy( trace->endpos, le->pos.trBase ); + le->pos.trTime = cg.time; + + + // check for stop, making sure that even on low FPS systems it doesn't bobble + + if ( le->leMarkType == LEMT_BLOOD && trace->startsolid ) { + //centity_t *cent; + //cent = &cg_entities[trace->entityNum]; + //if (cent && cent->currentState.apos.trType != TR_STATIONARY) + // le->pos.trType = TR_STATIONARY; + } else if ( trace->allsolid || + ( trace->plane.normal[2] > 0 && + ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { + +//----(SA) if it's a fragment and it's not resting on the world... +// if(le->leType == LE_DEBRIS && trace->entityNum < (MAX_ENTITIES - 1)) + if ( le->leType == LE_FRAGMENT && trace->entityNum < ( MAX_ENTITIES - 1 ) ) { + le->pos.trType = TR_GRAVITY_PAUSED; + } else + { + le->pos.trType = TR_STATIONARY; + } + } else { + + } +} + + +//----(SA) added + +/* +============== +CG_AddEmitter +============== +*/ +void CG_AddEmitter( localEntity_t *le ) { + vec3_t dir; + + if ( le->breakCount > cg.time ) { // using 'breakCount' for 'wait' + return; + } + + // TODO: look up particle script and use proper effect rather than this check + //if(water){} + //else if(oil) {} + //else if(steam) {} + //else if(wine) {} + VectorScale( le->angles.trBase, 30, dir ); + CG_Particle_OilParticle( cgs.media.oilParticle, le->pos.trBase, dir, 15000, le->ownerNum ); + + le->breakCount = cg.time + 50; +} + +//----(SA) end + + +/* +================ +CG_AddFragment +================ +*/ +void CG_AddFragment( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + refEntity_t *re; + float flameAlpha = 0.0; // TTimo: init + vec3_t flameDir; + qboolean hasFlame = qfalse; + int i; + + // Ridah + re = &le->refEntity; + if ( !re->fadeStartTime || re->fadeEndTime < le->endTime ) { + if ( le->endTime - cg.time > 5000 ) { + re->fadeStartTime = le->endTime - 5000; + } else { + re->fadeStartTime = le->endTime - 1000; + } + re->fadeEndTime = le->endTime; + } + + // Ridah, flaming gibs + if ( le->onFireStart && ( le->onFireStart < cg.time && le->onFireEnd > cg.time ) ) { + hasFlame = qtrue; + // calc the alpha + flameAlpha = 1.0 - ( (float)( cg.time - le->onFireStart ) / (float)( le->onFireEnd - le->onFireStart ) ); + if ( flameAlpha < 0.0 ) { + flameAlpha = 0.0; + } + if ( flameAlpha > 1.0 ) { + flameAlpha = 1.0; + } + trap_S_AddLoopingSound( -1, le->refEntity.origin, vec3_origin, cgs.media.flameCrackSound, (int)( 20.0 * flameAlpha ) ); + } + +//----(SA) added + if ( le->leFlags & LEF_SMOKING ) { + float alpha; + refEntity_t flash; + + // create a little less smoke + + // TODO: FIXME: this is not quite right, because it'll become fps dependant - in a bad way. + // the slower the fps, the /more/ smoke there'll be, probably driving the fps lower. + if ( !( rand() % 5 ) ) { + alpha = 1.0 - ( (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime ) ); + alpha *= 0.25f; + memset( &flash, 0, sizeof( flash ) ); + CG_PositionEntityOnTag( &flash, &le->refEntity, "tag_flash", 0, NULL ); + CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 1000, 8, 20, 20, alpha ); + } + } +//----(SA) end + + if ( le->pos.trType == TR_STATIONARY ) { + int t; + + // Ridah, add the flame + if ( hasFlame ) { + refEntity_t backupEnt; + + backupEnt = le->refEntity; + + VectorClear( flameDir ); + flameDir[2] = 1; + + le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha ); + VectorCopy( flameDir, le->refEntity.fireRiseDir ); + le->refEntity.customShader = cgs.media.onFireShader; + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.customShader = cgs.media.onFireShader2; + trap_R_AddRefEntityToScene( &le->refEntity ); + + le->refEntity = backupEnt; + } + + t = le->endTime - cg.time; + trap_R_AddRefEntityToScene( &le->refEntity ); + + return; + + } else if ( le->pos.trType == TR_GRAVITY_PAUSED ) { + int t; + + // Ridah, add the flame + if ( hasFlame ) { + refEntity_t backupEnt; + + backupEnt = le->refEntity; + + VectorClear( flameDir ); + flameDir[2] = 1; + + le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha ); + VectorCopy( flameDir, le->refEntity.fireRiseDir ); + le->refEntity.customShader = cgs.media.onFireShader; + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.customShader = cgs.media.onFireShader2; + trap_R_AddRefEntityToScene( &le->refEntity ); + + le->refEntity = backupEnt; + } + + t = le->endTime - cg.time; + trap_R_AddRefEntityToScene( &le->refEntity ); + + + // trace a line from previous position down, to see if I should start falling again + + VectorCopy( le->refEntity.origin, newOrigin ); + newOrigin [2] -= 5; + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_MISSILECLIP ); + + if ( trace.fraction == 1.0 ) { // it's clear, start moving again + VectorClear( le->pos.trDelta ); + VectorClear( le->angles.trDelta ); + le->pos.trType = TR_GRAVITY; // nothing below me, start falling again + } else { + return; + } + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + + if ( hasFlame ) { + // calc the flame dir + VectorSubtract( le->refEntity.origin, newOrigin, flameDir ); + if ( VectorLength( flameDir ) == 0 ) { + flameDir[2] = 1; + // play a burning sound when not moving + trap_S_AddLoopingSound( 0, newOrigin, vec3_origin, cgs.media.flameSound, (int)( 0.3 * 255.0 * flameAlpha ) ); + } else { + VectorNormalize( flameDir ); + // play a flame blow sound when moving + trap_S_AddLoopingSound( 0, newOrigin, vec3_origin, cgs.media.flameBlowSound, (int)( 0.3 * 255.0 * flameAlpha ) ); + } + } + + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); + if ( trace.fraction == 1.0 ) { + // still in free fall + VectorCopy( newOrigin, le->refEntity.origin ); + + if ( le->leFlags & LEF_TUMBLE || le->angles.trType == TR_LINEAR ) { + vec3_t angles; + + BG_EvaluateTrajectory( &le->angles, cg.time, angles ); + AnglesToAxis( angles, le->refEntity.axis ); + if ( le->sizeScale && le->sizeScale != 1.0 ) { + for ( i = 0; i < 3; i++ ) VectorScale( le->refEntity.axis[i], le->sizeScale, le->refEntity.axis[i] ); + } + } + + // Ridah, add the flame + if ( hasFlame ) { + refEntity_t backupEnt; + + backupEnt = le->refEntity; + + le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha ); + VectorCopy( flameDir, le->refEntity.fireRiseDir ); + le->refEntity.customShader = cgs.media.onFireShader; + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.customShader = cgs.media.onFireShader2; + trap_R_AddRefEntityToScene( &le->refEntity ); + + le->refEntity = backupEnt; + } + + trap_R_AddRefEntityToScene( &le->refEntity ); + + // add a blood trail + if ( le->leBounceSoundType == LEBS_BLOOD ) { + CG_BloodTrail( le ); + } + + return; + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels + if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { + CG_FreeLocalEntity( le ); + return; + } + + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + // break on contact? + if ( le->breakCount ) { + clientInfo_t *ci; + int clientNum; + localEntity_t *nle; + vec3_t dir; + + clientNum = le->ownerNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity" ); + } + ci = &cgs.clientinfo[ clientNum ]; + + // spawn some new fragments + for ( i = 0; i <= le->breakCount; i++ ) { + nle = CG_AllocLocalEntity(); + memcpy( &( nle->leType ), &( le->leType ), sizeof( localEntity_t ) - 2 * sizeof( localEntity_t * ) ); + if ( nle->breakCount-- < 2 ) { + nle->refEntity.hModel = ci->gibModels[rand() % 2]; + } else { + nle->refEntity.hModel = ci->gibModels[rand() % 4]; + } + // make it smaller + nle->endTime = cg.time + 5000 + rand() % 2000; + nle->sizeScale *= 0.8; + if ( nle->sizeScale < 0.7 ) { + nle->sizeScale = 0.7; + nle->leBounceSoundType = 0; + } + // move us a bit + VectorNormalize2( nle->pos.trDelta, dir ); + VectorMA( trace.endpos, 4.0 * le->sizeScale * i, dir, nle->pos.trBase ); + // randomize vel a bit + VectorMA( nle->pos.trDelta, VectorLength( nle->pos.trDelta ) * 0.3, bytedirs[rand() % NUMVERTEXNORMALS], nle->pos.trDelta ); + } + // we're done + CG_FreeLocalEntity( le ); + return; + } + + if ( le->pos.trType == TR_STATIONARY && le->leMarkType == LEMT_BLOOD ) { + // RF, disabled for performance reasons in boss1 + //if (le->leBounceSoundType) + // CG_BloodPool (le, cgs.media.bloodPool, &trace); + + // leave a mark + if ( le->leMarkType ) { + CG_FragmentBounceMark( le, &trace ); + } + } + + // Ridah, add the flame + if ( hasFlame ) { + refEntity_t backupEnt; + + backupEnt = le->refEntity; + + le->refEntity.shaderRGBA[3] = ( unsigned char )( 255.0 * flameAlpha ); + VectorCopy( flameDir, le->refEntity.fireRiseDir ); + le->refEntity.customShader = cgs.media.onFireShader; + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.customShader = cgs.media.onFireShader2; + trap_R_AddRefEntityToScene( &le->refEntity ); + + le->refEntity = backupEnt; + } + + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +// Ridah +/* +================ +CG_AddMovingTracer +================ +*/ +void CG_AddMovingTracer( localEntity_t *le ) { + vec3_t start, end, dir; + + BG_EvaluateTrajectory( &le->pos, cg.time, start ); + VectorNormalize2( le->pos.trDelta, dir ); + VectorMA( start, cg_tracerLength.value, dir, end ); + + CG_DrawTracer( start, end ); +} + +/* +================ +CG_AddSparkElements +================ +*/ +void CG_AddSparkElements( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + float time; + float lifeFrac; + + time = (float)( cg.time - cg.frametime ); + + while ( 1 ) { + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + +// if ((le->endTime - le->startTime) > 500) { + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT ); + + // if stuck, kill it + if ( trace.startsolid ) { + // HACK, some walls screw up, so just pass through if starting in a solid + VectorCopy( newOrigin, trace.endpos ); + trace.fraction = 1.0; + } + + // moved some distance + VectorCopy( trace.endpos, le->refEntity.origin ); +/* + } else + { // just move it there + + VectorCopy( newOrigin, le->refEntity.origin ); + trace.fraction = 1.0; + + } +*/ + time += cg.frametime * trace.fraction; + + lifeFrac = (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime ); + + // add a trail + le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex, + le->refEntity.customShader, + le->refEntity.origin, + 200, + 1.0 - lifeFrac, // start alpha + 0.0, //1.0 - lifeFrac, // end alpha + lifeFrac * 2.0 * ( ( ( le->endTime - le->startTime ) > 400 ) + 1 ) * 1.5, + lifeFrac * 2.0 * ( ( ( le->endTime - le->startTime ) > 400 ) + 1 ) * 1.5 ); + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels +// for some reason SFM1.BSP is one big NODROP zone +// if ( trap_CM_PointContents( le->refEntity.origin, 0 ) & CONTENTS_NODROP ) { +// CG_FreeLocalEntity( le ); +// return; +// } + + if ( trace.fraction < 1.0 ) { + // just kill it + CG_FreeLocalEntity( le ); + return; +/* + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + // the intersection is a fraction of the frametime + le->pos.trTime = (int)time; +*/ + } + + if ( trace.fraction == 1.0 || time >= (float)cg.time ) { + return; + } + } + +} + +/* +================ +CG_AddFuseSparkElements +================ +*/ +void CG_AddFuseSparkElements( localEntity_t *le ) { + + float FUSE_SPARK_WIDTH = 1.0; + + int step = 10; + float time; + float lifeFrac; + static vec3_t whiteColor = {1,1,1}; + + time = (float)( le->lastTrailTime ); + + while ( time < cg.time ) { + + // calculate new position + BG_EvaluateTrajectory( &le->pos, time, le->refEntity.origin ); + + lifeFrac = (float)( time - le->startTime ) / (float)( le->endTime - le->startTime ); + + //if (lifeFrac > 0.2) { + // add a trail + le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex, cgs.media.sparkParticleShader, time, STYPE_STRETCH, le->refEntity.origin, (int)( lifeFrac * (float)( le->endTime - le->startTime ) / 2.0 ), + 1.0 /*(1.0 - lifeFrac)*/, 0.0, FUSE_SPARK_WIDTH * ( 1.0 - lifeFrac ), FUSE_SPARK_WIDTH * ( 1.0 - lifeFrac ), TJFL_SPARKHEADFLARE, whiteColor, whiteColor, 0, 0 ); + //} + + time += step; + + le->lastTrailTime = time; + } + +} + +/* +================ +CG_AddBloodElements +================ +*/ +void CG_AddBloodElements( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + float time; + float lifeFrac; + + time = (float)( cg.time - cg.frametime ); + + while ( 1 ) { + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT ); + + // if stuck, kill it + if ( trace.startsolid ) { + // HACK, some walls screw up, so just pass through if starting in a solid + VectorCopy( newOrigin, trace.endpos ); + trace.fraction = 1.0; + } + + // moved some distance + VectorCopy( trace.endpos, le->refEntity.origin ); + time += cg.frametime * trace.fraction; + + lifeFrac = (float)( cg.time - le->startTime ) / (float)( le->endTime - le->startTime ); + + // add a trail + le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex, + cgs.media.bloodTrailShader, + le->refEntity.origin, + 200, + 1.0 - lifeFrac, // start alpha + 1.0 - lifeFrac, // end alpha + 3.0, + 5.0 ); + + if ( trace.fraction < 1.0 ) { + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + // TODO: spawn a blood decal here? + + // the intersection is a fraction of the frametime + le->pos.trTime = (int)time; + } + + if ( trace.fraction == 1.0 || time >= (float)cg.time ) { + return; + } + } + +} + +/* +================ +CG_AddClientCritter +================ +*/ +void CG_AddClientCritter( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + int time, step = 25, i; + vec3_t v, ang, v2, oDelta; + localEntity_t backup; + float oldSpeed, enemyDist, of; + vec3_t enemyPos; + float alpha; + + if ( cg_entities[le->ownerNum].currentState.otherEntityNum2 == cg.snap->ps.clientNum ) { + VectorCopy( cg.snap->ps.origin, enemyPos ); + enemyPos[2] += cg.snap->ps.viewheight; + } else { + VectorCopy( cg_entities[le->ownerNum].currentState.origin2, enemyPos ); + } + + VectorCopy( le->pos.trDelta, oDelta ); + + // vary the enemyPos to create a psuedo-randomness + of = (float)cg.time + le->startTime; + enemyPos[0] += 12 * ( sin( of / 100 ) * cos( of / 78 ) ); + enemyPos[1] += 12 * ( sin( of / 70 ) * cos( of / 82 ) ); + enemyPos[2] += 12 * ( sin( of / 67 ) * cos( of / 98 ) ); + + time = le->lastTrailTime + step; + + while ( time <= cg.time ) { + if ( time > le->refEntity.fadeStartTime ) { + alpha = (float)( time - le->refEntity.fadeStartTime ) / (float)( le->refEntity.fadeEndTime - le->refEntity.fadeStartTime ); + if ( alpha < 0 ) { + alpha = 0; + } else if ( alpha > 1 ) { + alpha = 1; + } + } else { + alpha = 1.0; + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, time, newOrigin ); + + VectorSubtract( enemyPos, le->refEntity.origin, v ); + enemyDist = VectorNormalize( v ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, le->ownerNum, MASK_SHOT ); + + // if stuck, kill it + if ( trace.startsolid || ( trace.fraction < 1.0 ) ) { + // kill it + CG_FreeLocalEntity( le ); + return; + } + + // moved some distance + VectorCopy( trace.endpos, le->refEntity.origin ); + + if ( le->leType == LE_ZOMBIE_SPIRIT ) { + le->headJuncIndex = CG_AddTrailJunc( le->headJuncIndex, + cgs.media.zombieSpiritTrailShader, + time, + STYPE_STRETCH, + le->refEntity.origin, + (int)le->effectWidth, // trail life + 0.3 * alpha, + 0.0, + le->radius, + 0, + 0, //TJFL_FIXDISTORT, + colorWhite, + colorWhite, + 1.0, 1 ); + } + + // tracking factor + if ( le->leType == LE_ZOMBIE_BAT ) { + le->bounceFactor = 3.0 * (float)step / 1000.0; + } else { + le->bounceFactor = 5.0 * (float)step / 1000.0; + } + oldSpeed = VectorLength( le->pos.trDelta ); + + // track the enemy + backup = *le; + VectorSubtract( enemyPos, le->refEntity.origin, v ); + enemyDist = VectorNormalize( v ); + + if ( alpha > 0.5 && ( le->lastSpiritDmgTime < time - 100 ) && enemyDist < 24 ) { + // inflict the damage! + CG_ClientDamage( cg_entities[le->ownerNum].currentState.otherEntityNum2, le->ownerNum, CLDMG_SPIRIT ); + le->lastSpiritDmgTime = time; + } + + VectorMA( le->pos.trDelta, le->bounceFactor * oldSpeed, v, le->pos.trDelta ); + //VectorCopy( v, le->pos.trDelta ); + if ( VectorLength( le->pos.trDelta ) < 1 ) { + CG_FreeLocalEntity( le ); + return; + } + + le->bounceFactor = 5.0 * (float)step / 1000.0; // avoidance factor + + // the intersection is a fraction of the frametime + le->pos.trTime = time; + VectorCopy( le->refEntity.origin, le->pos.trBase ); + VectorNormalize( le->pos.trDelta ); + VectorScale( le->pos.trDelta, oldSpeed, le->pos.trDelta ); + + // now trace ahead of time, if we're going to hit something, then avoid it + // only avoid dangers if we don't have direct sight to the enemy + trap_CM_BoxTrace( &trace, le->refEntity.origin, enemyPos, NULL, NULL, 0, MASK_SOLID ); + if ( trace.fraction < 1.0 ) { + BG_EvaluateTrajectory( &le->pos, time + 1000, newOrigin ); + + // if we would go passed the enemy, don't bother + if ( VectorDistance( le->refEntity.origin, enemyPos ) > VectorDistance( le->refEntity.origin, newOrigin ) ) { + + trap_CM_BoxTrace( &trace, le->refEntity.origin, newOrigin, NULL, NULL, 0, MASK_SOLID ); + + if ( trace.fraction < 1.0 ) { + // make sure we are not heading away from the enemy too much + VectorNormalize2( le->pos.trDelta, v2 ); + if ( DotProduct( v, v2 ) > 0.7 ) { + // avoid world geometry + backup = *le; + le->bounceFactor = ( 1.0 - trace.fraction ) * 10.0 * (float)step / 1000.0; // tracking and avoidance factor + // reflect the velocity on the trace plane + VectorMA( le->pos.trDelta, le->bounceFactor * oldSpeed, trace.plane.normal, le->pos.trDelta ); + if ( VectorLength( le->pos.trDelta ) < 1 ) { + CG_FreeLocalEntity( le ); + return; + } + // the intersection is a fraction of the frametime + le->pos.trTime = time; + VectorCopy( le->refEntity.origin, le->pos.trBase ); + VectorNormalize( le->pos.trDelta ); + VectorScale( le->pos.trDelta, oldSpeed, le->pos.trDelta ); + // + // double check end velocity + VectorNormalize2( le->pos.trDelta, v2 ); + if ( DotProduct( v, v2 ) <= 0.2 ) { + // restore + *le = backup; + } + } + } + } + } + + // set the angles + VectorNormalize2( le->pos.trDelta, v ); + // HACK!!! skull model is back-to-front, need to fix + if ( le->leType == LE_ZOMBIE_SPIRIT ) { + VectorInverse( v ); + } + vectoangles( v, ang ); + AnglesToAxis( ang, le->refEntity.axis ); + // lean when turning + if ( le->leType == LE_ZOMBIE_BAT ) { + VectorSubtract( le->pos.trDelta, oDelta, v2 ); + ang[ROLL] = -5.0 * DotProduct( le->refEntity.axis[1], v2 ); + if ( fabs( ang[ROLL] ) > 80 ) { + if ( ang[ROLL] > 80 ) { + ang[ROLL] = 80; + } else { ang[ROLL] = -80;} + } + } + AnglesToAxis( ang, le->refEntity.axis ); + + // HACK: the skull is slightly higher than the origin + if ( le->leType == LE_ZOMBIE_SPIRIT ) { + // set the size scale + for ( i = 0; i < 3; i++ ) + VectorScale( le->refEntity.axis[i], 0.35, le->refEntity.axis[i] ); + VectorMA( le->refEntity.origin, -10, le->refEntity.axis[2], le->refEntity.origin ); + } + + le->lastTrailTime = time; + time += step; + } + + // Bats, set the frame + if ( le->leType == LE_ZOMBIE_BAT ) { + #define BAT_ANIM_FRAMETIME 30 + le->refEntity.frame = ( cg.time / BAT_ANIM_FRAMETIME + 1 ) % 19; + le->refEntity.oldframe = ( cg.time / BAT_ANIM_FRAMETIME ) % 19; + le->refEntity.backlerp = 1.0 - ( (float)( cg.time % BAT_ANIM_FRAMETIME ) / (float)BAT_ANIM_FRAMETIME ); + } + + // add the sound + if ( le->loopingSound ) { + if ( cg.time > le->refEntity.fadeStartTime ) { + trap_S_AddLoopingSound( 0, le->refEntity.origin, vec3_origin, le->loopingSound, 255 - (int)( 255.0 * (float)( cg.time - le->refEntity.fadeStartTime ) / (float)( le->refEntity.fadeEndTime - le->refEntity.fadeStartTime ) ) ); + } else if ( le->startTime + 1000 > cg.time ) { + trap_S_AddLoopingSound( 0, le->refEntity.origin, vec3_origin, le->loopingSound, (int)( 255.0 * (float)( cg.time - le->startTime ) / 1000.0 ) ); + } else { + trap_S_AddLoopingSound( 0, le->refEntity.origin, vec3_origin, le->loopingSound, 255 ); + } + } + + trap_R_AddRefEntityToScene( &le->refEntity ); +/* + // HACK: the skull is slightly higher than the origin + if (le->leType == LE_ZOMBIE_SPIRIT) { + // set the size scale + for (i=0; i<3; i++) + VectorScale( le->refEntity.axis[i], 1.0/0.35, le->refEntity.axis[i] ); + VectorMA( le->refEntity.origin, 10, le->refEntity.axis[2], le->refEntity.origin ); + } +*/ + // Bats, add the flame + if ( le->leType == LE_ZOMBIE_BAT ) { +// float lightSize, alpha; + // + le->refEntity.shaderRGBA[3] = 255; + VectorNormalize2( le->pos.trDelta, v ); + VectorInverse( v ); + v[2] += 1; + VectorNormalize2( v, le->refEntity.fireRiseDir ); + + le->refEntity.customShader = cgs.media.onFireShader2; + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.shaderTime = 1434; + trap_R_AddRefEntityToScene( &le->refEntity ); +// le->refEntity.customShader = cgs.media.onFireShader; +// le->refEntity.shaderTime = 0; +// trap_R_AddRefEntityToScene( &le->refEntity ); + + le->refEntity.customShader = 0; + le->refEntity.shaderTime = 0; +/* + // drop a dlight + lightSize = 1.0 + 0.2*(sin(1.0*cg.time/50.0) * cos(1.0*cg.time/43.0)); + alpha = 0.2 * (lightSize / 1.2); + trap_R_AddLightToScene( le->refEntity.origin, 150.0 + 80.0*lightSize, 1.000000*alpha, 0.603922*alpha, 0.207843*alpha, 0 ); + // add some sound + trap_S_AddLoopingSound( -1, le->refEntity.origin, vec3_origin, cgs.media.flameSound, 100 ); + trap_S_AddLoopingSound( -1, le->refEntity.origin, vec3_origin, cgs.media.flameBlowSound, 100 ); +*/ + } +} + +/* +================ +CG_AddDebrisElements +================ +*/ +void CG_AddDebrisElements( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + float lifeFrac; + int t, step = 50; + + for ( t = le->lastTrailTime + step; t < cg.time; t += step ) { + // calculate new position + BG_EvaluateTrajectory( &le->pos, t, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, MASK_SHOT ); + + // if stuck, kill it + if ( trace.startsolid ) { + // HACK, some walls screw up, so just pass through if starting in a solid + VectorCopy( newOrigin, trace.endpos ); + trace.fraction = 1.0; + } + + // moved some distance + VectorCopy( trace.endpos, le->refEntity.origin ); + + // add a trail + lifeFrac = (float)( t - le->startTime ) / (float)( le->endTime - le->startTime ); + +#if 0 + // fire +#if 1 // flame + if ( le->effectWidth > 0 ) { + le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex, + cgs.media.fireTrailShader, + le->refEntity.origin, + (int)( 500.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ), // trail life + 1.0, // alpha + 0.5, // end alpha + 3, // start width + le->effectWidth ); // end width +#else // spark line + if ( le->effectWidth > 0 ) { + le->headJuncIndex = CG_AddSparkJunc( le->headJuncIndex, + cgs.media.sparkParticleShader, + le->refEntity.origin, + (int)( 600.0 * ( 0.5 + 0.5 * ( 0.5 - lifeFrac ) ) ), // trail life + 1.0 - lifeFrac * 2, // alpha + 0.5 * ( 1.0 - lifeFrac ), // end alpha + 5.0 * ( 1.0 - lifeFrac ), // start width + 5.0 * ( 1.0 - lifeFrac ) ); // end width +#endif + } +#endif + + // smoke + if ( le->effectFlags & 1 ) { + le->headJuncIndex2 = CG_AddSmokeJunc( le->headJuncIndex2, + cgs.media.smokeTrailShader, + le->refEntity.origin, + (int)( 2000.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ), // trail life + 1.0 * ( trace.fraction == 1.0 ) * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ), // alpha + 1, // start width + (int)( 60.0 * ( 0.5 + 0.5 * ( 1.0 - lifeFrac ) ) ) ); // end width + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels +// if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { +// CG_FreeLocalEntity( le ); +// return; +// } + + if ( trace.fraction < 1.0 ) { + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + if ( VectorLength( le->pos.trDelta ) < 1 ) { + CG_FreeLocalEntity( le ); + return; + } + // the intersection is a fraction of the frametime + le->pos.trTime = t; + } + + le->lastTrailTime = t; + } + +} + +// Rafael Shrapnel +/* +=============== +CG_AddShrapnel +=============== +*/ +void CG_AddShrapnel( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + + if ( le->pos.trType == TR_STATIONARY ) { + // sink into the ground if near the removal time + int t; + float oldZ; + + t = le->endTime - cg.time; + if ( t < SINK_TIME ) { + // we must use an explicit lighting origin, otherwise the + // lighting would be lost as soon as the origin went + // into the ground + VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); + le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; + oldZ = le->refEntity.origin[2]; + le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.origin[2] = oldZ; + } else { + trap_R_AddRefEntityToScene( &le->refEntity ); + CG_AddParticleShrapnel( le ); + } + + return; + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); + if ( trace.fraction == 1.0 ) { + // still in free fall + VectorCopy( newOrigin, le->refEntity.origin ); + + if ( le->leFlags & LEF_TUMBLE ) { + vec3_t angles; + + BG_EvaluateTrajectory( &le->angles, cg.time, angles ); + AnglesToAxis( angles, le->refEntity.axis ); + } + + trap_R_AddRefEntityToScene( &le->refEntity ); + CG_AddParticleShrapnel( le ); + return; + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels + if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { + CG_FreeLocalEntity( le ); + return; + } + + // leave a mark + CG_FragmentBounceMark( le, &trace ); + + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + trap_R_AddRefEntityToScene( &le->refEntity ); + CG_AddParticleShrapnel( le ); +} +// done. + +/* +===================================================================== + +TRIVIAL LOCAL ENTITIES + +These only do simple scaling or modulation before passing to the renderer +===================================================================== +*/ + +/* +==================== +CG_AddFadeRGB +==================== +*/ +void CG_AddFadeRGB( localEntity_t *le ) { + refEntity_t *re; + float c; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) * le->lifeRate; + c *= 0xff; + + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + trap_R_AddRefEntityToScene( re ); +} + +/* +================== +CG_AddMoveScaleFade +================== +*/ +static void CG_AddMoveScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time +// c = ( le->endTime - cg.time ) * le->lifeRate; + if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { + // fade / grow time + c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); + } else { + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + } + + // Ridah, spark + if ( !( le->leFlags & LEF_NOFADEALPHA ) ) { + // done. + re->shaderRGBA[3] = 0xff * c * le->color[3]; + } + + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + c = ( le->endTime - cg.time ) * le->lifeRate; + re->radius = le->radius * ( 1.0 - c ) + 8; + } + + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +=================== +CG_AddScaleFade + +For rocket smokes that hang in place, fade out, and are +removed if the view passes through them. +There are often many of these, so it needs to be simple. +=================== +*/ +static void CG_AddScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + re->radius = le->radius * ( 1.0 - c ) + 8; + } + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +================= +CG_AddFallScaleFade + +This is just an optimized CG_AddMoveScaleFade +For blood mists that drift down, fade out, and are +removed if the view passes through them. +There are often 100+ of these, so it needs to be simple. +================= +*/ +static void CG_AddFallScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; + + re->radius = le->radius * ( 1.0 - c ) + 16; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +================ +CG_AddExplosion +================ +*/ +static void CG_AddExplosion( localEntity_t *ex ) { + refEntity_t *ent; + + ent = &ex->refEntity; + + // add the entity + // RF, don't add if shader is invalid + if ( ent->customShader >= 0 ) { + trap_R_AddRefEntityToScene( ent ); + } + + // add the dlight + if ( ex->light ) { + float light; + + light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = ex->light * light; + trap_R_AddLightToScene( ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2], 0 ); + } +} + +/* +================ +CG_AddSpriteExplosion +================ +*/ +static void CG_AddSpriteExplosion( localEntity_t *le ) { + refEntity_t re; + float c; + + re = le->refEntity; + + c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); + if ( c > 1 ) { + c = 1.0; // can happen during connection problems + } + + re.shaderRGBA[0] = 0xff; + re.shaderRGBA[1] = 0xff; + re.shaderRGBA[2] = 0xff; + re.shaderRGBA[3] = 0xff * c * 0.33; + + re.reType = RT_SPRITE; + re.radius = 42 * ( 1.0 - c ) + 30; + + // Ridah, move away from surface + VectorMA( le->pos.trBase, ( 1.0 - c ), le->pos.trDelta, re.origin ); + // done. + + // RF, don't add if shader is invalid + if ( re.customShader >= 0 ) { + trap_R_AddRefEntityToScene( &re ); + } + + // add the dlight + if ( le->light ) { + float light; + + // Ridah, modified this so the light fades out rather than shrinking + /* + light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = le->light * light; + trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2], 0 ); + */ + light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + trap_R_AddLightToScene( re.origin, le->light, light * le->lightColor[0], light * le->lightColor[1], light * le->lightColor[2], 0 ); + // done. + } +} + + +//============================================================================== + +/* +=================== +CG_AddLocalEntities + +=================== +*/ +void CG_AddLocalEntities( void ) { + localEntity_t *le, *next; + + // walk the list backwards, so any new local entities generated + // (trails, marks, etc) will be present this frame + le = cg_activeLocalEntities.prev; + for ( ; le != &cg_activeLocalEntities ; le = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = le->prev; + + if ( cg.time >= le->endTime ) { + CG_FreeLocalEntity( le ); + continue; + } + switch ( le->leType ) { + default: + CG_Error( "Bad leType: %i", le->leType ); + break; + + // Ridah + case LE_MOVING_TRACER: + CG_AddMovingTracer( le ); + break; + case LE_SPARK: + CG_AddSparkElements( le ); + break; + case LE_FUSE_SPARK: + CG_AddFuseSparkElements( le ); + break; + case LE_DEBRIS: + CG_AddDebrisElements( le ); + break; + case LE_BLOOD: + CG_AddBloodElements( le ); + break; + case LE_ZOMBIE_SPIRIT: + case LE_ZOMBIE_BAT: + CG_AddClientCritter( le ); + break; + // done. + + case LE_MARK: + break; + + case LE_SPRITE_EXPLOSION: + CG_AddSpriteExplosion( le ); + break; + + case LE_EXPLOSION: + CG_AddExplosion( le ); + break; + + case LE_FRAGMENT: // gibs and brass + CG_AddFragment( le ); + break; + + case LE_MOVE_SCALE_FADE: // water bubbles + CG_AddMoveScaleFade( le ); + break; + + case LE_FADE_RGB: // teleporters, railtrails + CG_AddFadeRGB( le ); + break; + + case LE_FALL_SCALE_FADE: // gib blood trails + CG_AddFallScaleFade( le ); + break; + + case LE_SCALE_FADE: // rocket trails + CG_AddScaleFade( le ); + break; + + case LE_EMITTER: + CG_AddEmitter( le ); + break; + + } + } +} + diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c new file mode 100644 index 0000000..99b09c9 --- /dev/null +++ b/src/cgame/cg_main.c @@ -0,0 +1,2409 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: cg_main.c + * + * desc: initialization and primary entry point for cgame + * +*/ + + +#include "cg_local.h" +#include "../ui/ui_shared.h" + +displayContextDef_t cgDC; + +int forceModelModificationCount = -1; +int autoReloadModificationCount = -1; + +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_Shutdown( void ); + + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +#if defined( __MACOS__ ) +#pragma export on +#endif +int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { +#if defined( __MACOS__ ) +#pragma export off +#endif + switch ( command ) { + case CG_INIT: + CG_Init( arg0, arg1, arg2 ); + return 0; + case CG_SHUTDOWN: + CG_Shutdown(); + return 0; + case CG_CONSOLE_COMMAND: + return CG_ConsoleCommand(); + case CG_DRAW_ACTIVE_FRAME: + CG_DrawActiveFrame( arg0, arg1, arg2 ); + return 0; + case CG_CROSSHAIR_PLAYER: + return CG_CrosshairPlayer(); + case CG_LAST_ATTACKER: + return CG_LastAttacker(); + case CG_KEY_EVENT: + CG_KeyEvent( arg0, arg1 ); + return 0; + case CG_MOUSE_EVENT: + cgDC.cursorx = cgs.cursorX; + cgDC.cursory = cgs.cursorY; + CG_MouseEvent( arg0, arg1 ); + return 0; + case CG_EVENT_HANDLING: + CG_EventHandling( arg0 ); + return 0; + case CG_GET_TAG: + return CG_GetTag( arg0, (char *)arg1, (orientation_t *)arg2 ); + case CG_CHECKCENTERVIEW: + return CG_CheckCenterView(); + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + return -1; +} + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[MAX_GENTITIES]; +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + +vmCvar_t cg_railTrailTime; +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_bobup; +vmCvar_t cg_bobpitch; +vmCvar_t cg_bobroll; +vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_gibs; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_draw3dIcons; +vmCvar_t cg_drawIcons; +vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_drawCrosshairPickups; +vmCvar_t cg_hudAlpha; +vmCvar_t cg_weaponCycleDelay; //----(SA) added +vmCvar_t cg_cycleAllWeaps; +vmCvar_t cg_useWeapsForZoom; +vmCvar_t cg_drawAllWeaps; +vmCvar_t cg_drawRewards; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_crosshairX; +vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairHealth; +vmCvar_t cg_draw2D; +vmCvar_t cg_drawFrags; +vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_noVoiceChats; // NERVE - SMF +vmCvar_t cg_noVoiceText; // NERVE - SMF +vmCvar_t cg_drawStatus; +vmCvar_t cg_animSpeed; +vmCvar_t cg_drawSpreadScale; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_markTime; +vmCvar_t cg_brassTime; +vmCvar_t cg_viewsize; +vmCvar_t cg_letterbox; //----(SA) added +vmCvar_t cg_drawGun; +vmCvar_t cg_drawFPGun; +vmCvar_t cg_drawGamemodels; +vmCvar_t cg_cursorHints; //----(SA) added +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_tracerChance; +vmCvar_t cg_tracerWidth; +vmCvar_t cg_tracerLength; +vmCvar_t cg_tracerSpeed; +vmCvar_t cg_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_zoomFov; +vmCvar_t cg_zoomStepBinoc; +vmCvar_t cg_zoomStepSniper; +vmCvar_t cg_zoomStepSnooper; +vmCvar_t cg_zoomStepFG; //----(SA) added +vmCvar_t cg_zoomDefaultBinoc; +vmCvar_t cg_zoomDefaultSniper; +vmCvar_t cg_zoomDefaultSnooper; +vmCvar_t cg_zoomDefaultFG; //----(SA) added +vmCvar_t cg_reticles; +vmCvar_t cg_reticleType; +vmCvar_t cg_reticleBrightness; //----(SA) added +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawAttacker; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_teamChatTime; +vmCvar_t cg_teamChatHeight; +vmCvar_t cg_stats; +vmCvar_t cg_buildScript; +vmCvar_t cg_forceModel; +vmCvar_t cg_coronafardist; +vmCvar_t cg_coronas; +vmCvar_t cg_paused; +vmCvar_t cg_blood; +vmCvar_t cg_predictItems; +vmCvar_t cg_deferPlayers; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_uselessNostalgia; // JPW NERVE +vmCvar_t cg_enableBreath; +vmCvar_t cg_autoactivate; +vmCvar_t cg_blinktime; //----(SA) added + +vmCvar_t cg_smoothClients; +vmCvar_t pmove_fixed; +vmCvar_t pmove_msec; + +// Rafael - particle switch +vmCvar_t cg_wolfparticles; +// done + +// Ridah +vmCvar_t cg_gameType; +vmCvar_t cg_bloodTime; +vmCvar_t cg_norender; +vmCvar_t cg_skybox; + +// Rafael +vmCvar_t cg_gameSkill; +// done + +// JPW NERVE +vmCvar_t cg_medicChargeTime; +vmCvar_t cg_engineerChargeTime; +vmCvar_t cg_LTChargeTime; +vmCvar_t cg_soldierChargeTime; +vmCvar_t cg_redlimbitime; +vmCvar_t cg_bluelimbotime; +// jpw + +vmCvar_t cg_hunkUsed; +vmCvar_t cg_soundAdjust; +vmCvar_t cg_expectedhunkusage; + +vmCvar_t cg_showAIState; + +vmCvar_t cg_notebook; +vmCvar_t cg_notebookpages; // bitflags for the currently accessable pages. if they wanna cheat, let 'em. Most won't, or will wait 'til they actually play it. + +vmCvar_t cg_currentSelectedPlayer; +vmCvar_t cg_currentSelectedPlayerName; +vmCvar_t cg_cameraMode; +vmCvar_t cg_cameraOrbit; +vmCvar_t cg_cameraOrbitDelay; +vmCvar_t cg_timescaleFadeEnd; +vmCvar_t cg_timescaleFadeSpeed; +vmCvar_t cg_timescale; +vmCvar_t cg_smallFont; +vmCvar_t cg_bigFont; +vmCvar_t cg_noTaunt; // NERVE - SMF +vmCvar_t cg_voiceSpriteTime; // DHM - Nerve +vmCvar_t cg_hudFiles; + +vmCvar_t cg_animState; +vmCvar_t cg_missionStats; +vmCvar_t cg_waitForFire; + +// NERVE - SMF - Wolf multiplayer configuration cvars +vmCvar_t mp_playerType; +vmCvar_t mp_currentPlayerType; +vmCvar_t mp_team; +vmCvar_t mp_currentTeam; +vmCvar_t mp_weapon; +vmCvar_t mp_pistol; +vmCvar_t mp_item1; + +vmCvar_t cg_drawCompass; +vmCvar_t cg_drawNotifyText; +vmCvar_t cg_quickMessageAlt; +vmCvar_t cg_popupLimboMenu; +vmCvar_t cg_descriptiveText; +// -NERVE - SMF + +vmCvar_t cg_medicChargeTime; +vmCvar_t cg_engineerChargeTime; +vmCvar_t cg_LTChargeTime; +vmCvar_t cg_soldierChargeTime; +vmCvar_t cg_redlimbotime; +vmCvar_t cg_bluelimbotime; + +vmCvar_t cg_autoReload; +vmCvar_t cg_antilag; + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +cvarTable_t cvarTable[] = { + { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging + { &cg_autoswitch, "cg_autoswitch", "2", CVAR_ARCHIVE }, + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_drawGamemodels, "cg_drawGamemodels", "1", CVAR_CHEAT }, + { &cg_drawFPGun, "cg_drawFPGun", "1", CVAR_ARCHIVE }, + { &cg_gun_frame, "cg_gun_frame", "0", CVAR_TEMP }, + { &cg_cursorHints, "cg_cursorHints", "1", CVAR_ARCHIVE }, + { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, + { &cg_zoomDefaultBinoc, "cg_zoomDefaultBinoc", "22.5", CVAR_ARCHIVE }, + { &cg_zoomDefaultSniper, "cg_zoomDefaultSniper", "20", CVAR_ARCHIVE }, // JPW NERVE changed per atvi req + { &cg_zoomDefaultSnooper, "cg_zoomDefaultSnooper", "40", CVAR_ARCHIVE }, // JPW NERVE made temp + { &cg_zoomDefaultFG, "cg_zoomDefaultFG", "55", CVAR_ARCHIVE }, //----(SA) added // JPW NERVE made temp + { &cg_zoomStepBinoc, "cg_zoomStepBinoc", "3", CVAR_ARCHIVE }, + { &cg_zoomStepSniper, "cg_zoomStepSniper", "2", CVAR_ARCHIVE }, + { &cg_zoomStepSnooper, "cg_zoomStepSnooper", "5", CVAR_ARCHIVE }, + { &cg_zoomStepFG, "cg_zoomStepFG", "10", CVAR_ARCHIVE }, //----(SA) added + { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_letterbox, "cg_letterbox", "0", CVAR_TEMP }, //----(SA) added + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, + { &cg_draw2D, "cg_draw2D", "1", CVAR_CHEAT }, // JPW NERVE changed per atvi req to prevent sniper rifle zoom cheats + { &cg_drawSpreadScale, "cg_drawSpreadScale", "1", CVAR_ARCHIVE }, + { &cg_drawFrags, "cg_drawFrags", "1", CVAR_ARCHIVE }, + { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, + { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, + { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, + { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshairPickups, "cg_drawCrosshairPickups", "1", CVAR_ARCHIVE }, + { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, + { &cg_hudAlpha, "cg_hudAlpha", "1", CVAR_ARCHIVE }, + { &cg_useWeapsForZoom, "cg_useWeapsForZoom", "1", CVAR_ARCHIVE }, + { &cg_weaponCycleDelay, "cg_weaponCycleDelay", "150", CVAR_ARCHIVE }, //----(SA) added + { &cg_cycleAllWeaps, "cg_cycleAllWeaps", "1", CVAR_ARCHIVE }, + { &cg_drawAllWeaps, "cg_drawAllWeaps", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "48", CVAR_ARCHIVE }, + { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, // JPW NERVE + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_reticles, "cg_reticles", "1", CVAR_CHEAT }, + { &cg_reticleType, "cg_reticleType", "1", CVAR_ARCHIVE }, + { &cg_reticleBrightness, "cg_reticleBrightness", "0.7", CVAR_ARCHIVE }, + { &cg_markTime, "cg_marktime", "10000", CVAR_ARCHIVE }, + { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE }, + { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, + { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, + { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, + { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, + { &cg_centertime, "cg_centertime", "5", CVAR_CHEAT }, // DHM - Nerve :: changed from 3 to 5 + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_bobup, "cg_bobup", "0.005", CVAR_ARCHIVE }, + { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, + { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, + + // JOSEPH 10-27-99 + { &cg_autoactivate, "cg_autoactivate", "1", CVAR_ARCHIVE }, + // END JOSEPH + + // Ridah, more fluid rotations + { &cg_swingSpeed, "cg_swingSpeed", "0.1", CVAR_CHEAT }, // was 0.3 for Q3 + { &cg_bloodTime, "cg_bloodTime", "120", CVAR_ARCHIVE }, + { &cg_hunkUsed, "com_hunkUsed", "0", 0 }, + { &cg_soundAdjust, "hunk_soundadjust", "0", 0 }, + + { &cg_skybox, "cg_skybox", "1", CVAR_CHEAT }, + // done. + + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, + { &cg_tracerWidth, "cg_tracerwidth", "0.8", CVAR_CHEAT }, + { &cg_tracerSpeed, "cg_tracerSpeed", "4500", CVAR_CHEAT }, + { &cg_tracerLength, "cg_tracerlength", "160", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "80", CVAR_CHEAT }, // JPW NERVE per atvi req + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_CHEAT }, // JPW NERVE per atvi req + { &cg_teamChatTime, "cg_teamChatTime", "8000", CVAR_ARCHIVE }, + { &cg_teamChatHeight, "cg_teamChatHeight", "8", CVAR_ARCHIVE }, + { &cg_forceModel, "", "0", CVAR_ARCHIVE }, // DHM - Nerve + { &cg_coronafardist, "cg_coronafardist", "1536", CVAR_ARCHIVE }, + { &cg_coronas, "cg_coronas", "1", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "2", CVAR_ARCHIVE }, + { &cg_uselessNostalgia, "cg_uselessNostalgia", "0", CVAR_ARCHIVE }, // JPW NERVE + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_blinktime, "cg_blinktime", "100", CVAR_ARCHIVE}, //----(SA) added + + { &cg_enableBreath, "g_enableBreath", "1", CVAR_SERVERINFO}, + { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, + { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, + { &cg_timescale, "timescale", "1", 0}, +// { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, + + { &pmove_fixed, "pmove_fixed", "0", 0}, + { &pmove_msec, "pmove_msec", "8", 0}, + + { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, // NERVE - SMF + { &cg_voiceSpriteTime, "cg_voiceSpriteTime", "6000", CVAR_ARCHIVE}, // DHM - Nerve + + { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, + { &cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, + { &cg_hudFiles, "cg_hudFiles", "ui_mp/hud.txt", CVAR_ARCHIVE}, + + { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, // NERVE - SMF + { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, // NERVE - SMF + + // the following variables are created in other parts of the system, + // but we also reference them here + + { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + + { &cg_blood, "cg_showblood", "1", CVAR_ARCHIVE }, + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo + { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, + { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, + + // Rafael - particle switch + { &cg_wolfparticles, "cg_wolfparticles", "1", CVAR_ARCHIVE }, + // done + + // Ridah + { &cg_gameType, "g_gametype", "0", 0 }, // communicated by systeminfo + { &cg_norender, "cg_norender", "0", 0 }, // only used during single player, to suppress rendering until the server is ready + + // Rafael gameskill + { &cg_gameSkill, "g_gameskill", "3", 0 }, // communicated by systeminfo + // done + + // JPW NERVE + { &cg_medicChargeTime, "g_medicChargeTime", "10000", 0 }, // communicated by systeminfo + { &cg_LTChargeTime, "g_LTChargeTime", "30000", 0 }, // communicated by systeminfo + { &cg_engineerChargeTime, "g_engineerChargeTime", "30000", 0 }, // communicated by systeminfo + { &cg_soldierChargeTime, "g_soldierChargeTime", "20000", 0 }, // communicated by systeminfo + // DHM - TEMP FIX + { &cg_bluelimbotime, "", "30000", 0 }, // communicated by systeminfo + { &cg_redlimbotime, "", "30000", 0 }, // communicated by systeminfo + // jpw + + { &cg_notebook, "cl_notebook", "0", CVAR_ROM }, + { &cg_notebookpages, "cg_notebookpages", "0", CVAR_ROM}, + + { &cg_animState, "cg_animState", "0", CVAR_CHEAT}, + { &cg_missionStats, "g_missionStats", "0", CVAR_ROM}, + { &cg_waitForFire, "cl_waitForFire", "0", CVAR_ROM}, + + { &cg_expectedhunkusage, "com_expectedhunkusage", "0", CVAR_ROM}, + + // NERVE - SMF + { &mp_playerType, "mp_playerType", "0", 0 }, + { &mp_currentPlayerType, "mp_currentPlayerType", "0", 0 }, + + { &mp_team, "mp_team", "0", 0 }, + { &mp_currentTeam, "mp_currentTeam", "0", 0 }, + + { &mp_weapon, "mp_weapon", "0", 0 }, + { &mp_pistol, "mp_pistol", "0", 0 }, + { &mp_item1, "mp_item1", "0", 0 }, + + { &cg_drawCompass, "cg_drawCompass", "1", CVAR_ARCHIVE }, + { &cg_drawNotifyText, "cg_drawNotifyText", "1", CVAR_ARCHIVE }, + { &cg_quickMessageAlt, "cg_quickMessageAlt", "1", CVAR_ARCHIVE }, + { &cg_popupLimboMenu, "cg_popupLimboMenu", "1", CVAR_ARCHIVE }, + { &cg_descriptiveText, "cg_descriptiveText", "1", CVAR_ARCHIVE }, + // -NERVE - SMF + + { &cg_showAIState, "cg_showAIState", "0", CVAR_CHEAT}, + + { &cg_autoReload, "cg_autoReload", "1", CVAR_ARCHIVE }, + + { &cg_antilag, "g_antilag", "0", 0 } +}; +int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) { + int i; + cvarTable_t *cv; + char var[MAX_TOKEN_CHARS]; + + trap_Cvar_Set( "cg_letterbox", "0" ); // force this for people who might have it in their + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); + + forceModelModificationCount = cg_forceModel.modificationCount; + + trap_Cvar_Register( NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register( NULL, "head", DEFAULT_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); + + +} + +/* +=================== +CG_ForceModelChange +=================== +*/ +static void CG_ForceModelChange( void ) { + int i; + + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { + const char *clientInfo; + + clientInfo = CG_ConfigString( CS_PLAYERS + i ); + if ( !clientInfo[0] ) { + continue; + } + CG_NewClientInfo( i ); + } +} + +/* +================= +CG_UpdateCvars +================= +*/ +void CG_UpdateCvars( void ) { + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Update( cv->vmCvar ); + } + + // if force model changed + if ( forceModelModificationCount != cg_forceModel.modificationCount ) { + forceModelModificationCount = cg_forceModel.modificationCount; + CG_ForceModelChange(); + } + + if ( autoReloadModificationCount != cg_autoReload.modificationCount ) { + if ( cg_autoReload.integer ) { + cg.pmext.bAutoReload = qtrue; + } else { + cg.pmext.bAutoReload = qfalse; + } + autoReloadModificationCount = cg_autoReload.modificationCount; + } +} + + +int CG_CrosshairPlayer( void ) { + if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { + return -1; + } + return cg.crosshairClientNum; +} + +int CG_LastAttacker( void ) { + if ( !cg.attackerTime ) { + return -1; + } + return cg.snap->ps.persistant[PERS_ATTACKER]; +} + +void QDECL CG_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + if ( !Q_strncmp( text, "[cgnotify]", 10 ) ) { + char buf[1024]; + + if ( !cg_drawNotifyText.integer ) { + Q_strncpyz( buf, &text[10], 1013 ); + trap_Print( buf ); + return; + } + + CG_AddToNotify( &text[10] ); + Q_strncpyz( buf, &text[10], 1013 ); + Q_strncpyz( text, "[skipnotify]", 13 ); + Q_strcat( text, 1011, buf ); + } + + trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + trap_Error( text ); +} + +#ifndef CGAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, error ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); + va_end( argptr ); + + CG_Error( "%s", text ); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + CG_Printf( "%s", text ); +} + +#endif + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +//======================================================================== +void CG_SetupDlightstyles( void ) { + int i, j; + char *str; + char *token; + int entnum; + centity_t *cent; + + cg.lightstylesInited = qtrue; + + for ( i = 1; i < MAX_DLIGHT_CONFIGSTRINGS; i++ ) + { + str = (char *) CG_ConfigString( CS_DLIGHTS + i ); + if ( !strlen( str ) ) { + break; + } + + token = COM_Parse( &str ); // ent num + entnum = atoi( token ); + cent = &cg_entities[entnum]; + + token = COM_Parse( &str ); // stylestring + Q_strncpyz( cent->dl_stylestring, token, strlen( token ) ); + + token = COM_Parse( &str ); // offset + cent->dl_frame = atoi( token ); + cent->dl_oldframe = cent->dl_frame - 1; + if ( cent->dl_oldframe < 0 ) { + cent->dl_oldframe = strlen( cent->dl_stylestring ); + } + + token = COM_Parse( &str ); // sound id + cent->dl_sound = atoi( token ); + + token = COM_Parse( &str ); // attenuation + cent->dl_atten = atoi( token ); + + for ( j = 0; j < strlen( cent->dl_stylestring ); j++ ) { + + cent->dl_stylestring[j] += cent->dl_atten; // adjust character for attenuation/amplification + + // clamp result + if ( cent->dl_stylestring[j] < 'a' ) { + cent->dl_stylestring[j] = 'a'; + } + if ( cent->dl_stylestring[j] > 'z' ) { + cent->dl_stylestring[j] = 'z'; + } + } + + cent->dl_backlerp = 0.0; + cent->dl_time = cg.time; + } + +} + +//======================================================================== + +/* +================= +CG_RegisterItemSounds + +The server says this item is used on this level +================= +*/ +static void CG_RegisterItemSounds( int itemNum ) { + gitem_t *item; + char data[MAX_QPATH]; + char *s, *start; + int len; + + item = &bg_itemlist[ itemNum ]; + + if ( item->pickup_sound ) { + trap_S_RegisterSound( item->pickup_sound ); + } + + // parse the space seperated precache string for other media + s = item->sounds; + if ( !s || !s[0] ) { + return; + } + + while ( *s ) { + start = s; + while ( *s && *s != ' ' ) { + s++; + } + + len = s - start; + if ( len >= MAX_QPATH || len < 5 ) { + CG_Error( "PrecacheItem: %s has bad precache string", + item->classname ); + return; + } + memcpy( data, start, len ); + data[len] = 0; + if ( *s ) { + s++; + } + + if ( !strcmp( data + len - 3, "wav" ) ) { + trap_S_RegisterSound( data ); + } + } +} + + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) { + int i; + char items[MAX_ITEMS + 1]; + char name[MAX_QPATH]; + const char *soundName; + + // NERVE - SMF - voice commands + CG_LoadVoiceChats(); + + // Ridah, init sound scripts + CG_SoundInit(); + // done. + +// JPW NERVE + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) { + cgs.media.n_health = trap_S_RegisterSound( "sound/multiplayer/health_pickup.wav" ); + } else { + cgs.media.n_health = trap_S_RegisterSound( "sound/items/n_health.wav" ); + } +// jpw + cgs.media.noFireUnderwater = trap_S_RegisterSound( "sound/weapons/underwaterfire.wav" ); //----(SA) added + + cgs.media.snipersound = trap_S_RegisterSound( "sound/weapons/mauser/mauserf1.wav" ); + cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/machinegun/buletby1.wav" ); + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav" ); +// JPW NERVE + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) { + cgs.media.wearOffSound = trap_S_RegisterSound( "sound/multiplayer/respawn.wav" ); + /* cgs.media.twoMinuteSound_g = trap_S_RegisterSound("sound/multiplayer/axis/g-twominutes1.wav"); + cgs.media.twoMinuteSound_a = trap_S_RegisterSound("sound/multiplayer/allies/a-twominutes1.wav"); + cgs.media.thirtySecondSound_g = trap_S_RegisterSound("sound/multiplayer/axis/g-thirtyseconds1.wav"); + cgs.media.thirtySecondSound_a = trap_S_RegisterSound("sound/multiplayer/allies/a-thirtyseconds1.wav");*/ +/* if( cg.twoMinuteSound_g[0] != '0' ) + cgs.media.twoMinuteSound_g = trap_S_RegisterSound(cg.twoMinuteSound_g); + if( cg.twoMinuteSound_a[0] != '0' ) + cgs.media.twoMinuteSound_a = trap_S_RegisterSound(cg.twoMinuteSound_a); + if( cg.thirtySecondSound_g[0] != '0' ) + cgs.media.thirtySecondSound_g = trap_S_RegisterSound(cg.thirtySecondSound_g); + if( cg.thirtySecondSound_a[0] != '0' ) + cgs.media.thirtySecondSound_a = trap_S_RegisterSound(cg.thirtySecondSound_a);*/ + trap_S_RegisterSound( "sound/multiplayer/land_hurt.wav" ); + } else { + cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav" ); + } +// jpw + cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav" ); + cgs.media.gibSound = trap_S_RegisterSound( "sound/player/gibsplt1.wav" ); + //cgs.media.gibBounce1Sound = trap_S_RegisterSound( "sound/player/gibimp1.wav" ); + cgs.media.gibBounce2Sound = trap_S_RegisterSound( "sound/player/gibimp2.wav" ); + //cgs.media.gibBounce3Sound = trap_S_RegisterSound( "sound/player/gibimp3.wav" ); + +// cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/telein.wav" ); +// cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/teleout.wav" ); +// cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav" ); + + cgs.media.grenadebounce1 = trap_S_RegisterSound( "sound/weapons/grenade/hgrenb1a.wav" ); + cgs.media.grenadebounce2 = trap_S_RegisterSound( "sound/weapons/grenade/hgrenb2a.wav" ); + + cgs.media.dynamitebounce1 = trap_S_RegisterSound( "sound/weapons/dynamite/dynamite_bounce.wav" ); + + cgs.media.fbarrelexp1 = trap_S_RegisterSound( "sound/weapons/flamebarrel/fbarrela.wav" ); + cgs.media.fbarrelexp2 = trap_S_RegisterSound( "sound/weapons/flamebarrel/fbarrelb.wav" ); + +/* JPW NERVE kick pulled from MP to prevent bind cheats + cgs.media.fkickwall = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav" ); + cgs.media.fkickflesh = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav" ); + cgs.media.fkickmiss = trap_S_RegisterSound( "sound/weapons/melee/fstmiss.wav" ); +*/ + + cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav" ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav" ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav" ); + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav" ); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav" ); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav" ); + + cgs.media.underWaterSound = trap_S_RegisterSound( "sound/world/underwater03.wav" ); + + for ( i = 0 ; i < 4 ; i++ ) { + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/step%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_NORMAL][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/boot%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_BOOT][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/flesh%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_FLESH][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/mech%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_MECH][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/energy%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_ENERGY][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/splash%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/clank%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_METAL][i] = trap_S_RegisterSound( name ); + + + // (SA) Wolf footstep sound registration + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/wood%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_WOOD][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/grass%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_GRASS][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/gravel%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_GRAVEL][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/roof%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_ROOF][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/snow%i.wav", i + 1 ); + cgs.media.footsteps[FOOTSTEP_SNOW][i] = trap_S_RegisterSound( name ); + + Com_sprintf( name, sizeof( name ), "sound/player/footsteps/carpet%i.wav", i + 1 ); //----(SA) + cgs.media.footsteps[FOOTSTEP_CARPET][i] = trap_S_RegisterSound( name ); + } + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS ) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_RegisterItemSounds( i ); + } + } + + for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { + soundName = CG_ConfigString( CS_SOUNDS + i ); + if ( !soundName[0] ) { + break; + } + if ( soundName[0] == '*' ) { + continue; // custom sound + } + + // Ridah, register sound scripts seperately + if ( !strstr( soundName, ".wav" ) ) { + CG_SoundScriptPrecache( soundName ); + } else { + cgs.gameSounds[i] = trap_S_RegisterSound( soundName ); + } + } + + //----(SA) added + cgs.media.grenadePulseSound4 = trap_S_RegisterSound( "sound/weapons/grenade/grenpulse4.wav" ); + cgs.media.grenadePulseSound3 = trap_S_RegisterSound( "sound/weapons/grenade/grenpulse3.wav" ); + cgs.media.grenadePulseSound2 = trap_S_RegisterSound( "sound/weapons/grenade/grenpulse2.wav" ); + cgs.media.grenadePulseSound1 = trap_S_RegisterSound( "sound/weapons/grenade/grenpulse1.wav" ); + //----(SA) end + + //----(SA) added + cgs.media.debBounce1Sound = trap_S_RegisterSound( "sound/world/block.wav" ); + cgs.media.debBounce2Sound = trap_S_RegisterSound( "sound/world/brick.wav" ); + cgs.media.debBounce3Sound = trap_S_RegisterSound( "sound/world/brick2.wav" ); + + // Ridah + cgs.media.flameSound = trap_S_RegisterSound( "sound/weapons/flamethrower/fl_fire.wav" ); + cgs.media.flameBlowSound = trap_S_RegisterSound( "sound/weapons/flamethrower/fl_blow.wav" ); + cgs.media.flameStartSound = trap_S_RegisterSound( "sound/weapons/flamethrower/fl_start.wav" ); + cgs.media.flameStreamSound = trap_S_RegisterSound( "sound/weapons/flamethrower/fl_stream.wav" ); + cgs.media.flameCrackSound = trap_S_RegisterSound( "sound/world/firecrack1.wav" ); + cgs.media.boneBounceSound = trap_S_RegisterSound( "sound/world/boardbreak.wav" ); // TODO: need a real sound for this + +/* JPW NERVE not in MP + cgs.media.lightningSounds[0] = trap_S_RegisterSound( "sound/world/electzap1.wav" ); + cgs.media.lightningSounds[1] = trap_S_RegisterSound( "sound/world/electzap2.wav" ); + cgs.media.lightningSounds[2] = trap_S_RegisterSound( "sound/world/electzap3.wav" ); + cgs.media.lightningZap = trap_S_RegisterSound( "sound/world/electrocute.wav" ); +*/ + // precache sound scripts that get called from the cgame + cgs.media.bulletHitFleshScript = CG_SoundScriptPrecache( "bulletHitFlesh" ); + +/* JPW NERVE not in MP + cgs.media.teslaZapScript = CG_SoundScriptPrecache( "teslaZap" ); + cgs.media.teslaLoopSound = trap_S_RegisterSound ( "sound/weapons/tesla/loop.wav" ); +*/ +// cgs.media.batsFlyingLoopSound = trap_S_RegisterSound( "sound/world/bats_flying.wav" ); + + // FIXME: only needed with item +// cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav" ); +// cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_medkit.wav"); +/* JPW NERVE -- not in MP + cgs.media.elecSound = trap_S_RegisterSound ("sound/items/use_elec.wav"); + cgs.media.fireSound = trap_S_RegisterSound ("sound/items/use_fire.wav"); + cgs.media.waterSound = trap_S_RegisterSound ("sound/items/use_water.wav"); + cgs.media.wineSound = trap_S_RegisterSound ("sound/items/use_wine.wav"); + cgs.media.quadSound = trap_S_RegisterSound("sound/items/damage3.wav"); +*/ + cgs.media.sfx_ric1 = trap_S_RegisterSound( "sound/weapons/machinegun/ric1.wav" ); + cgs.media.sfx_ric2 = trap_S_RegisterSound( "sound/weapons/machinegun/ric2.wav" ); + cgs.media.sfx_ric3 = trap_S_RegisterSound( "sound/weapons/machinegun/ric3.wav" ); +// cgs.media.sfx_railg = trap_S_RegisterSound ("sound/weapons/railgun/railgf1a.wav"); + cgs.media.sfx_rockexp = trap_S_RegisterSound( "sound/weapons/rocket/rocklx1a.wav" ); + cgs.media.sfx_rockexpDist = trap_S_RegisterSound( "sound/multiplayer/artillery_exp01.wav" ); // JPW NERVE + cgs.media.sfx_dynamiteexp = trap_S_RegisterSound( "sound/weapons/dynamite/dynamite_exp.wav" ); + cgs.media.sfx_dynamiteexpDist = trap_S_RegisterSound( "sound/weapons/dynamite/dynamite_exp_dist.wav" ); //----(SA) added + + +// cgs.media.sfx_spearhit = trap_S_RegisterSound ("sound/weapons/speargun/spearhit.wav"); // pulled JPW NERVE + + cgs.media.sfx_knifehit[0] = trap_S_RegisterSound( "sound/weapons/knife/knife_hit1.wav" ); // hitting player + cgs.media.sfx_knifehit[1] = trap_S_RegisterSound( "sound/weapons/knife/knife_hit2.wav" ); + cgs.media.sfx_knifehit[2] = trap_S_RegisterSound( "sound/weapons/knife/knife_hit3.wav" ); + cgs.media.sfx_knifehit[3] = trap_S_RegisterSound( "sound/weapons/knife/knife_hit4.wav" ); + + cgs.media.sfx_knifehit[4] = trap_S_RegisterSound( "sound/weapons/knife/knife_hitwall1.wav" ); // hitting wall + + cgs.media.sfx_knifehit[4] = trap_S_RegisterSound( "sound/weapons/knife/knife_hitwall1.wav" ); // hitting wall + + cgs.media.sfx_bullet_metalhit[0] = trap_S_RegisterSound( "sound/weapons/bullethit_metal1.wav" ); + cgs.media.sfx_bullet_metalhit[1] = trap_S_RegisterSound( "sound/weapons/bullethit_metal2.wav" ); + cgs.media.sfx_bullet_metalhit[2] = trap_S_RegisterSound( "sound/weapons/bullethit_metal3.wav" ); + + cgs.media.sfx_bullet_woodhit[0] = trap_S_RegisterSound( "sound/weapons/bullethit_wood1.wav" ); + cgs.media.sfx_bullet_woodhit[1] = trap_S_RegisterSound( "sound/weapons/bullethit_wood2.wav" ); + cgs.media.sfx_bullet_woodhit[2] = trap_S_RegisterSound( "sound/weapons/bullethit_wood3.wav" ); + + cgs.media.sfx_bullet_roofhit[0] = trap_S_RegisterSound( "sound/weapons/bullethit_roof1.wav" ); + cgs.media.sfx_bullet_roofhit[1] = trap_S_RegisterSound( "sound/weapons/bullethit_roof2.wav" ); + cgs.media.sfx_bullet_roofhit[2] = trap_S_RegisterSound( "sound/weapons/bullethit_roof3.wav" ); + + cgs.media.sfx_bullet_ceramichit[0] = trap_S_RegisterSound( "sound/weapons/bullethit_ceramic1.wav" ); + cgs.media.sfx_bullet_ceramichit[1] = trap_S_RegisterSound( "sound/weapons/bullethit_ceramic2.wav" ); + cgs.media.sfx_bullet_ceramichit[2] = trap_S_RegisterSound( "sound/weapons/bullethit_ceramic3.wav" ); + + cgs.media.sfx_bullet_glasshit[0] = trap_S_RegisterSound( "sound/weapons/bullethit_glass1.wav" ); + cgs.media.sfx_bullet_glasshit[1] = trap_S_RegisterSound( "sound/weapons/bullethit_glass2.wav" ); + cgs.media.sfx_bullet_glasshit[2] = trap_S_RegisterSound( "sound/weapons/bullethit_glass3.wav" ); + + + cgs.media.sparkSounds[0] = trap_S_RegisterSound( "sound/world/saarc2.wav" ); + cgs.media.sparkSounds[1] = trap_S_RegisterSound( "sound/world/arc2.wav" ); + + +//----(SA) doors and kick + + // DHM - Nerve :: Used for multiplayer + if ( cgs.gametype >= GT_WOLF ) { + trap_S_RegisterSound( "sound/multiplayer/artillery_01.wav" ); + trap_S_RegisterSound( "sound/multiplayer/airstrike_01.wav" ); + } + + //----(SA) removed some unnecessary stuff + +/* JPW NERVE -- kick pulled from MP (cheat issues) + trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav" ); + trap_S_RegisterSound( "sound/weapons/melee/fstmiss.wav" ); + + trap_S_RegisterSound( "sound/Loogie/spit.wav" ); + trap_S_RegisterSound( "sound/Loogie/sizzle.wav" ); +*/ +} + + +//=================================================================================== + + +void CG_MakeItemFindable( int i ) { +// cg_entities[i].clipmask |= CONTENTS_ITEM +} + + + + +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *modelName, const char *skinName ); +qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName ); + +static void CG_RegisterGraphics( void ) { + char name[1024]; + + int i; + char items[MAX_ITEMS + 1]; + static char *sb_nums[11] = { + "gfx/2d/numbers/zero_32b", + "gfx/2d/numbers/one_32b", + "gfx/2d/numbers/two_32b", + "gfx/2d/numbers/three_32b", + "gfx/2d/numbers/four_32b", + "gfx/2d/numbers/five_32b", + "gfx/2d/numbers/six_32b", + "gfx/2d/numbers/seven_32b", + "gfx/2d/numbers/eight_32b", + "gfx/2d/numbers/nine_32b", + "gfx/2d/numbers/minus_32b", + }; + + // clear any references to old media + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + trap_R_ClearScene(); + + CG_LoadingString( cgs.mapname ); + + trap_R_LoadWorldMap( cgs.mapname ); + + CG_LoadingString( "entities" ); + + CG_ParseEntitiesFromString(); + + // precache status bar pics + CG_LoadingString( "game media" ); + + CG_LoadingString( " - textures" ); + + for ( i = 0 ; i < 11 ; i++ ) { + cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); + } + + +// cgs.media.botSkillShaders[0] = trap_R_RegisterShader( "menu/art/skill1.tga" ); +// cgs.media.botSkillShaders[1] = trap_R_RegisterShader( "menu/art/skill2.tga" ); +// cgs.media.botSkillShaders[2] = trap_R_RegisterShader( "menu/art/skill3.tga" ); +// cgs.media.botSkillShaders[3] = trap_R_RegisterShader( "menu/art/skill4.tga" ); +// cgs.media.botSkillShaders[4] = trap_R_RegisterShader( "menu/art/skill5.tga" ); + +// cgs.media.viewBloodShader = trap_R_RegisterShader( "viewBloodBlend" ); + +// cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); + +// cgs.media.scoreboardName = trap_R_RegisterShaderNoMip( "menu/tab/name.tga" ); +// cgs.media.scoreboardPing = trap_R_RegisterShaderNoMip( "menu/tab/ping.tga" ); +// cgs.media.scoreboardScore = trap_R_RegisterShaderNoMip( "menu/tab/score.tga" ); +// cgs.media.scoreboardTime = trap_R_RegisterShaderNoMip( "menu/tab/time.tga" ); + +// JPW NERVE + cgs.media.fleshSmokePuffShader = trap_R_RegisterShader( "fleshimpactsmokepuff" ); // JPW NERVE + cgs.media.nerveTestShader = trap_R_RegisterShader( "jpwtest1" ); + cgs.media.idTestShader = trap_R_RegisterShader( "jpwtest2" ); + cgs.media.hud1Shader = trap_R_RegisterShader( "jpwhud1" ); + cgs.media.hud2Shader = trap_R_RegisterShader( "jpwhud2" ); + cgs.media.hud3Shader = trap_R_RegisterShader( "jpwhud3" ); + cgs.media.hud4Shader = trap_R_RegisterShader( "jpwhud4" ); + cgs.media.hud5Shader = trap_R_RegisterShader( "jpwhud5" ); +// jpw + cgs.media.smokePuffShader = trap_R_RegisterShader( "smokePuff" ); + + // Rafael - blood pool + //cgs.media.bloodPool = trap_R_RegisterShader ("bloodPool"); + + // RF, blood cloud + cgs.media.bloodCloudShader = trap_R_RegisterShader( "bloodCloud" ); + + // Rafael - cannon + cgs.media.smokePuffShaderdirty = trap_R_RegisterShader( "smokePuffdirty" ); + cgs.media.smokePuffShaderb1 = trap_R_RegisterShader( "smokePuffblack1" ); + cgs.media.smokePuffShaderb2 = trap_R_RegisterShader( "smokePuffblack2" ); + cgs.media.smokePuffShaderb3 = trap_R_RegisterShader( "smokePuffblack3" ); + cgs.media.smokePuffShaderb4 = trap_R_RegisterShader( "smokePuffblack4" ); + cgs.media.smokePuffShaderb5 = trap_R_RegisterShader( "smokePuffblack5" ); + // done + + // Rafael - bleedanim + for ( i = 0; i < 5; i++ ) { + cgs.media.viewBloodAni[i] = trap_R_RegisterShader( va( "viewBloodBlend%i", i + 1 ) ); + } + cgs.media.viewFlashBlood = trap_R_RegisterShader( "viewFlashBlood" ); + for ( i = 0; i < 16; i++ ) { + cgs.media.viewFlashFire[i] = trap_R_RegisterShader( va( "viewFlashFire%i", i + 1 ) ); + } + // done + + // Rafael bats + //for (i=0; i<10; i++) { + // cgs.media.bats[i] = trap_R_RegisterShader ( va("bats%i",i+1) ); + //} + // done + + cgs.media.smokePuffRageProShader = trap_R_RegisterShader( "smokePuffRagePro" ); + cgs.media.shotgunSmokePuffShader = trap_R_RegisterShader( "shotgunSmokePuff" ); + + cgs.media.bloodTrailShader = trap_R_RegisterShader( "bloodTrail" ); + cgs.media.lagometerShader = trap_R_RegisterShader( "lagometer" ); + cgs.media.connectionShader = trap_R_RegisterShader( "disconnected" ); + + cgs.media.nailPuffShader = trap_R_RegisterShader( "nailtrail" ); + +//----(SA) +// cgs.media.reticleShader = trap_R_RegisterShader( "gfx/misc/reticle" ); + cgs.media.reticleShaderSimple = trap_R_RegisterShader( "gfx/misc/reticlesimple" ); +// cgs.media.snooperShader = trap_R_RegisterShader( "gfx/misc/snooper" ); + cgs.media.snooperShaderSimple = trap_R_RegisterShader( "gfx/misc/snoopersimple" ); + +// cgs.media.binocShader = trap_R_RegisterShader( "gfx/misc/binoc" ); + cgs.media.binocShaderSimple = trap_R_RegisterShader( "gfx/misc/binocsimple" ); +//----(SA) + + // Rafael + // cgs.media.snowShader = trap_R_RegisterShader ( "snowPuff" ); + cgs.media.snowShader = trap_R_RegisterShader( "snow_tri" ); + + cgs.media.oilParticle = trap_R_RegisterShader( "oilParticle" ); + cgs.media.oilSlick = trap_R_RegisterShader( "oilSlick" ); + + cgs.media.waterBubbleShader = trap_R_RegisterShader( "waterBubble" ); + + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); + cgs.media.selectShader = trap_R_RegisterShader( "gfx/2d/select" ); + +//----(SA) cursor hints + cgs.media.usableHintShader = trap_R_RegisterShader( "gfx/2d/usableHint" ); + cgs.media.notUsableHintShader = trap_R_RegisterShader( "gfx/2d/notUsableHint" ); + cgs.media.doorHintShader = trap_R_RegisterShader( "gfx/2d/doorHint" ); + cgs.media.doorRotateHintShader = trap_R_RegisterShader( "gfx/2d/doorRotateHint" ); + cgs.media.doorLockHintShader = trap_R_RegisterShader( "gfx/2d/doorLockHint" ); + cgs.media.doorRotateLockHintShader = trap_R_RegisterShader( "gfx/2d/doorRotateLockHint" ); + cgs.media.mg42HintShader = trap_R_RegisterShader( "gfx/2d/mg42Hint" ); + cgs.media.breakableHintShader = trap_R_RegisterShader( "gfx/2d/breakableHint" ); + cgs.media.chairHintShader = trap_R_RegisterShader( "gfx/2d/chairHint" ); + cgs.media.alarmHintShader = trap_R_RegisterShader( "gfx/2d/alarmHint" ); + cgs.media.healthHintShader = trap_R_RegisterShader( "gfx/2d/healthHint" ); + cgs.media.treasureHintShader = trap_R_RegisterShader( "gfx/2d/treasureHint" ); + cgs.media.knifeHintShader = trap_R_RegisterShader( "gfx/2d/knifeHint" ); + cgs.media.ladderHintShader = trap_R_RegisterShader( "gfx/2d/ladderHint" ); + cgs.media.buttonHintShader = trap_R_RegisterShader( "gfx/2d/buttonHint" ); + cgs.media.waterHintShader = trap_R_RegisterShader( "gfx/2d/waterHint" ); + cgs.media.cautionHintShader = trap_R_RegisterShader( "gfx/2d/cautionHint" ); + cgs.media.dangerHintShader = trap_R_RegisterShader( "gfx/2d/dangerHint" ); + cgs.media.secretHintShader = trap_R_RegisterShader( "gfx/2d/secretHint" ); + cgs.media.qeustionHintShader = trap_R_RegisterShader( "gfx/2d/questionHint" ); + cgs.media.exclamationHintShader = trap_R_RegisterShader( "gfx/2d/exclamationHint" ); + cgs.media.clipboardHintShader = trap_R_RegisterShader( "gfx/2d/clipboardHint" ); + cgs.media.weaponHintShader = trap_R_RegisterShader( "gfx/2d/weaponHint" ); + cgs.media.ammoHintShader = trap_R_RegisterShader( "gfx/2d/ammoHint" ); + cgs.media.armorHintShader = trap_R_RegisterShader( "gfx/2d/armorHint" ); + cgs.media.powerupHintShader = trap_R_RegisterShader( "gfx/2d/powerupHint" ); + cgs.media.holdableHintShader = trap_R_RegisterShader( "gfx/2d/holdableHint" ); + cgs.media.inventoryHintShader = trap_R_RegisterShader( "gfx/2d/inventoryHint" ); + cgs.media.exitHintShader = trap_R_RegisterShader( "gfx/2d/exitHint" ); + + // (SA) not used yet +// cgs.media.hintPlrFriendShader = trap_R_RegisterShader( "gfx/2d/hintPlrFriend" ); +// cgs.media.hintPlrNeutralShader = trap_R_RegisterShader( "gfx/2d/hintPlrNeutral" ); +// cgs.media.hintPlrEnemyShader = trap_R_RegisterShader( "gfx/2d/hintPlrEnemy" ); +// cgs.media.hintPlrUnknownShader = trap_R_RegisterShader( "gfx/2d/hintPlrUnknown" ); + + cgs.media.buildHintShader = trap_R_RegisterShader( "gfx/2d/buildHint" ); // DHM - Nerve + cgs.media.disarmHintShader = trap_R_RegisterShader( "gfx/2d/disarmHint" ); // DHM - Nerve + cgs.media.reviveHintShader = trap_R_RegisterShader( "gfx/2d/reviveHint" ); // DHM - Nerve + cgs.media.dynamiteHintShader = trap_R_RegisterShader( "gfx/2d/dynamiteHint" ); // DHM - Nerve + +//----(SA) end + + for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { + cgs.media.crosshairShader[i] = trap_R_RegisterShader( va( "gfx/2d/crosshair%c", 'a' + i ) ); + cg.crosshairShaderAlt[i] = trap_R_RegisterShader( va( "gfx/2d/crosshair%c_alt", 'a' + i ) ); + } + + cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); + cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" ); + + // powerup shaders +// cgs.media.quadShader = trap_R_RegisterShader("powerups/quad" ); +// cgs.media.quadWeaponShader = trap_R_RegisterShader("powerups/quadWeapon" ); +// cgs.media.battleSuitShader = trap_R_RegisterShader("powerups/battleSuit" ); +// cgs.media.battleWeaponShader = trap_R_RegisterShader("powerups/battleWeapon" ); +// cgs.media.invisShader = trap_R_RegisterShader("powerups/invisibility" ); +// cgs.media.regenShader = trap_R_RegisterShader("powerups/regen" ); +// cgs.media.hastePuffShader = trap_R_RegisterShader("hasteSmokePuff" ); + + // DHM - Nerve :: Allow flags again, will change later to more appropriate models + if ( cgs.gametype == GT_CTF || cgs.gametype >= GT_WOLF || cg_buildScript.integer ) { + //cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); + cgs.media.redFlagModel = trap_R_RegisterModel( "models/multiplayer/treasure/treasure.md3" ); + //cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); + cgs.media.blueFlagModel = trap_R_RegisterModel( "models/multiplayer/treasure/treasure.md3" ); + } + +// if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { +// cgs.media.friendShader = trap_R_RegisterShader( "sprites/foe" ); +// cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); // NERVE - SMF + // } + +// JPW NERVE + cgs.media.redColorBar = trap_R_RegisterShader( "redcolorbar" ); + cgs.media.blueColorBar = trap_R_RegisterShader( "bluecolorbar" ); + cgs.media.hudPowerBar = trap_R_RegisterShader( "powerbar" ); + cgs.media.hudSprintBar = trap_R_RegisterShader( "sprintbar" ); + cgs.media.hudAlliedHelmet = trap_R_RegisterShader( "AlliedHelmet" ); + cgs.media.hudAxisHelmet = trap_R_RegisterShader( "AxisHelmet" ); +// jpw + + CG_LoadingString( " - models" ); + +// cgs.media.armorModel = trap_R_RegisterModel( "models/powerups/armor/armor_yel.md3" ); + + cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); + + cgs.media.panzerfaustBrassModel = trap_R_RegisterModel( "models/weapons2/shells/pf_shell.md3" ); + + // Rafael + cgs.media.smallgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/sm_shell.md3" ); + +// cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); + +// RF: old style gibs not needed anymore (changed over to using gibs.cfg) +/* + cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); + cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); + cgs.media.gibChest = trap_R_RegisterModel( "models/gibs/chest.md3" ); + cgs.media.gibFist = trap_R_RegisterModel( "models/gibs/fist.md3" ); + cgs.media.gibFoot = trap_R_RegisterModel( "models/gibs/foot.md3" ); + cgs.media.gibForearm = trap_R_RegisterModel( "models/gibs/forearm.md3" ); + cgs.media.gibIntestine = trap_R_RegisterModel( "models/gibs/intestine.md3" ); + cgs.media.gibLeg = trap_R_RegisterModel( "models/gibs/leg.md3" ); + cgs.media.gibSkull = trap_R_RegisterModel( "models/gibs/skull.md3" ); + cgs.media.gibBrain = trap_R_RegisterModel( "models/gibs/brain.md3" ); +*/ + + //----(SA) wolf debris + cgs.media.debBlock[0] = trap_R_RegisterModel( "models/mapobjects/debris/brick1.md3" ); + cgs.media.debBlock[1] = trap_R_RegisterModel( "models/mapobjects/debris/brick2.md3" ); + cgs.media.debBlock[2] = trap_R_RegisterModel( "models/mapobjects/debris/brick3.md3" ); + cgs.media.debBlock[3] = trap_R_RegisterModel( "models/mapobjects/debris/brick4.md3" ); + cgs.media.debBlock[4] = trap_R_RegisterModel( "models/mapobjects/debris/brick5.md3" ); + cgs.media.debBlock[5] = trap_R_RegisterModel( "models/mapobjects/debris/brick6.md3" ); + + cgs.media.debRock[0] = trap_R_RegisterModel( "models/mapobjects/debris/rubble1.md3" ); + cgs.media.debRock[1] = trap_R_RegisterModel( "models/mapobjects/debris/rubble2.md3" ); + cgs.media.debRock[2] = trap_R_RegisterModel( "models/mapobjects/debris/rubble3.md3" ); + + + cgs.media.debWood[0] = trap_R_RegisterModel( "models/gibs/wood/wood1.md3" ); + cgs.media.debWood[1] = trap_R_RegisterModel( "models/gibs/wood/wood2.md3" ); + cgs.media.debWood[2] = trap_R_RegisterModel( "models/gibs/wood/wood3.md3" ); + cgs.media.debWood[3] = trap_R_RegisterModel( "models/gibs/wood/wood4.md3" ); + cgs.media.debWood[4] = trap_R_RegisterModel( "models/gibs/wood/wood5.md3" ); + cgs.media.debWood[5] = trap_R_RegisterModel( "models/gibs/wood/wood6.md3" ); +// cgs.media.debWoodl = trap_R_RegisterModel( "models/mapobjects/debris/woodxl.md3" ); +// cgs.media.debWoodm = trap_R_RegisterModel( "models/mapobjects/debris/woodm.md3" ); +// cgs.media.debWoods = trap_R_RegisterModel( "models/mapobjects/debris/woodsm.md3" ); + + cgs.media.debFabric[0] = trap_R_RegisterModel( "models/shards/fabric1.md3" ); + cgs.media.debFabric[1] = trap_R_RegisterModel( "models/shards/fabric2.md3" ); + cgs.media.debFabric[2] = trap_R_RegisterModel( "models/shards/fabric3.md3" ); + + //----(SA) end + + cgs.media.spawnInvincibleShader = trap_R_RegisterShader( "sprites/shield" ); + cgs.media.scoreEliminatedShader = trap_R_RegisterShader( "sprites/skull" ); + + cgs.media.medicReviveShader = trap_R_RegisterShader( "sprites/medic_revive" ); + cgs.media.voiceChatShader = trap_R_RegisterShader( "sprites/voiceChat" ); + cgs.media.balloonShader = trap_R_RegisterShader( "sprites/balloon3" ); + + for ( i = 0; i < MAX_AISTATES; i++ ) { + cgs.media.aiStateShaders[i] = trap_R_RegisterShader( va( "sprites/aistate%i", i + 1 ) ); + } + + cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); + + //cgs.media.bleedExplosionShader = trap_R_RegisterShader( "bleedExplosion" ); + + //----(SA) water splash + cgs.media.waterSplashModel = trap_R_RegisterModel( "models/weaphits/bullet.md3" ); + cgs.media.waterSplashShader = trap_R_RegisterShader( "waterSplash" ); + //----(SA) end + + cgs.media.spearModel = trap_R_RegisterModel( "models/weaphits/spear.md3" ); //----(SA) + + cgs.media.bulletFlashModel = trap_R_RegisterModel( "models/weaphits/bullet.md3" ); + cgs.media.ringFlashModel = trap_R_RegisterModel( "models/weaphits/ring02.md3" ); + cgs.media.dishFlashModel = trap_R_RegisterModel( "models/weaphits/boom01.md3" ); +// cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); +// cgs.media.teleportEffectShader = trap_R_RegisterShader( "teleportEffect" ); + +// cgs.media.spiritSkullModel = trap_R_RegisterModel( "models/mapobjects/skull/skul2t.md3" ); +// cgs.media.batModel = trap_R_RegisterModel( "models/mapobjects/bat/bat.md3" ); + +// cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); +// cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); +// cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); + + // Ridah, spark particles + cgs.media.sparkParticleShader = trap_R_RegisterShader( "sparkParticle" ); + cgs.media.smokeTrailShader = trap_R_RegisterShader( "smokeTrail" ); +// cgs.media.fireTrailShader = trap_R_RegisterShader( "fireTrail" ); + cgs.media.lightningBoltShader = trap_R_RegisterShader( "lightningBolt" ); + cgs.media.flamethrowerFireStream = trap_R_RegisterShader( "flamethrowerFireStream" ); + cgs.media.flamethrowerBlueStream = trap_R_RegisterShader( "flamethrowerBlueStream" ); + //cgs.media.flamethrowerFuelStream = trap_R_RegisterShader( "flamethrowerFuelStream" ); + //cgs.media.flamethrowerFuelShader = trap_R_RegisterShader( "flamethrowerFuel" ); + cgs.media.onFireShader2 = trap_R_RegisterShader( "entityOnFire1" ); + cgs.media.onFireShader = trap_R_RegisterShader( "entityOnFire2" ); + //cgs.media.dripWetShader2 = trap_R_RegisterShader( "dripWet2" ); + //cgs.media.dripWetShader = trap_R_RegisterShader( "dripWet1" ); + cgs.media.viewFadeBlack = trap_R_RegisterShader( "viewFadeBlack" ); + cgs.media.sparkFlareShader = trap_R_RegisterShader( "sparkFlareParticle" ); + cgs.media.spotLightShader = trap_R_RegisterShader( "spotLight" ); + cgs.media.spotLightBeamShader = trap_R_RegisterShader( "lightBeam" ); + cgs.media.lightningHitWallShader = trap_R_RegisterShader( "lightningHitWall" ); + cgs.media.lightningWaveShader = trap_R_RegisterShader( "lightningWave" ); + cgs.media.bulletParticleTrailShader = trap_R_RegisterShader( "bulletParticleTrail" ); + cgs.media.smokeParticleShader = trap_R_RegisterShader( "smokeParticle" ); + + // DHM - Nerve :: bullet hitting dirt + cgs.media.dirtParticle1Shader = trap_R_RegisterShader( "dirt_splash" ); + cgs.media.dirtParticle2Shader = trap_R_RegisterShader( "water_splash" ); + //cgs.media.dirtParticle3Shader = trap_R_RegisterShader( "dirtParticle3" ); + +// cgs.media.teslaDamageEffectShader = trap_R_RegisterShader( "teslaDamageEffect" ); +// cgs.media.teslaAltDamageEffectShader = trap_R_RegisterShader( "teslaAltDamageEffect" ); +// cgs.media.viewTeslaDamageEffectShader = trap_R_RegisterShader( "viewTeslaDamageEffect" ); +// cgs.media.viewTeslaAltDamageEffectShader = trap_R_RegisterShader( "viewTeslaAltDamageEffect" ); + // done. + + cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); // (SA) for debugging server traces + +// cgs.media.thirdPersonBinocModel = trap_R_RegisterModel( "models/powerups/holdable/binocs_thirdperson.md3" ); //----(SA) added + cgs.media.thirdPersonBinocModel = trap_R_RegisterModel( "models/multiplayer/binocs/binocs.md3" ); // NERVE - SMF + + // RF, not used anymore + //cgs.media.targetEffectExplosionShader = trap_R_RegisterShader( "targetEffectExplode" ); + //cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); + //cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + + // zombie shot + //cgs.media.zombieLoogie = trap_R_RegisterModel( "models/mapobjects/bodyparts/zom_loog.md3" ); + cgs.media.flamebarrel = trap_R_RegisterModel( "models/furniture/barrel/barrel_a.md3" ); + //----(SA) end + + cgs.media.mg42muzzleflash = trap_R_RegisterModel( "models/weapons2/machinegun/mg42_flash.md3" ); + // cgs.media.mg42muzzleflashgg = trap_R_RegisterModel ("models/weapons2/machinegun/mg42_flash_gg.md3" ); + + //cgs.media.planemuzzleflash = trap_R_RegisterModel ("models/mapobjects/vehicles/gunflare.md3"); + //cgs.media.crowbar = trap_R_RegisterModel ("models/weapons2/wrench/wrench.md3"); + + // Rafael shards + cgs.media.shardGlass1 = trap_R_RegisterModel( "models/shards/glass1.md3" ); + cgs.media.shardGlass2 = trap_R_RegisterModel( "models/shards/glass2.md3" ); + cgs.media.shardWood1 = trap_R_RegisterModel( "models/shards/wood1.md3" ); + cgs.media.shardWood2 = trap_R_RegisterModel( "models/shards/wood2.md3" ); + cgs.media.shardMetal1 = trap_R_RegisterModel( "models/shards/metal1.md3" ); + cgs.media.shardMetal2 = trap_R_RegisterModel( "models/shards/metal2.md3" ); + cgs.media.shardCeramic1 = trap_R_RegisterModel( "models/shards/ceramic1.md3" ); + cgs.media.shardCeramic2 = trap_R_RegisterModel( "models/shards/ceramic2.md3" ); + // done + + cgs.media.shardRubble1 = trap_R_RegisterModel( "models/mapobjects/debris/brick000.md3" ); + cgs.media.shardRubble2 = trap_R_RegisterModel( "models/mapobjects/debris/brick001.md3" ); + cgs.media.shardRubble3 = trap_R_RegisterModel( "models/mapobjects/debris/brick002.md3" ); + + for ( i = 0; i < MAX_LOCKER_DEBRIS; i++ ) + { + Com_sprintf( name, sizeof( name ), "models/mapobjects/debris/personal%i.md3", i + 1 ); + cgs.media.shardJunk[i] = trap_R_RegisterModel( name ); + } + + memset( cg_items, 0, sizeof( cg_items ) ); + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + +// TODO: FIXME: REMOVE REGISTRATION OF EACH MODEL FOR EVERY LEVEL LOAD + + + //----(SA) okay, new stuff to intialize rather than doing it at level load time (or "give all" time) + // (I'm certainly not against being efficient here, but I'm tired of the rocket launcher effect only registering + // sometimes and want it to work for sure for this demo) + + CG_LoadingString( " - weapons" ); + for ( i = WP_KNIFE; i < WP_GAUNTLET; i++ ) { + // DHM - Nerve :: Only register weapons we use in WolfMP + if ( cgs.gametype >= GT_WOLF ) { + if ( BG_WeaponInWolfMP( i ) ) { + CG_RegisterWeapon( i ); + } + } else { + CG_RegisterWeapon( i ); + } + } + +// END + + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS ) ); + + CG_LoadingString( " - items" ); + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_LoadingItem( i ); + CG_RegisterItemVisuals( i ); + CG_MakeItemFindable( i ); + } + } + + // wall marks + cgs.media.bulletMarkShader = trap_R_RegisterShader( "gfx/damage/bullet_mrk" ); + cgs.media.burnMarkShader = trap_R_RegisterShader( "gfx/damage/burn_med_mrk" ); + cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.shadowFootShader = trap_R_RegisterShader( "markShadowFoot" ); + cgs.media.shadowTorsoShader = trap_R_RegisterShader( "markShadowTorso" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + cgs.media.wakeMarkShaderAnim = trap_R_RegisterShader( "wakeAnim" ); // (SA) + + //----(SA) added + cgs.media.bulletMarkShaderMetal = trap_R_RegisterShader( "gfx/damage/metal_mrk" ); + cgs.media.bulletMarkShaderWood = trap_R_RegisterShader( "gfx/damage/wood_mrk" ); + cgs.media.bulletMarkShaderCeramic = trap_R_RegisterShader( "gfx/damage/ceramic_mrk" ); + cgs.media.bulletMarkShaderGlass = trap_R_RegisterShader( "gfx/damage/glass_mrk" ); + + for ( i = 0 ; i < 5 ; i++ ) { + char name[32]; + //Com_sprintf( name, sizeof(name), "textures/decals/blood%i", i+1 ); + //cgs.media.bloodMarkShaders[i] = trap_R_RegisterShader( name ); + Com_sprintf( name, sizeof( name ), "blood_dot%i", i + 1 ); + cgs.media.bloodDotShaders[i] = trap_R_RegisterShader( name ); + } + + CG_LoadingString( " - inline models" ); + + // register the inline models + cgs.numInlineModels = trap_CM_NumInlineModels(); + for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { + char name[10]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof( name ), "*%i", i ); + cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); + trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) { + cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + } + + CG_LoadingString( " - server models" ); + + // register all the server specified models + for ( i = 1 ; i < MAX_MODELS ; i++ ) { + const char *modelName; + + modelName = CG_ConfigString( CS_MODELS + i ); + if ( !modelName[0] ) { + break; + } + cgs.gameModels[i] = trap_R_RegisterModel( modelName ); + } + + CG_LoadingString( " - particles" ); + CG_ClearParticles(); + + for ( i = 1; i < MAX_PARTICLES_AREAS; i++ ) + { + { + int rval; + + rval = CG_NewParticleArea( CS_PARTICLES + i ); + if ( !rval ) { + break; + } + } + } + + // NERVE - SMF - register WolfMP models and skins + { + clientInfo_t ci; + memset( &ci, 0, sizeof( ci ) ); + + CG_RegisterClientModelname( &ci, "multi_axis", "redsoldier1" ); + CG_RegisterClientModelname( &ci, "multi_axis", "redengineer1" ); + CG_RegisterClientModelname( &ci, "multi_axis", "redmedic1" ); + CG_RegisterClientModelname( &ci, "multi_axis", "redlieutenant1" ); + + CG_RegisterClientModelname( &ci, "multi", "bluesoldier1" ); + CG_RegisterClientModelname( &ci, "multi", "blueengineer1" ); + CG_RegisterClientModelname( &ci, "multi", "bluemedic1" ); + CG_RegisterClientModelname( &ci, "multi", "bluelieutenant1" ); + } + // -NERVE - SMF + +// cgs.media.cursor = trap_R_RegisterShaderNoMip( "menu/art/3_cursor2" ); + cgs.media.sizeCursor = trap_R_RegisterShaderNoMip( "ui_mp/assets/sizecursor.tga" ); + cgs.media.selectCursor = trap_R_RegisterShaderNoMip( "ui_mp/assets/selectcursor.tga" ); + CG_LoadingString( " - game media done" ); + +} + +/* +=================== +CG_RegisterClients + +=================== +*/ +static void CG_RegisterClients( void ) { + int i; + + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { + const char *clientInfo; + + clientInfo = CG_ConfigString( CS_PLAYERS + i ); + if ( !clientInfo[0] ) { + continue; + } + CG_LoadingClient( i ); + CG_NewClientInfo( i ); + } +} + +//=========================================================================== + +/* +================= +CG_ConfigString +================= +*/ +const char *CG_ConfigString( int index ) { + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + CG_Error( "CG_ConfigString: bad index: %i", index ); + } + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + if ( strlen( parm1 ) ) { + trap_S_StartBackgroundTrack( parm1, parm2 ); + } +} + +#if 0 //DAJ unused +char *CG_GetMenuBuffer( const char *filename ) { + int len; + fileHandle_t f; + static char buf[MAX_MENUFILE]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return NULL; + } + if ( len >= MAX_MENUFILE ) { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + return buf; +} +#endif +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +qboolean CG_Asset_Parse( int handle ) { + pc_token_t token; + const char *tempStr; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( Q_stricmp( token.string, "{" ) != 0 ) { + return qfalse; + } + + while ( 1 ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + + if ( Q_stricmp( token.string, "}" ) == 0 ) { + return qtrue; + } + + // font + if ( Q_stricmp( token.string, "font" ) == 0 ) { + int pointSize; + if ( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) { + return qfalse; + } + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.textFont ); + continue; + } + + // smallFont + if ( Q_stricmp( token.string, "smallFont" ) == 0 ) { + int pointSize; + if ( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) { + return qfalse; + } + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.smallFont ); + continue; + } + + // font + if ( Q_stricmp( token.string, "bigfont" ) == 0 ) { + int pointSize; + if ( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) ) { + return qfalse; + } + cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.bigFont ); + continue; + } + + // gradientbar + if ( Q_stricmp( token.string, "gradientbar" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr ); + continue; + } + + // enterMenuSound + if ( Q_stricmp( token.string, "menuEnterSound" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr ); + continue; + } + + // exitMenuSound + if ( Q_stricmp( token.string, "menuExitSound" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr ); + continue; + } + + // itemFocusSound + if ( Q_stricmp( token.string, "itemFocusSound" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr ); + continue; + } + + // menuBuzzSound + if ( Q_stricmp( token.string, "menuBuzzSound" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr ); + continue; + } + + if ( Q_stricmp( token.string, "cursor" ) == 0 ) { + if ( !PC_String_Parse( handle, &cgDC.Assets.cursorStr ) ) { + return qfalse; + } + cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr ); + continue; + } + + if ( Q_stricmp( token.string, "fadeClamp" ) == 0 ) { + if ( !PC_Float_Parse( handle, &cgDC.Assets.fadeClamp ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "fadeCycle" ) == 0 ) { + if ( !PC_Int_Parse( handle, &cgDC.Assets.fadeCycle ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "fadeAmount" ) == 0 ) { + if ( !PC_Float_Parse( handle, &cgDC.Assets.fadeAmount ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "shadowX" ) == 0 ) { + if ( !PC_Float_Parse( handle, &cgDC.Assets.shadowX ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "shadowY" ) == 0 ) { + if ( !PC_Float_Parse( handle, &cgDC.Assets.shadowY ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "shadowColor" ) == 0 ) { + if ( !PC_Color_Parse( handle, &cgDC.Assets.shadowColor ) ) { + return qfalse; + } + cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; + continue; + } + } + //return qfalse; +} + +void CG_ParseMenu( const char *menuFile ) { + pc_token_t token; + int handle; + + handle = trap_PC_LoadSource( menuFile ); + if ( !handle ) { + handle = trap_PC_LoadSource( "ui_mp/testhud.menu" ); + } + if ( !handle ) { + return; + } + + while ( 1 ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( token.string[0] == '}' ) { + break; + } + + if ( Q_stricmp( token.string, "assetGlobalDef" ) == 0 ) { + if ( CG_Asset_Parse( handle ) ) { + continue; + } else { + break; + } + } + + + if ( Q_stricmp( token.string, "menudef" ) == 0 ) { + // start a new menu + Menu_New( handle ); + } + } + trap_PC_FreeSource( handle ); +} + +qboolean CG_Load_Menu( char **p ) { + char *token; + + token = COM_ParseExt( p, qtrue ); + + if ( token[0] != '{' ) { + return qfalse; + } + + while ( 1 ) { + + token = COM_ParseExt( p, qtrue ); + + if ( Q_stricmp( token, "}" ) == 0 ) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + CG_ParseMenu( token ); + } + return qfalse; +} + + + +void CG_LoadMenus( const char *menuFile ) { + char *token; + char *p; + int len, start; + fileHandle_t f; + static char buf[MAX_MENUDEFFILE]; + + start = trap_Milliseconds(); + + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + if ( !f ) { + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + len = trap_FS_FOpenFile( "ui_mp/hud.txt", &f, FS_READ ); + if ( !f ) { + trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); + } + } + + if ( len >= MAX_MENUDEFFILE ) { + trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress( buf ); + + Menu_Reset(); + + p = buf; + + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( !token || token[0] == 0 || token[0] == '}' ) { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( Q_stricmp( token, "}" ) == 0 ) { + break; + } + + if ( Q_stricmp( token, "loadmenu" ) == 0 ) { + if ( CG_Load_Menu( &p ) ) { + continue; + } else { + break; + } + } + } + + Com_Printf( "UI menu load time = %d milli seconds\n", trap_Milliseconds() - start ); + +} + + + +static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int flags, float *special, int key ) { + return qfalse; +} + + +static int CG_FeederCount( float feederID ) { + int i, count; + count = 0; + if ( feederID == FEEDER_REDTEAM_LIST ) { + for ( i = 0; i < cg.numScores; i++ ) { + if ( cg.scores[i].team == TEAM_RED ) { + count++; + } + } + } else if ( feederID == FEEDER_BLUETEAM_LIST ) { + for ( i = 0; i < cg.numScores; i++ ) { + if ( cg.scores[i].team == TEAM_BLUE ) { + count++; + } + } + } else if ( feederID == FEEDER_SCOREBOARD ) { + return cg.numScores; + } + return count; +} + + + + +/////////////////////////// +/////////////////////////// + +static clientInfo_t * CG_InfoFromScoreIndex( int index, int team, int *scoreIndex ) { + int i, count; + if ( cgs.gametype >= GT_TEAM ) { + count = 0; + for ( i = 0; i < cg.numScores; i++ ) { + if ( cg.scores[i].team == team ) { + if ( count == index ) { + *scoreIndex = i; + return &cgs.clientinfo[cg.scores[i].client]; + } + count++; + } + } + } + *scoreIndex = index; + return &cgs.clientinfo[ cg.scores[index].client ]; +} + +static const char *CG_FeederItemText( float feederID, int index, int column, qhandle_t *handle ) { +#ifdef MISSIONPACK + gitem_t *item; +#endif // #ifdef MISSIONPACK + int scoreIndex = 0; + clientInfo_t *info = NULL; + int team = -1; + score_t *sp = NULL; + + *handle = -1; + + if ( feederID == FEEDER_REDTEAM_LIST ) { + team = TEAM_RED; + } else if ( feederID == FEEDER_BLUETEAM_LIST ) { + team = TEAM_BLUE; + } + + info = CG_InfoFromScoreIndex( index, team, &scoreIndex ); + sp = &cg.scores[scoreIndex]; + + if ( info && info->infoValid ) { + switch ( column ) { + case 0: +#ifdef MISSIONPACK + if ( info->powerups & ( 1 << PW_NEUTRALFLAG ) ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + *handle = cg_items[ ITEM_INDEX( item ) ].icon; + } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + *handle = cg_items[ ITEM_INDEX( item ) ].icon; + } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + *handle = cg_items[ ITEM_INDEX( item ) ].icon; + } else { + if ( info->botSkill > 0 && info->botSkill <= 5 ) { + *handle = cgs.media.botSkillShaders[ info->botSkill - 1 ]; + } else if ( info->handicap < 100 ) { + return va( "%i", info->handicap ); + } + } + break; + case 1: + if ( team == -1 ) { + return ""; + } else { + *handle = CG_StatusHandle( info->teamTask ); + } + break; + case 2: + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << sp->client ) ) { + return "Ready"; + } + if ( team == -1 ) { + if ( cgs.gametype == GT_TOURNAMENT ) { + return va( "%i/%i", info->wins, info->losses ); + } else if ( info->infoValid && info->team == TEAM_SPECTATOR ) { + return "Spectator"; + } else { + return ""; + } + } else { + if ( info->teamLeader ) { + return "Leader"; + } + } +#endif // #ifdef MISSIONPACK + break; + case 3: + return info->name; + break; + case 4: + return va( "%i", info->score ); + break; + case 5: + return va( "%4i", sp->time ); + break; + case 6: + if ( sp->ping == -1 ) { + return "connecting"; + } + return va( "%4i", sp->ping ); + break; + } + } + + return ""; +} + +static qhandle_t CG_FeederItemImage( float feederID, int index ) { + return 0; +} + +static void CG_FeederSelection( float feederID, int index ) { + if ( cgs.gametype >= GT_TEAM ) { + int i, count; + int team = ( feederID == FEEDER_REDTEAM_LIST ) ? TEAM_RED : TEAM_BLUE; + count = 0; + for ( i = 0; i < cg.numScores; i++ ) { + if ( cg.scores[i].team == team ) { + if ( index == count ) { + cg.selectedScore = i; + } + count++; + } + } + } else { + cg.selectedScore = index; + } +} + +float CG_Cvar_Get( const char *cvar ) { + char buff[128]; + memset( buff, 0, sizeof( buff ) ); + trap_Cvar_VariableStringBuffer( cvar, buff, sizeof( buff ) ); + return atof( buff ); +} + +void CG_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style ) { + CG_Text_Paint( x, y, scale, color, text, 0, limit, style ); +} + +static int CG_OwnerDrawWidth( int ownerDraw, float scale ) { + switch ( ownerDraw ) { + case CG_GAME_TYPE: + return CG_Text_Width( CG_GameTypeString(), scale, 0 ); + case CG_GAME_STATUS: + return CG_Text_Width( CG_GetGameStatusText(), scale, 0 ); + break; + case CG_KILLER: + return CG_Text_Width( CG_GetKillerText(), scale, 0 ); + break; +#ifdef MISSIONPACK + case CG_RED_NAME: + return CG_Text_Width( cg_redTeamName.string, scale, 0 ); + break; + case CG_BLUE_NAME: + return CG_Text_Width( cg_blueTeamName.string, scale, 0 ); + break; +#endif + + } + return 0; +} + +static int CG_PlayCinematic( const char *name, float x, float y, float w, float h ) { + return trap_CIN_PlayCinematic( name, x, y, w, h, CIN_loop ); +} + +static void CG_StopCinematic( int handle ) { + trap_CIN_StopCinematic( handle ); +} + +static void CG_DrawCinematic( int handle, float x, float y, float w, float h ) { + trap_CIN_SetExtents( handle, x, y, w, h ); + trap_CIN_DrawCinematic( handle ); +} + +static void CG_RunCinematicFrame( int handle ) { + trap_CIN_RunCinematic( handle ); +} + + + + +/* +================= +CG_LoadHudMenu(); + +================= +*/ +void CG_LoadHudMenu() { + char buff[1024]; + const char *hudSet; + + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + cgDC.setColor = &trap_R_SetColor; + cgDC.drawHandlePic = &CG_DrawPic; + cgDC.drawStretchPic = &trap_R_DrawStretchPic; + cgDC.drawText = &CG_Text_Paint; + cgDC.textWidth = &CG_Text_Width; + cgDC.textHeight = &CG_Text_Height; + cgDC.registerModel = &trap_R_RegisterModel; + cgDC.modelBounds = &trap_R_ModelBounds; + cgDC.fillRect = &CG_FillRect; + cgDC.drawRect = &CG_DrawRect; + cgDC.drawSides = &CG_DrawSides; + cgDC.drawTopBottom = &CG_DrawTopBottom; + cgDC.clearScene = &trap_R_ClearScene; + cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + cgDC.renderScene = &trap_R_RenderScene; + cgDC.registerFont = &trap_R_RegisterFont; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.runScript = &CG_RunMenuScript; + cgDC.getTeamColor = &CG_GetTeamColor; + cgDC.setCVar = trap_Cvar_Set; + cgDC.getCVarString = trap_Cvar_VariableStringBuffer; + cgDC.getCVarValue = CG_Cvar_Get; + cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; + //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.startLocalSound = &trap_S_StartLocalSound; + cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; + cgDC.feederCount = &CG_FeederCount; + cgDC.feederItemImage = &CG_FeederItemImage; + cgDC.feederItemText = &CG_FeederItemText; + cgDC.feederSelection = &CG_FeederSelection; + cgDC.setBinding = &trap_Key_SetBinding; // NERVE - SMF + cgDC.getBindingBuf = &trap_Key_GetBindingBuf; // NERVE - SMF + cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; // NERVE - SMF + cgDC.translateString = &CG_TranslateString; // NERVE - SMF + //cgDC.executeText = &trap_Cmd_ExecuteText; + cgDC.Error = &Com_Error; + cgDC.Print = &Com_Printf; + cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.Pause = &CG_Pause; + cgDC.registerSound = &trap_S_RegisterSound; + cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + cgDC.playCinematic = &CG_PlayCinematic; + cgDC.stopCinematic = &CG_StopCinematic; + cgDC.drawCinematic = &CG_DrawCinematic; + cgDC.runCinematicFrame = &CG_RunCinematicFrame; + + Init_Display( &cgDC ); + + Menu_Reset(); + + trap_Cvar_Set( "cg_hudFiles", "ui_mp/hud.txt" ); // NERVE - SMF - we need to hardwire for wolfMP + + trap_Cvar_VariableStringBuffer( "cg_hudFiles", buff, sizeof( buff ) ); + hudSet = buff; + if ( hudSet[0] == '\0' ) { + hudSet = "ui_mp/hud.txt"; + } + + CG_LoadMenus( hudSet ); +} + +void CG_AssetCache() { + //if (Assets.textFont == NULL) { + // trap_R_RegisterFont("fonts/arial.ttf", 72, &Assets.textFont); + //} + //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); + //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + cgDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); + cgDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); + cgDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); + cgDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_GREEN ); + cgDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_TEAL ); + cgDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); + cgDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_CYAN ); + cgDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); + cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); +} + + +extern qboolean initTrails; +void CG_ClearTrails( void ); +extern qboolean initparticles; +void CG_ClearParticles( void ); + +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { + const char *s; + + // clear everything + memset( &cgs, 0, sizeof( cgs ) ); + memset( &cg, 0, sizeof( cg ) ); + memset( cg_entities, 0, sizeof( cg_entities ) ); + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + memset( cg_items, 0, sizeof( cg_items ) ); + + // RF, init the anim scripting + cgs.animScriptData.soundIndex = CG_SoundScriptPrecache; + cgs.animScriptData.playSound = CG_SoundPlayIndexedScript; + + cg.clientNum = clientNum; // NERVE - SMF - TA merge + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + // load a few needed things before we do any screen updates + cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/hudchars" ); //trap_R_RegisterShader( "gfx/2d/bigchars" ); + // JOSEPH 4-17-00 + cgs.media.menucharsetShader = trap_R_RegisterShader( "gfx/2d/hudchars" ); + // END JOSEPH + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + cgs.media.charsetProp = trap_R_RegisterShaderNoMip( "menu/art/font1_prop.tga" ); + cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); + cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "menu/art/font2_prop.tga" ); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + + CG_ClearTrails(); + CG_ClearParticles(); + +// cg.weaponSelect = WP_MP40; + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + if ( strcmp( s, GAME_VERSION ) ) { + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + } + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + +// JPW NERVE -- pick a direction for smoke drift on the client -- cheap trick because it can be different on different clients, but who cares? + cgs.smokeWindDir = crandom(); +// jpw + + CG_ParseServerinfo(); + CG_ParseWolfinfo(); // NERVE - SMF + + // load the new map + CG_LoadingString( "collision map" ); + + trap_CM_LoadMap( cgs.mapname ); + + String_Init(); + + cg.loading = qtrue; // force players to load instead of defer + + CG_LoadingString( "sounds" ); + + CG_RegisterSounds(); + + CG_LoadingString( "graphics" ); + + CG_RegisterGraphics(); + + CG_LoadingString( "flamechunks" ); + + CG_InitFlameChunks(); // RF, register and clear all flamethrower resources + + CG_LoadingString( "clients" ); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + + CG_AssetCache(); + CG_LoadHudMenu(); // load new hud stuff + + cg.loading = qfalse; // future players will be deferred + + CG_InitLocalEntities(); + + CG_InitMarkPolys(); + + // remove the last loading update + cg.infoScreenText[0] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues(); + + CG_StartMusic(); + + cg.lightstylesInited = qfalse; + + CG_LoadingString( "" ); + + CG_ShaderStateChanged(); + + trap_S_ClearLoopingSounds( qtrue ); + + // NERVE - SMF +// JPW NERVE -- commented out 'cause this moved + + if ( cgs.gametype >= GT_WOLF ) { + trap_Cvar_Set( "cg_drawTimer", "0" ); // jpw + } + // jpw + // -NERVE - SMF +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) { + // some mods may need to do cleanup work here, + // like closing files or archiving session data +} + diff --git a/src/cgame/cg_marks.c b/src/cgame/cg_marks.c new file mode 100644 index 0000000..70ada39 --- /dev/null +++ b/src/cgame/cg_marks.c @@ -0,0 +1,350 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_marks.c -- wall marks + +#include "cg_local.h" + +/* +=================================================================== + +MARK POLYS + +=================================================================== +*/ + + +markPoly_t cg_activeMarkPolys; // double linked list +markPoly_t *cg_freeMarkPolys; // single linked list +markPoly_t cg_markPolys[MAX_MARK_POLYS]; + +/* +=================== +CG_InitMarkPolys + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitMarkPolys( void ) { + int i; + markPoly_t *trav, *lasttrav; + + memset( cg_markPolys, 0, sizeof( cg_markPolys ) ); + + cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; + cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; + cg_freeMarkPolys = cg_markPolys; + for ( i = 0, trav = cg_markPolys + 1, lasttrav = cg_markPolys ; i < MAX_MARK_POLYS - 1 ; i++, trav++ ) { + lasttrav->nextMark = trav; + lasttrav = trav; + } +} + + +/* +================== +CG_FreeMarkPoly +================== +*/ +void CG_FreeMarkPoly( markPoly_t *le ) { + if ( !le->prevMark ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prevMark->nextMark = le->nextMark; + le->nextMark->prevMark = le->prevMark; + + // the free list is only singly linked + le->nextMark = cg_freeMarkPolys; + cg_freeMarkPolys = le; +} + +/* +=================== +CG_AllocMark + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +markPoly_t *CG_AllocMark( int endTime ) { + markPoly_t *le; //, *trav, *lastTrav; + int time; + + if ( !cg_freeMarkPolys ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + time = cg_activeMarkPolys.prevMark->time; + while ( cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time ) { + CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); + } + } + + le = cg_freeMarkPolys; + cg_freeMarkPolys = cg_freeMarkPolys->nextMark; + + memset( le, 0, sizeof( *le ) ); + + // Ridah, TODO: sort this, so the list is always sorted by longest duration -> shortest duration, + // this way the shortest duration mark will always get overwritten first + //for (trav = cg_activeMarkPolys.nextMark; (trav->duration + trav->time > endTime) && (trav != cg_activeMarkPolys.prevMark) ; lastTrav = trav, trav++ ) { + // Respect the FOR loop + //} + + // link into the active list + le->nextMark = cg_activeMarkPolys.nextMark; + le->prevMark = &cg_activeMarkPolys; + cg_activeMarkPolys.nextMark->prevMark = le; + cg_activeMarkPolys.nextMark = le; + return le; +} + + + +/* +================= +CG_ImpactMark + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +// Ridah, increased this since we leave them around for longer +#define MAX_MARK_FRAGMENTS 384 +#define MAX_MARK_POINTS 1024 +//#define MAX_MARK_FRAGMENTS 128 +//#define MAX_MARK_POINTS 384 + +// these are ignored now for the most part +//#define MARK_TOTAL_TIME 20000 // (SA) made this a cvar: cg_markTime (we could cap the time or remove marks quicker if too long a time starts to cause new marks to not appear) +#define MARK_FADE_TIME 10000 + +void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, + float orientation, float red, float green, float blue, float alpha, + qboolean alphaFade, float radius, qboolean temporary, int duration ) { + vec3_t axis[3]; + float texCoordScale; + vec3_t originalPoints[4]; + byte colors[4]; + int i, j; + int numFragments; + markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; + vec5_t markPoints[MAX_MARK_POINTS]; // Ridah, made it vec5_t so it includes S/T + vec3_t projection; + int multMaxFragments = 1; + + if ( !cg_markTime.integer ) { + return; + } + + if ( radius <= 0 ) { + CG_Error( "CG_ImpactMark called with <= 0 radius" ); + } + + // Ridah, if no duration, use the default + if ( duration < 0 ) { + if ( duration == -2 ) { + multMaxFragments = -1; // use original mapping + } + +// duration = MARK_TOTAL_TIME; + duration = cg_markTime.integer; + } + + // create the texture axis + VectorNormalize2( dir, axis[0] ); + PerpendicularVector( axis[1], axis[0] ); + RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); + CrossProduct( axis[0], axis[2], axis[1] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for ( i = 0 ; i < 3 ; i++ ) { + originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + // get the fragments + //VectorScale( dir, -20, projection ); + VectorScale( dir, radius * 2, projection ); + numFragments = trap_CM_MarkFragments( (int)orientation, (void *)originalPoints, + projection, MAX_MARK_POINTS, (float *)&markPoints[0], + MAX_MARK_FRAGMENTS * multMaxFragments, markFragments ); + + colors[0] = red * 255; + colors[1] = green * 255; + colors[2] = blue * 255; + colors[3] = alpha * 255; + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { + polyVert_t *v; + polyVert_t verts[MAX_VERTS_ON_POLY]; + markPoly_t *mark; + qboolean hasST; + + // we have an upper limit on the complexity of polygons + // that we store persistantly + if ( mf->numPoints > MAX_VERTS_ON_POLY ) { + mf->numPoints = MAX_VERTS_ON_POLY; + } + if ( mf->numPoints < 0 ) { + hasST = qtrue; + mf->numPoints *= -1; + } else { + hasST = qfalse; + } + for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { + vec3_t delta; + + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + + if ( !hasST ) { + VectorSubtract( v->xyz, origin, delta ); + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; + } else { + v->st[0] = markPoints[mf->firstPoint + j][3]; + v->st[1] = markPoints[mf->firstPoint + j][4]; + } + + *(int *)v->modulate = *(int *)colors; + } + + // if it is a temporary (shadow) mark, add it immediately and forget about it + if ( temporary ) { + trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); + continue; + } + + // otherwise save it persistantly + mark = CG_AllocMark( cg.time + duration ); + mark->time = cg.time; + mark->alphaFade = alphaFade; + mark->markShader = markShader; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = red; + mark->color[1] = green; + mark->color[2] = blue; + mark->color[3] = alpha; + mark->duration = duration; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + } +} + + +/* +=============== +CG_AddMarks +=============== +*/ + +void CG_AddMarks( void ) { + int j; + markPoly_t *mp, *next; + int t; + int fade; + + if ( !cg_markTime.integer ) { + return; + } + + mp = cg_activeMarkPolys.nextMark; + for ( ; mp != &cg_activeMarkPolys ; mp = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = mp->nextMark; + + // see if it is time to completely remove it + if ( cg.time > mp->time + mp->duration ) { + CG_FreeMarkPoly( mp ); + continue; + } + + // fade out the energy bursts + if ( mp->markShader == cgs.media.energyMarkShader ) { + + fade = 450 - 450 * ( ( cg.time - mp->time ) / 3000.0 ); + if ( fade < 255 ) { + if ( fade < 0 ) { + fade = 0; + } + if ( mp->verts[0].modulate[0] != 0 ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + } + + // fade in the zombie spirit marks + if ( mp->markShader == cgs.media.zombieSpiritWallShader ) { + + fade = 255 * ( ( cg.time - mp->time ) / 2000.0 ); + if ( fade < 255 ) { + if ( fade < 0 ) { + fade = 0; + } + if ( mp->verts[0].modulate[0] != 0 ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + } + + // fade all marks out with time + t = mp->time + mp->duration - cg.time; + if ( t < (float)mp->duration / 2.0 ) { + fade = (int)( 255.0 * (float)t / ( (float)mp->duration / 2.0 ) ); + if ( mp->alphaFade ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[3] = fade; + } + } else { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + + trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); + } +} + diff --git a/src/cgame/cg_newDraw.c b/src/cgame/cg_newDraw.c new file mode 100644 index 0000000..4898d23 --- /dev/null +++ b/src/cgame/cg_newDraw.c @@ -0,0 +1,2706 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +#include "cg_local.h" +#include "../ui/ui_shared.h" + +extern displayContextDef_t cgDC; + +// set in CG_ParseTeamInfo + +//static int sortedTeamPlayers[TEAM_MAXOVERLAY]; +//static int numSortedTeamPlayers; +int drawTeamOverlayModificationCount = -1; + +//static char systemChat[256]; +//static char teamChat1[256]; +//static char teamChat2[256]; + +int CG_DrawField( int x, int y, int width, int value, int charWidth, int charHeight, qboolean dodrawpic, qboolean leftAlign ); // NERVE - SMF + +void CG_InitTeamChat() { +#ifdef MISSIONPACK + memset( teamChat1, 0, sizeof( teamChat1 ) ); + memset( teamChat2, 0, sizeof( teamChat2 ) ); + memset( systemChat, 0, sizeof( systemChat ) ); +#endif // #ifdef MISSIONPACK +} + +void CG_SetPrintString( int type, const char *p ) { + if ( type == SYSTEM_PRINT ) { + strcpy( systemChat, p ); + } else { + strcpy( teamChat2, teamChat1 ); + strcpy( teamChat1, p ); + } +} + +void CG_CheckOrderPending() { +#ifdef MISSIONPACK + if ( cgs.gametype < GT_CTF ) { + return; + } + if ( cgs.orderPending ) { + //clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; + const char *p1, *p2, *b; + p1 = p2 = b = NULL; + switch ( cgs.currentOrder ) { + case TEAMTASK_OFFENSE: + p1 = VOICECHAT_ONOFFENSE; + p2 = VOICECHAT_OFFENSE; + b = "+button7; wait; -button7"; + break; + case TEAMTASK_DEFENSE: + p1 = VOICECHAT_ONDEFENSE; + p2 = VOICECHAT_DEFEND; + b = "+button8; wait; -button8"; + break; + case TEAMTASK_PATROL: + p1 = VOICECHAT_ONPATROL; + p2 = VOICECHAT_PATROL; + b = "+button9; wait; -button9"; + break; + case TEAMTASK_FOLLOW: + p1 = VOICECHAT_ONFOLLOW; + p2 = VOICECHAT_FOLLOWME; + b = "+button10; wait; -button10"; + break; + case TEAMTASK_CAMP: + p1 = VOICECHAT_ONCAMPING; + p2 = VOICECHAT_CAMP; + break; + case TEAMTASK_RETRIEVE: + p1 = VOICECHAT_ONGETFLAG; + p2 = VOICECHAT_RETURNFLAG; + break; + case TEAMTASK_ESCORT: + p1 = VOICECHAT_ONFOLLOWCARRIER; + p2 = VOICECHAT_FOLLOWFLAGCARRIER; + break; + } + + if ( cg_currentSelectedPlayer.integer == numSortedTeamPlayers ) { + // to everyone + trap_SendConsoleCommand( va( "cmd vsay_team %s\n", p2 ) ); + } else { + // for the player self + if ( sortedTeamPlayers[cg_currentSelectedPlayer.integer] == cg.snap->ps.clientNum && p1 ) { + trap_SendConsoleCommand( va( "teamtask %i\n", cgs.currentOrder ) ); + //trap_SendConsoleCommand(va("cmd say_team %s\n", p2)); + trap_SendConsoleCommand( va( "cmd vsay_team %s\n", p1 ) ); + } else if ( p2 ) { + //trap_SendConsoleCommand(va("cmd say_team %s, %s\n", ci->name,p)); + trap_SendConsoleCommand( va( "cmd vtell %d %s\n", sortedTeamPlayers[cg_currentSelectedPlayer.integer], p2 ) ); + } + } + if ( b ) { + trap_SendConsoleCommand( b ); + } + cgs.orderPending = qfalse; + } +#endif // #ifdef MISSIONPACK +} + +static void CG_SetSelectedPlayerName() { + if ( cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers ) { + clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; + if ( ci ) { + trap_Cvar_Set( "cg_selectedPlayerName", ci->name ); + trap_Cvar_Set( "cg_selectedPlayer", va( "%d", sortedTeamPlayers[cg_currentSelectedPlayer.integer] ) ); +// cgs.currentOrder = ci->teamTask; + } + } else { + trap_Cvar_Set( "cg_selectedPlayerName", "Everyone" ); + } +} +int CG_GetSelectedPlayer() { + if ( cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers ) { + cg_currentSelectedPlayer.integer = 0; + } + return cg_currentSelectedPlayer.integer; +} + +void CG_SelectNextPlayer() { + CG_CheckOrderPending(); + if ( cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers ) { + cg_currentSelectedPlayer.integer++; + } else { + cg_currentSelectedPlayer.integer = 0; + } + CG_SetSelectedPlayerName(); +} + +void CG_SelectPrevPlayer() { + CG_CheckOrderPending(); + if ( cg_currentSelectedPlayer.integer > 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers ) { + cg_currentSelectedPlayer.integer--; + } else { + cg_currentSelectedPlayer.integer = numSortedTeamPlayers; + } + CG_SetSelectedPlayerName(); +} + + +// (SA) not sure what you'd use this for anyway... + +static void CG_DrawPlayerArmorIcon( rectDef_t *rect, qboolean draw2D ) { +#ifdef MISSIONPACK + centity_t *cent; + playerState_t *ps; + vec3_t angles; + vec3_t origin; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( draw2D || !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + CG_DrawPic( rect->x, rect->y + rect->h / 2 + 1, rect->w, rect->h, cgs.media.armorIcon ); + } else if ( cg_draw3dIcons.integer ) { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cgs.media.armorModel, 0, origin, angles ); + } +#endif +} + +static void CG_DrawPlayerArmorValue( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + char num[16]; + int value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + // NERVE - SMF - don't draw armor in wolfMP + if ( cgs.gametype >= GT_WOLF ) { + return; + } + + value = ps->stats[STAT_ARMOR]; + + + if ( shader ) { + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + } else { + Com_sprintf( num, sizeof( num ), "%i", value ); + value = CG_Text_Width( num, scale, 0 ); + CG_Text_Paint( rect->x + ( rect->w - value ) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle ); + } +} + +/* +// TTimo: unused +static float healthColors[4][4] = { +// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; + { 1, 0.69f, 0, 1.0f } , // normal + { 1.0f, 0.2f, 0.2f, 1.0f }, // low health + {0.5f, 0.5f, 0.5f, 1}, // weapon firing + { 1, 1, 1, 1 } }; // health > 100 +*/ + +/* +============== +weapIconDrawSize +============== +*/ +static int weapIconDrawSize( int weap ) { + switch ( weap ) { + + // weapons to not draw + case WP_KNIFE: + case WP_KNIFE2: + return 0; + + // weapons with 'wide' icons + case WP_THOMPSON: + case WP_MP40: + case WP_STEN: + case WP_MAUSER: + case WP_GARAND: + case WP_VENOM: + case WP_TESLA: + case WP_ROCKET_LAUNCHER: + case WP_PANZERFAUST: + case WP_FLAMETHROWER: + case WP_SPEARGUN: + case WP_BAR: //----(SA) added + case WP_BAR2: //----(SA) added + case WP_FG42: + case WP_FG42SCOPE: + case WP_SNOOPERSCOPE: + case WP_SNIPERRIFLE: + return 2; + } + + return 1; +} + + +/* +============== +CG_DrawPlayerWeaponIcon +============== +*/ +static void CG_DrawPlayerWeaponIcon( rectDef_t *rect, qboolean drawHighlighted, int align ) { + int size; + int realweap; // DHM - Nerve + qhandle_t icon; + float scale,halfScale; +// JPW NERVE + vec4_t hcolor; + + VectorSet( hcolor,1.f,1.f,1.f ); + hcolor[3] = cg_hudAlpha.value; +// jpw + + + if ( !cg_drawIcons.integer ) { + return; + } + + realweap = cg.predictedPlayerState.weapon; + + size = weapIconDrawSize( realweap ); + + if ( !size ) { + return; + } + + if ( drawHighlighted ) { + icon = cg_weapons[ realweap ].weaponIcon[1]; + } else { + icon = cg_weapons[ realweap ].weaponIcon[0]; + } + + + + // pulsing grenade icon to help the player 'count' in their head + if ( cg.predictedPlayerState.grenadeTimeLeft ) { // grenades and dynamite set this + + // these time differently + if ( realweap == WP_DYNAMITE || realweap == WP_DYNAMITE2 ) { + if ( ( ( cg.grenLastTime ) % 1000 ) > ( ( cg.predictedPlayerState.grenadeTimeLeft ) % 1000 ) ) { + trap_S_StartLocalSound( cgs.media.grenadePulseSound4, CHAN_LOCAL_SOUND ); + } + } else { + if ( ( ( cg.grenLastTime ) % 1000 ) < ( ( cg.predictedPlayerState.grenadeTimeLeft ) % 1000 ) ) { + switch ( cg.predictedPlayerState.grenadeTimeLeft / 1000 ) { + case 3: + trap_S_StartLocalSound( cgs.media.grenadePulseSound4, CHAN_LOCAL_SOUND ); + break; + case 2: + trap_S_StartLocalSound( cgs.media.grenadePulseSound3, CHAN_LOCAL_SOUND ); + break; + case 1: + trap_S_StartLocalSound( cgs.media.grenadePulseSound2, CHAN_LOCAL_SOUND ); + break; + case 0: + trap_S_StartLocalSound( cgs.media.grenadePulseSound1, CHAN_LOCAL_SOUND ); + break; + } + } + } + + scale = (float)( ( cg.predictedPlayerState.grenadeTimeLeft ) % 1000 ) / 100.0f; + halfScale = scale * 0.5f; + + cg.grenLastTime = cg.predictedPlayerState.grenadeTimeLeft; + } else { + scale = halfScale = 0; + } + + + if ( icon ) { + float x, y, w, h; + + if ( size == 1 ) { // draw half width to match the icon asset + + // start at left + x = rect->x - halfScale; + y = rect->y - halfScale; + w = rect->w / 2 + scale; + h = rect->h + scale; + + switch ( align ) { + case ITEM_ALIGN_CENTER: + x += rect->w / 4; + break; + case ITEM_ALIGN_RIGHT: + x += rect->w / 2; + break; + case ITEM_ALIGN_LEFT: + default: + break; + } + + } else { + x = rect->x - halfScale; + y = rect->y - halfScale; + w = rect->w + scale; + h = rect->h + scale; + } + + + trap_R_SetColor( hcolor ); // JPW NERVE + CG_DrawPic( x, y, w, h, icon ); + } +} + + +/* +============== +CG_DrawPlayerAmmoIcon +============== +*/ +static void CG_DrawPlayerAmmoIcon( rectDef_t *rect, qboolean draw2D ) { + centity_t *cent; + playerState_t *ps; + vec3_t angles; + vec3_t origin; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( draw2D || ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) ) { + qhandle_t icon; + icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; + if ( icon ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, icon ); + } + } else if ( cg_draw3dIcons.integer ) { + if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { + VectorClear( angles ); + origin[0] = 70; + origin[1] = 0; + origin[2] = 0; + angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); + } + } +} + +extern void CG_CheckForCursorHints( void ); +#define CURSORHINT_SCALE 10 + +/* +============== +CG_DrawCursorHints + + cg_cursorHints.integer == + 0: no hints + 1: sin size pulse + 2: one way size pulse + 3: alpha pulse + 4+: static image + +============== +*/ +static void CG_DrawCursorhint( rectDef_t *rect ) { + float *color; + qhandle_t icon, icon2 = 0; + float scale, halfscale; + + if ( !cg_cursorHints.integer ) { + return; + } + + CG_CheckForCursorHints(); + + switch ( cg.cursorHintIcon ) { + + case HINT_NONE: + case HINT_FORCENONE: + icon = 0; + break; + case HINT_DOOR: + icon = cgs.media.doorHintShader; + break; + case HINT_DOOR_ROTATING: + icon = cgs.media.doorRotateHintShader; + break; + case HINT_DOOR_LOCKED: + icon = cgs.media.doorLockHintShader; + break; + case HINT_DOOR_ROTATING_LOCKED: + icon = cgs.media.doorRotateLockHintShader; + break; + case HINT_MG42: + icon = cgs.media.mg42HintShader; + break; + case HINT_BREAKABLE: + case HINT_BREAKABLE_DYNAMITE: + + // DHM - Nerve :: We use HINT_BREAKABLE_DYNAMITE in multiplayer + if ( cgs.gametype >= GT_WOLF && cg.cursorHintIcon == HINT_BREAKABLE_DYNAMITE ) { + icon = cgs.media.dynamiteHintShader; + } else { + icon = cgs.media.breakableHintShader; + } + + break; + case HINT_CHAIR: + icon = cgs.media.notUsableHintShader; + + // only show 'pickupable' if you're not armed, or are armed with a single handed weapon + if ( !( cg.predictedPlayerState.weapon ) || + WEAPS_ONE_HANDED & ( 1 << ( cg.predictedPlayerState.weapon ) ) + ) { + icon = cgs.media.chairHintShader; + } + break; + case HINT_ALARM: + icon = cgs.media.alarmHintShader; + break; + case HINT_HEALTH: + icon = cgs.media.healthHintShader; + break; + case HINT_TREASURE: + icon = cgs.media.treasureHintShader; + break; + case HINT_KNIFE: + icon = cgs.media.knifeHintShader; + break; + case HINT_LADDER: + icon = cgs.media.ladderHintShader; + break; + case HINT_BUTTON: + icon = cgs.media.buttonHintShader; + break; + case HINT_WATER: + icon = cgs.media.waterHintShader; + break; + case HINT_CAUTION: + icon = cgs.media.cautionHintShader; + break; + case HINT_DANGER: + icon = cgs.media.dangerHintShader; + break; + case HINT_SECRET: + icon = cgs.media.secretHintShader; + break; + case HINT_QUESTION: + icon = cgs.media.qeustionHintShader; + break; + case HINT_EXCLAMATION: + icon = cgs.media.exclamationHintShader; + break; + case HINT_CLIPBOARD: + icon = cgs.media.clipboardHintShader; + break; + case HINT_WEAPON: + icon = cgs.media.weaponHintShader; + break; + case HINT_AMMO: + icon = cgs.media.ammoHintShader; + break; + case HINT_ARMOR: + icon = cgs.media.armorHintShader; + break; + case HINT_POWERUP: + icon = cgs.media.powerupHintShader; + break; + case HINT_HOLDABLE: + icon = cgs.media.holdableHintShader; + break; + case HINT_INVENTORY: + icon = cgs.media.inventoryHintShader; + break; + case HINT_EXIT: + icon = cgs.media.exitHintShader; + break; + case HINT_PLYR_FRIEND: + icon = cgs.media.hintPlrFriendShader; + break; + case HINT_PLYR_NEUTRAL: + icon = cgs.media.hintPlrNeutralShader; + break; + case HINT_PLYR_ENEMY: + icon = cgs.media.hintPlrEnemyShader; + break; + case HINT_PLYR_UNKNOWN: + icon = cgs.media.hintPlrUnknownShader; + break; + + // DHM - Nerve :: multiplayer hints + case HINT_BUILD: + icon = cgs.media.buildHintShader; + break; + case HINT_DISARM: + icon = cgs.media.disarmHintShader; + break; + case HINT_REVIVE: + icon = cgs.media.reviveHintShader; + break; + case HINT_DYNAMITE: + icon = cgs.media.dynamiteHintShader; + break; + // dhm - end + + case HINT_ACTIVATE: + case HINT_PLAYER: + default: + icon = cgs.media.usableHintShader; + break; + } + + + if ( !icon ) { + return; + } + + + // color + color = CG_FadeColor( cg.cursorHintTime, cg.cursorHintFade ); + if ( !color ) { + trap_R_SetColor( NULL ); + return; + } + + if ( cg_cursorHints.integer == 3 ) { + color[3] *= 0.5 + 0.5 * sin( (float)cg.time / 150.0 ); + } + + + // size + if ( cg_cursorHints.integer >= 3 ) { // no size pulsing + scale = halfscale = 0; + } else { + if ( cg_cursorHints.integer == 2 ) { + scale = (float)( ( cg.cursorHintTime ) % 1000 ) / 100.0f; // one way size pulse + } else { + scale = CURSORHINT_SCALE * ( 0.5 + 0.5 * sin( (float)cg.time / 150.0 ) ); // sin pulse + + } + halfscale = scale * 0.5f; + } + + // set color and draw the hint + trap_R_SetColor( color ); + CG_DrawPic( rect->x - halfscale, rect->y - halfscale, rect->w + scale, rect->h + scale, icon ); + + if ( icon2 ) { + CG_DrawPic( rect->x - halfscale, rect->y - halfscale, rect->w + scale, rect->h + scale, icon2 ); + } + + trap_R_SetColor( NULL ); + + // draw status bar under the cursor hint + if ( cg.cursorHintValue ) { + Vector4Set( color, 0, 0, 1, 0.5f ); + CG_FilledBar( rect->x, rect->y + rect->h + 4, rect->w, 8, color, NULL, NULL, (float)cg.cursorHintValue / 255.0f, 0 ); + } + +} + + +/* +============== +CG_DrawPlayerAmmoValue + 0 - ammo + 1 - clip +============== +*/ +int CG_DrawFieldWidth( int x, int y, int width, int value, int charWidth, int charHeight ); + +static void CG_DrawPlayerAmmoValue( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle, int type ) { + int ammovalue, ammovalue2 = 0, clipvalue, clipvalue2 = 0; + centity_t *cent; + playerState_t *ps; + int weap, startx; + qboolean special = qfalse; + qboolean skipammo = qfalse; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + weap = cent->currentState.weapon; + + if ( !weap ) { + return; + } + + switch ( weap ) { // some weapons don't draw ammo count text + case WP_KNIFE: + case WP_KNIFE2: + case WP_MEDKIT: + case WP_PLIERS: + case WP_SMOKE_GRENADE: + case WP_AMMO: // JPW NERVE + case WP_DYNAMITE: // JPW NERVE + return; + + case WP_AKIMBO: + special = qtrue; + break; + + case WP_MEDIC_SYRINGE: // JPW NERVE + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: +// case WP_DYNAMITE: // JPW NERVE these recharge + case WP_DYNAMITE2: + case WP_TESLA: + case WP_FLAMETHROWER: + skipammo = qtrue; + if ( type == 0 ) { // don't draw reserve value, just clip (since these weapons have all their ammo in the clip) + return; + } + break; + + default: + break; + } + + // ammo + ammovalue = cg.snap->ps.ammo[BG_FindAmmoForWeapon( weap )]; + ammovalue2 = ps->ammoclip[BG_FindClipForWeapon( weap )]; + + // clip + clipvalue = ps->ammoclip[BG_FindClipForWeapon( weap )]; + if ( special ) { + clipvalue2 = clipvalue; + if ( weapAlts[weap] ) { + clipvalue = ps->ammoclip[weapAlts[weap]]; + } + } + + // NERVE - SMF + if ( !skipammo ) { + startx = CG_DrawFieldWidth( rect->x, rect->y, 4, ammovalue, 20 * scale, 32 * scale ); + } else { + startx = 0; + } + + if ( ammovalue > -1 && type == 0 ) { + if ( shader ) { + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + } else { + trap_R_SetColor( color ); + + // draw the clip count + CG_DrawField( rect->x, rect->y, 4, ammovalue, 20 * scale, 32 * scale, qtrue, qfalse ); + } + + // draw the slash + if ( type == 0 ) { + CG_DrawPic( rect->x - startx - 16, rect->y, 16, 28, trap_R_RegisterShader( "gfx/2d/numbers/slash" ) ); + } + } + + if ( clipvalue > -1 && type == 1 ) { + if ( shader ) { + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + } else { + trap_R_SetColor( color ); + + // draw the clip count + CG_DrawField( rect->x - startx, rect->y, 4, clipvalue, 20 * scale, 32 * scale, qtrue, qfalse ); + } + } +} + + + +static void CG_DrawPlayerHead( rectDef_t *rect, qboolean draw2D ) { + vec3_t angles; + float size, stretch; + float frac; + float x = rect->x; + + VectorClear( angles ); + + if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { + frac = (float)( cg.time - cg.damageTime ) / DAMAGE_TIME; + size = rect->w * 1.25 * ( 1.5 - frac * 0.5 ); + + stretch = size - rect->w * 1.25; + // kick in the direction of damage + x -= stretch * 0.5 + cg.damageX * stretch * 0.5; + + cg.headStartYaw = 180 + cg.damageX * 45; + + cg.headEndYaw = 180 + 20 * cos( crandom() * M_PI ); + cg.headEndPitch = 5 * cos( crandom() * M_PI ); + + cg.headStartTime = cg.time; + cg.headEndTime = cg.time + 100 + random() * 2000; + } else { + if ( cg.time >= cg.headEndTime ) { + // select a new head angle + cg.headStartYaw = cg.headEndYaw; + cg.headStartPitch = cg.headEndPitch; + cg.headStartTime = cg.headEndTime; + cg.headEndTime = cg.time + 100 + random() * 2000; + + cg.headEndYaw = 180 + 20 * cos( crandom() * M_PI ); + cg.headEndPitch = 5 * cos( crandom() * M_PI ); + } + + size = rect->w * 1.25; + } + + // if the server was frozen for a while we may have a bad head start time + if ( cg.headStartTime > cg.time ) { + cg.headStartTime = cg.time; + } + + frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); + frac = frac * frac * ( 3 - 2 * frac ); + angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; + angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; + + CG_DrawHead( x, rect->y, rect->w, rect->h, cg.snap->ps.clientNum, angles ); +} + +static void CG_DrawSelectedPlayerHealth( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + clientInfo_t *ci; + int value; + char num[16]; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if ( ci ) { + if ( shader ) { + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + } else { + Com_sprintf( num, sizeof( num ), "%i", ci->health ); + value = CG_Text_Width( num, scale, 0 ); + CG_Text_Paint( rect->x + ( rect->w - value ) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle ); + } + } +} + +static void CG_DrawSelectedPlayerArmor( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + clientInfo_t *ci; + int value; + char num[16]; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if ( ci ) { + if ( ci->armor > 0 ) { + if ( shader ) { + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + } else { + Com_sprintf( num, sizeof( num ), "%i", ci->armor ); + value = CG_Text_Width( num, scale, 0 ); + CG_Text_Paint( rect->x + ( rect->w - value ) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle ); + } + } + } +} + +qhandle_t CG_StatusHandle( int task ) { +#ifdef MISSIONPACK + qhandle_t h = cgs.media.assaultShader; + switch ( task ) { + case TEAMTASK_OFFENSE: + h = cgs.media.assaultShader; + break; + case TEAMTASK_DEFENSE: + h = cgs.media.defendShader; + break; + case TEAMTASK_PATROL: + h = cgs.media.patrolShader; + break; + case TEAMTASK_FOLLOW: + h = cgs.media.followShader; + break; + case TEAMTASK_CAMP: + h = cgs.media.campShader; + break; + case TEAMTASK_RETRIEVE: + h = cgs.media.retrieveShader; + break; + case TEAMTASK_ESCORT: + h = cgs.media.escortShader; + break; + default: + h = cgs.media.assaultShader; + break; + } + return h; +#else + return 0; +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawSelectedPlayerStatus( rectDef_t *rect ) { +#ifdef MISSIONPACK + clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if ( ci ) { + qhandle_t h; + if ( cgs.orderPending ) { + // blink the icon + if ( cg.time > cgs.orderTime - 2500 && ( cg.time >> 9 ) & 1 ) { + return; + } + h = CG_StatusHandle( cgs.currentOrder ); + } else { + h = CG_StatusHandle( ci->teamTask ); + } + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h ); + } +#endif // #ifdef MISSIONPACK +} + + +static void CG_DrawPlayerStatus( rectDef_t *rect ) { +#ifdef MISSIONPACK + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + if ( ci ) { + qhandle_t h = CG_StatusHandle( ci->teamTask ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h ); + } +#endif // #ifdef MISSIONPACK +} + + +static void CG_DrawSelectedPlayerName( rectDef_t *rect, float scale, vec4_t color, qboolean voice, int textStyle ) { + clientInfo_t *ci; + +// ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if ( ci ) { + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, ci->name, 0, 0, textStyle ); + } +} + +static void CG_DrawSelectedPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + clientInfo_t *ci; + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if ( ci ) { + const char *p = CG_TranslateString( CG_ConfigString( CS_LOCATIONS + ci->location ) ); + + if ( !p || !*p ) { + p = "unknown"; + } + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle ); + } +} + +static void CG_DrawPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + if ( ci ) { + const char *p = CG_TranslateString( CG_ConfigString( CS_LOCATIONS + ci->location ) ); + if ( !p || !*p ) { + p = "unknown"; + } + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle ); + } +} + + + +static void CG_DrawSelectedPlayerWeapon( rectDef_t *rect ) { + clientInfo_t *ci; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if ( ci ) { + if ( cg_weapons[ci->curWeapon].weaponIcon[1] ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ci->curWeapon].weaponIcon[1] ); // (SA) using the 'selected' version of the icon + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader ); + } + } +} + +static void CG_DrawPlayerScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + char num[16]; + int value = cg.snap->ps.persistant[PERS_SCORE]; + + if ( shader ) { + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + } else { + Com_sprintf( num, sizeof( num ), "%i", value ); + value = CG_Text_Width( num, scale, 0 ); + CG_Text_Paint( rect->x + ( rect->w - value ) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle ); + } +} + + +static void CG_DrawHoldableItem( rectDef_t *rect, float scale, qboolean draw2D ) { +/* + int value; + gitem_t *item; + + item = BG_FindItemForHoldable(cg.holdableSelect); + + if(!item) + return; + + value = cg.predictedPlayerState.holdable[cg.holdableSelect]; + + if ( value ) { + CG_RegisterItemVisuals( item - bg_itemlist ); + + if(cg.holdableSelect == HI_WINE) { + if(value > 3) + value = 3; // 3 stages to icon, just draw full if beyond 'full' + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[item - bg_itemlist].icons[2-(value-1)] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[item - bg_itemlist].icons[0] ); + } + } +*/ +} + +static void CG_DrawPlayerItem( rectDef_t *rect, float scale, qboolean draw2D ) { + int value; + vec3_t origin, angles; + + value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; + + if ( value ) { + CG_RegisterItemVisuals( value ); + + if ( qtrue ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icons[0] ); + } else { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].models[0], 0, origin, angles ); + } + } +} + + +static void CG_DrawSelectedPlayerPowerup( rectDef_t *rect, qboolean draw2D ) { + clientInfo_t *ci; + int j; + float x, y; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if ( ci ) { + x = rect->x; + y = rect->y; + + for ( j = 0; j < PW_NUM_POWERUPS; j++ ) { + if ( ci->powerups & ( 1 << j ) ) { + gitem_t *item; + item = BG_FindItemForPowerup( j ); + if ( item ) { + CG_DrawPic( x, y, rect->w, rect->h, trap_R_RegisterShader( item->icon ) ); + x += 3; + y += 3; + return; + } + } + } + } +} + + +static void CG_DrawSelectedPlayerHead( rectDef_t *rect, qboolean draw2D, qboolean voice ) { + clipHandle_t cm; + clientInfo_t *ci; + float len; + vec3_t origin; + vec3_t mins, maxs, angles; + + +// ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + + if ( ci ) { + if ( cg_draw3dIcons.integer ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->modelInfo->headOffset, origin ); + + angles[PITCH] = 0; + angles[YAW] = 180; + angles[ROLL] = 0; + + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, ci->headModel, ci->headSkin, origin, angles ); + + } else if ( cg_drawIcons.integer ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, ci->modelIcon ); + } + + // if they are deferred, draw a cross out + if ( ci->deferred ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader ); + } + } +} + +static void CG_DrawPlayerHealth( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + playerState_t *ps; + int value; + vec4_t color2; + + ps = &cg.snap->ps; + + if ( cgs.gametype >= GT_WOLF && ( ps->pm_flags & PMF_FOLLOW ) ) { + value = cgs.clientinfo[ ps->clientNum ].health; + } else { + value = ps->stats[STAT_HEALTH]; + } + + // DHM - Nerve :: Don't show negative health + if ( value < 0 ) { + value = 0; + } + + if ( shader ) { + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + trap_R_SetColor( NULL ); + } else { + trap_R_SetColor( color ); + CG_DrawField( rect->x, rect->y, 3, value, 20 * scale, 32 * scale, qtrue, qtrue ); // NERVE - SMF + + // DHM - Nerve :: temp display of number of lifes left + + if ( ps->persistant[PERS_RESPAWNS_LEFT] >= 0 ) { + + VectorSet( color2, 1.0f, 1.0f, 1.0f ); + color2[3] = color[3]; + trap_R_SetColor( color2 ); + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + CG_DrawPic( rect->x + 92, rect->y - 2, 64, 32, cgs.media.hudAlliedHelmet ); + } else { + CG_DrawPic( rect->x + 92, rect->y - 2, 64, 32, cgs.media.hudAxisHelmet ); + } + + trap_R_SetColor( color ); + CG_DrawField( rect->x + 144, rect->y, 3, ps->persistant[PERS_RESPAWNS_LEFT], 20 * scale, 32 * scale, qtrue, qtrue ); + } + + if ( cgs.clientinfo[cg.snap->ps.clientNum].powerups & ( 1 << PW_INVULNERABLE ) ) { + CG_DrawPic( rect->x + 6, rect->y - 44, 36, 36, cgs.media.spawnInvincibleShader ); + } + // dhm + } +} + +static void CG_DrawRedScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int value; + char num[16]; + if ( cgs.scores1 == SCORE_NOT_PRESENT ) { + Com_sprintf( num, sizeof( num ), "-" ); + } else { + Com_sprintf( num, sizeof( num ), "%i", cgs.scores1 ); + } + value = CG_Text_Width( num, scale, 0 ); + CG_Text_Paint( rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle ); +} + +static void CG_DrawBlueScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int value; + char num[16]; + + if ( cgs.scores2 == SCORE_NOT_PRESENT ) { + Com_sprintf( num, sizeof( num ), "-" ); + } else { + Com_sprintf( num, sizeof( num ), "%i", cgs.scores2 ); + } + value = CG_Text_Width( num, scale, 0 ); + CG_Text_Paint( rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle ); +} + +// FIXME: team name support +static void CG_DrawRedName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { +#ifdef MISSIONPACK + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, cg_redTeamName.string, 0, 0, textStyle ); +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawBlueName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { +#ifdef MISSIONPACK + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, cg_blueTeamName.string, 0, 0, textStyle ); +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawBlueFlagName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { +#ifdef MISSIONPACK + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1 << PW_BLUEFLAG ) ) { + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle ); + return; + } + } +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawBlueFlagStatus( rectDef_t *rect, qhandle_t shader ) { +#ifdef MISSIONPACK + if ( cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF ) { + if ( cgs.gametype == GT_HARVESTER ) { + vec4_t color = {0, 0, 1, 1}; + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.blueCubeIcon ); + trap_R_SetColor( NULL ); + } + return; + } + if ( shader ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + } else { + gitem_t *item = BG_FindItemForPowerup( PW_BLUEFLAG ); + if ( item ) { + vec4_t color = {0, 0, 1, 1}; + trap_R_SetColor( color ); + if ( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.blueflag] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); + } + trap_R_SetColor( NULL ); + } + } +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawBlueFlagHead( rectDef_t *rect ) { +#ifdef MISSIONPACK + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1 << PW_BLUEFLAG ) ) { + vec3_t angles; + VectorClear( angles ); + angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; + CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); + return; + } + } +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawRedFlagName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { +#ifdef MISSIONPACK + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1 << PW_REDFLAG ) ) { + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle ); + return; + } + } +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawRedFlagStatus( rectDef_t *rect, qhandle_t shader ) { +#ifdef MISSIONPACK + if ( cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF ) { + if ( cgs.gametype == GT_HARVESTER ) { + vec4_t color = {1, 0, 0, 1}; + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.redCubeIcon ); + trap_R_SetColor( NULL ); + } + return; + } + if ( shader ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + } else { + gitem_t *item = BG_FindItemForPowerup( PW_REDFLAG ); + if ( item ) { + vec4_t color = {1, 0, 0, 1}; + trap_R_SetColor( color ); + if ( cgs.redflag >= 0 && cgs.redflag <= 2 ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.redflag] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); + } + trap_R_SetColor( NULL ); + } + } +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawRedFlagHead( rectDef_t *rect ) { +#ifdef MISSIONPACK + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1 << PW_REDFLAG ) ) { + vec3_t angles; + VectorClear( angles ); + angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; + CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); + return; + } + } +#endif // #ifdef MISSIONPACK +} + +static void CG_HarvesterSkulls( rectDef_t *rect, float scale, vec4_t color, qboolean force2D, int textStyle ) { +#ifdef MISSIONPACK + char num[16]; + vec3_t origin, angles; + qhandle_t handle; + int value = cg.snap->ps.generic1; + + if ( cgs.gametype != GT_HARVESTER ) { + return; + } + + if ( value > 99 ) { + value = 99; + } + + Com_sprintf( num, sizeof( num ), "%i", value ); + value = CG_Text_Width( num, scale, 0 ); + CG_Text_Paint( rect->x + ( rect->w - value ), rect->y + rect->h, scale, color, num, 0, 0, textStyle ); + + if ( cg_drawIcons.integer ) { + if ( !force2D && cg_draw3dIcons.integer ) { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeModel; + } else { + handle = cgs.media.blueCubeModel; + } + CG_Draw3DModel( rect->x, rect->y, 35, 35, handle, 0, origin, angles ); + } else { + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeIcon; + } else { + handle = cgs.media.blueCubeIcon; + } + CG_DrawPic( rect->x + 3, rect->y + 16, 20, 20, handle ); + } + } +#endif // #ifdef MISSIONPACK +} + +static void CG_OneFlagStatus( rectDef_t *rect ) { +#ifdef MISSIONPACK + if ( cgs.gametype != GT_1FCTF ) { + return; + } else { + gitem_t *item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + if ( item ) { + if ( cgs.flagStatus >= 0 && cgs.flagStatus <= 4 ) { + vec4_t color = {1, 1, 1, 1}; + int index = 0; + if ( cgs.flagStatus == FLAG_TAKEN_RED ) { + color[1] = color[2] = 0; + index = 1; + } else if ( cgs.flagStatus == FLAG_TAKEN_BLUE ) { + color[0] = color[1] = 0; + index = 1; + } else if ( cgs.flagStatus == FLAG_DROPPED ) { + index = 2; + } + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[index] ); + } + } + } +#endif // #ifdef MISSIONPACK +} + + +static void CG_DrawCTFPowerUp( rectDef_t *rect ) { +#ifdef MISSIONPACK + int value; + value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); + } +#endif // #ifdef MISSIONPACK +} + + + +static void CG_DrawTeamColor( rectDef_t *rect, vec4_t color ) { + CG_DrawTeamBackground( rect->x, rect->y, rect->w, rect->h, color[3], cg.snap->ps.persistant[PERS_TEAM] ); +} + + +static void CG_DrawAreaHoldable( rectDef_t *rect, int align, float spacing, float scale, vec4_t color ) { +} + +static void CG_DrawAreaWeapons( rectDef_t *rect, int align, float spacing, float scale, vec4_t color ) { +} + + +static void CG_DrawAreaPowerUp( rectDef_t *rect, int align, float spacing, float scale, vec4_t color ) { + char num[16]; + int sorted[MAX_POWERUPS]; + int sortedTime[MAX_POWERUPS]; + int i, j, k; + int active; + playerState_t *ps; + int t; + gitem_t *item; + float f; + rectDef_t r2; + float *inc; + r2.x = rect->x; + r2.y = rect->y; + r2.w = rect->w; + r2.h = rect->h; + + inc = ( align == HUD_VERTICAL ) ? &r2.y : &r2.x; + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + return; + } + + // sort the list by time remaining + active = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( !ps->powerups[ i ] ) { + continue; + } + t = ps->powerups[ i ] - cg.time; + // ZOID--don't draw if the power up has unlimited time (999 seconds) + // This is true of the CTF flags + if ( t <= 0 || t >= 999000 ) { + continue; + } + + // insert into the list + for ( j = 0 ; j < active ; j++ ) { + if ( sortedTime[j] >= t ) { + for ( k = active - 1 ; k >= j ; k-- ) { + sorted[k + 1] = sorted[k]; + sortedTime[k + 1] = sortedTime[k]; + } + break; + } + } + sorted[j] = i; + sortedTime[j] = t; + active++; + } + + // draw the icons and timers + for ( i = 0 ; i < active ; i++ ) { + item = BG_FindItemForPowerup( sorted[i] ); + + if ( item ) { + t = ps->powerups[ sorted[i] ]; + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + trap_R_SetColor( NULL ); + } else { + vec4_t modulate; + + f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; + f -= (int)f; + modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; + trap_R_SetColor( modulate ); + } + + CG_DrawPic( r2.x, r2.y, r2.w * .75, r2.h, trap_R_RegisterShader( item->icon ) ); + + Com_sprintf( num, sizeof( num ), "%i", sortedTime[i] / 1000 ); + CG_Text_Paint( r2.x + ( r2.w * .75 ) + 3, r2.y + r2.h, scale, color, num, 0, 0, 0 ); + *inc += r2.w + spacing; + } + + } + trap_R_SetColor( NULL ); + +} + +float CG_GetValue( int ownerDraw, int type ) { + centity_t *cent; + clientInfo_t *ci; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + switch ( ownerDraw ) { + + case CG_SELECTEDPLAYER_ARMOR: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->armor; + break; + case CG_SELECTEDPLAYER_HEALTH: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->health; + break; + + case CG_PLAYER_ARMOR_VALUE: + return ps->stats[STAT_ARMOR]; + break; + case CG_PLAYER_AMMO_VALUE: + if ( cent->currentState.weapon ) { + if ( type == RANGETYPE_RELATIVE ) { + int weap = BG_FindAmmoForWeapon( cent->currentState.weapon ); + return (float)ps->ammo[weap] / (float)ammoTable[weap].maxammo; + } else { + return ps->ammo[BG_FindAmmoForWeapon( cent->currentState.weapon )]; + } + } + break; + case CG_PLAYER_AMMOCLIP_VALUE: + if ( cent->currentState.weapon ) { + if ( type == RANGETYPE_RELATIVE ) { + return (float)ps->ammoclip[BG_FindClipForWeapon( cent->currentState.weapon )] / (float)ammoTable[cent->currentState.weapon].maxclip; + } else { + return ps->ammoclip[BG_FindClipForWeapon( cent->currentState.weapon )]; + } + } + break; + case CG_PLAYER_SCORE: + return cg.snap->ps.persistant[PERS_SCORE]; + break; + case CG_PLAYER_HEALTH: + if ( cgs.gametype >= GT_WOLF && ( ps->pm_flags & PMF_FOLLOW ) ) { + ci = &cgs.clientinfo[ ps->clientNum ]; + return ci->health; + } else { + return ps->stats[STAT_HEALTH]; + } + break; + case CG_RED_SCORE: + return cgs.scores1; + break; + case CG_BLUE_SCORE: + return cgs.scores2; + break; + case CG_PLAYER_WEAPON_STABILITY: //----(SA) added + return ps->aimSpreadScale; + break; + +#define BONUSTIME 60000.0f +#define SPRINTTIME 20000.0f + + case CG_STAMINA: //----(SA) added + if ( type == RANGETYPE_RELATIVE ) { + return (float)cg.snap->ps.sprintTime / SPRINTTIME; + } else { + return cg.snap->ps.sprintTime; + } + break; + default: + break; + } + + return -1; +} + +qboolean CG_OtherTeamHasFlag() { +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + if ( cgs.gametype == GT_1FCTF ) { + if ( team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_BLUE ) { + return qtrue; + } else if ( team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_RED ) { + return qtrue; + } else { + return qfalse; + } + } else { + if ( team == TEAM_RED && cgs.redflag == FLAG_TAKEN ) { + return qtrue; + } else if ( team == TEAM_BLUE && cgs.blueflag == FLAG_TAKEN ) { + return qtrue; + } else { + return qfalse; + } + } + } +#endif // #ifdef MISSIONPACK + return qfalse; +} + +qboolean CG_YourTeamHasFlag() { +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + if ( cgs.gametype == GT_1FCTF ) { + if ( team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_RED ) { + return qtrue; + } else if ( team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_BLUE ) { + return qtrue; + } else { + return qfalse; + } + } else { + if ( team == TEAM_RED && cgs.blueflag == FLAG_TAKEN ) { + return qtrue; + } else if ( team == TEAM_BLUE && cgs.redflag == FLAG_TAKEN ) { + return qtrue; + } else { + return qfalse; + } + } + } +#endif // #ifdef MISSIONPACK + return qfalse; +} + +// THINKABOUTME: should these be exclusive or inclusive.. +// +qboolean CG_OwnerDrawVisible( int flags ) { + +//----(SA) added + if ( flags & CG_SHOW_NOT_V_BINOC ) { // if looking through binocs + if ( cg.zoomedBinoc ) { + return qfalse; + } + } + + if ( flags & CG_SHOW_NOT_V_SNIPER ) { // if looking through sniper scope + if ( cg.weaponSelect == WP_SNIPERRIFLE ) { + return qfalse; + } + } + + if ( flags & CG_SHOW_NOT_V_SNOOPER ) { // if looking through snooper scope + if ( cg.weaponSelect == WP_SNOOPERSCOPE ) { + return qfalse; + } + } + + if ( flags & CG_SHOW_NOT_V_FGSCOPE ) { // if looking through fg42 scope + if ( cg.weaponSelect == WP_FG42SCOPE ) { + return qfalse; + } + } + +//----(SA) end + + if ( flags & CG_SHOW_TEAMINFO ) { + return ( cg_currentSelectedPlayer.integer == numSortedTeamPlayers ); + } + + if ( flags & CG_SHOW_NOTEAMINFO ) { + return !( cg_currentSelectedPlayer.integer == numSortedTeamPlayers ); + } + + if ( flags & CG_SHOW_OTHERTEAMHASFLAG ) { + return CG_OtherTeamHasFlag(); + } + + if ( flags & CG_SHOW_YOURTEAMHASENEMYFLAG ) { + return CG_YourTeamHasFlag(); + } + +#ifdef MISSIONPACK + if ( flags & ( CG_SHOW_BLUE_TEAM_HAS_REDFLAG | CG_SHOW_RED_TEAM_HAS_BLUEFLAG ) ) { + if ( flags & CG_SHOW_BLUE_TEAM_HAS_REDFLAG && ( cgs.redflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_RED ) ) { + return qtrue; + } else if ( flags & CG_SHOW_RED_TEAM_HAS_BLUEFLAG && ( cgs.blueflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_BLUE ) ) { + return qtrue; + } + return qfalse; + } +#endif // #ifdef MISSIONPACK + + if ( flags & CG_SHOW_ANYTEAMGAME ) { + if ( cgs.gametype >= GT_TEAM ) { + return qtrue; + } + } + + if ( flags & CG_SHOW_ANYNONTEAMGAME ) { + if ( cgs.gametype < GT_TEAM ) { + return qtrue; + } + } + +#ifdef MISSIONPACK + if ( flags & CG_SHOW_HARVESTER ) { + if ( cgs.gametype == GT_HARVESTER ) { + return qtrue; + } else { + return qfalse; + } + } + + if ( flags & CG_SHOW_ONEFLAG ) { + if ( cgs.gametype == GT_1FCTF ) { + return qtrue; + } else { + return qfalse; + } + } + +#endif // #ifdef MISSIONPACK + if ( flags & CG_SHOW_CTF ) { + if ( cgs.gametype == GT_CTF ) { + return qtrue; + } + } + +#ifdef MISSIONPACK + if ( flags & CG_SHOW_OBELISK ) { + if ( cgs.gametype == GT_OBELISK ) { + return qtrue; + } else { + return qfalse; + } + } +#endif // #ifdef MISSIONPACK + + if ( flags & CG_SHOW_HEALTHCRITICAL ) { + if ( cg.snap->ps.stats[STAT_HEALTH] < 25 ) { + return qtrue; + } + } + + if ( flags & CG_SHOW_HEALTHOK ) { + if ( cg.snap->ps.stats[STAT_HEALTH] > 25 ) { + return qtrue; + } + } + + if ( flags & CG_SHOW_SINGLEPLAYER ) { + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + return qtrue; + } + } + + if ( flags & CG_SHOW_TOURNAMENT ) { + if ( cgs.gametype == GT_TOURNAMENT ) { + return qtrue; + } + } + + if ( flags & CG_SHOW_DURINGINCOMINGVOICE ) { + } + +#ifdef MISSIONPACK + if ( flags & CG_SHOW_IF_PLAYER_HAS_FLAG ) { + if ( cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG] ) { + return qtrue; + } + } +#endif // #ifdef MISSIONPACK + +//----(SA) added + if ( flags & CG_SHOW_NOT_V_CLEAR ) { + // if /not/ looking through binocs,snooper or sniper + if ( !cg.zoomedBinoc && !( cg.weaponSelect == WP_SNIPERRIFLE ) && !( cg.weaponSelect == WP_SNOOPERSCOPE ) && !( cg.weaponSelect == WP_FG42SCOPE ) ) { + return qfalse; + } + } + + if ( flags & ( CG_SHOW_NOT_V_BINOC | CG_SHOW_NOT_V_SNIPER | CG_SHOW_NOT_V_SNOOPER | CG_SHOW_NOT_V_FGSCOPE ) ) { + // setting any of these does not necessarily disable drawing in regular view + // CG_SHOW_NOT_V_CLEAR must also be set to hide for reg view + if ( !( flags & CG_SHOW_NOT_V_CLEAR ) ) { + return qtrue; + } + } + +//----(SA) end + + + return qfalse; +} + + + +static void CG_DrawPlayerHasFlag( rectDef_t *rect, qboolean force2D ) { +#ifdef MISSIONPACK + int adj = ( force2D ) ? 0 : 2; + if ( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_RED, force2D ); + } else if ( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_BLUE, force2D ); + } else if ( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_FREE, force2D ); + } +#endif // #ifdef MISSIONPACK +} + +static void CG_DrawAreaSystemChat( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader ) { + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, systemChat, 0, 0, 0 ); +} + +static void CG_DrawAreaTeamChat( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader ) { + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color,teamChat1, 0, 0, 0 ); +} + +static void CG_DrawAreaChat( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader ) { + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, teamChat2, 0, 0, 0 ); +} + +const char *CG_GetKillerText() { + const char *s = ""; + if ( cg.killerName[0] ) { + s = va( "Fragged by %s", cg.killerName ); + } + return s; +} + + +static void CG_DrawKiller( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + // fragged by ... line + if ( cg.killerName[0] ) { + int x = rect->x + rect->w / 2; + CG_Text_Paint( x - CG_Text_Width( CG_GetKillerText(), scale, 0 ) / 2, rect->y + rect->h, scale, color, CG_GetKillerText(), 0, 0, textStyle ); + } + +} + + +static void CG_DrawCapFragLimit( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int limit = ( cgs.gametype >= GT_CTF ) ? cgs.capturelimit : cgs.fraglimit; + CG_Text_Paint( rect->x, rect->y, scale, color, va( "%2i", limit ),0, 0, textStyle ); +} + +static void CG_Draw1stPlace( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + if ( cgs.scores1 != SCORE_NOT_PRESENT ) { + CG_Text_Paint( rect->x, rect->y, scale, color, va( "%2i", cgs.scores1 ),0, 0, textStyle ); + } +} + +static void CG_Draw2ndPlace( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + if ( cgs.scores2 != SCORE_NOT_PRESENT ) { + CG_Text_Paint( rect->x, rect->y, scale, color, va( "%2i", cgs.scores2 ),0, 0, textStyle ); + } +} + +const char *CG_GetGameStatusText() { + const char *s = ""; + if ( cgs.gametype < GT_TEAM ) { + if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + s = va( "%s place with %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),cg.snap->ps.persistant[PERS_SCORE] ); + } + } else { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va( "Teams are tied at %i", cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va( "Red leads Blue, %i to %i", cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va( "Blue leads Red, %i to %i", cg.teamScores[1], cg.teamScores[0] ); + } + } + return s; +} + +static void CG_DrawGameStatus( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, CG_GetGameStatusText(), 0, 0, textStyle ); +} + +const char *CG_GameTypeString() { + if ( cgs.gametype == GT_FFA ) { + return "Free For All"; + } else if ( cgs.gametype == GT_TEAM ) { + return "Team Deathmatch"; + } else if ( cgs.gametype == GT_CTF ) { + return "Capture the Flag"; +#ifdef MISSIONPACK + } else if ( cgs.gametype == GT_1FCTF ) { + return "One Flag CTF"; + } else if ( cgs.gametype == GT_OBELISK ) { + return "Overload"; + } else if ( cgs.gametype == GT_HARVESTER ) { + return "Harvester"; +#endif // #ifdef MISSIONPACK + } + return ""; +} +static void CG_DrawGameType( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + CG_Text_Paint( rect->x, rect->y + rect->h, scale, color, CG_GameTypeString(), 0, 0, textStyle ); +} + +static void CG_Text_Paint_Limit( float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit ) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + if ( text ) { + const char *s = text; + float max = *maxX; + float useScale; + fontInfo_t *font = &cgDC.Assets.textFont; + if ( scale <= cg_smallFont.value ) { + font = &cgDC.Assets.smallFont; + } else if ( scale > cg_bigFont.value ) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + trap_R_SetColor( color ); + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + glyph = &font->glyphs[(unsigned char)*s]; // NERVE - SMF - this needs to be an unsigned cast for localization + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if ( CG_Text_Width( s, useScale, 1 ) + x > max ) { + *maxX = 0; + break; + } + CG_Text_PaintChar( x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + x += ( glyph->xSkip * useScale ) + adjust; + *maxX = x; + count++; + s++; + } + } + trap_R_SetColor( NULL ); + } + +} + + + +#define PIC_WIDTH 12 + +void CG_DrawNewTeamInfo( rectDef_t *rect, float text_x, float text_y, float scale, vec4_t color, qhandle_t shader ) { +#ifdef MISSIONPACK + int xx; + float y; + int i, j, len, count; + const char *p; + vec4_t hcolor; + float pwidth, lwidth, maxx, leftOver; + clientInfo_t *ci; + gitem_t *item; + qhandle_t h; + + // max player name width + pwidth = 0; + count = ( numSortedTeamPlayers > 8 ) ? 8 : numSortedTeamPlayers; + for ( i = 0; i < count; i++ ) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM] ) { + len = CG_Text_Width( ci->name, scale, 0 ); + if ( len > pwidth ) { + pwidth = len; + } + } + } + + // max location name width + lwidth = 0; + for ( i = 1; i < MAX_LOCATIONS; i++ ) { + p = CG_TranslateString( CG_ConfigString( CS_LOCATIONS + i ) ); + if ( p && *p ) { + len = CG_Text_Width( p, scale, 0 ); + if ( len > lwidth ) { + lwidth = len; + } + } + } + + y = rect->y; + + for ( i = 0; i < count; i++ ) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM] ) { + + xx = rect->x + 1; + for ( j = 0; j <= PW_NUM_POWERUPS; j++ ) { + if ( ci->powerups & ( 1 << j ) ) { + + item = BG_FindItemForPowerup( j ); + + if ( item ) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, trap_R_RegisterShader( item->icon ) ); + xx += PIC_WIDTH; + } + } + } + + // FIXME: max of 3 powerups shown properly + xx = rect->x + ( PIC_WIDTH * 3 ) + 2; + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + trap_R_SetColor( hcolor ); + CG_DrawPic( xx, y + 1, PIC_WIDTH - 2, PIC_WIDTH - 2, cgs.media.heartShader ); + + //Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + //CG_Text_Paint(xx, y + text_y, scale, hcolor, st, 0, 0); + + // draw weapon icon + xx += PIC_WIDTH + 1; + +// weapon used is not that useful, use the space for task +#if 0 + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cgs.media.deferShader ); + } +#endif + + trap_R_SetColor( NULL ); + if ( cgs.orderPending ) { + // blink the icon + if ( cg.time > cgs.orderTime - 2500 && ( cg.time >> 9 ) & 1 ) { + h = 0; + } else { + h = CG_StatusHandle( cgs.currentOrder ); + } + } else { + h = CG_StatusHandle( ci->teamTask ); + } + + if ( h ) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, h ); + } + + xx += PIC_WIDTH + 1; + + leftOver = rect->w - xx; + maxx = xx + leftOver / 3; + + + + CG_Text_Paint_Limit( &maxx, xx, y + text_y, scale, color, ci->name, 0, 0 ); + + p = CG_TranslateString( CG_ConfigString( CS_LOCATIONS + ci->location ) ); + if ( !p || !*p ) { + p = "unknown"; + } + + xx += leftOver / 3 + 2; + maxx = rect->w - 4; + + CG_Text_Paint_Limit( &maxx, xx, y + text_y, scale, color, p, 0, 0 ); + y += text_y + 2; + if ( y + text_y + 2 > rect->y + rect->h ) { + break; + } + + } + } +#endif // #ifdef MISSIONPACK +} + + +void CG_DrawTeamSpectators( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader ) { + if ( cg.spectatorLen ) { + float maxX; + + if ( cg.spectatorWidth == -1 ) { + cg.spectatorWidth = 0; + cg.spectatorPaintX = rect->x + 1; + cg.spectatorPaintX2 = -1; + } + + if ( cg.spectatorOffset > cg.spectatorLen ) { + cg.spectatorOffset = 0; + cg.spectatorPaintX = rect->x + 1; + cg.spectatorPaintX2 = -1; + } + + if ( cg.time > cg.spectatorTime ) { + cg.spectatorTime = cg.time + 10; + if ( cg.spectatorPaintX <= rect->x + 2 ) { + if ( cg.spectatorOffset < cg.spectatorLen ) { + cg.spectatorPaintX += CG_Text_Width( &cg.spectatorList[cg.spectatorOffset], scale, 1 ) - 1; + cg.spectatorOffset++; + } else { + cg.spectatorOffset = 0; + if ( cg.spectatorPaintX2 >= 0 ) { + cg.spectatorPaintX = cg.spectatorPaintX2; + } else { + cg.spectatorPaintX = rect->x + rect->w - 2; + } + cg.spectatorPaintX2 = -1; + } + } else { + cg.spectatorPaintX--; + if ( cg.spectatorPaintX2 >= 0 ) { + cg.spectatorPaintX2--; + } + } + } + + maxX = rect->x + rect->w - 2; + CG_Text_Paint_Limit( &maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, &cg.spectatorList[cg.spectatorOffset], 0, 0 ); + if ( cg.spectatorPaintX2 >= 0 ) { + float maxX2 = rect->x + rect->w - 2; + CG_Text_Paint_Limit( &maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, color, cg.spectatorList, 0, cg.spectatorOffset ); + } + if ( cg.spectatorOffset && maxX > 0 ) { + // if we have an offset ( we are skipping the first part of the string ) and we fit the string + if ( cg.spectatorPaintX2 == -1 ) { + cg.spectatorPaintX2 = rect->x + rect->w - 2; + } + } else { + cg.spectatorPaintX2 = -1; + } + + } +} + + + +void CG_DrawMedal( int ownerDraw, rectDef_t *rect, float scale, vec4_t color, qhandle_t shader ) { + score_t *score = &cg.scores[cg.selectedScore]; + float value = 0; + char *text = NULL; + color[3] = 0.25; + + switch ( ownerDraw ) { + case CG_ACCURACY: + value = score->accuracy; + break; + case CG_ASSISTS: + value = score->assistCount; + break; + case CG_DEFEND: + value = score->defendCount; + break; + case CG_EXCELLENT: + value = score->excellentCount; + break; + case CG_IMPRESSIVE: + value = score->impressiveCount; + break; + case CG_PERFECT: + value = score->perfect; + break; + case CG_GAUNTLET: + value = score->guantletCount; + break; + case CG_CAPTURES: + value = score->captures; + break; + } + + if ( value > 0 ) { + if ( ownerDraw != CG_PERFECT ) { + if ( ownerDraw == CG_ACCURACY ) { + text = va( "%i%%", (int)value ); + if ( value > 50 ) { + color[3] = 1.0; + } + } else { + text = va( "%i", (int)value ); + color[3] = 1.0; + } + } else { + if ( value ) { + color[3] = 1.0; + } + text = "Wow"; + } + } + + trap_R_SetColor( color ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + + if ( text ) { + color[3] = 1.0; + value = CG_Text_Width( text, scale, 0 ); + CG_Text_Paint( rect->x + ( rect->w - value ) / 2, rect->y + rect->h + 10, scale, color, text, 0, 0, 0 ); + } + trap_R_SetColor( NULL ); + +} + + +/* +============== +CG_DrawWeapStability + draw a bar showing current stability level (0-255), max at current weapon/ability, and 'perfect' reference mark + + probably only drawn for scoped weapons +============== +*/ +void CG_DrawWeapStability( rectDef_t *rect, vec4_t color, int align ) { + vec4_t goodColor = {0, 1, 0, 0.5f}, badColor = {1, 0, 0, 0.5f}; + + if ( !cg_drawSpreadScale.integer ) { + return; + } + + if ( cg_drawSpreadScale.integer == 1 && !( cg.weaponSelect == WP_SNOOPERSCOPE || cg.weaponSelect == WP_SNIPERRIFLE ) ) { + // cg_drawSpreadScale of '1' means only draw for scoped weapons, '2' means draw all the time (for debugging) + return; + } + + if ( !( cg.snap->ps.aimSpreadScale ) ) { + return; + } + + CG_FilledBar( rect->x, rect->y, rect->w, rect->h, goodColor, badColor, NULL, (float)cg.snap->ps.aimSpreadScale / 255.0f, 2 | 4 | 256 ); // flags (BAR_CENTER|BAR_VERT|BAR_LERP_COLOR) +} + + +/* +============== +CG_DrawWeapHeat +============== +*/ +void CG_DrawWeapHeat( rectDef_t *rect, int align ) { + vec4_t color = {1, 0, 0, 0.2f}, color2 = {1, 0, 0, 0.5f}; + int flags = 0; + + if ( !( cg.snap->ps.curWeapHeat ) ) { + return; + } + + if ( align != HUD_HORIZONTAL ) { + flags |= 4; // BAR_VERT + + } + flags |= 1; // BAR_LEFT - this is hardcoded now, but will be decided by the menu script + flags |= 16; // BAR_BG - draw the filled contrast box +// flags|=32; // BAR_BGSPACING_X0Y5 - different style + + flags |= 256; // BAR_COLOR_LERP + CG_FilledBar( rect->x, rect->y, rect->w, rect->h, color, color2, NULL, (float)cg.snap->ps.curWeapHeat / 255.0f, flags ); +} + + +/* +============== +CG_DrawFatigue +============== +*/ + +static void CG_DrawFatigue( rectDef_t *rect, vec4_t color, int align ) { + // vec4_t color = {0, 1, 0, 1}, color2 = {1, 0, 0, 1}; + vec4_t colorBonus = {1, 1, 0, 0.45f}; // yellow (a little more solid for the 'bonus' stamina) + float barFrac; //, omBarFrac; + int flags = 0; + + barFrac = (float)cg.snap->ps.sprintTime / SPRINTTIME; +// omBarFrac = 1.0f-barFrac; + + if ( align != HUD_HORIZONTAL ) { + flags |= 4; // BAR_VERT + flags |= 1; // BAR_LEFT (left, when vertical means grow 'up') + } + + CG_FilledBar( rect->x, rect->y + 6, rect->w, rect->h * 0.82f, color, NULL, NULL, (float)cg.snap->ps.sprintTime / SPRINTTIME, flags ); + + // fill in the left side of the bar with the counter for the nofatigue powerup + + if ( cg.snap->ps.powerups[PW_NOFATIGUE] ) { + CG_FilledBar( rect->x, rect->y, rect->w / 2, rect->h, colorBonus, NULL, NULL, cg.snap->ps.powerups[PW_NOFATIGUE] / BONUSTIME, flags ); + } + + colorBonus[2] = 1.0f; + colorBonus[3] = cg_hudAlpha.value; + trap_R_SetColor( colorBonus ); // JPW NERVE +// CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.hudSprintBar ); +} + +// DHM - Nerve +static void CG_DrawWeapRecharge( rectDef_t *rect, vec4_t color, int align ) { + float barFrac; + float chargeTime; + int weap = 0; + int flags = 0; + qboolean fade = qfalse; + vec4_t bgcolor = {1.0f, 1.0f, 1.0f, 0.25f}; + + if ( align != HUD_HORIZONTAL ) { + flags |= 4; // BAR_VERT + flags |= 1; // BAR_LEFT (left, when vertical means grow 'up') + } + flags |= 16; + +// JPW NERVE -- added drawWeaponPercent in multiplayer + if ( cgs.gametype >= GT_WOLF ) { + + // DHM - Only draw bar if weapon uses it + weap = cg.snap->ps.weapon; + + if ( !( cg.snap->ps.eFlags & EF_ZOOMING ) ) { + if ( weap != WP_PANZERFAUST && weap != WP_DYNAMITE && weap != WP_MEDKIT && weap != WP_SMOKE_GRENADE && weap != WP_PLIERS && weap != WP_AMMO ) { + fade = qtrue; + } + } + + if ( cg.snap->ps.stats[ STAT_PLAYER_CLASS ] == PC_ENGINEER ) { + chargeTime = cg_engineerChargeTime.value; + } else if ( cg.snap->ps.stats[ STAT_PLAYER_CLASS ] == PC_MEDIC ) { + chargeTime = cg_medicChargeTime.value; + } else if ( cg.snap->ps.stats[ STAT_PLAYER_CLASS ] == PC_LT ) { + chargeTime = cg_LTChargeTime.value; + } else { + chargeTime = cg_soldierChargeTime.value; + } + + barFrac = (float)( cg.time - cg.snap->ps.classWeaponTime ) / chargeTime; + + if ( barFrac > 1.0 ) { + barFrac = 1.0; + } + + color[0] = 1.0f; + color[1] = color[2] = barFrac; + color[3] = 0.25 + barFrac * 0.5; + + if ( fade ) { + bgcolor[3] *= 0.4f; + color[3] *= 0.4; + } + + CG_FilledBar( rect->x, rect->y + 6, rect->w, rect->h * 0.84f, color, NULL, bgcolor, barFrac, flags ); + + color[1] = color[2] = 1.0f; + color[3] = cg_hudAlpha.value; + trap_R_SetColor( color ); +// CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.hudSprintBar ); + } +// jpw +} +// dhm - end + +/* +============== +CG_OwnerDraw +============== +*/ +void CG_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + rectDef_t rect; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + //if (ownerDrawFlags != 0 && !CG_OwnerDrawVisible(ownerDrawFlags)) { + // return; + //} + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + switch ( ownerDraw ) { + case CG_PLAYER_WEAPON_ICON2D: + CG_DrawPlayerWeaponIcon( &rect, ownerDrawFlags & CG_SHOW_HIGHLIGHTED, align ); + break; + case CG_PLAYER_ARMOR_ICON: + CG_DrawPlayerArmorIcon( &rect, ownerDrawFlags & CG_SHOW_2DONLY ); + break; + case CG_PLAYER_ARMOR_ICON2D: + CG_DrawPlayerArmorIcon( &rect, qtrue ); + break; + case CG_PLAYER_ARMOR_VALUE: + CG_DrawPlayerArmorValue( &rect, scale, color, shader, textStyle ); + break; + case CG_PLAYER_AMMO_ICON: + CG_DrawPlayerAmmoIcon( &rect, ownerDrawFlags & CG_SHOW_2DONLY ); + break; + case CG_PLAYER_AMMO_ICON2D: + CG_DrawPlayerAmmoIcon( &rect, qtrue ); + break; + case CG_PLAYER_AMMO_VALUE: + CG_DrawPlayerAmmoValue( &rect, scale, color, shader, textStyle, 0 ); + break; + case CG_CURSORHINT: + CG_DrawCursorhint( &rect ); + break; + case CG_PLAYER_AMMOCLIP_VALUE: + CG_DrawPlayerAmmoValue( &rect, scale, color, shader, textStyle, 1 ); + break; + case CG_SELECTEDPLAYER_HEAD: + CG_DrawSelectedPlayerHead( &rect, ownerDrawFlags & CG_SHOW_2DONLY, qfalse ); + break; + case CG_VOICE_HEAD: + CG_DrawSelectedPlayerHead( &rect, ownerDrawFlags & CG_SHOW_2DONLY, qtrue ); + break; + case CG_VOICE_NAME: + CG_DrawSelectedPlayerName( &rect, scale, color, qtrue, textStyle ); + break; + case CG_SELECTEDPLAYER_STATUS: + CG_DrawSelectedPlayerStatus( &rect ); + break; + case CG_SELECTEDPLAYER_ARMOR: + CG_DrawSelectedPlayerArmor( &rect, scale, color, shader, textStyle ); + break; + case CG_SELECTEDPLAYER_HEALTH: + CG_DrawSelectedPlayerHealth( &rect, scale, color, shader, textStyle ); + break; + case CG_SELECTEDPLAYER_NAME: + CG_DrawSelectedPlayerName( &rect, scale, color, qfalse, textStyle ); + break; + case CG_SELECTEDPLAYER_LOCATION: + CG_DrawSelectedPlayerLocation( &rect, scale, color, textStyle ); + break; + case CG_SELECTEDPLAYER_WEAPON: + CG_DrawSelectedPlayerWeapon( &rect ); + break; + case CG_SELECTEDPLAYER_POWERUP: + CG_DrawSelectedPlayerPowerup( &rect, ownerDrawFlags & CG_SHOW_2DONLY ); + break; + case CG_PLAYER_WEAPON_HEAT: + CG_DrawWeapHeat( &rect, align ); + break; + case CG_PLAYER_WEAPON_STABILITY: + CG_DrawWeapStability( &rect, color, align ); + break; + case CG_STAMINA: + CG_DrawFatigue( &rect, color, align ); + break; + // DHM - Nerve + case CG_PLAYER_WEAPON_RECHARGE: + CG_DrawWeapRecharge( &rect, color, align ); + break; + // dhm - end + case CG_PLAYER_HEAD: + CG_DrawPlayerHead( &rect, ownerDrawFlags & CG_SHOW_2DONLY ); + break; + case CG_PLAYER_HOLDABLE: + CG_DrawHoldableItem( &rect, scale, ownerDrawFlags & CG_SHOW_2DONLY ); + break; + case CG_PLAYER_ITEM: + CG_DrawPlayerItem( &rect, scale, ownerDrawFlags & CG_SHOW_2DONLY ); + break; + case CG_PLAYER_SCORE: + CG_DrawPlayerScore( &rect, scale, color, shader, textStyle ); + break; + case CG_PLAYER_HEALTH: + CG_DrawPlayerHealth( &rect, scale, color, shader, textStyle ); + break; + case CG_RED_SCORE: + CG_DrawRedScore( &rect, scale, color, shader, textStyle ); + break; + case CG_BLUE_SCORE: + CG_DrawBlueScore( &rect, scale, color, shader, textStyle ); + break; + case CG_RED_NAME: + CG_DrawRedName( &rect, scale, color, textStyle ); + break; + case CG_BLUE_NAME: + CG_DrawBlueName( &rect, scale, color, textStyle ); + break; + case CG_BLUE_FLAGHEAD: + CG_DrawBlueFlagHead( &rect ); + break; + case CG_BLUE_FLAGSTATUS: + CG_DrawBlueFlagStatus( &rect, shader ); + break; + case CG_BLUE_FLAGNAME: + CG_DrawBlueFlagName( &rect, scale, color, textStyle ); + break; + case CG_RED_FLAGHEAD: + CG_DrawRedFlagHead( &rect ); + break; + case CG_RED_FLAGSTATUS: + CG_DrawRedFlagStatus( &rect, shader ); + break; + case CG_RED_FLAGNAME: + CG_DrawRedFlagName( &rect, scale, color, textStyle ); + break; + case CG_HARVESTER_SKULLS: + CG_HarvesterSkulls( &rect, scale, color, qfalse, textStyle ); + break; + case CG_HARVESTER_SKULLS2D: + CG_HarvesterSkulls( &rect, scale, color, qtrue, textStyle ); + break; + case CG_ONEFLAG_STATUS: + CG_OneFlagStatus( &rect ); + break; + case CG_PLAYER_LOCATION: + CG_DrawPlayerLocation( &rect, scale, color, textStyle ); + break; + case CG_TEAM_COLOR: + CG_DrawTeamColor( &rect, color ); + break; + case CG_CTF_POWERUP: + CG_DrawCTFPowerUp( &rect ); + break; + case CG_AREA_WEAPON: + CG_DrawAreaWeapons( &rect, align, special, scale, color ); + break; + case CG_AREA_HOLDABLE: + CG_DrawAreaHoldable( &rect, align, special, scale, color ); + break; + case CG_AREA_POWERUP: + CG_DrawAreaPowerUp( &rect, align, special, scale, color ); + break; + case CG_PLAYER_STATUS: + CG_DrawPlayerStatus( &rect ); + break; + case CG_PLAYER_HASFLAG: + CG_DrawPlayerHasFlag( &rect, qfalse ); + break; + case CG_PLAYER_HASFLAG2D: + CG_DrawPlayerHasFlag( &rect, qtrue ); + break; + case CG_AREA_SYSTEMCHAT: + CG_DrawAreaSystemChat( &rect, scale, color, shader ); + break; + case CG_AREA_TEAMCHAT: + CG_DrawAreaTeamChat( &rect, scale, color, shader ); + break; + case CG_AREA_CHAT: + CG_DrawAreaChat( &rect, scale, color, shader ); + break; + case CG_GAME_TYPE: + CG_DrawGameType( &rect, scale, color, shader, textStyle ); + break; + case CG_GAME_STATUS: + CG_DrawGameStatus( &rect, scale, color, shader, textStyle ); + break; + case CG_KILLER: + CG_DrawKiller( &rect, scale, color, shader, textStyle ); + break; + case CG_ACCURACY: + case CG_ASSISTS: + case CG_DEFEND: + case CG_EXCELLENT: + case CG_IMPRESSIVE: + case CG_PERFECT: + case CG_GAUNTLET: + case CG_CAPTURES: + CG_DrawMedal( ownerDraw, &rect, scale, color, shader ); + break; + case CG_SPECTATORS: + CG_DrawTeamSpectators( &rect, scale, color, shader ); + break; + case CG_TEAMINFO: + if ( cg_currentSelectedPlayer.integer == numSortedTeamPlayers ) { + CG_DrawNewTeamInfo( &rect, text_x, text_y, scale, color, shader ); + } + break; + case CG_CAPFRAGLIMIT: + CG_DrawCapFragLimit( &rect, scale, color, shader, textStyle ); + break; + case CG_1STPLACE: + CG_Draw1stPlace( &rect, scale, color, shader, textStyle ); + break; + case CG_2NDPLACE: + CG_Draw2ndPlace( &rect, scale, color, shader, textStyle ); + break; + default: + break; + } +} + +void CG_MouseEvent( int x, int y ) { + int n; + + if ( ( cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_SPECTATOR ) && cg.showScores == qfalse ) { + trap_Key_SetCatcher( 0 ); + return; + } + + cgs.cursorX += x; + if ( cgs.cursorX < 0 ) { + cgs.cursorX = 0; + } else if ( cgs.cursorX > 640 ) { + cgs.cursorX = 640; + } + + cgs.cursorY += y; + if ( cgs.cursorY < 0 ) { + cgs.cursorY = 0; + } else if ( cgs.cursorY > 480 ) { + cgs.cursorY = 480; + } + + n = Display_CursorType( cgs.cursorX, cgs.cursorY ); + cgs.activeCursor = 0; + if ( n == CURSOR_ARROW ) { + cgs.activeCursor = cgs.media.selectCursor; + } else if ( n == CURSOR_SIZER ) { + cgs.activeCursor = cgs.media.sizeCursor; + } + + if ( cgs.capturedItem ) { + Display_MouseMove( cgs.capturedItem, x, y ); + } else { + Display_MouseMove( NULL, cgs.cursorX, cgs.cursorY ); + } + +} + +/* +================== +CG_HideTeamMenus +================== + +*/ +void CG_HideTeamMenu() { + Menus_CloseByName( "teamMenu" ); + Menus_CloseByName( "getMenu" ); +} + +/* +================== +CG_ShowTeamMenus +================== + +*/ +void CG_ShowTeamMenu() { + Menus_OpenByName( "teamMenu" ); +} + + + + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +void CG_EventHandling( int type ) { + cgs.eventHandling = type; + if ( type == CGAME_EVENT_NONE ) { + CG_HideTeamMenu(); + } else if ( type == CGAME_EVENT_TEAMMENU ) { + //CG_ShowTeamMenu(); + } else if ( type == CGAME_EVENT_SCOREBOARD ) { + } + +} + +void CG_KeyEvent( int key, qboolean down ) { + + if ( !down ) { + return; + } + + if ( cg.predictedPlayerState.pm_type == PM_NORMAL || ( cg.predictedPlayerState.pm_type == PM_SPECTATOR && cg.showScores == qfalse ) ) { + CG_EventHandling( CGAME_EVENT_NONE ); + trap_Key_SetCatcher( 0 ); + return; + } + + //if (key == trap_Key_GetKey("teamMenu") || !Display_CaptureItem(cgs.cursorX, cgs.cursorY)) { + // if we see this then we should always be visible + // CG_EventHandling(CGAME_EVENT_NONE); + // trap_Key_SetCatcher(0); + //} + + Display_HandleKey( key, down, cgs.cursorX, cgs.cursorY ); + + if ( cgs.capturedItem ) { + cgs.capturedItem = NULL; + } else { + if ( key == K_MOUSE2 && down ) { + cgs.capturedItem = Display_CaptureItem( cgs.cursorX, cgs.cursorY ); + } + } +} + +// prevent centerview exploits +qboolean CG_CheckCenterView() { + if ( cg.pmext.blockCenterViewTime && cg.time < cg.pmext.blockCenterViewTime ) { + return qfalse; + } + return qtrue; +} + +int CG_ClientNumFromName( const char *p ) { + int i; + for ( i = 0; i < cgs.maxclients; i++ ) { + if ( cgs.clientinfo[i].infoValid && Q_stricmp( cgs.clientinfo[i].name, p ) == 0 ) { + return i; + } + } + return -1; +} + +void CG_ShowResponseHead() { + Menus_OpenByName( "voiceMenu" ); + trap_Cvar_Set( "cl_conXOffset", "72" ); + cg.voiceTime = cg.time; +} + +void CG_RunMenuScript( char **args ) { +} + + +void CG_GetTeamColor( vec4_t *color ) { + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + ( *color )[0] = 1; + ( *color )[3] = .25f; + ( *color )[1] = ( *color )[2] = 0; + } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + ( *color )[0] = ( *color )[1] = 0; + ( *color )[2] = 1; + ( *color )[3] = .25f; + } else { + ( *color )[0] = ( *color )[2] = 0; + ( *color )[1] = .17f; + ( *color )[3] = .25f; + } +} + diff --git a/src/cgame/cg_particles.c b/src/cgame/cg_particles.c new file mode 100644 index 0000000..0c5d7eb --- /dev/null +++ b/src/cgame/cg_particles.c @@ -0,0 +1,2512 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// Rafael particles +// cg_particles.c + +#include "cg_local.h" + +#define MUSTARD 1 +#define BLOODRED 2 +#define EMISIVEFADE 3 +#define GREY75 4 +#define ZOMBIE 5 + +typedef struct particle_s +{ + struct particle_s *next; + + float time; + float endtime; + + vec3_t org; + vec3_t vel; + vec3_t accel; + int color; + float colorvel; + float alpha; + float alphavel; + int type; + qhandle_t pshader; + + float height; + float width; + + float endheight; + float endwidth; + + float start; + float end; + + float startfade; + qboolean rotate; + int snum; + + qboolean link; + + // Ridah + int shaderAnim; + int roll; + + int accumroll; + +} cparticle_t; + +typedef enum +{ + P_NONE, + P_WEATHER, + P_FLAT, + P_SMOKE, + P_ROTATE, + P_WEATHER_TURBULENT, + P_ANIM, // Ridah + P_BAT, + P_BLEED, + P_FLAT_SCALEUP, + P_FLAT_SCALEUP_FADE, + P_WEATHER_FLURRY, + P_SMOKE_IMPACT, + P_BUBBLE, + P_BUBBLE_TURBULENT, + P_SPRITE +} particle_type_t; + +#define MAX_SHADER_ANIMS 8 +#define MAX_SHADER_ANIM_FRAMES 64 +static char *shaderAnimNames[MAX_SHADER_ANIMS] = { + "explode1", + "blacksmokeanim", + "twiltb2", +// "expblue", +// "blacksmokeanimb", // uses 'explode1' sequence // JPW NERVE pulled + "blood", + NULL +}; +static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; +static int shaderAnimCounts[MAX_SHADER_ANIMS] = { + 23, + 23, // (SA) removing warning messages from startup + 45, + 25, + 23, + 5, +}; +static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { + 1, // NERVE - SMF - changed from 1.405 to 1 + 1, + 1, + 1, + 1, + 1, +}; +static int numShaderAnims; +// done. + +#define PARTICLE_GRAVITY 40 +#define MAX_PARTICLES 1024 * 8 + +cparticle_t *active_particles, *free_particles; +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + +qboolean initparticles = qfalse; +vec3_t vforward, vright, vup; +vec3_t rforward, rright, rup; + +float oldtime; + +/* +=============== +CL_ClearParticles +=============== +*/ +void CG_ClearParticles( void ) { + int i; + + memset( particles, 0, sizeof( particles ) ); + + free_particles = &particles[0]; + active_particles = NULL; + + for ( i = 0 ; i < cl_numparticles ; i++ ) + { + particles[i].next = &particles[i + 1]; + particles[i].type = 0; + } + particles[cl_numparticles - 1].next = NULL; + + oldtime = cg.time; + + // Ridah, init the shaderAnims + for ( i = 0; shaderAnimNames[i]; i++ ) { + int j; + + for ( j = 0; j < shaderAnimCounts[i]; j++ ) { + shaderAnims[i][j] = trap_R_RegisterShader( va( "%s%i", shaderAnimNames[i], j + 1 ) ); + } + } + numShaderAnims = i; + // done. + + initparticles = qtrue; +} + + +/* +===================== +CG_AddParticleToScene +===================== +*/ +void CG_AddParticleToScene( cparticle_t *p, vec3_t org, float alpha ) { + + vec3_t point; + polyVert_t verts[4]; + float width; + float height; + float time, time2; + float ratio; + float invratio; + vec3_t color; + polyVert_t TRIverts[3]; + vec3_t rright2, rup2; + + if ( p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT ) { // create a front facing polygon + + if ( p->type != P_WEATHER_FLURRY ) { + if ( p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT ) { + if ( org[2] > p->end ) { + p->time = cg.time; + VectorCopy( org, p->org ); // Ridah, fixes rare snow flakes that flicker on the ground + + p->org[2] = ( p->start + crandom() * 4 ); + + + if ( p->type == P_BUBBLE_TURBULENT ) { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + } + } else + { + if ( org[2] < p->end ) { + p->time = cg.time; + VectorCopy( org, p->org ); // Ridah, fixes rare snow flakes that flicker on the ground + + while ( p->org[2] < p->end ) + { + p->org[2] += ( p->start - p->end ); + } + + + if ( p->type == P_WEATHER_TURBULENT ) { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + } + } + + + // Rafael snow pvs check + if ( !p->link ) { + return; + } + + p->alpha = 1; + } + + // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp + if ( Distance( cg.snap->ps.origin, org ) > 1024 ) { + return; + } + // done. + + if ( p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT ) { + VectorMA( org, -p->height, vup, point ); + VectorMA( point, -p->width, vright, point ); + VectorCopy( point, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255 * p->alpha; + + VectorMA( org, -p->height, vup, point ); + VectorMA( point, p->width, vright, point ); + VectorCopy( point, verts[1].xyz ); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255 * p->alpha; + + VectorMA( org, p->height, vup, point ); + VectorMA( point, p->width, vright, point ); + VectorCopy( point, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255 * p->alpha; + + VectorMA( org, p->height, vup, point ); + VectorMA( point, -p->width, vright, point ); + VectorCopy( point, verts[3].xyz ); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255 * p->alpha; + } else + { + VectorMA( org, -p->height, vup, point ); + VectorMA( point, -p->width, vright, point ); + VectorCopy( point, TRIverts[0].xyz ); + TRIverts[0].st[0] = 1; + TRIverts[0].st[1] = 0; + TRIverts[0].modulate[0] = 255; + TRIverts[0].modulate[1] = 255; + TRIverts[0].modulate[2] = 255; + TRIverts[0].modulate[3] = 255 * p->alpha; + + VectorMA( org, p->height, vup, point ); + VectorMA( point, -p->width, vright, point ); + VectorCopy( point, TRIverts[1].xyz ); + TRIverts[1].st[0] = 0; + TRIverts[1].st[1] = 0; + TRIverts[1].modulate[0] = 255; + TRIverts[1].modulate[1] = 255; + TRIverts[1].modulate[2] = 255; + TRIverts[1].modulate[3] = 255 * p->alpha; + + VectorMA( org, p->height, vup, point ); + VectorMA( point, p->width, vright, point ); + VectorCopy( point, TRIverts[2].xyz ); + TRIverts[2].st[0] = 0; + TRIverts[2].st[1] = 1; + TRIverts[2].modulate[0] = 255; + TRIverts[2].modulate[1] = 255; + TRIverts[2].modulate[2] = 255; + TRIverts[2].modulate[3] = 255 * p->alpha; + } + + } else if ( p->type == P_SPRITE ) { + vec3_t rr, ru; + vec3_t rotate_ang; + + VectorSet( color, 1.0, 1.0, 1.0 ); + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width ) ); + height = p->height + ( ratio * ( p->endheight - p->height ) ); + + if ( p->roll ) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors( rotate_ang, NULL, rr, ru ); + } + + if ( p->roll ) { + VectorMA( org, -height, ru, point ); + VectorMA( point, -width, rr, point ); + } else { + VectorMA( org, -height, vup, point ); + VectorMA( point, -width, vright, point ); + } + VectorCopy( point, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if ( p->roll ) { + VectorMA( point, 2 * height, ru, point ); + } else { + VectorMA( point, 2 * height, vup, point ); + } + VectorCopy( point, verts[1].xyz ); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if ( p->roll ) { + VectorMA( point, 2 * width, rr, point ); + } else { + VectorMA( point, 2 * width, vright, point ); + } + VectorCopy( point, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if ( p->roll ) { + VectorMA( point, -2 * height, ru, point ); + } else { + VectorMA( point, -2 * height, vup, point ); + } + VectorCopy( point, verts[3].xyz ); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } else if ( p->type == P_SMOKE || p->type == P_SMOKE_IMPACT ) { // create a front rotating facing polygon + + if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024 ) { + return; + } + + if ( p->color == MUSTARD ) { + VectorSet( color, 0.42, 0.33, 0.19 ); + } else if ( p->color == BLOODRED ) { + VectorSet( color, 0.22, 0, 0 ); + } else if ( p->color == ZOMBIE ) { + VectorSet( color, 0.4, 0.28, 0.23 ); + } else if ( p->color == GREY75 ) { + float len; + float greyit; + float val; + len = Distance( cg.snap->ps.origin, org ); + if ( !len ) { + len = 1; + } + + val = 4096 / len; + greyit = 0.25 * val; + if ( greyit > 0.5 ) { + greyit = 0.5; + } + + VectorSet( color, greyit, greyit, greyit ); + } else { + VectorSet( color, 1.0, 1.0, 1.0 ); + } + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if ( cg.time > p->startfade ) { + invratio = 1 - ( ( cg.time - p->startfade ) / ( p->endtime - p->startfade ) ); + + if ( p->color == EMISIVEFADE ) { + float fval; + fval = ( invratio * invratio ); + if ( fval < 0 ) { + fval = 0; + } + VectorSet( color, fval, fval, fval ); + } + invratio *= p->alpha; + } else { + invratio = 1 * p->alpha; + } + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + invratio = 1; + } + + if ( invratio > 1 ) { + invratio = 1; + } + + width = p->width + ( ratio * ( p->endwidth - p->width ) ); + height = p->height + ( ratio * ( p->endheight - p->height ) ); + +// if (p->type != P_SMOKE_IMPACT) + { + vec3_t temp; + + vectoangles( rforward, temp ); + p->accumroll += p->roll; + temp[ROLL] += p->accumroll * 0.1; +// temp[ROLL] += p->roll * 0.1; + AngleVectors( temp, NULL, rright2, rup2 ); + } +// else +// { +// VectorCopy (rright, rright2); +// VectorCopy (rup, rup2); +// } + + if ( p->rotate ) { + VectorMA( org, -height, rup2, point ); + VectorMA( point, -width, rright2, point ); + } else + { + VectorMA( org, -p->height, vup, point ); + VectorMA( point, -p->width, vright, point ); + } + VectorCopy( point, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255 * invratio; + + if ( p->rotate ) { + VectorMA( org, -height, rup2, point ); + VectorMA( point, width, rright2, point ); + } else + { + VectorMA( org, -p->height, vup, point ); + VectorMA( point, p->width, vright, point ); + } + VectorCopy( point, verts[1].xyz ); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255 * invratio; + + if ( p->rotate ) { + VectorMA( org, height, rup2, point ); + VectorMA( point, width, rright2, point ); + } else + { + VectorMA( org, p->height, vup, point ); + VectorMA( point, p->width, vright, point ); + } + VectorCopy( point, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255 * invratio; + + if ( p->rotate ) { + VectorMA( org, height, rup2, point ); + VectorMA( point, -width, rright2, point ); + } else + { + VectorMA( org, p->height, vup, point ); + VectorMA( point, -p->width, vright, point ); + } + VectorCopy( point, verts[3].xyz ); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255 * invratio; + + } else if ( p->type == P_BAT ) { + p->pshader = cgs.media.bats[( cg.time / 50 + (int)( p - particles ) ) % 10]; + + VectorMA( org, -p->height, vup, point ); + VectorMA( point, -p->width, vright, point ); + VectorCopy( point, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorMA( org, -p->height, vup, point ); + VectorMA( point, p->width, vright, point ); + VectorCopy( point, verts[1].xyz ); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorMA( org, p->height, vup, point ); + VectorMA( point, p->width, vright, point ); + VectorCopy( point, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorMA( org, p->height, vup, point ); + VectorMA( point, -p->width, vright, point ); + VectorCopy( point, verts[3].xyz ); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } else if ( p->type == P_BLEED ) { + vec3_t rr, ru; + vec3_t rotate_ang; + float alpha; + + alpha = p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + alpha = 1; + } + + if ( p->roll ) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors( rotate_ang, NULL, rr, ru ); + } else + { + VectorCopy( vup, ru ); + VectorCopy( vright, rr ); + } + + VectorMA( org, -p->height, ru, point ); + VectorMA( point, -p->width, rr, point ); + VectorCopy( point, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 111; + verts[0].modulate[1] = 19; + verts[0].modulate[2] = 9; + verts[0].modulate[3] = 255 * alpha; + + VectorMA( org, -p->height, ru, point ); + VectorMA( point, p->width, rr, point ); + VectorCopy( point, verts[1].xyz ); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 111; + verts[1].modulate[1] = 19; + verts[1].modulate[2] = 9; + verts[1].modulate[3] = 255 * alpha; + + VectorMA( org, p->height, ru, point ); + VectorMA( point, p->width, rr, point ); + VectorCopy( point, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 111; + verts[2].modulate[1] = 19; + verts[2].modulate[2] = 9; + verts[2].modulate[3] = 255 * alpha; + + VectorMA( org, p->height, ru, point ); + VectorMA( point, -p->width, rr, point ); + VectorCopy( point, verts[3].xyz ); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 111; + verts[3].modulate[1] = 19; + verts[3].modulate[2] = 9; + verts[3].modulate[3] = 255 * alpha; + + } else if ( p->type == P_FLAT_SCALEUP ) { + float width, height; + float sinR, cosR; + + if ( p->color == BLOODRED ) { + VectorSet( color, 1, 1, 1 ); + } else { + VectorSet( color, 0.5, 0.5, 0.5 ); + } + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width ) ); + height = p->height + ( ratio * ( p->endheight - p->height ) ); + + if ( width > p->endwidth ) { + width = p->endwidth; + } + + if ( height > p->endheight ) { + height = p->endheight; + } + + sinR = height * sin( DEG2RAD( p->roll ) ) * sqrt( 2 ); + cosR = width * cos( DEG2RAD( p->roll ) ) * sqrt( 2 ); + + VectorCopy( org, verts[0].xyz ); + verts[0].xyz[0] -= sinR; + verts[0].xyz[1] -= cosR; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255; + + VectorCopy( org, verts[1].xyz ); + verts[1].xyz[0] -= cosR; + verts[1].xyz[1] += sinR; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255; + + VectorCopy( org, verts[2].xyz ); + verts[2].xyz[0] += sinR; + verts[2].xyz[1] += cosR; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255; + + VectorCopy( org, verts[3].xyz ); + verts[3].xyz[0] += cosR; + verts[3].xyz[1] -= sinR; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255; + } else if ( p->type == P_FLAT ) { + + VectorCopy( org, verts[0].xyz ); + verts[0].xyz[0] -= p->height; + verts[0].xyz[1] -= p->width; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy( org, verts[1].xyz ); + verts[1].xyz[0] -= p->height; + verts[1].xyz[1] += p->width; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy( org, verts[2].xyz ); + verts[2].xyz[0] += p->height; + verts[2].xyz[1] += p->width; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy( org, verts[3].xyz ); + verts[3].xyz[0] += p->height; + verts[3].xyz[1] -= p->width; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } + // Ridah + else if ( p->type == P_ANIM ) { + vec3_t rr, ru; + vec3_t rotate_ang; + int i, j; + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + if ( ratio >= 1.0 ) { + ratio = 0.9999; + } + + width = p->width + ( ratio * ( p->endwidth - p->width ) ); + height = p->height + ( ratio * ( p->endheight - p->height ) ); + + // if we are "inside" this sprite, don't draw + if ( Distance( cg.snap->ps.origin, org ) < width / 1.5 ) { + return; + } + + i = p->shaderAnim; + j = (int)floor( ratio * shaderAnimCounts[p->shaderAnim] ); + p->pshader = shaderAnims[i][j]; + +// JPW NERVE more particle testing + if ( cg_fxflags & 1 ) { + p->roll = 0; + p->pshader = getTestShader(); + rotate_ang[ROLL] = 90; + } +// jpw + if ( p->roll ) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors( rotate_ang, NULL, rr, ru ); + } + + if ( p->roll ) { + VectorMA( org, -height, ru, point ); + VectorMA( point, -width, rr, point ); + } else { + VectorMA( org, -height, vup, point ); + VectorMA( point, -width, vright, point ); + } + VectorCopy( point, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if ( p->roll ) { + VectorMA( point, 2 * height, ru, point ); + } else { + VectorMA( point, 2 * height, vup, point ); + } + VectorCopy( point, verts[1].xyz ); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if ( p->roll ) { + VectorMA( point, 2 * width, rr, point ); + } else { + VectorMA( point, 2 * width, vright, point ); + } + VectorCopy( point, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if ( p->roll ) { + VectorMA( point, -2 * height, ru, point ); + } else { + VectorMA( point, -2 * height, vup, point ); + } + VectorCopy( point, verts[3].xyz ); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + // done. + + if ( !cg_wolfparticles.integer ) { + return; + } + + if ( !p->pshader ) { +// (SA) temp commented out for DM again. FIXME: TODO: this needs to be addressed +// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); + return; + } + + if ( p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY ) { + trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + } else { + trap_R_AddPolyToScene( p->pshader, 4, verts ); + } + +} + +// Ridah, made this static so it doesn't interfere with other files +static float roll = 0.0; + +/* +=============== +CG_AddParticles +=============== +*/ +void CG_AddParticles( void ) { + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + int type; + vec3_t rotate_ang; + + if ( !initparticles ) { + CG_ClearParticles(); + } + + VectorCopy( cg.refdef.viewaxis[0], vforward ); + VectorCopy( cg.refdef.viewaxis[1], vright ); + VectorCopy( cg.refdef.viewaxis[2], vup ); + + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + roll += ( ( cg.time - oldtime ) * 0.1 ) ; + rotate_ang[ROLL] += ( roll * 0.9 ); + AngleVectors( rotate_ang, rforward, rright, rup ); + + oldtime = cg.time; + + active = NULL; + tail = NULL; + + for ( p = active_particles ; p ; p = next ) + { + + next = p->next; + + time = ( cg.time - p->time ) * 0.001; + + alpha = p->alpha + time * p->alphavel; + if ( alpha <= 0 ) { // faded out + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + if ( p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT ) { + if ( cg.time > p->endtime ) { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + + } + + if ( p->type == P_WEATHER_FLURRY ) { + if ( cg.time > p->endtime ) { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + } + + + if ( p->type == P_FLAT_SCALEUP_FADE ) { + if ( cg.time > p->endtime ) { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + } + + if ( ( p->type == P_BAT || p->type == P_SPRITE ) && p->endtime < 0 ) { + // temporary sprite + CG_AddParticleToScene( p, p->org, alpha ); + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + p->next = NULL; + if ( !tail ) { + active = tail = p; + } else + { + tail->next = p; + tail = p; + } + + if ( alpha > 1.0 ) { + alpha = 1; + } + + color = p->color; + + time2 = time * time; + + org[0] = p->org[0] + p->vel[0] * time + p->accel[0] * time2; + org[1] = p->org[1] + p->vel[1] * time + p->accel[1] * time2; + org[2] = p->org[2] + p->vel[2] * time + p->accel[2] * time2; + + type = p->type; + + CG_AddParticleToScene( p, org, alpha ); + } + + active_particles = active; +} + +/* +====================== +CG_AddParticles +====================== +*/ +void CG_ParticleSnowFlurry( qhandle_t pshader, centity_t *cent ) { + cparticle_t *p; + qboolean turb = qtrue; + + if ( !pshader ) { + CG_Printf( "CG_ParticleSnowFlurry pshader == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.90; + p->alphavel = 0; + + p->start = cent->currentState.origin2[0]; + p->end = cent->currentState.origin2[1]; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->pshader = pshader; + + if ( rand() % 100 > 90 ) { + p->height = 32; + p->width = 32; + p->alpha = 0.10; + } else + { + p->height = 1; + p->width = 1; + } + + p->vel[2] = -20; + + p->type = P_WEATHER_FLURRY; + + if ( turb ) { + p->vel[2] = -10; + } + + VectorCopy( cent->currentState.origin, p->org ); + + p->org[0] = p->org[0]; + p->org[1] = p->org[1]; + p->org[2] = p->org[2]; + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += cent->currentState.angles[0] * 32 + ( crandom() * 16 ); + p->vel[1] += cent->currentState.angles[1] * 32 + ( crandom() * 16 ); + p->vel[2] += cent->currentState.angles[2]; + + if ( turb ) { + p->accel[0] = crandom() * 16; + p->accel[1] = crandom() * 16; + } + +} + +void CG_ParticleSnow( qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum ) { + cparticle_t *p; + + if ( !pshader ) { + CG_Printf( "CG_ParticleSnow pshader == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + p->height = 1; + p->width = 1; + + p->vel[2] = -50; + + if ( turb ) { + p->type = P_WEATHER_TURBULENT; + p->vel[2] = -50 * 1.3; + } else + { + p->type = P_WEATHER; + } + + VectorCopy( origin, p->org ); + + p->org[0] = p->org[0] + ( crandom() * range ); + p->org[1] = p->org[1] + ( crandom() * range ); + p->org[2] = p->org[2] + ( crandom() * ( p->start - p->end ) ); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if ( turb ) { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleBubble( qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum ) { + cparticle_t *p; + float randsize; + + if ( !pshader ) { + CG_Printf( "CG_ParticleSnow pshader == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + + randsize = 1 + ( crandom() * 0.5 ); + + p->height = randsize; + p->width = randsize; + + p->vel[2] = 50 + ( crandom() * 10 ); + + if ( turb ) { + p->type = P_BUBBLE_TURBULENT; + p->vel[2] = 50 * 1.3; + } else + { + p->type = P_BUBBLE; + } + + VectorCopy( origin, p->org ); + + p->org[0] = p->org[0] + ( crandom() * range ); + p->org[1] = p->org[1] + ( crandom() * range ); + p->org[2] = p->org[2] + ( crandom() * ( p->start - p->end ) ); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if ( turb ) { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleSmoke( qhandle_t pshader, centity_t *cent ) { + + // using cent->density = enttime + // cent->frame = startfade + cparticle_t *p; + vec3_t dir; + + if ( !pshader ) { + CG_Printf( "CG_ParticleSmoke == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + if ( cent->currentState.density == 1 ) { + p->rotate = qfalse; + p->height = 8; + p->width = 8; + p->endheight = 32; + p->endwidth = 32; + } else if ( cent->currentState.density == 2 ) { + p->rotate = qtrue; + p->height = 4; + p->width = 4; + p->endheight = 8; + p->endwidth = 8; + } else if ( cent->currentState.density == 3 ) { + p->rotate = qfalse; + { + float scale; + + scale = 16 + ( crandom() * 8 ); + p->height = 24 + scale; + p->width = 24 + scale; + p->endheight = 64 + scale; + p->endwidth = 64 + scale; + } + } else if ( cent->currentState.density == 4 ) { // white smoke + p->rotate = qtrue; + p->height = cent->currentState.angles2[0]; + p->width = cent->currentState.angles2[0]; + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + p->color = GREY75; + } else if ( cent->currentState.density == 5 ) { // mustard gas + p->rotate = qtrue; + p->height = cent->currentState.angles2[0]; + p->width = cent->currentState.angles2[0]; + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + p->color = MUSTARD; + p->alpha = 0.75; + } else // black smoke + { + p->rotate = qtrue; + p->height = cent->currentState.angles2[0]; + p->width = cent->currentState.angles2[0]; + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + + { + int rval; + rval = rand() % 6; + if ( rval == 1 ) { + p->pshader = cgs.media.smokePuffShaderb1; + } else if ( rval == 2 ) { + p->pshader = cgs.media.smokePuffShaderb2; + } else if ( rval == 3 ) { + p->pshader = cgs.media.smokePuffShaderb3; + } else if ( rval == 4 ) { + p->pshader = cgs.media.smokePuffShaderb4; + } else { + p->pshader = cgs.media.smokePuffShaderb5; + } + } + } + + + p->type = P_SMOKE; + + VectorCopy( cent->currentState.origin, p->org ); + + p->vel[0] = p->vel[1] = 0; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if ( cent->currentState.density == 1 ) { + p->vel[2] = 5; + } else if ( cent->currentState.density == 2 ) { + p->vel[2] = 5; + } else if ( cent->currentState.density == 3 ) { // cannon + VectorCopy( cent->currentState.origin2, dir ); + p->vel[0] = dir[0] * 128 + ( crandom() * 64 ); + p->vel[1] = dir[1] * 128 + ( crandom() * 64 ); + p->vel[2] = 15 + ( crandom() * 16 ); + } else if ( cent->currentState.density == 5 ) { // gas or cover smoke + VectorCopy( cent->currentState.origin2, dir ); + p->vel[0] = dir[0] * 32 + ( crandom() * 16 ); + p->vel[1] = dir[1] * 32 + ( crandom() * 16 ); + p->vel[2] = 4 + ( crandom() * 2 ); + } else // smoke + { + VectorCopy( cent->currentState.origin2, dir ); + p->vel[0] = dir[0] + ( crandom() * p->height ); + p->vel[1] = dir[1] + ( crandom() * p->height ); + p->vel[2] = cent->currentState.angles2[2]; + } + + if ( cent->currentState.frame == 1 ) { // reverse gravity + p->vel[2] *= -1; + } + + p->roll = 8 + ( crandom() * 4 ); +} + + +void CG_ParticleBulletDebris( vec3_t org, vec3_t vel, int duration ) { + + cparticle_t *p; + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration / 2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy( org, p->org ); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -60; + p->vel[2] += -20; + +} + +// DHM - Nerve :: bullets hitting dirt + +void CG_ParticleDirtBulletDebris( vec3_t org, vec3_t vel, int duration ) { + int r = rand() % 3; + cparticle_t *p; + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration / 2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 1.2; + p->width = 1.2; + p->endheight = 4.5; + p->endwidth = 4.5; + + if ( r == 0 ) { + p->pshader = cgs.media.dirtParticle1Shader; + } else if ( r == 1 ) { + p->pshader = cgs.media.dirtParticle2Shader; + } else { + p->pshader = cgs.media.dirtParticle3Shader; + } + + p->type = P_SMOKE; + + VectorCopy( org, p->org ); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -330; + p->vel[2] += -20; +} + +// NERVE - SMF :: the core of the dirt explosion +void CG_ParticleDirtBulletDebris_Core( vec3_t org, vec3_t vel, int duration, + float width, float height, float alpha, char *shadername ) { // JPW NERVE +// int r = rand(); // TTimo: unused + cparticle_t *p; + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration / 2; + + p->color = EMISIVEFADE; + p->alpha = alpha; + p->alphavel = 0; + + p->height = width; // JPW NERVE was 512/5.f; + p->width = height; // JPW NERVE was 128/5.f; + p->endheight = p->height; + p->endwidth = p->width; + + p->rotate = 0; + + p->type = P_SMOKE; + +// JPW NERVE + p->pshader = trap_R_RegisterShader( shadername ); // JPW NERVE was "dirt_splash" + if ( cg_fxflags & 1 ) { + p->pshader = getTestShader(); + p->rotate = 0; + p->roll = 0; + p->type = P_SPRITE; + } +// jpw + + + VectorCopy( org, p->org ); + VectorCopy( vel, p->vel ); + +// p->vel[0] = vel[0]; +// p->vel[1] = vel[1]; +// p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -330; +// p->vel[2] += -20; +} + +// DHM - Nerve :: end + +/* +====================== +CG_ParticleExplosion +====================== +*/ + +void CG_ParticleExplosion( char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd ) { + cparticle_t *p; + int anim; + + if ( animStr < (char *)10 ) { + CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + } + + // find the animation string + for ( anim = 0; shaderAnimNames[anim]; anim++ ) { + if ( !Q_strcasecmp( animStr, shaderAnimNames[anim] ) ) { + break; + } + } + if ( !shaderAnimNames[anim] ) { + CG_Error( "CG_ParticleExplosion: unknown animation string: %s\n", animStr ); + return; + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + + if ( duration < 0 ) { + duration *= -1; + p->roll = 0; + } else { + p->roll = crandom() * 179; + } + + p->shaderAnim = anim; + + p->width = sizeStart; + p->height = sizeStart * shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + + p->endheight = sizeEnd; + p->endwidth = sizeEnd * shaderAnimSTRatio[anim]; + + p->endtime = cg.time + duration; + + p->type = P_ANIM; + + VectorCopy( origin, p->org ); + VectorCopy( vel, p->vel ); + VectorClear( p->accel ); + +} + +// Rafael Shrapnel +void CG_AddParticleShrapnel( localEntity_t *le ) { + return; +} +// done. + +int CG_NewParticleArea( int num ) { + // const char *str; + char *str; + char *token; + int type; + vec3_t origin, origin2; + int i; + float range = 0; + int turb; + int numparticles; + int snum; + + str = (char *) CG_ConfigString( num ); + if ( !str[0] ) { + return ( 0 ); + } + + // returns type 128 64 or 32 + token = COM_Parse( &str ); + type = atoi( token ); + + if ( type == 1 ) { + range = 128; + } else if ( type == 2 ) { + range = 64; + } else if ( type == 3 ) { + range = 32; + } else if ( type == 0 ) { + range = 256; + } else if ( type == 4 ) { + range = 8; + } else if ( type == 5 ) { + range = 16; + } else if ( type == 6 ) { + range = 32; + } else if ( type == 7 ) { + range = 64; + } + + + for ( i = 0; i < 3; i++ ) + { + token = COM_Parse( &str ); + origin[i] = atof( token ); + } + + for ( i = 0; i < 3; i++ ) + { + token = COM_Parse( &str ); + origin2[i] = atof( token ); + } + + token = COM_Parse( &str ); + numparticles = atoi( token ); + + token = COM_Parse( &str ); + turb = atoi( token ); + + token = COM_Parse( &str ); + snum = atoi( token ); + + for ( i = 0; i < numparticles; i++ ) + { + if ( type >= 4 ) { + CG_ParticleBubble( cgs.media.waterBubbleShader, origin, origin2, turb, range, snum ); + } else { + CG_ParticleSnow( cgs.media.snowShader, origin, origin2, turb, range, snum ); + } + } + + return ( 1 ); +} + +void CG_SnowLink( centity_t *cent, qboolean particleOn ) { + cparticle_t *p, *next; + int id; + + id = cent->currentState.frame; + + for ( p = active_particles ; p ; p = next ) + { + next = p->next; + + if ( p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT ) { + if ( p->snum == id ) { + if ( particleOn ) { + p->link = qtrue; + } else { + p->link = qfalse; + } + } + } + + } +} + +void CG_ParticleBat( centity_t *cent ) { + cparticle_t *p; + vec3_t origin; + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->height = 4; + p->width = 4; + + VectorCopy( cent->lerpOrigin, origin ); + VectorCopy( origin, p->org ); + VectorClear( p->vel ); + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->snum = cent->currentState.frame; + + p->type = P_BAT; + p->endtime = -1; // last one frame only +} + + +void CG_ParticleBats( qhandle_t pshader, centity_t *cent ) { + cparticle_t *p; + vec3_t origin; + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40; + p->alphavel = 0; + p->pshader = pshader; + p->height = 4; + p->width = 4; + + VectorCopy( cent->currentState.origin, origin ); + VectorCopy( origin, p->org ); + + p->org[0] = p->org[0] + ( crandom() * 32 ); + p->org[1] = p->org[1] + ( crandom() * 32 ); + p->org[2] = p->org[2] + ( crandom() * 32 ); + + p->vel[0] = cent->currentState.angles[0] * cent->currentState.time; + p->vel[1] = cent->currentState.angles[1] * cent->currentState.time; + p->vel[2] = cent->currentState.angles[2] * cent->currentState.time; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->snum = cent->currentState.frame; + + p->type = P_BAT; +} + +void CG_BatsUpdatePosition( centity_t *cent ) { + cparticle_t *p, *next; + int id; + float time; + + id = cent->currentState.frame; + + for ( p = active_particles ; p ; p = next ) + { + next = p->next; + + if ( p->type == P_BAT ) { + if ( p->snum == id ) { + time = ( cg.time - p->time ) * 0.001; + + p->org[0] = p->org[0] + p->vel[0] * time; + p->org[1] = p->org[1] + p->vel[1] * time; + p->org[2] = p->org[2] + p->vel[2] * time; + + p->time = cg.time; + + p->vel[0] = cent->currentState.angles[0] * cent->currentState.time; + p->vel[1] = cent->currentState.angles[1] * cent->currentState.time; + p->vel[2] = cent->currentState.angles[2] * cent->currentState.time; + + } + } + + } +} + + +void CG_ParticleImpactSmokePuffExtended( qhandle_t pshader, vec3_t origin, int lifetime, int vel, int acc, int maxroll, float alpha ) { + cparticle_t *p; + + if ( !pshader ) { + CG_Printf( "CG_ParticleImpactSmokePuff pshader == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = alpha; + p->alphavel = 0; + + // (SA) roll either direction + p->roll = rand() % ( 2 * maxroll ); +// p->roll = crandom()*(float)(maxroll*2); + p->roll -= maxroll; + + p->pshader = pshader; + + p->endtime = cg.time + lifetime; + p->startfade = cg.time + 100; + + p->width = rand() % 4 + 8; + p->height = rand() % 4 + 8; + + p->endheight = p->height * 2; + p->endwidth = p->width * 2; + + p->type = P_SMOKE_IMPACT; + + VectorCopy( origin, p->org ); + VectorSet( p->vel, 0, 0, vel ); + VectorSet( p->accel, 0, 0, acc ); + + p->rotate = qtrue; +} + +void CG_ParticleImpactSmokePuff( qhandle_t pshader, vec3_t origin ) { + CG_ParticleImpactSmokePuffExtended( pshader, origin, 500, 20, 20, 30, 0.25f ); +} + + +void CG_Particle_Bleed( qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration ) { + cparticle_t *p; + + if ( !pshader ) { + CG_Printf( "CG_Particle_Bleed pshader == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + if ( fleshEntityNum ) { + p->startfade = cg.time; + } else { + p->startfade = cg.time + 100; + } + + p->width = 4; + p->height = 4; + + p->endheight = 4 + rand() % 3; + p->endwidth = p->endheight; + + p->type = P_SMOKE; + + VectorCopy( start, p->org ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -20; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand() % 179; + + if ( fleshEntityNum ) { + p->color = MUSTARD; + } else { + p->color = BLOODRED; + } + p->alpha = 0.75; + +} + +//void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +void CG_Particle_OilParticle( qhandle_t pshader, vec3_t origin, vec3_t dir, int ptime, int snum ) { // snum is parent ent number? + cparticle_t *p; + + int time; + int time2; + float ratio; + +// float duration = 1500; + float duration = 2000; + + time = cg.time; + time2 = cg.time + ptime; + + ratio = (float)1 - ( (float)time / (float)time2 ); + + if ( !pshader ) { + CG_Printf( "CG_Particle_OilParticle == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + p->startfade = p->endtime; + + p->width = 2; + p->height = 2; + + p->endwidth = 1; + p->endheight = 1; + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = ( dir[0] * ( 16 * ratio ) ); + p->vel[1] = ( dir[1] * ( 16 * ratio ) ); + p->vel[2] = ( dir[2] * ( 16 * ratio ) ); +// p->vel[2] = (dir[2]); + + p->snum = snum; + + VectorClear( p->accel ); + + p->accel[2] = -20; + + p->rotate = qfalse; + + p->roll = rand() % 179; + + p->alpha = 0.5; + + p->color = BLOODRED; + +} + + +void CG_Particle_OilSlick( qhandle_t pshader, centity_t *cent ) { + cparticle_t *p; + + if ( !pshader ) { + CG_Printf( "CG_Particle_OilSlick == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + if ( cent->currentState.angles2[2] ) { + p->endtime = cg.time + cent->currentState.angles2[2]; + } else { + p->endtime = cg.time + 60000; + } + + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + if ( cent->currentState.angles2[0] || cent->currentState.angles2[1] ) { + p->width = cent->currentState.angles2[0]; + p->height = cent->currentState.angles2[0]; + + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + } else + { + p->width = 8; + p->height = 8; + + p->endheight = 16; + p->endwidth = 16; + } + + p->type = P_FLAT_SCALEUP; + + p->snum = cent->currentState.density; + + VectorCopy( cent->currentState.origin, p->org ); + + p->org[2] += 0.55 + ( crandom() * 0.5 ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand() % 179; + + p->alpha = 0.75; + +} + +void CG_OilSlickRemove( centity_t *cent ) { + cparticle_t *p, *next; + int id; + + id = cent->currentState.density; + + if ( !id ) { + CG_Printf( "CG_OilSlickRevove NULL id\n" ); + } + + for ( p = active_particles ; p ; p = next ) + { + next = p->next; + + if ( p->type == P_FLAT_SCALEUP ) { + if ( p->snum == id ) { + p->endtime = cg.time + 100; + p->startfade = p->endtime; + p->type = P_FLAT_SCALEUP_FADE; + + } + } + + } +} + +qboolean ValidBloodPool( vec3_t start ) { +#define EXTRUDE_DIST 0.5 + + vec3_t angles; + vec3_t right, up; + vec3_t this_pos, x_pos, center_pos, end_pos; + float x, y; + float fwidth, fheight; + trace_t trace; + vec3_t normal; + + fwidth = 16; + fheight = 16; + + VectorSet( normal, 0, 0, 1 ); + + vectoangles( normal, angles ); + AngleVectors( angles, NULL, right, up ); + + VectorMA( start, EXTRUDE_DIST, normal, center_pos ); + + for ( x = -fwidth / 2; x < fwidth; x += fwidth ) + { + VectorMA( center_pos, x, right, x_pos ); + + for ( y = -fheight / 2; y < fheight; y += fheight ) + { + VectorMA( x_pos, y, up, this_pos ); + VectorMA( this_pos, -EXTRUDE_DIST * 2, normal, end_pos ); + + CG_Trace( &trace, this_pos, NULL, NULL, end_pos, -1, CONTENTS_SOLID ); + + + if ( trace.entityNum < ( MAX_ENTITIES - 1 ) ) { // may only land on world + return qfalse; + } + + if ( !( !trace.startsolid && trace.fraction < 1 ) ) { + return qfalse; + } + + } + } + + return qtrue; +} + +void CG_BloodPool( localEntity_t *le, qhandle_t pshader, trace_t *tr ) { + cparticle_t *p; + qboolean legit; + vec3_t start; + float rndSize; + + if ( !pshader ) { + CG_Printf( "CG_BloodPool pshader == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + + VectorCopy( tr->endpos, start ); + legit = ValidBloodPool( start ); + + if ( !legit ) { + return; + } + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + 3000; + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + rndSize = 0.4 + random() * 0.6; + + p->width = 8 * rndSize; + p->height = 8 * rndSize; + + p->endheight = 16 * rndSize; + p->endwidth = 16 * rndSize; + + p->type = P_FLAT_SCALEUP; + + VectorCopy( start, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand() % 179; + + p->alpha = 0.75; + + p->color = BLOODRED; +} + +#define NORMALSIZE 16 +#define LARGESIZE 32 + +void CG_ParticleBloodCloud( centity_t *cent, vec3_t origin, vec3_t dir ) { + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength( dir ); + vectoangles( dir, angles ); + AngleVectors( angles, forward, NULL, NULL ); + + if ( cent->currentState.density == 0 ) { // normal ai size + crittersize = NORMALSIZE; + } else { + crittersize = LARGESIZE; + } + + if ( length ) { + dist = length / crittersize; + } + + if ( dist < 1 ) { + dist = 1; + } + + VectorCopy( origin, point ); + + for ( i = 0; i < dist; i++ ) + { + VectorMA( point, crittersize, forward, point ); + + if ( !free_particles ) { + return; + } + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + p->endtime = cg.time + 350 + ( crandom() * 100 ); + + p->startfade = cg.time; + + if ( cent->currentState.density == 0 ) { // normal ai size + p->width = NORMALSIZE; + p->height = NORMALSIZE; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } else // large frame + { + p->width = LARGESIZE; + p->height = LARGESIZE; + + p->endheight = LARGESIZE; + p->endwidth = LARGESIZE; + } + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -1; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand() % 179; + + if ( cent->currentState.aiChar == AICHAR_ZOMBIE ) { + p->color = MUSTARD; + } else { + p->color = BLOODRED; + } + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleBloodCloudZombie( centity_t *cent, vec3_t origin, vec3_t dir ) { + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength( dir ); + vectoangles( dir, angles ); + AngleVectors( angles, forward, NULL, NULL ); + + if ( cent->currentState.density == 0 ) { // normal ai size + crittersize = NORMALSIZE / 4; + } else { + crittersize = LARGESIZE / 3; + } + + if ( length ) { + dist = length / crittersize; + } + + if ( dist < 1 ) { + dist = 1; + } + + VectorCopy( origin, point ); + + for ( i = 0; i < dist; i++ ) + { + VectorMA( point, crittersize, forward, point ); + + if ( !free_particles ) { + return; + } + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 0.2; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.bloodCloudShader; + + // RF, stay around for long enough to expand and dissipate naturally + if ( length ) { + p->endtime = cg.time + 3500 + ( crandom() * 2000 ); + } else { + p->endtime = cg.time + 750 + ( crandom() * 500 ); + } + + p->startfade = cg.time; + + if ( cent->currentState.density == 0 ) { // normal ai size + p->width = NORMALSIZE; + p->height = NORMALSIZE; + + // RF, expand while falling + p->endheight = NORMALSIZE * 4.0; + p->endwidth = NORMALSIZE * 4.0; + } else // large frame + { + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE * 3.0; + p->endwidth = LARGESIZE * 3.0; + } + + if ( !length ) { + p->width *= 0.2; + p->height *= 0.2; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = crandom() * 6; + p->vel[1] = crandom() * 6; + p->vel[2] = random() * 6; + + // RF, add some gravity/randomness + p->accel[0] = crandom() * 3; + p->accel[1] = crandom() * 3; + p->accel[2] = -PARTICLE_GRAVITY * 0.2; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand() % 179; + + p->color = ZOMBIE; + + } + + +} + +void CG_ParticleSparks( vec3_t org, vec3_t vel, int duration, float x, float y, float speed ) { + cparticle_t *p; + + if ( !free_particles ) { + return; + } + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration / 2; + + p->color = EMISIVEFADE; + p->alpha = 0.4; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy( org, p->org ); + + p->org[0] += ( crandom() * x ); + p->org[1] += ( crandom() * y ); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += ( crandom() * 4 ); + p->vel[1] += ( crandom() * 4 ); + p->vel[2] += ( 20 + ( crandom() * 10 ) ) * speed; + + p->accel[0] = crandom() * 4; + p->accel[1] = crandom() * 4; + +} + +void CG_ParticleDust( centity_t *cent, vec3_t origin, vec3_t dir ) { + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + VectorNegate( dir, dir ); + length = VectorLength( dir ); + vectoangles( dir, angles ); + AngleVectors( angles, forward, NULL, NULL ); + + if ( cent->currentState.density == 0 ) { // normal ai size + crittersize = NORMALSIZE; + } else { + crittersize = LARGESIZE; + } + + if ( length ) { + dist = length / crittersize; + } + + if ( dist < 1 ) { + dist = 1; + } + + VectorCopy( origin, point ); + + for ( i = 0; i < dist; i++ ) + { + VectorMA( point, crittersize, forward, point ); + + if ( !free_particles ) { + return; + } + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 5.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.bloodCloudShader; + + // RF, stay around for long enough to expand and dissipate naturally + if ( length ) { + p->endtime = cg.time + 4500 + ( crandom() * 3500 ); + } else { + p->endtime = cg.time + 750 + ( crandom() * 500 ); + } + + p->startfade = cg.time; + + if ( cent->currentState.density == 0 ) { // normal ai size + p->width = NORMALSIZE; + p->height = NORMALSIZE; + + // RF, expand while falling + p->endheight = NORMALSIZE * 4.0; + p->endwidth = NORMALSIZE * 4.0; + } else // large frame + { + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE * 3.0; + p->endwidth = LARGESIZE * 3.0; + } + + if ( !length ) { + p->width *= 0.2; + p->height *= 0.2; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( point, p->org ); + + p->vel[0] = crandom() * 6; + p->vel[1] = crandom() * 6; + p->vel[2] = random() * 20; + + // RF, add some gravity/randomness + p->accel[0] = crandom() * 3; + p->accel[1] = crandom() * 3; + p->accel[2] = -PARTICLE_GRAVITY * 0.4; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand() % 179; + + if ( cent->currentState.density ) { + p->color = GREY75; + } else { + p->color = MUSTARD; + } + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleMisc( qhandle_t pshader, vec3_t origin, int size, int duration, float alpha ) { + cparticle_t *p; + + if ( !pshader ) { + CG_Printf( "CG_ParticleImpactSmokePuff pshader == ZERO!\n" ); + } + + if ( !free_particles ) { + return; + } + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = rand() % 179; + + p->pshader = pshader; + + if ( duration > 0 ) { + p->endtime = cg.time + duration; + } else { + p->endtime = duration; + } + + p->startfade = cg.time; + + p->width = size; + p->height = size; + + p->endheight = size; + p->endwidth = size; + + p->type = P_SPRITE; + + VectorCopy( origin, p->org ); + + p->rotate = qfalse; +} diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c new file mode 100644 index 0000000..a00d01b --- /dev/null +++ b/src/cgame/cg_players.c @@ -0,0 +1,3107 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: cg_players.c + * + * desc: handle the media and animation for player entities + * +*/ + +static char text[100000]; + +#include "cg_local.h" + +#define SWING_RIGHT 1 +#define SWING_LEFT 2 + +char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { + "*death1.wav", + "*death2.wav", + "*death3.wav", + "*jump1.wav", + "*pain25_1.wav", + "*pain50_1.wav", + "*pain75_1.wav", + "*pain100_1.wav", + "*falling1.wav", + "*gasp.wav", + "*drown.wav", + "*fall1.wav", + "*taunt.wav", + "*exert1.wav", + "*exert2.wav", + "*exert3.wav", +}; + + +/* +================ +CG_EntOnFire +================ +*/ +qboolean CG_EntOnFire( centity_t *cent ) { + if ( cent->currentState.number == cg.snap->ps.clientNum ) { + return ( ( cg.snap->ps.onFireStart < cg.time ) && + ( ( cg.snap->ps.onFireStart + 2000 ) > cg.time ) ); + } else { + return ( ( cent->currentState.onFireStart < cg.time ) && + ( cent->currentState.onFireEnd > cg.time ) ); + } +} + +/* +================ +CG_IsCrouchingAnim +================ +*/ +qboolean CG_IsCrouchingAnim( clientInfo_t *ci, int animNum ) { + animation_t *anim; + + // FIXME: make compatible with new scripting + animNum &= ~ANIM_TOGGLEBIT; + // + anim = BG_GetAnimationForIndex( ci->clientNum, animNum ); + // + if ( anim->movetype & ( ( 1 << ANIM_MT_IDLECR ) | ( 1 << ANIM_MT_WALKCR ) | ( 1 << ANIM_MT_WALKCRBK ) ) ) { + return qtrue; + } + // + return qfalse; +} + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { + clientInfo_t *ci; + int i; + + if ( soundName[0] != '*' ) { + return trap_S_RegisterSound( soundName ); + } + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { + if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { + return ci->sounds[i]; + } + } + + CG_Error( "Unknown custom sound: %s", soundName ); + return 0; +} + + + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ + +/* +====================== +CG_ParseGibModels + +Read a configuration file containing gib models for use with this character +====================== +*/ +static qboolean CG_ParseGibModels( const char *filename, clientInfo_t *ci ) { + char *text_p; + int len; + int i; + char *token; + fileHandle_t f; + + memset( ci->gibModels, 0, sizeof( ci->gibModels ) ); + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + for ( i = 0; i < MAX_GIB_MODELS; i++ ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + // cache this model + ci->gibModels[i] = trap_R_RegisterModel( token ); + } + + return qtrue; +} + + +/* +================== +CG_CalcMoveSpeeds +================== +*/ +void CG_CalcMoveSpeeds( clientInfo_t *ci ) { + char *tags[2] = {"tag_footleft", "tag_footright"}; + vec3_t oldPos[2]; + refEntity_t refent; + animation_t *anim; + int i, j, k; + float totalSpeed; + int numSpeed; + int lastLow, low; + orientation_t o[2]; + + refent.hModel = ci->legsModel; + + for ( i = 0, anim = ci->modelInfo->animations; i < ci->modelInfo->numAnimations; i++, anim++ ) { + + if ( anim->moveSpeed >= 0 ) { + continue; + } + + totalSpeed = 0; + lastLow = -1; + numSpeed = 0; + + // for each frame + for ( j = 0; j < anim->numFrames; j++ ) { + + refent.frame = anim->firstFrame + j; + refent.oldframe = refent.frame; + + // for each foot + for ( k = 0; k < 2; k++ ) { + if ( trap_R_LerpTag( &o[k], &refent, tags[k], 0 ) < 0 ) { + CG_Error( "CG_CalcMoveSpeeds: unable to find tag %s, cannot calculate movespeed", tags[k] ); + } + } + + // find the contact foot + if ( anim->flags & ANIMFL_LADDERANIM ) { + if ( o[0].origin[0] > o[1].origin[0] ) { + low = 0; + } else { + low = 1; + } + totalSpeed += fabs( oldPos[low][2] - o[low].origin[2] ); + } else { + if ( o[0].origin[2] < o[1].origin[2] ) { + low = 0; + } else { + low = 1; + } + totalSpeed += fabs( oldPos[low][0] - o[low].origin[0] ); + } + + numSpeed++; + + // save the positions + for ( k = 0; k < 2; k++ ) { + VectorCopy( o[k].origin, oldPos[k] ); + } + lastLow = low; + } + + // record the speed + anim->moveSpeed = (int)( ( totalSpeed / numSpeed ) * 1000.0 / anim->frameLerp ); + } + + if ( cgs.localServer ) { + CG_SendMoveSpeed( ci->modelInfo->animations, ci->modelInfo->numAnimations, ci->modelInfo->modelname ); + } +} + +/* +====================== +CG_ParseAnimationFiles + + Read in all the configuration and script files for this model. +====================== +*/ +static qboolean CG_ParseAnimationFiles( const char *modelname, clientInfo_t *ci, int client ) { + char filename[MAX_QPATH]; + fileHandle_t f; + int len; + + // set the name of the model in the modelinfo structure + Q_strncpyz( ci->modelInfo->modelname, modelname, sizeof( ci->modelInfo->modelname ) ); + + // load the cfg file + Com_sprintf( filename, sizeof( filename ), "models/players/%s/wolfanim.cfg", modelname ); + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + BG_AnimParseAnimConfig( ci->modelInfo, filename, text ); + + if ( ci->isSkeletal != ci->modelInfo->isSkeletal ) { + CG_Error( "Mis-match in %s, loaded skeletal model, but file does not specify SKELETAL\n", filename ); + } + + // calc movespeed values if required + CG_CalcMoveSpeeds( ci ); + + // load the script file + Com_sprintf( filename, sizeof( filename ), "models/players/%s/wolfanim.script", modelname ); + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + if ( ci->modelInfo->version > 1 ) { + return qfalse; + } + // try loading the default script for old legacy models + Com_sprintf( filename, sizeof( filename ), "models/players/default.script", modelname ); + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + } + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + BG_AnimParseAnimScript( ci->modelInfo, &cgs.animScriptData, ci->clientNum, filename, text ); + return qtrue; +} + +/* +========================== +CG_RegisterClientSkin +========================== +*/ + +//----(SA) modified this for head separation + +qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *modelName, const char *skinName ) { + char filename[MAX_QPATH]; + + // RF, try and register the new "body_*.skin" file for skeletal animation + Com_sprintf( filename, sizeof( filename ), "models/players/%s/body_%s.skin", modelName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + if ( ci->legsSkin ) { // skeletal model + ci->torsoSkin = ci->legsSkin; + return qtrue; + } + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + + if ( !ci->legsSkin || !ci->torsoSkin ) { + return qfalse; + } + + return qtrue; +} + +/* +============== +CG_RegisterClientHeadSkin +============== +*/ +static qboolean CG_RegisterClientHeadSkin( clientInfo_t *ci, const char *modelName, const char *hSkinName ) { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head_%s.skin", modelName, hSkinName ); + ci->headSkin = trap_R_RegisterSkin( filename ); + + if ( !ci->headSkin ) { + return qfalse; + } + + return qtrue; + +} + +//----(SA) end + + + +//----(SA) added +/* +============== +CG_RegisterAcc +============== +*/ +static qboolean CG_RegisterAcc( clientInfo_t *ci, const char *modelName, const char *skinName, int *model, int *skin ) { + char namefromskin[MAX_QPATH]; + char filename[MAX_QPATH]; + + if ( !model || !skin ) { + return qfalse; + } + + // FIXME: have the check the last 4 chars rather than strstr() + if ( !strstr( skinName, ".md3" ) ) { // try to find a skin in the acc folder that matches + *skin = trap_R_RegisterSkin( va( "%s/%s.skin", modelName, skinName ) ); + + if ( *skin ) { + if ( trap_R_GetSkinModel( *skin, "md3_part", &namefromskin[0] ) ) { + Com_sprintf( filename, sizeof( filename ), "%s/acc/%s", modelName, namefromskin ); + // NOTE: FIXME: this will currently only work with accessories in the /acc directory. + // It will have to strip the directory off the end and then use the remaining + // path in order to work for arbitrary sub-directories. +// Com_sprintf( filename, sizeof( filename ), "%s/%s/%s.md3", modelName, , namefromskin ); + + } else { + Com_sprintf( filename, sizeof( filename ), "%s/%s.md3", modelName, skinName ); + } + } else { + Com_sprintf( filename, sizeof( filename ), "%s/%s.md3", modelName, skinName ); + } + } else { // the skin wants a straight model + Com_sprintf( filename, sizeof( filename ), "%s/%s", modelName, skinName ); + } + + + *model = trap_R_RegisterModel( filename ); + + if ( *model ) { + return qtrue; + } + + return qfalse; +} + +//----(SA) end + + +/* +================== +CG_CheckForExistingModelInfo + + If this player model has already been parsed, then use the existing information. + Otherwise, set the modelInfo pointer to the first free slot. + + returns qtrue if existing model found, qfalse otherwise +================== +*/ +qboolean CG_CheckForExistingModelInfo( clientInfo_t *ci, char *modelName, animModelInfo_t **modelInfo ) { + int i; + animModelInfo_t *trav, *firstFree = NULL; + clientInfo_t *ci_trav; + char modelsUsed[MAX_ANIMSCRIPT_MODELS]; + + for ( i = 0, trav = cgs.animScriptData.modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, trav++ ) { + if ( trav->modelname[0] ) { + if ( !Q_stricmp( trav->modelname, modelName ) ) { + // found a match, use this modelinfo + *modelInfo = trav; + cgs.animScriptData.clientModels[ci->clientNum] = i + 1; + return qtrue; + } + } else if ( !firstFree ) { + firstFree = trav; + cgs.animScriptData.clientModels[ci->clientNum] = i + 1; + } + } + + // set the modelInfo to the first free slot + if ( !firstFree ) { + // attempt to free a model that is no longer being used + memset( modelsUsed, 0, sizeof( modelsUsed ) ); + for ( i = 0, ci_trav = cgs.clientinfo; i < MAX_CLIENTS; i++, ci_trav++ ) { + if ( ci_trav->infoValid && ci_trav != ci ) { + modelsUsed[ (int)( ci_trav->modelInfo - cgs.animScriptData.modelInfo ) ] = 1; + } + } + // now use the first slot that isn't being utilized + for ( i = 0, trav = cgs.animScriptData.modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, trav++ ) { + if ( !modelsUsed[i] ) { + firstFree = trav; + cgs.animScriptData.clientModels[ci->clientNum] = i + 1; + break; + } + } + } + + if ( !firstFree ) { + CG_Error( "unable to find a free modelinfo slot, cannot continue\n" ); + } else { + *modelInfo = firstFree; + // clear the structure out ready for use + memset( *modelInfo, 0, sizeof( *modelInfo ) ); + } + // qfalse signifies that we need to parse the information from the script files + return qfalse; +} + + +/* +========================== +CG_RegisterClientModelname +========================== +*/ + +//----(SA) modified this for head separation + +qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName ) { + char namefromskin[MAX_QPATH]; + char filename[MAX_QPATH]; + + // if any skins failed to load, return failure +//----(SA) modified this for head separation + if ( !CG_RegisterClientSkin( ci, modelName, skinName ) ) { + Com_Printf( "Failed to load skin file: %s/%s\n", modelName, skinName ); + return qfalse; + } + + // load cmodels before models so filecache works + + if ( trap_R_GetSkinModel( ci->legsSkin, "md3_part", &namefromskin[0] ) ) { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s", modelName, namefromskin ); + ci->legsModel = trap_R_RegisterModel( filename ); + } else { // try skeletal model + Com_sprintf( filename, sizeof( filename ), "models/players/%s/body.mds", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + + if ( !ci->legsModel ) { // revert to mesh animation + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + } else { // found skeletal model + ci->isSkeletal = qtrue; + ci->torsoModel = ci->legsModel; + } + } + + if ( !ci->isSkeletal ) { + + if ( !ci->legsModel ) { + Com_Printf( "Failed to load legs model file %s\n", filename ); + return qfalse; + } + + if ( trap_R_GetSkinModel( ci->torsoSkin, "md3_part", &namefromskin[0] ) ) { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s", modelName, namefromskin ); + } else { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); + } + + ci->torsoModel = trap_R_RegisterModel( filename ); + + if ( !ci->torsoModel ) { + Com_Printf( "Failed to load torso model file %s\n", filename ); + return qfalse; + } + + } + +//----(SA) testing + { + char scaleString[MAX_QPATH]; + char *string_p; + char *scaleToken; + qboolean badscale = qfalse; + + string_p = scaleString; + + if ( trap_R_GetSkinModel( ci->legsSkin, "playerscale", &scaleString[0] ) ) { + scaleToken = COM_Parse( &string_p ); + if ( !scaleToken ) { + badscale = qtrue; // and drop to "if(badscale)" below + } else + { + ci->playermodelScale[0] = atof( scaleToken ); + + scaleToken = COM_Parse( &string_p ); + if ( !scaleToken ) { + badscale = qtrue; // and drop to "if(badscale)" below + } else + { + ci->playermodelScale[1] = atof( scaleToken ); + + scaleToken = COM_Parse( &string_p ); + if ( !scaleToken ) { + badscale = qtrue; // and drop to "if(badscale)" below + } else + { + ci->playermodelScale[2] = atof( scaleToken ); + } + } + } + } + + if ( badscale ) { + ci->playermodelScale[0] = + ci->playermodelScale[1] = + ci->playermodelScale[2] = 0.0f; + } + } +//----(SA) end + + + // try all the accessories + if ( trap_R_GetSkinModel( ci->legsSkin, "md3_beltr", &namefromskin[0] ) ) { + CG_RegisterAcc( ci, va( "models/players/%s", modelName ), namefromskin, &ci->accModels[ACC_BELT_LEFT], &ci->accSkins[ACC_BELT_LEFT] ); + } + if ( trap_R_GetSkinModel( ci->legsSkin, "md3_beltl", &namefromskin[0] ) ) { + CG_RegisterAcc( ci, va( "models/players/%s", modelName ), namefromskin, &ci->accModels[ACC_BELT_RIGHT], &ci->accSkins[ACC_BELT_RIGHT] ); + } + if ( trap_R_GetSkinModel( ci->torsoSkin, "md3_belt", &namefromskin[0] ) ) { + CG_RegisterAcc( ci, va( "models/players/%s", modelName ), namefromskin, &ci->accModels[ACC_BELT], &ci->accSkins[ACC_BELT] ); + } + if ( trap_R_GetSkinModel( ci->torsoSkin, "md3_back", &namefromskin[0] ) ) { + CG_RegisterAcc( ci, va( "models/players/%s", modelName ), namefromskin, &ci->accModels[ACC_BACK], &ci->accSkins[ACC_BACK] ); + } +//----(SA) added + if ( trap_R_GetSkinModel( ci->torsoSkin, "md3_weapon", &namefromskin[0] ) ) { + CG_RegisterAcc( ci, va( "models/players/%s", modelName ), namefromskin, &ci->accModels[ACC_WEAPON], &ci->accSkins[ACC_WEAPON] ); + } + if ( trap_R_GetSkinModel( ci->torsoSkin, "md3_weapon2", &namefromskin[0] ) ) { + CG_RegisterAcc( ci, va( "models/players/%s", modelName ), namefromskin, &ci->accModels[ACC_WEAPON2], &ci->accSkins[ACC_WEAPON2] ); + } +//----(SA) end + + // look for this model in the list of models already opened + if ( !CG_CheckForExistingModelInfo( ci, (char *)modelName, &ci->modelInfo ) ) { + + if ( !CG_ParseAnimationFiles( modelName, ci, ci->clientNum ) ) { + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; + } + } + + return qtrue; +} + +/* +============== +CG_RegisterClientHeadname +============== +*/ +static qboolean CG_RegisterClientHeadname( clientInfo_t *ci, const char *modelName, const char *hSkinName ) { + char namefromskin[MAX_QPATH]; + char filename[MAX_QPATH]; + int i; + + if ( !CG_RegisterClientHeadSkin( ci, modelName, hSkinName ) ) { + Com_Printf( "Failed to load head skin file: %s/head_%s.skin\n", modelName, hSkinName ); //----(SA) + return qfalse; + } + + if ( trap_R_GetSkinModel( ci->headSkin, "md3_part", &namefromskin[0] ) ) { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s", modelName, namefromskin ); + } else { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", modelName ); + } + + ci->headModel = trap_R_RegisterModel( filename ); + if ( !ci->headModel ) { + Com_Printf( "Failed to load head model file %s\n", filename ); //----(SA) + return qfalse; + } + + if ( trap_R_GetSkinModel( ci->headSkin, "md3_hat", &namefromskin[0] ) ) { + CG_RegisterAcc( ci, va( "models/players/%s", modelName ), namefromskin, &ci->accModels[ACC_HAT], &ci->accSkins[ACC_HAT] ); + } + + for ( i = 0; i < ACC_NUM_MOUTH - 1; i++ ) { + if ( trap_R_GetSkinModel( ci->headSkin, va( "md3_hat%d", 2 + i ), &namefromskin[0] ) ) { + CG_RegisterAcc( ci, va( "models/players/%s", modelName ), namefromskin, &ci->accModels[ACC_MOUTH2 + i], &ci->accSkins[ACC_MOUTH2 + i] ); + } + } + + return qtrue; +} +/* +==================== +CG_ColorFromString +==================== +*/ +static void CG_ColorFromString( const char *v, vec3_t color ) { + int val; + + VectorClear( color ); + + val = atoi( v ); + + if ( val < 1 || val > 7 ) { + VectorSet( color, 1, 1, 1 ); + return; + } + + if ( val & 1 ) { + color[2] = 1.0f; + } + if ( val & 2 ) { + color[1] = 1.0f; + } + if ( val & 4 ) { + color[0] = 1.0f; + } +} + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits. +This will usually be deferred to a safe time +=================== +*/ +static void CG_LoadClientInfo( clientInfo_t *ci ) { + const char *dir, *fallback; + int i; + const char *s; + int clientNum; + int headfail = 0; + char filename[MAX_QPATH]; + +//----(SA) modified this for head separation + + // load the head first (since if the head loads but there is a problem with something in the lower + // body, you will want to default the model back to a default and want the head to match) + // + + if ( !CG_RegisterClientHeadname( ci, ci->modelName, ci->hSkinName ) ) { + if ( cg_buildScript.integer ) { + CG_Error( "CG_RegisterClientHeadname( %s, %s ) failed. setting default", ci->modelName, ci->hSkinName ); + } + + // fall back to default head + if ( !CG_RegisterClientHeadname( ci, ci->modelName, "default" ) ) { + headfail = 1; + if ( cg_buildScript.integer ) { + CG_Error( "head model/skin (%s/default) failed to register", ci->modelName ); //----(SA) + } + } + } + + if ( headfail || !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName ) ) { + if ( cg_buildScript.integer ) { + CG_Error( "CG_RegisterClientModelname( %s, %s ) failed", ci->modelName, ci->skinName ); + } + + // fall back + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name but set default model + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, ci->skinName ) ) { + CG_Error( "DEFAULT_MODEL / skin (%s/%s) failed to register", DEFAULT_MODEL, ci->skinName ); + } + } else if ( cgs.gametype == GT_SINGLE_PLAYER && !headfail ) { + // try to keep the model but default the skin (so you can tell bad guys from good) + if ( !CG_RegisterClientModelname( ci, ci->modelName, "default" ) ) { + CG_Error( "DEFAULT_MODEL (%s/default) failed to register", ci->modelName ); + } + } else { + // go totally default + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default" ) ) { + CG_Error( "DEFAULT_MODEL (%s/default) failed to register", DEFAULT_MODEL ); + } + + // fall back to default head + if ( !CG_RegisterClientHeadname( ci, DEFAULT_MODEL, "default" ) ) { + CG_Error( "model/ DEFAULT_HEAD / skin (%s/default) failed to register", DEFAULT_HEAD ); + } + + } + + } + +//----(SA) end + + // sounds + dir = ci->modelName; + fallback = DEFAULT_MODEL; + + // DHM - Nerve :: Not using in multiplayer, was causing hitches on player respawns/joins + if ( cgs.gametype < GT_WOLF ) { + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { + s = cg_customSoundNames[i]; + if ( !s ) { + break; + } + ci->sounds[i] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ) ); + if ( !ci->sounds[i] ) { + ci->sounds[i] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ) ); + } + } + } + // dhm + + // load the gibs + Com_sprintf( filename, sizeof( filename ), "models/players/%s/gibs.cfg", dir ); + if ( !CG_ParseGibModels( filename, ci ) ) { + // n/mind.. gib code will automatically fall back to old gibs + } + + ci->deferred = qfalse; + + // reset any existing players and bodies, because they might be in bad + // frames for this new model + clientNum = ci - cgs.clientinfo; + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { + if ( cg_entities[i].currentState.clientNum == clientNum + && cg_entities[i].currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( &cg_entities[i] ); + } + } +} + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { + VectorCopy( from->playermodelScale, to->playermodelScale ); + + to->legsModel = from->legsModel; + to->legsSkin = from->legsSkin; + to->torsoModel = from->torsoModel; + to->torsoSkin = from->torsoSkin; + to->headModel = from->headModel; + to->headSkin = from->headSkin; + to->isSkeletal = from->isSkeletal; + to->modelIcon = from->modelIcon; + +//----(SA) + memcpy( to->accModels, from->accModels, sizeof( to->accModels ) ); + memcpy( to->accSkins, from->accSkins, sizeof( to->accSkins ) ); + memcpy( to->partModels, from->partModels, sizeof( to->partModels ) ); +// memcpy( to->partSkins, from->partSkins, sizeof( to->partSkins ) ); +//----(SA) end + + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); + + // Ridah + memcpy( to->gibModels, from->gibModels, sizeof( to->gibModels ) ); + // done. + + // copy the modelInfo + to->modelInfo = from->modelInfo; + cgs.animScriptData.clientModels[ to->clientNum ] = cgs.animScriptData.clientModels[ from->clientNum ]; + +} + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + if ( match->deferred ) { + continue; + } +//----(SA) added checks for same head. FIXME: soon this will be more efficient than just looking for a complete character (body/head) match. + if ( + !Q_stricmp( ci->modelName, match->modelName ) + && !Q_stricmp( ci->skinName, match->skinName ) + && !Q_stricmp( ci->hSkinName, match->hSkinName ) + ) { + +//----(SA) done + + // this clientinfo is identical, so use it's handles + + ci->deferred = qfalse; + + CG_CopyClientInfoModel( match, ci ); + + return qtrue; + } + } + + // nothing matches, so defer the load + return qfalse; +} + +/* +====================== +CG_SetDeferredClientInfo + +We aren't going to load it now, so grab some other +client's info to use until we have some spare time. +====================== +*/ +static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + // if we are in teamplay, only grab a model if the skin is correct + if ( cgs.gametype >= GT_TEAM ) { + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) ) { + continue; + } + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // load the full model, because we don't ever want to show + // an improper team skin. This will cause a hitch for the first + // player, when the second enters. Combat shouldn't be going on + // yet, so it shouldn't matter + CG_LoadClientInfo( ci ); + return; + } + + // find the first valid clientinfo and grab its stuff + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // we should never get here... + CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); + + CG_LoadClientInfo( ci ); +} + + +/* +====================== +CG_NewClientInfo +====================== +*/ +void CG_NewClientInfo( int clientNum ) { + clientInfo_t *ci; + clientInfo_t newInfo; + const char *configstring; + const char *v; + char *slash; + + ci = &cgs.clientinfo[clientNum]; + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if ( !configstring[0] ) { + memset( ci, 0, sizeof( *ci ) ); + return; // player just left + } + + // build into a temp buffer so the defer checks can use + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + newInfo.clientNum = clientNum; + + // isolate the player's name + v = Info_ValueForKey( configstring, "n" ); + Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + + // colors + v = Info_ValueForKey( configstring, "c1" ); + CG_ColorFromString( v, newInfo.color ); + + // bot skill + v = Info_ValueForKey( configstring, "skill" ); + newInfo.botSkill = atoi( v ); + + // handicap + v = Info_ValueForKey( configstring, "hc" ); + newInfo.handicap = atoi( v ); + + // wins + v = Info_ValueForKey( configstring, "w" ); + newInfo.wins = atoi( v ); + + // losses + v = Info_ValueForKey( configstring, "l" ); + newInfo.losses = atoi( v ); + + // team + v = Info_ValueForKey( configstring, "t" ); + newInfo.team = atoi( v ); + +//----(SA) modified this for head separation + + // head + v = Info_ValueForKey( configstring, "head" ); + if ( cg_forceModel.integer ) { + char modelStr[MAX_QPATH]; + + // forcemodel makes everyone use a single model + // to prevent load hitches + + trap_Cvar_VariableStringBuffer( "head", modelStr, sizeof( modelStr ) ); + Q_strncpyz( newInfo.hSkinName, modelStr, sizeof( newInfo.hSkinName ) ); + } else { + Q_strncpyz( newInfo.hSkinName, v, sizeof( newInfo.hSkinName ) ); + } + +//----(SA) modified this for head separation + + // model + v = Info_ValueForKey( configstring, "model" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char modelStr[MAX_QPATH]; + char *skin; + + trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); + if ( ( skin = strchr( modelStr, '/' ) ) == NULL ) { + skin = "default"; + } else { + *skin++ = 0; + } + + Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); + Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + slash = strchr( v, '/' ); + if ( slash ) { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + } + } + } else { + Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + + slash = strchr( newInfo.modelName, '/' ); + if ( !slash ) { + // modelName did not include a skin name + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } else { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + // truncate modelName + *slash = 0; + } + } + + //----(SA) modify \/ to differentiate for head models/skins as well + + + // scan for an existing clientinfo that matches this modelname + // so we can avoid loading checks if possible + if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { + qboolean forceDefer; + + forceDefer = trap_MemoryRemaining() < 4000000; + + if ( cgs.gametype != GT_SINGLE_PLAYER && ( forceDefer || ( cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) ) { + // keep whatever they had if it won't violate team skins + if ( ci->infoValid && + ( cgs.gametype < GT_TEAM || !Q_stricmp( newInfo.skinName, ci->skinName ) ) ) { + CG_CopyClientInfoModel( ci, &newInfo ); + newInfo.deferred = qtrue; + } else { + // use whatever is available + CG_SetDeferredClientInfo( &newInfo ); + } + // if we are low on memory, leave them with this model + if ( forceDefer ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + newInfo.deferred = qfalse; + } + } else { + CG_LoadClientInfo( &newInfo ); + } + } + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + *ci = newInfo; +} + + + +/* +====================== +CG_LoadDeferredPlayers + +Called each frame when a player is dead +and the scoreboard is up +so deferred players can be loaded +====================== +*/ +void CG_LoadDeferredPlayers( void ) { + int i; + clientInfo_t *ci; + + // scan for a deferred player to load + for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { + if ( ci->infoValid && ci->deferred ) { + // if we are low on memory, leave it deferred + if ( trap_MemoryRemaining() < 4000000 ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + ci->deferred = qfalse; + continue; + } + CG_LoadClientInfo( ci ); + } + } +} + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + + +/* +=============== +CG_SetLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim; + + if ( !ci->modelInfo ) { + return; + } + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= ci->modelInfo->numAnimations ) { + CG_Error( "Bad animation number (CG_SLFA): %i", newAnimation ); + } + + anim = &ci->modelInfo->animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if ( cg_debugAnim.integer == 1 ) { // DHM - Nerve :: extra debug info + CG_Printf( "Anim: %i, %s\n", newAnimation, ci->modelInfo->animations[newAnimation].name ); + } +} + +/* +=============== +CG_RunLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { + int f; + animation_t *anim; + + // debugging tool to get no animations + if ( cg_animSpeed.integer == 0 ) { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return; + } + + // see if the animation sequence is switching + if ( ci && ( newAnimation != lf->animationNumber || !lf->animation ) ) { //----(SA) modified + CG_SetLerpFrameAnimation( ci, lf, newAnimation ); + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( cg.time >= lf->frameTime ) { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if ( !anim->frameLerp ) { + return; // shouldn't happen + } + if ( cg.time < lf->animationTime ) { + lf->frameTime = lf->animationTime; // initial lerp + } else { + lf->frameTime = lf->oldFrameTime + anim->frameLerp; + } + f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + f *= speedScale; // adjust for haste, etc + if ( f >= anim->numFrames ) { + f -= anim->numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = anim->numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = cg.time; + } + } + lf->frame = anim->firstFrame + f; + if ( cg.time > lf->frameTime ) { + lf->frameTime = cg.time; + if ( cg_debugAnim.integer ) { + CG_Printf( "Clamp lf->frameTime\n" ); + } + } + } + + if ( lf->frameTime > cg.time + 200 ) { + lf->frameTime = cg.time; + } + + if ( lf->oldFrameTime > cg.time ) { + lf->oldFrameTime = cg.time; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} + + +/* +=============== +CG_ClearLerpFrame +=============== +*/ +static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { + lf->frameTime = lf->oldFrameTime = cg.time; + CG_SetLerpFrameAnimation( ci, lf, animationNumber ); + if ( lf->animation ) { + lf->oldFrame = lf->frame = lf->animation->firstFrame; + } +} + +//------------------------------------------------------------------------------ +// Ridah, variable speed animations +/* +=============== +CG_SetLerpFrameAnimationRate + +may include ANIM_TOGGLEBIT +=============== +*/ +void CG_SetLerpFrameAnimationRate( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim, *oldanim; + int transitionMin = -1, oldAnimTime, oldAnimNum; + qboolean firstAnim = qfalse; + + if ( !ci->modelInfo ) { + return; + } + + oldAnimTime = lf->animationTime; + oldanim = lf->animation; + oldAnimNum = lf->animationNumber; + + if ( !lf->animation ) { + firstAnim = qtrue; + } + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= ci->modelInfo->numAnimations ) { + CG_Error( "Bad animation number (CG_SLFAR): %i", newAnimation ); + } + + anim = &ci->modelInfo->animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if ( !( anim->flags & ANIMFL_FIRINGANIM ) || ( lf != ¢->pe.torso ) ) { + if ( ( lf == ¢->pe.legs ) && ( CG_IsCrouchingAnim( ci, newAnimation ) != CG_IsCrouchingAnim( ci, oldAnimNum ) ) ) { + if ( anim->moveSpeed || ( anim->movetype & ( ( 1 << ANIM_MT_TURNLEFT ) | ( 1 << ANIM_MT_TURNRIGHT ) ) ) ) { // if unknown movetype, go there faster + transitionMin = lf->frameTime + 200; // slowly raise/drop + } else { + transitionMin = lf->frameTime + 350; // slowly raise/drop + } + } else if ( anim->moveSpeed ) { + transitionMin = lf->frameTime + 120; // always do some lerping (?) + } else { // not moving, so take your time + transitionMin = lf->frameTime + 170; // always do some lerping (?) + + } + if ( oldanim && oldanim->animBlend ) { //transitionMin < lf->frameTime + oldanim->animBlend) { + transitionMin = lf->frameTime + oldanim->animBlend; + lf->animationTime = transitionMin; + } else { + // slow down transitions according to speed + if ( anim->moveSpeed && lf->animSpeedScale < 1.0 ) { + lf->animationTime += anim->initialLerp; + } + + if ( lf->animationTime < transitionMin ) { + lf->animationTime = transitionMin; + } + } + } + + // if first anim, go immediately + if ( firstAnim ) { + lf->frameTime = cg.time - 1; + lf->animationTime = cg.time - 1; + lf->frame = anim->firstFrame; + } + + if ( cg_debugAnim.integer == 1 ) { // DHM - Nerve :: extra debug info + CG_Printf( "Anim: %i, %s\n", newAnimation, ci->modelInfo->animations[newAnimation].name ); + } +} + +/* +=============== +CG_RunLerpFrameRate + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +void CG_RunLerpFrameRate( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, centity_t *cent, int recursion ) { + int f; + animation_t *anim, *oldAnim; + animation_t *otherAnim = NULL; + qboolean isLadderAnim; + +#define ANIM_SCALEMAX_LOW 1.1 +#define ANIM_SCALEMAX_HIGH 1.6 + +#define ANIM_SPEEDMAX_LOW 100 +#define ANIM_SPEEDMAX_HIGH 20 + + // debugging tool to get no animations + if ( cg_animSpeed.integer == 0 ) { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return; + } + + isLadderAnim = lf->animation && ( lf->animation->flags & ANIMFL_LADDERANIM ); + + oldAnim = lf->animation; + + // see if the animation sequence is switching + if ( newAnimation != lf->animationNumber || !lf->animation ) { + CG_SetLerpFrameAnimationRate( cent, ci, lf, newAnimation ); + } + + // Ridah, make sure the animation speed is updated when possible + anim = lf->animation; + if ( anim->moveSpeed && lf->oldFrameSnapshotTime ) { + float moveSpeed; + + // calculate the speed at which we moved over the last frame + if ( cg.latestSnapshotTime != lf->oldFrameSnapshotTime && cg.nextSnap ) { + if ( cent->currentState.number == cg.snap->ps.clientNum ) { + if ( isLadderAnim ) { // only use Z axis for speed + if ( cent->currentState.aiChar != AICHAR_FEMZOMBIE ) { // femzombie has sideways climbing + lf->oldFramePos[0] = cent->lerpOrigin[0]; + lf->oldFramePos[1] = cent->lerpOrigin[1]; + } + } else { // only use x/y axis + lf->oldFramePos[2] = cent->lerpOrigin[2]; + } + moveSpeed = Distance( cent->lerpOrigin, lf->oldFramePos ) / ( (float)( cg.time - lf->oldFrameTime ) / 1000.0 ); + } else { + if ( isLadderAnim ) { // only use Z axis for speed + lf->oldFramePos[0] = cent->currentState.pos.trBase[0]; + lf->oldFramePos[1] = cent->currentState.pos.trBase[1]; + } + + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + moveSpeed = Distance( cent->currentState.pos.trBase, cent->nextState.pos.trBase ) / ( (float)( cg.nextSnap->serverTime - cg.snap->serverTime ) / 1000.0 ); + } else { + moveSpeed = Distance( cent->lerpOrigin, lf->oldFramePos ) / ( (float)( cg.time - lf->oldFrameTime ) / 1000.0 ); + } + } + // + // convert it to a factor of this animation's movespeed + lf->animSpeedScale = moveSpeed / (float)anim->moveSpeed; + lf->oldFrameSnapshotTime = cg.latestSnapshotTime; + } + } else { + // move at normal speed + lf->animSpeedScale = 1.0; + lf->oldFrameSnapshotTime = cg.latestSnapshotTime; + } + // adjust with manual setting (pain anims) + lf->animSpeedScale *= cent->pe.animSpeed; + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( cg.time >= lf->frameTime ) { + + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + VectorCopy( cent->lerpOrigin, lf->oldFramePos ); + + // restrict the speed range + if ( lf->animSpeedScale < 0.25 ) { // if it's too slow, then a really slow spped, combined with a sudden take-off, can leave them playing a really slow frame while they a moving really fast + if ( lf->animSpeedScale < 0.01 && isLadderAnim ) { + lf->animSpeedScale = 0.0; + } else { + lf->animSpeedScale = 0.25; + } + } else if ( lf->animSpeedScale > ANIM_SCALEMAX_LOW ) { + + if ( !( anim->flags & ANIMFL_LADDERANIM ) ) { + // allow slower anims to speed up more than faster anims + if ( anim->moveSpeed > ANIM_SPEEDMAX_LOW ) { + lf->animSpeedScale = ANIM_SCALEMAX_LOW; + } else if ( anim->moveSpeed < ANIM_SPEEDMAX_HIGH ) { + if ( lf->animSpeedScale > ANIM_SCALEMAX_HIGH ) { + lf->animSpeedScale = ANIM_SCALEMAX_HIGH; + } + } else { + lf->animSpeedScale = ANIM_SCALEMAX_HIGH - ( ANIM_SCALEMAX_HIGH - ANIM_SCALEMAX_LOW ) * (float)( anim->moveSpeed - ANIM_SPEEDMAX_HIGH ) / (float)( ANIM_SPEEDMAX_LOW - ANIM_SPEEDMAX_HIGH ); + } + } else if ( lf->animSpeedScale > 4.0 ) { + lf->animSpeedScale = 4.0; + } + + } + + if ( lf == ¢->pe.legs ) { + otherAnim = cent->pe.torso.animation; + } else if ( lf == ¢->pe.torso ) { + otherAnim = cent->pe.legs.animation; + } + + // get the next frame based on the animation + if ( !lf->animSpeedScale ) { + // stopped on the ladder, so stay on the same frame + f = lf->frame - anim->firstFrame; + lf->frameTime += anim->frameLerp; // don't wait too long before starting to move again + } else if ( lf->oldAnimationNumber != lf->animationNumber && + ( !anim->moveSpeed || lf->oldFrame < anim->firstFrame || lf->oldFrame >= anim->firstFrame + anim->numFrames ) ) { // Ridah, added this so walking frames don't always get reset to 0, which can happen in the middle of a walking anim, which looks wierd + lf->frameTime = lf->animationTime; // initial lerp + if ( oldAnim && anim->moveSpeed ) { // keep locomotions going continuously + f = ( lf->frame - oldAnim->firstFrame ) + 1; + while ( f < 0 ) { + f += anim->numFrames; + } + } else { + f = 0; + } + } else if ( ( lf == ¢->pe.legs ) && otherAnim && !( anim->flags & ANIMFL_FIRINGANIM ) && ( ( lf->animationNumber & ~ANIM_TOGGLEBIT ) == ( cent->pe.torso.animationNumber & ~ANIM_TOGGLEBIT ) ) && ( !anim->moveSpeed ) ) { + // legs should synch with torso + f = cent->pe.torso.frame - otherAnim->firstFrame; + if ( f >= anim->numFrames || f < 0 ) { + f = 0; // wait at the start for the legs to catch up (assuming they are still in an old anim) + } + lf->frameTime = cent->pe.torso.frameTime; + } else if ( ( lf == ¢->pe.torso ) && otherAnim && !( anim->flags & ANIMFL_FIRINGANIM ) && ( ( lf->animationNumber & ~ANIM_TOGGLEBIT ) == ( cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT ) ) && ( otherAnim->moveSpeed ) ) { + // torso needs to sync with legs + f = cent->pe.legs.frame - otherAnim->firstFrame; + if ( f >= anim->numFrames || f < 0 ) { + f = 0; // wait at the start for the legs to catch up (assuming they are still in an old anim) + } + lf->frameTime = cent->pe.legs.frameTime; + } else { + lf->frameTime = lf->oldFrameTime + (int)( (float)anim->frameLerp * ( 1.0 / lf->animSpeedScale ) ); + if ( lf->frameTime < cg.time ) { + lf->frameTime = cg.time; + } + + // check for skipping frames (eg. death anims play in slo-mo if low framerate) + if ( cg.time > lf->frameTime && !anim->moveSpeed ) { + f = ( lf->frame - anim->firstFrame ) + 1 + ( cg.time - lf->frameTime ) / anim->frameLerp; + } else { + f = ( lf->frame - anim->firstFrame ) + 1; + } + + if ( f < 0 ) { + f = 0; + } + } + //f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + if ( f >= anim->numFrames ) { + f -= anim->numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = anim->numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = cg.time; + } + } + lf->frame = anim->firstFrame + f; + if ( cg.time > lf->frameTime ) { + + // Ridah, run the frame again until we move ahead of the current time, fixes walking speeds for zombie + if ( /*!anim->moveSpeed ||*/ recursion > 4 ) { + lf->frameTime = cg.time; + } else { + CG_RunLerpFrameRate( ci, lf, newAnimation, cent, recursion + 1 ); + } + + if ( cg_debugAnim.integer > 3 ) { + CG_Printf( "Clamp lf->frameTime\n" ); + } + } + lf->oldAnimationNumber = lf->animationNumber; + } + + if ( lf->oldFrameTime > cg.time ) { + lf->oldFrameTime = cg.time; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} + +/* +=============== +CG_ClearLerpFrameRate +=============== +*/ +void CG_ClearLerpFrameRate( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber, centity_t *cent ) { + lf->frameTime = lf->oldFrameTime = cg.time; + CG_SetLerpFrameAnimationRate( cent, ci, lf, animationNumber ); + if ( lf->animation ) { + lf->oldFrame = lf->frame = lf->animation->firstFrame; + } +} + +// done. +//------------------------------------------------------------------------------ + + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) { + clientInfo_t *ci; + int clientNum; + int animIndex, tempIndex; + + clientNum = cent->currentState.clientNum; + + if ( cg_noPlayerAnims.integer ) { + *legsOld = *legs = *torsoOld = *torso = 0; + return; + } + + ci = &cgs.clientinfo[ clientNum ]; + + // default to whatever the legs are currently doing + animIndex = cent->currentState.legsAnim; + + // do the shuffle turn frames locally + if ( !( cent->currentState.eFlags & EF_DEAD ) && cent->pe.legs.yawing ) { +//CG_Printf("turn: %i\n", cg.time ); + tempIndex = BG_GetAnimScriptAnimation( clientNum, cent->currentState.aiState & ~( 1 << 2 ), ( cent->pe.legs.yawing == SWING_RIGHT ? ANIM_MT_TURNRIGHT : ANIM_MT_TURNLEFT ) ); + if ( tempIndex > -1 ) { + animIndex = tempIndex; + } + } + // run the animation + CG_RunLerpFrameRate( ci, ¢->pe.legs, animIndex, cent, 0 ); + + *legsOld = cent->pe.legs.oldFrame; + *legs = cent->pe.legs.frame; + *legsBackLerp = cent->pe.legs.backlerp; + + CG_RunLerpFrameRate( ci, ¢->pe.torso, cent->currentState.torsoAnim, cent, 0 ); + + *torsoOld = cent->pe.torso.oldFrame; + *torso = cent->pe.torso.frame; + *torsoBackLerp = cent->pe.torso.backlerp; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +/* +================== +CG_SwingAngles +================== +*/ +static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging ) { + float swing; + float move; + float scale; + + if ( !*swinging ) { + // see if a swing should be started + swing = AngleSubtract( *angle, destination ); + if ( swing > swingTolerance || swing < -swingTolerance ) { + *swinging = qtrue; + } + } + + if ( !*swinging ) { + return; + } + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + scale *= 0.05; + if ( scale < 0.5 ) { + scale = 0.5; + } + + // swing towards the destination angle + if ( swing >= 0 ) { + move = cg.frametime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } else { + *swinging = SWING_LEFT; // left + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = cg.frametime * scale * -speed; + if ( move <= swing ) { + move = swing; + *swinging = qfalse; + } else { + *swinging = SWING_RIGHT; // right + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if ( swing > clampTolerance ) { + *angle = AngleMod( destination - ( clampTolerance - 1 ) ); + } else if ( swing < -clampTolerance ) { + *angle = AngleMod( destination + ( clampTolerance - 1 ) ); + } +} + +/* +================= +CG_AddPainTwitch +================= +*/ +static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { + int t; + float f; + int duration; + float direction; + + if ( !cent->pe.animSpeed ) { + // we need to inititialize this stuff + cent->pe.painAnimLegs = -1; + cent->pe.painAnimTorso = -1; + cent->pe.animSpeed = 1.0; + } + + if ( cent->currentState.eFlags & EF_DEAD ) { + cent->pe.painAnimLegs = -1; + cent->pe.painAnimTorso = -1; + cent->pe.animSpeed = 1.0; + return; + } + + // special pain anims for AI + if ( cent->currentState.aiChar ) { + if ( cent->pe.painAnimTorso >= 0 ) { + animation_t *anim; + clientInfo_t *ci; + + ci = &cgs.clientinfo[ cent->currentState.number ]; + anim = &ci->modelInfo->animations[ cent->pe.painAnimTorso ]; + + // play the current animation + if ( cent->pe.torso.frame != cent->pe.torso.oldFrame || cent->pe.torso.frame != anim->firstFrame + anim->numFrames - 1 ) { + if ( cent->pe.painAnimLegs >= 0 ) { + cent->currentState.legsAnim = cent->pe.painAnimLegs; + } + cent->currentState.torsoAnim = cent->pe.painAnimTorso; + } else { // end it + cent->pe.painAnimLegs = -1; + cent->pe.painAnimTorso = -1; + cent->pe.animSpeed = 1.0; + } + } + + return; + } + + if ( cent->pe.painDuration ) { + duration = cent->pe.painDuration; + } else { + duration = PAIN_TWITCH_TIME; + } + direction = (float)duration * 0.085; + if ( direction > 30 ) { + direction = 30; + } + if ( direction < 10 ) { + direction = 10; + } + direction *= (float)( cent->pe.painDirection * 2 ) - 1; + + t = cg.time - cent->pe.painTime; + if ( t >= duration ) { + return; + } + + if ( cent->currentState.clientNum && cgs.gametype == GT_SINGLE_PLAYER ) { + #define FADEIN_RATIO 0.25 + #define FADEOUT_RATIO ( 1.0 - FADEIN_RATIO ) + f = (float)t / duration; + if ( f < FADEIN_RATIO ) { + torsoAngles[ROLL] += ( 0.5 * direction * ( f * ( 1.0 / FADEIN_RATIO ) ) ); + torsoAngles[PITCH] -= ( fabs( direction ) * ( f * ( 1.0 / FADEIN_RATIO ) ) ); + torsoAngles[YAW] += ( direction * ( f * ( 1.0 / FADEIN_RATIO ) ) ); + } else { + torsoAngles[ROLL] += ( 0.5 * direction * ( 1.0 - ( f - FADEIN_RATIO ) ) * ( 1.0 / FADEOUT_RATIO ) ); + torsoAngles[PITCH] -= ( fabs( direction ) * ( 1.0 - ( f - FADEIN_RATIO ) ) * ( 1.0 / FADEOUT_RATIO ) ); + torsoAngles[YAW] += ( direction * ( 1.0 - ( f - FADEIN_RATIO ) ) * ( 1.0 / FADEOUT_RATIO ) ); + } + } else { // fast, Q3 style + f = 1.0 - (float)t / duration; + if ( cent->pe.painDirection ) { + torsoAngles[ROLL] += 20 * f; + } else { + torsoAngles[ROLL] -= 20 * f; + } + } +} + +/* +=============== +CG_PlayerAngles + +Handles seperate torso motion + + legs pivot based on direction of movement + + head always looks exactly at cent->lerpAngles + + if motion < 20 degrees, show in head only + if < 45 degrees, also show in torso +=============== +*/ +static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { + vec3_t legsAngles, torsoAngles, headAngles; + float dest; +// static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; // TTimo: unused + vec3_t velocity; + float speed; + float clampTolerance; + int legsSet, torsoSet; + clientInfo_t *ci; + ci = &cgs.clientinfo[ cent->currentState.number ]; + + // special case (female zombie while climbing wall) + if ( cent->currentState.eFlags & EF_FORCED_ANGLES ) { + // torso & legs parts should turn to face the given angles + VectorCopy( cent->lerpAngles, legsAngles ); + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( vec3_origin, torso ); + AnglesToAxis( vec3_origin, head ); + return; + } + + legsSet = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + torsoSet = cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT; + + VectorCopy( cent->lerpAngles, headAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + + // --------- yaw ------------- + + // allow yaw to drift a bit, unless these conditions don't allow them + if ( !( BG_GetConditionValue( cent->currentState.number, ANIM_COND_MOVETYPE, qfalse ) & ( ( 1 << ANIM_MT_IDLE ) | ( 1 << ANIM_MT_IDLECR ) ) )/* + || (BG_GetConditionValue( cent->currentState.number, ANIM_COND_MOVETYPE, qfalse ) & ((1<pe.torso.yawing = qtrue; // always center + cent->pe.torso.pitching = qtrue; // always center + cent->pe.legs.yawing = qtrue; // always center + + // if firing, make sure torso and head are always aligned + } else if ( BG_GetConditionValue( cent->currentState.number, ANIM_COND_FIRING, qtrue ) ) { + + cent->pe.torso.yawing = qtrue; // always center + cent->pe.torso.pitching = qtrue; // always center + + } + + // adjust legs for movement dir + if ( cent->currentState.eFlags & EF_DEAD ) { + // don't let dead bodies twitch + legsAngles[YAW] = headAngles[YAW]; + torsoAngles[YAW] = headAngles[YAW]; + } else { + legsAngles[YAW] = headAngles[YAW] + cent->currentState.angles2[YAW]; + + if ( cent->currentState.eFlags & EF_NOSWINGANGLES ) { + legsAngles[YAW] = torsoAngles[YAW] = headAngles[YAW]; // always face firing direction + clampTolerance = 60; + } else if ( !( cent->currentState.eFlags & EF_FIRING ) ) { + torsoAngles[YAW] = headAngles[YAW] + 0.35 * cent->currentState.angles2[YAW]; + clampTolerance = 90; + } else { // must be firing + torsoAngles[YAW] = headAngles[YAW]; // always face firing direction + //if (fabs(cent->currentState.angles2[YAW]) > 30) + // legsAngles[YAW] = headAngles[YAW]; + clampTolerance = 60; + } + + // torso + CG_SwingAngles( torsoAngles[YAW], 25, clampTolerance, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); + + // if the legs are yawing (facing heading direction), allow them to rotate a bit, so we don't keep calling + // the legs_turn animation while an AI is firing, and therefore his angles will be randomizing according to their accuracy + + clampTolerance = 150; + + if ( BG_GetConditionValue( ci->clientNum, ANIM_COND_MOVETYPE, qfalse ) & ( 1 << ANIM_MT_IDLE ) ) { + cent->pe.legs.yawing = qfalse; // set it if they really need to swing + CG_SwingAngles( legsAngles[YAW], 20, clampTolerance, 0.5 * cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + } else + //if ( BG_GetConditionValue( ci->clientNum, ANIM_COND_MOVETYPE, qfalse ) & ((1<clientNum, legsSet ), "strafe" ) ) { + cent->pe.legs.yawing = qfalse; // set it if they really need to swing + legsAngles[YAW] = headAngles[YAW]; + CG_SwingAngles( legsAngles[YAW], 0, clampTolerance, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + } else + if ( cent->pe.legs.yawing ) { + CG_SwingAngles( legsAngles[YAW], 0, clampTolerance, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + } else + { + CG_SwingAngles( legsAngles[YAW], 40, clampTolerance, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + } + + torsoAngles[YAW] = cent->pe.torso.yawAngle; + legsAngles[YAW] = cent->pe.legs.yawAngle; + } + + // --------- pitch ------------- + + // only show a fraction of the pitch angle in the torso + if ( headAngles[PITCH] > 180 ) { + dest = ( -360 + headAngles[PITCH] ) * 0.75; + } else { + dest = headAngles[PITCH] * 0.75; + } + CG_SwingAngles( dest, 15, 30, 0.1, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); + torsoAngles[PITCH] = cent->pe.torso.pitchAngle; + + // --------- roll ------------- + + + // lean towards the direction of travel + VectorCopy( cent->currentState.pos.trDelta, velocity ); + speed = VectorNormalize( velocity ); + if ( speed ) { + vec3_t axis[3]; + float side; + + speed *= 0.05; + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[1] ); + legsAngles[ROLL] -= side; + + side = speed * DotProduct( velocity, axis[0] ); + legsAngles[PITCH] += side; + } + + // pain twitch + CG_AddPainTwitch( cent, torsoAngles ); + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( torsoAngles, torso ); + AnglesToAxis( headAngles, head ); +} + + +//========================================================================== + +/* +=============== +CG_HasteTrail +=============== +*/ +static void CG_HasteTrail( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + int anim; + + if ( cent->trailTime > cg.time ) { + return; + } + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; +// RF, this is all broken by scripting system +// if ( anim != LEGS_RUN && anim != LEGS_BACK ) { +// return; +// } + + cent->trailTime += 100; + if ( cent->trailTime < cg.time ) { + cent->trailTime = cg.time; + } + + VectorCopy( cent->lerpOrigin, origin ); + origin[2] -= 16; + + smoke = CG_SmokePuff( origin, vec3_origin, + 8, + 1, 1, 1, 1, + 500, + cg.time, + 0, + 0, + cgs.media.hastePuffShader ); + + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; +} + + +//----(SA) added and modified from missionpack +/* +=============== +CG_BreathPuffs +=============== +*/ +static void CG_BreathPuffs( centity_t *cent, refEntity_t *head ) { + clientInfo_t *ci; + vec3_t up, forward; + int contents; + + vec3_t mang, morg, maxis[3]; //, mang; + + ci = &cgs.clientinfo[ cent->currentState.number ]; + + if ( !cg_enableBreath.integer ) { + return; + } + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { + return; + } + if ( cent->currentState.eFlags & EF_DEAD ) { + return; + } + + // allow cg_enableBreath to force everyone to have breath + if ( cg_enableBreath.integer == 1 ) { + if ( !( cent->currentState.eFlags & EF_BREATH ) ) { + return; + } + } + + contents = trap_CM_PointContents( head->origin, 0 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + if ( ci->breathPuffTime > cg.time ) { + return; + } + + CG_GetOriginForTag( cent, head, "tag_mouth", 0, morg, maxis ); + AxisToAngles( maxis, mang ); + AngleVectors( mang, forward, NULL, up ); + + //push the origin out a tad so it's not right in the guys face (tad==4) + VectorMA( morg, 4, forward, morg ); + + forward[0] = up[0] * 8 + forward[0] * 5; + forward[1] = up[1] * 8 + forward[1] * 5; + forward[2] = up[2] * 8 + forward[2] * 5; + + CG_SmokePuff( morg, forward, 4, 1, 1, 1, 0.5f, 2000, cg.time, cg.time + 400, 0, cgs.media.shotgunSmokePuffShader ); + + + ci->breathPuffTime = cg.time + 3000 + random() * 1000; + +} + +//----(SA) end + +/* +=============== +CG_TrailItem +=============== +*/ +static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { + refEntity_t ent; + vec3_t angles; + qboolean ducking; + + // DHM - Nerve :: Don't draw icon above your own head + if ( cent->currentState.number == cg.snap->ps.clientNum ) { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( cent->lerpAngles, angles ); + angles[PITCH] = 0; + angles[ROLL] = 0; + AnglesToAxis( angles, ent.axis ); + + // DHM - Nerve :: adjusted values + VectorCopy( cent->lerpOrigin, ent.origin ); + + // Account for ducking + if ( cent->currentState.clientNum == cg.snap->ps.clientNum ) { + ducking = ( cg.snap->ps.pm_flags & PMF_DUCKED ); + } else { + ducking = (qboolean)cent->currentState.animMovetype; + } + + if ( ducking ) { + ent.origin[2] += 38; + } else { + ent.origin[2] += 56; + } + + ent.hModel = hModel; + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_PlayerPowerups +=============== +*/ +static void CG_PlayerPowerups( centity_t *cent ) { + int powerups; + + if ( cent->pe.teslaDamagedTime > cg.time - 400 ) { + trap_R_AddLightToScene( cent->lerpOrigin, 128 + 128 * sin( cg.time * cg.time ), 0.2, 0.6, 1, 0 ); + } + + // RF, AI don't use these effects, they are generally added manually by the game + if ( cent->currentState.aiChar ) { + return; + } + + powerups = cent->currentState.powerups; + if ( !powerups ) { + return; + } + + // quad gives a dlight + if ( powerups & ( 1 << PW_QUAD ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + ( rand() & 31 ), 0.2, 0.2, 1, 0 ); + } + + // redflag + if ( powerups & ( 1 << PW_REDFLAG ) ) { + CG_TrailItem( cent, cgs.media.redFlagModel ); + } + + // blueflag + if ( powerups & ( 1 << PW_BLUEFLAG ) ) { + CG_TrailItem( cent, cgs.media.blueFlagModel ); + } + + // haste leaves smoke trails + if ( powerups & ( 1 << PW_HASTE ) ) { + CG_HasteTrail( cent ); + } +} + + +/* +=============== +CG_PlayerFloatSprite + +Float a sprite over the player's head +DHM - Nerve :: added height parameter +=============== +*/ +static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader, int height ) { + int rf; + refEntity_t ent; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { + rf = RF_THIRD_PERSON; // only show in mirrors + } else { + rf = 0; + } + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[2] += height; // DHM - Nerve :: was '48' + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 6.66; + ent.renderfx = rf; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) { + int team; + + if ( cent->currentState.eFlags & EF_CONNECTION ) { + CG_PlayerFloatSprite( cent, cgs.media.connectionShader, 48 ); + return; + } + + if ( cent->currentState.powerups & ( 1 << PW_INVULNERABLE ) + && ( ( cent->currentState.effect3Time + 3000 ) > cg.time ) ) { + CG_PlayerFloatSprite( cent, cgs.media.spawnInvincibleShader, 56 ); + return; + } + + team = cgs.clientinfo[ cent->currentState.clientNum ].team; + + // DHM - Nerve :: If this client is a medic, draw a 'revive' icon over + // dead players that are not in limbo yet. + if ( cgs.gametype >= GT_WOLF && ( cent->currentState.eFlags & EF_DEAD ) + && cent->currentState.number == cent->currentState.clientNum + && cg.snap->ps.stats[ STAT_PLAYER_CLASS ] == PC_MEDIC + && cg.snap->ps.stats[ STAT_HEALTH ] > 0 + && cg.snap->ps.persistant[PERS_TEAM] == team ) { + + CG_PlayerFloatSprite( cent, cgs.media.medicReviveShader, 8 ); + return; + } + + // DHM - Nerve :: show voice chat signal so players know who's talking + if ( cgs.gametype >= GT_WOLF && cent->voiceChatSpriteTime > cg.time + && cg.snap->ps.persistant[PERS_TEAM] == team ) { + CG_PlayerFloatSprite( cent, cent->voiceChatSprite, 56 ); + return; + } + + // DHM - Nerve :: only show talk icon to team-mates + if ( cent->currentState.eFlags & EF_TALK && cg.snap->ps.persistant[PERS_TEAM] == team ) { + CG_PlayerFloatSprite( cent, cgs.media.balloonShader, 48 ); + return; + } +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + + should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE 64 +#define ZOFS 6.0 +#define SHADOW_MIN_DIST 250.0 +#define SHADOW_MAX_DIST 512.0 + +typedef struct { + char *tagname; + float size; + float maxdist; + float maxalpha; + qhandle_t shader; +} shadowPart_t; + +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { + vec3_t end; + trace_t trace; + float alpha, dist, distfade; + int tagIndex, subIndex; + vec3_t origin, angles, axis[3]; + shadowPart_t shadowParts[] = { + {"tag_footleft", 10, 4, 1.0, 0}, + {"tag_footright", 10, 4, 1.0, 0}, + {"tag_torso", 18, 96, 0.8, 0}, + {NULL, 0} + }; + + shadowParts[0].shader = cgs.media.shadowFootShader; //DAJ pulled out of initliization + shadowParts[1].shader = cgs.media.shadowFootShader; + shadowParts[2].shader = cgs.media.shadowTorsoShader; + + *shadowPlane = 0; + + if ( cg_shadows.integer == 0 ) { + return qfalse; + } + + // no shadows when invisible + if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { + return qfalse; + } + + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + end[2] -= SHADOW_DISTANCE; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, NULL, NULL, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction == 1.0 || trace.fraction == 0.0f ) { + return qfalse; + } + + *shadowPlane = trace.endpos[2] + 1; + + if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows + return qtrue; + } + + // no shadows when dead + if ( cent->currentState.eFlags & EF_DEAD ) { + return qfalse; + } + + // fade the shadow out with height + alpha = 1.0 - trace.fraction; + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + dist = VectorDistance( cent->lerpOrigin, cg.snap->ps.origin ); + if ( !( cent->currentState.eFlags & EF_ZOOMING ) && ( dist > SHADOW_MIN_DIST ) ) { + if ( dist < SHADOW_MAX_DIST ) { + distfade = ( dist - SHADOW_MIN_DIST ) / ( SHADOW_MAX_DIST - SHADOW_MIN_DIST ); + } else { + if ( dist > SHADOW_MAX_DIST * 2 ) { + return qfalse; + } else { // fade out + distfade = 1.0 - ( ( dist - SHADOW_MAX_DIST ) / SHADOW_MAX_DIST ); + } + } + alpha *= distfade; + CG_ImpactMark( cgs.media.shadowTorsoShader, trace.endpos, trace.plane.normal, + 0, alpha,alpha,alpha,1, qfalse, 16, qtrue, -1 ); + } else { + distfade = 0.0; + } + + if ( dist < SHADOW_MAX_DIST ) { // show more detail + // now add shadows for the various body parts + for ( tagIndex = 0; shadowParts[tagIndex].tagname; tagIndex++ ) { + // grab each tag with this name + for ( subIndex = 0; ( subIndex = CG_GetOriginForTag( cent, ¢->pe.legsRefEnt, shadowParts[tagIndex].tagname, subIndex, origin, axis ) ) >= 0; subIndex++ ) { + // project it onto the shadow plane + if ( origin[2] < *shadowPlane ) { + origin[2] = *shadowPlane; + } + alpha = 1.0 - ( ( origin[2] - ( *shadowPlane + ZOFS ) ) / shadowParts[tagIndex].maxdist ); + if ( alpha < 0 ) { + continue; + } + if ( alpha > shadowParts[tagIndex].maxalpha ) { + alpha = shadowParts[tagIndex].maxalpha; + } + alpha *= ( 1.0 - distfade ); + + origin[2] = *shadowPlane; + + AxisToAngles( axis, angles ); + + CG_ImpactMark( shadowParts[tagIndex].shader, origin, trace.plane.normal, + angles[YAW] /*cent->pe.legs.yawAngle*/, alpha,alpha,alpha,1, qfalse, shadowParts[tagIndex].size, qtrue, -1 ); + } + } + } + + return qtrue; +} + + + +/* +=============== +CG_PlayerSplash + +Draw a mark at the water surface +=============== +*/ +static void CG_PlayerSplash( centity_t *cent ) { + vec3_t start, end; + trace_t trace; + int contents; + polyVert_t verts[4]; + + if ( !cg_shadows.integer ) { + return; + } + + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + + // if the feet aren't in liquid, don't make a mark + // this won't handle moving water brushes, but they wouldn't draw right anyway... + contents = trap_CM_PointContents( end, 0 ); + if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + return; + } + + VectorCopy( cent->lerpOrigin, start ); + start[2] += 32; + + // if the head isn't out of liquid, don't make a mark + contents = trap_CM_PointContents( start, 0 ); + if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); + + if ( trace.fraction == 1.0 ) { + return; + } + + // create a mark polygon + VectorCopy( trace.endpos, verts[0].xyz ); + verts[0].xyz[0] -= 32; + verts[0].xyz[1] -= 32; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[1].xyz ); + verts[1].xyz[0] -= 32; + verts[1].xyz[1] += 32; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[2].xyz ); + verts[2].xyz[0] += 32; + verts[2].xyz[1] += 32; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[3].xyz ); + verts[3].xyz[0] += 32; + verts[3].xyz[1] -= 32; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); +} + + +//========================================================================== + +/* +=============== +CG_AddRefEntityWithPowerups + +Adds a piece with modifications or duplications for powerups +Also called by CG_Missile for quad rockets, but nobody can tell... +=============== +*/ +void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int team, entityState_t *es, const vec3_t fireRiseDir ) { + centity_t *cent; + refEntity_t backupRefEnt; //, parentEnt; + qboolean onFire = qfalse; + float alpha = 0.0; + float fireStart, fireEnd; + + cent = &cg_entities[es->number]; + + ent->entityNum = es->number; + + if ( cent->pe.forceLOD ) { + ent->reFlags |= REFLAG_FORCE_LOD; + } + + backupRefEnt = *ent; + + if ( CG_EntOnFire( &cg_entities[es->number] ) ) { + ent->reFlags |= REFLAG_FORCE_LOD; + } + + trap_R_AddRefEntityToScene( ent ); + + if ( !onFire && CG_EntOnFire( &cg_entities[es->number] ) ) { + onFire = qtrue; + // set the alpha + if ( ent->entityNum == cg.snap->ps.clientNum ) { + fireStart = cg.snap->ps.onFireStart; + fireEnd = cg.snap->ps.onFireStart + 1500; + } else { + fireStart = es->onFireStart; + fireEnd = es->onFireEnd; + } + + alpha = ( cg.time - fireStart ) / 1500.0; + if ( alpha > 1.0 ) { + alpha = ( fireEnd - cg.time ) / 1500.0; + if ( alpha > 1.0 ) { + alpha = 1.0; + } + } + } + + if ( onFire ) { + if ( alpha < 0.0 ) { + alpha = 0.0; + } + ent->shaderRGBA[3] = ( unsigned char )( 255.0 * alpha ); + VectorCopy( fireRiseDir, ent->fireRiseDir ); + if ( VectorCompare( ent->fireRiseDir, vec3_origin ) ) { + VectorSet( ent->fireRiseDir, 0, 0, 1 ); + } + ent->customShader = cgs.media.onFireShader; + trap_R_AddRefEntityToScene( ent ); + + ent->customShader = cgs.media.onFireShader2; + trap_R_AddRefEntityToScene( ent ); + + if ( ent->hModel == cent->pe.legsRefEnt.hModel ) { + trap_S_AddLoopingSound( es->number, ent->origin, vec3_origin, cgs.media.flameCrackSound, (int)( 255.0 * alpha ) ); + } + } + + // tesla effect + if ( cg_entities[es->number].pe.teslaDamagedTime > cg.time - 400 ) { + float alpha; + + alpha = ( 400.0 - (float)( cg.time - cg_entities[es->number].pe.teslaDamagedTime ) ) / 400.0; + + ent->shaderRGBA[0] = ( unsigned char )( 50.0 * alpha ); + ent->shaderRGBA[1] = ( unsigned char )( 130.0 * alpha ); + ent->shaderRGBA[2] = ( unsigned char )( 255.0 * alpha ); + + if ( ( cg.time / 50 ) % ( 2 + ( cg.time % 2 ) ) == 0 ) { + ent->customShader = cgs.media.teslaAltDamageEffectShader; + } else { + ent->customShader = cgs.media.teslaDamageEffectShader; + } + trap_R_AddRefEntityToScene( ent ); + } + + *ent = backupRefEnt; +} + +char *vtosf( const vec3_t v ) { + static int index; + static char str[8][64]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = ( index + 1 ) & 7; + + Com_sprintf( s, 64, "(%f %f %f)", v[0], v[1], v[2] ); + + return s; +} + + +/* +=============== +CG_AnimPlayerConditions + + predict, or calculate condition for this entity, if it is not the local client +=============== +*/ +void CG_AnimPlayerConditions( centity_t *cent ) { + entityState_t *es; + clientInfo_t *ci; + int legsAnim; + + if ( cg.snap && cg.snap->ps.clientNum == cent->currentState.number && !cg.renderingThirdPerson ) { + return; + } + + es = ¢->currentState; + ci = &cgs.clientinfo[es->clientNum]; + + // WEAPON + if ( es->eFlags & EF_ZOOMING ) { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_WEAPON, WP_BINOCULARS, qtrue ); + } else { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_WEAPON, es->weapon, qtrue ); + } + + // MOUNTED + if ( es->eFlags & EF_MG42_ACTIVE ) { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_MOUNTED, MOUNTED_MG42, qtrue ); + } else { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_MOUNTED, MOUNTED_UNUSED, qtrue ); + } + + // UNDERHAND + BG_UpdateConditionValue( es->clientNum, ANIM_COND_UNDERHAND, cent->lerpAngles[0] > 0, qtrue ); + + if ( es->eFlags & EF_CROUCHING ) { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_CROUCHING, qtrue, qtrue ); + } else { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_CROUCHING, qfalse, qtrue ); + } + + if ( es->eFlags & EF_FIRING ) { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_FIRING, qtrue, qtrue ); + } else { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_FIRING, qfalse, qtrue ); + } + + // reverse engineer the legs anim -> movetype (if possible) + legsAnim = es->legsAnim & ~ANIM_TOGGLEBIT; + if ( ci->modelInfo->animations[legsAnim].movetype ) { + BG_UpdateConditionValue( es->clientNum, ANIM_COND_MOVETYPE, ci->modelInfo->animations[legsAnim].movetype, qfalse ); + } + +} + +/* +=============== +CG_Player +=============== +*/ +void CG_Player( centity_t *cent ) { + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + refEntity_t acc; + + vec3_t playerOrigin; + vec3_t lightorigin; + + int clientNum,i; + int renderfx; + qboolean shadow; + float shadowPlane; + + qboolean usingBinocs = qfalse; // NERVE - SMF + + centity_t *cgsnap; + + cgsnap = &cg_entities[cg.snap->ps.clientNum]; + + shadow = qfalse; // gjd added to make sure it was initialized + shadowPlane = 0.0; // ditto + VectorCopy( vec3_origin, playerOrigin ); + + // if set to invisible, skip + if ( cent->currentState.eFlags & EF_NODRAW ) { + return; + } + + // the client number is stored in clientNum. It can't be derived + // from the entity number, because a single client may have + // multiple corpses on the level using the same clientinfo + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity" ); + } + ci = &cgs.clientinfo[ clientNum ]; + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if ( !ci->infoValid ) { + return; + } + + // Arnout: see if we're attached to a gun + if ( cent->currentState.eFlags & EF_MG42_ACTIVE ) { + centity_t *mg42; + int num; + + // find the mg42 we're attached to + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + mg42 = &cg_entities[ cg.snap->entities[ num ].number ]; + if ( mg42->currentState.eType == ET_MG42_BARREL && + mg42->currentState.otherEntityNum == cent->currentState.number ) { + // found it, clamp behind gun + vec3_t forward, right, up; + + //AngleVectors (mg42->s.apos.trBase, forward, right, up); + AngleVectors( cent->lerpAngles, forward, right, up ); + VectorMA( mg42->currentState.pos.trBase, -36, forward, playerOrigin ); + playerOrigin[2] = cent->lerpOrigin[2]; + break; + } + + if ( num == cg.snap->numEntities ) { + VectorCopy( cent->lerpOrigin, playerOrigin ); + } + } + } else { + VectorCopy( cent->lerpOrigin, playerOrigin ); + } + + memset( &legs, 0, sizeof( legs ) ); + memset( &torso, 0, sizeof( torso ) ); + memset( &head, 0, sizeof( head ) ); + memset( &acc, 0, sizeof( acc ) ); + + // get the rotation information + CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); + +//----(SA) added + // setting the legs axis should pass the scale down through the other parts/tags/etc. + // and will need to be 'undone' for the weapon + if ( ci->playermodelScale[0] ) { + VectorScale( legs.axis[0], ci->playermodelScale[0], legs.axis[0] ); + VectorScale( legs.axis[1], ci->playermodelScale[1], legs.axis[1] ); + VectorScale( legs.axis[2], ci->playermodelScale[2], legs.axis[2] ); + legs.nonNormalizedAxes = qtrue; + torso.nonNormalizedAxes = qtrue; + head.nonNormalizedAxes = qtrue; + } +//----(SA) end + + // copy the torso rotation to the accessories + AxisCopy( torso.axis, acc.axis ); + + // calculate client-side conditions + CG_AnimPlayerConditions( cent ); + + // get the animation state (after rotation, to allow feet shuffle) + CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + + // NERVE - SMF - forcibly set binoc animation + if ( cent->currentState.eFlags & EF_ZOOMING ) { + usingBinocs = qtrue; + } + // -NERVE - SMF + + // add powerups floating behind the player + CG_PlayerPowerups( cent ); + + // add the talk baloon or disconnect icon + CG_PlayerSprites( cent ); + + // add a water splash if partially in and out of water + CG_PlayerSplash( cent ); + + // get the player model information + renderfx = 0; + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { + renderfx = RF_THIRD_PERSON; // only draw in mirrors + } + + // draw the player in cameras + if ( cg.cameraMode ) { + renderfx &= ~RF_THIRD_PERSON; + } + + if ( cg_shadows.integer == 3 && shadow ) { + renderfx |= RF_SHADOW_PLANE; + } + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all + + // set renderfx for accessories + acc.renderfx = renderfx; + + //VectorCopy(cent->lerpOrigin, lightorigin); + VectorCopy( playerOrigin, lightorigin ); + lightorigin[2] += 31 + (float)cg_drawFPGun.integer; + + // + // add the legs + // + legs.hModel = ci->legsModel; + legs.customSkin = ci->legsSkin; + + //VectorCopy( cent->lerpOrigin, legs.origin ); + VectorCopy( playerOrigin, legs.origin ); + + if ( ci->playermodelScale[0] != 0 ) { // player scaled, adjust for the (-24) offset of player legs origin to ground + legs.origin[2] -= 24 * ( 1.0 - ci->playermodelScale[2] ); + } + + VectorCopy( lightorigin, legs.lightingOrigin ); + legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all + + if ( !ci->isSkeletal ) { + CG_AddRefEntityWithPowerups( &legs, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir ); + } + + cent->pe.legsRefEnt = legs; + + // if the model failed, allow the default nullmodel to be displayed + if ( !legs.hModel ) { + return; + } + + // (SA) only need to set this once... + VectorCopy( lightorigin, acc.lightingOrigin ); + + + // + // add the torso + // + torso.hModel = ci->torsoModel; + + if ( !torso.hModel ) { + return; + } + + torso.customSkin = ci->torsoSkin; + + VectorCopy( lightorigin, torso.lightingOrigin ); + + //----(SA) check for ladder and if you're on it, don't allow torso model rotation (so the body climbs aligned with the ladder) + //----(SA) also taking care of the Loper's interesting heirarchy (his upper body is effectively the same as a weapon_hand.md3. it keeps things connected, but has no geometry) + + if ( !ci->isSkeletal ) { + if ( cgsnap == cent && ( cg.snap->ps.pm_flags & PMF_LADDER ) ) { + CG_PositionEntityOnTag( &torso, &legs, "tag_torso", 0, NULL ); + } else { + CG_PositionRotatedEntityOnTag( &torso, &legs, "tag_torso" ); + } + } else { // just clear out the angles + if ( cgsnap == cent && ( cg.snap->ps.pm_flags & PMF_LADDER ) ) { + memcpy( torso.axis, legs.axis, sizeof( torso.axis ) ); + } + } + + torso.shadowPlane = shadowPlane; + torso.renderfx = renderfx; + + if ( !ci->isSkeletal ) { + + CG_AddRefEntityWithPowerups( &torso, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir ); + + } else { // SKELETAL ANIMATION + + // skeletal models combine the legs and torso, so we must build a compiled refEntity_t + + legs.torsoFrame = torso.frame; + legs.oldTorsoFrame = torso.oldframe; + + memcpy( legs.torsoAxis, torso.axis, sizeof( torso.axis ) ); + legs.torsoBacklerp = torso.backlerp; + + CG_AddRefEntityWithPowerups( &legs, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir ); + + cent->pe.legsRefEnt = legs; + torso = legs; // so tag calls use the correct values + } + + cent->pe.torsoRefEnt = torso; + + // + // add the head + // + head.hModel = ci->headModel; + if ( !head.hModel ) { + return; + } + head.customSkin = ci->headSkin; + + VectorCopy( lightorigin, head.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &head, &torso, "tag_head" ); + + head.shadowPlane = shadowPlane; + head.renderfx = renderfx; + + head.frame = 0; + head.oldframe = 0; + head.backlerp = 0.0; + +// JPW NERVE -- test +#ifndef PRE_RELEASE_DEMO + if ( cg_fxflags & 2 ) { + int i; + for ( i = 0; i < 3; i++ ) + VectorScale( head.axis[i], 2.5, head.axis[i] ); + head.nonNormalizedAxes = qtrue; + } +#endif +// jpw + + CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir ); + + cent->pe.headRefEnt = head; + + // add the shadow + shadow = CG_PlayerShadow( cent, &shadowPlane ); + + // set the shadowplane for accessories + acc.shadowPlane = shadowPlane; + + CG_BreathPuffs( cent, &head ); + + // + // add the gun / barrel / flash + // + if ( !( cent->currentState.eFlags & EF_DEAD ) && !usingBinocs ) { // NERVE - SMF + CG_AddPlayerWeapon( &torso, NULL, cent ); + } + + cent->lastWeaponClientFrame = cg.clientFrame; + + // + // add binoculars (if it's not the player) + // + if ( usingBinocs ) { // NERVE - SMF + acc.hModel = cgs.media.thirdPersonBinocModel; + CG_PositionEntityOnTag( &acc, &torso, "tag_weapon", 0, NULL ); + CG_AddRefEntityWithPowerups( &acc, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir ); + } + + // + // add accessories + // + for ( i = ACC_BELT_LEFT; i < ACC_MAX; i++ ) { + if ( !( ci->accModels[i] ) ) { + continue; + } + + acc.hModel = ci->accModels[i]; // set the model + + if ( ci->accSkins[i] ) { + acc.customSkin = ci->accSkins[i]; // and the skin if there is one + + + } + switch ( i ) { + case ACC_BELT_LEFT: + CG_PositionEntityOnTag( &acc, &legs, "tag_bright", 0, NULL ); + break; + case ACC_BELT_RIGHT: + CG_PositionEntityOnTag( &acc, &legs, "tag_bleft", 0, NULL ); + break; + + case ACC_BELT: + CG_PositionEntityOnTag( &acc, &torso, "tag_ubelt", 0, NULL ); + break; + case ACC_BACK: + CG_PositionEntityOnTag( &acc, &torso, "tag_back", 0, NULL ); + break; + + case ACC_HAT: //hat + if ( cent->currentState.eFlags & EF_HEADSHOT ) { + continue; + } + case ACC_MOUTH2: // hat2 + case ACC_MOUTH3: // hat3 + CG_PositionEntityOnTag( &acc, &head, "tag_mouth", 0, NULL ); + break; + + // weapon and weapon2 + // these are used by characters who have permanent weapons attached to their character in the skin + case ACC_WEAPON: // weap + CG_PositionEntityOnTag( &acc, &torso, "tag_weapon", 0, NULL ); + break; + case ACC_WEAPON2: // weap2 + CG_PositionEntityOnTag( &acc, &torso, "tag_weapon2", 0, NULL ); + break; + + default: + continue; + } + + CG_AddRefEntityWithPowerups( &acc, cent->currentState.powerups, ci->team, ¢->currentState, cent->fireRiseDir ); + } + +} + + +//===================================================================== + +extern void CG_ClearWeapLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ); + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) { + cent->errorTime = -99999; // guarantee no error decay added + cent->extrapolated = qfalse; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) { + CG_ClearLerpFrameRate( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim, cent ); + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); + + memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); + cent->pe.legs.yawAngle = cent->rawAngles[YAW]; + cent->pe.legs.yawing = qfalse; + cent->pe.legs.pitchAngle = 0; + cent->pe.legs.pitching = qfalse; + + memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); + cent->pe.torso.yawAngle = cent->rawAngles[YAW]; + cent->pe.torso.yawing = qfalse; + cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; + cent->pe.torso.pitching = qfalse; + } + + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + VectorCopy( cent->lerpOrigin, cent->rawOrigin ); + VectorCopy( cent->lerpAngles, cent->rawAngles ); + + if ( cg_debugPosition.integer ) { + CG_Printf( "%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + } + + cent->pe.painAnimLegs = -1; + cent->pe.painAnimTorso = -1; + cent->pe.animSpeed = 1.0; + +} + +void CG_GetBleedOrigin( vec3_t head_origin, vec3_t torso_origin, vec3_t legs_origin, int fleshEntityNum ) { + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + centity_t *cent, backupCent; + + ci = &cgs.clientinfo[ fleshEntityNum ]; + + cent = &cg_entities [ fleshEntityNum ]; + backupCent = *cent; + + if ( !ci->infoValid ) { + return; + } + + memset( &legs, 0, sizeof( legs ) ); + memset( &torso, 0, sizeof( torso ) ); + memset( &head, 0, sizeof( head ) ); + + CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); + CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + + legs.hModel = ci->legsModel; + VectorCopy( cent->lerpOrigin, legs.origin ); + VectorCopy( legs.origin, legs.oldorigin ); + + // Ridah, restore the cent so we don't interfere with animation timings + *cent = backupCent; + + if ( !legs.hModel ) { + return; + } + + torso.hModel = ci->torsoModel; + if ( !torso.hModel ) { + return; + } + + head.hModel = ci->headModel; + if ( !head.hModel ) { + return; + } + + CG_PositionRotatedEntityOnTag( &torso, &legs, "tag_torso" ); + CG_PositionRotatedEntityOnTag( &head, &torso, "tag_head" ); + + VectorCopy( head.origin, head_origin ); + VectorCopy( torso.origin, torso_origin ); + VectorCopy( legs.origin, legs_origin ); +} + +/* +=============== +CG_GetTag +=============== +*/ +qboolean CG_GetTag( int clientNum, char *tagname, orientation_t *or ) { + clientInfo_t *ci; + centity_t *cent; + refEntity_t *refent; + vec3_t tempAxis[3]; + vec3_t org; + int i; + + ci = &cgs.clientinfo[ clientNum ]; + + if ( !ci->isSkeletal ) { + return qfalse; // only skeletal models supported + + } + if ( cg.snap && clientNum == cg.snap->ps.clientNum && cg.renderingThirdPerson ) { + cent = &cg.predictedPlayerEntity; + } else { + cent = &cg_entities[ci->clientNum]; + if ( !cent->currentValid ) { + return qfalse; // not currently in PVS + } + } + + refent = ¢->pe.legsRefEnt; + + if ( trap_R_LerpTag( or, refent, tagname, 0 ) < 0 ) { + return qfalse; + } + + VectorCopy( refent->origin, org ); + + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( org, or->origin[i], refent->axis[i], org ); + } + + VectorCopy( org, or->origin ); + + // rotate with entity + MatrixMultiply( refent->axis, or->axis, tempAxis ); + memcpy( or->axis, tempAxis, sizeof( vec3_t ) * 3 ); + + return qtrue; +} + +/* +=============== +CG_GetWeaponTag +=============== +*/ +qboolean CG_GetWeaponTag( int clientNum, char *tagname, orientation_t *or ) { + clientInfo_t *ci; + centity_t *cent; + refEntity_t *refent; + vec3_t tempAxis[3]; + vec3_t org; + int i; + + ci = &cgs.clientinfo[ clientNum ]; + + if ( !ci->isSkeletal ) { + return qfalse; // only skeletal models supported + + } + if ( cg.snap && clientNum == cg.snap->ps.clientNum && cg.renderingThirdPerson ) { + cent = &cg.predictedPlayerEntity; + } else { + cent = &cg_entities[ci->clientNum]; + if ( !cent->currentValid ) { + return qfalse; // not currently in PVS + } + } + + if ( cent->pe.gunRefEntFrame < cg.clientFrame - 1 ) { + return qfalse; + } + + refent = ¢->pe.gunRefEnt; + + if ( trap_R_LerpTag( or, refent, tagname, 0 ) < 0 ) { + return qfalse; + } + + VectorCopy( refent->origin, org ); + + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( org, or->origin[i], refent->axis[i], org ); + } + + VectorCopy( org, or->origin ); + + // rotate with entity + MatrixMultiply( refent->axis, or->axis, tempAxis ); + memcpy( or->axis, tempAxis, sizeof( vec3_t ) * 3 ); + + return qtrue; +} diff --git a/src/cgame/cg_playerstate.c b/src/cgame/cg_playerstate.c new file mode 100644 index 0000000..f4f6943 --- /dev/null +++ b/src/cgame/cg_playerstate.c @@ -0,0 +1,590 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// following another player or playing back a demo, it will be checked +// when the snapshot transitions like all the other entities + +#include "cg_local.h" + +/* +============== +CG_CheckAmmo + +If the ammo has gone low enough to generate the warning, play a sound +============== +*/ +void CG_CheckAmmo( void ) { + int i; + int total; + int weapons[MAX_WEAPONS / ( sizeof( int ) * 8 )]; + + // see about how many seconds of ammo we have remaining + memcpy( weapons, cg.snap->ps.weapons, sizeof( weapons ) ); + + if ( !weapons[0] && !weapons[1] ) { // (SA) we start out with no weapons, so don't make a click on startup + return; + } + + total = 0; + + // first weap now WP_LUGER + for ( i = WP_FIRST ; i < WP_NUM_WEAPONS ; i++ ) + { + if ( !( weapons[0] & ( 1 << i ) ) ) { + continue; + } + switch ( i ) + { + case WP_ROCKET_LAUNCHER: + case WP_PANZERFAUST: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_LUGER: + case WP_COLT: + case WP_AKIMBO: + case WP_SILENCER: + case WP_FG42: + case WP_FG42SCOPE: + case WP_BAR: //----(SA) added + case WP_BAR2: //----(SA) added + case WP_MP40: + case WP_THOMPSON: + case WP_STEN: + case WP_VENOM: + case WP_CROSS: + case WP_TESLA: + case WP_MAUSER: + case WP_GARAND: + default: + total += cg.snap->ps.ammo[BG_FindAmmoForWeapon( i )] * 1000; +// break; +// default: +// total += cg.snap->ps.ammo[BG_FindAmmoForWeapon(i)] * 200; +// break; + } + + if ( total >= 5000 ) { + cg.lowAmmoWarning = 0; + return; + } + } + + if ( !cg.lowAmmoWarning ) { + // play a sound on this transition + trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); + } + + if ( total == 0 ) { + cg.lowAmmoWarning = 2; + } else { + cg.lowAmmoWarning = 1; + } +} + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { + float left, front, up; + float kick; + int health; + float scale; + vec3_t dir; + vec3_t angles; + float dist; + float yaw, pitch; + int slot; + viewDamage_t *vd; + + // show the attacking player's head and name in corner + cg.attackerTime = cg.time; + + // the lower on health you are, the greater the view kick will be + health = cg.snap->ps.stats[STAT_HEALTH]; + if ( health < 40 ) { + scale = 1; + } else { + scale = 40.0 / health; + } + kick = damage * scale; + + if ( kick < 5 ) { + kick = 5; + } + if ( kick > 10 ) { + kick = 10; + } + + // find a free slot + for ( slot = 0; slot < MAX_VIEWDAMAGE; slot++ ) { + if ( cg.viewDamage[slot].damageTime + cg.viewDamage[slot].damageDuration < cg.time ) { + break; + } + } + + if ( slot == MAX_VIEWDAMAGE ) { + return; // no free slots, never override or splats will suddenly disappear + + } + vd = &cg.viewDamage[slot]; + + // if yaw and pitch are both 255, make the damage always centered (falling, etc) + if ( yawByte == 255 && pitchByte == 255 ) { + vd->damageX = 0; + vd->damageY = 0; + cg.v_dmg_roll = 0; + cg.v_dmg_pitch = -kick; + } else { + // positional + pitch = pitchByte / 255.0 * 360; + yaw = yawByte / 255.0 * 360; + + angles[PITCH] = pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; + + AngleVectors( angles, dir, NULL, NULL ); + VectorSubtract( vec3_origin, dir, dir ); + + front = DotProduct( dir, cg.refdef.viewaxis[0] ); + left = DotProduct( dir, cg.refdef.viewaxis[1] ); + up = DotProduct( dir, cg.refdef.viewaxis[2] ); + + dir[0] = front; + dir[1] = left; + dir[2] = 0; + dist = VectorLength( dir ); + if ( dist < 0.1 ) { + dist = 0.1; + } + + cg.v_dmg_roll = kick * left; + + cg.v_dmg_pitch = -kick * front; + + if ( front <= 0.1 ) { + front = 0.1; + } + vd->damageX = crandom() * 0.3 + - left / front; + vd->damageY = crandom() * 0.3 + up / dist; + } + + // clamp the position + if ( vd->damageX > 1.0 ) { + vd->damageX = 1.0; + } + if ( vd->damageX < -1.0 ) { + vd->damageX = -1.0; + } + + if ( vd->damageY > 1.0 ) { + vd->damageY = 1.0; + } + if ( vd->damageY < -1.0 ) { + vd->damageY = -1.0; + } + + // don't let the screen flashes vary as much + if ( kick > 10 ) { + kick = 10; + } + vd->damageValue = kick; + cg.v_dmg_time = cg.time + DAMAGE_TIME; + vd->damageTime = cg.snap->serverTime; + vd->damageDuration = kick * 50 * ( 1 + 2 * ( !vd->damageX && !vd->damageY ) ); + cg.damageTime = cg.snap->serverTime; + cg.damageIndex = slot; +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) { + // no error decay on player movement + cg.thisFrameTeleport = qtrue; + + // need to reset client-side weapon animations + cg.predictedPlayerState.weapAnim = WEAP_IDLE1; // reset weapon animations + cg.predictedPlayerState.weapAnimTimer = 0; // allow other animations to happen right away + cg.predictedPlayerState.weaponstate = WEAPON_RAISING; // hmm, set this? what to? + + // display weapons available + cg.weaponSelectTime = cg.time; + + cg.holdableSelectTime = 0; //----(SA) reset holdable timer + + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + cg.centerPrintTime = 0; //----(SA) reset centerprint counter so previous messages don't re-appear + + } + cg.cursorHintIcon = 0; + cg.cursorHintTime = 0; + + cg.cameraMode = 0; //----(SA) get out of camera for sure + + // select the weapon the server says we are using + cg.weaponSelect = cg.snap->ps.weapon; + // DHM - Nerve :: Clear even more things on respawn + cg.zoomedBinoc = qfalse; + cg.zoomedBinoc = cg.zoomedScope = qfalse; + cg.zoomTime = 0; + cg.zoomval = 0; + + // clear pmext + memset( &cg.pmext, 0, sizeof( cg.pmext ) ); + + if ( cg_autoReload.integer ) { + cg.pmext.bAutoReload = qtrue; + } + + // set current player type + if ( mp_currentPlayerType.integer != cg.snap->ps.stats[ STAT_PLAYER_CLASS ] ) { + trap_Cvar_Set( "mp_currentPlayerType", va( "%i", cg.snap->ps.stats[ STAT_PLAYER_CLASS ] ) ); + } + + // reset fog to world fog (if present) + trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP,20,0,0,0,0 ); + // dhm - end + + trap_Cvar_Set( "cg_notebookpages", "3" ); // (SA) TEMP: clear notebook pages on spawn (cept for page 1) this is temporary + trap_Cvar_Set( "ui_notebookCurrentPage", "0" ); // (SA) TEMP: clear notebook pages on spawn (cept for page 1) this is temporary + + +} + +extern char *eventnames[]; + +/* +============== +CG_CheckPlayerstateEvents +============== +*/ +void CG_CheckPlayerstateEvents_wolf( playerState_t *ps, playerState_t *ops ) { + int i; + int event; + centity_t *cent; +/* + if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { + cent = &cg_entities[ ps->clientNum ]; + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } +*/ + cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + // go through the predictable events buffer + for ( i = ps->eventSequence - MAX_EVENTS ; i < ps->eventSequence ; i++ ) { + if ( ps->events[i & ( MAX_EVENTS - 1 )] != ops->events[i & ( MAX_EVENTS - 1 )] + || i >= ops->eventSequence ) { + event = ps->events[ i & ( MAX_EVENTS - 1 ) ]; + + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & ( MAX_EVENTS - 1 ) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + } +} + +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { + int i; + int event; + centity_t *cent; + + if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { + cent = &cg_entities[ ps->clientNum ]; + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + + cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + // go through the predictable events buffer + for ( i = ps->eventSequence - MAX_EVENTS ; i < ps->eventSequence ; i++ ) { + // if we have a new predictable event + if ( i >= ops->eventSequence + // or the server told us to play another event instead of a predicted event we already issued + // or something the server told us changed our prediction causing a different event + || ( i > ops->eventSequence - MAX_EVENTS && ps->events[i & ( MAX_EVENTS - 1 )] != ops->events[i & ( MAX_EVENTS - 1 )] ) ) { + + event = ps->events[ i & ( MAX_EVENTS - 1 ) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & ( MAX_EVENTS - 1 ) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event; + + cg.eventSequence++; + } + } +} + +/* +================== +CG_CheckChangedPredictableEvents +================== +*/ +void CG_CheckChangedPredictableEvents( playerState_t *ps ) { + int i; + int event; + centity_t *cent; + + cent = &cg.predictedPlayerEntity; + for ( i = ps->eventSequence - MAX_EVENTS ; i < ps->eventSequence ; i++ ) { + // + if ( i >= cg.eventSequence ) { + continue; + } + // if this event is not further back in than the maximum predictable events we remember + if ( i > cg.eventSequence - MAX_PREDICTED_EVENTS ) { + // if the new playerstate event is different from a previously predicted one + if ( ps->events[i & ( MAX_EVENTS - 1 )] != cg.predictableEvents[i & ( MAX_PREDICTED_EVENTS - 1 ) ] ) { + + event = ps->events[ i & ( MAX_EVENTS - 1 ) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & ( MAX_EVENTS - 1 ) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event; + + if ( cg_showmiss.integer ) { + CG_Printf( "WARNING: changed predicted event\n" ); + } + } + } + } +} + + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { +// const char *s; +// int highScore; + +/* JPW NERVE pulled from wolf MP + // hit changes + if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { + trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); + } +*/ + + // health changes of more than -1 should make pain sounds + if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { + if ( ps->stats[STAT_HEALTH] > 0 ) { + CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH], qfalse ); + } + } + + +/* // NERVE - SMF - don't do this in wolfMP + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } +*/ + +/* JPW NERVE pulled from wolf MP + // reward sounds + if ( ps->persistant[PERS_REWARD_COUNT] > ops->persistant[PERS_REWARD_COUNT] ) { + switch ( ps->persistant[PERS_REWARD] ) { + case REWARD_IMPRESSIVE: + trap_S_StartLocalSound( cgs.media.impressiveSound, CHAN_ANNOUNCER ); + cg.rewardTime = cg.time; + cg.rewardShader = cgs.media.medalImpressive; + cg.rewardCount = ps->persistant[PERS_IMPRESSIVE_COUNT]; + break; + case REWARD_EXCELLENT: + trap_S_StartLocalSound( cgs.media.excellentSound, CHAN_ANNOUNCER ); + cg.rewardTime = cg.time; + cg.rewardShader = cgs.media.medalExcellent; + cg.rewardCount = ps->persistant[PERS_EXCELLENT_COUNT]; + break; + case REWARD_DENIED: + trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); + break; + case REWARD_GAUNTLET: + trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); + // if we are the killer and not the killee, show the award + if ( ps->stats[STAT_HEALTH] ) { + cg.rewardTime = cg.time; + cg.rewardShader = cgs.media.medalGauntlet; + cg.rewardCount = ps->persistant[PERS_GAUNTLET_FRAG_COUNT]; + } + break; + default: + CG_Error( "Bad reward_t" ); + } + } else { + // lead changes (only if no reward) + s = CG_ConfigString( CS_WARMUP ); + if ( !s[0] ) { + // never play lead changes during warmup + if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { + if ( cgs.gametype >= GT_TEAM ) { + if ( ps->persistant[PERS_RANK] == 2 ) { + trap_S_StartLocalSound( cgs.media.teamsTiedSound, CHAN_ANNOUNCER ); + } else if ( ps->persistant[PERS_RANK] == 0 ) { + trap_S_StartLocalSound( cgs.media.redLeadsSound, CHAN_ANNOUNCER ); + } else if ( ps->persistant[PERS_RANK] == 1 ) { + trap_S_StartLocalSound( cgs.media.blueLeadsSound, CHAN_ANNOUNCER ); + } + } else { + if ( ps->persistant[PERS_RANK] == 0 ) { + trap_S_StartLocalSound( cgs.media.takenLeadSound, CHAN_ANNOUNCER ); + } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { + trap_S_StartLocalSound( cgs.media.tiedLeadSound, CHAN_ANNOUNCER ); + } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { + trap_S_StartLocalSound( cgs.media.lostLeadSound, CHAN_ANNOUNCER ); + } + } + } + } + } +*/ + // timelimit warnings + if ( cgs.timelimit > 0 ) { + int msec; + + msec = cg.time - cgs.levelStartTime; + + if ( cgs.timelimit > 2 && !( cg.timelimitWarnings & 1 ) && ( msec > ( cgs.timelimit - 2 ) * 60 * 1000 ) && + ( msec < ( cgs.timelimit - 2 ) * 60 * 1000 + 1000 ) ) { + cg.timelimitWarnings |= 1; + if ( ps->persistant[PERS_TEAM] == TEAM_RED && cg.twoMinuteSound_g[0] != '0' ) { + trap_S_StartLocalSound( cgs.media.twoMinuteSound_g, CHAN_ANNOUNCER ); + } else if ( ps->persistant[PERS_TEAM] == TEAM_BLUE && cg.twoMinuteSound_a[0] != '0' ) { + trap_S_StartLocalSound( cgs.media.twoMinuteSound_a, CHAN_ANNOUNCER ); + } + } + if ( !( cg.timelimitWarnings & 2 ) && ( msec > ( cgs.timelimit ) * 60 * 1000 - 30000 ) && + ( msec < ( cgs.timelimit ) * 60 * 1000 - 29000 ) ) { + cg.timelimitWarnings |= 2; + if ( ps->persistant[PERS_TEAM] == TEAM_RED && cg.thirtySecondSound_g[0] != '0' ) { + trap_S_StartLocalSound( cgs.media.thirtySecondSound_g, CHAN_ANNOUNCER ); + } else if ( ps->persistant[PERS_TEAM] == TEAM_BLUE && cg.thirtySecondSound_a[0] != '0' ) { + trap_S_StartLocalSound( cgs.media.thirtySecondSound_a, CHAN_ANNOUNCER ); + } + } +/* // JPW NERVE not used in wolf + if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { + cg.timelimitWarnings |= 4; + trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); + } +*/ + } + +/* JPW NERVE -- not used in wolf MP + // fraglimit warnings + if ( cgs.fraglimit > 0 && cgs.gametype != GT_CTF ) { + highScore = cgs.scores1; + if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { + cg.fraglimitWarnings |= 1; + trap_S_StartLocalSound( cgs.media.threeFragSound, CHAN_ANNOUNCER ); + } + if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { + cg.fraglimitWarnings |= 2; + trap_S_StartLocalSound( cgs.media.twoFragSound, CHAN_ANNOUNCER ); + } + if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { + cg.fraglimitWarnings |= 4; + trap_S_StartLocalSound( cgs.media.oneFragSound, CHAN_ANNOUNCER ); + } + } +*/ +} + +/* +=============== +CG_TransitionPlayerState + +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { + // check for changing follow mode + if ( ps->clientNum != ops->clientNum ) { + cg.thisFrameTeleport = qtrue; + // make sure we don't get any unwanted transition effects + *ops = *ps; + + // DHM - Nerve :: After Limbo, make sure and do a CG_Respawn + if ( ps->clientNum == cg.clientNum ) { + ops->persistant[PERS_SPAWN_COUNT]--; + } + } + + // damage events (player is getting wounded) + if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { + CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); + } + + // respawning + if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { + CG_Respawn(); + } + + if ( cg.mapRestart ) { + CG_Respawn(); + cg.mapRestart = qfalse; + } + + if ( cg.snap->ps.pm_type != PM_INTERMISSION + && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + CG_CheckLocalSounds( ps, ops ); + } + + // check for going low on ammo + CG_CheckAmmo(); + + // run events + CG_CheckPlayerstateEvents( ps, ops ); + + // smooth the ducking viewheight change + if ( ps->viewheight != ops->viewheight ) { + cg.duckChange = ps->viewheight - ops->viewheight; + cg.duckTime = cg.time; + } +} + diff --git a/src/cgame/cg_predict.c b/src/cgame/cg_predict.c new file mode 100644 index 0000000..e62fd3f --- /dev/null +++ b/src/cgame/cg_predict.c @@ -0,0 +1,805 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +// cg_predict.c -- this file generates cg.predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// ahead the client's movement. +// It also handles local physics interaction, like fragments bouncing off walls + +#include "cg_local.h" + +static pmove_t cg_pmove; + +static int cg_numSolidEntities; +static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; +static int cg_numTriggerEntities; +static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +/* +==================== +CG_BuildSolidList + +When a new cg.snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) { + int i; + centity_t *cent; + snapshot_t *snap; + entityState_t *ent; + + cg_numSolidEntities = 0; + cg_numTriggerEntities = 0; + + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + snap = cg.nextSnap; + } else { + snap = cg.snap; + } + + for ( i = 0 ; i < snap->numEntities ; i++ ) { + cent = &cg_entities[ snap->entities[ i ].number ]; + ent = ¢->currentState; + + // RF, dont clip again non-solid bmodels + if ( cent->nextState.solid == SOLID_BMODEL && ( cent->nextState.eFlags & EF_NONSOLID_BMODEL ) ) { + continue; + } + + if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER + || ent->eType == ET_CONCUSSIVE_TRIGGER || ent->eType == ET_OID_TRIGGER ) { // JPW NERVE + cg_triggerEntities[cg_numTriggerEntities] = cent; + cg_numTriggerEntities++; + continue; + } + + if ( cent->nextState.solid ) { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + continue; + } + } +} + +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +static void CG_ClipMoveToEntities( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask, int capsule, trace_t *tr ) { + int i, x, zd, zu; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + vec3_t bmins, bmaxs; + vec3_t origin, angles; + centity_t *cent; + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + ent = ¢->currentState; + + if ( ent->number == skipNumber ) { + continue; + } + + if ( ent->solid == SOLID_BMODEL ) { + // special value for bmodel + cmodel = trap_CM_InlineModel( ent->modelindex ); +// VectorCopy( cent->lerpAngles, angles ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.physicsTime, angles ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); + } else { + // encoded bbox + x = ( ent->solid & 255 ); + zd = ( ( ent->solid >> 8 ) & 255 ); + zu = ( ( ent->solid >> 16 ) & 255 ) - 32; + + bmins[0] = bmins[1] = -x; + bmaxs[0] = bmaxs[1] = x; + bmins[2] = -zd; + bmaxs[2] = zu; + + // MrE: use bbox or capsule + if ( ent->eFlags & EF_CAPSULE ) { + cmodel = trap_CM_TempCapsuleModel( bmins, bmaxs ); + } else { + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + } + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + } + // MrE: use bbox of capsule + if ( capsule ) { + trap_CM_TransformedCapsuleTrace( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles ); + } else { + trap_CM_TransformedBoxTrace( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles ); + } + + if ( trace.allsolid || trace.fraction < tr->fraction ) { + trace.entityNum = ent->number; + *tr = trace; + } else if ( trace.startsolid ) { + tr->startsolid = qtrue; + } + if ( tr->allsolid ) { + return; + } + } +} + +/* +================ +CG_Trace +================ +*/ +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + trace_t t; + + trap_CM_BoxTrace( &t, start, end, mins, maxs, 0, mask ); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, qfalse, &t ); + + *result = t; +} + +/* +================ +CG_TraceCapsule +================ +*/ +void CG_TraceCapsule( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + trace_t t; + + trap_CM_CapsuleTrace( &t, start, end, mins, maxs, 0, mask ); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, qtrue, &t ); + + *result = t; +} + +/* +================ +CG_PointContents +================ +*/ +int CG_PointContents( const vec3_t point, int passEntityNum ) { + int i; + entityState_t *ent; + centity_t *cent; + clipHandle_t cmodel; + int contents; + + contents = trap_CM_PointContents( point, 0 ); + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + + ent = ¢->currentState; + + if ( ent->number == passEntityNum ) { + continue; + } + + if ( ent->solid != SOLID_BMODEL ) { // special value for bmodel + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + } + + return contents; +} + + +/* +======================== +CG_InterpolatePlayerState + +Generates cg.predictedPlayerState by interpolating between +cg.snap->player_state and cg.nextFrame->player_state +======================== +*/ +static void CG_InterpolatePlayerState( qboolean grabAngles ) { + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg.predictedPlayerState; + prev = cg.snap; + next = cg.nextSnap; + + *out = cg.snap->ps; + + // if we are still allowing local input, short circuit the view angles + if ( grabAngles ) { + usercmd_t cmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + + PM_UpdateViewAngles( out, &cmd, CG_Trace ); + } + + // if the next frame is a teleport, we can't lerp to it + if ( cg.nextFrameTeleport ) { + return; + } + + if ( !next || next->serverTime <= prev->serverTime ) { + return; + } + + f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + + i = next->ps.bobCycle; + if ( i < prev->ps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->ps.origin[i] + f * ( next->ps.origin[i] - prev->ps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->ps.viewangles[i], next->ps.viewangles[i], f ); + } + out->velocity[i] = prev->ps.velocity[i] + + f * ( next->ps.velocity[i] - prev->ps.velocity[i] ); + } + +} + +/* +=================== +CG_TouchItem +=================== +*/ +static void CG_TouchItem( centity_t *cent ) { + gitem_t *item; + + if ( !cg_predictItems.integer ) { + return; + } + +//----(SA) wolf -- not allowing this for single player games +// if( cgs.gametype == GT_SINGLE_PLAYER) { +// return; +// } + +//----(SA) autoactivate + if ( !cg_autoactivate.integer ) { + return; + } +//----(SA) end + + + if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { + return; + } + + // never pick an item up twice in a prediction + if ( cent->miscTime == cg.time ) { + return; + } + + if ( !BG_CanItemBeGrabbed( ¢->currentState, &cg.predictedPlayerState ) ) { + return; // can't hold it + } + + item = &bg_itemlist[ cent->currentState.modelindex ]; + + // (SA) no prediction of books/clipboards + if ( item->giType == IT_HOLDABLE ) { + if ( item->giTag >= HI_BOOK1 && item->giTag <= HI_BOOK3 ) { + return; + } + } + if ( item->giType == IT_CLIPBOARD ) { + return; + } + + // (SA) treasure needs to be activeated, no touch + if ( item->giType == IT_TREASURE ) { + return; + } + + // Special case for flags. + // We don't predict touching our own flag + if ( cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && + item->giTag == PW_REDFLAG ) { + return; + } + if ( cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && + item->giTag == PW_BLUEFLAG ) { + return; + } + + + // grab it + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex, &cg.predictedPlayerState ); + + // remove it from the frame so it won't be drawn + cent->currentState.eFlags |= EF_NODRAW; + + // don't touch it again this prediction + cent->miscTime = cg.time; + + // if its a weapon, give them some predicted ammo so the autoswitch will work + if ( item->giType == IT_WEAPON ) { + COM_BitSet( cg.predictedPlayerState.weapons, item->giTag ); + +//----(SA) added + if ( item->giTag == WP_SNOOPERSCOPE ) { + COM_BitSet( cg.predictedPlayerState.weapons, WP_GARAND ); + } else if ( item->giTag == WP_GARAND ) { + COM_BitSet( cg.predictedPlayerState.weapons, WP_SNOOPERSCOPE ); + } +//----(SA) end + + if ( !cg.predictedPlayerState.ammo[ BG_FindAmmoForWeapon( item->giTag )] ) { + cg.predictedPlayerState.ammo[ BG_FindAmmoForWeapon( item->giTag )] = 1; + } + } + +//----(SA) + if ( item->giType == IT_HOLDABLE ) { + cg.predictedPlayerState.stats[ STAT_HOLDABLE_ITEM ] |= 1 << item->giTag; + } +//----(SA) end +} + +void CG_AddDirtBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale, + float width, float height, float alpha, char *shadername ); + + +/* +========================= +CG_TouchTriggerPrediction + +Predict push triggers and items +========================= +*/ +static void CG_TouchTriggerPrediction( void ) { + int i; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + centity_t *cent; + qboolean spectator; +// vec3_t mins, maxs; // TTimo: unused + const char *cs; + + // dead clients don't activate triggers + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + spectator = ( ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ) || ( cg.predictedPlayerState.pm_flags & PMF_LIMBO ) ); // JPW NERVE + + if ( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) { + return; + } + + for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { + cent = cg_triggerEntities[ i ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM && !spectator ) { + CG_TouchItem( cent ); + continue; + } + + if ( ent->solid != SOLID_BMODEL ) { + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, + cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); + + if ( !trace.startsolid ) { + continue; + } +/* +// JPW NERVE + if (ent->eType == ET_CONCUSSIVE_TRIGGER) { + vec3_t dir,center; + dir[0] = 0; + dir[1] = 0; + dir[2] = -1; + CG_Printf("in concussive trigger\n"); + trap_R_ModelBounds(cmodel,mins,maxs); + VectorAdd(mins,maxs,center); + VectorScale(center,0.5,center); + CG_Printf("cmodel=%d mins=%f,%f,%f maxs=%f,%f,%f\n",cmodel, + mins[0],mins[1],mins[2],maxs[0],maxs[1],maxs[2]); + + CG_AddDirtBulletParticles( mins, dir, + 190, // speed + 900, // duration + 1, // count + 0.25, 32,32, 0.5, "dirt_splash" ); // rand scale + CG_AddDirtBulletParticles( maxs, dir, + 190, // speed + 900, // duration + 1, // count + 0.25, 32,32, 0.5, "dirt_splash" ); // rand scale + } + else +// jpw + +*/ + if ( ent->eType == ET_OID_TRIGGER ) { + cs = CG_ConfigString( CS_OID_TRIGGERS + ent->teamNum ); + + CG_ObjectivePrint( va( "You are near %s\n", cs ), SMALLCHAR_WIDTH ); + } else if ( ent->eType == ET_TELEPORT_TRIGGER ) { + cg.hyperspace = qtrue; + } else if ( ent->eType == ET_PUSH_TRIGGER ) { + float s; + vec3_t dir; + + // we hit this push trigger + if ( spectator ) { + continue; + } + + // flying characters don't hit bounce pads + if ( cg.predictedPlayerState.powerups[PW_FLIGHT] ) { + continue; + } + + // if we are already flying along the bounce direction, don't play sound again + VectorNormalize2( ent->origin2, dir ); + s = DotProduct( cg.predictedPlayerState.velocity, dir ); + if ( s < 500 ) { + // don't play the event sound again if we are in a fat trigger + BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, 0, &cg.predictedPlayerState ); + } + VectorCopy( ent->origin2, cg.predictedPlayerState.velocity ); + } + } +} + + + +/* +================= +CG_PredictPlayerState + +Generates cg.predictedPlayerState for the current cg.time +cg.predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one. Would require saving all intermediate +playerState_t during prediction. (this is "dead reckoning" and would definately +be nice to have in there (SA)) + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ +void CG_PredictPlayerState( void ) { + int cmdNum, current; + playerState_t oldPlayerState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + vec3_t deltaAngles; + + cg.hyperspace = qfalse; // will be set if touching a trigger_teleport + + // if this is the first frame we must guarantee + // predictedPlayerState is valid even if there is some + // other error condition + if ( !cg.validPPS ) { + cg.validPPS = qtrue; + cg.predictedPlayerState = cg.snap->ps; + } + + // demo playback just copies the moves + if ( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { + CG_InterpolatePlayerState( qfalse ); + return; + } + + // non-predicting local movement will grab the latest angles + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_InterpolatePlayerState( qtrue ); + return; + } + + // prepare for pmove + cg_pmove.ps = &cg.predictedPlayerState; + cg_pmove.pmext = &cg.pmext; + + // Arnout: are we using an mg42? + if ( cg_pmove.ps->eFlags & EF_MG42_ACTIVE ) { + cg_pmove.pmext->harc = cg_entities[cg_pmove.ps->viewlocked_entNum].currentState.origin2[0]; + cg_pmove.pmext->varc = cg_entities[cg_pmove.ps->viewlocked_entNum].currentState.origin2[1]; + VectorCopy( cg_entities[cg_pmove.ps->viewlocked_entNum].currentState.angles2, cg_pmove.pmext->centerangles ); + cg_pmove.pmext->centerangles[PITCH] = AngleNormalize180( cg_pmove.pmext->centerangles[PITCH] ); + cg_pmove.pmext->centerangles[YAW] = AngleNormalize180( cg_pmove.pmext->centerangles[YAW] ); + cg_pmove.pmext->centerangles[ROLL] = AngleNormalize180( cg_pmove.pmext->centerangles[ROLL] ); + } + + //DHM - Nerve :: We've gone back to using normal bbox traces + //cg_pmove.trace = CG_TraceCapsule; + cg_pmove.trace = CG_Trace; + cg_pmove.pointcontents = CG_PointContents; + if ( cg_pmove.ps->pm_type == PM_DEAD ) { + cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + // DHM-Nerve added:: EF_DEAD is checked for in Pmove functions, but wasn't being set + // until after Pmove + cg_pmove.ps->eFlags |= EF_DEAD; + // dhm-Nerve end + } else { + cg_pmove.tracemask = MASK_PLAYERSOLID; + } + if ( ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) || ( cg.snap->ps.pm_flags & PMF_LIMBO ) ) { // JPW NERVE limbo + cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + } + cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + + //----(SA) added + cg_pmove.noWeapClips = ( cgs.dmflags & DF_NO_WEAPRELOAD ) > 0; + if ( cg.predictedPlayerState.aiChar ) { + cg_pmove.noWeapClips = qtrue; // ensure AI characters don't use clips + } +//----(SA) end + + + // save the state before the pmove so we can detect transitions + oldPlayerState = cg.predictedPlayerState; + + current = trap_GetCurrentCmdNumber(); + + // if we don't have the commands right after the snapshot, we + // can't accurately predict a current position, so just freeze at + // the last good position we had + cmdNum = current - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &oldestCmd ); + if ( oldestCmd.serverTime > cg.snap->ps.commandTime + && oldestCmd.serverTime < cg.time ) { // special check for map_restart + if ( cg_showmiss.integer ) { + CG_Printf( "exceeded PACKET_BACKUP on commands\n" ); + } +// return; + } + + // get the latest command so we can know which commands are from previous map_restarts + trap_GetUserCmd( current, &latestCmd ); + + // get the most recent information we have, even if + // the server time is beyond our current cg.time, + // because predicted player positions are going to + // be ahead of everything else anyway + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + cg.predictedPlayerState = cg.nextSnap->ps; + cg.physicsTime = cg.nextSnap->serverTime; + } else { + cg.predictedPlayerState = cg.snap->ps; + cg.physicsTime = cg.snap->serverTime; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set( "pmove_msec", "8" ); + } else if ( pmove_msec.integer > 33 ) { + trap_Cvar_Set( "pmove_msec", "33" ); + } + + cg_pmove.pmove_fixed = pmove_fixed.integer; // | cg_pmove_fixed.integer; + cg_pmove.pmove_msec = pmove_msec.integer; + +//----(SA) added + // restore persistant client-side playerstate variables before doing the pmove + // this could be done as suggested in q_shared.h ~line 1155, but right now I copy each variable individually + // DHM - Nerve :: weapAnim is transmitted now + //cg.predictedPlayerState.weapAnim = oldPlayerState.weapAnim; + cg.predictedPlayerState.weapAnimTimer = oldPlayerState.weapAnimTimer; + cg.predictedPlayerState.venomTime = oldPlayerState.venomTime; +//----(SA) end + + // RF, anim system + if ( cg_animState.integer ) { + cg.predictedPlayerState.aiState = cg_animState.integer - 1; + } + + // run cmds + moved = qfalse; + for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { + // get the command + trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); + // get the previous command + trap_GetUserCmd( cmdNum - 1, &cg_pmove.oldcmd ); + + if ( cg_pmove.pmove_fixed ) { + PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd, CG_Trace ); + } + + // don't do anything if the time is before the snapshot player time + if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { + continue; + } + + // don't do anything if the command was from a previous map_restart + if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { + continue; + } + + // check for a prediction error from last frame + // on a lan, this will often be the exact value + // from the snapshot, but on a wan we will have + // to predict several commands to get to the point + // we want to compare + if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) { + vec3_t delta; + float len; + + if ( cg_pmove.ps->eFlags & EF_MG42_ACTIVE ) { + // no prediction errors here, we're locked in place + VectorClear( cg.predictedError ); + } else if ( cg.thisFrameTeleport ) { + // a teleport will not cause an error decay + VectorClear( cg.predictedError ); + if ( cg_showmiss.integer ) { + CG_Printf( "PredictionTeleport\n" ); + } + cg.thisFrameTeleport = qfalse; + } else { + vec3_t adjusted; + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted, deltaAngles ); + // RF, add the deltaAngles (fixes jittery view while riding trains) + cg.predictedPlayerState.delta_angles[YAW] += ANGLE2SHORT( deltaAngles[YAW] ); + + if ( cg_showmiss.integer ) { + if ( !VectorCompare( oldPlayerState.origin, adjusted ) ) { + CG_Printf( "prediction error\n" ); + } + } + VectorSubtract( oldPlayerState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) { + if ( cg_showmiss.integer ) { + CG_Printf( "Prediction miss: %f\n", len ); + } + if ( cg_errorDecay.integer ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f < 0 ) { + f = 0; + } + if ( f > 0 && cg_showmiss.integer ) { + CG_Printf( "Double prediction decay: %f\n", f ); + } + VectorScale( cg.predictedError, f, cg.predictedError ); + } else { + VectorClear( cg.predictedError ); + } + VectorAdd( delta, cg.predictedError, cg.predictedError ); + cg.predictedErrorTime = cg.oldTime; + } + } + } + + // don't predict gauntlet firing, which is only supposed to happen + // when it actually inflicts damage + cg_pmove.gauntletHit = qfalse; + + if ( cg_pmove.pmove_fixed ) { + cg_pmove.cmd.serverTime = ( ( cg_pmove.cmd.serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; + } + + // RF, if waiting for mission stats to go, ignore all input + if ( strlen( cg_missionStats.string ) > 1 || cg_norender.integer ) { + cg_pmove.cmd.buttons = 0; + cg_pmove.cmd.forwardmove = 0; + cg_pmove.cmd.rightmove = 0; + cg_pmove.cmd.upmove = 0; + cg_pmove.cmd.wbuttons = 0; + cg_pmove.cmd.wolfkick = 0; + cg_pmove.cmd.angles[0] = cg_pmove.oldcmd.angles[0]; + cg_pmove.cmd.angles[1] = cg_pmove.oldcmd.angles[1]; + cg_pmove.cmd.angles[2] = cg_pmove.oldcmd.angles[2]; + } + + // NERVE - SMF + cg_pmove.gametype = cgs.gametype; + cg_pmove.ltChargeTime = cg_LTChargeTime.integer; + cg_pmove.soldierChargeTime = cg_soldierChargeTime.integer; + cg_pmove.engineerChargeTime = cg_engineerChargeTime.integer; + cg_pmove.medicChargeTime = cg_medicChargeTime.integer; + // -NERVE - SMF + + Pmove( &cg_pmove ); + + moved = qtrue; + + // add push trigger movement effects + CG_TouchTriggerPrediction(); + } + + if ( cg_showmiss.integer > 1 ) { + CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); + } + + if ( !moved ) { + if ( cg_showmiss.integer ) { + CG_Printf( "not moved\n" ); + } + return; + } + + // adjust for the movement of the groundentity + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, + cg.physicsTime, cg.time, cg.predictedPlayerState.origin, deltaAngles ); + + // fire events and other transition triggered things + CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); +} + diff --git a/src/cgame/cg_public.h b/src/cgame/cg_public.h new file mode 100644 index 0000000..df9869b --- /dev/null +++ b/src/cgame/cg_public.h @@ -0,0 +1,284 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +#define CMD_BACKUP 64 +#define CMD_MASK ( CMD_BACKUP - 1 ) +// allow a lot of command backups for very fast systems +// multiple commands may be combined into a single packet, so this +// needs to be larger than PACKET_BACKUP + + +#define MAX_ENTITIES_IN_SNAPSHOT 256 + +// snapshots are a view of the server at a given time + +// Snapshots are generated at regular time intervals by the server, +// but they may not be sent if a client's rate level is exceeded, or +// they may be dropped by the network. +typedef struct { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +} snapshot_t; + +enum { + CGAME_EVENT_NONE, + CGAME_EVENT_TEAMMENU, + CGAME_EVENT_SCOREBOARD, + CGAME_EVENT_EDITHUD +}; + + +/* +================================================================== + +functions imported from the main executable + +================================================================== +*/ + +#define CGAME_IMPORT_API_VERSION 3 + +typedef enum { + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, +// MrE: + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_CM_TEMPCAPSULEMODEL, +// done. + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTSOUNDEX, //----(SA) added + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, +// Ridah, talking animations + CG_S_GETVOICEAMPLITUDE, +// done. + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_S_STARTSTREAMINGSOUND, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + + CG_R_GETSKINMODEL, // client allowed to view what the .skin loaded so they can set their model appropriately + CG_R_GETMODELSHADER, // client allowed the shader handle for given model/surface (for things like debris inheriting shader from explosive) + + CG_R_REGISTERFONT, + CG_R_CLEARSCENE, + CG_R_ADDREFENTITYTOSCENE, + CG_GET_ENTITY_TOKEN, + CG_R_ADDPOLYTOSCENE, +// Ridah + CG_R_ADDPOLYSTOSCENE, +// done. + CG_R_ADDLIGHTTOSCENE, + + CG_R_ADDCORONATOSCENE, + CG_R_SETFOG, + + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_DRAWSTRETCHPIC_GRADIENT, //----(SA) added + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_SETCLIENTLERPORIGIN, // DHM - Nerve + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, + + CG_PC_ADD_GLOBAL_DEFINE, + CG_PC_LOAD_SOURCE, + CG_PC_FREE_SOURCE, + CG_PC_READ_TOKEN, + CG_PC_SOURCE_FILE_AND_LINE, + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, + + CG_SENDMOVESPEEDSTOGAME, + + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + CG_R_REMAP_SHADER, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + + CG_LOADCAMERA, + CG_STARTCAMERA, + CG_GETCAMERAINFO, + + CG_MEMSET = 100, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS, + + CG_INGAME_POPUP, //----(SA) added + + // NERVE - SMF + CG_INGAME_CLOSEPOPUP, + CG_LIMBOCHAT, + + CG_R_DRAWROTATEDPIC, + + CG_KEY_GETBINDINGBUF, + CG_KEY_SETBINDING, + CG_KEY_KEYNUMTOSTRINGBUF, + + CG_TRANSLATE_STRING + // -NERVE - SMF +} cgameImport_t; + + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum { + CG_INIT, +// void CG_Init( int serverMessageNum, int serverCommandSequence ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, +// void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, +// qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, +// int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, +// int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, +// void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, +// void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING, +// void (*CG_EventHandling)(int type); + + CG_GET_TAG, +// qboolean CG_GetTag( int clientNum, char *tagname, orientation_t *or ); + + CG_CHECKCENTERVIEW, +// qboolean CG_CheckCenterView(); + +} cgameExport_t; + +//---------------------------------------------- diff --git a/src/cgame/cg_scoreboard.c b/src/cgame/cg_scoreboard.c new file mode 100644 index 0000000..5c40bf3 --- /dev/null +++ b/src/cgame/cg_scoreboard.c @@ -0,0 +1,895 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_scoreboard -- draw the scoreboard on top of the game screen +#include "cg_local.h" + + +#define SCOREBOARD_WIDTH ( 31 * BIGCHAR_WIDTH ) + +/* +================= +CG_DrawScoreboard +================= +*/ +static void CG_DrawClientScore( int x, int y, score_t *score, float *color, float fade ) { + char string[1024]; + vec3_t headAngles; + clientInfo_t *ci; + + if ( score->client < 0 || score->client >= cgs.maxclients ) { + Com_Printf( "Bad score->client: %i\n", score->client ); + return; + } + + ci = &cgs.clientinfo[score->client]; + + // draw the handicap or bot skill marker + if ( ci->botSkill > 0 && ci->botSkill <= 5 ) { + CG_DrawPic( 0, y - 8, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } else if ( ci->handicap < 100 ) { + Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); + CG_DrawSmallStringColor( 8, y, string, color ); + } + + // draw the wins / losses + if ( cgs.gametype == GT_TOURNAMENT ) { + Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); + CG_DrawSmallStringColor( x + SCOREBOARD_WIDTH + 2, y, string, color ); + } + + // draw the face + VectorClear( headAngles ); + headAngles[YAW] = 180; + + CG_DrawHead( x - ICON_SIZE, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, + score->client, headAngles ); + + if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { + CG_DrawFlagModel( x - ICON_SIZE - ICON_SIZE / 2, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, + TEAM_RED ); + } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) { + CG_DrawFlagModel( x - ICON_SIZE - ICON_SIZE / 2, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, + TEAM_BLUE ); + } + + // draw the score line + if ( score->ping == -1 ) { + Com_sprintf( string, sizeof( string ), + "connecting %s", ci->name ); + } else if ( ci->team == TEAM_SPECTATOR ) { + Com_sprintf( string, sizeof( string ), + "SPECT %4i %4i %s", score->ping, score->time, ci->name ); + } else { + Com_sprintf( string, sizeof( string ), + "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name ); + } + + // highlight your position + if ( score->client == cg.snap->ps.clientNum ) { + float hcolor[4]; + int rank; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR + || cgs.gametype >= GT_TEAM ) { + rank = -1; + } else { + rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; + } + + if ( rank == 0 ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + } else if ( rank == 1 ) { + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( rank == 2 ) { + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + } else { + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0.7; + } + + hcolor[3] = fade * 0.7; + CG_FillRect( x - 2, y, SCOREBOARD_WIDTH, BIGCHAR_HEIGHT + 1, hcolor ); + } + + CG_DrawBigString( x, y, string, fade ); + + // add the "ready" marker for intermission exiting + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { + CG_DrawBigStringColor( 0, y, "READY", color ); + } +} + +/* +================= +CG_TeamScoreboard +================= +*/ +static int CG_TeamScoreboard( int x, int y, team_t team, float fade ) { + int i; + score_t *score; + float color[4]; + int count; + int lineHeight; + clientInfo_t *ci; + + color[0] = color[1] = color[2] = 1.0; + color[3] = fade; + + count = 0; + lineHeight = 40; + // don't draw more than 9 rows + for ( i = 0 ; i < cg.numScores && count < 9 ; i++ ) { + score = &cg.scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != ci->team ) { + continue; + } + + CG_DrawClientScore( x, y + lineHeight * count, score, color, fade ); + count++; + } + + return y + count * lineHeight + 20; +} + +/* +================= +WM_DrawObjectives +================= +*/ +// NERVE - SMF +static int INFO_PLAYER_WIDTH = 200; +static int INFO_CLASS_WIDTH = 80; +static int INFO_SCORE_WIDTH = 50; +static int INFO_LATENCY_WIDTH = 60; +static int INFO_TOTAL_WIDTH = 0; +static int INFO_TEAM_HEIGHT = 24; +static int INFO_BORDER = 2; +static int INFO_LINE_HEIGHT = 30; + +int WM_DrawObjectives( int x, int y, int width, float fade ) { + const char *s, *buf, *str; + char teamstr[32]; + int i, num, strwidth, status; + int height, tempy, rows; + vec4_t hcolor; + int msec, mins, seconds, tens; // JPW NERVE + + rows = 7; + height = SMALLCHAR_HEIGHT * rows; + + hcolor[0] = hcolor[1] = hcolor[2] = 1; + hcolor[3] = 1.f; + trap_R_SetColor( hcolor ); + if ( cg.snap->ps.pm_type != PM_INTERMISSION ) { + CG_DrawPic( x - 10, y, width + 20, height + SMALLCHAR_HEIGHT + 10 + 1, trap_R_RegisterShaderNoMip( "ui_mp/assets/mp_score_objectives" ) ); + } + VectorSet( hcolor, 1, 1, 1 ); + trap_R_SetColor( NULL ); + + VectorSet( hcolor, ( 68.f / 255.f ), ( 0.f / 255.f ), ( 0.f / 255.f ) ); + hcolor[3] = 1 * fade; + + // draw header + if ( cg.snap->ps.pm_type != PM_INTERMISSION ) { + CG_DrawSmallString( x, y, CG_TranslateString( "Goals" ), fade ); + } + y += SMALLCHAR_HEIGHT + 3; + + // draw color bands + tempy = y; + + if ( cg.snap->ps.pm_type != PM_INTERMISSION ) { + for ( i = 0; i < rows; i++ ) { + hcolor[3] = fade * 0.3; + if ( i % 2 == 0 ) { + VectorSet( hcolor, ( 0.f / 255.f ), ( 113.f / 255.f ), ( 163.f / 255.f ) ); // LIGHT BLUE + } else { + VectorSet( hcolor, ( 0.f / 255.f ), ( 92.f / 255.f ), ( 133.f / 255.f ) ); // DARK BLUE + } + hcolor[3] = 0; + + CG_FillRect( x, y, width, SMALLCHAR_HEIGHT, hcolor ); + + y += SMALLCHAR_HEIGHT; + } + } + hcolor[3] = 1; + + y = tempy; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + const char *s, *buf, *shader = NULL, *flagshader = NULL, *nameshader = NULL; + + s = CG_ConfigString( CS_MULTI_MAPWINNER ); + buf = Info_ValueForKey( s, "winner" ); + + if ( atoi( buf ) == -1 ) { + str = "ITS A TIE!"; + } else if ( atoi( buf ) ) { + str = "ALLIES"; + shader = "ui_mp/assets/portraits/allies_win"; + flagshader = "ui_mp/assets/portraits/allies_win_flag.tga"; + nameshader = "ui_mp/assets/portraits/text_allies.tga"; + + if ( !cg.latchVictorySound ) { + cg.latchVictorySound = qtrue; + trap_S_StartLocalSound( trap_S_RegisterSound( "sound/multiplayer/music/l_complete_2.wav" ), CHAN_LOCAL_SOUND ); + } + } else { + str = "AXIS"; + shader = "ui_mp/assets/portraits/axis_win"; + flagshader = "ui_mp/assets/portraits/axis_win_flag.tga"; + nameshader = "ui_mp/assets/portraits/text_axis.tga"; + + if ( !cg.latchVictorySound ) { + cg.latchVictorySound = qtrue; + trap_S_StartLocalSound( trap_S_RegisterSound( "sound/multiplayer/music/s_stinglow.wav" ), CHAN_LOCAL_SOUND ); + } + } + + y += SMALLCHAR_HEIGHT * ( ( rows - 2 ) / 2 ); + + if ( flagshader ) { + CG_DrawPic( 10, 10, 210, 136, trap_R_RegisterShaderNoMip( flagshader ) ); + CG_DrawPic( 415, 10, 210, 136, trap_R_RegisterShaderNoMip( flagshader ) ); + } + + if ( shader ) { + CG_DrawPic( 229, 10, 182, 136, trap_R_RegisterShaderNoMip( shader ) ); + } + if ( nameshader ) { + CG_DrawPic( 50, 50, 127, 64, trap_R_RegisterShaderNoMip( nameshader ) ); + CG_DrawPic( 455, 50, 127, 64, trap_R_RegisterShaderNoMip( "ui_mp/assets/portraits/text_win.tga" ) ); + } + return y; + } +// JPW NERVE -- mission time & reinforce time + else { + tempy = y; + y += SMALLCHAR_HEIGHT * ( rows - 1 ); + msec = ( cgs.timelimit * 60.f * 1000.f ) - ( cg.time - cgs.levelStartTime ); + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + if ( msec < 0 ) { + s = va( "%s %s", CG_TranslateString( "Mission time:" ), CG_TranslateString( "Sudden Death" ) ); + } else { + s = va( "%s %2.0f:%i%i", CG_TranslateString( "Mission time:" ), (float)mins, tens, seconds ); // float cast to line up with reinforce time + + } + CG_DrawSmallString( x,y,s,fade ); + + if ( cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_RED ) { + msec = cg_redlimbotime.integer - ( cg.time % cg_redlimbotime.integer ); + } else if ( cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_BLUE ) { + msec = cg_bluelimbotime.integer - ( cg.time % cg_bluelimbotime.integer ); + } else { // no team (spectator mode) + msec = 0; + } + + if ( msec ) { + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + s = va( "%s %2.0f:%i%i", CG_TranslateString( "Reinforce time:" ), (float)mins, tens, seconds ); + CG_DrawSmallString( x + 425,y,s,fade ); + } + + // NERVE - SMF + if ( cgs.gametype == GT_WOLF_STOPWATCH ) { + int w; + s = va( "%s %i", CG_TranslateString( "Stopwatch Round" ), cgs.currentRound + 1 ); + w = CG_DrawStrlen( s ) * SMALLCHAR_WIDTH; + CG_DrawSmallString( x + 300 - w / 2,y,s,fade ); + } + // -NERVE - SMF + + y = tempy; + } +// jpw + + // determine character's team + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + strcpy( teamstr, "short_axis_desc" ); + } else { + strcpy( teamstr, "short_allied_desc" ); + } + + s = CG_ConfigString( CS_MULTI_INFO ); + buf = Info_ValueForKey( s, "numobjectives" ); + + if ( buf && atoi( buf ) ) { + num = atoi( buf ); + + for ( i = 0; i < num; i++ ) { + s = CG_ConfigString( CS_MULTI_OBJECTIVE1 + i ); + buf = Info_ValueForKey( s, teamstr ); + if ( !buf || !strlen( buf ) ) { + str = va( "%s %i", CG_TranslateString( "Objective" ), i + 1 ); + } else { + str = va( "%s", CG_TranslateString( buf ) ); + } + + // draw text + strwidth = CG_DrawStrlen( str ) * SMALLCHAR_WIDTH; + CG_DrawSmallString( x + width / 2 - strwidth / 2 - 12, y, str, fade ); + + // draw status flags + s = CG_ConfigString( CS_MULTI_OBJ1_STATUS + i ); + status = atoi( Info_ValueForKey( s, "status" ) ); + + if ( status == 0 ) { + CG_DrawPic( x, y + 1, 24, 14, trap_R_RegisterShaderNoMip( "ui_mp/assets/ger_flag.tga" ) ); + } else if ( status == 1 ) { + CG_DrawPic( x, y + 1, 24, 14, trap_R_RegisterShaderNoMip( "ui_mp/assets/usa_flag.tga" ) ); + } + + y += SMALLCHAR_HEIGHT; + } + } + + return y; +} + +static void WM_DrawClientScore( int x, int y, score_t *score, float *color, float fade ) { + int maxchars, offset; + float tempx; + vec4_t hcolor; + clientInfo_t *ci; + + if ( y + SMALLCHAR_HEIGHT >= 470 ) { + return; + } + + ci = &cgs.clientinfo[score->client]; + + if ( score->client == cg.snap->ps.clientNum ) { + tempx = x; + + hcolor[3] = fade * 0.3; + VectorSet( hcolor, 0.4452, 0.1172, 0.0782 ); // DARK-RED + + CG_FillRect( tempx, y + 1, INFO_PLAYER_WIDTH - INFO_BORDER, SMALLCHAR_HEIGHT - 1, hcolor ); + tempx += INFO_PLAYER_WIDTH; + + if ( ci->team == TEAM_SPECTATOR ) { + int width; + width = INFO_CLASS_WIDTH + INFO_SCORE_WIDTH + INFO_LATENCY_WIDTH; + + CG_FillRect( tempx, y + 1, width - INFO_BORDER, SMALLCHAR_HEIGHT - 1, hcolor ); + tempx += width; + } else { + CG_FillRect( tempx, y + 1, INFO_CLASS_WIDTH - INFO_BORDER, SMALLCHAR_HEIGHT - 1, hcolor ); + tempx += INFO_CLASS_WIDTH; + + CG_FillRect( tempx, y + 1, INFO_SCORE_WIDTH - INFO_BORDER, SMALLCHAR_HEIGHT - 1, hcolor ); + tempx += INFO_SCORE_WIDTH; + + CG_FillRect( tempx, y + 1, INFO_LATENCY_WIDTH - INFO_BORDER, SMALLCHAR_HEIGHT - 1, hcolor ); + tempx += INFO_LATENCY_WIDTH; + } + } + + tempx = x; + + // DHM - Nerve + VectorSet( hcolor, 1, 1, 1 ); + hcolor[3] = fade; + + maxchars = 17; + offset = 0; + + if ( ci->team != TEAM_SPECTATOR ) { + if ( ci->powerups & ( ( 1 << PW_REDFLAG ) | ( 1 << PW_BLUEFLAG ) ) ) { + CG_DrawPic( tempx - 4, y - 4, 24, 24, trap_R_RegisterShader( "models/multiplayer/treasure/treasure" ) ); + offset += 16; + tempx += 16; + maxchars -= 2; + } + + // draw the skull icon if out of lives + if ( score->respawnsLeft == -2 ) { + CG_DrawPic( tempx, y, 18, 18, cgs.media.scoreEliminatedShader ); + offset += 18; + tempx += 18; + maxchars -= 2; + } + } + + // draw name + CG_DrawStringExt( tempx, y, ci->name, hcolor, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, maxchars ); + tempx += INFO_PLAYER_WIDTH - offset; + // dhm - nerve + + if ( ci->team == TEAM_SPECTATOR ) { + const char *s; + int w, totalwidth; + + totalwidth = INFO_CLASS_WIDTH + INFO_SCORE_WIDTH + INFO_LATENCY_WIDTH - 8; + + s = CG_TranslateString( "^3SPECTATOR" ); + w = CG_DrawStrlen( s ) * SMALLCHAR_WIDTH; + + CG_DrawSmallString( tempx + totalwidth - w, y, s, fade ); + return; + } else if ( cg.snap->ps.persistant[PERS_TEAM] == ci->team ) { + int val = score->playerClass; // cg_entities[ ci->clientNum ].currentState.teamNum; + const char *s; + + if ( val == 0 ) { + s = "Soldr"; + } else if ( val == 1 ) { + s = "Medic"; + } else if ( val == 2 ) { + s = "Engr"; + } else if ( val == 3 ) { + s = "Lieut"; + } else { + s = ""; + } + + CG_DrawSmallString( tempx, y, CG_TranslateString( s ), fade ); + } + tempx += INFO_CLASS_WIDTH; + + CG_DrawSmallString( tempx, y, va( "%4i", score->score ), fade ); + tempx += INFO_SCORE_WIDTH; + + CG_DrawSmallString( tempx, y, va( "%4i", score->ping ), fade ); + tempx += INFO_LATENCY_WIDTH; +} + +const char* WM_TimeToString( float msec ) { + int mins, seconds, tens; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + return va( "%i:%i%i", mins, tens, seconds ); +} + +static int WM_DrawInfoLine( int x, int y, float fade ) { + int w, defender, winner; + const char *s; + + if ( cg.snap->ps.pm_type != PM_INTERMISSION ) { + return y; + } + + w = 300; + CG_DrawPic( 320 - w / 2, y, w, INFO_LINE_HEIGHT, trap_R_RegisterShaderNoMip( "ui_mp/assets/mp_line_strip.tga" ) ); + + s = CG_ConfigString( CS_MULTI_INFO ); + defender = atoi( Info_ValueForKey( s, "defender" ) ); + + s = CG_ConfigString( CS_MULTI_MAPWINNER ); + winner = atoi( Info_ValueForKey( s, "winner" ) ); + + if ( cgs.currentRound ) { + // first round + s = va( CG_TranslateString( "Clock is now set to %s!" ), WM_TimeToString( cgs.nextTimeLimit * 60.f * 1000.f ) ); + } else { + // second round + if ( !defender ) { + if ( winner != defender ) { + s = "Allies sucessfully beat the clock!"; + } else { + s = "Allies couldn't beat the clock!"; + } + } else { + if ( winner != defender ) { + s = "Axis sucessfully beat the clock!"; + } else { + s = "Axis couldn't beat the clock!"; + } + } + + s = CG_TranslateString( s ); + } + + w = CG_DrawStrlen( s ) * SMALLCHAR_WIDTH; + + CG_DrawSmallString( 320 - w / 2, ( y + INFO_LINE_HEIGHT / 2 ) - SMALLCHAR_HEIGHT / 2, s, fade ); + return y + INFO_LINE_HEIGHT + 10; +} + +static int WM_TeamScoreboard( int x, int y, team_t team, float fade, int maxrows ) { + vec4_t hcolor; + float tempx, tempy; + int height, width; + int i, rows; + + height = SMALLCHAR_HEIGHT * maxrows; + width = INFO_PLAYER_WIDTH + INFO_CLASS_WIDTH + INFO_SCORE_WIDTH + INFO_LATENCY_WIDTH; + rows = height / SMALLCHAR_HEIGHT - 2; + + hcolor[0] = hcolor[1] = hcolor[2] = 1; + hcolor[3] = 1.f; + trap_R_SetColor( hcolor ); + CG_DrawPic( x - 15, y - 4, width + 30, height + 1, trap_R_RegisterShaderNoMip( "ui_mp/assets/mp_score_team.tga" ) ); + trap_R_SetColor( NULL ); + + if ( team == TEAM_RED ) { + VectorSet( hcolor, ( 68.f / 255.f ), ( 0.f / 255.f ), ( 0.f / 255.f ) ); + } else if ( team == TEAM_BLUE ) { + VectorSet( hcolor, ( 0.f / 255.f ), ( 45.f / 255.f ), ( 68.f / 255.f ) ); + } + + hcolor[3] = 1 * fade; + + // draw header + if ( team == TEAM_RED ) { + CG_DrawSmallString( x, y, va( "%s [%d] (%d %s)", CG_TranslateString( "Axis" ), cg.teamScores[0], cg.teamPlayers[team], CG_TranslateString( "players" ) ), fade ); + } else if ( team == TEAM_BLUE ) { + CG_DrawSmallString( x, y, va( "%s [%d] (%d %s)", CG_TranslateString( "Allies" ), cg.teamScores[1], cg.teamPlayers[team], CG_TranslateString( "players" ) ), fade ); + } + y += SMALLCHAR_HEIGHT + 4; + + // save off y val + tempy = y; + + // draw color bands + for ( i = 0; i < rows; i++ ) { + hcolor[3] = fade * 0.3; + if ( i % 2 == 0 ) { + VectorSet( hcolor, ( 0.f / 255.f ), ( 163.f / 255.f ), ( 113.f / 255.f ) ); // LIGHT BLUE + } else { + VectorSet( hcolor, ( 0.f / 255.f ), ( 133.f / 255.f ), ( 92.f / 255.f ) ); // DARK BLUE + } + hcolor[3] = 0; + + CG_FillRect( x, y, width, SMALLCHAR_HEIGHT, hcolor ); + + y += SMALLCHAR_HEIGHT; + } + hcolor[3] = 1; + + y = tempy; + + tempx = x; + + // draw player info headings + CG_DrawSmallString( tempx, y, CG_TranslateString( "Name" ), fade ); + tempx += INFO_PLAYER_WIDTH; + + CG_DrawSmallString( tempx, y, CG_TranslateString( "Class" ), fade ); + tempx += INFO_CLASS_WIDTH; + + CG_DrawSmallString( tempx, y, CG_TranslateString( "Score" ), fade ); + tempx += INFO_SCORE_WIDTH; + + CG_DrawSmallString( tempx, y, CG_TranslateString( "Ping" ), fade ); + tempx += INFO_LATENCY_WIDTH; + + y += SMALLCHAR_HEIGHT; + + // draw player info + VectorSet( hcolor, 1, 1, 1 ); + hcolor[3] = fade; + + cg.teamPlayers[team] = 0; // JPW NERVE + for ( i = 0; i < cg.numScores; i++ ) { + if ( team != cgs.clientinfo[ cg.scores[i].client ].team ) { + continue; + } + cg.teamPlayers[team]++; // JPW NERVE // one frame latency, but who cares? + WM_DrawClientScore( x, y, &cg.scores[i], hcolor, fade ); + y += SMALLCHAR_HEIGHT; + } + + // draw spectators + y += SMALLCHAR_HEIGHT; + + for ( i = 0; i < cg.numScores; i++ ) { + if ( cgs.clientinfo[ cg.scores[i].client ].team != TEAM_SPECTATOR ) { + continue; + } + if ( team == TEAM_RED && ( i % 2 ) ) { + continue; + } + if ( team == TEAM_BLUE && ( ( i + 1 ) % 2 ) ) { + continue; + } + + WM_DrawClientScore( x, y, &cg.scores[i], hcolor, fade ); + y += SMALLCHAR_HEIGHT; + } + + return y; +} +// -NERVE - SMF + +/* +================= +CG_DrawScoreboard + +Draw the normal in-game scoreboard +================= +*/ +qboolean CG_DrawScoreboard( void ) { + int x = 0, y = 0, w; + float fade; + float *fadeColor; + char *s; + + // don't draw amuthing if the menu or console is up + if ( cg_paused.integer ) { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg.warmup && !cg.showScores ) { + return qfalse; + } + + // NERVE - SMF - added mp wolf check + if ( cg.showScores || ( cg.predictedPlayerState.pm_type == PM_DEAD && cgs.gametype < GT_WOLF ) || + cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); + + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg.deferredPlayerLoading = 0; + cg.killerName[0] = 0; + return qfalse; + } + fade = *fadeColor; + } + + + // fragged by ... line + if ( cg.killerName[0] ) { + s = va( "Killed by %s", cg.killerName ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + } + + // current rank + + //----(SA) enclosed this so it doesn't draw for SP + if ( cgs.gametype != GT_SINGLE_PLAYER && cgs.gametype < GT_WOLF ) { // NERVE - SMF - added wolf multiplayer check + if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + if ( cgs.gametype < GT_TEAM ) { + s = va( "%s place with %i", + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; + CG_DrawBigString( x, y, s, fade ); + } else { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va( "Teams are tied at %i", cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va( "Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va( "Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] ); + } + + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; + CG_DrawBigString( x, y, s, fade ); + } + } + + // scoreboard + x = 320 - SCOREBOARD_WIDTH / 2; + y = 86; + + #if 0 + CG_DrawBigStringColor( x, y, "SCORE PING TIME NAME", fadeColor ); + CG_DrawBigStringColor( x, y + 12, "----- ---- ---- ---------------", fadeColor ); + #endif + CG_DrawPic( x + 1 * 16, y, 64, 32, cgs.media.scoreboardScore ); + CG_DrawPic( x + 6 * 16 + 8, y, 64, 32, cgs.media.scoreboardPing ); + CG_DrawPic( x + 11 * 16 + 8, y, 64, 32, cgs.media.scoreboardTime ); + CG_DrawPic( x + 16 * 16, y, 64, 32, cgs.media.scoreboardName ); + + y += 32; + } + +//----(SA) added + + // Secrets + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + s = "Secrets: 0/12"; + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; +// CG_DrawBigStringColor( x, y, s, fadeColor ); + } + +//----(SA) end + + // NERVE - SMF + if ( cgs.gametype >= GT_WOLF ) { + INFO_PLAYER_WIDTH = 140; + INFO_SCORE_WIDTH = 50; + INFO_CLASS_WIDTH = 50; + INFO_LATENCY_WIDTH = 40; + INFO_TEAM_HEIGHT = 24; + INFO_BORDER = 2; + INFO_TOTAL_WIDTH = INFO_PLAYER_WIDTH + INFO_CLASS_WIDTH + INFO_SCORE_WIDTH + INFO_LATENCY_WIDTH; + + x = 20; + y = 10; + + WM_DrawObjectives( x, y, 595, fade ); + + if ( cgs.gametype == GT_WOLF_STOPWATCH && ( cg.snap->ps.pm_type == PM_INTERMISSION ) ) { + y = WM_DrawInfoLine( x, 155, fade ); + + WM_TeamScoreboard( x, y, TEAM_RED, fade, 18 ); + x = 335; + WM_TeamScoreboard( x, y, TEAM_BLUE, fade, 18 ); + } else { + y = 155; + + WM_TeamScoreboard( x, y, TEAM_RED, fade, 20 ); + x = 335; + WM_TeamScoreboard( x, y, TEAM_BLUE, fade, 20 ); + } + } + // -NERVE - SMF + else if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + if ( cg.teamScores[0] >= cg.teamScores[1] ) { + y = CG_TeamScoreboard( x, y, TEAM_RED, fade ); + y = CG_TeamScoreboard( x, y, TEAM_BLUE, fade ); + } else { + y = CG_TeamScoreboard( x, y, TEAM_BLUE, fade ); + y = CG_TeamScoreboard( x, y, TEAM_RED, fade ); + } + y = CG_TeamScoreboard( x, y, TEAM_SPECTATOR, fade ); + + } else if ( cgs.gametype != GT_SINGLE_PLAYER ) { //----(SA) modified + // + // free for all scoreboard + // + y = CG_TeamScoreboard( x, y, TEAM_FREE, fade ); + y = CG_TeamScoreboard( x, y, TEAM_SPECTATOR, fade ); + } + + // load any models that have been deferred + if ( ++cg.deferredPlayerLoading > 1 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +} + +//================================================================================ + +/* +================ +CG_CenterGiantLine +================ +*/ +/* +// TTimo: unused +static void CG_CenterGiantLine( float y, const char *string ) { + float x; + vec4_t color; + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); + + CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); +} +*/ + +/* +================= +CG_DrawTourneyScoreboard + +Draw the oversize scoreboard for tournements +================= +*/ +void CG_DrawTourneyScoreboard( void ) { + vec4_t color; + int x,y; + + // request more scores regularly + if ( cg.scoresRequestTime + 2000 < cg.time ) { + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + } + + // draw the dialog background + color[0] = color[1] = color[2] = 0; + color[3] = 1; + CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); + + if ( cgs.gametype >= GT_WOLF ) { + INFO_PLAYER_WIDTH = 140; + INFO_SCORE_WIDTH = 50; + INFO_CLASS_WIDTH = 50; + INFO_LATENCY_WIDTH = 40; + INFO_TEAM_HEIGHT = 24; + INFO_BORDER = 2; + INFO_TOTAL_WIDTH = INFO_PLAYER_WIDTH + INFO_CLASS_WIDTH + INFO_SCORE_WIDTH + INFO_LATENCY_WIDTH; + + x = 20; + y = 10; + + WM_DrawObjectives( x, y, 595, 1.f ); + + if ( cgs.gametype == GT_WOLF_STOPWATCH && ( cg.snap->ps.pm_type == PM_INTERMISSION ) ) { + y = WM_DrawInfoLine( x, 155, 1.f ); + + WM_TeamScoreboard( x, y, TEAM_RED, 1.f, 18 ); + x = 335; + WM_TeamScoreboard( x, y, TEAM_BLUE, 1.f, 18 ); + } else { + y = 155; + + WM_TeamScoreboard( x, y, TEAM_RED, 1.f, 20 ); + x = 335; + WM_TeamScoreboard( x, y, TEAM_BLUE, 1.f, 20 ); + } + } +} + diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c new file mode 100644 index 0000000..4345041 --- /dev/null +++ b/src/cgame/cg_servercmds.c @@ -0,0 +1,1623 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +// cg_servercmds.c -- reliably sequenced text commands sent by the server +// these are processed at snapshot transition time, so there will definately +// be a valid snapshot this frame + +#include "cg_local.h" +#include "../ui/ui_shared.h" // bk001205 - for Q3_ui as well + +void CG_StartShakeCamera( float param ); // NERVE - SMF + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) { + int i, powerups; + + cg.numScores = atoi( CG_Argv( 1 ) ); + if ( cg.numScores > MAX_CLIENTS ) { + cg.numScores = MAX_CLIENTS; + } + + cg.teamScores[0] = atoi( CG_Argv( 2 ) ); + cg.teamScores[1] = atoi( CG_Argv( 3 ) ); + + memset( cg.scores, 0, sizeof( cg.scores ) ); + for ( i = 0 ; i < cg.numScores ; i++ ) { + // + cg.scores[i].client = atoi( CG_Argv( i * 8 + 4 ) ); + cg.scores[i].score = atoi( CG_Argv( i * 8 + 5 ) ); + cg.scores[i].ping = atoi( CG_Argv( i * 8 + 6 ) ); + cg.scores[i].time = atoi( CG_Argv( i * 8 + 7 ) ); + cg.scores[i].scoreFlags = atoi( CG_Argv( i * 8 + 8 ) ); + powerups = atoi( CG_Argv( i * 8 + 9 ) ); + cg.scores[i].playerClass = atoi( CG_Argv( i * 8 + 10 ) ); // NERVE - SMF + cg.scores[i].respawnsLeft = atoi( CG_Argv( i * 8 + 11 ) ); // NERVE - SMF + // DHM - Nerve :: the following parameters are not sent by server + /* + cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); + cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); + cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); + cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); + cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); + cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); + cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); + cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17)); + */ + + if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { + cg.scores[i].client = 0; + } + cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; + cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; + + cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; + } +#ifdef MISSIONPACK + CG_SetScoreSelection( NULL ); +#endif + +} + +/* +================= +CG_ParseTeamInfo + +================= +*/ +static void CG_ParseTeamInfo( void ) { + int i; + int client; + + // NERVE - SMF + cg.identifyClientNum = atoi( CG_Argv( 1 ) ); + cg.identifyClientHealth = atoi( CG_Argv( 2 ) ); + // -NERVE - SMF + + numSortedTeamPlayers = atoi( CG_Argv( 3 ) ); + + for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { + client = atoi( CG_Argv( i * 5 + 4 ) ); + + sortedTeamPlayers[i] = client; + + cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 5 + 5 ) ); + cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 5 + 6 ) ); + cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 5 + 7 ) ); + + cg_entities[ client ].currentState.teamNum = atoi( CG_Argv( i * 5 + 8 ) ); + } +} + + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) { + const char *info; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); + cgs.antilag = atoi( Info_ValueForKey( info, "g_antilag" ) ); + if ( !cgs.localServer ) { + trap_Cvar_Set( "g_gametype", va( "%i", cgs.gametype ) ); + trap_Cvar_Set( "g_antilag", va( "%i", cgs.antilag ) ); + } + cgs.dmflags = 0; //atoi( Info_ValueForKey( info, "dmflags" ) ); // NERVE - SMF - no longer serverinfo + cgs.teamflags = 0; //atoi( Info_ValueForKey( info, "teamflags" ) ); + cgs.fraglimit = 0; //atoi( Info_ValueForKey( info, "fraglimit" ) ); // NERVE - SMF - no longer serverinfo + cgs.capturelimit = 0; //atoi( Info_ValueForKey( info, "capturelimit" ) ); // NERVE - SMF - no longer serverinfo + cgs.timelimit = atof( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); + +// JPW NERVE +// prolly should parse all CS_SERVERINFO keys automagically, but I don't want to break anything that might be improperly set for wolf SP, so I'm just parsing MP relevant stuff here + trap_Cvar_Set( "g_medicChargeTime",Info_ValueForKey( info,"g_medicChargeTime" ) ); + trap_Cvar_Set( "g_engineerChargeTime",Info_ValueForKey( info,"g_engineerChargeTime" ) ); + trap_Cvar_Set( "g_soldierChargeTime",Info_ValueForKey( info,"g_soldierChargeTime" ) ); + trap_Cvar_Set( "g_LTChargeTime",Info_ValueForKey( info,"g_LTChargeTime" ) ); + trap_Cvar_Set( "g_redlimbotime",Info_ValueForKey( info,"g_redlimbotime" ) ); + // DHM - TEMP FIX + cg_redlimbotime.integer = atoi( Info_ValueForKey( info,"g_redlimbotime" ) ); + //trap_Cvar_Set("g_bluelimbotime",Info_ValueForKey(info,"g_bluelimbotime")); + cg_bluelimbotime.integer = atoi( Info_ValueForKey( info,"g_bluelimbotime" ) ); +// jpw + + cgs.minclients = atoi( Info_ValueForKey( info, "g_minGameClients" ) ); // NERVE - SMF + + // TTimo - make this available for ingame_callvote + trap_Cvar_Set( "cg_ui_voteFlags", Info_ValueForKey( info, "g_voteFlags" ) ); +} + +/* +================== +CG_ParseWolfinfo + +NERVE - SMF +================== +*/ +void CG_ParseWolfinfo( void ) { + const char *info; + + info = CG_ConfigString( CS_WOLFINFO ); + + cgs.currentRound = atoi( Info_ValueForKey( info, "g_currentRound" ) ); + cgs.nextTimeLimit = atof( Info_ValueForKey( info, "g_nextTimeLimit" ) ); + cgs.gamestate = atoi( Info_ValueForKey( info, "gamestate" ) ); + if ( !cgs.localServer ) { + trap_Cvar_Set( "gamestate", va( "%i", cgs.gamestate ) ); + } +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) { + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg.warmupCount = -1; + + if ( warmup == 0 && cg.warmup ) { + + } else if ( warmup > 0 && cg.warmup <= 0 ) { + trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER ); + } + + cg.warmup = warmup; +} + +/* +===================== +CG_ParseScreenFade +===================== +*/ +static void CG_ParseScreenFade( void ) { + const char *info; + char *token; + + info = CG_ConfigString( CS_SCREENFADE ); + + token = COM_Parse( (char **)&info ); + cgs.fadeAlpha = atof( token ); + + token = COM_Parse( (char **)&info ); + cgs.fadeStartTime = atoi( token ); + token = COM_Parse( (char **)&info ); + cgs.fadeDuration = atoi( token ); + + if ( cgs.fadeStartTime + cgs.fadeDuration < cg.time ) { + cgs.fadeAlphaCurrent = cgs.fadeAlpha; + } +} + + +/* +============== +CG_ParseFog + float near dist + float far dist + float density + float[3] r,g,b + int time +============== +*/ +static void CG_ParseFog( void ) { + const char *info; + char *token; + float ne, fa, r, g, b, density; + int time; + + info = CG_ConfigString( CS_FOGVARS ); + + token = COM_Parse( (char **)&info ); ne = atof( token ); + token = COM_Parse( (char **)&info ); fa = atof( token ); + token = COM_Parse( (char **)&info ); density = atof( token ); + token = COM_Parse( (char **)&info ); r = atof( token ); + token = COM_Parse( (char **)&info ); g = atof( token ); + token = COM_Parse( (char **)&info ); b = atof( token ); + token = COM_Parse( (char **)&info ); time = atoi( token ); + + if ( fa ) { // far of '0' from a target_fog means "return to map fog" + trap_R_SetFog( FOG_SERVER, (int)ne, (int)fa, r, g, b, density + .1 ); + trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_SERVER, time, 0, 0, 0, 0 ); + } else { + trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP, time, 0, 0, 0, 0 ); + } +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) { +#ifdef MISSIONPACK + const char *s; +#endif + + cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); + cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); + cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.redflag = s[0] - '0'; + cgs.blueflag = s[1] - '0'; + } else if ( cgs.gametype == GT_1FCTF ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.flagStatus = s[0] - '0'; + } +#endif + cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); +} + +/* +===================== +CG_ShaderStateChanged +===================== +*/ +void CG_ShaderStateChanged( void ) { + char originalShader[MAX_QPATH]; + char newShader[MAX_QPATH]; + char timeOffset[16]; + const char *o; + char *n,*t; + + o = CG_ConfigString( CS_SHADERSTATE ); + while ( o && *o ) { + n = strstr( o, "=" ); + if ( n && *n ) { + strncpy( originalShader, o, n - o ); + originalShader[n - o] = 0; + n++; + t = strstr( n, ":" ); + if ( t && *t ) { + strncpy( newShader, n, t - n ); + newShader[t - n] = 0; + } else { + break; + } + t++; + o = strstr( t, "@" ); + if ( o ) { + strncpy( timeOffset, t, o - t ); + timeOffset[o - t] = 0; + o++; + trap_R_RemapShader( originalShader, newShader, timeOffset ); + } + } else { + break; + } + } +} + +/* +================ +CG_ConfigStringModified + +================ +*/ +static void CG_ConfigStringModified( void ) { + const char *str; + int num; + + num = atoi( CG_Argv( 1 ) ); + + // get the gamestate from the client system, which will have the + // new configstring already integrated + trap_GetGameState( &cgs.gameState ); + + // look up the individual string that was modified + str = CG_ConfigString( num ); + + // do something with it if necessary + if ( num == CS_MUSIC ) { + CG_StartMusic(); + } else if ( num == CS_SERVERINFO ) { + CG_ParseServerinfo(); + } else if ( num == CS_WARMUP ) { + CG_ParseWarmup(); + } else if ( num == CS_WOLFINFO ) { // NERVE - SMF + CG_ParseWolfinfo(); + } else if ( num == CS_SCORES1 ) { + cgs.scores1 = atoi( str ); + } else if ( num == CS_SCORES2 ) { + cgs.scores2 = atoi( str ); + } else if ( num == CS_LEVEL_START_TIME ) { + cgs.levelStartTime = atoi( str ); + } else if ( num == CS_VOTE_TIME ) { + cgs.voteTime = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_YES ) { + cgs.voteYes = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_NO ) { + cgs.voteNo = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_STRING ) { + Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); +#if 0 + trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); + } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1 ) { + cgs.teamVoteTime[num - CS_TEAMVOTE_TIME] = atoi( str ); + cgs.teamVoteModified[num - CS_TEAMVOTE_TIME] = qtrue; + } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 ) { + cgs.teamVoteYes[num - CS_TEAMVOTE_YES] = atoi( str ); + cgs.teamVoteModified[num - CS_TEAMVOTE_YES] = qtrue; + } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 ) { + cgs.teamVoteNo[num - CS_TEAMVOTE_NO] = atoi( str ); + cgs.teamVoteModified[num - CS_TEAMVOTE_NO] = qtrue; + } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 ) { + Q_strncpyz( cgs.teamVoteString[num - CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); + trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); +#endif + } else if ( num == CS_INTERMISSION ) { + cg.intermissionStarted = atoi( str ); + } else if ( num == CS_SCREENFADE ) { + CG_ParseScreenFade(); + } else if ( num == CS_FOGVARS ) { + CG_ParseFog(); + } else if ( num >= CS_MODELS && num < CS_MODELS + MAX_MODELS ) { + cgs.gameModels[ num - CS_MODELS ] = trap_R_RegisterModel( str ); + } else if ( num >= CS_SOUNDS && num < CS_SOUNDS + MAX_MODELS ) { + if ( str[0] != '*' ) { // player specific sounds don't register here + + // Ridah, register sound scripts seperately + if ( !strstr( str, ".wav" ) ) { + CG_SoundScriptPrecache( str ); + } else { + cgs.gameSounds[ num - CS_SOUNDS] = trap_S_RegisterSound( str ); + } + + } + } else if ( num >= CS_PLAYERS && num < CS_PLAYERS + MAX_CLIENTS ) { + CG_NewClientInfo( num - CS_PLAYERS ); + } + // Rafael particle configstring + else if ( num >= CS_PARTICLES && num < CS_PARTICLES + MAX_PARTICLES_AREAS ) { + CG_NewParticleArea( num ); + } +//----(SA) have not reached this code yet so I don't know if I really need this here + else if ( num >= CS_DLIGHTS && num < CS_DLIGHTS + MAX_DLIGHTS ) { +// CG_Printf(">>>>>>>>>>>got configstring for dlight: %d\ntell Sherman!!!!!!!!!!", num-CS_DLIGHTS); +//----(SA) + } else if ( num == CS_SHADERSTATE ) { + CG_ShaderStateChanged(); + } +} + + +/* +======================= +CG_AddToTeamChat + +======================= +*/ +static void CG_AddToTeamChat( const char *str ) { + int len; + char *p, *ls; + int lastcolor; + int chatHeight; + + if ( cg_teamChatHeight.integer < TEAMCHAT_HEIGHT ) { + chatHeight = cg_teamChatHeight.integer; + } else { + chatHeight = TEAMCHAT_HEIGHT; + } + + if ( chatHeight <= 0 || cg_teamChatTime.integer <= 0 ) { + // team chat disabled, dump into normal chat + cgs.teamChatPos = cgs.teamLastChatPos = 0; + return; + } + + len = 0; + + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + + lastcolor = '7'; + + ls = NULL; + while ( *str ) { + if ( len > TEAMCHAT_WIDTH - 1 ) { + if ( ls ) { + str -= ( p - ls ); + str++; + p -= ( p - ls ); + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + + cgs.teamChatPos++; + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + *p++ = Q_COLOR_ESCAPE; + *p++ = lastcolor; + len = 0; + ls = NULL; + } + + if ( Q_IsColorString( str ) ) { + *p++ = *str++; + lastcolor = *str; + *p++ = *str++; + continue; + } + if ( *str == ' ' ) { + ls = p; + } + *p++ = *str++; + len++; + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + cgs.teamChatPos++; + + if ( cgs.teamChatPos - cgs.teamLastChatPos > chatHeight ) { + cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; + } +} + +/* +======================= +CG_AddToNotify + +======================= +*/ +void CG_AddToNotify( const char *str ) { + int len; + char *p, *ls; + int lastcolor; + int chatHeight; + float notifytime; + char var[MAX_TOKEN_CHARS]; + + trap_Cvar_VariableStringBuffer( "con_notifytime", var, sizeof( var ) ); + notifytime = atof( var ) * 1000; + + chatHeight = NOTIFY_HEIGHT; + + if ( chatHeight <= 0 || notifytime <= 0 ) { + // team chat disabled, dump into normal chat + cgs.notifyPos = cgs.notifyLastPos = 0; + return; + } + + len = 0; + + p = cgs.notifyMsgs[cgs.notifyPos % chatHeight]; + *p = 0; + + lastcolor = '7'; + + ls = NULL; + while ( *str ) { + if ( len > NOTIFY_WIDTH - 1 || ( *str == '\n' && ( *( str + 1 ) != 0 ) ) ) { + if ( ls ) { + str -= ( p - ls ); + str++; + p -= ( p - ls ); + } + *p = 0; + + cgs.notifyMsgTimes[cgs.notifyPos % chatHeight] = cg.time; + + cgs.notifyPos++; + p = cgs.notifyMsgs[cgs.notifyPos % chatHeight]; + *p = 0; + *p++ = Q_COLOR_ESCAPE; + *p++ = lastcolor; + len = 0; + ls = NULL; + } + + if ( Q_IsColorString( str ) ) { + *p++ = *str++; + lastcolor = *str; + *p++ = *str++; + continue; + } + if ( *str == ' ' ) { + ls = p; + } + while ( *str == '\n' ) { + // TTimo gcc warning: value computed is not used + // was *str++; + str++; + } + + if ( *str ) { + *p++ = *str++; + len++; + } + } + *p = 0; + + cgs.notifyMsgTimes[cgs.notifyPos % chatHeight] = cg.time; + cgs.notifyPos++; + + if ( cgs.notifyPos - cgs.notifyLastPos > chatHeight ) { + cgs.notifyLastPos = cgs.notifyPos - chatHeight; + } +} + +/* +=============== +CG_SendMoveSpeed +=============== +*/ +void CG_SendMoveSpeed( animation_t *animList, int numAnims, char *modelName ) { + animation_t *anim; + int i; + char text[10000]; + + if ( !cgs.localServer ) { + return; + } + + text[0] = 0; + Q_strcat( text, sizeof( text ), modelName ); + + for ( i = 0, anim = animList; i < numAnims; i++, anim++ ) { + if ( anim->moveSpeed <= 0 ) { + continue; + } + + // add this to the list + Q_strcat( text, sizeof( text ), va( " %s %i", anim->name, anim->moveSpeed ) ); + } + + // send the movespeeds to the server + trap_SendMoveSpeedsToGame( 0, text ); +} + +/* +=============== +CG_SendMoveSpeeds + + send moveSpeeds for all unique models +=============== +*/ +void CG_SendMoveSpeeds( void ) { + int i; + animModelInfo_t *modelInfo; + + for ( i = 0, modelInfo = cgs.animScriptData.modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, modelInfo++ ) { + if ( !modelInfo->modelname[0] ) { + continue; + } + + // send this model + CG_SendMoveSpeed( modelInfo->animations, modelInfo->numAnimations, modelInfo->modelname ); + } + +} + + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A tournement restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +static void CG_MapRestart( void ) { + int i; + if ( cg_showmiss.integer ) { + CG_Printf( "CG_MapRestart\n" ); + } + + memset( &cg.lastWeapSelInBank[0], 0, MAX_WEAP_BANKS * sizeof( int ) ); // clear weapon bank selections + + cg.centerPrintTime = 0; // reset centerprint counter so previous messages don't re-appear + cg.itemPickupTime = 0; // reset item pickup counter so previous messages don't re-appear + cg.cursorHintFade = 0; // reset cursor hint timer + + // DHM - Nerve :: Reset complaint system + cgs.complaintClient = -1; + cgs.complaintEndTime = 0; + + // (SA) clear zoom (so no warpies) + cg.zoomedBinoc = qfalse; + cg.zoomedBinoc = cg.zoomedScope = qfalse; + cg.zoomTime = 0; + cg.zoomval = 0; + + // reset fog to world fog (if present) + trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP,20,0,0,0,0 ); + + CG_InitLocalEntities(); + CG_InitMarkPolys(); + + //Rafael particles + CG_ClearParticles(); + // done. + + for ( i = 1; i < MAX_PARTICLES_AREAS; i++ ) + { + { + int rval; + + rval = CG_NewParticleArea( CS_PARTICLES + i ); + if ( !rval ) { + break; + } + } + } + + + // Ridah, trails +// CG_ClearTrails (); + // done. + + // Ridah + CG_ClearFlameChunks(); + CG_SoundInit(); + // done. + + // make sure the "3 frags left" warnings play again + cg.fraglimitWarnings = 0; + + cg.timelimitWarnings = 0; + + cg.intermissionStarted = qfalse; + + cgs.voteTime = 0; + + cg.lightstylesInited = qfalse; + + cg.mapRestart = qtrue; + + CG_StartMusic(); + + trap_S_ClearLoopingSounds( qtrue ); + + cg.latchVictorySound = qfalse; // NERVE - SMF +// JPW NERVE -- reset render flags + cg_fxflags = 0; +// jpw + + + // we really should clear more parts of cg here and stop sounds + cg.v_dmg_time = 0; + cg.v_noFireTime = 0; + cg.v_fireTime = 0; + + // inform LOCAL server of animationSpeeds for AI use (ONLY) + //if (cgs.localServer) { + //CG_SendMoveSpeeds(); + //} + + // play the "fight" sound if this is a restart without warmup + if ( cg.warmup == 0 && cgs.gametype == GT_TOURNAMENT ) { + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH * 2 ); + } +#ifdef MISSIONPACK + if ( cg_singlePlayerActive.integer ) { + trap_Cvar_Set( "ui_matchStartTime", va( "%i", cg.time ) ); + if ( cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string ) { + trap_SendConsoleCommand( va( "set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string ) ); + } + } +#endif + trap_Cvar_Set( "cg_thirdPerson", "0" ); +} + +/* +================= +CG_RequestMoveSpeed +================= +*/ +void CG_RequestMoveSpeed( const char *modelname ) { + animModelInfo_t *modelInfo; + + modelInfo = BG_ModelInfoForModelname( (char *)modelname ); + + if ( !modelInfo ) { + // ignore it + return; + } + + // send it + CG_SendMoveSpeed( modelInfo->animations, modelInfo->numAnimations, (char *)modelname ); +} + +// NERVE - SMF +#define MAX_VOICEFILESIZE 16384 +#define MAX_VOICEFILES 8 +#define MAX_VOICECHATS 64 +#define MAX_VOICESOUNDS 64 +#define MAX_CHATSIZE 64 +#define MAX_HEADMODELS 64 + +typedef struct voiceChat_s +{ + char id[64]; + int numSounds; + sfxHandle_t sounds[MAX_VOICESOUNDS]; + char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; + qhandle_t sprite[MAX_VOICESOUNDS]; // DHM - Nerve +} voiceChat_t; + +typedef struct voiceChatList_s +{ + char name[64]; + int gender; + int numVoiceChats; + voiceChat_t voiceChats[MAX_VOICECHATS]; +} voiceChatList_t; + +typedef struct headModelVoiceChat_s +{ + char headmodel[64]; + int voiceChatNum; +} headModelVoiceChat_t; + +voiceChatList_t voiceChatLists[MAX_VOICEFILES]; +headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; + +/* +================= +CG_ParseVoiceChats +================= +*/ +int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { + int len, i; + int current = 0; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + char **p, *ptr; + char *token; + voiceChat_t *voiceChats; + qboolean compress; + + compress = qtrue; + if ( cg_buildScript.integer ) { + compress = qfalse; + } + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); + return qfalse; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return qfalse; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + Com_sprintf( voiceChatList->name, sizeof( voiceChatList->name ), "%s", filename ); + voiceChats = voiceChatList->voiceChats; + for ( i = 0; i < maxVoiceChats; i++ ) { + voiceChats[i].id[0] = 0; + } + token = COM_ParseExt( p, qtrue ); + if ( !token || token[0] == 0 ) { + return qtrue; + } + if ( !Q_stricmp( token, "female" ) ) { + voiceChatList->gender = GENDER_FEMALE; + } else if ( !Q_stricmp( token, "male" ) ) { + voiceChatList->gender = GENDER_MALE; + } else if ( !Q_stricmp( token, "neuter" ) ) { + voiceChatList->gender = GENDER_NEUTER; + } else { + trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); + return qfalse; + } + + voiceChatList->numVoiceChats = 0; + while ( 1 ) { + token = COM_ParseExt( p, qtrue ); + if ( !token || token[0] == 0 ) { + return qtrue; + } + Com_sprintf( voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token ); + token = COM_ParseExt( p, qtrue ); + if ( Q_stricmp( token, "{" ) ) { + trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); + return qfalse; + } + voiceChats[voiceChatList->numVoiceChats].numSounds = 0; + current = voiceChats[voiceChatList->numVoiceChats].numSounds; + + while ( 1 ) { + token = COM_ParseExt( p, qtrue ); + if ( !token || token[0] == 0 ) { + return qtrue; + } + if ( !Q_stricmp( token, "}" ) ) { + break; + } + voiceChats[voiceChatList->numVoiceChats].sounds[current] = trap_S_RegisterSound( token /*, compress */ ); + token = COM_ParseExt( p, qtrue ); + if ( !token || token[0] == 0 ) { + return qtrue; + } + Com_sprintf( voiceChats[voiceChatList->numVoiceChats].chats[current], MAX_CHATSIZE, "%s", token ); + + // DHM - Nerve :: Specify sprite shader to show above player's head + token = COM_ParseExt( p, qfalse ); + if ( !Q_stricmp( token, "}" ) || !token || token[0] == 0 ) { + voiceChats[voiceChatList->numVoiceChats].sprite[current] = trap_R_RegisterShader( "sprites/voiceChat" ); + COM_RestoreParseSession( p ); + } else { + voiceChats[voiceChatList->numVoiceChats].sprite[current] = trap_R_RegisterShader( token ); + if ( voiceChats[voiceChatList->numVoiceChats].sprite[current] == 0 ) { + voiceChats[voiceChatList->numVoiceChats].sprite[current] = trap_R_RegisterShader( "sprites/voiceChat" ); + } + } + // dhm - end + + voiceChats[voiceChatList->numVoiceChats].numSounds++; + current = voiceChats[voiceChatList->numVoiceChats].numSounds; + + if ( voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS ) { + break; + } + } + + voiceChatList->numVoiceChats++; + if ( voiceChatList->numVoiceChats >= maxVoiceChats ) { + return qtrue; + } + } + return qtrue; +} + +/* +================= +CG_LoadVoiceChats +================= +*/ +void CG_LoadVoiceChats( void ) { + int size; + + size = trap_MemoryRemaining(); + CG_ParseVoiceChats( "scripts/wm_axis_chat.voice", &voiceChatLists[0], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/wm_allies_chat.voice", &voiceChatLists[1], MAX_VOICECHATS ); +// CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS ); +// CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS ); +// CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS ); +// CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS ); +// CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS ); +// CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS ); +// CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS ); + CG_Printf( "voice chat memory size = %d\n", size - trap_MemoryRemaining() ); +} + +/* +================= +CG_HeadModelVoiceChats +================= +*/ +int CG_HeadModelVoiceChats( char *filename ) { + int len, i; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + char **p, *ptr; + char *token; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( "voice chat file not found: %s\n", filename ) ); + return -1; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return -1; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + token = COM_ParseExt( p, qtrue ); + if ( !token || token[0] == 0 ) { + return -1; + } + + for ( i = 0; i < MAX_VOICEFILES; i++ ) { + if ( !Q_stricmp( token, voiceChatLists[i].name ) ) { + return i; + } + } + + //FIXME: maybe try to load the .voice file which name is stored in token? + + return -1; +} + + +/* +================= +CG_GetVoiceChat +================= +*/ +int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, qhandle_t *sprite, char **chat ) { + int i, rnd; + + for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) { + if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) { + rnd = random() * voiceChatList->voiceChats[i].numSounds; + *snd = voiceChatList->voiceChats[i].sounds[rnd]; + *sprite = voiceChatList->voiceChats[i].sprite[rnd]; + *chat = voiceChatList->voiceChats[i].chats[rnd]; + return qtrue; + } + } + return qfalse; +} + +/* +================= +CG_VoiceChatListForClient +================= +*/ +voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { + clientInfo_t *ci; + int voiceChatNum, i, j, k, gender; + char filename[128], *headModelName; + + // NERVE - SMF + if ( cgs.gametype >= GT_WOLF ) { + if ( cgs.clientinfo[ clientNum ].team == TEAM_RED ) { + return &voiceChatLists[0]; + } else { + return &voiceChatLists[1]; + } + } + // -NERVE - SMF + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + headModelName = ci->headModelName; + if ( headModelName[0] == '*' ) { + headModelName++; + } + // find the voice file for the head model the client uses + for ( i = 0; i < MAX_HEADMODELS; i++ ) { + if ( !Q_stricmp( headModelVoiceChat[i].headmodel, headModelName ) ) { + break; + } + } + if ( i < MAX_HEADMODELS ) { + return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; + } + // find a .vc file + for ( i = 0; i < MAX_HEADMODELS; i++ ) { + if ( !strlen( headModelVoiceChat[i].headmodel ) ) { + Com_sprintf( filename, sizeof( filename ), "scripts/%s.vc", headModelName ); + voiceChatNum = CG_HeadModelVoiceChats( filename ); + if ( voiceChatNum == -1 ) { + break; + } + Com_sprintf( headModelVoiceChat[i].headmodel, sizeof( headModelVoiceChat[i].headmodel ), + "%s", headModelName ); + headModelVoiceChat[i].voiceChatNum = voiceChatNum; + return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; + } + } + gender = ci->gender; + for ( k = 0; k < 2; k++ ) { + // just pick the first with the right gender + for ( i = 0; i < MAX_VOICEFILES; i++ ) { + if ( strlen( voiceChatLists[i].name ) ) { + if ( voiceChatLists[i].gender == gender ) { + // store this head model with voice chat for future reference + for ( j = 0; j < MAX_HEADMODELS; j++ ) { + if ( !strlen( headModelVoiceChat[j].headmodel ) ) { + Com_sprintf( headModelVoiceChat[j].headmodel, sizeof( headModelVoiceChat[j].headmodel ), + "%s", headModelName ); + headModelVoiceChat[j].voiceChatNum = i; + break; + } + } + return &voiceChatLists[i]; + } + } + } + // fall back to male gender because we don't have neuter in the mission pack + if ( gender == GENDER_MALE ) { + break; + } + gender = GENDER_MALE; + } + // store this head model with voice chat for future reference + for ( j = 0; j < MAX_HEADMODELS; j++ ) { + if ( !strlen( headModelVoiceChat[j].headmodel ) ) { + Com_sprintf( headModelVoiceChat[j].headmodel, sizeof( headModelVoiceChat[j].headmodel ), + "%s", headModelName ); + headModelVoiceChat[j].voiceChatNum = 0; + break; + } + } + // just return the first voice chat list + return &voiceChatLists[0]; +} + +#define MAX_VOICECHATBUFFER 32 + +typedef struct bufferedVoiceChat_s +{ + int clientNum; + sfxHandle_t snd; + qhandle_t sprite; + int voiceOnly; + char cmd[MAX_SAY_TEXT]; + char message[MAX_SAY_TEXT]; + vec3_t origin; // NERVE - SMF +} bufferedVoiceChat_t; + +bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; + +/* +================= +CG_PlayVoiceChat +================= +*/ +void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) { + // if we are going into the intermission, don't start any voices +/* // NERVE - SMF - don't do this in wolfMP + if ( cg.intermissionStarted ) { + return; + } +*/ + + if ( !cg_noVoiceChats.integer ) { + trap_S_StartLocalSound( vchat->snd, CHAN_VOICE ); + + // DHM - Nerve :: Show icon above head + if ( vchat->clientNum == cg.snap->ps.clientNum ) { + cg.predictedPlayerEntity.voiceChatSprite = vchat->sprite; + if ( vchat->sprite == cgs.media.voiceChatShader ) { + cg.predictedPlayerEntity.voiceChatSpriteTime = cg.time + cg_voiceSpriteTime.integer; + } else { + cg.predictedPlayerEntity.voiceChatSpriteTime = cg.time + cg_voiceSpriteTime.integer * 2; + } + } else { + cg_entities[ vchat->clientNum ].voiceChatSprite = vchat->sprite; + VectorCopy( vchat->origin, cg_entities[ vchat->clientNum ].lerpOrigin ); // NERVE - SMF + if ( vchat->sprite == cgs.media.voiceChatShader ) { + cg_entities[ vchat->clientNum ].voiceChatSpriteTime = cg.time + cg_voiceSpriteTime.integer; + } else { + cg_entities[ vchat->clientNum ].voiceChatSpriteTime = cg.time + cg_voiceSpriteTime.integer * 2; + } + } + // dhm - end + +#ifdef MISSIONPACK + if ( vchat->clientNum != cg.snap->ps.clientNum ) { + int orderTask = CG_ValidOrder( vchat->cmd ); + if ( orderTask > 0 ) { + cgs.acceptOrderTime = cg.time + 5000; + Q_strncpyz( cgs.acceptVoice, vchat->cmd, sizeof( cgs.acceptVoice ) ); + cgs.acceptTask = orderTask; + cgs.acceptLeader = vchat->clientNum; + } + // see if this was an order + CG_ShowResponseHead(); + } +#endif + } + if ( !vchat->voiceOnly && !cg_noVoiceText.integer ) { + CG_AddToTeamChat( vchat->message ); + CG_Printf( va( "[skipnotify]: %s\n", vchat->message ) ); // JPW NERVE + } + voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; +} + +/* +===================== +CG_PlayBufferedVoieChats +===================== +*/ +void CG_PlayBufferedVoiceChats( void ) { + if ( cg.voiceChatTime < cg.time ) { + if ( cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd ) { + // + CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); + // + cg.voiceChatBufferOut = ( cg.voiceChatBufferOut + 1 ) % MAX_VOICECHATBUFFER; + cg.voiceChatTime = cg.time + 1000; + } + } +} + +/* +===================== +CG_AddBufferedVoiceChat +===================== +*/ +void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) { + // if we are going into the intermission, don't start any voices +/* // NERVE - SMF - don't do this in wolfMP + if ( cg.intermissionStarted ) { + return; + } +*/ + +// JPW NERVE new system doesn't buffer but overwrites vchats FIXME put this on a cvar to choose which to use + memcpy( &voiceChatBuffer[0],vchat,sizeof( bufferedVoiceChat_t ) ); + cg.voiceChatBufferIn = 0; + CG_PlayVoiceChat( &voiceChatBuffer[0] ); + +/* JPW NERVE pulled this + memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); + cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; + if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) { + CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); + cg.voiceChatBufferOut++; + } +*/ +} + +/* +================= +CG_VoiceChatLocal +================= +*/ +void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd, vec3_t origin ) { + char *chat; + voiceChatList_t *voiceChatList; + clientInfo_t *ci; + sfxHandle_t snd; + qhandle_t sprite; + bufferedVoiceChat_t vchat; + const char *loc; // NERVE - SMF + +/* // NERVE - SMF - don't do this in wolfMP + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } +*/ + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + cgs.currentVoiceClient = clientNum; + + voiceChatList = CG_VoiceChatListForClient( clientNum ); + + if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &sprite, &chat ) ) { + // + if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { + vchat.clientNum = clientNum; + vchat.snd = snd; + vchat.sprite = sprite; + vchat.voiceOnly = voiceOnly; + VectorCopy( origin, vchat.origin ); // NERVE - SMF + Q_strncpyz( vchat.cmd, cmd, sizeof( vchat.cmd ) ); + + // NERVE - SMF - get location + loc = CG_ConfigString( CS_LOCATIONS + ci->location ); + if ( !loc || !*loc ) { + loc = " "; + } + // -NERVE - SMF + + if ( mode == SAY_TELL ) { + Com_sprintf( vchat.message, sizeof( vchat.message ), "[%s]%c%c[%s]: %c%c%s", + ci->name, Q_COLOR_ESCAPE, COLOR_YELLOW, CG_TranslateString( loc ), Q_COLOR_ESCAPE, color, CG_TranslateString( chat ) ); + } else if ( mode == SAY_TEAM ) { + Com_sprintf( vchat.message, sizeof( vchat.message ), "(%s)%c%c(%s): %c%c%s", + ci->name, Q_COLOR_ESCAPE, COLOR_YELLOW, CG_TranslateString( loc ), Q_COLOR_ESCAPE, color, CG_TranslateString( chat ) ); + } else { + Com_sprintf( vchat.message, sizeof( vchat.message ), "%s %c%c(%s): %c%c%s", + ci->name, Q_COLOR_ESCAPE, COLOR_YELLOW, CG_TranslateString( loc ), Q_COLOR_ESCAPE, color, CG_TranslateString( chat ) ); + } + CG_AddBufferedVoiceChat( &vchat ); + } + } +} + +/* +================= +CG_VoiceChat +================= +*/ +void CG_VoiceChat( int mode ) { + const char *cmd; + int clientNum, color; + qboolean voiceOnly; + vec3_t origin; // NERVE - SMF + + voiceOnly = atoi( CG_Argv( 1 ) ); + clientNum = atoi( CG_Argv( 2 ) ); + color = atoi( CG_Argv( 3 ) ); + + // NERVE - SMF - added origin + origin[0] = atoi( CG_Argv( 5 ) ); + origin[1] = atoi( CG_Argv( 6 ) ); + origin[2] = atoi( CG_Argv( 7 ) ); + + cmd = CG_Argv( 4 ); + + if ( cg_noTaunt.integer != 0 ) { + if ( !strcmp( cmd, VOICECHAT_KILLINSULT ) || !strcmp( cmd, VOICECHAT_TAUNT ) || \ + !strcmp( cmd, VOICECHAT_DEATHINSULT ) || !strcmp( cmd, VOICECHAT_KILLGAUNTLET ) || \ + !strcmp( cmd, VOICECHAT_PRAISE ) ) { + return; + } + } + + CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd, origin ); +} +// -NERVE - SMF + +/* +================= +CG_RemoveChatEscapeChar +================= +*/ +static void CG_RemoveChatEscapeChar( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if ( text[i] == '\x19' ) { + continue; + } + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +================= +CG_LocalizeServerCommand + +NERVE - SMF - localize string sent from server + +- localization is ON by default. +- use [lof] in string to turn OFF +- use [lon] in string to turn back ON +================= +*/ +const char* CG_LocalizeServerCommand( const char *buf ) { + static char token[MAX_TOKEN_CHARS]; + char temp[MAX_TOKEN_CHARS]; + qboolean togloc = qtrue; + const char *s; + int i, prev; + + memset( token, 0, sizeof( token ) ); + s = buf; + prev = 0; + + for ( i = 0; *s; i++, s++ ) { + // TTimo: + // line was: if ( *s == '[' && !Q_strncmp( s, "[lon]", 5 ) || !Q_strncmp( s, "[lof]", 5 ) ) { + // || prevails on &&, gcc warning was 'suggest parentheses around && within ||' + // modified to the correct behaviour + if ( *s == '[' && ( !Q_strncmp( s, "[lon]", 5 ) || !Q_strncmp( s, "[lof]", 5 ) ) ) { + + if ( togloc ) { + memset( temp, 0, sizeof( temp ) ); + strncpy( temp, buf + prev, i - prev ); + strcat( token, CG_TranslateString( temp ) ); + } else { + strncat( token, buf + prev, i - prev ); + } + + if ( s[3] == 'n' ) { + togloc = qtrue; + } else { + togloc = qfalse; + } + + i += 5; + s += 5; + prev = i; + } + } + + if ( togloc ) { + memset( temp, 0, sizeof( temp ) ); + strncpy( temp, buf + prev, i - prev ); + strcat( token, CG_TranslateString( temp ) ); + } else { + strncat( token, buf + prev, i - prev ); + } + + return token; +} +// -NERVE - SMF + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) { + const char *cmd; + char text[MAX_SAY_TEXT]; + + cmd = CG_Argv( 0 ); + + if ( !cmd[0] ) { + // server claimed the command + return; + } + + if ( !strcmp( cmd, "tinfo" ) ) { + CG_ParseTeamInfo(); + return; + } + + if ( !strcmp( cmd, "scores" ) ) { + CG_ParseScores(); + return; + } + + if ( !strcmp( cmd, "cp" ) ) { + // NERVE - SMF + int args = trap_Argc(); + char *s; + + if ( args >= 3 ) { + s = CG_TranslateString( CG_Argv( 1 ) ); + + if ( args == 4 ) { + s = va( "%s%s", CG_Argv( 3 ), s ); + } + + CG_PriorityCenterPrint( s, SCREEN_HEIGHT - ( SCREEN_HEIGHT * 0.25 ), SMALLCHAR_WIDTH, atoi( CG_Argv( 2 ) ) ); + } else { + CG_CenterPrint( CG_LocalizeServerCommand( CG_Argv( 1 ) ), SCREEN_HEIGHT - ( SCREEN_HEIGHT * 0.25 ), SMALLCHAR_WIDTH ); //----(SA) modified + } + return; + } + + if ( !strcmp( cmd, "cs" ) ) { + CG_ConfigStringModified(); + return; + } + + // NERVE - SMF + if ( !strcmp( cmd, "shake" ) ) { + CG_StartShakeCamera( atof( CG_Argv( 1 ) ) ); + return; + } + // -NERVE - SMF + + if ( !strcmp( cmd, "print" ) ) { + CG_Printf( "[cgnotify]%s", CG_LocalizeServerCommand( CG_Argv( 1 ) ) ); +#ifdef MISSIONPACK + cmd = CG_Argv( 1 ); // yes, this is obviously a hack, but so is the way we hear about + // votes passing or failing + if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 ) ) { + trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER ); + } else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) { + trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER ); + } +#endif + return; + } + + if ( !strcmp( cmd, "chat" ) ) { + const char *s; + + if ( cg_teamChatsOnly.integer ) { + return; + } + + if ( atoi( CG_Argv( 2 ) ) ) { + s = CG_LocalizeServerCommand( CG_Argv( 1 ) ); + } else { + s = CG_Argv( 1 ); + } + + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, s, MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_AddToTeamChat( text ); // JPW NERVE + CG_Printf( "[skipnotify]%s\n", text ); // JPW NERVE + + // NERVE - SMF - we also want this to display in limbo chat + if ( cgs.gametype >= GT_WOLF ) { + trap_UI_LimboChat( text ); + } + + return; + } + + if ( !strcmp( cmd, "tchat" ) ) { + const char *s; + + if ( atoi( CG_Argv( 2 ) ) ) { + s = CG_LocalizeServerCommand( CG_Argv( 1 ) ); + } else { + s = CG_Argv( 1 ); + } + + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, s, MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_AddToTeamChat( text ); + CG_Printf( "[skipnotify]%s\n", text ); // JPW NERVE + + // NERVE - SMF - we also want this to display in limbo chat + if ( cgs.gametype >= GT_WOLF ) { + trap_UI_LimboChat( text ); + } + + return; + } + + if ( !strcmp( cmd, "vchat" ) ) { + CG_VoiceChat( SAY_ALL ); // NERVE - SMF - enabled support + return; + } + + if ( !strcmp( cmd, "vtchat" ) ) { + CG_VoiceChat( SAY_TEAM ); // NERVE - SMF - enabled support + return; + } + + if ( !strcmp( cmd, "vtell" ) ) { + CG_VoiceChat( SAY_TELL ); // NERVE - SMF - enabled support + return; + } + + // NERVE - SMF - limbo chat + if ( !strcmp( cmd, "lchat" ) ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + trap_UI_LimboChat( text ); + CG_Printf( "[skipnotify]%s\n", text ); // JPW NERVE + return; + } + // -NERVE - SMF + + // NERVE - SMF + if ( !Q_stricmp( cmd, "oid" ) ) { + CG_ObjectivePrint( CG_Argv( 1 ), SMALLCHAR_WIDTH ); + return; + } + // -NERVE - SMF + + // DHM - Nerve :: Allow client to lodge a complaing + if ( !Q_stricmp( cmd, "complaint" ) ) { + cgs.complaintEndTime = cg.time + 20000; + cgs.complaintClient = atoi( CG_Argv( 1 ) ); + + if ( cgs.complaintClient < 0 ) { + cgs.complaintEndTime = cg.time + 10000; + } + + return; + } + // dhm + + if ( !strcmp( cmd, "map_restart" ) ) { + CG_MapRestart(); + return; + } + +// if ( !strcmp( cmd, "startCam" ) ) { +// CG_StartCamera( CG_Argv(1), atoi(CG_Argv(2)) ); +// return; +// } + + if ( !strcmp( cmd, "mvspd" ) ) { + CG_RequestMoveSpeed( CG_Argv( 1 ) ); + return; + } + + if ( Q_stricmp( cmd, "remapShader" ) == 0 ) { + if ( trap_Argc() == 4 ) { + trap_R_RemapShader( CG_Argv( 1 ), CG_Argv( 2 ), CG_Argv( 3 ) ); + } + } + + // loaddeferred can be both a servercmd and a consolecmd + if ( !strcmp( cmd, "loaddeferred" ) ) { // spelling fixed (SA) + CG_LoadDeferredPlayers(); + return; + } + + // clientLevelShot is sent before taking a special screenshot for + // the menu system during development + if ( !strcmp( cmd, "clientLevelShot" ) ) { + cg.levelShot = qtrue; + return; + } + + CG_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) { + while ( cgs.serverCommandSequence < latestSequence ) { + if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { + CG_ServerCommand(); + } + } +} diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c new file mode 100644 index 0000000..f2dc29f --- /dev/null +++ b/src/cgame/cg_snapshot.c @@ -0,0 +1,503 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + + + +#include "cg_local.h" + + + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) { + // if an event is set, assume it is new enough to use + // if the event had timed out, it would have been cleared + // RF, not needed for wolf + // DHM - Nerve :: Wolf is now using this. + cent->previousEvent = 0; + cent->previousEventSequence = cent->currentState.eventSequence; + + cent->trailTime = cg.snap->serverTime; + + // Ridah + cent->headJuncIndex = 0; + cent->headJuncIndex2 = 0; + // done. + + VectorCopy( cent->currentState.origin, cent->lerpOrigin ); + VectorCopy( cent->currentState.angles, cent->lerpAngles ); + if ( cent->currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( cent ); + } +} + + + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) { + + // Ridah, update the fireDir if it's on fire + if ( CG_EntOnFire( cent ) ) { + vec3_t newDir, newPos, oldPos; + float adjust; + // + BG_EvaluateTrajectory( ¢->nextState.pos, cg.snap->serverTime, newPos ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, oldPos ); + // update the fireRiseDir + VectorSubtract( oldPos, newPos, newDir ); + // fire should go upwards if travelling slow + newDir[2] += 2; + if ( VectorNormalize( newDir ) < 1 ) { + VectorClear( newDir ); + newDir[2] = 1; + } + // now move towards the newDir + adjust = 0.3; + VectorMA( cent->fireRiseDir, adjust, newDir, cent->fireRiseDir ); + if ( VectorNormalize( cent->fireRiseDir ) <= 0.1 ) { + VectorCopy( newDir, cent->fireRiseDir ); + } + } + + //----(SA) the ent lost or gained some part(s), do any necessary effects + //TODO: check for ai first + if ( cent->currentState.dmgFlags != cent->nextState.dmgFlags ) { + CG_AttachedPartChange( cent ); + } + + + cent->currentState = cent->nextState; + cent->currentValid = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if ( !cent->interpolate ) { + CG_ResetEntity( cent ); + } + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) { + int i; + centity_t *cent; + entityState_t *state; + + cg.snap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList(); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn(); + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + state = &cg.snap->entities[ i ]; + cent = &cg_entities[ state->number ]; + + memcpy( ¢->currentState, state, sizeof( entityState_t ) ); + //cent->currentState = *state; + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } + +// JPW NERVE -- make sure we didn't break anything + cg_fxflags = 0; +// jpw + + // DHM - Nerve :: Set cg.clientNum so that it may be used elsewhere + // NERVE - SMF - commented out, TA merge, this has been moved to CG_Init +// cg.clientNum = snap->ps.clientNum; + + // NERVE - SMF + { + static char prevmap[64] = { 0 }; + char curmap[64]; + + trap_Cvar_VariableStringBuffer( "mapname", curmap, 64 ); + + if ( cgs.gametype >= GT_WOLF && Q_stricmp( curmap, prevmap ) ) { + strcpy( prevmap, curmap ); + trap_SendConsoleCommand( "openLimboMenu\n" ); + } + } + // -NERVE - SMF +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) { + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if ( !cg.snap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); + } + if ( !cg.nextSnap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); + } + + // execute any server string commands before transitioning entities + CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); + + // if we had a map_restart, set everthing with initial + + if ( !( cg.snap ) || !( cg.nextSnap ) ) { + return; + } + + // clear the currentValid flag for all entities in the existing snapshot + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + cent->currentValid = qfalse; + } + + // move nextSnap to snap and do the transitions + oldFrame = cg.snap; + cg.snap = cg.nextSnap; + + BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + CG_TransitionEntity( cent ); + } + + cg.nextSnap = NULL; + + // check for playerstate transition events + if ( oldFrame ) { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg.snap->ps; + // teleporting checks are irrespective of prediction + if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { + cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + } + + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if ( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) + || cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_TransitionPlayerState( ps, ops ); + } + } + +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) { + int num; + entityState_t *es; + centity_t *cent; + + cg.nextSnap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + + // check for extrapolation errors + for ( num = 0 ; num < snap->numEntities ; num++ ) { + es = &snap->entities[num]; + cent = &cg_entities[ es->number ]; + + memcpy( ¢->nextState, es, sizeof( entityState_t ) ); + //cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate + if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { + cent->interpolate = qfalse; + } else { + cent->interpolate = qtrue; + } + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { + cg.nextFrameTeleport = qtrue; + } else { + cg.nextFrameTeleport = qfalse; + } + + // if changing follow mode, don't interpolate + if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) { + cg.nextFrameTeleport = qtrue; + } + + // if changing server restarts, don't interpolate + if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { + cg.nextFrameTeleport = qtrue; + } + + // sort out solid entities + CG_BuildSolidList(); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) { + qboolean r; + snapshot_t *dest; + + if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { + CG_Printf( "[skipnotify]WARNING: CG_ReadNextSnapshot: way out of range, %i > %i\n", + cg.latestSnapshotNum, cgs.processedSnapshotNum ); + } + + while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { + // decide which of the two slots to load it into + if ( cg.snap == &cg.activeSnapshots[0] ) { + dest = &cg.activeSnapshots[1]; + } else { + dest = &cg.activeSnapshots[0]; + } + + // try to read the snapshot from the client system + cgs.processedSnapshotNum++; + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); + + // FIXME: why would trap_GetSnapshot return a snapshot with the same server time + if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { + //continue; + } + + // if it succeeded, return + if ( r ) { + CG_AddLagometerSnapshotInfo( dest ); +// JPW NERVE pulled for MP, maybe this was the code that was causing crashes. Shouldn't need it for savegame stuff + // Ridah, savegame: we should use this as our new base snapshot + // server has been restarted + if ( cg.snap && ( dest->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { +// int i; // TTimo: unused +// centity_t backupCent; // TTimo: unused +// CG_SetInitialSnapshot( dest ); +// cg.nextFrameTeleport = qtrue; + cg.damageTime = 0; + cg.duckTime = -1; + cg.landTime = -1; + cg.stepTime = -1; + // go through an reset the cent's +/* // JPW NERVE -- return NULL mighta been bad, q3ta doesn't include this stuff + for (i=0; isnapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_SetInitialSnapshot( snap ); + } + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg.time to interpolate towards, or we run + // out of available snapshots + do { + // if we don't have a nextframe, try and read a new one in + if ( !cg.nextSnap ) { + snap = CG_ReadNextSnapshot(); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if ( !snap ) { + break; + } + + CG_SetNextSnap( snap ); + + + // if time went backwards, we have a level restart + if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); + } + } + + // if our time is < nextFrame's, we have a nice interpolating state + if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) { + break; + } + + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot(); + } while ( 1 ); + + // assert our valid conditions upon exiting + if ( cg.snap == NULL ) { + CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); + } + if ( cg.time < cg.snap->serverTime ) { + // this can happen right after a vid_restart + cg.time = cg.snap->serverTime; + } + if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) { + CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); + } + +} diff --git a/src/cgame/cg_sound.c b/src/cgame/cg_sound.c new file mode 100644 index 0000000..5cb3fbc --- /dev/null +++ b/src/cgame/cg_sound.c @@ -0,0 +1,454 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// Ridah, cg_sound.c - parsing and use of sound script files + +#include "cg_local.h" + +typedef struct soundScriptSound_s +{ + char filename[MAX_QPATH]; + sfxHandle_t sfxHandle; + int lastPlayed; + + struct soundScriptSound_s *next; +} soundScriptSound_t; + +typedef struct soundScript_s +{ + int index; + char name[MAX_QPATH]; + int channel; + int attenuation; + qboolean streaming; + qboolean looping; + qboolean random; // TODO + int numSounds; + soundScriptSound_t *soundList; // pointer into the global list of soundScriptSounds (defined below) + + struct soundScript_s *nextHash; // next soundScript in our hashTable list position +} soundScript_t; + +// we have to define these static lists, since we can't alloc memory within the cgame + +#define FILE_HASH_SIZE 1024 +static soundScript_t* hashTable[FILE_HASH_SIZE]; + +#define MAX_SOUND_SCRIPTS 4096 +static soundScript_t soundScripts[MAX_SOUND_SCRIPTS]; +int numSoundScripts = 0; + +#define MAX_SOUND_SCRIPT_SOUNDS 8192 +static soundScriptSound_t soundScriptSounds[MAX_SOUND_SCRIPT_SOUNDS]; +int numSoundScriptSounds = 0; + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter == '\\' ) { + letter = '/'; // damn path names + } + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( FILE_HASH_SIZE - 1 ); + return hash; +} + +/* +============== +CG_SoundScriptPrecache + + returns the index+1 of the script in the global list, for fast calling +============== +*/ +int CG_SoundScriptPrecache( const char *name ) { + soundScriptSound_t *scriptSound; + long hash; + char *s; + soundScript_t *sound; + + if ( !name || !name[0] ) { + return 0; + } + + hash = generateHashValue( name ); + + s = (char *)name; + sound = hashTable[hash]; + while ( sound ) { + if ( !Q_strcasecmp( s, sound->name ) ) { + // found a match, precache these sounds + scriptSound = sound->soundList; + if ( !sound->streaming ) { + while ( scriptSound ) { + scriptSound->sfxHandle = trap_S_RegisterSound( scriptSound->filename ); + scriptSound = scriptSound->next; + } + } else if ( cg_buildScript.integer ) { + while ( scriptSound ) { + // just open the file so it gets copied to the build dir + fileHandle_t f; + trap_FS_FOpenFile( scriptSound->filename, &f, FS_READ ); + trap_FS_FCloseFile( f ); + scriptSound = scriptSound->next; + } + } + return sound->index + 1; + } + sound = sound->nextHash; + } + + return 0; +} + +/* +============== +CG_SoundPickOldestRandomSound +============== +*/ +void CG_SoundPickOldestRandomSound( soundScript_t *sound, vec3_t org, int entnum ) { + int oldestTime = 0; // TTimo: init + soundScriptSound_t *oldestSound; + soundScriptSound_t *scriptSound; + + oldestSound = NULL; + scriptSound = sound->soundList; + while ( scriptSound ) { + if ( !oldestSound || ( scriptSound->lastPlayed < oldestTime ) ) { + oldestTime = scriptSound->lastPlayed; + oldestSound = scriptSound; + } + scriptSound = scriptSound->next; + } + + if ( oldestSound ) { + // play this sound + if ( !sound->streaming ) { + if ( !oldestSound->sfxHandle ) { + oldestSound->sfxHandle = trap_S_RegisterSound( oldestSound->filename ); + } + trap_S_StartSound( org, entnum, sound->channel, oldestSound->sfxHandle ); + } else { + trap_S_StartStreamingSound( oldestSound->filename, sound->looping ? oldestSound->filename : NULL, entnum, sound->channel, sound->attenuation ); + } + oldestSound->lastPlayed = cg.time; + } else { + CG_Error( "Unable to locate a valid sound for soundScript: %s\n", sound->name ); + } +} + +/* +============== +CG_SoundPlaySoundScript + + returns qtrue is a script is found +============== +*/ +qboolean CG_SoundPlaySoundScript( const char *name, vec3_t org, int entnum ) { + long hash; + char *s; + soundScript_t *sound; + + if ( !name || !name[0] ) { + return qfalse; + } + + hash = generateHashValue( name ); + + s = (char *)name; + sound = hashTable[hash]; + while ( sound ) { + if ( !Q_strcasecmp( s, sound->name ) ) { + // found a match, pick the oldest sound + CG_SoundPickOldestRandomSound( sound, org, entnum ); + return qtrue; + } + sound = sound->nextHash; + } + + //CG_Printf( S_COLOR_RED "CG_SoundPlaySoundScript: cannot find sound script '%s'\n", name ); + return qfalse; +} + +/* +============== +CG_SoundPlayIndexedScript + + returns qtrue is a script is found +============== +*/ +void CG_SoundPlayIndexedScript( int index, vec3_t org, int entnum ) { + soundScript_t *sound; + + if ( !index ) { + return; + } + + if ( index > numSoundScripts ) { + return; + } + + sound = &soundScripts[index - 1]; + // pick the oldest sound + CG_SoundPickOldestRandomSound( sound, org, entnum ); +} + +/* +=============== +CG_SoundParseSounds +=============== +*/ +static void CG_SoundParseSounds( char *filename, char *buffer ) { + char *token, **text; + int s; + long hash; + soundScript_t sound; // the current sound being read + soundScriptSound_t *scriptSound; + qboolean inSound, wantSoundName; + + s = 0; + inSound = qfalse; + wantSoundName = qtrue; + text = &buffer; + + while ( 1 ) { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) { + if ( inSound ) { + CG_Error( "no concluding '}' in sound %s, file %s\n", sound.name, filename ); + } + return; + } + if ( !Q_strcasecmp( token, "{" ) ) { + if ( inSound ) { + CG_Error( "no concluding '}' in sound %s, file %s\n", sound.name, filename ); + } + if ( wantSoundName ) { + CG_Error( "'{' found but not expected, after %s, file %s\n", sound.name, filename ); + } + inSound = qtrue; + continue; + } + if ( !Q_strcasecmp( token, "}" ) ) { + if ( !inSound ) { + CG_Error( "'}' unexpected after sound %s, file %s\n", sound.name, filename ); + } + + // end of a sound, copy it to the global list and stick it in the hashTable + hash = generateHashValue( sound.name ); + sound.nextHash = hashTable[hash]; + soundScripts[numSoundScripts] = sound; + hashTable[hash] = &soundScripts[numSoundScripts++]; + + if ( numSoundScripts == MAX_SOUND_SCRIPTS ) { + CG_Error( "MAX_SOUND_SCRIPTS exceeded.\nReduce number of sound scripts.\n" ); + } + + inSound = qfalse; + wantSoundName = qtrue; + continue; + } + if ( !inSound ) { + // this is the identifier for a new sound + if ( !wantSoundName ) { + CG_Error( "'%s' unexpected after sound %s, file %s\n", token, sound.name, filename ); + } + memset( &sound, 0, sizeof( sound ) ); + Q_strncpyz( sound.name, token, sizeof( sound.name ) ); + wantSoundName = qfalse; + sound.index = numSoundScripts; + // setup the new sound defaults + sound.channel = CHAN_AUTO; + sound.attenuation = 1; // default to fade away with distance (for streaming sounds) + // + continue; + } + + // we are inside a sound script + + if ( !Q_strcasecmp( token, "channel" ) ) { + // ignore this now, just look for the channel identifiers explicitly + continue; + } + if ( !Q_strcasecmp( token, "local" ) ) { + sound.channel = CHAN_LOCAL; + continue; + } else if ( !Q_strcasecmp( token, "announcer" ) ) { + sound.channel = CHAN_ANNOUNCER; + continue; + } else if ( !Q_strcasecmp( token, "body" ) ) { + sound.channel = CHAN_BODY; + continue; + } else if ( !Q_strcasecmp( token, "voice" ) ) { + sound.channel = CHAN_VOICE; + continue; + } else if ( !Q_strcasecmp( token, "weapon" ) ) { + sound.channel = CHAN_WEAPON; + continue; + } else if ( !Q_strcasecmp( token, "item" ) ) { + sound.channel = CHAN_ITEM; + continue; + } else if ( !Q_strcasecmp( token, "auto" ) ) { + sound.channel = CHAN_AUTO; + continue; + } + if ( !Q_strcasecmp( token, "global" ) ) { + sound.attenuation = 0; + continue; + } + if ( !Q_strcasecmp( token, "streaming" ) ) { + sound.streaming = qtrue; + continue; + } + if ( !Q_strcasecmp( token, "looping" ) ) { + sound.looping = qtrue; + continue; + } + if ( !Q_strcasecmp( token, "sound" ) ) { + // grab a free scriptSound + scriptSound = &soundScriptSounds[numSoundScriptSounds++]; + + if ( numSoundScripts == MAX_SOUND_SCRIPT_SOUNDS ) { + CG_Error( "MAX_SOUND_SCRIPT_SOUNDS exceeded.\nReduce number of sound scripts.\n" ); + } + + token = COM_ParseExt( text, qtrue ); + Q_strncpyz( scriptSound->filename, token, sizeof( scriptSound->filename ) ); + scriptSound->lastPlayed = 0; + scriptSound->sfxHandle = 0; + scriptSound->next = sound.soundList; + sound.soundList = scriptSound; + continue; + } + } +} + +/* +=============== +CG_SoundLoadSoundFiles +=============== +*/ +#define MAX_SOUND_FILES 128 +#define MAX_BUFFER 20000 +static void CG_SoundLoadSoundFiles( void ) { + char soundFiles[MAX_SOUND_FILES][MAX_QPATH]; + char buffer[MAX_BUFFER]; + char *text; + char filename[MAX_QPATH]; + fileHandle_t f; + int numSounds; + int i, len; + char *token; + + // scan for sound files + Com_sprintf( filename, MAX_QPATH, "sound/scripts/filelist.txt" ); + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + CG_Printf( S_COLOR_RED "WARNING: no sound files found (filelist.txt not found in sound/scripts)\n" ); + return; + } + if ( len > MAX_BUFFER ) { + CG_Error( "%s is too big, make it smaller (max = %i bytes)\n", filename, MAX_BUFFER ); + } + // load the file into memory + trap_FS_Read( buffer, len, f ); + buffer[len] = 0; + trap_FS_FCloseFile( f ); + // parse the list + text = buffer; + numSounds = 0; + while ( 1 ) { + token = COM_ParseExt( &text, qtrue ); + if ( !token[0] ) { + break; + } + Com_sprintf( soundFiles[numSounds++], MAX_QPATH, token ); + } + + if ( !numSounds ) { + CG_Printf( S_COLOR_RED "WARNING: no sound files found\n" ); + return; + } + + // load and parse sound files + for ( i = 0; i < numSounds; i++ ) + { + Com_sprintf( filename, sizeof( filename ), "sound/scripts/%s", soundFiles[i] ); + CG_Printf( "...loading '%s'\n", filename ); + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + CG_Error( "Couldn't load %s", filename ); + } + if ( len > MAX_BUFFER ) { + CG_Error( "%s is too big, make it smaller (max = %i bytes)\n", filename, MAX_BUFFER ); + } + memset( buffer, 0, sizeof( buffer ) ); + trap_FS_Read( buffer, len, f ); + trap_FS_FCloseFile( f ); + CG_SoundParseSounds( filename, buffer ); + } +} + +/* +============== +CG_SoundInit +============== +*/ +void CG_SoundInit( void ) { + + if ( numSoundScripts ) { + // keep all the information, just reset the vars + int i; + + for ( i = 0; i < numSoundScriptSounds; i++ ) { + soundScriptSounds[i].lastPlayed = 0; + soundScriptSounds[i].sfxHandle = 0; + } + } else { + CG_Printf( "\n.........................\n" + "Initializing Sound Scripts\n" ); + CG_SoundLoadSoundFiles(); + CG_Printf( "done.\n" ); + } + +} diff --git a/src/cgame/cg_spawn.c b/src/cgame/cg_spawn.c new file mode 100644 index 0000000..b9f0e49 --- /dev/null +++ b/src/cgame/cg_spawn.c @@ -0,0 +1,270 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: cg_spawn.c + * + * desc: + * +*/ + +#include "cg_local.h" + +qboolean CG_SpawnString( const char *key, const char *defaultString, char **out ) { + int i; + + if ( !cg.spawning ) { + *out = (char *)defaultString; +// CG_Error( "CG_SpawnString() called while not spawning" ); + } + + for ( i = 0 ; i < cg.numSpawnVars ; i++ ) { + if ( !strcmp( key, cg.spawnVars[i][0] ) ) { + *out = cg.spawnVars[i][1]; + return qtrue; + } + } + + *out = (char *)defaultString; + return qfalse; +} + +qboolean CG_SpawnFloat( const char *key, const char *defaultString, float *out ) { + char *s; + qboolean present; + + present = CG_SpawnString( key, defaultString, &s ); + *out = atof( s ); + return present; +} + +qboolean CG_SpawnInt( const char *key, const char *defaultString, int *out ) { + char *s; + qboolean present; + + present = CG_SpawnString( key, defaultString, &s ); + *out = atoi( s ); + return present; +} + +qboolean CG_SpawnVector( const char *key, const char *defaultString, float *out ) { + char *s; + qboolean present; + + present = CG_SpawnString( key, defaultString, &s ); + sscanf( s, "%f %f %f", &out[0], &out[1], &out[2] ); + return present; +} + +qboolean CG_SpawnVector2D( const char *key, const char *defaultString, float *out ) { + char *s; + qboolean present; + + present = CG_SpawnString( key, defaultString, &s ); + sscanf( s, "%f %f", &out[0], &out[1] ); + return present; +} + +typedef struct { + char *name; + void ( *spawn )( void ); +} spawn_t; + +spawn_t spawns[] = { + {0, 0} +}; + +#define NUMSPAWNS ( sizeof( spawns ) / sizeof( spawn_t ) ) + +/* +=================== +CG_ParseEntityFromSpawnVars + +Spawn an entity and fill in all of the level fields from +cg.spawnVars[], then call the class specfic spawn function +=================== +*/ +void CG_ParseEntityFromSpawnVars( void ) { + int i; + char *classname; + + // check for "notteam" / "notfree" flags + CG_SpawnInt( "notteam", "0", &i ); + if ( i ) { + return; + } + + if ( CG_SpawnString( "classname", "", &classname ) ) { + for ( i = 0; i < NUMSPAWNS; i++ ) { + if ( !Q_stricmp( spawns[i].name, classname ) ) { + spawns[i].spawn(); + break; + } + } + } + +} + +/* +==================== +CG_AddSpawnVarToken +==================== +*/ +char *CG_AddSpawnVarToken( const char *string ) { + int l; + char *dest; + + l = strlen( string ); + if ( cg.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) { + CG_Error( "CG_AddSpawnVarToken: MAX_SPAWN_VARS" ); + } + + dest = cg.spawnVarChars + cg.numSpawnVarChars; + memcpy( dest, string, l + 1 ); + + cg.numSpawnVarChars += l + 1; + + return dest; +} + +/* +==================== +CG_ParseSpawnVars + +Parses a brace bounded set of key / value pairs out of the +level's entity strings into cg.spawnVars[] + +This does not actually spawn an entity. +==================== +*/ +qboolean CG_ParseSpawnVars( void ) { + char keyname[MAX_TOKEN_CHARS]; + char com_token[MAX_TOKEN_CHARS]; + + cg.numSpawnVars = 0; + cg.numSpawnVarChars = 0; + + // parse the opening brace + if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) { + // end of spawn string + return qfalse; + } + if ( com_token[0] != '{' ) { + CG_Error( "CG_ParseSpawnVars: found %s when expecting {",com_token ); + } + + // go through all the key / value pairs + while ( 1 ) { + // parse key + if ( !trap_GetEntityToken( keyname, sizeof( keyname ) ) ) { + CG_Error( "CG_ParseSpawnVars: EOF without closing brace" ); + } + + if ( keyname[0] == '}' ) { + break; + } + + // parse value + if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) { + CG_Error( "CG_ParseSpawnVars: EOF without closing brace" ); + } + + if ( com_token[0] == '}' ) { + CG_Error( "CG_ParseSpawnVars: closing brace without data" ); + } + if ( cg.numSpawnVars == MAX_SPAWN_VARS ) { + CG_Error( "CG_ParseSpawnVars: MAX_SPAWN_VARS" ); + } + cg.spawnVars[ cg.numSpawnVars ][0] = CG_AddSpawnVarToken( keyname ); + cg.spawnVars[ cg.numSpawnVars ][1] = CG_AddSpawnVarToken( com_token ); + cg.numSpawnVars++; + } + + return qtrue; +} + +void SP_worldspawn( void ) { + char *s; + + CG_SpawnString( "classname", "", &s ); + if ( Q_stricmp( s, "worldspawn" ) ) { + CG_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" ); + } + + cg.twoMinuteSound_g[0] = cg.twoMinuteSound_a[0] = cg.thirtySecondSound_g[0] = cg.thirtySecondSound_a[0] = '\0'; + + CG_SpawnString( "twoMinuteSound_axis", "sound/multiplayer/axis/g-twominutes1.wav", &s ); + Q_strncpyz( cg.twoMinuteSound_g, s, sizeof( cg.twoMinuteSound_g ) ); + CG_SpawnString( "twoMinuteSound_allied", "sound/multiplayer/allies/a-twominutes1.wav", &s ); + Q_strncpyz( cg.twoMinuteSound_a, s, sizeof( cg.twoMinuteSound_a ) ); + CG_SpawnString( "thirtySecondSound_axis", "sound/multiplayer/axis/g-thirtyseconds1.wav", &s ); + Q_strncpyz( cg.thirtySecondSound_g, s, sizeof( cg.thirtySecondSound_g ) ); + CG_SpawnString( "thirtySecondSound_allied", "sound/multiplayer/allies/a-thirtyseconds1.wav", &s ); + Q_strncpyz( cg.thirtySecondSound_a, s, sizeof( cg.thirtySecondSound_a ) ); + + if ( cg.twoMinuteSound_g[0] != '0' ) { + cgs.media.twoMinuteSound_g = trap_S_RegisterSound( cg.twoMinuteSound_g ); + } + if ( cg.twoMinuteSound_a[0] != '0' ) { + cgs.media.twoMinuteSound_a = trap_S_RegisterSound( cg.twoMinuteSound_a ); + } + if ( cg.thirtySecondSound_g[0] != '0' ) { + cgs.media.thirtySecondSound_g = trap_S_RegisterSound( cg.thirtySecondSound_g ); + } + if ( cg.thirtySecondSound_a[0] != '0' ) { + cgs.media.thirtySecondSound_a = trap_S_RegisterSound( cg.thirtySecondSound_a ); + } +} + +/* +============== +CG_ParseEntitiesFromString + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +void CG_ParseEntitiesFromString( void ) { + // allow calls to CG_Spawn*() + cg.spawning = qtrue; + cg.numSpawnVars = 0; + + // the worldspawn is not an actual entity, but it still + // has a "spawn" function to perform any global setup + // needed by a level (setting configstrings or cvars, etc) + if ( !CG_ParseSpawnVars() ) { + CG_Error( "ParseEntities: no entities" ); + } + SP_worldspawn(); + + // parse ents + while ( CG_ParseSpawnVars() ) { + CG_ParseEntityFromSpawnVars(); + } + + cg.spawning = qfalse; // any future calls to CG_Spawn*() will be errors +} diff --git a/src/cgame/cg_syscalls.c b/src/cgame/cg_syscalls.c new file mode 100644 index 0000000..77a6c78 --- /dev/null +++ b/src/cgame/cg_syscalls.c @@ -0,0 +1,544 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_syscalls.c -- this file is only included when building a dll +// cg_syscalls.asm is included instead when building a qvm +#include "cg_local.h" + +static int ( QDECL * syscall )( int arg, ... ) = ( int ( QDECL * )( int, ... ) ) - 1; + +#if defined( __MACOS__ ) +#pragma export on +#endif +void dllEntry( int ( QDECL *syscallptr )( int arg,... ) ) { + syscall = syscallptr; +} +#if defined( __MACOS__ ) +#pragma export off +#endif + +int PASSFLOAT( float x ) { + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_Print( const char *fmt ) { + syscall( CG_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) { + syscall( CG_ERROR, fmt ); +} + +int trap_Milliseconds( void ) { + return syscall( CG_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { + syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) { + syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Argc( void ) { + return syscall( CG_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void trap_Args( char *buffer, int bufferLength ) { + syscall( CG_ARGS, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( CG_FS_FCLOSEFILE, f ); +} + +void trap_SendConsoleCommand( const char *text ) { + syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void trap_AddCommand( const char *cmdName ) { + syscall( CG_ADDCOMMAND, cmdName ); +} + +void trap_SendClientCommand( const char *s ) { + syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void trap_UpdateScreen( void ) { + syscall( CG_UPDATESCREEN ); +} + +void trap_CM_LoadMap( const char *mapname ) { + syscall( CG_CM_LOADMAP, mapname ); +} + +int trap_CM_NumInlineModels( void ) { + return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) { + return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); +} + +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { + return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ) { + return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); +} + +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { + syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); +} + +//----(SA) added +void trap_S_StartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx, int flags ) { + syscall( CG_S_STARTSOUNDEX, origin, entityNum, entchannel, sfx, flags ); +} +//----(SA) end + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void trap_S_ClearLoopingSounds( qboolean killall ) { + syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); +} + +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx, int volume ) { + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, 1250, sfx, volume ); // volume was previously removed from CG_S_ADDLOOPINGSOUND. I added 'range' +} + +//----(SA) added +void trap_S_AddRangedLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx, int range ) { + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, range, sfx, 255 ); // RF, assume full volume, since thats how it worked before +} +//----(SA) end + +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, 1250, sfx, 255 ); //----(SA) modified +} + +void trap_S_StopLoopingSound( int entityNum ) { + syscall( CG_S_STOPLOOPINGSOUND, entityNum ); +} + +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +// Ridah, talking animations +int trap_S_GetVoiceAmplitude( int entityNum ) { + return syscall( CG_S_GETVOICEAMPLITUDE, entityNum ); +} +// done. + +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { + syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample ) { + CG_DrawInformation(); + return syscall( CG_S_REGISTERSOUND, sample ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +void trap_S_StartStreamingSound( const char *intro, const char *loop, int entnum, int channel, int attenuation ) { + syscall( CG_S_STARTSTREAMINGSOUND, intro, loop, entnum, channel, attenuation ); +} + +void trap_R_LoadWorldMap( const char *mapname ) { + syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + CG_DrawInformation(); + return syscall( CG_R_REGISTERMODEL, name ); +} + +//----(SA) added +qboolean trap_R_GetSkinModel( qhandle_t skinid, const char *type, char *name ) { + return syscall( CG_R_GETSKINMODEL, skinid, type, name ); +} + +qhandle_t trap_R_GetShaderFromModel( qhandle_t modelid, int surfnum, int withlightmap ) { + return syscall( CG_R_GETMODELSHADER, modelid, surfnum, withlightmap ); +} +//----(SA) end + +qhandle_t trap_R_RegisterSkin( const char *name ) { + CG_DrawInformation(); + return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) { + CG_DrawInformation(); + return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + CG_DrawInformation(); + return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ) { + syscall( CG_R_REGISTERFONT, fontName, pointSize, font ); +} + +void trap_R_ClearScene( void ) { + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts ) { + syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +// Ridah +void trap_R_AddPolysToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) { + syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, numPolys ); +} +// done. + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b, int overdraw ) { + syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT( intensity ), PASSFLOAT( r ), PASSFLOAT( g ), PASSFLOAT( b ), overdraw ); +} + +//----(SA) +void trap_R_AddCoronaToScene( const vec3_t org, float r, float g, float b, float scale, int id, qboolean visible ) { + syscall( CG_R_ADDCORONATOSCENE, org, PASSFLOAT( r ), PASSFLOAT( g ), PASSFLOAT( b ), PASSFLOAT( scale ), id, visible ); +} +//----(SA) + +//----(SA) +void trap_R_SetFog( int fogvar, int var1, int var2, float r, float g, float b, float density ) { + syscall( CG_R_SETFOG, fogvar, var1, var2, PASSFLOAT( r ), PASSFLOAT( g ), PASSFLOAT( b ), PASSFLOAT( density ) ); +} +//----(SA) +void trap_R_RenderScene( const refdef_t *fd ) { + syscall( CG_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( CG_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT( x ), PASSFLOAT( y ), PASSFLOAT( w ), PASSFLOAT( h ), PASSFLOAT( s1 ), PASSFLOAT( t1 ), PASSFLOAT( s2 ), PASSFLOAT( t2 ), hShader ); +} + +void trap_R_DrawRotatedPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, float angle ) { + syscall( CG_R_DRAWROTATEDPIC, PASSFLOAT( x ), PASSFLOAT( y ), PASSFLOAT( w ), PASSFLOAT( h ), PASSFLOAT( s1 ), PASSFLOAT( t1 ), PASSFLOAT( s2 ), PASSFLOAT( t2 ), hShader, PASSFLOAT( angle ) ); +} + +void trap_R_DrawStretchPicGradient( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, + const float *gradientColor, int gradientType ) { + syscall( CG_R_DRAWSTRETCHPIC_GRADIENT, PASSFLOAT( x ), PASSFLOAT( y ), PASSFLOAT( w ), PASSFLOAT( h ), PASSFLOAT( s1 ), PASSFLOAT( t1 ), PASSFLOAT( s2 ), PASSFLOAT( t2 ), hShader, gradientColor, gradientType ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +int trap_R_LerpTag( orientation_t *tag, const refEntity_t *refent, const char *tagName, int startIndex ) { + return syscall( CG_R_LERPTAG, tag, refent, tagName, startIndex ); +} + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) { + syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean trap_GetServerCommand( int serverCommandNumber ) { + return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) { + return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void trap_SetUserCmdValue( int stateValue, int holdableValue, float sensitivityScale, int mpSetup, int mpIdentClient ) { + syscall( CG_SETUSERCMDVALUE, stateValue, holdableValue, PASSFLOAT( sensitivityScale ), mpSetup, mpIdentClient ); +} + +void trap_SetClientLerpOrigin( float x, float y, float z ) { + syscall( CG_SETCLIENTLERPORIGIN, PASSFLOAT( x ), PASSFLOAT( y ), PASSFLOAT( z ) ); +} + +void testPrintInt( char *string, int i ) { + syscall( CG_TESTPRINTINT, string, i ); +} + +void testPrintFloat( char *string, float f ) { + syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT( f ) ); +} + +int trap_MemoryRemaining( void ) { + return syscall( CG_MEMORY_REMAINING ); +} + +qboolean trap_loadCamera( int camNum, const char *name ) { + return syscall( CG_LOADCAMERA, camNum, name ); +} + +void trap_startCamera( int camNum, int time ) { + syscall( CG_STARTCAMERA, camNum, time ); +} + +qboolean trap_getCameraInfo( int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov ) { + return syscall( CG_GETCAMERAINFO, camNum, time, origin, angles, fov ); +} + + +qboolean trap_Key_IsDown( int keynum ) { + return syscall( CG_KEY_ISDOWN, keynum ); +} + +int trap_Key_GetCatcher( void ) { + return syscall( CG_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { + syscall( CG_KEY_SETCATCHER, catcher ); +} + +int trap_Key_GetKey( const char *binding ) { + return syscall( CG_KEY_GETKEY, binding ); +} + + +int trap_PC_AddGlobalDefine( char *define ) { + return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); +} + +int trap_PC_LoadSource( const char *filename ) { + return syscall( CG_PC_LOAD_SOURCE, filename ); +} + +int trap_PC_FreeSource( int handle ) { + return syscall( CG_PC_FREE_SOURCE, handle ); +} + +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { + return syscall( CG_PC_READ_TOKEN, handle, pc_token ); +} + + +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { + return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_S_StopBackgroundTrack( void ) { + syscall( CG_S_STOPBACKGROUNDTRACK ); +} + +int trap_RealTime( qtime_t *qtime ) { + return syscall( CG_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) { + syscall( CG_SNAPVECTOR, v ); +} + +void trap_SendMoveSpeedsToGame( int entnum, char *movespeeds ) { + syscall( CG_SENDMOVESPEEDSTOGAME, entnum, movespeeds ); +} + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ) { + return syscall( CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits ); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic( int handle ) { + return syscall( CG_CIN_STOPCINEMATIC, handle ); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic( int handle ) { + return syscall( CG_CIN_RUNCINEMATIC, handle ); +} + + +// draws the current frame +void trap_CIN_DrawCinematic( int handle ) { + syscall( CG_CIN_DRAWCINEMATIC, handle ); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents( int handle, int x, int y, int w, int h ) { + syscall( CG_CIN_SETEXTENTS, handle, x, y, w, h ); +} + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { + return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); +} + +//----(SA) added +// bring up a popup menu +extern void Menus_OpenByName( const char *p ); + +void trap_UI_Popup( const char *arg0 ) { + syscall( CG_INGAME_POPUP, arg0 ); +} + +// NERVE - SMF +void trap_UI_LimboChat( const char *arg0 ) { + syscall( CG_LIMBOCHAT, arg0 ); +} + +void trap_UI_ClosePopup( const char *arg0 ) { + syscall( CG_INGAME_CLOSEPOPUP, arg0 ); +} + +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + syscall( CG_KEY_GETBINDINGBUF, keynum, buf, buflen ); +} + +void trap_Key_SetBinding( int keynum, const char *binding ) { + syscall( CG_KEY_SETBINDING, keynum, binding ); +} + +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { + syscall( CG_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen ); +} + +#define MAX_VA_STRING 32000 + +char* trap_TranslateString( const char *string ) { + static char staticbuf[2][MAX_VA_STRING]; + static int bufcount = 0; + char *buf; + + buf = staticbuf[bufcount++ % 2]; + + syscall( CG_TRANSLATE_STRING, string, buf ); + + return buf; +} +// -NERVE - SMF diff --git a/src/cgame/cg_trails.c b/src/cgame/cg_trails.c new file mode 100644 index 0000000..4515a9e --- /dev/null +++ b/src/cgame/cg_trails.c @@ -0,0 +1,808 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// Ridah, cg_trails.c - draws a trail using multiple junction points + +#include "cg_local.h" + +typedef struct trailJunc_s +{ + struct trailJunc_s *nextGlobal, *prevGlobal; // next junction in the global list it is in (free or used) + struct trailJunc_s *nextJunc; // next junction in the trail + struct trailJunc_s *nextHead, *prevHead; // next head junc in the world + + qboolean inuse, freed; + int ownerIndex; + qhandle_t shader; + + int sType; + int flags; + float sTex; + vec3_t pos; + int spawnTime, endTime; + float alphaStart, alphaEnd; + vec3_t colorStart, colorEnd; + float widthStart, widthEnd; + + // current settings + float alpha; + float width; + vec3_t color; + +} trailJunc_t; + +#define MAX_TRAILJUNCS 4096 + +trailJunc_t trailJuncs[MAX_TRAILJUNCS]; +trailJunc_t *freeTrails, *activeTrails; +trailJunc_t *headTrails; + +qboolean initTrails = qfalse; + +int numTrailsInuse; + +/* +=============== +CG_ClearTrails +=============== +*/ +void CG_ClearTrails( void ) { + int i; + + memset( trailJuncs, 0, sizeof( trailJunc_t ) * MAX_TRAILJUNCS ); + + freeTrails = trailJuncs; + activeTrails = NULL; + headTrails = NULL; + + for ( i = 0 ; i < MAX_TRAILJUNCS ; i++ ) + { + trailJuncs[i].nextGlobal = &trailJuncs[i + 1]; + + if ( i > 0 ) { + trailJuncs[i].prevGlobal = &trailJuncs[i - 1]; + } else { + trailJuncs[i].prevGlobal = NULL; + } + + trailJuncs[i].inuse = qfalse; + } + trailJuncs[MAX_TRAILJUNCS - 1].nextGlobal = NULL; + + initTrails = qtrue; + numTrailsInuse = 0; +} + +/* +=============== +CG_SpawnTrailJunc +=============== +*/ +trailJunc_t *CG_SpawnTrailJunc( trailJunc_t *headJunc ) { + trailJunc_t *j; + + if ( !freeTrails ) { + return NULL; + } + + if ( cg_paused.integer ) { + return NULL; + } + + // select the first free trail, and remove it from the list + j = freeTrails; + freeTrails = j->nextGlobal; + if ( freeTrails ) { + freeTrails->prevGlobal = NULL; + } + + j->nextGlobal = activeTrails; + if ( activeTrails ) { + activeTrails->prevGlobal = j; + } + activeTrails = j; + j->prevGlobal = NULL; + j->inuse = qtrue; + j->freed = qfalse; + + // if this owner has a headJunc, add us to the start + if ( headJunc ) { + // remove the headJunc from the list of heads + if ( headJunc == headTrails ) { + headTrails = headJunc->nextHead; + if ( headTrails ) { + headTrails->prevHead = NULL; + } + } else { + if ( headJunc->nextHead ) { + headJunc->nextHead->prevHead = headJunc->prevHead; + } + if ( headJunc->prevHead ) { + headJunc->prevHead->nextHead = headJunc->nextHead; + } + } + headJunc->prevHead = NULL; + headJunc->nextHead = NULL; + } + // make us the headTrail + if ( headTrails ) { + headTrails->prevHead = j; + } + j->nextHead = headTrails; + j->prevHead = NULL; + headTrails = j; + + j->nextJunc = headJunc; // if headJunc is NULL, then we'll just be the end of the list + + numTrailsInuse++; + + // debugging +// CG_Printf( "NumTrails: %i\n", numTrailsInuse ); + + return j; +} + + +/* +=============== +CG_AddTrailJunc + + returns the index of the trail junction created + + Used for generic trails +=============== +*/ +int CG_AddTrailJunc( int headJuncIndex, qhandle_t shader, int spawnTime, int sType, vec3_t pos, int trailLife, float alphaStart, float alphaEnd, float startWidth, float endWidth, int flags, vec3_t colorStart, vec3_t colorEnd, float sRatio, float animSpeed ) { + trailJunc_t *j, *headJunc; + + if ( headJuncIndex < 0 || headJuncIndex >= MAX_TRAILJUNCS ) { + return 0; + } + + if ( headJuncIndex > 0 ) { + headJunc = &trailJuncs[headJuncIndex - 1]; + + if ( !headJunc->inuse ) { + headJunc = NULL; + } + } else { + headJunc = NULL; + } + + j = CG_SpawnTrailJunc( headJunc ); + if ( !j ) { +// CG_Printf("couldnt spawn trail junc\n"); + return 0; + } + + if ( alphaStart > 1.0 ) { + alphaStart = 1.0; + } + if ( alphaStart < 0.0 ) { + alphaStart = 0.0; + } + if ( alphaEnd > 1.0 ) { + alphaEnd = 1.0; + } + if ( alphaEnd < 0.0 ) { + alphaEnd = 0.0; + } + + // setup the trail junction + j->shader = shader; + j->sType = sType; + VectorCopy( pos, j->pos ); + j->flags = flags; + + j->spawnTime = spawnTime; + j->endTime = spawnTime + trailLife; + + VectorCopy( colorStart, j->colorStart ); + VectorCopy( colorEnd, j->colorEnd ); + + j->alphaStart = alphaStart; + j->alphaEnd = alphaEnd; + + j->widthStart = startWidth; + j->widthEnd = endWidth; + + if ( sType == STYPE_REPEAT ) { + if ( headJunc ) { + j->sTex = headJunc->sTex + ( ( Distance( headJunc->pos, pos ) / sRatio ) / j->widthEnd ); + } else { + // FIXME: need a way to specify offset timing + j->sTex = ( animSpeed * ( 1.0 - ( (float)( cg.time % 1000 ) / 1000.0 ) ) ) / ( sRatio ); +// j->sTex = 0; + } + } + + return ( (int)( j - trailJuncs ) + 1 ); +} + +/* +=============== +CG_AddSparkJunc + + returns the index of the trail junction created +=============== +*/ +int CG_AddSparkJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, float alphaStart, float alphaEnd, float startWidth, float endWidth ) { + trailJunc_t *j, *headJunc; + + if ( headJuncIndex < 0 || headJuncIndex >= MAX_TRAILJUNCS ) { + return 0; + } + + if ( headJuncIndex > 0 ) { + headJunc = &trailJuncs[headJuncIndex - 1]; + + if ( !headJunc->inuse ) { + headJunc = NULL; + } + } else { + headJunc = NULL; + } + + j = CG_SpawnTrailJunc( headJunc ); + if ( !j ) { + return 0; + } + + // setup the trail junction + j->shader = shader; + j->sType = STYPE_STRETCH; + VectorCopy( pos, j->pos ); + j->flags = TJFL_NOCULL; // don't worry about fading up close + + j->spawnTime = cg.time; + j->endTime = cg.time + trailLife; + + VectorSet( j->colorStart, 1.0, 0.8 + 0.2 * alphaStart, 0.4 + 0.4 * alphaStart ); + VectorSet( j->colorEnd, 1.0, 0.8 + 0.2 * alphaEnd, 0.4 + 0.4 * alphaEnd ); +// VectorScale( j->colorStart, alphaStart, j->colorStart ); +// VectorScale( j->colorEnd, alphaEnd, j->colorEnd ); + + j->alphaStart = alphaStart * 2; + j->alphaEnd = alphaEnd * 2; +// j->alphaStart = 1.0; +// j->alphaEnd = 1.0; + + j->widthStart = startWidth; + j->widthEnd = endWidth; + + return ( (int)( j - trailJuncs ) + 1 ); +} + +/* +=============== +CG_AddSmokeJunc + + returns the index of the trail junction created +=============== +*/ +int CG_AddSmokeJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, float alpha, float startWidth, float endWidth ) { +#define ST_RATIO 4.0 // sprite image: width / height + trailJunc_t *j, *headJunc; + + if ( headJuncIndex < 0 || headJuncIndex >= MAX_TRAILJUNCS ) { + return 0; + } + + if ( headJuncIndex > 0 ) { + headJunc = &trailJuncs[headJuncIndex - 1]; + + if ( !headJunc->inuse ) { + headJunc = NULL; + } + } else { + headJunc = NULL; + } + + j = CG_SpawnTrailJunc( headJunc ); + if ( !j ) { + return 0; + } + + // setup the trail junction + j->shader = shader; + j->sType = STYPE_REPEAT; + VectorCopy( pos, j->pos ); + j->flags = TJFL_FADEIN; + + j->spawnTime = cg.time; + j->endTime = cg.time + trailLife; + + if ( cgs.gametype >= GT_WOLF ) { + VectorSet( j->colorStart, 0.7, 0.7, 0.7 ); + VectorSet( j->colorEnd, 0.0, 0.0, 0.0 ); + } else { + VectorSet( j->colorStart, 0.0, 0.0, 0.0 ); + VectorSet( j->colorEnd, 0.0, 0.0, 0.0 ); + } + + j->alphaStart = alpha; + j->alphaEnd = 0.0; + + j->widthStart = startWidth; + j->widthEnd = endWidth; + + if ( headJunc ) { + j->sTex = headJunc->sTex + ( ( Distance( headJunc->pos, pos ) / ST_RATIO ) / j->widthEnd ); + } else { + // first junction, so this will become the "tail" very soon, make it fade out + j->sTex = 0; + j->alphaStart = 0.0; + j->alphaEnd = 0.0; + } + + return ( (int)( j - trailJuncs ) + 1 ); +} + +void CG_KillTrail( trailJunc_t *t ); + +/* +=========== +CG_FreeTrailJunc +=========== +*/ +void CG_FreeTrailJunc( trailJunc_t *junc ) { + // kill any juncs after us, so they aren't left hanging + if ( junc->nextJunc ) { + CG_KillTrail( junc ); + } + + // make it non-active + junc->inuse = qfalse; + junc->freed = qtrue; + if ( junc->nextGlobal ) { + junc->nextGlobal->prevGlobal = junc->prevGlobal; + } + if ( junc->prevGlobal ) { + junc->prevGlobal->nextGlobal = junc->nextGlobal; + } + if ( junc == activeTrails ) { + activeTrails = junc->nextGlobal; + } + + // if it's a head, remove it + if ( junc == headTrails ) { + headTrails = junc->nextHead; + } + if ( junc->nextHead ) { + junc->nextHead->prevHead = junc->prevHead; + } + if ( junc->prevHead ) { + junc->prevHead->nextHead = junc->nextHead; + } + junc->nextHead = NULL; + junc->prevHead = NULL; + + // stick it in the free list + junc->prevGlobal = NULL; + junc->nextGlobal = freeTrails; + if ( freeTrails ) { + freeTrails->prevGlobal = junc; + } + freeTrails = junc; + + numTrailsInuse--; +} + +/* +=========== +CG_KillTrail +=========== +*/ +void CG_KillTrail( trailJunc_t *t ) { + trailJunc_t *next; + if ( !t->inuse && t->freed ) { + return; + } + next = t->nextJunc; + if ( next < &trailJuncs[0] || next >= &trailJuncs[MAX_TRAILJUNCS] ) { + next = NULL; + } + t->nextJunc = NULL; + if ( next->nextJunc && next->nextJunc == t ) { + next->nextJunc = NULL; + } + if ( next ) { + CG_FreeTrailJunc( next ); + } +} + +/* +============== +CG_AddTrailToScene + + TODO: this can do with some major optimization +============== +*/ +static vec3_t vforward, vright, vup; +#define MAX_TRAIL_VERTS 2048 +static polyVert_t verts[MAX_TRAIL_VERTS]; +static polyVert_t outVerts[MAX_TRAIL_VERTS * 3]; + +void CG_AddTrailToScene( trailJunc_t *trail, int iteration, int numJuncs ) { + int k, i, n, l, numOutVerts; + polyVert_t mid; + float mod[4]; + float sInc, s; + trailJunc_t *j, *jNext; + vec3_t fwd, up, p, v; + // clipping vars + #define TRAIL_FADE_CLOSE_DIST 64.0 + #define TRAIL_FADE_FAR_SCALE 4.0 + vec3_t viewProj; + float viewDist, fadeAlpha; + + // add spark shader at head position + if ( trail->flags & TJFL_SPARKHEADFLARE ) { + j = trail; + VectorCopy( j->pos, p ); + VectorMA( p, -j->width * 2, vup, p ); + VectorMA( p, -j->width * 2, vright, p ); + VectorCopy( p, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = ( unsigned char )( j->alpha * 255.0 ); + + VectorCopy( j->pos, p ); + VectorMA( p, -j->width * 2, vup, p ); + VectorMA( p, j->width * 2, vright, p ); + VectorCopy( p, verts[1].xyz ); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = ( unsigned char )( j->alpha * 255.0 ); + + VectorCopy( j->pos, p ); + VectorMA( p, j->width * 2, vup, p ); + VectorMA( p, j->width * 2, vright, p ); + VectorCopy( p, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = ( unsigned char )( j->alpha * 255.0 ); + + VectorCopy( j->pos, p ); + VectorMA( p, j->width * 2, vup, p ); + VectorMA( p, -j->width * 2, vright, p ); + VectorCopy( p, verts[3].xyz ); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = ( unsigned char )( j->alpha * 255.0 ); + + trap_R_AddPolyToScene( cgs.media.sparkFlareShader, 4, verts ); + } + +// if (trail->flags & TJFL_CROSSOVER && iteration < 1) { +// iteration = 1; +// } + + sInc = 0; + + if ( !numJuncs ) { + // first count the number of juncs in the trail + j = trail; + numJuncs = 0; + sInc = 0; + while ( j ) { + numJuncs++; + + // check for a dead next junc + if ( !j->inuse && j->nextJunc && !j->nextJunc->inuse ) { + CG_KillTrail( j ); + } else if ( j->nextJunc && j->nextJunc->freed ) { + // not sure how this can happen, but it does, and causes infinite loops + j->nextJunc = NULL; + } + + if ( j->nextJunc ) { + sInc += VectorDistance( j->nextJunc->pos, j->pos ); + } + + j = j->nextJunc; + } + } + + if ( numJuncs < 2 ) { + return; + } + + s = 0; + if ( trail->sType == STYPE_STRETCH ) { + //sInc = ((1.0 - 0.1) / (float)(numJuncs)); // hack, the end of funnel shows a bit of the start (looping) + s = 0.05; + //s = 0.05; + } else if ( trail->sType == STYPE_REPEAT ) { + s = trail->sTex; + } + + // now traverse the list + j = trail; + jNext = j->nextJunc; + i = 0; + while ( jNext ) { + + // first get the directional vectors to the next junc + VectorSubtract( jNext->pos, j->pos, fwd ); + GetPerpendicularViewVector( cg.refdef.vieworg, j->pos, jNext->pos, up ); + + // if it's a crossover, draw it twice + if ( j->flags & TJFL_CROSSOVER ) { + if ( iteration > 0 ) { + ProjectPointOntoVector( cg.refdef.vieworg, j->pos, jNext->pos, viewProj ); + VectorSubtract( cg.refdef.vieworg, viewProj, v ); + VectorNormalize( v ); + + if ( iteration == 1 ) { + VectorMA( up, 0.3, v, up ); + } else { + VectorMA( up, -0.3, v, up ); + } + VectorNormalize( up ); + } + } + // do fading when moving towards the projection point onto the trail segment vector + else if ( !( j->flags & TJFL_NOCULL ) && ( j->widthEnd > 4 || jNext->widthEnd > 4 ) ) { + ProjectPointOntoVector( cg.refdef.vieworg, j->pos, jNext->pos, viewProj ); + viewDist = Distance( viewProj, cg.refdef.vieworg ); + if ( viewDist < ( TRAIL_FADE_CLOSE_DIST * TRAIL_FADE_FAR_SCALE ) ) { + if ( viewDist < TRAIL_FADE_CLOSE_DIST ) { + fadeAlpha = 0.0; + } else { + fadeAlpha = ( viewDist - TRAIL_FADE_CLOSE_DIST ) / ( TRAIL_FADE_CLOSE_DIST * TRAIL_FADE_FAR_SCALE ); + } + if ( fadeAlpha < j->alpha ) { + j->alpha = fadeAlpha; + } + if ( fadeAlpha < jNext->alpha ) { + jNext->alpha = fadeAlpha; + } + } + } + + // now output the QUAD for this segment + + // 1 ---- + VectorMA( j->pos, 0.5 * j->width, up, p ); + VectorCopy( p, verts[i].xyz ); + verts[i].st[0] = s; + verts[i].st[1] = 1.0; + for ( k = 0; k < 3; k++ ) + verts[i].modulate[k] = ( unsigned char )( j->color[k] * 255.0 ); + verts[i].modulate[3] = ( unsigned char )( j->alpha * 255.0 ); + + // blend this with the previous junc + if ( j != trail ) { + VectorAdd( verts[i].xyz, verts[i - 1].xyz, verts[i].xyz ); + VectorScale( verts[i].xyz, 0.5, verts[i].xyz ); + VectorCopy( verts[i].xyz, verts[i - 1].xyz ); + } else if ( j->flags & TJFL_FADEIN ) { + verts[i].modulate[3] = 0; // fade in + } + + i++; + + // 2 ---- + VectorMA( p, -1 * j->width, up, p ); + VectorCopy( p, verts[i].xyz ); + verts[i].st[0] = s; + verts[i].st[1] = 0.0; + for ( k = 0; k < 3; k++ ) + verts[i].modulate[k] = ( unsigned char )( j->color[k] * 255.0 ); + verts[i].modulate[3] = ( unsigned char )( j->alpha * 255.0 ); + + // blend this with the previous junc + if ( j != trail ) { + VectorAdd( verts[i].xyz, verts[i - 3].xyz, verts[i].xyz ); + VectorScale( verts[i].xyz, 0.5, verts[i].xyz ); + VectorCopy( verts[i].xyz, verts[i - 3].xyz ); + } else if ( j->flags & TJFL_FADEIN ) { + verts[i].modulate[3] = 0; // fade in + } + + i++; + + if ( trail->sType == STYPE_REPEAT ) { + s = jNext->sTex; + } else { + //s += sInc; + s += VectorDistance( j->pos, jNext->pos ) / sInc; + if ( s > 1.0 ) { + s = 1.0; + } + } + + // 3 ---- + VectorMA( jNext->pos, -0.5 * jNext->width, up, p ); + VectorCopy( p, verts[i].xyz ); + verts[i].st[0] = s; + verts[i].st[1] = 0.0; + for ( k = 0; k < 3; k++ ) + verts[i].modulate[k] = ( unsigned char )( jNext->color[k] * 255.0 ); + verts[i].modulate[3] = ( unsigned char )( jNext->alpha * 255.0 ); + i++; + + // 4 ---- + VectorMA( p, jNext->width, up, p ); + VectorCopy( p, verts[i].xyz ); + verts[i].st[0] = s; + verts[i].st[1] = 1.0; + for ( k = 0; k < 3; k++ ) + verts[i].modulate[k] = ( unsigned char )( jNext->color[k] * 255.0 ); + verts[i].modulate[3] = ( unsigned char )( jNext->alpha * 255.0 ); + i++; + + if ( i + 4 > MAX_TRAIL_VERTS ) { + break; + } + + j = jNext; + jNext = j->nextJunc; + } + + if ( trail->flags & TJFL_FIXDISTORT ) { + // build the list of outVerts, by dividing up the QUAD's into 4 Tri's each, so as to allow + // any shaped (convex) Quad without bilinear distortion + for ( k = 0, numOutVerts = 0; k < i; k += 4 ) { + VectorCopy( verts[k].xyz, mid.xyz ); + mid.st[0] = verts[k].st[0]; + mid.st[1] = verts[k].st[1]; + for ( l = 0; l < 4; l++ ) { + mod[l] = (float)verts[k].modulate[l]; + } + for ( n = 1; n < 4; n++ ) { + VectorAdd( verts[k + n].xyz, mid.xyz, mid.xyz ); + mid.st[0] += verts[k + n].st[0]; + mid.st[1] += verts[k + n].st[1]; + for ( l = 0; l < 4; l++ ) { + mod[l] += (float)verts[k + n].modulate[l]; + } + } + VectorScale( mid.xyz, 0.25, mid.xyz ); + mid.st[0] *= 0.25; + mid.st[1] *= 0.25; + for ( l = 0; l < 4; l++ ) { + mid.modulate[l] = ( unsigned char )( mod[l] / 4.0 ); + } + + // now output the tri's + for ( n = 0; n < 4; n++ ) { + outVerts[numOutVerts++] = verts[k + n]; + outVerts[numOutVerts++] = mid; + if ( n < 3 ) { + outVerts[numOutVerts++] = verts[k + n + 1]; + } else { + outVerts[numOutVerts++] = verts[k]; + } + } + + } + + if ( !( trail->flags & TJFL_NOPOLYMERGE ) ) { + trap_R_AddPolysToScene( trail->shader, 3, &outVerts[0], numOutVerts / 3 ); + } else { + int k; + for ( k = 0; k < numOutVerts / 3; k++ ) { + trap_R_AddPolyToScene( trail->shader, 3, &outVerts[k * 3] ); + } + } + } else + { + // send the polygons + // FIXME: is it possible to send a GL_STRIP here? We are actually sending 2x the verts we really need to + if ( !( trail->flags & TJFL_NOPOLYMERGE ) ) { + trap_R_AddPolysToScene( trail->shader, 4, &verts[0], i / 4 ); + } else { + int k; + for ( k = 0; k < i / 4; k++ ) { + trap_R_AddPolyToScene( trail->shader, 4, &verts[k * 4] ); + } + } + } + + // do we need to make another pass? + if ( trail->flags & TJFL_CROSSOVER ) { + if ( iteration < 2 ) { + CG_AddTrailToScene( trail, iteration + 1, numJuncs ); + } + } + +} + +/* +=============== +CG_AddTrails +=============== +*/ +void CG_AddTrails( void ) { + float lifeFrac; + trailJunc_t *j, *jNext; + + if ( !initTrails ) { + CG_ClearTrails(); + } + + //AngleVectors( cg.snap->ps.viewangles, vforward, vright, vup ); + VectorCopy( cg.refdef.viewaxis[0], vforward ); + VectorCopy( cg.refdef.viewaxis[1], vright ); + VectorCopy( cg.refdef.viewaxis[2], vup ); + + // update the settings for each junc + j = activeTrails; + while ( j ) { + lifeFrac = (float)( cg.time - j->spawnTime ) / (float)( j->endTime - j->spawnTime ); + if ( lifeFrac >= 1.0 ) { + j->inuse = qfalse; // flag it as dead + j->width = j->widthEnd; + j->alpha = j->alphaEnd; + if ( j->alpha > 1.0 ) { + j->alpha = 1.0; + } else if ( j->alpha < 0.0 ) { + j->alpha = 0.0; + } + VectorCopy( j->colorEnd, j->color ); + } else { + j->width = j->widthStart + ( j->widthEnd - j->widthStart ) * lifeFrac; + j->alpha = j->alphaStart + ( j->alphaEnd - j->alphaStart ) * lifeFrac; + if ( j->alpha > 1.0 ) { + j->alpha = 1.0; + } else if ( j->alpha < 0.0 ) { + j->alpha = 0.0; + } + VectorSubtract( j->colorEnd, j->colorStart, j->color ); + VectorMA( j->colorStart, lifeFrac, j->color, j->color ); + } + + j = j->nextGlobal; + } + + // draw the trailHeads + j = headTrails; + while ( j ) { + jNext = j->nextHead; // in case it gets removed + if ( !j->inuse ) { + CG_FreeTrailJunc( j ); + } else { + CG_AddTrailToScene( j, 0, 0 ); + } + j = jNext; + } +} diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c new file mode 100644 index 0000000..00550c4 --- /dev/null +++ b/src/cgame/cg_view.c @@ -0,0 +1,1809 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering +#include "cg_local.h" + +//======================== +extern int notebookModel; +//======================== + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f( void ) { + vec3_t angles; + + memset( &cg.testModelEntity, 0, sizeof( cg.testModelEntity ) ); + if ( trap_Argc() < 2 ) { + return; + } + + Q_strncpyz( cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + + if ( trap_Argc() == 3 ) { + cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg.testModelEntity.frame = 1; + cg.testModelEntity.oldframe = 0; + } + if ( !cg.testModelEntity.hModel ) { + CG_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); + + angles[PITCH] = 0; + angles[YAW] = 180 + cg.refdefViewAngles[1]; + angles[ROLL] = 0; + + AnglesToAxis( angles, cg.testModelEntity.axis ); + cg.testGun = qfalse; +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f( void ) { + CG_TestModel_f(); + cg.testGun = qtrue; + cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f( void ) { + cg.testModelEntity.frame++; + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f( void ) { + cg.testModelEntity.frame--; + if ( cg.testModelEntity.frame < 0 ) { + cg.testModelEntity.frame = 0; + } + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f( void ) { + cg.testModelEntity.skinNum++; + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f( void ) { + cg.testModelEntity.skinNum--; + if ( cg.testModelEntity.skinNum < 0 ) { + cg.testModelEntity.skinNum = 0; + } + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel( void ) { + int i; + + // re-register the model, because the level may have changed + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + if ( !cg.testModelEntity.hModel ) { + CG_Printf( "Can't register model\n" ); + return; + } + + // if testing a gun, set the origin reletive to the view origin + if ( cg.testGun ) { + VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); + VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); + VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); + VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); + + // allow the position to be adjusted + for ( i = 0 ; i < 3 ; i++ ) { + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; + } + } + + trap_R_AddRefEntityToScene( &cg.testModelEntity ); +} + + + +//============================================================================ + + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +//static float letterbox_frac = 1.0f; // used for transitioning to letterbox for cutscenes // TODO: add to cg. // TTimo: unused + +static void CG_CalcVrect( void ) { + int xsize, ysize; + float lbheight, lbdiff; + + // NERVE - SMF + if ( cg.limboMenu ) { + float x, y, w, h; + x = LIMBO_3D_X; + y = LIMBO_3D_Y; + w = LIMBO_3D_W; + h = LIMBO_3D_H; + + cg.refdef.width = 0; + CG_AdjustFrom640( &x, &y, &w, &h ); + + cg.refdef.x = x; + cg.refdef.y = y; + cg.refdef.width = w; + cg.refdef.height = h; + return; + } + // -NERVE - SMF + + // the intermission should allways be full screen + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + xsize = ysize = 100; + } else { + // bound normal viewsize + if ( cg_viewsize.integer < 30 ) { + trap_Cvar_Set( "cg_viewsize","30" ); + xsize = ysize = 30; + } else if ( cg_viewsize.integer > 100 ) { + trap_Cvar_Set( "cg_viewsize","100" ); + xsize = ysize = 100; + } else { + xsize = ysize = cg_viewsize.integer; + } + } + +//----(SA) added transition to/from letterbox +// normal aspect is xx:xx +// letterbox is yy:yy (85% of 'normal' height) + + lbheight = ysize * 0.85; + lbdiff = ysize - lbheight; + + if ( cg_letterbox.integer ) { + ysize = lbheight; +// if(letterbox_frac != 0) { +// letterbox_frac -= 0.01f; // (SA) TODO: make non fps dependant +// if(letterbox_frac < 0) +// letterbox_frac = 0; +// ysize += (lbdiff * letterbox_frac); +// } +// } else { +// if(letterbox_frac != 1) { +// letterbox_frac += 0.01f; // (SA) TODO: make non fps dependant +// if(letterbox_frac > 1) +// letterbox_frac = 1; +// ysize = lbheight + (lbdiff * letterbox_frac); +// } + } +//----(SA) end + + + cg.refdef.width = cgs.glconfig.vidWidth * xsize / 100; + cg.refdef.width &= ~1; + + cg.refdef.height = cgs.glconfig.vidHeight * ysize / 100; + cg.refdef.height &= ~1; + + cg.refdef.x = ( cgs.glconfig.vidWidth - cg.refdef.width ) / 2; + cg.refdef.y = ( cgs.glconfig.vidHeight - cg.refdef.height ) / 2; +} + +//============================================================================== + + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*/ +#define FOCUS_DISTANCE 512 +static void CG_OffsetThirdPersonView( void ) { + vec3_t forward, right, up; + vec3_t view; + vec3_t focusAngles; + trace_t trace; + static vec3_t mins = { -4, -4, -4 }; + static vec3_t maxs = { 4, 4, 4 }; + vec3_t focusPoint; + float focusDist; + float forwardScale, sideScale; + + cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; + + VectorCopy( cg.refdefViewAngles, focusAngles ); + + // if dead, look at killer + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + + if ( focusAngles[PITCH] > 45 ) { + focusAngles[PITCH] = 45; // don't go too far overhead + } + AngleVectors( focusAngles, forward, NULL, NULL ); + + VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg.refdef.vieworg, view ); + + view[2] += 8; + + cg.refdefViewAngles[PITCH] *= 0.5; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); + sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); + VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); + VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos, view ); + view[2] += ( 1.0 - trace.fraction ) * 32; + // try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + VectorCopy( trace.endpos, view ); + } + + + VectorCopy( view, cg.refdef.vieworg ); + + // select pitch to look at focus point from vieword + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) { + focusDist = 1; // should never happen + } + cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; +} + + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + // Ridah + if ( timeDelta < 0 ) { + cg.stepTime = cg.time; + } + if ( timeDelta < STEP_TIME ) { + cg.refdef.vieworg[2] -= cg.stepChange + * ( STEP_TIME - timeDelta ) / STEP_TIME; + } +} + +/* +================ +CG_KickAngles +================ +*/ +void CG_KickAngles( void ) { + const vec3_t centerSpeed = {2400, 2400, 2400}; + const float recoilCenterSpeed = 200; + const float recoilIgnoreCutoff = 15; + const float recoilMaxSpeed = 50; + const vec3_t maxKickAngles = {10,10,10}; + float idealCenterSpeed, kickChange; + int i, frametime, t; + float ft; + #define STEP 20 + char buf[32]; // NERVE - SMF + + // this code is frametime-dependant, so split it up into small chunks + //cg.kickAngles[PITCH] = 0; + cg.recoilPitchAngle = 0; + for ( t = cg.frametime; t > 0; t -= STEP ) { + if ( t > STEP ) { + frametime = STEP; + } else { + frametime = t; + } + + ft = ( (float)frametime / 1000 ); + + // kickAngles is spring-centered + for ( i = 0; i < 3; i++ ) { + if ( cg.kickAVel[i] || cg.kickAngles[i] ) { + // apply centering forces to kickAvel + if ( cg.kickAngles[i] && frametime ) { + idealCenterSpeed = -( 2.0 * ( cg.kickAngles[i] > 0 ) - 1.0 ) * centerSpeed[i]; + if ( idealCenterSpeed ) { + cg.kickAVel[i] += idealCenterSpeed * ft; + } + } + // add the kickAVel to the kickAngles + kickChange = cg.kickAVel[i] * ft; + if ( cg.kickAngles[i] && ( cg.kickAngles[i] < 0 ) != ( kickChange < 0 ) ) { // slower when returning to center + kickChange *= 0.06; + } + // check for crossing back over the center point + if ( !cg.kickAngles[i] || ( ( cg.kickAngles[i] + kickChange ) < 0 ) == ( cg.kickAngles[i] < 0 ) ) { + cg.kickAngles[i] += kickChange; + if ( !cg.kickAngles[i] && frametime ) { + cg.kickAVel[i] = 0; + } else if ( fabs( cg.kickAngles[i] ) > maxKickAngles[i] ) { + cg.kickAngles[i] = maxKickAngles[i] * ( ( 2 * ( cg.kickAngles[i] > 0 ) ) - 1 ); + cg.kickAVel[i] = 0; // force Avel to return us to center rather than keep going outside range + } + } else { // about to cross, so just zero it out + cg.kickAngles[i] = 0; + cg.kickAVel[i] = 0; + } + } + } + + // recoil is added to input viewangles per frame + if ( cg.recoilPitch ) { + // apply max recoil + if ( fabs( cg.recoilPitch ) > recoilMaxSpeed ) { + if ( cg.recoilPitch > 0 ) { + cg.recoilPitch = recoilMaxSpeed; + } else { + cg.recoilPitch = -recoilMaxSpeed; + } + } + // apply centering forces to kickAvel + if ( frametime ) { + idealCenterSpeed = -( 2.0 * ( cg.recoilPitch > 0 ) - 1.0 ) * recoilCenterSpeed * ft; + if ( idealCenterSpeed ) { + if ( fabs( idealCenterSpeed ) < fabs( cg.recoilPitch ) ) { + cg.recoilPitch += idealCenterSpeed; + } else { // back zero out + cg.recoilPitch = 0; + } + } + } + } + if ( fabs( cg.recoilPitch ) > recoilIgnoreCutoff ) { + cg.recoilPitchAngle += cg.recoilPitch * ft; + } + } + + // NERVE - SMF - only change cg_recoilPitch cvar when we need to + trap_Cvar_VariableStringBuffer( "cg_recoilPitch", buf, sizeof( buf ) ); + + if ( atof( buf ) != cg.recoilPitchAngle ) { + // encode the kick angles into a 24bit number, for sending to the client exe + trap_Cvar_Set( "cg_recoilPitch", va( "%f", cg.recoilPitchAngle ) ); + } +} + + +/* +CG_Concussive +*/ +void CG_Concussive( centity_t *cent ) { + float length; +// vec3_t dir, forward; + vec3_t vec; +// float dot; + + // + float pitchRecoilAdd, pitchAdd; + float yawRandom; + vec3_t recoil; + // + + if ( !cg.renderingThirdPerson && cent->currentState.density == cg.snap->ps.clientNum ) { + // + pitchRecoilAdd = 0; + pitchAdd = 0; + yawRandom = 0; + // + + VectorSubtract( cg.snap->ps.origin, cent->currentState.origin, vec ); + length = VectorLength( vec ); + + // pitchAdd = 12+rand()%3; + // yawRandom = 6; + + if ( length > 1024 ) { + return; + } + + pitchAdd = ( 32 / length ) * 64; + yawRandom = ( 32 / length ) * 64; + + // recoil[YAW] = crandom()*yawRandom; + if ( rand() % 100 > 50 ) { + recoil[YAW] = -yawRandom; + } else { + recoil[YAW] = yawRandom; + } + + recoil[ROLL] = -recoil[YAW]; // why not + recoil[PITCH] = -pitchAdd; + // scale it up a bit (easier to modify this while tweaking) + VectorScale( recoil, 30, recoil ); + // set the recoil + VectorCopy( recoil, cg.kickAVel ); + // set the recoil + cg.recoilPitch -= pitchRecoilAdd; + + } +} + + +/* +============== +CG_ZoomSway + sway for scoped weapons. + this takes aimspread into account so the view settles after a bit +============== +*/ +static void CG_ZoomSway( void ) { + float spreadfrac; + float phase; + + if ( !cg.zoomval ) { // not zoomed + return; + } + + if ( cg.snap->ps.eFlags & EF_MG42_ACTIVE ) { // don't draw when on mg_42 + return; + } + + spreadfrac = (float)cg.snap->ps.aimSpreadScale / 255.0; + + phase = cg.time / 1000.0 * ZOOM_PITCH_FREQUENCY * M_PI * 2; + cg.refdefViewAngles[PITCH] += ZOOM_PITCH_AMPLITUDE * sin( phase ) * ( spreadfrac + ZOOM_PITCH_MIN_AMPLITUDE ); + + phase = cg.time / 1000.0 * ZOOM_YAW_FREQUENCY * M_PI * 2; + cg.refdefViewAngles[YAW] += ZOOM_YAW_AMPLITUDE * sin( phase ) * ( spreadfrac + ZOOM_YAW_MIN_AMPLITUDE ); + +} + + + +/* +=============== +CG_OffsetFirstPersonView + +=============== +*/ +static void CG_OffsetFirstPersonView( void ) { + float *origin; + float *angles; + float bob; + float ratio; + float delta; + float speed; + float f; + vec3_t predictedVelocity; + int timeDelta; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg.refdef.vieworg; + angles = cg.refdefViewAngles; + + // if dead, fix the angle and don't add any kick + if ( !( cg.snap->ps.pm_flags & PMF_LIMBO ) && cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + origin[2] += cg.predictedPlayerState.viewheight; + return; + } + + // add angles based on weapon kick + VectorAdd( angles, cg.kick_angles, angles ); + + // RF, add new weapon kick angles + CG_KickAngles(); + VectorAdd( angles, cg.kickAngles, angles ); + // RF, pitch is already added + //angles[0] -= cg.kickAngles[PITCH]; + + // add angles based on damage kick + if ( cg.damageTime ) { + ratio = cg.time - cg.damageTime; + if ( ratio < DAMAGE_DEFLECT_TIME ) { + ratio /= DAMAGE_DEFLECT_TIME; + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } else { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if ( ratio > 0 ) { + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg.time - cg.landTime ) / FALL_TIME; + if ( ratio < 0 ) { + ratio = 0; + } + angles[PITCH] += ratio * cg.fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[0] ); + angles[PITCH] += delta * cg_runpitch.value; + + delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[1] ); + angles[ROLL] -= delta * cg_runroll.value; + + // add angles based on bob + + // make sure the bob is visible even at low speeds + speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + + delta = cg.bobfracsin * cg_bobpitch.value * speed; + if ( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) { + delta *= 3; // crouching + } + angles[PITCH] += delta; + delta = cg.bobfracsin * cg_bobroll.value * speed; + if ( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) { + delta *= 3; // crouching accentuates roll + } + if ( cg.bobcycle & 1 ) { + delta = -delta; + } + angles[ROLL] += delta; + +//=================================== + + // add view height + origin[2] += cg.predictedPlayerState.viewheight; + + // smooth out duck height changes + timeDelta = cg.time - cg.duckTime; + if ( timeDelta < 0 ) { // Ridah + cg.duckTime = cg.time - DUCK_TIME; + } + if ( timeDelta < DUCK_TIME ) { + cg.refdef.vieworg[2] -= cg.duckChange + * ( DUCK_TIME - timeDelta ) / DUCK_TIME; + } + + // add bob height + bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value; + if ( bob > 6 ) { + bob = 6; + } + + origin[2] += bob; + + + // add fall height + delta = cg.time - cg.landTime; + if ( delta < 0 ) { // Ridah + cg.landTime = cg.time - ( LAND_DEFLECT_TIME + LAND_RETURN_TIME ); + } + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + cg.refdef.vieworg[2] += cg.landChange * f; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + cg.refdef.vieworg[2] += cg.landChange * f; + } + + // add step offset + CG_StepOffset(); + + CG_ZoomSway(); + + // adjust for 'lean' + if ( cg.predictedPlayerState.leanf != 0 ) { + //add leaning offset + vec3_t right; + cg.refdefViewAngles[2] += cg.predictedPlayerState.leanf / 2.0f; + AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); + VectorMA( cg.refdef.vieworg, cg.predictedPlayerState.leanf, right, cg.refdef.vieworg ); + } + + // add kick offset + + VectorAdd( origin, cg.kick_origin, origin ); + + // pivot the eye based on a neck length +#if 0 + { +#define NECK_LENGTH 8 + vec3_t forward, up; + + cg.refdef.vieworg[2] -= NECK_LENGTH; + AngleVectors( cg.refdefViewAngles, forward, NULL, up ); + VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); + } +#endif +} + +//====================================================================== + +// +// Zoom controls +// + + +// probably move to server variables +float zoomTable[ZOOM_MAX_ZOOMS][2] = { +// max {out,in} + {0, 0}, + + {36, 8}, // binoc + {20, 4}, // sniper + {60, 20}, // snooper + {55, 55}, // fg42 + {55, 55} // mg42 +}; + +void CG_AdjustZoomVal( float val, int type ) { + cg.zoomval += val; + if ( cg.zoomval > zoomTable[type][ZOOM_OUT] ) { + cg.zoomval = zoomTable[type][ZOOM_OUT]; + } + if ( cg.zoomval < zoomTable[type][ZOOM_IN] ) { + cg.zoomval = zoomTable[type][ZOOM_IN]; + } +} + +void CG_ZoomIn_f( void ) { + if ( cg_entities[cg.snap->ps.clientNum].currentState.weapon == WP_SNIPERRIFLE ) { + CG_AdjustZoomVal( -( cg_zoomStepSniper.value ), ZOOM_SNIPER ); + } else if ( cg_entities[cg.snap->ps.clientNum].currentState.weapon == WP_SNOOPERSCOPE ) { + CG_AdjustZoomVal( -( cg_zoomStepSniper.value ), ZOOM_SNIPER ); // JPW NERVE per atvi request ZOOM_SNOOPER); + } else if ( cg.zoomedBinoc ) { + CG_AdjustZoomVal( -( cg_zoomStepSniper.value ), ZOOM_SNIPER ); // JPW NERVE per atvi request all use same vals to match menu (was zoomStepBinoc, ZOOM_BINOC); + } +} + +void CG_ZoomOut_f( void ) { + if ( cg_entities[cg.snap->ps.clientNum].currentState.weapon == WP_SNIPERRIFLE ) { + CG_AdjustZoomVal( cg_zoomStepSniper.value, ZOOM_SNIPER ); + } else if ( cg_entities[cg.snap->ps.clientNum].currentState.weapon == WP_SNOOPERSCOPE ) { + CG_AdjustZoomVal( cg_zoomStepSniper.value, ZOOM_SNIPER ); // JPW NERVE per atvi requestSNOOPER); + } else if ( cg.zoomedBinoc ) { + CG_AdjustZoomVal( cg_zoomStepSniper.value, ZOOM_SNIPER ); // JPW NERVE per atvi request BINOC); + } +} + + +/* +============== +CG_Zoom +============== +*/ +void CG_Zoom( void ) { + if ( cgs.gametype >= GT_WOLF && ( ( cg.snap->ps.pm_flags & PMF_FOLLOW ) || cg.demoPlayback ) ) { + cg.predictedPlayerState.eFlags = cg.snap->ps.eFlags; + cg.predictedPlayerState.weapon = cg.snap->ps.weapon; + + // check for scope wepon in use, and switch to if necessary + if ( cg.predictedPlayerState.weapon == WP_SNOOPERSCOPE ) { + cg.zoomval = cg_zoomDefaultSniper.value; // JPW NERVE was DefaultSnooper, changed per atvi req + } else if ( cg.predictedPlayerState.weapon == WP_SNIPERRIFLE ) { + cg.zoomval = cg_zoomDefaultSniper.value; + } else if ( cg.predictedPlayerState.weapon == WP_FG42SCOPE ) { + cg.zoomval = cg_zoomDefaultSniper.value; // JPW NERVE was DefaultFG, changed per atvi req + } else if ( !( cg.predictedPlayerState.eFlags & EF_ZOOMING ) ) { + cg.zoomval = 0; + } + } + if ( cg.predictedPlayerState.eFlags & EF_ZOOMING ) { + if ( cg.zoomedBinoc ) { + return; + } + cg.zoomedBinoc = qtrue; + cg.zoomTime = cg.time; + cg.zoomval = cg_zoomDefaultSniper.value; // JPW NERVE was DefaultBinoc, changed per atvi req + } else { + if ( !cg.zoomedBinoc ) { + return; + } + cg.zoomedBinoc = qfalse; + cg.zoomTime = cg.time; + + // check for scope wepon in use, and switch to if necessary + if ( cg.predictedPlayerState.weapon == WP_SNOOPERSCOPE ) { + cg.zoomval = cg_zoomDefaultSniper.value; // JPW NERVE was DefaultSnooper, changed per atvi req + } else if ( cg.predictedPlayerState.weapon == WP_SNIPERRIFLE ) { + cg.zoomval = cg_zoomDefaultSniper.value; + } else if ( cg.predictedPlayerState.weapon == WP_FG42SCOPE ) { + cg.zoomval = cg_zoomDefaultSniper.value; // JPW NERVE was DefaultFG, changed per atvi req + } else { + cg.zoomval = 0; + } + } +} + + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +static int CG_CalcFov( void ) { + static float lastfov = 90; // for transitions back from zoomed in modes + float x; + float phase; + float v; + int contents; + float fov_x, fov_y; + float zoomFov; + float f; + int inwater; + + CG_Zoom(); + + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 && !( cgs.gametype >= GT_WOLF && cg.snap->ps.pm_flags & PMF_FOLLOW ) ) { + cg.zoomedBinoc = qfalse; + cg.zoomTime = 0; + cg.zoomval = 0; + } + + if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + // if in intermission, use a fixed value + fov_x = 90; + } else { + // user selectable + if ( cgs.dmflags & DF_FIXED_FOV ) { + // dmflag to prevent wide fov for all clients + fov_x = 90; + } else { + fov_x = cg_fov.value; + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > 160 ) { + fov_x = 160; + } + } else { + if ( fov_x < 90 ) { + fov_x = 90; + } else if ( fov_x > 160 ) { + fov_x = 160; + } + } + } + + // account for zooms + if ( cg.zoomval ) { + zoomFov = cg.zoomval; // (SA) use user scrolled amount + + if ( zoomFov < 1 ) { + zoomFov = 1; + } else if ( zoomFov > 160 ) { + zoomFov = 160; + } + } else { + zoomFov = lastfov; + } + + // do smooth transitions for the binocs + if ( cg.zoomedBinoc ) { // binoc zooming in + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = zoomFov; + } else { + fov_x = fov_x + f * ( zoomFov - fov_x ); + } + lastfov = fov_x; + } else if ( cg.zoomval ) { // zoomed by sniper/snooper + fov_x = cg.zoomval; + lastfov = fov_x; + } else { // binoc zooming out + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov + f * ( fov_x - zoomFov ); + } + } + } + + if ( cg.weaponSelect == WP_SNOOPERSCOPE ) { + cg.refdef.rdflags |= RDF_SNOOPERVIEW; + } else { + cg.refdef.rdflags &= ~RDF_SNOOPERVIEW; + } + + if ( cg.snap->ps.persistant[PERS_HWEAPON_USE] ) { + fov_x = 55; + } + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // warp if underwater + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + cg.refdef.rdflags |= RDF_UNDERWATER; + } else { + cg.refdef.rdflags &= ~RDF_UNDERWATER; + inwater = qfalse; + } + + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + cg.refdef.rdflags |= RDF_UNDERWATER; + } else { + cg.refdef.rdflags &= ~RDF_UNDERWATER; + } + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + if ( !cg.zoomedBinoc ) { + // NERVE - SMF - fix for zoomed in/out movement bug + if ( cg.zoomval ) { + if ( cg.snap->ps.weapon == WP_SNOOPERSCOPE ) { + cg.zoomSensitivity = 0.3f * ( cg.zoomval / 90.f ); // NERVE - SMF - changed to get less sensitive as you zoom in; + } +// cg.zoomSensitivity = 0.2; + else { + cg.zoomSensitivity = 0.6 * ( cg.zoomval / 90.f ); // NERVE - SMF - changed to get less sensitive as you zoom in + } +// cg.zoomSensitivity = 0.1; + } else { + cg.zoomSensitivity = 1; + } + // -NERVE - SMF + } else { + cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + } + + return inwater; +} + + +/* +============== +CG_UnderwaterSounds +============== +*/ +#define UNDERWATER_BIT 8 +static void CG_UnderwaterSounds( void ) { +// trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.underWaterSound, 255 ); + trap_S_AddLoopingSound( cg.snap->ps.clientNum, cg.snap->ps.origin, vec3_origin, cgs.media.underWaterSound, 255 & ( 1 << 8 ) ); +} + + +/* +=============== +CG_DamageBlendBlob + +=============== +*/ +static void CG_DamageBlendBlob( void ) { + int t,i; + int maxTime; + refEntity_t ent; + qboolean pointDamage; + viewDamage_t *vd; + float redFlash; + + // ragePro systems can't fade blends, so don't obscure the screen + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + return; + } + + redFlash = 0; + + for ( i = 0; i < MAX_VIEWDAMAGE; i++ ) { + + vd = &cg.viewDamage[i]; + + if ( !vd->damageValue ) { + continue; + } + + maxTime = vd->damageDuration; + t = cg.time - vd->damageTime; + if ( t <= 0 || t >= maxTime ) { + vd->damageValue = 0; + continue; + } + + pointDamage = !( !vd->damageX && !vd->damageY ); + + // if not point Damage, only do flash blend + if ( !pointDamage ) { + redFlash += 10.0 * ( 1.0 - (float)t / maxTime ); + continue; + } + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); + VectorMA( ent.origin, vd->damageX * -8, cg.refdef.viewaxis[1], ent.origin ); + VectorMA( ent.origin, vd->damageY * 8, cg.refdef.viewaxis[2], ent.origin ); + + ent.radius = vd->damageValue * 0.4 * ( 0.5 + 0.5 * (float)t / maxTime ) * ( 0.75 + 0.5 * fabs( sin( vd->damageTime ) ) ); + + ent.customShader = cgs.media.viewBloodAni[(int)( floor( ( (float)t / maxTime ) * 4.9 ) )]; //cgs.media.viewBloodShader; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); + + redFlash += ent.radius; + } + + /* moved over to cg_draw.c + if (cg.v_dmg_time > cg.time) { + redFlash = fabs(cg.v_dmg_pitch * ((cg.v_dmg_time - cg.time) / DAMAGE_TIME)); + + // blend the entire screen red + if (redFlash > 5) + redFlash = 5; + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); + ent.radius = 80; // occupy entire screen + ent.customShader = cgs.media.viewFlashBlood; + ent.shaderRGBA[3] = (int)(180.0 * redFlash/5.0); + + trap_R_AddRefEntityToScene( &ent ); + } + */ +} + +/* +=============== +CG_DrawScreenFade +=============== +*/ +static void CG_DrawScreenFade( void ) { +/* moved over to cg_draw.c + static int lastTime; + int elapsed, time; + refEntity_t ent; + + if (cgs.fadeStartTime + cgs.fadeDuration < cg.time) { + cgs.fadeAlphaCurrent = cgs.fadeAlpha; + } else if (cgs.fadeAlphaCurrent != cgs.fadeAlpha) { + elapsed = (time = trap_Milliseconds()) - lastTime; // we need to use trap_Milliseconds() here since the cg.time gets modified upon reloading + lastTime = time; + if (elapsed < 500 && elapsed > 0) { + if (cgs.fadeAlphaCurrent > cgs.fadeAlpha) { + cgs.fadeAlphaCurrent -= ((float)elapsed/(float)cgs.fadeDuration); + if (cgs.fadeAlphaCurrent < cgs.fadeAlpha) + cgs.fadeAlphaCurrent = cgs.fadeAlpha; + } else { + cgs.fadeAlphaCurrent += ((float)elapsed/(float)cgs.fadeDuration); + if (cgs.fadeAlphaCurrent > cgs.fadeAlpha) + cgs.fadeAlphaCurrent = cgs.fadeAlpha; + } + } + } + // now draw the fade + if (cgs.fadeAlphaCurrent > 0.0) { + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); + ent.radius = 80; // occupy entire screen + ent.customShader = cgs.media.viewFadeBlack; + ent.shaderRGBA[3] = (int)(255.0 * cgs.fadeAlphaCurrent); + + trap_R_AddRefEntityToScene( &ent ); + } +*/ +} + +/* +=============== +CG_CalcViewValues + +Sets cg.refdef view values +=============== +*/ +static int CG_CalcViewValues( void ) { + playerState_t *ps; + + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + + // strings for in game rendering + // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) ); + // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) ); + + // calculate size of 3D view + CG_CalcVrect(); + + ps = &cg.predictedPlayerState; + + if ( cg.cameraMode ) { + vec3_t origin, angles; + float fov = 90; + float x; + + if ( trap_getCameraInfo( CAM_PRIMARY, cg.time, &origin, &angles, &fov ) ) { + VectorCopy( origin, cg.refdef.vieworg ); + angles[ROLL] = 0; + angles[PITCH] = -angles[PITCH]; // (SA) compensate for reversed pitch (this makes the game match the editor, however I'm guessing the real fix is to be done there) + VectorCopy( angles, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + x = cg.refdef.width / tan( fov / 360 * M_PI ); + cg.refdef.fov_y = atan2( cg.refdef.height, x ); + cg.refdef.fov_y = cg.refdef.fov_y * 360 / M_PI; + cg.refdef.fov_x = fov; + + trap_SendClientCommand( va( "setCameraOrigin %f %f %f", origin[0], origin[1], origin[2] ) ); + return 0; + + } else { + cg.cameraMode = qfalse; + trap_Cvar_Set( "cg_letterbox", "0" ); + trap_SendClientCommand( "stopCamera" ); + CG_Fade( 0, 0, 0, 255, 0 ); // go black + CG_Fade( 0, 0, 0, 0, 1500 ); // then fadeup + } + } + + // intermission view + if ( ps->pm_type == PM_INTERMISSION ) { + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } + + cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + +// VectorCopy( ps->origin, cg.refdef.vieworg ); + // Arnout: see if we're attached to a gun + if ( cg.renderingThirdPerson && ps->eFlags & EF_MG42_ACTIVE ) { + centity_t *mg42 = &cg_entities[ps->viewlocked_entNum]; + vec3_t forward, right, up; + + AngleVectors( ps->viewangles, forward, right, up ); + VectorMA( mg42->currentState.pos.trBase, -36, forward, cg.refdef.vieworg ); + cg.refdef.vieworg[2] = ps->origin[2]; + } else { + VectorCopy( ps->origin, cg.refdef.vieworg ); + } + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + + // add error decay + if ( cg_errorDecay.value > 0 ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f > 0 && f < 1 ) { + VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); + } else { + cg.predictedErrorTime = 0; + } + } + + // Ridah, lock the viewangles if the game has told us to + if ( ps->viewlocked ) { + + /* + if (ps->viewlocked == 4) + { + centity_t *tent; + tent = &cg_entities[ps->viewlocked_entNum]; + VectorCopy (tent->currentState.apos.trBase, cg.refdefViewAngles); + } + else + */ + // DHM - Nerve :: don't bother evaluating if set to 7 (look at medic) + if ( ps->viewlocked != 7 && ps->viewlocked != 3 && ps->viewlocked != 2 ) { + BG_EvaluateTrajectory( &cg_entities[ps->viewlocked_entNum].currentState.apos, cg.time, cg.refdefViewAngles ); + } + + if ( ps->viewlocked == 2 ) { + cg.refdefViewAngles[0] += crandom(); + cg.refdefViewAngles[1] += crandom(); + } + } + // done. + + if ( cg.renderingThirdPerson ) { + // back away from character + CG_OffsetThirdPersonView(); + } else { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + + // Ridah, lock the viewangles if the game has told us to + if ( ps->viewlocked == 7 ) { + centity_t *tent; + vec3_t vec; + + tent = &cg_entities[ps->viewlocked_entNum]; + VectorCopy( tent->lerpOrigin, vec ); + VectorSubtract( vec, cg.refdef.vieworg, vec ); + vectoangles( vec, cg.refdefViewAngles ); + } else if ( ps->viewlocked == 4 ) { + vec3_t fwd; + AngleVectors( cg.refdefViewAngles, fwd, NULL, NULL ); + VectorMA( cg_entities[ps->viewlocked_entNum].currentState.pos.trBase, 16, fwd, cg.refdef.vieworg ); + } else if ( ps->viewlocked ) { + vec3_t fwd; + float oldZ; + // set our position to be behind it + oldZ = cg.refdef.vieworg[2]; + AngleVectors( cg.refdefViewAngles, fwd, NULL, NULL ); + VectorMA( cg_entities[ps->viewlocked_entNum].currentState.pos.trBase, -34, fwd, cg.refdef.vieworg ); + cg.refdef.vieworg[2] = oldZ; + +// CG_Printf( "ps->origin[2]: %f\n", ps->origin[2] ); +// CG_Printf( "fwd: %f %f %f\n", fwd[0], fwd[1], fwd[2] ); +// CG_Printf( "base: %f %f %f\n", cg_entities[ps->viewlocked_entNum].currentState.pos.trBase[0], cg_entities[ps->viewlocked_entNum].currentState.pos.trBase[1], cg_entities[ps->viewlocked_entNum].currentState.pos.trBase[2] ); + } + // done. + } + + // position eye reletive to origin + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + if ( cg.hyperspace ) { + cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + } + + // field of view + return CG_CalcFov(); +} + + +/* +===================== +CG_PowerupTimerSounds +===================== +*/ +/* +// TTimo: unused +static void CG_PowerupTimerSounds( void ) { + int i; + int t; + + // powerup timers going away + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + t = cg.snap->ps.powerups[i]; + if ( t <= cg.time ) { + continue; + } + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + continue; + } + if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); + } + } +} +*/ + +//========================================================================= + +/* +============== +CG_DrawSkyBoxPortal +============== +*/ +void CG_DrawSkyBoxPortal( void ) { + static float lastfov = 90; // for transitions back from zoomed in modes + refdef_t backuprefdef; + float fov_x; + float fov_y; + float x; + char *cstr; + char *token; + float zoomFov; + float f; + static qboolean foginited = qfalse; // only set the portal fog values once + + if ( !cg_skybox.integer ) { + return; + } + + if ( !( cstr = (char *)CG_ConfigString( CS_SKYBOXORG ) ) || !strlen( cstr ) ) { + // no skybox in this map + return; + } + + // if they are waiting at the mission stats screen, show the stats + if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { + if ( strlen( cg_missionStats.string ) > 1 ) { + return; + } + } + + backuprefdef = cg.refdef; + + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n" ); + } + cg.refdef.vieworg[0] = atof( token ); + + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n" ); + } + cg.refdef.vieworg[1] = atof( token ); + + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n" ); + } + cg.refdef.vieworg[2] = atof( token ); + + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n" ); + } + fov_x = atoi( token ); + + if ( !fov_x ) { + fov_x = 90; + } + + + // setup fog the first time, ignore this part of the configstring after that + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog state\n" ); + } else { + vec4_t fogColor; + int fogStart, fogEnd; + + if ( atoi( token ) ) { // this camera has fog + if ( !foginited ) { + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[0]\n" ); + } + fogColor[0] = atof( token ); + + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[1]\n" ); + } + fogColor[1] = atof( token ); + + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[2]\n" ); + } + fogColor[2] = atof( token ); + + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + fogStart = 0; + } else { + fogStart = atoi( token ); + } + + token = COM_ParseExt( &cstr, qfalse ); + if ( !token || !token[0] ) { + fogEnd = 0; + } else { + fogEnd = atoi( token ); + } + + trap_R_SetFog( FOG_PORTALVIEW, fogStart, fogEnd, fogColor[0], fogColor[1], fogColor[2], 1.1 ); + foginited = qtrue; + } + } else { + if ( !foginited ) { + trap_R_SetFog( FOG_PORTALVIEW, 0,0,0,0,0,0 ); // init to null + foginited = qtrue; + } + } + } + +//----(SA) end + + + if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + // if in intermission, use a fixed value + fov_x = 90; + } else { + // user selectable + if ( cgs.dmflags & DF_FIXED_FOV ) { + // dmflag to prevent wide fov for all clients + fov_x = 90; + } else { + fov_x = cg_fov.value; + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > 160 ) { + fov_x = 160; + } + } + + // account for zooms + if ( cg.zoomval ) { + zoomFov = cg.zoomval; // (SA) use user scrolled amount + + if ( zoomFov < 1 ) { + zoomFov = 1; + } else if ( zoomFov > 160 ) { + zoomFov = 160; + } + } else { + zoomFov = lastfov; + } + + // do smooth transitions for the binocs + if ( cg.zoomedBinoc ) { // binoc zooming in + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = zoomFov; + } else { + fov_x = fov_x + f * ( zoomFov - fov_x ); + } + lastfov = fov_x; + } else if ( cg.zoomval ) { // zoomed by sniper/snooper + fov_x = cg.zoomval; + lastfov = fov_x; + } else { // binoc zooming out + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov + f * ( fov_x - zoomFov ); + } + } + } + + if ( cg.weaponSelect == WP_SNOOPERSCOPE ) { + cg.refdef.rdflags |= RDF_SNOOPERVIEW; + } else { + cg.refdef.rdflags &= ~RDF_SNOOPERVIEW; + } + + if ( cg.snap->ps.persistant[PERS_HWEAPON_USE] ) { + fov_x = 55; + } + + + + + cg.refdef.time = cg.time; + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + cg.refdef.rdflags |= RDF_SKYBOXPORTAL; + + // draw the skybox + trap_R_RenderScene( &cg.refdef ); + + cg.refdef = backuprefdef; +} + +/* +========================= +CG_GetMPSetupValue + +Pack multiplayer options into a bitfield. + +This is necessary for all options in the limbo menu +as this is the only way to maintain accurate sync with the game. +========================= +*/ +int CG_GetMPSetupValue() { + int value = 0; + + value |= 1 << MP_TEAM_OFFSET; + value |= mp_playerType.integer << MP_CLASS_OFFSET; + value |= mp_team.integer << MP_TEAM_OFFSET; + value |= mp_weapon.integer << MP_WEAPON_OFFSET; + + return value; +} + +/* +========================= +CG_DrawNotebook +========================= +*/ +/* + VectorCopy(cg.refdefViewAngles, kickangle); + AnglesToAxis (kickangle, wolfkick.axis); + + + frame = cg.snap->ps.persistant[PERS_WOLFKICK]; + + wolfkick.frame = frame; + wolfkick.oldframe = frame - 1; + wolfkick.backlerp = 1 - cg.frameInterpolation; + +*/ +void CG_DrawNotebook( void ) { +/* + refEntity_t notebook; + vec3_t notebookangle; + int frame; + static int oldtime = 0; + + static int tempnotebookcnt = 0; + + memset (¬ebook, 0, sizeof (notebook)); + + // note to self we want this to lerp and advance frame + notebook.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_HILIGHT; + notebook.hModel = notebookModel; + + VectorCopy( cg.refdef.vieworg, notebook.origin ); + + //----(SA) allow offsets for testing boot model + if(cg_gun_x.value) VectorMA( notebook.origin, cg_gun_x.value, cg.refdef.viewaxis[0], notebook.origin ); + if(cg_gun_y.value) VectorMA( notebook.origin, cg_gun_y.value, cg.refdef.viewaxis[1], notebook.origin ); + if(cg_gun_z.value) VectorMA( notebook.origin, cg_gun_z.value, cg.refdef.viewaxis[2], notebook.origin ); + //----(SA) end + + VectorCopy(cg.refdefViewAngles, notebookangle); + AnglesToAxis (notebookangle, notebook.axis); + + frame = tempnotebookcnt; + + tempnotebookcnt++; + + if (tempnotebookcnt > 25) + tempnotebookcnt = 0; + + // CG_Printf("frame: %d\n", frame); + + CG_Printf("journal: frame: %d\n", frame); + + notebook.frame = frame; + notebook.oldframe = frame - 1; + notebook.backlerp = 1 - cg.frameInterpolation; + trap_R_AddRefEntityToScene( ¬ebook ); +*/ +} + +//========================================================================= + +extern void CG_SetupDlightstyles( void ); + + +//#define DEBUGTIME_ENABLED +#ifdef DEBUGTIME_ENABLED +#define DEBUGTIME CG_Printf( "t%i:%i ", dbgCnt++, elapsed = ( trap_Milliseconds() - dbgTime ) ); dbgTime += elapsed; +#else +#define DEBUGTIME +#endif + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { + int inwater; + int mpSetup; // NERVE - SMF + +#ifdef DEBUGTIME_ENABLED + int dbgTime = trap_Milliseconds(),elapsed; + int dbgCnt = 0; +#endif + + cg.time = serverTime; + cg.demoPlayback = demoPlayback; + + // update cvars + CG_UpdateCvars(); + +#ifdef DEBUGTIME_ENABLED + CG_Printf( "\n" ); +#endif + DEBUGTIME + + // if we are only updating the screen as a loading + // pacifier, don't even try to read snapshots + if ( cg.infoScreenText[0] != 0 ) { + CG_DrawInformation(); + return; + } + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds( qfalse ); + + DEBUGTIME + + // clear all the render lists + trap_R_ClearScene(); + + DEBUGTIME + + // set up cg.snap and possibly cg.nextSnap + CG_ProcessSnapshots(); + + DEBUGTIME + + // if we haven't received any snapshots yet, all + // we can draw is the information screen + if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_DrawInformation(); + return; + } + + // check for server set weapons we might not know about + // (FIXME: this is a hack for the time being since a scripted "selectweapon" does + // not hit the first snap, the server weapon set in cg_playerstate.c line 219 doesn't + // do the trick) + if ( !cg.weaponSelect && cg.snap->ps.weapon ) { + cg.weaponSelect = cg.snap->ps.weapon; + cg.weaponSelectTime = cg.time; + } + +//----(SA) nerve uses this for snooper/sniper + if ( cg.weaponSelect == WP_FG42SCOPE ) { + float spd; + spd = VectorLength( cg.snap->ps.velocity ); + if ( spd > 180.0f ) { + CG_FinishWeaponChange( WP_FG42SCOPE, WP_FG42 ); + } + } + + DEBUGTIME + + if ( !cg.lightstylesInited ) { + CG_SetupDlightstyles(); + } + + DEBUGTIME + + // if we have been told not to render, don't + if ( cg_norender.integer ) { + return; + } + + // this counter will be bumped for every valid scene we generate + cg.clientFrame++; + + // update cg.predictedPlayerState + CG_PredictPlayerState(); + + DEBUGTIME + + // decide on third person view + cg.renderingThirdPerson = cg_thirdPerson.integer || ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ); + + // build cg.refdef + inwater = CG_CalcViewValues(); + + DEBUGTIME + + // RF, draw the skyboxportal + CG_DrawSkyBoxPortal(); + + DEBUGTIME + + if ( inwater ) { + CG_UnderwaterSounds(); + } + + DEBUGTIME + + // first person blend blobs, done after AnglesToAxis + if ( !cg.renderingThirdPerson ) { + CG_DamageBlendBlob(); + } + + DEBUGTIME + + // build the render lists + if ( !cg.hyperspace ) { + CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct + CG_AddMarks(); + + DEBUGTIME + + // Rafael particles + CG_AddParticles(); + // done. + + DEBUGTIME + + CG_AddLocalEntities(); + + DEBUGTIME + } + // Rafael mg42 + if ( !( cg.snap->ps.persistant[PERS_HWEAPON_USE] ) ) { + CG_AddViewWeapon( &cg.predictedPlayerState ); + } + + // NERVE - SMF - play buffered voice chats + CG_PlayBufferedVoiceChats(); + + DEBUGTIME +/* + if (cg_notebook.integer) + { + CG_DrawNotebook (); + } +*/ + DEBUGTIME + + // Ridah, trails + if ( !cg.hyperspace ) { + CG_AddFlameChunks(); + CG_AddTrails(); // this must come last, so the trails dropped this frame get drawn + } + // done. + + DEBUGTIME + + // finish up the rest of the refdef + if ( cg.testModelEntity.hModel ) { + CG_AddTestModel(); + } + cg.refdef.time = cg.time; + memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); + + DEBUGTIME + + // warning sounds when powerup is wearing off + //CG_PowerupTimerSounds(); + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if ( stereoView != STEREO_RIGHT ) { + cg.frametime = cg.time - cg.oldTime; + if ( cg.frametime < 0 ) { + cg.frametime = 0; + } + cg.oldTime = cg.time; + CG_AddLagometerFrameInfo(); + } + + DEBUGTIME + + // Ridah, fade the screen + CG_DrawScreenFade(); + + DEBUGTIME + + mpSetup = CG_GetMPSetupValue(); // NERVE - SMF - setup mpSetup values + + // let the client system know what our weapon, holdable item and zoom settings are + trap_SetUserCmdValue( cg.weaponSelect, cg.holdableSelect, cg.zoomSensitivity, mpSetup, cg.identifyClientRequest ); + + // DHM - Nerve :: let client system know our predicted origin + trap_SetClientLerpOrigin( cg.refdef.vieworg[0], cg.refdef.vieworg[1], cg.refdef.vieworg[2] ); + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + DEBUGTIME + + // update audio positions + trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + + if ( cg_stats.integer ) { + CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); + } + + DEBUGTIME +} + diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c new file mode 100644 index 0000000..a981f7a --- /dev/null +++ b/src/cgame/cg_weapons.c @@ -0,0 +1,6285 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: cg_weapons.c + * + * desc: events and effects dealing with weapons + * +*/ + +#include "cg_local.h" + +int wolfkickModel; +int hWeaponSnd; +int hWeaponEchoSnd; // JPW NERVE +int hflakWeaponSnd; +int notebookModel; +int propellerModel; + +vec3_t ejectBrassCasingOrigin; + +//----(SA) +// forward decs +static int getAltWeapon( int weapnum ); +int getEquivWeapon( int weapnum ); +int CG_WeaponIndex( int weapnum, int *bank, int *cycle ); +static qboolean CG_WeaponHasAmmo( int i ); +char cg_fxflags; +static int maxWeapBanks = MAX_WEAP_BANKS, maxWeapsInBank = MAX_WEAPS_IN_BANK; // JPW NERVE + +int weapBanks[MAX_WEAP_BANKS][MAX_WEAPS_IN_BANK] = { + // bank + {0, 0, 0 }, // 0 (empty) + + {WP_KNIFE, WP_KNIFE2, 0 }, // 1 + {WP_LUGER, WP_COLT, 0 }, // 2 // WP_AKIMBO + {WP_MP40, WP_THOMPSON, WP_STEN }, // 3 + {WP_MAUSER, WP_GARAND, 0 }, // 4 + {WP_FG42, WP_BAR, 0 }, // 5 + {WP_GRENADE_LAUNCHER, WP_GRENADE_PINEAPPLE, WP_DYNAMITE }, // 6 + {WP_PANZERFAUST, WP_ROCKET_LAUNCHER, 0 }, // 7 + {WP_VENOM, 0, 0 }, // 8 + {WP_FLAMETHROWER, 0, 0 }, // 9 + {WP_TESLA, 0, 0 } // 10 +// {WP_SPEARGUN, 0, 0 } +// {WP_CROSS, 0, 0 } +}; + +extern int weapBanksMultiPlayer[MAX_WEAP_BANKS_MP][MAX_WEAPS_IN_BANK_MP]; // JPW NERVE moved to bg_misc.c so I can get a droplist +// jpw + +//----(SA) end + + +/* +============== +CG_MachineGunEjectBrassNew +============== +*/ +void CG_MachineGunEjectBrassNew( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + float waterScale = 1.0f; + vec3_t v[3]; + + if ( cg_brassTime.integer <= 0 ) { + return; + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = -50 + 25 * crandom(); // JPW NERVE + velocity[1] = -100 + 40 * crandom(); // JPW NERVE + velocity[2] = 200 + 50 * random(); // JPW NERVE + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time - ( rand() & 15 ); + + AnglesToAxis( cent->lerpAngles, v ); + + VectorCopy( ejectBrassCasingOrigin, re->origin ); + + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) { //----(SA) modified since slime is no longer deadly +// if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->hModel = cgs.media.smallgunBrassModel; + + le->bounceFactor = 0.4 * waterScale; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = ( rand() & 31 ) + 60; // bullets should come out horizontal not vertical JPW NERVE + le->angles.trBase[1] = rand() & 255; // random spin from extractor + le->angles.trBase[2] = rand() & 31; + le->angles.trDelta[0] = 2; + le->angles.trDelta[1] = 1; + le->angles.trDelta[2] = 0; + + le->leFlags = LEF_TUMBLE; + + + { + int contents; + vec3_t end; + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + contents = trap_CM_PointContents( end, 0 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + le->leBounceSoundType = LEBS_NONE; + } else { + le->leBounceSoundType = LEBS_BRASS; + } + } + + le->leMarkType = LEMT_NONE; +} + +/* +========================== +CG_MachineGunEjectBrass +========================== +*/ + +void CG_MachineGunEjectBrass( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + vec3_t offset, xoffset; + float waterScale = 1.0f; + vec3_t v[3]; + + if ( cg_brassTime.integer <= 0 ) { + return; + } + + if ( !( cg.snap->ps.persistant[PERS_HWEAPON_USE] ) && ( cent->currentState.clientNum == cg.snap->ps.clientNum ) && // JPW NERVE + ( !( cent->currentState.eFlags & EF_MG42_ACTIVE ) ) ) { // JPW NERVE + // if ( (!(cent->currentState.eFlags & EF_MG42_ACTIVE))&& (cent->currentState.clientNum == cg.snap->ps.clientNum) ) + CG_MachineGunEjectBrassNew( cent ); + return; + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time - ( rand() & 15 ); + + AnglesToAxis( cent->lerpAngles, v ); + +// JPW NERVE new brass handling behavior because the SP stuff just doesn't cut it for MP + if ( cent->currentState.eFlags & EF_MG42_ACTIVE ) { + offset[0] = 25; + offset[1] = -4; + offset[2] = 28; + velocity[0] = -20 + 40 * crandom(); // JPW NERVE -- more reasonable brass ballistics for a machinegun + velocity[1] = -150 + 40 * crandom(); // JPW NERVE + velocity[2] = 100 + 50 * crandom(); // JPW NERVE + re->hModel = cgs.media.machinegunBrassModel; + le->angles.trBase[0] = 90; //rand()&31; // JPW NERVE belt-fed rounds should come out horizontal + le->angles.trBase[1] = rand() & 255; + le->angles.trBase[2] = rand() & 31; + le->angles.trDelta[0] = 2; + le->angles.trDelta[1] = 1; + le->angles.trDelta[2] = 0; + } else { + re->hModel = cgs.media.smallgunBrassModel; + switch ( cent->currentState.weapon ) { + case WP_LUGER: + case WP_COLT: + offset[0] = 24; + offset[1] = -4; + offset[2] = 36; + break; + case WP_VENOM: + offset[0] = 12; + offset[1] = -4; + offset[2] = 24; + re->hModel = cgs.media.machinegunBrassModel; + break; + case WP_MAUSER: + case WP_SNIPERRIFLE: + re->hModel = cgs.media.machinegunBrassModel; + case WP_MP40: + case WP_THOMPSON: + case WP_STEN: + default: + offset[0] = 16; + offset[1] = -4; + offset[2] = 24; + break; + } + velocity[0] = -50 + 25 * crandom(); + velocity[1] = -100 + 40 * crandom(); + velocity[2] = 200 + 50 * random(); + le->angles.trBase[0] = ( rand() & 15 ) + 82; // bullets should come out horizontal not vertical JPW NERVE + le->angles.trBase[1] = rand() & 255; // random spin from extractor + le->angles.trBase[2] = rand() & 31; + le->angles.trDelta[0] = 2; + le->angles.trDelta[1] = 1; + le->angles.trDelta[2] = 0; + } +// jpw + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, re->origin ); + + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) { //----(SA) modified since slime is no longer deadly +// if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + + le->bounceFactor = 0.4 * waterScale; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + + le->leFlags = LEF_TUMBLE; + + { + int contents; + vec3_t end; + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + contents = trap_CM_PointContents( end, 0 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + le->leBounceSoundType = LEBS_NONE; + } else { + le->leBounceSoundType = LEBS_BRASS; + } + } + + le->leMarkType = LEMT_NONE; +} + + +//----(SA) added +/* +============== +CG_PanzerFaustEjectBrass + toss the 'used' panzerfaust casing (unit is one-shot, disposable) +============== +*/ +static void CG_PanzerFaustEjectBrass( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + vec3_t offset, xoffset; + float waterScale = 1.0f; + vec3_t v[3]; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + +// velocity[0] = 16; +// velocity[1] = -50 + 40 * crandom(); +// velocity[2] = 100 + 50 * crandom(); + + velocity[0] = 16; + velocity[1] = -200; + velocity[2] = 0; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; +// le->startTime = cg.time + 2000; + le->endTime = le->startTime + ( cg_brassTime.integer * 8 ) + ( cg_brassTime.integer * random() ); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time - ( rand() & 15 ); +// le->pos.trTime = cg.time - 2000; + + AnglesToAxis( cent->lerpAngles, v ); + +// offset[0] = 12; +// offset[1] = -4; +// offset[2] = 24; + + offset[0] = -24; // forward + offset[1] = -4; // left + offset[2] = 24; // up + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, re->origin ); + + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) { + waterScale = 0.10; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + + // (SA) make it bigger + le->sizeScale = 3.0f; + + re->hModel = cgs.media.panzerfaustBrassModel; + + le->bounceFactor = 0.4 * waterScale; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; +// le->angles.trBase[0] = rand()&31; +// le->angles.trBase[1] = rand()&31; +// le->angles.trBase[2] = rand()&31; + le->angles.trBase[0] = 0; + le->angles.trBase[1] = cent->currentState.apos.trBase[1]; // rotate to match the player + le->angles.trBase[2] = 0; +// le->angles.trDelta[0] = 2; +// le->angles.trDelta[1] = 1; +// le->angles.trDelta[2] = 0; + le->angles.trDelta[0] = 0; + le->angles.trDelta[1] = 0; + le->angles.trDelta[2] = 0; + + le->leFlags = LEF_TUMBLE | LEF_SMOKING; // (SA) probably doesn't need to be 'tumble' since it doesn't really rotate much when flying + + le->leBounceSoundType = LEBS_NONE; + + le->leMarkType = LEMT_NONE; +} +/* +============== +CG_SpearTrail + simple bubble trail behind a missile +============== +*/ +void CG_SpearTrail( centity_t *ent, const weaponInfo_t *wi ) { + int contents, lastContents; + vec3_t origin, lastPos; + entityState_t *es; + + es = &ent->currentState; + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 1, 8 ); + } + } +} + +// JPW NERVE -- compute random wind vector for smoke puff +/* +========================== +CG_GetWindVector +========================== +*/ +void CG_GetWindVector( vec3_t dir ) { + dir[0] = random() * 0.25; + dir[1] = cgs.smokeWindDir; // simulate a little wind so it looks natural + dir[2] = random(); // one direction (so smoke goes side-like) + VectorNormalize( dir ); +} +// jpw + +// JPW NERVE -- LT pyro for marking air strikes +/* +========================== +CG_PyroSmokeTrail +========================== +*/ +void CG_PyroSmokeTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos, dir; + int contents; + int lastContents, startTime; + entityState_t *es; + int t; + float rnd; + localEntity_t *le; + + step = 30; + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( ( startTime + step ) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + +/* smoke pyro works fine in water (well, it's dye in real life, might wanna change this in-game) + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) + return; +*/ + + // drop fire trail sprites + for ( ; t <= ent->trailTime ; t += step ) { + + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + rnd = random(); + + //VectorCopy (ent->lerpOrigin, lastPos); + + if ( ent->currentState.density ) { // corkscrew effect + vec3_t right; + vec3_t angles; + VectorCopy( ent->currentState.apos.trBase, angles ); + angles[ROLL] += cg.time % 360; + AngleVectors( angles, NULL, right, NULL ); + VectorMA( lastPos, ent->currentState.density, right, lastPos ); + } + + dir[0] = crandom() * 5; // compute offset from flare base + dir[1] = crandom() * 5; + dir[2] = 0; + VectorAdd( lastPos,dir,origin ); // store in origin + + rnd = random(); + + CG_GetWindVector( dir ); + VectorScale( dir,65,dir ); // was 75, before that 55 + + if ( !ent->currentState.otherEntityNum2 ) { // axis team, generate red smoke + le = CG_SmokePuff( origin, dir, + 25 + rnd * 110, // width + rnd * 0.5 + 0.5, rnd * 0.5 + 0.5, 1, 0.5, + 4800 + ( rand() % 2800 ), // duration was 2800+ + t, + 0, + 0, + cgs.media.smokePuffShader ); + } else { + le = CG_SmokePuff( origin, dir, + 25 + rnd * 110, // width + 1.0, rnd * 0.5 + 0.5, rnd * 0.5 + 0.5, 0.5, + 4800 + ( rand() % 2800 ), // duration was 2800+ + t, + 0, + 0, + cgs.media.smokePuffShader ); + } +// CG_ParticleExplosion( "expblue", lastPos, vec3_origin, 100 + (int)(rnd*400), 4, 4 ); // fire "flare" + + + // use the optimized local entity add +// le->leType = LE_SCALE_FADE; +/* this one works + if (rand()%4) + CG_ParticleExplosion( "blacksmokeanim", origin, dir, 2800+(int)(random()*1500), 15, 45+(int)(rnd*90) ); // smoke blacksmokeanim + else + CG_ParticleExplosion( "expblue", lastPos, vec3_origin, 100 + (int)(rnd*400), 4, 4 ); // fire "flare" +*/ + } +} +// jpw + + +// Ridah, new trail effects +/* +========================== +CG_RocketTrail +========================== +*/ +void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int contents; + int lastContents, startTime; + entityState_t *es; + int t; +// localEntity_t *le; + + if ( ent->currentState.eType == ET_FLAMEBARREL ) { + step = 30; + } else if ( ent->currentState.eType == ET_FP_PARTS ) { + step = 50; + } else if ( ent->currentState.eType == ET_RAMJET ) { + step = 10; + } else { + step = 10; + } + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( ( startTime + step ) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( ( ent->currentState.eType != ET_RAMJET ) && es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 3, 8 ); + } + return; + } + + // drop fire trail sprites + for ( ; t <= ent->trailTime ; t += step ) { + float rnd; + + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + /* + le = CG_SmokePuff( lastPos, vec3_origin, + 5, // width + 1, 1, 1, 0.33, + 150 + rand()%350, // duration + t, + 0, + cgs.media.flameThrowerhitShader ); + + // use the optimized local entity add + le->leType = LE_SCALE_FADE; + */ + rnd = random(); + if ( ent->currentState.eType == ET_FLAMEBARREL ) { + if ( ( rand() % 100 ) > 50 ) { + CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, 100 + (int)( rnd * 400 ), 5, 7 + (int)( rnd * 10 ) ); // fire + + } + CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ) ); // smoke + } else if ( ent->currentState.eType == ET_FP_PARTS ) { + if ( ( rand() % 100 ) > 50 ) { + CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, 100 + (int)( rnd * 400 ), 5, 7 + (int)( rnd * 10 ) ); // fire + + } + CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ) ); // smoke + } else if ( ent->currentState.eType == ET_RAMJET ) { + int duration; + + VectorCopy( ent->lerpOrigin, lastPos ); + duration = 100; + CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, duration + (int)( rnd * 100 ), 5, 5 + (int)( rnd * 10 ) ); // fire + CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 400 + (int)( rnd * 750 ), 12, 24 + (int)( rnd * 30 ) ); // smoke + } else if ( ent->currentState.eType == ET_FIRE_COLUMN || ent->currentState.eType == ET_FIRE_COLUMN_SMOKE ) { + int duration; + int sizeStart; + int sizeEnd; + + //VectorCopy (ent->lerpOrigin, lastPos); + + if ( ent->currentState.density ) { // corkscrew effect + vec3_t right; + vec3_t angles; + VectorCopy( ent->currentState.apos.trBase, angles ); + angles[ROLL] += cg.time % 360; + AngleVectors( angles, NULL, right, NULL ); + VectorMA( lastPos, ent->currentState.density, right, lastPos ); + } + + duration = ent->currentState.angles[0]; + sizeStart = ent->currentState.angles[1]; + sizeEnd = ent->currentState.angles[2]; + + if ( !duration ) { + duration = 100; + } + + if ( !sizeStart ) { + sizeStart = 5; + } + + if ( !sizeEnd ) { + sizeEnd = 7; + } + + CG_ParticleExplosion( "twiltb2", lastPos, vec3_origin, duration + (int)( rnd * 400 ), sizeStart, sizeEnd + (int)( rnd * 10 ) ); // fire + + if ( ent->currentState.eType == ET_FIRE_COLUMN_SMOKE && ( rand() % 100 ) > 50 ) { + CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ) ); // smoke + } + } else + { + //CG_ParticleExplosion( "twiltb", lastPos, vec3_origin, 300+(int)(rnd*100), 4, 14+(int)(rnd*8) ); // fire + CG_ParticleExplosion( "blacksmokeanim", lastPos, vec3_origin, 800 + (int)( rnd * 1500 ), 5, 12 + (int)( rnd * 30 ) ); // smoke + } + } +/* + // spawn a smoke junction + if ((cg.time - ent->lastTrailTime) >= 50 + rand()%50) { + ent->headJuncIndex = CG_AddSmokeJunc( ent->headJuncIndex, + cgs.media.smokeTrailShader, + origin, + 4500, 0.4, 20, 80 ); + ent->lastTrailTime = cg.time; + } +*/ +// done. +} + +// JPW NERVE +/* +========================== +CG_DynamiteTrail +========================== +*/ +static void CG_DynamiteTrail( centity_t *ent, const weaponInfo_t *wi ) { + vec3_t origin; + float mult; + + BG_EvaluateTrajectory( &ent->currentState.pos, cg.time, origin ); + + if ( ent->currentState.teamNum < 4 ) { + mult = 0.004f * ( cg.time - ent->currentState.effect1Time ) / 30000.0f; + trap_R_AddLightToScene( origin, 200 + 300 * fabs( sin( ( cg.time - ent->currentState.effect1Time ) * mult ) ),1.0,0,0, REF_FORCE_DLIGHT ); + } else { + mult = 1 - ( ( cg.time - ent->trailTime ) / 15500.0f ); + trap_R_AddLightToScene( origin, 10 + 300 * mult, 1.f, 1.f, 0, REF_FORCE_DLIGHT ); + } +} +// jpw + +// Ridah +/* +========================== +CG_GrenadeTrail +========================== +*/ +static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int contents; + int lastContents, startTime; + entityState_t *es; + int t; + + step = 15; // nice and smooth curves + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( ( startTime + step ) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 2, 8 ); + } + return; + } + +//----(SA) trying this back on for DM + + // spawn smoke junctions + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, origin ); + ent->headJuncIndex = CG_AddSmokeJunc( ent->headJuncIndex, + cgs.media.smokeTrailShader, + origin, +// 1500, 0.3, 10, 50 ); + 1000, 0.3, 2, 20 ); + ent->lastTrailTime = cg.time; + } +//----(SA) end +} +// done. + + + + +/* +========================== +CG_NailgunEjectBrass +========================== +*/ +/* +// TTimo: defined but not used +static void CG_NailgunEjectBrass( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + vec3_t v[3]; + vec3_t offset; + vec3_t xoffset; + vec3_t up; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 0; + offset[1] = -12; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, origin ); + + VectorSet( up, 0, 0, 64 ); + + smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; +} +*/ + +/* +// TTimo: defined but not used +static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int t; + int startTime, contents; + int lastContents; + entityState_t *es; + vec3_t up; + localEntity_t *smoke; + + up[0] = 0; + up[1] = 0; + up[2] = 0; + + step = 50; + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 1, 8 ); + } + return; + } + + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.nailPuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } + +} +*/ + +/* +========================== +CG_RailTrail + SA: re-inserted this as a debug mechanism for bullets +========================== +*/ +void CG_RailTrail2( clientInfo_t *ci, vec3_t start, vec3_t end ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + cg_railTrailTime.value; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_RAIL_CORE; + re->customShader = cgs.media.railCoreShader; + + VectorCopy( start, re->origin ); + VectorCopy( end, re->oldorigin ); + +// // still allow different colors so we can tell AI shots from player shots, etc. + if ( ci ) { + le->color[0] = ci->color[0] * 0.75; + le->color[1] = ci->color[1] * 0.75; + le->color[2] = ci->color[2] * 0.75; + } else { + le->color[0] = 1; + le->color[1] = 0; + le->color[2] = 0; + } + le->color[3] = 1.0f; + + AxisClear( re->axis ); +} + +//void CG_RailTrailBox( clientInfo_t *ci, vec3_t start, vec3_t end) { +/* +============== +CG_RailTrail + modified so we could draw boxes for debugging as well +============== +*/ +void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end, int type ) { //----(SA) added 'type' + vec3_t diff, v1, v2, v3, v4, v5, v6; + + if ( !type ) { // just a line + CG_RailTrail2( ci, start, end ); + return; + } + + // type '1' (box) + + VectorSubtract( start, end, diff ); + + VectorCopy( start, v1 ); + VectorCopy( start, v2 ); + VectorCopy( start, v3 ); + v1[0] -= diff[0]; + v2[1] -= diff[1]; + v3[2] -= diff[2]; + CG_RailTrail2( ci, start, v1 ); + CG_RailTrail2( ci, start, v2 ); + CG_RailTrail2( ci, start, v3 ); + + VectorCopy( end, v4 ); + VectorCopy( end, v5 ); + VectorCopy( end, v6 ); + v4[0] += diff[0]; + v5[1] += diff[1]; + v6[2] += diff[2]; + CG_RailTrail2( ci, end, v4 ); + CG_RailTrail2( ci, end, v5 ); + CG_RailTrail2( ci, end, v6 ); + + CG_RailTrail2( ci, v2, v6 ); + CG_RailTrail2( ci, v6, v1 ); + CG_RailTrail2( ci, v1, v5 ); + + CG_RailTrail2( ci, v2, v4 ); + CG_RailTrail2( ci, v4, v3 ); + CG_RailTrail2( ci, v3, v5 ); + +} + + + + + +/* +====================== +CG_ParseWeaponConfig + read information for weapon animations (first/length/fps) +====================== +*/ +static qboolean CG_ParseWeaponConfig( const char *filename, weaponInfo_t *wi ) { + char *text_p, *prev; + int len; + int i; + float fps; + char *token; + qboolean newfmt = qfalse; //----(SA) + char text[20000]; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( "File %s too long\n", filename ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while ( 1 ) { + prev = text_p; // so we can unget + token = COM_Parse( &text_p ); + if ( !token ) { // get the variable + break; + } + if ( !Q_stricmp( token, "whatever_variable" ) ) { + token = COM_Parse( &text_p ); // get the value + if ( !token ) { + break; + } + continue; + } + + if ( !Q_stricmp( token, "newfmt" ) ) { + newfmt = qtrue; + continue; + } + + // if it is a number, start parsing animations + if ( token[0] >= '0' && token[0] <= '9' ) { + text_p = prev; // unget the token + break; + } + Com_Printf( "unknown token in weapon cfg '%s' is %s\n", token, filename ); + } + + + for ( i = 0 ; i < MAX_WP_ANIMATIONS ; i++ ) { + + token = COM_Parse( &text_p ); // first frame + if ( !token ) { + break; + } + wi->weapAnimations[i].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); // length + if ( !token ) { + break; + } + wi->weapAnimations[i].numFrames = atoi( token ); + + token = COM_Parse( &text_p ); // fps + if ( !token ) { + break; + } + fps = atof( token ); + if ( fps == 0 ) { + fps = 1; + } + + wi->weapAnimations[i].frameLerp = 1000 / fps; + wi->weapAnimations[i].initialLerp = 1000 / fps; + + token = COM_Parse( &text_p ); // looping frames + if ( !token ) { + break; + } + wi->weapAnimations[i].loopFrames = atoi( token ); + if ( wi->weapAnimations[i].loopFrames > wi->weapAnimations[i].numFrames ) { + wi->weapAnimations[i].loopFrames = wi->weapAnimations[i].numFrames; + } else if ( wi->weapAnimations[i].loopFrames < 0 ) { + wi->weapAnimations[i].loopFrames = 0; + } + + + // store animation/draw bits in '.moveSpeed' + + wi->weapAnimations[i].moveSpeed = 0; + + if ( newfmt ) { + token = COM_Parse( &text_p ); // barrel anim bits + if ( !token ) { + break; + } + wi->weapAnimations[i].moveSpeed = atoi( token ); + + token = COM_Parse( &text_p ); // animated weapon + if ( !token ) { + break; + } + if ( atoi( token ) ) { + wi->weapAnimations[i].moveSpeed |= ( 1 << W_MAX_PARTS ); // set the bit one higher than can be set by the barrel bits + + } + token = COM_Parse( &text_p ); // barrel hide bits (so objects can be flagged to not be drawn during all sequences (a reloading hand that comes in from off screen for that one animation for example) + if ( !token ) { + break; + } + wi->weapAnimations[i].moveSpeed |= ( ( atoi( token ) ) << 8 ); // use 2nd byte for draw bits + } + + } + + if ( i != MAX_WP_ANIMATIONS ) { + CG_Printf( "Error parsing weapon animation file: %s", filename ); + return qfalse; + } + + + return qtrue; +} + + +/* +================= +CG_RegisterWeapon + +The server says this item is used on this level +================= +*/ +void CG_RegisterWeapon( int weaponNum ) { + weaponInfo_t *weaponInfo; + gitem_t *item, *ammo; + char path[MAX_QPATH], comppath[MAX_QPATH]; + vec3_t mins, maxs; + int i; + + weaponInfo = &cg_weapons[weaponNum]; + + if ( weaponNum == 0 ) { + return; + } + + if ( weaponInfo->registered ) { + return; + } + + memset( weaponInfo, 0, sizeof( *weaponInfo ) ); + weaponInfo->registered = qtrue; + + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { + if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { + weaponInfo->item = item; + break; + } + } + if ( !item->classname ) { + CG_Error( "Couldn't find weapon %i", weaponNum ); + } + + CG_RegisterItemVisuals( item - bg_itemlist ); + + // load cmodel before model so filecache works + + // alternate view weapon + weaponInfo->weaponModel[W_TP_MODEL] = trap_R_RegisterModel( item->world_model[W_TP_MODEL] ); + weaponInfo->weaponModel[W_FP_MODEL] = trap_R_RegisterModel( item->world_model[W_FP_MODEL] ); + weaponInfo->weaponModel[W_FP_MODEL_SWAP] = trap_R_RegisterModel( item->world_model[W_FP_MODEL_SWAP] ); + weaponInfo->weaponModel[W_SKTP_MODEL] = trap_R_RegisterModel( item->world_model[W_SKTP_MODEL] ); + + if ( cg_gameType.integer >= GT_WOLF ) { // JPW NERVE nasty hack, but we gotta put it somewhere + if ( weaponNum == WP_PANZERFAUST ) { + weaponInfo->weaponModel[W_SKTP_MODEL] = trap_R_RegisterModel( "models/multiplayer/panzerfaust/multi_pf.md3" ); + } + } +// jpw + + + if ( !weaponInfo->weaponModel[W_FP_MODEL] || !cg_drawFPGun.integer ) { + weaponInfo->weaponModel[W_FP_MODEL] = weaponInfo->weaponModel[W_TP_MODEL]; + } + + if ( !weaponInfo->weaponModel[W_TP_MODEL] ) { + // left commented out since we have level-loading optimization issues to still resolve. + // ie. every weapon and it's associated effects/parts/sounds etc. are loaded for every level. + // This was turned off when we started (the "only load what the level calls for" thing) because when + // DM does a "give all" and fires, he doesn't want to wait for everything to load. So perhaps a "cacheallweaps" or something. +// CG_Printf( "Couldn't register weapon model %i (unable to load view model)", weaponNum ); +// CG_Error( "Couldn't register weapon model %i (unable to load view model)", weaponNum ); + return; + } + + + // load weapon config +//----(SA) modified. use first person model for finding weapon config name, not third + if ( item->world_model[W_FP_MODEL] ) { + COM_StripFilename( item->world_model[W_FP_MODEL], path ); + if ( !CG_ParseWeaponConfig( va( "%sweapon.cfg", path ), weaponInfo ) ) { +// CG_Error( "Couldn't register weapon %i (%s) (failed to parse weapon.cfg)", weaponNum, path ); + } + } +//----(SA) end + + // calc midpoint for rotation + trap_R_ModelBounds( weaponInfo->weaponModel[W_TP_MODEL], mins, maxs ); + + for ( i = 0 ; i < 3 ; i++ ) { + weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); + } + + weaponInfo->weaponIcon[0] = trap_R_RegisterShader( item->icon ); + weaponInfo->weaponIcon[1] = trap_R_RegisterShader( va( "%s_select", item->icon ) ); // get the 'selected' icon as well + + // JOSEPH 4-17-00 + weaponInfo->ammoIcon = trap_R_RegisterShader( item->ammoicon ); + // END JOSEPH + + for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { + if ( ( ammo->giType == IT_AMMO && ammo->giTag == BG_FindAmmoForWeapon( weaponNum ) ) ) { + break; + } + } + if ( ammo->classname && ammo->world_model[0] ) { + weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); + } + + if ( item->world_model[W_FP_MODEL] ) { + strcpy( comppath, item->world_model[W_FP_MODEL] ); // first try the fp view weap + } else if ( item->world_model[W_TP_MODEL] ) { + strcpy( comppath, item->world_model[W_TP_MODEL] ); // not there, use the standard view hand + + } + if ( ( !comppath || !cg_drawFPGun.integer ) && // then if it didn't find the 1st person one or you are set to not use one + item->world_model[W_TP_MODEL] ) { + strcpy( comppath, item->world_model[W_TP_MODEL] ); // use the standard view hand + + } + for ( i = W_TP_MODEL; i < W_NUM_TYPES; i++ ) + { + int j; + qboolean bail = qfalse; + + if ( !item->world_model[i] ) { + strcpy( path, comppath ); + } else { + strcpy( path, item->world_model[i] ); + } + + COM_StripExtension( path, path ); + strcat( path, "_flash.md3" ); + weaponInfo->flashModel[i] = trap_R_RegisterModel( path ); + + + for ( j = 0; j < W_MAX_PARTS; j++ ) { + if ( !item->world_model[i] ) { + strcpy( path, comppath ); + } else { + strcpy( path, item->world_model[i] ); + } + COM_StripExtension( path, path ); + if ( j == W_PART_1 ) { + strcat( path, "_barrel.md3" ); + } else { + strcat( path, va( "_barrel%d.md3", j + 1 ) ); + } + + if ( !bail || weaponNum == WP_MAUSER ) { + weaponInfo->partModels[i][j] = trap_R_RegisterModel( path ); + } else { + weaponInfo->partModels[i][j] = 0; + } + + if ( weaponInfo->partModels[i][j] == 0 ) { + bail = qtrue; + } + } + + bail = qfalse; + + // used for spinning belt on venom + if ( i == W_FP_MODEL ) { + if ( !item->world_model[2] ) { + strcpy( path, comppath ); + } else { + strcpy( path, item->world_model[2] ); + } + COM_StripExtension( path, path ); + strcat( path, "_barrel6b.md3" ); + weaponInfo->partModels[i][W_PART_7] = trap_R_RegisterModel( path ); + } + } + + + // sniper scope model + if ( weaponNum == WP_MAUSER || weaponNum == WP_GARAND ) { + + if ( !item->world_model[W_FP_MODEL] ) { + strcpy( path, comppath ); + } else { + strcpy( path, item->world_model[W_FP_MODEL] ); + } + COM_StripExtension( path, path ); + strcat( path, "_scope.md3" ); + weaponInfo->modModel[0] = trap_R_RegisterModel( path ); + } + + if ( !item->world_model[W_FP_MODEL] ) { + strcpy( path, comppath ); + } else { + strcpy( path, item->world_model[W_FP_MODEL] ); + } + COM_StripExtension( path, path ); + strcat( path, "_hand.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( path ); + + if ( !weaponInfo->handsModel ) { + weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); + } + +//----(SA) weapon pickup 'stand' + if ( !item->world_model[W_TP_MODEL] ) { + strcpy( path, comppath ); + } else { + strcpy( path, item->world_model[W_TP_MODEL] ); + } + COM_StripExtension( path, path ); + strcat( path, "_stand.md3" ); + weaponInfo->standModel = trap_R_RegisterModel( path ); +//----(SA) end + + switch ( weaponNum ) { + case WP_MONSTER_ATTACK1: + case WP_MONSTER_ATTACK2: + case WP_MONSTER_ATTACK3: + break; + + case WP_SPEARGUN: + case WP_SPEARGUN_CO2: + break; + + case WP_AKIMBO: //----(SA) added + case WP_COLT: + MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/colt/coltf1.wav" ); + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40e1.wav" ); // use same as mp40 + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/colt/colt_reload.wav" ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + break; + + + case WP_KNIFE: + case WP_KNIFE2: + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/knife/knife_slash1.wav" ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/knife/knife_slash2.wav" ); + break; + + case WP_LUGER: + MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/luger/lugerf1.wav" ); + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40e1.wav" ); // use same as mp40 + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/luger/luger_reload.wav" ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + break; + + case WP_MAUSER: + MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/mauserf1.wav" ); + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/mausere1.wav" ); + weaponInfo->lastShotSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/mauserf1_last.wav" ); + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/mauser/mauser_reload.wav" ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + break; + case WP_SNIPERRIFLE: + MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/sniperf1.wav" ); + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mauser/mausere1.wav" ); + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/mauser/sniper_reload.wav" ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + break; + + case WP_GARAND: + break; + case WP_SNOOPERSCOPE: + break; + + case WP_THOMPSON: + MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/thompson/thompson.wav" ); + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/multiplayer/thompson_far.wav" ); // JPW NERVE + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/thompson/thompson_reload.wav" ); + weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/thompson/thompson_overheat.wav" ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + break; + + case WP_MP40: + MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40f1.wav" ); + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/weapons/mp40/mp40e1.wav" ); + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/mp40/mp40_reload.wav" ); + weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/mp40/mp40_overheat.wav" ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + break; + + case WP_STEN: + MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/sten/stenf1.wav" ); + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/sten/sten_reload.wav" ); + weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/sten/sten_overheat.wav" ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + break; + + case WP_FG42: + case WP_FG42SCOPE: + break; +//----(SA) end + + case WP_SILENCER: + break; + + case WP_PANZERFAUST: + weaponInfo->ejectBrassFunc = CG_PanzerFaustEjectBrass; + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav" ); + weaponInfo->missileTrailFunc = CG_RocketTrail; + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 2000; + weaponInfo->trailRadius = 64; + MAKERGB( weaponInfo->flashDlightColor, 0.75, 0.3, 0.0 ); + MAKERGB( weaponInfo->missileDlightColor, 0.75, 0.3, 0.0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav" ); + weaponInfo->spinupSound = trap_S_RegisterSound( "sound/multiplayer/p_charge1.wav" ); // JPW NERVE preamble for pfaust balance + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/multiplayer/artillery_exp01.wav" ); // JPW NERVE + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/rocket/rocklf_reload.wav" ); + cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); + break; + + case WP_ROCKET_LAUNCHER: + break; + + case WP_MORTAR: + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/mortar/mortarf1.wav" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->missileDlight = 400; + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav" ); + weaponInfo->wiTrailTime = 300; + weaponInfo->trailRadius = 32; + MAKERGB( weaponInfo->flashDlightColor, 1, 0.7, 0.5 ); + break; + // JPW NERVE + case WP_SMOKE_GRENADE: + weaponInfo->missileModel = trap_R_RegisterModel( "models/multiplayer/smokegrenade/smokegrenade.md3" ); + weaponInfo->missileTrailFunc = CG_PyroSmokeTrail; + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/multiplayer/artillery_exp01.wav" ); // use same as mp40 + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 4000; + weaponInfo->trailRadius = 256; + break; + case WP_SMOKETRAIL: // JPW NERVE -- for smoke bits from artillery spotter round, plus other effects maybe + weaponInfo->missileTrailFunc = CG_PyroSmokeTrail; + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 4000; + weaponInfo->trailRadius = 256; + break; + case WP_ARTY: // JPW NERVE + break; + case WP_AMMO: // JPW NERVE + case WP_MEDKIT: + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/multiplayer/bag_toss.wav" ); + break; + case WP_PLIERS: + case WP_MEDIC_SYRINGE: // JPW NERVE + break; + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + if ( weaponNum == WP_GRENADE_LAUNCHER ) { + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); + } else { + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/grenade/pineapple.md3" ); + } + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->wiTrailTime = 1000; + weaponInfo->trailRadius = 32; + MAKERGB( weaponInfo->flashDlightColor, 1, 0.7, 0.5 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav" ); + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/grenade/grenlf_reload.wav" ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; + + case WP_DYNAMITE: + case WP_DYNAMITE2: + weaponInfo->missileModel = trap_R_RegisterModel( "models/multiplayer/dynamite/dynamite.md3" ); + + // DHM - Nerve :: ticking sound + if ( cgs.gametype >= GT_WOLF ) { + weaponInfo->spindownSound = trap_S_RegisterSound( "sound/multiplayer/dynamite_01.wav" ); + weaponInfo->missileTrailFunc = CG_DynamiteTrail; // JPW NERVE + } + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; + + case WP_VENOM: + MAKERGB( weaponInfo->flashDlightColor, 1.0, 0.6, 0.23 ); + weaponInfo->spinupSound = trap_S_RegisterSound( "sound/weapons/venom/venomsu1.wav" ); //----(SA) added + weaponInfo->spindownSound = trap_S_RegisterSound( "sound/weapons/venom/venomsd1.wav" ); //----(SA) added + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/venom/venomf1.wav" ); + weaponInfo->flashEchoSound[0] = trap_S_RegisterSound( "sound/multiplayer/venom_far.wav" ); // JPW NERVE + weaponInfo->reloadSound = trap_S_RegisterSound( "sound/weapons/venom/venom_reload.wav" ); + weaponInfo->overheatSound = trap_S_RegisterSound( "sound/weapons/venom/venom_overheat.wav" ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + break; + + case WP_VENOM_FULL: + case WP_FLAMETHROWER: + case WP_CROSS: + case WP_TESLA: + case WP_GAUNTLET: + break; + + default: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav" ); + break; + } +} + +/* +================= +CG_RegisterItemVisuals + +The server says this item is used on this level +================= +*/ +void CG_RegisterItemVisuals( int itemNum ) { + itemInfo_t *itemInfo; + gitem_t *item; + int i; + + itemInfo = &cg_items[ itemNum ]; + if ( itemInfo->registered ) { + return; + } + + item = &bg_itemlist[ itemNum ]; + + memset( itemInfo, 0, sizeof( &itemInfo ) ); + + for ( i = 0; i < MAX_ITEM_MODELS; i++ ) + itemInfo->models[i] = trap_R_RegisterModel( item->world_model[i] ); + + + itemInfo->icons[0] = trap_R_RegisterShader( item->icon ); + if ( item->giType == IT_HOLDABLE ) { + // (SA) register alternate icons (since holdables can have multiple uses, they might have different icons to represent how many uses are left) + for ( i = 1; i < MAX_ITEM_ICONS; i++ ) + itemInfo->icons[i] = trap_R_RegisterShader( va( "%s%i", item->icon, i + 1 ) ); + } + + if ( item->giType == IT_WEAPON ) { + CG_RegisterWeapon( item->giTag ); + } + + itemInfo->registered = qtrue; //----(SA) moved this down after the registerweapon() + + //wolfkickModel = trap_R_RegisterModel( "models/weapons2/foot/v_wolfoot_10f.md3" ); + hWeaponSnd = trap_S_RegisterSound( "sound/weapons/mg42/37mm.wav" ); + hWeaponEchoSnd = trap_S_RegisterSound( "sound/multiplayer/mg42_far.wav" ); // JPW NERVE for mg42 echo + + //hflakWeaponSnd = trap_S_RegisterSound ("sound/weapons/flak/flak.wav"); + //notebookModel = trap_R_RegisterModel( "models/mapobjects/book/book.md3" ); + //propellerModel = trap_R_RegisterModel( "models/mapobjects/vehicles/m109_prop.md3" ); + +// JPW NERVE had to put this somewhere, this seems OK + if ( cg_gameType.integer < GT_WOLF ) { + maxWeapBanks = MAX_WEAP_BANKS; + maxWeapsInBank = MAX_WEAPS_IN_BANK; + } else { + CG_RegisterWeapon( WP_SMOKE_GRENADE ); // register WP_CLASS_SPECIAL visuals here + CG_RegisterWeapon( WP_MEDKIT ); + CG_RegisterWeapon( WP_SMOKETRAIL ); + maxWeapBanks = MAX_WEAP_BANKS_MP; + maxWeapsInBank = MAX_WEAPS_IN_BANK_MP; + } +// if player runs out of SMG ammunition, it shouldn't *also* deplete pistol ammunition. If you change this, change +// g_spawn.c as well + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) { + item = BG_FindItem( "Thompson" ); + item->giAmmoIndex = WP_THOMPSON; + item = BG_FindItem( "Sten" ); + item->giAmmoIndex = WP_STEN; + item = BG_FindItem( "MP40" ); + item->giAmmoIndex = WP_MP40; + } +// jpw +} + + +/* +======================================================================================== + +VIEW WEAPON + +======================================================================================== +*/ + + +// +// weapon animations +// + +/* +============== +CG_GetPartFramesFromWeap + get animation info from the parent if necessary +============== +*/ +qboolean CG_GetPartFramesFromWeap( centity_t *cent, refEntity_t *part, refEntity_t *parent, int partid, weaponInfo_t *wi ) { + int i; + int frameoffset = 0; + animation_t *anim; + + anim = cent->pe.weap.animation; + + if ( partid == W_MAX_PARTS ) { + return qtrue; // primary weap model drawn for all frames right now + } + + // check draw bit + if ( anim->moveSpeed & ( 1 << ( partid + 8 ) ) ) { // hide bits are in high byte + return qfalse; // not drawn for current sequence + } + + // find part's start frame for this animation sequence + for ( i = 0; i < cent->pe.weap.animationNumber; i++ ) { + if ( wi->weapAnimations[i].moveSpeed & ( 1 << partid ) ) { // this part has animation for this sequence + frameoffset += wi->weapAnimations[i].numFrames; + } + } + + // now set the correct frame into the part + if ( anim->moveSpeed & ( 1 << partid ) ) { + part->backlerp = parent->backlerp; + part->oldframe = frameoffset + ( parent->oldframe - anim->firstFrame ); + part->frame = frameoffset + ( parent->frame - anim->firstFrame ); + } + + return qtrue; +} + + +/* +=============== +CG_SetWeapLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetWeapLerpFrameAnimation( weaponInfo_t *wi, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= MAX_WP_ANIMATIONS ) { + CG_Error( "Bad animation number (CG_SWLFA): %i", newAnimation ); + } + + anim = &wi->weapAnimations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if ( cg_debugAnim.integer & 2 ) { + CG_Printf( "Weap Anim: %d\n", newAnimation ); + } +} + + +/* +=============== +CG_ClearWeapLerpFrame +=============== +*/ +void CG_ClearWeapLerpFrame( weaponInfo_t *wi, lerpFrame_t *lf, int animationNumber ) { + lf->frameTime = lf->oldFrameTime = cg.time; + CG_SetWeapLerpFrameAnimation( wi, lf, animationNumber ); + lf->oldFrame = lf->frame = lf->animation->firstFrame; + +} + + +/* +=============== +CG_RunWeapLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunWeapLerpFrame( clientInfo_t *ci, weaponInfo_t *wi, lerpFrame_t *lf, int newAnimation, float speedScale ) { + int f; + animation_t *anim; + + // debugging tool to get no animations + if ( cg_animSpeed.integer == 0 ) { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return; + } + + // see if the animation sequence is switching + if ( !lf->animation ) { + CG_ClearWeapLerpFrame( wi, lf, newAnimation ); + } else if ( newAnimation != lf->animationNumber ) { + if ( ( newAnimation & ~ANIM_TOGGLEBIT ) == WEAP_RAISE ) { + CG_ClearWeapLerpFrame( wi, lf, newAnimation ); // clear when switching to raise (since it should be out of view anyway) + } else { + CG_SetWeapLerpFrameAnimation( wi, lf, newAnimation ); + } + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( cg.time >= lf->frameTime ) { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if ( !anim->frameLerp ) { + return; // shouldn't happen + } + if ( cg.time < lf->animationTime ) { + lf->frameTime = lf->animationTime; // initial lerp + } else { + lf->frameTime = lf->oldFrameTime + anim->frameLerp; + } + f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + f *= speedScale; // adjust for haste, etc + if ( f >= anim->numFrames ) { + f -= anim->numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = anim->numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = cg.time; + } + } + lf->frame = anim->firstFrame + f; + if ( cg.time > lf->frameTime ) { + lf->frameTime = cg.time; + if ( cg_debugAnim.integer ) { + CG_Printf( "Clamp lf->frameTime\n" ); + } + } + } + + if ( lf->frameTime > cg.time + 200 ) { + lf->frameTime = cg.time; + } + + if ( lf->oldFrameTime > cg.time ) { + lf->oldFrameTime = cg.time; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} + + + +/* +============== +CG_WeaponAnimation +============== +*/ + +//----(SA) modified. this is now client-side only (server does not dictate weapon animation info) +static void CG_WeaponAnimation( playerState_t *ps, weaponInfo_t *weapon, int *weapOld, int *weap, float *weapBackLerp ) { + + centity_t *cent = &cg.predictedPlayerEntity; + clientInfo_t *ci = &cgs.clientinfo[ ps->clientNum ]; + + if ( cg_noPlayerAnims.integer ) { + *weapOld = *weap = 0; + return; + } + + CG_RunWeapLerpFrame( ci, weapon, ¢->pe.weap, ps->weapAnim, 1 ); + + *weapOld = cent->pe.weap.oldFrame; + *weap = cent->pe.weap.frame; + *weapBackLerp = cent->pe.weap.backlerp; + + if ( cg_debugAnim.integer == 3 ) { + CG_Printf( "oldframe: %d frame: %d backlerp: %f\n", cent->pe.weap.oldFrame, cent->pe.weap.frame, cent->pe.weap.backlerp ); + } +} + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// + + +// (SA) it wasn't used anyway + + +/* +============== +CG_CalculateWeaponPosition +============== +*/ +static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { + float scale; + int delta; + float fracsin; + + VectorCopy( cg.refdef.vieworg, origin ); + VectorCopy( cg.refdefViewAngles, angles ); + + // adjust 'lean' into weapon + if ( cg.predictedPlayerState.leanf != 0 ) { + vec3_t right, up; + float myfrac = 1.0f; + + if ( cg.predictedPlayerState.weapon == WP_GARAND ) { + myfrac = 3.0f; + } + if ( cg.predictedPlayerState.weapon == WP_FLAMETHROWER ) { + myfrac = 2.0f; + } + if ( cg.predictedPlayerState.weapon == WP_TESLA ) { + myfrac = 2.0f; + } + if ( cg.predictedPlayerState.weapon == WP_MAUSER ) { + myfrac = 2.0f; + } + + // reverse the roll on the weapon so it stays relatively level + angles[ROLL] -= cg.predictedPlayerState.leanf / ( myfrac * 2.0f ); + AngleVectors( angles, NULL, right, up ); + VectorMA( origin, angles[ROLL], right, origin ); + + // pitch the gun down a bit to show that firing is not allowed when leaning + angles[PITCH] += ( abs( cg.predictedPlayerState.leanf ) / 2.0f ); + + // this gives you some impression that the weapon stays in relatively the same + // position while you lean, so you appear to 'peek' over the weapon + AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); + VectorMA( origin, -cg.predictedPlayerState.leanf / 4.0f, right, origin ); + } + + + // on odd legs, invert some angles + if ( cg.bobcycle & 1 ) { + scale = -cg.xyspeed; + } else { + scale = cg.xyspeed; + } + + // gun angles from bobbing + + angles[ROLL] += scale * cg.bobfracsin * 0.005; + angles[YAW] += scale * cg.bobfracsin * 0.01; + angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; + + // drop the weapon when landing + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + origin[2] += cg.landChange * 0.25 * delta / LAND_DEFLECT_TIME; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin[2] += cg.landChange * 0.25 * + ( LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta ) / LAND_RETURN_TIME; + } + +#if 0 + // drop the weapon when stair climbing + delta = cg.time - cg.stepTime; + if ( delta < STEP_TIME / 2 ) { + origin[2] -= cg.stepChange * 0.25 * delta / ( STEP_TIME / 2 ); + } else if ( delta < STEP_TIME ) { + origin[2] -= cg.stepChange * 0.25 * ( STEP_TIME - delta ) / ( STEP_TIME / 2 ); + } +#endif + + // idle drift +//----(SA) adjustment for MAX KAUFMAN +// scale = cg.xyspeed + 40; + scale = 80; +//----(SA) end + fracsin = sin( cg.time * 0.001 ); + angles[ROLL] += scale * fracsin * 0.01; + angles[YAW] += scale * fracsin * 0.01; + angles[PITCH] += scale * fracsin * 0.01; + + // RF, subtract the kickAngles + VectorMA( angles, -1.0, cg.kickAngles, angles ); + +} + + +// Ridah +/* +=============== +CG_FlamethrowerFlame +=============== +*/ +static void CG_FlamethrowerFlame( centity_t *cent, vec3_t origin ) { + + if ( cent->currentState.weapon != WP_FLAMETHROWER ) { + return; + } + + CG_FireFlameChunks( cent, origin, cent->lerpAngles, 1.0, qtrue ); + return; +} +// done. + +/* +====================== +CG_MachinegunSpinAngle +====================== +*/ +/* +// TTimo: unused +//#define SPIN_SPEED 0.9 +//#define COAST_TIME 1000 +#define SPIN_SPEED 1 +#define COAST_TIME 2000 +static float CG_MachinegunSpinAngle( centity_t *cent ) { + int delta; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + if ( cent->pe.barrelSpinning ) { + angle = cent->pe.barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = cent->pe.barrelAngle + delta * speed; + } + + if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); + } + + return angle; +} +*/ + +/* +============== +CG_TeslaSpinAngle +============== +*/ +//#define TESLA_SPINSPEED .2 +//#define TESLA_COASTTIME 2000 +#define TESLA_SPINSPEED .05 +#define TESLA_IDLESPEED .15 +#define TESLA_COASTTIME 1000 +/* +// TTimo: unused +static float CG_TeslaSpinAngle( centity_t *cent ) { + int delta; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + + angle = cent->pe.barrelAngle; + + if(cent->currentState.eFlags & EF_FIRING) + angle += delta * TESLA_SPINSPEED; + else + angle += delta * TESLA_IDLESPEED; + + cent->pe.barrelAngle = AngleMod( angle ); + + cent->pe.barrelTime = cg.time; + + return AngleMod(angle); + +//----(SA) trying new tesla effect scheme for MK +// angle = -(cent->pe.barrelAngle + delta * TESLA_SPINSPEED); +// cent->pe.barrelAngle = AngleMod( angle ); + +// if(cent->currentState.eFlags & EF_FIRING) +// cent->pe.barrelAngle += delta * TESLA_SPINSPEED; +// else +// cent->pe.barrelAngle += delta * TESLA_IDLESPEED; + + return AngleMod(cent->pe.barrelAngle); + + + + + +return angle; + + + if ( cent->pe.barrelSpinning ) { + angle = -(cent->pe.barrelAngle + delta * TESLA_SPINSPEED); + } else { + if ( delta > TESLA_COASTTIME ) { + delta = TESLA_COASTTIME; + } + + speed = 0.5 * ( TESLA_SPINSPEED + (float)( TESLA_COASTTIME - delta ) / TESLA_COASTTIME ); + angle = - (cent->pe.barrelAngle + delta * speed); + } + + if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); + } + + return angle; +} +*/ + +//----(SA) added + +/* +====================== +CG_VenomSpinAngle +====================== +*/ + +#define VENOM_LOADTIME 2000 +#define VENOM_DELTATIME ( VENOM_LOADTIME / 10 ) // as there are 10 shots to be loaded + +#define SPIN_SPEED 1 +#define COAST_TIME 2000 + +static float CG_VenomSpinAngle( centity_t *cent ) { + int delta; + float ramp; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + + ramp = delta % VENOM_DELTATIME; + + delta = cg.time - cent->pe.barrelTime; + if ( cent->pe.barrelSpinning ) { + angle = cent->pe.barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = cent->pe.barrelAngle + delta * speed; + } + + if ( cent->pe.barrelSpinning == !( cent->currentState.eFlags & EF_FIRING ) ) { + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = !!( cent->currentState.eFlags & EF_FIRING ); + + // just switching between not spinning and spinning, play the appropriate weapon sound + if ( cent->pe.barrelSpinning ) { + if ( cg_weapons[WP_VENOM].spinupSound ) { + trap_S_StartSoundEx( NULL, cent->currentState.number, CHAN_WEAPON, cg_weapons[WP_VENOM].spinupSound, SND_OKTOCUT ); + } + } else { + if ( cg_weapons[WP_VENOM].spindownSound ) { + trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, cg_weapons[WP_VENOM].spindownSound ); + } + } + + } + + return angle; +} + + +/* +======================== +CG_AddWeaponWithPowerups +======================== +*/ +static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups, playerState_t *ps, centity_t *cent ) { + + // add powerup effects + // DHM - Nerve :: no powerup effects on weapons + trap_R_AddRefEntityToScene( gun ); +} + +/* +============== +CG_PlayerTeslaCoilFire + + TODO: this needs to be fixed for multiplay. entities being hurt need to be sent + by server to all clients, so they draw the correct effects. +============== +*/ +void CG_PlayerTeslaCoilFire( centity_t *cent, vec3_t flashorigin ) { +/* +#define TESLA_LIGHTNING_POINT_TIMEOUT 3000 +#define TESLA_LIGHTNING_MAX_DIST TESLA_RANGE // use these to perhaps vary the distance according to aiming +#define TESLA_LIGHTNING_NORMAL_DIST TESLA_RANGE +#define TESLA_MAX_POINT_TESTS 10 +#define TESLA_MAX_POINT_TESTS_PERFRAME 20 + + int i, j, pointTests=0; + vec3_t testPos, tagPos, vec; + trace_t tr; + float maxDist; + int numPoints; + vec3_t viewAngles, viewDir; + int visEnemies[16]; + float visDists[16]; + int visEnemiesSorted[MAX_TESLA_BOLTS]; + int numEnemies, numSorted, best; + float bestDist; + centity_t *ctrav; + vec3_t traceOrg; + int playerTeam; + + if (cent->currentState.weapon != WP_TESLA) + return; + +// JPW NERVE no tesla in multiplayer + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) + return; + + //if (cent->currentState.number == cg.snap->ps.clientNum) + // VectorCopy( cg.snap->ps.viewangles, viewAngles ); + //else + VectorCopy( cent->lerpAngles, viewAngles ); + + AngleVectors( viewAngles, viewDir, NULL, NULL ); + + if (cent->currentState.number == cg.snap->ps.clientNum) { + VectorCopy( cg.snap->ps.origin, traceOrg ); + playerTeam = cg.snap->ps.teamNum; + } else { + VectorCopy( cent->lerpOrigin, traceOrg ); + playerTeam = cent->currentState.teamNum; + } + + maxDist = TESLA_LIGHTNING_MAX_DIST; + numPoints = MAX_TESLA_BOLTS; + + VectorCopy( flashorigin, tagPos ); + + // first, build a list of visible enemies that can be hurt by this tesla, then filter by distance + if (!cent->pe.teslaDamageApplyTime || cent->pe.teslaDamageApplyTime < cg.time - 200) { + numEnemies = 0; + // check the local playing client + VectorSubtract( cg.snap->ps.origin, traceOrg, vec ); + VectorNormalize( vec ); + if ((cent != &cg_entities[cg.snap->ps.clientNum]) && + (cg.snap->ps.teamNum != playerTeam) && + (Distance( tagPos, cg.snap->ps.origin ) < TESLA_LIGHTNING_MAX_DIST) && + (DotProduct(viewDir, vec) > 0.8)) { + CG_Trace( &tr, traceOrg, NULL, NULL, cg.snap->ps.origin, cg.snap->ps.clientNum, MASK_SHOT & ~CONTENTS_BODY ); + if (tr.fraction == 1 || tr.entityNum == cg.snap->ps.clientNum) { + visDists[numEnemies] = Distance( tagPos, cg.snap->ps.origin ); + visEnemies[numEnemies++] = cg.snap->ps.clientNum; + } + } + + if (cgs.localServer && cgs.gametype == GT_SINGLE_PLAYER) { + // check for AI's getting hurt (TODO: bot support?) + for (ctrav=cg_entities, i=0; icurrentState.aiChar) { + case AICHAR_SUPERSOLDIER: + case AICHAR_PROTOSOLDIER: + continue; + } + + if (ctrav->currentState.aiChar && + (ctrav != cent) && + (ctrav->currentState.teamNum != playerTeam) && + !(ctrav->currentState.eFlags & EF_DEAD) && + ctrav->currentValid && // is in the visible frame + (Distance( tagPos, ctrav->lerpOrigin ) < TESLA_LIGHTNING_MAX_DIST)) + { + VectorSubtract( ctrav->lerpOrigin, traceOrg, vec ); + VectorNormalize( vec ); + + if (DotProduct(viewDir, vec) > 0.8) { + CG_Trace( &tr, traceOrg, NULL, NULL, ctrav->lerpOrigin, ctrav->currentState.number, MASK_SHOT & ~CONTENTS_BODY ); + if (tr.fraction == 1 || tr.entityNum == ctrav->currentState.number) { + visDists[numEnemies] = Distance( tagPos, ctrav->lerpOrigin ); + visEnemies[numEnemies++] = ctrav->currentState.number; + } + } + } + } + } + + // now sort by distance + for (j=0; j= 0) { + visEnemies[best] = -1; + numSorted = j+1; + } + } + + // now fill in the teslaEnemy[]'s + for (i=0; ipe.teslaEnemy[i] = visEnemiesSorted[j]; + // apply damage + CG_ClientDamage( visEnemiesSorted[j], cent->currentState.number, CLDMG_TESLA ); + // show the effect + cg_entities[ visEnemiesSorted[j] ].pe.teslaDamagedTime = cg.time; + } else { + if (cent->pe.teslaEnemy[i] >= 0) { + cent->pe.teslaEndPointTimes[i] = 0; // make sure we find a new spot + } + cent->pe.teslaEnemy[i] = -1; + } + } + cent->pe.teslaDamageApplyTime = cg.time; + } + + for (i=0; ipe.teslaEndPoints[i], tagPos, vec ); + VectorNormalize( vec ); + + // if this point has timed out, find a new spot + if (cent->pe.teslaEnemy[i] >= 0) + { + // attacking the player + VectorSet( testPos, 6*crandom(), + 6*crandom(), + 20*crandom() - 8 ); + //VectorClear( testPos ); + if (cent->pe.teslaEnemy[i] != cg.snap->ps.clientNum) { + VectorAdd( testPos, cg_entities[cent->pe.teslaEnemy[i]].lerpOrigin, testPos ); + } else { + VectorAdd( testPos, cg.snap->ps.origin, testPos ); + } + cent->pe.teslaEndPointTimes[i] = cg.time;// - rand()%(TESLA_LIGHTNING_POINT_TIMEOUT/2); + VectorCopy( testPos, cent->pe.teslaEndPoints[i] ); + } else if ( (!cent->pe.teslaEndPointTimes[i]) || + (cent->pe.teslaEndPointTimes[i] > cg.time) || + (cent->pe.teslaEndPointTimes[i] < cg.time - TESLA_LIGHTNING_POINT_TIMEOUT) || + (VectorDistance( tagPos, cent->pe.teslaEndPoints[i] ) > maxDist) || + (DotProduct(viewDir, vec) < 0.7)) { + + //if (cent->currentState.groundEntityNum == ENTITYNUM_NONE) + // continue; // must be on the ground + + // find a new spot + for (j=0; jcurrentState.number, MASK_SHOT & ~CONTENTS_BODY ); + if (tr.fraction < 1 && tr.entityNum == ENTITYNUM_WORLD && !(tr.surfaceFlags & (SURF_NOIMPACT|SURF_SKY))) { + // found a valid spot! + cent->pe.teslaEndPointTimes[i] = cg.time - rand()%(TESLA_LIGHTNING_POINT_TIMEOUT/2); + VectorCopy( tr.endpos, cent->pe.teslaEndPoints[i] ); + break; + } + if (pointTests++ > TESLA_MAX_POINT_TESTS_PERFRAME) { + j=TESLA_MAX_POINT_TESTS; + continue; + } + } + if (j==TESLA_MAX_POINT_TESTS) { + continue; // just don't draw this point + } + + // add an impact mark on the wall + VectorSubtract( cent->pe.teslaEndPoints[i], tagPos, vec ); + VectorNormalize( vec ); + VectorInverse( vec ); + CG_ImpactMark( cgs.media.lightningHitWallShader, cent->pe.teslaEndPoints[i], vec, random()*360, 0.2, 0.2, 0.2, 1.0, qtrue, 4, qfalse, 300 ); + } + // + // we have a valid lightning point, so draw it + // sanity check though to make sure it's valid + if (VectorDistance( tagPos, cent->pe.teslaEndPoints[i] ) <= maxDist) { + CG_DynamicLightningBolt( cgs.media.lightningBoltShader, tagPos, cent->pe.teslaEndPoints[i], 1+((cg.time%((i+2)*(i+3)))+i)%2, 20 + (float)(i%3)*5 + 6.0*random(), qtrue, 1.0, 0, i*i*3 ); + + // play a zap sound + if (cent->pe.lightningSoundTime < cg.time - 200) { + CG_SoundPlayIndexedScript( cgs.media.teslaZapScript, cent->pe.teslaEndPoints[i], ENTITYNUM_WORLD ); + CG_SoundPlayIndexedScript( cgs.media.teslaZapScript, cent->lerpOrigin, ENTITYNUM_WORLD ); + //trap_S_StartSound( cent->pe.teslaEndPoints[i], ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.lightningSounds[rand()%3] ); + cent->pe.lightningSoundTime = cg.time + rand()%200; + } + } + } + + if (cg.time % 3) { // break it up a bit + // add the looping sound + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.teslaLoopSound, 255 ); + } + + // drop a dynamic light out infront of us + AngleVectors( viewAngles, vec, NULL, NULL ); + VectorMA( tagPos, 300, vec, testPos ); + // try a trace to find a world collision + CG_Trace( &tr, tagPos, NULL, NULL, testPos, cent->currentState.number, MASK_SOLID ); + + if ((cg.time/50)%(4+(cg.time%4)) == 0) { + // alt light + trap_R_AddLightToScene( tr.endpos, 256 + 600*tr.fraction, 0.2, 0.6, 1, 2 ); + } else if ((cg.time/50)%(4+(cg.time%4)) == 1) { + // no light + //trap_R_AddLightToScene( tr.endpos, 128 + 500*tr.fraction, 1, 1, 1, 10 ); + } else { + // blue light + trap_R_AddLightToScene( tr.endpos, 256 + 600*tr.fraction, 0.2, 0.6, 1, 1 ); + } + */ +} + + +// Ridah +/* +============== +CG_MonsterUsingWeapon +============== +*/ +qboolean CG_MonsterUsingWeapon( centity_t *cent, int aiChar, int weaponNum ) { + return ( cent->currentState.aiChar == aiChar ) && ( cent->currentState.weapon == weaponNum ); +} + +/* +============= +CG_AddPlayerWeapon + +Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) +The main player will have this called for BOTH cases, so effects like light and +sound should only be done on the world model case. +============= +*/ +static qboolean debuggingweapon = qfalse; + +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ) { + + refEntity_t gun; + refEntity_t barrel; + refEntity_t flash; + vec3_t angles; + weapon_t weaponNum; + weaponInfo_t *weapon; + centity_t *nonPredictedCent; + qboolean firing; // Ridah + + qboolean playerScaled; + qboolean drawpart; + int i; + qboolean isPlayer; + + // (SA) might as well have this check consistant throughout the routine + isPlayer = (qboolean)( cent->currentState.clientNum == cg.snap->ps.clientNum ); + + weaponNum = cent->currentState.weapon; + + if ( ps && cg.cameraMode ) { + return; + } + + // don't draw any weapons when the binocs are up + if ( cent->currentState.eFlags & EF_ZOOMING ) { + if ( isPlayer ) { + if ( !cg.renderingThirdPerson ) { + return; + } + } else { + return; + } + } + + // don't draw weapon stuff when looking through a scope + if ( weaponNum == WP_SNOOPERSCOPE || weaponNum == WP_SNIPERRIFLE ) { + if ( isPlayer && !cg.renderingThirdPerson ) { + return; + } + } + + // no weapon when on mg_42 + if ( cent->currentState.eFlags & EF_MG42_ACTIVE ) { + // Arnout: MG42 Muzzle Flash + if ( cg.time - cent->muzzleFlashTime < MUZZLE_FLASH_TIME ) { + CG_MG42EFX( cent ); + } + return; + } + + CG_RegisterWeapon( weaponNum ); + weapon = &cg_weapons[weaponNum]; + + // add the weapon + memset( &gun, 0, sizeof( gun ) ); + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); + gun.shadowPlane = parent->shadowPlane; + gun.renderfx = parent->renderfx; + + // set custom shading for railgun refire rate + if ( ps ) { + gun.shaderRGBA[0] = 255; + gun.shaderRGBA[1] = 255; + gun.shaderRGBA[2] = 255; + gun.shaderRGBA[3] = 255; + } + + if ( ps ) { + gun.hModel = weapon->weaponModel[W_FP_MODEL]; + if ( ps->persistant[PERS_TEAM] == TEAM_RED ) { + if ( weaponNum != WP_MP40 && weaponNum != WP_THOMPSON ) { + if ( weapon->weaponModel[W_FP_MODEL_SWAP] ) { + gun.hModel = weapon->weaponModel[W_FP_MODEL_SWAP]; + } + } + } + } else { + // skeletal guys use a different third person weapon (for different tag business) + if ( cgs.clientinfo[ cent->currentState.clientNum ].isSkeletal && weapon->weaponModel[W_SKTP_MODEL] ) { + gun.hModel = weapon->weaponModel[W_SKTP_MODEL]; + } else { + gun.hModel = weapon->weaponModel[W_TP_MODEL]; + } + } + + if ( !gun.hModel ) { + if ( debuggingweapon ) { + CG_Printf( "returning due to: !gun.hModel\n" ); + } + return; + } + + if ( !ps && cg.snap->ps.pm_flags & PMF_LADDER && isPlayer ) { //----(SA) player on ladder + if ( debuggingweapon ) { + CG_Printf( "returning due to: !ps && cg.snap->ps.pm_flags & PMF_LADDER\n" ); + } + return; + } + + if ( !ps ) { + // add weapon ready sound + cent->pe.lightningFiring = qfalse; + if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { + // lightning gun and guantlet make a different sound when fire is held down + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound, 255 ); + cent->pe.lightningFiring = qtrue; + } else if ( weapon->readySound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound, 255 ); + } + } + + + // Ridah + firing = ( ( cent->currentState.eFlags & EF_FIRING ) != 0 ); + + CG_PositionEntityOnTag( &gun, parent, "tag_weapon", 0, NULL ); + + playerScaled = (qboolean)( cgs.clientinfo[ cent->currentState.clientNum ].playermodelScale[0] != 0 ); + if ( !ps && playerScaled ) { // don't "un-scale" weap up in 1st person + for ( i = 0; i < 3; i++ ) { // scale weapon back up so it doesn't pick up the adjusted scale of the character models. + // this will affect any parts attached to the gun as well (barrel/bolt/flash/brass/etc.) + VectorScale( gun.axis[i], 1.0 / ( cgs.clientinfo[ cent->currentState.clientNum ].playermodelScale[i] ), gun.axis[i] ); + } + + } + + if ( ps ) { + drawpart = CG_GetPartFramesFromWeap( cent, &gun, parent, W_MAX_PARTS, weapon ); // W_MAX_PARTS specifies this as the primary view model + } else { + drawpart = qtrue; + } + + if ( drawpart ) { + CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups, ps, cent ); + } + + if ( isPlayer ) { + refEntity_t brass; + + CG_PositionRotatedEntityOnTag( &brass, &gun, "tag_brass" ); + VectorCopy( brass.origin, ejectBrassCasingOrigin ); + } + + memset( &barrel, 0, sizeof( barrel ) ); + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + // add barrels + // attach generic weapon parts to the first person weapon. + // if a barrel should be attached for third person, add it in the (!ps) section below + angles[YAW] = angles[PITCH] = 0; + + if ( ps ) { + qboolean spunpart; + + for ( i = W_PART_1; i < W_MAX_PARTS; i++ ) { + + spunpart = qfalse; + barrel.hModel = weapon->partModels[W_FP_MODEL][i]; + + // check for spinning + if ( weaponNum == WP_VENOM ) { + if ( i == W_PART_1 ) { + angles[ROLL] = CG_VenomSpinAngle( cent ); + spunpart = qtrue; + } else if ( i == W_PART_2 ) { + angles[ROLL] = -CG_VenomSpinAngle( cent ); + spunpart = qtrue; + } + // 'blurry' barel when firing + // (SA) not right now. at the moment, just spin the belt when firing, no swapout + else if ( i == W_PART_3 ) { + if ( ( cent->pe.weap.animationNumber & ~ANIM_TOGGLEBIT ) == WEAP_ATTACK1 ) { + barrel.hModel = weapon->partModels[W_FP_MODEL][i]; + angles[ROLL] = -CG_VenomSpinAngle( cent ); + angles[ROLL] = -( angles[ROLL] / 8.0f ); + } else { + angles[ROLL] = 0; + } + spunpart = qtrue; + } + } else if ( weaponNum == WP_MP40 || weaponNum == WP_THOMPSON ) { + if ( i == W_PART_3 ) { + if ( ps->persistant[PERS_TEAM] == TEAM_RED ) { + if ( weapon->weaponModel[W_FP_MODEL_SWAP] ) { + barrel.hModel = weapon->weaponModel[W_FP_MODEL_SWAP]; + } + } + } + } + + if ( spunpart ) { + AnglesToAxis( angles, barrel.axis ); + } + // end spinning + + + if ( barrel.hModel ) { + if ( i == W_PART_1 ) { + if ( spunpart ) { + CG_PositionRotatedEntityOnTag( &barrel, parent, "tag_barrel" ); + } else { CG_PositionEntityOnTag( &barrel, parent, "tag_barrel", 0, NULL );} + } else { + if ( spunpart ) { + CG_PositionRotatedEntityOnTag( &barrel, parent, va( "tag_barrel%d", i + 1 ) ); + } else { CG_PositionEntityOnTag( &barrel, parent, va( "tag_barrel%d", i + 1 ), 0, NULL );} + } + + drawpart = CG_GetPartFramesFromWeap( cent, &barrel, parent, i, weapon ); + + if ( drawpart ) { + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); + } + } + } + } else { // weapons with barrels drawn in third person + if ( weaponNum == WP_VENOM ) { + + angles[ROLL] = CG_VenomSpinAngle( cent ); + AnglesToAxis( angles, barrel.axis ); + + barrel.hModel = weapon->partModels[W_TP_MODEL][W_PART_1]; + CG_PositionRotatedEntityOnTag( &barrel, &gun, "tag_barrel" ); + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); + } + } + + // add the scope model to the rifle if you've got it + if ( isPlayer && !cg.renderingThirdPerson ) { // (SA) for now just do it on the first person weapons + if ( weaponNum == WP_MAUSER ) { + if ( COM_BitCheck( cg.predictedPlayerState.weapons, WP_SNIPERRIFLE ) ) { + barrel.hModel = weapon->modModel[0]; + if ( barrel.hModel ) { + CG_PositionEntityOnTag( &barrel, &gun, "tag_scope", 0, NULL ); + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); + } + } + } else if ( weaponNum == WP_GARAND ) { + if ( COM_BitCheck( cg.predictedPlayerState.weapons, WP_SNOOPERSCOPE ) ) { + barrel.hModel = weapon->modModel[0]; + if ( barrel.hModel ) { + CG_PositionEntityOnTag( &barrel, &gun, "tag_scope", 0, NULL ); + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ps, cent ); + } + } + } + } + + + // make sure we aren't looking at cg.predictedPlayerEntity for LG + nonPredictedCent = &cg_entities[cent->currentState.clientNum]; + + // if the index of the nonPredictedCent is not the same as the clientNum + // then this is a fake player (like on the single player podiums), so + // go ahead and use the cent + if ( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { + nonPredictedCent = cent; + } + + + // add the flash + memset( &flash, 0, sizeof( flash ) ); + VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); + flash.shadowPlane = parent->shadowPlane; + flash.renderfx = parent->renderfx; + + if ( ps ) { + flash.hModel = weapon->flashModel[W_FP_MODEL]; + } else { + flash.hModel = weapon->flashModel[W_TP_MODEL]; + } + + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = crandom() * 10; + AnglesToAxis( angles, flash.axis ); + + if ( ps && ps->weapon == WP_AKIMBO ) { + if ( BG_AkimboFireSequence( ps ) ) { + CG_PositionRotatedEntityOnTag( &flash, &gun, "tag_flash" ); + } else { + CG_PositionRotatedEntityOnTag( &flash, &gun, "tag_flash2" ); + } + } else { + CG_PositionRotatedEntityOnTag( &flash, &gun, "tag_flash" ); + } + + // store this position for other cgame elements to access + cent->pe.gunRefEnt = gun; + cent->pe.gunRefEntFrame = cg.clientFrame; + + if ( ( weaponNum == WP_FLAMETHROWER || weaponNum == WP_TESLA ) && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) { + // continuous flash + + } else { + + // continuous smoke after firing +#define BARREL_SMOKE_TIME 1000 + + if ( ps || cg.renderingThirdPerson || !isPlayer ) { + if ( weaponNum == WP_VENOM || weaponNum == WP_STEN ) { + // hot smoking gun + if ( cg.time - cent->overheatTime < 3000 ) { + if ( !( rand() % 3 ) ) { + float alpha; + alpha = 1.0f - ( (float)( cg.time - cent->overheatTime ) / 3000.0f ); + alpha *= 0.25f; // .25 max alpha + if ( weaponNum == WP_VENOM ) { // silly thing that makes the smoke off the venom swirlier since it's spinning real fast + CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 1000, 8, 20, 70, alpha ); + } else { + CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 1000, 8, 20, 30, alpha ); + } + } + } + + } else if ( weaponNum == WP_VENOM_FULL || weaponNum == WP_PANZERFAUST || weaponNum == WP_ROCKET_LAUNCHER ) { + if ( cg.time - cent->muzzleFlashTime < BARREL_SMOKE_TIME ) { + if ( !( rand() % 5 ) ) { + float alpha; + alpha = 1.0f - ( (float)( cg.time - cent->muzzleFlashTime ) / (float)BARREL_SMOKE_TIME ); // what fraction of BARREL_SMOKE_TIME are we at + alpha *= 0.25f; // .25 max alpha + CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 1000, 8, 20, 30, alpha ); + } + } + } + } + + // impulse flash + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ) { + // Ridah, blue ignition flame if not firing flamer + if ( weaponNum != WP_FLAMETHROWER && weaponNum != WP_TESLA ) { + return; + } + } + + } + + // weapons that don't need to go any further as they have no flash or light + if ( weaponNum == WP_GRENADE_LAUNCHER || + weaponNum == WP_GRENADE_PINEAPPLE || + weaponNum == WP_SPEARGUN || + weaponNum == WP_SPEARGUN_CO2 || + weaponNum == WP_KNIFE || + weaponNum == WP_KNIFE2 || + weaponNum == WP_CROSS || + weaponNum == WP_DYNAMITE || + weaponNum == WP_DYNAMITE2 ) { + return; + } + + if ( weaponNum == WP_STEN ) { // sten has no muzzleflash + flash.hModel = 0; + } + + // weaps with barrel smoke + if ( ps || cg.renderingThirdPerson || !isPlayer ) { + if ( weaponNum == WP_STEN || weaponNum == WP_VENOM ) { + if ( cg.time - cent->muzzleFlashTime < 100 ) { +// CG_ParticleImpactSmokePuff (cgs.media.smokeParticleShader, flash.origin); + CG_ParticleImpactSmokePuffExtended( cgs.media.smokeParticleShader, flash.origin, 500, 8, 20, 30, 0.25f ); + } + } + } + + + if ( flash.hModel ) { + if ( weaponNum != WP_FLAMETHROWER && weaponNum != WP_TESLA ) { //Ridah, hide the flash also for now + // RF, changed this so the muzzle flash stays onscreen for long enough to be seen + if ( cg.time - cent->muzzleFlashTime < MUZZLE_FLASH_TIME ) { + //if (firing) { // Ridah + trap_R_AddRefEntityToScene( &flash ); + } + } + } + + // Ridah, zombie fires from his head + //if (CG_MonsterUsingWeapon( cent, AICHAR_ZOMBIE, WP_MONSTER_ATTACK1 )) { + // CG_PositionEntityOnTag( &flash, parent, parent->hModel, "tag_head", NULL); + //} + + if ( ps || cg.renderingThirdPerson || !isPlayer ) { + + if ( firing ) { + // Ridah, Flamethrower effect + CG_FlamethrowerFlame( cent, flash.origin ); + + // RF, Tesla coil + CG_PlayerTeslaCoilFire( cent, flash.origin ); + + // make a dlight for the flash + if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { + trap_R_AddLightToScene( flash.origin, 200 + ( rand() & 31 ), weapon->flashDlightColor[0], + weapon->flashDlightColor[1], weapon->flashDlightColor[2], 0 ); + } + } else { + if ( weaponNum == WP_FLAMETHROWER ) { + vec3_t angles; + AxisToAngles( flash.axis, angles ); +// JPW NERVE + weaponNum = BG_FindAmmoForWeapon( WP_FLAMETHROWER ); + if ( ps ) { + if ( ps->ammoclip[weaponNum] ) { + CG_FireFlameChunks( cent, flash.origin, angles, 1.0, qfalse ); + } + } else { + CG_FireFlameChunks( cent, flash.origin, angles, 1.0, qfalse ); + } +// jpw + } + } + } +} + +void CG_AddPlayerFoot( refEntity_t *parent, playerState_t *ps, centity_t *cent ) { + refEntity_t wolfkick; + vec3_t kickangle; + weaponInfo_t *weapon; + weapon_t weaponNum; + int frame; + static int oldtime = 0; + + if ( !( cg.snap->ps.persistant[PERS_WOLFKICK] ) ) { + oldtime = 0; + return; + } + + weaponNum = cent->currentState.weapon; + weapon = &cg_weapons[weaponNum]; + + memset( &wolfkick, 0, sizeof( wolfkick ) ); + + VectorCopy( parent->lightingOrigin, wolfkick.lightingOrigin ); + wolfkick.shadowPlane = parent->shadowPlane; + + // note to self we want this to lerp and advance frame + wolfkick.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON;; + wolfkick.hModel = wolfkickModel; + + VectorCopy( cg.refdef.vieworg, wolfkick.origin ); + //----(SA) allow offsets for testing boot model + if ( cg_gun_x.value ) { + VectorMA( wolfkick.origin, cg_gun_x.value, cg.refdef.viewaxis[0], wolfkick.origin ); + } + if ( cg_gun_y.value ) { + VectorMA( wolfkick.origin, cg_gun_y.value, cg.refdef.viewaxis[1], wolfkick.origin ); + } + if ( cg_gun_z.value ) { + VectorMA( wolfkick.origin, cg_gun_z.value, cg.refdef.viewaxis[2], wolfkick.origin ); + } + //----(SA) end + + + VectorCopy( cg.refdefViewAngles, kickangle ); + if ( kickangle[0] < 0 ) { + kickangle[0] = 0; //----(SA) avoid "Rockette" syndrome :) + } + AnglesToAxis( kickangle, wolfkick.axis ); + + + frame = cg.snap->ps.persistant[PERS_WOLFKICK]; + +// CG_Printf("frame: %d\n", frame); + + wolfkick.frame = frame; + wolfkick.oldframe = frame - 1; + wolfkick.backlerp = 1 - cg.frameInterpolation; + trap_R_AddRefEntityToScene( &wolfkick ); + +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ +void CG_AddViewWeapon( playerState_t *ps ) { + refEntity_t hand; + float fovOffset; + vec3_t angles; + vec3_t gunoff; + weaponInfo_t *weapon; + + if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + if ( ps->pm_type == PM_INTERMISSION ) { + return; + } + + // no gun if in third person view + if ( cg.renderingThirdPerson ) { + return; + } + + // allow the gun to be completely removed + if ( ( !cg_drawGun.integer ) || ( cg_uselessNostalgia.integer ) ) { + vec3_t origin; + + if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { + // special hack for flamethrower... + VectorCopy( cg.refdef.vieworg, origin ); + + VectorMA( origin, 18, cg.refdef.viewaxis[0], origin ); + VectorMA( origin, -7, cg.refdef.viewaxis[1], origin ); + VectorMA( origin, -4, cg.refdef.viewaxis[2], origin ); + + // Ridah, Flamethrower effect + CG_FlamethrowerFlame( &cg.predictedPlayerEntity, origin ); + } + return; + } + + // don't draw if testing a gun model + if ( cg.testGun ) { + return; + } + + if ( ps->eFlags & EF_MG42_ACTIVE ) { + return; + } + + // drop gun lower at higher fov + if ( cg_fov.integer > 90 ) { + fovOffset = -0.2 * ( cg_fov.integer - 90 ); + } else { + fovOffset = 0; + } + + if ( ps->weapon > WP_NONE ) { + CG_RegisterWeapon( ps->weapon ); + weapon = &cg_weapons[ ps->weapon ]; + + memset( &hand, 0, sizeof( hand ) ); + + // set up gun position + CG_CalculateWeaponPosition( hand.origin, angles ); + + gunoff[0] = cg_gun_x.value; + gunoff[1] = cg_gun_y.value; + gunoff[2] = cg_gun_z.value; + +//----(SA) removed + + VectorMA( hand.origin, gunoff[0], cg.refdef.viewaxis[0], hand.origin ); + VectorMA( hand.origin, gunoff[1], cg.refdef.viewaxis[1], hand.origin ); + VectorMA( hand.origin, ( gunoff[2] + fovOffset ), cg.refdef.viewaxis[2], hand.origin ); + + AnglesToAxis( angles, hand.axis ); + + if ( cg_gun_frame.integer ) { + hand.frame = hand.oldframe = cg_gun_frame.integer; + hand.backlerp = 0; + } else { // get the animation state + CG_WeaponAnimation( ps, weapon, &hand.oldframe, &hand.frame, &hand.backlerp ); //----(SA) changed + } + + + hand.hModel = weapon->handsModel; + hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; //----(SA) + + // add everything onto the hand + CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity ); + // Ridah + + } // end "if ( ps->weapon > WP_NONE)" + + // Rafael + // add the foot + CG_AddPlayerFoot( &hand, ps, &cg.predictedPlayerEntity ); + + cg.predictedPlayerEntity.lastWeaponClientFrame = cg.clientFrame; +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ + +#define WP_ICON_X 38 // new sizes per MK +#define WP_ICON_X_WIDE 72 // new sizes per MK +#define WP_ICON_Y 38 +#define WP_ICON_SPACE_Y 10 +#define WP_DRAW_X 640 - WP_ICON_X - 4 // 4 is 'selected' border width +#define WP_DRAW_X_WIDE 640 - WP_ICON_X_WIDE - 4 +#define WP_DRAW_Y 4 + +// secondary fire icons +#define WP_ICON_SEC_X 18 // new sizes per MK +#define WP_ICON_SEC_Y 18 + + +/* +=================== +CG_DrawWeaponSelect +=================== +*/ +void CG_DrawWeaponSelect( void ) { + int i; + int x, y; + int curweap, curweapbank = 0, curweapcycle = 0, drawweap; + int realweap; // DHM - Nerve + int bits[MAX_WEAPONS / ( sizeof( int ) * 8 )]; + float *color; + + // don't display if dead + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + if ( !cg.weaponSelect ) { + return; + } + + color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); + if ( !color ) { + return; + } + trap_R_SetColor( color ); + + +//----(SA) neither of these overlap the weapon selection area anymore, so let them stay + // showing weapon select clears pickup item display, but not the blend blob + cg.itemPickupTime = 0; + + // also clear holdable list +// cg.holdableSelectTime = 0; +//----(SA) end + + // count the number of weapons owned + memcpy( bits, cg.snap->ps.weapons, sizeof( bits ) ); + + curweap = cg.weaponSelect; + + // get bank/cycle of current weapon + if ( !CG_WeaponIndex( curweap, &curweapbank, &curweapcycle ) ) { + + // weapon selected isn't a primary weapon, so draw the alternates bank + CG_WeaponIndex( getAltWeapon( curweap ), &curweapbank, &curweapcycle ); + + } + + // DHM - Nerve :: Move it under the Team Overlay + if ( cg_drawTeamOverlay.integer ) { + y = WP_DRAW_Y + ( numSortedTeamPlayers * TINYCHAR_HEIGHT ); + } else { + y = WP_DRAW_Y; + } + // dhm + + for ( i = 0; i < maxWeapsInBank; i++ ) { + + qboolean wideweap; // is the icon one of the double width ones + + // primary fire +// JPW NERVE + if ( cg_gameType.integer >= GT_WOLF ) { + drawweap = weapBanksMultiPlayer[curweapbank][i]; + } else { +// jpw + drawweap = weapBanks[curweapbank][i]; + } + + realweap = drawweap; // DHM - Nerve + + switch ( drawweap ) { + case WP_THOMPSON: + case WP_MP40: + case WP_STEN: + case WP_MAUSER: + case WP_GARAND: + case WP_VENOM: + case WP_TESLA: + case WP_ROCKET_LAUNCHER: + case WP_PANZERFAUST: + case WP_FLAMETHROWER: + case WP_SPEARGUN: + case WP_FG42: + case WP_FG42SCOPE: + case WP_BAR: //----(SA) added + case WP_BAR2: //----(SA) added + wideweap = qtrue; + break; + default: + wideweap = qfalse; + break; + } + + if ( wideweap ) { + x = WP_DRAW_X_WIDE; + } else { + x = WP_DRAW_X; + } + + // DHM - Nerve :: Changed to use COM_BitCheck so it would support 64 weapons + if ( drawweap && COM_BitCheck( bits, drawweap ) ) { + CG_RegisterWeapon( drawweap ); + + if ( wideweap ) { + // weapon icon + if ( realweap == curweap ) { + CG_DrawPic( x, y, WP_ICON_X_WIDE, WP_ICON_Y, cg_weapons[drawweap].weaponIcon[1] ); + } else { + CG_DrawPic( x, y, WP_ICON_X_WIDE, WP_ICON_Y, cg_weapons[drawweap].weaponIcon[0] ); + } + + // no ammo cross + if ( !CG_WeaponHasAmmo( realweap ) ) { // DHM - Nerve + CG_DrawPic( x, y, WP_ICON_X_WIDE, WP_ICON_Y, cgs.media.noammoShader ); + } + } else { + // weapon icon + if ( realweap == curweap ) { + CG_DrawPic( x, y, WP_ICON_X, WP_ICON_Y, cg_weapons[drawweap].weaponIcon[1] ); + } else { + CG_DrawPic( x, y, WP_ICON_X, WP_ICON_Y, cg_weapons[drawweap].weaponIcon[0] ); + } + + // no ammo cross + if ( !CG_WeaponHasAmmo( realweap ) ) { // DHM - Nerve + CG_DrawPic( x, y, WP_ICON_X, WP_ICON_Y, cgs.media.noammoShader ); + } + } + + } else { + continue; + } + + // secondary fire + if ( wideweap ) { + x = WP_DRAW_X_WIDE - WP_ICON_SEC_X - 4; + } else { + x = WP_DRAW_X - WP_ICON_SEC_X - 4; + } + +// JPW NERVE + if ( cg_gameType.integer >= GT_WOLF ) { + drawweap = getAltWeapon( weapBanksMultiPlayer[curweapbank][i] ); + } else { +// jpw + drawweap = getAltWeapon( weapBanks[curweapbank][i] ); + } + + // clear drawweap if getaltweap() returns the same weap as passed in. (no secondary available) +// JPW NERVE + if ( cg_gameType.integer >= GT_WOLF ) { + if ( drawweap == weapBanksMultiPlayer[curweapbank][i] ) { + drawweap = 0; + } + } else { +// jpw + if ( drawweap == weapBanks[curweapbank][i] ) { + drawweap = 0; + } + } + + realweap = drawweap; // DHM - Nerve + + // DHM - Nerve :: Changed to use COM_BitCheck so it would support 64 weapons + if ( drawweap && COM_BitCheck( bits, drawweap ) ) { + CG_RegisterWeapon( drawweap ); + + // weapon icon + if ( realweap == cg.weaponSelect ) { + CG_DrawPic( x, y, WP_ICON_SEC_X, WP_ICON_SEC_Y, cg_weapons[drawweap].weaponIcon[1] ); + } else { + CG_DrawPic( x, y, WP_ICON_SEC_X, WP_ICON_SEC_Y, cg_weapons[drawweap].weaponIcon[0] ); + } + + // no ammo cross + if ( !CG_WeaponHasAmmo( realweap ) ) { + CG_DrawPic( x, y, WP_ICON_SEC_X, WP_ICON_SEC_Y, cgs.media.noammoShader ); + } + } + + + y += ( WP_ICON_Y + WP_ICON_SPACE_Y ); + } +} + + + + +/* +============== +CG_WeaponHasAmmo + check for ammo +============== +*/ +static qboolean CG_WeaponHasAmmo( int i ) { + if ( !( cg.predictedPlayerState.ammo[BG_FindAmmoForWeapon( i )] ) && + !( cg.predictedPlayerState.ammoclip[BG_FindClipForWeapon( i )] ) ) { + return qfalse; + } + + return qtrue; +} + + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( int i ) { + + // allow the player to unselect all weapons +// if(i == WP_NONE) +// return qtrue; + + // if holding a melee weapon (chair/shield/etc.) only allow single-handed weapons + if ( cg.snap->ps.eFlags & EF_MELEE_ACTIVE ) { + if ( !( WEAPS_ONE_HANDED & ( 1 << i ) ) ) { + return qfalse; + } + } + + // check for weapon + if ( !( COM_BitCheck( cg.predictedPlayerState.weapons, i ) ) ) { + return qfalse; + } + + if ( !CG_WeaponHasAmmo( i ) ) { + return qfalse; + } + + return qtrue; +} + + + + +/* +============== +CG_WeaponIndex +============== +*/ +int CG_WeaponIndex( int weapnum, int *bank, int *cycle ) { + static int bnk, cyc; + + if ( weapnum <= 0 || weapnum >= WP_NUM_WEAPONS ) { + if ( bank ) { + *bank = 0; + } + if ( cycle ) { + *cycle = 0; + } + return 0; + } + + for ( bnk = 0; bnk < maxWeapBanks; bnk++ ) { + for ( cyc = 0; cyc < maxWeapsInBank; cyc++ ) { + + // end of cycle, go to next bank + if ( cg_gameType.integer < GT_WOLF ) { // JPW NERVE + if ( !weapBanks[bnk][cyc] ) { + break; + } + + // found the current weapon + if ( weapBanks[bnk][cyc] == weapnum ) { + if ( bank ) { + *bank = bnk; + } + if ( cycle ) { + *cycle = cyc; + } + return 1; + } + } +// JPW NERVE + else { + if ( !weapBanksMultiPlayer[bnk][cyc] ) { + break; + } + + // found the current weapon + if ( weapBanksMultiPlayer[bnk][cyc] == weapnum ) { + if ( bank ) { + *bank = bnk; + } + if ( cycle ) { + *cycle = cyc; + } + return 1; + } + } +// jpw + } + } + + // failed to find the weapon in the table + // probably an alternate + + return 0; +} + + + +/* +============== +getNextWeapInBank + Pass in a bank and cycle and this will return the next valid weapon higher in the cycle. + if the weap passed in is above highest in a cycle (maxWeapsInBank), this will safely loop around +============== +*/ +static int getNextWeapInBank( int bank, int cycle ) { + + cycle++; + + cycle = cycle % maxWeapsInBank; + + if ( cg_gameType.integer < GT_WOLF ) { // JPW NERVE + if ( weapBanks[bank][cycle] ) { // return next weapon in bank if there is one + return weapBanks[bank][cycle]; + } else { // return first in bank + return weapBanks[bank][0]; + } + } +// JPW NERVE + else { + if ( weapBanksMultiPlayer[bank][cycle] ) { // return next weapon in bank if there is one + return weapBanksMultiPlayer[bank][cycle]; + } else { // return first in bank + return weapBanksMultiPlayer[bank][0]; + } + } +// jpw +} + +static int getNextWeapInBankBynum( int weapnum ) { + int bank, cycle; + + if ( !CG_WeaponIndex( weapnum, &bank, &cycle ) ) { + return weapnum; + } + + return getNextWeapInBank( bank, cycle ); +} + + +/* +============== +getPrevWeapInBank + Pass in a bank and cycle and this will return the next valid weapon lower in the cycle. + if the weap passed in is the lowest in a cycle (0), this will loop around to the + top (maxWeapsInBank-1) and start down from there looking for a valid weapon position +============== +*/ +static int getPrevWeapInBank( int bank, int cycle ) { + cycle--; + if ( cycle < 0 ) { + cycle = maxWeapsInBank - 1; + } + + + if ( cg_gameType.integer < GT_WOLF ) { + while ( !weapBanks[bank][cycle] ) { + cycle--; + + if ( cycle < 0 ) { + cycle = maxWeapsInBank - 1; + } + } + return weapBanks[bank][cycle]; + } else { + while ( !weapBanksMultiPlayer[bank][cycle] ) { + cycle--; + + if ( cycle < 0 ) { + cycle = maxWeapsInBank - 1; + } + } + return weapBanksMultiPlayer[bank][cycle]; + } +} + + +static int getPrevWeapInBankBynum( int weapnum ) { + int bank, cycle; + + if ( !CG_WeaponIndex( weapnum, &bank, &cycle ) ) { + return weapnum; + } + + return getPrevWeapInBank( bank, cycle ); +} + + + +/* +============== +getNextBankWeap + Pass in a bank and cycle and this will return the next valid weapon in a higher bank. + sameBankPosition: if there's a weapon in the next bank at the same cycle, + return that (colt returns thompson for example) rather than the lowest weapon +============== +*/ +static int getNextBankWeap( int bank, int cycle, qboolean sameBankPosition ) { + bank++; + + bank = bank % maxWeapBanks; + + if ( cg_gameType.integer < GT_WOLF ) { // JPW NERVE + if ( sameBankPosition && weapBanks[bank][cycle] ) { + return weapBanks[bank][cycle]; + } else { + return weapBanks[bank][0]; + } + } +// JPW NERVE + else { + if ( sameBankPosition && weapBanksMultiPlayer[bank][cycle] ) { + return weapBanksMultiPlayer[bank][cycle]; + } else { + return weapBanksMultiPlayer[bank][0]; + } + } +// jpw +} + +/* +============== +getPrevBankWeap + Pass in a bank and cycle and this will return the next valid weapon in a lower bank. + sameBankPosition: if there's a weapon in the prev bank at the same cycle, + return that (thompson returns colt for example) rather than the highest weapon +============== +*/ +static int getPrevBankWeap( int bank, int cycle, qboolean sameBankPosition ) { + int i; + + bank--; + + if ( bank < 0 ) { // don't go below 0, cycle up to top + bank += maxWeapBanks; // JPW NERVE + + } + bank = bank % maxWeapBanks; + + if ( cg_gameType.integer < GT_WOLF ) { // JPW NERVE + if ( sameBankPosition && weapBanks[bank][cycle] ) { + return weapBanks[bank][cycle]; + } else + { // find highest weap in bank + for ( i = maxWeapsInBank - 1; i >= 0; i-- ) { + if ( weapBanks[bank][i] ) { + return weapBanks[bank][i]; + } + } + + // if it gets to here, no valid weaps in this bank, go down another bank + return getPrevBankWeap( bank, cycle, sameBankPosition ); + } + } +// JPW NERVE + else { + if ( sameBankPosition && weapBanksMultiPlayer[bank][cycle] ) { + return weapBanksMultiPlayer[bank][cycle]; + } else + { // find highest weap in bank + for ( i = maxWeapsInBank - 1; i >= 0; i-- ) { + if ( weapBanksMultiPlayer[bank][i] ) { + return weapBanksMultiPlayer[bank][i]; + } + } + + // if it gets to here, no valid weaps in this bank, go down another bank + return getPrevBankWeap( bank, cycle, sameBankPosition ); + } + } +// jpw +} + +/* +============== +getAltWeapon +============== +*/ +static int getAltWeapon( int weapnum ) { + if ( weapnum > MAX_WEAP_ALTS ) { + return weapnum; + } + + if ( weapAlts[weapnum] ) { + return weapAlts[weapnum]; + } + + return weapnum; +} + +/* +============== +getEquivWeapon + return the id of the opposite team's weapon. + Passing the weapnum of the mp40 returns the id of the thompson, and likewise + passing the weapnum of the thompson returns the id of the mp40. + No equivalent available will return the weapnum passed in. +============== +*/ +int getEquivWeapon( int weapnum ) { + int num = weapnum; + + switch ( weapnum ) { + // going from german to american + case WP_LUGER: num = WP_COLT; break; + case WP_MAUSER: num = WP_GARAND; break; + case WP_MP40: num = WP_THOMPSON; break; + case WP_GRENADE_LAUNCHER: num = WP_GRENADE_PINEAPPLE; break; + case WP_PANZERFAUST: num = WP_ROCKET_LAUNCHER; break; + + // going from american to german + case WP_COLT: num = WP_LUGER; break; + case WP_GARAND: num = WP_MAUSER; break; + case WP_THOMPSON: num = WP_MP40; break; + case WP_GRENADE_PINEAPPLE: num = WP_GRENADE_LAUNCHER; break; + case WP_ROCKET_LAUNCHER: num = WP_PANZERFAUST; break; + } + return num; +} + + + + +/* +============== +CG_SetSniperZoom +============== +*/ + +void CG_SetSniperZoom( int lastweap, int newweap ) { + int zoomindex; + + if ( lastweap == newweap ) { + return; + } + + cg.zoomval = 0; + cg.zoomedScope = 0; + + // check for fade-outs + switch ( lastweap ) { + case WP_SNIPERRIFLE: +// cg.zoomedScope = 500; // TODO: add to zoomTable +// cg.zoomTime = cg.time; + break; + case WP_SNOOPERSCOPE: +// cg.zoomedScope = 500; // TODO: add to zoomTable +// cg.zoomTime = cg.time; + break; + case WP_FG42SCOPE: +// cg.zoomedScope = 1; // TODO: add to zoomTable +// cg.zoomTime = cg.time; + break; + } + + switch ( newweap ) { + + default: + return; // no sniper zoom, get out. + + case WP_SNIPERRIFLE: + cg.zoomval = cg_zoomDefaultSniper.value; + cg.zoomedScope = 900; // TODO: add to zoomTable + zoomindex = ZOOM_SNIPER; + break; + case WP_SNOOPERSCOPE: + cg.zoomval = cg_zoomDefaultSniper.value; // JPW NERVE changed from snoopervalue per atvi request for consistency + cg.zoomedScope = 800; // TODO: add to zoomTable + zoomindex = ZOOM_SNIPER; // JPW NERVE was ZOOM_SNOOPER + break; + case WP_FG42SCOPE: + cg.zoomval = cg_zoomDefaultSniper.value; // JPW NERVE changed from defaultFG per atvi req + cg.zoomedScope = 1; // TODO: add to zoomTable + zoomindex = ZOOM_SNIPER; // JPW NERVE was FG42SCOPE + break; + } + + // constrain user preferred fov to weapon limitations + if ( cg.zoomval > zoomTable[zoomindex][ZOOM_OUT] ) { + cg.zoomval = zoomTable[zoomindex][ZOOM_OUT]; + } + if ( cg.zoomval < zoomTable[zoomindex][ZOOM_IN] ) { + cg.zoomval = zoomTable[zoomindex][ZOOM_IN]; + } + + cg.zoomTime = cg.time; +} + +/* +============== +CG_FinishWeaponChange +============== +*/ +void CG_FinishWeaponChange( int lastweap, int newweap ) { + int newbank; + + cg.weaponSelectTime = cg.time; // flash the weapon icon + + // NERVE - SMF + if ( cg.newCrosshairIndex ) { + trap_Cvar_Set( "cg_drawCrossHair", va( "%d", cg.newCrosshairIndex - 1 ) ); + } + cg.newCrosshairIndex = 0; + // -NERVE - SMF + + // remember which weapon in this bank was last selected so when cycling back + // to this bank, that weap will be highlighted first + if ( CG_WeaponIndex( newweap, &newbank, NULL ) ) { + cg.lastWeapSelInBank[newbank] = newweap; + } + + if ( lastweap == newweap ) { // no need to do any more than flash the icon + return; + } + + CG_SetSniperZoom( lastweap, newweap ); + + // setup for a user call to CG_LastWeaponUsed_f() + if ( lastweap == cg.lastFiredWeapon ) { + // don't set switchback for some weaps... + switch ( lastweap ) { + case WP_SNIPERRIFLE: + case WP_SNOOPERSCOPE: + case WP_FG42SCOPE: + break; + default: + cg.switchbackWeapon = lastweap; + break; + } + } else { + // if this ended up having the switchback be the same + // as the new weapon, set the switchback to the prev + // selected weapon will become the switchback + if ( cg.switchbackWeapon == newweap ) { + cg.switchbackWeapon = lastweap; + } + } + + cg.weaponSelect = newweap; +} + +/* +============== +CG_AltfireWeapon_f + for example, switching between WP_MAUSER and WP_SNIPERRIFLE +============== +*/ +void CG_AltWeapon_f( void ) { + int original, num; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { + return; // force pause so holding it down won't go too fast + + } + // Don't try to switch when in the middle of reloading. + if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { + return; + } + + original = cg.weaponSelect; + + num = getAltWeapon( original ); + + if ( CG_WeaponSelectable( num ) ) { // new weapon is valid + +//----(SA) testing mod functionality for the silencer on the luger + // (SA) this way, if you switch away from the silenced luger, + // the silencer will still be attached when you switch back + // (until you remove it) + // TODO: will need to make sure the table gets initialized properly on restart/death/whatever. + // I still think I'm going to make the weapon banks stored in the config, so this will + // just be a matter of resetting the banks to what's in the config. + switch ( original ) { + case WP_LUGER: + weapBanks[2][0] = WP_SILENCER; + break; + case WP_SILENCER: + weapBanks[2][0] = WP_LUGER; + break; + + case WP_AKIMBO: + weapBanks[2][1] = WP_COLT; + break; + case WP_COLT: + weapBanks[2][1] = WP_AKIMBO; + break; + + case WP_BAR: + weapBanks[5][1] = WP_BAR2; + break; + case WP_BAR2: + weapBanks[5][1] = WP_BAR; + break; + + case WP_DYNAMITE: + weapBanks[6][2] = WP_DYNAMITE2; + break; + case WP_DYNAMITE2: + weapBanks[6][2] = WP_DYNAMITE; + break; + } + +//----(SA) end + CG_FinishWeaponChange( original, num ); + } +} + + +/* +============== +CG_NextWeap + + switchBanks - curweap is the last in a bank, 'qtrue' means go to the next available bank, 'qfalse' means loop to the head of the bank +============== +*/ +void CG_NextWeap( qboolean switchBanks ) { + int bank = 0, cycle = 0, newbank = 0, newcycle = 0; + int num, curweap; + qboolean nextbank = qfalse; // need to switch to the next bank of weapons? + int i, j; + + num = curweap = cg.weaponSelect; + + CG_WeaponIndex( curweap, &bank, &cycle ); // get bank/cycle of current weapon + + // if you're using an alt mode weapon, try switching back to the parent first + if ( curweap >= WP_BEGINSECONDARY && curweap <= WP_LASTSECONDARY ) { + num = getAltWeapon( curweap ); // base any further changes on the parent + if ( CG_WeaponSelectable( num ) ) { // the parent was selectable, drop back to that + CG_FinishWeaponChange( curweap, num ); + return; + } + } + + + if ( cg_cycleAllWeaps.integer || !switchBanks ) { + for ( i = 0; i < maxWeapsInBank; i++ ) { + num = getNextWeapInBankBynum( num ); + + CG_WeaponIndex( num, NULL, &newcycle ); // get cycle of new weapon. if it's lower than the original, then it cycled around + + if ( switchBanks ) { + if ( newcycle <= cycle ) { + nextbank = qtrue; + break; + } + } else { // don't switch banks if you get to the end + + if ( num == curweap ) { // back to start, just leave it where it is + return; + } + } + + if ( CG_WeaponSelectable( num ) ) { + break; + } + } + } else { + nextbank = qtrue; + } + + if ( nextbank ) { + for ( i = 0; i < maxWeapBanks; i++ ) { + if ( cg_cycleAllWeaps.integer ) { + num = getNextBankWeap( bank + i, cycle, qfalse ); // cycling all weaps always starts the next bank at the bottom + } else { + if ( cg.lastWeapSelInBank[bank + i + 1] ) { + num = cg.lastWeapSelInBank[bank + i + 1]; + } else { + num = getNextBankWeap( bank + i, cycle, qtrue ); + } + } + + if ( num == 0 ) { + continue; + } + + if ( CG_WeaponSelectable( num ) ) { // first entry in bank was selectable, no need to scan the bank + break; + } + + CG_WeaponIndex( num, &newbank, &newcycle ); // get the bank of the new weap + + for ( j = newcycle; j < maxWeapsInBank; j++ ) { + num = getNextWeapInBank( newbank, j ); + + if ( CG_WeaponSelectable( num ) ) { // found selectable weapon + break; + } + + num = 0; + } + + if ( num ) { // a selectable weapon was found in the current bank + break; + } + } + } + + CG_FinishWeaponChange( curweap, num ); //----(SA) +} + +/* +============== +CG_PrevWeap + + switchBanks - curweap is the last in a bank + 'qtrue' - go to the next available bank + 'qfalse' - loop to the head of the bank +============== +*/ +void CG_PrevWeap( qboolean switchBanks ) { + int bank = 0, cycle = 0, newbank = 0, newcycle = 0; + int num, curweap; + qboolean prevbank = qfalse; // need to switch to the next bank of weapons? + int i, j; + + num = curweap = cg.weaponSelect; + + CG_WeaponIndex( curweap, &bank, &cycle ); // get bank/cycle of current weapon + + // if you're using an alt mode weapon, try switching back to the parent first + if ( curweap >= WP_BEGINSECONDARY && curweap <= WP_LASTSECONDARY ) { + num = getAltWeapon( curweap ); // base any further changes on the parent + if ( CG_WeaponSelectable( num ) ) { // the parent was selectable, drop back to that + CG_FinishWeaponChange( curweap, num ); + return; + } + } + + // initially, just try to find a lower weapon in the current bank + if ( cg_cycleAllWeaps.integer || !switchBanks ) { + +// if(cycle == 0) { // already at bottom of list +// prevbank = qtrue; +// } else { + for ( i = cycle; i >= 0; i-- ) { +// num = getPrevWeapInBank(bank, i); + num = getPrevWeapInBankBynum( num ); + + CG_WeaponIndex( num, NULL, &newcycle ); // get cycle of new weapon. if it's greater than the original, then it cycled around + + if ( switchBanks ) { + if ( newcycle > ( cycle - 1 ) ) { + prevbank = qtrue; + break; + } + } else { // don't switch banks if you get to the end + if ( num == curweap ) { // back to start, just leave it where it is + return; + } + } + + if ( CG_WeaponSelectable( num ) ) { + break; + } + } +// } + } else { + prevbank = qtrue; + } + + // cycle to previous bank. + // if cycleAllWeaps: find highest weapon in bank + // else: try to find weap in bank that matches cycle position + // else: use base weap in bank + + if ( prevbank ) { + for ( i = 0; i < maxWeapBanks; i++ ) { + if ( cg_cycleAllWeaps.integer ) { + num = getPrevBankWeap( bank - i, cycle, qfalse ); // cycling all weaps always starts the next bank at the bottom + } else { + num = getPrevBankWeap( bank - i, cycle, qtrue ); + } + + if ( num == 0 ) { + continue; + } + + if ( CG_WeaponSelectable( num ) ) { // first entry in bank was selectable, no need to scan the bank + break; + } + + CG_WeaponIndex( num, &newbank, &newcycle ); // get the bank of the new weap + + for ( j = maxWeapsInBank; j > 0; j-- ) { + num = getPrevWeapInBank( newbank, j ); + + if ( CG_WeaponSelectable( num ) ) { // found selectable weapon + break; + } + + num = 0; + } + + if ( num ) { // a selectable weapon was found in the current bank + break; + } + } + } + + CG_FinishWeaponChange( curweap, num ); //----(SA) +} + + +/* +============== +CG_LastWeaponUsed_f +============== +*/ +void CG_LastWeaponUsed_f( void ) { + int lastweap; + + if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { + return; // force pause so holding it down won't go too fast + + } + cg.weaponSelectTime = cg.time; // flash the current weapon icon + + // don't switchback if reloading (it nullifies the reload) + if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { + return; + } + + if ( !cg.switchbackWeapon ) { + cg.switchbackWeapon = cg.weaponSelect; + return; + } + + if ( CG_WeaponSelectable( cg.switchbackWeapon ) ) { + lastweap = cg.weaponSelect; + CG_FinishWeaponChange( cg.weaponSelect, cg.switchbackWeapon ); + } else { // switchback no longer selectable, reset cycle + cg.switchbackWeapon = 0; + } + +} + +/* +============== +CG_NextWeaponInBank_f +============== +*/ +void CG_NextWeaponInBank_f( void ) { + + if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { + return; // force pause so holding it down won't go too fast + + } + // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel) + // for zooming (binocs/snooper/sniper/etc.) + if ( cg.zoomval ) { + if ( cg_useWeapsForZoom.integer == 1 ) { + CG_ZoomIn_f(); + return; + } else if ( cg_useWeapsForZoom.integer == 2 ) { + CG_ZoomOut_f(); + return; + } + } + + cg.weaponSelectTime = cg.time; // flash the current weapon icon + + CG_NextWeap( qfalse ); +} + +/* +============== +CG_PrevWeaponInBank_f +============== +*/ +void CG_PrevWeaponInBank_f( void ) { + + if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { + return; // force pause so holding it down won't go too fast + + } + // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel) + // for zooming (binocs/snooper/sniper/etc.) + if ( cg.zoomval ) { + if ( cg_useWeapsForZoom.integer == 2 ) { + CG_ZoomIn_f(); + return; + } else if ( cg_useWeapsForZoom.integer == 1 ) { + CG_ZoomOut_f(); + return; + } + } + + cg.weaponSelectTime = cg.time; // flash the current weapon icon + + CG_PrevWeap( qfalse ); +} + + +/* +============== +CG_NextWeapon_f +============== +*/ +void CG_NextWeapon_f( void ) { + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel) + // for zooming (binocs/snooper/sniper/etc.) + if ( cg.zoomval ) { + if ( cg_useWeapsForZoom.integer == 1 ) { + CG_ZoomIn_f(); + return; + } else if ( cg_useWeapsForZoom.integer == 2 ) { + CG_ZoomOut_f(); + return; + } + } + + if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { + return; // force pause so holding it down won't go too fast + + } + cg.weaponSelectTime = cg.time; // flash the current weapon icon + + // Don't try to switch when in the middle of reloading. + // cheatinfo: The server actually would let you switch if this check were not + // present, but would discard the reload. So the when you switched + // back you'd have to start the reload over. This seems bad, however + // the delay for the current reload is already in effect, so you'd lose + // the reload time twice. (the first pause for the current weapon reload, + // and the pause when you have to reload again 'cause you canceled this one) + + if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { + return; + } + + CG_NextWeap( qtrue ); +} + + +/* +============== +CG_PrevWeapon_f +============== +*/ +void CG_PrevWeapon_f( void ) { + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + // this cvar is an option that lets the player use his weapon switching keys (probably the mousewheel) + // for zooming (binocs/snooper/sniper/etc.) + if ( cg.zoomval ) { + if ( cg_useWeapsForZoom.integer == 1 ) { + CG_ZoomOut_f(); + return; + } else if ( cg_useWeapsForZoom.integer == 2 ) { + CG_ZoomIn_f(); + return; + } + } + + if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { + return; // force pause so holding it down won't go too fast + + } + cg.weaponSelectTime = cg.time; // flash the current weapon icon + + // Don't try to switch when in the middle of reloading. + if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { + return; + } + + CG_PrevWeap( qtrue ); +} + + +/* +============== +CG_WeaponBank_f + weapon keys are not generally bound directly('bind 1 weapon 1'), + rather the key is bound to a given bank ('bind 1 weaponbank 1') +============== +*/ +void CG_WeaponBank_f( void ) { + int num, i, curweap; + int curbank = 0, curcycle = 0, bank = 0, cycle = 0; + + if ( !cg.snap ) { + return; + } + + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if ( cg.time - cg.weaponSelectTime < cg_weaponCycleDelay.integer ) { + return; // force pause so holding it down won't go too fast + + } + cg.weaponSelectTime = cg.time; // flash the current weapon icon + + // Don't try to switch when in the middle of reloading. + if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { + return; + } + + bank = atoi( CG_Argv( 1 ) ); + + if ( bank <= 0 || bank > maxWeapBanks ) { + return; + } + + curweap = cg.weaponSelect; + CG_WeaponIndex( curweap, &curbank, &curcycle ); // get bank/cycle of current weapon + + if ( !cg.lastWeapSelInBank[bank] ) { + if ( cg_gameType.integer < GT_WOLF ) { // JPW NERVE + num = weapBanks[bank][0]; + } +// JPW NERVE + else { + num = weapBanksMultiPlayer[bank][0]; + } +// jpw + cycle -= 1; // cycle up to first weap + } else { + num = cg.lastWeapSelInBank[bank]; + CG_WeaponIndex( num, &bank, &cycle ); + if ( bank != curbank ) { + cycle -= 1; + } + + } + + for ( i = 0; i < maxWeapsInBank; i++ ) { + num = getNextWeapInBank( bank, cycle + i ); + + if ( CG_WeaponSelectable( num ) ) { + break; + } + } + + if ( i == maxWeapsInBank ) { + return; + } + + CG_FinishWeaponChange( curweap, num ); + +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) { + int num, i, curweap; + int bank = 0, cycle = 0, newbank = 0, newcycle = 0; + qboolean banked = qfalse; + + if ( !cg.snap ) { + return; + } + + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + num = atoi( CG_Argv( 1 ) ); + +// JPW NERVE +// weapon bind should execute weaponbank instead -- for splitting out class weapons, per Id request + if ( cg_gameType.integer >= GT_WOLF ) { + if ( num < maxWeapBanks ) { + CG_WeaponBank_f(); + } + return; + } +// jpw + + cg.weaponSelectTime = cg.time; // flash the current weapon icon + + // Don't try to switch when in the middle of reloading. + if ( cg.snap->ps.weaponstate == WEAPON_RELOADING ) { + return; + } + + + if ( num <= WP_NONE || num > WP_NUM_WEAPONS ) { + return; + } + + curweap = cg.weaponSelect; + + CG_WeaponIndex( curweap, &bank, &cycle ); // get bank/cycle of current weapon + banked = CG_WeaponIndex( num, &newbank, &newcycle ); // get bank/cycle of requested weapon + + // the new weapon was not found in the reglar banks + // assume the player want's to go directly to it if possible + if ( !banked ) { + if ( CG_WeaponSelectable( num ) ) { + CG_FinishWeaponChange( curweap, num ); + return; + } + } + + if ( bank != newbank ) { + cycle = newcycle - 1; // drop down one from the requested weap's cycle so it will + } + // try to initially cycle up to the requested weapon + + for ( i = 0; i < maxWeapsInBank; i++ ) { + num = getNextWeapInBank( newbank, cycle + i ); + + if ( num == curweap ) { // no other weapons in bank + return; + } + + if ( CG_WeaponSelectable( num ) ) { + break; + } + } + + if ( i == maxWeapsInBank ) { + return; + } + + CG_FinishWeaponChange( curweap, num ); +} + +/* +=================== +CG_OutOfAmmoChange + +The current weapon has just run out of ammo +=================== +*/ +void CG_OutOfAmmoChange( void ) { + int i; + int bank, cycle; + int equiv = WP_NONE; + + // + // trivial switching + // + +// JPW NERVE -- early out if we just dropped dynamite, go to pliers + if ( cg.weaponSelect == WP_DYNAMITE ) { + if ( CG_WeaponSelectable( WP_PLIERS ) ) { + cg.weaponSelect = WP_PLIERS; + CG_FinishWeaponChange( cg.predictedPlayerState.weapon, WP_PLIERS ); + return; + } + } +// jpw + +// JPW NERVE -- early out if we just fired Panzerfaust, go to pistola, then grenades + if ( cg.weaponSelect == WP_PANZERFAUST ) { + for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) + if ( CG_WeaponSelectable( weapBanksMultiPlayer[2][i] ) ) { // find a pistol + cg.weaponSelect = weapBanksMultiPlayer[2][i]; + CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); + return; + } + for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) + if ( CG_WeaponSelectable( weapBanksMultiPlayer[4][i] ) ) { // find a grenade + cg.weaponSelect = weapBanksMultiPlayer[4][i]; + CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); + return; + } + } +// jpw + + // never switch weapon if auto-reload is disabled + if ( !cg.pmext.bAutoReload && IS_AUTORELOAD_WEAPON( cg.weaponSelect ) ) { + return; + } + + // if you're using an alt mode weapon, try switching back to the parent + // otherwise, switch to the equivalent if you've got it + if ( cg.weaponSelect >= WP_BEGINSECONDARY && cg.weaponSelect <= WP_LASTSECONDARY ) { + cg.weaponSelect = equiv = getAltWeapon( cg.weaponSelect ); // base any further changes on the parent + if ( CG_WeaponSelectable( equiv ) ) { // the parent was selectable, drop back to that + CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA) + return; + } + } + + + // now try the opposite team's equivalent weap + equiv = getEquivWeapon( cg.weaponSelect ); + + if ( equiv != cg.weaponSelect && CG_WeaponSelectable( equiv ) ) { + cg.weaponSelect = equiv; + CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA) + return; + } + + // + // more complicated selection + // + + // didn't have available alternative or equivalent, try another weap in the bank + CG_WeaponIndex( cg.weaponSelect, &bank, &cycle ); // get bank/cycle of current weapon + +// JPW NERVE -- more useful weapon changes -- check if rifle or pistol is still working, and use that if available + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) { + for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) + if ( CG_WeaponSelectable( weapBanksMultiPlayer[3][i] ) ) { // find a rifle + cg.weaponSelect = weapBanksMultiPlayer[3][i]; + CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); + return; + } + for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) + if ( CG_WeaponSelectable( weapBanksMultiPlayer[2][i] ) ) { // find a pistol + cg.weaponSelect = weapBanksMultiPlayer[2][i]; + CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); + return; + } + } + // otherwise just do something +// jpw + + for ( i = cycle; i < maxWeapsInBank; i++ ) { + equiv = getNextWeapInBank( bank, i ); + if ( CG_WeaponSelectable( equiv ) ) { // found a reasonable replacement + cg.weaponSelect = equiv; + CG_FinishWeaponChange( cg.predictedPlayerState.weapon, cg.weaponSelect ); //----(SA) + return; + } + } + + + // still nothing available, just go to the next + // available weap using the regular selection scheme + CG_NextWeap( qtrue ); + +} + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +void CG_MG42EFX( centity_t *cent ) { + // Arnout: complete overhaul of this one + centity_t *mg42; + int num; + vec3_t forward, point; + refEntity_t flash; + + // find the mg42 we're attached to + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + mg42 = &cg_entities[ cg.snap->entities[ num ].number ]; + if ( mg42->currentState.eType == ET_MG42_BARREL && + mg42->currentState.otherEntityNum == cent->currentState.number ) { + // found it, clamp behind gun + + VectorCopy( mg42->currentState.pos.trBase, point ); + //AngleVectors (mg42->s.apos.trBase, forward, NULL, NULL); + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + VectorMA( point, 40, forward, point ); + + memset( &flash, 0, sizeof( flash ) ); + flash.renderfx = RF_LIGHTING_ORIGIN; + flash.hModel = cgs.media.mg42muzzleflash; + + VectorCopy( point, flash.origin ); + AnglesToAxis( cent->lerpAngles, flash.axis ); + + trap_R_AddRefEntityToScene( &flash ); + + return; + } + } + +} + +// Note to self this is dead code +void CG_FLAKEFX( centity_t *cent, int whichgun ) { + entityState_t *ent; + vec3_t forward, right, up; + vec3_t point; + refEntity_t flash; + + ent = ¢->currentState; + + VectorCopy( cent->currentState.pos.trBase, point ); + AngleVectors( cent->currentState.apos.trBase, forward, right, up ); + + // gun 1 and 2 were switched + if ( whichgun == 2 ) { + VectorMA( point, 136, forward, point ); + VectorMA( point, 31, up, point ); + VectorMA( point, 22, right, point ); + } else if ( whichgun == 1 ) { + VectorMA( point, 136, forward, point ); + VectorMA( point, 31, up, point ); + VectorMA( point, -22, right, point ); + } else if ( whichgun == 3 ) { + VectorMA( point, 136, forward, point ); + VectorMA( point, 10, up, point ); + VectorMA( point, 22, right, point ); + } else if ( whichgun == 4 ) { + VectorMA( point, 136, forward, point ); + VectorMA( point, 10, up, point ); + VectorMA( point, -22, right, point ); + } + + trap_R_AddLightToScene( point, 200 + ( rand() & 31 ),1.0, 0.6, 0.23, 0 ); + + memset( &flash, 0, sizeof( flash ) ); + flash.renderfx = RF_LIGHTING_ORIGIN; + flash.hModel = cgs.media.mg42muzzleflash; + + VectorCopy( point, flash.origin ); + AnglesToAxis( cg.refdefViewAngles, flash.axis ); + + trap_R_AddRefEntityToScene( &flash ); + + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, hflakWeaponSnd ); +} + + +//----(SA) +/* +============== +CG_MortarEFX + Right now mostly copied directly from Raf's MG42 FX, but with the optional addtion of smoke +============== +*/ +void CG_MortarEFX( centity_t *cent ) { + refEntity_t flash; + + if ( cent->currentState.density & 1 ) { + // smoke + CG_ParticleImpactSmokePuff( cgs.media.smokePuffShader, cent->currentState.origin ); + } + + if ( cent->currentState.density & 2 ) { + // light + trap_R_AddLightToScene( cent->currentState.origin, 200 + ( rand() & 31 ), 1.0, 1.0, 1.0, 0 ); + + // muzzle flash + memset( &flash, 0, sizeof( flash ) ); + flash.renderfx = RF_LIGHTING_ORIGIN; + flash.hModel = cgs.media.mg42muzzleflash; + VectorCopy( cent->currentState.origin, flash.origin ); + AnglesToAxis( cg.refdefViewAngles, flash.axis ); + trap_R_AddRefEntityToScene( &flash ); + } +} + +//----(SA) end + + +// RF +/* +============== +CG_WeaponFireRecoil +============== +*/ +void CG_WeaponFireRecoil( int weapon ) { +// const vec3_t maxKickAngles = {25, 30, 25}; + float pitchRecoilAdd, pitchAdd; + float yawRandom; + vec3_t recoil; + // + pitchRecoilAdd = 0; + pitchAdd = 0; + yawRandom = 0; + // + switch ( weapon ) { + case WP_LUGER: + case WP_SILENCER: + case WP_COLT: + case WP_AKIMBO: //----(SA) added + //pitchAdd = 2+rand()%3; + //yawRandom = 2; + break; + case WP_MAUSER: + case WP_GARAND: + //pitchAdd = 4+rand()%3; + //yawRandom = 4; + pitchAdd = 2; //----(SA) for DM + yawRandom = 1; //----(SA) for DM + break; + case WP_SNIPERRIFLE: + case WP_SNOOPERSCOPE: + pitchAdd = 0.6; + break; + case WP_FG42SCOPE: + case WP_FG42: + case WP_BAR: //----(SA) added + case WP_BAR2: //----(SA) added + case WP_MP40: + case WP_THOMPSON: + case WP_STEN: + //pitchRecoilAdd = 1; + pitchAdd = 1 + rand() % 3; + yawRandom = 2; + + + pitchAdd *= 0.3; + yawRandom *= 0.3; + break; + case WP_PANZERFAUST: + case WP_ROCKET_LAUNCHER: + //pitchAdd = 12+rand()%3; + //yawRandom = 6; + + // push the player back instead + break; + case WP_VENOM: + case WP_VENOM_FULL: + pitchRecoilAdd = pow( random(),8 ) * ( 10 + VectorLength( cg.snap->ps.velocity ) / 5 ); + pitchAdd = ( rand() % 5 ) - 2; + yawRandom = 2; + + + pitchRecoilAdd *= 0.5; + pitchAdd *= 0.5; + yawRandom *= 0.5; + break; + default: + return; + } + // calc the recoil + recoil[YAW] = crandom() * yawRandom; + recoil[ROLL] = -recoil[YAW]; // why not + recoil[PITCH] = -pitchAdd; + // scale it up a bit (easier to modify this while tweaking) + VectorScale( recoil, 30, recoil ); + // set the recoil + VectorCopy( recoil, cg.kickAVel ); + // set the recoil + cg.recoilPitch -= pitchRecoilAdd; +} + + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event + +================ +*/ +void CG_FireWeapon( centity_t *cent ) { + entityState_t *ent; + int c; + weaponInfo_t *weap; + sfxHandle_t *firesound; + sfxHandle_t *fireEchosound; + + ent = ¢->currentState; + + // Rafael - mg42 +// if ( (cent->currentState.clientNum == cg.snap->ps.clientNum && cg.snap->ps.persistant[PERS_HWEAPON_USE] ) || +// (cent->currentState.clientNum != cg.snap->ps.clientNum && (cent->currentState.eFlags & EF_MG42_ACTIVE) ) ) + if ( ( cent->currentState.clientNum == cg.snap->ps.clientNum && cg.snap->ps.persistant[PERS_HWEAPON_USE] ) || + ( cent->currentState.eFlags & EF_MG42_ACTIVE ) ) { + if ( cg.snap->ps.gunfx ) { + return; + } + + trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, hWeaponSnd ); + //trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, hWeaponSnd ); + if ( cg_brassTime.integer > 0 ) { + CG_MachineGunEjectBrass( cent ); + } + + cent->muzzleFlashTime = cg.time; + + return; + } + + if ( ent->weapon == WP_NONE ) { + return; + } + if ( ent->weapon >= WP_NUM_WEAPONS ) { + CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); + return; + } + weap = &cg_weapons[ ent->weapon ]; + + cg.lastFiredWeapon = ent->weapon; //----(SA) added + + // mark the entity as muzzle flashing, so when it is added it will + // append the flash to the weapon model + cent->muzzleFlashTime = cg.time; + + // RF, kick angles + if ( ent->number == cg.snap->ps.clientNum ) { + CG_WeaponFireRecoil( ent->weapon ); + } + + // lightning gun only does this this on initial press + if ( ent->weapon == WP_FLAMETHROWER ) { + if ( cent->pe.lightningFiring ) { + return; + } + } else if ( ent->weapon == WP_GRENADE_LAUNCHER || + ent->weapon == WP_GRENADE_PINEAPPLE || + ent->weapon == WP_DYNAMITE || + ent->weapon == WP_DYNAMITE2 || + ent->weapon == WP_SMOKE_GRENADE ) { // JPW NERVE +// if (ent->weapon == WP_SMOKE_GRENADE) +// CG_Printf("smoke grenade!\n"); + if ( ent->apos.trBase[0] > 0 ) { // underhand + return; + } + } + + // play quad sound if needed + if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { + trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); + } + + if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == EV_FIRE_WEAPON_LASTSHOT ) { + firesound = &weap->lastShotSound[0]; + fireEchosound = &weap->flashEchoSound[0]; + + // try to use the lastShotSound, but don't assume it's there. + // if a weapon without the sound calls it, drop back to regular fire sound + + for ( c = 0; c < 4; c++ ) { + if ( !firesound[c] ) { + break; + } + } + if ( !c ) { + firesound = &weap->flashSound[0]; + fireEchosound = &weap->flashEchoSound[0]; + } + } else { + firesound = &weap->flashSound[0]; + fireEchosound = &weap->flashEchoSound[0]; + } + + +// JPW NERVE -- special case medic tool + if ( ent->weapon == WP_MEDKIT ) { + firesound = &cg_weapons[ WP_MEDKIT ].flashSound[0]; + } + //= trap_S_RegisterSound("sound/multiplayer/bag_toss.wav"); +// jpw + + if ( !( cent->currentState.eFlags & EF_ZOOMING ) ) { // JPW NERVE -- don't play sounds or eject brass if zoomed in + // play a sound + for ( c = 0 ; c < 4 ; c++ ) { + if ( !firesound[c] ) { + break; + } + } + if ( c > 0 ) { + c = rand() % c; + if ( firesound[c] ) { + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, firesound[c] ); + + if ( fireEchosound && fireEchosound[c] ) { // check for echo + centity_t *cent; + vec3_t porg, gorg, norm; // player/gun origin + float gdist; + + cent = &cg_entities[ent->number]; + VectorCopy( cent->currentState.pos.trBase, gorg ); + VectorCopy( cg.refdef.vieworg, porg ); + VectorSubtract( gorg, porg, norm ); + gdist = VectorNormalize( norm ); + if ( gdist > 512 && gdist < 4096 ) { // temp dist. TODO: use numbers that are weapon specific + // use gorg as the new sound origin + VectorMA( cg.refdef.vieworg, 64, norm, gorg ); // sound-on-a-stick + trap_S_StartSoundEx( gorg, ent->number, CHAN_WEAPON, fireEchosound[c], SND_NOCUT ); + } + } + } + } + + // do brass ejection + if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { + weap->ejectBrassFunc( cent ); + } + } // jpw +} + + +// Ridah +/* +================= +CG_AddSparks +================= +*/ +void CG_AddSparks( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity; + int i; + + for ( i = 0; i < count; i++ ) { + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + VectorSet( velocity, dir[0] + crandom() * randScale, dir[1] + crandom() * randScale, dir[2] + crandom() * randScale ); + VectorScale( velocity, (float)speed, velocity ); + + le->leType = LE_SPARK; + le->startTime = cg.time; + le->endTime = le->startTime + duration - (int)( 0.5 * random() * duration ); + le->lastTrailTime = cg.time; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + + le->pos.trType = TR_GRAVITY_LOW; + VectorCopy( origin, le->pos.trBase ); + VectorMA( le->pos.trBase, 2 + random() * 4, dir, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->refEntity.customShader = cgs.media.sparkParticleShader; + + le->bounceFactor = 0.9; + +// le->leBounceSoundType = LEBS_BLOOD; +// le->leMarkType = LEMT_BLOOD; + } +} +/* +================= +CG_AddBulletParticles +================= +*/ +void CG_AddBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale ) { +// localEntity_t *le; +// refEntity_t *re; + vec3_t velocity, pos; + int i; +/* + // add the falling streaks + for (i=0; irefEntity; + + VectorSet( velocity, dir[0] + crandom()*randScale, dir[1] + crandom()*randScale, dir[2] + crandom()*randScale ); + VectorScale( velocity, (float)speed*3, velocity ); + + le->leType = LE_SPARK; + le->startTime = cg.time; + le->endTime = le->startTime + duration - (int)(0.5 * random() * duration); + le->lastTrailTime = cg.time; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorMA( le->pos.trBase, 2 + random()*4, dir, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->refEntity.customShader = cgs.media.bulletParticleTrailShader; +// le->refEntity.customShader = cgs.media.sparkParticleShader; + + le->bounceFactor = 0.9; + +// le->leBounceSoundType = LEBS_BLOOD; +// le->leMarkType = LEMT_BLOOD; + } +*/ + // add the falling particles + for ( i = 0; i < count; i++ ) { + + VectorSet( velocity, dir[0] + crandom() * randScale, dir[1] + crandom() * randScale, dir[2] + crandom() * randScale ); + VectorScale( velocity, (float)speed, velocity ); + + VectorCopy( origin, pos ); + VectorMA( pos, 2 + random() * 4, dir, pos ); + + CG_ParticleBulletDebris( pos, velocity, 300 + rand() % 300 ); + + } +} + +// DHM - Nerve :: New Dirt bullet particles +/* +================= +CG_AddDirtBulletParticles +================= +*/ +void CG_AddDirtBulletParticles( vec3_t origin, vec3_t dir, int speed, int duration, int count, float randScale, + float width, float height, float alpha, char *shadername ) { // JPW NERVE + vec3_t velocity, pos; + int i; + + // add the big falling particle + VectorSet( velocity, 0, 0, (float)speed ); + + VectorCopy( origin, pos ); + +// JPW NERVE + CG_ParticleDirtBulletDebris_Core( pos, velocity, duration, width,height, alpha, shadername ); //600 + rand()%300 ); // keep central one + for ( i = 0; i < count; i++ ) { + VectorSet( velocity, dir[0] * crandom() * speed * randScale, + dir[1] * crandom() * speed * randScale, dir[2] * random() * speed ); + CG_ParticleDirtBulletDebris_Core( pos, velocity, duration + ( rand() % ( duration >> 1 ) ), // dur * 0.5, but int + width,height, alpha, shadername ); + } +// jpw +/* old one (this one works) + CG_ParticleDirtBulletDebris_Core( pos, velocity, 600 + rand()%300 ); + + // add the falling particles + for (i=0; irefEntity; + + VectorSet( unitvel, dir[0] + crandom() * 0.9, dir[1] + crandom() * 0.9, fabs( dir[2] ) > 0.5 ? dir[2] * ( 0.2 + 0.8 * random() ) : random() * 0.6 ); + VectorScale( unitvel, (float)speed + (float)speed * 0.5 * crandom(), velocity ); + + le->leType = LE_DEBRIS; + le->startTime = cg.time; + le->endTime = le->startTime + duration + (int)( (float)duration * 0.8 * crandom() ); + le->lastTrailTime = cg.time; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + + le->pos.trType = TR_GRAVITY_LOW; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + timeAdd = 10.0 + random() * 40.0; + BG_EvaluateTrajectory( &le->pos, cg.time + (int)timeAdd, le->pos.trBase ); + + le->bounceFactor = 0.5; + +// if (!rand()%2) +// le->effectWidth = 0; // no flame +// else + le->effectWidth = 5 + random() * 5; + +// if (rand()%3) + le->effectFlags |= 1; // smoke trail + + +// le->leBounceSoundType = LEBS_BLOOD; +// le->leMarkType = LEMT_BLOOD; + } +} +// done. + + +/* +============== +CG_WaterRipple +============== +*/ +void CG_WaterRipple( qhandle_t shader, vec3_t loc, vec3_t dir, int size, int lifetime ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leType = LE_SCALE_FADE; + le->leFlags = LEF_PUFF_DONT_SCALE; + + le->startTime = cg.time; + le->endTime = cg.time + lifetime; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + VectorCopy( loc, re->origin ); + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_SPLASH; + re->radius = size; + re->customShader = shader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + le->color[3] = 1.0; +} + +/* +================= +CG_MissileHitWall + +Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing + +ClientNum is a dummy field used to define what sort of effect to spawn +================= +*/ +void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, int surfFlags ) { // (SA) modified to send missilehitwall surface parameters + qhandle_t mod; + qhandle_t mark; + qhandle_t shader; + sfxHandle_t sfx, sfx2; + float radius; + float light; + vec3_t lightColor; + localEntity_t *le; + int r; + qboolean alphaFade = qfalse; + qboolean isSprite; + int duration; + // Ridah + int lightOverdraw; + vec3_t sprOrg; + vec3_t sprVel; + int i,j; + int markDuration; + trace_t trace; // JPW NERVE + vec3_t tmpv,tmpv2; // JPW NERVE + float sfx2range = 0; // JPW NERVE -- kludge to get around lack of *VOLUME CONTROL* in sound mixer + + mark = 0; + radius = 32; + sfx = 0; + sfx2 = 0; + mod = 0; + shader = 0; + light = 0; + lightColor[0] = 1; + lightColor[1] = 1; + lightColor[2] = 0; + // Ridah + lightOverdraw = 0; + + // set defaults + isSprite = qfalse; + duration = 600; + markDuration = -1; + + if ( surfFlags & SURF_SKY ) { + return; + } + + switch ( weapon ) { + case WP_KNIFE2: + case WP_KNIFE: + i = rand() % 4; + + // DHM - Nerve :: this was cause global sound on client 0 + //if ( cgs.media.sfx_knifehit[i] ) + // trap_S_StartSound( origin, clientNum, CHAN_WEAPON, cgs.media.sfx_knifehit[i] ); + +// JPW NERVE + if ( !surfFlags ) { + sfx = cgs.media.sfx_knifehit[4]; // different values for different types (stone/metal/wood/etc.) + mark = cgs.media.bulletMarkShader; + radius = 1 + rand() % 2; + + CG_AddBulletParticles( origin, dir, + 20, // speed + 800, // duration + 3 + rand() % 6, // count + 1.0 ); // rand scale + } else { + sfx = cgs.media.sfx_knifehit[i]; + } +// jpw + break; + + case WP_LUGER: + case WP_AKIMBO: //----(SA) added + case WP_COLT: + case WP_MAUSER: + case WP_GARAND: + case WP_SNIPERRIFLE: + case WP_SNOOPERSCOPE: + case WP_MP40: + case WP_FG42: + case WP_FG42SCOPE: + case WP_BAR: //----(SA) added + case WP_BAR2: //----(SA) added + case WP_THOMPSON: + case WP_STEN: + case WP_SILENCER: + case WP_VENOM: + case WP_VENOM_FULL: + + // actually yeah. meant that. very rare. + if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { // JPW NERVE + r = rand() & 31; + } else { + r = ( rand() & 3 ) + 1; // JPW NERVE increased spark frequency so players can tell where rounds are coming from in MP + + } + if ( r == 3 ) { + sfx = cgs.media.sfx_ric1; + } else if ( r == 2 ) { + sfx = cgs.media.sfx_ric2; + } else if ( r == 1 ) { + sfx = cgs.media.sfx_ric3; + } + + // clientNum is a dummy field used to define what sort of effect to spawn + + if ( !clientNum ) { + // RF, why is this here? we need sparks if clientNum = 0, used for warzombie + // if ( sfx ) + CG_AddSparks( origin, dir, + 350, // speed + 200, // duration + 15 + rand() % 7, // count + 0.2 ); // rand scale + } else if ( clientNum == 1 ) { // just do a little smoke puff + vec3_t d, o; + VectorMA( origin, 12, dir, o ); + VectorScale( dir, 7, d ); + d[2] += 16; + + // DHM - Nerve :: use dirt images + if ( cgs.gametype >= GT_WOLF && ( surfFlags & SURF_GRASS || surfFlags & SURF_GRAVEL || surfFlags & SURF_SNOW ) ) { // JPW NERVE added SURF_SNOW + + // some debris particles + // JPW NERVE added surf_snow + if ( surfFlags & SURF_SNOW ) { + CG_AddDirtBulletParticles( origin, dir, + 190, // speed + 900, // duration + 5, // count + 0.25, 80,32, 0.5, "water_splash" ); // rand scale + } else { + CG_AddDirtBulletParticles( origin, dir, + 190, // speed + 900, // duration + 5, // count + 0.5, 80,16, 0.5, "dirt_splash" ); // rand scale + } + } else { + CG_ParticleImpactSmokePuff( cgs.media.smokeParticleShader, o ); + + // some debris particles + CG_AddBulletParticles( origin, dir, + 20, // speed + 800, // duration + 3 + rand() % 6, // count + 1.0 ); // rand scale + + // just do a little one + if ( sfx && ( rand() % 3 == 0 ) ) { + CG_AddSparks( origin, dir, + 450, // speed + 300, // duration + 3 + rand() % 3, // count + 0.5 ); // rand scale + } + } + } else if ( clientNum == 2 ) { + sfx = 0; + mark = 0; + + // (SA) needed to do the CG_WaterRipple using a localent since I needed the timer reset on the shader for each shot + CG_WaterRipple( cgs.media.wakeMarkShaderAnim, origin, tv( 0, 0, 1 ), 32, 1000 ); +// JPW NERVE + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) { + CG_AddDirtBulletParticles( origin, dir, + 190, // speed + 900, // duration + 5, // count + 0.5, 80, 16, 0.125, "water_splash" ); // rand scale + } +// jpw + break; // (SA) testing + + // play a water splash + mod = cgs.media.waterSplashModel; + shader = cgs.media.waterSplashShader; + duration = 250; + } + + // Ridah, optimization, only spawn the bullet hole if we are close + // enough to see it, this way we can leave other marks around a lot + // longer, since most of the time we can't actually see the bullet holes +// (SA) small modification. only do this for non-rifles (so you can see your shots hitting when you're zooming with a rifle scope) + if ( weapon == WP_FG42SCOPE || weapon == WP_SNIPERRIFLE || weapon == WP_SNOOPERSCOPE || ( Distance( cg.refdef.vieworg, origin ) < 384 ) ) { + + if ( clientNum ) { + + // mark and sound can potentially use the surface for override values + + mark = cgs.media.bulletMarkShader; // default + alphaFade = qtrue; // max made the bullet mark alpha (he'll make everything in the game out of 1024 textures, all with alpha blend funcs yet...) + radius = 1.5f + rand() % 2; // slightly larger for DM + + if ( surfFlags & SURF_METAL ) { + sfx = cgs.media.sfx_bullet_metalhit[rand() % 3]; + mark = cgs.media.bulletMarkShaderMetal; + alphaFade = qtrue; + } else if ( surfFlags & SURF_WOOD ) { + sfx = cgs.media.sfx_bullet_woodhit[rand() % 3]; + mark = cgs.media.bulletMarkShaderWood; + alphaFade = qtrue; + radius += 1; // experimenting with different mark sizes per surface + +/* + if (rand()%100 > 75) + { + gentity_t *sfx; + vec3_t start; + vec3_t dir; + + sfx = G_Spawn (); + + sfx->s.density = type; + + VectorCopy (tr.endpos, start); + + VectorCopy (muzzleTrace, dir); + VectorNegate (dir, dir); + + G_SetOrigin (sfx, start); + G_SetAngle (sfx, dir); + + G_AddEvent( sfx, EV_SHARD, DirToByte( dir )); + + sfx->think = G_FreeEntity; + sfx->nextthink = level.time + 1000; + + sfx->s.frame = 3 + (rand()%3) ; + + trap_LinkEntity (sfx); + + +void CG_Shard(centity_t *cent, vec3_t origin, vec3_t dir) + CG_Shard + + } + +*/ + + +// CG_Explodef(origin, dir, mass, type, sound, lowgrav, shader); + + + } else if ( surfFlags & SURF_CERAMIC ) { + sfx = cgs.media.sfx_bullet_ceramichit[rand() % 3]; + mark = cgs.media.bulletMarkShaderCeramic; + alphaFade = qtrue; + radius += 2; // experimenting with different mark sizes per surface + + } else if ( surfFlags & SURF_GLASS ) { + sfx = cgs.media.sfx_bullet_glasshit[rand() % 3]; + mark = cgs.media.bulletMarkShaderGlass; + alphaFade = qtrue; + } else if ( surfFlags & SURF_GRASS ) { + + } else if ( surfFlags & SURF_GRAVEL ) { + + } else if ( surfFlags & SURF_SNOW ) { + + } else if ( surfFlags & SURF_ROOF ) { + + } else if ( surfFlags & SURF_CARPET ) { + + } + + } + } + break; + + + case WP_SPEARGUN: + case WP_SPEARGUN_CO2: + mod = cgs.media.spearModel; + duration = 8000; // leave stuck in wall for 8 sec + sfx = cgs.media.sfx_spearhit; + VectorSubtract( vec3_origin, dir, dir ); // flip the direction around since it's made to be the flying projectile + break; + + + case WP_MORTAR: + sfx = cgs.media.sfx_rockexp; + sfx2 = cgs.media.sfx_rockexpDist; + sfx2range = 1200; + mark = cgs.media.burnMarkShader; + markDuration = 60000; + radius = 64; + light = 300; + isSprite = qtrue; + duration = 1000; + lightColor[0] = 0.75; + lightColor[1] = 0.5; + lightColor[2] = 0.1; + + VectorScale( dir, 16, sprVel ); + if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { // JPW NERVE + for ( i = 0; i < 5; i++ ) { + for ( j = 0; j < 3; j++ ) + sprOrg[j] = origin[j] + 64 * dir[j] + 24 * crandom(); + sprVel[2] += rand() % 50; +// CG_ParticleExplosion( 2, sprOrg, sprVel, 1000+rand()%250, 20, 40+rand()%60 ); + CG_ParticleExplosion( "blacksmokeanim", sprOrg, sprVel, 3500 + rand() % 250, 10, 250 + rand() % 60 ); // JPW NERVE was animb + } + + VectorMA( origin, 24, dir, sprOrg ); + VectorScale( dir, 64, sprVel ); + // RF, I like this new animation, feel free to revert + CG_ParticleExplosion( "expblue", sprOrg, sprVel, 1000, 20, 300 ); + //CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1200, 9, 300 ); + } +// JPW NERVE + else { +// check for water/dirt spurt + if ( trap_CM_PointContents( origin, 0 ) & CONTENTS_WATER ) { + VectorCopy( origin,tmpv ); + tmpv[2] += 10000; + + trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); + CG_WaterRipple( cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 150, 1000 ); + + CG_AddDirtBulletParticles( trace.endpos, dir, + 900, // speed + 1800, // duration + 15, // count + 0.5, 350,128, 0.125, "water_splash" ); + } else { + VectorCopy( origin,tmpv ); + tmpv[2] += 20; + VectorCopy( origin,tmpv2 ); + tmpv2[2] -= 20; + trap_CM_BoxTrace( &trace,tmpv,tmpv2,NULL,NULL,0,MASK_SHOT ); + if ( trace.surfaceFlags & SURF_GRASS || trace.surfaceFlags & SURF_GRAVEL ) { + CG_AddDirtBulletParticles( origin, dir, + 600, // speed + 2000, // duration + 10, // count + 0.5, 275,125, 0.25, "dirt_splash" ); // 128,64 + + } + for ( i = 0; i < 5; i++ ) { + for ( j = 0; j < 3; j++ ) + sprOrg[j] = origin[j] + 64 * dir[j] + 24 * crandom(); + sprVel[2] += rand() % 50; + CG_ParticleExplosion( "blacksmokeanim", sprOrg, sprVel, 3500 + rand() % 250, 10, 250 + rand() % 60 ); // JPW NERVE was animb + } + VectorMA( origin, 24, dir, sprOrg ); + VectorScale( dir, 64, sprVel ); + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1000, 20, 300 ); + } + } +// jpw + break; + + case WP_DYNAMITE: + case WP_DYNAMITE2: + shader = cgs.media.rocketExplosionShader; + sfx = cgs.media.sfx_dynamiteexp; + sfx2 = cgs.media.sfx_dynamiteexpDist; + sfx2range = 400; + mark = cgs.media.burnMarkShader; + markDuration = 60000; + radius = 64; + light = 300; + isSprite = qtrue; + duration = 1000; + lightColor[0] = 0.75; + lightColor[1] = 0.5; + lightColor[2] = 0.1; + +// JPW NERVE +// biggie dynamite explosions that mean it -- dynamite is biggest explode, so it gets extra crap thrown on +// check for water/dirt spurt + if ( trap_CM_PointContents( origin, 0 ) & CONTENTS_WATER ) { + VectorCopy( origin,tmpv ); + tmpv[2] += 10000; + + trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); + CG_WaterRipple( cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 300, 2000 ); + + CG_AddDirtBulletParticles( trace.endpos, dir, + 400 + random() * 200, // speed + 900, // duration + 15, // count + 0.5, 512,128, 0.125, "water_splash" ); + CG_AddDirtBulletParticles( trace.endpos, dir, // flat splashy bits + 400 + random() * 600, // speed + 1400, // duration + 15, // count + 0.5, 128,512, 0.125, "water_splash" ); + + } else { + VectorCopy( origin,tmpv ); + tmpv[2] += 20; + VectorCopy( origin,tmpv2 ); + tmpv2[2] -= 20; + trap_CM_BoxTrace( &trace,tmpv,tmpv2,NULL,NULL,0,MASK_SHOT ); + if ( trace.surfaceFlags & SURF_GRASS || trace.surfaceFlags & SURF_GRAVEL ) { + CG_AddDirtBulletParticles( origin, dir, + 400 + random() * 200, // speed + 3000, // duration + 10, // count + 0.5, 400,256, 0.25, "dirt_splash" ); // 128,64 + } + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + sprOrg[j] = origin[j] + 150 * crandom(); + sprVel[j] = 0.35 * crandom(); + } + VectorAdd( sprVel, trace.plane.normal, sprVel ); + VectorScale( sprVel,130,sprVel ); + CG_ParticleExplosion( "blacksmokeanim", sprOrg, sprVel, 6000 + random() * 2000, 40, 400 + random() * 200 ); // JPW NERVE was blacksmokeanimb + } + for ( i = 0; i < 4; i++ ) { // JPW random vector based on plane normal so explosions move away from walls/dirt/etc + for ( j = 0; j < 3; j++ ) { + sprOrg[j] = origin[j] + 100 * crandom(); + sprVel[j] = 0.65 * crandom(); // wider fireball spread + } + VectorAdd( sprVel, trace.plane.normal, sprVel ); + VectorScale( sprVel,random() * 100 + 300,sprVel ); + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1000 + rand() % 1450, 40, 400 + random() * 200 ); + } + CG_AddDebris( origin, dir, + 400 + random() * 200, // speed + rand() % 2000 + 1400, //350, // duration + // 15 + rand()%5 ); // count + 12 + rand() % 12 ); // count + + } +// jpw + break; + + case WP_SMOKE_GRENADE: // JPW NERVE + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_PROX: //----(SA) added +// mod = cgs.media.dishFlashModel; +// shader = cgs.media.grenadeExplosionShader; + shader = cgs.media.rocketExplosionShader; // copied from RL + sfx = cgs.media.sfx_rockexp; + sfx2 = cgs.media.sfx_rockexpDist; + sfx2range = 1400; + mark = cgs.media.burnMarkShader; + markDuration = 60000; + radius = 64; + light = 300; + isSprite = qtrue; + duration = 1000; + lightColor[0] = 0.75; + lightColor[1] = 0.5; + lightColor[2] = 0.1; + + // Ridah, explosion sprite animation + VectorMA( origin, 16, dir, sprOrg ); + VectorScale( dir, 100, sprVel ); + + if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { // JPW NERVE + // RF, testing new explosion animation + CG_ParticleExplosion( "expblue", sprOrg, sprVel, 700, 20, 160 ); + //CG_ParticleExplosion( "twiltb", sprOrg, sprVel, 600, 9, 100 ); + //CG_ParticleExplosion( 3, sprOrg, sprVel, 900, 9, 250 ); + /* + r = 2 + rand()%3; + for (i=0; i<3; i++) { + for (j=0;j<3;j++) sprOrg[j] = origin[j] + 14*dir[j] + 14*crandom(); + CG_ParticleExplosion( 3, sprOrg, sprVel, 800+rand()%250, 9, 60+rand()%200 ); + } + */ + // Ridah, throw some debris + CG_AddDebris( origin, dir, + 280, // speed + 1400, // duration + // 15 + rand()%5 ); // count + 7 + rand() % 2 ); // count + } +// JPW NERVE + else { +// check for water/dirt spurt + if ( trap_CM_PointContents( origin, 0 ) & CONTENTS_WATER ) { + VectorCopy( origin,tmpv ); + tmpv[2] += 10000; + + trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); + CG_WaterRipple( cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 150, 1000 ); + + CG_AddDirtBulletParticles( trace.endpos, dir, + 400, // speed + 900, // duration + 15, // count + 0.5, 256,128, 0.125, "water_splash" ); + } else { + VectorCopy( origin,tmpv ); + tmpv[2] += 20; + VectorCopy( origin,tmpv2 ); + tmpv2[2] -= 20; + trap_CM_BoxTrace( &trace,tmpv,tmpv2,NULL,NULL,0,MASK_SHOT ); + if ( trace.surfaceFlags & SURF_GRASS || trace.surfaceFlags & SURF_GRAVEL ) { + CG_AddDirtBulletParticles( origin, dir, + 400, // speed + 2000, // duration + 10, // count + 0.5, 200,75, 0.25, "dirt_splash" ); // 128,64 + } + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 700, 60, 240 ); + CG_AddDebris( origin, dir, + 280, + 1400, + 7 + rand() % 2 ); + } + } + +// jpw + break; + case WP_PANZERFAUST: + sfx = cgs.media.sfx_rockexp; + case WP_ROCKET_LAUNCHER: + case VERYBIGEXPLOSION: +// mod = cgs.media.dishFlashModel; +// shader = cgs.media.rocketExplosionShader; + sfx = cgs.media.sfx_rockexp; + sfx2 = cgs.media.sfx_rockexpDist; + sfx2range = 800; + mark = cgs.media.burnMarkShader; + markDuration = 60000; + radius = 64; + light = 600; + isSprite = qtrue; + duration = 1000; + // Ridah, changed to flamethrower colors +// lightColor[0] = 1; +// lightColor[1] = 1;//0.75; +// lightColor[2] = 0.6;//0.0; + lightColor[0] = 0.75; + lightColor[1] = 0.5; + lightColor[2] = 0.1; + + // explosion sprite animation + VectorMA( origin, 24, dir, sprOrg ); + VectorScale( dir, 64, sprVel ); +// JPW NERVE + if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { + if ( weapon == VERYBIGEXPLOSION ) { + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1200, 20, 300 ); + } else { + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 40, 70 ); + } + + // NOTE: these must all have the same duration, so that we are less likely to use a wider range of images per scene + r = 2 + rand() % 3; + for ( i = 0; i < 4; i++ ) { + if ( weapon == VERYBIGEXPLOSION ) { + for ( j = 0; j < 3; j++ ) sprOrg[j] = origin[j] + 32 * dir[j] + 32 * crandom(); + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1200, 40, 160 + rand() % 120 ); + } else if ( i < 2 ) { + for ( j = 0; j < 3; j++ ) sprOrg[j] = origin[j] + 24 * dir[j] + 16 * crandom(); + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 15, 40 + rand() % 30 ); + } + } + + // Ridah, throw some debris + CG_AddDebris( origin, dir, + 120, // speed + 2000, //350, // duration + // 15 + rand()%5 ); // count + 7 + rand() % 2 ); // count + } +// JPW NERVE -- multiplayer explosions over the top due to large damage radiusesesizes + else { +// check for water/dirt spurt + if ( trap_CM_PointContents( origin, 0 ) & CONTENTS_WATER ) { +/* + VectorCopy(origin,tmpv); + tmpv[2] += 10000; + + trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); + CG_WaterRipple(cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 300, 2000); + + CG_AddDirtBulletParticles( trace.endpos, dir, + 400+random()*200, // speed + 900, // duration + 15, // count + 0.5, 512,128, 0.125, "water_splash" ); +*/ + VectorCopy( origin,tmpv ); + tmpv[2] += 10000; + + trap_CM_BoxTrace( &trace, tmpv,origin, NULL, NULL, 0, MASK_WATER ); + CG_WaterRipple( cgs.media.wakeMarkShaderAnim, trace.endpos, dir, 300, 2000 ); + + CG_AddDirtBulletParticles( trace.endpos, dir, + 400 + random() * 200, // speed + 900, // duration + 15, // count + 0.5, 512,128, 0.125, "water_splash" ); + CG_AddDirtBulletParticles( trace.endpos, dir, // flat splashy bits + 400 + random() * 600, // speed + 1400, // duration + 15, // count + 0.5, 128,512, 0.125, "water_splash" ); + } else { + VectorCopy( origin,tmpv ); + tmpv[2] += 20; + VectorCopy( origin,tmpv2 ); + tmpv2[2] -= 20; + trap_CM_BoxTrace( &trace,tmpv,tmpv2,NULL,NULL,0,MASK_SHOT ); +// I'm still not happy with this chunk but I think the particle system is jacked... not sure the best fix for it -- JPW + if ( trace.surfaceFlags & SURF_GRASS || trace.surfaceFlags & SURF_GRAVEL ) { + CG_AddDirtBulletParticles( origin, dir, + 400 + random() * 200, // speed + 3000, // duration + 10, // count + 0.5, 400,256, 0.25, "dirt_splash" ); // 128,64 + } + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1600, 20, 200 + random() * 400 ); + for ( i = 0; i < 4; i++ ) { // JPW random vector based on plane normal so explosions move away from walls/dirt/etc + for ( j = 0; j < 3; j++ ) { + sprOrg[j] = origin[j] + 50 * crandom(); + sprVel[j] = 0.35 * crandom(); + } + VectorAdd( sprVel, trace.plane.normal, sprVel ); + VectorScale( sprVel,300,sprVel ); + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1600, 40, 260 + rand() % 120 ); + } + CG_AddDebris( origin, dir, + 400 + random() * 200, // speed + rand() % 2000 + 1000, //350, // duration + // 15 + rand()%5 ); // count + 5 + rand() % 5 ); // count + + } + } +// jpw +/* + // some sparks + CG_AddSparks( origin, dir, + 200, // speed + 800, // duration + 5 + rand()%10, // count + 0.8 ); // rand scale +*/ + // done. + + break; + + case WP_CROSS: + return; + break; + + default: + case WP_FLAMETHROWER: + // no explosion at LG impact, it is added with the beam + return; + + break; + + } + // done. + + if ( sfx ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + +//----(SA) added + if ( sfx2 ) { // distant sounds for weapons with a broadcast fire sound (so you /always/ hear dynamite explosions) + if ( cg_gameType.integer == GT_SINGLE_PLAYER ) { // JPW NERVE + trap_S_StartLocalSound( sfx2, CHAN_AUTO ); + } +// JPW NERVE + else { + vec3_t porg, gorg, norm; // player/gun origin + float gdist; + +// cent = &cg_entities[ent->number]; + VectorCopy( origin, gorg ); + VectorCopy( cg.refdef.vieworg, porg ); + VectorSubtract( gorg, porg, norm ); + gdist = VectorNormalize( norm ); + if ( gdist > 1200 && gdist < 8000 ) { // 1200 is max cam shakey dist (2*600) + // use gorg as the new sound origin + VectorMA( cg.refdef.vieworg, sfx2range, norm, gorg ); // JPW NERVE non-distance falloff makes more sense; sfx2range was gdist*0.2 + // sfx2range is variable to give us minimum volume control different explosion sizes (see mortar, panzerfaust, and grenade) +// CG_Printf("d=%f\n",(gdist-1200)*3); + trap_S_StartSoundEx( gorg, ENTITYNUM_WORLD, CHAN_WEAPON, sfx2, SND_NOCUT ); + } + } + } +// jpw + + // + // create the explosion + // + if ( mod ) { + le = CG_MakeExplosion( origin, dir, + mod, shader, + duration, isSprite ); + le->light = light; + // Ridah + le->lightOverdraw = lightOverdraw; + VectorCopy( lightColor, le->lightColor ); + } + + // + // impact mark + // + if ( mark ) { + CG_ImpactMark( mark, origin, dir, random() * 360, 1,1,1,1, alphaFade, radius, qfalse, -1 ); + } +} + +/* +============== +CG_MissileHitWallSmall +============== +*/ +void CG_MissileHitWallSmall( int weapon, int clientNum, vec3_t origin, vec3_t dir ) { + qhandle_t mod; + qhandle_t mark; + qhandle_t shader; + sfxHandle_t sfx; + float radius; + float light; + vec3_t lightColor; + localEntity_t *le; +// int r; + qboolean alphaFade; + qboolean isSprite; + int duration; + // Ridah + int lightOverdraw; + vec3_t sprOrg; + vec3_t sprVel; +// int i,j; + + mark = 0; + radius = 32; + sfx = 0; + mod = 0; + shader = 0; + light = 0; + lightColor[0] = 1; + lightColor[1] = 1; + lightColor[2] = 0; + // Ridah + lightOverdraw = 0; + + // set defaults + isSprite = qfalse; + duration = 600; + + shader = cgs.media.rocketExplosionShader; // copied from RL + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + duration = 1000; + lightColor[0] = 0.75; + lightColor[1] = 0.5; + lightColor[2] = 0.1; + + // Ridah, explosion sprite animation + VectorMA( origin, 16, dir, sprOrg ); + VectorScale( dir, 64, sprVel ); + + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 600, 6, 50 ); + + // Ridah, throw some debris + CG_AddDebris( origin, dir, + 280, // speed + 1400, // duration + // 15 + rand()%5 ); // count + 7 + rand() % 2 ); // count + + if ( sfx ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + + // + // create the explosion + // + if ( mod ) { + le = CG_MakeExplosion( origin, dir, + mod, shader, + duration, isSprite ); + le->light = light; + // Ridah + le->lightOverdraw = lightOverdraw; + VectorCopy( lightColor, le->lightColor ); + } + + // + // impact mark + // + alphaFade = ( mark == cgs.media.energyMarkShader ); // plasma fades alpha, all others fade color + // CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse, 60000 ); + CG_ImpactMark( mark, origin, dir, random() * 360, 1,1,1,1, alphaFade, radius, qfalse, 0xffffffff ); + + +} + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer( centity_t *cent, int weapon, vec3_t origin, vec3_t dir, int entityNum ) { + + CG_Bleed( origin, entityNum ); + + // some weapons will make an explosion with the blood, while + // others will just make the blood + switch ( weapon ) { + case WP_GRENADE_LAUNCHER: + case WP_ROCKET_LAUNCHER: + case WP_PANZERFAUST: + case WP_VENOM_FULL: + CG_MissileHitWall( weapon, 0, origin, dir, 0 ); // JPW NERVE like the old one + break; + case WP_KNIFE: + case WP_KNIFE2: + CG_MissileHitWall( weapon, 0, origin, dir, 1 ); // JPW NERVE this one makes the hitting fleshy sound. whee + break; + default: + break; + } +} + + + +/* +============================================================================ + +VENOM GUN TRACING + +============================================================================ +*/ + +/* +================ +CG_VenomPellet +================ +*/ +static void CG_VenomPellet( vec3_t start, vec3_t end, int skipNum ) { + trace_t tr; + int sourceContentType, destContentType; + + CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); + + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( tr.endpos, 0 ); + + // FIXME: should probably move this cruft into CG_BubbleTrail + if ( sourceContentType == destContentType ) { + if ( sourceContentType & CONTENTS_WATER ) { + CG_BubbleTrail( start, tr.endpos, 1, 32 ); + } + } else if ( sourceContentType & CONTENTS_WATER ) { + trace_t trace; + + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, 1, 32 ); + } else if ( destContentType & CONTENTS_WATER ) { + trace_t trace; + + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( tr.endpos, trace.endpos, 1, 32 ); + } + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { + CG_MissileHitPlayer( &cg_entities[tr.entityNum], WP_VENOM_FULL, tr.endpos, tr.plane.normal, tr.entityNum ); + } else { + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + // SURF_NOIMPACT will not make a flame puff or a mark + return; + } + CG_MissileHitWall( WP_VENOM_FULL, 0, tr.endpos, tr.plane.normal, tr.surfaceFlags ); // (SA) modified to send missilehitwall surface parameters + } +} + + +//----(SA) all changes to venom below should be mine +#define DEFAULT_VENOM_COUNT 10 +//#define DEFAULT_VENOM_SPREAD 20 +//#define DEFAULT_VENOM_SPREAD 400 +#define DEFAULT_VENOM_SPREAD 700 + +/* +================ +CG_VenomPattern + +Perform the same traces the server did to locate the +hit splashes (FIXME: random seed isn't synced anymore) + + (SA) right now this is random like a shotgun. I want to make it more + organized so that the pattern is more of a circle (with some degree of randomness) +================ +*/ +static void CG_VenomPattern( vec3_t origin, vec3_t origin2, int otherEntNum ) { + int i; + float r, u; + vec3_t end; + vec3_t forward, right, up; + + // derive the right and up vectors from the forward vector, because + // the client won't have any other information + VectorNormalize2( origin2, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + // generate the "random" spread pattern + for ( i = 0 ; i < DEFAULT_VENOM_COUNT ; i++ ) { + r = crandom() * DEFAULT_VENOM_SPREAD; + u = crandom() * DEFAULT_VENOM_SPREAD; + VectorMA( origin, 8192, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + CG_VenomPellet( origin, end, otherEntNum ); + } +} + +/* +============== +CG_VenomFire +============== +*/ +void CG_VenomFire( entityState_t *es, qboolean fullmode ) { + vec3_t v; + int contents; + + VectorSubtract( es->origin2, es->pos.trBase, v ); + VectorNormalize( v ); + VectorScale( v, 32, v ); + VectorAdd( es->pos.trBase, v, v ); + if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) { + // ragepro can't alpha fade, so don't even bother with smoke + vec3_t up; + + contents = trap_CM_PointContents( es->pos.trBase, 0 ); + if ( !( contents & CONTENTS_WATER ) ) { + VectorSet( up, 0, 0, 32 ); + if ( fullmode ) { + CG_SmokePuff( v, up, 24, 1, 1, 1, 0.33, 1200, cg.time, 0, 0, cgs.media.shotgunSmokePuffShader ); // LEF_PUFF_DONT_SCALE + } +//----(SA) for the time being don't do the single shot smoke as it's position is funky +// else +// CG_SmokePuff( v, up, 4, 1, 1, 1, 0.33, 700, cg.time, 0, cgs.media.shotgunSmokePuffShader ); + } + } + if ( fullmode ) { + CG_VenomPattern( es->pos.trBase, es->origin2, es->otherEntityNum ); + } +} + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + +/* +=============== +CG_SpawnTracer +=============== +*/ +void CG_SpawnTracer( int sourceEnt, vec3_t pstart, vec3_t pend ) { + localEntity_t *le; + float dist; + vec3_t dir, ofs; + orientation_t or; + vec3_t start, end; + + VectorCopy( pstart, start ); + VectorCopy( pend, end ); + + // DHM - make MG42 tracers line up + if ( cg_entities[sourceEnt].currentState.eFlags & EF_MG42_ACTIVE ) { + start[2] -= 42; + } + + VectorSubtract( end, start, dir ); + dist = VectorNormalize( dir ); + + if ( dist < 2.0 * cg_tracerLength.value ) { + return; // segment isnt long enough, dont bother + + } + if ( sourceEnt < cgs.maxclients ) { + // for visual purposes, find the actual tag_weapon for this client + // and offset the start and end accordingly + if ( !( cg_entities[sourceEnt].currentState.eFlags & EF_MG42_ACTIVE ) ) { // not MG42 + if ( CG_GetWeaponTag( sourceEnt, "tag_flash", &or ) ) { + VectorSubtract( or.origin, start, ofs ); + if ( VectorLength( ofs ) < 64 ) { + VectorAdd( start, ofs, start ); + } + } + } + } + + // subtract the length of the tracer from the end point, so we dont go through the end point + VectorMA( end, -cg_tracerLength.value, dir, end ); + dist = VectorDistance( start, end ); + + le = CG_AllocLocalEntity(); + le->leType = LE_MOVING_TRACER; + le->startTime = cg.time - ( cg.frametime ? ( rand() % cg.frametime ) / 2 : 0 ); + le->endTime = le->startTime + 1000.0 * dist / cg_tracerSpeed.value; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = le->startTime; + VectorCopy( start, le->pos.trBase ); + VectorScale( dir, cg_tracerSpeed.value, le->pos.trDelta ); +} + +/* +=============== +CG_DrawTracer +=============== +*/ +void CG_DrawTracer( vec3_t start, vec3_t finish ) { + vec3_t forward, right; + polyVert_t verts[4]; + vec3_t line; + + VectorSubtract( finish, start, forward ); + + line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); + line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); + + VectorScale( cg.refdef.viewaxis[1], line[1], right ); + VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); + VectorNormalize( right ); + + VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); + verts[0].st[0] = 1; + verts[0].st[1] = 1; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz ); + verts[1].st[0] = 1; + verts[1].st[1] = 0; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); + verts[2].st[0] = 0; + verts[2].st[1] = 0; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); + verts[3].st[0] = 0; + verts[3].st[1] = 1; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); +} + +/* +=============== +CG_Tracer +=============== +*/ +void CG_Tracer( vec3_t source, vec3_t dest, int sparks ) { + float len, begin, end; + vec3_t start, finish; + vec3_t midpoint; + vec3_t forward; + + // tracer + VectorSubtract( dest, source, forward ); + len = VectorNormalize( forward ); + + // start at least a little ways from the muzzle + if ( len < 100 && !sparks ) { + return; + } + begin = 50 + random() * ( len - 60 ); + end = begin + cg_tracerLength.value; + if ( end > len ) { + end = len; + } + VectorMA( source, begin, forward, start ); + VectorMA( source, end, forward, finish ); + + CG_DrawTracer( start, finish ); + + midpoint[0] = ( start[0] + finish[0] ) * 0.5; + midpoint[1] = ( start[1] + finish[1] ) * 0.5; + midpoint[2] = ( start[2] + finish[2] ) * 0.5; + + // add the tracer sound + // trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); + +} + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { + vec3_t forward, right, up; + centity_t *cent; + int anim; + + if ( entityNum == cg.snap->ps.clientNum ) { + VectorCopy( cg.snap->ps.origin, muzzle ); + muzzle[2] += cg.snap->ps.viewheight; + AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 14, forward, muzzle ); + return qtrue; + } + + cent = &cg_entities[entityNum]; +//----(SA) removed check. is this still necessary? (this way works for ai's firing mg42) should I check for mg42? +// if ( !cent->currentValid ) { +// return qfalse; +// } +//----(SA) end + + VectorCopy( cent->currentState.pos.trBase, muzzle ); + + AngleVectors( cent->currentState.apos.trBase, forward, right, up ); + anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; +// RF, this is all broken by scripting system +// if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR || anim == LEGS_IDLE_ALT ) { +// muzzle[2] += CROUCH_VIEWHEIGHT; +// } else { + muzzle[2] += DEFAULT_VIEWHEIGHT; +// } + + VectorMA( muzzle, 14, forward, muzzle ); + + return qtrue; + +} + +void SnapVectorTowards( vec3_t v, vec3_t to ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + if ( to[i] <= v[i] ) { +// v[i] = (int)v[i]; + v[i] = floor( v[i] ); + } else { +// v[i] = (int)v[i] + 1; + v[i] = ceil( v[i] ); + } + } +} + + +/* +====================== +CG_Bullet + +Renders bullet effects. +====================== +*/ +void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum, qboolean wolfkick, int otherEntNum2, int seed ) { + trace_t trace,trace2; + int sourceContentType, destContentType; + vec3_t dir; + vec3_t start, trend; // JPW + static int lastBloodSpat; + centity_t *cent; + + cent = &cg_entities[fleshEntityNum]; + +// JPW NERVE -- don't ever shoot if we're binoced in + if ( cg_entities[sourceEntityNum].currentState.eFlags & EF_ZOOMING ) { + return; + } +// jpw + + // Arnout: snap tracers for MG42 to viewangle of client when antilag is enabled + if ( cgs.antilag && otherEntNum2 == cg.snap->ps.clientNum && cg_entities[otherEntNum2].currentState.eFlags & EF_MG42_ACTIVE ) { + vec3_t muzzle, forward, right, up; + float r, u; + trace_t tr; + + AngleVectors( cg.predictedPlayerState.viewangles, forward, right, up ); + VectorCopy( cg_entities[cg.snap->ps.viewlocked_entNum].currentState.pos.trBase, muzzle ); + VectorMA( muzzle, 16, up, muzzle ); + + r = Q_crandom( &seed ) * MG42_SPREAD_MP; + u = Q_crandom( &seed ) * MG42_SPREAD_MP; + + VectorMA( muzzle, 8192, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + CG_Trace( &tr, muzzle, NULL, NULL, end, otherEntNum2, MASK_SHOT ); + + SnapVectorTowards( tr.endpos, muzzle ); + VectorCopy( tr.endpos, end ); + } + + // if the shooter is currently valid, calc a source point and possibly + // do trail effects + if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) { + if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( end, 0 ); + + // do a complete bubble trail if necessary + if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { + CG_BubbleTrail( start, end, .5, 8 ); + } + // bubble trail from water into air + else if ( ( sourceContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, .5, 8 ); + } + // bubble trail from air into water + else if ( ( destContentType & CONTENTS_WATER ) ) { + // only add bubbles if effect is close to viewer + if ( Distance( cg.snap->ps.origin, end ) < 1024 ) { + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( end, trace.endpos, .5, 8 ); + } + } + + // if not flesh, then do a moving tracer + if ( flesh ) { + // draw a tracer + if ( !wolfkick && random() < cg_tracerChance.value ) { + CG_Tracer( start, end, 0 ); + } + } else { // (not flesh) + if ( otherEntNum2 >= 0 && otherEntNum2 != ENTITYNUM_NONE ) { + CG_SpawnTracer( otherEntNum2, start, end ); + } else { + CG_SpawnTracer( sourceEntityNum, start, end ); + } + } + } + } + + // impact splash and mark + if ( flesh ) { + vec3_t origin; + localEntity_t *le; // JPW NERVE + float rnd, tmpf; // JPW NERVE + vec3_t smokedir, tmpv, tmpv2; // JPW NERVE + int i,headshot; // JPW NERVE + + if ( fleshEntityNum < MAX_CLIENTS ) { + CG_Bleed( end, fleshEntityNum ); + } + + // JPW NERVE smoke puffs (sometimes with some blood) + VectorSubtract( end,start,smokedir ); // get a nice "through the body" vector + VectorNormalize( smokedir ); + // all this to come up with a decent center-body displacement of bullet impact point + VectorSubtract( cent->currentState.pos.trBase,end,tmpv ); + tmpv[2] = 0; + tmpf = VectorLength( tmpv ); + VectorScale( smokedir,tmpf,tmpv ); + VectorAdd( end,tmpv,origin ); + // whee, got a bullet impact point projected to center body + CG_GetOriginForTag( cent,¢->pe.headRefEnt, "tag_mouth", 0, tmpv, NULL ); + tmpv[2] += 5; + VectorSubtract( tmpv, origin, tmpv2 ); + headshot = ( VectorLength( tmpv2 ) < 10 ); + + if ( headshot && cg_blood.integer ) { + for ( i = 0; i < 5; i++ ) { + rnd = random(); + VectorScale( smokedir,25.0 + random() * 25,tmpv ); + tmpv[0] += crandom() * 25.0f; + tmpv[1] += crandom() * 25.0f; + tmpv[2] += crandom() * 25.0f; + CG_GetWindVector( tmpv2 ); + VectorScale( tmpv2,35,tmpv2 ); // was 75, before that 55 + tmpv2[2] = 0; + VectorAdd( tmpv,tmpv2,tmpv ); + le = CG_SmokePuff( origin, tmpv, + 5 + rnd * 10, // width + 1, rnd * 0.8, rnd * 0.8, 0.5, + 500 + ( rand() % 800 ), // duration was 2800+ + cg.time, + 0, + 0, + cgs.media.fleshSmokePuffShader ); + } + } else { + // puff out the front (more dust no blood) + for ( i = 0; i < 10; i++ ) { + rnd = random(); + VectorScale( smokedir,-35.0 + random() * 25,tmpv ); + tmpv[0] += crandom() * 25.0f; + tmpv[1] += crandom() * 25.0f; + tmpv[2] += crandom() * 25.0f; + CG_GetWindVector( tmpv2 ); + VectorScale( tmpv2,35,tmpv2 ); // was 75, before that 55 + tmpv2[2] = 0; + VectorAdd( tmpv,tmpv2,tmpv ); + le = CG_SmokePuff( origin, tmpv, + 5 + rnd * 10, // width + rnd * 0.3f + 0.5f, rnd * 0.3f + 0.5f, rnd * 0.3f + 0.5f, 0.125f, + 500 + ( rand() % 300 ), // duration was 2800+ + cg.time, + 0, + 0, + cgs.media.smokePuffShader ); + } + } +// jpw + + // play the bullet hit flesh sound + // HACK, if this is not us getting hit, make it quieter // JPW NERVE pulled hack, we like loud impact sounds for MP + if ( fleshEntityNum == cg.snap->ps.clientNum ) { + CG_SoundPlayIndexedScript( cgs.media.bulletHitFleshScript, NULL, fleshEntityNum ); + } else { + CG_SoundPlayIndexedScript( cgs.media.bulletHitFleshScript, cg_entities[fleshEntityNum].currentState.origin, ENTITYNUM_WORLD ); // JPW NERVE changed from ,origin, to this + } + + // if we haven't dropped a blood spat in a while, check if this is a good scenario + if ( cg_blood.integer && ( lastBloodSpat > cg.time || lastBloodSpat < cg.time - 500 ) ) { + if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { + VectorSubtract( end, start, dir ); + VectorNormalize( dir ); + VectorMA( end, 128, dir, trend ); + trap_CM_BoxTrace( &trace, end, trend, NULL, NULL, 0, MASK_SHOT & ~CONTENTS_BODY ); + + if ( trace.fraction < 1 ) { + CG_ImpactMark( cgs.media.bloodDotShaders[rand() % 5], trace.endpos, trace.plane.normal, random() * 360, + 1,1,1,1, qtrue, 15 + random() * 20, qfalse, cg_bloodTime.integer * 1000 ); + lastBloodSpat = cg.time; + } else if ( lastBloodSpat < cg.time - 1000 ) { + // drop one on the ground? + VectorCopy( end, trend ); + trend[2] -= 64; + trap_CM_BoxTrace( &trace, end, trend, NULL, NULL, 0, MASK_SHOT & ~CONTENTS_BODY ); + + if ( trace.fraction < 1 ) { + CG_ImpactMark( cgs.media.bloodDotShaders[rand() % 5], trace.endpos, trace.plane.normal, random() * 360, + 1,1,1,1, qtrue, 15 + random() * 10, qfalse, cg_bloodTime.integer * 1000 ); + lastBloodSpat = cg.time; + } + } + } + } + + } else { // (not flesh) + int fromweap; + fromweap = cg_entities[sourceEntityNum].currentState.weapon; + + if ( !fromweap || cg_entities[sourceEntityNum].currentState.eFlags & EF_MG42_ACTIVE ) { // mounted + fromweap = WP_MP40; + } + + // TODO: not sure what kind of effect were going to do + if ( wolfkick ) { + return; + } + + if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) + || cg.snap->ps.persistant[PERS_HWEAPON_USE] ) { + vec3_t start2; + VectorSubtract( end, start, dir ); + VectorNormalize( dir ); + VectorMA( end, -4, dir, start2 ); // back off a little so it doesn't start in solid + VectorMA( end, 64, dir, dir ); + trap_CM_BoxTrace( &trace, start2, dir, NULL, NULL, 0, MASK_SHOT ); + // JPW NERVE -- water check + if ( cg_gameType.integer != GT_SINGLE_PLAYER ) { + trap_CM_BoxTrace( &trace2, start2, dir, NULL, NULL, 0, CONTENTS_WATER | MASK_SHOT ); + if ( trace.fraction != trace2.fraction ) { + trap_CM_BoxTrace( &trace2, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_MissileHitWall( fromweap, 2, trace2.endpos, trace2.plane.normal, trace2.surfaceFlags ); + return; + } + } + + CG_MissileHitWall( fromweap, 1, end, normal, trace.surfaceFlags ); // smoke puff // (SA) modified to send missilehitwall surface parameters + + } + } +} + +/* +============ +CG_ClientDamage +============ +*/ +void CG_ClientDamage( int entnum, int enemynum, int id ) { + // NERVE - SMF - don't do this in multiplayer + if ( cgs.gametype != GT_SINGLE_PLAYER ) { + return; + } + + if ( id > CLDMG_MAX ) { + CG_Error( "CG_ClientDamage: unknown damage type: %i\n", id ); + } + + trap_SendClientCommand( va( "cld %i %i %i", entnum, enemynum, id ) ); +} diff --git a/src/cgame/cgame.def b/src/cgame/cgame.def new file mode 100644 index 0000000..2ee748e --- /dev/null +++ b/src/cgame/cgame.def @@ -0,0 +1,3 @@ +EXPORTS + vmMain + dllEntry diff --git a/src/cgame/cgame.vcproj b/src/cgame/cgame.vcproj new file mode 100644 index 0000000..fc83d43 --- /dev/null +++ b/src/cgame/cgame.vcproj @@ -0,0 +1,943 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cgame/tr_types.h b/src/cgame/tr_types.h new file mode 100644 index 0000000..af363cc --- /dev/null +++ b/src/cgame/tr_types.h @@ -0,0 +1,333 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + + +#define MAX_CORONAS 32 //----(SA) not really a reason to limit this other than trying to keep a reasonable count +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces +#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing + +// renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 8 // for view weapon Z crunching +#define RF_NOSHADOW 64 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting +#define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count + +#define RF_HILIGHT ( 1 << 8 ) // more than RF_MINLIGHT. For when an object is "Highlighted" (looked at/training identification/etc) +#define RF_BLINK ( 1 << 9 ) // eyes in 'blink' state + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_HYPERSPACE 4 // teleportation effect + +// Rafael +#define RDF_SKYBOXPORTAL 8 + +//----(SA) +#define RDF_UNDERWATER ( 1 << 4 ) // so the renderer knows to use underwater fog when the player is underwater +#define RDF_DRAWINGSKY ( 1 << 5 ) +#define RDF_SNOOPERVIEW ( 1 << 6 ) //----(SA) added + + +typedef struct { + vec3_t xyz; + float st[2]; + byte modulate[4]; +} polyVert_t; + +typedef struct poly_s { + qhandle_t hShader; + int numVerts; + polyVert_t *verts; +} poly_t; + +typedef enum { + RT_MODEL, + RT_POLY, + RT_SPRITE, + RT_SPLASH, // ripple effect + RT_BEAM, + RT_RAIL_CORE, + RT_RAIL_CORE_TAPER, // a modified core that creates a properly texture mapped core that's wider at one end + RT_RAIL_RINGS, + RT_LIGHTNING, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +#define ZOMBIEFX_FADEOUT_TIME 10000 + +#define REFLAG_ONLYHAND 1 // only draw hand surfaces +#define REFLAG_FORCE_LOD 8 // force a low lod +#define REFLAG_ORIENT_LOD 16 // on LOD switch, align the model to the player's camera +#define REFLAG_DEAD_LOD 32 // allow the LOD to go lower than recommended + +typedef struct { + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + vec3_t axis[3]; // rotation vectors + vec3_t torsoAxis[3]; // rotation vectors for torso section of skeletal animation + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + float origin[3]; // also used as MODEL_BEAM's "from" + int frame; // also used as MODEL_BEAM's diameter + int torsoFrame; // skeletal torso can have frame independant of legs frame + + // previous data for frame interpolation + float oldorigin[3]; // also used as MODEL_BEAM's "to" + int oldframe; + int oldTorsoFrame; + float backlerp; // 0.0 = current, 1.0 = old + float torsoBacklerp; + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers + float shaderTime; // subtracted from refdef time to control effect start times + + // extra sprite information + float radius; + float rotation; + + // Ridah + vec3_t fireRiseDir; + + // Ridah, entity fading (gibs, debris, etc) + int fadeStartTime, fadeEndTime; + + float hilightIntensity; //----(SA) added + + int reFlags; + + int entityNum; // currentState.number, so we can attach rendering effects to specific entities (Zombie) + +} refEntity_t; + +//----(SA) + +// // +// WARNING:: synch FOG_SERVER in sv_ccmds.c if you change anything // +// // +typedef enum { + FOG_NONE, // 0 + + FOG_SKY, // 1 fog values to apply to the sky when using density fog for the world (non-distance clipping fog) (only used if(glfogsettings[FOG_MAP].registered) or if(glfogsettings[FOG_MAP].registered)) + FOG_PORTALVIEW, // 2 used by the portal sky scene + FOG_HUD, // 3 used by the 3D hud scene + + // The result of these for a given frame is copied to the scene.glFog when the scene is rendered + + // the following are fogs applied to the main world scene + FOG_MAP, // 4 use fog parameter specified using the "fogvars" in the sky shader + FOG_WATER, // 5 used when underwater + FOG_SERVER, // 6 the server has set my fog (probably a target_fog) (keep synch in sv_ccmds.c !!!) + FOG_CURRENT, // 7 stores the current values when a transition starts + FOG_LAST, // 8 stores the current values when a transition starts + FOG_TARGET, // 9 the values it's transitioning to. + + FOG_CMD_SWITCHFOG, // 10 transition to the fog specified in the second parameter of R_SetFog(...) (keep synch in sv_ccmds.c !!!) + + NUM_FOGS +} glfogType_t; + + +typedef struct { + int mode; // GL_LINEAR, GL_EXP + int hint; // GL_DONT_CARE + int startTime; // in ms + int finishTime; // in ms + float color[4]; + float start; // near + float end; // far + qboolean useEndForClip; // use the 'far' value for the far clipping plane + float density; // 0.0-1.0 + qboolean registered; // has this fog been set up? + qboolean drawsky; // draw skybox + qboolean clearscreen; // clear the GL color buffer +} glfog_t; + +//----(SA) end + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + int time; // time in milliseconds for shader effects and other time dependent rendering issues + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + + + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + +//----(SA) added (needed to pass fog infos into the portal sky scene) + glfog_t glfog; +//----(SA) end + +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + TC_NONE, + TC_S3TC, + TC_EXT_COMP_S3TC +} textureCompression_t; + +typedef enum { + GLDRV_ICD, // driver is integrated with window system + // WARNING: there are tests that check for + // > GLDRV_ICD for minidriverness, so this + // should always be the lowest value in this + // enum set + GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver + GLDRV_VOODOO // driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { + GLHW_GENERIC, // where everthing works the way it should + GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is + // the hardware type then there can NOT exist a secondary + // display adapter + GLHW_RIVA128, // where you can't interpolate alpha + GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures + GLHW_PERMEDIA2 // where you don't have src*dst +} glHardwareType_t; + +typedef struct { + char renderer_string[MAX_STRING_CHARS]; + char vendor_string[MAX_STRING_CHARS]; + char version_string[MAX_STRING_CHARS]; + char extensions_string[MAX_STRING_CHARS * 4]; // TTimo - bumping, some cards have a big extension string + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + + int colorBits, depthBits, stencilBits; + + glDriverType_t driverType; + glHardwareType_t hardwareType; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + qboolean anisotropicAvailable; //----(SA) added + float maxAnisotropy; //----(SA) added + + // vendor-specific support + // NVidia + qboolean NVFogAvailable; //----(SA) added + int NVFogMode; //----(SA) added + // ATI + int ATIMaxTruformTess; // for truform support + int ATINormalMode; // for truform support + int ATIPointMode; // for truform support + + int vidWidth, vidHeight; + // aspect is the screen's physical width / height, which may be different + // than scrWidth / scrHeight if the pixels are non-square + // normal screens should be 4/3, but wide aspect monitors may be 16/9 + float windowAspect; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; + qboolean smpActive; // dual processor + + qboolean textureFilterAnisotropicAvailable; //DAJ +} glconfig_t; + + +#if !defined _WIN32 + +#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so.3.1" +// show_bug.cgi?id=524 +#define OPENGL_DRIVER_NAME "libGL.so.1" + +#else + +#define _3DFX_DRIVER_NAME "3dfxvgl" +#define OPENGL_DRIVER_NAME "opengl32" +#define WICKED3D_V5_DRIVER_NAME "gl/openglv5.dll" +#define WICKED3D_V3_DRIVER_NAME "gl/openglv3.dll" + +#endif // !defined _WIN32 + + +#endif // __TR_TYPES_H diff --git a/src/client/cl_cgame.c b/src/client/cl_cgame.c new file mode 100644 index 0000000..4fac360 --- /dev/null +++ b/src/client/cl_cgame.c @@ -0,0 +1,1347 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cl_cgame.c -- client system interaction with client game + +#include "client.h" + +#include "../game/botlib.h" + +extern botlib_export_t *botlib_export; + +extern qboolean loadCamera( int camNum, const char *name ); +extern void startCamera( int camNum, int time ); +extern qboolean getCameraInfo( int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov ); + +// RF, this is only used when running a local server +extern void SV_SendMoveSpeedsToGame( int entnum, char *text ); + +// NERVE - SMF +void Key_GetBindingBuf( int keynum, char *buf, int buflen ); +void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); +// -NERVE - SMF + +/* +==================== +CL_GetGameState +==================== +*/ +void CL_GetGameState( gameState_t *gs ) { + *gs = cl.gameState; +} + +/* +==================== +CL_GetGlconfig +==================== +*/ +void CL_GetGlconfig( glconfig_t *glconfig ) { + *glconfig = cls.glconfig; +} + + +/* +==================== +CL_GetUserCmd +==================== +*/ +qboolean CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + // cmds[cmdNumber] is the last properly generated command + + // can't return anything that we haven't created yet + if ( cmdNumber > cl.cmdNumber ) { + Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber ); + } + + // the usercmd has been overwritten in the wrapping + // buffer because it is too far out of date + if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP ) { + return qfalse; + } + + *ucmd = cl.cmds[ cmdNumber & CMD_MASK ]; + + return qtrue; +} + +int CL_GetCurrentCmdNumber( void ) { + return cl.cmdNumber; +} + + +/* +==================== +CL_GetParseEntityState +==================== +*/ +qboolean CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) { + // can't return anything that hasn't been parsed yet + if ( parseEntityNumber >= cl.parseEntitiesNum ) { + Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i", + parseEntityNumber, cl.parseEntitiesNum ); + } + + // can't return anything that has been overwritten in the circular buffer + if ( parseEntityNumber <= cl.parseEntitiesNum - MAX_PARSE_ENTITIES ) { + return qfalse; + } + + *state = cl.parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ]; + return qtrue; +} + +/* +==================== +CL_GetCurrentSnapshotNumber +==================== +*/ +void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + *snapshotNumber = cl.snap.messageNum; + *serverTime = cl.snap.serverTime; +} + +/* +==================== +CL_GetSnapshot +==================== +*/ +qboolean CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + clSnapshot_t *clSnap; + int i, count; + + if ( snapshotNumber > cl.snap.messageNum ) { + Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" ); + } + + // if the frame has fallen out of the circular buffer, we can't return it + if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP ) { + return qfalse; + } + + // if the frame is not valid, we can't return it + clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK]; + if ( !clSnap->valid ) { + return qfalse; + } + + // if the entities in the frame have fallen out of their + // circular buffer, we can't return it + if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) { + return qfalse; + } + + // write the snapshot + snapshot->snapFlags = clSnap->snapFlags; + snapshot->serverCommandSequence = clSnap->serverCommandNum; + snapshot->ping = clSnap->ping; + snapshot->serverTime = clSnap->serverTime; + memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) ); + snapshot->ps = clSnap->ps; + count = clSnap->numEntities; + if ( count > MAX_ENTITIES_IN_SNAPSHOT ) { + Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT ); + count = MAX_ENTITIES_IN_SNAPSHOT; + } + snapshot->numEntities = count; + for ( i = 0 ; i < count ; i++ ) { + snapshot->entities[i] = + cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & ( MAX_PARSE_ENTITIES - 1 ) ]; + } + + // FIXME: configstring changes and server commands!!! + + return qtrue; +} + +/* +============== +CL_SetUserCmdValue +============== +*/ +void CL_SetUserCmdValue( int userCmdValue, int holdableValue, float sensitivityScale, int mpSetup, int mpIdentClient ) { + cl.cgameUserCmdValue = userCmdValue; + cl.cgameUserHoldableValue = holdableValue; + cl.cgameSensitivity = sensitivityScale; + cl.cgameMpSetup = mpSetup; // NERVE - SMF + cl.cgameMpIdentClient = mpIdentClient; // NERVE - SMF +} + +/* +================== +CL_SetClientLerpOrigin +================== +*/ +void CL_SetClientLerpOrigin( float x, float y, float z ) { + cl.cgameClientLerpOrigin[0] = x; + cl.cgameClientLerpOrigin[1] = y; + cl.cgameClientLerpOrigin[2] = z; +} + +/* +============== +CL_AddCgameCommand +============== +*/ +void CL_AddCgameCommand( const char *cmdName ) { + Cmd_AddCommand( cmdName, NULL ); +} + +/* +============== +CL_CgameError +============== +*/ +void CL_CgameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + + +/* +===================== +CL_ConfigstringModified +===================== +*/ +void CL_ConfigstringModified( void ) { + char *old, *s; + int i, index; + char *dup; + gameState_t oldGs; + int len; + + index = atoi( Cmd_Argv( 1 ) ); + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } +// s = Cmd_Argv(2); + // get everything after "cs " + s = Cmd_ArgsFrom( 2 ); + + old = cl.gameState.stringData + cl.gameState.stringOffsets[ index ]; + if ( !strcmp( old, s ) ) { + return; // unchanged + } + + // build the new gameState_t + oldGs = cl.gameState; + + memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + + // leave the first 0 for uninitialized strings + cl.gameState.dataCount = 1; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( i == index ) { + dup = s; + } else { + dup = oldGs.stringData + oldGs.stringOffsets[ i ]; + } + if ( !dup[0] ) { + continue; // leave with the default empty string + } + + len = strlen( dup ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 ); + cl.gameState.dataCount += len + 1; + } + + if ( index == CS_SYSTEMINFO ) { + // parse serverId and other cvars + CL_SystemInfoChanged(); + } + +} + + +/* +=================== +CL_GetServerCommand + +Set up argc/argv for the given command +=================== +*/ +qboolean CL_GetServerCommand( int serverCommandNumber ) { + char *s; + char *cmd; + static char bigConfigString[BIG_INFO_STRING]; + int argc; + + // if we have irretrievably lost a reliable command, drop the connection + if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { + // when a demo record was started after the client got a whole bunch of + // reliable commands then the client never got those first reliable commands + if ( clc.demoplaying ) { + return qfalse; + } + Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); + return qfalse; + } + + if ( serverCommandNumber > clc.serverCommandSequence ) { + Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); + return qfalse; + } + + s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + clc.lastExecutedServerCommand = serverCommandNumber; + + if ( cl_showServerCommands->integer ) { // NERVE - SMF + Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); + } + +rescan: + Cmd_TokenizeString( s ); + cmd = Cmd_Argv( 0 ); + argc = Cmd_Argc(); + + if ( !strcmp( cmd, "disconnect" ) ) { + // NERVE - SMF - allow server to indicate why they were disconnected + if ( argc >= 2 ) { + Com_Error( ERR_SERVERDISCONNECT, va( "Server Disconnected - %s", Cmd_Argv( 1 ) ) ); + } else { + Com_Error( ERR_SERVERDISCONNECT,"Server disconnected\n" ); + } + } + + if ( !strcmp( cmd, "bcs0" ) ) { + Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv( 1 ), Cmd_Argv( 2 ) ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs1" ) ) { + s = Cmd_Argv( 2 ); + if ( strlen( bigConfigString ) + strlen( s ) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs2" ) ) { + s = Cmd_Argv( 2 ); + if ( strlen( bigConfigString ) + strlen( s ) + 1 >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + strcat( bigConfigString, "\"" ); + s = bigConfigString; + goto rescan; + } + + if ( !strcmp( cmd, "cs" ) ) { + CL_ConfigstringModified(); + // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() + Cmd_TokenizeString( s ); + return qtrue; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + // clear notify lines and outgoing commands before passing + // the restart to the cgame + Con_ClearNotify(); + memset( cl.cmds, 0, sizeof( cl.cmds ) ); + return qtrue; + } + + if ( !strcmp( cmd, "popup" ) ) { // direct server to client popup request, bypassing cgame +// trap_UI_Popup(Cmd_Argv(1)); +// if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { +// VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_CLIPBOARD); +// Menus_OpenByName(Cmd_Argv(1)); +// } + return qfalse; + } + + + // the clientLevelShot command is used during development + // to generate 128*128 screenshots from the intermission + // point of levels for the menu system to use + // we pass it along to the cgame to make apropriate adjustments, + // but we also clear the console and notify lines here + if ( !strcmp( cmd, "clientLevelShot" ) ) { + // don't do it if we aren't running the server locally, + // otherwise malicious remote servers could overwrite + // the existing thumbnails + if ( !com_sv_running->integer ) { + return qfalse; + } + // close the console + Con_Close(); + // take a special screenshot next frame + Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" ); + return qtrue; + } + + // we may want to put a "connect to other server" command here + + // cgame can now act on the command + return qtrue; +} + +// DHM - Nerve :: Copied from server to here +/* +==================== +CL_SetExpectedHunkUsage + + Sets com_expectedhunkusage, so the client knows how to draw the percentage bar +==================== +*/ +void CL_SetExpectedHunkUsage( const char *mapname ) { + int handle; + char *memlistfile = "hunkusage.dat"; + char *buf; + char *buftrav; + char *token; + int len; + + len = FS_FOpenFileByMode( memlistfile, &handle, FS_READ ); + if ( len >= 0 ) { // the file exists, so read it in, strip out the current entry for this map, and save it out, so we can append the new value + + buf = (char *)Z_Malloc( len + 1 ); + memset( buf, 0, len + 1 ); + + FS_Read( (void *)buf, len, handle ); + FS_FCloseFile( handle ); + + // now parse the file, filtering out the current map + buftrav = buf; + while ( ( token = COM_Parse( &buftrav ) ) && token[0] ) { + if ( !Q_strcasecmp( token, (char *)mapname ) ) { + // found a match + token = COM_Parse( &buftrav ); // read the size + if ( token && token[0] ) { + // this is the usage + Cvar_Set( "com_expectedhunkusage", token ); + Z_Free( buf ); + return; + } + } + } + + Z_Free( buf ); + } + // just set it to a negative number,so the cgame knows not to draw the percent bar + Cvar_Set( "com_expectedhunkusage", "-1" ); +} + +// dhm - nerve + +/* +==================== +CL_CM_LoadMap + +Just adds default parameters that cgame doesn't need to know about +==================== +*/ +void CL_CM_LoadMap( const char *mapname ) { + int checksum; + + // DHM - Nerve :: If we are not running the server, then set expected usage here + if ( !com_sv_running->integer ) { + CL_SetExpectedHunkUsage( mapname ); + } else + { + // TTimo + // catch here when a local server is started to avoid outdated com_errorDiagnoseIP + Cvar_Set( "com_errorDiagnoseIP", "" ); + } + + CM_LoadMap( mapname, qtrue, &checksum ); +} + +/* +==================== +CL_ShutdonwCGame + +==================== +*/ +void CL_ShutdownCGame( void ) { + cls.keyCatchers &= ~KEYCATCH_CGAME; + cls.cgameStarted = qfalse; + if ( !cgvm ) { + return; + } + VM_Call( cgvm, CG_SHUTDOWN ); + VM_Free( cgvm ); + cgvm = NULL; +} + +static int FloatAsInt( float f ) { + int temp; + + *(float *)&temp = f; + + return temp; +} + +/* +==================== +CL_CgameSystemCalls + +The cgame module is making a system call +==================== +*/ +#define VMA( x ) VM_ArgPtr( args[x] ) +#define VMF( x ) ( (float *)args )[x] +int CL_CgameSystemCalls( int *args ) { + switch ( args[0] ) { + case CG_PRINT: + Com_Printf( "%s", VMA( 1 ) ); + return 0; + case CG_ERROR: + Com_Error( ERR_DROP, "%s", VMA( 1 ) ); + return 0; + case CG_MILLISECONDS: + return Sys_Milliseconds(); + case CG_CVAR_REGISTER: + Cvar_Register( VMA( 1 ), VMA( 2 ), VMA( 3 ), args[4] ); + return 0; + case CG_CVAR_UPDATE: + Cvar_Update( VMA( 1 ) ); + return 0; + case CG_CVAR_SET: + Cvar_Set( VMA( 1 ), VMA( 2 ) ); + return 0; + case CG_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( VMA( 1 ), VMA( 2 ), args[3] ); + return 0; + case CG_ARGC: + return Cmd_Argc(); + case CG_ARGV: + Cmd_ArgvBuffer( args[1], VMA( 2 ), args[3] ); + return 0; + case CG_ARGS: + Cmd_ArgsBuffer( VMA( 1 ), args[2] ); + return 0; + case CG_FS_FOPENFILE: + return FS_FOpenFileByMode( VMA( 1 ), VMA( 2 ), args[3] ); + case CG_FS_READ: + FS_Read( VMA( 1 ), args[2], args[3] ); + return 0; + case CG_FS_WRITE: + return FS_Write( VMA( 1 ), args[2], args[3] ); + case CG_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + case CG_SENDCONSOLECOMMAND: + Cbuf_AddText( VMA( 1 ) ); + return 0; + case CG_ADDCOMMAND: + CL_AddCgameCommand( VMA( 1 ) ); + return 0; + case CG_REMOVECOMMAND: + Cmd_RemoveCommand( VMA( 1 ) ); + return 0; + case CG_SENDCLIENTCOMMAND: + CL_AddReliableCommand( VMA( 1 ) ); + return 0; + case CG_UPDATESCREEN: + // this is used during lengthy level loading, so pump message loop +// Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN! +// We can't call Com_EventLoop here, a restart will crash and this _does_ happen +// if there is a map change while we are downloading at pk3. +// ZOID + SCR_UpdateScreen(); + return 0; + case CG_CM_LOADMAP: + CL_CM_LoadMap( VMA( 1 ) ); + return 0; + case CG_CM_NUMINLINEMODELS: + return CM_NumInlineModels(); + case CG_CM_INLINEMODEL: + return CM_InlineModel( args[1] ); + case CG_CM_TEMPBOXMODEL: + return CM_TempBoxModel( VMA( 1 ), VMA( 2 ), qfalse ); + case CG_CM_TEMPCAPSULEMODEL: + return CM_TempBoxModel( VMA( 1 ), VMA( 2 ), qtrue ); + case CG_CM_POINTCONTENTS: + return CM_PointContents( VMA( 1 ), args[2] ); + case CG_CM_TRANSFORMEDPOINTCONTENTS: + return CM_TransformedPointContents( VMA( 1 ), args[2], VMA( 3 ), VMA( 4 ) ); + case CG_CM_BOXTRACE: + CM_BoxTrace( VMA( 1 ), VMA( 2 ), VMA( 3 ), VMA( 4 ), VMA( 5 ), args[6], args[7], /*int capsule*/ qfalse ); + return 0; + case CG_CM_TRANSFORMEDBOXTRACE: + CM_TransformedBoxTrace( VMA( 1 ), VMA( 2 ), VMA( 3 ), VMA( 4 ), VMA( 5 ), args[6], args[7], VMA( 8 ), VMA( 9 ), /*int capsule*/ qfalse ); + return 0; + case CG_CM_CAPSULETRACE: + CM_BoxTrace( VMA( 1 ), VMA( 2 ), VMA( 3 ), VMA( 4 ), VMA( 5 ), args[6], args[7], /*int capsule*/ qtrue ); + return 0; + case CG_CM_TRANSFORMEDCAPSULETRACE: + CM_TransformedBoxTrace( VMA( 1 ), VMA( 2 ), VMA( 3 ), VMA( 4 ), VMA( 5 ), args[6], args[7], VMA( 8 ), VMA( 9 ), /*int capsule*/ qtrue ); + return 0; + case CG_CM_MARKFRAGMENTS: + return re.MarkFragments( args[1], VMA( 2 ), VMA( 3 ), args[4], VMA( 5 ), args[6], VMA( 7 ) ); + case CG_S_STARTSOUND: + S_StartSound( VMA( 1 ), args[2], args[3], args[4] ); + return 0; +//----(SA) added + case CG_S_STARTSOUNDEX: + S_StartSoundEx( VMA( 1 ), args[2], args[3], args[4], args[5] ); + return 0; +//----(SA) end + case CG_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + case CG_S_CLEARLOOPINGSOUNDS: + S_ClearLoopingSounds(); // (SA) modified so no_pvs sounds can function + return 0; + case CG_S_ADDLOOPINGSOUND: + // FIXME MrE: handling of looping sounds changed + S_AddLoopingSound( args[1], VMA( 2 ), VMA( 3 ), args[4], args[5], args[6] ); + return 0; + case CG_S_ADDREALLOOPINGSOUND: + S_AddLoopingSound( args[1], VMA( 2 ), VMA( 3 ), args[4], args[5], args[6] ); + //S_AddRealLoopingSound( args[1], VMA(2), VMA(3), args[4], args[5] ); + return 0; + case CG_S_STOPLOOPINGSOUND: + // RF, not functional anymore, since we reverted to old looping code + //S_StopLoopingSound( args[1] ); + return 0; + case CG_S_UPDATEENTITYPOSITION: + S_UpdateEntityPosition( args[1], VMA( 2 ) ); + return 0; +// Ridah, talking animations + case CG_S_GETVOICEAMPLITUDE: + return S_GetVoiceAmplitude( args[1] ); +// done. + case CG_S_RESPATIALIZE: + S_Respatialize( args[1], VMA( 2 ), VMA( 3 ), args[4] ); + return 0; + case CG_S_REGISTERSOUND: +#ifdef DOOMSOUND ///// (SA) DOOMSOUND + return S_RegisterSound( VMA( 1 ) ); +#else + return S_RegisterSound( VMA( 1 ), qfalse ); +#endif ///// (SA) DOOMSOUND + case CG_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( VMA( 1 ), VMA( 2 ) ); + return 0; + case CG_S_STARTSTREAMINGSOUND: + S_StartStreamingSound( VMA( 1 ), VMA( 2 ), args[3], args[4], args[5] ); + return 0; + case CG_R_LOADWORLDMAP: + re.LoadWorld( VMA( 1 ) ); + return 0; + case CG_R_REGISTERMODEL: + return re.RegisterModel( VMA( 1 ) ); + case CG_R_REGISTERSKIN: + return re.RegisterSkin( VMA( 1 ) ); + + //----(SA) added + case CG_R_GETSKINMODEL: + return re.GetSkinModel( args[1], VMA( 2 ), VMA( 3 ) ); + case CG_R_GETMODELSHADER: + return re.GetShaderFromModel( args[1], args[2], args[3] ); + //----(SA) end + + case CG_R_REGISTERSHADER: + return re.RegisterShader( VMA( 1 ) ); + case CG_R_REGISTERFONT: + re.RegisterFont( VMA( 1 ), args[2], VMA( 3 ) ); + case CG_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( VMA( 1 ) ); + case CG_R_CLEARSCENE: + re.ClearScene(); + return 0; + case CG_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( VMA( 1 ) ); + return 0; + case CG_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA( 3 ) ); + return 0; + // Ridah + case CG_R_ADDPOLYSTOSCENE: + re.AddPolysToScene( args[1], args[2], VMA( 3 ), args[4] ); + return 0; + // done. +// case CG_R_LIGHTFORPOINT: +// return re.LightForPoint( VMA(1), VMA(2), VMA(3), VMA(4) ); + case CG_R_ADDLIGHTTOSCENE: + re.AddLightToScene( VMA( 1 ), VMF( 2 ), VMF( 3 ), VMF( 4 ), VMF( 5 ), args[6] ); + return 0; +// case CG_R_ADDADDITIVELIGHTTOSCENE: +// re.AddAdditiveLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); +// return 0; + case CG_R_ADDCORONATOSCENE: + re.AddCoronaToScene( VMA( 1 ), VMF( 2 ), VMF( 3 ), VMF( 4 ), VMF( 5 ), args[6], args[7] ); + return 0; + case CG_R_SETFOG: + re.SetFog( args[1], args[2], args[3], VMF( 4 ), VMF( 5 ), VMF( 6 ), VMF( 7 ) ); + return 0; + case CG_R_RENDERSCENE: + re.RenderScene( VMA( 1 ) ); + return 0; + case CG_R_SETCOLOR: + re.SetColor( VMA( 1 ) ); + return 0; + case CG_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF( 1 ), VMF( 2 ), VMF( 3 ), VMF( 4 ), VMF( 5 ), VMF( 6 ), VMF( 7 ), VMF( 8 ), args[9] ); + return 0; + case CG_R_DRAWROTATEDPIC: + re.DrawRotatedPic( VMF( 1 ), VMF( 2 ), VMF( 3 ), VMF( 4 ), VMF( 5 ), VMF( 6 ), VMF( 7 ), VMF( 8 ), args[9], VMF( 10 ) ); + return 0; + case CG_R_DRAWSTRETCHPIC_GRADIENT: + re.DrawStretchPicGradient( VMF( 1 ), VMF( 2 ), VMF( 3 ), VMF( 4 ), VMF( 5 ), VMF( 6 ), VMF( 7 ), VMF( 8 ), args[9], VMA( 10 ), args[11] ); + return 0; + case CG_R_MODELBOUNDS: + re.ModelBounds( args[1], VMA( 2 ), VMA( 3 ) ); + return 0; + case CG_R_LERPTAG: + return re.LerpTag( VMA( 1 ), VMA( 2 ), VMA( 3 ), args[4] ); + case CG_GETGLCONFIG: + CL_GetGlconfig( VMA( 1 ) ); + return 0; + case CG_GETGAMESTATE: + CL_GetGameState( VMA( 1 ) ); + return 0; + case CG_GETCURRENTSNAPSHOTNUMBER: + CL_GetCurrentSnapshotNumber( VMA( 1 ), VMA( 2 ) ); + return 0; + case CG_GETSNAPSHOT: + return CL_GetSnapshot( args[1], VMA( 2 ) ); + case CG_GETSERVERCOMMAND: + return CL_GetServerCommand( args[1] ); + case CG_GETCURRENTCMDNUMBER: + return CL_GetCurrentCmdNumber(); + case CG_GETUSERCMD: + return CL_GetUserCmd( args[1], VMA( 2 ) ); + case CG_SETUSERCMDVALUE: + CL_SetUserCmdValue( args[1], args[2], VMF( 3 ), args[4], args[5] ); + return 0; + case CG_SETCLIENTLERPORIGIN: + CL_SetClientLerpOrigin( VMF( 1 ), VMF( 2 ), VMF( 3 ) ); + return 0; + case CG_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + case CG_KEY_ISDOWN: + return Key_IsDown( args[1] ); + case CG_KEY_GETCATCHER: + return Key_GetCatcher(); + case CG_KEY_SETCATCHER: + Key_SetCatcher( args[1] ); + return 0; + case CG_KEY_GETKEY: + return Key_GetKey( VMA( 1 ) ); + + + + case CG_MEMSET: + return (int)memset( VMA( 1 ), args[2], args[3] ); + case CG_MEMCPY: + return (int)memcpy( VMA( 1 ), VMA( 2 ), args[3] ); + case CG_STRNCPY: + return (int)strncpy( VMA( 1 ), VMA( 2 ), args[3] ); + case CG_SIN: + return FloatAsInt( sin( VMF( 1 ) ) ); + case CG_COS: + return FloatAsInt( cos( VMF( 1 ) ) ); + case CG_ATAN2: + return FloatAsInt( atan2( VMF( 1 ), VMF( 2 ) ) ); + case CG_SQRT: + return FloatAsInt( sqrt( VMF( 1 ) ) ); + case CG_FLOOR: + return FloatAsInt( floor( VMF( 1 ) ) ); + case CG_CEIL: + return FloatAsInt( ceil( VMF( 1 ) ) ); + case CG_ACOS: + return FloatAsInt( Q_acos( VMF( 1 ) ) ); + + case CG_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( VMA( 1 ) ); + case CG_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( VMA( 1 ) ); + case CG_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case CG_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], VMA( 2 ) ); + case CG_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], VMA( 2 ), VMA( 3 ) ); + + case CG_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + + case CG_REAL_TIME: + return Com_RealTime( VMA( 1 ) ); + case CG_SNAPVECTOR: + Sys_SnapVector( VMA( 1 ) ); + return 0; + + case CG_SENDMOVESPEEDSTOGAME: + SV_SendMoveSpeedsToGame( args[1], VMA( 2 ) ); + return 0; + + case CG_CIN_PLAYCINEMATIC: + return CIN_PlayCinematic( VMA( 1 ), args[2], args[3], args[4], args[5], args[6] ); + + case CG_CIN_STOPCINEMATIC: + return CIN_StopCinematic( args[1] ); + + case CG_CIN_RUNCINEMATIC: + return CIN_RunCinematic( args[1] ); + + case CG_CIN_DRAWCINEMATIC: + CIN_DrawCinematic( args[1] ); + return 0; + + case CG_CIN_SETEXTENTS: + CIN_SetExtents( args[1], args[2], args[3], args[4], args[5] ); + return 0; + + case CG_R_REMAP_SHADER: + re.RemapShader( VMA( 1 ), VMA( 2 ), VMA( 3 ) ); + return 0; + + case CG_TESTPRINTINT: + Com_Printf( "%s%i\n", VMA( 1 ), args[2] ); + return 0; + case CG_TESTPRINTFLOAT: + Com_Printf( "%s%f\n", VMA( 1 ), VMF( 2 ) ); + return 0; + + case CG_LOADCAMERA: + return loadCamera( args[1], VMA( 2 ) ); + + case CG_STARTCAMERA: + startCamera( args[1], args[2] ); + return 0; + + case CG_GETCAMERAINFO: + return getCameraInfo( args[1], args[2], VMA( 3 ), VMA( 4 ), VMA( 5 ) ); + + case CG_GET_ENTITY_TOKEN: + return re.GetEntityToken( VMA( 1 ), args[2] ); + + case CG_INGAME_POPUP: + if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { + // NERVE - SMF + if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "UIMENU_WM_PICKTEAM" ) ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_WM_PICKTEAM ); + } else if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "UIMENU_WM_PICKPLAYER" ) ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_WM_PICKPLAYER ); + } else if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "UIMENU_WM_QUICKMESSAGE" ) ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_WM_QUICKMESSAGE ); + } else if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "UIMENU_WM_QUICKMESSAGEALT" ) ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_WM_QUICKMESSAGEALT ); + } else if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "UIMENU_WM_LIMBO" ) ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_WM_LIMBO ); + } else if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "UIMENU_WM_AUTOUPDATE" ) ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_WM_AUTOUPDATE ); + } + // -NERVE - SMF + else if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "hbook1" ) ) { //----(SA) + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_BOOK1 ); + } else if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "hbook2" ) ) { //----(SA) + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_BOOK2 ); + } else if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "hbook3" ) ) { //----(SA) + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_BOOK3 ); + } else { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_CLIPBOARD ); + } + } + return 0; + + // NERVE - SMF + case CG_INGAME_CLOSEPOPUP: + // if popup menu is up, then close it + if ( VMA( 1 ) && !Q_stricmp( VMA( 1 ), "UIMENU_WM_LIMBO" ) ) { + if ( VM_Call( uivm, UI_GET_ACTIVE_MENU ) == UIMENU_WM_LIMBO ) { + VM_Call( uivm, UI_KEY_EVENT, K_ESCAPE, qtrue ); + VM_Call( uivm, UI_KEY_EVENT, K_ESCAPE, qtrue ); + } + } + return 0; + + case CG_LIMBOCHAT: + if ( VMA( 1 ) ) { + CL_AddToLimboChat( VMA( 1 ) ); + } + return 0; + + case CG_KEY_GETBINDINGBUF: + Key_GetBindingBuf( args[1], VMA( 2 ), args[3] ); + return 0; + + case CG_KEY_SETBINDING: + Key_SetBinding( args[1], VMA( 2 ) ); + return 0; + + case CG_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf( args[1], VMA( 2 ), args[3] ); + return 0; + + case CG_TRANSLATE_STRING: + CL_TranslateString( VMA( 1 ), VMA( 2 ) ); + return 0; + // - NERVE - SMF + default: + Com_Error( ERR_DROP, "Bad cgame system trap: %i", args[0] ); + } + return 0; +} + +/* +==================== +CL_UpdateLevelHunkUsage + + This updates the "hunkusage.dat" file with the current map and it's hunk usage count + + This is used for level loading, so we can show a percentage bar dependant on the amount + of hunk memory allocated so far + + This will be slightly inaccurate if some settings like sound quality are changed, but these + things should only account for a small variation (hopefully) +==================== +*/ +void CL_UpdateLevelHunkUsage( void ) { + int handle; + char *memlistfile = "hunkusage.dat"; + char *buf, *outbuf; + char *buftrav, *outbuftrav; + char *token; + char outstr[256]; + int len, memusage; + + memusage = Cvar_VariableIntegerValue( "com_hunkused" ) + Cvar_VariableIntegerValue( "hunk_soundadjust" ); + + len = FS_FOpenFileByMode( memlistfile, &handle, FS_READ ); + if ( len >= 0 ) { // the file exists, so read it in, strip out the current entry for this map, and save it out, so we can append the new value + + buf = (char *)Z_Malloc( len + 1 ); + memset( buf, 0, len + 1 ); + outbuf = (char *)Z_Malloc( len + 1 ); + memset( outbuf, 0, len + 1 ); + + FS_Read( (void *)buf, len, handle ); + FS_FCloseFile( handle ); + + // now parse the file, filtering out the current map + buftrav = buf; + outbuftrav = outbuf; + outbuftrav[0] = '\0'; + while ( ( token = COM_Parse( &buftrav ) ) && token[0] ) { + if ( !Q_strcasecmp( token, cl.mapname ) ) { + // found a match + token = COM_Parse( &buftrav ); // read the size + if ( token && token[0] ) { + if ( atoi( token ) == memusage ) { // if it is the same, abort this process + Z_Free( buf ); + Z_Free( outbuf ); + return; + } + } + } else { // send it to the outbuf + Q_strcat( outbuftrav, len + 1, token ); + Q_strcat( outbuftrav, len + 1, " " ); + token = COM_Parse( &buftrav ); // read the size + if ( token && token[0] ) { + Q_strcat( outbuftrav, len + 1, token ); + Q_strcat( outbuftrav, len + 1, "\n" ); + } else { + Com_Error( ERR_DROP, "hunkusage.dat file is corrupt\n" ); + } + } + } + +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'TEXT'; + _fcreator = 'WlfS'; + } +#endif + handle = FS_FOpenFileWrite( memlistfile ); + if ( handle < 0 ) { + Com_Error( ERR_DROP, "cannot create %s\n", memlistfile ); + } + // input file is parsed, now output to the new file + len = strlen( outbuf ); + if ( FS_Write( (void *)outbuf, len, handle ) != len ) { + Com_Error( ERR_DROP, "cannot write to %s\n", memlistfile ); + } + FS_FCloseFile( handle ); + + Z_Free( buf ); + Z_Free( outbuf ); + } + // now append the current map to the current file + FS_FOpenFileByMode( memlistfile, &handle, FS_APPEND ); + if ( handle < 0 ) { + Com_Error( ERR_DROP, "cannot write to hunkusage.dat, check disk full\n" ); + } + Com_sprintf( outstr, sizeof( outstr ), "%s %i\n", cl.mapname, memusage ); + FS_Write( outstr, strlen( outstr ), handle ); + FS_FCloseFile( handle ); + + // now just open it and close it, so it gets copied to the pak dir + len = FS_FOpenFileByMode( memlistfile, &handle, FS_READ ); + if ( len >= 0 ) { + FS_FCloseFile( handle ); + } +} + +/* +==================== +CL_InitCGame + +Should only by called by CL_StartHunkUsers +==================== +*/ +void CL_InitCGame( void ) { + const char *info; + const char *mapname; + int t1, t2; + + t1 = Sys_Milliseconds(); + + // put away the console + Con_Close(); + + // find the current mapname + info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname ); + + // load the dll + cgvm = VM_Create( "cgame", CL_CgameSystemCalls, VMI_NATIVE ); + if ( !cgvm ) { + Com_Error( ERR_DROP, "VM_Create on cgame failed" ); + } + cls.state = CA_LOADING; + + // init for this gamestate + // use the lastExecutedServerCommand instead of the serverCommandSequence + // otherwise server commands sent just before a gamestate are dropped + VM_Call( cgvm, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum ); + + // we will send a usercmd this frame, which + // will cause the server to send us the first snapshot + cls.state = CA_PRIMED; + + t2 = Sys_Milliseconds(); + + Com_Printf( "CL_InitCGame: %5.2f seconds\n", ( t2 - t1 ) / 1000.0 ); + + // have the renderer touch all its images, so they are present + // on the card even if the driver does deferred loading + re.EndRegistration(); + + // make sure everything is paged in + if ( !Sys_LowPhysicalMemory() ) { + Com_TouchMemory(); + } + + // clear anything that got printed + Con_ClearNotify(); + + // Ridah, update the memory usage file + CL_UpdateLevelHunkUsage(); +} + + +/* +==================== +CL_GameCommand + +See if the current console command is claimed by the cgame +==================== +*/ +qboolean CL_GameCommand( void ) { + if ( !cgvm ) { + return qfalse; + } + + return VM_Call( cgvm, CG_CONSOLE_COMMAND ); +} + + + +/* +===================== +CL_CGameRendering +===================== +*/ +void CL_CGameRendering( stereoFrame_t stereo ) { + VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying ); + VM_Debug( 0 ); +} + + +/* +================= +CL_AdjustTimeDelta + +Adjust the clients view of server time. + +We attempt to have cl.serverTime exactly equal the server's view +of time plus the timeNudge, but with variable latencies over +the internet it will often need to drift a bit to match conditions. + +Our ideal time would be to have the adjusted time approach, but not pass, +the very latest snapshot. + +Adjustments are only made when a new snapshot arrives with a rational +latency, which keeps the adjustment process framerate independent and +prevents massive overadjustment during times of significant packet loss +or bursted delayed packets. +================= +*/ + +#define RESET_TIME 500 + +void CL_AdjustTimeDelta( void ) { + int resetTime; + int newDelta; + int deltaDelta; + + cl.newSnapshots = qfalse; + + // the delta never drifts when replaying a demo + if ( clc.demoplaying ) { + return; + } + + // if the current time is WAY off, just correct to the current value + if ( com_sv_running->integer ) { + resetTime = 100; + } else { + resetTime = RESET_TIME; + } + + newDelta = cl.snap.serverTime - cls.realtime; + deltaDelta = abs( newDelta - cl.serverTimeDelta ); + + if ( deltaDelta > RESET_TIME ) { + cl.serverTimeDelta = newDelta; + cl.oldServerTime = cl.snap.serverTime; // FIXME: is this a problem for cgame? + cl.serverTime = cl.snap.serverTime; + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + } else if ( deltaDelta > 100 ) { + // fast adjust, cut the difference in half + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1; + } else { + // slow drift adjust, only move 1 or 2 msec + + // if any of the frames between this and the previous snapshot + // had to be extrapolated, nudge our sense of time back a little + // the granularity of +1 / -2 is too high for timescale modified frametimes + if ( com_timescale->value == 0 || com_timescale->value == 1 ) { + if ( cl.extrapolatedSnapshot ) { + cl.extrapolatedSnapshot = qfalse; + cl.serverTimeDelta -= 2; + } else { + // otherwise, move our sense of time forward to minimize total latency + cl.serverTimeDelta++; + } + } + } + + if ( cl_showTimeDelta->integer ) { + Com_Printf( "%i ", cl.serverTimeDelta ); + } +} + + +/* +================== +CL_FirstSnapshot +================== +*/ +void CL_FirstSnapshot( void ) { + // ignore snapshots that don't have entities + if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) { + return; + } + cls.state = CA_ACTIVE; + + // set the timedelta so we are exactly on this first frame + cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; + cl.oldServerTime = cl.snap.serverTime; + + clc.timeDemoBaseTime = cl.snap.serverTime; + + // if this is the first frame of active play, + // execute the contents of activeAction now + // this is to allow scripting a timedemo to start right + // after loading + if ( cl_activeAction->string[0] ) { + Cbuf_AddText( cl_activeAction->string ); + Cvar_Set( "activeAction", "" ); + } + + Sys_BeginProfiling(); +} + +/* +================== +CL_SetCGameTime +================== +*/ +void CL_SetCGameTime( void ) { + // getting a valid frame message ends the connection process + if ( cls.state != CA_ACTIVE ) { + if ( cls.state != CA_PRIMED ) { + return; + } + if ( clc.demoplaying ) { + // we shouldn't get the first snapshot on the same frame + // as the gamestate, because it causes a bad time skip + if ( !clc.firstDemoFrameSkipped ) { + clc.firstDemoFrameSkipped = qtrue; + return; + } + CL_ReadDemoMessage(); + } + if ( cl.newSnapshots ) { + cl.newSnapshots = qfalse; + CL_FirstSnapshot(); + } + if ( cls.state != CA_ACTIVE ) { + return; + } + } + + // if we have gotten to this point, cl.snap is guaranteed to be valid + if ( !cl.snap.valid ) { + Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" ); + } + + // allow pause in single player + if ( sv_paused->integer && cl_paused->integer && com_sv_running->integer ) { + // paused + return; + } + + if ( cl.snap.serverTime < cl.oldFrameServerTime ) { + // Ridah, if this is a localhost, then we are probably loading a savegame + if ( !Q_stricmp( cls.servername, "localhost" ) ) { + // do nothing? + CL_FirstSnapshot(); + } else { + Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" ); + } + } + cl.oldFrameServerTime = cl.snap.serverTime; + + + // get our current view of time + + if ( clc.demoplaying && cl_freezeDemo->integer ) { + // cl_freezeDemo is used to lock a demo in place for single frame advances + + } else { + // cl_timeNudge is a user adjustable cvar that allows more + // or less latency to be added in the interest of better + // smoothness or better responsiveness. + int tn; + + tn = cl_timeNudge->integer; + if ( tn < -30 ) { + tn = -30; + } else if ( tn > 30 ) { + tn = 30; + } + + cl.serverTime = cls.realtime + cl.serverTimeDelta - tn; + + // guarantee that time will never flow backwards, even if + // serverTimeDelta made an adjustment or cl_timeNudge was changed + if ( cl.serverTime < cl.oldServerTime ) { + cl.serverTime = cl.oldServerTime; + } + cl.oldServerTime = cl.serverTime; + + // note if we are almost past the latest frame (without timeNudge), + // so we will try and adjust back a bit when the next snapshot arrives + if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) { + cl.extrapolatedSnapshot = qtrue; + } + } + + // if we have gotten new snapshots, drift serverTimeDelta + // don't do this every frame, or a period of packet loss would + // make a huge adjustment + if ( cl.newSnapshots ) { + CL_AdjustTimeDelta(); + } + + if ( !clc.demoplaying ) { + return; + } + + // if we are playing a demo back, we can just keep reading + // messages from the demo file until the cgame definately + // has valid snapshots to interpolate between + + // a timedemo will always use a deterministic set of time samples + // no matter what speed machine it is run on, + // while a normal demo may have different time samples + // each time it is played back + if ( cl_timedemo->integer ) { + if ( !clc.timeDemoStart ) { + clc.timeDemoStart = Sys_Milliseconds(); + } + clc.timeDemoFrames++; + cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50; + } + + while ( cl.serverTime >= cl.snap.serverTime ) { + // feed another messag, which should change + // the contents of cl.snap + CL_ReadDemoMessage(); + if ( cls.state != CA_ACTIVE ) { + return; // end of demo + } + } + +} + +/* +==================== +CL_GetTag +==================== +*/ +qboolean CL_GetTag( int clientNum, char *tagname, orientation_t *or ) { + if ( !cgvm ) { + return qfalse; + } + + return VM_Call( cgvm, CG_GET_TAG, clientNum, tagname, or ); +} diff --git a/src/client/cl_cin.c b/src/client/cl_cin.c new file mode 100644 index 0000000..14505fd --- /dev/null +++ b/src/client/cl_cin.c @@ -0,0 +1,1801 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: cl_cin.c + * + * desc: video and cinematic playback + * + * + * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 + * + *****************************************************************************/ + +//#define ADAPTED_TO_STREAMING_SOUND +// (SA) MISSIONPACK MERGE +// s_rawend for wolf is [] and for q3 is just a single value +// I need to ask Ryan if it's as simple as a constant index or +// if some more coding needs to be done. + + +#include "client.h" +#include "snd_local.h" +#define MAXSIZE 8 +#define MINSIZE 4 + +#define DEFAULT_CIN_WIDTH 512 +#define DEFAULT_CIN_HEIGHT 512 + +#define ROQ_QUAD 0x1000 +#define ROQ_QUAD_INFO 0x1001 +#define ROQ_CODEBOOK 0x1002 +#define ROQ_QUAD_VQ 0x1011 +#define ROQ_QUAD_JPEG 0x1012 +#define ROQ_QUAD_HANG 0x1013 +#define ROQ_PACKET 0x1030 +#define ZA_SOUND_MONO 0x1020 +#define ZA_SOUND_STEREO 0x1021 + +#define MAX_VIDEO_HANDLES 16 + +extern glconfig_t glConfig; + + +static void RoQ_init( void ); + +/****************************************************************************** +* +* Class: trFMV +* +* Description: RoQ/RnR manipulation routines +* not entirely complete for first run +* +******************************************************************************/ + +static long ROQ_YY_tab[256]; +static long ROQ_UB_tab[256]; +static long ROQ_UG_tab[256]; +static long ROQ_VG_tab[256]; +static long ROQ_VR_tab[256]; +static unsigned short vq2[256 * 16 * 4]; +static unsigned short vq4[256 * 64 * 4]; +static unsigned short vq8[256 * 256 * 4]; + + +typedef struct { + byte linbuf[DEFAULT_CIN_WIDTH * DEFAULT_CIN_HEIGHT * 4 * 2]; + byte file[65536]; + short sqrTable[256]; + + unsigned int mcomp[256]; + byte *qStatus[2][32768]; + + long oldXOff, oldYOff, oldysize, oldxsize; + + int currentHandle; +} cinematics_t; + +typedef struct { + char fileName[MAX_OSPATH]; + int CIN_WIDTH, CIN_HEIGHT; + int xpos, ypos, width, height; + qboolean looping, holdAtEnd, dirty, alterGameState, silent, shader; + fileHandle_t iFile; + e_status status; + unsigned int startTime; + unsigned int lastTime; + long tfps; + long RoQPlayed; + long ROQSize; + unsigned int RoQFrameSize; + long onQuad; + long numQuads; + long samplesPerLine; + unsigned int roq_id; + long screenDelta; + + void ( *VQ0 )( byte *status, void *qdata ); + void ( *VQ1 )( byte *status, void *qdata ); + void ( *VQNormal )( byte *status, void *qdata ); + void ( *VQBuffer )( byte *status, void *qdata ); + + long samplesPerPixel; // defaults to 2 + byte* gray; + unsigned int xsize, ysize, maxsize, minsize; + + qboolean half, smootheddouble, inMemory; + long normalBuffer0; + long roq_flags; + long roqF0; + long roqF1; + long t[2]; + long roqFPS; + int playonwalls; + byte* buf; + long drawX, drawY; +} cin_cache; + +static cinematics_t cin; +static cin_cache cinTable[MAX_VIDEO_HANDLES]; +static int currentHandle = -1; +static int CL_handle = -1; + +void CIN_CloseAllVideos( void ) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if ( cinTable[i].fileName[0] != 0 ) { + CIN_StopCinematic( i ); + } + } +} + + +static int CIN_HandleForVideo( void ) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if ( cinTable[i].fileName[0] == 0 ) { + return i; + } + } + Com_Error( ERR_DROP, "CIN_HandleForVideo: none free" ); + return -1; +} + + +extern int CL_ScaledMilliseconds( void ); + +//----------------------------------------------------------------------------- +// RllSetupTable +// +// Allocates and initializes the square table. +// +// Parameters: None +// +// Returns: Nothing +//----------------------------------------------------------------------------- +static void RllSetupTable() { + int z; + + for ( z = 0; z < 128; z++ ) { + cin.sqrTable[z] = (short)( z * z ); + cin.sqrTable[z + 128] = (short)( -cin.sqrTable[z] ); + } +} + + + +//----------------------------------------------------------------------------- +// RllDecodeMonoToMono +// +// Decode mono source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of shorts of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToMono( unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag ) { + unsigned int z; + int prev; + + if ( signedOutput ) { + prev = flag - 0x8000; + } else { + prev = flag; + } + + for ( z = 0; z < size; z++ ) { + prev = to[z] = (short)( prev + cin.sqrTable[from[z]] ); + } + return size; //*sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeMonoToStereo +// +// Decode mono source data into a stereo buffer. Output is 4 times the number +// of bytes in the input. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/4 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToStereo( unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag ) { + unsigned int z; + int prev; + + if ( signedOutput ) { + prev = flag - 0x8000; + } else { + prev = flag; + } + + for ( z = 0; z < size; z++ ) { + prev = (short)( prev + cin.sqrTable[from[z]] ); + to[z * 2 + 0] = to[z * 2 + 1] = (short)( prev ); + } + + return size; // * 2 * sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToStereo +// +// Decode stereo source data into a stereo buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/2 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToStereo( unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag ) { + unsigned int z; + unsigned char *zz = from; + int prevL, prevR; + + if ( signedOutput ) { + prevL = ( flag & 0xff00 ) - 0x8000; + prevR = ( ( flag & 0x00ff ) << 8 ) - 0x8000; + } else { + prevL = flag & 0xff00; + prevR = ( flag & 0x00ff ) << 8; + } + + for ( z = 0; z < size; z += 2 ) { + prevL = (short)( prevL + cin.sqrTable[*zz++] ); + prevR = (short)( prevR + cin.sqrTable[*zz++] ); + to[z + 0] = (short)( prevL ); + to[z + 1] = (short)( prevR ); + } + + return ( size >> 1 ); //*sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToMono +// +// Decode stereo source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToMono( unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag ) { + unsigned int z; + int prevL,prevR; + + if ( signedOutput ) { + prevL = ( flag & 0xff00 ) - 0x8000; + prevR = ( ( flag & 0x00ff ) << 8 ) - 0x8000; + } else { + prevL = flag & 0xff00; + prevR = ( flag & 0x00ff ) << 8; + } + + for ( z = 0; z < size; z += 1 ) { + prevL = prevL + cin.sqrTable[from[z * 2]]; + prevR = prevR + cin.sqrTable[from[z * 2 + 1]]; + to[z] = (short)( ( prevL + prevR ) / 2 ); + } + + return size; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void move8_32( byte *src, byte *dst, int spl ) { + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl >> 3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void move4_32( byte *src, byte *dst, int spl ) { + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl >> 3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blit8_32( byte *src, byte *dst, int spl ) { + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl >> 3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +#define movs double +static void blit4_32( byte *src, byte *dst, int spl ) { + movs *dsrc, *ddst; + int dspl; + + dsrc = (movs *)src; + ddst = (movs *)dst; + dspl = spl >> 3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blit2_32( byte *src, byte *dst, int spl ) { + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl >> 3; + + ddst[0] = dsrc[0]; + ddst[dspl] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blitVQQuad32fs( byte **status, unsigned char *data ) { + unsigned short newd, celdata, code; + unsigned int index, i; + int spl; + + newd = 0; + celdata = 0; + index = 0; + + spl = cinTable[currentHandle].samplesPerLine; + + do { + if ( !newd ) { + newd = 7; + celdata = data[0] + data[1] * 256; + data += 2; + } else { + newd--; + } + + code = ( unsigned short )( celdata & 0xc000 ); + celdata <<= 2; + + switch ( code ) { + case 0x8000: // vq code + blit8_32( (byte *)&vq8[( *data ) * 128], status[index], spl ); + data++; + index += 5; + break; + case 0xc000: // drop + index++; // skip 8x8 + for ( i = 0; i < 4; i++ ) { + if ( !newd ) { + newd = 7; + celdata = data[0] + data[1] * 256; + data += 2; + } else { + newd--; + } + + code = ( unsigned short )( celdata & 0xc000 ); celdata <<= 2; + + switch ( code ) { // code in top two bits of code + case 0x8000: // 4x4 vq code + blit4_32( (byte *)&vq4[( *data ) * 32], status[index], spl ); + data++; + break; + case 0xc000: // 2x2 vq code + blit2_32( (byte *)&vq2[( *data ) * 8], status[index], spl ); + data++; + blit2_32( (byte *)&vq2[( *data ) * 8], status[index] + 8, spl ); + data++; + blit2_32( (byte *)&vq2[( *data ) * 8], status[index] + spl * 2, spl ); + data++; + blit2_32( (byte *)&vq2[( *data ) * 8], status[index] + spl * 2 + 8, spl ); + data++; + break; + case 0x4000: // motion compensation + move4_32( status[index] + cin.mcomp[( *data )], status[index], spl ); + data++; + break; + } + index++; + } + break; + case 0x4000: // motion compensation + move8_32( status[index] + cin.mcomp[( *data )], status[index], spl ); + data++; + index += 5; + break; + case 0x0000: + index += 5; + break; + } + } while ( status[index] != NULL ); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void ROQ_GenYUVTables( void ) { + float t_ub,t_vr,t_ug,t_vg; + long i; + + t_ub = ( 1.77200f / 2.0f ) * (float)( 1 << 6 ) + 0.5f; + t_vr = ( 1.40200f / 2.0f ) * (float)( 1 << 6 ) + 0.5f; + t_ug = ( 0.34414f / 2.0f ) * (float)( 1 << 6 ) + 0.5f; + t_vg = ( 0.71414f / 2.0f ) * (float)( 1 << 6 ) + 0.5f; + for ( i = 0; i < 256; i++ ) { + float x = (float)( 2 * i - 255 ); + + ROQ_UB_tab[i] = (long)( ( t_ub * x ) + ( 1 << 5 ) ); + ROQ_VR_tab[i] = (long)( ( t_vr * x ) + ( 1 << 5 ) ); + ROQ_UG_tab[i] = (long)( ( -t_ug * x ) ); + ROQ_VG_tab[i] = (long)( ( -t_vg * x ) + ( 1 << 5 ) ); + ROQ_YY_tab[i] = (long)( ( i << 6 ) | ( i >> 2 ) ); + } +} + +#define VQ2TO4( a,b,c,d ) { \ + *c++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *c++ = a[1]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *c++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *c++ = b[1]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + a += 2; b += 2; } + +#define VQ2TO2( a,b,c,d ) { \ + *c++ = *a; \ + *d++ = *a; \ + *d++ = *a; \ + *c++ = *b; \ + *d++ = *b; \ + *d++ = *b; \ + *d++ = *a; \ + *d++ = *a; \ + *d++ = *b; \ + *d++ = *b; \ + a++; b++; } + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static unsigned short yuv_to_rgb( long y, long u, long v ) { + long r,g,b,YY = (long)( ROQ_YY_tab[( y )] ); + + r = ( YY + ROQ_VR_tab[v] ) >> 9; + g = ( YY + ROQ_UG_tab[u] + ROQ_VG_tab[v] ) >> 8; + b = ( YY + ROQ_UB_tab[u] ) >> 9; + + if ( r < 0 ) { + r = 0; + } + if ( g < 0 ) { + g = 0; + } + if ( b < 0 ) { + b = 0; + } + if ( r > 31 ) { + r = 31; + } + if ( g > 63 ) { + g = 63; + } + if ( b > 31 ) { + b = 31; + } + + return ( unsigned short )( ( r << 11 ) + ( g << 5 ) + ( b ) ); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +#if defined( MACOS_X ) + +static inline unsigned int yuv_to_rgb24( long y, long u, long v ) { + long r,g,b,YY; + + YY = (long)( ROQ_YY_tab[( y )] ); + + r = ( YY + ROQ_VR_tab[v] ) >> 6; + g = ( YY + ROQ_UG_tab[u] + ROQ_VG_tab[v] ) >> 6; + b = ( YY + ROQ_UB_tab[u] ) >> 6; + + if ( r < 0 ) { + r = 0; + } + if ( g < 0 ) { + g = 0; + } + if ( b < 0 ) { + b = 0; + } + if ( r > 255 ) { + r = 255; + } + if ( g > 255 ) { + g = 255; + } + if ( b > 255 ) { + b = 255; + } + + return ( ( r << 24 ) | ( g << 16 ) | ( b << 8 ) ) | ( 255 ); //+(255<<24)); +} + +#else +static unsigned int yuv_to_rgb24( long y, long u, long v ) { + long r,g,b,YY = (long)( ROQ_YY_tab[( y )] ); + + r = ( YY + ROQ_VR_tab[v] ) >> 6; + g = ( YY + ROQ_UG_tab[u] + ROQ_VG_tab[v] ) >> 6; + b = ( YY + ROQ_UB_tab[u] ) >> 6; + + if ( r < 0 ) { + r = 0; + } + if ( g < 0 ) { + g = 0; + } + if ( b < 0 ) { + b = 0; + } + if ( r > 255 ) { + r = 255; + } + if ( g > 255 ) { + g = 255; + } + if ( b > 255 ) { + b = 255; + } + + return LittleLong( ( r ) | ( g << 8 ) | ( b << 16 ) | ( 255 << 24 ) ); +} +#endif + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void decodeCodeBook( byte *input, unsigned short roq_flags ) { + long i, j, two, four; + unsigned short *aptr, *bptr, *cptr, *dptr; + long y0,y1,y2,y3,cr,cb; + byte *bbptr, *baptr, *bcptr, *bdptr; + unsigned int *iaptr, *ibptr, *icptr, *idptr; + + if ( !roq_flags ) { + two = four = 256; + } else { + two = roq_flags >> 8; + if ( !two ) { + two = 256; + } + four = roq_flags & 0xff; + } + + four *= 2; + + bptr = (unsigned short *)vq2; + + if ( !cinTable[currentHandle].half ) { + if ( !cinTable[currentHandle].smootheddouble ) { +// +// normal height +// + if ( cinTable[currentHandle].samplesPerPixel == 2 ) { + for ( i = 0; i < two; i++ ) { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input++; + cr = (long)*input++; + cb = (long)*input++; + *bptr++ = yuv_to_rgb( y0, cr, cb ); + *bptr++ = yuv_to_rgb( y1, cr, cb ); + *bptr++ = yuv_to_rgb( y2, cr, cb ); + *bptr++ = yuv_to_rgb( y3, cr, cb ); + } + + cptr = (unsigned short *)vq4; + dptr = (unsigned short *)vq8; + + for ( i = 0; i < four; i++ ) { + aptr = (unsigned short *)vq2 + ( *input++ ) * 4; + bptr = (unsigned short *)vq2 + ( *input++ ) * 4; + for ( j = 0; j < 2; j++ ) + VQ2TO4( aptr,bptr,cptr,dptr ); + } + } else if ( cinTable[currentHandle].samplesPerPixel == 4 ) { + ibptr = (unsigned int *)bptr; + for ( i = 0; i < two; i++ ) { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input++; + cr = (long)*input++; + cb = (long)*input++; + *ibptr++ = yuv_to_rgb24( y0, cr, cb ); + *ibptr++ = yuv_to_rgb24( y1, cr, cb ); + *ibptr++ = yuv_to_rgb24( y2, cr, cb ); + *ibptr++ = yuv_to_rgb24( y3, cr, cb ); + } + + icptr = (unsigned int *)vq4; + idptr = (unsigned int *)vq8; + + for ( i = 0; i < four; i++ ) { + iaptr = (unsigned int *)vq2 + ( *input++ ) * 4; + ibptr = (unsigned int *)vq2 + ( *input++ ) * 4; + for ( j = 0; j < 2; j++ ) + VQ2TO4( iaptr, ibptr, icptr, idptr ); + } + } else if ( cinTable[currentHandle].samplesPerPixel == 1 ) { + bbptr = (byte *)bptr; + for ( i = 0; i < two; i++ ) { + *bbptr++ = cinTable[currentHandle].gray[*input++]; + *bbptr++ = cinTable[currentHandle].gray[*input++]; + *bbptr++ = cinTable[currentHandle].gray[*input++]; + *bbptr++ = cinTable[currentHandle].gray[*input]; input += 3; + } + + bcptr = (byte *)vq4; + bdptr = (byte *)vq8; + + for ( i = 0; i < four; i++ ) { + baptr = (byte *)vq2 + ( *input++ ) * 4; + bbptr = (byte *)vq2 + ( *input++ ) * 4; + for ( j = 0; j < 2; j++ ) + VQ2TO4( baptr,bbptr,bcptr,bdptr ); + } + } + } else { +// +// double height, smoothed +// + if ( cinTable[currentHandle].samplesPerPixel == 2 ) { + for ( i = 0; i < two; i++ ) { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input++; + cr = (long)*input++; + cb = (long)*input++; + *bptr++ = yuv_to_rgb( y0, cr, cb ); + *bptr++ = yuv_to_rgb( y1, cr, cb ); + *bptr++ = yuv_to_rgb( ( ( y0 * 3 ) + y2 ) / 4, cr, cb ); + *bptr++ = yuv_to_rgb( ( ( y1 * 3 ) + y3 ) / 4, cr, cb ); + *bptr++ = yuv_to_rgb( ( y0 + ( y2 * 3 ) ) / 4, cr, cb ); + *bptr++ = yuv_to_rgb( ( y1 + ( y3 * 3 ) ) / 4, cr, cb ); + *bptr++ = yuv_to_rgb( y2, cr, cb ); + *bptr++ = yuv_to_rgb( y3, cr, cb ); + } + + cptr = (unsigned short *)vq4; + dptr = (unsigned short *)vq8; + + for ( i = 0; i < four; i++ ) { + aptr = (unsigned short *)vq2 + ( *input++ ) * 8; + bptr = (unsigned short *)vq2 + ( *input++ ) * 8; + for ( j = 0; j < 2; j++ ) { + VQ2TO4( aptr,bptr,cptr,dptr ); + VQ2TO4( aptr,bptr,cptr,dptr ); + } + } + } else if ( cinTable[currentHandle].samplesPerPixel == 4 ) { + ibptr = (unsigned int *)bptr; + for ( i = 0; i < two; i++ ) { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input++; + cr = (long)*input++; + cb = (long)*input++; + *ibptr++ = yuv_to_rgb24( y0, cr, cb ); + *ibptr++ = yuv_to_rgb24( y1, cr, cb ); + *ibptr++ = yuv_to_rgb24( ( ( y0 * 3 ) + y2 ) / 4, cr, cb ); + *ibptr++ = yuv_to_rgb24( ( ( y1 * 3 ) + y3 ) / 4, cr, cb ); + *ibptr++ = yuv_to_rgb24( ( y0 + ( y2 * 3 ) ) / 4, cr, cb ); + *ibptr++ = yuv_to_rgb24( ( y1 + ( y3 * 3 ) ) / 4, cr, cb ); + *ibptr++ = yuv_to_rgb24( y2, cr, cb ); + *ibptr++ = yuv_to_rgb24( y3, cr, cb ); + } + + icptr = (unsigned int *)vq4; + idptr = (unsigned int *)vq8; + + for ( i = 0; i < four; i++ ) { + iaptr = (unsigned int *)vq2 + ( *input++ ) * 8; + ibptr = (unsigned int *)vq2 + ( *input++ ) * 8; + for ( j = 0; j < 2; j++ ) { + VQ2TO4( iaptr, ibptr, icptr, idptr ); + VQ2TO4( iaptr, ibptr, icptr, idptr ); + } + } + } else if ( cinTable[currentHandle].samplesPerPixel == 1 ) { + bbptr = (byte *)bptr; + for ( i = 0; i < two; i++ ) { + y0 = (long)*input++; + y1 = (long)*input++; + y2 = (long)*input++; + y3 = (long)*input; input += 3; + *bbptr++ = cinTable[currentHandle].gray[y0]; + *bbptr++ = cinTable[currentHandle].gray[y1]; + *bbptr++ = cinTable[currentHandle].gray[( ( y0 * 3 ) + y2 ) / 4]; + *bbptr++ = cinTable[currentHandle].gray[( ( y1 * 3 ) + y3 ) / 4]; + *bbptr++ = cinTable[currentHandle].gray[( y0 + ( y2 * 3 ) ) / 4]; + *bbptr++ = cinTable[currentHandle].gray[( y1 + ( y3 * 3 ) ) / 4]; + *bbptr++ = cinTable[currentHandle].gray[y2]; + *bbptr++ = cinTable[currentHandle].gray[y3]; + } + + bcptr = (byte *)vq4; + bdptr = (byte *)vq8; + + for ( i = 0; i < four; i++ ) { + baptr = (byte *)vq2 + ( *input++ ) * 8; + bbptr = (byte *)vq2 + ( *input++ ) * 8; + for ( j = 0; j < 2; j++ ) { + VQ2TO4( baptr,bbptr,bcptr,bdptr ); + VQ2TO4( baptr,bbptr,bcptr,bdptr ); + } + } + } + } + } else { +// +// 1/4 screen +// + if ( cinTable[currentHandle].samplesPerPixel == 2 ) { + for ( i = 0; i < two; i++ ) { + y0 = (long)*input; input += 2; + y2 = (long)*input; input += 2; + cr = (long)*input++; + cb = (long)*input++; + *bptr++ = yuv_to_rgb( y0, cr, cb ); + *bptr++ = yuv_to_rgb( y2, cr, cb ); + } + + cptr = (unsigned short *)vq4; + dptr = (unsigned short *)vq8; + + for ( i = 0; i < four; i++ ) { + aptr = (unsigned short *)vq2 + ( *input++ ) * 2; + bptr = (unsigned short *)vq2 + ( *input++ ) * 2; + for ( j = 0; j < 2; j++ ) { + VQ2TO2( aptr,bptr,cptr,dptr ); + } + } + } else if ( cinTable[currentHandle].samplesPerPixel == 1 ) { + bbptr = (byte *)bptr; + + for ( i = 0; i < two; i++ ) { + *bbptr++ = cinTable[currentHandle].gray[*input]; input += 2; + *bbptr++ = cinTable[currentHandle].gray[*input]; input += 4; + } + + bcptr = (byte *)vq4; + bdptr = (byte *)vq8; + + for ( i = 0; i < four; i++ ) { + baptr = (byte *)vq2 + ( *input++ ) * 2; + bbptr = (byte *)vq2 + ( *input++ ) * 2; + for ( j = 0; j < 2; j++ ) { + VQ2TO2( baptr,bbptr,bcptr,bdptr ); + } + } + } else if ( cinTable[currentHandle].samplesPerPixel == 4 ) { + ibptr = (unsigned int *) bptr; + for ( i = 0; i < two; i++ ) { + y0 = (long)*input; input += 2; + y2 = (long)*input; input += 2; + cr = (long)*input++; + cb = (long)*input++; + *ibptr++ = yuv_to_rgb24( y0, cr, cb ); + *ibptr++ = yuv_to_rgb24( y2, cr, cb ); + } + + icptr = (unsigned int *)vq4; + idptr = (unsigned int *)vq8; + + for ( i = 0; i < four; i++ ) { + iaptr = (unsigned int *)vq2 + ( *input++ ) * 2; + ibptr = (unsigned int *)vq2 + ( *input++ ) * 2; + for ( j = 0; j < 2; j++ ) { + VQ2TO2( iaptr,ibptr,icptr,idptr ); + } + } + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void recurseQuad( long startX, long startY, long quadSize, long xOff, long yOff ) { + byte *scroff; + long bigx, bigy, lowx, lowy, useY; + long offset; + + offset = cinTable[currentHandle].screenDelta; + + lowx = lowy = 0; + bigx = cinTable[currentHandle].xsize; + bigy = cinTable[currentHandle].ysize; + + if ( bigx > cinTable[currentHandle].CIN_WIDTH ) { + bigx = cinTable[currentHandle].CIN_WIDTH; + } + if ( bigy > cinTable[currentHandle].CIN_HEIGHT ) { + bigy = cinTable[currentHandle].CIN_HEIGHT; + } + + if ( ( startX >= lowx ) && ( startX + quadSize ) <= ( bigx ) && ( startY + quadSize ) <= ( bigy ) && ( startY >= lowy ) && quadSize <= MAXSIZE ) { + useY = startY; + scroff = cin.linbuf + ( useY + ( ( cinTable[currentHandle].CIN_HEIGHT - bigy ) >> 1 ) + yOff ) * ( cinTable[currentHandle].samplesPerLine ) + ( ( ( startX + xOff ) ) * cinTable[currentHandle].samplesPerPixel ); + + cin.qStatus[0][cinTable[currentHandle].onQuad ] = scroff; + cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff + offset; + } + + if ( quadSize != MINSIZE ) { + quadSize >>= 1; + recurseQuad( startX, startY, quadSize, xOff, yOff ); + recurseQuad( startX + quadSize, startY, quadSize, xOff, yOff ); + recurseQuad( startX, startY + quadSize, quadSize, xOff, yOff ); + recurseQuad( startX + quadSize, startY + quadSize, quadSize, xOff, yOff ); + } +} + + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void setupQuad( long xOff, long yOff ) { + long numQuadCels, i,x,y; + byte *temp; + + if ( xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && cinTable[currentHandle].xsize == cin.oldxsize ) { + return; + } + + cin.oldXOff = xOff; + cin.oldYOff = yOff; + cin.oldysize = cinTable[currentHandle].ysize; + cin.oldxsize = cinTable[currentHandle].xsize; + + numQuadCels = ( cinTable[currentHandle].CIN_WIDTH * cinTable[currentHandle].CIN_HEIGHT ) / ( 16 ); + numQuadCels += numQuadCels / 4 + numQuadCels / 16; + numQuadCels += 64; // for overflow + + numQuadCels = ( cinTable[currentHandle].xsize * cinTable[currentHandle].ysize ) / ( 16 ); + numQuadCels += numQuadCels / 4; + numQuadCels += 64; // for overflow + + cinTable[currentHandle].onQuad = 0; + + for ( y = 0; y < (long)cinTable[currentHandle].ysize; y += 16 ) + for ( x = 0; x < (long)cinTable[currentHandle].xsize; x += 16 ) + recurseQuad( x, y, 16, xOff, yOff ); + + temp = NULL; + + for ( i = ( numQuadCels - 64 ); i < numQuadCels; i++ ) { + cin.qStatus[0][i] = temp; // eoq + cin.qStatus[1][i] = temp; // eoq + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void readQuadInfo( byte *qData ) { + if ( currentHandle < 0 ) { + return; + } + + cinTable[currentHandle].xsize = qData[0] + qData[1] * 256; + cinTable[currentHandle].ysize = qData[2] + qData[3] * 256; + cinTable[currentHandle].maxsize = qData[4] + qData[5] * 256; + cinTable[currentHandle].minsize = qData[6] + qData[7] * 256; + + cinTable[currentHandle].CIN_HEIGHT = cinTable[currentHandle].ysize; + cinTable[currentHandle].CIN_WIDTH = cinTable[currentHandle].xsize; + + cinTable[currentHandle].samplesPerLine = cinTable[currentHandle].CIN_WIDTH * cinTable[currentHandle].samplesPerPixel; + cinTable[currentHandle].screenDelta = cinTable[currentHandle].CIN_HEIGHT * cinTable[currentHandle].samplesPerLine; + + cinTable[currentHandle].half = qfalse; + cinTable[currentHandle].smootheddouble = qfalse; + + cinTable[currentHandle].VQ0 = cinTable[currentHandle].VQNormal; + cinTable[currentHandle].VQ1 = cinTable[currentHandle].VQBuffer; + + cinTable[currentHandle].t[0] = ( 0 - (unsigned int)cin.linbuf ) + (unsigned int)cin.linbuf + cinTable[currentHandle].screenDelta; + cinTable[currentHandle].t[1] = ( 0 - ( (unsigned int)cin.linbuf + cinTable[currentHandle].screenDelta ) ) + (unsigned int)cin.linbuf; + + cinTable[currentHandle].drawX = cinTable[currentHandle].CIN_WIDTH; + cinTable[currentHandle].drawY = cinTable[currentHandle].CIN_HEIGHT; + + // rage pro is very slow at 512 wide textures, voodoo can't do it at all + if ( glConfig.hardwareType == GLHW_RAGEPRO || glConfig.maxTextureSize <= 256 ) { + if ( cinTable[currentHandle].drawX > 256 ) { + cinTable[currentHandle].drawX = 256; + } + if ( cinTable[currentHandle].drawY > 256 ) { + cinTable[currentHandle].drawY = 256; + } + if ( cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256 ) { + Com_Printf( "HACK: approxmimating cinematic for Rage Pro or Voodoo\n" ); + } + } +//#ifdef __MACOS__ +// cinTable[currentHandle].drawX = 256; +// cinTable[currentHandle].drawX = 256; +//#endif +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQPrepMcomp( long xoff, long yoff ) { + long i, j, x, y, temp, temp2; + + i = cinTable[currentHandle].samplesPerLine; j = cinTable[currentHandle].samplesPerPixel; + if ( cinTable[currentHandle].xsize == ( cinTable[currentHandle].ysize * 4 ) && !cinTable[currentHandle].half ) { + j = j + j; i = i + i; + } + + for ( y = 0; y < 16; y++ ) { + temp2 = ( y + yoff - 8 ) * i; + for ( x = 0; x < 16; x++ ) { + temp = ( x + xoff - 8 ) * j; + cin.mcomp[( x * 16 ) + y] = cinTable[currentHandle].normalBuffer0 - ( temp2 + temp ); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void initRoQ() { + if ( currentHandle < 0 ) { + return; + } + + cinTable[currentHandle].VQNormal = ( void( * ) ( byte *, void * ) )blitVQQuad32fs; + cinTable[currentHandle].VQBuffer = ( void( * ) ( byte *, void * ) )blitVQQuad32fs; + cinTable[currentHandle].samplesPerPixel = 4; + ROQ_GenYUVTables(); + RllSetupTable(); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +/* +static byte* RoQFetchInterlaced( byte *source ) { + int x, *src, *dst; + + if (currentHandle < 0) return NULL; + + src = (int *)source; + dst = (int *)cinTable[currentHandle].buf2; + + for(x=0;x<256*256;x++) { + *dst = *src; + dst++; src += 2; + } + return cinTable[currentHandle].buf2; +} +*/ +static void RoQReset() { + + if ( currentHandle < 0 ) { + return; + } + + Sys_EndStreamedFile( cinTable[currentHandle].iFile ); + + // DHM - Properly close file so we don't run out of handles + FS_FCloseFile( cinTable[currentHandle].iFile ); + cinTable[currentHandle].iFile = 0; + // dhm - end + + FS_FOpenFileRead( cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue ); + // let the background thread start reading ahead + Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); + Sys_StreamedRead( cin.file, 16, 1, cinTable[currentHandle].iFile ); + RoQ_init(); + cinTable[currentHandle].status = FMV_LOOPED; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQInterrupt( void ) { + byte *framedata; + short sbuf[32768]; + int ssize; + + if ( currentHandle < 0 ) { + return; + } + + Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize + 8, 1, cinTable[currentHandle].iFile ); + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if ( cinTable[currentHandle].holdAtEnd == qfalse ) { + if ( cinTable[currentHandle].looping ) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata = cin.file; +// +// new frame is ready +// +redump: + switch ( cinTable[currentHandle].roq_id ) + { + case ROQ_QUAD_VQ: + if ( ( cinTable[currentHandle].numQuads & 1 ) ) { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ1( (byte *)cin.qStatus[1], framedata ); + cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta; + } else { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ0( (byte *)cin.qStatus[0], framedata ); + cinTable[currentHandle].buf = cin.linbuf; + } + if ( cinTable[currentHandle].numQuads == 0 ) { // first frame + Com_Memcpy( cin.linbuf + cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine * cinTable[currentHandle].ysize ); + } + cinTable[currentHandle].numQuads++; + cinTable[currentHandle].dirty = qtrue; + break; + case ROQ_CODEBOOK: + decodeCodeBook( framedata, (unsigned short)cinTable[currentHandle].roq_flags ); + break; + case ZA_SOUND_MONO: + if ( !cinTable[currentHandle].silent ) { + ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags ); + S_RawSamples( ssize, 22050, 2, 1, (byte *)sbuf, 1.0f, 1.0f, 0 ); + } + break; + case ZA_SOUND_STEREO: + if ( !cinTable[currentHandle].silent ) { + ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags ); + S_RawSamples( ssize, 22050, 2, 2, (byte *)sbuf, 1.0f, 1.0f, 0 ); + } + break; + case ROQ_QUAD_INFO: + if ( cinTable[currentHandle].numQuads == -1 ) { + readQuadInfo( framedata ); + setupQuad( 0, 0 ); + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds() * com_timescale->value; + } + if ( cinTable[currentHandle].numQuads != 1 ) { + cinTable[currentHandle].numQuads = 0; + } + break; + case ROQ_PACKET: + cinTable[currentHandle].inMemory = cinTable[currentHandle].roq_flags; + cinTable[currentHandle].RoQFrameSize = 0; // for header + break; + case ROQ_QUAD_HANG: + cinTable[currentHandle].RoQFrameSize = 0; + break; + case ROQ_QUAD_JPEG: + break; + default: + cinTable[currentHandle].status = FMV_EOF; + break; + } +// +// read in next frame data +// + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if ( cinTable[currentHandle].holdAtEnd == qfalse ) { + if ( cinTable[currentHandle].looping ) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata += cinTable[currentHandle].RoQFrameSize; + cinTable[currentHandle].roq_id = framedata[0] + framedata[1] * 256; + cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3] * 256 + framedata[4] * 65536; + cinTable[currentHandle].roq_flags = framedata[6] + framedata[7] * 256; + cinTable[currentHandle].roqF0 = (char)framedata[7]; + cinTable[currentHandle].roqF1 = (char)framedata[6]; + + if ( cinTable[currentHandle].RoQFrameSize > 65536 || cinTable[currentHandle].roq_id == 0x1084 ) { + Com_DPrintf( "roq_size>65536||roq_id==0x1084\n" ); + cinTable[currentHandle].status = FMV_EOF; + if ( cinTable[currentHandle].looping ) { + RoQReset(); + } + return; + } + if ( cinTable[currentHandle].inMemory && ( cinTable[currentHandle].status != FMV_EOF ) ) { + cinTable[currentHandle].inMemory--; framedata += 8; goto redump; + } +// +// one more frame hits the dust +// +// assert(cinTable[currentHandle].RoQFrameSize <= 65536); +// r = Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); + cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize + 8; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQ_init( void ) { + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds() * com_timescale->value; + + cinTable[currentHandle].RoQPlayed = 24; + +/* get frame rate */ + cinTable[currentHandle].roqFPS = cin.file[ 6] + cin.file[ 7] * 256; + + if ( !cinTable[currentHandle].roqFPS ) { + cinTable[currentHandle].roqFPS = 30; + } + + cinTable[currentHandle].numQuads = -1; + + cinTable[currentHandle].roq_id = cin.file[ 8] + cin.file[ 9] * 256; + cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11] * 256 + cin.file[12] * 65536; + cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15] * 256; + + if ( cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize ) { + return; + } + +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQShutdown( void ) { + const char *s; + + if ( !cinTable[currentHandle].buf ) { + return; + } + + if ( cinTable[currentHandle].status == FMV_IDLE ) { + return; + } + Com_DPrintf( "finished cinematic\n" ); + cinTable[currentHandle].status = FMV_IDLE; + + if ( cinTable[currentHandle].iFile ) { + Sys_EndStreamedFile( cinTable[currentHandle].iFile ); + FS_FCloseFile( cinTable[currentHandle].iFile ); + cinTable[currentHandle].iFile = 0; + } + + if ( cinTable[currentHandle].alterGameState ) { + cls.state = CA_DISCONNECTED; + // we can't just do a vstr nextmap, because + // if we are aborting the intro cinematic with + // a devmap command, nextmap would be valid by + // the time it was referenced + s = Cvar_VariableString( "nextmap" ); + if ( s[0] ) { + Cbuf_ExecuteText( EXEC_APPEND, va( "%s\n", s ) ); + Cvar_Set( "nextmap", "" ); + } + CL_handle = -1; + } + cinTable[currentHandle].fileName[0] = 0; + currentHandle = -1; +} + +/* +================== +SCR_StopCinematic +================== +*/ +e_status CIN_StopCinematic( int handle ) { + + if ( handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF ) { + return FMV_EOF; + } + currentHandle = handle; + + Com_DPrintf( "trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName ); + + if ( !cinTable[currentHandle].buf ) { + return FMV_EOF; + } + + if ( cinTable[currentHandle].alterGameState ) { + if ( cls.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + cinTable[currentHandle].status = FMV_EOF; + RoQShutdown(); + + return FMV_EOF; +} + +/* +================== +SCR_RunCinematic + +Fetch and decompress the pending frame +================== +*/ + + +e_status CIN_RunCinematic( int handle ) { + // bk001204 - init + int start = 0; + int thisTime = 0; + + if ( handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF ) { + return FMV_EOF; + } + + if ( cin.currentHandle != handle ) { + currentHandle = handle; + cin.currentHandle = currentHandle; + cinTable[currentHandle].status = FMV_EOF; + RoQReset(); + } + + if ( cinTable[handle].playonwalls < -1 ) { + return cinTable[handle].status; + } + + currentHandle = handle; + + if ( cinTable[currentHandle].alterGameState ) { + if ( cls.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + + if ( cinTable[currentHandle].status == FMV_IDLE ) { + return cinTable[currentHandle].status; + } + + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + thisTime = CL_ScaledMilliseconds() * com_timescale->value; + if ( cinTable[currentHandle].shader && ( abs( thisTime - cinTable[currentHandle].lastTime ) ) > 100 ) { + cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime; + } + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].tfps = ( ( ( ( CL_ScaledMilliseconds() * com_timescale->value ) - cinTable[currentHandle].startTime ) * 3 ) / 100 ); + + start = cinTable[currentHandle].startTime; + while ( ( cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads ) + && ( cinTable[currentHandle].status == FMV_PLAY ) ) + { + RoQInterrupt(); + if ( start != cinTable[currentHandle].startTime ) { + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].tfps = ( ( ( ( CL_ScaledMilliseconds() * com_timescale->value ) + - cinTable[currentHandle].startTime ) * 3 ) / 100 ); + start = cinTable[currentHandle].startTime; + } + } + + cinTable[currentHandle].lastTime = thisTime; + + if ( cinTable[currentHandle].status == FMV_LOOPED ) { + cinTable[currentHandle].status = FMV_PLAY; + } + + if ( cinTable[currentHandle].status == FMV_EOF ) { + if ( cinTable[currentHandle].looping ) { + RoQReset(); + } else { + RoQShutdown(); + } + } + + return cinTable[currentHandle].status; +} + +/* +================== +CL_PlayCinematic + +================== +*/ +int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBits ) { + unsigned short RoQID; + char name[MAX_OSPATH]; + int i; + + if ( strstr( arg, "/" ) == NULL && strstr( arg, "\\" ) == NULL ) { + Com_sprintf( name, sizeof( name ), "video/%s", arg ); + } else { + Com_sprintf( name, sizeof( name ), "%s", arg ); + } + + if ( !( systemBits & CIN_system ) ) { + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if ( !strcmp( cinTable[i].fileName, name ) ) { + return i; + } + } + } + + Com_DPrintf( "SCR_PlayCinematic( %s )\n", arg ); + + Com_Memset( &cin, 0, sizeof( cinematics_t ) ); + currentHandle = CIN_HandleForVideo(); + + cin.currentHandle = currentHandle; + + strcpy( cinTable[currentHandle].fileName, name ); + + cinTable[currentHandle].ROQSize = 0; + cinTable[currentHandle].ROQSize = FS_FOpenFileRead( cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue ); + + if ( cinTable[currentHandle].ROQSize <= 0 ) { + Com_DPrintf( "play(%s), ROQSize<=0\n", arg ); + cinTable[currentHandle].fileName[0] = 0; + return -1; + } + + CIN_SetExtents( currentHandle, x, y, w, h ); + CIN_SetLooping( currentHandle, ( systemBits & CIN_loop ) != 0 ); + + cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT; + cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH; + cinTable[currentHandle].holdAtEnd = ( systemBits & CIN_hold ) != 0; + cinTable[currentHandle].alterGameState = ( systemBits & CIN_system ) != 0; + cinTable[currentHandle].playonwalls = 1; + cinTable[currentHandle].silent = ( systemBits & CIN_silent ) != 0; + cinTable[currentHandle].shader = ( systemBits & CIN_shader ) != 0; + + if ( cinTable[currentHandle].alterGameState ) { + // close the menu + if ( uivm ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + } else { + cinTable[currentHandle].playonwalls = cl_inGameVideo->integer; + } + + initRoQ(); + + FS_Read( cin.file, 16, cinTable[currentHandle].iFile ); + + RoQID = ( unsigned short )( cin.file[0] ) + ( unsigned short )( cin.file[1] ) * 256; + if ( RoQID == 0x1084 ) { + RoQ_init(); +// FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile); + // let the background thread start reading ahead + Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); + + cinTable[currentHandle].status = FMV_PLAY; + Com_DPrintf( "trFMV::play(), playing %s\n", arg ); + + if ( cinTable[currentHandle].alterGameState ) { + cls.state = CA_CINEMATIC; + } + + Con_Close(); + +// s_rawend = s_soundtime; + + return currentHandle; + } + Com_DPrintf( "trFMV::play(), invalid RoQ ID\n" ); + + RoQShutdown(); + return -1; +} + +void CIN_SetExtents( int handle, int x, int y, int w, int h ) { + if ( handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF ) { + return; + } + cinTable[handle].xpos = x; + cinTable[handle].ypos = y; + cinTable[handle].width = w; + cinTable[handle].height = h; + cinTable[handle].dirty = qtrue; +} + +void CIN_SetLooping( int handle, qboolean loop ) { + if ( handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF ) { + return; + } + cinTable[handle].looping = loop; +} + +/* +================== +SCR_DrawCinematic + +================== +*/ +void CIN_DrawCinematic( int handle ) { + float x, y, w, h; + byte *buf; + + if ( handle < 0 || handle >= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF ) { + return; + } + + if ( !cinTable[handle].buf ) { + return; + } + + x = cinTable[handle].xpos; + y = cinTable[handle].ypos; + w = cinTable[handle].width; + h = cinTable[handle].height; + buf = cinTable[handle].buf; + SCR_AdjustFrom640( &x, &y, &w, &h ); + + if ( cinTable[handle].dirty && ( cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY ) ) { + int ix, iy, *buf2, *buf3, xm, ym, ll; + + xm = cinTable[handle].CIN_WIDTH / 256; + ym = cinTable[handle].CIN_HEIGHT / 256; + ll = 8; + if ( cinTable[handle].CIN_WIDTH == 512 ) { + ll = 9; + } + + buf3 = (int*)buf; + buf2 = Hunk_AllocateTempMemory( 256 * 256 * 4 ); + if ( xm == 2 && ym == 2 ) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for ( iy = 0; iy < 256; iy++ ) { + iiy = iy << 12; + for ( ix = 0; ix < 2048; ix += 8 ) { + for ( ic = ix; ic < ( ix + 4 ); ic++ ) { + *bc2 = ( bc3[iiy + ic] + bc3[iiy + 4 + ic] + bc3[iiy + 2048 + ic] + bc3[iiy + 2048 + 4 + ic] ) >> 2; + bc2++; + } + } + } + } else if ( xm == 2 && ym == 1 ) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for ( iy = 0; iy < 256; iy++ ) { + iiy = iy << 11; + for ( ix = 0; ix < 2048; ix += 8 ) { + for ( ic = ix; ic < ( ix + 4 ); ic++ ) { + *bc2 = ( bc3[iiy + ic] + bc3[iiy + 4 + ic] ) >> 1; + bc2++; + } + } + } + } else { + for ( iy = 0; iy < 256; iy++ ) { + for ( ix = 0; ix < 256; ix++ ) { + buf2[( iy << 8 ) + ix] = buf3[( ( iy * ym ) << ll ) + ( ix * xm )]; + } + } + } + re.DrawStretchRaw( x, y, w, h, 256, 256, (byte *)buf2, handle, qtrue ); + cinTable[handle].dirty = qfalse; + Hunk_FreeTempMemory( buf2 ); + return; + } + + re.DrawStretchRaw( x, y, w, h, cinTable[handle].drawX, cinTable[handle].drawY, buf, handle, cinTable[handle].dirty ); + cinTable[handle].dirty = qfalse; +} + +void CL_PlayCinematic_f( void ) { + char *arg, *s; + qboolean holdatend; + int bits = CIN_system; + + Com_DPrintf( "CL_PlayCinematic_f\n" ); + if ( cls.state == CA_CINEMATIC ) { + SCR_StopCinematic(); + } + + arg = Cmd_Argv( 1 ); + s = Cmd_Argv( 2 ); + + holdatend = qfalse; + if ( ( s && s[0] == '1' ) || Q_stricmp( arg,"demoend.roq" ) == 0 || Q_stricmp( arg,"end.roq" ) == 0 ) { + bits |= CIN_hold; + } + if ( s && s[0] == '2' ) { + bits |= CIN_loop; + } + + S_StopAllSounds(); + + CL_handle = CIN_PlayCinematic( arg, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, bits ); + if ( CL_handle >= 0 ) { + do { + SCR_RunCinematic(); + } while ( cinTable[currentHandle].buf == NULL && cinTable[currentHandle].status == FMV_PLAY ); // wait for first frame (load codebook and sound) + } +} + + +void SCR_DrawCinematic( void ) { + if ( CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES ) { + CIN_DrawCinematic( CL_handle ); + } +} + +void SCR_RunCinematic( void ) { + if ( CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES ) { + CIN_RunCinematic( CL_handle ); + } +} + +void SCR_StopCinematic( void ) { + if ( CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES ) { + CIN_StopCinematic( CL_handle ); + S_StopAllSounds(); + CL_handle = -1; + } +} + +void CIN_UploadCinematic( int handle ) { + if ( handle >= 0 && handle < MAX_VIDEO_HANDLES ) { + if ( !cinTable[handle].buf ) { + return; + } + if ( cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty ) { + if ( cinTable[handle].playonwalls == 0 ) { + cinTable[handle].playonwalls = -1; + } else { + if ( cinTable[handle].playonwalls == -1 ) { + cinTable[handle].playonwalls = -2; + } else { + cinTable[handle].dirty = qfalse; + } + } + } + re.UploadCinematic( 256, 256, 256, 256, cinTable[handle].buf, handle, cinTable[handle].dirty ); + if ( cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1 ) { + cinTable[handle].playonwalls--; + } + } +} diff --git a/src/client/cl_console.c b/src/client/cl_console.c new file mode 100644 index 0000000..c81211b --- /dev/null +++ b/src/client/cl_console.c @@ -0,0 +1,863 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// console.c + +#include "client.h" + + +int g_console_field_width = 78; + +#define COLNSOLE_COLOR COLOR_WHITE //COLOR_BLACK + +#define NUM_CON_TIMES 4 + +//#define CON_TEXTSIZE 32768 +#define CON_TEXTSIZE 65536 // (SA) DM want's more console... + +typedef struct { + qboolean initialized; + + short text[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line + + int linewidth; // characters across screen + int totallines; // total lines in console scrollback + + float xadjust; // for wide aspect screens + + float displayFrac; // aproaches finalFrac at scr_conspeed + float finalFrac; // 0.0 to 1.0 lines of console to display + + int vislines; // in scanlines + + int times[NUM_CON_TIMES]; // cls.realtime time the line was generated + // for transparent notify lines + vec4_t color; +} console_t; + +extern console_t con; + +console_t con; + +cvar_t *con_debug; +cvar_t *con_conspeed; +cvar_t *con_notifytime; + +// DHM - Nerve :: Must hold CTRL + SHIFT + ~ to get console +cvar_t *con_restricted; + +#define DEFAULT_CONSOLE_WIDTH 78 + +vec4_t console_color = {1.0, 1.0, 1.0, 1.0}; + + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f( void ) { + // closing a full screen console restarts the demo loop + if ( cls.state == CA_DISCONNECTED && cls.keyCatchers == KEYCATCH_CONSOLE ) { + CL_StartDemoLoop(); + return; + } + + if ( con_restricted->integer && ( !keys[K_CTRL].down || !keys[K_SHIFT].down ) ) { + return; + } + + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + + Con_ClearNotify(); + cls.keyCatchers ^= KEYCATCH_CONSOLE; +} + +/* +================ +Con_MessageMode_f +================ +*/ +void Con_MessageMode_f( void ) { + chat_playerNum = -1; + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + + cls.keyCatchers ^= KEYCATCH_MESSAGE; +} + +/* +================ +Con_MessageMode2_f +================ +*/ +void Con_MessageMode2_f( void ) { + chat_playerNum = -1; + chat_team = qtrue; + Field_Clear( &chatField ); + chatField.widthInChars = 25; + cls.keyCatchers ^= KEYCATCH_MESSAGE; +} + +/* +================ +Con_MessageMode3_f +================ +*/ +void Con_MessageMode3_f( void ) { + chat_playerNum = VM_Call( cgvm, CG_CROSSHAIR_PLAYER ); + if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { + chat_playerNum = -1; + return; + } + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + cls.keyCatchers ^= KEYCATCH_MESSAGE; +} + +/* +================ +Con_MessageMode4_f +================ +*/ +void Con_MessageMode4_f( void ) { + chat_playerNum = VM_Call( cgvm, CG_LAST_ATTACKER ); + if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { + chat_playerNum = -1; + return; + } + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + cls.keyCatchers ^= KEYCATCH_MESSAGE; +} + +// NERVE - SMF +/* +================ +Con_StartLimboMode_f +================ +*/ +void Con_StartLimboMode_f( void ) { + chat_limbo = qtrue; +} + +/* +================ +Con_StopLimboMode_f +================ +*/ +void Con_StopLimboMode_f( void ) { + chat_limbo = qfalse; +} +// -NERVE - SMF + +/* +================ +Con_Clear_f +================ +*/ +void Con_Clear_f( void ) { + int i; + + for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { + con.text[i] = ( ColorIndex( COLNSOLE_COLOR ) << 8 ) | ' '; + } + + Con_Bottom(); // go to end +} + +/* +================ +Con_Dump_f + +Save the console contents out to a file +================ +*/ +void Con_Dump_f( void ) { + int l, x, i; + short *line; + fileHandle_t f; + char buffer[1024]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: condump \n" ); + return; + } + + Com_Printf( "Dumped console text to %s.\n", Cmd_Argv( 1 ) ); + +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'TEXT'; + _fcreator = 'R*ch'; + } +#endif + f = FS_FOpenFileWrite( Cmd_Argv( 1 ) ); + if ( !f ) { + Com_Printf( "ERROR: couldn't open.\n" ); + return; + } + + // skip empty lines + for ( l = con.current - con.totallines + 1 ; l <= con.current ; l++ ) + { + line = con.text + ( l % con.totallines ) * con.linewidth; + for ( x = 0 ; x < con.linewidth ; x++ ) + if ( ( line[x] & 0xff ) != ' ' ) { + 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; + for ( i = 0; i < con.linewidth; i++ ) + buffer[i] = line[i] & 0xff; + for ( x = con.linewidth - 1 ; x >= 0 ; x-- ) + { + if ( buffer[x] == ' ' ) { + buffer[x] = 0; + } else { + break; + } + } + strcat( buffer, "\n" ); + FS_Write( buffer, strlen( buffer ), f ); + } + + FS_FCloseFile( f ); +} + + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify( void ) { + int i; + + for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { + con.times[i] = 0; + } +} + + + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize( void ) { + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + MAC_STATIC short tbuf[CON_TEXTSIZE]; + + width = ( SCREEN_WIDTH / SMALLCHAR_WIDTH ) - 2; + + if ( width == con.linewidth ) { + return; + } + + if ( width < 1 ) { // video hasn't been initialized yet + width = DEFAULT_CONSOLE_WIDTH; + con.linewidth = width; + con.totallines = CON_TEXTSIZE / con.linewidth; + for ( i = 0; i < CON_TEXTSIZE; i++ ) + + con.text[i] = ( ColorIndex( COLNSOLE_COLOR ) << 8 ) | ' '; + } else + { + oldwidth = con.linewidth; + con.linewidth = width; + oldtotallines = con.totallines; + con.totallines = CON_TEXTSIZE / con.linewidth; + numlines = oldtotallines; + + if ( con.totallines < numlines ) { + numlines = con.totallines; + } + + numchars = oldwidth; + + if ( con.linewidth < numchars ) { + numchars = con.linewidth; + } + + memcpy( tbuf, con.text, CON_TEXTSIZE * sizeof( short ) ); + for ( i = 0; i < CON_TEXTSIZE; i++ ) + + con.text[i] = ( ColorIndex( COLNSOLE_COLOR ) << 8 ) | ' '; + + + for ( i = 0 ; i < numlines ; i++ ) + { + for ( j = 0 ; j < numchars ; j++ ) + { + con.text[( con.totallines - 1 - i ) * con.linewidth + j] = + tbuf[( ( con.current - i + oldtotallines ) % + oldtotallines ) * oldwidth + j]; + } + } + + Con_ClearNotify(); + } + + con.current = con.totallines - 1; + con.display = con.current; +} + + +/* +================ +Con_Init +================ +*/ +void Con_Init( void ) { + int i; + + con_notifytime = Cvar_Get( "con_notifytime", "7", 0 ); // JPW NERVE increased per id req for obits + con_conspeed = Cvar_Get( "scr_conspeed", "3", 0 ); + con_debug = Cvar_Get( "con_debug", "0", CVAR_ARCHIVE ); //----(SA) added + con_restricted = Cvar_Get( "con_restricted", "0", CVAR_INIT ); // DHM - Nerve + + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) { + Field_Clear( &historyEditLines[i] ); + historyEditLines[i].widthInChars = g_console_field_width; + } + + Cmd_AddCommand( "toggleconsole", Con_ToggleConsole_f ); + Cmd_AddCommand( "messagemode", Con_MessageMode_f ); + Cmd_AddCommand( "messagemode2", Con_MessageMode2_f ); + Cmd_AddCommand( "messagemode3", Con_MessageMode3_f ); + Cmd_AddCommand( "messagemode4", Con_MessageMode4_f ); + Cmd_AddCommand( "startLimboMode", Con_StartLimboMode_f ); // NERVE - SMF + Cmd_AddCommand( "stopLimboMode", Con_StopLimboMode_f ); // NERVE - SMF + Cmd_AddCommand( "clear", Con_Clear_f ); + Cmd_AddCommand( "condump", Con_Dump_f ); +} + + +/* +=============== +Con_Linefeed +=============== +*/ +void Con_Linefeed( qboolean skipnotify ) { + int i; + + // mark time for transparent overlay + if ( con.current >= 0 ) { + if ( skipnotify ) { + con.times[con.current % NUM_CON_TIMES] = 0; + } else { + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } + } + + con.x = 0; + if ( con.display == con.current ) { + con.display++; + } + con.current++; + for ( i = 0; i < con.linewidth; i++ ) + con.text[( con.current % con.totallines ) * con.linewidth + i] = ( ColorIndex( COLNSOLE_COLOR ) << 8 ) | ' '; +} + +/* +================ +CL_ConsolePrint + +Handles cursor positioning, line wrapping, etc +All console printing must go through this in order to be logged to disk +If no console is visible, the text will appear at the top of the game window +================ +*/ +#if defined( _WIN32 ) && defined( NDEBUG ) +#pragma optimize( "g", off ) // SMF - msvc totally screws this function up with optimize on +#endif + +void CL_ConsolePrint( char *txt ) { + int y; + int c, l; + int color; + qboolean skipnotify = qfalse; // NERVE - SMF + int prev; // NERVE - SMF + + // NERVE - SMF - work around for text that shows up in console but not in notify + if ( !Q_strncmp( txt, "[skipnotify]", 12 ) ) { + skipnotify = qtrue; + txt += 12; + } + + // for some demos we don't want to ever show anything on the console + if ( cl_noprint && cl_noprint->integer ) { + return; + } + + if ( !con.initialized ) { + con.color[0] = + con.color[1] = + con.color[2] = + con.color[3] = 1.0f; + con.linewidth = -1; + Con_CheckResize(); + con.initialized = qtrue; + } + + color = ColorIndex( COLNSOLE_COLOR ); + + while ( ( c = *txt ) != 0 ) { + if ( Q_IsColorString( txt ) ) { + color = ColorIndex( *( txt + 1 ) ); + txt += 2; + continue; + } + + // count word length + for ( l = 0 ; l < con.linewidth ; l++ ) { + if ( txt[l] <= ' ' ) { + break; + } + + } + + // word wrap + if ( l != con.linewidth && ( con.x + l >= con.linewidth ) ) { + Con_Linefeed( skipnotify ); + + } + + txt++; + + switch ( c ) + { + case '\n': + Con_Linefeed( skipnotify ); + break; + case '\r': + con.x = 0; + break; + default: // display character and advance + y = con.current % con.totallines; + con.text[y * con.linewidth + con.x] = ( color << 8 ) | c; + con.x++; + if ( con.x >= con.linewidth ) { + + Con_Linefeed( skipnotify ); + con.x = 0; + } + break; + } + } + + // mark time for transparent overlay + if ( con.current >= 0 ) { + // NERVE - SMF + if ( skipnotify ) { + prev = con.current % NUM_CON_TIMES - 1; + if ( prev < 0 ) { + prev = NUM_CON_TIMES - 1; + } + con.times[prev] = 0; + } else { + // -NERVE - SMF + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } + } +} + +#if defined( _WIN32 ) && defined( NDEBUG ) +#pragma optimize( "g", on ) // SMF - re-enabled optimization +#endif + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +Con_DrawInput + +Draw the editline after a ] prompt +================ +*/ +void Con_DrawInput( void ) { + int y; + + if ( cls.state != CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_CONSOLE ) ) { + return; + } + + y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); + + re.SetColor( con.color ); + + SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); + + Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y, + SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue ); +} + + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify( void ) { + int x, v; + short *text; + int i; + int time; + int skip; + int currentColor; + + // NERVE - SMF - we dont want draw notify in limbo mode + if ( Cvar_VariableIntegerValue( "ui_limboMode" ) ) { + return; + } + + currentColor = 7; + re.SetColor( g_color_table[currentColor] ); + + v = 0; + for ( i = con.current - NUM_CON_TIMES + 1 ; i <= con.current ; i++ ) + { + if ( i < 0 ) { + continue; + } + time = con.times[i % NUM_CON_TIMES]; + if ( time == 0 ) { + continue; + } + time = cls.realtime - time; + if ( time > con_notifytime->value * 1000 ) { + continue; + } + text = con.text + ( i % con.totallines ) * con.linewidth; + + if ( cl.snap.ps.pm_type != PM_INTERMISSION && cls.keyCatchers & ( KEYCATCH_UI | KEYCATCH_CGAME ) ) { + continue; + } + + for ( x = 0 ; x < con.linewidth ; x++ ) { + if ( ( text[x] & 0xff ) == ' ' ) { + continue; + } + if ( ( ( text[x] >> 8 ) & 7 ) != currentColor ) { + currentColor = ( text[x] >> 8 ) & 7; + re.SetColor( g_color_table[currentColor] ); + } + SCR_DrawSmallChar( cl_conXOffset->integer + con.xadjust + ( x + 1 ) * SMALLCHAR_WIDTH, v, text[x] & 0xff ); + } + + v += SMALLCHAR_HEIGHT; + } + + re.SetColor( NULL ); + + if ( cls.keyCatchers & ( KEYCATCH_UI | KEYCATCH_CGAME ) ) { + return; + } + + // draw the chat line + if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { + if ( chat_team ) { + char buf[128]; + CL_TranslateString( "say_team:", buf ); + SCR_DrawBigString( 8, v, buf, 1.0f ); + skip = strlen( buf ) + 2; + } else + { + char buf[128]; + CL_TranslateString( "say:", buf ); + SCR_DrawBigString( 8, v, buf, 1.0f ); + skip = strlen( buf ) + 1; + } + + Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, v, + SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue ); + + v += BIGCHAR_HEIGHT; + } + +} + +/* +================ +Con_DrawSolidConsole + +Draws the console with the solid background +================ +*/ + +void Con_DrawSolidConsole( float frac ) { + int i, x, y; + int rows; + short *text; + int row; + int lines; + int currentColor; + vec4_t color; + + lines = cls.glconfig.vidHeight * frac; + if ( lines <= 0 ) { + return; + } + + if ( lines > cls.glconfig.vidHeight ) { + lines = cls.glconfig.vidHeight; + } + + // on wide screens, we will center the text + con.xadjust = 0; + SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); + + // draw the background + y = frac * SCREEN_HEIGHT - 2; + if ( y < 1 ) { + y = 0; + } else { + SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader ); + + // NERVE - SMF - merged from WolfSP + if ( frac >= 0.5f ) { + color[0] = color[1] = color[2] = frac * 2.0f; + color[3] = 1.0f; + re.SetColor( color ); + + // draw the logo + SCR_DrawPic( 192, 70, 256, 128, cls.consoleShader2 ); + re.SetColor( NULL ); + } + // -NERVE - SMF + } + + color[0] = 0; + color[1] = 0; + color[2] = 0; +// color[3] = 1; + color[3] = 0.6f; + SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color ); + + + // draw the version number + + re.SetColor( g_color_table[ColorIndex( COLNSOLE_COLOR )] ); + + i = strlen( Q3_VERSION ); + + for ( x = 0 ; x < i ; x++ ) { + + SCR_DrawSmallChar( cls.glconfig.vidWidth - ( i - x ) * SMALLCHAR_WIDTH, + + ( lines - ( SMALLCHAR_HEIGHT + SMALLCHAR_HEIGHT / 2 ) ), Q3_VERSION[x] ); + + } + + + // draw the text + con.vislines = lines; + rows = ( lines - SMALLCHAR_WIDTH ) / SMALLCHAR_WIDTH; // rows of text to draw + + y = lines - ( SMALLCHAR_HEIGHT * 3 ); + + // draw from the bottom up + if ( con.display != con.current ) { + // draw arrows to show the buffer is backscrolled + re.SetColor( g_color_table[ColorIndex( COLOR_WHITE )] ); + for ( x = 0 ; x < con.linewidth ; x += 4 ) + SCR_DrawSmallChar( con.xadjust + ( x + 1 ) * SMALLCHAR_WIDTH, y, '^' ); + y -= SMALLCHAR_HEIGHT; + rows--; + } + + row = con.display; + + if ( con.x == 0 ) { + row--; + } + + currentColor = 7; + re.SetColor( g_color_table[currentColor] ); + + for ( i = 0 ; i < rows ; i++, y -= SMALLCHAR_HEIGHT, row-- ) + { + if ( row < 0 ) { + break; + } + if ( con.current - row >= con.totallines ) { + // past scrollback wrap point + continue; + } + + text = con.text + ( row % con.totallines ) * con.linewidth; + + for ( x = 0 ; x < con.linewidth ; x++ ) { + if ( ( text[x] & 0xff ) == ' ' ) { + continue; + } + + if ( ( ( text[x] >> 8 ) & 7 ) != currentColor ) { + currentColor = ( text[x] >> 8 ) & 7; + re.SetColor( g_color_table[currentColor] ); + } + SCR_DrawSmallChar( con.xadjust + ( x + 1 ) * SMALLCHAR_WIDTH, y, text[x] & 0xff ); + } + } + + // draw the input prompt, user text, and cursor if desired + Con_DrawInput(); + + re.SetColor( NULL ); +} + + + +/* +================== +Con_DrawConsole +================== +*/ +void Con_DrawConsole( void ) { + // check for console width changes from a vid mode change + Con_CheckResize(); + + // if disconnected, render console full screen + if ( cls.state == CA_DISCONNECTED ) { + if ( !( cls.keyCatchers & ( KEYCATCH_UI | KEYCATCH_CGAME ) ) ) { + Con_DrawSolidConsole( 1.0 ); + return; + } + } + + if ( con.displayFrac ) { + Con_DrawSolidConsole( con.displayFrac ); + } else { + // draw notify lines + if ( cls.state == CA_ACTIVE ) { + Con_DrawNotify(); + } + } +} + +//================================================================ + +/* +================== +Con_RunConsole + +Scroll it up or down +================== +*/ +void Con_RunConsole( void ) { + // decide on the destination height of the console + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { + con.finalFrac = 0.5; // half screen + } else { + con.finalFrac = 0; // none visible + + } + // scroll towards the destination height + if ( con.finalFrac < con.displayFrac ) { + con.displayFrac -= con_conspeed->value * cls.realFrametime * 0.001; + if ( con.finalFrac > con.displayFrac ) { + con.displayFrac = con.finalFrac; + } + + } else if ( con.finalFrac > con.displayFrac ) { + con.displayFrac += con_conspeed->value * cls.realFrametime * 0.001; + if ( con.finalFrac < con.displayFrac ) { + con.displayFrac = con.finalFrac; + } + } + +} + + +void Con_PageUp( void ) { + con.display -= 2; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_PageDown( void ) { + con.display += 2; + if ( con.display > con.current ) { + con.display = con.current; + } +} + +void Con_Top( void ) { + con.display = con.totallines; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_Bottom( void ) { + con.display = con.current; +} + + +void Con_Close( void ) { + if ( !com_cl_running->integer ) { + return; + } + Field_Clear( &g_consoleField ); + Con_ClearNotify(); + cls.keyCatchers &= ~KEYCATCH_CONSOLE; + con.finalFrac = 0; // none visible + con.displayFrac = 0; +} diff --git a/src/client/cl_input.c b/src/client/cl_input.c new file mode 100644 index 0000000..bd36617 --- /dev/null +++ b/src/client/cl_input.c @@ -0,0 +1,1065 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cl.input.c -- builds an intended movement command to send to the server + +#include "client.h" + +unsigned frame_msec; +int old_com_frameTime; + +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as argv(1) so it can be matched up with the release. + +argv(2) will be set to the time the event happened, which allows exact +control even at low framerates when the down and up events may both get qued +at the same time. + +=============================================================================== +*/ + +static kbutton_t kb[NUM_BUTTONS]; + +void IN_MLookDown( void ) { + kb[KB_MLOOK].active = qtrue; +} + +void IN_MLookUp( void ) { + kb[KB_MLOOK].active = qfalse; + if ( !cl_freelook->integer ) { + IN_CenterView(); + } +} + +void IN_KeyDown( kbutton_t *b ) { + int k; + char *c; + + c = Cmd_Argv( 1 ); + if ( c[0] ) { + k = atoi( c ); + } else { + k = -1; // typed manually at the console for continuous down + } + + if ( k == b->down[0] || k == b->down[1] ) { + return; // repeating key + } + + if ( !b->down[0] ) { + b->down[0] = k; + } else if ( !b->down[1] ) { + b->down[1] = k; + } else { + Com_Printf( "Three keys down for a button!\n" ); + return; + } + + if ( b->active ) { + return; // still down + } + + // save timestamp for partial frame summing + c = Cmd_Argv( 2 ); + b->downtime = atoi( c ); + + b->active = qtrue; + b->wasPressed = qtrue; +} + +void IN_KeyUp( kbutton_t *b ) { + int k; + char *c; + unsigned uptime; + + c = Cmd_Argv( 1 ); + if ( c[0] ) { + k = atoi( c ); + } else { + // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->active = qfalse; + return; + } + + if ( b->down[0] == k ) { + b->down[0] = 0; + } else if ( b->down[1] == k ) { + b->down[1] = 0; + } else { + return; // key up without coresponding down (menu pass through) + } + if ( b->down[0] || b->down[1] ) { + return; // some other key is still holding it down + } + + b->active = qfalse; + + // save timestamp for partial frame summing + c = Cmd_Argv( 2 ); + uptime = atoi( c ); + if ( uptime ) { + b->msec += uptime - b->downtime; + } else { + b->msec += frame_msec / 2; + } + + b->active = qfalse; +} + + + +/* +=============== +CL_KeyState + +Returns the fraction of the frame that the key was down +=============== +*/ +float CL_KeyState( kbutton_t *key ) { + float val; + int msec; + + msec = key->msec; + key->msec = 0; + + if ( key->active ) { + // still down + if ( !key->downtime ) { + msec = com_frameTime; + } else { + msec += com_frameTime - key->downtime; + } + key->downtime = com_frameTime; + } + +#if 0 + if ( msec ) { + Com_Printf( "%i ", msec ); + } +#endif + + val = (float)msec / frame_msec; + if ( val < 0 ) { + val = 0; + } + if ( val > 1 ) { + val = 1; + } + + return val; +} + + + +void IN_UpDown( void ) {IN_KeyDown( &kb[KB_UP] );} +void IN_UpUp( void ) {IN_KeyUp( &kb[KB_UP] );} +void IN_DownDown( void ) {IN_KeyDown( &kb[KB_DOWN] );} +void IN_DownUp( void ) {IN_KeyUp( &kb[KB_DOWN] );} +void IN_LeftDown( void ) {IN_KeyDown( &kb[KB_LEFT] );} +void IN_LeftUp( void ) {IN_KeyUp( &kb[KB_LEFT] );} +void IN_RightDown( void ) {IN_KeyDown( &kb[KB_RIGHT] );} +void IN_RightUp( void ) {IN_KeyUp( &kb[KB_RIGHT] );} +void IN_ForwardDown( void ) {IN_KeyDown( &kb[KB_FORWARD] );} +void IN_ForwardUp( void ) {IN_KeyUp( &kb[KB_FORWARD] );} +void IN_BackDown( void ) {IN_KeyDown( &kb[KB_BACK] );} +void IN_BackUp( void ) {IN_KeyUp( &kb[KB_BACK] );} +void IN_LookupDown( void ) {IN_KeyDown( &kb[KB_LOOKUP] );} +void IN_LookupUp( void ) {IN_KeyUp( &kb[KB_LOOKUP] );} +void IN_LookdownDown( void ) {IN_KeyDown( &kb[KB_LOOKDOWN] );} +void IN_LookdownUp( void ) {IN_KeyUp( &kb[KB_LOOKDOWN] );} +void IN_MoveleftDown( void ) {IN_KeyDown( &kb[KB_MOVELEFT] );} +void IN_MoveleftUp( void ) {IN_KeyUp( &kb[KB_MOVELEFT] );} +void IN_MoverightDown( void ) {IN_KeyDown( &kb[KB_MOVERIGHT] );} +void IN_MoverightUp( void ) {IN_KeyUp( &kb[KB_MOVERIGHT] );} + +void IN_SpeedDown( void ) {IN_KeyDown( &kb[KB_SPEED] );} +void IN_SpeedUp( void ) {IN_KeyUp( &kb[KB_SPEED] );} +void IN_StrafeDown( void ) {IN_KeyDown( &kb[KB_STRAFE] );} +void IN_StrafeUp( void ) {IN_KeyUp( &kb[KB_STRAFE] );} + +void IN_Button0Down( void ) {IN_KeyDown( &kb[KB_BUTTONS0] );} +void IN_Button0Up( void ) {IN_KeyUp( &kb[KB_BUTTONS0] );} +void IN_Button1Down( void ) {IN_KeyDown( &kb[KB_BUTTONS1] );} +void IN_Button1Up( void ) {IN_KeyUp( &kb[KB_BUTTONS1] );} +void IN_UseItemDown( void ) {IN_KeyDown( &kb[KB_BUTTONS2] );} +void IN_UseItemUp( void ) {IN_KeyUp( &kb[KB_BUTTONS2] );} +void IN_Button3Down( void ) {IN_KeyDown( &kb[KB_BUTTONS3] );} +void IN_Button3Up( void ) {IN_KeyUp( &kb[KB_BUTTONS3] );} +void IN_Button4Down( void ) {IN_KeyDown( &kb[KB_BUTTONS4] );} +void IN_Button4Up( void ) {IN_KeyUp( &kb[KB_BUTTONS4] );} +// void IN_Button5Down(void) {IN_KeyDown(&kb[KB_BUTTONS5]);} +// void IN_Button5Up(void) {IN_KeyUp(&kb[KB_BUTTONS5]);} + +// void IN_Button6Down(void) {IN_KeyDown(&kb[KB_BUTTONS6]);} +// void IN_Button6Up(void) {IN_KeyUp(&kb[KB_BUTTONS6]);} + +// Rafael activate +void IN_ActivateDown( void ) {IN_KeyDown( &kb[KB_BUTTONS6] );} +void IN_ActivateUp( void ) {IN_KeyUp( &kb[KB_BUTTONS6] );} +// done. + +// Rafael Kick +void IN_KickDown( void ) {IN_KeyDown( &kb[KB_KICK] );} +void IN_KickUp( void ) {IN_KeyUp( &kb[KB_KICK] );} +// done. + +void IN_SprintDown( void ) {IN_KeyDown( &kb[KB_BUTTONS5] );} +void IN_SprintUp( void ) {IN_KeyUp( &kb[KB_BUTTONS5] );} + + +// wbuttons (wolf buttons) +void IN_Wbutton0Down( void ) { IN_KeyDown( &kb[KB_WBUTTONS0] ); } //----(SA) secondary fire button +void IN_Wbutton0Up( void ) { IN_KeyUp( &kb[KB_WBUTTONS0] ); } +void IN_ZoomDown( void ) { IN_KeyDown( &kb[KB_WBUTTONS1] ); } //----(SA) zoom key +void IN_ZoomUp( void ) { IN_KeyUp( &kb[KB_WBUTTONS1] ); } +void IN_QuickGrenDown( void ) { IN_KeyDown( &kb[KB_WBUTTONS2] ); } //----(SA) "Quickgrenade" +void IN_QuickGrenUp( void ) { IN_KeyUp( &kb[KB_WBUTTONS2] ); } +void IN_ReloadDown( void ) { IN_KeyDown( &kb[KB_WBUTTONS3] ); } //----(SA) manual weapon re-load +void IN_ReloadUp( void ) { IN_KeyUp( &kb[KB_WBUTTONS3] ); } +void IN_LeanLeftDown( void ) { IN_KeyDown( &kb[KB_WBUTTONS4] ); } //----(SA) lean left +void IN_LeanLeftUp( void ) { IN_KeyUp( &kb[KB_WBUTTONS4] ); } +void IN_LeanRightDown( void ) { IN_KeyDown( &kb[KB_WBUTTONS5] ); } //----(SA) lean right +void IN_LeanRightUp( void ) { IN_KeyUp( &kb[KB_WBUTTONS5] ); } + +// JPW NERVE +void IN_MP_DropWeaponDown( void ) {IN_KeyDown( &kb[KB_WBUTTONS6] );} +void IN_MP_DropWeaponUp( void ) {IN_KeyUp( &kb[KB_WBUTTONS6] );} +// jpw + +// unused +void IN_Wbutton7Down( void ) { IN_KeyDown( &kb[KB_WBUTTONS7] ); } +void IN_Wbutton7Up( void ) { IN_KeyUp( &kb[KB_WBUTTONS7] ); } + + + + +void IN_ButtonDown( void ) { + IN_KeyDown( &kb[KB_BUTTONS1] ); +} +void IN_ButtonUp( void ) { + IN_KeyUp( &kb[KB_BUTTONS1] ); +} + +void IN_CenterView( void ) { + qboolean ok = qtrue; + if ( cgvm ) { + ok = VM_Call( cgvm, CG_CHECKCENTERVIEW ); + } + if ( ok ) { + cl.viewangles[PITCH] = -SHORT2ANGLE( cl.snap.ps.delta_angles[PITCH] ); + } +} + +void IN_Notebook( void ) { + //if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { + //VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NOTEBOOK); // startup notebook + //} +} + +void IN_Help( void ) { + if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_HELP ); // startup help system + } +} + + +//========================================================================== + +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_sidespeed; + +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; + +cvar_t *cl_run; + +cvar_t *cl_anglespeedkey; + +cvar_t *cl_recoilPitch; + +cvar_t *cl_bypassMouseInput; // NERVE - SMF + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles( void ) { + float speed; + + if ( kb[KB_SPEED].active ) { + speed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + speed = 0.001 * cls.frametime; + } + + if ( !kb[KB_STRAFE].active ) { + cl.viewangles[YAW] -= speed * cl_yawspeed->value * CL_KeyState( &kb[KB_RIGHT] ); + cl.viewangles[YAW] += speed * cl_yawspeed->value * CL_KeyState( &kb[KB_LEFT] ); + } + + cl.viewangles[PITCH] -= speed * cl_pitchspeed->value * CL_KeyState( &kb[KB_LOOKUP] ); + cl.viewangles[PITCH] += speed * cl_pitchspeed->value * CL_KeyState( &kb[KB_LOOKDOWN] ); +} + +/* +================ +CL_KeyMove + +Sets the usercmd_t based on key states +================ +*/ +void CL_KeyMove( usercmd_t *cmd ) { + int movespeed; + int forward, side, up; + // Rafael Kick + int kick; + // done + + // + // adjust for speed key / running + // the walking flag is to keep animations consistant + // even during acceleration and develeration + // + if ( kb[KB_SPEED].active ^ cl_run->integer ) { + movespeed = 127; + cmd->buttons &= ~BUTTON_WALKING; + } else { + cmd->buttons |= BUTTON_WALKING; + movespeed = 64; + } + + forward = 0; + side = 0; + up = 0; + if ( kb[KB_STRAFE].active ) { + side += movespeed * CL_KeyState( &kb[KB_RIGHT] ); + side -= movespeed * CL_KeyState( &kb[KB_LEFT] ); + } + + side += movespeed * CL_KeyState( &kb[KB_MOVERIGHT] ); + side -= movespeed * CL_KeyState( &kb[KB_MOVELEFT] ); + +//----(SA) added + if ( cmd->buttons & BUTTON_ACTIVATE ) { + if ( side > 0 ) { + cmd->wbuttons |= WBUTTON_LEANRIGHT; + } else if ( side < 0 ) { + cmd->wbuttons |= WBUTTON_LEANLEFT; + } + + side = 0; // disallow the strafe when holding 'activate' + } +//----(SA) end + + up += movespeed * CL_KeyState( &kb[KB_UP] ); + up -= movespeed * CL_KeyState( &kb[KB_DOWN] ); + + forward += movespeed * CL_KeyState( &kb[KB_FORWARD] ); + forward -= movespeed * CL_KeyState( &kb[KB_BACK] ); + + // Rafael Kick + kick = CL_KeyState( &kb[KB_KICK] ); + // done + + if ( !( cl.snap.ps.persistant[PERS_HWEAPON_USE] ) ) { + cmd->forwardmove = ClampChar( forward ); + cmd->rightmove = ClampChar( side ); + cmd->upmove = ClampChar( up ); + + // Rafael - Kick + cmd->wolfkick = ClampChar( kick ); + // done + + } +} + +/* +================= +CL_MouseEvent +================= +*/ +void CL_MouseEvent( int dx, int dy, int time ) { + if ( cls.keyCatchers & KEYCATCH_UI ) { + + // NERVE - SMF - if we just want to pass it along to game + if ( cl_bypassMouseInput->integer == 1 ) { + cl.mouseDx[cl.mouseIndex] += dx; + cl.mouseDy[cl.mouseIndex] += dy; + } else { + VM_Call( uivm, UI_MOUSE_EVENT, dx, dy ); + } + + } else if ( cls.keyCatchers & KEYCATCH_CGAME ) { + VM_Call( cgvm, CG_MOUSE_EVENT, dx, dy ); + } else { + cl.mouseDx[cl.mouseIndex] += dx; + cl.mouseDy[cl.mouseIndex] += dy; + } +} + +/* +================= +CL_JoystickEvent + +Joystick values stay set until changed +================= +*/ +void CL_JoystickEvent( int axis, int value, int time ) { + if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) { + Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis ); + } + cl.joystickAxis[axis] = value; +} + +/* +================= +CL_JoystickMove +================= +*/ +void CL_JoystickMove( usercmd_t *cmd ) { + int movespeed; + float anglespeed; + + if ( kb[KB_SPEED].active ^ cl_run->integer ) { + movespeed = 2; + } else { + movespeed = 1; + cmd->buttons |= BUTTON_WALKING; + } + + if ( kb[KB_SPEED].active ) { + anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + anglespeed = 0.001 * cls.frametime; + } + +#ifdef __MACOS__ + cmd->rightmove = ClampChar( cmd->rightmove + cl.joystickAxis[AXIS_SIDE] ); +#else + if ( !kb[KB_STRAFE].active ) { + cl.viewangles[YAW] += anglespeed * cl_yawspeed->value * cl.joystickAxis[AXIS_SIDE]; + } else { + cmd->rightmove = ClampChar( cmd->rightmove + cl.joystickAxis[AXIS_SIDE] ); + } +#endif + if ( kb[KB_MLOOK].active ) { + cl.viewangles[PITCH] += anglespeed * cl_pitchspeed->value * cl.joystickAxis[AXIS_FORWARD]; + } else { + cmd->forwardmove = ClampChar( cmd->forwardmove + cl.joystickAxis[AXIS_FORWARD] ); + } + + cmd->upmove = ClampChar( cmd->upmove + cl.joystickAxis[AXIS_UP] ); +} + +/* +================= +CL_MouseMove +================= +*/ +void CL_MouseMove( usercmd_t *cmd ) { + float mx, my; + float accelSensitivity; + float rate; + + // allow mouse smoothing + if ( m_filter->integer ) { + mx = ( cl.mouseDx[0] + cl.mouseDx[1] ) * 0.5; + my = ( cl.mouseDy[0] + cl.mouseDy[1] ) * 0.5; + } else { + mx = cl.mouseDx[cl.mouseIndex]; + my = cl.mouseDy[cl.mouseIndex]; + } + cl.mouseIndex ^= 1; + cl.mouseDx[cl.mouseIndex] = 0; + cl.mouseDy[cl.mouseIndex] = 0; + + rate = sqrt( mx * mx + my * my ) / (float)frame_msec; + accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; + + // scale by FOV + accelSensitivity *= cl.cgameSensitivity; + +/* NERVE - SMF - this has moved to CG_CalcFov to fix zoomed-in/out transition movement bug + if ( cl.snap.ps.stats[STAT_ZOOMED_VIEW] ) { + if(cl.snap.ps.weapon == WP_SNIPERRIFLE) { + accelSensitivity *= 0.1; + } + else if(cl.snap.ps.weapon == WP_SNOOPERSCOPE) { + accelSensitivity *= 0.2; + } + } +*/ + if ( rate && cl_showMouseRate->integer ) { + Com_Printf( "%f : %f\n", rate, accelSensitivity ); + } + +// Ridah, experimenting with a slow tracking gun + + // Rafael - mg42 + if ( cl.snap.ps.persistant[PERS_HWEAPON_USE] ) { + mx *= 2.5; //(accelSensitivity * 0.1); + my *= 2; //(accelSensitivity * 0.075); + } else + { + mx *= accelSensitivity; + my *= accelSensitivity; + } + + if ( !mx && !my ) { + return; + } + + // add mouse X/Y movement to cmd + if ( kb[KB_STRAFE].active ) { + cmd->rightmove = ClampChar( cmd->rightmove + m_side->value * mx ); + } else { + cl.viewangles[YAW] -= m_yaw->value * mx; + } + + if ( ( kb[KB_MLOOK].active || cl_freelook->integer ) && !kb[KB_STRAFE].active ) { + cl.viewangles[PITCH] += m_pitch->value * my; + } else { + cmd->forwardmove = ClampChar( cmd->forwardmove - m_forward->value * my ); + } +} + + +/* +============== +CL_CmdButtons +============== +*/ +void CL_CmdButtons( usercmd_t *cmd ) { + int i; + + // + // figure button bits + // send a button bit even if the key was pressed and released in + // less than a frame + // + for ( i = 0 ; i < 7 ; i++ ) { + if ( kb[KB_BUTTONS0 + i].active || kb[KB_BUTTONS0 + i].wasPressed ) { + cmd->buttons |= 1 << i; + } + kb[KB_BUTTONS0 + i].wasPressed = qfalse; + } + + for ( i = 0 ; i < 7 ; i++ ) { + if ( kb[KB_WBUTTONS0 + i].active || kb[KB_WBUTTONS0 + i].wasPressed ) { + cmd->wbuttons |= 1 << i; + } + kb[KB_WBUTTONS0 + i].wasPressed = qfalse; + } + + if ( cls.keyCatchers && !cl_bypassMouseInput->integer ) { + cmd->buttons |= BUTTON_TALK; + } + + // allow the game to know if any key at all is + // currently pressed, even if it isn't bound to anything + if ( anykeydown && ( !cls.keyCatchers || cl_bypassMouseInput->integer ) ) { + cmd->buttons |= BUTTON_ANY; + } +} + + +/* +============== +CL_FinishMove +============== +*/ +void CL_FinishMove( usercmd_t *cmd ) { + int i; + + // copy the state that the cgame is currently sending + cmd->weapon = cl.cgameUserCmdValue; + + cmd->holdable = cl.cgameUserHoldableValue; //----(SA) modified + + cmd->mpSetup = cl.cgameMpSetup; // NERVE - SMF + cmd->identClient = cl.cgameMpIdentClient; // NERVE - SMF + + // send the current server time so the amount of movement + // can be determined without allowing cheating + cmd->serverTime = cl.serverTime; + + for ( i = 0 ; i < 3 ; i++ ) { + cmd->angles[i] = ANGLE2SHORT( cl.viewangles[i] ); + } +} + + +/* +================= +CL_CreateCmd +================= +*/ +usercmd_t CL_CreateCmd( void ) { + usercmd_t cmd; + vec3_t oldAngles; + float recoilAdd; + + VectorCopy( cl.viewangles, oldAngles ); + + // keyboard angle adjustment + CL_AdjustAngles(); + + memset( &cmd, 0, sizeof( cmd ) ); + + CL_CmdButtons( &cmd ); + + // get basic movement from keyboard + CL_KeyMove( &cmd ); + + // get basic movement from mouse + CL_MouseMove( &cmd ); + + // get basic movement from joystick + CL_JoystickMove( &cmd ); + + // check to make sure the angles haven't wrapped + if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) { + cl.viewangles[PITCH] = oldAngles[PITCH] + 90; + } else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) { + cl.viewangles[PITCH] = oldAngles[PITCH] - 90; + } + + // RF, set the kickAngles so aiming is effected + recoilAdd = cl_recoilPitch->value; + if ( fabs( cl.viewangles[PITCH] + recoilAdd ) < 40 ) { + cl.viewangles[PITCH] += recoilAdd; + } + // the recoilPitch has been used, so clear it out + cl_recoilPitch->value = 0; + + // store out the final values + CL_FinishMove( &cmd ); + + // draw debug graphs of turning for mouse testing + if ( cl_debugMove->integer ) { + if ( cl_debugMove->integer == 1 ) { + SCR_DebugGraph( abs( cl.viewangles[YAW] - oldAngles[YAW] ), 0 ); + } + if ( cl_debugMove->integer == 2 ) { + SCR_DebugGraph( abs( cl.viewangles[PITCH] - oldAngles[PITCH] ), 0 ); + } + } + + return cmd; +} + + +/* +================= +CL_CreateNewCommands + +Create a new usercmd_t structure for this frame +================= +*/ +void CL_CreateNewCommands( void ) { + usercmd_t *cmd; + int cmdNum; + + // no need to create usercmds until we have a gamestate + if ( cls.state < CA_PRIMED ) { + return; + } + + frame_msec = com_frameTime - old_com_frameTime; + + // if running less than 5fps, truncate the extra time to prevent + // unexpected moves after a hitch + if ( frame_msec > 200 ) { + frame_msec = 200; + } + old_com_frameTime = com_frameTime; + + + // generate a command for this frame + cl.cmdNumber++; + cmdNum = cl.cmdNumber & CMD_MASK; + cl.cmds[cmdNum] = CL_CreateCmd(); + cmd = &cl.cmds[cmdNum]; +} + +/* +================= +CL_ReadyToSendPacket + +Returns qfalse if we are over the maxpackets limit +and should choke back the bandwidth a bit by not sending +a packet this frame. All the commands will still get +delivered in the next packet, but saving a header and +getting more delta compression will reduce total bandwidth. +================= +*/ +qboolean CL_ReadyToSendPacket( void ) { + int oldPacketNum; + int delta; + + // don't send anything if playing back a demo + if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { + return qfalse; + } + + // If we are downloading, we send no less than 50ms between packets + if ( *clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 50 ) { + return qfalse; + } + + // if we don't have a valid gamestate yet, only send + // one packet a second + if ( cls.state != CA_ACTIVE && + cls.state != CA_PRIMED && + !*clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 1000 ) { + return qfalse; + } + + // send every frame for loopbacks + if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) { + return qtrue; + } + + // send every frame for LAN + if ( Sys_IsLANAddress( clc.netchan.remoteAddress ) ) { + return qtrue; + } + + // check for exceeding cl_maxpackets + if ( cl_maxpackets->integer < 15 ) { + Cvar_Set( "cl_maxpackets", "15" ); + } else if ( cl_maxpackets->integer > 100 ) { + Cvar_Set( "cl_maxpackets", "100" ); + } + oldPacketNum = ( clc.netchan.outgoingSequence - 1 ) & PACKET_MASK; + delta = cls.realtime - cl.outPackets[ oldPacketNum ].p_realtime; + if ( delta < 1000 / cl_maxpackets->integer ) { + // the accumulated commands will go out in the next packet + return qfalse; + } + + return qtrue; +} + +/* +=================== +CL_WritePacket + +Create and send the command packet to the server +Including both the reliable commands and the usercmds + +During normal gameplay, a client packet will contain something like: + +4 sequence number +2 qport +4 serverid +4 acknowledged sequence number +4 clc.serverCommandSequence + +1 clc_move or clc_moveNoDelta +1 command count + + +=================== +*/ +void CL_WritePacket( void ) { + msg_t buf; + byte data[MAX_MSGLEN]; + int i, j; + usercmd_t *cmd, *oldcmd; + usercmd_t nullcmd; + int packetNum; + int oldPacketNum; + int count, key; + + // don't send anything if playing back a demo + if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { + return; + } + + memset( &nullcmd, 0, sizeof( nullcmd ) ); + oldcmd = &nullcmd; + + MSG_Init( &buf, data, sizeof( data ) ); + + MSG_Bitstream( &buf ); + // write the current serverId so the server + // can tell if this is from the current gameState + MSG_WriteLong( &buf, cl.serverId ); + + // write the last message we received, which can + // be used for delta compression, and is also used + // to tell if we dropped a gamestate + MSG_WriteLong( &buf, clc.serverMessageSequence ); + + // write the last reliable message we received + MSG_WriteLong( &buf, clc.serverCommandSequence ); + + // write any unacknowledged clientCommands + // NOTE TTimo: if you verbose this, you will see that there are quite a few duplicates + // typically several unacknowledged cp or userinfo commands stacked up + for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { + MSG_WriteByte( &buf, clc_clientCommand ); + MSG_WriteLong( &buf, i ); + MSG_WriteString( &buf, clc.reliableCommands[ i & ( MAX_RELIABLE_COMMANDS - 1 ) ] ); + } + + // we want to send all the usercmds that were generated in the last + // few packet, so even if a couple packets are dropped in a row, + // all the cmds will make it to the server + if ( cl_packetdup->integer < 0 ) { + Cvar_Set( "cl_packetdup", "0" ); + } else if ( cl_packetdup->integer > 5 ) { + Cvar_Set( "cl_packetdup", "5" ); + } + oldPacketNum = ( clc.netchan.outgoingSequence - 1 - cl_packetdup->integer ) & PACKET_MASK; + count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; + if ( count > MAX_PACKET_USERCMDS ) { + count = MAX_PACKET_USERCMDS; + Com_Printf( "MAX_PACKET_USERCMDS\n" ); + } + if ( count >= 1 ) { + if ( cl_showSend->integer ) { + Com_Printf( "(%i)", count ); + } + + // begin a client move command + if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting + || clc.serverMessageSequence != cl.snap.messageNum ) { + MSG_WriteByte( &buf, clc_moveNoDelta ); + } else { + MSG_WriteByte( &buf, clc_move ); + } + + // write the command count + MSG_WriteByte( &buf, count ); + + // use the checksum feed in the key + key = clc.checksumFeed; + // also use the message acknowledge + key ^= clc.serverMessageSequence; + // also use the last acknowledged server command in the key + key ^= Com_HashKey( clc.serverCommands[ clc.serverCommandSequence & ( MAX_RELIABLE_COMMANDS - 1 ) ], 32 ); + + // write all the commands, including the predicted command + for ( i = 0 ; i < count ; i++ ) { + j = ( cl.cmdNumber - count + i + 1 ) & CMD_MASK; + cmd = &cl.cmds[j]; + MSG_WriteDeltaUsercmdKey( &buf, key, oldcmd, cmd ); + oldcmd = cmd; + } + } + + // + // deliver the message + // + packetNum = clc.netchan.outgoingSequence & PACKET_MASK; + cl.outPackets[ packetNum ].p_realtime = cls.realtime; + cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; + cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; + clc.lastPacketSentTime = cls.realtime; + + if ( cl_showSend->integer ) { + Com_Printf( "%i ", buf.cursize ); + } + CL_Netchan_Transmit( &clc.netchan, &buf ); + + // clients never really should have messages large enough + // to fragment, but in case they do, fire them all off + // at once + // TTimo: this causes a packet burst, which is bad karma for winsock + // added a WARNING message, we'll see if there are legit situations where this happens + while ( clc.netchan.unsentFragments ) { + if ( cl_showSend->integer ) { + Com_Printf( "WARNING: unsent fragments (not supposed to happen!)\n" ); + } + CL_Netchan_TransmitNextFragment( &clc.netchan ); + } +} + +/* +================= +CL_SendCmd + +Called every frame to builds and sends a command packet to the server. +================= +*/ +void CL_SendCmd( void ) { + // don't send any message if not connected + if ( cls.state < CA_CONNECTED ) { + return; + } + + // don't send commands if paused + if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer ) { + return; + } + + // we create commands even if a demo is playing, + CL_CreateNewCommands(); + + // don't send a packet if the last packet was sent too recently + if ( !CL_ReadyToSendPacket() ) { + if ( cl_showSend->integer ) { + Com_Printf( ". " ); + } + return; + } + + CL_WritePacket(); +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput( void ) { + Cmd_AddCommand( "centerview",IN_CenterView ); + + Cmd_AddCommand( "+moveup",IN_UpDown ); + Cmd_AddCommand( "-moveup",IN_UpUp ); + Cmd_AddCommand( "+movedown",IN_DownDown ); + Cmd_AddCommand( "-movedown",IN_DownUp ); + Cmd_AddCommand( "+left",IN_LeftDown ); + Cmd_AddCommand( "-left",IN_LeftUp ); + Cmd_AddCommand( "+right",IN_RightDown ); + Cmd_AddCommand( "-right",IN_RightUp ); + Cmd_AddCommand( "+forward",IN_ForwardDown ); + Cmd_AddCommand( "-forward",IN_ForwardUp ); + Cmd_AddCommand( "+back",IN_BackDown ); + Cmd_AddCommand( "-back",IN_BackUp ); + Cmd_AddCommand( "+lookup", IN_LookupDown ); + Cmd_AddCommand( "-lookup", IN_LookupUp ); + Cmd_AddCommand( "+lookdown", IN_LookdownDown ); + Cmd_AddCommand( "-lookdown", IN_LookdownUp ); + Cmd_AddCommand( "+strafe", IN_StrafeDown ); + Cmd_AddCommand( "-strafe", IN_StrafeUp ); + Cmd_AddCommand( "+moveleft", IN_MoveleftDown ); + Cmd_AddCommand( "-moveleft", IN_MoveleftUp ); + Cmd_AddCommand( "+moveright", IN_MoverightDown ); + Cmd_AddCommand( "-moveright", IN_MoverightUp ); + Cmd_AddCommand( "+speed", IN_SpeedDown ); + Cmd_AddCommand( "-speed", IN_SpeedUp ); + + Cmd_AddCommand( "+attack", IN_Button0Down ); // ---- id (primary firing) + Cmd_AddCommand( "-attack", IN_Button0Up ); +// Cmd_AddCommand ("+button0", IN_Button0Down); +// Cmd_AddCommand ("-button0", IN_Button0Up); + + Cmd_AddCommand( "+button1", IN_Button1Down ); + Cmd_AddCommand( "-button1", IN_Button1Up ); + + Cmd_AddCommand( "+useitem", IN_UseItemDown ); + Cmd_AddCommand( "-useitem", IN_UseItemUp ); + + Cmd_AddCommand( "+salute", IN_Button3Down ); //----(SA) salute + Cmd_AddCommand( "-salute", IN_Button3Up ); +// Cmd_AddCommand ("+button3", IN_Button3Down); +// Cmd_AddCommand ("-button3", IN_Button3Up); + + Cmd_AddCommand( "+button4", IN_Button4Down ); + Cmd_AddCommand( "-button4", IN_Button4Up ); + //Cmd_AddCommand ("+button5", IN_Button5Down); + //Cmd_AddCommand ("-button5", IN_Button5Up); + + //Cmd_AddCommand ("+button6", IN_Button6Down); + //Cmd_AddCommand ("-button6", IN_Button6Up); + + // Rafael Activate + Cmd_AddCommand( "+activate", IN_ActivateDown ); + Cmd_AddCommand( "-activate", IN_ActivateUp ); + // done. + + // Rafael Kick + Cmd_AddCommand( "+kick", IN_KickDown ); + Cmd_AddCommand( "-kick", IN_KickUp ); + // done + + Cmd_AddCommand( "+sprint", IN_SprintDown ); + Cmd_AddCommand( "-sprint", IN_SprintUp ); + + + // wolf buttons + Cmd_AddCommand( "+attack2", IN_Wbutton0Down ); //----(SA) secondary firing + Cmd_AddCommand( "-attack2", IN_Wbutton0Up ); + Cmd_AddCommand( "+zoom", IN_ZoomDown ); // + Cmd_AddCommand( "-zoom", IN_ZoomUp ); + Cmd_AddCommand( "+quickgren", IN_QuickGrenDown ); // + Cmd_AddCommand( "-quickgren", IN_QuickGrenUp ); + Cmd_AddCommand( "+reload", IN_ReloadDown ); // + Cmd_AddCommand( "-reload", IN_ReloadUp ); + Cmd_AddCommand( "+leanleft", IN_LeanLeftDown ); + Cmd_AddCommand( "-leanleft", IN_LeanLeftUp ); + Cmd_AddCommand( "+leanright", IN_LeanRightDown ); + Cmd_AddCommand( "-leanright", IN_LeanRightUp ); +// JPW NERVE multiplayer buttons + Cmd_AddCommand( "+dropweapon", IN_MP_DropWeaponDown ); // JPW NERVE drop two-handed weapon + Cmd_AddCommand( "-dropweapon", IN_MP_DropWeaponUp ); +// jpw + Cmd_AddCommand( "+wbutton7", IN_Wbutton7Down ); // + Cmd_AddCommand( "-wbutton7", IN_Wbutton7Up ); +//----(SA) end + + + Cmd_AddCommand( "+mlook", IN_MLookDown ); + Cmd_AddCommand( "-mlook", IN_MLookUp ); + + //Cmd_AddCommand ("notebook",IN_Notebook); + Cmd_AddCommand( "help",IN_Help ); + + cl_nodelta = Cvar_Get( "cl_nodelta", "0", 0 ); + cl_debugMove = Cvar_Get( "cl_debugMove", "0", 0 ); +} + + +/* +============ +CL_ClearKeys +============ +*/ +void CL_ClearKeys( void ) { + memset( kb, 0, sizeof( kb ) ); +} diff --git a/src/client/cl_keys.c b/src/client/cl_keys.c new file mode 100644 index 0000000..1c8fc0f --- /dev/null +++ b/src/client/cl_keys.c @@ -0,0 +1,1908 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "client.h" + +/* + +key up events are sent even if in console mode + +*/ + +field_t historyEditLines[COMMAND_HISTORY]; + +int nextHistoryLine; // the last line in the history buffer, not masked +int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + +field_t g_consoleField; +field_t chatField; +qboolean chat_team; +qboolean chat_limbo; // NERVE - SMF + +int chat_playerNum; + + +qboolean key_overstrikeMode; + +qboolean anykeydown; +qkey_t keys[MAX_KEYS]; + + +typedef struct { + char *name; + int keynum; +} keyname_t; + +qboolean UI_checkKeyExec( int key ); // NERVE - SMF + +// names not in this list can either be lowercase ascii, or '0xnn' hex sequences +keyname_t keynames[] = +{ + {"TAB", K_TAB}, + {"ENTER", K_ENTER}, + {"ESCAPE", K_ESCAPE}, + {"SPACE", K_SPACE}, + {"BACKSPACE", K_BACKSPACE}, + {"UPARROW", K_UPARROW}, + {"DOWNARROW", K_DOWNARROW}, + {"LEFTARROW", K_LEFTARROW}, + {"RIGHTARROW", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"SHIFT", K_SHIFT}, + + {"CAPSLOCK", K_CAPSLOCK}, + + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"INS", K_INS}, + {"DEL", K_DEL}, + {"PGDN", K_PGDN}, + {"PGUP", K_PGUP}, + {"HOME", K_HOME}, + {"END", K_END}, + + {"MOUSE1", K_MOUSE1}, + {"MOUSE2", K_MOUSE2}, + {"MOUSE3", K_MOUSE3}, + {"MOUSE4", K_MOUSE4}, + {"MOUSE5", K_MOUSE5}, + + {"MWHEELUP", K_MWHEELUP }, + {"MWHEELDOWN", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + + {"KP_HOME", K_KP_HOME }, + {"KP_UPARROW", K_KP_UPARROW }, + {"KP_PGUP", K_KP_PGUP }, + {"KP_LEFTARROW", K_KP_LEFTARROW }, + {"KP_5", K_KP_5 }, + {"KP_RIGHTARROW", K_KP_RIGHTARROW }, + {"KP_END", K_KP_END }, + {"KP_DOWNARROW", K_KP_DOWNARROW }, + {"KP_PGDN", K_KP_PGDN }, + {"KP_ENTER", K_KP_ENTER }, + {"KP_INS", K_KP_INS }, + {"KP_DEL", K_KP_DEL }, + {"KP_SLASH", K_KP_SLASH }, + {"KP_MINUS", K_KP_MINUS }, + {"KP_PLUS", K_KP_PLUS }, + {"KP_NUMLOCK", K_KP_NUMLOCK }, + {"KP_STAR", K_KP_STAR }, + {"KP_EQUALS", K_KP_EQUALS }, + + {"PAUSE", K_PAUSE}, + + {"SEMICOLON", ';'}, // because a raw semicolon seperates commands + + {"COMMAND", K_COMMAND}, //mac + + {NULL,0} +}; + +keyname_t keynames_d[] = //deutsch +{ + {"TAB", K_TAB}, + {"EINGABETASTE", K_ENTER}, + {"ESC", K_ESCAPE}, + {"LEERTASTE", K_SPACE}, + {"RÜCKTASTE", K_BACKSPACE}, + {"PFEILT.AUF", K_UPARROW}, + {"PFEILT.UNTEN", K_DOWNARROW}, + {"PFEILT.LINKS", K_LEFTARROW}, + {"PFEILT.RECHTS", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"STRG", K_CTRL}, + {"UMSCHALLT", K_SHIFT}, + + {"FESTSTELLT", K_CAPSLOCK}, + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"EINFG", K_INS}, + {"ENTF", K_DEL}, + {"BILD-AB", K_PGDN}, + {"BILD-AUF", K_PGUP}, + {"POS1", K_HOME}, + {"ENDE", K_END}, + + {"MAUS1", K_MOUSE1}, + {"MAUS2", K_MOUSE2}, + {"MAUS3", K_MOUSE3}, + {"MAUS4", K_MOUSE4}, + {"MAUS5", K_MOUSE5}, + + {"MRADOBEN", K_MWHEELUP }, + {"MRADUNTEN", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + + {"ZB_POS1", K_KP_HOME }, + {"ZB_PFEILT.AUF", K_KP_UPARROW }, + {"ZB_BILD-AUF", K_KP_PGUP }, + {"ZB_PFEILT.LINKS", K_KP_LEFTARROW }, + {"ZB_5", K_KP_5 }, + {"ZB_PFEILT.RECHTS",K_KP_RIGHTARROW }, + {"ZB_ENDE", K_KP_END }, + {"ZB_PFEILT.UNTEN", K_KP_DOWNARROW }, + {"ZB_BILD-AB", K_KP_PGDN }, + {"ZB_ENTER", K_KP_ENTER }, + {"ZB_EINFG", K_KP_INS }, + {"ZB_ENTF", K_KP_DEL }, + {"ZB_SLASH", K_KP_SLASH }, + {"ZB_MINUS", K_KP_MINUS }, + {"ZB_PLUS", K_KP_PLUS }, + {"ZB_NUM", K_KP_NUMLOCK }, + {"ZB_*", K_KP_STAR }, + {"ZB_EQUALS", K_KP_EQUALS }, + + {"PAUSE", K_PAUSE}, + + {"COMMAND", K_COMMAND}, //mac + {NULL,0} +}; //end german + +keyname_t keynames_f[] = //french +{ + {"TAB", K_TAB}, + {"ENTREE", K_ENTER}, + {"ECHAP", K_ESCAPE}, + {"ESPACE", K_SPACE}, + {"RETOUR", K_BACKSPACE}, + {"HAUT", K_UPARROW}, + {"BAS", K_DOWNARROW}, + {"GAUCHE", K_LEFTARROW}, + {"DROITE", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"MAJ", K_SHIFT}, + + {"VERRMAJ", K_CAPSLOCK}, + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"INSER", K_INS}, + {"SUPPR", K_DEL}, + {"PGBAS", K_PGDN}, + {"PGHAUT", K_PGUP}, + {"ORIGINE", K_HOME}, + {"FIN", K_END}, + + {"SOURIS1", K_MOUSE1}, + {"SOURIS2", K_MOUSE2}, + {"SOURIS3", K_MOUSE3}, + {"SOURIS4", K_MOUSE4}, + {"SOURIS5", K_MOUSE5}, + + {"MOLETTEHT.", K_MWHEELUP }, + {"MOLETTEBAS", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + + {"PN_ORIGINE", K_KP_HOME }, + {"PN_HAUT", K_KP_UPARROW }, + {"PN_PGBAS", K_KP_PGUP }, + {"PN_GAUCHE", K_KP_LEFTARROW }, + {"PN_5", K_KP_5 }, + {"PN_DROITE", K_KP_RIGHTARROW }, + {"PN_FIN", K_KP_END }, + {"PN_BAS", K_KP_DOWNARROW }, + {"PN_PGBAS", K_KP_PGDN }, + {"PN_ENTR", K_KP_ENTER }, + {"PN_INSER", K_KP_INS }, + {"PN_SUPPR", K_KP_DEL }, + {"PN_SLASH", K_KP_SLASH }, + {"PN_MOINS", K_KP_MINUS }, + {"PN_PLUS", K_KP_PLUS }, + {"PN_VERRNUM", K_KP_NUMLOCK }, + {"PN_*", K_KP_STAR }, + {"PN_EQUALS", K_KP_EQUALS }, + + {"PAUSE", K_PAUSE}, + + {"COMMAND", K_COMMAND}, //mac + + {NULL,0} +}; //end french + +keyname_t keynames_s[] = //Spanish +{ + {"TABULADOR", K_TAB}, + {"INTRO", K_ENTER}, + {"ESC", K_ESCAPE}, + {"BARRA_ESPACIAD", K_SPACE}, + {"RETROCESO", K_BACKSPACE}, + {"CURSOR_ARRIBA", K_UPARROW}, + {"CURSOR_ABAJO", K_DOWNARROW}, + {"CURSOR_IZQDA", K_LEFTARROW}, + {"CURSOR_DERECHA", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"MAYÚS", K_SHIFT}, + + {"BLOQ_MAYÚS", K_CAPSLOCK}, + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"INSERT", K_INS}, + {"SUPR", K_DEL}, + {"AV_PÁG", K_PGDN}, + {"RE_PÁG", K_PGUP}, + {"INICIO", K_HOME}, + {"FIN", K_END}, + + {"RATÓN1", K_MOUSE1}, + {"RATÓN2", K_MOUSE2}, + {"RATÓN3", K_MOUSE3}, + {"RATÓN4", K_MOUSE4}, + {"RATÓN5", K_MOUSE5}, + + {"RUEDA_HACIA_ARRIBA", K_MWHEELUP }, + {"RUEDA_HACIA_ABAJO", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + + {"INICIO(NUM)", K_KP_HOME }, + {"ARRIBA(NUM)", K_KP_UPARROW }, + {"RE_PÁG(NUM)", K_KP_PGUP }, + {"IZQUIERDA(NUM)", K_KP_LEFTARROW }, + {"5(NUM)", K_KP_5 }, + {"DERECHA(NUM)", K_KP_RIGHTARROW }, + {"FIN(NUM)", K_KP_END }, + {"ABAJO(NUM)", K_KP_DOWNARROW }, + {"AV_PÁG(NUM)", K_KP_PGDN }, + {"INTRO(NUM)", K_KP_ENTER }, + {"INS(NUM)", K_KP_INS }, + {"SUPR(NUM)", K_KP_DEL }, + {"/(NUM)", K_KP_SLASH }, + {"-(NUM)", K_KP_MINUS }, + {"+(NUM)", K_KP_PLUS }, + {"BLOQ_NUM", K_KP_NUMLOCK }, + {"*(NUM)", K_KP_STAR }, + {"INTRO(NUM)", K_KP_EQUALS }, + + {"PAUSA", K_PAUSE}, + + {"PUNTO_Y_COMA", ';'}, // because a raw semicolon seperates commands + + {"COMANDO", K_COMMAND}, //mac + + {NULL,0} +}; + +keyname_t keynames_i[] = //Italian +{ + {"TAB", K_TAB}, + {"INVIO", K_ENTER}, + {"ESC", K_ESCAPE}, + {"SPAZIO", K_SPACE}, + {"BACKSPACE", K_BACKSPACE}, + {"FRECCIASU", K_UPARROW}, + {"FRECCIAGIŮ", K_DOWNARROW}, + {"FRECCIASX", K_LEFTARROW}, + {"FRECCIADX", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"MAIUSC", K_SHIFT}, + + {"BLOCMAIUSC", K_CAPSLOCK}, + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"INS", K_INS}, + {"CANC", K_DEL}, + {"PAGGIŮ", K_PGDN}, + {"PAGGSU", K_PGUP}, + {"HOME", K_HOME}, + {"FINE", K_END}, + + {"MOUSE1", K_MOUSE1}, + {"MOUSE2", K_MOUSE2}, + {"MOUSE3", K_MOUSE3}, + {"MOUSE4", K_MOUSE4}, + {"MOUSE5", K_MOUSE5}, + + {"ROTELLASU", K_MWHEELUP }, + {"ROTELLAGIŮ", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + + {"TN_HOME", K_KP_HOME }, + {"TN_FRECCIASU", K_KP_UPARROW }, + {"TN_PAGGSU", K_KP_PGUP }, + {"TN_FRECCIASX", K_KP_LEFTARROW }, + {"TN_5", K_KP_5 }, + {"TN_FRECCIA_DX", K_KP_RIGHTARROW }, + {"TN_FINE", K_KP_END }, + {"TN_FRECCIAGIŮ", K_KP_DOWNARROW }, + {"TN_PAGGIŮ", K_KP_PGDN }, + {"TN_INVIO", K_KP_ENTER }, + {"TN_INS", K_KP_INS }, + {"TN_CANC", K_KP_DEL }, + {"TN_/", K_KP_SLASH }, + {"TN_-", K_KP_MINUS }, + {"TN_+", K_KP_PLUS }, + {"TN_BLOCNUM", K_KP_NUMLOCK }, + {"TN_*", K_KP_STAR }, + {"TN_=", K_KP_EQUALS }, + + {"PAUSA", K_PAUSE}, + + {"ň", ';'}, // because a raw semicolon seperates commands + + {"COMMAND", K_COMMAND}, //mac + + {NULL,0} +}; + +/* +============================================================================= + +EDIT FIELDS + +============================================================================= +*/ + + +/* +=================== +Field_Draw + +Handles horizontal scrolling and cursor blinking +x, y, amd width are in pixels +=================== +*/ +void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor ) { + int len; + int drawLen; + int prestep; + int cursorChar; + char str[MAX_STRING_CHARS]; + int i; + + drawLen = edit->widthInChars; + len = strlen( edit->buffer ) + 1; + + // guarantee that cursor will be visible + if ( len <= drawLen ) { + prestep = 0; + } else { + if ( edit->scroll + drawLen > len ) { + edit->scroll = len - drawLen; + if ( edit->scroll < 0 ) { + edit->scroll = 0; + } + } + prestep = edit->scroll; + +/* + if ( edit->cursor < len - drawLen ) { + prestep = edit->cursor; // cursor at start + } else { + prestep = len - drawLen; + } +*/ + } + + if ( prestep + drawLen > len ) { + drawLen = len - prestep; + } + + // extract characters from the field at + if ( drawLen >= MAX_STRING_CHARS ) { + Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" ); + } + + memcpy( str, edit->buffer + prestep, drawLen ); + str[ drawLen ] = 0; + + // draw it + if ( size == SMALLCHAR_WIDTH ) { + float color[4]; + + color[0] = color[1] = color[2] = color[3] = 1.0; + SCR_DrawSmallStringExt( x, y, str, color, qfalse ); + } else { + // draw big string with drop shadow + SCR_DrawBigString( x, y, str, 1.0 ); + } + + // draw the cursor + if ( !showCursor ) { + return; + } + + if ( (int)( cls.realtime >> 8 ) & 1 ) { + return; // off blink + } + + if ( key_overstrikeMode ) { + cursorChar = 11; + } else { + cursorChar = 10; + } + + i = drawLen - ( Q_PrintStrlen( str ) + 1 ); + + if ( size == SMALLCHAR_WIDTH ) { + SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar ); + } else { + str[0] = cursorChar; + str[1] = 0; + SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0 ); + + } +} + +void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor ) { + Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor ); +} + +void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor ) { + Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor ); +} + +/* +================ +Field_Paste +================ +*/ +void Field_Paste( field_t *edit ) { + char *cbd; + int pasteLen, i; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + return; + } + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( cbd ); + for ( i = 0 ; i < pasteLen ; i++ ) { + Field_CharEvent( edit, cbd[i] ); + } + + free( cbd ); +} + +/* +================= +Field_KeyDownEvent + +Performs the basic line editing functions for the console, +in-game talk, and menu fields + +Key events are used for non-printable characters, others are gotten from char events. +================= +*/ +void Field_KeyDownEvent( field_t *edit, int key ) { + int len; + + // shift-insert is paste + if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) { + Field_Paste( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( key == K_DEL ) { + if ( edit->cursor < len ) { + memmove( edit->buffer + edit->cursor, + edit->buffer + edit->cursor + 1, len - edit->cursor ); + } + return; + } + + if ( key == K_RIGHTARROW ) { + if ( edit->cursor < len ) { + edit->cursor++; + } + + if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) { + edit->scroll++; + } + return; + } + + if ( key == K_LEFTARROW ) { + if ( edit->cursor > 0 ) { + edit->cursor--; + } + if ( edit->cursor < edit->scroll ) { + edit->scroll--; + } + return; + } + + if ( key == K_HOME || ( tolower( key ) == 'a' && keys[K_CTRL].down ) ) { + edit->cursor = 0; + return; + } + + if ( key == K_END || ( tolower( key ) == 'e' && keys[K_CTRL].down ) ) { + edit->cursor = len; + return; + } + + if ( key == K_INS ) { + key_overstrikeMode = !key_overstrikeMode; + return; + } +} + +/* +================== +Field_CharEvent +================== +*/ +void Field_CharEvent( field_t *edit, int ch ) { + int len; + + if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste + Field_Paste( edit ); + return; + } + + if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field + Field_Clear( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace + if ( edit->cursor > 0 ) { + memmove( edit->buffer + edit->cursor - 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->cursor--; + if ( edit->cursor < edit->scroll ) { + edit->scroll--; + } + } + return; + } + + if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home + edit->cursor = 0; + edit->scroll = 0; + return; + } + + if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end + edit->cursor = len; + edit->scroll = edit->cursor - edit->widthInChars; + return; + } + + // + // ignore any other non printable chars + // + if ( ch < 32 ) { + return; + } + + if ( key_overstrikeMode ) { + if ( edit->cursor == MAX_EDIT_LINE - 1 ) { + return; + } + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } else { // insert mode + if ( len == MAX_EDIT_LINE - 1 ) { + return; // all full + } + memmove( edit->buffer + edit->cursor + 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } + + + if ( edit->cursor >= edit->widthInChars ) { + edit->scroll++; + } + + if ( edit->cursor == len + 1 ) { + edit->buffer[edit->cursor] = 0; + } +} + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ + +static const char *completionString; +static char shortestMatch[MAX_TOKEN_CHARS]; +static int matchCount; + +/* +=============== +FindMatches + +=============== +*/ +static void FindMatches( const char *s ) { + int i; + + if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) { + return; + } + matchCount++; + if ( matchCount == 1 ) { + Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) ); + return; + } + + // cut shortestMatch to the amount common with s + for ( i = 0 ; s[i] ; i++ ) { + if ( tolower( shortestMatch[i] ) != tolower( s[i] ) ) { + shortestMatch[i] = 0; + } + } +} + +/* +=============== +PrintMatches + +=============== +*/ +static void PrintMatches( const char *s ) { + if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) { + Com_Printf( " %s\n", s ); + } +} + +static void keyConcatArgs( void ) { + int i; + char *arg; + + for ( i = 1 ; i < Cmd_Argc() ; i++ ) { + Q_strcat( g_consoleField.buffer, sizeof( g_consoleField.buffer ), " " ); + arg = Cmd_Argv( i ); + while ( *arg ) { + if ( *arg == ' ' ) { + Q_strcat( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\"" ); + break; + } + arg++; + } + Q_strcat( g_consoleField.buffer, sizeof( g_consoleField.buffer ), Cmd_Argv( i ) ); + if ( *arg == ' ' ) { + Q_strcat( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\"" ); + } + } +} + +static void ConcatRemaining( const char *src, const char *start ) { + char *str; + + str = strstr( src, start ); + if ( !str ) { + keyConcatArgs(); + return; + } + + str += strlen( start ); + Q_strcat( g_consoleField.buffer, sizeof( g_consoleField.buffer ), str ); +} + + +/* +=============== +CompleteCommand + +Tab expansion +=============== +*/ +static void CompleteCommand( void ) { + field_t *edit; + field_t temp; + + edit = &g_consoleField; + + // only look at the first token for completion purposes + Cmd_TokenizeString( edit->buffer ); + + completionString = Cmd_Argv( 0 ); + if ( completionString[0] == '\\' || completionString[0] == '/' ) { + completionString++; + } + + matchCount = 0; + shortestMatch[0] = 0; + + if ( strlen( completionString ) == 0 ) { + return; + } + + Cmd_CommandCompletion( FindMatches ); + Cvar_CommandCompletion( FindMatches ); + + if ( matchCount == 0 ) { + return; // no matches + } + + Com_Memcpy( &temp, edit, sizeof( field_t ) ); + + if ( matchCount == 1 ) { + Com_sprintf( edit->buffer, sizeof( edit->buffer ), "\\%s", shortestMatch ); + if ( Cmd_Argc() == 1 ) { + Q_strcat( g_consoleField.buffer, sizeof( g_consoleField.buffer ), " " ); + } else { + ConcatRemaining( temp.buffer, completionString ); + } + edit->cursor = strlen( edit->buffer ); + return; + } + + // multiple matches, complete to shortest + Com_sprintf( edit->buffer, sizeof( edit->buffer ), "\\%s", shortestMatch ); + edit->cursor = strlen( edit->buffer ); + ConcatRemaining( temp.buffer, completionString ); + + Com_Printf( "]%s\n", edit->buffer ); + + // run through again, printing matches + Cmd_CommandCompletion( PrintMatches ); + Cvar_CommandCompletion( PrintMatches ); +} + + +/* +==================== +Console_Key + +Handles history and console scrollback +==================== +*/ +void Console_Key( int key ) { + // ctrl-L clears screen + if ( key == 'l' && keys[K_CTRL].down ) { + Cbuf_AddText( "clear\n" ); + return; + } + + // enter finishes the line + if ( key == K_ENTER || key == K_KP_ENTER ) { + // if not in the game explicitly prepent a slash if needed + if ( cls.state != CA_ACTIVE && g_consoleField.buffer[0] != '\\' + && g_consoleField.buffer[0] != '/' ) { + char temp[MAX_STRING_CHARS]; + + Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) ); + Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp ); + g_consoleField.cursor++; + } + + Com_Printf( "]%s\n", g_consoleField.buffer ); + + // leading slash is an explicit command + if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) { + Cbuf_AddText( g_consoleField.buffer + 1 ); // valid command + Cbuf_AddText( "\n" ); + } else { + // other text will be chat messages + if ( !g_consoleField.buffer[0] ) { + return; // empty lines just scroll the console without adding to history + } else { + Cbuf_AddText( "cmd say " ); + Cbuf_AddText( g_consoleField.buffer ); + Cbuf_AddText( "\n" ); + } + } + + // copy line to history buffer + historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField; + nextHistoryLine++; + historyLine = nextHistoryLine; + + Field_Clear( &g_consoleField ); + + g_consoleField.widthInChars = g_console_field_width; + + if ( cls.state == CA_DISCONNECTED ) { + SCR_UpdateScreen(); // force an update, because the command + } // may take some time + return; + } + + // command completion + + if ( key == K_TAB ) { + CompleteCommand(); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + + //----(SA) added some mousewheel functionality to the console + if ( ( key == K_MWHEELUP && keys[K_SHIFT].down ) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || + ( ( tolower( key ) == 'p' ) && keys[K_CTRL].down ) ) { + if ( nextHistoryLine - historyLine < COMMAND_HISTORY + && historyLine > 0 ) { + historyLine--; + } + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + //----(SA) added some mousewheel functionality to the console + if ( ( key == K_MWHEELDOWN && keys[K_SHIFT].down ) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || + ( ( tolower( key ) == 'n' ) && keys[K_CTRL].down ) ) { + if ( historyLine == nextHistoryLine ) { + return; + } + historyLine++; + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + // console scrolling + if ( key == K_PGUP ) { + Con_PageUp(); + return; + } + + if ( key == K_PGDN ) { + Con_PageDown(); + return; + } + + if ( key == K_MWHEELUP ) { //----(SA) added some mousewheel functionality to the console + Con_PageUp(); + if ( keys[K_CTRL].down ) { // hold to accelerate scrolling + Con_PageUp(); + Con_PageUp(); + } + return; + } + + if ( key == K_MWHEELDOWN ) { //----(SA) added some mousewheel functionality to the console + Con_PageDown(); + if ( keys[K_CTRL].down ) { // hold to accelerate scrolling + Con_PageDown(); + Con_PageDown(); + } + return; + } + + // ctrl-home = top of console + if ( key == K_HOME && keys[K_CTRL].down ) { + Con_Top(); + return; + } + + // ctrl-end = bottom of console + if ( key == K_END && keys[K_CTRL].down ) { + Con_Bottom(); + return; + } + + // pass to the normal editline routine + Field_KeyDownEvent( &g_consoleField, key ); +} + +//============================================================================ + + +/* +================ +Message_Key + +In game talk message +================ +*/ +void Message_Key( int key ) { + + char buffer[MAX_STRING_CHARS]; + + + if ( key == K_ESCAPE ) { + cls.keyCatchers &= ~KEYCATCH_MESSAGE; + Field_Clear( &chatField ); + return; + } + + if ( key == K_ENTER || key == K_KP_ENTER ) { + if ( chatField.buffer[0] && cls.state == CA_ACTIVE ) { + if ( chat_playerNum != -1 ) { + + Com_sprintf( buffer, sizeof( buffer ), "tell %i \"%s\"\n", chat_playerNum, chatField.buffer ); + } else if ( chat_team ) { + + Com_sprintf( buffer, sizeof( buffer ), "say_team \"%s\"\n", chatField.buffer ); + } + // NERVE - SMF + else if ( chat_limbo ) { + + Com_sprintf( buffer, sizeof( buffer ), "say_limbo \"%s\"\n", chatField.buffer ); + } + // -NERVE - SMF + else { + Com_sprintf( buffer, sizeof( buffer ), "say \"%s\"\n", chatField.buffer ); + } + + + + CL_AddReliableCommand( buffer ); + } + cls.keyCatchers &= ~KEYCATCH_MESSAGE; + Field_Clear( &chatField ); + return; + } + + Field_KeyDownEvent( &chatField, key ); +} + +//============================================================================ + + +qboolean Key_GetOverstrikeMode( void ) { + return key_overstrikeMode; +} + + +void Key_SetOverstrikeMode( qboolean state ) { + key_overstrikeMode = state; +} + + +/* +=================== +Key_IsDown +=================== +*/ +qboolean Key_IsDown( int keynum ) { + if ( keynum == -1 ) { + return qfalse; + } + + return keys[keynum].down; +} + + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keys[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. + +0x11 will be interpreted as raw hex, which will allow new controlers + +to be configured even if they don't have defined names. +=================== +*/ +int Key_StringToKeynum( char *str ) { + keyname_t *kn; + + if ( !str || !str[0] ) { + return -1; + } + if ( !str[1] ) { + return str[0]; + } + + // check for hex code + if ( str[0] == '0' && str[1] == 'x' && strlen( str ) == 4 ) { + int n1, n2; + + n1 = str[2]; + if ( n1 >= '0' && n1 <= '9' ) { + n1 -= '0'; + } else if ( n1 >= 'a' && n1 <= 'f' ) { + n1 = n1 - 'a' + 10; + } else { + n1 = 0; + } + + n2 = str[3]; + if ( n2 >= '0' && n2 <= '9' ) { + n2 -= '0'; + } else if ( n2 >= 'a' && n2 <= 'f' ) { + n2 = n2 - 'a' + 10; + } else { + n2 = 0; + } + + return n1 * 16 + n2; + } + + // scan for a text match + for ( kn = keynames ; kn->name ; kn++ ) { + if ( !Q_stricmp( str,kn->name ) ) { + return kn->keynum; + } + } + + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the +given keynum. +=================== +*/ +char *Key_KeynumToString( int keynum, qboolean bTranslate ) { + keyname_t *kn; + static char tinystr[5]; + int i, j; + + if ( keynum == -1 ) { + return ""; + } + + if ( keynum < 0 || keynum > 255 ) { + return ""; + } + + // check for printable ascii (don't use quote) + if ( keynum > 32 && keynum < 127 && keynum != '"' ) { + tinystr[0] = keynum; + tinystr[1] = 0; + if ( keynum == ';' && !bTranslate ) { + //fall through and use keyname table + } else { + return tinystr; + } + } + + + kn = keynames; //init to english +#ifndef __MACOS__ //DAJ USA + if ( bTranslate ) { + if ( cl_language->integer - 1 == LANGUAGE_FRENCH ) { + kn = keynames_f; //use french + } else if ( cl_language->integer - 1 == LANGUAGE_GERMAN ) { + kn = keynames_d; //use german + } else if ( cl_language->integer - 1 == LANGUAGE_ITALIAN ) { + kn = keynames_i; //use italian + } else if ( cl_language->integer - 1 == LANGUAGE_SPANISH ) { + kn = keynames_s; //use spanish + } + } +#endif + + // check for a key string + for ( ; kn->name ; kn++ ) { + if ( keynum == kn->keynum ) { + return kn->name; + } + } + + // make a hex string + i = keynum >> 4; + j = keynum & 15; + + tinystr[0] = '0'; + tinystr[1] = 'x'; + tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; + tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; + tinystr[4] = 0; + + return tinystr; +} + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding( int keynum, const char *binding ) { + if ( keynum == -1 ) { + return; + } + + // free old bindings + if ( keys[ keynum ].binding ) { + Z_Free( keys[ keynum ].binding ); + } + + // allocate memory for new binding + keys[keynum].binding = CopyString( binding ); + + // consider this like modifying an archived cvar, so the + // file write will be triggered at the next oportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; +} + + +/* +=================== +Key_GetBinding +=================== +*/ +char *Key_GetBinding( int keynum ) { + if ( keynum == -1 ) { + return ""; + } + + return keys[ keynum ].binding; +} + +/* +=================== +Key_GetKey +=================== +*/ + +int Key_GetKey( const char *binding ) { + int i; + + if ( binding ) { + for ( i = 0 ; i < 256 ; i++ ) { + if ( keys[i].binding && Q_stricmp( binding, keys[i].binding ) == 0 ) { + return i; + } + } + } + return -1; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f( void ) { + int b; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "unbind : remove commands from a key\n" ); + return; + } + + b = Key_StringToKeynum( Cmd_Argv( 1 ) ); + if ( b == -1 ) { + Com_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 ) ); + return; + } + + Key_SetBinding( b, "" ); +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f( void ) { + int i; + + for ( i = 0 ; i < 256 ; i++ ) + if ( keys[i].binding ) { + Key_SetBinding( i, "" ); + } +} + + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f( void ) { + int i, c, b; + char cmd[1024]; + + c = Cmd_Argc(); + + if ( c < 2 ) { + Com_Printf( "bind [command] : attach a command to a key\n" ); + return; + } + b = Key_StringToKeynum( Cmd_Argv( 1 ) ); + if ( b == -1 ) { + Com_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 ) ); + return; + } + + if ( c == 2 ) { + if ( keys[b].binding ) { + Com_Printf( "\"%s\" = \"%s\"\n", Cmd_Argv( 1 ), keys[b].binding ); + } else { + Com_Printf( "\"%s\" is not bound\n", Cmd_Argv( 1 ) ); + } + return; + } + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for ( i = 2 ; i < c ; i++ ) + { + strcat( cmd, Cmd_Argv( i ) ); + if ( i != ( c - 1 ) ) { + strcat( cmd, " " ); + } + } + + Key_SetBinding( b, cmd ); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings( fileHandle_t f ) { + int i; + + FS_Printf( f, "unbindall\n" ); + + for ( i = 0 ; i < 256 ; i++ ) { + if ( keys[i].binding && keys[i].binding[0] ) { + FS_Printf( f, "bind %s \"%s\"\n", Key_KeynumToString( i, qfalse ), keys[i].binding ); + + } + + } +} + + +/* +============ +Key_Bindlist_f + +============ +*/ +void Key_Bindlist_f( void ) { + int i; + + for ( i = 0 ; i < 256 ; i++ ) { + if ( keys[i].binding && keys[i].binding[0] ) { + Com_Printf( "%s \"%s\"\n", Key_KeynumToString( i, qfalse ), keys[i].binding ); + } + } +} + + +/* +=================== +CL_InitKeyCommands +=================== +*/ +void CL_InitKeyCommands( void ) { + // register our functions + Cmd_AddCommand( "bind",Key_Bind_f ); + Cmd_AddCommand( "unbind",Key_Unbind_f ); + Cmd_AddCommand( "unbindall",Key_Unbindall_f ); + Cmd_AddCommand( "bindlist",Key_Bindlist_f ); +} + +/* +=================== +CL_KeyEvent + +Called by the system for both key up and key down events +=================== +*/ +//static consoleCount = 0; +void CL_KeyEvent( int key, qboolean down, unsigned time ) { + char *kb; + char cmd[1024]; + qboolean bypassMenu = qfalse; // NERVE - SMF + + // update auto-repeat status and BUTTON_ANY status + keys[key].down = down; + + if ( down ) { + keys[key].repeats++; + if ( keys[key].repeats == 1 ) { + anykeydown++; + } + } else { + keys[key].repeats = 0; + anykeydown--; + if ( anykeydown < 0 ) { + anykeydown = 0; + } + } + +#ifdef __linux__ + if ( key == K_ENTER ) { + if ( down ) { + if ( keys[K_ALT].down ) { + Key_ClearStates(); + if ( Cvar_VariableValue( "r_fullscreen" ) == 0 ) { + Com_Printf( "Switching to fullscreen rendering\n" ); + Cvar_Set( "r_fullscreen", "1" ); + } else + { + Com_Printf( "Switching to windowed rendering\n" ); + Cvar_Set( "r_fullscreen", "0" ); + } + Cbuf_ExecuteText( EXEC_APPEND, "vid_restart\n" ); + return; + } + } + } +#endif + + // are we waiting to clear stats and move to briefing screen + if ( down && cl_waitForFire && cl_waitForFire->integer ) { //DAJ BUG in dedicated cl_waitForFire don't exist + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { // get rid of the consol + Con_ToggleConsole_f(); + } + // clear all input controls + CL_ClearKeys(); + // allow only attack command input + kb = keys[key].binding; + if ( !Q_stricmp( kb, "+attack" ) ) { + // clear the stats out, ignore the keypress + Cvar_Set( "g_missionStats", "xx" ); // just remove the stats, but still wait until we're done loading, and player has hit fire to begin playing + Cvar_Set( "cl_waitForFire", "0" ); + } + return; // no buttons while waiting + } + + // are we waiting to begin the level + if ( down && cl_missionStats && cl_missionStats->string[0] && cl_missionStats->string[1] ) { //DAJ BUG in dedicated cl_missionStats don't exist + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { // get rid of the consol + Con_ToggleConsole_f(); + } + // clear all input controls + CL_ClearKeys(); + // allow only attack command input + kb = keys[key].binding; + if ( !Q_stricmp( kb, "+attack" ) ) { + // clear the stats out, ignore the keypress + Cvar_Set( "com_expectedhunkusage", "-1" ); + Cvar_Set( "g_missionStats", "0" ); + } + return; // no buttons while waiting + } + + // console key is hardcoded, so the user can never unbind it + if ( key == '`' || key == '~' ) { + if ( !down ) { + return; + + } + Con_ToggleConsole_f(); + return; + } + + // most keys during demo playback will bring up the menu, but non-ascii + + // keys can still be used for bound actions + if ( down && ( key < 128 || key == K_MOUSE1 ) + && ( clc.demoplaying || cls.state == CA_CINEMATIC ) && !cls.keyCatchers ) { + + Cvar_Set( "nextdemo","" ); + key = K_ESCAPE; + } + + // escape is always handled special + if ( key == K_ESCAPE && down ) { + if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { + // clear message mode + Message_Key( key ); + return; + } + + // escape always gets out of CGAME stuff + if ( cls.keyCatchers & KEYCATCH_CGAME ) { + cls.keyCatchers &= ~KEYCATCH_CGAME; + VM_Call( cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE ); + return; + } + + if ( !( cls.keyCatchers & KEYCATCH_UI ) ) { + if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME ); + } else { + CL_Disconnect_f(); + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + return; + } + + VM_Call( uivm, UI_KEY_EVENT, key, down ); + return; + } + + // + // key up events only perform actions if the game key binding is + // a button command (leading + sign). These will be processed even in + // console mode and menu mode, to keep the character from continuing + // an action started before a mode switch. + // + if ( !down ) { + kb = keys[key].binding; + if ( kb && kb[0] == '+' ) { + // button commands add keynum and time as parms so that multiple + // sources can be discriminated and subframe corrected + Com_sprintf( cmd, sizeof( cmd ), "-%s %i %i\n", kb + 1, key, time ); + Cbuf_AddText( cmd ); + } + + if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, down ); + } else if ( cls.keyCatchers & KEYCATCH_CGAME && cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, down ); + } + + return; + } + + // NERVE - SMF - if we just want to pass it along to game + if ( cl_bypassMouseInput && cl_bypassMouseInput->integer ) { //DAJ BUG in dedicated cl_missionStats don't exist + if ( ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) { + if ( cl_bypassMouseInput->integer == 1 ) { + bypassMenu = qtrue; + } + } else if ( !UI_checkKeyExec( key ) ) { + bypassMenu = qtrue; + } + } + + // distribute the key down event to the apropriate handler + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { + Console_Key( key ); + } else if ( cls.keyCatchers & KEYCATCH_UI && !bypassMenu ) { + kb = keys[key].binding; + + if ( VM_Call( uivm, UI_GET_ACTIVE_MENU ) == UIMENU_CLIPBOARD ) { + // any key gets out of clipboard + key = K_ESCAPE; + } else { + + // when in the notebook, check for the key bound to "notebook" and allow that as an escape key + + if ( kb ) { + if ( !Q_stricmp( "notebook", kb ) ) { + if ( VM_Call( uivm, UI_GET_ACTIVE_MENU ) == UIMENU_NOTEBOOK ) { + key = K_ESCAPE; + } + } + + if ( !Q_stricmp( "help", kb ) ) { + if ( VM_Call( uivm, UI_GET_ACTIVE_MENU ) == UIMENU_HELP ) { + key = K_ESCAPE; + } + } + } + } + + VM_Call( uivm, UI_KEY_EVENT, key, down ); + } else if ( cls.keyCatchers & KEYCATCH_CGAME ) { + if ( cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, down ); + } + } else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { + Message_Key( key ); + } else if ( cls.state == CA_DISCONNECTED ) { + + Console_Key( key ); + + } else { + // send the bound action + kb = keys[key].binding; + if ( !kb ) { + if ( key >= 200 ) { + Com_Printf( "%s is unbound, use controls menu to set.\n" + , Key_KeynumToString( key, qfalse ) ); + } + } else if ( kb[0] == '+' ) { + // button commands add keynum and time as parms so that multiple + // sources can be discriminated and subframe corrected + Com_sprintf( cmd, sizeof( cmd ), "%s %i %i\n", kb, key, time ); + Cbuf_AddText( cmd ); + } else { + // down-only command + Cbuf_AddText( kb ); + Cbuf_AddText( "\n" ); + } + } +} + + +/* +=================== +CL_CharEvent + +Normal keyboard characters, already shifted / capslocked / etc +=================== +*/ +void CL_CharEvent( int key ) { + // the console key should never be used as a char + if ( key == '`' || key == '~' ) { + return; + } + + // distribute the key down event to the apropriate handler + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { + Field_CharEvent( &g_consoleField, key ); + } else if ( cls.keyCatchers & KEYCATCH_UI ) { + VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue ); + } else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { + Field_CharEvent( &chatField, key ); + } else if ( cls.state == CA_DISCONNECTED ) { + Field_CharEvent( &g_consoleField, key ); + } +} + + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates( void ) { + int i; + + anykeydown = qfalse; + + for ( i = 0 ; i < MAX_KEYS ; i++ ) { + if ( keys[i].down ) { + CL_KeyEvent( i, qfalse, 0 ); + + } + keys[i].down = 0; + keys[i].repeats = 0; + } +} + diff --git a/src/client/cl_main.c b/src/client/cl_main.c new file mode 100644 index 0000000..eb101ec --- /dev/null +++ b/src/client/cl_main.c @@ -0,0 +1,4664 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cl_main.c -- client main loop + +#include "client.h" +#include + +#ifdef __linux__ +#include +#endif + +cvar_t *cl_wavefilerecord; +cvar_t *cl_nodelta; +cvar_t *cl_debugMove; + +cvar_t *cl_noprint; +cvar_t *cl_motd; +cvar_t *cl_autoupdate; // DHM - Nerve + +cvar_t *rcon_client_password; +cvar_t *rconAddress; + +cvar_t *cl_timeout; +cvar_t *cl_maxpackets; +cvar_t *cl_packetdup; +cvar_t *cl_timeNudge; +cvar_t *cl_showTimeDelta; +cvar_t *cl_freezeDemo; + +cvar_t *cl_shownet = NULL; // NERVE - SMF - This is referenced in msg.c and we need to make sure it is NULL +cvar_t *cl_shownuments; // DHM - Nerve +cvar_t *cl_visibleClients; // DHM - Nerve +cvar_t *cl_showSend; +cvar_t *cl_showServerCommands; // NERVE - SMF +cvar_t *cl_timedemo; +cvar_t *cl_avidemo; +cvar_t *cl_forceavidemo; + +cvar_t *cl_freelook; +cvar_t *cl_sensitivity; + +cvar_t *cl_mouseAccel; +cvar_t *cl_showMouseRate; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; +cvar_t *m_filter; + +cvar_t *cl_activeAction; + +cvar_t *cl_motdString; + +cvar_t *cl_allowDownload; +cvar_t *cl_conXOffset; +cvar_t *cl_inGameVideo; + +cvar_t *cl_serverStatusResendTime; +cvar_t *cl_trn; +cvar_t *cl_missionStats; +cvar_t *cl_waitForFire; + +// NERVE - SMF - localization +cvar_t *cl_language; +cvar_t *cl_debugTranslation; +// -NERVE - SMF +// DHM - Nerve :: Auto-Update +cvar_t *cl_updateavailable; +cvar_t *cl_updatefiles; +// DHM - Nerve + +clientActive_t cl; +clientConnection_t clc; +clientStatic_t cls; +vm_t *cgvm; + +// Structure containing functions exported from refresh DLL +refexport_t re; + +ping_t cl_pinglist[MAX_PINGREQUESTS]; + +typedef struct serverStatus_s +{ + char string[BIG_INFO_STRING]; + netadr_t address; + int time, startTime; + qboolean pending; + qboolean print; + qboolean retrieved; +} serverStatus_t; + +serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; +int serverStatusCount; + +// DHM - Nerve :: Have we heard from the auto-update server this session? +qboolean autoupdateChecked; +qboolean autoupdateStarted; +// TTimo : moved from char* to array (was getting the char* from va(), broke on big downloads) +char autoupdateFilename[MAX_QPATH]; +// "updates" shifted from -7 +#define AUTOUPDATE_DIR "ni]Zm^l" +#define AUTOUPDATE_DIR_SHIFT 7 + +extern void SV_BotFrame( int time ); +void CL_CheckForResend( void ); +void CL_ShowIP_f( void ); +void CL_ServerStatus_f( void ); +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); +void CL_SaveTranslations_f( void ); +void CL_LoadTranslations_f( void ); + +/* +=============== +CL_CDDialog + +Called by Com_Error when a cd is needed +=============== +*/ +void CL_CDDialog( void ) { + cls.cddialog = qtrue; // start it next frame +} + + +/* +======================================================================= + +CLIENT RELIABLE COMMAND COMMUNICATION + +======================================================================= +*/ + +/* +====================== +CL_AddReliableCommand + +The given command will be transmitted to the server, and is gauranteed to +not have future usercmd_t executed before it is executed +====================== +*/ +void CL_AddReliableCommand( const char *cmd ) { + int index; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { + Com_Error( ERR_DROP, "Client command overflow" ); + } + clc.reliableSequence++; + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) ); +} + +/* +====================== +CL_ChangeReliableCommand +====================== +*/ +void CL_ChangeReliableCommand( void ) { + int r, index, l; + + // NOTE TTimo: what is the randomize for? + r = clc.reliableSequence - ( random() * 5 ); + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + l = strlen( clc.reliableCommands[ index ] ); + if ( l >= MAX_STRING_CHARS - 1 ) { + l = MAX_STRING_CHARS - 2; + } + clc.reliableCommands[ index ][ l ] = '\n'; + clc.reliableCommands[ index ][ l + 1 ] = '\0'; +} + +/* +======================================================================= + +CLIENT SIDE DEMO RECORDING + +======================================================================= +*/ + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length +==================== +*/ +void CL_WriteDemoMessage( msg_t *msg, int headerBytes ) { + int len, swlen; + + // write the packet sequence + len = clc.serverMessageSequence; + swlen = LittleLong( len ); + FS_Write( &swlen, 4, clc.demofile ); + + // skip the packet sequencing information + len = msg->cursize - headerBytes; + swlen = LittleLong( len ); + FS_Write( &swlen, 4, clc.demofile ); + FS_Write( msg->data + headerBytes, len, clc.demofile ); +} + + +/* +==================== +CL_StopRecording_f + +stop recording a demo +==================== +*/ +void CL_StopRecord_f( void ) { + int len; + + if ( !clc.demorecording ) { + Com_Printf( "Not recording a demo.\n" ); + return; + } + + // finish up + len = -1; + FS_Write( &len, 4, clc.demofile ); + FS_Write( &len, 4, clc.demofile ); + FS_FCloseFile( clc.demofile ); + clc.demofile = 0; + clc.demorecording = qfalse; + Com_Printf( "Stopped demo.\n" ); +} + +/* +================== +CL_DemoFilename +================== +*/ +void CL_DemoFilename( int number, char *fileName ) { + int a,b,c,d; + + if ( number < 0 || number > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" ); + return; + } + + a = number / 1000; + number -= a * 1000; + b = number / 100; + number -= b * 100; + c = number / 10; + number -= c * 10; + d = number; + + Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" + , a, b, c, d ); +} + +/* +==================== +CL_Record_f + +record + +Begins recording a demo from the current position +==================== +*/ +static char demoName[MAX_QPATH]; // compiler bug workaround +void CL_Record_f( void ) { + char name[MAX_OSPATH]; + byte bufData[MAX_MSGLEN]; + msg_t buf; + int i; + int len; + entityState_t *ent; + entityState_t nullstate; + char *s; + + if ( Cmd_Argc() > 2 ) { + Com_Printf( "record \n" ); + return; + } + + if ( clc.demorecording ) { + Com_Printf( "Already recording.\n" ); + return; + } + + if ( cls.state != CA_ACTIVE ) { + Com_Printf( "You must be in a level to record.\n" ); + return; + } + + // ATVI Wolfenstein Misc #479 - changing this to a warning + // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. + if ( !Cvar_VariableValue( "g_synchronousClients" ) ) { + Com_Printf( S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n" ); + } + + if ( Cmd_Argc() == 2 ) { + s = Cmd_Argv( 1 ); + Q_strncpyz( demoName, s, sizeof( demoName ) ); + Com_sprintf( name, sizeof( name ), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); + } else { + int number; + + // scan for a free demo name + for ( number = 0 ; number <= 9999 ; number++ ) { + CL_DemoFilename( number, demoName ); + Com_sprintf( name, sizeof( name ), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); + + len = FS_ReadFile( name, NULL ); + if ( len <= 0 ) { + break; // file doesn't exist + } + } + } + + // open the demo file + +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'WlfB'; + _fcreator = 'WlfM'; + } +#endif + Com_Printf( "recording to %s.\n", name ); + clc.demofile = FS_FOpenFileWrite( name ); + if ( !clc.demofile ) { + Com_Printf( "ERROR: couldn't open.\n" ); + return; + } + clc.demorecording = qtrue; + Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); + + // don't start saving messages until a non-delta compressed message is received + clc.demowaiting = qtrue; + + // write out the gamestate message + MSG_Init( &buf, bufData, sizeof( bufData ) ); + MSG_Bitstream( &buf ); + + // NOTE, MRE: all server->client messages now acknowledge + MSG_WriteLong( &buf, clc.reliableSequence ); + + MSG_WriteByte( &buf, svc_gamestate ); + MSG_WriteLong( &buf, clc.serverCommandSequence ); + + // configstrings + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( !cl.gameState.stringOffsets[i] ) { + continue; + } + s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; + MSG_WriteByte( &buf, svc_configstring ); + MSG_WriteShort( &buf, i ); + MSG_WriteBigString( &buf, s ); + } + + // baselines + memset( &nullstate, 0, sizeof( nullstate ) ); + for ( i = 0; i < MAX_GENTITIES ; i++ ) { + ent = &cl.entityBaselines[i]; + if ( !ent->number ) { + continue; + } + MSG_WriteByte( &buf, svc_baseline ); + MSG_WriteDeltaEntity( &buf, &nullstate, ent, qtrue ); + } + + MSG_WriteByte( &buf, svc_EOF ); + + // finished writing the gamestate stuff + + // write the client num + MSG_WriteLong( &buf, clc.clientNum ); + // write the checksum feed + MSG_WriteLong( &buf, clc.checksumFeed ); + + // finished writing the client packet + MSG_WriteByte( &buf, svc_EOF ); + + // write it to the demo file + len = LittleLong( clc.serverMessageSequence - 1 ); + FS_Write( &len, 4, clc.demofile ); + + len = LittleLong( buf.cursize ); + FS_Write( &len, 4, clc.demofile ); + FS_Write( buf.data, buf.cursize, clc.demofile ); + + // the rest of the demo file will be copied from net messages +} + +/* +======================================================================= + +CLIENT SIDE DEMO PLAYBACK + +======================================================================= +*/ + +/* +================= +CL_DemoCompleted +================= +*/ +void CL_DemoCompleted( void ) { + if ( cl_timedemo && cl_timedemo->integer ) { + int time; + + time = Sys_Milliseconds() - clc.timeDemoStart; + if ( time > 0 ) { + Com_Printf( "%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames, + time / 1000.0, clc.timeDemoFrames * 1000.0 / time ); + } + } + + CL_Disconnect( qtrue ); + CL_NextDemo(); +} + +/* +================= +CL_ReadDemoMessage +================= +*/ +void CL_ReadDemoMessage( void ) { + int r; + msg_t buf; + byte bufData[ MAX_MSGLEN ]; + int s; + + if ( !clc.demofile ) { + CL_DemoCompleted(); + return; + } + + // get the sequence number + r = FS_Read( &s, 4, clc.demofile ); + if ( r != 4 ) { + CL_DemoCompleted(); + return; + } + clc.serverMessageSequence = LittleLong( s ); + + // init the message + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + // get the length + r = FS_Read( &buf.cursize, 4, clc.demofile ); + if ( r != 4 ) { + CL_DemoCompleted(); + return; + } + buf.cursize = LittleLong( buf.cursize ); + if ( buf.cursize == -1 ) { + CL_DemoCompleted(); + return; + } + if ( buf.cursize > buf.maxsize ) { + Com_Error( ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN" ); + } + r = FS_Read( buf.data, buf.cursize, clc.demofile ); + if ( r != buf.cursize ) { + Com_Printf( "Demo file was truncated.\n" ); + CL_DemoCompleted(); + return; + } + + clc.lastPacketTime = cls.realtime; + buf.readcount = 0; + CL_ParseServerMessage( &buf ); +} + +/* +==================== + + Wave file saving functions + +==================== +*/ + +void CL_WriteWaveOpen() { + // we will just save it as a 16bit stereo 22050kz pcm file + clc.wavefile = FS_FOpenFileWrite( "demodata.pcm" ); + clc.wavetime = -1; +} + +void CL_WriteWaveClose() { + // and we're outta here + FS_FCloseFile( clc.wavefile ); +} + +extern int s_soundtime; +extern portable_samplepair_t *paintbuffer; + +void CL_WriteWaveFilePacket() { + int total, i; + if ( clc.wavetime == -1 ) { + clc.wavetime = s_soundtime; + return; + } + + total = s_soundtime - clc.wavetime; + clc.wavetime = s_soundtime; + + for ( i = 0; i < total; i++ ) { + int parm; + short out; + parm = ( paintbuffer[i].left ) >> 8; + if ( parm > 32767 ) { + parm = 32767; + } + if ( parm < -32768 ) { + parm = -32768; + } + out = parm; + FS_Write( &out, 2, clc.wavefile ); + parm = ( paintbuffer[i].right ) >> 8; + if ( parm > 32767 ) { + parm = 32767; + } + if ( parm < -32768 ) { + parm = -32768; + } + out = parm; + FS_Write( &out, 2, clc.wavefile ); + } +} + +/* +==================== +CL_PlayDemo_f + +demo + +==================== +*/ +void CL_PlayDemo_f( void ) { + char name[MAX_OSPATH], extension[32]; + char *arg; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "playdemo \n" ); + return; + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + + CL_Disconnect( qtrue ); + + +// CL_FlushMemory(); //----(SA) MEM NOTE: in missionpack, this is moved to CL_DownloadsComplete + + + // open the demo file + arg = Cmd_Argv( 1 ); + Com_sprintf( extension, sizeof( extension ), ".dm_%d", PROTOCOL_VERSION ); + if ( !Q_stricmp( arg + strlen( arg ) - strlen( extension ), extension ) ) { + Com_sprintf( name, sizeof( name ), "demos/%s", arg ); + } else { + Com_sprintf( name, sizeof( name ), "demos/%s.dm_%d", arg, PROTOCOL_VERSION ); + } + + FS_FOpenFileRead( name, &clc.demofile, qtrue ); + if ( !clc.demofile ) { + Com_Error( ERR_DROP, "couldn't open %s", name ); + return; + } + Q_strncpyz( clc.demoName, Cmd_Argv( 1 ), sizeof( clc.demoName ) ); + + Con_Close(); + + cls.state = CA_CONNECTED; + clc.demoplaying = qtrue; + + if ( Cvar_VariableValue( "cl_wavefilerecord" ) ) { + CL_WriteWaveOpen(); + clc.waverecording = qtrue; + } + + Q_strncpyz( cls.servername, Cmd_Argv( 1 ), sizeof( cls.servername ) ); + + // read demo messages until connected + while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { + CL_ReadDemoMessage(); + if ( clc.waverecording ) { + CL_WriteWaveFilePacket(); + } + } + // don't get the first snapshot this frame, to prevent the long + // time from the gamestate load from messing causing a time skip + clc.firstDemoFrameSkipped = qfalse; + if ( clc.waverecording ) { + CL_WriteWaveClose(); + clc.waverecording = qfalse; + } +} + + +/* +==================== +CL_StartDemoLoop + +Closing the main menu will restart the demo loop +==================== +*/ +void CL_StartDemoLoop( void ) { + // start the demo loop again + Cbuf_AddText( "d1\n" ); + cls.keyCatchers = 0; +} + +/* +================== +CL_NextDemo + +Called when a demo or cinematic finishes +If the "nextdemo" cvar is set, that command will be issued +================== +*/ +void CL_NextDemo( void ) { + char v[MAX_STRING_CHARS]; + + Q_strncpyz( v, Cvar_VariableString( "nextdemo" ), sizeof( v ) ); + v[MAX_STRING_CHARS - 1] = 0; + Com_DPrintf( "CL_NextDemo: %s\n", v ); + if ( !v[0] ) { + return; + } + + Cvar_Set( "nextdemo","" ); + Cbuf_AddText( v ); + Cbuf_AddText( "\n" ); + Cbuf_Execute(); +} + + +//====================================================================== + +/* +===================== +CL_ShutdownAll +===================== +*/ +void CL_ShutdownAll( void ) { + + // clear sounds + S_DisableSounds(); + // shutdown CGame + CL_ShutdownCGame(); + // shutdown UI + CL_ShutdownUI(); + + // shutdown the renderer + if ( re.Shutdown ) { + re.Shutdown( qfalse ); // don't destroy window or context + } + + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.rendererStarted = qfalse; + cls.soundRegistered = qfalse; +} + +/* +================= +CL_FlushMemory + +Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only +ways a client gets into a game +Also called by Com_Error +================= +*/ +void CL_FlushMemory( void ) { + + // shutdown all the client stuff + CL_ShutdownAll(); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + // clear collision map data + CM_ClearMap(); + } else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + CL_StartHunkUsers(); +} + +/* +===================== +CL_MapLoading + +A local server is starting to load a map, so update the +screen to let the user know about it, then dump all client +memory on the hunk from cgame, ui, and renderer +===================== +*/ +void CL_MapLoading( void ) { + if ( !com_cl_running->integer ) { + return; + } + + Con_Close(); + cls.keyCatchers = 0; + + // if we are already connected to the local host, stay connected + if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { + cls.state = CA_CONNECTED; // so the connect screen is drawn + memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); + memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); + memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + clc.lastPacketSentTime = -9999; + SCR_UpdateScreen(); + } else { + // clear nextmap so the cinematic shutdown doesn't execute it + Cvar_Set( "nextmap", "" ); + CL_Disconnect( qtrue ); + Q_strncpyz( cls.servername, "localhost", sizeof( cls.servername ) ); + cls.state = CA_CHALLENGING; // so the connect screen is drawn + cls.keyCatchers = 0; + SCR_UpdateScreen(); + clc.connectTime = -RETRANSMIT_TIMEOUT; + NET_StringToAdr( cls.servername, &clc.serverAddress ); + // we don't need a challenge on the localhost + + CL_CheckForResend(); + } +} + +/* +===================== +CL_ClearState + +Called before parsing a gamestate +===================== +*/ +void CL_ClearState( void ) { + +// S_StopAllSounds(); + + memset( &cl, 0, sizeof( cl ) ); +} + + +/* +===================== +CL_Disconnect + +Called when a connection, demo, or cinematic is being terminated. +Goes from a connected state to either a menu state or a console state +Sends a disconnect message to the server +This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( qboolean showMainMenu ) { + if ( !com_cl_running || !com_cl_running->integer ) { + return; + } + + // shutting down the client so enter full screen ui mode + Cvar_Set( "r_uiFullScreen", "1" ); + + if ( clc.demorecording ) { + CL_StopRecord_f(); + } + + if ( clc.download ) { + FS_FCloseFile( clc.download ); + clc.download = 0; + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + autoupdateStarted = qfalse; + autoupdateFilename[0] = '\0'; + + if ( clc.demofile ) { + FS_FCloseFile( clc.demofile ); + clc.demofile = 0; + } + + if ( uivm && showMainMenu ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + + SCR_StopCinematic(); + S_ClearSoundBuffer(); + + // send a disconnect message to the server + // send it a few times in case one is dropped + if ( cls.state >= CA_CONNECTED ) { + CL_AddReliableCommand( "disconnect" ); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + } + + CL_ClearState(); + + // wipe the client connection + memset( &clc, 0, sizeof( clc ) ); + + cls.state = CA_DISCONNECTED; + + // allow cheats locally + Cvar_Set( "sv_cheats", "1" ); + + // not connected to a pure server anymore + cl_connectedToPureServer = qfalse; +} + + +/* +=================== +CL_ForwardCommandToServer + +adds the current command line as a clientCommand +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void CL_ForwardCommandToServer( const char *string ) { + char *cmd; + + cmd = Cmd_Argv( 0 ); + + // ignore key up commands + if ( cmd[0] == '-' ) { + return; + } + + if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { + Com_Printf( "Unknown command \"%s\"\n", cmd ); + return; + } + + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand( string ); + } else { + CL_AddReliableCommand( cmd ); + } +} + +/* +=================== +CL_RequestMotd + +=================== +*/ +void CL_RequestMotd( void ) { + char info[MAX_INFO_STRING]; + + if ( !cl_motd->integer ) { + return; + } + Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME ); + if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + cls.updateServer.port = BigShort( PORT_UPDATE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME, + cls.updateServer.ip[0], cls.updateServer.ip[1], + cls.updateServer.ip[2], cls.updateServer.ip[3], + BigShort( cls.updateServer.port ) ); + + info[0] = 0; + Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", rand() ); + + Info_SetValueForKey( info, "challenge", cls.updateChallenge ); + Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); + Info_SetValueForKey( info, "version", com_version->string ); + + NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info ); +} + +/* +=================== +CL_RequestAuthorization + +Authorization server protocol +----------------------------- + +All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff). + +Whenever the client tries to get a challenge from the server it wants to +connect to, it also blindly fires off a packet to the authorize server: + +getKeyAuthorize + +cdkey may be "demo" + + +#OLD The authorize server returns a: +#OLD +#OLD keyAthorize +#OLD +#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP +#OLD address in the last 15 minutes. + + +The server sends a: + +getIpAuthorize + +The authorize server returns a: + +ipAuthorize + +A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes. +If no response is received from the authorize server after two tries, the client will be let +in anyway. +=================== +*/ +void CL_RequestAuthorization( void ) { + char nums[64]; + int i, j, l; + cvar_t *fs; + + if ( !cls.authorizeServer.port ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + + cls.authorizeServer.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + cls.authorizeServer.ip[0], cls.authorizeServer.ip[1], + cls.authorizeServer.ip[2], cls.authorizeServer.ip[3], + BigShort( cls.authorizeServer.port ) ); + } + if ( cls.authorizeServer.type == NA_BAD ) { + return; + } + + if ( Cvar_VariableValue( "fs_restrict" ) ) { + Q_strncpyz( nums, "demo", sizeof( nums ) ); + } else { + // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces + j = 0; + l = strlen( cl_cdkey ); + if ( l > 32 ) { + l = 32; + } + for ( i = 0 ; i < l ; i++ ) { + if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' ) + || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' ) + || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) + ) { + nums[j] = cl_cdkey[i]; + j++; + } + } + nums[j] = 0; + } + + fs = Cvar_Get( "cl_anonymous", "0", CVAR_INIT | CVAR_SYSTEMINFO ); + NET_OutOfBandPrint( NS_CLIENT, cls.authorizeServer, va( "getKeyAuthorize %i %s", fs->integer, nums ) ); +} + +/* +====================================================================== + +CONSOLE COMMANDS + +====================================================================== +*/ + +/* +================== +CL_ForwardToServer_f +================== +*/ +void CL_ForwardToServer_f( void ) { + if ( cls.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf( "Not connected to a server.\n" ); + return; + } + + // don't forward the first argument + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand( Cmd_Args() ); + } +} + +/* +================== +CL_Setenv_f + +Mostly for controlling voodoo environment variables +================== +*/ +void CL_Setenv_f( void ) { + int argc = Cmd_Argc(); + + if ( argc > 2 ) { + char buffer[1024]; + int i; + + strcpy( buffer, Cmd_Argv( 1 ) ); + strcat( buffer, "=" ); + + for ( i = 2; i < argc; i++ ) { + strcat( buffer, Cmd_Argv( i ) ); + strcat( buffer, " " ); + } + +#ifdef _WIN32 + _putenv( buffer ); +#else + putenv( buffer ); +#endif + } else if ( argc == 2 ) { + char *env = getenv( Cmd_Argv( 1 ) ); + + if ( env ) { + Com_Printf( "%s=%s\n", Cmd_Argv( 1 ), env ); + } else { + Com_Printf( "%s undefined\n", Cmd_Argv( 1 ), env ); + } + } +} + + +/* +================== +CL_Disconnect_f +================== +*/ +void CL_Disconnect_f( void ) { + SCR_StopCinematic(); + if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { + Com_Error( ERR_DISCONNECT, "Disconnected from server" ); + } +} + + +/* +================ +CL_Reconnect_f + +================ +*/ +void CL_Reconnect_f( void ) { + if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { + Com_Printf( "Can't reconnect to localhost.\n" ); + return; + } + Cbuf_AddText( va( "connect %s\n", cls.servername ) ); +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) { + char *server; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: connect [server]\n" ); + return; + } + + S_StopAllSounds(); // NERVE - SMF + + // starting to load a map so we get out of full screen ui mode + Cvar_Set( "r_uiFullScreen", "0" ); + + // fire a message off to the motd server + CL_RequestMotd(); + + // clear any previous "server full" type messages + clc.serverMessage[0] = 0; + + server = Cmd_Argv( 1 ); + + if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { + // if running a local server, kill it + SV_Shutdown( "Server quit\n" ); + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + SV_Frame( 0 ); + + CL_Disconnect( qtrue ); + Con_Close(); + + Q_strncpyz( cls.servername, server, sizeof( cls.servername ) ); + + if ( !NET_StringToAdr( cls.servername, &clc.serverAddress ) ) { + Com_Printf( "Bad server address\n" ); + cls.state = CA_DISCONNECTED; + return; + } + if ( clc.serverAddress.port == 0 ) { + clc.serverAddress.port = BigShort( PORT_SERVER ); + } + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername, + clc.serverAddress.ip[0], clc.serverAddress.ip[1], + clc.serverAddress.ip[2], clc.serverAddress.ip[3], + BigShort( clc.serverAddress.port ) ); + + // if we aren't playing on a lan, we need to authenticate + // with the cd key + if ( NET_IsLocalAddress( clc.serverAddress ) ) { + cls.state = CA_CHALLENGING; + } else { + cls.state = CA_CONNECTING; + } + + // show_bug.cgi?id=507 + // prepare to catch a connection process that would turn bad + Cvar_Set( "com_errorDiagnoseIP", NET_AdrToString( clc.serverAddress ) ); + // ATVI Wolfenstein Misc #439 + // we need to setup a correct default for this, otherwise the first val we set might reappear + Cvar_Set( "com_errorMessage", "" ); + + cls.keyCatchers = 0; + clc.connectTime = -99999; // CL_CheckForResend() will fire immediately + clc.connectPacketCount = 0; + + // server connection string + Cvar_Set( "cl_currentServerAddress", server ); + + // NERVE - SMF - reset some cvars + Cvar_Set( "mp_playerType", "0" ); + Cvar_Set( "mp_currentPlayerType", "0" ); + Cvar_Set( "mp_weapon", "0" ); + Cvar_Set( "mp_team", "0" ); + Cvar_Set( "mp_currentTeam", "0" ); + + Cvar_Set( "ui_limboOptions", "0" ); + Cvar_Set( "ui_limboPrevOptions", "0" ); + Cvar_Set( "ui_limboObjective", "0" ); + // -NERVE - SMF + +} + + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) { + char message[1024]; + netadr_t to; + + if ( !rcon_client_password->string ) { + Com_Printf( "You must set 'rconpassword' before\n" + "issuing an rcon command.\n" ); + return; + } + + message[0] = -1; + message[1] = -1; + message[2] = -1; + message[3] = -1; + message[4] = 0; + + strcat( message, "rcon " ); + + strcat( message, rcon_client_password->string ); + strcat( message, " " ); + + // ATVI Wolfenstein Misc #284 + strcat( message, Cmd_Cmd() + 5 ); + + if ( cls.state >= CA_CONNECTED ) { + to = clc.netchan.remoteAddress; + } else { + if ( !strlen( rconAddress->string ) ) { + Com_Printf( "You must either be connected,\n" + "or set the 'rconAddress' cvar\n" + "to issue rcon commands\n" ); + + return; + } + NET_StringToAdr( rconAddress->string, &to ); + if ( to.port == 0 ) { + to.port = BigShort( PORT_SERVER ); + } + } + + NET_SendPacket( NS_CLIENT, strlen( message ) + 1, message, to ); +} + +/* +================= +CL_SendPureChecksums +================= +*/ +void CL_SendPureChecksums( void ) { + const char *pChecksums; + char cMsg[MAX_INFO_VALUE]; + int i; + + // if we are pure we need to send back a command with our referenced pk3 checksums + pChecksums = FS_ReferencedPakPureChecksums(); + + // "cp" + Com_sprintf( cMsg, sizeof( cMsg ), "Va " ); + Q_strcat( cMsg, sizeof( cMsg ), va( "%d ", cl.serverId ) ); + Q_strcat( cMsg, sizeof( cMsg ), pChecksums ); + for ( i = 0; i < 2; i++ ) { + cMsg[i] += 13 + ( i * 2 ); + } + CL_AddReliableCommand( cMsg ); +} + +/* +================= +CL_ResetPureClientAtServer +================= +*/ +void CL_ResetPureClientAtServer( void ) { + CL_AddReliableCommand( va( "vdr" ) ); +} + +/* +================= +CL_Vid_Restart_f + +Restart the video subsystem + +we also have to reload the UI and CGame because the renderer +doesn't know what graphics to reload +================= +*/ +void CL_Vid_Restart_f( void ) { + + // RF, don't show percent bar, since the memory usage will just sit at the same level anyway + Cvar_Set( "com_expectedhunkusage", "-1" ); + + // don't let them loop during the restart + S_StopAllSounds(); + // shutdown the UI + CL_ShutdownUI(); + // shutdown the CGame + CL_ShutdownCGame(); + // shutdown the renderer and clear the renderer interface + CL_ShutdownRef(); + // client is no longer pure untill new checksums are sent + CL_ResetPureClientAtServer(); + // clear pak references + FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); + // reinitialize the filesystem if the game directory or checksum has changed + FS_ConditionalRestart( clc.checksumFeed ); + + S_BeginRegistration(); // all sound handles are now invalid + + cls.rendererStarted = qfalse; + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.soundRegistered = qfalse; + autoupdateChecked = qfalse; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + } else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + // initialize the renderer interface + CL_InitRef(); + + // startup all the client stuff + CL_StartHunkUsers(); + + // start the cgame if connected + if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { + cls.cgameStarted = qtrue; + CL_InitCGame(); + // send pure checksums + CL_SendPureChecksums(); + } +} + +/* +================= +CL_UI_Restart_f + +Restart the ui subsystem +================= +*/ +void CL_UI_Restart_f( void ) { // NERVE - SMF + // shutdown the UI + CL_ShutdownUI(); + + autoupdateChecked = qfalse; + + // init the UI + CL_InitUI(); +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem +The cgame and game must also be forced to restart because +handles will be invalid +================= +*/ +void CL_Snd_Restart_f( void ) { + S_Shutdown(); + S_Init(); + + CL_Vid_Restart_f(); +} + + +/* +================== +CL_PK3List_f +================== +*/ +void CL_OpenedPK3List_f( void ) { + Com_Printf( "Opened PK3 Names: %s\n", FS_LoadedPakNames() ); +} + +/* +================== +CL_PureList_f +================== +*/ +void CL_ReferencedPK3List_f( void ) { + Com_Printf( "Referenced PK3 Names: %s\n", FS_ReferencedPakNames() ); +} + +/* +================== +CL_Configstrings_f +================== +*/ +void CL_Configstrings_f( void ) { + int i; + int ofs; + + if ( cls.state != CA_ACTIVE ) { + Com_Printf( "Not connected to a server.\n" ); + return; + } + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + ofs = cl.gameState.stringOffsets[ i ]; + if ( !ofs ) { + continue; + } + Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); + } +} + +/* +============== +CL_Clientinfo_f +============== +*/ +void CL_Clientinfo_f( void ) { + Com_Printf( "--------- Client Information ---------\n" ); + Com_Printf( "state: %i\n", cls.state ); + Com_Printf( "Server: %s\n", cls.servername ); + Com_Printf( "User info settings:\n" ); + Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); + Com_Printf( "--------------------------------------\n" ); +} + + +//==================================================================== + +/* +================= +CL_DownloadsComplete + +Called when all downloading has been completed +================= +*/ +void CL_DownloadsComplete( void ) { + +#ifndef _WIN32 + char *fs_write_path; +#endif + char *fn; + + // DHM - Nerve :: Auto-update (not finished yet) + if ( autoupdateStarted ) { + + if ( autoupdateFilename && ( strlen( autoupdateFilename ) > 4 ) ) { +#ifdef _WIN32 + // win32's Sys_StartProcess prepends the current dir + fn = va( "%s/%s", FS_ShiftStr( AUTOUPDATE_DIR, AUTOUPDATE_DIR_SHIFT ), autoupdateFilename ); +#else + fs_write_path = Cvar_VariableString( "fs_homepath" ); + fn = FS_BuildOSPath( fs_write_path, FS_ShiftStr( AUTOUPDATE_DIR, AUTOUPDATE_DIR_SHIFT ), autoupdateFilename ); +#ifdef __linux__ + Sys_Chmod( fn, S_IXUSR ); +#endif +#endif + Sys_StartProcess( fn, qtrue ); + } + + autoupdateStarted = qfalse; + CL_Disconnect( qtrue ); + return; + } + + // if we downloaded files we need to restart the file system + if ( clc.downloadRestart ) { + clc.downloadRestart = qfalse; + + FS_Restart( clc.checksumFeed ); // We possibly downloaded a pak, restart the file system to load it + + // inform the server so we get new gamestate info + CL_AddReliableCommand( "donedl" ); + + // by sending the donedl command we request a new gamestate + // so we don't want to load stuff yet + return; + } + + // let the client game init and load data + cls.state = CA_LOADING; + + Com_EventLoop(); + + // if the gamestate was changed by calling Com_EventLoop + // then we loaded everything already and we don't want to do it again. + if ( cls.state != CA_LOADING ) { + return; + } + + // starting to load a map so we get out of full screen ui mode + Cvar_Set( "r_uiFullScreen", "0" ); + + // flush client memory and start loading stuff + // this will also (re)load the UI + // if this is a local client then only the client part of the hunk + // will be cleared, note that this is done after the hunk mark has been set + CL_FlushMemory(); + + // initialize the CGame + cls.cgameStarted = qtrue; + CL_InitCGame(); + + // set pure checksums + CL_SendPureChecksums(); + + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); +} + +/* +================= +CL_BeginDownload + +Requests a file to download from the server. Stores it in the current +game directory. +================= +*/ +void CL_BeginDownload( const char *localName, const char *remoteName ) { + + Com_DPrintf( "***** CL_BeginDownload *****\n" + "Localname: %s\n" + "Remotename: %s\n" + "****************************\n", localName, remoteName ); + + Q_strncpyz( clc.downloadName, localName, sizeof( clc.downloadName ) ); + Com_sprintf( clc.downloadTempName, sizeof( clc.downloadTempName ), "%s.tmp", localName ); + + // Set so UI gets access to it + Cvar_Set( "cl_downloadName", remoteName ); + Cvar_Set( "cl_downloadSize", "0" ); + Cvar_Set( "cl_downloadCount", "0" ); + Cvar_SetValue( "cl_downloadTime", cls.realtime ); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + CL_AddReliableCommand( va( "download %s", remoteName ) ); +} + +/* +================= +CL_NextDownload + +A download completed or failed +================= +*/ +void CL_NextDownload( void ) { + char *s; + char *remoteName, *localName; + + // We are looking to start a download here + if ( *clc.downloadList ) { + s = clc.downloadList; + + // format is: + // @remotename@localname@remotename@localname, etc. + + if ( *s == '@' ) { + s++; + } + remoteName = s; + + if ( ( s = strchr( s, '@' ) ) == NULL ) { + CL_DownloadsComplete(); + return; + } + + *s++ = 0; + localName = s; + if ( ( s = strchr( s, '@' ) ) != NULL ) { + *s++ = 0; + } else { + s = localName + strlen( localName ); // point at the nul byte + + } + CL_BeginDownload( localName, remoteName ); + + clc.downloadRestart = qtrue; + + // move over the rest + memmove( clc.downloadList, s, strlen( s ) + 1 ); + + return; + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_InitDownloads + +After receiving a valid game state, we valid the cgame and local zip files here +and determine if we need to download them +================= +*/ +void CL_InitDownloads( void ) { +#ifndef PRE_RELEASE_DEMO + char missingfiles[1024]; + char *dir = FS_ShiftStr( AUTOUPDATE_DIR, AUTOUPDATE_DIR_SHIFT ); + + if ( autoupdateStarted && NET_CompareAdr( cls.autoupdateServer, clc.serverAddress ) ) { + if ( strlen( cl_updatefiles->string ) > 4 ) { + Q_strncpyz( autoupdateFilename, cl_updatefiles->string, sizeof( autoupdateFilename ) ); + Q_strncpyz( clc.downloadList, va( "@%s/%s@%s/%s", dir, cl_updatefiles->string, dir, cl_updatefiles->string ), MAX_INFO_STRING ); + cls.state = CA_CONNECTED; + CL_NextDownload(); + return; + } + } else + { + // whatever autodownlad configuration, store missing files in a cvar, use later in the ui maybe + if ( FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) { + Cvar_Set( "com_missingFiles", missingfiles ); + } else { + Cvar_Set( "com_missingFiles", "" ); + } + + if ( cl_allowDownload->integer && FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ), qtrue ) ) { + // this gets printed to UI, i18n + Com_Printf( CL_TranslateStringBuf( "Need paks: %s\n" ), clc.downloadList ); + + if ( *clc.downloadList ) { + // if autodownloading is not enabled on the server + cls.state = CA_CONNECTED; + CL_NextDownload(); + return; + } + } + } + +#endif + + + CL_DownloadsComplete(); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +void CL_CheckForResend( void ) { + int port, i; + char info[MAX_INFO_STRING]; + char data[MAX_INFO_STRING]; + + // don't send anything if playing back a demo + if ( clc.demoplaying ) { + return; + } + + // resend if we haven't gotten a reply yet + if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { + return; + } + + if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { + return; + } + + clc.connectTime = cls.realtime; // for retransmit requests + clc.connectPacketCount++; + + + switch ( cls.state ) { + case CA_CONNECTING: + // requesting a challenge + if ( !Sys_IsLANAddress( clc.serverAddress ) ) { + CL_RequestAuthorization(); + } + NET_OutOfBandPrint( NS_CLIENT, clc.serverAddress, "getchallenge" ); + break; + + case CA_CHALLENGING: + // sending back the challenge + port = Cvar_VariableValue( "net_qport" ); + + Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); + Info_SetValueForKey( info, "protocol", va( "%i", PROTOCOL_VERSION ) ); + Info_SetValueForKey( info, "qport", va( "%i", port ) ); + Info_SetValueForKey( info, "challenge", va( "%i", clc.challenge ) ); + + strcpy( data, "connect " ); + + data[8] = '\"'; // NERVE - SMF - spaces in name bugfix + + for ( i = 0; i < strlen( info ); i++ ) { + data[9 + i] = info[i]; // + (clc.challenge)&0x3; + } + data[9 + i] = '\"'; // NERVE - SMF - spaces in name bugfix + data[10 + i] = 0; + + NET_OutOfBandData( NS_CLIENT, clc.serverAddress, &data[0], i + 10 ); + // the most current userinfo has been sent, so watch for any + // newer changes to userinfo variables + cvar_modifiedFlags &= ~CVAR_USERINFO; + break; + + default: + Com_Error( ERR_FATAL, "CL_CheckForResend: bad cls.state" ); + } +} + +/* +=================== +CL_DisconnectPacket + +Sometimes the server can drop the client and the netchan based +disconnect can be lost. If the client continues to send packets +to the server, the server will send out of band disconnect packets +to the client so it doesn't have to wait for the full timeout period. +=================== +*/ +void CL_DisconnectPacket( netadr_t from ) { + const char* message; + + if ( cls.state < CA_AUTHORIZING ) { + return; + } + + // if not from our server, ignore it + if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { + return; + } + + // if we have received packets within three seconds, ignore (it might be a malicious spoof) + // NOTE TTimo: + // there used to be a clc.lastPacketTime = cls.realtime; line in CL_PacketEvent before calling CL_ConnectionLessPacket + // therefore .. packets never got through this check, clients never disconnected + // switched the clc.lastPacketTime = cls.realtime to happen after the connectionless packets have been processed + // you still can't spoof disconnects, cause legal netchan packets will maintain realtime - lastPacketTime below the threshold + if ( cls.realtime - clc.lastPacketTime < 3000 ) { + return; + } + + // drop the connection + message = CL_TranslateStringBuf( "Server disconnected for unknown reason\n" ); + Com_Printf( message ); + Cvar_Set( "com_errorMessage", message ); + CL_Disconnect( qtrue ); +} + + +/* +=================== +CL_MotdPacket + +=================== +*/ +void CL_MotdPacket( netadr_t from ) { + char *challenge; + char *info; + + // if not from our server, ignore it + if ( !NET_CompareAdr( from, cls.updateServer ) ) { + return; + } + + info = Cmd_Argv( 1 ); + + // check challenge + challenge = Info_ValueForKey( info, "challenge" ); + if ( strcmp( challenge, cls.updateChallenge ) ) { + return; + } + + challenge = Info_ValueForKey( info, "motd" ); + + Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) ); + Cvar_Set( "cl_motdString", challenge ); +} + +/* +=================== +CL_PrintPackets +an OOB message from server, with potential markups +print OOB are the only messages we handle markups in +[err_dialog]: used to indicate that the connection should be aborted + no further information, just do an error diagnostic screen afterwards +[err_prot]: HACK. This is a protocol error. The client uses a custom + protocol error message (client sided) in the diagnostic window. + The space for the error message on the connection screen is limited + to 256 chars. +=================== +*/ +void CL_PrintPacket( netadr_t from, msg_t *msg ) { + char *s; + s = MSG_ReadBigString( msg ); + if ( !Q_stricmpn( s, "[err_dialog]", 12 ) ) { + Q_strncpyz( clc.serverMessage, s + 12, sizeof( clc.serverMessage ) ); + Cvar_Set( "com_errorMessage", clc.serverMessage ); + } else if ( !Q_stricmpn( s, "[err_prot]", 10 ) ) { + Q_strncpyz( clc.serverMessage, s + 10, sizeof( clc.serverMessage ) ); + Cvar_Set( "com_errorMessage", CL_TranslateStringBuf( PROTOCOL_MISMATCH_ERROR_LONG ) ); + } else { + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); + } + Com_Printf( "%s", clc.serverMessage ); +} + +/* +=================== +CL_InitServerInfo +=================== +*/ +void CL_InitServerInfo( serverInfo_t *server, serverAddress_t *address ) { + server->adr.type = NA_IP; + server->adr.ip[0] = address->ip[0]; + server->adr.ip[1] = address->ip[1]; + server->adr.ip[2] = address->ip[2]; + server->adr.ip[3] = address->ip[3]; + server->adr.port = address->port; + server->clients = 0; + server->hostName[0] = '\0'; + server->mapName[0] = '\0'; + server->maxClients = 0; + server->maxPing = 0; + server->minPing = 0; + server->ping = -1; + server->game[0] = '\0'; + server->gameType = 0; + server->netType = 0; + server->allowAnonymous = 0; +} + +#define MAX_SERVERSPERPACKET 256 + +/* +=================== +CL_ServersResponsePacket +=================== +*/ +void CL_ServersResponsePacket( netadr_t from, msg_t *msg ) { + int i, count, max, total; + serverAddress_t addresses[MAX_SERVERSPERPACKET]; + int numservers; + byte* buffptr; + byte* buffend; + + Com_Printf( "CL_ServersResponsePacket\n" ); + + if ( cls.numglobalservers == -1 ) { + // state to detect lack of servers or lack of response + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + } + + if ( cls.nummplayerservers == -1 ) { + cls.nummplayerservers = 0; + } + + // parse through server response string + numservers = 0; + buffptr = msg->data; + buffend = buffptr + msg->cursize; + while ( buffptr + 1 < buffend ) { + // advance to initial token + do { + if ( *buffptr++ == '\\' ) { + break; + } + } + while ( buffptr < buffend ); + + if ( buffptr >= buffend - 6 ) { + break; + } + + // parse out ip + addresses[numservers].ip[0] = *buffptr++; + addresses[numservers].ip[1] = *buffptr++; + addresses[numservers].ip[2] = *buffptr++; + addresses[numservers].ip[3] = *buffptr++; + + // parse out port + addresses[numservers].port = ( *buffptr++ ) << 8; + addresses[numservers].port += *buffptr++; + addresses[numservers].port = BigShort( addresses[numservers].port ); + + // syntax check + if ( *buffptr != '\\' ) { + break; + } + + Com_DPrintf( "server: %d ip: %d.%d.%d.%d:%d\n",numservers, + addresses[numservers].ip[0], + addresses[numservers].ip[1], + addresses[numservers].ip[2], + addresses[numservers].ip[3], + addresses[numservers].port ); + + numservers++; + if ( numservers >= MAX_SERVERSPERPACKET ) { + break; + } + + // parse out EOT + if ( buffptr[1] == 'E' && buffptr[2] == 'O' && buffptr[3] == 'T' ) { + break; + } + } + + if ( cls.masterNum == 0 ) { + count = cls.numglobalservers; + max = MAX_GLOBAL_SERVERS; + } else { + count = cls.nummplayerservers; + max = MAX_OTHER_SERVERS; + } + + for ( i = 0; i < numservers && count < max; i++ ) { + // build net address + serverInfo_t *server = ( cls.masterNum == 0 ) ? &cls.globalServers[count] : &cls.mplayerServers[count]; + + CL_InitServerInfo( server, &addresses[i] ); + // advance to next slot + count++; + } + + // if getting the global list + if ( cls.masterNum == 0 ) { + if ( cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) { + // if we couldn't store the servers in the main list anymore + for (; i < numservers && count >= max; i++ ) { + serverAddress_t *addr; + // just store the addresses in an additional list + addr = &cls.globalServerAddresses[cls.numGlobalServerAddresses++]; + addr->ip[0] = addresses[i].ip[0]; + addr->ip[1] = addresses[i].ip[1]; + addr->ip[2] = addresses[i].ip[2]; + addr->ip[3] = addresses[i].ip[3]; + addr->port = addresses[i].port; + } + } + } + + if ( cls.masterNum == 0 ) { + cls.numglobalservers = count; + total = count + cls.numGlobalServerAddresses; + } else { + cls.nummplayerservers = count; + total = count; + } + + Com_Printf( "%d servers parsed (total %d)\n", numservers, total ); +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv( 0 ); + + Com_DPrintf( "CL packet %s: %s\n", NET_AdrToString( from ), c ); + + // challenge from the server we are connecting to + if ( !Q_stricmp( c, "challengeResponse" ) ) { + if ( cls.state != CA_CONNECTING ) { + Com_Printf( "Unwanted challenge response received. Ignored.\n" ); + } else { + // start sending challenge repsonse instead of challenge request packets + clc.challenge = atoi( Cmd_Argv( 1 ) ); + if ( Cmd_Argc() > 2 ) { + clc.onlyVisibleClients = atoi( Cmd_Argv( 2 ) ); // DHM - Nerve + } else { + clc.onlyVisibleClients = 0; + } + cls.state = CA_CHALLENGING; + clc.connectPacketCount = 0; + clc.connectTime = -99999; + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + clc.serverAddress = from; + Com_DPrintf( "challenge: %d\n", clc.challenge ); + } + return; + } + + // server connection + if ( !Q_stricmp( c, "connectResponse" ) ) { + if ( cls.state >= CA_CONNECTED ) { + Com_Printf( "Dup connect received. Ignored.\n" ); + return; + } + if ( cls.state != CA_CHALLENGING ) { + Com_Printf( "connectResponse packet while not connecting. Ignored.\n" ); + return; + } + if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) { + Com_Printf( "connectResponse from a different address. Ignored.\n" ); + Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), + NET_AdrToString( clc.serverAddress ) ); + return; + } + + // DHM - Nerve :: If we have completed a connection to the Auto-Update server... + if ( autoupdateChecked && NET_CompareAdr( cls.autoupdateServer, clc.serverAddress ) ) { + // Mark the client as being in the process of getting an update + if ( cl_updateavailable->integer ) { + autoupdateStarted = qtrue; + } + } + + Netchan_Setup( NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); + cls.state = CA_CONNECTED; + clc.lastPacketSentTime = -9999; // send first packet immediately + return; + } + + // server responding to an info broadcast + if ( !Q_stricmp( c, "infoResponse" ) ) { + CL_ServerInfoPacket( from, msg ); + return; + } + + // server responding to a get playerlist + if ( !Q_stricmp( c, "statusResponse" ) ) { + CL_ServerStatusResponse( from, msg ); + return; + } + + // a disconnect message from the server, which will happen if the server + // dropped the connection but it is still getting packets from us + if ( !Q_stricmp( c, "disconnect" ) ) { + CL_DisconnectPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp( c, "echo" ) ) { + NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv( 1 ) ); + return; + } + + // cd check + if ( !Q_stricmp( c, "keyAuthorize" ) ) { + // we don't use these now, so dump them on the floor + return; + } + + // global MOTD from id + if ( !Q_stricmp( c, "motd" ) ) { + CL_MotdPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp( c, "print" ) ) { + CL_PrintPacket( from, msg ); + return; + } + + // DHM - Nerve :: Auto-update server response message + if ( !Q_stricmp( c, "updateResponse" ) ) { + CL_UpdateInfoPacket( from ); + return; + } + // DHM - Nerve + + // NERVE - SMF - bugfix, make this compare first n chars so it doesnt bail if token is parsed incorrectly + // echo request from server + if ( !Q_strncmp( c, "getserversResponse", 18 ) ) { + CL_ServersResponsePacket( from, msg ); + return; + } + + Com_DPrintf( "Unknown connectionless packet command.\n" ); +} + + +/* +================= +CL_PacketEvent + +A packet has arrived from the main event loop +================= +*/ +void CL_PacketEvent( netadr_t from, msg_t *msg ) { + int headerBytes; + + if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { + CL_ConnectionlessPacket( from, msg ); + return; + } + + clc.lastPacketTime = cls.realtime; + + if ( cls.state < CA_CONNECTED ) { + return; // can't be a valid sequenced packet + } + + if ( msg->cursize < 4 ) { + Com_Printf( "%s: Runt packet\n",NET_AdrToString( from ) ); + return; + } + + // + // packet from server + // + if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { + Com_DPrintf( "%s:sequenced packet without connection\n" + ,NET_AdrToString( from ) ); + // FIXME: send a client disconnect? + return; + } + + if ( !CL_Netchan_Process( &clc.netchan, msg ) ) { + return; // out of order, duplicated, etc + } + + // the header is different lengths for reliable and unreliable messages + headerBytes = msg->readcount; + + // track the last message received so it can be returned in + // client messages, allowing the server to detect a dropped + // gamestate + clc.serverMessageSequence = LittleLong( *(int *)msg->data ); + + clc.lastPacketTime = cls.realtime; + CL_ParseServerMessage( msg ); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // + if ( clc.demorecording && !clc.demowaiting ) { + CL_WriteDemoMessage( msg, headerBytes ); + } +} + +/* +================== +CL_CheckTimeout + +================== +*/ +void CL_CheckTimeout( void ) { + // + // check timeout + // + if ( ( !cl_paused->integer || !sv_paused->integer ) + && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC + && cls.realtime - clc.lastPacketTime > cl_timeout->value * 1000 ) { + if ( ++cl.timeoutcount > 5 ) { // timeoutcount saves debugger + Cvar_Set( "com_errorMessage", CL_TranslateStringBuf( "Server connection timed out." ) ); + CL_Disconnect( qtrue ); + return; + } + } else { + cl.timeoutcount = 0; + } +} + + +//============================================================================ + +/* +================== +CL_CheckUserinfo + +================== +*/ +void CL_CheckUserinfo( void ) { + // don't add reliable commands when not yet connected + if ( cls.state < CA_CHALLENGING ) { + return; + } + // don't overflow the reliable command buffer when paused + if ( cl_paused->integer ) { + return; + } + // send a reliable userinfo update if needed + if ( cvar_modifiedFlags & CVAR_USERINFO ) { + cvar_modifiedFlags &= ~CVAR_USERINFO; + CL_AddReliableCommand( va( "userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) ); + } +} + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame( int msec ) { + + if ( !com_cl_running->integer ) { + return; + } + + if ( cls.cddialog ) { + // bring up the cd error dialog if needed + cls.cddialog = qfalse; + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD ); + } else if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI ) + && !com_sv_running->integer ) { + // if disconnected, bring up the menu + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + + // if recording an avi, lock to a fixed fps + if ( cl_avidemo->integer && msec ) { + // save the current screen + if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer ) { + Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" ); + } + // fixed time for next frame + msec = ( 1000 / cl_avidemo->integer ) * com_timescale->value; + if ( msec == 0 ) { + msec = 1; + } + } + + // save the msec before checking pause + cls.realFrametime = msec; + + // decide the simulation time + cls.frametime = msec; + + cls.realtime += cls.frametime; + + if ( cl_timegraph->integer ) { + SCR_DebugGraph( cls.realFrametime * 0.25, 0 ); + } + + // see if we need to update any userinfo + CL_CheckUserinfo(); + + // if we haven't gotten a packet in a long time, + // drop the connection + CL_CheckTimeout(); + + // send intentions now + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // decide on the serverTime to render + CL_SetCGameTime(); + + // update the screen + SCR_UpdateScreen(); + + // update the sound + S_Update(); + + // advance local effects for next frame + SCR_RunCinematic(); + + Con_RunConsole(); + + cls.framecount++; +} + + +//============================================================================ +// Ridah, startup-caching system +typedef struct { + char name[MAX_QPATH]; + int hits; + int lastSetIndex; +} cacheItem_t; +typedef enum { + CACHE_SOUNDS, + CACHE_MODELS, + CACHE_IMAGES, + + CACHE_NUMGROUPS +} cacheGroup_t; +static cacheItem_t cacheGroups[CACHE_NUMGROUPS] = { + {{'s','o','u','n','d',0}, CACHE_SOUNDS}, + {{'m','o','d','e','l',0}, CACHE_MODELS}, + {{'i','m','a','g','e',0}, CACHE_IMAGES}, +}; +#define MAX_CACHE_ITEMS 4096 +#define CACHE_HIT_RATIO 0.75 // if hit on this percentage of maps, it'll get cached + +static int cacheIndex; +static cacheItem_t cacheItems[CACHE_NUMGROUPS][MAX_CACHE_ITEMS]; + +static void CL_Cache_StartGather_f( void ) { + cacheIndex = 0; + memset( cacheItems, 0, sizeof( cacheItems ) ); + + Cvar_Set( "cl_cacheGathering", "1" ); +} + +static void CL_Cache_UsedFile_f( void ) { + char groupStr[MAX_QPATH]; + char itemStr[MAX_QPATH]; + int i,group; + cacheItem_t *item; + + if ( Cmd_Argc() < 2 ) { + Com_Error( ERR_DROP, "usedfile without enough parameters\n" ); + return; + } + + strcpy( groupStr, Cmd_Argv( 1 ) ); + + strcpy( itemStr, Cmd_Argv( 2 ) ); + for ( i = 3; i < Cmd_Argc(); i++ ) { + strcat( itemStr, " " ); + strcat( itemStr, Cmd_Argv( i ) ); + } + Q_strlwr( itemStr ); + + // find the cache group + for ( i = 0; i < CACHE_NUMGROUPS; i++ ) { + if ( !Q_strncmp( groupStr, cacheGroups[i].name, MAX_QPATH ) ) { + break; + } + } + if ( i == CACHE_NUMGROUPS ) { + Com_Error( ERR_DROP, "usedfile without a valid cache group\n" ); + return; + } + + // see if it's already there + group = i; + for ( i = 0, item = cacheItems[group]; i < MAX_CACHE_ITEMS; i++, item++ ) { + if ( !item->name[0] ) { + // didn't find it, so add it here + Q_strncpyz( item->name, itemStr, MAX_QPATH ); + if ( cacheIndex > 9999 ) { // hack, but yeh + item->hits = cacheIndex; + } else { + item->hits++; + } + item->lastSetIndex = cacheIndex; + break; + } + if ( item->name[0] == itemStr[0] && !Q_strncmp( item->name, itemStr, MAX_QPATH ) ) { + if ( item->lastSetIndex != cacheIndex ) { + item->hits++; + item->lastSetIndex = cacheIndex; + } + break; + } + } +} + +static void CL_Cache_SetIndex_f( void ) { + if ( Cmd_Argc() < 2 ) { + Com_Error( ERR_DROP, "setindex needs an index\n" ); + return; + } + + cacheIndex = atoi( Cmd_Argv( 1 ) ); +} + +static void CL_Cache_MapChange_f( void ) { + cacheIndex++; +} + +static void CL_Cache_EndGather_f( void ) { + // save the frequently used files to the cache list file + int i, j, handle, cachePass; + char filename[MAX_QPATH]; + + cachePass = (int)floor( (float)cacheIndex * CACHE_HIT_RATIO ); + +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'WlfB'; + _fcreator = 'WlfM'; + } +#endif + for ( i = 0; i < CACHE_NUMGROUPS; i++ ) { + Q_strncpyz( filename, cacheGroups[i].name, MAX_QPATH ); + Q_strcat( filename, MAX_QPATH, ".cache" ); + + handle = FS_FOpenFileWrite( filename ); + + for ( j = 0; j < MAX_CACHE_ITEMS; j++ ) { + // if it's a valid filename, and it's been hit enough times, cache it + if ( cacheItems[i][j].hits >= cachePass && strstr( cacheItems[i][j].name, "/" ) ) { + FS_Write( cacheItems[i][j].name, strlen( cacheItems[i][j].name ), handle ); + FS_Write( "\n", 1, handle ); + } + } + + FS_FCloseFile( handle ); + } + + Cvar_Set( "cl_cacheGathering", "0" ); +} + +// done. +//============================================================================ + +/* +================ +CL_SetRecommended_f +================ +*/ +void CL_SetRecommended_f( void ) { + Com_SetRecommended(); +} + + + +/* +================ +CL_RefPrintf + +DLL glue +================ +*/ +void QDECL CL_RefPrintf( int print_level, const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start( argptr,fmt ); + Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ); + va_end( argptr ); + + if ( print_level == PRINT_ALL ) { + Com_Printf( "%s", msg ); + } else if ( print_level == PRINT_WARNING ) { + Com_Printf( S_COLOR_YELLOW "%s", msg ); // yellow + } else if ( print_level == PRINT_DEVELOPER ) { + Com_DPrintf( S_COLOR_RED "%s", msg ); // red + } +} + + + +/* +============ +CL_ShutdownRef +============ +*/ +void CL_ShutdownRef( void ) { + if ( !re.Shutdown ) { + return; + } + re.Shutdown( qtrue ); + memset( &re, 0, sizeof( re ) ); +} + +/* +============ +CL_InitRenderer +============ +*/ +void CL_InitRenderer( void ) { + // this sets up the renderer and calls R_Init + re.BeginRegistration( &cls.glconfig ); + + // load character sets + cls.charSetShader = re.RegisterShader( "gfx/2d/hudchars" ); + cls.whiteShader = re.RegisterShader( "white" ); + +// JPW NERVE + + cls.consoleShader = re.RegisterShader( "console-16bit" ); // JPW NERVE shader works with 16bit + cls.consoleShader2 = re.RegisterShader( "console2-16bit" ); // JPW NERVE same + + g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; + g_consoleField.widthInChars = g_console_field_width; +} + +/* +============================ +CL_StartHunkUsers + +After the server has cleared the hunk, these will need to be restarted +This is the only place that any of these functions are called from +============================ +*/ +void CL_StartHunkUsers( void ) { + if ( !com_cl_running ) { + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + if ( !cls.rendererStarted ) { + cls.rendererStarted = qtrue; + CL_InitRenderer(); + } + + if ( !cls.soundStarted ) { + cls.soundStarted = qtrue; + S_Init(); + } + + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; + S_BeginRegistration(); + } + + if ( !cls.uiStarted ) { + cls.uiStarted = qtrue; + CL_InitUI(); + } +} + +// DHM - Nerve +void CL_CheckAutoUpdate( void ) { + int validServerNum = 0; + int i = 0, rnd = 0; + netadr_t temp; + char *servername; + + if ( !cl_autoupdate->integer ) { + return; + } + + // Only check once per session + if ( autoupdateChecked ) { + return; + } + + srand( Com_Milliseconds() ); + + // Find out how many update servers have valid DNS listings + for ( i = 0; i < MAX_AUTOUPDATE_SERVERS; i++ ) { + if ( NET_StringToAdr( cls.autoupdateServerNames[i], &temp ) ) { + validServerNum++; + } + } + + // Pick a random server + if ( validServerNum > 1 ) { + rnd = rand() % validServerNum; + } else { + rnd = 0; + } + + servername = cls.autoupdateServerNames[rnd]; + + Com_DPrintf( "Resolving AutoUpdate Server... " ); + if ( !NET_StringToAdr( servername, &cls.autoupdateServer ) ) { + Com_DPrintf( "Couldn't resolve first address, trying default..." ); + + // Fall back to the first one + if ( !NET_StringToAdr( cls.autoupdateServerNames[0], &cls.autoupdateServer ) ) { + Com_DPrintf( "Failed to resolve any Auto-update servers.\n" ); + autoupdateChecked = qtrue; + return; + } + } + cls.autoupdateServer.port = BigShort( PORT_SERVER ); + Com_DPrintf( "%i.%i.%i.%i:%i\n", cls.autoupdateServer.ip[0], cls.autoupdateServer.ip[1], + cls.autoupdateServer.ip[2], cls.autoupdateServer.ip[3], + BigShort( cls.autoupdateServer.port ) ); + + NET_OutOfBandPrint( NS_CLIENT, cls.autoupdateServer, "getUpdateInfo \"%s\" \"%s\"\n", Q3_VERSION, CPUSTRING ); + + CL_RequestMotd(); + + autoupdateChecked = qtrue; +} + +void CL_GetAutoUpdate( void ) { + + // Don't try and get an update if we haven't checked for one + if ( !autoupdateChecked ) { + return; + } + + // Make sure there's a valid update file to request + if ( strlen( cl_updatefiles->string ) < 5 ) { + return; + } + + Com_DPrintf( "Connecting to auto-update server...\n" ); + + S_StopAllSounds(); // NERVE - SMF + + // starting to load a map so we get out of full screen ui mode + Cvar_Set( "r_uiFullScreen", "0" ); + + // clear any previous "server full" type messages + clc.serverMessage[0] = 0; + + if ( com_sv_running->integer ) { + // if running a local server, kill it + SV_Shutdown( "Server quit\n" ); + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + SV_Frame( 0 ); + + CL_Disconnect( qtrue ); + Con_Close(); + + Q_strncpyz( cls.servername, "Auto-Updater", sizeof( cls.servername ) ); + + if ( cls.autoupdateServer.type == NA_BAD ) { + Com_Printf( "Bad server address\n" ); + cls.state = CA_DISCONNECTED; + return; + } + + // Copy auto-update server address to Server connect address + memcpy( &clc.serverAddress, &cls.autoupdateServer, sizeof( netadr_t ) ); + + Com_DPrintf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername, + clc.serverAddress.ip[0], clc.serverAddress.ip[1], + clc.serverAddress.ip[2], clc.serverAddress.ip[3], + BigShort( clc.serverAddress.port ) ); + + cls.state = CA_CONNECTING; + + cls.keyCatchers = 0; + clc.connectTime = -99999; // CL_CheckForResend() will fire immediately + clc.connectPacketCount = 0; + + // server connection string + Cvar_Set( "cl_currentServerAddress", "Auto-Updater" ); +} +// DHM - Nerve + +/* +============ +CL_RefMalloc +============ +*/ +#ifdef ZONE_DEBUG +void *CL_RefMallocDebug( int size, char *label, char *file, int line ) { + return Z_TagMallocDebug( size, TAG_RENDERER, label, file, line ); +} +#else +void *CL_RefMalloc( int size ) { + return Z_TagMalloc( size, TAG_RENDERER ); +} +#endif + +/* +============ +CL_RefTagFree +============ +*/ +void CL_RefTagFree( void ) { + Z_FreeTags( TAG_RENDERER ); + return; +} + +int CL_ScaledMilliseconds( void ) { + return Sys_Milliseconds() * com_timescale->value; +} + +/* +============ +CL_InitRef +============ +*/ +void CL_InitRef( void ) { + refimport_t ri; + refexport_t *ret; + + Com_Printf( "----- Initializing Renderer ----\n" ); + + ri.Cmd_AddCommand = Cmd_AddCommand; + ri.Cmd_RemoveCommand = Cmd_RemoveCommand; + ri.Cmd_Argc = Cmd_Argc; + ri.Cmd_Argv = Cmd_Argv; + ri.Cmd_ExecuteText = Cbuf_ExecuteText; + ri.Printf = CL_RefPrintf; + ri.Error = Com_Error; + ri.Milliseconds = CL_ScaledMilliseconds; +#ifdef ZONE_DEBUG + ri.Z_MallocDebug = CL_RefMallocDebug; +#else + ri.Z_Malloc = CL_RefMalloc; +#endif + ri.Free = Z_Free; + ri.Tag_Free = CL_RefTagFree; + ri.Hunk_Clear = Hunk_ClearToMark; +#ifdef HUNK_DEBUG + ri.Hunk_AllocDebug = Hunk_AllocDebug; +#else + ri.Hunk_Alloc = Hunk_Alloc; +#endif + ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; + ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; + ri.CM_DrawDebugSurface = CM_DrawDebugSurface; + ri.FS_ReadFile = FS_ReadFile; + ri.FS_FreeFile = FS_FreeFile; + ri.FS_WriteFile = FS_WriteFile; + ri.FS_FreeFileList = FS_FreeFileList; + ri.FS_ListFiles = FS_ListFiles; + ri.FS_FileIsInPAK = FS_FileIsInPAK; + ri.FS_FileExists = FS_FileExists; + ri.Cvar_Get = Cvar_Get; + ri.Cvar_Set = Cvar_Set; + + // cinematic stuff + + ri.CIN_UploadCinematic = CIN_UploadCinematic; + ri.CIN_PlayCinematic = CIN_PlayCinematic; + ri.CIN_RunCinematic = CIN_RunCinematic; + + ret = GetRefAPI( REF_API_VERSION, &ri ); + +#if 0 // MrE defined __USEA3D && defined __A3D_GEOM + hA3Dg_ExportRenderGeom( ret ); +#endif + + Com_Printf( "-------------------------------\n" ); + + if ( !ret ) { + Com_Error( ERR_FATAL, "Couldn't initialize refresh" ); + } + + re = *ret; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); +} + +// RF, trap manual client damage commands so users can't issue them manually +void CL_ClientDamageCommand( void ) { + // do nothing +} + +// NERVE - SMF +void CL_startSingleplayer_f( void ) { +#if defined( __linux__ ) + Sys_StartProcess( "./wolfsp.x86", qtrue ); +#else + Sys_StartProcess( "WolfSP.exe", qtrue ); +#endif +} + +// NERVE - SMF +void CL_buyNow_f( void ) { + Sys_OpenURL( "http://www.activision.com/games/wolfenstein/purchase.html", qtrue ); +} + +// NERVE - SMF +void CL_singlePlayLink_f( void ) { + Sys_OpenURL( "http://www.activision.com/games/wolfenstein/home.html", qtrue ); +} + +#if !defined( __MACOS__ ) +void CL_SaveTranslations_f( void ) { + CL_SaveTransTable( "scripts/translation.cfg", qfalse ); +} + +void CL_SaveNewTranslations_f( void ) { + char fileName[512]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: SaveNewTranslations \n" ); + return; + } + + strcpy( fileName, va( "translations/%s.cfg", Cmd_Argv( 1 ) ) ); + + CL_SaveTransTable( fileName, qtrue ); +} + +void CL_LoadTranslations_f( void ) { + CL_ReloadTranslation(); +} +// -NERVE - SMF +#endif + +//=========================================================================================== + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) { + Com_Printf( "----- Client Initialization -----\n" ); + + Con_Init(); + + CL_ClearState(); + + cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + + cls.realtime = 0; + + CL_InitInput(); + + // + // register our variables + // + cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); + cl_motd = Cvar_Get( "cl_motd", "1", 0 ); + cl_autoupdate = Cvar_Get( "cl_autoupdate", "1", CVAR_ARCHIVE ); + + cl_timeout = Cvar_Get( "cl_timeout", "200", 0 ); + + cl_wavefilerecord = Cvar_Get( "cl_wavefilerecord", "0", CVAR_TEMP ); + + cl_timeNudge = Cvar_Get( "cl_timeNudge", "0", CVAR_TEMP ); + cl_shownet = Cvar_Get( "cl_shownet", "0", CVAR_TEMP ); + cl_shownuments = Cvar_Get( "cl_shownuments", "0", CVAR_TEMP ); + cl_visibleClients = Cvar_Get( "cl_visibleClients", "0", CVAR_TEMP ); + cl_showServerCommands = Cvar_Get( "cl_showServerCommands", "0", 0 ); + cl_showSend = Cvar_Get( "cl_showSend", "0", CVAR_TEMP ); + cl_showTimeDelta = Cvar_Get( "cl_showTimeDelta", "0", CVAR_TEMP ); + cl_freezeDemo = Cvar_Get( "cl_freezeDemo", "0", CVAR_TEMP ); + rcon_client_password = Cvar_Get( "rconPassword", "", CVAR_TEMP ); + cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); + + cl_timedemo = Cvar_Get( "timedemo", "0", 0 ); + cl_avidemo = Cvar_Get( "cl_avidemo", "0", 0 ); + cl_forceavidemo = Cvar_Get( "cl_forceavidemo", "0", 0 ); + + rconAddress = Cvar_Get( "rconAddress", "", 0 ); + + cl_yawspeed = Cvar_Get( "cl_yawspeed", "140", CVAR_ARCHIVE ); + cl_pitchspeed = Cvar_Get( "cl_pitchspeed", "140", CVAR_ARCHIVE ); + cl_anglespeedkey = Cvar_Get( "cl_anglespeedkey", "1.5", 0 ); + + cl_maxpackets = Cvar_Get( "cl_maxpackets", "30", CVAR_ARCHIVE ); + cl_packetdup = Cvar_Get( "cl_packetdup", "1", CVAR_ARCHIVE ); + + cl_run = Cvar_Get( "cl_run", "1", CVAR_ARCHIVE ); + cl_sensitivity = Cvar_Get( "sensitivity", "5", CVAR_ARCHIVE ); + cl_mouseAccel = Cvar_Get( "cl_mouseAccel", "0", CVAR_ARCHIVE ); + cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); + + cl_showMouseRate = Cvar_Get( "cl_showmouserate", "0", 0 ); + + cl_allowDownload = Cvar_Get( "cl_allowDownload", "0", CVAR_ARCHIVE ); + + // init autoswitch so the ui will have it correctly even + // if the cgame hasn't been started + // -NERVE - SMF - disabled autoswitch by default + Cvar_Get( "cg_autoswitch", "0", CVAR_ARCHIVE ); + + // Rafael - particle switch + Cvar_Get( "cg_wolfparticles", "1", CVAR_ARCHIVE ); + // done + + cl_conXOffset = Cvar_Get( "cl_conXOffset", "0", 0 ); + cl_inGameVideo = Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE ); + + cl_serverStatusResendTime = Cvar_Get( "cl_serverStatusResendTime", "750", 0 ); + + // RF + cl_recoilPitch = Cvar_Get( "cg_recoilPitch", "0", CVAR_ROM ); + + cl_bypassMouseInput = Cvar_Get( "cl_bypassMouseInput", "0", 0 ); //CVAR_ROM ); // NERVE - SMF + + m_pitch = Cvar_Get( "m_pitch", "0.022", CVAR_ARCHIVE ); + m_yaw = Cvar_Get( "m_yaw", "0.022", CVAR_ARCHIVE ); + m_forward = Cvar_Get( "m_forward", "0.25", CVAR_ARCHIVE ); + m_side = Cvar_Get( "m_side", "0.25", CVAR_ARCHIVE ); + m_filter = Cvar_Get( "m_filter", "0", CVAR_ARCHIVE ); + + cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); + + Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); + + // NERVE - SMF + Cvar_Get( "cg_drawCompass", "1", CVAR_ARCHIVE ); + Cvar_Get( "cg_drawNotifyText", "1", CVAR_ARCHIVE ); + Cvar_Get( "cg_quickMessageAlt", "1", CVAR_ARCHIVE ); + Cvar_Get( "cg_popupLimboMenu", "1", CVAR_ARCHIVE ); + Cvar_Get( "cg_descriptiveText", "1", CVAR_ARCHIVE ); + Cvar_Get( "cg_drawTeamOverlay", "2", CVAR_ARCHIVE ); + Cvar_Get( "cg_uselessNostalgia", "0", CVAR_ARCHIVE ); // JPW NERVE + Cvar_Get( "cg_drawGun", "1", CVAR_ARCHIVE ); + Cvar_Get( "cg_cursorHints", "1", CVAR_ARCHIVE ); + Cvar_Get( "cg_voiceSpriteTime", "6000", CVAR_ARCHIVE ); + Cvar_Get( "cg_teamChatsOnly", "0", CVAR_ARCHIVE ); + Cvar_Get( "cg_noVoiceChats", "0", CVAR_ARCHIVE ); + Cvar_Get( "cg_noVoiceText", "0", CVAR_ARCHIVE ); + Cvar_Get( "cg_crosshairSize", "48", CVAR_ARCHIVE ); + Cvar_Get( "cg_drawCrosshair", "1", CVAR_ARCHIVE ); + Cvar_Get( "cg_zoomDefaultSniper", "20", CVAR_ARCHIVE ); + Cvar_Get( "cg_zoomstepsniper", "2", CVAR_ARCHIVE ); + + Cvar_Get( "mp_playerType", "0", 0 ); + Cvar_Get( "mp_currentPlayerType", "0", 0 ); + Cvar_Get( "mp_weapon", "0", 0 ); + Cvar_Get( "mp_team", "0", 0 ); + Cvar_Get( "mp_currentTeam", "0", 0 ); + // -NERVE - SMF + + // userinfo + Cvar_Get( "name", "WolfPlayer", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get( "rate", "5000", CVAR_USERINFO | CVAR_ARCHIVE ); // NERVE - SMF - changed from 3000 + Cvar_Get( "snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); +// Cvar_Get ("model", "american", CVAR_USERINFO | CVAR_ARCHIVE ); // temp until we have an skeletal american model + Cvar_Get( "model", "multi", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get( "head", "default", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get( "color", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get( "handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get( "sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get( "cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); + + Cvar_Get( "password", "", CVAR_USERINFO ); + Cvar_Get( "cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); + +//----(SA) added + Cvar_Get( "cg_autoactivate", "1", CVAR_USERINFO | CVAR_ARCHIVE ); +//----(SA) end + + // cgame might not be initialized before menu is used + Cvar_Get( "cg_viewsize", "100", CVAR_ARCHIVE ); + + Cvar_Get( "cg_autoReload", "1", CVAR_ARCHIVE | CVAR_USERINFO ); + + cl_missionStats = Cvar_Get( "g_missionStats", "0", CVAR_ROM ); + cl_waitForFire = Cvar_Get( "cl_waitForFire", "0", CVAR_ROM ); + + // NERVE - SMF - localization + cl_language = Cvar_Get( "cl_language", "0", CVAR_ARCHIVE ); + cl_debugTranslation = Cvar_Get( "cl_debugTranslation", "0", 0 ); + // -NERVE - SMF + + // DHM - Nerve :: Auto-update + cl_updateavailable = Cvar_Get( "cl_updateavailable", "0", CVAR_ROM ); + cl_updatefiles = Cvar_Get( "cl_updatefiles", "", CVAR_ROM ); + + Q_strncpyz( cls.autoupdateServerNames[0], AUTOUPDATE_SERVER1_NAME, MAX_QPATH ); + Q_strncpyz( cls.autoupdateServerNames[1], AUTOUPDATE_SERVER2_NAME, MAX_QPATH ); + Q_strncpyz( cls.autoupdateServerNames[2], AUTOUPDATE_SERVER3_NAME, MAX_QPATH ); + Q_strncpyz( cls.autoupdateServerNames[3], AUTOUPDATE_SERVER4_NAME, MAX_QPATH ); + Q_strncpyz( cls.autoupdateServerNames[4], AUTOUPDATE_SERVER5_NAME, MAX_QPATH ); + // DHM - Nerve + + // + // register our commands + // + Cmd_AddCommand( "cmd", CL_ForwardToServer_f ); + Cmd_AddCommand( "configstrings", CL_Configstrings_f ); + Cmd_AddCommand( "clientinfo", CL_Clientinfo_f ); + Cmd_AddCommand( "snd_restart", CL_Snd_Restart_f ); + Cmd_AddCommand( "vid_restart", CL_Vid_Restart_f ); + Cmd_AddCommand( "ui_restart", CL_UI_Restart_f ); // NERVE - SMF + Cmd_AddCommand( "disconnect", CL_Disconnect_f ); + Cmd_AddCommand( "record", CL_Record_f ); + Cmd_AddCommand( "demo", CL_PlayDemo_f ); + Cmd_AddCommand( "cinematic", CL_PlayCinematic_f ); + Cmd_AddCommand( "stoprecord", CL_StopRecord_f ); + Cmd_AddCommand( "connect", CL_Connect_f ); + Cmd_AddCommand( "reconnect", CL_Reconnect_f ); + Cmd_AddCommand( "localservers", CL_LocalServers_f ); + Cmd_AddCommand( "globalservers", CL_GlobalServers_f ); + Cmd_AddCommand( "rcon", CL_Rcon_f ); + Cmd_AddCommand( "setenv", CL_Setenv_f ); + Cmd_AddCommand( "ping", CL_Ping_f ); + Cmd_AddCommand( "serverstatus", CL_ServerStatus_f ); + Cmd_AddCommand( "showip", CL_ShowIP_f ); + Cmd_AddCommand( "fs_openedList", CL_OpenedPK3List_f ); + Cmd_AddCommand( "fs_referencedList", CL_ReferencedPK3List_f ); + + // Ridah, startup-caching system + Cmd_AddCommand( "cache_startgather", CL_Cache_StartGather_f ); + Cmd_AddCommand( "cache_usedfile", CL_Cache_UsedFile_f ); + Cmd_AddCommand( "cache_setindex", CL_Cache_SetIndex_f ); + Cmd_AddCommand( "cache_mapchange", CL_Cache_MapChange_f ); + Cmd_AddCommand( "cache_endgather", CL_Cache_EndGather_f ); + + Cmd_AddCommand( "updatehunkusage", CL_UpdateLevelHunkUsage ); + Cmd_AddCommand( "updatescreen", SCR_UpdateScreen ); + // done. +#ifndef __MACOS__ //DAJ USA + Cmd_AddCommand( "SaveTranslations", CL_SaveTranslations_f ); // NERVE - SMF - localization + Cmd_AddCommand( "SaveNewTranslations", CL_SaveNewTranslations_f ); // NERVE - SMF - localization + Cmd_AddCommand( "LoadTranslations", CL_LoadTranslations_f ); // NERVE - SMF - localization +#endif + // NERVE - SMF - don't do this in multiplayer + // RF, add this command so clients can't bind a key to send client damage commands to the server +// Cmd_AddCommand ("cld", CL_ClientDamageCommand ); + + Cmd_AddCommand( "startSingleplayer", CL_startSingleplayer_f ); // NERVE - SMF + Cmd_AddCommand( "buyNow", CL_buyNow_f ); // NERVE - SMF + Cmd_AddCommand( "singlePlayLink", CL_singlePlayLink_f ); // NERVE - SMF + + Cmd_AddCommand( "setRecommended", CL_SetRecommended_f ); + + CL_InitRef(); + + SCR_Init(); + + Cbuf_Execute(); + + Cvar_Set( "cl_running", "1" ); + + // DHM - Nerve + autoupdateChecked = qfalse; + autoupdateStarted = qfalse; + +#ifndef __MACOS__ //DAJ USA + CL_InitTranslation(); // NERVE - SMF - localization +#endif + + Com_Printf( "----- Client Initialization Complete -----\n" ); +} + + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown( void ) { + static qboolean recursive = qfalse; + + Com_Printf( "----- CL_Shutdown -----\n" ); + + if ( recursive ) { + printf( "recursive shutdown\n" ); + return; + } + recursive = qtrue; + + CL_Disconnect( qtrue ); + + S_Shutdown(); + CL_ShutdownRef(); + + CL_ShutdownUI(); + + Cmd_RemoveCommand( "cmd" ); + Cmd_RemoveCommand( "configstrings" ); + Cmd_RemoveCommand( "userinfo" ); + Cmd_RemoveCommand( "snd_restart" ); + Cmd_RemoveCommand( "vid_restart" ); + Cmd_RemoveCommand( "disconnect" ); + Cmd_RemoveCommand( "record" ); + Cmd_RemoveCommand( "demo" ); + Cmd_RemoveCommand( "cinematic" ); + Cmd_RemoveCommand( "stoprecord" ); + Cmd_RemoveCommand( "connect" ); + Cmd_RemoveCommand( "localservers" ); + Cmd_RemoveCommand( "globalservers" ); + Cmd_RemoveCommand( "rcon" ); + Cmd_RemoveCommand( "setenv" ); + Cmd_RemoveCommand( "ping" ); + Cmd_RemoveCommand( "serverstatus" ); + Cmd_RemoveCommand( "showip" ); + Cmd_RemoveCommand( "model" ); + + // Ridah, startup-caching system + Cmd_RemoveCommand( "cache_startgather" ); + Cmd_RemoveCommand( "cache_usedfile" ); + Cmd_RemoveCommand( "cache_setindex" ); + Cmd_RemoveCommand( "cache_mapchange" ); + Cmd_RemoveCommand( "cache_endgather" ); + + Cmd_RemoveCommand( "updatehunkusage" ); + // done. + + Cvar_Set( "cl_running", "0" ); + + recursive = qfalse; + + memset( &cls, 0, sizeof( cls ) ); + + Com_Printf( "-----------------------\n" ); +} + + +static void CL_SetServerInfo( serverInfo_t *server, const char *info, int ping ) { + if ( server ) { + if ( info ) { + server->clients = atoi( Info_ValueForKey( info, "clients" ) ); + Q_strncpyz( server->hostName,Info_ValueForKey( info, "hostname" ), MAX_NAME_LENGTH ); + Q_strncpyz( server->mapName, Info_ValueForKey( info, "mapname" ), MAX_NAME_LENGTH ); + server->maxClients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + Q_strncpyz( server->game,Info_ValueForKey( info, "game" ), MAX_NAME_LENGTH ); + server->gameType = atoi( Info_ValueForKey( info, "gametype" ) ); + server->netType = atoi( Info_ValueForKey( info, "nettype" ) ); + server->minPing = atoi( Info_ValueForKey( info, "minping" ) ); + server->maxPing = atoi( Info_ValueForKey( info, "maxping" ) ); + server->allowAnonymous = atoi( Info_ValueForKey( info, "sv_allowAnonymous" ) ); + server->friendlyFire = atoi( Info_ValueForKey( info, "friendlyFire" ) ); // NERVE - SMF + server->maxlives = atoi( Info_ValueForKey( info, "maxlives" ) ); // NERVE - SMF + server->tourney = atoi( Info_ValueForKey( info, "tourney" ) ); // NERVE - SMF + server->punkbuster = atoi( Info_ValueForKey( info, "punkbuster" ) ); // DHM - Nerve + Q_strncpyz( server->gameName, Info_ValueForKey( info, "gamename" ), MAX_NAME_LENGTH ); // Arnout + server->antilag = atoi( Info_ValueForKey( info, "g_antilag" ) ); + } + server->ping = ping; + } +} + +static void CL_SetServerInfoByAddress( netadr_t from, const char *info, int ping ) { + int i; + + for ( i = 0; i < MAX_OTHER_SERVERS; i++ ) { + if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) { + CL_SetServerInfo( &cls.localServers[i], info, ping ); + } + } + + for ( i = 0; i < MAX_OTHER_SERVERS; i++ ) { + if ( NET_CompareAdr( from, cls.mplayerServers[i].adr ) ) { + CL_SetServerInfo( &cls.mplayerServers[i], info, ping ); + } + } + + for ( i = 0; i < MAX_GLOBAL_SERVERS; i++ ) { + if ( NET_CompareAdr( from, cls.globalServers[i].adr ) ) { + CL_SetServerInfo( &cls.globalServers[i], info, ping ); + } + } + + for ( i = 0; i < MAX_OTHER_SERVERS; i++ ) { + if ( NET_CompareAdr( from, cls.favoriteServers[i].adr ) ) { + CL_SetServerInfo( &cls.favoriteServers[i], info, ping ); + } + } + +} + +/* +=================== +CL_ServerInfoPacket +=================== +*/ +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { + int i, type; + char info[MAX_INFO_STRING]; + char* str; + char *infoString; + int prot; + char *gameName; + + infoString = MSG_ReadString( msg ); + + // Arnout: if this isn't the correct game, ignore it + gameName = Info_ValueForKey( infoString, "gamename" ); + if ( !gameName[0] || Q_stricmp( gameName, GAMENAME_STRING ) ) { + Com_DPrintf( "Different game info packet: %s\n", infoString ); + return; + } + + // if this isn't the correct protocol version, ignore it + prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); + if ( prot != PROTOCOL_VERSION ) { + Com_DPrintf( "Different protocol info packet: %s\n", infoString ); + return; + } + + // iterate servers waiting for ping response + for ( i = 0; i < MAX_PINGREQUESTS; i++ ) + { + if ( cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr( from, cl_pinglist[i].adr ) ) { + // calc ping time + cl_pinglist[i].time = cls.realtime - cl_pinglist[i].start + 1; + Com_DPrintf( "ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString( from ) ); + + // save of info + Q_strncpyz( cl_pinglist[i].info, infoString, sizeof( cl_pinglist[i].info ) ); + + // tack on the net type + // NOTE: make sure these types are in sync with the netnames strings in the UI + switch ( from.type ) + { + case NA_BROADCAST: + case NA_IP: + str = "udp"; + type = 1; + break; + + case NA_IPX: + case NA_BROADCAST_IPX: + str = "ipx"; + type = 2; + break; + + default: + str = "???"; + type = 0; + break; + } + Info_SetValueForKey( cl_pinglist[i].info, "nettype", va( "%d", type ) ); + CL_SetServerInfoByAddress( from, infoString, cl_pinglist[i].time ); + + return; + } + } + + // if not just sent a local broadcast or pinging local servers + if ( cls.pingUpdateSource != AS_LOCAL ) { + return; + } + + for ( i = 0 ; i < MAX_OTHER_SERVERS ; i++ ) { + // empty slot + if ( cls.localServers[i].adr.port == 0 ) { + break; + } + + // avoid duplicate + if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) { + return; + } + } + + if ( i == MAX_OTHER_SERVERS ) { + Com_DPrintf( "MAX_OTHER_SERVERS hit, dropping infoResponse\n" ); + return; + } + + // add this to the list + cls.numlocalservers = i + 1; + cls.localServers[i].adr = from; + cls.localServers[i].clients = 0; + cls.localServers[i].hostName[0] = '\0'; + cls.localServers[i].mapName[0] = '\0'; + cls.localServers[i].maxClients = 0; + cls.localServers[i].maxPing = 0; + cls.localServers[i].minPing = 0; + cls.localServers[i].ping = -1; + cls.localServers[i].game[0] = '\0'; + cls.localServers[i].gameType = 0; + cls.localServers[i].netType = from.type; + cls.localServers[i].allowAnonymous = 0; + cls.localServers[i].friendlyFire = 0; // NERVE - SMF + cls.localServers[i].maxlives = 0; // NERVE - SMF + cls.localServers[i].tourney = 0; // NERVE - SMF + cls.localServers[i].punkbuster = 0; // DHM - Nerve + cls.localServers[i].gameName[0] = '\0'; // Arnout + + Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING ); + if ( strlen( info ) ) { + if ( info[strlen( info ) - 1] != '\n' ) { + strncat( info, "\n", sizeof( info ) ); + } + Com_Printf( "%s: %s", NET_AdrToString( from ), info ); + } +} + +/* +=================== +CL_UpdateInfoPacket +=================== +*/ +void CL_UpdateInfoPacket( netadr_t from ) { + + if ( cls.autoupdateServer.type == NA_BAD ) { + Com_DPrintf( "CL_UpdateInfoPacket: Auto-Updater has bad address\n" ); + return; + } + + Com_DPrintf( "Auto-Updater resolved to %i.%i.%i.%i:%i\n", + cls.autoupdateServer.ip[0], cls.autoupdateServer.ip[1], + cls.autoupdateServer.ip[2], cls.autoupdateServer.ip[3], + BigShort( cls.autoupdateServer.port ) ); + + if ( !NET_CompareAdr( from, cls.autoupdateServer ) ) { + Com_DPrintf( "CL_UpdateInfoPacket: Received packet from %i.%i.%i.%i:%i\n", + from.ip[0], from.ip[1], from.ip[2], from.ip[3], + BigShort( from.port ) ); + return; + } + + Cvar_Set( "cl_updateavailable", Cmd_Argv( 1 ) ); + + if ( !Q_stricmp( cl_updateavailable->string, "1" ) ) { + Cvar_Set( "cl_updatefiles", Cmd_Argv( 2 ) ); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_WM_AUTOUPDATE ); + } +} +// DHM - Nerve + +/* +=================== +CL_GetServerStatus +=================== +*/ +serverStatus_t *CL_GetServerStatus( netadr_t from ) { + serverStatus_t *serverStatus; + int i, oldest, oldestTime; + + serverStatus = NULL; + for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) { + if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { + return &cl_serverStatusList[i]; + } + } + for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) { + if ( cl_serverStatusList[i].retrieved ) { + return &cl_serverStatusList[i]; + } + } + oldest = -1; + oldestTime = 0; + for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) { + if ( oldest == -1 || cl_serverStatusList[i].startTime < oldestTime ) { + oldest = i; + oldestTime = cl_serverStatusList[i].startTime; + } + } + if ( oldest != -1 ) { + return &cl_serverStatusList[oldest]; + } + serverStatusCount++; + return &cl_serverStatusList[serverStatusCount & ( MAX_SERVERSTATUSREQUESTS - 1 )]; +} + +/* +=================== +CL_ServerStatus +=================== +*/ +int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ) { + int i; + netadr_t to; + serverStatus_t *serverStatus; + + // if no server address then reset all server status requests + if ( !serverAddress ) { + for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) { + cl_serverStatusList[i].address.port = 0; + cl_serverStatusList[i].retrieved = qtrue; + } + return qfalse; + } + // get the address + if ( !NET_StringToAdr( serverAddress, &to ) ) { + return qfalse; + } + serverStatus = CL_GetServerStatus( to ); + // if no server status string then reset the server status request for this address + if ( !serverStatusString ) { + serverStatus->retrieved = qtrue; + return qfalse; + } + + // if this server status request has the same address + if ( NET_CompareAdr( to, serverStatus->address ) ) { + // if we recieved an response for this server status request + if ( !serverStatus->pending ) { + Q_strncpyz( serverStatusString, serverStatus->string, maxLen ); + serverStatus->retrieved = qtrue; + serverStatus->startTime = 0; + return qtrue; + } + // resend the request regularly + else if ( serverStatus->startTime < Sys_Milliseconds() - cl_serverStatusResendTime->integer ) { + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->time = 0; + serverStatus->startTime = Sys_Milliseconds(); + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + } + // if retrieved + else if ( serverStatus->retrieved ) { + serverStatus->address = to; + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->startTime = Sys_Milliseconds(); + serverStatus->time = 0; + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + return qfalse; +} + +/* +=================== +CL_ServerStatusResponse +=================== +*/ +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { + char *s; + char info[MAX_INFO_STRING]; + int i, l, score, ping; + int len; + serverStatus_t *serverStatus; + + serverStatus = NULL; + for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) { + if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { + serverStatus = &cl_serverStatusList[i]; + break; + } + } + // if we didn't request this server status + if ( !serverStatus ) { + return; + } + + s = MSG_ReadStringLine( msg ); + + len = 0; + Com_sprintf( &serverStatus->string[len], sizeof( serverStatus->string ) - len, "%s", s ); + + if ( serverStatus->print ) { + Com_Printf( "Server settings:\n" ); + // print cvars + while ( *s ) { + for ( i = 0; i < 2 && *s; i++ ) { + if ( *s == '\\' ) { + s++; + } + l = 0; + while ( *s ) { + info[l++] = *s; + if ( l >= MAX_INFO_STRING - 1 ) { + break; + } + s++; + if ( *s == '\\' ) { + break; + } + } + info[l] = '\0'; + if ( i ) { + Com_Printf( "%s\n", info ); + } else { + Com_Printf( "%-24s", info ); + } + } + } + } + + len = strlen( serverStatus->string ); + Com_sprintf( &serverStatus->string[len], sizeof( serverStatus->string ) - len, "\\" ); + + if ( serverStatus->print ) { + Com_Printf( "\nPlayers:\n" ); + Com_Printf( "num: score: ping: name:\n" ); + } + for ( i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++ ) { + + len = strlen( serverStatus->string ); + Com_sprintf( &serverStatus->string[len], sizeof( serverStatus->string ) - len, "\\%s", s ); + + if ( serverStatus->print ) { + score = ping = 0; + sscanf( s, "%d %d", &score, &ping ); + s = strchr( s, ' ' ); + if ( s ) { + s = strchr( s + 1, ' ' ); + } + if ( s ) { + s++; + } else { + s = "unknown"; + } + Com_Printf( "%-2d %-3d %-3d %s\n", i, score, ping, s ); + } + } + len = strlen( serverStatus->string ); + Com_sprintf( &serverStatus->string[len], sizeof( serverStatus->string ) - len, "\\" ); + + serverStatus->time = Sys_Milliseconds(); + serverStatus->address = from; + serverStatus->pending = qfalse; + if ( serverStatus->print ) { + serverStatus->retrieved = qtrue; + } +} + +/* +================== +CL_LocalServers_f +================== +*/ +void CL_LocalServers_f( void ) { + char *message; + int i, j; + netadr_t to; + + Com_Printf( "Scanning for servers on the local network...\n" ); + + // reset the list, waiting for response + cls.numlocalservers = 0; + cls.pingUpdateSource = AS_LOCAL; + + for ( i = 0; i < MAX_OTHER_SERVERS; i++ ) { + qboolean b = cls.localServers[i].visible; + Com_Memset( &cls.localServers[i], 0, sizeof( cls.localServers[i] ) ); + cls.localServers[i].visible = b; + } + Com_Memset( &to, 0, sizeof( to ) ); + + // The 'xxx' in the message is a challenge that will be echoed back + // by the server. We don't care about that here, but master servers + // can use that to prevent spoofed server responses from invalid ip + message = "\377\377\377\377getinfo xxx"; + + // send each message twice in case one is dropped + for ( i = 0 ; i < 2 ; i++ ) { + // send a broadcast packet on each server port + // we support multiple server ports so a single machine + // can nicely run multiple servers + for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { + to.port = BigShort( (short)( PORT_SERVER + j ) ); + + to.type = NA_BROADCAST; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + + to.type = NA_BROADCAST_IPX; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + } + } +} + +/* +================== +CL_GlobalServers_f +================== +*/ +void CL_GlobalServers_f( void ) { + netadr_t to; + int i; + int count; + char *buffptr; + char command[1024]; + + if ( Cmd_Argc() < 3 ) { + Com_Printf( "usage: globalservers [keywords]\n" ); + return; + } + + cls.masterNum = atoi( Cmd_Argv( 1 ) ); + + Com_Printf( "Requesting servers from the master...\n" ); + + // reset the list, waiting for response + // -1 is used to distinguish a "no response" + + if ( cls.masterNum == 1 ) { + NET_StringToAdr( "master.quake3world.com", &to ); + cls.nummplayerservers = -1; + cls.pingUpdateSource = AS_MPLAYER; + } else { + NET_StringToAdr( MASTER_SERVER_NAME, &to ); + cls.numglobalservers = -1; + cls.pingUpdateSource = AS_GLOBAL; + } + to.type = NA_IP; + to.port = BigShort( PORT_MASTER ); + + sprintf( command, "getservers %s", Cmd_Argv( 2 ) ); + + // tack on keywords + buffptr = command + strlen( command ); + count = Cmd_Argc(); + for ( i = 3; i < count; i++ ) + buffptr += sprintf( buffptr, " %s", Cmd_Argv( i ) ); + + // if we are a demo, automatically add a "demo" keyword + if ( Cvar_VariableValue( "fs_restrict" ) ) { + buffptr += sprintf( buffptr, " demo" ); + } + + NET_OutOfBandPrint( NS_SERVER, to, command ); +} + + +/* +================== +CL_GetPing +================== +*/ +void CL_GetPing( int n, char *buf, int buflen, int *pingtime ) { + const char *str; + int time; + int maxPing; + + if ( !cl_pinglist[n].adr.port ) { + // empty slot + buf[0] = '\0'; + *pingtime = 0; + return; + } + + str = NET_AdrToString( cl_pinglist[n].adr ); + Q_strncpyz( buf, str, buflen ); + + time = cl_pinglist[n].time; + if ( !time ) { + // check for timeout + time = cls.realtime - cl_pinglist[n].start; + maxPing = Cvar_VariableIntegerValue( "cl_maxPing" ); + if ( maxPing < 100 ) { + maxPing = 100; + } + if ( time < maxPing ) { + // not timed out yet + time = 0; + } + } + + CL_SetServerInfoByAddress( cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time ); + + *pingtime = time; +} + +/* +================== +CL_UpdateServerInfo +================== +*/ +void CL_UpdateServerInfo( int n ) { + if ( !cl_pinglist[n].adr.port ) { + return; + } + + CL_SetServerInfoByAddress( cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time ); +} + +/* +================== +CL_GetPingInfo +================== +*/ +void CL_GetPingInfo( int n, char *buf, int buflen ) { + if ( !cl_pinglist[n].adr.port ) { + // empty slot + if ( buflen ) { + buf[0] = '\0'; + } + return; + } + + Q_strncpyz( buf, cl_pinglist[n].info, buflen ); +} + +/* +================== +CL_ClearPing +================== +*/ +void CL_ClearPing( int n ) { + if ( n < 0 || n >= MAX_PINGREQUESTS ) { + return; + } + + cl_pinglist[n].adr.port = 0; +} + +/* +================== +CL_GetPingQueueCount +================== +*/ +int CL_GetPingQueueCount( void ) { + int i; + int count; + ping_t* pingptr; + + count = 0; + pingptr = cl_pinglist; + + for ( i = 0; i < MAX_PINGREQUESTS; i++, pingptr++ ) { + if ( pingptr->adr.port ) { + count++; + } + } + + return ( count ); +} + +/* +================== +CL_GetFreePing +================== +*/ +ping_t* CL_GetFreePing( void ) { + ping_t* pingptr; + ping_t* best; + int oldest; + int i; + int time; + + pingptr = cl_pinglist; + for ( i = 0; i < MAX_PINGREQUESTS; i++, pingptr++ ) + { + // find free ping slot + if ( pingptr->adr.port ) { + if ( !pingptr->time ) { + if ( cls.realtime - pingptr->start < 500 ) { + // still waiting for response + continue; + } + } else if ( pingptr->time < 500 ) { + // results have not been queried + continue; + } + } + + // clear it + pingptr->adr.port = 0; + return ( pingptr ); + } + + // use oldest entry + pingptr = cl_pinglist; + best = cl_pinglist; + oldest = INT_MIN; + for ( i = 0; i < MAX_PINGREQUESTS; i++, pingptr++ ) + { + // scan for oldest + time = cls.realtime - pingptr->start; + if ( time > oldest ) { + oldest = time; + best = pingptr; + } + } + + return ( best ); +} + +/* +================== +CL_Ping_f +================== +*/ +void CL_Ping_f( void ) { + netadr_t to; + ping_t* pingptr; + char* server; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: ping [server]\n" ); + return; + } + + memset( &to, 0, sizeof( netadr_t ) ); + + server = Cmd_Argv( 1 ); + + if ( !NET_StringToAdr( server, &to ) ) { + return; + } + + pingptr = CL_GetFreePing(); + + memcpy( &pingptr->adr, &to, sizeof( netadr_t ) ); + pingptr->start = cls.realtime; + pingptr->time = 0; + + CL_SetServerInfoByAddress( pingptr->adr, NULL, 0 ); + + NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); +} + +/* +================== +CL_UpdateVisiblePings_f +================== +*/ +qboolean CL_UpdateVisiblePings_f( int source ) { + int slots, i; + char buff[MAX_STRING_CHARS]; + int pingTime; + int max; + qboolean status = qfalse; + + if ( source < 0 || source > AS_FAVORITES ) { + return qfalse; + } + + cls.pingUpdateSource = source; + + slots = CL_GetPingQueueCount(); + if ( slots < MAX_PINGREQUESTS ) { + serverInfo_t *server = NULL; + + max = ( source == AS_GLOBAL ) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; + switch ( source ) { + case AS_LOCAL: + server = &cls.localServers[0]; + max = cls.numlocalservers; + break; + case AS_MPLAYER: + server = &cls.mplayerServers[0]; + max = cls.nummplayerservers; + break; + case AS_GLOBAL: + server = &cls.globalServers[0]; + max = cls.numglobalservers; + break; + case AS_FAVORITES: + server = &cls.favoriteServers[0]; + max = cls.numfavoriteservers; + break; + } + for ( i = 0; i < max; i++ ) { + if ( server[i].visible ) { + if ( server[i].ping == -1 ) { + int j; + + if ( slots >= MAX_PINGREQUESTS ) { + break; + } + for ( j = 0; j < MAX_PINGREQUESTS; j++ ) { + if ( !cl_pinglist[j].adr.port ) { + continue; + } + if ( NET_CompareAdr( cl_pinglist[j].adr, server[i].adr ) ) { + // already on the list + break; + } + } + if ( j >= MAX_PINGREQUESTS ) { + status = qtrue; + for ( j = 0; j < MAX_PINGREQUESTS; j++ ) { + if ( !cl_pinglist[j].adr.port ) { + break; + } + } + memcpy( &cl_pinglist[j].adr, &server[i].adr, sizeof( netadr_t ) ); + cl_pinglist[j].start = cls.realtime; + cl_pinglist[j].time = 0; + NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); + slots++; + } + } + // if the server has a ping higher than cl_maxPing or + // the ping packet got lost + else if ( server[i].ping == 0 ) { + // if we are updating global servers + if ( source == AS_GLOBAL ) { + // + if ( cls.numGlobalServerAddresses > 0 ) { + // overwrite this server with one from the additional global servers + cls.numGlobalServerAddresses--; + CL_InitServerInfo( &server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses] ); + // NOTE: the server[i].visible flag stays untouched + } + } + } + } + } + } + + if ( slots ) { + status = qtrue; + } + for ( i = 0; i < MAX_PINGREQUESTS; i++ ) { + if ( !cl_pinglist[i].adr.port ) { + continue; + } + CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); + if ( pingTime != 0 ) { + CL_ClearPing( i ); + status = qtrue; + } + } + + return status; +} + +/* +================== +CL_ServerStatus_f +================== +*/ +void CL_ServerStatus_f( void ) { + netadr_t to; + char *server; + serverStatus_t *serverStatus; + + Com_Memset( &to, 0, sizeof( netadr_t ) ); + + if ( Cmd_Argc() != 2 ) { + if ( cls.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf( "Not connected to a server.\n" ); + Com_Printf( "Usage: serverstatus [server]\n" ); + return; + } + server = cls.servername; + } else { + server = Cmd_Argv( 1 ); + } + + if ( !NET_StringToAdr( server, &to ) ) { + return; + } + + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + + serverStatus = CL_GetServerStatus( to ); + serverStatus->address = to; + serverStatus->print = qtrue; + serverStatus->pending = qtrue; +} + +/* +================== +CL_ShowIP_f +================== +*/ +void CL_ShowIP_f( void ) { + Sys_ShowIP(); +} + +/* +================= +bool CL_CDKeyValidate +================= +*/ +qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { + char ch; + byte sum; + char chs[3]; + int i, len; + + len = strlen( key ); + if ( len != CDKEY_LEN ) { + return qfalse; + } + + if ( checksum && strlen( checksum ) != CDCHKSUM_LEN ) { + return qfalse; + } + + sum = 0; + // for loop gets rid of conditional assignment warning + for ( i = 0; i < len; i++ ) { + ch = *key++; + if ( ch >= 'a' && ch <= 'z' ) { + ch -= 32; + } + switch ( ch ) { + case '2': + case '3': + case '7': + case 'A': + case 'B': + case 'C': + case 'D': + case 'G': + case 'H': + case 'J': + case 'L': + case 'P': + case 'R': + case 'S': + case 'T': + case 'W': + sum = ( sum << 1 ) ^ ch; + continue; + default: + return qfalse; + } + } + + + sprintf( chs, "%02x", sum ); + + if ( checksum && !Q_stricmp( chs, checksum ) ) { + return qtrue; + } + + if ( !checksum ) { + return qtrue; + } + + return qfalse; +} + +// NERVE - SMF +/* +======================= +CL_AddToLimboChat + +======================= +*/ +void CL_AddToLimboChat( const char *str ) { + int len; + char *p, *ls; + int lastcolor; + int chatHeight; + int i; + + chatHeight = LIMBOCHAT_HEIGHT; + cl.limboChatPos = LIMBOCHAT_HEIGHT - 1; + len = 0; + + // copy old strings + for ( i = cl.limboChatPos; i > 0; i-- ) { + strcpy( cl.limboChatMsgs[i], cl.limboChatMsgs[i - 1] ); + } + + // copy new string + p = cl.limboChatMsgs[0]; + *p = 0; + + lastcolor = '7'; + + ls = NULL; + while ( *str ) { + if ( len > LIMBOCHAT_WIDTH - 1 ) { + break; + } + + if ( Q_IsColorString( str ) ) { + *p++ = *str++; + lastcolor = *str; + *p++ = *str++; + continue; + } + if ( *str == ' ' ) { + ls = p; + } + *p++ = *str++; + len++; + } + *p = 0; +} + +/* +======================= +CL_GetLimboString + +======================= +*/ +qboolean CL_GetLimboString( int index, char *buf ) { + if ( index >= LIMBOCHAT_HEIGHT ) { + return qfalse; + } + + strncpy( buf, cl.limboChatMsgs[index], 140 ); + return qtrue; +} +// -NERVE - SMF + + + +// NERVE - SMF - Localization code +#define FILE_HASH_SIZE 1024 +#define MAX_VA_STRING 32000 +#define MAX_TRANS_STRING 4096 + +#ifndef __MACOS__ //DAJ USA +typedef struct trans_s { + char original[MAX_TRANS_STRING]; + char translated[MAX_LANGUAGES][MAX_TRANS_STRING]; + struct trans_s *next; + float x_offset; + float y_offset; + qboolean fromFile; +} trans_t; + +static trans_t* transTable[FILE_HASH_SIZE]; + +/* +======================= +AllocTrans +======================= +*/ +static trans_t* AllocTrans( char *original, char *translated[MAX_LANGUAGES] ) { + trans_t *t; + int i; + + t = malloc( sizeof( trans_t ) ); + memset( t, 0, sizeof( trans_t ) ); + + if ( original ) { + strncpy( t->original, original, MAX_TRANS_STRING ); + } + + if ( translated ) { + for ( i = 0; i < MAX_LANGUAGES; i++ ) + strncpy( t->translated[i], translated[i], MAX_TRANS_STRING ); + } + + return t; +} + +/* +======================= +generateHashValue +======================= +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( FILE_HASH_SIZE - 1 ); + return hash; +} + +/* +======================= +LookupTrans +======================= +*/ +static trans_t* LookupTrans( char *original, char *translated[MAX_LANGUAGES], qboolean isLoading ) { + trans_t *t, *newt, *prev = NULL; + long hash; + + hash = generateHashValue( original ); + + for ( t = transTable[hash]; t; prev = t, t = t->next ) { + if ( !Q_stricmp( original, t->original ) ) { + if ( isLoading ) { + Com_DPrintf( S_COLOR_YELLOW "WARNING: Duplicate string found: \"%s\"\n", original ); + } + return t; + } + } + + newt = AllocTrans( original, translated ); + + if ( prev ) { + prev->next = newt; + } else { + transTable[hash] = newt; + } + + if ( cl_debugTranslation->integer >= 1 && !isLoading ) { + Com_Printf( "Missing translation: \'%s\'\n", original ); + } + + // see if we want to save out the translation table everytime a string is added + if ( cl_debugTranslation->integer == 2 && !isLoading ) { + CL_SaveTransTable(); + } + + return newt; +} + +/* +======================= +CL_SaveTransTable +======================= +*/ +void CL_SaveTransTable( const char *fileName, qboolean newOnly ) { + int bucketlen, bucketnum, maxbucketlen, avebucketlen; + int untransnum, transnum; + const char *buf; + fileHandle_t f; + trans_t *t; + int i, j, len; + + if ( cl.corruptedTranslationFile ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Cannot save corrupted translation file. Please reload first." ); + return; + } + + FS_FOpenFileByMode( fileName, &f, FS_WRITE ); + + bucketnum = 0; + maxbucketlen = 0; + avebucketlen = 0; + transnum = 0; + untransnum = 0; + + // write out version, if one + if ( strlen( cl.translationVersion ) ) { + buf = va( "#version\t\t\"%s\"\n", cl.translationVersion ); + } else { + buf = va( "#version\t\t\"1.0 01/01/01\"\n" ); + } + + len = strlen( buf ); + FS_Write( buf, len, f ); + + // write out translated strings + for ( j = 0; j < 2; j++ ) { + + for ( i = 0; i < FILE_HASH_SIZE; i++ ) { + t = transTable[i]; + + if ( !t || ( newOnly && t->fromFile ) ) { + continue; + } + + bucketlen = 0; + + for ( ; t; t = t->next ) { + bucketlen++; + + if ( strlen( t->translated[0] ) ) { + if ( j ) { + continue; + } + transnum++; + } else { + if ( !j ) { + continue; + } + untransnum++; + } + + buf = va( "{\n\tenglish\t\t\"%s\"\n", t->original ); + len = strlen( buf ); + FS_Write( buf, len, f ); + + buf = va( "\tfrench\t\t\"%s\"\n", t->translated[LANGUAGE_FRENCH] ); + len = strlen( buf ); + FS_Write( buf, len, f ); + + buf = va( "\tgerman\t\t\"%s\"\n", t->translated[LANGUAGE_GERMAN] ); + len = strlen( buf ); + FS_Write( buf, len, f ); + + buf = va( "\titalian\t\t\"%s\"\n", t->translated[LANGUAGE_ITALIAN] ); + len = strlen( buf ); + FS_Write( buf, len, f ); + + buf = va( "\tspanish\t\t\"%s\"\n", t->translated[LANGUAGE_SPANISH] ); + len = strlen( buf ); + FS_Write( buf, len, f ); + + buf = va( "}\n", t->original ); + len = strlen( buf ); + FS_Write( buf, len, f ); + } + + if ( bucketlen > maxbucketlen ) { + maxbucketlen = bucketlen; + } + + if ( bucketlen ) { + bucketnum++; + avebucketlen += bucketlen; + } + } + } + + Com_Printf( "Saved translation table.\nTotal = %i, Translated = %i, Untranslated = %i, aveblen = %2.2f, maxblen = %i\n", + transnum + untransnum, transnum, untransnum, (float)avebucketlen / bucketnum, maxbucketlen ); + + FS_FCloseFile( f ); +} + +/* +======================= +CL_CheckTranslationString + +NERVE - SMF - compare formatting characters +======================= +*/ +qboolean CL_CheckTranslationString( char *original, char *translated ) { + char format_org[128], format_trans[128]; + int len, i; + + memset( format_org, 0, 128 ); + memset( format_trans, 0, 128 ); + + // generate formatting string for original + len = strlen( original ); + + for ( i = 0; i < len; i++ ) { + if ( original[i] != '%' ) { + continue; + } + + strcat( format_org, va( "%c%c ", '%', original[i + 1] ) ); + } + + // generate formatting string for translated + len = strlen( translated ); + if ( !len ) { + return qtrue; + } + + for ( i = 0; i < len; i++ ) { + if ( translated[i] != '%' ) { + continue; + } + + strcat( format_trans, va( "%c%c ", '%', translated[i + 1] ) ); + } + + // compare + len = strlen( format_org ); + + if ( len != strlen( format_trans ) ) { + return qfalse; + } + + for ( i = 0; i < len; i++ ) { + if ( format_org[i] != format_trans[i] ) { + return qfalse; + } + } + + return qtrue; +} + +/* +======================= +CL_LoadTransTable +======================= +*/ +void CL_LoadTransTable( const char *fileName ) { + char translated[MAX_LANGUAGES][MAX_VA_STRING]; + char original[MAX_VA_STRING]; + qboolean aborted; + char *text; + fileHandle_t f; + char *text_p; + char *token; + int len, i; + trans_t *t; + int count; + + count = 0; + aborted = qfalse; + cl.corruptedTranslationFile = qfalse; + + len = FS_FOpenFileByMode( fileName, &f, FS_READ ); + if ( len <= 0 ) { + return; + } + + text = malloc( len + 1 ); + if ( !text ) { + return; + } + + FS_Read( text, len, f ); + text[len] = 0; + FS_FCloseFile( f ); + + // parse the text + text_p = text; + + do { + token = COM_Parse( &text_p ); + if ( Q_stricmp( "{", token ) ) { + // parse version number + if ( !Q_stricmp( "#version", token ) ) { + token = COM_Parse( &text_p ); + strcpy( cl.translationVersion, token ); + continue; + } + + break; + } + + // english + token = COM_Parse( &text_p ); + if ( Q_stricmp( "english", token ) ) { + aborted = qtrue; + break; + } + + token = COM_Parse( &text_p ); + strcpy( original, token ); + + if ( cl_debugTranslation->integer == 3 ) { + Com_Printf( "%i Loading: \"%s\"\n", count, original ); + } + + // french + token = COM_Parse( &text_p ); + if ( Q_stricmp( "french", token ) ) { + aborted = qtrue; + break; + } + + token = COM_Parse( &text_p ); + strcpy( translated[LANGUAGE_FRENCH], token ); + if ( !CL_CheckTranslationString( original, translated[LANGUAGE_FRENCH] ) ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Translation formatting doesn't match up with English version!\n" ); + aborted = qtrue; + break; + } + + // german + token = COM_Parse( &text_p ); + if ( Q_stricmp( "german", token ) ) { + aborted = qtrue; + break; + } + + token = COM_Parse( &text_p ); + strcpy( translated[LANGUAGE_GERMAN], token ); + if ( !CL_CheckTranslationString( original, translated[LANGUAGE_GERMAN] ) ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Translation formatting doesn't match up with English version!\n" ); + aborted = qtrue; + break; + } + + // italian + token = COM_Parse( &text_p ); + if ( Q_stricmp( "italian", token ) ) { + aborted = qtrue; + break; + } + + token = COM_Parse( &text_p ); + strcpy( translated[LANGUAGE_ITALIAN], token ); + if ( !CL_CheckTranslationString( original, translated[LANGUAGE_ITALIAN] ) ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Translation formatting doesn't match up with English version!\n" ); + aborted = qtrue; + break; + } + + // spanish + token = COM_Parse( &text_p ); + if ( Q_stricmp( "spanish", token ) ) { + aborted = qtrue; + break; + } + + token = COM_Parse( &text_p ); + strcpy( translated[LANGUAGE_SPANISH], token ); + if ( !CL_CheckTranslationString( original, translated[LANGUAGE_SPANISH] ) ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Translation formatting doesn't match up with English version!\n" ); + aborted = qtrue; + break; + } + + // do lookup + t = LookupTrans( original, NULL, qtrue ); + + if ( t ) { + t->fromFile = qtrue; + + for ( i = 0; i < MAX_LANGUAGES; i++ ) + strncpy( t->translated[i], translated[i], MAX_TRANS_STRING ); + } + + token = COM_Parse( &text_p ); + + // set offset if we have one + if ( !Q_stricmp( "offset", token ) ) { + token = COM_Parse( &text_p ); + t->x_offset = atof( token ); + + token = COM_Parse( &text_p ); + t->y_offset = atof( token ); + + token = COM_Parse( &text_p ); + } + + if ( Q_stricmp( "}", token ) ) { + aborted = qtrue; + break; + } + + count++; + } while ( token ); + + if ( aborted ) { + int i, line = 1; + + for ( i = 0; i < len && ( text + i ) < text_p; i++ ) { + if ( text[i] == '\n' ) { + line++; + } + } + + Com_Printf( S_COLOR_YELLOW "WARNING: Problem loading %s on line %i\n", fileName, line ); + cl.corruptedTranslationFile = qtrue; + } else { + Com_Printf( "Loaded %i translation strings from %s\n", count, fileName ); + } + + // cleanup + free( text ); +} + +/* +======================= +CL_ReloadTranslation +======================= +*/ +void CL_ReloadTranslation() { + char **fileList; + int numFiles, i; + + for ( i = 0; i < FILE_HASH_SIZE; i++ ) { + if ( transTable[i] ) { + free( transTable[i] ); + } + } + + memset( transTable, 0, sizeof( trans_t* ) * FILE_HASH_SIZE ); + CL_LoadTransTable( "scripts/translation.cfg" ); + + fileList = FS_ListFiles( "translations", "cfg", &numFiles ); + + for ( i = 0; i < numFiles; i++ ) { + CL_LoadTransTable( va( "translations/%s", fileList[i] ) ); + } +} + +/* +======================= +CL_InitTranslation +======================= +*/ +void CL_InitTranslation() { + char **fileList; + int numFiles, i; + + memset( transTable, 0, sizeof( trans_t* ) * FILE_HASH_SIZE ); + CL_LoadTransTable( "scripts/translation.cfg" ); + + fileList = FS_ListFiles( "translations", ".cfg", &numFiles ); + + for ( i = 0; i < numFiles; i++ ) { + CL_LoadTransTable( va( "translations/%s", fileList[i] ) ); + } +} + +#else +typedef struct trans_s { + char original[MAX_TRANS_STRING]; + struct trans_s *next; + float x_offset; + float y_offset; +} trans_t; + +#endif //DAJ USA + +/* +======================= +CL_TranslateString +======================= +*/ +void CL_TranslateString( const char *string, char *dest_buffer ) { + int i, count, currentLanguage; + trans_t *t; + qboolean newline = qfalse; + char *buf; + + buf = dest_buffer; + currentLanguage = cl_language->integer - 1; + + // early bail if we only want english or bad language type + if ( !string ) { + strcpy( buf, "(null)" ); + return; + } else if ( currentLanguage == -1 || currentLanguage >= MAX_LANGUAGES || !strlen( string ) ) { + strcpy( buf, string ); + return; + } +#if !defined( __MACOS__ ) + // ignore newlines + if ( string[strlen( string ) - 1] == '\n' ) { + newline = qtrue; + } + + for ( i = 0, count = 0; string[i] != '\0'; i++ ) { + if ( string[i] != '\n' ) { + buf[count++] = string[i]; + } + } + buf[count] = '\0'; + + t = LookupTrans( buf, NULL, qfalse ); + + if ( t && strlen( t->translated[currentLanguage] ) ) { + int offset = 0; + + if ( cl_debugTranslation->integer >= 1 ) { + buf[0] = '^'; + buf[1] = '1'; + buf[2] = '['; + offset = 3; + } + + strcpy( buf + offset, t->translated[currentLanguage] ); + + if ( cl_debugTranslation->integer >= 1 ) { + int len2 = strlen( buf ); + + buf[len2] = ']'; + buf[len2 + 1] = '^'; + buf[len2 + 2] = '7'; + buf[len2 + 3] = '\0'; + } + + if ( newline ) { + int len2 = strlen( buf ); + + buf[len2] = '\n'; + buf[len2 + 1] = '\0'; + } + } else { + int offset = 0; + + if ( cl_debugTranslation->integer >= 1 ) { + buf[0] = '^'; + buf[1] = '1'; + buf[2] = '['; + offset = 3; + } + + strcpy( buf + offset, string ); + + if ( cl_debugTranslation->integer >= 1 ) { + int len2 = strlen( buf ); + qboolean addnewline = qfalse; + + if ( buf[len2 - 1] == '\n' ) { + len2--; + addnewline = qtrue; + } + + buf[len2] = ']'; + buf[len2 + 1] = '^'; + buf[len2 + 2] = '7'; + buf[len2 + 3] = '\0'; + + if ( addnewline ) { + buf[len2 + 3] = '\n'; + buf[len2 + 4] = '\0'; + } + } + } +#endif //DAJ USA +} + +/* +======================= +CL_TranslateStringBuf +TTimo - handy, stores in a static buf, converts \n to chr(13) +======================= +*/ +const char* CL_TranslateStringBuf( const char *string ) { + char *p; + int i,l; + static char buf[MAX_VA_STRING]; + CL_TranslateString( string, buf ); + while ( ( p = strstr( buf, "\\n" ) ) ) + { + *p = '\n'; + p++; + // Com_Memcpy(p, p+1, strlen(p) ); b0rks on win32 + l = strlen( p ); + for ( i = 0; i < l; i++ ) + { + *p = *( p + 1 ); + p++; + } + } + return buf; +} + +/* +======================= +CL_OpenURLForCvar +======================= +*/ +void CL_OpenURL( const char *url ) { + if ( !url || !strlen( url ) ) { + Com_Printf( CL_TranslateStringBuf( "invalid/empty URL\n" ) ); + return; + } + Sys_OpenURL( url, qtrue ); +} diff --git a/src/client/cl_net_chan.c b/src/client/cl_net_chan.c new file mode 100644 index 0000000..cc8525a --- /dev/null +++ b/src/client/cl_net_chan.c @@ -0,0 +1,174 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "client.h" + +/* +============== +CL_Netchan_Encode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Encode( msg_t *msg ) { + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit, soob; + byte key, *string; + + if ( msg->cursize <= CL_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = 0; + + serverId = MSG_ReadLong( msg ); + messageAcknowledge = MSG_ReadLong( msg ); + reliableAcknowledge = MSG_ReadLong( msg ); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)clc.serverCommands[ reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + index = 0; + // + key = clc.challenge ^ serverId ^ messageAcknowledge; + for ( i = CL_ENCODE_START; i < msg->cursize; i++ ) { + // modify the key with the last received now acknowledged server command + if ( !string[index] ) { + index = 0; + } + if ( string[index] > 127 || string[index] == '%' ) { + key ^= '.' << ( i & 1 ); + } else { + key ^= string[index] << ( i & 1 ); + } + index++; + // encode the data with this key + *( msg->data + i ) = ( *( msg->data + i ) ) ^ key; + } +} + +/* +============== +CL_Netchan_Decode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Decode( msg_t *msg ) { + long reliableAcknowledge, i, index; + byte key, *string; + int srdc, sbit, soob; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = 0; + + reliableAcknowledge = MSG_ReadLong( msg ); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = clc.reliableCommands[ reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + index = 0; + // xor the client challenge with the netchan sequence number (need something that changes every message) + key = clc.challenge ^ LittleLong( *(unsigned *)msg->data ); + for ( i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++ ) { + // modify the key with the last sent and with this message acknowledged client command + if ( !string[index] ) { + index = 0; + } + if ( string[index] > 127 || string[index] == '%' ) { + key ^= '.' << ( i & 1 ); + } else { + key ^= string[index] << ( i & 1 ); + } + index++; + // decode the data with this key + *( msg->data + i ) = *( msg->data + i ) ^ key; + } +} + +/* +================= +CL_Netchan_TransmitNextFragment +================= +*/ +void CL_Netchan_TransmitNextFragment( netchan_t *chan ) { + Netchan_TransmitNextFragment( chan ); +} + +/* +================ +CL_Netchan_Transmit +================ +*/ +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { + MSG_WriteByte( msg, clc_EOF ); + CL_Netchan_Encode( msg ); + Netchan_Transmit( chan, msg->cursize, msg->data ); +} + +extern int oldsize; +int newsize = 0; + +/* +================= +CL_Netchan_Process +================= +*/ +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { + int ret; + + ret = Netchan_Process( chan, msg ); + if ( !ret ) { + return qfalse; + } + CL_Netchan_Decode( msg ); + newsize += msg->cursize; + return qtrue; +} diff --git a/src/client/cl_parse.c b/src/client/cl_parse.c new file mode 100644 index 0000000..fc1eec0 --- /dev/null +++ b/src/client/cl_parse.c @@ -0,0 +1,795 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cl_parse.c -- parse a message received from the server + +#include "client.h" + +char *svc_strings[256] = { + "svc_bad", + + "svc_nop", + "svc_gamestate", + "svc_configstring", + "svc_baseline", + "svc_serverCommand", + "svc_download", + "svc_snapshot" +}; + +void SHOWNET( msg_t *msg, char *s ) { + if ( cl_shownet->integer >= 2 ) { + Com_Printf( "%3i:%s\n", msg->readcount - 1, s ); + } +} + + +/* +========================================================================= + +MESSAGE PARSING + +========================================================================= +*/ +#if 1 + +int entLastVisible[MAX_CLIENTS]; + +qboolean isEntVisible( entityState_t *ent ) { + trace_t tr; + vec3_t start, end, temp; + vec3_t forward, up, right, right2; + float view_height; + + VectorCopy( cl.cgameClientLerpOrigin, start ); + start[2] += ( cl.snap.ps.viewheight - 1 ); + if ( cl.snap.ps.leanf != 0 ) { + vec3_t lright, v3ViewAngles; + VectorCopy( cl.snap.ps.viewangles, v3ViewAngles ); + v3ViewAngles[2] += cl.snap.ps.leanf / 2.0f; + AngleVectors( v3ViewAngles, NULL, lright, NULL ); + VectorMA( start, cl.snap.ps.leanf, lright, start ); + } + + VectorCopy( ent->pos.trBase, end ); + + // Compute vector perpindicular to view to ent + VectorSubtract( end, start, forward ); + VectorNormalizeFast( forward ); + VectorSet( up, 0, 0, 1 ); + CrossProduct( forward, up, right ); + VectorNormalizeFast( right ); + VectorScale( right, 10, right2 ); + VectorScale( right, 18, right ); + + // Set viewheight + if ( ent->animMovetype ) { + view_height = 16; + } else { + view_height = 40; + } + + // First, viewpoint to viewpoint + end[2] += view_height; + CM_BoxTrace( &tr, start, end, NULL, NULL, 0, CONTENTS_SOLID, qfalse ); + if ( tr.fraction == 1.f ) { + return qtrue; + } + + // First-b, viewpoint to top of head + end[2] += 16; + CM_BoxTrace( &tr, start, end, NULL, NULL, 0, CONTENTS_SOLID, qfalse ); + if ( tr.fraction == 1.f ) { + return qtrue; + } + end[2] -= 16; + + // Second, viewpoint to ent's origin + end[2] -= view_height; + CM_BoxTrace( &tr, start, end, NULL, NULL, 0, CONTENTS_SOLID, qfalse ); + if ( tr.fraction == 1.f ) { + return qtrue; + } + + // Third, to ent's right knee + VectorAdd( end, right, temp ); + temp[2] += 8; + CM_BoxTrace( &tr, start, temp, NULL, NULL, 0, CONTENTS_SOLID, qfalse ); + if ( tr.fraction == 1.f ) { + return qtrue; + } + + // Fourth, to ent's right shoulder + VectorAdd( end, right2, temp ); + if ( ent->animMovetype ) { + temp[2] += 28; + } else { + temp[2] += 52; + } + CM_BoxTrace( &tr, start, temp, NULL, NULL, 0, CONTENTS_SOLID, qfalse ); + if ( tr.fraction == 1.f ) { + return qtrue; + } + + // Fifth, to ent's left knee + VectorScale( right, -1, right ); + VectorScale( right2, -1, right2 ); + VectorAdd( end, right2, temp ); + temp[2] += 2; + CM_BoxTrace( &tr, start, temp, NULL, NULL, 0, CONTENTS_SOLID, qfalse ); + if ( tr.fraction == 1.f ) { + return qtrue; + } + + // Sixth, to ent's left shoulder + VectorAdd( end, right, temp ); + if ( ent->animMovetype ) { + temp[2] += 16; + } else { + temp[2] += 36; + } + CM_BoxTrace( &tr, start, temp, NULL, NULL, 0, CONTENTS_SOLID, qfalse ); + if ( tr.fraction == 1.f ) { + return qtrue; + } + + return qfalse; +} + +#endif + +/* +================== +CL_DeltaEntity + +Parses deltas from the given base and adds the resulting entity +to the current frame +================== +*/ +void CL_DeltaEntity( msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, + qboolean unchanged ) { + entityState_t *state; + + // save the parsed entity state into the big circular buffer so + // it can be used as the source for a later delta + state = &cl.parseEntities[cl.parseEntitiesNum & ( MAX_PARSE_ENTITIES - 1 )]; + + if ( unchanged ) { + *state = *old; + } else { + MSG_ReadDeltaEntity( msg, old, state, newnum ); + } + + if ( state->number == ( MAX_GENTITIES - 1 ) ) { + return; // entity was delta removed + } + +#if 1 + // DHM - Nerve :: Only draw clients if visible + if ( clc.onlyVisibleClients ) { + if ( state->number < MAX_CLIENTS ) { + if ( isEntVisible( state ) ) { + entLastVisible[state->number] = frame->serverTime; + state->eFlags &= ~EF_NODRAW; + } else { + if ( entLastVisible[state->number] < ( frame->serverTime - 600 ) ) { + state->eFlags |= EF_NODRAW; + } + } + } + } +#endif + + cl.parseEntitiesNum++; + frame->numEntities++; +} + +/* +================== +CL_ParsePacketEntities + +================== +*/ +void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe ) { + int newnum; + entityState_t *oldstate; + int oldindex, oldnum; + + newframe->parseEntitiesNum = cl.parseEntitiesNum; + newframe->numEntities = 0; + + // delta from the entities present in oldframe + oldindex = 0; + oldstate = NULL; + if ( !oldframe ) { + oldnum = 99999; + } else { + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + ( oldframe->parseEntitiesNum + oldindex ) & ( MAX_PARSE_ENTITIES - 1 )]; + oldnum = oldstate->number; + } + } + + while ( 1 ) { + // read the entity index number + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + + if ( newnum == ( MAX_GENTITIES - 1 ) ) { + break; + } + + if ( msg->readcount > msg->cursize ) { + Com_Error( ERR_DROP,"CL_ParsePacketEntities: end of message" ); + } + + while ( oldnum < newnum ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf( "%3i: unchanged: %i\n", msg->readcount, oldnum ); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + ( oldframe->parseEntitiesNum + oldindex ) & ( MAX_PARSE_ENTITIES - 1 )]; + oldnum = oldstate->number; + } + } + if ( oldnum == newnum ) { + // delta from previous state + if ( cl_shownet->integer == 3 ) { + Com_Printf( "%3i: delta: %i\n", msg->readcount, newnum ); + } + CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + ( oldframe->parseEntitiesNum + oldindex ) & ( MAX_PARSE_ENTITIES - 1 )]; + oldnum = oldstate->number; + } + continue; + } + + if ( oldnum > newnum ) { + // delta from baseline + if ( cl_shownet->integer == 3 ) { + Com_Printf( "%3i: baseline: %i\n", msg->readcount, newnum ); + } + CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse ); + continue; + } + + } + + // any remaining entities in the old frame are copied over + while ( oldnum != 99999 ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf( "%3i: unchanged: %i\n", msg->readcount, oldnum ); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + ( oldframe->parseEntitiesNum + oldindex ) & ( MAX_PARSE_ENTITIES - 1 )]; + oldnum = oldstate->number; + } + } + + if ( cl_shownuments->integer ) { + Com_Printf( "Entities in packet: %i\n", newframe->numEntities ); + } +} + + +/* +================ +CL_ParseSnapshot + +If the snapshot is parsed properly, it will be copied to +cl.snap and saved in cl.snapshots[]. If the snapshot is invalid +for any reason, no changes to the state will be made at all. +================ +*/ +void CL_ParseSnapshot( msg_t *msg ) { + int len; + clSnapshot_t *old; + clSnapshot_t newSnap; + int deltaNum; + int oldMessageNum; + int i, packetNum; + + // get the reliable sequence acknowledge number + // NOTE: now sent with all server to client messages + //clc.reliableAcknowledge = MSG_ReadLong( msg ); + + // read in the new snapshot to a temporary buffer + // we will only copy to cl.snap if it is valid + memset( &newSnap, 0, sizeof( newSnap ) ); + + // we will have read any new server commands in this + // message before we got to svc_snapshot + newSnap.serverCommandNum = clc.serverCommandSequence; + + newSnap.serverTime = MSG_ReadLong( msg ); + + newSnap.messageNum = clc.serverMessageSequence; + + deltaNum = MSG_ReadByte( msg ); + if ( !deltaNum ) { + newSnap.deltaNum = -1; + } else { + newSnap.deltaNum = newSnap.messageNum - deltaNum; + } + newSnap.snapFlags = MSG_ReadByte( msg ); + + // If the frame is delta compressed from data that we + // no longer have available, we must suck up the rest of + // the frame, but not use it, then ask for a non-compressed + // message + if ( newSnap.deltaNum <= 0 ) { + newSnap.valid = qtrue; // uncompressed frame + old = NULL; + clc.demowaiting = qfalse; // we can start recording now + } else { + old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; + if ( !old->valid ) { + // should never happen + Com_Printf( "Delta from invalid frame (not supposed to happen!).\n" ); + } else if ( old->messageNum != newSnap.deltaNum ) { + // The frame that the server did the delta from + // is too old, so we can't reconstruct it properly. + Com_DPrintf( "Delta frame too old.\n" ); + } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES - 128 ) { + Com_DPrintf( "Delta parseEntitiesNum too old.\n" ); + } else { + newSnap.valid = qtrue; // valid delta parse + } + } + + // read areamask + len = MSG_ReadByte( msg ); + + if ( len > sizeof( newSnap.areamask ) ) { + Com_Error( ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask.", len ); + return; + } + + MSG_ReadData( msg, &newSnap.areamask, len ); + + // read playerinfo + SHOWNET( msg, "playerstate" ); + if ( old ) { + MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps ); + } else { + MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps ); + } + + // read packet entities + SHOWNET( msg, "packet entities" ); + CL_ParsePacketEntities( msg, old, &newSnap ); + + // if not valid, dump the entire thing now that it has + // been properly read + if ( !newSnap.valid ) { + return; + } + + // clear the valid flags of any snapshots between the last + // received and this one, so if there was a dropped packet + // it won't look like something valid to delta from next + // time we wrap around in the buffer + oldMessageNum = cl.snap.messageNum + 1; + + if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { + oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); + } + for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { + cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; + } + + // copy to the current good spot + cl.snap = newSnap; + cl.snap.ping = 999; + // calculate ping time + for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { + packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK; + if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) { + cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime; + break; + } + } + // save the frame off in the backup array for later delta comparisons + cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; + + if ( cl_shownet->integer == 3 ) { + Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, + cl.snap.deltaNum, cl.snap.ping ); + } + + cl.newSnapshots = qtrue; +} + + +//===================================================================== + +int cl_connectedToPureServer; + +/* +================== +CL_SystemInfoChanged + +The systeminfo configstring has been changed, so parse +new information out of it. This will happen at every +gamestate, and possibly during gameplay. +================== +*/ +void CL_SystemInfoChanged( void ) { + char *systemInfo; + const char *s, *t; + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + + systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ]; + // NOTE TTimo: + // when the serverId changes, any further messages we send to the server will use this new serverId + // show_bug.cgi?id=475 + // in some cases, outdated cp commands might get sent with this news serverId + cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) ); + + memset( &entLastVisible, 0, sizeof( entLastVisible ) ); + + // don't set any vars when playing a demo + if ( clc.demoplaying ) { + return; + } + + s = Info_ValueForKey( systemInfo, "sv_cheats" ); + if ( atoi( s ) == 0 ) { + Cvar_SetCheatState(); + } + + // check pure server string + s = Info_ValueForKey( systemInfo, "sv_paks" ); + t = Info_ValueForKey( systemInfo, "sv_pakNames" ); + FS_PureServerSetLoadedPaks( s, t ); + + s = Info_ValueForKey( systemInfo, "sv_referencedPaks" ); + t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" ); + FS_PureServerSetReferencedPaks( s, t ); + + // scan through all the variables in the systeminfo and locally set cvars to match + s = systemInfo; + while ( s ) { + Info_NextPair( &s, key, value ); + if ( !key[0] ) { + break; + } + + Cvar_Set( key, value ); + } + cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" ); +} + +/* +================== +CL_ParseGamestate +================== +*/ +void CL_ParseGamestate( msg_t *msg ) { + int i; + entityState_t *es; + int newnum; + entityState_t nullstate; + int cmd; + char *s; + + Con_Close(); + + clc.connectPacketCount = 0; + + // wipe local client state + CL_ClearState(); + + // a gamestate always marks a server command sequence + clc.serverCommandSequence = MSG_ReadLong( msg ); + + // parse all the configstrings and baselines + cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings + while ( 1 ) { + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF ) { + break; + } + + if ( cmd == svc_configstring ) { + int len; + + i = MSG_ReadShort( msg ); + if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + s = MSG_ReadBigString( msg ); + len = strlen( s ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); + cl.gameState.dataCount += len + 1; + } else if ( cmd == svc_baseline ) { + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + if ( newnum < 0 || newnum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); + } + memset( &nullstate, 0, sizeof( nullstate ) ); + es = &cl.entityBaselines[ newnum ]; + MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); + } else { + Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); + } + } + + clc.clientNum = MSG_ReadLong( msg ); + // read the checksum feed + clc.checksumFeed = MSG_ReadLong( msg ); + + // parse serverId and other cvars + CL_SystemInfoChanged(); + + // reinitialize the filesystem if the game directory has changed + if ( FS_ConditionalRestart( clc.checksumFeed ) ) { + // don't set to true because we yet have to start downloading + // enabling this can cause double loading of a map when connecting to + // a server which has a different game directory set + //clc.downloadRestart = qtrue; + } + + // This used to call CL_StartHunkUsers, but now we enter the download state before loading the + // cgame + CL_InitDownloads(); + + // make sure the game starts + Cvar_Set( "cl_paused", "0" ); +} + + +//===================================================================== + +/* +===================== +CL_ParseDownload + +A download message has been received from the server +===================== +*/ +void CL_ParseDownload( msg_t *msg ) { + int size; + unsigned char data[MAX_MSGLEN]; + int block; + + if ( !*clc.downloadTempName ) { + Com_Printf( "Server sending download, but no download was requested\n" ); + CL_AddReliableCommand( "stopdl" ); + return; + } + + // read the data + block = MSG_ReadShort( msg ); + + if ( !block ) { + // block zero is special, contains file size + clc.downloadSize = MSG_ReadLong( msg ); + + Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); + + if ( clc.downloadSize < 0 ) { + Com_Error( ERR_DROP, MSG_ReadString( msg ) ); + return; + } + } + + size = MSG_ReadShort( msg ); + if ( size < 0 || size > sizeof( data ) ) { + Com_Error( ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk.", size ); + return; + } + + MSG_ReadData( msg, data, size ); + + if ( clc.downloadBlock != block ) { + Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", clc.downloadBlock, block ); + return; + } + + // open the file if not opened yet + if ( !clc.download ) { + clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName ); + + if ( !clc.download ) { + Com_Printf( "Could not create %s\n", clc.downloadTempName ); + CL_AddReliableCommand( "stopdl" ); + CL_NextDownload(); + return; + } + } + + if ( size ) { + FS_Write( data, size, clc.download ); + } + + CL_AddReliableCommand( va( "nextdl %d", clc.downloadBlock ) ); + clc.downloadBlock++; + + clc.downloadCount += size; + + // So UI gets access to it + Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); + + if ( !size ) { // A zero length block means EOF + if ( clc.download ) { + FS_FCloseFile( clc.download ); + clc.download = 0; + + // rename the file + FS_SV_Rename( clc.downloadTempName, clc.downloadName ); + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + // send intentions now + // We need this because without it, we would hold the last nextdl and then start + // loading right away. If we take a while to load, the server is happily trying + // to send us that last block over and over. + // Write it twice to help make sure we acknowledge the download + CL_WritePacket(); + CL_WritePacket(); + + // get another file if needed + CL_NextDownload(); + } +} + +/* +===================== +CL_ParseCommandString + +Command strings are just saved off until cgame asks for them +when it transitions a snapshot +===================== +*/ +void CL_ParseCommandString( msg_t *msg ) { + char *s; + int seq; + int index; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed stored it off + if ( clc.serverCommandSequence >= seq ) { + return; + } + clc.serverCommandSequence = seq; + + index = seq & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) ); +} + + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage( msg_t *msg ) { + int cmd; + msg_t msgback; + + msgback = *msg; + + if ( cl_shownet->integer == 1 ) { + Com_Printf( "%i ",msg->cursize ); + } else if ( cl_shownet->integer >= 2 ) { + Com_Printf( "------------------\n" ); + } + + MSG_Bitstream( msg ); + + // get the reliable sequence acknowledge number + clc.reliableAcknowledge = MSG_ReadLong( msg ); + // + if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { + clc.reliableAcknowledge = clc.reliableSequence; + } + + // + // parse the message + // + while ( 1 ) { + if ( msg->readcount > msg->cursize ) { + Com_Error( ERR_DROP,"CL_ParseServerMessage: read past end of server message" ); + break; + } + + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF ) { + SHOWNET( msg, "END OF MESSAGE" ); + break; + } + + if ( cl_shownet->integer >= 2 ) { + if ( !svc_strings[cmd] ) { + Com_Printf( "%3i:BAD CMD %i\n", msg->readcount - 1, cmd ); + } else { + SHOWNET( msg, svc_strings[cmd] ); + } + } + + // other commands + switch ( cmd ) { + default: + Com_Error( ERR_DROP,"CL_ParseServerMessage: Illegible server message %d\n", cmd ); + break; + case svc_nop: + break; + case svc_serverCommand: + CL_ParseCommandString( msg ); + break; + case svc_gamestate: + CL_ParseGamestate( msg ); + break; + case svc_snapshot: + CL_ParseSnapshot( msg ); + break; + case svc_download: + CL_ParseDownload( msg ); + break; + } + } +} diff --git a/src/client/cl_scrn.c b/src/client/cl_scrn.c new file mode 100644 index 0000000..655f34d --- /dev/null +++ b/src/client/cl_scrn.c @@ -0,0 +1,555 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc + +#include "client.h" + +qboolean scr_initialized; // ready to draw + +cvar_t *cl_timegraph; +cvar_t *cl_debuggraph; +cvar_t *cl_graphheight; +cvar_t *cl_graphscale; +cvar_t *cl_graphshift; + +/* +================ +SCR_DrawNamedPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { + qhandle_t hShader; + + assert( width != 0 ); + + hShader = re.RegisterShader( picname ); + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + +/* +================ +SCR_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ) { + float xscale; + float yscale; + +#if 0 + // adjust for wide screens + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + + // scale for screen sizes + xscale = cls.glconfig.vidWidth / 640.0; + yscale = cls.glconfig.vidHeight / 480.0; + if ( x ) { + *x *= xscale; + } + if ( y ) { + *y *= yscale; + } + if ( w ) { + *w *= xscale; + } + if ( h ) { + *h *= yscale; + } +} + +/* +================ +SCR_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_FillRect( float x, float y, float width, float height, const float *color ) { + re.SetColor( color ); + + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader ); + + re.SetColor( NULL ); +} + + +/* +================ +SCR_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +/* +** SCR_DrawChar +** chars are drawn at 640*480 virtual screen size +*/ +static void SCR_DrawChar( int x, int y, float size, int ch ) { + int row, col; + float frow, fcol; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -size ) { + return; + } + + ax = x; + ay = y; + aw = size; + ah = size; + SCR_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625; + fcol = col * 0.0625; + size = 0.0625; + + re.DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cls.charSetShader ); +} + +/* +** SCR_DrawSmallChar +** small chars are drawn at native screen resolution +*/ +void SCR_DrawSmallChar( int x, int y, int ch ) { + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -SMALLCHAR_HEIGHT ) { + return; + } + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625; + fcol = col * 0.0625; + size = 0.0625; + + re.DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, + fcol, frow, + fcol + size, frow + size, + cls.charSetShader ); +} + + +/* +================== +SCR_DrawBigString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void SCR_DrawStringExt( int x, int y, float size, const char *string, float *setColor, qboolean forceColor ) { + vec4_t color; + const char *s; + int xx; + + // draw the drop shadow + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + re.SetColor( color ); + s = string; + xx = x; + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + SCR_DrawChar( xx + 2, y + 2, size, *s ); + xx += size; + s++; + } + + + // draw the colored text + s = string; + xx = x; + re.SetColor( setColor ); + while ( *s ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( color ) ); + color[3] = setColor[3]; + re.SetColor( color ); + } + s += 2; + continue; + } + SCR_DrawChar( xx, y, size, *s ); + xx += size; + s++; + } + re.SetColor( NULL ); +} + + +void SCR_DrawBigString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qfalse ); +} + +void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { + SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qtrue ); +} + + +/* +================== +SCR_DrawSmallString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor ) { + vec4_t color; + const char *s; + int xx; + + // draw the colored text + s = string; + xx = x; + re.SetColor( setColor ); + while ( *s ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( color ) ); + color[3] = setColor[3]; + re.SetColor( color ); + } + s += 2; + continue; + } + SCR_DrawSmallChar( xx, y, *s ); + xx += SMALLCHAR_WIDTH; + s++; + } + re.SetColor( NULL ); +} + + + +/* +** SCR_Strlen -- skips color escape codes +*/ +static int SCR_Strlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +** SCR_GetBigStringWidth +*/ +int SCR_GetBigStringWidth( const char *str ) { + return SCR_Strlen( str ) * 16; +} + + +//=============================================================================== + +/* +================= +SCR_DrawDemoRecording +================= +*/ +void SCR_DrawDemoRecording( void ) { + char string[1024]; + int pos; + + if ( !clc.demorecording ) { + return; + } + + pos = FS_FTell( clc.demofile ); + sprintf( string, "RECORDING %s: %ik", clc.demoName, pos / 1024 ); + + SCR_DrawStringExt( 5, 470, 8, string, g_color_table[7], qtrue ); +} + + +/* +=============================================================================== + +DEBUG GRAPH + +=============================================================================== +*/ + +typedef struct +{ + float value; + int color; +} graphsamp_t; + +static int current; +static graphsamp_t values[1024]; + +/* +============== +SCR_DebugGraph +============== +*/ +void SCR_DebugGraph( float value, int color ) { + values[current & 1023].value = value; + values[current & 1023].color = color; + current++; +} + +/* +============== +SCR_DrawDebugGraph +============== +*/ +void SCR_DrawDebugGraph( void ) { + int a, x, y, w, i, h; + float v; + int color; + + // + // draw the graph + // + w = cls.glconfig.vidWidth; + x = 0; + y = cls.glconfig.vidHeight; + re.SetColor( g_color_table[0] ); + re.DrawStretchPic( x, y - cl_graphheight->integer, + w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + + for ( a = 0 ; a < w ; a++ ) + { + i = ( current - 1 - a + 1024 ) & 1023; + v = values[i].value; + color = values[i].color; + v = v * cl_graphscale->integer + cl_graphshift->integer; + + if ( v < 0 ) { + v += cl_graphheight->integer * ( 1 + (int)( -v / cl_graphheight->integer ) ); + } + h = (int)v % cl_graphheight->integer; + re.DrawStretchPic( x + w - 1 - a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader ); + } +} + +//============================================================================= + +/* +================== +SCR_Init +================== +*/ +void SCR_Init( void ) { + cl_timegraph = Cvar_Get( "timegraph", "0", CVAR_CHEAT ); + cl_debuggraph = Cvar_Get( "debuggraph", "0", CVAR_CHEAT ); + cl_graphheight = Cvar_Get( "graphheight", "32", CVAR_CHEAT ); + cl_graphscale = Cvar_Get( "graphscale", "1", CVAR_CHEAT ); + cl_graphshift = Cvar_Get( "graphshift", "0", CVAR_CHEAT ); + + scr_initialized = qtrue; +} + + +//======================================================= + +/* +================== +SCR_DrawScreenField + +This will be called twice if rendering in stereo mode +================== +*/ +void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { + re.BeginFrame( stereoFrame ); + + // wide aspect ratio screens need to have the sides cleared + // unless they are displaying game renderings + if ( cls.state != CA_ACTIVE ) { + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + re.SetColor( g_color_table[0] ); + re.DrawStretchPic( 0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + } + } + + if ( !uivm ) { + Com_DPrintf( "draw screen without UI loaded\n" ); + return; + } + + // if the menu is going to cover the entire screen, we + // don't need to render anything under it + if ( !VM_Call( uivm, UI_IS_FULLSCREEN ) ) { + switch ( cls.state ) { + default: + Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad cls.state" ); + break; + case CA_CINEMATIC: + SCR_DrawCinematic(); + break; + case CA_DISCONNECTED: + // force menu up + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + break; + case CA_CONNECTING: + case CA_CHALLENGING: + case CA_CONNECTED: + // connecting clients will only show the connection dialog + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); + break; +// // Ridah, if the cgame is valid, fall through to there +// if (!cls.cgameStarted || !com_sv_running->integer) { +// // connecting clients will only show the connection dialog +// VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); +// break; +// } + case CA_LOADING: + case CA_PRIMED: + // draw the game information screen and loading progress + CL_CGameRendering( stereoFrame ); + + // also draw the connection information, so it doesn't + // flash away too briefly on local or lan games + //if (!com_sv_running->value || Cvar_VariableIntegerValue("sv_cheats")) // Ridah, don't draw useless text if not in dev mode + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue ); + break; + case CA_ACTIVE: + CL_CGameRendering( stereoFrame ); + SCR_DrawDemoRecording(); + break; + } + } + + // the menu draws next + if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_REFRESH, cls.realtime ); + } + + // console draws next + Con_DrawConsole(); + + // debug graph can be drawn on top of anything + if ( cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer ) { + SCR_DrawDebugGraph(); + } +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. +================== +*/ +void SCR_UpdateScreen( void ) { + static int recursive; + + if ( !scr_initialized ) { + return; // not initialized yet + } + + if ( ++recursive > 2 ) { + Com_Error( ERR_FATAL, "SCR_UpdateScreen: recursively called" ); + } + recursive = 1; + + // if running in stereo, we need to draw the frame twice + if ( cls.glconfig.stereoEnabled ) { + SCR_DrawScreenField( STEREO_LEFT ); + SCR_DrawScreenField( STEREO_RIGHT ); + } else { + SCR_DrawScreenField( STEREO_CENTER ); + } + + if ( com_speeds->integer ) { + re.EndFrame( &time_frontend, &time_backend ); + } else { + re.EndFrame( NULL, NULL ); + } + + recursive = 0; +} diff --git a/src/client/cl_ui.c b/src/client/cl_ui.c new file mode 100644 index 0000000..37aae19 --- /dev/null +++ b/src/client/cl_ui.c @@ -0,0 +1,1259 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "client.h" + +#include "../game/botlib.h" + +extern botlib_export_t *botlib_export; + +vm_t *uivm; + +/* +==================== +GetClientState +==================== +*/ +static void GetClientState( uiClientState_t *state ) { + state->connectPacketCount = clc.connectPacketCount; + state->connState = cls.state; + Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) ); + Q_strncpyz( state->updateInfoString, cls.updateInfoString, sizeof( state->updateInfoString ) ); + Q_strncpyz( state->messageString, clc.serverMessage, sizeof( state->messageString ) ); + state->clientNum = cl.snap.ps.clientNum; +} + +/* +==================== +LAN_LoadCachedServers +==================== +*/ +void LAN_LoadCachedServers() { + int size; + fileHandle_t fileIn; + cls.numglobalservers = cls.nummplayerservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + if ( FS_SV_FOpenFileRead( "servercache.dat", &fileIn ) ) { + FS_Read( &cls.numglobalservers, sizeof( int ), fileIn ); + FS_Read( &cls.nummplayerservers, sizeof( int ), fileIn ); + FS_Read( &cls.numfavoriteservers, sizeof( int ), fileIn ); + FS_Read( &size, sizeof( int ), fileIn ); + if ( size == sizeof( cls.globalServers ) + sizeof( cls.favoriteServers ) + sizeof( cls.mplayerServers ) ) { + FS_Read( &cls.globalServers, sizeof( cls.globalServers ), fileIn ); + FS_Read( &cls.mplayerServers, sizeof( cls.mplayerServers ), fileIn ); + FS_Read( &cls.favoriteServers, sizeof( cls.favoriteServers ), fileIn ); + } else { + cls.numglobalservers = cls.nummplayerservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + } + FS_FCloseFile( fileIn ); + } +} + +/* +==================== +LAN_SaveServersToCache +==================== +*/ +void LAN_SaveServersToCache() { + int size; + fileHandle_t fileOut; +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'WlfB'; + _fcreator = 'WlfM'; + } +#endif + fileOut = FS_SV_FOpenFileWrite( "servercache.dat" ); + FS_Write( &cls.numglobalservers, sizeof( int ), fileOut ); + FS_Write( &cls.nummplayerservers, sizeof( int ), fileOut ); + FS_Write( &cls.numfavoriteservers, sizeof( int ), fileOut ); + size = sizeof( cls.globalServers ) + sizeof( cls.favoriteServers ) + sizeof( cls.mplayerServers ); + FS_Write( &size, sizeof( int ), fileOut ); + FS_Write( &cls.globalServers, sizeof( cls.globalServers ), fileOut ); + FS_Write( &cls.mplayerServers, sizeof( cls.mplayerServers ), fileOut ); + FS_Write( &cls.favoriteServers, sizeof( cls.favoriteServers ), fileOut ); + FS_FCloseFile( fileOut ); +} + + +/* +==================== +LAN_ResetPings +==================== +*/ +static void LAN_ResetPings( int source ) { + int count,i; + serverInfo_t *servers = NULL; + count = 0; + + switch ( source ) { + case AS_LOCAL: + servers = &cls.localServers[0]; + count = MAX_OTHER_SERVERS; + break; + case AS_MPLAYER: + servers = &cls.mplayerServers[0]; + count = MAX_OTHER_SERVERS; + break; + case AS_GLOBAL: + servers = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES: + servers = &cls.favoriteServers[0]; + count = MAX_OTHER_SERVERS; + break; + } + if ( servers ) { + for ( i = 0; i < count; i++ ) { + servers[i].ping = -1; + } + } +} + +/* +==================== +LAN_AddServer +==================== +*/ +static int LAN_AddServer( int source, const char *name, const char *address ) { + int max, *count, i; + netadr_t adr; + serverInfo_t *servers = NULL; + max = MAX_OTHER_SERVERS; + count = 0; + + switch ( source ) { + case AS_LOCAL: + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER: + count = &cls.nummplayerservers; + servers = &cls.mplayerServers[0]; + break; + case AS_GLOBAL: + max = MAX_GLOBAL_SERVERS; + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES: + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if ( servers && *count < max ) { + NET_StringToAdr( address, &adr ); + for ( i = 0; i < *count; i++ ) { + if ( NET_CompareAdr( servers[i].adr, adr ) ) { + break; + } + } + if ( i >= *count ) { + servers[*count].adr = adr; + Q_strncpyz( servers[*count].hostName, name, sizeof( servers[*count].hostName ) ); + servers[*count].visible = qtrue; + ( *count )++; + return 1; + } + return 0; + } + return -1; +} + +/* +==================== +LAN_RemoveServer +==================== +*/ +static void LAN_RemoveServer( int source, const char *addr ) { + int *count, i; + serverInfo_t *servers = NULL; + count = 0; + switch ( source ) { + case AS_LOCAL: + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER: + count = &cls.nummplayerservers; + servers = &cls.mplayerServers[0]; + break; + case AS_GLOBAL: + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES: + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if ( servers ) { + netadr_t comp; + NET_StringToAdr( addr, &comp ); + for ( i = 0; i < *count; i++ ) { + if ( NET_CompareAdr( comp, servers[i].adr ) ) { + int j = i; + while ( j < *count - 1 ) { + Com_Memcpy( &servers[j], &servers[j + 1], sizeof( servers[j] ) ); + j++; + } + ( *count )--; + break; + } + } + } +} + + +/* +==================== +LAN_GetServerCount +==================== +*/ +static int LAN_GetServerCount( int source ) { + switch ( source ) { + case AS_LOCAL: + return cls.numlocalservers; + break; + case AS_MPLAYER: + return cls.nummplayerservers; + break; + case AS_GLOBAL: + return cls.numglobalservers; + break; + case AS_FAVORITES: + return cls.numfavoriteservers; + break; + } + return 0; +} + +/* +==================== +LAN_GetLocalServerAddressString +==================== +*/ +static void LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { + switch ( source ) { + case AS_LOCAL: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + Q_strncpyz( buf, NET_AdrToString( cls.localServers[n].adr ), buflen ); + return; + } + break; + case AS_MPLAYER: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + Q_strncpyz( buf, NET_AdrToString( cls.mplayerServers[n].adr ), buflen ); + return; + } + break; + case AS_GLOBAL: + if ( n >= 0 && n < MAX_GLOBAL_SERVERS ) { + Q_strncpyz( buf, NET_AdrToString( cls.globalServers[n].adr ), buflen ); + return; + } + break; + case AS_FAVORITES: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + Q_strncpyz( buf, NET_AdrToString( cls.favoriteServers[n].adr ), buflen ); + return; + } + break; + } + buf[0] = '\0'; +} + +/* +==================== +LAN_GetServerInfo +==================== +*/ +static void LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { + char info[MAX_STRING_CHARS]; + serverInfo_t *server = NULL; + info[0] = '\0'; + switch ( source ) { + case AS_LOCAL: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + server = &cls.mplayerServers[n]; + } + break; + case AS_GLOBAL: + if ( n >= 0 && n < MAX_GLOBAL_SERVERS ) { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + server = &cls.favoriteServers[n]; + } + break; + } + if ( server && buf ) { + buf[0] = '\0'; + Info_SetValueForKey( info, "hostname", server->hostName ); + Info_SetValueForKey( info, "mapname", server->mapName ); + Info_SetValueForKey( info, "clients", va( "%i",server->clients ) ); + Info_SetValueForKey( info, "sv_maxclients", va( "%i",server->maxClients ) ); + Info_SetValueForKey( info, "ping", va( "%i",server->ping ) ); + Info_SetValueForKey( info, "minping", va( "%i",server->minPing ) ); + Info_SetValueForKey( info, "maxping", va( "%i",server->maxPing ) ); + Info_SetValueForKey( info, "game", server->game ); + Info_SetValueForKey( info, "gametype", va( "%i",server->gameType ) ); + Info_SetValueForKey( info, "nettype", va( "%i",server->netType ) ); + Info_SetValueForKey( info, "addr", NET_AdrToString( server->adr ) ); + Info_SetValueForKey( info, "sv_allowAnonymous", va( "%i", server->allowAnonymous ) ); + Info_SetValueForKey( info, "friendlyFire", va( "%i", server->friendlyFire ) ); // NERVE - SMF + Info_SetValueForKey( info, "maxlives", va( "%i", server->maxlives ) ); // NERVE - SMF + Info_SetValueForKey( info, "tourney", va( "%i", server->tourney ) ); // NERVE - SMF + Info_SetValueForKey( info, "punkbuster", va( "%i", server->punkbuster ) ); // DHM - Nerve + Info_SetValueForKey( info, "gamename", server->gameName ); // Arnout + Info_SetValueForKey( info, "g_antilag", va( "%i", server->antilag ) ); // TTimo + Q_strncpyz( buf, info, buflen ); + } else { + if ( buf ) { + buf[0] = '\0'; + } + } +} + +/* +==================== +LAN_GetServerPing +==================== +*/ +static int LAN_GetServerPing( int source, int n ) { + serverInfo_t *server = NULL; + switch ( source ) { + case AS_LOCAL: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + server = &cls.mplayerServers[n]; + } + break; + case AS_GLOBAL: + if ( n >= 0 && n < MAX_GLOBAL_SERVERS ) { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + server = &cls.favoriteServers[n]; + } + break; + } + if ( server ) { + return server->ping; + } + return -1; +} + +/* +==================== +LAN_GetServerPtr +==================== +*/ +static serverInfo_t *LAN_GetServerPtr( int source, int n ) { + switch ( source ) { + case AS_LOCAL: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + return &cls.localServers[n]; + } + break; + case AS_MPLAYER: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + return &cls.mplayerServers[n]; + } + break; + case AS_GLOBAL: + if ( n >= 0 && n < MAX_GLOBAL_SERVERS ) { + return &cls.globalServers[n]; + } + break; + case AS_FAVORITES: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + return &cls.favoriteServers[n]; + } + break; + } + return NULL; +} + +/* +==================== +LAN_CompareServers +==================== +*/ +static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { + int res; + serverInfo_t *server1, *server2; + + server1 = LAN_GetServerPtr( source, s1 ); + server2 = LAN_GetServerPtr( source, s2 ); + if ( !server1 || !server2 ) { + return 0; + } + + res = 0; + switch ( sortKey ) { + case SORT_HOST: + res = Q_stricmp( server1->hostName, server2->hostName ); + break; + + case SORT_MAP: + res = Q_stricmp( server1->mapName, server2->mapName ); + break; + case SORT_CLIENTS: + if ( server1->clients < server2->clients ) { + res = -1; + } else if ( server1->clients > server2->clients ) { + res = 1; + } else { + res = 0; + } + break; + case SORT_GAME: + if ( server1->gameType < server2->gameType ) { + res = -1; + } else if ( server1->gameType > server2->gameType ) { + res = 1; + } else { + res = 0; + } + break; + case SORT_PING: + if ( server1->ping < server2->ping ) { + res = -1; + } else if ( server1->ping > server2->ping ) { + res = 1; + } else { + res = 0; + } + break; + case SORT_PUNKBUSTER: + if ( server1->punkbuster < server2->punkbuster ) { + res = -1; + } else if ( server1->punkbuster > server2->punkbuster ) { + res = 1; + } else { + res = 0; + } + } + + if ( sortDir ) { + if ( res < 0 ) { + return 1; + } + if ( res > 0 ) { + return -1; + } + return 0; + } + return res; +} + +/* +==================== +LAN_GetPingQueueCount +==================== +*/ +static int LAN_GetPingQueueCount( void ) { + return ( CL_GetPingQueueCount() ); +} + +/* +==================== +LAN_ClearPing +==================== +*/ +static void LAN_ClearPing( int n ) { + CL_ClearPing( n ); +} + +/* +==================== +LAN_GetPing +==================== +*/ +static void LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { + CL_GetPing( n, buf, buflen, pingtime ); +} + +/* +==================== +LAN_GetPingInfo +==================== +*/ +static void LAN_GetPingInfo( int n, char *buf, int buflen ) { + CL_GetPingInfo( n, buf, buflen ); +} + +/* +==================== +LAN_MarkServerVisible +==================== +*/ +static void LAN_MarkServerVisible( int source, int n, qboolean visible ) { + if ( n == -1 ) { + int count = MAX_OTHER_SERVERS; + serverInfo_t *server = NULL; + switch ( source ) { + case AS_LOCAL: + server = &cls.localServers[0]; + break; + case AS_MPLAYER: + server = &cls.mplayerServers[0]; + break; + case AS_GLOBAL: + server = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES: + server = &cls.favoriteServers[0]; + break; + } + if ( server ) { + for ( n = 0; n < count; n++ ) { + server[n].visible = visible; + } + } + + } else { + switch ( source ) { + case AS_LOCAL: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + cls.localServers[n].visible = visible; + } + break; + case AS_MPLAYER: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + cls.mplayerServers[n].visible = visible; + } + break; + case AS_GLOBAL: + if ( n >= 0 && n < MAX_GLOBAL_SERVERS ) { + cls.globalServers[n].visible = visible; + } + break; + case AS_FAVORITES: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + cls.favoriteServers[n].visible = visible; + } + break; + } + } +} + + +/* +======================= +LAN_ServerIsVisible +======================= +*/ +static int LAN_ServerIsVisible( int source, int n ) { + switch ( source ) { + case AS_LOCAL: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + return cls.localServers[n].visible; + } + break; + case AS_MPLAYER: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + return cls.mplayerServers[n].visible; + } + break; + case AS_GLOBAL: + if ( n >= 0 && n < MAX_GLOBAL_SERVERS ) { + return cls.globalServers[n].visible; + } + break; + case AS_FAVORITES: + if ( n >= 0 && n < MAX_OTHER_SERVERS ) { + return cls.favoriteServers[n].visible; + } + break; + } + return qfalse; +} + +/* +======================= +LAN_UpdateVisiblePings +======================= +*/ +qboolean LAN_UpdateVisiblePings( int source ) { + return CL_UpdateVisiblePings_f( source ); +} + +/* +==================== +LAN_GetServerStatus +==================== +*/ +int LAN_GetServerStatus( char *serverAddress, char *serverStatus, int maxLen ) { + return CL_ServerStatus( serverAddress, serverStatus, maxLen ); +} + +/* +==================== +CL_GetGlConfig +==================== +*/ +static void CL_GetGlconfig( glconfig_t *config ) { + *config = cls.glconfig; +} + +/* +==================== +GetClipboardData +==================== +*/ +static void GetClipboardData( char *buf, int buflen ) { + char *cbd; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + *buf = 0; + return; + } + + Q_strncpyz( buf, cbd, buflen ); + + Z_Free( cbd ); +} + +/* +==================== +Key_KeynumToStringBuf +==================== +*/ +void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { + Q_strncpyz( buf, Key_KeynumToString( keynum, qtrue ), buflen ); +} + +/* +==================== +Key_GetBindingBuf +==================== +*/ +void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + char *value; + + value = Key_GetBinding( keynum ); + if ( value ) { + Q_strncpyz( buf, value, buflen ); + } else { + *buf = 0; + } +} + +/* +==================== +Key_GetCatcher +==================== +*/ +int Key_GetCatcher( void ) { + return cls.keyCatchers; +} + +/* +==================== +Ket_SetCatcher +==================== +*/ +void Key_SetCatcher( int catcher ) { + // NERVE - SMF - console overrides everything + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { + cls.keyCatchers = catcher | KEYCATCH_CONSOLE; + } else { + cls.keyCatchers = catcher; + } + +} + + +/* +==================== +CLUI_GetCDKey +==================== +*/ +static void CLUI_GetCDKey( char *buf, int buflen ) { + cvar_t *fs; + fs = Cvar_Get( "fs_game", "", CVAR_INIT | CVAR_SYSTEMINFO ); + if ( UI_usesUniqueCDKey() && fs && fs->string[0] != 0 ) { + memcpy( buf, &cl_cdkey[16], 16 ); + buf[16] = 0; + } else { + memcpy( buf, cl_cdkey, 16 ); + buf[16] = 0; + } +} + + +/* +==================== +CLUI_SetCDKey +==================== +*/ +static void CLUI_SetCDKey( char *buf ) { + cvar_t *fs; + fs = Cvar_Get( "fs_game", "", CVAR_INIT | CVAR_SYSTEMINFO ); + if ( UI_usesUniqueCDKey() && fs && fs->string[0] != 0 ) { + memcpy( &cl_cdkey[16], buf, 16 ); + cl_cdkey[32] = 0; + // set the flag so the fle will be written at the next opportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } else { + memcpy( cl_cdkey, buf, 16 ); + // set the flag so the fle will be written at the next opportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } +} + + +/* +==================== +GetConfigString +==================== +*/ +static int GetConfigString( int index, char *buf, int size ) { + int offset; + + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + return qfalse; + } + + offset = cl.gameState.stringOffsets[index]; + if ( !offset ) { + if ( size ) { + buf[0] = 0; + } + return qfalse; + } + + Q_strncpyz( buf, cl.gameState.stringData + offset, size ); + + return qtrue; +} + +/* +==================== +FloatAsInt +==================== +*/ +static int FloatAsInt( float f ) { + int temp; + + *(float *)&temp = f; + + return temp; +} + +void *VM_ArgPtr( int intValue ); +#define VMA( x ) VM_ArgPtr( args[x] ) +#define VMF( x ) ( (float *)args )[x] + +/* +==================== +CL_UISystemCalls + +The ui module is making a system call +==================== +*/ +int CL_UISystemCalls( int *args ) { + switch ( args[0] ) { + case UI_ERROR: + Com_Error( ERR_DROP, "%s", VMA( 1 ) ); + return 0; + + case UI_PRINT: + Com_Printf( "%s", VMA( 1 ) ); + return 0; + + case UI_MILLISECONDS: + return Sys_Milliseconds(); + + case UI_CVAR_REGISTER: + Cvar_Register( VMA( 1 ), VMA( 2 ), VMA( 3 ), args[4] ); + return 0; + + case UI_CVAR_UPDATE: + Cvar_Update( VMA( 1 ) ); + return 0; + + case UI_CVAR_SET: + Cvar_Set( VMA( 1 ), VMA( 2 ) ); + return 0; + + case UI_CVAR_VARIABLEVALUE: + return FloatAsInt( Cvar_VariableValue( VMA( 1 ) ) ); + + case UI_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( VMA( 1 ), VMA( 2 ), args[3] ); + return 0; + + case UI_CVAR_SETVALUE: + Cvar_SetValue( VMA( 1 ), VMF( 2 ) ); + return 0; + + case UI_CVAR_RESET: + Cvar_Reset( VMA( 1 ) ); + return 0; + + case UI_CVAR_CREATE: + Cvar_Get( VMA( 1 ), VMA( 2 ), args[3] ); + return 0; + + case UI_CVAR_INFOSTRINGBUFFER: + Cvar_InfoStringBuffer( args[1], VMA( 2 ), args[3] ); + return 0; + + case UI_ARGC: + return Cmd_Argc(); + + case UI_ARGV: + Cmd_ArgvBuffer( args[1], VMA( 2 ), args[3] ); + return 0; + + case UI_CMD_EXECUTETEXT: + Cbuf_ExecuteText( args[1], VMA( 2 ) ); + return 0; + + case UI_FS_FOPENFILE: + return FS_FOpenFileByMode( VMA( 1 ), VMA( 2 ), args[3] ); + + case UI_FS_READ: + FS_Read( VMA( 1 ), args[2], args[3] ); + return 0; + + case UI_FS_WRITE: + FS_Write( VMA( 1 ), args[2], args[3] ); + return 0; + + case UI_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + + case UI_FS_DELETEFILE: + return FS_Delete( VMA( 1 ) ); + + case UI_FS_GETFILELIST: + return FS_GetFileList( VMA( 1 ), VMA( 2 ), VMA( 3 ), args[4] ); + + case UI_R_REGISTERMODEL: + return re.RegisterModel( VMA( 1 ) ); + + case UI_R_REGISTERSKIN: + return re.RegisterSkin( VMA( 1 ) ); + + case UI_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( VMA( 1 ) ); + + case UI_R_CLEARSCENE: + re.ClearScene(); + return 0; + + case UI_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( VMA( 1 ) ); + return 0; + + case UI_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA( 3 ) ); + return 0; + + // Ridah + case UI_R_ADDPOLYSTOSCENE: + re.AddPolysToScene( args[1], args[2], VMA( 3 ), args[4] ); + return 0; + // done. + + case UI_R_ADDLIGHTTOSCENE: + re.AddLightToScene( VMA( 1 ), VMF( 2 ), VMF( 3 ), VMF( 4 ), VMF( 5 ), args[6] ); + return 0; + + case UI_R_ADDCORONATOSCENE: + re.AddCoronaToScene( VMA( 1 ), VMF( 2 ), VMF( 3 ), VMF( 4 ), VMF( 5 ), args[6], args[7] ); + return 0; + + case UI_R_RENDERSCENE: + re.RenderScene( VMA( 1 ) ); + return 0; + + case UI_R_SETCOLOR: + re.SetColor( VMA( 1 ) ); + return 0; + + case UI_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF( 1 ), VMF( 2 ), VMF( 3 ), VMF( 4 ), VMF( 5 ), VMF( 6 ), VMF( 7 ), VMF( 8 ), args[9] ); + return 0; + + case UI_R_MODELBOUNDS: + re.ModelBounds( args[1], VMA( 2 ), VMA( 3 ) ); + return 0; + + case UI_UPDATESCREEN: + SCR_UpdateScreen(); + return 0; + + case UI_CM_LERPTAG: + return re.LerpTag( VMA( 1 ), VMA( 2 ), VMA( 3 ), args[4] ); + + case UI_S_REGISTERSOUND: +#ifdef DOOMSOUND ///// (SA) DOOMSOUND + return S_RegisterSound( VMA( 1 ) ); +#else + return S_RegisterSound( VMA( 1 ), qfalse ); +#endif ///// (SA) DOOMSOUND + + case UI_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + + case UI_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf( args[1], VMA( 2 ), args[3] ); + return 0; + + case UI_KEY_GETBINDINGBUF: + Key_GetBindingBuf( args[1], VMA( 2 ), args[3] ); + return 0; + + case UI_KEY_SETBINDING: + Key_SetBinding( args[1], VMA( 2 ) ); + return 0; + + case UI_KEY_ISDOWN: + return Key_IsDown( args[1] ); + + case UI_KEY_GETOVERSTRIKEMODE: + return Key_GetOverstrikeMode(); + + case UI_KEY_SETOVERSTRIKEMODE: + Key_SetOverstrikeMode( args[1] ); + return 0; + + case UI_KEY_CLEARSTATES: + Key_ClearStates(); + return 0; + + case UI_KEY_GETCATCHER: + return Key_GetCatcher(); + + case UI_KEY_SETCATCHER: + Key_SetCatcher( args[1] ); + return 0; + + case UI_GETCLIPBOARDDATA: + GetClipboardData( VMA( 1 ), args[2] ); + return 0; + + case UI_GETCLIENTSTATE: + GetClientState( VMA( 1 ) ); + return 0; + + case UI_GETGLCONFIG: + CL_GetGlconfig( VMA( 1 ) ); + return 0; + + case UI_GETCONFIGSTRING: + return GetConfigString( args[1], VMA( 2 ), args[3] ); + + case UI_LAN_LOADCACHEDSERVERS: + LAN_LoadCachedServers(); + return 0; + + case UI_LAN_SAVECACHEDSERVERS: + LAN_SaveServersToCache(); + return 0; + + case UI_LAN_ADDSERVER: + return LAN_AddServer( args[1], VMA( 2 ), VMA( 3 ) ); + + case UI_LAN_REMOVESERVER: + LAN_RemoveServer( args[1], VMA( 2 ) ); + return 0; + + case UI_LAN_GETPINGQUEUECOUNT: + return LAN_GetPingQueueCount(); + + case UI_LAN_CLEARPING: + LAN_ClearPing( args[1] ); + return 0; + + case UI_LAN_GETPING: + LAN_GetPing( args[1], VMA( 2 ), args[3], VMA( 4 ) ); + return 0; + + case UI_LAN_GETPINGINFO: + LAN_GetPingInfo( args[1], VMA( 2 ), args[3] ); + return 0; + + case UI_LAN_GETSERVERCOUNT: + return LAN_GetServerCount( args[1] ); + + case UI_LAN_GETSERVERADDRESSSTRING: + LAN_GetServerAddressString( args[1], args[2], VMA( 3 ), args[4] ); + return 0; + + case UI_LAN_GETSERVERINFO: + LAN_GetServerInfo( args[1], args[2], VMA( 3 ), args[4] ); + return 0; + + case UI_LAN_GETSERVERPING: + return LAN_GetServerPing( args[1], args[2] ); + + case UI_LAN_MARKSERVERVISIBLE: + LAN_MarkServerVisible( args[1], args[2], args[3] ); + return 0; + + case UI_LAN_SERVERISVISIBLE: + return LAN_ServerIsVisible( args[1], args[2] ); + + case UI_LAN_UPDATEVISIBLEPINGS: + return LAN_UpdateVisiblePings( args[1] ); + + case UI_LAN_RESETPINGS: + LAN_ResetPings( args[1] ); + return 0; + + case UI_LAN_SERVERSTATUS: + return LAN_GetServerStatus( VMA( 1 ), VMA( 2 ), args[3] ); + + case UI_SET_PBCLSTATUS: + return 0; + + case UI_SET_PBSVSTATUS: + return 0; + + case UI_LAN_COMPARESERVERS: + return LAN_CompareServers( args[1], args[2], args[3], args[4], args[5] ); + + case UI_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + + case UI_GET_CDKEY: + CLUI_GetCDKey( VMA( 1 ), args[2] ); + return 0; + + case UI_SET_CDKEY: + CLUI_SetCDKey( VMA( 1 ) ); + return 0; + + case UI_R_REGISTERFONT: + re.RegisterFont( VMA( 1 ), args[2], VMA( 3 ) ); + return 0; + + case UI_MEMSET: + return (int)memset( VMA( 1 ), args[2], args[3] ); + + case UI_MEMCPY: + return (int)memcpy( VMA( 1 ), VMA( 2 ), args[3] ); + + case UI_STRNCPY: + return (int)strncpy( VMA( 1 ), VMA( 2 ), args[3] ); + + case UI_SIN: + return FloatAsInt( sin( VMF( 1 ) ) ); + + case UI_COS: + return FloatAsInt( cos( VMF( 1 ) ) ); + + case UI_ATAN2: + return FloatAsInt( atan2( VMF( 1 ), VMF( 2 ) ) ); + + case UI_SQRT: + return FloatAsInt( sqrt( VMF( 1 ) ) ); + + case UI_FLOOR: + return FloatAsInt( floor( VMF( 1 ) ) ); + + case UI_CEIL: + return FloatAsInt( ceil( VMF( 1 ) ) ); + + case UI_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( VMA( 1 ) ); + case UI_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( VMA( 1 ) ); + case UI_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case UI_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], VMA( 2 ) ); + case UI_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], VMA( 2 ), VMA( 3 ) ); + + case UI_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + case UI_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( VMA( 1 ), VMA( 2 ) ); + return 0; + + case UI_REAL_TIME: + return Com_RealTime( VMA( 1 ) ); + + case UI_CIN_PLAYCINEMATIC: + Com_DPrintf( "UI_CIN_PlayCinematic\n" ); + return CIN_PlayCinematic( VMA( 1 ), args[2], args[3], args[4], args[5], args[6] ); + + case UI_CIN_STOPCINEMATIC: + return CIN_StopCinematic( args[1] ); + + case UI_CIN_RUNCINEMATIC: + return CIN_RunCinematic( args[1] ); + + case UI_CIN_DRAWCINEMATIC: + CIN_DrawCinematic( args[1] ); + return 0; + + case UI_CIN_SETEXTENTS: + CIN_SetExtents( args[1], args[2], args[3], args[4], args[5] ); + return 0; + + case UI_R_REMAP_SHADER: + re.RemapShader( VMA( 1 ), VMA( 2 ), VMA( 3 ) ); + return 0; + + case UI_VERIFY_CDKEY: + return CL_CDKeyValidate( VMA( 1 ), VMA( 2 ) ); + + // NERVE - SMF + case UI_CL_GETLIMBOSTRING: + return CL_GetLimboString( args[1], VMA( 2 ) ); + + case UI_CL_TRANSLATE_STRING: + CL_TranslateString( VMA( 1 ), VMA( 2 ) ); + return 0; + // -NERVE - SMF + + // DHM - Nerve + case UI_CHECKAUTOUPDATE: + CL_CheckAutoUpdate(); + return 0; + + case UI_GET_AUTOUPDATE: + CL_GetAutoUpdate(); + return 0; + // DHM - Nerve + + case UI_OPENURL: + CL_OpenURL( (const char *)VMA( 1 ) ); + return 0; + + default: + Com_Error( ERR_DROP, "Bad UI system trap: %i", args[0] ); + + } + + return 0; +} + +/* +==================== +CL_ShutdownUI +==================== +*/ +void CL_ShutdownUI( void ) { + cls.keyCatchers &= ~KEYCATCH_UI; + cls.uiStarted = qfalse; + if ( !uivm ) { + return; + } + VM_Call( uivm, UI_SHUTDOWN ); + VM_Free( uivm ); + uivm = NULL; +} + +/* +==================== +CL_InitUI +==================== +*/ + +void CL_InitUI( void ) { + int v; + + uivm = VM_Create( "ui", CL_UISystemCalls, VMI_NATIVE ); + if ( !uivm ) { + Com_Error( ERR_FATAL, "VM_Create on UI failed" ); + } + + // sanity check + v = VM_Call( uivm, UI_GETAPIVERSION ); + if ( v != UI_API_VERSION ) { + Com_Error( ERR_FATAL, "User Interface is version %d, expected %d", v, UI_API_VERSION ); + cls.uiStarted = qfalse; + } + + // init for this gamestate + VM_Call( uivm, UI_INIT, ( cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE ) ); +} + + +qboolean UI_usesUniqueCDKey() { + if ( uivm ) { + return ( VM_Call( uivm, UI_HASUNIQUECDKEY ) == qtrue ); + } else { + return qfalse; + } +} + +qboolean UI_checkKeyExec( int key ) { + if ( uivm ) { + return VM_Call( uivm, UI_CHECKEXECKEY, key ); + } else { + return qfalse; + } +} + +/* +==================== +UI_GameCommand + +See if the current console command is claimed by the ui +==================== +*/ +qboolean UI_GameCommand( void ) { + if ( !uivm ) { + return qfalse; + } + + return VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime ); +} diff --git a/src/client/client.h b/src/client/client.h new file mode 100644 index 0000000..774903a --- /dev/null +++ b/src/client/client.h @@ -0,0 +1,632 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// client.h -- primary header for client + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +#include "../ui/ui_public.h" +#include "keys.h" +#include "snd_public.h" +#include "../cgame/cg_public.h" +#include "../game/bg_public.h" + +#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits + +#define LIMBOCHAT_WIDTH 140 // NERVE - SMF - NOTE TTimo buffer size indicator, not related to screen bbox +#define LIMBOCHAT_HEIGHT 7 // NERVE - SMF + +// snapshots are a view of the server at a given time +typedef struct { + qboolean valid; // cleared if delta parsing was invalid + int snapFlags; // rate delayed and dropped commands + + int serverTime; // server time the message is valid for (in msec) + + int messageNum; // copied from netchan->incoming_sequence + int deltaNum; // messageNum the delta is from + int ping; // time from when cmdNum-1 was sent to time packet was reeceived + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + int cmdNum; // the next cmdNum the server is expecting + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + int parseEntitiesNum; // at the time of this snapshot + + int serverCommandNum; // execute all commands up to this before + // making the snapshot current +} clSnapshot_t; + + + +/* +============================================================================= + +the clientActive_t structure is wiped completely at every +new gamestate_t, potentially several times during an established connection + +============================================================================= +*/ + +typedef struct { + int p_cmdNumber; // cl.cmdNumber when packet was sent + int p_serverTime; // usercmd->serverTime when packet was sent + int p_realtime; // cls.realtime when packet was sent +} outPacket_t; + +// the parseEntities array must be large enough to hold PACKET_BACKUP frames of +// entities, so that when a delta compressed message arives from the server +// it can be un-deltad from the original +#define MAX_PARSE_ENTITIES 2048 + +extern int g_console_field_width; + +typedef struct { + int timeoutcount; // it requres several frames in a timeout condition + // to disconnect, preventing debugging breaks from + // causing immediate disconnects on continue + clSnapshot_t snap; // latest received from server + + int serverTime; // may be paused during play + int oldServerTime; // to prevent time from flowing bakcwards + int oldFrameServerTime; // to check tournament restarts + int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta + // this value changes as net lag varies + qboolean extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate + // cleared when CL_AdjustTimeDelta looks at it + qboolean newSnapshots; // set on parse of any valid packet + + gameState_t gameState; // configstrings + char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO + + int parseEntitiesNum; // index (not anded off) into cl_parse_entities[] + + int mouseDx[2], mouseDy[2]; // added to by mouse events + int mouseIndex; + int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + + // cgame communicates a few values to the client system + int cgameUserCmdValue; // current weapon to add to usercmd_t + int cgameUserHoldableValue; // current holdable item to add to usercmd_t //----(SA) added + float cgameSensitivity; + int cgameMpSetup; // NERVE - SMF + int cgameMpIdentClient; // NERVE - SMF + vec3_t cgameClientLerpOrigin; // DHM - Nerve + + // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last + // properly generated command + usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds + int cmdNumber; // incremented each frame, because multiple + // frames may need to be packed into a single packet + + outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out + + // the client maintains its own idea of view angles, which are + // sent to the server each frame. It is cleared to 0 upon entering each level. + // the server sends a delta each frame which is added to the locally + // tracked view angles to account for standing on rotating objects, + // and teleport direction changes + vec3_t viewangles; + + int serverId; // included in each client message so the server + // can tell if it is for a prior map_restart + // big stuff at end of structure so most offsets are 15 bits or less + clSnapshot_t snapshots[PACKET_BACKUP]; + + entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame + + entityState_t parseEntities[MAX_PARSE_ENTITIES]; + + // NERVE - SMF + // NOTE TTimo - UI uses LIMBOCHAT_WIDTH strings (140), + // but for the processing in CL_AddToLimboChat we need some safe room + char limboChatMsgs[LIMBOCHAT_HEIGHT][LIMBOCHAT_WIDTH * 3 + 1]; + int limboChatPos; + + qboolean corruptedTranslationFile; + char translationVersion[MAX_STRING_TOKENS]; + // -NERVE - SMF +} clientActive_t; + +extern clientActive_t cl; + +/* +============================================================================= + +the clientConnection_t structure is wiped when disconnecting from a server, +either to go to a full screen console, play a demo, or connect to a different server + +A connection can be to either a server through the network layer or a +demo through a file. + +============================================================================= +*/ + + +typedef struct { + + int clientNum; + int lastPacketSentTime; // for retransmits during connection + int lastPacketTime; // for timeouts + + netadr_t serverAddress; + int connectTime; // for connection retransmits + int connectPacketCount; // for display on connection dialog + char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog + + int challenge; // from the server to use for connecting + int checksumFeed; // from the server for checksum calculations + + int onlyVisibleClients; // DHM - Nerve + + // these are our reliable messages that go to the server + int reliableSequence; + int reliableAcknowledge; // the last one the server has executed + // TTimo - NOTE: incidentally, reliableCommands[0] is never used (always start at reliableAcknowledge+1) + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_TOKEN_CHARS]; + + // server message (unreliable) and command (reliable) sequence + // numbers are NOT cleared at level changes, but continue to + // increase as long as the connection is valid + + // message sequence is used by both the network layer and the + // delta compression layer + int serverMessageSequence; + + // reliable messages received from server + int serverCommandSequence; + int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand + char serverCommands[MAX_RELIABLE_COMMANDS][MAX_TOKEN_CHARS]; + + // file transfer from server + fileHandle_t download; + char downloadTempName[MAX_OSPATH]; + char downloadName[MAX_OSPATH]; + int downloadNumber; + int downloadBlock; // block we are waiting for + int downloadCount; // how many bytes we got + int downloadSize; // how many bytes we got + char downloadList[MAX_INFO_STRING]; // list of paks we need to download + qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak + + // demo information + char demoName[MAX_QPATH]; + qboolean demorecording; + qboolean demoplaying; + qboolean demowaiting; // don't record until a non-delta message is received + qboolean firstDemoFrameSkipped; + fileHandle_t demofile; + + qboolean waverecording; + fileHandle_t wavefile; + int wavetime; + + int timeDemoFrames; // counter of rendered frames + int timeDemoStart; // cls.realtime before first frame + int timeDemoBaseTime; // each frame will be at this time + frameNum * 50 + + // big stuff at end of structure so most offsets are 15 bits or less + netchan_t netchan; +} clientConnection_t; + +extern clientConnection_t clc; + +/* +================================================================== + +the clientStatic_t structure is never wiped, and is used even when +no client connection is active at all + +================================================================== +*/ + +typedef struct { + netadr_t adr; + int start; + int time; + char info[MAX_INFO_STRING]; +} ping_t; + +typedef struct { + netadr_t adr; + char hostName[MAX_NAME_LENGTH]; + char mapName[MAX_NAME_LENGTH]; + char game[MAX_NAME_LENGTH]; + int netType; + int gameType; + int clients; + int maxClients; + int minPing; + int maxPing; + int ping; + qboolean visible; + int allowAnonymous; + int friendlyFire; // NERVE - SMF + int maxlives; // NERVE - SMF + int tourney; // NERVE - SMF + int punkbuster; // DHM - Nerve + int antilag; // TTimo + char gameName[MAX_NAME_LENGTH]; // Arnout +} serverInfo_t; + +typedef struct { + byte ip[4]; + unsigned short port; +} serverAddress_t; + +#define MAX_AUTOUPDATE_SERVERS 5 +typedef struct { + connstate_t state; // connection status + int keyCatchers; // bit flags + + qboolean cddialog; // bring up the cd needed dialog next frame + + char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect) + + // when the server clears the hunk, all of these must be restarted + qboolean rendererStarted; + qboolean soundStarted; + qboolean soundRegistered; + qboolean uiStarted; + qboolean cgameStarted; + + int framecount; + int frametime; // msec since last frame + + int realtime; // ignores pause + int realFrametime; // ignoring pause, so console always works + + int numlocalservers; + serverInfo_t localServers[MAX_OTHER_SERVERS]; + + int numglobalservers; + serverInfo_t globalServers[MAX_GLOBAL_SERVERS]; + // additional global servers + int numGlobalServerAddresses; + serverAddress_t globalServerAddresses[MAX_GLOBAL_SERVERS]; + + int numfavoriteservers; + serverInfo_t favoriteServers[MAX_OTHER_SERVERS]; + + int nummplayerservers; + serverInfo_t mplayerServers[MAX_OTHER_SERVERS]; + + int pingUpdateSource; // source currently pinging or updating + + int masterNum; + + // update server info + netadr_t updateServer; + char updateChallenge[MAX_TOKEN_CHARS]; + char updateInfoString[MAX_INFO_STRING]; + + netadr_t authorizeServer; + + // DHM - Nerve :: Auto-update Info + char autoupdateServerNames[MAX_AUTOUPDATE_SERVERS][MAX_QPATH]; + netadr_t autoupdateServer; + + // rendering info + glconfig_t glconfig; + qhandle_t charSetShader; + qhandle_t whiteShader; + qhandle_t consoleShader; + qhandle_t consoleShader2; // NERVE - SMF - merged from WolfSP +} clientStatic_t; + +extern clientStatic_t cls; + +//============================================================================= + +extern vm_t *cgvm; // interface to cgame dll or vm +extern vm_t *uivm; // interface to ui dll or vm +extern refexport_t re; // interface to refresh .dll + + +// +// cvars +// +extern cvar_t *cl_nodelta; +extern cvar_t *cl_debugMove; +extern cvar_t *cl_noprint; +extern cvar_t *cl_timegraph; +extern cvar_t *cl_maxpackets; +extern cvar_t *cl_packetdup; +extern cvar_t *cl_shownet; +extern cvar_t *cl_shownuments; // DHM - Nerve +extern cvar_t *cl_visibleClients; // DHM - Nerve +extern cvar_t *cl_showSend; +extern cvar_t *cl_showServerCommands; // NERVE - SMF +extern cvar_t *cl_timeNudge; +extern cvar_t *cl_showTimeDelta; +extern cvar_t *cl_freezeDemo; + +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_run; +extern cvar_t *cl_anglespeedkey; + +extern cvar_t *cl_recoilPitch; // RF + +extern cvar_t *cl_bypassMouseInput; // NERVE - SMF + +extern cvar_t *cl_sensitivity; +extern cvar_t *cl_freelook; + +extern cvar_t *cl_mouseAccel; +extern cvar_t *cl_showMouseRate; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; +extern cvar_t *m_filter; + +extern cvar_t *cl_timedemo; + +extern cvar_t *cl_activeAction; + +extern cvar_t *cl_allowDownload; +extern cvar_t *cl_conXOffset; +extern cvar_t *cl_inGameVideo; + +extern cvar_t *cl_missionStats; +extern cvar_t *cl_waitForFire; + +// NERVE - SMF - localization +extern cvar_t *cl_language; +// -NERVE - SMF + +//================================================= + +// +// cl_main +// + +void CL_Init( void ); +void CL_FlushMemory( void ); +void CL_ShutdownAll( void ); +void CL_AddReliableCommand( const char *cmd ); + +void CL_StartHunkUsers( void ); + +#ifndef UPDATE_SERVER +void CL_CheckAutoUpdate( void ); +void CL_GetAutoUpdate( void ); +#endif + +void CL_Disconnect_f( void ); +void CL_GetChallengePacket( void ); +void CL_Vid_Restart_f( void ); +void CL_Snd_Restart_f( void ); +void CL_StartDemoLoop( void ); +void CL_NextDemo( void ); +void CL_ReadDemoMessage( void ); + +void CL_InitDownloads( void ); +void CL_NextDownload( void ); + +void CL_GetPing( int n, char *buf, int buflen, int *pingtime ); +void CL_GetPingInfo( int n, char *buf, int buflen ); +void CL_ClearPing( int n ); +int CL_GetPingQueueCount( void ); + +void CL_ShutdownRef( void ); +void CL_InitRef( void ); +qboolean CL_CDKeyValidate( const char *key, const char *checksum ); +int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ); + +void CL_AddToLimboChat( const char *str ); // NERVE - SMF +qboolean CL_GetLimboString( int index, char *buf ); // NERVE - SMF + +// NERVE - SMF - localization +void CL_InitTranslation(); +void CL_SaveTransTable(); +void CL_ReloadTranslation(); +void CL_TranslateString( const char *string, char *dest_buffer ); +const char* CL_TranslateStringBuf( const char *string ); // TTimo +// -NERVE - SMF + +void CL_OpenURL( const char *url ); // TTimo + +// +// cl_input +// +typedef struct { + int down[2]; // key nums holding it down + unsigned downtime; // msec timestamp + unsigned msec; // msec down this frame if both a down and up happened + qboolean active; // current state + qboolean wasPressed; // set when down, not cleared when up +} kbutton_t; + +typedef enum { + KB_LEFT, + KB_RIGHT, + KB_FORWARD, + KB_BACK, + KB_LOOKUP, + KB_LOOKDOWN, + KB_MOVELEFT, + KB_MOVERIGHT, + KB_STRAFE, + KB_SPEED, + KB_UP, + KB_DOWN, + KB_BUTTONS0, + KB_BUTTONS1, + KB_BUTTONS2, + KB_BUTTONS3, + KB_BUTTONS4, + KB_BUTTONS5, + KB_BUTTONS6, + KB_BUTTONS7, + KB_WBUTTONS0, + KB_WBUTTONS1, + KB_WBUTTONS2, + KB_WBUTTONS3, + KB_WBUTTONS4, + KB_WBUTTONS5, + KB_WBUTTONS6, + KB_WBUTTONS7, + KB_MLOOK, + KB_KICK, + NUM_BUTTONS +} kbuttons_t; + + +void CL_ClearKeys( void ); + +void CL_InitInput( void ); +void CL_SendCmd( void ); +void CL_ClearState( void ); +void CL_ReadPackets( void ); + +void CL_WritePacket( void ); +void IN_CenterView( void ); +void IN_Notebook( void ); +void IN_Help( void ); + +//----(SA) salute +void IN_Salute( void ); +//----(SA) + +void CL_VerifyCode( void ); + +float CL_KeyState( kbutton_t *key ); +char *Key_KeynumToString( int keynum, qboolean bTranslate ); + +// +// cl_parse.c +// +extern int cl_connectedToPureServer; + +void CL_SystemInfoChanged( void ); +void CL_ParseServerMessage( msg_t *msg ); + +//==================================================================== + +void CL_UpdateInfoPacket( netadr_t from ); // DHM - Nerve + +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ); +void CL_LocalServers_f( void ); +void CL_GlobalServers_f( void ); +void CL_FavoriteServers_f( void ); +void CL_Ping_f( void ); +qboolean CL_UpdateVisiblePings_f( int source ); + + +// +// console +// +void Con_DrawCharacter( int cx, int line, int num ); + +void Con_CheckResize( void ); +void Con_Init( void ); +void Con_Clear_f( void ); +void Con_ToggleConsole_f( void ); +void Con_DrawNotify( void ); +void Con_ClearNotify( void ); +void Con_RunConsole( void ); +void Con_DrawConsole( void ); +void Con_PageUp( void ); +void Con_PageDown( void ); +void Con_Top( void ); +void Con_Bottom( void ); +void Con_Close( void ); + + +// +// cl_scrn.c +// +void SCR_Init( void ); +void SCR_UpdateScreen( void ); + +void SCR_DebugGraph( float value, int color ); + +int SCR_GetBigStringWidth( const char *str ); // returns in virtual 640x480 coordinates + +void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ); +void SCR_FillRect( float x, float y, float width, float height, + const float *color ); +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ); + +void SCR_DrawBigString( int x, int y, const char *s, float alpha ); // draws a string with embedded color control characters with fade +void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); // ignores embedded color control characters +void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor ); +void SCR_DrawSmallChar( int x, int y, int ch ); + + +// +// cl_cin.c +// + +void CL_PlayCinematic_f( void ); +void SCR_DrawCinematic( void ); +void SCR_RunCinematic( void ); +void SCR_StopCinematic( void ); +int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ); +e_status CIN_StopCinematic( int handle ); +e_status CIN_RunCinematic( int handle ); +void CIN_DrawCinematic( int handle ); +void CIN_SetExtents( int handle, int x, int y, int w, int h ); +void CIN_SetLooping( int handle, qboolean loop ); +void CIN_UploadCinematic( int handle ); +void CIN_CloseAllVideos( void ); + +// +// cl_cgame.c +// +void CL_InitCGame( void ); +void CL_ShutdownCGame( void ); +qboolean CL_GameCommand( void ); +void CL_CGameRendering( stereoFrame_t stereo ); +void CL_SetCGameTime( void ); +void CL_FirstSnapshot( void ); +void CL_ShaderStateChanged( void ); +void CL_UpdateLevelHunkUsage( void ); +// +// cl_ui.c +// +void CL_InitUI( void ); +void CL_ShutdownUI( void ); +int Key_GetCatcher( void ); +void Key_SetCatcher( int catcher ); +void LAN_LoadCachedServers(); +void LAN_SaveServersToCache(); + + +// +// cl_net_chan.c +// +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ); //int length, const byte *data ); +void CL_Netchan_TransmitNextFragment( netchan_t *chan ); +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); diff --git a/src/client/keys.h b/src/client/keys.h new file mode 100644 index 0000000..d6417db --- /dev/null +++ b/src/client/keys.h @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../ui/keycodes.h" + +#define MAX_KEYS 256 + +typedef struct { + qboolean down; + int repeats; // if > 1, it is autorepeating + char *binding; +} qkey_t; + +extern qboolean key_overstrikeMode; +extern qkey_t keys[MAX_KEYS]; + +// NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h + +void Field_KeyDownEvent( field_t *edit, int key ); +void Field_CharEvent( field_t *edit, int ch ); +void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor ); +void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor ); + +#define COMMAND_HISTORY 32 +extern field_t historyEditLines[COMMAND_HISTORY]; + +extern field_t g_consoleField; +extern field_t chatField; +extern qboolean anykeydown; +extern qboolean chat_team; +extern qboolean chat_limbo; // NERVE - SMF +extern int chat_playerNum; + +void Key_WriteBindings( fileHandle_t f ); +void Key_SetBinding( int keynum, const char *binding ); +char *Key_GetBinding( int keynum ); +qboolean Key_IsDown( int keynum ); +qboolean Key_GetOverstrikeMode( void ); +void Key_SetOverstrikeMode( qboolean state ); +void Key_ClearStates( void ); +int Key_GetKey( const char *binding ); diff --git a/src/client/snd_adpcm.c b/src/client/snd_adpcm.c new file mode 100644 index 0000000..d8cd415 --- /dev/null +++ b/src/client/snd_adpcm.c @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "snd_local.h" + + +void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) { + // LordHavoc: removed 4-clause BSD code for Intel ADPCM codec +} + + +void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) { + // LordHavoc: removed 4-clause BSD code for Intel ADPCM codec +} + + +/* +==================== +S_AdpcmMemoryNeeded + +Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format +==================== +*/ +int S_AdpcmMemoryNeeded( const wavinfo_t *info ) { + float scale; + int scaledSampleCount; + int sampleMemory; + int blockCount; + int headerMemory; + + // determine scale to convert from input sampling rate to desired sampling rate + scale = (float)info->rate / dma.speed; + + // calc number of samples at playback sampling rate + scaledSampleCount = info->samples / scale; + + // calc memory need to store those samples using ADPCM at 4 bits per sample + sampleMemory = scaledSampleCount / 2; + + // calc number of sample blocks needed of PAINTBUFFER_SIZE + blockCount = scaledSampleCount / PAINTBUFFER_SIZE; + if ( scaledSampleCount % PAINTBUFFER_SIZE ) { + blockCount++; + } + + // calc memory needed to store the block headers + headerMemory = blockCount * sizeof( adpcm_state_t ); + + return sampleMemory + headerMemory; +} + + +/* +==================== +S_AdpcmGetSamples +==================== +*/ +void S_AdpcmGetSamples( sndBuffer *chunk, short *to ) { + adpcm_state_t state; + byte *out; + + // get the starting state from the block header + state.index = chunk->adpcm.index; + state.sample = chunk->adpcm.sample; + + out = (byte *)chunk->sndChunk; + // get samples + S_AdpcmDecode( out, to, SND_CHUNK_SIZE_BYTE * 2, &state ); +} + + +/* +==================== +S_AdpcmEncodeSound +==================== +*/ +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { + adpcm_state_t state; + int inOffset; + int count; + int n; + sndBuffer *newchunk, *chunk; + byte *out; + + inOffset = 0; + count = sfx->soundLength; + state.index = 0; + state.sample = samples[0]; + + chunk = NULL; + while ( count ) { + n = count; + if ( n > SND_CHUNK_SIZE_BYTE * 2 ) { + n = SND_CHUNK_SIZE_BYTE * 2; + } + + newchunk = SND_malloc(); + if ( sfx->soundData == NULL ) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + + // output the header + chunk->adpcm.index = state.index; + chunk->adpcm.sample = state.sample; + + out = (byte *)chunk->sndChunk; + + // encode the samples + S_AdpcmEncode( samples + inOffset, out, n, &state ); + + inOffset += n; + count -= n; + } +} diff --git a/src/client/snd_dma.c b/src/client/snd_dma.c new file mode 100644 index 0000000..170e7c7 --- /dev/null +++ b/src/client/snd_dma.c @@ -0,0 +1,2160 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: snd_dma.c + * + * desc: main control for any streaming sound output device + * + * $Archive: /Wolfenstein MP/src/client/snd_dma.c $ + * + *****************************************************************************/ + +#include "snd_local.h" +#include "client.h" + +void S_Play_f( void ); +void S_SoundList_f( void ); +void S_Music_f( void ); +void S_StreamingSound_f( void ); + +void S_Update_Mix(); +void S_StopAllSounds( void ); +void S_UpdateStreamingSounds( void ); +// Ridah, streaming sounds +// !! NOTE: the first streaming sound is always the music +streamingSound_t streamingSounds[MAX_STREAMING_SOUNDS]; +int numStreamingSounds = 0; +static vec3_t entityPositions[MAX_GENTITIES]; + +void *crit; + +typedef struct { + vec3_t origin; + qboolean fixedOrigin; + int entityNum; + int entityChannel; + sfxHandle_t sfx; + int flags; +} s_pushStack; + +#define MAX_PUSHSTACK 64 +static s_pushStack pushPop[MAX_PUSHSTACK]; +static int tart = 0; + +typedef struct { + char intro[256]; + char loop[256]; + qboolean music; + int entnum; + int channel; + int attenuation; +} s_streamStack; + +//static s_streamStack Sstream[MAX_PUSHSTACK]; // TTimo: unused +//static int onStream; // TTimo: unused + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +// only begin attenuating sound volumes when outside the FULLVOLUME range +#define SOUND_FULLVOLUME 80 + +#define SOUND_ATTENUATE 0.0008f +#define SOUND_RANGE_DEFAULT 1250 + +channel_t s_channels[MAX_CHANNELS]; +channel_t loop_channels[MAX_CHANNELS]; +int numLoopChannels; + +static int s_soundStarted; +static qboolean s_soundMuted; +static qboolean s_soundPainted; +static int s_clearSoundBuffer = 0; +dma_t dma; + +static int listener_number; +static vec3_t listener_origin; +static vec3_t listener_axis[3]; + +int s_soundtime; // sample PAIRS +int s_paintedtime; // sample PAIRS + +// MAX_SFX may be larger than MAX_SOUNDS because +// of custom player sounds +#define MAX_SFX 4096 +sfx_t s_knownSfx[MAX_SFX]; +int s_numSfx = 0; + +#define LOOP_HASH 128 +static sfx_t *sfxHash[LOOP_HASH]; + +cvar_t *s_volume; +cvar_t *s_testsound; +cvar_t *s_khz; +cvar_t *s_show; +cvar_t *s_mixahead; +cvar_t *s_mixPreStep; +cvar_t *s_musicVolume; +cvar_t *s_separation; +cvar_t *s_doppler; +cvar_t *s_mute; // (SA) for DM so he can 'toggle' sound on/off without disturbing volume levels +cvar_t *s_defaultsound; // (SA) added to silence the default beep sound if desired +cvar_t *cl_cacheGathering; // Ridah +cvar_t *s_wavonly; + +#define MAX_LOOP_SOUNDS 128 +static int numLoopSounds; +static loopSound_t loopSounds[MAX_LOOP_SOUNDS]; + +static channel_t *freelist = NULL; +static channel_t *endflist = NULL; + +// Rafael +cvar_t *s_nocompressed; + +// for streaming sounds +int s_rawend[MAX_STREAMING_SOUNDS]; +int s_rawpainted[MAX_STREAMING_SOUNDS]; +portable_samplepair_t s_rawsamples[MAX_STREAMING_SOUNDS][MAX_RAW_SAMPLES]; +// RF, store the volumes, since now they get adjusted at time of painting, so we can extract talking data first +portable_samplepair_t s_rawVolume[MAX_STREAMING_SOUNDS]; + + +/* +================ +S_SoundInfo_f +================ +*/ +void S_SoundInfo_f( void ) { + Com_Printf( "----- Sound Info -----\n" ); + if ( !s_soundStarted ) { + Com_Printf( "sound system not started\n" ); + } else { + if ( s_soundMuted ) { + Com_Printf( "sound system is muted\n" ); + } + + Com_Printf( "%5d stereo\n", dma.channels - 1 ); + Com_Printf( "%5d samples\n", dma.samples ); + Com_Printf( "%5d samplebits\n", dma.samplebits ); + Com_Printf( "%5d submission_chunk\n", dma.submission_chunk ); + Com_Printf( "%5d speed\n", dma.speed ); + Com_Printf( "0x%x dma buffer\n", dma.buffer ); + if ( streamingSounds[0].file ) { + Com_Printf( "Background file: %s\n", streamingSounds[0].loop ); + } else { + Com_Printf( "No background file.\n" ); + } + + } + Com_Printf( "----------------------\n" ); +} + +void S_ChannelSetup(); + +/* +================ +S_Init +================ +*/ +void S_Init( void ) { + cvar_t *cv; + qboolean r; + + Com_Printf( "\n------- sound initialization -------\n" ); + + s_mute = Cvar_Get( "s_mute", "0", CVAR_TEMP ); //----(SA) added + s_volume = Cvar_Get( "s_volume", "0.8", CVAR_ARCHIVE ); + s_musicVolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE ); + s_separation = Cvar_Get( "s_separation", "0.5", CVAR_ARCHIVE ); + s_doppler = Cvar_Get( "s_doppler", "1", CVAR_ARCHIVE ); + s_khz = Cvar_Get( "s_khz", "22", CVAR_ARCHIVE ); + s_mixahead = Cvar_Get( "s_mixahead", "0.2", CVAR_ARCHIVE ); + + s_mixPreStep = Cvar_Get( "s_mixPreStep", "0.05", CVAR_ARCHIVE ); + s_show = Cvar_Get( "s_show", "0", CVAR_CHEAT ); + s_testsound = Cvar_Get( "s_testsound", "0", CVAR_CHEAT ); + s_defaultsound = Cvar_Get( "s_defaultsound", "0", CVAR_ARCHIVE ); + s_wavonly = Cvar_Get( "s_wavonly", "0", CVAR_ARCHIVE | CVAR_LATCH ); + // Ridah + cl_cacheGathering = Cvar_Get( "cl_cacheGathering", "0", 0 ); + + // Rafael + s_nocompressed = Cvar_Get( "s_nocompressed", "0", CVAR_INIT ); + + cv = Cvar_Get( "s_initsound", "1", 0 ); + if ( !cv->integer ) { + Com_Printf( "not initializing.\n" ); + Com_Printf( "------------------------------------\n" ); + return; + } + + crit = Sys_InitializeCriticalSection(); + + Cmd_AddCommand( "play", S_Play_f ); + Cmd_AddCommand( "music", S_Music_f ); + Cmd_AddCommand( "streamingsound", S_StreamingSound_f ); + Cmd_AddCommand( "s_list", S_SoundList_f ); + Cmd_AddCommand( "s_info", S_SoundInfo_f ); + Cmd_AddCommand( "s_stop", S_StopAllSounds ); + + r = SNDDMA_Init(); + Com_Printf( "------------------------------------\n" ); + + if ( r ) { + Com_Memset( sfxHash, 0, sizeof( sfx_t * ) * LOOP_HASH ); + + s_soundStarted = 1; + s_soundMuted = 1; +// s_numSfx = 0; + + s_soundtime = 0; + s_paintedtime = 0; + + S_StopAllSounds(); + + S_SoundInfo_f(); + S_ChannelSetup(); + } + +} + +/* +================ +S_ChannelFree +================ +*/ +void S_ChannelFree( channel_t *v ) { + v->thesfx = NULL; + v->threadReady = qfalse; + *(channel_t **)endflist = v; + endflist = v; + *(channel_t **)v = NULL; +} + +/* +================ +S_ChannelMalloc +================ +*/ +channel_t* S_ChannelMalloc() { + channel_t *v; + if ( freelist == NULL ) { + return NULL; + } + v = freelist; + freelist = *(channel_t **)freelist; + v->allocTime = Sys_Milliseconds(); + return v; +} + +/* +================ +S_ChannelSetup +================ +*/ +void S_ChannelSetup() { + channel_t *p, *q; + + // clear all the sounds so they don't + Com_Memset( s_channels, 0, sizeof( s_channels ) ); + + p = s_channels;; + q = p + MAX_CHANNELS; + while ( --q > p ) { + *(channel_t **)q = q - 1; + } + + endflist = q; + *(channel_t **)q = NULL; + freelist = p + MAX_CHANNELS - 1; + Com_DPrintf( "Channel memory manager started\n" ); +} + +/* +================ +S_Shutdown +================ +*/ +void S_Shutdown( void ) { + if ( !s_soundStarted ) { + return; + } + + Sys_EnterCriticalSection( crit ); + + SNDDMA_Shutdown(); + + s_soundStarted = 0; + s_soundMuted = qtrue; + + Cmd_RemoveCommand( "play" ); + Cmd_RemoveCommand( "music" ); + Cmd_RemoveCommand( "stopsound" ); + Cmd_RemoveCommand( "soundlist" ); + Cmd_RemoveCommand( "soundinfo" ); +} + +/* +================ +S_HashSFXName + +return a hash value for the sfx name +================ +*/ +static long S_HashSFXName( const char *name ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( name[i] != '\0' ) { + letter = tolower( name[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter == '\\' ) { + letter = '/'; // damn path names + } + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( LOOP_HASH - 1 ); + return hash; +} + +/* +====================== +S_FreeOldestSound +====================== +*/ +void S_FreeOldestSound( void ) { + int i, oldest, used; + sfx_t *sfx; + sndBuffer *buffer, *nbuffer; + + oldest = Sys_Milliseconds(); + used = 0; + + for ( i = 1 ; i < s_numSfx ; i++ ) { + sfx = &s_knownSfx[i]; + if ( sfx->inMemory && sfx->lastTimeUsed < oldest ) { + used = i; + oldest = sfx->lastTimeUsed; + } + } + + sfx = &s_knownSfx[used]; + + // DHM - Nerve :: can cause race conditions + //Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName); + + buffer = sfx->soundData; + while ( buffer != NULL ) { + nbuffer = buffer->next; + SND_free( buffer ); + buffer = nbuffer; + } + sfx->inMemory = qfalse; + sfx->soundData = NULL; +} + +/* +================== +S_FindName + +Will allocate a new sfx if it isn't found +================== +*/ +static sfx_t *S_FindName( const char *name ) { + int i; + int hash; + + sfx_t *sfx; + + if ( !name ) { + //Com_Error (ERR_FATAL, "S_FindName: NULL\n"); + name = "*default*"; + } + if ( !name[0] ) { + //Com_Error (ERR_FATAL, "S_FindName: empty name\n"); + name = "*default*"; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Error( ERR_FATAL, "Sound name too long: %s", name ); + } + + // Ridah, caching + if ( cl_cacheGathering->integer ) { + Cbuf_ExecuteText( EXEC_NOW, va( "cache_usedfile sound %s\n", name ) ); + } + + hash = S_HashSFXName( name ); + + sfx = sfxHash[hash]; + // see if already loaded + while ( sfx ) { + if ( !Q_stricmp( sfx->soundName, name ) ) { + return sfx; + } + sfx = sfx->next; + } + + // find a free sfx + for ( i = 0 ; i < s_numSfx ; i++ ) { + if ( !s_knownSfx[i].soundName[0] ) { + break; + } + } + + if ( i == s_numSfx ) { + if ( s_numSfx == MAX_SFX ) { + Com_Error( ERR_FATAL, "S_FindName: out of sfx_t" ); + } + s_numSfx++; + } + + sfx = &s_knownSfx[i]; + Com_Memset( sfx, 0, sizeof( *sfx ) ); + strcpy( sfx->soundName, name ); + + sfx->next = sfxHash[hash]; + sfxHash[hash] = sfx; + + return sfx; +} + +/* +================= +S_DefaultSound +================= +*/ +void S_DefaultSound( sfx_t *sfx ) { + int i; + + sfx->soundLength = 512; + sfx->soundData = SND_malloc(); + sfx->soundData->next = NULL; + + if ( s_defaultsound->integer ) { + for ( i = 0 ; i < sfx->soundLength ; i++ ) { + sfx->soundData->sndChunk[i] = i; + } + } else { + for ( i = 0 ; i < sfx->soundLength ; i++ ) { + sfx->soundData->sndChunk[i] = 0; + } + } +} + +/* +=================== +S_DisableSounds + +Disables sounds until the next S_BeginRegistration. +This is called when the hunk is cleared and the sounds +are no longer valid. +=================== +*/ +void S_DisableSounds( void ) { + S_StopAllSounds(); + s_soundMuted = qtrue; +} + +/* +===================== +S_BeginRegistration +===================== +*/ +void S_BeginRegistration( void ) { + sfx_t *sfx; + s_soundMuted = qfalse; // we can play again + + if ( s_numSfx == 0 ) { + SND_setup(); + + s_numSfx = 0; + Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) ); + Com_Memset( sfxHash, 0, sizeof( sfx_t * ) * LOOP_HASH ); + + sfx = S_FindName( "***DEFAULT***" ); + S_DefaultSound( sfx ); + } +} + +/* +================== +S_RegisterSound + +Creates a default buzz sound if the file can't be loaded +================== +*/ +sfxHandle_t S_RegisterSound( const char *name, qboolean compressed ) { + sfx_t *sfx; + + if ( !s_soundStarted ) { + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_DPrintf( "Sound name exceeds MAX_QPATH\n" ); + return 0; + } + + sfx = S_FindName( name ); + if ( sfx->soundData ) { + if ( sfx->defaultSound ) { + if ( com_developer->integer ) { + Com_DPrintf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + } + return 0; + } + return sfx - s_knownSfx; + } + + sfx->inMemory = qfalse; + sfx->soundCompressed = compressed; + +// if (!compressed) { + S_memoryLoad( sfx ); +// } + + if ( sfx->defaultSound ) { + if ( com_developer->integer ) { + Com_DPrintf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + } + return 0; + } + + return sfx - s_knownSfx; +} + +/* +================= +S_memoryLoad +================= +*/ +void S_memoryLoad( sfx_t *sfx ) { + // load the sound file + if ( !S_LoadSound( sfx ) ) { +// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName ); + sfx->defaultSound = qtrue; + } + sfx->inMemory = qtrue; +} + +//============================================================================= + +/* +================= +S_SpatializeOrigin + +Used for spatializing s_channels +================= +*/ +void S_SpatializeOrigin( vec3_t origin, int master_vol, int *left_vol, int *right_vol, float range ) { + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + vec3_t vec; + +// const float dist_mult = SOUND_ATTENUATE; + float dist_mult, dist_fullvol; + + dist_fullvol = range * 0.064f; // default range of 1250 gives 80 + dist_mult = dist_fullvol * 0.00001f; // default range of 1250 gives .0008 +// dist_mult = range*0.00000064f; // default range of 1250 gives .0008 + + // calculate stereo seperation and distance attenuation + VectorSubtract( origin, listener_origin, source_vec ); + + dist = VectorNormalize( source_vec ); +// dist -= SOUND_FULLVOLUME; + dist -= dist_fullvol; + if ( dist < 0 ) { + dist = 0; // close enough to be at full volume + + } + if ( dist ) { + dist = dist / range; // FIXME: lose the divide again + } +// dist *= dist_mult; // different attenuation levels + + VectorRotate( source_vec, listener_axis, vec ); + + dot = -vec[1]; + + if ( dma.channels == 1 ) { // no attenuation = no spatialization + rscale = 1.0; + lscale = 1.0; + } else + { + rscale = 0.5 * ( 1.0 + dot ); + lscale = 0.5 * ( 1.0 - dot ); + //rscale = s_separation->value + ( 1.0 - s_separation->value ) * dot; + //lscale = s_separation->value - ( 1.0 - s_separation->value ) * dot; + if ( rscale < 0 ) { + rscale = 0; + } + if ( lscale < 0 ) { + lscale = 0; + } + } + + // add in distance effect + scale = ( 1.0 - dist ) * rscale; + *right_vol = ( master_vol * scale ); + if ( *right_vol < 0 ) { + *right_vol = 0; + } + + scale = ( 1.0 - dist ) * lscale; + *left_vol = ( master_vol * scale ); + if ( *left_vol < 0 ) { + *left_vol = 0; + } +} + +/* +==================== +S_StartSound + +Validates the parms and queues the sound up +if pos is NULL, the sound will be dynamically sourced from the entity +Entchannel 0 will never override a playing sound + + flags: (currently apply only to non-looping sounds) + SND_NORMAL 0 - (default) allow sound to be cut off only by the same sound on this channel + SND_OKTOCUT 0x001 - allow sound to be cut off by any following sounds on this channel + SND_REQUESTCUT 0x002 - allow sound to be cut off by following sounds on this channel only for sounds who request cutoff + SND_CUTOFF 0x004 - cut off sounds on this channel that are marked 'SND_REQUESTCUT' + SND_CUTOFF_ALL 0x008 - cut off all sounds on this channel +==================== +*/ + +void S_StartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, int flags ) { + if ( !s_soundStarted || s_soundMuted || ( cls.state != CA_ACTIVE && cls.state != CA_DISCONNECTED ) ) { + return; + } + if ( tart < MAX_PUSHSTACK ) { + sfx_t *sfx; + if ( origin ) { + VectorCopy( origin, pushPop[tart].origin ); + pushPop[tart].fixedOrigin = qtrue; + } else { + pushPop[tart].fixedOrigin = qfalse; + } + pushPop[tart].entityNum = entityNum; + pushPop[tart].entityChannel = entchannel; + pushPop[tart].sfx = sfxHandle; + pushPop[tart].flags = flags; + sfx = &s_knownSfx[ sfxHandle ]; + + if ( sfx->inMemory == qfalse ) { + S_memoryLoad( sfx ); + } + + tart++; + } +} + +void S_ThreadStartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, int flags ) { + channel_t *ch; + sfx_t *sfx; + int i, oldest, chosen; + + chosen = -1; + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_DPrintf( S_COLOR_YELLOW, "S_StartSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if ( s_show->integer == 1 ) { + Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName ); + } + +// Com_Printf("playing %s\n", sfx->soundName); + + sfx->lastTimeUsed = Sys_Milliseconds(); + + // check for a streaming sound that this entity is playing in this channel + // kill it if it exists + if ( entityNum >= 0 ) { + for ( i = 1; i < MAX_STREAMING_SOUNDS; i++ ) { // track 0 is music/cinematics + if ( !streamingSounds[i].file ) { + continue; + } + // check to see if this character currently has another sound streaming on the same channel + if ( ( entchannel != CHAN_AUTO ) && ( streamingSounds[i].entnum >= 0 ) && ( streamingSounds[i].channel == entchannel ) && ( streamingSounds[i].entnum == entityNum ) ) { + // found a match, override this channel + streamingSounds[i].kill = qtrue; + break; + } + } + } + + ch = NULL; + +//----(SA) modified + + // shut off other sounds on this channel if necessary + for ( i = 0 ; i < MAX_CHANNELS ; i++ ) { + if ( s_channels[i].entnum == entityNum && s_channels[i].thesfx && s_channels[i].entchannel == entchannel ) { + + // cutoff all on channel + if ( flags & SND_CUTOFF_ALL ) { + S_ChannelFree( &s_channels[i] ); + continue; + } + + if ( s_channels[i].flags & SND_NOCUT ) { + continue; + } + + // cutoff sounds that expect to be overwritten + if ( s_channels[i].flags & SND_OKTOCUT ) { + S_ChannelFree( &s_channels[i] ); + continue; + } + + // cutoff 'weak' sounds on channel + if ( flags & SND_CUTOFF ) { + if ( s_channels[i].flags & SND_REQUESTCUT ) { + S_ChannelFree( &s_channels[i] ); + continue; + } + } + + } + } + + // re-use channel if applicable + for ( i = 0 ; i < MAX_CHANNELS ; i++ ) { + if ( s_channels[i].entnum == entityNum && s_channels[i].entchannel == entchannel ) { + if ( !( s_channels[i].flags & SND_NOCUT ) && s_channels[i].thesfx == sfx ) { + ch = &s_channels[i]; + break; + } + } + } + + if ( !ch ) { + ch = S_ChannelMalloc(); + } +//----(SA) end + + if ( !ch ) { + ch = s_channels; + + oldest = sfx->lastTimeUsed; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( ch->entnum == entityNum && ch->thesfx == sfx ) { + chosen = i; + break; + } + if ( ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTime < oldest && ch->entchannel != CHAN_ANNOUNCER ) { + oldest = ch->allocTime; + chosen = i; + } + } + if ( chosen == -1 ) { + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( ch->entnum != listener_number && ch->allocTime < oldest && ch->entchannel != CHAN_ANNOUNCER ) { + oldest = ch->allocTime; + chosen = i; + } + } + if ( chosen == -1 ) { + if ( ch->entnum == listener_number ) { + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( ch->allocTime < oldest ) { + oldest = ch->allocTime; + chosen = i; + } + } + } + if ( chosen == -1 ) { + //Com_Printf("dropping sound\n"); + return; + } + } + } + ch = &s_channels[chosen]; + ch->allocTime = sfx->lastTimeUsed; + } + + if ( origin ) { + VectorCopy( origin, ch->origin ); + ch->fixed_origin = qtrue; + } else { + ch->fixed_origin = qfalse; + } + + ch->flags = flags; //----(SA) added + ch->master_vol = 127; + ch->entnum = entityNum; + ch->thesfx = sfx; + ch->entchannel = entchannel; + ch->leftvol = ch->master_vol; // these will get calced at next spatialize + ch->rightvol = ch->master_vol; // unless the game isn't running + ch->doppler = qfalse; + + if ( ch->fixed_origin ) { + S_SpatializeOrigin( ch->origin, ch->master_vol, &ch->leftvol, &ch->rightvol, SOUND_RANGE_DEFAULT ); + } else { + S_SpatializeOrigin( entityPositions[ ch->entnum ], ch->master_vol, &ch->leftvol, &ch->rightvol, SOUND_RANGE_DEFAULT ); + } + + ch->startSample = START_SAMPLE_IMMEDIATE; + ch->threadReady = qtrue; +} + +/* +============== +S_StartSound +============== +*/ +void S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { + S_StartSoundEx( origin, entityNum, entchannel, sfxHandle, 0 ); +} + + + +/* +================== +S_StartLocalSound +================== +*/ +void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_DPrintf( S_COLOR_YELLOW, "S_StartLocalSound: handle %i out of range\n", sfxHandle ); + return; + } + + S_StartSound( NULL, listener_number, channelNum, sfxHandle ); +} + + +/* +================== +S_ClearSoundBuffer + +If we are about to perform file access, clear the buffer +so sound doesn't stutter. +================== +*/ +void S_ClearSoundBuffer( void ) { + if ( !s_soundStarted ) { + return; + } + + if ( !s_soundPainted ) { // RF, buffers are clear, no point clearing again + return; + } + + s_soundPainted = qfalse; + + s_clearSoundBuffer = 4; + + S_Update(); // NERVE - SMF - force an update +} + +/* +================== +S_StopAllSounds +================== +*/ +void S_StopAllSounds( void ) { + int i; + if ( !s_soundStarted ) { + return; + } + +//DAJ BUGFIX for(i=0;i= MAX_LOOP_SOUNDS ) { + return; + } + if ( !volume ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Error( ERR_DROP, "S_AddLoopingSound: handle %i out of range", sfxHandle ); + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if ( sfx->inMemory == qfalse ) { + S_memoryLoad( sfx ); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + VectorCopy( origin, loopSounds[numLoopSounds].origin ); + VectorCopy( velocity, loopSounds[numLoopSounds].velocity ); + loopSounds[numLoopSounds].sfx = sfx; + if ( range ) { + loopSounds[numLoopSounds].range = range; + } else { + loopSounds[numLoopSounds].range = SOUND_RANGE_DEFAULT; + } + + if ( volume & 1 << UNDERWATER_BIT ) { + loopSounds[numLoopSounds].loudUnderWater = qtrue; + } + + if ( volume > 255 ) { + volume = 255; + } else if ( volume < 0 ) { + volume = 0; + } + loopSounds[numLoopSounds].vol = volume; + + numLoopSounds++; +} + +/* +================== +S_AddLoopSounds + +Spatialize all of the looping sounds. +All sounds are on the same cycle, so any duplicates can just +sum up the channel multipliers. +================== +*/ +void S_AddLoopSounds( void ) { + int i, j, time; + int left_total, right_total, left, right; + channel_t *ch; + loopSound_t *loop, *loop2; + static int loopFrame; + +// Sys_EnterCriticalSection(crit); + + numLoopChannels = 0; + + time = Sys_Milliseconds(); + + loopFrame++; + for ( i = 0 ; i < numLoopSounds ; i++ ) { + loop = &loopSounds[i]; + if ( loop->mergeFrame == loopFrame ) { + continue; // already merged into an earlier sound + } + + //if (loop->kill) { + // S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total, loop->range); // 3d + //} else { + S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total, loop->range ); // sphere + //} + + // adjust according to volume + left_total = (int)( (float)loop->vol * (float)left_total / 256.0 ); + right_total = (int)( (float)loop->vol * (float)right_total / 256.0 ); + + loop->sfx->lastTimeUsed = time; + + for ( j = ( i + 1 ); j < numLoopChannels ; j++ ) { + loop2 = &loopSounds[j]; + if ( loop2->sfx != loop->sfx ) { + continue; + } + loop2->mergeFrame = loopFrame; + + //if (loop2->kill) { + // S_SpatializeOrigin( loop2->origin, 127, &left, &right, loop2->range); // 3d + //} else { + S_SpatializeOrigin( loop2->origin, 90, &left, &right, loop2->range ); // sphere + //} + + // adjust according to volume + left = (int)( (float)loop2->vol * (float)left / 256.0 ); + right = (int)( (float)loop2->vol * (float)right / 256.0 ); + + loop2->sfx->lastTimeUsed = time; + left_total += left; + right_total += right; + } + if ( left_total == 0 && right_total == 0 ) { + continue; // not audible + } + + // allocate a channel + ch = &loop_channels[numLoopChannels]; + + if ( left_total > 255 ) { + left_total = 255; + } + if ( right_total > 255 ) { + right_total = 255; + } + + ch->master_vol = 127; + ch->leftvol = left_total; + ch->rightvol = right_total; + ch->thesfx = loop->sfx; + + // RF, disabled doppler for looping sounds for now, since we are reverting to the old looping sound code + ch->doppler = qfalse; + + numLoopChannels++; + if ( numLoopChannels == MAX_CHANNELS ) { + i = numLoopSounds + 1; + } + } +// Sys_LeaveCriticalSection(crit); +} + +//============================================================================= + +/* +================= +S_ByteSwapRawSamples + +If raw data has been loaded in little endien binary form, this must be done. +If raw data was calculated, as with ADPCM, this should not be called. +================= +*/ +//DAJ void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { +void S_ByteSwapRawSamples( int samples, int width, int s_channels, short *data ) { + int i; + + if ( width != 2 ) { + return; + } +#ifndef __MACOS__ //DAJ save this test + if ( LittleShort( 256 ) == 256 ) { + return; + } +#endif + //DAJ use a faster loop technique + if ( s_channels == 2 ) { + i = samples << 1; + } else { + i = samples; + } + + do { + *data = LittleShort( *data ); + data++; +//DAJ ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } while ( --i ); +} + +/* +============ +S_GetRawSamplePointer +============ +*/ +portable_samplepair_t *S_GetRawSamplePointer() { + return s_rawsamples[0]; +} + +/* +============ +S_RawSamples + +Music streaming +============ +*/ +void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float lvol, float rvol, int streamingIndex ) { + int i; + int src, dst; + float scale; + int intVolumeL, intVolumeR; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + // volume taken into account when mixed + s_rawVolume[streamingIndex].left = 256 * lvol; + s_rawVolume[streamingIndex].right = 256 * rvol; + + intVolumeL = 256; + intVolumeR = 256; + + if ( s_rawend[streamingIndex] < s_soundtime ) { + Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend[streamingIndex], s_soundtime ); + s_rawend[streamingIndex] = s_soundtime; + } + + scale = (float)rate / dma.speed; + + //Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend); + if ( s_channels == 2 && width == 2 ) { + if ( scale == 1.0 ) { // optimized case + for ( i = 0; i < samples; i++ ) + { + dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 ); + s_rawend[streamingIndex]++; + s_rawsamples[streamingIndex][dst].left = ( (short *)data )[i * 2] * intVolumeL; + s_rawsamples[streamingIndex][dst].right = ( (short *)data )[i * 2 + 1] * intVolumeR; + } + } else + { + for ( i = 0; ; i++ ) + { + src = i * scale; + if ( src >= samples ) { + break; + } + dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 ); + s_rawend[streamingIndex]++; + s_rawsamples[streamingIndex][dst].left = ( (short *)data )[src * 2] * intVolumeL; + s_rawsamples[streamingIndex][dst].right = ( (short *)data )[src * 2 + 1] * intVolumeR; + } + } + } else if ( s_channels == 1 && width == 2 ) { + for ( i = 0; ; i++ ) + { + src = i * scale; + if ( src >= samples ) { + break; + } + dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 ); + s_rawend[streamingIndex]++; + s_rawsamples[streamingIndex][dst].left = ( (short *)data )[src] * intVolumeL; + s_rawsamples[streamingIndex][dst].right = ( (short *)data )[src] * intVolumeR; + } + } else if ( s_channels == 2 && width == 1 ) { + intVolumeL *= 256; + intVolumeR *= 256; + + for ( i = 0 ; ; i++ ) + { + src = i * scale; + if ( src >= samples ) { + break; + } + dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 ); + s_rawend[streamingIndex]++; + s_rawsamples[streamingIndex][dst].left = ( (char *)data )[src * 2] * intVolumeL; + s_rawsamples[streamingIndex][dst].right = ( (char *)data )[src * 2 + 1] * intVolumeR; + } + } else if ( s_channels == 1 && width == 1 ) { + intVolumeL *= 256; + intVolumeR *= 256; + + for ( i = 0; ; i++ ) + { + src = i * scale; + if ( src >= samples ) { + break; + } + dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 ); + s_rawend[streamingIndex]++; + s_rawsamples[streamingIndex][dst].left = ( ( (byte *)data )[src] - 128 ) * intVolumeL; + s_rawsamples[streamingIndex][dst].right = ( ( (byte *)data )[src] - 128 ) * intVolumeR; + } + } + + if ( s_rawend[streamingIndex] > s_soundtime + MAX_RAW_SAMPLES ) { + Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend[streamingIndex], s_soundtime ); + } +} + +//============================================================================= + +/* +===================== +S_UpdateEntityPosition + +let the sound system know where an entity currently is +====================== +*/ +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + } + VectorCopy( origin, entityPositions[entityNum] ); +} + + +/* +============ +S_Respatialize + +Change the volumes of all the playing sounds for changes in their positions +============ +*/ +void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + listener_number = entityNum; + VectorCopy( head, listener_origin ); + VectorCopy( axis[0], listener_axis[0] ); + VectorCopy( axis[1], listener_axis[1] ); + VectorCopy( axis[2], listener_axis[2] ); +} + +void S_ThreadRespatialize() { + int i; + channel_t *ch; + vec3_t origin; + // update spatialization for dynamic sounds + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx ) { + continue; + } + // anything coming from the view entity will always be full volume + if ( ch->entnum == listener_number ) { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + } else { + if ( ch->fixed_origin ) { + VectorCopy( ch->origin, origin ); + } else { + VectorCopy( entityPositions[ ch->entnum ], origin ); + } + + S_SpatializeOrigin( origin, ch->master_vol, &ch->leftvol, &ch->rightvol, SOUND_RANGE_DEFAULT ); + } + } +} + +/* +======================== +S_ScanChannelStarts + +Returns qtrue if any new sounds were started since the last mix +======================== +*/ +qboolean S_ScanChannelStarts( void ) { + channel_t *ch; + int i; + qboolean newSamples; + + newSamples = qfalse; + ch = s_channels; + + for ( i = 0; i < MAX_CHANNELS; i++, ch++ ) { + if ( !ch->thesfx ) { + continue; + } + // if this channel was just started this frame, + // set the sample count to it begins mixing + // into the very first sample + if ( ch->startSample == START_SAMPLE_IMMEDIATE && ch->threadReady == qtrue ) { + ch->startSample = s_paintedtime; + newSamples = qtrue; + continue; + } + + // if it is completely finished by now, clear it + if ( ch->startSample + ( ch->thesfx->soundLength ) <= s_paintedtime ) { + S_ChannelFree( ch ); + } + } + + return newSamples; +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ + +void S_Update( void ) { + int i; + int total; + channel_t *ch; + + if ( !s_soundStarted || s_soundMuted ) { +// Com_DPrintf ("not started or muted\n"); + return; + } + + // + // debugging output + // + if ( s_show->integer == 2 ) { + total = 0; + ch = s_channels; + for ( i = 0; i < MAX_CHANNELS; i++, ch++ ) { + if ( ch->thesfx && ( ch->leftvol || ch->rightvol ) ) { + Com_DPrintf( "%i %i %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName ); // <- this is not thread safe + total++; + } + } + + Com_Printf( "----(%i)---- painted: %i\n", total, s_paintedtime ); + } + // add loopsounds + S_AddLoopSounds(); + // do all the rest + S_UpdateThread(); +} + +void S_UpdateThread( void ) { + if ( !s_soundStarted || s_soundMuted ) { +// Com_DPrintf ("not started or muted\n"); + return; + } + +#ifdef TALKANIM + // default to ZERO amplitude, overwrite if sound is playing + memset( s_entityTalkAmplitude, 0, sizeof( s_entityTalkAmplitude ) ); +#endif + + if ( s_clearSoundBuffer ) { + int clear; + int i; + Sys_EnterCriticalSection( crit ); + // stop looping sounds + S_ClearLoopingSounds(); + + for ( i = 0; i < MAX_STREAMING_SOUNDS; i++ ) { + s_rawend[i] = 0; + } + + if ( dma.samplebits == 8 ) { + clear = 0x80; + } else { + clear = 0; + } + + SNDDMA_BeginPainting(); + if ( dma.buffer ) { + // TTimo: due to a particular bug workaround in linux sound code, + // have to optionally use a custom C implementation of Com_Memset + // not affecting win32, we have #define Snd_Memset Com_Memset + // show_bug.cgi?id=371 + Snd_Memset( dma.buffer, clear, dma.samples * dma.samplebits / 8 ); + } + SNDDMA_Submit(); + s_clearSoundBuffer = 0; + Sys_LeaveCriticalSection( crit ); + + // NERVE - SMF - clear out channels so they don't finish playing when audio restarts + S_ChannelSetup(); + } else { + Sys_EnterCriticalSection( crit ); + + S_ThreadRespatialize(); + // add raw data from streamed samples + S_UpdateStreamingSounds(); + // mix some sound + S_Update_Mix(); + + Sys_LeaveCriticalSection( crit ); + } +} +/* +============ +S_GetSoundtime +============ +*/ +void S_GetSoundtime( void ) { + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = dma.samples / dma.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 ( s_paintedtime > 0x40000000 ) { // time to chop things off to avoid 32 bit limits + buffers = 0; + s_paintedtime = fullsamples; + S_StopAllSounds(); + } + } + oldsamplepos = samplepos; + + s_soundtime = buffers * fullsamples + samplepos / dma.channels; + +#if 0 +// check to make sure that we haven't overshot + if ( s_paintedtime < s_soundtime ) { + Com_DPrintf( "S_GetSoundtime : overflow\n" ); + s_paintedtime = s_soundtime; + } +#endif + + if ( dma.submission_chunk < 256 ) { + s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed; + } else { + s_paintedtime = s_soundtime + dma.submission_chunk; + } +} + +/* +============ +S_Update_Mix +============ +*/ +void S_Update_Mix( void ) { + unsigned endtime; + int samps, i; + static float lastTime = 0.0f; + float ma, op; + float thisTime, sane; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + + for ( i = 0; i < tart; i++ ) { + if ( pushPop[i].fixedOrigin ) { + S_ThreadStartSoundEx( pushPop[i].origin, pushPop[i].entityNum, pushPop[i].entityChannel, pushPop[i].sfx, pushPop[i].flags ); + } else { + S_ThreadStartSoundEx( NULL, pushPop[i].entityNum, pushPop[i].entityChannel, pushPop[i].sfx, pushPop[i].flags ); + } + } + + tart = 0; + + s_soundPainted = qtrue; + + thisTime = Sys_Milliseconds(); + + // Updates s_soundtime + S_GetSoundtime(); + + // clear any sound effects that end before the current time, + // and start any new sounds + S_ScanChannelStarts(); + + sane = thisTime - lastTime; + if ( sane < 11 ) { + sane = 11; // 85hz + } + + ma = s_mixahead->value * dma.speed; + op = s_mixPreStep->value + sane * dma.speed * 0.01; + + if ( op < ma ) { + ma = op; + } + + // mix ahead of current position + endtime = s_soundtime + ma; + + // mix to an even submission block size + endtime = ( endtime + dma.submission_chunk - 1 ) + & ~( dma.submission_chunk - 1 ); + + // never mix more than the complete buffer + samps = dma.samples >> ( dma.channels - 1 ); + if ( endtime - s_soundtime > samps ) { + endtime = s_soundtime + samps; + } + + SNDDMA_BeginPainting(); + S_PaintChannels( endtime ); + SNDDMA_Submit(); + + lastTime = thisTime; +} + +/* +=============================================================================== + +console functions + +=============================================================================== +*/ + +void S_Play_f( void ) { + int i; + sfxHandle_t h; + char name[256]; + + i = 1; + while ( i < Cmd_Argc() ) { + if ( !Q_strrchr( Cmd_Argv( i ), '.' ) ) { + Com_sprintf( name, sizeof( name ), "%s.wav", Cmd_Argv( 1 ) ); + } else { + Q_strncpyz( name, Cmd_Argv( i ), sizeof( name ) ); + } + h = S_RegisterSound( name, qfalse ); + if ( h ) { + S_StartLocalSound( h, CHAN_LOCAL_SOUND ); + } + i++; + } +} + +void S_Music_f( void ) { + int c; + + c = Cmd_Argc(); + + if ( c == 2 ) { + S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 1 ) ); + } else if ( c == 3 ) { + S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ) ); + Q_strncpyz( streamingSounds[0].loop, Cmd_Argv( 2 ), sizeof( streamingSounds[0].loop ) ); + } else { + Com_Printf( "music [loopfile]\n" ); + return; + } + +} + +// Ridah, just for testing the streaming sounds +void S_StreamingSound_f( void ) { + int c; + + c = Cmd_Argc(); + + if ( c == 2 ) { + S_StartStreamingSound( Cmd_Argv( 1 ), 0, -1, 0, 0 ); + } else if ( c == 5 ) { + S_StartStreamingSound( Cmd_Argv( 1 ), 0, atoi( Cmd_Argv( 2 ) ), atoi( Cmd_Argv( 3 ) ), atoi( Cmd_Argv( 4 ) ) ); + } else { + Com_Printf( "streamingsound [entnum channel attenuation]\n" ); + return; + } + +} + +void S_SoundList_f( void ) { + int i; + sfx_t *sfx; + int size, total; + char type[4][16]; + char mem[2][16]; + + strcpy( type[0], "16bit" ); + strcpy( type[1], "adpcm" ); + strcpy( type[2], "daub4" ); + strcpy( type[3], "mulaw" ); + strcpy( mem[0], "paged out" ); + strcpy( mem[1], "resident " ); + total = 0; + for ( sfx = s_knownSfx, i = 0 ; i < s_numSfx ; i++, sfx++ ) { + size = sfx->soundLength; + total += size; + Com_Printf( "%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], sfx->soundName, mem[sfx->inMemory] ); + } + Com_Printf( "Total resident: %i\n", total ); + S_DisplayFreeMemory(); +} + + +/* +=============================================================================== + +STREAMING SOUND + +=============================================================================== +*/ + +int FGetLittleLong( fileHandle_t f ) { + int v; + + FS_Read( &v, sizeof( v ), f ); + + return LittleLong( v ); +} + +int FGetLittleShort( fileHandle_t f ) { + short v; + + FS_Read( &v, sizeof( v ), f ); + + return LittleShort( v ); +} + +// returns the length of the data in the chunk, or 0 if not found +int S_FindWavChunk( fileHandle_t f, char *chunk ) { + char name[5]; + int len; + int r; + + name[4] = 0; + len = 0; + r = FS_Read( name, 4, f ); + if ( r != 4 ) { + return 0; + } + len = FGetLittleLong( f ); + if ( len < 0 || len > 0xfffffff ) { + len = 0; + return 0; + } + len = ( len + 1 ) & ~1; // pad to word boundary +// s_nextWavChunk += len + 8; + + if ( strcmp( name, chunk ) ) { + return 0; + } + + return len; +} + +/* +====================== +S_StartBackgroundTrack +====================== +*/ +void S_StartBackgroundTrack( const char *intro, const char *loop ) { + int len; + char dump[16]; + char name[MAX_QPATH]; + int i; + streamingSound_t *ss; + fileHandle_t fh; + +// if (!s_soundStarted || !crit) { +// return; +// } + + Sys_EnterCriticalSection( crit ); + + if ( !intro ) { + intro = ""; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + Com_DPrintf( "S_StartBackgroundTrack( %s, %s )...\n", intro, loop ); + + // music is always track 0 + i = 0; + + ss = &streamingSounds[i]; + + Q_strncpyz( ss->loop, loop, sizeof( ss->loop ) - 4 ); + + Q_strncpyz( name, intro, sizeof( name ) - 4 ); + COM_DefaultExtension( name, sizeof( name ), ".wav" ); + + // close the current sound if present, but DON'T reset s_rawend + if ( ss->file ) { + Sys_EndStreamedFile( ss->file ); + FS_FCloseFile( ss->file ); + ss->file = 0; + } + + if ( !intro[0] ) { + Com_DPrintf( "Fail to start: %s\n", name ); + Sys_LeaveCriticalSection( crit ); + return; + } + + ss->channel = 0; + ss->entnum = -1; + ss->attenuation = 0; + + // + // open up a wav file and get all the info + // + FS_FOpenFileRead( name, &fh, qtrue ); + if ( !fh ) { + Com_Printf( "Couldn't open streaming sound file %s\n", name ); + Sys_LeaveCriticalSection( crit ); + return; + } + + // skip the riff wav header + + FS_Read( dump, 12, fh ); + + if ( !S_FindWavChunk( fh, "fmt " ) ) { + Com_Printf( "No fmt chunk in %s\n", name ); + FS_FCloseFile( fh ); + Sys_LeaveCriticalSection( crit ); + return; + } + + // save name for soundinfo + ss->info.format = FGetLittleShort( fh ); + ss->info.channels = FGetLittleShort( fh ); + ss->info.rate = FGetLittleLong( fh ); + FGetLittleLong( fh ); + FGetLittleShort( fh ); + ss->info.width = FGetLittleShort( fh ) / 8; + + if ( ss->info.format != WAV_FORMAT_PCM ) { + FS_FCloseFile( fh ); + Com_Printf( "Not a microsoft PCM format wav: %s\n", name ); + Sys_LeaveCriticalSection( crit ); + return; + } + + if ( ss->info.channels != 2 || ss->info.rate != 22050 ) { + Com_Printf( "WARNING: music file %s is not 22k stereo\n", name ); + } + + if ( ( len = S_FindWavChunk( fh, "data" ) ) == 0 ) { + FS_FCloseFile( fh ); + Com_Printf( "No data chunk in %s\n", name ); + Sys_LeaveCriticalSection( crit ); + return; + } + + ss->info.samples = len / ( ss->info.width * ss->info.channels ); + + ss->samples = ss->info.samples; + + // + // start the background streaming + // + Sys_BeginStreamedFile( fh, 0x10000 ); + + ss->file = fh; + ss->kill = qfalse; + numStreamingSounds++; + + Com_DPrintf( "S_StartBackgroundTrack - Success\n", intro, loop ); + Sys_LeaveCriticalSection( crit ); +} + +/* +====================== +S_StartStreamingSound + + FIXME: record the starting cg.time of the sound, so we can determine the + position by looking at the current cg.time, this way pausing or loading a + savegame won't screw up the timing of important sounds +====================== +*/ +void S_StartStreamingSound( const char *intro, const char *loop, int entnum, int channel, int attenuation ) { + int len; + char dump[16]; + char name[MAX_QPATH]; + int i; + streamingSound_t *ss; + fileHandle_t fh; + + if ( !crit || !s_soundStarted || s_soundMuted || cls.state != CA_ACTIVE ) { + return; + } + + Sys_EnterCriticalSection( crit ); + if ( !intro || !intro[0] ) { + if ( loop && loop[0] ) { + intro = loop; + } else { + intro = ""; + } + } + Com_DPrintf( "S_StartStreamingSound( %s, %s, %i, %i, %i )\n", intro, loop, entnum, channel, attenuation ); + + // look for a free track, but first check for overriding a currently playing sound for this entity + ss = NULL; + if ( entnum >= 0 ) { + for ( i = 1; i < MAX_STREAMING_SOUNDS; i++ ) { // track 0 is music/cinematics + if ( !streamingSounds[i].file ) { + continue; + } + // check to see if this character currently has another sound streaming on the same channel + if ( ( channel != CHAN_AUTO ) && ( streamingSounds[i].entnum >= 0 ) && ( streamingSounds[i].channel == channel ) && ( streamingSounds[i].entnum == entnum ) ) { + // found a match, override this channel + streamingSounds[i].kill = qtrue; + ss = &streamingSounds[i]; // use this track to start the new stream + break; + } + } + } + if ( !ss ) { + // no need to override a current stream, so look for a free track + for ( i = 1; i < MAX_STREAMING_SOUNDS; i++ ) { // track 0 is music/cinematics + if ( !streamingSounds[i].file ) { + ss = &streamingSounds[i]; + break; + } + } + } + if ( !ss ) { + if ( !s_mute->integer ) { // don't do the print if you're muted + Com_Printf( "S_StartStreamingSound: No free streaming tracks\n" ); + } + Sys_LeaveCriticalSection( crit ); + return; + } + + if ( ss->loop && loop ) { + Q_strncpyz( ss->loop, loop, sizeof( ss->loop ) - 4 ); + } else { + ss->loop[0] = 0; + } + + Q_strncpyz( name, intro, sizeof( name ) - 4 ); + COM_DefaultExtension( name, sizeof( name ), ".wav" ); + + // close the current sound if present, but DON'T reset s_rawend + if ( ss->file ) { + Sys_EndStreamedFile( ss->file ); + FS_FCloseFile( ss->file ); + ss->file = 0; + } + + if ( !intro[0] ) { + Sys_LeaveCriticalSection( crit ); + return; + } + + // + // open up a wav file and get all the info + // + FS_FOpenFileRead( name, &fh, qtrue ); + if ( !fh ) { + Com_Printf( "Couldn't open streaming sound file %s\n", name ); + Sys_LeaveCriticalSection( crit ); + return; + } + + // skip the riff wav header + + FS_Read( dump, 12, fh ); + + if ( !S_FindWavChunk( fh, "fmt " ) ) { + Com_Printf( "No fmt chunk in %s\n", name ); + FS_FCloseFile( fh ); + Sys_LeaveCriticalSection( crit ); + return; + } + + // save name for soundinfo + ss->info.format = FGetLittleShort( fh ); + ss->info.channels = FGetLittleShort( fh ); + ss->info.rate = FGetLittleLong( fh ); + FGetLittleLong( fh ); + FGetLittleShort( fh ); + ss->info.width = FGetLittleShort( fh ) / 8; + + if ( ss->info.format != WAV_FORMAT_PCM ) { + FS_FCloseFile( fh ); + Com_Printf( "Not a microsoft PCM format wav: %s\n", name ); + Sys_LeaveCriticalSection( crit ); + return; + } + + //if ( ss->info.channels != 2 || ss->info.rate != 22050 ) { + // Com_Printf("WARNING: music file %s is not 22k stereo\n", name ); + //} + + if ( ( len = S_FindWavChunk( fh, "data" ) ) == 0 ) { + FS_FCloseFile( fh ); + Com_Printf( "No data chunk in %s\n", name ); + Sys_LeaveCriticalSection( crit ); + return; + } + + ss->info.samples = len / ( ss->info.width * ss->info.channels ); + + ss->samples = ss->info.samples; + ss->channel = channel; + ss->attenuation = attenuation; + ss->entnum = entnum; + ss->kill = qfalse; + + // + // start the background streaming + // + Sys_BeginStreamedFile( fh, 0x10000 ); + + ss->file = fh; + numStreamingSounds++; + Sys_LeaveCriticalSection( crit ); +} + +/* +====================== +S_StopStreamingSound +====================== +*/ +void S_StopStreamingSound( int index ) { + if ( !streamingSounds[index].file ) { + return; + } + Sys_EnterCriticalSection( crit ); + streamingSounds[index].kill = qtrue; + Sys_LeaveCriticalSection( crit ); +} + +/* +====================== +S_StopBackgroundTrack +====================== +*/ +void S_StopBackgroundTrack( void ) { + S_StopStreamingSound( 0 ); +} + +/* +====================== +S_UpdateStreamingSounds +====================== +*/ +void S_UpdateStreamingSounds( void ) { + int bufferSamples; + int fileSamples; + byte raw[30000]; // just enough to fit in a mac stack frame + int fileBytes; + int r, i; + streamingSound_t *ss; + int *re, *rp; + qboolean looped; + float lvol, rvol; + int soundMixAheadTime; + +// if (!s_soundStarted || !crit) { +// return; +// } + + if ( s_mute->value ) { //----(SA) sound is muted, skip everything + return; + } + + soundMixAheadTime = s_soundtime; // + (int)(0.35 * dma.speed); // allow for talking animations + + //----(SA) it seems this could potentially be in the wrong place. + // The intended purpose is to just quiet all sounds if s_mute is set (like a TV mute button) + // however, it seems the location here could potentially cause some streaming sound updates + // to not happen properly, so if you mute and un-mute while listening to a conversation + // you could screw up the timing. Is that the case? + // Ryan, could you give it a quick once-over to see if this is okay? + // + // (go ahead and delete commentary when you read) + + + s_soundPainted = qtrue; + + for ( i = 0, ss = streamingSounds, re = s_rawend, rp = s_rawpainted; i < MAX_STREAMING_SOUNDS; i++, ss++, re++, rp++ ) { + if ( ss->kill ) { + fileHandle_t file; + file = ss->file; + ss->file = 0; + Sys_EndStreamedFile( file ); + FS_FCloseFile( file ); + numStreamingSounds--; + ss->kill = qfalse; + continue; + } + + *rp = qfalse; + + if ( !ss->file ) { + continue; + } + + // don't bother playing anything if musicvolume is 0 + if ( i == 0 && s_musicVolume->value <= 0 ) { + continue; + } + if ( i > 0 && s_volume->value <= 0 ) { + continue; + } + + // see how many samples should be copied into the raw buffer + if ( *re < soundMixAheadTime ) { // RF, read a bit ahead of time to allow for talking animations + *re = soundMixAheadTime; + } + + looped = qfalse; + + while ( *re < soundMixAheadTime + MAX_RAW_SAMPLES ) { + bufferSamples = MAX_RAW_SAMPLES - ( *re - soundMixAheadTime ); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * ss->info.rate / dma.speed; + + // if there are no samples due to be read this frame, abort painting + // but keep the streaming going, since it might just need to wait until + // the next frame before it needs to paint some more + if ( !fileSamples ) { + break; + } + + // don't try and read past the end of the file + if ( fileSamples > ss->samples ) { + fileSamples = ss->samples; + } + + // our max buffer size + fileBytes = fileSamples * ( ss->info.width * ss->info.channels ); + if ( fileBytes > sizeof( raw ) ) { + fileBytes = sizeof( raw ); + fileSamples = fileBytes / ( ss->info.width * ss->info.channels ); + } + + r = Sys_StreamedRead( raw, 1, fileBytes, ss->file ); + if ( r != fileBytes ) { + Com_DPrintf( "StreamedRead failure on stream sound\n" ); + ss->kill = qtrue; + break; + } + + // byte swap if needed + S_ByteSwapRawSamples( fileSamples, ss->info.width, ss->info.channels, (short*)raw ); + + // calculate the volume + if ( i == 0 ) { // music + lvol = rvol = s_musicVolume->value; + } else { // attenuate if required + if ( ss->entnum >= 0 && ss->attenuation ) { + int r, l; + S_SpatializeOrigin( entityPositions[ ss->entnum ], s_volume->value * 255.0f, &l, &r, SOUND_RANGE_DEFAULT ); + if ( ( lvol = ( (float)l / 255.0 ) ) > 1.0 ) { + lvol = 1.0; + } + if ( ( rvol = ( (float)r / 255.0 ) ) > 1.0 ) { + rvol = 1.0; + } + } else { + lvol = rvol = s_volume->value; + } + } + + // add to raw buffer + S_RawSamples( fileSamples, ss->info.rate, + ss->info.width, ss->info.channels, raw, lvol, rvol, i ); + + *rp = qtrue; + + ss->samples -= fileSamples; + + if ( !ss->samples ) { + + if ( ss->loop && ss->loop[0] ) { + // loop + if ( looped ) { + // already looped once + //*re = 0; + break; + } else { + char dump[16]; + Sys_StreamSeek( ss->file, 0, FS_SEEK_SET ); + FS_Read( dump, 12, ss->file ); + + if ( !S_FindWavChunk( ss->file, "fmt " ) ) { + ss->kill = qtrue; + break; + } + + // save name for soundinfo + ss->info.format = FGetLittleShort( ss->file ); + ss->info.channels = FGetLittleShort( ss->file ); + ss->info.rate = FGetLittleLong( ss->file ); + FGetLittleLong( ss->file ); + FGetLittleShort( ss->file ); + ss->info.width = FGetLittleShort( ss->file ) / 8; + looped = qtrue; + ss->samples = ss->info.samples; + if ( ( S_FindWavChunk( ss->file, "data" ) ) == 0 ) { + ss->kill = qtrue; + return; + } + } + } else { + // no loop, just stop + ss->kill = qtrue;; + break; + } + + } + } + } +} diff --git a/src/client/snd_local.h b/src/client/snd_local.h new file mode 100644 index 0000000..cd57fc6 --- /dev/null +++ b/src/client/snd_local.h @@ -0,0 +1,243 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// snd_local.h -- private sound definations + + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "snd_public.h" + +#define PAINTBUFFER_SIZE 4096 // this is in samples + +#define SND_CHUNK_SIZE 1024 // samples +#define SND_CHUNK_SIZE_FLOAT ( SND_CHUNK_SIZE / 2 ) // floats +#define SND_CHUNK_SIZE_BYTE ( SND_CHUNK_SIZE * 2 ) // floats + +//#define TALKANIM // NERVE - SMF - we don't want this for multiplayer + +typedef struct adpcm_state { + short sample; /* Previous output value */ + char index; /* Index into stepsize table */ +} adpcm_state_t; + +typedef struct sndBuffer_s { + short sndChunk[SND_CHUNK_SIZE]; + struct sndBuffer_s *next; + int size; + adpcm_state_t adpcm; +} sndBuffer; + +typedef struct sfx_s { + sndBuffer *soundData; + qboolean defaultSound; // couldn't be loaded, so use buzz + qboolean inMemory; // not in Memory + qboolean soundCompressed; // not in Memory + int soundCompressionMethod; + int soundLength; + char soundName[MAX_QPATH]; + int lastTimeUsed; + struct sfx_s *next; +} sfx_t; + +typedef struct { + int channels; + int samples; // mono samples in buffer + int submission_chunk; // don't mix less than this # + int samplebits; + int speed; + int samplepos; + byte *buffer; +} dma_t; + +#define START_SAMPLE_IMMEDIATE 0x7fffffff + +typedef struct loopSound_s { + vec3_t origin; + vec3_t velocity; + float range; //----(SA) added + sfx_t *sfx; + int mergeFrame; + int vol; + qboolean loudUnderWater; // (SA) set if this sound should be played at full vol even when under water (under water loop sound for ex.) + qboolean doppler; + float dopplerScale; + float oldDopplerScale; + int framenum; +} loopSound_t; + +typedef struct +{ + int *prt; //DAJ BUGFIX for freelist/endlist pointer + int allocTime; + int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix + int entnum; // to allow overriding a specific sound + int entchannel; // to allow overriding a specific sound + int leftvol; // 0-255 volume after spatialization + int rightvol; // 0-255 volume after spatialization + int master_vol; // 0-255 volume before spatialization + float dopplerScale; + float oldDopplerScale; + vec3_t origin; // only use if fixed_origin is set + qboolean fixed_origin; // use origin instead of fetching entnum's origin + sfx_t *thesfx; // sfx structure + qboolean doppler; + int flags; //----(SA) added + qboolean threadReady; +} channel_t; + + +#define WAV_FORMAT_PCM 1 + + +typedef struct { + int format; + int rate; + int width; + int channels; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + + +/* +==================================================================== + + SYSTEM SPECIFIC FUNCTIONS + +==================================================================== +*/ + +// initializes cycling through a DMA buffer and returns information on it +qboolean SNDDMA_Init( void ); + +// gets the current DMA position +int SNDDMA_GetDMAPos( void ); + +// shutdown the DMA xfer. +void SNDDMA_Shutdown( void ); + +void SNDDMA_BeginPainting( void ); + +void SNDDMA_Submit( void ); + +//==================================================================== + +#ifdef __MACOS__ +#define MAX_CHANNELS 64 +#else +#define MAX_CHANNELS 96 +#endif + +extern channel_t s_channels[MAX_CHANNELS]; +extern channel_t loop_channels[MAX_CHANNELS]; +extern int numLoopChannels; + +extern int s_paintedtime; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; +extern dma_t dma; + +#ifdef TALKANIM +extern unsigned char s_entityTalkAmplitude[MAX_CLIENTS]; +#endif + +// Ridah, streaming sounds +typedef struct { + fileHandle_t file; + wavinfo_t info; + int samples; + char loop[MAX_QPATH]; + int entnum; + int channel; + int attenuation; + qboolean kill; +} streamingSound_t; + +#ifdef __MACOS__ +#define MAX_STREAMING_SOUNDS 12 //DAJ use SP number (was 24) // need to keep it low, or the rawsamples will get too big +#else +#define MAX_STREAMING_SOUNDS 24 // need to keep it low, or the rawsamples will get too big +#endif +#define MAX_RAW_SAMPLES 16384 + +extern streamingSound_t streamingSounds[MAX_STREAMING_SOUNDS]; +extern int s_rawend[MAX_STREAMING_SOUNDS]; +extern portable_samplepair_t s_rawsamples[MAX_STREAMING_SOUNDS][MAX_RAW_SAMPLES]; +extern portable_samplepair_t s_rawVolume[MAX_STREAMING_SOUNDS]; + + +extern cvar_t *s_volume; +extern cvar_t *s_nosound; +extern cvar_t *s_khz; +extern cvar_t *s_show; +extern cvar_t *s_mixahead; +extern cvar_t *s_mute; + +extern cvar_t *s_testsound; +extern cvar_t *s_separation; + +qboolean S_LoadSound( sfx_t *sfx ); + +void SND_free( sndBuffer *v ); +sndBuffer* SND_malloc(); +void SND_setup(); + +void S_PaintChannels( int endtime ); + +void S_memoryLoad( sfx_t *sfx ); +portable_samplepair_t *S_GetRawSamplePointer(); + +// spatializes a channel +void S_Spatialize( channel_t *ch ); + +// adpcm functions +int S_AdpcmMemoryNeeded( const wavinfo_t *info ); +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ); +void S_AdpcmGetSamples( sndBuffer *chunk, short *to ); + +// wavelet function + +#define SENTINEL_MULAW_ZERO_RUN 127 +#define SENTINEL_MULAW_FOUR_BIT_RUN 126 + +void S_FreeOldestSound(); + +#define NXStream byte + +void encodeWavelet( sfx_t *sfx, short *packets ); +void decodeWavelet( sndBuffer *stream, short *packets ); + +void encodeMuLaw( sfx_t *sfx, short *packets ); +extern short mulawToShort[256]; + +extern short *sfxScratchBuffer; +extern const sfx_t *sfxScratchPointer; +extern int sfxScratchIndex; + diff --git a/src/client/snd_mem.c b/src/client/snd_mem.c new file mode 100644 index 0000000..29afbd9 --- /dev/null +++ b/src/client/snd_mem.c @@ -0,0 +1,448 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: snd_mem.c + * + * desc: sound caching + * + * $Archive: /Wolfenstein MP/src/client/snd_mem.c $ + * + *****************************************************************************/ + +#include "snd_local.h" + +#define DEF_COMSOUNDMEGS "24" // (SA) upped for GD + +/* +=============================================================================== + +SOUND MEMORY MANAGENT + +=============================================================================== +*/ + +static sndBuffer *buffer = NULL; +static sndBuffer *freelist = NULL; +static int inUse = 0; +static int totalInUse = 0; + +short *sfxScratchBuffer = NULL; +const sfx_t *sfxScratchPointer = NULL; +int sfxScratchIndex = 0; + +extern cvar_t *s_nocompressed; + +/* +================ +SND_free +================ +*/ +void SND_free( sndBuffer *v ) { + *(sndBuffer **)v = freelist; + freelist = (sndBuffer*)v; + inUse += sizeof( sndBuffer ); +} + +/* +================ +SND_malloc +================ +*/ +sndBuffer* SND_malloc() { + sndBuffer *v; + + while ( freelist == NULL ) { + S_FreeOldestSound(); + } + + inUse -= sizeof( sndBuffer ); + totalInUse += sizeof( sndBuffer ); + + v = freelist; + freelist = *(sndBuffer **)freelist; + v->next = NULL; + return v; +} + +/* +================ +SND_setup +================ +*/ +void SND_setup() { + sndBuffer *p, *q; + cvar_t *cv; + int scs; + + cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE ); + + scs = cv->integer * 512; + + buffer = malloc( scs * sizeof( sndBuffer ) ); + // allocate the stack based hunk allocator +//DAJ HOG sfxScratchBuffer = malloc(SND_CHUNK_SIZE * sizeof(short) * 4); + sfxScratchBuffer = Hunk_Alloc( SND_CHUNK_SIZE * sizeof( short ) * 4, h_high ); //DAJ HOG was CO + sfxScratchPointer = NULL; + + inUse = scs * sizeof( sndBuffer ); + p = buffer;; + q = p + scs; + while ( --q > p ) { + *(sndBuffer **)q = q - 1; + } + *(sndBuffer **)q = NULL; + freelist = p + scs - 1; + + Com_Printf( "Sound memory manager started\n" ); +} + +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + +static byte *data_p; +static byte *iff_end; +static byte *last_chunk; +static byte *iff_data; +static int iff_chunk_len; + +/* +================ +GetLittleShort +================ +*/ +static short GetLittleShort( void ) { + short val = 0; + val = *data_p; + val = val + ( *( data_p + 1 ) << 8 ); + data_p += 2; + return val; +} + +/* +================ +GetLittleLong +================ +*/ +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; +} + +/* +================ +FindNextChunk +================ +*/ +static void FindNextChunk( char *name ) { + while ( 1 ) + { + data_p = last_chunk; + + if ( data_p >= iff_end ) { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = GetLittleLong(); + if ( iff_chunk_len < 0 ) { + data_p = NULL; + return; + } + data_p -= 8; + last_chunk = data_p + 8 + ( ( iff_chunk_len + 1 ) & ~1 ); + if ( !strncmp( (char *)data_p, name, 4 ) ) { + return; + } + } +} + +/* +================ +FindChunk +================ +*/ +static void FindChunk( char *name ) { + last_chunk = iff_data; + FindNextChunk( name ); +} + +/* +============ +GetWavinfo +============ +*/ +static wavinfo_t GetWavinfo( char *name, byte *wav, int wavlength ) { + wavinfo_t info; + + Com_Memset( &info, 0, sizeof( info ) ); + + if ( !wav ) { + return info; + } + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk( "RIFF" ); + if ( !( data_p && !strncmp( (char *)data_p + 8, "WAVE", 4 ) ) ) { + Com_Printf( "Missing RIFF/WAVE chunks\n" ); + return info; + } + +// get "fmt " chunk + iff_data = data_p + 12; +// DumpChunks (); + + FindChunk( "fmt " ); + if ( !data_p ) { + Com_Printf( "Missing fmt chunk\n" ); + return info; + } + data_p += 8; + info.format = GetLittleShort(); + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4 + 2; + info.width = GetLittleShort() / 8; + + if ( info.format != 1 ) { + Com_Printf( "Microsoft PCM format only\n" ); + return info; + } + + +// find data chunk + FindChunk( "data" ); + if ( !data_p ) { + Com_Printf( "Missing data chunk\n" ); + return info; + } + + data_p += 4; + info.samples = GetLittleLong() / info.width; + info.dataofs = data_p - wav; + + return info; +} + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) { + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + int part; + sndBuffer *chunk; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = sfx->soundLength / stepscale; + sfx->soundLength = outcount; + + samplefrac = 0; + fracstep = stepscale * 256; + chunk = sfx->soundData; + + for ( i = 0 ; i < outcount ; i++ ) + { + srcsample = samplefrac >> 8; + samplefrac += fracstep; + if ( inwidth == 2 ) { + sample = LittleShort( ( (short *)data )[srcsample] ); + } else { + sample = (int)( ( unsigned char )( data[srcsample] ) - 128 ) << 8; + } + part = ( i & ( SND_CHUNK_SIZE - 1 ) ); + if ( part == 0 ) { + sndBuffer *newchunk; + newchunk = SND_malloc(); + if ( chunk == NULL ) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + } + + chunk->sndChunk[part] = sample; + } +} + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) { + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = samples / stepscale; + + samplefrac = 0; + fracstep = stepscale * 256; + + for ( i = 0 ; i < outcount ; i++ ) + { + srcsample = samplefrac >> 8; + samplefrac += fracstep; + if ( inwidth == 2 ) { + sample = LittleShort( ( (short *)data )[srcsample] ); + } else { + sample = (int)( ( unsigned char )( data[srcsample] ) - 128 ) << 8; + } + sfx[i] = sample; + } + return outcount; +} + + +//============================================================================= + +/* +============== +S_LoadSound + +The filename may be different than sfx->name in the case +of a forced fallback of a player specific sound +============== +*/ +qboolean S_LoadSound( sfx_t *sfx ) { + byte *data; + short *samples; + wavinfo_t info; + int size; + + // player specific sounds are never directly loaded + if ( sfx->soundName[0] == '*' ) { + return qfalse; + } + + // load it in + size = FS_ReadFile( sfx->soundName, (void **)&data ); + if ( !data ) { + return qfalse; + } + + info = GetWavinfo( sfx->soundName, data, size ); + if ( info.channels != 1 ) { + Com_Printf( "%s is a stereo wav file\n", sfx->soundName ); + FS_FreeFile( data ); + return qfalse; + } + + if ( info.width == 1 ) { + Com_DPrintf( S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName ); + } + + if ( info.rate != 22050 ) { + Com_DPrintf( S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName ); + } + + samples = Hunk_AllocateTempMemory( info.samples * sizeof( short ) * 2 ); + + // DHM - Nerve + sfx->lastTimeUsed = Sys_Milliseconds() + 1; + + // each of these compression schemes works just fine + // but the 16bit quality is much nicer and with a local + // install assured we can rely upon the sound memory + // manager to do the right thing for us and page + // sound in as needed + + + if ( s_nocompressed->value ) { + sfx->soundCompressionMethod = 0; + sfx->soundLength = info.samples; + sfx->soundData = NULL; + ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse ); + } else if ( sfx->soundCompressed == qtrue ) { + sfx->soundCompressionMethod = 1; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, ( data + info.dataofs ) ); + S_AdpcmEncodeSound( sfx, samples ); +#ifdef COMPRESSION + } else if ( info.samples > ( SND_CHUNK_SIZE * 16 ) && info.width > 1 ) { + sfx->soundCompressionMethod = 3; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, ( data + info.dataofs ) ); + encodeMuLaw( sfx, samples ); + } else if ( info.samples > ( SND_CHUNK_SIZE * 6400 ) && info.width > 1 ) { + sfx->soundCompressionMethod = 2; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, ( data + info.dataofs ) ); + encodeWavelet( sfx, samples ); +#endif + } else { + sfx->soundCompressionMethod = 0; + sfx->soundLength = info.samples; + sfx->soundData = NULL; + ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse ); + } + Hunk_FreeTempMemory( samples ); + FS_FreeFile( data ); + + return qtrue; +} + +/* +================ +S_DisplayFreeMemory +================ +*/ +void S_DisplayFreeMemory() { + Com_Printf( "%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse ); +} diff --git a/src/client/snd_mix.c b/src/client/snd_mix.c new file mode 100644 index 0000000..bb031da --- /dev/null +++ b/src/client/snd_mix.c @@ -0,0 +1,954 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: snd_mix.c + * + * desc: portable code to mix sounds for snd_dma.c + * + * $Archive: /Wolfenstein MP/src/client/snd_mix.c $ + * + *****************************************************************************/ + +#include "snd_local.h" + +portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +static int snd_vol; + +// TTimo not static, required by unix/snd_mixa.s +int *snd_p; +int snd_linear_count; +short *snd_out; + +#if !( defined __linux__ && defined __i386__ ) +#if !id386 + +/* +=================== +S_WriteLinearBlastStereo16 +=================== +*/ +void S_WriteLinearBlastStereo16( void ) { + int i; + int val; + + for ( i = 0 ; i < snd_linear_count ; i += 2 ) + { + val = snd_p[i] >> 8; + if ( val > 0x7fff ) { + snd_out[i] = 0x7fff; + } else if ( val < (short)0x8000 ) { + snd_out[i] = (short)0x8000; + } else { + snd_out[i] = val; + } + + val = snd_p[i + 1] >> 8; + if ( val > 0x7fff ) { + snd_out[i + 1] = 0x7fff; + } else if ( val < (short)0x8000 ) { + snd_out[i + 1] = (short)0x8000; + } else { + snd_out[i + 1] = val; + } + } +} + +#else // !id386 + +__declspec( naked ) void S_WriteLinearBlastStereo16( void ) { + __asm { + + push edi + push ebx + mov ecx,ds : dword ptr[snd_linear_count] + mov ebx,ds : dword ptr[snd_p] + mov edi,ds : dword ptr[snd_out] +LWLBLoopTop: + mov eax,ds : dword ptr[-8 + ebx + ecx * 4] + sar eax,8 + cmp eax,07FFFh + jg LClampHigh + cmp eax,0FFFF8000h + jnl LClampDone + mov eax,0FFFF8000h + jmp LClampDone +LClampHigh: + mov eax,07FFFh +LClampDone: + mov edx,ds : dword ptr[-4 + ebx + ecx * 4] + sar edx,8 + cmp edx,07FFFh + jg LClampHigh2 + cmp edx,0FFFF8000h + jnl LClampDone2 + mov edx,0FFFF8000h + jmp LClampDone2 +LClampHigh2: + mov edx,07FFFh +LClampDone2: + shl edx,16 + and eax,0FFFFh + or edx,eax + mov ds : dword ptr[-4 + edi + ecx * 2],edx + sub ecx,2 + jnz LWLBLoopTop + pop ebx + pop edi + ret + } +} + +#endif // !id386 + +#else // !(defined __linux__ && defined __i386__) + +// snd_mixa.s +void S_WriteLinearBlastStereo16( void ); + +#endif + +/* +=================== +S_TransferStereo16 +=================== +*/ +void S_TransferStereo16( unsigned long *pbuf, int endtime ) { + int lpos; + int ls_paintedtime; + + snd_p = (int *) paintbuffer; + ls_paintedtime = s_paintedtime; + + while ( ls_paintedtime < endtime ) + { + // handle recirculating buffer issues + lpos = ls_paintedtime & ( ( dma.samples >> 1 ) - 1 ); + + snd_out = (short *) pbuf + ( lpos << 1 ); + + snd_linear_count = ( dma.samples >> 1 ) - lpos; + if ( ls_paintedtime + snd_linear_count > endtime ) { + snd_linear_count = endtime - ls_paintedtime; + } + + snd_linear_count <<= 1; + + // write a linear blast of samples + S_WriteLinearBlastStereo16(); + + snd_p += snd_linear_count; + ls_paintedtime += ( snd_linear_count >> 1 ); + } +} + +/* +=================== +S_TransferPaintBuffer +=================== +*/ +void S_TransferPaintBuffer( int endtime ) { + int out_idx; + int count; + int out_mask; + int *p; + int step; + int val; + unsigned long *pbuf; + + pbuf = (unsigned long *)dma.buffer; + if ( !pbuf ) { + return; + } + + if ( s_testsound->integer ) { + int i; + int count; + + // write a fixed sine wave + count = ( endtime - s_paintedtime ); + for ( i = 0 ; i < count ; i++ ) + paintbuffer[i].left = paintbuffer[i].right = sin( ( s_paintedtime + i ) * 0.1 ) * 20000 * 256; + } + + + if ( dma.samplebits == 16 && dma.channels == 2 ) { + // optimized case + S_TransferStereo16( pbuf, endtime ); + } else + { // general case + p = (int *) paintbuffer; + count = ( endtime - s_paintedtime ) * dma.channels; + out_mask = dma.samples - 1; + out_idx = s_paintedtime * dma.channels & out_mask; + step = 3 - dma.channels; + + if ( dma.samplebits == 16 ) { + short *out = (short *) pbuf; + while ( count-- ) + { + val = *p >> 8; + p += step; + if ( val > 0x7fff ) { + val = 0x7fff; + } else if ( val < -32768 ) { + val = -32768; + } + out[out_idx] = val; + out_idx = ( out_idx + 1 ) & out_mask; + } + } else if ( dma.samplebits == 8 ) { + unsigned char *out = (unsigned char *) pbuf; + while ( count-- ) + { + val = *p >> 8; + p += step; + if ( val > 0x7fff ) { + val = 0x7fff; + } else if ( val < -32768 ) { + val = -32768; + } + out[out_idx] = ( val >> 8 ) + 128; + out_idx = ( out_idx + 1 ) & out_mask; + } + } + } +} + +/* +=============================================================================== + +LIP SYNCING + +=============================================================================== +*/ + +#ifdef TALKANIM + +unsigned char s_entityTalkAmplitude[MAX_CLIENTS]; + +/* +=================== +S_SetVoiceAmplitudeFrom16 +=================== +*/ +void S_SetVoiceAmplitudeFrom16( const sfx_t *sc, int sampleOffset, int count, int entnum ) { + int data, i, sfx_count; + sndBuffer *chunk; + short *samples; + + if ( count <= 0 ) { + return; // must have gone ahead of the end of the sound + } + chunk = sc->soundData; + while ( sampleOffset >= SND_CHUNK_SIZE ) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if ( !chunk ) { + chunk = sc->soundData; + } + } + + sfx_count = 0; + samples = chunk->sndChunk; + for ( i = 0; i < count; i++ ) { + if ( sampleOffset >= SND_CHUNK_SIZE ) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + data = samples[sampleOffset++]; + if ( abs( data ) > 5000 ) { + sfx_count += ( data * 255 ) >> 8; + } + } + //Com_Printf("Voice sfx_count = %d, count = %d\n", sfx_count, count ); + // adjust the sfx_count according to the frametime (scale down for longer frametimes) + sfx_count = abs( sfx_count ); + sfx_count = (int)( (float)sfx_count / ( 2.0 * (float)count ) ); + if ( sfx_count > 255 ) { + sfx_count = 255; + } + if ( sfx_count < 25 ) { + sfx_count = 0; + } + //Com_Printf("sfx_count = %d\n", sfx_count ); + // update the amplitude for this entity + s_entityTalkAmplitude[entnum] = (unsigned char)sfx_count; +} + +/* +=================== +S_SetVoiceAmplitudeFromADPCM +=================== +*/ +void S_SetVoiceAmplitudeFromADPCM( const sfx_t *sc, int sampleOffset, int count, int entnum ) { + int data, i, sfx_count; + sndBuffer *chunk; + short *samples; + + if ( count <= 0 ) { + return; // must have gone ahead of the end of the sound + } + i = 0; + chunk = sc->soundData; + while ( sampleOffset >= ( SND_CHUNK_SIZE * 4 ) ) { + chunk = chunk->next; + sampleOffset -= ( SND_CHUNK_SIZE * 4 ); + i++; + } + + if ( i != sfxScratchIndex || sfxScratchPointer != sc ) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + sfx_count = 0; + samples = sfxScratchBuffer; + for ( i = 0; i < count; i++ ) { + if ( sampleOffset >= SND_CHUNK_SIZE * 4 ) { + chunk = chunk->next; + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sampleOffset = 0; + sfxScratchIndex++; + } + data = samples[sampleOffset++]; + if ( abs( data ) > 5000 ) { + sfx_count += ( data * 255 ) >> 8; + } + } + //Com_Printf("Voice sfx_count = %d, count = %d\n", sfx_count, count ); + // adjust the sfx_count according to the frametime (scale down for longer frametimes) + sfx_count = abs( sfx_count ); + sfx_count = (int)( (float)sfx_count / ( 2.0 * (float)count ) ); + if ( sfx_count > 255 ) { + sfx_count = 255; + } + if ( sfx_count < 25 ) { + sfx_count = 0; + } + //Com_Printf("sfx_count = %d\n", sfx_count ); + // update the amplitude for this entity + s_entityTalkAmplitude[entnum] = (unsigned char)sfx_count; +} + +/* +=================== +S_SetVoiceAmplitudeFromWavelet +=================== +*/ +void S_SetVoiceAmplitudeFromWavelet( const sfx_t *sc, int sampleOffset, int count, int entnum ) { + int data, i, sfx_count; + sndBuffer *chunk; + short *samples; + + if ( count <= 0 ) { + return; // must have gone ahead of the end of the sound + } + i = 0; + chunk = sc->soundData; + while ( sampleOffset >= ( SND_CHUNK_SIZE_FLOAT * 4 ) ) { + chunk = chunk->next; + sampleOffset -= ( SND_CHUNK_SIZE_FLOAT * 4 ); + i++; + } + if ( i != sfxScratchIndex || sfxScratchPointer != sc ) { + decodeWavelet( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + sfx_count = 0; + samples = sfxScratchBuffer; + for ( i = 0; i < count; i++ ) { + if ( sampleOffset >= ( SND_CHUNK_SIZE_FLOAT * 4 ) ) { + chunk = chunk->next; + decodeWavelet( chunk, sfxScratchBuffer ); + sfxScratchIndex++; + sampleOffset = 0; + } + data = samples[sampleOffset++]; + if ( abs( data ) > 5000 ) { + sfx_count += ( data * 255 ) >> 8; + } + } + + //Com_Printf("Voice sfx_count = %d, count = %d\n", sfx_count, count ); + // adjust the sfx_count according to the frametime (scale down for longer frametimes) + sfx_count = abs( sfx_count ); + sfx_count = (int)( (float)sfx_count / ( 2.0 * (float)count ) ); + if ( sfx_count > 255 ) { + sfx_count = 255; + } + if ( sfx_count < 25 ) { + sfx_count = 0; + } + //Com_Printf("sfx_count = %d\n", sfx_count ); + // update the amplitude for this entity + s_entityTalkAmplitude[entnum] = (unsigned char)sfx_count; +} + +/* +=================== +S_SetVoiceAmplitudeFromMuLaw +=================== +*/ +void S_SetVoiceAmplitudeFromMuLaw( const sfx_t *sc, int sampleOffset, int count, int entnum ) { + int data, i, sfx_count; + sndBuffer *chunk; + byte *samples; + + if ( count <= 0 ) { + return; // must have gone ahead of the end of the sound + } + chunk = sc->soundData; + while ( sampleOffset >= ( SND_CHUNK_SIZE * 2 ) ) { + chunk = chunk->next; + sampleOffset -= ( SND_CHUNK_SIZE * 2 ); + if ( !chunk ) { + chunk = sc->soundData; + } + } + sfx_count = 0; + samples = (byte *)chunk->sndChunk + sampleOffset; + for ( i = 0; i < count; i++ ) { + if ( samples >= (byte *)chunk->sndChunk + ( SND_CHUNK_SIZE * 2 ) ) { + chunk = chunk->next; + samples = (byte *)chunk->sndChunk; + } + data = mulawToShort[*samples]; + if ( abs( data ) > 5000 ) { + sfx_count += ( data * 255 ) >> 8; + } + samples++; + } + //Com_Printf("Voice sfx_count = %d, count = %d\n", sfx_count, count ); + // adjust the sfx_count according to the frametime (scale down for longer frametimes) + sfx_count = abs( sfx_count ); + sfx_count = (int)( (float)sfx_count / ( 2.0 * (float)count ) ); + if ( sfx_count > 255 ) { + sfx_count = 255; + } + if ( sfx_count < 25 ) { + sfx_count = 0; + } + //Com_Printf("sfx_count = %d\n", sfx_count ); + // update the amplitude for this entity + s_entityTalkAmplitude[entnum] = (unsigned char)sfx_count; +} + +/* +=================== +S_GetVoiceAmplitude +=================== +*/ +int S_GetVoiceAmplitude( int entityNum ) { + if ( entityNum >= MAX_CLIENTS ) { + Com_Printf( "Error: S_GetVoiceAmplitude() called for a non-client\n" ); + return 0; + } + + return (int)s_entityTalkAmplitude[entityNum]; +} +#else + +// NERVE - SMF +int S_GetVoiceAmplitude( int entityNum ) { + return 0; +} +// -NERVE - SMF + +#endif + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +/* +=================== +S_PaintChannelFrom16 +=================== +*/ +static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata, fdiv, fleftvol, frightvol; + + samp = &paintbuffer[ bufferOffset ]; + + if ( ch->doppler ) { + sampleOffset = sampleOffset * ch->oldDopplerScale; + } + + chunk = sc->soundData; + while ( sampleOffset >= SND_CHUNK_SIZE ) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if ( !chunk ) { + chunk = sc->soundData; + } + } + + if ( !ch->doppler ) { + leftvol = ch->leftvol * snd_vol; + rightvol = ch->rightvol * snd_vol; + + samples = chunk->sndChunk; + for ( i = 0; i < count; i++ ) { + if ( sampleOffset >= SND_CHUNK_SIZE ) { + chunk = chunk->next; + if ( chunk == NULL ) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + sampleOffset -= SND_CHUNK_SIZE; + } + data = samples[sampleOffset++]; + samp[i].left += ( data * leftvol ) >> 8; + samp[i].right += ( data * rightvol ) >> 8; + } + } else { + fleftvol = ch->leftvol * snd_vol; + frightvol = ch->rightvol * snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + for ( i = 0 ; i < count ; i++ ) { + aoff = ooff; + ooff = ooff + ch->dopplerScale; + boff = ooff; + fdata = 0; + for ( j = aoff; j < boff; j++ ) { + if ( j >= SND_CHUNK_SIZE ) { + chunk = chunk->next; + if ( !chunk ) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + fdata += samples[j & ( SND_CHUNK_SIZE - 1 )]; + } + fdiv = 256 * ( boff - aoff ); + samp[i].left += ( fdata * fleftvol ) / fdiv; + samp[i].right += ( fdata * frightvol ) / fdiv; + } + } +} + +/* +=================== +S_PaintChannelFromWavelet +=================== +*/ +void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol * snd_vol; + rightvol = ch->rightvol * snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while ( sampleOffset >= ( SND_CHUNK_SIZE_FLOAT * 4 ) ) { + chunk = chunk->next; + sampleOffset -= ( SND_CHUNK_SIZE_FLOAT * 4 ); + i++; + } + + if ( i != sfxScratchIndex || sfxScratchPointer != sc ) { + decodeWavelet( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + // FIXME: doppler + + for ( i = 0; i < count; i++ ) { + if ( sampleOffset >= ( SND_CHUNK_SIZE_FLOAT * 4 ) ) { + chunk = chunk->next; + decodeWavelet( chunk, sfxScratchBuffer ); + sfxScratchIndex++; + sampleOffset = 0; + } + data = samples[sampleOffset++]; + samp[i].left += ( data * leftvol ) >> 8; + samp[i].right += ( data * rightvol ) >> 8; + } +} + +/* +=================== +S_PaintChannelFromADPCM +=================== +*/ +void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol * snd_vol; + rightvol = ch->rightvol * snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + + if ( ch->doppler ) { + sampleOffset = sampleOffset * ch->oldDopplerScale; + } + + while ( sampleOffset >= ( SND_CHUNK_SIZE * 4 ) ) { + chunk = chunk->next; + sampleOffset -= ( SND_CHUNK_SIZE * 4 ); + i++; + } + + if ( i != sfxScratchIndex || sfxScratchPointer != sc ) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + for ( i = 0; i < count; i++ ) { + if ( sampleOffset >= SND_CHUNK_SIZE * 4 ) { + chunk = chunk->next; + if ( !chunk ) { + chunk = sc->soundData; + } + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sampleOffset = 0; + sfxScratchIndex++; + } + data = samples[sampleOffset++]; + samp[i].left += ( data * leftvol ) >> 8; + samp[i].right += ( data * rightvol ) >> 8; + } +} + +/* +=================== +S_PaintChannelFromMuLaw +=================== +*/ +void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + byte *samples; + float ooff; + + leftvol = ch->leftvol * snd_vol; + rightvol = ch->rightvol * snd_vol; + + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while ( sampleOffset >= ( SND_CHUNK_SIZE * 2 ) ) { + chunk = chunk->next; + sampleOffset -= ( SND_CHUNK_SIZE * 2 ); + if ( !chunk ) { + chunk = sc->soundData; + } + } + + if ( !ch->doppler ) { + samples = (byte *)chunk->sndChunk + sampleOffset; + for ( i = 0; i < count; i++ ) { + if ( samples >= (byte *)chunk->sndChunk + ( SND_CHUNK_SIZE * 2 ) ) { + chunk = chunk->next; + samples = (byte *)chunk->sndChunk; + } + data = mulawToShort[*samples]; + samp[i].left += ( data * leftvol ) >> 8; + samp[i].right += ( data * rightvol ) >> 8; + samples++; + } + } else { + ooff = sampleOffset; + samples = (byte *)chunk->sndChunk; + for ( i = 0; i < count; i++ ) { + if ( ooff >= SND_CHUNK_SIZE * 2 ) { + chunk = chunk->next; + if ( !chunk ) { + chunk = sc->soundData; + } + samples = (byte *)chunk->sndChunk; + ooff = 0.0; + } + data = mulawToShort[samples[(int)( ooff )]]; + ooff = ooff + ch->dopplerScale; + samp[i].left += ( data * leftvol ) >> 8; + samp[i].right += ( data * rightvol ) >> 8; + } + } +} + +#define TALK_FUTURE_SEC 0.2 // go this far into the future (seconds) + +/* +=================== +S_PaintChannels +=================== +*/ +void S_PaintChannels( int endtime ) { + int i, si; + int end; + channel_t *ch; + sfx_t *sc; + int ltime, count; + int sampleOffset; + streamingSound_t *ss; + + if ( s_mute->value ) { + snd_vol = 0; + } else { + snd_vol = s_volume->value * 256; + } + + //Com_Printf ("%i to %i\n", s_paintedtime, endtime); + while ( s_paintedtime < endtime ) { + // if paintbuffer is smaller than DMA buffer + // we may need to fill it multiple times + end = endtime; + if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) { + end = s_paintedtime + PAINTBUFFER_SIZE; + } + + // clear pain buffer for the current time + Com_Memset( paintbuffer, 0, ( end - s_paintedtime ) * sizeof( portable_samplepair_t ) ); + // mix all streaming sounds into paint buffer + for ( si = 0, ss = streamingSounds; si < MAX_STREAMING_SOUNDS; si++, ss++ ) { + // if this streaming sound is still playing + if ( s_rawend[si] >= s_paintedtime ) { + // copy from the streaming sound source + int s; + int stop; +// float fsir, fsil; // TTimo: unused + + stop = ( end < s_rawend[si] ) ? end : s_rawend[si]; + + // precalculating this saves zillions of cycles +//DAJ fsir = ((float)s_rawVolume[si].left/255.0f); +//DAJ fsil = ((float)s_rawVolume[si].right/255.0f); + for ( i = s_paintedtime ; i < stop ; i++ ) { + s = i & ( MAX_RAW_SAMPLES - 1 ); +//DAJ paintbuffer[i-s_paintedtime].left += (int)((float)s_rawsamples[si][s].left * fsir); +//DAJ paintbuffer[i-s_paintedtime].right += (int)((float)s_rawsamples[si][s].right * fsil); + //DAJ even faster + paintbuffer[i - s_paintedtime].left += ( s_rawsamples[si][s].left * s_rawVolume[si].left ) >> 8; + paintbuffer[i - s_paintedtime].right += ( s_rawsamples[si][s].right * s_rawVolume[si].right ) >> 8; + } +#ifdef TALKANIM + if ( ss->channel == CHAN_VOICE && ss->entnum < MAX_CLIENTS ) { + int talkcnt, talktime; + int sfx_count, vstop; + int data; + + // we need to go into the future, since the interpolated behaviour of the facial + // animation creates lag in the time it takes to display the current facial frame + talktime = s_paintedtime + (int)( TALK_FUTURE_SEC * (float)s_khz->integer * 1000 ); + vstop = ( talktime + 100 < s_rawend[si] ) ? talktime + 100 : s_rawend[si]; + talkcnt = 1; + sfx_count = 0; + + for ( i = talktime ; i < vstop ; i++ ) { + s = i & ( MAX_RAW_SAMPLES - 1 ); + data = abs( ( s_rawsamples[si][s].left ) / 8000 ); + if ( data > sfx_count ) { + sfx_count = data; + } + } + + if ( sfx_count > 255 ) { + sfx_count = 255; + } + if ( sfx_count < 25 ) { + sfx_count = 0; + } + + //Com_Printf("sfx_count = %d\n", sfx_count ); + + // update the amplitude for this entity + s_entityTalkAmplitude[ss->entnum] = (unsigned char)sfx_count; + } +#endif + } + } + + // paint in the channels. + ch = s_channels; + for ( i = 0; i < MAX_CHANNELS; i++, ch++ ) { + if ( ch->startSample == START_SAMPLE_IMMEDIATE || !ch->thesfx || ( ch->leftvol < 0.25 && ch->rightvol < 0.25 ) ) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + +// if (!sc->inMemory) { +// S_memoryLoad(sc); +// } + + // DHM - Nerve :: Somehow ch->startSample can get here with values around 0x40000000 + if ( ch->startSample > ltime ) { + ch->startSample = ltime; + } + // dhm - end + + sampleOffset = ltime - ch->startSample; + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { +#ifdef TALKANIM + // Ridah, talking animations + // TODO: check that this entity has talking animations enabled! + if ( ch->entchannel == CHAN_VOICE && ch->entnum < MAX_CLIENTS ) { + int talkofs, talkcnt, talktime; + // we need to go into the future, since the interpolated behaviour of the facial + // animation creates lag in the time it takes to display the current facial frame + talktime = ltime + (int)( TALK_FUTURE_SEC * (float)s_khz->integer * 1000 ); + talkofs = talktime - ch->startSample; + talkcnt = 100; + if ( talkofs + talkcnt < sc->soundLength ) { + if ( sc->soundCompressionMethod == 1 ) { + S_SetVoiceAmplitudeFromADPCM( sc, talkofs, talkcnt, ch->entnum ); + } else if ( sc->soundCompressionMethod == 2 ) { + S_SetVoiceAmplitudeFromWavelet( sc, talkofs, talkcnt, ch->entnum ); + } else if ( sc->soundCompressionMethod == 3 ) { + S_SetVoiceAmplitudeFromMuLaw( sc, talkofs, talkcnt, ch->entnum ); + } else { + S_SetVoiceAmplitudeFrom16( sc, talkofs, talkcnt, ch->entnum ); + } + } + } +#endif + if ( sc->soundCompressionMethod == 1 ) { + S_PaintChannelFromADPCM( ch, sc, count, sampleOffset, ltime - s_paintedtime ); + } else if ( sc->soundCompressionMethod == 2 ) { + S_PaintChannelFromWavelet( ch, sc, count, sampleOffset, ltime - s_paintedtime ); + } else if ( sc->soundCompressionMethod == 3 ) { + S_PaintChannelFromMuLaw( ch, sc, count, sampleOffset, ltime - s_paintedtime ); + } else { + S_PaintChannelFrom16( ch, sc, count, sampleOffset, ltime - s_paintedtime ); + } + } + } + + // paint in the looped channels. + ch = loop_channels; + for ( i = 0; i < numLoopChannels ; i++, ch++ ) { + sc = ch->thesfx; + if ( !ch->thesfx || ( !ch->leftvol && !ch->rightvol ) ) { + continue; + } + + ltime = s_paintedtime; + + if ( sc->soundData == NULL || sc->soundLength == 0 ) { + continue; + } + // we might have to make two passes if it + // is a looping sound effect and the end of + // the sample is hit + do { + sampleOffset = ( ltime % sc->soundLength ); + + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { +#ifdef TALKANIM + // Ridah, talking animations + // TODO: check that this entity has talking animations enabled! + if ( ch->entchannel == CHAN_VOICE && ch->entnum < MAX_CLIENTS ) { + int talkofs, talkcnt, talktime; + // we need to go into the future, since the interpolated behaviour of the facial + // animation creates lag in the time it takes to display the current facial frame + talktime = ltime + (int)( TALK_FUTURE_SEC * (float)s_khz->integer * 1000 ); + talkofs = talktime % sc->soundLength; + talkcnt = 100; + if ( talkofs + talkcnt < sc->soundLength ) { + if ( sc->soundCompressionMethod == 1 ) { + S_SetVoiceAmplitudeFromADPCM( sc, talkofs, talkcnt, ch->entnum ); + } else if ( sc->soundCompressionMethod == 2 ) { + S_SetVoiceAmplitudeFromWavelet( sc, talkofs, talkcnt, ch->entnum ); + } else if ( sc->soundCompressionMethod == 3 ) { + S_SetVoiceAmplitudeFromMuLaw( sc, talkofs, talkcnt, ch->entnum ); + } else { + S_SetVoiceAmplitudeFrom16( sc, talkofs, talkcnt, ch->entnum ); + } + } + } +#endif + if ( sc->soundCompressionMethod == 1 ) { + S_PaintChannelFromADPCM( ch, sc, count, sampleOffset, ltime - s_paintedtime ); + } else if ( sc->soundCompressionMethod == 2 ) { + S_PaintChannelFromWavelet( ch, sc, count, sampleOffset, ltime - s_paintedtime ); + } else if ( sc->soundCompressionMethod == 3 ) { + S_PaintChannelFromMuLaw( ch, sc, count, sampleOffset, ltime - s_paintedtime ); + } else { + S_PaintChannelFrom16( ch, sc, count, sampleOffset, ltime - s_paintedtime ); + } + ltime += count; + } + } while ( ltime < end ); + } + + // transfer out according to DMA format + S_TransferPaintBuffer( end ); + s_paintedtime = end; + } +} diff --git a/src/client/snd_public.h b/src/client/snd_public.h new file mode 100644 index 0000000..48d7fbf --- /dev/null +++ b/src/client/snd_public.h @@ -0,0 +1,110 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#ifdef DOOMSOUND ///// (SA) DOOMSOUND +#ifdef __cplusplus +extern "C" { +#endif +#endif ///// (SA) DOOMSOUND + +#ifndef __snd_public_h__ +#define __snd_public_h__ + +void S_Init( void ); +void S_Shutdown( void ); +void S_UpdateThread( void ); + +// if origin is NULL, the sound will be dynamically sourced from the entity +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); +void S_StartSoundEx( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx, int flags ); +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); + +void S_StartBackgroundTrack( const char *intro, const char *loop ); +void S_StopBackgroundTrack( void ); + +void S_StartStreamingSound( const char *intro, const char *loop, int entnum, int channel, int attenuation ); +void S_StopStreamingSound( int index ); + +// cinematics and voice-over-network will send raw samples +// 1.0 volume will be direct output of source samples +void S_RawSamples( int samples, int rate, int width, int s_channels, + const byte *data, float lvol, float rvol, int streamingIndex ); + +// stop all sounds and the background track +void S_StopAllSounds( void ); + +// all continuous looping sounds must be added before calling S_Update +void S_ClearLoopingSounds( void ); +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, const int range, sfxHandle_t sfxHandle, int volume ); +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, const int range, sfxHandle_t sfx ); +void S_StopLoopingSound( int entityNum ); + +#ifdef DOOMSOUND ///// (SA) DOOMSOUND +void S_ClearSoundBuffer( void ); +#endif ///// (SA) DOOMSOUND +// recompute the reletive volumes for all running sounds +// reletive to the given entityNum / orientation +void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + +// let the sound system know where an entity currently is +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +void S_Update( void ); + +void S_DisableSounds( void ); + +void S_BeginRegistration( void ); + +// RegisterSound will allways return a valid sample, even if it +// has to create a placeholder. This prevents continuous filesystem +// checks for missing files +#ifdef DOOMSOUND ///// (SA) DOOMSOUND +sfxHandle_t S_RegisterSound( const char *sample ); +#else +sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ); +#endif ///// (SA) DOOMSOUND + +void S_DisplayFreeMemory( void ); + +// +int S_GetVoiceAmplitude( int entityNum ); + + +typedef struct { + int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down + int right; +} portable_samplepair_t; + +#endif // __snd_public_h__ + +#ifdef DOOMSOUND ///// (SA) DOOMSOUND +#ifdef __cplusplus +} +#endif +#endif ///// (SA) DOOMSOUND diff --git a/src/client/snd_wavelet.c b/src/client/snd_wavelet.c new file mode 100644 index 0000000..53e9ecf --- /dev/null +++ b/src/client/snd_wavelet.c @@ -0,0 +1,280 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: snd_wavelet.c + * + * desc: + * + * + *****************************************************************************/ + +#include "snd_local.h" + +long myftol( float f ); + +#define C0 0.4829629131445341 +#define C1 0.8365163037378079 +#define C2 0.2241438680420134 +#define C3 -0.1294095225512604 + +void daub4( float b[], unsigned long n, int isign ) { + float wksp[4097]; + float *a = b - 1; // numerical recipies so a[1] = b[0] + + unsigned long nh,nh1,i,j; + + if ( n < 4 ) { + return; + } + + nh1 = ( nh = n >> 1 ) + 1; + if ( isign >= 0 ) { + for ( i = 1, j = 1; j <= n - 3; j += 2, i++ ) { + wksp[i] = C0 * a[j] + C1 * a[j + 1] + C2 * a[j + 2] + C3 * a[j + 3]; + wksp[i + nh] = C3 * a[j] - C2 * a[j + 1] + C1 * a[j + 2] - C0 * a[j + 3]; + } + wksp[i ] = C0 * a[n - 1] + C1 * a[n] + C2 * a[1] + C3 * a[2]; + wksp[i + nh] = C3 * a[n - 1] - C2 * a[n] + C1 * a[1] - C0 * a[2]; + } else { + wksp[1] = C2 * a[nh] + C1 * a[n] + C0 * a[1] + C3 * a[nh1]; + wksp[2] = C3 * a[nh] - C0 * a[n] + C1 * a[1] - C2 * a[nh1]; + for ( i = 1, j = 3; i < nh; i++ ) { + wksp[j++] = C2 * a[i] + C1 * a[i + nh] + C0 * a[i + 1] + C3 * a[i + nh1]; + wksp[j++] = C3 * a[i] - C0 * a[i + nh] + C1 * a[i + 1] - C2 * a[i + nh1]; + } + } + for ( i = 1; i <= n; i++ ) { + a[i] = wksp[i]; + } +} + +void wt1( float a[], unsigned long n, int isign ) { + unsigned long nn; + int inverseStartLength = n / 4; + if ( n < inverseStartLength ) { + return; + } + if ( isign >= 0 ) { + for ( nn = n; nn >= inverseStartLength; nn >>= 1 ) daub4( a,nn,isign ); + } else { + for ( nn = inverseStartLength; nn <= n; nn <<= 1 ) daub4( a,nn,isign ); + } +} + +/* The number of bits required by each value */ +static unsigned char numBits[] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, +}; + +byte MuLawEncode( short s ) { + unsigned long adjusted; + byte sign, exponent, mantissa; + + sign = ( s < 0 ) ? 0 : 0x80; + + if ( s < 0 ) { + s = -s; + } + adjusted = (long)s << ( 16 - sizeof( short ) * 8 ); + adjusted += 128L + 4L; + if ( adjusted > 32767 ) { + adjusted = 32767; + } + exponent = numBits[( adjusted >> 7 ) & 0xff] - 1; + mantissa = ( adjusted >> ( exponent + 3 ) ) & 0xf; + return ~( sign | ( exponent << 4 ) | mantissa ); +} + +short MuLawDecode( byte uLaw ) { + signed long adjusted; + byte exponent, mantissa; + + uLaw = ~uLaw; + exponent = ( uLaw >> 4 ) & 0x7; + mantissa = ( uLaw & 0xf ) + 16; + adjusted = ( mantissa << ( exponent + 3 ) ) - 128 - 4; + + return ( uLaw & 0x80 ) ? adjusted : -adjusted; +} + +short mulawToShort[256]; +static qboolean madeTable = qfalse; + +static int NXStreamCount; + +void NXPutc( NXStream *stream, char out ) { + stream[NXStreamCount++] = out; +} + + +void encodeWavelet( sfx_t *sfx, short *packets ) { + float wksp[4097], temp; + int i, samples, size; + sndBuffer *newchunk, *chunk; + byte *out; + + if ( !madeTable ) { + for ( i = 0; i < 256; i++ ) { + mulawToShort[i] = (float)MuLawDecode( (byte)i ); + } + madeTable = qtrue; + } + chunk = NULL; + + samples = sfx->soundLength; + while ( samples > 0 ) { + size = samples; + if ( size > ( SND_CHUNK_SIZE * 2 ) ) { + size = ( SND_CHUNK_SIZE * 2 ); + } + + if ( size < 4 ) { + size = 4; + } + + newchunk = SND_malloc(); + if ( sfx->soundData == NULL ) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + for ( i = 0; i < size; i++ ) { + wksp[i] = *packets; + packets++; + } + wt1( wksp, size, 1 ); + out = (byte *)chunk->sndChunk; + + for ( i = 0; i < size; i++ ) { + temp = wksp[i]; + if ( temp > 32767 ) { + temp = 32767; + } else if ( temp < -32768 ) { + temp = -32768; + } + out[i] = MuLawEncode( (short)temp ); + } + + chunk->size = size; + samples -= size; + } +} + +void decodeWavelet( sndBuffer *chunk, short *to ) { + float wksp[4097]; + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for ( i = 0; i < size; i++ ) { + wksp[i] = mulawToShort[out[i]]; + } + + wt1( wksp, size, -1 ); + + if ( !to ) { + return; + } + + for ( i = 0; i < size; i++ ) { + to[i] = wksp[i]; + } +} + + +void encodeMuLaw( sfx_t *sfx, short *packets ) { + int i, samples, size, grade, poop; + sndBuffer *newchunk, *chunk; + byte *out; + + if ( !madeTable ) { + for ( i = 0; i < 256; i++ ) { + mulawToShort[i] = (float)MuLawDecode( (byte)i ); + } + madeTable = qtrue; + } + + chunk = NULL; + samples = sfx->soundLength; + grade = 0; + + while ( samples > 0 ) { + size = samples; + if ( size > ( SND_CHUNK_SIZE * 2 ) ) { + size = ( SND_CHUNK_SIZE * 2 ); + } + + newchunk = SND_malloc(); + if ( sfx->soundData == NULL ) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + out = (byte *)chunk->sndChunk; + for ( i = 0; i < size; i++ ) { + poop = packets[0] + grade; + if ( poop > 32767 ) { + poop = 32767; + } else if ( poop < -32768 ) { + poop = -32768; + } + out[i] = MuLawEncode( (short)poop ); + grade = poop - mulawToShort[out[i]]; + packets++; + } + chunk->size = size; + samples -= size; + } +} + +void decodeMuLaw( sndBuffer *chunk, short *to ) { + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for ( i = 0; i < size; i++ ) { + to[i] = mulawToShort[out[i]]; + } +} + + diff --git a/src/extractfuncs/ChangeLog b/src/extractfuncs/ChangeLog new file mode 100644 index 0000000..c20e613 --- /dev/null +++ b/src/extractfuncs/ChangeLog @@ -0,0 +1,17 @@ +2001-12-07 Timothee Besset + +Imported from the Wolf MP version, Mac/Linux friendly version +Fixed argbase bug in *nix main +Escape BoxOnPlaneSide on linux (taken out by preprocessing) + +2001-11-02 Timothee Besset + +Modified extractfuncs to works on linux +Would still need to integrate it correctly with the build system +Changed the command line syntax of the linux ver: +screwup [-o ] [ ..] + +if none specified, is "g_funcs" + +on linux at least, those header files need to be tweaked by hand +because unresolved externs are not ignored by gcc (otherwise harmless on win32) diff --git a/src/extractfuncs/Conscript b/src/extractfuncs/Conscript new file mode 100644 index 0000000..f8be465 --- /dev/null +++ b/src/extractfuncs/Conscript @@ -0,0 +1,9 @@ +# extractfuncs building + +$env = new cons( + CC => 'gcc', + CFLAGS => '-g -DSCREWUP ' + ); + +Program $env 'extractfuncs', 'extractfuncs.c', 'l_log.c', 'l_memory.c', 'l_precomp.c', 'l_script.c'; +Install $env '#', 'extractfuncs'; diff --git a/src/extractfuncs/Construct b/src/extractfuncs/Construct new file mode 100644 index 0000000..121bc1b --- /dev/null +++ b/src/extractfuncs/Construct @@ -0,0 +1,9 @@ +# extractfunc top-level Construct +# +# Sep. 2001 TTimo +# + +Default '.'; +Link 'out' => '../..'; +Build 'out/src/extractfuncs/Conscript'; + diff --git a/src/extractfuncs/extractfuncs.bat b/src/extractfuncs/extractfuncs.bat new file mode 100644 index 0000000..57d45ba --- /dev/null +++ b/src/extractfuncs/extractfuncs.bat @@ -0,0 +1,3 @@ +cd ..\game +..\extractfuncs\extractfuncs *.c +cd .. \ No newline at end of file diff --git a/src/extractfuncs/extractfuncs.c b/src/extractfuncs/extractfuncs.c new file mode 100644 index 0000000..4f73836 --- /dev/null +++ b/src/extractfuncs/extractfuncs.c @@ -0,0 +1,653 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#endif +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" + +typedef enum {false, true} qboolean; + +//#define PATHSEPERATOR_STR "\\" + +void Error( char *error, ... ) { + va_list argptr; + + va_start( argptr, error ); + vprintf( error, argptr ); + va_end( argptr ); + + exit( 1 ); +} + +/* +int FileLength (FILE *f) +{ + int pos; + int end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} //end of the function FileLength + +void Remove(char *buf, int length, char *from, char *to, char *skip) +{ + int i, remove = false; + + for (i = 0; i < length; i++) + { + if (remove) + { + if ((unsigned) length - i > strlen(skip)) + { + if (!strncmp(&buf[i], skip, strlen(skip))) + { + i += strlen(skip); + } //end if + } //end if + if ((unsigned) length - i > strlen(to)) + { + if (!strncmp(&buf[i], to, strlen(to))) + { + length = i + strlen(to); + } //end if + } //end if + if (buf[i]) buf[i] = 'a'; + } //end if + else + { + if ((unsigned) length - i < strlen(from)) return; + if (!strncmp(&buf[i], from, strlen(from))) remove = true; + } //end else + } //end for +} //end of the function Remove + +void main(int argc, char *argv[]) +{ + FILE *fp; + int filelength; + char *from, *to, *skip, *ptr; + + if (argc < 2) Error("USAGE: screwup "); + fp = fopen(argv[1], "rb"); + if (!fp) Error("error opening %s\n", argv[1]); + + filelength = FileLength(fp); + ptr = malloc(filelength); + fread(ptr, filelength, 1, fp); + fclose(fp); + + from = argv[3];//"be_aas_bspq2.c"; + to = argv[4];//"BotWeaponNameFromModel"; + skip = "GetBotAPI"; + + Remove(ptr, filelength, from, to, skip); + + fp = fopen(argv[2], "wb"); + if (!fp) Error("error opening %s\n", argv[2]); + fwrite(ptr, filelength, 1, fp); + fclose(fp); + + free(ptr); +} //end of the function main +*/ + +typedef struct replacefunc_s +{ + char *name; + char *newname; + char *filename; + char dec[MAX_TOKEN]; //function declaration + struct replacefunc_s *next; +} replacefunc_t; + +replacefunc_t *replacefuncs; +int numfuncs; + +extern int Q_stricmp( const char *s1, const char *s2 ); + +// the function names +//#define DEFAULT_FUNCBASE "g_func" +static char *func_filename = "g_funcs.h"; +static char *func_filedesc = "g_func_decs.h"; + +void DumpReplaceFunctions( void ) { + replacefunc_t *rf; + char path[_MAX_PATH]; + FILE *f; + int len, newlen; + unsigned char *buf, *newbuf; + int updated; + + updated = 0; + + // dump the function header + strcpy( path, "." ); + strcat( path, PATHSEPERATOR_STR ); + strcat( path, "g_funcs.tmp" ); + Log_Open( path ); + for ( rf = replacefuncs; rf; rf = rf->next ) + { + Log_Print( "{\"%s\", (byte *)%s},\n", rf->name, rf->name ); + } //end for + Log_Print( "{0, 0}\n" ); + Log_Close(); + + // if it's different, rename the file over the real header + strcpy( path, "g_funcs.tmp" ); + f = fopen( path, "rb" ); + fseek( f, 0, SEEK_END ); + len = ftell( f ); + buf = (unsigned char *) malloc( len + 1 ); + fseek( f, 0, SEEK_SET ); + fread( buf, len, 1, f ); + buf[len] = 0; + fclose( f ); + + strcpy( path, func_filename ); + if ( f = fopen( path, "rb" ) ) { + fseek( f, 0, SEEK_END ); + newlen = ftell( f ); + newbuf = (unsigned char *) malloc( newlen + 1 ); + fseek( f, 0, SEEK_SET ); + fread( newbuf, newlen, 1, f ); + newbuf[newlen] = 0; + fclose( f ); + + if ( len != newlen || Q_stricmp( buf, newbuf ) ) { + char newpath[_MAX_PATH]; + + // delete the old file, rename the new one + strcpy( path, func_filename ); + remove( path ); + + strcpy( newpath, "g_funcs.tmp" ); + rename( newpath, path ); + + // make g_save recompile itself + remove( "debug\\g_save.obj" ); + remove( "debug\\g_save.sbr" ); + remove( "release\\g_save.obj" ); + remove( "release\\g_save.sbr" ); + + updated = 1; + } else { + // delete the old file + strcpy( path, "g_funcs.tmp" ); + remove( path ); + } + } else { + rename( "g_funcs.tmp", func_filename ); + } + + free( buf ); + free( newbuf ); + + // dump the function declarations + strcpy( path, "g_func_decs.tmp" ); + Log_Open( path ); + for ( rf = replacefuncs; rf; rf = rf->next ) + { + Log_Print( "extern %s;\n", rf->dec ); + } //end for + Log_Close(); + + // if it's different, rename the file over the real header + strcpy( path, "g_func_decs.tmp" ); + f = fopen( path, "rb" ); + fseek( f, 0, SEEK_END ); + len = ftell( f ); + buf = (unsigned char *) malloc( len + 1 ); + fseek( f, 0, SEEK_SET ); + fread( buf, len, 1, f ); + buf[len] = 0; + fclose( f ); + + strcpy( path, func_filedesc ); + if ( f = fopen( path, "rb" ) ) { + fseek( f, 0, SEEK_END ); + newlen = ftell( f ); + newbuf = (unsigned char *) malloc( newlen + 1 ); + fseek( f, 0, SEEK_SET ); + fread( newbuf, newlen, 1, f ); + newbuf[newlen] = 0; + fclose( f ); + + if ( len != newlen || Q_stricmp( buf, newbuf ) ) { + char newpath[_MAX_PATH]; + + // delete the old file, rename the new one + strcpy( path, func_filedesc ); + remove( path ); + + strcpy( newpath, "g_func_decs.tmp" ); + rename( newpath, path ); + + // make g_save recompile itself + // NOTE TTimo win32 only? (harmless on *nix anyway) + remove( "debug\\g_save.obj" ); + remove( "debug\\g_save.sbr" ); + remove( "release\\g_save.obj" ); + remove( "release\\g_save.sbr" ); + + updated = 1; + } else { + // delete the old file + strcpy( path, "g_func_decs.tmp" ); + remove( path ); + } + } else { + rename( "g_func_decs.tmp", func_filedesc ); + } + + free( buf ); + free( newbuf ); + + if ( updated ) { + printf( "Updated the function table, recompile required.\n" ); + } + +} //end of the function DumpReplaceFunctions + +replacefunc_t *FindFunctionName( char *funcname ) { + replacefunc_t *f; + + for ( f = replacefuncs; f; f = f->next ) + { + if ( !strcmp( f->name, funcname ) ) { + return f; + } + } //end for + return NULL; +} //end of the function FindFunctionName + +int MayScrewUp( char *funcname ) { + if ( !strcmp( funcname, "GetBotAPI" ) ) { + return false; + } + if ( !strcmp( funcname, "main" ) ) { + return false; + } + if ( !strcmp( funcname, "WinMain" ) ) { + return false; + } + return true; +} //end of the function MayScrewUp + +typedef struct tokenList_s { + token_t token; + struct tokenList_s *next; +} tokenList_t; + +#define MAX_TOKEN_LIST 64 +tokenList_t tokenList[MAX_TOKEN_LIST]; +int tokenListHead = 0; + +void ConcatDec( tokenList_t *list, char *str, int inc ) { +/* + if (!((list->token.type == TT_NAME) || (list->token.string[0] == '*'))) { + if (list->token.string[0] == ')' || list->token.string[0] == '(') { + if (inc++ >= 2) + return; + } else { + return; + } + } +*/ + if ( list->next ) { + ConcatDec( list->next, str, inc ); + } + strcat( str, list->token.string ); + strcat( str, " " ); +} + +void AddFunctionName( char *funcname, char *filename, tokenList_t *head ) { + replacefunc_t *f; + tokenList_t *list; + + if ( FindFunctionName( funcname ) ) { + return; + } + +#if defined( __linux__ ) + // the bad thing is, this doesn't preprocess .. on __linux__ this + // function is not implemented (q_math.c) + if ( !Q_stricmp( funcname, "BoxOnPlaneSide" ) ) { + return; + } +#endif + + // NERVE - SMF - workaround for Graeme's predifined MACOSX functions + // TTimo - looks like linux version needs to escape those too +#if defined( _WIN32 ) || defined( __linux__ ) + if ( !Q_stricmp( funcname, "qmax" ) ) { + return; + } else if ( !Q_stricmp( funcname, "qmin" ) ) { + return; + } +#endif + // -NERVE - SMF + + f = (replacefunc_t *) GetMemory( sizeof( replacefunc_t ) + strlen( funcname ) + 1 + 6 + strlen( filename ) + 1 ); + f->name = (char *) f + sizeof( replacefunc_t ); + strcpy( f->name, funcname ); + f->newname = (char *) f + sizeof( replacefunc_t ) + strlen( funcname ) + 1; + sprintf( f->newname, "F%d", numfuncs++ ); + f->filename = (char *) f + sizeof( replacefunc_t ) + strlen( funcname ) + 1 + strlen( f->newname ) + 1; + strcpy( f->filename, filename ); + f->next = replacefuncs; + replacefuncs = f; + + // construct the declaration + list = head; + f->dec[0] = '\0'; + ConcatDec( list, f->dec, 0 ); + +} //end of the function AddFunctionName + +void AddTokenToList( tokenList_t **head, token_t *token ) { + tokenList_t *newhead; + + newhead = &tokenList[tokenListHead++]; //GetMemory( sizeof( tokenList_t ) ); + if ( tokenListHead == MAX_TOKEN_LIST ) { + tokenListHead = 0; + } + + newhead->next = *head; + newhead->token = *token; + + *head = newhead; +} +/* +void KillTokenList( tokenList_t *head ) +{ + if (head->next) { + KillTokenList( head->next ); + FreeMemory( head->next ); + head->next = NULL; + } +} +*/ +void StripTokenList( tokenList_t *head ) { + tokenList_t *trav, *lastTrav; + + trav = head; + + // now go back to the start of the declaration + lastTrav = trav; + trav = trav->next; // should be on the function name now + while ( ( trav->token.type == TT_NAME ) || ( trav->token.string[0] == '*' ) ) { + lastTrav = trav; + trav = trav->next; + if ( !trav ) { + return; + } + } + // now kill everything after lastTrav +// KillTokenList( lastTrav ); + lastTrav->next = NULL; +} + +void GetFunctionNamesFromFile( char *filename ) { + source_t *source; + token_t token, lasttoken; + int indent = 0, brace; + int isStatic = 0; + tokenList_t *listHead; + + // filter some files out + if ( !Q_stricmp( filename, "bg_lib.c" ) ) { + return; + } + + listHead = NULL; + source = LoadSourceFile( filename ); + if ( !source ) { + Error( "error opening %s", filename ); + return; + } //end if +// printf("loaded %s\n", filename); +// if (!PC_ReadToken(source, &lasttoken)) +// { +// FreeSource(source); +// return; +// } //end if + while ( 1 ) + { + if ( !PC_ReadToken( source, &token ) ) { + break; + } + AddTokenToList( &listHead, &token ); + if ( token.type == TT_PUNCTUATION ) { + switch ( token.string[0] ) + { + case ';': + { + isStatic = 0; + break; + } + case '{': + { + indent++; + break; + } //end case + case '}': + { + indent--; + if ( indent < 0 ) { + indent = 0; + } + break; + } //end case + case '(': + { + if ( indent <= 0 && lasttoken.type == TT_NAME ) { + StripTokenList( listHead ); + + brace = 1; + while ( PC_ReadToken( source, &token ) ) + { + AddTokenToList( &listHead, &token ); + if ( token.string[0] == '(' ) { + brace++; + } //end if + else if ( token.string[0] == ')' ) { + brace--; + if ( brace <= 0 ) { + if ( !PC_ReadToken( source, &token ) ) { + break; + } + if ( token.string[0] == '{' ) { + indent++; + if ( !isStatic && MayScrewUp( lasttoken.string ) ) { + AddFunctionName( lasttoken.string, filename, listHead ); + } //end if + } //end if + break; + } //end if + } //end if + } //end while + } //end if + break; + } //end case + } //end if + } //end switch + if ( token.type == TT_NAME ) { + if ( token.string[0] == 's' && !strcmp( token.string, "static" ) ) { + isStatic = 1; + } + } + memcpy( &lasttoken, &token, sizeof( token_t ) ); + } //end while + FreeSource( source ); +} //end of the function GetFunctionNamesFromFile + +void WriteWhiteSpace( FILE *fp, script_t *script ) { + int c; + //write out the white space + c = PS_NextWhiteSpaceChar( script ); + while ( c ) + { + //NOTE: do NOT write out carriage returns (for unix/linux compatibility + if ( c != 13 ) { + fputc( c, fp ); + } + c = PS_NextWhiteSpaceChar( script ); + } //end while +} //end of the function WriteWhiteSpace + +void WriteString( FILE *fp, script_t *script ) { + char *ptr; + + ptr = script->endwhitespace_p; + while ( ptr < script->script_p ) + { + fputc( *ptr, fp ); + ptr++; + } //end while +} //end of the function WriteString + +void ScrewUpFile( char *oldfile, char *newfile ) { + FILE *fp; + script_t *script; + token_t token; + replacefunc_t *f; + char *ptr; + + printf( "screwing up file %s\n", oldfile ); + script = LoadScriptFile( oldfile ); + if ( !script ) { + Error( "error opening %s\n", oldfile ); + } + fp = fopen( newfile, "wb" ); + if ( !fp ) { + Error( "error opening %s\n", newfile ); + } + // + while ( PS_ReadToken( script, &token ) ) + { + WriteWhiteSpace( fp, script ); + if ( token.type == TT_NAME ) { + f = FindFunctionName( token.string ); + if ( f ) { + ptr = f->newname; + } else { ptr = token.string;} + while ( *ptr ) + { + fputc( *ptr, fp ); + ptr++; + } //end while + } //end if + else + { + WriteString( fp, script ); + } //end else + } //end while + WriteWhiteSpace( fp, script ); + FreeMemory( script ); + fclose( fp ); +} //end of the function ScrewUpFile + +int verbose = 0; + +#ifdef _WIN32 + +void main( int argc, char *argv[] ) { + WIN32_FIND_DATA filedata; + HWND handle; + int done; //, i; + + if ( argc < 2 ) { + Error( "USAGE: screwup \n" ); + } //end if + + handle = FindFirstFile( argv[1], &filedata ); + done = ( handle == INVALID_HANDLE_VALUE ); + while ( !done ) + { + if ( !( filedata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) { + // + GetFunctionNamesFromFile( filedata.cFileName ); + } //end if + //find the next file + done = !FindNextFile( handle, &filedata ); + } //end while + DumpReplaceFunctions(); +} //end of the function main + +#else + +void Usage() { + Error( "USAGE: screwup [-o ] [ ..]\n" + "no -o defaults to g_funcs.h g_func_decs.h\n" ); +} + +/* +*nix version, let the shell do the pattern matching +(that's what shells are for :-)) +*/ +int main( int argc, char *argv[] ) { + int i; + int argbase = 1; + + if ( argc < 2 ) { + Usage(); + } //end if + + if ( !Q_stricmp( argv[1],"-o" ) ) { + if ( argc < 5 ) { + Usage(); + } + func_filename = argv[2]; + func_filedesc = argv[3]; + argbase = 4; + } + + for ( i = argbase; i < argc; i++ ) + { + printf( "%d: %s\n", i, argv[i] ); + GetFunctionNamesFromFile( argv[i] ); + } + DumpReplaceFunctions(); +} + +#endif diff --git a/src/extractfuncs/extractfuncs.vcproj b/src/extractfuncs/extractfuncs.vcproj new file mode 100644 index 0000000..7d4fa2a --- /dev/null +++ b/src/extractfuncs/extractfuncs.vcproj @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/extractfuncs/l_log.c b/src/extractfuncs/l_log.c new file mode 100644 index 0000000..c4a2426 --- /dev/null +++ b/src/extractfuncs/l_log.c @@ -0,0 +1,199 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_log.c +// Function: log file stuff +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-31 +// Tab Size: 3 +//=========================================================================== + +#include +#include +#include + +#define MAX_QPATH 64 +#include "../bspc/qbsp.h" + +#define MAX_LOGFILENAMESIZE 1024 + +typedef struct logfile_s +{ + char filename[MAX_LOGFILENAMESIZE]; + FILE *fp; + int numwrites; +} logfile_t; + +logfile_t logfile; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Open( char *filename ) { + if ( !filename || !strlen( filename ) ) { + printf( "openlog \n" ); + return; + } //end if + if ( logfile.fp ) { + printf( "log file %s is already opened\n", logfile.filename ); + return; + } //end if + logfile.fp = fopen( filename, "wb" ); + if ( !logfile.fp ) { + printf( "can't open the log file %s\n", filename ); + return; + } //end if + strncpy( logfile.filename, filename, MAX_LOGFILENAMESIZE ); +// printf("Opened log %s\n", logfile.filename); +} //end of the function Log_Create +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Close( void ) { + if ( !logfile.fp ) { + printf( "no log file to close\n" ); + return; + } //end if + if ( fclose( logfile.fp ) ) { + printf( "can't close log file %s\n", logfile.filename ); + return; + } //end if + logfile.fp = NULL; +// printf("Closed log %s\n", logfile.filename); +} //end of the function Log_Close +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Shutdown( void ) { + if ( logfile.fp ) { + Log_Close(); + } +} //end of the function Log_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Print( char *fmt, ... ) { + va_list ap; +#ifdef WINBSPC + char buf[2048]; +#endif //WINBSPC + + if ( verbose ) { + va_start( ap, fmt ); +#ifdef WINBSPC + vsprintf( buf, fmt, ap ); + WinBSPCPrint( buf ); +#else + vprintf( fmt, ap ); +#endif //WINBSPS + va_end( ap ); + } //end if + + va_start( ap, fmt ); + if ( logfile.fp ) { + vfprintf( logfile.fp, fmt, ap ); + fflush( logfile.fp ); + } //end if + va_end( ap ); +} //end of the function Log_Print +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Write( char *fmt, ... ) { + va_list ap; + + if ( !logfile.fp ) { + return; + } + va_start( ap, fmt ); + vfprintf( logfile.fp, fmt, ap ); + va_end( ap ); + fflush( logfile.fp ); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_WriteTimeStamped( char *fmt, ... ) { + va_list ap; + + if ( !logfile.fp ) { + return; + } +/* fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", + logfile.numwrites, + (int) (botlibglobals.time / 60 / 60), + (int) (botlibglobals.time / 60), + (int) (botlibglobals.time), + (int) ((int) (botlibglobals.time * 100)) - + ((int) botlibglobals.time) * 100);*/ + va_start( ap, fmt ); + vfprintf( logfile.fp, fmt, ap ); + va_end( ap ); + logfile.numwrites++; + fflush( logfile.fp ); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +FILE *Log_FileStruct( void ) { + return logfile.fp; +} //end of the function Log_FileStruct +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Flush( void ) { + if ( logfile.fp ) { + fflush( logfile.fp ); + } +} //end of the function Log_Flush diff --git a/src/extractfuncs/l_log.h b/src/extractfuncs/l_log.h new file mode 100644 index 0000000..5754245 --- /dev/null +++ b/src/extractfuncs/l_log.h @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: l_log.h +// Function: log file stuff +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-31 +// Tab Size: 3 +//=========================================================================== + +//open a log file +void Log_Open( char *filename ); +//close the current log file +void Log_Close( void ); +//close log file if present +void Log_Shutdown( void ); +//print on stdout and write to the current opened log file +void Log_Print( char *fmt, ... ); +//write to the current opened log file +void Log_Write( char *fmt, ... ); +//write to the current opened log file with a time stamp +void Log_WriteTimeStamped( char *fmt, ... ); +//returns the log file structure +FILE *Log_FileStruct( void ); +//flush log file +void Log_Flush( void ); + +int Log_Written( void ); + +#ifdef WINBSPC +void WinBSPCPrint( char *str ); +#endif //WINBSPC diff --git a/src/extractfuncs/l_memory.c b/src/extractfuncs/l_memory.c new file mode 100644 index 0000000..748925d --- /dev/null +++ b/src/extractfuncs/l_memory.c @@ -0,0 +1,444 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_memory.c + * + * desc: memory allocation + * + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "l_log.h" +#include "../../src/botlib/be_interface.h" + +//#define MEMDEBUG +//#define MEMORYMANEGER + +#define MEM_ID 0x12345678l +#define HUNK_ID 0x87654321l + +int allocatedmemory; +int totalmemorysize; +int numblocks; + +#ifdef MEMORYMANEGER + +typedef struct memoryblock_s +{ + unsigned long int id; + void *ptr; + int size; +#ifdef MEMDEBUG + char *label; + char *file; + int line; +#endif //MEMDEBUG + struct memoryblock_s *prev, *next; +} memoryblock_t; + +memoryblock_t *memory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LinkMemoryBlock( memoryblock_t *block ) { + block->prev = NULL; + block->next = memory; + if ( memory ) { + memory->prev = block; + } + memory = block; +} //end of the function LinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnlinkMemoryBlock( memoryblock_t *block ) { + if ( block->prev ) { + block->prev->next = block->next; + } else { memory = block->next;} + if ( block->next ) { + block->next->prev = block->prev; + } +} //end of the function UnlinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = malloc( size + sizeof( memoryblock_t ) ); + block = (memoryblock_t *) ptr; + block->id = MEM_ID; + block->ptr = (char *) ptr + sizeof( memoryblock_t ); + block->size = size + sizeof( memoryblock_t ); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock( block ); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof( memoryblock_t ); + numblocks++; + return block->ptr; +} //end of the function GetMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug( size, label, file, line ); +#else +ptr = GetMemory( size ); +#endif //MEMDEBUG +memset( ptr, 0, size ); +return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetHunkMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = malloc( size + sizeof( memoryblock_t ) ); + block = (memoryblock_t *) ptr; + block->id = HUNK_ID; + block->ptr = (char *) ptr + sizeof( memoryblock_t ); + block->size = size + sizeof( memoryblock_t ); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock( block ); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof( memoryblock_t ); + numblocks++; + return block->ptr; +} //end of the function GetHunkMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedHunkMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug( size, label, file, line ); +#else +ptr = GetHunkMemory( size ); +#endif //MEMDEBUG +memset( ptr, 0, size ); +return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +memoryblock_t *BlockFromPointer( void *ptr, char *str ) { + memoryblock_t *block; + + if ( !ptr ) { +#ifdef MEMDEBUG + //char *crash = (char *) NULL; + //crash[0] = 1; + printf( PRT_FATAL, "%s: NULL pointer\n", str ); +#endif MEMDEBUG + return NULL; + } //end if + block = ( memoryblock_t * )( (char *) ptr - sizeof( memoryblock_t ) ); + if ( block->id != MEM_ID && block->id != HUNK_ID ) { + printf( PRT_FATAL, "%s: invalid memory block\n", str ); + return NULL; + } //end if + if ( block->ptr != ptr ) { + printf( PRT_FATAL, "%s: memory block pointer invalid\n", str ); + return NULL; + } //end if + return block; +} //end of the function BlockFromPointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory( void *ptr ) { + memoryblock_t *block; + + block = BlockFromPointer( ptr, "FreeMemory" ); + if ( !block ) { + return; + } + UnlinkMemoryBlock( block ); + allocatedmemory -= block->size; + totalmemorysize -= block->size + sizeof( memoryblock_t ); + numblocks--; + // + if ( block->id == MEM_ID ) { + free( block ); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemoryByteSize( void *ptr ) { + memoryblock_t *block; + + block = BlockFromPointer( ptr, "MemoryByteSize" ); + if ( !block ) { + return 0; + } + return block->size; +} //end of the function MemoryByteSize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize( void ) { + printf( PRT_MESSAGE, "total allocated memory: %d KB\n", allocatedmemory >> 10 ); + printf( PRT_MESSAGE, "total botlib memory: %d KB\n", totalmemorysize >> 10 ); + printf( PRT_MESSAGE, "total memory blocks: %d\n", numblocks ); +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels( void ) { + memoryblock_t *block; + int i; + + PrintUsedMemorySize(); + i = 0; + Log_Write( "\r\n" ); + for ( block = memory; block; block = block->next ) + { +#ifdef MEMDEBUG + if ( block->id == HUNK_ID ) { + Log_Write( "%6d, hunk %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label ); + } //end if + else + { + Log_Write( "%6d, %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label ); + } //end else +#endif //MEMDEBUG + i++; + } //end for +} //end of the function PrintMemoryLabels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DumpMemory( void ) { + memoryblock_t *block; + + for ( block = memory; block; block = memory ) + { + FreeMemory( block->ptr ); + } //end for + totalmemorysize = 0; + allocatedmemory = 0; +} //end of the function DumpMemory + +#else + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = malloc( size + sizeof( unsigned long int ) ); + if ( !ptr ) { + return NULL; + } + memid = (unsigned long int *) ptr; + *memid = MEM_ID; + return (unsigned long int *) ( (char *) ptr + sizeof( unsigned long int ) ); +} //end of the function GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug( size, label, file, line ); +#else +ptr = GetMemory( size ); +#endif //MEMDEBUG +memset( ptr, 0, size ); +return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetHunkMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = malloc( size + sizeof( unsigned long int ) ); + if ( !ptr ) { + return NULL; + } + memid = (unsigned long int *) ptr; + *memid = HUNK_ID; + return (unsigned long int *) ( (char *) ptr + sizeof( unsigned long int ) ); +} //end of the function GetHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug( unsigned long size, char *label, char *file, int line ) +#else +void *GetClearedHunkMemory( unsigned long size ) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug( size, label, file, line ); +#else +ptr = GetHunkMemory( size ); +#endif //MEMDEBUG +memset( ptr, 0, size ); +return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory( void *ptr ) { + unsigned long int *memid; + + memid = (unsigned long int *) ( (char *) ptr - sizeof( unsigned long int ) ); + + if ( *memid == MEM_ID ) { + free( memid ); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize( void ) { +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels( void ) { +} //end of the function PrintMemoryLabels + +#endif diff --git a/src/extractfuncs/l_memory.h b/src/extractfuncs/l_memory.h new file mode 100644 index 0000000..6c99a52 --- /dev/null +++ b/src/extractfuncs/l_memory.h @@ -0,0 +1,80 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_memory.h + * + * desc: memory management + * + * + *****************************************************************************/ + +//#define MEMDEBUG + +#ifdef MEMDEBUG +#define GetMemory( size ) GetMemoryDebug( size, # size, __FILE__, __LINE__ ); +#define GetClearedMemory( size ) GetClearedMemoryDebug( size, # size, __FILE__, __LINE__ ); +//allocate a memory block of the given size +void *GetMemoryDebug( unsigned long size, char *label, char *file, int line ); +//allocate a memory block of the given size and clear it +void *GetClearedMemoryDebug( unsigned long size, char *label, char *file, int line ); +// +#define GetHunkMemory( size ) GetHunkMemoryDebug( size, # size, __FILE__, __LINE__ ); +#define GetClearedHunkMemory( size ) GetClearedHunkMemoryDebug( size, # size, __FILE__, __LINE__ ); +//allocate a memory block of the given size +void *GetHunkMemoryDebug( unsigned long size, char *label, char *file, int line ); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemoryDebug( unsigned long size, char *label, char *file, int line ); +#else +//allocate a memory block of the given size +void *GetMemory( unsigned long size ); +//allocate a memory block of the given size and clear it +void *GetClearedMemory( unsigned long size ); +// +#ifdef BSPC +#define GetHunkMemory GetMemory +#define GetClearedHunkMemory GetClearedMemory +#else +//allocate a memory block of the given size +void *GetHunkMemory( unsigned long size ); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemory( unsigned long size ); +#endif +#endif + +//free the given memory block +void FreeMemory( void *ptr ); +//prints the total used memory size +void PrintUsedMemorySize( void ); +//print all memory blocks with label +void PrintMemoryLabels( void ); +//returns the size of the memory block in bytes +int MemoryByteSize( void *ptr ); +//free all allocated memory +void DumpMemory( void ); diff --git a/src/extractfuncs/l_precomp.c b/src/extractfuncs/l_precomp.c new file mode 100644 index 0000000..a209e4f --- /dev/null +++ b/src/extractfuncs/l_precomp.c @@ -0,0 +1,3165 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_precomp.c + * + * desc: pre compiler + * + * + *****************************************************************************/ + +//Notes: fix: PC_StringizeTokens + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" + +typedef enum {qfalse, qtrue} qboolean; + +// Ridah, ripped from q_shared.c +/* +============= +Q_strncpyz + +Safe strncpy that ensures a trailing zero +============= +*/ +extern void Error( char *error, ... ); +void Q_strncpyz( char *dest, const char *src, int destsize ) { + if ( !src ) { + Error( "Q_strncpyz: NULL src" ); + } + if ( destsize < 1 ) { + Error( "Q_strncpyz: destsize < 1" ); + } + + strncpy( dest, src, destsize - 1 ); + dest[destsize - 1] = 0; +} + +int Q_stricmpn( const char *s1, const char *s2, int n ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + if ( c1 != c2 ) { + return c1 < c2 ? -1 : 1; + } + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_strncmp( const char *s1, const char *s2, int n ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + if ( c1 != c2 ) { + return c1 < c2 ? -1 : 1; + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_stricmp( const char *s1, const char *s2 ) { + return Q_stricmpn( s1, s2, 99999 ); +} + + +char *Q_strlwr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = tolower( *s ); + s++; + } + return s1; +} + +char *Q_strupr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = toupper( *s ); + s++; + } + return s1; +} + +#endif //SCREWUP + +#ifdef BOTLIB +#include "../game/q_shared.h" +#include "botlib.h" +#include "be_interface.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" +#endif //BOTLIB + +#ifdef MEQCC +#include "qcc.h" +#include "time.h" //time & ctime +#include "math.h" //fabs +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" + +#define qtrue true +#define qfalse false +#define Q_stricmp stricmp +#endif //BSPC + +#if defined( QUAKE ) && !defined( BSPC ) +#include "l_utils.h" +#endif //QUAKE + +//#define DEBUG_EVAL + +#define MAX_DEFINEPARMS 128 + +#define DEFINEHASHING 1 + +//directive name with parse function +typedef struct directive_s +{ + char *name; + int ( *func )( source_t *source ); +} directive_t; + +#define DEFINEHASHSIZE 1024 + +#define TOKEN_HEAP_SIZE 4096 + +int numtokens; +/* +int tokenheapinitialized; //true when the token heap is initialized +token_t token_heap[TOKEN_HEAP_SIZE]; //heap with tokens +token_t *freetokens; //free tokens from the heap +*/ + +//list with global defines added to every source loaded +define_t *globaldefines; + +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void QDECL SourceError( source_t *source, char *str, ... ) { + char text[1024]; + va_list ap; + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); +#ifdef BOTLIB + botimport.Print( PRT_ERROR, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //BOTLIB +#ifdef MEQCC + printf( "error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //MEQCC +#ifdef BSPC + Log_Print( "error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //BSPC +} //end of the function SourceError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL SourceWarning( source_t *source, char *str, ... ) { + char text[1024]; + va_list ap; + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); +#ifdef BOTLIB + botimport.Print( PRT_WARNING, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //BOTLIB +#ifdef MEQCC + printf( "warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //MEQCC +#ifdef BSPC + Log_Print( "warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text ); +#endif //BSPC +} //end of the function ScriptWarning +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushIndent( source_t *source, int type, int skip ) { + indent_t *indent; + + indent = (indent_t *) GetMemory( sizeof( indent_t ) ); + indent->type = type; + indent->script = source->scriptstack; + indent->skip = ( skip != 0 ); + source->skip += indent->skip; + indent->next = source->indentstack; + source->indentstack = indent; +} //end of the function PC_PushIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PopIndent( source_t *source, int *type, int *skip ) { + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = source->indentstack; + if ( !indent ) { + return; + } + + //must be an indent from the current script + if ( source->indentstack->script != source->scriptstack ) { + return; + } + + *type = indent->type; + *skip = indent->skip; + source->indentstack = source->indentstack->next; + source->skip -= indent->skip; + FreeMemory( indent ); +} //end of the function PC_PopIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushScript( source_t *source, script_t *script ) { + script_t *s; + + for ( s = source->scriptstack; s; s = s->next ) + { + if ( !Q_stricmp( s->filename, script->filename ) ) { + SourceError( source, "%s recursively included", script->filename ); + return; + } //end if + } //end for + //push the script on the script stack + script->next = source->scriptstack; + source->scriptstack = script; +} //end of the function PC_PushScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_InitTokenHeap( void ) { + /* + int i; + + if (tokenheapinitialized) return; + freetokens = NULL; + for (i = 0; i < TOKEN_HEAP_SIZE; i++) + { + token_heap[i].next = freetokens; + freetokens = &token_heap[i]; + } //end for + tokenheapinitialized = qtrue; + */ +} //end of the function PC_InitTokenHeap +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +token_t *PC_CopyToken( token_t *token ) { + token_t *t; + +// t = (token_t *) malloc(sizeof(token_t)); + t = (token_t *) GetMemory( sizeof( token_t ) ); +// t = freetokens; + if ( !t ) { +#ifdef BSPC + Error( "out of token space\n" ); +#else +#ifdef SCREWUP + Error( "out of token space\n" ); +#else + Com_Error( ERR_FATAL, "out of token space\n" ); +#endif +#endif + return NULL; + } //end if +// freetokens = freetokens->next; + memcpy( t, token, sizeof( token_t ) ); + t->next = NULL; + numtokens++; + return t; +} //end of the function PC_CopyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeToken( token_t *token ) { + //free(token); + FreeMemory( token ); +// token->next = freetokens; +// freetokens = token; + numtokens--; +} //end of the function PC_FreeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadSourceToken( source_t *source, token_t *token ) { + token_t *t; + script_t *script; + int type, skip; + + //if there's no token already available + while ( !source->tokens ) + { + //if there's a token to read from the script + if ( PS_ReadToken( source->scriptstack, token ) ) { + return qtrue; + } + //if at the end of the script + if ( EndOfScript( source->scriptstack ) ) { + //remove all indents of the script + while ( source->indentstack && + source->indentstack->script == source->scriptstack ) + { + SourceWarning( source, "missing #endif" ); + PC_PopIndent( source, &type, &skip ); + } //end if + } //end if + //if this was the initial script + if ( !source->scriptstack->next ) { + return qfalse; + } + //remove the script and return to the last one + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript( script ); + } //end while + //copy the already available token + memcpy( token, source->tokens, sizeof( token_t ) ); + //free the read token + t = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken( t ); + return qtrue; +} //end of the function PC_ReadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_UnreadSourceToken( source_t *source, token_t *token ) { + token_t *t; + + t = PC_CopyToken( token ); + t->next = source->tokens; + source->tokens = t; + return qtrue; +} //end of the function PC_UnreadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadDefineParms( source_t *source, define_t *define, token_t **parms, int maxparms ) { + token_t token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "define %s missing parms", define->name ); + return qfalse; + } //end if + // + if ( define->numparms > maxparms ) { + SourceError( source, "define with more than %d parameters", maxparms ); + return qfalse; + } //end if + // + for ( i = 0; i < define->numparms; i++ ) parms[i] = NULL; + //if no leading "(" + if ( strcmp( token.string, "(" ) ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "define %s missing parms", define->name ); + return qfalse; + } //end if + //read the define parameters + for ( done = 0, numparms = 0, indent = 0; !done; ) + { + if ( numparms >= maxparms ) { + SourceError( source, "define %s with too many parms", define->name ); + return qfalse; + } //end if + if ( numparms >= define->numparms ) { + SourceWarning( source, "define %s has too many parms", define->name ); + return qfalse; + } //end if + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while ( !done ) + { + // + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "define %s incomplete", define->name ); + return qfalse; + } //end if + // + if ( !strcmp( token.string, "," ) ) { + if ( indent <= 0 ) { + if ( lastcomma ) { + SourceWarning( source, "too many comma's" ); + } + lastcomma = 1; + break; + } //end if + } //end if + lastcomma = 0; + // + if ( !strcmp( token.string, "(" ) ) { + indent++; + continue; + } //end if + else if ( !strcmp( token.string, ")" ) ) { + if ( --indent <= 0 ) { + if ( !parms[define->numparms - 1] ) { + SourceWarning( source, "too few define parms" ); + } //end if + done = 1; + break; + } //end if + } //end if + // + if ( numparms < define->numparms ) { + // + t = PC_CopyToken( &token ); + t->next = NULL; + if ( last ) { + last->next = t; + } else { parms[numparms] = t;} + last = t; + } //end if + } //end while + numparms++; + } //end for + return qtrue; +} //end of the function PC_ReadDefineParms +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_StringizeTokens( token_t *tokens, token_t *token ) { + token_t *t; + + token->type = TT_STRING; + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->string[0] = '\0'; + strcat( token->string, "\"" ); + for ( t = tokens; t; t = t->next ) + { + strncat( token->string, t->string, MAX_TOKEN - strlen( token->string ) ); + } //end for + strncat( token->string, "\"", MAX_TOKEN - strlen( token->string ) ); + return qtrue; +} //end of the function PC_StringizeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_MergeTokens( token_t *t1, token_t *t2 ) { + //merging of a name with a name or number + if ( t1->type == TT_NAME && ( t2->type == TT_NAME || t2->type == TT_NUMBER ) ) { + strcat( t1->string, t2->string ); + return qtrue; + } //end if + //merging of two strings + if ( t1->type == TT_STRING && t2->type == TT_STRING ) { + //remove trailing double quote + t1->string[strlen( t1->string ) - 1] = '\0'; + //concat without leading double quote + strcat( t1->string, &t2->string[1] ); + return qtrue; + } //end if + //FIXME: merging of two number of the same sub type + return qfalse; +} //end of the function PC_MergeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +/* +void PC_PrintDefine(define_t *define) +{ + printf("define->name = %s\n", define->name); + printf("define->flags = %d\n", define->flags); + printf("define->builtin = %d\n", define->builtin); + printf("define->numparms = %d\n", define->numparms); +// token_t *parms; //define parameters +// token_t *tokens; //macro tokens (possibly containing parm tokens) +// struct define_s *next; //next defined macro in a list +} //end of the function PC_PrintDefine*/ +#if DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PrintDefineHashTable( define_t **definehash ) { + int i; + define_t *d; + + for ( i = 0; i < DEFINEHASHSIZE; i++ ) + { + Log_Write( "%4d:", i ); + for ( d = definehash[i]; d; d = d->hashnext ) + { + Log_Write( " %s", d->name ); + } //end for + Log_Write( "\n" ); + } //end for +} //end of the function PC_PrintDefineHashTable +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; + +int PC_NameHash( char *name ) { + int register hash, i; + + hash = 0; + for ( i = 0; name[i] != '\0'; i++ ) + { + hash += name[i] * ( 119 + i ); + //hash += (name[i] << 7) + i; + //hash += (name[i] << (i&15)); + } //end while + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ) & ( DEFINEHASHSIZE - 1 ); + return hash; +} //end of the function PC_NameHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddDefineToHash( define_t *define, define_t **definehash ) { + int hash; + + hash = PC_NameHash( define->name ); + define->hashnext = definehash[hash]; + definehash[hash] = define; +} //end of the function PC_AddDefineToHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindHashedDefine( define_t **definehash, char *name ) { + define_t *d; + int hash; + + hash = PC_NameHash( name ); + for ( d = definehash[hash]; d; d = d->hashnext ) + { + if ( !strcmp( d->name, name ) ) { + return d; + } + } //end for + return NULL; +} //end of the function PC_FindHashedDefine +#endif //DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindDefine( define_t *defines, char *name ) { + define_t *d; + + for ( d = defines; d; d = d->next ) + { + if ( !strcmp( d->name, name ) ) { + return d; + } + } //end for + return NULL; +} //end of the function PC_FindDefine +//============================================================================ +// +// Parameter: - +// Returns: number of the parm +// if no parm found with the given name -1 is returned +// Changes Globals: - +//============================================================================ +int PC_FindDefineParm( define_t *define, char *name ) { + token_t *p; + int i; + + i = 0; + for ( p = define->parms; p; p = p->next ) + { + if ( !strcmp( p->string, name ) ) { + return i; + } + i++; + } //end for + return -1; +} //end of the function PC_FindDefineParm +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeDefine( define_t *define ) { + token_t *t, *next; + + //free the define parameters + for ( t = define->parms; t; t = next ) + { + next = t->next; + PC_FreeToken( t ); + } //end for + //free the define tokens + for ( t = define->tokens; t; t = next ) + { + next = t->next; + PC_FreeToken( t ); + } //end for + //free the define + FreeMemory( define ); +} //end of the function PC_FreeDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddBuiltinDefines( source_t *source ) { + int i; + define_t *define; + struct builtin + { + char *string; + int builtin; + } builtin[] = { + "__LINE__", BUILTIN_LINE, + "__FILE__", BUILTIN_FILE, + "__DATE__", BUILTIN_DATE, + "__TIME__", BUILTIN_TIME, +// "__STDC__", BUILTIN_STDC, + NULL, 0 + }; + + for ( i = 0; builtin[i].string; i++ ) + { + define = (define_t *) GetMemory( sizeof( define_t ) + strlen( builtin[i].string ) + 1 ); + memset( define, 0, sizeof( define_t ) ); + define->name = (char *) define + sizeof( define_t ); + strcpy( define->name, builtin[i].string ); + define->flags |= DEFINE_FIXED; + define->builtin = builtin[i].builtin; + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash( define, source->definehash ); +#else + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + } //end for +} //end of the function PC_AddBuiltinDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandBuiltinDefine( source_t *source, define_t *define, + token_t **firsttoken, token_t **lasttoken ) { + token_t token; + unsigned long t; // time_t t; //to prevent LCC warning + char *curtime; + + memcpy( &token, &source->token, sizeof( token_t ) ); + switch ( define->builtin ) + { + case BUILTIN_LINE: + { + sprintf( token.string, "%d", source->token.line ); +#ifdef NUMBERVALUE + token.intvalue = source->token.line; + token.floatvalue = source->token.line; +#endif //NUMBERVALUE + token.type = TT_NUMBER; + token.subtype = TT_DECIMAL | TT_INTEGER; + *firsttoken = &token; + *lasttoken = &token; + break; + } //end case + case BUILTIN_FILE: + { + strcpy( token.string, source->scriptstack->filename ); + token.type = TT_NAME; + token.subtype = strlen( token.string ); + *firsttoken = &token; + *lasttoken = &token; + break; + } //end case + case BUILTIN_DATE: + { + t = time( NULL ); + curtime = ctime( &t ); + strcpy( token.string, "\"" ); + strncat( token.string, curtime + 4, 7 ); + strncat( token.string + 7, curtime + 20, 4 ); + strcat( token.string, "\"" ); + free( curtime ); + token.type = TT_NAME; + token.subtype = strlen( token.string ); + *firsttoken = &token; + *lasttoken = &token; + break; + } //end case + case BUILTIN_TIME: + { + t = time( NULL ); + curtime = ctime( &t ); + strcpy( token.string, "\"" ); + strncat( token.string, curtime + 11, 8 ); + strcat( token.string, "\"" ); + free( curtime ); + token.type = TT_NAME; + token.subtype = strlen( token.string ); + *firsttoken = &token; + *lasttoken = &token; + break; + } //end case + case BUILTIN_STDC: + default: + { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } //end case + } //end switch + return qtrue; +} //end of the function PC_ExpandBuiltinDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefine( source_t *source, define_t *define, + token_t **firsttoken, token_t **lasttoken ) { + token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + token_t *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + //if it is a builtin define + if ( define->builtin ) { + return PC_ExpandBuiltinDefine( source, define, firsttoken, lasttoken ); + } //end if + //if the define has parameters + if ( define->numparms ) { + if ( !PC_ReadDefineParms( source, define, parms, MAX_DEFINEPARMS ) ) { + return qfalse; + } +#ifdef DEBUG_EVAL + for ( i = 0; i < define->numparms; i++ ) + { + Log_Write( "define parms %d:", i ); + for ( pt = parms[i]; pt; pt = pt->next ) + { + Log_Write( "%s", pt->string ); + } //end for + } //end for +#endif //DEBUG_EVAL + } //end if + //empty list at first + first = NULL; + last = NULL; + //create a list with tokens of the expanded define + for ( dt = define->tokens; dt; dt = dt->next ) + { + parmnum = -1; + //if the token is a name, it could be a define parameter + if ( dt->type == TT_NAME ) { + parmnum = PC_FindDefineParm( define, dt->string ); + } //end if + //if it is a define parameter + if ( parmnum >= 0 ) { + for ( pt = parms[parmnum]; pt; pt = pt->next ) + { + t = PC_CopyToken( pt ); + //add the token to the list + t->next = NULL; + if ( last ) { + last->next = t; + } else { first = t;} + last = t; + } //end for + } //end if + else + { + //if stringizing operator + if ( dt->string[0] == '#' && dt->string[1] == '\0' ) { + //the stringizing operator must be followed by a define parameter + if ( dt->next ) { + parmnum = PC_FindDefineParm( define, dt->next->string ); + } else { parmnum = -1;} + // + if ( parmnum >= 0 ) { + //step over the stringizing operator + dt = dt->next; + //stringize the define parameter tokens + if ( !PC_StringizeTokens( parms[parmnum], &token ) ) { + SourceError( source, "can't stringize tokens" ); + return qfalse; + } //end if + t = PC_CopyToken( &token ); + } //end if + else + { + SourceWarning( source, "stringizing operator without define parameter" ); + continue; + } //end if + } //end if + else + { + t = PC_CopyToken( dt ); + } //end else + //add the token to the list + t->next = NULL; + if ( last ) { + last->next = t; + } else { first = t;} + last = t; + } //end else + } //end for + //check for the merging operator + for ( t = first; t; ) + { + if ( t->next ) { + //if the merging operator + if ( t->next->string[0] == '#' && t->next->string[1] == '#' ) { + t1 = t; + t2 = t->next->next; + if ( t2 ) { + if ( !PC_MergeTokens( t1, t2 ) ) { + SourceError( source, "can't merge %s with %s", t1->string, t2->string ); + return qfalse; + } //end if + PC_FreeToken( t1->next ); + t1->next = t2->next; + if ( t2 == last ) { + last = t1; + } + PC_FreeToken( t2 ); + continue; + } //end if + } //end if + } //end if + t = t->next; + } //end for + //store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + //free all the parameter tokens + for ( i = 0; i < define->numparms; i++ ) + { + for ( pt = parms[i]; pt; pt = nextpt ) + { + nextpt = pt->next; + PC_FreeToken( pt ); + } //end for + } //end for + // + return qtrue; +} //end of the function PC_ExpandDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefineIntoSource( source_t *source, define_t *define ) { + token_t *firsttoken, *lasttoken; + + if ( !PC_ExpandDefine( source, define, &firsttoken, &lasttoken ) ) { + return qfalse; + } + + if ( firsttoken && lasttoken ) { + lasttoken->next = source->tokens; + source->tokens = firsttoken; + return qtrue; + } //end if + return qfalse; +} //end of the function PC_ExpandDefineIntoSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ConvertPath( char *path ) { + char *ptr; + + //remove double path seperators + for ( ptr = path; *ptr; ) + { + if ( ( *ptr == '\\' || *ptr == '/' ) && + ( *( ptr + 1 ) == '\\' || *( ptr + 1 ) == '/' ) ) { + strcpy( ptr, ptr + 1 ); + } //end if + else + { + ptr++; + } //end else + } //end while + //set OS dependent path seperators + for ( ptr = path; *ptr; ) + { + if ( *ptr == '/' || *ptr == '\\' ) { + *ptr = PATHSEPERATOR_CHAR; + } + ptr++; + } //end while +} //end of the function PC_ConvertPath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_include( source_t *source ) { + script_t *script; + token_t token; + char path[_MAX_PATH]; +#ifdef QUAKE + foundfile_t file; +#endif //QUAKE + + if ( source->skip > 0 ) { + return qtrue; + } + // + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "#include without file name" ); + return qfalse; + } //end if + if ( token.linescrossed > 0 ) { + SourceError( source, "#include without file name" ); + return qfalse; + } //end if + if ( token.type == TT_STRING ) { + StripDoubleQuotes( token.string ); + PC_ConvertPath( token.string ); + script = LoadScriptFile( token.string ); + if ( !script ) { + strcpy( path, source->includepath ); + strcat( path, token.string ); + script = LoadScriptFile( path ); + } //end if + } //end if + else if ( token.type == TT_PUNCTUATION && *token.string == '<' ) { + strcpy( path, source->includepath ); + while ( PC_ReadSourceToken( source, &token ) ) + { + if ( token.linescrossed > 0 ) { + PC_UnreadSourceToken( source, &token ); + break; + } //end if + if ( token.type == TT_PUNCTUATION && *token.string == '>' ) { + break; + } + strncat( path, token.string, _MAX_PATH ); + } //end while + if ( *token.string != '>' ) { + SourceWarning( source, "#include missing trailing >" ); + } //end if + if ( !strlen( path ) ) { + SourceError( source, "#include without file name between < >" ); + return qfalse; + } //end if + PC_ConvertPath( path ); + script = LoadScriptFile( path ); + } //end if + else + { + SourceError( source, "#include without file name" ); + return qfalse; + } //end else +#ifdef QUAKE + if ( !script ) { + memset( &file, 0, sizeof( foundfile_t ) ); + script = LoadScriptFile( path ); + if ( script ) { + strncpy( script->filename, path, _MAX_PATH ); + } + } //end if +#endif //QUAKE + if ( !script ) { +#ifdef SCREWUP + SourceWarning( source, "file %s not found", path ); + return qtrue; +#else + SourceError( source, "file %s not found", path ); + return qfalse; +#endif //SCREWUP + } //end if + PC_PushScript( source, script ); + return qtrue; +} //end of the function PC_Directive_include +//============================================================================ +// reads a token from the current line, continues reading on the next +// line only if a backslash '\' is encountered. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadLine( source_t *source, token_t *token ) { + int crossline; + + crossline = 0; + do + { + if ( !PC_ReadSourceToken( source, token ) ) { + return qfalse; + } + + if ( token->linescrossed > crossline ) { + PC_UnreadSourceToken( source, token ); + return qfalse; + } //end if + crossline = 1; + } while ( !strcmp( token->string, "\\" ) ); + return qtrue; +} //end of the function PC_ReadLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_WhiteSpaceBeforeToken( token_t *token ) { + return token->endwhitespace_p - token->whitespace_p > 0; +} //end of the function PC_WhiteSpaceBeforeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ClearTokenWhiteSpace( token_t *token ) { + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->linescrossed = 0; +} //end of the function PC_ClearTokenWhiteSpace +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_undef( source_t *source ) { + token_t token; + define_t *define, *lastdefine; + int hash; + + if ( source->skip > 0 ) { + return qtrue; + } + // + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "undef without name" ); + return qfalse; + } //end if + if ( token.type != TT_NAME ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "expected name, found %s", token.string ); + return qfalse; + } //end if +#if DEFINEHASHING + + hash = PC_NameHash( token.string ); + for ( lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext ) + { + if ( !strcmp( define->name, token.string ) ) { + if ( define->flags & DEFINE_FIXED ) { + SourceWarning( source, "can't undef %s", token.string ); + } //end if + else + { + if ( lastdefine ) { + lastdefine->hashnext = define->hashnext; + } else { source->definehash[hash] = define->hashnext;} + PC_FreeDefine( define ); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#else //DEFINEHASHING + for ( lastdefine = NULL, define = source->defines; define; define = define->next ) + { + if ( !strcmp( define->name, token.string ) ) { + if ( define->flags & DEFINE_FIXED ) { + SourceWarning( source, "can't undef %s", token.string ); + } //end if + else + { + if ( lastdefine ) { + lastdefine->next = define->next; + } else { source->defines = define->next;} + PC_FreeDefine( define ); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_Directive_undef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_define( source_t *source ) { + token_t token, *t, *last; + define_t *define; + + if ( source->skip > 0 ) { + return qtrue; + } + // + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "#define without name" ); + return qfalse; + } //end if + if ( token.type != TT_NAME ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "expected name after #define, found %s", token.string ); + return qfalse; + } //end if + //check if the define already exists +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token.string ); +#else + define = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + if ( define ) { + if ( define->flags & DEFINE_FIXED ) { + SourceError( source, "can't redefine %s", token.string ); + return qfalse; + } //end if + SourceWarning( source, "redefinition of %s", token.string ); + //unread the define name before executing the #undef directive + PC_UnreadSourceToken( source, &token ); + if ( !PC_Directive_undef( source ) ) { + return qfalse; + } + //if the define was not removed (define->flags & DEFINE_FIXED) +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token.string ); +#else + define = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + } //end if + //allocate define + define = (define_t *) GetMemory( sizeof( define_t ) + strlen( token.string ) + 1 ); + memset( define, 0, sizeof( define_t ) ); + define->name = (char *) define + sizeof( define_t ); + strcpy( define->name, token.string ); + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash( define, source->definehash ); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + //if nothing is defined, just return + if ( !PC_ReadLine( source, &token ) ) { + return qtrue; + } + //if it is a define with parameters + if ( !PC_WhiteSpaceBeforeToken( &token ) && !strcmp( token.string, "(" ) ) { + //read the define parameters + last = NULL; + if ( !PC_CheckTokenString( source, ")" ) ) { + while ( 1 ) + { + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "expected define parameter" ); + return qfalse; + } //end if + //if it isn't a name + if ( token.type != TT_NAME ) { + SourceError( source, "invalid define parameter" ); + return qfalse; + } //end if + // + if ( PC_FindDefineParm( define, token.string ) >= 0 ) { + SourceError( source, "two the same define parameters" ); + return qfalse; + } //end if + //add the define parm + t = PC_CopyToken( &token ); + PC_ClearTokenWhiteSpace( t ); + t->next = NULL; + if ( last ) { + last->next = t; + } else { define->parms = t;} + last = t; + define->numparms++; + //read next token + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "define parameters not terminated" ); + return qfalse; + } //end if + // + if ( !strcmp( token.string, ")" ) ) { + break; + } + //then it must be a comma + if ( strcmp( token.string, "," ) ) { + SourceError( source, "define not terminated" ); + return qfalse; + } //end if + } //end while + } //end if + if ( !PC_ReadLine( source, &token ) ) { + return qtrue; + } + } //end if + //read the defined stuff + last = NULL; + do + { + t = PC_CopyToken( &token ); + if ( t->type == TT_NAME && !strcmp( t->string, define->name ) ) { + SourceError( source, "recursive define (removed recursion)" ); + continue; + } //end if + PC_ClearTokenWhiteSpace( t ); + t->next = NULL; + if ( last ) { + last->next = t; + } else { define->tokens = t;} + last = t; + } while ( PC_ReadLine( source, &token ) ); + // + if ( last ) { + //check for merge operators at the beginning or end + if ( !strcmp( define->tokens->string, "##" ) || + !strcmp( last->string, "##" ) ) { + SourceError( source, "define with misplaced ##" ); + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function PC_Directive_define +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_DefineFromString( char *string ) { + script_t *script; + source_t src; + token_t *t; + int res, i; + define_t *def; + + PC_InitTokenHeap(); + + script = LoadScriptMemory( string, strlen( string ), "*extern" ); + //create a new source + memset( &src, 0, sizeof( source_t ) ); + strncpy( src.filename, "*extern", _MAX_PATH ); + src.scriptstack = script; +#if DEFINEHASHING + src.definehash = GetClearedMemory( DEFINEHASHSIZE * sizeof( define_t * ) ); +#endif //DEFINEHASHING + //create a define from the source + res = PC_Directive_define( &src ); + //free any tokens if left + for ( t = src.tokens; t; t = src.tokens ) + { + src.tokens = src.tokens->next; + PC_FreeToken( t ); + } //end for +#ifdef DEFINEHASHING + def = NULL; + for ( i = 0; i < DEFINEHASHSIZE; i++ ) + { + if ( src.definehash[i] ) { + def = src.definehash[i]; + break; + } //end if + } //end for +#else + def = src.defines; +#endif //DEFINEHASHING + // +#if DEFINEHASHING + FreeMemory( src.definehash ); +#endif //DEFINEHASHING + // + FreeScript( script ); + //if the define was created succesfully + if ( res > 0 ) { + return def; + } + //free the define if created + if ( src.defines ) { + PC_FreeDefine( def ); + } + // + return NULL; +} //end of the function PC_DefineFromString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddDefine( source_t *source, char *string ) { + define_t *define; + + define = PC_DefineFromString( string ); + if ( !define ) { + return qfalse; + } +#if DEFINEHASHING + PC_AddDefineToHash( define, source->definehash ); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_AddDefine +//============================================================================ +// add a globals define that will be added to all opened sources +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddGlobalDefine( char *string ) { + define_t *define; + + define = PC_DefineFromString( string ); + if ( !define ) { + return qfalse; + } + define->next = globaldefines; + globaldefines = define; + return qtrue; +} //end of the function PC_AddGlobalDefine +//============================================================================ +// remove the given global define +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_RemoveGlobalDefine( char *name ) { + define_t *define; + + define = PC_FindDefine( globaldefines, name ); + if ( define ) { + PC_FreeDefine( define ); + return qtrue; + } //end if + return qfalse; +} //end of the function PC_RemoveGlobalDefine +//============================================================================ +// remove all globals defines +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_RemoveAllGlobalDefines( void ) { + define_t *define; + + for ( define = globaldefines; define; define = globaldefines ) + { + globaldefines = globaldefines->next; + PC_FreeDefine( define ); + } //end for +} //end of the function PC_RemoveAllGlobalDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_CopyDefine( source_t *source, define_t *define ) { + define_t *newdefine; + token_t *token, *newtoken, *lasttoken; + + newdefine = (define_t *) GetMemory( sizeof( define_t ) + strlen( define->name ) + 1 ); + //copy the define name + newdefine->name = (char *) newdefine + sizeof( define_t ); + strcpy( newdefine->name, define->name ); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for ( lasttoken = NULL, token = define->tokens; token; token = token->next ) + { + newtoken = PC_CopyToken( token ); + newtoken->next = NULL; + if ( lasttoken ) { + lasttoken->next = newtoken; + } else { newdefine->tokens = newtoken;} + lasttoken = newtoken; + } //end for + //copy the define parameters + newdefine->parms = NULL; + for ( lasttoken = NULL, token = define->parms; token; token = token->next ) + { + newtoken = PC_CopyToken( token ); + newtoken->next = NULL; + if ( lasttoken ) { + lasttoken->next = newtoken; + } else { newdefine->parms = newtoken;} + lasttoken = newtoken; + } //end for + return newdefine; +} //end of the function PC_CopyDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddGlobalDefinesToSource( source_t *source ) { + define_t *define, *newdefine; + + for ( define = globaldefines; define; define = define->next ) + { + newdefine = PC_CopyDefine( source, define ); +#if DEFINEHASHING + PC_AddDefineToHash( newdefine, source->definehash ); +#else //DEFINEHASHING + newdefine->next = source->defines; + source->defines = newdefine; +#endif //DEFINEHASHING + } //end for +} //end of the function PC_AddGlobalDefinesToSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if_def( source_t *source, int type ) { + token_t token; + define_t *d; + int skip; + + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "#ifdef without name" ); + return qfalse; + } //end if + if ( token.type != TT_NAME ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "expected name after #ifdef, found %s", token.string ); + return qfalse; + } //end if +#if DEFINEHASHING + d = PC_FindHashedDefine( source->definehash, token.string ); +#else + d = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + skip = ( type == INDENT_IFDEF ) == ( d == NULL ); + PC_PushIndent( source, type, skip ); + return qtrue; +} //end of the function PC_Directiveif_def +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifdef( source_t *source ) { + return PC_Directive_if_def( source, INDENT_IFDEF ); +} //end of the function PC_Directive_ifdef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifndef( source_t *source ) { + return PC_Directive_if_def( source, INDENT_IFNDEF ); +} //end of the function PC_Directive_ifndef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_else( source_t *source ) { + int type, skip; + + PC_PopIndent( source, &type, &skip ); + if ( !type ) { + SourceError( source, "misplaced #else" ); + return qfalse; + } //end if + if ( type == INDENT_ELSE ) { + SourceError( source, "#else after #else" ); + return qfalse; + } //end if + PC_PushIndent( source, INDENT_ELSE, !skip ); + return qtrue; +} //end of the function PC_Directive_else +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_endif( source_t *source ) { + int type, skip; + + PC_PopIndent( source, &type, &skip ); + if ( !type ) { + SourceError( source, "misplaced #endif" ); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_Directive_endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +typedef struct operator_s +{ + int operator; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +int PC_OperatorPriority( int op ) { + switch ( op ) + { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } //end switch + return qfalse; +} //end of the function PC_OperatorPriority + +//#define AllocValue() GetClearedMemory(sizeof(value_t)); +//#define FreeValue(val) FreeMemory(val) +//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); +//#define FreeOperator(op) FreeMemory(op); + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 +#define AllocValue( val ) \ + if ( numvalues >= MAX_VALUES ) { \ + SourceError( source, "out value space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + val = &value_heap[numvalues++];} +#define FreeValue( val ) +// +#define AllocOperator( op ) \ + if ( numoperators >= MAX_OPERATORS ) { \ + SourceError( source, "out operator space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + op = &operator_heap[numoperators++];} +#define FreeOperator( op ) + +int PC_EvaluateTokens( source_t *source, token_t *tokens, signed long int *intvalue, + double *floatvalue, int integer ) { + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + token_t *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = qfalse; + int lastoperatortype = 0; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if ( intvalue ) { + *intvalue = 0; + } + if ( floatvalue ) { + *floatvalue = 0; + } + for ( t = tokens; t; t = t->next ) + { + switch ( t->type ) + { + case TT_NAME: + { + if ( lastwasvalue || negativevalue ) { + SourceError( source, "syntax error in #if/#elif" ); + error = 1; + break; + } //end if + if ( strcmp( t->string, "defined" ) ) { + SourceError( source, "undefined name %s in #if/#elif", t->string ); + error = 1; + break; + } //end if + t = t->next; + if ( !strcmp( t->string, "(" ) ) { + brace = qtrue; + t = t->next; + } //end if + if ( !t || t->type != TT_NAME ) { + SourceError( source, "defined without name in #if/#elif" ); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue( v ); +#if DEFINEHASHING + if ( PC_FindHashedDefine( source->definehash, t->string ) ) +#else + if ( PC_FindDefine( source->defines, t->string ) ) +#endif //DEFINEHASHING + { + v->intvalue = 1; + v->floatvalue = 1; + } //end if + else + { + v->intvalue = 0; + v->floatvalue = 0; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if ( lastvalue ) { + lastvalue->next = v; + } else { firstvalue = v;} + lastvalue = v; + if ( brace ) { + t = t->next; + if ( !t || strcmp( t->string, ")" ) ) { + SourceError( source, "defined without ) in #if/#elif" ); + error = 1; + break; + } //end if + } //end if + brace = qfalse; + // defined() creates a value + lastwasvalue = 1; + break; + } //end case + case TT_NUMBER: + { + if ( lastwasvalue ) { + SourceError( source, "syntax error in #if/#elif" ); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue( v ); + if ( negativevalue ) { + v->intvalue = -(signed int) t->intvalue; + v->floatvalue = -t->floatvalue; + } //end if + else + { + v->intvalue = t->intvalue; + v->floatvalue = t->floatvalue; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if ( lastvalue ) { + lastvalue->next = v; + } else { firstvalue = v;} + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } //end case + case TT_PUNCTUATION: + { + if ( negativevalue ) { + SourceError( source, "misplaced minus sign in #if/#elif" ); + error = 1; + break; + } //end if + if ( t->subtype == P_PARENTHESESOPEN ) { + parentheses++; + break; + } //end if + else if ( t->subtype == P_PARENTHESESCLOSE ) { + parentheses--; + if ( parentheses < 0 ) { + SourceError( source, "too many ) in #if/#elsif" ); + error = 1; + } //end if + break; + } //end else if + //check for invalid operators on floating point values + if ( !integer ) { + if ( t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR ) { + SourceError( source, "illigal operator %s on floating point operands\n", t->string ); + error = 1; + break; + } //end if + } //end if + switch ( t->subtype ) + { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if ( lastwasvalue ) { + SourceError( source, "! or ~ after value in #if/#elif" ); + error = 1; + break; + } //end if + break; + } //end case + case P_SUB: + { + if ( !lastwasvalue ) { + negativevalue = 1; + break; + } //end if + } //end case + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if ( !lastwasvalue ) { + SourceError( source, "operator %s after operator in #if/#elif", t->string ); + error = 1; + break; + } //end if + break; + } //end case + default: + { + SourceError( source, "invalid operator %s in #if/#elif", t->string ); + error = 1; + break; + } //end default + } //end switch + if ( !error && !negativevalue ) { + //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); + AllocOperator( o ); + o->operator = t->subtype; + o->priority = PC_OperatorPriority( t->subtype ); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if ( lastoperator ) { + lastoperator->next = o; + } else { firstoperator = o;} + lastoperator = o; + lastwasvalue = 0; + } //end if + break; + } //end case + default: + { + SourceError( source, "unknown %s in #if/#elif", t->string ); + error = 1; + break; + } //end default + } //end switch + if ( error ) { + break; + } + } //end for + if ( !error ) { + if ( !lastwasvalue ) { + SourceError( source, "trailing operator in #if/#elif" ); + error = 1; + } //end if + else if ( parentheses ) { + SourceError( source, "too many ( in #if/#elif" ); + error = 1; + } //end else if + } //end if + // + gotquestmarkvalue = qfalse; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while ( !error && firstoperator ) + { + v = firstvalue; + for ( o = firstoperator; o->next; o = o->next ) + { + //if the current operator is nested deeper in parentheses + //than the next operator + if ( o->parentheses > o->next->parentheses ) { + break; + } + //if the current and next operator are nested equally deep in parentheses + if ( o->parentheses == o->next->parentheses ) { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if ( o->priority >= o->next->priority ) { + break; + } + } //end if + //if the arity of the operator isn't equal to 1 + if ( o->operator != P_LOGIC_NOT + && o->operator != P_BIN_NOT ) { + v = v->next; + } + //if there's no value or no next value + if ( !v ) { + SourceError( source, "mising values in #if/#elif" ); + error = 1; + break; + } //end if + } //end for + if ( error ) { + break; + } + v1 = v; + v2 = v->next; +#ifdef DEBUG_EVAL + if ( integer ) { + Log_Write( "operator %s, value1 = %d", PunctuationFromNum( source->scriptstack, o->operator ), v1->intvalue ); + if ( v2 ) { + Log_Write( "value2 = %d", v2->intvalue ); + } + } //end if + else + { + Log_Write( "operator %s, value1 = %f", PunctuationFromNum( source->scriptstack, o->operator ), v1->floatvalue ); + if ( v2 ) { + Log_Write( "value2 = %f", v2->floatvalue ); + } + } //end else +#endif //DEBUG_EVAL + switch ( o->operator ) + { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: v1->intvalue %= v2->intvalue; + break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if ( !gotquestmarkvalue ) { + SourceError( source, ": without ? in #if/#elif" ); + error = 1; + break; + } //end if + if ( integer ) { + if ( !questmarkintvalue ) { + v1->intvalue = v2->intvalue; + } + } //end if + else + { + if ( !questmarkfloatvalue ) { + v1->floatvalue = v2->floatvalue; + } + } //end else + gotquestmarkvalue = qfalse; + break; + } //end case + case P_QUESTIONMARK: + { + if ( gotquestmarkvalue ) { + SourceError( source, "? after ? in #if/#elif" ); + error = 1; + break; + } //end if + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = qtrue; + break; + } //end if + } //end switch +#ifdef DEBUG_EVAL + if ( integer ) { + Log_Write( "result value = %d", v1->intvalue ); + } else { Log_Write( "result value = %f", v1->floatvalue );} +#endif //DEBUG_EVAL + if ( error ) { + break; + } + lastoperatortype = o->operator; + //if not an operator with arity 1 + if ( o->operator !=P_LOGIC_NOT + && o->operator !=P_BIN_NOT ) { + //remove the second value if not question mark operator + if ( o->operator != P_QUESTIONMARK ) {v = v->next;} + // + if ( v->prev ) { + v->prev->next = v->next; + } else { firstvalue = v->next;} + if ( v->next ) { + v->next->prev = v->prev; + } else { lastvalue = v->prev;} + //FreeMemory(v); + FreeValue( v ); + } //end if + //remove the operator + if ( o->prev ) { + o->prev->next = o->next; + } else { firstoperator = o->next;} + if ( o->next ) { + o->next->prev = o->prev; + } else { lastoperator = o->prev;} + //FreeMemory(o); + FreeOperator( o ); + } //end while + if ( firstvalue ) { + if ( intvalue ) { + *intvalue = firstvalue->intvalue; + } + if ( floatvalue ) { + *floatvalue = firstvalue->floatvalue; + } + } //end if + for ( o = firstoperator; o; o = lastoperator ) + { + lastoperator = o->next; + //FreeMemory(o); + FreeOperator( o ); + } //end for + for ( v = firstvalue; v; v = lastvalue ) + { + lastvalue = v->next; + //FreeMemory(v); + FreeValue( v ); + } //end for + if ( !error ) { + return qtrue; + } + if ( intvalue ) { + *intvalue = 0; + } + if ( floatvalue ) { + *floatvalue = 0; + } + return qfalse; +} //end of the function PC_EvaluateTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Evaluate( source_t *source, signed long int *intvalue, + double *floatvalue, int integer ) { + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + int defined = qfalse; + + if ( intvalue ) { + *intvalue = 0; + } + if ( floatvalue ) { + *floatvalue = 0; + } + // + if ( !PC_ReadLine( source, &token ) ) { + SourceError( source, "no value after #if/#elif" ); + return qfalse; + } //end if + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if ( token.type == TT_NAME ) { + if ( defined ) { + defined = qfalse; + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end if + else if ( !strcmp( token.string, "defined" ) ) { + defined = qtrue; + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token.string ); +#else + define = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + if ( !define ) { + SourceError( source, "can't evaluate %s, not defined", token.string ); + return qfalse; + } //end if + if ( !PC_ExpandDefineIntoSource( source, define ) ) { + return qfalse; + } + } //end else + } //end if + //if the token is a number or a punctuation + else if ( token.type == TT_NUMBER || token.type == TT_PUNCTUATION ) { + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError( source, "can't evaluate %s", token.string ); + return qfalse; + } //end else + } while ( PC_ReadLine( source, &token ) ); + // + if ( !PC_EvaluateTokens( source, firsttoken, intvalue, floatvalue, integer ) ) { + return qfalse; + } + // +#ifdef DEBUG_EVAL + Log_Write( "eval:" ); +#endif //DEBUG_EVAL + for ( t = firsttoken; t; t = nexttoken ) + { +#ifdef DEBUG_EVAL + Log_Write( " %s", t->string ); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken( t ); + } //end for +#ifdef DEBUG_EVAL + if ( integer ) { + Log_Write( "eval result: %d", *intvalue ); + } else { Log_Write( "eval result: %f", *floatvalue );} +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_Evaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarEvaluate( source_t *source, signed long int *intvalue, + double *floatvalue, int integer ) { + int indent, defined = qfalse; + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + + if ( intvalue ) { + *intvalue = 0; + } + if ( floatvalue ) { + *floatvalue = 0; + } + // + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "no leading ( after $evalint/$evalfloat" ); + return qfalse; + } //end if + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "nothing to evaluate" ); + return qfalse; + } //end if + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if ( token.type == TT_NAME ) { + if ( defined ) { + defined = qfalse; + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end if + else if ( !strcmp( token.string, "defined" ) ) { + defined = qtrue; + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token.string ); +#else + define = PC_FindDefine( source->defines, token.string ); +#endif //DEFINEHASHING + if ( !define ) { + SourceError( source, "can't evaluate %s, not defined", token.string ); + return qfalse; + } //end if + if ( !PC_ExpandDefineIntoSource( source, define ) ) { + return qfalse; + } + } //end else + } //end if + //if the token is a number or a punctuation + else if ( token.type == TT_NUMBER || token.type == TT_PUNCTUATION ) { + if ( *token.string == '(' ) { + indent++; + } else if ( *token.string == ')' ) { + indent--; + } + if ( indent <= 0 ) { + break; + } + t = PC_CopyToken( &token ); + t->next = NULL; + if ( lasttoken ) { + lasttoken->next = t; + } else { firsttoken = t;} + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError( source, "can't evaluate %s", token.string ); + return qfalse; + } //end else + } while ( PC_ReadSourceToken( source, &token ) ); + // + if ( !PC_EvaluateTokens( source, firsttoken, intvalue, floatvalue, integer ) ) { + return qfalse; + } + // +#ifdef DEBUG_EVAL + Log_Write( "$eval:" ); +#endif //DEBUG_EVAL + for ( t = firsttoken; t; t = nexttoken ) + { +#ifdef DEBUG_EVAL + Log_Write( " %s", t->string ); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken( t ); + } //end for +#ifdef DEBUG_EVAL + if ( integer ) { + Log_Write( "$eval result: %d", *intvalue ); + } else { Log_Write( "$eval result: %f", *floatvalue );} +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_DollarEvaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_elif( source_t *source ) { + signed long int value; + int type, skip; + + PC_PopIndent( source, &type, &skip ); + if ( !type || type == INDENT_ELSE ) { + SourceError( source, "misplaced #elif" ); + return qfalse; + } //end if + if ( !PC_Evaluate( source, &value, NULL, qtrue ) ) { + return qfalse; + } + skip = ( value == 0 ); + PC_PushIndent( source, INDENT_ELIF, skip ); + return qtrue; +} //end of the function PC_Directive_elif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if( source_t *source ) { + signed long int value; + int skip; + + if ( !PC_Evaluate( source, &value, NULL, qtrue ) ) { + return qfalse; + } + skip = ( value == 0 ); + PC_PushIndent( source, INDENT_IF, skip ); + return qtrue; +} //end of the function PC_Directive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_line( source_t *source ) { + SourceError( source, "#line directive not supported" ); + return qfalse; +} //end of the function PC_Directive_line +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_error( source_t *source ) { + token_t token; + + strcpy( token.string, "" ); + PC_ReadSourceToken( source, &token ); + SourceError( source, "#error directive: %s", token.string ); + return qfalse; +} //end of the function PC_Directive_error +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_pragma( source_t *source ) { + token_t token; + + SourceWarning( source, "#pragma directive not supported" ); + while ( PC_ReadLine( source, &token ) ) ; + return qtrue; +} //end of the function PC_Directive_pragma +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void UnreadSignToken( source_t *source ) { + token_t token; + + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + strcpy( token.string, "-" ); + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + PC_UnreadSourceToken( source, &token ); +} //end of the function UnreadSignToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_eval( source_t *source ) { + signed long int value; + token_t token; + + if ( !PC_Evaluate( source, &value, NULL, qtrue ) ) { + return qfalse; + } + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf( token.string, "%d", abs( value ) ); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER | TT_LONG | TT_DECIMAL; + PC_UnreadSourceToken( source, &token ); + if ( value < 0 ) { + UnreadSignToken( source ); + } + return qtrue; +} //end of the function PC_Directive_eval +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_evalfloat( source_t *source ) { + double value; + token_t token; + + if ( !PC_Evaluate( source, NULL, &value, qfalse ) ) { + return qfalse; + } + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf( token.string, "%1.2f", fabs( value ) ); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT | TT_LONG | TT_DECIMAL; + PC_UnreadSourceToken( source, &token ); + if ( value < 0 ) { + UnreadSignToken( source ); + } + return qtrue; +} //end of the function PC_Directive_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t directives[20] = +{ + {"if", PC_Directive_if}, + {"ifdef", PC_Directive_ifdef}, + {"ifndef", PC_Directive_ifndef}, + {"elif", PC_Directive_elif}, + {"else", PC_Directive_else}, + {"endif", PC_Directive_endif}, + {"include", PC_Directive_include}, + {"define", PC_Directive_define}, + {"undef", PC_Directive_undef}, + {"line", PC_Directive_line}, + {"error", PC_Directive_error}, + {"pragma", PC_Directive_pragma}, + {"eval", PC_Directive_eval}, + {"evalfloat", PC_Directive_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDirective( source_t *source ) { + token_t token; + int i; + + //read the directive name + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "found # without name" ); + return qfalse; + } //end if + //directive name must be on the same line + if ( token.linescrossed > 0 ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "found # at end of line" ); + return qfalse; + } //end if + //if if is a name + if ( token.type == TT_NAME ) { + //find the precompiler directive + for ( i = 0; directives[i].name; i++ ) + { + if ( !strcmp( directives[i].name, token.string ) ) { + return directives[i].func( source ); + } //end if + } //end for + } //end if + SourceError( source, "unknown precompiler directive %s", token.string ); + return qfalse; +} //end of the function PC_ReadDirective +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalint( source_t *source ) { + signed long int value; + token_t token; + + if ( !PC_DollarEvaluate( source, &value, NULL, qtrue ) ) { + return qfalse; + } + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf( token.string, "%d", abs( value ) ); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER | TT_LONG | TT_DECIMAL; +#ifdef NUMBERVALUE + token.intvalue = value; + token.floatvalue = value; +#endif //NUMBERVALUE + PC_UnreadSourceToken( source, &token ); + if ( value < 0 ) { + UnreadSignToken( source ); + } + return qtrue; +} //end of the function PC_DollarDirective_evalint +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalfloat( source_t *source ) { + double value; + token_t token; + + if ( !PC_DollarEvaluate( source, NULL, &value, qfalse ) ) { + return qfalse; + } + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf( token.string, "%1.2f", fabs( value ) ); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT | TT_LONG | TT_DECIMAL; +#ifdef NUMBERVALUE + token.intvalue = (unsigned long) value; + token.floatvalue = value; +#endif //NUMBERVALUE + PC_UnreadSourceToken( source, &token ); + if ( value < 0 ) { + UnreadSignToken( source ); + } + return qtrue; +} //end of the function PC_DollarDirective_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t dollardirectives[20] = +{ + {"evalint", PC_DollarDirective_evalint}, + {"evalfloat", PC_DollarDirective_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDollarDirective( source_t *source ) { + token_t token; + int i; + + //read the directive name + if ( !PC_ReadSourceToken( source, &token ) ) { + SourceError( source, "found $ without name" ); + return qfalse; + } //end if + //directive name must be on the same line + if ( token.linescrossed > 0 ) { + PC_UnreadSourceToken( source, &token ); + SourceError( source, "found $ at end of line" ); + return qfalse; + } //end if + //if if is a name + if ( token.type == TT_NAME ) { + //find the precompiler directive + for ( i = 0; dollardirectives[i].name; i++ ) + { + if ( !strcmp( dollardirectives[i].name, token.string ) ) { + return dollardirectives[i].func( source ); + } //end if + } //end for + } //end if + PC_UnreadSourceToken( source, &token ); + SourceError( source, "unknown precompiler directive %s", token.string ); + return qfalse; +} //end of the function PC_ReadDirective + +#ifdef QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int BuiltinFunction( source_t *source ) { + token_t token; + + if ( !PC_ReadSourceToken( source, &token ) ) { + return qfalse; + } + if ( token.type == TT_NUMBER ) { + PC_UnreadSourceToken( source, &token ); + return qtrue; + } //end if + else + { + PC_UnreadSourceToken( source, &token ); + return qfalse; + } //end else +} //end of the function BuiltinFunction +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int QuakeCMacro( source_t *source ) { + int i; + token_t token; + + if ( !PC_ReadSourceToken( source, &token ) ) { + return qtrue; + } + if ( token.type != TT_NAME ) { + PC_UnreadSourceToken( source, &token ); + return qtrue; + } //end if + //find the precompiler directive + for ( i = 0; dollardirectives[i].name; i++ ) + { + if ( !strcmp( dollardirectives[i].name, token.string ) ) { + PC_UnreadSourceToken( source, &token ); + return qfalse; + } //end if + } //end for + PC_UnreadSourceToken( source, &token ); + return qtrue; +} //end of the function QuakeCMacro +#endif //QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadToken( source_t *source, token_t *token ) { + define_t *define; + + while ( 1 ) + { + if ( !PC_ReadSourceToken( source, token ) ) { + return qfalse; + } + + //check for precompiler directives + if ( token->type == TT_PUNCTUATION && *token->string == '#' ) { +#ifdef SCREWUP // Ridah, skip all # directives + while ( PC_ReadLine( source, token ) ) ; + continue; +#endif // SCREWUP + +#ifdef QUAKEC + if ( !BuiltinFunction( source ) ) +#endif //QUAKC + { + //read the precompiler directive + if ( !PC_ReadDirective( source ) ) { + return qfalse; + } + continue; + } //end if + } //end if + if ( token->type == TT_PUNCTUATION && *token->string == '$' ) { +#ifdef QUAKEC + if ( !QuakeCMacro( source ) ) +#endif //QUAKEC + { + //read the precompiler directive + if ( !PC_ReadDollarDirective( source ) ) { + return qfalse; + } + continue; + } //end if + } //end if + //if skipping source because of conditional compilation + if ( source->skip ) { + continue; + } + //if the token is a name + if ( token->type == TT_NAME ) { + //check if the name is a define macro +#if DEFINEHASHING + define = PC_FindHashedDefine( source->definehash, token->string ); +#else + define = PC_FindDefine( source->defines, token->string ); +#endif //DEFINEHASHING + //if it is a define macro + if ( define ) { + //expand the defined macro + if ( !PC_ExpandDefineIntoSource( source, define ) ) { + return qfalse; + } + continue; + } //end if + } //end if + //copy token for unreading + memcpy( &source->token, token, sizeof( token_t ) ); + //found a token + return qtrue; + } //end while +} //end of the function PC_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenString( source_t *source, char *string ) { + token_t token; + + if ( !PC_ReadToken( source, &token ) ) { + SourceError( source, "couldn't find expected %s", string ); + return qfalse; + } //end if + + if ( strcmp( token.string, string ) ) { + SourceError( source, "expected %s, found %s", string, token.string ); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_ExpectTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenType( source_t *source, int type, int subtype, token_t *token ) { + char str[MAX_TOKEN]; + + if ( !PC_ReadToken( source, token ) ) { + SourceError( source, "couldn't read expected token" ); + return qfalse; + } //end if + + if ( token->type != type ) { + strcpy( str, "" ); + if ( type == TT_STRING ) { + strcpy( str, "string" ); + } + if ( type == TT_LITERAL ) { + strcpy( str, "literal" ); + } + if ( type == TT_NUMBER ) { + strcpy( str, "number" ); + } + if ( type == TT_NAME ) { + strcpy( str, "name" ); + } + if ( type == TT_PUNCTUATION ) { + strcpy( str, "punctuation" ); + } + SourceError( source, "expected a %s, found %s", str, token->string ); + return qfalse; + } //end if + if ( token->type == TT_NUMBER ) { + if ( ( token->subtype & subtype ) != subtype ) { + if ( subtype & TT_DECIMAL ) { + strcpy( str, "decimal" ); + } + if ( subtype & TT_HEX ) { + strcpy( str, "hex" ); + } + if ( subtype & TT_OCTAL ) { + strcpy( str, "octal" ); + } + if ( subtype & TT_BINARY ) { + strcpy( str, "binary" ); + } + if ( subtype & TT_LONG ) { + strcat( str, " long" ); + } + if ( subtype & TT_UNSIGNED ) { + strcat( str, " unsigned" ); + } + if ( subtype & TT_FLOAT ) { + strcat( str, " float" ); + } + if ( subtype & TT_INTEGER ) { + strcat( str, " integer" ); + } + SourceError( source, "expected %s, found %s", str, token->string ); + return qfalse; + } //end if + } //end if + else if ( token->type == TT_PUNCTUATION ) { + if ( token->subtype != subtype ) { + SourceError( source, "found %s", token->string ); + return qfalse; + } //end if + } //end else if + return qtrue; +} //end of the function PC_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectAnyToken( source_t *source, token_t *token ) { + if ( !PC_ReadToken( source, token ) ) { + SourceError( source, "couldn't read expected token" ); + return qfalse; + } //end if + else + { + return qtrue; + } //end else +} //end of the function PC_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenString( source_t *source, char *string ) { + token_t tok; + + if ( !PC_ReadToken( source, &tok ) ) { + return qfalse; + } + //if the token is available + if ( !strcmp( tok.string, string ) ) { + return qtrue; + } + // + PC_UnreadSourceToken( source, &tok ); + return qfalse; +} //end of the function PC_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenType( source_t *source, int type, int subtype, token_t *token ) { + token_t tok; + + if ( !PC_ReadToken( source, &tok ) ) { + return qfalse; + } + //if the type matches + if ( tok.type == type && + ( tok.subtype & subtype ) == subtype ) { + memcpy( token, &tok, sizeof( token_t ) ); + return qtrue; + } //end if + // + PC_UnreadSourceToken( source, &tok ); + return qfalse; +} //end of the function PC_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SkipUntilString( source_t *source, char *string ) { + token_t token; + + while ( PC_ReadToken( source, &token ) ) + { + if ( !strcmp( token.string, string ) ) { + return qtrue; + } + } //end while + return qfalse; +} //end of the function PC_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadLastToken( source_t *source ) { + PC_UnreadSourceToken( source, &source->token ); +} //end of the function PC_UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadToken( source_t *source, token_t *token ) { + PC_UnreadSourceToken( source, token ); +} //end of the function PC_UnreadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetIncludePath( source_t *source, char *path ) { + strncpy( source->includepath, path, _MAX_PATH ); + //add trailing path seperator + if ( source->includepath[strlen( source->includepath ) - 1] != '\\' && + source->includepath[strlen( source->includepath ) - 1] != '/' ) { + strcat( source->includepath, PATHSEPERATOR_STR ); + } //end if +} //end of the function PC_SetIncludePath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetPunctuations( source_t *source, punctuation_t *p ) { + source->punctuations = p; +} //end of the function PC_SetPunctuations +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceFile( char *filename ) { + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + + script = LoadScriptFile( filename ); + if ( !script ) { + return NULL; + } + + script->next = NULL; + + source = (source_t *) GetMemory( sizeof( source_t ) ); + memset( source, 0, sizeof( source_t ) ); + + strncpy( source->filename, filename, _MAX_PATH ); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = GetClearedMemory( DEFINEHASHSIZE * sizeof( define_t * ) ); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource( source ); + return source; +} //end of the function LoadSourceFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceMemory( char *ptr, int length, char *name ) { + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + + script = LoadScriptMemory( ptr, length, name ); + if ( !script ) { + return NULL; + } + script->next = NULL; + + source = (source_t *) GetMemory( sizeof( source_t ) ); + memset( source, 0, sizeof( source_t ) ); + + strncpy( source->filename, name, _MAX_PATH ); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = GetClearedMemory( DEFINEHASHSIZE * sizeof( define_t * ) ); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource( source ); + return source; +} //end of the function LoadSourceMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeSource( source_t *source ) { + script_t *script; + token_t *token; + define_t *define; + indent_t *indent; + int i; + + //PC_PrintDefineHashTable(source->definehash); + //free all the scripts + while ( source->scriptstack ) + { + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript( script ); + } //end for + //free all the tokens + while ( source->tokens ) + { + token = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken( token ); + } //end for +#if DEFINEHASHING + for ( i = 0; i < DEFINEHASHSIZE; i++ ) + { + while ( source->definehash[i] ) + { + define = source->definehash[i]; + source->definehash[i] = source->definehash[i]->hashnext; + PC_FreeDefine( define ); + } //end while + } //end for +#else //DEFINEHASHING + //free all defines + while ( source->defines ) + { + define = source->defines; + source->defines = source->defines->next; + PC_FreeDefine( define ); + } //end for +#endif //DEFINEHASHING + //free all indents + while ( source->indentstack ) + { + indent = source->indentstack; + source->indentstack = source->indentstack->next; + FreeMemory( indent ); + } //end for +#if DEFINEHASHING + // + if ( source->definehash ) { + FreeMemory( source->definehash ); + } +#endif //DEFINEHASHING + //free the source itself + FreeMemory( source ); +} //end of the function FreeSource + diff --git a/src/extractfuncs/l_precomp.h b/src/extractfuncs/l_precomp.h new file mode 100644 index 0000000..054abc3 --- /dev/null +++ b/src/extractfuncs/l_precomp.h @@ -0,0 +1,158 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_precomp.h + * + * desc: pre compiler + * + * + *****************************************************************************/ + +#ifndef _MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +#ifndef PATH_SEPERATORSTR + #if defined( WIN32 ) | defined( _WIN32 ) | defined( __NT__ ) | defined( __WINDOWS__ ) | defined( __WINDOWS_386__ ) + #define PATHSEPERATOR_STR "\\" + #else + #define PATHSEPERATOR_STR "/" + #endif +#endif +#ifndef PATH_SEPERATORCHAR + #if defined( WIN32 ) | defined( _WIN32 ) | defined( __NT__ ) | defined( __WINDOWS__ ) | defined( __WINDOWS_386__ ) + #define PATHSEPERATOR_CHAR '\\' + #else + #define PATHSEPERATOR_CHAR '/' + #endif +#endif + + +#define DEFINE_FIXED 0x0001 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +//macro definitions +typedef struct define_s +{ + char *name; //define name + int flags; //define flags + int builtin; // > 0 if builtin define + int numparms; //number of define parameters + token_t *parms; //define parameters + token_t *tokens; //macro tokens (possibly containing parm tokens) + struct define_s *next; //next defined macro in a list + struct define_s *hashnext; //next define in the hash chain +} define_t; + +//indents +//used for conditional compilation directives: +//#if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s +{ + int type; //indent type + int skip; //true if skipping current indent + script_t *script; //script the indent was in + struct indent_s *next; //next indent on the indent stack +} indent_t; + +//source file +typedef struct source_s +{ + char filename[_MAX_PATH]; //file name of the script + char includepath[_MAX_PATH]; //path to include files + punctuation_t *punctuations; //punctuations to use + script_t *scriptstack; //stack with scripts of the source + token_t *tokens; //tokens to read first + define_t *defines; //list with macro definitions + define_t **definehash; //hash chain with defines + indent_t *indentstack; //stack with indents + int skip; // > 0 if skipping conditional code + token_t token; //last read token +} source_t; + + +//read a token from the source +int PC_ReadToken( source_t *source, token_t *token ); +//expect a certain token +int PC_ExpectTokenString( source_t *source, char *string ); +//expect a certain token type +int PC_ExpectTokenType( source_t *source, int type, int subtype, token_t *token ); +//expect a token +int PC_ExpectAnyToken( source_t *source, token_t *token ); +//returns true when the token is available +int PC_CheckTokenString( source_t *source, char *string ); +//returns true an reads the token when a token with the given type is available +int PC_CheckTokenType( source_t *source, int type, int subtype, token_t *token ); +//skip tokens until the given token string is read +int PC_SkipUntilString( source_t *source, char *string ); +//unread the last token read from the script +void PC_UnreadLastToken( source_t *source ); +//unread the given token +void PC_UnreadToken( source_t *source, token_t *token ); +//read a token only if on the same line, lines are concatenated with a slash +int PC_ReadLine( source_t *source, token_t *token ); +//returns true if there was a white space in front of the token +int PC_WhiteSpaceBeforeToken( token_t *token ); +//add a define to the source +int PC_AddDefine( source_t *source, char *string ); +//add a globals define that will be added to all opened sources +int PC_AddGlobalDefine( char *string ); +//remove the given global define +int PC_RemoveGlobalDefine( char *name ); +//remove all globals defines +void PC_RemoveAllGlobalDefines( void ); +//add builtin defines +void PC_AddBuiltinDefines( source_t *source ); +//set the source include path +void PC_SetIncludePath( source_t *source, char *path ); +//set the punction set +void PC_SetPunctuations( source_t *source, punctuation_t *p ); +//load a source file +source_t *LoadSourceFile( char *filename ); +//load a source from memory +source_t *LoadSourceMemory( char *ptr, int length, char *name ); +//free the given source +void FreeSource( source_t *source ); +//print a source error +void QDECL SourceError( source_t *source, char *str, ... ); +//print a source warning +void QDECL SourceWarning( source_t *source, char *str, ... ); + diff --git a/src/extractfuncs/l_script.c b/src/extractfuncs/l_script.c new file mode 100644 index 0000000..caf4665 --- /dev/null +++ b/src/extractfuncs/l_script.c @@ -0,0 +1,1419 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_script.c + * + * desc: lexicographical parser + * + * + *****************************************************************************/ + +//#define SCREWUP +//#define BOTLIB +//#define MEQCC +//#define BSPC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" + +typedef enum {qfalse, qtrue} qboolean; + +#endif //SCREWUP + +#ifdef BOTLIB +//include files for usage in the bot library +#include "../game/q_shared.h" +#include "botlib.h" +#include "be_interface.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#endif //BOTLIB + +#ifdef MEQCC +//include files for usage in MrElusive's QuakeC Compiler +#include "qcc.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + + +#define PUNCTABLE + +//longer punctuations first +punctuation_t default_punctuations[] = +{ + //binary operators + {">>=",P_RSHIFT_ASSIGN, NULL}, + {"<<=",P_LSHIFT_ASSIGN, NULL}, + // + {"...",P_PARMS, NULL}, + //define merge operator + {"##",P_PRECOMPMERGE, NULL}, + //logic operators + {"&&",P_LOGIC_AND, NULL}, + {"||",P_LOGIC_OR, NULL}, + {">=",P_LOGIC_GEQ, NULL}, + {"<=",P_LOGIC_LEQ, NULL}, + {"==",P_LOGIC_EQ, NULL}, + {"!=",P_LOGIC_UNEQ, NULL}, + //arithmatic operators + {"*=",P_MUL_ASSIGN, NULL}, + {"/=",P_DIV_ASSIGN, NULL}, + {"%=",P_MOD_ASSIGN, NULL}, + {"+=",P_ADD_ASSIGN, NULL}, + {"-=",P_SUB_ASSIGN, NULL}, + {"++",P_INC, NULL}, + {"--",P_DEC, NULL}, + //binary operators + {"&=",P_BIN_AND_ASSIGN, NULL}, + {"|=",P_BIN_OR_ASSIGN, NULL}, + {"^=",P_BIN_XOR_ASSIGN, NULL}, + {">>",P_RSHIFT, NULL}, + {"<<",P_LSHIFT, NULL}, + //reference operators + {"->",P_POINTERREF, NULL}, + //C++ + {"::",P_CPP1, NULL}, + {".*",P_CPP2, NULL}, + //arithmatic operators + {"*",P_MUL, NULL}, + {"/",P_DIV, NULL}, + {"%",P_MOD, NULL}, + {"+",P_ADD, NULL}, + {"-",P_SUB, NULL}, + {"=",P_ASSIGN, NULL}, + //binary operators + {"&",P_BIN_AND, NULL}, + {"|",P_BIN_OR, NULL}, + {"^",P_BIN_XOR, NULL}, + {"~",P_BIN_NOT, NULL}, + //logic operators + {"!",P_LOGIC_NOT, NULL}, + {">",P_LOGIC_GREATER, NULL}, + {"<",P_LOGIC_LESS, NULL}, + //reference operator + {".",P_REF, NULL}, + //seperators + {",",P_COMMA, NULL}, + {";",P_SEMICOLON, NULL}, + //label indication + {":",P_COLON, NULL}, + //if statement + {"?",P_QUESTIONMARK, NULL}, + //embracements + {"(",P_PARENTHESESOPEN, NULL}, + {")",P_PARENTHESESCLOSE, NULL}, + {"{",P_BRACEOPEN, NULL}, + {"}",P_BRACECLOSE, NULL}, + {"[",P_SQBRACKETOPEN, NULL}, + {"]",P_SQBRACKETCLOSE, NULL}, + // + {"\\",P_BACKSLASH, NULL}, + //precompiler operator + {"#",P_PRECOMP, NULL}, +#ifdef DOLLAR + {"$",P_DOLLAR, NULL}, +#endif //DOLLAR + {NULL, 0} +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PS_CreatePunctuationTable( script_t *script, punctuation_t *punctuations ) { + int i; + punctuation_t *p, *lastp, *newp; + + //get memory for the table + if ( !script->punctuationtable ) { + script->punctuationtable = (punctuation_t **) + GetMemory( 256 * sizeof( punctuation_t * ) ); + } + memset( script->punctuationtable, 0, 256 * sizeof( punctuation_t * ) ); + //add the punctuations in the list to the punctuation table + for ( i = 0; punctuations[i].p; i++ ) + { + newp = &punctuations[i]; + lastp = NULL; + //sort the punctuations in this table entry on length (longer punctuations first) + for ( p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next ) + { + if ( strlen( p->p ) < strlen( newp->p ) ) { + newp->next = p; + if ( lastp ) { + lastp->next = newp; + } else { script->punctuationtable[(unsigned int) newp->p[0]] = newp;} + break; + } //end if + lastp = p; + } //end for + if ( !p ) { + newp->next = NULL; + if ( lastp ) { + lastp->next = newp; + } else { script->punctuationtable[(unsigned int) newp->p[0]] = newp;} + } //end if + } //end for +} //end of the function PS_CreatePunctuationTable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *PunctuationFromNum( script_t *script, int num ) { + int i; + + for ( i = 0; script->punctuations[i].p; i++ ) + { + if ( script->punctuations[i].n == num ) { + return script->punctuations[i].p; + } + } //end for + return "unkown punctuation"; +} //end of the function PunctuationFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptError( script_t *script, char *str, ... ) { + char text[1024]; + va_list ap; + + if ( script->flags & SCFL_NOERRORS ) { + return; + } + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); +#ifdef BOTLIB + botimport.Print( PRT_ERROR, "file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //BOTLIB +#ifdef MEQCC + printf( "error: file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //MEQCC +#ifdef BSPC + Log_Print( "error: file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //BSPC +} //end of the function ScriptError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptWarning( script_t *script, char *str, ... ) { + char text[1024]; + va_list ap; + + if ( script->flags & SCFL_NOWARNINGS ) { + return; + } + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); +#ifdef BOTLIB + botimport.Print( PRT_WARNING, "file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //BOTLIB +#ifdef MEQCC + printf( "warning: file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //MEQCC +#ifdef BSPC + Log_Print( "warning: file %s, line %d: %s\n", script->filename, script->line, text ); +#endif //BSPC +} //end of the function ScriptWarning +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetScriptPunctuations( script_t *script, punctuation_t *p ) { +#ifdef PUNCTABLE + if ( p ) { + PS_CreatePunctuationTable( script, p ); + } else { PS_CreatePunctuationTable( script, default_punctuations );} +#endif //PUNCTABLE + if ( p ) { + script->punctuations = p; + } else { script->punctuations = default_punctuations;} +} //end of the function SetScriptPunctuations +//============================================================================ +// Reads spaces, tabs, C-like comments etc. +// When a newline character is found the scripts line counter is increased. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadWhiteSpace( script_t *script ) { + while ( 1 ) + { + //skip white space + while ( *script->script_p <= ' ' ) + { + if ( !*script->script_p ) { + return 0; + } + if ( *script->script_p == '\n' ) { + script->line++; + } + script->script_p++; + } //end while + //skip comments + if ( *script->script_p == '/' ) { + //comments // + if ( *( script->script_p + 1 ) == '/' ) { + script->script_p++; + do + { + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + } //end do + while ( *script->script_p != '\n' ); + script->line++; + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + continue; + } //end if + //comments /* */ + else if ( *( script->script_p + 1 ) == '*' ) { + script->script_p++; + do + { + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + if ( *script->script_p == '\n' ) { + script->line++; + } + } //end do + while ( !( *script->script_p == '*' && *( script->script_p + 1 ) == '/' ) ); + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + script->script_p++; + if ( !*script->script_p ) { + return 0; + } + continue; + } //end if + } //end if + break; + } //end while + return 1; +} //end of the function PS_ReadWhiteSpace +//============================================================================ +// Reads an escape character. +// +// Parameter: script : script to read from +// ch : place to store the read escape character +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadEscapeCharacter( script_t *script, char *ch ) { + int c, val, i; + + //step over the leading '\\' + script->script_p++; + //determine the escape character + switch ( *script->script_p ) + { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + script->script_p++; + for ( i = 0, val = 0; ; i++, script->script_p++ ) + { + c = *script->script_p; + if ( c >= '0' && c <= '9' ) { + c = c - '0'; + } else if ( c >= 'A' && c <= 'Z' ) { + c = c - 'A' + 10; + } else if ( c >= 'a' && c <= 'z' ) { + c = c - 'a' + 10; + } else { break;} + val = ( val << 4 ) + c; + } //end for + script->script_p--; + if ( val > 0xFF ) { + ScriptWarning( script, "too large value in escape character" ); + val = 0xFF; + } //end if + c = val; + break; + } //end case + default: //NOTE: decimal ASCII code, NOT octal + { + if ( *script->script_p < '0' || *script->script_p > '9' ) { + ScriptError( script, "unknown escape char" ); + } + for ( i = 0, val = 0; ; i++, script->script_p++ ) + { + c = *script->script_p; + if ( c >= '0' && c <= '9' ) { + c = c - '0'; + } else { break;} + val = val * 10 + c; + } //end for + script->script_p--; + if ( val > 0xFF ) { + ScriptWarning( script, "too large value in escape character" ); + val = 0xFF; + } //end if + c = val; + break; + } //end default + } //end switch + //step over the escape character or the last digit of the number + script->script_p++; + //store the escape character + *ch = c; + //succesfully read escape character + return 1; +} //end of the function PS_ReadEscapeCharacter +//============================================================================ +// Reads C-like string. Escape characters are interpretted. +// Quotes are included with the string. +// Reads two strings with a white space between them as one string. +// +// Parameter: script : script to read from +// token : buffer to store the string +// Returns: qtrue when a string was read succesfully +// Changes Globals: - +//============================================================================ +int PS_ReadString( script_t *script, token_t *token, int quote ) { + int len, tmpline; + char *tmpscript_p; + + if ( quote == '\"' ) { + token->type = TT_STRING; + } else { token->type = TT_LITERAL;} + + len = 0; + //leading quote + token->string[len++] = *script->script_p++; + // + while ( 1 ) + { + //minus 2 because trailing double quote and zero have to be appended + if ( len >= MAX_TOKEN - 2 ) { + ScriptError( script, "string longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + //if there is an escape character and + //if escape characters inside a string are allowed + if ( *script->script_p == '\\' && !( script->flags & SCFL_NOSTRINGESCAPECHARS ) ) { + if ( !PS_ReadEscapeCharacter( script, &token->string[len] ) ) { + token->string[len] = 0; + return 0; + } //end if + len++; + } //end if + //if a trailing quote + else if ( *script->script_p == quote ) { + //step over the double quote + script->script_p++; + //if white spaces in a string are not allowed + if ( script->flags & SCFL_NOSTRINGWHITESPACES ) { + break; + } + // + tmpscript_p = script->script_p; + tmpline = script->line; + //read unusefull stuff between possible two following strings + if ( !PS_ReadWhiteSpace( script ) ) { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //if there's no leading double qoute + if ( *script->script_p != quote ) { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //step over the new leading double quote + script->script_p++; + } //end if + else + { + if ( *script->script_p == '\0' ) { + token->string[len] = 0; + ScriptError( script, "missing trailing quote" ); + return 0; + } //end if + if ( *script->script_p == '\n' ) { + token->string[len] = 0; + ScriptError( script, "newline inside string %s", token->string ); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end else + } //end while + //trailing quote + token->string[len++] = quote; + //end string with a zero + token->string[len] = '\0'; + //the sub type is the length of the string + token->subtype = len; + return 1; +} //end of the function PS_ReadString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadName( script_t *script, token_t *token ) { + int len = 0; + char c; + + token->type = TT_NAME; + do + { + token->string[len++] = *script->script_p++; + if ( len >= MAX_TOKEN ) { + ScriptError( script, "name longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + c = *script->script_p; + } while ( ( c >= 'a' && c <= 'z' ) || + ( c >= 'A' && c <= 'Z' ) || + ( c >= '0' && c <= '9' ) || + c == '_' ); + token->string[len] = '\0'; + //the sub type is the length of the name + token->subtype = len; + return 1; +} //end of the function PS_ReadName +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void NumberValue( char *string, int subtype, unsigned long int *intvalue, + long double *floatvalue ) { + unsigned long int dotfound = 0; + + *intvalue = 0; + *floatvalue = 0; + //floating point number + if ( subtype & TT_FLOAT ) { + while ( *string ) + { + if ( *string == '.' ) { + if ( dotfound ) { + return; + } + dotfound = 10; + string++; + } //end if + if ( dotfound ) { + *floatvalue = *floatvalue + ( long double )( *string - '0' ) / + (long double) dotfound; + dotfound *= 10; + } //end if + else + { + *floatvalue = *floatvalue * 10.0 + ( long double )( *string - '0' ); + } //end else + string++; + } //end while + *intvalue = (unsigned long) *floatvalue; + } //end if + else if ( subtype & TT_DECIMAL ) { + while ( *string ) *intvalue = *intvalue * 10 + ( *string++ - '0' ); + *floatvalue = *intvalue; + } //end else if + else if ( subtype & TT_HEX ) { + //step over the leading 0x or 0X + string += 2; + while ( *string ) + { + *intvalue <<= 4; + if ( *string >= 'a' && *string <= 'f' ) { + *intvalue += *string - 'a' + 10; + } else if ( *string >= 'A' && *string <= 'F' ) { + *intvalue += *string - 'A' + 10; + } else { *intvalue += *string - '0';} + string++; + } //end while + *floatvalue = *intvalue; + } //end else if + else if ( subtype & TT_OCTAL ) { + //step over the first zero + string += 1; + while ( *string ) *intvalue = ( *intvalue << 3 ) + ( *string++ - '0' ); + *floatvalue = *intvalue; + } //end else if + else if ( subtype & TT_BINARY ) { + //step over the leading 0b or 0B + string += 2; + while ( *string ) *intvalue = ( *intvalue << 1 ) + ( *string++ - '0' ); + *floatvalue = *intvalue; + } //end else if +} //end of the function NumberValue +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadNumber( script_t *script, token_t *token ) { + int len = 0, i; + int octal, dot; + char c; +// unsigned long int intvalue = 0; +// long double floatvalue = 0; + + token->type = TT_NUMBER; + //check for a hexadecimal number + if ( *script->script_p == '0' && + ( *( script->script_p + 1 ) == 'x' || + *( script->script_p + 1 ) == 'X' ) ) { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while ( ( c >= '0' && c <= '9' ) || + ( c >= 'a' && c <= 'f' ) || + ( c >= 'A' && c <= 'A' ) ) + { + token->string[len++] = *script->script_p++; + if ( len >= MAX_TOKEN ) { + ScriptError( script, "hexadecimal number longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_HEX; + } //end if +#ifdef BINARYNUMBERS + //check for a binary number + else if ( *script->script_p == '0' && + ( *( script->script_p + 1 ) == 'b' || + *( script->script_p + 1 ) == 'B' ) ) { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while ( c == '0' || c == '1' ) + { + token->string[len++] = *script->script_p++; + if ( len >= MAX_TOKEN ) { + ScriptError( script, "binary number longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_BINARY; + } //end if +#endif //BINARYNUMBERS + else //decimal or octal integer or floating point number + { + octal = qfalse; + dot = qfalse; + if ( *script->script_p == '0' ) { + octal = qtrue; + } + while ( 1 ) + { + token->string[len++] = *script->script_p++; + if ( len >= MAX_TOKEN ) { + ScriptError( script, "number longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + c = *script->script_p; + if ( c == '.' ) { + dot = qtrue; + } else if ( c == '8' || c == '9' ) { + octal = qfalse; + } else if ( c < '0' || c > '9' ) { + break; + } + } //end while + if ( octal ) { + token->subtype |= TT_OCTAL; + } else { token->subtype |= TT_DECIMAL;} + if ( dot ) { + token->subtype |= TT_FLOAT; + } + } //end else + for ( i = 0; i < 2; i++ ) + { + c = *script->script_p; + //check for a LONG number + if ( c == 'l' || c == 'L' && + !( token->subtype & TT_LONG ) ) { + script->script_p++; + token->subtype |= TT_LONG; + } //end if + //check for an UNSIGNED number + else if ( c == 'u' || c == 'U' && + !( token->subtype & ( TT_UNSIGNED | TT_FLOAT ) ) ) { + script->script_p++; + token->subtype |= TT_UNSIGNED; + } //end if + } //end for + token->string[len] = '\0'; +#ifdef NUMBERVALUE + NumberValue( token->string, token->subtype, &token->intvalue, &token->floatvalue ); +#endif //NUMBERVALUE + if ( !( token->subtype & TT_FLOAT ) ) { + token->subtype |= TT_INTEGER; + } + return 1; +} //end of the function PS_ReadNumber +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadLiteral( script_t *script, token_t *token ) { + token->type = TT_LITERAL; + //first quote + token->string[0] = *script->script_p++; + //check for end of file + if ( !*script->script_p ) { + ScriptError( script, "end of file before trailing \'" ); + return 0; + } //end if + //if it is an escape character + if ( *script->script_p == '\\' ) { + if ( !PS_ReadEscapeCharacter( script, &token->string[1] ) ) { + return 0; + } + } //end if + else + { + token->string[1] = *script->script_p++; + } //end else + //check for trailing quote + if ( *script->script_p != '\'' ) { + ScriptWarning( script, "too many characters in literal, ignored" ); + while ( *script->script_p && + *script->script_p != '\'' && + *script->script_p != '\n' ) + { + script->script_p++; + } //end while + if ( *script->script_p == '\'' ) { + script->script_p++; + } + } //end if + //store the trailing quote + token->string[2] = *script->script_p++; + //store trailing zero to end the string + token->string[3] = '\0'; + //the sub type is the integer literal value + token->subtype = token->string[1]; + // + return 1; +} //end of the function PS_ReadLiteral +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPunctuation( script_t *script, token_t *token ) { + int len; + char *p; + punctuation_t *punc; + +#ifdef PUNCTABLE + for ( punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next ) + { +#else + int i; + + for ( i = 0; script->punctuations[i].p; i++ ) + { + punc = &script->punctuations[i]; +#endif //PUNCTABLE + p = punc->p; + len = strlen( p ); + //if the script contains at least as much characters as the punctuation + if ( script->script_p + len <= script->end_p ) { + //if the script contains the punctuation + if ( !strncmp( script->script_p, p, len ) ) { + strncpy( token->string, p, MAX_TOKEN ); + script->script_p += len; + token->type = TT_PUNCTUATION; + //sub type is the number of the punctuation + token->subtype = punc->n; + return 1; + } //end if + } //end if + } //end for + return 0; +} //end of the function PS_ReadPunctuation +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPrimitive( script_t *script, token_t *token ) { + int len; + + len = 0; + while ( *script->script_p > ' ' && *script->script_p != ';' ) + { + if ( len >= MAX_TOKEN ) { + ScriptError( script, "primitive token longer than MAX_TOKEN = %d", MAX_TOKEN ); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end while + token->string[len] = 0; + //copy the token into the script structure + memcpy( &script->token, token, sizeof( token_t ) ); + //primitive reading successfull + return 1; +} //end of the function PS_ReadPrimitive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadToken( script_t *script, token_t *token ) { + //if there is a token available (from UnreadToken) + if ( script->tokenavailable ) { + script->tokenavailable = 0; + memcpy( token, &script->token, sizeof( token_t ) ); + return 1; + } //end if + //save script pointer + script->lastscript_p = script->script_p; + //save line counter + script->lastline = script->line; + //clear the token stuff + memset( token, 0, sizeof( token_t ) ); + //start of the white space + script->whitespace_p = script->script_p; + token->whitespace_p = script->script_p; + //read unusefull stuff + if ( !PS_ReadWhiteSpace( script ) ) { + return 0; + } + //end of the white space + script->endwhitespace_p = script->script_p; + token->endwhitespace_p = script->script_p; + //line the token is on + token->line = script->line; + //number of lines crossed before token + token->linescrossed = script->line - script->lastline; + //if there is a leading double quote + if ( *script->script_p == '\"' ) { + if ( !PS_ReadString( script, token, '\"' ) ) { + return 0; + } + } //end if + //if an literal + else if ( *script->script_p == '\'' ) { + //if (!PS_ReadLiteral(script, token)) return 0; + if ( !PS_ReadString( script, token, '\'' ) ) { + return 0; + } + } //end if + //if there is a number + else if ( ( *script->script_p >= '0' && *script->script_p <= '9' ) || + ( *script->script_p == '.' && + ( *( script->script_p + 1 ) >= '0' && *( script->script_p + 1 ) <= '9' ) ) ) { + if ( !PS_ReadNumber( script, token ) ) { + return 0; + } + } //end if + //if this is a primitive script + else if ( script->flags & SCFL_PRIMITIVE ) { + return PS_ReadPrimitive( script, token ); + } //end else if + //if there is a name + else if ( ( *script->script_p >= 'a' && *script->script_p <= 'z' ) || + ( *script->script_p >= 'A' && *script->script_p <= 'Z' ) || + *script->script_p == '_' ) { + if ( !PS_ReadName( script, token ) ) { + return 0; + } + } //end if + //check for punctuations + else if ( !PS_ReadPunctuation( script, token ) ) { + ScriptError( script, "can't read token" ); + return 0; + } //end if + //copy the token into the script structure + memcpy( &script->token, token, sizeof( token_t ) ); + //succesfully read a token + return 1; +} //end of the function PS_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenString( script_t *script, char *string ) { + token_t token; + + if ( !PS_ReadToken( script, &token ) ) { + ScriptError( script, "couldn't find expected %s", string ); + return 0; + } //end if + + if ( strcmp( token.string, string ) ) { + ScriptError( script, "expected %s, found %s", string, token.string ); + return 0; + } //end if + return 1; +} //end of the function PS_ExpectToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenType( script_t *script, int type, int subtype, token_t *token ) { + char str[MAX_TOKEN]; + + if ( !PS_ReadToken( script, token ) ) { + ScriptError( script, "couldn't read expected token" ); + return 0; + } //end if + + if ( token->type != type ) { + if ( type == TT_STRING ) { + strcpy( str, "string" ); + } + if ( type == TT_LITERAL ) { + strcpy( str, "literal" ); + } + if ( type == TT_NUMBER ) { + strcpy( str, "number" ); + } + if ( type == TT_NAME ) { + strcpy( str, "name" ); + } + if ( type == TT_PUNCTUATION ) { + strcpy( str, "punctuation" ); + } + ScriptError( script, "expected a %s, found %s", str, token->string ); + return 0; + } //end if + if ( token->type == TT_NUMBER ) { + if ( ( token->subtype & subtype ) != subtype ) { + if ( subtype & TT_DECIMAL ) { + strcpy( str, "decimal" ); + } + if ( subtype & TT_HEX ) { + strcpy( str, "hex" ); + } + if ( subtype & TT_OCTAL ) { + strcpy( str, "octal" ); + } + if ( subtype & TT_BINARY ) { + strcpy( str, "binary" ); + } + if ( subtype & TT_LONG ) { + strcat( str, " long" ); + } + if ( subtype & TT_UNSIGNED ) { + strcat( str, " unsigned" ); + } + if ( subtype & TT_FLOAT ) { + strcat( str, " float" ); + } + if ( subtype & TT_INTEGER ) { + strcat( str, " integer" ); + } + ScriptError( script, "expected %s, found %s", str, token->string ); + return 0; + } //end if + } //end if + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + ScriptError( script, "BUG: wrong punctuation subtype" ); + return 0; + } //end if + if ( token->subtype != subtype ) { + ScriptError( script, "expected %s, found %s", + script->punctuations[subtype], token->string ); + return 0; + } //end if + } //end else if + return 1; +} //end of the function PS_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectAnyToken( script_t *script, token_t *token ) { + if ( !PS_ReadToken( script, token ) ) { + ScriptError( script, "couldn't read expected token" ); + return 0; + } //end if + else + { + return 1; + } //end else +} //end of the function PS_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenString( script_t *script, char *string ) { + token_t tok; + + if ( !PS_ReadToken( script, &tok ) ) { + return 0; + } + //if the token is available + if ( !strcmp( tok.string, string ) ) { + return 1; + } + //token not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenType( script_t *script, int type, int subtype, token_t *token ) { + token_t tok; + + if ( !PS_ReadToken( script, &tok ) ) { + return 0; + } + //if the type matches + if ( tok.type == type && + ( tok.subtype & subtype ) == subtype ) { + memcpy( token, &tok, sizeof( token_t ) ); + return 1; + } //end if + //token is not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_SkipUntilString( script_t *script, char *string ) { + token_t token; + + while ( PS_ReadToken( script, &token ) ) + { + if ( !strcmp( token.string, string ) ) { + return 1; + } + } //end while + return 0; +} //end of the function PS_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadLastToken( script_t *script ) { + script->tokenavailable = 1; +} //end of the function UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadToken( script_t *script, token_t *token ) { + memcpy( &script->token, token, sizeof( token_t ) ); + script->tokenavailable = 1; +} //end of the function UnreadToken +//============================================================================ +// returns the next character of the read white space, returns NULL if none +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +char PS_NextWhiteSpaceChar( script_t *script ) { + if ( script->whitespace_p != script->endwhitespace_p ) { + return *script->whitespace_p++; + } //end if + else + { + return 0; + } //end else +} //end of the function PS_NextWhiteSpaceChar +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripDoubleQuotes( char *string ) { + if ( *string == '\"' ) { + strcpy( string, string + 1 ); + } //end if + if ( string[strlen( string ) - 1] == '\"' ) { + string[strlen( string ) - 1] = '\0'; + } //end if +} //end of the function StripDoubleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripSingleQuotes( char *string ) { + if ( *string == '\'' ) { + strcpy( string, string + 1 ); + } //end if + if ( string[strlen( string ) - 1] == '\'' ) { + string[strlen( string ) - 1] = '\0'; + } //end if +} //end of the function StripSingleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +long double ReadSignedFloat( script_t *script ) { + token_t token; + long double sign = 1; + + PS_ExpectAnyToken( script, &token ); + if ( !strcmp( token.string, "-" ) ) { + sign = -1; + PS_ExpectTokenType( script, TT_NUMBER, 0, &token ); + } //end if + else if ( token.type != TT_NUMBER ) { + ScriptError( script, "expected float value, found %s\n", token.string ); + } //end else if + return sign * token.floatvalue; +} //end of the function ReadSignedFloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +signed long int ReadSignedInt( script_t *script ) { + token_t token; + signed long int sign = 1; + + PS_ExpectAnyToken( script, &token ); + if ( !strcmp( token.string, "-" ) ) { + sign = -1; + PS_ExpectTokenType( script, TT_NUMBER, TT_INTEGER, &token ); + } //end if + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + ScriptError( script, "expected integer value, found %s\n", token.string ); + } //end else if + return sign * token.intvalue; +} //end of the function ReadSignedInt +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void SetScriptFlags( script_t *script, int flags ) { + script->flags = flags; +} //end of the function SetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int GetScriptFlags( script_t *script ) { + return script->flags; +} //end of the function GetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void ResetScript( script_t *script ) { + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //begin of white space + script->whitespace_p = NULL; + //end of white space + script->endwhitespace_p = NULL; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + //clear the saved token + memset( &script->token, 0, sizeof( token_t ) ); +} //end of the function ResetScript +//============================================================================ +// returns true if at the end of the script +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int EndOfScript( script_t *script ) { + return script->script_p >= script->end_p; +} //end of the function EndOfScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int NumLinesCrossed( script_t *script ) { + return script->line - script->lastline; +} //end of the function NumLinesCrossed +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int ScriptSkipTo( script_t *script, char *value ) { + int len; + char firstchar; + + firstchar = *value; + len = strlen( value ); + do + { + if ( !PS_ReadWhiteSpace( script ) ) { + return 0; + } + if ( *script->script_p == firstchar ) { + if ( !strncmp( script->script_p, value, len ) ) { + return 1; + } //end if + } //end if + script->script_p++; + } while ( 1 ); +} //end of the function ScriptSkipTo +#ifndef BOTLIB +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int FileLength( FILE *fp ) { + int pos; + int end; + + pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + end = ftell( fp ); + fseek( fp, pos, SEEK_SET ); + + return end; +} //end of the function FileLength +#endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptFile( char *filename ) { +#ifdef BOTLIB + fileHandle_t fp; + char pathname[MAX_QPATH]; +#else + FILE *fp; +#endif + int length; + void *buffer; + script_t *script; + +#ifdef BOTLIB + Com_sprintf( pathname, MAX_QPATH, "botfiles/%s", filename ); + length = botimport.FS_FOpenFile( pathname, &fp, FS_READ ); + if ( !fp ) { + return NULL; + } +#else + fp = fopen( filename, "rb" ); + if ( !fp ) { + return NULL; + } + + length = FileLength( fp ); +#endif + + buffer = GetClearedMemory( sizeof( script_t ) + length + 1 ); + script = (script_t *) buffer; + memset( script, 0, sizeof( script_t ) ); + strcpy( script->filename, filename ); + script->buffer = (char *) buffer + sizeof( script_t ); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations( script, NULL ); + // +#ifdef BOTLIB + botimport.FS_Read( script->buffer, length, fp ); + botimport.FS_FCloseFile( fp ); +#else + if ( fread( script->buffer, length, 1, fp ) != 1 ) { + FreeMemory( buffer ); + script = NULL; + } //end if + fclose( fp ); +#endif + // + return script; +} //end of the function LoadScriptFile +//============================================================================ +//load a script from the given memory with the given length +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptMemory( char *ptr, int length, char *name ) { + void *buffer; + script_t *script; + + buffer = GetClearedMemory( sizeof( script_t ) + length + 1 ); + script = (script_t *) buffer; + memset( script, 0, sizeof( script_t ) ); + strcpy( script->filename, name ); + script->buffer = (char *) buffer + sizeof( script_t ); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations( script, NULL ); + // + memcpy( script->buffer, ptr, length ); + // + return script; +} //end of the function LoadScriptMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeScript( script_t *script ) { +#ifdef PUNCTABLE + if ( script->punctuationtable ) { + FreeMemory( script->punctuationtable ); + } +#endif //PUNCTABLE + FreeMemory( script ); +} //end of the function FreeScript diff --git a/src/extractfuncs/l_script.h b/src/extractfuncs/l_script.h new file mode 100644 index 0000000..e41b9fd --- /dev/null +++ b/src/extractfuncs/l_script.h @@ -0,0 +1,266 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: l_script.h + * + * desc: lexicographical parser + * + * + *****************************************************************************/ + +// Ridah, can't get it to compile without this +#ifndef QDECL + +// for windows fastcall option +#define QDECL +//======================= WIN32 DEFINES ================================= +#ifdef WIN32 +#undef QDECL +#define QDECL __cdecl +#endif +#endif +// done. + +//undef if binary numbers of the form 0b... or 0B... are not allowed +#define BINARYNUMBERS +//undef if not using the token.intvalue and token.floatvalue +#define NUMBERVALUE +//use dollar sign also as punctuation +#define DOLLAR + +//maximum token length +#define MAX_TOKEN 1024 +//maximum path length +#ifndef MAX_QPATH + #define MAX_QPATH 64 +#endif +#ifndef _MAX_PATH + #define _MAX_PATH MAX_QPATH +#endif + +//script flags +#define SCFL_NOERRORS 0x0001 +#define SCFL_NOWARNINGS 0x0002 +#define SCFL_NOSTRINGWHITESPACES 0x0004 +#define SCFL_NOSTRINGESCAPECHARS 0x0008 +#define SCFL_PRIMITIVE 0x0010 +#define SCFL_NOBINARYNUMBERS 0x0020 +#define SCFL_NONUMBERVALUES 0x0040 + +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +//string sub type +//--------------- +// the length of the string +//literal sub type +//---------------- +// the ASCII code of the literal +//number sub type +//--------------- +#define TT_DECIMAL 0x0008 // decimal number +#define TT_HEX 0x0100 // hexadecimal number +#define TT_OCTAL 0x0200 // octal number +#ifdef BINARYNUMBERS +#define TT_BINARY 0x0400 // binary number +#endif //BINARYNUMBERS +#define TT_FLOAT 0x0800 // floating point number +#define TT_INTEGER 0x1000 // integer number +#define TT_LONG 0x2000 // long number +#define TT_UNSIGNED 0x4000 // unsigned number +//punctuation sub type +//-------------------- +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 +//name sub type +//------------- +// the length of the name + +//punctuation +typedef struct punctuation_s +{ + char *p; //punctuation character(s) + int n; //punctuation indication + struct punctuation_s *next; //next punctuation +} punctuation_t; + +//token +typedef struct token_s +{ + char string[MAX_TOKEN]; //available token + int type; //last read token type + int subtype; //last read token sub type +#ifdef NUMBERVALUE + unsigned long int intvalue; //integer value + long double floatvalue; //floating point value +#endif //NUMBERVALUE + char *whitespace_p; //start of white space before token + char *endwhitespace_p; //start of white space before token + int line; //line the token was on + int linescrossed; //lines crossed in white space + struct token_s *next; //next token in chain +} token_t; + +//script file +typedef struct script_s +{ + char filename[_MAX_PATH]; //file name of the script + char *buffer; //buffer containing the script + char *script_p; //current pointer in the script + char *end_p; //pointer to the end of the script + char *lastscript_p; //script pointer before reading token + char *whitespace_p; //begin of the white space + char *endwhitespace_p; //end of the white space + int length; //length of the script in bytes + int line; //current line in script + int lastline; //line before reading token + int tokenavailable; //set by UnreadLastToken + int flags; //several script flags + punctuation_t *punctuations; //the punctuations used in the script + punctuation_t **punctuationtable; + token_t token; //available token + struct script_s *next; //next script in a chain +} script_t; + +//read a token from the script +int PS_ReadToken( script_t *script, token_t *token ); +//expect a certain token +int PS_ExpectTokenString( script_t *script, char *string ); +//expect a certain token type +int PS_ExpectTokenType( script_t *script, int type, int subtype, token_t *token ); +//expect a token +int PS_ExpectAnyToken( script_t *script, token_t *token ); +//returns true when the token is available +int PS_CheckTokenString( script_t *script, char *string ); +//returns true an reads the token when a token with the given type is available +int PS_CheckTokenType( script_t *script, int type, int subtype, token_t *token ); +//skip tokens until the given token string is read +int PS_SkipUntilString( script_t *script, char *string ); +//unread the last token read from the script +void PS_UnreadLastToken( script_t *script ); +//unread the given token +void PS_UnreadToken( script_t *script, token_t *token ); +//returns the next character of the read white space, returns NULL if none +char PS_NextWhiteSpaceChar( script_t *script ); +//remove any leading and trailing double quotes from the token +void StripDoubleQuotes( char *string ); +//remove any leading and trailing single quotes from the token +void StripSingleQuotes( char *string ); +//read a possible signed integer +signed long int ReadSignedInt( script_t *script ); +//read a possible signed floating point number +long double ReadSignedFloat( script_t *script ); +//set an array with punctuations, NULL restores default C/C++ set +void SetScriptPunctuations( script_t *script, punctuation_t *p ); +//set script flags +void SetScriptFlags( script_t *script, int flags ); +//get script flags +int GetScriptFlags( script_t *script ); +//reset a script +void ResetScript( script_t *script ); +//returns true if at the end of the script +int EndOfScript( script_t *script ); +//returns a pointer to the punctuation with the given number +char *PunctuationFromNum( script_t *script, int num ); +//load a script from the given file at the given offset with the given length +script_t *LoadScriptFile( char *filename ); +//load a script from the given memory with the given length +script_t *LoadScriptMemory( char *ptr, int length, char *name ); +//free a script +void FreeScript( script_t *script ); +//print a script error with filename and line number +void QDECL ScriptError( script_t *script, char *str, ... ); +//print a script warning with filename and line number +void QDECL ScriptWarning( script_t *script, char *str, ... ); + + diff --git a/src/game/ai_cast.c b/src/game/ai_cast.c new file mode 100644 index 0000000..75b5993 --- /dev/null +++ b/src/game/ai_cast.c @@ -0,0 +1,878 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast.c +// Function: Wolfenstein AI Character Routines +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +The Wolfenstein AI uses the bot movement functions, and goal handling. + +The actual core thinking and decision making is handled by the Cast AI, +typically within the ai_cast*.c files. + +Some modifications to the botlib and botai are to be expected, the extent of those +changes is currently unknown. + +Currently, this seems to the the best approach, since if we're going to +use the AAS for navigation, we want to avoid having to re-write the movement +routines which are heavily associated with the AAS information. +*/ + +//cast states (allocated at run-time) +cast_state_t *caststates; +//number of characters +int numcast; +// +qboolean saveGamePending; +// +// minimum time between thinks (maximum is double this) +int aicast_thinktime; +// maximum number of character thinks at once +int aicast_maxthink; +// maximum clients +int aicast_maxclients; +// skill scale (0.0 -> 1.0) +float aicast_skillscale; + +// cvar to enable aicast debugging, set higher for more levels of debugging +vmCvar_t aicast_debug; +vmCvar_t aicast_debugname; +vmCvar_t aicast_scripts; + +// string versions of the attributes used for per-level, per-character definitions +char *castAttributeStrings[] = +{ + "RUNNING_SPEED", // max = 300 (running speed) + "WALKING_SPEED", // max = 300 (walking speed) + "CROUCHING_SPEED", // max = 300 (crouching speed) + "FOV", // max = 360 (field of view) + "YAW_SPEED", // max = 300 (yaw speed) + "LEADER", // max = 1.0 (ability to lead an AI squadron) + "AIM_SKILL", // max = 1.0 (skill while aiming) + "AIM_ACCURACY", // max = 1.0 (accuracy of firing) + "ATTACK_SKILL", // max = 1.0 (ability to attack and do other things, like retreat) + "REACTION_TIME", // max = 1.0 (upon seeing enemy, wait this long before reaction) + "ATTACK_CROUCH", // max = 1.0 (likely to crouch while firing) + "IDLE_CROUCH", // max = 1.0 (likely to crouch while idling) + "AGGRESSION", // max = 1.0 (willingness to fight till the death) + "TACTICAL", // max = 1.0 (ability to use strategy to their advantage, also behaviour whilst hunting enemy, more likely to creep around) + "CAMPER", // max = 1.0 (set this to make them stay in the spot they are spawned) + "ALERTNESS", // max = 1.0 (ability to notice enemies at long range) + "STARTING_HEALTH", + "HEARING_SCALE", + "INNER_DETECTION_RADIUS", + "PAIN_THRESHOLD_SCALE", + + NULL +}; + +/* +============ +AICast_Printf +============ +*/ +void AICast_Printf( int type, const char *fmt, ... ) { + char str[2048]; + va_list ap; + + va_start( ap, fmt ); + Q_vsnprintf( str, sizeof( str ), fmt, ap ); + va_end( ap ); + + switch ( type ) { + case AICAST_PRT_ALWAYS: { + G_Printf( "%s", str ); + break; + } + default: { + if ( aicast_debug.integer >= type ) { + G_Printf( "%s", str ); + } + break; + } + } +} + +/* +============ +AICast_GetCastState +============ +*/ +cast_state_t *AICast_GetCastState( int entitynum ) { + return &( caststates[ entitynum ] ); +} + +/* +============== +AICast_SetupClient +============== +*/ +int AICast_SetupClient( int client ) { + cast_state_t *cs; + bot_state_t *bs; + + if ( !botstates[client] ) { + botstates[client] = G_Alloc( sizeof( bot_state_t ) ); + memset( botstates[client], 0, sizeof( bot_state_t ) ); + } + bs = botstates[client]; + + if ( bs->inuse ) { + BotAI_Print( PRT_FATAL, "client %d already setup\n", client ); + return qfalse; + } + + cs = AICast_GetCastState( client ); + cs->bs = bs; + + //allocate a goal state + bs->gs = trap_BotAllocGoalState( client ); + + bs->inuse = qtrue; + bs->client = client; + bs->entitynum = client; + bs->setupcount = qtrue; + bs->entergame_time = trap_AAS_Time(); + bs->ms = trap_BotAllocMoveState(); + + return qtrue; +} + +/* +============== +AICast_ShutdownClient +============== +*/ +int AICast_ShutdownClient( int client ) { + cast_state_t *cs; + bot_state_t *bs; + + if ( !( bs = botstates[client] ) ) { + return BLERR_NOERROR; + } + if ( !bs->inuse ) { + BotAI_Print( PRT_ERROR, "client %d already shutdown\n", client ); + return BLERR_AICLIENTALREADYSHUTDOWN; + } + + cs = AICast_GetCastState( client ); + // + memset( cs, 0, sizeof( cast_state_t ) ); + numcast--; + + // now do the other bot stuff + +#ifdef DEBUG +// botai_import.DebugLineDelete(bs->debugline); +#endif //DEBUG + + trap_BotFreeMoveState( bs->ms ); + //free the goal state + trap_BotFreeGoalState( bs->gs ); + // + //clear the bot state + memset( bs, 0, sizeof( bot_state_t ) ); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //everything went ok + return BLERR_NOERROR; +} + +/* +============ +AICast_AddCastToGame +============ +*/ +//----(SA) modified this for head separation +gentity_t *AICast_AddCastToGame( gentity_t *ent, char *castname, char *model, char *head, char *sex, char *color, char *handicap ) { + int clientNum; + gentity_t *bot; + char userinfo[MAX_INFO_STRING]; + usercmd_t cmd; + + // create the bot's userinfo + userinfo[0] = '\0'; + + Info_SetValueForKey( userinfo, "name", castname ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + Info_SetValueForKey( userinfo, "handicap", handicap ); + Info_SetValueForKey( userinfo, "model", model ); + Info_SetValueForKey( userinfo, "head", head ); + Info_SetValueForKey( userinfo, "color", color ); + + // have the server allocate a client slot + clientNum = trap_BotAllocateClient(); + if ( clientNum == -1 ) { + G_Printf( S_COLOR_RED "BotAllocateClient failed\n" ); + return NULL; + } + bot = &g_entities[ clientNum ]; + bot->r.svFlags |= SVF_BOT; + bot->r.svFlags |= SVF_CASTAI; // flag it for special Cast AI behaviour + + // register the userinfo + trap_SetUserinfo( bot->s.number, userinfo ); + + // have it connect to the game as a normal client +//----(SA) ClientConnect requires a third 'isbot' parameter. setting to qfalse and noting + ClientConnect( bot->s.number, qtrue, qfalse ); +//----(SA) end + + // copy the origin/angles across + VectorCopy( ent->s.origin, bot->s.origin ); + VectorCopy( ent->s.angles, bot->s.angles ); + + memset( &cmd, 0, sizeof( cmd ) ); + ClientBegin( bot->s.number ); + + // set up the ai + AICast_SetupClient( bot->s.number ); + + return bot; +} + +/* +============ +AICast_CheckLevelAttributes +============ +*/ +void AICast_CheckLevelAttributes( cast_state_t *cs, gentity_t *ent, char **ppStr ) { + char *s; + int i; + + if ( !*ppStr ) { + return; + } + + while ( 1 ) { + s = COM_Parse( ppStr ); + if ( !s[0] || !Q_strncmp( s, "}", 2 ) ) { // end of attributes + break; + } + // + for ( i = 0; i < AICAST_MAX_ATTRIBUTES; i++ ) { + if ( !Q_strcasecmp( s, castAttributeStrings[i] ) ) { + // found a match, read in the value + s = COM_Parse( ppStr ); + if ( !s[0] ) { // end of attributes + break; + } + // set the attribute + cs->attributes[i] = atof( s ); + break; + } + } + } +} + +/* +============ +AICast_SetAASIndex +============ +*/ +void AICast_SetAASIndex( cast_state_t *cs ) { + if ( aiDefaults[cs->aiCharacter].bboxType == BBOX_SMALL ) { + cs->aasWorldIndex = AASWORLD_STANDARD; + cs->travelflags = AICAST_TFL_DEFAULT; + } else if ( aiDefaults[cs->aiCharacter].bboxType == BBOX_LARGE ) { + cs->aasWorldIndex = AASWORLD_LARGE; + cs->travelflags = AICAST_TFL_DEFAULT & ~TFL_DONOTENTER_LARGE; + } else { + Com_Error( ERR_DROP, "AICast_SetAASIndex: unsupported bounds size (%i)", aiDefaults[cs->aiCharacter].bboxType ); + } + + if ( !cs->attributes[ATTACK_CROUCH] ) { + cs->travelflags &= ~TFL_CROUCH; + } +} + +/* +============ +AICast_CreateCharacter + + returns 0 if unable to create the character +============ +*/ +gentity_t *AICast_CreateCharacter( gentity_t *ent, float *attributes, cast_weapon_info_t *weaponInfo, char *castname, char *model, char *head, char *sex, char *color, char *handicap ) { + gentity_t *newent; + gclient_t *client; + cast_state_t *cs; + char **ppStr; + int j; + + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { // no cast AI in multiplayer + return NULL; + } + // are bots enabled? + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + G_Printf( S_COLOR_RED "ERROR: Unable to spawn %s, 'bot_enable' is not set\n", ent->classname ); + return NULL; + } + // + // make sure we have a free slot for them + // + if ( level.numPlayingClients + 1 > aicast_maxclients ) { + G_Error( "Exceeded sv_maxclients (%d), unable to create %s\n", aicast_maxclients, ent->classname ); + return NULL; + } + // + // add it to the list (only do this if everything else passed) + // + + newent = AICast_AddCastToGame( ent, castname, model, head, sex, color, handicap ); + + if ( !newent ) { + return NULL; + } + client = newent->client; + // + // setup the character.. + // + cs = AICast_GetCastState( newent->s.number ); + // + // setup the attributes + memcpy( cs->attributes, attributes, sizeof( cs->attributes ) ); + ppStr = &ent->aiAttributes; + AICast_CheckLevelAttributes( cs, ent, ppStr ); + // + AICast_SetAASIndex( cs ); + // make sure they face the right direction + VectorCopy( ent->s.angles, cs->bs->ideal_viewangles ); + // factor in the delta_angles + for ( j = 0; j < 3; j++ ) { + cs->bs->viewangles[j] = AngleMod( newent->s.angles[j] - SHORT2ANGLE( newent->client->ps.delta_angles[j] ) ); + } + VectorCopy( ent->s.angles, newent->s.angles ); + VectorCopy( ent->s.origin, cs->startOrigin ); + // + cs->lastEnemy = -1; + cs->bs->enemy = -1; + cs->leaderNum = -1; + cs->castScriptStatus.scriptGotoEnt = -1; + cs->aiCharacter = ent->aiCharacter; + // + newent->aiName = ent->aiName; + newent->aiTeam = ent->aiTeam; + newent->targetname = ent->targetname; + // + newent->AIScript_AlertEntity = ent->AIScript_AlertEntity; + newent->aiInactive = ent->aiInactive; + newent->aiCharacter = cs->aiCharacter; + // + // parse the AI script for this character (if applicable) + cs->aiFlags |= AIFL_CORPSESIGHTING; // this is on by default for all characters, disabled if they have a "friendlysightcorpse" script event + AICast_ScriptParse( cs ); + // + // setup bounding boxes + //VectorCopy( mins, client->ps.mins ); + //VectorCopy( maxs, client->ps.maxs ); + AIChar_SetBBox( newent, cs ); + client->ps.friction = cs->attributes[RUNNING_SPEED] / 300.0; + // + // clear weapons/ammo + client->ps.weapon = 0; + memcpy( client->ps.weapons, weaponInfo->startingWeapons, sizeof( weaponInfo->startingWeapons ) ); + memcpy( client->ps.ammo, weaponInfo->startingAmmo, sizeof( client->ps.ammo ) ); + // + // starting health + if ( ent->health ) { + newent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = ent->health; + } else { + newent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = cs->attributes[STARTING_HEALTH]; + } + // + cs->weaponInfo = weaponInfo; + // + cs->lastThink = level.time; + // + newent->pain = AICast_Pain; + newent->die = AICast_Die; + // + //update the attack inventory values + AICast_UpdateBattleInventory( cs, cs->bs->enemy ); + +//----(SA) make sure all clips are loaded so we don't hear everyone loading up +// (we don't want to do this inside AICast_UpdateBattleInventory(), only on spawn or giveweapon) + for ( j = 0; j < MAX_WEAPONS; j++ ) { + Fill_Clip( &client->ps, j ); + } +//----(SA) end + + // select a weapon + AICast_ChooseWeapon( cs, qfalse ); + + // + // set the default function, overwrite if necessary + cs->aiFlags |= AIFL_JUST_SPAWNED; + AIFunc_DefaultStart( cs ); + // + numcast++; + // + return newent; +} + +/* +============ +AICast_Init + + called at each level start, before the world and it's entities have been spawned +============ +*/ +static int numSpawningCast; + +void AICast_Init( void ) { + vmCvar_t cvar; + int i; + + numSecrets = 0; + numcast = 0; + numSpawningCast = 0; + saveGamePending = qtrue; + + trap_Cvar_Register( &aicast_debug, "aicast_debug", "0", 0 ); + trap_Cvar_Register( &aicast_debugname, "aicast_debugname", "", 0 ); + trap_Cvar_Register( &aicast_scripts, "aicast_scripts", "1", 0 ); + + // (aicast_thinktime / sv_fps) * aicast_maxthink = number of cast's to think between each aicast frame + // so.. + // (100 / 20) * 6 = 30 + // + // so if the level has more than 30 AI cast's, they could start to bunch up, resulting in slower thinks + + trap_Cvar_Register( &cvar, "aicast_thinktime", "50", 0 ); + aicast_thinktime = trap_Cvar_VariableIntegerValue( "aicast_thinktime" ); + + trap_Cvar_Register( &cvar, "aicast_maxthink", "12", 0 ); + aicast_maxthink = trap_Cvar_VariableIntegerValue( "aicast_maxthink" ); + + aicast_maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" ); + + aicast_skillscale = (float)trap_Cvar_VariableIntegerValue( "g_gameSkill" ) / (float)GSKILL_MAX; + + caststates = G_Alloc( aicast_maxclients * sizeof( cast_state_t ) ); + memset( caststates, 0, sizeof( caststates ) ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + caststates[i].entityNum = i; + } + + // try and load in the AAS now, so we can interact with it during spawning of entities + i = 0; + trap_AAS_SetCurrentWorld( 0 ); + while ( !trap_AAS_Initialized() && ( i++ < 10 ) ) { + trap_BotLibStartFrame( (float) level.time / 1000 ); + } +} + +/* +=============== +AICast_FindEntityForName +=============== +*/ +gentity_t *AICast_FindEntityForName( char *name ) { + gentity_t *trav; + int i; + + for ( trav = g_entities, i = 0; i < aicast_maxclients; i++, trav++ ) { + if ( !trav->inuse ) { + continue; + } + if ( !trav->client ) { + continue; + } + if ( !trav->aiName ) { + continue; + } + if ( strcmp( trav->aiName, name ) ) { + continue; + } + return trav; + } + return NULL; +} + +/* +=============== +AICast_TravEntityForName +=============== +*/ +gentity_t *AICast_TravEntityForName( gentity_t *startent, char *name ) { + gentity_t *trav; + + if ( !startent ) { + trav = g_entities; + } else { + trav = startent + 1; + } + + for ( ; trav < g_entities + aicast_maxclients; trav++ ) { + if ( !trav->inuse ) { + continue; + } + if ( !trav->client ) { + continue; + } + if ( !trav->aiName ) { + continue; + } + if ( strcmp( trav->aiName, name ) ) { + continue; + } + return trav; + } + return NULL; +} + +/* +============ +AIChar_AIScript_AlertEntity + + triggered spawning, called from AI scripting +============ +*/ +void AIChar_AIScript_AlertEntity( gentity_t *ent ) { + vec3_t mins, maxs; + int numTouch, touch[10], i; + cast_state_t *cs; + + if ( !ent->aiInactive ) { + return; + } + + cs = AICast_GetCastState( ent->s.number ); + + // if the current bounding box is invalid, then wait + VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); + VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); + trap_UnlinkEntity( ent ); + + numTouch = trap_EntitiesInBox( mins, maxs, touch, 10 ); + + // check that another client isn't inside us + if ( numTouch ) { + for ( i = 0; i < numTouch; i++ ) { + // RF, note we should only check against clients since zombies need to spawn inside func_explosive (so they dont clip into view after it explodes) + if ( g_entities[touch[i]].client && g_entities[touch[i]].r.contents == CONTENTS_BODY ) { + //if (g_entities[touch[i]].r.contents & MASK_PLAYERSOLID) + break; + } + } + if ( i == numTouch ) { + numTouch = 0; + } + } + + if ( numTouch ) { + // invalid location + cs->aiFlags |= AIFL_WAITINGTOSPAWN; + return; + } + + // RF, has to disable this so I could test some maps which have erroneously placed alertentity calls + //ent->AIScript_AlertEntity = NULL; + cs->aiFlags &= ~AIFL_WAITINGTOSPAWN; + ent->aiInactive = qfalse; + trap_LinkEntity( ent ); + + // trigger a spawn script event + AICast_ScriptEvent( AICast_GetCastState( ent->s.number ), "spawn", "" ); + // make it think so we update animations/angles + AICast_Think( ent->s.number, (float)FRAMETIME / 1000 ); + cs->lastThink = level.time; + AICast_UpdateInput( cs, FRAMETIME ); + trap_BotUserCommand( cs->bs->client, &( cs->bs->lastucmd ) ); +} + + +/* +================ +AICast_DelayedSpawnCast +================ +*/ +void AICast_DelayedSpawnCast( gentity_t *ent, int castType ) { + // ............................ + // head separation + if ( !ent->aiSkin ) { + G_SpawnString( "skin", "", &ent->aiSkin ); + } + if ( !ent->aihSkin ) { + G_SpawnString( "head", "default", &ent->aihSkin ); + } + G_SpawnInt( "aiteam", "-1", &ent->aiTeam ); + // ............................ + + + // we have to wait a bit before spawning it, otherwise the server will just delete it, since it's treated like a client + ent->think = AIChar_spawn; + ent->nextthink = level.time + FRAMETIME * 4; // have to wait more than 3 frames, since the server runs 3 frames before it clears all clients + + // we don't really want to start this character right away, but if we don't spawn the client + // now, if the game gets saved after the character spawns in, when it gets re-loaded, the client + // won't get spawned properly. + if ( ent->spawnflags & 1 ) { // TriggerSpawn + ent->AIScript_AlertEntity = AIChar_AIScript_AlertEntity; + ent->aiInactive = qtrue; + } + + // RF, had to move this down since some dev maps don't properly spawn the guys in, so we + // get a crash when transitioning between levels after they all spawn at once (overloading + // the client/server command buffers) + ent->nextthink += FRAMETIME * ( ( numSpawningCast + 1 ) / 3 ); // space them out a bit so we don't overflow the client + + ent->aiCharacter = castType; + numSpawningCast++; +} + +/* +================== +AICast_CastScriptThink +================== +*/ +void AICast_CastScriptThink( void ) { + int i; + gentity_t *ent; + cast_state_t *cs; + + for ( i = 0, ent = g_entities, cs = caststates; i < level.maxclients; i++, ent++, cs++ ) { + if ( !ent->inuse ) { + continue; + } + if ( !cs->bs ) { + continue; + } + AICast_ScriptRun( cs, qfalse ); + } +} + +/* +================== +AICast_CheckLoadGame + + at the start of a level, the game is either saved, or loaded + + we must wait for all AI to spawn themselves, and a real client to connect +================== +*/ +void AICast_CheckLoadGame( void ) { + char loading[4]; + gentity_t *ent; + qboolean ready; + + // have we already done the save or load? + if ( !saveGamePending ) { + return; + } + + // tell the cgame NOT to render the scene while we are waiting for things to settle + trap_Cvar_Set( "cg_norender", "1" ); + + trap_Cvar_VariableStringBuffer( "savegame_loading", loading, sizeof( loading ) ); + + // screen should be black if we are at this stage + trap_SetConfigstring( CS_SCREENFADE, va( "1 %i 1", level.time - 10 ) ); + reloading = qtrue; + + if ( strlen( loading ) > 0 && atoi( loading ) != 0 ) { + if ( !reloading && atoi( loading ) == 2 ) { + reloading = qtrue; // this gets reset at the Map_Restart() since the server unloads the game dll + } + + ready = qtrue; + if ( numSpawningCast != numcast ) { + ready = qfalse; + } else if ( !( ent = AICast_FindEntityForName( "player" ) ) ) { + ready = qfalse; + } else if ( !ent->client || ent->client->pers.connected != CON_CONNECTED ) { + ready = qfalse; + } + + if ( ready ) { + trap_Cvar_Set( "savegame_loading", "0" ); // in-case it aborts +// G_LoadGame( NULL ); // always load the "current" savegame + trap_Cvar_Set( "cg_norender", "0" ); + saveGamePending = qfalse; + + // wait for the clients to return from faded screen + // trap_SetConfigstring( CS_SCREENFADE, va("0 %i 1500", level.time + 500) ); + trap_SetConfigstring( CS_SCREENFADE, va( "0 %i 750", level.time + 500 ) ); + level.reloadPauseTime = level.time + 1100; + + AICast_CastScriptThink(); + } + } else { + + ready = qtrue; + if ( numSpawningCast != numcast ) { + ready = qfalse; + } else if ( !( ent = AICast_FindEntityForName( "player" ) ) ) { + ready = qfalse; + } else if ( !ent->client || ent->client->pers.connected != CON_CONNECTED ) { + ready = qfalse; + } + + // not loading a game, we must be in a new level, so look for some persistant data to read in, then save the game + if ( ready ) { +// G_LoadPersistant(); // make sure we save the game after we have brought across the items +// G_SaveGame( NULL ); + trap_Cvar_Set( "cg_norender", "0" ); + saveGamePending = qfalse; + + // wait for the clients to return from faded screen + // trap_SetConfigstring( CS_SCREENFADE, va("0 %i 1500", level.time + 500) ); + trap_SetConfigstring( CS_SCREENFADE, va( "0 %i 750", level.time + 500 ) ); + level.reloadPauseTime = level.time + 1100; + + AICast_CastScriptThink(); + } + } +} + +/* +=============== +AICast_SolidsInBBox +=============== +*/ +qboolean AICast_SolidsInBBox( vec3_t pos, vec3_t mins, vec3_t maxs, int entnum, int mask ) { + trace_t tr; + + if ( g_entities[entnum].health <= 0 ) { + return qfalse; + } + + trap_Trace( &tr, pos, mins, maxs, pos, entnum, mask ); + if ( tr.startsolid || tr.allsolid ) { + return qtrue; + } else { + return qfalse; + } +} + +/* +=============== +AICast_Activate +=============== +*/ +void AICast_Activate( int activatorNum, int entNum ) { + cast_state_t *cs; + + cs = AICast_GetCastState( entNum ); + if ( cs->activate ) { + cs->activate( entNum, activatorNum ); + } + + AICast_Printf( AICAST_PRT_DEBUG, "activated entity # %i\n", entNum ); +} + +/* +================ +AICast_NoFlameDamage +================ +*/ +qboolean AICast_NoFlameDamage( int entNum ) { + cast_state_t *cs; + + if ( entNum >= MAX_CLIENTS ) { + return qfalse; + } + + // DHM - Nerve :: Not in multiplayer + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + return qfalse; + } + + cs = AICast_GetCastState( entNum ); + return ( ( cs->aiFlags & AIFL_NO_FLAME_DAMAGE ) != 0 ); +} + +/* +=============== +G_SetAASBlockingEntity + + Adjusts routing so AI knows it can't move through this entity +=============== +*/ +void G_SetAASBlockingEntity( gentity_t *ent, qboolean blocking ) { + ent->AASblocking = blocking; + trap_AAS_SetAASBlockingEntity( ent->r.absmin, ent->r.absmax, blocking ); +} + +/* +=============== +AICast_AdjustIdealYawForMover +=============== +*/ +void AICast_AdjustIdealYawForMover( int entnum, float yaw ) { + cast_state_t *cs = AICast_GetCastState( entnum ); + // + cs->bs->ideal_viewangles[YAW] += yaw; +} + +/* +=============== +AICast_AgePlayTime +=============== +*/ +void AICast_AgePlayTime( int entnum ) { + cast_state_t *cs = AICast_GetCastState( entnum ); + // + if ( ( level.time - cs->lastLoadTime ) > 100 ) { + if ( ( level.time - cs->lastLoadTime ) < 1000 ) { + cs->totalPlayTime += level.time - cs->lastLoadTime; + } + // + cs->lastLoadTime = level.time; + } +} + +/* +=============== +AICast_NoReload +=============== +*/ +int AICast_NoReload( int entnum ) { + cast_state_t *cs = AICast_GetCastState( entnum ); + // + return ( ( cs->aiFlags & AIFL_NO_RELOAD ) != 0 ); +} diff --git a/src/game/ai_cast.h b/src/game/ai_cast.h new file mode 100644 index 0000000..8098d9a --- /dev/null +++ b/src/game/ai_cast.h @@ -0,0 +1,705 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast.h +// Function: Wolfenstein AI Character Routines +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../botai/ai_main.h" // just so we can use the structures +#include "../botai/ai_dmq3.h" // just so we can use the structures + +#include "ai_cast_fight.h" + +// +// constants/defines +// +#define MAX_AIFUNCS 15 // if we go over this per frame, likely to have an infinite loop +// +#define SIGHT_PER_SEC 100 // do this many sight iterations per second +// +// Cast AI specific action flags (get translated into ucmd's +#define CASTACTION_WALK 1 +// +#define MAX_SCRIPT_ACCUM_BUFFERS 8 +// +#define AICAST_PRT_ALWAYS 0 +#define AICAST_PRT_DEBUG 1 +// +#define DEBUG_FOLLOW_DIST 96 +// +#define MAX_LEADER_DIST 256 +// +#define AASWORLD_STANDARD 0 +#define AASWORLD_LARGE 1 +// +// use this for returning the length of an anim +#define ANIMLENGTH( frames,fps ) ( ( frames * 1000 ) / fps ) +// +#define AICAST_TFL_DEFAULT TFL_DEFAULT & ~( TFL_JUMPPAD | TFL_ROCKETJUMP | TFL_BFGJUMP | TFL_GRAPPLEHOOK | TFL_DOUBLEJUMP | TFL_RAMPJUMP | TFL_STRAFEJUMP | TFL_LAVA ) //----(SA) modified since slime is no longer deadly +//#define AICAST_TFL_DEFAULT TFL_DEFAULT & ~(TFL_JUMPPAD|TFL_ROCKETJUMP|TFL_BFGJUMP|TFL_GRAPPLEHOOK|TFL_DOUBLEJUMP|TFL_RAMPJUMP|TFL_STRAFEJUMP|TFL_SLIME|TFL_LAVA) +// +// AI flags +#define AIFL_CATCH_GRENADE 0x1 +#define AIFL_NO_FLAME_DAMAGE 0x2 +#define AIFL_FIRED 0x4 +#define AIFL_LAND_ANIM_PLAYED 0x8 +#define AIFL_ROLL_ANIM 0x10 +#define AIFL_FLIP_ANIM 0x20 +#define AIFL_STAND_IDLE2 0x40 +#define AIFL_NOAVOID 0x80 // if set, this AI will ignore requests for us to move out the way +#define AIFL_NOPAIN 0x100 // don't stop for pain anims +#define AIFL_WALKFORWARD 0x200 // only walk forward +#define AIFL_DENYACTION 0x400 // used by scripting to prevent dynamic code from executing certain behaviour +#define AIFL_VIEWLOCKED 0x800 // prevent anything outside movement routines from changing view +#define AIFL_CORPSESIGHTING 0x1000 // share information through friendly corpses +#define AIFL_WAITINGTOSPAWN 0x2000 // waiting until space is clear to spawn in to the game +#define AIFL_JUST_SPAWNED 0x4000 +#define AIFL_NO_RELOAD 0x8000 // this character doesn't need to reload +#define AIFL_TALKING 0x10000 +#define AIFL_NO_HEADLOOK 0x20000 +#define AIFL_ATTACK_CROUCH 0x40000 +#define AIFL_MISCFLAG1 0x80000 // used various bits of code, temporarily +#define AIFL_MISCFLAG2 0x100000 // used various bits of code, temporarily +#define AIFL_ZOOMING 0x200000 +#define AIFL_NO_HEADSHOT_DMG 0x400000 +#define AIFL_DIVE_ANIM 0x800000 // able to dive to cover +#define AIFL_NO_TESLA_DAMAGE 0x1000000 +// +// predict events +typedef enum +{ + PREDICTSTOP_NONE, + PREDICTSTOP_HITENT, + PREDICTSTOP_HITCLIENT +} predictStop_t; +// +typedef enum +{ + AITEAM_NAZI, + AITEAM_ALLIES, + AITEAM_MONSTER, + AITEAM_SPARE1, + AITEAM_SPARE2, + AITEAM_SPARE3, + AITEAM_SPARE4, + AITEAM_NEUTRAL +} AITeam_t; +// +typedef enum +{ + BBOX_SMALL, + BBOX_LARGE +} BBoxType_t; +// +// attributes +// !!! NOTE: any changes to this must be reflected in the attributeStrings in ai_cast.c +typedef enum +{ + RUNNING_SPEED, // max = 300 (running speed) + WALKING_SPEED, // max = 300 (walking speed) + CROUCHING_SPEED, // max = 300 (crouching speed) + FOV, // max = 360 (field of view) + YAW_SPEED, // max = 300 (yaw speed, so we can make zombie's turn slowly) + LEADER, // max = 1.0 (ability to lead an AI squadron) + AIM_SKILL, // max = 1.0 (skill while aiming) + AIM_ACCURACY, // max = 1.0 (accuracy of firing) + ATTACK_SKILL, // max = 1.0 (ability to attack and do other things, like retreat) + REACTION_TIME, // max = 1.0 (upon seeing enemy, wait this long before reaction) + ATTACK_CROUCH, // max = 1.0 (likely to crouch while firing) + IDLE_CROUCH, // max = 1.0 (likely to crouch while idling) + AGGRESSION, // max = 1.0 (willingness to fight till the death) + TACTICAL, // max = 1.0 (ability to use strategy to their advantage, also behaviour whilst hunting enemy, more likely to creep around) + CAMPER, // max = 1.0 (set this to make them stay in the spot they are spawned) + ALERTNESS, // max = 1.0 (ability to notice enemies at long range) + STARTING_HEALTH, // MAX = 999 (starting health) + HEARING_SCALE, // max = 999 (multiply default hearing ranges by this) + INNER_DETECTION_RADIUS, // default = 512 (enemies within this range trigger immediate combat mode + PAIN_THRESHOLD_SCALE, // default = 1.0 + + AICAST_MAX_ATTRIBUTES + +} castAttributes_t; +// +typedef struct { + char *name; + float attributes[AICAST_MAX_ATTRIBUTES]; + char *sightSoundScript; + char *attackSoundScript; + char *ordersSoundScript; + char *deathSoundScript; + char *quietDeathSoundScript; //----(SA) added for silent deaths (sniper/knife) + char *painSoundScript; + + char *staySoundScript; + char *followSoundScript; + char *ordersDenySoundScript; + + int aiTeam; + char *skin; + int weapons[8]; + int bboxType; + vec2_t crouchstandZ; + int aiFlags; + + char *( *aifuncAttack1 )( struct cast_state_s *cs ); //use this battle aifunc for monster_attack1 + char *( *aifuncAttack2 )( struct cast_state_s *cs ); //use this battle aifunc for monster_attack2 + char *( *aifuncAttack3 )( struct cast_state_s *cs ); //use this battle aifunc for monster_attack2 + + char *loopingSound; // play this sound constantly while alive + + aistateEnum_t aiState; +} AICharacterDefaults_t; + +// +// script flags +#define SFL_NOCHANGEWEAPON 0x1 +#define SFL_NOAIDAMAGE 0x2 +#define SFL_FRIENDLYSIGHTCORPSE_TRIGGERED 0x4 +#define SFL_WAITING_RESTORE 0x8 +#define SFL_FIRST_CALL 0x10 +// +// attributes strings (used for per-entity attribute definitions) +// NOTE: these must match the attributes above) +extern char *castAttributeStrings[]; +extern AICharacterDefaults_t aiDefaults[NUM_CHARACTERS]; +// +// structure defines +// +#define AIVIS_ENEMY 1 +#define AIVIS_INSPECTED 2 // we have inspected them once already +#define AIVIS_INSPECT 4 // we should inspect them when we get a chance +#define AIVIS_PROCESS_SIGHTING 8 // so we know if we have or haven't processed the sighting since they were last seen +// +// share range +#define AIVIS_SHARE_RANGE 170 // if we are within this range of a friendly, share their vis info +// +#define MAX_CHASE_MARKERS 3 +#define CHASE_MARKER_INTERVAL 1000 +// +#define COMBAT_TIMEOUT 8000 +// sight info +typedef struct +{ + int flags; + int lastcheck_timestamp; + int real_visible_timestamp; + int real_update_timestamp; + int real_notvisible_timestamp; + int visible_timestamp; // time we last recorded a sighting + vec3_t visible_pos; // position we last knew of them being at (could be hearing, etc) + vec3_t real_visible_pos; // position we last physically saw them + vec3_t visible_vel; // velocity during last sighting + int notvisible_timestamp; // last time we didn't see the entity (used for reaction delay) + vec3_t chase_marker[MAX_CHASE_MARKERS]; + int chase_marker_count; + int lastcheck_health; +} cast_visibility_t; +// +// starting weapons, ammo, etc +typedef struct +{ + int startingWeapons[MAX_WEAPONS / ( sizeof( int ) * 8 )]; + int startingAmmo[MAX_WEAPONS]; // starting ammo values for each weapon (set to 999 for unlimited) +} cast_weapon_info_t; +// +// scripting +typedef struct +{ + char *actionString; + qboolean ( *actionFunc )( struct cast_state_s *cs, char *params ); +} cast_script_stack_action_t; +// +typedef struct +{ + // + // set during script parsing + cast_script_stack_action_t *action; // points to an action to perform + char *params; +} cast_script_stack_item_t; +// +#define AICAST_MAX_SCRIPT_STACK_ITEMS 64 +// +typedef struct +{ + cast_script_stack_item_t items[AICAST_MAX_SCRIPT_STACK_ITEMS]; + int numItems; +} cast_script_stack_t; +// +typedef struct +{ + int eventNum; // index in scriptEvents[] + char *params; // trigger targetname, etc + cast_script_stack_t stack; +} cast_script_event_t; +// +typedef struct +{ + char *eventStr; + qboolean ( *eventMatch )( cast_script_event_t *event, char *eventParm ); +} cast_script_event_define_t; +// +typedef struct +{ + int castScriptStackHead, castScriptStackChangeTime; + int castScriptEventIndex; // current event containing stack of actions to perform + // scripting system AI variables (set by scripting system, used directly by AI) + int scriptId; // incremented each time the script changes + int scriptFlags; + int scriptNoAttackTime; + int scriptNoMoveTime; + int scriptGotoEnt; // just goto them, then resume normal behaviour (don't follow) + int scriptGotoId; + vec3_t scriptWaitPos; + int scriptWaitMovetime; + vec3_t scriptWaitHidePos; + int scriptWaitHideTime; + int scriptNoSightTime; + int scriptAttackEnt; // we should always attack this AI if they are alive +} cast_script_status_t; +// +typedef struct +{ + aistateEnum_t currentState; + aistateEnum_t nextState; + int nextStateTimer; // time left until "newState" is reached +} aistate_t; +// +typedef enum +{ + MS_DEFAULT, + MS_WALK, + MS_RUN, + MS_CROUCH +} movestate_t; +// +typedef enum +{ + MSTYPE_NONE, + MSTYPE_TEMPORARY, + MSTYPE_PERMANENT +} movestateType_t; +// +// +typedef struct aicast_checkattack_cache_s +{ + int enemy; + qboolean allowHitWorld; + int time; + int weapon; + qboolean result; +} aicast_checkattack_cache_t; +// +// -------------------------------------------------------------------------------- +// the main cast structure +typedef struct cast_state_s +{ + bot_state_t *bs; + int entityNum; + + int aasWorldIndex; // set this according to our bounding box type + + // Cast specific information follows. Add to this as needed, this way the bot_state_t structure + // remains untouched. + + int aiCharacter; + int aiFlags; + int lastThink; // time they last thinked, so we can vary the think times + int actionFlags; // cast AI specific movement flags + int lastPain, lastPainDamage; + int travelflags; + int thinkFuncChangeTime; + + aistateEnum_t aiState; + movestate_t movestate; // walk, run, crouch etc (can be specified in a script) + movestateType_t movestateType; // temporary, permanent, etc + + float attributes[AICAST_MAX_ATTRIBUTES]; + // these define the abilities of each cast AI + + // scripting system + int numCastScriptEvents; + cast_script_event_t *castScriptEvents; // contains a list of actions to perform for each event type + cast_script_status_t castScriptStatus; // current status of scripting + cast_script_status_t castScriptStatusCurrent; // scripting status to use for backups + cast_script_status_t castScriptStatusBackup; // perm backup of status of scripting, only used by backup and restore commands + int scriptCallIndex; // inc'd each time a script is called + int scriptAnimTime, scriptAnimNum; // last time an anim was played using scripting + // the accumulation buffer + int scriptAccumBuffer[MAX_SCRIPT_ACCUM_BUFFERS]; + + // + cast_weapon_info_t *weaponInfo; // FIXME: make this a list, so they can have multiple weapons? + cast_visibility_t vislist[MAX_CLIENTS]; // array of all other client entities, allocated at level start-up + int weaponFireTimes[MAX_WEAPONS]; + + char *( *aifunc )( struct cast_state_s *cs ); //current AI function + char *( *oldAifunc )( struct cast_state_s *cs ); // just so we can restore the last aiFunc if required + + char *( *aifuncAttack1 )( struct cast_state_s *cs ); //use this battle aifunc for monster_attack1 + char *( *aifuncAttack2 )( struct cast_state_s *cs ); //use this battle aifunc for monster_attack2 + char *( *aifuncAttack3 )( struct cast_state_s *cs ); //use this battle aifunc for monster_attack2 + + void ( *painfunc )( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ); + void ( *deathfunc )( gentity_t *ent, gentity_t *attacker, int damage, int mod ); //----(SA) added mod + void ( *sightfunc )( gentity_t *ent, gentity_t *other, int lastSight ); + + //int (*getDeathAnim)(gentity_t *ent, gentity_t *attacker, int damage); + void ( *sightEnemy )( gentity_t *ent, gentity_t *other ); + void ( *sightFriend )( gentity_t *ent, gentity_t *other ); + + void ( *activate )( int entNum, int activatorNum ); + + // + // !!! NOTE: make sure any entityNum type variables get initialized + // to -1 in AICast_CreateCharacter(), or they'll be defaulting to + // the player (index 0) + // + + // goal/AI stuff + + int followEntity; + float followDist; + qboolean followIsGoto; // we are really just going to the entity, but should wait until scripting tells us we can stop + int followTime; // if this runs out, the scripting has probably been interupted + qboolean followSlowApproach; + + int leaderNum; // entnum of player we are following + + float speedScale; // so we can vary movement speed + + float combatGoalTime; + vec3_t combatGoalOrigin; + + int lastGetHidePos; + int startAttackCount; // incremented each time we start a standing attack + // used to make sure we only find a combat spot once per attack + int combatSpotAttackCount; + int combatSpotDelayTime; + int startBattleChaseTime; + + int blockedTime; // time they were last blocked by a solid entity + int obstructingTime; // time that we should move so we are not obstructing someone else + vec3_t obstructingPos; + + int blockedAvoidTime; + float blockedAvoidYaw; + + int deathTime; + int rebirthTime, revivingTime; + + // battle values + int enemyHeight; + int enemyDist; + + vec3_t takeCoverPos, takeCoverEnemyPos; + int takeCoverTime; + + int attackSpotTime; + + int triggerReleaseTime; + + int lastWeaponFired; // set each time a weapon is fired. used to detect when a weapon has been fired from within scripting + vec3_t lastWeaponFiredPos; + int lastWeaponFiredWeaponNum; + + // idle behaviour stuff + int lastEnemy, nextIdleAngleChange; + float idleYawChange, idleYaw; + + qboolean crouchHideFlag; + + int doorMarker, doorEntNum; + + // Rafael + int attackSNDtime; + int attacksnd; + int painSoundTime; + int firstSightTime; + qboolean secondDeadTime; + // done + + int startGrenadeFlushTime; + int lockViewAnglesTime; + int grenadeFlushEndTime; + int grenadeFlushFiring; + + int dangerEntity; + int dangerEntityValidTime; // dangerEntity is valid until this time expires + vec3_t dangerEntityPos; // dangerEntity is predicted to end up here + int dangerEntityTimestamp; // time this danger was recorded + float dangerDist; + + int mountedEntity; // mg42, etc that we have mounted + int inspectBodyTime; + vec3_t startOrigin; + + int damageQuota; + int damageQuotaTime; + + int dangerLastGetAvoid; + int lastAvoid; + + int doorMarkerTime, doorMarkerNum, doorMarkerDoor; + + int pauseTime; // absolutely don't move move while this is > level.time + + aicast_checkattack_cache_t checkAttackCache; + + int secretsFound; + + int attempts; + + qboolean grenadeGrabFlag; // if this is set, we need to play the anim before we can grab it + + vec3_t lastMoveToPosGoalOrg; // if this changes, we should reset the Bot Avoid Reach + + int noAttackTime; // used by dynamic AI to stop attacking for set time + + int lastRollMove; + int lastFlipMove; + + vec3_t stimFlyAttackPos; + + int lastDodgeRoll; // last time we rolled to get out of our enemies direct aim + int battleRollTime; + + vec3_t viewlock_viewangles; + int grenadeKickWeapon; + + int animHitCount; // for stepping through the frames on which to inflict damage + + int totalPlayTime, lastLoadTime; + + int queryStartTime, queryCountValidTime, queryCount, queryAlertSightTime; + + int lastScriptSound; + + int inspectNum; + + int scriptPauseTime; + + int bulletImpactTime; // last time we heard/saw a bullet impact + int bulletImpactIgnoreTime; + vec3_t bulletImpactStart, bulletImpactEnd; + + int audibleEventTime; + vec3_t audibleEventOrg; + int audibleEventEnt; + + int battleChaseMarker, battleChaseMarkerDir; + + int lastBattleHunted; // last time an enemy decided to hunt us + int battleHuntPauseTime, battleHuntViewTime; + + int lastAttackCrouch; + + int lastMoveThink; // last time we ran our ClientThink() + + int numEnemies; // last count of enemies that are currently pursuing us + + // ------------------------------------------------------------------------------------------- + // if working on a post release patch, new variables should ONLY be inserted after this point + // ------------------------------------------------------------------------------------------- + +} cast_state_t; +// +#define CSFOFS( x ) ( (int)&( ( (cast_state_t *)0 )->x ) ) +// +typedef struct aicast_predictmove_s +{ + vec3_t endpos; //position at the end of movement prediction + vec3_t velocity; //velocity at the end of movement prediction + int presencetype; //presence type at end of movement prediction + int stopevent; //event that made the prediction stop + float time; //time predicted ahead + int frames; //number of frames predicted ahead + int numtouch; + int touchents[MAXTOUCH]; + int groundEntityNum; +} aicast_predictmove_t; +// +// variables/globals +// +//cast states +extern cast_state_t *caststates; +//number of characters +extern int numcast; +// +// minimum time between thinks (maximum is double this) +extern int aicast_thinktime; +// maximum number of character thinks at once +extern int aicast_maxthink; +// maximum clients +extern int aicast_maxclients; +// skill scale +extern float aicast_skillscale; +// +// cvar to enable aicast debugging, set higher for more levels of debugging +extern vmCvar_t aicast_debug; +extern vmCvar_t aicast_debugname; +extern vmCvar_t aicast_scripts; +// +extern int numSecrets; +// +// procedure defines +// +// ai_cast.c +void AIChar_SetBBox( gentity_t *ent, cast_state_t *cs ); +void AICast_Printf( int type, const char *fmt, ... ); +gentity_t *AICast_CreateCharacter( gentity_t *ent, float *attributes, cast_weapon_info_t *weaponInfo, char *castname, char *model, char *head, char *sex, char *color, char *handicap ); +void AICast_Init( void ); +void AICast_DelayedSpawnCast( gentity_t *ent, int castType ); +qboolean AICast_SolidsInBBox( vec3_t pos, vec3_t mins, vec3_t maxs, int entnum, int mask ); +void AICast_CheckLevelAttributes( cast_state_t *cs, gentity_t *ent, char **ppStr ); +// +// ai_cast_sight.c +void AICast_SightUpdate( int numchecks ); +qboolean AICast_VisibleFromPos( vec3_t srcpos, int srcnum, + vec3_t destpos, int destnum, qboolean updateVisPos ); +void AICast_UpdateVisibility( gentity_t *srcent, gentity_t *destent, qboolean shareVis, qboolean directview ); +qboolean AICast_CheckVisibility( gentity_t *srcent, gentity_t *destent ); +void AICast_SightSoundEvent( cast_state_t *cs, float range ); +// +// ai_cast_debug.c +void AICast_DBG_InitAIFuncs( void ); +void AICast_DBG_AddAIFunc( cast_state_t *cs, char *funcname ); +void AICast_DBG_ListAIFuncs( cast_state_t *cs, int numprint ); +void AICast_DBG_RouteTable_f( vec3_t org, char *param ); +int Sys_MilliSeconds( void ); +void AICast_DebugFrame( cast_state_t *cs ); +// +// ai_cast_funcs.c +bot_moveresult_t *AICast_MoveToPos( cast_state_t *cs, vec3_t pos, int entnum ); +float AICast_SpeedScaleForDistance( cast_state_t *cs, float startdist, float idealDist ); +char *AIFunc_DefaultStart( cast_state_t *cs ); +char *AIFunc_IdleStart( cast_state_t *cs ); +char *AIFunc_ChaseGoalIdleStart( cast_state_t *cs, int entitynum, float reachdist ); +char *AIFunc_ChaseGoalStart( cast_state_t *cs, int entitynum, float reachdist, qboolean slowApproach ); +char *AIFunc_BattleChaseStart( cast_state_t *cs ); +char *AIFunc_BattleStart( cast_state_t *cs ); +char *AIFunc_DoorMarkerStart( cast_state_t *cs, int doornum, int markernum ); +char *AIFunc_DoorMarker( cast_state_t *cs ); +char *AIFunc_BattleTakeCoverStart( cast_state_t *cs ); +char *AIFunc_GrenadeFlushStart( cast_state_t *cs ); +char *AIFunc_AvoidDangerStart( cast_state_t *cs ); +char *AIFunc_BattleMG42Start( cast_state_t *cs ); +char *AIFunc_InspectBodyStart( cast_state_t *cs ); +char *AIFunc_GrenadeKickStart( cast_state_t *cs ); +char *AIFunc_InspectFriendlyStart( cast_state_t *cs, int entnum ); +char *AIFunc_InspectBulletImpactStart( cast_state_t *cs ); +char *AIFunc_InspectAudibleEventStart( cast_state_t *cs, int entnum ); +char *AIFunc_BattleAmbushStart( cast_state_t *cs ); +char *AIFunc_BattleHuntStart( cast_state_t *cs ); +// +// ai_cast_func_attack.c +char *AIFunc_ZombieFlameAttackStart( cast_state_t *cs ); +char *AIFunc_ZombieAttack2Start( cast_state_t *cs ); +char *AIFunc_LoperAttack1Start( cast_state_t *cs ); +char *AIFunc_LoperAttack2Start( cast_state_t *cs ); +char *AIFunc_LoperAttack3Start( cast_state_t *cs ); +char *AIFunc_StimSoldierAttack1Start( cast_state_t *cs ); +char *AIFunc_StimSoldierAttack2Start( cast_state_t *cs ); +char *AIFunc_BlackGuardAttack1Start( cast_state_t *cs ); +char *AIFunc_RejectAttack1Start( cast_state_t *cs ); //----(SA) +char *AIFunc_WarriorZombieMeleeStart( cast_state_t *cs ); +char *AIFunc_WarriorZombieSightStart( cast_state_t *cs ); +char *AIFunc_WarriorZombieDefenseStart( cast_state_t *cs ); +// +// ai_cast_func_boss1.c +char *AIFunc_FZombie_IdleStart( cast_state_t *cs ); +char *AIFunc_FZombie_HandLightningAttackStart( cast_state_t *cs ); +char *AIFunc_FZombie_LightningAttackStart( cast_state_t *cs ); +char *AIFunc_Helga_IdleStart( cast_state_t *cs ); +char *AIFunc_FlameZombie_PortalStart( cast_state_t *cs ); +// +// ai_cast_fight.c +qboolean AICast_StateChange( cast_state_t *cs, aistateEnum_t newaistate ); +void AICast_WeaponSway( cast_state_t *cs, vec3_t ofs ); +int AICast_ScanForEnemies( cast_state_t *cs, int *enemies ); +void AICast_UpdateBattleInventory( cast_state_t *cs, int enemy ); +float AICast_Aggression( cast_state_t *cs ); +int AICast_WantsToChase( cast_state_t *cs ); +int AICast_WantsToTakeCover( cast_state_t *cs, qboolean attacking ); +qboolean AICast_EntityVisible( cast_state_t *cs, int enemynum, qboolean directview ); +bot_moveresult_t AICast_CombatMove( cast_state_t *cs, int tfl ); +qboolean AICast_AimAtEnemy( cast_state_t *cs ); +qboolean AICast_CheckAttackAtPos( int entnum, int enemy, vec3_t pos, qboolean ducking, qboolean allowHitWorld ); +qboolean AICast_CheckAttack( cast_state_t *cs, int enemy, qboolean allowHitWorld ); +void AICast_ProcessAttack( cast_state_t *cs ); +void AICast_ChooseWeapon( cast_state_t *cs, qboolean battleFunc ); +qboolean AICast_GetTakeCoverPos( cast_state_t *cs, int enemyNum, vec3_t enemyPos, vec3_t returnPos ); +qboolean AICast_CanMoveWhileFiringWeapon( int weaponnum ); +float AICast_GetWeaponSoundRange( int weapon ); +qboolean AICast_StopAndAttack( cast_state_t *cs ); +qboolean AICast_WantToRetreat( cast_state_t *cs ); +int AICast_SafeMissileFire( gentity_t *ent, int duration, int enemyNum, vec3_t enemyPos, int selfNum, vec3_t endPos ); +void AIChar_AttackSound( cast_state_t *cs ); +qboolean AICast_GotEnoughAmmoForWeapon( cast_state_t *cs, int weapon ); +qboolean AICast_HostileEnemy( cast_state_t *cs, int enemynum ); +qboolean AICast_QueryEnemy( cast_state_t *cs, int enemynum ); +void AICast_AudibleEvent( int srcnum, vec3_t pos, float range ); + +// +// ai_cast_events.c +void AICast_Pain( gentity_t *targ, gentity_t *attacker, int damage, vec3_t point ); +void AICast_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ); +void AICast_Sight( gentity_t *ent, gentity_t *other, int lastSight ); +void AICast_EndChase( cast_state_t *cs ); +void AICast_ProcessActivate( int entNum, int activatorNum ); +// +// ai_cast_think.c +void AICast_Think( int client, float thinktime ); +void AICast_UpdateInput( cast_state_t *cs, int time ); +void AICast_InputToUserCommand( cast_state_t * cs, bot_input_t * bi, usercmd_t * ucmd, int delta_angles[3] ); +void AICast_PredictMovement( cast_state_t *cs, int numframes, float frametime, aicast_predictmove_t *move, usercmd_t *ucmd, int checkHitEnt ); +void AICast_Blocked( cast_state_t *cs, bot_moveresult_t *moveresult, int activate, bot_goal_t *goal ); +qboolean AICast_RequestCrouchAttack( cast_state_t *cs, vec3_t org, float time ); +qboolean AICast_GetAvoid( cast_state_t *cs, bot_goal_t *goal, vec3_t outpos, qboolean reverse, int blockEnt ); +void AICast_QueryThink( cast_state_t *cs ); +void AICast_DeadClipWalls( cast_state_t *cs ); +// +// ai_cast_script.c +qboolean AICast_ScriptRun( cast_state_t *cs, qboolean force ); +// +// ai_cast_soldier.c +void AIChar_spawn( gentity_t *ent ); +// +// other/external defines +void BotCheckAir( bot_state_t *bs ); +void BotUpdateInput( bot_state_t *bs, int time ); +float AngleDifference( float ang1, float ang2 ); +float BotChangeViewAngle( float angle, float ideal_angle, float speed ); +void BotInputToUserCommand( bot_input_t * bi, usercmd_t * ucmd, int delta_angles[3] ); +void GibEntity( gentity_t *self, int killer ); +void GibHead( gentity_t *self, int killer ); +// +extern bot_state_t *botstates[MAX_CLIENTS]; diff --git a/src/game/ai_cast_characters.c b/src/game/ai_cast_characters.c new file mode 100644 index 0000000..e24308f --- /dev/null +++ b/src/game/ai_cast_characters.c @@ -0,0 +1,1988 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_characters.c +// Function: Wolfenstein AI Characters +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "g_local.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +//--------------------------------------------------------------------------- +// Character specific attributes (defaults, these can be altered in the editor (TODO!)) +AICharacterDefaults_t aiDefaults[NUM_CHARACTERS] = { + //AICHAR_NONE + {0}, + //AICHAR_SOLDIER + { + "Soldier", + { + 220, // running speed + 90, // walking speed + 80, // crouching speed + 90, // Field of View + 200, // Yaw Speed // RF change + 0.0, // leader + 0.5, // aim skill + 0.5, // aim accuracy + 0.75, // attack skill + 0.5, // reaction time + 0.4, // attack crouch + 0.0, // idle crouch + 0.5, // aggression + 0.8, // tactical + 0.0, // camper + 16000, // alertness + 100, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "infantrySightPlayer", + "infantryAttackPlayer", + "infantryOrders", + "infantryDeath", + "infantrySilentDeath", //----(SA) added + "infantryPain", + "infantryStay", // stay - you're told to stay put + "infantryFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "infantryOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, // team + "infantryss/default", // default model/skin + {WP_MP40,WP_GRENADE_LAUNCHER}, // starting weapons + BBOX_SMALL, {32,48}, // bbox, crouch/stand height + AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2, // flags + NULL, NULL, NULL, // special attack routine + NULL, // looping sound + AISTATE_RELAXED + }, + //AICHAR_AMERICAN + { + "American", + { + 220, // running speed + 90, // walking speed + 80, // crouching speed + 90, // Field of View + 200, // Yaw Speed // RF change + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.5, // reaction time + 0.3, // attack crouch + 0.0, // idle crouch + 0.5, // aggression + 0.8, // tactical + 0.0, // camper + 16000, // alertness + 100, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "americanSightPlayer", + "americanAttackPlayer", + "americanOrders", + "americanDeath", + "americanDeath", //----(SA) added + "americanPain", + "americanStay", // stay - you're told to stay put + "americanFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "americanOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_ALLIES, + "american/default", + {WP_THOMPSON,WP_GRENADE_PINEAPPLE}, + BBOX_SMALL, {32,48}, + AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2, + NULL, NULL, NULL, + NULL, + AISTATE_RELAXED + }, + //AICHAR_ZOMBIE + { + "Zombie", + { + 200, // running speed //----(SA) DK requested change + 60, // walking speed //----(SA) DK requested change + 80, // crouching speed + 90, // Field of View + 350, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.1, // reaction time + 0.0, // attack crouch + 0.0, // idle crouch + 1.0, // aggression + 0.0, // tactical + 0.0, // camper + 16000, // alertness + 180, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "zombieSightPlayer", + "zombieAttackPlayer", + "zombieOrders", + "zombieDeath", + "zombieDeath", //----(SA) added + "zombiePain", + "zombieStay", // stay - you're told to stay put + "zombieFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "zombieOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_MONSTER, + "zombie/default", + {WP_GAUNTLET,WP_MONSTER_ATTACK2}, + BBOX_SMALL, {32,48}, + /*AIFL_NOPAIN|*/ AIFL_WALKFORWARD | AIFL_NO_RELOAD, + AIFunc_ZombieFlameAttackStart, AIFunc_ZombieAttack2Start, NULL, + NULL, + AISTATE_ALERT + }, + +//----(SA) added + //AICHAR_WARZOMBIE + { + "WarriorZombie", + { + 250, // running speed (SA) upped from 200->250 per Mike/DK + 60, // walking speed + 80, // crouching speed + 90, // Field of View + 350, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.1, // reaction time + 0.0, // attack crouch + 0.0, // idle crouch + 1.0, // aggression + 0.0, // tactical + 0.0, // camper + 16000, // alertness + 180, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "warzombieSightPlayer", + "warzombieAttackPlayer", + "warzombieOrders", + "warzombieDeath", + "warzombieDeath", //----(SA) added + "warzombiePain", + "sound/weapons/melee/fstatck.wav", // stay - you're told to stay put + "warzombieFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "warzombieOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_MONSTER, + "warrior/default", + {WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3}, + BBOX_SMALL, {10,48}, // very low defense position + AIFL_NO_RELOAD, + AIFunc_WarriorZombieMeleeStart, /*AIFunc_WarriorZombieSightStart*/ NULL, AIFunc_WarriorZombieDefenseStart, + NULL, + AISTATE_ALERT + }, +//----(SA) end + + //AICHAR_FEMZOMBIE + { + "FemZombie", + { + 90, // running speed + 30, // walking speed + 80, // crouching speed + 90, // Field of View + 200, // Yaw Speed // RF change + 0.0, // leader + 0.5, // aim skill + 0.5, // aim accuracy + 0.75, // attack skill + 0.8, // reaction time + 0.0, // attack crouch + 0.0, // idle crouch + 1.0, // aggression + 0.8, // tactical + 0.0, // camper + 16000, // alertness + 180, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "zombieFemSightPlayer", + "zombieFemAttackPlayer", + "zombieFemOrders", + "zombieFemDeath", + "zombieFemDeath", //----(SA) added + "zombieFemPain", + "zombieFemStay", // stay - you're told to stay put + "zombieFemFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "zombieFemOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_MONSTER, // team + "femzombie/default", // default model/skin + {WP_GAUNTLET,WP_MONSTER_ATTACK2}, // starting weapons + BBOX_SMALL, {32,48}, // bbox, crouch/stand height + AIFL_NO_FLAME_DAMAGE | AIFL_STAND_IDLE2 | AIFL_WALKFORWARD | AIFL_NO_RELOAD, // flags + AIFunc_FZombie_LightningAttackStart, AIFunc_FZombie_HandLightningAttackStart, NULL, // special attack routine + NULL, + AISTATE_ALERT + }, + + +//----(SA) end + + //AICHAR_UNDEAD + { + "Undead", + { + 70, // running speed + 70, // walking speed + 80, // crouching speed + 90, // Field of View + 100, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.8, // reaction time + 0.0, // attack crouch + 0.0, // idle crouch + 1.0, // aggression + 0.0, // tactical + 0.0, // camper + 16000, // alertness + 100, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "undeadSightPlayer", + "undeadAttackPlayer", + "undeadOrders", + "undeadDeath", + "undeadDeath", //----(SA) added + "undeadPain", + "undeadStay", // stay - you're told to stay put + "undeadFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "undeadOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_MONSTER, + "undead/default", + {WP_GAUNTLET}, + BBOX_SMALL, {32,48}, + AIFL_WALKFORWARD | AIFL_NO_RELOAD, + NULL, NULL, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_VENOM + { + "Venom", + { + 110, // running speed + 100, // walking speed + 80, // crouching speed + 90, // Field of View + 200, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.5, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 0.9, // aggression + 0.2, // tactical + 0.0, // camper + 16000, // alertness + 240, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "venomSightPlayer", + "venomAttackPlayer", + "venomOrders", + "venomDeath", + "venomDeath", //----(SA) added + "venomPain", + "venomStay", // stay - you're told to stay put + "venomFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "venomOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "venom/default", + {WP_VENOM,WP_VENOM_FULL,WP_FLAMETHROWER}, + BBOX_SMALL, {32,48}, + AIFL_NO_FLAME_DAMAGE | AIFL_WALKFORWARD | AIFL_NO_RELOAD | AIFL_NO_HEADSHOT_DMG, + NULL, NULL, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_LOPER + { + "Loper", + { + 220, // running speed + 70, // walking speed + 220, // crouching speed + 90, // Field of View + 200, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.8, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 1.0, // aggression + 0.0, // tactical + 0.0, // camper + 16000, // alertness + 500, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "loperSightPlayer", + "loperAttackPlayer", + "loperOrders", + "loperDeath", + "loperDeath", //----(SA) added + "loperPain", + "loperStay", // stay - you're told to stay put + "loperFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "loperOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_MONSTER, + "loper/default", + {WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3}, + BBOX_LARGE, {48,48}, // large is for wide characters + AIFL_NO_RELOAD, + AIFunc_LoperAttack1Start, AIFunc_LoperAttack2Start, AIFunc_LoperAttack3Start, + "sound/world/electloop.wav", + AISTATE_ALERT + }, + //AICHAR_SEALOPER + + //----(SA) no 'correct' values/weapons/etc. have been set. only adding character basics + { + "SeaLoper", + { + 220, // running speed + 70, // walking speed + 220, // crouching speed + 90, // Field of View + 200, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.8, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 1.0, // aggression + 0.0, // tactical + 0.0, // camper + 16000, // alertness + 500, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "sealoperSightPlayer", + "sealoperAttackPlayer", + "sealoperOrders", + "sealoperDeath", + "sealoperDeath", //----(SA) added + "sealoperPain", + "sealoperStay", // stay - you're told to stay put + "sealoperFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "sealoperOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_MONSTER, + "sealoper/default", + + {WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3}, + BBOX_LARGE, {48,48}, // large is for wide characters + AIFL_NO_RELOAD, + AIFunc_LoperAttack1Start, AIFunc_LoperAttack2Start, AIFunc_LoperAttack3Start, + "sound/world/electloop.wav", + AISTATE_ALERT + + }, + + //AICHAR_ELITEGUARD + { + "Elite Guard", + { + 230, // running speed + 90, // walking speed + 100, // crouching speed + 90, // Field of View + 200, // Yaw Speed // RF change + 0.0, // leader + 0.5, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.3, // reaction time + 0.4, // attack crouch + 0.0, // idle crouch + 0.5, // aggression + 1.0, // tactical + 0.0, // camper + 16000, // alertness + 120, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "eliteGuardSightPlayer", + "eliteGuardAttackPlayer", + "eliteGuardOrders", + "eliteGuardDeath", + "eliteGuardDeath", //----(SA) added + "eliteGuardPain", + "eliteGuardStay", // stay - you're told to stay put + "eliteGuardFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "eliteGuardOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "eliteguard/default", + {WP_SILENCER}, //----(SA) TODO: replace w/ "silenced luger" + BBOX_SMALL, {32,48}, + AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2, + NULL, NULL, NULL, + NULL, + AISTATE_ALERT + }, + + //AICHAR_STIMSOLDIER1 + { + "Stim Soldier", + { + 170, // running speed + 100, // walking speed + 90, // crouching speed + 90, // Field of View + 150, // Yaw Speed + 0.0, // leader + 0.7, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.6, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 0.9, // aggression + 0.1, // tactical + 0.0, // camper + 16000, // alertness + 300, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "stimSoldierSightPlayer", + "stimSoldierAttackPlayer", + "stimSoldierOrders", + "stimSoldierDeath", + "stimSoldierDeath", //----(SA) added + "stimSoldierPain", + "stimSoldierStay", // stay - you're told to stay put + "stimSoldierFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "stimSoldierOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "stim/default", + {WP_MONSTER_ATTACK2}, // TODO: dual machinegun attack + BBOX_LARGE, {48,64}, + AIFL_NO_RELOAD, + NULL, AIFunc_StimSoldierAttack2Start, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_STIMSOLDIER2 + { + "Stim Soldier", + { + 170, // running speed + 100, // walking speed + 90, // crouching speed + 90, // Field of View + 150, // Yaw Speed + 0.0, // leader + 0.7, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.6, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 0.9, // aggression + 0.1, // tactical + 0.0, // camper + 16000, // alertness + 300, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "stimSoldierSightPlayer", + "stimSoldierAttackPlayer", + "stimSoldierOrders", + "stimSoldierDeath", + "stimSoldierDeath", //----(SA) added + "stimSoldierPain", + "stimSoldierStay", // stay - you're told to stay put + "stimSoldierFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "stimSoldierOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "stim/default", + {WP_MP40, WP_ROCKET_LAUNCHER, WP_MONSTER_ATTACK1}, // attack1 is leaping rocket attack + BBOX_LARGE, {48,64}, + AIFL_NO_RELOAD, + AIFunc_StimSoldierAttack1Start, NULL, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_STIMSOLDIER3 + { + "Stim Soldier", + { + 170, // running speed + 100, // walking speed + 90, // crouching speed + 90, // Field of View + 150, // Yaw Speed + 0.0, // leader + 0.7, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.6, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 0.9, // aggression + 0.1, // tactical + 0.0, // camper + 16000, // alertness + 300, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "stimSoldierSightPlayer", + "stimSoldierAttackPlayer", + "stimSoldierOrders", + "stimSoldierDeath", + "stimSoldierDeath", //----(SA) added + "stimSoldierPain", + "stimSoldierStay", // stay - you're told to stay put + "stimSoldierFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "stimSoldierOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "stim/default", + {WP_MP40, WP_TESLA}, // no monster_attack1, since that's only used for the jumping rocket attack + BBOX_LARGE, {48,64}, + AIFL_NO_RELOAD, + AIFunc_StimSoldierAttack1Start, NULL, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_SUPERSOLDIER + { + "Super Soldier", + { + 170, // running speed + 100, // walking speed + 90, // crouching speed + 90, // Field of View + 150, // Yaw Speed + 0.0, // leader + 0.7, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.6, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 0.9, // aggression + 0.1, // tactical + 0.0, // camper + 16000, // alertness + 300, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 2.0, // pain threshold multiplier + }, + "superSoldierSightPlayer", + "superSoldierAttackPlayer", + "superSoldierOrders", + "superSoldierDeath", + "superSoldierDeath", //----(SA) added + "superSoldierPain", + "superSoldierStay", // stay - you're told to stay put + "superSoldierFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "superSoldierOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "supersoldier/default", + {WP_VENOM}, + BBOX_LARGE, {48,64}, + AIFL_NO_RELOAD | AIFL_NO_FLAME_DAMAGE | AIFL_NO_TESLA_DAMAGE, + NULL, NULL, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_BLACKGUARD + { + "Black Guard", + { + 220, // running speed + 90, // walking speed + 100, // crouching speed + 90, // Field of View + 300, // Yaw Speed + 0.0, // leader + 0.5, // aim skill + 0.8, // aim accuracy + 0.9, // attack skill + 0.3, // reaction time + 0.4, // attack crouch + 0.0, // idle crouch + 0.5, // aggression + 1.0, // tactical + 0.0, // camper + 16000, // alertness + 120, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "blackGuardSightPlayer", + "blackGuardAttackPlayer", + "blackGuardOrders", + "blackGuardDeath", + "blackGuardDeath", //----(SA) added + "blackGuardPain", + "blackGuardStay", // stay - you're told to stay put + "blackGuardFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "blackGuardOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "blackguard/default", +// {WP_MP40, WP_GRENADE_LAUNCHER, WP_MONSTER_ATTACK1}, // attack1 is melee kick + {WP_FG42, WP_FG42SCOPE, WP_GRENADE_LAUNCHER, WP_MONSTER_ATTACK1}, // attack1 is melee kick + BBOX_SMALL, {32,48}, + AIFL_CATCH_GRENADE | AIFL_FLIP_ANIM | AIFL_STAND_IDLE2, + AIFunc_BlackGuardAttack1Start, NULL, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_PROTOSOLDIER + { + "Protosoldier", + { + 170, // running speed + 100, // walking speed + 90, // crouching speed + 90, // Field of View + 230, // Yaw Speed + 0.0, // leader + 0.7, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.2, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 0.9, // aggression + 0.1, // tactical + 0.0, // camper + 16000, // alertness + 300, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 2.0, // pain threshold multiplier + }, + "protoSoldierSightPlayer", + "protoSoldierAttackPlayer", + "protoSoldierOrders", + "protoSoldierDeath", + "protoSoldierDeath", //----(SA) added + "protoSoldierPain", + "protoSoldierStay", // stay - you're told to stay put + "protoSoldierFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "protoSoldierOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "protosoldier/default", +// {WP_TESLA}, + {WP_VENOM_FULL}, + BBOX_LARGE, {48,64}, + AIFL_NO_TESLA_DAMAGE | AIFL_NO_FLAME_DAMAGE | AIFL_WALKFORWARD | AIFL_NO_RELOAD, + NULL, NULL, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_REJECTX + { + "Reject X Creature", + { + // (SA) trying to set basics up per DM's requests + 300, // running speed // 220 * 1.35 (DM number) + 150, // walking speed + 100, // crouching speed + 90, // Field of View + 150, // Yaw Speed + 0.0, // leader + 0.7, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.6, // reaction time + 0.0, // attack crouch + 0.2, // idle crouch + 0.6, // aggression + 0.02, // tactical + 0.0, // camper + 16000, // alertness + 500, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "rejectXSightPlayer", + "rejectXAttackPlayer", + "rejectXOrders", + "rejectXDeath", + "rejectXDeath", //----(SA) added + "rejectXPain", + "rejectXStay", + "rejectXFollow", + "rejectXOrdersDeny", + AITEAM_NAZI, + "rejectx/default", + {WP_MONSTER_ATTACK1}, // ,WP_FLAMETHROWER}, + BBOX_SMALL, {32, 48}, + AIFL_STAND_IDLE2 | AIFL_NO_RELOAD, + AIFunc_RejectAttack1Start, NULL, NULL, + NULL, + AISTATE_ALERT + }, +// AICHAR_FROGMAN + { + "Frogman", + { + 170, // running speed + 100, // walking speed + 90, // crouching speed + 90, // Field of View + 150, // Yaw Speed + 0.0, // leader + 0.7, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.6, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 0.9, // aggression + 0.1, // tactical + 0.0, // camper + 16000, // alertness + 200, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "frogmanSightPlayer", + "frogmanAttackPlayer", + "frogmanOrders", + "frogmanDeath", + "frogmanDeath", //----(SA) added + "frogmanPain", + "frogmanStay", // stay - you're told to stay put + "frogmanFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "frogmanOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "frogman/default", + {WP_SPEARGUN}, + BBOX_SMALL, {32,48}, // bbox, crouch/stand height + 0, + NULL, NULL, NULL, + NULL, + AISTATE_RELAXED + }, + //AICHAR_HELGA + { + "Helga", + { + 140, // running speed + 90, // walking speed + 80, // crouching speed + 90, // Field of View + 300, // Yaw Speed + 0.0, // leader + 0.5, // aim skill + 0.5, // aim accuracy + 0.75, // attack skill + 0.5, // reaction time + 0.0, // attack crouch + 0.0, // idle crouch + 0.5, // aggression + 0.8, // tactical + 0.0, // camper + 16000, // alertness + 100, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "helgaSightPlayer", + "helgaAttackPlayer", + "helgaOrders", + "helgaDeath", + "helgaDeath", //----(SA) added + "helgaPain", + "helgaStay", // stay - you're told to stay put + "helgaFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "helgaOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_MONSTER, // team + "helga/default", // default model/skin + {WP_LUGER}, // starting weapons + BBOX_SMALL, {32,48}, // bbox, crouch/stand height +// AIFL_STAND_IDLE2, // flags + 0, + NULL, NULL, NULL, // special attack routine + NULL, + AISTATE_ALERT + }, + //AICHAR_HEINRICH + { + "Heinrich", + { + 170, // running speed + 100, // walking speed + 90, // crouching speed + 90, // Field of View + 230, // Yaw Speed + 0.0, // leader + 0.7, // aim skill + 1.0, // aim accuracy + 0.9, // attack skill + 0.2, // reaction time + 0.05, // attack crouch + 0.0, // idle crouch + 0.9, // aggression + 0.1, // tactical + 0.0, // camper + 16000, // alertness + 300, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "heinrichSightPlayer", + "heinrichAttackPlayer", + "heinrichOrders", + "heinrichDeath", + "heinrichDeath", + "heinrichPain", + "heinrichStay", // stay - you're told to stay put + "heinrichFollow", // follow - go with ordering player ("i'm with you" rather than "yes sir!") + "heinrichOrdersDeny", // deny - refuse orders (doing something else) + AITEAM_NAZI, + "heinrich/default", + {WP_VENOM_FULL}, + BBOX_LARGE, {110,140}, // (SA) height is not exact. just eyeballed. + AIFL_WALKFORWARD | AIFL_NO_RELOAD, + NULL, NULL, NULL, + NULL, + AISTATE_ALERT + }, + //AICHAR_PARTISAN + { + "Partisan", + { + 220, // running speed + 90, // walking speed + 80, // crouching speed + 90, // Field of View + 300, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.5, // reaction time + 0.3, // attack crouch + 0.0, // idle crouch + 0.5, // aggression + 0.8, // tactical + 0.0, // camper + 16000, // alertness + 100, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "partisanSightPlayer", + "partisanAttackPlayer", + "partisanOrders", + "partisanDeath", + "partisanDeath", //----(SA) added + "partisanPain", + "partisanStay", + "partisanFollow", + "partisanOrdersDeny", + AITEAM_ALLIES, //----(SA) changed affiliation for DK + "partisan/default", + {WP_THOMPSON}, + BBOX_SMALL, {32,48}, + AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2, + NULL, NULL, NULL, + NULL, + AISTATE_RELAXED + }, + //AICHAR_CIVILIAN + { + "Civilian", + { + 220, // running speed + 90, // walking speed + 80, // crouching speed + 90, // Field of View + 300, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.5, // reaction time + 0.3, // attack crouch + 0.0, // idle crouch + 0.5, // aggression + 0.8, // tactical + 0.0, // camper + 16000, // alertness + 100, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "civilianSightPlayer", + "civilianAttackPlayer", + "civilianOrders", + "civilianDeath", + "civilianDeath", //----(SA) added + "civilianPain", + "civilianStay", + "civilianFollow", + "civilianOrdersDeny", + AITEAM_NEUTRAL, //----(SA) changed affiliation for DK + "civilian/default", + {0}, + BBOX_SMALL, {32,48}, + AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2, + NULL, NULL, NULL, + NULL, + AISTATE_RELAXED + }, + //AICHAR_CHIMP + { + "Chimp", + { + 220, // running speed + 90, // walking speed + 80, // crouching speed + 90, // Field of View + 300, // Yaw Speed + 0.0, // leader + 0.70, // aim skill + 0.70, // aim accuracy + 0.75, // attack skill + 0.5, // reaction time + 0.3, // attack crouch + 0.0, // idle crouch + 0.5, // aggression + 0.8, // tactical + 0.0, // camper + 16000, // alertness + 100, // starting health + 1.0, // hearing range + 512, // relaxec detection radius + 1.0, // pain threshold multiplier + }, + "chimpSightPlayer", + "chimpAttackPlayer", + "chimpOrders", + "chimpDeath", + "chimpDeath", //----(SA) added + "chimpPain", + "chimpStay", + "chimpFollow", + "chimpOrdersDeny", + AITEAM_ALLIES, //----(SA) changed affiliation for DK + "chimp/default", + {0}, + BBOX_SMALL, {16,24}, + AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2 | AIFL_NO_RELOAD, + NULL, NULL, NULL, + NULL, + AISTATE_ALERT + }, +}; +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Bounding boxes +static vec3_t bbmins[2] = {{-18, -18, -24},{-32,-32,-24}}; +static vec3_t bbmaxs[2] = {{ 18, 18, 48},{ 32, 32, 68}}; +// TTimo unused +//static float crouchMaxZ[2] = {32,48}; // same as player, will head be ok? +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// Weapon info +cast_weapon_info_t weaponInfo; +//--------------------------------------------------------------------------- + +/* +============ +AIChar_SetBBox + + FIXME: pass a maxZ into this so we can tailor the height for each character, + since height isn't important for the AAS routing (whereas width is very important) +============ +*/ +void AIChar_SetBBox( gentity_t *ent, cast_state_t *cs ) { + VectorCopy( bbmins[cs->aasWorldIndex], ent->client->ps.mins ); + VectorCopy( bbmaxs[cs->aasWorldIndex], ent->client->ps.maxs ); + ent->client->ps.maxs[2] = aiDefaults[cs->aiCharacter].crouchstandZ[1]; + VectorCopy( ent->client->ps.mins, ent->r.mins ); + VectorCopy( ent->client->ps.maxs, ent->r.maxs ); + ent->client->ps.crouchMaxZ = aiDefaults[cs->aiCharacter].crouchstandZ[0]; + ent->s.density = cs->aasWorldIndex; +} + +/* +============ +AIChar_Death +============ +*/ +void AIChar_Death( gentity_t *ent, gentity_t *attacker, int damage, int mod ) { //----(SA) added mod + // need this check otherwise sound will overwrite gib message + if ( ent->health > GIB_HEALTH ) { + if ( ent->client->ps.eFlags & EF_HEADSHOT ) { + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].quietDeathSoundScript ) ); + } else { + switch ( mod ) { //----(SA) modified to add 'quiet' deaths + case MOD_KNIFE_STEALTH: + case MOD_SNIPERRIFLE: + case MOD_SNOOPERSCOPE: + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].quietDeathSoundScript ) ); + break; + default: + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].deathSoundScript ) ); + break; + } + } + } +} + +/* +============= +AIChar_GetPainLocation +============= +*/ +int AIChar_GetPainLocation( gentity_t *ent, vec3_t point ) { + static char *painTagNames[] = { + "tag_head", + "tag_chest", + "tag_torso", + "tag_groin", + "tag_armright", + "tag_armleft", + "tag_legright", + "tag_legleft", + NULL, + }; + + int tagIndex, bestTag; + float bestDist, dist; + orientation_t or; + + // first make sure the client is able to retrieve tag information + // TTimo gcc: warning: comparison is always false due to limited range of data type + // initial line: if (trap_GetTag( ent->s.number, painTagNames[0], &or ) < 0) + if ( !trap_GetTag( ent->s.number, painTagNames[0], &or ) ) { + return 0; + } + + // find a correct animation to play, based on the body orientation at previous frame + for ( tagIndex = 0, bestDist = 0, bestTag = -1; painTagNames[tagIndex]; tagIndex++ ) { + // grab the tag with this name + // TTimo gcc: warning: comparison is always true due to limited range of data type + // initial line: if (trap_GetTag( ent->s.number, painTagNames[tagIndex], &or ) >= 0) + if ( trap_GetTag( ent->s.number, painTagNames[tagIndex], &or ) ) { + dist = VectorDistance( or.origin, point ); + if ( !bestDist || dist < bestDist ) { + bestTag = tagIndex; + bestDist = dist; + } + } + } + + if ( bestTag >= 0 ) { + return bestTag + 1; + } + + return 0; +} + +/* +============ +AIChar_Pain +============ +*/ +void AIChar_Pain( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ) { + #define PAIN_THRESHOLD 25 + #define STUNNED_THRESHOLD 30 + cast_state_t *cs; + float dist; + qboolean forceStun = qfalse; + float painThreshold, stunnedThreshold; + + cs = AICast_GetCastState( ent->s.number ); + + if ( g_testPain.integer == 1 ) { + ent->health = ent->client->pers.maxHealth; // debugging + } + + if ( g_testPain.integer != 2 ) { + if ( level.time < cs->painSoundTime ) { + return; + } + } + + painThreshold = PAIN_THRESHOLD * cs->attributes[PAIN_THRESHOLD_SCALE]; + stunnedThreshold = STUNNED_THRESHOLD * cs->attributes[PAIN_THRESHOLD_SCALE]; + + // if they are already playing another animation, we might get confused and cut it off, so don't play a pain + if ( ent->client->ps.torsoTimer || ent->client->ps.legsTimer ) { + return; + } + + // if we are waiting for our weapon to fire (throwing a grenade) + if ( ent->client->ps.weaponDelay ) { + return; + } + + // HACK: if the attacker is using the flamethrower, don't do any special pain anim or sound + // FIXME: we should pass in the MOD here, since they could have fired a grenade, then switched weapons + if ( attacker->s.weapon == WP_FLAMETHROWER ) { + return; + } + + if ( !Q_stricmp( attacker->classname, "props_statue" ) ) { + damage = 99999; // try and force a stun + forceStun = qtrue; + } + + // now check the damageQuota to see if we should play a pain animation + // first reduce the current damageQuota with time + if ( cs->damageQuotaTime && cs->damageQuota > 0 ) { + cs->damageQuota -= (int)( ( 1.0 + ( g_gameskill.value / GSKILL_MAX ) ) * ( (float)( level.time - cs->damageQuotaTime ) / 1000 ) * ( 7.5 + cs->attributes[ATTACK_SKILL] * 10.0 ) ); + if ( cs->damageQuota < 0 ) { + cs->damageQuota = 0; + } + } + + // if it's been a long time since our last pain, scale it up + if ( cs->painSoundTime < level.time - 1000 ) { + float scale; + scale = (float)( level.time - cs->painSoundTime - 1000 ) / 1000.0; + if ( scale > 4.0 ) { + scale = 4.0; + } + damage = (int)( (float)damage * ( 1.0 + ( scale * ( 1.0 - 0.5 * g_gameskill.value / GSKILL_MAX ) ) ) ); + } + + // adjust the new damage with distance, if they are really close, scale it down, to make it + // harder to get through the game by continually rushing the enemies + if ( ( dist = VectorDistance( ent->r.currentOrigin, attacker->r.currentAngles ) ) < 384 ) { + damage -= (int)( (float)damage * ( 1.0 - ( dist / 384.0 ) ) * ( 0.5 + 0.5 * g_gameskill.value / GSKILL_MAX ) ); + } + + // add the new damage + cs->damageQuota += damage; + cs->damageQuotaTime = level.time; + + if ( forceStun ) { + damage = 99999; // try and force a stun + cs->damageQuota = painThreshold + 1; + } + + // if it's over the threshold, play a pain + + // don't do this if crouching, or we might clip through the world + + if ( g_testPain.integer == 2 || ( cs->damageQuota > painThreshold ) ) { + int delay; + + // stunned? + if ( damage > stunnedThreshold && ( forceStun || ( rand() % 2 ) ) ) { // stunned + BG_UpdateConditionValue( ent->s.number, ANIM_COND_STUNNED, qtrue, qfalse ); + } + // enemy weapon + if ( attacker->client ) { + BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_WEAPON, attacker->s.weapon, qtrue ); + } + if ( point ) { + // location + BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, AIChar_GetPainLocation( ent, point ), qtrue ); + } else { + BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, 0, qfalse ); + } + + // pause while we play a pain + delay = BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_PAIN, qfalse, qtrue ); + + // turn off temporary conditions + BG_UpdateConditionValue( ent->s.number, ANIM_COND_STUNNED, 0, qfalse ); + BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse ); + BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, 0, qfalse ); + + if ( delay >= 0 ) { + // setup game stuff to handle the character movements, etc + cs->pauseTime = level.time + delay + 250; + cs->lockViewAnglesTime = cs->pauseTime; + // make sure we stop crouching + cs->bs->attackcrouch_time = 0; + // don't fire while in pain? + cs->triggerReleaseTime = cs->pauseTime; + // stay crouching if we were before the pain + if ( cs->bs->cur_ps.viewheight > cs->bs->cur_ps.crouchViewHeight ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + (float)( cs->pauseTime - level.time ) / 1000.0 + 0.5; + } + } + + // if we didnt just play a scripted sound, then play one of the default sounds + if ( cs->lastScriptSound < level.time ) { + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].painSoundScript ) ); + } + + // reset the quota + cs->damageQuota = 0; + cs->damageQuotaTime = 0; + // + cs->painSoundTime = cs->pauseTime + (int)( 1000 * ( g_gameskill.value / GSKILL_MAX ) ); // add a bit more of a buffer before the next one + } + +} + +/* +============ +AIChar_Sight +============ +*/ +void AIChar_Sight( gentity_t *ent, gentity_t *other, int lastSight ) { + cast_state_t *cs; + + cs = AICast_GetCastState( ent->s.number ); + + // if we are in noattack mode, don't make sounds + if ( cs->castScriptStatus.scriptNoAttackTime >= level.time ) { + return; + } + if ( cs->noAttackTime >= level.time ) { + return; + } + + // if they have recently played a script sound, then ignore this + if ( cs->lastScriptSound > level.time - 4000 ) { + return; + } + + if ( !AICast_SameTeam( cs, other->s.number ) ) { + if ( !cs->firstSightTime || cs->firstSightTime < ( level.time - 15000 ) ) { + //G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].sightSoundScript ) ); + } + cs->firstSightTime = level.time; + } + +} + +/* +===================== +AIChar_AttackSND + + NOTE: this should just lookup a sound script for this character/weapon combo +===================== +*/ +void AIChar_AttackSound( cast_state_t *cs ) { + + gentity_t *ent; + + ent = &g_entities [cs->entityNum]; + + if ( cs->attackSNDtime > level.time ) { + return; + } + + // if we are in noattack mode, don't make sounds + if ( cs->castScriptStatus.scriptNoAttackTime >= level.time ) { + return; + } + if ( cs->noAttackTime >= level.time ) { + return; + } + + // Ridah, only yell when throwing grenades every now and then, since it's not very "stealthy" + if ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER && rand() % 5 ) { + return; + } + + cs->attackSNDtime = level.time + 5000 + ( 1000 * rand() % 10 ); + + AICast_ScriptEvent( cs, "attacksound", ent->aiName ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + return; + } + + if ( cs->bs->weaponnum == WP_LUGER ) { + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].ordersSoundScript ) ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].attackSoundScript ) ); + } + +} + +/* +============ +AIChar_spawn +============ +*/ +void AIChar_spawn( gentity_t *ent ) { + gentity_t *newent; + cast_state_t *cs; + AICharacterDefaults_t *aiCharDefaults; + int i; + static int lastCall; + static int numCalls; + + // if there are other cast's waiting to spawn before us, wait for them + for ( i = MAX_CLIENTS, newent = &g_entities[MAX_CLIENTS]; i < MAX_GENTITIES; i++, newent++ ) { + if ( !newent->inuse ) { + continue; + } + if ( newent->think != AIChar_spawn ) { + continue; + } + if ( newent == ent ) { + break; // we are the first in line + } + // still waiting for someone else + ent->nextthink = level.time + FRAMETIME; + return; + } + + // if the client hasn't connected yet, wait around + if ( !AICast_FindEntityForName( "player" ) ) { + ent->nextthink = level.time + FRAMETIME; + return; + } + + if ( lastCall == level.time ) { + if ( numCalls++ > 2 ) { + ent->nextthink = level.time + FRAMETIME; + return; // spawned enough this frame already + } + } else { + numCalls = 0; + } + lastCall = level.time; + + aiCharDefaults = &aiDefaults[ent->aiCharacter]; + + // ............................ + // setup weapon info + // + // starting weapons/ammo + memset( &weaponInfo, 0, sizeof( weaponInfo ) ); + for ( i = 0; aiCharDefaults->weapons[i]; i++ ) { + //weaponInfo.startingWeapons[(aiCharDefaults->weapons[i] / 32)] |= ( 1 << aiCharDefaults->weapons[i] ); + //weaponInfo.startingWeapons[0] |= ( 1 << aiCharDefaults->weapons[i] ); + COM_BitSet( weaponInfo.startingWeapons, aiCharDefaults->weapons[i] ); + if ( aiCharDefaults->weapons[i] == WP_GRENADE_LAUNCHER ) { // give them a bunch of grenades, but not an unlimited supply + weaponInfo.startingAmmo[BG_FindAmmoForWeapon( aiCharDefaults->weapons[i] )] = 6; + } else { + weaponInfo.startingAmmo[BG_FindAmmoForWeapon( aiCharDefaults->weapons[i] )] = 999; + } + } + // + // use the default skin if nothing specified + if ( !ent->aiSkin || !strlen( ent->aiSkin ) ) { + ent->aiSkin = aiCharDefaults->skin; + } + // ............................ + // + // create the character + + // (there will always be an ent->aiSkin (SA)) + newent = AICast_CreateCharacter( ent, aiCharDefaults->attributes, &weaponInfo, aiCharDefaults->name, ent->aiSkin, ent->aihSkin, "m", "7", "100" ); + + if ( !newent ) { + G_FreeEntity( ent ); + return; + } + // copy any character-specific information to the new entity (like editor fields, etc) + // + // copy this across so killing ai can trigger a target + newent->target = ent->target; + // + newent->classname = ent->classname; + newent->r.svFlags |= ( ent->r.svFlags & SVF_NOFOOTSTEPS ); + newent->aiCharacter = ent->aiCharacter; + newent->client->ps.aiChar = ent->aiCharacter; + newent->spawnflags = ent->spawnflags; + newent->aiTeam = ent->aiTeam; + if ( newent->aiTeam < 0 ) { + newent->aiTeam = aiCharDefaults->aiTeam; + } + newent->client->ps.teamNum = newent->aiTeam; + if ( newent->aiCharacter == AICHAR_FEMZOMBIE ) { + newent->flags |= FL_NO_KNOCKBACK; + } + // + // kill the old entity + G_FreeEntity( ent ); + // attach to the new entity + ent = newent; + // + // precache any specific sounds + // + // ... + // + // get the cast state + cs = AICast_GetCastState( ent->s.number ); + // + // setup any character specific cast_state variables + cs->deathfunc = AIChar_Death; + cs->painfunc = AIChar_Pain; + cs->aiFlags |= aiCharDefaults->aiFlags; + cs->aiState = aiCharDefaults->aiState; + // + cs->queryCountValidTime = -1; + // + // randomly choose idle animation + if ( cs->aiFlags & AIFL_STAND_IDLE2 ) { + newent->client->ps.eFlags |= EF_STAND_IDLE2; + } + // + // attach any event specific functions (pain, death, etc) + // + //cs->getDeathAnim = AIChar_getDeathAnim; + cs->sightfunc = AIChar_Sight; + if ( ent->aiTeam == AITEAM_ALLIES || ent->aiTeam == AITEAM_NEUTRAL ) { // friendly + cs->activate = AICast_ProcessActivate; + } else { + cs->activate = NULL; + } + cs->aifuncAttack1 = aiCharDefaults->aifuncAttack1; + cs->aifuncAttack2 = aiCharDefaults->aifuncAttack2; + cs->aifuncAttack3 = aiCharDefaults->aifuncAttack3; + // + // looping sound? + if ( aiCharDefaults->loopingSound ) { + ent->s.loopSound = G_SoundIndex( aiCharDefaults->loopingSound ); + } + // + // precache sounds for this character + G_SoundIndex( aiDefaults[ent->aiCharacter].attackSoundScript ); + G_SoundIndex( aiDefaults[ent->aiCharacter].sightSoundScript ); + G_SoundIndex( aiDefaults[ent->aiCharacter].ordersSoundScript ); + G_SoundIndex( aiDefaults[ent->aiCharacter].deathSoundScript ); + G_SoundIndex( aiDefaults[ent->aiCharacter].quietDeathSoundScript ); //----(SA) added + G_SoundIndex( aiDefaults[ent->aiCharacter].painSoundScript ); + G_SoundIndex( aiDefaults[ent->aiCharacter].staySoundScript ); + G_SoundIndex( aiDefaults[ent->aiCharacter].followSoundScript ); + G_SoundIndex( aiDefaults[ent->aiCharacter].ordersDenySoundScript ); + // + // special spawnflag stuff + if ( ent->spawnflags & 2 ) { + cs->secondDeadTime = qtrue; + } + // + // init scripting + cs->castScriptStatus.castScriptEventIndex = -1; + cs->castScriptStatus.scriptAttackEnt = -1; + // + // set crouch move speed + ent->client->ps.crouchSpeedScale = cs->attributes[CROUCHING_SPEED] / cs->attributes[RUNNING_SPEED]; + // + // check for some anims which we can use for special behaviours + if ( BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_ROLL ) >= 0 ) { + cs->aiFlags |= AIFL_ROLL_ANIM; + } + if ( BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_FLIP ) >= 0 ) { + cs->aiFlags |= AIFL_FLIP_ANIM; + } + if ( BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_DIVE ) >= 0 ) { + cs->aiFlags |= AIFL_DIVE_ANIM; + } + // + // check for no headshot damage + if ( cs->aiFlags & AIFL_NO_HEADSHOT_DMG ) { + ent->headshotDamageScale = 0.0; + } + // + if ( !ent->aiInactive ) { + // trigger a spawn script event + AICast_ScriptEvent( cs, "spawn", "" ); + } else { + trap_UnlinkEntity( ent ); + } + +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_soldier (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +soldier entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'infantryss/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ +/* +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models\mapobjects\characters\test\nazi.md3" +*/ +/* +============ +SP_ai_soldier +============ +*/ +void SP_ai_soldier( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_SOLDIER ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_american (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +american entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'american/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_american +============ +*/ +void SP_ai_american( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_AMERICAN ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_zombie (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive PortalZombie +zombie entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'zombie/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_zombie +============ +*/ +void SP_ai_zombie( gentity_t *ent ) { + ent->r.svFlags |= SVF_NOFOOTSTEPS; + AICast_DelayedSpawnCast( ent, AICHAR_ZOMBIE ); +} + + +//----(SA) added +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_warzombie (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive PortalZombie +warrior zombie entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'warrior/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_zombie +============ +*/ +void SP_ai_warzombie( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_WARZOMBIE ); +} + + +//----(SA) end + + + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_femzombie (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +zombie entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'femzombie/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_femzombie +============ +*/ +void SP_ai_femzombie( gentity_t *ent ) { + ent->r.svFlags |= SVF_NOFOOTSTEPS; + AICast_DelayedSpawnCast( ent, AICHAR_FEMZOMBIE ); +} + + + + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_undead (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +undead entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'undead/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_undead +============ +*/ +void SP_ai_undead( gentity_t *ent ) { + ent->r.svFlags |= SVF_NOFOOTSTEPS; + AICast_DelayedSpawnCast( ent, AICHAR_UNDEAD ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_venom (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +venom entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'venom/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_venom +============ +*/ +void SP_ai_venom( gentity_t *ent ) { + ent->r.svFlags |= SVF_NOFOOTSTEPS; + AICast_DelayedSpawnCast( ent, AICHAR_VENOM ); +} + + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_loper (1 0.25 0) (-32 -32 -24) (32 32 48) TriggerSpawn NoRevive +loper entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'loper/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_loper +============ +*/ +void SP_ai_loper( gentity_t *ent ) { + ent->r.svFlags |= SVF_NOFOOTSTEPS; + AICast_DelayedSpawnCast( ent, AICHAR_LOPER ); + // + level.loperZapSound = G_SoundIndex( "loperZap" ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_sealoper (1 0.25 0) (-32 -32 -24) (32 32 48) TriggerSpawn NoRevive +loper entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'sealoper/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_sealoper +============ +*/ +void SP_ai_sealoper( gentity_t *ent ) { + ent->r.svFlags |= SVF_NOFOOTSTEPS; + AICast_DelayedSpawnCast( ent, AICHAR_SEALOPER ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_boss_helga (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +helga entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'helga/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_boss_helga +============ +*/ +void SP_ai_boss_helga( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_HELGA ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_boss_heinrich (1 0.25 0) (-32 -32 -24) (32 32 156) TriggerSpawn NoRevive +heinrich entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'helga/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_boss_heinrich +============ +*/ +void SP_ai_boss_heinrich( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_HEINRICH ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_partisan (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'partisan/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_partisan +============ +*/ +void SP_ai_partisan( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_PARTISAN ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_civilian (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'civilian/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_civilian +============ +*/ +void SP_ai_civilian( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_CIVILIAN ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_chimp (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'chimp/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_chimp +============ +*/ +void SP_ai_chimp( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_CHIMP ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_eliteguard (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +elite guard entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'eliteguard/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_eliteguard +============ +*/ +void SP_ai_eliteguard( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_ELITEGUARD ); +} + + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_frogman (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +elite guard entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'frogman/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_frogman +============ +*/ +void SP_ai_frogman( gentity_t *ent ) { + ent->r.svFlags |= SVF_NOFOOTSTEPS; + AICast_DelayedSpawnCast( ent, AICHAR_FROGMAN ); +} + + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_stimsoldier_dual (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive +stim soldier entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_stimsoldier_dual +============ +*/ +void SP_ai_stimsoldier_dual( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER1 ); + // + level.stimSoldierFlySound = G_SoundIndex( "sound/stimsoldier/flyloop.wav" ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_stimsoldier_rocket (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive +stim soldier entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_stimsoldier_rocket +============ +*/ +void SP_ai_stimsoldier_rocket( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER2 ); + // + level.stimSoldierFlySound = G_SoundIndex( "sound/stimsoldier/flyloop.wav" ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_stimsoldier_tesla (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive +stim soldier entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_stimsoldier_tesla +============ +*/ +void SP_ai_stimsoldier_tesla( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER3 ); + // + level.stimSoldierFlySound = G_SoundIndex( "sound/stimsoldier/flyloop.wav" ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_supersoldier (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive +supersoldier entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'supersoldier/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_supersoldier +============ +*/ +void SP_ai_supersoldier( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_SUPERSOLDIER ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_protosoldier (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive +protosoldier entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'protosoldier/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_protosoldier +============ +*/ +void SP_ai_protosoldier( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_PROTOSOLDIER ); +} + + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_rejectxcreature (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +Reject X creature +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'rejectx/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +void SP_ai_rejectxcreature( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_REJECTX ); +} + +//---------------------------------------------------------------------------------------------------------------------------- +/*QUAKED ai_blackguard (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive +black guard entity +"skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'blackguard/default' is used) +"head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used) +"ainame" name of AI +*/ + +/* +============ +SP_ai_blackguard +============ +*/ +void SP_ai_blackguard( gentity_t *ent ) { + AICast_DelayedSpawnCast( ent, AICHAR_BLACKGUARD ); +} diff --git a/src/game/ai_cast_debug.c b/src/game/ai_cast_debug.c new file mode 100644 index 0000000..1ba6544 --- /dev/null +++ b/src/game/ai_cast_debug.c @@ -0,0 +1,238 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_debug.c +// Function: Wolfenstein AI Character Routines +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "g_local.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +static int numaifuncs; +static char *aifuncs[MAX_AIFUNCS]; + +/* +========== +AICast_DBG_InitAIFuncs +========== +*/ +void AICast_DBG_InitAIFuncs( void ) { + numaifuncs = 0; +} + +/* +========== +AICast_DBG_AddAIFunc +========== +*/ +void AICast_DBG_AddAIFunc( cast_state_t *cs, char *funcname ) { + if ( aicast_debug.integer ) { + if ( aicast_debug.integer != 2 || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) { + G_Printf( "%s: %s\n", g_entities[cs->entityNum].aiName, funcname ); + } + } + aifuncs[numaifuncs] = funcname; + numaifuncs++; +} + +/* +========== +AICast_DBG_ListAIFuncs +========== +*/ +void AICast_DBG_ListAIFuncs( cast_state_t *cs, int numprint ) { + int i; + + if ( aicast_debug.integer != 2 || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) { + AICast_Printf( AICAST_PRT_DEBUG, S_COLOR_RED "AICast_ProcessAIFunctions: executed more than %d AI funcs\n", MAX_AIFUNCS ); + for ( i = MAX_AIFUNCS - numprint; i < MAX_AIFUNCS; i++ ) + AICast_Printf( AICAST_PRT_DEBUG, "%s, ", aifuncs[i] ); + AICast_Printf( AICAST_PRT_DEBUG, "\n" ); + } +} + +/* +========== +AICast_DebugFrame +========== +*/ +void AICast_DebugFrame( cast_state_t *cs ) { + gentity_t *ent; + + if ( aicast_debug.integer ) { + ent = &g_entities[cs->entityNum]; + + if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) { + ent->client->ps.eFlags |= EF_TALK; + } else { + ent->client->ps.eFlags &= ~EF_TALK; + } + } +} + +/* +=========== +AICast_DBG_RouteTable_f +=========== +*/ +void AICast_DBG_RouteTable_f( vec3_t org, char *param ) { + static int srcarea = 0, dstarea = 0; + // TTimo unused +// extern botlib_export_t botlib; + + if ( !param || strlen( param ) < 1 ) { + trap_Printf( "You must specify 'src', 'dest' or 'show'\n" ); + return; + } + + trap_AAS_SetCurrentWorld( 0 ); // use the default world, which should have a routetable + + if ( Q_stricmp( param, "toggle" ) == 0 ) { + trap_AAS_RT_ShowRoute( vec3_origin, -666, -666 ); // stupid toggle hack + return; + } + + if ( Q_stricmp( param, "src" ) == 0 ) { // set the src + srcarea = 1 + trap_AAS_PointAreaNum( org ); + return; + } else if ( Q_stricmp( param, "dest" ) == 0 ) { + dstarea = 1 + trap_AAS_PointAreaNum( org ); + } + + if ( srcarea && dstarea ) { // show the path + trap_AAS_RT_ShowRoute( org, srcarea - 1, dstarea - 1 ); + } else + { + trap_Printf( "You must specify 'src' & 'dest' first\n" ); + } +} + +/* +=============== +AICast_DBG_Spawn_f +=============== +*/ +void AICast_DBG_Spawn_f( gclient_t *client, char *cmd ) { + extern qboolean G_CallSpawn( gentity_t * ent ); + gentity_t *ent; + vec3_t dir; + + ent = G_Spawn(); + ent->classname = G_Alloc( strlen( cmd ) + 1 ); + strcpy( ent->classname, cmd ); + AngleVectors( client->ps.viewangles, dir, NULL, NULL ); + VectorMA( client->ps.origin, 96, dir, ent->s.origin ); + + if ( !G_CallSpawn( ent ) ) { + G_Printf( "Error: unable to spawn \"%s\" entity\n", cmd ); + } +} + +/* +=============== +AICast_DBG_Cmd_f + + General entry point for all "aicast ..." commands +=============== +*/ +void AICast_DBG_Cmd_f( int clientNum ) { + gentity_t *ent; + char cmd[MAX_TOKEN_CHARS]; + + ent = g_entities + clientNum; + if ( !ent->client ) { + return; // not fully in game yet + } + + // get the first word following "aicast" + trap_Argv( 1, cmd, sizeof( cmd ) ); + + if ( Q_stricmp( cmd, "dbg_routetable" ) == 0 ) { + trap_Argv( 2, cmd, sizeof( cmd ) ); + AICast_DBG_RouteTable_f( ent->client->ps.origin, cmd ); + return; + } + if ( Q_stricmp( cmd, "spawn" ) == 0 ) { + // spawn a given character + trap_Argv( 2, cmd, sizeof( cmd ) ); + AICast_DBG_Spawn_f( ent->client, cmd ); + return; + } + if ( Q_stricmp( cmd, "getname" ) == 0 ) { + // get name of character we're looking at +// AICast_DBG_GetName_f(ent); + return; + } + if ( Q_stricmp( cmd, "followme" ) == 0 ) { + // tell character to follow us + trap_Argv( 2, cmd, sizeof( cmd ) ); +// AICast_DBG_FollowMe_f(ent->client, cmd); + return; + } +} +/* +// Ridah, faster Win32 code +#ifdef _WIN32 +#undef MAX_PATH // this is an ugly hack, to temporarily ignore the current definition, since it's also defined in windows.h +#include +#undef MAX_PATH +#define MAX_PATH MAX_QPATH +#endif + +int Sys_MilliSeconds(void) +{ +// Ridah, faster Win32 code +#ifdef _WIN32 + int sys_curtime; + static qboolean initialized = qfalse; + static int sys_timeBase; + + if (!initialized) { + sys_timeBase = timeGetTime(); + initialized = qtrue; + } + sys_curtime = timeGetTime() - sys_timeBase; + + return sys_curtime; +#else + return clock() * 1000 / CLOCKS_PER_SEC; +#endif +} //end of the function Sys_MilliSeconds +*/ diff --git a/src/game/ai_cast_events.c b/src/game/ai_cast_events.c new file mode 100644 index 0000000..8f59da2 --- /dev/null +++ b/src/game/ai_cast_events.c @@ -0,0 +1,568 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_events.c +// Function: Wolfenstein AI Character Events +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +Contains response functions for various events that require specific handling +for Cast AI's. +*/ + +/* +============ +AICast_Sight +============ +*/ +void AICast_Sight( gentity_t *ent, gentity_t *other, int lastSight ) { + cast_state_t *cs, *ocs; + + cs = AICast_GetCastState( ent->s.number ); + ocs = AICast_GetCastState( other->s.number ); + + // + // call the sightfunc for this cast, so we can play associated sounds, or do any character-specific things + // + if ( cs->sightfunc ) { + // factor in the reaction time + if ( AICast_EntityVisible( cs, other->s.number, qfalse ) ) { + cs->sightfunc( ent, other, lastSight ); + } + } + + if ( other->aiName && other->health <= 0 ) { + + // they died since we last saw them + if ( ocs->deathTime > lastSight ) { + if ( !AICast_SameTeam( cs, other->s.number ) ) { + AICast_ScriptEvent( cs, "enemysightcorpse", other->aiName ); + } else if ( !( cs->castScriptStatus.scriptFlags & SFL_FRIENDLYSIGHTCORPSE_TRIGGERED ) ) { + cs->castScriptStatus.scriptFlags |= SFL_FRIENDLYSIGHTCORPSE_TRIGGERED; + AICast_ScriptEvent( cs, "friendlysightcorpse", "" ); + } + } + + // if this is the first time, call the sight script event + } else if ( !lastSight && other->aiName ) { + if ( !AICast_SameTeam( cs, other->s.number ) ) { + // disabled.. triggered when entering combat mode + //AICast_ScriptEvent( cs, "enemysight", other->aiName ); + } else { + AICast_ScriptEvent( cs, "sight", other->aiName ); + } + } +} + +/* +============ +AICast_Pain +============ +*/ +void AICast_Pain( gentity_t *targ, gentity_t *attacker, int damage, vec3_t point ) { + cast_state_t *cs; + + cs = AICast_GetCastState( targ->s.number ); + + // print debugging message + if ( aicast_debug.integer == 2 && attacker->s.number == 0 ) { + G_Printf( "hit %s %i\n", targ->aiName, targ->health ); + } + + // if we are below alert mode, then go there immediately + if ( cs->aiState < AISTATE_ALERT ) { + AICast_StateChange( cs, AISTATE_ALERT ); + } + + if ( cs->aiFlags & AIFL_NOPAIN ) { + return; + } + + // process the event (turn to face the attacking direction? go into hide/retreat state?) + // need to weigh up the situation, but foremost, an inactive AI cast should always react in some way to being hurt + cs->lastPain = level.time; + + // record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way) + if ( attacker->client ) { + AICast_UpdateVisibility( targ, attacker, qtrue, qtrue ); + } + + // if either of us are neutral, then we are now enemies + if ( targ->aiTeam == AITEAM_NEUTRAL || attacker->aiTeam == AITEAM_NEUTRAL ) { + cs->vislist[attacker->s.number].flags |= AIVIS_ENEMY; + } + + AICast_ScriptEvent( cs, "pain", va( "%d %d", targ->health, targ->health + damage ) ); + + if ( cs->aiFlags & AIFL_DENYACTION ) { + // dont play any sounds + return; + } + + // + // call the painfunc for this cast, so we can play associated sounds, or do any character-specific things + // + if ( cs->painfunc ) { + cs->painfunc( targ, attacker, damage, point ); + } +} + +/* +============ +AICast_Die +============ +*/ +void AICast_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { + int contents; + int killer; + cast_state_t *cs; + qboolean nogib = qtrue; + + // print debugging message + if ( aicast_debug.integer == 2 && attacker->s.number == 0 ) { + G_Printf( "killed %s\n", self->aiName ); + } + + cs = AICast_GetCastState( self->s.number ); + + if ( attacker ) { + killer = attacker->s.number; + } else { + killer = ENTITYNUM_WORLD; + } + + // record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way) + if ( attacker->client ) { + AICast_UpdateVisibility( self, attacker, qtrue, qtrue ); + } + + // the zombie should show special effect instead of gibbing + if ( self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime ) { + if ( cs->secondDeadTime > 1 ) { + // we are already totally dead + self->health += damage; // don't drop below gib_health if we weren't already below it + return; + } +/* + if (!cs->rebirthTime) + { + self->health = -999; + damage = 999; + } else if ( self->health >= GIB_HEALTH ) { + // while waiting for rebirth, we only "die" if we drop below gib health + return; + } +*/ + // always gib + self->health = -999; + damage = 999; + } + + // Zombies are very fragile against highly explosives + if ( self->aiCharacter == AICHAR_ZOMBIE && damage > 20 && inflictor != attacker ) { + self->health = -999; + damage = 999; + } + + // process the event + if ( self->client->ps.pm_type == PM_DEAD ) { + // already dead + if ( self->health < GIB_HEALTH ) { + if ( self->aiCharacter == AICHAR_ZOMBIE ) { + // RF, changed this so Zombies always gib now + GibEntity( self, killer ); + nogib = qfalse; +/* + // Zombie has special exploding cloud effect + if (attacker != inflictor || attacker->s.weapon == WP_VENOM) + { + GibEntity( self, killer ); + nogib = qfalse; + } else { + // Zombie will decompose upon dying + self->client->ps.eFlags |= EF_MONSTER_EFFECT2; + self->s.effect2Time = level.time+200; + self->health = -1; + } +*/ + self->takedamage = qfalse; + self->r.contents = 0; + cs->secondDeadTime = 2; + cs->rebirthTime = 0; + cs->revivingTime = 0; + } else { + body_die( self, inflictor, attacker, damage, meansOfDeath ); + return; + } + } + + } else { // this is our first death, so set everything up + + if ( level.intermissiontime ) { + return; + } + + self->client->ps.pm_type = PM_DEAD; + + self->enemy = attacker; + + // drop a weapon? + // if client is in a nodrop area, don't drop anything + contents = trap_PointContents( self->r.currentOrigin, -1 ); + if ( !( contents & CONTENTS_NODROP ) ) { + TossClientItems( self ); + } + + // make sure the client doesn't forget about this entity until it's set to "dead" frame + // otherwise it might replay it's death animation if it goes out and into client view + self->r.svFlags |= SVF_BROADCAST; + + self->takedamage = qtrue; // can still be gibbed + + self->s.weapon = WP_NONE; + self->s.powerups = 0; + self->r.contents = CONTENTS_CORPSE; + + self->s.angles[0] = 0; + self->s.angles[1] = self->client->ps.viewangles[1]; + self->s.angles[2] = 0; + + VectorCopy( self->s.angles, self->client->ps.viewangles ); + + self->s.loopSound = 0; + + self->r.maxs[2] = -8; + self->client->ps.maxs[2] = self->r.maxs[2]; + + // remove powerups + memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) ); + + //cs->rebirthTime = 0; + + // never gib in a nodrop + if ( self->health <= GIB_HEALTH ) { + if ( self->aiCharacter == AICHAR_ZOMBIE ) { + // RF, changed this so Zombies always gib now + GibEntity( self, killer ); + nogib = qfalse; +/* + // Zombie has special exploding cloud effect + if (attacker != inflictor || attacker->s.weapon == WP_VENOM) + { + GibEntity( self, killer ); + nogib = qfalse; + self->takedamage = qfalse; + self->r.contents = 0; + cs->secondDeadTime = 2; + } else { + self->client->ps.eFlags |= EF_MONSTER_EFFECT2; + self->s.effect2Time = level.time+200; + self->takedamage = qfalse; + self->r.contents = 0; + self->health = -1; + cs->secondDeadTime = 2; + } +*/ + } else if ( !( contents & CONTENTS_NODROP ) ) { + body_die( self, inflictor, attacker, damage, meansOfDeath ); + //GibEntity( self, killer ); + nogib = qfalse; + } + } + + // if we are a zombie, and lying down during our first death, then we should just die + if ( !( self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime && cs->rebirthTime ) ) { + + // set enemy weapon + BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse ); + if ( attacker->client ) { + BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, inflictor->s.weapon, qtrue ); + } else { + BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse ); + } + + // set enemy location + BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, 0, qfalse ); + if ( infront( self, inflictor ) ) { + BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_INFRONT, qtrue ); + } else { + BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_BEHIND, qtrue ); + } + + // play the animation + BG_AnimScriptEvent( &self->client->ps, ANIM_ET_DEATH, qfalse, qtrue ); + + // set this flag so no other anims override us + self->client->ps.eFlags |= EF_DEAD; + self->s.eFlags |= EF_DEAD; + + } + } + + if ( nogib ) { + // set for rebirth + if ( self->aiCharacter == AICHAR_ZOMBIE ) { + if ( !cs->secondDeadTime ) { + cs->rebirthTime = level.time + 5000 + rand() % 2000; + cs->secondDeadTime = qtrue; + cs->revivingTime = 0; + } else if ( cs->secondDeadTime > 1 ) { + cs->rebirthTime = 0; + cs->revivingTime = 0; + cs->deathTime = level.time; + } + } else { + // the body can still be gibbed + self->die = body_die; + } + } + + trap_LinkEntity( self ); + + // mark the time of death + cs->deathTime = level.time; + + // dying ai's can trigger a target + if ( !cs->rebirthTime ) { + G_UseTargets( self, self ); + // really dead now, so call the script + AICast_ScriptEvent( cs, "death", "" ); + // call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things + if ( !( cs->aiFlags & AIFL_DENYACTION ) && cs->deathfunc ) { + cs->deathfunc( self, attacker, damage, meansOfDeath ); //----(SA) added mod + } + } else { + // really dead now, so call the script + AICast_ScriptEvent( cs, "fakedeath", "" ); + // call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things + if ( !( cs->aiFlags & AIFL_DENYACTION ) && cs->deathfunc ) { + cs->deathfunc( self, attacker, damage, meansOfDeath ); //----(SA) added mod + } + } +} + +/* +=============== +AICast_EndChase +=============== +*/ +void AICast_EndChase( cast_state_t *cs ) { + // anything? +} + +/* +=============== +AICast_AIDoor_Touch +=============== +*/ +void AICast_AIDoor_Touch( gentity_t *ent, gentity_t *aidoor_trigger, gentity_t *door ) { + cast_state_t *cs, *ocs; + gentity_t *trav; + int i; + trace_t tr; + vec3_t mins, pos, dir; + + cs = AICast_GetCastState( ent->s.number ); + + if ( !cs->bs ) { + return; + } + + // does the aidoor have ai_marker's? + if ( !aidoor_trigger->targetname ) { + G_Printf( "trigger_aidoor has no ai_marker's at %s\n", vtos( ent->r.currentOrigin ) ); + return; + } + + // are we heading for an ai_marker? + if ( cs->aifunc == AIFunc_DoorMarker ) { + return; + } + + // if they are moving away from the door, ignore them + if ( VectorLength( cs->bs->velocity ) > 1 ) { + VectorAdd( door->r.absmin, door->r.absmax, pos ); + VectorScale( pos, 0.5, pos ); + VectorSubtract( pos, cs->bs->origin, dir ); + if ( DotProduct( cs->bs->velocity, dir ) < 0 ) { + return; + } + } + + // TTimo: assignment used as truth value + for ( trav = NULL; ( trav = G_Find( trav, FOFS( target ), aidoor_trigger->targetname ) ); ) { + // make sure the marker is vacant + trap_Trace( &tr, trav->r.currentOrigin, ent->r.mins, ent->r.maxs, trav->r.currentOrigin, ent->s.number, ent->clipmask ); + if ( tr.startsolid ) { + continue; + } + // search all other AI's, to see if they are heading for this marker + for ( i = 0, ocs = AICast_GetCastState( 0 ); i < aicast_maxclients; i++, ocs++ ) { + if ( !ocs->bs ) { + continue; + } + if ( ocs->aifunc != AIFunc_DoorMarker ) { + continue; + } + if ( ocs->doorMarker != trav->s.number ) { + continue; + } + // found a match + break; + } + if ( i < aicast_maxclients ) { + continue; + } + // make sure there is a clear path + VectorCopy( ent->r.mins, mins ); + mins[2] += 16; // step height + trap_Trace( &tr, ent->r.currentOrigin, mins, ent->r.maxs, trav->r.currentOrigin, ent->s.number, ent->clipmask ); + if ( tr.fraction < 1.0 ) { + continue; + } + // the marker is vacant and available + cs->doorMarkerTime = level.time; + cs->doorMarkerNum = trav->s.number; + cs->doorMarkerDoor = door->s.number; + break; + } +} + +/* +============ +AICast_ProcessActivate +============ +*/ +void AICast_ProcessActivate( int entNum, int activatorNum ) { + cast_state_t *cs; + gentity_t *newent, *ent, *activator; + gclient_t *client; + + cs = AICast_GetCastState( entNum ); + client = &level.clients[entNum]; + ent = &g_entities[entNum]; + activator = &g_entities[activatorNum]; + + if ( !AICast_SameTeam( cs, activatorNum ) ) { + + if ( ent->aiTeam == AITEAM_NEUTRAL ) { + AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName ); + } + + return; + } + + // try running the activate event, if it denies us the request, then abort + cs->aiFlags &= ~AIFL_DENYACTION; + AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + return; + } + + // if we are doing something else + if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) { + if ( ent->eventTime != level.time ) { + G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersDenySoundScript ) ); + } + return; + } + + // if we are already following them, stop following + if ( cs->leaderNum == activatorNum ) { + if ( ent->eventTime != level.time ) { + G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].staySoundScript ) ); + } + + cs->leaderNum = -1; + + // create a goal at this position + newent = G_Spawn(); + newent->classname = "AI_wait_goal"; + newent->r.ownerNum = entNum; + G_SetOrigin( newent, cs->bs->origin ); + AIFunc_ChaseGoalStart( cs, newent->s.number, 128, qtrue ); + + //AIFunc_IdleStart( cs ); + } else { // start following + int count, i; + cast_state_t *tcs; + + // if they already have enough followers, deny + for ( count = 0, i = 0, tcs = caststates; i < level.maxclients; i++, tcs++ ) { + if ( tcs->bs && tcs != cs && tcs->entityNum != activatorNum && g_entities[tcs->entityNum].health > 0 && tcs->leaderNum == activatorNum ) { + count++; + } + } + if ( count >= 3 ) { + if ( ent->eventTime != level.time ) { + G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersDenySoundScript ) ); + } + return; + } + + if ( ent->eventTime != level.time ) { + G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].followSoundScript ) ); + } + + // if they have a wait goal, free it + if ( cs->followEntity >= MAX_CLIENTS && g_entities[cs->followEntity].classname && !strcmp( g_entities[cs->followEntity].classname, "AI_wait_goal" ) ) { + G_FreeEntity( &g_entities[cs->followEntity] ); + } + + cs->followEntity = -1; + cs->leaderNum = activatorNum; + } +} + +/* +================ +AICast_RecordScriptSound +================ +*/ +void AICast_RecordScriptSound( int client ) { + cast_state_t *cs; + + cs = AICast_GetCastState( client ); + cs->lastScriptSound = level.time; +} diff --git a/src/game/ai_cast_fight.c b/src/game/ai_cast_fight.c new file mode 100644 index 0000000..98482b7 --- /dev/null +++ b/src/game/ai_cast_fight.c @@ -0,0 +1,2233 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_fight.c +// Function: Wolfenstein AI Character Fighting/Combat +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +Support routines for the Decision Making layer. +*/ + +// FIXME: go through here and convert all weapon/character parameters to #define's +// and move them to a seperate header file for easy modification + +/* +================= +AICast_StateChange + + returns qfalse if scripting has denied the action +================= +*/ +qboolean AICast_StateChange( cast_state_t *cs, aistateEnum_t newaistate ) { + gentity_t *ent; + int result, scriptIndex; + aistateEnum_t oldstate; + + ent = &g_entities[cs->entityNum]; + + oldstate = cs->aiState; + cs->aiState = newaistate; + + // if moving from query mode, kill the anim and pausetime + if ( oldstate == AISTATE_QUERY ) { + // stop playing the animation + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + cs->pauseTime = 0; + } + + // if moving to combat mode, default back to normal movetype (fast) + if ( newaistate == AISTATE_COMBAT ) { + cs->movestate = MS_DEFAULT; + cs->movestateType = MSTYPE_NONE; + } + + scriptIndex = cs->scriptCallIndex; + + // check scripting to see if this event should be ignored (no anim or handling) + cs->aiFlags &= ~AIFL_DENYACTION; + AICast_ScriptEvent( cs, "statechange", va( "%s %s", animStateStr[oldstate].string, animStateStr[newaistate].string ) ); + + if ( !( cs->aiFlags & AIFL_DENYACTION ) ) { + // if no script was found, try enemysight + if ( newaistate == AISTATE_COMBAT && cs->scriptCallIndex == scriptIndex ) { // no script was found, so default back to enemysight + AICast_ScriptEvent( cs, "enemysight", g_entities[cs->bs->enemy].aiName ); + if ( !( cs->aiFlags & AIFL_DENYACTION ) ) { + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].sightSoundScript ) ); + } + + if ( cs->aiFlags & AIFL_DENYACTION ) { + // don't run any dynamic handling or default anims + return qfalse; + } + } + + // look for an animation + result = BG_AnimScriptStateChange( &ent->client->ps, newaistate, oldstate ); + + if ( result > 0 ) { + // pause while the animation plays + cs->pauseTime = level.time + result; + } + } + + // set query mode fields + if ( newaistate == AISTATE_QUERY ) { + cs->queryStartTime = level.time; + if ( cs->queryCountValidTime < level.time ) { + cs->queryCount = 0; + } else { + cs->queryCount++; + } + cs->queryCountValidTime = level.time + 60000; // one minute + switch ( cs->queryCount ) { + case 0: + cs->queryAlertSightTime = level.time + 1000; + break; + case 1: + cs->queryAlertSightTime = level.time + 500; + break; + default: + cs->queryAlertSightTime = -1; // IMMEDIATE COMBAT MODE + break; + } + } + + return qtrue; +} + +/* +================= +AICast_ScanForEnemies + + returns the number of enemies visible, filling the "enemies" list before exiting + + if we only found queryEnemies (possibly hostile, but not sure) while relaxed, + then a negative count is returned +================= +*/ +int AICast_ScanForEnemies( cast_state_t *cs, int *enemies ) { + int i, j, enemyCount, queryCount, friendlyAlertCount; + static float distances[MAX_CLIENTS]; + static int sortedEnemies[MAX_CLIENTS]; + float lastDist; + int best, oldEnemy, oldPauseTime; + cast_state_t *ocs; + + if ( cs->castScriptStatus.scriptAttackEnt >= 0 ) { + if ( g_entities[cs->castScriptStatus.scriptAttackEnt].health <= 0 ) { + cs->castScriptStatus.scriptAttackEnt = -1; + } else { + // if we are not in combat mode, then an enemy should trigger a state change straight to combat mode + if ( cs->aiState < AISTATE_COMBAT ) { + AICast_StateChange( cs, AISTATE_COMBAT ); // just go straight to combat mode + } + enemies[0] = cs->castScriptStatus.scriptAttackEnt; + return 1; + } + } + + if ( cs->castScriptStatus.scriptNoAttackTime >= level.time ) { + return qfalse; + } + + if ( cs->noAttackTime >= level.time ) { + return qfalse; + } + + if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) { + return qfalse; + } + + enemyCount = 0; + queryCount = 0; + friendlyAlertCount = 0; + + // while we're here, may as well check for some baddies + for ( i = 0; i < g_maxclients.integer; i++ ) + { + if ( g_entities[i].inuse ) { + // try not to commit suicide + if ( i != cs->bs->entitynum ) { + if ( AICast_EntityVisible( cs, i, qfalse ) ) { + // how should we deal with them? + if ( ( g_entities[i].health > 0 ) && AICast_HostileEnemy( cs, i ) ) { // visible and a baddy! + enemies[enemyCount] = i; + enemyCount++; + queryCount = 0; + friendlyAlertCount = 0; + } else if ( !enemyCount && ( g_entities[i].health > 0 ) && AICast_QueryEnemy( cs, i ) && ( cs->vislist[i].flags & AIVIS_PROCESS_SIGHTING ) ) { + enemies[queryCount] = i; + queryCount++; + friendlyAlertCount = 0; + } else if ( !queryCount && !enemyCount && ( cs->vislist[i].flags & AIVIS_INSPECT ) ) { + enemies[friendlyAlertCount] = i; + friendlyAlertCount++; + } + // the sighting has been processed + cs->vislist[i].flags &= ~AIVIS_PROCESS_SIGHTING; + } + } + } + } + + if ( !enemyCount ) { + if ( queryCount ) { + enemyCount = queryCount; + } else if ( friendlyAlertCount ) { + enemyCount = friendlyAlertCount; + } + } + + if ( !enemyCount ) { // nothing worth doing anything about + // look for audible events that we should investigate + if ( cs->audibleEventTime && cs->audibleEventTime < level.time && cs->audibleEventTime > level.time - 2000 ) { + return -4; + } + // look for bullet impacts that have occured recently + if ( cs->bulletImpactTime && cs->bulletImpactTime < level.time && cs->bulletImpactTime > level.time - 1000 ) { + return -3; + } + return 0; + } + + // sort the enemies by distance + for ( i = 0; i < enemyCount; i++ ) { + distances[i] = Distance( cs->bs->origin, g_entities[enemies[i]].client->ps.origin ); + if ( !distances[i] ) { + G_Printf( "WARNING: zero distance between enemies:\n%s at %s, %s at %s\n", g_entities[cs->entityNum].aiName, vtos( cs->bs->origin ), g_entities[enemies[i]].aiName, vtos( g_entities[enemies[i]].client->ps.origin ) ); + distances[i] = 999998; // try to ignore them (HACK) + } + } + for ( j = 0; j < enemyCount; j++ ) { + lastDist = 999999; + best = -1; + for ( i = 0; i < enemyCount; i++ ) { + if ( distances[i] && distances[i] < lastDist ) { + lastDist = distances[i]; + best = i; + } + } + if ( best < 0 ) { + G_Error( "error sorting enemies by distance\n" ); + } + sortedEnemies[j] = enemies[best]; + distances[best] = -1; + } + memcpy( enemies, sortedEnemies, sizeof( int ) * enemyCount ); + + // if we are not in combat mode, then an enemy should trigger a state change straight to combat mode + if ( !queryCount && !friendlyAlertCount && enemyCount && cs->aiState < AISTATE_COMBAT ) { + // if only one enemy, face them while making the transition + oldEnemy = cs->bs->enemy; + if ( enemyCount == 1 ) { + cs->bs->enemy = enemies[0]; + AICast_AimAtEnemy( cs ); + // leave it set so StateChange() can use the name for the script calls + } + AICast_StateChange( cs, AISTATE_COMBAT ); // just go straight to combat mode + cs->bs->enemy = oldEnemy; + } + + // if we are in relaxed state, and we see a query enemy, then go into query mode + if ( queryCount ) { + if ( cs->aiState == AISTATE_RELAXED ) { + // go into query mode + if ( AICast_StateChange( cs, AISTATE_QUERY ) ) { + cs->bs->enemy = enemies[0]; // lock onto the closest potential enemy + return -1; + } + return 0; // scripting obviously doesn't want us to progress from relaxed just yet + } + // else ignore the query mode, since we are already above relaxed mode + return 0; + } + if ( friendlyAlertCount ) { + // call a script event + oldPauseTime = cs->scriptPauseTime; + if ( g_entities[enemies[0]].health <= 0 ) { + AICast_ForceScriptEvent( cs, "inspectbodystart", g_entities[enemies[0]].aiName ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + // ignore this friendly + cs->vislist[i].flags |= AIVIS_INSPECTED; + return 0; + } + } + // + if ( cs->aiState < AISTATE_COMBAT ) { + // go into alert mode, and return this entity so we can inspect it or something, + // but let dynamic AI sort out what it wants to do + if ( cs->aiState == AISTATE_ALERT || AICast_StateChange( cs, AISTATE_ALERT ) ) { + // only return the entity if they are in combat mode or dead + ocs = AICast_GetCastState( enemies[0] ); + if ( ( g_entities[enemies[0]].health <= 0 ) || ( ocs->aiState >= AISTATE_COMBAT ) ) { + return -2; + } + } + return 0; // scripting failed or they're not worth physically inspecting + } + // ignore the friendly, we have our hands full + return 0; + } + + // must be hostile enemy(s) found, so return them + return enemyCount; +} + +/* +================== +AICast_EntityVisible +================== +*/ +qboolean AICast_EntityVisible( cast_state_t *cs, int enemynum, qboolean directview ) { + cast_visibility_t *vis; + int last_visible; + int reactionTime; + float dist; + + if ( enemynum >= MAX_CLIENTS ) { + return qtrue; // FIXME: do a visibility calculation on non-client entities? + + } + vis = &cs->vislist[enemynum]; + + if ( !vis->visible_timestamp && !vis->real_visible_timestamp ) { + return qfalse; // they are not visible at all + + } + if ( directview ) { + last_visible = vis->real_visible_timestamp; + } else { + last_visible = vis->visible_timestamp; + } + + reactionTime = (int)( 1000 * cs->attributes[REACTION_TIME] ); + if ( cs->startAttackCount > 1 ) { + // we recently saw them, so we are more "aware" of their presence + reactionTime /= 2; + } + + // if they are close, we should react faster + if ( cs->bs && enemynum == cs->bs->enemy ) { + dist = cs->enemyDist; + } else { + dist = VectorDistance( g_entities[cs->entityNum].client->ps.origin, cs->vislist[enemynum].visible_pos ); + } + if ( dist < 384 ) { + reactionTime *= 0.5 + 0.5 * ( dist / 384 ); + } + + if ( vis->notvisible_timestamp < ( level.time - reactionTime ) ) { + // make sure we've seen them since we've last not seen them (since the visibility checking is spread amongst server frames) + if ( vis->notvisible_timestamp < last_visible ) { + return qtrue; + } + } + + // we can't directly see them, but if they've just left our sight, pretend we can see them for another second or so + + if ( !directview && last_visible ) { + if ( vis->notvisible_timestamp > last_visible ) { + if ( vis->notvisible_timestamp < ( last_visible + 5000 ) ) { + return qtrue; + } + } + } + + + return qfalse; +} + +/* +================== +AICast_HostileEnemy + + returns qtrue if the entity is hostile +================== +*/ +qboolean AICast_HostileEnemy( cast_state_t *cs, int enemynum ) { + // if we hate them, they are an enemy + if ( cs->vislist[enemynum].flags & AIVIS_ENEMY ) { + return qtrue; + } else { + return qfalse; + } +} + +/* +================== +AICast_QueryEnemy + + returns qtrue if the entity can become hostile (they hurt us or we recognize them) +================== +*/ +qboolean AICast_QueryEnemy( cast_state_t *cs, int enemynum ) { + + if ( g_entities[cs->entityNum].aiTeam != g_entities[enemynum].aiTeam ) { + if ( g_entities[cs->entityNum].aiTeam == AITEAM_MONSTER || g_entities[enemynum].aiTeam == AITEAM_MONSTER ) { + // monsters hate all non-monsters and vice-versa + return qtrue; + } else { + // neutral's can only possibly become hostile if they hurt us, otherwise they are assumed to be harmless + if ( g_entities[cs->entityNum].aiTeam == AITEAM_NEUTRAL || g_entities[enemynum].aiTeam == AITEAM_NEUTRAL ) { + return qfalse; // one of us is neutral, assume harmless + } + return qtrue; + } + } else { // same team + return qfalse; // be cool bitch + } + +} + +/* +================== +AICast_SameTeam + + the player is always team 1, AI's default to team 0 + + MONSTER's hate everyone else except other MONSTER's + + NEUTRAL's are cool with everyone that hasn't hurt them +================== +*/ +qboolean AICast_SameTeam( cast_state_t *cs, int enemynum ) { + + if ( g_entities[cs->entityNum].aiTeam != g_entities[enemynum].aiTeam ) { + if ( g_entities[cs->entityNum].aiTeam == AITEAM_NEUTRAL || g_entities[enemynum].aiTeam == AITEAM_NEUTRAL ) { + // if we hate them, they are an enemy + if ( cs->vislist[enemynum].flags & AIVIS_ENEMY ) { + return qfalse; + } else { + return qtrue; + } + } else { + return qfalse; // they are an enemy + } + } else { + return qtrue; // be cool bitch + } + +} + +/* +================== +AICast_WeaponRange +================== +*/ +float AICast_WeaponRange( cast_state_t *cs, int weaponnum ) { + switch ( weaponnum ) { + case WP_GAUNTLET: + return 32 - 4; // to be safe + case WP_TESLA: + return ( TESLA_RANGE * 0.9 ) - 50; // allow for bounding box + case WP_FLAMETHROWER: + return ( FLAMETHROWER_RANGE * 0.6 ) - 50; // allow for bounding box + case WP_PANZERFAUST: + case WP_ROCKET_LAUNCHER: + return 8000; + + case WP_SPEARGUN: //----(SA) + case WP_SPEARGUN_CO2: + return 300; + + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + return 800; + case WP_MONSTER_ATTACK1: + switch ( cs->aiCharacter ) { + case AICHAR_WARZOMBIE: + return 44; + case AICHAR_LOPER: // close attack, head-butt, fist + case AICHAR_SEALOPER: + return 60; + case AICHAR_BLACKGUARD: + return BLACKGUARD_MELEE_RANGE; + case AICHAR_STIMSOLDIER3: + return TESLA_RANGE; + case AICHAR_ZOMBIE: // zombie flaming attack + //return (FLAMETHROWER_RANGE*0.6) - 50; // allow for bounding box + return ZOMBIE_FLAME_RADIUS - 100; // get well within range before starting + case AICHAR_REJECTX: + return REJECT_MELEE_RANGE; + } + break; + case WP_MONSTER_ATTACK2: + switch ( cs->aiCharacter ) { + case AICHAR_ZOMBIE: // zombie spirit attack + return 800; + case AICHAR_LOPER: // loper leap attack + return 256; + } + break; + case WP_MONSTER_ATTACK3: + switch ( cs->aiCharacter ) { + case AICHAR_LOPER: // loper ground attack + return LOPER_GROUND_RANGE; + case AICHAR_WARZOMBIE: // warzombie defense + return 2000; + } + break; + + // Rafael added these changes as per Mikes request + case WP_MAUSER: + case WP_GARAND: + case WP_SNIPERRIFLE: + case WP_SNOOPERSCOPE: + return 8000; + break; + + + } + // default range + return 3000; +} + +/* +================== +AICast_CheckAttack_real +================== +*/ +qboolean AICast_CheckAttack_real( cast_state_t *cs, int enemy, qboolean allowHitWorld ) { + //float points; + vec3_t forward, right, start, end, dir, up, angles; + weaponinfo_t wi; + trace_t trace; + float traceDist; + static vec3_t smins = {-6, -6, -6}, smaxs = {6, 6, 6}; + static vec3_t fmins = {-30, -30, -24}, fmaxs = {30, 30, 24}; + float *mins, *maxs; + float halfHeight; + int traceMask; + int fuzzyCount, i; + gentity_t *ent, *enemyEnt; + float dist; + int passEnt; + int weapnum; + // + if ( enemy < 0 ) { + return qfalse; + } + ent = &g_entities[cs->entityNum]; + enemyEnt = &g_entities[enemy]; + // + if ( cs->bs ) { + weapnum = cs->bs->weaponnum; + } else { + weapnum = ent->client->ps.weapon; + } + // + if ( !weapnum ) { + return qfalse; + } + // + // don't attack while in air (like on a ladder) + if ( !ent->waterlevel && ent->client->ps.groundEntityNum == ENTITYNUM_NONE && !ent->active ) { + // stim is allowed to fire while in air for flying attack + if ( !ent->client->ps.powerups[PW_FLIGHT] ) { + return qfalse; + } + } + // + if ( ent->health <= 0 ) { + return qfalse; + } + // can't attack without any ammo + if ( cs->bs ) { + if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + return qfalse; + } + } + // special case: femzombie can always use portal lightning attack when available + if ( cs->aiCharacter == AICHAR_FEMZOMBIE && weapnum == WP_MONSTER_ATTACK1 ) { + return qtrue; + } + // special case: warzombie should play laughing anim at first sight + if ( cs->aiCharacter == AICHAR_WARZOMBIE && weapnum == WP_MONSTER_ATTACK2 ) { + return qtrue; + } + // + //if the enemy isn't directly visible + if ( !allowHitWorld && cs->vislist[enemy].real_visible_timestamp != cs->vislist[enemy].real_update_timestamp ) { + return qfalse; + } + // + //get the weapon info (FIXME: hard-code the weapon info?) + memset( &wi, 0, sizeof( weaponinfo_t ) ); + // + traceMask = MASK_SHOT; // FIXME: assign mask's to different weapons + //end point aiming at + if ( !ent->active ) { + //get the start point shooting from + VectorCopy( enemyEnt->r.currentOrigin, start ); + start[2] += enemyEnt->client->ps.viewheight; + VectorCopy( ent->r.currentOrigin, end ); + end[2] += ent->client->ps.viewheight; + VectorSubtract( start, end, dir ); + vectoangles( dir, angles ); + AngleVectors( angles, forward, right, up ); + CalcMuzzlePoint( &g_entities[cs->entityNum], weapnum, forward, right, up, start ); + + traceDist = AICast_WeaponRange( cs, weapnum ); + switch ( weapnum ) { + case WP_GAUNTLET: + mins = NULL; + maxs = NULL; + break; + case WP_DYNAMITE: + case WP_DYNAMITE2: + case WP_PANZERFAUST: + case WP_ROCKET_LAUNCHER: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_PROX: + traceMask = MASK_MISSILESHOT; + mins = smins; + maxs = smaxs; + break; + case WP_FLAMETHROWER: + mins = fmins; + maxs = fmaxs; + break; + default: + mins = smins; + maxs = smaxs; + break; + } + passEnt = cs->entityNum; + + // don't try too far + dist = Distance( ent->r.currentOrigin, enemyEnt->r.currentOrigin ); + if ( traceDist > dist ) { + traceDist = dist; + fuzzyCount = 6; + } else if ( dist > traceDist + 32 ) { + return qfalse; + } else { + fuzzyCount = 0; + } + } else { + gentity_t *mg42; + // we are mounted on a weapon + mg42 = &g_entities[cs->mountedEntity]; + VectorCopy( enemyEnt->r.currentOrigin, start ); + start[2] += enemyEnt->client->ps.viewheight; + VectorCopy( mg42->r.currentOrigin, end ); + VectorSubtract( start, end, dir ); + vectoangles( dir, angles ); + AngleVectors( angles, forward, right, up ); + + VectorCopy( mg42->r.currentOrigin, start ); + VectorMA( start, 16, forward, start ); + VectorMA( start, 16, up, start ); + // snap to integer coordinates for more efficient network bandwidth usage + SnapVector( start ); + + traceDist = 8192; + mins = NULL; + maxs = NULL; + if ( mg42->mg42BaseEnt >= 0 ) { + passEnt = mg42->mg42BaseEnt; + } else { + passEnt = cs->entityNum; + } + + // don't try too far + dist = Distance( start, enemyEnt->r.currentOrigin ); + if ( ( traceDist - dist ) > 0 ) { + traceDist = dist; + fuzzyCount = 6; + } else if ( ( dist - traceDist ) > 32 ) { + return qfalse; + } else { + fuzzyCount = 0; + } + } + + for ( i = 0; i <= fuzzyCount; i++ ) { + VectorMA( start, traceDist, forward, end ); + + // fuzzy end point + if ( i > 0 ) { + VectorMA( end, enemyEnt->r.maxs[0] * 0.9 * (float)( ( i % 2 ) * 2 - 1 ), right, end ); + halfHeight = ( enemyEnt->r.maxs[2] - enemyEnt->r.mins[2] ) / 2.0; + end[2] = ( enemyEnt->r.currentOrigin[2] + enemyEnt->r.mins[2] ) + halfHeight; + VectorMA( end, halfHeight * 0.9 * ( ( (float)( ( i - 1 ) - ( ( i - 1 ) % 2 ) ) / 2 - 1.0 ) ), up, end ); + } + + if ( allowHitWorld && !trap_InPVS( start, end ) ) { + // not possibly attackable + continue; + } + + trap_Trace( &trace, start, mins, maxs, end, passEnt, traceMask ); + if ( trace.fraction == 1.0 ) { + //return qfalse; + continue; + } + //if won't hit the enemy + if ( trace.entityNum != enemy ) { + if ( !allowHitWorld ) { + continue; + } + + if ( trace.startsolid ) { + continue; + } + + //if the entity is a client + if ( trace.entityNum >= 0 && trace.entityNum < MAX_CLIENTS ) { + //if a teammate is hit + if ( AICast_SameTeam( cs, trace.entityNum ) ) { + return qfalse; + } + } + //if the projectile does a radial damage + if ( cs->bs->weaponnum == WP_ROCKET_LAUNCHER || cs->bs->weaponnum == WP_PANZERFAUST ) { + if ( Distance( trace.endpos, g_entities[enemy].s.pos.trBase ) > 120 ) { + continue; + } + //FIXME: check if a teammate gets radial damage + } + } + // will successfully hit enemy + return qtrue; + } + // + return qfalse; +} + +/* +================== +AICast_CheckAttackAtPos +================== +*/ +qboolean AICast_CheckAttackAtPos( int entnum, int enemy, vec3_t pos, qboolean ducking, qboolean allowHitWorld ) { + gentity_t *ent; + vec3_t savepos; + int saveview; + qboolean rval; + cast_state_t *cs; + + cs = AICast_GetCastState( entnum ); + ent = &g_entities[cs->bs->entitynum]; + + VectorCopy( ent->r.currentOrigin, savepos ); + VectorCopy( pos, ent->r.currentOrigin ); + + saveview = ent->client->ps.viewheight; + if ( ducking ) { + if ( ent->client->ps.viewheight != ent->client->ps.crouchViewHeight ) { + ent->client->ps.viewheight = ent->client->ps.crouchViewHeight; + } + } else { + if ( ent->client->ps.viewheight != ent->client->ps.standViewHeight ) { + ent->client->ps.viewheight = ent->client->ps.standViewHeight; + } + } + + rval = AICast_CheckAttack_real( cs, enemy, allowHitWorld ); + + VectorCopy( savepos, ent->r.currentOrigin ); + ent->client->ps.viewheight = saveview; + + return rval; +} + +/* +================== +AICast_CheckAttack + + optimization, uses the cache to avoid possible duplicate calls with same world paramaters +================== +*/ +qboolean AICast_CheckAttack( cast_state_t *cs, int enemy, qboolean allowHitWorld ) { + if ( cs->bs ) { + if ( ( cs->checkAttackCache.time == level.time ) + && ( cs->checkAttackCache.enemy == enemy ) + && ( cs->checkAttackCache.weapon == cs->bs->weaponnum ) + && ( cs->checkAttackCache.allowHitWorld == allowHitWorld ) ) { + //G_Printf( "checkattack cache hit\n" ); + return ( cs->checkAttackCache.result ); + } else { + cs->checkAttackCache.allowHitWorld = allowHitWorld; + cs->checkAttackCache.enemy = enemy; + cs->checkAttackCache.time = level.time; + cs->checkAttackCache.weapon = cs->bs->weaponnum; + return ( cs->checkAttackCache.result = AICast_CheckAttack_real( cs, enemy, allowHitWorld ) ); + } + } else { + return AICast_CheckAttack_real( cs, enemy, allowHitWorld ); + } +} + +/* +================== +AICast_UpdateBattleInventory +================== +*/ +void AICast_UpdateBattleInventory( cast_state_t *cs, int enemy ) { + vec3_t dir; + int i; + + if ( enemy >= 0 ) { + VectorSubtract( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->origin, dir ); + cs->enemyHeight = (int) dir[2]; + cs->enemyDist = (int) VectorLength( dir ); + } + + // stock up ammo that should never run out + for ( i = 0; i < MAX_WEAPONS; i++ ) { + if ( g_entities[cs->bs->entitynum].client->ps.ammo[ BG_FindAmmoForWeapon( i )] > 800 ) { + //g_entities[cs->bs->entitynum].client->ps.ammo[ BG_FindAmmoForWeapon(i)] = 999; + Add_Ammo( &g_entities[cs->entityNum], cs->bs->weaponnum, 999, qfalse ); + } + } + + BotAI_GetClientState( cs->entityNum, &( cs->bs->cur_ps ) ); + +} + +/* +============== +AICast_WeaponWantScale +============== +*/ +float AICast_WeaponWantScale( cast_state_t *cs, int weapon ) { + switch ( weapon ) { + case WP_GAUNTLET: + return 0.1; + case WP_FLAMETHROWER: + return 2.0; // if we have this up close, definately use it + default: + return 1.0; + } +} + +/* +============== +AICast_GotEnoughAmmoForWeapon +============== +*/ +qboolean AICast_GotEnoughAmmoForWeapon( cast_state_t *cs, int weapon ) { + gentity_t *ent; + int ammo, clip; + + ent = &g_entities[cs->entityNum]; + ammo = ent->client->ps.ammo[BG_FindAmmoForWeapon( weapon )]; + clip = ent->client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; + + // TODO!! check some kind of weapon list that holds the minimum requirements for each weapon + switch ( weapon ) { + case WP_GAUNTLET: + return qtrue; + default: + return (qboolean)( ( clip >= ammoTable[weapon].uses ) || ( ammo >= ammoTable[weapon].uses ) ); //----(SA) + } +} + +/* +============== +AICast_WeaponUsable + + This is used to prevent weapons from being selected, even if they have ammo. + + This can be used to add a delay between firing for special attacks, or make certain + that certain weapons are only selected within a certain range or under certain conditions. + + NOTE: that monster_attack2 will always override monster_attack1 if both are usable +============== +*/ +qboolean AICast_WeaponUsable( cast_state_t *cs, int weaponNum ) { + int delay, oldweap, hitclient; + float dist = -1; + gentity_t *ent, *grenade; + + if ( cs->bs->enemy >= 0 ) { + dist = Distance( cs->bs->origin, g_entities[cs->bs->enemy].s.pos.trBase ); + } + + oldweap = cs->bs->weaponnum; + ent = &g_entities[cs->entityNum]; + delay = -1; + + // just return qfalse if this weapon isn't ready for use + switch ( weaponNum ) { + // don't attempt to lob a grenade more than this often, since we will abort a grenade + // throw if it's not safe, we shouldn't keep switching back too quickly + case WP_DYNAMITE: + case WP_DYNAMITE2: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + if ( cs->bs->enemy < 0 ) { + return qfalse; + } + delay = 5000; + if ( dist > 0 && dist < 200 ) { + return qfalse; + } + if ( cs->weaponFireTimes[weaponNum] < level.time - delay ) { + // make sure it's safe + CalcMuzzlePoints( ent, weaponNum ); + grenade = weapon_grenadelauncher_fire( ent, weaponNum ); + hitclient = AICast_SafeMissileFire( grenade, grenade->nextthink - level.time, cs->bs->enemy, g_entities[cs->bs->enemy].s.pos.trBase, cs->entityNum, NULL ); + G_FreeEntity( grenade ); + if ( hitclient > -1 ) { + return qtrue; + } else { + return qfalse; // it's not safe + } + } + break; + case WP_ROCKET_LAUNCHER: + switch ( cs->aiCharacter ) { + case AICHAR_STIMSOLDIER2: + return qfalse; // stime only uses Rocket Launcher in jumping attack + //delay = 1000 + (cs->weaponFireTimes[weaponNum]%2000); // somewhat randomize it so it's less predictable + //break; + } + break; + case WP_TESLA: + switch ( cs->aiCharacter ) { + case AICHAR_STIMSOLDIER3: + if ( dist < 0 || dist >= TESLA_RANGE ) { + return qfalse; + } + } + break; + case WP_MONSTER_ATTACK1: + switch ( g_entities[cs->entityNum].aiCharacter ) { + case AICHAR_ZOMBIE: // zombie flaming attack + delay = 4000; + if ( dist < 0 ) { // || dist < 128) { + return qfalse; + } + if ( dist > 1200 ) { + return qfalse; + } + if ( cs->bs->enemy < 0 ) { + return qfalse; + } + //if (cs->vislist[cs->bs->enemy].notvisible_timestamp > level.time - 500) { + // return qfalse; + //} + break; + + // melee attacks are always available + case AICHAR_LOPER: + case AICHAR_WARZOMBIE: + case AICHAR_REJECTX: + case AICHAR_SEALOPER: + return qtrue; // always usable + + case AICHAR_STIMSOLDIER2: + delay = 7000; + if ( dist < 0 || dist < 300 ) { + return qfalse; + } + break; + case AICHAR_STIMSOLDIER3: // stim flying tesla attack + delay = 7000; + if ( dist < 0 || dist < 300 ) { + return qfalse; + } + break; + case AICHAR_BLACKGUARD: + delay = 2000; + if ( dist < 0 || dist > BLACKGUARD_MELEE_RANGE ) { + return qfalse; + } + break; + default: + delay = -1; + break; + } + break; + case WP_MONSTER_ATTACK2: + switch ( g_entities[cs->entityNum].aiCharacter ) { + case AICHAR_WARZOMBIE: + delay = 9999999; + break; + case AICHAR_ZOMBIE: + delay = 6000; + // zombie "flying spirit" attack + if ( dist < 0 || dist < 64 ) { + return qfalse; + } + if ( dist > 1200 ) { + return qfalse; + } + if ( cs->bs->enemy < 0 ) { + return qfalse; + } + if ( cs->vislist[cs->bs->enemy].notvisible_timestamp > level.time - 500 ) { + return qfalse; + } + break; + case AICHAR_LOPER: // loper leap attack + delay = 3500; + if ( dist < 200 ) { + return qfalse; + } + break; + case AICHAR_FEMZOMBIE: // hand lightning + delay = 2500; + break; + default: + delay = -1; + break; + } + break; + case WP_MONSTER_ATTACK3: + switch ( g_entities[cs->entityNum].aiCharacter ) { + case AICHAR_LOPER: // loper ground zap + delay = 3500; + if ( dist < 0 || dist > LOPER_GROUND_RANGE ) { + return qfalse; + } + break; + case AICHAR_WARZOMBIE: // warzombie defense + delay = 2000; + if ( dist < 0 || dist > 2000 ) { + return qfalse; + } + break; + default: + delay = -1; + break; + } + break; + default: + delay = -1; + } + // + return ( !cs->weaponFireTimes[weaponNum] || ( cs->weaponFireTimes[weaponNum] < level.time - delay ) ); +} + +/* +============== +AICast_ChooseWeapon +============== +*/ +void AICast_ChooseWeapon( cast_state_t *cs, qboolean battleFunc ) { + int i; + int *ammo; + float wantScale, bestWantScale; + + BotAI_GetClientState( cs->entityNum, &( cs->bs->cur_ps ) ); + ammo = cs->bs->cur_ps.ammo; + bestWantScale = 0.0; + // read back in the weapon that we are really using + //cs->bs->weaponnum = cs->bs->cur_ps.weapon; + + if ( cs->bs->cur_ps.weaponstate == WEAPON_RAISING || + cs->bs->cur_ps.weaponstate == WEAPON_DROPPING ) { + return; + } + +// disabled this, makes grenade guy keep trying to throw a grenade he doesn't have +// if (cs->bs->cur_ps.weaponDelay || cs->bs->cur_ps.weaponTime) +// return; + + if ( cs->bs->weaponnum && ( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) { + if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) && AICast_WeaponUsable( cs, cs->bs->weaponnum ) ) { + return; + } else { + cs->castScriptStatus.scriptFlags &= ~SFL_NOCHANGEWEAPON; + } + } + + // choose the best weapon to fight with + for ( i = 0; i < MAX_WEAPONS; i++ ) { + if ( i == WP_GRENADE_LAUNCHER || i == WP_GRENADE_PINEAPPLE ) { + continue; // never choose grenades at will, only when going into grenade flush mode + } + + if ( !battleFunc && ( i == WP_MONSTER_ATTACK1 ) && cs->aifuncAttack1 ) { + continue; // only choose this weapon from within AIFunc_BattleStart() + } + if ( !battleFunc && ( i == WP_MONSTER_ATTACK2 ) && cs->aifuncAttack2 ) { + continue; // only choose this weapon from within AIFunc_BattleStart() + } + if ( !battleFunc && ( i == WP_MONSTER_ATTACK3 ) && cs->aifuncAttack3 ) { + continue; // only choose this weapon from within AIFunc_BattleStart() + } + + if ( COM_BitCheck( cs->bs->cur_ps.weapons, i ) ) { + // check that our ammo is enough + if ( !AICast_GotEnoughAmmoForWeapon( cs, i ) || + !AICast_WeaponUsable( cs, i ) ) { + continue; + } + // get the wantScale for this weapon given the current circumstances (0.0 - 1.0) + wantScale = AICast_WeaponWantScale( cs, i ); + // + if ( wantScale >= bestWantScale ) { + cs->bs->weaponnum = i; + bestWantScale = wantScale; + } + } + } +} + +/* +================== +AICast_Aggression + + Check all possible reasons why we shouldn't attack, returning a value from 1.0 (fully willing) + to 0.0 (please don't hurt me). +================== +*/ +float AICast_Aggression( cast_state_t *cs ) { + bot_state_t *bs; + float scale, dist; + int painTime; + int *ammo; + + bs = cs->bs; + + // if we are out of ammo, we should never chase + ammo = cs->bs->cur_ps.ammo; + if ( g_entities[cs->entityNum].aiTeam != AITEAM_MONSTER ) { + if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + return 0; + } + } + + // start fully willing to attack + scale = 1.0; + + //if the enemy is located way higher + //if (cs->enemyHeight > 200) + // scale -= (cs->enemyHeight)/800.0; + + //if very low on health + if ( bs->cur_ps.stats[STAT_HEALTH] < 50 ) { + scale -= ( 1.0 - cs->attributes[AGGRESSION] ) * ( 1.0 - ( (float)bs->cur_ps.stats[STAT_HEALTH] / 50.0 ) ); + } + + // if they've recently hit us, factor that in, so we get scared off by being + // damaged, but later return once we've regained our confidence + painTime = 15000 - (int)( 10000.0 * cs->attributes[AGGRESSION] * cs->attributes[AGGRESSION] ); + if ( cs->lastPain + painTime > level.time ) { + scale -= 3 * ( 1.0 - cs->attributes[AGGRESSION] ) * ( (float)( cs->lastPain + painTime - level.time ) / (float)painTime ); + } + + // if we just rolled, stay out of view if we jumped behind cover + painTime = 10000 - (int)( 10000.0 * cs->attributes[AGGRESSION] * cs->attributes[AGGRESSION] ); + if ( cs->battleRollTime + painTime > level.time ) { + scale -= 2 * ( 1.0 - cs->attributes[AGGRESSION] ) * ( (float)( cs->battleRollTime + painTime - level.time ) / (float)painTime ); + } + + // gain in confidence the further we are away + if ( cs->bs->enemy >= 0 ) { + dist = Distance( cs->bs->origin, g_entities[bs->enemy].s.pos.trBase ); + //if (dist > 512) { + scale += ( dist - 800.0 ) / ( 8000.0 ); + //} + } + + // if our weapon is reloading, we should hide + if ( cs->bs->cur_ps.weaponTime > 0 ) { + scale -= ( (float)cs->bs->cur_ps.weaponTime / 1000.0 ); + } + + scale *= cs->attributes[AGGRESSION]; + + // this should increase the chances of an ambush attack + if ( ( ( level.time + 2000 * g_entities[cs->entityNum].aiTeam ) % ( 4000 + 500 * g_entities[cs->entityNum].aiTeam ) ) > 4000 ) { + scale += 0.3; + } + + if ( scale < 0 ) { + scale = 0; + } + + return scale; +} + +/* +================== +AICast_WantsToChase +================== +*/ +int AICast_WantsToChase( cast_state_t *cs ) { + int *ammo; + ammo = cs->bs->cur_ps.ammo; + if ( g_entities[cs->entityNum].aiTeam != AITEAM_MONSTER ) { + if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + return qfalse; + } + } + if ( cs->attributes[AGGRESSION] == 1.0 ) { + return qtrue; + } + if ( AICast_Aggression( cs ) > 0.6 ) { + return qtrue; + } + return qfalse; +} + +/* +================== +AICast_WantsToTakeCover +================== +*/ +int AICast_WantsToTakeCover( cast_state_t *cs, qboolean attacking ) { + float aggrScale; + int *ammo; + + ammo = cs->bs->cur_ps.ammo; + if ( g_entities[cs->entityNum].aiTeam != AITEAM_MONSTER ) { + if ( !cs->bs->weaponnum ) { + return qtrue; + } + if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + return qtrue; + } + } + if ( cs->attributes[AGGRESSION] == 1.0 ) { + return qfalse; + } + // if currently attacking, we should stick around if not getting hurt + if ( attacking ) { + aggrScale = 1.2; + } else { aggrScale = 0.8 /*+ 0.4 * random()*/;} + // + // if currently following someone, we should be more aggressive + if ( cs->leaderNum >= 0 ) { + aggrScale *= 3; + } + // + // Dodge enemy aim? + if ( cs->attributes[AGGRESSION] < 1.0 && attacking && ( cs->bs->enemy >= 0 ) && ( g_entities[cs->bs->enemy].client->ps.weapon ) && ( cs->attributes[TACTICAL] > 0.5 ) && ( cs->aiFlags & AIFL_ROLL_ANIM ) && ( VectorLength( cs->bs->cur_ps.velocity ) < 1 ) ) { + vec3_t aim, enemyVec; + // are they aiming at us? + AngleVectors( g_entities[cs->bs->enemy].client->ps.viewangles, aim, NULL, NULL ); + VectorSubtract( cs->bs->origin, g_entities[cs->bs->enemy].r.currentOrigin, enemyVec ); + VectorNormalize( enemyVec ); + // if they are looking at us, we should avoid them + if ( DotProduct( aim, enemyVec ) > 0.97 ) { + //G_Printf("%s: I'm in danger, I should probably avoid\n", g_entities[cs->entityNum].aiName); + aggrScale *= 0.6; + } + } + // + // FIXME: instead of a constant, call a "attack danger" + // function, so we only attack if our aggression is greater than + // the danger + if ( AICast_Aggression( cs ) * aggrScale < 0.4 ) { + //G_Printf("%s: run for your life!\n", g_entities[cs->entityNum].aiName); + return qtrue; + } + // + return qfalse; +} + +/* +================== +AICast_CombatMove +================== +*/ +bot_moveresult_t AICast_CombatMove( cast_state_t *cs, int tfl ) { + bot_state_t *bs; + float attack_skill, croucher, dist; + vec3_t forward, backward; //, up = {0, 0, 1}; + bot_moveresult_t moveresult; + bot_goal_t goal; + + bs = cs->bs; + + //get the enemy entity info + memset( &moveresult, 0, sizeof( bot_moveresult_t ) ); + // + attack_skill = cs->attributes[ATTACK_SKILL]; + croucher = ( cs->attributes[ATTACK_CROUCH] > 0.1 ); + + //initialize the movement state + BotSetupForMovement( bs ); + //direction towards the enemy + VectorSubtract( cs->vislist[cs->bs->enemy].visible_pos, bs->origin, forward ); + //the distance towards the enemy + dist = VectorNormalize( forward ); + VectorNegate( forward, backward ); + // + // do we have somewhere we are trying to get to? + if ( cs->combatGoalTime > level.time ) { + if ( VectorLength( cs->combatGoalOrigin ) > 1 ) { + //create the chase goal + goal.areanum = BotPointAreaNum( cs->combatGoalOrigin ); + VectorCopy( cs->combatGoalOrigin, goal.origin ); + + // if we are really close, stop going for it + // FIXME: a better way of doing this, so we don't stop short of the goal? + if ( ( dist = Distance( goal.origin, cs->bs->origin ) ) < 32 ) { + if ( cs->combatGoalTime > level.time + 3000 ) { + cs->combatGoalTime = level.time + 2000 + rand() % 1000; + cs->combatSpotDelayTime = level.time + 4000 + rand() % 3000; + } + VectorClear( cs->combatGoalOrigin ); + } else { + aicast_predictmove_t move; + // + AICast_MoveToPos( cs, cs->combatGoalOrigin, -1 ); + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 32 ); + // + // if we are going to move out of view very soon, stop moving + AICast_PredictMovement( cs, 1, 0.8, &move, &cs->bs->lastucmd, -1 ); + // + if ( move.numtouch || !AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, move.endpos, qfalse, qfalse ) ) { + // abort the manouver, reached a good spot + cs->combatGoalTime = 0; + cs->combatSpotAttackCount = cs->startAttackCount; + } + } + + // if we are there, and the enemy can see us, but we cant hit them, abort immediately + } else if ( !AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) && + AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->enemy, cs->bs->origin, cs->entityNum, qfalse ) ) { + cs->combatGoalTime = 0; + cs->combatSpotAttackCount = cs->startAttackCount; + } + } else { // look for a good position to move to? + if ( ( ( cs->attributes[CAMPER] < random() ) + && ( cs->takeCoverTime < level.time ) + && ( cs->combatSpotAttackCount < cs->startAttackCount ) + && ( cs->combatSpotDelayTime < level.time ) ) ) { + + if ( ( cs->attributes[TACTICAL] > 0.3 + random() * 0.5 ) + && trap_AAS_RT_GetHidePos( cs->bs->origin, cs->bs->entitynum, cs->bs->areanum, cs->vislist[cs->bs->enemy].visible_pos, bs->enemy, BotPointAreaNum( cs->vislist[cs->bs->enemy].visible_pos ), cs->combatGoalOrigin ) ) { + cs->combatGoalTime = level.time + 10000; // give us plenty of time to get there + //cs->combatSpotAttackCount = cs->startAttackCount + 3; // don't keep moving around to different positions on our own + cs->combatSpotDelayTime = level.time + 3000 + rand() % 3000; + } else { + // don't look again until we've moved + //cs->combatSpotAttackCount = cs->startAttackCount; + cs->combatSpotDelayTime = level.time + 3000 + rand() % 3000; + } + } + } + // + return moveresult; +} + +/* +================== +AICast_WeaponSway + + Some weapons should be "sprayed" around a bit while firing +================== +*/ +void AICast_WeaponSway( cast_state_t *cs, vec3_t ofs ) { + VectorClear( ofs ); + switch ( cs->bs->weaponnum ) { + case WP_MONSTER_ATTACK1: + if ( cs->aiCharacter != AICHAR_ZOMBIE ) { + break; // only allow flaming zombie beyond here + } + case WP_FLAMETHROWER: + ofs[PITCH] = ( 3.0 + 4.0 * sin( ( (float)level.time / 320.0 ) ) ) * sin( ( (float)level.time / 500.0 ) ); + ofs[YAW] = ( 6.0 + 8.0 * sin( ( (float)level.time / 250.0 ) ) ) * sin( ( (float)level.time / 400.0 ) ); + ofs[ROLL] = 0; + break; + case WP_VENOM: + ofs[PITCH] = 2 * (float)cos( ( level.time / 200 ) ); + ofs[YAW] = 10 * (float)sin( ( level.time / 150 ) ) * (float)sin( ( level.time / 100 ) ); + ofs[ROLL] = 0; + break; + } +} + +/* +================== +AICast_AimAtEnemy +================== +*/ +qboolean AICast_AimAtEnemy( cast_state_t *cs ) { + bot_state_t *bs; + float aim_skill, aim_accuracy; + vec3_t dir, bestorigin, start, enemyOrg; + vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; + bsp_trace_t trace; + float dist; + cast_visibility_t *vis; + + // + if ( cs->castScriptStatus.scriptNoAttackTime >= ( level.time + 500 ) ) { + return qfalse; + } + // + if ( cs->noAttackTime >= level.time ) { + return qfalse; + } + // + bs = cs->bs; + // + //if the bot has no enemy + if ( bs->enemy < 0 ) { + return qfalse; + } + // + aim_skill = cs->attributes[AIM_SKILL]; + aim_accuracy = AICast_GetAccuracy( cs->entityNum ); + if ( aim_accuracy <= 0 ) { + aim_accuracy = 0.0001; + } + + // StimSoldier is very good at firing Rocket Launcher + if ( cs->aiCharacter == AICHAR_STIMSOLDIER2 && cs->bs->weaponnum == WP_ROCKET_LAUNCHER ) { + aim_skill = 1; + aim_accuracy = 1; + } + + //get the weapon information + + //get the enemy entity information + vis = &cs->vislist[bs->enemy]; + if ( vis->visible_timestamp < vis->lastcheck_timestamp ) { + // use our last visible position of them + if ( vis->real_visible_timestamp == vis->lastcheck_timestamp ) { + VectorCopy( vis->real_visible_pos, bestorigin ); + } else { + VectorCopy( vis->visible_pos, bestorigin ); + } + } else { + // we can see them, if this weapon isn't a direct-hit weapon (bullets), + // then predict where they are going to be + if ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER || cs->bs->weaponnum == WP_GRENADE_PINEAPPLE ) { + aicast_predictmove_t move; + AICast_PredictMovement( AICast_GetCastState( cs->bs->enemy ), 1, 1.0, &move, &g_entities[bs->enemy].client->pers.cmd, -1 ); + VectorCopy( move.endpos, bestorigin ); + } else { // they are visible, use actual position + VectorCopy( g_entities[bs->enemy].client->ps.origin, bestorigin ); + } + } + + bestorigin[2] += g_entities[bs->enemy].client->ps.viewheight; + //get the start point shooting from + //NOTE: the x and y projectile start offsets are ignored + VectorCopy( bs->origin, start ); + start[2] += bs->cur_ps.viewheight; + // + VectorCopy( cs->vislist[bs->enemy].visible_pos, enemyOrg ); + // + // grenade hack: aim grenades at their feet if they are close + if ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER || cs->bs->weaponnum == WP_GRENADE_PINEAPPLE ) { + if ( Distance( start, bestorigin ) < 180 ) { + bestorigin[2] = enemyOrg[2] + g_entities[bs->enemy].r.mins[2] + crandom() * 20; + } else if ( Distance( start, bestorigin ) > 400 ) { // aim up a bit for distance + bestorigin[2] += 12 + Distance( start, bestorigin ) / 50 + crandom() * 20; + } + } + dist = Distance( bs->eye, bestorigin ); + // rocket launcher should aim ahead + if ( bs->weaponnum == WP_ROCKET_LAUNCHER || bs->weaponnum == WP_PANZERFAUST ) { + VectorMA( bestorigin, aim_skill * aim_skill * ( dist / 900 ), g_entities[bs->enemy].client->ps.velocity, bestorigin ); + } + // + BotAI_Trace( &trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT ); + //if the enemy is NOT hit + if ( trace.fraction <= 1 && trace.ent != bs->enemy ) { + if ( bs->weaponnum == WP_ROCKET_LAUNCHER || bs->weaponnum == WP_PANZERFAUST ) { + if ( Distance( trace.endpos, bestorigin ) > 100 ) { + // remove the prediction + VectorMA( bestorigin, aim_skill * aim_skill * ( dist / 900 ), g_entities[bs->enemy].client->ps.velocity, bestorigin ); + // aim downwards + bestorigin[2] -= 16; + } + } else { + bestorigin[2] += 16; + } + } + // + // adjust accuracy with distance if upclose, so we don't start firing the wrong direction +/* + scale = 1; + switch (cs->bs->weaponnum) { + // these weapons don't do random offsetting + case WP_FLAMETHROWER: + break; + default: + { + float scale; + + if (dist < 256) + scale *= (dist / 256); + else + scale *= 1.0; + + bestorigin[0] += scale * 96 * sin((float)level.time/(200.0 + (40.0*((cs->entityNum+3)%4)))) * (1 - aim_accuracy); + bestorigin[1] += scale * 96 * cos((float)level.time/(220.0 + (36.0*((cs->entityNum+1)%5)))) * (1 - aim_accuracy); + bestorigin[2] += scale * 48 * sin((float)level.time/(210.0 + (32.0*((cs->entityNum+2)%6)))) * (1 - aim_accuracy); + } + break; + } +*/ + // if the enemy is moving, they are harder to hit + if ( dist > 256 ) { + VectorMA( bestorigin, ( 0.3 + 0.7 * ( 1 - aim_accuracy ) ) * 0.4 * sin( (float)level.time / ( 500.0 + ( 100.0 * ( ( cs->entityNum + 3 ) % 4 ) ) ) ), g_entities[bs->enemy].client->ps.velocity, bestorigin ); + } + //get aim direction + VectorSubtract( bestorigin, bs->eye, dir ); + //set the ideal view angles + vectoangles( dir, bs->ideal_viewangles ); + + return qtrue; // do real aim checking after we've moved the angles +} + +/* +================== +AICast_CanMoveWhileFiringWeapon +================== +*/ +qboolean AICast_CanMoveWhileFiringWeapon( int weaponnum ) { + switch ( weaponnum ) { + case WP_MAUSER: + case WP_GARAND: + case WP_SNIPERRIFLE: //----(SA) added + case WP_SNOOPERSCOPE: //----(SA) added + case WP_FG42SCOPE: //----(SA) added + case WP_PANZERFAUST: + case WP_ROCKET_LAUNCHER: + return qfalse; + default: + return qtrue; + } +} + +/* +================ +AICast_RandomTriggerRelease +================ +*/ +qboolean AICast_RandomTriggerRelease( cast_state_t *cs ) { + // some characters override all weapon settings for trigger release + switch ( cs->aiCharacter ) { + case AICHAR_BLACKGUARD: // this is here since his "ready" frame is different to his firing frame, so it looks wierd to keep swapping between them + case AICHAR_STIMSOLDIER1: + case AICHAR_STIMSOLDIER2: + case AICHAR_STIMSOLDIER3: + return qfalse; + break; + } + + switch ( cs->bs->weaponnum ) { + case WP_MP40: + case WP_VENOM: + //case WP_FLAMETHROWER: + return qtrue; + default: + return qfalse; + } +} + +/* +================== +AICast_ProcessAttack + + NOTE: this should always be called after the movement has been processed +================== +*/ +void AICast_ProcessAttack( cast_state_t *cs ) { + bot_state_t *bs; + + // if our enemy is dead, stop attacking them + if ( cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0 ) { + return; + } + // + if ( cs->castScriptStatus.scriptNoAttackTime >= level.time ) { + return; + } + // + if ( cs->noAttackTime >= level.time ) { + return; + } + // select a weapon + AICast_ChooseWeapon( cs, qfalse ); + // + bs = cs->bs; + // check for not firing while moving flag, if present, abort attack if any movement has been issued + if ( !AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum ) ) { + // if we are moving, don't fire + bot_input_t bi; + if ( cs->bs->cur_ps.weaponTime > 200 ) { + // if we recently fired, don't let us move for a bit + cs->speedScale = 0; + AICast_AimAtEnemy( cs ); // keep looking at them regardless + } + // if we're trying to move somewhere, don't let us shoot, until we've arrived + trap_EA_GetInput( bs->client, (float) level.time / 1000, &bi ); + if ( ( cs->castScriptStatus.scriptNoMoveTime < level.time ) && + ( ( bi.actionflags & ACTION_MOVEFORWARD ) || + ( bi.actionflags & ACTION_MOVEBACK ) || + ( bi.actionflags & ACTION_MOVELEFT ) || + ( bi.actionflags & ACTION_MOVERIGHT ) || + ( bi.speed ) ) ) { + return; + } + } + // + // if we are stuck in this position, we should duck if we can't hit them + if ( !AICast_CheckAttack( cs, bs->enemy, qfalse ) ) { + // we should duck if the enemy is shooting at us, and we can't hit them + if ( cs->attributes[ATTACK_CROUCH] && ( cs->castScriptStatus.scriptNoMoveTime >= level.time ) ) { + if ( !AICast_CheckAttackAtPos( cs->entityNum, bs->enemy, cs->bs->origin, qfalse, qfalse ) ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 2; + } else { + cs->bs->attackcrouch_time = 0; // we can attack them if we stand, so go for it + } + } + return; + } + // + //aim at the enemy + if ( !AICast_AimAtEnemy( cs ) ) { + // not fully facing them yet + return; + } + // + // release the trigger every now and then + if ( AICast_RandomTriggerRelease( cs ) && cs->triggerReleaseTime < ( level.time - 500 ) ) { + if ( rand() % 5 == 0 ) { + cs->triggerReleaseTime = level.time + 100 + rand() % 100; + return; + } + } + // + if ( cs->triggerReleaseTime > level.time ) { + return; + } + // + // FIXME: handle fire-on-release weapons? + trap_EA_Attack( bs->client ); + // + bs->flags |= BFL_ATTACKED; + +} + +/* +============== +AICast_GetTakeCoverPos +============== +*/ +qboolean AICast_GetTakeCoverPos( cast_state_t *cs, int enemyNum, vec3_t enemyPos, vec3_t returnPos ) { + cs->crouchHideFlag = qfalse; + // + if ( cs->castScriptStatus.scriptNoMoveTime > level.time ) { + return qfalse; + } + // + cs->lastGetHidePos = level.time; + // + // can we just crouch? + if ( ( cs->bs->attackcrouch_time < trap_AAS_Time() ) + && ( enemyNum < aicast_maxclients ) + && AICast_CheckAttackAtPos( cs->entityNum, enemyNum, cs->bs->origin, qfalse, qfalse ) + && !AICast_CheckAttackAtPos( cs->entityNum, enemyNum, cs->bs->origin, qtrue, qfalse ) ) { + + // do a more thorough check to see if the enemy can see us if we crouch + vec3_t omaxs; + gentity_t *ent; + qboolean visible; + + ent = &g_entities[cs->entityNum]; + VectorCopy( ent->r.maxs, omaxs ); + ent->r.maxs[2] = ent->client->ps.crouchMaxZ + 4; // + 4 to be safe + + visible = AICast_VisibleFromPos( g_entities[enemyNum].r.currentOrigin, enemyNum, cs->bs->origin, cs->entityNum, qfalse ); + + ent->r.maxs[2] = omaxs[2]; + + if ( !visible ) { + VectorCopy( enemyPos, cs->takeCoverEnemyPos ); + VectorCopy( cs->bs->origin, returnPos ); + cs->crouchHideFlag = qtrue; + return qtrue; + } + } + // look for a hiding spot + if ( trap_AAS_RT_GetHidePos( cs->bs->origin, cs->bs->entitynum, cs->bs->areanum, enemyPos, enemyNum, BotPointAreaNum( enemyPos ), returnPos ) ) { + return qtrue; + } + // if we are hiding from a dangerous entity, try and avoid it + if ( cs->dangerEntity == enemyNum && cs->dangerEntityValidTime > level.time ) { + if ( cs->dangerLastGetAvoid > level.time - 750 ) { + return qtrue; + } else if ( AICast_GetAvoid( cs, NULL, cs->takeCoverPos, qtrue, cs->dangerEntity ) ) { + cs->dangerLastGetAvoid = level.time; + return qtrue; + } + } + // + return qfalse; +} + +/* +============== +AICast_AIDamageOK +============== +*/ +qboolean AICast_AIDamageOK( cast_state_t *cs, cast_state_t *ocs ) { + if ( cs->castScriptStatus.scriptFlags & SFL_NOAIDAMAGE ) { + return qfalse; + } else { + + if ( cs->aiCharacter == AICHAR_LOPER && ocs->aiCharacter == AICHAR_LOPER ) { + return qfalse; + } + + return qtrue; + } +} + +/* +=============== +AICast_RecordWeaponFire + + used for scripting, so we know when the weapon has been fired +=============== +*/ +void AICast_RecordWeaponFire( gentity_t *ent ) { + cast_state_t *cs; + float range; + + cs = AICast_GetCastState( ent->s.number ); + cs->lastWeaponFired = level.time; + cs->lastWeaponFiredWeaponNum = ent->client->ps.weapon; + VectorCopy( ent->r.currentOrigin, cs->lastWeaponFiredPos ); + + cs->weaponFireTimes[cs->lastWeaponFiredWeaponNum] = level.time; + + // do sighting + range = AICast_GetWeaponSoundRange( cs->lastWeaponFiredWeaponNum ); + + AICast_AudibleEvent( cs->entityNum, cs->lastWeaponFiredPos, range ); + //AICast_SightSoundEvent( cs, range ); + /* + if (!cs->sightSoundTime || cs->sightSoundRange < range) { + cs->sightSoundRange = range; + cs->sightSoundTime = level.time; + } + */ + + if ( cs->bs ) { // real player's don't need to play AI sounds + AIChar_AttackSound( cs ); + } +} + +/* +=============== +AICast_GetWeaponSoundRange +=============== +*/ +float AICast_GetWeaponSoundRange( int weapon ) { + // NOTE: made this a case, that way changing the ordering of weapons won't cause problems, as it would + // with an array lookup + + switch ( weapon ) { + case WP_NONE: + return 0; + case WP_KNIFE: + case WP_KNIFE2: + case WP_GAUNTLET: + case WP_SPEARGUN: + case WP_SPEARGUN_CO2: + case WP_STEN: + case WP_SILENCER: + return 64; + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_PROX: //----(SA) added + return 1500; + case WP_SNIPERRIFLE: + return 1500; + case WP_SNOOPERSCOPE: + return 128; + case WP_LUGER: + case WP_COLT: + case WP_AKIMBO: + return 700; + + case WP_MONSTER_ATTACK1: + case WP_MONSTER_ATTACK2: + case WP_MONSTER_ATTACK3: + // TODO: case for each monster + return 1000; + + case WP_GARAND: + case WP_MP40: + case WP_THOMPSON: + return 1000; + + case WP_BAR: + case WP_BAR2: + return 2000; //----(SA) added + + case WP_FG42: + case WP_FG42SCOPE: + return 1500; + + case WP_MAUSER: //----(SA) change for DM + return 1500; + + case WP_DYNAMITE: + case WP_DYNAMITE2: + return 3000; + + case WP_PANZERFAUST: + case WP_ROCKET_LAUNCHER: + case WP_VENOM: + case WP_VENOM_FULL: + case WP_FLAMETHROWER: + case WP_CROSS: + case WP_TESLA: + return 1000; + } + + G_Error( "AICast_GetWeaponSoundRange: unknown weapon index: %i\n", weapon ); + return 0; // shutup the compiler +} + +/* +=============== +AICast_StopAndAttack + + returns qtrue if they should go back to a battle state to attack, + qfalse if they should keep chasing while they attack (like the Zombie) +=============== +*/ +qboolean AICast_StopAndAttack( cast_state_t *cs ) { + float dist = -1; + + if ( cs->bs->enemy >= 0 ) { + dist = Distance( cs->bs->origin, g_entities[cs->bs->enemy].r.currentOrigin ); + } +/* + switch (cs->bs->weaponnum) { + + // removed + + } +*/ + return qtrue; +} + +/* +=============== +AICast_GetAccuracy +=============== +*/ +float AICast_GetAccuracy( int entnum ) { + #define AICAST_VARIABLE_ACC_ENABLED 1 + #define AICAST_ACC_VISTIME 4000 + #define AICAST_ACC_SCALE 0.4 + cast_state_t *cs; + float acc; + + cs = AICast_GetCastState( entnum ); + // the more they stay in our sights, the more accurate we get + acc = cs->attributes[AIM_ACCURACY]; + + if ( AICAST_VARIABLE_ACC_ENABLED ) { + if ( cs->bs->enemy >= 0 ) { + if ( cs->vislist[cs->bs->enemy].real_notvisible_timestamp < level.time - AICAST_ACC_VISTIME ) { + acc += AICAST_ACC_SCALE; + } else { + acc += AICAST_ACC_SCALE * ( (float)( level.time - cs->vislist[cs->bs->enemy].real_notvisible_timestamp ) / (float)( AICAST_ACC_VISTIME ) ); + } + + if ( acc > 1.0 ) { + acc = 1.0; + } else if ( acc < 0.0 ) { + acc = 0.0; + } + } + } + return ( acc ); +} + +/* +============== +AICast_WantToRetreat +============== +*/ +qboolean AICast_WantToRetreat( cast_state_t *cs ) { + int *ammo; + + ammo = cs->bs->cur_ps.ammo; + if ( g_entities[cs->entityNum].aiTeam != AITEAM_MONSTER ) { + if ( !cs->bs->weaponnum ) { + return qtrue; + } + if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + return qtrue; + } + } + + if ( cs->attributes[AGGRESSION] >= 1.0 ) { + return qfalse; + } + + if ( cs->leaderNum < 0 ) { + if ( ( cs->attributes[TACTICAL] > 0.11 + random() * 0.5 ) && + ( ( cs->bs->cur_ps.weaponTime > 500 ) || + ( ( cs->takeCoverTime < level.time - 100 ) && + ( AICast_WantsToTakeCover( cs, qtrue ) ) ) ) ) { + return qtrue; + } + } + // + return qfalse; +} + +/* +============== +AICast_SafeMissileFire + + checks to see if firing the missile will be successful, neutral, or dangerous to us or a friendly +============== +*/ +int AICast_SafeMissileFire( gentity_t *ent, int duration, int enemyNum, vec3_t enemyPos, int selfNum, vec3_t endPos ) { + int rval; + vec3_t org; + gentity_t *trav; + + if ( !G_PredictMissile( ent, duration, org, qtrue ) ) { + // not point firing, since it won't explode + return 0; + } + + if ( endPos ) { + VectorCopy( org, endPos ); + } + + // at end of life, so do radius damage + rval = ( Distance( org, enemyPos ) < ent->splashRadius ) && AICast_VisibleFromPos( org, ent->s.number, enemyPos, enemyNum, qfalse ); + if ( rval ) { + // don't hurt ourselves + // disabled, don't worry about us, we can get out the way in time (we hope!) + //if (Distance( org, g_entities[selfNum].r.currentOrigin ) < ent->splashRadius*1.5) + // return -1; + // make sure we don't injure a friendly + for ( trav = g_entities; trav < g_entities + g_maxclients.integer; trav++ ) { + if ( !trav->inuse ) { + continue; + } + if ( !trav->client ) { + continue; + } + if ( trav->health <= 0 ) { + continue; + } + if ( trav->s.number == selfNum ) { + continue; + } + if ( AICast_SameTeam( AICast_GetCastState( selfNum ), trav->s.number ) ) { + if ( Distance( org, trav->r.currentOrigin ) < ent->splashRadius ) { + return -1; + } + } + } + } + // if it overshot the mark + if ( !rval && Distance( g_entities[ent->r.ownerNum].r.currentOrigin, org ) > Distance( g_entities[ent->r.ownerNum].r.currentOrigin, enemyPos ) ) { + return -2; // so the AI can try aiming down a bit next time + } + // + return rval; +} + +/* +============= +AICast_CheckDangerousEntity + + check to see if the given entity can harm an AI character, if so, informs them + appropriately +============= +*/ +void AICast_CheckDangerousEntity( gentity_t *ent, int dangerFlags, float dangerDist, float tacticalLevel, float aggressionLevel, qboolean hurtFriendly ) { + vec3_t org, fwd, vec; + cast_state_t *cs, *dcs; + gentity_t *trav; + int i, endTime; + float dist; + // + // + if ( dangerFlags & DANGER_MISSILE ) { + // predict where the entity will explode + if ( !( endTime = G_PredictMissile( ent, ent->nextthink - level.time, org, qtrue ) ) ) { + return; // missile won't explode, so no danger + } + } else { + // just avoid it for a bit, then forget it + endTime = level.time + 1000; + VectorCopy( ent->r.currentOrigin, org ); + } + if ( dangerFlags & DANGER_CLIENTAIM ) { + AngleVectors( ent->client->ps.viewangles, fwd, NULL, NULL ); + } + // + if ( ent->client ) { + dcs = AICast_GetCastState( ent->s.number ); + } else { + dcs = NULL; + } + // + // see if this will hurt anyone + for ( trav = g_entities, cs = AICast_GetCastState( 0 ), i = 0; i < level.numPlayingClients; cs++, trav++ ) { + if ( !trav->inuse || !trav->client ) { + continue; + } + i++; // found a connected client + if ( trav == ent ) { // don't be afraid of ourself + continue; + } + if ( trav->health <= 0 ) { + continue; + } + if ( !cs->bs ) { // not an AI, they should look out for themselves + continue; + } + if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) { + continue; // absolutely no sight (or hear) information allowed + } + if ( !hurtFriendly && ent->s.number < MAX_CLIENTS && AICast_SameTeam( cs, ent->s.number ) ) { + continue; // trust that friends will not hurt us + } + if ( ( dangerFlags & DANGER_FLAMES ) && ( cs->aiFlags & AIFL_NO_FLAME_DAMAGE ) ) { + continue; // venom not effected by flames + } + if ( cs->attributes[TACTICAL] < tacticalLevel ) { // not smart enough + continue; + } + if ( cs->aiState >= AISTATE_COMBAT && cs->attributes[AGGRESSION] > aggressionLevel ) { // we are too aggressive to worry about being hurt by this + continue; + } + // if they are below alert mode, and the danger is not in FOV, then ignore it + if ( cs->aiState < AISTATE_ALERT ) { + vec3_t ang, dir; + VectorSubtract( ent->r.currentOrigin, cs->bs->origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, ang ); + if ( !AICast_InFieldOfVision( cs->bs->viewangles, cs->attributes[FOV], ang ) ) { + // can't see it + continue; + } + } + if ( ent->client && + ( !cs->vislist[ent->s.number].visible_timestamp || ( cs->vislist[ent->s.number].visible_timestamp < level.time - 3000 ) ) ) { + // (!dcs->vislist[trav->s.number].visible_timestamp || (dcs->vislist[trav->s.number].visible_timestamp < level.time - 3000)))) + continue; // not aware of them, and they're not aware of us + } + // are they in danger? + if ( cs->dangerEntityValidTime < level.time + 50 ) { + VectorSubtract( cs->bs->cur_ps.origin, org, vec ); + dist = VectorLength( vec ); + if ( dist < dangerDist ) { + if ( dangerFlags & DANGER_CLIENTAIM ) { + // also check aiming + if ( DotProduct( vec, fwd ) < ( dist * 0.95 - 100 ) ) { + continue; + } + } + // + cs->aiFlags &= ~AIFL_DENYACTION; + AICast_ScriptEvent( cs, "avoiddanger", ent->classname ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + continue; + } + cs->dangerEntity = ent->s.number; + VectorCopy( org, cs->dangerEntityPos ); + cs->dangerEntityValidTime = endTime + 50; + cs->dangerDist = dangerDist * 1.5; // when we hide from it, get a good distance away + cs->dangerEntityTimestamp = level.time; + } + } + } +} + + +qboolean AICast_HasFiredWeapon( int entNum, int weapon ) { + if ( AICast_GetCastState( entNum )->weaponFireTimes[weapon] ) { + return qtrue; + } + + return qfalse; +} + +qboolean AICast_AllowFlameDamage( int entNum ) { + // DHM - Nerve :: caststates are not initialized in multiplayer + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + return qtrue; + } + // dhm + + if ( caststates[entNum].aiFlags & AIFL_NO_FLAME_DAMAGE ) { + return qfalse; + } + return qtrue; +} + +/* +============= +AICast_ProcessBullet +============= +*/ +void AICast_ProcessBullet( gentity_t *attacker, vec3_t start, vec3_t end ) { + gentity_t *tent; + int i; + float dist; + vec3_t vProj, vDir, dir; + cast_state_t *cs; + + VectorSubtract( end, start, dir ); + + // RF, AI should hear this pass by them really closely, or hitting a wall close by + for ( cs = caststates, tent = g_entities, i = 0; i < level.maxclients; i++, tent++, cs++ ) { + if ( !tent->inuse ) { + continue; + } + if ( tent == attacker ) { + continue; + } + if ( tent->aiInactive ) { + continue; + } + if ( tent->health <= 0 ) { + continue; + } + if ( cs->castScriptStatus.scriptNoSightTime > level.time ) { + continue; + } + if ( !( tent->r.svFlags & SVF_CASTAI ) ) { + continue; + } + if ( cs->aiState >= AISTATE_COMBAT ) { // RF add // already fighting, not interested in bullet impacts + continue; + } + if ( cs->bulletImpactIgnoreTime > level.time ) { + continue; + } + dist = Distance( tent->client->ps.origin, end ); + if ( dist <= cs->attributes[INNER_DETECTION_RADIUS] ) { + // close enough to hear/see the impact? + // first check pvs + if ( !trap_InPVS( tent->client->ps.origin, end ) ) { + continue; + } + // heard it + goto heard; + } + // are they within radius of the bullet path to hear it travel through the air? + ProjectPointOntoVector( tent->client->ps.origin, start, end, vProj ); + VectorSubtract( vProj, start, vDir ); + if ( DotProduct( vDir, dir ) < 0 ) { // they are behind the path of the bullet + continue; + } + if ( Distance( vProj, tent->client->ps.origin ) > 0.5 * cs->attributes[INNER_DETECTION_RADIUS] ) { + continue; + } +heard: + // call the script event + AICast_ScriptEvent( cs, "bulletimpact", "" ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + continue; // ignore the bullet + } + // + cs->bulletImpactTime = level.time + 200 + rand() % 300; // random reaction delay; + VectorCopy( start, cs->bulletImpactStart ); + VectorCopy( end, cs->bulletImpactEnd ); + } +} + +/* +================ +AICast_AudibleEvent +================ +*/ +void AICast_AudibleEvent( int srcnum, vec3_t pos, float range ) { + int i; + cast_state_t *cs, *scs; + gentity_t *ent, *sent; + + // DHM - Nerve :: caststates are not initialized in multiplayer + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + return; + } + // dhm + + sent = &g_entities[srcnum]; + scs = AICast_GetCastState( srcnum ); + + for ( ent = g_entities, cs = caststates, i = 0; i < level.maxclients; i++, cs++, ent++ ) { + if ( !cs->bs ) { + continue; + } + //if (cs->aiState >= AISTATE_COMBAT) + // continue; + if ( ent == sent ) { + continue; + } + if ( cs->castScriptStatus.scriptNoSightTime > level.time ) { + continue; + } + if ( ent->health <= 0 ) { + continue; + } + // if within range, and this sound was made by an enemy + if ( scs->aiState < AISTATE_COMBAT && !AICast_QueryEnemy( cs, srcnum ) ) { + continue; + } + if ( Distance( pos, ent->s.pos.trBase ) > range ) { + continue; + } + // we heard it + cs->audibleEventTime = level.time + 200 + rand() % 300; // random reaction delay + VectorCopy( pos, cs->audibleEventOrg ); + cs->audibleEventEnt = ent->s.number; + } +} diff --git a/src/game/ai_cast_fight.h b/src/game/ai_cast_fight.h new file mode 100644 index 0000000..2376b6f --- /dev/null +++ b/src/game/ai_cast_fight.h @@ -0,0 +1,42 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_fight.h +// Function: Wolfenstein AI Fighting Values +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +// This is where all constants should reside, which decide when or if a character +// can attack it's enemy. + +#define LOPER_GROUND_RANGE 200 +#define BLACKGUARD_MELEE_RANGE 90 +#define REJECT_MELEE_RANGE 90 //----(SA) added diff --git a/src/game/ai_cast_func_attack.c b/src/game/ai_cast_func_attack.c new file mode 100644 index 0000000..a32bbb9 --- /dev/null +++ b/src/game/ai_cast_func_attack.c @@ -0,0 +1,1078 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_funcs.c +// Function: Wolfenstein AI Character Decision Making +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +//================================================================================= +// +// ZOMBIE SPECIAL ATTACKS +// +//================================================================================= + +/* +============ +AIFunc_ZombieFlameAttack() + + Zombie "Flaming Bats" attack. + + NOTE: this actually uses the EFFECT3 slot for client-side effects (others are taken) +============ +*/ + +#define ZOMBIE_FLAME_DURATION 4000 + +char *AIFunc_ZombieFlameAttack( cast_state_t *cs ) { + bot_state_t *bs; + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + bs = cs->bs; + // + ent->s.onFireEnd = level.time + 2000; + // + if ( ent->health < 0 ) { + ent->s.onFireEnd = 0; + return AIFunc_DefaultStart( cs ); + } + // + if ( cs->bs->enemy < 0 ) { + ent->s.onFireEnd = level.time + 1500; + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return AIFunc_DefaultStart( cs ); + } +/* disabled, keep going so they cant come back for the easy kill + // + // if we can't see them anymore, abort immediately + if (cs->vislist[cs->bs->enemy].real_visible_timestamp != cs->vislist[cs->bs->enemy].real_update_timestamp) { + ent->s.onFireEnd = level.time + 1500; + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return AIFunc_DefaultStart( cs ); + } +*/ + // if outside range, move closer + if ( VectorDistance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) > ZOMBIE_FLAME_RADIUS ) { + ent->s.onFireEnd = level.time + 1500; + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return AIFunc_DefaultStart( cs ); + } + // we are firing this weapon, so record it + cs->weaponFireTimes[WP_MONSTER_ATTACK1] = level.time; + // once an attack has started, only abort once the player leaves our view, or time runs out + if ( cs->thinkFuncChangeTime < level.time - ZOMBIE_FLAME_DURATION ) { + + // finish this attack + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return AIFunc_DefaultStart( cs ); + + } else { + + ent->client->ps.torsoTimer = 400; + //ent->client->ps.legsTimer = 400; + + // draw the client-side effect + ent->client->ps.eFlags |= EF_MONSTER_EFFECT3; + + // inform the client of our enemies position + //VectorCopy( g_entities[cs->bs->enemy].client->ps.origin, ent->s.origin2 ); + //ent->s.origin2[2] += g_entities[cs->bs->enemy].client->ps.viewheight; + + // keep facing them + AICast_AimAtEnemy( cs ); + + // look slightly downwards since animation is facing upwards slightly + cs->bs->ideal_viewangles[PITCH] += 10; + } + // + // + return NULL; +} + +char *AIFunc_ZombieFlameAttackStart( cast_state_t *cs ) { + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + ent->s.otherEntityNum2 = cs->bs->enemy; + ent->s.effect3Time = level.time; + // + // dont turn + cs->bs->ideal_viewangles[YAW] = cs->bs->viewangles[YAW]; + cs->bs->ideal_viewangles[PITCH] = -45; // look upwards + // start the flame + ent->s.onFireStart = level.time; + ent->s.onFireEnd = level.time + ZOMBIE_FLAME_DURATION; + // + // set the correct animation + BG_PlayAnimName( &ent->client->ps, "both_attack1", ANIM_BP_BOTH, qtrue, qfalse, qtrue ); + // + cs->aifunc = AIFunc_ZombieFlameAttack; + return "AIFunc_ZombieFlameAttack"; +} + + + +/* +============ +AIFunc_ZombieAttack2() + + Zombie "Evil Spirit" attack. + + Character draws the light from surrounding walls (expanding negative light) and builds + up to the release of a flying translucent skull with trail effect (and beady eyes). + + Spirit should track it's enemy slightly, inflicting lots of damage, removing sprint bar, + and effecting sight temporarily. + + Speed of spirit is effected by skill level, higher skill = faster speed + + Spirits inflicting AI soldiers should kill instantly, removing all flesh from the + soldier's face (draw skull under head model, then fade head model away over a short period). +============ +*/ +extern void weapon_zombiespirit( gentity_t *ent, gentity_t *missile ); + +#define ZOMBIE_SPIRIT_BUILDUP_TIME 10000 // last for this long +#define ZOMBIE_SPIRIT_FADEOUT_TIME 1000 +#define ZOMBIE_SPIRIT_DLIGHT_RADIUS_MAX 256 +#define ZOMBIE_SPIRIT_FIRE_INTERVAL 1000 + +int lastZombieSpiritAttack; + +char *AIFunc_ZombieAttack2( cast_state_t *cs ) { + bot_state_t *bs; + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + bs = cs->bs; + // + lastZombieSpiritAttack = level.time; + // + if ( cs->bs->enemy < 0 ) { + return AIFunc_DefaultStart( cs ); + } + // + // if we can't see them anymore, abort immediately + if ( cs->vislist[cs->bs->enemy].real_visible_timestamp != cs->vislist[cs->bs->enemy].real_update_timestamp ) { + return AIFunc_DefaultStart( cs ); + } + // we are firing this weapon, so record it + cs->weaponFireTimes[WP_MONSTER_ATTACK2] = level.time; + // once an attack has started, only abort once the player leaves our view, or time runs out + if ( cs->thinkFuncChangeTime < level.time - ZOMBIE_SPIRIT_BUILDUP_TIME ) { + // if enough time has elapsed, finish this attack + if ( level.time > cs->thinkFuncChangeTime + ZOMBIE_SPIRIT_BUILDUP_TIME + ZOMBIE_SPIRIT_FADEOUT_TIME ) { + return AIFunc_DefaultStart( cs ); + } + } else { + + // set torso to the correct animation + // TODO + //ent->client->ps.torsoTimer = 300; // leave enough time to cancel if we stop coming in here, but stay in the anim if we come back next thing + + // draw the client-side effect + ent->client->ps.eFlags |= EF_MONSTER_EFFECT; + + // inform the client of our enemies position + VectorCopy( g_entities[cs->bs->enemy].client->ps.origin, ent->s.origin2 ); + ent->s.origin2[2] += g_entities[cs->bs->enemy].client->ps.viewheight; + } + // + // + return NULL; +} + +char *AIFunc_ZombieAttack2Start( cast_state_t *cs ) { + gentity_t *ent; + // + // don't allow 2 consecutive spirit attacks at once + if ( lastZombieSpiritAttack > level.time || lastZombieSpiritAttack > level.time - 300 ) { + return NULL; + } + // + ent = &g_entities[cs->entityNum]; + ent->s.otherEntityNum2 = cs->bs->enemy; + ent->s.effect1Time = level.time; + // + // dont turn + cs->bs->ideal_viewangles[YAW] = cs->bs->viewangles[YAW]; + // set torso to the correct animation + // TODO + //ent->client->ps.torsoAnim = + // ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_SALUTE; // FIXME: need a specific anim for this + // + cs->aifunc = AIFunc_ZombieAttack2; + return "AIFunc_ZombieAttack2"; +} + +//================================================================================= +// +// LOPER MELEE ATTACK +// +//================================================================================= + +#define LOPER_MELEE_FPS 15 + +#define LOPER_MELEE_DAMAGE_FRAME 3 +#define LOPER_MELEE_DAMAGE_DELAY ( LOPER_MELEE_DAMAGE_FRAME*( 1000 / LOPER_MELEE_FPS ) ) + +#define LOPER_MELEE_FRAME_COUNT 11 +#define LOPER_MELEE_DURATION ( LOPER_MELEE_FRAME_COUNT*( 1000 / LOPER_MELEE_FPS ) ) + +#define NUM_LOPER_MELEE_ANIMS 2 +// TTimo unused +//static int loperMeleeAnims[NUM_LOPER_MELEE_ANIMS] = {LEGS_EXTRA1, LEGS_EXTRA2}; + +#define LOPER_MELEE_DAMAGE 20 +#define LOPER_MELEE_RANGE 48 + +/* +=============== +AIFunc_LoperAttack1() + + Loper's close range melee attack +=============== +*/ +char *AIFunc_LoperAttack1( cast_state_t *cs ) { + trace_t *tr; + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + // + // draw the client-side lightning effect + //ent->client->ps.eFlags |= EF_MONSTER_EFFECT; + // + // have we inflicted the damage? + if ( cs->weaponFireTimes[WP_MONSTER_ATTACK1] > cs->thinkFuncChangeTime ) { + // has the animation finished? + if ( cs->thinkFuncChangeTime < level.time - LOPER_MELEE_DURATION ) { + return AIFunc_DefaultStart( cs ); + } + return NULL; // just wait for anim to finish + } + // ready to inflict damage? + if ( cs->thinkFuncChangeTime < level.time - LOPER_MELEE_DAMAGE_DELAY ) { + // check for damage + // TTimo: assignment used as truth value + if ( ( tr = CheckMeleeAttack( &g_entities[cs->entityNum], LOPER_MELEE_RANGE, qfalse ) ) ) { + G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, + LOPER_MELEE_DAMAGE, 0, MOD_LOPER_HIT ); + } + cs->weaponFireTimes[WP_MONSTER_ATTACK1] = level.time; + } + + return NULL; +} + + +char *AIFunc_LoperAttack1Start( cast_state_t *cs ) { + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + // face them + AICast_AimAtEnemy( cs ); + // start the animation + //ent->client->ps.legsAnim = + // ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | loperMeleeAnims[rand()%NUM_LOPER_MELEE_ANIMS]; + //ent->client->ps.legsTimer = LOPER_MELEE_DURATION; + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_WEAPON, cs->bs->weaponnum, qtrue ); + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue ); + // play the sound + // TODO + // + cs->aifunc = AIFunc_LoperAttack1; + return "AIFunc_LoperAttack1"; +} + +//================================================================================= +// +// LOPER LEAP ATTACK +// +//================================================================================= + +#define LOPER_LEAP_ANIM LEGS_EXTRA3 +#define LOPER_LEAP_FPS 15 + +// update for a version of the loper new today (6/26) +//#define LOPER_LEAP_FRAME_COUNT 4 +#define LOPER_LEAP_FRAME_COUNT 10 +#define LOPER_LEAP_DURATION ( LOPER_LEAP_FRAME_COUNT*( 1000 / LOPER_LEAP_FPS ) ) + + +#define LOPER_LAND_ANIM LEGS_EXTRA4 +#define LOPER_LAND_FPS 15 + +// update for a version of the loper new today (6/26) +//#define LOPER_LAND_FRAME_COUNT 17 +#define LOPER_LAND_FRAME_COUNT 21 +#define LOPER_LAND_DURATION ( LOPER_LAND_FRAME_COUNT*( 1000 / LOPER_LAND_FPS ) ) + + +#define LOPER_LEAP_DAMAGE 8 +#define LOPER_LEAP_DELAY 100 +#define LOPER_LEAP_RANGE 90 +#define LOPER_LEAP_VELOCITY_START 400.0 +#define LOPER_LEAP_VELOCITY_END 650.0 +#define LOPER_LEAP_VELOCITY_Z 300 +#define LOPER_LEAP_LAND_MOMENTUM 250 + +/* +=============== +AIFunc_LoperAttack2() + + Loper's leaping long range attack +=============== +*/ +char *AIFunc_LoperAttack2( cast_state_t *cs ) { + gentity_t *ent; + vec3_t vec; + qboolean onGround = qfalse; + // + ent = &g_entities[cs->entityNum]; + // + // are we waiting to inflict damage? + if ( ( cs->weaponFireTimes[WP_MONSTER_ATTACK2] < level.time - 100 ) && + ( cs->bs->cur_ps.groundEntityNum == ENTITYNUM_NONE ) ) { + // ready to inflict damage? + if ( cs->thinkFuncChangeTime < level.time - LOPER_LEAP_DELAY ) { + // check for damage + if ( //(tr = CheckMeleeAttack(&g_entities[cs->entityNum], LOPER_LEAP_RANGE, qtrue)) && + ( G_RadiusDamage( cs->bs->origin, ent, LOPER_LEAP_DAMAGE, LOPER_LEAP_RANGE, ent, MOD_LOPER_LEAP ) ) ) { + // draw the client-side lightning effect + ent->client->ps.eFlags |= EF_MONSTER_EFFECT; + // do the damage + //G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, + // LOPER_LEAP_DAMAGE, 0, MOD_LOPER_LEAP ); + G_Sound( &g_entities[cs->entityNum], level.loperZapSound ); + //cs->weaponFireTimes[WP_MONSTER_ATTACK2] = level.time; + // TODO: client-side visual effect + // TODO: throw them backwards (away from us) + } + } + } + // + // landed? + if ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) { + onGround = qtrue; + } else { // predict a landing + aicast_predictmove_t move; + float changeTime; + AICast_PredictMovement( cs, 1, 0.2, &move, &cs->bs->lastucmd, cs->bs->enemy ); + if ( move.groundEntityNum != ENTITYNUM_NONE ) { + onGround = qtrue; + } + // + // adjust velocity + VectorCopy( g_entities[cs->entityNum].s.pos.trDelta, vec ); + vec[2] = 0; + VectorNormalize( vec ); + changeTime = 2.0 * ( 0.001 * ( level.time - cs->thinkFuncChangeTime ) ); + if ( changeTime > 1.0 ) { + changeTime = 1.0; + } + VectorScale( vec, LOPER_LEAP_VELOCITY_START + changeTime * ( LOPER_LEAP_VELOCITY_END - LOPER_LEAP_VELOCITY_START ), vec ); + g_entities[cs->entityNum].s.pos.trDelta[0] = vec[0]; + g_entities[cs->entityNum].s.pos.trDelta[1] = vec[1]; + } + // + if ( onGround || ( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { + // if we just started the attack recently, we probably haven't had a chance to get airborne yet + if ( cs->thinkFuncChangeTime < level.time - LOPER_LEAP_DELAY ) { + // loper is back on ground, wait for animation to play out + if ( !( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { + ent->client->ps.legsAnim = + ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | LOPER_LAND_ANIM; + ent->client->ps.legsTimer = LOPER_LAND_DURATION; + // + cs->aiFlags |= AIFL_LAND_ANIM_PLAYED; + // TODO:play the landing thud + } + // + if ( !ent->client->ps.legsTimer ) { // we're done + return AIFunc_DefaultStart( cs ); + } + // keep moving slightly in our facing direction to simulate landing momentum + AngleVectors( cs->bs->viewangles, vec, NULL, NULL ); + trap_EA_Move( cs->entityNum, vec, ( (float)ent->client->ps.legsTimer / (float)LOPER_LAND_DURATION ) * (float)LOPER_LEAP_LAND_MOMENTUM ); + return NULL; + } + } + return NULL; +} + +char *AIFunc_LoperAttack2Start( cast_state_t *cs ) { + gentity_t *ent; + vec3_t vec, avec; + // + ent = &g_entities[cs->entityNum]; + // + // face them + AICast_AimAtEnemy( cs ); + // if not facing them yet, wait + VectorSubtract( cs->vislist[cs->bs->enemy].real_visible_pos, cs->bs->origin, vec ); + VectorNormalize( vec ); + AngleVectors( cs->bs->viewangles, avec, NULL, NULL ); + if ( DotProduct( vec, avec ) < 0.95 ) { + //cs->aifunc = AIFunc_LoperAttack2Start; + return NULL; + } + // OK, start the animation + ent->client->ps.legsAnim = + ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | LOPER_LEAP_ANIM; + ent->client->ps.legsTimer = 20000; // stay on this until landing + // send us hurtling towards our enemy + VectorScale( vec, LOPER_LEAP_VELOCITY_START, vec ); + vec[2] = LOPER_LEAP_VELOCITY_Z; + VectorCopy( vec, ent->client->ps.velocity ); + // + cs->aiFlags &= ~AIFL_LAND_ANIM_PLAYED; + // play the sound + // TODO + // + cs->aifunc = AIFunc_LoperAttack2; + return "AIFunc_LoperAttack2"; +} + +//================================================================================= +// +// LOPER GROUND ATTACK +// +//================================================================================= + +#define LOPER_GROUND_ANIM LEGS_EXTRA5 +#define LOPER_GROUND_FPS 10 + +#define LOPER_GROUND_FRAME_COUNT 8 +#define LOPER_GROUND_DURATION ( LOPER_GROUND_FRAME_COUNT*( 1000 / LOPER_GROUND_FPS ) ) + +#define LOPER_GROUND_DAMAGE 20 +#define LOPER_GROUND_DELAY 5000 + +/* +=============== +AIFunc_LoperAttack3() + + Loper's ground electrical attack +=============== +*/ +char *AIFunc_LoperAttack3( cast_state_t *cs ) { + gentity_t *ent; + qboolean hitClient = qfalse; + // + ent = &g_entities[cs->entityNum]; + // + // done with this attack? + if ( cs->thinkFuncChangeTime < level.time - LOPER_GROUND_DELAY ) { + cs->pauseTime = level.time + 600; // don't move until effect is done + ent->client->ps.legsTimer = 600; // stay down until effect is done + return AIFunc_DefaultStart( cs ); + } + // ready to inflict damage? + if ( cs->thinkFuncChangeTime < level.time - 900 ) { + // + // draw the client-side lightning effect + ent->client->ps.eFlags |= EF_MONSTER_EFFECT3; + //ent->s.effect3Time = level.time + 500;//cs->thinkFuncChangeTime + LOPER_GROUND_DELAY - 200; + // + // are we waiting to inflict damage? + if ( cs->weaponFireTimes[WP_MONSTER_ATTACK3] < level.time - 100 ) { + // check for damage + hitClient = G_RadiusDamage( cs->bs->origin, ent, LOPER_GROUND_DAMAGE, LOPER_GROUND_RANGE, ent, MOD_LOPER_GROUND ); + // + cs->weaponFireTimes[WP_MONSTER_ATTACK3] = level.time; + // TODO: client-side visual effect + // TODO: throw them backwards (away from us) + } else { + hitClient = qtrue; // so we don't abort + } + // + if ( !hitClient && cs->thinkFuncChangeTime < ( level.time - 1500 ) ) { // we're done with this attack + cs->pauseTime = level.time + 600; // don't move until effect is done + ent->client->ps.legsTimer = 600; // stay down until effect is done + return AIFunc_DefaultStart( cs ); + } + } + // + if ( ent->client->ps.legsTimer < 1000 ) { + ent->client->ps.legsTimer = 1000; // stay down until effect is done + } + return NULL; +} + +char *AIFunc_LoperAttack3Start( cast_state_t *cs ) { + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + // + // face them + AICast_AimAtEnemy( cs ); + // play the animation + ent->client->ps.legsAnim = + ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | LOPER_GROUND_ANIM; + ent->client->ps.legsTimer = LOPER_GROUND_DELAY; // stay down until attack is finished + // + // play the buildup sound + // TODO + // + cs->aifunc = AIFunc_LoperAttack3; + return "AIFunc_LoperAttack3"; +} + +//================================================================================= +// +// STIM SOLDIER FLYING ATTACK +// +//================================================================================= + +#define STIMSOLDIER_FLYJUMP_ANIM LEGS_EXTRA1 +#define STIMSOLDIER_FLYJUMP_FPS 15 +#define STIMSOLDIER_FLYJUMP_FRAME_COUNT 28 +#define STIMSOLDIER_FLYJUMP_DURATION ( STIMSOLDIER_FLYJUMP_FRAME_COUNT*( 1000 / STIMSOLDIER_FLYJUMP_FPS ) ) +#define STIMSOLDIER_FLYJUMP_DELAY ( STIMSOLDIER_FLYJUMP_DURATION + 3000 ) + +// hover plays continuously +#define STIMSOLDIER_FLYHOVER_ANIM LEGS_EXTRA2 +#define STIMSOLDIER_FLYHOVER_FPS 5 + +#define STIMSOLDIER_FLYLAND_ANIM LEGS_LAND +#define STIMSOLDIER_FLYLAND_FPS 15 +#define STIMSOLDIER_FLYLAND_FRAME_COUNT 14 +#define STIMSOLDIER_FLYLAND_DURATION ( STIMSOLDIER_FLYLAND_FRAME_COUNT*( 1000 / STIMSOLDIER_FLYLAND_FPS ) ) + +#define STIMSOLDIER_STARTJUMP_DELAY ( STIMSOLDIER_FLYJUMP_DURATION*0.5 ) + +char *AIFunc_StimSoldierAttack1( cast_state_t *cs ) { + gentity_t *ent; + vec3_t vec; + static vec3_t up = {0,0,1}; + // + ent = &g_entities[cs->entityNum]; + cs->weaponFireTimes[WP_MONSTER_ATTACK1] = level.time; + // face them + AICast_AimAtEnemy( cs ); + // + // are we done with this attack? + if ( cs->thinkFuncChangeTime < level.time - STIMSOLDIER_FLYJUMP_DELAY ) { + // have we hit the ground yet? + if ( ent->s.groundEntityNum != ENTITYNUM_NONE ) { + // we are on something, have we started the landing animation? + if ( !( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { + ent->client->ps.legsAnim = + ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | STIMSOLDIER_FLYLAND_ANIM; + ent->client->ps.legsTimer = STIMSOLDIER_FLYLAND_DURATION; // stay down until attack is finished + // + cs->noAttackTime = level.time + STIMSOLDIER_FLYLAND_DURATION; + cs->aiFlags |= AIFL_LAND_ANIM_PLAYED; + } else { + if ( !ent->client->ps.legsTimer ) { // animation has finished, resume AI + return AIFunc_DefaultStart( cs ); + } + } + } else { + // still flying + } + return NULL; + } + // + // are we ready to start flying? + if ( cs->thinkFuncChangeTime < ( level.time - STIMSOLDIER_STARTJUMP_DELAY ) ) { + if ( !ent->client->ps.powerups[PW_FLIGHT] ) { + // play a special ignition sound? + } + ent->client->ps.powerups[PW_FLIGHT] = 1; // let them fly + ent->s.loopSound = level.stimSoldierFlySound; + ent->client->ps.eFlags |= EF_MONSTER_EFFECT; // client-side stim engine effect + if ( ent->s.effect1Time != ( cs->thinkFuncChangeTime + STIMSOLDIER_STARTJUMP_DELAY ) ) { + ent->s.effect1Time = ( cs->thinkFuncChangeTime + STIMSOLDIER_STARTJUMP_DELAY ); + // start the hovering animation + ent->client->ps.legsAnim = + ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | STIMSOLDIER_FLYHOVER_ANIM; + } + // give us some upwards velocity? + if ( cs->thinkFuncChangeTime > level.time - STIMSOLDIER_FLYJUMP_DURATION * 0.9 ) { + trap_EA_Move( cs->entityNum, up, 300 ); + //trap_EA_Jump(cs->entityNum); + VectorCopy( cs->bs->origin, cs->stimFlyAttackPos ); + } else { + // attack them + // + // if we can't attack, abort + if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) { + // apply weapons.. + trap_EA_Attack( cs->entityNum ); + } + // we're done here + cs->thinkFuncChangeTime = -9999; + } + } else { + // still on ground, so move forward to account for stepping animation + AngleVectors( cs->bs->viewangles, vec, NULL, NULL ); + trap_EA_Move( cs->entityNum, vec, 300 ); + } + // + if ( ent->client->ps.legsTimer < 1000 ) { + ent->client->ps.legsTimer = 1000; // stay down until effect is done + } + // + return NULL; +} + +char *AIFunc_StimSoldierAttack1Start( cast_state_t *cs ) { + gentity_t *ent; + //static vec3_t mins={-96,-96,0}, maxs={96,96,72}; + vec3_t pos, dir; + trace_t tr; + // + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + ent = &g_entities[cs->entityNum]; + // + // face them + AICast_AimAtEnemy( cs ); + // first, check if this is a good place to start the flying attack + AngleVectors( cs->bs->ideal_viewangles, dir, NULL, NULL ); + VectorMA( cs->bs->origin, 300, dir, pos ); + pos[2] += 128; + trap_Trace( &tr, cs->bs->origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, pos, cs->entityNum, MASK_PLAYERSOLID ); + if ( tr.startsolid || tr.allsolid ) { + return NULL; // not a good place + } + // check we can attack them from there + // select our special weapon (rocket launcher or tesla) + if ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_ROCKET_LAUNCHER ) ) { + cs->bs->weaponnum = WP_ROCKET_LAUNCHER; + } else if ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_TESLA ) ) { + cs->bs->weaponnum = WP_TESLA; + } else { // no weapon? + G_Error( "stim soldier tried special jump attack without a tesla or rocket launcher\n" ); + } + if ( !AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, pos, qfalse, qfalse ) ) { + AICast_ChooseWeapon( cs, qfalse ); + return NULL; + } + // play the animation + ent->client->ps.legsAnim = + ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | STIMSOLDIER_FLYJUMP_ANIM; + ent->client->ps.legsTimer = STIMSOLDIER_FLYJUMP_DELAY; // stay down until attack is finished + // + cs->aiFlags &= ~AIFL_LAND_ANIM_PLAYED; + // play the buildup sound + // TODO + // + cs->aifunc = AIFunc_StimSoldierAttack1; + return "AIFunc_StimSoldierAttack1"; +} + +//================================================================================= +// +// STIM SOLDIER DUAL MACHINEGUN ATTACK +// +//================================================================================= + +char *AIFunc_StimSoldierAttack2( cast_state_t *cs ) { + return NULL; +} + +char *AIFunc_StimSoldierAttack2Start( cast_state_t *cs ) { + gentity_t *ent; + // + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + ent = &g_entities[cs->entityNum]; + // + // face them + AICast_AimAtEnemy( cs ); + // TODO! + G_Printf( "TODO: stim dual machinegun attack\n" ); + // + cs->aifunc = AIFunc_StimSoldierAttack2; + return "AIFunc_StimSoldierAttack2"; +} + +//================================================================================= +// +// BLACK GUARD MELEE KICK ATTACK +// +//================================================================================= + +char *AIFunc_BlackGuardAttack1( cast_state_t *cs ) { + return NULL; +} + +char *AIFunc_BlackGuardAttack1Start( cast_state_t *cs ) { + gentity_t *ent; + // + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + +// TODO! + G_Printf( "TODO: black guard kick attack\n" ); + return NULL; + + // + ent = &g_entities[cs->entityNum]; + // + // face them + AICast_AimAtEnemy( cs ); + // + cs->aifunc = AIFunc_BlackGuardAttack1; + return "AIFunc_BlackGuardAttack1"; +} + + +//================================================================================= +// +// REJECT X-CREATURE +// +// Attacks are: backhand slap, blowtorch (small flamethrower) +// +//================================================================================= + +////// Backhand attack (slap) + +/* +============== +AIFunc_RejectAttack1 +============== +*/ +char *AIFunc_RejectAttack1( cast_state_t *cs ) { + return NULL; +} + +/* +============== +AIFunc_RejectAttack1Start +============== +*/ +char *AIFunc_RejectAttack1Start( cast_state_t *cs ) { + gentity_t *ent; + + ent = &g_entities[cs->entityNum]; + ent->s.effect1Time = level.time; + cs->bs->ideal_viewangles[YAW] = cs->bs->viewangles[YAW]; + cs->aifunc = AIFunc_RejectAttack1; + return "AIFunc_RejectAttack1"; +} + + +//================================================================================= +// +// WARRIOR ZOMBIE +// +// Standing melee attacks +// +//================================================================================= + +int warriorHitDamage[5] = { + 16, + 16, + 16, + 12, + 20 +}; + +#define NUM_WARRIOR_ANIMS 5 +int warriorHitTimes[NUM_WARRIOR_ANIMS][3] = { // up to three hits per attack + {ANIMLENGTH( 10,20 ),-1}, + {ANIMLENGTH( 15,20 ),-1}, + {ANIMLENGTH( 18,20 ),-1}, + {ANIMLENGTH( 15,20 ),-1}, + {ANIMLENGTH( 14,20 ),-1}, +}; + +/* +================ +AIFunc_WarriorZombieMelee +================ +*/ +char *AIFunc_WarriorZombieMelee( cast_state_t *cs ) { + gentity_t *ent = &g_entities[cs->entityNum]; + int hitDelay = -1, anim; + trace_t *tr; + + if ( !ent->client->ps.torsoTimer ) { + return AIFunc_DefaultStart( cs ); + } + + anim = ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) - BG_AnimationIndexForString( "attack1", cs->entityNum ); + if ( anim < 0 || anim >= NUM_WARRIOR_ANIMS ) { + // animation interupted + return AIFunc_DefaultStart( cs ); + //G_Error( "AIFunc_WarriorZombieMelee: warrior using invalid or unknown attack anim" ); + } + if ( warriorHitTimes[anim][cs->animHitCount] >= 0 ) { + + // face them + AICast_AimAtEnemy( cs ); + + if ( !cs->animHitCount ) { + hitDelay = warriorHitTimes[anim][cs->animHitCount]; + } else { + hitDelay = warriorHitTimes[anim][cs->animHitCount] - warriorHitTimes[anim][cs->animHitCount - 1]; + } + + // check for inflicting damage + if ( level.time - cs->weaponFireTimes[cs->bs->weaponnum] > hitDelay ) { + // do melee damage + if ( ( tr = CheckMeleeAttack( ent, 48, qfalse ) ) && ( tr->entityNum == cs->bs->enemy ) ) { + G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, + warriorHitDamage[anim], 0, MOD_GAUNTLET ); + } + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].staySoundScript ) ); + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + cs->animHitCount++; + } else { + // if they are outside range, move forward + if ( anim != 4 && !CheckMeleeAttack( ent, 48, qfalse ) ) { + //ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; // allow legs us to move + //return AIFunc_DefaultStart(cs); + trap_EA_MoveForward( cs->entityNum ); + } + } + } + + return NULL; +} + +/* +================ +AIFunc_WarriorZombieMeleeStart +================ +*/ +char *AIFunc_WarriorZombieMeleeStart( cast_state_t *cs ) { + gentity_t *ent; + + ent = &g_entities[cs->entityNum]; + ent->s.effect1Time = level.time; + cs->bs->ideal_viewangles[YAW] = cs->bs->viewangles[YAW]; + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + cs->animHitCount = 0; + + // face them + AICast_AimAtEnemy( cs ); + + // play an anim + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_WEAPON, cs->bs->weaponnum, qtrue ); + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue ); + + // stop charging + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_CHARGING, 0, qfalse ); + ent->flags &= ~FL_WARZOMBIECHARGE; + + cs->aifunc = AIFunc_WarriorZombieMelee; + return "AIFunc_WarriorZombieMelee"; +} + +// Warrior "sight" animation +/* +================ +AIFunc_WarriorZombieSight +================ +*/ +char *AIFunc_WarriorZombieSight( cast_state_t *cs ) { + gentity_t *ent = &g_entities[cs->entityNum]; + + if ( !ent->client->ps.torsoTimer ) { + return AIFunc_DefaultStart( cs ); + } + return NULL; +} + +/* +================ +AIFunc_WarriorZombieSightStart +================ +*/ +char *AIFunc_WarriorZombieSightStart( cast_state_t *cs ) { + gentity_t *ent; + +// RF, disabled + return NULL; + + ent = &g_entities[cs->entityNum]; + cs->bs->ideal_viewangles[YAW] = cs->bs->viewangles[YAW]; + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + + // face them + AICast_AimAtEnemy( cs ); + + // anim + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_FIRSTSIGHT, qfalse, qtrue ); + //BG_PlayAnimName( &ent->client->ps, "first_sight", ANIM_BP_BOTH, qtrue, qfalse, qtrue ); + + cs->aifunc = AIFunc_WarriorZombieSight; + return "AIFunc_WarriorZombieSight"; +} + +/* +================ +AIFunc_WarriorZombieDefense +================ +*/ +char *AIFunc_WarriorZombieDefense( cast_state_t *cs ) { + gentity_t *ent, *enemy; + vec3_t enemyDir, vec; + float dist; + + ent = &g_entities[cs->entityNum]; + + if ( !( ent->flags & FL_DEFENSE_GUARD ) ) { + if ( cs->weaponFireTimes[cs->bs->weaponnum] < level.time - 100 ) { + return AIFunc_DefaultStart( cs ); + } + return NULL; + } + + if ( cs->bs->enemy < 0 ) { + ent->flags &= ~FL_DEFENSE_GUARD; + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return NULL; + } + + enemy = &g_entities[cs->bs->enemy]; + + if ( cs->thinkFuncChangeTime < level.time - 1500 ) { + // if we cant see them + if ( !AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) ) { + ent->flags &= ~FL_DEFENSE_GUARD; + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return NULL; + } + + // if our enemy isn't using a dangerous weapon + if ( enemy->client->ps.weapon < WP_LUGER || enemy->client->ps.weapon > WP_CROSS ) { + ent->flags &= ~FL_DEFENSE_GUARD; + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return NULL; + } + + // if our enemy isn't looking right at us, abort + VectorSubtract( ent->client->ps.origin, enemy->client->ps.origin, vec ); + dist = VectorNormalize( vec ); + if ( dist > 512 ) { + dist = 512; + } + AngleVectors( enemy->client->ps.viewangles, enemyDir, NULL, NULL ); + if ( DotProduct( vec, enemyDir ) < ( 0.98 - 0.2 * ( dist / 512 ) ) ) { + ent->flags &= ~FL_DEFENSE_GUARD; + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return NULL; + } + } + + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + + if ( !ent->client->ps.torsoTimer ) { + ent->flags &= ~FL_DEFENSE_GUARD; + ent->client->ps.torsoTimer = 0; + ent->client->ps.legsTimer = 0; + return NULL; + } + + // face them + AICast_AimAtEnemy( cs ); + // crouching position, use smaller bounding box + trap_EA_Crouch( cs->bs->client ); + + return NULL; +} + +/* +================ +AIFunc_WarriorZombieDefenseStart +================ +*/ +char *AIFunc_WarriorZombieDefenseStart( cast_state_t *cs ) { + gentity_t *ent, *enemy; + vec3_t enemyDir, vec; + float dist; + + ent = &g_entities[cs->entityNum]; + enemy = &g_entities[cs->bs->enemy]; + + // if our enemy isn't using a dangerous weapon + if ( enemy->client->ps.weapon < WP_LUGER || enemy->client->ps.weapon > WP_CROSS ) { + return NULL; + } + + // if our enemy isn't looking right at us, abort + VectorSubtract( ent->client->ps.origin, enemy->client->ps.origin, vec ); + dist = VectorNormalize( vec ); + if ( dist > 512 ) { + dist = 512; + } + if ( dist < 128 ) { + return NULL; + } + AngleVectors( enemy->client->ps.viewangles, enemyDir, NULL, NULL ); + if ( DotProduct( vec, enemyDir ) < ( 0.98 - 0.2 * ( dist / 512 ) ) ) { + return NULL; + } + + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + + // face them + AICast_AimAtEnemy( cs ); + + // anim + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_WEAPON, cs->bs->weaponnum, qtrue ); + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue ); + ent->client->ps.torsoTimer = 3000; + ent->client->ps.legsTimer = 3000; + + ent->flags |= FL_DEFENSE_GUARD; + + // when they come out of defense mode, go into charge mode + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_CHARGING, 1, qfalse ); + ent->flags |= FL_WARZOMBIECHARGE; + + cs->aifunc = AIFunc_WarriorZombieDefense; + return "AIFunc_WarriorZombieDefense"; +} diff --git a/src/game/ai_cast_func_boss1.c b/src/game/ai_cast_func_boss1.c new file mode 100644 index 0000000..0dc2e09 --- /dev/null +++ b/src/game/ai_cast_func_boss1.c @@ -0,0 +1,555 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_funcs.c +// Function: Wolfenstein AI Character Decision Making +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +//================================================================================= +// +// Helga (in zombie form), the first boss +// +//================================================================================= + +void AICast_FZombie_StartLightning( gentity_t *ent ) { + ent->AIScript_AlertEntity = NULL; + AIFunc_FZombie_LightningAttackStart( AICast_GetCastState( ent->s.number ) ); +} + +/* +=============== +AIFunc_FZombie_Idle +=============== +*/ +char *AIFunc_FZombie_Idle( cast_state_t *cs ) { + gentity_t *ent = &g_entities[cs->entityNum]; + // + if ( cs->thinkFuncChangeTime < level.time - PORTAL_FEMZOMBIE_SPAWNTIME ) { + // HACK, make them aware of the player + cs->castScriptStatus.scriptNoSightTime = 0; + AICast_UpdateVisibility( &g_entities[cs->entityNum], AICast_FindEntityForName( "player" ), qfalse, qtrue ); + ent->s.time2 = 0; // turn spawning effect off + // allow us to be informed to start the portal lightning + ent->AIScript_AlertEntity = AICast_FZombie_StartLightning; + return AIFunc_DefaultStart( cs ); + } + // + return NULL; +} + +/* +=============== +AIFunc_FZombie_IdleStart +=============== +*/ +char *AIFunc_FZombie_IdleStart( cast_state_t *cs ) { + cs->aifunc = AIFunc_FZombie_Idle; + return "AIFunc_FZombie_Idle"; +} + +/* +=============== +AICast_FZombie_EndLightning +=============== +*/ +void AICast_FZombie_EndLightning( gentity_t *ent ) { + ent->s.effect2Time = level.time; + // allow us to be informed to start the portal lightning + ent->AIScript_AlertEntity = AICast_FZombie_StartLightning; +} + +/* +=============== +AIFunc_FZombie_LightningAttack + + The big portal lightning effect. While this is going on, the FemZombie will climb + across the walls like an insect. +=============== +*/ +char *AIFunc_FZombie_LightningAttack( cast_state_t *cs ) { + bot_state_t *bs; + gentity_t *ent, *marker, *trav; + // TTimo gcc: 'best' might be used uninitialized in this function + gentity_t *best = NULL; + qboolean move; + float bestdist, dist; + vec3_t axis[3]; + cast_state_t *ecs; + // + ent = &g_entities[cs->entityNum]; + bs = cs->bs; + trav = AICast_FindEntityForName( "player" ); + if ( !trav ) { + return NULL; // huh? + } + cs->bs->enemy = trav->s.number; + ecs = AICast_GetCastState( cs->bs->enemy ); + // + // we should show a big lightning effect and then die once we've given the player enough time to get to us + // + ent->s.effect1Time = cs->thinkFuncChangeTime; + ent->client->ps.eFlags |= EF_MONSTER_EFFECT; + // + // TODO: might be cool to have the head move around a bit faster (like an insect?) + g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK; + // + // turn on flight only if we need it this frame + ent->client->ps.powerups[PW_FLIGHT] = 0; + // + // has this effect finished? + if ( ent->s.effect2Time ) { + + // we are in climb-down mode as lightning subsides + if ( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD ) { + + return AIFunc_DefaultStart( cs ); + + } else { // climb down + + if ( cs->followEntity == -1 ) { // find the closest marker and go there + + // assume we are on the upper wall, and have just reached a marker + trav = NULL; + // TTimo assignment used as truth value + while ( ( trav = G_Find( trav, FOFS( classname ), "ai_marker" ) ) ) { + if ( trav->count == ent->count && trav->targetname ) { + cs->followEntity = trav->s.number; + return AIFunc_FZombie_LightningAttack( cs ); // think again + } + } + if ( !trav ) { + G_Error( "AIFunc_FZombie_LightningAttack: unable to find matching wall marker for count = %i", ent->count ); + } + + } else if ( cs->followEntity == -2 ) { // at the base of wall, rotate around then dismount + + ent->client->ps.eFlags |= EF_FORCED_ANGLES; // face angles exactly + cs->bs->ideal_viewangles[ROLL] = 0; + + if ( fabs( cs->bs->viewangles[ROLL] ) < 5 ) { // we are done, dismount and get outta here + ent->client->ps.powerups[PW_FLIGHT] = 0; + return AIFunc_DefaultStart( cs ); + } + + ent->client->ps.powerups[PW_FLIGHT] = 1; // stay here + return NULL; + } + + } + + } + + if ( cs->followEntity >= 0 ) { // move towards our current goal + + marker = &g_entities[cs->followEntity]; + + ent->count = marker->count; + + if ( !ent->s.effect2Time && marker->targetname ) { // ground marker + + dist = VectorDistance( cs->bs->origin, marker->s.origin ); + if ( dist < 4 ) { + // we made it there, find the corresponding wall marker + trav = NULL; + // TTimo gcc: suggest parentheses around assignment used as truth value + while ( ( trav = G_Find( trav, FOFS( classname ), "ai_marker" ) ) ) { + if ( trav->count == marker->count && !trav->targetname ) { + cs->followEntity = trav->s.number; + return AIFunc_FZombie_LightningAttack( cs ); // think again + } + } + if ( !trav ) { + G_Error( "AIFunc_FZombie_LightningAttack: unable to find matching wall marker for count = %i", marker->count ); + } + } + + cs->followSlowApproach = qtrue; + AICast_MoveToPos( cs, marker->s.origin, cs->followEntity ); + + // should we slow down? + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 8 ); + + // check for a movement we should be making + if ( cs->obstructingTime > level.time ) { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + cs->movestate = MS_WALK; + cs->movestateType = MSTYPE_TEMPORARY; + } + + } else { // in-air + + if ( !ent->s.effect2Time && ( ecs->aiFlags & AIFL_ROLL_ANIM ) ) { + // hurt player if they are visible from portal + ent->client->ps.eFlags |= EF_MONSTER_EFFECT2; + } + + dist = VectorDistance( cs->bs->origin, marker->s.origin ); + if ( dist < 20 ) { + // we made it there, stop + if ( ent->s.effect2Time && marker->targetname ) { + cs->followEntity = -2; + } else { + cs->followEntity = -1; + } + return NULL; + } + + // climb the walls + + ent->client->ps.powerups[PW_FLIGHT] = 1; // let them fly + ent->client->ps.eFlags |= EF_FORCED_ANGLES; // face angles exactly + + if ( ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) != BOTH_CLIMB ) { + ent->client->ps.torsoAnim = + ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_CLIMB; + ent->client->ps.legsAnim = + ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_CLIMB; + } + ent->client->ps.torsoTimer = 500; + ent->client->ps.legsTimer = 500; + + // should we slow down? + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 32 ); + + // use angles of the marker, but ROLL so we point upwards towards the marker + // fwd is the marker angles, up is the vec, right is the cross product + AngleVectors( marker->s.angles, axis[0], NULL, NULL ); + VectorSubtract( marker->s.origin, cs->bs->origin, axis[2] ); + VectorNormalize( axis[2] ); + //if (ent->s.effect2Time && marker->targetname) { // walk downwards + //VectorInverse( axis[2] ); + // VectorSet( axis[2], 0, 0, 1 ); + //} + CrossProduct( axis[0], axis[2], axis[1] ); + VectorInverse( axis[1] ); + AxisToAngles( axis, cs->bs->ideal_viewangles ); + + // movement + //if (ent->s.effect2Time && marker->targetname) { // walk downwards + // trap_EA_MoveDown(cs->entityNum); + //} else { + trap_EA_Jump( cs->entityNum ); + //} + trap_EA_Move( cs->entityNum, axis[0], 20 ); // move towards the wall so we stay attached to it + } + + } else { // we are motionless on the wall.. keep checking to see if we should head somewhere else + + if ( !ent->s.effect2Time && ( ecs->aiFlags & AIFL_ROLL_ANIM ) ) { + // hurt player if they are visible from portal + ent->client->ps.eFlags |= EF_MONSTER_EFFECT2; + } + + ent->client->ps.powerups[PW_FLIGHT] = 1; // let them fly + ent->client->ps.eFlags |= EF_FORCED_ANGLES; // face angles exactly + + move = qfalse; + + if ( cs->lastPain >= cs->lastThink ) { // we've been injured, move + cs->lastPain = 0; + move = qtrue; + } else if ( AICast_VisibleFromPos( g_entities[cs->bs->enemy].client->ps.origin, cs->bs->enemy, cs->bs->origin, cs->entityNum, qfalse ) ) { + move = qtrue; + } + + if ( move ) { // we need to start moving again + trav = NULL; + bestdist = -1; + while ( ( trav = G_Find( trav, FOFS( classname ), "ai_marker" ) ) ) { + if ( trav->targetname ) { // floor marker + continue; + } + if ( VectorDistance( cs->bs->origin, trav->s.origin ) < 48 ) { + continue; + } + if ( ( trav->count < 10 ) == ( ent->count < 10 ) ) { // this marker is on our wall + // if this marker no visible from the enemy + if ( !AICast_VisibleFromPos( g_entities[cs->bs->enemy].client->ps.origin, cs->bs->enemy, trav->s.origin, cs->entityNum, qfalse ) ) { + cs->followEntity = trav->s.number; + break; + } + dist = VectorDistance( trav->s.origin, g_entities[cs->bs->enemy].client->ps.origin ); + if ( bestdist < 0 || bestdist < dist ) { + best = trav; + bestdist = dist; + } + } + } + if ( !trav ) { + if ( !best ) { + G_Error( "AIFunc_FZombie_LightningAttack: unable to find matching wall marker for count = %i", ent->count ); + } + cs->followEntity = best->s.number; + } + // + return AIFunc_FZombie_LightningAttack( cs ); // think again + } + + } + // + return NULL; +} + +/* +=============== +AIFunc_FZombie_LightningAttackStart +=============== +*/ +char *AIFunc_FZombie_LightningAttackStart( cast_state_t *cs ) { + gentity_t *ent = &g_entities[cs->entityNum], *marker, *best; + float bestdist, dist; + // + ent->AIScript_AlertEntity = AICast_FZombie_EndLightning; // scripting will tell us to stop + ent->s.effect2Time = 0; + // + // find the closest ai_marker on the ground + marker = NULL; + best = NULL; + bestdist = -1; + // TTimo gcc: suggest parentheses around assignment used as truth value + while ( ( marker = G_Find( marker, FOFS( classname ), "ai_marker" ) ) ) { + if ( !marker->targetname || ( Q_stricmp( marker->targetname, "zfloor" ) != 0 ) ) { + continue; + } + dist = VectorDistance( marker->s.origin, cs->bs->origin ); + if ( bestdist >= 0 && ( bestdist < dist ) ) { + continue; + } + // closer + best = marker; + bestdist = dist; + } + // + if ( !best ) { + G_Error( "AIFunc_FZombie_LightningAttackStart: unable to find a close ai_marker with targetname = \"zfloor\"" ); + } + cs->followEntity = best->s.number; + // + cs->aifunc = AIFunc_FZombie_LightningAttack; + return "AIFunc_FZombie_LightningAttack"; +} + +/* +=============== +AIFunc_FZombie_HandLightningAttack + + Shoots lightning out at the player from her hands +=============== +*/ +#define FEMZOMBIE_HANDATTACK_DURATION 3400 + +char *AIFunc_FZombie_HandLightningAttack( cast_state_t *cs ) { + bot_state_t *bs; + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + bs = cs->bs; + cs->weaponFireTimes[WP_MONSTER_ATTACK2] = level.time; + // + if ( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) { // stop the effects + if ( !ent->client->ps.torsoTimer ) { + // are we ready to do the big portal lightning effect? + if ( AICast_GotEnoughAmmoForWeapon( cs, WP_MONSTER_ATTACK1 ) ) { + return AIFunc_FZombie_LightningAttackStart( cs ); + } else { + return AIFunc_BattleChaseStart( cs ); + } + } + return NULL; + } + // + // face them and do the effect + AICast_AimAtEnemy( cs ); + if ( ent->client->ps.torsoTimer < FEMZOMBIE_HANDATTACK_DURATION - 1000 ) { + ent->client->ps.eFlags |= EF_MONSTER_EFFECT3; + ent->s.otherEntityNum = bs->enemy; + // + if ( ent->client->ps.torsoTimer < 400 || cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_MONSTER_ATTACK1 )] || !AICast_EntityVisible( cs, bs->enemy, qtrue ) || !AICast_CheckAttack( cs, bs->enemy, qfalse ) ) { + // finish this attack + ent->client->ps.torsoAnim = + ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_ATTACK5; + ent->client->ps.torsoTimer = 300; + cs->aiFlags |= AIFL_LAND_ANIM_PLAYED; + } + } + // + return NULL; +} + +/* +=============== +AIFunc_FZombie_HandLightningAttackStart +=============== +*/ +char *AIFunc_FZombie_HandLightningAttackStart( cast_state_t *cs ) { + gentity_t *ent = &g_entities[cs->entityNum]; + // + ent->client->ps.torsoAnim = + ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_ATTACK4; + ent->client->ps.torsoTimer = FEMZOMBIE_HANDATTACK_DURATION; + // + cs->aiFlags &= ~AIFL_LAND_ANIM_PLAYED; + ent->s.effect3Time = level.time; + cs->aifunc = AIFunc_FZombie_HandLightningAttack; + return "AIFunc_FZombie_handLightningAttack"; +} + +//================================================================================= +// +// Helga (in normal form), the first boss +// +//================================================================================= + +/* +=============== +AICast_Helga_Alert + + Special code hooks for helga scripting +=============== +*/ +void AICast_Helga_Alert( gentity_t *ent ) { + cast_state_t *cs = AICast_GetCastState( ent->s.number ); + + if ( !ent->s.effect2Time ) { + ent->s.eFlags |= EF_MONSTER_EFFECT2; + ent->s.effect2Time = level.time; + } else if ( !( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { + cs->aiFlags |= AIFL_LAND_ANIM_PLAYED; // stop the effects + } else { + // we are no longer in the game + ent->aiInactive = qtrue; + trap_UnlinkEntity( ent ); + } +} + +/* +=============== +AIFunc_Helga_Idle +=============== +*/ +char *AIFunc_Helga_Idle( cast_state_t *cs ) { + bot_state_t *bs; + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + bs = cs->bs; + // + if ( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) { + return NULL; + } + // we should show a big lightning effect and then die once we've given the player enough time to get to us + // + ent->s.effect1Time = cs->thinkFuncChangeTime; + ent->client->ps.eFlags |= EF_MONSTER_EFFECT; + // + // are we in lightning death mode? + if ( ent->s.effect2Time && !( cs->aiFlags & AIFL_LAND_ANIM_PLAYED ) ) { + ent->client->ps.eFlags |= EF_MONSTER_EFFECT2; + } + // + return NULL; +} + +/* +=============== +AIFunc_Helga_IdleStart +=============== +*/ +char *AIFunc_Helga_IdleStart( cast_state_t *cs ) { + gentity_t *ent; + // + ent = &g_entities[cs->entityNum]; + // special alertEntity function so scripting can initialize the death routine + ent->AIScript_AlertEntity = AICast_Helga_Alert; + ent->s.effect2Time = 0; + // + cs->aiFlags &= ~AIFL_LAND_ANIM_PLAYED; + // + cs->aifunc = AIFunc_Helga_Idle; + return "AIFunc_Helga_Idle"; +} + +//=================================================================== + +/* +============== +AIFunc_FlameZombie_Portal +============== +*/ +char *AIFunc_FlameZombie_Portal( cast_state_t *cs ) { + gentity_t *ent = &g_entities[cs->entityNum]; + // + if ( cs->thinkFuncChangeTime < level.time - PORTAL_ZOMBIE_SPAWNTIME ) { + // HACK, make them aware of the player + AICast_UpdateVisibility( &g_entities[cs->entityNum], AICast_FindEntityForName( "player" ), qfalse, qtrue ); + ent->s.time2 = 0; // turn spawning effect off + return AIFunc_DefaultStart( cs ); + } + // + return NULL; +} + +/* +============== +AIFunc_FlameZombie_PortalStart +============== +*/ +char *AIFunc_FlameZombie_PortalStart( cast_state_t *cs ) { + gentity_t *ent = &g_entities[cs->entityNum]; + // + ent->s.time2 = level.time + 200; // hijacking this for portal spawning effect + // + // play a special animation + ent->client->ps.torsoAnim = + ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_EXTRA1; + ent->client->ps.legsAnim = + ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_EXTRA1; + ent->client->ps.torsoTimer = PORTAL_ZOMBIE_SPAWNTIME - 200; + ent->client->ps.legsTimer = PORTAL_ZOMBIE_SPAWNTIME - 200; + // + cs->thinkFuncChangeTime = level.time; + // + cs->aifunc = AIFunc_FlameZombie_Portal; + return "AIFunc_FlameZombie_Portal"; +} diff --git a/src/game/ai_cast_funcs.c b/src/game/ai_cast_funcs.c new file mode 100644 index 0000000..f6ab5e9 --- /dev/null +++ b/src/game/ai_cast_funcs.c @@ -0,0 +1,4745 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: ai_cast_funcs.c + * + * desc: Wolfenstein AI Character Decision Making + * +*/ + + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +This file contains the generic thinking states for the characters. + +Different types of movement or behaviour will be represented by +a seperate thinking function, which may or may not pass control +over to a new behaviour function. + +If control is passed onto a new function, the string name of the +current function is returned, mostly for debugging purposes. + +!!! NOTE: control must not be passed to a new AI func from outside of +this file. A new AI func must only be called from within another AI func. + +This gives us the ability to keep all code related to sections of AI +self-contained, so adding new features to the AI will be less likely to +step on other areas of AI. +*/ + +static int enemies[MAX_CLIENTS], numEnemies; + +// this is used to prevent try/abort/try/abort/etc grenade flush behaviour +static int lastGrenadeFlush = 0; + +#define AICAST_LEADERDIST_MAX 240 // try and stay at least this close to them when nothing else to do +#define AICAST_LEADERDIST_MIN 64 // get this close if we have a clear line of sight to them + +char *AIFunc_BattleChase( cast_state_t *cs ); +char *AIFunc_Battle( cast_state_t *cs ); + +static bot_moveresult_t *moveresult; + +/* +============ +AIFunc_Restore() + + restores the last aifunc that was backed up +============ +*/ +char *AIFunc_Restore( cast_state_t *cs ) { + // if the old aifunc was BattleChase, set it back to Battle, in case we have found a good position + if ( cs->oldAifunc == AIFunc_BattleChase ) { + cs->oldAifunc = AIFunc_Battle; + } + cs->aifunc = cs->oldAifunc; + return cs->aifunc( cs ); +} + +/* +============ +AICast_GetRandomViewAngle() +============ +*/ +float AICast_GetRandomViewAngle( cast_state_t *cs, float tracedist ) { + int cnt, passent, contents_mask; + vec3_t vec, dir, start, end; + trace_t trace; + float bestdist, bestyaw; + + cnt = 0; + VectorClear( vec ); + // + VectorCopy( cs->bs->origin, start ); + start[2] += cs->bs->cur_ps.viewheight; + // + passent = cs->bs->entitynum; + contents_mask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WATER | CONTENTS_SLIME; +// contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WATER; + bestdist = 0; + bestyaw = 0; + // + while ( cnt++ < 4 ) + { + vec[YAW] = random() * 360.0; + // + AngleVectors( vec, dir, NULL, NULL ); + VectorMA( start, tracedist, dir, end ); + // + trap_Trace( &trace, start, NULL, NULL, end, passent, contents_mask ); + // + if ( trace.fraction >= 1 ) { + return vec[YAW]; + } else if ( !bestdist || bestdist < trace.fraction ) { + bestdist = trace.fraction; + bestyaw = vec[YAW]; + } + } + // + if ( bestdist ) { + return bestyaw; + } + // just return their current direction + return cs->bs->ideal_viewangles[YAW]; +} + +/* +============ +AICast_MoveToPos() + + returns a pointer to the moveresult it used to make the move, so we can investigate it + outside of this function +============ +*/ +bot_moveresult_t *AICast_MoveToPos( cast_state_t *cs, vec3_t pos, int entnum ) { + bot_goal_t goal; + vec3_t /*target,*/ dir; + static bot_moveresult_t lmoveresult; + int tfl; + bot_state_t *bs; + +//int pretime = Sys_MilliSeconds(); + + moveresult = NULL; + + if ( cs->castScriptStatus.scriptNoMoveTime > level.time ) { + return NULL; + } + if ( cs->pauseTime > level.time ) { + return NULL; + } + // + bs = cs->bs; + tfl = cs->travelflags; + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + tfl |= TFL_SLIME; + } + // + //create the chase goal + memset( &goal, 0, sizeof( goal ) ); + goal.entitynum = entnum; + goal.areanum = BotPointAreaNum( pos ); + VectorCopy( pos, goal.origin ); + VectorSet( goal.mins, -8, -8, -8 ); + VectorSet( goal.maxs, 8, 8, 8 ); + if ( entnum == cs->followEntity && !cs->followSlowApproach ) { + goal.flags |= GFL_NOSLOWAPPROACH; // just speed right passed it + } + // + // debugging, show the route + if ( aicast_debug.integer == 2 && ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) { + trap_AAS_RT_ShowRoute( cs->bs->origin, cs->bs->areanum, goal.areanum ); + } + // + //initialize the movement state + BotSetupForMovement( bs ); + //if this is a slow moving creature, don't use avoidreach + if ( cs->attributes[RUNNING_SPEED] < 100 ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + } else if ( !VectorCompare( cs->lastMoveToPosGoalOrg, pos ) ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + VectorCopy( pos, cs->lastMoveToPosGoalOrg ); + } + //move towards the goal + trap_BotMoveToGoal( &lmoveresult, bs->ms, &goal, tfl ); + //if the movement failed + if ( lmoveresult.failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", lmoveresult.traveltype); + // clear all movement + trap_EA_Move( cs->entityNum, vec3_origin, 0 ); + } + // + if ( lmoveresult.flags & ( MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { + VectorCopy( lmoveresult.ideal_viewangles, bs->ideal_viewangles ); + VectorCopy( bs->ideal_viewangles, cs->viewlock_viewangles ); + cs->aiFlags |= AIFL_VIEWLOCKED; + } else if ( !( cs->bs->flags & BFL_ATTACKED ) ) { // if we are attacking, don't change angles + bot_input_t bi; + + trap_EA_GetInput( bs->client, 0.1, &bi ); + if ( VectorLength( lmoveresult.movedir ) < 0.5 ) { + VectorSubtract( goal.origin, bs->origin, dir ); + vectoangles( dir, bs->ideal_viewangles ); + } else { + // use our velocity if we are moving + if ( VectorNormalize2( cs->bs->cur_ps.velocity, dir ) > 1 ) { + vectoangles( dir, bs->ideal_viewangles ); + } else { + vectoangles( lmoveresult.movedir, bs->ideal_viewangles ); + } + } + bs->ideal_viewangles[2] *= 0.5; +// disabled, needs work +/* + if (lmoveresult.flags & MOVERESULT_FUTUREVIEW) { + if (AngleDifference(bs->ideal_viewangles[1], lmoveresult.ideal_viewangles[1]) > 45) + bs->ideal_viewangles[1] += 45; + else if (AngleDifference(bs->ideal_viewangles[1], lmoveresult.ideal_viewangles[1]) < -45) + bs->ideal_viewangles[1] -= 45; + else + bs->ideal_viewangles[1] = lmoveresult.ideal_viewangles[1]; + bs->ideal_viewangles[1] = AngleNormalize360( bs->ideal_viewangles[1] ); + bs->ideal_viewangles[0] = lmoveresult.ideal_viewangles[0]; + bs->ideal_viewangles[0] = 0.5*AngleNormalize180( bs->ideal_viewangles[0] ); + } +*/ + } + // this must go last so we face the direction we avoid move + AICast_Blocked( cs, &lmoveresult, qfalse, &goal ); + +//G_Printf("MoveToPos: %i ms\n", -pretime + Sys_MilliSeconds() ); + +// debug, print movement info + if ( 0 ) { // (SA) added to hide the print + bot_input_t bi; + + trap_EA_GetInput( cs->bs->client, (float) level.time / 1000, &bi ); + G_Printf( "spd: %i\n", (int)bi.speed ); + } + + return &lmoveresult; +} + +/* +============ +AICast_SpeedScaleForDistance() +============ +*/ +float AICast_SpeedScaleForDistance( cast_state_t *cs, float startdist, float idealDist ) { +#define PREDICT_TIME_WALK 0.2 +#define PREDICT_TIME_CROUCH 0.2 +#define PREDICT_TIME_RUN 0.3 + float speed, dist; + + dist = startdist - idealDist; + if ( dist < 1 ) { + dist = 1; + } + + // if walking + if ( cs->movestate == MS_WALK ) { + speed = cs->attributes[WALKING_SPEED]; + if ( speed * PREDICT_TIME_WALK > dist ) { + return 0.2 + 0.8 * ( dist / ( speed * PREDICT_TIME_WALK ) ); + } else { + return 1.0; + } + } else + // if crouching + if ( cs->movestate == MS_CROUCH || cs->bs->attackcrouch_time > trap_AAS_Time() ) { + speed = cs->attributes[RUNNING_SPEED] * cs->bs->cur_ps.crouchSpeedScale; + if ( speed * PREDICT_TIME_CROUCH > dist ) { + return 0.3 + 0.7 * ( dist / ( speed * PREDICT_TIME_CROUCH ) ); + } else { + return 1.0; + } + } else + // running + { + speed = cs->attributes[RUNNING_SPEED]; + if ( speed * PREDICT_TIME_RUN > dist ) { + return 0.2 + 0.8 * ( dist / ( speed * PREDICT_TIME_RUN ) ); + } else { + return 1.0; + } + } +} + +/* +============ +AIFunc_Idle() + + The cast AI is standing around, contemplating the meaning of life +============ +*/ +char *AIFunc_Idle( cast_state_t *cs ) { + + // we are in an idle state, looking for something to do + + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + // + // do we need to go to our leader? + if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) { + return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue ); + } + // + // look for things we should attack + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } else if ( numEnemies == -3 ) { // bullet impact + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectBulletImpactStart( cs ); + } + } else if ( numEnemies == -4 ) { // audible event + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt ); + } + } else if ( numEnemies > 0 ) { + int i; + + cs->bs->enemy = -1; + // choose an enemy + for ( i = 0; i < numEnemies; i++ ) { + if ( Distance( cs->bs->origin, cs->vislist[enemies[i]].visible_pos ) > 16 ) { // if we are really close to the last place we saw them, no point trying to attack, since we'll just end up back here + if ( cs->bs->enemy < 0 ) { + cs->bs->enemy = enemies[i]; + } else if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) { + cs->bs->enemy = enemies[i]; + return AIFunc_BattleStart( cs ); + } + } + } + if ( cs->bs->enemy >= 0 ) { + if ( ( ( cs->leaderNum < 0 ) || ( cs->thinkFuncChangeTime < level.time - 3000 ) ) && AICast_WantsToChase( cs ) ) { // don't leave our leader as soon as we get to them + return AIFunc_BattleStart( cs ); + } else if ( AICast_EntityVisible( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qtrue ) || AICast_CheckAttack( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qfalse ) ) { + // if we are tactical enough, look for a hiding spot + if ( !( cs->leaderNum >= 0 ) && cs->attributes[TACTICAL] > 0.4 && cs->attributes[AGGRESSION] < 1.0 ) { + // they can see us, and we want to hide from them + if ( !AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) { + // go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time + cs->takeCoverTime = level.time + 2000 + rand() % 3000; + return AIFunc_BattleTakeCoverStart( cs ); + } + } + // attack them if nothing else to do, since they can attack us here + return AIFunc_BattleStart( cs ); + } else if ( cs->leaderNum < 0 ) { // we should pursue if no leader, and not wanting to hide + return AIFunc_BattleStart( cs ); + } else { + // they can't see us anyway, so ignore them + cs->lastEnemy = cs->bs->enemy; // at least face them if they come to get us + cs->bs->enemy = -1; + // crouching makes us look like we are hiding, which is what we are doing + if ( cs->attributes[ATTACK_CROUCH] > 0.5 ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + } + } + } + // + // if we are in combat mode, then we should relax, since we dont have an enemy + if ( cs->aiState >= AISTATE_COMBAT ) { + AICast_StateChange( cs, AISTATE_ALERT ); + } + // + // if we couldn't find anything, see if our previous enemy is still around, if so, go find them + // this is an attempt to prevent guys from running away to hide from something, never to + // be seen again. They shouldn't really "forget" that they are indeed soldiers. + if ( !( cs->leaderNum >= 0 ) && cs->lastEnemy >= 0 && g_entities[cs->lastEnemy].health > 0 && cs->vislist[cs->lastEnemy].real_visible_timestamp < level.time - 5000 && + cs->takeCoverTime < level.time - 5000 ) { + cs->bs->enemy = cs->lastEnemy; // just go to the place we last saw them + return AIFunc_BattleStart( cs ); + } + // + // if we've recently been in a fight, keep looking around, so we don't look stupid + if ( cs->lastEnemy >= 0 ) { + // + // if we like to crouch, then do so, since we are in a battle situation +/* if (cs->lastEnemy > 0) { // not the player + if (cs->attributes[ATTACK_CROUCH] > 0.3) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 2; + } + } else { // if we were attacking the player, relax a bit more + if (cs->attributes[ATTACK_CROUCH] > 0.6) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 2; + } + } +*/ // if we only just recently saw them, face that direction + if ( ( g_entities[cs->lastEnemy].health > 0 ) && cs->vislist[cs->lastEnemy].visible_timestamp > ( level.time - 20000 ) + && AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, cs->vislist[cs->lastEnemy].visible_pos, cs->lastEnemy, qfalse ) ) { + vec3_t dir; + // + VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir ); + if ( VectorLength( dir ) < 1 ) { + cs->bs->ideal_viewangles[PITCH] = 0; + } else { + VectorNormalize( dir ); + vectoangles( dir, cs->bs->ideal_viewangles ); + cs->bs->ideal_viewangles[PITCH] = AngleNormalize180( cs->bs->ideal_viewangles[PITCH] ) * 0.5; + } + // if we like to crouch, then do so, since we are in a battle situation +// if (cs->attributes[ATTACK_CROUCH] > 0.1) { +// cs->bs->attackcrouch_time = trap_AAS_Time() + 2; +// } + } else if ( /*cs->castScriptStatus.castScriptEventIndex < 0 &&*/ + cs->attributes[TACTICAL] && cs->nextIdleAngleChange < level.time ) { + // wait a second before changing again + if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) { + + // FIXME: This could be changed to use some AAS sampling, which would: + // + // Given a src area, pick a random dest area which is visible from that area + // and return it's position, which we'd then use to set the next view angles + // + // This would result in more efficient, more realistic behaviour, since they'd + // also use PITCH angles to look at areas above/below them + + cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 ); + + if ( abs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) { + cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500; + } else { // do really fast + cs->nextIdleAngleChange = level.time + 500; + } + + // adjust with time + cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ); + /// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0); + + cs->bs->ideal_viewangles[PITCH] = 0; + } + } else if ( cs->idleYawChange ) { + cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ); + cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) ); + } + } + + // check for a movement we should be making + if ( cs->obstructingTime > level.time ) { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + if ( cs->movestate != MS_CROUCH ) { + cs->movestate = MS_WALK; + } + cs->movestateType = MSTYPE_TEMPORARY; + } + + // set head look flag if no enemy + if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) { + g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK; + } + + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + + return NULL; +} + +/* +============ +AIFunc_IdleStart() +============ +*/ +char *AIFunc_IdleStart( cast_state_t *cs ) { + g_entities[cs->entityNum].flags &= ~FL_AI_GRENADE_KICK; + // stop following + cs->followEntity = -1; + // if our enemy has just died, inspect the body + if ( cs->bs->enemy >= 0 ) { + if ( g_entities[cs->entityNum].aiTeam == AITEAM_NAZI && g_entities[cs->bs->enemy].health <= 0 ) { + return AIFunc_InspectBodyStart( cs ); + } else { + cs->bs->enemy = -1; + } + } + // make sure we don't avoid any areas when we start again + trap_BotInitAvoidReach( cs->bs->ms ); + + // randomly choose idle animation +//----(SA) try always using the 'casual' stand on spawn and change to crouching one when 'alerted' + if ( cs->aiFlags & AIFL_STAND_IDLE2 ) { +// if (rand()%2 || (cs->lastEnemy < 0 && cs->aiFlags & AIFL_TALKING)) + g_entities[cs->entityNum].client->ps.eFlags |= EF_STAND_IDLE2; +// else +// g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2; + } + + cs->aifunc = AIFunc_Idle; + return "AIFunc_Idle"; +} + +/* +============ +AIFunc_InspectFriendly() +============ +*/ +char *AIFunc_InspectFriendly( cast_state_t *cs ) { + gentity_t *followent, *ent; + bot_state_t *bs; + vec3_t destorg; + float dist; + qboolean moved = qfalse; + + ent = &g_entities[cs->entityNum]; + + // if we have an enemy, attack now! + if ( cs->bs->enemy >= 0 ) { + return AIFunc_BattleStart( cs ); + } + + cs->followEntity = cs->inspectNum; + cs->followDist = 64; + + cs->scriptPauseTime = level.time + 4000; // wait for at least this long before resuming any scripted walking, etc + + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + cs->castScriptStatus.scriptGotoId = -1; + cs->movestate = MS_DEFAULT; + cs->movestateType = MSTYPE_NONE; + return AIFunc_AvoidDangerStart( cs ); + } + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + + followent = &g_entities[cs->followEntity]; + + // if the entity is not ready yet + if ( !followent->inuse ) { + // if it's a connecting client, wait + if ( cs->followEntity < MAX_CLIENTS + && ( ( followent->client && followent->client->pers.connected == CON_CONNECTING ) + || ( level.time < 3000 ) ) ) { + return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist ); + } else // stop following it + { + AICast_EndChase( cs ); + return AIFunc_IdleStart( cs ); + } + } + + if ( followent->client ) { + VectorCopy( followent->client->ps.origin, destorg ); + } else { + VectorCopy( followent->r.currentOrigin, destorg ); + } + + // they are ready, are they inside range? FIXME: make configurable + dist = Distance( destorg, cs->bs->origin ); + if ( !( dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) ) { + // + // go to them + // + bs = cs->bs; + + // set this flag so we know when we;ve just reached them + cs->aiFlags |= AIFL_MISCFLAG1; + + // move straight to them if we can + if ( !moved && + ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) { + aicast_predictmove_t move; + vec3_t dir; + bot_input_t bi; + usercmd_t ucmd; + trace_t tr; + qboolean simTest = qfalse; + + if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) { + simTest = qtrue; + } + + if ( !simTest ) { + // trace will eliminate most unsuccessful paths + trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask ); + if ( tr.entityNum == cs->followEntity || tr.fraction == 1 ) { + simTest = qtrue; + } + } + + if ( simTest ) { + // try walking straight to them + VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir ); + VectorNormalize( dir ); + if ( !ent->waterlevel ) { + dir[2] = 0; + } + trap_EA_Move( cs->entityNum, dir, 400 ); + trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); + AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles ); + AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, cs->followEntity ); + + if ( move.stopevent == PREDICTSTOP_HITENT ) { // success! + vectoangles( dir, cs->bs->ideal_viewangles ); + cs->bs->ideal_viewangles[2] *= 0.5; + moved = qtrue; + } + } + } + // + if ( !moved ) { + // use AAS routing + moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, cs->followEntity ); + // if we cant get there, forget it + if ( moveresult && moveresult->failure ) { + return AIFunc_DefaultStart( cs ); + } + } + + // should we slow down? + if ( cs->followDist && cs->followSlowApproach ) { + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist ); + } +/* + // check for a movement we should be making + if (cs->obstructingTime > level.time) + { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + if (cs->movestate != MS_CROUCH) { + cs->movestate = MS_WALK; + } + cs->movestateType = MSTYPE_TEMPORARY; + } +*/ + } else if ( cs->aiFlags & AIFL_MISCFLAG1 ) { + cs->aiFlags &= ~AIFL_MISCFLAG1; + if ( g_entities[cs->inspectNum].health <= 0 ) { + // call a script event + cs->aiFlags &= ~AIFL_DENYACTION; + AICast_ForceScriptEvent( cs, "inspectbodyend", g_entities[cs->inspectNum].aiName ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + // relinguish control back to scripting + return AIFunc_DefaultStart( cs ); + } + } + } + + { + int numEnemies; + // + // look for things we should attack + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection + // only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone + if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) { + return AIFunc_InspectFriendlyStart( cs, enemies[0] ); + } + } + // RF, disabled this, if we are interrupted, scripts might not work right, and anyway, this is only a bullet, not as if it's a dead guy or anything + //else if (numEnemies == -3) // bullet impact + //{ + // if (cs->aiState < AISTATE_COMBAT) { + // return AIFunc_InspectBulletImpactStart( cs ); + // } + //} + else if ( numEnemies > 0 ) { + int i; + + cs->bs->enemy = enemies[0]; // just attack the first one + // override with a visible enemy + for ( i = 1; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) { + cs->bs->enemy = enemies[i]; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + + return AIFunc_BattleStart( cs ); + } + } + + if ( cs->nextIdleAngleChange < level.time ) { + // wait a second before changing again + if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) { + + // FIXME: This could be changed to use some AAS sampling, which would: + // + // Given a src area, pick a random dest area which is visible from that area + // and return it's position, which we'd then use to set the next view angles + // + // This would result in more efficient, more realistic behaviour, since they'd + // also use PITCH angles to look at areas above/below them + + cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 ); + + if ( abs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) { + cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500; + } else { // do really fast + cs->nextIdleAngleChange = level.time + 500; + } + + // adjust with time + cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ); + /// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0); + + cs->bs->ideal_viewangles[PITCH] = 0; + } + } else if ( cs->idleYawChange ) { + cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ); + cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) ); + } + + // set head look flag if no enemy + if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) { + g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK; + } + + // reload? + if ( ( cs->bs->cur_ps.ammoclip[cs->bs->cur_ps.weapon] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + + return NULL; +} + +/* +============ +AIFunc_InspectFriendlyStart +============ +*/ +char *AIFunc_InspectFriendlyStart( cast_state_t *cs, int entnum ) { + cast_state_t *ocs; + + ocs = AICast_GetCastState( entnum ); + + // we are about to deal with the request for inspection + cs->vislist[entnum].flags &= ~AIVIS_INSPECT; + cs->scriptPauseTime = level.time + 4000; // wait for at least this long before resuming any scripted walking, etc + + if ( ocs->aiState >= AISTATE_COMBAT || g_entities[entnum].health <= 0 ) { + // mark this character as having been inspected + cs->vislist[entnum].flags |= AIVIS_INSPECTED; + } + + // what should we do? wait here? hide? go see them? + + // if dead, go see them + if ( g_entities[entnum].health <= 0 ) { + cs->inspectNum = entnum; + cs->aifunc = AIFunc_InspectFriendly; + return "AIFunc_InspectFriendlyStart"; + } + + // not dead, so call scripting event + AICast_ForceScriptEvent( cs, "inspectfriendlycombatstart", g_entities[entnum].aiName ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + // ignore this friendly forever and ever amen + cs->vislist[entnum].flags |= AIVIS_INSPECTED; + return NULL; + } + + // if they are in combat, then act according to aggressiveness + if ( ocs->aiState >= AISTATE_COMBAT ) { + if ( cs->attributes[AGGRESSION] < 0.3 ) { + if ( !AICast_GetTakeCoverPos( cs, entnum, g_entities[entnum].client->ps.origin, cs->takeCoverPos ) ) { + cs->takeCoverTime = level.time + 10000; // hide for 10 seconds + cs->scriptPauseTime = cs->takeCoverTime; + // crouch there if possible + if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0; + } + return AIFunc_BattleTakeCoverStart( cs ); + } + } + } + + // if still around, then we need to go to them + cs->inspectNum = entnum; + cs->aifunc = AIFunc_InspectFriendly; + return "AIFunc_InspectFriendly"; +} + +/* +============ +AIFunc_InspectBulletImpact() +============ +*/ +char *AIFunc_InspectBulletImpact( cast_state_t *cs ) { + gentity_t *ent; + vec3_t v1; + gclient_t *client; + // + client = &level.clients[cs->entityNum]; + // + ent = &g_entities[cs->entityNum]; + // + cs->bulletImpactIgnoreTime = level.time + 800; + // + // wait until we are looking at the impact + if ( cs->aiFlags & AIFL_MISCFLAG2 ) { + // pause any scripting + cs->scriptPauseTime = level.time + 1000; + // look at bullet impact + VectorSubtract( cs->bulletImpactEnd, cs->bs->origin, v1 ); + VectorNormalize( v1 ); + vectoangles( v1, cs->bs->ideal_viewangles ); + // + // if we are facing that direction, we've looked at the impact point + if ( fabs( cs->bs->ideal_viewangles[YAW] - cs->bs->viewangles[YAW] ) < 1 ) { + cs->aiFlags &= ~AIFL_MISCFLAG2; + } + return NULL; + } else if ( cs->aiFlags & AIFL_MISCFLAG1 ) { + // clear the flag now + cs->aiFlags &= ~AIFL_MISCFLAG1; + // start looking back at bullet + VectorSubtract( cs->bulletImpactStart, cs->bs->origin, v1 ); + VectorNormalize( v1 ); + vectoangles( v1, cs->bs->ideal_viewangles ); + if ( cs->aiState < AISTATE_ALERT ) { + // change to alert state + if ( !AICast_StateChange( cs, AISTATE_ALERT ) ) { + // stop doing whatever we are doing, and return control to scripting + cs->scriptPauseTime = 0; + return AIFunc_IdleStart( cs ); + } + // make sure we didnt change thinkfunc + if ( cs->aifunc != AIFunc_InspectBulletImpact ) { + //G_Error( "scripting passed control out of AIFunc_InspectBulletImpact(), this is bad" ); + return NULL; + } + } + // pause any scripting + if ( ent->client->ps.legsTimer ) { + cs->scriptPauseTime = level.time + ent->client->ps.legsTimer; + } else { // just wait for a few seconds looking at the source + cs->scriptPauseTime = level.time + 3500; + } + } + // are we done? + if ( cs->scriptPauseTime < level.time ) { + return AIFunc_IdleStart( cs ); + } + // + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + // + // check for enemies + { + int numEnemies; + // + // look for things we should attack + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -2 ) { // inspection + // only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone + if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) { + + return AIFunc_InspectFriendlyStart( cs, enemies[0] ); + } + } else if ( numEnemies > 0 ) { + int i; + + cs->bs->enemy = enemies[0]; // just attack the first one + // override with a visible enemy + for ( i = 1; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) { + cs->bs->enemy = enemies[i]; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + + return AIFunc_BattleStart( cs ); + } + } + // + return NULL; +} + +/* +============ +AIFunc_InspectBulletImpactStart() +============ +*/ +char *AIFunc_InspectBulletImpactStart( cast_state_t *cs ) { + int oldScriptIndex; + // set the impact timer so we ignore bullets while inspecting this one + cs->bulletImpactIgnoreTime = level.time + 5000; + // pause any scripting + cs->scriptPauseTime = level.time + 1000; + // set this so we know if we've started the trace back to the bullet origin + cs->aiFlags |= AIFL_MISCFLAG1; + cs->aiFlags |= AIFL_MISCFLAG2; + // + // call the script event + oldScriptIndex = cs->scriptCallIndex; + AICast_ScriptEvent( cs, "bulletimpactsound", "" ); + if ( oldScriptIndex == cs->scriptCallIndex ) { + // no script event, so call the animation script + BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_BULLETIMPACT, qfalse, qtrue ); + } + // + cs->aifunc = AIFunc_InspectBulletImpact; + return "AIFunc_InspectBulletImpact"; +} + +/* +============ +AIFunc_InspectAudibleEvent() +============ +*/ +char *AIFunc_InspectAudibleEvent( cast_state_t *cs ) { + gentity_t *ent; + bot_state_t *bs; + vec3_t destorg; + float dist; + qboolean moved = qfalse; + + ent = &g_entities[cs->entityNum]; + + // if we have an enemy, attack now! + if ( cs->bs->enemy >= 0 ) { + return AIFunc_BattleStart( cs ); + } + + cs->followDist = 64; + + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + cs->castScriptStatus.scriptGotoId = -1; + cs->movestate = MS_DEFAULT; + cs->movestateType = MSTYPE_NONE; + return AIFunc_AvoidDangerStart( cs ); + } + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + + VectorCopy( cs->audibleEventOrg, destorg ); + + // they are ready, are they inside range? FIXME: make configurable + dist = Distance( destorg, cs->bs->origin ); + if ( !( dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) ) { + // + // go to them + // + bs = cs->bs; + + // set this flag so we know when we;ve just reached them + cs->aiFlags |= AIFL_MISCFLAG1; + + // if not overly aggressive, pursue with caution + if ( cs->attributes[AGGRESSION] <= 0.8 ) { + cs->movestate = MS_CROUCH; + cs->movestateType = MSTYPE_TEMPORARY; + } + + // move straight to them if we can + if ( !moved && + ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) { + aicast_predictmove_t move; + vec3_t dir; + bot_input_t bi; + usercmd_t ucmd; + trace_t tr; + qboolean simTest = qfalse; + + if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) { + simTest = qtrue; + } + + if ( !simTest ) { + // trace will eliminate most unsuccessful paths + trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, destorg, cs->entityNum, g_entities[cs->entityNum].clipmask ); + if ( tr.fraction == 1 ) { + simTest = qtrue; + } + } + + if ( simTest ) { + // try walking straight to them + gentity_t *gent; + // + gent = G_Spawn(); + VectorCopy( destorg, gent->r.currentOrigin ); + // + VectorSubtract( destorg, cs->bs->origin, dir ); + VectorNormalize( dir ); + if ( !ent->waterlevel ) { + dir[2] = 0; + } + trap_EA_Move( cs->entityNum, dir, 400 ); + trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); + AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles ); + AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, gent->s.number ); + // + if ( move.stopevent == PREDICTSTOP_HITENT ) { // success! + vectoangles( dir, cs->bs->ideal_viewangles ); + cs->bs->ideal_viewangles[2] *= 0.5; + moved = qtrue; + } + // + G_FreeEntity( gent ); + } + } + // + if ( !moved ) { + // use AAS routing + AICast_MoveToPos( cs, destorg, -1 ); + // if we cant get there, forget it + if ( moveresult && moveresult->failure ) { + return AIFunc_DefaultStart( cs ); + } + } + + // should we slow down? + if ( cs->followDist && cs->followSlowApproach ) { + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist ); + } +/* + // check for a movement we should be making + if (cs->obstructingTime > level.time) + { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + if (cs->movestate != MS_CROUCH) { + cs->movestate = MS_WALK; + } + cs->movestateType = MSTYPE_TEMPORARY; + } +*/ + } else if ( cs->aiFlags & AIFL_MISCFLAG1 ) { + cs->aiFlags &= ~AIFL_MISCFLAG1; + // call a script event + cs->aiFlags &= ~AIFL_DENYACTION; + AICast_ForceScriptEvent( cs, "inspectsoundend", g_entities[cs->audibleEventEnt].aiName ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + // relinguish control back to scripting + return AIFunc_DefaultStart( cs ); + } + } else { + // look around randomly + if ( cs->battleHuntViewTime < level.time ) { + cs->battleHuntViewTime = level.time + 700 + rand() % 1000; + // set a random viewangle + cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( 45.0 + random() * 45.0 ) * ( 2 * ( rand() % 2 ) - 1 ) ); + } + // + if ( cs->scriptPauseTime < level.time ) { + // we're done waiting around here + return AIFunc_DefaultStart( cs ); + } + } + + { + int numEnemies; + // + // look for things we should attack + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection + // only override current objective if we are inspecting a dead guy, and the new inspect target is fighting someone + if ( ( g_entities[cs->inspectNum].health <= 0 ) && ( g_entities[enemies[0]].health > 0 ) ) { + return AIFunc_InspectFriendlyStart( cs, enemies[0] ); + } + } else if ( numEnemies > 0 ) { + int i; + + cs->bs->enemy = enemies[0]; // just attack the first one + // override with a visible enemy + for ( i = 1; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) { + cs->bs->enemy = enemies[i]; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + + return AIFunc_BattleStart( cs ); + } + } + + // set head look flag if no enemy + if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) { + g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK; + } + + // reload? + if ( ( cs->bs->cur_ps.ammoclip[cs->bs->cur_ps.weapon] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + + return NULL; +} + +/* +============ +AIFunc_InspectAudibleEventStart +============ +*/ +char *AIFunc_InspectAudibleEventStart( cast_state_t *cs, int entnum ) { + cast_state_t *ocs; + int oldScriptIndex; + + ocs = AICast_GetCastState( entnum ); + + // we have now processed the audible event (whether we act on it or not) + cs->audibleEventTime = -9999; + + // trigger a script event, which has the ability to deny the request + oldScriptIndex = cs->scriptCallIndex; + AICast_ForceScriptEvent( cs, "inspectsoundstart", g_entities[cs->audibleEventEnt].aiName ); + if ( cs->aiFlags & AIFL_DENYACTION ) { + return NULL; + } + + // if not in alert mode, go there now + if ( cs->aiState < AISTATE_ALERT ) { + AICast_StateChange( cs, AISTATE_ALERT ); + } + + if ( oldScriptIndex == cs->scriptCallIndex ) { + BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_INSPECTSOUND, qfalse, qtrue ); + } + + // pause the scripting for now + cs->scriptPauseTime = level.time + 4000; // wait for at least this long before resuming any scripted walking, etc + + // what should we do? wait here? hide? go see them? + + // if dead, go see them + if ( g_entities[entnum].health <= 0 ) { + cs->inspectNum = entnum; + cs->aifunc = AIFunc_InspectFriendly; + return "AIFunc_InspectFriendlyStart"; + } + + // if they are in combat, then act according to aggressiveness + if ( ocs->aiState >= AISTATE_COMBAT ) { + if ( cs->attributes[AGGRESSION] < 0.3 ) { + if ( !AICast_GetTakeCoverPos( cs, entnum, g_entities[entnum].client->ps.origin, cs->takeCoverPos ) ) { + cs->takeCoverTime = level.time + 10000; // hide for 10 seconds + cs->scriptPauseTime = cs->takeCoverTime; + // crouch there if possible + if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0; + } + return AIFunc_BattleTakeCoverStart( cs ); + } + } + } + + cs->aifunc = AIFunc_InspectAudibleEvent; + return "AIFunc_InspectAudibleEvent"; +} + +/* +============ +AIFunc_ChaseGoalIdle() +============ +*/ +char *AIFunc_ChaseGoalIdle( cast_state_t *cs ) { + gentity_t *followent; + vec3_t dir; + + if ( cs->followEntity < 0 ) { + AICast_EndChase( cs ); + return AIFunc_IdleStart( cs ); + } + + followent = &g_entities[cs->followEntity]; + + // CHECK: will this interfere with scripting? + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + // + // if the player is not ready yet, wait + if ( !followent->inuse ) { + return NULL; + } + + // has the scripting stopped asking us to pursue this goal? + if ( cs->followIsGoto && ( cs->followTime < level.time ) ) { + return AIFunc_Idle( cs ); + } + + // they are ready, are they outside range? + if ( Distance( followent->r.currentOrigin, cs->bs->origin ) > cs->followDist ) { + return AIFunc_ChaseGoalStart( cs, cs->followEntity, cs->followDist, qtrue ); + } + + // check for a movement we should be making + if ( cs->obstructingTime > level.time ) { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + cs->speedScale = cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED]; + } + // if we have an enemy, fire if they're visible + else if ( cs->bs->enemy >= 0 ) { + //attack the enemy if possible + AICast_ProcessAttack( cs ); + } + // if we had an enemy recently, face them + else if ( cs->lastEnemy >= 0 ) { + vec3_t dir; + // + VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir ); + if ( VectorLength( dir ) < 1 ) { + cs->bs->ideal_viewangles[PITCH] = 0; + } else { + VectorNormalize( dir ); + vectoangles( dir, cs->bs->ideal_viewangles ); + } + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + } else if ( followent->client ) { + // face them + VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir ); + dir[2] += followent->client->ps.viewheight - g_entities[cs->bs->entitynum].client->ps.viewheight; + VectorNormalize( dir ); + vectoangles( dir, cs->bs->ideal_viewangles ); + } + + // look for things we should attack + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } else if ( numEnemies == -3 ) { // bullet impact + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectBulletImpactStart( cs ); + } + } else if ( numEnemies == -4 ) { // audible event + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt ); + } + } else if ( numEnemies > 0 ) { + cs->bs->enemy = enemies[0]; // just attack the first one + } + + // set head look flag if no enemy + if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) { + g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK; + } + + return NULL; +} + +/* +============ +AIFunc_ChaseGoalIdleStart() +============ +*/ +char *AIFunc_ChaseGoalIdleStart( cast_state_t *cs, int entitynum, float reachdist ) { + // make sure we don't avoid any areas when we start again + trap_BotInitAvoidReach( cs->bs->ms ); + + // if we are following someone, always use the default (ready for action) anim + if ( entitynum < MAX_CLIENTS ) { + g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2; + } else { + // randomly choose idle animation +//----(SA) try always using the 'casual' stand on spawn and change to crouching one when 'alerted' + if ( cs->aiFlags & AIFL_STAND_IDLE2 ) { +// if (cs->lastEnemy < 0) + g_entities[cs->entityNum].client->ps.eFlags |= EF_STAND_IDLE2; +// else +// g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2; + } + } + + cs->followEntity = entitynum; + cs->followDist = reachdist; + cs->aifunc = AIFunc_ChaseGoalIdle; + return "AIFunc_ChaseGoalIdle"; +} + +/* +============ +AIFunc_ChaseGoal() +============ +*/ +char *AIFunc_ChaseGoal( cast_state_t *cs ) { + gentity_t *followent, *ent; + bot_state_t *bs; + vec3_t destorg; + float dist; + qboolean moved = qfalse; + + ent = &g_entities[cs->entityNum]; + + if ( cs->followEntity < 0 ) { + AICast_EndChase( cs ); + return AIFunc_IdleStart( cs ); + } + + // CHECK: will this mess with scripting? + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + cs->castScriptStatus.scriptGotoId = -1; + cs->movestate = MS_DEFAULT; + cs->movestateType = MSTYPE_NONE; + return AIFunc_AvoidDangerStart( cs ); + } + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + + followent = &g_entities[cs->followEntity]; + + // if the entity is not ready yet + if ( !followent->inuse ) { + // if it's a connecting client, wait + if ( cs->followEntity < MAX_CLIENTS + && ( ( followent->client && followent->client->pers.connected == CON_CONNECTING ) + || ( level.time < 3000 ) ) ) { + return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist ); + } else // stop following it + { + AICast_EndChase( cs ); + return AIFunc_IdleStart( cs ); + } + } + + // has the scripting stopped asking us to pursue this goal? + if ( cs->followIsGoto && ( cs->followTime < level.time ) ) { + return AIFunc_Idle( cs ); + } + + if ( followent->client ) { + VectorCopy( followent->client->ps.origin, destorg ); + } else { + VectorCopy( followent->r.currentOrigin, destorg ); + } + + // they are ready, are they inside range? FIXME: make configurable + dist = Distance( destorg, cs->bs->origin ); + if ( cs->followSlowApproach && dist < cs->followDist && ( ent->waterlevel || ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) ) ) { + // if this is a scripted GOTO, stop following now + if ( cs->followEntity == cs->castScriptStatus.scriptGotoEnt ) { + AICast_EndChase( cs ); + return AIFunc_IdleStart( cs ); + } + // if we have reached our leader + else + { + if ( cs->followEntity == cs->leaderNum ) { + if ( dist < AICAST_LEADERDIST_MIN ) { + AICast_EndChase( cs ); + return AIFunc_IdleStart( cs ); + } else { + trace_t tr; + // if we have a clear line to our leader, move closer, since there may be others following also + trap_Trace( &tr, cs->bs->origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, g_entities[cs->followEntity].r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask ); + if ( tr.entityNum != cs->followEntity ) { + AICast_EndChase( cs ); + return AIFunc_IdleStart( cs ); + } + // if we have crouching ability, then use it while we are just moving closer + if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0; + } + } + } else + { + return AIFunc_ChaseGoalIdleStart( cs, cs->followEntity, cs->followDist ); + } + } + } + // + // go to them + // + bs = cs->bs; + // + // RF, disabled this, MIKE sees dead people + //if (followent->client && followent->health <= 0) { + // AICast_EndChase( cs ); + // return AIFunc_IdleStart(cs); + //} + + // move straight to them if we can + if ( !moved && + ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) { + aicast_predictmove_t move; + vec3_t dir; + bot_input_t bi; + usercmd_t ucmd; + trace_t tr; + qboolean simTest = qfalse; + + if ( VectorLength( cs->bs->cur_ps.velocity ) < 120 ) { + simTest = qtrue; + } + + if ( !simTest ) { + // trace will eliminate most unsuccessful paths + trap_Trace( &tr, cs->bs->origin, NULL /*g_entities[cs->entityNum].r.mins*/, NULL /*g_entities[cs->entityNum].r.maxs*/, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask ); + if ( tr.entityNum == cs->followEntity || tr.fraction == 1 ) { + simTest = qtrue; + } + } + + if ( simTest ) { + // try walking straight to them + VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir ); + VectorNormalize( dir ); + if ( !ent->waterlevel ) { + dir[2] = 0; + } + trap_EA_Move( cs->entityNum, dir, 400 ); + trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); + AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles ); + AICast_PredictMovement( cs, 10, 0.8, &move, &ucmd, cs->followEntity ); + + if ( move.stopevent == PREDICTSTOP_HITENT ) { // success! + vectoangles( dir, cs->bs->ideal_viewangles ); + cs->bs->ideal_viewangles[2] *= 0.5; + moved = qtrue; + } + } + } + // + if ( !moved ) { + // use AAS routing + moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, cs->followEntity ); + if ( moveresult && moveresult->failure ) { + // shit? + } + } + + // should we slow down? + if ( cs->followDist && cs->followSlowApproach && cs->followDist < 48 ) { + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist ); + } + + // check for a movement we should be making + if ( cs->obstructingTime > level.time ) { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + if ( cs->movestate != MS_CROUCH ) { + cs->movestate = MS_WALK; + } + cs->movestateType = MSTYPE_TEMPORARY; + } + + // if we have an enemy, fire if they're visible + if ( cs->bs->enemy >= 0 ) { //attack the enemy if possible + AICast_ProcessAttack( cs ); + } else { + int numEnemies; + // + // look for things we should attack + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } else if ( numEnemies == -3 ) { // bullet impact + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectBulletImpactStart( cs ); + } + } else if ( numEnemies == -4 ) { // audible event + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt ); + } + } else if ( numEnemies > 0 ) { + int i; + + cs->bs->enemy = enemies[0]; // just attack the first one + // override with a visible enemy + for ( i = 1; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) { + cs->bs->enemy = enemies[i]; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + } + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + } + + // set head look flag if no enemy + if ( cs->bs->enemy < 0 && cs->attributes[TACTICAL] >= 0.5 && !( cs->aiFlags & AIFL_NO_HEADLOOK ) ) { + g_entities[cs->entityNum].client->ps.eFlags |= EF_HEADLOOK; + } + + return NULL; + +} + +/* +============ +AIFunc_ChaseGoalStart() +============ +*/ +char *AIFunc_ChaseGoalStart( cast_state_t *cs, int entitynum, float reachdist, qboolean slowApproach ) { + cs->followEntity = entitynum; + cs->followDist = reachdist; + cs->followIsGoto = qfalse; + cs->followSlowApproach = slowApproach; + cs->aifunc = AIFunc_ChaseGoal; + return "AIFunc_ChaseGoal"; +} + +/* +============ +AIFunc_DoorMarker() +============ +*/ +char *AIFunc_DoorMarker( cast_state_t *cs ) { + gentity_t *followent, *door; + bot_state_t *bs; + vec3_t destorg; + float dist; + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + + followent = &g_entities[cs->doorMarker]; + + // if the entity is not ready yet + if ( !followent->inuse ) { + cs->doorMarkerTime = 0; + //return AIFunc_DefaultStart( cs ); + return AIFunc_Restore( cs ); + } + + // if the door is open or idle + door = &g_entities[cs->doorEntNum]; + if ( ( !door->key ) && + ( door->s.apos.trType == TR_STATIONARY && door->s.pos.trType == TR_STATIONARY ) ) { + cs->doorMarkerTime = 0; + //return AIFunc_DefaultStart( cs ); + return AIFunc_Restore( cs ); + } + + // if we have an enemy, fire if they're visible + if ( cs->bs->enemy >= 0 ) { //attack the enemy if possible + AICast_ProcessAttack( cs ); + } + + // they are ready, are they inside range? FIXME: make configurable + dist = Distance( destorg, cs->bs->origin ); + if ( dist < 12 ) { + // check for a movement we should be making + if ( cs->obstructingTime > level.time ) { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + } + // if the door is locked, resume + if ( followent->key ) { + return AIFunc_Restore( cs ); + } + return NULL; + } + + // go to it + // + bs = cs->bs; + // + moveresult = AICast_MoveToPos( cs, followent->r.currentOrigin, followent->s.number ); + // if we cant get there, forget it + if ( moveresult && moveresult->failure ) { + return AIFunc_Restore( cs ); + } + // should we slow down? + if ( cs->followDist ) { + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, cs->followDist ); + } + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + return NULL; + +} + +/* +============ +AIFunc_DoorMarkerStart() +============ +*/ +char *AIFunc_DoorMarkerStart( cast_state_t *cs, int doornum, int markernum ) { + cs->doorEntNum = doornum; + cs->doorMarker = markernum; + cs->oldAifunc = cs->aifunc; + cs->aifunc = AIFunc_DoorMarker; + return "AIFunc_DoorMarker"; +} + +/* +============= +AIFunc_BattleRoll() +============= +*/ +char *AIFunc_BattleRoll( cast_state_t *cs ) { + gclient_t *client = &level.clients[cs->entityNum]; + vec3_t dir; + // + // record the time + cs->lastRollMove = level.time; + client->ps.eFlags |= EF_NOSWINGANGLES; + // + if ( !client->ps.torsoTimer ) { + if ( cs->battleRollTime < level.time ) { + return AIFunc_Restore( cs ); + } else { + // attack? + if ( cs->bs->enemy >= 0 ) { + AICast_ProcessAttack( cs ); + } + } + } + if ( g_entities[cs->entityNum].health <= 0 ) { + return AIFunc_DefaultStart( cs ); + } + if ( ( cs->bs->enemy >= 0 ) && ( client->ps.torsoTimer < 400 ) && ( cs->takeCoverTime < level.time ) ) { + // start turning towards our enemy + AICast_AimAtEnemy( cs ); + if ( client->ps.torsoTimer ) { + AICast_ProcessAttack( cs ); + } + } + // + // all characters so far only move during the first second of animation + if ( cs->thinkFuncChangeTime > level.time - 800 ) { + // just move in the direction of our ideal_viewangles + AngleVectors( cs->bs->ideal_viewangles, dir, NULL, NULL ); + trap_EA_Move( cs->entityNum, dir, 300 ); + // if we are crouching, move a little faster than normal + if ( cs->bs->attackcrouch_time > trap_AAS_Time() ) { + cs->speedScale = 1.5; + } + } else if ( cs->takeCoverTime > level.time ) { + // + // if we are taking Cover, use this position, if it's bad, we'll just look for a better spot once we're done here + VectorCopy( cs->bs->origin, cs->takeCoverPos ); + } + // + return NULL; +} + +/* +============= +AIFunc_BattleRollStart() +============= +*/ +char *AIFunc_BattleRollStart( cast_state_t *cs, vec3_t vec ) { + int duration; + // TTimo unused + //gclient_t *client = &level.clients[cs->entityNum]; + // + // backup the current thinkfunc, so we can return to it when done + cs->oldAifunc = cs->aifunc; + // + // face the direction of movement + vectoangles( vec, cs->bs->ideal_viewangles ); + // do the roll + duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_ROLL, qfalse, qfalse ); + // + if ( duration < 0 ) { // it failed + return NULL; + } + // add some duration to make sure it fully plays out + duration += 100; + g_entities[cs->entityNum].client->ps.legsTimer = duration; + g_entities[cs->entityNum].client->ps.torsoTimer = duration; + // + cs->noAttackTime = level.time + duration - 200; + // set the duration + cs->battleRollTime = level.time + duration; + // move into crouch position + cs->bs->attackcrouch_time = trap_AAS_Time() + ( 0.001 * duration ) + 1.0; + // record the time + cs->lastRollMove = level.time; + // + // make sure we move this frame + AIFunc_BattleRoll( cs ); + // + cs->aifunc = AIFunc_BattleRoll; + return "AIFunc_BattleRoll"; +} + +/* +============= +AIFunc_BattleDiveStart() +============= +*/ +char *AIFunc_BattleDiveStart( cast_state_t *cs, vec3_t vec ) { + int duration; + // TTimo unused + //gclient_t *client = &level.clients[cs->entityNum]; + // + // backup the current thinkfunc, so we can return to it when done + cs->oldAifunc = cs->aifunc; + // + // face the direction of movement + vectoangles( vec, cs->bs->ideal_viewangles ); + // do the roll + duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_DIVE, qfalse, qfalse ); + // + if ( duration < 0 ) { // it failed + return NULL; + } + // + cs->noAttackTime = level.time + duration - 200; + // set the duration + cs->battleRollTime = level.time + duration; + // move into crouch position + cs->bs->attackcrouch_time = trap_AAS_Time() + ( 0.001 * duration ) + 1.0; + // record the time + cs->lastRollMove = level.time; + // + // make sure we move this frame + AIFunc_BattleRoll( cs ); + // + cs->aifunc = AIFunc_BattleRoll; + return "AIFunc_BattleRoll"; +} + +/* +============= +AIFunc_FlipMove() +============= +*/ +char *AIFunc_FlipMove( cast_state_t *cs ) { + gclient_t *client = &level.clients[cs->entityNum]; + vec3_t dir; + // + if ( !client->ps.torsoTimer ) { + cs->bs->attackcrouch_time = 0; + return AIFunc_Restore( cs ); + } + if ( g_entities[cs->entityNum].health <= 0 ) { + return AIFunc_DefaultStart( cs ); + } + // + // just move in the direction of our ideal_viewangles + AngleVectors( cs->bs->ideal_viewangles, dir, NULL, NULL ); + trap_EA_Move( cs->entityNum, dir, 400 ); + // if we are crouching, move a little faster than normal + if ( cs->bs->attackcrouch_time > trap_AAS_Time() ) { + cs->speedScale = 1.5; + } + // + return NULL; +} + +/* +============= +AIFunc_FlipMoveStart() +============= +*/ +char *AIFunc_FlipMoveStart( cast_state_t *cs, vec3_t vec ) { + int duration; + // TTimo unused + //gclient_t *client = &level.clients[cs->entityNum]; + // + // backup the current thinkfunc, so we can return to it when done + cs->oldAifunc = cs->aifunc; + // + // record the time + cs->lastRollMove = level.time; + // face the direction of movement + vectoangles( vec, cs->bs->ideal_viewangles ); + cs->noAttackTime = level.time + 1200; + // do the roll + duration = BG_AnimScriptEvent( &g_entities[cs->entityNum].client->ps, ANIM_ET_ROLL, qfalse, qfalse ); + // + if ( duration < 0 ) { // it failed + return NULL; + } + // move into crouch position + cs->bs->attackcrouch_time = trap_AAS_Time() + 0.8; + // + // make sure we move this frame + AIFunc_FlipMove( cs ); + // + cs->aifunc = AIFunc_FlipMove; + return "AIFunc_FlipMove"; +} + +/* +============= +AIFunc_BattleHunt() +============= +*/ +char *AIFunc_BattleHunt( cast_state_t *cs ) { + const float chaseDist = 32; + gentity_t *followent, *ent; + bot_state_t *bs; + vec3_t destorg; + qboolean moved = qfalse; + // TTimo unused + //gclient_t *client = &level.clients[cs->entityNum]; + char *rval; + // TTimo might be used uninitialized + float dist = 0; + cast_state_t *ocs; + int *ammo, i; + + ent = &g_entities[cs->entityNum]; + + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + // + bs = cs->bs; + // + if ( bs->enemy < 0 ) { + return AIFunc_IdleStart( cs ); + } + // + ocs = AICast_GetCastState( cs->bs->enemy ); + // + if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + // + followent = &g_entities[bs->enemy]; + // + // if the entity is not ready yet + if ( !followent->inuse ) { + // if it's a connecting client, wait + if ( !( ( bs->enemy < MAX_CLIENTS ) + && ( ( followent->client && followent->client->pers.connected == CON_CONNECTING ) + || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking + bs->enemy = -1; + } + + return AIFunc_IdleStart( cs ); + } + // + // if we can see them, go back to an attack state + AICast_ChooseWeapon( cs, qtrue ); // enable special weapons, if we cant get them, change back + if ( AICast_EntityVisible( cs, bs->enemy, qtrue ) // take into account reaction time + && AICast_CheckAttack( cs, bs->enemy, qfalse ) + && cs->obstructingTime < level.time ) { + if ( AICast_StopAndAttack( cs ) ) { + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( rval = AIFunc_BattleStart( cs ) ) ) { + return rval; + } + } else { // just attack them now and keep chasing + AICast_ProcessAttack( cs ); + } + AICast_ChooseWeapon( cs, qfalse ); + } else + { + int numEnemies, shouldAttack; + + AICast_ChooseWeapon( cs, qfalse ); + + ammo = cs->bs->cur_ps.ammo; + shouldAttack = qfalse; + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + if ( cs->aiState < AISTATE_COMBAT ) { + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } + } else if ( numEnemies == -3 ) { // bullet impact + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectBulletImpactStart( cs ); + } + } else if ( numEnemies == -4 ) { // audible event + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt ); + } + } else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + if ( numEnemies > 0 ) { + // default to the first known enemy, overwrite if we find a clearer shot + cs->bs->enemy = enemies[0]; + // + for ( i = 0; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) { + cs->bs->enemy = enemies[i]; + shouldAttack = qtrue; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + // note: next frame we'll process this new enemy an begin an attack if necessary + } + } + AICast_ChooseWeapon( cs, qfalse ); + } + + // have we spent enough time in combat mode? + if ( cs->aiState == AISTATE_COMBAT ) { + if ( cs->vislist[bs->enemy].visible_timestamp < level.time - COMBAT_TIMEOUT ) { + AICast_StateChange( cs, AISTATE_ALERT ); + } + } + + // while hunting, use crouch mode if possible + if ( cs->attributes[ATTACK_CROUCH] >= 0.1 ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + + if ( cs->battleHuntPauseTime ) { + if ( cs->battleHuntPauseTime < level.time ) { + // pausetime has expired, so go into ambush mode + if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], cs->takeCoverPos ) ) { + // wait in ambush, for them to return + VectorCopy( cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], cs->combatGoalOrigin ); + return AIFunc_BattleAmbushStart( cs ); + } + // couldn't find a spot, so just stay here? + VectorCopy( cs->bs->origin, cs->combatGoalOrigin ); + VectorCopy( cs->bs->origin, cs->takeCoverPos ); + return AIFunc_BattleAmbushStart( cs ); + } else { + // stay here, looking around + if ( cs->battleHuntViewTime < level.time ) { + cs->battleHuntViewTime = level.time + 700 + rand() % 1000; + // set a random viewangle + cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( 45.0 + random() * 45.0 ) * ( 2 * ( rand() % 2 ) - 1 ) ); + cs->bs->ideal_viewangles[PITCH] = 0; + } + } + } else { + // cycle through markers + VectorCopy( cs->vislist[bs->enemy].chase_marker[cs->battleChaseMarker], destorg ); + if ( ( dist = Distance( destorg, cs->bs->origin ) ) < chaseDist ) { + if ( cs->battleChaseMarker == ( cs->vislist[bs->enemy].chase_marker_count - 1 ) ) { + cs->battleHuntPauseTime = level.time + 4000; + cs->battleHuntViewTime = level.time + 1000; + } else { + cs->battleChaseMarker += cs->battleChaseMarkerDir; + if ( cs->battleChaseMarker > cs->vislist[bs->enemy].chase_marker_count ) { + cs->battleChaseMarkerDir *= -1; + cs->battleChaseMarker = cs->vislist[bs->enemy].chase_marker_count - 1; + } else if ( cs->battleChaseMarker < 0 ) { + cs->battleChaseMarkerDir *= -1; + cs->battleChaseMarker = 0; + } + } + } + // + if ( cs->battleHuntPauseTime < level.time ) { + // just go to them + if ( !moved && cs->leaderNum < 0 ) { + moveresult = AICast_MoveToPos( cs, destorg, bs->enemy ); + if ( moveresult && moveresult->failure ) { // no path, so go back to idle behaviour + // try to go to ambush mode + cs->bs->enemy = -1; + return AIFunc_DefaultStart( cs ); + } else { + moved = qtrue; + } + } + } + } + // + // slow down real close to the goal, so we don't go passed it + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, chaseDist ); + + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + + return NULL; +} + +/* +============= +AIFunc_BattleHuntStart() +============= +*/ +char *AIFunc_BattleHuntStart( cast_state_t *cs ) { + cs->combatGoalTime = 0; + cs->battleChaseMarker = 0; + cs->battleHuntPauseTime = 0; + // + cs->aifunc = AIFunc_BattleHunt; + return "AIFunc_BattleHunt"; +} + +/* +============= +AIFunc_BattleAmbush() +============= +*/ +char *AIFunc_BattleAmbush( cast_state_t *cs ) { + bot_state_t *bs; + vec3_t destorg, vec; + float dist, moveDist; + int enemies[MAX_CLIENTS], numEnemies, i; + qboolean shouldAttack, idleYaw; + aicast_predictmove_t move; + int *ammo; + vec3_t dir; + // TTimo unused + //gclient_t *client = &level.clients[cs->entityNum]; + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + + // we need to move towards it + bs = cs->bs; + // + // note: removing this will cause problems down below! + if ( bs->enemy < 0 ) { + return AIFunc_IdleStart( cs ); + } + // + // have we spent enough time in combat mode? + if ( cs->aiState == AISTATE_COMBAT ) { + if ( cs->vislist[bs->enemy].visible_timestamp < level.time - COMBAT_TIMEOUT ) { + AICast_StateChange( cs, AISTATE_ALERT ); + } + } + // while hunting, use crouch mode if possible + if ( cs->attributes[ATTACK_CROUCH] >= 0.1 ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + // + VectorCopy( cs->takeCoverPos, destorg ); + VectorSubtract( destorg, cs->bs->origin, vec ); + vec[2] *= 0.2; + dist = VectorLength( vec ); + // + // update the chase marker + if ( cs->vislist[cs->bs->enemy].chase_marker_count > 0 ) { + VectorCopy( cs->vislist[cs->bs->enemy].chase_marker[cs->vislist[cs->bs->enemy].chase_marker_count - 1], cs->combatGoalOrigin ); + } + // + // look for things we should attack + // if we are out of ammo, we shouldn't bother trying to attack (and we should keep hiding) + ammo = cs->bs->cur_ps.ammo; + shouldAttack = qfalse; + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } else if ( numEnemies == -3 ) { // bullet impact + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectBulletImpactStart( cs ); + } + } else if ( numEnemies == -4 ) { // audible event + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt ); + } + } else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + if ( numEnemies > 0 ) { + // default to the first known enemy, overwrite if we find a clearer shot + cs->bs->enemy = enemies[0]; + // + for ( i = 0; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) { + cs->bs->enemy = enemies[i]; + shouldAttack = qtrue; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + } + + } else { // keep hiding forever + + cs->takeCoverTime = level.time + 1000; + + } + // + memset( &move, 0, sizeof( move ) ); + // + // are we close enough to the goal? + if ( VectorLength( cs->takeCoverPos ) > 1 && dist > 8 && ( cs->obstructingTime < level.time ) && !shouldAttack ) { + const float simTime = 0.8; + float enemyDist; + // + // we haven't reached it yet, make sure we at least wait there for a few seconds after arriving + cs->takeCoverTime = level.time + 2000 + rand() % 2000; + // + moveresult = AICast_MoveToPos( cs, destorg, -1 ); + if ( moveresult ) { + //if the movement failed + if ( moveresult->failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + // couldn't get there, so stop trying to get there + VectorClear( cs->takeCoverPos ); + dist = 0; + } + // + if ( moveresult->blocked ) { + // abort the TakeCover + VectorClear( cs->takeCoverPos ); + dist = 0; + } + } + // + // NOTE: this is also used by hidepos prediction (below) + // if we are going to bump into something soon, abort it + AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, -1 ); + enemyDist = Distance( cs->bs->origin, g_entities[cs->bs->enemy].s.pos.trBase ); + VectorSubtract( move.endpos, cs->bs->origin, vec ); + moveDist = VectorNormalize( vec ); + // + if ( ( move.numtouch && move.touchents[0] < aicast_maxclients ) // hit something + // or moved closer to the enemy + || ( ( enemyDist < 128 ) + && ( ( enemyDist - 1 ) > ( Distance( move.endpos, g_entities[cs->bs->enemy].s.pos.trBase ) ) ) ) ) { + // abort the manouver + VectorClear( cs->takeCoverPos ); + dist = 0; + } + // + // we should slow down on approaching the destination point + else if ( dist < 64 ) { + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 ); + } + + // don't actually hide, check if we are about to, so we can hide right here + if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) ) { + if ( move.numtouch || !AICast_VisibleFromPos( move.endpos, cs->entityNum, cs->combatGoalOrigin, cs->bs->enemy, qfalse ) ) { + // abort the manouver, reached a good spot + cs->aiFlags |= AIFL_MISCFLAG1; + VectorCopy( cs->bs->origin, cs->takeCoverPos ); + } + } + + } else { + // + // check for a movement we should be making + if ( cs->obstructingTime > level.time ) { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + } + // if we have some enemies that we can attack immediately (without going anywhere to chase them) + if ( shouldAttack ) { + return AIFunc_BattleStart( cs ); + } + // do we need to go to our leader? + else if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) { + // wait until we've been hiding for long enough + if ( level.time > cs->takeCoverTime ) { + return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue ); + } + } + // else, crouch while we hide + if ( cs->attributes[ATTACK_CROUCH] > 0.1 || cs->crouchHideFlag ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + } + // + idleYaw = qtrue; + // if we can see the place we are hiding from, look at it + if ( AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, cs->combatGoalOrigin, cs->lastEnemy, qfalse ) ) { + // face the position we are retreating from + VectorSubtract( cs->combatGoalOrigin, cs->bs->origin, dir ); + dir[2] = 0; + if ( VectorNormalize( dir ) > 4 ) { + idleYaw = qfalse; + vectoangles( dir, cs->bs->ideal_viewangles ); + } + + } + // + if ( idleYaw ) { // look around randomly (but not straight into walls) + + if ( cs->nextIdleAngleChange < level.time ) { + // wait a second before changing again + if ( ( cs->nextIdleAngleChange + 3000 ) < level.time ) { + + // FIXME: This could be changed to use some AAS sampling, which would: + // + // Given a src area, pick a random dest area which is visible from that area + // and return it's position, which we'd then use to set the next view angles + // + // This would result in more efficient, more realistic behaviour, since they'd + // also use PITCH angles to look at areas above/below them + + cs->idleYaw = AICast_GetRandomViewAngle( cs, 512 ); + + if ( abs( AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ) ) < 45 ) { + cs->nextIdleAngleChange = level.time + 1000 + rand() % 2500; + } else { // do really fast + cs->nextIdleAngleChange = level.time + 500; + } + + // adjust with time + cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ); + /// ((float)(cs->nextIdleAngleChange - level.time) / 1000.0); + + cs->bs->ideal_viewangles[PITCH] = 0; + } + } else if ( cs->idleYawChange ) { + cs->idleYawChange = AngleDelta( cs->idleYaw, cs->bs->ideal_viewangles[YAW] ); + cs->bs->ideal_viewangles[YAW] = AngleMod( cs->bs->ideal_viewangles[YAW] + ( cs->idleYawChange * cs->bs->thinktime ) ); + } + + } + // + if ( !cs->crouchHideFlag ) { // no enemy, and no need to crouch, so stop crouching + if ( cs->bs->attackcrouch_time > trap_AAS_Time() + 1 ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + } + + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + + return NULL; +} + +/* +============= +AIFunc_BattleAmbushStart() +============= +*/ +char *AIFunc_BattleAmbushStart( cast_state_t *cs ) { + if ( !AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum ) ) { + // always run to the cover point + cs->bs->attackcrouch_time = 0; + } else if ( cs->bs->attackcrouch_time > trap_AAS_Time() + 1.0 ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0; + } + + // + // start a crouch attack? + if ( cs->attributes[ATTACK_CROUCH] > 0.1 && cs->bs->attackcrouch_time >= trap_AAS_Time() ) { + // continue + cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0; + } + // if we arent crouching, start crouching soon after we start retreating + if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) { + cs->aiFlags |= AIFL_ATTACK_CROUCH; + } else { + cs->aiFlags &= ~AIFL_ATTACK_CROUCH; + } + + // miscflag1 used to set predicted point as our goal, so we dont keep setting this over and over + cs->aiFlags &= ~AIFL_MISCFLAG1; + + cs->aifunc = AIFunc_BattleAmbush; + return "AIFunc_BattleAmbush"; +} + +/* +============ +AIFunc_BattleChase() +============ +*/ +char *AIFunc_BattleChase( cast_state_t *cs ) { + const float chaseDist = 32; + gentity_t *followent, *ent; + bot_state_t *bs; + vec3_t destorg; + qboolean moved = qfalse; + gclient_t *client = &level.clients[cs->entityNum]; + char *rval; + float dist; + cast_state_t *ocs; + + ent = &g_entities[cs->entityNum]; + + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + + bs = cs->bs; + // + if ( bs->enemy < 0 ) { + return AIFunc_IdleStart( cs ); + } + // + // Retreat? + if ( AICast_WantToRetreat( cs ) ) { + if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) { + // go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time + cs->takeCoverTime = level.time + 2000 + rand() % 3000; + return AIFunc_BattleTakeCoverStart( cs ); + } + } + // + ocs = AICast_GetCastState( cs->bs->enemy ); + // + if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + // + followent = &g_entities[bs->enemy]; + // + // if the entity is not ready yet + if ( !followent->inuse ) { + // if it's a connecting client, wait + if ( !( ( bs->enemy < MAX_CLIENTS ) + && ( ( followent->client && followent->client->pers.connected == CON_CONNECTING ) + || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking + bs->enemy = -1; + } + + return AIFunc_IdleStart( cs ); + } + // + // if we can see them, go back to an attack state + AICast_ChooseWeapon( cs, qtrue ); // enable special weapons, if we cant get them, change back + if ( AICast_EntityVisible( cs, bs->enemy, qtrue ) // take into account reaction time + && AICast_CheckAttack( cs, bs->enemy, qfalse ) + && cs->obstructingTime < level.time ) { + if ( AICast_StopAndAttack( cs ) ) { + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( rval = AIFunc_BattleStart( cs ) ) ) { + return rval; + } + } else { // just attack them now and keep chasing + AICast_ProcessAttack( cs ); + } + AICast_ChooseWeapon( cs, qfalse ); + } else + { + AICast_ChooseWeapon( cs, qfalse ); + // not visible, go to their previously visible position + /* + if (!cs->vislist[bs->enemy].visible_timestamp || Distance( bs->origin, cs->vislist[bs->enemy].visible_pos ) < 16) + { + // we're done attacking, go back to default state, which in turn will recall previous state + // + return AIFunc_DefaultStart( cs ); + } + */ + } + // + // find the chase position + if ( followent->client ) { + // go to the last visible position + VectorCopy( cs->vislist[bs->enemy].visible_pos, destorg ); + // if we have reached it, go into hunt mode + if ( ( dist = Distance( destorg, cs->bs->origin ) ) < chaseDist ) { + // if we haven't been hunted for a while, do so + if ( ocs->lastBattleHunted < level.time - 5000 ) { + ocs->lastBattleHunted = level.time; + return AIFunc_BattleHuntStart( cs ); + } + // otherwise, go into ambush mode + if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) { + VectorCopy( cs->vislist[cs->bs->enemy].real_visible_pos, cs->combatGoalOrigin ); + return AIFunc_BattleAmbushStart( cs ); + } + // couldn't find a spot, so just stay here? + VectorCopy( cs->bs->origin, cs->combatGoalOrigin ); + VectorCopy( cs->bs->origin, cs->takeCoverPos ); + return AIFunc_BattleAmbushStart( cs ); + } + } else // assume we know where other entities are + { + VectorCopy( followent->s.pos.trBase, destorg ); + dist = Distance( cs->bs->origin, destorg ); + } + // + // if the enemy is inside a CONTENTS_DONOTENTER brush, and we are close enough, stop chasing them + if ( AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) && VectorDistance( cs->bs->origin, destorg ) < 384 ) { + if ( trap_PointContents( destorg, cs->bs->enemy ) & ( CONTENTS_DONOTENTER | CONTENTS_DONOTENTER_LARGE ) ) { + // just stay here, and hope they move out of the brush without finding a spot where they can hit us but we can't hit them + return NULL; + } + } + // + // is there someone else we can go for instead? + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } + AICast_ChooseWeapon( cs, qtrue ); // enable special weapons, if we cant get them, change back + if ( numEnemies > 0 ) { + int i; + for ( i = 0; i < numEnemies; i++ ) { + if ( enemies[i] != cs->bs->enemy && AICast_CheckAttack( cs, enemies[i], qfalse ) ) { + cs->bs->enemy = enemies[i]; + return AIFunc_BattleStart( cs ); + } + } + } + AICast_ChooseWeapon( cs, qfalse ); + + // + // if we only recently saw them, face them + // + if ( cs->vislist[cs->bs->enemy].visible_timestamp > level.time - 3000 ) { + AICast_AimAtEnemy( cs ); // be ready for an attack if they become visible again + //if (cs->attributes[ATTACK_CROUCH] > 0.1) { // crouching for combat + // cs->bs->attackcrouch_time = trap_AAS_Time() + 1.0; + //} + } + + // + // Lob a Grenade? + // if we haven't thrown a grenade in a bit, go into "grenade flush mode" + if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) && + ( cs->castScriptStatus.castScriptEventIndex < 0 ) && + ( cs->startGrenadeFlushTime < level.time - 3000 ) && + ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) && + ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) && + ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) && + ( ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) > 100 ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) < 1400 ) ) { + // try and flush them out with a grenade + //G_Printf("pineapple?\n"); + return AIFunc_GrenadeFlushStart( cs ); + } else if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) && + ( cs->castScriptStatus.castScriptEventIndex < 0 ) && + ( cs->startGrenadeFlushTime < level.time - 2000 ) && + ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_PINEAPPLE ) ) && + ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) && + ( cs->weaponFireTimes[WP_GRENADE_PINEAPPLE] < level.time - (int)( aicast_skillscale * 3000 ) ) && + ( ( cs->bs->weaponnum == WP_GRENADE_PINEAPPLE ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) > 100 ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].visible_pos ) < 1400 ) ) { + // try and flush them out with a grenade + //G_Printf("pineapple?\n"); + return AIFunc_GrenadeFlushStart( cs ); + } + // + // Flaming Zombie? Shoot flames while running + if ( ( cs->aiCharacter == AICHAR_ZOMBIE ) && + ( IS_FLAMING_ZOMBIE( ent->s ) ) && + ( fabs( cs->bs->ideal_viewangles[YAW] - cs->bs->viewangles[YAW] ) < 5 ) ) { + if ( fabs( sin( ( level.time + cs->entityNum * 314 ) / 1000 ) * cos( ( level.time + cs->entityNum * 267 ) / 979 ) ) < 0.5 ) { + ent->s.time = level.time + 800; + } + } + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + + if ( dist < chaseDist ) { + return NULL; + } + + // + // go to them + // + // ........................................................... + // Do the movement.. + // + // move straight to them if we can + if ( !moved && cs->leaderNum < 0 && + ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) && + AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) ) { + aicast_predictmove_t move; + vec3_t dir; + bot_input_t bi; + usercmd_t ucmd; + trace_t tr; + + // trace will eliminate most unsuccessful paths + trap_Trace( &tr, cs->bs->origin, g_entities[cs->entityNum].r.mins, g_entities[cs->entityNum].r.maxs, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask ); + if ( tr.entityNum == followent->s.number ) { + // try walking straight to them + VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir ); + VectorNormalize( dir ); + if ( !ent->waterlevel ) { + dir[2] = 0; + } + trap_EA_Move( cs->entityNum, dir, 400 ); + trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); + AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles ); + AICast_PredictMovement( cs, 5, 2.0, &move, &ucmd, bs->enemy ); + + if ( move.stopevent == PREDICTSTOP_HITENT ) { // success! + vectoangles( dir, cs->bs->ideal_viewangles ); + cs->bs->ideal_viewangles[2] *= 0.5; + moved = qtrue; + } + } + } + // + // if they are visible, but not attackable, look for a spot where we can attack them, and head + // for there. This should prevent AI's getting stuck in a bunch. + if ( !moved && cs->bs->weaponnum >= WP_LUGER && cs->bs->weaponnum <= WP_SPEARGUN_CO2 && cs->attributes[TACTICAL] >= 0.1 ) { + // + // check for another movement we should be making + if ( cs->obstructingTime > level.time ) { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + moved = qtrue; + } + // + if ( cs->leaderNum >= 0 ) { + if ( cs->combatGoalTime < level.time ) { + if ( cs->attackSpotTime < level.time ) { + cs->attackSpotTime = level.time + 500 + rand() % 500; + if ( trap_AAS_FindAttackSpotWithinRange( cs->entityNum, cs->leaderNum, cs->bs->enemy, MAX_LEADER_DIST, AICAST_TFL_DEFAULT, cs->combatGoalOrigin ) ) { + cs->combatGoalTime = level.time + 2000; + } + } + } + if ( cs->combatGoalTime > level.time ) { + if ( Distance( cs->combatGoalOrigin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) { + // go find a new combatSpot + cs->combatGoalTime = 0; + } else { + // go to the combat spot + moveresult = AICast_MoveToPos( cs, cs->combatGoalOrigin, -1 ); + if ( moveresult && moveresult->failure ) { // no path, so go back to idle behaviour + cs->combatGoalTime = 0; + } else { + moved = qtrue; + if ( Distance( cs->bs->origin, cs->combatGoalOrigin ) < 32 ) { + cs->combatGoalTime = 0; + } + } + } + } else { + // we can't find a way to get to our enemy, so go back to our leader if outside range + // do we need to go to our leader? + if ( Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) { + return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue ); + } + } + } else { + if ( cs->combatGoalTime < level.time ) { + if ( cs->attackSpotTime < level.time ) { + cs->attackSpotTime = level.time + 500 + rand() % 500; + if ( trap_AAS_FindAttackSpotWithinRange( cs->entityNum, cs->entityNum, cs->bs->enemy, 512, AICAST_TFL_DEFAULT, cs->combatGoalOrigin ) ) { + cs->combatGoalTime = level.time + 2000; + } + } + } + if ( cs->combatGoalTime > level.time ) { + // go to the combat spot + moveresult = AICast_MoveToPos( cs, cs->combatGoalOrigin, -1 ); + if ( moveresult && moveresult->failure ) { // no path, so go back to idle behaviour + cs->combatGoalTime = 0; + } else { + moved = qtrue; + if ( Distance( cs->bs->origin, cs->combatGoalOrigin ) < 32 ) { + cs->combatGoalTime = 0; + } + } + } + } + } + // just go to them + if ( !moved && cs->leaderNum < 0 ) { + moveresult = AICast_MoveToPos( cs, destorg, bs->enemy ); + if ( moveresult && moveresult->failure ) { // no path, so try and hude from them + // pausetime has expired, so go into ambush mode + if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) { + // wait in ambush, for them to return + VectorCopy( cs->bs->origin, cs->combatGoalOrigin ); + return AIFunc_BattleAmbushStart( cs ); + } + // couldn't find a spot, so just stay here? + VectorCopy( cs->bs->origin, cs->combatGoalOrigin ); + VectorCopy( cs->bs->origin, cs->takeCoverPos ); + return AIFunc_BattleAmbushStart( cs ); + } else { + moved = qtrue; + } + } + // + // slow down real close to the goal, so we don't go passed it + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, chaseDist ); + // + // ........................................................... + // speed up over some time + #define BATTLE_CHASE_ACCEL_TIME 300 + if ( ( cs->attributes[RUNNING_SPEED] > 170 ) && ( cs->bs->weaponnum != WP_GAUNTLET ) && ( level.time < ( cs->startBattleChaseTime + BATTLE_CHASE_ACCEL_TIME ) ) ) { + float ideal; + + ideal = 0.5 + 0.5 * ( 1.0 - ( (float)( ( cs->startBattleChaseTime + BATTLE_CHASE_ACCEL_TIME ) - level.time ) / BATTLE_CHASE_ACCEL_TIME ) ); + if ( ideal < cs->speedScale ) { + cs->speedScale = ideal; + } + } + // + // if we are going to reach them soon, predict the attack + { + float simTime = 1.0; + aicast_predictmove_t move; + float moveDist; + vec3_t vec; + // + if ( cs->bs->weaponnum == WP_GAUNTLET ) { + simTime = 0.5; + } + // + AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, cs->bs->enemy ); + VectorSubtract( move.endpos, cs->bs->origin, vec ); + moveDist = VectorNormalize( vec ); + // + if ( cs->bs->weaponnum == WP_GAUNTLET ) { + if ( move.stopevent == PREDICTSTOP_HITENT ) { + AICast_AimAtEnemy( cs ); + trap_EA_Attack( bs->client ); + bs->flags |= BFL_ATTACKED; + } + } + // + // do we went to play a diving animation into a cover position? + else if ( ( cs->attributes[TACTICAL] > 0.85 && cs->aiFlags & AIFL_ROLL_ANIM && !client->ps.torsoTimer && !client->ps.legsTimer && cs->lastRollMove < level.time - 800 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.8 && move.groundEntityNum == ENTITYNUM_WORLD ) && + ( AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, move.endpos, cs->bs->attackcrouch_time > trap_AAS_Time(), qfalse ) ) ) { + return AIFunc_BattleRollStart( cs, vec ); + } + // + else if ( cs->aiFlags & AIFL_FLIP_ANIM && cs->lastRollMove < level.time - 800 && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.9 && move.groundEntityNum == ENTITYNUM_WORLD && cs->bs->attackcrouch_time < trap_AAS_Time() ) { + int destarea, simarea, starttravel, simtravel; + // if we'll be closer after the move, proceed + destarea = BotPointAreaNum( destorg ); + simarea = BotPointAreaNum( move.endpos ); + starttravel = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, destarea, cs->travelflags ); + simtravel = trap_AAS_AreaTravelTimeToGoalArea( simarea, move.endpos, destarea, cs->travelflags ); + if ( simtravel < starttravel ) { + return AIFunc_FlipMoveStart( cs, vec ); + } + } + // slow down? so we don't go too far from behind the obstruction which is protecting us + else if ( ( cs->obstructingTime < level.time ) && ( cs->attributes[TACTICAL] > 0.1 ) && + ( AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->enemy, move.endpos, cs->entityNum, qfalse ) ) ) { + // start a crouch attack? + //if (cs->attributes[ATTACK_CROUCH] > 0.1) { + // cs->bs->attackcrouch_time = trap_AAS_Time() + 3.0; + //else + cs->bs->attackcrouch_time = 0; + if ( cs->bs->cur_ps.viewheight > cs->bs->cur_ps.crouchViewHeight && cs->attributes[RUNNING_SPEED] * cs->speedScale > 120 ) { + cs->speedScale = 120.0 * cs->attributes[RUNNING_SPEED]; + } + // also face them, ready for the attack + AICast_AimAtEnemy( cs ); + /* disabled, causes them to use up ammo in the clip before they get visible + if ((cs->castScriptStatus.scriptNoAttackTime < level.time) && (cs->noAttackTime < level.time)) { + // if we are using a bullet weapon, start firing now + switch (cs->bs->weaponnum) { + case WP_MP40: + case WP_VENOM: + case WP_THOMPSON: + case WP_STEN: //----(SA) added + trap_EA_Attack(cs->entityNum); + } + } + */ + } + } + + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + + return NULL; +} + +/* +============ +AIFunc_BattleChaseStart() +============ +*/ +char *AIFunc_BattleChaseStart( cast_state_t *cs ) { + cs->startBattleChaseTime = level.time; + cs->combatGoalTime = 0; + cs->battleChaseMarker = -99; + cs->battleChaseMarkerDir = 1; + // don't wait too long before taking cover, if we just aborted one + if ( cs->takeCoverTime > level.time ) { + cs->takeCoverTime = level.time + 1500 + rand() % 500; + } + // + // start a crouch attack? + if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) { + cs->aiFlags |= AIFL_ATTACK_CROUCH; + } else { + cs->aiFlags &= ~AIFL_ATTACK_CROUCH; + } + // + cs->aifunc = AIFunc_BattleChase; + return "AIFunc_BattleChase"; +} + +/* +============ +AIFunc_AvoidDanger() +============ +*/ +char *AIFunc_AvoidDanger( cast_state_t *cs ) { + bot_state_t *bs; + vec3_t destorg, vec; + float dist; + int enemies[MAX_CLIENTS], numEnemies, i; + qboolean shouldAttack; + gentity_t *ent; + trace_t tr; + vec3_t end; + gentity_t *danger; + + // we need to move towards it + bs = cs->bs; + ent = g_entities + cs->entityNum; + // + // TODO: if we are on fire, play the correct torso animation + if ( ent->s.onFireEnd > level.time ) { + // set the animation, and a short timer, but long enough to last until the next frame + //if (g_cheats.integer) G_Printf( "TODO: torso onfire animation\n" ); + } + // + // is the danger gone? + if ( cs->dangerEntityValidTime < level.time ) { + return AIFunc_DefaultStart( cs ); + } + // + // special case: if it's a grenade, and it's going to land near us with some time left before it + // explodes, try and kick it back + // + danger = &g_entities[cs->dangerEntity]; + if ( ent->s.onFireEnd < level.time ) { + if ( ( danger->s.weapon == WP_GRENADE_LAUNCHER || danger->s.weapon == WP_GRENADE_PINEAPPLE ) && + ( danger->nextthink - level.time > 1500 ) && + ( level.lastGrenadeKick < level.time - 3000 ) && + ( cs->aiFlags & AIFL_CATCH_GRENADE ) && + !( danger->flags & FL_AI_GRENADE_KICK ) ) { + // if it was thrown by a friend of ours, leave it alone + if ( !AICast_SameTeam( cs, danger->r.ownerNum ) ) { + if ( G_PredictMissile( danger, danger->nextthink - level.time, cs->takeCoverPos, qfalse ) ) { + // make sure it's a valid position, and drop it down to the ground + cs->takeCoverPos[2] += -ent->r.mins[2] + 12; + VectorCopy( cs->takeCoverPos, end ); + end[2] -= 90; + trap_Trace( &tr, cs->takeCoverPos, ent->r.mins, ent->r.maxs, end, cs->entityNum, MASK_SOLID ); + VectorCopy( tr.endpos, cs->takeCoverPos ); + if ( !tr.startsolid && ( tr.fraction < 1.0 ) && + VectorDistance( cs->bs->origin, cs->takeCoverPos ) < cs->attributes[RUNNING_SPEED] * 0.0004 * ( danger->nextthink - level.time - 2000 ) ) { + + // check for a clear path to the grenade + trap_Trace( &tr, cs->bs->origin, ent->r.mins, ent->r.maxs, cs->takeCoverPos, cs->entityNum, MASK_SOLID ); + + if ( VectorDistance( tr.endpos, cs->takeCoverPos ) < 8 ) { + danger->flags |= FL_AI_GRENADE_KICK; + ent->flags |= FL_AI_GRENADE_KICK; + level.lastGrenadeKick = level.time; + return AIFunc_GrenadeKickStart( cs ); // we should decide our course of action within this start function (dive or return grenade) + } + } + } + } + // if it's really close to us, and we're heading for it, may as well pick it up + if ( VectorLength( danger->s.pos.trDelta ) < 10 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 128 && + ( level.lastGrenadeKick < level.time - 3000 ) && + ( cs->aiFlags & AIFL_CATCH_GRENADE ) ) { + vec3_t vec; + VectorSubtract( danger->r.currentOrigin, cs->bs->origin, vec ); + if ( DotProduct( vec, cs->bs->velocity ) > 0 ) { + danger->flags |= FL_AI_GRENADE_KICK; + ent->flags |= FL_AI_GRENADE_KICK; + level.lastGrenadeKick = level.time; + return AIFunc_GrenadeKickStart( cs ); // we should decide our course of action within this start function (dive or return grenade) + } + } + } + } + // + if ( g_entities[cs->dangerEntity].inuse ) { + // is our current destination still safe? + if ( Distance( cs->dangerEntityPos, cs->takeCoverPos ) < cs->dangerDist && + AICast_VisibleFromPos( cs->dangerEntityPos, cs->dangerEntity, cs->takeCoverPos, cs->entityNum, qfalse ) ) { + //G_Printf("current coverPos is dangerous, looking for a better place..\n" ); + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // just run away from it ??? + } + } + } else { + // the entity isn't here anymore, so stop hiding + cs->dangerEntityValidTime = -1; + return AIFunc_DefaultStart( cs ); + } + // + VectorCopy( cs->takeCoverPos, destorg ); + VectorSubtract( destorg, cs->bs->origin, vec ); + vec[2] *= 0.2; + dist = VectorLength( vec ); + // + shouldAttack = qfalse; + if ( ent->s.onFireEnd < level.time ) { + // look for things we should attack + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies > 0 ) { + // default to the first known enemy, overwrite if we find a clearer shot + cs->bs->enemy = enemies[0]; + // + for ( i = 0; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) { + cs->bs->enemy = enemies[i]; + shouldAttack = qtrue; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + } + } + // + // if we are now safe from the danger, stop running away + if ( cs->dangerEntity >= MAX_CLIENTS && Distance( cs->dangerEntityPos, cs->bs->origin ) > cs->dangerDist * 1.5 ) { + // don't move, wait for danger to pass + } else + // are we close enough to the goal? + if ( dist > 8 ) { + moveresult = AICast_MoveToPos( cs, destorg, -1 ); + if ( moveresult ) { + //if the movement failed + if ( moveresult->failure || moveresult->blocked ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + if ( g_entities[cs->dangerEntity].inuse ) { + // find a better spot? + AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ); + } else { + VectorCopy( cs->bs->origin, cs->takeCoverPos ); + } + } + } + if ( ent->s.onFireEnd < level.time ) { + // slow down real close to the goal, so we don't go passed it + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 ); + } + // + // pretend we can still see them while we run to our hide pos, this way they are less likely + // to forget about their enemy once they get there + if ( ent->s.onFireEnd < level.time && cs->bs->enemy >= 0 && cs->vislist[cs->bs->enemy].real_visible_timestamp && ( cs->vislist[cs->bs->enemy].real_visible_timestamp > level.time - 10000 ) ) { + AICast_UpdateVisibility( &g_entities[cs->entityNum], &g_entities[cs->bs->enemy], qfalse, cs->vislist[cs->bs->enemy].real_visible_timestamp == cs->vislist[cs->bs->enemy].lastcheck_timestamp ); + } + + } else { + // set our origin as the hidepos, that way if we are still in danger, we should find a better spot + VectorCopy( cs->bs->origin, cs->takeCoverPos ); + // check for a movement we should be making + if ( cs->obstructingTime > level.time ) { + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + } + + // if we are on fire, never stop + if ( ent->s.onFireEnd > level.time ) { + VectorCopy( cs->bs->origin, cs->dangerEntityPos ); + cs->dangerEntityValidTime = level.time + 10000; + } + + } + // + // if we should be attacking something on our way + if ( shouldAttack ) { + //attack the enemy if possible + AICast_ProcessAttack( cs ); + } else { //if (dist < 48) { + // if we've recently been in a fight, look towards the enemy + if ( cs->lastEnemy >= 0 ) { + // if we only just recently saw them, face that direction + if ( cs->vislist[cs->lastEnemy].visible_timestamp > ( level.time - 20000 ) ) { + vec3_t dir; + // + VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, cs->bs->ideal_viewangles ); + } + } + } + + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + + return NULL; +} + +/* +============ +AIFunc_AvoidDangerStart() +============ +*/ +char *AIFunc_AvoidDangerStart( cast_state_t *cs ) { + // + //if (!AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum )) { + // always run to the cover point + cs->bs->attackcrouch_time = 0; + //} + // make sure we move if we are allowed (scripting will overwrite this if necessary) + cs->castScriptStatus.scriptNoMoveTime = 0; + // resume following once danger has gone + cs->castScriptStatus.scriptGotoId = -1; + // + cs->aifunc = AIFunc_AvoidDanger; + return "AIFunc_AvoidDanger"; +} + +/* +============ +AIFunc_BattleTakeCover() +============ +*/ +char *AIFunc_BattleTakeCover( cast_state_t *cs ) { + bot_state_t *bs; + vec3_t destorg, vec; + float dist, moveDist; + int enemies[MAX_CLIENTS], numEnemies, i; + qboolean shouldAttack; + aicast_predictmove_t move; + int *ammo; + gclient_t *client = &level.clients[cs->entityNum]; + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + + // we need to move towards it + bs = cs->bs; + // + // note: removing this will cause problems down below! + if ( bs->enemy < 0 ) { + return AIFunc_IdleStart( cs ); + } + // + VectorCopy( cs->takeCoverPos, destorg ); + VectorSubtract( destorg, cs->bs->origin, vec ); + vec[2] *= 0.2; + dist = VectorLength( vec ); + // + // look for things we should attack + // if we are out of ammo, we shouldn't bother trying to attack (and we should keep hiding) + ammo = cs->bs->cur_ps.ammo; + shouldAttack = qfalse; + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } else if ( numEnemies == -3 ) { // bullet impact + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectBulletImpactStart( cs ); + } + } else if ( numEnemies == -4 ) { // audible event + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt ); + } + } else if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + if ( numEnemies > 0 ) { + // default to the first known enemy, overwrite if we find a clearer shot + cs->bs->enemy = enemies[0]; + // + for ( i = 0; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) { + cs->bs->enemy = enemies[i]; + shouldAttack = qtrue; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + } + } + // + if ( !shouldAttack ) { + // if the enemy can see our hide position, find a better spot + if ( AICast_VisibleFromPos( g_entities[bs->enemy].client->ps.origin, bs->enemy, cs->takeCoverPos, bs->entitynum, qfalse ) ) { + if ( !AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos ) ) { + // shit!! umm.. try and fire? + return AIFunc_BattleStart( cs ); + } else { // recalc distance + VectorCopy( cs->takeCoverPos, destorg ); + VectorSubtract( destorg, cs->bs->origin, vec ); + vec[2] *= 0.2; + dist = VectorLength( vec ); + } + } else if ( dist < 8 ) { + // if they can see us, find a better spot + if ( AICast_EntityVisible( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qtrue ) || AICast_CheckAttack( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qfalse ) ) { + if ( !AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos ) ) { + // shit!! umm.. try and fire? + return AIFunc_BattleStart( cs ); + } else { // recalc distance + VectorCopy( cs->takeCoverPos, destorg ); + VectorSubtract( destorg, cs->bs->origin, vec ); + vec[2] *= 0.2; + dist = VectorLength( vec ); + } + } + } + //cs->takeCoverTime = level.time + 1000; + } + // + // pretend we can still see them while we run to our hide pos, this way they are less likely + // to forget about their enemy once they get there +// DISABLED: doesn't work well with new AI system + //if (cs->bs->enemy >= 0 && cs->vislist[cs->bs->enemy].real_visible_timestamp && (cs->vislist[cs->bs->enemy].real_visible_timestamp > level.time - 2000)) { + // AICast_UpdateVisibility( &g_entities[cs->entityNum], &g_entities[cs->bs->enemy], qfalse, cs->vislist[cs->bs->enemy].real_visible_timestamp == cs->vislist[cs->bs->enemy].lastcheck_timestamp ); + //} + // + memset( &move, 0, sizeof( move ) ); + // + // are we close enough to the goal? + if ( VectorLength( cs->takeCoverPos ) > 1 && dist > 8 ) { + const float simTime = 0.8; + float enemyDist; + // + // we haven't reached it yet, make sure we at least wait there for a few seconds after arriving + cs->takeCoverTime = level.time + 2000 + rand() % 2000; + // + moveresult = AICast_MoveToPos( cs, destorg, -1 ); + if ( moveresult ) { + //if the movement failed + if ( moveresult->failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + // couldn't get there, so stop trying to get there + VectorClear( cs->takeCoverPos ); + dist = 0; + } + // + if ( moveresult->blocked ) { + // abort the TakeCover + VectorClear( cs->takeCoverPos ); + dist = 0; + } + } + // + // if we are going to bump into something soon, abort it + AICast_PredictMovement( cs, 1, simTime, &move, &cs->bs->lastucmd, -1 ); + enemyDist = Distance( cs->bs->origin, g_entities[cs->bs->enemy].s.pos.trBase ); + VectorSubtract( move.endpos, cs->bs->origin, vec ); + moveDist = VectorNormalize( vec ); + // + if ( ( move.numtouch && move.touchents[0] < aicast_maxclients ) // hit something + // or moved closer to the enemy + || ( ( enemyDist < 128 ) + && ( ( enemyDist - 1 ) > ( Distance( move.endpos, g_entities[cs->bs->enemy].s.pos.trBase ) ) ) ) ) { + // abort the manouver + VectorClear( cs->takeCoverPos ); + dist = 0; + } + // + // do we went to play a rolling animation into a cover position? + else if ( ( cs->aiFlags & AIFL_DIVE_ANIM && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && cs->lastRollMove < level.time - 800 && move.numtouch == 0 && moveDist > 64 && move.groundEntityNum == ENTITYNUM_WORLD ) && + ( shouldAttack && !AICast_VisibleFromPos( g_entities[cs->bs->enemy].s.pos.trBase, cs->bs->enemy, move.endpos, cs->entityNum, qfalse ) ) ) { + VectorClear( cs->takeCoverPos ); // stay there when done rolling + return AIFunc_BattleDiveStart( cs, vec ); + } + // + // we should slow down on approaching the destination point + else if ( dist < 64 ) { + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 ); + } + // + // if they cant see us, then stay here + if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) && !AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].real_visible_pos, cs->bs->enemy, move.endpos, cs->entityNum, qfalse ) + && !AICast_VisibleFromPos( cs->vislist[cs->bs->enemy].real_visible_pos, cs->bs->enemy, cs->bs->origin, cs->entityNum, qfalse ) ) { + VectorCopy( move.endpos, cs->takeCoverPos ); + dist = 0; + cs->aiFlags |= AIFL_MISCFLAG1; // dont do this again + } + // + if ( cs->aiFlags & AIFL_FLIP_ANIM && cs->lastRollMove < level.time - 800 && !client->ps.torsoTimer && cs->castScriptStatus.castScriptEventIndex < 0 && move.numtouch == 0 && moveDist > simTime * cs->attributes[RUNNING_SPEED] * 0.9 && move.groundEntityNum == ENTITYNUM_WORLD && cs->bs->attackcrouch_time < trap_AAS_Time() ) { + int destarea, simarea, starttravel, simtravel; + // if we'll be closer after the move, proceed + destarea = BotPointAreaNum( destorg ); + simarea = BotPointAreaNum( move.endpos ); + starttravel = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, destarea, cs->travelflags ); + simtravel = trap_AAS_AreaTravelTimeToGoalArea( simarea, move.endpos, destarea, cs->travelflags ); + if ( simtravel < starttravel ) { + return AIFunc_FlipMoveStart( cs, vec ); + } + } + // set crouching status + //if (dist && (cs->thinkFuncChangeTime < level.time - 2000) && (cs->crouchHideFlag || cs->aiFlags & AIFL_ATTACK_CROUCH)) { + if ( cs->crouchHideFlag || cs->aiFlags & AIFL_ATTACK_CROUCH ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + + } else { + // + // have we been Taking Cover for enough time? + if ( level.time > cs->takeCoverTime ) { + return AIFunc_DefaultStart( cs ); + } + // + // check for a movement we should be making + if ( cs->obstructingTime > level.time ) { + VectorClear( cs->takeCoverPos ); + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + } + // if we have some enemies that we can attack immediately (without going anywhere to chase them) + if ( shouldAttack ) { + return AIFunc_BattleStart( cs ); + } + // if we have some enemies in sight, but they can't attack us, flee if possible, otherwise if we are not afraid, go attack them + else if ( numEnemies ) { + + // we can't hit them and they cant hit us, so dont bother doing anything + + //if (!AICast_GetTakeCoverPos( cs, bs->enemy, cs->vislist[bs->enemy].visible_pos, cs->takeCoverPos )) { + //if (!AICast_WantsToTakeCover(cs, qfalse)) + //return AIFunc_BattleStart( cs ); + //} + } + // do we need to go to our leader? + else if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) { + // wait until we've been hiding for long enough + if ( level.time > cs->takeCoverTime ) { + return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue ); + } + } + + // else, crouch while we hide + if ( cs->attributes[ATTACK_CROUCH] > 0.1 || cs->crouchHideFlag ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + } + // + // if we should be attacking something on our way + if ( shouldAttack ) { + vec3_t vec, dir; + float dist; + // + // if they are close, and we're heading for them, we should abort this manouver + VectorSubtract( g_entities[bs->enemy].client->ps.origin, bs->origin, vec ); + if ( ( dist = VectorNormalize( vec ) ) < 256 ) { + if ( VectorNormalize2( bs->velocity, dir ) > 20 ) { // we are moving + if ( DotProduct( dir, vec ) > 0 ) { + // abort + return AIFunc_BattleStart( cs ); + } + } + } + // + // if the enemy can see our hide position, abort the manouver + if ( ( cs->thinkFuncChangeTime < level.time - 1000 ) && ( AICast_VisibleFromPos( g_entities[bs->enemy].client->ps.origin, bs->enemy, cs->takeCoverPos, bs->entitynum, qfalse ) ) ) { + // abort + return AIFunc_BattleStart( cs ); + } + // + // if we are tactical and can crouch, do so + if ( !move.numtouch && ( dist > 128 ) && cs->attributes[TACTICAL] > 0.4 && cs->attributes[ATTACK_CROUCH] > 0.1 && + ( cs->bs->attackcrouch_time >= trap_AAS_Time() ) ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + } + // + //attack the enemy if possible + AICast_ProcessAttack( cs ); + // + } else /*if (dist < 48)*/ { + // if we've recently been in a fight, look towards the enemy + if ( cs->bs->enemy >= 0 ) { + AICast_AimAtEnemy( cs ); + } else if ( cs->lastEnemy >= 0 ) { + // if we are not moving, face them + if ( VectorLength( cs->bs->cur_ps.velocity ) < 50 ) { + vec3_t dir; + // + VectorSubtract( cs->vislist[cs->lastEnemy].visible_pos, cs->bs->origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, cs->bs->ideal_viewangles ); + } + } else if ( !cs->crouchHideFlag ) { // no enemy, and no need to crouch, so stop crouching + //if (cs->bs->attackcrouch_time > trap_AAS_Time() + 1) { + // cs->bs->attackcrouch_time = trap_AAS_Time() + 1; + //} + } + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( 0.75 * ammoTable[cs->bs->cur_ps.weapon].maxclip ) ) && cs->bs->cur_ps.ammo[BG_FindAmmoForWeapon( cs->bs->cur_ps.weapon )] ) { + trap_EA_Reload( cs->entityNum ); + } + } + + return NULL; +} + +/* +============ +AIFunc_BattleTakeCoverStart() +============ +*/ +char *AIFunc_BattleTakeCoverStart( cast_state_t *cs ) { +// debugging +#ifdef DEBUG + if ( cs->attributes[AGGRESSION] >= 1.0 ) { + AICast_Printf( 0, "AI taking cover with full aggression!\n" ); + } +#endif + + if ( !AICast_CanMoveWhileFiringWeapon( cs->bs->weaponnum ) ) { + // always run to the cover point + cs->bs->attackcrouch_time = 0; + cs->aiFlags &= ~AIFL_ATTACK_CROUCH; + } else { + // if we arent crouching, start crouching soon after we start retreating + if ( cs->attributes[ATTACK_CROUCH] > 0.1 ) { + cs->aiFlags |= AIFL_ATTACK_CROUCH; + } else { + cs->aiFlags &= ~AIFL_ATTACK_CROUCH; + } + } + + // miscflag1 used to set predicted point as our goal, so we dont keep setting this over and over + cs->aiFlags &= ~AIFL_MISCFLAG1; + + cs->aifunc = AIFunc_BattleTakeCover; + return "AIFunc_BattleTakeCover"; +} + +/* +============ +AIFunc_GrenadeFlush() +============ +*/ +char *AIFunc_GrenadeFlush( cast_state_t *cs ) { + vec3_t dir; + gentity_t *followent, *grenade, *ent; + bot_state_t *bs; + vec3_t destorg, endPos; + qboolean moved = qfalse; + int hitclient; + qboolean attacked = qfalse; + float dist, oldyaw; + int grenadeType; + int *ammo; + + bs = cs->bs; + ent = &g_entities[cs->entityNum]; + // + // if we are throwing the grenade, keep holding down fire + + // (SA) probably read the fweapon from t + if ( cs->grenadeFlushFiring ) { + if ( cs->weaponFireTimes[cs->grenadeFlushFiring] < cs->thinkFuncChangeTime ) { + // have we switched weapons? + if ( cs->bs->weaponnum != cs->grenadeFlushFiring ) { + // damn + hitclient = -1; + } else { + // keep checking it's ok + grenade = weapon_grenadelauncher_fire( ent, WP_GRENADE_LAUNCHER ); + hitclient = AICast_SafeMissileFire( grenade, grenade->nextthink - level.time, cs->bs->enemy, destorg, cs->entityNum, NULL ); + G_FreeEntity( grenade ); + } + if ( hitclient == -1 ) { // doh + //G_Printf("aborted grenade\n"); + cs->castScriptStatus.scriptNoMoveTime = 0; + cs->lockViewAnglesTime = 0; + AICast_ChooseWeapon( cs, qfalse ); + return AIFunc_DefaultStart( cs ); + } + if ( !cs->bs->cur_ps.grenadeTimeLeft ) { + // hold fire button down + trap_EA_Attack( bs->client ); + bs->flags |= BFL_ATTACKED; + } + cs->lockViewAnglesTime = level.time + 500; + return NULL; + } + // the grenade/pineapple has been released! + cs->lockViewAnglesTime = -1; + cs->startGrenadeFlushTime = level.time + 2000 + rand() % 2000; // dont throw one again for a bit + return AIFunc_DefaultStart( cs ); + } + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + // + if ( cs->bs->weaponnum && ( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) { + return AIFunc_IdleStart( cs ); + } + // + if ( bs->enemy < 0 ) { + return AIFunc_IdleStart( cs ); + } + // + // if we have started a script, abort the grenade flush + if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) { + return AIFunc_IdleStart( cs ); + } + // trying for too long? + if ( cs->startGrenadeFlushTime < level.time - 4000 ) { + cs->startGrenadeFlushTime = level.time; + return AIFunc_IdleStart( cs ); + } + // + followent = &g_entities[bs->enemy]; + // + // if the entity is not ready yet + if ( !followent->inuse ) { + // if it's a connecting client, wait + if ( !( ( bs->enemy < MAX_CLIENTS ) + && ( ( followent->client && followent->client->pers.connected == CON_CONNECTING ) + || ( level.time < 3000 ) ) ) ) { // they don't exist anymore, stop attacking + bs->enemy = -1; + } + return AIFunc_IdleStart( cs ); + } + // + // if we can see them, go back to an attack state after some time + if ( AICast_CheckAttack( cs, bs->enemy, qfalse ) + && cs->obstructingTime < level.time ) { // give us some time to throw the grenade, otherwise go back to attack state + if ( ( cs->grenadeFlushEndTime > 0 && cs->grenadeFlushEndTime < level.time ) ) { + //G_Printf("aborting, enemy is attackable\n"); + return AIFunc_BattleStart( cs ); + } else if ( cs->grenadeFlushEndTime < 0 ) { + cs->grenadeFlushEndTime = level.time + 1500; + } + //attack the enemy if possible + AICast_ProcessAttack( cs ); + attacked = qtrue; + } else { + // not visible, go to their previously visible position + if ( !cs->vislist[bs->enemy].visible_timestamp || Distance( bs->origin, cs->vislist[bs->enemy].real_visible_pos ) < 16 ) { + // we're done attacking, go back to default state, which in turn will recall previous state + // + // face the direction they currently are from this position (bit of a hack, but it looks best) + VectorSubtract( g_entities[bs->enemy].client->ps.origin, cs->vislist[bs->enemy].visible_pos, dir ); + vectoangles( dir, cs->bs->ideal_viewangles ); + // + //G_Printf("aborting, reached visible pos\n"); + return AIFunc_DefaultStart( cs ); + } + } + + // is there someone else we can go for instead? + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } else if ( !( cs->bs->flags & BFL_ATTACKED ) && numEnemies > 0 ) { + int i; + for ( i = 0; i < numEnemies; i++ ) { + if ( enemies[i] != cs->bs->enemy && AICast_CheckAttack( cs, enemies[i], qfalse ) ) { + cs->bs->enemy = enemies[i]; + //G_Printf("aborting, other enemy\n"); + return AIFunc_BattleStart( cs ); + } + } + } + + if ( followent->client ) { // go to the last visible position + VectorCopy( cs->vislist[bs->enemy].visible_pos, destorg ); + } else // assume we know where other entities are + { + VectorCopy( followent->s.pos.trBase, destorg ); + } + // + dist = VectorDistance( destorg, cs->bs->origin ); + // + if ( cs->vislist[bs->enemy].lastcheck_timestamp > cs->vislist[bs->enemy].real_visible_timestamp || + dist > 128 ) { + // + // go to them + // + if ( followent->client && followent->health <= 0 ) { + cs->bs->enemy = -1; + //G_Printf("aborting, enemy dead\n"); + return AIFunc_DefaultStart( cs ); + } + // + // ........................................................... + // Do the movement.. + // + // move straight to them if we can + if ( ( cs->leaderNum < 0 ) && + ( cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || g_entities[cs->entityNum].waterlevel > 1 ) ) { + aicast_predictmove_t move; + vec3_t dir; + bot_input_t bi; + usercmd_t ucmd; + trace_t tr; + + // trace will eliminate most unsuccessful paths + trap_Trace( &tr, cs->bs->origin, g_entities[cs->entityNum].r.mins, g_entities[cs->entityNum].r.maxs, followent->r.currentOrigin, cs->entityNum, g_entities[cs->entityNum].clipmask ); + if ( tr.entityNum == followent->s.number ) { + // try walking straight to them + VectorSubtract( followent->r.currentOrigin, cs->bs->origin, dir ); + VectorNormalize( dir ); + if ( !ent->waterlevel ) { + dir[2] = 0; + } + trap_EA_Move( cs->entityNum, dir, 400 ); + trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); + AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles ); + AICast_PredictMovement( cs, 5, 2.0, &move, &ucmd, bs->enemy ); + + if ( move.stopevent == PREDICTSTOP_HITENT ) { // success! + vectoangles( dir, cs->bs->ideal_viewangles ); + cs->bs->ideal_viewangles[2] *= 0.5; + moved = qtrue; + } + } + } + // just go to them + if ( !moved ) { + moveresult = AICast_MoveToPos( cs, destorg, bs->enemy ); + if ( moveresult && moveresult->failure ) { // no path, so go back to idle behaviour + cs->bs->enemy = -1; + //G_Printf("aborting, movement failure\n"); + return AIFunc_DefaultStart( cs ); + } else { + moved = qtrue; + } + } + } + // + // ........................................................... + // if we throw a grenade from here, will it get their last visible position? + // + ammo = cs->bs->cur_ps.ammo; + if ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) { + grenadeType = WP_GRENADE_LAUNCHER; + } else if ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) { + grenadeType = WP_GRENADE_PINEAPPLE; + } else { // not enough ammo, abort + return AIFunc_DefaultStart( cs ); + } + + CalcMuzzlePoints( ent, grenadeType ); + // fire a dummy grenade + grenade = weapon_grenadelauncher_fire( ent, WP_GRENADE_LAUNCHER ); + // check to see what will happen + hitclient = AICast_SafeMissileFire( grenade, grenade->nextthink - level.time, cs->bs->enemy, destorg, cs->entityNum, endPos ); + // kill the grenade + G_FreeEntity( grenade ); + if ( !attacked ) { + cs->bs->weaponnum = grenadeType; // select grenade launcher + } + // set our angles for the next frame + oldyaw = cs->bs->ideal_viewangles[YAW]; + AICast_AimAtEnemy( cs ); + // if we can't see them, keep facing our movement dir, but use the pitch information + if ( !AICast_EntityVisible( cs, cs->bs->enemy, qtrue ) ) { + cs->bs->ideal_viewangles[YAW] = oldyaw; + } + + if ( hitclient == 1 ) { + // it will hit their last visible position + // give us some time to aim and adjust + if ( cs->thinkFuncChangeTime < level.time - 200 ) { + trap_EA_Attack( bs->client ); + bs->flags |= BFL_ATTACKED; + cs->bs->weaponnum = grenadeType; // select grenade launcher + cs->grenadeFlushFiring = cs->bs->weaponnum; + // pause for a bit, so the grenade comes out correctly + cs->lockViewAnglesTime = level.time + 1200; + if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 1200 ) { + cs->castScriptStatus.scriptNoMoveTime = level.time + 1200; + } + return NULL; + } + } else if ( hitclient == -1 ) { + // hit a friendly + cs->startGrenadeFlushTime = level.time + 3000; // don't try again for a while + //G_Printf("aborting, too dangerous\n"); + return AIFunc_DefaultStart( cs ); + } else if ( hitclient == -2 ) { + // went too far, so angle down a bit + cs->bs->ideal_viewangles[PITCH] += 15 * random(); + } else { + if ( cs->thinkFuncChangeTime < level.time - 200 ) { + // if it went reasonably close to them, but safe from us, then fire away + if ( Distance( endPos, cs->bs->origin ) > 100 + Distance( endPos, g_entities[cs->bs->enemy].r.currentOrigin ) ) { + trap_EA_Attack( bs->client ); + bs->flags |= BFL_ATTACKED; + cs->bs->weaponnum = grenadeType; // select grenade launcher + cs->grenadeFlushFiring = cs->bs->weaponnum; + // pause for a bit, so the grenade comes out correctly + cs->lockViewAnglesTime = level.time + 1200; + if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 1200 ) { + cs->castScriptStatus.scriptNoMoveTime = level.time + 1200; + } + return NULL; + } + } + cs->bs->ideal_viewangles[PITCH] += -10 * random(); + } + // + return NULL; +} + +/* +============ +AIFunc_GrenadeFlushStart() +============ +*/ +char *AIFunc_GrenadeFlushStart( cast_state_t *cs ) { + lastGrenadeFlush = level.time; // + rand()%2000; + cs->startGrenadeFlushTime = level.time; + cs->grenadeFlushEndTime = -1; + cs->lockViewAnglesTime = 0; + cs->combatGoalTime = 0; + cs->grenadeFlushFiring = qfalse; + // don't wait too long before taking cover, if we just aborted one + if ( cs->takeCoverTime > level.time + 1000 ) { + cs->takeCoverTime = level.time + 500 + rand() % 500; + } + // + cs->aifunc = AIFunc_GrenadeFlush; + return "AIFunc_GrenadeFlush"; +} + +/* +============ +AIFunc_BattleMG42() +============ +*/ +char *AIFunc_BattleMG42( cast_state_t *cs ) { + bot_state_t *bs; + gentity_t *mg42, *ent; + vec3_t angles, vec, bestangles; + + mg42 = &g_entities[cs->mountedEntity]; + ent = &g_entities[cs->entityNum]; + bs = cs->bs; + + // have we dismounted the MG42? + if ( !g_entities[cs->entityNum].active ) { + return AIFunc_DefaultStart( cs ); + } + + // if enemy is dead, stop attacking them + if ( g_entities[bs->enemy].health <= 0 ) { + bs->enemy = -1; + } + + //if no enemy, or our current enemy isn't attackable, look for a better enemy + if ( bs->enemy >= 0 ) { + if ( cs->vislist[bs->enemy].real_visible_timestamp && cs->vislist[bs->enemy].real_visible_timestamp > ( level.time - 5000 ) ) { + VectorSubtract( cs->vislist[bs->enemy].real_visible_pos, mg42->r.currentOrigin, vec ); + } else if ( cs->vislist[bs->enemy].visible_timestamp && cs->vislist[bs->enemy].visible_timestamp > ( level.time - 5000 ) ) { + VectorSubtract( cs->vislist[bs->enemy].visible_pos, mg42->r.currentOrigin, vec ); + } else { // just aim straight forward + AngleVectors( mg42->s.angles, vec, NULL, NULL ); + } + + VectorNormalize( vec ); + vectoangles( vec, angles ); + angles[PITCH] = AngleNormalize180( angles[PITCH] ); + VectorCopy( angles, bestangles ); + } + + if ( bs->enemy < 0 || + !AICast_CheckAttack( cs, bs->enemy, qfalse ) || + ( fabs( AngleDifference( angles[YAW], mg42->s.angles[YAW] ) ) > mg42->harc ) || + ( angles[PITCH] < 0 && angles[PITCH] + 5 < -mg42->varc ) || + ( angles[PITCH] > 0 && angles[PITCH] - 5 > 5.0 ) ) { + qboolean shouldAttack; + + // look for a better enemy + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } + if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } + shouldAttack = qfalse; + if ( numEnemies > 0 ) { + int i; + // default to the first known enemy, overwrite if we find a clearer shot + cs->bs->enemy = enemies[0]; + // + for ( i = 0; i < numEnemies; i++ ) { + if ( !cs->vislist[enemies[i]].real_visible_timestamp || + ( cs->vislist[enemies[i]].real_visible_timestamp < level.time - 2000 ) ) { + // we can't see them, ignore them + continue; + } + + // if they are in the range + if ( cs->vislist[enemies[i]].real_visible_timestamp > ( level.time - 5000 ) ) { + VectorSubtract( cs->vislist[enemies[i]].real_visible_pos, mg42->r.currentOrigin, vec ); + } else { + VectorSubtract( cs->vislist[enemies[i]].visible_pos, mg42->r.currentOrigin, vec ); + } + VectorNormalize( vec ); + vectoangles( vec, angles ); + angles[PITCH] = AngleNormalize180( angles[PITCH] ); + if ( !( ( fabs( AngleDifference( angles[YAW], mg42->s.angles[YAW] ) ) > mg42->harc ) || + ( angles[YAW] < 0 && angles[YAW] + 2 < -mg42->varc ) || + ( angles[YAW] > 0 && angles[YAW] - 2 > 5.0 ) ) ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) ) { + VectorCopy( angles, bestangles ); + cs->bs->enemy = enemies[i]; + shouldAttack = qtrue; + break; + } else if ( AICast_CheckAttack( cs, enemies[i], qtrue ) ) { + // keep firing at anything behind solids, in case they find a position where they can shoot us, but our checkattack() doesn't find a clear shot + VectorCopy( angles, bestangles ); + cs->bs->enemy = enemies[i]; + shouldAttack = qtrue; + } + } + } + } + + if ( !shouldAttack ) { + // keep firing at anything behind solids, in case they find a position where they can shoot us, but our checkattack() doesn't find a clear shot + if ( bs->enemy < 0 || !AICast_CheckAttack( cs, bs->enemy, qtrue ) || + ( !cs->vislist[bs->enemy].real_visible_timestamp || + ( cs->vislist[bs->enemy].real_visible_timestamp < level.time - 2000 ) ) ) { + // face straight forward + cs->bs->ideal_viewangles[PITCH] = 0; + return NULL; + } + } + } + // + // hold down fire, and track them + // + // TODO: play a special "holding mg42" torso animation + // + VectorCopy( angles, bs->ideal_viewangles ); + if ( cs->triggerReleaseTime < level.time ) { + trap_EA_Attack( bs->client ); + bs->flags |= BFL_ATTACKED; + + if ( cs->triggerReleaseTime < level.time - 3000 ) { + cs->triggerReleaseTime = level.time + 700 + rand() % 700; + } + } + // + return NULL; +} + +/* +============ +AIFunc_BattleMG42Start() +============ +*/ +char *AIFunc_BattleMG42Start( cast_state_t *cs ) { + cs->aifunc = AIFunc_BattleMG42; + return "AIFunc_BattleMG42"; +} + +/* +============ +AIFunc_InspectBody() + + go up to the enemy, and have a good look at them, randomly taunt them +============ +*/ +char *AIFunc_InspectBody( cast_state_t *cs ) { + bot_state_t *bs; + vec3_t destorg, enemyOrg; + // + // stop crouching + cs->bs->attackcrouch_time = 0; + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + // + // if running a script + if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) { + cs->bs->enemy = -1; + return AIFunc_IdleStart( cs ); + } + // + bs = cs->bs; + // + if ( bs->enemy < 0 ) { + return AIFunc_IdleStart( cs ); + } + // + // look for things we should attack + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } else if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } else if ( numEnemies == -3 ) { // bullet impact + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectBulletImpactStart( cs ); + } + } else if ( numEnemies == -4 ) { // audible event + if ( cs->aiState < AISTATE_COMBAT ) { + return AIFunc_InspectAudibleEventStart( cs, cs->audibleEventEnt ); + } + } else if ( numEnemies > 0 ) { + cs->bs->enemy = enemies[0]; // just attack the first one + return AIFunc_BattleStart( cs ); + } + // + VectorCopy( cs->vislist[bs->enemy].visible_pos, enemyOrg ); + if ( ( cs->inspectBodyTime < 0 ) && ( Distance( cs->bs->origin, enemyOrg ) > 64 ) ) { + // if they were gibbed, don't go all the way + if ( g_entities[cs->bs->enemy].health < GIB_HEALTH && ( Distance( cs->bs->origin, enemyOrg ) < 180 ) ) { + cs->inspectBodyTime = level.time + 1000 + rand() % 1000; + trap_EA_Gesture( cs->entityNum ); + G_AddEvent( &g_entities[cs->entityNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersSoundScript ) ); + } + // walk to them + if ( cs->movestate != MS_CROUCH ) { + cs->movestate = MS_WALK; + } + cs->movestateType = MSTYPE_TEMPORARY; + // + moveresult = AICast_MoveToPos( cs, enemyOrg, -1 ); + //if the movement failed + if ( !moveresult || moveresult->failure || moveresult->blocked ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + // couldn't get there, so stop trying to get there + cs->bs->enemy = -1; + return AIFunc_IdleStart( cs ); + } + if ( Distance( cs->bs->origin, enemyOrg ) < 180 ) { + // look down at them + VectorSubtract( enemyOrg, cs->bs->origin, destorg ); + destorg[2] -= 20; + VectorNormalize( destorg ); + vectoangles( destorg, cs->bs->ideal_viewangles ); + } + } else if ( cs->inspectBodyTime < 0 ) { + // just reached them + cs->inspectBodyTime = level.time + 1000 + rand() % 1000; + trap_EA_Gesture( cs->entityNum ); + G_AddEvent( &g_entities[cs->entityNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersSoundScript ) ); + } else if ( cs->inspectBodyTime < level.time ) { + vec3_t vec; + VectorSubtract( cs->startOrigin, cs->bs->origin, vec ); + vec[2] = 0; + // ready to go back to start position + if ( VectorLength( vec ) > 64 ) { + if ( cs->movestate != MS_CROUCH ) { + cs->movestate = MS_WALK; + } + cs->movestateType = MSTYPE_TEMPORARY; + moveresult = AICast_MoveToPos( cs, cs->startOrigin, -1 ); + //if the movement failed + if ( !moveresult || moveresult->failure || moveresult->blocked ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + // couldn't get there, so stop trying to get there + cs->bs->enemy = -1; + return AIFunc_IdleStart( cs ); + } + // stay looking at them for a bit after starting to walk back + if ( cs->inspectBodyTime + 750 > level.time ) { + // look down at them + VectorSubtract( enemyOrg, cs->bs->origin, destorg ); + destorg[2] -= 20; + VectorNormalize( destorg ); + vectoangles( destorg, cs->bs->ideal_viewangles ); + } + } else { + cs->attackSNDtime = level.time; + cs->bs->enemy = -1; + return AIFunc_IdleStart( cs ); + } + } + // + return NULL; +} + +/* +============ +AIFunc_InspectBodyStart() +============ +*/ +char *AIFunc_InspectBodyStart( cast_state_t *cs ) { + static int lastInspect; + // + // if an inspection was already started not long ago, forget it + if ( lastInspect <= level.time && lastInspect > level.time - 1000 ) { + cs->inspectBodyTime = 1; // go back to start position + } else { + lastInspect = level.time; + cs->inspectBodyTime = -1; + } + cs->aifunc = AIFunc_InspectBody; + return "AIFunc_InspectBody"; +} + +/* +============ +AIFunc_GrenadeKick() +============ +*/ +char *AIFunc_GrenadeKick( cast_state_t *cs ) { + bot_state_t *bs; + vec3_t destorg, vec; + float dist, speed; + int enemies[MAX_CLIENTS], numEnemies, i; + qboolean shouldAttack; + int *ammo; + gentity_t *danger; + gentity_t *ent; + vec3_t end; + trace_t tr; + vec3_t dir; + int weapon; + + // !!! NOTE: the only way control should pass out of here, is by calling AIFunc_DefaultStart() + + ent = &g_entities[cs->entityNum]; + danger = &g_entities[cs->dangerEntity]; + + weapon = cs->grenadeKickWeapon; + + // just to be sure, give us the grenade launcher + //ent->client->ps.weapons |= (1 << weapon); + + // + // NOTE: ignore all danger, since we are trying to solve the situation anyway + // + // we need to move towards it + bs = cs->bs; + // + // are we throwing it back? + if ( cs->grenadeFlushFiring ) { + // wait until the animation is done + if ( !ent->client->ps.legsTimer ) { + return AIFunc_DefaultStart( cs ); + } + // wait till its finished + return NULL; + /* + cs->bs->weaponnum = weapon; // select grenade launcher + // + if (cs->weaponFireTimes[weapon] < cs->thinkFuncChangeTime) { + if (!cs->bs->cur_ps.grenadeTimeLeft) { + // hold fire button down + AICast_AimAtEnemy( cs ); + trap_EA_Attack(bs->client); + bs->flags |= BFL_ATTACKED; + } + // + return NULL; + } + // the grenade has been released! + // + // modify the explode time + g_entities[ent->grenadeFired].nextthink = ent->grenadeExplodeTime; + if (g_entities[ent->grenadeFired].nextthink < level.time + 200) { // cut them some slack + g_entities[ent->grenadeFired].nextthink = level.time + 200 + rand()%500; + } + // make sure no-one tries to throw this back again (hot potatoe syndrome) + g_entities[ent->grenadeFired].flags |= FL_AI_GRENADE_KICK; + // + cs->grenadeFlushFiring = qfalse; + cs->lockViewAnglesTime = -1; + cs->startGrenadeFlushTime = level.time + 2000 + rand()%2000; // dont throw one again for a bit + level.lastGrenadeKick = level.time; + return AIFunc_DefaultStart( cs ); + */ + } + // +/* + // have we caught the grenade? + if (!(ent->flags & FL_AI_GRENADE_KICK)) { + // select grenades + cs->bs->weaponnum = weapon; // select grenade launcher + AICast_AimAtEnemy( cs ); + // hold fire + trap_EA_Attack(bs->client); + bs->flags |= BFL_ATTACKED; + cs->grenadeFlushFiring = qtrue; + // + return NULL; + } +*/ + // + // is it about to explode in our face? + if ( level.time > danger->nextthink - (int)( 2.0 * VectorDistance( cs->bs->origin, danger->r.currentOrigin ) ) ) { + // abort!! + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = danger->nextthink + 1000; + cs->bs->attackcrouch_time = 0; + level.lastGrenadeKick = level.time; + return AIFunc_AvoidDangerStart( cs ); + } + // + /* + // are we close enough to start crouching? + if (danger->s.pos.trDelta[2] < 40 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 48 && (danger->r.currentOrigin[2] < cs->bs->origin[2]) && + VectorLength(danger->s.pos.trDelta) < 40) { + // crouch to pick it up + cs->bs->attackcrouch_time = trap_AAS_Time() + 0.3; + } + */ + cs->bs->attackcrouch_time = 0; // animation is played from standing start + // + // are we close enough to pick it up? + if ( /*cs->grenadeGrabFlag <= 0 || */ + ( danger->s.pos.trDelta[2] < 20 && VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 48 && ( danger->r.currentOrigin[2] < cs->bs->origin[2] ) && + VectorLength( danger->s.pos.trDelta ) < 50 ) ) { + // + // we have a choice here, either pick up and return, or just kick it +// if ((cs->grenadeGrabFlag == -1) || (cs->grenadeGrabFlag == qtrue && level.time > danger->nextthink - 2000)) { // kick + + // play the kick anim + if ( cs->grenadeGrabFlag == qtrue ) { + AICast_AimAtEnemy( cs ); + // play the kick anim + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_KICKGRENADE, qfalse, qtrue ); + cs->grenadeGrabFlag = -1; + // stop the grenade from moving away + danger->s.pos.trDelta[0] = 0; + danger->s.pos.trDelta[1] = 0; + if ( danger->s.pos.trDelta[2] > 0 ) { + danger->s.pos.trDelta[2] = 0; + } + } else if ( ent->client->ps.legsTimer < 800 ) { + // send the grenade on its way + cs->grenadeFlushFiring = qtrue; + AngleVectors( cs->bs->viewangles, dir, NULL, NULL ); + dir[2] = 0.4; + VectorNormalize( dir ); + speed = 400; + if ( cs->bs->enemy >= 0 ) { + speed = 1.5 * VectorDistance( danger->r.currentOrigin, g_entities[cs->bs->enemy].r.currentOrigin ); + if ( speed > 650 ) { + speed = 650; + } + } + VectorScale( dir, speed, danger->s.pos.trDelta ); + danger->r.ownerNum = ent->s.number; // we are now the owner, let it pass through us + danger->s.pos.trTime = level.time - 50; // move a bit on the very first frame + VectorCopy( danger->r.currentOrigin, danger->s.pos.trBase ); + danger->s.pos.trType = TR_GRAVITY; + SnapVector( danger->s.pos.trDelta ); // save net bandwidth + } +/* + } else { // throw + + if (cs->grenadeGrabFlag == qtrue) { + AICast_AimAtEnemy( cs ); + // play the pickup anim + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_PICKUPGRENADE, qfalse, qtrue ); + cs->grenadeGrabFlag = qfalse; + // stop the grenade from moving away + danger->s.pos.trDelta[0] = 0; + danger->s.pos.trDelta[1] = 0; + if (danger->s.pos.trDelta[2] > 0) { + danger->s.pos.trDelta[2] = 0; + } + } else if (ent->client->ps.legsTimer < 400) { + // send the grenade on its way + cs->grenadeFlushFiring = qtrue; + AngleVectors( cs->bs->viewangles, dir, NULL, NULL ); + dir[2] = 0.4; + VectorNormalize( dir ); + speed = 500; + if (cs->bs->enemy >= 0) { + speed = 2*VectorDistance(danger->r.currentOrigin, g_entities[cs->bs->enemy].r.currentOrigin); + if (speed > 650) + speed = 650; + } + VectorScale( dir, speed, danger->s.pos.trDelta ); + trap_LinkEntity( danger ); + } else if (ent->client->ps.legsTimer < 800) { + // stop showing the grenade + trap_UnlinkEntity( danger ); + } + } +*/ + // + return NULL; + } + // + cs->grenadeGrabFlag = qtrue; // we must play the anim before we can grab it + // + // is the danger gone? + if ( level.time > cs->dangerEntityValidTime || !danger->inuse ) { + return AIFunc_DefaultStart( cs ); + } + // + // update the predicted position of the grenade + if ( G_PredictMissile( danger, danger->nextthink - level.time, cs->takeCoverPos, qfalse ) ) { + // make sure it's a valid position, and drop it down to the ground + cs->takeCoverPos[2] += -ent->r.mins[2] + 8; + VectorCopy( cs->takeCoverPos, end ); + end[2] -= 80; + trap_Trace( &tr, cs->takeCoverPos, ent->r.mins, ent->r.maxs, end, cs->entityNum, MASK_SOLID ); + if ( tr.startsolid ) { // not a valid position, abort + level.lastGrenadeKick = level.time; + return AIFunc_DefaultStart( cs ); + } + VectorCopy( tr.endpos, cs->takeCoverPos ); + } else { // prediction failed, so use current position + VectorCopy( danger->r.currentOrigin, cs->takeCoverPos ); + cs->takeCoverPos[2] += 16; // lift it off the floor + } + + VectorCopy( cs->takeCoverPos, destorg ); + VectorSubtract( destorg, cs->bs->origin, vec ); + //vec[2] *= 0.2; + dist = VectorLength( vec ); + // + // look for things we should attack + // if we are out of ammo, we shouldn't bother trying to attack + ammo = cs->bs->cur_ps.ammo; + shouldAttack = qfalse; + numEnemies = 0; + if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + numEnemies = AICast_ScanForEnemies( cs, enemies ); + if ( numEnemies == -1 ) { // query mode + return NULL; + } + if ( numEnemies == -2 ) { // inspection may be required + char *retval; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( retval = AIFunc_InspectFriendlyStart( cs, enemies[0] ) ) ) { + return retval; + } + } + if ( numEnemies > 0 ) { + // default to the first known enemy, overwrite if we find a clearer shot + cs->bs->enemy = enemies[0]; + // + for ( i = 0; i < numEnemies; i++ ) { + if ( AICast_CheckAttack( cs, enemies[i], qfalse ) || AICast_CheckAttack( AICast_GetCastState( enemies[i] ), cs->entityNum, qfalse ) ) { + cs->bs->enemy = enemies[i]; + shouldAttack = qtrue; + break; + } else if ( cs->bs->enemy < 0 ) { + cs->lastEnemy = enemies[i]; + } + } + } + } + // + // are we close enough to the goal? + if ( dist > 12 ) { // not close enough + // + moveresult = AICast_MoveToPos( cs, destorg, -1 ); + if ( moveresult ) { + //if the movement failed + if ( moveresult->failure ) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach( bs->ms ); + // couldn't get there, so stop trying to get there + level.lastGrenadeKick = level.time; + return AIFunc_DefaultStart( cs ); + } + // + if ( moveresult->blocked ) { // abort if we get blocked at any point + level.lastGrenadeKick = level.time; + return AIFunc_DefaultStart( cs ); + } + } + // we should slow down on approaching it + cs->speedScale = AICast_SpeedScaleForDistance( cs, dist, 0 ); + } + // + // if we are close, put our weapon away and get ready to catch it + if ( VectorDistance( danger->r.currentOrigin, cs->bs->origin ) < 128 ) { + // put weapon away, select grenades + // FIXME: does this fail if we don't have any grenades? + cs->bs->weaponnum = weapon; // select grenade launcher + shouldAttack = qfalse; // don't attack until we've caught it + } + + // if we should be attacking something on our way + if ( shouldAttack ) { + vec3_t vec, dir; + + //attack the enemy if possible + AICast_ProcessAttack( cs ); + // + // if they are close, and we're heading for them, we should abort this manouver + VectorSubtract( g_entities[bs->enemy].client->ps.origin, bs->origin, vec ); + if ( VectorNormalize( vec ) < 64 ) { + if ( VectorNormalize2( bs->velocity, dir ) > 20 ) { // we are moving + if ( DotProduct( dir, vec ) > 0 ) { + // abort + level.lastGrenadeKick = level.time; + return AIFunc_DefaultStart( cs ); + } + } + } + } else { + // face the direction that the grenade is coming + VectorSubtract( danger->r.currentOrigin, cs->bs->origin, dir ); + dir[2] = 0; + VectorNormalize( dir ); + vectoangles( dir, cs->bs->ideal_viewangles ); + } + + return NULL; +} + +/* +============= +AIFunc_GrenadeKickStart() +============= +*/ +char *AIFunc_GrenadeKickStart( cast_state_t *cs ) { + gentity_t *danger; + gentity_t *ent; + //gentity_t *trav; + //int numFriends, i; + + //G_Printf( "Excuse me, you dropped something\n" ); + + ent = &g_entities[cs->entityNum]; + danger = &g_entities[cs->dangerEntity]; + // should we dive onto the grenade? + /* + if (danger->s.pos.trDelta[2] < 30) { + // count the number of friends near us + numFriends = 0; + for (i=0, trav=g_entities; iinuse) + continue; + if (trav->aiInactive) + continue; + if (trav->health <= 0) + continue; + if (!AICast_SameTeam( cs, i )) + continue; + if (VectorDistance( cs->takeCoverPos, trav->r.currentOrigin ) > 200) + continue; + numFriends++; + } + // if there are enough friends around, and we have a clear path to the position, sacrifice ourself! + if (numFriends > 2) { + trace_t tr; + trap_Trace( &tr, cs->bs->origin, ent->r.mins, ent->r.maxs, cs->takeCoverPos, cs->entityNum, MASK_SOLID ); + if (tr.fraction == 1.0 && !tr.startsolid) { + return AIFunc_GrenadeDiveStart( cs ); + } + } + } + */ + // + // we have decided to kick or throw the grenade away + cs->grenadeKickWeapon = danger->s.weapon; + cs->grenadeFlushFiring = qfalse; + cs->aifunc = AIFunc_GrenadeKick; + return "AIFunc_GrenadeKick"; +} + +/* +============ +AIFunc_Battle() +============ +*/ +char *AIFunc_Battle( cast_state_t *cs ) { + bot_moveresult_t moveresult; + int tfl; + bot_state_t *bs; + gentity_t *ent, *enemy; + + ent = &g_entities[cs->entityNum]; + enemy = &g_entities[cs->bs->enemy]; + + // if we are not in combat mode, then go there now! + if ( cs->aiState < AISTATE_COMBAT ) { + AICast_StateChange( cs, AISTATE_COMBAT ); // just go straight to combat mode + } + // + // do we need to avoid a danger? + if ( cs->dangerEntityValidTime >= level.time ) { + if ( !AICast_GetTakeCoverPos( cs, cs->dangerEntity, cs->dangerEntityPos, cs->takeCoverPos ) ) { + // shit?? + } + // go to a position that cannot be seen from the dangerPos + cs->takeCoverTime = cs->dangerEntityValidTime + 1000; + cs->bs->attackcrouch_time = 0; + return AIFunc_AvoidDangerStart( cs ); + } + // + // are we waiting for a door? + if ( cs->doorMarkerTime > level.time - 100 ) { + return AIFunc_DoorMarkerStart( cs, cs->doorMarkerDoor, cs->doorMarkerNum ); + } + // + // do we need to go to our leader? + if ( cs->leaderNum >= 0 && Distance( cs->bs->origin, g_entities[cs->leaderNum].r.currentOrigin ) > MAX_LEADER_DIST ) { + return AIFunc_ChaseGoalStart( cs, cs->leaderNum, AICAST_LEADERDIST_MAX, qtrue ); + } + bs = cs->bs; + //if no enemy + if ( bs->enemy < 0 ) { + // go back to whatever our default action is + return AIFunc_DefaultStart( cs ); + } + // + if ( enemy->health <= 0 ) { + // go back to whatever our default action is + if ( g_entities[cs->entityNum].aiTeam == AITEAM_NAZI ) { + return AIFunc_InspectBodyStart( cs ); + } else { + return AIFunc_DefaultStart( cs ); + } + } + // + // if we are crouching, don't stay down for too long after we finish fighting + if ( cs->aiFlags & AIFL_ATTACK_CROUCH ) { + bs->attackcrouch_time = trap_AAS_Time() + 1; + } else { + bs->attackcrouch_time = 0; // only set it if we need it + } + // + // if the enemy is no longer visible + if ( ( cs->bs->cur_ps.weaponTime < 100 ) // if reloading, don't chase until ready + && ( cs->castScriptStatus.scriptNoMoveTime < level.time ) + && ( !AICast_EntityVisible( cs, bs->enemy, qtrue ) || !AICast_CheckAttack( cs, bs->enemy, qfalse ) ) ) { + + // if we are heading for a combatGoal, give us some time to get there + if ( cs->combatGoalTime > level.time ) { + if ( cs->combatGoalTime > level.time + 3000 ) { + cs->combatGoalTime = level.time + 2000 + rand() % 1000; + cs->combatSpotDelayTime = level.time + 4000 + rand() % 3000; + } + } else + if ( cs->leaderNum >= 0 ) { + // chase them, nothing else to do + return AIFunc_BattleChaseStart( cs ); + } else + // if we weren't moving, it is likely they have dodged back behind something, ready to duck out and take another + // shot. so, we could fool them by hiding from the position we last saw them from, in the hope that when they + // return to fire at us, we won't be in their sight. + if ( cs->attributes[TACTICAL] > 0.3 + && cs->attributes[AGGRESSION] < 1.0 + && cs->attributes[AGGRESSION] < ( random() + 0.5 * cs->attributes[TACTICAL] ) + && ( cs->takeCoverTime < level.time ) + && AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) { + // start taking cover + cs->takeCoverTime = level.time + 2000 + rand() % 4000; // only move a little bit + //cs->bs->attackcrouch_time = 0; // get out of here real quick + return AIFunc_BattleTakeCoverStart( cs ); + } else + // if we haven't thrown a grenade in a bit, go into "grenade flush mode" + if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) && + ( cs->castScriptStatus.castScriptEventIndex < 0 ) && + ( ( ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) && + ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) && + ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) ) || + ( ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_PINEAPPLE ) ) && + ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_PINEAPPLE ) ) && + ( cs->weaponFireTimes[WP_GRENADE_PINEAPPLE] < level.time - (int)( aicast_skillscale * 3000 ) ) ) ) && + !( cs->bs->weaponnum && ( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) > 100 ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 1200 ) && + ( AICast_WantsToChase( cs ) ) ) { + // try and flush them out with a grenade + //G_Printf("get outta there..\n"); + return AIFunc_GrenadeFlushStart( cs ); + } else + // not visible, should we chase them? + if ( AICast_WantsToChase( cs ) ) { + // chase them + return AIFunc_BattleChaseStart( cs ); + } else + // Take Cover? + if ( AICast_WantsToTakeCover( cs, qfalse ) + && ( cs->takeCoverTime < level.time ) + && AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].real_visible_pos, cs->takeCoverPos ) ) { + // go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time + cs->takeCoverTime = level.time + 4000 + rand() % 2000; + //cs->bs->attackcrouch_time = 0; + return AIFunc_BattleTakeCoverStart( cs ); + } else + { + // chase them, nothing else to do + return AIFunc_BattleChaseStart( cs ); + } + } + // if we are obstructing someone else, move out the way + if ( cs->obstructingTime > level.time ) { + // setup a combatgoal in the obstructionYaw direction + //cs->combatGoalTime = level.time + 10; + //VectorCopy( cs->obstructingPos, cs->combatGoalOrigin ); + AICast_MoveToPos( cs, cs->obstructingPos, -1 ); + // if not crouching, walk instead of running + cs->speedScale = cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED]; + } + // if the enemy is really close, avoid them + else if ( ( cs->obstructingTime < ( level.time - 500 + rand() % 300 ) ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 100 ) ) { + if ( AICast_GetAvoid( cs, NULL, cs->obstructingPos, qtrue, cs->bs->enemy ) ) { + cs->obstructingTime = level.time + 500; + } else { + cs->obstructingTime = level.time - 1; // wait a bit before trying again + } + } + // + // setup for the fight + // + tfl = cs->travelflags; + //if in lava or slime the bot should be able to get out + if ( BotInLava( bs ) ) { + tfl |= TFL_LAVA; + } + if ( BotInSlime( bs ) ) { + tfl |= TFL_SLIME; + } + // + /* + moveresult = AICast_CombatMove(cs, tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + // reset the combatgoal + cs->combatGoalTime = 0; + } else if (cs->combatGoalTime > level.time && VectorLength(cs->bs->cur_ps.velocity)) { // crouch if moving? + if (cs->attributes[ATTACK_CROUCH] > 0.1) { + AICast_RequestCrouchAttack( cs, cs->bs->origin, 0.5 ); + } + } + */ + // + AICast_Blocked( cs, &moveresult, qfalse, NULL ); + // + // Retreat? + if ( AICast_WantToRetreat( cs ) ) { + if ( AICast_GetTakeCoverPos( cs, cs->bs->enemy, cs->vislist[cs->bs->enemy].visible_pos, cs->takeCoverPos ) ) { + // go to a position that cannot be seen from the last place we saw the enemy, and wait there for some time + cs->takeCoverTime = level.time + 2000 + rand() % 3000; + return AIFunc_BattleTakeCoverStart( cs ); + } + } + // + // Lob a Grenade? + // if we haven't thrown a grenade in a bit, go into "grenade flush mode" + if ( ( lastGrenadeFlush > level.time || lastGrenadeFlush < level.time - 7000 ) && + ( cs->castScriptStatus.castScriptEventIndex < 0 ) && + ( cs->startGrenadeFlushTime < level.time - 3000 ) && + ( COM_BitCheck( cs->bs->cur_ps.weapons, WP_GRENADE_LAUNCHER ) ) && + ( AICast_GotEnoughAmmoForWeapon( cs, WP_GRENADE_LAUNCHER ) ) && + ( cs->weaponFireTimes[WP_GRENADE_LAUNCHER] < level.time - (int)( aicast_skillscale * 3000 ) ) && + ( ( cs->bs->weaponnum == WP_GRENADE_LAUNCHER ) || !( cs->castScriptStatus.scriptFlags & SFL_NOCHANGEWEAPON ) ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) > 100 ) && + ( Distance( cs->bs->origin, cs->vislist[cs->bs->enemy].real_visible_pos ) < 2000 ) ) { + // try and flush them out with a grenade + //G_Printf("pineapple?\n"); + return AIFunc_GrenadeFlushStart( cs ); + } + // + // Dodge enemy aim? + if ( ( cs->attributes[AGGRESSION] < 1.0 ) && + ( ent->client->ps.weapon ) && + ( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD ) && + ( !cs->lastRollMove || cs->lastRollMove < level.time - 4000 ) && + ( cs->attributes[TACTICAL] > 0.5 ) && ( cs->aiFlags & AIFL_ROLL_ANIM ) && + ( VectorLength( cs->bs->cur_ps.velocity ) < 1 ) ) { + vec3_t aim, enemyVec, right; + // are they aiming at us? + AngleVectors( enemy->client->ps.viewangles, aim, right, NULL ); + VectorSubtract( cs->bs->origin, enemy->r.currentOrigin, enemyVec ); + VectorNormalize( enemyVec ); + // if they are looking at us, we should avoid them + if ( DotProduct( aim, enemyVec ) > 0.97 ) { + aicast_predictmove_t move; + vec3_t dir; + bot_input_t bi, bi_back; + usercmd_t ucmd; + float simTime = 0.8; + + cs->lastRollMove = level.time; + + trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi_back ); + trap_EA_ResetInput( cs->entityNum, NULL ); + if ( level.time % 200 < 100 ) { + VectorNegate( right, dir ); + } else { VectorCopy( right, dir );} + trap_EA_Move( cs->entityNum, dir, 400 ); + trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); + AICast_InputToUserCommand( cs, &bi, &ucmd, bs->cur_ps.delta_angles ); + AICast_PredictMovement( cs, 4, simTime / 4, &move, &ucmd, bs->enemy ); + + trap_EA_ResetInput( cs->entityNum, &bi_back ); + + if ( move.groundEntityNum == ENTITYNUM_WORLD && + VectorDistance( move.endpos, cs->bs->origin ) > simTime * cs->attributes[RUNNING_SPEED] * 0.8 ) { + // good enough + if ( AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, move.endpos, cs->bs->cur_ps.viewheight == cs->bs->cur_ps.crouchViewHeight, qfalse ) ) { + return AIFunc_BattleRollStart( cs, dir ); + } + } + } + } + // + // reload? + if ( ( cs->bs->cur_ps.ammoclip[BG_FindClipForWeapon( cs->bs->cur_ps.weapon )] < (int)( ammoTable[cs->bs->cur_ps.weapon].uses ) ) ) { + if ( AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + trap_EA_Reload( cs->entityNum ); + } else { // no ammo, switch? + AICast_ChooseWeapon( cs, qfalse ); + if ( !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + // no ammo, get out of here + return AIFunc_DefaultStart( cs ); + } + } + } else { + //attack the enemy if possible + AICast_ProcessAttack( cs ); + } + // + return NULL; +} + +/* +============ +AIFunc_BattleStart() +============ +*/ +char *AIFunc_BattleStart( cast_state_t *cs ) { + char *rval; + int lastweap; + // make sure we don't avoid any areas when we start again + trap_BotInitAvoidReach( cs->bs->ms ); + // wait some time before taking cover again + cs->takeCoverTime = level.time + 300 + rand() % ( 2000 + (int)( 2000.0 * cs->attributes[AGGRESSION] ) ); + // wait some time before going to a combat spot + cs->combatSpotDelayTime = level.time + 1500 + rand() % 2500; + // + // start a crouch attack? + if ( ( random() * 3.0 + 1.0 < cs->attributes[ATTACK_CROUCH] ) + && AICast_RequestCrouchAttack( cs, cs->bs->origin, 0.0 ) ) { + cs->aiFlags |= AIFL_ATTACK_CROUCH; + } else { + cs->bs->attackcrouch_time = 0; + cs->aiFlags &= ~AIFL_ATTACK_CROUCH; + } + // + cs->lastEnemy = cs->bs->enemy; + cs->startAttackCount++; + cs->crouchHideFlag = qfalse; + // + // get out of talking state + cs->aiFlags &= ~AIFL_TALKING; + // + //update the attack inventory values + AICast_UpdateBattleInventory( cs, cs->bs->enemy ); + // + // if we have a special attack, call the correct AI routine +recheck: + rval = NULL; + // ignore special attacks until we are facing our enemy + if ( fabs( AngleDifference( cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW] ) ) < 10 ) { + // select a weapon + AICast_ChooseWeapon( cs, qtrue ); + // + if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK1 ) && cs->aifuncAttack1 ) { + if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) { + rval = cs->aifuncAttack1( cs ); + } else { + rval = AIFunc_BattleChaseStart( cs ); + } + } else if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK2 ) && cs->aifuncAttack2 ) { + if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) { + rval = cs->aifuncAttack2( cs ); + } else { + rval = AIFunc_BattleChaseStart( cs ); + } + } else if ( ( cs->bs->weaponnum == WP_MONSTER_ATTACK3 ) && cs->aifuncAttack3 ) { + if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ) { + rval = cs->aifuncAttack3( cs ); + } else { + rval = AIFunc_BattleChaseStart( cs ); + } + } + // + if ( !rval && cs->bs->weaponnum >= WP_MONSTER_ATTACK1 && cs->bs->weaponnum <= WP_MONSTER_ATTACK3 ) { + // don't use this weapon again for a while + cs->weaponFireTimes[cs->bs->weaponnum] = level.time; + // select a different weapon + lastweap = cs->bs->weaponnum; + AICast_ChooseWeapon( cs, qfalse ); // qfalse so we don't choose a special weapon + if ( cs->bs->weaponnum == lastweap ) { + return NULL; + } + // try again + goto recheck; + } + } else { // normal weapons + // select a weapon + AICast_ChooseWeapon( cs, qfalse ); + rval = NULL; + } + // + if ( !rval ) { // use the generic battle routine for all "normal" weapons + if ( cs->bs->weaponnum >= WP_MONSTER_ATTACK1 && cs->bs->weaponnum <= WP_MONSTER_ATTACK3 ) { + // monster attacks are not allowed to go into the normal battle mode + return NULL; + } else { + cs->aifunc = AIFunc_Battle; + return "AIFunc_Battle"; + } + } + // + // we decided to start a special monster attack + return rval; +} + +/* +============ +AIFunc_DefaultStart() +============ +*/ +char *AIFunc_DefaultStart( cast_state_t *cs ) { + qboolean first = qfalse; + char *rval = NULL; + // + if ( cs->aiFlags & AIFL_JUST_SPAWNED ) { + first = qtrue; + cs->aiFlags &= ~AIFL_JUST_SPAWNED; + } + // + switch ( cs->aiCharacter ) { + case AICHAR_FEMZOMBIE: + if ( first ) { + return AIFunc_FZombie_IdleStart( cs ); + } + break; + case AICHAR_ZOMBIE: + // portal zombie, requires spawning effect + if ( first && ( g_entities[cs->entityNum].spawnflags & 4 ) ) { + return AIFunc_FlameZombie_PortalStart( cs ); + } + break; + case AICHAR_HELGA: + if ( first ) { + return AIFunc_Helga_IdleStart( cs ); + } + break; + } + // + // if they have an enemy, then pursue + if ( cs->bs->enemy >= 0 ) { + rval = AIFunc_BattleStart( cs ); + } + // + if ( !rval ) { + return AIFunc_IdleStart( cs ); + } + // + return rval; +} diff --git a/src/game/ai_cast_global.h b/src/game/ai_cast_global.h new file mode 100644 index 0000000..176063c --- /dev/null +++ b/src/game/ai_cast_global.h @@ -0,0 +1,77 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_global.h +// Function: Global AI Cast defines +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +// TTimo no typedef, "warning: useless keyword or type name in empty declaration" +struct cast_state_s; + +#define AICAST_AIM_SPREAD 2048.0 // a really bad shooter will offset a maximum of this per shot, from the end point of the 8192 trace length + +#define DANGER_MISSILE ( 1 << 0 ) +#define DANGER_CLIENTAIM ( 1 << 1 ) +#define DANGER_FLAMES ( 1 << 2 ) + +extern qboolean saveGamePending; + +qboolean AICast_SameTeam( struct cast_state_s *cs, int enemynum ); +struct cast_state_s *AICast_GetCastState( int entitynum ); +void AICast_ScriptLoad( void ); +void AICast_ScriptEvent( struct cast_state_s *cs, char *eventStr, char *params ); +void AICast_ForceScriptEvent( struct cast_state_s *cs, char *eventStr, char *params ); +qboolean AICast_AIDamageOK( struct cast_state_s *cs, struct cast_state_s *ocs ); +gentity_t *AICast_FindEntityForName( char *name ); +gentity_t *AICast_TravEntityForName( gentity_t *startent, char *name ); +void AICast_ScriptParse( struct cast_state_s *cs ); +void AICast_StartFrame( int time ); +void AICast_StartServerFrame( int time ); +void AICast_RecordWeaponFire( gentity_t *ent ); +void AICast_AIDoor_Touch( gentity_t *ent, gentity_t *aidoor_trigger, gentity_t *door ); +float AICast_GetAccuracy( int entnum ); +void AICast_Activate( int activatorNum, int entNum ); +void AICast_CheckDangerousEntity( gentity_t *ent, int dangerFlags, float dangerDist, float tacticalLevel, float aggressionLevel, qboolean hurtFriendly ); +qboolean AICast_NoFlameDamage( int entNum ); +qboolean AICast_HasFiredWeapon( int entNum, int weapon ); +void G_SetAASBlockingEntity( gentity_t *ent, qboolean blocking ); +qboolean AICast_InFieldOfVision( vec3_t viewangles, float fov, vec3_t angles ); +qboolean AICast_VisibleFromPos( vec3_t srcpos, int srcnum, + vec3_t destpos, int destnum, qboolean updateVisPos ); +qboolean AICast_AllowFlameDamage( int entNum ); +void AICast_AdjustIdealYawForMover( int entnum, float yaw ); +void AICast_AgePlayTime( int entnum ); +int AICast_NoReload( int entnum ); +void AICast_RecordScriptSound( int client ); +void AICast_UpdateVisibility( gentity_t *srcent, gentity_t *destent, qboolean shareVis, qboolean directview ); +void AICast_ProcessBullet( gentity_t *attacker, vec3_t start, vec3_t end ); +void AICast_AudibleEvent( int srcnum, vec3_t pos, float range ); diff --git a/src/game/ai_cast_script.c b/src/game/ai_cast_script.c new file mode 100644 index 0000000..a8a1273 --- /dev/null +++ b/src/game/ai_cast_script.c @@ -0,0 +1,729 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_script.c +// Function: Wolfenstein AI Character Scripting +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +Scripting that allows the designers to control the behaviour of AI characters +according to each different scenario. +*/ + +// action functions need to be declared here so they can be accessed in the scriptAction table +qboolean AICast_ScriptAction_GotoMarker( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_WalkToMarker( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_CrouchToMarker( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_GotoCast( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_WalkToCast( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_CrouchToCast( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Wait( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Trigger( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_FollowCast( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_PlaySound( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_NoAttack( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Attack( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_PlayAnim( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_ClearAnim( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_SetAmmo( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_SetClip( cast_state_t *cs, char *params ); //----(SA) added +qboolean AICast_ScriptAction_SelectWeapon( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_GiveArmor( cast_state_t *cs, char *params ); //----(SA) added +qboolean AICast_ScriptAction_GiveWeapon( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_GiveInventory( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_TakeWeapon( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Movetype( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_AlertEntity( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_SaveGame( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_FireAtTarget( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_GodMode( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Accum( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_SpawnCast( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_MissionFailed( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_MissionSuccess( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_NoAIDamage( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Print( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_FaceTargetAngles( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_ResetScript( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Mount( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Unmount( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_SavePersistant( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_ChangeLevel( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_FoundSecret( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_NoSight( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Sight( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_NoAvoid( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Avoid( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Attrib( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_DenyAction( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_LightningDamage( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Headlook( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_BackupScript( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_RestoreScript( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_StateType( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_KnockBack( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Zoom( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Parachute( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_StartCam( cast_state_t *cs, char *params ); //----(SA) added +qboolean AICast_ScriptAction_StartCamBlack( cast_state_t *cs, char *params ); //----(SA) added +qboolean AICast_ScriptAction_EntityScriptName( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_AIScriptName( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_SetHealth( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_NoTarget( cast_state_t *cs, char *params ); +qboolean AICast_ScriptAction_Cvar( cast_state_t *cs, char *params ); + +// these are the actions that each event can call +cast_script_stack_action_t scriptActions[] = +{ + {"gotomarker", AICast_ScriptAction_GotoMarker}, + {"runtomarker", AICast_ScriptAction_GotoMarker}, + {"walktomarker", AICast_ScriptAction_WalkToMarker}, + {"crouchtomarker", AICast_ScriptAction_CrouchToMarker}, + {"gotocast", AICast_ScriptAction_GotoCast}, + {"runtocast", AICast_ScriptAction_GotoCast}, + {"walktocast", AICast_ScriptAction_WalkToCast}, + {"crouchtocast", AICast_ScriptAction_CrouchToCast}, + {"followcast", AICast_ScriptAction_FollowCast}, + {"playsound", AICast_ScriptAction_PlaySound}, + {"playanim", AICast_ScriptAction_PlayAnim}, + {"clearanim", AICast_ScriptAction_ClearAnim}, + {"wait", AICast_ScriptAction_Wait}, + {"trigger", AICast_ScriptAction_Trigger}, + {"setammo", AICast_ScriptAction_SetAmmo}, + {"setclip", AICast_ScriptAction_SetClip}, //----(SA) added + {"selectweapon", AICast_ScriptAction_SelectWeapon}, + {"noattack", AICast_ScriptAction_NoAttack}, + {"attack", AICast_ScriptAction_Attack}, + {"givearmor", AICast_ScriptAction_GiveArmor}, //----(SA) added + {"giveinventory", AICast_ScriptAction_GiveInventory}, + {"giveweapon", AICast_ScriptAction_GiveWeapon}, + {"takeweapon", AICast_ScriptAction_TakeWeapon}, + {"movetype", AICast_ScriptAction_Movetype}, + {"alertentity", AICast_ScriptAction_AlertEntity}, + {"savegame", AICast_ScriptAction_SaveGame}, + {"fireattarget", AICast_ScriptAction_FireAtTarget}, + {"godmode", AICast_ScriptAction_GodMode}, + {"accum", AICast_ScriptAction_Accum}, + {"spawncast", AICast_ScriptAction_SpawnCast}, + {"missionfailed", AICast_ScriptAction_MissionFailed}, + {"missionsuccess", AICast_ScriptAction_MissionSuccess}, + {"noaidamage", AICast_ScriptAction_NoAIDamage}, + {"print", AICast_ScriptAction_Print}, + {"facetargetangles",AICast_ScriptAction_FaceTargetAngles}, + {"resetscript", AICast_ScriptAction_ResetScript}, + {"mount", AICast_ScriptAction_Mount}, + {"unmount", AICast_ScriptAction_Unmount}, + {"savepersistant", AICast_ScriptAction_SavePersistant}, + {"changelevel", AICast_ScriptAction_ChangeLevel}, + {"foundsecret", AICast_ScriptAction_FoundSecret}, + {"nosight", AICast_ScriptAction_NoSight}, + {"sight", AICast_ScriptAction_Sight}, + {"noavoid", AICast_ScriptAction_NoAvoid}, + {"avoid", AICast_ScriptAction_Avoid}, + {"attrib", AICast_ScriptAction_Attrib}, + {"denyactivate", AICast_ScriptAction_DenyAction}, + {"lightningdamage", AICast_ScriptAction_LightningDamage}, + {"deny", AICast_ScriptAction_DenyAction}, + {"headlook", AICast_ScriptAction_Headlook}, + {"backupscript", AICast_ScriptAction_BackupScript}, + {"restorescript", AICast_ScriptAction_RestoreScript}, + {"statetype", AICast_ScriptAction_StateType}, + {"knockback", AICast_ScriptAction_KnockBack}, + {"zoom", AICast_ScriptAction_Zoom}, + {"parachute", AICast_ScriptAction_Parachute}, + {"startcam", AICast_ScriptAction_StartCam}, //----(SA) added + {"startcamblack", AICast_ScriptAction_StartCamBlack}, //----(SA) added + {"entityscriptname",AICast_ScriptAction_EntityScriptName}, + {"aiscriptname", AICast_ScriptAction_AIScriptName}, + {"sethealth", AICast_ScriptAction_SetHealth}, + {"notarget", AICast_ScriptAction_NoTarget}, + {"cvar", AICast_ScriptAction_Cvar}, + + {NULL, NULL} +}; + +qboolean AICast_EventMatch_StringEqual( cast_script_event_t *event, char *eventParm ); +qboolean AICast_EventMatch_IntInRange( cast_script_event_t *event, char *eventParm ); + +// the list of events that can start an action sequence +// NOTE!!: only append to this list, DO NOT INSERT!! +cast_script_event_define_t scriptEvents[] = +{ + {"spawn", NULL}, // called as each character is spawned into the game + {"enemysight", AICast_EventMatch_StringEqual}, // enemy has been sighted for the first time (once only) + {"sight", AICast_EventMatch_StringEqual}, // non-enemy has been sighted for the first time (once only) + {"enemydead", AICast_EventMatch_StringEqual}, // our enemy is now dead + {"trigger", AICast_EventMatch_StringEqual}, // something has triggered us (always followed by an identifier) + {"pain", AICast_EventMatch_IntInRange}, // we've been hurt + {"death", NULL}, // RIP + {"activate", AICast_EventMatch_StringEqual}, // "param" has just activated us + {"enemysightcorpse",AICast_EventMatch_StringEqual}, // sighted the given enemy as a corpse, for the first time + {"friendlysightcorpse", NULL}, // sighted a friendly as a corpse for the first time + {"avoiddanger", AICast_EventMatch_StringEqual}, // we are avoiding something dangerous + {"blocked", AICast_EventMatch_StringEqual}, // blocked by someone else + {"statechange", AICast_EventMatch_StringEqual}, // changing aistates + {"bulletimpact", NULL}, + {"inspectbodystart", AICast_EventMatch_StringEqual}, // starting to travel to body for inspection + {"inspectbodyend", AICast_EventMatch_StringEqual}, // reached body for inspection + {"inspectsoundstart", AICast_EventMatch_StringEqual}, // reached sound for inspection + {"inspectsoundend", AICast_EventMatch_StringEqual}, // reached sound for inspection + {"attacksound", AICast_EventMatch_StringEqual}, // play a custom attack sound, and/or deny playing the default sound + {"fakedeath", NULL}, + {"bulletimpactsound", NULL}, + {"inspectfriendlycombatstart", NULL}, + + {NULL, NULL} +}; + +int numSecrets; + +/* +=============== +AICast_EventMatch_StringEqual +=============== +*/ +qboolean AICast_EventMatch_StringEqual( cast_script_event_t *event, char *eventParm ) { + if ( !event->params || !event->params[0] || ( eventParm && !Q_strcasecmp( event->params, eventParm ) ) ) { + return qtrue; + } else { + return qfalse; + } +} + +/* +=============== +AICast_EventMatch_IntInRange +=============== +*/ +qboolean AICast_EventMatch_IntInRange( cast_script_event_t *event, char *eventParm ) { + char *pString, *token; + int int1, int2, eInt; + + // get the cast name + pString = eventParm; + token = COM_ParseExt( &pString, qfalse ); + int1 = atoi( token ); + token = COM_ParseExt( &pString, qfalse ); + int2 = atoi( token ); + + eInt = atoi( event->params ); + + if ( eventParm && eInt > int1 && eInt <= int2 ) { + return qtrue; + } else { + return qfalse; + } +} + +/* +=============== +AICast_EventForString +=============== +*/ +int AICast_EventForString( char *string ) { + int i; + + for ( i = 0; scriptEvents[i].eventStr; i++ ) + { + if ( !Q_strcasecmp( string, scriptEvents[i].eventStr ) ) { + return i; + } + } + + return -1; +} + +/* +=============== +AICast_ActionForString +=============== +*/ +cast_script_stack_action_t *AICast_ActionForString( char *string ) { + int i; + + for ( i = 0; scriptActions[i].actionString; i++ ) + { + if ( !Q_strcasecmp( string, scriptActions[i].actionString ) ) { + if ( !Q_strcasecmp( string, "foundsecret" ) ) { + numSecrets++; + } + return &scriptActions[i]; + } + } + + return NULL; +} + +/* +============= +AICast_ScriptLoad + + Loads the script for the current level into the buffer +============= +*/ +void AICast_ScriptLoad( void ) { + char filename[MAX_QPATH]; + vmCvar_t mapname; + fileHandle_t f; + int len; + + level.scriptAI = NULL; + + trap_Cvar_VariableStringBuffer( "ai_scriptName", filename, sizeof( filename ) ); + if ( strlen( filename ) > 0 ) { + trap_Cvar_Register( &mapname, "ai_scriptName", "", CVAR_ROM ); + } else { + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + } + Q_strncpyz( filename, "maps/", sizeof( filename ) ); + Q_strcat( filename, sizeof( filename ), mapname.string ); + Q_strcat( filename, sizeof( filename ), ".ai" ); + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + + // make sure we clear out the temporary scriptname + trap_Cvar_Set( "ai_scriptName", "" ); + + if ( len < 0 ) { + return; + } + + level.scriptAI = G_Alloc( len ); + trap_FS_Read( level.scriptAI, len, f ); + + trap_FS_FCloseFile( f ); + + return; +} + +/* +============== +AICast_ScriptParse + + Parses the script for the given character +============== +*/ +void AICast_ScriptParse( cast_state_t *cs ) { + #define MAX_SCRIPT_EVENTS 64 + gentity_t *ent; + char *pScript; + char *token; + qboolean wantName; + qboolean inScript; + int eventNum; + cast_script_event_t events[MAX_SCRIPT_EVENTS]; + int numEventItems; + cast_script_event_t *curEvent; + char params[MAX_QPATH]; + cast_script_stack_action_t *action; + int i; + int bracketLevel; + + if ( !level.scriptAI ) { + return; + } + + ent = &g_entities[cs->entityNum]; + if ( !ent->aiName ) { + return; + } + + pScript = level.scriptAI; + wantName = qtrue; + inScript = qfalse; + COM_BeginParseSession( "AICast_ScriptParse" ); + bracketLevel = 0; + numEventItems = 0; + + memset( events, 0, sizeof( events ) ); + + while ( 1 ) + { + token = COM_Parse( &pScript ); + + if ( !token[0] ) { + if ( !wantName ) { + G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); + } + break; + } + + // end of script + if ( token[0] == '}' ) { + if ( inScript ) { + break; + } + if ( wantName ) { + G_Error( "AICast_ScriptParse(), Error (line %d): '}' found, but not expected.\n", COM_GetCurrentParseLine() ); + } + wantName = qtrue; + } else if ( token[0] == '{' ) { + if ( wantName ) { + G_Error( "AICast_ScriptParse(), Error (line %d): '{' found, NAME expected.\n", COM_GetCurrentParseLine() ); + } + } else if ( wantName ) { + if ( !Q_strcasecmp( ent->aiName, token ) ) { + inScript = qtrue; + numEventItems = 0; + } + wantName = qfalse; + } else if ( inScript ) { + if ( !Q_strcasecmp( token, "attributes" ) ) { + // read in all the attributes + AICast_CheckLevelAttributes( cs, ent, &pScript ); + continue; + } + eventNum = AICast_EventForString( token ); + if ( eventNum < 0 ) { + G_Error( "AICast_ScriptParse(), Error (line %d): unknown event: %s.\n", COM_GetCurrentParseLine(), token ); + } + if ( numEventItems >= MAX_SCRIPT_EVENTS ) { + G_Error( "AICast_ScriptParse(), Error (line %d): MAX_SCRIPT_EVENTS reached (%d)\n", COM_GetCurrentParseLine(), MAX_SCRIPT_EVENTS ); + } + + // if this is a "friendlysightcorpse" event, then disable corpse vis sharing + if ( !Q_stricmp( token, "friendlysightcorpse" ) ) { + cs->aiFlags &= ~AIFL_CORPSESIGHTING; + } + + curEvent = &events[numEventItems]; + curEvent->eventNum = eventNum; + memset( params, 0, sizeof( params ) ); + + // parse any event params before the start of this event's actions + while ( ( token = COM_Parse( &pScript ) ) && ( token[0] != '{' ) ) + { + if ( !token[0] ) { + G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); + } + + if ( eventNum == 13 ) { // statechange event, check params + if ( strlen( token ) > 1 ) { + if ( BG_IndexForString( token, animStateStr, qtrue ) < 0 ) { + G_Error( "AICast_ScriptParse(), Error (line %d): unknown state type '%s'.\n", COM_GetCurrentParseLine(), token ); + } + } + } + + if ( strlen( params ) ) { // add a space between each param + Q_strcat( params, sizeof( params ), " " ); + } + Q_strcat( params, sizeof( params ), token ); + } + + if ( strlen( params ) ) { // copy the params into the event + curEvent->params = G_Alloc( strlen( params ) + 1 ); + Q_strncpyz( curEvent->params, params, strlen( params ) + 1 ); + } + + // parse the actions for this event + while ( ( token = COM_Parse( &pScript ) ) && ( token[0] != '}' ) ) + { + if ( !token[0] ) { + G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); + } + + action = AICast_ActionForString( token ); + if ( !action ) { + G_Error( "AICast_ScriptParse(), Error (line %d): unknown action: %s.\n", COM_GetCurrentParseLine(), token ); + } + + curEvent->stack.items[curEvent->stack.numItems].action = action; + + memset( params, 0, sizeof( params ) ); + token = COM_ParseExt( &pScript, qfalse ); + for ( i = 0; token[0]; i++ ) + { + if ( strlen( params ) ) { // add a space between each param + Q_strcat( params, sizeof( params ), " " ); + } + + // Special case: playsound's need to be cached on startup to prevent in-game pauses + if ( ( i == 0 ) && !Q_stricmp( action->actionString, "playsound" ) ) { + G_SoundIndex( token ); + } + + if ( strrchr( token,' ' ) ) { // need to wrap this param in quotes since it has more than one word + Q_strcat( params, sizeof( params ), "\"" ); + } + + Q_strcat( params, sizeof( params ), token ); + + if ( strrchr( token,' ' ) ) { // need to wrap this param in quotes since it has more than one word + Q_strcat( params, sizeof( params ), "\"" ); + } + + token = COM_ParseExt( &pScript, qfalse ); + } + + if ( strlen( params ) ) { // copy the params into the event + curEvent->stack.items[curEvent->stack.numItems].params = G_Alloc( strlen( params ) + 1 ); + Q_strncpyz( curEvent->stack.items[curEvent->stack.numItems].params, params, strlen( params ) + 1 ); + } + + curEvent->stack.numItems++; + + if ( curEvent->stack.numItems >= AICAST_MAX_SCRIPT_STACK_ITEMS ) { + G_Error( "AICast_ScriptParse(): script exceeded MAX_SCRIPT_ITEMS (%d), line %d\n", AICAST_MAX_SCRIPT_STACK_ITEMS, COM_GetCurrentParseLine() ); + } + } + + numEventItems++; + } else // skip this character completely + { + // TTimo gcc: suggest parentheses around assignment used as truth value + while ( ( token = COM_Parse( &pScript ) ) ) + { + if ( !token[0] ) { + G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); + } else if ( token[0] == '{' ) { + bracketLevel++; + } else if ( token[0] == '}' ) { + if ( !--bracketLevel ) { + break; + } + } + } + } + } + + // alloc and copy the events into the cast_state_t for this cast + if ( numEventItems > 0 ) { + cs->castScriptEvents = G_Alloc( sizeof( cast_script_event_t ) * numEventItems ); + memcpy( cs->castScriptEvents, events, sizeof( cast_script_event_t ) * numEventItems ); + cs->numCastScriptEvents = numEventItems; + + cs->castScriptStatus.castScriptEventIndex = -1; + } +} + +/* +================ +AICast_ScriptChange +================ +*/ +void AICast_ScriptChange( cast_state_t *cs, int newScriptNum ) { + cast_script_status_t scriptStatusBackup; + + cs->scriptCallIndex++; + + // backup the current scripting + scriptStatusBackup = cs->castScriptStatus; + + // set the new script to this cast, and reset script status + cs->castScriptStatus.castScriptStackHead = 0; + cs->castScriptStatus.castScriptStackChangeTime = level.time; + cs->castScriptStatus.castScriptEventIndex = newScriptNum; + cs->castScriptStatus.scriptId = scriptStatusBackup.scriptId + 1; + cs->castScriptStatus.scriptGotoId = -1; + cs->castScriptStatus.scriptGotoEnt = -1; + cs->castScriptStatus.scriptFlags |= SFL_FIRST_CALL; + + // try and run the script, if it doesn't finish, then abort the current script (discard backup) + if ( AICast_ScriptRun( cs, qtrue ) ) { + // completed successfully + cs->castScriptStatus.castScriptStackHead = scriptStatusBackup.castScriptStackHead; + cs->castScriptStatus.castScriptStackChangeTime = scriptStatusBackup.castScriptStackChangeTime; + cs->castScriptStatus.castScriptEventIndex = scriptStatusBackup.castScriptEventIndex; + cs->castScriptStatus.scriptId = scriptStatusBackup.scriptId; + cs->castScriptStatus.scriptFlags = scriptStatusBackup.scriptFlags; + } +} + +/* +================ +AICast_ScriptEvent + + An event has occured, for which a script may exist +================ +*/ +void AICast_ScriptEvent( struct cast_state_s *cs, char *eventStr, char *params ) { + int i, eventNum; + + eventNum = -1; + + // find out which event this is + for ( i = 0; scriptEvents[i].eventStr; i++ ) + { + if ( !Q_strcasecmp( eventStr, scriptEvents[i].eventStr ) ) { // match found + eventNum = i; + break; + } + } + + if ( eventNum < 0 ) { + if ( g_cheats.integer ) { // dev mode + G_Printf( "devmode-> AICast_ScriptEvent(), unknown event: %s\n", eventStr ); + } + } + + // show debugging info + if ( ( ( aicast_debug.integer == 1 ) || + ( ( aicast_debug.integer == 2 ) && + ( ( strlen( aicast_debugname.string ) < 1 ) || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) ) ) ) { + G_Printf( "(%s) AIScript event: %s %s ", g_entities[cs->entityNum].aiName, eventStr, params ); + } + + cs->aiFlags &= ~AIFL_DENYACTION; + + // see if this cast has this event + for ( i = 0; i < cs->numCastScriptEvents; i++ ) + { + if ( cs->castScriptEvents[i].eventNum == eventNum ) { + if ( ( !cs->castScriptEvents[i].params ) + || ( !scriptEvents[eventNum].eventMatch || scriptEvents[eventNum].eventMatch( &cs->castScriptEvents[i], params ) ) ) { + + // show debugging info + if ( ( ( aicast_debug.integer == 1 ) || + ( ( aicast_debug.integer == 2 ) && + ( ( strlen( aicast_debugname.string ) < 1 ) || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) ) ) ) { + G_Printf( "found, calling script\n", g_entities[cs->entityNum].aiName, eventStr, params ); + } + + AICast_ScriptChange( cs, i ); + break; + } + } + } + + // show debugging info + if ( ( ( aicast_debug.integer == 1 ) || + ( ( aicast_debug.integer == 2 ) && + ( ( strlen( aicast_debugname.string ) < 1 ) || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) ) ) ) { + if ( i == cs->numCastScriptEvents ) { + G_Printf( "not found\n" ); + } + } + +} + +/* +================ +AICast_ForceScriptEvent + + Definately run this event now, overriding any paised state +================ +*/ +void AICast_ForceScriptEvent( struct cast_state_s *cs, char *eventStr, char *params ) { + int oldPauseTime; + + oldPauseTime = cs->scriptPauseTime; + cs->scriptPauseTime = 0; + + AICast_ScriptEvent( cs, eventStr, params ); + + cs->scriptPauseTime = oldPauseTime; +} + +/* +============= +AICast_ScriptRun + + returns qtrue if the script completed +============= +*/ +qboolean AICast_ScriptRun( cast_state_t *cs, qboolean force ) { + cast_script_stack_t *stack; + + if ( saveGamePending ) { + return qfalse; + } + + if ( strlen( g_missionStats.string ) > 1 ) { + return qfalse; + } + + if ( !aicast_scripts.integer ) { + return qtrue; + } + + if ( cs->castScriptStatus.castScriptEventIndex < 0 ) { + return qtrue; + } + + if ( !cs->castScriptEvents ) { + cs->castScriptStatus.castScriptEventIndex = -1; + return qtrue; + } + + if ( !force && ( cs->scriptPauseTime >= level.time ) ) { + return qtrue; + } + + stack = &cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack; + + if ( !stack->numItems ) { + cs->castScriptStatus.castScriptEventIndex = -1; + return qtrue; + } + + while ( cs->castScriptStatus.castScriptStackHead < stack->numItems ) + { + // + // show debugging info + if ( ( cs->castScriptStatus.castScriptStackChangeTime == level.time ) && + ( ( aicast_debug.integer == 1 ) || + ( ( aicast_debug.integer == 2 ) && + ( ( strlen( aicast_debugname.string ) < 1 ) || ( g_entities[cs->entityNum].aiName && !strcmp( aicast_debugname.string, g_entities[cs->entityNum].aiName ) ) ) ) ) ) { + G_Printf( "(%s) AIScript command: %s %s\n", g_entities[cs->entityNum].aiName, stack->items[cs->castScriptStatus.castScriptStackHead].action->actionString, ( stack->items[cs->castScriptStatus.castScriptStackHead].params ? stack->items[cs->castScriptStatus.castScriptStackHead].params : "" ) ); + } + // + if ( !stack->items[cs->castScriptStatus.castScriptStackHead].action->actionFunc( cs, stack->items[cs->castScriptStatus.castScriptStackHead].params ) ) { + cs->castScriptStatus.scriptFlags &= ~SFL_FIRST_CALL; + return qfalse; + } + // move to the next action in the script + cs->castScriptStatus.castScriptStackHead++; + // record the time that this new item became active + cs->castScriptStatus.castScriptStackChangeTime = level.time; + // reset misc stuff + cs->castScriptStatus.scriptGotoId = -1; + cs->castScriptStatus.scriptGotoEnt = -1; + cs->castScriptStatus.scriptFlags |= SFL_FIRST_CALL; + } + + cs->castScriptStatus.castScriptEventIndex = -1; + + return qtrue; +} diff --git a/src/game/ai_cast_script_actions.c b/src/game/ai_cast_script_actions.c new file mode 100644 index 0000000..3439ebf --- /dev/null +++ b/src/game/ai_cast_script_actions.c @@ -0,0 +1,2416 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_script_actions.c +// Function: Wolfenstein AI Character Scripting +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +Contains the code to handle the various commands available with an event script. + +These functions will return true if the action has been performed, and the script +should proceed to the next item on the list. +*/ + +/* +=============== +AICast_NoAttackIfNotHurtSinceLastScriptAction + + Not an actual command, this is just used by the script code +=============== +*/ +void AICast_NoAttackIfNotHurtSinceLastScriptAction( cast_state_t *cs ) { + if ( cs->castScriptStatus.scriptNoAttackTime > level.time ) { + return; + } + + // if not moving, we should attack + if ( VectorLength( cs->bs->velocity ) < 10 ) { + return; + } + + // if our enemy is in the direction we are moving, don't hold back + if ( cs->bs->enemy >= 0 && cs->castScriptStatus.scriptGotoEnt >= 0 ) { + vec3_t v; + + VectorSubtract( g_entities[cs->bs->enemy].r.currentOrigin, cs->bs->origin, v ); + if ( DotProduct( cs->bs->velocity, v ) > 0 ) { + return; + } + } + + if ( cs->lastPain < cs->castScriptStatus.castScriptStackChangeTime ) { + cs->castScriptStatus.scriptNoAttackTime = level.time + FRAMETIME; + } +} + +/* +=============== +AICast_ScriptAction_GotoMarker + + syntax: gotomarker [firetarget [noattack]] [nostop] OR runtomarker [firetarget [noattack]] [nostop] +=============== +*/ +qboolean AICast_ScriptAction_GotoMarker( cast_state_t *cs, char *params ) { +#define SCRIPT_REACHGOAL_DIST 8 + char *pString, *token; + gentity_t *ent; + vec3_t vec, org; + int i, diff; + qboolean slowApproach; + + ent = NULL; + + // if we are avoiding danger, then wait for the danger to pass + if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) { + return qfalse; + } + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI scripting: gotomarker must have an targetname\n" ); + } + + // if we already are going to the marker, just use that, and check if we're in range + if ( cs->castScriptStatus.scriptGotoEnt >= 0 && cs->castScriptStatus.scriptGotoId == cs->thinkFuncChangeTime ) { + ent = &g_entities[cs->castScriptStatus.scriptGotoEnt]; + if ( ent->targetname && !Q_strcasecmp( ent->targetname, token ) ) { + // if we're not slowing down, then check for passing the marker, otherwise check distance only + VectorSubtract( ent->r.currentOrigin, cs->bs->origin, vec ); + // + if ( cs->followSlowApproach && VectorLength( vec ) < cs->followDist ) { + AIFunc_IdleStart( cs ); // resume normal AI + return qtrue; + } else if ( !cs->followSlowApproach && VectorLength( vec ) < 64 /*&& DotProduct(cs->bs->cur_ps.velocity, vec) < 0*/ ) { + AIFunc_IdleStart( cs ); // resume normal AI + return qtrue; + } else + { + // do we have a firetarget ? + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] || !Q_stricmp( token,"nostop" ) ) { + AICast_NoAttackIfNotHurtSinceLastScriptAction( cs ); + } else { // yes we do + // find this targetname + ent = G_Find( NULL, FOFS( targetname ), token ); + if ( !ent ) { + ent = AICast_FindEntityForName( token ); + if ( !ent ) { + G_Error( "AI Scripting: gotomarker cannot find targetname \"%s\"\n", token ); + } + } + // set the view angle manually + BG_EvaluateTrajectory( &ent->s.pos, level.time, org ); + VectorSubtract( org, cs->bs->origin, vec ); + VectorNormalize( vec ); + vectoangles( vec, cs->bs->ideal_viewangles ); + // noattack? + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] || Q_stricmp( token,"noattack" ) ) { + qboolean fire = qtrue; + // if it's an AI, and they aren't visible, dont shoot + if ( ent->r.svFlags & SVF_CASTAI ) { + if ( cs->vislist[ent->s.number].real_visible_timestamp != cs->vislist[ent->s.number].lastcheck_timestamp ) { + fire = qfalse; + } + } + if ( fire ) { + for ( i = 0; i < 2; i++ ) { + diff = abs( AngleDifference( cs->bs->viewangles[i], cs->bs->ideal_viewangles[i] ) ); + if ( diff < 20 ) { + // force fire + trap_EA_Attack( cs->bs->client ); + // + cs->bs->flags |= BFL_ATTACKED; + } + } + } + } + } + cs->followTime = level.time + FRAMETIME * 3; + return qfalse; + } + } else + { + ent = NULL; + } + } + + // find the ai_marker with the given "targetname" + // TTimo gcc: suggest parentheses around assignment used as truth value + while ( ( ent = G_Find( ent, FOFS( classname ), "ai_marker" ) ) ) + { + if ( ent->targetname && !Q_strcasecmp( ent->targetname, token ) ) { + break; + } + } + + if ( !ent ) { + G_Error( "AI Scripting: can't find ai_marker with \"targetname\" = \"%s\"\n", token ); + } + + if ( Distance( cs->bs->origin, ent->r.currentOrigin ) < SCRIPT_REACHGOAL_DIST ) { // we made it + return qtrue; + } + + cs->castScriptStatus.scriptNoMoveTime = 0; + cs->castScriptStatus.scriptGotoEnt = ent->s.number; + // + // slow approach to the goal? + if ( !params || !strstr( params," nostop" ) ) { + slowApproach = qtrue; + } else { + slowApproach = qfalse; + } + // + AIFunc_ChaseGoalStart( cs, ent->s.number, ( slowApproach ? SCRIPT_REACHGOAL_DIST : 32 ), slowApproach ); + cs->followIsGoto = qtrue; + cs->followTime = 0x7fffffff; // make sure it gets through for the first frame + cs->castScriptStatus.scriptGotoId = cs->thinkFuncChangeTime; + + AICast_NoAttackIfNotHurtSinceLastScriptAction( cs ); + return qfalse; +} + +/* +=============== +AICast_ScriptAction_WalkToMarker + + syntax: walktomarker [firetarget [noattack]] [nostop] +=============== +*/ +qboolean AICast_ScriptAction_WalkToMarker( cast_state_t *cs, char *params ) { + // if we are avoiding danger, then wait for the danger to pass + if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) { + return qfalse; + } + if ( !AICast_ScriptAction_GotoMarker( cs, params ) || ( !strstr( params, " nostop" ) && VectorLength( cs->bs->cur_ps.velocity ) ) ) { + cs->movestate = MS_WALK; + cs->movestateType = MSTYPE_TEMPORARY; + AICast_NoAttackIfNotHurtSinceLastScriptAction( cs ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +AICast_ScriptAction_CrouchToMarker + + syntax: crouchtomarker [firetarget [noattack]] [nostop] +=============== +*/ +qboolean AICast_ScriptAction_CrouchToMarker( cast_state_t *cs, char *params ) { + // if we are avoiding danger, then wait for the danger to pass + if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) { + return qfalse; + } + if ( !AICast_ScriptAction_GotoMarker( cs, params ) || ( !strstr( params, " nostop" ) && VectorLength( cs->bs->cur_ps.velocity ) ) ) { + cs->movestate = MS_CROUCH; + cs->movestateType = MSTYPE_TEMPORARY; + AICast_NoAttackIfNotHurtSinceLastScriptAction( cs ); + return qfalse; + } + + return qtrue; +} + +/* +=============== +AICast_ScriptAction_GotoCast + + syntax: gotocast [firetarget [noattack]] OR runtocast [firetarget [noattack]] +=============== +*/ +qboolean AICast_ScriptAction_GotoCast( cast_state_t *cs, char *params ) { +#define SCRIPT_REACHCAST_DIST 64 + char *pString, *token; + gentity_t *ent; + vec3_t vec, org; + int i, diff; + + ent = NULL; + + // if we are avoiding danger, then wait for the danger to pass + if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) { + return qfalse; + } + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI scripting: gotocast must have an ainame\n" ); + } + + // if we already are going to the marker, just use that, and check if we're in range + if ( cs->castScriptStatus.scriptGotoEnt >= 0 && cs->castScriptStatus.scriptGotoId == cs->thinkFuncChangeTime ) { + ent = &g_entities[cs->castScriptStatus.scriptGotoEnt]; + if ( ent->targetname && !Q_strcasecmp( ent->targetname, token ) ) { + if ( Distance( cs->bs->origin, ent->r.currentOrigin ) < cs->followDist ) { + AIFunc_IdleStart( cs ); // resume normal AI + return qtrue; + } else + { + // do we have a firetarget ? + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + AICast_NoAttackIfNotHurtSinceLastScriptAction( cs ); + } else { // yes we do + // find this targetname + ent = G_Find( NULL, FOFS( targetname ), token ); + if ( !ent ) { + ent = AICast_FindEntityForName( token ); + if ( !ent ) { + G_Error( "AI Scripting: gotomarker cannot find targetname \"%s\"\n", token ); + } + } + + // set the view angle manually + BG_EvaluateTrajectory( &ent->s.pos, level.time, org ); + VectorSubtract( org, cs->bs->origin, vec ); + VectorNormalize( vec ); + vectoangles( vec, cs->bs->ideal_viewangles ); + // noattack? + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] || Q_stricmp( token,"noattack" ) ) { + qboolean fire = qtrue; + // if it's an AI, and they aren't visible, dont shoot + if ( ent->r.svFlags & SVF_CASTAI ) { + if ( cs->vislist[ent->s.number].real_visible_timestamp != cs->vislist[ent->s.number].lastcheck_timestamp ) { + fire = qfalse; + } + } + if ( fire ) { + for ( i = 0; i < 2; i++ ) { + diff = abs( AngleDifference( cs->bs->viewangles[i], cs->bs->ideal_viewangles[i] ) ); + if ( diff < 20 ) { + // force fire + trap_EA_Attack( cs->bs->client ); + // + cs->bs->flags |= BFL_ATTACKED; + } + } + } + } + } + cs->followTime = level.time + FRAMETIME * 3; // keep following them for another few frames + return qfalse; + } + } else + { + ent = NULL; + } + } + + // find the cast/player with the given "name" + ent = AICast_FindEntityForName( token ); + if ( !ent ) { + G_Error( "AI Scripting: can't find AI cast with \"ainame\" = \"%s\"\n", token ); + } + + if ( Distance( cs->bs->origin, ent->r.currentOrigin ) < SCRIPT_REACHCAST_DIST ) { // we made it + return qtrue; + } + + if ( !ent ) { + G_Error( "AI Scripting: can't find ai_marker with \"targetname\" = \"%s\"\n", token ); + } + + cs->castScriptStatus.scriptNoMoveTime = 0; + cs->castScriptStatus.scriptGotoEnt = ent->s.number; + // + AIFunc_ChaseGoalStart( cs, ent->s.number, SCRIPT_REACHCAST_DIST, qtrue ); + cs->followTime = 0x7fffffff; + AICast_NoAttackIfNotHurtSinceLastScriptAction( cs ); + cs->castScriptStatus.scriptGotoId = cs->thinkFuncChangeTime; + + return qfalse; +} + +/* +=============== +AICast_ScriptAction_WalkToCast + + syntax: walktocast [firetarget [noattack]] +=============== +*/ +qboolean AICast_ScriptAction_WalkToCast( cast_state_t *cs, char *params ) { + // if we are avoiding danger, then wait for the danger to pass + if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) { + return qfalse; + } + if ( !AICast_ScriptAction_GotoCast( cs, params ) ) { + cs->movestate = MS_WALK; + cs->movestateType = MSTYPE_TEMPORARY; + AICast_NoAttackIfNotHurtSinceLastScriptAction( cs ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +AICast_ScriptAction_CrouchToCast + + syntax: crouchtocast [firetarget [noattack]] +=============== +*/ +qboolean AICast_ScriptAction_CrouchToCast( cast_state_t *cs, char *params ) { + // if we are avoiding danger, then wait for the danger to pass + if ( cs->castScriptStatus.scriptGotoId < 0 && cs->dangerEntityValidTime > level.time ) { + return qfalse; + } + if ( !AICast_ScriptAction_GotoCast( cs, params ) ) { + cs->movestate = MS_CROUCH; + cs->movestateType = MSTYPE_TEMPORARY; + AICast_NoAttackIfNotHurtSinceLastScriptAction( cs ); + return qfalse; + } + + return qtrue; +} + +/* +================= +AICast_ScriptAction_Wait + + syntax: wait [moverange] [facetarget] + + moverange defaults to 200, allows some monouverability to avoid fire or attack +================= +*/ +qboolean AICast_ScriptAction_Wait( cast_state_t *cs, char *params ) { + char *pString, *token, *facetarget; + int duration; + float moverange; + float dist; + gentity_t *ent; + vec3_t org, vec; + + // EXPERIMENTAL: if they are on fire, or avoiding danger, let them loose until it passes (or they die) + if ( cs->dangerEntityValidTime > level.time ) { + cs->castScriptStatus.scriptNoMoveTime = -1; + return qfalse; + } + + if ( cs->castScriptStatus.castScriptStackChangeTime == level.time && cs->bs ) { + // first call, init the waitPos + VectorCopy( cs->bs->origin, cs->castScriptStatus.scriptWaitPos ); + } + + // get the duration + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI scripting: wait must have a duration\n" ); + } + if ( !Q_stricmp( token, "forever" ) ) { + duration = level.time + 10000; + } else { + duration = atoi( token ); + } + + // if this is for the player, don't worry about enforcing the moverange + if ( !cs->bs ) { + return ( cs->castScriptStatus.castScriptStackChangeTime + duration < level.time ); + } + + token = COM_ParseExt( &pString, qfalse ); + // if this token is a number, then assume it is the moverange, otherwise we have a default moverange with a facetarget + moverange = -999; + facetarget = NULL; + if ( token[0] ) { + if ( toupper( token[0] ) >= 'A' && toupper( token[0] ) <= 'Z' ) { + facetarget = token; + } else { // we found a moverange + moverange = atof( token ); + token = COM_ParseExt( &pString, qfalse ); + if ( token[0] ) { + facetarget = token; + } + } + } + + if ( moverange == -999 ) { + moverange = 200; + } + + if ( moverange != 0 ) { // default to 200 if no range given + if ( moverange > 0 ) { + dist = Distance( cs->bs->origin, cs->castScriptStatus.scriptWaitPos ); + // if we are able to move, and have an enemy + if ( ( cs->castScriptStatus.scriptWaitMovetime < level.time ) + && ( cs->bs->enemy >= 0 ) ) { + + // if we can attack them, or they can't attack us, stay here + // TTimo gcc: suggest parentheses around && within || + if ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) + || ( !AICast_EntityVisible( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qfalse ) + && !AICast_CheckAttack( AICast_GetCastState( cs->bs->enemy ), cs->entityNum, qfalse ) ) ) { + cs->castScriptStatus.scriptNoMoveTime = level.time + 200; + } + + } + // if outside range, move towards waitPos + if ( ( ( cs->castScriptStatus.scriptWaitMovetime > level.time ) && ( dist > 32 ) ) || ( dist > moverange ) ) { + cs->castScriptStatus.scriptNoMoveTime = 0; + AICast_MoveToPos( cs, cs->castScriptStatus.scriptWaitPos, 0 ); + if ( dist > 64 ) { + cs->castScriptStatus.scriptWaitMovetime = level.time + 600; + } + } else + // if we are reloading, look for somewhere to hide + if ( cs->castScriptStatus.scriptWaitHideTime > level.time || cs->bs->cur_ps.weaponTime > 500 ) { + if ( cs->castScriptStatus.scriptWaitHideTime < level.time ) { + // look for a hide pos within the wait range + + } + cs->castScriptStatus.scriptWaitHideTime = level.time + 500; + } + } + } else { + cs->castScriptStatus.scriptNoMoveTime = cs->castScriptStatus.castScriptStackChangeTime + duration; + } + + // do we have a facetarget ? + if ( facetarget ) { // yes we do + // find this targetname + ent = G_Find( NULL, FOFS( targetname ), facetarget ); + if ( !ent ) { + ent = AICast_FindEntityForName( facetarget ); + if ( !ent ) { + G_Error( "AI Scripting: wait cannot find targetname \"%s\"\n", token ); + } + } + // set the view angle manually + BG_EvaluateTrajectory( &ent->s.pos, level.time, org ); + VectorSubtract( org, cs->bs->origin, vec ); + VectorNormalize( vec ); + vectoangles( vec, cs->bs->ideal_viewangles ); + } + + return ( cs->castScriptStatus.castScriptStackChangeTime + duration < level.time ); +} + +/* +================= +AICast_ScriptAction_Trigger + + syntax: trigger + + Calls the specified trigger for the given ai character +================= +*/ +qboolean AICast_ScriptAction_Trigger( cast_state_t *cs, char *params ) { + gentity_t *ent; + char *pString, *token; + int oldId; + + // get the cast name + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI scripting: trigger must have a name and an identifier\n" ); + } + + ent = AICast_FindEntityForName( token ); + if ( !ent ) { + ent = G_Find( &g_entities[MAX_CLIENTS], FOFS( scriptName ), token ); + if ( !ent ) { + if ( trap_Cvar_VariableIntegerValue( "developer" ) ) { + G_Printf( "AI Scripting: can't find AI cast with \"ainame\" = \"%s\"\n", params ); + } + return qtrue; + } + } + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI scripting: trigger must have a name and an identifier\n" ); + } + + oldId = cs->castScriptStatus.scriptId; + if ( ent->client ) { + AICast_ScriptEvent( AICast_GetCastState( ent->s.number ), "trigger", token ); + } else { + G_Script_ScriptEvent( ent, "trigger", token ); + } + + // if the script changed, return false so we don't muck with it's variables + return ( oldId == cs->castScriptStatus.scriptId ); +} + +/* +=================== +AICast_ScriptAction_FollowCast + + syntax: followcast +=================== +*/ +qboolean AICast_ScriptAction_FollowCast( cast_state_t *cs, char *params ) { + gentity_t *ent; + + // find the cast/player with the given "name" + ent = AICast_FindEntityForName( params ); + if ( !ent ) { + G_Error( "AI Scripting: can't find AI cast with \"ainame\" = \"%s\"\n", params ); + } + + AIFunc_ChaseGoalStart( cs, ent->s.number, 64, qtrue ); + + return qtrue; +}; + +/* +================ +AICast_ScriptAction_PlaySound + + syntax: playsound + + Currently only allows playing on the VOICE channel, unless you use a sound script (yay) +================ +*/ +qboolean AICast_ScriptAction_PlaySound( cast_state_t *cs, char *params ) { + if ( !params ) { + G_Error( "AI Scripting: syntax error\n\nplaysound \n" ); + } + + G_AddEvent( &g_entities[cs->bs->entitynum], EV_GENERAL_SOUND, G_SoundIndex( params ) ); + + // assume we are talking + cs->aiFlags |= AIFL_TALKING; + + // randomly choose idle animation + if ( cs->aiFlags & AIFL_STAND_IDLE2 ) { + if ( cs->lastEnemy < 0 && cs->aiFlags & AIFL_TALKING ) { + g_entities[cs->entityNum].client->ps.eFlags |= EF_STAND_IDLE2; + } else { + g_entities[cs->entityNum].client->ps.eFlags &= ~EF_STAND_IDLE2; + } + } + + return qtrue; +} + +/* +================= +AICast_ScriptAction_NoAttack + + syntax: noattack +================= +*/ +qboolean AICast_ScriptAction_NoAttack( cast_state_t *cs, char *params ) { + if ( !params ) { + G_Error( "AI Scripting: syntax error\n\nnoattack \n" ); + } + + cs->castScriptStatus.scriptNoAttackTime = level.time + atoi( params ); + + return qtrue; +} + +/* +================= +AICast_ScriptAction_Attack + + syntax: attack [ainame] + + Resumes attacking after a noattack was issued + + if ainame is given, we will attack only that entity as long as they are alive +================= +*/ +qboolean AICast_ScriptAction_Attack( cast_state_t *cs, char *params ) { + gentity_t *ent; + + cs->castScriptStatus.scriptNoAttackTime = 0; + + // if we have specified an aiName, then we should attack only this person + if ( params ) { + ent = AICast_FindEntityForName( params ); + if ( !ent ) { + G_Error( "AI Scripting: \"attack\" command unable to find aiName \"%s\"", params ); + } + cs->castScriptStatus.scriptAttackEnt = ent->s.number; + cs->bs->enemy = ent->s.number; + } else { + cs->castScriptStatus.scriptAttackEnt = -1; + } + + return qtrue; +} + +/* +================= +AICast_ScriptAction_PlayAnim + + syntax: playanim [legs/torso/both] + + NOTE: any new animations that are needed by the scripting system, will need to be added here +================= +*/ +qboolean AICast_ScriptAction_PlayAnim( cast_state_t *cs, char *params ) { + char *pString, *token, tokens[3][MAX_QPATH]; + int i, endtime, duration; + gclient_t *client; + + pString = params; + + client = &level.clients[cs->entityNum]; + + if ( level.animScriptData.modelInfo[level.animScriptData.clientModels[cs->entityNum] - 1].version > 1 ) { // new (scripted) model + + // read the name + token = COM_ParseExt( &pString, qfalse ); + if ( !token || !token[0] ) { + G_Error( "AI Scripting: syntax error\n\nplayanim \n" ); + } + Q_strncpyz( tokens[0], token, sizeof( tokens[0] ) ); + Q_strlwr( tokens[0] ); + + // read the body part + token = COM_ParseExt( &pString, qfalse ); + if ( !token || !token[0] ) { + G_Error( "AI Scripting: syntax error\n\nplayanim \n" ); + } + Q_strncpyz( tokens[1], token, sizeof( tokens[1] ) ); + Q_strlwr( tokens[1] ); + + cs->scriptAnimTime = level.time; + + if ( cs->castScriptStatus.scriptFlags & SFL_FIRST_CALL ) { + // first time in here, play the anim + BG_PlayAnimName( &( client->ps ), tokens[0], BG_IndexForString( tokens[1], animBodyPartsStr, qfalse ), qtrue, qfalse, qtrue ); + if ( !strcmp( tokens[1], "torso" ) ) { + cs->scriptAnimNum = client->ps.torsoAnim & ~ANIM_TOGGLEBIT; + } else { + cs->scriptAnimNum = client->ps.legsAnim & ~ANIM_TOGGLEBIT; + } + } else { + // wait for the anim to stop playing + if ( ( cs->castScriptStatus.castScriptStackChangeTime != level.time ) && ( client->ps.legsTimer < 250 ) && ( client->ps.torsoTimer < 250 ) ) { + return qtrue; + } + } + + return qfalse; + + } else { // old model + + for ( i = 0; i < 3; i++ ) { + token = COM_ParseExt( &pString, qfalse ); + if ( !token || !token[0] ) { + //G_Error("AI Scripting: syntax error\n\nplayanim [legs/torso/both]\n"); + G_Printf( "AI Scripting: syntax error\n\nplayanim \n" ); + return qtrue; + } else { + Q_strncpyz( tokens[i], token, sizeof( tokens[i] ) ); + } + } + + Q_strlwr( tokens[2] ); + + endtime = cs->castScriptStatus.castScriptStackChangeTime + atoi( tokens[1] ); + duration = endtime - level.time + 200; // so animations don't run out before starting a next animation + if ( duration > 2000 ) { + duration = 2000; + } + + cs->scriptAnimTime = level.time; + + if ( duration < 200 ) { + return qtrue; // done playing animation + + } + // call the anims directly based on the animation token + + //if (cs->castScriptStatus.castScriptStackChangeTime == level.time) { + for ( i = 0; i < MAX_ANIMATIONS; i++ ) { + if ( !Q_strcasecmp( tokens[0], animStrings[i] ) ) { + if ( !Q_strcasecmp( tokens[2],"torso" ) ) { + if ( ( client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) != i ) { + client->ps.torsoAnim = ( ( client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | i; + } + client->ps.torsoTimer = duration; + } else if ( !Q_strcasecmp( tokens[2],"legs" ) ) { + if ( ( client->ps.legsAnim & ~ANIM_TOGGLEBIT ) != i ) { + client->ps.legsAnim = ( ( client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | i; + } + client->ps.legsTimer = duration; + } else if ( !Q_strcasecmp( tokens[2],"both" ) ) { + if ( ( client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) != i ) { + client->ps.torsoAnim = ( ( client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | i; + } + client->ps.torsoTimer = duration; + if ( ( client->ps.legsAnim & ~ANIM_TOGGLEBIT ) != i ) { + client->ps.legsAnim = ( ( client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | i; + } + client->ps.legsTimer = duration; + } else { + G_Printf( "AI Scripting: syntax error\n\nplayanim \n" ); + } + break; + } + } + if ( i == MAX_ANIMATIONS ) { + G_Printf( "AI Scripting: playanim has unknown or invalid animation \"%s\"\n", tokens[0] ); + } + //} + + if ( !strcmp( tokens[2], "torso" ) ) { + cs->scriptAnimNum = client->ps.torsoAnim & ~ANIM_TOGGLEBIT; + } else { + cs->scriptAnimNum = client->ps.legsAnim & ~ANIM_TOGGLEBIT; + } + + if ( cs->castScriptStatus.scriptNoMoveTime < level.time + 300 ) { + cs->castScriptStatus.scriptNoMoveTime = level.time + 300; + } + if ( cs->castScriptStatus.scriptNoAttackTime < level.time + 300 ) { + cs->castScriptStatus.scriptNoAttackTime = level.time + 300; + } + return qfalse; + } +}; + +/* +================= +AICast_ScriptAction_ClearAnim + + stops any animation that is currently playing +================= +*/ +qboolean AICast_ScriptAction_ClearAnim( cast_state_t *cs, char *params ) { + gclient_t *client; + + client = &level.clients[cs->entityNum]; + + client->ps.torsoTimer = 0; + client->ps.legsTimer = 0; + + return qtrue; +} + +/* +================= +AICast_ScriptAction_SetAmmo + + syntax: setammo +================= +*/ +qboolean AICast_ScriptAction_SetAmmo( cast_state_t *cs, char *params ) { + char *pString, *token; + int weapon; + int i; + + pString = params; + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI Scripting: setammo without ammo identifier\n" ); + } + + weapon = WP_NONE; + + for ( i = 1; bg_itemlist[i].classname; i++ ) + { + //----(SA) first try the name they see in the editor, then the pickup name + if ( !Q_strcasecmp( token, bg_itemlist[i].classname ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + + if ( !Q_strcasecmp( token, bg_itemlist[i].pickup_name ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + } + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI Scripting: setammo without ammo count\n" ); + } + + if ( weapon != WP_NONE ) { + // give them the ammo + +//----(SA) // trying this with Add_Ammo() again so automatic stuff happens automatically + Add_Ammo( &g_entities[cs->entityNum], weapon, atoi( token ), qtrue ); +// g_entities[cs->entityNum].client->ps.ammo[BG_FindAmmoForWeapon(weapon)] = atoi(token); +// Fill_Clip (&g_entities[cs->entityNum].client->ps, weapon); //----(SA) added (Add_Ammo would do the same, but this leaves more in the hands of the ai) +//----(SA) end + + } else { + +// G_Printf( "--SCRIPTER WARNING-- AI Scripting: setammo: unknown ammo \"%s\"", params ); + return qfalse; // (SA) temp as scripts transition to new names +// G_Error( "AI Scripting: setammo: unknown ammo \"%s\"", params ); + + } + + return qtrue; +}; + +/* +================= +AICast_ScriptAction_SetClip + + syntax: setclip + +================= +*/ +qboolean AICast_ScriptAction_SetClip( cast_state_t *cs, char *params ) { + char *pString, *token; + int weapon; + int i; + + pString = params; + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI Scripting: setclip without weapon identifier\n" ); + } + + weapon = WP_NONE; + + for ( i = 1; bg_itemlist[i].classname; i++ ) + { + //----(SA) first try the name they see in the editor, then the pickup name + if ( !Q_strcasecmp( token, bg_itemlist[i].classname ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + + if ( !Q_strcasecmp( token, bg_itemlist[i].pickup_name ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + } + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI Scripting: setclip without ammo count\n" ); + } + + if ( weapon != WP_NONE ) { + + int spillover = atoi( token ) - ammoTable[weapon].maxclip; + + if ( spillover > 0 ) { + // there was excess, put it in storage and fill the clip + g_entities[cs->entityNum].client->ps.ammo[BG_FindAmmoForWeapon( weapon )] += spillover; + g_entities[cs->entityNum].client->ps.ammoclip[BG_FindClipForWeapon( weapon )] = ammoTable[weapon].maxclip; + } else { + // set the clip amount to the exact specified value + g_entities[cs->entityNum].client->ps.ammoclip[weapon] = atoi( token ); + } + + } else { +// G_Printf( "--SCRIPTER WARNING-- AI Scripting: setclip: unknown weapon \"%s\"", params ); + return qfalse; // (SA) temp as scripts transition to new names +// G_Error( "AI Scripting: setclip: unknown weapon \"%s\"", params ); + } + + return qtrue; +}; + +/* +================= +AICast_ScriptAction_SelectWeapon + + syntax: selectweapon +================= +*/ +qboolean AICast_ScriptAction_SelectWeapon( cast_state_t *cs, char *params ) { + int weapon; + int i; + + weapon = WP_NONE; + + for ( i = 1; bg_itemlist[i].classname; i++ ) + { + //----(SA) first try the name they see in the editor, then the pickup name + if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + + if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + } + + if ( weapon != WP_NONE ) { + if ( cs->bs ) { + cs->bs->weaponnum = weapon; + } + cs->castScriptStatus.scriptFlags |= SFL_NOCHANGEWEAPON; + + g_entities[cs->entityNum].client->ps.weapon = weapon; + g_entities[cs->entityNum].client->ps.weaponstate = WEAPON_READY; + + if ( !cs->aiCharacter ) { // only do this for player + g_entities[cs->entityNum].client->ps.weaponTime = 500; // (SA) HACK: FIXME: TODO: delay to catch initial weapon reload + + } + } else { +// G_Printf( "--SCRIPTER WARNING-- AI Scripting: selectweapon: unknown weapon \"%s\"", params ); + return qfalse; // (SA) temp as scripts transition to new names +// G_Error( "AI Scripting: selectweapon: unknown weapon \"%s\"", params ); + } + + return qtrue; +}; + + + +//----(SA) added +/* +============== +AICast_ScriptAction_GiveArmor + + syntax: givearmor + + will probably be more like: + syntax: givearmor +============== +*/ +qboolean AICast_ScriptAction_GiveArmor( cast_state_t *cs, char *params ) { + int i; + // TTimo unused +// gentity_t *ent=&g_entities[cs->entityNum]; + gitem_t *item = 0; + + for ( i = 1; bg_itemlist[i].classname; i++ ) { + //----(SA) first try the name they see in the editor, then the pickup name + if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) { + item = &bg_itemlist[i]; + } + + if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) { + item = &bg_itemlist[i]; + } + } + + if ( !item ) { // item not found + G_Error( "AI Scripting: givearmor%s, unknown item", params ); + } + + if ( item->giType == IT_ARMOR ) { + g_entities[cs->entityNum].client->ps.stats[STAT_ARMOR] += item->quantity; + } + + return qtrue; +} +//----(SA) end + + + +/* +================= +AICast_ScriptAction_GiveWeapon + + syntax: giveweapon +================= +*/ +qboolean AICast_ScriptAction_GiveWeapon( cast_state_t *cs, char *params ) { + int weapon; + int i; + gentity_t *ent = &g_entities[cs->entityNum]; + + weapon = WP_NONE; + + for ( i = 1; bg_itemlist[i].classname; i++ ) + { + //----(SA) first try the name they see in the editor, then the pickup name + if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + + if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) { + weapon = bg_itemlist[i].giTag; + } + } + + if ( weapon != WP_NONE ) { + COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, weapon ); + +//----(SA) some weapons always go together (and they share a clip, so this is okay) + if ( weapon == WP_GARAND ) { + COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_SNOOPERSCOPE ); + } + if ( weapon == WP_SNOOPERSCOPE ) { + COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_GARAND ); + } + if ( weapon == WP_FG42 ) { + COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_FG42SCOPE ); + } + if ( weapon == WP_BAR ) { + COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_BAR2 ); + } + if ( weapon == WP_DYNAMITE ) { + COM_BitSet( g_entities[cs->entityNum].client->ps.weapons, WP_DYNAMITE2 ); + } +//----(SA) end + + // monsters have full ammo for their attacks + // knife gets infinite ammo too + if ( !Q_strncasecmp( params, "monsterattack", 13 ) || weapon == WP_KNIFE || weapon == WP_KNIFE2 ) { + g_entities[cs->entityNum].client->ps.ammo[BG_FindAmmoForWeapon( weapon )] = 999; + Fill_Clip( &g_entities[cs->entityNum].client->ps, weapon ); //----(SA) added + } + // conditional flags + if ( ent->aiCharacter == AICHAR_ZOMBIE ) { + if ( COM_BitCheck( ent->client->ps.weapons, WP_MONSTER_ATTACK1 ) ) { + cs->aiFlags |= AIFL_NO_FLAME_DAMAGE; + SET_FLAMING_ZOMBIE( ent->s, 1 ); + } + } + } else { + G_Error( "AI Scripting: giveweapon %s, unknown weapon", params ); + } + + return qtrue; +}; + +/* +================= +AICast_ScriptAction_TakeWeapon + + syntax: takeweapon +================= +*/ +qboolean AICast_ScriptAction_TakeWeapon( cast_state_t *cs, char *params ) { + int weapon; + int i; + + weapon = WP_NONE; + + if ( !Q_stricmp( params, "all" ) ) { + + // clear out all weapons + memset( g_entities[cs->entityNum].client->ps.weapons, 0, sizeof( g_entities[cs->entityNum].client->ps.weapons ) ); + memset( g_entities[cs->entityNum].client->ps.ammo, 0, sizeof( g_entities[cs->entityNum].client->ps.ammo ) ); + + } else { + + for ( i = 1; bg_itemlist[i].classname; i++ ) + { + //----(SA) first try the name they see in the editor, then the pickup name + if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + + if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) { + weapon = bg_itemlist[i].giTag; + break; + } + } + + if ( weapon != WP_NONE ) { + qboolean clear; + // + COM_BitClear( g_entities[cs->entityNum].client->ps.weapons, weapon ); + // also remove the ammo for this weapon + // but first make sure we dont have any other weapons that use the same ammo + clear = qtrue; + for ( i = 0; i < WP_NUM_WEAPONS; i++ ) { + if ( BG_FindAmmoForWeapon( weapon ) != BG_FindAmmoForWeapon( i ) ) { + continue; + } + if ( COM_BitCheck( g_entities[cs->entityNum].client->ps.weapons, i ) ) { + clear = qfalse; + } + } + if ( clear ) { +// (SA) temp only. commented out for pistol guys in escape1 +// g_entities[cs->entityNum].client->ps.ammo[BG_FindAmmoForWeapon(weapon)] = 0; + } + } else { + G_Error( "AI Scripting: takeweapon %s, unknown weapon", params ); + } + + } + + if ( !g_entities[cs->entityNum].client->ps.weapons ) { + if ( cs->bs ) { + cs->bs->weaponnum = WP_NONE; + } else { + g_entities[cs->entityNum].client->ps.weapon = WP_NONE; + } + } + + return qtrue; +}; + + + +//----(SA) added + +/* +============== +AICast_ScriptAction_GiveInventory +============== +*/ +qboolean AICast_ScriptAction_GiveInventory( cast_state_t *cs, char *params ) { + int i; + // TTimo unused +// gentity_t *ent=&g_entities[cs->entityNum]; + gitem_t *item = 0; + + for ( i = 1; bg_itemlist[i].classname; i++ ) { + //----(SA) first try the name they see in the editor, then the pickup name + if ( !Q_strcasecmp( params, bg_itemlist[i].classname ) ) { + item = &bg_itemlist[i]; + } + + if ( !Q_strcasecmp( params, bg_itemlist[i].pickup_name ) ) { + item = &bg_itemlist[i]; + } + } + + if ( !item ) { // item not found + G_Error( "AI Scripting: giveinventory %s, unknown item", params ); + } + + if ( item->giType == IT_KEY ) { + g_entities[cs->entityNum].client->ps.stats[STAT_KEYS] |= ( 1 << item->giTag ); + } else if ( item->giType == IT_HOLDABLE ) { + // (SA) TODO + } + + return qtrue; +}; + + +//----(SA) end + + + +/* +================= +AICast_ScriptAction_Movetype + + syntax: movetype + + Sets this character's movement type, will exist until another movetype command is called +================= +*/ +qboolean AICast_ScriptAction_Movetype( cast_state_t *cs, char *params ) { + if ( !Q_strcasecmp( params, "walk" ) ) { + cs->movestate = MS_WALK; + cs->movestateType = MSTYPE_PERMANENT; + return qtrue; + } + if ( !Q_strcasecmp( params, "run" ) ) { + cs->movestate = MS_RUN; + cs->movestateType = MSTYPE_PERMANENT; + return qtrue; + } + if ( !Q_strcasecmp( params, "crouch" ) ) { + cs->movestate = MS_CROUCH; + cs->movestateType = MSTYPE_PERMANENT; + return qtrue; + } + if ( !Q_strcasecmp( params, "default" ) ) { + cs->movestate = MS_DEFAULT; + cs->movestateType = MSTYPE_NONE; + return qtrue; + } + + return qtrue; +} +/* +================= +AICast_ScriptAction_AlertEntity + + syntax: alertentity +================= +*/ +qboolean AICast_ScriptAction_AlertEntity( cast_state_t *cs, char *params ) { + gentity_t *ent; + + if ( !params || !params[0] ) { + G_Error( "AI Scripting: alertentity without targetname\n" ); + } + + // find this targetname + ent = G_Find( NULL, FOFS( targetname ), params ); + if ( !ent ) { + ent = G_Find( NULL, FOFS( aiName ), params ); // look for an AI + if ( !ent || !ent->client ) { // accept only AI for aiName check + G_Error( "AI Scripting: alertentity cannot find targetname \"%s\"\n", params ); + } + } + + // call this entity's AlertEntity function + if ( !ent->AIScript_AlertEntity ) { + if ( !ent->client && ent->use && !Q_stricmp( ent->classname, "ai_trigger" ) ) { + ent->use( ent, NULL, NULL ); + return qtrue; + } + + if ( aicast_debug.integer ) { + G_Printf( "AI Scripting: alertentity \"%s\" (classname = %s) doesn't have an \"AIScript_AlertEntity\" function\n", params, ent->classname ); + } + //G_Error( "AI Scripting: alertentity \"%s\" (classname = %s) doesn't have an \"AIScript_AlertEntity\" function\n", params, ent->classname ); + return qtrue; + } + + ent->AIScript_AlertEntity( ent ); + + return qtrue; +} + +/* +================= +AICast_ScriptAction_SaveGame + + NOTE: only use this command in "player" scripts, not for AI + + syntax: savegame +================= +*/ +qboolean AICast_ScriptAction_SaveGame( cast_state_t *cs, char *params ) { + char *pString, *saveName; + pString = params; + + if ( cs->bs ) { + G_Error( "AI Scripting: savegame attempted on a non-player" ); + } + +//----(SA) check for parameter + saveName = COM_ParseExt( &pString, qfalse ); +// if (!saveName[0]) +// G_SaveGame( NULL ); // save the default "current" savegame +// else +// G_SaveGame( saveName ); +//----(SA) end + + return qtrue; +} + +/* +================= +AICast_ScriptAction_FireAtTarget + + syntax: fireattarget [duration] +================= +*/ +qboolean AICast_ScriptAction_FireAtTarget( cast_state_t *cs, char *params ) { + gentity_t *ent; + vec3_t vec, org, src; + char *pString, *token; + float diff; + int i; + + pString = params; + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI Scripting: fireattarget without a targetname\n" ); + } + + if ( !cs->bs ) { + G_Error( "AI Scripting: fireattarget called for non-AI character\n" ); + } + + // find this targetname + ent = G_Find( NULL, FOFS( targetname ), token ); + if ( !ent ) { + ent = AICast_FindEntityForName( token ); + if ( !ent ) { + G_Error( "AI Scripting: fireattarget cannot find targetname/aiName \"%s\"\n", token ); + } + } + + // if this is our first call for this fireattarget, record the ammo count + if ( cs->castScriptStatus.castScriptStackChangeTime == level.time ) { + cs->lastWeaponFired = 0; + } + // make sure we don't move or shoot while turning to our target + if ( cs->castScriptStatus.scriptNoAttackTime < level.time ) { + cs->castScriptStatus.scriptNoAttackTime = level.time + 250; + } + // don't move while firing at all + //if (cs->castScriptStatus.scriptNoMoveTime < level.time) { + cs->castScriptStatus.scriptNoMoveTime = level.time + 250; + //} + // set the view angle manually + BG_EvaluateTrajectory( &ent->s.pos, level.time, org ); + VectorCopy( cs->bs->origin, src ); + src[2] += cs->bs->cur_ps.viewheight; + VectorSubtract( org, src, vec ); + VectorNormalize( vec ); + vectoangles( vec, cs->bs->ideal_viewangles ); + for ( i = 0; i < 2; i++ ) { + diff = abs( AngleDifference( cs->bs->cur_ps.viewangles[i], cs->bs->ideal_viewangles[i] ) ); + if ( VectorCompare( vec3_origin, ent->s.pos.trDelta ) ) { + if ( diff ) { + return qfalse; // not facing yet + } + } else { + if ( diff > 25 ) { // allow some slack when target is moving + return qfalse; + } + } + } + + // force fire + trap_EA_Attack( cs->bs->client ); + // + cs->bs->flags |= BFL_ATTACKED; + // + // if we haven't fired yet + if ( !cs->lastWeaponFired ) { + return qfalse; + } + // + // do we need to stay and fire for a duration? + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + return qtrue; // no need to wait around + } + // only return true if we've been firing for long enough + return ( ( cs->castScriptStatus.castScriptStackChangeTime + atoi( token ) ) < level.time ); +} + +/* +================= +AICast_ScriptAction_GodMode + + syntax: godmode +================= +*/ +qboolean AICast_ScriptAction_GodMode( cast_state_t *cs, char *params ) { + if ( !params || !params[0] ) { + G_Error( "AI Scripting: godmode requires an on/off specifier\n" ); + } + + if ( !Q_stricmp( params, "on" ) ) { + g_entities[cs->bs->entitynum].flags |= FL_GODMODE; + } else if ( !Q_stricmp( params, "off" ) ) { + g_entities[cs->bs->entitynum].flags &= ~FL_GODMODE; + } else { + G_Error( "AI Scripting: godmode requires an on/off specifier\n" ); + } + + return qtrue; +} + +/* +================= +AICast_ScriptAction_Accum + + syntax: accum + + Commands: + + accum inc + accum abort_if_less_than + accum abort_if_greater_than + accum abort_if_not_equal + accum abort_if_equal + accum set + accum random + accum bitset + accum bitreset + accum abort_if_bitset + accum abort_if_not_bitset +================= +*/ +qboolean AICast_ScriptAction_Accum( cast_state_t *cs, char *params ) { + char *pString, *token, lastToken[MAX_QPATH]; + int bufferIndex; + + pString = params; + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI Scripting: accum without a buffer index\n" ); + } + + bufferIndex = atoi( token ); + if ( bufferIndex >= MAX_SCRIPT_ACCUM_BUFFERS ) { + G_Error( "AI Scripting: accum buffer is outside range (0 - %i)\n", MAX_SCRIPT_ACCUM_BUFFERS ); + } + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI Scripting: accum without a command\n" ); + } + + Q_strncpyz( lastToken, token, sizeof( lastToken ) ); + token = COM_ParseExt( &pString, qfalse ); + + if ( !Q_stricmp( lastToken, "inc" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + cs->scriptAccumBuffer[bufferIndex] += atoi( token ); + } else if ( !Q_stricmp( lastToken, "abort_if_less_than" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( cs->scriptAccumBuffer[bufferIndex] < atoi( token ) ) { + // abort the current script + cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "abort_if_greater_than" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( cs->scriptAccumBuffer[bufferIndex] > atoi( token ) ) { + // abort the current script + cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "abort_if_not_equal" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( cs->scriptAccumBuffer[bufferIndex] != atoi( token ) ) { + // abort the current script + cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "abort_if_equal" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( cs->scriptAccumBuffer[bufferIndex] == atoi( token ) ) { + // abort the current script + cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "bitset" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + cs->scriptAccumBuffer[bufferIndex] |= ( 1 << atoi( token ) ); + } else if ( !Q_stricmp( lastToken, "bitreset" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + cs->scriptAccumBuffer[bufferIndex] &= ~( 1 << atoi( token ) ); + } else if ( !Q_stricmp( lastToken, "abort_if_bitset" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( cs->scriptAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) { + // abort the current script + cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "abort_if_not_bitset" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( !( cs->scriptAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) ) { + // abort the current script + cs->castScriptStatus.castScriptStackHead = cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "set" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + cs->scriptAccumBuffer[bufferIndex] = atoi( token ); + } else if ( !Q_stricmp( lastToken, "random" ) ) { + if ( !token[0] ) { + G_Error( "AI Scripting: accum %s requires a parameter\n", lastToken ); + } + cs->scriptAccumBuffer[bufferIndex] = rand() % atoi( token ); + } else { + G_Error( "AI Scripting: accum %s: unknown command\n", params ); + } + + return qtrue; +} + +/* +================= +AICast_ScriptAction_SpawnCast + + syntax: spawncast + + is the entity marker which has the position and angles of where we want the new + cast AI to spawn +================= +*/ +qboolean AICast_ScriptAction_SpawnCast( cast_state_t *cs, char *params ) { +// char *pString, *token; +// char *classname; +// gentity_t *targetEnt, *newCast; + + G_Error( "AI Scripting: spawncast is no longer functional. Use trigger_spawn instead.\n" ); + return qfalse; +/* + if (!params[0]) { + G_Error( "AI Scripting: spawncast without a classname\n" ); + } + + pString = params; + + token = COM_ParseExt( &pString, qfalse ); + if (!token[0]) { + G_Error( "AI Scripting: spawncast without a classname\n" ); + } + + classname = G_Alloc( strlen(token)+1 ); + Q_strncpyz( classname, token, strlen(token)+1 ); + + token = COM_ParseExt( &pString, qfalse ); + if (!token[0]) { + G_Error( "AI Scripting: spawncast without a targetname\n" ); + } + + targetEnt = G_Find( NULL, FOFS(targetname), token ); + if (!targetEnt) { + G_Error( "AI Scripting: cannot find targetname \"%s\"\n", token ); + } + + token = COM_ParseExt( &pString, qfalse ); + if (!token[0]) { + G_Error( "AI Scripting: spawncast without an ainame\n" ); + } + + newCast = G_Spawn(); + newCast->classname = classname; + VectorCopy( targetEnt->s.origin, newCast->s.origin ); + VectorCopy( targetEnt->s.angles, newCast->s.angles ); + newCast->aiName = G_Alloc( strlen(token)+1 ); + Q_strncpyz( newCast->aiName, token, strlen(token)+1 ); + + if (!G_CallSpawn( newCast )) { + G_Error( "AI Scripting: spawncast for unknown entity \"%s\"\n", newCast->classname ); + } + + return qtrue; +*/ +} + +/* +================= +AICast_ScriptAction_MissionFailed + + syntax: missionfailed +================= +*/ +qboolean AICast_ScriptAction_MissionFailed( cast_state_t *cs, char *params ) { + // todo!! (just kill the player for now) + gentity_t *player; + player = AICast_FindEntityForName( "player" ); + if ( player ) { + G_Damage( player, player, player, vec3_origin, vec3_origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE ); + } + + G_Printf( "Mission Failed...\n" ); // todo + + return qtrue; +} + +/* +================= +AICast_ScriptAction_MissionSuccess + + syntax: missionsuccess +================= +*/ +qboolean AICast_ScriptAction_MissionSuccess( cast_state_t *cs, char *params ) { + gentity_t *player; + + if ( !params || !params[0] ) { + G_Error( "AI Scripting: missionsuccess requires a mission_level identifier\n" ); + } + + player = AICast_FindEntityForName( "player" ); + // double check that they are still alive + if ( player->health <= 0 ) { + return qfalse; // hold the script here + + } + player->missionLevel = atoi( params ); + + G_Printf( "Mission Success!!!!\n" ); // todo + +// G_SaveGame( NULL ); + + return qtrue; +} + +/* +================= +AICast_ScriptAction_NoAIDamage + + syntax: noaidamage +================= +*/ +qboolean AICast_ScriptAction_NoAIDamage( cast_state_t *cs, char *params ) { + if ( !params || !params[0] ) { + G_Error( "AI Scripting: noaidamage requires an on/off specifier\n" ); + } + + if ( !Q_stricmp( params, "on" ) ) { + cs->castScriptStatus.scriptFlags |= SFL_NOAIDAMAGE; + } else if ( !Q_stricmp( params, "off" ) ) { + cs->castScriptStatus.scriptFlags &= ~SFL_NOAIDAMAGE; + } else { + G_Error( "AI Scripting: noaidamage requires an on/off specifier\n" ); + } + return qtrue; +} + +/* +================= +AICast_ScriptAction_Print + + syntax: print + + Mostly for debugging purposes +================= +*/ +qboolean AICast_ScriptAction_Print( cast_state_t *cs, char *params ) { + if ( !params || !params[0] ) { + G_Error( "AI Scripting: print requires some text\n" ); + } + + G_Printf( "(AI) %s-> %s\n", g_entities[cs->entityNum].aiName, params ); + return qtrue; +} + +/* +================= +AICast_ScriptAction_FaceTargetAngles + + syntax: facetargetangles + + The AI will face the same direction that the target entity is facing +================= +*/ +qboolean AICast_ScriptAction_FaceTargetAngles( cast_state_t *cs, char *params ) { + gentity_t *targetEnt; + + if ( !params || !params[0] ) { + G_Error( "AI Scripting: facetargetangles requires a targetname\n" ); + } + + targetEnt = G_Find( NULL, FOFS( targetname ), params ); + if ( !targetEnt ) { + G_Error( "AI Scripting: cannot find targetname \"%s\"\n", params ); + } + + VectorCopy( targetEnt->s.angles, cs->bs->ideal_viewangles ); + + return qtrue; +} + +/* +=================== +AICast_ScriptAction_ResetScript + + causes any currently running scripts to abort, in favour of the current script +=================== +*/ +qboolean AICast_ScriptAction_ResetScript( cast_state_t *cs, char *params ) { + gclient_t *client; + + client = &level.clients[cs->entityNum]; + + // stop any anim from playing + if ( client->ps.torsoTimer && ( client->ps.torsoTimer > ( level.time - cs->scriptAnimTime ) ) ) { + if ( ( client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) == cs->scriptAnimNum ) { + client->ps.torsoTimer = 0; + } + } + if ( client->ps.legsTimer && ( client->ps.legsTimer > ( level.time - cs->scriptAnimTime ) ) ) { + if ( ( client->ps.legsAnim & ~ANIM_TOGGLEBIT ) == cs->scriptAnimNum ) { + client->ps.legsTimer = 0; + } + } + + cs->castScriptStatus.scriptNoMoveTime = 0; + // stop following anything that we don't need to be following + cs->followEntity = -1; + if ( level.time == cs->castScriptStatus.castScriptStackChangeTime ) { + return qfalse; + } + + // make sure zoom is off + cs->aiFlags &= ~AIFL_ZOOMING; + + return qtrue; +} + +/* +=================== +AICast_ScriptAction_Mount + + syntax: mount + + Used to an AI to mount the MG42 +=================== +*/ +qboolean AICast_ScriptAction_Mount( cast_state_t *cs, char *params ) { + gentity_t *targetEnt, *ent; + vec3_t vec; + float dist; + + if ( !params || !params[0] ) { + G_Error( "AI Scripting: mount requires a targetname\n" ); + } + + targetEnt = G_Find( NULL, FOFS( targetname ), params ); + if ( !targetEnt ) { + G_Error( "AI Scripting: cannot find targetname \"%s\"\n", params ); + } + + VectorSubtract( targetEnt->r.currentOrigin, cs->bs->origin, vec ); + dist = VectorNormalize( vec ); + vectoangles( vec, cs->bs->ideal_viewangles ); + + if ( dist > 40 ) { + // walk towards it + trap_EA_Move( cs->entityNum, vec, 80 ); + return qfalse; + } + + // if we are facing it, start holding activate + if ( fabs( cs->bs->ideal_viewangles[YAW] - cs->bs->viewangles[YAW] ) < 10 ) { + ent = &g_entities[cs->entityNum]; + Cmd_Activate_f( ent ); + // did we mount it? + if ( ent->active && targetEnt->r.ownerNum == ent->s.number ) { + cs->mountedEntity = targetEnt->s.number; + AIFunc_BattleMG42Start( cs ); + return qtrue; + } + } + + return qfalse; +} + +/* +=================== +AICast_ScriptAction_Unmount + + syntax: unmount + + Stop using their current mounted entity +=================== +*/ +qboolean AICast_ScriptAction_Unmount( cast_state_t *cs, char *params ) { + gentity_t *ent; + + ent = &g_entities[cs->entityNum]; + + if ( !ent->active ) { + return qtrue; // nothing mounted, just skip this command + } + Cmd_Activate_f( ent ); + if ( !ent->active ) { + return qtrue; + } + // waiting to unmount still + return qfalse; +} + +/* +==================== +AICast_ScriptAction_SavePersistant + + syntax: savepersistant + + Saves out the data that should be retained between certain levels. Not + calling this routine before changing levels, is the equivalent of resetting + the player's inventory/health/etc. + + The is used to identify the next map we don't + accidentally read in persistant data that was intended for a different map. +==================== +*/ +qboolean AICast_ScriptAction_SavePersistant( cast_state_t *cs, char *params ) { +// G_SavePersistant( params ); + return qtrue; +} + +/* +==================== +AICast_ScriptAction_ChangeLevel + + syntax: changelevel [nostats] [persistant] + + Issues an spdevmap/spmap to the consol. Optionally add "persistant" if you want the player to + keep their inventory through the transition. +==================== +*/ +qboolean AICast_ScriptAction_ChangeLevel( cast_state_t *cs, char *params ) { + char *pch, *newstr, cmd[MAX_QPATH]; + + // if the player is dead, we can't change levels + if ( g_entities[0].health <= 0 ) { + return qtrue; + } + + // build the mission stats string + newstr = va( params ); + pch = strstr( newstr, " nostats" ); + if ( !pch ) { + int kills[2]; + int nazis[2]; + int monsters[2]; + int i; + gentity_t *ent; + + memset( cmd, 0, sizeof( cmd ) ); + Q_strcat( cmd, sizeof( cmd ), "s=" ); + + // count kills + kills[0] = kills[1] = 0; + nazis[0] = nazis[1] = 0; + monsters[0] = monsters[1] = 0; + for ( i = 0; i < aicast_maxclients; i++ ) { + ent = &g_entities[i]; + + if ( !ent->inuse ) { + continue; + } + + if ( !( ent->r.svFlags & SVF_CASTAI ) ) { + continue; + } + + if ( ent->aiTeam == AITEAM_ALLIES ) { + continue; + } + + kills[1]++; + + if ( ent->health <= 0 ) { + kills[0]++; + } + + if ( ent->aiTeam == AITEAM_NAZI ) { + nazis[1]++; + if ( ent->health <= 0 ) { + nazis[0]++; + } + } else { + monsters[1]++; + if ( ent->health <= 0 ) { + monsters[0]++; + } + } + } + Q_strcat( cmd, sizeof( cmd ), va( ",%i,%i,%i,%i,%i,%i", kills[0], kills[1], nazis[0], nazis[1], monsters[0], monsters[1] ) ); + + // time + Q_strcat( cmd, sizeof( cmd ), va( ",%i,%i,%i", ( ( cs->totalPlayTime / 1000 ) / 60 ) / 60, ( cs->totalPlayTime / 1000 ) / 60, ( cs->totalPlayTime / 1000 ) % 60 ) ); + + // secrets + Q_strcat( cmd, sizeof( cmd ), va( ",%i,%i", cs->secretsFound, numSecrets ) ); + + // attempts + Q_strcat( cmd, sizeof( cmd ), va( ",%i", cs->attempts ) ); + + trap_Cvar_Set( "g_missionStats", cmd ); + } + + // save persistant data if required + newstr = va( params ); + pch = strstr( newstr, " persistant" ); + if ( pch ) { + pch = strstr( newstr, " " ); + *pch = '\0'; +// G_SavePersistant( newstr ); + } + + // make sure we strip any params after the mapname + pch = strstr( newstr, " " ); + if ( pch ) { + *pch = '\0'; + } + + // wait for a key before clearing stats and loading client data/showing mission briefing + trap_Cvar_Set( "cl_waitForFire", "1" ); + + if ( g_cheats.integer ) { + Com_sprintf( cmd, MAX_QPATH, "spdevmap %s\n", newstr ); + } else { + Com_sprintf( cmd, MAX_QPATH, "spmap %s\n", newstr ); + } + + trap_SendConsoleCommand( EXEC_APPEND, cmd ); + + return qtrue; +} + +/* +================== +AICast_ScriptAction_FoundSecret +================== +*/ +qboolean AICast_ScriptAction_FoundSecret( cast_state_t *cs, char *params ) { + cs->secretsFound++; + return qtrue; +} + +/* +================== +AICast_ScriptAction_NoSight + + syntax: nosight +================== +*/ +qboolean AICast_ScriptAction_NoSight( cast_state_t *cs, char *params ) { + if ( !params ) { + G_Error( "AI Scripting: syntax error\n\nnosight \n" ); + } + + cs->castScriptStatus.scriptNoSightTime = level.time + atoi( params ); + + return qtrue; +} + +/* +================== +AICast_ScriptAction_Sight + + syntax: sight +================== +*/ +qboolean AICast_ScriptAction_Sight( cast_state_t *cs, char *params ) { + cs->castScriptStatus.scriptNoSightTime = 0; + return qtrue; +} + +/* +================== +AICast_ScriptAction_NoAvoid + + syntax: noavoid +================== +*/ +qboolean AICast_ScriptAction_NoAvoid( cast_state_t *cs, char *params ) { + cs->aiFlags |= AIFL_NOAVOID; + return qtrue; +} + +/* +================== +AICast_ScriptAction_Avoid + + syntax: avoid +================== +*/ +qboolean AICast_ScriptAction_Avoid( cast_state_t *cs, char *params ) { + cs->aiFlags &= ~AIFL_NOAVOID; + return qtrue; +} + +/* +================== +AICast_ScriptAction_Attrib + + syntax: attrib +================== +*/ +qboolean AICast_ScriptAction_Attrib( cast_state_t *cs, char *params ) { + char *pString, *token; + int i; + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI_Scripting: syntax: attrib " ); + } + + for ( i = 0; i < AICAST_MAX_ATTRIBUTES; i++ ) { + if ( !Q_strcasecmp( token, castAttributeStrings[i] ) ) { + // found a match, read in the value + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI_Scripting: syntax: attrib " ); + } + // set the attribute + cs->attributes[i] = atof( token ); + break; + } + } + + return qtrue; +} + +/* +================= +AICast_ScriptAction_DenyAction + + syntax: deny +================= +*/ +qboolean AICast_ScriptAction_DenyAction( cast_state_t *cs, char *params ) { + cs->aiFlags |= AIFL_DENYACTION; + return qtrue; +} + +/* +================= +AICast_ScriptAction_LightningDamage +================= +*/ +qboolean AICast_ScriptAction_LightningDamage( cast_state_t *cs, char *params ) { + Q_strlwr( params ); + if ( !Q_stricmp( params, "on" ) ) { + cs->aiFlags |= AIFL_ROLL_ANIM; // hijacking this since the player doesn't use it + } else { + cs->aiFlags &= ~AIFL_ROLL_ANIM; + } + return qtrue; +} + +/* +================= +AICast_ScriptAction_Headlook +================= +*/ +qboolean AICast_ScriptAction_Headlook( cast_state_t *cs, char *params ) { + char *pString, *token; + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI_Scripting: syntax: headlook " ); + } + Q_strlwr( token ); + + if ( !Q_stricmp( token, "on" ) ) { + cs->aiFlags &= ~AIFL_NO_HEADLOOK; + } else if ( !Q_stricmp( token, "off" ) ) { + cs->aiFlags |= AIFL_NO_HEADLOOK; + } else { + G_Error( "AI_Scripting: syntax: headlook " ); + } + + return qtrue; +} + +/* +================= +AICast_ScriptAction_BackupScript + + backs up the current state of the scripting, so we can restore it later and resume + were we left off (useful if player gets in our way) +================= +*/ +qboolean AICast_ScriptAction_BackupScript( cast_state_t *cs, char *params ) { + + if ( !( cs->castScriptStatus.scriptFlags & SFL_WAITING_RESTORE ) ) { + cs->castScriptStatusBackup = cs->castScriptStatusCurrent; + cs->castScriptStatus.scriptFlags |= SFL_WAITING_RESTORE; + } + + return qtrue; +} + +/* +================= +AICast_ScriptAction_RestoreScript + + restores the state of the scripting to the previous backup +================= +*/ +qboolean AICast_ScriptAction_RestoreScript( cast_state_t *cs, char *params ) { + + cs->castScriptStatus = cs->castScriptStatusBackup; + + // make sure we restart any goto's + cs->castScriptStatus.scriptGotoId = -1; + cs->castScriptStatus.scriptGotoEnt = -1; + + return qfalse; // dont continue scripting until next frame +} + +/* +================= +AICast_ScriptAction_StateType + + set the default state for this character + + currently only accepts "alert" since "relaxed" is the default +================= +*/ +qboolean AICast_ScriptAction_StateType( cast_state_t *cs, char *params ) { + + if ( !Q_stricmp( params, "alert" ) ) { + cs->aiState = AISTATE_ALERT; + } + + return qtrue; +} + +/* +================ +AICast_ScriptAction_KnockBack + + syntax: knockback [ON/OFF] +================ +*/ +qboolean AICast_ScriptAction_KnockBack( cast_state_t *cs, char *params ) { + + char *pString, *token; + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI_Scripting: syntax: knockback " ); + } + Q_strlwr( token ); + + if ( !Q_stricmp( token, "on" ) ) { + g_entities[cs->entityNum].flags &= ~FL_NO_KNOCKBACK; + } else if ( !Q_stricmp( token, "off" ) ) { + g_entities[cs->entityNum].flags |= FL_NO_KNOCKBACK; + } else { + G_Error( "AI_Scripting: syntax: knockback " ); + } + + return qtrue; + +} + +/* +================ +AICast_ScriptAction_Zoom + + syntax: zoom [ON/OFF] +================ +*/ +qboolean AICast_ScriptAction_Zoom( cast_state_t *cs, char *params ) { + + char *pString, *token; + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI_Scripting: syntax: zoom " ); + } + Q_strlwr( token ); + + // give them the inventory item + g_entities[cs->entityNum].client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); + + if ( !Q_stricmp( token, "on" ) ) { + cs->aiFlags |= AIFL_ZOOMING; + } else if ( !Q_stricmp( token, "off" ) ) { + cs->aiFlags &= ~AIFL_ZOOMING; + } else { + G_Error( "AI_Scripting: syntax: zoom " ); + } + + return qtrue; + +} + +//----(SA) added +/* +=================== +AICast_ScriptAction_StartCam + + syntax: startcam +=================== +*/ +qboolean ScriptStartCam( cast_state_t *cs, char *params, qboolean black ) { + char *pString, *token; + gentity_t *ent; + + ent = &g_entities[cs->entityNum]; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_Cam: filename parameter required\n" ); + } + + // turn off noclient flag + ent->r.svFlags &= ~SVF_NOCLIENT; + + // issue a start camera command to the client + trap_SendServerCommand( cs->entityNum, va( "startCam %s %d", token, (int)black ) ); + + return qtrue; +} + +qboolean AICast_ScriptAction_StartCam( cast_state_t *cs, char *params ) { + return ScriptStartCam( cs, params, qfalse ); +} +qboolean AICast_ScriptAction_StartCamBlack( cast_state_t *cs, char *params ) { + return ScriptStartCam( cs, params, qtrue ); +} + + +//----(SA) end + +/* +================= +AICast_ScriptAction_Parachute + + syntax: parachute [ON/OFF] +================= +*/ +qboolean AICast_ScriptAction_Parachute( cast_state_t *cs, char *params ) { + + char *pString, *token; + gentity_t *ent; + + ent = &g_entities[cs->entityNum]; + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI_Scripting: syntax: parachute " ); + } + Q_strlwr( token ); + + if ( !Q_stricmp( token, "on" ) ) { + ent->flags |= FL_PARACHUTE; + } else if ( !Q_stricmp( token, "off" ) ) { + ent->flags &= ~FL_PARACHUTE; + } else { + G_Error( "AI_Scripting: syntax: parachute " ); + } + + return qtrue; + +} + +/* +================= +AICast_ScriptAction_EntityScriptName +================= +*/ +qboolean AICast_ScriptAction_EntityScriptName( cast_state_t *cs, char *params ) { + trap_Cvar_Set( "g_scriptName", params ); + return qtrue; +} + + +/* +================= +AICast_ScriptAction_AIScriptName +================= +*/ +qboolean AICast_ScriptAction_AIScriptName( cast_state_t *cs, char *params ) { + trap_Cvar_Set( "ai_scriptName", params ); + return qtrue; +} + +/* +================= +AICast_ScriptAction_SetHealth +================= +*/ +qboolean AICast_ScriptAction_SetHealth( cast_state_t *cs, char *params ) { + if ( !params || !params[0] ) { + G_Error( "AI Scripting: sethealth requires a health value" ); + } + + g_entities[cs->entityNum].health = atoi( params ); + g_entities[cs->entityNum].client->ps.stats[STAT_HEALTH] = atoi( params ); + + return qtrue; +} + +/* +================= +AICast_ScriptAction_NoTarget + + syntax: notarget ON/OFF +================= +*/ +qboolean AICast_ScriptAction_NoTarget( cast_state_t *cs, char *params ) { + if ( !params || !params[0] ) { + G_Error( "AI Scripting: notarget requires ON or OFF as parameter" ); + } + + if ( !Q_strcasecmp( params, "ON" ) ) { + g_entities[cs->entityNum].flags |= FL_NOTARGET; + } else if ( !Q_strcasecmp( params, "OFF" ) ) { + g_entities[cs->entityNum].flags &= ~FL_NOTARGET; + } else { + G_Error( "AI Scripting: notarget requires ON or OFF as parameter" ); + } + + return qtrue; +} + +/* +================== +AICast_ScriptAction_Cvar +================== +*/ +qboolean AICast_ScriptAction_Cvar( cast_state_t *cs, char *params ) { + vmCvar_t cvar; + char *pString, *token; + char cvarName[MAX_QPATH]; + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI_Scripting: syntax: cvar " ); + } + Q_strncpyz( cvarName, token, sizeof( cvarName ) ); + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "AI_Scripting: syntax: cvar " ); + } + + trap_Cvar_Register( &cvar, cvarName, token, CVAR_ROM ); + // set it to make sure + trap_Cvar_Set( cvarName, token ); + return qtrue; +} diff --git a/src/game/ai_cast_script_ents.c b/src/game/ai_cast_script_ents.c new file mode 100644 index 0000000..4894228 --- /dev/null +++ b/src/game/ai_cast_script_ents.c @@ -0,0 +1,229 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_script_ents.c +// Function: Wolfenstein AI Character Scripting +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/*QUAKED ai_marker (1 0.5 0) (-18 -18 -24) (18 18 48) NODROP +AI marker + +NODROP means dont drop it to the ground + +"targetname" : identifier for this marker +*/ + +/* +============ +SP_ai_marker +============ +*/ +extern vec3_t playerMins, playerMaxs; +void SP_ai_marker( gentity_t *ent ) { + vec3_t dest; + trace_t tr; + vec3_t checkMins, checkMaxs; + + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + G_FreeEntity( ent ); + return; + } + +//----(SA) move the bounding box for the check in 1 unit on each side so they can butt up against a wall and not startsolid + VectorCopy( playerMins, checkMins ); + checkMins[0] += 1; + checkMins[1] += 1; + VectorCopy( playerMaxs, checkMaxs ); + checkMaxs[0] -= 1; + checkMaxs[1] -= 1; +//----(SA) end + + if ( !( ent->spawnflags & 1 ) ) { + // drop to floor + ent->r.currentOrigin[2] += 1.0; // fixes QErad -> engine bug? + VectorSet( dest, ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2] - 4096 ); + trap_Trace( &tr, ent->r.currentOrigin, checkMins, checkMaxs, dest, ent->s.number, MASK_PLAYERSOLID ); + + if ( tr.startsolid ) { + G_Printf( "WARNING: ai_marker (%s) in solid at %s\n", ent->targetname, vtos( ent->r.currentOrigin ) ); + return; + } + + G_SetOrigin( ent, tr.endpos ); + } +} + +/*QUAKED ai_effect (0.3 0.8 0.2) (-4 -4 -4) (4 4 4) +AI effect entity + +"ainame" is the name of the AI character that uses this entity for effects +*/ + +/* +============ +SP_ai_effect +============ +*/ +void ai_effect_think( gentity_t *ent ) { + gentity_t *targ; + + // find the client number that uses this entity + targ = AICast_FindEntityForName( ent->aiName ); + if ( !targ ) { + // keep waiting until they enter, if they never do, then we have no purpose, therefore no harm can be done + ent->think = ai_effect_think; + ent->nextthink = level.time + 200; + return; + //G_Error( "ai_effect with invalid aiName at %s\n", vtos(ent->s.origin) ); + } + + // make sure the clients can use this association + ent->s.otherEntityNum = targ->s.number; + + ent->s.eType = ET_AI_EFFECT; + G_SetOrigin( ent, ent->s.origin ); + trap_LinkEntity( ent ); + ent->r.svFlags |= SVF_BROADCAST; // make sure all clients are aware of this entity +} + +void SP_ai_effect( gentity_t *ent ) { + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + G_FreeEntity( ent ); + return; + } + + ent->think = ai_effect_think; + ent->nextthink = level.time + 500; +} + +//=========================================================== + +// the wait time has passed, so set back up for another activation +void AICast_trigger_wait( gentity_t *ent ) { + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void AICast_trigger_trigger( gentity_t *ent, gentity_t *activator ) { + if ( ent->nextthink ) { + return; // can't retrigger until the wait is over + } + + ent->activator = AICast_FindEntityForName( ent->aiName ); + if ( ent->activator ) { // they might be dead + // trigger the script event + AICast_ScriptEvent( AICast_GetCastState( ent->activator->s.number ), "trigger", ent->target ); + } + + if ( ent->wait > 0 ) { + ent->think = AICast_trigger_wait; + ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000; + } else { + // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = 0; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEntity; + } +} + +void AICast_Touch_Trigger( gentity_t *self, gentity_t *other, trace_t *trace ) { + if ( !other->client || ( other->r.svFlags & SVF_CASTAI ) ) { + return; + } + AICast_trigger_trigger( self, other ); +} + +/*QUAKED ai_trigger (1 0.5 0) ? Startoff +Triggered only by the player touching it +"wait" : Seconds between triggerings, -1 = one time only (default). +"ainame" : name of AI to target (use "player" for the.. player) +"target" : trigger identifier for that AI script +*/ +extern void InitTrigger( gentity_t *self ); + +void ai_trigger_activate( gentity_t *self ) { + if ( self->r.linked ) { + return; + } + + self->use = NULL; + self->AIScript_AlertEntity = NULL; + + self->touch = AICast_Touch_Trigger; + + InitTrigger( self ); + trap_LinkEntity( self ); +} + +void ai_trigger_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + ai_trigger_activate( self ); +} + +void SP_ai_trigger( gentity_t *ent ) { + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + G_FreeEntity( ent ); + return; + } + + G_SpawnFloat( "wait", "-1", &ent->wait ); + + if ( !ent->aiName ) { + G_Error( "ai_trigger without \"ainame\"\n" ); + } + if ( !ent->target ) { + G_Error( "ai_trigger without \"target\"\n" ); + } + + if ( ent->spawnflags & 1 ) { // TriggerSpawn + ent->AIScript_AlertEntity = ai_trigger_activate; + ent->use = ai_trigger_use; + trap_UnlinkEntity( ent ); + } else { + ai_trigger_activate( ent ); + } +} diff --git a/src/game/ai_cast_sight.c b/src/game/ai_cast_sight.c new file mode 100644 index 0000000..d66dab3 --- /dev/null +++ b/src/game/ai_cast_sight.c @@ -0,0 +1,774 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_sight.c +// Function: Wolfenstein AI Character Visiblity +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +Does sight checking for Cast AI's. +*/ + +static float aiStateFovScales[] = +{ + 1.0, // relaxed + 1.5, // query + 1.5, // alert + 2.0, // combat +}; + +/* +============== +AICast_InFieldOfVision +============== +*/ +qboolean AICast_InFieldOfVision( vec3_t viewangles, float fov, vec3_t angles ) { + int i; + float diff, angle; + + for ( i = 0; i < 2; i++ ) + { + angle = AngleMod( viewangles[i] ); + angles[i] = AngleMod( angles[i] ); + diff = angles[i] - angle; + if ( angles[i] > angle ) { + if ( diff > 180.0 ) { + diff -= 360.0; + } + } else + { + if ( diff < -180.0 ) { + diff += 360.0; + } + } + if ( diff > 0 ) { + if ( diff > fov * 0.5 ) { + return qfalse; + } + } else + { + if ( diff < -fov * 0.5 ) { + return qfalse; + } + } + } + return qtrue; +} + +/* +============== +AICast_VisibleFromPos +============== +*/ +qboolean AICast_VisibleFromPos( vec3_t srcpos, int srcnum, + vec3_t destpos, int destnum, qboolean updateVisPos ) { + int i, contents_mask, passent, hitent; + trace_t trace; + vec3_t start, end, middle, eye; + cast_state_t *cs = NULL; + int srcviewheight; + vec3_t destmins, destmaxs; + vec3_t right, vec; + qboolean inPVS; + + if ( g_entities[destnum].flags & FL_NOTARGET ) { + return qfalse; + } + + if ( srcnum < aicast_maxclients ) { + cs = AICast_GetCastState( srcnum ); + } + // + if ( cs && cs->bs ) { + srcviewheight = cs->bs->cur_ps.viewheight; + } else if ( g_entities[srcnum].client ) { + srcviewheight = g_entities[srcnum].client->ps.viewheight; + } else { + srcviewheight = 0; + } + // + VectorCopy( g_entities[destnum].r.mins, destmins ); + VectorCopy( g_entities[destnum].r.maxs, destmaxs ); + // + //calculate middle of bounding box + VectorAdd( destmins, destmaxs, middle ); + VectorScale( middle, 0.5, middle ); + VectorAdd( destpos, middle, middle ); + // calculate eye position + VectorCopy( srcpos, eye ); + eye[2] += srcviewheight; + // + // set the right vector + VectorSubtract( middle, eye, vec ); + VectorNormalize( vec ); + right[0] = vec[1]; + right[1] = vec[0]; + right[2] = 0; + // + inPVS = qfalse; + // + for ( i = 0; i < 5; i++ ) + { + if ( cs && updateVisPos ) { // if it's a grenade or something, PVS checks don't work very well + //if the point is not in potential visible sight + if ( i < 3 ) { // don't do PVS check for left/right checks + if ( !trap_InPVS( eye, middle ) ) { + continue; + } else { + inPVS = qtrue; + } + } else if ( !inPVS ) { + break; // wasn't in potential view in either of the previous tests + } // so don't bother doing left/right + } + // + contents_mask = MASK_SHOT & ~CONTENTS_BODY; // we can see anything that a bullet can pass through + passent = srcnum; + hitent = destnum; + VectorCopy( eye, start ); + VectorCopy( middle, end ); + //if the entity is in water, lava or slime + if ( trap_PointContents( middle, destnum ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { + contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); + } //end if + //if eye is in water, lava or slime + if ( trap_PointContents( eye, srcnum ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { + if ( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) { + passent = destnum; + hitent = srcnum; + VectorCopy( middle, start ); + VectorCopy( eye, end ); + } //end if + contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); + } //end if + //trace from start to end + trap_Trace( &trace, start, NULL, NULL, end, ENTITYNUM_NONE /*passent*/, contents_mask ); + //if water was hit + if ( trace.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) { + //if the water surface is translucent +// if (trace.surface.flags & (SURF_TRANS33|SURF_TRANS66)) + { + //trace through the water + contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); + trap_Trace( &trace, trace.endpos, NULL, NULL, end, passent, contents_mask ); + } //end if + } //end if + //if a full trace or the hitent was hit + if ( trace.fraction >= 1 || trace.entityNum == hitent ) { + return qtrue; + } + //check bottom and top of bounding box as well + if ( i == 0 ) { + middle[2] -= ( destmaxs[2] - destmins[2] ) * 0.5; + } else if ( i == 1 ) { + middle[2] += destmaxs[2] - destmins[2]; + } else if ( i == 2 ) { // right side + middle[2] -= ( destmaxs[2] - destmins[2] ) / 2.0; + VectorMA( eye, destmaxs[0] - 0.5, right, eye ); + } else if ( i == 3 ) { // left side + VectorMA( eye, -2.0 * ( destmaxs[0] - 0.5 ), right, eye ); + } + } //end for + + return qfalse; +} + +/* +============== +AICast_CheckVisibility +============== +*/ +qboolean AICast_CheckVisibility( gentity_t *srcent, gentity_t *destent ) { + vec3_t dir, entangles, middle, eye, viewangles; + cast_state_t *cs, *ocs; + float fov, dist; + int viewer, ent; + cast_visibility_t *vis; + orientation_t or; + + if ( destent->flags & FL_NOTARGET ) { + return qfalse; + } + + viewer = srcent->s.number; + ent = destent->s.number; + // + cs = AICast_GetCastState( viewer ); + ocs = AICast_GetCastState( ent ); + // + vis = &cs->vislist[ent]; + // + // if we heard them + /* + if ( (vis->lastcheck_timestamp) && + (ocs->lastWeaponFired) && + (ocs->lastWeaponFired >= vis->lastcheck_timestamp) && + (AICast_GetWeaponSoundRange( ocs->lastWeaponFiredWeaponNum ) > Distance( srcent->r.currentOrigin, ocs->lastWeaponFiredPos ))) { + return qtrue; + } + */ + // + // set the FOV + fov = cs->attributes[FOV] * aiStateFovScales[cs->aiState]; + if ( !fov ) { // assume it's a player, give them a generic fov + fov = 180; + } + if ( cs->aiFlags & AIFL_ZOOMING ) { + fov *= 0.8; + } + //calculate middle of bounding box + VectorAdd( destent->r.mins, destent->r.maxs, middle ); + VectorScale( middle, 0.5, middle ); + VectorAdd( destent->client->ps.origin, middle, middle ); + // calculate eye position + if ( srcent->r.svFlags & SVF_CASTAI ) { + if ( trap_GetTag( srcent->s.number, "tag_head", &or ) ) { + // use the actual direction the head is facing + vectoangles( or.axis[0], viewangles ); + // and the actual position of the head + VectorCopy( or.origin, eye ); + } else { + VectorCopy( srcent->client->ps.origin, eye ); + eye[2] += srcent->client->ps.viewheight; + VectorCopy( srcent->client->ps.viewangles, viewangles ); + } + } else { + VectorCopy( srcent->client->ps.origin, eye ); + eye[2] += srcent->client->ps.viewheight; + VectorCopy( srcent->client->ps.viewangles, viewangles ); + } + //check if entity is within field of vision + VectorSubtract( middle, eye, dir ); + vectoangles( dir, entangles ); + // + dist = VectorLength( dir ); + // + // alertness is visible range + if ( cs->bs && dist > cs->attributes[ALERTNESS] ) { + return qfalse; + } + // check FOV + if ( !AICast_InFieldOfVision( viewangles, fov, entangles ) ) { + return qfalse; + } + // + if ( !AICast_VisibleFromPos( srcent->client->ps.origin, srcent->s.number, destent->client->ps.origin, destent->s.number, qtrue ) ) { + return qfalse; + } + // + return qtrue; +} + +/* +============== +AICast_UpdateVisibility +============== +*/ +void AICast_UpdateVisibility( gentity_t *srcent, gentity_t *destent, qboolean shareVis, qboolean directview ) { + cast_visibility_t *vis, *ovis, *svis, oldvis; + cast_state_t *cs, *ocs; + qboolean shareRange; + int cnt, i; + + if ( destent->flags & FL_NOTARGET ) { + return; + } + + cs = AICast_GetCastState( srcent->s.number ); + ocs = AICast_GetCastState( destent->s.number ); + + if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) { + return; // absolutely no sight (or hear) information allowed + + } + shareRange = ( VectorDistance( srcent->client->ps.origin, destent->client->ps.origin ) < AIVIS_SHARE_RANGE ); + + vis = &cs->vislist[destent->s.number]; + + vis->chase_marker_count = 0; + + if ( aicast_debug.integer == 1 ) { + if ( !vis->visible_timestamp || vis->visible_timestamp < level.time - 5000 ) { + if ( directview ) { + G_Printf( "SIGHT (direct): %s sees %s\n", srcent->aiName, destent->aiName ); + } else { + G_Printf( "SIGHT (non-direct/audible): %s sees %s\n", srcent->aiName, destent->aiName ); + } + } + } + + // trigger the sight event + AICast_Sight( srcent, destent, vis->visible_timestamp ); + + // update the values + vis->lastcheck_timestamp = level.time; + vis->visible_timestamp = level.time; + VectorCopy( destent->client->ps.origin, vis->visible_pos ); + VectorCopy( destent->client->ps.velocity, vis->visible_vel ); + + // we may need to process this visibility at some point, even after they become not visible again + vis->flags |= AIVIS_PROCESS_SIGHTING; + + if ( directview ) { + vis->real_visible_timestamp = level.time; + VectorCopy( destent->client->ps.origin, vis->real_visible_pos ); + vis->real_update_timestamp = level.time; + } + + // if we are on fire, then run away from anything we see + if ( cs->attributes[AGGRESSION] < 1.0 && srcent->s.onFireEnd > level.time && ( !destent->s.number || cs->dangerEntityValidTime < level.time + 2000 ) && !( cs->aiFlags & AIFL_NO_FLAME_DAMAGE ) ) { + cs->dangerEntity = destent->s.number; + VectorCopy( destent->r.currentOrigin, cs->dangerEntityPos ); + cs->dangerEntityValidTime = level.time + 5000; + cs->dangerDist = 99999; + cs->dangerEntityTimestamp = level.time; + } + + // Look for reasons to make this character an enemy of ours + + // if they are an enemy and inside the detection radius, go hostile + if ( !( vis->flags & AIVIS_ENEMY ) && !AICast_SameTeam( cs, destent->s.number ) ) { + float idr; + + idr = cs->attributes[INNER_DETECTION_RADIUS]; + if ( cs->aiFlags & AIFL_ZOOMING ) { + idr *= 10; + } + if ( !( vis->flags & AIVIS_ENEMY ) && VectorDistance( vis->visible_pos, g_entities[cs->entityNum].r.currentOrigin ) < idr ) { + // RF, moved them over to AICast_ScanForEnemies() + //AICast_ScriptEvent( cs, "enemysight", destent->aiName ); + vis->flags |= AIVIS_ENEMY; + } + // if we are in (or above) ALERT mode, then we now know this is an enemy + else if ( cs->aiState >= AISTATE_ALERT ) { + // RF, moved them over to AICast_ScanForEnemies() + //AICast_ScriptEvent( cs, "enemysight", destent->aiName ); + vis->flags |= AIVIS_ENEMY; + } + } + + // if they are friendly, then we should help them out if they are in trouble + if ( AICast_SameTeam( cs, destent->s.number ) && ( srcent->aiTeam == AITEAM_ALLIES || srcent->aiTeam == AITEAM_NAZI ) ) { + // if they are dead, we should check them out + if ( destent->health <= 0 ) { + // if we haven't already checked them out + if ( !( vis->flags & AIVIS_INSPECTED ) ) { + vis->flags |= AIVIS_INSPECT; + } + // if they are mad, we should help, or at least act concerned + } else if ( cs->aiState < AISTATE_COMBAT && ocs->aiState >= AISTATE_COMBAT && ocs->bs && ( ocs->bs->enemy >= 0 ) ) { + // if we haven't already checked them out + if ( !( vis->flags & AIVIS_INSPECTED ) ) { + vis->flags |= AIVIS_INSPECT; + } + // if they are alert, we should also go alert + } else if ( cs->aiState < AISTATE_ALERT && ocs->aiState == AISTATE_ALERT && ocs->bs ) { + AICast_StateChange( cs, AISTATE_ALERT ); + } + } + + // if this is a friendly, then check them for hostile's that we currently haven't upgraded so + + if ( ( destent->health > 0 ) && + ( srcent->aiTeam == destent->aiTeam ) && // only share with exact same team, and non-neutrals + ( srcent->aiTeam != AITEAM_NEUTRAL ) ) { + ocs = AICast_GetCastState( destent->s.number ); + cnt = 0; + // + for ( i = 0; i < aicast_maxclients && cnt < level.numPlayingClients; i++ ) { + if ( !g_entities[i].inuse ) { + continue; + } + // + cnt++; + // + if ( i == srcent->s.number ) { + continue; + } + if ( i == destent->s.number ) { + continue; + } + // + ovis = &ocs->vislist[i]; + svis = &cs->vislist[i]; + // + // if we are close to the friendly, then we should share their visibility info + if ( destent->health > 0 && shareRange ) { + // if they have seen this character more recently than us, share + if ( ovis->visible_timestamp > svis->visible_timestamp ) { + // trigger an EVENT + + // trigger the sight event + AICast_Sight( srcent, destent, ovis->visible_timestamp ); + + // we may need to process this visibility at some point, even after they become not visible again + svis->flags |= AIVIS_PROCESS_SIGHTING; + + // if we are sharing information about an enemy, then trigger a scripted event + if ( !svis->real_visible_timestamp && ovis->real_visible_timestamp && ( ovis->flags & AIVIS_ENEMY ) ) { + // setup conditions + BG_UpdateConditionValue( ocs->entityNum, ANIM_COND_ENEMY_TEAM, g_entities[i].aiTeam, qfalse ); + // call the event + BG_AnimScriptEvent( &g_entities[ocs->entityNum].client->ps, ANIM_ET_INFORM_FRIENDLY_OF_ENEMY, qfalse, qfalse ); + } + oldvis = *svis; + // copy the whole structure + *svis = *ovis; + // minus the flags + svis->flags = oldvis.flags; + // check to see if we just made this character an enemy of ours + if ( ( cs->aiState == AISTATE_COMBAT ) && ( ovis->flags & AIVIS_ENEMY ) && !( oldvis.flags & AIVIS_ENEMY ) ) { + svis->flags |= AIVIS_ENEMY; + AICast_ScriptEvent( cs, "enemysight", g_entities[i].aiName ); + if ( !( cs->aiFlags & AIFL_DENYACTION ) ) { + G_AddEvent( srcent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].sightSoundScript ) ); + } + } + } + } else { + // if either of us haven't seen this character yet, then ignore it + if ( !svis->visible_timestamp || !ovis->visible_timestamp ) { + continue; + } + } + // + // if they have marked this character as hostile, then we should also + if ( ( cs->aiState == AISTATE_COMBAT ) && AICast_HostileEnemy( ocs, i ) && !AICast_HostileEnemy( cs, i ) ) { + AICast_ScriptEvent( cs, "enemysight", g_entities[i].aiName ); + if ( !( cs->aiFlags & AIFL_DENYACTION ) ) { + G_AddEvent( srcent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].sightSoundScript ) ); + } + svis->flags |= AIVIS_ENEMY; + } + } + } +} + +/* +============== +AICast_UpdateNonVisibility +============== +*/ +void AICast_UpdateNonVisibility( gentity_t *srcent, gentity_t *destent, qboolean directview ) { + cast_visibility_t *vis; + cast_state_t *cs; + + cs = AICast_GetCastState( srcent->s.number ); + + vis = &cs->vislist[destent->s.number]; + + // update the values + vis->lastcheck_timestamp = level.time; + vis->notvisible_timestamp = level.time; + + if ( directview ) { + vis->real_update_timestamp = level.time; + vis->real_notvisible_timestamp = level.time; + } + + // if enough time has passed, and still within chase period, drop a marker + if ( vis->chase_marker_count < MAX_CHASE_MARKERS ) { + if ( ( level.time - vis->visible_timestamp ) > ( vis->chase_marker_count + 1 ) * CHASE_MARKER_INTERVAL ) { + VectorCopy( destent->client->ps.origin, vis->chase_marker[vis->chase_marker_count] ); + vis->chase_marker_count++; + } + } +} + +/* +============== +AICast_SightSoundEvent + + this cast has made a sound which should be heard by others +============== +*/ +void AICast_SightSoundEvent( cast_state_t *cs, float range ) { + int i; + cast_state_t *ocs; + gentity_t *oent, *ent; + + ent = &g_entities[cs->entityNum]; + if ( ent->flags & FL_NOTARGET ) { + return; + } + for ( i = 0, ocs = caststates, oent = g_entities; i < level.maxclients; i++, ocs++, oent++ ) { + if ( !oent->inuse ) { + continue; + } + if ( oent->aiInactive ) { + continue; + } + if ( !ocs->bs ) { + continue; + } + if ( oent->health <= 0 ) { + continue; + } + if ( Distance( oent->r.currentOrigin, ent->r.currentOrigin ) > range * ocs->attributes[HEARING_SCALE] ) { + continue; + } + // they heard us + AICast_UpdateVisibility( oent, ent, qfalse, qfalse ); + } +} + +/* +============== +AICast_SightUpdate +============== +*/ +static int lastsrc = 0, lastdest = 0; + +void AICast_SightUpdate( int numchecks ) { + int count = 0, destcount, srccount; + int src, dest; + gentity_t *srcent, *destent; + cast_state_t *cs; + // TTimo unused +// static int lastNumUpdated; + cast_visibility_t *vis; + + src = 0; + dest = 0; + if ( numchecks < 5 ) { + numchecks = 5; + } + + if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) { + return; + } + + if ( saveGamePending ) { + return; + } + + // First, check all REAL clients, so sighting player is only effected by reaction_time, not + // effected by framerate also + for ( srccount = 0, src = 0, srcent = &g_entities[0]; + src < aicast_maxclients && srccount < level.numPlayingClients; + src++, srcent++ ) + { + if ( !srcent->inuse ) { + continue; + } + + srccount++; + + if ( srcent->aiInactive ) { + continue; + } + if ( srcent->health <= 0 ) { + continue; + } + if ( !( srcent->r.svFlags & SVF_CASTAI ) ) { // only source check AI Cast + continue; + } + + cs = AICast_GetCastState( src ); + + if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) { + continue; + } + + // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) + trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); + + for ( destcount = 0, dest = 0, destent = &g_entities[0]; + dest < aicast_maxclients && destcount < level.numPlayingClients; + dest++, destent++ ) + { + if ( !destent->inuse ) { + continue; + } + + destcount++; + + if ( destent->health <= 0 ) { + continue; + } + if ( destent->r.svFlags & SVF_CASTAI ) { // only dest check REAL clients + continue; + } + + if ( src == dest ) { + continue; + } + + vis = &cs->vislist[destent->s.number]; + + // if we saw them last frame, skip this test, so we only check initial sightings each frame + if ( vis->lastcheck_timestamp == vis->real_visible_timestamp ) { + continue; + } + + // if we recently checked this vis, skip + if ( vis->lastcheck_timestamp >= level.time - 100 ) { + continue; + } + + if ( vis->lastcheck_timestamp > level.time ) { + continue; // let the loadgame settle down + + } + // check for visibility + if ( !( destent->flags & FL_NOTARGET ) + && ( AICast_CheckVisibility( srcent, destent ) ) ) { + // record the sighting + AICast_UpdateVisibility( srcent, destent, qtrue, qtrue ); + } else // if (vis->lastcheck_timestamp == vis->real_update_timestamp) + { + AICast_UpdateNonVisibility( srcent, destent, qtrue ); + } + } + } + + // Now do the normal timeslice checks + for ( srccount = 0, src = lastsrc, srcent = &g_entities[lastsrc]; + src < aicast_maxclients; // && srccount < level.numPlayingClients; + src++, srcent++ ) + { + if ( !srcent->inuse ) { + continue; + } + + srccount++; + + if ( srcent->aiInactive ) { + continue; + } + if ( srcent->health <= 0 ) { + continue; + } + + cs = AICast_GetCastState( src ); + + if ( cs->castScriptStatus.scriptNoSightTime >= level.time ) { + continue; + } + + // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) + trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); + + if ( lastdest < 0 ) { + lastdest = 0; + } + + for ( destcount = 0, dest = lastdest, destent = &g_entities[lastdest]; + dest < aicast_maxclients; // && destcount < level.numPlayingClients; + dest++, destent++ ) + { + if ( !destent->inuse ) { + continue; + } + + destcount++; + + if ( destent->aiInactive ) { + continue; + } + if ( src == dest ) { + continue; + } + + vis = &cs->vislist[destent->s.number]; + + // we only check for initial sighting above + if ( !( destent->r.svFlags & SVF_CASTAI ) ) { + if ( vis->lastcheck_timestamp != vis->real_visible_timestamp ) { + continue; + } + } + if ( vis->lastcheck_timestamp == level.time ) { + continue; // already checked this frame + + } + if ( vis->lastcheck_timestamp > level.time ) { + continue; // let the loadgame settle down + + } + // if they are friends, only check very infrequently + if ( AICast_SameTeam( cs, destent->s.number ) && ( vis->lastcheck_timestamp == vis->visible_timestamp ) + && ( destent->health == vis->lastcheck_health ) ) { + if ( vis->lastcheck_timestamp > ( level.time - ( 2000 + rand() % 1000 ) ) ) { + continue; // dont check too often + } + } + + // check for visibility + if ( !( destent->flags & FL_NOTARGET ) + && ( AICast_CheckVisibility( srcent, destent ) ) ) { + // make sure they are still with us + if ( destent->inuse ) { + // record the sighting + AICast_UpdateVisibility( srcent, destent, qtrue, qtrue ); + } + } else // if (vis->lastcheck_timestamp == vis->real_update_timestamp) + { + AICast_UpdateNonVisibility( srcent, destent, qtrue ); + } + + // break if we've processed the maximum visibilities + if ( ++count > numchecks ) { + dest++; + if ( dest >= aicast_maxclients ) { + src++; + } + goto escape; + } + } + + lastdest = 0; + } + +escape: + + if ( src >= aicast_maxclients ) { + src = 0; + } + lastsrc = src; + if ( dest >= aicast_maxclients ) { + dest = 0; + } + lastdest = dest; +} diff --git a/src/game/ai_cast_think.c b/src/game/ai_cast_think.c new file mode 100644 index 0000000..277b38f --- /dev/null +++ b/src/game/ai_cast_think.c @@ -0,0 +1,1612 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: ai_cast_think.c +// Function: Wolfenstein AI Character Thinking +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +The lowest level of Cast AI thinking. +*/ + +/* +============ +AICast_ProcessAIFunctions +============ +*/ +void AICast_ProcessAIFunctions( cast_state_t *cs, float thinktime ) { + int i; + char *funcname; + + //check for air + BotCheckAir( cs->bs ); + //if the cast has no ai function + if ( !cs->aifunc ) { + AIFunc_DefaultStart( cs ); + } + // + // call AI funcs for this cast + // + AICast_DBG_InitAIFuncs(); + // + // only allow looping in debug mode (since it's much slower) + for ( i = 0; i < ( aicast_debug.integer ? MAX_AIFUNCS : 1 ); i++ ) + { + if ( !( funcname = cs->aifunc( cs ) ) ) { + break; + } else { + trap_BotResetAvoidReach( cs->bs->ms ); // reset avoidreach + cs->thinkFuncChangeTime = level.time; + AICast_DBG_AddAIFunc( cs, funcname ); + } + } + // + //if the cast executed too many AI functions + // + if ( aicast_debug.integer && i >= MAX_AIFUNCS ) { + AICast_DBG_ListAIFuncs( cs, 10 ); // print the last 10 funcs called + } +} + + +/* +============== +AICast_ChangeViewAngles +============== +*/ +void AICast_ChangeViewAngles( cast_state_t *cs, float thinktime ) { + float diff, factor, maxchange, anglespeed; + int i; + bot_state_t *bs; + + bs = cs->bs; + // + // restoire locked viewangles if required + if ( cs->aiFlags & AIFL_VIEWLOCKED ) { + VectorCopy( cs->viewlock_viewangles, bs->ideal_viewangles ); + } + // + if ( bs->ideal_viewangles[PITCH] > 180 ) { + bs->ideal_viewangles[PITCH] -= 360; + } + // + maxchange = cs->attributes[YAW_SPEED]; //300; + if ( cs->aiState >= AISTATE_COMBAT ) { + factor = 2.0; + maxchange *= 2.0; + } else { + factor = 0.7; + } + // + if ( cs->lockViewAnglesTime < level.time ) { + maxchange *= thinktime; + for ( i = 0; i < 3; i++ ) { + diff = fabs( AngleDifference( bs->viewangles[i], bs->ideal_viewangles[i] ) ); + anglespeed = diff * factor; + if ( anglespeed > maxchange ) { + anglespeed = maxchange; + } + bs->viewangles[i] = BotChangeViewAngle( bs->viewangles[i], + bs->ideal_viewangles[i], anglespeed ); + //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` + //bs->viewangles[i] = bs->ideal_viewangles[i]; + } + } + if ( bs->viewangles[PITCH] > 180 ) { + bs->viewangles[PITCH] -= 360; + } + //elementary action: view + trap_EA_View( bs->client, bs->viewangles ); +} + + +/* +============== +AICast_InputToUserCommand +============== +*/ +static int serverTime; +void AICast_InputToUserCommand( cast_state_t *cs, bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3] ) { + vec3_t angles, forward, right, up; + short temp; + int j; + signed char movechar; + gentity_t *ent; + + ent = &g_entities[cs->entityNum]; + + //clear the whole structure + memset( ucmd, 0, sizeof( usercmd_t ) ); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = serverTime; + //crouch/movedown + if ( aiDefaults[cs->aiCharacter].attributes[ATTACK_CROUCH] ) { // only crouch if this character is physically able to + //RF, disabled this bit so truck guy in forest_6 doesn't get stuck and then gib + if ( /*cs->bs->cur_ps.groundEntityNum != ENTITYNUM_NONE &&*/ bi->actionflags & ACTION_CROUCH ) { + ucmd->upmove -= 127; + } + } + // + // actions not effected by script pausing + // + // set zoom button + if ( cs->aiFlags & AIFL_ZOOMING ) { + ucmd->wbuttons |= WBUTTON_ZOOM; + } + //set the buttons + if ( bi->actionflags & ACTION_ATTACK ) { + vec3_t ofs; + // don't fire if we are not facing the right direction yet + if ( ( cs->triggerReleaseTime < level.time ) && + ( ( cs->lockViewAnglesTime >= level.time ) || + ( fabs( AngleDifference( cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW] ) ) < 20 ) ) && + // check for radid luger firing by skilled users (release fire between shots) + ( ( ( level.time + cs->entityNum * 500 ) / 2000 ) % 2 || !( rand() % ( 1 + g_gameskill.integer ) ) || ( cs->attributes[ATTACK_SKILL] < 0.5 ) || ( cs->bs->weaponnum != WP_LUGER ) || ( cs->bs->cur_ps.weaponTime == 0 ) || ( cs->bs->cur_ps.releasedFire ) ) ) { + ucmd->buttons |= BUTTON_ATTACK; + // do some swaying around for some weapons + AICast_WeaponSway( cs, ofs ); + VectorAdd( bi->viewangles, ofs, bi->viewangles ); + } + } + // + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT( bi->viewangles[PITCH] ); + ucmd->angles[YAW] = ANGLE2SHORT( bi->viewangles[YAW] ); + ucmd->angles[ROLL] = ANGLE2SHORT( bi->viewangles[ROLL] ); + //subtract the delta angles + for ( j = 0; j < 3; j++ ) { + temp = ucmd->angles[j] - delta_angles[j]; + ucmd->angles[j] = temp; + } + + +//----(SA) modified slightly for DM/DK + ucmd->weapon = bi->weapon; + // + // relaxed mode show no weapons + if ( cs->aiState <= AISTATE_QUERY ) { + if ( WEAPS_ONE_HANDED & ( 1 << ucmd->weapon ) ) { // one-handed wepons don't draw, others do + ucmd->weapon = WP_NONE; + } + } +//----(SA) end + + // + if ( bi->actionflags & ACTION_GESTURE ) { + ucmd->buttons |= BUTTON_GESTURE; + } + if ( bi->actionflags & ACTION_RELOAD ) { + ucmd->wbuttons |= WBUTTON_RELOAD; + } + // + // if we are locked down, don't do anything + // + if ( cs->pauseTime > level.time ) { + return; + } + // + // if scripted pause, no movement + // + if ( cs->castScriptStatus.scriptNoMoveTime > level.time ) { + return; + } + // + // if viewlock, wait until we are facing ideal angles before we move + if ( cs->aiFlags & AIFL_VIEWLOCKED ) { + if ( fabs( AngleDifference( cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW] ) ) > 10 ) { + return; + } + } + // + if ( bi->actionflags & ACTION_DELAYEDJUMP ) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + // + // only move if we are in combat or we are facing where our ideal angles + if ( bi->speed ) { + if ( ( !( cs->aiFlags & AIFL_WALKFORWARD ) && cs->bs->enemy >= 0 ) || ( fabs( AngleDifference( cs->bs->ideal_viewangles[YAW], cs->bs->viewangles[YAW] ) ) < 60 ) ) { + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if ( bi->dir[2] ) { + angles[PITCH] = bi->viewangles[PITCH]; + } else { angles[PITCH] = 0;} + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors( angles, forward, right, up ); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / 400; + //set the view independent movement + ucmd->forwardmove = DotProduct( forward, bi->dir ) * bi->speed; + ucmd->rightmove = DotProduct( right, bi->dir ) * bi->speed; + + // RF, changed this to fix stim soldier flying attack + if ( !ucmd->upmove ) { // only change it if we don't already have an upmove set + ucmd->upmove = DotProduct( up, bi->dir ) * bi->speed; + } + //if (!ucmd->upmove) // only change it if we don't already have an upmove set + // ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; + } + } + // + //normal keyboard movement + if ( cs->actionFlags & CASTACTION_WALK ) { + movechar = 70; + } else { + movechar = 127; + } + if ( bi->actionflags & ACTION_MOVEFORWARD ) { + ucmd->forwardmove = movechar; + } + if ( !( cs->aiFlags & AIFL_WALKFORWARD ) ) { // only do other movements if we are allowed to + if ( bi->actionflags & ACTION_MOVEBACK ) { + ucmd->forwardmove = -movechar; + } + if ( bi->actionflags & ACTION_MOVELEFT ) { + ucmd->rightmove = -movechar; + } + if ( bi->actionflags & ACTION_MOVERIGHT ) { + ucmd->rightmove = movechar; + } + } + //jump/moveup + if ( bi->actionflags & ACTION_JUMP ) { + ucmd->upmove = 127; // JUMP always takes preference over ducking + } + if ( bi->actionflags & ACTION_MOVEDOWN ) { + ucmd->upmove = -127; // JUMP always takes preference over ducking + } + if ( bi->actionflags & ACTION_MOVEUP ) { + ucmd->upmove = 127; // JUMP always takes preference over ducking + } + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); +} + + +/* +============== +AICast_UpdateInput +============== +*/ +void AICast_UpdateInput( cast_state_t *cs, int time ) { + bot_input_t bi; + bot_state_t *bs; + int j; + float speed; + + bs = cs->bs; + + //add the delta angles to the bot's current view angles + for ( j = 0; j < 3; j++ ) { + bs->viewangles[j] = AngleMod( bs->viewangles[j] + SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); + } + // + AICast_ChangeViewAngles( cs, (float) time / 1000 ); + // + if ( cs->pauseTime > level.time ) { + trap_EA_View( bs->client, bs->viewangles ); + trap_EA_GetInput( bs->client, (float) time / 1000, &bi ); + AICast_InputToUserCommand( cs, &bi, &bs->lastucmd, bs->cur_ps.delta_angles ); + g_entities[cs->bs->entitynum].client->ps.pm_flags &= ~PMF_RESPAWNED; + // + //subtract the delta angles + for ( j = 0; j < 3; j++ ) { + bs->viewangles[j] = AngleMod( bs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); + } + // + return; + } + // + trap_EA_GetInput( bs->client, (float) time / 1000, &bi ); + // + // restrict the speed according to the character and their current speedScale + // HACK, don't slow down while crouching + if ( bi.actionflags & ACTION_CROUCH && cs->speedScale < 1.0 ) { + cs->speedScale = 1.0; + } + // + // check some Cast AI specific movement flags + if ( cs->actionFlags & CASTACTION_WALK ) { + if ( cs->speedScale > ( cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED] ) ) { + cs->speedScale = ( cs->attributes[WALKING_SPEED] / cs->attributes[RUNNING_SPEED] ); + } + } + // don't ever let the speed get too low + if ( cs->speedScale < 0.25 ) { + cs->speedScale = 0.25; + } + if ( cs->speedScale > 1.2 ) { + cs->speedScale = 1.2; + } + // + speed = cs->speedScale * cs->attributes[RUNNING_SPEED]; + // + //if (speed <= (cs->attributes[WALKING_SPEED] + (cs->attributes[WALKING_SPEED] + 50 < cs->attributes[RUNNING_SPEED] ? 50 : -1))) // do a fast shuffle if slightly over walking speed + if ( speed <= cs->attributes[WALKING_SPEED] ) { + cs->actionFlags |= CASTACTION_WALK; + } + // + // we use 300 here, because the default player speed is 300, so Cast AI's can't move faster than that + if ( ( bi.speed / 400.0 ) > ( speed / 300.0 ) ) { + bi.speed = 400.0 * ( speed / 300.0 ); + if ( bi.speed > 400.0 ) { + bi.speed = 400.0; // just in case, we should never exceed this + } + } + // + // do a fast shuffle if slightly over walking speed + if ( bi.speed <= ( 400.0 / 300.0 ) * ( cs->attributes[WALKING_SPEED] + ( cs->attributes[WALKING_SPEED] + 50 < cs->attributes[RUNNING_SPEED] ? 50 : -1 ) ) ) { + cs->actionFlags |= CASTACTION_WALK; + } + // + AICast_InputToUserCommand( cs, &bi, &bs->lastucmd, bs->cur_ps.delta_angles ); + // + // check some Cast AI specific movement flags + if ( cs->actionFlags & CASTACTION_WALK ) { + bs->lastucmd.buttons |= BUTTON_WALKING; // play the walking animation + } + // + //subtract the delta angles + for ( j = 0; j < 3; j++ ) { + bs->viewangles[j] = AngleMod( bs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) ); + } + // + // make sure the respawn flag is disabled (causes problems after multiple "map xxx" commands) + g_entities[cs->bs->entitynum].client->ps.pm_flags &= ~PMF_RESPAWNED; + // set the aiState + g_entities[cs->bs->entitynum].client->ps.aiState = cs->aiState; +} + +/* +============ +AICast_Think + + entry point for all cast AI +============ +*/ +void AICast_Think( int client, float thinktime ) { + gentity_t *ent; + cast_state_t *cs; + int i; + int animIndex; + animation_t *anim; + +// if (saveGamePending || (strlen( g_missionStats.string ) > 2 )) { +// return; +// } + + // + // get the cast ready for processing + // + cs = AICast_GetCastState( client ); + ent = &g_entities[client]; + // + // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) + trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); + // + // make sure we have a valid navigation system + // + if ( !trap_AAS_Initialized() ) { + return; + } + // + trap_EA_ResetInput( client, NULL ); + cs->aiFlags &= ~AIFL_VIEWLOCKED; + //cs->bs->weaponnum = ent->client->ps.weapon; + // + // turn off flags that are set each frame if needed + ent->client->ps.eFlags &= ~( EF_NOSWINGANGLES | EF_MONSTER_EFFECT | EF_MONSTER_EFFECT2 | EF_MONSTER_EFFECT3 ); + // conditional flags + if ( ent->aiCharacter == AICHAR_ZOMBIE ) { + if ( COM_BitCheck( ent->client->ps.weapons, WP_MONSTER_ATTACK1 ) ) { + cs->aiFlags |= AIFL_NO_FLAME_DAMAGE; + SET_FLAMING_ZOMBIE( ent->s, 1 ); + } else { + SET_FLAMING_ZOMBIE( ent->s, 0 ); + } + } + // + // if we're dead, do special stuff only + if ( ent->health <= 0 || cs->revivingTime || cs->rebirthTime ) { + // + if ( cs->revivingTime && cs->revivingTime < level.time ) { + // start us thinking again + ent->client->ps.pm_type = PM_NORMAL; + cs->revivingTime = 0; + } + // + if ( cs->rebirthTime && cs->rebirthTime < level.time ) { + vec3_t mins, maxs; + int touch[10], numTouch; + float oldmaxZ; + + oldmaxZ = ent->r.maxs[2]; + + // make sure the area is clear + AIChar_SetBBox( ent, cs ); + + VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); + VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); + trap_UnlinkEntity( ent ); + + numTouch = trap_EntitiesInBox( mins, maxs, touch, 10 ); + + if ( numTouch ) { + for ( i = 0; i < numTouch; i++ ) { + //if (!g_entities[touch[i]].client || g_entities[touch[i]].r.contents == CONTENTS_BODY) + if ( g_entities[touch[i]].r.contents & MASK_PLAYERSOLID ) { + break; + } + } + if ( i == numTouch ) { + numTouch = 0; + } + } + + if ( numTouch == 0 ) { // ok to spawn + + // give them health when they start reviving, so we won't gib after + // just a couple shots while reviving + ent->health = + ent->client->ps.stats[STAT_HEALTH] = + ent->client->ps.stats[STAT_MAX_HEALTH] = + ( ( cs->attributes[STARTING_HEALTH] - 50 ) > 30 ? ( cs->attributes[STARTING_HEALTH] - 50 ) : 30 ); + + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->takedamage = qtrue; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + ent->die = AICast_Die; + ent->client->ps.eFlags &= ~EF_DEAD; + ent->s.eFlags &= ~EF_DEAD; + + cs->rebirthTime = 0; + cs->deathTime = 0; + + // play the revive animation + cs->revivingTime = level.time + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_REVIVE, qfalse, qtrue );; + } else { + // can't spawn yet, so set bbox back, and wait + ent->r.maxs[2] = oldmaxZ; + ent->client->ps.maxs[2] = ent->r.maxs[2]; + } + trap_LinkEntity( ent ); + } + // ZOMBIE should set effect flag if really dead + if ( cs->aiCharacter == AICHAR_ZOMBIE && !ent->r.contents ) { + ent->client->ps.eFlags |= EF_MONSTER_EFFECT2; + } + // + if ( ent->health > GIB_HEALTH && cs->deathTime && cs->deathTime < ( level.time - 3000 ) ) { +/* + // been dead for long enough, set our animation to the end frame + switch ( ent->s.legsAnim & ~ANIM_TOGGLEBIT ) { + case BOTH_DEATH1: + case BOTH_DEAD1: + anim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + anim = BOTH_DEAD2; + break; + case BOTH_DEATH3: + case BOTH_DEAD3: + anim = BOTH_DEAD3; + break; + default: + G_Error( "%s has unknown death animation\n", ent->classname); + } + ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + ent->client->ps.legsAnim = ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; +*/ + cs->deathTime = 0; + ent->r.svFlags &= ~SVF_BROADCAST; + } + // + // no more thinking required + return; + } + // + // set some anim conditions + if ( cs->secondDeadTime ) { + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qtrue, qfalse ); + } else { + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SECONDLIFE, qfalse, qfalse ); + } + // set health value + if ( ent->health <= 0.25 * cs->attributes[STARTING_HEALTH] ) { + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 3, qfalse ); + } else if ( ent->health <= 0.5 * cs->attributes[STARTING_HEALTH] ) { + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 2, qfalse ); + } else { + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_HEALTH_LEVEL, 1, qfalse ); + } + // + cs->speedScale = 1.0; // reset each frame, set if required + cs->actionFlags = 0; // FIXME: move this to a Cast AI movement init function! + //retrieve the current client state + BotAI_GetClientState( client, &( cs->bs->cur_ps ) ); + // + // setup movement speeds for the given state + // walking + animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALK ); + if ( animIndex >= 0 ) { + anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); + cs->attributes[WALKING_SPEED] = anim->moveSpeed; + } + // crouching + animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_WALKCR ); + if ( animIndex >= 0 ) { + anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); + cs->attributes[CROUCHING_SPEED] = anim->moveSpeed; + } + // running + animIndex = BG_GetAnimScriptAnimation( cs->entityNum, ent->client->ps.aiState, ANIM_MT_RUN ); + if ( animIndex >= 0 ) { + anim = BG_GetAnimationForIndex( cs->entityNum, animIndex ); + cs->attributes[RUNNING_SPEED] = anim->moveSpeed; + } + // update crouch speed scale + ent->client->ps.crouchSpeedScale = cs->attributes[CROUCHING_SPEED] / cs->attributes[RUNNING_SPEED]; + // + // only enable headlook if we want to this frame + ent->client->ps.eFlags &= ~EF_HEADLOOK; + if ( cs->bs->enemy >= 0 ) { + ent->client->ps.eFlags &= ~EF_STAND_IDLE2; // never use alt idle if fighting + } + // + // check for dead leader + if ( cs->leaderNum >= 0 && g_entities[cs->leaderNum].health <= 0 ) { + cs->leaderNum = -1; + } + // +#if 0 + // HACK for village2, if they are stuck, find a good position (there is a friendly guy placed inside a table) + { + trace_t tr; + vec3_t org; + trap_Trace( &tr, cs->bs->cur_ps.origin, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, cs->bs->cur_ps.origin, cs->entityNum, CONTENTS_SOLID ); + while ( tr.startsolid ) { + VectorCopy( cs->bs->cur_ps.origin, org ); + org[0] += 96 * crandom(); + org[1] += 96 * crandom(); + org[2] += 16 * crandom(); + trap_Trace( &tr, org, cs->bs->cur_ps.mins, cs->bs->cur_ps.maxs, org, cs->entityNum, CONTENTS_SOLID ); + G_SetOrigin( &g_entities[cs->entityNum], org ); + VectorCopy( org, g_entities[cs->entityNum].client->ps.origin ); + } + } +#endif + //add the delta angles to the cast's current view angles + for ( i = 0; i < 3; i++ ) { + cs->bs->viewangles[i] = AngleMod( cs->bs->viewangles[i] + SHORT2ANGLE( cs->bs->cur_ps.delta_angles[i] ) ); + } + // + //increase the local time of the cast + cs->bs->ltime += thinktime; + // + cs->bs->thinktime = thinktime; + //origin of the cast + VectorCopy( cs->bs->cur_ps.origin, cs->bs->origin ); + //eye coordinates of the cast + VectorCopy( cs->bs->cur_ps.origin, cs->bs->eye ); + cs->bs->eye[2] += cs->bs->cur_ps.viewheight; + //get the area the cast is in + cs->bs->areanum = BotPointAreaNum( cs->bs->origin ); + // clear flags each frame + cs->bs->flags = 0; + // + // check enemy health + if ( cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0 ) { + cs->bs->enemy = -1; + } + // + // if the previous movetype was temporary, set it back + if ( cs->movestateType == MSTYPE_TEMPORARY ) { + cs->movestate = MS_DEFAULT; + cs->movestateType = MSTYPE_NONE; + } + // crouching? + if ( ( cs->bs->attackcrouch_time > trap_AAS_Time() ) && + ( ( cs->lastAttackCrouch > level.time - 500 ) || ( cs->thinkFuncChangeTime < level.time - 1000 ) ) ) { + // if we are not moving, and we are firing, always stand, unless we are allowed to crouch + fire + if ( VectorLength( cs->bs->cur_ps.velocity ) || ( cs->lastWeaponFired < level.time - 2000 ) || ( cs->aiFlags & AIFL_ATTACK_CROUCH ) ) { + cs->lastAttackCrouch = level.time; + trap_EA_Crouch( cs->bs->client ); + } + } + // + //if (cs->bs->enemy >= 0) { + //update the attack inventory values + AICast_UpdateBattleInventory( cs, cs->bs->enemy ); + //} + // + // if we don't have ammo for the current weapon, get rid of it + if ( !( COM_BitCheck( cs->bs->cur_ps.weapons, cs->bs->weaponnum ) ) || !AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum ) ) { + // select a weapon + AICast_ChooseWeapon( cs, qfalse ); + // if still no ammo, select a blank weapon + //if (!AICast_GotEnoughAmmoForWeapon( cs, cs->bs->weaponnum )) { + // cs->bs->weaponnum = WP_NONE; + //} + } + // + // in query mode, we do special handling (pause scripting, check for transition to alert/combat, etc) + if ( cs->aiState == AISTATE_QUERY ) { + AICast_QueryThink( cs ); + } else if ( cs->pauseTime < level.time ) { + // do the thinking + AICast_ProcessAIFunctions( cs, thinktime ); + // + // make sure the correct weapon is selected + trap_EA_SelectWeapon( cs->bs->client, cs->bs->weaponnum ); + // + // process current script if it exists + cs->castScriptStatusCurrent = cs->castScriptStatus; + AICast_ScriptRun( cs, qfalse ); + } + // + // set special movestate if necessary + if ( cs->movestateType != MSTYPE_NONE ) { + switch ( cs->movestate ) { + case MS_WALK: + cs->actionFlags |= CASTACTION_WALK; + break; + case MS_CROUCH: + trap_EA_Crouch( cs->entityNum ); + break; + default: + break; // TTimo gcc: MS_DEFAULT MS_RUN not handled in switch + } + } + // + //subtract the delta angles + for ( i = 0; i < 3; i++ ) { + cs->bs->viewangles[i] = AngleMod( cs->bs->viewangles[i] - SHORT2ANGLE( cs->bs->cur_ps.delta_angles[i] ) ); + } +} + +/* +============ +AICast_StartFrame + + Think any clients that need thinking +============ +*/ +void CopyToBodyQue( gentity_t *ent ); + +void AICast_StartFrame( int time ) { + int i, elapsed, count, clCount; + cast_state_t *cs; + int castcount; + static int lasttime; + static vmCvar_t aicast_disable; + gentity_t *ent; + + if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) { + return; + } + + if ( saveGamePending ) { + return; + } + + // if waiting at intermission, don't think + if ( strlen( g_missionStats.string ) > 1 ) { + return; + } + + if ( !aicast_disable.handle ) { + trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT ); + } else + { + trap_Cvar_Update( &aicast_disable ); + if ( aicast_disable.integer ) { + return; + } + } + + trap_Cvar_Update( &aicast_debug ); + trap_Cvar_Update( &aicast_debugname ); + trap_Cvar_Update( &aicast_scripts ); + + // no need to think during the intermission + if ( level.intermissiontime ) { + return; + } + // + // make sure the AAS gets updated + trap_BotLibStartFrame( (float) time / 1000 ); + // + // + elapsed = time - lasttime; + if ( elapsed == 0 ) { + return; // no time has elapsed + } + +//G_Printf( "AI startframe: %i\n", time ); + + if ( elapsed < 0 ) { + elapsed = 0; + lasttime = time; + } + // don't let the framerate drop below 10 + if ( elapsed > 100 ) { + elapsed = 100; + } + //AICast_SightUpdate( (int)((float)SIGHT_PER_SEC * ((float)elapsed / 1000)) ); + // + count = 0; + castcount = 0; + clCount = 0; + ent = g_entities; + // + //update the AI characters + // TTimo gcc: left-hand operand of comma expression has no effect + // initial line was: for (i = 0; i < aicast_maxclients, clCount < level.numPlayingClients; i++, ent++) + for ( i = 0; ( i < aicast_maxclients ) && ( clCount < level.numPlayingClients ) ; i++, ent++ ) + { + if ( ent->client ) { + clCount++; + } + // + cs = AICast_GetCastState( i ); + // is this a cast AI? + if ( cs->bs ) { + if ( ent->inuse ) { + if ( ent->aiInactive == qfalse ) { + // + elapsed = time - cs->lastThink; + // + // if they're moving/firing think every frame + if ( ( elapsed >= 50 ) && + ( ( ( ( !VectorCompare( ent->client->ps.velocity, vec3_origin ) ) || + ( ent->client->buttons ) || + ( elapsed >= aicast_thinktime ) ) && + ( count <= aicast_maxthink ) ) || + ( elapsed >= aicast_thinktime * 2 ) ) ) { + // make it think now + AICast_Think( i, (float)elapsed / 1000 ); + cs->lastThink = time; + // + count++; + } + // check for any debug info updates + AICast_DebugFrame( cs ); + } else if ( cs->aiFlags & AIFL_WAITINGTOSPAWN ) { + // check f the space is clear yet + ent->AIScript_AlertEntity( ent ); + } + } else { + trap_UnlinkEntity( ent ); + } + // + // see if we've checked all cast AI's + if ( ++castcount >= numcast ) { + break; + } + } + } + // + lasttime = time; +} + +/* +============ +AICast_StartServerFrame + + Do movements, sighting, etc +============ +*/ +void AICast_StartServerFrame( int time ) { + int i, elapsed, count, clCount; + cast_state_t *cs; + int castcount; + static int lasttime; + static vmCvar_t aicast_disable; + gentity_t *ent; + cast_state_t *pcs; +// int oldLegsTimer; + + if ( trap_Cvar_VariableIntegerValue( "savegame_loading" ) ) { + return; + } + + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + return; + } + + if ( saveGamePending ) { + return; + } + + // if waiting at intermission, don't think + if ( strlen( g_missionStats.string ) > 1 ) { + return; + } + + if ( !aicast_disable.handle ) { + trap_Cvar_Register( &aicast_disable, "aicast_disable", "0", CVAR_CHEAT ); + } else + { + trap_Cvar_Update( &aicast_disable ); + if ( aicast_disable.integer ) { + return; + } + } + + trap_Cvar_Update( &aicast_debug ); + + // no need to think during the intermission + if ( level.intermissiontime ) { + return; + } + // + // make sure the AAS gets updated + trap_BotLibStartFrame( (float) time / 1000 ); + // + // + elapsed = time - lasttime; + if ( elapsed == 0 ) { + return; // no time has elapsed + } + + pcs = AICast_GetCastState( 0 ); + +//G_Printf( "AI startserverframe: %i\n", time ); + + if ( elapsed < 0 ) { + elapsed = 0; + lasttime = time; + } + // don't let the framerate drop below 10 + if ( elapsed > 100 ) { + elapsed = 100; + } + // + // process player's current script if it exists + AICast_ScriptRun( AICast_GetCastState( 0 ), qfalse ); + // + AICast_SightUpdate( (int)( (float)SIGHT_PER_SEC * ( (float)elapsed / 1000 ) ) ); + // + count = 0; + castcount = 0; + clCount = 0; + ent = g_entities; + // + //update the AI characters + // TTimo gcc: left-hand operand of comma expression has no effect + // initial line: for (i = 0; i < aicast_maxclients, clCount < level.numPlayingClients; i++, ent++) + for ( i = 0; ( i < aicast_maxclients ) && ( clCount < level.numPlayingClients ) ; i++, ent++ ) + { + if ( ent->client ) { + clCount++; + } + // + cs = AICast_GetCastState( i ); + // is this a cast AI? + if ( cs->bs ) { + if ( ent->aiInactive == qfalse && ent->inuse ) { + // + elapsed = level.time - cs->lastMoveThink; + // + // optimization, if they're not in the player's PVS, and they aren't trying to move, then don't bother thinking + if ( ( ( ent->health > 0 ) && ( elapsed > 300 ) ) + || ( g_entities[0].client && g_entities[0].client->cameraPortal ) + || ( cs->vislist[0].visible_timestamp == cs->vislist[0].lastcheck_timestamp ) + || ( pcs->vislist[cs->entityNum].visible_timestamp == pcs->vislist[cs->entityNum].lastcheck_timestamp ) + || ( VectorLength( ent->client->ps.velocity ) > 0 ) + || ( cs->bs->lastucmd.forwardmove || cs->bs->lastucmd.rightmove || cs->bs->lastucmd.upmove > 0 || cs->bs->lastucmd.buttons || cs->bs->lastucmd.wbuttons ) + || ( trap_InPVS( cs->bs->origin, g_entities[0].s.pos.trBase ) ) ) { // do pvs check last, since it's the most expensive to call +// oldLegsTimer = ent->client->ps.legsTimer; + // + // send it's movement commands + // + serverTime = time; + AICast_UpdateInput( cs, elapsed ); + trap_BotUserCommand( cs->bs->client, &( cs->bs->lastucmd ) ); + cs->lastMoveThink = level.time; + // + // check for anim changes that may require us to stay still + // +/* if (oldLegsTimer != ent->client->ps.legsTimer) { + // dont move until they are finished + if (cs->castScriptStatus.scriptNoMoveTime < level.time + ent->client->ps.legsTimer) { + cs->castScriptStatus.scriptNoMoveTime = level.time + ent->client->ps.legsTimer; + } + } +*/ } + } else { + trap_UnlinkEntity( ent ); + } + // + // see if we've checked all cast AI's + if ( ++castcount >= numcast ) { + break; + } + } + } + // + lasttime = time; +} + +/* +============== +AICast_PredictMovement + + Simulates movement over a number of frames, returning the end position +============== +*/ +void AICast_PredictMovement( cast_state_t *cs, int numframes, float frametime, aicast_predictmove_t *move, usercmd_t *ucmd, int checkHitEnt ) { + int frame, i; + playerState_t ps; + pmove_t pm; + trace_t tr; + vec3_t end, startHitVec, thisHitVec, lastOrg, projPoint; + qboolean checkReachMarker; + +//int pretime = Sys_MilliSeconds(); +//G_Printf("PredictMovement: %f duration, %i frames\n", frametime, numframes ); + VectorCopy( vec3_origin, startHitVec ); + + if ( cs->bs ) { + ps = cs->bs->cur_ps; + } else { + ps = g_entities[cs->entityNum].client->ps; + } + + ps.eFlags |= EF_DUMMY_PMOVE; + + move->stopevent = PREDICTSTOP_NONE; + + if ( checkHitEnt >= 0 && !Q_stricmp( g_entities[checkHitEnt].classname, "ai_marker" ) ) { + checkReachMarker = qtrue; + VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, ps.origin, startHitVec ); + VectorCopy( ps.origin, lastOrg ); + } else { + checkReachMarker = qfalse; + } + + // don't let the frametime be too high +// while (frametime > 0.2) { +// numframes *= 2; +// frametime /= 2; +// } + + for ( frame = 0; frame < numframes; frame++ ) + { + memset( &pm, 0, sizeof( pm ) ); + pm.ps = &ps; + pm.cmd = *ucmd; + pm.oldcmd = *ucmd; + pm.ps->commandTime = 0; + pm.cmd.serverTime = (int)( 1000.0 * frametime ); + pm.tracemask = g_entities[cs->entityNum].clipmask; //MASK_PLAYERSOLID; + + pm.trace = trap_TraceCapsule; //trap_Trace; + pm.pointcontents = trap_PointContents; + pm.debugLevel = qfalse; + pm.noFootsteps = qtrue; + // RF, not needed for prediction + //pm.noWeapClips = qtrue; // (SA) AI's ignore weapon clips + + // perform a pmove + Pmove( &pm ); + + if ( checkHitEnt >= 0 ) { + // if we've hit the checkent, abort + if ( checkReachMarker ) { + VectorSubtract( g_entities[checkHitEnt].r.currentOrigin, pm.ps->origin, thisHitVec ); + if ( DotProduct( startHitVec, thisHitVec ) < 0 ) { + // project the marker onto the movement vec, and check distance + ProjectPointOntoVector( g_entities[checkHitEnt].r.currentOrigin, lastOrg, pm.ps->origin, projPoint ); + if ( VectorDistance( g_entities[checkHitEnt].r.currentOrigin, projPoint ) < 8 ) { + move->stopevent = PREDICTSTOP_HITENT; + goto done; + } + } + // use this position as the base for the next test + //VectorCopy( thisHitVec, startHitVec ); + VectorCopy( pm.ps->origin, lastOrg ); + } + // if we didnt reach the marker, then check for something that blocked us + for ( i = 0; i < pm.numtouch; i++ ) { + if ( pm.touchents[i] == pm.ps->groundEntityNum ) { + continue; + } + if ( pm.touchents[i] == checkHitEnt ) { + move->stopevent = PREDICTSTOP_HITENT; + goto done; + } else if ( pm.touchents[i] < MAX_CLIENTS || + ( pm.touchents[i] != ENTITYNUM_WORLD && ( g_entities[pm.touchents[i]].s.eType != ET_MOVER || g_entities[pm.touchents[i]].moverState != MOVER_POS1 ) ) ) { + // we have hit another entity, so abort + move->stopevent = PREDICTSTOP_HITCLIENT; + goto done; + } else if ( !Q_stricmp( g_entities[pm.touchents[i]].classname, "script_mover" ) ) { + // avoid script_mover's + move->stopevent = PREDICTSTOP_HITCLIENT; + goto done; + } + } + } + } + +done: + + // hack, if we are above ground, chances are it's because we only did one frame, and gravity isn't applied until + // after the frame, so try and drop us down some + if ( move->groundEntityNum == ENTITYNUM_NONE ) { + VectorCopy( move->endpos, end ); + end[2] -= 32; + trap_Trace( &tr, move->endpos, pm.mins, pm.maxs, end, pm.ps->clientNum, pm.tracemask ); + if ( !tr.startsolid && !tr.allsolid && tr.fraction < 1 ) { + VectorCopy( tr.endpos, pm.ps->origin ); + pm.ps->groundEntityNum = tr.entityNum; + } + } + + // copy off the results + VectorCopy( pm.ps->origin, move->endpos ); + move->frames = numframes; + //move->presencetype = cs->bs->presencetype; + VectorCopy( pm.ps->velocity, move->velocity ); + move->numtouch = pm.numtouch; + memcpy( move->touchents, pm.touchents, sizeof( pm.touchents ) ); + move->groundEntityNum = pm.ps->groundEntityNum; + +//G_Printf("PredictMovement: %i ms\n", -pretime + Sys_MilliSeconds() ); +} + +/* +============ +AICast_GetAvoid +============ +*/ +qboolean AICast_GetAvoid( cast_state_t *cs, bot_goal_t *goal, vec3_t outpos, qboolean reverse, int blockEnt ) { + float yaw, oldyaw, distmoved, bestmoved, bestyaw; + vec3_t bestpos; + aicast_predictmove_t castmove; + usercmd_t ucmd; + qboolean enemyVisible; + float angleDiff; + // TTimo might be used uninitialized + int starttraveltime = 0; + int besttraveltime, traveltime; + int invert; + float inc; + qboolean averting = qfalse; + float maxYaw, simTime; + static int lastTime; + + VectorCopy( vec3_origin, bestpos ); + + // + // if we are in the air, no chance of avoiding + if ( cs->bs->cur_ps.groundEntityNum == ENTITYNUM_NONE && g_entities[cs->entityNum].waterlevel <= 1 ) { + return qfalse; + } + // + if ( cs->lastAvoid > level.time - rand() % 500 ) { + return qfalse; + } + cs->lastAvoid = level.time + 50 + rand() % 500; + // + if ( lastTime == level.time ) { + return qfalse; + } + lastTime = level.time; + + // if they have an enemy, and can currently see them, don't move out of their view + enemyVisible = ( cs->bs->enemy >= 0 ) && + ( AICast_CheckAttack( cs, cs->bs->enemy, qfalse ) ); + // + // look for a good direction to move out of the way + bestmoved = 0; + bestyaw = 360; + besttraveltime = 9999999; + if ( goal ) { + starttraveltime = trap_AAS_AreaTravelTimeToGoalArea( cs->bs->areanum, cs->bs->origin, goal->areanum, cs->travelflags ); + } + memcpy( &ucmd, &cs->bs->lastucmd, sizeof( usercmd_t ) ); + ucmd.forwardmove = 127; + ucmd.rightmove = 0; + ucmd.upmove = 0; + if ( cs->dangerEntity >= 0 && cs->dangerEntityValidTime >= level.time ) { + averting = qtrue; + } else if ( !goal ) { + averting = qtrue; // not heading for a goal, so we must be getting out of someone's way + } + // + maxYaw = 0; + simTime = 1.2; + // + if ( averting ) { + // avoiding danger, go anywhere! + angleDiff = 300; + inc = 60; + invert = 1; + } else { + if ( level.time % 1000 < 500 ) { + invert = 1; + } else { + invert = -1; + } + angleDiff = 140; + inc = 35; + } + if ( blockEnt > aicast_maxclients ) { + maxYaw = angleDiff; + simTime = 0.5; + } + // + for ( yaw = -angleDiff * invert; yaw*invert <= maxYaw; yaw += inc * invert ) { + if ( !averting && !yaw ) { + continue; + } + oldyaw = cs->bs->cur_ps.viewangles[YAW]; + cs->bs->cur_ps.viewangles[YAW] += yaw + reverse * 180; + // + ucmd.angles[YAW] = ANGLE2SHORT( AngleMod( cs->bs->cur_ps.viewangles[YAW] ) ); + // + AICast_PredictMovement( cs, 5, 0.4, &castmove, &ucmd, -1 ); + // if we have a danger entity, try and get away from it at all costs + if ( cs->dangerEntity >= 0 && cs->dangerEntityValidTime >= level.time ) { + distmoved = Distance( castmove.endpos, cs->dangerEntityPos ); + } else if ( goal ) { + //distmoved = 99999 - trap_AAS_AreaTravelTimeToGoalArea( BotPointAreaNum(castmove.endpos), castmove.endpos, goal->areanum, cs->travelflags ); + distmoved = 99999 - Distance( castmove.endpos, goal->origin ); + } else { + distmoved = Distance( castmove.endpos, cs->bs->cur_ps.origin ); + } + if ( ( distmoved > bestmoved ) + //&& ((cs->bs->origin[2] - castmove.endpos[2]) < 64) // allow up, but not down (falling) + && ( castmove.groundEntityNum != ENTITYNUM_NONE ) ) { + // they all passed, check any other stuff + if ( !enemyVisible || AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, castmove.endpos, qfalse, qfalse ) ) { + if ( !goal || ( traveltime = trap_AAS_AreaTravelTimeToGoalArea( BotPointAreaNum( castmove.endpos ), castmove.endpos, goal->areanum, cs->travelflags ) ) < ( starttraveltime + 200 ) ) { + bestyaw = yaw; + bestmoved = distmoved; + besttraveltime = traveltime; + VectorCopy( castmove.endpos, bestpos ); + } + } + } + // + cs->bs->cur_ps.viewangles[YAW] = oldyaw; + } + // + if ( bestmoved > 0 ) { + VectorCopy( bestpos, outpos ); + return qtrue; + } else { + return qfalse; + } + +//G_Printf("GetAvoid: %i ms\n", -pretime + Sys_MilliSeconds() ); +} + +/* +============ +AICast_Blocked +============ +*/ +void AICast_Blocked( cast_state_t *cs, bot_moveresult_t *moveresult, int activate, bot_goal_t *goal ) { + vec3_t pos, dir; + aicast_predictmove_t move; + usercmd_t ucmd; + bot_input_t bi; + cast_state_t *ocs; + int i, blockEnt = -1; + bot_goal_t ogoal; + + if ( cs->blockedAvoidTime < level.time ) { + if ( cs->blockedAvoidTime < level.time - 300 ) { + if ( VectorCompare( cs->bs->cur_ps.velocity, vec3_origin ) && !cs->bs->lastucmd.forwardmove && !cs->bs->lastucmd.rightmove ) { + // not moving, don't bother checking + cs->blockedAvoidTime = level.time - 1; + return; + } + // are we going to hit someone soon? + trap_EA_GetInput( cs->entityNum, (float) level.time / 1000, &bi ); + AICast_InputToUserCommand( cs, &bi, &ucmd, cs->bs->cur_ps.delta_angles ); + AICast_PredictMovement( cs, 1, 0.6, &move, &ucmd, ( goal && goal->entitynum > -1 ) ? goal->entitynum : cs->entityNum ); + + // blocked if we hit a client (or non-stationary mover) other than our enemy or goal + if ( move.stopevent != PREDICTSTOP_HITCLIENT ) { + // not blocked + cs->blockedAvoidTime = level.time - 1; + return; + } + + // if we stopped passed our goal, ignore it + if ( goal ) { + if ( VectorDistance( cs->bs->origin, goal->origin ) < VectorDistance( cs->bs->origin, move.endpos ) ) { + vec3_t v1, v2; + VectorSubtract( goal->origin, cs->bs->origin, v1 ); + VectorSubtract( goal->origin, move.endpos, v2 ); + VectorNormalize( v1 ); + VectorNormalize( v2 ); + if ( DotProduct( v1, v2 ) < 0 ) { + // we went passed the goal, so assume we can reach it + cs->blockedAvoidTime = level.time - 1; + return; + } + } + } + + // try and get them to move, in case we can't get around them + blockEnt = -1; + for ( i = 0; i < move.numtouch; i++ ) { + if ( move.touchents[i] >= MAX_CLIENTS ) { + if ( !Q_stricmp( g_entities[move.touchents[i]].classname, "script_mover" ) ) { + // avoid script_mover's + blockEnt = move.touchents[i]; + } + // if we are close to the impact point, then avoid this entity + else if ( VectorDistance( cs->bs->origin, move.endpos ) < 10 ) { + //G_Printf("AI (%s) avoiding %s\n", g_entities[cs->entityNum].aiName, g_entities[move.touchents[i]].classname ); + blockEnt = move.touchents[i]; + } + continue; + } + // + ocs = AICast_GetCastState( move.touchents[i] ); + if ( !ocs->bs ) { + blockEnt = move.touchents[i]; + } + // reject this blocker if we are following or going to them + else if ( cs->followEntity != ocs->entityNum ) { + // if they are moving away from us already, let them go + if ( VectorLength( ocs->bs->cur_ps.velocity ) > 10 ) { + vec3_t v1, v2; + + VectorSubtract( ocs->bs->origin, cs->bs->origin, v2 ); + VectorNormalize( v2 ); + VectorNormalize2( ocs->bs->cur_ps.velocity, v1 ); + + if ( DotProduct( v1, v2 ) > 0.0 ) { + continue; + } + } + // + // if they recently were asked to avoid us, then they're probably not listening + if ( ocs->obstructingTime > level.time - 500 ) { + blockEnt = move.touchents[i]; + } + // + // if they are not avoiding, ignore + if ( !( ocs->aiFlags & AIFL_NOAVOID ) ) { + continue; + } + // + // they should avoid us + if ( ocs->leaderNum >= 0 ) { + ogoal.entitynum = ocs->leaderNum; + VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin ); + if ( AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) { + // give them time to move somewhere else + ocs->obstructingTime = level.time + 1000; + } else { + // make sure they don't call GetAvoid() for another few frames to let others avoid also + ocs->obstructingTime = level.time - 1; + blockEnt = move.touchents[i]; + } + } else { + if ( AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) { + // give them time to move somewhere else + ocs->obstructingTime = level.time + 1000; + } else { + // make sure they don't call GetAvoid() for another few frames to let others avoid also + ocs->obstructingTime = level.time - 1; + blockEnt = move.touchents[i]; + } + } + } + } + + } else { + return; + } + + if ( blockEnt < 0 ) { + // nothing found to be worth avoding + cs->blockedAvoidTime = level.time - 1; + return; + } + + // something is blocking our path + if ( g_entities[blockEnt].aiName && g_entities[blockEnt].client ) { + int oldId = cs->castScriptStatus.scriptId; + AICast_ScriptEvent( cs, "blocked", g_entities[blockEnt].aiName ); + if ( oldId != cs->castScriptStatus.scriptId ) { + // the script has changed, so assume the scripting is handling the avoidance + return; + } + } + + // avoid geometry and props, but assume clients will get out the way + if ( /*blockEnt > MAX_CLIENTS &&*/ AICast_GetAvoid( cs, goal, pos, qfalse, blockEnt ) ) { + VectorSubtract( pos, cs->bs->cur_ps.origin, dir ); + VectorNormalize( dir ); + cs->blockedAvoidYaw = vectoyaw( dir ); + if ( blockEnt >= MAX_CLIENTS ) { + cs->blockedAvoidTime = level.time + 100 + rand() % 200; + } else { + cs->blockedAvoidTime = level.time + 300 + rand() % 400; + } + } else { + cs->blockedAvoidTime = level.time - 1; // don't look again for another few frames + return; + } + } + + VectorClear( pos ); + pos[YAW] = cs->blockedAvoidYaw; + AngleVectors( pos, dir, NULL, NULL ); + + if ( moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE ) { + trap_EA_Jump( cs->bs->entitynum ); + } + + trap_EA_Move( cs->bs->entitynum, dir, 200 ); //400); + + vectoangles( dir, cs->bs->ideal_viewangles ); + cs->bs->ideal_viewangles[2] *= 0.5; +} + +/* +================ +AICast_EvaluatePmove + + Avoidance after the event (leaders instruct AI's to get out the way, AI's instruct other non-moving AI's to get out the way) +================ +*/ +void AICast_EvaluatePmove( int clientnum, pmove_t *pm ) { + cast_state_t *cs, *ocs; + int i, ent; + bot_goal_t ogoal; + + //vec3_t pos, dir; + cs = AICast_GetCastState( clientnum ); + // make sure we are using the right AAS data for this entity (one's that don't get set will default to the player's AAS data) + trap_AAS_SetCurrentWorld( cs->aasWorldIndex ); + + // NOTE: this is only enabled for real clients, so their followers get out of their way + //if (cs->bs) + // return; + + // look through the touchent's to see if we've bumped into something we should avoid, or react to + for ( i = 0; i < pm->numtouch; i++ ) + { + // mark the time, so they can deal with the obstruction in their own think functions + cs->blockedTime = level.time; + + if ( pm->touchents[i] == pm->ps->groundEntityNum ) { + continue; + } + + // if they are an AI Cast, inform them of our disposition, and hope that they are reasonable + // enough to assist us in our desire to move beyond our current position + if ( pm->touchents[i] < aicast_maxclients ) { + if ( !AICast_EntityVisible( cs, pm->touchents[i], qtrue ) ) { + continue; + } + + // if we are inspecting the body, abort if we touch anything + if ( cs->bs && cs->bs->enemy >= 0 && g_entities[cs->bs->enemy].health <= 0 ) { + cs->bs->enemy = -1; + } + + // anything we touch, should see us + AICast_UpdateVisibility( &g_entities[pm->touchents[i]], &g_entities[cs->entityNum], qfalse, qtrue ); + + ocs = AICast_GetCastState( pm->touchents[i] ); + if ( ( ocs->bs ) && + ( !( ocs->aiFlags & AIFL_NOAVOID ) ) && + ( ( ocs->leaderNum == cs->entityNum ) || ( VectorLength( ocs->bs->velocity ) < 5 ) ) && + ( ocs->obstructingTime < ( level.time + 100 ) ) ) { + // if they are moving away from us already, let them go + if ( VectorLength( ocs->bs->cur_ps.velocity ) > 10 ) { + vec3_t v1, v2; + + VectorSubtract( ocs->bs->origin, g_entities[clientnum].client->ps.velocity, v2 ); + VectorNormalize( v2 ); + VectorNormalize2( ocs->bs->cur_ps.velocity, v1 ); + + if ( DotProduct( v1, v2 ) > 0.0 ) { + continue; + } + } + if ( ocs->leaderNum >= 0 ) { + VectorCopy( g_entities[ocs->leaderNum].r.currentOrigin, ogoal.origin ); + ogoal.areanum = BotPointAreaNum( ogoal.origin ); + ogoal.entitynum = ocs->leaderNum; + if ( ocs->bs && AICast_GetAvoid( ocs, &ogoal, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else + ocs->obstructingTime = level.time + 1000; + } + } else { + if ( ocs->bs && AICast_GetAvoid( ocs, NULL, ocs->obstructingPos, qfalse, cs->entityNum ) ) { // give them time to move somewhere else + ocs->obstructingTime = level.time + 1000; + } + } + } + } else if ( cs->bs ) { + // if we are blocked by a brush entity, see if we can activate it + ent = pm->touchents[i]; + if ( g_entities[ent].s.modelindex > 0 && g_entities[ent].s.eType == ET_MOVER ) { + //find the bsp entity which should be activated in order to remove + //the blocking entity + + if ( !g_entities[ent].isProp + && Q_stricmp( g_entities[ent].classname, "func_static" ) + && Q_stricmp( g_entities[ent].classname, "func_button" ) + && Q_stricmp( g_entities[ent].classname, "func_tram" ) ) { + G_Activate( &g_entities[ent], &g_entities[cs->entityNum] ); + } + + } + } + } +} + +/* +============== +AICast_RequestCrouchAttack +============== +*/ +qboolean AICast_RequestCrouchAttack( cast_state_t *cs, vec3_t org, float time ) { + if ( cs->attributes[ATTACK_CROUCH] > 0 && AICast_CheckAttackAtPos( cs->entityNum, cs->bs->enemy, org, qtrue, qfalse ) ) { + if ( time ) { + cs->bs->attackcrouch_time = trap_AAS_Time() + time; + } + return qtrue; + } + + return qfalse; +} + +/* +============== +AICast_QueryThink +============== +*/ +void AICast_QueryThink( cast_state_t *cs ) { + gentity_t *ent; + qboolean visible; + cast_state_t *ocs; + vec3_t vec; + + ent = &g_entities[cs->entityNum]; + ocs = AICast_GetCastState( cs->bs->enemy ); + + // never crouch while in this state (by choice anyway) + cs->bs->attackcrouch_time = 0; + + // look at where we last (thought we) saw them + VectorSubtract( cs->vislist[cs->bs->enemy].visible_pos, cs->bs->origin, vec ); + VectorNormalize( vec ); + vectoangles( vec, cs->bs->ideal_viewangles ); + + // are they visible now? + visible = AICast_VisibleFromPos( cs->bs->origin, cs->entityNum, g_entities[cs->bs->enemy].r.currentOrigin, cs->bs->enemy, qfalse ); + + // make sure we dont process the sighting of this enemy by going into query mode again, without them being visible again after we leave here + cs->vislist[cs->bs->enemy].flags &= ~AIVIS_PROCESS_SIGHTING; + + // look towards where we last saw them + AICast_AimAtEnemy( cs ); + + // if visible and alert time has expired, go POSTAL + if ( ( cs->queryAlertSightTime < 0 ) || ( ( cs->queryAlertSightTime < level.time ) && visible ) ) { + if ( !cs->queryAlertSightTime ) { + // set the "short reaction" condition + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse ); + } + AICast_StateChange( cs, AISTATE_COMBAT ); + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse ); + AIFunc_BattleStart( cs ); + return; + } + + // if they've fired since the start of the query mode, go POSTAL + if ( ocs->lastWeaponFired > cs->queryStartTime ) { + // set the "short reaction" condition + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qtrue, qfalse ); + AICast_StateChange( cs, AISTATE_COMBAT ); + BG_UpdateConditionValue( cs->entityNum, ANIM_COND_SHORT_REACTION, qfalse, qfalse ); + AIFunc_BattleStart( cs ); + return; + } + + // if not visible, then kill the Lock On timer + if ( ( cs->queryAlertSightTime > 0 ) && !visible ) { + cs->queryAlertSightTime = 0; + } + + // if the query has expired, go back to relaxed + if ( !ent->client->ps.legsTimer ) { + AICast_StateChange( cs, AISTATE_RELAXED ); + } +} + +/* +================ +AICast_DeadClipWalls +================ +*/ +void AICast_DeadClipWalls( cast_state_t *cs ) { +/* + //animation_t *anim; + orientation_t or; + vec3_t src, vel; + trace_t tr; + + // get the death animation we are currently playing + //anim = BG_GetAnimationForIndex( cs->entityNum, (cs->bs->cur_ps.torsoAnim & ~ANIM_TOGGLEBIT) ); + + // find the head position + trap_GetTag( cs->entityNum, "tag_head", &or ); + // move up a tad + or.origin[2] += 3; + + // trace from the base of our bounding box, to the head + VectorCopy( cs->bs->origin, src ); + src[2] -= cs->bs->cur_ps.mins[2] + 3; + trap_Trace( &tr, src, vec3_origin, vec3_origin, or.origin, cs->entityNum, MASK_SOLID ); + + // if we hit something, move away from it + if (!tr.startsolid && !tr.allsolid && tr.fraction < 1.0) { + VectorScale( tr.plane.normal, 80, vel ); + vel[2] = 0; + VectorAdd( g_entities[cs->entityNum].client->ps.velocity, vel, g_entities[cs->entityNum].client->ps.velocity ); + } +*/ +} diff --git a/src/game/be_aas.h b/src/game/be_aas.h new file mode 100644 index 0000000..8b7cac3 --- /dev/null +++ b/src/game/be_aas.h @@ -0,0 +1,192 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_aas.h + * + * desc: Area Awareness System, stuff exported to the AI + * + * + *****************************************************************************/ + +#define MAX_AAS_WORLDS 2 // one for each bounding box type + +#ifndef MAX_STRINGFIELD +#define MAX_STRINGFIELD 80 +#endif + +//travel flags +#define TFL_INVALID 0x0000001 //traveling temporary not possible +#define TFL_WALK 0x0000002 //walking +#define TFL_CROUCH 0x0000004 //crouching +#define TFL_BARRIERJUMP 0x0000008 //jumping onto a barrier +#define TFL_JUMP 0x0000010 //jumping +#define TFL_LADDER 0x0000020 //climbing a ladder +#define TFL_WALKOFFLEDGE 0x0000080 //walking of a ledge +#define TFL_SWIM 0x0000100 //swimming +#define TFL_WATERJUMP 0x0000200 //jumping out of the water +#define TFL_TELEPORT 0x0000400 //teleporting +#define TFL_ELEVATOR 0x0000800 //elevator +#define TFL_ROCKETJUMP 0x0001000 //rocket jumping +#define TFL_BFGJUMP 0x0002000 //bfg jumping +#define TFL_GRAPPLEHOOK 0x0004000 //grappling hook +#define TFL_DOUBLEJUMP 0x0008000 //double jump +#define TFL_RAMPJUMP 0x0010000 //ramp jump +#define TFL_STRAFEJUMP 0x0020000 //strafe jump +#define TFL_JUMPPAD 0x0040000 //jump pad +#define TFL_AIR 0x0080000 //travel through air +#define TFL_WATER 0x0100000 //travel through water +#define TFL_SLIME 0x0200000 //travel through slime +#define TFL_LAVA 0x0400000 //travel through lava +#define TFL_DONOTENTER 0x0800000 //travel through donotenter area +#define TFL_FUNCBOB 0x1000000 //func bobbing +#define TFL_DONOTENTER_LARGE 0x2000000 //travel through donotenter area + +//default travel flags + +//----(SA) modified since slime is no longer deadly +#define TFL_DEFAULT ( TFL_WALK | TFL_CROUCH | TFL_BARRIERJUMP | \ + TFL_JUMP | TFL_LADDER | \ + TFL_WALKOFFLEDGE | TFL_SWIM | TFL_WATERJUMP | \ + TFL_TELEPORT | TFL_ELEVATOR | TFL_AIR | \ + TFL_WATER | TFL_SLIME | \ + TFL_JUMPPAD | TFL_FUNCBOB ) + +typedef enum +{ + SOLID_NOT, // no interaction with other objects + SOLID_TRIGGER, // only touch when inside, after moving + SOLID_BBOX, // touch on edge + SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//a trace is returned when a box is swept through the AAS world +typedef struct aas_trace_s +{ + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + int ent; // entity blocking the trace + int lastarea; // last area the trace was in (zero if none) + int area; // area blocking the trace (zero if none) + int planenum; // number of the plane that was hit +} aas_trace_t; + +/* Defined in botlib.h + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//a trace is returned when a box is swept through the BSP world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // hit surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; +// +*/ + +//entity info +typedef struct aas_entityinfo_s +{ + int valid; // true if updated this frame + int type; // entity type + int flags; // entity flags + float ltime; // local time + float update_time; // time between last and current update + int number; // number of the entity + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t lastvisorigin; // last visible origin + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +// int weapAnim; // mask off ANIM_TOGGLEBIT //----(SA) added +//----(SA) didn't want to comment in as I wasn't sure of any implications of changing this structure. +} aas_entityinfo_t; + + +//client movement prediction stop events, stop as soon as: +#define SE_NONE 0 +#define SE_HITGROUND 1 // the ground is hit +#define SE_LEAVEGROUND 2 // there's no ground +#define SE_ENTERWATER 4 // water is entered +#define SE_ENTERSLIME 8 // slime is entered +#define SE_ENTERLAVA 16 // lava is entered +#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage +#define SE_GAP 64 // there's a gap +#define SE_TOUCHJUMPPAD 128 // touching a jump pad area +#define SE_TOUCHTELEPORTER 256 // touching teleporter +#define SE_ENTERAREA 512 // the given stoparea is entered +#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit + +typedef struct aas_clientmove_s +{ + vec3_t endpos; //position at the end of movement prediction + vec3_t velocity; //velocity at the end of movement prediction + aas_trace_t trace; //last trace + int presencetype; //presence type at end of movement prediction + int stopevent; //event that made the prediction stop + float endcontents; //contents at the end of movement prediction + float time; //time predicted ahead + int frames; //number of frames predicted ahead +} aas_clientmove_t; + +typedef struct aas_altroutegoal_s +{ + vec3_t origin; + int areanum; + unsigned short starttraveltime; + unsigned short goaltraveltime; + unsigned short extratraveltime; +} aas_altroutegoal_t; diff --git a/src/game/be_ai_char.h b/src/game/be_ai_char.h new file mode 100644 index 0000000..96a1a5c --- /dev/null +++ b/src/game/be_ai_char.h @@ -0,0 +1,53 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_char.h + * + * desc: bot characters + * + * + *****************************************************************************/ + +//loads a bot character from a file +int BotLoadCharacter( char *charfile, int skill ); +//frees a bot character +void BotFreeCharacter( int character ); +//float characteristic +float Characteristic_Float( int character, int index ); +//bounded float characteristic +float Characteristic_BFloat( int character, int index, float min, float max ); +//integer characteristic +int Characteristic_Integer( int character, int index ); +//bounded integer characteristic +int Characteristic_BInteger( int character, int index, int min, int max ); +//string characteristic +void Characteristic_String( int character, int index, char *buf, int size ); +//free cached bot characters +void BotShutdownCharacters( void ); diff --git a/src/game/be_ai_chat.h b/src/game/be_ai_chat.h new file mode 100644 index 0000000..835651d --- /dev/null +++ b/src/game/be_ai_chat.h @@ -0,0 +1,118 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_chat.h + * + * desc: char AI + * + * + *****************************************************************************/ + +#define MAX_MESSAGE_SIZE 150 //limit in game dll +#define MAX_CHATTYPE_NAME 32 +#define MAX_MATCHVARIABLES 8 + +#define CHAT_GENDERLESS 0 +#define CHAT_GENDERFEMALE 1 +#define CHAT_GENDERMALE 2 + +#define CHAT_ALL 0 +#define CHAT_TEAM 1 + +//a console message +typedef struct bot_consolemessage_s +{ + int handle; + float time; //message time + int type; //message type + char message[MAX_MESSAGE_SIZE]; //message + struct bot_consolemessage_s *prev, *next; //prev and next in list +} bot_consolemessage_t; + +//match variable +typedef struct bot_matchvariable_s +{ + char *ptr; + int length; +} bot_matchvariable_t; +//returned to AI when a match is found +typedef struct bot_match_s +{ + char string[MAX_MESSAGE_SIZE]; + int type; + int subtype; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; +} bot_match_t; + +//setup the chat AI +int BotSetupChatAI( void ); +//shutdown the chat AI +void BotShutdownChatAI( void ); +//returns the handle to a newly allocated chat state +int BotAllocChatState( void ); +//frees the chatstate +void BotFreeChatState( int handle ); +//adds a console message to the chat state +void BotQueueConsoleMessage( int chatstate, int type, char *message ); +//removes the console message from the chat state +void BotRemoveConsoleMessage( int chatstate, int handle ); +//returns the next console message from the state +int BotNextConsoleMessage( int chatstate, bot_consolemessage_t *cm ); +//returns the number of console messages currently stored in the state +int BotNumConsoleMessages( int chatstate ); +//enters a chat message of the given type +void BotInitialChat( int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ); +//returns the number of initial chat messages of the given type +int BotNumInitialChats( int chatstate, char *type ); +//find a reply for the given message +int BotReplyChat( int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ); +//returns the length of the currently selected chat message +int BotChatLength( int chatstate ); +//enters the selected chat message +void BotEnterChat( int chatstate, int client, int sendto ); +//get the chat message ready to be output +void BotGetChatMessage( int chatstate, char *buf, int size ); +//checks if the first string contains the second one, returns index into first string or -1 if not found +int StringContains( char *str1, char *str2, int casesensitive ); +//finds a match for the given string +int BotFindMatch( char *str, bot_match_t *match, unsigned long int context ); +//returns a variable from a match +void BotMatchVariable( bot_match_t *match, int variable, char *buf, int size ); +//unify all the white spaces in the string +void UnifyWhiteSpaces( char *string ); +//replace all the context related synonyms in the string +void BotReplaceSynonyms( char *string, unsigned long int context ); +//loads a chat file for the chat state +int BotLoadChatFile( int chatstate, char *chatfile, char *chatname ); +//store the gender of the bot in the chat state +void BotSetChatGender( int chatstate, int gender ); +//store the bot name in the chat state +void BotSetChatName( int chatstate, char *name ); + diff --git a/src/game/be_ai_gen.h b/src/game/be_ai_gen.h new file mode 100644 index 0000000..fc77490 --- /dev/null +++ b/src/game/be_ai_gen.h @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_gen.h + * + * desc: genetic selection + * + * + *****************************************************************************/ + +int GeneticParentsAndChildSelection( int numranks, float *ranks, int *parent1, int *parent2, int *child ); diff --git a/src/game/be_ai_goal.h b/src/game/be_ai_goal.h new file mode 100644 index 0000000..c726562 --- /dev/null +++ b/src/game/be_ai_goal.h @@ -0,0 +1,117 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_goal.h + * + * desc: goal AI + * + * + *****************************************************************************/ + +#define MAX_AVOIDGOALS 64 +#define MAX_GOALSTACK 8 + +#define GFL_NONE 0 +#define GFL_ITEM 1 +#define GFL_ROAM 2 +#define GFL_NOSLOWAPPROACH 4 + +//a bot goal +typedef struct bot_goal_s +{ + vec3_t origin; //origin of the goal + int areanum; //area number of the goal + vec3_t mins, maxs; //mins and maxs of the goal + int entitynum; //number of the goal entity + int number; //goal number + int flags; //goal flags + int iteminfo; //item information +} bot_goal_t; + +//reset the whole goal state, but keep the item weights +void BotResetGoalState( int goalstate ); +//reset avoid goals +void BotResetAvoidGoals( int goalstate ); +//remove the goal with the given number from the avoid goals +void BotRemoveFromAvoidGoals( int goalstate, int number ); +//push a goal +void BotPushGoal( int goalstate, bot_goal_t *goal ); +//pop a goal +void BotPopGoal( int goalstate ); +//makes the bot's goal stack empty +void BotEmptyGoalStack( int goalstate ); +//dump the avoid goals +void BotDumpAvoidGoals( int goalstate ); +//dump the goal stack +void BotDumpGoalStack( int goalstate ); +//name of the goal +void BotGoalName( int number, char *name, int size ); +//get goal from top of stack +int BotGetTopGoal( int goalstate, bot_goal_t *goal ); +int BotGetSecondGoal( int goalstate, bot_goal_t *goal ); +//choose the best long term goal item for the bot +int BotChooseLTGItem( int goalstate, vec3_t origin, int *inventory, int travelflags ); +//choose the best nearby goal item for the bot +int BotChooseNBGItem( int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime ); +//returns true if the bot touches the goal +int BotTouchingGoal( vec3_t origin, bot_goal_t *goal ); +//returns true if the goal should be visible but isn't +int BotItemGoalInVisButNotVisible( int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal ); +//get some info about a level item +int BotGetLevelItemGoal( int index, char *classname, bot_goal_t *goal ); +//get the next camp spot in the map +int BotGetNextCampSpotGoal( int num, bot_goal_t *goal ); +//get the map location with the given name +int BotGetMapLocationGoal( char *name, bot_goal_t *goal ); +//returns the avoid goal time +float BotAvoidGoalTime( int goalstate, int number ); +//initializes the items in the level +void BotInitLevelItems( void ); +//regularly update dynamic entity items (dropped weapons, flags etc.) +void BotUpdateEntityItems( void ); +//interbreed the goal fuzzy logic +void BotInterbreedGoalFuzzyLogic( int parent1, int parent2, int child ); +//save the goal fuzzy logic to disk +void BotSaveGoalFuzzyLogic( int goalstate, char *filename ); +//mutate the goal fuzzy logic +void BotMutateGoalFuzzyLogic( int goalstate, float range ); +//loads item weights for the bot +int BotLoadItemWeights( int goalstate, char *filename ); +//frees the item weights of the bot +void BotFreeItemWeights( int goalstate ); +//returns the handle of a newly allocated goal state +int BotAllocGoalState( int client ); +//free the given goal state +void BotFreeGoalState( int handle ); +//setup the goal AI +int BotSetupGoalAI( void ); +//shut down the goal AI +void BotShutdownGoalAI( void ); diff --git a/src/game/be_ai_move.h b/src/game/be_ai_move.h new file mode 100644 index 0000000..2434cba --- /dev/null +++ b/src/game/be_ai_move.h @@ -0,0 +1,133 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_move.h + * + * desc: movement AI + * + * + *****************************************************************************/ + +//movement types +#define MOVE_WALK 1 +#define MOVE_CROUCH 2 +#define MOVE_JUMP 4 +#define MOVE_GRAPPLE 8 +#define MOVE_ROCKETJUMP 16 +#define MOVE_BFGJUMP 32 +//move flags +#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump +#define MFL_ONGROUND 2 //bot is in the ground +#define MFL_SWIMMING 4 //bot is swimming +#define MFL_AGAINSTLADDER 8 //bot is against a ladder +#define MFL_WATERJUMP 16 //bot is waterjumping +#define MFL_TELEPORTED 32 //bot is being teleported +#define MFL_ACTIVEGRAPPLE 64 //bot is using the grapple hook +#define MFL_GRAPPLERESET 128 //bot has reset the grapple +#define MFL_WALK 256 //bot should walk slowly +//move result flags +#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement +#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming +#define MOVERESULT_WAITING 4 //bot is waiting for something +#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code +#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement +#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle +#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing +#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) +#define MOVERESULT_FUTUREVIEW 256 // RF, if we want to look ahead of time, this is a good direction + +// +#define MAX_AVOIDREACH 1 +// +#define RESULTTYPE_ELEVATORUP 1 +#define RESULTTYPE_INVISIBLEGRAPPLE 2 + +//structure used to initialize the movement state +//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate +typedef struct bot_initmove_s +{ + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + int or_moveflags; //values ored to the movement flags +} bot_initmove_t; + +//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set +typedef struct bot_moveresult_s +{ + int failure; //true if movement failed all together + int type; //failure or blocked type + int blocked; //true if blocked by an entity + int blockentity; //entity blocking the bot + int traveltype; //last executed travel type + int flags; //result flags + int weapon; //weapon used for movement + vec3_t movedir; //movement direction + vec3_t ideal_viewangles; //ideal viewangles for the movement +} bot_moveresult_t; + +//resets the whole movestate +void BotResetMoveState( int movestate ); +//moves the bot to the given goal +void BotMoveToGoal( bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags ); +//moves the bot in the specified direction +int BotMoveInDirection( int movestate, vec3_t dir, float speed, int type ); +//reset avoid reachability +void BotResetAvoidReach( int movestate ); +//resets the last avoid reachability +void BotResetLastAvoidReach( int movestate ); +//returns a reachability area if the origin is in one +int BotReachabilityArea( vec3_t origin, int client ); +//view target based on movement +int BotMovementViewTarget( int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target ); +//predict the position of a player +int BotPredictVisiblePosition( vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target ); +//returns the handle of a newly allocated movestate +int BotAllocMoveState( void ); +//frees the movestate with the given handle +void BotFreeMoveState( int handle ); +//initialize movement state +void BotInitMoveState( int handle, bot_initmove_t *initmove ); +//must be called every map change +void BotSetBrushModelTypes( void ); +//setup movement AI +int BotSetupMoveAI( void ); +//shutdown movement AI +void BotShutdownMoveAI( void ); + +// Ridah +//initialize avoid reachabilities +void BotInitAvoidReach( int handle ); +// done. diff --git a/src/game/be_ai_weap.h b/src/game/be_ai_weap.h new file mode 100644 index 0000000..bf77d23 --- /dev/null +++ b/src/game/be_ai_weap.h @@ -0,0 +1,109 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ai_weap.h + * + * desc: weapon AI + * + * + *****************************************************************************/ + +//projectile flags +#define PFL_WINDOWDAMAGE 1 //projectile damages through window +#define PFL_RETURN 2 //set when projectile returns to owner +//weapon flags +#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event +//damage types +#define DAMAGETYPE_IMPACT 1 //damage on impact +#define DAMAGETYPE_RADIAL 2 //radial damage +#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile + +typedef struct projectileinfo_s +{ + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int flags; + float gravity; + int damage; + float radius; + int visdamage; + int damagetype; + int healthinc; + float push; + float detonation; + float bounce; + float bouncefric; + float bouncestop; +} projectileinfo_t; + +typedef struct weaponinfo_s +{ + int valid; //true if the weapon info is valid + int number; //number of the weapon + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int level; + int weaponindex; + int flags; + char projectile[MAX_STRINGFIELD]; + int numprojectiles; + float hspread; + float vspread; + float speed; + float acceleration; + vec3_t recoil; + vec3_t offset; + vec3_t angleoffset; + float extrazvelocity; + int ammoamount; + int ammoindex; + float activate; + float reload; + float spinup; + float spindown; + projectileinfo_t proj; //pointer to the used projectile +} weaponinfo_t; + +//setup the weapon AI +int BotSetupWeaponAI( void ); +//shut down the weapon AI +void BotShutdownWeaponAI( void ); +//returns the best weapon to fight with +int BotChooseBestFightWeapon( int weaponstate, int *inventory ); +//returns the information of the current weapon +void BotGetWeaponInfo( int weaponstate, int weapon, weaponinfo_t *weaponinfo ); +//loads the weapon weights +int BotLoadWeaponWeights( int weaponstate, char *filename ); +//returns a handle to a newly allocated weapon state +int BotAllocWeaponState( void ); +//frees the weapon state +void BotFreeWeaponState( int weaponstate ); +//resets the whole weapon state +void BotResetWeaponState( int weaponstate ); diff --git a/src/game/be_ea.h b/src/game/be_ea.h new file mode 100644 index 0000000..a105ede --- /dev/null +++ b/src/game/be_ea.h @@ -0,0 +1,72 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: be_ea.h + * + * desc: elementary actions + * + * + *****************************************************************************/ + +//ClientCommand elementary actions +void EA_Say( int client, char *str ); +void EA_SayTeam( int client, char *str ); +void EA_UseItem( int client, char *it ); +void EA_DropItem( int client, char *it ); +void EA_UseInv( int client, char *inv ); +void EA_DropInv( int client, char *inv ); +void EA_Command( int client, char *command ); +//regular elementary actions +void EA_SelectWeapon( int client, int weapon ); +void EA_Attack( int client ); +void EA_Reload( int client ); +void EA_Respawn( int client ); +void EA_Talk( int client ); +void EA_Gesture( int client ); +void EA_Use( int client ); +void EA_Jump( int client ); +void EA_DelayedJump( int client ); +void EA_Crouch( int client ); +void EA_Walk( int client ); +void EA_MoveUp( int client ); +void EA_MoveDown( int client ); +void EA_MoveForward( int client ); +void EA_MoveBack( int client ); +void EA_MoveLeft( int client ); +void EA_MoveRight( int client ); +void EA_Move( int client, vec3_t dir, float speed ); +void EA_View( int client, vec3_t viewangles ); +//send regular input to the server +void EA_EndRegular( int client, float thinktime ); +void EA_GetInput( int client, float thinktime, bot_input_t *input ); +void EA_ResetInput( int client, bot_input_t *init ); +//setup and shutdown routines +int EA_Setup( void ); +void EA_Shutdown( void ); diff --git a/src/game/bg_animation.c b/src/game/bg_animation.c new file mode 100644 index 0000000..da50e7d --- /dev/null +++ b/src/game/bg_animation.c @@ -0,0 +1,2020 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +//=========================================================================== +// bg_animation.c +// +// Incorporates several elements related to the new flexible animation system. +// +// This includes scripting elements, support routines for new animation set +// reference system and other bits and pieces. +// +//=========================================================================== + +#include "q_shared.h" +#include "bg_public.h" + +// JPW NERVE -- added because I need to check single/multiplayer instances and branch accordingly +#ifdef CGAMEDLL +extern vmCvar_t cg_gameType; +#endif +#ifdef GAMEDLL +extern vmCvar_t g_gametype; +#endif + +// debug defines, to prevent doing costly string cvar lookups +//#define DBGANIMS +//#define DBGANIMEVENTS + +// this is used globally within this file to reduce redundant params +static animScriptData_t *globalScriptData = NULL; + +#define MAX_ANIM_DEFINES 16 + +static char *globalFilename; // to prevent redundant params +static int parseClient; + +// these are used globally during script parsing +static int numDefines[NUM_ANIM_CONDITIONS]; +static char defineStrings[10000]; // stores the actual strings +static int defineStringsOffset; +static animStringItem_t defineStr[NUM_ANIM_CONDITIONS][MAX_ANIM_DEFINES]; +static int defineBits[NUM_ANIM_CONDITIONS][MAX_ANIM_DEFINES][2]; + +static scriptAnimMoveTypes_t parseMovetype; +static int parseEvent; + +animStringItem_t weaponStrings[WP_NUM_WEAPONS]; +qboolean weaponStringsInited = qfalse; + +animStringItem_t animStateStr[] = +{ + {"RELAXED", -1}, + {"QUERY", -1}, + {"ALERT", -1}, + {"COMBAT", -1}, + + {NULL, -1}, +}; + +static animStringItem_t animMoveTypesStr[] = +{ + {"** UNUSED **", -1}, + {"IDLE", -1}, + {"IDLECR", -1}, + {"WALK", -1}, + {"WALKBK", -1}, + {"WALKCR", -1}, + {"WALKCRBK", -1}, + {"RUN", -1}, + {"RUNBK", -1}, + {"SWIM", -1}, + {"SWIMBK", -1}, + {"STRAFERIGHT", -1}, + {"STRAFELEFT", -1}, + {"TURNRIGHT", -1}, + {"TURNLEFT", -1}, + {"CLIMBUP", -1}, + {"CLIMBDOWN", -1}, + {"FALLEN", -1}, // DHM - Nerve :: dead, before limbo + + {NULL, -1}, +}; + +static animStringItem_t animEventTypesStr[] = +{ + {"PAIN", -1}, + {"DEATH", -1}, + {"FIREWEAPON", -1}, + {"JUMP", -1}, + {"JUMPBK", -1}, + {"LAND", -1}, + {"DROPWEAPON", -1}, + {"RAISEWEAPON", -1}, + {"CLIMBMOUNT", -1}, + {"CLIMBDISMOUNT", -1}, + {"RELOAD", -1}, + {"PICKUPGRENADE", -1}, + {"KICKGRENADE", -1}, + {"QUERY", -1}, + {"INFORM_FRIENDLY_OF_ENEMY", -1}, + {"KICK", -1}, + {"REVIVE", -1}, + {"FIRSTSIGHT", -1}, + {"ROLL", -1}, + {"FLIP", -1}, + {"DIVE", -1}, + {"PRONE_TO_CROUCH", -1}, + {"BULLETIMPACT", -1}, + {"INSPECTSOUND", -1}, + + {NULL, -1}, +}; + +animStringItem_t animBodyPartsStr[] = +{ + {"** UNUSED **", -1}, + {"LEGS", -1}, + {"TORSO", -1}, + {"BOTH", -1}, + + {NULL, -1}, +}; + +//------------------------------------------------------------ +// conditions + +static animStringItem_t animConditionPositionsStr[] = +{ + {"** UNUSED **", -1}, + {"BEHIND", -1}, + {"INFRONT", -1}, + {"RIGHT", -1}, + {"LEFT", -1}, + + {NULL, -1}, +}; + +static animStringItem_t animConditionMountedStr[] = +{ + {"** UNUSED **", -1}, + {"MG42", -1}, + + {NULL, -1}, +}; + +static animStringItem_t animConditionLeaningStr[] = +{ + {"** UNUSED **", -1}, + {"RIGHT", -1}, + {"LEFT", -1}, + + {NULL, -1}, +}; + +// !!! NOTE: this must be kept in sync with the tag names in ai_cast_characters.c +static animStringItem_t animConditionImpactPointsStr[] = +{ + {"** UNUSED **", -1}, + {"HEAD", -1}, + {"CHEST", -1}, + {"GUT", -1}, + {"GROIN", -1}, + {"SHOULDER_RIGHT", -1}, + {"SHOULDER_LEFT", -1}, + {"KNEE_RIGHT", -1}, + {"KNEE_LEFT", -1}, + + {NULL, -1}, +}; + +// !!! NOTE: this must be kept in sync with the teams in ai_cast.h +static animStringItem_t animEnemyTeamsStr[] = +{ + {"NAZI", -1}, + {"ALLIES", -1}, + {"MONSTER", -1}, + {"SPARE1", -1}, + {"SPARE2", -1}, + {"SPARE3", -1}, + {"SPARE4", -1}, + {"NEUTRAL", -1} +}; + +static animStringItem_t animHealthLevelStr[] = +{ + {"1", -1}, + {"2", -1}, + {"3", -1}, +}; + +typedef enum +{ + ANIM_CONDTYPE_BITFLAGS, + ANIM_CONDTYPE_VALUE, + + NUM_ANIM_CONDTYPES +} animScriptConditionTypes_t; + +typedef struct +{ + animScriptConditionTypes_t type; + animStringItem_t *values; +} animConditionTable_t; + +static animStringItem_t animConditionsStr[] = +{ + {"WEAPONS", -1}, + {"ENEMY_POSITION", -1}, + {"ENEMY_WEAPON", -1}, + {"UNDERWATER", -1}, + {"MOUNTED", -1}, + {"MOVETYPE", -1}, + {"UNDERHAND", -1}, + {"LEANING", -1}, + {"IMPACT_POINT", -1}, + {"CROUCHING", -1}, + {"STUNNED", -1}, + {"FIRING", -1}, + {"SHORT_REACTION", -1}, + {"ENEMY_TEAM", -1}, + {"PARACHUTE", -1}, + {"CHARGING", -1}, + {"SECONDLIFE", -1}, + {"HEALTH_LEVEL", -1}, + + {NULL, -1}, +}; + +static animConditionTable_t animConditionsTable[NUM_ANIM_CONDITIONS] = +{ + {ANIM_CONDTYPE_BITFLAGS, weaponStrings}, + {ANIM_CONDTYPE_BITFLAGS, animConditionPositionsStr}, + {ANIM_CONDTYPE_BITFLAGS, weaponStrings}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, animConditionMountedStr}, + {ANIM_CONDTYPE_BITFLAGS, animMoveTypesStr}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, animConditionLeaningStr}, + {ANIM_CONDTYPE_VALUE, animConditionImpactPointsStr}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, animEnemyTeamsStr}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, NULL}, + {ANIM_CONDTYPE_VALUE, animHealthLevelStr}, +}; + +//------------------------------------------------------------ + +/* +================ +return a hash value for the given string +================ +*/ +static long BG_StringHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + hash += (long)( letter ) * ( i + 119 ); + i++; + } + if ( hash == -1 ) { + hash = 0; // never return -1 + } + return hash; +} + +/* +================= +BG_AnimParseError +================= +*/ +void QDECL BG_AnimParseError( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + if ( globalFilename ) { + Com_Error( ERR_DROP, "%s: (%s, line %i)", text, globalFilename, COM_GetCurrentParseLine() + 1 ); + } else { + Com_Error( ERR_DROP, "%s", text ); + } +} + +/* +================= +BG_ModelInfoForClient +================= +*/ +animModelInfo_t *BG_ModelInfoForClient( int client ) { + if ( !globalScriptData ) { + BG_AnimParseError( "BG_ModelInfoForClient: NULL globalScriptData" ); + } + // + if ( !globalScriptData->clientModels[client] ) { + BG_AnimParseError( "BG_ModelInfoForClient: client %i has no modelinfo", client ); + } + // + return &globalScriptData->modelInfo[globalScriptData->clientModels[client] - 1]; +} + +/* +================= +BG_ModelInfoForModelname +================= +*/ +animModelInfo_t *BG_ModelInfoForModelname( char *modelname ) { + int i; + animModelInfo_t *modelInfo; + // + if ( !globalScriptData ) { + BG_AnimParseError( "BG_ModelInfoForModelname: NULL globalScriptData" ); + } + // + for ( i = 0, modelInfo = globalScriptData->modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, modelInfo++ ) { + if ( !modelInfo->modelname[0] ) { + continue; + } + if ( !Q_stricmp( modelname, modelInfo->modelname ) ) { + return modelInfo; + } + } + // + return NULL; +} + +/* +================= +BG_AnimationIndexForString +================= +*/ +int BG_AnimationIndexForString( char *string, int client ) { + int i, hash; + animation_t *anim; + animModelInfo_t *modelInfo; + + modelInfo = BG_ModelInfoForClient( client ); + + hash = BG_StringHashValue( string ); + + for ( i = 0, anim = modelInfo->animations; i < modelInfo->numAnimations; i++, anim++ ) { + if ( ( hash == anim->nameHash ) && !Q_stricmp( string, anim->name ) ) { + // found a match + return i; + } + } + // no match found + BG_AnimParseError( "BG_AnimationIndexForString: unknown index '%s' for model '%s'", string, modelInfo->modelname ); + return -1; // shutup compiler +} + +/* +================= +BG_AnimationForString +================= +*/ +animation_t *BG_AnimationForString( char *string, animModelInfo_t *modelInfo ) { + int i, hash; + animation_t *anim; + + hash = BG_StringHashValue( string ); + + for ( i = 0, anim = modelInfo->animations; i < modelInfo->numAnimations; i++, anim++ ) { + if ( ( hash == anim->nameHash ) && !Q_stricmp( string, anim->name ) ) { + // found a match + return anim; + } + } + // no match found + Com_Error( ERR_DROP, "BG_AnimationForString: unknown animation '%s' for model '%s'", string, modelInfo->modelname ); + return NULL; // shutup compiler +} + +/* +================= +BG_IndexForString + + errors out if no match found +================= +*/ +int BG_IndexForString( char *token, animStringItem_t *strings, qboolean allowFail ) { + int i, hash; + animStringItem_t *strav; + + hash = BG_StringHashValue( token ); + + for ( i = 0, strav = strings; strav->string; strav++, i++ ) { + if ( strav->hash == -1 ) { + strav->hash = BG_StringHashValue( strav->string ); + } + if ( ( hash == strav->hash ) && !Q_stricmp( token, strav->string ) ) { + // found a match + return i; + } + } + // no match found + if ( !allowFail ) { + BG_AnimParseError( "BG_IndexForString: unknown token '%s'", token ); + } + // + return -1; +} + +/* +=============== +BG_CopyStringIntoBuffer +=============== +*/ +char *BG_CopyStringIntoBuffer( char *string, char *buffer, int bufSize, int *offset ) { + char *pch; + + // check for overloaded buffer + if ( *offset + strlen( string ) + 1 >= bufSize ) { + BG_AnimParseError( "BG_CopyStringIntoBuffer: out of buffer space" ); + } + + pch = &buffer[*offset]; + + // safe to do a strcpy since we've already checked for overrun + strcpy( pch, string ); + + // move the offset along + *offset += strlen( string ) + 1; + + return pch; +} + +/* +============ +BG_InitWeaponStrings + + Builds the list of weapon names from the item list. This is done here rather + than hardcoded to ease the process of modifying the weapons. +============ +*/ +void BG_InitWeaponStrings( void ) { + int i; + gitem_t *item; + + memset( weaponStrings, 0, sizeof( weaponStrings ) ); + + for ( i = 0; i < WP_NUM_WEAPONS; i++ ) { + // find this weapon in the itemslist, and extract the name + for ( item = bg_itemlist + 1; item->classname; item++ ) { + if ( item->giType == IT_WEAPON && item->giTag == i ) { + // found a match + weaponStrings[i].string = item->pickup_name; + weaponStrings[i].hash = BG_StringHashValue( weaponStrings[i].string ); + break; + } + } + + if ( !item->classname ) { + weaponStrings[i].string = "(unknown)"; + weaponStrings[i].hash = BG_StringHashValue( weaponStrings[i].string ); + } + } + + weaponStringsInited = qtrue; +} + +/* +============ +BG_AnimParseAnimConfig + + returns qfalse if error, qtrue otherwise +============ +*/ +qboolean BG_AnimParseAnimConfig( animModelInfo_t *animModelInfo, const char *filename, const char *input ) { + char *text_p, *token; + animation_t *animations; + headAnimation_t *headAnims; + int i, fps, skip = -1; + + if ( !weaponStringsInited ) { + BG_InitWeaponStrings(); + } + + globalFilename = (char *)filename; + + animations = animModelInfo->animations; + animModelInfo->numAnimations = 0; + headAnims = animModelInfo->headAnims; + + text_p = (char *)input; + COM_BeginParseSession( "BG_AnimParseAnimConfig" ); + + animModelInfo->footsteps = FOOTSTEP_NORMAL; + VectorClear( animModelInfo->headOffset ); + animModelInfo->gender = GENDER_MALE; + animModelInfo->isSkeletal = qfalse; + animModelInfo->version = 0; + + // read optional parameters + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { + animModelInfo->footsteps = FOOTSTEP_NORMAL; + } else if ( !Q_stricmp( token, "boot" ) ) { + animModelInfo->footsteps = FOOTSTEP_BOOT; + } else if ( !Q_stricmp( token, "flesh" ) ) { + animModelInfo->footsteps = FOOTSTEP_FLESH; + } else if ( !Q_stricmp( token, "mech" ) ) { + animModelInfo->footsteps = FOOTSTEP_MECH; + } else if ( !Q_stricmp( token, "energy" ) ) { + animModelInfo->footsteps = FOOTSTEP_ENERGY; + } else { + BG_AnimParseError( "Bad footsteps parm '%s'\n", token ); + } + continue; + } else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animModelInfo->headOffset[i] = atof( token ); + } + continue; + } else if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + animModelInfo->gender = GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + animModelInfo->gender = GENDER_NEUTER; + } else { + animModelInfo->gender = GENDER_MALE; + } + continue; + } else if ( !Q_stricmp( token, "version" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animModelInfo->version = atoi( token ); + continue; + } else if ( !Q_stricmp( token, "skeletal" ) ) { + animModelInfo->isSkeletal = qtrue; + continue; + } + + if ( animModelInfo->version < 2 ) { + // if it is a number, start parsing animations + if ( token[0] >= '0' && token[0] <= '9' ) { + text_p -= strlen( token ); // unget the token + break; + } + } + + // STARTANIMS marks the start of the animations + if ( !Q_stricmp( token, "STARTANIMS" ) ) { + break; + } + BG_AnimParseError( "unknown token '%s'", token ); + } + + // read information for each frame + for ( i = 0 ; ( animModelInfo->version > 1 ) || ( i < MAX_ANIMATIONS ) ; i++ ) { + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + + if ( animModelInfo->version > 1 ) { // includes animation names at start of each line + + if ( !Q_stricmp( token, "ENDANIMS" ) ) { // end of animations + break; + } + + Q_strncpyz( animations[i].name, token, sizeof( animations[i].name ) ); + // convert to all lower case + Q_strlwr( animations[i].name ); + // + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "end of file without ENDANIMS" ); + } + } else { + // just set it to the equivalent animStrings[] + Q_strncpyz( animations[i].name, animStrings[i], sizeof( animations[i].name ) ); + // convert to all lower case + Q_strlwr( animations[i].name ); + } + + animations[i].firstFrame = atoi( token ); + + if ( !animModelInfo->isSkeletal ) { // skeletal models dont require adjustment + + // leg only frames are adjusted to not count the upper body only frames + if ( i == LEGS_WALKCR ) { + skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; + } + if ( i >= LEGS_WALKCR ) { + animations[i].firstFrame -= skip; + } + + } + + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "end of file without ENDANIMS" ); + } + animations[i].numFrames = atoi( token ); + + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "end of file without ENDANIMS: line %i" ); + } + animations[i].loopFrames = atoi( token ); + + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "end of file without ENDANIMS: line %i" ); + } + fps = atof( token ); + if ( fps == 0 ) { + fps = 1; + } + animations[i].frameLerp = 1000 / fps; + animations[i].initialLerp = 1000 / fps; + + // movespeed + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "end of file without ENDANIMS" ); + } + animations[i].moveSpeed = atoi( token ); + + // animation blending + token = COM_ParseExt( &text_p, qfalse ); // must be on same line + if ( !token ) { + animations[i].animBlend = 0; + } else { + animations[i].animBlend = atoi( token ); + } + + // calculate the duration + animations[i].duration = animations[i].initialLerp + + animations[i].frameLerp * animations[i].numFrames + + animations[i].animBlend; + + // get the nameHash + animations[i].nameHash = BG_StringHashValue( animations[i].name ); + + if ( !Q_strncmp( animations[i].name, "climb", 5 ) ) { + animations[i].flags |= ANIMFL_LADDERANIM; + } + if ( strstr( animations[i].name, "firing" ) ) { + animations[i].flags |= ANIMFL_FIRINGANIM; + animations[i].initialLerp = 40; + } + + } + + animModelInfo->numAnimations = i; + + if ( animModelInfo->version < 2 && i != MAX_ANIMATIONS ) { + BG_AnimParseError( "Incorrect number of animations" ); + return qfalse; + } + + // check for head anims + token = COM_Parse( &text_p ); + if ( token && token[0] ) { + if ( animModelInfo->version < 2 || !Q_stricmp( token, "HEADFRAMES" ) ) { + + // read information for each head frame + for ( i = 0 ; i < MAX_HEAD_ANIMS ; i++ ) { + + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) { + break; + } + + if ( animModelInfo->version > 1 ) { // includes animation names at start of each line + // just throw this information away, not required for head + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + break; + } + } + + if ( !i ) { + skip = atoi( token ); + } + + headAnims[i].firstFrame = atoi( token ); + // modify according to last frame of the main animations, since the head is totally seperate + headAnims[i].firstFrame -= animations[MAX_ANIMATIONS - 1].firstFrame + animations[MAX_ANIMATIONS - 1].numFrames + skip; + + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + break; + } + headAnims[i].numFrames = atoi( token ); + + // skip the movespeed + token = COM_ParseExt( &text_p, qfalse ); + } + + animModelInfo->numHeadAnims = i; + + if ( i != MAX_HEAD_ANIMS ) { + BG_AnimParseError( "Incorrect number of head frames" ); + return qfalse; + } + + } + } + + return qtrue; +} + +/* +================= +BG_ParseConditionBits + + convert the string into a single int containing bit flags, stopping at a ',' or end of line +================= +*/ +void BG_ParseConditionBits( char **text_pp, animStringItem_t *stringTable, int condIndex, int result[2] ) { + qboolean endFlag = qfalse; + int indexFound; + int /*indexBits,*/ tempBits[2]; + char currentString[MAX_QPATH]; + qboolean minus = qfalse; + char *token; + + //indexBits = 0; + currentString[0] = '\0'; + memset( result, 0, sizeof( result ) ); + memset( tempBits, 0, sizeof( tempBits ) ); + + while ( !endFlag ) { + + token = COM_ParseExt( text_pp, qfalse ); + if ( !token || !token[0] ) { + COM_RestoreParseSession( text_pp ); // go back to the previous token + endFlag = qtrue; // done parsing indexes + if ( !strlen( currentString ) ) { + break; + } + } + + if ( !Q_stricmp( token, "," ) ) { + endFlag = qtrue; // end of indexes + } + + if ( !Q_stricmp( token, "none" ) ) { // first bit is always the "unused" bit + COM_BitSet( result, 0 ); + continue; + } + + if ( !Q_stricmp( token, "none," ) ) { // first bit is always the "unused" bit + COM_BitSet( result, 0 ); + endFlag = qtrue; // end of indexes + continue; + } + + if ( !Q_stricmp( token, "NOT" ) ) { + token = "MINUS"; // NOT is equivalent to MINUS + } + + if ( !endFlag && Q_stricmp( token, "AND" ) && Q_stricmp( token, "MINUS" ) ) { // must be a index + // check for a comma (end of indexes) + if ( token[strlen( token ) - 1] == ',' ) { + endFlag = qtrue; + token[strlen( token ) - 1] = '\0'; + } + // append this to the currentString + if ( strlen( currentString ) ) { + Q_strcat( currentString, sizeof( currentString ), " " ); + } + Q_strcat( currentString, sizeof( currentString ), token ); + } + + if ( !Q_stricmp( token, "AND" ) || !Q_stricmp( token, "MINUS" ) || endFlag ) { + // process the currentString + if ( !strlen( currentString ) ) { + if ( endFlag ) { + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected end of condition" ); + } else { + // check for minus indexes to follow + if ( !Q_stricmp( token, "MINUS" ) ) { + minus = qtrue; + continue; + } + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); + } + } + if ( !Q_stricmp( currentString, "all" ) ) { + tempBits[0] = ~0x0; + tempBits[1] = ~0x0; + } else { + // first check this string with our defines + indexFound = BG_IndexForString( currentString, defineStr[condIndex], qtrue ); + if ( indexFound >= 0 ) { + // we have precalculated the bitflags for the defines + tempBits[0] = defineBits[condIndex][indexFound][0]; + tempBits[1] = defineBits[condIndex][indexFound][1]; + } else { + // convert the string into an index + indexFound = BG_IndexForString( currentString, stringTable, qfalse ); + // convert the index into a bitflag + COM_BitSet( tempBits, indexFound ); + } + } + // perform operation + if ( minus ) { // subtract + result[0] &= ~tempBits[0]; + result[1] &= ~tempBits[1]; + } else { // add + result[0] |= tempBits[0]; + result[1] |= tempBits[1]; + } + // clear the currentString + currentString[0] = '\0'; + // check for minus indexes to follow + if ( !Q_stricmp( token, "MINUS" ) ) { + minus = qtrue; + } + } + + } +} + +/* +================= +BG_ParseConditions + + returns qtrue if everything went ok, error drops otherwise +================= +*/ +qboolean BG_ParseConditions( char **text_pp, animScriptItem_t *scriptItem ) { + int conditionIndex, conditionValue[2]; + char *token; + + conditionValue[0] = 0; + conditionValue[1] = 0; + + while ( 1 ) { + + token = COM_ParseExt( text_pp, qfalse ); + if ( !token || !token[0] ) { + break; + } + + // special case, "default" has no conditions + if ( !Q_stricmp( token, "default" ) ) { + return qtrue; + } + + conditionIndex = BG_IndexForString( token, animConditionsStr, qfalse ); + + switch ( animConditionsTable[conditionIndex].type ) { + case ANIM_CONDTYPE_BITFLAGS: + BG_ParseConditionBits( text_pp, animConditionsTable[conditionIndex].values, conditionIndex, conditionValue ); + break; + case ANIM_CONDTYPE_VALUE: + if ( animConditionsTable[conditionIndex].values ) { + token = COM_ParseExt( text_pp, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected condition value, found end of line" ); // RF modification + } + // check for a comma (condition divider) + if ( token[strlen( token ) - 1] == ',' ) { + token[strlen( token ) - 1] = '\0'; + } + conditionValue[0] = BG_IndexForString( token, animConditionsTable[conditionIndex].values, qfalse ); + } else { + conditionValue[0] = 1; // not used, just check for a positive condition + } + break; + default: // TTimo gcc: NUM_ANIM_CONDTYPES not handled in switch + break; + } + + // now append this condition to the item + scriptItem->conditions[scriptItem->numConditions].index = conditionIndex; + scriptItem->conditions[scriptItem->numConditions].value[0] = conditionValue[0]; + scriptItem->conditions[scriptItem->numConditions].value[1] = conditionValue[1]; + scriptItem->numConditions++; + } + + if ( scriptItem->numConditions == 0 ) { + BG_AnimParseError( "BG_ParseConditions: no conditions found" ); // RF mod + } + + return qtrue; +} + +/* +================= +BG_ParseCommands +================= +*/ +void BG_ParseCommands( char **input, animScriptItem_t *scriptItem, animModelInfo_t *modelInfo, animScriptData_t *scriptData ) { + char *token; + // TTimo gcc: might be used uninitialized + animScriptCommand_t *command = NULL; + int partIndex = 0; + + while ( 1 ) { + + // parse the body part + token = COM_ParseExt( input, ( partIndex < 1 ) ); + if ( !token || !token[0] ) { + break; + } + if ( !Q_stricmp( token, "}" ) ) { + // unget the bracket and get out of here + *input -= strlen( token ); + break; + } + + // new command? + if ( partIndex == 0 ) { + // have we exceeded the maximum number of commands? + if ( scriptItem->numCommands >= MAX_ANIMSCRIPT_ANIMCOMMANDS ) { + BG_AnimParseError( "BG_ParseCommands: exceeded maximum number of animations (%i)", MAX_ANIMSCRIPT_ANIMCOMMANDS ); + } + command = &scriptItem->commands[scriptItem->numCommands++]; + memset( command, 0, sizeof( command ) ); + } + + command->bodyPart[partIndex] = BG_IndexForString( token, animBodyPartsStr, qtrue ); + if ( command->bodyPart[partIndex] > 0 ) { + // parse the animation + token = COM_ParseExt( input, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "BG_ParseCommands: expected animation" ); + } + command->animIndex[partIndex] = BG_AnimationIndexForString( token, parseClient ); + command->animDuration[partIndex] = modelInfo->animations[command->animIndex[partIndex]].duration; + // if this is a locomotion, set the movetype of the animation so we can reverse engineer the movetype from the animation, on the client + if ( parseMovetype != ANIM_MT_UNUSED && command->bodyPart[partIndex] != ANIM_BP_TORSO ) { + modelInfo->animations[command->animIndex[partIndex]].movetype |= ( 1 << parseMovetype ); + } + // if this is a fireweapon event, then this is a firing animation + if ( parseEvent == ANIM_ET_FIREWEAPON ) { + modelInfo->animations[command->animIndex[partIndex]].flags |= ANIMFL_FIRINGANIM; + modelInfo->animations[command->animIndex[partIndex]].initialLerp = 40; + } + // check for a duration for this animation instance + token = COM_ParseExt( input, qfalse ); + if ( token && token[0] ) { + if ( !Q_stricmp( token, "duration" ) ) { + // read the duration + token = COM_ParseExt( input, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "BG_ParseCommands: expected duration value" ); + } + command->animDuration[partIndex] = atoi( token ); + } else { // unget the token + COM_RestoreParseSession( input ); + } + } else { + COM_RestoreParseSession( input ); + } + + if ( command->bodyPart[partIndex] != ANIM_BP_BOTH && partIndex++ < 1 ) { + continue; // allow parsing of another bodypart + } + } else { + // unget the token + *input -= strlen( token ); + } + + // parse optional parameters (sounds, etc) + while ( 1 ) { + token = COM_ParseExt( input, qfalse ); + if ( !token || !token[0] ) { + break; + } + + if ( !Q_stricmp( token, "sound" ) ) { + + token = COM_ParseExt( input, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "BG_ParseCommands: expected sound" ); + } + // NOTE: only sound script are supported at this stage + if ( strstr( token, ".wav" ) ) { + BG_AnimParseError( "BG_ParseCommands: wav files not supported, only sound scripts" ); // RF mod + } + command->soundIndex = globalScriptData->soundIndex( token ); + + } else { + // unknown?? + BG_AnimParseError( "BG_ParseCommands: unknown parameter '%s'", token ); + } + } + + partIndex = 0; + } +} + +/* +================= +BG_AnimParseAnimScript + + Parse the animation script for this model, converting it into run-time structures +================= +*/ + +typedef enum +{ + PARSEMODE_DEFINES, + PARSEMODE_ANIMATION, + PARSEMODE_CANNED_ANIMATIONS, + PARSEMODE_STATECHANGES, + PARSEMODE_EVENTS +} animScriptParseMode_t; + +static animStringItem_t animParseModesStr[] = +{ + {"defines", -1}, + {"animations", -1}, + {"canned_animations", -1}, + {"statechanges", -1}, + {"events", -1}, + + {NULL, -1}, +}; + +void BG_AnimParseAnimScript( animModelInfo_t *modelInfo, animScriptData_t *scriptData, int client, char *filename, char *input ) { + #define MAX_INDENT_LEVELS 3 + + char *text_p, *token; + animScriptParseMode_t parseMode; + animScript_t *currentScript; + animScriptItem_t tempScriptItem; + // TTimo gcc: might be used unitialized + animScriptItem_t *currentScriptItem = NULL; + int indexes[MAX_INDENT_LEVELS], indentLevel, oldState, newParseMode; + int i, defineType; + + // the scriptData passed into here must be the one this binary is using + globalScriptData = scriptData; + + // current client being parsed + parseClient = client; + + // start at the defines + parseMode = PARSEMODE_DEFINES; + + // record which modelInfo this client is using + scriptData->clientModels[client] = 1 + (int)( modelInfo - scriptData->modelInfo ); + + // init the global defines + globalFilename = filename; + memset( defineStr, 0, sizeof( defineStr ) ); + memset( defineStrings, 0, sizeof( defineStrings ) ); + memset( numDefines, 0, sizeof( numDefines ) ); + defineStringsOffset = 0; + + for ( i = 0; i < MAX_INDENT_LEVELS; i++ ) + indexes[i] = -1; + indentLevel = 0; + currentScript = NULL; + + text_p = input; + COM_BeginParseSession( "BG_AnimParseAnimScript" ); + + // read in the weapon defines + while ( 1 ) { + + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) { + if ( indentLevel ) { + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected end of file: %s" ); + } + break; + } + + // check for a new section + newParseMode = BG_IndexForString( token, animParseModesStr, qtrue ); + if ( newParseMode >= 0 ) { + if ( indentLevel ) { + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + } + + parseMode = newParseMode; + parseMovetype = ANIM_MT_UNUSED; + parseEvent = -1; + continue; + } + + switch ( parseMode ) { + + case PARSEMODE_DEFINES: + + if ( !Q_stricmp( token, "set" ) ) { + + // read in the define type + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected condition type string" ); // RF mod + } + defineType = BG_IndexForString( token, animConditionsStr, qfalse ); + + // read in the define + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected condition define string" ); // RF mod + } + + // copy the define to the strings list + defineStr[defineType][numDefines[defineType]].string = BG_CopyStringIntoBuffer( token, defineStrings, sizeof( defineStrings ), &defineStringsOffset ); + defineStr[defineType][numDefines[defineType]].hash = BG_StringHashValue( defineStr[defineType][numDefines[defineType]].string ); + // expecting an = + token = COM_ParseExt( &text_p, qfalse ); + if ( !token ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected '=', found end of line" ); // RF mod + } + if ( Q_stricmp( token, "=" ) ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected '=', found '%s'", token ); // RF mod + } + + // parse the bits + BG_ParseConditionBits( &text_p, animConditionsTable[defineType].values, defineType, defineBits[defineType][numDefines[defineType]] ); + numDefines[defineType]++; + + // copy the weapon defines over to the enemy_weapon defines + memcpy( &defineStr[ANIM_COND_ENEMY_WEAPON][0], &defineStr[ANIM_COND_WEAPON][0], sizeof( animStringItem_t ) * MAX_ANIM_DEFINES ); + memcpy( &defineBits[ANIM_COND_ENEMY_WEAPON][0], &defineBits[ANIM_COND_WEAPON][0], sizeof( defineBits[ANIM_COND_ENEMY_WEAPON][0] ) * MAX_ANIM_DEFINES ); + numDefines[ANIM_COND_ENEMY_WEAPON] = numDefines[ANIM_COND_WEAPON]; + + } + + break; + + case PARSEMODE_ANIMATION: + case PARSEMODE_CANNED_ANIMATIONS: + + if ( !Q_stricmp( token, "{" ) ) { + + // about to increment indent level, check that we have enough information to do this + if ( indentLevel >= MAX_INDENT_LEVELS ) { // too many indentations + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + } + if ( indexes[indentLevel] < 0 ) { // we havent found out what this new group is yet + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + } + // + indentLevel++; + + } else if ( !Q_stricmp( token, "}" ) ) { + + // reduce the indentLevel + indentLevel--; + if ( indentLevel < 0 ) { + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + } + if ( indentLevel == 1 ) { + currentScript = NULL; + } + // make sure we read a new index before next indent + indexes[indentLevel] = -1; + + } else if ( indentLevel == 0 && ( indexes[indentLevel] < 0 ) ) { + + if ( Q_stricmp( token, "state" ) ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected 'state'" ); // RF mod + } + + // read in the state type + token = COM_ParseExt( &text_p, qfalse ); + if ( !token ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected state type" ); // RF mod + } + indexes[indentLevel] = BG_IndexForString( token, animStateStr, qfalse ); + +//----(SA) // RF mod + // check for the open bracket + token = COM_ParseExt( &text_p, qtrue ); + if ( !token || Q_stricmp( token, "{" ) ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected '{'" ); + } + indentLevel++; +//----(SA) // RF mod + } else if ( ( indentLevel == 1 ) && ( indexes[indentLevel] < 0 ) ) { + + // we are expecting a movement type + indexes[indentLevel] = BG_IndexForString( token, animMoveTypesStr, qfalse ); + if ( parseMode == PARSEMODE_ANIMATION ) { + currentScript = &modelInfo->scriptAnims[indexes[0]][indexes[1]]; + parseMovetype = indexes[1]; + } else if ( parseMode == PARSEMODE_CANNED_ANIMATIONS ) { + currentScript = &modelInfo->scriptCannedAnims[indexes[0]][indexes[1]]; + } + memset( currentScript, 0, sizeof( *currentScript ) ); + + } else if ( ( indentLevel == 2 ) && ( indexes[indentLevel] < 0 ) ) { + + // we are expecting a condition specifier + // move the text_p backwards so we can read in the last token again + text_p -= strlen( token ); + // sanity check that + if ( Q_strncmp( text_p, token, strlen( token ) ) ) { + // this should never happen, just here to check that this operation is correct before code goes live + BG_AnimParseError( "BG_AnimParseAnimScript: internal error" ); + } + // + memset( &tempScriptItem, 0, sizeof( tempScriptItem ) ); + indexes[indentLevel] = BG_ParseConditions( &text_p, &tempScriptItem ); + // do we have enough room in this script for another item? + if ( currentScript->numItems >= MAX_ANIMSCRIPT_ITEMS ) { + BG_AnimParseError( "BG_AnimParseAnimScript: exceeded maximum items per script (%i)", MAX_ANIMSCRIPT_ITEMS ); // RF mod + } + // are there enough items left in the global list? + if ( modelInfo->numScriptItems >= MAX_ANIMSCRIPT_ITEMS_PER_MODEL ) { + BG_AnimParseError( "BG_AnimParseAnimScript: exceeded maximum global items (%i)", MAX_ANIMSCRIPT_ITEMS_PER_MODEL ); // RF mod + } + // it was parsed ok, so grab an item from the global list to use + currentScript->items[currentScript->numItems] = &modelInfo->scriptItems[ modelInfo->numScriptItems++ ]; + currentScriptItem = currentScript->items[currentScript->numItems]; + currentScript->numItems++; + // copy the data across from the temp script item + *currentScriptItem = tempScriptItem; + + } else if ( indentLevel == 3 ) { + + // we are reading the commands, so parse this line as if it were a command + // move the text_p backwards so we can read in the last token again + text_p -= strlen( token ); + // sanity check that + if ( Q_strncmp( text_p, token, strlen( token ) ) ) { + // this should never happen, just here to check that this operation is correct before code goes live + BG_AnimParseError( "BG_AnimParseAnimScript: internal error" ); + } + // + BG_ParseCommands( &text_p, currentScriptItem, modelInfo, scriptData ); + + } else { + + // huh ?? + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + + } + + break; + + case PARSEMODE_STATECHANGES: + case PARSEMODE_EVENTS: + + if ( !Q_stricmp( token, "{" ) ) { + + // about to increment indent level, check that we have enough information to do this + if ( indentLevel >= MAX_INDENT_LEVELS ) { // too many indentations + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + } + if ( indexes[indentLevel] < 0 ) { // we havent found out what this new group is yet + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + } + // + indentLevel++; + + } else if ( !Q_stricmp( token, "}" ) ) { + + // reduce the indentLevel + indentLevel--; + if ( indentLevel < 0 ) { + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + } + if ( indentLevel == 0 ) { + currentScript = NULL; + } + // make sure we read a new index before next indent + indexes[indentLevel] = -1; + + } else if ( indentLevel == 0 && ( indexes[indentLevel] < 0 ) ) { + + if ( parseMode == PARSEMODE_STATECHANGES ) { + + if ( Q_stricmp( token, "statechange" ) ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected 'statechange', got '%s'", token ); // RF mod + } + + // read in the old state type + token = COM_ParseExt( &text_p, qfalse ); + if ( !token ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected " ); // RF mod + } + oldState = BG_IndexForString( token, animStateStr, qfalse ); + + // read in the new state type + token = COM_ParseExt( &text_p, qfalse ); + if ( !token ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected " ); // RF mod + } + indexes[indentLevel] = BG_IndexForString( token, animStateStr, qfalse ); + + currentScript = &modelInfo->scriptStateChange[oldState][indexes[indentLevel]]; + +//----(SA) // RF mod + // check for the open bracket + token = COM_ParseExt( &text_p, qtrue ); + if ( !token || Q_stricmp( token, "{" ) ) { + BG_AnimParseError( "BG_AnimParseAnimScript: expected '{'" ); + } + indentLevel++; +//----(SA) // RF mod + } else { + + // read in the event type + indexes[indentLevel] = BG_IndexForString( token, animEventTypesStr, qfalse ); + currentScript = &modelInfo->scriptEvents[indexes[0]]; + + parseEvent = indexes[indentLevel]; + + } + + memset( currentScript, 0, sizeof( *currentScript ) ); + + } else if ( ( indentLevel == 1 ) && ( indexes[indentLevel] < 0 ) ) { + + // we are expecting a condition specifier + // move the text_p backwards so we can read in the last token again + text_p -= strlen( token ); + // sanity check that + if ( Q_strncmp( text_p, token, strlen( token ) ) ) { + // this should never happen, just here to check that this operation is correct before code goes live + BG_AnimParseError( "BG_AnimParseAnimScript: internal error" ); + } + // + memset( &tempScriptItem, 0, sizeof( tempScriptItem ) ); + indexes[indentLevel] = BG_ParseConditions( &text_p, &tempScriptItem ); + // do we have enough room in this script for another item? + if ( currentScript->numItems >= MAX_ANIMSCRIPT_ITEMS ) { + BG_AnimParseError( "BG_AnimParseAnimScript: exceeded maximum items per script (%i)", MAX_ANIMSCRIPT_ITEMS ); // RF mod + } + // are there enough items left in the global list? + if ( modelInfo->numScriptItems >= MAX_ANIMSCRIPT_ITEMS_PER_MODEL ) { + BG_AnimParseError( "BG_AnimParseAnimScript: exceeded maximum global items (%i)", MAX_ANIMSCRIPT_ITEMS_PER_MODEL ); // RF mod + } + // it was parsed ok, so grab an item from the global list to use + currentScript->items[currentScript->numItems] = &modelInfo->scriptItems[ modelInfo->numScriptItems++ ]; + currentScriptItem = currentScript->items[currentScript->numItems]; + currentScript->numItems++; + // copy the data across from the temp script item + *currentScriptItem = tempScriptItem; + + } else if ( indentLevel == 2 ) { + + // we are reading the commands, so parse this line as if it were a command + // move the text_p backwards so we can read in the last token again + text_p -= strlen( token ); + // sanity check that + if ( Q_strncmp( text_p, token, strlen( token ) ) ) { + // this should never happen, just here to check that this operation is correct before code goes live + BG_AnimParseError( "BG_AnimParseAnimScript: internal error" ); + } + // + BG_ParseCommands( &text_p, currentScriptItem, modelInfo, scriptData ); + + } else { + + // huh ?? + BG_AnimParseError( "BG_AnimParseAnimScript: unexpected '%s'", token ); // RF mod + + } + + } + + } + + globalFilename = NULL; + +} + +//------------------------------------------------------------------------ +// +// run-time gameplay functions, these are called during gameplay, so they must be +// cpu efficient. +// + +/* +=============== +BG_EvaluateConditions + + returns qfalse if the set of conditions fails, qtrue otherwise +=============== +*/ +qboolean BG_EvaluateConditions( int client, animScriptItem_t *scriptItem ) { + int i; + animScriptCondition_t *cond; + + for ( i = 0, cond = scriptItem->conditions; i < scriptItem->numConditions; i++, cond++ ) + { + switch ( animConditionsTable[cond->index].type ) { + case ANIM_CONDTYPE_BITFLAGS: + if ( !( globalScriptData->clientConditions[client][cond->index][0] & cond->value[0] ) && + !( globalScriptData->clientConditions[client][cond->index][1] & cond->value[1] ) ) { + return qfalse; + } + break; + case ANIM_CONDTYPE_VALUE: + if ( !( globalScriptData->clientConditions[client][cond->index][0] == cond->value[0] ) ) { + return qfalse; + } + break; + default: // TTimo NUM_ANIM_CONDTYPES not handled + break; + } + } + // + // all conditions must have passed + return qtrue; +} + +/* +=============== +BG_FirstValidItem + + scroll through the script items, returning the first script found to pass all conditions + + returns NULL if no match found +=============== +*/ +animScriptItem_t *BG_FirstValidItem( int client, animScript_t *script ) { + animScriptItem_t **ppScriptItem; + + int i; + + for ( i = 0, ppScriptItem = script->items; i < script->numItems; i++, ppScriptItem++ ) + { + if ( BG_EvaluateConditions( client, *ppScriptItem ) ) { + return *ppScriptItem; + } + } + // + return NULL; +} + +/* +=============== +BG_PlayAnim +=============== +*/ +int BG_PlayAnim( playerState_t *ps, int animNum, animBodyPart_t bodyPart, int forceDuration, qboolean setTimer, qboolean isContinue, qboolean force ) { + int duration; + qboolean wasSet = qfalse; + animModelInfo_t *modelInfo; + + modelInfo = BG_ModelInfoForClient( ps->clientNum ); + + if ( forceDuration ) { + duration = forceDuration; + } else { + duration = modelInfo->animations[animNum].duration + 50; // account for lerping between anims + } + + switch ( bodyPart ) { + case ANIM_BP_BOTH: + case ANIM_BP_LEGS: + + if ( ( ps->legsTimer < 50 ) || force ) { + if ( !isContinue || !( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) == animNum ) ) { + wasSet = qtrue; + ps->legsAnim = ( ( ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | animNum; + if ( setTimer ) { + ps->legsTimer = duration; + } + } else if ( setTimer && modelInfo->animations[animNum].loopFrames ) { + ps->legsTimer = duration; + } + } + + if ( bodyPart == ANIM_BP_LEGS ) { + break; + } + + case ANIM_BP_TORSO: + + if ( ( ps->torsoTimer < 50 ) || force ) { + if ( !isContinue || !( ( ps->torsoAnim & ~ANIM_TOGGLEBIT ) == animNum ) ) { + ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | animNum; + if ( setTimer ) { + ps->torsoTimer = duration; + } + } else if ( setTimer && modelInfo->animations[animNum].loopFrames ) { + ps->torsoTimer = duration; + } + } + + break; + default: // TTimo default ANIM_BP_UNUSED NUM_ANIM_BODYPARTS not handled + break; + } + + if ( !wasSet ) { + return -1; + } + + return duration; +} + +/* +=============== +BG_PlayAnimName +=============== +*/ +int BG_PlayAnimName( playerState_t *ps, char *animName, animBodyPart_t bodyPart, qboolean setTimer, qboolean isContinue, qboolean force ) { + return BG_PlayAnim( ps, BG_AnimationIndexForString( animName, ps->clientNum ), bodyPart, 0, setTimer, isContinue, force ); +} + +/* +=============== +BG_ExecuteCommand + + returns the duration of the animation, -1 if no anim was set +=============== +*/ +int BG_ExecuteCommand( playerState_t *ps, animScriptCommand_t *scriptCommand, qboolean setTimer, qboolean isContinue, qboolean force ) { + int duration = -1; + qboolean playedLegsAnim = qfalse; + + if ( scriptCommand->bodyPart[0] ) { + duration = scriptCommand->animDuration[0] + 50; + // FIXME: how to sync torso/legs anims accounting for transition blends, etc + if ( scriptCommand->bodyPart[0] == ANIM_BP_BOTH || scriptCommand->bodyPart[0] == ANIM_BP_LEGS ) { + playedLegsAnim = ( BG_PlayAnim( ps, scriptCommand->animIndex[0], scriptCommand->bodyPart[0], duration, setTimer, isContinue, force ) > -1 ); + } else { + BG_PlayAnim( ps, scriptCommand->animIndex[0], scriptCommand->bodyPart[0], duration, setTimer, isContinue, force ); + } + } + if ( scriptCommand->bodyPart[1] ) { + duration = scriptCommand->animDuration[0] + 50; + // FIXME: how to sync torso/legs anims accounting for transition blends, etc + // just play the animation for the torso + if ( scriptCommand->bodyPart[1] == ANIM_BP_BOTH || scriptCommand->bodyPart[1] == ANIM_BP_LEGS ) { + playedLegsAnim = ( BG_PlayAnim( ps, scriptCommand->animIndex[1], scriptCommand->bodyPart[1], duration, setTimer, isContinue, force ) > -1 ); + } else { + BG_PlayAnim( ps, scriptCommand->animIndex[1], scriptCommand->bodyPart[1], duration, setTimer, isContinue, force ); + } + } + + if ( scriptCommand->soundIndex ) { + globalScriptData->playSound( scriptCommand->soundIndex, ps->origin, ps->clientNum ); + } + + if ( !playedLegsAnim ) { + return -1; + } + + return duration; +} + +/* +================ +BG_AnimScriptAnimation + + runs the normal locomotive animations + + returns 1 if an animation was set, -1 if no animation was found, 0 otherwise +================ +*/ +int BG_AnimScriptAnimation( playerState_t *ps, aistateEnum_t state, scriptAnimMoveTypes_t movetype, qboolean isContinue ) { + animModelInfo_t *modelInfo = NULL; + animScript_t *script = NULL; + animScriptItem_t *scriptItem = NULL; + animScriptCommand_t *scriptCommand = NULL; + + // DHM - Nerve :: Allow fallen movetype while dead + if ( ps->eFlags & EF_DEAD && movetype != ANIM_MT_FALLEN ) { + return -1; + } + + modelInfo = BG_ModelInfoForClient( ps->clientNum ); + +#ifdef DBGANIMS + if ( ps->clientNum ) { + Com_Printf( "script anim: cl %i, mt %s, ", ps->clientNum, animMoveTypesStr[movetype] ); + } +#endif + + // try finding a match in all states below the given state + while ( !scriptItem && state >= 0 ) { + script = &modelInfo->scriptAnims[ state ][ movetype ]; + if ( !script->numItems ) { + state--; + continue; + } + // find the first script item, that passes all the conditions for this event + scriptItem = BG_FirstValidItem( ps->clientNum, script ); + if ( !scriptItem ) { + state--; + continue; + } + } + // + if ( !scriptItem ) { +#ifdef DBGANIMS + if ( ps->clientNum ) { + Com_Printf( "no valid conditions\n" ); + } +#endif + return -1; + } + // save this as our current movetype + BG_UpdateConditionValue( ps->clientNum, ANIM_COND_MOVETYPE, movetype, qtrue ); + // pick the correct animation for this character (animations must be constant for each character, otherwise they'll constantly change) + scriptCommand = &scriptItem->commands[ ps->clientNum % scriptItem->numCommands ]; + +#ifdef DBGANIMS + if ( scriptCommand->bodyPart[0] ) { + if ( ps->clientNum ) { + Com_Printf( "anim0 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[0]].string, modelInfo->animations[scriptCommand->animIndex[0]].name ); + } + } + if ( scriptCommand->bodyPart[1] ) { + if ( ps->clientNum ) { + Com_Printf( "anim1 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[1]].string, modelInfo->animations[scriptCommand->animIndex[1]].name ); + } + } + if ( ps->clientNum ) { + Com_Printf( "\n" ); + } +#endif + + // run it + return ( BG_ExecuteCommand( ps, scriptCommand, qfalse, isContinue, qfalse ) != -1 ); +} + +/* +================ +BG_AnimScriptCannedAnimation + + uses the current movetype for this client to play a canned animation + + returns the duration in milliseconds that this model should be paused. -1 if no anim found +================ +*/ +int BG_AnimScriptCannedAnimation( playerState_t *ps, aistateEnum_t state ) { + animModelInfo_t *modelInfo; + animScript_t *script; + animScriptItem_t *scriptItem; + animScriptCommand_t *scriptCommand; + scriptAnimMoveTypes_t movetype; + + if ( ps->eFlags & EF_DEAD ) { + return -1; + } + + movetype = globalScriptData->clientConditions[ ps->clientNum ][ ANIM_COND_MOVETYPE ][0]; + if ( !movetype ) { // no valid movetype yet for this client + return -1; + } + // + modelInfo = BG_ModelInfoForClient( ps->clientNum ); + script = &modelInfo->scriptCannedAnims[ state ][ movetype ]; + if ( !script->numItems ) { + return -1; + } + // find the first script item, that passes all the conditions for this event + scriptItem = BG_FirstValidItem( ps->clientNum, script ); + if ( !scriptItem ) { + return -1; + } + // pick a random command + scriptCommand = &scriptItem->commands[ rand() % scriptItem->numCommands ]; + // run it + return BG_ExecuteCommand( ps, scriptCommand, qtrue, qfalse, qfalse ); +} + +/* +================ +BG_AnimScriptStateChange + + returns the duration in milliseconds that this model should be paused. -1 if no anim found +================ +*/ +int BG_AnimScriptStateChange( playerState_t *ps, aistateEnum_t newState, aistateEnum_t oldState ) { + animModelInfo_t *modelInfo; + animScript_t *script; + animScriptItem_t *scriptItem; + animScriptCommand_t *scriptCommand; + + if ( ps->eFlags & EF_DEAD ) { + return -1; + } + + modelInfo = BG_ModelInfoForClient( ps->clientNum ); + script = &modelInfo->scriptStateChange[ oldState ][ newState ]; + if ( !script->numItems ) { + return -1; + } + // find the first script item, that passes all the conditions for this event + scriptItem = BG_FirstValidItem( ps->clientNum, script ); + if ( !scriptItem ) { + return -1; + } + // pick a random command + scriptCommand = &scriptItem->commands[ rand() % scriptItem->numCommands ]; + // run it + return BG_ExecuteCommand( ps, scriptCommand, qtrue, qfalse, qfalse ); +} + +/* +================ +BG_AnimScriptEvent + + returns the duration in milliseconds that this model should be paused. -1 if no event found +================ +*/ +int BG_AnimScriptEvent( playerState_t *ps, scriptAnimEventTypes_t event, qboolean isContinue, qboolean force ) { + animModelInfo_t *modelInfo; + animScript_t *script; + animScriptItem_t *scriptItem; + animScriptCommand_t *scriptCommand; + + if ( event != ANIM_ET_DEATH && ps->eFlags & EF_DEAD ) { + return -1; + } + +#ifdef DBGANIMEVENTS + Com_Printf( "script event: cl %i, ev %s, ", ps->clientNum, animEventTypesStr[event] ); +#endif + + modelInfo = BG_ModelInfoForClient( ps->clientNum ); + script = &modelInfo->scriptEvents[ event ]; + if ( !script->numItems ) { +#ifdef DBGANIMEVENTS + Com_Printf( "no entry\n" ); +#endif + return -1; + } + // find the first script item, that passes all the conditions for this event + scriptItem = BG_FirstValidItem( ps->clientNum, script ); + if ( !scriptItem ) { +#ifdef DBGANIMEVENTS + Com_Printf( "no valid conditions\n" ); +#endif + return -1; + } + // pick a random command + scriptCommand = &scriptItem->commands[ rand() % scriptItem->numCommands ]; + +#ifdef DBGANIMEVENTS + if ( scriptCommand->bodyPart[0] ) { + Com_Printf( "anim0 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[0]].string, modelInfo->animations[scriptCommand->animIndex[0]].name ); + } + if ( scriptCommand->bodyPart[1] ) { + Com_Printf( "anim1 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[1]].string, modelInfo->animations[scriptCommand->animIndex[1]].name ); + } + Com_Printf( "\n" ); +#endif + + // run it + return BG_ExecuteCommand( ps, scriptCommand, qtrue, isContinue, force ); +} + +/* +=============== +BG_ValidAnimScript + + returns qtrue if the given client has animation scripts +=============== +*/ +qboolean BG_ValidAnimScript( int clientNum ) { + if ( !globalScriptData->clientModels[clientNum] ) { + return qfalse; + } + // + if ( !globalScriptData->modelInfo[ globalScriptData->clientModels[clientNum] ].numScriptItems ) { + return qfalse; + } + // + return qtrue; +} + +/* +=============== +BG_GetAnimString +=============== +*/ +char *BG_GetAnimString( int client, int anim ) { + animModelInfo_t *modelinfo = BG_ModelInfoForClient( client ); + // + if ( anim >= modelinfo->numAnimations ) { + BG_AnimParseError( "BG_GetAnimString: anim index is out of range" ); + } + // + return modelinfo->animations[anim].name; +} + +/* +============== +BG_UpdateConditionValue +============== +*/ +void BG_UpdateConditionValue( int client, int condition, int value, qboolean checkConversion ) { + if ( checkConversion ) { + // we may need to convert to bitflags + if ( animConditionsTable[condition].type == ANIM_CONDTYPE_BITFLAGS ) { + + // DHM - Nerve :: We want to set the ScriptData to the explicit value passed in. + // COM_BitSet will OR values on top of each other, so clear it first. + globalScriptData->clientConditions[client][condition][0] = 0; + globalScriptData->clientConditions[client][condition][1] = 0; + // dhm - end + + COM_BitSet( globalScriptData->clientConditions[client][condition], value ); + return; + } + } + globalScriptData->clientConditions[client][condition][0] = value; +} + +/* +============== +BG_GetConditionValue +============== +*/ +int BG_GetConditionValue( int client, int condition, qboolean checkConversion ) { + int value, i; + + // TTimo gcc: assignment makes integer from pointer without a cast + value = (int)globalScriptData->clientConditions[client][condition]; + + if ( checkConversion ) { + // we may need to convert to a value + if ( animConditionsTable[condition].type == ANIM_CONDTYPE_BITFLAGS ) { + //if (!value) + // return 0; + for ( i = 0; i < 8 * sizeof( globalScriptData->clientConditions[0][0] ); i++ ) { + if ( COM_BitCheck( globalScriptData->clientConditions[client][condition], i ) ) { + return i; + } + } + // nothing found + return 0; + //BG_AnimParseError( "BG_GetConditionValue: internal error" ); + } + } + + return value; +} + +/* +================ +BG_GetAnimScriptAnimation + + returns the locomotion animation index, -1 if no animation was found, 0 otherwise +================ +*/ +int BG_GetAnimScriptAnimation( int client, aistateEnum_t state, scriptAnimMoveTypes_t movetype ) { + animModelInfo_t *modelInfo; + animScript_t *script; + animScriptItem_t *scriptItem = NULL; + animScriptCommand_t *scriptCommand; + + modelInfo = BG_ModelInfoForClient( client ); + + // try finding a match in all states below the given state + while ( !scriptItem && state >= 0 ) { + script = &modelInfo->scriptAnims[ state ][ movetype ]; + if ( !script->numItems ) { + state--; + continue; + } + // find the first script item, that passes all the conditions for this event + scriptItem = BG_FirstValidItem( client, script ); + if ( !scriptItem ) { + state--; + continue; + } + } + // + if ( !scriptItem ) { + return -1; + } + // pick the correct animation for this character (animations must be constant for each character, otherwise they'll constantly change) + scriptCommand = &scriptItem->commands[ client % scriptItem->numCommands ]; + if ( !scriptCommand->bodyPart[0] ) { + return -1; + } + // return the animation + return scriptCommand->animIndex[0]; +} + +/* +================ +BG_GetAnimScriptEvent + + returns the animation index for this event +================ +*/ +int BG_GetAnimScriptEvent( playerState_t *ps, scriptAnimEventTypes_t event ) { + animModelInfo_t *modelInfo; + animScript_t *script; + animScriptItem_t *scriptItem; + animScriptCommand_t *scriptCommand; + + if ( event != ANIM_ET_DEATH && ps->eFlags & EF_DEAD ) { + return -1; + } + + modelInfo = BG_ModelInfoForClient( ps->clientNum ); + script = &modelInfo->scriptEvents[ event ]; + if ( !script->numItems ) { + return -1; + } + // find the first script item, that passes all the conditions for this event + scriptItem = BG_FirstValidItem( ps->clientNum, script ); + if ( !scriptItem ) { + return -1; + } + // pick a random command + scriptCommand = &scriptItem->commands[ rand() % scriptItem->numCommands ]; + + // return the animation + return scriptCommand->animIndex[0]; +} + +/* +=============== +BG_GetAnimationForIndex + + returns the animation_t for the given index +=============== +*/ +animation_t *BG_GetAnimationForIndex( int client, int index ) { + animModelInfo_t *modelInfo; + + modelInfo = BG_ModelInfoForClient( client ); + if ( index < 0 || index >= modelInfo->numAnimations ) { + Com_Error( ERR_DROP, "BG_GetAnimationForIndex: index out of bounds" ); + } + + return &modelInfo->animations[index]; +} + +/* +================= +BG_AnimUpdatePlayerStateConditions +================= +*/ +void BG_AnimUpdatePlayerStateConditions( pmove_t *pmove ) { + playerState_t *ps = pmove->ps; + + // WEAPON + if ( ps->eFlags & EF_ZOOMING ) { + BG_UpdateConditionValue( ps->clientNum, ANIM_COND_WEAPON, WP_BINOCULARS, qtrue ); + } else { + BG_UpdateConditionValue( ps->clientNum, ANIM_COND_WEAPON, ps->weapon, qtrue ); + } + + // MOUNTED + if ( ps->eFlags & EF_MG42_ACTIVE ) { + BG_UpdateConditionValue( ps->clientNum, ANIM_COND_MOUNTED, MOUNTED_MG42, qtrue ); + } else { + BG_UpdateConditionValue( ps->clientNum, ANIM_COND_MOUNTED, MOUNTED_UNUSED, qtrue ); + } + + // UNDERHAND + BG_UpdateConditionValue( ps->clientNum, ANIM_COND_UNDERHAND, ps->viewangles[0] > 0, qtrue ); + + if ( ps->viewheight == ps->crouchViewHeight ) { + ps->eFlags |= EF_CROUCHING; + } else { + ps->eFlags &= ~EF_CROUCHING; + } + + if ( pmove->cmd.buttons & BUTTON_ATTACK ) { + BG_UpdateConditionValue( ps->clientNum, ANIM_COND_FIRING, qtrue, qtrue ); + } else { + BG_UpdateConditionValue( ps->clientNum, ANIM_COND_FIRING, qfalse, qtrue ); + } +} diff --git a/src/game/bg_local.h b/src/game/bg_local.h new file mode 100644 index 0000000..5c2b0f8 --- /dev/null +++ b/src/game/bg_local.h @@ -0,0 +1,99 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// bg_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7 // can't walk on very steep slopes + +#define STEPSIZE 18 + +#define JUMP_VELOCITY 270 + +#define TIMER_LAND 130 +#define TIMER_GESTURE ( 34 * 66 + 50 ) + + +#define OVERCLIP 1.001 + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server +typedef struct { + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; + + // Ridah, ladders + qboolean ladder; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +// movement parameters +extern float pm_stopspeed; +//extern float pm_duckScale; + +//----(SA) modified +extern float pm_waterSwimScale; +extern float pm_waterWadeScale; +extern float pm_slagSwimScale; +extern float pm_slagWadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_slagaccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_slagfriction; +extern float pm_flightfriction; + +//----(SA) end + +extern int c_pmove; + +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c new file mode 100644 index 0000000..34cbfa9 --- /dev/null +++ b/src/game/bg_misc.c @@ -0,0 +1,4082 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: bg_misc.c + * + * desc: both games misc functions, all completely stateless + * +*/ + + +#include "q_shared.h" +#include "bg_public.h" + +// JPW NERVE -- added because I need to check single/multiplayer instances and branch accordingly +#ifdef CGAMEDLL +extern vmCvar_t cg_gameType; +#endif +#ifdef GAMEDLL +extern vmCvar_t g_gametype; +#endif +// jpw + +// NOTE: weapons that share ammo (ex. colt/thompson) need to share max ammo, but not necessarily uses or max clip +#define MAX_AMMO_45 300 +#define MAX_AMMO_9MM 300 +#define MAX_AMMO_VENOM 1000 +#define MAX_AMMO_MAUSER 50 +#define MAX_AMMO_GARAND 1000 +#define MAX_AMMO_FG42 500 +#define MAX_AMMO_BAR 500 + + +// these defines are matched with the character torso animations +#define DELAY_LOW 100 // machineguns, tesla, spear, flame +#define DELAY_HIGH 100 // mauser, garand +#define DELAY_PISTOL 100 // colt, luger, sp5, cross +#define DELAY_SHOULDER 50 // rl +#define DELAY_THROW 250 // grenades, dynamite + +// JPW NERVE -- moved this from cg_weapons.c 'cause I need it for a droplist for weapondrop command (wbuttons & (1 << 6)) +// JPW NERVE -- in mutiplayer, characters get knife/special on button 1, pistols on 2, 2-handed on 3 +int weapBanksMultiPlayer[MAX_WEAP_BANKS_MP][MAX_WEAPS_IN_BANK_MP] = { + {0, 0, 0, 0, 0, 0, 0, 0 }, // empty bank '0' + {WP_KNIFE, 0, 0, 0, 0, 0, 0, 0 }, + {WP_LUGER, WP_COLT, 0, 0, 0, 0, 0, 0 }, + {WP_MP40, WP_THOMPSON, WP_STEN, WP_MAUSER, WP_GARAND, WP_PANZERFAUST, WP_VENOM, WP_FLAMETHROWER }, + {WP_GRENADE_LAUNCHER, WP_GRENADE_PINEAPPLE, 0, 0, 0, 0, 0, 0, }, + {WP_MEDIC_SYRINGE, WP_PLIERS, WP_SMOKE_GRENADE, 0, 0, 0, 0, 0, }, + {WP_DYNAMITE, WP_MEDKIT, WP_AMMO, 0, 0, 0, 0, 0 } +}; +// jpw + +// [0] = maxammo - max player ammo carrying capacity. +// [1] = uses - how many 'rounds' it takes/costs to fire one cycle. +// [2] = maxclip - max 'rounds' in a clip. +// [3] = reloadTime - time from start of reload until ready to fire. +// [4] = fireDelayTime - time from pressing 'fire' until first shot is fired. (used for delaying fire while weapon is 'readied' in animation) +// [5] = nextShotTime - when firing continuously, this is the time between shots +// [6] = maxHeat - max active firing time before weapon 'overheats' (at which point the weapon will fail for a moment) +// [7] = coolRate - how fast the weapon cools down. +// [8] = mod - means of death + +// potential inclusions in the table: +// damage - +// splashDamage - +// soundRange - distance which ai can hear the weapon +// ammoWarning - amount we give the player a 'low on ammo' warning (just a HUD color change or something) +// clipWarning - amount we give the player a 'low in clip' warning (just a HUD color change or something) +// maxclip2 - allow the player to (mod/powerup) upgrade clip size when aplicable (luger has 8 round standard clip and 32 round snail magazine, for ex.) +// +// +// + +ammotable_t ammoTable[] = { + // MAX USES MAX RELOAD FIRE NEXT HEAT, COOL, MOD, ... + // AMMO AMT. CLIP TIME DELAY SHOT + { 0, 0, 0, 0, 50, 0, 0, 0, 0 }, // WP_NONE // 0 + + { 999, 0, 999, 0, 50, 200, 0, 0, MOD_KNIFE }, // WP_KNIFE // 1 + + { MAX_AMMO_9MM, 1, 8, 1500, DELAY_PISTOL, 400, 0, 0, MOD_LUGER }, // WP_LUGER // 2 // NOTE: also 32 round 'snail' magazine + { MAX_AMMO_9MM, 1, 32, 2600, DELAY_LOW, 100, 0, 0, MOD_MP40 }, // WP_MP40 // 3 + { MAX_AMMO_MAUSER,1, 10, 2500, DELAY_HIGH, 1200, 0, 0, MOD_MAUSER }, // WP_MAUSER // 4 // NOTE: authentic clips are 5/10/25 rounds + { MAX_AMMO_FG42, 1, 20, 2000, DELAY_LOW, 200, 0, 0, MOD_FG42 }, // WP_FG42 // 5 + { 15, 1, 15, 1000, DELAY_THROW, 1600, 0, 0, MOD_GRENADE_LAUNCHER }, // WP_GRENADE_LAUNCHER // 6 + { 5, 1, 1, 1000, 750, 2000, 0, 0, MOD_PANZERFAUST }, // WP_PANZERFAUST // 7 // DHM - Nerve :: updated delay so prediction is correct +// { MAX_AMMO_VENOM, 1, 500, 3000, 750, 30, 5000, 200, MOD_VENOM }, // WP_VENOM // - + { MAX_AMMO_VENOM, 1, 500, 3000, 750, 45, 5000, 200, MOD_VENOM }, // WP_VENOM // 8 // JPW NOTE: changed next_shot 50->45 to genlock firing to every server frame (fire rate shouldn't be framerate dependent now) + { 200, 1, 200, 1000, DELAY_LOW, 50, 0, 0, MOD_FLAMETHROWER }, // WP_FLAMETHROWER // 9 // JPW NOTE: changed maxclip for MP 500->150 + { 300, 1, 300, 1000, DELAY_LOW, 0, 0, 0, MOD_TESLA }, // WP_TESLA // 10 + { 50, 1, 50, 1000, DELAY_LOW, 1200, 0, 0, MOD_SPEARGUN }, // WP_SPEARGUN // 11 + + { 999, 0, 999, 0, 50, 200, 0, 0, MOD_KNIFE2 }, // WP_KNIFE2 // 12 + { MAX_AMMO_45, 1, 8, 1500, DELAY_PISTOL, 400, 0, 0, MOD_COLT }, // WP_COLT // 13 + { MAX_AMMO_45, 1, 30, 2400, DELAY_LOW, 120, 0, 0, MOD_THOMPSON }, // WP_THOMPSON // 14 // NOTE: also 50 round drum magazine + { MAX_AMMO_GARAND,1, 5, 2500, DELAY_HIGH, 1200, 0, 0, MOD_GARAND }, // WP_GARAND // 15 // NOTE: always 5 round clips + { MAX_AMMO_BAR, 1, 20, 2000, DELAY_LOW, 200, 0, 0, MOD_BAR }, // WP_BAR // 16 + { 15, 1, 15, 1000, DELAY_THROW, 1600, 0, 0, MOD_GRENADE_PINEAPPLE }, // WP_GRENADE_PINEAPPLE // 17 + { 5, 1, 5, 1000, DELAY_SHOULDER, 1200, 0, 0, MOD_ROCKET_LAUNCHER }, // WP_ROCKET_LAUNCHER // 18 + + { MAX_AMMO_MAUSER,1, 10, 3000, 0, 1700, 0, 0, MOD_SNIPERRIFLE }, // WP_SNIPER_GER // 19 + { MAX_AMMO_GARAND,1, 5, 3000, 0, 1200, 0, 0, MOD_SNOOPERSCOPE }, // WP_SNIPER_AM // 20 +// { MAX_AMMO_VENOM, 10, 300, 3000, 1200, 1200, 0, 0, MOD_VENOM_FULL }, // WP_VENOM_FULL // - + { MAX_AMMO_VENOM, 10, 300, 3000, 1000, 1000, 0, 0, MOD_VENOM_FULL }, // WP_VENOM_FULL // 21 + { 20, 1, 20, 1000, DELAY_LOW, 1200, 0, 0, MOD_SPEARGUN_CO2 }, // WP_SPEARGUN_CO2 // 22 + + { MAX_AMMO_FG42, 1, 20, 2000, DELAY_LOW, 200, 0, 0, MOD_FG42SCOPE }, // WP_FG42SCOPE // 23 + { MAX_AMMO_BAR, 1, 20, 2000, DELAY_LOW, 90, 0, 0, MOD_BAR }, // WP_BAR2 // 24 + { MAX_AMMO_9MM, 1, 32, 3100, DELAY_LOW, 110, 700, 300, MOD_STEN }, // WP_STEN // 25 + { 3, 1, 1, 1500, 50, 1000, 0, 0, MOD_SYRINGE }, // WP_MEDIC_SYRINGE // 26 // JPW NERVE + { 1, 0, 1, 3000, 50, 1000, 0, 0, MOD_AMMO, }, // WP_AMMO // 27 // JPW NERVE + { 1, 0, 1, 3000, 50, 1000, 0, 0, MOD_ARTY, }, // WP_ARTY + { MAX_AMMO_9MM, 1, 8, 1500, DELAY_PISTOL, 400, 0, 0, MOD_SILENCER }, // WP_SILENCER // 28 + { 30, 1, 8, 1850, DELAY_PISTOL, 200, 0, 0, MOD_AKIMBO }, // WP_AKIMBO // 29 + + { 100, 1, 100, 1000, DELAY_PISTOL, 900, 0, 0, MOD_CROSS }, // WP_CROSS // 31 + { 10, 0, 10, 1000, DELAY_THROW, 1600, 0, 0, MOD_DYNAMITE }, // WP_DYNAMITE // 32 // JPW NERVE used 1 + { 10, 1, 10, 1000, DELAY_THROW, 1600, 0, 0, MOD_DYNAMITE }, // WP_DYNAMITE2 // 33 + +// stubs for some "not-real" weapons (so they always return "yes, you have enough ammo for that gauntlet", etc.) + { 5, 1, 5, 1000, DELAY_SHOULDER, 1200, 0, 0, 0 /*mod_prox*/ }, // WP_PROX // 34 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_MONSTER_ATTACK1 // 35 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_MONSTER_ATTACK2 // 36 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_MONSTER_ATTACK3 // 37 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_GAUNTLET // 38 + + // NERVE - SMF + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_SNIPER // 39 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_MORTAR // 40 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // VERYBIGEXPLOSION // 41 + + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_MEDKIT // 42 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_PLIERS // 43 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_SMOKE_GRENADE // 44 + { 999, 0, 999, 0, 50, 0, 0, 0, 0 }, // WP_SMOKE_GRENADE // 44 + // -NERVE - SMF +}; + + +//----(SA) moved in here so both games can get to it +int weapAlts[] = { + WP_NONE, // 0 WP_NONE + WP_NONE, // 1 WP_KNIFE + WP_SILENCER, // 2 WP_LUGER + WP_NONE, // 3 WP_MP40 + WP_SNIPERRIFLE, // 4 WP_MAUSER + WP_FG42SCOPE, // 5 WP_FG42 // was SP5 + WP_NONE, // 6 WP_GRENADE_LAUNCHER + WP_NONE, // 7 WP_PANZERFAUST + WP_VENOM_FULL, // 8 WP_VENOM +// WP_NONE, // WP_VENOM -- taking venom shotgun out of rotation until animations are done for venom + WP_NONE, // 9 WP_FLAMETHROWER + WP_NONE, // 10 WP_TESLA + WP_SPEARGUN_CO2, // 11 WP_SPEARGUN + WP_NONE, // 12 WP_KNIFE2 + WP_AKIMBO, // 13 WP_COLT //----(SA) new + WP_NONE, // 14 WP_THOMPSON + WP_SNOOPERSCOPE, // 15 WP_GARAND + WP_BAR2, // 16 WP_BAR //----(SA) modified + WP_NONE, // 17 WP_GRENADE_PINEAPPLE + WP_NONE, // 18 WP_ROCKET_LAUNCHER + WP_MAUSER, // 19 WP_SNIPERRIFLE + WP_GARAND, // 20 WP_SNOOPERSCOPE + WP_VENOM, // 21 WP_VENOM_FULL + WP_SPEARGUN, // 22 WP_SPEARGUN_CO2 + WP_FG42, // 23 WP_FG42SCOPE + WP_BAR, // 24 WP_BAR2 //----(SA) new + WP_NONE, // 25 WP_STEN + WP_NONE, // 26 WP_MEDIC_SYRINGE // JPW NERVE + WP_NONE, // 27 WP_AMMO // JPW NERVE + WP_NONE, // 28 WP_ARTY // JPW NERVE + WP_LUGER, // 29 WP_SILENCER //----(SA) was sp5 + WP_COLT, // 30 WP_AKIMBO //----(SA) new + WP_NONE, // 31 WP_CROSS + WP_NONE, // 32 WP_DYNAMITE //----(SA) modified (not in rotation yet) + WP_NONE, /*WP_DYNAMITE2,*/ // 33 WP_DYNAMITE //----(SA) modified + WP_DYNAMITE, // 34 WP_DYNAMITE2 //----(SA) new + + // NERVE - SMF + WP_NONE, // 34 WP_PROX + WP_NONE, // 35 WP_MONSTER_ATTACK1 + WP_NONE, // 36 WP_MONSTER_ATTACK2 + WP_NONE, // 37 WP_MONSTER_ATTACK3 + WP_NONE, // 38 WP_SMOKETRAIL + WP_NONE, // 39 WP_GAUNTLET + WP_NONE, // 40 WP_SNIPER + WP_NONE, // 41 WP_MORTAR + WP_NONE, // 42 VERYBIGEXPLOSION + WP_NONE, // 43 WP_MEDKIT + WP_NONE, // 44 WP_PLIERS + WP_NONE, // 45 WP_SMOKE_GRENADE + // -NERVE - SMF +}; + + +// new (10/18/00) +char *animStrings[] = { + "BOTH_DEATH1", + "BOTH_DEAD1", + "BOTH_DEAD1_WATER", + "BOTH_DEATH2", + "BOTH_DEAD2", + "BOTH_DEAD2_WATER", + "BOTH_DEATH3", + "BOTH_DEAD3", + "BOTH_DEAD3_WATER", + + "BOTH_CLIMB", + "BOTH_CLIMB_DOWN", + "BOTH_CLIMB_DISMOUNT", + + "BOTH_SALUTE", + + "BOTH_PAIN1", + "BOTH_PAIN2", + "BOTH_PAIN3", + "BOTH_PAIN4", + "BOTH_PAIN5", + "BOTH_PAIN6", + "BOTH_PAIN7", + "BOTH_PAIN8", + + "BOTH_GRAB_GRENADE", + + "BOTH_ATTACK1", + "BOTH_ATTACK2", + "BOTH_ATTACK3", + "BOTH_ATTACK4", + "BOTH_ATTACK5", + + "BOTH_EXTRA1", + "BOTH_EXTRA2", + "BOTH_EXTRA3", + "BOTH_EXTRA4", + "BOTH_EXTRA5", + "BOTH_EXTRA6", + "BOTH_EXTRA7", + "BOTH_EXTRA8", + "BOTH_EXTRA9", + "BOTH_EXTRA10", + "BOTH_EXTRA11", + "BOTH_EXTRA12", + "BOTH_EXTRA13", + "BOTH_EXTRA14", + "BOTH_EXTRA15", + "BOTH_EXTRA16", + "BOTH_EXTRA17", + "BOTH_EXTRA18", + "BOTH_EXTRA19", + "BOTH_EXTRA20", + + "TORSO_GESTURE", + "TORSO_GESTURE2", + "TORSO_GESTURE3", + "TORSO_GESTURE4", + + "TORSO_DROP", + + "TORSO_RAISE", // (low) + "TORSO_ATTACK", + "TORSO_STAND", + "TORSO_STAND_ALT1", + "TORSO_STAND_ALT2", + "TORSO_READY", + "TORSO_RELAX", + + "TORSO_RAISE2", // (high) + "TORSO_ATTACK2", + "TORSO_STAND2", + "TORSO_STAND2_ALT1", + "TORSO_STAND2_ALT2", + "TORSO_READY2", + "TORSO_RELAX2", + + "TORSO_RAISE3", // (pistol) + "TORSO_ATTACK3", + "TORSO_STAND3", + "TORSO_STAND3_ALT1", + "TORSO_STAND3_ALT2", + "TORSO_READY3", + "TORSO_RELAX3", + + "TORSO_RAISE4", // (shoulder) + "TORSO_ATTACK4", + "TORSO_STAND4", + "TORSO_STAND4_ALT1", + "TORSO_STAND4_ALT2", + "TORSO_READY4", + "TORSO_RELAX4", + + "TORSO_RAISE5", // (throw) + "TORSO_ATTACK5", + "TORSO_ATTACK5B", + "TORSO_STAND5", + "TORSO_STAND5_ALT1", + "TORSO_STAND5_ALT2", + "TORSO_READY5", + "TORSO_RELAX5", + + "TORSO_RELOAD1", // (low) + "TORSO_RELOAD2", // (high) + "TORSO_RELOAD3", // (pistol) + "TORSO_RELOAD4", // (shoulder) + + "TORSO_MG42", // firing tripod mounted weapon animation + + "TORSO_MOVE", // torso anim to play while moving and not firing (swinging arms type thing) + "TORSO_MOVE_ALT", // torso anim to play while moving and not firing (swinging arms type thing) + + "TORSO_EXTRA", + "TORSO_EXTRA2", + "TORSO_EXTRA3", + "TORSO_EXTRA4", + "TORSO_EXTRA5", + "TORSO_EXTRA6", + "TORSO_EXTRA7", + "TORSO_EXTRA8", + "TORSO_EXTRA9", + "TORSO_EXTRA10", + + "LEGS_WALKCR", + "LEGS_WALKCR_BACK", + "LEGS_WALK", + "LEGS_RUN", + "LEGS_BACK", + "LEGS_SWIM", + "LEGS_SWIM_IDLE", + + "LEGS_JUMP", + "LEGS_JUMPB", + "LEGS_LAND", + + "LEGS_IDLE", + "LEGS_IDLE_ALT", // "LEGS_IDLE2" + "LEGS_IDLECR", + + "LEGS_TURN", + + "LEGS_BOOT", // kicking animation + + "LEGS_EXTRA1", + "LEGS_EXTRA2", + "LEGS_EXTRA3", + "LEGS_EXTRA4", + "LEGS_EXTRA5", + "LEGS_EXTRA6", + "LEGS_EXTRA7", + "LEGS_EXTRA8", + "LEGS_EXTRA9", + "LEGS_EXTRA10", +}; + + +// old +char *animStringsOld[] = { + "BOTH_DEATH1", + "BOTH_DEAD1", + "BOTH_DEATH2", + "BOTH_DEAD2", + "BOTH_DEATH3", + "BOTH_DEAD3", + + "BOTH_CLIMB", + "BOTH_CLIMB_DOWN", + "BOTH_CLIMB_DISMOUNT", + + "BOTH_SALUTE", + + "BOTH_PAIN1", + "BOTH_PAIN2", + "BOTH_PAIN3", + "BOTH_PAIN4", + "BOTH_PAIN5", + "BOTH_PAIN6", + "BOTH_PAIN7", + "BOTH_PAIN8", + + "BOTH_EXTRA1", + "BOTH_EXTRA2", + "BOTH_EXTRA3", + "BOTH_EXTRA4", + "BOTH_EXTRA5", + + "TORSO_GESTURE", + "TORSO_GESTURE2", + "TORSO_GESTURE3", + "TORSO_GESTURE4", + + "TORSO_DROP", + + "TORSO_RAISE", // (low) + "TORSO_ATTACK", + "TORSO_STAND", + "TORSO_READY", + "TORSO_RELAX", + + "TORSO_RAISE2", // (high) + "TORSO_ATTACK2", + "TORSO_STAND2", + "TORSO_READY2", + "TORSO_RELAX2", + + "TORSO_RAISE3", // (pistol) + "TORSO_ATTACK3", + "TORSO_STAND3", + "TORSO_READY3", + "TORSO_RELAX3", + + "TORSO_RAISE4", // (shoulder) + "TORSO_ATTACK4", + "TORSO_STAND4", + "TORSO_READY4", + "TORSO_RELAX4", + + "TORSO_RAISE5", // (throw) + "TORSO_ATTACK5", + "TORSO_ATTACK5B", + "TORSO_STAND5", + "TORSO_READY5", + "TORSO_RELAX5", + + "TORSO_RELOAD1", // (low) + "TORSO_RELOAD2", // (high) + "TORSO_RELOAD3", // (pistol) + "TORSO_RELOAD4", // (shoulder) + + "TORSO_MG42", // firing tripod mounted weapon animation + + "TORSO_MOVE", // torso anim to play while moving and not firing (swinging arms type thing) + + "TORSO_EXTRA2", + "TORSO_EXTRA3", + "TORSO_EXTRA4", + "TORSO_EXTRA5", + + "LEGS_WALKCR", + "LEGS_WALKCR_BACK", + "LEGS_WALK", + "LEGS_RUN", + "LEGS_BACK", + "LEGS_SWIM", + + "LEGS_JUMP", + "LEGS_LAND", + + "LEGS_IDLE", + "LEGS_IDLE2", + "LEGS_IDLECR", + + "LEGS_TURN", + + "LEGS_BOOT", // kicking animation + + "LEGS_EXTRA1", + "LEGS_EXTRA2", + "LEGS_EXTRA3", + "LEGS_EXTRA4", + "LEGS_EXTRA5", +}; + +/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) SUSPENDED SPIN PERSISTANT +DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. +SUSPENDED - will allow items to hang in the air, otherwise they are dropped to the next surface. +SPIN - will allow items to spin in place. +PERSISTANT - some items (ex. clipboards) can be picked up, but don't disappear + +If an item is the target of another entity, it will not spawn in until fired. + +An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. + +"notfree" if set to 1, don't spawn in free for all games +"notteam" if set to 1, don't spawn in team games +"notsingle" if set to 1, don't spawn in single player games +"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. +"random" random number of plus or minus seconds varied from the respawn time +"count" override quantity or duration on most items. +"stand" if the item has a stand (ex: mp40_stand.md3) this specifies which stand tag to attach the weapon to ("stand":"4" would mean "tag_stand4" for example) only weapons support stands currently +*/ + +// JOSEPH 5-2-00 +//----(SA) the addition of the 'ammotype' field was added by me, not removed by id (SA) +gitem_t bg_itemlist[] = +{ + { + NULL, + NULL, + { NULL, + NULL, + 0, 0, 0}, + NULL, // icon + NULL, // ammo icon + NULL, // pickup + 0, + 0, + 0, + 0, // ammotype + 0, // cliptype + "", // precache + "", // sounds + {0,0,0,0,0} + }, // leave index 0 alone + + + +/*QUAKED item_clipboard (1 1 0) (-8 -8 -8) (8 8 8) SUSPENDED SPIN PERSISTANT +"model" - model to display in the world. defaults to 'models/powerups/clipboard/clipboard.md3' (the clipboard laying flat is 'clipboard2.md3') +"popup" - menu to popup. no default since you won't want the same clipboard laying around. (clipboard will display a 'put popup here' message) +"notebookpage" - when clipboard is picked up, this page (menu) will be added to your notebook (FIXME: TODO: more info goes here) + +We currently use: +clip_interrogation +clip_codeddispatch +clip_alertstatus + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/clipboard/clipboard.md3" +*/ +/* +"scriptName" +*/ + { + "item_clipboard", + "", + { "models/powerups/clipboard/clipboard.md3", + 0, + 0, + 0, 0 }, + "icons/iconh_small", + NULL, // ammo icon + "", + 1, + IT_CLIPBOARD, + 0, + 0, + 0, + "", + "", + {0,0,0,0,0} + }, + +/*QUAKED item_treasure (1 1 0) (-8 -8 -8) (8 8 8) suspended +Items the player picks up that are just used to tally a score at end-level +"model" defaults to 'models/powerups/treasure/goldbar.md3' +"noise" sound to play on pickup. defaults to 'sound/pickup/treasure/gold.wav' +"message" what to call the item when it's picked up. defaults to "Treasure Item" (SA: temp) +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/treasure/goldbar.md3" +*/ +/* +"scriptName" +*/ + { + "item_treasure", + "sound/pickup/treasure/gold.wav", + { "models/powerups/treasure/goldbar.md3", + 0, + 0, + 0, 0 }, + "icons/iconh_small", // (SA) placeholder + NULL, // ammo icon + "Treasure Item", // (SA) placeholder + 5, + IT_TREASURE, + 0, + 0, + 0, + "", + "", + {0,0,0,0,0} + }, + + + // + // ARMOR/HEALTH/STAMINA + // + + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/health/health_s.md3" +*/ + { + "item_health_small", + "sound/items/n_health.wav", + { "models/powerups/health/health_s.md3", + 0, + 0, 0, 0 }, + "icons/iconh_small", + NULL, // ammo icon + "Small Health", + 5, + IT_HEALTH, + 0, + 0, + 0, + "", + "", + {10,5,5,5,5} + }, + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/health/health_m.md3" +*/ + { + "item_health", + "sound/multiplayer/health_pickup.wav", // JPW NERVE also not single-binary friendly FIXME + { "models/multiplayer/medpack/medpack_pickup.md3", // JPW NERVE was "models/powerups/health/health_m.md3", + 0, // FIXME this isn't single/multiplayer friendly if we go back to 1 codebase + 0, 0, 0 }, + "icons/iconh_med", + NULL, // ammo icon + "Med Health", + 25, + IT_HEALTH, + 0, + 0, + 0, + "", + "", + {50,25,20,15,15} + }, + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/health/health_l.md3" +*/ + { + "item_health_large", + "sound/items/n_health.wav", + { "models/powerups/health/health_l.md3", + 0, 0, 0, 0 }, + "icons/iconh_large", + NULL, // ammo icon + "Large Health", + 50, + IT_HEALTH, + 0, + 0, + 0, + "", + "", + {100,50,50,30,30} + }, + +/*QUAKED item_health_turkey (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +multi-stage health item. +gives amount on first use based on skill: +skill 1: 50 +skill 2: 50 +skill 3: 50 +skill 4: 40 +skill 5: 30 + +then gives 15 on "finishing up" + +player will only eat what he needs. health at 90, turkey fills up and leaves remains (leaving 15). health at 5 you eat the whole thing. +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/health/health_t1.md3" +*/ + { + "item_health_turkey", + "sound/items/n_health.wav", + { "models/powerups/health/health_t3.md3", // just plate (should now be destructable) + "models/powerups/health/health_t2.md3", // half eaten + "models/powerups/health/health_t1.md3", // whole turkey + 0, 0 }, + "icons/iconh_turkey", + NULL, // ammo icon + "Hot Meal", + 15, // amount given in last stage + IT_HEALTH, + 0, + 0, + 0, + "", + "", + {50,50,50,40,30} // amount given in first stage based on gameskill level + }, + +/*QUAKED item_health_wall (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +defaults to 50 pts health +you will probably want to check the 'suspended' box to keep it from falling to the ground +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/health/health_w.md3" +*/ + { + "item_health_wall", + "sound/items/n_health.wav", + { "models/powerups/health/health_w.md3", + 0, 0, 0, 0 }, + "icons/iconh_wall", + NULL, // ammo icon + "Health", + 25, + IT_HEALTH, + 0, + 0, + 0, + "", + "", + {25,25,25,25,25} + }, + + // + // STAMINA + // + + +/*QUAKED item_stamina_stein (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +defaults to 30 sec stamina boost +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/instant/stamina_stein.md3" +*/ + { + "item_stamina_stein", + "sound/items/n_health.wav", + { "models/powerups/instant/stamina_stein.md3", + 0, 0, 0, 0 }, + "icons/icons_wall", + NULL, // ammo icon + "Stamina", + 25, + IT_POWERUP, + PW_NOFATIGUE, + 0, + 0, + "", + "", + {25,25,25,25,25} + }, + + +/*QUAKED item_stamina_brandy (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +defaults to 30 sec stamina boost + +multi-stage health item. +gives amount on first use based on skill: +skill 1: 50 +skill 2: 50 +skill 3: 50 +skill 4: 40 +skill 5: 30 + +then gives 15 on "finishing up" + +player will only eat what he needs. health at 90, turkey fills up and leaves remains (leaving 15). health at 5 you eat the whole thing. +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/instant/stamina_brandy1.md3" +*/ + { + "item_stamina_brandy", + "sound/items/n_health.wav", + { "models/powerups/instant/stamina_brandy2.md3", + "models/powerups/instant/stamina_brandy1.md3", + 0, 0, 0 }, + "icons/iconh_wall", + NULL, // ammo icon + "Stamina", + 25, + IT_POWERUP, + PW_NOFATIGUE, + 0, + 0, + "", + "", + {25,25,25,25,25} + }, + + + + // + // ARMOR + // + + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/armor/armor_body1.md3" +*/ + { + "item_armor_body", + "sound/pickup/armor/body_pickup.wav", + { "models/powerups/armor/armor_body1.md3", + 0, 0, 0, 0 }, + "icons/iconr_body", + NULL, // ammo icon + "Flak Jacket", + 75, + IT_ARMOR, + 0, + 0, + 0, + "", + "", + {75,75,75,75,75} + }, + +/*QUAKED item_armor_head (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/armor/armor_head1.md3" +*/ + { + "item_armor_head", + "sound/pickup/armor/head_pickup.wav", + { "models/powerups/armor/armor_head1.md3", + 0, 0, 0, 0 }, + "icons/iconr_head", + NULL, // ammo icon + "Armored Helmet", + 25, + IT_ARMOR, + 0, + 0, + 0, + "", + "", + {25,25,25,25,25} + }, + + + + // + // WEAPONS + // + // wolf weapons (SA) + +/*QUAKED weapon_knife (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/knife/knife.md3" +*/ + { + "weapon_knife", + "sound/misc/w_pkup.wav", + { "models/multiplayer/knife/knife.md3", + "models/multiplayer/knife/v_knife.md3", + 0, + "models/multiplayer/knife/v_knife_axis.md3", + 0}, + + "icons/iconw_knife_1", // icon + "icons/ammo2", // ammo icon + "Knife", // pickup + 50, + IT_WEAPON, + WP_KNIFE, + WP_KNIFE, + WP_KNIFE, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + + +/*QUAKED weapon_knife2 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/knife2/knife2.md3" +*/ + { + "weapon_knife2", + "sound/misc/w_pkup.wav", + { "models/weapons2/knife2/knife2.md3", + "models/weapons2/knife2/v_knife2.md3", + 0, 0, 0 }, + + "icons/iconw_knife2_1", // icon + "icons/ammo2", // ammo icon + "Other Knife", // pickup + 50, + IT_WEAPON, + WP_KNIFE2, + WP_KNIFE2, + WP_KNIFE2, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + + + +/*QUAKED weapon_luger (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/luger/luger.md3" +*/ + { + "weapon_luger", + "sound/misc/w_pkup.wav", + { "models/weapons2/luger/luger.md3", + "models/weapons2/luger/v_luger.md3", + 0, 0, + "models/weapons2/luger/ss_luger.md3"}, + + "icons/iconw_luger_1", // icon + "icons/ammo2", // ammo icon + "Luger", // pickup + 50, + IT_WEAPON, + WP_LUGER, + WP_LUGER, + WP_LUGER, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + + +/*QUAKED weapon_mauserRifle (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/mauser/mauser.md3" +*/ + { + "weapon_mauserRifle", + "sound/misc/w_pkup.wav", + { "models/weapons2/mauser/mauser.md3", + "models/weapons2/mauser/v_mauser.md3", + "models/multiplayer/mauser/mauser_pickup.md3", + "models/multiplayer/mauser/v_mauser_axis.md3", + "models/weapons2/mauser/ss_mauser.md3" }, + + "icons/iconw_mauser_1", // icon + "icons/ammo3", // ammo icon + "Mauser Rifle", // pickup + 50, + IT_WEAPON, + WP_MAUSER, + WP_MAUSER, + WP_MAUSER, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/*QUAKED weapon_thompson (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/thompson/thompson.md3" +*/ + { + "weapon_thompson", + "sound/misc/w_pkup.wav", + { "models/weapons2/thompson/thompson.md3", + "models/weapons2/thompson/v_thompson.md3", + 0, + "models/multiplayer/thompson/v_thompson_barrel3_axis.md3", + "models/weapons2/thompson/ss_thompson.md3"}, + + "icons/iconw_thompson_1", // icon + "icons/ammo2", // ammo icon + "Thompson", // pickup + 30, + IT_WEAPON, + WP_THOMPSON, + WP_COLT, + WP_THOMPSON, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/*QUAKED weapon_sten (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/sten/sten.md3" +*/ + { + "weapon_sten", + "sound/misc/w_pkup.wav", + { "models/weapons2/sten/sten.md3", + "models/weapons2/sten/v_sten.md3", + 0, 0, + "models/weapons2/sten/ss_sten.md3"}, + "icons/iconw_sten_1", // icon + "icons/ammo2", // ammo icon + "Sten", // pickup + 30, + IT_WEAPON, + WP_STEN, + WP_LUGER, + WP_STEN, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/*QUAKED weapon_colt (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/colt/colt.md3" +*/ + { + "weapon_colt", + "sound/misc/w_pkup.wav", + { "models/weapons2/colt/colt.md3", + "models/weapons2/colt/v_colt.md3", + 0, 0, + "models/weapons2/colt/ss_colt.md3"}, + + "icons/iconw_colt_1", // icon + "icons/ammo2", // ammo icon + "Colt", // pickup + 50, + IT_WEAPON, + WP_COLT, + WP_COLT, + WP_COLT, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/*QUAKED weapon_mp40 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +"stand" values: + no value: laying in a default position on it's side (default) + 2: upright, barrel pointing up, slightly angled (rack mount) +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models\weapons2\mp40\mp40.md3" +*/ + { + "weapon_mp40", + "sound/misc/w_pkup.wav", + { "models/weapons2/mp40/mp40.md3", + "models/weapons2/mp40/v_mp40.md3", + 0, + "models/multiplayer/mp40/v_mp40_barrel3_axis.md3", + "models/weapons2/mp40/ss_mp40.md3" }, + + "icons/iconw_mp40_1", // icon + "icons/ammo2", // ammo icon + "MP40", // pickup + 30, + IT_WEAPON, + WP_MP40, + WP_LUGER, + WP_MP40, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/*QUAKED weapon_panzerfaust (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/panzerfaust/pf.md3" +*/ + { + "weapon_panzerfaust", + "sound/misc/w_pkup.wav", + { "models/weapons2/panzerfaust/pf.md3", + "models/weapons2/panzerfaust/v_pf.md3", + 0, 0, + "models/weapons2/panzerfaust/ss_pf.md3"}, + + "icons/iconw_panzerfaust_1", // icon + "icons/ammo6", // ammo icon + "Panzerfaust", // pickup + 1, + IT_WEAPON, + WP_PANZERFAUST, + WP_PANZERFAUST, + WP_PANZERFAUST, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +//----(SA) removed the quaked for this. we don't actually have a grenade launcher as such. It's given implicitly +// by virtue of getting grenade ammo. So we don't need to have them in maps +/* +weapon_grenadelauncher +*/ + { + "weapon_grenadelauncher", + "sound/misc/w_pkup.wav", + { "models/weapons2/grenade/grenade.md3", + "models/weapons2/grenade/v_grenade.md3", + 0, 0, + "models/weapons2/grenade/ss_grenade.md3"}, + + "icons/iconw_grenade_1", // icon + "icons/icona_grenade", // ammo icon + "Grenade", // pickup + 6, + IT_WEAPON, + WP_GRENADE_LAUNCHER, + WP_GRENADE_LAUNCHER, + WP_GRENADE_LAUNCHER, + "", // precache + "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav", // sounds + {0,0,0,0,0} + }, + +/* +weapon_grenadePineapple +*/ + { + "weapon_grenadepineapple", + "sound/misc/w_pkup.wav", + { "models/weapons2/grenade/pineapple.md3", + "models/weapons2/grenade/v_pineapple.md3", + 0, 0, + "models/weapons2/grenade/ss_pineapple.md3"}, + + "icons/iconw_pineapple_1", // icon + "icons/icona_pineapple", // ammo icon + "Pineapple", // pickup + 6, + IT_WEAPON, + WP_GRENADE_PINEAPPLE, + WP_GRENADE_PINEAPPLE, + WP_GRENADE_PINEAPPLE, + "", // precache + "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav", // sounds + {0,0,0,0,0} + }, + +/* JPW NERVE +weapon_grenadesmoke +*/ + { + "weapon_grenadesmoke", + "sound/misc/w_pkup.wav", + { "models/multiplayer/smokegrenade/smokegrenade.md3", + "models/multiplayer/smokegrenade/v_smokegrenade.md3", + 0, 0, 0}, + + "icons/iconw_smokegrenade_1", // icon + "icons/ammo2", // ammo icon + "smokeGrenade", // pickup + 50, + IT_WEAPON, + WP_SMOKE_GRENADE, + WP_SMOKE_GRENADE, + WP_SMOKE_GRENADE, + "", // precache + "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav", // sounds + {0,0,0,0,0} + }, +// jpw + +/* JPW NERVE +weapon_smoketrail -- only used as a special effects emitter for smoke trails (artillery spotter etc) +*/ + { + "weapon_smoketrail", + "sound/misc/w_pkup.wav", + { "models/multiplayer/smokegrenade/smokegrenade.md3", + "models/multiplayer/smokegrenade/v_smokegrenade.md3", + 0, 0, 0}, + + "icons/iconw_smokegrenade_1", // icon + "icons/ammo2", // ammo icon + "smokeTrail", // pickup + 50, + IT_WEAPON, + WP_SMOKETRAIL, + WP_SMOKETRAIL, + WP_SMOKETRAIL, + "", // precache + "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav", // sounds + {0,0,0,0,0} + }, +// jpw + +// DHM - Nerve +/* +weapon_medic_heal +*/ + { + "weapon_medic_heal", + "sound/misc/w_pkup.wav", + { "models/multiplayer/medpack/medpack.md3", + "models/multiplayer/medpack/v_medpack.md3", + 0, 0, 0 }, + + "icons/iconw_medheal_1", // icon + "icons/ammo2", // ammo icon + "medicheal", // pickup + 50, + IT_WEAPON, + WP_MEDKIT, + WP_MEDKIT, + WP_MEDKIT, + "", // precache + "sound/multiplayer/allies/a-medic3.wav sound/multiplayer/axis/g-medic3.wav sound/multiplayer/allies/a-medic2.wav sound/multiplayer/axis/g-medic2.wav sound/multiplayer/axis/g-medic1.wav sound/multiplayer/allies/a-medic1.wav", // sounds + {0,0,0,0,0} + }, +// dhm + +/* +weapon_dynamite +*/ + { + "weapon_dynamite", + "sound/misc/w_pkup.wav", + { "models/multiplayer/dynamite/dynamite_3rd.md3", // JPW NERVE + "models/weapons2/dynamite/v_dynamite.md3", // JPW NERVE + 0, 0, 0 }, + + "icons/iconw_dynamite_1", // icon + "icons/ammo9", // ammo icon + "Dynamite Weapon", // pickup + 7, + IT_WEAPON, + WP_DYNAMITE, + WP_DYNAMITE, + WP_DYNAMITE, + "models/multiplayer/dynamite/dynamite.md3 models/multiplayer/dynamite/dynamite_3rd.md3", // precache // JPW NERVE + "", // sounds + {0,0,0,0,0} + }, + + +/* +weapon_dynamite2 +*/ + { + "weapon_dynamite2", + "sound/misc/w_pkup.wav", + { "models/weapons2/dynamite/dynamite.md3", + "models/weapons2/dynamite/v_dynamite.md3", + 0, 0, 0 }, + + "icons/iconw_dynamite_1", // icon + "icons/ammo9", // ammo icon + "Dynamite Weapon", // pickup + 7, + IT_WEAPON, + WP_DYNAMITE2, + WP_DYNAMITE, + WP_DYNAMITE, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/*QUAKED weapon_venom (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/venom/venom.md3" +*/ + { + "weapon_venom", + "sound/misc/w_pkup.wav", + { "models/weapons2/venom/venom.md3", + "models/weapons2/venom/v_venom.md3", + "models/weapons2/venom/pu_venom.md3", + 0, + "models/weapons2/venom/ss_venom.md3"}, + + "icons/iconw_venom_1", // icon + "icons/ammo8", // ammo icon + "Venom", // pickup + 700, + IT_WEAPON, + WP_VENOM, + WP_VENOM, + WP_VENOM, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/*QUAKED weapon_flamethrower (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/flamethrower/flamethrower.md3" +*/ + { + "weapon_flamethrower", + "sound/misc/w_pkup.wav", + { "models/weapons2/flamethrower/flamethrower.md3", + "models/weapons2/flamethrower/v_flamethrower.md3", + "models/weapons2/flamethrower/pu_flamethrower.md3", + 0, + "models/weapons2/flamethrower/ss_flamethrower.md3"}, + + "icons/iconw_flamethrower_1", // icon + "icons/ammo10", // ammo icon + "Flamethrower", // pickup + 200, + IT_WEAPON, + WP_FLAMETHROWER, + WP_FLAMETHROWER, + WP_FLAMETHROWER, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/*QUAKED weapon_sniperScope (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons2/mauser/mauser.md3" +*/ + { + "weapon_sniperScope", + "sound/misc/w_pkup.wav", + { "models/weapons2/mauser/mauser.md3", + "models/weapons2/mauser/v_mauser.md3", +// "models/weapons2/mauser/v_mauser_scope.md3", + "models/multiplayer/mauser/mauser_pickup.md3", + 0, + "models/weapons2/mauser/ss_mauser.md3"}, + +// "icons/iconw_sniper_1", // icon + "icons/iconw_mauser_1", // icon + "icons/ammo10", // ammo icon + "Sniper Scope", // pickup + 200, + IT_WEAPON, + WP_SNIPERRIFLE, + WP_MAUSER, + WP_MAUSER, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +/* +weapon_mortar (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_mortar", + "sound/misc/w_pkup.wav", + { "models/weapons2/grenade/grenade.md3", + "models/weapons2/grenade/v_grenade.md3", + 0, 0}, + "icons/iconw_grenade_1", // icon + "icons/icona_grenade", // ammo icon + "nopickup(WP_MORTAR)", // pickup + 6, + IT_WEAPON, + WP_MORTAR, + WP_MORTAR, + WP_MORTAR, + "", // precache + "sound/weapons/mortar/mortarf1.wav", // sounds + {0,0,0,0,0} + }, + + +// JPW NERVE -- class-specific multiplayer weapon, can't be picked up, dropped, or placed in map +/* +weapon_class_special (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_class_special", + "sound/misc/w_pkup.wav", + { "models/multiplayer/pliers/pliers.md3", + "models/multiplayer/pliers/v_pliers.md3", + 0, + "models/multiplayer/pliers/v_pliers_axis.md3", + ""}, + + "icons/iconw_pliers_1", // icon + "icons/ammo2", // ammo icon + "Special", // pickup + 50, // this should never be picked up + IT_WEAPON, + WP_PLIERS, + WP_PLIERS, + WP_PLIERS, + "", // precache + "sound/multiplayer/allies/a-dynamite_planted.wav sound/multiplayer/axis/g-dynamite_planted.wav sound/multiplayer/allies/a-dynamite_defused.wav sound/multiplayer/axis/g-dynamite_defused.wav", // sounds + {0,0,0,0,0} + }, + +/* +weapon_arty (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_arty", + "sound/misc/w_pkup.wav", + { "models/multiplayer/syringe/syringe.md3", + "models/multiplayer/syringe/v_syringe.md3", + 0, + 0, + ""}, + + "icons/iconw_syringe_1", // icon + "icons/ammo2", // ammo icon + "Artillery", // pickup + 50, // this should never be picked up + IT_WEAPON, + WP_ARTY, + WP_ARTY, + WP_ARTY, + "", // precache + "sound/multiplayer/allies/a-firing.wav sound/multiplayer/axis/g-firing.wav sound/multiplayer/allies/a-art_abort.wav sound/multiplayer/axis/g-art_abort.wav", // sounds + {0,0,0,0,0} + }, + + /* +weapon_medic_syringe (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_medic_syringe", + "sound/misc/w_pkup.wav", + { "models/multiplayer/syringe/syringe.md3", + "models/multiplayer/syringe/v_syringe.md3", + 0, + "models/multiplayer/syringe/v_syringe_axis.md3", + ""}, + + "icons/iconw_syringe_1", // icon + "icons/ammo2", // ammo icon + "Syringe", // pickup + 50, // this should never be picked up + IT_WEAPON, + WP_MEDIC_SYRINGE, + WP_MEDIC_SYRINGE, + WP_MEDIC_SYRINGE, + "", // precache + "sound/multiplayer/vo_revive.wav", // sounds + {0,0,0,0,0} + }, +/* +weapon_magicammo (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_magicammo", + "sound/misc/w_pkup.wav", + { "models/multiplayer/ammopack/ammopack.md3", + "models/multiplayer/ammopack/v_ammopack.md3", + "models/multiplayer/ammopack/ammopack_pickup.md3", + 0, + ""}, + + "icons/iconw_ammopack_1", // icon + "icons/ammo2", // ammo icon + "Ammo Pack", // pickup + 50, // this should never be picked up + IT_WEAPON, + WP_AMMO, + WP_AMMO, + WP_AMMO, + "", // precache + "sound/multiplayer/allies/a-aborting.wav sound/multiplayer/axis/g-aborting.wav sound/multiplayer/allies/a-affirmative_omw.wav sound/multiplayer/axis/g-affirmative_omw.wav", // sounds + {0,0,0,0,0} + }, +/* +weapon_binoculars (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_binoculars", + "sound/misc/w_pkup.wav", + { "", + "", + "", + 0, + ""}, + + "", // icon + "", // ammo icon + "Binoculars", // pickup + 50, // this should never be picked up + IT_WEAPON, + WP_BINOCULARS, + WP_BINOCULARS, + WP_BINOCULARS, + "", // precache + "", // sounds + {0,0,0,0,0} + }, + +// jpw + + + // + // AMMO ITEMS + // + + + +/*QUAKED ammo_9mm_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Luger pistol, MP40 machinegun + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am9mm_s.md3" +*/ + { + "ammo_9mm_small", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am9mm_s.md3", + 0, 0, 0, 0 }, + "icons/iconw_luger_1", // icon + NULL, // ammo icon + "9mm Rounds", // pickup + 30, + IT_AMMO, + WP_LUGER, + WP_LUGER, + WP_LUGER, + "", // precache + "", // sounds + {100,60,45,30,30} + }, +/*QUAKED ammo_9mm (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Luger pistol, MP40 machinegun + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am9mm_m.md3" +*/ + { + "ammo_9mm", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am9mm_m.md3", + 0, 0, 0, 0 }, + "icons/iconw_luger_1", // icon + NULL, // ammo icon + "9mm", // pickup //----(SA) changed + 60, + IT_AMMO, + WP_LUGER, + WP_LUGER, + WP_LUGER, + "", // precache + "", // sounds + {100,60,45,30,30} + }, +/*QUAKED ammo_9mm_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Luger pistol, MP40 machinegun + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am9mm_l.md3" +*/ + { + "ammo_9mm_large", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am9mm_l.md3", + 0, 0, 0, 0 }, + "icons/iconw_luger_1", // icon + NULL, // ammo icon + "9mm Box", // pickup + 100, + IT_AMMO, + WP_LUGER, + WP_LUGER, + WP_LUGER, + "", // precache + "", // sounds + {100,60,45,30,30} + }, + + +/*QUAKED ammo_45cal_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Thompson, Colt + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am45cal_s.md3" +*/ + { + "ammo_45cal_small", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am45cal_s.md3", + 0, 0, 0, 0 }, + "icons/iconw_luger_1", // icon + NULL, // ammo icon + ".45cal Rounds", // pickup + 20, + IT_AMMO, + WP_COLT, + WP_COLT, + WP_COLT, + "", // precache + "", // sounds + {100,60,45,30,30} + }, +/*QUAKED ammo_45cal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Thompson, Colt + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am45cal_m.md3" +*/ + { + "ammo_45cal", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am45cal_m.md3", + 0, 0, 0, 0 }, + "icons/iconw_luger_1", // icon + NULL, // ammo icon + ".45cal", // pickup //----(SA) changed + 60, + IT_AMMO, + WP_COLT, + WP_COLT, + WP_COLT, + "", // precache + "", // sounds + {100,60,45,30,30} + }, +/*QUAKED ammo_45cal_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Thompson, Colt + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am45cal_l.md3" +*/ + { + "ammo_45cal_large", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am45cal_l.md3", + 0, 0, 0, 0 }, + "icons/iconw_luger_1", // icon + NULL, // ammo icon + ".45cal Box", // pickup + 100, + IT_AMMO, + WP_COLT, + WP_COLT, + WP_COLT, + "", // precache + "", // sounds + {100,60,45,30,30} + }, + + + + +/*QUAKED ammo_792mm_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Mauser rifle, FG42 + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am792mm_s.md3" +*/ + { + "ammo_792mm_small", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am792mm_s.md3", + 0, 0, 0, 0 }, + "icons/icona_machinegun", // icon + NULL, // ammo icon + "7.92mm Rounds", // pickup + 50, + IT_AMMO, + WP_MAUSER, + WP_MAUSER, + WP_MAUSER, + "", // precache + "", // sounds + {85,50,35,25,25} + }, +/*QUAKED ammo_792mm (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Mauser rifle, FG42 + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am792mm_m.md3" +*/ + { + "ammo_792mm", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am792mm_m.md3", + 0, 0, 0, 0 }, + "icons/icona_machinegun", // icon + NULL, // ammo icon + "7.92mm", // pickup //----(SA) changed + 10, + IT_AMMO, + WP_MAUSER, + WP_MAUSER, + WP_MAUSER, + "", // precache + "", // sounds + {10,10,10,10,10} + }, +/*QUAKED ammo_792mm_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Mauser rifle, FG42 + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am792mm_l.md3" +*/ + { + "ammo_792mm_large", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am792mm_l.md3", + 0, 0, 0, 0 }, + "icons/icona_machinegun", // icon + NULL, // ammo icon + "7.92mm Box", // pickup + 50, + IT_AMMO, + WP_MAUSER, + WP_MAUSER, + WP_MAUSER, + "", // precache + "", // sounds + {85,50,35,25,25} + }, + + + + +/*QUAKED ammo_30cal_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Garand rifle + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am30cal_s.md3" +*/ + { + "ammo_30cal_small", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am30cal_s.md3", + 0, 0, 0, 0 }, + "icons/icona_machinegun", // icon + NULL, // ammo icon + ".30cal Rounds", // pickup + 50, + IT_AMMO, + WP_GARAND, + WP_GARAND, + WP_GARAND, + "", // precache + "", // sounds + {85,50,35,25,25} + }, +/*QUAKED ammo_30cal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Garand rifle + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am30cal_m.md3" +*/ + { + "ammo_30cal", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am30cal_m.md3", + 0, 0, 0, 0 }, + "icons/icona_machinegun", // icon + NULL, // ammo icon + ".30cal", // pickup //----(SA) changed + 50, + IT_AMMO, + WP_GARAND, + WP_GARAND, + WP_GARAND, + "", // precache + "", // sounds + {85,50,35,25,25} + }, +/*QUAKED ammo_30cal_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Garand rifle + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am30cal_l.md3" +*/ + { + "ammo_30cal_large", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am30cal_l.md3", + 0, 0, 0, 0 }, + "icons/icona_machinegun", // icon + NULL, // ammo icon + ".30cal Box", // pickup + 50, + IT_AMMO, + WP_GARAND, + WP_GARAND, + WP_GARAND, + "", // precache + "", // sounds + {85,50,35,25,25} + }, + + + + +/*QUAKED ammo_127mm (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Venom gun + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/am127mm.md3" +*/ + { + "ammo_127mm", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/am127mm.md3", + 0, 0, 0, 0 }, + "icons/icona_machinegun", // icon + NULL, // ammo icon + "12.7mm", // pickup + 100, + IT_AMMO, + WP_VENOM, + WP_VENOM, + WP_VENOM, + "", // precache + "", // sounds + {150,100,75,50,50} + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amgren_bag.md3" +*/ + { + "ammo_grenades", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/amgren_bag.md3", + 0, 0, 0, 0 }, + "icons/icona_grenade", // icon + NULL, // ammo icon + "Grenades", // pickup + 5, + IT_AMMO, + WP_GRENADE_LAUNCHER, + WP_GRENADE_LAUNCHER, + WP_GRENADE_LAUNCHER, + "", // precache + "", // sounds + {10,5,4,3,3} + }, + +/*QUAKED ammo_grenades_american (.3 .3 1) (-16 -16 -16) (16 16 16) suspended + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amgren_bag.md3" +*/ + { + "ammo_grenades_american", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/amgren_bag.md3", + 0, 0, 0, 0 }, + "icons/icona_pineapple", // icon + NULL, // ammo icon + "Pineapples", // pickup + 5, + IT_AMMO, + WP_GRENADE_PINEAPPLE, + WP_GRENADE_PINEAPPLE, + WP_GRENADE_PINEAPPLE, + "", // precache + "", // sounds + {10,5,4,3,3} + }, + +/*QUAKED ammo_dynamite (.3 .3 1) (-16 -16 -16) (16 16 16) suspended + + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amgren_bag.md3" +*/ + { + "ammo_dynamite", + "sound/misc/am_pkup.wav", + { "models/multiplayer/dynamite/dynamite.md3", + 0, 0, 0, 0 }, + "icons/icona_dynamite", // icon + NULL, // ammo icon + "Dynamite", // pickup + 1, + IT_AMMO, + WP_DYNAMITE, + WP_DYNAMITE, + WP_DYNAMITE, + "", // precache + "", // sounds + {1,1,1,1,1} + }, + + +/*QUAKED ammo_cell (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Tesla + +Boosts recharge on Tesla +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amfuel.md3" +*/ + { + "ammo_cell", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/amcell.md3", + 0, 0, 0, 0 }, + "icons/icona_cell", // icon + NULL, // ammo icon + "Cell", // pickup + 500, + IT_AMMO, + WP_TESLA, + WP_TESLA, + WP_TESLA, + "", // precache + "", // sounds + {150,100,75,50,50} + }, + + + +/*QUAKED ammo_fuel (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Flamethrower + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amfuel.md3" +*/ + { + "ammo_fuel", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/amfuel.md3", + 0, 0, 0, 0 }, + "icons/icona_fuel", // icon + NULL, // ammo icon + "Fuel", // pickup + 100, + IT_AMMO, + WP_FLAMETHROWER, + WP_FLAMETHROWER, + WP_FLAMETHROWER, + "", // precache + "", // sounds + {150,100,75,50,50} + }, + + +/*QUAKED ammo_speargun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Speargun + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amspear.md3" +*/ + { + "ammo_speargun", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/amspear.md3", + 0, 0, 0, 0 }, + "icons/icona_spear", // icon + NULL, // ammo icon + "Speargun Bolts", // pickup + 10, + IT_AMMO, + WP_SPEARGUN, + WP_SPEARGUN, + WP_SPEARGUN, + "", // precache + "", // sounds + {150,100,75,50,50} + }, + + +/*QUAKED ammo_speargun_co2 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +CO2 tipped speargun bolts + +used by: Speargun + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amspear.md3" +*/ + { + "ammo_speargun_co2", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/amspear.md3", + 0, 0, 0, 0 }, + "icons/icona_spear", // icon + NULL, // ammo icon + "C02 Speargun Bolts", // pickup + 10, + IT_AMMO, + WP_SPEARGUN_CO2, + WP_SPEARGUN_CO2, + WP_SPEARGUN_CO2, + "", // precache + "", // sounds + {150,100,75,50,50} + }, + + +//----(SA) removed ammo_sniper(_n) + +//----(SA) removed ammo_snooper(_n) + + +/*QUAKED ammo_panzerfaust (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: German Panzerfaust + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/ampf.md3" +*/ + { + "ammo_panzerfaust", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/ampf.md3", + 0, 0, 0, 0 }, + "icons/icona_panzerfaust", // icon + NULL, // ammo icon + "Panzerfaust Rockets", // pickup + 5, + IT_AMMO, + WP_PANZERFAUST, + WP_PANZERFAUST, + WP_PANZERFAUST, + "", // precache + "", // sounds + {10,5,4,3,3} + }, + + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Allied Rocket Launcher (bazooka) + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amrocket.md3" +*/ + { + "ammo_rockets", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/amrocket.md3", + 0, 0, 0, 0 }, + "icons/icona_rocket", // icon + NULL, // ammo icon + "Rockets", // pickup + 5, + IT_AMMO, + WP_ROCKET_LAUNCHER, + WP_ROCKET_LAUNCHER, + WP_ROCKET_LAUNCHER, + "", // precache + "", // sounds + {10,5,4,3,3} + }, + + +/*QUAKED ammo_charges (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Cross of Coronado + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/ammo/amcharges.md3" +*/ + { + "ammo_charges", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/amcharges.md3", + 0, 0, 0, 0 }, + "icons/icona_charges", // icon + NULL, // ammo icon + "Charges", // pickup + 2, + IT_AMMO, + WP_CROSS, + WP_CROSS, + WP_CROSS, + "", // precache + "", // sounds + {4,2,2,1,1} + }, + +//----(SA) hopefully it doesn't need to be a quaked thing. +// apologies if it does and I'll put it back. +/* +ammo_monster_attack1 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +used by: Monster Attack 1 (specific to each monster) +*/ + { + "ammo_monster_attack1", + "", + { "", + 0, 0, 0}, + "", // icon + NULL, // ammo icon + "MonsterAttack1", // pickup + 60, + IT_AMMO, + WP_MONSTER_ATTACK1, + WP_MONSTER_ATTACK1, + WP_MONSTER_ATTACK1, + "", + "", + {0,0,0,0,0} + }, + + + // + // HOLDABLE ITEMS + // + +//----(SA) updated a number of powerup items (11/6/00) + +/*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended + +pickup sound : "sound/pickup/holdable/get_medkit.wav" +use sound : "sound/pickup/holdable/get_medkit.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/medkit.md3" +*/ + { + "holdable_medkit", + "sound/pickup/holdable/get_medkit.wav", + { + "models/powerups/holdable/medkit.md3", + "models/powerups/holdable/medkit_sphere.md3", + 0, 0, 0 + }, + "icons/medkit", // icon + NULL, // ammo icon + "Medkit", // pickup + 1, + IT_HOLDABLE, + HI_MEDKIT, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_medkit.wav", // sounds + {0,0,0,0,0} + }, + + +/*QUAKED holdable_wine (.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin + +pickup sound : "sound/pickup/holdable/get_wine.wav" +use sound : "sound/pickup/holdable/use_wine.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/wine.md3" +*/ + { + "holdable_wine", + "sound/pickup/holdable/get_wine.wav", + { + "models/powerups/holdable/wine.md3", + 0, 0, 0, 0 + }, + "icons/wine", // icon + NULL, // ammo icon + "1921 Chateau Lafite", // pickup + 1, + IT_HOLDABLE, + HI_WINE, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_wine.wav", // sounds + {3,0,0,0,0} + }, + + +/*QUAKED holdable_skull (.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +Skull of Invulnerability +Protection from all attacks + +pickup sound : "sound/pickup/holdable/get_skull.wav" +use sound : "sound/pickup/holdable/use_skull.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/skull.md3" +*/ + { + "holdable_skull", + "sound/pickup/holdable/get_skull.wav", + { + "models/powerups/holdable/skull.md3", + 0, 0, 0 + , 0 + }, + "icons/skull", // icon + NULL, // ammo icon + "Skull of Invulnerability", // pickup + 1, + IT_HOLDABLE, + HI_SKULL, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_skull.wav", // sounds + {0,0,0,0,0} + }, + + + +/*QUAKED holdable_p_water (.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +Protection from drowning for n seconds +"time" (in seconds) How much extra underwater time is given + +pickup sound : "sound/pickup/holdable/get_water.wav" +use sound : "sound/pickup/holdable/use_water.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/water.md3" +*/ + { + "holdable_p_water", + "sound/pickup/holdable/get_water.wav", + { + "models/powerups/holdable/water.md3", + 0, 0, 0 + , 0 + }, + "icons/water", // icon + NULL, // ammo icon + "Breather", // pickup + 1, + IT_HOLDABLE, + HI_WATER, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_water.wav", // sounds + {0,0,0,0,0} + }, + + +/*QUAKED holdable_p_elec (.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +Protection from electric attacks +Absorbs "dmg" points of electric damage + +pickup sound : "sound/pickup/holdable/get_elec.wav" +use sound : "sound/pickup/holdable/use_elec.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/elec.md3" +*/ + { + "holdable_p_elec", + "sound/pickup/holdable/get_elec.wav", + { + "models/powerups/holdable/elec.md3", + 0, 0, 0 + , 0 + }, + "icons/elec", // icon + NULL, // ammo icon + "Electric Protection", // pickup + 1, + IT_HOLDABLE, + HI_ELECTRIC, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_elec.wav", // sounds + {0,0,0,0,0} + }, + + +/*QUAKED holdable_p_fire (.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +Protection from fire attacks +Absorbs "dmg" points of fire damage + +pickup sound : "sound/pickup/holdable/get_fire.wav" +use sound : "sound/pickup/holdable/use_fire.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/fire.md3" +*/ + { + "holdable_p_fire", + "sound/pickup/holdable/get_fire.wav", + { + "models/powerups/holdable/fire.md3", + 0, 0, 0 + , 0 + }, + "icons/fire", // icon + NULL, // ammo icon + "Fire Protection", // pickup + 1, + IT_HOLDABLE, + HI_FIRE, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_fire.wav", // sounds + {0,0,0,0,0} + }, + + +/*QUAKED holdable_stamina(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +Protection from fatigue +Using the "sprint" key will not fatigue the character + +pickup sound : "sound/pickup/holdable/get_stamina.wav" +use sound : "sound/pickup/holdable/use_stamina.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/stamina.md3" +*/ + { + "holdable_stamina", + "sound/pickup/holdable/get_stamina.wav", + { + "models/powerups/holdable/stamina.md3", + 0, 0, 0 + , 0 + }, + "icons/stamina", // icon + NULL, // ammo icon + "Added Stamina", // pickup + 1, + IT_HOLDABLE, + HI_STAMINA, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_stamina.wav", // sounds + {0,0,0,0,0} + }, + + + +/*QUAKED holdable_book1(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/venom_book.md3" +*/ + { + "holdable_book1", + "sound/pickup/holdable/get_book1.wav", + { + "models/powerups/holdable/venom_book.md3", + 0, 0, 0 + , 0 + }, + "icons/icon_vbook", // icon + NULL, // ammo icon + "Venom Tech Manual", // pickup + 1, + IT_HOLDABLE, + HI_BOOK1, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_book.wav", // sounds + {0,0,0,0,0} + }, + + +/*QUAKED holdable_book2(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/paranormal_book.md3" +*/ + { + "holdable_book2", + "sound/pickup/holdable/get_book2.wav", + { + "models/powerups/holdable/paranormal_book.md3", + 0, 0, 0 + , 0 + }, + "icons/icon_pbook", // icon + NULL, // ammo icon + "Project Book", // pickup + 1, + IT_HOLDABLE, + HI_BOOK2, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_book.wav", // sounds + {0,0,0,0,0} + }, + + +/*QUAKED holdable_book3(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/zemphr_book.md3" +*/ + { + "holdable_book3", + "sound/pickup/holdable/get_book3.wav", + { + "models/powerups/holdable/zemphr_book.md3", + 0, 0, 0 + , 0 + }, + "icons/icon_zbook", // icon + NULL, // ammo icon + "Dr. Zemph's Journal", // pickup + 1, + IT_HOLDABLE, + HI_BOOK3, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_book.wav", // sounds + {0,0,0,0,0} + }, + + + + +/*QUAKED holdable_11(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/11.md3" +*/ + { + "holdable_11", + "sound/pickup/holdable/get_11.wav", + { + "models/powerups/holdable/11.md3", + 0, 0, 0 + , 0 + }, + "icons/11", // icon + NULL, // ammo icon + "11", // pickup + 1, + IT_HOLDABLE, + HI_11, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_11.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED holdable_12(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/12.md3" +*/ + { + "holdable_12", + "sound/pickup/holdable/get_12.wav", + { + "models/powerups/holdable/12.md3", + 0, 0, 0 + , 0 + }, + "icons/12", // icon + NULL, // ammo icon + "12", // pickup + 1, + IT_HOLDABLE, + HI_12, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_12.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED holdable_13(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/13.md3" +*/ + { + "holdable_13", + "sound/pickup/holdable/get_13.wav", + { + "models/powerups/holdable/13.md3", + 0, 0, 0 + , 0 + }, + "icons/13", // icon + NULL, // ammo icon + "13", // pickup + 1, + IT_HOLDABLE, + HI_13, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_13.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED holdable_14(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/holdable/14.md3" +*/ + { + "holdable_14", + "sound/pickup/holdable/get_14.wav", + { + "models/powerups/holdable/14.md3", + 0, 0, 0 + , 0 + }, + "icons/14", // icon + NULL, // ammo icon + "14", // pickup + 1, + IT_HOLDABLE, + HI_14, + 0, + 0, + "", // precache + "sound/pickup/holdable/use_14.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED holdable_15(.3 .3 1) (-8 -8 -8) (8 8 8) suspended spin +/ + { + "holdable_15", + "sound/pickup/holdable/get_15.wav", + { + "models/powerups/holdable/15.md3", + 0, 0, 0 + , 0 }, + "icons/15", // icon + NULL, // ammo icon + "15", // pickup + 1, + IT_HOLDABLE, + HI_15, + 0, + "", // precache + "sound/pickup/holdable/use_15.wav", // sounds + {0,0,0,0,0} + }, + +*/ + + + + // + // POWERUP ITEMS + // + +/*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) +Only in CTF games +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/flags/r_flag.md3" +*/ + { + "team_CTF_redflag", + "", + { "models/multiplayer/treasure/treasure.md3", + 0, 0, 0, 0 }, + "icons/iconf_red", // icon + NULL, // ammo icon + "Objective", // pickup + 0, + IT_TEAM, + PW_REDFLAG, + 0, + 0, + "", // precache + "sound/multiplayer/axis/g-objective_secure.wav sound/multiplayer/allies/a-objective_taken.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in CTF games +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/flags/b_flag.md3" +*/ + { + "team_CTF_blueflag", + "", + { "models/multiplayer/treasure/treasure.md3", + 0, 0, 0, 0 }, + "icons/iconf_blu", // icon + NULL, // ammo icon + "Blue Flag", // pickup + 0, + IT_TEAM, + PW_BLUEFLAG, + 0, + 0, + "", // precache + "sound/multiplayer/allies/a-objective_secure.wav sound/multiplayer/axis/g-objective_taken.wav", // sounds + {0,0,0,0,0} + }, + + + //---- (SA) Wolf keys + +/*QUAKED key_skull1 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 1 + +pickup sound : "sound/pickup/keys/skull.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/skull.md3" +*/ + { + "key_skull1", + "sound/pickup/keys/skull.wav", + { + "models/powerups/keys/skull.md3", + 0, 0, 0 + , 0 + }, + "icons/iconk_skull", // icon + NULL, // ammo icon + "Crystal Skull", // pickup + 0, + IT_KEY, + KEY_1, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_chalice2 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 2 + +pickup sound : "sound/pickup/keys/chalice.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/chalice.md3" +*/ + { + "key_chalice2", + "sound/pickup/keys/chalice.wav", + { + "models/powerups/keys/chalice.md3", + 0, 0, 0 + , 0 + }, + "icons/iconk_chalice", // icon + NULL, // ammo icon + "Chalice", // pickup + 0, + IT_KEY, + KEY_2, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_eye3 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 3 + +pickup sound : "sound/pickup/keys/eye.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/eye.md3" +*/ + { + "key_eye3", + "sound/pickup/keys/eye.wav", + { + "models/powerups/keys/eye.md3", + 0, 0, 0 + , 0 + }, + "icons/iconk_eye", // icon + NULL, // ammo icon + "Eye of Isis", // pickup + 0, + IT_KEY, + KEY_3, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_radio4 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 4 + +pickup sound : "sound/pickup/keys/radio.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/radio_port.md3" +*/ + { + "key_radio4", + "sound/pickup/keys/radio.wav", + { + "models/powerups/keys/radio_port.md3", + 0, 0, 0 + , 0 + }, + "icons/iconk_radio", // icon + NULL, // ammo icon + "Field Radio", // pickup + 0, + IT_KEY, + KEY_4, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_satchelcharge5 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 5 + +pickup sound : "sound/pickup/keys/satchelcharge.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/satchel_charge.md3" +*/ + { + "key_satchelcharge5", + "sound/pickup/keys/satchelcharge.wav", + { + "models/powerups/keys/satchel_charge.md3", + 0, 0, 0 + , 0 + }, + "icons/iconk_satchel", // icon + NULL, // ammo icon + "Satchel Charge", // pickup + 0, + IT_KEY, + KEY_5, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_binocs (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +Binoculars. + +pickup sound : "sound/pickup/keys/binocs.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/binoculars.md3" +*/ + { + "key_binocs", + "sound/pickup/keys/binocs.wav", + { + "models/powerups/keys/binoculars.md3", + 0, 0, 0 + , 0 + }, + "icons/key6", // icon + NULL, // ammo icon + "Binoculars", // pickup + 0, + IT_KEY, + INV_BINOCS, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_goldbar (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +//key 7 +Gold bars until we have a "collectables" entity type + +pickup sound : "sound/pickup/keys/goldbar.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/goldbar.md3" +*/ + { + "key_goldbar", + "sound/pickup/keys/goldbar.wav", + { + "models/powerups/keys/goldbar.md3", + 0, 0, 0 + , 0 + }, + "icons/key7", // icon + NULL, // ammo icon + "Gold Bars", // pickup + 0, + IT_KEY, + KEY_7, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key8 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 8 + +pickup sound : "sound/pickup/keys/key8.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key8", + "sound/pickup/keys/key8.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key8", // icon + NULL, // ammo icon + "Key 8", // pickup + 0, + IT_KEY, + KEY_8, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key9 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 9 + +pickup sound : "sound/pickup/keys/key9.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key9", + "sound/pickup/keys/key9.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key9", // icon + NULL, // ammo icon + "Key 9", // pickup + 0, + IT_KEY, + KEY_9, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key10 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 10 + +pickup sound : "sound/pickup/keys/key10.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key10", + "sound/pickup/keys/key10.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key10", // icon + NULL, // ammo icon + "Key 10", // pickup + 0, + IT_KEY, + KEY_10, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key11 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 11 + +pickup sound : "sound/pickup/keys/key11.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key11", + "sound/pickup/keys/key11.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key11", // icon + NULL, // ammo icon + "Key 11", // pickup + 0, + IT_KEY, + KEY_11, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key12 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 12 + +pickup sound : "sound/pickup/keys/key12.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key12", + "sound/pickup/keys/key12.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key12", // icon + NULL, // ammo icon + "Key 12", // pickup + 0, + IT_KEY, + KEY_12, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key13 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 13 + +pickup sound : "sound/pickup/keys/key13.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key13", + "sound/pickup/keys/key13.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key13", // icon + NULL, // ammo icon + "Key 13", // pickup + 0, + IT_KEY, + KEY_13, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key14 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 14 + +pickup sound : "sound/pickup/keys/key14.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key14", + "sound/pickup/keys/key14.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key14", // icon + NULL, // ammo icon + "Key 14", // pickup + 0, + IT_KEY, + KEY_14, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key15 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 15 + +pickup sound : "sound/pickup/keys/key15.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key15", + "sound/pickup/keys/key15.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key15", // icon + NULL, // ammo icon + "Key 15", // pickup + 0, + IT_KEY, + KEY_15, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + +/*QUAKED key_key16 (1 1 0) (-8 -8 -8) (8 8 8) suspended spin +key 16 + +pickup sound : "sound/pickup/keys/key16.wav" +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/powerups/keys/key.md3" +*/ + { + "key_key16", + "sound/pickup/keys/key16.wav", + { + "models/powerups/keys/key.md3", + 0, 0, 0 + , 0 + }, + "icons/key16", // icon + NULL, // ammo icon + "Key 16", // pickup + 0, + IT_KEY, + KEY_16, + 0, + 0, + "", // precache + "models/keys/key.wav", // sounds + {0,0,0,0,0} + }, + + + + + // end of list marker + {NULL} +}; +// END JOSEPH + +int bg_numItems = sizeof( bg_itemlist ) / sizeof( bg_itemlist[0] ) - 1; + + +/* +============== +BG_FindItemForPowerup +============== +*/ +gitem_t *BG_FindItemForPowerup( powerup_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( ( bg_itemlist[i].giType == IT_POWERUP || + bg_itemlist[i].giType == IT_TEAM ) && + bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + return NULL; +} + + +/* +============== +BG_FindItemForHoldable +============== +*/ +gitem_t *BG_FindItemForHoldable( holdable_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + +// Com_Error( ERR_DROP, "HoldableItem not found" ); + + return NULL; +} + + +/* +=============== +BG_FindItemForWeapon + +=============== +*/ +gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( it->giType == IT_WEAPON && it->giTag == weapon ) { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon ); + return NULL; +} + +//----(SA) added + +#define DEATHMATCH_SHARED_AMMO 0 + + +/* +============== +BG_FindClipForWeapon +============== +*/ +weapon_t BG_FindClipForWeapon( weapon_t weapon ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( it->giType == IT_WEAPON && it->giTag == weapon ) { + return it->giClipIndex; + } + } + + return 0; +} + + + +/* +============== +BG_FindAmmoForWeapon +============== +*/ +weapon_t BG_FindAmmoForWeapon( weapon_t weapon ) { + gitem_t *it; + int DMAmmo = 0; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( it->giType == IT_WEAPON && it->giTag == weapon ) { +// if(g_gametype.integer != GT_SINGLE_PLAYER) + if ( 0 ) { + if ( DEATHMATCH_SHARED_AMMO ) { // this would be a !single_player server cvar that lets Allied and Axis like-weapons share like-ammo for dm + switch ( it->giAmmoIndex ) + { + case WP_AKIMBO: //----(SA) added + case WP_COLT: + DMAmmo = WP_LUGER; + break; + case WP_THOMPSON: + DMAmmo = WP_MP40; + break; + case WP_GARAND: + DMAmmo = WP_MAUSER; + break; + case WP_GRENADE_PINEAPPLE: + DMAmmo = WP_GRENADE_LAUNCHER; + break; + default: + break; + } + if ( DMAmmo ) { + return DMAmmo; + } + } + } + return it->giAmmoIndex; + } + } + return 0; +} + +/* +============== +BG_AkimboFireSequence +============== +*/ +qboolean BG_AkimboFireSequence( playerState_t *ps ) { + + if ( ps->weapon != WP_AKIMBO ) { + return qfalse; + } + + if ( ( ps->ammoclip[WP_AKIMBO] + ps->ammoclip[WP_COLT] ) & 1 ) { + if ( ps->ammoclip[WP_AKIMBO] > ps->ammoclip[WP_COLT] ) { + return qtrue; + } + } else { + if ( ps->ammoclip[WP_AKIMBO] <= ps->ammoclip[WP_COLT] ) { + return qtrue; + } + } + return qfalse; +} + +//----(SA) end + +//----(SA) Added keys +/* +============== +BG_FindItemForKey +============== +*/ +gitem_t *BG_FindItemForKey( wkey_t k, int *indexreturn ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_KEY && bg_itemlist[i].giTag == k ) { + { + if ( indexreturn ) { + *indexreturn = i; + } + return &bg_itemlist[i]; + } + } + } + + Com_Error( ERR_DROP, "Key %d not found", k ); + return NULL; +} +//----(SA) end + + +//----(SA) added +/* +============== +BG_FindItemForAmmo +============== +*/ +gitem_t *BG_FindItemForAmmo( int ammo ) { + int i = 0; + + for (; i < bg_numItems; i++ ) + { + if ( bg_itemlist[i].giType == IT_AMMO && bg_itemlist[i].giAmmoIndex == ammo ) { + return &bg_itemlist[i]; + } + } + Com_Error( ERR_DROP, "Item not found for ammo: %d", ammo ); + return NULL; +} +//----(SA) end + + +/* +=============== +BG_FindItem + +=============== +*/ +gitem_t *BG_FindItem( const char *pickupName ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( !Q_stricmp( it->pickup_name, pickupName ) ) { + return it; + } + } + + return NULL; +} + + +//----(SA) added +/* +============== +BG_PlayerSeesItem + Try to quickly determine if an item should be highlighted as per the current cg_drawCrosshairPickups.integer value. + pvs check should have already been done by the time we get in here, so we shouldn't have to check +============== +*/ + +//----(SA) not used +/* +qboolean BG_PlayerSeesItem(playerState_t *ps, entityState_t *item, int atTime) +{ + vec3_t vorigin, eorigin, viewa, dir; + float dot, dist, foo; + + BG_EvaluateTrajectory( &item->pos, atTime, eorigin ); + + VectorCopy(ps->origin, vorigin); + vorigin[2] += ps->viewheight; // get the view loc up to the viewheight + eorigin[2] += 16; // and subtract the item's offset (that is used to place it on the ground) + VectorSubtract(vorigin, eorigin, dir); + + dist = VectorNormalize(dir); // dir is now the direction from the item to the player + + if(dist > 255) + return qfalse; // only run the remaining stuff on items that are close enough + + // (SA) FIXME: do this without AngleVectors. + // It'd be nice if the angle vectors for the player + // have already been figured at this point and I can + // just pick them up. (if anybody is storing this somewhere, + // for the current frame please let me know so I don't + // have to do redundant calcs) + AngleVectors(ps->viewangles, viewa, 0, 0); + dot = DotProduct(viewa, dir ); + + // give more range based on distance (the hit area is wider when closer) + + foo = -0.94f - (dist/255.0f) * 0.057f; // (ranging from -0.94 to -0.997) (it happened to be a pretty good range) + +// Com_Printf("test: if(%f > %f) return qfalse (dot > foo)\n", dot, foo); + if(dot > foo) + return qfalse; + + return qtrue; +} +*/ +//----(SA) end + +// DHM - Nerve :: returns qtrue if a weapon is indeed used in multiplayer +qboolean BG_WeaponInWolfMP( int weapon ) { + + switch ( weapon ) { + case WP_KNIFE: + case WP_LUGER: + case WP_COLT: + case WP_MP40: + case WP_THOMPSON: + case WP_STEN: + case WP_MAUSER: + case WP_SNIPERRIFLE: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_PANZERFAUST: + case WP_VENOM: + case WP_FLAMETHROWER: + case WP_AMMO: + case WP_ARTY: + case WP_SMOKETRAIL: + case WP_MEDKIT: + case WP_PLIERS: + case WP_SMOKE_GRENADE: + case WP_DYNAMITE: + case WP_MEDIC_SYRINGE: + return qtrue; + default: + return qfalse; + } +} + +/* +============ +BG_PlayerTouchesItem + +Items can be picked up without actually touching their physical bounds to make +grabbing them easier +============ +*/ + +extern int trap_Cvar_VariableIntegerValue( const char *var_name ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { + vec3_t origin; + + BG_EvaluateTrajectory( &item->pos, atTime, origin ); + + // we are ignoring ducked differences here + if ( ps->origin[0] - origin[0] > 36 + || ps->origin[0] - origin[0] < -36 + || ps->origin[1] - origin[1] > 36 + || ps->origin[1] - origin[1] < -36 + || ps->origin[2] - origin[2] > 36 + || ps->origin[2] - origin[2] < -36 ) { + return qfalse; + } + + return qtrue; +} + + + +#define AMMOFORWEAP BG_FindAmmoForWeapon( item->giTag ) +/* +================ +BG_CanItemBeGrabbed + +Returns false if the item should not be picked up. +This needs to be the same for client side prediction and server use. +================ +*/ +qboolean BG_CanItemBeGrabbed( const entityState_t *ent, const playerState_t *ps ) { + gitem_t *item; + int ammoweap,weapbank; // JPW NERVE + + + if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); + } + + item = &bg_itemlist[ent->modelindex]; + + switch ( item->giType ) { + case IT_WEAPON: +// JPW NERVE -- medics & engineers can only pick up same weapon type + if ( item->giTag == WP_AMMO ) { // magic ammo for any two-handed weapon + return qtrue; + } + if ( ( ps->stats[STAT_PLAYER_CLASS] == PC_MEDIC ) || ( ps->stats[STAT_PLAYER_CLASS] == PC_ENGINEER ) ) { + if ( !COM_BitCheck( ps->weapons, item->giTag ) ) { + return qfalse; + } else { + return qtrue; + } + } + + if ( ps->stats[STAT_PLAYER_CLASS] == PC_LT ) { + if ( ( item->giTag != WP_MP40 ) && ( item->giTag != WP_THOMPSON ) && ( item->giTag != WP_STEN ) ) { + return qfalse; + } + } + +// JPW NERVE wolf multiplayer: other classes can only pick up weapon if weapon's bank is empty +#ifdef GAMEDLL + if ( g_gametype.integer >= GT_WOLF ) +#endif +#ifdef CGAMEDLL + if ( cg_gameType.integer >= GT_WOLF ) +#endif + { + weapbank = 0; + for ( ammoweap = 0; ammoweap < MAX_WEAPS_IN_BANK_MP; ammoweap++ ) + if ( item->giTag == weapBanksMultiPlayer[3][ammoweap] ) { + weapbank = 1; + } + if ( !weapbank ) { + return qfalse; + } + for ( ammoweap = 0; ammoweap < MAX_WEAPS_IN_BANK_MP; ammoweap++ ) + if ( COM_BitCheck( ps->weapons,weapBanksMultiPlayer[3][ammoweap] ) ) { + return qfalse; + } + } + return qtrue; +// jpw + case IT_AMMO: + ammoweap = BG_FindAmmoForWeapon( item->giTag ); + + if ( ps->ammo[ammoweap] >= ammoTable[ammoweap].maxammo ) { + return qfalse; + } + + return qtrue; + + case IT_ARMOR: + // we also clamp armor to the maxhealth for handicapping + if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + + case IT_HEALTH: + if ( ent->density == ( 1 << 9 ) ) { // density tracks how many uses left + return qfalse; + } + + // small and mega healths will go over the max, otherwise + // don't pick up if already at max + if ( item->quantity == 5 || item->quantity == 100 ) { // (SA) this is /totally/ a Q3 check. TODO: adapt for Wolf + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + } + + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + return qfalse; + } + return qtrue; + + case IT_POWERUP: + if ( ent->density == ( 1 << 9 ) ) { // density tracks how many uses left + return qfalse; + } + return qtrue; + + case IT_TEAM: // team items, such as flags + + // DHM - Nerve :: otherEntity2 is now used instead of modelindex2 + // ent->modelindex2 is non-zero on items if they are dropped + // we need to know this because we can pick up our dropped flag (and return it) + // but we can't pick up our flag at base + if ( ps->persistant[PERS_TEAM] == TEAM_RED ) { + if ( item->giTag == PW_BLUEFLAG || + ( item->giTag == PW_REDFLAG && ent->otherEntityNum2 /*ent->modelindex2*/ ) || + ( item->giTag == PW_REDFLAG && ps->powerups[PW_BLUEFLAG] ) ) { + return qtrue; + } + } else if ( ps->persistant[PERS_TEAM] == TEAM_BLUE ) { + if ( item->giTag == PW_REDFLAG || + ( item->giTag == PW_BLUEFLAG && ent->otherEntityNum2 /*ent->modelindex2*/ ) || + ( item->giTag == PW_BLUEFLAG && ps->powerups[PW_REDFLAG] ) ) { + return qtrue; + } + } + return qfalse; + + + case IT_HOLDABLE: + return qtrue; + + case IT_TREASURE: // treasure always picked up + return qtrue; + + case IT_CLIPBOARD: // clipboards always picked up + return qtrue; + + //---- (SA) Wolf keys + case IT_KEY: + return qtrue; // keys are always picked up + + case IT_BAD: + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); + + } + return qfalse; +} + +//====================================================================== + +/* +================ +BG_EvaluateTrajectory + +================ +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + vec3_t v; + + switch ( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + case TR_GRAVITY_PAUSED: //----(SA) + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; +//----(SA) removed + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if ( deltaTime < 0 ) { + deltaTime = 0; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + // Ridah + case TR_GRAVITY_LOW: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * ( DEFAULT_GRAVITY * 0.3 ) * deltaTime * deltaTime; // FIXME: local gravity... + break; + // done. +//----(SA) + case TR_GRAVITY_FLOAT: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * ( DEFAULT_GRAVITY * 0.2 ) * deltaTime; + break; +//----(SA) end + // RF, acceleration + case TR_ACCELERATE: // trDelta is the ultimate speed + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + // phase is the acceleration constant + phase = VectorLength( tr->trDelta ) / ( tr->trDuration * 0.001 ); + // trDelta at least gives us the acceleration direction + VectorNormalize2( tr->trDelta, result ); + // get distance travelled at current time + VectorMA( tr->trBase, phase * 0.5 * deltaTime * deltaTime, result, result ); + break; + case TR_DECCELERATE: // trDelta is the starting speed + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + // phase is the breaking constant + phase = VectorLength( tr->trDelta ) / ( tr->trDuration * 0.001 ); + // trDelta at least gives us the acceleration direction + VectorNormalize2( tr->trDelta, result ); + // get distance travelled at current time (without breaking) + VectorMA( tr->trBase, deltaTime, tr->trDelta, v ); + // subtract breaking force + VectorMA( v, -phase * 0.5 * deltaTime * deltaTime, result, result ); + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +================ +BG_EvaluateTrajectoryDelta + +For determining velocity at a given time +================ +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch ( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; +//----(SA) removed + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + // Ridah + case TR_GRAVITY_LOW: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= ( DEFAULT_GRAVITY * 0.3 ) * deltaTime; // FIXME: local gravity... + break; + // done. +//----(SA) + case TR_GRAVITY_FLOAT: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= ( DEFAULT_GRAVITY * 0.2 ) * deltaTime; + break; +//----(SA) end + // RF, acceleration + case TR_ACCELERATE: // trDelta is eventual speed + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + phase = deltaTime / (float)tr->trDuration; + VectorScale( tr->trDelta, deltaTime * deltaTime, result ); + break; + case TR_DECCELERATE: // trDelta is breaking force + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorScale( tr->trDelta, deltaTime, result ); + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +============ +BG_GetMarkDir + + used to find a good directional vector for a mark projection, which will be more likely + to wrap around adjacent surfaces + + dir is the direction of the projectile or trace that has resulted in a surface being hit +============ +*/ +void BG_GetMarkDir( const vec3_t dir, const vec3_t normal, vec3_t out ) { + vec3_t ndir, lnormal; + float minDot = 0.3; + + if ( VectorLength( normal ) < 1.0 ) { + VectorSet( lnormal, 0, 0, 1 ); + } else { + VectorCopy( normal, lnormal ); + } + + VectorNegate( dir, ndir ); + VectorNormalize( ndir ); + if ( normal[2] > 0.8 ) { + minDot = 0.7; + } + // make sure it makrs the impact surface + while ( DotProduct( ndir, lnormal ) < minDot ) { + VectorMA( ndir, 0.5, lnormal, ndir ); + VectorNormalize( ndir ); + } + + VectorCopy( ndir, out ); +} + + +char *eventnames[] = { + "EV_NONE", + "EV_FOOTSTEP", + "EV_FOOTSTEP_METAL", + "EV_FOOTSTEP_WOOD", + "EV_FOOTSTEP_GRASS", + "EV_FOOTSTEP_GRAVEL", + "EV_FOOTSTEP_ROOF", + "EV_FOOTSTEP_SNOW", + "EV_FOOTSTEP_CARPET", + "EV_FOOTSPLASH", + "EV_FOOTWADE", + "EV_SWIM", + "EV_STEP_4", + "EV_STEP_8", + "EV_STEP_12", + "EV_STEP_16", + "EV_FALL_SHORT", + "EV_FALL_MEDIUM", + "EV_FALL_FAR", + "EV_FALL_NDIE", + "EV_FALL_DMG_10", + "EV_FALL_DMG_15", + "EV_FALL_DMG_25", + "EV_FALL_DMG_50", + "EV_JUMP_PAD", // boing sound at origin, jump sound on player + "EV_JUMP", + "EV_WATER_TOUCH", // foot touches + "EV_WATER_LEAVE", // foot leaves + "EV_WATER_UNDER", // head touches + "EV_WATER_CLEAR", // head leaves + "EV_ITEM_PICKUP", // normal item pickups are predictable + "EV_ITEM_PICKUP_QUIET", // (SA) same, but don't play the default pickup sound as it was specified in the ent + "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone + "EV_NOITEM", + "EV_NOAMMO", + "EV_EMPTYCLIP", + "EV_FILL_CLIP", + "EV_MG42_FIXED", // JPW NERVE + "EV_WEAP_OVERHEAT", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + "EV_FIRE_WEAPONB", + "EV_FIRE_WEAPON_LASTSHOT", + "EV_FIRE_QUICKGREN", // "Quickgrenade" + "EV_NOFIRE_UNDERWATER", + "EV_FIRE_WEAPON_MG42", + "EV_USE_ITEM0", + "EV_USE_ITEM1", + "EV_USE_ITEM2", + "EV_USE_ITEM3", + "EV_USE_ITEM4", + "EV_USE_ITEM5", + "EV_USE_ITEM6", + "EV_USE_ITEM7", + "EV_USE_ITEM8", + "EV_USE_ITEM9", + "EV_USE_ITEM10", + "EV_USE_ITEM11", + "EV_USE_ITEM12", + "EV_USE_ITEM13", + "EV_USE_ITEM14", + "EV_USE_ITEM15", + "EV_ITEM_RESPAWN", + "EV_ITEM_POP", + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + "EV_BULLET_HIT_FLESH", + "EV_BULLET_HIT_WALL", + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_RAILTRAIL", + "EV_VENOM", + "EV_VENOMFULL", + "EV_BULLET", // otherEntity is the shooter + "EV_LOSE_HAT", + "EV_GIB_HEAD", // only blow off the head + "EV_PAIN", + "EV_CROUCH_PAIN", + "EV_DEATH1", + "EV_DEATH2", + "EV_DEATH3", + "EV_OBITUARY", + "EV_STOPSTREAMINGSOUND", // JPW NERVE swiped from Sherman + "EV_POWERUP_QUAD", + "EV_POWERUP_BATTLESUIT", + "EV_POWERUP_REGEN", + "EV_GIB_PLAYER", // gib a previously living player + "EV_DEBUG_LINE", + "EV_STOPLOOPINGSOUND", + "EV_TAUNT", + "EV_SMOKE", + "EV_SPARKS", + "EV_SPARKS_ELECTRIC", + "EV_BATS", + "EV_BATS_UPDATEPOSITION", + "EV_BATS_DEATH", + "EV_EXPLODE", // func_explosive + "EV_EFFECT", // target_effect + "EV_MORTAREFX", // mortar firing + "EV_SPINUP", // JPW NERVE panzerfaust preamble for MP balance + "EV_SNOW_ON", + "EV_SNOW_OFF", + "EV_MISSILE_MISS_SMALL", + "EV_MISSILE_MISS_LARGE", + "EV_WOLFKICK_HIT_FLESH", + "EV_WOLFKICK_HIT_WALL", + "EV_WOLFKICK_MISS", + "EV_SPIT_HIT", + "EV_SPIT_MISS", + "EV_SHARD", + "EV_JUNK", + "EV_EMITTER", //----(SA) added + "EV_OILPARTICLES", + "EV_OILSLICK", + "EV_OILSLICKREMOVE", + "EV_MG42EFX", + "EV_FLAMEBARREL_BOUNCE", + "EV_FLAKGUN1", + "EV_FLAKGUN2", + "EV_FLAKGUN3", + "EV_FLAKGUN4", + "EV_EXERT1", + "EV_EXERT2", + "EV_EXERT3", + "EV_SNOWFLURRY", + "EV_CONCUSSIVE", + "EV_DUST", + "EV_RUMBLE_EFX", + "EV_GUNSPARKS", + "EV_FLAMETHROWER_EFFECT", + "EV_SNIPER_SOUND", + "EV_POPUP", + "EV_POPUPBOOK", + "EV_GIVEPAGE", + "EV_MG42BULLET_HIT_FLESH", + "EV_MG42BULLET_HIT_WALL", + + "EV_MAX_EVENTS" +}; + +/* +=============== +BG_AddPredictableEventToPlayerstate + +Handles the sequence numbers +=============== +*/ + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { + +#ifndef NDEBUG + { + char buf[256]; + trap_Cvar_VariableStringBuffer( "showevents", buf, sizeof( buf ) ); + if ( atof( buf ) != 0 ) { +#ifdef GAMEDLL + Com_Printf( " game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount /*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm ); +#else + Com_Printf( "Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount /*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm ); +#endif + } + } +#endif + ps->events[ps->eventSequence & ( MAX_EVENTS - 1 )] = newEvent; + ps->eventParms[ps->eventSequence & ( MAX_EVENTS - 1 )] = eventParm; + ps->eventSequence++; +} + + +/* +======================== +BG_PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_flags & PMF_LIMBO ) { // JPW NERVE limbo + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + if ( ps->movementDir > 128 ) { + s->angles2[YAW] = (float)ps->movementDir - 256; + } else { + s->angles2[YAW] = ps->movementDir; + } + + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + // Ridah, let clients know if this person is using a mounted weapon + // so they don't show any client muzzle flashes + + // (SA) moved up since it needs to set the ps->eFlags too. + // Seems like this could be the problem Raf was + // encountering with the EF_DEAD flag below when guys + // dead flags weren't sticking + + if ( ps->persistant[PERS_HWEAPON_USE] ) { + ps->eFlags |= EF_MG42_ACTIVE; + } else { + ps->eFlags &= ~EF_MG42_ACTIVE; + } + + s->eFlags = ps->eFlags; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + +// from MP + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_EVENTS ) { + ps->entityEventSequence = ps->eventSequence - MAX_EVENTS; + } + seq = ps->entityEventSequence & ( MAX_EVENTS - 1 ); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } +// end + // Ridah, now using a circular list of events for all entities + // add any new events that have been added to the playerState_t + // (possibly overwriting entityState_t events) + for ( i = ps->oldEventSequence; i != ps->eventSequence; i++ ) { + s->events[s->eventSequence & ( MAX_EVENTS - 1 )] = ps->events[i & ( MAX_EVENTS - 1 )]; + s->eventParms[s->eventSequence & ( MAX_EVENTS - 1 )] = ps->eventParms[i & ( MAX_EVENTS - 1 )]; + s->eventSequence++; + } + ps->oldEventSequence = ps->eventSequence; + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + s->aiChar = ps->aiChar; // Ridah +// s->loopSound = ps->loopSound; + s->teamNum = ps->teamNum; + s->aiState = ps->aiState; +} + +/* +======================== +BG_PlayerStateToEntityStateExtraPolate + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_flags & PMF_LIMBO ) { // JPW NERVE limbo + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_LINEAR_STOP; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction and linear prediction + VectorCopy( ps->velocity, s->pos.trDelta ); + // set the time for linear prediction + s->pos.trTime = time; + // set maximum extra polation time + s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + + if ( ps->persistant[PERS_HWEAPON_USE] ) { + ps->eFlags |= EF_MG42_ACTIVE; + } else { + ps->eFlags &= ~EF_MG42_ACTIVE; + } + + + s->eFlags = ps->eFlags; + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_EVENTS ) { + ps->entityEventSequence = ps->eventSequence - MAX_EVENTS; + } + seq = ps->entityEventSequence & ( MAX_EVENTS - 1 ); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + // Ridah, now using a circular list of events for all entities + // add any new events that have been added to the playerState_t + // (possibly overwriting entityState_t events) + for ( i = ps->oldEventSequence; i != ps->eventSequence; i++ ) { + s->events[s->eventSequence & ( MAX_EVENTS - 1 )] = ps->events[i & ( MAX_EVENTS - 1 )]; + s->eventParms[s->eventSequence & ( MAX_EVENTS - 1 )] = ps->eventParms[i & ( MAX_EVENTS - 1 )]; + s->eventSequence++; + } + ps->oldEventSequence = ps->eventSequence; + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + +// s->loopSound = ps->loopSound; +// s->generic1 = ps->generic1; + s->aiChar = ps->aiChar; // Ridah + s->teamNum = ps->teamNum; + s->aiState = ps->aiState; +} diff --git a/src/game/bg_pmove.c b/src/game/bg_pmove.c new file mode 100644 index 0000000..c50aec6 --- /dev/null +++ b/src/game/bg_pmove.c @@ -0,0 +1,4089 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +// bg_pmove.c -- both games player movement code +// takes a playerstate and a usercmd as input and returns a modifed playerstate + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +// Rafael gameskill +int bg_pmove_gameskill_integer; +// done + +// JPW NERVE -- stuck this here so it can be seen client & server side +float Com_GetFlamethrowerRange( void ) { + if ( pm->gametype != GT_SINGLE_PLAYER ) { + return 2500; // multiplayer range is longer for balance + } else { + return 850; // single player range remains unchanged + } +} +// jpw + +pmove_t *pm; +pml_t pml; + +// movement parameters +float pm_stopspeed = 100; +//float pm_duckScale = 0.25; + +//----(SA) modified +float pm_waterSwimScale = 0.5; +float pm_waterWadeScale = 0.70; +float pm_slagSwimScale = 0.30; +float pm_slagWadeScale = 0.70; + +float pm_accelerate = 10; +float pm_airaccelerate = 1; +float pm_wateraccelerate = 4; +float pm_slagaccelerate = 2; +float pm_flyaccelerate = 8; + +float pm_friction = 6; +float pm_waterfriction = 1; +float pm_slagfriction = 1; +float pm_flightfriction = 3; +float pm_ladderfriction = 14; +float pm_spectatorfriction = 5.0f; + +//----(SA) end + +int c_pmove = 0; + +/* +=============== +PM_AddEvent + +=============== +*/ +void PM_AddEvent( int newEvent ) { + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt( int entityNum ) { + int i; + + if ( entityNum == ENTITYNUM_WORLD ) { + return; + } + if ( pm->numtouch == MAXTOUCH ) { + return; + } + + // see if it is already added + for ( i = 0 ; i < pm->numtouch ; i++ ) { + if ( pm->touchents[ i ] == entityNum ) { + return; + } + } + + // add it + pm->touchents[pm->numtouch] = entityNum; + pm->numtouch++; +} + +/* +============== +PM_StartWeaponAnim +============== +*/ +static void PM_StartWeaponAnim( int anim ) { + if ( pm->ps->pm_type >= PM_DEAD ) { + return; + } + + if ( pm->ps->weapAnimTimer > 0 ) { + return; + } + + if ( pm->cmd.weapon == WP_NONE ) { + return; + } + + pm->ps->weapAnim = ( ( pm->ps->weapAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; +} + +static void PM_ContinueWeaponAnim( int anim ) { + if ( pm->cmd.weapon == WP_NONE ) { + return; + } + + if ( ( pm->ps->weapAnim & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + if ( pm->ps->weapAnimTimer > 0 ) { + return; // a high priority animation is running + } + PM_StartWeaponAnim( anim ); +} + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { + float backoff; + float change; + int i; + + backoff = DotProduct( in, normal ); + + if ( backoff < 0 ) { + backoff *= overbounce; + } else { + backoff /= overbounce; + } + + for ( i = 0 ; i < 3 ; i++ ) { + change = normal[i] * backoff; + out[i] = in[i] - change; + } +} + +/* +======================== +PM_ExertSound + +plays random exertion sound when sprint key is press +======================== +*/ +static void PM_ExertSound( void ) { + int rval; + static int oldexerttime = 0; + static int oldexertcnt = 0; + + if ( pm->cmd.serverTime > oldexerttime + 500 ) { + oldexerttime = pm->cmd.serverTime; + } else { + return; + } + + rval = rand() % 3; + + if ( oldexertcnt != rval ) { + oldexertcnt = rval; + } else { + oldexertcnt++; + } + + if ( oldexertcnt > 2 ) { + oldexertcnt = 0; + } + + if ( oldexertcnt == 1 ) { + PM_AddEvent( EV_EXERT2 ); + } else if ( oldexertcnt == 2 ) { + PM_AddEvent( EV_EXERT3 ); + } else { + PM_AddEvent( EV_EXERT1 ); + } +} + + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +static void PM_Friction( void ) { + vec3_t vec; + float *vel; + float speed, newspeed, control; + float drop; + + vel = pm->ps->velocity; + + VectorCopy( vel, vec ); + if ( pml.walking ) { + vec[2] = 0; // ignore slope movement + } + + speed = VectorLength( vec ); + if ( speed < 1 ) { + vel[0] = 0; + vel[1] = 0; // allow sinking underwater + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + // apply ground friction + if ( pm->waterlevel <= 1 ) { + if ( pml.walking && !( pml.groundTrace.surfaceFlags & SURF_SLICK ) ) { + // if getting knocked back, no friction + if ( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control * pm_friction * pml.frametime; + } + } + } + + // apply water friction even if just wading + if ( pm->waterlevel ) { + if ( pm->watertype == CONTENTS_SLIME ) { //----(SA) slag + drop += speed * pm_slagfriction * pm->waterlevel * pml.frametime; + } else { + drop += speed * pm_waterfriction * pm->waterlevel * pml.frametime; + } + } + + // apply flying friction + if ( pm->ps->powerups[PW_FLIGHT] ) { + drop += speed * pm_flightfriction * pml.frametime; + } + + if ( pm->ps->pm_type == PM_SPECTATOR ) { + drop += speed * pm_spectatorfriction * pml.frametime; + } + + // apply ladder strafe friction + if ( pml.ladder ) { + drop += speed * pm_ladderfriction * pml.frametime; + } + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0 ) { + newspeed = 0; + } + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { +#if 1 + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct( pm->ps->velocity, wishdir ); + addspeed = wishspeed - currentspeed; + if ( addspeed <= 0 ) { + return; + } + accelspeed = accel * pml.frametime * wishspeed; + if ( accelspeed > addspeed ) { + accelspeed = addspeed; + } + + // Ridah, variable friction for AI's + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) { + accelspeed *= ( 1.0 / pm->ps->friction ); + } + if ( accelspeed > addspeed ) { + accelspeed = addspeed; + } + + for ( i = 0 ; i < 3 ; i++ ) { + pm->ps->velocity[i] += accelspeed * wishdir[i]; + } +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + vec3_t wishVelocity; + vec3_t pushDir; + float pushLen; + float canPush; + + VectorScale( wishdir, wishspeed, wishVelocity ); + VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); + pushLen = VectorNormalize( pushDir ); + + canPush = accel * pml.frametime * wishspeed; + if ( canPush > pushLen ) { + canPush = pushLen; + } + + VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); +#endif +} + + + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +static float PM_CmdScale( usercmd_t *cmd ) { + int max; + float total; + float scale; + + if ( pm->ps->aiChar ) { + // restrict AI character movements (don't strafe or run backwards as fast as they can run forwards) + if ( cmd->forwardmove < -64.0 ) { + cmd->forwardmove = -64.0; + } + if ( cmd->rightmove > 64.0 ) { + cmd->rightmove = 64.0; + } else if ( cmd->rightmove < -64.0 ) { + cmd->rightmove = -64.0; + } + } + + max = abs( cmd->forwardmove ); + if ( abs( cmd->rightmove ) > max ) { + max = abs( cmd->rightmove ); + } + if ( abs( cmd->upmove ) > max ) { + max = abs( cmd->upmove ); + } + if ( !max ) { + return 0; + } + + total = sqrt( cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); + scale = (float)pm->ps->speed * max / ( 127.0 * total ); + + if ( pm->cmd.buttons & BUTTON_SPRINT && pm->ps->sprintTime > 50 ) { + scale *= pm->ps->sprintSpeedScale; + } else { + scale *= pm->ps->runSpeedScale; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) { + scale *= 3; + } + +// JPW NERVE -- half move speed if heavy weapon is carried +// this is the counterstrike way of doing it -- ie you can switch to a non-heavy weapon and move at +// full speed. not completely realistic (well, sure, you can run faster with the weapon strapped to your +// back than in carry position) but more fun to play. If it doesn't play well this way we'll bog down the +// player if the own the weapon at all. +// +// added #ifdef for game/cgame to project so we can get correct g_gametype variable and only do this in +// multiplayer if necessary + if ( pm->gametype != GT_SINGLE_PLAYER ) { + if ( ( pm->ps->weapon == WP_VENOM ) || ( pm->ps->weapon == WP_VENOM_FULL ) || ( pm->ps->weapon == WP_PANZERFAUST ) ) { + scale *= 0.5; + } + if ( pm->ps->weapon == WP_FLAMETHROWER ) { // trying some different balance for the FT + scale *= 0.7; + } + } +// jpw + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) { +// Ridah, changed this for more realistic angles (at the cost of more network traffic?) +#if 1 + float speed; + vec3_t moved; + int moveyaw; + + VectorSubtract( pm->ps->origin, pml.previous_origin, moved ); + + if ( ( pm->cmd.forwardmove || pm->cmd.rightmove ) + && ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + && ( speed = VectorLength( moved ) ) + && ( speed > pml.frametime * 5 ) ) { // if moving slower than 20 units per second, just face head angles + vec3_t dir; + + VectorNormalize2( moved, dir ); + vectoangles( dir, dir ); + + moveyaw = (int)AngleDelta( dir[YAW], pm->ps->viewangles[YAW] ); + + if ( pm->cmd.forwardmove < 0 ) { + moveyaw = (int)AngleNormalize180( moveyaw + 180 ); + } + + if ( abs( moveyaw ) > 75 ) { + if ( moveyaw > 0 ) { + moveyaw = 75; + } else + { + moveyaw = -75; + } + } + + pm->ps->movementDir = (signed char)moveyaw; + } else + { + pm->ps->movementDir = 0; + } +#else + if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { + if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 0; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 1; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 2; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 3; + } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 4; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 5; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 6; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 7; + } + } else { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if ( pm->ps->movementDir == 2 ) { + pm->ps->movementDir = 1; + } else if ( pm->ps->movementDir == 6 ) { + pm->ps->movementDir = 7; + } + } +#endif +} + + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) { + // JPW NERVE -- jumping in multiplayer uses and requires sprint juice (to prevent turbo skating, sprint + jumps) + if ( pm->gametype != GT_SINGLE_PLAYER ) { + // don't allow jump accel + if ( pm->cmd.serverTime - pm->ps->jumpTime < 850 ) { + return qfalse; + } + + // don't allow if player tired +// if (pm->ps->sprintTime < 2500) // JPW pulled this per id request; made airborne jumpers wildly inaccurate with gunfire to compensate +// return qfalse; + } + // jpw + + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return qfalse; // don't allow jump until all buttons are up + } + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + return qfalse; + } + + // must wait for jump to be released + if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->velocity[2] = JUMP_VELOCITY; + PM_AddEvent( EV_JUMP ); + + if ( pm->cmd.forwardmove >= 0 ) { + BG_AnimScriptEvent( pm->ps, ANIM_ET_JUMP, qfalse, qtrue ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + BG_AnimScriptEvent( pm->ps, ANIM_ET_JUMPBK, qfalse, qtrue ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) { + vec3_t spot; + int cont; + vec3_t flatforward; + + if ( pm->ps->pm_time ) { + return qfalse; + } + + // check for water jump + if ( pm->waterlevel != 2 ) { + return qfalse; + } + + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize( flatforward ); + + VectorMA( pm->ps->origin, 30, flatforward, spot ); + spot[2] += 4; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + if ( !( cont & CONTENTS_SOLID ) ) { + return qfalse; + } + + spot[2] += 16; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + if ( cont ) { + return qfalse; + } + + // jump out of water + VectorScale( pml.forward, 200, pm->ps->velocity ); + pm->ps->velocity[2] = 350; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) { + // waterjump has no control, but falls + + PM_StepSlideMove( qtrue ); + + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + if ( pm->ps->velocity[2] < 0 ) { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +static void PM_WaterMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if ( PM_CheckWaterJump() ) { + PM_WaterJumpMove(); + return; + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if ( pm->ps->velocity[2] > -300 ) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if ( pm->watertype == CONTENTS_SLIME ) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction(); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = -60; // sink towards bottom +// wishvel[2] = -10; //----(SA) mod for DM + } else { + for ( i = 0 ; i < 3 ; i++ ) + wishvel[i] = scale * pml.forward[i] * pm->cmd.forwardmove + scale * pml.right[i] * pm->cmd.rightmove; + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + if ( pm->watertype == CONTENTS_SLIME ) { //----(SA) slag + if ( wishspeed > pm->ps->speed * pm_slagSwimScale ) { + wishspeed = pm->ps->speed * pm_slagSwimScale; + } + + PM_Accelerate( wishdir, wishspeed, pm_slagaccelerate ); + } else { + if ( wishspeed > pm->ps->speed * pm_waterSwimScale ) { + wishspeed = pm->ps->speed * pm_waterSwimScale; + } + + PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate ); + } + + + // make sure we can go up slopes easily under water + if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { + vel = VectorLength( pm->ps->velocity ); + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + } + + PM_SlideMove( qfalse ); +} + +// TTimo gcc: defined but not used +#if 0 +/* +=================== +PM_InvulnerabilityMove + +Only with the invulnerability powerup +=================== +*/ +static void PM_InvulnerabilityMove( void ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + VectorClear( pm->ps->velocity ); +} +#endif + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +static void PM_FlyMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + // normal slowdown + PM_Friction(); + + if ( pm->ps->aiChar == AICHAR_NONE || pml.ladder ) { + scale = PM_CmdScale( &pm->cmd ); + } else { + // AI is allowed to fly freely + scale = 1.0; + } + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = 0; + } else { + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = scale * pml.forward[i] * pm->cmd.forwardmove + scale * pml.right[i] * pm->cmd.rightmove; + } + + if ( pm->ps->aiChar == AICHAR_FEMZOMBIE ) { // femzombie has upmove relative to angles + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] += scale * pml.up[i] * pm->cmd.upmove; + } + } else { + wishvel[2] += scale * pm->cmd.upmove; + } + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); + + PM_StepSlideMove( qfalse ); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + +// Ridah, moved this down, so we use the actual movement direction + // set the movementDir so clients can rotate the legs for strafing +// PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + for ( i = 0 ; i < 2 ; i++ ) { + wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; + } + wishvel[2] = 0; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // not on ground, so little effect on velocity + PM_Accelerate( wishdir, wishspeed, pm_airaccelerate ); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( pml.groundPlane ) { + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + + PM_StepSlideMove( qtrue ); + +// Ridah, moved this down, so we use the actual movement direction + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); +} + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { + // begin swimming + PM_WaterMove(); + return; + } + + + if ( PM_CheckJump() ) { + // jumped away + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + + // JPW NERVE + if ( pm->gametype != GT_SINGLE_PLAYER ) { + pm->ps->jumpTime = pm->cmd.serverTime; + pm->ps->sprintTime -= 2500; + if ( pm->ps->sprintTime < 0 ) { + pm->ps->sprintTime = 0; + } + } + // jpw + + return; + } + + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + +// Ridah, moved this down, so we use the actual movement direction + // set the movementDir so clients can rotate the legs for strafing +// PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + + // project the forward and right directions onto the ground plane + PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; + } + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // clamp the speed lower if ducking + if ( pm->ps->pm_flags & PMF_DUCKED ) { + /* + if ( wishspeed > pm->ps->speed * pm_duckScale ) { + wishspeed = pm->ps->speed * pm_duckScale; + } + */ + if ( wishspeed > pm->ps->speed * pm->ps->crouchSpeedScale ) { + wishspeed = pm->ps->speed * pm->ps->crouchSpeedScale; + } + } + + // clamp the speed lower if wading or walking on the bottom + if ( pm->waterlevel ) { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + if ( pm->watertype == CONTENTS_SLIME ) { //----(SA) slag + waterScale = 1.0 - ( 1.0 - pm_slagSwimScale ) * waterScale; + } else { + waterScale = 1.0 - ( 1.0 - pm_waterSwimScale ) * waterScale; + } + + if ( wishspeed > pm->ps->speed * waterScale ) { + wishspeed = pm->ps->speed * waterScale; + } + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { + accelerate = pm_airaccelerate; + } else if ( ( pm->ps->stats[STAT_HEALTH] <= 0 ) && pm->ps->aiChar && ( pml.groundTrace.surfaceFlags & SURF_MONSTERSLICK ) ) { + accelerate = pm_airaccelerate; + } else { + accelerate = pm_accelerate; + } + + PM_Accelerate( wishdir, wishspeed, accelerate ); + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); + + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + } else if ( ( pm->ps->stats[STAT_HEALTH] <= 0 ) && pm->ps->aiChar && ( pml.groundTrace.surfaceFlags & SURF_MONSTERSLICK ) ) { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + } else { + // don't reset the z velocity for slopes + //pm->ps->velocity[2] = 0; + } + +//----(SA) added + // show breath when standing on 'snow' surfaces + if ( pml.groundTrace.surfaceFlags & SURF_SNOW ) { + pm->ps->eFlags |= EF_BREATH; + } else { + pm->ps->eFlags &= ~EF_BREATH; + } +//----(SA) end + + vel = VectorLength( pm->ps->velocity ); + + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't do anything if standing still + if ( !pm->ps->velocity[0] && !pm->ps->velocity[1] ) { + return; + } + + // don't decrease velocity when going up or down a slope + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + + PM_StepSlideMove( qfalse ); + +// Ridah, moved this down, so we use the actual movement direction + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); +} + + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) { + float forward; + + if ( !pml.walking ) { + return; + } + + // extra friction + + forward = VectorLength( pm->ps->velocity ); + forward -= 20; + if ( forward <= 0 ) { + VectorClear( pm->ps->velocity ); + } else { + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, forward, pm->ps->velocity ); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) { + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + speed = VectorLength( pm->ps->velocity ); + if ( speed < 1 ) { + VectorCopy( vec3_origin, pm->ps->velocity ); + } else + { + drop = 0; + + friction = pm_friction * 1.5; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control * friction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0 ) { + newspeed = 0; + } + newspeed /= speed; + + VectorScale( pm->ps->velocity, newspeed, pm->ps->velocity ); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for ( i = 0 ; i < 3 ; i++ ) + wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; + wishvel[2] += pm->cmd.upmove; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA( pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin ); +} + +//============================================================================ + +/* +================ +PM_FootstepForSurface + +Returns an event number apropriate for the groundsurface +================ +*/ +static int PM_FootstepForSurface( void ) { + if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { + return 0; + } + // JOSEPH 9-16-99 + if ( pml.groundTrace.surfaceFlags & SURF_METAL ) { + return EV_FOOTSTEP_METAL; + } + + if ( pml.groundTrace.surfaceFlags & SURF_WOOD ) { + return EV_FOOTSTEP_WOOD; + } + + if ( pml.groundTrace.surfaceFlags & SURF_GRASS ) { + return EV_FOOTSTEP_GRASS; + } + + if ( pml.groundTrace.surfaceFlags & SURF_GRAVEL ) { + return EV_FOOTSTEP_GRAVEL; + } + // END JOSEPH + + if ( pml.groundTrace.surfaceFlags & SURF_ROOF ) { + return EV_FOOTSTEP_ROOF; + } + + if ( pml.groundTrace.surfaceFlags & SURF_SNOW ) { + return EV_FOOTSTEP_SNOW; + } + +//----(SA) added + if ( pml.groundTrace.surfaceFlags & SURF_CARPET ) { + return EV_FOOTSTEP_CARPET; + } +//----(SA) end + return EV_FOOTSTEP; +} + + +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ +static void PM_CrashLand( void ) { + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + + // Ridah, only play this if coming down hard + if ( !pm->ps->legsTimer ) { + if ( pml.previous_velocity[2] < -220 ) { + BG_AnimScriptEvent( pm->ps, ANIM_ET_LAND, qfalse, qtrue ); + } + } + + // calculate the exact velocity on landing + dist = pm->ps->origin[2] - pml.previous_origin[2]; + vel = pml.previous_velocity[2]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta * delta * 0.0001; + + // never take falling damage if completely underwater + if ( pm->waterlevel == 3 ) { + return; + } + + // reduce falling damage if there is standing water + if ( pm->waterlevel == 2 ) { + delta *= 0.25; + } + if ( pm->waterlevel == 1 ) { + delta *= 0.5; + } + + if ( delta < 1 ) { + return; + } + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if ( !( pml.groundTrace.surfaceFlags & SURF_NODAMAGE ) ) { + if ( pm->debugLevel ) { + Com_Printf( "delta: %5.2f\n", delta ); + } + +/* JPW NERVE removed from MP, breaks too many levels and skill as no-fall-damage indicator isn't obvious + // Rafael gameskill + if (bg_pmove_gameskill_integer == 1) + { + if (delta > 7) + delta = 8; + } + // done +*/ + + if ( delta > 77 ) { + PM_AddEvent( EV_FALL_NDIE ); + } + //else if (delta > 67) + //{ + // PM_AddEvent(EV_FALL_DMG_75); + //} + else if ( delta > 67 ) { + PM_AddEvent( EV_FALL_DMG_50 ); + } + //else if (delta > 48) + //{ + // PM_AddEvent(EV_FALL_DMG_30); + //} + else if ( delta > 58 ) { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) { + PM_AddEvent( EV_FALL_DMG_25 ); + } + } else if ( delta > 48 ) { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) { + PM_AddEvent( EV_FALL_DMG_15 ); + } + } else if ( delta > 38.75 ) { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) { + PM_AddEvent( EV_FALL_DMG_10 ); + } + } else if ( delta > 7 ) { + PM_AddEvent( EV_FALL_SHORT ); + } else + { + PM_AddEvent( PM_FootstepForSurface() ); + } + } + + // start footstep cycle over + pm->ps->bobCycle = 0; +} + + + +/* +============= +PM_CorrectAllSolid +============= +*/ +static int PM_CorrectAllSolid( trace_t *trace ) { + int i, j, k; + vec3_t point; + + if ( pm->debugLevel ) { + Com_Printf( "%i:allsolid\n", c_pmove ); + } + + // jitter around + for ( i = -1; i <= 1; i++ ) { + for ( j = -1; j <= 1; j++ ) { + for ( k = -1; k <= 1; k++ ) { + VectorCopy( pm->ps->origin, point ); + point[0] += (float) i; + point[1] += (float) j; + point[2] += (float) k; + pm->trace( trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + if ( !trace->allsolid ) { + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace( trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + pml.groundTrace = *trace; + return qtrue; + } + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + + +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) { + trace_t trace; + vec3_t point; + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) { + // we just transitioned into freefall + if ( pm->debugLevel ) { + Com_Printf( "%i:lift\n", c_pmove ); + } + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[2] -= 64; + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + if ( trace.fraction == 1.0 ) { + if ( pm->cmd.forwardmove >= 0 ) { + BG_AnimScriptEvent( pm->ps, ANIM_ET_JUMP, qfalse, qtrue ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + BG_AnimScriptEvent( pm->ps, ANIM_ET_JUMPBK, qfalse, qtrue ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) { + vec3_t point; + trace_t trace; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + // DHM - Nerve + if ( pm->ps->eFlags & EF_MG42_ACTIVE ) { + point[2] = pm->ps->origin[2] - 1.f; + } else { + point[2] = pm->ps->origin[2] - 0.25; + } + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) { + if ( !PM_CorrectAllSolid( &trace ) ) { + return; + } + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 ) { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // check if getting thrown off the ground + if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { + if ( pm->debugLevel ) { + Com_Printf( "%i:kickoff\n", c_pmove ); + } + // go into jump animation + if ( pm->cmd.forwardmove >= 0 ) { + BG_AnimScriptEvent( pm->ps, ANIM_ET_JUMP, qfalse, qfalse ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + BG_AnimScriptEvent( pm->ps, ANIM_ET_JUMPBK, qfalse, qfalse ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { + if ( pm->debugLevel ) { + Com_Printf( "%i:steep\n", c_pmove ); + } + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if ( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) { + pm->ps->pm_flags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND ); + pm->ps->pm_time = 0; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + // just hit the ground + if ( pm->debugLevel ) { + Com_Printf( "%i:Land\n", c_pmove ); + } + + PM_CrashLand(); + + // don't do landing time if we were just going down a slope + if ( pml.previous_velocity[2] < -200 ) { + // don't allow another jump for a little while + pm->ps->pm_flags |= PMF_TIME_LAND; + pm->ps->pm_time = 250; + } + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving +============= +*/ +static void PM_SetWaterLevel( void ) { + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + // Ridah, modified this + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + pm->ps->mins[2] + 1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if ( cont & MASK_WATER ) { + sample2 = pm->ps->viewheight - pm->ps->mins[2]; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[2] = pm->ps->origin[2] + pm->ps->mins[2] + sample1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) { + pm->waterlevel = 2; + point[2] = pm->ps->origin[2] + pm->ps->mins[2] + sample2; + cont = pm->pointcontents( point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) { + pm->waterlevel = 3; + } + } + } + // done. + + // UNDERWATER + BG_UpdateConditionValue( pm->ps->clientNum, ANIM_COND_UNDERWATER, ( pm->waterlevel > 2 ), qtrue ); + +} + + + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +static void PM_CheckDuck( void ) { + trace_t trace; + + // Ridah, modified this for configurable bounding boxes + pm->mins[0] = pm->ps->mins[0]; + pm->mins[1] = pm->ps->mins[1]; + + pm->maxs[0] = pm->ps->maxs[0]; + pm->maxs[1] = pm->ps->maxs[1]; + + pm->mins[2] = pm->ps->mins[2]; + + if ( pm->ps->pm_type == PM_DEAD ) { + pm->maxs[2] = pm->ps->maxs[2]; // NOTE: must set death bounding box in game code + pm->ps->viewheight = pm->ps->deadViewHeight; + return; + } + + if ( pm->cmd.upmove < 0 ) { // duck + pm->ps->pm_flags |= PMF_DUCKED; + } else + { // stand up if possible + if ( pm->ps->pm_flags & PMF_DUCKED ) { + // try to stand up + pm->maxs[2] = pm->ps->maxs[2]; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid ) { + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + } + + if ( pm->ps->pm_flags & PMF_DUCKED ) { + pm->maxs[2] = pm->ps->crouchMaxZ; + pm->ps->viewheight = pm->ps->crouchViewHeight; + } else + { + pm->maxs[2] = pm->ps->maxs[2]; + pm->ps->viewheight = pm->ps->standViewHeight; + } + // done. +} + + + +//=================================================================== + + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) { + float bobmove; + int old; + qboolean footstep; + qboolean iswalking; + int animResult = -1; + + if ( pm->ps->eFlags & EF_DEAD ) { + + // DHM - Nerve :: before going to limbo, play a wounded/fallen animation + if ( !pm->ps->pm_time && !( pm->ps->pm_flags & PMF_LIMBO ) ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_FALLEN, qtrue ); + } + + return; + } + + iswalking = qfalse; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] + + pm->ps->velocity[1] * pm->ps->velocity[1] ); + + // mg42, always idle + if ( pm->ps->persistant[PERS_HWEAPON_USE] ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_IDLE, qtrue ); + // + return; + } + + // swimming + if ( pm->waterlevel > 2 ) { + + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_SWIMBK, qtrue ); + } else { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_SWIM, qtrue ); + } + + return; + } + + // in the air + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + if ( pm->ps->pm_flags & PMF_LADDER ) { // on ladder + if ( pm->ps->velocity[2] >= 0 ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_CLIMBUP, qtrue ); + //BG_PlayAnimName( pm->ps, "BOTH_CLIMB", ANIM_BP_BOTH, qfalse, qtrue, qfalse ); + } else if ( pm->ps->velocity[2] < 0 ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_CLIMBDOWN, qtrue ); + //BG_PlayAnimName( pm->ps, "BOTH_CLIMB_DOWN", ANIM_BP_BOTH, qfalse, qtrue, qfalse ); + } + } + + return; + } + + // if not trying to move + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { + if ( pm->xyspeed < 5 ) { + pm->ps->bobCycle = 0; // start at beginning of cycle again + } + if ( pm->xyspeed > 120 ) { + return; // continue what they were doing last frame, until we stop + } + if ( pm->ps->pm_flags & PMF_DUCKED ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_IDLECR, qtrue ); + } + if ( animResult < 0 ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_IDLE, qtrue ); + } + // + return; + } + + + footstep = qfalse; + + if ( pm->ps->pm_flags & PMF_DUCKED ) { + bobmove = 0.5; // ducked characters bob much faster + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_WALKCRBK, qtrue ); + } else { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_WALKCR, qtrue ); + } + // ducked characters never play footsteps + } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4; // faster speeds bob faster + footstep = qtrue; + // check for strafing + if ( pm->cmd.rightmove && !pm->cmd.forwardmove ) { + if ( pm->cmd.rightmove > 0 ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_STRAFERIGHT, qtrue ); + } else { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_STRAFELEFT, qtrue ); + } + } + if ( animResult < 0 ) { // if we havent found an anim yet, play the run + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_RUNBK, qtrue ); + } + } else { + bobmove = 0.3; + // check for strafing + if ( pm->cmd.rightmove && !pm->cmd.forwardmove ) { + if ( pm->cmd.rightmove > 0 ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_STRAFERIGHT, qtrue ); + } else { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_STRAFELEFT, qtrue ); + } + } + if ( animResult < 0 ) { // if we havent found an anim yet, play the run + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_WALKBK, qtrue ); + } + } + + } else { + + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4; // faster speeds bob faster + footstep = qtrue; + // check for strafing + if ( pm->cmd.rightmove && !pm->cmd.forwardmove ) { + if ( pm->cmd.rightmove > 0 ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_STRAFERIGHT, qtrue ); + } else { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_STRAFELEFT, qtrue ); + } + } + if ( animResult < 0 ) { // if we havent found an anim yet, play the run + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_RUN, qtrue ); + } + } else { + bobmove = 0.3; // walking bobs slow + if ( pm->ps->aiChar != AICHAR_NONE ) { + footstep = qtrue; + iswalking = qtrue; + } + if ( pm->cmd.rightmove && !pm->cmd.forwardmove ) { + if ( pm->cmd.rightmove > 0 ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_STRAFERIGHT, qtrue ); + } else { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_STRAFELEFT, qtrue ); + } + } + if ( animResult < 0 ) { // if we havent found an anim yet, play the run + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_WALK, qtrue ); + } + } + } + + // if no anim found yet, then just use the idle as default + if ( animResult < 0 ) { + animResult = BG_AnimScriptAnimation( pm->ps, pm->ps->aiState, ANIM_MT_IDLE, qtrue ); + } + + // check for footstep / splash sounds + old = pm->ps->bobCycle; + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if ( iswalking ) { + // sounds much more natural this way + if ( old > pm->ps->bobCycle ) { + + if ( pm->waterlevel == 0 ) { + if ( footstep && !pm->noFootsteps ) { + PM_AddEvent( PM_FootstepForSurface() ); + } + } else if ( pm->waterlevel == 1 ) { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } else if ( pm->waterlevel == 2 ) { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } else if ( pm->waterlevel == 3 ) { + // no sound when completely underwater + } + + } + } else if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) { + + if ( pm->ps->sprintExertTime && pm->waterlevel <= 2 ) { + PM_ExertSound(); + } + + if ( pm->waterlevel == 0 ) { + // on ground will only play sounds if running + if ( footstep && !pm->noFootsteps ) { + PM_AddEvent( PM_FootstepForSurface() ); + } + } else if ( pm->waterlevel == 1 ) { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } else if ( pm->waterlevel == 2 ) { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } else if ( pm->waterlevel == 3 ) { + // no sound when completely underwater + + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) { // FIXME? + // + // if just entered a water volume, play a sound + // + if ( !pml.previous_waterlevel && pm->waterlevel ) { + PM_AddEvent( EV_WATER_TOUCH ); + } + + // + // if just completely exited a water volume, play a sound + // + if ( pml.previous_waterlevel && !pm->waterlevel ) { + PM_AddEvent( EV_WATER_LEAVE ); + } + + // + // check for head just going under water + // + if ( pml.previous_waterlevel != 3 && pm->waterlevel == 3 ) { + PM_AddEvent( EV_WATER_UNDER ); + } + + // + // check for head just coming out of water + // + if ( pml.previous_waterlevel == 3 && pm->waterlevel != 3 ) { + PM_AddEvent( EV_WATER_CLEAR ); + } +} + + +/* +============== +PM_BeginWeaponReload +============== +*/ +static void PM_BeginWeaponReload( int weapon ) { + // only allow reload if the weapon isn't already occupied (firing is okay) + if ( pm->ps->weaponstate != WEAPON_READY && pm->ps->weaponstate != WEAPON_FIRING ) { + return; + } + + if ( weapon < WP_BEGINGERMAN || weapon > WP_DYNAMITE2 ) { + return; + } + + // no reload when you've got a chair in your hands + if ( pm->ps->eFlags & EF_MELEE_ACTIVE ) { + return; + } + + // no reload when leaning (this includes manual and auto reloads) + if ( pm->ps->leanf ) { + return; + } + + // (SA) easier check now that the animation system handles the specifics + switch ( weapon ) { + case WP_DYNAMITE: + case WP_DYNAMITE2: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + break; + + default: + // DHM - Nerve :: override current animation (so reloading after firing will work) + BG_AnimScriptEvent( pm->ps, ANIM_ET_RELOAD, qfalse, qtrue ); + break; + } + + + PM_ContinueWeaponAnim( WEAP_RELOAD1 ); + + + // okay to reload while overheating without tacking the reload time onto the end of the + // current weaponTime (the reload time is partially absorbed into the overheat time) + if ( pm->ps->weaponstate == WEAPON_READY ) { + pm->ps->weaponTime += ammoTable[weapon].reloadTime; + } else if ( pm->ps->weaponTime < ammoTable[weapon].reloadTime ) { + pm->ps->weaponTime += ( ammoTable[weapon].reloadTime - pm->ps->weaponTime ); + } + + pm->ps->weaponstate = WEAPON_RELOADING; + PM_AddEvent( EV_FILL_CLIP ); // play reload sound +} + + + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +static void PM_BeginWeaponChange( int oldweapon, int newweapon ) { //----(SA) modified to play 1st person alt-mode transition animations. + int switchtime; + + if ( newweapon <= WP_NONE || newweapon >= WP_NUM_WEAPONS ) { + return; + } + + if ( !( COM_BitCheck( pm->ps->weapons, newweapon ) ) ) { + return; + } + + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + return; + } + + // don't allow switch if you're holding a hot potato or dynamite + if ( pm->ps->grenadeTimeLeft > 0 ) { + return; + } + + switch ( newweapon ) { + + case WP_GAUNTLET: + case WP_MONSTER_ATTACK1: + case WP_MONSTER_ATTACK2: + case WP_MONSTER_ATTACK3: + break; + + case WP_DYNAMITE: + case WP_DYNAMITE2: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + // initialize the timer on the potato you're switching to + pm->ps->grenadeTimeLeft = 0; + + default: + //----(SA) only play the weapon switch sound for the player + if ( !( pm->ps->aiChar ) ) { + PM_AddEvent( EV_CHANGE_WEAPON ); + } + + // it's an alt mode, play different anim + if ( newweapon == weapAlts[oldweapon] ) { + PM_StartWeaponAnim( WEAP_ALTSWITCHFROM ); + } else { + PM_StartWeaponAnim( WEAP_DROP ); // PM_ContinueWeaponAnim(WEAP_DROP); + } + } + + BG_AnimScriptEvent( pm->ps, ANIM_ET_DROPWEAPON, qfalse, qfalse ); + + pm->ps->weaponstate = WEAPON_DROPPING; + + switchtime = 250; // dropping/raising usually takes 1/4 sec. + // sometimes different switch times for alt weapons + switch ( oldweapon ) { + case WP_LUGER: + if ( newweapon == weapAlts[oldweapon] ) { + switchtime = 50; + } + break; + case WP_SILENCER: + if ( newweapon == weapAlts[oldweapon] ) { + switchtime = 1200; + } + break; + case WP_FG42: + case WP_FG42SCOPE: + if ( newweapon == weapAlts[oldweapon] ) { + switchtime = 50; // fast + } + break; + } + + pm->ps->weaponTime += switchtime; +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +static void PM_FinishWeaponChange( void ) { + int oldweapon, newweapon, switchtime; + + newweapon = pm->cmd.weapon; + if ( newweapon < WP_NONE || newweapon >= WP_NUM_WEAPONS ) { + newweapon = WP_NONE; + } + + if ( !( COM_BitCheck( pm->ps->weapons, newweapon ) ) ) { + newweapon = WP_NONE; + } + + oldweapon = pm->ps->weapon; + + pm->ps->weapon = newweapon; + pm->ps->weaponstate = WEAPON_RAISING; + + switch ( newweapon ) + { + // don't really care about anim since these weapons don't show in view. + // However, need to set the animspreadscale so they are initally at worst accuracy + case WP_SNOOPERSCOPE: + case WP_SNIPERRIFLE: + pm->ps->aimSpreadScale = 255; // initially at lowest accuracy + pm->ps->aimSpreadScaleFloat = 255.0f; // initially at lowest accuracy + + default: + break; + } + + // doesn't happen too often (player switched weapons away then back very quickly) + if ( oldweapon == newweapon ) { + return; + } + + // dropping/raising usually takes 1/4 sec. + switchtime = 250; + + // sometimes different switch times for alt weapons + switch ( newweapon ) { + case WP_LUGER: + if ( newweapon == weapAlts[oldweapon] ) { + switchtime = 50; + } + break; + case WP_SILENCER: + if ( newweapon == weapAlts[oldweapon] ) { + switchtime = 1190; + } + break; + case WP_FG42: + case WP_FG42SCOPE: + if ( newweapon == weapAlts[oldweapon] ) { + switchtime = 50; // fast + } + break; + } + + pm->ps->weaponTime += switchtime; + + BG_UpdateConditionValue( pm->ps->clientNum, ANIM_COND_WEAPON, newweapon, qtrue ); + + // play an animation + BG_AnimScriptEvent( pm->ps, ANIM_ET_RAISEWEAPON, qfalse, qfalse ); + + // alt weapon switch was played when switching away, just go into idle + if ( weapAlts[oldweapon] == newweapon ) { + PM_StartWeaponAnim( WEAP_ALTSWITCHTO ); + } else { + PM_StartWeaponAnim( WEAP_RAISE ); + } + +} + + +/* +============== +PM_ReloadClip +============== +*/ +static void PM_ReloadClip( int weapon ) { + int ammoreserve, ammoclip, ammomove; + + ammoreserve = pm->ps->ammo[ BG_FindAmmoForWeapon( weapon )]; + ammoclip = pm->ps->ammoclip[BG_FindClipForWeapon( weapon )]; + + ammomove = ammoTable[weapon].maxclip - ammoclip; + + if ( ammoreserve < ammomove ) { + ammomove = ammoreserve; + } + + if ( ammomove ) { + pm->ps->ammo[ BG_FindAmmoForWeapon( weapon )] -= ammomove; + pm->ps->ammoclip[BG_FindClipForWeapon( weapon )] += ammomove; + } + + if ( weapon == WP_AKIMBO ) { // reload colt too + PM_ReloadClip( WP_COLT ); + } +} + +/* +============== +PM_FinishWeaponReload +============== +*/ + +static void PM_FinishWeaponReload( void ) { + PM_ReloadClip( pm->ps->weapon ); // move ammo into clip + pm->ps->weaponstate = WEAPON_READY; // ready to fire +} + + +/* +============== +PM_CheckforReload +============== +*/ +void PM_CheckForReload( int weapon ) { + qboolean autoreload; + qboolean reloadRequested; + + if ( pm->noWeapClips ) { // no need to reload + return; + } + + // user is forcing a reload (manual reload) + reloadRequested = (qboolean)( pm->cmd.wbuttons & WBUTTON_RELOAD ); + + switch ( pm->ps->weaponstate ) { + case WEAPON_RAISING: + case WEAPON_DROPPING: + case WEAPON_READYING: + case WEAPON_RELAXING: + case WEAPON_RELOADING: + return; + break; + default: + break; + } + + autoreload = pm->pmext->bAutoReload || !IS_AUTORELOAD_WEAPON( weapon ); + + // in auto reload mode, clip is empty, but you have reserves. + if ( autoreload && !( pm->ps->ammoclip[BG_FindClipForWeapon( weapon )] ) && // clip is empty... + pm->ps->ammo[BG_FindAmmoForWeapon( weapon )] ) { // and you have reserves + PM_BeginWeaponReload( weapon ); + } else if ( reloadRequested ) { + // don't allow a force reload if it won't have any effect (no more ammo reserves or full clip) + if ( pm->ps->ammo[BG_FindAmmoForWeapon( weapon )] && pm->ps->ammoclip[BG_FindClipForWeapon( weapon )] < ammoTable[weapon].maxclip ) { + PM_BeginWeaponReload( weapon ); + } + } else if ( weapon == WP_AKIMBO ) { // also check colt for reload + PM_CheckForReload( WP_COLT ); + } +} + +/* +============== +PM_SwitchIfEmpty +============== +*/ +static void PM_SwitchIfEmpty( void ) { + // weapon from here down will be a thrown explosive + if ( pm->ps->weapon != WP_GRENADE_LAUNCHER && + pm->ps->weapon != WP_GRENADE_PINEAPPLE && + pm->ps->weapon != WP_DYNAMITE && + pm->ps->weapon != WP_DYNAMITE2 ) { + return; + } + + if ( pm->ps->ammoclip[ BG_FindClipForWeapon( pm->ps->weapon )] ) { // still got ammo in clip + return; + } + + if ( pm->ps->ammo[ BG_FindAmmoForWeapon( pm->ps->weapon )] ) { // still got ammo in reserve + return; + } + + // If this was the last one, remove the weapon and switch away before the player tries to fire next + + // NOTE: giving grenade ammo to a player will re-give him the weapon (if you do it through add_ammo()) + switch ( pm->ps->weapon ) { + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_DYNAMITE: + case WP_DYNAMITE2: + COM_BitClear( pm->ps->weapons, pm->ps->weapon ); + break; + default: + break; + } + + PM_AddEvent( EV_NOAMMO ); +} + + +/* +============== +PM_WeaponUseAmmo + accounts for clips being used/not used +============== +*/ +void PM_WeaponUseAmmo( int wp, int amount ) { + int takeweapon; + + if ( pm->noWeapClips ) { + pm->ps->ammo[ BG_FindAmmoForWeapon( wp )] -= amount; + } else { + takeweapon = BG_FindClipForWeapon( wp ); + if ( wp == WP_AKIMBO ) { + if ( !BG_AkimboFireSequence( pm->ps ) ) { + takeweapon = WP_COLT; + } + } + + pm->ps->ammoclip[takeweapon] -= amount; + } +} + + +/* +============== +PM_WeaponAmmoAvailable + accounts for clips being used/not used +============== +*/ +int PM_WeaponAmmoAvailable( int wp ) { + if ( pm->noWeapClips ) { + return pm->ps->ammo[ BG_FindAmmoForWeapon( wp )]; + } else { + return pm->ps->ammoclip[BG_FindClipForWeapon( wp )]; + } +} + +/* +============== +PM_WeaponClipEmpty + accounts for clips being used/not used +============== +*/ +int PM_WeaponClipEmpty( int wp ) { + if ( pm->noWeapClips ) { + if ( !( pm->ps->ammo[ BG_FindAmmoForWeapon( wp )] ) ) { + return 1; + } + } else { + if ( !( pm->ps->ammoclip[BG_FindClipForWeapon( wp )] ) ) { + return 1; + } + } + + return 0; +} + + +/* +============== +PM_CoolWeapons +============== +*/ +void PM_CoolWeapons( void ) { + int wp; + + for ( wp = 0; wp < WP_NUM_WEAPONS; wp++ ) { + + // if you have the weapon + if ( COM_BitCheck( pm->ps->weapons, wp ) ) { + // and it's hot + if ( pm->ps->weapHeat[wp] ) { + pm->ps->weapHeat[wp] -= ( (float)ammoTable[wp].coolRate * pml.frametime ); + + if ( pm->ps->weapHeat[wp] < 0 ) { + pm->ps->weapHeat[wp] = 0; + } + + } + } + } + + // a weapon is currently selected, convert current heat value to 0-255 range for client transmission + if ( pm->ps->weapon ) { + pm->ps->curWeapHeat = ( ( (float)pm->ps->weapHeat[pm->ps->weapon] / (float)ammoTable[pm->ps->weapon].maxHeat ) ) * 255.0f; + +// if(pm->ps->weapHeat[pm->ps->weapon]) +// Com_Printf("pm heat: %d, %d\n", pm->ps->weapHeat[pm->ps->weapon], pm->ps->curWeapHeat); + } + +} + +/* +============== +PM_AdjustAimSpreadScale +============== +*/ +//#define AIMSPREAD_DECREASE_RATE 300.0f +#define AIMSPREAD_DECREASE_RATE 200.0f // (SA) when I made the increase/decrease floats (so slower weapon recover could happen for scoped weaps) the average rate increased significantly +#define AIMSPREAD_INCREASE_RATE 800.0f +#define AIMSPREAD_VIEWRATE_MIN 30.0f // degrees per second +#define AIMSPREAD_VIEWRATE_RANGE 120.0f // degrees per second + +void PM_AdjustAimSpreadScale( void ) { +// int increase, decrease, i; + int i; + float increase, decrease; // (SA) was losing lots of precision on slower weapons (scoped) + float viewchange, cmdTime, wpnScale; + + // all weapons are very inaccurate in zoomed mode + if ( pm->ps->eFlags & EF_ZOOMING ) { + + pm->ps->aimSpreadScale = 255; + pm->ps->aimSpreadScaleFloat = 255; + return; + } + + cmdTime = (float)( pm->cmd.serverTime - pm->oldcmd.serverTime ) / 1000.0; + + wpnScale = 0.0f; + switch ( pm->ps->weapon ) { + case WP_LUGER: + case WP_SILENCER: + wpnScale = 0.5f; + break; + case WP_AKIMBO: //----(SA) added + wpnScale = 0.5; + break; + case WP_COLT: + wpnScale = 0.4f; // doesn't fire as fast, but easier to handle than luger + break; + case WP_VENOM: + wpnScale = 0.9f; // very heavy + break; + case WP_VENOM_FULL: + wpnScale = 1.5f; + break; + case WP_SNIPERRIFLE: // (SA) looong time to recover + wpnScale = 10.0f; + break; + case WP_SNOOPERSCOPE: // (SA) looong time to recover + wpnScale = 8.0f; + break; + case WP_MAUSER: + wpnScale = 0.5f; + break; + case WP_GARAND: + wpnScale = 0.5f; + break; + case WP_MP40: + wpnScale = 0.6f; // 2 handed, but not as long as mauser, so harder to keep aim + break; +//----(SA) added + case WP_BAR: + case WP_BAR2: + wpnScale = 1.0f; + break; +//----(SA) end + case WP_FG42: + case WP_FG42SCOPE: + wpnScale = 0.6f; + break; + case WP_THOMPSON: + wpnScale = 0.6f; + break; + case WP_STEN: + wpnScale = 0.6f; + break; + //case WP_PANZERFAUST: + //case WP_ROCKET_LAUNCHER: + // wpnScale = 0.5; + // break; + } + + if ( wpnScale ) { + +// JPW NERVE crouched players recover faster (mostly useful for snipers) + if ( pm->ps->eFlags & EF_CROUCHING ) { + wpnScale *= 0.5; + } +// jpw + + decrease = ( cmdTime * AIMSPREAD_DECREASE_RATE ) / wpnScale; + + viewchange = 0; + // take player movement into account (even if only for the scoped weapons) + // TODO: also check for jump/crouch and adjust accordingly + if ( pm->ps->weapon == WP_SNIPERRIFLE || pm->ps->weapon == WP_SNOOPERSCOPE ) { + for ( i = 0; i < 2; i++ ) + viewchange += fabs( pm->ps->velocity[i] ); + } else { + // take player view rotation into account + for ( i = 0; i < 2; i++ ) + viewchange += fabs( SHORT2ANGLE( pm->cmd.angles[i] ) - SHORT2ANGLE( pm->oldcmd.angles[i] ) ); + } + + viewchange = (float)viewchange / cmdTime; // convert into this movement for a second + viewchange -= AIMSPREAD_VIEWRATE_MIN / wpnScale; + if ( viewchange <= 0 ) { + viewchange = 0; + } else if ( viewchange > ( AIMSPREAD_VIEWRATE_RANGE / wpnScale ) ) { + viewchange = AIMSPREAD_VIEWRATE_RANGE / wpnScale; + } + + // now give us a scale from 0.0 to 1.0 to apply the spread increase + viewchange = viewchange / (float)( AIMSPREAD_VIEWRATE_RANGE / wpnScale ); + + increase = (int)( cmdTime * viewchange * AIMSPREAD_INCREASE_RATE ); + } else { + increase = 0; + decrease = AIMSPREAD_DECREASE_RATE; + } + + // update the aimSpreadScale + pm->ps->aimSpreadScaleFloat += ( increase - decrease ); + if ( pm->ps->aimSpreadScaleFloat < 0 ) { + pm->ps->aimSpreadScaleFloat = 0; + } + if ( pm->ps->aimSpreadScaleFloat > 255 ) { + pm->ps->aimSpreadScaleFloat = 255; + } + + pm->ps->aimSpreadScale = (int)pm->ps->aimSpreadScaleFloat; // update the int for the client +} + +#define weaponstateFiring ( pm->ps->weaponstate == WEAPON_FIRING || pm->ps->weaponstate == WEAPON_FIRINGALT ) + +#define GRENADE_DELAY 250 + +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ + +#define VENOM_LOW_IDLE WEAP_IDLE1 +#define VENOM_HI_IDLE WEAP_IDLE2 +#define VENOM_RAISE WEAP_ATTACK1 +#define VENOM_ATTACK WEAP_ATTACK2 +#define VENOM_LOWER WEAP_ATTACK_LASTSHOT + +//#define DO_WEAPON_DBG 1 + +static void PM_Weapon( void ) { + int addTime = 0; // TTimo: init + int ammoNeeded; + qboolean delayedFire; //----(SA) true if the delay time has just expired and this is the frame to send the fire event + int aimSpreadScaleAdd; + int weapattackanim; + int pfausttimeout; +#ifdef DO_WEAPON_DBG + static int weaponstate_last = -1; +#endif + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // ignore if spectator + if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + //pm->ps->weapon = WP_NONE; + return; + } + + // special mounted mg42 handling + if ( pm->ps->persistant[PERS_HWEAPON_USE] ) { + if ( pm->cmd.buttons & BUTTON_ATTACK ) { + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime < 0 ) { + pm->ps->weaponTime = 0; + } + return; + } + + PM_AddEvent( EV_FIRE_WEAPON_MG42 ); + + pm->ps->weaponTime += MG42_RATE_OF_FIRE; + + BG_AnimScriptEvent( pm->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue ); + pm->ps->viewlocked = 2; // this enable screen jitter when firing + } + return; + } + + pm->watertype = 0; + + // TTimo + // show_bug.cgi?id=416 +#ifdef DO_WEAPON_DBG + if ( pm->ps->weaponstate != weaponstate_last ) { + #ifdef CGAMEDLL + Com_Printf( "CGAME DLL\n" ); + #else + Com_Printf( "GAME DLL\n" ); + #endif + switch ( pm->ps->weaponstate ) { + case WEAPON_READY: + Com_Printf( " -- WEAPON_READY\n" ); + break; + case WEAPON_RAISING: + Com_Printf( " -- WEAPON_RAISING\n" ); + break; + case WEAPON_DROPPING: + Com_Printf( " -- WEAPON_DROPPING\n" ); + break; + case WEAPON_READYING: + Com_Printf( " -- WEAPON_READYING\n" ); + break; + case WEAPON_RELAXING: + Com_Printf( " -- WEAPON_RELAXING\n" ); + break; + case WEAPON_VENOM_REST: + Com_Printf( " -- WEAPON_VENOM_REST\n" ); + break; + case WEAPON_FIRING: + Com_Printf( " -- WEAPON_FIRING\n" ); + break; + case WEAPON_FIRINGALT: + Com_Printf( " -- WEAPON_FIRINGALT\n" ); + break; + case WEAPON_RELOADING: + Com_Printf( " -- WEAPON_RELOADING\n" ); + break; + } + Com_Printf( "weap: %d\n", pm->ps->weapon ); + weaponstate_last = pm->ps->weaponstate; + } +#endif + + // dec venom timer + if ( pm->ps->venomTime > 0 ) { + pm->ps->venomTime -= pml.msec; + } + + // weapon cool down + PM_CoolWeapons(); + + // check for item using + if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { + if ( !( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { + gitem_t *item; + + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + + if ( pm->cmd.holdable ) { + item = BG_FindItemForHoldable( pm->cmd.holdable ); + + if ( item && ( pm->ps->holdable[pm->cmd.holdable] >= item->quantity ) ) { // ->quantity being how much 'ammo' is taken per use + PM_AddEvent( EV_USE_ITEM0 + pm->cmd.holdable ); + // don't take books away when used + if ( pm->cmd.holdable < HI_BOOK1 || pm->cmd.holdable > HI_BOOK3 ) { + pm->ps->holdable[ pm->cmd.holdable ] -= item->quantity; + } + + if ( pm->ps->holdable[pm->cmd.holdable] <= 0 ) { // empty + PM_AddEvent( EV_NOITEM ); + } + } + } else { + PM_AddEvent( EV_USE_ITEM0 ); // send "using nothing" + } + return; + } + } else + { + pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; + } + + + delayedFire = qfalse; + + if ( pm->ps->weapon == WP_GRENADE_LAUNCHER || pm->ps->weapon == WP_GRENADE_PINEAPPLE || pm->ps->weapon == WP_DYNAMITE || pm->ps->weapon == WP_DYNAMITE2 ) { + // (SA) AI's don't set grenadeTimeLeft on +attack, so I don't check for (pm->ps->aiChar) here + if ( pm->ps->grenadeTimeLeft > 0 ) { + if ( pm->ps->weapon == WP_DYNAMITE || pm->ps->weapon == WP_DYNAMITE2 ) { + pm->ps->grenadeTimeLeft += pml.msec; + + // JPW NERVE -- in multiplayer, dynamite becomes strategic, so start timer @ 30 seconds + if ( pm->gametype != GT_SINGLE_PLAYER ) { + if ( pm->ps->grenadeTimeLeft < 5000 ) { + pm->ps->grenadeTimeLeft = 5000; + } + } + // jpw + +// Com_Printf("Dynamite Timer: %d\n", pm->ps->grenadeTimeLeft); + } else { + pm->ps->grenadeTimeLeft -= pml.msec; +// Com_Printf("Grenade Timer: %d\n", pm->ps->grenadeTimeLeft); + + if ( pm->ps->grenadeTimeLeft <= 100 ) { // give two frames advance notice so there's time to launch and detonate + pm->ps->grenadeTimeLeft = 100; + PM_AddEvent( EV_FIRE_WEAPON ); + pm->ps->weaponTime = 1600; + return; + } + } + + if ( !( pm->cmd.buttons & BUTTON_ATTACK ) ) { //----(SA) modified + if ( pm->ps->weaponDelay == ammoTable[pm->ps->weapon].fireDelayTime ) { + // released fire button. Fire!!! + BG_AnimScriptEvent( pm->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue ); + } + } else { + return; + } + } + } + + if ( pm->ps->weaponDelay > 0 ) { + pm->ps->weaponDelay -= pml.msec; + if ( pm->ps->weaponDelay <= 0 ) { + pm->ps->weaponDelay = 0; + delayedFire = qtrue; // weapon delay has expired. Fire this frame + + // double check the player is still holding the fire button down for these weapons + // so you don't get a delayed "non-fire" (fire hit and released, then shot fires) + switch ( pm->ps->weapon ) { + case WP_VENOM: + case WP_VENOM_FULL: + if ( pm->ps->weaponstate == WEAPON_FIRING ) { + delayedFire = qfalse; + } + break; + default: + break; + } + } + } + + + if ( pm->ps->weaponstate == WEAPON_RELAXING ) { + pm->ps->weaponstate = WEAPON_READY; + return; + } + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime < 0 ) { + pm->ps->weaponTime = 0; + } + + // JPW NERVE -- added back for multiplayer pistol balancing + if ( pm->gametype != GT_SINGLE_PLAYER ) { + if ( pm->ps->weapon == WP_LUGER ) { + if ( pm->ps->releasedFire ) { + if ( ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->weaponTime <= 150 ) { + pm->ps->weaponTime = 0; + } + } else if ( !( pm->cmd.buttons & BUTTON_ATTACK ) ) { + pm->ps->releasedFire = qtrue; + } + } else if ( pm->ps->weapon == WP_COLT ) { + if ( pm->ps->releasedFire ) { + if ( ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->weaponTime <= 150 ) { + pm->ps->weaponTime = 0; + } + } else if ( !( pm->cmd.buttons & BUTTON_ATTACK ) ) { + pm->ps->releasedFire = qtrue; + } + } + } +// jpw + + } + + + + // check for weapon change + // can't change if weapon is firing, but can change + // again if lowering or raising + + // TTimo gcc: suggest parentheses around && within || + if ( pm->ps->weaponTime <= 0 || ( !weaponstateFiring && pm->ps->weaponDelay <= 0 ) ) { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->ps->weapon, pm->cmd.weapon ); //----(SA) modified + } + } + + // check for clip change + PM_CheckForReload( pm->ps->weapon ); + + if ( pm->ps->weaponTime > 0 || pm->ps->weaponDelay > 0 ) { + return; + } + + if ( pm->ps->weaponstate == WEAPON_RELOADING ) { + PM_FinishWeaponReload(); + } + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + if ( pm->ps->weaponstate == WEAPON_RAISING ) { + pm->ps->weaponstate = WEAPON_READY; + PM_StartWeaponAnim( WEAP_IDLE1 ); + return; + } + + + if ( pm->ps->weapon == WP_NONE ) { // this is possible since the player starts with nothing + return; + } + + + // JPW NERVE -- in multiplayer, don't allow panzerfaust or dynamite to fire if charge bar isn't full + if ( pm->gametype >= GT_WOLF ) { + if ( pm->ps->weapon == WP_PANZERFAUST ) { + if ( pm->ps->stats[ STAT_PLAYER_CLASS ] == PC_LT ) { + pfausttimeout = pm->ltChargeTime; + } else { + pfausttimeout = pm->soldierChargeTime; + } + if ( pm->cmd.serverTime - pm->ps->classWeaponTime < pfausttimeout ) { + return; + } + } + if ( pm->ps->weapon == WP_DYNAMITE ) { + if ( pm->cmd.serverTime - pm->ps->classWeaponTime < pm->engineerChargeTime ) { + return; + } + } + if ( pm->ps->weapon == WP_MEDKIT ) { + if ( pm->cmd.serverTime - pm->ps->classWeaponTime < ( pm->medicChargeTime * 0.25f ) ) { + return; + } + } + if ( pm->ps->weapon == WP_AMMO ) { + if ( pm->cmd.serverTime - pm->ps->classWeaponTime < ( pm->ltChargeTime * 0.25f ) ) { + return; + } + } + if ( pm->ps->weapon == WP_SMOKE_GRENADE ) { + if ( pm->cmd.serverTime - pm->ps->classWeaponTime < ( pm->ltChargeTime * 0.5f ) ) { + return; + } + } + } + // jpw + + // check for fire + // if not on fire button and there's not a delayed shot this frame... + if ( !( pm->cmd.buttons & ( BUTTON_ATTACK | WBUTTON_ATTACK2 ) ) && !delayedFire ) { + pm->ps->weaponTime = 0; + pm->ps->weaponDelay = 0; + + if ( weaponstateFiring ) { // you were just firing, time to relax + PM_ContinueWeaponAnim( WEAP_IDLE1 ); + } + + pm->ps->weaponstate = WEAPON_READY; + return; + } + + // jpw + // player is leaning - no fire + if ( pm->ps->leanf != 0 && pm->ps->weapon != WP_GRENADE_LAUNCHER && pm->ps->weapon != WP_GRENADE_PINEAPPLE ) { + return; + } + + // player is zooming - no fire + // JPW NERVE in MP, LT needs to zoom to call artillery + if ( pm->ps->eFlags & EF_ZOOMING ) { +#ifdef GAMEDLL + if ( pm->gametype != GT_SINGLE_PLAYER ) { + pm->ps->weaponTime += 500; + PM_AddEvent( EV_FIRE_WEAPON ); + } +#endif + return; + } + + // player is underwater - no fire + if ( pm->waterlevel == 3 ) { + if ( pm->ps->weapon != WP_KNIFE && + pm->ps->weapon != WP_KNIFE2 && + pm->ps->weapon != WP_GRENADE_LAUNCHER && + pm->ps->weapon != WP_GRENADE_PINEAPPLE && + pm->ps->weapon != WP_DYNAMITE && + pm->ps->weapon != WP_DYNAMITE2 ) { + PM_AddEvent( EV_NOFIRE_UNDERWATER ); // event for underwater 'click' for nofire + pm->ps->weaponTime = 500; + return; + } + } + + // start the animation even if out of ammo + switch ( pm->ps->weapon ) + { + default: + if ( !weaponstateFiring ) { + // delay so the weapon can get up into position before firing (and showing the flash) + pm->ps->weaponDelay = ammoTable[pm->ps->weapon].fireDelayTime; + } else { + BG_AnimScriptEvent( pm->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue ); + } + break; + // machineguns should continue the anim, rather than start each fire + case WP_MP40: + case WP_THOMPSON: + case WP_STEN: + case WP_VENOM: + case WP_BAR: //----(SA) added + case WP_BAR2: //----(SA) added + case WP_FG42: + case WP_FG42SCOPE: + case WP_MEDKIT: // NERVE - SMF + case WP_PLIERS: // NERVE - SMF + case WP_SMOKE_GRENADE: // NERVE - SMF + if ( !weaponstateFiring ) { + if ( pm->ps->aiChar == AICHAR_PROTOSOLDIER && pm->ps->weapon == WP_VENOM ) { + // proto gets fast spin-up + pm->ps->weaponDelay = 150; + } else { + // delay so the weapon can get up into position before firing (and showing the flash) + pm->ps->weaponDelay = ammoTable[pm->ps->weapon].fireDelayTime; + } + } else { + BG_AnimScriptEvent( pm->ps, ANIM_ET_FIREWEAPON, qtrue, qtrue ); + } + break; + case WP_PANZERFAUST: + case WP_ROCKET_LAUNCHER: + case WP_CROSS: + case WP_SILENCER: + case WP_LUGER: + case WP_COLT: + case WP_AKIMBO: //----(SA) added + case WP_SNIPERRIFLE: + case WP_SNOOPERSCOPE: + case WP_MAUSER: + case WP_GARAND: + case WP_VENOM_FULL: + if ( !weaponstateFiring ) { + // JPW NERVE -- pfaust has spinup time in MP + if ( pm->gametype != GT_SINGLE_PLAYER ) { + if ( pm->ps->weapon == WP_PANZERFAUST ) { + PM_AddEvent( EV_SPINUP ); + } + } + // jpw + + pm->ps->weaponDelay = ammoTable[pm->ps->weapon].fireDelayTime; + } else { + BG_AnimScriptEvent( pm->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue ); + } + break; + + // melee + case WP_KNIFE: + case WP_KNIFE2: + if ( !delayedFire ) { + BG_AnimScriptEvent( pm->ps, ANIM_ET_FIREWEAPON, qfalse, qfalse ); + } + break; + case WP_GAUNTLET: + if ( !delayedFire ) { + BG_AnimScriptEvent( pm->ps, ANIM_ET_FIREWEAPON, qfalse, qfalse ); + } + break; + + // throw + case WP_DYNAMITE: + case WP_DYNAMITE2: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + if ( !delayedFire ) { + if ( pm->ps->aiChar ) { // ai characters go into their regular animation setup + BG_AnimScriptEvent( pm->ps, ANIM_ET_FIREWEAPON, qtrue, qtrue ); + } else { // the player pulls the fuse and holds the hot potato + if ( PM_WeaponAmmoAvailable( pm->ps->weapon ) ) { + if ( pm->ps->weapon == WP_DYNAMITE || pm->ps->weapon == WP_DYNAMITE2 ) { + pm->ps->grenadeTimeLeft = 50; + } else { + pm->ps->grenadeTimeLeft = 4000; // start at four seconds and count down + + } + PM_StartWeaponAnim( WEAP_ATTACK1 ); + } + } + + pm->ps->weaponDelay = ammoTable[pm->ps->weapon].fireDelayTime; + + } + break; + } + + pm->ps->weaponstate = WEAPON_FIRING; + + + + // check for out of ammo + + ammoNeeded = ammoTable[pm->ps->weapon].uses; + + if ( pm->ps->weapon ) { + int ammoAvailable; + qboolean reloading, playswitchsound = qtrue; + + ammoAvailable = PM_WeaponAmmoAvailable( pm->ps->weapon ); + + if ( ammoNeeded > ammoAvailable ) { + // you have ammo for this, just not in the clip + reloading = (qboolean)( ammoNeeded <= pm->ps->ammo[ BG_FindAmmoForWeapon( pm->ps->weapon )] ); + + // if not in auto-reload mode, and reload was not explicitely requested, just play the 'out of ammo' sound + if ( !pm->pmext->bAutoReload && IS_AUTORELOAD_WEAPON( pm->ps->weapon ) && !( pm->cmd.wbuttons & WBUTTON_RELOAD ) ) { + reloading = qfalse; + } + + if ( pm->ps->eFlags & EF_MELEE_ACTIVE ) { // not going to be allowed to reload if holding a chair + reloading = qfalse; + } + + if ( pm->ps->weapon == WP_SNOOPERSCOPE ) { + reloading = qfalse; + } + + switch ( pm->ps->weapon ) { + // Ridah, only play if using a triggered weapon + case WP_GAUNTLET: + case WP_MONSTER_ATTACK1: + case WP_DYNAMITE: + case WP_DYNAMITE2: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + playswitchsound = qfalse; + break; + + // some weapons not allowed to reload. must switch back to primary first + case WP_SNOOPERSCOPE: + case WP_SNIPERRIFLE: + case WP_FG42SCOPE: + reloading = qfalse; + break; + } + + if ( playswitchsound ) { + if ( reloading ) { + PM_AddEvent( EV_EMPTYCLIP ); + } else { + PM_AddEvent( EV_NOAMMO ); + } + } + + if ( reloading ) { + PM_ContinueWeaponAnim( WEAP_RELOAD1 ); //----(SA) + } else { + PM_ContinueWeaponAnim( WEAP_IDLE1 ); + pm->ps->weaponTime += 500; + } + + return; + } + } + + if ( pm->ps->weaponDelay > 0 ) { + // if it hits here, the 'fire' has just been hit and the weapon dictated a delay. + // animations have been started, weaponstate has been set, but no weapon events yet. (except possibly EV_NOAMMO) + // checks for delayed weapons that have already been fired are return'ed above. + return; + } + + + // take an ammo away if not infinite + if ( PM_WeaponAmmoAvailable( pm->ps->weapon ) != -1 ) { + // Rafael - check for being mounted on mg42 + if ( !( pm->ps->persistant[PERS_HWEAPON_USE] ) ) { + PM_WeaponUseAmmo( pm->ps->weapon, ammoNeeded ); + } + } + + + // fire weapon + + // add weapon heat + if ( ammoTable[pm->ps->weapon].maxHeat ) { + pm->ps->weapHeat[pm->ps->weapon] += ammoTable[pm->ps->weapon].nextShotTime; + } + + // first person weapon animations + + // if this was the last round in the clip, play the 'lastshot' animation + // this animation has the weapon in a "ready to reload" state + if ( pm->ps->weapon == WP_AKIMBO ) { + if ( BG_AkimboFireSequence( pm->ps ) ) { + weapattackanim = WEAP_ATTACK2; + } else { + weapattackanim = WEAP_ATTACK1; + } + } else { + if ( PM_WeaponClipEmpty( pm->ps->weapon ) ) { + weapattackanim = WEAP_ATTACK_LASTSHOT; + } else { + weapattackanim = WEAP_ATTACK1; + } + } + + switch ( pm->ps->weapon ) { + case WP_MAUSER: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_DYNAMITE: + case WP_DYNAMITE2: + PM_StartWeaponAnim( weapattackanim ); + break; + + case WP_VENOM: + case WP_MP40: + case WP_THOMPSON: + case WP_STEN: + case WP_MEDKIT: + case WP_PLIERS: + case WP_SMOKE_GRENADE: + PM_ContinueWeaponAnim( weapattackanim ); + break; + + default: + // RF, testing +// PM_ContinueWeaponAnim(weapattackanim); + PM_StartWeaponAnim( weapattackanim ); + break; + } + + // JPW NERVE -- in multiplayer, pfaust fires once then switches to pistol since it's useless for a while + if ( pm->gametype != GT_SINGLE_PLAYER ) { + if ( ( pm->ps->weapon == WP_PANZERFAUST ) || ( pm->ps->weapon == WP_SMOKE_GRENADE ) || ( pm->ps->weapon == WP_DYNAMITE ) ) { + PM_AddEvent( EV_NOAMMO ); + } + } + // jpw + + if ( PM_WeaponClipEmpty( pm->ps->weapon ) ) { + PM_AddEvent( EV_FIRE_WEAPON_LASTSHOT ); + } else { + PM_AddEvent( EV_FIRE_WEAPON ); + } + + // RF + pm->ps->releasedFire = qfalse; + pm->ps->lastFireTime = pm->cmd.serverTime; + + + aimSpreadScaleAdd = 0; + + switch ( pm->ps->weapon ) { + case WP_KNIFE: + case WP_KNIFE2: + case WP_SPEARGUN_CO2: + case WP_SPEARGUN: + case WP_PANZERFAUST: + case WP_ROCKET_LAUNCHER: + case WP_DYNAMITE: + case WP_DYNAMITE2: + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_PROX: + case WP_FLAMETHROWER: + case WP_CROSS: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + break; + + case WP_LUGER: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 35; + break; + + case WP_COLT: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 20; + break; + +//----(SA) added + case WP_AKIMBO: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 20; + break; +//----(SA) end + + case WP_MAUSER: + case WP_GARAND: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 50; + break; + + case WP_SNIPERRIFLE: // (SA) not so much added per shot. these weapons mostly uses player movement to get out of whack + addTime = ammoTable[pm->ps->weapon].nextShotTime; + + // JPW NERVE crippling the rifle a bit in multiplayer; it's way too + // strong so make it go completely out every time you fire + if ( pm->gametype != GT_SINGLE_PLAYER ) { + // avoid exploiting centerview to go around the spread + pm->pmext->blockCenterViewTime = pm->cmd.serverTime + 1000; + aimSpreadScaleAdd = 100; + } else { + aimSpreadScaleAdd = 20; + } + // jpw + + break; + case WP_SNOOPERSCOPE: + // JPW NERVE crippling the rifle a bit in multiplayer; it's way too strong so + // make it go completely out every time you fire snooper doesn't do one-shot body + // kills, so give it a little less bounce + addTime = ammoTable[pm->ps->weapon].nextShotTime; + + if ( pm->gametype != GT_SINGLE_PLAYER ) { + aimSpreadScaleAdd = 50; +// addTime *= 2; + } else { + aimSpreadScaleAdd = 10; + } + // jpw + + break; + + case WP_BAR: //----(SA) added + case WP_BAR2: //----(SA) added + + case WP_FG42: + case WP_FG42SCOPE: + + case WP_MP40: + case WP_THOMPSON: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 15 + rand() % 10; // (SA) new values for DM + break; + + case WP_STEN: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 15 + rand() % 10; // (SA) new values for DM + break; + + case WP_SILENCER: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 35; + break; + + case WP_VENOM: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 10; + break; + + case WP_VENOM_FULL: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + aimSpreadScaleAdd = 40; + break; +// JPW NERVE + case WP_ARTY: + case WP_MEDIC_SYRINGE: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + break; + case WP_AMMO: + addTime = ammoTable[pm->ps->weapon].nextShotTime; + break; +// jpw + // JPW: engineers disarm bomb "on the fly" (high sample rate) but medics & LTs throw out health pack/smoke grenades slow + // NERVE - SMF + case WP_PLIERS: + addTime = 50; + break; + case WP_MEDKIT: + addTime = 1000; + break; + case WP_SMOKE_GRENADE: + addTime = 1000; + break; + // -NERVE - SMF + case WP_MONSTER_ATTACK1: + switch ( pm->ps->aiChar ) { + case AICHAR_ZOMBIE: + // Zombie spitting blood + addTime = 1000; + break; + default: + break; + } + + default: + case WP_GAUNTLET: + switch ( pm->ps->aiChar ) + { + case AICHAR_LOPER: // delay 'til next attack + case AICHAR_SEALOPER: + addTime = 1000; + break; + default: + addTime = 250; + break; + } + break; + } + + + // check for overheat + + // the weapon can overheat, and it's hot + if ( ammoTable[pm->ps->weapon].maxHeat && pm->ps->weapHeat[pm->ps->weapon] ) { + // it is overheating + if ( pm->ps->weapHeat[pm->ps->weapon] >= ammoTable[pm->ps->weapon].maxHeat ) { + pm->ps->weapHeat[pm->ps->weapon] = ammoTable[pm->ps->weapon].maxHeat; // cap heat to max + PM_AddEvent( EV_WEAP_OVERHEAT ); +// PM_StartWeaponAnim(WEAP_IDLE1); // removed. client handles anim in overheat event + addTime = 2000; // force "heat recovery minimum" to 2 sec right now + } + } + + if ( pm->ps->powerups[PW_HASTE] ) { + addTime /= 1.3; + } + + // add the recoil amount to the aimSpreadScale +// pm->ps->aimSpreadScale += 3.0*aimSpreadScaleAdd; +// if (pm->ps->aimSpreadScale > 255) pm->ps->aimSpreadScale = 255; + pm->ps->aimSpreadScaleFloat += 3.0 * aimSpreadScaleAdd; + if ( pm->ps->aimSpreadScaleFloat > 255 ) { + pm->ps->aimSpreadScaleFloat = 255; + } + pm->ps->aimSpreadScale = (int)( pm->ps->aimSpreadScaleFloat ); + + pm->ps->weaponTime += addTime; + + PM_SwitchIfEmpty(); +} + + +/* +================ +PM_Animate +================ +*/ +#define MYTIMER_SALUTE 1133 // 17 frames, 15 fps +#define MYTIMER_DISMOUNT 667 // 10 frames, 15 fps + +static void PM_Animate( void ) { +/* + if ( pm->cmd.buttons & BUTTON_GESTURE ) { + if ( pm->ps->torsoTimer == 0) { + PM_StartTorsoAnim( BOTH_SALUTE ); + PM_StartLegsAnim( BOTH_SALUTE ); + + pm->ps->torsoTimer = MYTIMER_SALUTE; + pm->ps->legsTimer = MYTIMER_SALUTE; + + if (!pm->ps->aiChar) // Ridah, we'll play a custom sound upon calling the Taunt + PM_AddEvent( EV_TAUNT ); // for playing the sound + } + } +*/ +} + + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) { + // drop misc timing counter + if ( pm->ps->pm_time ) { + if ( pml.msec >= pm->ps->pm_time ) { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } else { + pm->ps->pm_time -= pml.msec; + } + } + + // drop animation counter + if ( pm->ps->legsTimer > 0 ) { + pm->ps->legsTimer -= pml.msec; + if ( pm->ps->legsTimer < 0 ) { + pm->ps->legsTimer = 0; + } + } + + if ( pm->ps->torsoTimer > 0 ) { + pm->ps->torsoTimer -= pml.msec; + if ( pm->ps->torsoTimer < 0 ) { + pm->ps->torsoTimer = 0; + } + } + + // first person weapon counter + if ( pm->ps->weapAnimTimer > 0 ) { + pm->ps->weapAnimTimer -= pml.msec; + if ( pm->ps->weapAnimTimer < 0 ) { + pm->ps->weapAnimTimer = 0; + } + } +} + + + +#define LEAN_MAX 28.0f +#define LEAN_TIME_TO 200.0f // time to get to/from full lean +#define LEAN_TIME_FR 300.0f // time to get to/from full lean + +/* +============== +PM_CalcLean + +============== +*/ +void PM_UpdateLean( playerState_t *ps, usercmd_t *cmd, pmove_t *tpm ) { + vec3_t start, end, tmins, tmaxs, right; + int leaning = 0; // -1 left, 1 right + float leanofs = 0; + vec3_t viewangles; + trace_t trace; + + if ( ps->aiChar ) { + return; + } + + if ( ( cmd->wbuttons & ( WBUTTON_LEANLEFT | WBUTTON_LEANRIGHT ) ) && !cmd->forwardmove && cmd->upmove <= 0 ) { + // if both are pressed, result is no lean + if ( cmd->wbuttons & WBUTTON_LEANLEFT ) { + leaning -= 1; + } + if ( cmd->wbuttons & WBUTTON_LEANRIGHT ) { + leaning += 1; + } + } + + if ( ps->eFlags & EF_MG42_ACTIVE ) { + leaning = 0; // leaning not allowed on mg42 + } + + if ( ps->eFlags & EF_FIRING ) { + leaning = 0; // not allowed to lean while firing + + } + // ATVI Wolfenstein Misc #479 - initial fix to #270 would crash in g_synchronousClients 1 situation + if ( ps->weaponstate == WEAPON_FIRING && ( ps->weapon == WP_DYNAMITE || ps->weapon == WP_DYNAMITE2 ) ) { + leaning = 0; // not allowed while tossing dynamite + + } + leanofs = ps->leanf; + + + if ( !leaning ) { // go back to center position + if ( leanofs > 0 ) { // right + //FIXME: play lean anim backwards? + leanofs -= ( ( (float)pml.msec / (float)LEAN_TIME_FR ) * LEAN_MAX ); + if ( leanofs < 0 ) { + leanofs = 0; + } + } else if ( leanofs < 0 ) { // left + //FIXME: play lean anim backwards? + leanofs += ( ( (float)pml.msec / (float)LEAN_TIME_FR ) * LEAN_MAX ); + if ( leanofs > 0 ) { + leanofs = 0; + } + } + } + + if ( leaning ) { + if ( leaning > 0 ) { // right + if ( leanofs < LEAN_MAX ) { + leanofs += ( ( (float)pml.msec / (float)LEAN_TIME_TO ) * LEAN_MAX ); + } + + if ( leanofs > LEAN_MAX ) { + leanofs = LEAN_MAX; + } + + } else { // left + if ( leanofs > -LEAN_MAX ) { + leanofs -= ( ( (float)pml.msec / (float)LEAN_TIME_TO ) * LEAN_MAX ); + } + + if ( leanofs < -LEAN_MAX ) { + leanofs = -LEAN_MAX; + } + + } + } + + ps->leanf = leanofs; + + if ( leaning ) { + VectorCopy( ps->origin, start ); + start[2] += ps->viewheight; + + VectorCopy( ps->viewangles, viewangles ); + viewangles[ROLL] += leanofs / 2.0f; + AngleVectors( viewangles, NULL, right, NULL ); + VectorMA( start, leanofs, right, end ); + + VectorSet( tmins, -8, -8, -7 ); // ATVI Wolfenstein Misc #472, bumped from -4 to cover gun clipping issue + VectorSet( tmaxs, 8, 8, 4 ); + + if ( pm ) { + pm->trace( &trace, start, tmins, tmaxs, end, ps->clientNum, MASK_PLAYERSOLID ); + } else { + tpm->trace( &trace, start, tmins, tmaxs, end, ps->clientNum, MASK_PLAYERSOLID ); + } + + ps->leanf *= trace.fraction; + } + + + if ( ps->leanf ) { + cmd->rightmove = 0; // also disallowed in cl_input ~391 + + } +} + + + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated isntead of a full move +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, usercmd_t *cmd, void( trace ) ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ) ) { //----(SA) modified + short temp; + int i; + pmove_t tpm; + vec3_t oldViewAngles; + + // DHM - Nerve :: Added support for PMF_TIME_LOCKPLAYER + if ( ps->pm_type == PM_INTERMISSION || ps->pm_flags & PMF_TIME_LOCKPLAYER ) { + return; // no view changes at all + } + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { + + // DHM - Nerve :: Allow players to look around while 'wounded' or lock to a medic if nearby + temp = cmd->angles[1] + ps->delta_angles[1]; + if ( ps->stats[STAT_DEAD_YAW] == 999 ) { + ps->stats[STAT_DEAD_YAW] = SHORT2ANGLE( temp ); + } + return; // no view changes at all + } + + VectorCopy( ps->viewangles, oldViewAngles ); + + // circularly clamp the angles with deltas + for ( i = 0 ; i < 3 ; i++ ) { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) { + ps->delta_angles[i] = 16000 - cmd->angles[i]; + temp = 16000; + } else if ( temp < -16000 ) { + ps->delta_angles[i] = -16000 - cmd->angles[i]; + temp = -16000; + } + } + ps->viewangles[i] = SHORT2ANGLE( temp ); + } + + if ( ps->eFlags & EF_MG42_ACTIVE ) { + float yaw, oldYaw; + float degsSec = MG42_YAWSPEED; + float arcMin, arcMax, arcDiff; + + yaw = ps->viewangles[YAW]; + oldYaw = oldViewAngles[YAW]; + + if ( yaw - oldYaw > 180 ) { + yaw -= 360; + } + if ( yaw - oldYaw < -180 ) { + yaw += 360; + } + + if ( yaw > oldYaw ) { + if ( yaw - oldYaw > degsSec * pml.frametime ) { + ps->viewangles[YAW] = oldYaw + degsSec * pml.frametime; + + // Set delta_angles properly + ps->delta_angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - cmd->angles[YAW]; + } + } else if ( oldYaw > yaw ) { + if ( oldYaw - yaw > degsSec * pml.frametime ) { + ps->viewangles[YAW] = oldYaw - degsSec * pml.frametime; + + // Set delta_angles properly + ps->delta_angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - cmd->angles[YAW]; + } + } + + // limit harc and varc + + // pitch (varc) + arcMax = pm->pmext->varc; + arcMin = pm->pmext->varc / 2; + arcDiff = AngleNormalize180( ps->viewangles[PITCH] - pm->pmext->centerangles[PITCH] ); + + + if ( arcDiff > arcMin ) { + ps->viewangles[PITCH] = AngleNormalize180( pm->pmext->centerangles[PITCH] + arcMin ); + + // Set delta_angles properly + ps->delta_angles[PITCH] = ANGLE2SHORT( ps->viewangles[PITCH] ) - cmd->angles[PITCH]; + } else if ( arcDiff < -arcMax ) { + ps->viewangles[PITCH] = AngleNormalize180( pm->pmext->centerangles[PITCH] - arcMax ); + + // Set delta_angles properly + ps->delta_angles[PITCH] = ANGLE2SHORT( ps->viewangles[PITCH] ) - cmd->angles[PITCH]; + } + + // yaw (harc) + arcMin = arcMax = pm->pmext->harc; + arcDiff = AngleNormalize180( ps->viewangles[YAW] - pm->pmext->centerangles[YAW] ); + + + if ( arcDiff > arcMin ) { + ps->viewangles[YAW] = AngleNormalize180( pm->pmext->centerangles[YAW] + arcMin ); + + // Set delta_angles properly + ps->delta_angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - cmd->angles[YAW]; + } else if ( arcDiff < -arcMax ) { + ps->viewangles[YAW] = AngleNormalize180( pm->pmext->centerangles[YAW] - arcMax ); + + // Set delta_angles properly + ps->delta_angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - cmd->angles[YAW]; + } + } + + tpm.trace = (void *)&trace; +// tpm.trace (&trace, start, tmins, tmaxs, end, ps->clientNum, MASK_PLAYERSOLID); + + PM_UpdateLean( ps, cmd, &tpm ); +} + +/* +================ +PM_CheckLadderMove + + Checks to see if we are on a ladder +================ +*/ +qboolean ladderforward; +vec3_t laddervec; + +void PM_CheckLadderMove( void ) { + vec3_t spot; + vec3_t flatforward; + trace_t trace; + float tracedist; + #define TRACE_LADDER_DIST 48.0 + qboolean wasOnLadder; + + if ( pm->ps->pm_time ) { + return; + } + + //if (pm->ps->pm_flags & PM_DEAD) + // return; + + if ( pml.walking ) { + tracedist = 1.0; + } else { + tracedist = TRACE_LADDER_DIST; + } + + wasOnLadder = ( ( pm->ps->pm_flags & PMF_LADDER ) != 0 ); + + pml.ladder = qfalse; + pm->ps->pm_flags &= ~PMF_LADDER; // clear ladder bit + ladderforward = qfalse; + + /* + if (pm->ps->eFlags & EF_DEAD) { // dead bodies should fall down ladders + return; + } + + if (pm->ps->pm_flags & PM_DEAD && pm->ps->stats[STAT_HEALTH] <= 0) + { + return; + } + */ + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // check for ladder + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize( flatforward ); + + VectorMA( pm->ps->origin, tracedist, flatforward, spot ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, spot, pm->ps->clientNum, pm->tracemask ); + if ( ( trace.fraction < 1 ) && ( trace.surfaceFlags & SURF_LADDER ) ) { + pml.ladder = qtrue; + } +/* + if (!pml.ladder && DotProduct(pm->ps->velocity, pml.forward) < 0) { + // trace along the negative velocity, so we grab onto a ladder if we are trying to reverse onto it from above the ladder + flatforward[0] = -pm->ps->velocity[0]; + flatforward[1] = -pm->ps->velocity[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + VectorMA (pm->ps->origin, tracedist, flatforward, spot); + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, spot, pm->ps->clientNum, pm->tracemask); + if ((trace.fraction < 1) && (trace.surfaceFlags & SURF_LADDER)) + { + pml.ladder = qtrue; + } + } +*/ + if ( pml.ladder ) { + VectorCopy( trace.plane.normal, laddervec ); + } + + if ( pml.ladder && !pml.walking && ( trace.fraction * tracedist > 1.0 ) ) { + vec3_t mins; + // if we are only just on the ladder, don't do this yet, or it may throw us back off the ladder + pml.ladder = qfalse; + VectorCopy( pm->mins, mins ); + mins[2] = -1; + VectorMA( pm->ps->origin, -tracedist, laddervec, spot ); + pm->trace( &trace, pm->ps->origin, mins, pm->maxs, spot, pm->ps->clientNum, pm->tracemask ); + if ( ( trace.fraction < 1 ) && ( trace.surfaceFlags & SURF_LADDER ) ) { + ladderforward = qtrue; + pml.ladder = qtrue; + pm->ps->pm_flags |= PMF_LADDER; // set ladder bit + } else { + pml.ladder = qfalse; + } + } else if ( pml.ladder ) { + pm->ps->pm_flags |= PMF_LADDER; // set ladder bit + } + + // create some up/down velocity if touching ladder + if ( pml.ladder ) { + if ( pml.walking ) { + // we are currently on the ground, only go up and prevent X/Y if we are pushing forwards + if ( pm->cmd.forwardmove <= 0 ) { + pml.ladder = qfalse; + } + } + } + + // if we have just dismounted the ladder at the top, play dismount + if ( !pml.ladder && wasOnLadder && pm->ps->velocity[2] > 0 ) { + BG_AnimScriptEvent( pm->ps, ANIM_ET_CLIMB_DISMOUNT, qfalse, qfalse ); + } + // if we have just mounted the ladder + if ( pml.ladder && !wasOnLadder && pm->ps->velocity[2] < 0 ) { // only play anim if going down ladder + BG_AnimScriptEvent( pm->ps, ANIM_ET_CLIMB_MOUNT, qfalse, qfalse ); + } +} + +/* +============ +PM_LadderMove +============ +*/ +void PM_LadderMove( void ) { + float wishspeed, scale; + vec3_t wishdir, wishvel; + float upscale; + + if ( ladderforward ) { + // move towards the ladder + VectorScale( laddervec, -200.0, wishvel ); + pm->ps->velocity[0] = wishvel[0]; + pm->ps->velocity[1] = wishvel[1]; + } + + upscale = ( pml.forward[2] + 0.5 ) * 2.5; + if ( upscale > 1.0 ) { + upscale = 1.0; + } else if ( upscale < -1.0 ) { + upscale = -1.0; + } + + // forward/right should be horizontal only + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + // move depending on the view, if view is straight forward, then go up + // if view is down more then X degrees, start going down + // if they are back pedalling, then go in reverse of above + scale = PM_CmdScale( &pm->cmd ); + VectorClear( wishvel ); + + if ( pm->cmd.forwardmove ) { + if ( pm->ps->aiChar ) { + wishvel[2] = 0.5 * upscale * scale * (float)pm->cmd.forwardmove; + } else { // player speed + wishvel[2] = 0.9 * upscale * scale * (float)pm->cmd.forwardmove; + } + } +//Com_Printf("wishvel[2] = %i, fwdmove = %i\n", (int)wishvel[2], (int)pm->cmd.forwardmove ); + + if ( pm->cmd.rightmove ) { + // strafe, so we can jump off ladder + vec3_t ladder_right, ang; + vectoangles( laddervec, ang ); + AngleVectors( ang, NULL, ladder_right, NULL ); + + // if we are looking away from the ladder, reverse the right vector + if ( DotProduct( laddervec, pml.forward ) > 0 ) { + VectorInverse( ladder_right ); + } + + VectorMA( wishvel, 0.5 * scale * (float)pm->cmd.rightmove, pml.right, wishvel ); + } + + // do strafe friction + PM_Friction(); + + wishspeed = VectorNormalize2( wishvel, wishdir ); + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + if ( !wishvel[2] ) { + if ( pm->ps->velocity[2] > 0 ) { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + if ( pm->ps->velocity[2] < 0 ) { + pm->ps->velocity[2] = 0; + } + } else + { + pm->ps->velocity[2] += pm->ps->gravity * pml.frametime; + if ( pm->ps->velocity[2] > 0 ) { + pm->ps->velocity[2] = 0; + } + } + } + +//Com_Printf("vel[2] = %i\n", (int)pm->ps->velocity[2] ); + + PM_StepSlideMove( qfalse ); // no gravity while going up ladder + + // always point legs forward + pm->ps->movementDir = 0; +} + + +/* +============== +PM_Sprint +============== +*/ +void PM_Sprint( void ) { + if ( pm->cmd.buttons & BUTTON_SPRINT && ( pm->cmd.forwardmove || pm->cmd.rightmove ) + && !( pm->ps->pm_flags & PMF_DUCKED ) ) { + // take time from powerup before taking it from sprintTime + if ( pm->ps->powerups[PW_NOFATIGUE] ) { + pm->ps->powerups[PW_NOFATIGUE] -= 50; + + // (SA) go ahead and continue to recharge stamina at double + // rate with stamina powerup even when exerting + pm->ps->sprintTime += 10; + if ( pm->ps->sprintTime > 20000 ) { + pm->ps->sprintTime = 20000; + } + + if ( pm->ps->powerups[PW_NOFATIGUE] < 0 ) { + pm->ps->powerups[PW_NOFATIGUE] = 0; + } + } + // JPW NERVE -- sprint time tuned for multiplayer + else if ( pm->gametype != GT_SINGLE_PLAYER ) { + // JPW NERVE adjusted for framerate independence + pm->ps->sprintTime -= 5000 * pml.frametime; + } else { + // *not* adjusted, dunno what they want for tuning values + pm->ps->sprintTime -= 50; + } + // jpw + + if ( pm->ps->sprintTime < 0 ) { + pm->ps->sprintTime = 0; + } + + if ( !pm->ps->sprintExertTime ) { + pm->ps->sprintExertTime = 1; + } + } else + { + // JPW NERVE -- in multiplayer, recharge faster for top 75% of sprint bar + // (for people that *just* use it for jumping, not sprint) this code was + // mucked about with to eliminate client-side framerate-dependancy in wolf single player +#ifdef GAMEDLL + if ( pm->ps->powerups[PW_NOFATIGUE] ) { // (SA) recharge at 2x with stamina powerup + pm->ps->sprintTime += 10; + } else { + if ( pm->gametype != GT_SINGLE_PLAYER ) { + pm->ps->sprintTime += 500 * pml.frametime; // JPW NERVE adjusted for framerate independence + if ( pm->ps->sprintTime > 5000 ) { + pm->ps->sprintTime += 500 * pml.frametime; // JPW NERVE adjusted for framerate independence + } + } else { + pm->ps->sprintTime += 5; + } + // jpw + } +#endif // GAMEDLL + if ( pm->ps->sprintTime > 20000 ) { + pm->ps->sprintTime = 20000; + } + + pm->ps->sprintExertTime = 0; + } +} + +/* +================ +PmoveSingle + +================ +*/ +void trap_SnapVector( float *v ); + +void PmoveSingle( pmove_t *pmove ) { + // Ridah + qboolean isDummy; + + isDummy = ( ( pmove->ps->eFlags & EF_DUMMY_PMOVE ) != 0 ); + // done. + + if ( !isDummy ) { + // RF, update conditional values for anim system + BG_AnimUpdatePlayerStateConditions( pmove ); + } + + pm = pmove; + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; + pm->watertype = 0; + pm->waterlevel = 0; + + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + } + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { + pm->cmd.buttons &= ~BUTTON_WALKING; + } + + // set the talk balloon flag + if ( !isDummy ) { + if ( !pm->ps->aiChar && pm->cmd.buttons & BUTTON_TALK ) { + pm->ps->eFlags |= EF_TALK; + } else { + pm->ps->eFlags &= ~EF_TALK; + } + } + + // set the firing flag for continuous beam weapons + + pm->ps->eFlags &= ~( EF_FIRING | EF_ZOOMING ); + + if ( pm->cmd.wbuttons & WBUTTON_ZOOM && pm->ps->stats[STAT_HEALTH] >= 0 ) { + if ( pm->ps->stats[STAT_KEYS] & ( 1 << INV_BINOCS ) ) { // (SA) binoculars are an inventory item (inventory==keys) + if ( pm->ps->weapon != WP_SNIPERRIFLE && pm->ps->weapon != WP_SNOOPERSCOPE ) { // don't allow binocs if using the sniper scope + if ( !( pm->ps->eFlags & EF_MG42_ACTIVE ) ) { // or if mounted on a weapon + pm->ps->eFlags |= EF_ZOOMING; + } + } + + // don't allow binocs if in the middle of throwing grenade + if ( ( pm->ps->weapon == WP_GRENADE_LAUNCHER || pm->ps->weapon == WP_GRENADE_PINEAPPLE || pm->ps->weapon == WP_DYNAMITE || pm->ps->weapon == WP_DYNAMITE2 ) && pm->ps->grenadeTimeLeft > 0 ) { + pm->ps->eFlags &= ~EF_ZOOMING; + } + } + } + + + if ( !( pm->ps->pm_flags & PMF_RESPAWNED ) && + ( pm->ps->pm_type != PM_INTERMISSION ) ) { + + // check for ammo + if ( PM_WeaponAmmoAvailable( pm->ps->weapon ) ) { + // check if zooming + // DHM - Nerve :: Let's use the same flag we just checked above, Ok? + if ( !( pm->ps->eFlags & EF_ZOOMING ) ) { + if ( !pm->ps->leanf ) { + if ( pm->ps->weaponstate == WEAPON_READY || pm->ps->weaponstate == WEAPON_FIRING ) { + + // all clear, fire! + if ( pm->cmd.buttons & BUTTON_ATTACK && !( pm->cmd.buttons & BUTTON_TALK ) ) { + pm->ps->eFlags |= EF_FIRING; + } + } + } + } + } + } + + + // clear the respawned flag if attack and use are cleared + if ( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) ) { + pm->ps->pm_flags &= ~PMF_RESPAWNED; + } + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if ( pmove->cmd.buttons & BUTTON_TALK ) { + // keep the talk button set tho for when the cmd.serverTime > 66 msec + // and the same cmd is used multiple times in Pmove + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.wbuttons = 0; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + } + + // clear all pmove local vars + memset( &pml, 0, sizeof( pml ) ); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + if ( !isDummy ) { + if ( pml.msec < 1 ) { + pml.msec = 1; + } else if ( pml.msec > 200 ) { + pml.msec = 200; + } + } + pm->ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy( pm->ps->origin, pml.previous_origin ); + + // save old velocity for crashlanding + VectorCopy( pm->ps->velocity, pml.previous_velocity ); + + pml.frametime = pml.msec * 0.001; + + // update the viewangles + // Ridah + if ( !isDummy ) { + // done. + if ( !( pm->ps->pm_flags & PMF_LIMBO ) ) { // JPW NERVE + PM_UpdateViewAngles( pm->ps, &pm->cmd, pm->trace ); //----(SA) modified + + } + } + AngleVectors( pm->ps->viewangles, pml.forward, pml.right, pml.up ); + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + pm->ps->pm_flags &= ~PMF_JUMP_HELD; + } + + // decide if backpedaling animations should be used + if ( pm->cmd.forwardmove < 0 ) { + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { + pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; + } + + if ( pm->ps->pm_type >= PM_DEAD || pm->ps->pm_flags & ( PMF_LIMBO | PMF_TIME_LOCKPLAYER ) ) { // DHM - Nerve + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + if ( pm->ps->pm_type == PM_SPECTATOR ) { + PM_CheckDuck(); + PM_FlyMove(); + PM_DropTimers(); + return; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) { + PM_NoclipMove(); + PM_DropTimers(); + return; + } + + if ( pm->ps->pm_type == PM_FREEZE ) { + return; // no movement at all + } + + if ( pm->ps->pm_type == PM_INTERMISSION ) { + return; // no movement at all + } + + // set watertype, and waterlevel + PM_SetWaterLevel(); + pml.previous_waterlevel = pmove->waterlevel; + + // set mins, maxs, and viewheight + PM_CheckDuck(); + + // set groundentity + PM_GroundTrace(); + + if ( pm->ps->pm_type == PM_DEAD ) { + PM_DeadMove(); + } + + // Ridah, ladders + PM_CheckLadderMove(); + + if ( !isDummy ) { + PM_DropTimers(); + } + + if ( pml.ladder ) { + PM_LadderMove(); + } else if ( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) { + PM_WaterJumpMove(); + } else if ( pm->waterlevel > 1 ) { + // swimming + PM_WaterMove(); + } else if ( pml.walking ) { + // walking on ground + PM_WalkMove(); + } else { + // airborne + PM_AirMove(); + } + + + PM_Sprint(); + + + // Ridah + if ( !isDummy ) { + // done. + PM_Animate(); + } + + // set groundentity, watertype, and waterlevel + PM_GroundTrace(); + PM_SetWaterLevel(); + + // Ridah + if ( !isDummy ) { + // done. + + // weapons + PM_Weapon(); + + // footstep events / legs animations + PM_Footsteps(); + + // entering / leaving water splashes + PM_WaterEvents(); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity ); +// SnapVector( pm->ps->velocity ); + + // Ridah + } + // done. +} + + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +int Pmove( pmove_t *pmove ) { + int finalTime; + + // Ridah + if ( pmove->ps->eFlags & EF_DUMMY_PMOVE ) { + PmoveSingle( pmove ); + return ( 0 ); + } + // done. + + finalTime = pmove->cmd.serverTime; + + if ( finalTime < pmove->ps->commandTime ) { + return ( 0 ); // should not happen + } + + if ( finalTime > pmove->ps->commandTime + 1000 ) { + pmove->ps->commandTime = finalTime - 1000; + } + + pmove->ps->pmove_framecount = ( pmove->ps->pmove_framecount + 1 ) & ( ( 1 << PS_PMOVEFRAMECOUNTBITS ) - 1 ); + + // RF + pm = pmove; + PM_AdjustAimSpreadScale(); + +// startedTorsoAnim = -1; +// startedLegAnim = -1; + + // chop the move up if it is too long, to prevent framerate + // dependent behavior + while ( pmove->ps->commandTime != finalTime ) { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if ( pmove->pmove_fixed ) { + if ( msec > pmove->pmove_msec ) { + msec = pmove->pmove_msec; + } + } else { + if ( msec > 66 ) { + msec = 66; + } + } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + PmoveSingle( pmove ); + + if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { + pmove->cmd.upmove = 20; + } + } + + //PM_CheckStuck(); + + if ( ( pm->ps->stats[STAT_HEALTH] <= 0 || pm->ps->pm_type == PM_DEAD ) && pml.groundTrace.surfaceFlags & SURF_MONSTERSLICK ) { + return ( pml.groundTrace.surfaceFlags ); + } else { + return ( 0 ); + } + +} diff --git a/src/game/bg_public.h b/src/game/bg_public.h new file mode 100644 index 0000000..8a5d211 --- /dev/null +++ b/src/game/bg_public.h @@ -0,0 +1,1713 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: bg_public.h + * + * desc: definitions shared by both the server game and client game modules + * +*/ + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame + +#define GAME_VERSION "RTCW-MP" + +#define DEFAULT_GRAVITY 800 +#define FORCE_LIMBO_HEALTH -150 // JPW NERVE +#define GIB_HEALTH -175 // JPW NERVE +#define ARMOR_PROTECTION 0.66 + +#define MAX_ITEMS 256 + +#define RANK_TIED_FLAG 0x4000 + +#define DEFAULT_SHOTGUN_SPREAD 700 +#define DEFAULT_SHOTGUN_COUNT 11 + +//#define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection +#define ITEM_RADIUS 10 // Rafael changed the radius so that the items would fit in the 3 new containers + +// RF, zombie getup +#define TIMER_RESPAWN ( 38 * ( 1000 / 15 ) + 100 ) + +#define LIGHTNING_RANGE 600 +#define TESLA_RANGE 800 + +#define FLAMETHROWER_RANGE 2500 // DHM - Nerve :: multiplayer range, was 850 in SP + +#define ZOMBIE_FLAME_RADIUS 300 + +// RF, AI effects +#define PORTAL_ZOMBIE_SPAWNTIME 3000 +#define PORTAL_FEMZOMBIE_SPAWNTIME 3000 + +#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present + +#define VOTE_TIME 30000 // 30 seconds before vote times out + +// Ridah, disabled these +//#define MINS_Z -24 +//#define DEFAULT_VIEWHEIGHT 26 +//#define CROUCH_VIEWHEIGHT 12 +// done. + +// Rafael +// note to self: Corky test +//#define DEFAULT_VIEWHEIGHT 26 +//#define CROUCH_VIEWHEIGHT 12 +#define DEFAULT_VIEWHEIGHT 40 +#define CROUCH_VIEWHEIGHT 16 +#define DEAD_VIEWHEIGHT -16 + +#define DEFAULT_MODEL "multi" +#define DEFAULT_HEAD "default" // technically the default head skin. this means "head_default.skin" for the head + +// RF, on fire effects +#define FIRE_FLASH_TIME 2000 +#define FIRE_FLASH_FADEIN_TIME 1000 + +#define LIGHTNING_FLASH_TIME 150 + +#define MG42_SPREAD_MP 100 +#define MG42_RATE_OF_FIRE 100 // DHM - Nerve :: delay between firings // JPW NERVE was 150 +#define MG42_YAWSPEED 300.f // degrees per second + +// RF, client damage identifiers +typedef enum { + CLDMG_SPIRIT, + CLDMG_FLAMETHROWER, + CLDMG_TESLA, + CLDMG_BOSS1LIGHTNING, + CLDMG_MAX +} clientDamage_t; + +// RF +#define MAX_TAGCONNECTS 32 + +// (SA) zoom sway values +#define ZOOM_PITCH_AMPLITUDE 0.13f +#define ZOOM_PITCH_FREQUENCY 0.24f +#define ZOOM_PITCH_MIN_AMPLITUDE 0.1f // minimum amount of sway even if completely settled on target + +#define ZOOM_YAW_AMPLITUDE 0.7f +#define ZOOM_YAW_FREQUENCY 0.12f +#define ZOOM_YAW_MIN_AMPLITUDE 0.2f + +// DHM - Nerve +#define MAX_OBJECTIVES 6 +#define MAX_OID_TRIGGERS 16 +// dhm + +// +// config strings are a general means of communicating variable length strings +// from the server to all connected clients. +// + +// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h +#define CS_MUSIC 2 +#define CS_MESSAGE 3 // from the map worldspawn's message field +#define CS_MOTD 4 // g_motd string for server message of the day +#define CS_WARMUP 5 // server time when the match will be restarted +#define CS_SCORES1 6 +#define CS_SCORES2 7 +#define CS_VOTE_TIME 8 +#define CS_VOTE_STRING 9 +#define CS_VOTE_YES 10 +#define CS_VOTE_NO 11 +#define CS_GAME_VERSION 12 +#define CS_LEVEL_START_TIME 13 // so the timer only shows the current level +#define CS_INTERMISSION 14 // when 1, intermission will start in a second or two +// DHM - Nerve :: Wolf Multiplayer information +#define CS_MULTI_INFO 15 +#define CS_MULTI_MAPWINNER 16 +#define CS_MULTI_MAPDESC 17 +#define CS_MULTI_OBJECTIVE1 18 +#define CS_MULTI_OBJECTIVE2 19 +#define CS_MULTI_OBJECTIVE3 20 +#define CS_MULTI_OBJECTIVE4 21 +#define CS_MULTI_OBJECTIVE5 22 +#define CS_MULTI_OBJECTIVE6 23 + +#define CS_MULTI_OBJ1_STATUS 24 +#define CS_MULTI_OBJ2_STATUS 25 +#define CS_MULTI_OBJ3_STATUS 26 +#define CS_MULTI_OBJ4_STATUS 27 +#define CS_MULTI_OBJ5_STATUS 28 +#define CS_MULTI_OBJ6_STATUS 29 + +// dhm +#define CS_SHADERSTATE 30 +#define CS_ITEMS 31 // string of 0's and 1's that tell which items are present + +#define CS_SCREENFADE 32 // Ridah, used to tell clients to fade their screen to black/normal +#define CS_FOGVARS 33 //----(SA) used for saving the current state/settings of the fog +#define CS_SKYBOXORG 34 // this is where we should view the skybox from +#define CS_TARGETEFFECT 35 //----(SA) + +#define CS_WOLFINFO 36 // NERVE - SMF + +#define CS_MODELS 64 +#define CS_SOUNDS ( CS_MODELS + MAX_MODELS ) +#define CS_PLAYERS ( CS_SOUNDS + MAX_SOUNDS ) +#define CS_LOCATIONS ( CS_PLAYERS + MAX_CLIENTS ) +#define CS_PARTICLES ( CS_LOCATIONS + MAX_LOCATIONS ) +// JPW NERVE -- for spawnpoint selection +#define CS_MULTI_SPAWNTARGETS ( CS_PARTICLES + MAX_PARTICLES_AREAS ) +#define CS_OID_TRIGGERS ( CS_MULTI_SPAWNTARGETS + MAX_MULTI_SPAWNTARGETS ) +// jpw +#define CS_DLIGHTS ( CS_OID_TRIGGERS + MAX_OID_TRIGGERS ) +#define CS_CLIPBOARDS ( CS_DLIGHTS + MAX_DLIGHT_CONFIGSTRINGS ) +#define CS_SPLINES ( CS_CLIPBOARDS + MAX_CLIPBOARD_CONFIGSTRINGS ) +#define CS_TAGCONNECTS ( CS_SPLINES + MAX_SPLINE_CONFIGSTRINGS ) + +#define CS_MAX ( CS_TAGCONNECTS + MAX_TAGCONNECTS ) + +#if ( CS_MAX ) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum { + GT_FFA, // free for all + GT_TOURNAMENT, // one on one tournament + GT_SINGLE_PLAYER, // single player tournament + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_CTF, // capture the flag + GT_WOLF, // DHM - Nerve :: Wolfenstein Multiplayer + GT_WOLF_STOPWATCH, // NERVE - SMF - stopwatch gametype + GT_WOLF_CP, // NERVE - SMF - checkpoint gametype + GT_WOLF_CPH, // JPW NERVE - Capture & Hold gametype + GT_MAX_GAME_TYPE +} gametype_t; + +// Rafael gameskill +typedef enum { + GSKILL_EASY = 1, + GSKILL_MEDIUM, + GSKILL_MEDIUMHARD, // normal default level + GSKILL_HARD, + GSKILL_VERYHARD, + GSKILL_MAX // must always be last +} gameskill_t; + +typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + +typedef enum { + PM_NORMAL, // can accelerate and turn + PM_NOCLIP, // noclip movement + PM_SPECTATOR, // still run into walls + PM_DEAD, // no acceleration or turning, but free falling + PM_FREEZE, // stuck in place with no control + PM_INTERMISSION // no movement or status bar +} pmtype_t; + +typedef enum { + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_READYING, // getting from 'ready' to 'firing' + WEAPON_RELAXING, // weapon is ready, but since not firing, it's on it's way to a "relaxed" stance + WEAPON_VENOM_REST, + WEAPON_FIRING, + WEAPON_FIRINGALT, + WEAPON_RELOADING //----(SA) added +} weaponstate_t; + +// pmove->pm_flags (sent as max 16 bits in msg.c) +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_LADDER 4 // player is on a ladder +#define PMF_BACKWARDS_JUMP 8 // go into backwards land +#define PMF_BACKWARDS_RUN 16 // coast down to backwards run +#define PMF_TIME_LAND 32 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump +#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD 1024 +#define PMF_GRAPPLE_PULL 2048 // pull towards grapple location +#define PMF_FOLLOW 4096 // spectate following another player +#define PMF_SCOREBOARD 8192 // spectate as a scoreboard +#define PMF_LIMBO 16384 // JPW NERVE limbo state, pm_time is time until reinforce +#define PMF_TIME_LOCKPLAYER 32768 // DHM - Nerve :: Lock all movement and view changes + +#define PMF_ALL_TIMES ( PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_KNOCKBACK | PMF_TIME_LOCKPLAYER ) + +typedef struct { + qboolean bAutoReload; // do we predict autoreload of weapons + int blockCenterViewTime; // don't let centerview happen for a little while + + // Arnout: MG42 aiming + float varc, harc; + vec3_t centerangles; + +} pmoveExt_t; // data used both in client and server - store it here +// generally useful for data you want to manipulate in bg_* and cgame, or bg_* and game +// instead of playerstate to prevent different engine versions of playerstate between XP and MP + +#define MAXTOUCH 32 +typedef struct { + // state (in / out) + playerState_t *ps; + pmoveExt_t *pmext; + + // command (in) + usercmd_t cmd, oldcmd; + int tracemask; // collide against these types of surfaces + int debugLevel; // if set, diagnostic output will be printed + qboolean noFootsteps; // if the game is setup for no footsteps by the server + qboolean noWeapClips; // if the game is setup for no weapon clips by the server + qboolean gauntletHit; // true if a gauntlet attack would actually hit something + + // NERVE - SMF (in) + int gametype; + int ltChargeTime; + int soldierChargeTime; + int engineerChargeTime; + int medicChargeTime; + // -NERVE - SMF + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + float xyspeed; + + // for fixed msec Pmove + int pmove_fixed; + int pmove_msec; + + // callbacks to test the world + // these will be different functions during game and cgame + void ( *trace )( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); + int ( *pointcontents )( const vec3_t point, int passEntityNum ); +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t * ps, usercmd_t * cmd, void( trace ) ( trace_t * results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ) ); +int Pmove( pmove_t *pmove ); + +//=================================================================================== + +// JPW NERVE +#define PC_SOLDIER 0 // shoot stuff +#define PC_MEDIC 1 // heal stuff +#define PC_ENGINEER 2 // build stuff +#define PC_LT 3 // bomb stuff +#define PC_MEDIC_CHARGETIME 30000 // FIXME just for testing, this will change to server cvars for each class +// jpw + +// player_state->stats[] indexes +typedef enum { + STAT_HEALTH, + STAT_HOLDABLE_ITEM, +// STAT_WEAPONS, // 16 bit fields + STAT_ARMOR, +//----(SA) Keys for Wolf + STAT_KEYS, // 16 bit fields +//----(SA) end + STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) + STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) + STAT_MAX_HEALTH, // health / armor limit, changable by handicap + STAT_PLAYER_CLASS, // DHM - Nerve :: player class in multiplayer + STAT_CAPTUREHOLD_RED, // JPW NERVE - red team score + STAT_CAPTUREHOLD_BLUE // JPW NERVE - blue team score +} statIndex_t; + + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +typedef enum { + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_HITS, // total points damage inflicted so damage beeps can sound on change + PERS_RANK, + PERS_TEAM, + PERS_SPAWN_COUNT, // incremented every respawn + PERS_REWARD_COUNT, // incremented for each reward sound + PERS_REWARD, // a reward_t + PERS_ATTACKER, // clientnum of last damage inflicter + PERS_KILLED, // count of the number of times you died + // these were added for single player awards tracking + PERS_RESPAWNS_LEFT, // DHM - Nerve :: number of remaining respawns + + PERS_ACCURACY_SHOTS, + PERS_ACCURACY_HITS, + + // Rafael - mg42 // (SA) I don't understand these here. can someone explain? + PERS_HWEAPON_USE, + // Rafael wolfkick + PERS_WOLFKICK +} persEnum_t; + + +// entityState_t->eFlags +#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD +#define EF_NONSOLID_BMODEL 0x00000002 // bmodel is visible, but not solid +#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes +#define EF_MONSTER_EFFECT 0x00000008 // draw an aiChar dependant effect for this character +#define EF_CAPSULE 0x00000010 // use capsule for collisions +#define EF_CROUCHING 0x00000020 // player is crouching +#define EF_MG42_ACTIVE 0x00000040 // currently using an MG42 +#define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) +#define EF_FIRING 0x00000100 // for lightning gun +#define EF_INHERITSHADER EF_FIRING // some ents will never use EF_FIRING, hijack it for "USESHADER" +#define EF_BOUNCE_HEAVY 0x00000200 // more realistic bounce. not as rubbery as above (currently for c4) +#define EF_SPINNING 0x00000400 // (SA) added for level editor control of spinning pickup items +#define EF_BREATH EF_SPINNING // Characters will not have EF_SPINNING set, hijack for drawing character breath + +#define EF_MELEE_ACTIVE 0x00000800 // (SA) added for client knowledge of melee items held (chair/etc.) +#define EF_TALK 0x00001000 // draw a talk balloon +#define EF_SMOKING EF_MONSTER_EFFECT3 // DHM - Nerve :: ET_GENERAL ents will emit smoke if set // JPW switched to this after my code change +#define EF_CONNECTION 0x00002000 // draw a connection trouble sprite +#define EF_MONSTER_EFFECT2 0x00004000 // show the secondary special effect for this character +#define EF_SMOKINGBLACK EF_MONSTER_EFFECT2 // JPW NERVE -- like EF_SMOKING only darker & bigger +#define EF_HEADSHOT 0x00008000 // last hit to player was head shot +#define EF_MONSTER_EFFECT3 0x00010000 // show the third special effect for this character +#define EF_HEADLOOK 0x00020000 // make the head look around +#define EF_VOTED EF_HEADLOOK // already cast a vote +#define EF_STAND_IDLE2 0x00040000 // when standing, play idle2 instead of the default +#define EF_VIEWING_CAMERA EF_STAND_IDLE2 // NOTE: REMOVE STAND_IDLE2 !! +#define EF_TAGCONNECT 0x00080000 // connected to another entity via tag +#define EF_MOVER_BLOCKED 0x00100000 // mover was blocked dont lerp on the client +#define EF_FORCED_ANGLES 0x00200000 // enforce all body parts to use these angles + +#define EF_ZOOMING 0x00400000 // client is zooming +#define EF_NOSWINGANGLES 0x00800000 // try and keep all parts facing same direction + + +// !! NOTE: only place flags that don't need to go to the client beyond 0x00800000 + +#define EF_DUMMY_PMOVE 0x01000000 +#define EF_BOUNCE 0x04000000 // for missiles +#define EF_BOUNCE_HALF 0x08000000 // for missiles +#define EF_MOVER_STOP 0x10000000 // will push otherwise // (SA) moved down to make space for one more client flag + + +typedef enum { + PW_NONE, + + PW_QUAD, + PW_BATTLESUIT, + PW_HASTE, + PW_INVIS, + PW_REGEN, + PW_FLIGHT, + + // (SA) for Wolf + PW_INVULNERABLE, + PW_FIRE, //----(SA) + PW_ELECTRIC, //----(SA) + PW_BREATHER, //----(SA) + PW_NOFATIGUE, //----(SA) + + PW_REDFLAG, + PW_BLUEFLAG, + PW_BALL, + + PW_NUM_POWERUPS +} powerup_t; + +typedef enum { + //----(SA) These will probably all change to INV_n to get the word 'key' out of the game. + // id and DM don't want references to 'keys' in the game. + // I'll change to 'INV' as the item becomes 'permanent' and not a test item. + KEY_NONE, + KEY_1, // skull + KEY_2, // chalice + KEY_3, // eye + KEY_4, // field radio + KEY_5, // satchel charge + INV_BINOCS, // binoculars + KEY_7, + KEY_8, + KEY_9, + KEY_10, + KEY_11, + KEY_12, + KEY_13, + KEY_14, + KEY_15, + KEY_16, + KEY_NUM_KEYS +} wkey_t; // conflicts with types.h + +typedef enum { + HI_NONE, + +// HI_TELEPORTER, + HI_MEDKIT, + + // new for Wolf + HI_WINE, + HI_SKULL, + HI_WATER, + HI_ELECTRIC, + HI_FIRE, + HI_STAMINA, + HI_BOOK1, //----(SA) added + HI_BOOK2, //----(SA) added + HI_BOOK3, //----(SA) added + HI_11, + HI_12, + HI_13, + HI_14, +// HI_15, // ? + + HI_NUM_HOLDABLE +} holdable_t; + +// Ridah +// +// character presets +typedef enum +{ + AICHAR_NONE, + + AICHAR_SOLDIER, + AICHAR_AMERICAN, + AICHAR_ZOMBIE, + AICHAR_WARZOMBIE, + AICHAR_FEMZOMBIE, + + AICHAR_UNDEAD, + + AICHAR_VENOM, + + AICHAR_LOPER, + AICHAR_SEALOPER, + AICHAR_ELITEGUARD, + + AICHAR_STIMSOLDIER1, // dual machineguns + AICHAR_STIMSOLDIER2, // rocket in left hand + AICHAR_STIMSOLDIER3, // tesla in left hand + + AICHAR_SUPERSOLDIER, + + AICHAR_BLACKGUARD, + AICHAR_PROTOSOLDIER, + AICHAR_REJECTX, + + AICHAR_FROGMAN, + AICHAR_HELGA, + AICHAR_HEINRICH, //----(SA) added + + AICHAR_PARTISAN, + AICHAR_CIVILIAN, + AICHAR_CHIMP, + + NUM_CHARACTERS +} AICharacters_t; +// done. + + + +// NOTE: we can only use up to 15 in the client-server stream +// SA NOTE: should be 31 now (I added 1 bit in msg.c) +typedef enum { + WP_NONE, // 0 + + WP_KNIFE, // 1 + // German weapons + WP_LUGER, // 2 + WP_MP40, // 3 + WP_MAUSER, // 4 + WP_FG42, // 5 + WP_GRENADE_LAUNCHER, // 6 + WP_PANZERFAUST, // 7 + WP_VENOM, // 8 + WP_FLAMETHROWER, // 9 + WP_TESLA, // 10 + WP_SPEARGUN, // 11 + +// weapon keys only go 1-0, so put the alternates above that (since selection will be a double click on the german weapon key) + + // American equivalents + WP_KNIFE2, // 12 + WP_COLT, // 13 equivalent american weapon to german luger + WP_THOMPSON, // 14 equivalent american weapon to german mp40 + WP_GARAND, // 15 equivalent american weapon to german mauser + WP_BAR, // 16 equivalent american weapon to german fg42 + WP_GRENADE_PINEAPPLE, // 17 + WP_ROCKET_LAUNCHER, // 18 equivalent american weapon to german panzerfaust + + // secondary fire weapons + WP_SNIPERRIFLE, // 19 + WP_SNOOPERSCOPE, // 20 + WP_VENOM_FULL, // 21 + WP_SPEARGUN_CO2, // 22 + WP_FG42SCOPE, // 23 fg42 alt fire + WP_BAR2, // 24 + + // more weapons + WP_STEN, // 25 silenced sten sub-machinegun + WP_MEDIC_SYRINGE, // 26 // JPW NERVE -- broken out from CLASS_SPECIAL per Id request + WP_AMMO, // 27 // JPW NERVE likewise + WP_ARTY, // 28 + WP_SILENCER, // 29 // used to be sp5 + WP_AKIMBO, // 30 //----(SA) added + +// jpw + WP_CROSS, // 31 + WP_DYNAMITE, // 32 + WP_DYNAMITE2, // 33 + WP_PROX, // 34 + + WP_MONSTER_ATTACK1, // 35 // generic monster attack, slot 1 + WP_MONSTER_ATTACK2, // 36 // generic monster attack, slot 2 + WP_MONSTER_ATTACK3, // 37 // generic monster attack, slot 2 + + WP_SMOKETRAIL, // 38 + + WP_GAUNTLET, // 39 + + WP_SNIPER, // 40 + WP_MORTAR, // 41 + VERYBIGEXPLOSION, // 42 // explosion effect for airplanes + + // NERVE - SMF - special weapons are here now + WP_MEDKIT, // 43 + WP_PLIERS, // 44 + WP_SMOKE_GRENADE, // 45 + // -NERVE - SMF + WP_BINOCULARS, // 46 + + WP_NUM_WEAPONS // 47 NOTE: this cannot be larger than 64 for AI/player weapons! + +} weapon_t; + +// JPW NERVE moved from cg_weapons (now used in g_active) for drop command, actual array in bg_misc.c +extern int weapBanksMultiPlayer[MAX_WEAP_BANKS_MP][MAX_WEAPS_IN_BANK_MP]; +// jpw + +typedef struct ammotable_s { + int maxammo; // + int uses; // + int maxclip; // + int reloadTime; // + int fireDelayTime; // + int nextShotTime; // +//----(SA) added + int maxHeat; // max active firing time before weapon 'overheats' (at which point the weapon will fail) + int coolRate; // how fast the weapon cools down. (per second) +//----(SA) end + int mod; // means of death +} ammotable_t; + +extern ammotable_t ammoTable[]; // defined in bg_misc.c +extern int weapAlts[]; // defined in bg_misc.c + + +//----(SA) +// for routines that need to check if a WP_ is a given set of weapons +#define WP_FIRST WP_KNIFE +#define WP_BEGINGERMAN WP_KNIFE +#define WP_LASTGERMAN WP_SPEARGUN +#define WP_BEGINAMERICAN WP_KNIFE2 +#define WP_LASTAMERICAN WP_GRENADE_PINEAPPLE +#define WP_BEGINSECONDARY WP_SNIPERRIFLE +#define WP_LASTSECONDARY WP_SPEARGUN_CO2 + +#define WEAPS_ONE_HANDED ( ( 1 << WP_KNIFE ) | ( 1 << WP_KNIFE2 ) | ( 1 << WP_LUGER ) | ( 1 << WP_COLT ) | ( 1 << WP_SILENCER ) | ( 1 << WP_GRENADE_LAUNCHER ) | ( 1 << WP_GRENADE_PINEAPPLE ) ) + +// TTimo +// NOTE: what about WP_MAUSER WP_GARAND WP_VENOM +#define IS_AUTORELOAD_WEAPON( weapon ) ( ( weapon ) == WP_LUGER || ( weapon ) == WP_COLT || ( weapon ) == WP_MP40 \ + || ( weapon ) == WP_THOMPSON || ( weapon ) == WP_STEN ) + +//----(SA) end + +typedef enum { + WPOS_HIGH, + WPOS_LOW, + WPOS_KNIFE, + WPOS_PISTOL, + WPOS_SHOULDER, + WPOS_THROW, + WPOS_NUM_POSITIONS +} pose_t; + +// reward sounds +typedef enum { + REWARD_BAD, + + REWARD_IMPRESSIVE, + REWARD_EXCELLENT, + REWARD_DENIED, + REWARD_GAUNTLET +} reward_t; + + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS ( EV_EVENT_BIT1 | EV_EVENT_BIT2 ) + +typedef enum { + EV_NONE, + EV_FOOTSTEP, + EV_FOOTSTEP_METAL, + EV_FOOTSTEP_WOOD, + EV_FOOTSTEP_GRASS, + EV_FOOTSTEP_GRAVEL, + EV_FOOTSTEP_ROOF, + EV_FOOTSTEP_SNOW, + EV_FOOTSTEP_CARPET, + EV_FOOTSPLASH, + EV_FOOTWADE, + EV_SWIM, + EV_STEP_4, + EV_STEP_8, + EV_STEP_12, + EV_STEP_16, + EV_FALL_SHORT, + EV_FALL_MEDIUM, + EV_FALL_FAR, + EV_FALL_NDIE, + EV_FALL_DMG_10, + EV_FALL_DMG_15, + EV_FALL_DMG_25, + EV_FALL_DMG_50, + EV_JUMP_PAD, // boing sound at origin, jump sound on player + EV_JUMP, + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + EV_ITEM_PICKUP, // normal item pickups are predictable + EV_ITEM_PICKUP_QUIET, // (SA) same, but don't play the default pickup sound as it was specified in the ent + EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone + EV_NOITEM, + EV_NOAMMO, + EV_EMPTYCLIP, + EV_FILL_CLIP, + EV_MG42_FIXED, // JPW NERVE + EV_WEAP_OVERHEAT, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + EV_FIRE_WEAPONB, + EV_FIRE_WEAPON_LASTSHOT, + EV_FIRE_QUICKGREN, // "Quickgrenade" + EV_NOFIRE_UNDERWATER, + EV_FIRE_WEAPON_MG42, + EV_USE_ITEM0, + EV_USE_ITEM1, + EV_USE_ITEM2, + EV_USE_ITEM3, + EV_USE_ITEM4, + EV_USE_ITEM5, + EV_USE_ITEM6, + EV_USE_ITEM7, + EV_USE_ITEM8, + EV_USE_ITEM9, + EV_USE_ITEM10, + EV_USE_ITEM11, + EV_USE_ITEM12, + EV_USE_ITEM13, + EV_USE_ITEM14, + EV_USE_ITEM15, + EV_ITEM_RESPAWN, + EV_ITEM_POP, + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + EV_GLOBAL_CLIENT_SOUND, // DHM - Nerve :: no attenuation, only plays for specified client + EV_BULLET_HIT_FLESH, + EV_BULLET_HIT_WALL, + EV_MISSILE_HIT, + EV_MISSILE_MISS, + EV_RAILTRAIL, + EV_VENOM, + EV_VENOMFULL, + EV_BULLET, // otherEntity is the shooter + EV_LOSE_HAT, //----(SA) + EV_GIB_HEAD, // only blow off the head + EV_PAIN, + EV_CROUCH_PAIN, + EV_DEATH1, + EV_DEATH2, + EV_DEATH3, + EV_OBITUARY, + EV_STOPSTREAMINGSOUND, // JPW NERVE swiped from sherman + EV_POWERUP_QUAD, + EV_POWERUP_BATTLESUIT, + EV_POWERUP_REGEN, + EV_GIB_PLAYER, // gib a previously living player + EV_DEBUG_LINE, + EV_STOPLOOPINGSOUND, + EV_TAUNT, + EV_SMOKE, + EV_SPARKS, + EV_SPARKS_ELECTRIC, + EV_BATS, + EV_BATS_UPDATEPOSITION, + EV_BATS_DEATH, + EV_EXPLODE, // func_explosive + EV_EFFECT, // target_effect + EV_MORTAREFX, // mortar firing +// JPW NERVE + EV_SPINUP, // JPW NERVE panzerfaust preamble + EV_TESTID1, // new particle test + EV_TESTID2, + EV_ENDTEST, +// jpw + EV_SNOW_ON, + EV_SNOW_OFF, + EV_MISSILE_MISS_SMALL, + EV_MISSILE_MISS_LARGE, + EV_WOLFKICK_HIT_FLESH, + EV_WOLFKICK_HIT_WALL, + EV_WOLFKICK_MISS, + EV_SPIT_HIT, + EV_SPIT_MISS, + EV_SHARD, + EV_JUNK, + EV_EMITTER, //----(SA) added // generic particle emitter that uses client-side particle scripts + EV_OILPARTICLES, + EV_OILSLICK, + EV_OILSLICKREMOVE, + EV_MG42EFX, + EV_FLAMEBARREL_BOUNCE, + EV_FLAKGUN1, + EV_FLAKGUN2, + EV_FLAKGUN3, + EV_FLAKGUN4, + EV_EXERT1, + EV_EXERT2, + EV_EXERT3, + EV_SNOWFLURRY, + EV_CONCUSSIVE, + EV_DUST, + EV_RUMBLE_EFX, + EV_GUNSPARKS, + EV_FLAMETHROWER_EFFECT, + EV_SNIPER_SOUND, + EV_POPUP, + EV_POPUPBOOK, + EV_GIVEPAGE, //----(SA) added + EV_MG42BULLET_HIT_FLESH, // Arnout: these two send the seed as well + EV_MG42BULLET_HIT_WALL, + EV_MAX_EVENTS // just added as an 'endcap' + +} entity_event_t; + +// NOTE: this must be synched with the text list below + +// new (10/18/00) +typedef enum { + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEAD1_WATER, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEAD2_WATER, + BOTH_DEATH3, + BOTH_DEAD3, + BOTH_DEAD3_WATER, + + BOTH_CLIMB, +/*10*/ BOTH_CLIMB_DOWN, + BOTH_CLIMB_DISMOUNT, + + BOTH_SALUTE, + + BOTH_PAIN1, // head + BOTH_PAIN2, // chest + BOTH_PAIN3, // groin + BOTH_PAIN4, // right shoulder + BOTH_PAIN5, // left shoulder + BOTH_PAIN6, // right knee + BOTH_PAIN7, // left knee +/*20*/ BOTH_PAIN8, // dazed + + BOTH_GRAB_GRENADE, + + BOTH_ATTACK1, + BOTH_ATTACK2, + BOTH_ATTACK3, + BOTH_ATTACK4, + BOTH_ATTACK5, + + BOTH_EXTRA1, + BOTH_EXTRA2, + BOTH_EXTRA3, +/*30*/ BOTH_EXTRA4, + BOTH_EXTRA5, + BOTH_EXTRA6, + BOTH_EXTRA7, + BOTH_EXTRA8, + BOTH_EXTRA9, + BOTH_EXTRA10, + BOTH_EXTRA11, + BOTH_EXTRA12, + BOTH_EXTRA13, +/*40*/ BOTH_EXTRA14, + BOTH_EXTRA15, + BOTH_EXTRA16, + BOTH_EXTRA17, + BOTH_EXTRA18, + BOTH_EXTRA19, + BOTH_EXTRA20, + + TORSO_GESTURE, + TORSO_GESTURE2, + TORSO_GESTURE3, +/*50*/ TORSO_GESTURE4, + + TORSO_DROP, + + TORSO_RAISE, // (low) + TORSO_ATTACK, + TORSO_STAND, + TORSO_STAND_ALT1, + TORSO_STAND_ALT2, + TORSO_READY, + TORSO_RELAX, + + TORSO_RAISE2, // (high) +/*60*/ TORSO_ATTACK2, + TORSO_STAND2, + TORSO_STAND2_ALT1, + TORSO_STAND2_ALT2, + TORSO_READY2, + TORSO_RELAX2, + + TORSO_RAISE3, // (pistol) + TORSO_ATTACK3, + TORSO_STAND3, + TORSO_STAND3_ALT1, +/*70*/ TORSO_STAND3_ALT2, + TORSO_READY3, + TORSO_RELAX3, + + TORSO_RAISE4, // (shoulder) + TORSO_ATTACK4, + TORSO_STAND4, + TORSO_STAND4_ALT1, + TORSO_STAND4_ALT2, + TORSO_READY4, + TORSO_RELAX4, + +/*80*/ TORSO_RAISE5, // (throw) + TORSO_ATTACK5, + TORSO_ATTACK5B, + TORSO_STAND5, + TORSO_STAND5_ALT1, + TORSO_STAND5_ALT2, + TORSO_READY5, + TORSO_RELAX5, + + TORSO_RELOAD1, // (low) + TORSO_RELOAD2, // (high) +/*90*/ TORSO_RELOAD3, // (pistol) + TORSO_RELOAD4, // (shoulder) + + TORSO_MG42, // firing tripod mounted weapon animation + + TORSO_MOVE, // torso anim to play while moving and not firing (swinging arms type thing) + TORSO_MOVE_ALT, + + TORSO_EXTRA, + TORSO_EXTRA2, + TORSO_EXTRA3, + TORSO_EXTRA4, + TORSO_EXTRA5, +/*100*/ TORSO_EXTRA6, + TORSO_EXTRA7, + TORSO_EXTRA8, + TORSO_EXTRA9, + TORSO_EXTRA10, + + LEGS_WALKCR, + LEGS_WALKCR_BACK, + LEGS_WALK, + LEGS_RUN, + LEGS_BACK, +/*110*/ LEGS_SWIM, + LEGS_SWIM_IDLE, + + LEGS_JUMP, + LEGS_JUMPB, + LEGS_LAND, + + LEGS_IDLE, + LEGS_IDLE_ALT, // LEGS_IDLE2 + LEGS_IDLECR, + + LEGS_TURN, + + LEGS_BOOT, // kicking animation + +/*120*/ LEGS_EXTRA1, + LEGS_EXTRA2, + LEGS_EXTRA3, + LEGS_EXTRA4, + LEGS_EXTRA5, + LEGS_EXTRA6, + LEGS_EXTRA7, + LEGS_EXTRA8, + LEGS_EXTRA9, + LEGS_EXTRA10, + +/*130*/ MAX_ANIMATIONS +} animNumber_t; + +// text represenation for scripting +extern char *animStrings[]; // defined in bg_misc.c +extern char *animStringsOld[]; // defined in bg_misc.c + + +typedef enum { + WEAP_IDLE1, + WEAP_IDLE2, + WEAP_ATTACK1, + WEAP_ATTACK2, + WEAP_ATTACK_LASTSHOT, // used when firing the last round before having an empty clip. + WEAP_DROP, + WEAP_RAISE, + WEAP_RELOAD1, + WEAP_RELOAD2, + WEAP_RELOAD3, + WEAP_ALTSWITCHFROM, // switch from alt fire mode weap (scoped/silencer/etc) + WEAP_ALTSWITCHTO, // switch to alt fire mode weap + MAX_WP_ANIMATIONS +} weapAnimNumber_t; + + +#define ANIMFL_LADDERANIM 0x1 +#define ANIMFL_FIRINGANIM 0x2 + +typedef struct animation_s { + char name[MAX_QPATH]; + int firstFrame; + int numFrames; + int loopFrames; // 0 to numFrames + int frameLerp; // msec between frames + int initialLerp; // msec to get to first frame + int moveSpeed; + int animBlend; // take this long to blend to next anim + // + // derived + // + int duration; + int nameHash; + int flags; + int movetype; +} animation_t; + +// Ridah, head animations +typedef enum { + HEAD_NEUTRAL_CLOSED, + HEAD_NEUTRAL_A, + HEAD_NEUTRAL_O, + HEAD_NEUTRAL_I, + HEAD_NEUTRAL_E, + HEAD_HAPPY_CLOSED, + HEAD_HAPPY_O, + HEAD_HAPPY_I, + HEAD_HAPPY_E, + HEAD_HAPPY_A, + HEAD_ANGRY_CLOSED, + HEAD_ANGRY_O, + HEAD_ANGRY_I, + HEAD_ANGRY_E, + HEAD_ANGRY_A, + + MAX_HEAD_ANIMS +} animHeadNumber_t; + +typedef struct headAnimation_s { + int firstFrame; + int numFrames; +} headAnimation_t; +// done. + +// flip the togglebit every time an animation +// changes so a restart of the same anim can be detected +#define ANIM_TOGGLEBIT ( 1 << ( ANIM_BITS - 1 ) ) + + +typedef enum { + TEAM_FREE, + TEAM_RED, + TEAM_BLUE, + TEAM_SPECTATOR, + + TEAM_NUM_TEAMS +} team_t; + +// Time between location updates +#define TEAM_LOCATION_UPDATE_TIME 1000 + +// How many players on the overlay +#define TEAM_MAXOVERLAY 8 + +// means of death +typedef enum { + MOD_UNKNOWN, + MOD_SHOTGUN, + MOD_GAUNTLET, + MOD_MACHINEGUN, + MOD_GRENADE, + MOD_GRENADE_SPLASH, + MOD_ROCKET, + MOD_ROCKET_SPLASH, + MOD_RAILGUN, + MOD_LIGHTNING, + MOD_BFG, + MOD_BFG_SPLASH, + + // (SA) modified wolf weap mods + MOD_KNIFE, + MOD_KNIFE2, + MOD_KNIFE_STEALTH, + MOD_LUGER, + MOD_COLT, + MOD_MP40, + MOD_THOMPSON, + MOD_STEN, + MOD_MAUSER, + MOD_SNIPERRIFLE, + MOD_GARAND, + MOD_SNOOPERSCOPE, + MOD_SILENCER, //----(SA) + MOD_AKIMBO, //----(SA) + MOD_BAR, //----(SA) + MOD_FG42, + MOD_FG42SCOPE, + MOD_PANZERFAUST, + MOD_ROCKET_LAUNCHER, + MOD_GRENADE_LAUNCHER, + MOD_VENOM, + MOD_VENOM_FULL, + MOD_FLAMETHROWER, + MOD_TESLA, + MOD_SPEARGUN, + MOD_SPEARGUN_CO2, + MOD_GRENADE_PINEAPPLE, + MOD_CROSS, + // end + + MOD_MORTAR, + MOD_MORTAR_SPLASH, + + MOD_KICKED, + MOD_GRABBER, + + MOD_DYNAMITE, + MOD_DYNAMITE_SPLASH, + MOD_AIRSTRIKE, // JPW NERVE + MOD_SYRINGE, // JPW NERVE + MOD_AMMO, // JPW NERVE + MOD_ARTY, // JPW NERVE + + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, + MOD_GRAPPLE, + MOD_EXPLOSIVE, + MOD_POISONGAS, + + // RF, AI attacks + MOD_ZOMBIESPIT, + MOD_ZOMBIESPIT_SPLASH, + MOD_ZOMBIESPIRIT, + MOD_ZOMBIESPIRIT_SPLASH, + + MOD_LOPER_LEAP, + MOD_LOPER_GROUND, + MOD_LOPER_HIT, + +// JPW NERVE multiplayer class-specific MODs + MOD_LT_AMMO, + MOD_LT_AIRSTRIKE, + MOD_ENGINEER, // not sure if we'll use + MOD_MEDIC, // these like this or not +// + MOD_BAT + +} meansOfDeath_t; + + +//--------------------------------------------------------- + +// gitem_t->type +typedef enum { + IT_BAD, + IT_WEAPON, // EFX: rotate + upscale + minlight + + IT_AMMO, // EFX: rotate + IT_ARMOR, // EFX: rotate + minlight + IT_HEALTH, // EFX: static external sphere + rotating internal + IT_POWERUP, // instant on, timer based + // EFX: rotate + external ring that rotates + IT_HOLDABLE, // single use, holdable item + // EFX: rotate + bob + IT_KEY, + IT_TREASURE, // gold bars, etc. things that can be picked up and counted for a tally at end-level + IT_CLIPBOARD, // 'clipboard' used as a general term for 'popup' items where you pick up the item and it pauses and opens a menu + IT_TEAM +} itemType_t; + +#define MAX_ITEM_MODELS 5 +#define MAX_ITEM_ICONS 4 + +// JOSEPH 4-17-00 +typedef struct gitem_s { + char *classname; // spawning name + char *pickup_sound; + char *world_model[MAX_ITEM_MODELS]; + + char *icon; + char *ammoicon; + char *pickup_name; // for printing on pickup + + int quantity; // for ammo how much, or duration of powerup (value not necessary for ammo/health. that value set in gameskillnumber[] below) + itemType_t giType; // IT_* flags + + int giTag; + + int giAmmoIndex; // type of weapon ammo this uses. (ex. WP_MP40 and WP_LUGER share 9mm ammo, so they both have WP_LUGER for giAmmoIndex) + int giClipIndex; // which clip this weapon uses. this allows the sniper rifle to use the same clip as the garand, etc. + + char *precaches; // string of all models and images this item will use + char *sounds; // string of all sounds this item will use + + int gameskillnumber[5]; +} gitem_t; +// END JOSEPH + +// included in both the game dll and the client +extern gitem_t bg_itemlist[]; +extern int bg_numItems; + +gitem_t *BG_FindItem( const char *pickupName ); +gitem_t *BG_FindItemForWeapon( weapon_t weapon ); +gitem_t *BG_FindItemForPowerup( powerup_t pw ); +gitem_t *BG_FindItemForHoldable( holdable_t pw ); +gitem_t *BG_FindItemForAmmo( int weapon ); +gitem_t *BG_FindItemForKey( wkey_t k, int *index ); +weapon_t BG_FindAmmoForWeapon( weapon_t weapon ); +weapon_t BG_FindClipForWeapon( weapon_t weapon ); + +qboolean BG_AkimboFireSequence( playerState_t *ps ); //----(SA) added + +#define ITEM_INDEX( x ) ( ( x ) - bg_itemlist ) + +qboolean BG_CanItemBeGrabbed( const entityState_t *ent, const playerState_t *ps ); + + +// g_dmflags->integer flags +#define DF_NO_FALLING 8 +#define DF_FIXED_FOV 16 +#define DF_NO_FOOTSTEPS 32 +#define DF_NO_WEAPRELOAD 64 + +// content masks +#define MASK_ALL ( -1 ) +#define MASK_SOLID ( CONTENTS_SOLID ) +#define MASK_PLAYERSOLID ( CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY ) +#define MASK_DEADSOLID ( CONTENTS_SOLID | CONTENTS_PLAYERCLIP ) +#define MASK_WATER ( CONTENTS_WATER | CONTENTS_LAVA | CONTENTS_SLIME ) +//#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_OPAQUE ( CONTENTS_SOLID | CONTENTS_LAVA ) //----(SA) modified since slime is no longer deadly +#define MASK_SHOT ( CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_CORPSE ) +#define MASK_MISSILESHOT ( MASK_SHOT | CONTENTS_MISSILECLIP ) + +// +// entityState_t->eType +// +typedef enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER, + ET_BEAM, + ET_PORTAL, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_INVISIBLE, + ET_GRAPPLE, // grapple hooked on wall + ET_CONCUSSIVE_TRIGGER, // JPW NERVE trigger for concussive dust particles + ET_OID_TRIGGER, // DHM - Nerve :: Objective Info Display + ET_EXPLOSIVE_INDICATOR, // NERVE - SMF + + //---- (SA) Wolf + ET_EXPLOSIVE, // brush that will break into smaller bits when damaged + ET_EF_TESLA, + ET_EF_SPOTLIGHT, + ET_EFFECT3, + ET_ALARMBOX, + ET_CORONA, + ET_TRAP, + + ET_GAMEMODEL, // misc_gamemodel. similar to misc_model, but it's a dynamic model so we have LOD + ET_FOOTLOCKER, //----(SA) added + //---- end + + ET_FLAMEBARREL, + ET_FP_PARTS, + + // FIRE PROPS + ET_FIRE_COLUMN, + ET_FIRE_COLUMN_SMOKE, + ET_RAMJET, + + ET_FLAMETHROWER_CHUNK, // DHM - NERVE :: Used in server side collision detection for flamethrower + + ET_EXPLO_PART, + + ET_PROP, + ET_BAT, + + ET_AI_EFFECT, + + ET_CAMERA, + ET_MOVERSCALED, + + ET_CORPSE, // Arnout: dead player + ET_SMOKER, // Arnout: target_smoke entity + + ET_TEMPHEAD, // Gordon: temporary head for clients for bullet traces + + ET_MG42_BARREL, // Arnout: MG42 barrel + + ET_EVENTS // any of the EV_* events can be added freestanding + // by setting eType to ET_EVENTS + eventNum + // this avoids having to set eFlags and eventNum +} entityType_t; + + +// cursorhints (stored in ent->s.dmgFlags since that's only used for players at the moment) +typedef enum { + HINT_NONE, // reserved + HINT_FORCENONE, // reserved + HINT_PLAYER, + HINT_ACTIVATE, + HINT_DOOR, + HINT_DOOR_ROTATING, + HINT_DOOR_LOCKED, + HINT_DOOR_ROTATING_LOCKED, + HINT_MG42, + HINT_BREAKABLE, + HINT_BREAKABLE_DYNAMITE, + HINT_CHAIR, + HINT_ALARM, + HINT_HEALTH, + HINT_TREASURE, + HINT_KNIFE, + HINT_LADDER, + HINT_BUTTON, + HINT_WATER, + HINT_CAUTION, + HINT_DANGER, + HINT_SECRET, + HINT_QUESTION, + HINT_EXCLAMATION, + HINT_CLIPBOARD, + HINT_WEAPON, + HINT_AMMO, + HINT_ARMOR, + HINT_POWERUP, + HINT_HOLDABLE, + HINT_INVENTORY, + HINT_SCENARIC, + HINT_EXIT, + HINT_PLYR_FRIEND, + HINT_PLYR_NEUTRAL, + HINT_PLYR_ENEMY, + HINT_PLYR_UNKNOWN, + HINT_BUILD, // DHM - Nerve + HINT_DISARM, // DHM - Nerve + HINT_REVIVE, // DHM - Nerve + HINT_DYNAMITE, // DHM - Nerve + + HINT_BAD_USER, // invisible user with no target + + HINT_NUM_HINTS +} hintType_t; + + + +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_GetMarkDir( const vec3_t dir, const vec3_t normal, vec3_t out ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); + +//void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); + +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); + +qboolean BG_WeaponInWolfMP( int weapon ); +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); +qboolean BG_PlayerSeesItem( playerState_t *ps, entityState_t *item, int atTime ); + +//----(SA) removed PM_ammoNeeded 11/27/00 +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 64 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 64 +#define MAX_BOTS_TEXT 8192 + +typedef enum { + FOOTSTEP_NORMAL, + FOOTSTEP_BOOT, + FOOTSTEP_FLESH, + FOOTSTEP_MECH, + FOOTSTEP_ENERGY, + FOOTSTEP_METAL, + FOOTSTEP_WOOD, + FOOTSTEP_GRASS, + FOOTSTEP_GRAVEL, + // END JOSEPH + FOOTSTEP_SPLASH, + + FOOTSTEP_ROOF, + FOOTSTEP_SNOW, + FOOTSTEP_CARPET, //----(SA) added + + FOOTSTEP_ELITE_STEP, + FOOTSTEP_ELITE_METAL, + FOOTSTEP_ELITE_ROOF, + FOOTSTEP_ELITE_WOOD, + FOOTSTEP_ELITE_GRAVEL, + + FOOTSTEP_SUPERSOLDIER_METAL, + FOOTSTEP_SUPERSOLDIER_GRASS, + FOOTSTEP_SUPERSOLDIER_GRAVEL, + FOOTSTEP_SUPERSOLDIER_STEP, + FOOTSTEP_SUPERSOLDIER_WOOD, + + FOOTSTEP_LOPER_METAL, + FOOTSTEP_LOPER_STEP, + FOOTSTEP_LOPER_WOOD, + + FOOTSTEP_ZOMBIE_GRAVEL, + FOOTSTEP_ZOMBIE_STEP, + FOOTSTEP_ZOMBIE_WOOD, + + FOOTSTEP_TOTAL +} footstep_t; + +//================================================================== +// New Animation Scripting Defines + +#if defined( __MACOS__ ) //DAJ HOG +#define MAX_ANIMSCRIPT_MODELS 32 //DAJ tried 24 // allocated dynamically, so limit is scalable +#define MAX_ANIMSCRIPT_ITEMS_PER_MODEL 1024 //512 +#define MAX_MODEL_ANIMATIONS 256 // animations per model +#define MAX_ANIMSCRIPT_ANIMCOMMANDS 8 +#define MAX_ANIMSCRIPT_ITEMS 64 +#else +#define MAX_ANIMSCRIPT_MODELS 32 +#define MAX_ANIMSCRIPT_ITEMS_PER_MODEL 2048 +#define MAX_MODEL_ANIMATIONS 512 // animations per model +#define MAX_ANIMSCRIPT_ANIMCOMMANDS 8 +#define MAX_ANIMSCRIPT_ITEMS 128 +#endif +// NOTE: these must all be in sync with string tables in bg_animation.c + +typedef enum +{ + ANIM_MT_UNUSED, + ANIM_MT_IDLE, + ANIM_MT_IDLECR, + ANIM_MT_WALK, + ANIM_MT_WALKBK, + ANIM_MT_WALKCR, + ANIM_MT_WALKCRBK, + ANIM_MT_RUN, + ANIM_MT_RUNBK, + ANIM_MT_SWIM, + ANIM_MT_SWIMBK, + ANIM_MT_STRAFERIGHT, + ANIM_MT_STRAFELEFT, + ANIM_MT_TURNRIGHT, + ANIM_MT_TURNLEFT, + ANIM_MT_CLIMBUP, + ANIM_MT_CLIMBDOWN, + ANIM_MT_FALLEN, // DHM - Nerve :: dead, before limbo + + NUM_ANIM_MOVETYPES +} scriptAnimMoveTypes_t; + +typedef enum +{ + ANIM_ET_PAIN, + ANIM_ET_DEATH, + ANIM_ET_FIREWEAPON, + ANIM_ET_JUMP, + ANIM_ET_JUMPBK, + ANIM_ET_LAND, + ANIM_ET_DROPWEAPON, + ANIM_ET_RAISEWEAPON, + ANIM_ET_CLIMB_MOUNT, + ANIM_ET_CLIMB_DISMOUNT, + ANIM_ET_RELOAD, + ANIM_ET_PICKUPGRENADE, + ANIM_ET_KICKGRENADE, + ANIM_ET_QUERY, + ANIM_ET_INFORM_FRIENDLY_OF_ENEMY, + ANIM_ET_KICK, + ANIM_ET_REVIVE, + ANIM_ET_FIRSTSIGHT, + ANIM_ET_ROLL, + ANIM_ET_FLIP, + ANIM_ET_DIVE, + ANIM_ET_PRONE_TO_CROUCH, + ANIM_ET_BULLETIMPACT, + ANIM_ET_INSPECTSOUND, + ANIM_ET_SECONDLIFE, + + NUM_ANIM_EVENTTYPES +} scriptAnimEventTypes_t; + +typedef enum +{ + ANIM_BP_UNUSED, + ANIM_BP_LEGS, + ANIM_BP_TORSO, + ANIM_BP_BOTH, + + NUM_ANIM_BODYPARTS +} animBodyPart_t; + +typedef enum +{ + ANIM_COND_WEAPON, + ANIM_COND_ENEMY_POSITION, + ANIM_COND_ENEMY_WEAPON, + ANIM_COND_UNDERWATER, + ANIM_COND_MOUNTED, + ANIM_COND_MOVETYPE, + ANIM_COND_UNDERHAND, + ANIM_COND_LEANING, + ANIM_COND_IMPACT_POINT, + ANIM_COND_CROUCHING, + ANIM_COND_STUNNED, + ANIM_COND_FIRING, + ANIM_COND_SHORT_REACTION, + ANIM_COND_ENEMY_TEAM, + ANIM_COND_PARACHUTE, + ANIM_COND_CHARGING, + ANIM_COND_SECONDLIFE, + ANIM_COND_HEALTH_LEVEL, + + NUM_ANIM_CONDITIONS +} scriptAnimConditions_t; + +//------------------------------------------------------------------- + +typedef struct +{ + char *string; + int hash; +} animStringItem_t; + +typedef struct +{ + int index; // reference into the table of possible conditionals + int value[2]; // can store anything from weapon bits, to position enums, etc +} animScriptCondition_t; + +typedef struct +{ + short int bodyPart[2]; // play this animation on legs/torso/both + short int animIndex[2]; // animation index in our list of animations + short int animDuration[2]; + short int soundIndex; +} animScriptCommand_t; + +typedef struct +{ + int numConditions; + animScriptCondition_t conditions[NUM_ANIM_CONDITIONS]; + int numCommands; + animScriptCommand_t commands[MAX_ANIMSCRIPT_ANIMCOMMANDS]; +} animScriptItem_t; + +typedef struct +{ + int numItems; + animScriptItem_t *items[MAX_ANIMSCRIPT_ITEMS]; // pointers into a global list of items +} animScript_t; + +typedef struct +{ + char modelname[MAX_QPATH]; // name of the model + + // parsed from the start of the cfg file + gender_t gender; + footstep_t footsteps; + vec3_t headOffset; + int version; + qboolean isSkeletal; + + // parsed from cfg file + animation_t animations[MAX_MODEL_ANIMATIONS]; // anim names, frame ranges, etc + headAnimation_t headAnims[MAX_HEAD_ANIMS]; + int numAnimations, numHeadAnims; + + // parsed from script file + animScript_t scriptAnims[MAX_AISTATES][NUM_ANIM_MOVETYPES]; // locomotive anims, etc + animScript_t scriptCannedAnims[MAX_AISTATES][NUM_ANIM_MOVETYPES]; // played randomly + animScript_t scriptStateChange[MAX_AISTATES][MAX_AISTATES]; // state change events + animScript_t scriptEvents[NUM_ANIM_EVENTTYPES]; // events that trigger special anims + + // global list of script items for this model + animScriptItem_t scriptItems[MAX_ANIMSCRIPT_ITEMS_PER_MODEL]; + int numScriptItems; + +} animModelInfo_t; + +// this is the main structure that is duplicated on the client and server +typedef struct +{ + int clientModels[MAX_CLIENTS]; // so we know which model each client is using + animModelInfo_t modelInfo[MAX_ANIMSCRIPT_MODELS]; + int clientConditions[MAX_CLIENTS][NUM_ANIM_CONDITIONS][2]; + // + // pointers to functions from the owning module + // + // TTimo: constify the arg + int ( *soundIndex )( const char *name ); + void ( *playSound )( int soundIndex, vec3_t org, int clientNum ); +} animScriptData_t; + +//------------------------------------------------------------------ +// Conditional Constants + +typedef enum +{ + POSITION_UNUSED, + POSITION_BEHIND, + POSITION_INFRONT, + POSITION_RIGHT, + POSITION_LEFT, + + NUM_ANIM_COND_POSITIONS +} animScriptPosition_t; + +typedef enum +{ + MOUNTED_UNUSED, + MOUNTED_MG42, + + NUM_ANIM_COND_MOUNTED +} animScriptMounted_t; + +typedef enum +{ + LEANING_UNUSED, + LEANING_RIGHT, + LEANING_LEFT, + + NUM_ANIM_COND_LEANING +} animScriptLeaning_t; + +typedef enum +{ + IMPACTPOINT_UNUSED, + IMPACTPOINT_HEAD, + IMPACTPOINT_CHEST, + IMPACTPOINT_GUT, + IMPACTPOINT_GROIN, + IMPACTPOINT_SHOULDER_RIGHT, + IMPACTPOINT_SHOULDER_LEFT, + IMPACTPOINT_KNEE_RIGHT, + IMPACTPOINT_KNEE_LEFT, + + NUM_ANIM_COND_IMPACTPOINT +} animScriptImpactPoint_t; + +//------------------------------------------------------------------ +// Global Function Decs + +animModelInfo_t *BG_ModelInfoForModelname( char *modelname ); +qboolean BG_AnimParseAnimConfig( animModelInfo_t *animModelInfo, const char *filename, const char *input ); +void BG_AnimParseAnimScript( animModelInfo_t *modelInfo, animScriptData_t *scriptData, int client, char *filename, char *input ); +int BG_AnimScriptAnimation( playerState_t *ps, aistateEnum_t state, scriptAnimMoveTypes_t movetype, qboolean isContinue ); +int BG_AnimScriptCannedAnimation( playerState_t *ps, aistateEnum_t state ); +int BG_AnimScriptStateChange( playerState_t *ps, aistateEnum_t newState, aistateEnum_t oldState ); +int BG_AnimScriptEvent( playerState_t *ps, scriptAnimEventTypes_t event, qboolean isContinue, qboolean force ); +int BG_IndexForString( char *token, animStringItem_t *strings, qboolean allowFail ); +int BG_PlayAnimName( playerState_t *ps, char *animName, animBodyPart_t bodyPart, qboolean setTimer, qboolean isContinue, qboolean force ); +qboolean BG_ValidAnimScript( int clientNum ); +char *BG_GetAnimString( int client, int anim ); +void BG_UpdateConditionValue( int client, int condition, int value, qboolean checkConversion ); +int BG_GetConditionValue( int client, int condition, qboolean checkConversion ); +int BG_GetAnimScriptAnimation( int client, aistateEnum_t state, scriptAnimMoveTypes_t movetype ); +void BG_AnimUpdatePlayerStateConditions( pmove_t *pmove ); +int BG_AnimationIndexForString( char *string, int client ); +animation_t *BG_AnimationForString( char *string, animModelInfo_t *modelInfo ); +animation_t *BG_GetAnimationForIndex( int client, int index ); +int BG_GetAnimScriptEvent( playerState_t *ps, scriptAnimEventTypes_t event ); + +extern animStringItem_t animStateStr[]; +extern animStringItem_t animBodyPartsStr[]; diff --git a/src/game/bg_slidemove.c b/src/game/bg_slidemove.c new file mode 100644 index 0000000..3b3aaca --- /dev/null +++ b/src/game/bg_slidemove.c @@ -0,0 +1,329 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// bg_slidemove.c -- part of bg_pmove functionality + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +/* + +input: origin, velocity, bounds, groundPlane, trace function + +output: origin, velocity, impacts, stairup boolean + +*/ + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +qboolean PM_SlideMove( qboolean gravity ) { + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + + numbumps = 4; + + VectorCopy( pm->ps->velocity, primal_velocity ); + + if ( gravity ) { + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[2] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; + primal_velocity[2] = endVelocity[2]; + if ( pml.groundPlane ) { + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + } else { + numplanes = 0; + } + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount = 0 ; bumpcount < numbumps ; bumpcount++ ) { + + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask ); + + if ( trace.allsolid ) { + // entity is completely trapped in another solid + pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if ( trace.fraction > 0 ) { + // actually covered some distance + VectorCopy( trace.endpos, pm->ps->origin ); + } + + if ( trace.fraction == 1 ) { + break; // moved the entire distance + } + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if ( numplanes >= MAX_CLIP_PLANES ) { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0 ; i < numplanes ; i++ ) { + if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { + VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + VectorCopy( trace.plane.normal, planes[numplanes] ); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) { + into = DotProduct( pm->ps->velocity, planes[i] ); + if ( into >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) { + pml.impactSpeed = -into; + } + + // slide along the plane + PM_ClipVelocity( pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity( endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) { + if ( j == i ) { + continue; + } + if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + CrossProduct( planes[i], planes[j], dir ); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct( planes[i], planes[j], dir ); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a tripple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravity ) { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) { + VectorCopy( primal_velocity, pm->ps->velocity ); + } + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepSlideMove + +================== +*/ +void PM_StepSlideMove( qboolean gravity ) { + vec3_t start_o, start_v; + vec3_t down_o, down_v; + trace_t trace; +// float down_dist, up_dist; +// vec3_t delta, delta2; + vec3_t up, down; + + VectorCopy( pm->ps->origin, start_o ); + VectorCopy( pm->ps->velocity, start_v ); + + if ( PM_SlideMove( gravity ) == 0 ) { + return; // we got exactly where we wanted to go first try + } + + VectorCopy( start_o, down ); + down[2] -= STEPSIZE; + pm->trace( &trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask ); + VectorSet( up, 0, 0, 1 ); + // never step up when you still have up velocity + if ( pm->ps->velocity[2] > 0 && ( trace.fraction == 1.0 || + DotProduct( trace.plane.normal, up ) < 0.7 ) ) { + return; + } + + VectorCopy( pm->ps->origin, down_o ); + VectorCopy( pm->ps->velocity, down_v ); + + VectorCopy( start_o, up ); + up[2] += STEPSIZE; + + // test the player position if they were a stepheight higher + pm->trace( &trace, up, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask ); + if ( trace.allsolid ) { + if ( pm->debugLevel ) { + Com_Printf( "%i:bend can't step\n", c_pmove ); + } + return; // can't step up + } + + // try slidemove from this position + VectorCopy( up, pm->ps->origin ); + VectorCopy( start_v, pm->ps->velocity ); + + PM_SlideMove( gravity ); + + // push down the final amount + VectorCopy( pm->ps->origin, down ); + down[2] -= STEPSIZE; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid ) { + VectorCopy( trace.endpos, pm->ps->origin ); + } + if ( trace.fraction < 1.0 ) { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + +#if 0 + // if the down trace can trace back to the original position directly, don't step + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask ); + if ( trace.fraction == 1.0 ) { + // use the original move + VectorCopy( down_o, pm->ps->origin ); + VectorCopy( down_v, pm->ps->velocity ); + if ( pm->debugLevel ) { + Com_Printf( "%i:bend\n", c_pmove ); + } + } else +#endif + { + // use the step move + float delta; + + delta = pm->ps->origin[2] - start_o[2]; + if ( delta > 2 ) { + if ( delta < 7 ) { + PM_AddEvent( EV_STEP_4 ); + } else if ( delta < 11 ) { + PM_AddEvent( EV_STEP_8 ); + } else if ( delta < 15 ) { + PM_AddEvent( EV_STEP_12 ); + } else { + PM_AddEvent( EV_STEP_16 ); + } + } + if ( pm->debugLevel ) { + Com_Printf( "%i:stepped\n", c_pmove ); + } + } +} + diff --git a/src/game/botlib.h b/src/game/botlib.h new file mode 100644 index 0000000..70bacc0 --- /dev/null +++ b/src/game/botlib.h @@ -0,0 +1,521 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +/***************************************************************************** + * name: botlib.h + * + * desc: bot AI library + * + * + *****************************************************************************/ + +#define BOTLIB_API_VERSION 2 + +struct aas_clientmove_s; +struct aas_entityinfo_s; +struct bot_consolemessage_s; +struct bot_match_s; +struct bot_goal_s; +struct bot_moveresult_s; +struct bot_initmove_s; +struct weaponinfo_s; + + +//debug line colors +#define LINECOLOR_NONE -1 +#define LINECOLOR_RED 1 //0xf2f2f0f0L +#define LINECOLOR_GREEN 2 //0xd0d1d2d3L +#define LINECOLOR_BLUE 3 //0xf3f3f1f1L +#define LINECOLOR_YELLOW 4 //0xdcdddedfL +#define LINECOLOR_ORANGE 5 //0xe0e1e2e3L + +//Print types +#define PRT_MESSAGE 1 +#define PRT_WARNING 2 +#define PRT_ERROR 3 +#define PRT_FATAL 4 +#define PRT_EXIT 5 + +//console message types +#define CMS_NORMAL 0 +#define CMS_CHAT 1 + +//botlib error codes +#define BLERR_NOERROR 0 //no error +#define BLERR_LIBRARYNOTSETUP 1 //library not setup +#define BLERR_LIBRARYALREADYSETUP 2 //BotSetupLibrary: library already setup +#define BLERR_INVALIDCLIENTNUMBER 3 //invalid client number +#define BLERR_INVALIDENTITYNUMBER 4 //invalid entity number +#define BLERR_NOAASFILE 5 //BotLoadMap: no AAS file available +#define BLERR_CANNOTOPENAASFILE 6 //BotLoadMap: cannot open AAS file +#define BLERR_CANNOTSEEKTOAASFILE 7 //BotLoadMap: cannot seek to AAS file +#define BLERR_CANNOTREADAASHEADER 8 //BotLoadMap: cannot read AAS header +#define BLERR_WRONGAASFILEID 9 //BotLoadMap: incorrect AAS file id +#define BLERR_WRONGAASFILEVERSION 10 //BotLoadMap: incorrect AAS file version +#define BLERR_CANNOTREADAASLUMP 11 //BotLoadMap: cannot read AAS file lump +#define BLERR_NOBSPFILE 12 //BotLoadMap: no BSP file available +#define BLERR_CANNOTOPENBSPFILE 13 //BotLoadMap: cannot open BSP file +#define BLERR_CANNOTSEEKTOBSPFILE 14 //BotLoadMap: cannot seek to BSP file +#define BLERR_CANNOTREADBSPHEADER 15 //BotLoadMap: cannot read BSP header +#define BLERR_WRONGBSPFILEID 16 //BotLoadMap: incorrect BSP file id +#define BLERR_WRONGBSPFILEVERSION 17 //BotLoadMap: incorrect BSP file version +#define BLERR_CANNOTREADBSPLUMP 18 //BotLoadMap: cannot read BSP file lump +#define BLERR_AICLIENTNOTSETUP 19 //BotAI: client not setup +#define BLERR_AICLIENTALREADYSETUP 20 //BotSetupClient: client already setup +#define BLERR_AIMOVEINACTIVECLIENT 21 //BotMoveClient: cannot move inactive client +#define BLERR_AIMOVETOACTIVECLIENT 22 //BotMoveClient: cannot move to active client +#define BLERR_AICLIENTALREADYSHUTDOWN 23 //BotShutdownClient: client not setup +#define BLERR_AIUPDATEINACTIVECLIENT 24 //BotUpdateClient: called for inactive client +#define BLERR_AICMFORINACTIVECLIENT 25 //BotConsoleMessage: called for inactive client +#define BLERR_SETTINGSINACTIVECLIENT 26 //BotClientSettings: called for inactive client +#define BLERR_CANNOTLOADICHAT 27 //BotSetupClient: cannot load initial chats +#define BLERR_CANNOTLOADITEMWEIGHTS 28 //BotSetupClient: cannot load item weights +#define BLERR_CANNOTLOADITEMCONFIG 29 //BotSetupLibrary: cannot load item config +#define BLERR_CANNOTLOADWEAPONWEIGHTS 30 //BotSetupClient: cannot load weapon weights +#define BLERR_CANNOTLOADWEAPONCONFIG 31 //BotSetupLibrary: cannot load weapon config +#define BLERR_INVALIDSOUNDINDEX 32 //BotAddSound: invalid sound index value + +//action flags +#define ACTION_ATTACK 1 +#define ACTION_USE 2 +#define ACTION_RESPAWN 4 +#define ACTION_JUMP 8 +#define ACTION_MOVEUP 8 +#define ACTION_CROUCH 16 +#define ACTION_MOVEDOWN 16 +#define ACTION_MOVEFORWARD 32 +#define ACTION_MOVEBACK 64 +#define ACTION_MOVELEFT 128 +#define ACTION_MOVERIGHT 256 +#define ACTION_DELAYEDJUMP 512 +#define ACTION_TALK 1024 +#define ACTION_GESTURE 2048 +#define ACTION_WALK 4096 +#define ACTION_RELOAD 8192 + +//the bot input, will be converted to an usercmd_t +typedef struct bot_input_s +{ + float thinktime; //time since last output (in seconds) + vec3_t dir; //movement direction + float speed; //speed in the range [0, 400] + vec3_t viewangles; //the view angles + int actionflags; //one of the ACTION_? flags + int weapon; //weapon to use +} bot_input_t; + +#ifndef BSPTRACE + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//remove the bsp_trace_s structure definition l8r on +//a trace is returned when a box is swept through the world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // the hit point surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; + +#define BSPTRACE +#endif // BSPTRACE + +//entity state +typedef struct bot_entitystate_s +{ + int type; // entity type + int flags; // entity flags + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +// int weapAnim; // mask off ANIM_TOGGLEBIT //----(SA) added +//----(SA) didn't want to comment in as I wasn't sure of any implications of changing this structure. +} bot_entitystate_t; + +//bot AI library exported functions +typedef struct botlib_import_s +{ + //print messages from the bot library + void ( QDECL * Print )( int type, char *fmt, ... ); + //trace a bbox through the world + void ( *Trace )( bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask ); + //trace a bbox against a specific entity + void ( *EntityTrace )( bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask ); + //retrieve the contents at the given point + int ( *PointContents )( vec3_t point ); + //check if the point is in potential visible sight + int ( *inPVS )( vec3_t p1, vec3_t p2 ); + //retrieve the BSP entity data lump + char *( *BSPEntityData )( void ); + // + void ( *BSPModelMinsMaxsOrigin )( int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin ); + //send a bot client command + void ( *BotClientCommand )( int client, char *command ); + //memory allocation + void *( *GetMemory )( int size ); + void ( *FreeMemory )( void *ptr ); + void ( *FreeZoneMemory )( void ); + void *( *HunkAlloc )( int size ); + //file system access + int ( *FS_FOpenFile )( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int ( *FS_Read )( void *buffer, int len, fileHandle_t f ); + int ( *FS_Write )( const void *buffer, int len, fileHandle_t f ); + void ( *FS_FCloseFile )( fileHandle_t f ); + int ( *FS_Seek )( fileHandle_t f, long offset, int origin ); + //debug visualisation stuff + int ( *DebugLineCreate )( void ); + void ( *DebugLineDelete )( int line ); + void ( *DebugLineShow )( int line, vec3_t start, vec3_t end, int color ); + // + int ( *DebugPolygonCreate )( int color, int numPoints, vec3_t *points ); + void ( *DebugPolygonDelete )( int id ); + // + // Ridah, Cast AI stuff + qboolean ( *AICast_VisibleFromPos )( vec3_t srcpos, int srcnum, + vec3_t destpos, int destnum, qboolean updateVisPos ); + qboolean ( *AICast_CheckAttackAtPos )( int entnum, int enemy, vec3_t pos, qboolean ducking, qboolean allowHitWorld ); + // done. +} botlib_import_t; + +typedef struct aas_export_s +{ + //----------------------------------- + // be_aas_entity.h + //----------------------------------- + void ( *AAS_EntityInfo )( int entnum, struct aas_entityinfo_s *info ); + //----------------------------------- + // be_aas_main.h + //----------------------------------- + int ( *AAS_Initialized )( void ); + void ( *AAS_PresenceTypeBoundingBox )( int presencetype, vec3_t mins, vec3_t maxs ); + float ( *AAS_Time )( void ); + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + int ( *AAS_PointAreaNum )( vec3_t point ); + int ( *AAS_TraceAreas )( vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas ); + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + int ( *AAS_PointContents )( vec3_t point ); + int ( *AAS_NextBSPEntity )( int ent ); + int ( *AAS_ValueForBSPEpairKey )( int ent, char *key, char *value, int size ); + int ( *AAS_VectorForBSPEpairKey )( int ent, char *key, vec3_t v ); + int ( *AAS_FloatForBSPEpairKey )( int ent, char *key, float *value ); + int ( *AAS_IntForBSPEpairKey )( int ent, char *key, int *value ); + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + int ( *AAS_AreaReachability )( int areanum ); + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + int ( *AAS_AreaTravelTimeToGoalArea )( int areanum, vec3_t origin, int goalareanum, int travelflags ); + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + int ( *AAS_Swimming )( vec3_t origin ); + int ( *AAS_PredictClientMovement )( struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize ); + + // Ridah, route-tables + //-------------------------------------------- + // be_aas_routetable.c + //-------------------------------------------- + void ( *AAS_RT_ShowRoute )( vec3_t srcpos, int srcnum, int destnum ); + qboolean ( *AAS_RT_GetHidePos )( vec3_t srcpos, int srcnum, int srcarea, vec3_t destpos, int destnum, int destarea, vec3_t returnPos ); + int ( *AAS_FindAttackSpotWithinRange )( int srcnum, int rangenum, int enemynum, float rangedist, int travelflags, float *outpos ); + void ( *AAS_SetAASBlockingEntity )( vec3_t absmin, vec3_t absmax, qboolean blocking ); + // done. + + // Ridah + void ( *AAS_SetCurrentWorld )( int index ); + // done. + +} aas_export_t; + +typedef struct ea_export_s +{ + //ClientCommand elementary actions + void ( *EA_Say )( int client, char *str ); + void ( *EA_SayTeam )( int client, char *str ); + void ( *EA_UseItem )( int client, char *it ); + void ( *EA_DropItem )( int client, char *it ); + void ( *EA_UseInv )( int client, char *inv ); + void ( *EA_DropInv )( int client, char *inv ); + void ( *EA_Gesture )( int client ); + void ( *EA_Command )( int client, char *command ); + //regular elementary actions + void ( *EA_SelectWeapon )( int client, int weapon ); + void ( *EA_Talk )( int client ); + void ( *EA_Attack )( int client ); + void ( *EA_Reload )( int client ); + void ( *EA_Use )( int client ); + void ( *EA_Respawn )( int client ); + void ( *EA_Jump )( int client ); + void ( *EA_DelayedJump )( int client ); + void ( *EA_Crouch )( int client ); + void ( *EA_MoveUp )( int client ); + void ( *EA_MoveDown )( int client ); + void ( *EA_MoveForward )( int client ); + void ( *EA_MoveBack )( int client ); + void ( *EA_MoveLeft )( int client ); + void ( *EA_MoveRight )( int client ); + void ( *EA_Move )( int client, vec3_t dir, float speed ); + void ( *EA_View )( int client, vec3_t viewangles ); + //send regular input to the server + void ( *EA_EndRegular )( int client, float thinktime ); + void ( *EA_GetInput )( int client, float thinktime, bot_input_t *input ); + void ( *EA_ResetInput )( int client, bot_input_t *init ); +} ea_export_t; + +typedef struct ai_export_s +{ + //----------------------------------- + // be_ai_char.h + //----------------------------------- + int ( *BotLoadCharacter )( char *charfile, int skill ); + void ( *BotFreeCharacter )( int character ); + float ( *Characteristic_Float )( int character, int index ); + float ( *Characteristic_BFloat )( int character, int index, float min, float max ); + int ( *Characteristic_Integer )( int character, int index ); + int ( *Characteristic_BInteger )( int character, int index, int min, int max ); + void ( *Characteristic_String )( int character, int index, char *buf, int size ); + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + int ( *BotAllocChatState )( void ); + void ( *BotFreeChatState )( int handle ); + void ( *BotQueueConsoleMessage )( int chatstate, int type, char *message ); + void ( *BotRemoveConsoleMessage )( int chatstate, int handle ); + int ( *BotNextConsoleMessage )( int chatstate, struct bot_consolemessage_s *cm ); + int ( *BotNumConsoleMessages )( int chatstate ); + void ( *BotInitialChat )( int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ); + int ( *BotNumInitialChats )( int chatstate, char *type ); + int ( *BotReplyChat )( int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ); + int ( *BotChatLength )( int chatstate ); + void ( *BotEnterChat )( int chatstate, int client, int sendto ); + void ( *BotGetChatMessage )( int chatstate, char *buf, int size ); + int ( *StringContains )( char *str1, char *str2, int casesensitive ); + int ( *BotFindMatch )( char *str, struct bot_match_s *match, unsigned long int context ); + void ( *BotMatchVariable )( struct bot_match_s *match, int variable, char *buf, int size ); + void ( *UnifyWhiteSpaces )( char *string ); + void ( *BotReplaceSynonyms )( char *string, unsigned long int context ); + int ( *BotLoadChatFile )( int chatstate, char *chatfile, char *chatname ); + void ( *BotSetChatGender )( int chatstate, int gender ); + void ( *BotSetChatName )( int chatstate, char *name ); + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + void ( *BotResetGoalState )( int goalstate ); + void ( *BotResetAvoidGoals )( int goalstate ); + void ( *BotRemoveFromAvoidGoals )( int goalstate, int number ); + void ( *BotPushGoal )( int goalstate, struct bot_goal_s *goal ); + void ( *BotPopGoal )( int goalstate ); + void ( *BotEmptyGoalStack )( int goalstate ); + void ( *BotDumpAvoidGoals )( int goalstate ); + void ( *BotDumpGoalStack )( int goalstate ); + void ( *BotGoalName )( int number, char *name, int size ); + int ( *BotGetTopGoal )( int goalstate, struct bot_goal_s *goal ); + int ( *BotGetSecondGoal )( int goalstate, struct bot_goal_s *goal ); + int ( *BotChooseLTGItem )( int goalstate, vec3_t origin, int *inventory, int travelflags ); + int ( *BotChooseNBGItem )( int goalstate, vec3_t origin, int *inventory, int travelflags, + struct bot_goal_s *ltg, float maxtime ); + int ( *BotTouchingGoal )( vec3_t origin, struct bot_goal_s *goal ); + int ( *BotItemGoalInVisButNotVisible )( int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal ); + int ( *BotGetLevelItemGoal )( int index, char *classname, struct bot_goal_s *goal ); + int ( *BotGetNextCampSpotGoal )( int num, struct bot_goal_s *goal ); + int ( *BotGetMapLocationGoal )( char *name, struct bot_goal_s *goal ); + float ( *BotAvoidGoalTime )( int goalstate, int number ); + void ( *BotInitLevelItems )( void ); + void ( *BotUpdateEntityItems )( void ); + int ( *BotLoadItemWeights )( int goalstate, char *filename ); + void ( *BotFreeItemWeights )( int goalstate ); + void ( *BotInterbreedGoalFuzzyLogic )( int parent1, int parent2, int child ); + void ( *BotSaveGoalFuzzyLogic )( int goalstate, char *filename ); + void ( *BotMutateGoalFuzzyLogic )( int goalstate, float range ); + int ( *BotAllocGoalState )( int client ); + void ( *BotFreeGoalState )( int handle ); + //----------------------------------- + // be_ai_move.h + //----------------------------------- + void ( *BotResetMoveState )( int movestate ); + void ( *BotMoveToGoal )( struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags ); + int ( *BotMoveInDirection )( int movestate, vec3_t dir, float speed, int type ); + void ( *BotResetAvoidReach )( int movestate ); + void ( *BotResetLastAvoidReach )( int movestate ); + int ( *BotReachabilityArea )( vec3_t origin, int testground ); + int ( *BotMovementViewTarget )( int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target ); + int ( *BotPredictVisiblePosition )( vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target ); + int ( *BotAllocMoveState )( void ); + void ( *BotFreeMoveState )( int handle ); + void ( *BotInitMoveState )( int handle, struct bot_initmove_s *initmove ); + // Ridah + void ( *BotInitAvoidReach )( int handle ); + // done. + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + int ( *BotChooseBestFightWeapon )( int weaponstate, int *inventory ); + void ( *BotGetWeaponInfo )( int weaponstate, int weapon, struct weaponinfo_s *weaponinfo ); + int ( *BotLoadWeaponWeights )( int weaponstate, char *filename ); + int ( *BotAllocWeaponState )( void ); + void ( *BotFreeWeaponState )( int weaponstate ); + void ( *BotResetWeaponState )( int weaponstate ); + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + int ( *GeneticParentsAndChildSelection )( int numranks, float *ranks, int *parent1, int *parent2, int *child ); +} ai_export_t; + +//bot AI library imported functions +typedef struct botlib_export_s +{ + //Area Awareness System functions + aas_export_t aas; + //Elementary Action functions + ea_export_t ea; + //AI functions + ai_export_t ai; + //setup the bot library, returns BLERR_ + int ( *BotLibSetup )( void ); + //shutdown the bot library, returns BLERR_ + int ( *BotLibShutdown )( void ); + //sets a library variable returns BLERR_ + int ( *BotLibVarSet )( char *var_name, char *value ); + //gets a library variable returns BLERR_ + int ( *BotLibVarGet )( char *var_name, char *value, int size ); + + //sets a C-like define returns BLERR_ + int ( *PC_AddGlobalDefine )( char *string ); + int ( *PC_LoadSourceHandle )( const char *filename ); + int ( *PC_FreeSourceHandle )( int handle ); + int ( *PC_ReadTokenHandle )( int handle, pc_token_t *pc_token ); + int ( *PC_SourceFileAndLine )( int handle, char *filename, int *line ); + + //start a frame in the bot library + int ( *BotLibStartFrame )( float time ); + //load a new map in the bot library + int ( *BotLibLoadMap )( const char *mapname ); + //entity updates + int ( *BotLibUpdateEntity )( int ent, bot_entitystate_t *state ); + //just for testing + int ( *Test )( int parm0, char *parm1, vec3_t parm2, vec3_t parm3 ); +} botlib_export_t; + +//linking of bot library +botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); + +/* Library variables: + +name: default: module(s): description: + +"basedir" "" l_utils.c Quake2 base directory +"gamedir" "" l_utils.c Quake2 game directory +"cddir" "" l_utils.c Quake2 CD directory + +"autolaunchbspc" "0" be_aas_load.c automatically launch (Win)BSPC +"log" "0" l_log.c enable/disable creating a log file +"maxclients" "4" be_interface.c maximum number of clients +"maxentities" "1024" be_interface.c maximum number of entities + +"sv_friction" "6" be_aas_move.c ground friction +"sv_stopspeed" "100" be_aas_move.c stop speed +"sv_gravity" "800" be_aas_move.c gravity value +"sv_waterfriction" "1" be_aas_move.c water friction +"sv_watergravity" "400" be_aas_move.c gravity in water +"sv_maxvelocity" "300" be_aas_move.c maximum velocity +"sv_maxwalkvelocity" "300" be_aas_move.c maximum walk velocity +"sv_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity +"sv_maxswimvelocity" "150" be_aas_move.c maximum swim velocity +"sv_walkaccelerate" "10" be_aas_move.c walk acceleration +"sv_airaccelerate" "1" be_aas_move.c air acceleration +"sv_swimaccelerate" "4" be_aas_move.c swim acceleration +"sv_maxstep" "18" be_aas_move.c maximum step height +"sv_maxbarrier" "32" be_aas_move.c maximum barrier height +"sv_maxsteepness" "0.7" be_aas_move.c maximum floor steepness +"sv_jumpvel" "270" be_aas_move.c jump z velocity +"sv_maxwaterjump" "20" be_aas_move.c maximum waterjump height + +"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS +"max_bsplinks" "4096" be_aas_bsp.c maximum links in the BSP + +"notspawnflags" "2048" be_ai_goal.c entities with these spawnflags will be removed +"itemconfig" "items.c" be_ai_goal.c item configuration file +"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file +"synfile" "syn.c" be_ai_chat.c file with synonyms +"rndfile" "rnd.c" be_ai_chat.c file with random strings +"matchfile" "match.c" be_ai_chat.c file with match strings +"max_messages" "1024" be_ai_chat.c console message heap size +"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info +"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info +"max_iteminfo" "256" be_ai_goal.c maximum number of item info +"max_levelitems" "256" be_ai_goal.c maximum number of level items +"framereachability" "" be_aas_reach.c number of reachabilities to calucate per frame +"forceclustering" "0" be_aas_main.c force recalculation of clusters +"forcereachability" "0" be_aas_main.c force recalculation of reachabilities +"forcewrite" "0" be_aas_main.c force writing of aas file +"nooptimize" "0" be_aas_main.c no aas optimization + +"laserhook" "0" be_ai_move.c 0 = CTF hook, 1 = laser hook + +*/ + diff --git a/src/game/g_active.c b/src/game/g_active.c new file mode 100644 index 0000000..aff2cee --- /dev/null +++ b/src/game/g_active.c @@ -0,0 +1,1639 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "g_local.h" + +#include "ai_cast_fight.h" // need these for avoidance + + +extern void G_CheckForCursorHints( gentity_t *ent ); + + + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) { + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if ( client->ps.pm_type == PM_DEAD ) { + return; + } + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if ( count == 0 ) { + return; // didn't take any damage + } + + if ( count > 127 ) { + count = 127; + } + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if ( client->damage_fromWorld ) { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } else { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[PITCH] / 360.0 * 256; + client->ps.damageYaw = angles[YAW] / 360.0 * 256; + } + + // play an apropriate pain sound + if ( ( level.time > player->pain_debounce_time ) && !( player->flags & FL_GODMODE ) && !( player->r.svFlags & SVF_CASTAI ) && !( player->s.powerups & PW_INVULNERABLE ) ) { //----(SA) + player->pain_debounce_time = level.time + 700; + G_AddEvent( player, EV_PAIN, player->health ); + } + + client->ps.damageEvent++; // Ridah, always increment this since we do multiple view damage anims + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + + +#define MIN_BURN_INTERVAL 399 // JPW NERVE set burn timeinterval so we can do more precise damage (was 199 old model) + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) { + qboolean envirosuit; + int waterlevel; + // TTimo: unused +// static int lastflameburntime = 0; // JPW NERVE for slowing flamethrower burn intervals and doing more damage per interval + + if ( ent->client->noclip ) { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + waterlevel = ent->waterlevel; + + envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; + +// G_Printf("breathe: %d invuln: %d nofatigue: %d\n", ent->client->ps.powerups[PW_BREATHER], level.time - ent->client->ps.powerups[PW_INVULNERABLE], ent->client->ps.powerups[PW_NOFATIGUE]); + + // + // check for drowning + // + if ( waterlevel == 3 ) { + // envirosuit give air + if ( envirosuit ) { + ent->client->airOutTime = level.time + 10000; + } + + //----(SA) both these will end up being by virtue of having the 'breather' powerup + if ( ent->client->ps.aiChar == AICHAR_FROGMAN ) { // let frogmen breathe forever + ent->client->airOutTime = level.time + 10000; + } + + if ( ent->client->ps.aiChar == AICHAR_SEALOPER ) { // ditto + ent->client->airOutTime = level.time + 10000; + } + + // if out of air, start drowning + if ( ent->client->airOutTime < level.time ) { + + if ( ent->client->ps.powerups[PW_BREATHER] ) { // take air from the breather now that we need it + ent->client->ps.powerups[PW_BREATHER] -= ( level.time - ent->client->airOutTime ); + ent->client->airOutTime = level.time + ( level.time - ent->client->airOutTime ); + } else { + + + // drown! + ent->client->airOutTime += 1000; + if ( ent->health > 0 ) { + // take more damage the longer underwater + ent->damage += 2; + if ( ent->damage > 15 ) { + ent->damage = 15; + } + + // play a gurp sound instead of a normal pain sound + if ( ent->health <= ent->damage ) { + G_Sound( ent, G_SoundIndex( "*drown.wav" ) ); + } else if ( rand() & 1 ) { + G_Sound( ent, G_SoundIndex( "sound/player/gurp1.wav" ) ); + } else { + G_Sound( ent, G_SoundIndex( "sound/player/gurp2.wav" ) ); + } + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage( ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER ); + } + } + } + } else { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if ( waterlevel && ( ent->watertype & CONTENTS_LAVA ) ) { + if ( ent->health > 0 && ent->pain_debounce_time <= level.time ) { + + if ( envirosuit ) { + G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); + } else { + if ( ent->watertype & CONTENTS_LAVA ) { + G_Damage( ent, NULL, NULL, NULL, NULL, + 30 * waterlevel, 0, MOD_LAVA ); + } + } + + } + } + + // + // check for burning from flamethrower + // + // JPW NERVE MP way + if ( ent->s.onFireEnd && ent->client ) { + if ( level.time - ent->client->lastBurnTime >= MIN_BURN_INTERVAL ) { + + // JPW NERVE server-side incremental damage routine / player damage/health is int (not float) + // so I can't allocate 1.5 points per server tick, and 1 is too weak and 2 is too strong. + // solution: allocate damage far less often (MIN_BURN_INTERVAL often) and do more damage. + // That way minimum resolution (1 point) damage changes become less critical. + + ent->client->lastBurnTime = level.time; + if ( ( ent->s.onFireEnd > level.time ) && ( ent->health > 0 ) ) { + gentity_t *attacker; + attacker = g_entities + ent->flameBurnEnt; + G_Damage( ent, attacker->parent, attacker->parent, NULL, NULL, 5, DAMAGE_NO_KNOCKBACK, MOD_FLAMETHROWER ); // JPW NERVE was 7 + } + } + } + // jpw +} + + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) { + if ( ent->aiCharacter ) { + return; + } + + if ( ent->waterlevel && ( ent->watertype & CONTENTS_LAVA ) ) { //----(SA) modified since slime is no longer deadly + ent->s.loopSound = level.snd_fry; + } else { + ent->s.loopSound = 0; + } +} + + + +//============================================================== + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) { + int i, j; + trace_t trace; + gentity_t *other; + + memset( &trace, 0, sizeof( trace ) ); + for ( i = 0 ; i < pm->numtouch ; i++ ) { + for ( j = 0 ; j < i ; j++ ) { + if ( pm->touchents[j] == pm->touchents[i] ) { + break; + } + } + if ( j != i ) { + continue; // duplicated + } + other = &g_entities[ pm->touchents[i] ]; + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, other, &trace ); + } + + if ( !other->touch ) { + continue; + } + + other->touch( other, ent, &trace ); + } + +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + static vec3_t range = { 40, 40, 52 }; + + if ( !ent->client ) { + return; + } + + // dead clients don't activate triggers! + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for ( i = 0 ; i < num ; i++ ) { + hit = &g_entities[touch[i]]; + + if ( !hit->touch && !ent->touch ) { + continue; + } + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + + // ignore most entities if a spectator + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( hit->s.eType != ET_TELEPORT_TRIGGER ) { + continue; + } + } + + // use seperate code for determining if an item is picked up + // so you don't have to actually contact its bounding box + if ( hit->s.eType == ET_ITEM ) { + if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { + continue; + } + } else { + // MrE: always use capsule for player + //if ( !trap_EntityContactCapsule( mins, maxs, hit ) ) { + if ( !trap_EntityContact( mins, maxs, hit ) ) { + continue; + } + } + + memset( &trace, 0, sizeof( trace ) ); + + if ( hit->touch ) { + hit->touch( hit, ent, &trace ); + } + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, hit, &trace ); + } + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { + pmove_t pm; + gclient_t *client; + + client = ent->client; + + if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { + client->ps.pm_type = PM_SPECTATOR; + client->ps.speed = 400; // faster than normal + if ( client->ps.sprintExertTime ) { + client->ps.speed *= 3; // (SA) allow sprint in free-cam mode + + + } + // set up for pmove + memset( &pm, 0, sizeof( pm ) ); + pm.ps = &client->ps; + pm.pmext = &client->pmext; + pm.cmd = *ucmd; + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + + Pmove( &pm ); // JPW NERVE + + // Rafael - Activate + // Ridah, made it a latched event (occurs on keydown only) + if ( client->latched_buttons & BUTTON_ACTIVATE ) { + Cmd_Activate_f( ent ); + } + + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + } + + if ( ent->flags & FL_NOFATIGUE ) { + ent->client->ps.sprintTime = 20000; + } + + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + +//----(SA) added + client->oldwbuttons = client->wbuttons; + client->wbuttons = ucmd->wbuttons; + + // attack button cycles through spectators + if ( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) ) { + Cmd_FollowCycle_f( ent, 1 ); + } else if ( + ( client->sess.sessionTeam == TEAM_SPECTATOR ) && // don't let dead team players do free fly + ( client->sess.spectatorState == SPECTATOR_FOLLOW ) && + ( client->buttons & BUTTON_ACTIVATE ) && + !( client->oldbuttons & BUTTON_ACTIVATE ) ) { + // code moved to StopFollowing + StopFollowing( ent ); + } +} + + + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gclient_t *client ) { + if ( !g_inactivity.integer ) { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } else if ( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + ( client->pers.cmd.wbuttons & WBUTTON_ATTACK2 ) || + ( client->pers.cmd.buttons & BUTTON_ATTACK ) ) { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } else if ( !client->pers.localClient ) { + if ( level.time > client->inactivityTime ) { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { + client->inactivityWarning = qtrue; + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) { + gclient_t *client; + + client = ent->client; + client->timeResidual += msec; + + while ( client->timeResidual >= 1000 ) { + client->timeResidual -= 1000; + + // regenerate + // JPW NERVE, split these completely + if ( g_gametype.integer < GT_WOLF ) { + if ( client->ps.powerups[PW_REGEN] ) { + if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health += 15; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2 ) { + ent->health += 2; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } + } else { + // count down health when over max + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health--; + } + } + } +// JPW NERVE + else { // GT_WOLF + if ( client->ps.powerups[PW_REGEN] ) { + if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health += 3; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; + } + } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 1.12 ) { + ent->health += 2; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.12 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.12; + } + } + } else { + // count down health when over max + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health--; + } + } + } +// jpw + // count down armor when over max + if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { + client->ps.stats[STAT_ARMOR]--; + } + } +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) { + client->ps.eFlags &= ~EF_TALK; + client->ps.eFlags &= ~EF_FIRING; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + +//----(SA) added + client->oldwbuttons = client->wbuttons; + client->wbuttons = client->pers.cmd.wbuttons; + + if ( ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) || + ( client->wbuttons & WBUTTON_ATTACK2 & ( client->oldwbuttons ^ client->wbuttons ) ) ) { + client->readyToExit ^= 1; + } +} + + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +void ClientEvents( gentity_t *ent, int oldEventSequence ) { + int i; + int event; + gclient_t *client; + int damage; + vec3_t dir; + + client = ent->client; + + if ( oldEventSequence < client->ps.eventSequence - MAX_EVENTS ) { + oldEventSequence = client->ps.eventSequence - MAX_EVENTS; + } + for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { + event = client->ps.events[ i & ( MAX_EVENTS - 1 ) ]; + + switch ( event ) { + case EV_FALL_NDIE: + //case EV_FALL_SHORT: + case EV_FALL_DMG_10: + case EV_FALL_DMG_15: + case EV_FALL_DMG_25: + //case EV_FALL_DMG_30: + case EV_FALL_DMG_50: + //case EV_FALL_DMG_75: + + if ( ent->s.eType != ET_PLAYER ) { + break; // not in the player model + } + if ( g_dmflags.integer & DF_NO_FALLING ) { + break; + } + if ( event == EV_FALL_NDIE ) { + damage = 9999; + } else if ( event == EV_FALL_DMG_50 ) { + damage = 50; + ent->client->ps.pm_time = 1000; + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( ent->client->ps.velocity ); + } else if ( event == EV_FALL_DMG_25 ) { + damage = 25; + ent->client->ps.pm_time = 250; + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( ent->client->ps.velocity ); + } else if ( event == EV_FALL_DMG_15 ) { + damage = 15; + ent->client->ps.pm_time = 1000; + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( ent->client->ps.velocity ); + } else if ( event == EV_FALL_DMG_10 ) { + damage = 10; + ent->client->ps.pm_time = 1000; + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( ent->client->ps.velocity ); + } else { + damage = 5; // never used + } + VectorSet( dir, 0, 0, 1 ); + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage( ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING ); + break; +// JPW NERVE + case EV_TESTID1: + case EV_TESTID2: + case EV_ENDTEST: + break; +// jpw + case EV_FIRE_WEAPON_MG42: + mg42_fire( ent ); + break; + + case EV_FIRE_WEAPON: + case EV_FIRE_WEAPONB: + case EV_FIRE_WEAPON_LASTSHOT: + FireWeapon( ent ); + break; + +//----(SA) modified + case EV_USE_ITEM1: // ( HI_MEDKIT ) medkit + case EV_USE_ITEM2: // ( HI_WINE ) wine + case EV_USE_ITEM3: // ( HI_SKULL ) skull of invulnerable + case EV_USE_ITEM4: // ( HI_WATER ) protection from drowning + case EV_USE_ITEM5: // ( HI_ELECTRIC ) protection from electric attacks + case EV_USE_ITEM6: // ( HI_FIRE ) protection from fire attacks + case EV_USE_ITEM7: // ( HI_STAMINA ) restores fatigue bar and sets "nofatigue" for a time period + case EV_USE_ITEM8: // ( HI_BOOK1 ) + case EV_USE_ITEM9: // ( HI_BOOK2 ) + case EV_USE_ITEM10: // ( HI_BOOK3 ) + UseHoldableItem( ent, event - EV_USE_ITEM0 ); + break; +//----(SA) end + + default: + break; + } + } + +} + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) { + /* + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if ( ps->entityEventSequence < ps->eventSequence ) { + // create a temporary entity for this event which is sent to everyone + // except the client generated the event + seq = ps->entityEventSequence & (MAX_EVENTS-1); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } + */ +} + +// DHM - Nerve +void WolfFindMedic( gentity_t *self ) { + int i, medic = -1; + gclient_t *cl; + vec3_t start, end, temp; + trace_t tr; + float bestdist = 1024, dist; + + self->client->ps.viewlocked_entNum = 0; + self->client->ps.viewlocked = 0; + self->client->ps.stats[STAT_DEAD_YAW] = 999; + + VectorCopy( self->s.pos.trBase, start ); + start[2] += self->client->ps.viewheight; + + for ( i = 0; i < level.numPlayingClients; i++ ) { + cl = &level.clients[ level.sortedClients[i] ]; + + if ( cl->ps.clientNum == self->client->ps.clientNum ) { + continue; + } + if ( cl->sess.sessionTeam != self->client->sess.sessionTeam ) { + continue; + } + if ( cl->ps.stats[ STAT_HEALTH ] <= 0 ) { + continue; + } + if ( cl->ps.stats[ STAT_PLAYER_CLASS ] != PC_MEDIC ) { + continue; + } + + VectorCopy( g_entities[level.sortedClients[i]].s.pos.trBase, end ); + end[2] += cl->ps.viewheight; + + trap_Trace( &tr, start, NULL, NULL, end, self->s.number, CONTENTS_SOLID ); + if ( tr.fraction < 0.95 ) { + continue; + } + + VectorSubtract( end, start, end ); + dist = VectorNormalize( end ); + + if ( dist < bestdist ) { + medic = cl->ps.clientNum; + vectoangles( end, temp ); + self->client->ps.stats[STAT_DEAD_YAW] = temp[YAW]; + bestdist = dist; + } + } + + if ( medic >= 0 ) { + self->client->ps.viewlocked_entNum = medic; + self->client->ps.viewlocked = 7; + } +} + +void limbo( gentity_t *ent, qboolean makeCorpse ); // JPW NERVE +void reinforce( gentity_t *ent ); // JPW NERVE + +void ClientDamage( gentity_t *clent, int entnum, int enemynum, int id ); // NERVE - SMF + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) { + gclient_t *client; + pmove_t pm; +// vec3_t oldOrigin; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + int monsterslick = 0; +// JPW NERVE + int i; + vec3_t muzzlebounce; + gitem_t *item; + gentity_t *ent2; + vec3_t velocity, org, offset; + vec3_t angles,mins,maxs; + int weapon; + trace_t tr; +// jpw + + // Rafael wolfkick + //int validkick; + //static int wolfkicktimer = 0; + + client = ent->client; + + // don't think if the client is not yet connected (and thus not yet spawned in) + if ( client->pers.connected != CON_CONNECTED ) { + return; + } + + if ( client->cameraPortal ) { + G_SetOrigin( client->cameraPortal, client->ps.origin ); + trap_LinkEntity( client->cameraPortal ); + VectorCopy( client->cameraOrigin, client->cameraPortal->s.origin2 ); + } + + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + ent->client->ps.identifyClient = ucmd->identClient; // NERVE - SMF + +// JPW NERVE -- update counter for capture & hold display + if ( g_gametype.integer == GT_WOLF_CPH ) { + client->ps.stats[STAT_CAPTUREHOLD_RED] = level.capturetimes[TEAM_RED]; + client->ps.stats[STAT_CAPTUREHOLD_BLUE] = level.capturetimes[TEAM_BLUE]; + } +// jpw + + // sanity check the command time to prevent speedup cheating + if ( ucmd->serverTime > level.time + 200 ) { + ucmd->serverTime = level.time + 200; +// G_Printf("serverTime <<<<<\n" ); + } + if ( ucmd->serverTime < level.time - 1000 ) { + ucmd->serverTime = level.time - 1000; +// G_Printf("serverTime >>>>>\n" ); + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { + return; + /* + // Ridah, fixes savegame timing issue + if (msec < -100) { + client->ps.commandTime = ucmd->serverTime - 100; + msec = 100; + } else { + return; + } + */ + // done. + } + if ( msec > 200 ) { + msec = 200; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set( "pmove_msec", "8" ); + } else if ( pmove_msec.integer > 33 ) { + trap_Cvar_Set( "pmove_msec", "33" ); + } + + if ( pmove_fixed.integer || client->pers.pmoveFixed ) { + ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; + //if (ucmd->serverTime - client->ps.commandTime <= 0) + // return; + } + + // + // check for exiting intermission + // + if ( level.intermissiontime ) { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + // DHM - Nerve :: In limbo use SpectatorThink + if ( client->sess.sessionTeam == TEAM_SPECTATOR || client->ps.pm_flags & PMF_LIMBO ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + return; + } + SpectatorThink( ent, ucmd ); + return; + } + + // JPW NERVE do some time-based muzzle flip -- this never gets touched in single player (see g_weapon.c) + // #define RIFLE_SHAKE_TIME 150 // JPW NERVE this one goes with the commented out old damped "realistic" behavior below + #define RIFLE_SHAKE_TIME 300 // per Id request, longer recoil time + if ( client->sniperRifleFiredTime ) { + if ( level.time - client->sniperRifleFiredTime > RIFLE_SHAKE_TIME ) { + client->sniperRifleFiredTime = 0; + } else { + VectorCopy( client->ps.viewangles,muzzlebounce ); + + // JPW per Id request, longer recoil time + muzzlebounce[PITCH] -= 2 * cos( 2.5 * ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); + muzzlebounce[YAW] += 0.5*client->sniperRifleMuzzleYaw*cos( 1.0 - ( level.time - client->sniperRifleFiredTime ) * 3 / RIFLE_SHAKE_TIME ); + muzzlebounce[PITCH] -= 0.25 * random() * ( 1.0f - ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); + muzzlebounce[YAW] += 0.5 * crandom() * ( 1.0f - ( level.time - client->sniperRifleFiredTime ) / RIFLE_SHAKE_TIME ); + SetClientViewAngle( ent,muzzlebounce ); + } + } + if ( client->ps.stats[STAT_PLAYER_CLASS] == PC_MEDIC ) { + if ( level.time > client->ps.powerups[PW_REGEN] + 5000 ) { + client->ps.powerups[PW_REGEN] = level.time; + } + } + // also update weapon recharge time + + // JPW drop button drops secondary weapon so new one can be picked up + // TTimo explicit braces to avoid ambiguous 'else' + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + if ( ucmd->wbuttons & WBUTTON_DROP ) { + if ( !client->dropWeaponTime ) { + client->dropWeaponTime = 1; // just latch it for now + if ( ( client->ps.stats[STAT_PLAYER_CLASS] == PC_SOLDIER ) || ( client->ps.stats[STAT_PLAYER_CLASS] == PC_LT ) ) { + for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { + weapon = weapBanksMultiPlayer[3][i]; + if ( COM_BitCheck( client->ps.weapons,weapon ) ) { + + item = BG_FindItemForWeapon( weapon ); + VectorCopy( client->ps.viewangles, angles ); + + // clamp pitch + if ( angles[PITCH] < -30 ) { + angles[PITCH] = -30; + } else if ( angles[PITCH] > 30 ) { + angles[PITCH] = 30; + } + + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 64, offset ); + offset[2] += client->ps.viewheight / 2; + VectorScale( velocity, 75, velocity ); + velocity[2] += 50 + random() * 35; + + VectorAdd( client->ps.origin,offset,org ); + + VectorSet( mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); + VectorSet( maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS ); + + trap_Trace( &tr, client->ps.origin, mins, maxs, org, ent->s.number, MASK_SOLID ); + VectorCopy( tr.endpos, org ); + + ent2 = LaunchItem( item, org, velocity, client->ps.clientNum ); + COM_BitClear( client->ps.weapons,weapon ); + + if ( weapon == WP_MAUSER ) { + COM_BitClear( client->ps.weapons,WP_SNIPERRIFLE ); + } + + // Clear out empty weapon, change to next best weapon + G_AddEvent( ent, EV_NOAMMO, 0 ); + + i = MAX_WEAPS_IN_BANK_MP; + // show_bug.cgi?id=568 + if ( client->ps.weapon == weapon ) { + client->ps.weapon = 0; + } + ent2->count = client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; + ent2->item->quantity = client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; + client->ps.ammoclip[BG_FindClipForWeapon( weapon )] = 0; + } + } + } + } + } else { + client->dropWeaponTime = 0; + } + } +// jpw + + // check for inactivity timer, but never drop the local client of a non-dedicated server + if ( !ClientInactivityTimer( client ) ) { + return; + } + + if ( reloading || client->cameraPortal ) { + ucmd->buttons = 0; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->upmove = 0; + ucmd->wbuttons = 0; + ucmd->wolfkick = 0; + if ( client->cameraPortal ) { + client->ps.pm_type = PM_FREEZE; + } + } else if ( client->noclip ) { + client->ps.pm_type = PM_NOCLIP; + } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + client->ps.pm_type = PM_DEAD; + } else { + client->ps.pm_type = PM_NORMAL; + } + + // set parachute anim condition flag + BG_UpdateConditionValue( ent->s.number, ANIM_COND_PARACHUTE, ( ent->flags & FL_PARACHUTE ) != 0, qfalse ); + + // all playing clients are assumed to be in combat mode + if ( !client->ps.aiChar ) { + client->ps.aiState = AISTATE_COMBAT; + } + + client->ps.gravity = g_gravity.value; + + // set speed + client->ps.speed = g_speed.value; + + if ( client->ps.powerups[PW_HASTE] ) { + client->ps.speed *= 1.3; + } + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + client->currentAimSpreadScale = (float)client->ps.aimSpreadScale / 255.0; + + memset( &pm, 0, sizeof( pm ) ); + + pm.ps = &client->ps; + pm.pmext = &client->pmext; + pm.cmd = *ucmd; + pm.oldcmd = client->pers.oldcmd; + if ( pm.ps->pm_type == PM_DEAD ) { + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + // DHM-Nerve added:: EF_DEAD is checked for in Pmove functions, but wasn't being set + // until after Pmove + pm.ps->eFlags |= EF_DEAD; + // dhm-Nerve end + } else { + pm.tracemask = MASK_PLAYERSOLID; + } + // MrE: always use capsule for AI and player + //pm.trace = trap_TraceCapsule;//trap_Trace; + //DHM - Nerve :: We've gone back to using normal bbox traces + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + pm.noWeapClips = ( g_dmflags.integer & DF_NO_WEAPRELOAD ) > 0; + if ( ent->aiCharacter && AICast_NoReload( ent->s.number ) ) { + pm.noWeapClips = qtrue; // ensure AI characters don't use clips if they're not supposed to. + + } + // Ridah +// if (ent->r.svFlags & SVF_NOFOOTSTEPS) +// pm.noFootsteps = qtrue; + + VectorCopy( client->ps.origin, client->oldOrigin ); + + // NERVE - SMF + pm.gametype = g_gametype.integer; + pm.ltChargeTime = g_LTChargeTime.integer; + pm.soldierChargeTime = g_soldierChargeTime.integer; + pm.engineerChargeTime = g_engineerChargeTime.integer; + pm.medicChargeTime = g_medicChargeTime.integer; + // -NERVE - SMF + + monsterslick = Pmove( &pm ); + + if ( monsterslick && !( ent->flags & FL_NO_MONSTERSLICK ) ) { + //vec3_t dir; + //vec3_t kvel; + //vec3_t forward; + // TTimo gcc: might be used unitialized in this function + float angle = 0.0f; + qboolean bogus = qfalse; + + // NE + if ( ( monsterslick & SURF_MONSLICK_N ) && ( monsterslick & SURF_MONSLICK_E ) ) { + angle = 45; + } + // NW + else if ( ( monsterslick & SURF_MONSLICK_N ) && ( monsterslick & SURF_MONSLICK_W ) ) { + angle = 135; + } + // N + else if ( monsterslick & SURF_MONSLICK_N ) { + angle = 90; + } + // SE + else if ( ( monsterslick & SURF_MONSLICK_S ) && ( monsterslick & SURF_MONSLICK_E ) ) { + angle = 315; + } + // SW + else if ( ( monsterslick & SURF_MONSLICK_S ) && ( monsterslick & SURF_MONSLICK_W ) ) { + angle = 225; + } + // S + else if ( monsterslick & SURF_MONSLICK_S ) { + angle = 270; + } + // E + else if ( monsterslick & SURF_MONSLICK_E ) { + angle = 0; + } + // W + else if ( monsterslick & SURF_MONSLICK_W ) { + angle = 180; + } else + { + bogus = qtrue; + } + } + + // server cursor hints + if ( ent->lastHintCheckTime < level.time ) { + G_CheckForCursorHints( ent ); + + ent->lastHintCheckTime = level.time + FRAMETIME; + } + + // DHM - Nerve :: Set animMovetype to 1 if ducking + if ( ent->client->ps.pm_flags & PMF_DUCKED ) { + ent->s.animMovetype = 1; + } else { + ent->s.animMovetype = 0; + } + + // save results of pmove + if ( ent->client->ps.eventSequence != oldEventSequence ) { + ent->eventTime = level.time; + ent->r.eventTime = level.time; + } + + // Ridah, fixes jittery zombie movement + if ( g_smoothClients.integer ) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + + if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { + client->fireHeld = qfalse; // for grapple + } + +// +// // use the precise origin for linking +// VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); +// +// // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy( pm.mins, ent->r.mins ); + VectorCopy( pm.maxs, ent->r.maxs ); + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // execute client events + ClientEvents( ent, oldEventSequence ); + + // link entity now, after any personal teleporters have been used + trap_LinkEntity( ent ); + if ( !ent->client->noclip ) { + G_TouchTriggers( ent ); + } + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + + // store the client's current position for antilag traces + G_StoreClientPosition( ent ); + + // touch other objects + ClientImpacts( ent, &pm ); + + // save results of triggers and client events + if ( ent->client->ps.eventSequence != oldEventSequence ) { + ent->eventTime = level.time; + } + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons = client->buttons & ~client->oldbuttons; +// client->latched_buttons |= client->buttons & ~client->oldbuttons; // FIXME:? (SA) MP method (causes problems for us. activate 'sticks') + + //----(SA) added + client->oldwbuttons = client->wbuttons; + client->wbuttons = ucmd->wbuttons; + client->latched_wbuttons = client->wbuttons & ~client->oldwbuttons; +// client->latched_wbuttons |= client->wbuttons & ~client->oldwbuttons; // FIXME:? (SA) MP method + + // Rafael - Activate + // Ridah, made it a latched event (occurs on keydown only) + if ( client->latched_buttons & BUTTON_ACTIVATE ) { + Cmd_Activate_f( ent ); + } + + if ( ent->flags & FL_NOFATIGUE ) { + ent->client->ps.sprintTime = 20000; + } + + + // check for respawning + if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + + // DHM - Nerve + if ( g_gametype.integer >= GT_WOLF ) { + WolfFindMedic( ent ); + } + // dhm - end + + // wait for the attack button to be pressed + if ( level.time > client->respawnTime ) { + // forcerespawn is to prevent users from waiting out powerups + if ( ( g_gametype.integer != GT_SINGLE_PLAYER ) && + ( g_forcerespawn.integer > 0 ) && + ( ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) && + ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) ) { // JPW NERVE + // JPW NERVE + if ( g_gametype.integer >= GT_WOLF ) { + limbo( ent, qtrue ); + } else { + respawn( ent ); + } + // jpw + return; + } + + // DHM - Nerve :: Single player game respawns immediately as before, + // but in multiplayer, require button press before respawn + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + respawn( ent ); + } + // NERVE - SMF - we want to only respawn on jump button now + else if ( ( ucmd->upmove > 0 ) && + ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) ) { // JPW NERVE + // JPW NERVE + if ( g_gametype.integer >= GT_WOLF ) { + limbo( ent, qtrue ); + } else { + respawn( ent ); + } + // jpw + } + // dhm - Nerve :: end + // NERVE - SMF - we want to immediately go to limbo mode if gibbed + else if ( client->ps.stats[STAT_HEALTH] <= GIB_HEALTH && !( ent->client->ps.pm_flags & PMF_LIMBO ) ) { + if ( g_gametype.integer >= GT_WOLF ) { + limbo( ent, qfalse ); + } else { + respawn( ent ); + } + } + // -NERVE - SMF + } + return; + } + + // perform once-a-second actions + ClientTimerActions( ent, msec ); +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum ) { + gentity_t *ent; + + ent = g_entities + clientNum; + ent->client->pers.oldcmd = ent->client->pers.cmd; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if ( !g_synchronousClients.integer ) { + ClientThink_real( ent ); + } +} + + +void G_RunClient( gentity_t *ent ) { + if ( !g_synchronousClients.integer ) { + return; + } + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + +/* +================== +SpectatorClientEndFrame + +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) { + gclient_t *cl; + int do_respawn = 0; // JPW NERVE + int savedScore; // DHM - Nerve + int savedRespawns; // DHM - Nerve + int savedClass; // NERVE - SMF + int flags; + int testtime; + + // if we are doing a chase cam or a remote view, grab the latest info + if ( ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) || ( ent->client->ps.pm_flags & PMF_LIMBO ) ) { // JPW NERVE for limbo + int clientNum; + + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + testtime = level.time % g_redlimbotime.integer; + if ( testtime < ent->client->pers.lastReinforceTime ) { + do_respawn = 1; + } + ent->client->pers.lastReinforceTime = testtime; + } else if ( ent->client->sess.sessionTeam == TEAM_BLUE ) { + testtime = level.time % g_bluelimbotime.integer; + if ( testtime < ent->client->pers.lastReinforceTime ) { + do_respawn = 1; + } + ent->client->pers.lastReinforceTime = testtime; + } + + if ( ( g_maxlives.integer > 0 || g_alliedmaxlives.integer > 0 || g_axismaxlives.integer > 0 ) && ent->client->ps.persistant[PERS_RESPAWNS_LEFT] == 0 ) { + do_respawn = 0; + } + + if ( do_respawn ) { + reinforce( ent ); + return; + } + + clientNum = ent->client->sess.spectatorClient; + + // team follow1 and team follow2 go to whatever clients are playing + if ( clientNum == -1 ) { + clientNum = level.follow1; + } else if ( clientNum == -2 ) { + clientNum = level.follow2; + } + if ( clientNum >= 0 ) { + cl = &level.clients[ clientNum ]; + if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { + // DHM - Nerve :: carry flags over + flags = ( cl->ps.eFlags & ~( EF_VOTED ) ) | ( ent->client->ps.eFlags & ( EF_VOTED ) ); + // JPW NERVE -- limbo latch + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR && ent->client->ps.pm_flags & PMF_LIMBO ) { + // abuse do_respawn var + savedScore = ent->client->ps.persistant[PERS_SCORE]; + do_respawn = ent->client->ps.pm_time; + savedRespawns = ent->client->ps.persistant[PERS_RESPAWNS_LEFT]; + savedClass = ent->client->ps.stats[STAT_PLAYER_CLASS]; + + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.pm_flags |= PMF_LIMBO; + + ent->client->ps.persistant[PERS_RESPAWNS_LEFT] = savedRespawns; + ent->client->ps.pm_time = do_respawn; // put pm_time back + ent->client->ps.persistant[PERS_SCORE] = savedScore; // put score back + ent->client->ps.stats[STAT_PLAYER_CLASS] = savedClass; // NERVE - SMF - put player class back + } else { + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + } + // jpw + // DHM - Nerve :: carry flags over + ent->client->ps.eFlags = flags; + return; + } else { + // drop them to free spectators unless they are dedicated camera followers + if ( ent->client->sess.spectatorClient >= 0 ) { + ent->client->sess.spectatorState = SPECTATOR_FREE; + ClientBegin( ent->client - level.clients ); + } + } + } + } + + if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + ent->client->ps.pm_flags |= PMF_SCOREBOARD; + } else { + ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; + } +} + + +// DHM - Nerve :: After reviving a player, their contents stay CONTENTS_CORPSE until it is determined +// to be safe to return them to PLAYERSOLID + +qboolean StuckInClient( gentity_t *self ) { + gentity_t *hit; + vec3_t hitmin, hitmax; + vec3_t selfmin, selfmax; + int i; + + hit = &g_entities[0]; + for ( i = 0; i < level.maxclients; i++, hit++ ) { + if ( !hit->inuse ) { + continue; + } + if ( hit == self ) { + continue; + } + if ( !hit->client ) { + continue; + } + if ( !hit->s.solid ) { + continue; + } + if ( hit->health <= 0 ) { + continue; + } + + VectorAdd( hit->r.currentOrigin, hit->r.mins, hitmin ); + VectorAdd( hit->r.currentOrigin, hit->r.maxs, hitmax ); + VectorAdd( self->r.currentOrigin, self->r.mins, selfmin ); + VectorAdd( self->r.currentOrigin, self->r.maxs, selfmax ); + + if ( hitmin[0] > selfmax[0] ) { + continue; + } + if ( hitmax[0] < selfmin[0] ) { + continue; + } + if ( hitmin[1] > selfmax[1] ) { + continue; + } + if ( hitmax[1] < selfmin[1] ) { + continue; + } + if ( hitmin[2] > selfmax[2] ) { + continue; + } + if ( hitmax[2] < selfmin[2] ) { + continue; + } + + return qtrue; + } + return qfalse; +} + +extern vec3_t playerMins, playerMaxs; +#define WR_PUSHAMOUNT 25 + +void WolfRevivePushEnt( gentity_t *self, gentity_t *other ) { + vec3_t dir, push; + + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); + dir[2] = 0; + VectorNormalizeFast( dir ); + + VectorScale( dir, WR_PUSHAMOUNT, push ); + + if ( self->client ) { + VectorAdd( self->s.pos.trDelta, push, self->s.pos.trDelta ); + VectorAdd( self->client->ps.velocity, push, self->client->ps.velocity ); + } + + VectorScale( dir, -WR_PUSHAMOUNT, push ); + push[2] = WR_PUSHAMOUNT / 2; + + VectorAdd( other->s.pos.trDelta, push, other->s.pos.trDelta ); + //VectorAdd( other->client->ps.velocity, push, other->client->ps.velocity ); + if ( other->client ) { + VectorAdd( other->client->ps.velocity, push, other->client->ps.velocity ); + } +} + +void WolfReviveBbox( gentity_t *self ) { + int touch[MAX_GENTITIES]; + int num,i, touchnum = 0; + gentity_t *hit = NULL; + vec3_t mins, maxs; + gentity_t *capsulehit = NULL; + + VectorAdd( self->r.currentOrigin, playerMins, mins ); + VectorAdd( self->r.currentOrigin, playerMaxs, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // Arnout, we really should be using capsules, do a quick, more refined test using mover collision + if ( num ) { + capsulehit = G_TestEntityPosition( self ); + } + + for ( i = 0 ; i < num ; i++ ) { + hit = &g_entities[touch[i]]; + if ( hit->client ) { + // ATVI Wolfenstein Misc #467 + // don't look at yourself when counting the hits + if ( hit->client->ps.persistant[PERS_HWEAPON_USE] && hit != self ) { + touchnum++; + // Move corpse directly to the person who revived them + if ( self->props_frame_state >= 0 ) { + trap_UnlinkEntity( self ); + VectorCopy( g_entities[self->props_frame_state].client->ps.origin, self->client->ps.origin ); + VectorCopy( self->client->ps.origin, self->r.currentOrigin ); + trap_LinkEntity( self ); + + // Reset value so we don't continue to warp them + self->props_frame_state = -1; + } + } else if ( hit->health > 0 ) { + if ( hit->s.number != self->s.number ) { + WolfRevivePushEnt( hit, self ); + touchnum++; + } + } + } else if ( hit->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_PLAYERCLIP ) ) { + // Arnout: if hit is a mover, use capsulehit (this will only work if we touch one mover at a time - situations where you hit two are + // really rare anyway though. The real fix is to move everything to capsule collision detection though + if ( hit->s.eType == ET_MOVER ) { + if ( capsulehit && capsulehit != hit ) { + continue; // we collided with a mover, but we're not stuck in this one + } else { + continue; // we didn't collide with any movers + } + } + + WolfRevivePushEnt( hit, self ); + touchnum++; + } + } + + if ( g_dbgRevive.integer ) { + G_Printf( "WolfReviveBbox: touchnum: %d\n", touchnum ); + } + + if ( touchnum == 0 ) { + if ( g_dbgRevive.integer ) { + G_Printf( "WolfReviveBbox: Player is solid now!\n" ); + } + self->r.contents = CONTENTS_BODY; + } +} + +// dhm + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEndFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) { + int i; + + if ( ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) || ( ent->client->ps.pm_flags & PMF_LIMBO ) ) { // JPW NERVE + SpectatorClientEndFrame( ent ); + return; + } + + if ( !ent->aiCharacter ) { + // turn off any expired powerups + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + + if ( i == PW_FIRE || // these aren't dependant on level.time + i == PW_ELECTRIC || + i == PW_BREATHER || + i == PW_NOFATIGUE ) { + + continue; + } + + if ( ent->client->ps.powerups[ i ] < level.time ) { + ent->client->ps.powerups[ i ] = 0; + } + } + } + + // save network bandwidth +#if 0 + if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { + // FIXME: this must change eventually for non-sync demo recording + VectorClear( ent->client->ps.viewangles ); + } +#endif + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if ( level.intermissiontime ) { + return; + } + + // burn from lava, etc + P_WorldEffects( ent ); + + // apply all the damage taken this frame + P_DamageFeedback( ent ); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if ( level.time - ent->client->lastCmdTime > 1000 ) { + ent->s.eFlags |= EF_CONNECTION; + } else { + ent->s.eFlags &= ~EF_CONNECTION; + } + + ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... + + G_SetClientSound( ent ); + + // set the latest infor + + // Ridah, fixes jittery zombie movement + if ( g_smoothClients.integer ) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, ( ( ent->r.svFlags & SVF_CASTAI ) == 0 ) ); + } else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, ( ( ent->r.svFlags & SVF_CASTAI ) == 0 ) ); + } + + //SendPendingPredictableEvents( &ent->client->ps ); + + // DHM - Nerve :: If it's been a couple frames since being revived, and props_frame_state + // wasn't reset, go ahead and reset it + if ( ent->props_frame_state >= 0 && ( ( level.time - ent->s.effect3Time ) > 100 ) ) { + ent->props_frame_state = -1; + } + + if ( ent->health > 0 && StuckInClient( ent ) ) { + G_DPrintf( "%s is stuck in a client.\n", ent->client->pers.netname ); + ent->r.contents = CONTENTS_CORPSE; + } + + if ( g_gametype.integer >= GT_WOLF && ent->health > 0 && ent->r.contents == CONTENTS_CORPSE ) { + WolfReviveBbox( ent ); + } + + // DHM - Nerve :: Reset 'count2' for flamethrower + if ( !( ent->client->buttons & BUTTON_ATTACK ) ) { + ent->count2 = 0; + } + // dhm +} diff --git a/src/game/g_alarm.c b/src/game/g_alarm.c new file mode 100644 index 0000000..7194bf1 --- /dev/null +++ b/src/game/g_alarm.c @@ -0,0 +1,253 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + + +/* +============== +alarmExplosion + copied from propExplosion +============== +*/ +void alarmExplosion( gentity_t *ent ) { + gentity_t *bolt; + + extern void G_ExplodeMissile( gentity_t * ent ); + bolt = G_Spawn(); + bolt->classname = "props_explosion"; + bolt->nextthink = level.time + FRAMETIME; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + bolt->s.weapon = WP_NONE; + + bolt->s.eFlags = EF_BOUNCE_HALF; + bolt->r.ownerNum = ent->s.number; + bolt->parent = ent; + bolt->damage = ent->health; + bolt->splashDamage = ent->health; + bolt->splashRadius = ent->health * 1.5; + bolt->methodOfDeath = MOD_GRENADE; + bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH; + bolt->clipmask = MASK_SHOT; + + VectorCopy( ent->r.currentOrigin, bolt->s.pos.trBase ); + VectorCopy( ent->r.currentOrigin, bolt->r.currentOrigin ); +} + + +/* +============== +alarmbox_updateparts +============== +*/ +void alarmbox_updateparts( gentity_t *ent, qboolean matestoo ) { + gentity_t *t, *mate; + qboolean alarming = ( ent->s.frame == 1 ); + + // update teammates + if ( matestoo ) { + for ( mate = ent->teammaster; mate; mate = mate->teamchain ) + { + if ( mate == ent ) { + continue; + } + + if ( !( mate->active ) ) { // don't update dead alarm boxes, they stay dead + continue; + } + + if ( !( ent->active ) ) { // destroyed, so just turn teammates off + mate->s.frame = 0; + } else { + mate->s.frame = ent->s.frame; + } + + alarmbox_updateparts( mate, qfalse ); + } + } + + // update lights + if ( !ent->target ) { + return; + } + + t = NULL; + while ( ( t = G_Find( t, FOFS( targetname ), ent->target ) ) != NULL ) + { + if ( t == ent ) { + G_Printf( "WARNING: Entity used itself.\n" ); + } else + { + // give the dlight the sound + if ( !Q_stricmp( t->classname, "dlight" ) ) { + t->soundLoop = ent->soundLoop; + + if ( alarming ) { + if ( !( t->r.linked ) ) { + t->use( t, ent, 0 ); + } + } else + { + if ( t->r.linked ) { + t->use( t, ent, 0 ); + } + } + } + // alarmbox can tell script_trigger about activation + // (but don't trigger if dying, only activation) + else if ( !Q_stricmp( t->classname, "target_script_trigger" ) ) { + if ( ent->active ) { // not dead + t->use( t, ent, 0 ); + } + } + } + } +} + +/* +============== +alarmbox_use +============== +*/ +void alarmbox_use( gentity_t *ent, gentity_t *other, gentity_t *foo ) { + if ( !( ent->active ) ) { + return; + } + + if ( ent->s.frame ) { + ent->s.frame = 0; + } else { + ent->s.frame = 1; + } + + alarmbox_updateparts( ent, qtrue ); + if ( other->client ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos3 ); + } +// G_Printf("touched alarmbox\n"); + +} + + +/* +============== +alarmbox_die +============== +*/ +void alarmbox_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + alarmExplosion( ent ); + ent->s.frame = 2; + ent->active = qfalse; + ent->takedamage = qfalse; + alarmbox_updateparts( ent, qtrue ); +} + + + + +/* +============== +alarmbox_finishspawning +============== +*/ +void alarmbox_finishspawning( gentity_t *ent ) { + gentity_t *mate; + + // make sure they all have the same master (picked arbitrarily. last spawned) + for ( mate = ent; mate; mate = mate->teamchain ) + mate->teammaster = ent->teammaster; + + // find lights and set their state + alarmbox_updateparts( ent, qtrue ); +} + + +/*QUAKED alarm_box (1 0 1) START_ON +You need to have an origin brush as part of this entity +current alarm box model is (8 x 16 x 28) +"health" defaults to 10 + +"noise" the sound to play over the system (this would be the siren sound) + +START_ON means the button is pushed in, any dlights are cycling, and alarms are sounding + +"team" key/value is valid for teamed alarm boxes +teamed alarm_boxes work in tandem (switches/lights syncronize) +target a box to dlights to have them activate/deactivate with the system (use a stylestring that matches the cycletime for the alarmbox sound) +alarm sound locations are also placed in the dlights, so wherever you place an attached dlight, you will hear the alarm +model: the model used is "models/mapobjects/electronics/alarmbox.md3" +place the origin at the center of your trigger box +*/ +void SP_alarm_box( gentity_t *ent ) { + char *s; + + if ( !ent->model ) { + G_Printf( S_COLOR_RED "alarm_box with NULL model\n" ); + return; + } + + // model + trap_SetBrushModel( ent, ent->model ); + ent->s.modelindex2 = G_ModelIndex( "models/mapobjects/electronics/alarmbox.md3" ); + + // sound + if ( G_SpawnString( "noise", "0", &s ) ) { + ent->soundLoop = G_SoundIndex( s ); + } + + ent->soundPos3 = G_SoundIndex( "sound/world/alarmswitch.wav" ); + + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !ent->health ) { + ent->health = 10; + } + + if ( ent->spawnflags & 1 ) { + ent->s.frame = 1; + } else { + ent->s.frame = 0; + } + + ent->active = qtrue; + ent->s.eType = ET_ALARMBOX; + ent->takedamage = qtrue; + ent->die = alarmbox_die; + ent->use = alarmbox_use; + ent->think = alarmbox_finishspawning; + ent->nextthink = level.time + FRAMETIME; + + trap_LinkEntity( ent ); +} + + diff --git a/src/game/g_antilag.c b/src/game/g_antilag.c new file mode 100644 index 0000000..833e6ca --- /dev/null +++ b/src/game/g_antilag.c @@ -0,0 +1,255 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + +#define IS_ACTIVE( x ) ( \ + x->r.linked == qtrue && \ + x->client->ps.stats[STAT_HEALTH] > 0 && \ + x->client->sess.sessionTeam != TEAM_SPECTATOR && \ + ( x->client->ps.pm_flags & PMF_LIMBO ) == 0 \ + ) + +// OSP - +// Aside from the inline edits, I also changed the client loops to poll only +// the active client slots on the server, rather than looping through every +// potential (and usually unused) slot. +// + +void G_StoreClientPosition( gentity_t* ent ) { + int top, currentTime; + + if ( !IS_ACTIVE( ent ) ) { + return; + } + + top = ent->client->topMarker; + + // new frame, mark the old marker's time as the end of the last frame + if ( ent->client->clientMarkers[top].time < level.time ) { + ent->client->clientMarkers[top].time = level.previousTime; + top = ent->client->topMarker = ent->client->topMarker == MAX_CLIENT_MARKERS - 1 ? 0 : ent->client->topMarker + 1; + } + + currentTime = level.previousTime + trap_Milliseconds() - level.frameTime; + + if ( currentTime > level.time ) { + // owwie, we just went into the next frame... let's push them back + currentTime = level.time; + } + + VectorCopy( ent->r.mins, ent->client->clientMarkers[top].mins ); + VectorCopy( ent->r.maxs, ent->client->clientMarkers[top].maxs ); + VectorCopy( ent->r.currentOrigin, ent->client->clientMarkers[top].origin ); + + // OSP - these timers appear to be questionable + ent->client->clientMarkers[top].servertime = level.time; + ent->client->clientMarkers[top].time = currentTime; +} + +static void G_AdjustSingleClientPosition( gentity_t* ent, int time ) { + int i, j; + + if ( time > level.time ) { + time = level.time; + } // no lerping forward.... + + i = j = ent->client->topMarker; + do { + if ( ent->client->clientMarkers[i].time <= time ) { + break; + } + + j = i; + i = ( i > 0 ) ? i - 1 : MAX_CLIENT_MARKERS; + } while ( i != ent->client->topMarker ); + + if ( i == j ) { // oops, no valid stored markers + return; + } + + // OSP - I don't trust this caching, as the "warped" player's position updates potentially + // wont be counted after his think until the next server frame, which will result + // in a bad backupMarker +// if( ent->client->backupMarker.time != level.time ) { +// ent->client->backupMarker.time = level.time; + VectorCopy( ent->r.currentOrigin, ent->client->backupMarker.origin ); + VectorCopy( ent->r.mins, ent->client->backupMarker.mins ); + VectorCopy( ent->r.maxs, ent->client->backupMarker.maxs ); +// } + + if ( i != ent->client->topMarker ) { + float frac = ( (float)( ent->client->clientMarkers[j].time - time ) ) / ( ent->client->clientMarkers[j].time - ent->client->clientMarkers[i].time ); + + LerpPosition( ent->client->clientMarkers[j].origin, ent->client->clientMarkers[i].origin, frac, ent->r.currentOrigin ); + LerpPosition( ent->client->clientMarkers[j].mins, ent->client->clientMarkers[i].mins, frac, ent->r.mins ); + LerpPosition( ent->client->clientMarkers[j].maxs, ent->client->clientMarkers[i].maxs, frac, ent->r.maxs ); + } else { + VectorCopy( ent->client->clientMarkers[j].origin, ent->r.currentOrigin ); + VectorCopy( ent->client->clientMarkers[j].mins, ent->r.mins ); + VectorCopy( ent->client->clientMarkers[j].maxs, ent->r.maxs ); + } + + trap_LinkEntity( ent ); +} + +static void G_ReAdjustSingleClientPosition( gentity_t* ent ) { + + // OSP - I don't trust this caching, as the "warped" player's position updates potentially + // wont be counted after his think until the next server frame +// if( ent->client->backupMarker.time == level.time) { + VectorCopy( ent->client->backupMarker.origin, ent->r.currentOrigin ); + VectorCopy( ent->client->backupMarker.mins, ent->r.mins ); + VectorCopy( ent->client->backupMarker.maxs, ent->r.maxs ); + ent->client->backupMarker.servertime = 0; + + trap_LinkEntity( ent ); +// } +} + +void G_AdjustClientPositions( gentity_t* ent, int time, qboolean forward ) { + int i; + gentity_t *list; + + for ( i = 0; i < level.numConnectedClients; i++ ) { + list = g_entities + level.sortedClients[i]; + if ( list != ent && IS_ACTIVE( list ) ) { + if ( forward ) { + G_AdjustSingleClientPosition( list, time ); + } else { G_ReAdjustSingleClientPosition( list );} + } + } +} + +void G_ResetMarkers( gentity_t* ent ) { + int i, time; + char buffer[256]; + float period; + + trap_Cvar_VariableStringBuffer( "sv_fps", buffer, sizeof( buffer ) - 1 ); + + period = atoi( buffer ); + period = ( period == 0 ) ? 50.0f : 1000.f / period; + + ent->client->topMarker = MAX_CLIENT_MARKERS - 1; + for ( i = MAX_CLIENT_MARKERS, time = level.time; i >= 0; i--, time -= period ) { + ent->client->clientMarkers[i].servertime = time; + ent->client->clientMarkers[i].time = time; + + VectorCopy( ent->r.mins, ent->client->clientMarkers[i].mins ); + VectorCopy( ent->r.maxs, ent->client->clientMarkers[i].maxs ); + VectorCopy( ent->r.currentOrigin, ent->client->clientMarkers[i].origin ); + } +} + +void G_AttachBodyParts( gentity_t* ent ) { + int i; + gentity_t *list; + + for ( i = 0; i < level.numConnectedClients; i++ ) { + list = g_entities + level.sortedClients[i]; + list->client->tempHead = ( list != ent && IS_ACTIVE( list ) ) ? G_BuildHead( list ) : NULL; + } +} + +void G_DettachBodyParts() { + int i; + gentity_t *list; + + for ( i = 0; i < level.numConnectedClients; i++ ) { + list = g_entities + level.sortedClients[i]; + if ( list->client->tempHead != NULL ) { + G_FreeEntity( list->client->tempHead ); + } + } +} + +int G_SwitchBodyPartEntity( gentity_t* ent ) { + if ( ent->s.eType == ET_TEMPHEAD ) { + return ent->parent - g_entities; + } + return ent - g_entities; +} + +#define POSITION_READJUST \ + if ( res != results->entityNum ) { \ + VectorSubtract( end, start, dir ); \ + VectorNormalizeFast( dir ); \ + \ + VectorMA( results->endpos, -1, dir, results->endpos ); \ + results->entityNum = res; \ + } + +// Run a trace with players in historical positions. +void G_HistoricalTrace( gentity_t* ent, trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ) { + trace_t tr; + gentity_t *other; + int res; + vec3_t dir; + + if ( !g_antilag.integer || !ent->client ) { + G_AttachBodyParts( ent ) ; + + trap_Trace( results, start, mins, maxs, end, passEntityNum, contentmask ); + + res = G_SwitchBodyPartEntity( &g_entities[results->entityNum] ); + POSITION_READJUST + + G_DettachBodyParts(); + return; + } + + G_AdjustClientPositions( ent, ent->client->pers.cmd.serverTime, qtrue ); + + G_AttachBodyParts( ent ) ; + + trap_Trace( results, start, mins, maxs, end, passEntityNum, contentmask ); + + res = G_SwitchBodyPartEntity( &g_entities[results->entityNum] ); + POSITION_READJUST + + G_DettachBodyParts(); + + G_AdjustClientPositions( ent, 0, qfalse ); + + if ( results->entityNum >= 0 && results->entityNum < MAX_CLIENTS && ( other = &g_entities[results->entityNum] )->inuse ) { + G_AttachBodyParts( ent ) ; + + trap_Trace( &tr, start, mins, maxs, other->client->ps.origin, passEntityNum, contentmask ); + res = G_SwitchBodyPartEntity( &g_entities[results->entityNum] ); + POSITION_READJUST + + if ( tr.entityNum != results->entityNum ) { + trap_Trace( results, start, mins, maxs, end, passEntityNum, contentmask ); + res = G_SwitchBodyPartEntity( &g_entities[results->entityNum] ); + POSITION_READJUST + } + + G_DettachBodyParts(); + } +} diff --git a/src/game/g_bot.c b/src/game/g_bot.c new file mode 100644 index 0000000..9015e54 --- /dev/null +++ b/src/game/g_bot.c @@ -0,0 +1,847 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// g_bot.c + +#include "g_local.h" +#include "../botai/botai.h" + + +static int g_numBots; +static char g_botInfos[MAX_BOTS][MAX_INFO_STRING]; + + +int g_numArenas; +static char g_arenaInfos[MAX_ARENAS][MAX_INFO_STRING]; + + +#define BOT_BEGIN_DELAY_BASE 2000 +#define BOT_BEGIN_DELAY_INCREMENT 1500 + +#define BOT_SPAWN_QUEUE_DEPTH 16 + +typedef struct { + int clientNum; + int spawnTime; +} botSpawnQueue_t; + +static int botBeginDelay; +static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; + +vmCvar_t bot_minplayers; + +extern gentity_t *podium1; +extern gentity_t *podium2; +extern gentity_t *podium3; + +// TTimo gcc: defined but not used +#if 0 +/* +=============== +G_LoadArenas +=============== +*/ +static void G_LoadArenas( void ) { +#ifdef QUAKESTUFF + int len; + char *filename; + vmCvar_t arenasFile; + fileHandle_t f; + int n; + char buf[MAX_ARENAS_TEXT]; + + trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT | CVAR_ROM ); + if ( *arenasFile.string ) { + filename = arenasFile.string; + } else { + filename = "scripts/arenas.txt"; + } + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numArenas = COM_ParseInfos( buf, MAX_ARENAS, g_arenaInfos ); + trap_Printf( va( "%i arenas parsed\n", g_numArenas ) ); + + for ( n = 0; n < g_numArenas; n++ ) { + Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); + } +#endif +} +#endif + +/* +=============== +G_GetArenaInfoByNumber +=============== +*/ +const char *G_GetArenaInfoByMap( const char *map ) { + int n; + + for ( n = 0; n < g_numArenas; n++ ) { + if ( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { + return g_arenaInfos[n]; + } + } + + return NULL; +} + + +/* +================= +PlayerIntroSound +================= +*/ +static void PlayerIntroSound( const char *modelAndSkin ) { + char model[MAX_QPATH]; + char *skin; + + Q_strncpyz( model, modelAndSkin, sizeof( model ) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } else { + skin = model; + } + + if ( Q_stricmp( skin, "default" ) == 0 ) { + skin = model; + } + + trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); +} + +/* +=============== +G_AddRandomBot +=============== +*/ +void G_AddRandomBot( int team ) { + int i, n, num, skill; + char *value, netname[36], *teamstr; + gclient_t *cl; + + num = 0; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if ( i >= g_maxclients.integer ) { + num++; + } + } + num = random() * num; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if ( i >= g_maxclients.integer ) { + num--; + if ( num <= 0 ) { + skill = trap_Cvar_VariableIntegerValue( "g_spSkill" ); + if ( team == TEAM_RED ) { + teamstr = "red"; + } else if ( team == TEAM_BLUE ) { + teamstr = "blue"; + } else { teamstr = "";} + strncpy( netname, value, sizeof( netname ) - 1 ); + netname[sizeof( netname ) - 1] = '\0'; + Q_CleanStr( netname ); + trap_SendConsoleCommand( EXEC_INSERT, va( "addbot %s %i %s %i\n", netname, skill, teamstr, 0 ) ); + return; + } + } + } +} + +/* +=============== +G_RemoveRandomBot +=============== +*/ +int G_RemoveRandomBot( int team ) { + int i; + char netname[36]; + gclient_t *cl; + + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + strcpy( netname, cl->pers.netname ); + Q_CleanStr( netname ); + trap_SendConsoleCommand( EXEC_INSERT, va( "kick %s\n", netname ) ); + return qtrue; + } + return qfalse; +} + +/* +=============== +G_CountHumanPlayers +=============== +*/ +int G_CountHumanPlayers( int team ) { + int i, num; + gclient_t *cl; + + num = 0; + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CountBotPlayers +=============== +*/ +int G_CountBotPlayers( int team ) { + int i, n, num; + gclient_t *cl; + + num = 0; + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + num++; + } + for ( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if ( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CheckMinimumPlayers +=============== +*/ +void G_CheckMinimumPlayers( void ) { + int minplayers; + int humanplayers, botplayers; + static int checkminimumplayers_time; + + //only check once each 10 seconds + if ( checkminimumplayers_time > level.time - 10000 ) { + return; + } + checkminimumplayers_time = level.time; + trap_Cvar_Update( &bot_minplayers ); + minplayers = bot_minplayers.integer; + if ( minplayers <= 0 ) { + return; + } + + if ( g_gametype.integer >= GT_TEAM ) { + if ( minplayers >= g_maxclients.integer / 2 ) { + minplayers = ( g_maxclients.integer / 2 ) - 1; + } + + humanplayers = G_CountHumanPlayers( TEAM_RED ); + botplayers = G_CountBotPlayers( TEAM_RED ); + // + if ( humanplayers + botplayers < minplayers ) { + G_AddRandomBot( TEAM_RED ); + } else if ( humanplayers + botplayers > minplayers && botplayers ) { + G_RemoveRandomBot( TEAM_RED ); + } + // + humanplayers = G_CountHumanPlayers( TEAM_BLUE ); + botplayers = G_CountBotPlayers( TEAM_BLUE ); + // + if ( humanplayers + botplayers < minplayers ) { + G_AddRandomBot( TEAM_BLUE ); + } else if ( humanplayers + botplayers > minplayers && botplayers ) { + G_RemoveRandomBot( TEAM_BLUE ); + } + } else if ( g_gametype.integer == GT_TOURNAMENT ) { + if ( minplayers >= g_maxclients.integer ) { + minplayers = g_maxclients.integer - 1; + } + humanplayers = G_CountHumanPlayers( -1 ); + botplayers = G_CountBotPlayers( -1 ); + // + if ( humanplayers + botplayers < minplayers ) { + G_AddRandomBot( TEAM_FREE ); + } else if ( humanplayers + botplayers > minplayers && botplayers ) { + // try to remove spectators first + if ( !G_RemoveRandomBot( TEAM_SPECTATOR ) ) { + // just remove the bot that is playing + G_RemoveRandomBot( -1 ); + } + } + } else if ( g_gametype.integer == GT_FFA ) { + if ( minplayers >= g_maxclients.integer ) { + minplayers = g_maxclients.integer - 1; + } + humanplayers = G_CountHumanPlayers( TEAM_FREE ); + botplayers = G_CountBotPlayers( TEAM_FREE ); + // + if ( humanplayers + botplayers < minplayers ) { + G_AddRandomBot( TEAM_FREE ); + } else if ( humanplayers + botplayers > minplayers && botplayers ) { + G_RemoveRandomBot( TEAM_FREE ); + } + } +} + +/* +=============== +G_CheckBotSpawn +=============== +*/ +void G_CheckBotSpawn( void ) { + int n; + char userinfo[MAX_INFO_STRING]; + + G_CheckMinimumPlayers(); + + for ( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if ( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + ClientBegin( botSpawnQueue[n].clientNum ); + botSpawnQueue[n].spawnTime = 0; + + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof( userinfo ) ); + PlayerIntroSound( Info_ValueForKey( userinfo, "model" ) ); + } + } +} + + +/* +=============== +AddBotToSpawnQueue +=============== +*/ +static void AddBotToSpawnQueue( int clientNum, int delay ) { + int n; + + for ( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if ( !botSpawnQueue[n].spawnTime ) { + botSpawnQueue[n].spawnTime = level.time + delay; + botSpawnQueue[n].clientNum = clientNum; + return; + } + } + + G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); + ClientBegin( clientNum ); +} + + +/* +=============== +G_QueueBotBegin +=============== +*/ +void G_QueueBotBegin( int clientNum ) { + AddBotToSpawnQueue( clientNum, botBeginDelay ); + botBeginDelay += BOT_BEGIN_DELAY_INCREMENT; +} + + +/* +=============== +G_BotConnect +=============== +*/ +qboolean G_BotConnect( int clientNum, qboolean restart ) { + bot_settings_t settings; + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof( settings.characterfile ) ); + settings.skill = atoi( Info_ValueForKey( userinfo, "skill" ) ); + Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof( settings.team ) ); + + if ( !BotAISetupClient( clientNum, &settings ) ) { + trap_DropClient( clientNum, "BotAISetupClient failed" ); + return qfalse; + } + + if ( restart && g_gametype.integer == GT_SINGLE_PLAYER ) { + g_entities[clientNum].botDelayBegin = qtrue; + } else { + g_entities[clientNum].botDelayBegin = qfalse; + } + + return qtrue; +} + + +/* +=============== +G_AddBot +=============== +*/ +static void G_AddBot( const char *name, int skill, const char *team, int delay ) { + int clientNum; + char *botinfo; + gentity_t *bot; + char *key; + char *s; + char *botname; + char *model; + char userinfo[MAX_INFO_STRING]; + + // get the botinfo from bots.txt + botinfo = G_GetBotInfoByName( name ); + if ( !botinfo ) { + G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); + return; + } + + // create the bot's userinfo + userinfo[0] = '\0'; + + botname = Info_ValueForKey( botinfo, "funname" ); + if ( !botname[0] ) { + botname = Info_ValueForKey( botinfo, "name" ); + } + Info_SetValueForKey( userinfo, "name", botname ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + Info_SetValueForKey( userinfo, "skill", va( "%i", skill ) ); + + if ( skill == 1 ) { + Info_SetValueForKey( userinfo, "handicap", "50" ); + } else if ( skill == 2 ) { + Info_SetValueForKey( userinfo, "handicap", "70" ); + } else if ( skill == 3 ) { + Info_SetValueForKey( userinfo, "handicap", "90" ); + } + + key = "model"; + model = Info_ValueForKey( botinfo, key ); + if ( !*model ) { + model = "visor/default"; + } + Info_SetValueForKey( userinfo, key, model ); + + key = "gender"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "male"; + } + Info_SetValueForKey( userinfo, "sex", s ); + + key = "color"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "4"; + } + Info_SetValueForKey( userinfo, key, s ); + + s = Info_ValueForKey( botinfo, "aifile" ); + if ( !*s ) { + trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" ); + return; + } + + // have the server allocate a client slot + clientNum = trap_BotAllocateClient(); + if ( clientNum == -1 ) { + G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); + G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); + return; + } + + // initialize the bot settings + if ( !team || !*team ) { + if ( g_gametype.integer == GT_TEAM || g_gametype.integer == GT_CTF ) { + if ( PickTeam( clientNum ) == TEAM_RED ) { + team = "red"; + } else { + team = "blue"; + } + } else { + team = "red"; + } + } + Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); + Info_SetValueForKey( userinfo, "skill", va( "%i", skill ) ); + Info_SetValueForKey( userinfo, "team", team ); + + bot = &g_entities[ clientNum ]; + bot->r.svFlags |= SVF_BOT; + bot->inuse = qtrue; + + // register the userinfo + trap_SetUserinfo( clientNum, userinfo ); + + // have it connect to the game as a normal client + if ( ClientConnect( clientNum, qtrue, qtrue ) ) { + return; + } + + if ( delay == 0 ) { + ClientBegin( clientNum ); + return; + } + + AddBotToSpawnQueue( clientNum, delay ); +} + + +/* +=============== +Svcmd_AddBot_f +=============== +*/ +void Svcmd_AddBot_f( void ) { + int skill; + int delay; + char name[MAX_TOKEN_CHARS]; + char string[MAX_TOKEN_CHARS]; + char team[MAX_TOKEN_CHARS]; + + // are bots enabled? + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + // name + trap_Argv( 1, name, sizeof( name ) ); + if ( !name[0] ) { + trap_Printf( "Usage: Addbot [skill 1-4] [team] [msec delay]\n" ); + return; + } + + // skill + trap_Argv( 2, string, sizeof( string ) ); + if ( !string[0] ) { + skill = 4; + } else { + skill = atoi( string ); + } + + // team + trap_Argv( 3, team, sizeof( team ) ); + + // delay + trap_Argv( 4, string, sizeof( string ) ); + if ( !string[0] ) { + delay = 0; + } else { + delay = atoi( string ); + } + + G_AddBot( name, skill, team, delay ); + + // if this was issued during gameplay and we are playing locally, + // go ahead and load the bot's media immediately + if ( level.time - level.startTime > 1000 && + trap_Cvar_VariableIntegerValue( "cl_running" ) ) { + trap_SendServerCommand( -1, "loaddeferred\n" ); // spelling fixed (SA) + } +} + +// TTimo gcc: defined but not used +#if 0 +/* +=============== +G_SpawnBots +=============== +*/ +static void G_SpawnBots( char *botList, int baseDelay ) { + char *bot; + char *p; + int skill; + int delay; + char bots[MAX_INFO_VALUE]; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + skill = trap_Cvar_VariableIntegerValue( "g_spSkill" ); + if ( skill < 1 || skill > 5 ) { + trap_Cvar_Set( "g_spSkill", "2" ); + skill = 2; + } + + Q_strncpyz( bots, botList, sizeof( bots ) ); + p = &bots[0]; + delay = baseDelay; + while ( *p ) { + //skip spaces + while ( *p && *p == ' ' ) { + p++; + } + if ( !p ) { + break; + } + + // mark start of bot name + bot = p; + + // skip until space of null + while ( *p && *p != ' ' ) { + p++; + } + if ( *p ) { + *p++ = 0; + } + + // we must add the bot this way, calling G_AddBot directly at this stage + // does "Bad Things" + trap_SendConsoleCommand( EXEC_INSERT, va( "addbot %s %i free %i\n", bot, skill, delay ) ); + + delay += BOT_BEGIN_DELAY_INCREMENT; + } +} +#endif + +// TTimo gcc: defined but not used +#if 0 +/* +=============== +G_LoadBots +=============== +*/ +static void G_LoadBots( void ) { +#ifdef QUAKESTUFF + int len; + char *filename; + vmCvar_t botsFile; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT | CVAR_ROM ); + if ( *botsFile.string ) { + filename = botsFile.string; + } else { + filename = "scripts/bots.txt"; + } + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numBots = COM_ParseInfos( buf, MAX_BOTS, g_botInfos ); + trap_Printf( va( "%i bots parsed\n", g_numBots ) ); +#endif +} +#endif + +/* +=============== +G_GetBotInfoByNumber +=============== +*/ +char *G_GetBotInfoByNumber( int num ) { + if ( num < 0 || num >= g_numBots ) { + trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return g_botInfos[num]; +} + + +/* +=============== +G_GetBotInfoByName +=============== +*/ +char *G_GetBotInfoByName( const char *name ) { + int n; + char *value; + + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return g_botInfos[n]; + } + } + + return NULL; +} + +/* +=============== +G_InitBots +=============== +*/ +void G_InitBots( qboolean restart ) { + + // Ridah, we don't need this anymore + return; + // done. +/* + int fragLimit; + int timeLimit; + const char *arenainfo; + char *strValue; + int basedelay; + char map[MAX_QPATH]; + char serverinfo[MAX_INFO_STRING]; + + G_LoadBots(); + G_LoadArenas(); + + trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); + + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetServerinfo( serverinfo, sizeof(serverinfo) ); + Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) ); + arenainfo = G_GetArenaInfoByMap( map ); + if ( !arenainfo ) { + return; + } + + strValue = Info_ValueForKey( arenainfo, "fraglimit" ); + fragLimit = atoi( strValue ); + if ( fragLimit ) { + trap_Cvar_Set( "fraglimit", strValue ); + } + else { + trap_Cvar_Set( "fraglimit", "0" ); + } + + strValue = Info_ValueForKey( arenainfo, "timelimit" ); + timeLimit = atoi( strValue ); + if ( timeLimit ) { + trap_Cvar_Set( "timelimit", strValue ); + } + else { + trap_Cvar_Set( "timelimit", "0" ); + } + + if ( !fragLimit && !timeLimit ) { + trap_Cvar_Set( "fraglimit", "10" ); + trap_Cvar_Set( "timelimit", "0" ); + } + + basedelay = BOT_BEGIN_DELAY_BASE; + strValue = Info_ValueForKey( arenainfo, "special" ); + if( Q_stricmp( strValue, "training" ) == 0 ) { + basedelay += 10000; + } + + if( !restart ) { + G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay ); + } + } +*/ +} diff --git a/src/game/g_client.c b/src/game/g_client.c new file mode 100644 index 0000000..b344cc0 --- /dev/null +++ b/src/game/g_client.c @@ -0,0 +1,2223 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + +// g_client.c -- client functions that don't happen every frame + +// Ridah, new bounding box +//static vec3_t playerMins = {-15, -15, -24}; +//static vec3_t playerMaxs = {15, 15, 32}; +vec3_t playerMins = {-18, -18, -24}; +vec3_t playerMaxs = {18, 18, 48}; +// done. + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +If the start position is targeting an entity, the players camera will start out facing that ent (like an info_notnull) +*/ +void SP_info_player_deathmatch( gentity_t *ent ) { + int i; + vec3_t dir; + + G_SpawnInt( "nobots", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } + + ent->enemy = G_PickTarget( ent->target ); + if ( ent->enemy ) { + VectorSubtract( ent->enemy->s.origin, ent->s.origin, dir ); + vectoangles( dir, ent->s.angles ); + } + +} + +//----(SA) added +/*QUAKED info_player_checkpoint (1 0 0) (-16 -16 -24) (16 16 32) a b c d +these are start points /after/ the level start +the letter (a b c d) designates the checkpoint that needs to be complete in order to use this start position +*/ +void SP_info_player_checkpoint( gentity_t *ent ) { + ent->classname = "info_player_checkpoint"; + SP_info_player_deathmatch( ent ); +} + +//----(SA) end + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +equivelant to info_player_deathmatch +*/ +void SP_info_player_start( gentity_t *ent ) { + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) AXIS ALLIED +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) { + +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for ( i = 0 ; i < num ; i++ ) { + hit = &g_entities[touch[i]]; + if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + return qtrue; + } + + } + + return qfalse; +} + +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { + gentity_t *spot; + vec3_t delta; + float dist, nearestDist; + gentity_t *nearestSpot; + + nearestDist = 999999; + nearestSpot = NULL; + spot = NULL; + + while ( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) { + + VectorSubtract( spot->s.origin, from, delta ); + dist = VectorLength( delta ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + + return nearestSpot; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_SPAWN_POINTS]; + + count = 0; + spot = NULL; + + while ( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + + if ( !count ) { // no spots that won't telefrag + return G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); + } + + selection = rand() % count; + return spots[ selection ]; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + gentity_t *spot; + gentity_t *nearestSpot; + + nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); + + spot = SelectRandomDeathmatchSpawnPoint(); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + spot = SelectRandomDeathmatchSpawnPoint(); + if ( spot == nearestSpot ) { + // last try + spot = SelectRandomDeathmatchSpawnPoint(); + } + } + + // find a single player start spot + if ( !spot ) { + G_Error( "Couldn't find a spawn point" ); + } + + VectorCopy( spot->s.origin, origin ); + origin[2] += 9; + VectorCopy( spot->s.angles, angles ); + + return spot; +} + +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { + gentity_t *spot; + + spot = NULL; + while ( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) { + if ( spot->spawnflags & 1 ) { + break; + } + } + + if ( !spot || SpotWouldTelefrag( spot ) ) { + return SelectSpawnPoint( vec3_origin, origin, angles ); + } + + VectorCopy( spot->s.origin, origin ); + origin[2] += 9; + VectorCopy( spot->s.angles, angles ); + + return spot; +} + +/* +=========== +SelectSpectatorSpawnPoint + +============ +*/ +gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { + FindIntermissionPoint(); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +/* +=============== +InitBodyQue +=============== +*/ +void InitBodyQue( void ) { + int i; + gentity_t *ent; + + level.bodyQueIndex = 0; + for ( i = 0; i < BODY_QUEUE_SIZE ; i++ ) { + ent = G_Spawn(); + ent->classname = "bodyque"; + ent->neverFree = qtrue; + level.bodyQue[i] = ent; + } +} + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink( gentity_t *ent ) { + if ( level.time - ent->timestamp > 6500 ) { + // the body ques are never actually freed, they are just unlinked + trap_UnlinkEntity( ent ); + ent->physicsObject = qfalse; + return; + } + ent->nextthink = level.time + 100; + ent->s.pos.trBase[2] -= 1; +} + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +void CopyToBodyQue( gentity_t *ent ) { + gentity_t *body; + int contents, i; + + trap_UnlinkEntity( ent ); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->s.origin, -1 ); + if ( contents & CONTENTS_NODROP ) { + return; + } + + // grab a body que and cycle to the next one + body = level.bodyQue[ level.bodyQueIndex ]; + level.bodyQueIndex = ( level.bodyQueIndex + 1 ) % BODY_QUEUE_SIZE; + + trap_UnlinkEntity( body ); + + body->s = ent->s; + body->s.eFlags = EF_DEAD; // clear EF_TALK, etc + + if ( ent->client->ps.eFlags & EF_HEADSHOT ) { + body->s.eFlags |= EF_HEADSHOT; // make sure the dead body draws no head (if killed that way) + + } + body->s.eType = ET_CORPSE; + body->classname = "corpse"; + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + } else { + body->s.pos.trType = TR_STATIONARY; + } + body->s.event = 0; + + // DHM - Clear out event system + for ( i = 0; i < MAX_EVENTS; i++ ) + body->s.events[i] = 0; + body->s.eventSequence = 0; + + // DHM - Nerve + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + // change the animation to the last-frame only, so the sequence + // doesn't repeat anew for the body + switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { + case BOTH_DEATH1: + case BOTH_DEAD1: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; + break; + case BOTH_DEATH3: + case BOTH_DEAD3: + default: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; + break; + } + } + // dhm + + body->r.svFlags = ent->r.svFlags; + VectorCopy( ent->r.mins, body->r.mins ); + VectorCopy( ent->r.maxs, body->r.maxs ); + VectorCopy( ent->r.absmin, body->r.absmin ); + VectorCopy( ent->r.absmax, body->r.absmax ); + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + // DHM - Nerve :: allow bullets to pass through bbox + body->r.contents = 0; + body->r.ownerNum = ent->r.ownerNum; + + body->nextthink = level.time + 5000; + body->think = BodySink; + + body->die = body_die; + + // don't take more damage if already gibbed + if ( ent->health <= GIB_HEALTH ) { + body->takedamage = qfalse; + } else { + body->takedamage = qtrue; + } + + + VectorCopy( body->s.pos.trBase, body->r.currentOrigin ); + trap_LinkEntity( body ); +} + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { + int i; + + // set the delta angle + for ( i = 0 ; i < 3 ; i++ ) { + int cmdAngle; + + cmdAngle = ANGLE2SHORT( angle[i] ); + ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; + } + VectorCopy( angle, ent->s.angles ); + VectorCopy( ent->s.angles, ent->client->ps.viewangles ); +} + +/* JPW NERVE +================ +limbo +================ +*/ +void limbo( gentity_t *ent, qboolean makeCorpse ) { + int i,contents; + //int startclient = ent->client->sess.spectatorClient; + int startclient = ent->client->ps.clientNum; + + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + G_Printf( "FIXME: limbo called from single player game. Shouldn't see this\n" ); + return; + } + if ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) { + + // DHM - Nerve :: First save off persistant info we'll need for respawn + for ( i = 0; i < MAX_PERSISTANT; i++ ) + ent->client->saved_persistant[i] = ent->client->ps.persistant[i]; + // dhm + + ent->client->ps.pm_flags |= PMF_LIMBO; + ent->client->ps.pm_flags |= PMF_FOLLOW; + + if ( makeCorpse ) { + CopyToBodyQue( ent ); // make a nice looking corpse + } else { + trap_UnlinkEntity( ent ); + } + + // DHM - Nerve :: reset these values + ent->client->ps.viewlocked = 0; + ent->client->ps.viewlocked_entNum = 0; + + ent->r.maxs[2] = 0; + ent->r.currentOrigin[2] += 8; + contents = trap_PointContents( ent->r.currentOrigin, -1 ); // drop stuff + ent->s.weapon = ent->client->limboDropWeapon; // stored in player_die() + if ( makeCorpse && !( contents & CONTENTS_NODROP ) ) { + TossClientItems( ent ); + } + + ent->client->sess.spectatorClient = startclient; + Cmd_FollowCycle_f( ent,1 ); // get fresh spectatorClient + + if ( ent->client->sess.spectatorClient == startclient ) { + // No one to follow, so just stay put + ent->client->sess.spectatorState = SPECTATOR_FREE; + } else { + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + } + +// ClientUserinfoChanged( ent->client - level.clients ); // NERVE - SMF - don't do this + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + ent->client->deployQueueNumber = level.redNumWaiting; + level.redNumWaiting++; + } else if ( ent->client->sess.sessionTeam == TEAM_BLUE ) { + ent->client->deployQueueNumber = level.blueNumWaiting; + level.blueNumWaiting++; + } + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].ps.pm_flags & PMF_LIMBO + && level.clients[i].sess.spectatorClient == ent->s.number ) { + Cmd_FollowCycle_f( &g_entities[i], 1 ); + } + } + } +} + +/* JPW NERVE +================ +reinforce +================ +// -- called when time expires for a team deployment cycle and there is at least one guy ready to go +*/ +void reinforce( gentity_t *ent ) { + int p, team; // numDeployable=0, finished=0; // TTimo unused + char *classname; + gclient_t *rclient; + + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + G_Printf( "FIXME: reinforce called from single player game. Shouldn't see this\n" ); + return; + } + if ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) { + G_Printf( "player already deployed, skipping\n" ); + return; + } + // get team to deploy from passed entity + + team = ent->client->sess.sessionTeam; + + // find number active team spawnpoints + if ( team == TEAM_RED ) { + classname = "team_CTF_redspawn"; + } else if ( team == TEAM_BLUE ) { + classname = "team_CTF_bluespawn"; + } else { + assert( 0 ); + } + + // DHM - Nerve :: restore persistant data now that we're out of Limbo + rclient = ent->client; + for ( p = 0; p < MAX_PERSISTANT; p++ ) + rclient->ps.persistant[p] = rclient->saved_persistant[p]; + // dhm + + respawn( ent ); +} +// jpw + + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) { + //gentity_t *tent; + + // Ridah, if single player, reload the last saved game for this player + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + + if ( reloading || saveGamePending ) { + return; + } + + if ( !( ent->r.svFlags & SVF_CASTAI ) ) { + // Fast method, just do a map_restart, and then load in the savegame + // once everything is settled. + trap_SetConfigstring( CS_SCREENFADE, va( "1 %i 500", level.time + 250 ) ); + reloading = qtrue; + level.reloadDelayTime = level.time + 1500; + + return; + } + } + + // done. + + ent->client->ps.pm_flags &= ~PMF_LIMBO; // JPW NERVE turns off limbo + + // DHM - Nerve :: Decrease the number of respawns left + if ( g_maxlives.integer > 0 && ent->client->ps.persistant[PERS_RESPAWNS_LEFT] > 0 ) { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT]--; + } + + G_DPrintf( "Respawning %s, %i lives left\n", ent->client->pers.netname, ent->client->ps.persistant[PERS_RESPAWNS_LEFT] ); + + // DHM - Nerve :: Already handled in 'limbo()' + if ( g_gametype.integer < GT_WOLF ) { + CopyToBodyQue( ent ); + } + + ClientSpawn( ent, qfalse ); + + // DHM - Nerve :: Add back if we decide to have a spawn effect + // add a teleportation effect + //tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + //tent->s.clientNum = ent->s.clientNum; +} + +// NERVE - SMF - merge from team arena +/* +================ +TeamCount + +Returns number of players on a team +================ +*/ +team_t TeamCount( int ignoreClientNum, int team ) { + int i; + int count = 0; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( i == ignoreClientNum ) { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + count++; + } + } + + return count; +} +// -NERVE - SMF + +/* +================ +PickTeam + +================ +*/ +team_t PickTeam( int ignoreClientNum ) { + int i; + int counts[TEAM_NUM_TEAMS]; + + memset( counts, 0, sizeof( counts ) ); + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( i == ignoreClientNum ) { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == TEAM_BLUE ) { + counts[TEAM_BLUE]++; + } else if ( level.clients[i].sess.sessionTeam == TEAM_RED ) { + counts[TEAM_RED]++; + } + } + + if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { + return TEAM_RED; + } + if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { + return TEAM_BLUE; + } + // equal team count, so join the team with the lowest score + if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { + return TEAM_RED; + } + return TEAM_BLUE; +} + +/* +=========== +ForceClientSkin + +Forces a client's skin (for teamplay) +=========== +*/ +void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { + char *p; + + if ( ( p = strchr( model, '/' ) ) != NULL ) { + *p = 0; + } + + Q_strcat( model, MAX_QPATH, "/" ); + Q_strcat( model, MAX_QPATH, skin ); +} + +// NERVE - SMF +/* +=========== +SetWolfUserVars +=========== +*/ +void SetWolfUserVars( gentity_t *ent, char *wolfinfo ) { + gclient_t *client; + int mask, team; + + client = ent->client; + if ( !client ) { + return; + } + + // check if we have a valid snapshot + mask = MP_TEAM_MASK; + team = ( client->pers.cmd.mpSetup & mask ) >> MP_TEAM_OFFSET; + + if ( !team ) { + return; + } + + // set player class + mask = MP_CLASS_MASK; + client->sess.latchPlayerType = ( client->pers.cmd.mpSetup & mask ) >> MP_CLASS_OFFSET; + + // set weapon + mask = MP_WEAPON_MASK; + client->sess.latchPlayerWeapon = ( client->pers.cmd.mpSetup & mask ) >> MP_WEAPON_OFFSET; +} + +// -NERVE - SMF + + +// DHM - Nerve +/* +=========== +SetWolfSkin + +Forces a client's skin (for Wolfenstein teamplay) +=========== +*/ + +#define MULTIPLAYER_ALLIEDMODEL "multi" +#define MULTIPLAYER_AXISMODEL "multi_axis" + +void SetWolfSkin( gclient_t *client, char *model ) { + + switch ( client->sess.sessionTeam ) { + case TEAM_RED: + Q_strcat( model, MAX_QPATH, "red" ); + break; + case TEAM_BLUE: + Q_strcat( model, MAX_QPATH, "blue" ); + break; + default: + Q_strcat( model, MAX_QPATH, "red" ); + break; + } + + switch ( client->sess.playerType ) { + case PC_SOLDIER: + Q_strcat( model, MAX_QPATH, "soldier" ); + break; + case PC_MEDIC: + Q_strcat( model, MAX_QPATH, "medic" ); + break; + case PC_ENGINEER: + Q_strcat( model, MAX_QPATH, "engineer" ); + break; + case PC_LT: + Q_strcat( model, MAX_QPATH, "lieutenant" ); + break; + default: + Q_strcat( model, MAX_QPATH, "soldier" ); + break; + } + + // DHM - A skinnum will be in the session data soon... + switch ( client->sess.playerSkin ) { + case 1: + Q_strcat( model, MAX_QPATH, "1" ); + break; + case 2: + Q_strcat( model, MAX_QPATH, "2" ); + break; + case 3: + Q_strcat( model, MAX_QPATH, "3" ); + break; + default: + Q_strcat( model, MAX_QPATH, "1" ); + break; + } +} + +void SetWolfSpawnWeapons( gclient_t *client ) { + + int pc = client->sess.playerType; + int starthealth = 100,i,numMedics = 0; // JPW NERVE + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + + // Reset special weapon time + client->ps.classWeaponTime = -999999; + +// Xian -- Commented out and moved to ClientSpawn for clarity +// client->ps.powerups[PW_INVULNERABLE] = level.time + 3000; // JPW NERVE some time to find cover + + // Communicate it to cgame + client->ps.stats[STAT_PLAYER_CLASS] = pc; + + // Abuse teamNum to store player class as well (can't see stats for all clients in cgame) + client->ps.teamNum = pc; + + // JPW NERVE -- zero out all ammo counts + memset( client->ps.ammo,MAX_WEAPONS,sizeof( int ) ); + + // All players start with a knife (not OR-ing so that it clears previous weapons) + client->ps.weapons[0] = 0; + client->ps.weapons[1] = 0; + COM_BitSet( client->ps.weapons, WP_KNIFE ); + + client->ps.ammo[BG_FindAmmoForWeapon( WP_KNIFE )] = 1; + client->ps.weapon = WP_KNIFE; + client->ps.weaponstate = WEAPON_READY; + + // Engineer gets dynamite + if ( pc == PC_ENGINEER ) { + COM_BitSet( client->ps.weapons, WP_DYNAMITE ); + client->ps.ammo[BG_FindAmmoForWeapon( WP_DYNAMITE )] = 0; + client->ps.ammoclip[BG_FindClipForWeapon( WP_DYNAMITE )] = 1; + + // NERVE - SMF + COM_BitSet( client->ps.weapons, WP_PLIERS ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_PLIERS )] = 1; + client->ps.ammo[WP_PLIERS] = 1; + } + + if ( g_knifeonly.integer != 1 ) { + + // Lieutenant gets binoculars, ammo pack, artillery, and a grenade + if ( pc == PC_LT ) { + client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); + COM_BitSet( client->ps.weapons, WP_AMMO ); + client->ps.ammo[BG_FindAmmoForWeapon( WP_AMMO )] = 0; + client->ps.ammoclip[BG_FindClipForWeapon( WP_AMMO )] = 1; + COM_BitSet( client->ps.weapons, WP_ARTY ); + client->ps.ammo[BG_FindAmmoForWeapon( WP_ARTY )] = 0; + client->ps.ammoclip[BG_FindClipForWeapon( WP_ARTY )] = 1; + + // NERVE - SMF + COM_BitSet( client->ps.weapons, WP_SMOKE_GRENADE ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_SMOKE_GRENADE )] = 1; + client->ps.ammo[WP_SMOKE_GRENADE] = 1; + + switch ( client->sess.sessionTeam ) { + case TEAM_BLUE: + COM_BitSet( client->ps.weapons, WP_GRENADE_PINEAPPLE ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_PINEAPPLE )] = 1; + break; + case TEAM_RED: + COM_BitSet( client->ps.weapons, WP_GRENADE_LAUNCHER ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_LAUNCHER )] = 1; + break; + default: + COM_BitSet( client->ps.weapons, WP_GRENADE_PINEAPPLE ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_PINEAPPLE )] = 1; + break; + } + } + + // Everyone gets a pistol + switch ( client->sess.sessionTeam ) { // JPW NERVE was playerPistol + + case TEAM_RED: // JPW NERVE + COM_BitSet( client->ps.weapons, WP_LUGER ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_LUGER )] += 8; + client->ps.ammo[BG_FindAmmoForWeapon( WP_LUGER )] += 24; + client->ps.weapon = WP_LUGER; + break; + default: // '0' // TEAM_BLUE + COM_BitSet( client->ps.weapons, WP_COLT ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_COLT )] += 8; + client->ps.ammo[BG_FindAmmoForWeapon( WP_COLT )] += 24; + client->ps.weapon = WP_COLT; + break; + } + + // Everyone except Medic and LT get some grenades + if ( ( pc != PC_LT ) && ( pc != PC_MEDIC ) ) { // JPW NERVE + + switch ( client->sess.sessionTeam ) { // was playerItem + + case TEAM_BLUE: + COM_BitSet( client->ps.weapons, WP_GRENADE_PINEAPPLE ); + client->ps.ammo[BG_FindAmmoForWeapon( WP_GRENADE_PINEAPPLE )] = 0; + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_PINEAPPLE )] = 4 + 4 * ( pc == PC_ENGINEER ); // JPW NERVE + break; + case TEAM_RED: + COM_BitSet( client->ps.weapons, WP_GRENADE_LAUNCHER ); + client->ps.ammo[BG_FindAmmoForWeapon( WP_GRENADE_LAUNCHER )] = 0; + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_LAUNCHER )] = 4 + 4 * ( pc == PC_ENGINEER ); // JPW NERVE + break; + default: + COM_BitSet( client->ps.weapons, WP_GRENADE_PINEAPPLE ); + client->ps.ammo[BG_FindAmmoForWeapon( WP_GRENADE_PINEAPPLE )] = 0; + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_PINEAPPLE )] = 4 + 4 * ( pc == PC_ENGINEER ); // JPW NERVE + break; + } + } + + + // JPW NERVE + if ( pc == PC_MEDIC ) { + COM_BitSet( client->ps.weapons, WP_MEDIC_SYRINGE ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_MEDIC_SYRINGE )] = 10; + + // NERVE - SMF + COM_BitSet( client->ps.weapons, WP_MEDKIT ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_MEDKIT )] = 1; + client->ps.ammo[WP_MEDKIT] = 1; + + switch ( client->sess.sessionTeam ) { // was playerItem + case TEAM_BLUE: + COM_BitSet( client->ps.weapons, WP_GRENADE_PINEAPPLE ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_PINEAPPLE )] = 1; + break; + case TEAM_RED: + COM_BitSet( client->ps.weapons, WP_GRENADE_LAUNCHER ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_LAUNCHER )] = 1; + break; + default: + COM_BitSet( client->ps.weapons, WP_GRENADE_PINEAPPLE ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_GRENADE_PINEAPPLE )] = 1; + break; + } + } + // jpw + + // Soldiers and Lieutenants get a 2-handed weapon + if ( pc == PC_SOLDIER || pc == PC_LT ) { + + // JPW NERVE -- if LT is selected but illegal weapon, set to team-specific SMG + if ( ( pc == PC_LT ) && ( client->sess.playerWeapon > 5 ) ) { + if ( client->sess.sessionTeam == TEAM_RED ) { + client->sess.playerWeapon = 3; + } else { + client->sess.playerWeapon = 4; + } + } + // jpw + switch ( client->sess.playerWeapon ) { + + case 3: // WP_MP40 + COM_BitSet( client->ps.weapons, WP_MP40 ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_MP40 )] += 32; + if ( pc == PC_SOLDIER ) { + client->ps.ammo[BG_FindAmmoForWeapon( WP_MP40 )] += 64; + } else { + client->ps.ammo[BG_FindAmmoForWeapon( WP_MP40 )] += 32; + } + client->ps.weapon = WP_MP40; + break; + + case 4: // WP_THOMPSON + COM_BitSet( client->ps.weapons, WP_THOMPSON ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_THOMPSON )] += 30; + if ( pc == PC_SOLDIER ) { + client->ps.ammo[BG_FindAmmoForWeapon( WP_THOMPSON )] += 60; + } else { + client->ps.ammo[BG_FindAmmoForWeapon( WP_THOMPSON )] += 30; + } + client->ps.weapon = WP_THOMPSON; + break; + + case 5: // WP_STEN + COM_BitSet( client->ps.weapons, WP_STEN ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_STEN )] += 32; + if ( pc == PC_SOLDIER ) { + client->ps.ammo[BG_FindAmmoForWeapon( WP_STEN )] += 64; + } else { + client->ps.ammo[BG_FindAmmoForWeapon( WP_STEN )] += 32; + } + client->ps.weapon = WP_STEN; + break; + + case 6: // WP_MAUSER, WP_SNIPERRIFLE + if ( pc != PC_SOLDIER ) { + return; + } + + COM_BitSet( client->ps.weapons, WP_SNIPERRIFLE ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_SNIPERRIFLE )] = 10; + client->ps.ammo[BG_FindAmmoForWeapon( WP_SNIPERRIFLE )] = 10; + client->ps.weapon = WP_SNIPERRIFLE; + + COM_BitSet( client->ps.weapons, WP_MAUSER ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_MAUSER )] = 10; + client->ps.ammo[BG_FindAmmoForWeapon( WP_MAUSER )] = 10; + client->ps.weapon = WP_MAUSER; + break; + + case 8: // WP_PANZERFAUST + if ( pc != PC_SOLDIER ) { + return; + } + + COM_BitSet( client->ps.weapons, WP_PANZERFAUST ); + client->ps.ammo[BG_FindAmmoForWeapon( WP_PANZERFAUST )] = 4; + client->ps.weapon = WP_PANZERFAUST; + break; + + case 9: // WP_VENOM + if ( pc != PC_SOLDIER ) { + return; + } + COM_BitSet( client->ps.weapons, WP_VENOM ); + client->ps.ammoclip[BG_FindAmmoForWeapon( WP_VENOM )] = 500; + client->ps.weapon = WP_VENOM; + break; + + case 10: // WP_FLAMETHROWER + if ( pc != PC_SOLDIER ) { + return; + } + + COM_BitSet( client->ps.weapons, WP_FLAMETHROWER ); + client->ps.ammoclip[BG_FindAmmoForWeapon( WP_FLAMETHROWER )] = 200; + client->ps.weapon = WP_FLAMETHROWER; + break; + + default: // give MP40 if given invalid weapon number + if ( client->sess.sessionTeam == TEAM_RED ) { // JPW NERVE + COM_BitSet( client->ps.weapons, WP_MP40 ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_MP40 )] += 32; + if ( pc == PC_SOLDIER ) { + client->ps.ammo[BG_FindAmmoForWeapon( WP_MP40 )] += 64; + } else { + client->ps.ammo[BG_FindAmmoForWeapon( WP_MP40 )] += 32; + } + client->ps.weapon = WP_MP40; + } else { // TEAM_BLUE + COM_BitSet( client->ps.weapons, WP_THOMPSON ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_THOMPSON )] += 30; + if ( pc == PC_SOLDIER ) { + client->ps.ammo[BG_FindAmmoForWeapon( WP_THOMPSON )] += 60; + } else { + client->ps.ammo[BG_FindAmmoForWeapon( WP_THOMPSON )] += 30; + } + client->ps.weapon = WP_THOMPSON; + } + break; + } + } else { // medic or engineer gets assigned MP40 or Thompson with one magazine ammo + if ( client->sess.sessionTeam == TEAM_RED ) { // axis + COM_BitSet( client->ps.weapons, WP_MP40 ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_MP40 )] += 32; + // JPW NERVE + if ( pc == PC_ENGINEER ) { // OK so engineers get two mags + client->ps.ammo[BG_FindAmmoForWeapon( WP_MP40 )] += 32; + } + // jpw + client->ps.weapon = WP_MP40; + } else { // allied + COM_BitSet( client->ps.weapons, WP_THOMPSON ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_THOMPSON )] += 30; + // JPW NERVE + if ( pc == PC_ENGINEER ) { + client->ps.ammo[BG_FindAmmoForWeapon( WP_THOMPSON )] += 32; + } + // jpw + client->ps.weapon = WP_THOMPSON; + } + } + + } else // Knifeonly block + { + if ( pc == PC_MEDIC ) { + COM_BitSet( client->ps.weapons, WP_MEDIC_SYRINGE ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_MEDIC_SYRINGE )] = 20; + + // NERVE - SMF + COM_BitSet( client->ps.weapons, WP_MEDKIT ); + client->ps.ammoclip[BG_FindClipForWeapon( WP_MEDKIT )] = 1; + client->ps.ammo[WP_MEDKIT] = 1; + } + } // End Knifeonly stuff -- Ensure that medics get their basic stuff + + + // JPW NERVE -- medics on each team make cumulative health bonus -- this gets overridden for "revived" players + // count up # of medics on team + for ( i = 0; i < level.maxclients; i++ ) { + if ( level.clients[i].pers.connected != CON_CONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam != client->sess.sessionTeam ) { + continue; + } + if ( level.clients[i].ps.stats[STAT_PLAYER_CLASS] != PC_MEDIC ) { + continue; + } + numMedics++; + } + + // compute health mod + starthealth = 100 + 10 * numMedics; + if ( starthealth > 125 ) { + starthealth = 125; + } + + // give everybody health mod in stat_max_health + for ( i = 0; i < level.maxclients; i++ ) { + if ( level.clients[i].pers.connected != CON_CONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == client->sess.sessionTeam ) { + client->ps.stats[STAT_MAX_HEALTH] = starthealth; + } + } + // jpw +} +// dhm - end + + +/* +=========== +ClientCheckName +============ +*/ +static void ClientCleanName( const char *in, char *out, int outSize ) { + int len, colorlessLen; + char ch; + char *p; + int spaces; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + while ( 1 ) { + ch = *in++; + if ( !ch ) { + break; + } + + // don't allow leading spaces + if ( !*p && ch == ' ' ) { + continue; + } + + // check colors + if ( ch == Q_COLOR_ESCAPE ) { + // solo trailing carat is not a color prefix + if ( !*in ) { + break; + } + + // don't allow black in a name, period + if ( ColorIndex( *in ) == 0 ) { + in++; + continue; + } + + // make sure room in dest for both chars + if ( len > outSize - 2 ) { + break; + } + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if ( ch == ' ' ) { + spaces++; + if ( spaces > 3 ) { + continue; + } + } else { + spaces = 0; + } + + if ( len > outSize - 1 ) { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + *out = 0; + + // don't allow empty names + if ( *p == 0 || colorlessLen == 0 ) { + Q_strncpyz( p, "UnnamedPlayer", outSize ); + } +} + +/* +================== +G_CheckForExistingModelInfo + + If this player model has already been parsed, then use the existing information. + Otherwise, set the modelInfo pointer to the first free slot. + + returns qtrue if existing model found, qfalse otherwise +================== +*/ +qboolean G_CheckForExistingModelInfo( gclient_t *cl, char *modelName, animModelInfo_t **modelInfo ) { + int i; + animModelInfo_t *trav, *firstFree = NULL; + gclient_t *cl_trav; + char modelsUsed[MAX_ANIMSCRIPT_MODELS]; + + for ( i = 0, trav = level.animScriptData.modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, trav++ ) { + if ( trav->modelname[0] ) { + if ( !Q_stricmp( trav->modelname, modelName ) ) { + // found a match, use this modelinfo + *modelInfo = trav; + level.animScriptData.clientModels[cl->ps.clientNum] = i + 1; + return qtrue; + } + } else if ( !firstFree ) { + firstFree = trav; + level.animScriptData.clientModels[cl->ps.clientNum] = i + 1; + } + } + + // set the modelInfo to the first free slot + if ( !firstFree ) { + // attempt to free a model that is no longer being used + memset( modelsUsed, 0, sizeof( modelsUsed ) ); + for ( i = 0, cl_trav = level.clients; i < MAX_CLIENTS; i++, cl_trav++ ) { + if ( cl_trav != cl && g_entities[cl_trav->ps.clientNum].inuse && cl_trav->modelInfo ) { + modelsUsed[ (int)( cl_trav->modelInfo - level.animScriptData.modelInfo ) ] = 1; + } + } + // now use the first slot that isn't being utilized + for ( i = 0, trav = level.animScriptData.modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, trav++ ) { + if ( !modelsUsed[i] ) { + firstFree = trav; + level.animScriptData.clientModels[cl->ps.clientNum] = i + 1; + break; + } + } + } + + if ( !firstFree ) { + G_Error( "unable to find a free modelinfo slot, cannot continue\n" ); + } else { + *modelInfo = firstFree; + // clear the structure out ready for use + memset( *modelInfo, 0, sizeof( *modelInfo ) ); + } + // qfalse signifies that we need to parse the information from the script files + return qfalse; +} + +/* +============= +G_ParseAnimationFiles +============= +*/ +static char text[100000]; // <- was causing callstacks >64k + +qboolean G_ParseAnimationFiles( char *modelname, gclient_t *cl ) { + char filename[MAX_QPATH]; + fileHandle_t f; + int len; + + // set the name of the model in the modelinfo structure + Q_strncpyz( cl->modelInfo->modelname, modelname, sizeof( cl->modelInfo->modelname ) ); + + // load the cfg file + Com_sprintf( filename, sizeof( filename ), "models/players/%s/wolfanim.cfg", modelname ); + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + G_Printf( "G_ParseAnimationFiles(): file '%s' not found\n", filename ); //----(SA) added + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) { + G_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + BG_AnimParseAnimConfig( cl->modelInfo, filename, text ); + + // load the script file + Com_sprintf( filename, sizeof( filename ), "models/players/%s/wolfanim.script", modelname ); + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + if ( cl->modelInfo->version > 1 ) { + return qfalse; + } + // try loading the default script for old legacy models + Com_sprintf( filename, sizeof( filename ), "models/players/default.script", modelname ); + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + } + if ( len >= sizeof( text ) - 1 ) { + G_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + BG_AnimParseAnimScript( cl->modelInfo, &level.animScriptData, cl->ps.clientNum, filename, text ); + + // ask the client to send us the movespeeds if available + if ( g_gametype.integer == GT_SINGLE_PLAYER && g_entities[0].client && g_entities[0].client->pers.connected == CON_CONNECTED ) { + trap_SendServerCommand( 0, va( "mvspd %s", modelname ) ); + } + + return qtrue; +} + + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +void ClientUserinfoChanged( int clientNum ) { + + gentity_t *ent; + char *s; + char model[MAX_QPATH], modelname[MAX_QPATH]; + +//----(SA) added this for head separation + char head[MAX_QPATH]; + + char oldname[MAX_STRING_CHARS]; + gclient_t *client; + char *c1; + char userinfo[MAX_INFO_STRING]; + + ent = g_entities + clientNum; + client = ent->client; + + client->ps.clientNum = clientNum; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate( userinfo ) ) { + strcpy( userinfo, "\\name\\badinfo" ); + } + + // check for local client + s = Info_ValueForKey( userinfo, "ip" ); + if ( s && !strcmp( s, "localhost" ) ) { + client->pers.localClient = qtrue; + } + + // check the item prediction + s = Info_ValueForKey( userinfo, "cg_predictItems" ); + if ( !atoi( s ) ) { + client->pers.predictItemPickup = qfalse; + } else { + client->pers.predictItemPickup = qtrue; + } + + // check the auto activation + s = Info_ValueForKey( userinfo, "cg_autoactivate" ); + if ( !atoi( s ) ) { + client->pers.autoActivate = PICKUP_ACTIVATE; + } else { + client->pers.autoActivate = PICKUP_TOUCH; + } + + // check the auto reload setting + s = Info_ValueForKey( userinfo, "cg_autoReload" ); + if ( atoi( s ) ) { + client->pers.bAutoReloadAux = qtrue; + client->pmext.bAutoReload = qtrue; + } else { + client->pers.bAutoReloadAux = qfalse; + client->pmext.bAutoReload = qfalse; + } + + // set name + Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey( userinfo, "name" ); + ClientCleanName( s, client->pers.netname, sizeof( client->pers.netname ) ); + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) ); + } + } + + if ( client->pers.connected == CON_CONNECTED ) { + if ( strcmp( oldname, client->pers.netname ) ) { + trap_SendServerCommand( -1, va( "print \"[lof]%s" S_COLOR_WHITE " [lon]renamed to[lof] %s\n\"", oldname, + client->pers.netname ) ); + } + } + + // set max health + client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + + // set model + if ( g_forceModel.integer ) { + Q_strncpyz( model, DEFAULT_MODEL, sizeof( model ) ); + Q_strcat( model, sizeof( model ), "/default" ); + } else { + Q_strncpyz( model, Info_ValueForKey( userinfo, "model" ), sizeof( model ) ); + } + + // RF, reset anims so client's dont freak out + client->ps.legsAnim = 0; + client->ps.torsoAnim = 0; + + // DHM - Nerve :: Forcibly set both model and skin for multiplayer. + if ( g_gametype.integer >= GT_WOLF ) { + + // To communicate it to cgame + client->ps.stats[ STAT_PLAYER_CLASS ] = client->sess.playerType; + + if ( client->sess.sessionTeam == TEAM_BLUE ) { + Q_strncpyz( model, MULTIPLAYER_ALLIEDMODEL, MAX_QPATH ); + } else { + Q_strncpyz( model, MULTIPLAYER_AXISMODEL, MAX_QPATH ); + } + + Q_strcat( model, MAX_QPATH, "/" ); + + SetWolfSkin( client, model ); + + Q_strncpyz( head, "", MAX_QPATH ); + SetWolfSkin( client, head ); + } + + // strip the skin name + Q_strncpyz( modelname, model, sizeof( modelname ) ); + if ( strstr( modelname, "/" ) ) { + modelname[ strstr( modelname, "/" ) - modelname ] = 0; + } else if ( strstr( modelname, "\\" ) ) { + modelname[ strstr( modelname, "\\" ) - modelname ] = 0; + } + + if ( !G_CheckForExistingModelInfo( client, modelname, &client->modelInfo ) ) { + if ( !G_ParseAnimationFiles( modelname, client ) ) { + G_Error( "Failed to load animation scripts for model %s\n", modelname ); + } + } + + // team` + // DHM - Nerve :: Already took care of models and skins above + if ( g_gametype.integer < GT_WOLF ) { + + //----(SA) added this for head separation + // set head + if ( g_forceModel.integer ) { + Q_strncpyz( head, DEFAULT_HEAD, sizeof( head ) ); + } else { + Q_strncpyz( head, Info_ValueForKey( userinfo, "head" ), sizeof( head ) ); + } + + //----(SA) end + + switch ( client->sess.sessionTeam ) { + case TEAM_RED: + ForceClientSkin( client, model, "red" ); + break; + case TEAM_BLUE: + ForceClientSkin( client, model, "blue" ); + break; + default: // TEAM_FREE, TEAM_SPECTATOR, TEAM_NUM_TEAMS not handled in switch + break; + } + if ( g_gametype.integer >= GT_TEAM && client->sess.sessionTeam == TEAM_SPECTATOR ) { + // don't ever use a default skin in teamplay, it would just waste memory + ForceClientSkin( client, model, "red" ); + } + + } + //dhm - end + + + // colors + c1 = Info_ValueForKey( userinfo, "color" ); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + +//----(SA) modified these for head separation + + if ( ent->r.svFlags & SVF_BOT ) { + + s = va( "n\\%s\\t\\%i\\model\\%s\\head\\%s\\c1\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s", + client->pers.netname, client->sess.sessionTeam, model, head, c1, + client->pers.maxHealth, client->sess.wins, client->sess.losses, + Info_ValueForKey( userinfo, "skill" ) ); + } else { + s = va( "n\\%s\\t\\%i\\model\\%s\\head\\%s\\c1\\%s\\hc\\%i\\w\\%i\\l\\%i", + client->pers.netname, client->sess.sessionTeam, model, head, c1, + client->pers.maxHealth, client->sess.wins, client->sess.losses ); + } + +//----(SA) end + + trap_SetConfigstring( CS_PLAYERS + clientNum, s ); + + // this is not the userinfo actually, it's the config string + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); + G_DPrintf( "ClientUserinfoChanged: %i :: %s\n", clientNum, s ); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { + char *value; + gclient_t *client; + char userinfo[MAX_INFO_STRING]; + gentity_t *ent; + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // IP filtering + // show_bug.cgi?id=500 + // recommanding PB based IP / GUID banning, the builtin system is pretty limited + // check to see if they are on the banned IP list + value = Info_ValueForKey( userinfo, "ip" ); + if ( G_FilterPacket( value ) ) { + return "You are banned from this server."; + } + + // Xian - check for max lives enforcement ban + if ( g_enforcemaxlives.integer && ( g_maxlives.integer > 0 || g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0 ) ) { + value = Info_ValueForKey( userinfo, "cl_guid" ); + if ( G_FilterMaxLivesPacket( value ) ) { + return "Max Lives Enforcement Temp Ban"; + } + } + // End Xian + + // we don't check password for bots and local client + // NOTE: local client <-> "ip" "localhost" + // this means this client is not running in our current process + if ( !( ent->r.svFlags & SVF_BOT ) && ( strcmp( Info_ValueForKey( userinfo, "ip" ), "localhost" ) != 0 ) ) { + // check for a password + value = Info_ValueForKey( userinfo, "password" ); + if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value ) != 0 ) { + return "Invalid password"; + } + } + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + + memset( client, 0, sizeof( *client ) ); + + client->pers.connected = CON_CONNECTING; + client->pers.connectTime = level.time; // DHM - Nerve + + if ( firstTime ) { + client->pers.initialSpawn = qtrue; // DHM - Nerve + + } + client->pers.complaints = 0; // DHM - Nerve + + // read or initialize the session data + if ( firstTime || ( g_gametype.integer < GT_WOLF && level.newSession ) ) { + G_InitSessionData( client, userinfo ); + } + G_ReadSessionData( client ); + + if ( isBot ) { + ent->r.svFlags |= SVF_BOT; + ent->inuse = qtrue; + if ( !G_BotConnect( clientNum, !firstTime ) ) { + return "BotConnectfailed"; + } + } + + // get and distribute relevent paramters + G_LogPrintf( "ClientConnect: %i\n", clientNum ); + ClientUserinfoChanged( clientNum ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if ( firstTime ) { + // Ridah + if ( !ent->r.svFlags & SVF_CASTAI ) { + // done. + trap_SendServerCommand( -1, va( "print \"[lof]%s" S_COLOR_WHITE " [lon]connected\n\"", client->pers.netname ) ); + } + } + + // count current clients and rank for scoreboard + CalculateRanks(); + + return NULL; +} + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the level. This will happen every level load, +and on transition between teams, but doesn't happen on respawns +============ +*/ +void ClientBegin( int clientNum ) { + gentity_t *ent; + gclient_t *client; + //gentity_t *tent; + int flags; + int spawn_count; // DHM - Nerve + + ent = g_entities + clientNum; + + if ( ent->botDelayBegin ) { + G_QueueBotBegin( clientNum ); + ent->botDelayBegin = qfalse; + return; + } + + client = level.clients + clientNum; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + client->pers.connected = CON_CONNECTED; + // ATVI Wolfenstein Misc #414 + // don't reset the enterTime during a map_restart, we only want this when user explicitely changes team (and upon entering map) + if ( !trap_Cvar_VariableIntegerValue( "sv_serverRestarting" ) ) { + client->pers.enterTime = level.time; + } + client->pers.teamState.state = TEAM_BEGIN; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + // DHM - Nerve :: Also save PERS_SPAWN_COUNT, so that CG_Respawn happens + spawn_count = client->ps.persistant[PERS_SPAWN_COUNT]; + flags = client->ps.eFlags; + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + client->ps.persistant[PERS_SPAWN_COUNT] = spawn_count; + + // MrE: use capsule for collision + //client->ps.eFlags |= EF_CAPSULE; + //ent->r.svFlags |= SVF_CAPSULE; + + client->pers.complaintClient = -1; + client->pers.complaintEndTime = -1; + + // locate ent at a spawn point + ClientSpawn( ent, qfalse ); + + // Xian -- Changed below for team independant maxlives + + if ( g_maxlives.integer > 0 ) { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT] = ( g_maxlives.integer - 1 ); + } else { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT] = -1; + } + + if ( g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0 ) { + if ( client->sess.sessionTeam == TEAM_RED ) { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT] = ( g_axismaxlives.integer - 1 ); + } else if ( client->sess.sessionTeam == TEAM_BLUE ) { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT] = ( g_alliedmaxlives.integer - 1 ); + } else { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT] = -1; + } + } + + // DHM - Nerve :: Start players in limbo mode if they change teams during the match + if ( g_gametype.integer >= GT_WOLF && client->sess.sessionTeam != TEAM_SPECTATOR + && ( level.time - client->pers.connectTime ) > 60000 ) { + ent->client->ps.pm_type = PM_DEAD; + ent->r.contents = CONTENTS_CORPSE; + ent->health = 0; + ent->client->ps.stats[STAT_HEALTH] = 0; + + if ( g_maxlives.integer > 0 ) { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT]++; + } + + limbo( ent, qfalse ); + } + + // Ridah, trigger a spawn event + // DHM - Nerve :: Only in single player + if ( g_gametype.integer == GT_SINGLE_PLAYER && !( ent->r.svFlags & SVF_CASTAI ) ) { + AICast_ScriptEvent( AICast_GetCastState( clientNum ), "spawn", "" ); + } + + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { + // send event + // DHM - Nerve :: Add back if we decide to have a spawn effect + //tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + //tent->s.clientNum = ent->s.clientNum; + + if ( g_gametype.integer != GT_TOURNAMENT ) { + // Ridah + if ( !ent->r.svFlags & SVF_CASTAI ) { + // done. + trap_SendServerCommand( -1, va( "print \"[lof]%s" S_COLOR_WHITE " [lon]entered the game\n\"", client->pers.netname ) ); + } + } + } + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // Xian - Check for maxlives enforcement + if ( g_enforcemaxlives.integer == 1 && ( g_maxlives.integer > 0 || g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0 ) ) { + char *value; + char userinfo[MAX_INFO_STRING]; + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + value = Info_ValueForKey( userinfo, "cl_guid" ); + G_LogPrintf( "EnforceMaxLives-GUID: %s\n", value ); + AddMaxLivesGUID( value ); + } + // End Xian + + // count current clients and rank for scoreboard + CalculateRanks(); + +} + +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState +============ +*/ +void ClientSpawn( gentity_t *ent, qboolean revived ) { + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[MAX_PERSISTANT]; + gentity_t *spawnPoint; + int flags; + int savedPing; + int savedTeam; + qboolean savedVoted = qfalse; // NERVE - SMF + + index = ent - g_entities; + client = ent->client; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + + if ( revived ) { + spawnPoint = ent; + VectorCopy( ent->s.origin, spawn_origin ); + spawn_origin[2] += 9; // spawns seem to be sunk into ground? + VectorCopy( ent->s.angles, spawn_angles ); + } else + { + ent->aiName = "player"; // needed for script AI + //ent->aiTeam = 1; // member of allies + //ent->client->ps.teamNum = ent->aiTeam; + //AICast_ScriptParse( AICast_GetCastState(ent->s.number) ); + // done. + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + spawnPoint = SelectSpectatorSpawnPoint( + spawn_origin, spawn_angles ); + } else if ( g_gametype.integer >= GT_TEAM ) { + spawnPoint = SelectCTFSpawnPoint( + client->sess.sessionTeam, + client->pers.teamState.state, + spawn_origin, spawn_angles, client->sess.spawnObjectiveIndex ); + } else { + do { + // the first spawn should be at a good looking spot + if ( !client->pers.initialSpawn && client->pers.localClient ) { + client->pers.initialSpawn = qtrue; + spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); + } else { + // don't spawn near existing origin if possible + spawnPoint = SelectSpawnPoint( + client->ps.origin, + spawn_origin, spawn_angles ); + } + + if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + // just to be symetric, we have a nohumans option... + if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + + break; + + } while ( 1 ); + } + } + + client->pers.teamState.state = TEAM_ACTIVE; + + // toggle the teleport bit so the client knows to not lerp + flags = ent->client->ps.eFlags & EF_TELEPORT_BIT; + flags ^= EF_TELEPORT_BIT; + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; + savedTeam = client->ps.teamNum; + + // NERVE - SMF + if ( client->ps.eFlags & EF_VOTED ) { + savedVoted = qtrue; + } + + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + persistant[i] = client->ps.persistant[i]; + } + + memset( client, 0, sizeof( *client ) ); + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; + client->ps.teamNum = savedTeam; + + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + client->ps.persistant[i] = persistant[i]; + } + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; + + client->airOutTime = level.time + 12000; + + // clear entity values + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + client->ps.eFlags = flags; + // MrE: use capsules for AI and player + //client->ps.eFlags |= EF_CAPSULE; + + // TTimo + if ( savedVoted ) { + client->ps.eFlags |= EF_VOTED; + } + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->takedamage = qtrue; + ent->inuse = qtrue; + if ( !( ent->r.svFlags & SVF_CASTAI ) ) { + ent->classname = "player"; + } + ent->r.contents = CONTENTS_BODY; + + // RF, AI should be clipped by monsterclip brushes + if ( ent->r.svFlags & SVF_CASTAI ) { + ent->clipmask = MASK_PLAYERSOLID | CONTENTS_MONSTERCLIP; + } else { + ent->clipmask = MASK_PLAYERSOLID; + } + + // DHM - Nerve :: Init to -1 on first spawn; + if ( !revived ) { + ent->props_frame_state = -1; + } + + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + VectorCopy( playerMins, ent->r.mins ); + VectorCopy( playerMaxs, ent->r.maxs ); + + // Ridah, setup the bounding boxes and viewheights for prediction + VectorCopy( ent->r.mins, client->ps.mins ); + VectorCopy( ent->r.maxs, client->ps.maxs ); + + client->ps.crouchViewHeight = CROUCH_VIEWHEIGHT; + client->ps.standViewHeight = DEFAULT_VIEWHEIGHT; + client->ps.deadViewHeight = DEAD_VIEWHEIGHT; + + client->ps.crouchMaxZ = client->ps.maxs[2] - ( client->ps.standViewHeight - client->ps.crouchViewHeight ); + + client->ps.runSpeedScale = 0.8; + client->ps.sprintSpeedScale = 1.1; + client->ps.crouchSpeedScale = 0.25; + + // Rafael + client->ps.sprintTime = 20000; + client->ps.sprintExertTime = 0; + + client->ps.friction = 1.0; + // done. + + // TTimo + // retrieve from the persistant storage (we use this in pmoveExt_t beause we need it in bg_*) + client->pmext.bAutoReload = client->pers.bAutoReloadAux; + // done + + client->ps.clientNum = index; + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); // NERVE - SMF - moved this up here + + SetWolfUserVars( ent, NULL ); // NERVE - SMF + + // DHM - Nerve :: Add appropriate weapons + if ( g_gametype.integer >= GT_WOLF ) { + + if ( !revived ) { + qboolean update = qfalse; + + if ( client->sess.playerType != client->sess.latchPlayerType ) { + update = qtrue; + } + + client->sess.playerType = client->sess.latchPlayerType; + client->sess.playerWeapon = client->sess.latchPlayerWeapon; + client->sess.playerItem = client->sess.latchPlayerItem; + client->sess.playerSkin = client->sess.latchPlayerSkin; + + if ( update ) { + ClientUserinfoChanged( index ); + } + } + + // TTimo keep it isolated from spectator to be safe still + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { + // Xian - Moved the invul. stuff out of SetWolfSpawnWeapons and put it here for clarity + if ( g_fastres.integer == 1 && revived ) { + client->ps.powerups[PW_INVULNERABLE] = level.time + g_fastResMsec.integer; + } else { + client->ps.powerups[PW_INVULNERABLE] = level.time + 3000; + } + } + + // End Xian + SetWolfSpawnWeapons( client ); // JPW NERVE -- increases stats[STAT_MAX_HEALTH] based on # of medics in game + } + // dhm - end + + // JPW NERVE ***NOTE*** the following line is order-dependent and must *FOLLOW* SetWolfSpawnWeapons() in multiplayer + // SetWolfSpawnWeapons() now adds medic team bonus and stores in ps.stats[STAT_MAX_HEALTH]. + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + SetClientViewAngle( ent, spawn_angles ); + + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + //G_KillBox( ent ); + trap_LinkEntity( ent ); + } + + client->respawnTime = level.time; + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + client->latched_wbuttons = 0; //----(SA) added + + if ( level.intermissiontime ) { + MoveClientToIntermission( ent ); + } else { + // fire the targets of the spawn point + if ( !revived ) { + G_UseTargets( spawnPoint, ent ); + } + } + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent - g_entities ); + + // positively link the client, even if the command times are weird + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + + // show_bug.cgi?id=569 + G_ResetMarkers( ent ); +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) { + gentity_t *ent; + gentity_t *flag = NULL; + gitem_t *item = NULL; + vec3_t launchvel; + int i; + + ent = g_entities + clientNum; + if ( !ent->client ) { + return; + } + + // stop any following clients + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR + && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW + && level.clients[i].sess.spectatorClient == clientNum ) { + StopFollowing( &g_entities[i] ); + } + if ( g_gametype.integer >= GT_WOLF + && level.clients[i].ps.pm_flags & PMF_LIMBO + && level.clients[i].sess.spectatorClient == clientNum ) { + Cmd_FollowCycle_f( &g_entities[i], 1 ); + } + } + + // NERVE - SMF - remove complaint client + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.complaintClient == clientNum ) { + level.clients[i].pers.complaintClient = -1; + level.clients[i].pers.complaintEndTime = 0; + + trap_SendServerCommand( i, "complaint -2" ); + break; + } + } + + // send effect if they were completely connected + if ( ent->client->pers.connected == CON_CONNECTED + && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + + // They don't get to take powerups with them! + // Especially important for stuff like CTF flags + TossClientItems( ent ); + + // New code for tossing flags + if ( g_gametype.integer >= GT_WOLF ) { + if ( ent->client->ps.powerups[PW_REDFLAG] ) { + item = BG_FindItem( "Red Flag" ); + if ( !item ) { + item = BG_FindItem( "Objective" ); + } + + ent->client->ps.powerups[PW_REDFLAG] = 0; + } + if ( ent->client->ps.powerups[PW_BLUEFLAG] ) { + item = BG_FindItem( "Blue Flag" ); + if ( !item ) { + item = BG_FindItem( "Objective" ); + } + + ent->client->ps.powerups[PW_BLUEFLAG] = 0; + } + + if ( item ) { + launchvel[0] = crandom() * 20; + launchvel[1] = crandom() * 20; + launchvel[2] = 10 + random() * 10; + + flag = LaunchItem( item,ent->r.currentOrigin,launchvel,ent->s.number ); + flag->s.modelindex2 = ent->s.otherEntityNum2; // JPW NERVE FIXME set player->otherentitynum2 with old modelindex2 from flag and restore here + flag->message = ent->message; // DHM - Nerve :: also restore item name + // Clear out player's temp copies + ent->s.otherEntityNum2 = 0; + ent->message = NULL; + } + } + } + + G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); + + // if we are playing in tourney mode and losing, give a win to the other player + if ( g_gametype.integer == GT_TOURNAMENT && !level.intermissiontime + && !level.warmupTime && level.sortedClients[1] == clientNum ) { + level.clients[ level.sortedClients[0] ].sess.wins++; + ClientUserinfoChanged( level.sortedClients[0] ); + } + + trap_UnlinkEntity( ent ); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; + ent->client->sess.sessionTeam = TEAM_FREE; +// JPW NERVE -- mg42 additions + ent->active = 0; +// jpw + trap_SetConfigstring( CS_PLAYERS + clientNum, "" ); + + CalculateRanks(); + + if ( ent->r.svFlags & SVF_BOT ) { + BotAIShutdownClient( clientNum ); + } +} + + +/* +================== +G_RetrieveMoveSpeedsFromClient +================== +*/ +void G_RetrieveMoveSpeedsFromClient( int entnum, char *text ) { + char *text_p, *token; + animation_t *anim; + animModelInfo_t *modelInfo; + + text_p = text; + + // get the model name + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) { + G_Error( "G_RetrieveMoveSpeedsFromClient: internal error" ); + } + + modelInfo = BG_ModelInfoForModelname( token ); + + if ( !modelInfo ) { + // ignore it + return; + } + + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) { + break; + } + + // this is a name + anim = BG_AnimationForString( token, modelInfo ); + if ( anim->moveSpeed == 0 ) { + G_Error( "G_RetrieveMoveSpeedsFromClient: trying to set movespeed for non-moving animation" ); + } + + // get the movespeed + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) { + G_Error( "G_RetrieveMoveSpeedsFromClient: missing movespeed" ); + } + anim->moveSpeed = atoi( token ); + } +} diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c new file mode 100644 index 0000000..2c981ef --- /dev/null +++ b/src/game/g_cmds.c @@ -0,0 +1,2479 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage( gentity_t *ent ) { + char entry[1024]; + char string[1400]; + int stringlength; + int i, j; + gclient_t *cl; + int numSorted; + int scoreFlags; + + // send the latest information on all clients + string[0] = 0; + stringlength = 0; + scoreFlags = 0; + + // don't send more than 32 scores (FIXME?) + numSorted = level.numConnectedClients; + if ( numSorted > 32 ) { + numSorted = 32; + } + + for ( i = 0 ; i < numSorted ; i++ ) { + int ping; + int playerClass; + int respawnsLeft; + + cl = &level.clients[level.sortedClients[i]]; + + // NERVE - SMF - if on same team, send across player class + if ( cl->ps.persistant[PERS_TEAM] == ent->client->ps.persistant[PERS_TEAM] ) { + playerClass = cl->ps.stats[STAT_PLAYER_CLASS]; + } else { + playerClass = 0; + } + + // NERVE - SMF - number of respawns left + respawnsLeft = cl->ps.persistant[PERS_RESPAWNS_LEFT]; + if ( respawnsLeft == 0 && ( ( cl->ps.pm_flags & PMF_LIMBO ) || ( level.intermissiontime && g_entities[level.sortedClients[i]].health <= 0 ) ) ) { + respawnsLeft = -2; + } + + if ( cl->pers.connected == CON_CONNECTING ) { + ping = -1; + } else { + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + } + Com_sprintf( entry, sizeof( entry ), + " %i %i %i %i %i %i %i %i", level.sortedClients[i], + cl->ps.persistant[PERS_SCORE], ping, ( level.time - cl->pers.enterTime ) / 60000, + scoreFlags, g_entities[level.sortedClients[i]].s.powerups, playerClass, respawnsLeft ); + j = strlen( entry ); + if ( stringlength + j > 1024 ) { + break; + } + strcpy( string + stringlength, entry ); + stringlength += j; + } + + trap_SendServerCommand( ent - g_entities, va( "scores %i %i %i%s", i, + level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], + string ) ); +} + + +/* +================== +Cmd_Score_f + +Request current scoreboard information +================== +*/ +void Cmd_Score_f( gentity_t *ent ) { + DeathmatchScoreboardMessage( ent ); +} + + + +/* +================== +CheatsOk +================== +*/ +qboolean CheatsOk( gentity_t *ent ) { + if ( !g_cheats.integer ) { + trap_SendServerCommand( ent - g_entities, va( "print \"Cheats are not enabled on this server.\n\"" ) ); + return qfalse; + } + if ( ent->health <= 0 ) { + trap_SendServerCommand( ent - g_entities, va( "print \"You must be alive to use this command.\n\"" ) ); + return qfalse; + } + return qtrue; +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs( int start ) { + int i, c, tlen; + static char line[MAX_STRING_CHARS]; + int len; + char arg[MAX_STRING_CHARS]; + + len = 0; + c = trap_Argc(); + for ( i = start ; i < c ; i++ ) { + trap_Argv( i, arg, sizeof( arg ) ); + tlen = strlen( arg ); + if ( len + tlen >= MAX_STRING_CHARS - 1 ) { + break; + } + memcpy( line + len, arg, tlen ); + len += tlen; + if ( i != c - 1 ) { + line[len] = ' '; + len++; + } + } + + line[len] = 0; + + return line; +} + +/* +================== +SanitizeString + +Remove case and control characters +================== +*/ +void SanitizeString( char *in, char *out ) { + while ( *in ) { + if ( *in == 27 ) { + in += 2; // skip color code + continue; + } + if ( *in < 32 ) { + in++; + continue; + } + *out++ = tolower( *in++ ); + } + + *out = 0; +} + +/* +================== +ClientNumberFromString + +Returns a player number for either a number or name string +Returns -1 if invalid +================== +*/ +int ClientNumberFromString( gentity_t *to, char *s ) { + gclient_t *cl; + int idnum; + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + + // numeric values are just slot numbers + if ( s[0] >= '0' && s[0] <= '9' ) { + idnum = atoi( s ); + if ( idnum < 0 || idnum >= level.maxclients ) { + trap_SendServerCommand( to - g_entities, va( "print \"Bad client slot: [lof]%i\n\"", idnum ) ); + return -1; + } + + cl = &level.clients[idnum]; + if ( cl->pers.connected != CON_CONNECTED ) { + trap_SendServerCommand( to - g_entities, va( "print \"Client[lof] %i [lon]is not active\n\"", idnum ) ); + return -1; + } + return idnum; + } + + // check for a name match + SanitizeString( s, s2 ); + for ( idnum = 0,cl = level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + SanitizeString( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) { + return idnum; + } + } + + trap_SendServerCommand( to - g_entities, va( "print \"User [lof]%s [lon]is not on the server\n\"", s ) ); + return -1; +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( gentity_t *ent ) { + char *name, *amt; + gitem_t *it; + int i; + qboolean give_all; + gentity_t *it_ent; + trace_t trace; + int amount; + + if ( !CheatsOk( ent ) ) { + return; + } + + //----(SA) check for an amount (like "give health 30") + amt = ConcatArgs( 2 ); + amount = atoi( amt ); + //----(SA) end + + name = ConcatArgs( 1 ); + + if ( Q_stricmp( name, "all" ) == 0 ) { + give_all = qtrue; + } else { + give_all = qfalse; + } + + + if ( give_all || Q_stricmpn( name, "health", 6 ) == 0 ) { + //----(SA) modified + if ( amount ) { + ent->health += amount; + } else { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + if ( !give_all ) { + return; + } + } + + if ( give_all || Q_stricmp( name, "weapons" ) == 0 ) { + for ( i = 0; i < WP_NUM_WEAPONS; i++ ) { + if ( BG_WeaponInWolfMP( i ) ) { + COM_BitSet( ent->client->ps.weapons, i ); + } + } + + if ( !give_all ) { + return; + } + } + + if ( give_all || Q_stricmp( name, "holdable" ) == 0 ) { + ent->client->ps.stats[STAT_HOLDABLE_ITEM] = ( 1 << ( HI_BOOK3 - 1 ) ) - 1 - ( 1 << HI_NONE ); + for ( i = 1 ; i <= HI_BOOK3 ; i++ ) { + ent->client->ps.holdable[i] = 10; + } + + if ( !give_all ) { + return; + } + } + + if ( give_all || Q_stricmpn( name, "ammo", 4 ) == 0 ) { + if ( amount ) { + if ( ent->client->ps.weapon ) { + Add_Ammo( ent, ent->client->ps.weapon, amount, qtrue ); + } + } else { + for ( i = 1 ; i < WP_MONSTER_ATTACK1 ; i++ ) + Add_Ammo( ent, i, 9999, qtrue ); + } + + if ( !give_all ) { + return; + } + } + + // "give allammo " allows you to give a specific amount of ammo to /all/ weapons while + // allowing "give ammo " to only give to the selected weap. + if ( Q_stricmpn( name, "allammo", 7 ) == 0 && amount ) { + for ( i = 1 ; i < WP_MONSTER_ATTACK1 ; i++ ) + Add_Ammo( ent, i, amount, qtrue ); + + if ( !give_all ) { + return; + } + } + + if ( give_all || Q_stricmpn( name, "armor", 5 ) == 0 ) { + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE -- no armor in multiplayer + //----(SA) modified + if ( amount ) { + ent->client->ps.stats[STAT_ARMOR] += amount; + } else { + ent->client->ps.stats[STAT_ARMOR] = 200; + } + } // jpw + if ( !give_all ) { + return; + } + } + + //---- (SA) Wolf keys + if ( give_all || Q_stricmp( name, "keys" ) == 0 ) { + ent->client->ps.stats[STAT_KEYS] = ( 1 << KEY_NUM_KEYS ) - 2; + if ( !give_all ) { + return; + } + } + //---- (SA) end + + // spawn a specific item right on the player + if ( !give_all ) { + it = BG_FindItem( name ); + if ( !it ) { + return; + } + + it_ent = G_Spawn(); + VectorCopy( ent->r.currentOrigin, it_ent->s.origin ); + it_ent->classname = it->classname; + G_SpawnItem( it_ent, it ); + FinishSpawningItem( it_ent ); + memset( &trace, 0, sizeof( trace ) ); + it_ent->active = qtrue; + Touch_Item( it_ent, ent, &trace ); + it_ent->active = qfalse; + if ( it_ent->inuse ) { + G_FreeEntity( it_ent ); + } + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_GODMODE; + if ( !( ent->flags & FL_GODMODE ) ) { + msg = "godmode OFF\n"; + } else { + msg = "godmode ON\n"; + } + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + +/* +================== +Cmd_Nofatigue_f + +Sets client to nofatigue + +argv(0) nofatigue +================== +*/ + +void Cmd_Nofatigue_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_NOFATIGUE; + if ( !( ent->flags & FL_NOFATIGUE ) ) { + msg = "nofatigue OFF\n"; + } else { + msg = "nofatigue ON\n"; + } + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_NOTARGET; + if ( !( ent->flags & FL_NOTARGET ) ) { + msg = "notarget OFF\n"; + } else { + msg = "notarget ON\n"; + } + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + if ( ent->client->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + ent->client->noclip = !ent->client->noclip; + + trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_LevelShot_f( gentity_t *ent ) { + if ( !CheatsOk( ent ) ) { + return; + } + + // doesn't work in single player + if ( g_gametype.integer != 0 ) { + trap_SendServerCommand( ent - g_entities, + "print \"Must be in g_gametype 0 for levelshot\n\"" ); + return; + } + + BeginIntermission(); + trap_SendServerCommand( ent - g_entities, "clientLevelShot" ); +} + + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( gentity_t *ent ) { + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + if ( g_gamestate.integer != GS_PLAYING ) { + return; + } + if ( g_gametype.integer >= GT_WOLF && ent->client->ps.pm_flags & PMF_LIMBO ) { + return; + } + + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + ent->client->ps.persistant[PERS_HWEAPON_USE] = 0; // TTimo - if using /kill while at MG42 + player_die( ent, ent, ent, 100000, MOD_SUICIDE ); +} + + +/* +================= +SetTeam +================= +*/ +void SetTeam( gentity_t *ent, char *s ) { + int team, oldTeam; + gclient_t *client; + int clientNum; + spectatorState_t specState; + int specClient; + + // + // see what change is requested + // + client = ent->client; + + clientNum = client - level.clients; + specClient = 0; + + specState = SPECTATOR_NOT; + if ( !Q_stricmp( s, "scoreboard" ) || !Q_stricmp( s, "score" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_SCOREBOARD; + } else if ( !Q_stricmp( s, "follow1" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FOLLOW; + specClient = -1; + } else if ( !Q_stricmp( s, "follow2" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FOLLOW; + specClient = -2; + } else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FREE; + } else if ( g_gametype.integer >= GT_TEAM ) { + // if running a team game, assign player to one of the teams + specState = SPECTATOR_NOT; + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { + team = TEAM_RED; + } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { + team = TEAM_BLUE; + } else { + // pick the team with the least number of players + team = PickTeam( clientNum ); + } + + // NERVE - SMF + if ( g_noTeamSwitching.integer && team != ent->client->sess.sessionTeam && g_gamestate.integer == GS_PLAYING ) { + trap_SendServerCommand( clientNum, "cp \"You cannot switch during a match, please wait until the round ends.\n\"" ); + return; // ignore the request + } + + // NERVE - SMF - merge from team arena + if ( g_teamForceBalance.integer ) { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ent - g_entities, TEAM_BLUE ); + counts[TEAM_RED] = TeamCount( ent - g_entities, TEAM_RED ); + + // We allow a spread of one + if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] >= 1 ) { + trap_SendServerCommand( clientNum, + "cp \"The Axis has too many players.\n\"" ); + return; // ignore the request + } + if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] >= 1 ) { + trap_SendServerCommand( clientNum, + "cp \"The Allies have too many players.\n\"" ); + return; // ignore the request + } + + // It's ok, the team we are switching to has less or same number of players + } + // -NERVE - SMF + } else { + // force them to spectators if there aren't any spots free + team = TEAM_FREE; + } + + // override decision if limiting the players + if ( g_gametype.integer == GT_TOURNAMENT + && level.numNonSpectatorClients >= 2 ) { + team = TEAM_SPECTATOR; + } else if ( g_maxGameClients.integer > 0 && + level.numNonSpectatorClients >= g_maxGameClients.integer ) { + team = TEAM_SPECTATOR; + } + + // + // decide if we will allow the change + // + oldTeam = client->sess.sessionTeam; + if ( team == oldTeam && team != TEAM_SPECTATOR ) { + return; + } + + // NERVE - SMF - prevent players from switching to regain deployments + if ( g_maxlives.integer > 0 && ent->client->ps.persistant[PERS_RESPAWNS_LEFT] == 0 && + oldTeam != TEAM_SPECTATOR ) { + trap_SendServerCommand( clientNum, + "cp \"You can't switch teams because you are out of lives.\n\" 3" ); + return; // ignore the request + } + + // DHM - Nerve :: Force players to wait 30 seconds before they can join a new team. + if ( g_gametype.integer >= GT_WOLF && team != oldTeam && level.warmupTime == 0 && !client->pers.initialSpawn + && ( ( level.time - client->pers.connectTime ) > 10000 ) && ( ( level.time - client->pers.enterTime ) < 30000 ) ) { + trap_SendServerCommand( ent - g_entities, + va( "cp \"^3You must wait %i seconds before joining ^3a new team.\n\" 3", (int)( 30 - ( ( level.time - client->pers.enterTime ) / 1000 ) ) ) ); + return; + } + // dhm + + // + // execute the team change + // + + // DHM - Nerve + if ( client->pers.initialSpawn && team != TEAM_SPECTATOR ) { + client->pers.initialSpawn = qfalse; + } + + // he starts at 'base' + client->pers.teamState.state = TEAM_BEGIN; + if ( oldTeam != TEAM_SPECTATOR ) { + if ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) { + // Kill him (makes sure he loses flags, etc) + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die( ent, ent, ent, 100000, MOD_SUICIDE ); + } + } + // they go to the end of the line for tournements + if ( team == TEAM_SPECTATOR ) { + client->sess.spectatorTime = level.time; + } + + client->sess.sessionTeam = team; + client->sess.spectatorState = specState; + client->sess.spectatorClient = specClient; + + if ( team == TEAM_RED ) { + trap_SendServerCommand( -1, va( "cp \"[lof]%s" S_COLOR_WHITE " [lon]joined the Axis team.\n\"", + client->pers.netname ) ); + } else if ( team == TEAM_BLUE ) { + trap_SendServerCommand( -1, va( "cp \"[lof]%s" S_COLOR_WHITE " [lon]joined the Allied team.\n\"", + client->pers.netname ) ); + } else if ( team == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) { + trap_SendServerCommand( -1, va( "cp \"[lof]%s" S_COLOR_WHITE " [lon]joined the spectators.\n\"", + client->pers.netname ) ); + } else if ( team == TEAM_FREE ) { + trap_SendServerCommand( -1, va( "cp \"[lof]%s" S_COLOR_WHITE " [lon]joined the battle.\n\"", + client->pers.netname ) ); + } + + // get and distribute relevent paramters + ClientUserinfoChanged( clientNum ); + + ClientBegin( clientNum ); +} + +// DHM - Nerve +/* +================= +SetWolfData +================= +*/ +void SetWolfData( gentity_t *ent, char *ptype, char *weap, char *grenade, char *skinnum ) { // DHM - Nerve + gclient_t *client; + + client = ent->client; + + client->sess.latchPlayerType = atoi( ptype ); + client->sess.latchPlayerWeapon = atoi( weap ); + client->sess.latchPlayerItem = atoi( grenade ); + client->sess.latchPlayerSkin = atoi( skinnum ); +} +// dhm - end + +/* +================= +StopFollowing + +If the client being followed leaves the game, or you just want to drop +to free floating spectator mode + +================= +*/ +void StopFollowing( gentity_t *ent ) { + if ( g_gametype.integer < GT_WOLF ) { // NERVE - SMF - don't forcibly set this for multiplayer + ent->client->sess.sessionTeam = TEAM_SPECTATOR; + ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; + } + + // ATVI Wolfenstein Misc #474 + // divert behaviour if TEAM_SPECTATOR, moved the code from SpectatorThink to put back into free fly correctly + // (I am not sure this can be called in non-TEAM_SPECTATOR situation, better be safe) + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + // drop to free floating, somewhere above the current position (that's the client you were following) + vec3_t pos, angle; + int enterTime; + gclient_t *client = ent->client; + VectorCopy( client->ps.origin, pos ); pos[2] += 16; + VectorCopy( client->ps.viewangles, angle ); + // ATVI Wolfenstein Misc #414, backup enterTime + enterTime = client->pers.enterTime; + SetTeam( ent, "spectator" ); + client->pers.enterTime = enterTime; + VectorCopy( pos, client->ps.origin ); + SetClientViewAngle( ent, angle ); + } else + { + // legacy code, FIXME: useless? + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->r.svFlags &= ~SVF_BOT; + ent->client->ps.clientNum = ent - g_entities; + } +} + +/* +================= +Cmd_Team_f +================= +*/ +void Cmd_Team_f( gentity_t *ent ) { + int oldTeam; + char s[MAX_TOKEN_CHARS]; + char ptype[4], weap[4], pistol[4], grenade[4], skinnum[4]; + + if ( trap_Argc() < 2 ) { + oldTeam = ent->client->sess.sessionTeam; + switch ( oldTeam ) { + case TEAM_BLUE: + trap_SendServerCommand( ent - g_entities, "print \"Blue team\n\"" ); + break; + case TEAM_RED: + trap_SendServerCommand( ent - g_entities, "print \"Red team\n\"" ); + break; + case TEAM_FREE: + trap_SendServerCommand( ent - g_entities, "print \"Free team\n\"" ); + break; + case TEAM_SPECTATOR: + trap_SendServerCommand( ent - g_entities, "print \"Spectator team\n\"" ); + break; + } + return; + } + + // if they are playing a tournement game, count as a loss + if ( g_gametype.integer == GT_TOURNAMENT && ent->client->sess.sessionTeam == TEAM_FREE ) { + ent->client->sess.losses++; + } + + // DHM - Nerve + if ( g_gametype.integer >= GT_WOLF ) { + trap_Argv( 2, ptype, sizeof( ptype ) ); + trap_Argv( 3, weap, sizeof( weap ) ); + trap_Argv( 4, pistol, sizeof( pistol ) ); + trap_Argv( 5, grenade, sizeof( grenade ) ); + trap_Argv( 6, skinnum, sizeof( skinnum ) ); + + SetWolfData( ent, ptype, weap, grenade, skinnum ); + } + // dhm - end + + trap_Argv( 1, s, sizeof( s ) ); + + SetTeam( ent, s ); +} + +/* +================= +Cmd_Follow_f +================= +*/ +void Cmd_Follow_f( gentity_t *ent ) { + int i; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc() != 2 ) { + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + StopFollowing( ent ); + } + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + i = ClientNumberFromString( ent, arg ); + if ( i == -1 ) { + return; + } + + // can't follow self + if ( &level.clients[ i ] == ent->client ) { + return; + } + + // can't follow another spectator + if ( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + + if ( g_gametype.integer >= GT_WOLF ) { + if ( level.clients[ i ].ps.pm_flags & PMF_LIMBO ) { + return; + } + } + + // if they are playing a tournement game, count as a loss + if ( g_gametype.integer == GT_TOURNAMENT && ent->client->sess.sessionTeam == TEAM_FREE ) { + ent->client->sess.losses++; + } + + // first set them to spectator + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + SetTeam( ent, "spectator" ); + } + + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + ent->client->sess.spectatorClient = i; +} + +/* +================= +Cmd_FollowCycle_f +================= +*/ +void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { + int clientnum; + int original; + + // if they are playing a tournement game, count as a loss + if ( g_gametype.integer == GT_TOURNAMENT && ent->client->sess.sessionTeam == TEAM_FREE ) { + ent->client->sess.losses++; + } + // first set them to spectator + if ( ( ent->client->sess.spectatorState == SPECTATOR_NOT ) && ( !( ent->client->ps.pm_flags & PMF_LIMBO ) ) ) { // JPW NERVE for limbo state + SetTeam( ent, "spectator" ); + } + + if ( dir != 1 && dir != -1 ) { + G_Error( "Cmd_FollowCycle_f: bad dir %i", dir ); + } + + clientnum = ent->client->sess.spectatorClient; + original = clientnum; + do { + clientnum += dir; + if ( clientnum >= level.maxclients ) { + clientnum = 0; + } + if ( clientnum < 0 ) { + clientnum = level.maxclients - 1; + } + + // can only follow connected clients + if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) { + continue; + } + + // can't follow another spectator + if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) { + continue; + } + +// JPW NERVE -- couple extra checks for limbo mode + if ( ent->client->ps.pm_flags & PMF_LIMBO ) { + if ( level.clients[clientnum].ps.pm_flags & PMF_LIMBO ) { + continue; + } + if ( level.clients[clientnum].sess.sessionTeam != ent->client->sess.sessionTeam ) { + continue; + } + } +// jpw + + if ( g_gametype.integer >= GT_WOLF ) { + if ( level.clients[clientnum].ps.pm_flags & PMF_LIMBO ) { + continue; + } + } + + // this is good, we can use it + ent->client->sess.spectatorClient = clientnum; + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + return; + } while ( clientnum != original ); + + // leave it where it was +} + + +/* +================== +G_Say +================== +*/ +#define MAX_SAY_TEXT 150 + +#define SAY_ALL 0 +#define SAY_TEAM 1 +#define SAY_TELL 2 +#define SAY_LIMBO 3 // NERVE - SMF + +void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, qboolean localize ) { // removed static so it would link + if ( !other ) { + return; + } + if ( !other->inuse ) { + return; + } + if ( !other->client ) { + return; + } + if ( mode == SAY_TEAM && !OnSameTeam( ent, other ) ) { + return; + } + // no chatting to players in tournements + if ( g_gametype.integer == GT_TOURNAMENT + && other->client->sess.sessionTeam == TEAM_FREE + && ent->client->sess.sessionTeam != TEAM_FREE ) { + return; + } + + // NERVE - SMF - if spectator, no chatting to players in WolfMP + if ( g_gametype.integer >= GT_WOLF + && ( ( ent->client->sess.sessionTeam == TEAM_FREE && other->client->sess.sessionTeam != TEAM_FREE ) || + ( ent->client->sess.sessionTeam == TEAM_SPECTATOR && other->client->sess.sessionTeam != TEAM_SPECTATOR ) ) ) { + return; + } + + // NERVE - SMF + if ( mode == SAY_LIMBO ) { + trap_SendServerCommand( other - g_entities, va( "%s \"%s%c%c%s\"", + "lchat", name, Q_COLOR_ESCAPE, color, message ) ); + } + // -NERVE - SMF + else { + trap_SendServerCommand( other - g_entities, va( "%s \"%s%c%c%s\" %i", + mode == SAY_TEAM ? "tchat" : "chat", + name, Q_COLOR_ESCAPE, color, message, localize ) ); + } +} + +void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) { + int j; + gentity_t *other; + int color; + char name[64]; + // don't let text be too long for malicious reasons + char text[MAX_SAY_TEXT]; + char location[64]; + qboolean localize = qfalse; + + if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { + mode = SAY_ALL; + } + + switch ( mode ) { + default: + case SAY_ALL: + G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); + Com_sprintf( name, sizeof( name ), "%s%c%c: ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + color = COLOR_GREEN; + break; + case SAY_TEAM: + localize = qtrue; + G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); + if ( Team_GetLocationMsg( ent, location, sizeof( location ) ) ) { + Com_sprintf( name, sizeof( name ), "[lof](%s%c%c) (%s): ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); + } else { + Com_sprintf( name, sizeof( name ), "(%s%c%c): ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + } + color = COLOR_CYAN; + break; + case SAY_TELL: + if ( target && g_gametype.integer >= GT_TEAM && + target->client->sess.sessionTeam == ent->client->sess.sessionTeam && + Team_GetLocationMsg( ent, location, sizeof( location ) ) ) { + Com_sprintf( name, sizeof( name ), "[%s%c%c] (%s): ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); + } else { + Com_sprintf( name, sizeof( name ), "[%s%c%c]: ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + } + color = COLOR_MAGENTA; + break; + // NERVE - SMF + case SAY_LIMBO: + G_LogPrintf( "say_limbo: %s: %s\n", ent->client->pers.netname, chatText ); + Com_sprintf( name, sizeof( name ), "%s%c%c: ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + color = COLOR_GREEN; + break; + // -NERVE - SMF + } + + Q_strncpyz( text, chatText, sizeof( text ) ); + + if ( target ) { + G_SayTo( ent, target, mode, color, name, text, localize ); + return; + } + + // echo the text to the console + if ( g_dedicated.integer ) { + G_Printf( "%s%s\n", name, text ); + } + + // send it to all the apropriate clients + for ( j = 0; j < level.maxclients; j++ ) { + other = &g_entities[j]; + G_SayTo( ent, other, mode, color, name, text, localize ); + } +} + + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { + char *p; + + if ( trap_Argc() < 2 && !arg0 ) { + return; + } + + if ( arg0 ) { + p = ConcatArgs( 0 ); + } else + { + p = ConcatArgs( 1 ); + } + + G_Say( ent, NULL, mode, p ); +} + +/* +================== +Cmd_Tell_f +================== +*/ +static void Cmd_Tell_f( gentity_t *ent ) { + int targetNum; + gentity_t *target; + char *p; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc() < 2 ) { + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + targetNum = atoi( arg ); + if ( targetNum < 0 || targetNum >= level.maxclients ) { + return; + } + + target = &g_entities[targetNum]; + if ( !target || !target->inuse || !target->client ) { + return; + } + + p = ConcatArgs( 2 ); + + G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); + G_Say( ent, target, SAY_TELL, p ); + G_Say( ent, ent, SAY_TELL, p ); +} + +// NERVE - SMF +static void G_VoiceTo( gentity_t *ent, gentity_t *other, int mode, const char *id, qboolean voiceonly ) { + int color; + char *cmd; + + if ( !other ) { + return; + } + if ( !other->inuse ) { + return; + } + if ( !other->client ) { + return; + } + if ( mode == SAY_TEAM && !OnSameTeam( ent, other ) ) { + return; + } + // no chatting to players in tournements + if ( ( g_gametype.integer == GT_TOURNAMENT ) ) { + return; + } + + if ( mode == SAY_TEAM ) { + color = COLOR_CYAN; + cmd = "vtchat"; + } else if ( mode == SAY_TELL ) { + color = COLOR_MAGENTA; + cmd = "vtell"; + } else { + color = COLOR_GREEN; + cmd = "vchat"; + } + + trap_SendServerCommand( other - g_entities, va( "%s %d %d %d %s %i %i %i", cmd, voiceonly, ent->s.number, color, id, + (int)ent->s.pos.trBase[0], (int)ent->s.pos.trBase[1], (int)ent->s.pos.trBase[2] ) ); +} + +void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly ) { + int j; + gentity_t *other; + + if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { + mode = SAY_ALL; + } + + // DHM - Nerve :: Don't allow excessive spamming of voice chats + ent->voiceChatSquelch -= ( level.time - ent->voiceChatPreviousTime ); + ent->voiceChatPreviousTime = level.time; + + if ( ent->voiceChatSquelch < 0 ) { + ent->voiceChatSquelch = 0; + } + + if ( ent->voiceChatSquelch >= 30000 ) { + trap_SendServerCommand( ent - g_entities, "print \"^1Spam Protection^7: VoiceChat ignored\n\"" ); + return; + } + + if ( g_voiceChatsAllowed.integer ) { + ent->voiceChatSquelch += ( 34000 / g_voiceChatsAllowed.integer ); + } else { + return; + } + // dhm + + if ( target ) { + G_VoiceTo( ent, target, mode, id, voiceonly ); + return; + } + + // echo the text to the console + if ( g_dedicated.integer ) { + G_Printf( "voice: %s %s\n", ent->client->pers.netname, id ); + } + + // send it to all the apropriate clients + for ( j = 0; j < level.maxclients; j++ ) { + other = &g_entities[j]; + G_VoiceTo( ent, other, mode, id, voiceonly ); + } +} + +/* +================== +Cmd_Voice_f +================== +*/ +static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voiceonly ) { + char *p; + + if ( trap_Argc() < 2 && !arg0 ) { + return; + } + + if ( arg0 ) { + p = ConcatArgs( 0 ); + } else + { + p = ConcatArgs( 1 ); + } + + G_Voice( ent, NULL, mode, p, voiceonly ); +} + +// TTimo gcc: defined but not used +#if 0 +/* +================== +Cmd_VoiceTell_f +================== +*/ +static void Cmd_VoiceTell_f( gentity_t *ent, qboolean voiceonly ) { + int targetNum; + gentity_t *target; + char *id; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc() < 2 ) { + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + targetNum = atoi( arg ); + if ( targetNum < 0 || targetNum >= level.maxclients ) { + return; + } + + target = &g_entities[targetNum]; + if ( !target || !target->inuse || !target->client ) { + return; + } + + id = ConcatArgs( 2 ); + + G_LogPrintf( "vtell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, id ); + G_Voice( ent, target, SAY_TELL, id, voiceonly ); + // don't tell to the player self if it was already directed to this player + // also don't send the chat back to a bot + if ( ent != target && !( ent->r.svFlags & SVF_BOT ) ) { + G_Voice( ent, ent, SAY_TELL, id, voiceonly ); + } +} +#endif + +// TTimo gcc: defined but not used +#if 0 +/* +================== +Cmd_VoiceTaunt_f +================== +*/ +static void Cmd_VoiceTaunt_f( gentity_t *ent ) { + gentity_t *who; + int i; + + if ( !ent->client ) { + return; + } + + // insult someone who just killed you + if ( ent->enemy && ent->enemy->client && ent->enemy->client->lastkilled_client == ent->s.number ) { + // i am a dead corpse + if ( !( ent->enemy->r.svFlags & SVF_BOT ) ) { +// G_Voice( ent, ent->enemy, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); + } + if ( !( ent->r.svFlags & SVF_BOT ) ) { +// G_Voice( ent, ent, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); + } + ent->enemy = NULL; + return; + } + // insult someone you just killed + if ( ent->client->lastkilled_client >= 0 && ent->client->lastkilled_client != ent->s.number ) { + who = g_entities + ent->client->lastkilled_client; + if ( who->client ) { + // who is the person I just killed + if ( who->client->lasthurt_mod == MOD_GAUNTLET ) { + if ( !( who->r.svFlags & SVF_BOT ) ) { +// G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); // and I killed them with a gauntlet + } + if ( !( ent->r.svFlags & SVF_BOT ) ) { +// G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); + } + } else { + if ( !( who->r.svFlags & SVF_BOT ) ) { +// G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); // and I killed them with something else + } + if ( !( ent->r.svFlags & SVF_BOT ) ) { +// G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); + } + } + ent->client->lastkilled_client = -1; + return; + } + } + + if ( g_gametype.integer >= GT_TEAM ) { + // praise a team mate who just got a reward + for ( i = 0; i < MAX_CLIENTS; i++ ) { + who = g_entities + i; + if ( who->client && who != ent && who->client->sess.sessionTeam == ent->client->sess.sessionTeam ) { + if ( who->client->rewardTime > level.time ) { + if ( !( who->r.svFlags & SVF_BOT ) ) { +// G_Voice( ent, who, SAY_TELL, VOICECHAT_PRAISE, qfalse ); + } + if ( !( ent->r.svFlags & SVF_BOT ) ) { +// G_Voice( ent, ent, SAY_TELL, VOICECHAT_PRAISE, qfalse ); + } + return; + } + } + } + } + + // just say something +// G_Voice( ent, NULL, SAY_ALL, VOICECHAT_TAUNT, qfalse ); +} +// -NERVE - SMF +#endif + +static char *gc_orders[] = { + "hold your position", + "hold this position", + "come here", + "cover me", + "guard location", + "search and destroy", + "report" +}; + +void Cmd_GameCommand_f( gentity_t *ent ) { + int player; + int order; + char str[MAX_TOKEN_CHARS]; + + trap_Argv( 1, str, sizeof( str ) ); + player = atoi( str ); + trap_Argv( 2, str, sizeof( str ) ); + order = atoi( str ); + + if ( player < 0 || player >= MAX_CLIENTS ) { + return; + } + if ( order < 0 || order > sizeof( gc_orders ) / sizeof( char * ) ) { + return; + } + G_Say( ent, &g_entities[player], SAY_TELL, gc_orders[order] ); + G_Say( ent, ent, SAY_TELL, gc_orders[order] ); +} + +/* +================== +Cmd_Where_f +================== +*/ +void Cmd_Where_f( gentity_t *ent ) { + trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", vtos( ent->s.origin ) ) ); +} + + +static const char *gameNames[] = { + "Free For All", + "Tournament", + "Single Player", + "Team Deathmatch", + "Capture the Flag", + "Wolf Multiplayer", + "Wolf Stopwatch", + "Wolf Checkpoint" +}; + + +/* +================== +Cmd_CallVote_f +================== +*/ +void Cmd_CallVote_f( gentity_t *ent ) { + int i; + char arg1[MAX_STRING_TOKENS]; + char arg2[MAX_STRING_TOKENS]; + char cleanName[64]; // JPW NERVE + int mask = 0; + + if ( !g_voteFlags.integer ) { + trap_SendServerCommand( ent - g_entities, "print \"Voting not enabled on this server.\n\"" ); + return; + } + + if ( level.voteTime ) { + trap_SendServerCommand( ent - g_entities, "print \"A vote is already in progress.\n\"" ); + return; + } + if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) { + trap_SendServerCommand( ent - g_entities, "print \"You have called the maximum number of votes.\n\"" ); + return; + } + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + trap_SendServerCommand( ent - g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); + return; + } + + // make sure it is a valid command to vote on + trap_Argv( 1, arg1, sizeof( arg1 ) ); + trap_Argv( 2, arg2, sizeof( arg2 ) ); + + if ( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) { + trap_SendServerCommand( ent - g_entities, "print \"Invalid vote string.\n\"" ); + return; + } + + if ( !Q_stricmp( arg1, "map_restart" ) ) { + mask = VOTEFLAGS_RESTART; + } else if ( !Q_stricmp( arg1, "nextmap" ) ) { + mask = VOTEFLAGS_NEXTMAP; + } else if ( !Q_stricmp( arg1, "map" ) ) { + mask = VOTEFLAGS_MAP; + } else if ( !Q_stricmp( arg1, "g_gametype" ) ) { + mask = VOTEFLAGS_TYPE; + } else if ( !Q_stricmp( arg1, "kick" ) ) { + mask = VOTEFLAGS_KICK; + } else if ( !Q_stricmp( arg1, "clientkick" ) ) { + mask = VOTEFLAGS_KICK; + } else if ( !Q_stricmp( arg1, "start_match" ) ) { // NERVE - SMF + mask = VOTEFLAGS_STARTMATCH; + } else if ( !Q_stricmp( arg1, "reset_match" ) ) { // NERVE - SMF + mask = VOTEFLAGS_RESETMATCH; + } else if ( !Q_stricmp( arg1, "swap_teams" ) ) { // NERVE - SMF + mask = VOTEFLAGS_SWAP; +// JPW NERVE +#ifndef PRE_RELEASE_DEMO + } else if ( !Q_stricmp( arg1, testid1 ) ) { + } else if ( !Q_stricmp( arg1, testid2 ) ) { + } else if ( !Q_stricmp( arg1, testid3 ) ) { +#endif +// jpw + } else { + trap_SendServerCommand( ent - g_entities, "print \"Invalid vote string.\n\"" ); + trap_SendServerCommand( ent - g_entities, "print \"Vote commands are: map_restart, nextmap, start_match, swap_teams, reset_match, map , g_gametype , kick , clientkick \n\"" ); + return; + } + + if ( !( g_voteFlags.integer & mask ) ) { + trap_SendServerCommand( ent - g_entities, va( "print \"Voting for %s disabled on this server\n\"", arg1 ) ); + return; + } + + // if there is still a vote to be executed + if ( level.voteExecuteTime ) { + level.voteExecuteTime = 0; + trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) ); + } + + // special case for g_gametype, check for bad values + if ( !Q_stricmp( arg1, "g_gametype" ) ) { + i = atoi( arg2 ); + if ( i < GT_WOLF || i >= GT_MAX_GAME_TYPE ) { + trap_SendServerCommand( ent - g_entities, "print \"Invalid gametype.\n\"" ); + return; + } + + Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %d", arg1, i ); + Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s %s", arg1, gameNames[i] ); + } else if ( !Q_stricmp( arg1, "map_restart" ) ) { + // NERVE - SMF - do a warmup when we restart maps + if ( strlen( arg2 ) ) { + Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 ); + } else { + Com_sprintf( level.voteString, sizeof( level.voteString ), "%s", arg1, arg2 ); + } + + Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); + } else if ( !Q_stricmp( arg1, "map" ) ) { + // special case for map changes, we want to reset the nextmap setting + // this allows a player to change maps, but not upset the map rotation + char s[MAX_STRING_CHARS]; + + trap_Cvar_VariableStringBuffer( "nextmap", s, sizeof( s ) ); + if ( *s ) { + Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s; set nextmap \"%s\"", arg1, arg2, s ); + } else { + Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); + } + Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); + } else if ( !Q_stricmp( arg1, "nextmap" ) ) { + char s[MAX_STRING_CHARS]; + + trap_Cvar_VariableStringBuffer( "nextmap", s, sizeof( s ) ); + if ( !*s ) { + trap_SendServerCommand( ent - g_entities, "print \"nextmap not set.\n\"" ); + return; + } + Com_sprintf( level.voteString, sizeof( level.voteString ), "vstr nextmap" ); + Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); +// JPW NERVE + } else if ( !Q_stricmp( arg1,"kick" ) ) { + int i,kicknum = MAX_CLIENTS; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( level.clients[i].pers.connected != CON_CONNECTED ) { + continue; + } +// strip the color crap out + Q_strncpyz( cleanName, level.clients[i].pers.netname, sizeof( cleanName ) ); + Q_CleanStr( cleanName ); + if ( !Q_stricmp( cleanName, arg2 ) ) { + kicknum = i; + } + } + Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "kick %s", level.clients[kicknum].pers.netname ); + if ( kicknum != MAX_CLIENTS ) { // found a client # to kick, so override votestring with better one + Com_sprintf( level.voteString, sizeof( level.voteString ),"clientkick \"%d\"",kicknum ); + } else { // if it can't do a name match, don't allow kick (to prevent votekick text spam wars) + trap_SendServerCommand( ent - g_entities, "print \"Client not on server.\n\"" ); + return; + } +// jpw + } else { + Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 ); + Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); + } + + trap_SendServerCommand( -1, va( "print \"[lof]%s [lon]called a vote.\n\"", ent->client->pers.netname ) ); + + // start the voting, the caller autoamtically votes yes + level.voteTime = level.time; + level.voteYes = 1; + level.voteNo = 0; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + level.clients[i].ps.eFlags &= ~EF_VOTED; + } + ent->client->ps.eFlags |= EF_VOTED; + + trap_SetConfigstring( CS_VOTE_TIME, va( "%i", level.voteTime ) ); + trap_SetConfigstring( CS_VOTE_STRING, level.voteDisplayString ); + trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) ); + trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) ); +} + +/* +================== +Cmd_Vote_f +================== +*/ +void Cmd_Vote_f( gentity_t *ent ) { + char msg[64]; + int num; + + // DHM - Nerve :: Complaints supercede voting (and share command) + if ( ent->client->pers.complaintEndTime > level.time ) { + + gclient_t *cl = g_entities[ ent->client->pers.complaintClient ].client; + if ( !cl ) { + return; + } + if ( cl->pers.connected != CON_CONNECTED ) { + return; + } + if ( cl->pers.localClient ) { + trap_SendServerCommand( ent - g_entities, "complaint -3" ); + return; + } + + // Reset this ent's complainEndTime so they can't send multiple complaints + ent->client->pers.complaintEndTime = -1; + ent->client->pers.complaintClient = -1; + + trap_Argv( 1, msg, sizeof( msg ) ); + + if ( msg[0] == 'y' || msg[1] == 'Y' || msg[1] == '1' ) { + // Increase their complaint counter + cl->pers.complaints++; + + num = g_complaintlimit.integer - cl->pers.complaints; + + if ( num <= 0 && !cl->pers.localClient ) { + trap_DropClient( cl - level.clients, "kicked after too many complaints." ); + trap_SendServerCommand( ent - g_entities, "complaint -1" ); + return; + } + + trap_SendServerCommand( cl->ps.clientNum, va( "print \"^1Warning^7: Complaint filed against you. [lof](%d [lon]until kicked[lof])\n\"", num ) ); + trap_SendServerCommand( ent - g_entities, "complaint -1" ); + } else { + trap_SendServerCommand( ent - g_entities, "complaint -2" ); + } + + return; + } + // dhm + + // Reset this ent's complainEndTime so they can't send multiple complaints + ent->client->pers.complaintEndTime = -1; + ent->client->pers.complaintClient = -1; + + if ( !level.voteTime ) { + trap_SendServerCommand( ent - g_entities, "print \"No vote in progress.\n\"" ); + return; + } + if ( ent->client->ps.eFlags & EF_VOTED ) { + trap_SendServerCommand( ent - g_entities, "print \"Vote already cast.\n\"" ); + return; + } + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + trap_SendServerCommand( ent - g_entities, "print \"Not allowed to vote as spectator.\n\"" ); + return; + } + + trap_SendServerCommand( ent - g_entities, "print \"Vote cast.\n\"" ); + + ent->client->ps.eFlags |= EF_VOTED; + + trap_Argv( 1, msg, sizeof( msg ) ); + + if ( msg[0] == 'y' || msg[1] == 'Y' || msg[1] == '1' ) { + level.voteYes++; + trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) ); + } else { + level.voteNo++; + trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) ); + } + + // a majority will be determined in G_CheckVote, which will also account + // for players entering or leaving +} + + +qboolean G_canPickupMelee( gentity_t *ent ) { + +// JPW NERVE -- no "melee" weapons in net play + if ( g_gametype.integer >= GT_WOLF ) { + return qfalse; + } +// jpw + + if ( !( ent->client ) ) { + return qfalse; // hmm, shouldn't be too likely... + + } + if ( !( ent->s.weapon ) ) { // no weap, go ahead + return qtrue; + } + + if ( ent->client->ps.weaponstate == WEAPON_RELOADING ) { + return qfalse; + } + + if ( WEAPS_ONE_HANDED & ( 1 << ( ent->s.weapon ) ) ) { + return qtrue; + } + + return qfalse; +} + + + + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( gentity_t *ent ) { + vec3_t origin, angles; + char buffer[MAX_TOKEN_CHARS]; + int i; + + if ( !g_cheats.integer ) { + trap_SendServerCommand( ent - g_entities, va( "print \"Cheats are not enabled on this server.\n\"" ) ); + return; + } + if ( trap_Argc() != 5 ) { + trap_SendServerCommand( ent - g_entities, va( "print \"usage: setviewpos x y z yaw\n\"" ) ); + return; + } + + VectorClear( angles ); + for ( i = 0 ; i < 3 ; i++ ) { + trap_Argv( i + 1, buffer, sizeof( buffer ) ); + origin[i] = atof( buffer ); + } + + trap_Argv( 4, buffer, sizeof( buffer ) ); + angles[YAW] = atof( buffer ); + + TeleportPlayer( ent, origin, angles ); +} + +/* +================= +Cmd_StartCamera_f +================= +*/ +void Cmd_StartCamera_f( gentity_t *ent ) { + + if ( !CheatsOk( ent ) ) { + return; + } + + g_camEnt->r.svFlags |= SVF_PORTAL; + g_camEnt->r.svFlags &= ~SVF_NOCLIENT; + ent->client->cameraPortal = g_camEnt; + ent->client->ps.eFlags |= EF_VIEWING_CAMERA; + ent->s.eFlags |= EF_VIEWING_CAMERA; +} + +/* +================= +Cmd_StopCamera_f +================= +*/ +void Cmd_StopCamera_f( gentity_t *ent ) { + + if ( !CheatsOk( ent ) ) { + return; + } + + if ( ent->client->cameraPortal ) { + // send a script event + G_Script_ScriptEvent( ent->client->cameraPortal, "stopcam", "" ); + // go back into noclient mode + ent->client->cameraPortal->r.svFlags |= SVF_NOCLIENT; + ent->client->cameraPortal = NULL; + ent->s.eFlags &= ~EF_VIEWING_CAMERA; + ent->client->ps.eFlags &= ~EF_VIEWING_CAMERA; + } +} + +/* +================= +Cmd_SetCameraOrigin_f +================= +*/ +void Cmd_SetCameraOrigin_f( gentity_t *ent ) { + char buffer[MAX_TOKEN_CHARS]; + int i; + + if ( trap_Argc() != 4 ) { + return; + } + + VectorClear( ent->client->cameraOrigin ); + for ( i = 0 ; i < 3 ; i++ ) { + trap_Argv( i + 1, buffer, sizeof( buffer ) ); + ent->client->cameraOrigin[i] = atof( buffer ); + } +} + + +// Rafael +/* +================== +Cmd_Activate_f +================== +*/ +void Cmd_Activate_f( gentity_t *ent ) { + trace_t tr; + vec3_t end; + gentity_t *traceEnt; + vec3_t forward, right, up, offset; + static int oldactivatetime = 0; + int activatetime = level.time; + qboolean walking = qfalse; + + if ( ent->active ) { + if ( ent->client->ps.persistant[PERS_HWEAPON_USE] ) { + // DHM - Nerve :: Restore original position if current position is bad + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, MASK_PLAYERSOLID ); + if ( tr.startsolid ) { + VectorCopy( ent->TargetAngles, ent->client->ps.origin ); + VectorCopy( ent->TargetAngles, ent->r.currentOrigin ); + ent->r.contents = CONTENTS_CORPSE; // DHM - this will correct itself in ClientEndFrame + } + ent->client->ps.eFlags &= ~EF_MG42_ACTIVE; // DHM - Nerve :: unset flag + ent->client->ps.persistant[PERS_HWEAPON_USE] = 0; + ent->active = qfalse; + return; + } else + { + ent->active = qfalse; + } + } + + if ( ent->client->pers.cmd.buttons & BUTTON_WALKING ) { + walking = qtrue; + } + + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePointForActivate( ent, forward, right, up, offset ); + VectorMA( offset, 96, forward, end ); + + trap_Trace( &tr, offset, NULL, NULL, end, ent->s.number, ( CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_TRIGGER ) ); + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + if ( tr.entityNum == ENTITYNUM_WORLD ) { + return; + } + + traceEnt = &g_entities[ tr.entityNum ]; + + if ( traceEnt->classname ) { + traceEnt->flags &= ~FL_SOFTACTIVATE; // FL_SOFTACTIVATE will be set if the user is holding 'walk' key + + if ( traceEnt->s.eType == ET_ALARMBOX ) { + trace_t trace; + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + + memset( &trace, 0, sizeof( trace ) ); + + if ( traceEnt->use ) { + traceEnt->use( traceEnt, ent, 0 ); + } + } else if ( traceEnt->s.eType == ET_ITEM ) { + trace_t trace; + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + + memset( &trace, 0, sizeof( trace ) ); + + if ( traceEnt->touch ) { + if ( ent->client->pers.autoActivate == PICKUP_ACTIVATE ) { + ent->client->pers.autoActivate = PICKUP_FORCE; //----(SA) force pickup + } + traceEnt->active = qtrue; + traceEnt->touch( traceEnt, ent, &trace ); + } + + } else if ( ( Q_stricmp( traceEnt->classname, "misc_mg42" ) == 0 ) && traceEnt->active == qfalse ) { + if ( + ( traceEnt->r.currentOrigin[2] - ent->r.currentOrigin[2] < 40 ) && + ( traceEnt->r.currentOrigin[2] - ent->r.currentOrigin[2] > 0 ) && + !infront( traceEnt, ent ) ) { + //----(SA) make sure the client isn't holding a hot potato + gclient_t *cl; + cl = &level.clients[ ent->s.clientNum ]; + + if ( !( cl->ps.grenadeTimeLeft ) && !( cl->ps.pm_flags & PMF_DUCKED ) + && !( traceEnt->s.eFlags & EF_SMOKING ) && ( cl->ps.weapon != WP_SNIPERRIFLE ) ) { // JPW NERVE no mg42 while scoped in + // DHM - Remember initial gun position to restore later + vec3_t point; + + AngleVectors( traceEnt->s.apos.trBase, forward, NULL, NULL ); + VectorMA( traceEnt->r.currentOrigin, -36, forward, point ); + point[2] = ent->r.currentOrigin[2]; + + // Save initial position + VectorCopy( point, ent->TargetAngles ); + + // Zero out velocity + VectorCopy( vec3_origin, ent->client->ps.velocity ); + VectorCopy( vec3_origin, ent->s.pos.trDelta ); + + traceEnt->active = qtrue; + ent->active = qtrue; + traceEnt->r.ownerNum = ent->s.number; + VectorCopy( traceEnt->s.angles, traceEnt->TargetAngles ); + traceEnt->s.otherEntityNum = ent->s.number; + + cl->pmext.harc = traceEnt->harc; + cl->pmext.varc = traceEnt->varc; + VectorCopy( traceEnt->s.angles, cl->pmext.centerangles ); + cl->pmext.centerangles[PITCH] = AngleNormalize180( cl->pmext.centerangles[PITCH] ); + cl->pmext.centerangles[YAW] = AngleNormalize180( cl->pmext.centerangles[YAW] ); + cl->pmext.centerangles[ROLL] = AngleNormalize180( cl->pmext.centerangles[ROLL] ); + + if ( !( ent->r.svFlags & SVF_CASTAI ) ) { + G_UseTargets( traceEnt, ent ); //----(SA) added for Mike so mounting an MG42 can be a trigger event (let me know if there's any issues with this) + } + } + } + } else if ( ( ( Q_stricmp( traceEnt->classname, "func_door" ) == 0 ) || ( Q_stricmp( traceEnt->classname, "func_door_rotating" ) == 0 ) ) ) { + if ( walking ) { + traceEnt->flags |= FL_SOFTACTIVATE; // no noise + } + G_TryDoor( traceEnt, ent, ent ); // (door,other,activator) + } else if ( ( Q_stricmp( traceEnt->classname, "team_WOLF_checkpoint" ) == 0 ) ) { + if ( traceEnt->count != ent->client->sess.sessionTeam ) { + traceEnt->health++; + } + } else if ( ( Q_stricmp( traceEnt->classname, "func_button" ) == 0 ) + && ( traceEnt->s.apos.trType == TR_STATIONARY && traceEnt->s.pos.trType == TR_STATIONARY ) + && traceEnt->active == qfalse ) { + Use_BinaryMover( traceEnt, ent, ent ); + traceEnt->active = qtrue; + } else if ( !Q_stricmp( traceEnt->classname, "func_invisible_user" ) ) { + if ( walking ) { + traceEnt->flags |= FL_SOFTACTIVATE; // no noise + } + traceEnt->use( traceEnt, ent, ent ); + } else if ( !Q_stricmp( traceEnt->classname, "script_mover" ) ) { + G_Script_ScriptEvent( traceEnt, "activate", ent->aiName ); + } else if ( !Q_stricmp( traceEnt->classname, "props_footlocker" ) ) { + traceEnt->use( traceEnt, ent, ent ); + } + } + + if ( activatetime > oldactivatetime + 1000 ) { + oldactivatetime = activatetime; + } +} + +// Rafael WolfKick +//=================== +// Cmd_WolfKick +//=================== + +#define WOLFKICKDISTANCE 96 +int Cmd_WolfKick_f( gentity_t *ent ) { + trace_t tr; + vec3_t end; + gentity_t *traceEnt; + vec3_t forward, right, up, offset; + gentity_t *tent; + static int oldkicktime = 0; + int kicktime = level.time; + qboolean solidKick = qfalse; // don't play "hit" sound on a trigger unless it's an func_invisible_user + + int damage = 15; + + // DHM - Nerve :: No kick in wolf multiplayer + if ( g_gametype.integer >= GT_WOLF ) { + return 0; + } + + if ( ent->client->ps.leanf ) { + return 0; // no kick when leaning + + } + if ( oldkicktime > kicktime ) { + return ( 0 ); + } else { + oldkicktime = kicktime + 1000; + } + + // play the anim + BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_KICK, qfalse, qtrue ); + + ent->client->ps.persistant[PERS_WOLFKICK] = 1; + + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePointForActivate( ent, forward, right, up, offset ); + + // note to self: we need to determine the usable distance for wolf + VectorMA( offset, WOLFKICKDISTANCE, forward, end ); + + trap_Trace( &tr, offset, NULL, NULL, end, ent->s.number, ( CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_TRIGGER ) ); + + if ( tr.surfaceFlags & SURF_NOIMPACT || tr.fraction == 1.0 ) { + tent = G_TempEntity( tr.endpos, EV_WOLFKICK_MISS ); + tent->s.eventParm = ent->s.number; + return ( 1 ); + } + + traceEnt = &g_entities[ tr.entityNum ]; + + if ( !ent->melee ) { // because we dont want you to open a door with a prop + if ( ( Q_stricmp( traceEnt->classname, "func_door_rotating" ) == 0 ) + && ( traceEnt->s.apos.trType == TR_STATIONARY && traceEnt->s.pos.trType == TR_STATIONARY ) + && traceEnt->active == qfalse ) { + if ( traceEnt->key < 0 ) { // door force locked + //----(SA) play kick "hit" sound + tent = G_TempEntity( tr.endpos, EV_WOLFKICK_HIT_WALL ); + tent->s.otherEntityNum = ent->s.number; \ + //----(SA) end + + AICast_AudibleEvent( ent->s.clientNum, tr.endpos, HEAR_RANGE_DOOR_KICKLOCKED ); // "someone kicked a locked door near me!" + + if ( traceEnt->soundPos3 ) { + G_AddEvent( traceEnt, EV_GENERAL_SOUND, traceEnt->soundPos3 ); + } else { + G_AddEvent( traceEnt, EV_GENERAL_SOUND, G_SoundIndex( "sound/movers/doors/default_door_locked.wav" ) ); + } + return 1; //----(SA) changed. shows boot for locked doors + } + + if ( traceEnt->key > 0 ) { // door requires key + gitem_t *item = BG_FindItemForKey( traceEnt->key, 0 ); + if ( !( ent->client->ps.stats[STAT_KEYS] & ( 1 << item->giTag ) ) ) { + //----(SA) play kick "hit" sound + tent = G_TempEntity( tr.endpos, EV_WOLFKICK_HIT_WALL ); + tent->s.otherEntityNum = ent->s.number; \ + //----(SA) end + + AICast_AudibleEvent( ent->s.clientNum, tr.endpos, HEAR_RANGE_DOOR_KICKLOCKED ); // "someone kicked a locked door near me!" + + // player does not have key + if ( traceEnt->soundPos3 ) { + G_AddEvent( traceEnt, EV_GENERAL_SOUND, traceEnt->soundPos3 ); + } else { + G_AddEvent( traceEnt, EV_GENERAL_SOUND, G_SoundIndex( "sound/movers/doors/default_door_locked.wav" ) ); + } + return 1; //----(SA) changed. shows boot animation for locked doors + } + } + + if ( traceEnt->teammaster && traceEnt->team && traceEnt != traceEnt->teammaster ) { + traceEnt->teammaster->active = qtrue; + traceEnt->teammaster->flags |= FL_KICKACTIVATE; + Use_BinaryMover( traceEnt->teammaster, ent, ent ); + G_UseTargets( traceEnt->teammaster, ent ); + } else + { + traceEnt->active = qtrue; + traceEnt->flags |= FL_KICKACTIVATE; + Use_BinaryMover( traceEnt, ent, ent ); + G_UseTargets( traceEnt, ent ); + } + } else if ( ( Q_stricmp( traceEnt->classname, "func_button" ) == 0 ) + && ( traceEnt->s.apos.trType == TR_STATIONARY && traceEnt->s.pos.trType == TR_STATIONARY ) + && traceEnt->active == qfalse ) { + Use_BinaryMover( traceEnt, ent, ent ); + traceEnt->active = qtrue; + + } else if ( !Q_stricmp( traceEnt->classname, "func_invisible_user" ) ) { + traceEnt->flags |= FL_KICKACTIVATE; // so cell doors know they were kicked + // It doesn't hurt to pass this along since only ent use() funcs who care about it will check. + // However, it may become handy to put a "KICKABLE" or "NOTKICKABLE" flag on the invisible_user + traceEnt->use( traceEnt, ent, ent ); + traceEnt->flags &= ~FL_KICKACTIVATE; // reset + + solidKick = qtrue; //----(SA) + } else if ( !Q_stricmp( traceEnt->classname, "props_flippy_table" ) && traceEnt->use ) { + traceEnt->use( traceEnt, ent, ent ); + } + } + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, offset ); + + // send bullet impact + if ( traceEnt->takedamage && traceEnt->client ) { + tent = G_TempEntity( tr.endpos, EV_WOLFKICK_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + if ( LogAccuracyHit( traceEnt, ent ) ) { + ent->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + } else { + // Ridah, bullet impact should reflect off surface + vec3_t reflect; + float dot; + + if ( traceEnt->r.contents >= 0 && ( traceEnt->r.contents & CONTENTS_TRIGGER ) && !solidKick ) { + tent = G_TempEntity( tr.endpos, EV_WOLFKICK_MISS ); // (SA) don't play the "hit" sound if you kick most triggers + } else { + tent = G_TempEntity( tr.endpos, EV_WOLFKICK_HIT_WALL ); + } + + + dot = DotProduct( forward, tr.plane.normal ); + VectorMA( forward, -2 * dot, tr.plane.normal, reflect ); + VectorNormalize( reflect ); + + tent->s.eventParm = DirToByte( reflect ); + // done. + + if ( ent->melee ) { + ent->active = qfalse; + ent->melee->health = 0; + } + } + + tent->s.otherEntityNum = ent->s.number; + + // try to swing chair + if ( traceEnt->takedamage ) { + + if ( ent->melee ) { + ent->active = qfalse; + ent->melee->health = 0; + ent->client->ps.eFlags &= ~EF_MELEE_ACTIVE; + + } + + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_KICKED ); //----(SA) modified + } + + return ( 1 ); +} +// done + +/* +============================ +Cmd_ClientMonsterSlickAngle +============================ +*/ +/* +void Cmd_ClientMonsterSlickAngle (gentity_t *clent) { + + char s[MAX_STRING_CHARS]; + int entnum; + int angle; + gentity_t *ent; + vec3_t dir, kvel; + vec3_t forward; + + if (trap_Argc() != 3) { + G_Printf( "ClientDamage command issued with incorrect number of args\n" ); + } + + trap_Argv( 1, s, sizeof( s ) ); + entnum = atoi(s); + ent = &g_entities[entnum]; + + trap_Argv( 2, s, sizeof( s ) ); + angle = atoi(s); + + // sanity check (also protect from cheaters) + if (g_gametype.integer != GT_SINGLE_PLAYER && entnum != clent->s.number) { + trap_DropClient( clent->s.number, "Dropped due to illegal ClientMonsterSlick command\n" ); + return; + } + + VectorClear (dir); + dir[YAW] = angle; + AngleVectors (dir, forward, NULL, NULL); + + VectorScale (forward, 32, kvel); + VectorAdd (ent->client->ps.velocity, kvel, ent->client->ps.velocity); +} +*/ + +// NERVE - SMF +/* +============ +ClientDamage +============ +*/ +void ClientDamage( gentity_t *clent, int entnum, int enemynum, int id ) { + gentity_t *enemy, *ent; + vec3_t vec; + + ent = &g_entities[entnum]; + + enemy = &g_entities[enemynum]; + + // NERVE - SMF - took this out, this is causing more problems than its helping + // Either a new way has to be found, or this check needs to change. + // sanity check (also protect from cheaters) +// if (g_gametype.integer != GT_SINGLE_PLAYER && entnum != clent->s.number) { +// trap_DropClient( clent->s.number, "Dropped due to illegal ClientDamage command\n" ); +// return; +// } + // -NERVE - SMF + + // if the attacker can't see the target, then don't allow damage + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + // TTimo it can happen that enemy->client == NULL + // see Changelog 09/22/2001 + if ( ( enemy->client ) && ( !CanDamage( ent, enemy->client->ps.origin ) ) ) { + return; // don't allow damage + } + } + + switch ( id ) { + case CLDMG_SPIRIT: + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + G_Damage( ent, enemy, enemy, vec3_origin, vec3_origin, 3, DAMAGE_NO_KNOCKBACK, MOD_ZOMBIESPIRIT ); + } + break; + case CLDMG_BOSS1LIGHTNING: + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + break; + } + if ( ent->takedamage ) { + VectorSubtract( ent->r.currentOrigin, enemy->r.currentOrigin, vec ); + VectorNormalize( vec ); + G_Damage( ent, enemy, enemy, vec, ent->r.currentOrigin, 6 + rand() % 3, 0, MOD_LIGHTNING ); + } + break; + case CLDMG_TESLA: + // do some cheat protection + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + if ( enemy->s.weapon != WP_TESLA ) { + break; + } + if ( !( enemy->client->buttons & BUTTON_ATTACK ) ) { + break; + } + //if ( AICast_GetCastState( enemy->s.number )->lastWeaponFiredWeaponNum != WP_TESLA ) + // break; + //if ( AICast_GetCastState( enemy->s.number )->lastWeaponFired < level.time - 400 ) + // break; + } + + if ( ( ent->aiCharacter == AICHAR_PROTOSOLDIER ) || + ( ent->aiCharacter == AICHAR_SUPERSOLDIER ) || + ( ent->aiCharacter == AICHAR_LOPER ) || + ( ent->aiCharacter >= AICHAR_STIMSOLDIER1 && ent->aiCharacter <= AICHAR_STIMSOLDIER3 ) ) { + break; + } + + if ( ent->takedamage /*&& !AICast_NoFlameDamage(ent->s.number)*/ ) { + VectorSubtract( ent->r.currentOrigin, enemy->r.currentOrigin, vec ); + VectorNormalize( vec ); + G_Damage( ent, enemy, enemy, vec, ent->r.currentOrigin, 3, 0, MOD_LIGHTNING ); + } + break; + case CLDMG_FLAMETHROWER: + // do some cheat protection +/* JPW NERVE pulled flamethrower client damage completely + if (g_gametype.integer != GT_SINGLE_PLAYER) { + if ( enemy->s.weapon != WP_FLAMETHROWER ) + break; +// if ( !(enemy->client->buttons & BUTTON_ATTACK) ) // JPW NERVE flames should be able to damage while puffs are active +// break; + } else { + // this is required for Zombie flame attack + //if ((enemy->aiCharacter == AICHAR_ZOMBIE) && !AICast_VisibleFromPos( enemy->r.currentOrigin, enemy->s.number, ent->r.currentOrigin, ent->s.number, qfalse )) + // break; + } + + if ( ent->takedamage && !AICast_NoFlameDamage(ent->s.number) ) { + #define FLAME_THRESHOLD 50 + int damage = 5; + + // RF, only do damage once they start burning + //if (ent->health > 0) // don't explode from flamethrower + // G_Damage( traceEnt, ent, ent, forward, tr.endpos, 1, 0, MOD_LIGHTNING); + + // now check the damageQuota to see if we should play a pain animation + // first reduce the current damageQuota with time + if (ent->flameQuotaTime && ent->flameQuota > 0) { + ent->flameQuota -= (int)(((float)(level.time - ent->flameQuotaTime)/1000) * (float)damage/2.0); + if (ent->flameQuota < 0) + ent->flameQuota = 0; + } + + // add the new damage + ent->flameQuota += damage; + ent->flameQuotaTime = level.time; + + // Ridah, make em burn + if (ent->client && ( !(ent->r.svFlags & SVF_CASTAI) || ent->health <= 0 || ent->flameQuota > FLAME_THRESHOLD)) { if (ent->s.onFireEnd < level.time) + ent->s.onFireStart = level.time; + if (ent->health <= 0 || !(ent->r.svFlags & SVF_CASTAI) || (g_gametype.integer != GT_SINGLE_PLAYER)) { + if (ent->r.svFlags & SVF_CASTAI) { + ent->s.onFireEnd = level.time + 6000; + } else { + ent->s.onFireEnd = level.time + FIRE_FLASH_TIME; + } + } else { + ent->s.onFireEnd = level.time + 99999; // make sure it goes for longer than they need to die + } + ent->flameBurnEnt = enemy->s.number; + // add to playerState for client-side effect + ent->client->ps.onFireStart = level.time; + } + } +*/ + break; + } +} +// -NERVE - SMF + +/* +============ +Cmd_ClientDamage_f +============ +*/ +void Cmd_ClientDamage_f( gentity_t *clent ) { + char s[MAX_STRING_CHARS]; + int entnum, id, enemynum; + + if ( trap_Argc() != 4 ) { + G_Printf( "ClientDamage command issued with incorrect number of args\n" ); + } + + trap_Argv( 1, s, sizeof( s ) ); + entnum = atoi( s ); + + trap_Argv( 2, s, sizeof( s ) ); + enemynum = atoi( s ); + + trap_Argv( 3, s, sizeof( s ) ); + id = atoi( s ); + + ClientDamage( clent, entnum, enemynum, id ); +} + +/* +============== +Cmd_EntityCount_f +============== +*/ +#define AITEAM_NAZI 0 +#define AITEAM_ALLIES 1 +#define AITEAM_MONSTER 2 +void Cmd_EntityCount_f( gentity_t *ent ) { + if ( !g_cheats.integer ) { + return; + } + + G_Printf( "entity count = %i\n", level.num_entities ); + + { + int kills[2]; + int nazis[2]; + int monsters[2]; + int i; + gentity_t *ent; + + // count kills + kills[0] = kills[1] = 0; + nazis[0] = nazis[1] = 0; + monsters[0] = monsters[1] = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + ent = &g_entities[i]; + + if ( !ent->inuse ) { + continue; + } + + if ( !( ent->r.svFlags & SVF_CASTAI ) ) { + continue; + } + + if ( ent->aiTeam == AITEAM_ALLIES ) { + continue; + } + + kills[1]++; + + if ( ent->health <= 0 ) { + kills[0]++; + } + + if ( ent->aiTeam == AITEAM_NAZI ) { + nazis[1]++; + if ( ent->health <= 0 ) { + nazis[0]++; + } + } else { + monsters[1]++; + if ( ent->health <= 0 ) { + monsters[0]++; + } + } + } + G_Printf( "kills %i/%i nazis %i/%i monsters %i/%i \n",kills[0], kills[1], nazis[0], nazis[1], monsters[0], monsters[1] ); + + } +} + +// NERVE - SMF +/* +============ +Cmd_SetSpawnPoint_f +============ +*/ +void Cmd_SetSpawnPoint_f( gentity_t *clent ) { + char arg[MAX_TOKEN_CHARS]; +// int spawnIndex; + + if ( trap_Argc() != 2 ) { + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + if ( clent->client ) { // JPW NERVE + clent->client->sess.spawnObjectiveIndex = atoi( arg ); // JPW NERVE + } +} +// -NERVE - SMF + +/* +================= +ClientCommand +================= +*/ +void ClientCommand( int clientNum ) { + gentity_t *ent; + char cmd[MAX_TOKEN_CHARS]; + + ent = g_entities + clientNum; + if ( !ent->client ) { + return; // not fully in game yet + } + + trap_Argv( 0, cmd, sizeof( cmd ) ); + + if ( Q_stricmp( cmd, "say" ) == 0 ) { + Cmd_Say_f( ent, SAY_ALL, qfalse ); + return; + } + if ( Q_stricmp( cmd, "say_team" ) == 0 ) { + Cmd_Say_f( ent, SAY_TEAM, qfalse ); + return; + } + // NERVE - SMF + if ( Q_stricmp( cmd, "say_limbo" ) == 0 ) { + Cmd_Say_f( ent, SAY_LIMBO, qfalse ); + return; + } + if ( Q_stricmp( cmd, "vsay" ) == 0 ) { + Cmd_Voice_f( ent, SAY_ALL, qfalse, qfalse ); + return; + } + if ( Q_stricmp( cmd, "vsay_team" ) == 0 ) { + Cmd_Voice_f( ent, SAY_TEAM, qfalse, qfalse ); + return; + } + // -NERVE - SMF + + if ( Q_stricmp( cmd, "tell" ) == 0 ) { + Cmd_Tell_f( ent ); + return; + } + if ( Q_stricmp( cmd, "score" ) == 0 ) { + Cmd_Score_f( ent ); + return; + } + + // NERVE - SMF - moved this here so current/new players can set team during scoreboard win + if ( Q_stricmp( cmd, "team" ) == 0 ) { + Cmd_Team_f( ent ); + return; + } + + // ignore all other commands when at intermission + if ( level.intermissiontime ) { +// Cmd_Say_f (ent, qfalse, qtrue); // NERVE - SMF - we don't want to spam the clients with this. + return; + } + + if ( Q_stricmp( cmd, "give" ) == 0 ) { + Cmd_Give_f( ent ); + } else if ( Q_stricmp( cmd, "god" ) == 0 ) { + Cmd_God_f( ent ); + } else if ( Q_stricmp( cmd, "nofatigue" ) == 0 ) { + Cmd_Nofatigue_f( ent ); + } else if ( Q_stricmp( cmd, "notarget" ) == 0 ) { + Cmd_Notarget_f( ent ); + } else if ( Q_stricmp( cmd, "noclip" ) == 0 ) { + Cmd_Noclip_f( ent ); + } else if ( Q_stricmp( cmd, "kill" ) == 0 ) { + Cmd_Kill_f( ent ); + } else if ( Q_stricmp( cmd, "levelshot" ) == 0 ) { + Cmd_LevelShot_f( ent ); + } else if ( Q_stricmp( cmd, "follow" ) == 0 ) { + Cmd_Follow_f( ent ); + } else if ( Q_stricmp( cmd, "follownext" ) == 0 ) { + Cmd_FollowCycle_f( ent, 1 ); + } else if ( Q_stricmp( cmd, "followprev" ) == 0 ) { + Cmd_FollowCycle_f( ent, -1 ); + } +// else if (Q_stricmp (cmd, "team") == 0) // NERVE - SMF - moved above intermission check +// Cmd_Team_f (ent); + else if ( Q_stricmp( cmd, "where" ) == 0 ) { + Cmd_Where_f( ent ); + } else if ( Q_stricmp( cmd, "callvote" ) == 0 ) { + Cmd_CallVote_f( ent ); + } else if ( Q_stricmp( cmd, "vote" ) == 0 ) { + Cmd_Vote_f( ent ); + } else if ( Q_stricmp( cmd, "gc" ) == 0 ) { + Cmd_GameCommand_f( ent ); + } +// else if (Q_stricmp (cmd, "startCamera") == 0) +// Cmd_StartCamera_f( ent ); +// else if (Q_stricmp (cmd, "stopCamera") == 0) +// Cmd_StopCamera_f( ent ); +// else if (Q_stricmp (cmd, "setCameraOrigin") == 0) +// Cmd_SetCameraOrigin_f( ent ); + else if ( Q_stricmp( cmd, "setviewpos" ) == 0 ) { + Cmd_SetViewpos_f( ent ); + } else if ( Q_stricmp( cmd, "entitycount" ) == 0 ) { + Cmd_EntityCount_f( ent ); + } else if ( Q_stricmp( cmd, "setspawnpt" ) == 0 ) { + Cmd_SetSpawnPoint_f( ent ); + } else { + trap_SendServerCommand( clientNum, va( "print \"unknown cmd[lof] %s\n\"", cmd ) ); + } +} diff --git a/src/game/g_combat.c b/src/game/g_combat.c new file mode 100644 index 0000000..c80a555 --- /dev/null +++ b/src/game/g_combat.c @@ -0,0 +1,1390 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: g_combat.c + * + * desc: + * +*/ + +#include "g_local.h" + + +/* +============ +AddScore + +Adds score to both the client and his team +============ +*/ +void AddScore( gentity_t *ent, int score ) { + if ( !ent->client ) { + return; + } + // no scoring during pre-match warmup + if ( level.warmupTime ) { + return; + } + + // Ridah, no scoring during single player + // DHM - Nerve :: fix typo + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return; + } + // done. + + + ent->client->ps.persistant[PERS_SCORE] += score; + if ( g_gametype.integer >= GT_TEAM ) { // JPW NERVE changed to >= + level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score; + } + CalculateRanks(); +} + +/* +================= +TossClientItems + +Toss the weapon and powerups for the killed player +================= +*/ +void TossClientItems( gentity_t *self ) { + gitem_t *item; + int weapon; + gentity_t *drop = 0; + + // drop the weapon if not a gauntlet or machinegun + weapon = self->s.weapon; + + // make a special check to see if they are changing to a new + // weapon that isn't the mg or gauntlet. Without this, a client + // can pick up a weapon, be killed, and not drop the weapon because + // their weapon change hasn't completed yet and they are still holding the MG. + + // (SA) always drop what you were switching to + if ( 1 ) { + if ( self->client->ps.weaponstate == WEAPON_DROPPING ) { + weapon = self->client->pers.cmd.weapon; + } + if ( !( COM_BitCheck( self->client->ps.weapons, weapon ) ) ) { + weapon = WP_NONE; + } + } + + // JPW NERVE don't drop these weapon types + if ( ( weapon == WP_FLAMETHROWER ) || ( weapon == WP_GARAND ) || ( weapon == WP_MAUSER ) || ( weapon == WP_VENOM ) ) { + weapon = WP_NONE; + } + // jpw + + if ( weapon > WP_NONE && weapon < WP_MONSTER_ATTACK1 && self->client->ps.ammo[ BG_FindAmmoForWeapon( weapon )] ) { + // find the item type for this weapon + item = BG_FindItemForWeapon( weapon ); + // spawn the item + + // Rafael + if ( !( self->client->ps.persistant[PERS_HWEAPON_USE] ) ) { + drop = Drop_Item( self, item, 0, qfalse ); + // JPW NERVE -- fix ammo counts + drop->count = self->client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; + drop->item->quantity = self->client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; + // jpw + } + } +} + + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) { + vec3_t dir; + vec3_t angles; + + if ( attacker && attacker != self ) { + VectorSubtract( attacker->s.pos.trBase, self->s.pos.trBase, dir ); + } else if ( inflictor && inflictor != self ) { + VectorSubtract( inflictor->s.pos.trBase, self->s.pos.trBase, dir ); + } else { + self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW]; + return; + } + + self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw( dir ); + + angles[YAW] = vectoyaw( dir ); + angles[PITCH] = 0; + angles[ROLL] = 0; +} + + +/* +============== +GibHead +============== +*/ +void GibHead( gentity_t *self, int killer ) { + G_AddEvent( self, EV_GIB_HEAD, killer ); +} + +/* +================== +GibEntity +================== +*/ +void GibEntity( gentity_t *self, int killer ) { + gentity_t *other = &g_entities[killer]; + vec3_t dir; + + VectorClear( dir ); + if ( other->inuse ) { + if ( other->client ) { + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); + VectorNormalize( dir ); + } else if ( !VectorCompare( other->s.pos.trDelta, vec3_origin ) ) { + VectorNormalize2( other->s.pos.trDelta, dir ); + } + } + + G_AddEvent( self, EV_GIB_PLAYER, DirToByte( dir ) ); + self->takedamage = qfalse; + self->s.eType = ET_INVISIBLE; + self->r.contents = 0; +} + +/* +================== +body_die +================== +*/ +void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { + if ( self->health > GIB_HEALTH ) { + return; + } + + GibEntity( self, 0 ); +} + + +// these are just for logging, the client prints its own messages +char *modNames[] = { + "MOD_UNKNOWN", + "MOD_SHOTGUN", + "MOD_GAUNTLET", + "MOD_MACHINEGUN", + "MOD_GRENADE", + "MOD_GRENADE_SPLASH", + "MOD_ROCKET", + "MOD_ROCKET_SPLASH", + "MOD_RAILGUN", + "MOD_LIGHTNING", + "MOD_BFG", + "MOD_BFG_SPLASH", + "MOD_KNIFE", + "MOD_KNIFE2", + "MOD_KNIFE_STEALTH", + "MOD_LUGER", + "MOD_COLT", + "MOD_MP40", + "MOD_THOMPSON", + "MOD_STEN", + "MOD_MAUSER", + "MOD_SNIPERRIFLE", + "MOD_GARAND", + "MOD_SNOOPERSCOPE", + "MOD_SILENCER", //----(SA) + "MOD_AKIMBO", //----(SA) + "MOD_BAR", //----(SA) + "MOD_FG42", + "MOD_FG42SCOPE", + "MOD_PANZERFAUST", + "MOD_ROCKET_LAUNCHER", + "MOD_GRENADE_LAUNCHER", + "MOD_VENOM", + "MOD_VENOM_FULL", + "MOD_FLAMETHROWER", + "MOD_TESLA", + "MOD_SPEARGUN", + "MOD_SPEARGUN_CO2", + "MOD_GRENADE_PINEAPPLE", + "MOD_CROSS", + "MOD_MORTAR", + "MOD_MORTAR_SPLASH", + "MOD_KICKED", + "MOD_GRABBER", + "MOD_WATER", + "MOD_SLIME", + "MOD_LAVA", + "MOD_CRUSH", + "MOD_TELEFRAG", + "MOD_FALLING", + "MOD_SUICIDE", + "MOD_TARGET_LASER", + "MOD_TRIGGER_HURT", + "MOD_GRAPPLE", + "MOD_EXPLOSIVE", + "MOD_POISONGAS", + "MOD_ZOMBIESPIT", + "MOD_ZOMBIESPIT_SPLASH", + "MOD_ZOMBIESPIRIT", + "MOD_ZOMBIESPIRIT_SPLASH", + "MOD_LOPER_LEAP", + "MOD_LOPER_GROUND", + "MOD_LOPER_HIT", +// JPW NERVE + "MOD_LT_ARTILLERY", + "MOD_LT_AIRSTRIKE", + "MOD_ENGINEER", // not sure if we'll use + "MOD_MEDIC", // these like this or not +// jpw + "MOD_BAT" +}; + +/* +================== +player_die +================== +*/ +void limbo( gentity_t *ent, qboolean makeCorpse ); // JPW NERVE + +void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { + gentity_t *ent; + // TTimo might be used uninitialized + int contents = 0; + int killer; + int i; + char *killerName, *obit; + qboolean nogib = qtrue; + gitem_t *item = NULL; // JPW NERVE for flag drop + vec3_t launchvel,launchspot; // JPW NERVE + gentity_t *flag; // JPW NERVE + + if ( self->client->ps.pm_type == PM_DEAD ) { + return; + } + + if ( level.intermissiontime ) { + return; + } + + self->client->ps.pm_type = PM_DEAD; + + G_AddEvent( self, EV_STOPSTREAMINGSOUND, 0 ); + + if ( attacker ) { + killer = attacker->s.number; + if ( attacker->client ) { + killerName = attacker->client->pers.netname; + } else { + killerName = ""; + } + } else { + killer = ENTITYNUM_WORLD; + killerName = ""; + } + + if ( killer < 0 || killer >= MAX_CLIENTS ) { + killer = ENTITYNUM_WORLD; + killerName = ""; + } + + if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) { + obit = ""; + } else { + obit = modNames[ meansOfDeath ]; + } + + G_LogPrintf( "Kill: %i %i %i: %s killed %s by %s\n", + killer, self->s.number, meansOfDeath, killerName, + self->client->pers.netname, obit ); + + // broadcast the death event to everyone + ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); + ent->s.eventParm = meansOfDeath; + ent->s.otherEntityNum = self->s.number; + ent->s.otherEntityNum2 = killer; + ent->r.svFlags = SVF_BROADCAST; // send to everyone + + self->enemy = attacker; + + self->client->ps.persistant[PERS_KILLED]++; + +// JPW NERVE -- if player is holding ticking grenade, drop it + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + if ( ( self->client->ps.grenadeTimeLeft ) && ( self->s.weapon != WP_DYNAMITE ) ) { + launchvel[0] = crandom(); + launchvel[1] = crandom(); + launchvel[2] = random(); + VectorScale( launchvel, 160, launchvel ); + VectorCopy( self->r.currentOrigin, launchspot ); + launchspot[2] += 40; + fire_grenade( self, launchspot, launchvel, self->s.weapon ); + + } + } +// jpw + + if ( attacker && attacker->client ) { + if ( attacker == self || OnSameTeam( self, attacker ) ) { + + // DHM - Nerve :: Complaint lodging + if ( attacker != self && level.warmupTime <= 0 ) { + if ( attacker->client->pers.localClient ) { + trap_SendServerCommand( self - g_entities, "complaint -4" ); + } else { + trap_SendServerCommand( self - g_entities, va( "complaint %i", attacker->s.number ) ); + self->client->pers.complaintClient = attacker->s.clientNum; + self->client->pers.complaintEndTime = level.time + 20500; + } + } + // dhm + + // JPW NERVE + if ( g_gametype.integer >= GT_WOLF ) { // high penalty to offset medic heal + AddScore( attacker, WOLF_FRIENDLY_PENALTY ); + } else { + // jpw + AddScore( attacker, -1 ); + } + } else { + // JPW NERVE -- mostly added as conveneience so we can tweak from the #defines all in one place + if ( g_gametype.integer >= GT_WOLF ) { + AddScore( attacker, WOLF_FRAG_BONUS ); + } else { + // jpw + AddScore( attacker, 1 ); + } + + attacker->client->lastKillTime = level.time; + } + } else { + AddScore( self, -1 ); + } + + // Add team bonuses + Team_FragBonuses( self, inflictor, attacker ); + + // if client is in a nodrop area, don't drop anything +// JPW NERVE new drop behavior + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // only drop here in single player; in multiplayer, drop @ limbo + contents = trap_PointContents( self->r.currentOrigin, -1 ); + if ( !( contents & CONTENTS_NODROP ) ) { + TossClientItems( self ); + } + } + + // drop flag regardless + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + if ( self->client->ps.powerups[PW_REDFLAG] ) { + item = BG_FindItem( "Red Flag" ); + if ( !item ) { + item = BG_FindItem( "Objective" ); + } + + self->client->ps.powerups[PW_REDFLAG] = 0; + } + if ( self->client->ps.powerups[PW_BLUEFLAG] ) { + item = BG_FindItem( "Blue Flag" ); + if ( !item ) { + item = BG_FindItem( "Objective" ); + } + + self->client->ps.powerups[PW_BLUEFLAG] = 0; + } + + if ( item ) { + launchvel[0] = crandom() * 20; + launchvel[1] = crandom() * 20; + launchvel[2] = 10 + random() * 10; + + flag = LaunchItem( item,self->r.currentOrigin,launchvel,self->s.number ); + flag->s.modelindex2 = self->s.otherEntityNum2; // JPW NERVE FIXME set player->otherentitynum2 with old modelindex2 from flag and restore here + flag->message = self->message; // DHM - Nerve :: also restore item name + // Clear out player's temp copies + self->s.otherEntityNum2 = 0; + self->message = NULL; + } + + // send a fancy "MEDIC!" scream. Sissies, ain' they? + if ( self->client != NULL ) { + if ( self->health > GIB_HEALTH && meansOfDeath != MOD_SUICIDE ) { + + if ( self->client->sess.sessionTeam == TEAM_RED ) { + if ( random() > 0.5 ) { + G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/axis/g-medic2.wav" ) ); + } else { + G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/axis/g-medic3.wav" ) ); + } + } else { + if ( random() > 0.5 ) { + G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/allies/a-medic3.wav" ) ); + } else { + G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/allies/a-medic2.wav" ) ); + } + } + } + } + } +// jpw + + Cmd_Score_f( self ); // show scores + // send updated scores to any clients that are following this one, + // or they would get stale scoreboards + for ( i = 0 ; i < level.maxclients ; i++ ) { + gclient_t *client; + + client = &level.clients[i]; + if ( client->pers.connected != CON_CONNECTED ) { + continue; + } + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { + continue; + } + if ( client->sess.spectatorClient == self->s.number ) { + Cmd_Score_f( g_entities + i ); + } + } + + self->takedamage = qtrue; // can still be gibbed + self->r.contents = CONTENTS_CORPSE; + + self->s.powerups = 0; +// JPW NERVE -- only corpse in SP; in MP, need CONTENTS_BODY so medic can operate + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + self->s.weapon = WP_NONE; + self->s.angles[0] = 0; + } else { + self->client->limboDropWeapon = self->s.weapon; // store this so it can be dropped in limbo + } +// jpw + self->s.angles[2] = 0; + LookAtKiller( self, inflictor, attacker ); + + VectorCopy( self->s.angles, self->client->ps.viewangles ); + self->s.loopSound = 0; + + trap_UnlinkEntity( self ); + self->r.maxs[2] = 0; + self->client->ps.maxs[2] = 0; + trap_LinkEntity( self ); + + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + self->client->respawnTime = level.time + 800; + + // remove powerups + memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) ); + + // never gib in a nodrop + if ( self->health <= GIB_HEALTH && !( contents & CONTENTS_NODROP ) ) { + GibEntity( self, killer ); + nogib = qfalse; + } + + if ( nogib ) { + // normal death + // for the no-blood option, we need to prevent the health + // from going to gib level + if ( self->health <= GIB_HEALTH ) { + self->health = GIB_HEALTH + 1; + } + +// JPW NERVE for medic + self->client->medicHealAmt = 0; +// jpw + + // DHM - Play death animation, and set pm_time to delay 'fallen' animation + self->client->ps.pm_time = BG_AnimScriptEvent( &self->client->ps, ANIM_ET_DEATH, qfalse, qtrue ); + + G_AddEvent( self, EV_DEATH1 + 1, killer ); + + // the body can still be gibbed + self->die = body_die; + } + + trap_LinkEntity( self ); + + if ( g_gametype.integer >= GT_WOLF && meansOfDeath == MOD_SUICIDE ) { + limbo( self, qtrue ); + } +} + + +/* +================ +CheckArmor +================ +*/ +int CheckArmor( gentity_t *ent, int damage, int dflags ) { + gclient_t *client; + int save; + int count; + + if ( !damage ) { + return 0; + } + + client = ent->client; + + if ( !client ) { + return 0; + } + + if ( dflags & DAMAGE_NO_ARMOR ) { + return 0; + } + + // armor + count = client->ps.stats[STAT_ARMOR]; + save = ceil( damage * ARMOR_PROTECTION ); + if ( save >= count ) { + save = count; + } + + if ( !save ) { + return 0; + } + + client->ps.stats[STAT_ARMOR] -= save; + + return save; +} + +qboolean IsHeadShotWeapon( int mod, qboolean aicharacter ) { + if ( aicharacter ) { // ai's are allowed headshots from these weapons + if ( mod == MOD_SNIPERRIFLE || + mod == MOD_SNOOPERSCOPE ) { + return qtrue; + } + + return qfalse; + } + + // players are allowed headshots from these weapons + if ( mod == MOD_LUGER || + mod == MOD_COLT || + mod == MOD_AKIMBO || //----(SA) added + mod == MOD_MP40 || + mod == MOD_THOMPSON || + mod == MOD_STEN || + mod == MOD_BAR || + mod == MOD_FG42 || + mod == MOD_FG42SCOPE || + mod == MOD_MAUSER || + mod == MOD_GARAND || // JPW NERVE this was left out + mod == MOD_SNIPERRIFLE || + mod == MOD_SNOOPERSCOPE || + mod == MOD_SILENCER || //----(SA) modified + mod == MOD_SNIPERRIFLE ) { + return qtrue; + } + + return qfalse; +} + +qboolean IsHeadShot( gentity_t *targ, qboolean isAICharacter, vec3_t dir, vec3_t point, int mod ) { + gentity_t *head; + trace_t tr; + vec3_t start, end; + gentity_t *traceEnt; + orientation_t or; // DHM - Nerve + + qboolean head_shot_weapon = qfalse; + + // not a player or critter so bail + if ( !( targ->client ) ) { + return qfalse; + } + + if ( targ->health <= 0 ) { + return qfalse; + } + + head_shot_weapon = IsHeadShotWeapon( mod, isAICharacter ); + + if ( head_shot_weapon ) { + head = G_Spawn(); + + if ( trap_GetTag( targ->s.number, "tag_head", &or ) ) { + G_SetOrigin( head, or.origin ); + } else { + float height, dest; + vec3_t v, angles, forward, up, right; + + G_SetOrigin( head, targ->r.currentOrigin ); + + if ( targ->client->ps.pm_flags & PMF_DUCKED ) { // closer fake offset for 'head' box when crouching + height = targ->client->ps.crouchViewHeight - 12; + } else { + height = targ->client->ps.viewheight; + } + + // NERVE - SMF - this matches more closely with WolfMP models + VectorCopy( targ->client->ps.viewangles, angles ); + if ( angles[PITCH] > 180 ) { + dest = ( -360 + angles[PITCH] ) * 0.75; + } else { + dest = angles[PITCH] * 0.75; + } + angles[PITCH] = dest; + + AngleVectors( angles, forward, right, up ); + VectorScale( forward, 5, v ); + VectorMA( v, 18, up, v ); + + VectorAdd( v, head->r.currentOrigin, head->r.currentOrigin ); + head->r.currentOrigin[2] += height / 2; + // -NERVE - SMF + } + + VectorCopy( head->r.currentOrigin, head->s.origin ); + VectorCopy( targ->r.currentAngles, head->s.angles ); + VectorCopy( head->s.angles, head->s.apos.trBase ); + VectorCopy( head->s.angles, head->s.apos.trDelta ); + VectorSet( head->r.mins, -6, -6, -2 ); // JPW NERVE changed this z from -12 to -6 for crouching, also removed standing offset + VectorSet( head->r.maxs, 6, 6, 10 ); // changed this z from 0 to 6 + head->clipmask = CONTENTS_SOLID; + head->r.contents = CONTENTS_SOLID; + + trap_LinkEntity( head ); + + // trace another shot see if we hit the head + VectorCopy( point, start ); + VectorMA( start, 64, dir, end ); + trap_Trace( &tr, start, NULL, NULL, end, targ->s.number, MASK_SHOT ); + + traceEnt = &g_entities[ tr.entityNum ]; + + if ( g_debugBullets.integer >= 3 ) { // show hit player head bb + gentity_t *tent; + vec3_t b1, b2; + VectorCopy( head->r.currentOrigin, b1 ); + VectorCopy( head->r.currentOrigin, b2 ); + VectorAdd( b1, head->r.mins, b1 ); + VectorAdd( b2, head->r.maxs, b2 ); + tent = G_TempEntity( b1, EV_RAILTRAIL ); + VectorCopy( b2, tent->s.origin2 ); + tent->s.dmgFlags = 1; + + // show headshot trace + // end the headshot trace at the head box if it hits + if ( tr.fraction != 1 ) { + VectorMA( start, ( tr.fraction * 64 ), dir, end ); + } + tent = G_TempEntity( start, EV_RAILTRAIL ); + VectorCopy( end, tent->s.origin2 ); + tent->s.dmgFlags = 0; + } + + G_FreeEntity( head ); + + if ( traceEnt == head ) { + level.totalHeadshots++; // NERVE - SMF + return qtrue; + } else { + level.missedHeadshots++; // NERVE - SMF + } + } + + return qfalse; +} + +gentity_t* G_BuildHead( gentity_t *ent ) { + gentity_t* head; + orientation_t or; // DHM - Nerve + + head = G_Spawn(); + + if ( trap_GetTag( ent->s.number, "tag_head", &or ) ) { + G_SetOrigin( head, or.origin ); + } else { + float height, dest; + vec3_t v, angles, forward, up, right; + + G_SetOrigin( head, ent->r.currentOrigin ); + + if ( ent->client->ps.pm_flags & PMF_DUCKED ) { // closer fake offset for 'head' box when crouching + height = ent->client->ps.crouchViewHeight - 12; + } else { + height = ent->client->ps.viewheight; + } + + // NERVE - SMF - this matches more closely with WolfMP models + VectorCopy( ent->client->ps.viewangles, angles ); + if ( angles[PITCH] > 180 ) { + dest = ( -360 + angles[PITCH] ) * 0.75; + } else { + dest = angles[PITCH] * 0.75; + } + angles[PITCH] = dest; + + AngleVectors( angles, forward, right, up ); + VectorScale( forward, 5, v ); + VectorMA( v, 18, up, v ); + + VectorAdd( v, head->r.currentOrigin, head->r.currentOrigin ); + head->r.currentOrigin[2] += height / 2; + // -NERVE - SMF + } + + VectorCopy( head->r.currentOrigin, head->s.origin ); + VectorCopy( ent->r.currentAngles, head->s.angles ); + VectorCopy( head->s.angles, head->s.apos.trBase ); + VectorCopy( head->s.angles, head->s.apos.trDelta ); + VectorSet( head->r.mins, -6, -6, -2 ); // JPW NERVE changed this z from -12 to -6 for crouching, also removed standing offset + VectorSet( head->r.maxs, 6, 6, 10 ); // changed this z from 0 to 6 + head->clipmask = CONTENTS_SOLID; + head->r.contents = CONTENTS_SOLID; + head->parent = ent; + head->s.eType = ET_TEMPHEAD; + + trap_LinkEntity( head ); + + return head; +} + +/* +============== +G_ArmorDamage + brokeparts is how many should be broken off now + curbroke is how many are broken + the difference is how many to pop off this time +============== +*/ +void G_ArmorDamage( gentity_t *targ ) { + int brokeparts, curbroke; + int numParts; + int dmgbits = 16; // 32/2; + int i; + + if ( !targ->client ) { + return; + } + + if ( targ->s.aiChar == AICHAR_PROTOSOLDIER ) { + numParts = 9; + } else if ( targ->s.aiChar == AICHAR_SUPERSOLDIER ) { + numParts = 14; + } else if ( targ->s.aiChar == AICHAR_HEINRICH ) { + numParts = 20; + } else { + return; + } + + if ( numParts > dmgbits ) { + numParts = dmgbits; // lock this down so it doesn't overwrite any bits that it shouldn't. TODO: fix this + + + } + // determined here (on server) by location of hit and existing armor, you're updating here so + // the client knows which pieces are still in place, and by difference with previous state, which + // pieces to play an effect where the part is blown off. + // Need to do it here so we have info on where the hit registered (head, torso, legs or if we go with more detail; arm, leg, chest, codpiece, etc) + + // ... Ick, just discovered that the refined hit detection ("hit nearest to which tag") is clientside... + + // For now, I'll randomly pick a part that hasn't been cleared. This might end up looking okay, and we won't need the refined hits. + // however, we still have control on the server-side of which parts come off, regardless of what shceme is used. + + brokeparts = (int)( ( 1 - ( (float)( targ->health ) / (float)( targ->client->ps.stats[STAT_MAX_HEALTH] ) ) ) * numParts ); + + if ( brokeparts && ( ( targ->s.dmgFlags & ( ( 1 << numParts ) - 1 ) ) != ( 1 << numParts ) - 1 ) ) { // there are still parts left to clear + + // how many are removed already? + curbroke = 0; + for ( i = 0; i < numParts; i++ ) { + if ( targ->s.dmgFlags & ( 1 << i ) ) { + curbroke++; + } + } + + // need to remove more + if ( brokeparts - curbroke >= 1 && curbroke < numParts ) { + for ( i = 0; i < ( brokeparts - curbroke ); i++ ) { + int remove = rand() % ( numParts ); + + if ( !( ( targ->s.dmgFlags & ( ( 1 << numParts ) - 1 ) ) != ( 1 << numParts ) - 1 ) ) { // no parts are available any more + break; + } + + // FIXME: lose the 'while' loop. Still should be safe though, since the check above verifies that it will eventually find a valid part + while ( targ->s.dmgFlags & ( 1 << remove ) ) { + remove = rand() % ( numParts ); + } + + targ->s.dmgFlags |= ( 1 << remove ); // turn off 'undamaged' part + if ( (int)( random() + 0.5 ) ) { // choose one of two possible replacements + targ->s.dmgFlags |= ( 1 << ( numParts + remove ) ); + } + } + } + } +} + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +inflictor, attacker, dir, and point can be NULL for environmental effects + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ + +void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, + vec3_t dir, vec3_t point, int damage, int dflags, int mod ) { + gclient_t *client; + int take; + int save; + int asave; + int knockback; + + if ( !targ->takedamage ) { + return; + } + + // the intermission has allready been qualified for, so don't + // allow any extra scoring + if ( level.intermissionQueued || g_gamestate.integer != GS_PLAYING ) { + return; + } + + if ( !inflictor ) { + inflictor = &g_entities[ENTITYNUM_WORLD]; + } + if ( !attacker ) { + attacker = &g_entities[ENTITYNUM_WORLD]; + } + +// JPW NERVE + if ( ( targ->waterlevel >= 3 ) && ( mod == MOD_FLAMETHROWER ) ) { + return; + } +// jpw + + // shootable doors / buttons don't actually have any health + if ( targ->s.eType == ET_MOVER && !( targ->aiName ) && !( targ->isProp ) && !targ->scriptName ) { + if ( targ->use && targ->moverState == MOVER_POS1 ) { + targ->use( targ, inflictor, attacker ); + } + return; + } + + if ( targ->s.eType == ET_MOVER && targ->aiName && !( targ->isProp ) && !targ->scriptName ) { + switch ( mod ) { + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: + break; + default: + return; // no damage from other weapons + } + } else if ( targ->s.eType == ET_EXPLOSIVE ) { + // 32 Explosive + // 64 Dynamite only + if ( ( targ->spawnflags & 32 ) || ( targ->spawnflags & 64 ) ) { + switch ( mod ) { + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: + case MOD_AIRSTRIKE: + case MOD_GRENADE_PINEAPPLE: + case MOD_MORTAR: + case MOD_MORTAR_SPLASH: + case MOD_EXPLOSIVE: + if ( targ->spawnflags & 64 ) { + return; + } + + break; + + case MOD_DYNAMITE: + case MOD_DYNAMITE_SPLASH: + break; + + default: + return; + } + } + } + + client = targ->client; + + if ( client ) { + if ( client->noclip ) { + return; + } + } + + if ( !dir ) { + dflags |= DAMAGE_NO_KNOCKBACK; + } else { + VectorNormalize( dir ); + } + + knockback = damage; + if ( knockback > 200 ) { + knockback = 200; + } + if ( targ->flags & FL_NO_KNOCKBACK ) { + knockback = 0; + } + if ( dflags & DAMAGE_NO_KNOCKBACK ) { + knockback = 0; + } + + // figure momentum add, even if the damage won't be taken + if ( knockback && targ->client && ( g_friendlyFire.integer || !OnSameTeam( targ, attacker ) ) ) { + vec3_t kvel; + float mass; + + mass = 200; + + if ( mod == MOD_LIGHTNING && !( ( level.time + targ->s.number * 50 ) % 400 ) ) { + knockback = 60; + dir[2] = 0.3; + } + + VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel ); + VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); + + if ( targ == attacker && !( mod != MOD_ROCKET && + mod != MOD_ROCKET_SPLASH && + mod != MOD_GRENADE && + mod != MOD_GRENADE_SPLASH && + mod != MOD_DYNAMITE ) ) { + targ->client->ps.velocity[2] *= 0.25; + } + + // set the timer so that the other client can't cancel + // out the movement immediately + if ( !targ->client->ps.pm_time ) { + int t; + + t = knockback * 2; + if ( t < 50 ) { + t = 50; + } + if ( t > 200 ) { + t = 200; + } + targ->client->ps.pm_time = t; + targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + } + + // check for completely getting out of the damage + if ( !( dflags & DAMAGE_NO_PROTECTION ) ) { + + // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target + // if the attacker was on the same team + if ( targ != attacker && OnSameTeam( targ, attacker ) ) { + if ( !g_friendlyFire.integer ) { + return; + } + } + + // check for godmode + if ( targ->flags & FL_GODMODE ) { + return; + } + + // RF, warzombie defense position is basically godmode for the time being + if ( targ->flags & FL_DEFENSE_GUARD ) { + return; + } + + // check for invulnerability // (SA) moved from below so DAMAGE_NO_PROTECTION will still work + if ( client && client->ps.powerups[PW_INVULNERABLE] ) { //----(SA) added + return; + } + + } + + // battlesuit protects from all radius damage (but takes knockback) + // and protects 50% against all damage + if ( client && client->ps.powerups[PW_BATTLESUIT] ) { + G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 ); + if ( dflags & DAMAGE_RADIUS ) { + return; + } + damage *= 0.5; + } + + // add to the attacker's hit counter + if ( attacker->client && targ != attacker && targ->health > 0 ) { + if ( OnSameTeam( targ, attacker ) ) { + attacker->client->ps.persistant[PERS_HITS] -= damage; + } else { + attacker->client->ps.persistant[PERS_HITS] += damage; + } + } + + if ( damage < 1 ) { + damage = 1; + } + take = damage; + save = 0; + + // save some from armor + asave = CheckArmor( targ, take, dflags ); + take -= asave; + + if ( IsHeadShot( targ, qfalse, dir, point, mod ) ) { + + if ( take * 2 < 50 ) { // head shots, all weapons, do minimum 50 points damage + take = 50; + } else { + take *= 2; // sniper rifles can do full-kill (and knock into limbo) + + } + if ( !( targ->client->ps.eFlags & EF_HEADSHOT ) ) { // only toss hat on first headshot + G_AddEvent( targ, EV_LOSE_HAT, DirToByte( dir ) ); + } + + targ->client->ps.eFlags |= EF_HEADSHOT; + } + + if ( g_debugDamage.integer ) { + G_Printf( "client:%i health:%i damage:%i armor:%i\n", targ->s.number, + targ->health, take, asave ); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if ( client ) { + if ( attacker ) { + client->ps.persistant[PERS_ATTACKER] = attacker->s.number; + } else { + client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD; + } + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + + if ( dir ) { + VectorCopy( dir, client->damage_from ); + client->damage_fromWorld = qfalse; + } else { + VectorCopy( targ->r.currentOrigin, client->damage_from ); + client->damage_fromWorld = qtrue; + } + } + + // See if it's the player hurting the emeny flag carrier + Team_CheckHurtCarrier( targ, attacker ); + + if ( targ->client ) { + // set the last client who damaged the target + targ->client->lasthurt_client = attacker->s.number; + targ->client->lasthurt_mod = mod; + } + + // do the damage + if ( take ) { + targ->health = targ->health - take; + + // Ridah, can't gib with bullet weapons (except VENOM) + if ( mod != MOD_VENOM && attacker == inflictor && targ->health <= GIB_HEALTH ) { + if ( targ->aiCharacter != AICHAR_ZOMBIE ) { // zombie needs to be able to gib so we can kill him (although he doesn't actually GIB, he just dies) + targ->health = GIB_HEALTH + 1; + } + } + +// JPW NERVE overcome previous chunk of code for making grenades work again + if ( ( g_gametype.integer != GT_SINGLE_PLAYER ) && ( take > 190 ) ) { // 190 is greater than 2x mauser headshot, so headshots don't gib + targ->health = GIB_HEALTH - 1; + } +// jpw + //G_Printf("health at: %d\n", targ->health); + if ( targ->health <= 0 ) { + if ( client ) { + targ->flags |= FL_NO_KNOCKBACK; +// JPW NERVE -- repeated shooting sends to limbo + if ( g_gametype.integer >= GT_WOLF ) { + if ( ( targ->health < FORCE_LIMBO_HEALTH ) && ( targ->health > GIB_HEALTH ) && ( !( targ->client->ps.pm_flags & PMF_LIMBO ) ) ) { + limbo( targ, qtrue ); + } + } +// jpw + } + + if ( targ->health < -999 ) { + targ->health = -999; + } + + targ->enemy = attacker; + if ( targ->die ) { // Ridah, mg42 doesn't have die func (FIXME) + targ->die( targ, inflictor, attacker, take, mod ); + } + + // if we freed ourselves in death function + if ( !targ->inuse ) { + return; + } + + // RF, entity scripting + if ( targ->s.number >= MAX_CLIENTS && targ->health <= 0 ) { // might have revived itself in death function + G_Script_ScriptEvent( targ, "death", "" ); + } + + } else if ( targ->pain ) { + if ( dir ) { // Ridah, had to add this to fix NULL dir crash + VectorCopy( dir, targ->rotate ); + VectorCopy( point, targ->pos3 ); // this will pass loc of hit + } else { + VectorClear( targ->rotate ); + VectorClear( targ->pos3 ); + } + + targ->pain( targ, attacker, take, point ); + + // RF, entity scripting + if ( targ->s.number >= MAX_CLIENTS ) { + G_Script_ScriptEvent( targ, "pain", va( "%d %d", targ->health, targ->health + take ) ); + } + } + + //G_ArmorDamage(targ); //----(SA) moved out to separate routine + + // Ridah, this needs to be done last, incase the health is altered in one of the event calls + if ( targ->client ) { + targ->client->ps.stats[STAT_HEALTH] = targ->health; + } + } + +} + + +/* +============ +CanDamage + +Returns qtrue if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage( gentity_t *targ, vec3_t origin ) { + vec3_t dest; + trace_t tr; + vec3_t midpoint; + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin is 0,0,0 + VectorAdd( targ->r.absmin, targ->r.absmax, midpoint ); + VectorScale( midpoint, 0.5, midpoint ); + + VectorCopy( midpoint, dest ); + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if ( tr.fraction == 1.0 ) { + return qtrue; + } + + + + if ( &g_entities[tr.entityNum] == targ ) { + return qtrue; + } + + // this should probably check in the plane of projection, + // rather than in world coordinate, and also include Z + VectorCopy( midpoint, dest ); + dest[0] += 15.0; + dest[1] += 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if ( tr.fraction == 1.0 ) { + return qtrue; + } + + VectorCopy( midpoint, dest ); + dest[0] += 15.0; + dest[1] -= 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if ( tr.fraction == 1.0 ) { + return qtrue; + } + + VectorCopy( midpoint, dest ); + dest[0] -= 15.0; + dest[1] += 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if ( tr.fraction == 1.0 ) { + return qtrue; + } + + VectorCopy( midpoint, dest ); + dest[0] -= 15.0; + dest[1] -= 15.0; + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if ( tr.fraction == 1.0 ) { + return qtrue; + } + + + return qfalse; +} + + +/* +============ +G_RadiusDamage +============ +*/ +qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius, + gentity_t *ignore, int mod ) { + float points, dist; + gentity_t *ent; + int entityList[MAX_GENTITIES]; + int numListedEntities; + vec3_t mins, maxs; + vec3_t v; + vec3_t dir; + int i, e; + qboolean hitClient = qfalse; +// JPW NERVE + float boxradius; + vec3_t dest; + trace_t tr; + vec3_t midpoint; +// jpw + + + if ( radius < 1 ) { + radius = 1; + } + + boxradius = 1.41421356 * radius; // radius * sqrt(2) for bounding box enlargement -- + // bounding box was checking against radius / sqrt(2) if collision is along box plane + for ( i = 0 ; i < 3 ; i++ ) { + mins[i] = origin[i] - boxradius; // JPW NERVE + maxs[i] = origin[i] + boxradius; // JPW NERVE + } + + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) { + ent = &g_entities[entityList[ e ]]; + + if ( ent == ignore ) { + continue; + } + if ( !ent->takedamage ) { + continue; + } + +/* JPW NERVE -- we can put this back if we need to, but it kinna sucks for human-sized bboxes + // find the distance from the edge of the bounding box + for ( i = 0 ; i < 3 ; i++ ) { + if ( origin[i] < ent->r.absmin[i] ) { + v[i] = ent->r.absmin[i] - origin[i]; + } else if ( origin[i] > ent->r.absmax[i] ) { + v[i] = origin[i] - ent->r.absmax[i]; + } else { + v[i] = 0; + } + } +*/ +// JPW NERVE + if ( !ent->r.bmodel ) { + VectorSubtract( ent->r.currentOrigin,origin,v ); // JPW NERVE simpler centroid check that doesn't have box alignment weirdness + } else { + for ( i = 0 ; i < 3 ; i++ ) { + if ( origin[i] < ent->r.absmin[i] ) { + v[i] = ent->r.absmin[i] - origin[i]; + } else if ( origin[i] > ent->r.absmax[i] ) { + v[i] = origin[i] - ent->r.absmax[i]; + } else { + v[i] = 0; + } + } + } +// jpw + dist = VectorLength( v ); + if ( dist >= radius ) { + continue; + } + + points = damage * ( 1.0 - dist / radius ); + +// JPW NERVE -- different radiusdmg behavior for MP -- big explosions should do less damage (over less distance) through failed traces + if ( CanDamage( ent, origin ) ) { + if ( LogAccuracyHit( ent, attacker ) ) { + hitClient = qtrue; + } + VectorSubtract( ent->r.currentOrigin, origin, dir ); + // push the center of mass higher than the origin so players + // get knocked into the air more + dir[2] += 24; + G_Damage( ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod ); + } +// JPW NERVE -- MP weapons should do 1/8 damage through walls over 1/8th distance + else { + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + VectorAdd( ent->r.absmin, ent->r.absmax, midpoint ); + VectorScale( midpoint, 0.5, midpoint ); + VectorCopy( midpoint, dest ); + + trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); + if ( tr.fraction < 1.0 ) { + VectorSubtract( dest,origin,dest ); + dist = VectorLength( dest ); + if ( dist < radius * 0.2f ) { // closer than 1/4 dist + if ( LogAccuracyHit( ent, attacker ) ) { + hitClient = qtrue; + } + VectorSubtract( ent->r.currentOrigin, origin, dir ); + dir[2] += 24; + G_Damage( ent, NULL, attacker, dir, origin, (int)( points * 0.1f ), DAMAGE_RADIUS, mod ); + } + } + } + } +// jpw + } + return hitClient; +} diff --git a/src/game/g_items.c b/src/game/g_items.c new file mode 100644 index 0000000..81dc8bb --- /dev/null +++ b/src/game/g_items.c @@ -0,0 +1,1430 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: g_items.c + * + * desc: Items are any object that a player can touch to gain some effect. + * Pickup will return the number of seconds until they should respawn. + * all items should pop when dropped in lava or slime. + * Respawnable items don't actually go away when picked up, they are + * just made invisible and untouchable. This allows them to ride + * movers and respawn apropriately. + * +*/ + +#include "g_local.h" + + + +#define RESPAWN_SP -1 +#define RESPAWN_KEY 4 +#define RESPAWN_ARMOR 25 +#define RESPAWN_TEAM_WEAPON 30 +#define RESPAWN_HEALTH 35 +#define RESPAWN_AMMO 40 +#define RESPAWN_HOLDABLE 60 +#define RESPAWN_MEGAHEALTH 120 +#define RESPAWN_POWERUP 120 +#define RESPAWN_PARTIAL 998 // for multi-stage ammo/health +#define RESPAWN_PARTIAL_DONE 999 // for multi-stage ammo/health + + +//====================================================================== + +int Pickup_Powerup( gentity_t *ent, gentity_t *other ) { + int quantity; + int i; + gclient_t *client; + + if ( !other->client->ps.powerups[ent->item->giTag] ) { + + // some powerups are time based on how long the powerup is /used/ + // rather than timed from when the player picks it up. + if ( ent->item->giTag == PW_NOFATIGUE ) { + } else { + // round timing to seconds to make multiple powerup timers + // count in sync + other->client->ps.powerups[ent->item->giTag] = level.time - ( level.time % 1000 ); + } + } + + // if an amount was specified in the ent, use it + if ( ent->count ) { + quantity = ent->count; + } else { + quantity = ent->item->quantity; + } + + other->client->ps.powerups[ent->item->giTag] += quantity * 1000; + + + // brandy also gives a little health (10) + if ( ent->item->giTag == PW_NOFATIGUE ) { + if ( Q_stricmp( ent->item->classname, "item_stamina_brandy" ) == 0 ) { + other->health += 10; + if ( other->health > other->client->ps.stats[STAT_MAX_HEALTH] ) { + other->health = other->client->ps.stats[STAT_MAX_HEALTH]; + } + other->client->ps.stats[STAT_HEALTH] = other->health; + } + } + + + // Ridah, not in single player + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + // done. + // give any nearby players a "denied" anti-reward + for ( i = 0 ; i < level.maxclients ; i++ ) { + vec3_t delta; + float len; + vec3_t forward; + trace_t tr; + + client = &level.clients[i]; + if ( client == other->client ) { + continue; + } + if ( client->pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + continue; + } + + // if too far away, no sound + VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta ); + len = VectorNormalize( delta ); + if ( len > 192 ) { + continue; + } + + // if not facing, no sound + AngleVectors( client->ps.viewangles, forward, NULL, NULL ); + if ( DotProduct( delta, forward ) < 0.4 ) { + continue; + } + + // if not line of sight, no sound + trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID ); + if ( tr.fraction != 1.0 ) { + continue; + } + + // anti-reward + client->ps.persistant[PERS_REWARD_COUNT]++; + client->ps.persistant[PERS_REWARD] = REWARD_DENIED; + } + // Ridah + } + // done. + + if ( ent->s.density == 2 ) { // multi-stage health first stage + return RESPAWN_PARTIAL; + } else if ( ent->s.density == 1 ) { // last stage, leave the plate + return RESPAWN_PARTIAL_DONE; + } + + // single player has no respawns (SA) + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return RESPAWN_SP; + } + + return RESPAWN_POWERUP; +} + +//----(SA) Wolf keys +//====================================================================== +int Pickup_Key( gentity_t *ent, gentity_t *other ) { + other->client->ps.stats[STAT_KEYS] |= ( 1 << ent->item->giTag ); + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return RESPAWN_SP; + } else { + return RESPAWN_KEY; + } +} + + + +/* +============== +Pickup_Clipboard +============== +*/ +int Pickup_Clipboard( gentity_t *ent, gentity_t *other ) { + + if ( ent->spawnflags & 4 ) { + return 0; // leave in world + + } + return -1; +} + + +/* +============== +Pickup_Treasure +============== +*/ +int Pickup_Treasure( gentity_t *ent, gentity_t *other ) { + // TODO: increment treasure counter + return RESPAWN_SP; // no respawn +} + + +/* +============== +UseHoldableItem + server side handling of holdable item use +============== +*/ +void UseHoldableItem( gentity_t *ent, int item ) { + switch ( item ) { + case HI_MEDKIT: + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + break; + + case HI_WINE: // 1921 Chateu Lafite - gives 25 pts health up to max health + ent->health += 25; + if ( ent->health > ent->client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + break; + + case HI_SKULL: // skull of invulnerable - 30 sec invincible + ent->client->ps.powerups[PW_INVULNERABLE] = level.time + 30000; + break; + + case HI_WATER: // protection from drowning - 30 sec underwater breathing time + ent->client->ps.powerups[PW_BREATHER] = 30000; + break; + + case HI_ELECTRIC: // protection from electric attacks - absorbs 500 points of electric damage + ent->client->ps.powerups[PW_ELECTRIC] = 500; + break; + + case HI_FIRE: // protection from fire attacks - absorbs 500 points of fire damage + ent->client->ps.powerups[PW_FIRE] = 500; + break; + + case HI_STAMINA: // restores fatigue bar and sets "nofatigue" for a time period (currently forced to 60 sec) + //----(SA) NOTE: currently only gives free nofatigue time, doesn't reset fatigue bar. + // (this is because I'd like the restore to be visually gradual (on the HUD item representing + // current status of your fatigue) rather than snapping back to 'full') + ent->client->ps.powerups[PW_NOFATIGUE] = 60000; + break; + + case HI_BOOK1: + case HI_BOOK2: + case HI_BOOK3: + G_AddEvent( ent, EV_POPUPBOOK, ( item - HI_BOOK1 ) + 1 ); + break; + } +} + + +//====================================================================== + +int Pickup_Holdable( gentity_t *ent, gentity_t *other ) { + gitem_t *item; + +// item = BG_FindItemForHoldable(ent->item); + item = ent->item; + + if ( item->gameskillnumber[0] ) { // if the item specifies an amount, give it + other->client->ps.holdable[item->giTag] += item->gameskillnumber[0]; + } else { + other->client->ps.holdable[item->giTag] += 1; // add default of 1 + + } + other->client->ps.holding = item->giTag; + + other->client->ps.stats[STAT_HOLDABLE_ITEM] |= ( 1 << ent->item->giTag ); //----(SA) added + + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return RESPAWN_SP; + } else { + return RESPAWN_HOLDABLE; + } +} + + +//====================================================================== + +/* +============== +Fill_Clip + push reserve ammo into available space in the clip +============== +*/ +void Fill_Clip( playerState_t *ps, int weapon ) { + int inclip, maxclip, ammomove; + int ammoweap = BG_FindAmmoForWeapon( weapon ); + + if ( weapon < WP_LUGER || weapon >= WP_NUM_WEAPONS ) { + return; + } + + if ( g_dmflags.integer & DF_NO_WEAPRELOAD ) { + return; + } + + inclip = ps->ammoclip[BG_FindClipForWeapon( weapon )]; + maxclip = ammoTable[weapon].maxclip; + + ammomove = maxclip - inclip; // max amount that can be moved into the clip + + // cap move amount if it's more than you've got in reserve + if ( ammomove > ps->ammo[ammoweap] ) { + ammomove = ps->ammo[ammoweap]; + } + + if ( ammomove ) { + ps->ammo[ammoweap] -= ammomove; + ps->ammoclip[BG_FindClipForWeapon( weapon )] += ammomove; + } +} + +/* +============== +Add_Ammo + Try to always add ammo here unless you have specific needs + (like the AI "infinite ammo" where they get below 900 and force back up to 999) + + fillClip will push the ammo straight through into the clip and leave the rest in reserve +============== +*/ +//----(SA) modified +void Add_Ammo( gentity_t *ent, int weapon, int count, qboolean fillClip ) { + int ammoweap = BG_FindAmmoForWeapon( weapon ); + int totalcount; + + ent->client->ps.ammo[ammoweap] += count; + + if ( ammoweap == WP_GRENADE_LAUNCHER ) { // make sure if he picks up a grenade that he get's the "launcher" too + COM_BitSet( ent->client->ps.weapons, WP_GRENADE_LAUNCHER ); + fillClip = qtrue; // grenades always filter into the "clip" + } else if ( ammoweap == WP_GRENADE_PINEAPPLE ) { + COM_BitSet( ent->client->ps.weapons, WP_GRENADE_PINEAPPLE ); + fillClip = qtrue; // grenades always filter into the "clip" + } else if ( ammoweap == WP_DYNAMITE || ammoweap == WP_DYNAMITE2 ) { + COM_BitSet( ent->client->ps.weapons, WP_DYNAMITE ); + fillClip = qtrue; + } + + if ( fillClip ) { + Fill_Clip( &ent->client->ps, weapon ); + } + + // cap to max ammo + if ( g_dmflags.integer & DF_NO_WEAPRELOAD ) { // no clips + totalcount = ent->client->ps.ammo[ammoweap]; + if ( totalcount > ammoTable[ammoweap].maxammo ) { + ent->client->ps.ammo[ammoweap] = ammoTable[ammoweap].maxammo; + } + + } else { // using clips + totalcount = ent->client->ps.ammo[ammoweap] + ent->client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; + if ( totalcount > ammoTable[ammoweap].maxammo ) { + ent->client->ps.ammo[ammoweap] = ammoTable[ammoweap].maxammo - ent->client->ps.ammoclip[BG_FindClipForWeapon( weapon )]; + } + + } + + + if ( count >= 999 ) { // 'really, give /all/' + ent->client->ps.ammo[ammoweap] = count; + } // JPW NERVE + +} +//----(SA) end + + +/* +============== +Pickup_Ammo +============== +*/ +int Pickup_Ammo( gentity_t *ent, gentity_t *other ) { + int quantity; + + if ( ent->count ) { + quantity = ent->count; + } else { + // quantity = ent->item->quantity; + + quantity = ent->item->gameskillnumber[( g_gameskill.integer ) - 1]; + + // FIXME just for now + if ( !quantity ) { + quantity = ent->item->quantity; + } + } + + Add_Ammo( other, ent->item->giTag, quantity, qfalse ); //----(SA) modified + + // single player has no respawns (SA) + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return RESPAWN_SP; + } + + return RESPAWN_AMMO; +} + +//====================================================================== + + +int Pickup_Weapon( gentity_t *ent, gentity_t *other ) { + int quantity; + qboolean alreadyHave = qfalse; + int i,weapon; // JPW NERVE + +// JPW NERVE -- magic ammo for any two-handed weapon + if ( ent->item->giTag == WP_AMMO ) { +// if LT isn't giving ammo to self or another LT or the enemy, give him some props + if ( other->client->ps.stats[STAT_PLAYER_CLASS] != PC_LT ) { + if ( ent->parent ) { + if ( other->client->sess.sessionTeam == ent->parent->client->sess.sessionTeam ) { + if ( ent->parent->client ) { + if ( !( ent->parent->client->PCSpecialPickedUpCount % LT_SPECIAL_PICKUP_MOD ) ) { + AddScore( ent->parent, WOLF_AMMO_UP ); + } + ent->parent->client->PCSpecialPickedUpCount++; + } + } + } + } + + // everybody likes grenades -- abuse weapon var as grenade type and i as max # grenades class can carry + switch ( other->client->ps.stats[STAT_PLAYER_CLASS] ) { + case PC_LT: // redundant but added for completeness/flexibility + case PC_MEDIC: + i = 1; + break; + case PC_SOLDIER: + i = 4; + break; + case PC_ENGINEER: + i = 8; + break; + default: + i = 1; + break; + } + if ( other->client->sess.sessionTeam == TEAM_RED ) { + weapon = WP_GRENADE_LAUNCHER; + } else { + weapon = WP_GRENADE_PINEAPPLE; + } + if ( other->client->ps.ammoclip[BG_FindClipForWeapon( weapon )] < i ) { + other->client->ps.ammoclip[BG_FindClipForWeapon( weapon )]++; + } + COM_BitSet( other->client->ps.weapons,weapon ); + + // TTimo - add 8 pistol bullets + if ( other->client->sess.sessionTeam == TEAM_RED ) { + weapon = WP_LUGER; + } else { + weapon = WP_COLT; + } +// G_Printf("filling magazine for weapon %d colt/luger (%d rounds)\n", weapon, ammoTable[weapon].maxclip); + other->client->ps.ammo[BG_FindAmmoForWeapon( weapon )] += ammoTable[weapon].maxclip; + if ( other->client->ps.ammo[BG_FindAmmoForWeapon( weapon )] > ammoTable[weapon].maxclip * 4 ) { + other->client->ps.ammo[BG_FindAmmoForWeapon( weapon )] = ammoTable[weapon].maxclip * 4; + } + + // and some two-handed ammo + for ( i = 0; i < MAX_WEAPS_IN_BANK_MP; i++ ) { + weapon = weapBanksMultiPlayer[3][i]; + if ( COM_BitCheck( other->client->ps.weapons, weapon ) ) { +// G_Printf("filling magazine for weapon %d (%d rounds)\n",weapon,ammoTable[weapon].maxclip); + if ( weapon == WP_FLAMETHROWER ) { // FT doesn't use magazines so refill tank + other->client->ps.ammoclip[BG_FindAmmoForWeapon( WP_FLAMETHROWER )] = ammoTable[weapon].maxclip; + } else { + other->client->ps.ammo[BG_FindAmmoForWeapon( weapon )] += ammoTable[weapon].maxclip; + if ( other->client->ps.ammo[BG_FindAmmoForWeapon( weapon )] > ammoTable[weapon].maxclip * 3 ) { + other->client->ps.ammo[BG_FindAmmoForWeapon( weapon )] = ammoTable[weapon].maxclip * 3; + } + } + return RESPAWN_SP; + } + } + return RESPAWN_SP; + } +// jpw + if ( ent->count < 0 ) { + quantity = 0; // None for you, sir! + } else { + if ( ent->count ) { + quantity = ent->count; + } else { +//----(SA) modified +// JPW NERVE did this so ammocounts work right on dropped weapons + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + quantity = ent->item->quantity; + } else { +// jpw + quantity = ( random() * ( ent->item->quantity - 1 ) ) + 1; // giving 1- + } + } + } + + // check if player already had the weapon + alreadyHave = COM_BitCheck( other->client->ps.weapons, ent->item->giTag ); + + // add the weapon + COM_BitSet( other->client->ps.weapons, ent->item->giTag ); + + // DHM - Fixup mauser/sniper issues + if ( ent->item->giTag == WP_MAUSER ) { + COM_BitSet( other->client->ps.weapons, WP_SNIPERRIFLE ); + } + if ( ent->item->giTag == WP_SNIPERRIFLE ) { + COM_BitSet( other->client->ps.weapons, WP_MAUSER ); + } + + //----(SA) added + // snooper == automatic garand mod + if ( ent->item->giTag == WP_SNOOPERSCOPE ) { + COM_BitSet( other->client->ps.weapons, WP_GARAND ); + } + // fg42scope == automatic fg42 mod + else if ( ent->item->giTag == WP_FG42SCOPE ) { + COM_BitSet( other->client->ps.weapons, WP_FG42 ); + } else if ( ent->item->giTag == WP_GARAND ) { + COM_BitSet( other->client->ps.weapons, WP_SNOOPERSCOPE ); + } + //----(SA) end + +// JPW NERVE prevents drop/pickup weapon "quick reload" exploit + if ( alreadyHave ) { + Add_Ammo( other, ent->item->giTag, quantity, !alreadyHave ); + } else { + other->client->ps.ammoclip[BG_FindClipForWeapon( ent->item->giTag )] = quantity; + } +// jpw + + // single player has no respawns (SA) + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return RESPAWN_SP; + } + + if ( g_gametype.integer == GT_TEAM ) { + return g_weaponTeamRespawn.integer; + } + + return g_weaponRespawn.integer; +} + + +//====================================================================== + +int Pickup_Health( gentity_t *ent, gentity_t *other ) { + int max; + int quantity = 0; + +// JPW NERVE +// if medic isn't giving ammo to self or another medic or the enemy, give him some props + if ( other->client->ps.stats[STAT_PLAYER_CLASS] != PC_MEDIC ) { + if ( ent->parent ) { + if ( other->client->sess.sessionTeam == ent->parent->client->sess.sessionTeam ) { + if ( ent->parent->client ) { + if ( !( ent->parent->client->PCSpecialPickedUpCount % MEDIC_SPECIAL_PICKUP_MOD ) ) { + AddScore( ent->parent, WOLF_HEALTH_UP ); + } + ent->parent->client->PCSpecialPickedUpCount++; + } + } + } + } + +// jpw + + + + // small and mega healths will go over the max + if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) { + max = other->client->ps.stats[STAT_MAX_HEALTH]; + } else { + max = other->client->ps.stats[STAT_MAX_HEALTH] * 2; + } + + if ( ent->count ) { + quantity = ent->count; + } else { + if ( ent->s.density ) { // multi-stage health + if ( ent->s.density == 2 ) { // first stage (it counts down) + quantity = ent->item->gameskillnumber[( g_gameskill.integer ) - 1]; + } else if ( ent->s.density == 1 ) { // second stage + quantity = ent->item->quantity; + } + } else { + quantity = ent->item->gameskillnumber[( g_gameskill.integer ) - 1]; + } + } + + other->health += quantity; + + if ( other->health > max ) { + other->health = max; + } + other->client->ps.stats[STAT_HEALTH] = other->health; + + if ( ent->s.density == 2 ) { // multi-stage health first stage + return RESPAWN_PARTIAL; + } else if ( ent->s.density == 1 ) { // last stage, leave the plate + return RESPAWN_PARTIAL_DONE; + } + + // single player has no respawns (SA) + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return RESPAWN_SP; + } + + if ( ent->item->giTag == 100 ) { // mega health respawns slow + return RESPAWN_MEGAHEALTH; + } + + return RESPAWN_HEALTH; +} + +//====================================================================== + +int Pickup_Armor( gentity_t *ent, gentity_t *other ) { + other->client->ps.stats[STAT_ARMOR] += ent->item->quantity; + if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) { + other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2; + } + + // single player has no respawns (SA) + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return RESPAWN_SP; + } + + return RESPAWN_ARMOR; +} + +//====================================================================== + +/* +=============== +RespawnItem +=============== +*/ +void RespawnItem( gentity_t *ent ) { + // randomly select from teamed entities + if ( ent->team ) { + gentity_t *master; + int count; + int choice; + + if ( !ent->teammaster ) { + G_Error( "RespawnItem: bad teammaster" ); + } + master = ent->teammaster; + + for ( count = 0, ent = master; ent; ent = ent->teamchain, count++ ) + ; + + choice = rand() % count; + + for ( count = 0, ent = master; count < choice; ent = ent->teamchain, count++ ) + ; + } + + ent->r.contents = CONTENTS_TRIGGER; + //ent->s.eFlags &= ~EF_NODRAW; + ent->flags &= ~FL_NODRAW; + ent->r.svFlags &= ~SVF_NOCLIENT; + trap_LinkEntity( ent ); + +/* + if ( ent->item->giType == IT_POWERUP && g_gametype.integer != GT_SINGLE_PLAYER) { + // play powerup spawn sound to all clients + gentity_t *te; + + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" ); + te->r.svFlags |= SVF_BROADCAST; + } +*/ + + // play the normal respawn sound only to nearby clients + G_AddEvent( ent, EV_ITEM_RESPAWN, 0 ); + + ent->nextthink = 0; +} + + +/* +============== +Touch_Item + if other->client->pers.autoActivate == PICKUP_ACTIVATE (0), he will pick up items only when using +activate + if other->client->pers.autoActivate == PICKUP_TOUCH (1), he will pickup items when touched + if other->client->pers.autoActivate == PICKUP_FORCE (2), he will pickup the next item when touched (and reset to PICKUP_ACTIVATE when done) +============== +*/ +void Touch_Item_Auto( gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( other->client->pers.autoActivate == PICKUP_ACTIVATE ) { + return; + } + + ent->active = qtrue; + Touch_Item( ent, other, trace ); + + if ( other->client->pers.autoActivate == PICKUP_FORCE ) { // autoactivate probably forced by the "Cmd_Activate_f()" function + other->client->pers.autoActivate = PICKUP_ACTIVATE; // so reset it. + } +} + + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item( gentity_t *ent, gentity_t *other, trace_t *trace ) { + int respawn; + int makenoise = EV_ITEM_PICKUP; + + // only activated items can be picked up + if ( !ent->active ) { + return; + } else { + // need to set active to false if player is maxed out + ent->active = qfalse; + } + + if ( !other->client ) { + return; + } + if ( other->health < 1 ) { + return; // dead people can't pickup + + } + // the same pickup rules are used for client side and server side + if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) { + return; + } + + G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname ); + + // call the item-specific pickup function + switch ( ent->item->giType ) { + case IT_WEAPON: + respawn = Pickup_Weapon( ent, other ); + break; + case IT_AMMO: + respawn = Pickup_Ammo( ent, other ); + break; + case IT_ARMOR: + respawn = Pickup_Armor( ent, other ); + break; + case IT_HEALTH: + respawn = Pickup_Health( ent, other ); + break; + case IT_POWERUP: + respawn = Pickup_Powerup( ent, other ); + break; + case IT_TEAM: + respawn = Pickup_Team( ent, other ); + break; + case IT_HOLDABLE: + respawn = Pickup_Holdable( ent, other ); + break; + case IT_KEY: + respawn = Pickup_Key( ent, other ); + break; + case IT_TREASURE: + respawn = Pickup_Treasure( ent, other ); + break; + case IT_CLIPBOARD: + respawn = Pickup_Clipboard( ent, other ); + // send the event to the client to request that the UI draw a popup + // (specified by the configstring in ent->s.density). + //G_AddEvent( other, EV_POPUP, ent->s.density); + //if(ent->key) + //G_AddEvent( other, EV_GIVEPAGE, ent->key ); + break; + default: + return; + } + + if ( !respawn ) { + return; + } + + // play sounds + if ( ent->noise_index ) { + // (SA) a sound was specified in the entity, so play that sound + // (this G_AddEvent) and send the pickup as "EV_ITEM_PICKUP_QUIET" + // so it doesn't make the default pickup sound when the pickup event is recieved + makenoise = EV_ITEM_PICKUP_QUIET; + G_AddEvent( other, EV_GENERAL_SOUND, ent->noise_index ); + } + + + // send the pickup event + if ( other->client->pers.predictItemPickup ) { + G_AddPredictableEvent( other, makenoise, ent->s.modelindex ); + } else { + G_AddEvent( other, makenoise, ent->s.modelindex ); + } + + // powerup pickups are global broadcasts + if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM ) { + // (SA) probably need to check for IT_KEY here too... (coop?) + gentity_t *te; + + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP ); + te->s.eventParm = ent->s.modelindex; + te->r.svFlags |= SVF_BROADCAST; + + // (SA) set if we want this to only go to the pickup client +// te->r.svFlags |= SVF_SINGLECLIENT; +// te->r.singleClient = other->s.number; + + } + + // fire item targets + G_UseTargets( ent, other ); + + // wait of -1 will not respawn + if ( ent->wait == -1 ) { + ent->flags |= FL_NODRAW; + //ent->r.svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + ent->unlinkAfterEvent = qtrue; + return; + } + + // wait of -2 will respawn but not be available for pickup anymore + // (partial use things that leave a spent modle (ex. plate for turkey) + if ( respawn == RESPAWN_PARTIAL_DONE ) { + ent->s.density = ( 1 << 9 ); // (10 bits of data transmission for density) + ent->active = qtrue; // re-activate + trap_LinkEntity( ent ); + return; + } + + if ( respawn == RESPAWN_PARTIAL ) { // multi-stage health + ent->s.density--; + if ( ent->s.density ) { // still not completely used up ( (SA) this will change to == 0 and stage 1 will be a destroyable item (plate/etc.) ) + ent->active = qtrue; // re-activate + trap_LinkEntity( ent ); + return; + } + } + + + // non zero wait overrides respawn time + if ( ent->wait ) { + respawn = ent->wait; + } + + // random can be used to vary the respawn time + if ( ent->random ) { + respawn += crandom() * ent->random; + if ( respawn < 1 ) { + respawn = 1; + } + } + + // dropped items will not respawn + if ( ent->flags & FL_DROPPED_ITEM ) { + ent->freeAfterEvent = qtrue; + } + + // picked up items still stay around, they just don't + // draw anything. This allows respawnable items + // to be placed on movers. + ent->r.svFlags |= SVF_NOCLIENT; + ent->flags |= FL_NODRAW; + //ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + + // ZOID + // A negative respawn times means to never respawn this item (but don't + // delete it). This is used by items that are respawned by third party + // events such as ctf flags + if ( respawn <= 0 ) { + ent->nextthink = 0; + ent->think = 0; + } else { + ent->nextthink = level.time + respawn * 1000; + ent->think = RespawnItem; + } + trap_LinkEntity( ent ); +} + + +//====================================================================== + +/* +================ +LaunchItem + +Spawns an item and tosses it forward +================ +*/ +gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, int ownerNum ) { + gentity_t *dropped; + trace_t tr; + vec3_t vec, temp; + int i; + + dropped = G_Spawn(); + + dropped->s.eType = ET_ITEM; + dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex + dropped->s.otherEntityNum2 = 1; // DHM - Nerve :: this is taking modelindex2's place for a dropped item + + dropped->classname = item->classname; + dropped->item = item; + VectorSet( dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); //----(SA) so items sit on the ground + VectorSet( dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS ); //----(SA) so items sit on the ground + dropped->r.contents = CONTENTS_TRIGGER | CONTENTS_ITEM; + + dropped->clipmask = CONTENTS_SOLID | CONTENTS_MISSILECLIP; // NERVE - SMF - fix for items falling through grates + + dropped->touch = Touch_Item_Auto; + + trap_Trace( &tr, origin, dropped->r.mins, dropped->r.maxs, origin, ownerNum, MASK_SOLID ); + if ( tr.startsolid ) { + VectorSubtract( g_entities[ownerNum].s.origin, origin, temp ); + VectorNormalize( temp ); + + for ( i = 16; i <= 48; i += 16 ) { + VectorScale( temp, i, vec ); + VectorAdd( origin, vec, origin ); + + trap_Trace( &tr, origin, dropped->r.mins, dropped->r.maxs, origin, ownerNum, MASK_SOLID ); + if ( !tr.startsolid ) { + break; + } + } + } + + G_SetOrigin( dropped, origin ); + dropped->s.pos.trType = TR_GRAVITY; + dropped->s.pos.trTime = level.time; + VectorCopy( velocity, dropped->s.pos.trDelta ); + + dropped->s.eFlags |= EF_BOUNCE_HALF; + + if ( item->giType == IT_TEAM ) { // Special case for CTF flags + dropped->think = Team_DroppedFlagThink; + dropped->nextthink = level.time + 30000; + } else { // auto-remove after 30 seconds + dropped->think = G_FreeEntity; + dropped->nextthink = level.time + 30000; + } + + dropped->flags = FL_DROPPED_ITEM; + + trap_LinkEntity( dropped ); + + return dropped; +} + +/* +================ +Drop_Item + +Spawns an item and tosses it forward +================ +*/ +gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean novelocity ) { + vec3_t velocity; + vec3_t angles; + + VectorCopy( ent->s.apos.trBase, angles ); + angles[YAW] += angle; + angles[PITCH] = 0; // always forward + + if ( novelocity ) { + VectorClear( velocity ); + } else + { + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 150, velocity ); + velocity[2] += 200 + crandom() * 50; + } + + return LaunchItem( item, ent->s.pos.trBase, velocity, ent->s.number ); +} + + +/* +================ +Use_Item + +Respawn the item +================ +*/ +void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + RespawnItem( ent ); +} + +//====================================================================== + +/* +================ +FinishSpawningItem + +Traces down to find where an item should rest, instead of letting them +free fall from their spawn points +================ +*/ +void FinishSpawningItem( gentity_t *ent ) { + trace_t tr; + vec3_t dest; + vec3_t maxs; + + if ( ent->spawnflags & 1 ) { // suspended + VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS ); + VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); + VectorCopy( ent->r.maxs, maxs ); + } else + { + // Rafael + // had to modify this so that items would spawn in shelves + VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); + VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); + VectorCopy( ent->r.maxs, maxs ); + maxs[2] /= 2; + } + + ent->r.contents = CONTENTS_TRIGGER | CONTENTS_ITEM; + ent->touch = Touch_Item_Auto; + ent->s.eType = ET_ITEM; + ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex + + ent->s.otherEntityNum2 = 0; // DHM - Nerve :: takes modelindex2's place in signaling a dropped item +//----(SA) we don't use this (yet, anyway) so I'm taking it so you can specify a model for treasure items and clipboards +// ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item + if ( ent->model ) { + ent->s.modelindex2 = G_ModelIndex( ent->model ); + } + + + // if clipboard, add the menu name string to the client's configstrings + if ( ent->item->giType == IT_CLIPBOARD ) { + if ( !ent->message ) { + ent->s.density = G_FindConfigstringIndex( "clip_test", CS_CLIPBOARDS, MAX_CLIPBOARD_CONFIGSTRINGS, qtrue ); + } else { + ent->s.density = G_FindConfigstringIndex( ent->message, CS_CLIPBOARDS, MAX_CLIPBOARD_CONFIGSTRINGS, qtrue ); + } + + ent->touch = Touch_Item; // no auto-pickup, only activate + } else if ( ent->item->giType == IT_HOLDABLE ) { + if ( ent->item->giTag >= HI_BOOK1 && ent->item->giTag <= HI_BOOK3 ) { + G_FindConfigstringIndex( va( "hbook%d", ent->item->giTag - HI_BOOK1 ), CS_CLIPBOARDS, MAX_CLIPBOARD_CONFIGSTRINGS, qtrue ); + } + ent->touch = Touch_Item; // no auto-pickup, only activate + } + + +//----(SA) added + if ( ent->item->giType == IT_TREASURE ) { + ent->touch = Touch_Item; // no auto-pickup, only activate + } +//----(SA) end + + // using an item causes it to respawn + ent->use = Use_Item; + +//----(SA) moved this up so it happens for suspended items too (and made it a function) + G_SetAngle( ent, ent->s.angles ); + + if ( ent->spawnflags & 1 ) { // suspended + G_SetOrigin( ent, ent->s.origin ); + } else { + + VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); + trap_Trace( &tr, ent->s.origin, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID ); + + if ( tr.startsolid ) { + vec3_t temp; + + VectorCopy( ent->s.origin, temp ); + temp[2] -= ITEM_RADIUS; + + VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); + trap_Trace( &tr, temp, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID ); + } + +#if 0 + // drop to floor + VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 ); + trap_Trace( &tr, ent->s.origin, ent->r.mins, maxs, dest, ent->s.number, MASK_SOLID ); +#endif + if ( tr.startsolid ) { + G_Printf( "FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos( ent->s.origin ) ); + G_FreeEntity( ent ); + return; + } + + // allow to ride movers + ent->s.groundEntityNum = tr.entityNum; + + G_SetOrigin( ent, tr.endpos ); + } + + if ( ent->spawnflags & 2 ) { // spin + ent->s.eFlags |= EF_SPINNING; + } + + + // team slaves and targeted items aren't present at start + if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) { + ent->flags |= FL_NODRAW; + //ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + return; + } + + // health/ammo can potentially be multi-stage (multiple use) + if ( ent->item->giType == IT_HEALTH || ent->item->giType == IT_AMMO || ent->item->giType == IT_POWERUP ) { + int i; + + // having alternate models defined in bg_misc.c for a health or ammo item specify it as "multi-stage" + // TTimo left-hand operand of comma expression has no effect + // initial line: for(i=0;i<4,ent->item->world_model[i];i++) {} + for ( i = 0; i < 4 && ent->item->world_model[i] ; i++ ) {} + + ent->s.density = i - 1; // store number of stages in 'density' for client (most will have '1') + } + + // powerups don't spawn in for a while + if ( ent->item->giType == IT_POWERUP && g_gametype.integer != GT_SINGLE_PLAYER ) { + float respawn; + + respawn = 45 + crandom() * 15; + ent->flags |= FL_NODRAW; + //ent->s.eFlags |= EF_NODRAW; + ent->r.contents = 0; + ent->nextthink = level.time + respawn * 1000; + ent->think = RespawnItem; + return; + } + + trap_LinkEntity( ent ); +} + + +qboolean itemRegistered[MAX_ITEMS]; + +/* +================== +G_CheckTeamItems +================== +*/ +void G_CheckTeamItems( void ) { + if ( g_gametype.integer == GT_CTF ) { + gitem_t *item; + + // make sure we actually have two flags... + item = BG_FindItem( "Red Flag" ); + if ( !item || !itemRegistered[ item - bg_itemlist ] ) { + G_Error( "No team_CTF_redflag in map" ); + } + item = BG_FindItem( "Blue Flag" ); + if ( !item || !itemRegistered[ item - bg_itemlist ] ) { + G_Error( "No team_CTF_blueflag in map" ); + } + } +} + +/* +============== +ClearRegisteredItems +============== +*/ +void ClearRegisteredItems( void ) { + memset( itemRegistered, 0, sizeof( itemRegistered ) ); + + // players always start with the base weapon + // (SA) Nope, not any more... + +//----(SA) this will be determined by the level or starting position, or the savegame +// but for now, re-register the MP40 automatically +// RegisterItem( BG_FindItemForWeapon( WP_MP40 ) ); + RegisterItem( BG_FindItem( "Med Health" ) ); // NERVE - SMF - this is so med packs properly display +} + +/* +=============== +RegisterItem + +The item will be added to the precache list +=============== +*/ +void RegisterItem( gitem_t *item ) { + if ( !item ) { + G_Error( "RegisterItem: NULL" ); + } + itemRegistered[ item - bg_itemlist ] = qtrue; +} + + +/* +=============== +SaveRegisteredItems + +Write the needed items to a config string +so the client will know which ones to precache +=============== +*/ +void SaveRegisteredItems( void ) { + char string[MAX_ITEMS + 1]; + int i; + int count; + + count = 0; + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( itemRegistered[i] ) { + count++; + string[i] = '1'; + } else { + string[i] = '0'; + } + + // DHM - Nerve :: Make sure and register all weapons we use in WolfMP + if ( g_gametype.integer >= GT_WOLF && string[i] == '0' && bg_itemlist[i].giType == IT_WEAPON && BG_WeaponInWolfMP( bg_itemlist[i].giTag ) ) { + count++; + string[i] = '1'; + } + } + string[ bg_numItems ] = 0; + + if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) { + G_Printf( "%i items registered\n", count ); + } + trap_SetConfigstring( CS_ITEMS, string ); +} + + +/* +============ +G_SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void G_SpawnItem( gentity_t *ent, gitem_t *item ) { + char *noise; + int page; + + G_SpawnFloat( "random", "0", &ent->random ); + G_SpawnFloat( "wait", "0", &ent->wait ); + + RegisterItem( item ); + ent->item = item; + // some movers spawn on the second frame, so delay item + // spawns until the third frame so they can ride trains + ent->nextthink = level.time + FRAMETIME * 2; + ent->think = FinishSpawningItem; + + if ( G_SpawnString( "noise", 0, &noise ) ) { + ent->noise_index = G_SoundIndex( noise ); + } + + ent->physicsBounce = 0.50; // items are bouncy + + if ( ent->model ) { + ent->s.modelindex2 = G_ModelIndex( ent->model ); + } + + if ( item->giType == IT_CLIPBOARD ) { + if ( G_SpawnInt( "notebookpage", "1", &page ) ) { + ent->key = page; + } + } + + if ( item->giType == IT_POWERUP ) { + G_SoundIndex( "sound/items/poweruprespawn.wav" ); + } +} + + +/* +================ +G_BounceItem + +================ +*/ +void G_BounceItem( gentity_t *ent, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2 * dot, trace->plane.normal, ent->s.pos.trDelta ); + + // cut the velocity to keep from bouncing forever + VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta ); + + // check for stop + if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) { + trace->endpos[2] += 1.0; // make sure it is off ground + SnapVector( trace->endpos ); + G_SetOrigin( ent, trace->endpos ); + ent->s.groundEntityNum = trace->entityNum; + return; + } + + VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin ); + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; +} + +/* +================= +G_RunItemProp +================= +*/ + +void G_RunItemProp( gentity_t *ent, vec3_t origin ) { + gentity_t *traceEnt; + trace_t trace; + gentity_t *owner; + vec3_t start; + vec3_t end; + + owner = &g_entities[ent->r.ownerNum]; + + VectorCopy( ent->r.currentOrigin, start ); + start[2] += 1; + + VectorCopy( origin, end ); + end[2] += 1; + + trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, end, + ent->r.ownerNum, MASK_SHOT ); + + traceEnt = &g_entities[ trace.entityNum ]; + + if ( traceEnt && traceEnt->takedamage && traceEnt != ent ) { + ent->enemy = traceEnt; + } + + if ( owner->client && trace.startsolid && traceEnt != owner && traceEnt != ent /* && !traceEnt->active*/ ) { + + ent->takedamage = qfalse; + ent->die( ent, ent, NULL, 10, 0 ); + Prop_Break_Sound( ent ); + + return; + } else if ( trace.surfaceFlags & SURF_NOIMPACT ) { + ent->takedamage = qfalse; + + Props_Chair_Skyboxtouch( ent ); + + return; + } +} + +/* +================ +G_RunItem + +================ +*/ +void G_RunItem( gentity_t *ent ) { + vec3_t origin; + trace_t tr; + int contents; + int mask; + + // if groundentity has been set to -1, it may have been pushed off an edge + if ( ent->s.groundEntityNum == -1 ) { + if ( ent->s.pos.trType != TR_GRAVITY ) { + ent->s.pos.trType = TR_GRAVITY; + ent->s.pos.trTime = level.time; + } + } + + if ( ent->s.pos.trType == TR_STATIONARY || ent->s.pos.trType == TR_GRAVITY_PAUSED ) { //----(SA) + // check think function + G_RunThink( ent ); + return; + } + + // get current position + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + + // trace a line from the previous position to the current position + if ( ent->clipmask ) { + mask = ent->clipmask; + } else { + mask = MASK_SOLID; + } + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, + ent->r.ownerNum, mask ); + + if ( ent->isProp && ent->takedamage ) { + G_RunItemProp( ent, origin ); + } + + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + if ( tr.startsolid ) { + tr.fraction = 0; + } + + trap_LinkEntity( ent ); // FIXME: avoid this for stationary? + + // check think function + G_RunThink( ent ); + + if ( tr.fraction == 1 ) { + return; + } + + // if it is in a nodrop volume, remove it + contents = trap_PointContents( ent->r.currentOrigin, -1 ); + if ( contents & CONTENTS_NODROP ) { + if ( ent->item && ent->item->giType == IT_TEAM ) { + Team_FreeEntity( ent ); + } else { + G_FreeEntity( ent ); + } + return; + } + + G_BounceItem( ent, &tr ); +} + diff --git a/src/game/g_local.h b/src/game/g_local.h new file mode 100644 index 0000000..bc7b260 --- /dev/null +++ b/src/game/g_local.h @@ -0,0 +1,1452 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +// g_local.h -- local definitions for game module + +#include "q_shared.h" +#include "bg_public.h" +#include "g_public.h" + +//================================================================== + +// the "gameversion" client command will print this plus compile date +//----(SA) Wolfenstein +#define GAMEVERSION "main" +// done. + +#define BODY_QUEUE_SIZE 8 + +#define INFINITE 1000000 + +#define FRAMETIME 100 // msec +#define EVENT_VALID_MSEC 300 +#define CARNAGE_REWARD_TIME 3000 +#define REWARD_SPRITE_TIME 2000 + +#define INTERMISSION_DELAY_TIME 1000 + +#define MG42_MULTIPLAYER_HEALTH 350 // JPW NERVE + +// gentity->flags +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_DROPPED_ITEM 0x00001000 +#define FL_NO_BOTS 0x00002000 // spawn point not for bot use +#define FL_NO_HUMANS 0x00004000 // spawn point just for bots +#define FL_AI_GRENADE_KICK 0x00008000 // an AI has already decided to kick this grenade +// Rafael +#define FL_NOFATIGUE 0x00010000 // cheat flag no fatigue + +#define FL_TOGGLE 0x00020000 //----(SA) ent is toggling (doors use this for ex.) +#define FL_KICKACTIVATE 0x00040000 //----(SA) ent has been activated by a kick (doors use this too for ex.) +#define FL_SOFTACTIVATE 0x00000040 //----(SA) ent has been activated while 'walking' (doors use this too for ex.) +#define FL_DEFENSE_GUARD 0x00080000 // warzombie defense pose + +#define FL_PARACHUTE 0x00100000 +#define FL_WARZOMBIECHARGE 0x00200000 +#define FL_NO_MONSTERSLICK 0x00400000 +#define FL_NO_HEADCHECK 0x00800000 + +#define FL_NODRAW 0x01000000 + +// movers are things like doors, plats, buttons, etc +typedef enum { + MOVER_POS1, + MOVER_POS2, + MOVER_POS3, + MOVER_1TO2, + MOVER_2TO1, + // JOSEPH 1-26-00 + MOVER_2TO3, + MOVER_3TO2, + // END JOSEPH + + // Rafael + MOVER_POS1ROTATE, + MOVER_POS2ROTATE, + MOVER_1TO2ROTATE, + MOVER_2TO1ROTATE +} moverState_t; + + +// door AI sound ranges +#define HEAR_RANGE_DOOR_LOCKED 128 // really close since this is a cruel check +#define HEAR_RANGE_DOOR_KICKLOCKED 512 +#define HEAR_RANGE_DOOR_OPEN 256 +#define HEAR_RANGE_DOOR_KICKOPEN 768 + +// DHM - Nerve :: Worldspawn spawnflags to indicate if a gametype is not supported +#define NO_GT_WOLF 1 +#define NO_STOPWATCH 2 +#define NO_CHECKPOINT 4 + +//============================================================================ + +typedef struct gentity_s gentity_t; +typedef struct gclient_s gclient_t; + +//==================================================================== +// +// Scripting, these structure are not saved into savegames (parsed each start) +typedef struct +{ + char *actionString; + qboolean ( *actionFunc )( gentity_t *ent, char *params ); +} g_script_stack_action_t; +// +typedef struct +{ + // + // set during script parsing + g_script_stack_action_t *action; // points to an action to perform + char *params; +} g_script_stack_item_t; +// +#define G_MAX_SCRIPT_STACK_ITEMS 64 +// +typedef struct +{ + g_script_stack_item_t items[G_MAX_SCRIPT_STACK_ITEMS]; + int numItems; +} g_script_stack_t; +// +typedef struct +{ + int eventNum; // index in scriptEvents[] + char *params; // trigger targetname, etc + g_script_stack_t stack; +} g_script_event_t; +// +typedef struct +{ + char *eventStr; + qboolean ( *eventMatch )( g_script_event_t *event, char *eventParm ); +} g_script_event_define_t; +// +// Script Flags +#define SCFL_GOING_TO_MARKER 0x1 +#define SCFL_ANIMATING 0x2 +// +// Scripting Status (NOTE: this MUST NOT contain any pointer vars) +typedef struct +{ + int scriptStackHead, scriptStackChangeTime; + int scriptEventIndex; // current event containing stack of actions to perform + // scripting system variables + int scriptId; // incremented each time the script changes + int scriptFlags; + char *animatingParams; +} g_script_status_t; +// +#define G_MAX_SCRIPT_ACCUM_BUFFERS 8 +// +void G_Script_ScriptEvent( gentity_t *ent, char *eventStr, char *params ); +//==================================================================== + + +#define CFOFS( x ) ( (int)&( ( (gclient_t *)0 )->x ) ) + +struct gentity_s { + entityState_t s; // communicated by server to clients + entityShared_t r; // shared by both the server system and game + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + //================================ + + struct gclient_s *client; // NULL if not a client + + qboolean inuse; + + char *classname; // set in QuakeEd + int spawnflags; // set in QuakeEd + + qboolean neverFree; // if true, FreeEntity will only unlink + // bodyque uses this + + int flags; // FL_* variables + + char *model; + char *model2; + int freetime; // level.time when the object was freed + + int eventTime; // events will be cleared EVENT_VALID_MSEC after set + qboolean freeAfterEvent; + qboolean unlinkAfterEvent; + + qboolean physicsObject; // if true, it can be pushed by movers and fall off edges + // all game items are physicsObjects, + float physicsBounce; // 1.0 = continuous bounce, 0.0 = no bounce + int clipmask; // brushes with this content value will be collided against + // when moving. items and corpses do not collide against + // players, for instance + + // movers + moverState_t moverState; + int soundPos1; + int sound1to2; + int sound2to1; + int soundPos2; + int soundLoop; + // JOSEPH 1-26-00 + int sound2to3; + int sound3to2; + int soundPos3; + // END JOSEPH + + int soundKicked; + int soundKickedEnd; + + int soundSoftopen; + int soundSoftendo; + int soundSoftclose; + int soundSoftendc; + + gentity_t *parent; + gentity_t *nextTrain; + gentity_t *prevTrain; + // JOSEPH 1-26-00 + vec3_t pos1, pos2, pos3; + // END JOSEPH + + char *message; + + int timestamp; // body queue sinking, etc + + float angle; // set in editor, -1 = up, -2 = down + char *target; + char *targetname; + char *team; + char *targetShaderName; + char *targetShaderNewName; + gentity_t *target_ent; + + float speed; + float closespeed; // for movers that close at a different speed than they open + vec3_t movedir; + + int gDuration; + int gDurationBack; + vec3_t gDelta; + vec3_t gDeltaBack; + + int nextthink; + void ( *think )( gentity_t *self ); + void ( *reached )( gentity_t *self ); // movers call this when hitting endpoint + void ( *blocked )( gentity_t *self, gentity_t *other ); + void ( *touch )( gentity_t *self, gentity_t *other, trace_t *trace ); + void ( *use )( gentity_t *self, gentity_t *other, gentity_t *activator ); + void ( *pain )( gentity_t *self, gentity_t *attacker, int damage, vec3_t point ); + void ( *die )( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); + + int pain_debounce_time; + int fly_sound_debounce_time; // wind tunnel + int last_move_time; + + int health; + + qboolean takedamage; + + int damage; + int splashDamage; // quad will increase this without increasing radius + int splashRadius; + int methodOfDeath; + int splashMethodOfDeath; + + int count; + + gentity_t *chain; + gentity_t *enemy; + gentity_t *activator; + gentity_t *teamchain; // next entity in team + gentity_t *teammaster; // master of the team + + int watertype; + int waterlevel; + + int noise_index; + + // timing variables + float wait; + float random; + + // Rafael - sniper variable + // sniper uses delay, random, radius + int radius; + float delay; + + // JOSEPH 10-11-99 + int TargetFlag; + float duration; + vec3_t rotate; + vec3_t TargetAngles; + // END JOSEPH + + gitem_t *item; // for bonus items + + // Ridah, AI fields + char *aiAttributes; + char *aiName; + int aiTeam; + void ( *AIScript_AlertEntity )( gentity_t *ent ); + qboolean aiInactive; + int aiCharacter; // the index of the type of character we are (from aicast_soldier.c) + // done. + + char *aiSkin; + char *aihSkin; + + vec3_t dl_color; + char *dl_stylestring; + char *dl_shader; + int dl_atten; + + + int key; // used by: target_speaker->nopvs, + + qboolean active; + qboolean botDelayBegin; + + // Rafael - mg42 + float harc; + float varc; + + int props_frame_state; + + // Ridah + int missionLevel; // mission we are currently trying to complete + // gets reset each new level + // done. + + // Rafael + qboolean is_dead; + // done + + int start_size; + int end_size; + + // Rafael props + + qboolean isProp; + + int mg42BaseEnt; + + gentity_t *melee; + + char *spawnitem; + + qboolean nopickup; + + int flameQuota, flameQuotaTime, flameBurnEnt; + + int count2; + + int grenadeExplodeTime; // we've caught a grenade, which was due to explode at this time + int grenadeFired; // the grenade entity we last fired + + int mg42ClampTime; // time to wait before an AI decides to ditch the mg42 + + char *track; + + // entity scripting system + char *scriptName; + + int numScriptEvents; + g_script_event_t *scriptEvents; // contains a list of actions to perform for each event type + g_script_status_t scriptStatus; // current status of scripting + // the accumulation buffer + int scriptAccumBuffer[G_MAX_SCRIPT_ACCUM_BUFFERS]; + + qboolean AASblocking; + float accuracy; + + char *tagName; // name of the tag we are attached to + gentity_t *tagParent; + + float headshotDamageScale; + + int lastHintCheckTime; // DHM - Nerve + // ------------------------------------------------------------------------------------------- + // if working on a post release patch, new variables should ONLY be inserted after this point + // DHM - Nerve :: the above warning does not really apply to MP, but I'll follow it for good measure + + int voiceChatSquelch; // DHM - Nerve + int voiceChatPreviousTime; // DHM - Nerve + int lastBurnedFrameNumber; // JPW - Nerve : to fix FT instant-kill exploit +}; + +// Ridah +#include "ai_cast_global.h" +// done. + +typedef enum { + CON_DISCONNECTED, + CON_CONNECTING, + CON_CONNECTED +} clientConnected_t; + +typedef enum { + SPECTATOR_NOT, + SPECTATOR_FREE, + SPECTATOR_FOLLOW, + SPECTATOR_SCOREBOARD +} spectatorState_t; + +typedef enum { + TEAM_BEGIN, // Beginning a team game, spawn at base + TEAM_ACTIVE // Now actively playing +} playerTeamStateState_t; + +typedef struct { + playerTeamStateState_t state; + + int location; + + int captures; + int basedefense; + int carrierdefense; + int flagrecovery; + int fragcarrier; + int assists; + + float lasthurtcarrier; + float lastreturnedflag; + float flagsince; + float lastfraggedcarrier; +} playerTeamState_t; + +// the auto following clients don't follow a specific client +// number, but instead follow the first two active players +#define FOLLOW_ACTIVE1 -1 +#define FOLLOW_ACTIVE2 -2 + +// client data that stays across multiple levels or tournament restarts +// this is achieved by writing all the data to cvar strings at game shutdown +// time and reading them back at connection time. Anything added here +// MUST be dealt with in G_InitSessionData() / G_ReadSessionData() / G_WriteSessionData() +typedef struct { + team_t sessionTeam; + int spectatorTime; // for determining next-in-line to play + spectatorState_t spectatorState; + int spectatorClient; // for chasecam and follow mode + int wins, losses; // tournament stats + int playerType; // DHM - Nerve :: for GT_WOLF + int playerWeapon; // DHM - Nerve :: for GT_WOLF + int playerItem; // DHM - Nerve :: for GT_WOLF + int playerSkin; // DHM - Nerve :: for GT_WOLF + int spawnObjectiveIndex; // JPW NERVE index of objective to spawn nearest to (returned from UI) + int latchPlayerType; // DHM - Nerve :: for GT_WOLF not archived + int latchPlayerWeapon; // DHM - Nerve :: for GT_WOLF not archived + int latchPlayerItem; // DHM - Nerve :: for GT_WOLF not archived + int latchPlayerSkin; // DHM - Nerve :: for GT_WOLF not archived +} clientSession_t; + +// +#define MAX_NETNAME 36 +#define MAX_VOTE_COUNT 3 + +#define PICKUP_ACTIVATE 0 // pickup items only when using "+activate" +#define PICKUP_TOUCH 1 // pickup items when touched +#define PICKUP_FORCE 2 // pickup the next item when touched (and reset to PICKUP_ACTIVATE when done) + +// client data that stays across multiple respawns, but is cleared +// on each level change or team change at ClientBegin() +typedef struct { + clientConnected_t connected; + usercmd_t cmd; // we would lose angles if not persistant + usercmd_t oldcmd; // previous command processed by pmove() + qboolean localClient; // true if "ip" info key is "localhost" + qboolean initialSpawn; // the first spawn should be at a cool location + qboolean predictItemPickup; // based on cg_predictItems userinfo + qboolean pmoveFixed; // + char netname[MAX_NETNAME]; + + int autoActivate; // based on cg_autoactivate userinfo (uses the PICKUP_ values above) + int emptySwitch; // based on cg_emptyswitch userinfo (means "switch my weapon for me when ammo reaches '0' rather than -1) + + int maxHealth; // for handicapping + int enterTime; // level.time the client entered the game + int connectTime; // DHM - Nerve :: level.time the client first connected to the server + playerTeamState_t teamState; // status in teamplay games + int voteCount; // to prevent people from constantly calling votes + int teamVoteCount; // to prevent people from constantly calling votes + + int complaints; // DHM - Nerve :: number of complaints lodged against this client + int complaintClient; // DHM - Nerve :: able to lodge complaint against this client + int complaintEndTime; // DHM - Nerve :: until this time has expired + + int lastReinforceTime; // DHM - Nerve :: last reinforcement + + qboolean teamInfo; // send team overlay updates? + + qboolean bAutoReloadAux; // TTimo - auxiliary storage for pmoveExt_t::bAutoReload, to achieve persistance +} clientPersistant_t; + +typedef struct { + vec3_t mins; + vec3_t maxs; + + vec3_t origin; + + int time; + int servertime; +} clientMarker_t; + +#define MAX_CLIENT_MARKERS 10 + +#define LT_SPECIAL_PICKUP_MOD 3 // JPW NERVE # of times (minus one for modulo) LT must drop ammo before scoring a point +#define MEDIC_SPECIAL_PICKUP_MOD 4 // JPW NERVE same thing for medic + +// this structure is cleared on each ClientSpawn(), +// except for 'client->pers' and 'client->sess' +struct gclient_s { + // ps MUST be the first element, because the server expects it + playerState_t ps; // communicated by server to clients + + // the rest of the structure is private to game + clientPersistant_t pers; + clientSession_t sess; + + qboolean readyToExit; // wishes to leave the intermission + + qboolean noclip; + + int lastCmdTime; // level.time of last usercmd_t, for EF_CONNECTION + // we can't just use pers.lastCommand.time, because + // of the g_sycronousclients case + int buttons; + int oldbuttons; + int latched_buttons; + + int wbuttons; + int oldwbuttons; + int latched_wbuttons; + vec3_t oldOrigin; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + qboolean damage_fromWorld; // if true, don't use the damage_from vector + + int accurateCount; // for "impressive" reward sound + + int accuracy_shots; // total number of shots + int accuracy_hits; // total number of hits + + // + int lastkilled_client; // last client that this client killed + int lasthurt_client; // last client that damaged this client + int lasthurt_mod; // type of damage the client did + + // timers + int respawnTime; // can respawn when time > this, force after g_forcerespwan + int inactivityTime; // kick players when time > this + qboolean inactivityWarning; // qtrue if the five seoond warning has been given + int rewardTime; // clear the EF_AWARD_IMPRESSIVE, etc when time > this + + int airOutTime; + + int lastKillTime; // for multiple kill rewards + + qboolean fireHeld; // used for hook + gentity_t *hook; // grapple hook if out + + int switchTeamTime; // time the player switched teams + + // timeResidual is used to handle events that happen every second + // like health / armor countdowns and regeneration + int timeResidual; + + float currentAimSpreadScale; + + int medicHealAmt; + + // RF, may be shared by multiple clients/characters + animModelInfo_t *modelInfo; + + // ------------------------------------------------------------------------------------------- + // if working on a post release patch, new variables should ONLY be inserted after this point + + gentity_t *persistantPowerup; + int portalID; + int ammoTimes[WP_NUM_WEAPONS]; + int invulnerabilityTime; + + gentity_t *cameraPortal; // grapple hook if out + vec3_t cameraOrigin; + + int dropWeaponTime; // JPW NERVE last time a weapon was dropped + int limboDropWeapon; // JPW NERVE weapon to drop in limbo + int deployQueueNumber; // JPW NERVE player order in reinforcement FIFO queue + int sniperRifleFiredTime; // JPW NERVE last time a sniper rifle was fired (for muzzle flip effects) + float sniperRifleMuzzleYaw; // JPW NERVE for time-dependent muzzle flip in multiplayer + int lastBurnTime; // JPW NERVE last time index for flamethrower burn + int PCSpecialPickedUpCount; // JPW NERVE used to count # of times somebody's picked up this LTs ammo (or medic health) (for scoring) + int saved_persistant[MAX_PERSISTANT]; // DHM - Nerve :: Save ps->persistant here during Limbo + + // g_antilag.c + int topMarker; + clientMarker_t clientMarkers[MAX_CLIENT_MARKERS]; + clientMarker_t backupMarker; + + gentity_t *tempHead; // Gordon: storing a temporary head for bullet head shot detection + + pmoveExt_t pmext; +}; + + +// +// this structure is cleared as each map is entered +// +#define MAX_SPAWN_VARS 64 +#define MAX_SPAWN_VARS_CHARS 2048 + +typedef struct { + struct gclient_s *clients; // [maxclients] + + struct gentity_s *gentities; + int gentitySize; + int num_entities; // current number, <= MAX_GENTITIES + + int warmupTime; // restart match at this time + + fileHandle_t logFile; + + // store latched cvars here that we want to get at often + int maxclients; + + int framenum; + int time; // in msec + int previousTime; // so movers can back up when blocked + int frameTime; // Gordon: time the frame started, for antilag stuff + + int startTime; // level.time the map was started + + int teamScores[TEAM_NUM_TEAMS]; + int lastTeamLocationTime; // last time of client team location update + + qboolean newSession; // don't use any old session data, because + // we changed gametype + + qboolean restarted; // waiting for a map_restart to fire + + int numConnectedClients; + int numNonSpectatorClients; // includes connecting clients + int numPlayingClients; // connected, non-spectators + int sortedClients[MAX_CLIENTS]; // sorted by score + int follow1, follow2; // clientNums for auto-follow spectators + + int snd_fry; // sound index for standing in lava + + int warmupModificationCount; // for detecting if g_warmup is changed + + // voting state + char voteString[MAX_STRING_CHARS]; + char voteDisplayString[MAX_STRING_CHARS]; + int voteTime; // level.time vote was called + int voteExecuteTime; // time the vote is executed + int prevVoteExecuteTime; // JPW NERVE last vote execute time + int voteYes; + int voteNo; + int numVotingClients; // set by CalculateRanks + + // team voting state + char teamVoteString[2][MAX_STRING_CHARS]; + int teamVoteTime[2]; // level.time vote was called + int teamVoteYes[2]; + int teamVoteNo[2]; + int numteamVotingClients[2]; // set by CalculateRanks + + // spawn variables + qboolean spawning; // the G_Spawn*() functions are valid + int numSpawnVars; + char *spawnVars[MAX_SPAWN_VARS][2]; // key / value pairs + int numSpawnVarChars; + char spawnVarChars[MAX_SPAWN_VARS_CHARS]; + + // intermission state + int intermissionQueued; // intermission was qualified, but + // wait INTERMISSION_DELAY_TIME before + // actually going there so the last + // frag can be watched. Disable future + // kills during this delay + int intermissiontime; // time the intermission was started + char *changemap; + qboolean readyToExit; // at least one client wants to exit + int exitTime; + vec3_t intermission_origin; // also used for spectator spawns + vec3_t intermission_angle; + + qboolean locationLinked; // target_locations get linked + gentity_t *locationHead; // head of the location list + int bodyQueIndex; // dead bodies + gentity_t *bodyQue[BODY_QUEUE_SIZE]; + + int portalSequence; + // Ridah + char *scriptAI; + int reloadPauseTime; // don't think AI/client's until this time has elapsed + int reloadDelayTime; // don't start loading the savegame until this has expired + + int lastGrenadeKick; + + int loperZapSound; + int stimSoldierFlySound; + int bulletRicochetSound; + // done. + + int snipersound; + + //----(SA) added + int knifeSound[4]; + //----(SA) end + +// JPW NERVE + int capturetimes[4]; // red, blue, none, spectator for WOLF_MP_CPH + int redReinforceTime, blueReinforceTime; // last time reinforcements arrived in ms + int redNumWaiting, blueNumWaiting; // number of reinforcements in queue + vec3_t spawntargets[MAX_MULTI_SPAWNTARGETS]; // coordinates of spawn targets + int numspawntargets; // # spawntargets in this map +// jpw + + // RF, entity scripting + char *scriptEntity; + + // player/AI model scripting (server repository) + animScriptData_t animScriptData; + + // NERVE - SMF - debugging/profiling info + int totalHeadshots; + int missedHeadshots; + qboolean lastRestartTime; + // -NERVE - SMF + + int numFinalDead[2]; // DHM - Nerve :: unable to respawn and in limbo (per team) + int numOidTriggers; // DHM - Nerve + + qboolean latchGametype; // DHM - Nerve +} level_locals_t; + +extern qboolean reloading; // loading up a savegame +// JPW NERVE +extern char testid1[]; +extern char testid2[]; +extern char testid3[]; +// jpw + +// +// g_spawn.c +// +qboolean G_SpawnString( const char *key, const char *defaultString, char **out ); +// spawn string returns a temporary reference, you must CopyString() if you want to keep it +qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out ); +qboolean G_SpawnInt( const char *key, const char *defaultString, int *out ); +qboolean G_SpawnVector( const char *key, const char *defaultString, float *out ); +void G_SpawnEntitiesFromString( void ); +char *G_NewString( const char *string ); +// Ridah +qboolean G_CallSpawn( gentity_t *ent ); +// done. + +// +// g_cmds.c +// +void Cmd_Score_f( gentity_t *ent ); +void StopFollowing( gentity_t *ent ); +//void BroadcastTeamChange( gclient_t *client, int oldTeam ); +void SetTeam( gentity_t *ent, char *s ); +void SetWolfData( gentity_t *ent, char *ptype, char *weap, char *grenade, char *skinnum ); // DHM - Nerve +void Cmd_FollowCycle_f( gentity_t *ent, int dir ); + +// +// g_items.c +// +void G_CheckTeamItems( void ); +void G_RunItem( gentity_t *ent ); +void RespawnItem( gentity_t *ent ); + +void UseHoldableItem( gentity_t *ent, int item ); +void PrecacheItem( gitem_t *it ); +gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean novelocity ); +gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, int ownerNum ); +void SetRespawn( gentity_t *ent, float delay ); +void G_SpawnItem( gentity_t *ent, gitem_t *item ); +void FinishSpawningItem( gentity_t *ent ); +void Think_Weapon( gentity_t *ent ); +int ArmorIndex( gentity_t *ent ); +void Fill_Clip( playerState_t *ps, int weapon ); +void Add_Ammo( gentity_t *ent, int weapon, int count, qboolean fillClip ); +void Touch_Item( gentity_t *ent, gentity_t *other, trace_t *trace ); + +// Touch_Item_Auto is bound by the rules of autoactivation (if cg_autoactivate is 0, only touch on "activate") +void Touch_Item_Auto( gentity_t *ent, gentity_t *other, trace_t *trace ); + +void ClearRegisteredItems( void ); +void RegisterItem( gitem_t *item ); +void SaveRegisteredItems( void ); +void Prop_Break_Sound( gentity_t *ent ); +void Spawn_Shard( gentity_t *ent, gentity_t *inflictor, int quantity, int type ); + +// +// g_utils.c +// +// Ridah +int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ); +// done. +int G_ModelIndex( char *name ); +int G_SoundIndex( const char *name ); +void G_TeamCommand( team_t team, char *cmd ); +void G_KillBox( gentity_t *ent ); +gentity_t *G_Find( gentity_t *from, int fieldofs, const char *match ); +gentity_t *G_PickTarget( char *targetname ); +void G_UseTargets( gentity_t *ent, gentity_t *activator ); +void G_SetMovedir( vec3_t angles, vec3_t movedir ); + +void G_InitGentity( gentity_t *e ); +gentity_t *G_Spawn( void ); +gentity_t *G_TempEntity( vec3_t origin, int event ); +void G_Sound( gentity_t *ent, int soundIndex ); +void G_AnimScriptSound( int soundIndex, vec3_t org, int client ); +void G_FreeEntity( gentity_t *e ); +//qboolean G_EntitiesFree( void ); + +void G_TouchTriggers( gentity_t *ent ); +void G_TouchSolids( gentity_t *ent ); + +float *tv( float x, float y, float z ); +char *vtos( const vec3_t v ); + +void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ); +void G_AddEvent( gentity_t *ent, int event, int eventParm ); +void G_SetOrigin( gentity_t *ent, vec3_t origin ); +void AddRemap( const char *oldShader, const char *newShader, float timeOffset ); +const char *BuildShaderStateConfig(); +void G_SetAngle( gentity_t *ent, vec3_t angle ); + +qboolean infront( gentity_t *self, gentity_t *other ); + +void G_ProcessTagConnect( gentity_t *ent ); + +// +// g_combat.c +// +qboolean CanDamage( gentity_t *targ, vec3_t origin ); +void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod ); +qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod ); +void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ); +void TossClientItems( gentity_t *self ); +gentity_t* G_BuildHead( gentity_t *ent ); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect +#define DAMAGE_NO_TEAM_PROTECTION 0x00000010 // armor, shields, invulnerability, and godmode have no effect + +// +// g_missile.c +// +void G_RunMissile( gentity_t *ent ); +int G_PredictMissile( gentity_t *ent, int duration, vec3_t endPos, qboolean allowBounce ); + +// Rafael zombiespit +void G_RunDebris( gentity_t *ent ); + +//DHM - Nerve :: server side flamethrower collision +void G_RunFlamechunk( gentity_t *ent ); + +//----(SA) removed unused q3a weapon firing +gentity_t *fire_flamechunk( gentity_t *self, vec3_t start, vec3_t dir ); + +gentity_t *fire_grenade( gentity_t *self, vec3_t start, vec3_t aimdir, int grenadeWPID ); +gentity_t *fire_rocket( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_speargun( gentity_t *self, vec3_t start, vec3_t dir ); + +//----(SA) added from MP +gentity_t *fire_nail( gentity_t *self, vec3_t start, vec3_t forward, vec3_t right, vec3_t up ); +gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t aimdir ); +//----(SA) end + +// Rafael sniper +void fire_lead( gentity_t *self, vec3_t start, vec3_t dir, int damage ); +qboolean visible( gentity_t *self, gentity_t *other ); + +gentity_t *fire_mortar( gentity_t *self, vec3_t start, vec3_t dir ); +gentity_t *fire_flamebarrel( gentity_t *self, vec3_t start, vec3_t dir ); +// done + +// +// g_mover.c +// +gentity_t *G_TestEntityPosition( gentity_t *ent ); +void G_RunMover( gentity_t *ent ); +void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ); +void G_Activate( gentity_t *ent, gentity_t *activator ); + +void G_TryDoor( gentity_t *ent, gentity_t *other, gentity_t *activator ); //----(SA) added + +void InitMoverRotate( gentity_t *ent ); + +void InitMover( gentity_t *ent ); +void SetMoverState( gentity_t *ent, moverState_t moverState, int time ); + +// +// g_tramcar.c +// +void Reached_Tramcar( gentity_t *ent ); + + +// +// g_misc.c +// +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ); +void mg42_fire( gentity_t *other ); + + +// +// g_weapon.c +// +qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ); +void CalcMuzzlePoint( gentity_t *ent, int weapon, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ); +void SnapVectorTowards( vec3_t v, vec3_t to ); +trace_t *CheckMeleeAttack( gentity_t *ent, float dist, qboolean isTest ); +gentity_t *weapon_grenadelauncher_fire( gentity_t *ent, int grenadeWPID ); +// Rafael + +void CalcMuzzlePoints( gentity_t *ent, int weapon ); + +// Rafael - for activate +void CalcMuzzlePointForActivate( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ); +// done. + +// +// g_client.c +// +team_t TeamCount( int ignoreClientNum, int team ); // NERVE - SMF - merge from team arena +team_t PickTeam( int ignoreClientNum ); +void SetClientViewAngle( gentity_t *ent, vec3_t angle ); +gentity_t *SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ); +void respawn( gentity_t *ent ); +void BeginIntermission( void ); +void InitClientPersistant( gclient_t *client ); +void InitClientResp( gclient_t *client ); +void InitBodyQue( void ); +void ClientSpawn( gentity_t *ent, qboolean revived ); +void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); +void AddScore( gentity_t *ent, int score ); +void CalculateRanks( void ); +qboolean SpotWouldTelefrag( gentity_t *spot ); + +// +// g_svcmds.c +// +qboolean ConsoleCommand( void ); +void G_ProcessIPBans( void ); +qboolean G_FilterPacket( char *from ); +qboolean G_FilterMaxLivesPacket( char *from ); +void AddMaxLivesIP( char *str ); +void AddMaxLivesGUID( char *str ); +void ClearMaxLivesIP(); +void ClearMaxLivesGUID(); + +// +// g_weapon.c +// +void G_BurnMeGood( gentity_t *self, gentity_t *body ); +void FireWeapon( gentity_t *ent ); + +// +// p_hud.c +// +void MoveClientToIntermission( gentity_t *client ); +void G_SetStats( gentity_t *ent ); +void DeathmatchScoreboardMessage( gentity_t *client ); + +// +// g_cmds.c +// +void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, qboolean localize ); // JPW NERVE removed static declaration so it would link + +// +// g_pweapon.c +// + + +// +// g_main.c +// +void FindIntermissionPoint( void ); +void G_RunThink( gentity_t *ent ); +void QDECL G_LogPrintf( const char *fmt, ... ); +void SendScoreboardMessageToAllClients( void ); +void QDECL G_Printf( const char *fmt, ... ); +void QDECL G_DPrintf( const char *fmt, ... ); +void QDECL G_Error( const char *fmt, ... ); + +// +// g_client.c +// +char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ); +void ClientUserinfoChanged( int clientNum ); +void ClientDisconnect( int clientNum ); +void ClientBegin( int clientNum ); +void ClientCommand( int clientNum ); + +// +// g_active.c +// +void ClientThink( int clientNum ); +void ClientEndFrame( gentity_t *ent ); +void G_RunClient( gentity_t *ent ); + +// +// g_team.c +// +qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ); + + +// +// g_mem.c +// +void *G_Alloc( int size ); +void G_InitMemory( void ); +void Svcmd_GameMem_f( void ); + +// +// g_session.c +// +void G_ReadSessionData( gclient_t *client ); +void G_InitSessionData( gclient_t *client, char *userinfo ); + +void G_InitWorldSession( void ); +void G_WriteSessionData( void ); + +// +// g_bot.c +// +void G_InitBots( qboolean restart ); +char *G_GetBotInfoByNumber( int num ); +char *G_GetBotInfoByName( const char *name ); +void G_CheckBotSpawn( void ); +void G_QueueBotBegin( int clientNum ); +qboolean G_BotConnect( int clientNum, qboolean restart ); +void Svcmd_AddBot_f( void ); + +// ai_main.c +#define MAX_FILEPATH 144 + +//bot settings +typedef struct bot_settings_s +{ + char characterfile[MAX_FILEPATH]; + float skill; + char team[MAX_FILEPATH]; +} bot_settings_t; + +int BotAISetup( int restart ); +int BotAIShutdown( int restart ); +int BotAILoadMap( int restart ); +int BotAISetupClient( int client, struct bot_settings_s *settings ); +int BotAIShutdownClient( int client ); +int BotAIStartFrame( int time ); +void BotTestAAS( vec3_t origin ); + + +// g_cmd.c +void Cmd_Activate_f( gentity_t *ent ); +int Cmd_WolfKick_f( gentity_t *ent ); +// Ridah + +// g_save.c +/*qboolean G_SaveGame(char *username); +void G_LoadGame(char *username); +qboolean G_SavePersistant(char *nextmap); +void G_LoadPersistant(void); +void G_UpdatePlayTime ( void );*/ + +// g_script.c +void G_Script_ScriptParse( gentity_t *ent ); +qboolean G_Script_ScriptRun( gentity_t *ent ); +void G_Script_ScriptEvent( gentity_t *ent, char *eventStr, char *params ); +void G_Script_ScriptLoad( void ); + +float AngleDifference( float ang1, float ang2 ); + +// g_props.c +void Props_Chair_Skyboxtouch( gentity_t *ent ); + +#include "g_team.h" // teamplay specific stuff + + +extern level_locals_t level; +extern gentity_t g_entities[]; //DAJ was explicit set to MAX_ENTITIES +extern gentity_t *g_camEnt; + +#define FOFS( x ) ( (int)&( ( (gentity_t *)0 )->x ) ) + +extern vmCvar_t g_gametype; + +// Rafael gameskill +extern vmCvar_t g_gameskill; +// done + +extern vmCvar_t g_dedicated; +extern vmCvar_t g_cheats; +extern vmCvar_t g_maxclients; // allow this many total, including spectators +extern vmCvar_t g_maxGameClients; // allow this many active +extern vmCvar_t g_minGameClients; // NERVE - SMF - we need at least this many before match actually starts +extern vmCvar_t g_restarted; + +extern vmCvar_t g_dmflags; +extern vmCvar_t g_fraglimit; +extern vmCvar_t g_timelimit; +extern vmCvar_t g_capturelimit; +extern vmCvar_t g_friendlyFire; +extern vmCvar_t g_password; +extern vmCvar_t g_needpass; +extern vmCvar_t g_gravity; +extern vmCvar_t g_speed; +extern vmCvar_t g_knockback; +extern vmCvar_t g_quadfactor; +extern vmCvar_t g_forcerespawn; +extern vmCvar_t g_inactivity; +extern vmCvar_t g_debugMove; +extern vmCvar_t g_debugAlloc; +extern vmCvar_t g_debugDamage; +extern vmCvar_t g_debugBullets; //----(SA) added +extern vmCvar_t g_weaponRespawn; +extern vmCvar_t g_synchronousClients; +extern vmCvar_t g_motd; +extern vmCvar_t g_warmup; +extern vmCvar_t g_voteFlags; + +// DHM - Nerve :: The number of complaints allowed before kick/ban +extern vmCvar_t g_complaintlimit; +extern vmCvar_t g_maxlives; // DHM - Nerve :: number of respawns allowed (0==infinite) +extern vmCvar_t g_voiceChatsAllowed; // DHM - Nerve :: number before spam control +extern vmCvar_t g_alliedmaxlives; // Xian +extern vmCvar_t g_axismaxlives; // Xian +extern vmCvar_t g_fastres; // Xian - Fast medic res'ing +extern vmCvar_t g_fastResMsec; +extern vmCvar_t g_knifeonly; // Xian - Wacky Knife-Only rounds +extern vmCvar_t g_enforcemaxlives; // Xian - Temp ban with maxlives between rounds + +extern vmCvar_t g_needpass; +extern vmCvar_t g_weaponTeamRespawn; +extern vmCvar_t g_doWarmup; +extern vmCvar_t g_teamAutoJoin; +extern vmCvar_t g_teamForceBalance; +extern vmCvar_t g_banIPs; +extern vmCvar_t g_filterBan; +extern vmCvar_t g_rankings; +extern vmCvar_t g_enableBreath; +extern vmCvar_t g_smoothClients; +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; + +//Rafael +extern vmCvar_t g_autoactivate; + +extern vmCvar_t g_testPain; + +extern vmCvar_t g_missionStats; +extern vmCvar_t ai_scriptName; // name of AI script file to run (instead of default for that map) +extern vmCvar_t g_scriptName; // name of script file to run (instead of default for that map) + +extern vmCvar_t g_scriptDebug; + +extern vmCvar_t g_userAim; + +extern vmCvar_t g_forceModel; + +extern vmCvar_t g_mg42arc; + +extern vmCvar_t g_footstepAudibleRange; +// JPW NERVE multiplayer +extern vmCvar_t g_redlimbotime; +extern vmCvar_t g_bluelimbotime; +extern vmCvar_t g_medicChargeTime; +extern vmCvar_t g_engineerChargeTime; +extern vmCvar_t g_LTChargeTime; +extern vmCvar_t g_soldierChargeTime; +extern vmCvar_t sv_screenshake; +// jpw + +// NERVE - SMF +extern vmCvar_t g_warmupLatch; +extern vmCvar_t g_nextTimeLimit; +extern vmCvar_t g_showHeadshotRatio; +extern vmCvar_t g_userTimeLimit; +extern vmCvar_t g_userAlliedRespawnTime; +extern vmCvar_t g_userAxisRespawnTime; +extern vmCvar_t g_currentRound; +extern vmCvar_t g_noTeamSwitching; +extern vmCvar_t g_altStopwatchMode; +extern vmCvar_t g_gamestate; +extern vmCvar_t g_swapteams; +// -NERVE - SMF + +//Gordon +extern vmCvar_t g_antilag; + +extern vmCvar_t g_dbgRevive; + +void trap_Printf( const char *fmt ); +void trap_Error( const char *fmt ); +int trap_Milliseconds( void ); +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +int trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +int trap_FS_Rename( const char *from, const char *to ); +void trap_FS_FCloseFile( fileHandle_t f ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +void trap_SendConsoleCommand( int exec_when, const char *text ); +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ); +void trap_Cvar_Update( vmCvar_t *cvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +int trap_Cvar_VariableIntegerValue( const char *var_name ); +float trap_Cvar_VariableValue( const char *var_name ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +void trap_LocateGameData( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, playerState_t *gameClients, int sizeofGameClient ); +void trap_DropClient( int clientNum, const char *reason ); +void trap_SendServerCommand( int clientNum, const char *text ); +void trap_SetConfigstring( int num, const char *string ); +void trap_GetConfigstring( int num, char *buffer, int bufferSize ); +void trap_GetUserinfo( int num, char *buffer, int bufferSize ); +void trap_SetUserinfo( int num, const char *buffer ); +void trap_GetServerinfo( char *buffer, int bufferSize ); +void trap_SetBrushModel( gentity_t *ent, const char *name ); +void trap_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); +void trap_TraceCapsule( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); +int trap_PointContents( const vec3_t point, int passEntityNum ); +qboolean trap_InPVS( const vec3_t p1, const vec3_t p2 ); +qboolean trap_InPVSIgnorePortals( const vec3_t p1, const vec3_t p2 ); +void trap_AdjustAreaPortalState( gentity_t *ent, qboolean open ); +qboolean trap_AreasConnected( int area1, int area2 ); +void trap_LinkEntity( gentity_t *ent ); +void trap_UnlinkEntity( gentity_t *ent ); +int trap_EntitiesInBox( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ); +qboolean trap_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); +qboolean trap_EntityContactCapsule( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); +int trap_BotAllocateClient( void ); +void trap_BotFreeClient( int clientNum ); +void trap_GetUsercmd( int clientNum, usercmd_t *cmd ); +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); +qboolean trap_GetTag( int clientNum, char *tagName, orientation_t * or ); + +int trap_DebugPolygonCreate( int color, int numPoints, vec3_t *points ); +void trap_DebugPolygonDelete( int id ); + +int trap_BotLibSetup( void ); +int trap_BotLibShutdown( void ); +int trap_BotLibVarSet( char *var_name, char *value ); +int trap_BotLibVarGet( char *var_name, char *value, int size ); +int trap_BotLibDefine( char *string ); +int trap_BotLibStartFrame( float time ); +int trap_BotLibLoadMap( const char *mapname ); +int trap_BotLibUpdateEntity( int ent, void /* struct bot_updateentity_s */ *bue ); +int trap_BotLibTest( int parm0, char *parm1, vec3_t parm2, vec3_t parm3 ); + +int trap_BotGetSnapshotEntity( int clientNum, int sequence ); +int trap_BotGetServerCommand( int clientNum, char *message, int size ); +//int trap_BotGetConsoleMessage(int clientNum, char *message, int size); +void trap_BotUserCommand( int client, usercmd_t *ucmd ); + +void trap_AAS_EntityInfo( int entnum, void /* struct aas_entityinfo_s */ *info ); + +int trap_AAS_Initialized( void ); +void trap_AAS_PresenceTypeBoundingBox( int presencetype, vec3_t mins, vec3_t maxs ); +float trap_AAS_Time( void ); + +// Ridah +void trap_AAS_SetCurrentWorld( int index ); +// done. + +int trap_AAS_PointAreaNum( vec3_t point ); +int trap_AAS_TraceAreas( vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas ); + +int trap_AAS_PointContents( vec3_t point ); +int trap_AAS_NextBSPEntity( int ent ); +int trap_AAS_ValueForBSPEpairKey( int ent, char *key, char *value, int size ); +int trap_AAS_VectorForBSPEpairKey( int ent, char *key, vec3_t v ); +int trap_AAS_FloatForBSPEpairKey( int ent, char *key, float *value ); +int trap_AAS_IntForBSPEpairKey( int ent, char *key, int *value ); + +int trap_AAS_AreaReachability( int areanum ); + +int trap_AAS_AreaTravelTimeToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags ); + +int trap_AAS_Swimming( vec3_t origin ); +int trap_AAS_PredictClientMovement( void /* aas_clientmove_s */ *move, int entnum, vec3_t origin, int presencetype, int onground, vec3_t velocity, vec3_t cmdmove, int cmdframes, int maxframes, float frametime, int stopevent, int stopareanum, int visualize ); + +// Ridah, route-tables +void trap_AAS_RT_ShowRoute( vec3_t srcpos, int srcnum, int destnum ); +qboolean trap_AAS_RT_GetHidePos( vec3_t srcpos, int srcnum, int srcarea, vec3_t destpos, int destnum, int destarea, vec3_t returnPos ); +int trap_AAS_FindAttackSpotWithinRange( int srcnum, int rangenum, int enemynum, float rangedist, int travelflags, float *outpos ); +void trap_AAS_SetAASBlockingEntity( vec3_t absmin, vec3_t absmax, qboolean blocking ); +// done. + +void trap_EA_Say( int client, char *str ); +void trap_EA_SayTeam( int client, char *str ); +void trap_EA_UseItem( int client, char *it ); +void trap_EA_DropItem( int client, char *it ); +void trap_EA_UseInv( int client, char *inv ); +void trap_EA_DropInv( int client, char *inv ); +void trap_EA_Gesture( int client ); +void trap_EA_Command( int client, char *command ); + +void trap_EA_SelectWeapon( int client, int weapon ); +void trap_EA_Talk( int client ); +void trap_EA_Attack( int client ); +void trap_EA_Reload( int client ); +void trap_EA_Use( int client ); +void trap_EA_Respawn( int client ); +void trap_EA_Jump( int client ); +void trap_EA_DelayedJump( int client ); +void trap_EA_Crouch( int client ); +void trap_EA_MoveUp( int client ); +void trap_EA_MoveDown( int client ); +void trap_EA_MoveForward( int client ); +void trap_EA_MoveBack( int client ); +void trap_EA_MoveLeft( int client ); +void trap_EA_MoveRight( int client ); +void trap_EA_Move( int client, vec3_t dir, float speed ); +void trap_EA_View( int client, vec3_t viewangles ); + +void trap_EA_EndRegular( int client, float thinktime ); +void trap_EA_GetInput( int client, float thinktime, void /* struct bot_input_s */ *input ); +void trap_EA_ResetInput( int client, void *init ); + + +int trap_BotLoadCharacter( char *charfile, int skill ); +void trap_BotFreeCharacter( int character ); +float trap_Characteristic_Float( int character, int index ); +float trap_Characteristic_BFloat( int character, int index, float min, float max ); +int trap_Characteristic_Integer( int character, int index ); +int trap_Characteristic_BInteger( int character, int index, int min, int max ); +void trap_Characteristic_String( int character, int index, char *buf, int size ); + +int trap_BotAllocChatState( void ); +void trap_BotFreeChatState( int handle ); +void trap_BotQueueConsoleMessage( int chatstate, int type, char *message ); +void trap_BotRemoveConsoleMessage( int chatstate, int handle ); +int trap_BotNextConsoleMessage( int chatstate, void /* struct bot_consolemessage_s */ *cm ); +int trap_BotNumConsoleMessages( int chatstate ); +void trap_BotInitialChat( int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ); +int trap_BotNumInitialChats( int chatstate, char *type ); +int trap_BotReplyChat( int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ); +int trap_BotChatLength( int chatstate ); +void trap_BotEnterChat( int chatstate, int client, int sendto ); +void trap_BotGetChatMessage( int chatstate, char *buf, int size ); +int trap_StringContains( char *str1, char *str2, int casesensitive ); +int trap_BotFindMatch( char *str, void /* struct bot_match_s */ *match, unsigned long int context ); +void trap_BotMatchVariable( void /* struct bot_match_s */ *match, int variable, char *buf, int size ); +void trap_UnifyWhiteSpaces( char *string ); +void trap_BotReplaceSynonyms( char *string, unsigned long int context ); +int trap_BotLoadChatFile( int chatstate, char *chatfile, char *chatname ); +void trap_BotSetChatGender( int chatstate, int gender ); +void trap_BotSetChatName( int chatstate, char *name ); +void trap_BotResetGoalState( int goalstate ); +void trap_BotRemoveFromAvoidGoals( int goalstate, int number ); +void trap_BotResetAvoidGoals( int goalstate ); +void trap_BotPushGoal( int goalstate, void /* struct bot_goal_s */ *goal ); +void trap_BotPopGoal( int goalstate ); +void trap_BotEmptyGoalStack( int goalstate ); +void trap_BotDumpAvoidGoals( int goalstate ); +void trap_BotDumpGoalStack( int goalstate ); +void trap_BotGoalName( int number, char *name, int size ); +int trap_BotGetTopGoal( int goalstate, void /* struct bot_goal_s */ *goal ); +int trap_BotGetSecondGoal( int goalstate, void /* struct bot_goal_s */ *goal ); +int trap_BotChooseLTGItem( int goalstate, vec3_t origin, int *inventory, int travelflags ); +int trap_BotChooseNBGItem( int goalstate, vec3_t origin, int *inventory, int travelflags, void /* struct bot_goal_s */ *ltg, float maxtime ); +int trap_BotTouchingGoal( vec3_t origin, void /* struct bot_goal_s */ *goal ); +int trap_BotItemGoalInVisButNotVisible( int viewer, vec3_t eye, vec3_t viewangles, void /* struct bot_goal_s */ *goal ); +int trap_BotGetNextCampSpotGoal( int num, void /* struct bot_goal_s */ *goal ); +int trap_BotGetMapLocationGoal( char *name, void /* struct bot_goal_s */ *goal ); +int trap_BotGetLevelItemGoal( int index, char *classname, void /* struct bot_goal_s */ *goal ); +float trap_BotAvoidGoalTime( int goalstate, int number ); +void trap_BotInitLevelItems( void ); +void trap_BotUpdateEntityItems( void ); +int trap_BotLoadItemWeights( int goalstate, char *filename ); +void trap_BotFreeItemWeights( int goalstate ); +void trap_BotInterbreedGoalFuzzyLogic( int parent1, int parent2, int child ); +void trap_BotSaveGoalFuzzyLogic( int goalstate, char *filename ); +void trap_BotMutateGoalFuzzyLogic( int goalstate, float range ); +int trap_BotAllocGoalState( int state ); +void trap_BotFreeGoalState( int handle ); + +void trap_BotResetMoveState( int movestate ); +void trap_BotMoveToGoal( void /* struct bot_moveresult_s */ *result, int movestate, void /* struct bot_goal_s */ *goal, int travelflags ); +int trap_BotMoveInDirection( int movestate, vec3_t dir, float speed, int type ); +void trap_BotResetAvoidReach( int movestate ); +void trap_BotResetLastAvoidReach( int movestate ); +int trap_BotReachabilityArea( vec3_t origin, int testground ); +int trap_BotMovementViewTarget( int movestate, void /* struct bot_goal_s */ *goal, int travelflags, float lookahead, vec3_t target ); +int trap_BotPredictVisiblePosition( vec3_t origin, int areanum, void /* struct bot_goal_s */ *goal, int travelflags, vec3_t target ); +int trap_BotAllocMoveState( void ); +void trap_BotFreeMoveState( int handle ); +void trap_BotInitMoveState( int handle, void /* struct bot_initmove_s */ *initmove ); +// Ridah +void trap_BotInitAvoidReach( int handle ); +// done. + +int trap_BotChooseBestFightWeapon( int weaponstate, int *inventory ); +void trap_BotGetWeaponInfo( int weaponstate, int weapon, void /* struct weaponinfo_s */ *weaponinfo ); +int trap_BotLoadWeaponWeights( int weaponstate, char *filename ); +int trap_BotAllocWeaponState( void ); +void trap_BotFreeWeaponState( int weaponstate ); +void trap_BotResetWeaponState( int weaponstate ); + +int trap_GeneticParentsAndChildSelection( int numranks, float *ranks, int *parent1, int *parent2, int *child ); + +void trap_SnapVector( float *v ); + +typedef enum +{ + shard_glass = 0, + shard_wood, + shard_metal, + shard_ceramic, + shard_rubble +} shards_t; + +// g_antilag.c +void G_StoreClientPosition( gentity_t* ent ); +void G_HistoricalTrace( gentity_t* ent, trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); +void G_ResetMarkers( gentity_t* ent ); diff --git a/src/game/g_main.c b/src/game/g_main.c new file mode 100644 index 0000000..d8d9085 --- /dev/null +++ b/src/game/g_main.c @@ -0,0 +1,2768 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + +level_locals_t level; + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; + int modificationCount; // for tracking changes + qboolean trackChange; // track this variable, and announce if changed + qboolean teamShader; // track and if changed, update shader state +} cvarTable_t; + +gentity_t g_entities[MAX_GENTITIES]; +gclient_t g_clients[MAX_CLIENTS]; + +gentity_t *g_camEnt = NULL; //----(SA) script camera + +// Rafael gameskill +extern int bg_pmove_gameskill_integer; +// done + +vmCvar_t g_gametype; + +// Rafael gameskill +vmCvar_t g_gameskill; +// done + +vmCvar_t g_dmflags; +vmCvar_t g_fraglimit; +vmCvar_t g_timelimit; +vmCvar_t g_capturelimit; +vmCvar_t g_friendlyFire; +vmCvar_t g_password; +vmCvar_t g_maxclients; +vmCvar_t g_maxGameClients; +vmCvar_t g_minGameClients; // NERVE - SMF +vmCvar_t g_dedicated; +vmCvar_t g_speed; +vmCvar_t g_gravity; +vmCvar_t g_cheats; +vmCvar_t g_knockback; +vmCvar_t g_quadfactor; +vmCvar_t g_forcerespawn; +vmCvar_t g_inactivity; +vmCvar_t g_debugMove; +vmCvar_t g_debugDamage; +vmCvar_t g_debugAlloc; +vmCvar_t g_debugBullets; //----(SA) added +vmCvar_t g_weaponRespawn; +vmCvar_t g_motd; +vmCvar_t g_synchronousClients; +vmCvar_t g_warmup; + +// NERVE - SMF +vmCvar_t g_warmupLatch; +vmCvar_t g_nextTimeLimit; +vmCvar_t g_showHeadshotRatio; +vmCvar_t g_userTimeLimit; +vmCvar_t g_userAlliedRespawnTime; +vmCvar_t g_userAxisRespawnTime; +vmCvar_t g_currentRound; +vmCvar_t g_noTeamSwitching; +vmCvar_t g_altStopwatchMode; +vmCvar_t g_gamestate; +vmCvar_t g_swapteams; +// -NERVE - SMF + +vmCvar_t g_restarted; +vmCvar_t g_log; +vmCvar_t g_logSync; +vmCvar_t g_podiumDist; +vmCvar_t g_podiumDrop; +vmCvar_t g_voteFlags; +vmCvar_t g_complaintlimit; // DHM - Nerve +vmCvar_t g_maxlives; // DHM - Nerve +vmCvar_t g_voiceChatsAllowed; // DHM - Nerve +vmCvar_t g_alliedmaxlives; // Xian +vmCvar_t g_axismaxlives; // Xian +vmCvar_t g_fastres; // Xian +vmCvar_t g_fastResMsec; +vmCvar_t g_knifeonly; // Xian +vmCvar_t g_enforcemaxlives; // Xian + +vmCvar_t g_needpass; +vmCvar_t g_weaponTeamRespawn; +vmCvar_t g_doWarmup; +vmCvar_t g_teamAutoJoin; +vmCvar_t g_teamForceBalance; +vmCvar_t g_listEntity; +vmCvar_t g_banIPs; +vmCvar_t g_filterBan; +vmCvar_t g_rankings; +vmCvar_t g_enableBreath; +vmCvar_t g_smoothClients; +vmCvar_t pmove_fixed; +vmCvar_t pmove_msec; + +// Rafael +vmCvar_t g_autoactivate; +vmCvar_t g_testPain; + +vmCvar_t g_missionStats; +vmCvar_t ai_scriptName; // name of AI script file to run (instead of default for that map) +vmCvar_t g_scriptName; // name of script file to run (instead of default for that map) + +vmCvar_t g_developer; + +vmCvar_t g_userAim; + +vmCvar_t g_forceModel; + +vmCvar_t g_mg42arc; + +vmCvar_t g_footstepAudibleRange; +// JPW NERVE multiplayer reinforcement times +vmCvar_t g_redlimbotime; +vmCvar_t g_bluelimbotime; +// charge times for character class special weapons +vmCvar_t g_medicChargeTime; +vmCvar_t g_engineerChargeTime; +vmCvar_t g_LTChargeTime; +vmCvar_t g_soldierChargeTime; +// screen shakey magnitude multiplier +vmCvar_t sv_screenshake; +// jpw + +// Gordon +vmCvar_t g_antilag; + +vmCvar_t mod_url; +vmCvar_t url; + +vmCvar_t g_dbgRevive; + +cvarTable_t gameCvarTable[] = { + // don't override the cheat state set by the system + { &g_cheats, "sv_cheats", "", 0, qfalse }, + + // noset vars + { NULL, "gamename", GAMEVERSION, CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + { NULL, "gamedate", __DATE__, CVAR_ROM, 0, qfalse }, + { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse }, + { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, + + // latched vars + // DHM - Nerve :: default to GT_WOLF + { &g_gametype, "g_gametype", "5", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse }, + + // Rafael gameskill + { &g_gameskill, "g_gameskill", "3", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse }, + // done + +// JPW NERVE multiplayer stuffs + { &sv_screenshake, "sv_screenshake", "5", CVAR_ARCHIVE,0,qfalse}, + { &g_redlimbotime, "g_redlimbotime", "30000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse }, + { &g_bluelimbotime, "g_bluelimbotime", "30000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse }, + { &g_medicChargeTime, "g_medicChargeTime", "45000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse }, + { &g_engineerChargeTime, "g_engineerChargeTime", "30000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse }, + { &g_LTChargeTime, "g_LTChargeTime", "40000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse }, + { &g_soldierChargeTime, "g_soldierChargeTime", "20000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse }, +// jpw + + { &g_maxclients, "sv_maxclients", "20", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse }, // NERVE - SMF - made 20 from 8 + { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse }, + { &g_minGameClients, "g_minGameClients", "8", CVAR_SERVERINFO, 0, qfalse }, // NERVE - SMF + + // change anytime vars + { &g_dmflags, "dmflags", "0", /*CVAR_SERVERINFO |*/ CVAR_ARCHIVE, 0, qtrue }, + { &g_fraglimit, "fraglimit", "0", /*CVAR_SERVERINFO |*/ CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + { &g_capturelimit, "capturelimit", "8", /*CVAR_SERVERINFO |*/ CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + + { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse }, + + { &g_friendlyFire, "g_friendlyFire", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, + + { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE }, // NERVE - SMF - merge from team arena + + { &g_warmup, "g_warmup", "20", CVAR_ARCHIVE, 0, qtrue }, + { &g_doWarmup, "g_doWarmup", "0", 0, 0, qtrue }, + + // NERVE - SMF + { &g_warmupLatch, "g_warmupLatch", "1", 0, 0, qfalse }, + + { &g_nextTimeLimit, "g_nextTimeLimit", "0", CVAR_WOLFINFO, 0, qfalse }, + { &g_currentRound, "g_currentRound", "0", CVAR_WOLFINFO, 0, qfalse }, + { &g_altStopwatchMode, "g_altStopwatchMode", "0", CVAR_ARCHIVE, 0, qtrue }, + { &g_gamestate, "gamestate", "-1", CVAR_WOLFINFO | CVAR_ROM, 0, qfalse }, + + { &g_noTeamSwitching, "g_noTeamSwitching", "0", CVAR_ARCHIVE, 0, qtrue }, + + { &g_showHeadshotRatio, "g_showHeadshotRatio", "0", 0, 0, qfalse }, + + { &g_userTimeLimit, "g_userTimeLimit", "0", 0, 0, qfalse }, + { &g_userAlliedRespawnTime, "g_userAlliedRespawnTime", "0", 0, 0, qfalse }, + { &g_userAxisRespawnTime, "g_userAxisRespawnTime", "0", 0, 0, qfalse }, + + { &g_swapteams, "g_swapteams", "0", CVAR_ROM, 0, qfalse }, + // -NERVE - SMF + + { &g_log, "g_log", "", CVAR_ARCHIVE, 0, qfalse }, + { &g_logSync, "g_logSync", "0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse }, + { &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse }, + // show_bug.cgi?id=500 + { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse }, + + { &g_dedicated, "dedicated", "0", 0, 0, qfalse }, + + { &g_speed, "g_speed", "320", 0, 0, qtrue }, + { &g_gravity, "g_gravity", "800", 0, 0, qtrue }, + { &g_knockback, "g_knockback", "1000", 0, 0, qtrue }, + { &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue }, + { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue }, + { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue }, + { &g_forcerespawn, "g_forcerespawn", "0", 0, 0, qtrue }, + { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue }, + { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse }, + { &g_debugDamage, "g_debugDamage", "0", CVAR_CHEAT, 0, qfalse }, + { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse }, + { &g_debugBullets, "g_debugBullets", "0", CVAR_CHEAT, 0, qfalse}, //----(SA) added + { &g_motd, "g_motd", "", CVAR_ARCHIVE, 0, qfalse }, + + { &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse }, + { &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse }, + + { &g_voteFlags, "g_voteFlags", "255", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse }, + + { &g_complaintlimit, "g_complaintlimit", "3", CVAR_ARCHIVE, 0, qtrue}, // DHM - Nerve + { &g_maxlives, "g_maxlives", "0", CVAR_ARCHIVE | CVAR_LATCH | CVAR_SERVERINFO, 0, qtrue}, // DHM - Nerve + { &g_voiceChatsAllowed, "g_voiceChatsAllowed", "4", CVAR_ARCHIVE, 0, qfalse}, // DHM - Nerve + + { &g_alliedmaxlives, "g_alliedmaxlives", "0", CVAR_LATCH | CVAR_SERVERINFO, 0, qtrue}, // Xian + { &g_axismaxlives, "g_axismaxlives", "0", CVAR_LATCH | CVAR_SERVERINFO, 0, qtrue}, // Xian + { &g_fastres, "g_fastres", "0", CVAR_ARCHIVE, 0, qtrue}, // Xian - Fast Medic Resing + { &g_fastResMsec, "g_fastResMsec", "1000", CVAR_ARCHIVE, 0, qtrue}, // Xian - Fast Medic Resing + { &g_knifeonly, "g_knifeonly", "0", 0, 0, qtrue}, // Xian - Fast Medic Resing + { &g_enforcemaxlives, "g_enforcemaxlives", "1", CVAR_ARCHIVE, 0, qtrue}, // Xian - Gestapo enforce maxlives stuff by temp banning + + { &g_enableBreath, "g_enableBreath", "1", CVAR_SERVERINFO, 0, qtrue}, + { &g_testPain, "g_testPain", "0", CVAR_CHEAT, 0, qfalse }, + { &g_missionStats, "g_missionStats", "0", CVAR_ROM, 0, qfalse }, + { &g_developer, "developer", "0", CVAR_TEMP, 0, qfalse }, + { &g_rankings, "g_rankings", "0", 0, 0, qfalse}, + { &g_userAim, "g_userAim", "1", CVAR_CHEAT, 0, qfalse }, + + { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse}, + { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse}, + { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, + + {&g_mg42arc, "g_mg42arc", "0", CVAR_TEMP, 0, qfalse}, + + {&g_footstepAudibleRange, "g_footstepAudibleRange", "256", CVAR_CHEAT, 0, qfalse}, + + {&g_scriptName, "g_scriptName", "", CVAR_ROM, 0, qfalse}, + {&ai_scriptName, "ai_scriptName", "", CVAR_ROM, 0, qfalse}, + + // points to the URL for mod information, should not be modified by server admin + {&mod_url, "mod_url", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse}, + // configured by the server admin, points to the web pages for the server + {&url, "URL", "", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse}, + + {&g_antilag, "g_antilag", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse}, + + {&g_dbgRevive, "g_dbgRevive", "0", 0, 0, qfalse} +}; + +// bk001129 - made static to avoid aliasing +static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[0] ); + + +void G_InitGame( int levelTime, int randomSeed, int restart ); +void G_RunFrame( int levelTime ); +void G_ShutdownGame( int restart ); +void CheckExitRules( void ); + +// Ridah, Cast AI +qboolean AICast_VisibleFromPos( vec3_t srcpos, int srcnum, + vec3_t destpos, int destnum, qboolean updateVisPos ); +qboolean AICast_CheckAttackAtPos( int entnum, int enemy, vec3_t pos, qboolean ducking, qboolean allowHitWorld ); +void AICast_Init( void ); +// done. + +void G_RetrieveMoveSpeedsFromClient( int entnum, char *text ); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +#if defined( __MACOS__ ) +#pragma export on +#endif +int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6 ) { +#if defined( __MACOS__ ) +#pragma export off +#endif + switch ( command ) { + case GAME_INIT: + G_InitGame( arg0, arg1, arg2 ); + return 0; + case GAME_SHUTDOWN: + G_ShutdownGame( arg0 ); + return 0; + case GAME_CLIENT_CONNECT: + return (int)ClientConnect( arg0, arg1, arg2 ); + case GAME_CLIENT_THINK: + ClientThink( arg0 ); + return 0; + case GAME_CLIENT_USERINFO_CHANGED: + ClientUserinfoChanged( arg0 ); + return 0; + case GAME_CLIENT_DISCONNECT: + ClientDisconnect( arg0 ); + return 0; + case GAME_CLIENT_BEGIN: + ClientBegin( arg0 ); + return 0; + case GAME_CLIENT_COMMAND: + ClientCommand( arg0 ); + return 0; + case GAME_RUN_FRAME: + G_RunFrame( arg0 ); + return 0; + case GAME_CONSOLE_COMMAND: + return ConsoleCommand(); + case BOTAI_START_FRAME: + return BotAIStartFrame( arg0 ); + // Ridah, Cast AI + case AICAST_VISIBLEFROMPOS: + return AICast_VisibleFromPos( (float *)arg0, arg1, (float *)arg2, arg3, arg4 ); + case AICAST_CHECKATTACKATPOS: + return AICast_CheckAttackAtPos( arg0, arg1, (float *)arg2, arg3, arg4 ); + // done. + + case GAME_RETRIEVE_MOVESPEEDS_FROM_CLIENT: + G_RetrieveMoveSpeedsFromClient( arg0, (char *)arg1 ); + return 0; + } + + return -1; +} + +void QDECL G_Printf( const char *fmt, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, fmt ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + trap_Printf( text ); +} + +void QDECL G_DPrintf( const char *fmt, ... ) { + va_list argptr; + char text[1024]; + + if ( !g_developer.integer ) { + return; + } + + va_start( argptr, fmt ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + trap_Printf( text ); +} + +void QDECL G_Error( const char *fmt, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, fmt ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + trap_Error( text ); +} + + +#define CH_KNIFE_DIST 48 // from g_weapon.c +#define CH_LADDER_DIST 100 +#define CH_WATER_DIST 100 +#define CH_BREAKABLE_DIST 64 +#define CH_DOOR_DIST 96 +#define CH_ACTIVATE_DIST 96 +#define CH_EXIT_DIST 256 + +#define CH_MAX_DIST 256 // use the largest value from above +#define CH_MAX_DIST_ZOOM 8192 // max dist for zooming hints +/* +============== +G_CheckForCursorHints + non-AI's check for cursor hint contacts + + server-side because there's info we want to show that the client + just doesn't know about. (health or other info of an explosive,invisible_users,items,etc.) + + traceEnt is the ent hit by the trace, checkEnt is the ent that is being + checked against (in case the traceent was an invisible_user or something) + +============== +*/ +void G_CheckForCursorHints( gentity_t *ent ) { + vec3_t forward, right, up, offset, end; + trace_t *tr; + float dist; + gentity_t *checkEnt, *traceEnt = 0; + //gentity_t *traceEnt2 = 0; // JPW NERVE // TTimo unused + playerState_t *ps; + int hintType, hintDist, hintVal; + qboolean zooming, indirectHit; // indirectHit means the checkent was not the ent hit by the trace (checkEnt!=traceEnt) + int trace_contents; // DHM - Nerve + + // FIXME: no need at all to do this trace/string comparison every frame. + // stagger it at least a little bit + +// if(!servercursorhints) +// return; + + if ( !ent->client ) { + return; + } + + ps = &ent->client->ps; + + if ( ps->aiChar != AICHAR_NONE ) { + return; + } + + indirectHit = qfalse; + + zooming = (qboolean)( ps->eFlags & EF_ZOOMING ); + + AngleVectors( ps->viewangles, forward, right, up ); + + VectorCopy( ps->origin, offset ); + offset[2] += ps->viewheight; + + // lean + if ( ps->leanf ) { + VectorMA( offset, ps->leanf, right, offset ); + } + +// SnapVector( offset ); + + if ( zooming ) { + VectorMA( offset, CH_MAX_DIST_ZOOM, forward, end ); + } else { + VectorMA( offset, CH_MAX_DIST, forward, end ); + } + + tr = &ps->serverCursorHintTrace; + + // DHM - Nerve + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + trace_contents = ( CONTENTS_TRIGGER | CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY ); + } else { + trace_contents = ( CONTENTS_TRIGGER | CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY | CONTENTS_CORPSE ); + } + + trap_Trace( tr, offset, NULL, NULL, end, ps->clientNum, trace_contents ); + // dhm - end + + // reset all + hintType = ps->serverCursorHint = HINT_NONE; + hintVal = ps->serverCursorHintVal = 0; + + if ( zooming ) { + dist = tr->fraction * CH_MAX_DIST_ZOOM; + hintDist = CH_MAX_DIST_ZOOM; + } else { + dist = tr->fraction * CH_MAX_DIST; + hintDist = CH_MAX_DIST; + } + + if ( tr->fraction == 1 ) { + return; + } + + traceEnt = &g_entities[tr->entityNum]; + + // DHM - Nerve :: Ignore trigger_objective_info + if ( ( ps->stats[ STAT_PLAYER_CLASS ] != PC_MEDIC && traceEnt->client ) + || !( strcmp( traceEnt->classname, "trigger_objective_info" ) ) + || !( strcmp( traceEnt->classname, "trigger_multiple" ) ) ) { + + trap_Trace( tr, offset, NULL, NULL, end, traceEnt->s.number, trace_contents ); + if ( zooming ) { + dist = tr->fraction * CH_MAX_DIST_ZOOM; + hintDist = CH_MAX_DIST_ZOOM; + } else { + dist = tr->fraction * CH_MAX_DIST; + hintDist = CH_MAX_DIST; + } + if ( tr->fraction == 1 ) { + return; + } + traceEnt = &g_entities[tr->entityNum]; + } + + // + // WORLD + // + if ( tr->entityNum == ENTITYNUM_WORLD ) { + if ( ( tr->contents & CONTENTS_WATER ) && !( ps->powerups[PW_BREATHER] ) ) { + hintDist = CH_WATER_DIST; + hintType = HINT_WATER; + } + // ladder + else if ( ( tr->surfaceFlags & SURF_LADDER ) && !( ps->pm_flags & PMF_LADDER ) ) { + hintDist = CH_LADDER_DIST; + hintType = HINT_LADDER; + } + } + // + // PEOPLE + // + else if ( tr->entityNum < MAX_CLIENTS ) { + + // DHM - Nerve + if ( g_gametype.integer >= GT_WOLF ) { + // Show medics a syringe if they can revive someone + if ( traceEnt->client && traceEnt->client->sess.sessionTeam == ent->client->sess.sessionTeam + && ps->stats[ STAT_PLAYER_CLASS ] == PC_MEDIC + && traceEnt->client->ps.pm_type == PM_DEAD + && !( traceEnt->client->ps.pm_flags & PMF_LIMBO ) ) { + hintDist = 48; // JPW NERVE matches weapon_syringe in g_weapon.c + hintType = HINT_REVIVE; + } + } + // dhm - Nerve + + } + // + // OTHER ENTITIES + // + else { + checkEnt = traceEnt; + + // check invisible_users first since you don't want to draw a hint based + // on that ent, but rather on what they are targeting. + // so find the target and set checkEnt to that to show the proper hint. + if ( traceEnt->s.eType == ET_GENERAL ) { + + // ignore trigger_aidoor. can't just not trace for triggers, since I need invisible_users... + // damn, I would like to ignore some of these triggers though. + + if ( !Q_stricmp( traceEnt->classname, "trigger_aidoor" ) ) { + return; + } + + if ( !Q_stricmp( traceEnt->classname, "func_invisible_user" ) ) { + indirectHit = qtrue; + + // DHM - Nerve :: Put this back in only in multiplayer + if ( g_gametype.integer >= GT_WOLF && traceEnt->s.dmgFlags ) { // hint icon specified in entity + hintType = traceEnt->s.dmgFlags; + hintDist = CH_ACTIVATE_DIST; + checkEnt = 0; + } else { // use target for hint icon + checkEnt = G_Find( NULL, FOFS( targetname ), traceEnt->target ); + if ( !checkEnt ) { // no target found + hintType = HINT_BAD_USER; + hintDist = CH_MAX_DIST_ZOOM; // show this one from super far for debugging + } + } + } + } + + + if ( checkEnt ) { + if ( checkEnt->s.eType == ET_GENERAL || checkEnt->s.eType == ET_MG42_BARREL ) { + + // this is effectively an 'exit' brush. they should be created with: + // + // classname = 'ai_trigger' + // ainame = 'player' + // target = 'endmap' + // + if ( !Q_stricmp( traceEnt->classname, "ai_trigger" ) ) { + if ( ( !Q_stricmp( traceEnt->aiName, "player" ) ) && + ( !Q_stricmp( traceEnt->target, "endmap" ) ) ) { + hintDist = CH_EXIT_DIST; + hintType = HINT_EXIT; + + // show distance in the cursorhint bar + hintVal = (int)dist; // range for this hint is 256, so it happens to translate nicely + } + } + + if ( ( // general mg42 hint conditions + !Q_stricmp( traceEnt->classname, "misc_mg42" ) ) && + ( ps->weapon != WP_SNIPERRIFLE ) && // JPW NERVE no hint if you're scoped in sniperwise + // ATVI #470 + // mount hint conditions only, if busted MG42, no check + ( + ( traceEnt->health < 255 ) || + ( + !( ent->client->ps.pm_flags & PMF_DUCKED ) && + ( traceEnt->r.currentOrigin[2] - ent->r.currentOrigin[2] < 40 ) && + ( traceEnt->r.currentOrigin[2] - ent->r.currentOrigin[2] > 0 ) && + !infront( traceEnt, ent ) + ) + ) + ) { + + hintDist = CH_ACTIVATE_DIST; + hintType = HINT_MG42; + +// JPW NERVE -- busted MG42 shouldn't show a use hint by default + if ( traceEnt->health <= 0 ) { + hintDist = 0; + hintType = HINT_FORCENONE; + ps->serverCursorHint = HINT_FORCENONE; + } +// jpw + + // DHM - Nerve :: Engineers can repair turrets + if ( g_gametype.integer >= GT_WOLF ) { + if ( ps->stats[ STAT_PLAYER_CLASS ] == PC_ENGINEER ) { + if ( !traceEnt->takedamage ) { + hintType = HINT_BUILD; + hintDist = CH_BREAKABLE_DIST; + hintVal = traceEnt->health; + if ( hintVal > 255 ) { + hintVal = 255; + } + } + } + } + // dhm - end + } + } else if ( checkEnt->s.eType == ET_EXPLOSIVE ) { +// if(traceEnt->s.dmgFlags) { // override flag // hint icon specified in entity, this overrides type +// hintType = traceEnt->s.dmgFlags; +// } else { + if ( ( checkEnt->health > 0 ) || ( checkEnt->spawnflags & 64 ) ) { // JPW NERVE they are now if it's dynamite // 0 health explosives are not breakable + hintDist = CH_BREAKABLE_DIST; + hintType = HINT_BREAKABLE; + + // DHM - Nerve :: Show different hint if it can only be destroyed with dynamite + if ( g_gametype.integer >= GT_WOLF && ( checkEnt->spawnflags & 64 ) ) { +// JPW NERVE only show hint for players who can blow it up + vec3_t mins,maxs,range = { 40, 40, 52 }; + int i,num; + //int defendingTeam=0; // TTimo unused + int touch[MAX_GENTITIES]; + gentity_t *hit = NULL; + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + for ( i = 0 ; i < num ; i++ ) { + hit = &g_entities[touch[i]]; + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + if ( !strcmp( hit->classname,"trigger_objective_info" ) ) { + if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) { + continue; + } +// we're in a trigger_objective_info field with at least one owner, so use this one and bail + break; + } + } + if ( ( hit ) && + ( ( ( ent->client->sess.sessionTeam == TEAM_RED ) && ( hit->spawnflags & ALLIED_OBJECTIVE ) ) || + ( ( ent->client->sess.sessionTeam == TEAM_BLUE ) && ( hit->spawnflags & AXIS_OBJECTIVE ) ) ) + ) { + hintDist = CH_BREAKABLE_DIST * 2; + hintType = HINT_BREAKABLE_DYNAMITE; + } else { + hintDist = 0; + hintType = ps->serverCursorHint = HINT_FORCENONE; + hintVal = ps->serverCursorHintVal = 0; + return; + } +// jpw + } + // dhm - end + + hintVal = checkEnt->health; // also send health to client for visualization + } +// } + } else if ( checkEnt->s.eType == ET_ALARMBOX ) { + if ( checkEnt->health > 0 ) { +// hintDist = CH_BREAKABLE_DIST; + hintType = HINT_ACTIVATE; + } + } else if ( checkEnt->s.eType == ET_ITEM ) { + gitem_t *it; + it = &bg_itemlist[checkEnt->item - bg_itemlist]; + + hintDist = CH_ACTIVATE_DIST; + + switch ( it->giType ) { + case IT_HEALTH: + hintType = HINT_HEALTH; + break; + case IT_TREASURE: + hintType = HINT_TREASURE; + break; + case IT_CLIPBOARD: + hintType = HINT_CLIPBOARD; + break; + case IT_WEAPON: +// JPW NERVE changed this to be more specific + if ( it->giTag != WP_LUGER && it->giTag != WP_COLT ) { + if ( ps->stats[STAT_PLAYER_CLASS] == PC_SOLDIER ) { // soldier can pick up all weapons + hintType = HINT_WEAPON; + break; + } + if ( ps->stats[STAT_PLAYER_CLASS] != PC_LT ) { // medics & engrs can't pick up squat + break; + } + if ( it->giTag == WP_THOMPSON || it->giTag == WP_MP40 || it->giTag == WP_STEN ) { + hintType = HINT_WEAPON; + } + } +// jpw + break; + case IT_AMMO: + hintType = HINT_AMMO; + break; + case IT_ARMOR: + hintType = HINT_ARMOR; + break; + case IT_POWERUP: + hintType = HINT_POWERUP; + break; + case IT_HOLDABLE: + hintType = HINT_HOLDABLE; + break; + case IT_KEY: + hintType = HINT_INVENTORY; + break; + case IT_TEAM: + if ( !Q_stricmp( traceEnt->classname, "team_CTF_redflag" ) && ent->client->sess.sessionTeam == TEAM_BLUE ) { + hintType = HINT_POWERUP; + } else if ( !Q_stricmp( traceEnt->classname, "team_CTF_blueflag" ) && ent->client->sess.sessionTeam == TEAM_RED ) { + hintType = HINT_POWERUP; + } + break; + case IT_BAD: + default: + break; + } + } else if ( checkEnt->s.eType == ET_MOVER ) { + if ( !Q_stricmp( checkEnt->classname, "func_door_rotating" ) ) { + if ( checkEnt->moverState == MOVER_POS1ROTATE ) { // stationary/closed + hintDist = CH_DOOR_DIST; + hintType = HINT_DOOR_ROTATING; + + if ( checkEnt->key == -1 ) { // locked + // hintType = HINT_DOOR_ROTATING_LOCKED; + } + } + } else if ( !Q_stricmp( checkEnt->classname, "func_door" ) ) { + if ( checkEnt->moverState == MOVER_POS1 ) { // stationary/closed + hintDist = CH_DOOR_DIST; + hintType = HINT_DOOR; + + if ( checkEnt->key == -1 ) { // locked + // hintType = HINT_DOOR_LOCKED; + } + } + } else if ( !Q_stricmp( checkEnt->classname, "func_button" ) ) { + hintDist = CH_ACTIVATE_DIST; + hintType = HINT_BUTTON; + }/* + else if(checkEnt->s.dmgFlags == HINT_CHAIR) { + hintDist = CH_ACTIVATE_DIST; + hintType = HINT_CHAIR; + }*/ + } + + // DHM - Nerve :: Handle wolf multiplayer hints + if ( g_gametype.integer >= GT_WOLF ) { + + if ( checkEnt->s.eType == ET_MISSILE ) { + if ( ps->stats[ STAT_PLAYER_CLASS ] == PC_ENGINEER ) { + hintDist = CH_BREAKABLE_DIST; + hintType = HINT_DISARM; + hintVal = checkEnt->health; // also send health to client for visualization + if ( hintVal > 255 ) { + hintVal = 255; + } + } + } + + } + // dhm - end + + // hint icon specified in entity (and proper contact was made, so hintType was set) + // first try the checkent... + if ( checkEnt->s.dmgFlags && hintType ) { + hintType = checkEnt->s.dmgFlags; + } + } + + // then the traceent + if ( traceEnt->s.dmgFlags && hintType ) { + hintType = traceEnt->s.dmgFlags; + } + + } + + if ( zooming ) { + + hintDist = CH_MAX_DIST_ZOOM; + + // zooming can eat a lot of potential hints + switch ( hintType ) { + + // allow while zooming + case HINT_PLAYER: + case HINT_TREASURE: + case HINT_LADDER: + case HINT_EXIT: + case HINT_PLYR_FRIEND: + case HINT_PLYR_NEUTRAL: + case HINT_PLYR_ENEMY: + case HINT_PLYR_UNKNOWN: + break; + + default: + return; + } + } + + if ( dist <= hintDist ) { + ps->serverCursorHint = hintType; + ps->serverCursorHintVal = hintVal; + } + + +// Com_Printf("hint: %d\n", ps->serverCursorHint); +} + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. +Entity teams are used for item groups and multi-entity mover groups. + +All but the first will have the FL_TEAMSLAVE flag set and teammaster field set +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams( void ) { + gentity_t *e, *e2; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for ( i = 1, e = g_entities + i ; i < level.num_entities ; i++,e++ ) { + if ( !e->inuse ) { + continue; + } + + if ( !e->team ) { + continue; + } + + if ( e->flags & FL_TEAMSLAVE ) { + continue; + } + + if ( !Q_stricmp( e->classname, "func_tramcar" ) ) { + if ( e->spawnflags & 8 ) { // leader + e->teammaster = e; + } else + { + continue; + } + } + + c++; + c2++; + for ( j = i + 1, e2 = e + 1 ; j < level.num_entities ; j++,e2++ ) + { + if ( !e2->inuse ) { + continue; + } + if ( !e2->team ) { + continue; + } + if ( e2->flags & FL_TEAMSLAVE ) { + continue; + } + if ( !strcmp( e->team, e2->team ) ) { + c2++; + e2->teamchain = e->teamchain; + e->teamchain = e2; + e2->teammaster = e; +// e2->key = e->key; // (SA) I can't set the key here since the master door hasn't finished spawning yet and therefore has a key of -1 + e2->flags |= FL_TEAMSLAVE; + + if ( !Q_stricmp( e2->classname, "func_tramcar" ) ) { + trap_UnlinkEntity( e2 ); + } + + // make sure that targets only point at the master + if ( e2->targetname ) { + e->targetname = e2->targetname; + + // Rafael + // note to self: added this because of problems + // pertaining to keys and double doors + if ( Q_stricmp( e2->classname, "func_door_rotating" ) ) { + e2->targetname = NULL; + } + } + } + } + } + + if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) { + G_Printf( "%i teams with %i entities\n", c, c2 ); + } +} + + +/* +============== +G_RemapTeamShaders +============== +*/ +void G_RemapTeamShaders() { +#ifdef MISSIONPACK + char string[1024]; + float f = level.time * 0.001; + Com_sprintf( string, sizeof( string ), "team_icon/%s_red", g_redteam.string ); + AddRemap( "textures/ctf2/redteam01", string, f ); + AddRemap( "textures/ctf2/redteam02", string, f ); + Com_sprintf( string, sizeof( string ), "team_icon/%s_blue", g_blueteam.string ); + AddRemap( "textures/ctf2/blueteam01", string, f ); + AddRemap( "textures/ctf2/blueteam02", string, f ); + trap_SetConfigstring( CS_SHADERSTATE, BuildShaderStateConfig() ); +#endif +} + + +/* +================= +G_RegisterCvars +================= +*/ +void G_RegisterCvars( void ) { + int i; + cvarTable_t *cv; + qboolean remapped = qfalse; + + for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + if ( cv->vmCvar ) { + cv->modificationCount = cv->vmCvar->modificationCount; + } + + if ( cv->teamShader ) { + remapped = qtrue; + } + } + + if ( remapped ) { + G_RemapTeamShaders(); + } + + // check some things + // DHM - Gametype is currently restricted to supported types only + if ( g_gametype.integer < GT_WOLF || g_gametype.integer > GT_WOLF_CPH ) { // JPW NERVE WOLF_CPH now highest type + G_Printf( "g_gametype %i is out of range, defaulting to GT_WOLF(5)\n", g_gametype.integer ); + trap_Cvar_Set( "g_gametype", "5" ); + trap_Cvar_Update( &g_gametype ); + } + + // Rafael gameskill + if ( g_gameskill.integer < GSKILL_EASY || g_gameskill.integer > GSKILL_VERYHARD ) { + G_Printf( "g_gameskill %i is out of range, default to medium\n", g_gameskill.integer ); + trap_Cvar_Set( "g_gameskill", "3" ); // default to medium + } + + bg_pmove_gameskill_integer = g_gameskill.integer; + // done + + level.warmupModificationCount = g_warmup.modificationCount; +} + +/* +================= +G_UpdateCvars +================= +*/ +void G_UpdateCvars( void ) { + int i; + cvarTable_t *cv; + qboolean remapped = qfalse; + + for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) { + if ( cv->vmCvar ) { + trap_Cvar_Update( cv->vmCvar ); + + if ( cv->modificationCount != cv->vmCvar->modificationCount ) { + cv->modificationCount = cv->vmCvar->modificationCount; + + if ( cv->trackChange ) { + trap_SendServerCommand( -1, va( "print \"Server:[lof] %s [lon]changed to[lof] %s\n\"", + cv->cvarName, cv->vmCvar->string ) ); + } + + if ( cv->teamShader ) { + remapped = qtrue; + } + } + } + } + + if ( remapped ) { + G_RemapTeamShaders(); + } +} + + + +/* +============== +G_SpawnScriptCamera + create the game entity that's used for camera<->script communication and portal location for camera view +============== +*/ +void G_SpawnScriptCamera( void ) { + if ( g_camEnt ) { + G_FreeEntity( g_camEnt ); + } + + g_camEnt = G_Spawn(); + + g_camEnt->scriptName = "scriptcamera"; + + g_camEnt->s.eType = ET_CAMERA; + g_camEnt->s.apos.trType = TR_STATIONARY; + g_camEnt->s.apos.trTime = 0; + g_camEnt->s.apos.trDuration = 0; + VectorCopy( g_camEnt->s.angles, g_camEnt->s.apos.trBase ); + VectorClear( g_camEnt->s.apos.trDelta ); + + g_camEnt->s.frame = 0; + + g_camEnt->r.svFlags |= SVF_NOCLIENT; // only broadcast when in use + + if ( g_camEnt->s.number >= MAX_CLIENTS && g_camEnt->scriptName ) { + G_Script_ScriptParse( g_camEnt ); + G_Script_ScriptEvent( g_camEnt, "spawn", "" ); + } + +} + +/* +============ +G_InitGame + +============ +*/ +void G_InitGame( int levelTime, int randomSeed, int restart ) { + int i; + char cs[MAX_INFO_STRING]; + + if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) { + G_Printf( "------- Game Initialization -------\n" ); + G_Printf( "gamename: %s\n", GAMEVERSION ); + G_Printf( "gamedate: %s\n", __DATE__ ); + } + + srand( randomSeed ); + + G_RegisterCvars(); + + // Xian enforcemaxlives stuff + /* + we need to clear the list even if enforce maxlives is not active + in cas ethe g_maxlives was changed, and a map_restart happened + */ + ClearMaxLivesGUID(); + + // just for verbosity + if ( g_enforcemaxlives.integer && ( g_maxlives.integer > 0 || g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0 ) ) { + G_Printf( "EnforceMaxLives-Cleared GUID List\n" ); + } + + G_ProcessIPBans(); + + G_InitMemory(); + + // NERVE - SMF - intialize gamestate + if ( g_gamestate.integer == GS_INITIALIZE ) { + if ( g_noTeamSwitching.integer ) { + trap_Cvar_Set( "gamestate", va( "%i", GS_WAITING_FOR_PLAYERS ) ); + } else { + trap_Cvar_Set( "gamestate", va( "%i", GS_WARMUP ) ); + } + } + + // set some level globals + memset( &level, 0, sizeof( level ) ); + level.time = levelTime; + level.startTime = levelTime; + + level.snd_fry = G_SoundIndex( "sound/player/fry.wav" ); // FIXME standing in lava / slime + level.bulletRicochetSound = G_SoundIndex( "bulletRicochet" ); + level.snipersound = G_SoundIndex( "sound/weapons/mauser/mauserf1.wav" ); + level.knifeSound[0] = G_SoundIndex( "sound/weapons/knife/knife_hitwall1.wav" ); + + // RF, init the anim scripting + level.animScriptData.soundIndex = G_SoundIndex; + level.animScriptData.playSound = G_AnimScriptSound; + + if ( g_gametype.integer != GT_SINGLE_PLAYER && g_log.string[0] ) { + if ( g_logSync.integer ) { + trap_FS_FOpenFile( g_log.string, &level.logFile, FS_APPEND_SYNC ); + } else { + trap_FS_FOpenFile( g_log.string, &level.logFile, FS_APPEND ); + } + if ( !level.logFile ) { + G_Printf( "WARNING: Couldn't open logfile: %s\n", g_log.string ); + } else { + char serverinfo[MAX_INFO_STRING]; + + trap_GetServerinfo( serverinfo, sizeof( serverinfo ) ); + + G_LogPrintf( "------------------------------------------------------------\n" ); + G_LogPrintf( "InitGame: %s\n", serverinfo ); + } + } else { + if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) { + G_Printf( "Not logging to disk.\n" ); + } + } + + G_InitWorldSession(); + + // DHM - Nerve :: Clear out spawn target config strings + if ( g_gametype.integer >= GT_WOLF ) { + trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) ); + Info_SetValueForKey( cs, "numspawntargets", "0" ); + trap_SetConfigstring( CS_MULTI_INFO, cs ); + + for ( i = CS_MULTI_SPAWNTARGETS; i < CS_MULTI_SPAWNTARGETS + MAX_MULTI_SPAWNTARGETS; i++ ) { + trap_SetConfigstring( i, "" ); + } + } + + // initialize all entities for this game + memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[0] ) ); + level.gentities = g_entities; + + // initialize all clients for this game + level.maxclients = g_maxclients.integer; + memset( g_clients, 0, MAX_CLIENTS * sizeof( g_clients[0] ) ); + level.clients = g_clients; + + // set client fields on player ents + for ( i = 0 ; i < level.maxclients ; i++ ) { + g_entities[i].client = level.clients + i; + } + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + level.num_entities = MAX_CLIENTS; + + // let the server system know where the entites are + trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), + &level.clients[0].ps, sizeof( level.clients[0] ) ); + + // load level script + G_Script_ScriptLoad(); + + // reserve some spots for dead player bodies + InitBodyQue(); + + ClearRegisteredItems(); + + // parse the key/value pairs and spawn gentities + G_SpawnEntitiesFromString(); + + // create the camera entity that will communicate with the scripts + G_SpawnScriptCamera(); + + // general initialization + G_FindTeams(); + + // make sure we have flags for CTF, etc + if ( g_gametype.integer >= GT_TEAM ) { + G_CheckTeamItems(); + } + + SaveRegisteredItems(); + + if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) { + G_Printf( "-----------------------------------\n" ); + } + + if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + BotAISetup( restart ); + BotAILoadMap( restart ); + G_InitBots( restart ); + } + + G_RemapTeamShaders(); +} + + + +/* +================= +G_ShutdownGame +================= +*/ +void G_ShutdownGame( int restart ) { + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + G_Printf( "==== ShutdownGame ====\n" ); + } + + if ( level.logFile ) { + G_LogPrintf( "ShutdownGame:\n" ); + G_LogPrintf( "------------------------------------------------------------\n" ); + trap_FS_FCloseFile( level.logFile ); + } + + // Ridah, shutdown the Botlib, so weapons and things get reset upon doing a "map xxx" command + if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + int i; + + // Ridah, kill AI cast's + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + if ( g_entities[i].r.svFlags & SVF_CASTAI ) { + trap_DropClient( i, "Drop Cast AI" ); + } + } + // done. + } + // done. + + // write all the client session data so we can get it back + G_WriteSessionData(); + + + if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + BotAIShutdown( restart ); + } +} + + + +//=================================================================== + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, error ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); + va_end( argptr ); + + G_Error( "%s", text ); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + G_Printf( "%s", text ); +} + +#endif + +/* +======================================================================== + +PLAYER COUNTING / SCORE SORTING + +======================================================================== +*/ + +/* +============= +AddTournamentPlayer + +If there are less than two tournament players, put a +spectator in the game and restart +============= +*/ +void AddTournamentPlayer( void ) { + int i; + gclient_t *client; + gclient_t *nextInLine; + + if ( level.numPlayingClients >= 2 ) { + return; + } + + // never change during intermission + if ( level.intermissiontime ) { + return; + } + + nextInLine = NULL; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + client = &level.clients[i]; + if ( client->pers.connected != CON_CONNECTED ) { + continue; + } + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { + continue; + } + // never select the dedicated follow or scoreboard clients + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD || + client->sess.spectatorClient < 0 ) { + continue; + } + + if ( !nextInLine || client->sess.spectatorTime < nextInLine->sess.spectatorTime ) { + nextInLine = client; + } + } + + if ( !nextInLine ) { + return; + } + + level.warmupTime = -1; + + // set them to free-for-all team + SetTeam( &g_entities[ nextInLine - level.clients ], "f" ); +} + +/* +======================= +RemoveTournamentLoser + +Make the loser a spectator at the back of the line +======================= +*/ +void RemoveTournamentLoser( void ) { + int clientNum; + + if ( level.numPlayingClients != 2 ) { + return; + } + + clientNum = level.sortedClients[1]; + + if ( level.clients[ clientNum ].pers.connected != CON_CONNECTED ) { + return; + } + + // make them a spectator + SetTeam( &g_entities[ clientNum ], "s" ); +} + + +/* +======================= +AdjustTournamentScores + +======================= +*/ +void AdjustTournamentScores( void ) { + int clientNum; + + clientNum = level.sortedClients[0]; + if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) { + level.clients[ clientNum ].sess.wins++; + ClientUserinfoChanged( clientNum ); + } + + clientNum = level.sortedClients[1]; + if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) { + level.clients[ clientNum ].sess.losses++; + ClientUserinfoChanged( clientNum ); + } + +} + +/* +============= +SortRanks + +============= +*/ +int QDECL SortRanks( const void *a, const void *b ) { + gclient_t *ca, *cb; + + ca = &level.clients[*(int *)a]; + cb = &level.clients[*(int *)b]; + + // sort special clients last + if ( ca->sess.spectatorState == SPECTATOR_SCOREBOARD || ca->sess.spectatorClient < 0 ) { + return 1; + } + if ( cb->sess.spectatorState == SPECTATOR_SCOREBOARD || cb->sess.spectatorClient < 0 ) { + return -1; + } + + // then connecting clients + if ( ca->pers.connected == CON_CONNECTING ) { + return 1; + } + if ( cb->pers.connected == CON_CONNECTING ) { + return -1; + } + + + // then spectators + if ( ca->sess.sessionTeam == TEAM_SPECTATOR && cb->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( ca->sess.spectatorTime < cb->sess.spectatorTime ) { + return -1; + } + if ( ca->sess.spectatorTime > cb->sess.spectatorTime ) { + return 1; + } + return 0; + } + if ( ca->sess.sessionTeam == TEAM_SPECTATOR ) { + return 1; + } + if ( cb->sess.sessionTeam == TEAM_SPECTATOR ) { + return -1; + } + + // then sort by score + if ( ca->ps.persistant[PERS_SCORE] + > cb->ps.persistant[PERS_SCORE] ) { + return -1; + } + if ( ca->ps.persistant[PERS_SCORE] + < cb->ps.persistant[PERS_SCORE] ) { + return 1; + } + return 0; +} + +/* +============ +CalculateRanks + +Recalculates the score ranks of all players +This will be called on every client connect, begin, disconnect, death, +and team change. +============ +*/ +void CalculateRanks( void ) { + int i; + int rank; + int score; + int newScore; + gclient_t *cl; + + level.follow1 = -1; + level.follow2 = -1; + level.numConnectedClients = 0; + level.numNonSpectatorClients = 0; + level.numPlayingClients = 0; + level.numVotingClients = 0; // don't count bots + + level.numFinalDead[0] = 0; // NERVE - SMF + level.numFinalDead[1] = 0; // NERVE - SMF + + for ( i = 0; i < TEAM_NUM_TEAMS; i++ ) { + level.numteamVotingClients[i] = 0; + } + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected != CON_DISCONNECTED ) { + level.sortedClients[level.numConnectedClients] = i; + level.numConnectedClients++; + + if ( level.clients[i].sess.sessionTeam != TEAM_SPECTATOR ) { + level.numNonSpectatorClients++; + + // decide if this should be auto-followed + if ( level.clients[i].pers.connected == CON_CONNECTED ) { + level.numPlayingClients++; + if ( !( g_entities[i].r.svFlags & SVF_BOT ) ) { + level.numVotingClients++; + + if ( level.clients[i].sess.sessionTeam == TEAM_RED ) { + // NERVE - SMF + if ( level.clients[i].ps.persistant[PERS_RESPAWNS_LEFT] == 0 + && g_entities[i].health <= 0 ) { + level.numFinalDead[0]++; + } + + level.numteamVotingClients[0]++; + } else if ( level.clients[i].sess.sessionTeam == TEAM_BLUE ) { + // NERVE - SMF + if ( level.clients[i].ps.persistant[PERS_RESPAWNS_LEFT] == 0 + && g_entities[i].health <= 0 ) { + level.numFinalDead[1]++; + } + + level.numteamVotingClients[1]++; + } + } + if ( level.follow1 == -1 ) { + level.follow1 = i; + } else if ( level.follow2 == -1 ) { + level.follow2 = i; + } + } + } + } + } + + qsort( level.sortedClients, level.numConnectedClients, + sizeof( level.sortedClients[0] ), SortRanks ); + + // set the rank value for all clients that are connected and not spectators + if ( g_gametype.integer >= GT_TEAM ) { + // in team games, rank is just the order of the teams, 0=red, 1=blue, 2=tied + for ( i = 0; i < level.numConnectedClients; i++ ) { + cl = &level.clients[ level.sortedClients[i] ]; + if ( level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE] ) { + cl->ps.persistant[PERS_RANK] = 2; + } else if ( level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE] ) { + cl->ps.persistant[PERS_RANK] = 0; + } else { + cl->ps.persistant[PERS_RANK] = 1; + } + } + } else { + rank = -1; + score = 0; + for ( i = 0; i < level.numPlayingClients; i++ ) { + cl = &level.clients[ level.sortedClients[i] ]; + newScore = cl->ps.persistant[PERS_SCORE]; + if ( i == 0 || newScore != score ) { + rank = i; + // assume we aren't tied until the next client is checked + level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank; + } else { + // we are tied with the previous client + level.clients[ level.sortedClients[i - 1] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; + level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; + } + score = newScore; + if ( g_gametype.integer == GT_SINGLE_PLAYER && level.numPlayingClients == 1 ) { + level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; + } + } + } + + // set the CS_SCORES1/2 configstrings, which will be visible to everyone + if ( g_gametype.integer >= GT_TEAM ) { + trap_SetConfigstring( CS_SCORES1, va( "%i", level.teamScores[TEAM_RED] ) ); + trap_SetConfigstring( CS_SCORES2, va( "%i", level.teamScores[TEAM_BLUE] ) ); + } else { + if ( level.numConnectedClients == 0 ) { + trap_SetConfigstring( CS_SCORES1, va( "%i", SCORE_NOT_PRESENT ) ); + trap_SetConfigstring( CS_SCORES2, va( "%i", SCORE_NOT_PRESENT ) ); + } else if ( level.numConnectedClients == 1 ) { + trap_SetConfigstring( CS_SCORES1, va( "%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) ); + trap_SetConfigstring( CS_SCORES2, va( "%i", SCORE_NOT_PRESENT ) ); + } else { + trap_SetConfigstring( CS_SCORES1, va( "%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) ); + trap_SetConfigstring( CS_SCORES2, va( "%i", level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] ) ); + } + } + + // see if it is time to end the level + CheckExitRules(); + + // if we are at the intermission, send the new info to everyone + if ( level.intermissiontime ) { + SendScoreboardMessageToAllClients(); + } +} + + +/* +======================================================================== + +MAP CHANGING + +======================================================================== +*/ + +/* +======================== +SendScoreboardMessageToAllClients + +Do this at BeginIntermission time and whenever ranks are recalculated +due to enters/exits/forced team changes +======================== +*/ +void SendScoreboardMessageToAllClients( void ) { + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[ i ].pers.connected == CON_CONNECTED ) { + DeathmatchScoreboardMessage( g_entities + i ); + } + } +} + +/* +======================== +MoveClientToIntermission + +When the intermission starts, this will be called for all players. +If a new client connects, this will be called after the spawn function. +======================== +*/ +void MoveClientToIntermission( gentity_t *ent ) { + // take out of follow mode if needed + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + StopFollowing( ent ); + } + + // move to the spot + VectorCopy( level.intermission_origin, ent->s.origin ); + VectorCopy( level.intermission_origin, ent->client->ps.origin ); + VectorCopy( level.intermission_angle, ent->client->ps.viewangles ); + ent->client->ps.pm_type = PM_INTERMISSION; + + // clean up powerup info + // memset( ent->client->ps.powerups, 0, sizeof(ent->client->ps.powerups) ); + + ent->client->ps.eFlags = 0; + ent->s.eFlags = 0; + ent->s.eType = ET_GENERAL; + ent->s.modelindex = 0; + ent->s.loopSound = 0; + ent->s.event = 0; + ent->s.events[0] = ent->s.events[1] = ent->s.events[2] = ent->s.events[3] = 0; // DHM - Nerve + ent->r.contents = 0; +} + +/* +================== +FindIntermissionPoint + +This is also used for spectator spawns +================== +*/ +void FindIntermissionPoint( void ) { + gentity_t *ent, *target; + vec3_t dir; + char cs[MAX_STRING_CHARS]; // DHM - Nerve + char *buf; // DHM - Nerve + int winner; // DHM - Nerve + + if ( g_gametype.integer >= GT_WOLF ) { + ent = NULL; + + // NERVE - SMF - if the match hasn't ended yet, and we're just a spectator + if ( !level.intermissiontime ) { + + // try to find the intermission spawnpoint with no team flags set + ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" ); + + for ( ; ent; ent = G_Find( ent, FOFS( classname ), "info_player_intermission" ) ) { + if ( !ent->spawnflags ) { + break; + } + } + } + + trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) ); + buf = Info_ValueForKey( cs, "winner" ); + winner = atoi( buf ); + + // Change from scripting value for winner (0==AXIS, 1==ALLIES) to spawnflag value + if ( winner == 0 ) { + winner = 1; + } else { + winner = 2; + } + + if ( !ent ) { + ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" ); + + if ( ent && !( ent->spawnflags & winner ) ) { + ent = G_Find( ent, FOFS( classname ), "info_player_intermission" ); + } + } + } else { + // find the intermission spot + ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" ); + } + + if ( !ent ) { // the map creator forgot to put in an intermission point... + SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle ); + } else { + VectorCopy( ent->s.origin, level.intermission_origin ); + VectorCopy( ent->s.angles, level.intermission_angle ); + // if it has a target, look towards it + if ( ent->target ) { + target = G_PickTarget( ent->target ); + if ( target ) { + VectorSubtract( target->s.origin, level.intermission_origin, dir ); + vectoangles( dir, level.intermission_angle ); + } + } + } + +} + +/* +================== +BeginIntermission +================== +*/ +void BeginIntermission( void ) { + int i; + gentity_t *client; + + if ( level.intermissiontime ) { + return; // already active + } + + // if in tournement mode, change the wins / losses + if ( g_gametype.integer == GT_TOURNAMENT ) { + AdjustTournamentScores(); + } + + level.intermissiontime = level.time; + FindIntermissionPoint(); + + // move all clients to the intermission point + for ( i = 0 ; i < level.maxclients ; i++ ) { + client = g_entities + i; + if ( !client->inuse ) { + continue; + } + // respawn if dead + if ( g_gametype.integer < GT_WOLF && client->health <= 0 ) { + respawn( client ); + } + MoveClientToIntermission( client ); + } + + // send the current scoring to all clients + SendScoreboardMessageToAllClients(); + +} + + +/* +============= +ExitLevel + +When the intermission has been exited, the server is either killed +or moved to a new level based on the "nextmap" cvar + +============= +*/ +void ExitLevel( void ) { + int i; + gclient_t *cl; + + // if we are running a tournement map, kick the loser to spectator status, + // which will automatically grab the next spectator and restart + if ( g_gametype.integer == GT_TOURNAMENT ) { + if ( !level.restarted ) { + RemoveTournamentLoser(); + trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" ); + level.restarted = qtrue; + level.changemap = NULL; + level.intermissiontime = 0; + } + return; + } + + trap_SendConsoleCommand( EXEC_APPEND, "vstr nextmap\n" ); + level.changemap = NULL; + level.intermissiontime = 0; + + // reset all the scores so we don't enter the intermission again + level.teamScores[TEAM_RED] = 0; + level.teamScores[TEAM_BLUE] = 0; + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + cl->ps.persistant[PERS_SCORE] = 0; + } + + // we need to do this here before chaning to CON_CONNECTING + G_WriteSessionData(); + + // change all client states to connecting, so the early players into the + // next level will know the others aren't done reconnecting + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + + if ( level.clients[i].pers.connected == CON_CONNECTED ) { + level.clients[i].pers.connected = CON_CONNECTING; + } + } + + G_LogPrintf( "ExitLevel: executed\n" ); +} + +/* +================= +G_LogPrintf + +Print to the logfile with a time stamp if it is open +================= +*/ +void QDECL G_LogPrintf( const char *fmt, ... ) { + va_list argptr; + char string[1024]; + int min, tens, sec; + + sec = level.time / 1000; + + min = sec / 60; + sec -= min * 60; + tens = sec / 10; + sec -= tens * 10; + + Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); + + va_start( argptr, fmt ); + Q_vsnprintf( string + 7, sizeof( string ) - 7, fmt, argptr ); + va_end( argptr ); + + if ( g_dedicated.integer ) { + G_Printf( "%s", string + 7 ); + } + + if ( !level.logFile ) { + return; + } + + trap_FS_Write( string, strlen( string ), level.logFile ); +} + +/* +================ +LogExit + +Append information about this game to the log file +================ +*/ +void LogExit( const char *string ) { + int i, numSorted; + gclient_t *cl; + + // NERVE - SMF - do not allow LogExit to be called in non-playing gamestate + if ( g_gamestate.integer != GS_PLAYING ) { + return; + } + + G_LogPrintf( "Exit: %s\n", string ); + + level.intermissionQueued = level.time; + + // this will keep the clients from playing any voice sounds + // that will get cut off when the queued intermission starts + trap_SetConfigstring( CS_INTERMISSION, "1" ); + + // don't send more than 32 scores (FIXME?) + numSorted = level.numConnectedClients; + if ( numSorted > 32 ) { + numSorted = 32; + } + + if ( g_gametype.integer >= GT_TEAM ) { + G_LogPrintf( "red:%i blue:%i\n", + level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE] ); + } + + // NERVE - SMF - send gameCompleteStatus message to master servers + trap_SendConsoleCommand( EXEC_APPEND, "gameCompleteStatus\n" ); + + for ( i = 0 ; i < numSorted ; i++ ) { + int ping; + + cl = &level.clients[level.sortedClients[i]]; + + if ( cl->sess.sessionTeam == TEAM_SPECTATOR ) { + continue; + } + if ( cl->pers.connected == CON_CONNECTING ) { + continue; + } + + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + + G_LogPrintf( "score: %i ping: %i client: %i %s\n", + cl->ps.persistant[PERS_SCORE], ping, level.sortedClients[i], + cl->pers.netname ); + } + + // NERVE - SMF + if ( g_gametype.integer == GT_WOLF_STOPWATCH ) { + char cs[MAX_STRING_CHARS]; + int winner, defender; + + trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) ); + defender = atoi( Info_ValueForKey( cs, "defender" ) ); + + trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) ); + winner = atoi( Info_ValueForKey( cs, "winner" ) ); + + // NERVE - SMF + if ( !g_currentRound.integer ) { + if ( winner == defender ) { + // if the defenders won, use default timelimit + trap_Cvar_Set( "g_nextTimeLimit", va( "%f", g_timelimit.value ) ); + } else { + // use remaining time as next timer + trap_Cvar_Set( "g_nextTimeLimit", va( "%f", ( level.time - level.startTime ) / 60000.f ) ); + } + } else { + // reset timer + trap_Cvar_Set( "g_nextTimeLimit", "0" ); + } + + trap_Cvar_Set( "g_currentRound", va( "%i", !g_currentRound.integer ) ); + } + // -NERVE - SMF +} + + +/* +================= +CheckIntermissionExit + +The level will stay at the intermission for a minimum of 5 seconds +If all players wish to continue, the level will then exit. +If one or more players have not acknowledged the continue, the game will +wait 10 seconds before going on. +================= +*/ +void CheckIntermissionExit( void ) { + int ready, notReady; + int i; + gclient_t *cl; + int readyMask; + + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + return; + } + + // DHM - Nerve :: Flat 10 second timer until exit + if ( g_gametype.integer >= GT_WOLF ) { + if ( level.time < level.intermissiontime + 10000 ) { + return; + } + + ExitLevel(); + return; + } + // dhm - end + + // see which players are ready + ready = 0; + notReady = 0; + readyMask = 0; + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { + continue; + } + + if ( cl->readyToExit ) { + ready++; + if ( i < 16 ) { + readyMask |= 1 << i; + } + } else { + notReady++; + } + } + + // copy the readyMask to each player's stats so + // it can be displayed on the scoreboard + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + cl->ps.stats[STAT_CLIENTS_READY] = readyMask; + } + + // never exit in less than five seconds + if ( level.time < level.intermissiontime + 5000 ) { + return; + } + + // if nobody wants to go, clear timer + if ( !ready ) { + level.readyToExit = qfalse; + return; + } + + // if everyone wants to go, go now + if ( !notReady ) { + ExitLevel(); + return; + } + + // the first person to ready starts the ten second timeout + if ( !level.readyToExit ) { + level.readyToExit = qtrue; + level.exitTime = level.time; + } + + // if we have waited ten seconds since at least one player + // wanted to exit, go ahead + if ( level.time < level.exitTime + 10000 ) { + return; + } + + ExitLevel(); +} + +/* +============= +ScoreIsTied +============= +*/ +qboolean ScoreIsTied( void ) { + int a, b; + char cs[MAX_STRING_CHARS]; + char *buf; + + // DHM - Nerve :: GT_WOLF checks the current value of + if ( g_gametype.integer >= GT_WOLF ) { + trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) ); + + buf = Info_ValueForKey( cs, "winner" ); + a = atoi( buf ); + + return a == -1; + } + + if ( level.numPlayingClients < 2 ) { + return qfalse; + } + + if ( g_gametype.integer >= GT_TEAM ) { + return level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE]; + } + + a = level.clients[level.sortedClients[0]].ps.persistant[PERS_SCORE]; + b = level.clients[level.sortedClients[1]].ps.persistant[PERS_SCORE]; + + return a == b; +} + +qboolean G_ScriptAction_SetWinner( gentity_t *ent, char *params ); + +/* +================= +CheckExitRules + +There will be a delay between the time the exit is qualified for +and the time everyone is moved to the intermission spot, so you +can see the last frag. +================= +*/ +void CheckExitRules( void ) { + int i; + gclient_t *cl; + gentity_t *gm; + char cs[MAX_STRING_CHARS]; + char txt[5]; + int num; + + // if at the intermission, wait for all non-bots to + // signal ready, then go to next level + if ( level.intermissiontime ) { + CheckIntermissionExit(); + return; + } + + if ( level.intermissionQueued ) { + if ( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME || g_gametype.integer >= GT_WOLF ) { + level.intermissionQueued = 0; + BeginIntermission(); + } + return; + } + + if ( g_timelimit.value && !level.warmupTime ) { + if ( level.time - level.startTime >= g_timelimit.value * 60000 ) { + + // check for sudden death + if ( g_gametype.integer != GT_CTF && ScoreIsTied() ) { + // score is tied, so don't end the game + return; + } + + if ( g_gametype.integer >= GT_WOLF ) { + gm = G_Find( NULL, FOFS( scriptName ), "game_manager" ); +// JPW NERVE -- in CPH, check final capture/hold times and adjust winner + // 0 == axis, 1 == allied + if ( g_gametype.integer == GT_WOLF_CPH ) { + num = -1; + if ( level.capturetimes[TEAM_RED] > level.capturetimes[TEAM_BLUE] ) { + num = 0; + } + if ( level.capturetimes[TEAM_RED] < level.capturetimes[TEAM_BLUE] ) { + num = 1; + } + if ( num != -1 ) { + sprintf( txt,"%d",num ); + G_ScriptAction_SetWinner( NULL, txt ); + } + } +// jpw + if ( gm ) { + G_Script_ScriptEvent( gm, "trigger", "timelimit_hit" ); + } + } + + // NERVE - SMF - do not allow LogExit to be called in non-playing gamestate + // - This already happens in LogExit, but we need it for the print command + if ( g_gamestate.integer != GS_PLAYING ) { + return; + } + + trap_SendServerCommand( -1, "print \"Timelimit hit.\n\"" ); + LogExit( "Timelimit hit." ); + + return; + } + } + + if ( level.numPlayingClients < 2 ) { + return; + } + + if ( g_gametype.integer >= GT_WOLF && ( g_maxlives.integer > 0 || g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0 ) ) { + if ( level.numFinalDead[0] >= level.numteamVotingClients[0] && level.numteamVotingClients[0] > 0 ) { + trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) ); + Info_SetValueForKey( cs, "winner", "1" ); + trap_SetConfigstring( CS_MULTI_MAPWINNER, cs ); + LogExit( "Axis team eliminated." ); + } else if ( level.numFinalDead[1] >= level.numteamVotingClients[1] && level.numteamVotingClients[1] > 0 ) { + trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) ); + Info_SetValueForKey( cs, "winner", "0" ); + trap_SetConfigstring( CS_MULTI_MAPWINNER, cs ); + LogExit( "Allied team eliminated." ); + } + } + + if ( ( g_gametype.integer != GT_CTF && g_gametype.integer < GT_WOLF ) && g_fraglimit.integer ) { + if ( level.teamScores[TEAM_RED] >= g_fraglimit.integer ) { + trap_SendServerCommand( -1, "print \"Red hit the fraglimit.\n\"" ); + LogExit( "Fraglimit hit." ); + return; + } + + if ( level.teamScores[TEAM_BLUE] >= g_fraglimit.integer ) { + trap_SendServerCommand( -1, "print \"Blue hit the fraglimit.\n\"" ); + LogExit( "Fraglimit hit." ); + return; + } + + for ( i = 0 ; i < g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( cl->sess.sessionTeam != TEAM_FREE ) { + continue; + } + + if ( cl->ps.persistant[PERS_SCORE] >= g_fraglimit.integer ) { + LogExit( "Fraglimit hit." ); + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " hit the fraglimit.\n\"", + cl->pers.netname ) ); + return; + } + } + } + + if ( g_gametype.integer == GT_CTF && g_capturelimit.integer ) { + + if ( level.teamScores[TEAM_RED] >= g_capturelimit.integer ) { + trap_SendServerCommand( -1, "print \"Red hit the capturelimit.\n\"" ); + LogExit( "Capturelimit hit." ); + return; + } + + if ( level.teamScores[TEAM_BLUE] >= g_capturelimit.integer ) { + trap_SendServerCommand( -1, "print \"Blue hit the capturelimit.\n\"" ); + LogExit( "Capturelimit hit." ); + return; + } + } +} + + + +/* +======================================================================== + +FUNCTIONS CALLED EVERY FRAME + +======================================================================== +*/ + + +/* +============= +CheckTournement + +Once a frame, check for changes in tournement player state +============= +*/ +void CheckTournement( void ) { + // check because we run 3 game frames before calling Connect and/or ClientBegin + // for clients on a map_restart + if ( g_gametype.integer != GT_TOURNAMENT ) { + return; + } + if ( level.numPlayingClients == 0 ) { + return; + } + + // pull in a spectator if needed + if ( level.numPlayingClients < 2 ) { + AddTournamentPlayer(); + } + + // if we don't have two players, go back to "waiting for players" + if ( level.numPlayingClients != 2 ) { + if ( level.warmupTime != -1 ) { + level.warmupTime = -1; + trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) ); + G_LogPrintf( "Warmup:\n" ); + } + return; + } + + if ( level.warmupTime == 0 ) { + return; + } + + // if the warmup is changed at the console, restart it + if ( g_warmup.modificationCount != level.warmupModificationCount ) { + level.warmupModificationCount = g_warmup.modificationCount; + level.warmupTime = -1; + } + + // if all players have arrived, start the countdown + if ( level.warmupTime < 0 ) { + if ( level.numPlayingClients == 2 ) { + // fudge by -1 to account for extra delays + level.warmupTime = level.time + ( g_warmup.integer - 1 ) * 1000; + trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) ); + } + return; + } + + // if the warmup time has counted down, restart + if ( level.time > level.warmupTime ) { + level.warmupTime += 10000; + trap_Cvar_Set( "g_restarted", "1" ); + trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" ); + level.restarted = qtrue; + return; + } +} + +/* +============= +CheckWolfMP + +NERVE - SMF +============= +*/ +void CheckGameState() { + gamestate_t current_gs; + + current_gs = trap_Cvar_VariableIntegerValue( "gamestate" ); + + if ( level.intermissiontime && current_gs != GS_INTERMISSION ) { + trap_Cvar_Set( "gamestate", va( "%i", GS_INTERMISSION ) ); + return; + } + + if ( g_noTeamSwitching.integer && !trap_Cvar_VariableIntegerValue( "sv_serverRestarting" ) ) { + if ( current_gs != GS_WAITING_FOR_PLAYERS && level.numPlayingClients <= 1 && level.lastRestartTime + 1000 < level.time ) { + level.lastRestartTime = level.time; + trap_SendConsoleCommand( EXEC_APPEND, va( "map_restart 0 %i\n", GS_WAITING_FOR_PLAYERS ) ); + } + } + + if ( current_gs == GS_WAITING_FOR_PLAYERS && g_minGameClients.integer > 1 && + level.numPlayingClients >= g_minGameClients.integer && level.lastRestartTime + 1000 < level.time ) { + + level.lastRestartTime = level.time; + trap_SendConsoleCommand( EXEC_APPEND, va( "map_restart 0 %i\n", GS_WARMUP ) ); + } + + // if the warmup is changed at the console, restart it + if ( current_gs == GS_WARMUP_COUNTDOWN && g_warmup.modificationCount != level.warmupModificationCount ) { + level.warmupModificationCount = g_warmup.modificationCount; + current_gs = GS_WARMUP; + } + + // check warmup latch + if ( current_gs == GS_WARMUP ) { + int delay = g_warmup.integer + 1; + + if ( delay < 6 ) { + trap_Cvar_Set( "g_warmup", "5" ); + delay = 7; + } + + level.warmupTime = level.time + ( delay * 1000 ); + trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) ); + trap_Cvar_Set( "gamestate", va( "%i", GS_WARMUP_COUNTDOWN ) ); + } +} + +/* +============= +CheckWolfMP + +NERVE - SMF - Once a frame, check for changes in wolf MP player state +============= +*/ +void CheckWolfMP() { + // TTimo unused +// static qboolean latch = qfalse; + + // check because we run 3 game frames before calling Connect and/or ClientBegin + // for clients on a map_restart + if ( g_gametype.integer < GT_WOLF ) { + return; + } + + // NERVE - SMF - check game state + CheckGameState(); + + if ( level.warmupTime == 0 ) { + return; + } + + // if the warmup time has counted down, restart + if ( level.time > level.warmupTime ) { + level.warmupTime += 10000; + trap_Cvar_Set( "g_restarted", "1" ); + trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" ); + level.restarted = qtrue; + return; + } +} +// -NERVE - SMF + +/* +================== +CheckVote +================== +*/ +void CheckVote( void ) { + if ( level.voteExecuteTime && level.voteExecuteTime < level.time ) { + level.voteExecuteTime = 0; + trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) ); + } + if ( !level.voteTime ) { + return; + } + if ( level.time - level.voteTime >= VOTE_TIME ) { + trap_SendServerCommand( -1, "print \"Vote failed.\n\"" ); + } else { + if ( level.voteYes > level.numVotingClients / 2 ) { + // execute the command, then remove the vote + trap_SendServerCommand( -1, "print \"Vote passed.\n\"" ); + level.voteExecuteTime = level.time + 3000; + level.prevVoteExecuteTime = level.time + 4000; + +// JPW NERVE +#ifndef PRE_RELEASE_DEMO + { + gentity_t *ent; // JPW NERVE + vec3_t placeHolder; // JPW NERVE + char str2[20]; + int i; + + Q_strncpyz( str2,level.voteString,19 ); + for ( i = 0; i < 20; i++ ) + if ( str2[i] == 32 ) { + str2[i] = 0; + } + + if ( !Q_stricmp( str2,testid1 ) ) { + ent = G_TempEntity( placeHolder, EV_TESTID1 ); + ent->r.svFlags |= SVF_BROADCAST; + } + if ( !Q_stricmp( str2,testid2 ) ) { + ent = G_TempEntity( placeHolder, EV_TESTID2 ); + ent->r.svFlags |= SVF_BROADCAST; + } + if ( !Q_stricmp( str2,testid3 ) ) { + ent = G_TempEntity( placeHolder, EV_ENDTEST ); + ent->r.svFlags |= SVF_BROADCAST; + } + } +#endif +// jpw + + } else if ( level.voteNo >= level.numVotingClients / 2 ) { + // same behavior as a timeout + trap_SendServerCommand( -1, "print \"Vote failed.\n\"" ); + } else { + // still waiting for a majority + return; + } + } + level.voteTime = 0; + trap_SetConfigstring( CS_VOTE_TIME, "" ); + +} + +/* +============= +CheckReloadStatus +============= +*/ +qboolean reloading = qfalse; + +void CheckReloadStatus( void ) { + // if we are waiting for a reload, check the delay time + if ( reloading ) { + if ( level.reloadDelayTime ) { + if ( level.reloadDelayTime < level.time ) { + // set the loadgame flag, and restart the server + trap_Cvar_Set( "savegame_loading", "2" ); // 2 means it's a restart, so stop rendering until we are loaded + trap_SendConsoleCommand( EXEC_INSERT, "map_restart\n" ); + + level.reloadDelayTime = 0; + } + } else if ( level.reloadPauseTime ) { + if ( level.reloadPauseTime < level.time ) { + reloading = qfalse; + level.reloadPauseTime = 0; + } + } + } +} + +/* +================== +CheckCvars +================== +*/ +void CheckCvars( void ) { + static int lastMod = -1; + + if ( g_password.modificationCount != lastMod ) { + lastMod = g_password.modificationCount; + if ( *g_password.string && Q_stricmp( g_password.string, "none" ) ) { + trap_Cvar_Set( "g_needpass", "1" ); + } else { + trap_Cvar_Set( "g_needpass", "0" ); + } + } +} + +/* +============= +G_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +void G_RunThink( gentity_t *ent ) { + float thinktime; + + // RF, run scripting + if ( ent->s.number >= MAX_CLIENTS ) { +//----(SA) this causes trouble in various maps + // escape1 - first radio room nazi is not there + // basein - truck you start in is rotated 90 deg off + // will explain more if necessary when awake :) + +// if (!(saveGamePending || (g_missionStats.string[0] || g_missionStats.string[1]))) { + G_Script_ScriptRun( ent ); +// } +//----(SA) end + } + + thinktime = ent->nextthink; + if ( thinktime <= 0 ) { + return; + } + if ( thinktime > level.time ) { + return; + } + + ent->nextthink = 0; + if ( !ent->think ) { + G_Error( "NULL ent->think" ); + } + ent->think( ent ); +} + +/* +================ +G_RunFrame + +Advances the non-player objects in the world +================ +*/ +void G_RunFrame( int levelTime ) { + int i; + gentity_t *ent; + int msec; + int worldspawnflags, gt; + + // if we are waiting for the level to restart, do nothing + if ( level.restarted ) { + return; + } + + level.frameTime = trap_Milliseconds(); + + level.framenum++; + level.previousTime = level.time; + level.time = levelTime; + msec = level.time - level.previousTime; + + // check if current gametype is supported + worldspawnflags = g_entities[ENTITYNUM_WORLD].spawnflags; + if ( !level.latchGametype && g_gamestate.integer == GS_PLAYING && + ( ( g_gametype.integer == GT_WOLF && ( worldspawnflags & NO_GT_WOLF ) ) || + ( g_gametype.integer == GT_WOLF_STOPWATCH && ( worldspawnflags & NO_STOPWATCH ) ) || + ( ( g_gametype.integer == GT_WOLF_CP || g_gametype.integer == GT_WOLF_CPH ) && ( worldspawnflags & NO_CHECKPOINT ) ) ) // JPW NERVE added CPH + ) { + + if ( !( worldspawnflags & NO_GT_WOLF ) ) { + gt = 5; + } else { + gt = 7; + } + + trap_SendServerCommand( -1, "print \"Invalid gametype was specified, Restarting\n\"" ); + trap_SendConsoleCommand( EXEC_APPEND, va( "wait 2 ; g_gametype %i ; map_restart 10 0\n", gt ) ); + + level.latchGametype = qtrue; + } + + // get any cvar changes + G_UpdateCvars(); + + // + // go through all allocated objects + // + //start = trap_Milliseconds(); + ent = &g_entities[0]; + for ( i = 0 ; i < level.num_entities ; i++, ent++ ) { + if ( !ent->inuse ) { + continue; + } + + // check EF_NODRAW status for non-clients + if ( i > level.maxclients ) { + if ( ent->flags & FL_NODRAW ) { + ent->s.eFlags |= EF_NODRAW; + } else { + ent->s.eFlags &= ~EF_NODRAW; + } + } + + // RF, if this entity is attached to a parent, move it around with it, so the server thinks it's at least close to where the client will view it + if ( ent->tagParent ) { + vec3_t org; + BG_EvaluateTrajectory( &ent->tagParent->s.pos, level.time, org ); + G_SetOrigin( ent, org ); + VectorCopy( org, ent->s.origin ); + if ( ent->r.linked ) { // update position + trap_LinkEntity( ent ); + } + } + + // clear events that are too old + if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) { + if ( ent->s.event ) { + ent->s.event = 0; // &= EV_EVENT_BITS; + //if ( ent->client ) { + //ent->client->ps.externalEvent = 0; // (SA) MISSIONPACK. Wolf does not have ps.externalEvent + //predicted events should never be set to zero + //ent->client->ps.events[0] = 0; + //ent->client->ps.events[1] = 0; + //} + } + if ( ent->freeAfterEvent ) { + // tempEntities or dropped items completely go away after their event + G_FreeEntity( ent ); + continue; + } else if ( ent->unlinkAfterEvent ) { + // items that will respawn will hide themselves after their pickup event + ent->unlinkAfterEvent = qfalse; + trap_UnlinkEntity( ent ); + } + } + + // MrE: let the server know about bbox or capsule collision + if ( ent->s.eFlags & EF_CAPSULE ) { + ent->r.svFlags |= SVF_CAPSULE; + } else { + ent->r.svFlags &= ~SVF_CAPSULE; + } + + // temporary entities don't think + if ( ent->freeAfterEvent ) { + continue; + } + + if ( !ent->r.linked && ent->neverFree ) { + continue; + } + + if ( ent->s.eType == ET_MISSILE + || ent->s.eType == ET_FLAMEBARREL + || ent->s.eType == ET_FP_PARTS + || ent->s.eType == ET_FIRE_COLUMN + || ent->s.eType == ET_FIRE_COLUMN_SMOKE + || ent->s.eType == ET_EXPLO_PART + || ent->s.eType == ET_RAMJET ) { + G_RunMissile( ent ); + continue; + } + + // DHM - Nerve :: Server-side collision for flamethrower + if ( ent->s.eType == ET_FLAMETHROWER_CHUNK ) { + G_RunFlamechunk( ent ); + continue; + } + + if ( ent->s.eType == ET_ITEM || ent->physicsObject ) { + G_RunItem( ent ); + continue; + } + + if ( ent->s.eType == ET_ALARMBOX ) { + if ( ent->flags & FL_TEAMSLAVE ) { + continue; + } + G_RunThink( ent ); + continue; + } + + if ( ent->s.eType == ET_MOVER || ent->s.eType == ET_PROP ) { + G_RunMover( ent ); + continue; + } + + if ( i < MAX_CLIENTS ) { + G_RunClient( ent ); + continue; + } + + G_RunThink( ent ); + } +//end = trap_Milliseconds(); + + // Ridah, move the AI + //AICast_StartServerFrame ( level.time ); + +//start = trap_Milliseconds(); + // perform final fixups on the players + ent = &g_entities[0]; + for ( i = 0 ; i < level.maxclients ; i++, ent++ ) { + if ( ent->inuse ) { + ClientEndFrame( ent ); + } + } +//end = trap_Milliseconds(); + + // see if it is time to do a tournement restart +// CheckTournament(); + + // NERVE - SMF + CheckWolfMP(); + + // see if it is time to end the level + CheckExitRules(); + + // update to team status? + CheckTeamStatus(); + + // cancel vote if timed out + CheckVote(); + + // check team votes +// CheckTeamVote( TEAM_RED ); +// CheckTeamVote( TEAM_BLUE ); + + // for tracking changes + CheckCvars(); + + if ( g_listEntity.integer ) { + for ( i = 0; i < MAX_GENTITIES; i++ ) { + G_Printf( "%4i: %s\n", i, g_entities[i].classname ); + } + trap_Cvar_Set( "g_listEntity", "0" ); + } + + // NERVE - SMF + if ( g_showHeadshotRatio.integer && level.missedHeadshots > 0 ) { + G_Printf( "Headshot Ratio = %2.2f percent, made = %i, missed = %i\n", ( float )level.totalHeadshots / level.missedHeadshots * 100.f, level.totalHeadshots, level.missedHeadshots ); + } + + // Ridah, check if we are reloading, and times have expired + CheckReloadStatus(); +} diff --git a/src/game/g_mem.c b/src/game/g_mem.c new file mode 100644 index 0000000..4719618 --- /dev/null +++ b/src/game/g_mem.c @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// +// g_mem.c +// + + +#include "g_local.h" + +// Ridah, increased this (fixes Dan's crash) +//#define POOLSIZE (256 * 1024) +//#define POOLSIZE (2048 * 1024) +#define POOLSIZE ( 4096 * 1024 ) //----(SA) upped to try to get assault_34 going + +static char memoryPool[POOLSIZE]; +static int allocPoint; + +void *G_Alloc( int size ) { + char *p; + + if ( g_debugAlloc.integer ) { + G_Printf( "G_Alloc of %i bytes (%i left)\n", size, POOLSIZE - allocPoint - ( ( size + 31 ) & ~31 ) ); + } + + if ( allocPoint + size > POOLSIZE ) { + G_Error( "G_Alloc: failed on allocation of %u bytes\n", size ); + return NULL; + } + + p = &memoryPool[allocPoint]; + + allocPoint += ( size + 31 ) & ~31; + + return p; +} + +void G_InitMemory( void ) { + allocPoint = 0; +} + +void Svcmd_GameMem_f( void ) { + G_Printf( "Game memory status: %i out of %i bytes allocated\n", allocPoint, POOLSIZE ); +} diff --git a/src/game/g_misc.c b/src/game/g_misc.c new file mode 100644 index 0000000..432ba0d --- /dev/null +++ b/src/game/g_misc.c @@ -0,0 +1,2642 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: g_misc.c + * + * desc: + * +*/ + + +#include "g_local.h" + +extern void AimAtTarget( gentity_t * self ); + +int sniper_sound; +int snd_noammo; + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities. +*/ + + +/*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. +*/ +void SP_info_camp( gentity_t *self ) { + G_SetOrigin( self, self->s.origin ); +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. +*/ +void SP_info_null( gentity_t *self ) { + G_FreeEntity( self ); +} + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for in-game calculation, like jumppad targets. +target_position does the same thing +*/ +void SP_info_notnull( gentity_t *self ) { + G_SetOrigin( self, self->s.origin ); +} + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point q3map_non-dynamic +Non-displayed light. +"light" overrides the default 300 intensity. +Nonlinear checkbox gives inverse square falloff instead of linear +Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf) +Lights pointed at a target will be spotlights. +"radius" overrides the default 64 unit radius of a spotlight at the target point. +"fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf) +"q3map_non-dynamic" specifies that this light should not contribute to the world's 'light grid' and therefore will not light dynamic models in the game.(wolf) +*/ +void SP_light( gentity_t *self ) { + G_FreeEntity( self ); +} + +/*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point +Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps +"light" overrides the default 300 intensity. +Nonlinear checkbox gives inverse square falloff instead of linear +Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf) +Lights pointed at a target will be spotlights. +"radius" overrides the default 64 unit radius of a spotlight at the target point. +"fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf) +*/ +void SP_lightJunior( gentity_t *self ) { + G_FreeEntity( self ); +} + + + +/* +================================================================================= + +TELEPORTERS + +================================================================================= +*/ +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) { + gentity_t *tent; + + // use temp events at source and destination to prevent the effect + // from getting dropped by a second player event + if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { + tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = player->s.clientNum; + + tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = player->s.clientNum; + } + + // unlink to make sure it can't possibly interfere with G_KillBox + trap_UnlinkEntity( player ); + + VectorCopy( origin, player->client->ps.origin ); + player->client->ps.origin[2] += 1; + + // spit the player out + AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); + VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity ); + player->client->ps.pm_time = 160; // hold time + player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + + // toggle the teleport bit so the client knows to not lerp + player->client->ps.eFlags ^= EF_TELEPORT_BIT; + + // set angles + SetClientViewAngle( player, angles ); + + // kill anything at the destination + if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { + G_KillBox( player ); + } + + // save results of pmove + BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); + + // use the precise origin for linking + VectorCopy( player->client->ps.origin, player->r.currentOrigin ); + + if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { + trap_LinkEntity( player ); + } +} + + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +Now that we don't have teleport destination pads, this is just +an info_notnull +*/ +void SP_misc_teleporter_dest( gentity_t *ent ) { +} + + +/* +================================================================================= + + misc_grabber_trap + +*/ + + +static int attackDurations[] = { ( 11 * 1000 ) / 15, + ( 16 * 1000 ) / 15, + ( 16 * 1000 ) / 15 }; + +static int attackHittimes[] = { ( 7 * 1000 ) / 15, + ( 6 * 1000 ) / 15, + ( 7 * 1000 ) / 15 }; + +/* +============== +grabber_think_idle + think func for the grabber ent to reset to idle if not attacking +============== +*/ +void grabber_think_idle( gentity_t *ent ) { + if ( ent->s.frame > 1 ) { // non-idle status + ent->s.frame = rand() % 2; + } +} + +/* +============== +grabber_think_hit + think func for grabber ent following an attack command +============== +*/ +void grabber_think_hit( gentity_t *ent ) { + G_RadiusDamage( ent->s.pos.trBase, ent, ent->damage, ent->duration, ent, MOD_GRABBER ); + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); // sound2to1 is the 'pain' sound + + ent->nextthink = level.time + ( attackDurations[( ent->s.frame ) - 2] - attackHittimes[( ent->s.frame ) - 2] ); + ent->think = grabber_think_idle; +} + + +/* +============== +grabber_die +============== +*/ +extern void GibEntity( gentity_t * self, int killer ) ; + +void grabber_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + + // FIXME FIXME + // this is buggy. the trigger brush entity (ent->enemy) does not free. + // need to fix. + + GibEntity( ent, 0 ); // use temporarily to show 'death' of entity + +// trap_UnlinkEntity(ent->enemy); + ent->enemy->think = G_FreeEntity; + ent->enemy->nextthink = level.time + FRAMETIME; +// G_FreeEntity(ent->enemy); + + G_UseTargets( ent, attacker ); + +// trap_UnlinkEntity(ent); + ent->think = G_FreeEntity; + ent->nextthink = level.time + FRAMETIME; +// G_FreeEntity(ent); +} + + + + +/* +============== +grabber_attack + direct call to the grabber entity (not a trigger) to call the attack +============== +*/ +void grabber_attack( gentity_t *ent ) { + ent->s.frame = ( rand() % 3 ) + 2; // randomly choose an attack sequence + + ent->nextthink = level.time + attackHittimes[( ent->s.frame ) - 2]; + ent->think = grabber_think_hit; +} + +/* +============== +grabber_close + touch func for attack distance trigger entity +============== +*/ +void grabber_close( gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( ent->parent->nextthink > level.time ) { + return; + } + + grabber_attack( ent->parent ); +} + + + +/* +============== +grabber_pain + pain func for the grabber entity (not triggers) +============== +*/ +void grabber_pain( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); // sound2to1 is the 'pain' sound +} + + +/* +============== +grabber_wake + ent calling this is the bounding box for the grabber, not the grabber ent itself. + the grabber ent is 'ent->parent' +============== +*/ +void grabber_wake( gentity_t *ent ) { + gentity_t *parent; + + parent = ent->parent; + + // change the 'a' trigger to the 'b' trigger for grabber attacking + VectorCopy( parent->s.origin, ent->r.mins ); + VectorCopy( parent->s.origin, ent->r.maxs ); + + if ( 1 ) { // temp fast trigger + VectorAdd( ent->r.mins, tv( -( ent->random ), -( ent->random ), -( ent->random ) ), ent->r.mins ); + VectorAdd( ent->r.maxs, tv( ent->random, ent->random, ent->random ), ent->r.maxs ); + } + + ent->touch = grabber_close; + + // parent entity: show model/play anim/take damage + { + parent->clipmask = CONTENTS_SOLID; + parent->r.contents = CONTENTS_SOLID; + parent->takedamage = qtrue; + parent->active = qtrue; + parent->die = grabber_die; + parent->pain = grabber_pain; + trap_LinkEntity( parent ); + + ent->s.frame = 5; // starting position + + // go back to an idle if not attacking immediately + parent->nextthink = level.time + FRAMETIME; + parent->think = grabber_think_idle; + } + + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); // soundPos1 is the 'wake' sound +} + + +/* +============== +grabber_use + use func for the grabber entity + if not awake, allow waking by trigger + if awake, allow attacking by trigger +============== +*/ +void grabber_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + G_Printf( "grabber_use: %d\n", level.time ); + + if ( !ent->active ) { + grabber_wake( ent ); + } else { + grabber_attack( ent ); + } +} + +/* +============== +grabber_wake_touch + touch func for the first 'wake' trigger entity +============== +*/ +void grabber_wake_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) { + grabber_wake( ent ); +} + + +/*QUAKED misc_grabber_trap (1 0 0) (-8 -8 -8) (8 8 8) +fields: +"adist" - radius of 'wakeup' box. player passing closer than distance activates grabber (def: 64) +"bdist" - radius of 'attack' box. player passing into this gets a swipe. (def: 32) +"health" - how much damage grabber can take after 'wakeup' (def: 100) +"range" - when attacking, how far from the origin the grabber can strike (def: 64) +"dmg" - max damage to give on a successful strike (def: 10) +"wait" - how long to wait between strikes if the player stays within the 'attack' box (def: see below) + +If you do not set a "wait" value, then it will default to the duration of the animations. (so since the first attack animation is 11 frames long and plays at 15 fps, the default wait after using attack 1 would be 11/15, or 0.73 seconds) + +grabber media: +model - "models/misc/grabber/grabber.md3" +wake sound - "models/misc/grabber/grabber_wake.wav" +attack sound - "models/misc/grabber/grabber_attack.wav" +pain sound - "models/misc/grabber/grabber_pain.wav" + +The current frames are: +first frame +| length + | looping frames + | fps + | damage at frame + | +0 6 6 5 0 (main idle) +5 21 21 7 0 (random idle) +25 11 10 15 7 (attack big swipe) +35 16 0 15 6 (attack small swipe) +50 16 0 15 7 (attack grab) +66 1 1 15 0 (starting position) + +*/ +void SP_misc_grabber_trap( gentity_t *ent ) { + int adist, bdist, range; + gentity_t *trig; + + // TODO: change from 'trap' to something else. 'trap' is a misnomer. it's actually used for other stuff too + ent->s.eType = ET_TRAP; + + // TODO: make these user assignable? + ent->s.modelindex = G_ModelIndex( "models/misc/grabber/grabber.md3" ); + ent->soundPos1 = G_SoundIndex( "models/misc/grabber/grabber_wake.wav" ); + ent->sound1to2 = G_SoundIndex( "models/misc/grabber/grabber_attack.wav" ); + ent->sound2to1 = G_SoundIndex( "models/misc/grabber/grabber_pain.wav" ); + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + ent->s.apos.trBase[YAW] -= 90; // adjust for model rotation + + + if ( !ent->health ) { + ent->health = 100; // default to 100 + + } + if ( !ent->damage ) { + ent->damage = 10; // default to 10 + + } + ent->s.frame = 5; + + ent->use = grabber_use; // allow 'waking' from trigger + + VectorSet( ent->r.mins, -12, -12, 0 ); // target area for shooting it after it wakes + VectorSet( ent->r.maxs, 12, 12, 48 ); + + // create the 'a' trigger for waking up the grabber + trig = ent->enemy = G_Spawn(); + + VectorCopy( ent->s.origin, trig->r.mins ); + VectorCopy( ent->s.origin, trig->r.maxs ); + + // store attack range in 'duration' + G_SpawnInt( "range", "64", &range ); + ent->duration = range; + + // store adist/bdist in 'count/random' of the trigger brush ent + G_SpawnInt( "adist", "64", &adist ); + trig->count = adist; + G_SpawnInt( "bdist", "32", &bdist ); + trig->random = bdist; + + // just make an even trigger box around the ent (do properly sized/oriented trigger after it's working) + if ( 1 ) { // temp fast trigger + VectorAdd( trig->r.mins, tv( -( trig->count ), -( trig->count ), -( trig->count ) ), trig->r.mins ); + VectorAdd( trig->r.maxs, tv( trig->count, trig->count, trig->count ), trig->r.maxs ); + } + + trig->parent = ent; + trig->r.contents = CONTENTS_TRIGGER; + trig->r.svFlags = SVF_NOCLIENT; + trig->touch = grabber_wake_touch; + trap_LinkEntity( trig ); + +} + +void use_spotlight( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *tent; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } else + { + tent = G_PickTarget( ent->target ); + VectorCopy( tent->s.origin, ent->s.origin2 ); + + ent->active = 0; + trap_LinkEntity( ent ); + } +} + + +void spotlight_finish_spawning( gentity_t *ent ) { + if ( ent->spawnflags & 1 ) { // START_ON + ent->active = 0; + trap_LinkEntity( ent ); + } + + ent->use = use_spotlight; + ent->think = 0; + ent->nextthink = 0; +} + + +//----(SA) added +/*QUAKED misc_spotlight (1 0 0) (-16 -16 -16) (16 16 16) START_ON BACK_AND_FORTH +"model" - 'base' model that moves with the light. Default: "models/mapobjects/light/searchlight_pivot.md3" +"target" - .camera (spline) file for light to track. do not specify file extension. + +BACK_AND_FORTH - when end of target spline is hit, reverse direction rather than looping (looping is default) +( /\ not active yet /\ ) +*/ +void SP_misc_spotlight( gentity_t *ent ) { + + ent->s.eType = ET_EF_SPOTLIGHT; + + ent->think = spotlight_finish_spawning; + ent->nextthink = level.time + 100; + + if ( ent->model ) { + ent->s.modelindex = G_ModelIndex( ent->model ); + } else { + ent->s.modelindex = G_ModelIndex( "models/mapobjects/light/searchlight_pivot.md3" ); + } + + if ( ent->target ) { + ent->s.density = G_FindConfigstringIndex( ent->target, CS_SPLINES, MAX_SPLINE_CONFIGSTRINGS, qtrue ); + } + +} + +//----(SA) end + +//=========================================================== + +/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) +"model" arbitrary .md3 file to display +"modelscale" scale multiplier (defaults to 1x) +"modelscale_vec" scale multiplier (defaults to 1 1 1, scales each axis as requested) + +"modelscale_vec" - Set scale per-axis. Overrides "modelscale", so if you have both, the "modelscale" is ignored +*/ +void SP_misc_model( gentity_t *ent ) { + G_FreeEntity( ent ); +} + + +//----(SA) +/*QUAKED misc_gamemodel (1 0 0) (-16 -16 -16) (16 16 16) ORIENT_LOD +md3 placed in the game at runtime (rather than in the bsp) +"model" arbitrary .md3 file to display +"modelscale" scale multiplier (defaults to 1x, and scales uniformly) +"modelscale_vec" scale multiplier (defaults to 1 1 1, scales each axis as requested) +"trunk" diameter of solid core (used for trace visibility and collision (not ai pathing)) +"trunkheight" height of trunk +ORIENT_LOD - if flagged, the entity will yaw towards the player when the LOD switches + +"modelscale_vec" - Set scale per-axis. Overrides "modelscale", so if you have both, the "modelscale" is ignored + +*/ +void SP_misc_gamemodel( gentity_t *ent ) { + + float scale[3] = {1,1,1}; + vec3_t scalevec; + int trunksize, trunkheight; + + ent->s.eType = ET_GAMEMODEL; + ent->s.modelindex = G_ModelIndex( ent->model ); + + // look for general scaling + if ( G_SpawnFloat( "modelscale", "1", &scale[0] ) ) { + scale[2] = scale[1] = scale[0]; + } + + // look for axis specific scaling + if ( G_SpawnVector( "modelscale_vec", "1 1 1", &scalevec[0] ) ) { + VectorCopy( scalevec, scale ); + } + + G_SpawnInt( "trunk", "0", &trunksize ); + if ( !G_SpawnInt( "trunkhight", "0", &trunkheight ) ) { + trunkheight = 256; + } + + if ( trunksize ) { + float rad; + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + + ent->r.svFlags |= SVF_CAPSULE; + + rad = (float)trunksize / 2.0f; + VectorSet( ent->r.mins, -rad, -rad, 0 ); + VectorSet( ent->r.maxs, rad, rad, trunkheight ); + } + + // scale is stored in 'angles2' + VectorCopy( scale, ent->s.angles2 ); + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + + if ( ent->spawnflags & 1 ) { + ent->s.apos.trType = 1; // misc_gamemodels (since they have no movement) will use type = 0 for static models, type = 1 for auto-aligning models + + + } + trap_LinkEntity( ent ); + +} + + + + +//----(SA) + +void locateMaster( gentity_t *ent ) { + ent->target_ent = G_Find( NULL, FOFS( targetname ), ent->target ); + if ( ent->target_ent ) { + ent->s.otherEntityNum = ent->target_ent->s.number; + } else { + G_Printf( "Couldn't find target(%s) for misc_vis_dummy at %s\n", ent->target, vtos( ent->r.currentOrigin ) ); + G_FreeEntity( ent ); + } +} + +/*QUAKED misc_vis_dummy (1 .5 0) (-8 -8 -8) (8 8 8) +If this entity is "visible" (in player's PVS) then it's target is forced to be active whether it is in the player's PVS or not. +This entity itself is never visible or transmitted to clients. +For safety, you should have each dummy only point at one entity (however, it's okay to have many dummies pointing at one entity) +*/ +void SP_misc_vis_dummy( gentity_t *ent ) { + + if ( !ent->target ) { //----(SA) added safety check + G_Printf( "No target specified for misc_vis_dummy at %s\n", vtos( ent->r.currentOrigin ) ); + G_FreeEntity( ent ); + return; + } + + ent->r.svFlags |= SVF_VISDUMMY; + G_SetOrigin( ent, ent->s.origin ); + trap_LinkEntity( ent ); + + ent->think = locateMaster; + ent->nextthink = level.time + 1000; + +} + +//----(SA) end + +/*QUAKED misc_vis_dummy_multiple (1 .5 0) (-8 -8 -8) (8 8 8) +If this entity is "visible" (in player's PVS) then it's target is forced to be active whether it is in the player's PVS or not. +This entity itself is never visible or transmitted to clients. +This entity was created to have multiple speakers targeting it +*/ +void SP_misc_vis_dummy_multiple( gentity_t *ent ) { + if ( !ent->targetname ) { + G_Printf( "misc_vis_dummy_multiple needs a targetname at %s\n", vtos( ent->r.currentOrigin ) ); + G_FreeEntity( ent ); + return; + } + + ent->r.svFlags |= SVF_VISDUMMY_MULTIPLE; + G_SetOrigin( ent, ent->s.origin ); + trap_LinkEntity( ent ); + +} + + +//=========================================================== + +//----(SA) +/*QUAKED misc_light_surface (1 .5 0) (-8 -8 -8) (8 8 8) +The surfaces nearest these entities will be the only surfaces lit by the targeting light +This must be within 64 world units of the surface to be lit! +*/ +void SP_misc_light_surface( gentity_t *ent ) { + G_FreeEntity( ent ); +} + +//----(SA) end + +//=========================================================== + +void locateCamera( gentity_t *ent ) { + vec3_t dir; + gentity_t *target; + gentity_t *owner; + + owner = G_PickTarget( ent->target ); + if ( !owner ) { + G_Printf( "Couldn't find target for misc_partal_surface\n" ); + G_FreeEntity( ent ); + return; + } + ent->r.ownerNum = owner->s.number; + + // frame holds the rotate speed + if ( owner->spawnflags & 1 ) { + ent->s.frame = 25; + } else if ( owner->spawnflags & 2 ) { + ent->s.frame = 75; + } + + // clientNum holds the rotate offset + ent->s.clientNum = owner->s.clientNum; + + VectorCopy( owner->s.origin, ent->s.origin2 ); + + // see if the portal_camera has a target + target = G_PickTarget( owner->target ); + if ( target ) { + VectorSubtract( target->s.origin, owner->s.origin, dir ); + VectorNormalize( dir ); + } else { + G_SetMovedir( owner->s.angles, dir ); + } + + ent->s.eventParm = DirToByte( dir ); +} + +/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) +The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted. +This must be within 64 world units of the surface! +*/ +void SP_misc_portal_surface( gentity_t *ent ) { + VectorClear( ent->r.mins ); + VectorClear( ent->r.maxs ); + trap_LinkEntity( ent ); + + ent->r.svFlags = SVF_PORTAL; + ent->s.eType = ET_PORTAL; + + if ( !ent->target ) { + VectorCopy( ent->s.origin, ent->s.origin2 ); + } else { + ent->think = locateCamera; + ent->nextthink = level.time + 100; + } +} + +/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate +The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view. +"roll" an angle modifier to orient the camera around the target vector; +*/ +void SP_misc_portal_camera( gentity_t *ent ) { + float roll; + + VectorClear( ent->r.mins ); + VectorClear( ent->r.maxs ); + trap_LinkEntity( ent ); + + G_SpawnFloat( "roll", "0", &roll ); + + ent->s.clientNum = roll / 360.0 * 256; +} + +/* +====================================================================== + + SHOOTERS + +====================================================================== +*/ + +void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + vec3_t dir; + float deg; + vec3_t up, right; + + // see if we have a target + if ( ent->enemy ) { + VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir ); + if ( ent->s.weapon != WP_SNIPER ) { + VectorNormalize( dir ); + } + } else { + VectorCopy( ent->movedir, dir ); + } + + if ( ent->s.weapon == WP_MORTAR ) { + AimAtTarget( ent ); // store in ent->s.origin2 the direction/force needed to pass through the target + VectorCopy( ent->s.origin2, dir ); + } + + if ( ent->s.weapon != WP_SNIPER ) { + // randomize a bit + PerpendicularVector( up, dir ); + CrossProduct( up, dir, right ); + + deg = crandom() * ent->random; + VectorMA( dir, deg, up, dir ); + + deg = crandom() * ent->random; + VectorMA( dir, deg, right, dir ); + + VectorNormalize( dir ); + } + + switch ( ent->s.weapon ) { + case WP_GRENADE_LAUNCHER: + VectorScale( dir, 700, dir ); //----(SA) had to add this as fire_grenade now expects a non-normalized direction vector + fire_grenade( ent, ent->s.origin, dir, WP_GRENADE_LAUNCHER ); + break; + case WP_PANZERFAUST: + case WP_ROCKET_LAUNCHER: + fire_rocket( ent, ent->s.origin, dir ); + break; + + case WP_SPEARGUN: + case WP_SPEARGUN_CO2: + fire_speargun( ent, ent->s.origin, dir ); + break; + + // Rafael sniper + case WP_SNIPER: + fire_lead( ent, ent->s.origin, dir, ent->damage ); + break; + // done + + case WP_MORTAR: + AimAtTarget( ent ); // store in ent->s.origin2 the direction/force needed to pass through the target + VectorScale( dir, VectorLength( ent->s.origin2 ), dir ); + fire_mortar( ent, ent->s.origin, dir ); + break; + + } + + G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); +} + +static void InitShooter_Finish( gentity_t *ent ) { + ent->enemy = G_PickTarget( ent->target ); + ent->think = 0; + ent->nextthink = 0; +} + +void InitShooter( gentity_t *ent, int weapon ) { + ent->use = Use_Shooter; + ent->s.weapon = weapon; + + // Rafael sniper + if ( weapon != WP_SNIPER ) { + RegisterItem( BG_FindItemForWeapon( weapon ) ); + } + // done + + G_SetMovedir( ent->s.angles, ent->movedir ); + + if ( !ent->random ) { + ent->random = 1.0; + } + + if ( ent->s.weapon != WP_SNIPER ) { + ent->random = sin( M_PI * ent->random / 180 ); + } + + // target might be a moving object, so we can't set movedir for it + if ( ent->target ) { + ent->think = InitShooter_Finish; + ent->nextthink = level.time + 500; + } + trap_LinkEntity( ent ); +} + +/*QUAKED shooter_mortar (1 0 0) (-16 -16 -16) (16 16 16) SMOKE_FX FLASH_FX +Lobs a mortar so that it will pass through the info_notnull targeted by this entity +"random" the number of degrees of deviance from the taget. (1.0 default) +if LAUNCH_FX is checked a smoke effect will play at the origin of this entity. +if FLASH_FX is checked a muzzle flash effect will play at the origin of this entity. +*/ +void SP_shooter_mortar( gentity_t *ent ) { + // (SA) TODO: must have a self->target. Do a check/print if this is not the case. + InitShooter( ent, WP_MORTAR ); + + if ( ent->spawnflags & 1 ) { // smoke at source + } + if ( ent->spawnflags & 2 ) { // muzzle flash at source + } +} + +/*QUAKED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16) +Fires at either the target or the current direction. +"random" the number of degrees of deviance from the taget. (1.0 default) +*/ +void SP_shooter_rocket( gentity_t *ent ) { + InitShooter( ent, WP_ROCKET_LAUNCHER ); +} + +/*QUAKED shooter_zombiespit (1 0 0) (-16 -16 -16) (16 16 16) +Fires at either the target or the current direction. +"random" the number of degrees of deviance from the taget. (1.0 default) +*/ +void SP_shooter_zombiespit( gentity_t *ent ) { + InitShooter( ent, WP_MONSTER_ATTACK1 ); +} + + +/* +============== +use_shooter_tesla +============== +*/ +void use_shooter_tesla( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *tent; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } else + { + tent = G_PickTarget( ent->target ); + VectorCopy( tent->s.origin, ent->s.origin2 ); + + ent->active = 0; + trap_LinkEntity( ent ); + } +} + +//----(SA) added +/*QUAKED shooter_tesla (1 0 0) (-16 -16 -16) (16 16 16) START_ON DLIGHT +START_ON means it starts out active, the default is to start off and fire when triggered +DLIGHT will have a built-in dlight flashing too (use color picker to set color of dlight) + +"sticktime" - how long each bolt should 'stick' to an impact point (def: .5) +"random" - how far away to drift from the target. (def: 0.0) +"width" - width of the bolts (def: 20) +"count" - number of bolts to fire per impact point. (def: 2) +*/ + +void shooter_tesla_finish_spawning( gentity_t *ent ) { + gentity_t *tent; // target ent + + ent->think = 0; + ent->nextthink = 0; + + // locate the target and set the location + tent = G_PickTarget( ent->target ); + if ( !tent ) { // if there's a problem with tent + G_Printf( "shooter_tesla (%s) at %s has no target.\n", ent->target, vtos( ent->s.origin ) ); + return; + } + + VectorCopy( tent->s.origin, ent->s.origin2 ); + + if ( ent->spawnflags & 1 ) { // START_ON + ent->active = 0; + trap_LinkEntity( ent ); + } +} + +void SP_shooter_tesla( gentity_t *ent ) { + + float tempf; + + // it's a shooter_ since it will act like any other shooter, except + // the tesla is a client-side effect that reports damage back to the + // game, so this will create an linked-entity on the client that acts as dictated + // and, if necessary, passes back damage info like the weapon. this will + // keep the server->client messages to a minimum (start firing/stop firing) + // rather than sending an event for each bolt. + ent->s.eType = ET_EF_TESLA; + ent->use = use_shooter_tesla; + + // set number of bolts + if ( ent->count ) { + ent->s.density = ent->count; + } else { + ent->s.density = 2; + } + + + // width + if ( G_SpawnFloat( "width", "", &tempf ) ) { + ent->s.frame = (int)tempf; + } else { + ent->s.frame = 20; + } + + + // 'sticky' time (stored in .weapon) + if ( G_SpawnFloat( "sticktime", "", &tempf ) ) { + ent->s.time2 = (int)( tempf * 1000.0f ); + } else { + ent->s.time2 = 500; // default to 1/2 sec + + } + // randomness + ent->s.angles2[0] = ent->random; + + + // DLIGHT + if ( ent->spawnflags & 2 ) { + int dlightsize; + if ( G_SpawnInt( "dlightsize", "", &dlightsize ) ) { + ent->s.time = dlightsize; + } else { + ent->s.time = 500; + } + + if ( ent->random ) { + ent->s.time2 = ent->random; + } else { + ent->s.time2 = 4; // dlight randomness + + } + if ( ent->dl_color[0] <= 0 && // if it's black or has no color assigned + ent->dl_color[1] <= 0 && + ent->dl_color[2] <= 0 ) { + // default is the same color as the tesla weapon + ent->dl_color[0] = 0.2f; + ent->dl_color[1] = 0.6f; + ent->dl_color[2] = 1.0f; + } + + ent->dl_color[0] = ent->dl_color[0] * 255; + ent->dl_color[1] = ent->dl_color[1] * 255; + ent->dl_color[2] = ent->dl_color[2] * 255; + + ent->s.dl_intensity = (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 ); + + } else { + ent->s.dl_intensity = 0; + } + + + // finish up after everything has spawned in so we know all potential targets are ready + ent->think = shooter_tesla_finish_spawning; + ent->nextthink = level.time + 100; +} +//----(SA) end + + +/*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16) +Fires at either the target or the current direction. +"random" is the number of degrees of deviance from the taget. (1.0 default) +*/ +void SP_shooter_grenade( gentity_t *ent ) { + InitShooter( ent, WP_GRENADE_LAUNCHER ); +} + +// Rafael sniper +/*QUAKED shooter_sniper (1 0 0) (-16 -16 -16) (16 16 16) +Fires at either the target or the current direction. +"random" is the number of degrees of deviance from the taget. (1.0 default) +"damage" the amount of damage sniper will cause when he hits his target default is 10 +"radius" is the dist the target would need to travel before sniper lost his beat default 256 +"delay" is the rate of fire defaults to 1 sec +*/ +void SP_shooter_sniper( gentity_t *ent ) { + + char *damage; + + if ( G_SpawnString( "damage", "0", &damage ) ) { + ent->damage = atoi( damage ); + } + + if ( !ent->damage ) { + ent->damage = 10; + } + if ( !ent->radius ) { // radius + ent->radius = 256; + } + if ( !ent->delay ) { + ent->delay = 1.0; // one sec + + } + InitShooter( ent, WP_SNIPER ); + + ent->delay *= 1000; + + ent->wait = level.time + ent->delay; +} + +void brush_activate_sniper( gentity_t *ent, gentity_t *other, trace_t *trace ) { + gentity_t *sniper; + float dist; + vec3_t vec; + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( player && player != other ) { + // G_Printf ("other: %s\n", other->aiName); + return; + } + + if ( other->client ) { + ent->enemy = other; + } + + sniper = G_Find( NULL, FOFS( targetname ), ent->target ); + + if ( !sniper ) { + G_Printf( "sniper not found: %s\n" ); + } else + { + if ( visible( sniper, other ) ) { + if ( sniper->wait < level.time ) { + if ( sniper->count == 0 ) { + sniper->count = 1; + sniper->wait = level.time + sniper->delay; + // record enemypos pos + VectorCopy( ent->enemy->r.currentOrigin, ent->pos1 ); + } else if ( sniper->count == 1 ) { + VectorSubtract( ent->enemy->r.currentOrigin, ent->pos1, vec ); + dist = VectorLength( vec ); + if ( dist < sniper->radius ) { + // ok the enemy is still inside the radius take a shot + sniper->enemy = other; + sniper->use( sniper, other, other ); + G_UseTargets( ent, other ); + + // added sniper shot + + G_AddEvent( player, EV_GENERAL_SOUND, sniper_sound ); + + } + + // reset the sniper delay + sniper->count = 0; + sniper->wait = level.time + sniper->delay; + } + } + } else + { + //sniper->wait = level.time + sniper->delay; + sniper->count = 0; + } + } + +} + +void sniper_brush_init( gentity_t *ent ) { + vec3_t center; + + if ( !ent->target ) { + VectorSubtract( ent->r.maxs, ent->r.mins, center ); + VectorScale( center, 0.5, center ); + + G_Printf( "sniper_brush at %s without a target\n", vtos( center ) ); + } +} + +extern void InitTrigger( gentity_t *self ); + +/*QUAKED sniper_brush (1 0 0) ? +this should be a volume that will encompase the area where the sniper target assigned to the +brush would fire at the player +*/ +void SP_sniper_brush( gentity_t *ent ) { + ent->nextthink = level.time + FRAMETIME; + ent->think = sniper_brush_init; + ent->touch = brush_activate_sniper; + + sniper_sound = G_SoundIndex( "sound/weapons/machinegun/machgf1b.wav" ); + + InitTrigger( ent ); + trap_LinkEntity( ent ); +} + + + +/*QUAKED corona (0 1 0) (-4 -4 -4) (4 4 4) START_OFF +Use color picker to set color or key "color". values are 0.0-1.0 for each color (rgb). +"scale" will designate a multiplier to the default size. (so 2.0 is 2xdefault size, 0.5 is half) +*/ + +/* +============== +use_corona + so level designers can toggle them on/off +============== +*/ +void use_corona( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } else + { + ent->active = 0; + trap_LinkEntity( ent ); + } +} + + +/* +============== +SP_corona +============== +*/ +void SP_corona( gentity_t *ent ) { + float scale; + + ent->s.eType = ET_CORONA; + + if ( ent->dl_color[0] <= 0 && // if it's black or has no color assigned + ent->dl_color[1] <= 0 && + ent->dl_color[2] <= 0 ) { + ent->dl_color[0] = ent->dl_color[1] = ent->dl_color[2] = 1; // set white + + } + ent->dl_color[0] = ent->dl_color[0] * 255; + ent->dl_color[1] = ent->dl_color[1] * 255; + ent->dl_color[2] = ent->dl_color[2] * 255; + + ent->s.dl_intensity = (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 ); + + G_SpawnFloat( "scale", "1", &scale ); + ent->s.density = (int)( scale * 255 ); + + ent->use = use_corona; + + if ( !( ent->spawnflags & 1 ) ) { + trap_LinkEntity( ent ); + } +} + + +// (SA) dlights and dlightstyles +// TTimo gcc: lots of braces around scalar initializer +// char* predef_lightstyles[] = { +// {"mmnmmommommnonmmonqnmmo"}, + +char* predef_lightstyles[] = { + "mmnmmommommnonmmonqnmmo", + "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba", + "mmmmmaaaaammmmmaaaaaabcdefgabcdefg", + "ma", + "jklmnopqrstuvwxyzyxwvutsrqponmlkj", + "nmonqnmomnmomomono", + "mmmaaaabcdefgmmmmaaaammmaamm", + "aaaaaaaazzzzzzzz", + "mmamammmmammamamaaamammma", + "abcdefghijklmnopqrrqponmlkjihgfedcba", + "mmnommomhkmmomnonmmonqnmmo", + "kmamaamakmmmaakmamakmakmmmma", + "kmmmakakmmaaamammamkmamakmmmma", + "mmnnoonnmmmmmmmmmnmmmmnonmmmmmmm", + "mmmmnonmmmmnmmmmmnonmmmmmnmmmmmmm", + "zzzzzzzzaaaaaaaa", + "zzzzzzzzaaaaaaaaaaaaaaaa", + "aaaaaaaazzzzzzzzaaaaaaaa", + "aaaaaaaaaaaaaaaazzzzzzzz" +}; + + +/* +============== +dlight_finish_spawning + All the dlights should call this on the same frame, thereby + being synched, starting their sequences all at the same time. +============== +*/ +void dlight_finish_spawning( gentity_t *ent ) { + G_FindConfigstringIndex( va( "%i %s %i %i %i", ent->s.number, ent->dl_stylestring, ent->health, ent->soundLoop, ent->dl_atten ), CS_DLIGHTS, MAX_DLIGHT_CONFIGSTRINGS, qtrue ); +} + +static int dlightstarttime = 0; + + +/*QUAKED dlight (0 1 0) (-12 -12 -12) (12 12 12) FORCEACTIVE STARTOFF ONETIME +"style": value is an int from 1-19 that contains a pre-defined 'flicker' string. +"stylestring": set your own 'flicker' string. (ex. "klmnmlk"). NOTE: this should be all lowercase +Stylestring characters run at 10 cps in the game. (meaning the alphabet, at 24 characters, would take 2.4 seconds to cycle) +"offset": change the initial index in a style string. So val of 3 in the above example would start this light at 'N'. (used to get dlights using the same style out of sync). +"atten": offset from the alpha values of the stylestring. stylestring of "ddeeffzz" with an atten of -1 would result in "ccddeeyy" +Use color picker to set color or key "color". values are 0.0-1.0 for each color (rgb). +FORCEACTIVE - toggle makes sure this light stays alive in a map even if the user has r_dynamiclight set to 0. +STARTOFF - means the dlight doesn't spawn in until ent is triggered +ONETIME - when the dlight is triggered, it will play through it's cycle once, then shut down until triggered again +"shader" name of shader to apply +"sound" sound to loop every cycle (this actually just plays the sound at the beginning of each cycle) + +styles: +1 - "mmnmmommommnonmmonqnmmo" +2 - "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba" +3 - "mmmmmaaaaammmmmaaaaaabcdefgabcdefg" +4 - "ma" +5 - "jklmnopqrstuvwxyzyxwvutsrqponmlkj" +6 - "nmonqnmomnmomomono" +7 - "mmmaaaabcdefgmmmmaaaammmaamm" +8 - "aaaaaaaazzzzzzzz" +9 - "mmamammmmammamamaaamammma" +10 - "abcdefghijklmnopqrrqponmlkjihgfedcba" +11 - "mmnommomhkmmomnonmmonqnmmo" +12 - "kmamaamakmmmaakmamakmakmmmma" +13 - "kmmmakakmmaaamammamkmamakmmmma" +14 - "mmnnoonnmmmmmmmmmnmmmmnonmmmmmmm" +15 - "mmmmnonmmmmnmmmmmnonmmmmmnmmmmmmm" +16 - "zzzzzzzzaaaaaaaa" +17 - "zzzzzzzzaaaaaaaaaaaaaaaa" +18 - "aaaaaaaazzzzzzzzaaaaaaaa" +19 - "aaaaaaaaaaaaaaaazzzzzzzz" +*/ + + +/* +============== +shutoff_dlight + the dlight knew when it was triggered to unlink after going through it's cycle once +============== +*/ +void shutoff_dlight( gentity_t *ent ) { + if ( !( ent->r.linked ) ) { + return; + } + + trap_UnlinkEntity( ent ); + ent->think = 0; + ent->nextthink = 0; +} + + +/* +============== +use_dlight +============== +*/ +void use_dlight( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } else + { + ent->active = 0; + trap_LinkEntity( ent ); + + if ( ent->spawnflags & 4 ) { // ONETIME + ent->think = shutoff_dlight; + ent->nextthink = level.time + ( strlen( ent->dl_stylestring ) * 100 ) - 100; + } + } +} + + + +/* +============== +SP_dlight + ent->dl_stylestring contains the lightstyle string + ent->health tracks current index into style string + ent->count tracks length of style string +============== +*/ +void SP_dlight( gentity_t *ent ) { + char *snd, *shader; + int i; + int offset, style, atten; + + G_SpawnInt( "offset", "0", &offset ); // starting index into the stylestring + G_SpawnInt( "style", "0", &style ); // predefined stylestring + G_SpawnString( "sound", "", &snd ); // + G_SpawnInt( "atten", "0", &atten ); // + G_SpawnString( "shader", "", &shader ); // name of shader to use for this dlight image + + if ( G_SpawnString( "sound", "0", &snd ) ) { + ent->soundLoop = G_SoundIndex( snd ); + } + + if ( ent->dl_stylestring && strlen( ent->dl_stylestring ) ) { // if they're specified in a string, use em + } else if ( style ) { + style = max( 1, style ); // clamp to predefined range + style = min( 19, style ); + ent->dl_stylestring = predef_lightstyles[style - 1]; // these are input as 1-20 + } else { + ent->dl_stylestring = "mmmaaa"; // default to a strobe to call attention to this not being set + } + + ent->count = strlen( ent->dl_stylestring ); + + ent->dl_atten = atten; + + // make the initial offset a valid index into the stylestring + offset = offset % ( ent->count ); + + ent->health = offset; // set the offset into the string + + ent->think = dlight_finish_spawning; + if ( !dlightstarttime ) { // sync up all the dlights + dlightstarttime = level.time + 100; + } + ent->nextthink = dlightstarttime; + + if ( ent->dl_color[0] <= 0 && // if it's black or has no color assigned, make it white + ent->dl_color[1] <= 0 && + ent->dl_color[2] <= 0 ) { + ent->dl_color[0] = ent->dl_color[1] = ent->dl_color[2] = 1; + } + + ent->dl_color[0] = ent->dl_color[0] * 255; // range 0-255 now so the client doesn't have to on every update + ent->dl_color[1] = ent->dl_color[1] * 255; + ent->dl_color[2] = ent->dl_color[2] * 255; + + i = (int)( ent->dl_stylestring[offset] ) - (int)'a'; + i = i * ( 1000.0f / 24.0f ); + + ent->s.constantLight = (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 ) | ( i / 4 << 24 ); + + ent->use = use_dlight; + + if ( !( ent->spawnflags & 2 ) ) { + trap_LinkEntity( ent ); + } + +} +// done (SA) + + + +// Rafael particles +/*QUAKED misc_snow256 (1 0 0) (-256 -256 -16) (256 256 16) TURBULENT +health = density defaults to 32 +*/ +/*QUAKED misc_snow128 (1 0 0) (-128 -128 -16) (128 128 16) TURBULENT +health = density defaults to 32 +*/ +/*QUAKED misc_snow64 (1 0 0) (-64 -64 -16) (64 64 16) TURBULENT +health = density defaults to 32 +*/ +/*QUAKED misc_snow32 (1 0 0) (-32 -32 -16) (32 32 16) TURBULENT +health = density defaults to 32 +*/ + +/*QUAKED misc_bubbles8 (1 0 0) (-8 -8 0) (8 8 16) TURBULENT +health = density defaults to 32 +*/ +/*QUAKED misc_bubbles16 (1 0 0) (-16 -16 0) (16 16 16) TURBULENT +health = density defaults to 32 +*/ +/*QUAKED misc_bubbles32 (1 0 0) (-32 -32 0) (32 32 16) TURBULENT +health = density defaults to 32 +*/ +/*QUAKED misc_bubbles64 (1 0 0) (-64 -64 0) (64 64 64) TURBULENT +health = density defaults to 32 +*/ + + +void snowInPVS( gentity_t *ent ) { + gentity_t *tent; + gentity_t *player; + qboolean inPVS = qfalse; + qboolean oldactive; + + oldactive = ent->active; + + ent->nextthink = level.time + FRAMETIME; + + player = AICast_FindEntityForName( "player" ); + + if ( player ) { + inPVS = trap_InPVS( player->r.currentOrigin, ent->r.currentOrigin ); + + if ( inPVS ) { + ent->active = qtrue; + } else { + ent->active = qfalse; + } + } else { + return; + } + + // there hasn't been a change so bail + if ( oldactive == ent->active ) { + return; + } + + if ( ent->active ) { + tent = G_TempEntity( player->r.currentOrigin, EV_SNOW_ON ); +// G_Printf( "on\n"); + } else + { + tent = G_TempEntity( player->r.currentOrigin, EV_SNOW_OFF ); +// G_Printf( "off\n"); + } + + tent->s.frame = ent->s.number; + trap_LinkEntity( ent ); +} + +void snow_think( gentity_t *ent ) { + trace_t tr; + vec3_t dest; + int turb; + + VectorCopy( ent->s.origin, dest ); + + if ( ent->spawnflags & 2 ) { // bubble + dest[2] += 8192; + } else { + dest[2] -= 8192; + } + + trap_Trace( &tr, ent->s.origin, NULL, NULL, dest, ent->s.number, MASK_SHOT ); + + if ( ent->spawnflags & 1 ) { + turb = 1; + } else { + turb = 0; + } + + if ( !Q_stricmp( ent->classname, "misc_snow256" ) ) { + G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW256, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue ); + } else if ( !Q_stricmp( ent->classname, "misc_snow128" ) ) { + G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW128, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue ); + } else if ( !Q_stricmp( ent->classname, "misc_snow64" ) ) { + G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW64, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue ); + } else if ( !Q_stricmp( ent->classname, "misc_snow32" ) ) { + G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW32, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue ); + } else if ( !Q_stricmp( ent->classname, "misc_bubbles8" ) ) { + G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE8, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue ); + } else if ( !Q_stricmp( ent->classname, "misc_bubbles16" ) ) { + G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE16, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue ); + } else if ( !Q_stricmp( ent->classname, "misc_bubbles32" ) ) { + G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE32, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue ); + } else if ( !Q_stricmp( ent->classname, "misc_bubbles64" ) ) { + G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE64, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue ); + } + + ent->think = snowInPVS; + ent->nextthink = level.time + FRAMETIME; + +} + +void SP_Snow( gentity_t *ent ) { + ent->think = snow_think; + ent->nextthink = level.time + FRAMETIME; + + G_SetOrigin( ent, ent->s.origin ); + + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + + trap_LinkEntity( ent ); + + if ( !ent->health ) { + ent->health = 32; + } + + ent->active = qtrue; +} +// done. + + +void SP_Bubbles( gentity_t *ent ) { + ent->think = snow_think; + ent->nextthink = level.time + FRAMETIME; + + G_SetOrigin( ent, ent->s.origin ); + + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + + trap_LinkEntity( ent ); + + if ( !ent->health ) { + ent->health = 32; + } + + ent->active = qtrue; + + ent->spawnflags |= 2; +} + +static vec3_t forward, right, up; +static vec3_t muzzle; + +void flakPuff( vec3_t origin, qboolean sky ) { + gentity_t *tent; + vec3_t point; + + VectorCopy( origin, point ); + if ( sky ) { + VectorMA( point, -256, forward, point ); + } + + point[2] += 16; // raise puff off the ground some + tent = G_TempEntity( point, EV_SMOKE ); + VectorCopy( point, tent->s.origin ); + tent->s.time = 2000; + tent->s.time2 = 1000; + tent->s.density = 0; + tent->s.angles2[0] = 16 + 8; + tent->s.angles2[1] = 48 + 24; + tent->s.angles2[2] = 10; +} + +/* +============== +Fire_Lead +============== +*/ +//----(SA) added 'activator' so the bits that used to expect 'ent' to be the gun still work +void Fire_Lead( gentity_t *ent, gentity_t *activator, float spread, int damage ) { + trace_t tr; + vec3_t end; + float r; + float u; + gentity_t *tent; + gentity_t *traceEnt; + //qboolean isflak = qfalse; + int seed = rand() & 255; + + r = Q_crandom( &seed ) * spread; + u = Q_crandom( &seed ) * spread; + + ent->s.eFlags |= EF_MG42_ACTIVE; + activator->s.eFlags |= EF_MG42_ACTIVE; + + VectorMA( muzzle, 8192, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + G_HistoricalTrace( ent, &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + AICast_ProcessBullet( activator, muzzle, tr.endpos ); + } + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { +// mg42_muzzleflash (ent); +// G_AddEvent( ent, EV_FIRE_WEAPON_MG42, 0 ); + +// JPW NERVE added this event so tracers work if you're shooting mg42 into skybox, otherwise shouldn't ever see the event double-up + tent = G_TempEntity( tr.endpos, EV_MG42BULLET_HIT_WALL ); + tent->s.otherEntityNum = ent->s.number; + tent->s.otherEntityNum2 = activator->s.number; + tent->s.effect1Time = seed; +// jpw + + return; + } + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzle ); + + // send bullet impact + if ( traceEnt->takedamage && traceEnt->client ) { + tent = G_TempEntity( tr.endpos, EV_MG42BULLET_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + tent->s.otherEntityNum = ent->s.number; + tent->s.otherEntityNum2 = activator->s.number; + tent->s.effect1Time = seed; + + if ( LogAccuracyHit( traceEnt, ent ) ) { + ent->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + + } else { + // Ridah, bullet impact should reflect off surface + vec3_t reflect; + float dot; + + tent = G_TempEntity( tr.endpos, EV_MG42BULLET_HIT_WALL ); + + dot = DotProduct( forward, tr.plane.normal ); + VectorMA( forward, -2 * dot, tr.plane.normal, reflect ); + VectorNormalize( reflect ); + + tent->s.eventParm = DirToByte( reflect ); + tent->s.otherEntityNum = ent->s.number; + tent->s.otherEntityNum2 = activator->s.number; // (SA) store the user id, so the client can position the tracer + tent->s.effect1Time = seed; + } + + if ( traceEnt->takedamage ) { + G_Damage( traceEnt, ent, activator, forward, tr.endpos, + damage, 0, MOD_MACHINEGUN ); + } + +// mg42_muzzleflash (ent); +// G_AddEvent( ent, EV_FIRE_WEAPON_MG42, 0 ); +} + + +float AngleDifference( float ang1, float ang2 ); +// NOTE: use this value, and THEN the cl_input.c scales to tweak the feel +#define MG42_IDLEYAWSPEED 80.0 // degrees per second (while returning to base) + +void clamp_hweapontofirearc( gentity_t *self, vec3_t dang ) { + float diff, yawspeed; + qboolean clamped; + + clamped = qfalse; + + // go back to start position + VectorCopy( self->s.angles, dang ); + yawspeed = MG42_IDLEYAWSPEED; + + if ( dang[0] < 0 && dang[0] < -( self->varc ) ) { + clamped = qtrue; + dang[0] = -( self->varc ); + } + + if ( dang[0] > 0 && dang[0] > ( self->varc / 2 ) ) { + clamped = qtrue; + dang[0] = self->varc / 2; + } + + // sanity check the angles again to make sure we don't go passed the harc + diff = AngleDifference( self->s.angles[YAW], dang[YAW] ); + if ( fabs( diff ) > self->harc ) { + clamped = qtrue; + + if ( diff > 0 ) { + dang[YAW] = AngleMod( self->s.angles[YAW] - self->harc ); + } else { + dang[YAW] = AngleMod( self->s.angles[YAW] + self->harc ); + } + } + +// if (g_mg42arc.integer) +// G_Printf ("varc = %5.2f\n", dang[0]); +} + +// NOTE: this only effects the external view of the user, when using the mg42, the +// view position is set on the client-side to keep it firm behind the gun with +// interpolation +void clamp_playerbehindgun( gentity_t *self, gentity_t *other, vec3_t dang ) { + vec3_t forward, right, up; + vec3_t point; + + + AngleVectors( self->s.apos.trBase, forward, right, up ); + VectorMA( self->r.currentOrigin, -36, forward, point ); + + point[2] = other->r.currentOrigin[2]; + trap_UnlinkEntity( other ); + SnapVector( point ); + VectorCopy( point, other->client->ps.origin ); + + // save results of pmove + BG_PlayerStateToEntityState( &other->client->ps, &other->s, qfalse ); + + // use the precise origin for linking + VectorCopy( other->client->ps.origin, other->r.currentOrigin ); + + // DHM - Nerve :: Zero out velocity + other->client->ps.velocity[0] = other->client->ps.velocity[1] = 0.f; + other->s.pos.trDelta[0] = other->s.pos.trDelta[1] = 0.f; + + trap_LinkEntity( other ); +} + +#define MG42_DAMAGE_MP 20 // DHM - Nerve :: testing with slowed firing rate // JPW NERVE was 30 + +void mg42_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + vec3_t dang; + int i; + + if ( !self->active ) { + return; + } + + if ( other->active ) { + for ( i = 0; i < 3; i++ ) + dang[i] = SHORT2ANGLE( other->client->pers.cmd.angles[i] ); + + // now tell the client to lock the view in the direction of the gun + other->client->ps.viewlocked = 3; + other->client->ps.viewlocked_entNum = self->s.number; + + if ( self->s.frame ) { + other->client->ps.gunfx = 1; + } else { + other->client->ps.gunfx = 0; + } + + // clamp player behind the gun + clamp_playerbehindgun( self, other, dang ); + } +} + +void mg42_fire( gentity_t *other ) { + gentity_t *self; + + self = &g_entities[other->client->ps.viewlocked_entNum]; + + //AngleVectors (self->s.apos.trBase, forward, right, up); + AngleVectors( other->client->ps.viewangles, forward, right, up ); + VectorCopy( self->s.pos.trBase, muzzle ); + + // VectorMA (muzzle, 16, forward, muzzle); // JPW NERVE unnecessary and makes it so close-range enemies get missed + VectorMA( muzzle, 16, up, muzzle ); + + self->s.eFlags |= EF_MG42_ACTIVE; + other->s.eFlags |= EF_MG42_ACTIVE; + + // snap to integer coordinates for more efficient network bandwidth usage + SnapVector( muzzle ); + + Fire_Lead( self, other, MG42_SPREAD_MP, MG42_DAMAGE_MP ); +} + +void mg42_track( gentity_t *self, gentity_t *other ) { + int i; + + if ( !self->active ) { + return; + } + + if ( other->active ) { + // move to the position over the next frame + VectorSubtract( other->client->ps.viewangles, self->s.apos.trBase, self->s.apos.trDelta ); + for ( i = 0; i < 3; i++ ) { + self->s.apos.trDelta[i] = AngleNormalize180( self->s.apos.trDelta[i] ); + } + VectorScale( self->s.apos.trDelta, 1000 / 50, self->s.apos.trDelta ); + self->s.apos.trTime = level.time; + self->s.apos.trDuration = 50; + + SnapVector( self->s.apos.trDelta ); + } +} + +#define USEMG42_DISTANCE 46 +void mg42_think( gentity_t *self ) { + vec3_t vec; + gentity_t *owner; + int i; + float len; + float usedist; + + if ( level.intermissiontime ) { + return; + } + + VectorClear( vec ); + + owner = &g_entities[self->r.ownerNum]; + + // move to the current angles + if ( self->timestamp > level.time ) { + BG_EvaluateTrajectory( &self->s.apos, level.time, self->s.apos.trBase ); + } + + if ( owner->client ) { + VectorSubtract( self->r.currentOrigin, owner->r.currentOrigin, vec ); + len = VectorLength( vec ); + + usedist = 96; + + if ( len < usedist && owner->active && owner->health > 0 ) { + // ATVI Wolfenstein Misc #433 + owner->client->ps.pm_flags &= ~PMF_DUCKED; + + self->active = qtrue; + owner->client->ps.persistant[PERS_HWEAPON_USE] = 1; + mg42_track( self, owner ); + self->nextthink = level.time + 50; + self->timestamp = level.time + 1000; + + if ( !( owner->r.svFlags & SVF_CASTAI ) ) { + clamp_playerbehindgun( self, owner, vec3_origin ); + } + return; + } + } + + self->active = qfalse; + + if ( owner->client ) { + owner->client->ps.persistant[PERS_HWEAPON_USE] = 0; + owner->client->ps.viewlocked = 0; // let them look around + owner->active = qfalse; + owner->client->ps.gunfx = 0; + } + + self->r.ownerNum = self->s.number; + self->s.otherEntityNum = self->s.number; + + if ( self->timestamp > level.time ) { + // slowly rotate back to position + clamp_hweapontofirearc( self, vec ); + // move to the position over the next frame + VectorSubtract( vec, self->s.apos.trBase, self->s.apos.trDelta ); + for ( i = 0; i < 3; i++ ) { + self->s.apos.trDelta[i] = AngleNormalize180( self->s.apos.trDelta[i] ); + } + VectorScale( self->s.apos.trDelta, 1000 / 50, self->s.apos.trDelta ); + self->s.apos.trTime = level.time; + self->s.apos.trDuration = 50; + } + self->nextthink = level.time + 50; + + SnapVector( self->s.apos.trDelta ); +} + +void mg42_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + gentity_t *gun; + gentity_t *owner; + trace_t tr; + + // DHM - Nerve :: self->chain not set if no tripod + if ( self->chain ) { + gun = self->chain; + } else { + gun = self; + } + // dhm - end + + owner = &g_entities[gun->r.ownerNum]; + + if ( gun && self->health <= 0 ) { + gun->s.frame = 2; + gun->takedamage = qfalse; + + // DHM - Nerve :: health is used in repairing later + if ( g_gametype.integer >= GT_WOLF ) { + gun->health = 0; + gun->s.eFlags = EF_SMOKING; // Make it smoke on client side + self->health = 0; + } + // dhm - end + } + + self->takedamage = qfalse; + + if ( owner && owner->client ) { + // Restore original position if current position is bad + trap_Trace( &tr, owner->r.currentOrigin, owner->r.mins, owner->r.maxs, owner->r.currentOrigin, owner->s.number, MASK_PLAYERSOLID ); + if ( tr.startsolid ) { + VectorCopy( owner->TargetAngles, owner->client->ps.origin ); + VectorCopy( owner->TargetAngles, owner->r.currentOrigin ); + owner->r.contents = CONTENTS_CORPSE; // this will correct itself in ClientEndFrame + } + owner->client->ps.eFlags &= ~EF_MG42_ACTIVE; // DHM - Nerve :: unset flag + owner->client->ps.persistant[PERS_HWEAPON_USE] = 0; + self->r.ownerNum = self->s.number; + self->s.otherEntityNum = self->s.number; + owner->client->ps.viewlocked = 0; // let them look around + owner->active = qfalse; + owner->client->ps.gunfx = 0; + + self->active = qfalse; + gun->active = qfalse; + } + + + trap_LinkEntity( self ); +} + +void mg42_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *owner; + + owner = &g_entities[ent->r.ownerNum]; + + if ( owner && owner->client ) { + owner->client->ps.persistant[PERS_HWEAPON_USE] = 0; + ent->r.ownerNum = ent->s.number; + ent->s.otherEntityNum = ent->s.number; + owner->client->ps.viewlocked = 0; // let them look around + owner->active = qfalse; + owner->client->ps.gunfx = 0; +// other-> // JPW NERVE FIXME insert other->someUnusedVariable = ent, so we can get proper kill/score info later + } + + // G_Printf ("mg42 called use function\n"); + + trap_LinkEntity( ent ); +} + +void mg42_spawn( gentity_t *ent ) { + gentity_t *base, *gun; + vec3_t offset; + + // Xian -- If in knifeonly mode, prevent MG42's from spawning + if ( g_knifeonly.integer != 1 ) { + // Need to spawn the base even when no tripod cause the gun itself isn't solid + base = G_Spawn(); + + if ( !( ent->spawnflags & 2 ) ) { // no tripod + base->clipmask = CONTENTS_SOLID; + base->r.contents = CONTENTS_SOLID; + base->r.svFlags = SVF_USE_CURRENT_ORIGIN; + base->s.eType = ET_GENERAL; + base->takedamage = qtrue; + base->die = mg42_die; + + base->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/mg42b.md3" ); + } else { + base->takedamage = qfalse; + } + + VectorSet( base->r.mins, -8, -8, -8 ); + VectorSet( base->r.maxs, 8, 8, 48 ); + VectorCopy( ent->s.origin, offset ); + offset[2] -= 24; + G_SetOrigin( base, offset ); + base->s.apos.trType = TR_STATIONARY; + base->s.apos.trTime = 0; + base->s.apos.trDuration = 0; + base->s.dmgFlags = HINT_MG42; // identify this for cursorhints + VectorCopy( ent->s.angles, base->s.angles ); + VectorCopy( base->s.angles, base->s.apos.trBase ); + VectorCopy( base->s.angles, base->s.apos.trDelta ); + base->health = ent->health; + base->target = ent->target; //----(SA) added so mounting mg42 can trigger targets + trap_LinkEntity( base ); + + // Spawn the barrel + gun = G_Spawn(); + gun->classname = "misc_mg42"; + gun->clipmask = CONTENTS_SOLID; + gun->r.contents = CONTENTS_TRIGGER; + gun->r.svFlags = SVF_USE_CURRENT_ORIGIN; + gun->s.eType = ET_MG42_BARREL; + gun->health = base->health; // JPW NERVE + + // DHM - Don't need to specify here, handled in G_CheckForCursorHints + //gun->s.dmgFlags = HINT_MG42; // identify this for cursorhints + + gun->touch = mg42_touch; + + gun->last_move_time = 0; + // DHM - Nerve :: Use a different model in multiplayer + gun->s.modelindex = G_ModelIndex( "models/multiplayer/mg42/mg42.md3" ); + VectorCopy( ent->s.origin, offset ); + offset[2] += 24; + G_SetOrigin( gun, offset ); + VectorSet( gun->r.mins, -24, -24, -8 ); + VectorSet( gun->r.maxs, 24, 24, 48 ); + gun->s.apos.trTime = 0; + gun->s.apos.trDuration = 0; + VectorCopy( ent->s.angles, gun->s.angles ); + VectorCopy( gun->s.angles, gun->s.apos.trBase ); + VectorCopy( gun->s.angles, gun->s.apos.trDelta ); + + VectorCopy( ent->s.angles, gun->s.angles2 ); + + gun->think = mg42_think; + gun->nextthink = level.time + FRAMETIME; + gun->timestamp = level.time + 1000; + gun->s.number = gun - g_entities; + gun->harc = ent->harc; + gun->varc = ent->varc; + gun->s.origin2[0] = ent->harc; + gun->s.origin2[1] = ent->varc; // need these clientsided + gun->s.apos.trType = TR_LINEAR_STOP; // interpolate the angles + gun->takedamage = qtrue; + gun->targetname = ent->targetname; // need this for scripting + gun->damage = ent->damage; + gun->accuracy = ent->accuracy; + gun->target = ent->target; //----(SA) added so mounting mg42 can trigger targets + gun->use = mg42_use; + gun->die = mg42_die; // JPW NERVE we want it to be called for non-tripod machineguns too (for mp_beach etc) + + if ( !( ent->spawnflags & 2 ) ) { // no tripod + gun->mg42BaseEnt = base->s.number; + } else { + gun->mg42BaseEnt = -1; + } + + gun->spawnflags = ent->spawnflags; + + trap_LinkEntity( gun ); + + if ( !( ent->spawnflags & 2 ) ) { // no tripod + base->chain = gun; + } + + G_FreeEntity( ent ); + } +} + +/*QUAKED misc_mg42 (1 0 0) (-16 -16 -24) (16 16 24) HIGH NOTRIPOD +harc = horizonal fire arc Default is 57.5 (left and right) +varc = vertical fire arc Default is 45 (upwards and 22.5 down) +health = how much damage can it take default is 50 +damage = determines how much the weapon will inflict if a non player uses it +"accuracy" all guns are 100% accurate an entry of 0.5 would make it 50% +*/ +void SP_mg42( gentity_t *self ) { + char *damage; + char *accuracy; + + if ( !self->harc ) { + self->harc = 57.5; + } else + { + if ( self->harc < 45 ) { + self->harc = 45; + } + } + + if ( !self->varc ) { + self->varc = 45.0; + } + + if ( !self->health ) { + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE + self->health = 100; + } +// JPW NERVE + else { + self->health = MG42_MULTIPLAYER_HEALTH; + } + } +// jpw + + self->think = mg42_spawn; + self->nextthink = level.time + FRAMETIME; + + snd_noammo = G_SoundIndex( "sound/weapons/noammo.wav" ); + + if ( G_SpawnString( "damage", "0", &damage ) ) { + self->damage = atoi( damage ); + } + + G_SpawnString( "accuracy", "1.0", &accuracy ); + + self->accuracy = atof( accuracy ); + + if ( !self->accuracy ) { + self->accuracy = 1; + } +// JPW NERVE + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + if ( !self->damage ) { + self->damage = 25; + } + } +// jpw +} + +#define FLAK_SPREAD 100 +#define FLAK_DAMAGE 36 + +#define GUN1_IDLE 0 +#define GUN2_IDLE 4 +#define GUN3_IDLE 8 +#define GUN4_IDLE 12 + +#define GUN1_LASTFIRE 3 +#define GUN2_LASTFIRE 7 +#define GUN3_LASTFIRE 11 +#define GUN4_LASTFIRE 15 + +void Flak_Animate( gentity_t *ent ) { + //G_Printf ("frame %i\n", ent->s.frame); + + if ( ent->s.frame == GUN1_IDLE + || ent->s.frame == GUN2_IDLE + || ent->s.frame == GUN3_IDLE + || ent->s.frame == GUN4_IDLE ) { + return; + } + + if ( ent->count == 1 ) { + if ( ent->s.frame == GUN1_LASTFIRE ) { + ent->s.frame = GUN2_IDLE; + } else if ( ent->s.frame > GUN1_IDLE ) { + ent->s.frame++; + } + } else if ( ent->count == 2 ) { + if ( ent->s.frame == GUN2_LASTFIRE ) { + ent->s.frame = GUN3_IDLE; + } else if ( ent->s.frame > GUN2_IDLE ) { + ent->s.frame++; + } + } else if ( ent->count == 3 ) { + if ( ent->s.frame == GUN3_LASTFIRE ) { + ent->s.frame = GUN4_IDLE; + } else if ( ent->s.frame > GUN3_IDLE ) { + ent->s.frame++; + } + } else if ( ent->count == 4 ) { + if ( ent->s.frame == GUN4_LASTFIRE ) { + ent->s.frame = GUN1_IDLE; + } else if ( ent->s.frame > GUN4_IDLE ) { + ent->s.frame++; + } + } +} + + +void flak_spawn( gentity_t *ent ) { + gentity_t *gun; + vec3_t offset; + + gun = G_Spawn(); + gun->classname = "misc_flak"; + gun->clipmask = CONTENTS_SOLID; + gun->r.contents = CONTENTS_TRIGGER; + gun->r.svFlags = SVF_USE_CURRENT_ORIGIN; + gun->s.eType = ET_GENERAL; + gun->touch = mg42_touch; + gun->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/flak_a.md3" ); + VectorCopy( ent->s.origin, offset ); + G_SetOrigin( gun, offset ); + VectorSet( gun->r.mins, -24, -24, -8 ); + VectorSet( gun->r.maxs, 24, 24, 48 ); + gun->s.apos.trTime = 0; + gun->s.apos.trDuration = 0; + VectorCopy( ent->s.angles, gun->s.angles ); + VectorCopy( gun->s.angles, gun->s.apos.trBase ); + VectorCopy( gun->s.angles, gun->s.apos.trDelta ); + gun->think = mg42_think; + gun->nextthink = level.time + FRAMETIME; + gun->s.number = gun - g_entities; + gun->harc = ent->harc; + gun->varc = ent->varc; + gun->s.apos.trType = TR_LINEAR_STOP; // interpolate the angles + gun->takedamage = qtrue; + gun->targetname = ent->targetname; // need this for scripting + gun->mg42BaseEnt = ent->s.number; + + trap_LinkEntity( gun ); + +} + +/*QUAKED misc_flak (1 0 0) (-32 -32 0) (32 32 100) +*/ +void SP_misc_flak( gentity_t *self ) { + + if ( !self->harc ) { + self->harc = 180; + } else + { + if ( self->harc < 90 ) { + self->harc = 115; + } + } + + if ( !self->varc ) { + self->varc = 90.0; + } + + if ( !self->health ) { + self->health = 100; + } + + self->think = flak_spawn; + self->nextthink = level.time + FRAMETIME; + + snd_noammo = G_SoundIndex( "sound/weapons/noammo.wav" ); +} + +/*QUAKED misc_spawner (.3 .7 .8) (-8 -8 -8) (8 8 8) +use the pickup name + when this entity gets used it will spawn an item +that matches its spawnitem field +e.i. +spawnitem +9mm +*/ + +void misc_spawner_think( gentity_t *ent ) { + + gitem_t *item; + gentity_t *drop = NULL; + + item = BG_FindItem( ent->spawnitem ); + + drop = Drop_Item( ent, item, 0, qfalse ); + + if ( !drop ) { + G_Printf( "-----> WARNING <-------\n" ); + G_Printf( "misc_spawner used at %s failed to drop!\n", vtos( ent->r.currentOrigin ) ); + } + +} + +void misc_spawner_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + + ent->think = misc_spawner_think; + ent->nextthink = level.time + FRAMETIME; + +// VectorCopy (other->r.currentOrigin, ent->r.currentOrigin); +// VectorCopy (ent->r.currentOrigin, ent->s.pos.trBase); + +// VectorCopy (other->r.currentAngles, ent->r.currentAngles); + + trap_LinkEntity( ent ); +} + +void SP_misc_spawner( gentity_t *ent ) { + if ( !ent->spawnitem ) { + G_Printf( "-----> WARNING <-------\n" ); + G_Printf( "misc_spawner at loc %s has no spawnitem!\n", vtos( ent->s.origin ) ); + return; + } + + ent->use = misc_spawner_use; + + trap_LinkEntity( ent ); + +} + +//=========================================================================== +// +// Mounted Gunner, attached to a moving vehicle of some-sort +// + +/*QUAKED misc_mounted_gunner (.4 .9 .7) (-16 -16 -24) (16 16 64) TriggerSpawn +Gunner controlling an MG42 to be mounted onto a vehicle. + +Gun must be able to rotate full 360 degrees, with slight pitching. + +The following entity script triggers will be called at appropriate times by the engine: + + trigger gunner_start_attack + trigger gunner_end_attack + trigger gunner_death + +Fields: + +*IMPORTANT* color = vector offest of gun mount tag on truck, from truck's origin +*IMPORTANT* delay = offset distance of gunnner mount tag on gun model, from gun's origin +target = targetname of vehicle we are attached to +aiTeam = 0 for Nazi, 1 for Allies, 2 for Monster +harc = visible arc, for initially sighting enemies Default is 220 +radius = visible distance, for sighting enemies Default is 4096 + +varc = vertical fire arc Default is 90 (45 above and below) +health = how much damage can it take default is 50 +accuracy = all guns are 100% accurate an entry of 0.5 would make it 50% +damage = damage caused by each bullet +*/ + +#define MG42_SPREAD 200 +#define MG42_DAMAGE_AI 9 + +void miscGunnerEnemyScan( gentity_t *ent, vec3_t angles ) { + gentity_t *t; + vec3_t v, tang; + + for ( t = g_entities; t < g_entities + level.maxclients; t++ ) { + if ( !t->inuse ) { + continue; + } + if ( t->health < 0 ) { + continue; + } + if ( ent->aiTeam == t->aiTeam ) { + continue; + } + if ( VectorDistance( ent->r.currentOrigin, t->r.currentOrigin ) > ent->radius ) { + continue; + } + VectorSubtract( t->r.currentOrigin, ent->r.currentOrigin, v ); + vectoangles( v, tang ); + if ( !AICast_InFieldOfVision( angles, ent->harc, tang ) ) { + continue; + } + if ( !AICast_VisibleFromPos( ent->r.currentOrigin, ent->s.number, t->r.currentOrigin, t->s.number, qfalse ) ) { + continue; + } + // found an enemy + ent->enemy = t; + break; + } +} + + +void miscGunnerThink( gentity_t *ent ) { + gentity_t *truck, *gun, *enemy; + vec3_t vec, trang, gspot, gang; + vec3_t fwd, r, u; + qboolean fire = qfalse; + float yawspeed, diff; + vec3_t dang; + qboolean clamped = qfalse; + int i; + + // find the entities + gun = &g_entities[ent->mg42BaseEnt]; + truck = &g_entities[gun->mg42BaseEnt]; + + // calculate our position based on that of the truck and gun angles + BG_EvaluateTrajectory( &truck->s.apos, level.time, trang ); + AngleVectors( trang, fwd, r, u ); + BG_EvaluateTrajectory( &truck->s.pos, level.time, gspot ); + VectorMA( gspot, ent->dl_color[0], fwd, gspot ); + VectorMA( gspot, -ent->dl_color[1], r, gspot ); + VectorMA( gspot, ent->dl_color[2], u, gspot ); + G_SetOrigin( ent, gspot ); + G_SetOrigin( gun, gspot ); + + // calculate our facing angles + //VectorAdd( trang, gun->r.currentAngles, gang ); + if ( !gun->s.density ) { + VectorCopy( trang, gang ); + } else { + BG_EvaluateTrajectory( &gun->s.apos, level.time, gang ); + } + + // look for an enemy + if ( ent->enemy ) { + if ( ent->enemy->health <= 0 ) { + ent->enemy = NULL; + } else if ( AICast_VisibleFromPos( ent->r.currentOrigin, ent->s.number, ent->enemy->r.currentOrigin, ent->enemy->s.number, qfalse ) ) { + fire = qtrue; + } + } + + if ( !fire ) { + miscGunnerEnemyScan( ent, trang ); + if ( ent->enemy ) { + fire = qtrue; + + // if we have just found our first enemy, release us from the "fixed tag" angles + // so we can track them + if ( !gun->s.density ) { + gun->s.density = 1; + // start facing the same direct, so we don't snap to a different angle immediately + BG_EvaluateTrajectory( &truck->s.apos, level.time, ent->s.angles ); + VectorCopy( gun->s.angles, gun->s.apos.trBase ); + gun->s.apos.trTime = level.time; + gun->s.apos.trDuration = 0; + gun->s.apos.trType = TR_STATIONARY; + VectorClear( gun->s.apos.trDelta ); + } + } + } + + // third, attack that enemy if possible + if ( fire ) { + + // get the enemy + enemy = ent->enemy; + + // rotate to them + VectorSubtract( enemy->r.currentOrigin, gun->r.currentOrigin, vec ); + vectoangles( vec, gun->TargetAngles ); + VectorCopy( gun->TargetAngles, dang ); + for ( i = 0; i < 3; i++ ) { + dang[i] = AngleNormalize180( dang[i] ); + } + + // restrict vertical range + if ( dang[0] < 0 && fabs( dang[0] ) > ( gun->varc / 2 ) ) { + clamped = qtrue; + if ( dang[0] < 0 ) { + dang[0] = -( gun->varc / 2 ); + } else { + dang[0] = ( gun->varc / 2 ); + } + } + + // dang is now the ideal angles, restrict movement by speed + yawspeed = 60; + for ( i = 0; i < 3; i++ ) { + BG_EvaluateTrajectory( &gun->s.apos, level.time, gun->r.currentAngles ); + diff = AngleDifference( dang[i], gun->r.currentAngles[i] ); + if ( fabs( diff ) > ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) ) { + clamped = qtrue; + if ( diff > 0 ) { + dang[i] = AngleMod( gun->r.currentAngles[i] + ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) ); + } else { + dang[i] = AngleMod( gun->r.currentAngles[i] - ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) ); + } + } + } + + // move to the position over the next frame + VectorSubtract( dang, gun->r.currentAngles, gun->s.apos.trDelta ); + for ( i = 0; i < 3; i++ ) { + gun->s.apos.trDelta[i] = AngleNormalize180( gun->s.apos.trDelta[i] ); + } + VectorCopy( gun->r.currentAngles, gun->s.apos.trBase ); + VectorScale( gun->s.apos.trDelta, 1000 / 50, gun->s.apos.trDelta ); + gun->s.apos.trTime = level.time; + gun->s.apos.trType = TR_LINEAR_STOP; + gun->s.apos.trDuration = 50; + + // if we are facing them, fire + if ( fabs( AngleNormalize180( gun->r.currentAngles[YAW] - gun->TargetAngles[YAW] ) ) < 10 ) { + AngleVectors( gun->r.currentAngles, forward, right, up ); + VectorCopy( gspot, muzzle ); + + VectorMA( muzzle, 16, forward, muzzle ); + VectorMA( muzzle, 16, up, muzzle ); + + // snap to integer coordinates for more efficient network bandwidth usage + SnapVector( muzzle ); + + if ( gun->damage ) { + Fire_Lead( gun, gun, MG42_SPREAD / gun->accuracy, gun->damage ); + } else { + Fire_Lead( gun, gun, MG42_SPREAD / gun->accuracy, MG42_DAMAGE_AI ); + } + } + } + + ent->think = miscGunnerThink; + ent->nextthink = level.time + 50; +} + +void miscGunnerSpawn( gentity_t *ent ) { + gentity_t *sp, *veh; + + veh = G_Find( NULL, FOFS( targetname ), ent->target ); + if ( !veh ) { + G_Error( "can't find vehicle with targetname \"%s\" for mounted gunner", ent->target ); + } + + // create the ring (base) + sp = G_Spawn(); + sp->classname = "misc_gunner_ring"; + sp->r.contents = 0; + sp->s.eType = ET_GENERAL; + sp->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/turret_c.md3" ); + sp->tagParent = veh; + sp->tagName = "tag_ring"; // tag to connect to + G_ProcessTagConnect( sp ); + trap_LinkEntity( sp ); + + // create the gun + sp = G_Spawn(); + sp->classname = "misc_gunner_gun"; + sp->r.contents = 0; + sp->s.eType = ET_GENERAL; + sp->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/turret_a.md3" ); + sp->tagParent = veh; + sp->tagName = "tag_rider"; // tag to connect to + G_ProcessTagConnect( sp ); + trap_LinkEntity( sp ); + + sp->mg42BaseEnt = veh->s.number; + sp->varc = ent->varc; + sp->health = ent->health; + sp->accuracy = ent->accuracy; + sp->damage = ent->damage; + + // setup the gunner ready for action + sp->r.contents = 0; // fixme, make damagable so we can shoot them + sp->s.eType = ET_GENERAL; + ent->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/turret_b.md3" ); + ent->tagParent = sp; + ent->tagName = "tag_hand"; // tag to connect to + G_ProcessTagConnect( ent ); + trap_LinkEntity( ent ); + + ent->mg42BaseEnt = sp->s.number; + + // start us thinking + ent->think = miscGunnerThink; + ent->nextthink = level.time + 50; +} + +void miscGunnerTriggerSpawn( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + miscGunnerSpawn( ent ); +} + +void SP_misc_mounted_gunner( gentity_t *self ) { + char *damage; + char *accuracy; + +// if (VectorCompare(vec3_origin, self->dl_color)) { + if ( VectorCompare( vec3_origin, self->rotate ) ) { + G_Error( "misc_mounted_gunner requires an offset position (color field)\n" ); + } + if ( !self->delay ) { + G_Error( "misc_mounted_gunner requires an offset distance from gun mount (delay field)\n" ); + } + + if ( !self->harc ) { + self->harc = 115; + } else + { + if ( self->harc < 45 ) { + self->harc = 45; + } + } + + if ( !self->varc ) { + self->varc = 90.0; + } + + if ( !self->health ) { + self->health = 100; + } + + if ( !self->radius ) { + self->radius = 4096; + } + + snd_noammo = G_SoundIndex( "sound/weapons/noammo.wav" ); + + if ( G_SpawnString( "damage", "0", &damage ) ) { + self->damage = atoi( damage ); + } + + G_SpawnString( "accuracy", "1.0", &accuracy ); + + self->accuracy = atof( accuracy ); + + if ( !self->accuracy ) { + self->accuracy = 1; + } + + // we are idle at this point + self->enemy = NULL; + + if ( self->spawnflags & 1 ) { // TRIGGER_SPAWN + self->use = miscGunnerTriggerSpawn; + } else { + // delay the actual spawning, so we are sure everything else exists + self->think = miscGunnerSpawn; + self->nextthink = level.time + FRAMETIME; + } +} + +void firetrail_die( gentity_t *ent ) { + G_FreeEntity( ent ); +} + +void firetrail_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->s.eType == ET_RAMJET ) { + ent->s.eType = ET_GENERAL; + } else { + ent->s.eType = ET_RAMJET; + } + + trap_LinkEntity( ent ); + +} + +/*QUAKED misc_firetrails (.4 .9 .7) (-16 -16 -16) (16 16 16) +This entity must target the plane its going to be attached to + + its use function will turn the fire stream effect on and off + + an alert entity call will kill it +*/ + +void misc_firetrails_think( gentity_t *ent ) { + gentity_t *left, *right, *airplane; + + airplane = G_Find( NULL, FOFS( targetname ), ent->target ); + if ( !airplane ) { + G_Error( "can't find airplane with targetname \"%s\" for firetrails", ent->target ); + } + + // left fire trail + left = G_Spawn(); + left->classname = "left_firetrail"; + left->r.contents = 0; + left->s.eType = ET_RAMJET; + left->s.modelindex = G_ModelIndex( "models/ammo/rocket/rocket.md3" ); + left->tagParent = airplane; + left->tagName = "tag_engine1"; // tag to connect to + left->use = firetrail_use; + left->AIScript_AlertEntity = firetrail_die; + left->targetname = ent->targetname; + G_ProcessTagConnect( left ); + trap_LinkEntity( left ); + + // right fire trail + right = G_Spawn(); + right->classname = "right_firetrail"; + right->r.contents = 0; + right->s.eType = ET_RAMJET; + right->s.modelindex = G_ModelIndex( "models/ammo/rocket/rocket.md3" ); + right->tagParent = airplane; + right->tagName = "tag_engine2"; // tag to connect to + right->use = firetrail_use; + right->AIScript_AlertEntity = firetrail_die; + right->targetname = ent->targetname; + G_ProcessTagConnect( right ); + trap_LinkEntity( right ); + +} + +void SP_misc_firetrails( gentity_t *ent ) { + ent->think = misc_firetrails_think; + ent->nextthink = level.time + 100; + +} diff --git a/src/game/g_missile.c b/src/game/g_missile.c new file mode 100644 index 0000000..015edd6 --- /dev/null +++ b/src/game/g_missile.c @@ -0,0 +1,1713 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + +#define MISSILE_PRESTEP_TIME 50 + + +extern void gas_think( gentity_t *gas ); +extern void gas_touch( gentity_t *gas, gentity_t *other, trace_t *trace ); +extern void SP_target_smoke( gentity_t *ent ); + + + +void G_ExplodeMissilePoisonGas( gentity_t *ent ); +void M_think( gentity_t *ent ); + + +// JPW NERVE +// think func for below +void Shaker_think( gentity_t *ent ) { + vec3_t vec; // muzzlebounce, JPW NERVE no longer used + gentity_t *player; + float len, radius = ent->splashDamage, bounceamt; + int i; + char cmd[64]; //DAJ +/* JPW NERVE used for trigger_concussive_dust, currently not working + vec3_t mins, maxs; // JPW NERVE + static vec3_t range; // JPW NERVE + int num,touch[MAX_GENTITIES],scored=0; // JPW NERVE + gentity_t *hit, *dirtshake; // JPW NERVE +*/ + + // NERVE - SMF - we only want to call this once now +// if (level.time > ent->delay) + ent->think = G_FreeEntity; + ent->nextthink = level.time + FRAMETIME; + +/* +// JPW NERVE check if we're close to trigger_concussive_dust fields + range[0] = radius/1.41f; // not exactly right, since we're doing a box trap for a radius, but wtf, + range[1] = radius/1.41f; // this is all eye candy anyway + range[2] = radius/1.41f; + + VectorAdd(ent->s.origin,range,maxs); + VectorSubtract(ent->s.origin,range,mins); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // get a list of possibles + for ( i=0 ; is.eType & ET_CONCUSSIVE_TRIGGER) { // add a tempent to shake some shit loose + dirtshake = G_Spawn(); + dirtshake->nextthink = level.time + radius; // 1000 for aircraft flyby, other term for tumble stagger + VectorAdd(hit->r.maxs,hit->r.mins,vec); + VectorScale(vec,0.5f,vec); + VectorCopy(vec,dirtshake->s.pos.trBase); + VectorCopy(vec,dirtshake->s.origin); + VectorSubtract(vec,ent->s.origin,vec); + dirtshake->nextthink = level.time + 5000;//(radius - VectorLength(vec)); // closer the explosion, the longer the dirtshake + G_Printf("radius=%f dist=%f\n",radius,VectorLength(vec)); + dirtshake->think = G_FreeEntity; + dirtshake->s.eType = ET_CONCUSSIVE_TRIGGER + ET_EVENTS; + dirtshake->s.eFlags |= EF_SMOKINGBLACK; + VectorCopy(hit->r.maxs, dirtshake->r.maxs); + VectorCopy(hit->r.mins, dirtshake->r.mins); + dirtshake->s.pos.trType = TR_STATIONARY; + dirtshake->clipmask = 0; + dirtshake->r.svFlags &= ~SVF_NOCLIENT; + SnapVector(dirtshake->r.maxs); + SnapVector(dirtshake->r.mins); + SnapVector(dirtshake->s.pos.trDelta); + } + } +*/ + + for ( i = 0; i < level.maxclients; i++ ) { + // skip if not connected + if ( level.clients[i].pers.connected != CON_CONNECTED ) { + continue; + } + // skip if in limbo + if ( level.clients[i].ps.pm_flags & PMF_LIMBO ) { + continue; + } + // skip if not on same team + if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR ) { + continue; + } + + // found a live one + player = &g_entities[i]; + VectorSubtract( player->r.currentOrigin, ent->s.origin, vec ); + len = VectorLength( vec ); + + if ( len > radius ) { // largest bomb blast = 600 + continue; + } + + // NERVE - SMF - client side camera shake + //DAJ BUGFIX va() not doing %f's correctly + bounceamt = min( 1.0f, 1.0f - ( len / radius ) ); + sprintf( cmd, "shake %.4f", bounceamt ); //DAJ + trap_SendServerCommand( player->s.clientNum, cmd ); +//DAJ BUGFIX trap_SendServerCommand( player->s.clientNum, va( "shake %f", &bounceamt)); + } +} +// jpw + +// JPW NERVE +/* +============= +Ground_Shaker + like concussive_fx but means it +============= +*/ +void Ground_Shaker( vec3_t origin, float range ) { + gentity_t *concussive; + + concussive = G_Spawn(); + VectorCopy( origin, concussive->s.origin ); + concussive->think = Shaker_think; + concussive->nextthink = level.time + FRAMETIME; + concussive->splashDamage = range; + concussive->delay = level.time + 200; // NERVE - SMF - changed from 1000 to 200 + return; +} +// jpw + + +/* +================ +G_BounceMissile + +================ +*/ +void G_BounceMissile( gentity_t *ent, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + +// Arnout: removed this for MP as well (was already gone from SP) +/* + // Ridah, if we are a grenade, and we have hit an AI that is waiting to catch us, give them a grenade, and delete ourselves + if ((ent->splashMethodOfDeath == MOD_GRENADE_SPLASH) && (g_entities[trace->entityNum].flags & FL_AI_GRENADE_KICK) && + (trace->endpos[2] > g_entities[trace->entityNum].r.currentOrigin[2])) { + g_entities[trace->entityNum].grenadeExplodeTime = ent->nextthink; + g_entities[trace->entityNum].flags &= ~FL_AI_GRENADE_KICK; + Add_Ammo( &g_entities[trace->entityNum], WP_GRENADE_LAUNCHER, 1, qfalse ); //----(SA) modified + G_FreeEntity( ent ); + return; + } +*/ + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2 * dot, trace->plane.normal, ent->s.pos.trDelta ); + + // RF, record this for mover pushing + if ( trace->plane.normal[2] > 0.2 /*&& VectorLength( ent->s.pos.trDelta ) < 40*/ ) { + ent->s.groundEntityNum = trace->entityNum; + } + + if ( ent->s.eFlags & EF_BOUNCE_HALF ) { + if ( ent->s.eFlags & EF_BOUNCE ) { // both flags marked, do a third type of bounce + VectorScale( ent->s.pos.trDelta, 0.35, ent->s.pos.trDelta ); + } else { + VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta ); + } + + // check for stop + if ( trace->plane.normal[2] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 ) { +//----(SA) make the world the owner of the dynamite, so the player can shoot it after it stops moving + if ( ent->s.weapon == WP_DYNAMITE || ent->s.weapon == WP_DYNAMITE2 ) { + ent->r.ownerNum = ENTITYNUM_WORLD; + } +//----(SA) end + G_SetOrigin( ent, trace->endpos ); + return; + } + } + + SnapVector( ent->s.pos.trDelta ); + + VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin ); + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + + SnapVector( ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; +} + +/* +================ +G_MissileImpact + impactDamage is how much damage the impact will do to func_explosives +================ +*/ +void G_MissileImpact( gentity_t *ent, trace_t *trace, int impactDamage ) { + gentity_t *other; + qboolean hitClient = qfalse; + vec3_t velocity; + int etype; + + other = &g_entities[trace->entityNum]; + + // handle func_explosives + if ( other->classname && Q_stricmp( other->classname, "func_explosive" ) == 0 ) { + // the damage is sufficient to "break" the ent (health == 0 is non-breakable) + if ( other->health && impactDamage >= other->health ) { + // check for other->takedamage needs to be inside the health check since it is + // likely that, if successfully destroyed by the missile, in the next runmissile() + // update takedamage would be set to '0' and the func_explosive would not be + // removed yet, causing a bounce. + if ( other->takedamage ) { + BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity ); + G_Damage( other, ent, &g_entities[ent->r.ownerNum], velocity, ent->s.origin, impactDamage, 0, ent->methodOfDeath ); + } + + // its possible of the func_explosive not to die from this and it + // should reflect the missile or explode it not vanish into oblivion + if ( other->health <= 0 ) { + return; + } + } + } + + // check for bounce + if ( !other->takedamage && + ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) { + G_BounceMissile( ent, trace ); + // JPW NERVE -- spotter White Phosphorous rounds shouldn't bounce noise + if ( !Q_stricmp( ent->classname,"WP" ) ) { + return; + } + // jpw + if ( !Q_stricmp( ent->classname, "flamebarrel" ) ) { + G_AddEvent( ent, EV_FLAMEBARREL_BOUNCE, 0 ); + } else { + G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 ); + } + return; + } + + if ( other->takedamage && ent->s.density == 1 ) { + G_ExplodeMissilePoisonGas( ent ); + return; + } + + // impact damage + if ( other->takedamage ) { + if ( ent->damage ) { + + if ( LogAccuracyHit( other, &g_entities[ent->r.ownerNum] ) ) { + if ( g_entities[ent->r.ownerNum].client ) { + g_entities[ent->r.ownerNum].client->ps.persistant[PERS_ACCURACY_HITS]++; + } + hitClient = qtrue; + } + BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity ); + if ( VectorLength( velocity ) == 0 ) { + velocity[2] = 1; // stepped on a grenade + } + G_Damage( other, ent, &g_entities[ent->r.ownerNum], velocity, + ent->s.origin, ent->damage, + 0, ent->methodOfDeath ); + } else // if no damage value, then this is a splash damage grenade only + { + G_BounceMissile( ent, trace ); + return; + } + } + + // is it cheaper in bandwidth to just remove this ent and create a new + // one, rather than changing the missile into the explosion? + + if ( other->takedamage && other->client ) { + G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) ); + ent->s.otherEntityNum = other->s.number; + } else { + // Ridah, try projecting it in the direction it came from, for better decals + vec3_t dir; + BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, dir ); + BG_GetMarkDir( dir, trace->plane.normal, dir ); + G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); + } + + ent->freeAfterEvent = qtrue; + + // change over to a normal entity right at the point of impact + etype = ent->s.eType; + ent->s.eType = ET_GENERAL; + + SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth + + G_SetOrigin( ent, trace->endpos ); + + // splash damage (doesn't apply to person directly hit) + if ( ent->splashDamage ) { + if ( G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius, + other, ent->splashMethodOfDeath ) ) { + if ( !hitClient && g_entities[ent->r.ownerNum].client ) { + g_entities[ent->r.ownerNum].client->ps.persistant[PERS_ACCURACY_HITS]++; + } + } + } + + trap_LinkEntity( ent ); +} + +/* +============== +Concussive_think +============== +*/ +void Concussive_think( gentity_t *ent ) { + gentity_t *player; + vec3_t dir; + vec3_t kvel; + float grav = 24; + vec3_t vec; + float len; + + if ( level.time > ent->delay ) { + ent->think = G_FreeEntity; + } + + ent->nextthink = level.time + FRAMETIME; + + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE -- in multiplayer this should be handled by ground_shaker + player = AICast_FindEntityForName( "player" ); + + if ( !player ) { + return; + } + + VectorSubtract( player->r.currentOrigin, ent->s.origin, vec ); + len = VectorLength( vec ); + +// G_Printf ("len = %5.3f\n", len); + + if ( len > 512 ) { + return; + } + + VectorSet( dir, 0, 0, 1 ); + VectorScale( dir, grav, kvel ); + VectorAdd( player->client->ps.velocity, kvel, player->client->ps.velocity ); + + if ( !player->client->ps.pm_time ) { + int t; + + t = grav * 2; + if ( t < 50 ) { + t = 50; + } + if ( t > 200 ) { + t = 200; + } + player->client->ps.pm_time = t; + player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + + } +} + +/* +============== +Concussive_fx + shake the player + caused by explosives (grenades/dynamite/etc.) +============== +*/ +//void Concussive_fx (gentity_t *ent) +void Concussive_fx( vec3_t origin ) { +// gentity_t *tent; +// gentity_t *player; + + gentity_t *concussive; + + concussive = G_Spawn(); +// VectorCopy (ent->s.origin, concussive->s.origin); + VectorCopy( origin, concussive->s.origin ); + concussive->think = Concussive_think; + concussive->nextthink = level.time + FRAMETIME; + concussive->delay = level.time + 500; + return; + +// Grenade and bomb flinching event +/* + player = AICast_FindEntityForName( "player" ); + + if (!player) + return; + + if ( trap_InPVS (player->r.currentOrigin, ent->s.origin) ) + { + tent = G_TempEntity (ent->s.origin, EV_CONCUSSIVE); + VectorCopy (ent->s.origin, tent->s.origin); + tent->s.density = player->s.number; + + // G_Printf ("sending concussive event\n"); + } +*/ + +} + + + +/* +============== +M_think +============== +*/ +void M_think( gentity_t *ent ) { + gentity_t *tent; + + ent->count++; + +// if (ent->count == 1) +// Concussive_fx (ent); //----(SA) moved to G_ExplodeMissile() + + if ( ent->count == ent->health ) { + ent->think = G_FreeEntity; + } + + tent = G_TempEntity( ent->s.origin, EV_SMOKE ); + VectorCopy( ent->s.origin, tent->s.origin ); + if ( ent->s.density == 1 ) { + tent->s.origin[2] += 16; + } else { + // tent->s.origin[2]+=32; + // Note to self Maxx said to lower the spawn loc for the smoke 16 units + tent->s.origin[2] += 16; + } + + tent->s.time = 3000; + tent->s.time2 = 100; + tent->s.density = 0; + if ( ent->s.density == 1 ) { + tent->s.angles2[0] = 16; + } else { + // Note to self Maxx changed this to 24 + tent->s.angles2[0] = 24; + } + tent->s.angles2[1] = 96; + tent->s.angles2[2] = 50; + + ent->nextthink = level.time + FRAMETIME; + +} + +/* +================ +G_ExplodeMissile + +Explode a missile without an impact +================ +*/ +void G_ExplodeMissile( gentity_t *ent ) { + vec3_t dir; + vec3_t origin; + qboolean small = qfalse; + qboolean zombiespit = qfalse; + int etype; + + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + SnapVector( origin ); + G_SetOrigin( ent, origin ); + + // we don't have a valid direction, so just point straight up + dir[0] = dir[1] = 0; + dir[2] = 1; + + etype = ent->s.eType; + + ent->s.eType = ET_GENERAL; + + if ( !Q_stricmp( ent->classname, "props_explosion" ) ) { + G_AddEvent( ent, EV_MISSILE_MISS_SMALL, DirToByte( dir ) ); + small = qtrue; + } +// JPW NERVE + else if ( !Q_stricmp( ent->classname, "air strike" ) ) { + G_AddEvent( ent, EV_MISSILE_MISS_LARGE, DirToByte( dir ) ); + small = qfalse; + } +// jpw + else if ( !Q_stricmp( ent->classname, "props_explosion_large" ) ) { + G_AddEvent( ent, EV_MISSILE_MISS_LARGE, DirToByte( dir ) ); + small = qfalse; + } else if ( !Q_stricmp( ent->classname, "zombiespit" ) ) { + G_AddEvent( ent, EV_SPIT_MISS, DirToByte( dir ) ); + zombiespit = qtrue; + } else if ( !Q_stricmp( ent->classname, "flamebarrel" ) ) { + ent->freeAfterEvent = qtrue; + trap_LinkEntity( ent ); + return; + } else { + G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); + } + + ent->freeAfterEvent = qtrue; + + // splash damage + if ( ent->splashDamage ) { + if ( G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent, ent->splashMethodOfDeath ) ) { //----(SA) + if ( g_entities[ent->r.ownerNum].client ) { + g_entities[ent->r.ownerNum].client->ps.persistant[PERS_ACCURACY_HITS]++; + } + } + } + + trap_LinkEntity( ent ); + + if ( etype == ET_MISSILE ) { + // DHM - Nerve :: ... in single player anyway + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + if ( ent->s.weapon == WP_VENOM_FULL ) { // no default impact smoke + zombiespit = qtrue; + } else if ( ent->s.weapon == WP_DYNAMITE || ent->s.weapon == WP_DYNAMITE2 ) { +// // shot heard round the world... + gentity_t *player; + player = AICast_FindEntityForName( "player" ); + Concussive_fx( player->r.currentOrigin ); + } + } +// JPW NERVE -- big nasty dynamite scoring section + else { + if ( g_gametype.integer >= GT_WOLF ) { + if ( ent->s.weapon == WP_DYNAMITE ) { // do some scoring +// check if dynamite is in trigger_objective_info field + vec3_t mins, maxs; + //static vec3_t range = { 18, 18, 18 }; // NOTE can use this to massage throw distance outside trigger field // TTimo unused + int i,num,touch[MAX_GENTITIES]; + gentity_t *hit; + + // NERVE - SMF - made this the actual bounding box of dynamite instead of range + VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); + VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); + VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs ); + + for ( i = 0 ; i < num ; i++ ) { + hit = &g_entities[touch[i]]; + if ( !hit->target ) { + continue; + } + + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + if ( !strcmp( hit->classname,"trigger_objective_info" ) ) { + if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) { + continue; + } + + if ( ( ( hit->spawnflags & AXIS_OBJECTIVE ) && ( ent->s.teamNum == TEAM_BLUE ) ) || + ( ( hit->spawnflags & ALLIED_OBJECTIVE ) && ( ent->s.teamNum == TEAM_RED ) ) ) { + G_UseTargets( hit,ent ); + hit->think = G_FreeEntity; + hit->nextthink = level.time + FRAMETIME; + + if ( ent->parent->client ) { + if ( ent->s.teamNum == ent->parent->client->sess.sessionTeam ) { // make sure player hasn't changed teams -- per atvi req + AddScore( ent->parent, hit->accuracy ); // set from map, see g_trigger + } + } + } + } + } + } + } + // give big weapons the shakey shakey + if ( ent->s.weapon == WP_DYNAMITE || ent->s.weapon == WP_PANZERFAUST || ent->s.weapon == WP_GRENADE_LAUNCHER || + ent->s.weapon == WP_GRENADE_PINEAPPLE || ent->s.weapon == WP_ROCKET_LAUNCHER || ent->s.weapon == WP_MORTAR || + ent->s.weapon == WP_ARTY ) { + Ground_Shaker( ent->r.currentOrigin, ent->splashDamage * 4 ); + } + return; + } +// jpw + } + + + if ( !zombiespit ) { + gentity_t *Msmoke; + + Msmoke = G_Spawn(); + VectorCopy( ent->r.currentOrigin, Msmoke->s.origin ); + if ( small ) { + Msmoke->s.density = 1; + } + Msmoke->think = M_think; + Msmoke->nextthink = level.time + FRAMETIME; + + if ( ent->parent && !Q_stricmp( ent->parent->classname, "props_flamebarrel" ) ) { + Msmoke->health = 10; + } else { + Msmoke->health = 5; + } + + Concussive_fx( Msmoke->s.origin ); + } +} + +/* +================ +G_MissileDie +================ +*/ +void G_MissileDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + if ( inflictor == self ) { + return; + } + self->takedamage = qfalse; + self->think = G_ExplodeMissile; + self->nextthink = level.time + 10; +} + +/* +================ +G_ExplodeMissilePoisonGas + +Explode a missile without an impact +================ +*/ +void G_ExplodeMissilePoisonGas( gentity_t *ent ) { + vec3_t dir; + vec3_t origin; + + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + SnapVector( origin ); + G_SetOrigin( ent, origin ); + + // we don't have a valid direction, so just point straight up + dir[0] = dir[1] = 0; + dir[2] = 1; + + ent->freeAfterEvent = qtrue; + + + { + gentity_t *gas; + + gas = G_Spawn(); + gas->think = gas_think; + gas->nextthink = level.time + FRAMETIME; + gas->r.contents = CONTENTS_TRIGGER; + gas->touch = gas_touch; + gas->health = 100; + G_SetOrigin( gas, origin ); + + trap_LinkEntity( gas ); + } + +} + +/* +================ +G_RunMissile + +================ +*/ +void G_RunMissile( gentity_t *ent ) { + vec3_t origin; + trace_t tr; + int impactDamage; + + // Ridah, make AI aware of this danger + // DHM - Nerve :: Only in single player + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + AICast_CheckDangerousEntity( ent, DANGER_MISSILE, ent->splashRadius, 0.1, 0.99, qtrue ); + } + + // get current position + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + + if ( ( ent->clipmask & CONTENTS_BODY ) && ( ent->s.weapon == WP_DYNAMITE || ent->s.weapon == WP_ARTY + || ent->s.weapon == WP_GRENADE_LAUNCHER || ent->s.weapon == WP_GRENADE_PINEAPPLE ) ) { + + if ( !ent->s.pos.trDelta[0] && !ent->s.pos.trDelta[1] && !ent->s.pos.trDelta[2] ) { + ent->clipmask &= ~CONTENTS_BODY; + } + } + + // trace a line from the previous position to the current position, + // ignoring interactions with the missile owner + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, + ent->r.ownerNum, ent->clipmask ); + + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + if ( tr.startsolid ) { + tr.fraction = 0; + } + + trap_LinkEntity( ent ); + + if ( tr.fraction != 1 ) { + // never explode or bounce on sky + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + // If grapple, reset owner + if ( ent->parent && ent->parent->client && ent->parent->client->hook == ent ) { + ent->parent->client->hook = NULL; + } + G_FreeEntity( ent ); + return; + } + + if ( ent->s.weapon == WP_ROCKET_LAUNCHER || ent->s.weapon == WP_PANZERFAUST ) { + impactDamage = 999; // goes through pretty much any func_explosives + } else { + impactDamage = 20; // "grenade"/"dynamite" // probably adjust this based on velocity + + } + G_MissileImpact( ent, &tr, impactDamage ); + + if ( ent->s.eType != ET_MISSILE ) { +// JPW NERVE + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + Ground_Shaker( ent->r.currentOrigin, ent->splashDamage * 4 ); + } +// jpw + return; // exploded + } + } + + // check think function after bouncing + G_RunThink( ent ); +} + +/* +================ +G_PredictBounceMissile + +================ +*/ +void G_PredictBounceMissile( gentity_t *ent, trajectory_t *pos, trace_t *trace, int time ) { + vec3_t velocity, origin; + float dot; + int hitTime; + + BG_EvaluateTrajectory( pos, time, origin ); + + // reflect the velocity on the trace plane + hitTime = time; + BG_EvaluateTrajectoryDelta( pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2 * dot, trace->plane.normal, pos->trDelta ); + + if ( ent->s.eFlags & EF_BOUNCE_HALF ) { + if ( ent->s.eFlags & EF_BOUNCE ) { // both flags marked, do a third type of bounce + VectorScale( pos->trDelta, 0.35, pos->trDelta ); + } else { + VectorScale( pos->trDelta, 0.65, pos->trDelta ); + } + + // check for stop + if ( trace->plane.normal[2] > 0.2 && VectorLength( pos->trDelta ) < 40 ) { + VectorCopy( trace->endpos, pos->trBase ); + return; + } + } + + VectorAdd( origin, trace->plane.normal, pos->trBase ); + pos->trTime = time; +} + +/* +================ +G_PredictMissile + + selfNum is the character that is checking to see what the missile is going to do + + returns qfalse if the missile won't explode, otherwise it'll return the time is it expected to explode +================ +*/ +int G_PredictMissile( gentity_t *ent, int duration, vec3_t endPos, qboolean allowBounce ) { + vec3_t origin; + trace_t tr; + int time; + trajectory_t pos; + vec3_t org; + gentity_t backupEnt; + + pos = ent->s.pos; + BG_EvaluateTrajectory( &pos, level.time, org ); + + backupEnt = *ent; + + for ( time = level.time + FRAMETIME; time < level.time + duration; time += FRAMETIME ) { + + // get current position + BG_EvaluateTrajectory( &pos, time, origin ); + + // trace a line from the previous position to the current position, + // ignoring interactions with the missile owner + trap_Trace( &tr, org, ent->r.mins, ent->r.maxs, origin, + ent->r.ownerNum, ent->clipmask ); + + VectorCopy( tr.endpos, org ); + + if ( tr.startsolid ) { + *ent = backupEnt; + return qfalse; + } + + if ( tr.fraction != 1 ) { + // never explode or bounce on sky + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + *ent = backupEnt; + return qfalse; + } + + if ( allowBounce && ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) { + G_PredictBounceMissile( ent, &pos, &tr, time - FRAMETIME + (int)( (float)FRAMETIME * tr.fraction ) ); + pos.trTime = time; + continue; + } + + // exploded, so drop out of loop + break; + } + } +/* + if (!allowBounce && tr.fraction < 1 && tr.entityNum > level.maxclients) { + // go back a bit in time, so we can catch it in the air + time -= 200; + if (time < level.time + FRAMETIME) + time = level.time + FRAMETIME; + BG_EvaluateTrajectory( &pos, time, org ); + } +*/ + + // get current position + VectorCopy( org, endPos ); + // set the entity data back + *ent = backupEnt; + // + if ( allowBounce && ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) { + return ent->nextthink; + } else { // it will probably explode before it times out + return time; + } +} + +//============================================================================= +// DHM - Nerve :: Server side Flamethrower +//============================================================================= + +// copied from cg_flamethrower.c +#define FLAME_START_SIZE 1.0 +#define FLAME_START_MAX_SIZE 100.0 // when the flame is spawned, it should endevour to reach this size +#define FLAME_START_SPEED 1200.0 // speed of flame as it leaves the nozzle +#define FLAME_MIN_SPEED 60.0 + +// these are calculated (don't change) +#define FLAME_LENGTH ( FLAMETHROWER_RANGE + 50.0 ) // NOTE: only modify the range, since this should always reflect that range + +#define FLAME_LIFETIME (int)( ( FLAME_LENGTH / FLAME_START_SPEED ) * 1000 ) // life duration in milliseconds +#define FLAME_FRICTION_PER_SEC ( 2.0f * FLAME_START_SPEED ) +#define GET_FLAME_SIZE_SPEED( x ) ( ( (float)x / FLAME_LIFETIME ) / 0.3 ) // x is the current sizeMax + +#define FLAME_THRESHOLD 50 + +void G_FlameDamage( gentity_t *self ) { + gentity_t *body; + int entityList[MAX_GENTITIES]; + int i, e, numListedEntities; + float radius, boxradius, dist; + vec3_t mins, maxs, point, v; + trace_t tr; + + radius = self->speed; + boxradius = 1.41421356 * radius; // radius * sqrt(2) for bounding box enlargement + + for ( i = 0 ; i < 3 ; i++ ) { + mins[i] = self->r.currentOrigin[i] - boxradius; + maxs[i] = self->r.currentOrigin[i] + boxradius; + } + + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) { + body = &g_entities[entityList[ e ]]; + + if ( !body->takedamage ) { + continue; + } + +// JPW NERVE don't catch fire if invulnerable or same team in no FF + if ( body->client ) { + if ( body->client->ps.powerups[PW_INVULNERABLE] >= level.time ) { + body->flameQuota = 0; + body->s.onFireEnd = level.time - 1; + continue; + } + if ( !( g_friendlyFire.integer ) && OnSameTeam( body,self->parent ) ) { + continue; + } + } +// jpw + +// JPW NERVE don't catch fire if under water or invulnerable + if ( body->waterlevel >= 3 ) { + body->flameQuota = 0; + body->s.onFireEnd = level.time - 1; + continue; + } +// jpw + + if ( !body->r.bmodel ) { + VectorCopy( body->r.currentOrigin, point ); + if ( body->client ) { + point[2] += body->client->ps.viewheight; + } + VectorSubtract( point, self->r.currentOrigin, v ); + } else { + for ( i = 0 ; i < 3 ; i++ ) { + if ( self->s.origin[i] < body->r.absmin[i] ) { + v[i] = body->r.absmin[i] - self->r.currentOrigin[i]; + } else if ( self->r.currentOrigin[i] > body->r.absmax[i] ) { + v[i] = self->r.currentOrigin[i] - body->r.absmax[i]; + } else { + v[i] = 0; + } + } + } + + dist = VectorLength( v ); + + // The person who shot the flame only burns when within 1/2 the radius + if ( body->s.number == self->r.ownerNum && dist >= ( radius * 0.5 ) ) { + continue; + } + if ( dist >= radius ) { + continue; + } + + // Non-clients that take damage get damaged here + if ( !body->client ) { + if ( body->health > 0 ) { + G_Damage( body, self->parent, self->parent, vec3_origin, self->r.currentOrigin, 2, 0, MOD_FLAMETHROWER ); + } + continue; + } + + // JPW NERVE -- do a trace to see if there's a wall btwn. body & flame centroid -- prevents damage through walls + trap_Trace( &tr, self->r.currentOrigin, NULL, NULL, point, body->s.number, MASK_SHOT ); + if ( tr.fraction < 1.0 ) { + continue; + } + // jpw + + // now check the damageQuota to see if we should play a pain animation + // first reduce the current damageQuota with time + if ( body->flameQuotaTime && body->flameQuota > 0 ) { + body->flameQuota -= (int)( ( (float)( level.time - body->flameQuotaTime ) / 1000 ) * 2.5f ); + if ( body->flameQuota < 0 ) { + body->flameQuota = 0; + } + } + + G_BurnMeGood( self, body ); + } +} + +void G_RunFlamechunk( gentity_t *ent ) { + vec3_t vel, add; + vec3_t neworg; + trace_t tr; + float speed, dot; + + // Adust the current speed of the chunk + if ( level.time - ent->timestamp > 50 ) { + VectorCopy( ent->s.pos.trDelta, vel ); + speed = VectorNormalize( vel ); + speed -= ( 50.f / 1000.f ) * FLAME_FRICTION_PER_SEC; + + if ( speed < FLAME_MIN_SPEED ) { + speed = FLAME_MIN_SPEED; + } + + VectorScale( vel, speed, ent->s.pos.trDelta ); + } else { + speed = FLAME_START_SPEED; + } + + // Move the chunk + VectorScale( ent->s.pos.trDelta, 50.f / 1000.f, add ); + VectorAdd( ent->r.currentOrigin, add, neworg ); + + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, neworg, ent->r.ownerNum, MASK_SHOT | MASK_WATER ); // JPW NERVE + + if ( tr.startsolid ) { + VectorCopy( vec3_origin, ent->s.pos.trDelta ); + } else if ( tr.fraction != 1.0f && !( tr.surfaceFlags & SURF_NOIMPACT ) ) { + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + dot = DotProduct( vel, tr.plane.normal ); + VectorMA( vel, -2 * dot, tr.plane.normal, vel ); + VectorNormalize( vel ); + speed *= 0.5 * ( 0.25 + 0.75 * ( ( dot + 1.0 ) * 0.5 ) ); + VectorScale( vel, speed, ent->s.pos.trDelta ); + } else { + VectorCopy( neworg, ent->r.currentOrigin ); + } + + // Do damage to nearby entities, every 100ms + if ( ent->flameQuotaTime <= level.time ) { + ent->flameQuotaTime = level.time + 100; + G_FlameDamage( ent ); + } + + // Show debugging bbox + if ( g_debugBullets.integer > 3 ) { + gentity_t *bboxEnt; + float size = ent->speed / 2; + vec3_t b1, b2; + vec3_t temp; + VectorSet( temp, -size, -size, -size ); + VectorCopy( ent->r.currentOrigin, b1 ); + VectorCopy( ent->r.currentOrigin, b2 ); + VectorAdd( b1, temp, b1 ); + VectorSet( temp, size, size, size ); + VectorAdd( b2, temp, b2 ); + bboxEnt = G_TempEntity( b1, EV_RAILTRAIL ); + VectorCopy( b2, bboxEnt->s.origin2 ); + bboxEnt->s.dmgFlags = 1; // ("type") + } + + // Adjust the size + if ( ent->speed < FLAME_START_MAX_SIZE ) { + ent->speed += 10.f; + + if ( ent->speed > FLAME_START_MAX_SIZE ) { + ent->speed = FLAME_START_MAX_SIZE; + } + } + + // Remove after 2 seconds + if ( level.time - ent->timestamp > ( FLAME_LIFETIME - 150 ) ) { // JPW NERVE increased to 350 from 250 to match visuals better + G_FreeEntity( ent ); + return; + } + + G_RunThink( ent ); +} + +/* +================= +fire_flamechunk +================= +*/ +gentity_t *fire_flamechunk( gentity_t *self, vec3_t start, vec3_t dir ) { + gentity_t *bolt; + + // Only spawn every other frame + if ( self->count2 ) { + self->count2--; + return NULL; + } + + self->count2 = 1; + VectorNormalize( dir ); + + bolt = G_Spawn(); + bolt->classname = "flamechunk"; + + bolt->timestamp = level.time; + bolt->flameQuotaTime = level.time + 50; + bolt->s.eType = ET_FLAMETHROWER_CHUNK; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_NOCLIENT; + bolt->s.weapon = self->s.weapon; + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->methodOfDeath = MOD_FLAMETHROWER; + bolt->clipmask = MASK_MISSILESHOT; + + bolt->s.pos.trType = TR_DECCELERATE; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + bolt->s.pos.trDuration = 800; + + // 'speed' will be the current size radius of the chunk + bolt->speed = FLAME_START_SIZE; + VectorSet( bolt->r.mins, -4, -4, -4 ); + VectorSet( bolt->r.maxs, 4, 4, 4 ); + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, FLAME_START_SPEED, bolt->s.pos.trDelta ); + + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +//============================================================================= + +//----(SA) removed unused quake3 weapons. + +int G_GetWeaponDamage( int weapon ); // JPW NERVE + +void DynaSink( gentity_t *self ) { + + self->clipmask = 0; + self->r.contents = 0; + + if ( self->timestamp < level.time ) { + self->think = G_FreeEntity; + self->nextthink = level.time + FRAMETIME; + return; + } + + self->s.pos.trBase[2] -= 0.5f; + self->nextthink = level.time + 50; +} +/* +================= +fire_grenade + + NOTE!!!! NOTE!!!!! + + This accepts a /non-normalized/ direction vector to allow specification + of how hard it's thrown. Please scale the vector before calling. + +================= +*/ +gentity_t *fire_grenade( gentity_t *self, vec3_t start, vec3_t dir, int grenadeWPID ) { + gentity_t *bolt; + qboolean noExplode = qfalse; + + bolt = G_Spawn(); + + // no self->client for shooter_grenade's + if ( self->client && self->client->ps.grenadeTimeLeft ) { + // TTimo + // was: if( g_gametype.integer < GT_WOLF && grenadeWPID == WP_DYNAMITE || grenadeWPID == WP_DYNAMITE2) { + // gcc: suggest parentheses around && within || + if ( g_gametype.integer < GT_WOLF && ( grenadeWPID == WP_DYNAMITE || grenadeWPID == WP_DYNAMITE2 ) ) { // remove any fraction of a 5 second 'click' + self->client->ps.grenadeTimeLeft *= 5; + self->client->ps.grenadeTimeLeft -= ( self->client->ps.grenadeTimeLeft % 5000 ); + self->client->ps.grenadeTimeLeft += 5000; + if ( self->client->ps.grenadeTimeLeft < 5000 ) { // allow dropping of dynamite that won't explode (for shooting) + noExplode = qtrue; + } + } + + if ( !noExplode ) { + bolt->nextthink = level.time + self->client->ps.grenadeTimeLeft; + } + } else { + bolt->nextthink = level.time + 2500; + } + + // TTimo + // was: if( g_gametype.integer >= GT_WOLF && grenadeWPID == WP_DYNAMITE || grenadeWPID == WP_DYNAMITE2 ) { + // gcc: suggest parentheses around && within || + if ( g_gametype.integer >= GT_WOLF && ( grenadeWPID == WP_DYNAMITE || grenadeWPID == WP_DYNAMITE2 ) ) { + noExplode = qtrue; + bolt->nextthink = level.time + 15000; + bolt->think = DynaSink; + bolt->timestamp = level.time + 16500; + } + + // no self->client for shooter_grenade's + if ( self->client ) { + self->client->ps.grenadeTimeLeft = 0; // reset grenade timer + + } + if ( !noExplode ) { + bolt->think = G_ExplodeMissile; + } + + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST; + bolt->s.weapon = grenadeWPID; + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + +// JPW NERVE -- commented out bolt->damage and bolt->splashdamage, override with G_GetWeaponDamage() +// so it works with different netgame balance. didn't uncomment bolt->damage on dynamite 'cause its so *special* + bolt->damage = G_GetWeaponDamage( grenadeWPID ); // overridden for dynamite + bolt->splashDamage = G_GetWeaponDamage( grenadeWPID ); +// jpw + + switch ( grenadeWPID ) { + case WP_GRENADE_LAUNCHER: + bolt->classname = "grenade"; +// bolt->damage = 100; +// bolt->splashDamage = 100; + if ( g_gametype.integer >= GT_WOLF ) { + bolt->splashRadius = 300; + } else { + bolt->splashRadius = 150; + } + bolt->methodOfDeath = MOD_GRENADE; + bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH; + bolt->s.eFlags = EF_BOUNCE_HALF | EF_BOUNCE; + break; + case WP_GRENADE_PINEAPPLE: + bolt->classname = "grenade"; +// bolt->damage = 80; +// bolt->splashDamage = 80; + bolt->splashRadius = 300; + bolt->methodOfDeath = MOD_GRENADE; + bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH; + bolt->s.eFlags = EF_BOUNCE_HALF | EF_BOUNCE; + break; +// JPW NERVE + case WP_SMOKE_GRENADE: + bolt->classname = "grenade"; + bolt->s.eFlags = EF_BOUNCE_HALF | EF_BOUNCE; + break; +// jpw + case WP_DYNAMITE: + case WP_DYNAMITE2: + + bolt->accuracy = 0; // JPW NERVE sets to score below if dynamite is in trigger_objective_info & it's an objective + trap_SendServerCommand( self - g_entities, "cp \"Dynamite is set, but NOT armed!\"" ); + // differentiate non-armed dynamite with non-pulsing dlight + bolt->s.teamNum = self->client->sess.sessionTeam + 4; + bolt->classname = "dynamite"; + bolt->damage = 0; +// bolt->splashDamage = 300; + bolt->splashRadius = 400; + bolt->methodOfDeath = MOD_DYNAMITE; + bolt->splashMethodOfDeath = MOD_DYNAMITE_SPLASH; + bolt->s.eFlags = ( EF_BOUNCE | EF_BOUNCE_HALF ); // EF_BOUNCE_HEAVY; + + // dynamite is shootable + // JPW NERVE only in single player + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + bolt->health = 5; + bolt->takedamage = qtrue; + bolt->die = G_MissileDie; + } else { + bolt->health = 5; + bolt->takedamage = qfalse; + } + // jpw + + bolt->r.contents = CONTENTS_CORPSE; // (player can walk through) + + // nope - this causes the dynamite to impact on the players bb when he throws it. + // will try setting it when it settles +// bolt->r.ownerNum = ENTITYNUM_WORLD; // (SA) make the world the owner of the dynamite, so the player can shoot it without modifying the bullet code to ignore players id for hits + + // small target cube + VectorSet( bolt->r.mins, -12, -12, 0 ); + VectorCopy( bolt->r.mins, bolt->r.absmin ); + VectorSet( bolt->r.maxs, 12, 12, 20 ); + VectorCopy( bolt->r.maxs, bolt->r.absmax ); + break; + } + +// JPW NERVE -- blast radius proportional to damage + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + bolt->splashRadius = G_GetWeaponDamage( grenadeWPID ); + } +// jpw + + bolt->clipmask = MASK_MISSILESHOT; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorCopy( dir, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +//============================================================================= + + +/* +============== +fire_speargun +============== +*/ +#define SPEAR_WATERSPEED 400 +#define SPEAR_AIRSPEED 700 + +gentity_t *fire_speargun( gentity_t *self, vec3_t start, vec3_t dir ) { + gentity_t *bolt; + + VectorNormalize( dir ); + + bolt = G_Spawn(); + bolt->classname = "spear"; + bolt->nextthink = level.time + 10000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + bolt->s.weapon = WP_SPEARGUN; + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = 15; // (SA) spear damage here + bolt->splashDamage = 0; + bolt->methodOfDeath = MOD_SPEARGUN; + bolt->clipmask = MASK_MISSILESHOT; + + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + + // (SA) Kind of a cheap hack to make the speargun worthless out of the water + // This'll probably change to something better + if ( ( trap_PointContents( start, -1 ) & CONTENTS_WATER ) ) { + bolt->s.pos.trType = TR_LINEAR; + VectorScale( dir, SPEAR_WATERSPEED, bolt->s.pos.trDelta ); + } else { + bolt->s.pos.trType = TR_GRAVITY_LOW; + VectorScale( dir, SPEAR_AIRSPEED, bolt->s.pos.trDelta ); + } + + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + + return bolt; +} + + +/* +================= +fire_rocket +================= +*/ +gentity_t *fire_rocket( gentity_t *self, vec3_t start, vec3_t dir ) { + gentity_t *bolt; + + VectorNormalize( dir ); + + bolt = G_Spawn(); + bolt->classname = "rocket"; + bolt->nextthink = level.time + 20000; // push it out a little + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST; + + //DHM - Nerve :: Use the correct weapon in multiplayer + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + bolt->s.weapon = WP_ROCKET_LAUNCHER; + } else { + bolt->s.weapon = self->s.weapon; + } + + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = G_GetWeaponDamage( WP_ROCKET_LAUNCHER ); // JPW NERVE + bolt->splashDamage = G_GetWeaponDamage( WP_ROCKET_LAUNCHER ); // JPW NERVE +// JPW NERVE + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + bolt->splashRadius = G_GetWeaponDamage( WP_ROCKET_LAUNCHER ); + } else { + bolt->splashRadius = 120; + } +// jpw + bolt->methodOfDeath = MOD_ROCKET; + bolt->splashMethodOfDeath = MOD_ROCKET_SPLASH; +// bolt->clipmask = MASK_SHOT; + bolt->clipmask = MASK_MISSILESHOT; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); +// JPW NERVE + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + VectorScale( dir,2500,bolt->s.pos.trDelta ); + } else { + VectorScale( dir, 900, bolt->s.pos.trDelta ); + } +// jpw + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + +// Rafael flamebarrel +/* +====================== +fire_flamebarrel +====================== +*/ + +gentity_t *fire_flamebarrel( gentity_t *self, vec3_t start, vec3_t dir ) { + gentity_t *bolt; + + VectorNormalize( dir ); + + bolt = G_Spawn(); + bolt->classname = "flamebarrel"; + bolt->nextthink = level.time + 3000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_FLAMEBARREL; + bolt->s.eFlags = EF_BOUNCE_HALF; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_ROCKET_LAUNCHER; + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = 100; + bolt->splashDamage = 20; + bolt->splashRadius = 60; + bolt->s.eFlags |= EF_SMOKINGBLACK; + + bolt->methodOfDeath = MOD_ROCKET; + bolt->splashMethodOfDeath = MOD_ROCKET_SPLASH; + + bolt->clipmask = MASK_MISSILESHOT; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, 900 + ( crandom() * 100 ), bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + + +// Rafael sniper +/* +================= +fire_lead +================= +*/ + +void fire_lead( gentity_t *self, vec3_t start, vec3_t dir, int damage ) { + + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + vec3_t forward, right, up; + vec3_t angles; + float r, u; + qboolean anti_tank_enable = qfalse; + + r = crandom() * self->random; + u = crandom() * self->random; + + vectoangles( dir, angles ); + AngleVectors( angles, forward, right, up ); + + VectorMA( start, 8192, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + trap_Trace( &tr, start, NULL, NULL, end, self->s.number, MASK_SHOT ); + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, start ); + + // send bullet impact + if ( traceEnt->takedamage && traceEnt->client ) { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + } else { + // Ridah, bullet impact should reflect off surface + vec3_t reflect; + float dot; + + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL ); + + dot = DotProduct( forward, tr.plane.normal ); + VectorMA( forward, -2 * dot, tr.plane.normal, reflect ); + VectorNormalize( reflect ); + + tent->s.eventParm = DirToByte( reflect ); + // done. + } + tent->s.otherEntityNum = self->s.number; + + if ( traceEnt->takedamage ) { + + if ( self->s.weapon == WP_SNIPER + && traceEnt->s.eType == ET_MOVER + && traceEnt->aiName[0] ) { + anti_tank_enable = qtrue; + } + + if ( anti_tank_enable ) { + self->s.weapon = WP_ROCKET_LAUNCHER; + } + + G_Damage( traceEnt, self, self, forward, tr.endpos, + damage, 0, MOD_MACHINEGUN ); + + if ( anti_tank_enable ) { + self->s.weapon = WP_SNIPER; + } + + } + +} + + +// Rafael sniper +// visible + +/* +============== +visible +============== +*/ +qboolean visible( gentity_t *self, gentity_t *other ) { +// vec3_t spot1; +// vec3_t spot2; + trace_t tr; + gentity_t *traceEnt; + + trap_Trace( &tr, self->r.currentOrigin, NULL, NULL, other->r.currentOrigin, self->s.number, MASK_SHOT ); + + traceEnt = &g_entities[ tr.entityNum ]; + + if ( traceEnt == other ) { + return qtrue; + } + + return qfalse; + +} + + + +/* +============== +fire_mortar + dir is a non-normalized direction/power vector +============== +*/ +gentity_t *fire_mortar( gentity_t *self, vec3_t start, vec3_t dir ) { + gentity_t *bolt; + +// VectorNormalize (dir); + + if ( self->spawnflags ) { + gentity_t *tent; + tent = G_TempEntity( self->s.pos.trBase, EV_MORTAREFX ); + tent->s.density = self->spawnflags; // send smoke and muzzle flash flags + VectorCopy( self->s.pos.trBase, tent->s.origin ); + VectorCopy( self->s.apos.trBase, tent->s.angles ); + } + + bolt = G_Spawn(); + bolt->classname = "mortar"; + bolt->nextthink = level.time + 20000; // push it out a little + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST; // broadcast sound. not multiplayer friendly, but for mortars it should be okay + // bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + bolt->s.weapon = WP_MORTAR; + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = G_GetWeaponDamage( WP_MORTAR ); // JPW NERVE + bolt->splashDamage = G_GetWeaponDamage( WP_MORTAR ); // JPW NERVE + bolt->splashRadius = 120; + bolt->methodOfDeath = MOD_MORTAR; + bolt->splashMethodOfDeath = MOD_MORTAR_SPLASH; + bolt->clipmask = MASK_MISSILESHOT; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); +// VectorScale( dir, 900, bolt->s.pos.trDelta ); + VectorCopy( dir, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + + +/* +================= +fire_nail +================= +*/ +#define NAILGUN_SPREAD 1000 + +gentity_t *fire_nail( gentity_t *self, vec3_t start, vec3_t forward, vec3_t right, vec3_t up ) { + gentity_t *bolt; + vec3_t dir; + vec3_t end; + float r, u, scale; + + bolt = G_Spawn(); + bolt->classname = "nail"; + bolt->nextthink = level.time + 10000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; +// bolt->s.weapon = WP_NAILGUN; + bolt->s.weapon = WP_VENOM_FULL; + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = G_GetWeaponDamage( WP_VENOM_FULL ); +// bolt->methodOfDeath = MOD_NAIL; + bolt->methodOfDeath = MOD_VENOM_FULL; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time; + VectorCopy( start, bolt->s.pos.trBase ); + + r = random() * M_PI * 2.0f; + u = sin( r ) * crandom() * NAILGUN_SPREAD * 16; + r = cos( r ) * crandom() * NAILGUN_SPREAD * 16; + VectorMA( start, 8192 * 16, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + VectorSubtract( end, start, dir ); + VectorNormalize( dir ); + +// JPW NERVE + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + scale = 555 + random() * 1800; + } else { + scale = 1200 + random() * 2500; + } +// jpw + VectorScale( dir, scale, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); + + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} + + +/* +================= +fire_prox +================= +*/ +gentity_t *fire_prox( gentity_t *self, vec3_t start, vec3_t dir ) { + gentity_t *bolt; + + VectorNormalize( dir ); + + bolt = G_Spawn(); + bolt->classname = "prox mine"; + bolt->nextthink = level.time + 3000; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; +// bolt->s.weapon = WP_PROX_LAUNCHER; + bolt->s.eFlags = 0; + bolt->r.ownerNum = self->s.number; + bolt->parent = self; + bolt->damage = 0; + bolt->splashDamage = 100; + bolt->splashRadius = 150; +// bolt->methodOfDeath = MOD_PROXIMITY_MINE; +// bolt->splashMethodOfDeath = MOD_PROXIMITY_MINE; + bolt->clipmask = MASK_SHOT; + bolt->target_ent = NULL; + // count is used to check if the prox mine left the player bbox + // if count == 1 then the prox mine left the player bbox and can attack to it + bolt->count = 0; + + //FIXME: we prolly wanna abuse another field +// bolt->s.generic1 = self->client->sess.sessionTeam; + + bolt->s.pos.trType = TR_GRAVITY; + bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, 700, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + + VectorCopy( start, bolt->r.currentOrigin ); + + return bolt; +} diff --git a/src/game/g_mover.c b/src/game/g_mover.c new file mode 100644 index 0000000..5a8d6c9 --- /dev/null +++ b/src/game/g_mover.c @@ -0,0 +1,4570 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/* + * name: g_mover.c + * + * desc: + * +*/ + +#include "g_local.h" + +char *hintStrings[] = { + "", // HINT_NONE + "HINT_NONE", // actually HINT_FORCENONE, but since this is being specified in the ent, the designer actually means HINT_FORCENONE + "HINT_PLAYER", + "HINT_ACTIVATE", + "HINT_DOOR", + "HINT_DOOR_ROTATING", + "HINT_DOOR_LOCKED", + "HINT_DOOR_ROTATING_LOCKED", + "HINT_MG42", + "HINT_BREAKABLE", + "HINT_BREAKABLE_BIG", + "HINT_CHAIR", + "HINT_ALARM", + "HINT_HEALTH", + "HINT_TREASURE", + "HINT_KNIFE", + "HINT_LADDER", + "HINT_BUTTON", + "HINT_WATER", + "HINT_CAUTION", + "HINT_DANGER", + "HINT_SECRET", + "HINT_QUESTION", + "HINT_EXCLAMATION", + "HINT_CLIPBOARD", + "HINT_WEAPON", + "HINT_AMMO", + "HINT_ARMOR", + "HINT_POWERUP", + "HINT_HOLDABLE", + "HINT_INVENTORY", + "HINT_SCENARIC", + "HINT_EXIT", + "HINT_PLYR_FRIEND", + "HINT_PLYR_NEUTRAL", + "HINT_PLYR_ENEMY", + "HINT_PLYR_UNKNOWN", + "HINT_BUILD", // DHM - Nerve + "HINT_DISARM", // DHM - Nerve + "HINT_REVIVE", // DHM - Nerve + "HINT_DYNAMITE", // DHM - Nerve + + + "", // HINT_BAD_USER +}; + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +void MatchTeam( gentity_t *teamLeader, int moverState, int time ); +void Reached_Train( gentity_t *ent ); +void Think_BeginMoving( gentity_t *ent ); +void Use_Func_Rotate( gentity_t * ent, gentity_t * other, gentity_t * activator ); +void Blocked_Door( gentity_t *ent, gentity_t *other ); +void Blocked_DoorRotate( gentity_t *ent, gentity_t *other ); + +#define PUSH_STACK_DEPTH 3 +int pushedStackDepth = 0; + +typedef struct { + gentity_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_GENTITIES * PUSH_STACK_DEPTH], *pushed_p; // Arnout *PUSH_STACK_DEPTH to prevent overflows + + +/* +============ +G_TestEntityPosition + +============ +*/ +gentity_t *G_TestEntityPosition( gentity_t *ent ) { + trace_t tr; + int mask; + + if ( ent->clipmask ) { +// if ( ent->r.contents == CONTENTS_CORPSE && ent->health <= 0 ) { // Arnout: players waiting to be revived are important +// if ( ent->r.contents == CONTENTS_CORPSE ) { + // corpse aren't important + //G_Damage( ent, NULL, NULL, NULL, NULL, 99999, 0, MOD_CRUSH ); +// return NULL; +// } else { + mask = ent->clipmask; +// } + } else { + mask = MASK_SOLID; + } + if ( ent->client ) { + trap_TraceCapsule( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask ); + } else if ( ent->s.eType == ET_CORPSE ) { + vec3_t pos; + VectorCopy( ent->s.pos.trBase, pos ); + pos[2] += 4; // move up a bit - corpses normally got their origin slightly in the ground + trap_Trace( &tr, pos, ent->r.mins, ent->r.maxs, pos, ent->s.number, mask ); + // don't crush corpses against players +// if( tr.startsolid && g_entities[ tr.entityNum ].client ) +// return NULL; + } else if ( ent->s.eType == ET_MISSILE ) { + trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->r.ownerNum, mask ); + } else { + trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask ); + } + + if ( tr.startsolid ) { + return &g_entities[ tr.entityNum ]; + } + + return NULL; +} + +/* +============ +G_TestEntityDropToFloor + +============ +*/ +void G_TestEntityDropToFloor( gentity_t *ent, float maxdrop ) { + trace_t tr; + int mask; + vec3_t endpos; + + if ( ent->clipmask ) { + mask = ent->clipmask; + } else { + mask = MASK_SOLID; + } + if ( ent->client ) { + VectorCopy( ent->client->ps.origin, endpos ); + } else { + VectorCopy( ent->s.pos.trBase, endpos ); + } + + endpos[2] -= maxdrop; + if ( ent->client ) { + trap_TraceCapsule( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, endpos, ent->s.number, mask ); + } else { + trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, endpos, ent->s.number, mask ); + } + + VectorCopy( tr.endpos, ent->s.pos.trBase ); + if ( ent->client ) { + VectorCopy( tr.endpos, ent->client->ps.origin ); + } +} + +/* +============ +G_TestEntityMoveTowardsPos + +============ +*/ +void G_TestEntityMoveTowardsPos( gentity_t *ent, vec3_t pos ) { + trace_t tr; + int mask; + + if ( ent->clipmask ) { + mask = ent->clipmask; + } else { + mask = MASK_SOLID; + } + if ( ent->client ) { + trap_TraceCapsule( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, pos, ent->s.number, mask ); + } else { + trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, pos, ent->s.number, mask ); + } + + VectorCopy( tr.endpos, ent->s.pos.trBase ); + if ( ent->client ) { + VectorCopy( tr.endpos, ent->client->ps.origin ); + } +} + +/* +================ +G_CreateRotationMatrix +================ +*/ +void G_CreateRotationMatrix( const vec3_t angles, vec3_t matrix[3] ) { + AngleVectors( angles, matrix[0], matrix[1], matrix[2] ); + VectorInverse( matrix[1] ); +} + +/* +================ +G_TransposeMatrix +================ +*/ +void G_TransposeMatrix( const vec3_t matrix[3], vec3_t transpose[3] ) { + int i, j; + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + transpose[i][j] = matrix[j][i]; + } + } +} + +/* +================ +G_RotatePoint +================ +*/ +void G_RotatePoint( vec3_t point, const vec3_t matrix[3] ) { + vec3_t tvec; + + VectorCopy( point, tvec ); + point[0] = DotProduct( matrix[0], tvec ); + point[1] = DotProduct( matrix[1], tvec ); + point[2] = DotProduct( matrix[2], tvec ); +} + + +/* +================== +G_TryPushingEntity + +Returns qfalse if the move is blocked +================== +*/ +qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) { + vec3_t org, org2, move2; + gentity_t *block; + vec3_t matrix[3], transpose[3]; + float x, fx, y, fy, z, fz; +#define JITTER_INC 4 +#define JITTER_MAX ( check->r.maxs[0] / 2.0 ) + + // EF_MOVER_STOP will just stop when contacting another entity + // instead of pushing it, but entities can still ride on top of it + if ( ( pusher->s.eFlags & EF_MOVER_STOP ) && + check->s.groundEntityNum != pusher->s.number ) { + //pusher->s.eFlags |= EF_MOVER_BLOCKED; + return qfalse; + } + + // save off the old position + if ( pushed_p > &pushed[MAX_GENTITIES * PUSH_STACK_DEPTH] ) { + G_Error( "pushed_p > &pushed[MAX_GENTITIES*PUSH_STACK_DEPTH]" ); + } + pushed_p->ent = check; + VectorCopy( check->s.pos.trBase, pushed_p->origin ); + VectorCopy( check->s.apos.trBase, pushed_p->angles ); + if ( check->client ) { + pushed_p->deltayaw = check->client->ps.delta_angles[YAW]; + VectorCopy( check->client->ps.origin, pushed_p->origin ); + } + pushed_p++; + + // try moving the contacted entity + VectorAdd( check->s.pos.trBase, move, check->s.pos.trBase ); + if ( check->client ) { + // make sure the client's view rotates when on a rotating mover + // RF, this is done client-side now + check->client->ps.delta_angles[YAW] += ANGLE2SHORT( amove[YAW] ); + // + // RF, AI's need their ideal angle adjusted instead + if ( check->aiCharacter ) { + AICast_AdjustIdealYawForMover( check->s.number, ANGLE2SHORT( amove[YAW] ) ); + } + } + + // figure movement due to the pusher's amove + G_CreateRotationMatrix( amove, transpose ); + G_TransposeMatrix( (const vec3_t *)transpose, matrix ); + VectorSubtract( check->s.pos.trBase, pusher->r.currentOrigin, org ); + if ( check->client ) { + VectorSubtract( check->client->ps.origin, pusher->r.currentOrigin, org ); + } + VectorCopy( org, org2 ); + G_RotatePoint( org2, (const vec3_t *)matrix ); + VectorSubtract( org2, org, move2 ); + VectorAdd( check->s.pos.trBase, move2, check->s.pos.trBase ); + if ( check->client ) { + VectorAdd( check->client->ps.origin, move, check->client->ps.origin ); + VectorAdd( check->client->ps.origin, move2, check->client->ps.origin ); + } + + // may have pushed them off an edge + if ( check->s.groundEntityNum != pusher->s.number ) { + check->s.groundEntityNum = -1; + } + + block = G_TestEntityPosition( check ); + if ( !block ) { + // pushed ok + if ( check->client ) { + VectorCopy( check->client->ps.origin, check->r.currentOrigin ); + } else { + VectorCopy( check->s.pos.trBase, check->r.currentOrigin ); + } + return qtrue; + } + +// TESTTEST + // Arnout, if blocking entity is a player, try to move this player first. + if ( block->client ) { + pushedStackDepth++; + if ( pushedStackDepth < PUSH_STACK_DEPTH && G_TryPushingEntity( block, pusher, move, amove ) ) { + // pushed ok + if ( check->client ) { + VectorCopy( check->client->ps.origin, check->r.currentOrigin ); + } else { + VectorCopy( check->s.pos.trBase, check->r.currentOrigin ); + } + return qtrue; + } + pushedStackDepth--; + } +// TESTTEST + + // RF, if still not valid, move them around to see if we can find a good spot + if ( JITTER_MAX > JITTER_INC ) { + VectorCopy( check->s.pos.trBase, org ); + if ( check->client ) { + VectorCopy( check->client->ps.origin, org ); + } + for ( z = 0; z < JITTER_MAX; z += JITTER_INC ) + for ( fz = -z; fz <= z; fz += 2 * z ) { + for ( x = JITTER_INC; x < JITTER_MAX; x += JITTER_INC ) + for ( fx = -x; fx <= x; fx += 2 * x ) { + for ( y = JITTER_INC; y < JITTER_MAX; y += JITTER_INC ) + for ( fy = -y; fy <= y; fy += 2 * y ) { + VectorSet( move2, fx, fy, fz ); + VectorAdd( org, move2, org2 ); + VectorCopy( org2, check->s.pos.trBase ); + if ( check->client ) { + VectorCopy( org2, check->client->ps.origin ); + } + + // + // do the test + block = G_TestEntityPosition( check ); + if ( !block ) { + // pushed ok + if ( check->client ) { + VectorCopy( check->client->ps.origin, check->r.currentOrigin ); + } else { + VectorCopy( check->s.pos.trBase, check->r.currentOrigin ); + } + return qtrue; + } + } + } + if ( !fz ) { + break; + } + } + // didnt work, so set the position back + VectorCopy( org, check->s.pos.trBase ); + if ( check->client ) { + VectorCopy( org, check->client->ps.origin ); + } + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // Sliding trapdoors can cause this. + VectorCopy( ( pushed_p - 1 )->origin, check->s.pos.trBase ); + if ( check->client ) { + VectorCopy( ( pushed_p - 1 )->origin, check->client->ps.origin ); + } + VectorCopy( ( pushed_p - 1 )->angles, check->s.apos.trBase ); + block = G_TestEntityPosition( check ); + if ( !block ) { + check->s.groundEntityNum = -1; + pushed_p--; + return qtrue; + } + // blocked + return qfalse; +} + + +/* +============ +G_MoverPush + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +If qfalse is returned, *obstacle will be the blocking entity +============ +*/ +qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) { + int i, e; + gentity_t *check; + vec3_t mins, maxs; + pushed_t *p; + int entityList[MAX_GENTITIES]; + int moveList[MAX_GENTITIES]; + int listedEntities, moveEntities; + vec3_t totalMins, totalMaxs; + + *obstacle = NULL; + + + // mins/maxs are the bounds at the destination + // totalMins / totalMaxs are the bounds for the entire move + if ( pusher->r.currentAngles[0] || pusher->r.currentAngles[1] || pusher->r.currentAngles[2] + || amove[0] || amove[1] || amove[2] ) { + float radius; + + radius = RadiusFromBounds( pusher->r.mins, pusher->r.maxs ); + for ( i = 0; i < 3; i++ ) { + mins[i] = pusher->r.currentOrigin[i] - radius + move[i]; + maxs[i] = pusher->r.currentOrigin[i] + radius + move[i]; + totalMins[i] = pusher->r.currentOrigin[i] - radius; + totalMaxs[i] = pusher->r.currentOrigin[i] + radius; + } + } else { + for ( i = 0 ; i < 3 ; i++ ) { + mins[i] = pusher->r.absmin[i] + move[i]; + maxs[i] = pusher->r.absmax[i] + move[i]; + } + + VectorCopy( pusher->r.absmin, totalMins ); + VectorCopy( pusher->r.absmax, totalMaxs ); + } + for ( i = 0; i < 3; i++ ) { + if ( move[i] > 0 ) { + totalMaxs[i] += move[i]; + } else { + totalMins[i] += move[i]; + } + } + + // unlink the pusher so we don't get it in the entityList + trap_UnlinkEntity( pusher ); + + listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES ); + + // move the pusher to it's final position + VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin ); + VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles ); + trap_LinkEntity( pusher ); + + moveEntities = 0; + // see if any solid entities are inside the final position + for ( e = 0 ; e < listedEntities ; e++ ) { + check = &g_entities[ entityList[ e ] ]; + + if ( check->s.eType == ET_ALARMBOX ) { + continue; + } + + if ( check->isProp && check->s.eType == ET_PROP ) { + continue; + } + + // only push items and players + if ( check->s.eType != ET_MISSILE && check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) { + continue; + } + + if ( check->s.eType == ET_ITEM && check->item->giType == IT_CLIPBOARD && ( check->spawnflags & 1 ) ) { + continue; + } + + //if ( check->s.eType == ET_MISSILE && VectorLength( check->s.pos.trDelta ) ) { + // continue; // it's moving + //} + + // if the entity is standing on the pusher, it will definitely be moved + if ( check->s.groundEntityNum != pusher->s.number ) { + // see if the ent needs to be tested + if ( check->r.absmin[0] >= maxs[0] + || check->r.absmin[1] >= maxs[1] + || check->r.absmin[2] >= maxs[2] + || check->r.absmax[0] <= mins[0] + || check->r.absmax[1] <= mins[1] + || check->r.absmax[2] <= mins[2] ) { + continue; + } + // see if the ent's bbox is inside the pusher's final position + // this does allow a fast moving object to pass through a thin entity... + if ( G_TestEntityPosition( check ) != pusher ) { + continue; + } + } + + moveList[moveEntities++] = entityList[e]; + } + + // unlink all to be moved entities so they cannot get stuck in each other + for ( e = 0; e < moveEntities; e++ ) { + check = &g_entities[ moveList[e] ]; + + trap_UnlinkEntity( check ); + } + + for ( e = 0; e < moveEntities; e++ ) { + check = &g_entities[ moveList[e] ]; + + // the entity needs to be pushed + pushedStackDepth = 0; // Arnout: new push, reset stack depth + if ( G_TryPushingEntity( check, pusher, move, amove ) ) { + // link it in now so nothing else tries to clip into us + trap_LinkEntity( check ); + continue; + } + + // the move was blocked an entity + + // bobbing entities are instant-kill and never get blocked + if ( pusher->s.pos.trType == TR_SINE || pusher->s.apos.trType == TR_SINE ) { + G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH ); + continue; + } + + + // save off the obstacle so we can call the block function (crush, etc) + *obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for ( p = pushed_p - 1 ; p >= pushed ; p-- ) { + VectorCopy( p->origin, p->ent->s.pos.trBase ); + VectorCopy( p->angles, p->ent->s.apos.trBase ); + if ( p->ent->client ) { + p->ent->client->ps.delta_angles[YAW] = p->deltayaw; + VectorCopy( p->origin, p->ent->client->ps.origin ); + } + } + // link all entities at their original position + for ( e = 0; e < moveEntities; e++ ) { + check = &g_entities[ moveList[e] ]; + + trap_LinkEntity( check ); + } + // movement failed + return qfalse; + } + // link all entities at their final position + for ( e = 0; e < moveEntities; e++ ) { + check = &g_entities[ moveList[e] ]; + + trap_LinkEntity( check ); + } + // movement was successfull + return qtrue; +} + + +/* +================= +G_MoverTeam +================= +*/ +void G_MoverTeam( gentity_t *ent ) { + vec3_t move, amove; + gentity_t *part, *obstacle; + vec3_t origin, angles; + + obstacle = NULL; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out + pushed_p = pushed; + for ( part = ent ; part ; part = part->teamchain ) { + // get current position + BG_EvaluateTrajectory( &part->s.pos, level.time, origin ); + BG_EvaluateTrajectory( &part->s.apos, level.time, angles ); + VectorSubtract( origin, part->r.currentOrigin, move ); + VectorSubtract( angles, part->r.currentAngles, amove ); + + if ( part->s.eFlags == EF_MOVER_STOP ) { + part->s.eFlags &= ~EF_MOVER_BLOCKED; + } + + if ( part->s.eType == ET_BAT && part->model && !G_MoverPush( part, move, amove, &obstacle ) ) { + break; + } else if ( !G_MoverPush( part, move, amove, &obstacle ) ) { + break; // move was blocked + } + } + + if ( part ) { + // go back to the previous position + for ( part = ent ; part ; part = part->teamchain ) { + part->s.pos.trTime += level.time - level.previousTime; + part->s.apos.trTime += level.time - level.previousTime; + BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin ); + BG_EvaluateTrajectory( &part->s.apos, level.time, part->r.currentAngles ); + trap_LinkEntity( part ); + } + + // if the pusher has a "blocked" function, call it + if ( ent->blocked ) { + ent->blocked( ent, obstacle ); + } + return; + } + + // the move succeeded + for ( part = ent ; part ; part = part->teamchain ) { + // call the reached function if time is at or past end point + + // opening/closing sliding door + if ( part->s.pos.trType == TR_LINEAR_STOP ) { + if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) { + if ( part->reached ) { + part->reached( part ); + } + } + } +//----(SA) removed + // opening or closing rotating door + else if ( part->s.apos.trType == TR_LINEAR_STOP ) { + if ( level.time >= part->s.apos.trTime + part->s.apos.trDuration ) { + if ( part->reached ) { + part->reached( part ); + } + } + } + } +} + +/* +================ +G_RunMover + +================ +*/ +void G_RunMover( gentity_t *ent ) { + // if not a team captain, don't do anything, because + // the captain will handle everything + if ( ent->flags & FL_TEAMSLAVE ) { + // FIXME + // hack to fix problem of tram car slaves being linked + // after being unlinked in G_FindTeams + if ( ent->r.linked && !Q_stricmp( ent->classname, "func_tramcar" ) ) { + trap_UnlinkEntity( ent ); + } + // Sigh... need to figure out why re links in + else if ( ent->r.linked && !Q_stricmp( ent->classname, "func_rotating" ) ) { + trap_UnlinkEntity( ent ); + } + return; + } + + // if stationary at one of the positions, don't move anything + if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) { + G_MoverTeam( ent ); + } + + // check think function + G_RunThink( ent ); +} + +/* +============================================================================ + +GENERAL MOVERS + +Doors, plats, and buttons are all binary (two position) movers +Pos1 is "at rest", pos2 is "activated" +============================================================================ +*/ + +/* +=============== +SetMoverState +=============== +*/ +void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) { + vec3_t delta; + float f; + qboolean kicked = qfalse, soft = qfalse; + + kicked = (qboolean)( ent->flags & FL_KICKACTIVATE ); + soft = (qboolean)( ent->flags & FL_SOFTACTIVATE ); //----(SA) added + + ent->moverState = moverState; + ent->s.pos.trTime = time; + ent->s.apos.trTime = time; + switch ( moverState ) { + case MOVER_POS1: + VectorCopy( ent->pos1, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + ent->active = qfalse; + break; + case MOVER_POS2: + VectorCopy( ent->pos2, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + break; + + // JOSEPH 1-26-00 + case MOVER_POS3: + VectorCopy( ent->pos3, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + break; + + case MOVER_2TO3: + VectorCopy( ent->pos2, ent->s.pos.trBase ); + VectorSubtract( ent->pos3, ent->pos2, delta ); + f = 1000.0 / ent->s.pos.trDuration; + VectorScale( delta, f, ent->s.pos.trDelta ); + ent->s.pos.trType = TR_LINEAR_STOP; + break; + case MOVER_3TO2: + VectorCopy( ent->pos3, ent->s.pos.trBase ); + VectorSubtract( ent->pos2, ent->pos3, delta ); + f = 1000.0 / ent->s.pos.trDuration; + VectorScale( delta, f, ent->s.pos.trDelta ); + ent->s.pos.trType = TR_LINEAR_STOP; + break; + // END JOSEPH + + case MOVER_1TO2: // opening + VectorCopy( ent->pos1, ent->s.pos.trBase ); + VectorSubtract( ent->pos2, ent->pos1, delta ); +//----(SA) numerous changes start here + ent->s.pos.trDuration = ent->gDuration; + f = 1000.0 / ent->s.pos.trDuration; + VectorScale( delta, f, ent->s.pos.trDelta ); + ent->s.pos.trType = TR_LINEAR_STOP; + break; + case MOVER_2TO1: // closing + VectorCopy( ent->pos2, ent->s.pos.trBase ); + VectorSubtract( ent->pos1, ent->pos2, delta ); + if ( ent->closespeed ) { //----(SA) handle doors with different close speeds + ent->s.pos.trDuration = ent->gDurationBack; + f = 1000.0 / ent->gDurationBack; + } else { + ent->s.pos.trDuration = ent->gDuration; + f = 1000.0 / ent->s.pos.trDuration; + } + VectorScale( delta, f, ent->s.pos.trDelta ); + ent->s.pos.trType = TR_LINEAR_STOP; + break; + + + case MOVER_POS1ROTATE: // at close + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + ent->s.apos.trType = TR_STATIONARY; + break; + case MOVER_POS2ROTATE: // at open + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + ent->s.apos.trType = TR_STATIONARY; + break; + case MOVER_1TO2ROTATE: // opening + VectorClear( ent->s.apos.trBase ); // set base to start position {0,0,0} + + if ( kicked ) { + f = 2000.0 / ent->gDuration; // double speed when kicked open + ent->s.apos.trDuration = ent->gDuration / 2.0; + } else if ( soft ) { + f = 500.0 / ent->gDuration; // 1/2 speed when soft opened + ent->s.apos.trDuration = ent->gDuration * 2; + } else { + f = 1000.0 / ent->gDuration; +// ent->s.apos.trDuration = ent->gDurationBack; // (SA) durationback? + ent->s.apos.trDuration = ent->gDuration; + } + VectorScale( ent->rotate, f * ent->angle, ent->s.apos.trDelta ); + ent->s.apos.trType = TR_LINEAR_STOP; + break; + case MOVER_2TO1ROTATE: // closing + VectorScale( ent->rotate, ent->angle, ent->s.apos.trBase ); // set base to end position + // (kicked closes same as normally opened) + // (soft closes at 1/2 speed) + f = 1000.0 / ent->gDuration; + ent->s.apos.trDuration = ent->gDuration; + if ( soft ) { + ent->s.apos.trDuration *= 2; + f *= 0.5f; + } + VectorScale( ent->s.apos.trBase, -f, ent->s.apos.trDelta ); + ent->s.apos.trType = TR_LINEAR_STOP; + ent->active = qfalse; + break; + + + } + BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin ); +// if (!(ent->r.svFlags & SVF_NOCLIENT) || (ent->r.contents)) // RF, added this for bats, but this is safe for all movers, since if they aren't solid, and aren't visible to the client, they don't need to be linked +// trap_LinkEntity( ent ); + + if ( !( ent->r.svFlags & SVF_NOCLIENT ) || ( ent->r.contents ) ) { // RF, added this for bats, but this is safe for all movers, since if they aren't solid, and aren't visible to the client, they don't need to be linked + trap_LinkEntity( ent ); + // if this entity is blocking AAS, then update it + if ( ent->AASblocking && ent->s.pos.trType == TR_STATIONARY ) { + // reset old blocking areas + G_SetAASBlockingEntity( ent, qfalse ); + // set new areas + G_SetAASBlockingEntity( ent, qtrue ); + } + } +} + +/* +================ +MatchTeam + +All entities in a mover team will move from pos1 to pos2 +in the same amount of time +================ +*/ +void MatchTeam( gentity_t *teamLeader, int moverState, int time ) { + gentity_t *slave; + + for ( slave = teamLeader ; slave ; slave = slave->teamchain ) { + + // pass along flags for how door was activated + if ( teamLeader->flags & FL_KICKACTIVATE ) { + slave->flags |= FL_KICKACTIVATE; + } + if ( teamLeader->flags & FL_SOFTACTIVATE ) { + slave->flags |= FL_SOFTACTIVATE; + } + + SetMoverState( slave, moverState, time ); + } +} + +/* +MatchTeamReverseAngleOnSlaves + +the activator was blocking the door so reverse its direction +*/ +void MatchTeamReverseAngleOnSlaves( gentity_t *teamLeader, int moverState, int time ) { + gentity_t *slave; + + for ( slave = teamLeader ; slave ; slave = slave->teamchain ) { + // reverse open dir for teamLeader and all slaves + slave->angle *= -1; + + // pass along flags for how door was activated + if ( teamLeader->flags & FL_KICKACTIVATE ) { + slave->flags |= FL_KICKACTIVATE; + } + if ( teamLeader->flags & FL_SOFTACTIVATE ) { + slave->flags |= FL_SOFTACTIVATE; + } + + SetMoverState( slave, moverState, time ); + } +} + +/* +================ +ReturnToPos1 +================ +*/ +void ReturnToPos1( gentity_t *ent ) { + MatchTeam( ent, MOVER_2TO1, level.time ); + + // play starting sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + + ent->s.loopSound = 0; + // set looping sound + ent->s.loopSound = ent->sound3to2; + +} + +// JOSEPH 1-26-00 +/* +================ +ReturnToPos2 +================ +*/ +void ReturnToPos2( gentity_t *ent ) { + MatchTeam( ent, MOVER_3TO2, level.time ); + + ent->s.loopSound = 0; + // looping sound + ent->s.loopSound = ent->soundLoop; + + // starting sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound3to2 ); +} + +/* +================ +GotoPos3 +================ +*/ +void GotoPos3( gentity_t *ent ) { + MatchTeam( ent, MOVER_2TO3, level.time ); + + ent->s.loopSound = 0; + // looping sound + ent->s.loopSound = ent->soundLoop; + + // starting sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to3 ); +} +// END JOSEPH + +/* +================ +ReturnToPos1Rotate + closing +================ +*/ +void ReturnToPos1Rotate( gentity_t *ent ) { + qboolean inPVS = qfalse; + gentity_t *player; + + MatchTeam( ent, MOVER_2TO1ROTATE, level.time ); + + player = AICast_FindEntityForName( "player" ); + + if ( player ) { + inPVS = trap_InPVS( player->r.currentOrigin, ent->r.currentOrigin ); + } + + // play starting sound + if ( inPVS ) { + if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftclose ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + } + } + + ent->s.loopSound = ent->sound3to2; +} + +/* +================ +Reached_BinaryMover +================ +*/ +void Reached_BinaryMover( gentity_t *ent ) { + + // stop the looping sound + ent->s.loopSound = 0; +// ent->s.loopSound = ent->soundLoop; + + if ( ent->moverState == MOVER_1TO2 ) { + // reached pos2 + SetMoverState( ent, MOVER_POS2, level.time ); + + // play sound + if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftendo ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); + } + + // fire targets + if ( !ent->activator ) { + ent->activator = ent; + } + + G_UseTargets( ent, ent->activator ); + + if ( ent->flags & FL_TOGGLE ) { + ent->think = ReturnToPos1; + ent->nextthink = 0; + return; + } + + // JOSEPH 1-27-00 + // return to pos1 after a delay + if ( ent->wait != -1000 ) { + ent->think = ReturnToPos1; + ent->nextthink = level.time + ent->wait; + } + // END JOSEPH + } else if ( ent->moverState == MOVER_2TO1 ) { + // reached pos1 + SetMoverState( ent, MOVER_POS1, level.time ); + + // play sound + if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftendc ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); + } + + // close areaportals + if ( ent->teammaster == ent || !ent->teammaster ) { + trap_AdjustAreaPortalState( ent, qfalse ); + } + } else if ( ent->moverState == MOVER_1TO2ROTATE ) { + // reached pos2 + SetMoverState( ent, MOVER_POS2ROTATE, level.time ); + + // play sound + if ( ent->flags & FL_KICKACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundKickedEnd ); + } else if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftendo ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); + } + + // fire targets + if ( !ent->activator ) { + ent->activator = ent; + } + G_UseTargets( ent, ent->activator ); + + if ( ent->flags & FL_TOGGLE ) { + ent->think = ReturnToPos1Rotate; + ent->nextthink = 0; + return; + } + + // return to pos1 after a delay + ent->think = ReturnToPos1Rotate; + ent->nextthink = level.time + ent->wait; + + } else if ( ent->moverState == MOVER_2TO1ROTATE ) { + // reached pos1 + SetMoverState( ent, MOVER_POS1ROTATE, level.time ); + + // to stop sound from being requested if not in pvs anoying bug + { + qboolean inPVS = qfalse; + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( player ) { + inPVS = trap_InPVS( player->r.currentOrigin, ent->r.currentOrigin ); + } + + // play sound + if ( inPVS ) { + if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftendc ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); + } + } + } + + // clear the 'soft' flag + ent->flags &= ~FL_SOFTACTIVATE; //----(SA) added + + // close areaportals + if ( ent->teammaster == ent || !ent->teammaster ) { + trap_AdjustAreaPortalState( ent, qfalse ); + } + } else { + G_Error( "Reached_BinaryMover: bad moverState" ); + } + +// ent->flags &= ~(FL_KICKACTIVATE|FL_SOFTACTIVATE); // (SA) it was not opened normally. Clear this so it thinks it's closed normally + ent->flags &= ~FL_KICKACTIVATE; // (SA) it was not opened normally. Clear this so it thinks it's closed normally + +} + +/* +================ +IsBinaryMoverBlocked +================ +*/ +qboolean IsBinaryMoverBlocked( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + + vec3_t dir, angles; + vec3_t pos; + vec3_t vec; + float dot; + vec3_t forward; + qboolean is_relay = qfalse; + + if ( Q_stricmp( ent->classname, "func_door_rotating" ) == 0 ) { + if ( ent->spawnflags & 32 ) { + return qfalse; + } + + //----(SA) only check for blockage by players + if ( !activator ) { + if ( Q_stricmp( other->classname, "target_relay" ) == 0 ) { + is_relay = qtrue; + } else if ( !activator->client ) { + return qfalse; + } + } + //----(SA) end + + VectorAdd( ent->r.absmin, ent->r.absmax, pos ); + VectorScale( pos, 0.5, pos ); + + VectorSubtract( pos, ent->s.origin, dir ); + vectoangles( dir, angles ); + + if ( ent->rotate[YAW] ) { + angles[YAW] += ent->angle; + } else if ( ent->rotate[PITCH] ) { + angles[PITCH] += ent->angle; + } else if ( ent->rotate[ROLL] ) { + angles[ROLL] += ent->angle; + } + + AngleVectors( angles, forward, NULL, NULL ); + // VectorSubtract (other->r.currentOrigin, pos, vec); + + if ( is_relay ) { + VectorSubtract( other->r.currentOrigin, pos, vec ); + } else { + VectorSubtract( activator->r.currentOrigin, pos, vec ); + } + + VectorNormalize( vec ); + dot = DotProduct( vec, forward ); + + if ( dot >= 0 ) { + return qtrue; + } else { + return qfalse; + } + + } + + return qfalse; + +} + +// JOSEPH 1-26-00 +/* +================ +Reached_TrinaryMover +================ +*/ +void Reached_TrinaryMover( gentity_t *ent ) { + + // stop the looping sound + ent->s.loopSound = ent->soundLoop; + + if ( ent->moverState == MOVER_1TO2 ) { + // reached pos2 + SetMoverState( ent, MOVER_POS2, level.time ); + + // goto pos 3 + ent->think = GotoPos3; + ent->nextthink = level.time + 1000; //FRAMETIME; + + // play sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); + } else if ( ent->moverState == MOVER_2TO1 ) { + // reached pos1 + SetMoverState( ent, MOVER_POS1, level.time ); + + // play sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); + + // close areaportals + if ( ent->teammaster == ent || !ent->teammaster ) { + trap_AdjustAreaPortalState( ent, qfalse ); + } + } else if ( ent->moverState == MOVER_2TO3 ) { + // reached pos3 + SetMoverState( ent, MOVER_POS3, level.time ); + + // play sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos3 ); + + // return to pos2 after a delay + if ( ent->wait != -1000 ) { + ent->think = ReturnToPos2; + ent->nextthink = level.time + ent->wait; + } + + // fire targets + if ( !ent->activator ) { + ent->activator = ent; + } + G_UseTargets( ent, ent->activator ); + } else if ( ent->moverState == MOVER_3TO2 ) { + // reached pos2 + SetMoverState( ent, MOVER_POS2, level.time ); + + // return to pos1 + ent->think = ReturnToPos1; + ent->nextthink = level.time + 1000; //FRAMETIME; + + // play sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos3 ); + } else { + G_Error( "Reached_BinaryMover: bad moverState" ); + } +} +// END JOSEPH + +// JOSEPH 1-26-00 +/* +================ +Use_TrinaryMover +================ +*/ +void Use_TrinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + int total; + int partial; + qboolean isblocked = qfalse; + + isblocked = IsBinaryMoverBlocked( ent, other, activator ); + + if ( isblocked ) { + MatchTeamReverseAngleOnSlaves( ent, MOVER_1TO2ROTATE, level.time + 50 ); + + // starting sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // open areaportal + if ( ent->teammaster == ent || !ent->teammaster ) { + trap_AdjustAreaPortalState( ent, qtrue ); + } + return; + } + + // only the master should be used + if ( ent->flags & FL_TEAMSLAVE ) { + Use_TrinaryMover( ent->teammaster, other, activator ); + return; + } + + ent->activator = activator; + + if ( ent->moverState == MOVER_POS1 ) { + + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + MatchTeam( ent, MOVER_1TO2, level.time + 50 ); + + // starting sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + // open areaportal + if ( ent->teammaster == ent || !ent->teammaster ) { + trap_AdjustAreaPortalState( ent, qtrue ); + } + return; + } + + if ( ent->moverState == MOVER_POS2 ) { + + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + MatchTeam( ent, MOVER_2TO3, level.time + 50 ); + + // starting sound + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to3 ); + + // looping sound + ent->s.loopSound = ent->soundLoop; + + return; + } + + // if all the way up, just delay before coming down + if ( ent->moverState == MOVER_POS3 ) { + if ( ent->wait != -1000 ) { + ent->nextthink = level.time + ent->wait; + } + return; + } + + // only partway down before reversing + if ( ent->moverState == MOVER_2TO1 ) { + total = ent->s.pos.trDuration; + partial = level.time - ent->s.time; + if ( partial > total ) { + partial = total; + } + + MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) ); + + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + return; + } + + if ( ent->moverState == MOVER_3TO2 ) { + total = ent->s.pos.trDuration; + partial = level.time - ent->s.time; + if ( partial > total ) { + partial = total; + } + + MatchTeam( ent, MOVER_2TO3, level.time - ( total - partial ) ); + + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to3 ); + return; + } + + // only partway up before reversing + if ( ent->moverState == MOVER_1TO2 ) { + total = ent->s.pos.trDuration; + partial = level.time - ent->s.time; + if ( partial > total ) { + partial = total; + } + + MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) ); + + if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftclose ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + } + return; + } + + if ( ent->moverState == MOVER_2TO3 ) { + total = ent->s.pos.trDuration; + partial = level.time - ent->s.time; + if ( partial > total ) { + partial = total; + } + + MatchTeam( ent, MOVER_3TO2, level.time - ( total - partial ) ); + + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound3to2 ); + return; + } +} +// END JOSEPH + +/* +================ +Use_BinaryMover +================ +*/ +void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) { +// int total; +// int partial; + qboolean isblocked = qfalse; + qboolean nosound = qfalse; + + if ( level.time <= 4000 ) { // hack. don't play door sounds if in the first /four/ seconds of game (FIXME: TODO: THIS IS STILL A HACK) + nosound = qtrue; + } + + // only the master should be used + if ( ent->flags & FL_TEAMSLAVE ) { + + // pass along flags for how door was activated + if ( ent->flags & FL_KICKACTIVATE ) { + ent->teammaster->flags |= FL_KICKACTIVATE; + } + if ( ent->flags & FL_SOFTACTIVATE ) { + ent->teammaster->flags |= FL_SOFTACTIVATE; + } + + Use_BinaryMover( ent->teammaster, other, activator ); + return; + } + + // only check for blocking when opening, otherwise the door has no choice + if ( ent->moverState == MOVER_POS1 || ent->moverState == MOVER_POS1ROTATE ) { + isblocked = IsBinaryMoverBlocked( ent, other, activator ); + } + + + if ( isblocked ) { + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + // ent->angle *= -1; + // MatchTeam( ent, MOVER_1TO2ROTATE, level.time + 50 ); + MatchTeamReverseAngleOnSlaves( ent, MOVER_1TO2ROTATE, level.time + 50 ); + + // starting sound + if ( !nosound ) { + if ( ent->flags & FL_KICKACTIVATE ) { // kicked + if ( activator ) { + AICast_AudibleEvent( activator->s.number, ent->s.origin, HEAR_RANGE_DOOR_OPEN ); // "someone kicked open a door near me!" + } + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundKicked ); + } else if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftopen ); + } else { + if ( activator ) { + AICast_AudibleEvent( activator->s.number, ent->s.origin, HEAR_RANGE_DOOR_KICKOPEN ); // "someone kicked open a door near me!" + } + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + } + } + + ent->s.loopSound = 0; + + // looping sound + if ( !nosound ) { + ent->s.loopSound = ent->sound2to3; + } else if ( !nosound ) { + ent->s.loopSound = ent->soundLoop; + } + + // open areaportal + if ( ent->teammaster == ent || !ent->teammaster ) { + trap_AdjustAreaPortalState( ent, qtrue ); + } + return; + } + + ent->activator = activator; + + // Rafael + if ( ent->nextTrain && ent->nextTrain->wait == -1 && ent->nextTrain->count == 1 ) { + ent->nextTrain->count = 0; + return; + } + + if ( ent->moverState == MOVER_POS1 ) { + + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + MatchTeam( ent, MOVER_1TO2, level.time + 50 ); + + // play starting sound + if ( !nosound ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + } + + ent->s.loopSound = 0; + + // set looping sound + if ( !nosound ) { + ent->s.loopSound = ent->sound2to3; + } + + // open areaportal + if ( ent->teammaster == ent || !ent->teammaster ) { + trap_AdjustAreaPortalState( ent, qtrue ); + } + return; + } + + if ( ent->moverState == MOVER_POS1ROTATE ) { + + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + MatchTeam( ent, MOVER_1TO2ROTATE, level.time + 50 ); + + // play starting sound + if ( !nosound ) { + if ( ent->flags & FL_KICKACTIVATE ) { // kicked + if ( activator ) { + AICast_AudibleEvent( activator->s.number, ent->s.origin, HEAR_RANGE_DOOR_KICKOPEN ); // "someone kicked open a door near me!" + } + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundKicked ); + } else if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftopen ); + } else { + if ( activator ) { + AICast_AudibleEvent( activator->s.number, ent->s.origin, HEAR_RANGE_DOOR_OPEN ); // "someone opened a door near me!" + } + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + } + } + + ent->s.loopSound = 0; + // set looping sound + if ( !nosound ) { + ent->s.loopSound = ent->sound2to3; + } + + // open areaportal + if ( ent->teammaster == ent || !ent->teammaster ) { + trap_AdjustAreaPortalState( ent, qtrue ); + } + return; + } + + // if all the way up, just delay before coming down + // JOSEPH 1-27-00 + if ( ent->moverState == MOVER_POS2 ) { + if ( ent->flags & FL_TOGGLE ) { + ent->nextthink = level.time + 50; + return; + } + + if ( ent->wait != -1000 ) { + ent->nextthink = level.time + ent->wait; + } + return; + } + // END JOSEPH + + // if all the way up, just delay before coming down + if ( ent->moverState == MOVER_POS2ROTATE ) { + if ( ent->flags & FL_TOGGLE ) { + ent->nextthink = level.time + 50; // do it *now* for toggles + } else { + ent->nextthink = level.time + ent->wait; + } + return; + } + + // only partway down before reversing + if ( ent->moverState == MOVER_2TO1 ) { + Blocked_Door( ent, NULL ); + + if ( !nosound ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + } + return; + } + + // only partway up before reversing + if ( ent->moverState == MOVER_1TO2 ) { + Blocked_Door( ent, NULL ); + + if ( !nosound ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + } + return; + } + + // only partway closed before reversing + if ( ent->moverState == MOVER_2TO1ROTATE ) { + Blocked_DoorRotate( ent, NULL ); + + if ( !nosound ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); + } + return; + } + + // only partway open before reversing + if ( ent->moverState == MOVER_1TO2ROTATE ) { + Blocked_DoorRotate( ent, NULL ); + + if ( !nosound ) { + if ( ent->flags & FL_SOFTACTIVATE ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundSoftclose ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + } + } + return; + } +} + + + +/* +================ +InitMover + +"pos1", "pos2", and "speed" should be set before calling, +so the movement delta can be calculated +================ +*/ +void InitMover( gentity_t *ent ) { + vec3_t move; + float distance; + float light; + vec3_t color; + qboolean lightSet, colorSet; + char *sound; + + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if ( ent->model2 ) { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + } + + // if the "loopsound" key is set, use a constant looping sound when moving + if ( G_SpawnString( "noise", "100", &sound ) ) { + ent->s.loopSound = G_SoundIndex( sound ); + } + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + if ( lightSet || colorSet ) { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + // JOSEPH 1-26-00 + if ( !Q_stricmp( ent->classname,"func_secret" ) ) { + ent->use = Use_TrinaryMover; + ent->reached = Reached_TrinaryMover; + } else if ( !Q_stricmp( ent->classname, "func_rotating" ) ) { + ent->use = Use_Func_Rotate; + ent->reached = NULL; // rotating can never reach + } else + { + ent->use = Use_BinaryMover; + ent->reached = Reached_BinaryMover; + } + // END JOSEPH + + ent->moverState = MOVER_POS1; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + + VectorCopy( ent->pos1, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.pos.trBase ); + + // calculate time to reach second position from speed + VectorSubtract( ent->pos2, ent->pos1, move ); + distance = VectorLength( move ); + if ( !ent->speed ) { + ent->speed = 100; + } + +//----(SA) changes + // open time based on speed +// VectorScale( move, ent->speed, ent->s.pos.trDelta ); + VectorScale( move, ent->speed, ent->gDelta ); + ent->s.pos.trDuration = distance * 1000 / ent->speed; + if ( ent->s.pos.trDuration <= 0 ) { + ent->s.pos.trDuration = 1; + } + ent->gDurationBack = ent->gDuration = ent->s.pos.trDuration; + + // close time based on speed + if ( ent->closespeed ) { + VectorScale( move, ent->closespeed, ent->gDelta ); + ent->gDurationBack = distance * 1000 / ent->closespeed; + if ( ent->gDurationBack <= 0 ) { + ent->gDurationBack = 1; +//----(SA) end + } + } +} + +/* +================ +InitMoverRotate + +"pos1", "pos2", and "speed" should be set before calling, +so the movement delta can be calculated +================ +*/ +void InitMoverRotate( gentity_t *ent ) { + vec3_t move; + float distance; + float light; + vec3_t color; + qboolean lightSet, colorSet; + + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if ( ent->model2 ) { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + } + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + if ( lightSet || colorSet ) { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + + ent->use = Use_BinaryMover; + + if ( !( ent->spawnflags & 64 ) ) { // STAYOPEN + ent->reached = Reached_BinaryMover; + } + + ent->moverState = MOVER_POS1ROTATE; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->pos1, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.pos.trBase ); + + // calculate time to reach second position from speed + VectorSubtract( ent->pos2, ent->pos1, move ); + distance = VectorLength( move ); + if ( !ent->speed ) { + ent->speed = 100; + } + + VectorScale( move, ent->speed, ent->s.pos.trDelta ); + + ent->s.apos.trDuration = ent->speed; + if ( ent->s.apos.trDuration <= 0 ) { + ent->s.apos.trDuration = 1; + } + + ent->gDuration = ent->gDurationBack = ent->s.apos.trDuration; // (SA) store 'real' durations so doors can be opened/closed at different speeds +} + + +/* +=============================================================================== + +DOOR + +A use can be triggered either by a touch function, by being shot, or by being +targeted by another entity. + +=============================================================================== +*/ + +//---- (SA) don't remember what the max keys was supposed to be, so change here and nuke this comment if you know +#define MAX_DOOR_KEYS 16 + +/* +================ +Blocked_Door +================ +*/ +void Blocked_Door( gentity_t *ent, gentity_t *other ) { + gentity_t *slave; + int time; + + // remove anything other than a client + if ( other ) { + if ( !other->client && other->s.eType != ET_CORPSE ) { + // except CTF flags!!!! + if ( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) { + Team_DroppedFlagThink( other ); + return; + } + G_TempEntity( other->s.origin, EV_ITEM_POP ); + G_FreeEntity( other ); + return; + } + + if ( ent->damage ) { + G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH ); + } + } + + if ( ent->spawnflags & 4 ) { + return; // crushers don't reverse + } + + // reverse direction +// Use_BinaryMover( ent, ent, other ); + for ( slave = ent ; slave ; slave = slave->teamchain ) + { +// time = level.time - slave->s.pos.trTime; + time = level.time - ( slave->s.pos.trDuration - ( level.time - slave->s.pos.trTime ) ); + + if ( slave->moverState == MOVER_1TO2 ) { + SetMoverState( slave, MOVER_2TO1, time ); + } else { + SetMoverState( slave, MOVER_1TO2, time ); + } + trap_LinkEntity( slave ); + } + +} + +/* +================ +Touch_DoorTriggerSpectator +================ +*/ +static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_t *trace ) { + int i, axis; + vec3_t origin, dir, angles; + + axis = ent->count; + VectorClear( dir ); + if ( fabs( other->s.origin[axis] - ent->r.absmax[axis] ) < + fabs( other->s.origin[axis] - ent->r.absmin[axis] ) ) { + origin[axis] = ent->r.absmin[axis] - 10; + dir[axis] = -1; + } else { + origin[axis] = ent->r.absmax[axis] + 10; + dir[axis] = 1; + } + for ( i = 0; i < 3; i++ ) { + if ( i == axis ) { + continue; + } + origin[i] = ( ent->r.absmin[i] + ent->r.absmax[i] ) * 0.5; + } + vectoangles( dir, angles ); + TeleportPlayer( other, origin, angles ); +} + +/* +================ +Blocked_DoorRotate +================ +*/ + +#define DOORPUSHBACK 16 + +void Blocked_DoorRotate( gentity_t *ent, gentity_t *other ) { + + gentity_t *slave; + int time; + + // remove anything other than a client + if ( other ) { + if ( !other->client && other->s.eType != ET_CORPSE ) { + if ( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) { + Team_DroppedFlagThink( other ); + return; + } + G_TempEntity( other->s.origin, EV_ITEM_POP ); + G_FreeEntity( other ); + return; + } + + if ( other->health <= 0 ) { + G_Damage( other, ent, ent, NULL, NULL, 99999, 0, MOD_CRUSH ); + } + + if ( ent->damage ) { + G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH ); + } + } + + for ( slave = ent ; slave ; slave = slave->teamchain ) + { + // RF, trying to fix "stuck in door" bug + time = level.time - ( slave->s.apos.trDuration - ( level.time - slave->s.apos.trTime ) ); + //time = level.time - slave->s.apos.trTime; + + if ( slave->moverState == MOVER_1TO2ROTATE ) { + SetMoverState( slave, MOVER_2TO1ROTATE, time ); + } else + { + SetMoverState( slave, MOVER_1TO2ROTATE, time ); + } + trap_LinkEntity( slave ); + } + + +} + + +/* +================ +Touch_DoorTrigger +================ +*/ +void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) { + // if the door is not open and not opening + if ( ent->parent->moverState != MOVER_1TO2 && + ent->parent->moverState != MOVER_POS2 ) { + Touch_DoorTriggerSpectator( ent, other, trace ); + } + } else if ( ent->parent->moverState != MOVER_1TO2 ) { + Use_BinaryMover( ent->parent, ent, other ); + } +} + +/* +====================== +Think_SpawnNewDoorTrigger + +All of the parts of a door have been spawned, so create +a trigger that encloses all of them +====================== +*/ +void Think_SpawnNewDoorTrigger( gentity_t *ent ) { + gentity_t *other; + vec3_t mins, maxs; + int i, best; + + // set all of the slaves as shootable + for ( other = ent ; other ; other = other->teamchain ) { + other->takedamage = qtrue; + } + + // find the bounds of everything on the team + VectorCopy( ent->r.absmin, mins ); + VectorCopy( ent->r.absmax, maxs ); + + for ( other = ent->teamchain ; other ; other = other->teamchain ) { + AddPointToBounds( other->r.absmin, mins, maxs ); + AddPointToBounds( other->r.absmax, mins, maxs ); + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) { + best = i; + } + } + maxs[best] += 120; + mins[best] -= 120; + + // create a trigger with this size + other = G_Spawn(); + VectorCopy( mins, other->r.mins ); + VectorCopy( maxs, other->r.maxs ); + other->parent = ent; + other->r.contents = CONTENTS_TRIGGER; + other->touch = Touch_DoorTrigger; + trap_LinkEntity( other ); + + MatchTeam( ent, ent->moverState, level.time ); +} + +void Think_MatchTeam( gentity_t *ent ) { + MatchTeam( ent, ent->moverState, level.time ); +} + + +//----(SA) added + +/* +============== +findNonAIBrushTargeter + determine if there is an entity pointing at ent that is not a "trigger_aidoor" + (used now for checking which key to set for a door) +============== +*/ +qboolean findNonAIBrushTargeter( gentity_t *ent ) { + gentity_t *targeter = NULL; + + if ( !( ent->targetname ) ) { + return qfalse; + } + + while ( ( targeter = G_Find( targeter, FOFS( target ), ent->targetname ) ) != NULL ) + { + if ( strcmp( targeter->classname,"trigger_aidoor" ) && + Q_stricmp( targeter->classname, "func_invisible_user" ) ) { + return qtrue; + } + } + + return qfalse; +} + + +/* +============== +finishSpawningKeyedMover +============== +*/ +void finishSpawningKeyedMover( gentity_t *ent ) { + gentity_t *slave; + + // all ents should be spawned, so it's okay to check for special door triggers now + +//----(SA) modified + if ( ent->key == -2 ) { // the key was not set in the spawn + if ( ent->targetname && findNonAIBrushTargeter( ent ) ) { + ent->key = -1; // something is targeting this (other than a trigger_aidoor) so leave locked + } else { + ent->key = 0; + } + } +//----(SA) end + + if ( ent->key ) { + G_SetAASBlockingEntity( ent, qtrue ); + } + + ent->nextthink = level.time + FRAMETIME; + + if ( !( ent->flags & FL_TEAMSLAVE ) ) { + if ( ent->targetname || ent->takedamage ) { // non touch/shoot doors + ent->think = Think_MatchTeam; + } +// (SA) is this safe? is ent->spawnflags & 8 consistant among all keyed ents? + else if ( ( ent->spawnflags & 8 ) && ( strcmp( ent->classname, "func_door_rotating" ) ) ) { + ent->think = Think_SpawnNewDoorTrigger; + } else { + ent->think = Think_MatchTeam; + } + + // (SA) slaves have been marked as FL_TEAMSLAVE now, so they won't + // finish their think on their own. So set keys for teamed doors + for ( slave = ent ; slave ; slave = slave->teamchain ) + { + if ( slave == ent ) { + continue; + } + + slave->key = ent->key; + + if ( slave->key ) { + G_SetAASBlockingEntity( slave, qtrue ); + } + } + } +} + +//----(SA) end + + + +/* +============== +Door_reverse_sounds + The door has been marked as "START_OPEN" which means the open/closed + positions have been swapped. + This swaps the sounds around as well +============== +*/ +void Door_reverse_sounds( gentity_t *ent ) { + int stemp; + + stemp = ent->sound1to2; + ent->sound1to2 = ent->sound2to1; + ent->sound2to1 = stemp; + + stemp = ent->soundPos1; + ent->soundPos1 = ent->soundPos2; + ent->soundPos2 = stemp; + + stemp = ent->sound2to3; + ent->sound2to3 = ent->sound3to2; + ent->sound3to2 = stemp; + + + stemp = ent->soundSoftopen; + ent->soundSoftopen = ent->soundSoftclose; + ent->soundSoftclose = stemp; + + stemp = ent->soundSoftendo; + ent->soundSoftendo = ent->soundSoftendc; + ent->soundSoftendc = stemp; + +} + + +/* +============== +DoorSetSounds + get sound indexes for the various door sounds + (used by SP_func_door() and SP_func_door_rotating() ) +============== +*/ +void DoorSetSounds( gentity_t *ent, int doortype, qboolean isRotating ) { + ent->sound1to2 = G_SoundIndex( va( "sound/movers/doors/door%i_open.wav", doortype ) ); // opening + ent->soundPos2 = G_SoundIndex( va( "sound/movers/doors/door%i_endo.wav", doortype ) ); // open + ent->sound2to1 = G_SoundIndex( va( "sound/movers/doors/door%i_close.wav", doortype ) ); // closing + ent->soundPos1 = G_SoundIndex( va( "sound/movers/doors/door%i_endc.wav", doortype ) ); // closed + ent->sound2to3 = G_SoundIndex( va( "sound/movers/doors/door%i_loopo.wav", doortype ) ); // loopopen + ent->sound3to2 = G_SoundIndex( va( "sound/movers/doors/door%i_loopc.wav", doortype ) ); // loopclosed + ent->soundPos3 = G_SoundIndex( va( "sound/movers/doors/door%i_locked.wav", doortype ) ); // locked + + + ent->soundSoftopen = G_SoundIndex( va( "sound/movers/doors/door%i_openq.wav", doortype ) ); // opening quietly + ent->soundSoftendo = G_SoundIndex( va( "sound/movers/doors/door%i_endoq.wav", doortype ) ); // open quietly + ent->soundSoftclose = G_SoundIndex( va( "sound/movers/doors/door%i_closeq.wav", doortype ) ); // closing quietly + ent->soundSoftendc = G_SoundIndex( va( "sound/movers/doors/door%i_endcq.wav", doortype ) ); // closed quietly + + if ( isRotating ) { + ent->soundKicked = G_SoundIndex( va( "sound/movers/doors/door%i_kicked.wav", doortype ) ); + ent->soundKickedEnd = G_SoundIndex( va( "sound/movers/doors/door%i_kickedend.wav", doortype ) ); + } + +} + +//----(SA) added +/* +============== +G_TryDoor + seemed better to have this isolated. this way i can get func_invisible_user's using the + regular rules of doors. +============== +*/ +void G_TryDoor( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + qboolean walking = qfalse; + + walking = (qboolean)( ent->flags & FL_SOFTACTIVATE ); + + + if ( ( ent->s.apos.trType == TR_STATIONARY && ent->s.pos.trType == TR_STATIONARY ) ) { + if ( ent->active == qfalse ) { + if ( ent->key < 0 ) { // door force locked + if ( !walking && activator ) { // only send audible event if not trying to open slowly + AICast_AudibleEvent( activator->s.clientNum, ent->s.origin, HEAR_RANGE_DOOR_LOCKED ); // "someone tried locked door near me!" + } + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos3 ); + return; + } + + if ( activator ) { + if ( ent->key > 0 ) { // door requires key + gitem_t *item = BG_FindItemForKey( ent->key, 0 ); + if ( !( activator->client->ps.stats[STAT_KEYS] & ( 1 << item->giTag ) ) ) { + if ( !walking ) { // only send audible event if not trying to open slowly + AICast_AudibleEvent( activator->s.clientNum, ent->s.origin, HEAR_RANGE_DOOR_LOCKED ); // "someone tried locked door near me!" + } + // player does not have key + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos3 ); + return; + } + } + } + + + if ( ent->teammaster && ent->team && ent != ent->teammaster ) { + ent->teammaster->active = qtrue; + if ( walking ) { + ent->teammaster->flags |= FL_SOFTACTIVATE; // no noise generated + } else { + if ( activator ) { + AICast_AudibleEvent( activator->s.clientNum, ent->s.origin, HEAR_RANGE_DOOR_OPEN ); // "someone opened door near me!" + } + } + + Use_BinaryMover( ent->teammaster, activator, activator ); + G_UseTargets( ent->teammaster, activator ); + } else + { + ent->active = qtrue; + if ( walking ) { + ent->flags |= FL_SOFTACTIVATE; // no noise + } else { + if ( activator ) { + AICast_AudibleEvent( activator->s.clientNum, ent->s.origin, HEAR_RANGE_DOOR_OPEN ); // "someone opened door near me!" + } + } + + Use_BinaryMover( ent, activator, activator ); + G_UseTargets( ent, activator ); + } + } + } +} +//----(SA) end + + +/*QUAKED func_door (0 .5 .8) ? START_OPEN TOGGLE CRUSHER TOUCH SHOOT-THRU +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door +SHOOT-THRU Bullets don't stop when they hit the door. Set "shoot_thru_scale" with bullet damage scale (see below) + +"key" -1 for locked, key number for which key opens, 0 for open. default '0' unless door is targeted. (trigger_aidoor entities targeting this door do /not/ affect the key status) +"model2" .md3 model to also draw +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"speed" movement speed (100 default) +"closespeed" optional different movement speed for door closing +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +"health" if set, the door must be shot open +"team" team name. other doors with same team name will open/close in syncronicity +"shoot_thru_scale" Multiplier for how much damage bullets do that have passed through the door. Effectively how much damage the door 'absorbs'. 0.0 - 1.0 (0.0 will turn SHOOT-THRU off, 1.0 is full damage) +"type" use sounds based on construction of door: + 0 - nosound (default) + 1 - metal + 2 - stone + 3 - lab + 4 - wood + 5 - iron/jail + 6 - portcullis + 7 - wood (quiet) + +SOUND NAMING INFO - +inside "sound/movers/doors/door... + _open.wav // opening + _endo.wav // open + _close.wav // closing + _endc.wav // closed + _loopo.wav // opening loop + _loopc.wav // closing loop + _locked.wav // locked + + _openq.wav // opening quietly + _endoq.wav // open quietly + _closeq.wav // closing quietly + _endcq.wav // closed quietly + +and for rotating doors: + _kicked.wav + _kickedend.wav + +*/ +void SP_func_door( gentity_t *ent ) { + vec3_t abs_movedir; + float distance; + vec3_t size; + float lip; + int key, doortype; + + G_SpawnInt( "type", "0", &doortype ); + + if ( doortype ) { // /*why on earthy did this check for <=8?*/ && doortype <= 8) // no doortype = silent + DoorSetSounds( ent, doortype, qfalse ); + } + + ent->blocked = Blocked_Door; + + // default speed of 400 + if ( !ent->speed ) { + ent->speed = 400; + } + + // default wait of 2 seconds + if ( !ent->wait ) { + ent->wait = 2; + } + ent->wait *= 1000; + + //---- (SA) door keys + + if ( G_SpawnInt( "key", "", &key ) ) { // if door has a key entered, set it + ent->key = key; + } else { + ent->key = -2; // otherwise, set the key when this ent finishes spawning + + } + // if the key is invalid, set the key in the finishSpawning routine + if ( ent->key > MAX_DOOR_KEYS || ent->key < -2 ) { + G_Error( "invalid key number: %d in func_door_rotating\n", ent->key ); + ent->key = -2; + } + //---- (SA) end + + // default lip of 8 units + G_SpawnFloat( "lip", "8", &lip ); + + // default damage of 2 points + G_SpawnInt( "dmg", "2", &ent->damage ); + + // first position at start + VectorCopy( ent->s.origin, ent->pos1 ); + + // calculate second position + trap_SetBrushModel( ent, ent->model ); + G_SetMovedir( ent->s.angles, ent->movedir ); + abs_movedir[0] = fabs( ent->movedir[0] ); + abs_movedir[1] = fabs( ent->movedir[1] ); + abs_movedir[2] = fabs( ent->movedir[2] ); + VectorSubtract( ent->r.maxs, ent->r.mins, size ); + distance = DotProduct( abs_movedir, size ) - lip; + VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 ); + + if ( ent->spawnflags & 1 ) { // START_OPEN - reverse position 1 and 2 + vec3_t temp; + int tempi; + + VectorCopy( ent->pos2, temp ); + VectorCopy( ent->s.origin, ent->pos2 ); + VectorCopy( temp, ent->pos1 ); + + // swap speeds if door has 'closespeed' + if ( ent->closespeed ) { + tempi = ent->speed; + ent->speed = ent->closespeed; + ent->closespeed = tempi; + } + + // swap sounds + Door_reverse_sounds( ent ); + } + + // TOGGLE + if ( ent->spawnflags & 2 ) { + ent->flags |= FL_TOGGLE; + } + + InitMover( ent ); + + ent->s.dmgFlags = HINT_DOOR; // make it a door for cursorhints + + if ( !( ent->flags & FL_TEAMSLAVE ) ) { + int health; + + G_SpawnInt( "health", "0", &health ); + if ( health ) { + ent->takedamage = qtrue; + } + } + + ent->nextthink = level.time + FRAMETIME; + ent->think = finishSpawningKeyedMover; +} + +// JOSEPH 1-26-00 +/*QUAKED func_secret (0 .5 .8) ? REVERSE x CRUSHER TOUCH +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"key" -1 for locked, key number for which key opens, 0 for open. default '0' unless door is targeted. (trigger_aidoor entities targeting this door do /not/ affect the key status) +"model2" .md3 model to also draw +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"speed" movement speed (100 default) +"wait" wait before returning (2 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +"health" if set, the door must be shot open +*/ +void SP_func_secret( gentity_t *ent ) { + vec3_t abs_movedir; + vec3_t angles2; + float distance; + vec3_t size; + float lip; + int key; + + ent->sound1to2 = ent->sound2to1 = ent->sound2to3 = G_SoundIndex( "sound/movers/doors/dr1_strt.wav" ); + ent->soundPos1 = ent->soundPos3 = G_SoundIndex( "sound/movers/doors/dr1_end.wav" ); + + ent->blocked = Blocked_Door; + + // default speed of 100 + if ( !ent->speed ) { + ent->speed = 100; + } + + // default wait of 2 seconds + if ( !ent->wait ) { + ent->wait = 2; + } + ent->wait *= 1000; + + //---- (SA) door keys + + if ( G_SpawnInt( "key", "", &key ) ) { // if door has a key entered, set it + ent->key = key; + } else { + ent->key = -1; // otherwise, set the key when this ent finishes spawning + + } + // if the key is invalid, set the key in the finishSpawning routine + if ( ent->key > MAX_DOOR_KEYS || ent->key < -1 ) { + G_Error( "invalid key number: %d in func_door_rotating\n", ent->key ); + ent->key = -1; + } + //---- (SA) end + + // default lip of 8 units + G_SpawnFloat( "lip", "8", &lip ); + + // default damage of 2 points + G_SpawnInt( "dmg", "2", &ent->damage ); + + // first position at start + VectorCopy( ent->s.origin, ent->pos1 ); + + VectorCopy( ent->s.angles, angles2 ); + + if ( ent->spawnflags & 1 ) { + angles2[1] -= 90; + } else { + angles2[1] += 90; + } + + // calculate second position + trap_SetBrushModel( ent, ent->model ); + G_SetMovedir( ent->s.angles, ent->movedir ); + abs_movedir[0] = fabs( ent->movedir[0] ); + abs_movedir[1] = fabs( ent->movedir[1] ); + abs_movedir[2] = fabs( ent->movedir[2] ); + VectorSubtract( ent->r.maxs, ent->r.mins, size ); + distance = DotProduct( abs_movedir, size ) - lip; + VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 ); + + // calculate third position + G_SetMovedir( angles2, ent->movedir ); + abs_movedir[0] = fabs( ent->movedir[0] ); + abs_movedir[1] = fabs( ent->movedir[1] ); + abs_movedir[2] = fabs( ent->movedir[2] ); + VectorSubtract( ent->r.maxs, ent->r.mins, size ); + distance = DotProduct( abs_movedir, size ) - lip; + VectorMA( ent->pos2, distance, ent->movedir, ent->pos3 ); + + // if "start_open", reverse position 1 and 3 + /*if ( ent->spawnflags & 1 ) { + vec3_t temp; + + VectorCopy( ent->pos3, temp ); + VectorCopy( ent->s.origin, ent->pos3 ); + VectorCopy( temp, ent->pos1 ); + }*/ + + InitMover( ent ); + + if ( !( ent->flags & FL_TEAMSLAVE ) ) { + int health; + + G_SpawnInt( "health", "0", &health ); + if ( health ) { + ent->takedamage = qtrue; + } + } + + ent->nextthink = level.time + FRAMETIME; + ent->think = finishSpawningKeyedMover; +} +// END JOSEPH + +/* +=============================================================================== + +PLAT + +=============================================================================== +*/ + +/* +============== +Touch_Plat + +Don't allow decent if a living player is on it +=============== +*/ +void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + // delay return-to-pos1 by one second + if ( ent->moverState == MOVER_POS2 ) { + ent->nextthink = level.time + 1000; + } +} + +/* +============== +Touch_PlatCenterTrigger + +If the plat is at the bottom position, start it going up +=============== +*/ +void Touch_PlatCenterTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( !other->client ) { + return; + } + + if ( ent->parent->moverState == MOVER_POS1 ) { + Use_BinaryMover( ent->parent, ent, other ); + } +} + + +/* +================ +SpawnPlatTrigger + +Spawn a trigger in the middle of the plat's low position +Elevator cars require that the trigger extend through the entire low position, +not just sit on top of it. +================ +*/ +void SpawnPlatTrigger( gentity_t *ent ) { + gentity_t *trigger; + vec3_t tmin, tmax; + + // the middle trigger will be a thin trigger just + // above the starting position + trigger = G_Spawn(); + trigger->touch = Touch_PlatCenterTrigger; + trigger->r.contents = CONTENTS_TRIGGER; + trigger->parent = ent; + + tmin[0] = ent->pos1[0] + ent->r.mins[0] + 33; + tmin[1] = ent->pos1[1] + ent->r.mins[1] + 33; + tmin[2] = ent->pos1[2] + ent->r.mins[2]; + + tmax[0] = ent->pos1[0] + ent->r.maxs[0] - 33; + tmax[1] = ent->pos1[1] + ent->r.maxs[1] - 33; + tmax[2] = ent->pos1[2] + ent->r.maxs[2] + 8; + + if ( tmax[0] <= tmin[0] ) { + tmin[0] = ent->pos1[0] + ( ent->r.mins[0] + ent->r.maxs[0] ) * 0.5; + tmax[0] = tmin[0] + 1; + } + if ( tmax[1] <= tmin[1] ) { + tmin[1] = ent->pos1[1] + ( ent->r.mins[1] + ent->r.maxs[1] ) * 0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy( tmin, trigger->r.mins ); + VectorCopy( tmax, trigger->r.maxs ); + + trap_LinkEntity( trigger ); +} + + +/*QUAKED func_plat (0 .5 .8) ? +Plats are always drawn in the extended position so they will light correctly. + +"lip" default 8, protrusion above rest position +"height" total height of movement, defaults to model height +"speed" overrides default 200. +"dmg" overrides default 2 +"model2" .md3 model to also draw +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_plat( gentity_t *ent ) { + float lip, height; + + ent->sound1to2 = ent->sound2to1 = G_SoundIndex( "sound/movers/plats/pt1_strt.wav" ); + ent->soundPos1 = ent->soundPos2 = G_SoundIndex( "sound/movers/plats/pt1_end.wav" ); + + VectorClear( ent->s.angles ); + + G_SpawnFloat( "speed", "200", &ent->speed ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "wait", "1", &ent->wait ); + G_SpawnFloat( "lip", "8", &lip ); + + ent->wait = 1000; + + // create second position + trap_SetBrushModel( ent, ent->model ); + + if ( !G_SpawnFloat( "height", "0", &height ) ) { + height = ( ent->r.maxs[2] - ent->r.mins[2] ) - lip; + } + + // pos1 is the rest (bottom) position, pos2 is the top + VectorCopy( ent->s.origin, ent->pos2 ); + VectorCopy( ent->pos2, ent->pos1 ); + ent->pos1[2] -= height; + + InitMover( ent ); + + // touch function keeps the plat from returning while + // a live player is standing on it + ent->touch = Touch_Plat; + + ent->blocked = Blocked_Door; + + ent->parent = ent; // so it can be treated as a door + + // spawn the trigger if one hasn't been custom made + if ( !ent->targetname ) { + SpawnPlatTrigger( ent ); + } +} + + +/* +=============================================================================== + +BUTTON + +=============================================================================== +*/ + +/* +============== +Touch_Button + +=============== +*/ +void Touch_Button( gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( !other->client ) { + return; + } + + if ( ent->moverState == MOVER_POS1 ) { + Use_BinaryMover( ent, other, other ); + } +} + + +/*QUAKED func_button (0 .5 .8) ? x x x TOUCH x x STAYOPEN +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"model2" .md3 model to also draw +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_button( gentity_t *ent ) { + vec3_t abs_movedir; + float distance; + vec3_t size; + float lip; + + ent->sound1to2 = G_SoundIndex( "sound/movers/switches/butn2.wav" ); + + if ( !ent->speed ) { + ent->speed = 40; + } + + if ( !ent->wait ) { + ent->wait = 1; + } + ent->wait *= 1000; + + // first position + VectorCopy( ent->s.origin, ent->pos1 ); + + // calculate second position + trap_SetBrushModel( ent, ent->model ); + + G_SpawnFloat( "lip", "4", &lip ); + + G_SetMovedir( ent->s.angles, ent->movedir ); + abs_movedir[0] = fabs( ent->movedir[0] ); + abs_movedir[1] = fabs( ent->movedir[1] ); + abs_movedir[2] = fabs( ent->movedir[2] ); + VectorSubtract( ent->r.maxs, ent->r.mins, size ); + distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip; + VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 ); + + if ( ent->health ) { + // shootable button + ent->takedamage = qtrue; + } else if ( ent->spawnflags & 8 ) { + // touchable button + ent->touch = Touch_Button; + } + + InitMover( ent ); +} + + + +/* +=============================================================================== + +TRAIN + +=============================================================================== +*/ + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/* +=============== +Think_BeginMoving + +The wait time at a corner has completed, so start moving again +=============== +*/ +void Think_BeginMoving( gentity_t *ent ) { + ent->s.pos.trTime = level.time; + ent->s.pos.trType = TR_LINEAR_STOP; +} + +/* +=============== +Reached_Train +=============== +*/ +void Reached_Train( gentity_t *ent ) { + gentity_t *next; + float speed; + vec3_t move; + float length; + + // copy the apropriate values + next = ent->nextTrain; + if ( !next || !next->nextTrain ) { + return; // just stop + } + + // Rafael + if ( next->wait == -1 && next->count ) { + return; + } + + // fire all other targets + G_UseTargets( next, NULL ); + + // set the new trajectory + ent->nextTrain = next->nextTrain; + + if ( next->wait == -1 ) { + next->count = 1; + } + + VectorCopy( next->s.origin, ent->pos1 ); + VectorCopy( next->nextTrain->s.origin, ent->pos2 ); + + // if the path_corner has a speed, use that + if ( next->speed ) { + speed = next->speed; + } else { + // otherwise use the train's speed + speed = ent->speed; + } + if ( speed < 1 ) { + speed = 1; + } + + if ( !strcmp( ent->classname, "func_bats" ) && next->radius ) { + ent->radius = next->radius; + } + + // calculate duration + VectorSubtract( ent->pos2, ent->pos1, move ); + length = VectorLength( move ); + + ent->s.pos.trDuration = length * 1000 / speed; + ent->gDuration = ent->s.pos.trDuration; + + // looping sound + ent->s.loopSound = next->soundLoop; + + // start it going + SetMoverState( ent, MOVER_1TO2, level.time ); + + // if there is a "wait" value on the target, don't start moving yet + if ( next->wait ) { + ent->nextthink = level.time + next->wait * 1000; + ent->think = Think_BeginMoving; + ent->s.pos.trType = TR_STATIONARY; + } +} + + +/* +=============== +Think_SetupTrainTargets + +Link all the corners together +=============== +*/ +void Think_SetupTrainTargets( gentity_t *ent ) { + gentity_t *path, *next, *start; + + ent->nextTrain = G_Find( NULL, FOFS( targetname ), ent->target ); + if ( !ent->nextTrain ) { + G_Printf( "func_train at %s with an unfound target\n", + vtos( ent->r.absmin ) ); + return; + } + + start = NULL; + + if ( ent->s.eType == ET_BAT ) { + for ( path = ent->nextTrain ; path != start ; path = next ) { + + if ( !start ) { + start = path; + } + + if ( !path->target ) { + G_Printf( "Train corner at %s without a target\n", + vtos( path->s.origin ) ); + return; + } + + // find a path_corner among the targets + // there may also be other targets that get fired when the corner + // is reached + next = NULL; + do { + next = G_Find( next, FOFS( targetname ), path->target ); + if ( !next ) { + G_Printf( "Train corner at %s without a target path_corner\n", + vtos( path->s.origin ) ); + return; + } + } while ( strcmp( next->classname, "path_corner" ) ); + + path->nextTrain = next; + } + } else + { + for ( path = ent->nextTrain ; !path->nextTrain ; path = next ) { + + if ( !start ) { + start = path; + } + + if ( !path->target ) { + G_Printf( "Train corner at %s without a target\n", + vtos( path->s.origin ) ); + return; + } + + // find a path_corner among the targets + // there may also be other targets that get fired when the corner + // is reached + next = NULL; + do { + next = G_Find( next, FOFS( targetname ), path->target ); + if ( !next ) { + G_Printf( "Train corner at %s without a target path_corner\n", + vtos( path->s.origin ) ); + return; + } + } while ( strcmp( next->classname, "path_corner" ) ); + + path->nextTrain = next; + } + } + + if ( !Q_stricmp( ent->classname, "func_train" ) && ent->spawnflags & 2 ) { // TOGGLE + VectorCopy( ent->nextTrain->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->nextTrain->s.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } else if ( !Q_stricmp( ent->classname, "func_train_particles" ) && ent->spawnflags & 2 ) { // TOGGLE + VectorCopy( ent->nextTrain->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->nextTrain->s.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } else if ( !Q_stricmp( ent->classname, "func_tramcar" ) && ent->spawnflags & 2 ) { // TOGGLE + VectorCopy( ent->nextTrain->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->nextTrain->s.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } else if ( !Q_stricmp( ent->classname, "func_bat" ) ) { + //VectorCopy (ent->nextTrain->s.origin, ent->s.pos.trBase); + //VectorCopy (ent->nextTrain->s.origin, ent->r.currentOrigin); + //trap_LinkEntity (ent); + if ( ent->spawnflags & 1 ) { // start on + ent->use( ent, ent, ent ); + } + } else if ( !Q_stricmp( ent->classname, "truck_cam" ) && ent->spawnflags & 2 ) { // TOGGLE + VectorCopy( ent->nextTrain->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->nextTrain->s.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } else + { + if ( !Q_stricmp( ent->classname, "func_tramcar" ) ) { + Reached_Tramcar( ent ); + } else if ( !Q_stricmp( ent->classname, "truck_cam" ) ) { + Reached_Tramcar( ent ); + } else if ( !Q_stricmp( ent->classname, "camera_cam" ) ) { + Reached_Tramcar( ent ); + } else { + Reached_Train( ent ); + } + } +} + + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) STOP END REVERSE +Train path corners. +Target: next path corner and other targets to fire +"speed" speed to move to the next corner +"wait" seconds to wait before behining move to next corner + +"count2" used only in conjunction with the truck_cam to control playing of gear changes +*/ +void SP_path_corner( gentity_t *self ) { + if ( !self->targetname ) { + G_Printf( "path_corner with no targetname at %s\n", vtos( self->s.origin ) ); + G_FreeEntity( self ); + return; + } + // path corners don't need to be linked in + + if ( self->wait == -1 ) { + self->count = 1; + } +} + + + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +A train is a mover that moves between path_corner target points. +Trains MUST HAVE AN ORIGIN BRUSH. +The train spawns at the first target it is pointing at. +"model2" .md3 model to also draw +"speed" default 100 +"dmg" default 2 +"noise" looping sound to play when the train is in motion +"target" next path corner +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_train( gentity_t *self ) { + VectorClear( self->s.angles ); + + if ( self->spawnflags & TRAIN_BLOCK_STOPS ) { + self->damage = 0; + self->s.eFlags |= EF_MOVER_STOP; + } else { + if ( !self->damage ) { + self->damage = 2; + } + } + + if ( !self->speed ) { + self->speed = 100; + } + + if ( !self->target ) { + G_Printf( "func_train without a target at %s\n", vtos( self->r.absmin ) ); + G_FreeEntity( self ); + return; + } + + trap_SetBrushModel( self, self->model ); + InitMover( self ); + + self->reached = Reached_Train; + + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = Think_SetupTrainTargets; + + self->blocked = Blocked_Door; + +} + +// Rafael - bats +/*QUAKED func_train_particles ( 0.3 0.1 0.8) ? START_ON TOGGLE +health = default 16 bats +*/ +void Func_train_particles_reached( gentity_t *self ) { + gentity_t *tent; + vec3_t vec, ang; + vec3_t forward; + + Reached_Train( self ); + + if ( self->nextTrain->wait == -1 && self->nextTrain->count ) { + return; + } + + if ( !self->count ) { + tent = G_TempEntity( self->r.currentOrigin, EV_BATS ); + tent->s.time = self->speed; + tent->s.density = self->health; + VectorCopy( self->r.currentOrigin, tent->s.origin ); + VectorSubtract( self->nextTrain->s.origin, self->r.currentOrigin, vec ); + vectoangles( vec, ang ); + AngleVectors( ang, forward, NULL, NULL ); + VectorCopy( forward, tent->s.angles ); + self->count = 1; + } else + { + tent = G_TempEntity( self->r.currentOrigin, EV_BATS_UPDATEPOSITION ); + tent->s.time = self->speed; + VectorCopy( self->r.currentOrigin, tent->s.origin ); + VectorSubtract( self->nextTrain->s.origin, self->r.currentOrigin, vec ); + vectoangles( vec, ang ); + AngleVectors( ang, forward, NULL, NULL ); + VectorCopy( forward, tent->s.angles ); + } + + tent->s.frame = self->s.number; + trap_LinkEntity( self ); + +} + +void SP_func_train_particles( gentity_t *self ) { + SP_func_train( self ); + self->reached = Func_train_particles_reached; + self->blocked = NULL; + + self->damage = 0; + + if ( !self->health ) { + self->health = 16; + } + + if ( !self->speed ) { + self->speed = 50; + } +} + +// RF, bats v2.0 +/*QUAKED func_bats ( 0.3 0.1 0.8) (-32 -32 -32) (32 32 32) START_ON TOGGLE +count = default 10 bats +radius = maximum distance from center of entity to place each bat (default=32) +speed = speed to travel to next waypoint (default=300) +*/ +void FuncBatsReached( gentity_t *self ) { + if ( self->active == 2 ) { + self->nextthink = -1; + self->think = NULL; + return; + } + + Reached_Train( self ); + + if ( !self->nextTrain || !self->nextTrain->target ) { + self->active = 2; // remove the bats at next point + return; + } +} + +// each bat calls this every server frame, so it moves towards it's ideal position +void BatMoveThink( gentity_t *bat ) { + gentity_t *owner; + vec3_t goalpos, vec; + float speed, dist; + int i; + trace_t tr; + + owner = &g_entities[bat->r.ownerNum]; + if ( owner->active == qtrue ) { // move towards the owner + BG_EvaluateTrajectory( &owner->s.pos, level.time, goalpos ); + + // randomize ther movedir as we go + for ( i = 0; i < 3; i++ ) + bat->movedir[i] += crandom() * (float)owner->radius * 0.1; + if ( VectorLength( bat->movedir ) > (float)owner->radius ) { + VectorNormalize( bat->movedir ); + VectorScale( bat->movedir, (float)owner->radius, bat->movedir ); + } + VectorAdd( goalpos, bat->movedir, goalpos ); + + VectorSubtract( goalpos, bat->s.pos.trBase, vec ); + dist = VectorLength( vec ); + speed = dist / 48; + VectorMA( bat->s.pos.trBase, 0.05 * speed, vec, bat->s.pos.trBase ); + bat->s.pos.trTime = level.time; + VectorCopy( bat->s.pos.trBase, bat->r.currentOrigin ); + trap_LinkEntity( bat ); + + // check for hurting someone + if ( bat->damage < level.time ) { + trap_Trace( &tr, bat->r.currentOrigin, NULL, NULL, bat->r.currentOrigin, bat->s.number, CONTENTS_BODY ); + if ( tr.startsolid && tr.entityNum < MAX_CLIENTS && !g_entities[tr.entityNum].aiCharacter ) { + G_Damage( &g_entities[tr.entityNum], bat, bat, vec3_origin, bat->r.currentOrigin, 1 + rand() % 3, DAMAGE_NO_KNOCKBACK, MOD_BAT ); + + // !! TODO: bat biting sound and view feedback + + // don't keep hurting them each time we think + bat->damage = level.time + 1000; + } + } + + } else if ( owner->active == 2 ) { + // owner has finished + G_FreeEntity( bat ); + return; + } + bat->nextthink = level.time + 50; +} + +void BatDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) { + G_AddEvent( self, EV_BATS_DEATH, 0 ); + self->think = G_FreeEntity; + self->nextthink = level.time + 100; +} + +void FuncBatsActivate( gentity_t *self, gentity_t * other, gentity_t * activator ) { + int i; + gentity_t *bat; + vec3_t vec; + + if ( !self->active ) { + self->active = qtrue; + + // spawn "count" bats + for ( i = 0; i < self->count; i++ ) { + bat = G_Spawn(); + bat->classname = "func_bat"; + bat->s.eType = ET_BAT; + + VectorSet( vec, crandom(), crandom(), crandom() ); + VectorNormalize( vec ); + VectorScale( vec, random() * (float)self->radius, bat->movedir ); + VectorAdd( self->s.pos.trBase, bat->movedir, bat->s.pos.trBase ); + bat->s.pos.trTime = level.time; + VectorClear( bat->s.pos.trDelta ); + VectorCopy( bat->s.pos.trBase, bat->r.currentOrigin ); + + bat->r.ownerNum = self->s.number; + bat->r.contents = CONTENTS_CORPSE; + bat->takedamage = qtrue; + bat->health = 1; + bat->pain = NULL; + bat->die = BatDie; + VectorSet( bat->r.mins, -18, -18, -18 ); + VectorSet( bat->r.maxs, 18, 18, 18 ); + + bat->speed = self->speed; + bat->radius = self->radius; + + bat->think = BatMoveThink; + bat->nextthink = level.time + 50; + + trap_LinkEntity( bat ); + } + + InitMover( self ); // start moving + FuncBatsReached( self ); + self->reached = FuncBatsReached; + self->blocked = NULL; + + // disable this to debug path + self->r.svFlags |= SVF_NOCLIENT; + self->r.contents = 0; + + self->use = FuncBatsActivate; // make sure this stays the same + + } else { // second use kills bats + self->active = 2; + } +} + +void SP_func_bats( gentity_t *self ) { + if ( !self->count ) { + self->count = 10; + } + + if ( !self->radius ) { + self->radius = 32; + } + + if ( !self->speed ) { + self->speed = 300; + } + + // setup train waypoints + self->active = qfalse; + self->use = FuncBatsActivate; + + self->damage = 0; + + self->nextthink = level.time + FRAMETIME; + self->think = Think_SetupTrainTargets; + + // disable this to debug path + self->r.svFlags |= SVF_NOCLIENT; + self->r.contents = 0; + + // TODO: make us shootable, but not "solid", so that if the player hits the group, at least one bat is killed +} + +// JOSEPH 9-27-99 +/* +=============== +Think_BeginMoving_rotating + +The wait time at a corner has completed, so start moving again +=============== +*/ +void Think_BeginMoving_rotating( gentity_t *ent ) { + ent->s.pos.trTime = level.time; + ent->s.pos.trType = TR_LINEAR_STOP; +} + +/* +=============== +Reached_Train_rotating +=============== +*/ +void Reached_Train_rotating( gentity_t *ent ) { + gentity_t *next; + float speed; + vec3_t move; + float length; + float frames; + + // copy the apropriate values + next = ent->nextTrain; + if ( !next || !next->nextTrain ) { + return; // just stop + } + + // fire all other targets + G_UseTargets( next, NULL ); + + // set the new trajectory + ent->nextTrain = next->nextTrain; + VectorCopy( next->s.origin, ent->pos1 ); + VectorCopy( next->nextTrain->s.origin, ent->pos2 ); + + // if the path_corner has a speed, use that + if ( next->speed ) { + speed = next->speed; + } else { + // otherwise use the train's speed + speed = ent->speed; + } + if ( speed < 1 ) { + speed = 1; + } + + ent->rotate[0] = next->rotate[2]; + ent->rotate[1] = next->rotate[0]; + ent->rotate[2] = next->rotate[1]; + + // calculate duration + VectorSubtract( ent->pos2, ent->pos1, move ); + length = VectorLength( move ); + + if ( next->duration ) { + ent->s.pos.trDuration = ( next->duration * 1000 ); + } else { + ent->s.pos.trDuration = length * 1000 / speed; + } + + // Rotate the train + frames = floor( ent->s.pos.trDuration / 100 ); + + if ( !frames ) { + frames = 0.001; + } + + ent->s.apos.trType = TR_LINEAR; + + if ( ent->TargetFlag ) { + VectorCopy( ent->TargetAngles, ent->r.currentAngles ); + VectorCopy( ent->r.currentAngles, ent->s.angles ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + ent->TargetFlag = 0; + } + + //G_Printf( "Train angles %s\n", + // vtos(ent->s.angles) ); + + //G_Printf( "Add X Y X %s\n", + // vtos(ent->rotate) ); + + // X + if ( ent->rotate[2] ) { + ent->s.apos.trDelta[2] = ( ent->rotate[2] / frames ) * 10; + } else { + ent->s.apos.trDelta[2] = 0; + } + // Y + if ( ent->rotate[0] ) { + ent->s.apos.trDelta[0] = ( ent->rotate[0] / frames ) * 10; + } else { + ent->s.apos.trDelta[0] = 0; + } + // Z + if ( ent->rotate[1] ) { + ent->s.apos.trDelta[1] = ( ent->rotate[1] / frames ) * 10; + } else { + ent->s.apos.trDelta[1] = 0; + } + + // looping sound + ent->s.loopSound = next->soundLoop; + + ent->TargetFlag = 1; + ent->TargetAngles[0] = ent->r.currentAngles[0] + ent->rotate[0]; + //ent->TargetAngles[0] = AngleNormalize360 (ent->TargetAngles[0]); + ent->TargetAngles[1] = ent->r.currentAngles[1] + ent->rotate[1]; + //ent->TargetAngles[1] = AngleNormalize360 (ent->TargetAngles[1]); + ent->TargetAngles[2] = ent->r.currentAngles[2] + ent->rotate[2]; + //ent->TargetAngles[2] = AngleNormalize360 (ent->TargetAngles[2]); + + // start it going + SetMoverState( ent, MOVER_1TO2, level.time ); + + // if there is a "wait" value on the target, don't start moving yet + if ( next->wait ) { + ent->nextthink = level.time + next->wait * 1000; + ent->think = Think_BeginMoving_rotating; + ent->s.pos.trType = TR_STATIONARY; + } +} + +/* +=============== +Think_SetupTrainTargets_rotating + +Link all the corners together +=============== +*/ +void Think_SetupTrainTargets_rotating( gentity_t *ent ) { + gentity_t *path, *next, *start; + + + ent->nextTrain = G_Find( NULL, FOFS( targetname ), ent->target ); + if ( !ent->nextTrain ) { + G_Printf( "func_train at %s with an unfound target\n", + vtos( ent->r.absmin ) ); + return; + } + + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + VectorCopy( ent->s.angles, ent->TargetAngles ); + ent->TargetFlag = 1; + + start = NULL; + for ( path = ent->nextTrain ; path != start ; path = next ) { + if ( !start ) { + start = path; + } + + if ( !path->target ) { + G_Printf( "Train corner at %s without a target\n", + vtos( path->s.origin ) ); + return; + } + + // find a path_corner among the targets + // there may also be other targets that get fired when the corner + // is reached + next = NULL; + do { + next = G_Find( next, FOFS( targetname ), path->target ); + if ( !next ) { + G_Printf( "Train corner at %s without a target path_corner\n", + vtos( path->s.origin ) ); + return; + } + } while ( strcmp( next->classname, "path_corner" ) ); + + path->nextTrain = next; + } + + // start the train moving from the first corner + Reached_Train_rotating( ent ); +} + +/*QUAKED func_train_rotating (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +A train is a mover that moves between path_corner target points. +This train can also rotate along the X Y Z +Trains MUST HAVE AN ORIGIN BRUSH. +The train spawns at the first target it is pointing at. + +"model2" .md3 model to also draw +"dmg" default 2 +"speed" default 100 +"noise" looping sound to play when the train is in motion +"target" next path corner +"color" constantLight color +"light" constantLight radius + +On the path corner: +speed departure speed from that corner +rotate angle change for X Y Z to next corner +duration duration for angle change (overrides speed) +*/ + +void SP_func_train_rotating( gentity_t *self ) { + VectorClear( self->s.angles ); + + if ( self->spawnflags & TRAIN_BLOCK_STOPS ) { + self->damage = 0; + } else { + if ( !self->damage ) { + self->damage = 2; + } + } + + if ( !self->speed ) { + self->speed = 100; + } + + if ( !self->target ) { + G_Printf( "func_train without a target at %s\n", vtos( self->r.absmin ) ); + G_FreeEntity( self ); + return; + } + + trap_SetBrushModel( self, self->model ); + InitMover( self ); + + self->reached = Reached_Train_rotating; + + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = Think_SetupTrainTargets_rotating; +} +// END JOSEPH + +/* +=============================================================================== + +STATIC + +=============================================================================== +*/ + +/* +============== +Use_Static + toggle hide or show (including collisions) this entity +============== +*/ +void Use_Static( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + // DISABLED since func_static will carve up AAS anyway, so blocking makes no sense + // RF, AAS areas are now free + //if (ent->model) + // G_SetAASBlockingEntity( ent, qfalse ); + } else { + trap_LinkEntity( ent ); + // DISABLED since func_static will carve up AAS anyway, so blocking makes no sense + // RF, AAS areas are now occupied + //if (ent->model) + // G_SetAASBlockingEntity( ent, qtrue ); + } +} + +void Static_Pain( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ) { + vec3_t temp; + + if ( ent->spawnflags & 4 ) { + if ( level.time > ent->wait + ent->delay + rand() % 1000 + 500 ) { + ent->wait = level.time; + } else { + return; + } + + // TBD only venom mg42 rocket and grenade can inflict damage + if ( attacker && attacker->client + && ( attacker->s.weapon == WP_VENOM + || attacker->s.weapon == WP_VENOM_FULL + || attacker->s.weapon == WP_GRENADE_LAUNCHER + || attacker->s.weapon == WP_ROCKET_LAUNCHER + || attacker->client->ps.persistant[PERS_HWEAPON_USE] ) ) { + + VectorCopy( ent->r.currentOrigin, temp ); + VectorCopy( ent->pos3, ent->r.currentOrigin ); + Spawn_Shard( ent, attacker, 3, ent->count ); + VectorCopy( temp, ent->r.currentOrigin ); + } + return; + } + + if ( level.time > ent->wait + ent->delay + rand() % 1000 + 500 ) { + G_UseTargets( ent, NULL ); + ent->wait = level.time; + } + +} + +void G_BlockThink( gentity_t *ent ) { + if ( ent->r.linked ) { + G_SetAASBlockingEntity( ent, qtrue ); + } else { + G_SetAASBlockingEntity( ent, qfalse ); + } +} + + +/*QUAKED func_leaky (0 .5 .8) ? +"type" - leaks particles of this type + +1:oil +2:water +3:steam + + +*/ + +void SP_func_leaky( gentity_t *ent ) { + if ( ent->model2 ) { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + } + trap_SetBrushModel( ent, ent->model ); + trap_LinkEntity( ent ); + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); +} + + +/*QUAKED func_static (0 .5 .8) ? start_invis pain painEFX +A bmodel that just sits there, doing nothing. Can be used for conditional walls and models. +"model2" .md3 model to also draw +"color" constantLight color +"light" constantLight radius +"start_invis" will start the entity as non-existant +If targeted, it will toggle existance when triggered + +pain will use its target + +When using pain you will need to specify the delay time +value of 1 = 1 sec 2 = 2 sec so on... +default is 1 sec you can use decimals +example : +delay +1.27 + +painEFX will spawn a shards +example: +shard +4 +will spawn rubble + +shard default is 4 + +shard = +shard_glass = 0, +shard_wood = 1, +shard_metal = 2, +shard_ceramic = 3, +shard_pebbles = 4 +*/ +void SP_func_static( gentity_t *ent ) { + if ( ent->model2 ) { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + } + trap_SetBrushModel( ent, ent->model ); + InitMover( ent ); + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + ent->use = Use_Static; + + if ( ent->spawnflags & 1 ) { + trap_UnlinkEntity( ent ); + } + + if ( !( ent->flags & FL_TEAMSLAVE ) ) { + int health; + + G_SpawnInt( "health", "0", &health ); + if ( health ) { + ent->takedamage = qtrue; + } + } + + if ( ent->spawnflags & 2 || ent->spawnflags & 4 ) { + ent->pain = Static_Pain; + + if ( !ent->delay ) { + ent->delay = 1000; + } else { + ent->delay *= 1000; + } + + ent->takedamage = qtrue; + + ent->isProp = qtrue; + + ent->health = 9999; + + if ( !( ent->count ) ) { + ent->count = 4; + } + } + + // DISABLED since func_static will carve up AAS anyway, so blocking makes no sense + /* + // RF, check for blocking AAS + if ( ent->spawnflags & 1 ) { + // RF, AAS areas are now occupied + if (ent->model) { + ent->think = G_BlockThink; + ent->nextthink = level.time + FRAMETIME; + } + } + */ +} + + +/* +=============================================================================== + +ROTATING + +=============================================================================== +*/ + + +/*QUAKED func_rotating (0 .5 .8) ? START_ON STARTINVIS X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"model2" .md3 model to also draw +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +*/ + +void Use_Func_Rotate( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->spawnflags & 4 ) { + ent->s.apos.trDelta[2] = ent->speed; + } else if ( ent->spawnflags & 8 ) { + ent->s.apos.trDelta[0] = ent->speed; + } else { + ent->s.apos.trDelta[1] = ent->speed; + } + + if ( ent->spawnflags & 2 ) { + ent->flags &= ~FL_TEAMSLAVE; + } + + trap_LinkEntity( ent ); +} + +void SP_func_rotating( gentity_t *ent ) { + if ( !ent->speed ) { + ent->speed = 100; + } + + // set the axis of rotation + ent->s.apos.trType = TR_LINEAR; + + if ( ent->spawnflags & 1 ) { + if ( ent->spawnflags & 4 ) { + ent->s.apos.trDelta[2] = ent->speed; + } else if ( ent->spawnflags & 8 ) { + ent->s.apos.trDelta[0] = ent->speed; + } else { + ent->s.apos.trDelta[1] = ent->speed; + } + } + + if ( !ent->damage ) { + ent->damage = 2; + } + + trap_SetBrushModel( ent, ent->model ); + InitMover( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + VectorCopy( ent->s.apos.trBase, ent->r.currentAngles ); + + if ( ent->spawnflags & 2 ) { + ent->flags |= FL_TEAMSLAVE; + trap_UnlinkEntity( ent ); + } else { + trap_LinkEntity( ent ); + } + +} + + +/* +=============================================================================== + +BOBBING + +=============================================================================== +*/ + + +/*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS +Normally bobs on the Z axis +"model2" .md3 model to also draw +"height" amplitude of bob (32 default) +"speed" seconds to complete a bob cycle (4 default) +"phase" the 0.0 to 1.0 offset in the cycle to start at +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_bobbing( gentity_t *ent ) { + float height; + float phase; + + G_SpawnFloat( "speed", "4", &ent->speed ); + G_SpawnFloat( "height", "32", &height ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "phase", "0", &phase ); + + trap_SetBrushModel( ent, ent->model ); + InitMover( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + + ent->s.pos.trDuration = ent->speed * 1000; + ent->s.pos.trTime = ent->s.pos.trDuration * phase; + ent->s.pos.trType = TR_SINE; + + // set the axis of bobbing + if ( ent->spawnflags & 1 ) { + ent->s.pos.trDelta[0] = height; + } else if ( ent->spawnflags & 2 ) { + ent->s.pos.trDelta[1] = height; + } else { + ent->s.pos.trDelta[2] = height; + } +} + +/* +=============================================================================== + +PENDULUM + +=============================================================================== +*/ + + +/*QUAKED func_pendulum (0 .5 .8) ? +You need to have an origin brush as part of this entity. +Pendulums always swing north / south on unrotated models. Add an angles field to the model to allow rotation in other directions. +Pendulum frequency is a physical constant based on the length of the beam and gravity. +"model2" .md3 model to also draw +"speed" the number of degrees each way the pendulum swings, (30 default) +"phase" the 0.0 to 1.0 offset in the cycle to start at +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_pendulum( gentity_t *ent ) { + float freq; + float length; + float phase; + float speed; + + G_SpawnFloat( "speed", "30", &speed ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "phase", "0", &phase ); + + trap_SetBrushModel( ent, ent->model ); + + // find pendulum length + length = fabs( ent->r.mins[2] ); + if ( length < 8 ) { + length = 8; + } + + freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity.value / ( 3 * length ) ); + + ent->s.pos.trDuration = ( 1000 / freq ); + + InitMover( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + + ent->s.apos.trDuration = 1000 / freq; + ent->s.apos.trTime = ent->s.apos.trDuration * phase; + ent->s.apos.trType = TR_SINE; + ent->s.apos.trDelta[2] = speed; +} + +/*QUAKED func_door_rotating (0 .5 .8) ? - TOGGLE X_AXIS Y_AXIS REVERSE FORCE STAYOPEN TAKE_KEY +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that (only one axis allowed. If both X and Y +are checked, the default of Z will be used). +FORCE door opens even if blocked +TAKE_KEY removes the key from the players inventory +SHOOT-THRU Bullets don't stop when they hit the door. Set "shoot_thru_scale" with bullet damage scale (see below) + +"key" -1 for locked, key number for which key opens, 0 for open. default '0' unless door is targeted. (trigger_aidoor entities targeting this door do /not/ affect the key status) +"model2" .md3 model to also draw +"degrees" determines how many degrees it will turn (90 default) +"speed" movement speed (100 default) +"closespeed" optional different movement speed for door closing +"time" how many milliseconds it will take to open 1 sec = 1000 +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +"shoot_thru_scale" Multiplier for how much damage bullets do that have passed through the door. Effectively how much damage the door 'absorbs'. 0.0 - 1.0 (0.0 will turn SHOOT-THRU off, 1.0 is full damage) +"type" use sounds based on construction of door: + 0 - nosound (default) + 1 - metal + 2 - stone + 3 - lab + 4 - wood + 5 - iron/jail + 6 - portcullis + 7 - wood (quiet) +"team" team name. other doors with same team name will open/close in syncronicity +*/ + + + + +// +// +void SP_func_door_rotating( gentity_t *ent ) { + int key, doortype; + + G_SpawnInt( "type", "0", &doortype ); + + if ( doortype ) { // /*why on earthy did this check for <=8?*/ && doortype <= 8) // no doortype = silent + DoorSetSounds( ent, doortype, qtrue ); + } + + + // set the duration + if ( !ent->speed ) { + ent->speed = 1000; + } + + // degrees door will open + if ( !ent->angle ) { + ent->angle = 90; + } + + // reverse direction + if ( ent->spawnflags & 16 ) { + ent->angle *= -1; + } + + // TOGGLE + if ( ent->spawnflags & 2 ) { + ent->flags |= FL_TOGGLE; + } + + //---- (SA) door keys + + if ( G_SpawnInt( "key", "", &key ) ) { // if door has a key entered, set it + ent->key = key; + } else { + ent->key = -2; // otherwise, set the key when this ent finishes spawning + + } + // if the key is invalid, set the key in the finishSpawning routine + if ( ent->key > MAX_DOOR_KEYS || ent->key < -2 ) { + G_Error( "invalid key number: %d in func_door_rotating\n", ent->key ); + ent->key = -2; + } + //---- (SA) end + + + // set the rotation axis + VectorClear( ent->rotate ); + if ( ent->spawnflags & 4 ) { + ent->rotate[2] = 1; + } else if ( ent->spawnflags & 8 ) { + ent->rotate[0] = 1; + } else { ent->rotate[1] = 1;} + + if ( VectorLength( ent->rotate ) > 1 ) { // check that rotation is only set for one axis + G_Error( "Too many axis marked in func_door_rotating entity. Only choose one axis of rotation. (defaulting to standard door rotation)" ); + VectorClear( ent->rotate ); + ent->rotate[1] = 1; + } + + if ( !ent->wait ) { + ent->wait = 2; + } + ent->wait *= 1000; + + //if (!ent->damage) { + // ent->damage = 2; + //} + + trap_SetBrushModel( ent, ent->model ); + + InitMoverRotate( ent ); + + ent->s.dmgFlags = HINT_DOOR_ROTATING; + + if ( !( ent->flags & FL_TEAMSLAVE ) ) { + int health; + + G_SpawnInt( "health", "0", &health ); + if ( health ) { + ent->takedamage = qtrue; + } + } + + ent->nextthink = level.time + FRAMETIME; + ent->think = finishSpawningKeyedMover; + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + VectorCopy( ent->s.apos.trBase, ent->r.currentAngles ); + + ent->blocked = Blocked_DoorRotate; + + trap_LinkEntity( ent ); +} + + + + +/* +=============================================================================== + +EFFECTS + + I'm keeping all this stuff in here just to avoid collisions with Raf right now in g_misc or g_props + Will move. +=============================================================================== +*/ + +/* +============== +target_effect +============== +*/ +void target_effect( gentity_t *self, gentity_t *other, gentity_t *activator ) { + gentity_t *tent; + + tent = G_TempEntity( self->r.currentOrigin, EV_EFFECT ); + VectorCopy( self->r.currentOrigin, tent->s.origin ); + if ( self->spawnflags & 32 ) { + tent->s.dl_intensity = 1; // low grav + } else { + tent->s.dl_intensity = 0; + } + + trap_SetConfigstring( CS_TARGETEFFECT, self->dl_shader ); //----(SA) allow shader to be set from entity + + // (SA) this should match the values from func_explosive + tent->s.frame = self->key; // pass the type to the client ("glass", "wood", "metal", "gibs", "brick", "stone", "fabric", 0, 1, 2, 3, 4, 5, 6) + + tent->s.eventParm = self->spawnflags; + tent->s.density = self->health; + + if ( self->damage ) { + G_RadiusDamage( self->s.pos.trBase, self, self->damage, self->damage, self, MOD_EXPLOSIVE ); + } + + G_UseTargets( self, other ); +} + + +/*QUAKED target_effect (0 .5 .8) (-6 -6 -6) (6 6 6) fire explode smoke rubble gore lowgrav debris +"mass" defaults to 15. This determines how much debris is emitted when it explodes. (number of pieces) +"dmg" defaults to 0. damage radius blast when triggered +"type" - if 'rubble' is specified, this is the model type ("glass", "wood", "metal", "gibs", "brick", "rock", "fabric") default is "wood" +*/ +void SP_target_effect( gentity_t *ent ) { + int mass; + char *type; + + ent->use = target_effect; + + if ( G_SpawnInt( "mass", "15", &mass ) ) { + ent->health = mass; + } else { + ent->health = 15; + } + + // (SA) this should match the values from func_explosive + if ( G_SpawnString( "type", "wood", &type ) ) { + if ( !Q_stricmp( type,"wood" ) ) { + ent->key = 0; + } else if ( !Q_stricmp( type,"glass" ) ) { + ent->key = 1; + } else if ( !Q_stricmp( type,"metal" ) ) { + ent->key = 2; + } else if ( !Q_stricmp( type,"gibs" ) ) { + ent->key = 3; + } else if ( !Q_stricmp( type,"brick" ) ) { + ent->key = 4; + } else if ( !Q_stricmp( type,"rock" ) ) { + ent->key = 5; + } else if ( !Q_stricmp( type,"fabric" ) ) { + ent->key = 6; + } + } else { + ent->key = 5; // default to 'rock' + } + +} + + + +/* +=============================================================================== + +EXPLOSIVE + I'm keeping all this stuff in here just to avoid collisions with Raf right now in g_misc or g_props + Will move. +=============================================================================== +*/ + + +/* +============== +ThrowDebris +============== +*/ +void ThrowDebris( gentity_t *self, char *modelname, float speed, vec3_t origin ) { + // probably use le->leType = LE_FRAGMENT like brass and gibs +} + +/* +============== +BecomeExplosion + nuke the original entity and create all the debris entities that need to be synced to clients +============== +*/ +void BecomeExplosion( gentity_t *self ) { + self->die = NULL; + self->pain = NULL; + self->touch = NULL; + self->use = NULL; + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + + G_FreeEntity( self ); +} + + + +/* +============== +func_explosive_explode + NOTE: the 'damage' passed in is ignored completely +============== +*/ +void func_explosive_explode( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + vec3_t origin; + vec3_t size; + vec3_t dir = {0, 0, 1}; + gentity_t *tent = 0; + + // RF, AAS areas are now free + if ( !( self->spawnflags & 16 ) ) { + G_SetAASBlockingEntity( self, qfalse ); + } + + self->takedamage = qfalse; // don't allow anything try to hurt me now that i'm exploding + + self->think = BecomeExplosion; + self->nextthink = level.time + FRAMETIME; + + VectorSubtract( self->r.absmax, self->r.absmin, size ); + VectorScale( size, 0.5, size ); + VectorAdd( self->r.absmin, size, origin ); + + VectorCopy( origin, self->s.pos.trBase ); + + G_UseTargets( self, attacker ); + + self->s.density = self->count; // pass the "mass" to the client + self->s.weapon = self->duration; // pass the "force lowgrav" to client + self->s.frame = self->key; // pass the type to the client ("glass", "wood", "metal", "gibs", "brick", "stone", "fabric", 0, 1, 2, 3, 4, 5, 6) + + if ( self->damage ) { +// G_RadiusDamage(self->s.origin, self, self->damage, self->damage+40, self, MOD_EXPLOSIVE); + G_RadiusDamage( self->s.pos.trBase, self, self->damage, self->damage + 40, self, MOD_EXPLOSIVE ); + } + + // find target, aim at that + if ( self->target ) { + + // since the explosive might need to fire the target rather than + // aim at it, only aim at 'info_notnull' ents + while ( 1 ) + { + tent = G_Find( tent, FOFS( targetname ), self->target ); + if ( !tent ) { + break; + } + + if ( !Q_stricmp( tent->classname, "info_notnull" ) ) { + break; // found an info_notnull + } + } + + if ( tent ) { + VectorSubtract( tent->s.pos.trBase, self->s.pos.trBase, dir ); + VectorNormalize( dir ); + } + } + + // if a valid target entity was not found, check for a specified 'angle' for the explosion direction + if ( !tent ) { + if ( self->s.angles[1] ) { + // up + if ( self->s.angles[1] == -1 ) { + // it's 'up' by default + } + // down + else if ( self->s.angles[1] == -2 ) { + dir[2] = -1; + } + // yawed + else + { + RotatePointAroundVector( dir, dir, tv( 1, 0, 0 ), self->s.angles[1] ); + } + } + } + + G_AddEvent( self, EV_EXPLODE, DirToByte( dir ) ); + +} + +/* +============== +func_explosive_touch +============== +*/ +void func_explosive_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { +// func_explosive_explode(self, self, other, self->health, 0); + func_explosive_explode( self, self, other, self->damage, 0 ); +} + + +/* +============== +func_explosive_use +============== +*/ +void func_explosive_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { +// func_explosive_explode (self, self, other, self->health, 0); + G_Script_ScriptEvent( self, "death", "" ); // JPW NERVE used to trigger script stuff for MP + func_explosive_explode( self, self, other, self->damage, 0 ); +} + +/* +============== +func_explosive_alert +============== +*/ +void func_explosive_alert( gentity_t *self ) { + func_explosive_explode( self, self, self, self->damage, 0 ); +} + +/* +============== +func_explosive_spawn +============== +*/ +void func_explosive_spawn( gentity_t *self, gentity_t *other, gentity_t *activator ) { + trap_LinkEntity( self ); + self->use = func_explosive_use; + // turn the brush to visible + + // RF, AAS areas are now occupied + if ( !( self->spawnflags & 16 ) ) { + G_SetAASBlockingEntity( self, qtrue ); + } +} + + + + + +/* +============== +InitExplosive +============== +*/ +void InitExplosive( gentity_t *ent ) { + char *damage; + + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if ( ent->model2 ) { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + } + + // pick it up if the level designer uses "damage" instead of "dmg" + if ( G_SpawnString( "damage", "0", &damage ) ) { + ent->damage = atoi( damage ); + } + + ent->s.eType = ET_EXPLOSIVE; + trap_LinkEntity( ent ); + + ent->think = G_BlockThink; + ent->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_explosive (0 .5 .8) ? START_INVIS TOUCHABLE USESHADER LOWGRAV NOBLOCKAAS EXPLO DYNOMITE +EXPLO only explosives can damage it rockets grendades etc +DYNOMITE only can be damaged by DY-NO-MITE! +Any brush that you want to explode or break apart. If you want an explosion, set dmg and it will do a radius explosion of that amount at the center of the bursh. +TOUCHABLE means automatic use on player contact. +USESHADER will apply the shader used on the brush model to the debris. +LOWGRAV specifies that the debris will /always/ fall slowly +"item" - when it explodes, pop this item out with the debirs (use QUAKED name. ex: "item_health_small") +"dmg" - how much radius damage should be done, defaults to 0 +"health" - defaults to 100. If health is set to '0' the brush will not be shootable. +"targetname" - if set, no touch field will be spawned and a remote button or trigger field triggers the explosion. +"type" - type of debris ("glass", "wood", "metal", "gibs", "brick", "rock", "fabric") default is "wood" +"mass" - defaults to 75. This determines how much debris is emitted when it explodes. You get one large chunk per 100 of mass (up to 8) and one small chunk per 25 of mass (up to 16). So 800 gives the most. +"noise" - sound to play when triggered. The explosive will default to a sound that matches it's 'type'. Use the sound name "nosound" (case in-sensitive) if you want it silent. +the default sounds are: + "wood" - "sound/world/boardbreak.wav" + "glass" - "sound/world/glassbreak.wav" + "metal" - "sound/world/metalbreak.wav" + "gibs" - "sound/player/gibsplit1.wav" + "brick" - "sound/world/brickfall.wav" + "stone" - "sound/world/stonefall.wav" + "fabric" - "sound/world/metalbreak.wav" // (SA) temp +*/ +/* +"fxdensity" size of explosion 1 - 100 (default is 10) +*/ +void SP_func_explosive( gentity_t *ent ) { + int health, mass, dam, i; + char buffer[MAX_QPATH]; + char *s; + char *type; + char *cursorhint; + + trap_SetBrushModel( ent, ent->model ); + InitExplosive( ent ); + + if ( ent->spawnflags & 1 ) { // start invis + ent->use = func_explosive_spawn; + trap_UnlinkEntity( ent ); + } else if ( ent->targetname ) { + ent->use = func_explosive_use; + ent->AIScript_AlertEntity = func_explosive_alert; + } + + + if ( ent->spawnflags & 2 ) { // touchable + ent->touch = func_explosive_touch; + } else { + ent->touch = NULL; + } + + if ( ( ent->spawnflags & 4 ) && ent->model && strlen( ent->model ) ) { // use shader + ent->s.eFlags |= EF_INHERITSHADER; + } + + if ( ent->spawnflags & 8 ) { // force lowgravity + ent->duration = 1; + } + + G_SpawnInt( "health", "100", &health ); + ent->health = health; + + G_SpawnInt( "dmg", "0", &dam ); + ent->damage = dam; + + if ( ent->health ) { + ent->takedamage = qtrue; + } + + if ( G_SpawnInt( "mass", "75", &mass ) ) { + ent->count = mass; + } else { + ent->count = 75; + } + + if ( G_SpawnString( "type", "wood", &type ) ) { + if ( !Q_stricmp( type,"wood" ) ) { + ent->key = 0; + } else if ( !Q_stricmp( type,"glass" ) ) { + ent->key = 1; + } else if ( !Q_stricmp( type,"metal" ) ) { + ent->key = 2; + } else if ( !Q_stricmp( type,"gibs" ) ) { + ent->key = 3; + } else if ( !Q_stricmp( type,"brick" ) ) { + ent->key = 4; + } else if ( !Q_stricmp( type,"rock" ) ) { + ent->key = 5; + } else if ( !Q_stricmp( type,"fabric" ) ) { + ent->key = 6; + } + } else { + ent->key = 0; + } + + if ( G_SpawnString( "noise", "NOSOUND", &s ) ) { + if ( Q_stricmp( s, "nosound" ) ) { + Q_strncpyz( buffer, s, sizeof( buffer ) ); + ent->s.dl_intensity = G_SoundIndex( buffer ); + } + } else { +// ent->s.dl_intensity = 0; + switch ( ent->key ) + { + case 0: // "wood" + ent->s.dl_intensity = G_SoundIndex( "sound/world/boardbreak.wav" ); + break; + case 1: // "glass" + ent->s.dl_intensity = G_SoundIndex( "sound/world/glassbreak.wav" ); + break; + case 2: // "metal" + ent->s.dl_intensity = G_SoundIndex( "sound/world/metalbreak.wav" ); + break; + case 3: // "gibs" + ent->s.dl_intensity = G_SoundIndex( "sound/player/gibsplit1.wav" ); + break; + case 4: // "brick" + ent->s.dl_intensity = G_SoundIndex( "sound/world/brickfall.wav" ); + break; + case 5: // "stone" + ent->s.dl_intensity = G_SoundIndex( "sound/world/stonefall.wav" ); + break; + + default: + break; + } + } + +//----(SA) added + + ent->s.dmgFlags = 0; + + if ( G_SpawnString( "cursorhint", "0", &cursorhint ) ) { + + for ( i = 0; i < HINT_NUM_HINTS; i++ ) { + if ( !Q_strcasecmp( cursorhint, hintStrings[i] ) ) { + ent->s.dmgFlags = i; + } + } + } +//----(SA) end + + // (SA) shouldn't need this +// ent->s.density = ent->count; // pass the "mass" to the client + + + ent->die = func_explosive_explode; +} + +/*QUAKED func_invisible_user (.3 .5 .8) ? STARTOFF HAS_USER NO_OFF_NOISE NOT_KICKABLE +when activated will use its target +"delay" - time (in seconds) before it can be used again +"offnoise" - specifies an alternate sound +"cursorhint" - overrides the auto-location of targeted entity (list below) +Normally when a player 'activates' this entity, if the entity has been turned 'off' (by a scripted command) you will hear a sound to indicate that you cannot activate the user. +The sound defaults to "sound/movers/invis_user_off.wav" + +NO_OFF_NOISE - no sound will play if the invis_user is used when 'off' +NOT_KICKABLE - kicking doesn't fire, only player activating + +"cursorhint" cursor types: (probably more, ask sherman if you think the list is out of date) +they /don't/ need to be all uppercase + HINT_NONE + HINT_PLAYER + HINT_ACTIVATE + HINT_DOOR + HINT_DOOR_ROTATING + HINT_DOOR_LOCKED + HINT_DOOR_ROTATING_LOCKED + HINT_MG42 + HINT_BREAKABLE + HINT_BREAKABLE_BIG + HINT_CHAIR + HINT_ALARM + HINT_HEALTH + HINT_TREASURE + HINT_KNIFE + HINT_LADDER + HINT_BUTTON + HINT_WATER + HINT_CAUTION + HINT_DANGER + HINT_SECRET + HINT_QUESTION + HINT_EXCLAMATION + HINT_CLIPBOARD + HINT_WEAPON + HINT_AMMO + HINT_ARMOR + HINT_POWERUP + HINT_HOLDABLE + HINT_INVENTORY + HINT_SCENARIC + HINT_EXIT + HINT_PLYR_FRIEND + HINT_PLYR_NEUTRAL + HINT_PLYR_ENEMY + HINT_PLYR_UNKNOWN +*/ + +void use_invisible_user( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *player; + + if ( ent->wait < level.time ) { + ent->wait = level.time + ent->delay; + } else { + return; + } + + if ( !( other->client ) ) { + if ( ent->spawnflags & 1 ) { + ent->spawnflags &= ~1; + } else + { + ent->spawnflags |= 1; + } + + if ( ent->spawnflags & 2 && !( ent->spawnflags & 1 ) ) { + if ( ent->aiName ) { + player = AICast_FindEntityForName( "player" ); + if ( player ) { + AICast_ScriptEvent( AICast_GetCastState( player->s.number ), "trigger", ent->target ); + } + } + + G_UseTargets( ent, other ); + + // G_Printf ("ent%s used by %s\n", ent->classname, other->classname); + } + + return; + } + + if ( other->client && ent->spawnflags & 1 ) { + //----(SA) play 'off' sound + //----(SA) I think this is where this goes. Raf, let me know if it's wrong. I need someone to tell me what a test map is for this (I'll ask Dan tomorrow) + // not usable by player. turned off. + G_Sound( ent, ent->soundPos1 ); + return; + } + + if ( ent->aiName ) { + player = AICast_FindEntityForName( "player" ); + if ( player ) { + AICast_ScriptEvent( AICast_GetCastState( player->s.number ), "trigger", ent->target ); + } + } + + G_UseTargets( ent, other ); //----(SA) how about this so the triggered targets have an 'activator' as well as an 'other'? + //----(SA) Please let me know if you forsee any problems with this. +} + + +void func_invisible_user( gentity_t *ent ) { + int i; + char *sound; + char *cursorhint; + + VectorCopy( ent->s.origin, ent->pos1 ); + trap_SetBrushModel( ent, ent->model ); + + // InitMover (ent); + VectorCopy( ent->pos1, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.pos.trBase ); + + ent->r.contents = CONTENTS_TRIGGER; + + ent->r.svFlags = SVF_NOCLIENT; + + ent->delay *= 1000; // convert to ms + + ent->use = use_invisible_user; + + +//----(SA) added + if ( G_SpawnString( "cursorhint", "0", &cursorhint ) ) { + + for ( i = 0; i < HINT_NUM_HINTS; i++ ) { + if ( !Q_strcasecmp( cursorhint, hintStrings[i] ) ) { + ent->s.dmgFlags = i; + } + } + } +//----(SA) end + + + if ( !( ent->spawnflags & 4 ) ) { // !NO_OFF_NOISE + if ( G_SpawnString( "offnoise", "0", &sound ) ) { + ent->soundPos1 = G_SoundIndex( sound ); + } else { + ent->soundPos1 = G_SoundIndex( "sound/movers/invis_user_off.wav" ); + } + } + + +} + +/* +========== +G_Activate + + Generic activation routine for doors +========== +*/ +void G_Activate( gentity_t *ent, gentity_t *activator ) { + if ( ( ent->s.apos.trType == TR_STATIONARY && ent->s.pos.trType == TR_STATIONARY ) + && ent->active == qfalse ) { + // trigger the ent if possible, if not, then we'll just wait at the marker until it opens, which could be never(!?) + if ( ent->key < 0 ) { // ent force locked + return; + } + + if ( ent->key > 0 ) { // ent requires key + gitem_t *item = BG_FindItemForKey( ent->key, 0 ); + if ( !( activator->client->ps.stats[STAT_KEYS] & ( 1 << item->giTag ) ) ) { + return; + } + } + + if ( !Q_stricmp( ent->classname, "script_mover" ) ) { // RF, dont activate script_mover's + if ( activator->aiName ) { + G_Script_ScriptEvent( ent, "activate", activator->aiName ); + } + return; + } + + // hack fix for bigdoor1 on tram1_21 + + if ( !( ent->teammaster ) ) { + ent->active = qtrue; + Use_BinaryMover( ent, activator, activator ); + G_UseTargets( ent->teammaster, activator ); + return; + } + + if ( ent->team && ent != ent->teammaster ) { + ent->teammaster->active = qtrue; + Use_BinaryMover( ent->teammaster, activator, activator ); + G_UseTargets( ent->teammaster, activator ); + } else + { + ent->active = qtrue; + Use_BinaryMover( ent, activator, activator ); + G_UseTargets( ent->teammaster, activator ); + } + } +} diff --git a/src/game/g_props.c b/src/game/g_props.c new file mode 100644 index 0000000..88ff2ee --- /dev/null +++ b/src/game/g_props.c @@ -0,0 +1,4203 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + +#define GENERIC_DAMAGE 6 + +int snd_boardbreak; +int snd_glassbreak; +int snd_metalbreak; +int snd_ceramicbreak; +int snd_chaircreak; +int snd_chairthrow; +int snd_chairhitground; + +// JOSEPH 1-28-00 +void DropToFloorG( gentity_t *ent ) { + vec3_t dest; + trace_t tr; + + VectorSet( dest, ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2] - 4096 ); + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); + + if ( tr.startsolid ) { + return; + } + + ent->s.groundEntityNum = tr.entityNum; + + G_SetOrigin( ent, tr.endpos ); + + ent->nextthink = level.time + FRAMETIME; +} + +void DropToFloor( gentity_t *ent ) { + vec3_t dest; + trace_t tr; + + VectorSet( dest, ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2] - 4096 ); + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID ); + + if ( tr.startsolid ) { + return; + } + + if ( fabs( ent->r.currentOrigin[2] - tr.endpos[2] ) > 1.0 ) { + tr.endpos[2] = ( ent->r.currentOrigin[2] - 1.0 ); + } + + ent->s.groundEntityNum = tr.entityNum; + + G_SetOrigin( ent, tr.endpos ); + + ent->think = DropToFloorG; + ent->nextthink = level.time + FRAMETIME; +} + +void moveit( gentity_t *ent, float yaw, float dist ) { + vec3_t move; + vec3_t origin; + trace_t tr; + vec3_t mins, maxs; + + yaw = yaw * M_PI * 2 / 360; + + move[0] = cos( yaw ) * dist; + move[1] = sin( yaw ) * dist; + move[2] = 0; + + VectorAdd( ent->r.currentOrigin, move, origin ); + + mins[0] = ent->r.mins[0]; + mins[1] = ent->r.mins[1]; + mins[2] = ent->r.mins[2] + .01; + + maxs[0] = ent->r.maxs[0]; + maxs[1] = ent->r.maxs[1]; + maxs[2] = ent->r.maxs[2] - .01; + + trap_Trace( &tr, ent->r.currentOrigin, mins, maxs, origin, ent->s.number, MASK_SHOT ); + + if ( ( tr.endpos[0] != origin[0] ) || ( tr.endpos[1] != origin[1] ) ) { + mins[0] = ent->r.mins[0] - 2.0; + mins[1] = ent->r.mins[1] - 2.0; + maxs[0] = ent->r.maxs[0] + 2.0; + maxs[1] = ent->r.maxs[1] + 2.0; + + trap_Trace( &tr, ent->r.currentOrigin, mins, maxs, origin, ent->s.number, MASK_SHOT ); + } + + VectorCopy( tr.endpos, ent->r.currentOrigin ); + + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + + trap_LinkEntity( ent ); + + //DropToFloor( ent ); +} + +void touch_props_box_32( gentity_t *self, gentity_t *other, trace_t *trace ) { + float ratio; + vec3_t v; + + if ( other->r.currentOrigin[2] > ( self->r.currentOrigin[2] + 10 + 15 ) ) { + return; + } + + ratio = 2.5; + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, v ); + moveit( self, vectoyaw( v ), ( 20 * ratio * FRAMETIME ) * .001 ); +} + +/*QUAKED props_box_32 (1 0 0) (-16 -16 -16) (16 16 16) + +*/ +void SP_props_box_32( gentity_t *self ) { + self->s.modelindex = G_ModelIndex( "models/mapobjects/boxes/box32.md3" ); + + self->clipmask = CONTENTS_SOLID; + self->r.contents = CONTENTS_SOLID; + self->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + VectorSet( self->r.mins, -16, -16, -16 ); + VectorSet( self->r.maxs, 16, 16, 16 ); + + self->touch = touch_props_box_32; + + trap_LinkEntity( self ); + + self->think = DropToFloor; + self->nextthink = level.time + FRAMETIME; +} + +void touch_props_box_48( gentity_t *self, gentity_t *other, trace_t *trace ) { + float ratio; + vec3_t v; + + if ( other->r.currentOrigin[2] > ( self->r.currentOrigin[2] + 10 + 23 ) ) { + return; + } + + ratio = 2.0; + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, v ); + moveit( self, vectoyaw( v ), ( 20 * ratio * FRAMETIME ) * .001 ); +} + +/*QUAKED props_box_48 (1 0 0) (-24 -24 -24) (24 24 24) + +*/ +void SP_props_box_48( gentity_t *self ) { + self->s.modelindex = G_ModelIndex( "models/mapobjects/boxes/box48.md3" ); + + self->clipmask = CONTENTS_SOLID; + self->r.contents = CONTENTS_SOLID; + self->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + VectorSet( self->r.mins, -24, -24, -24 ); + VectorSet( self->r.maxs, 24, 24, 24 ); + + self->touch = touch_props_box_48; + + trap_LinkEntity( self ); + + self->think = DropToFloor; + self->nextthink = level.time + FRAMETIME; +} + +void touch_props_box_64( gentity_t *self, gentity_t *other, trace_t *trace ) { + float ratio; + vec3_t v; + + if ( other->r.currentOrigin[2] > ( self->r.currentOrigin[2] + 10 + 31 ) ) { + return; + } + + ratio = 1.5; + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, v ); + moveit( self, vectoyaw( v ), ( 20 * ratio * FRAMETIME ) * .001 ); +} + +/*QUAKED props_box_64 (1 0 0) (-32 -32 -32) (32 32 32) + +*/ +void SP_props_box_64( gentity_t *self ) { + self->s.modelindex = G_ModelIndex( "models/mapobjects/boxes/box64.md3" ); + + self->clipmask = CONTENTS_SOLID; + self->r.contents = CONTENTS_SOLID; + self->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + VectorSet( self->r.mins, -32, -32, -32 ); + VectorSet( self->r.maxs, 32, 32, 32 ); + + self->touch = touch_props_box_64; + + trap_LinkEntity( self ); + + self->think = DropToFloor; + self->nextthink = level.time + FRAMETIME; +} +// END JOSEPH + +// Rafael + +void Psmoke_think( gentity_t *ent ) { + gentity_t *tent; + + ent->count++; + + if ( ent->count == 30 ) { + ent->think = G_FreeEntity; + } + + tent = G_TempEntity( ent->s.origin, EV_SMOKE ); + VectorCopy( ent->s.origin, tent->s.origin ); + tent->s.time = 3000; + tent->s.time2 = 100; + tent->s.density = 0; + tent->s.angles2[0] = 4; + tent->s.angles2[1] = 32; + tent->s.angles2[2] = 50; + + ent->nextthink = level.time + FRAMETIME; +} + +void prop_smoke( gentity_t *ent ) { + gentity_t *Psmoke; + + Psmoke = G_Spawn(); + VectorCopy( ent->r.currentOrigin, Psmoke->s.origin ); + Psmoke->think = Psmoke_think; + Psmoke->nextthink = level.time + FRAMETIME; +} + +/*QUAKED props_sparks (.8 .46 .16) (-8 -8 -8) (8 8 8) ELECTRIC +the default direction is strait up use info_no_null for alt direction + +delay = how long till next spark effect +wait = life of the spark with some random variance default 1.0 sec +health = random number of sparks upto specified amount default 8 + +start_size default 8 along the x +end_size default 8 along the y +by changing the size will change the spawn origin of the individual spark +ei 16 x 8 or 24 x 32 would cause the sparks to spawn that many units from +the origin + +speed controls how quickly the sparks will travel default is 2 +*/ + +void PGUNsparks_use( gentity_t *ent, gentity_t *self, gentity_t *activator ) { + gentity_t *tent; + + tent = G_TempEntity( ent->r.currentOrigin, EV_GUNSPARKS ); + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + VectorCopy( ent->r.currentAngles, tent->s.angles ); + tent->s.density = ent->health; + tent->s.angles2[2] = ent->speed; + +} + +void Psparks_think( gentity_t *ent ) { + gentity_t *tent; + +//(SA) MOVE TO CLIENT! + return; + + + if ( ent->spawnflags & 1 ) { + tent = G_TempEntity( ent->r.currentOrigin, EV_SPARKS_ELECTRIC ); + } else { + tent = G_TempEntity( ent->r.currentOrigin, EV_SPARKS ); + } + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + VectorCopy( ent->r.currentAngles, tent->s.angles ); + tent->s.density = ent->health; + tent->s.frame = ent->wait; + tent->s.angles2[0] = ent->start_size; + tent->s.angles2[1] = ent->end_size; + tent->s.angles2[2] = ent->speed; + + ent->nextthink = level.time + FRAMETIME + ent->delay + ( rand() % 600 ); +} + +void sparks_angles_think( gentity_t *ent ) { + + gentity_t *target = NULL; + vec3_t vec; + + if ( ent->target ) { + target = G_Find( NULL, FOFS( targetname ), ent->target ); + } + + if ( !target ) { + VectorSet( ent->r.currentAngles, 0, 0, 1 ); + } else + { + VectorSubtract( ent->s.origin, target->s.origin, vec ); + VectorNormalize( vec ); + VectorCopy( vec, ent->r.currentAngles ); + } + + trap_LinkEntity( ent ); + + ent->nextthink = level.time + FRAMETIME; + if ( !Q_stricmp( ent->classname, "props_sparks" ) ) { + ent->think = Psparks_think; + } else { + ent->use = PGUNsparks_use; + } + +} + +void SP_props_sparks( gentity_t *ent ) { + // (SA) don't use in multiplayer right now since it makes decyphering net messages almost impossible + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + ent->think = G_FreeEntity; + return; + } + + G_SetOrigin( ent, ent->s.origin ); + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + + ent->think = sparks_angles_think; + ent->nextthink = level.time + FRAMETIME; + + if ( !ent->health ) { + ent->health = 8; + } + + if ( !ent->wait ) { + ent->wait = 1200; + } else { + ent->wait *= 1000; + } + + if ( !ent->start_size ) { + ent->start_size = 8; + } + + if ( !ent->end_size ) { + ent->end_size = 8; + } + + if ( !ent->speed ) { + ent->speed = 2; + } + + trap_LinkEntity( ent ); + +} + +/*QUAKED props_gunsparks (.8 .46 .16) (-8 -8 -8) (8 8 8) +the default direction is strait up use info_no_null for alt direction + +this entity must be used to see the effect + +"speed" default is 20 +"health" number to spawn default is 4 +*/ + +void SP_props_gunsparks( gentity_t *ent ) { + G_SetOrigin( ent, ent->s.origin ); + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + + ent->think = sparks_angles_think; + ent->nextthink = level.time + FRAMETIME; + + if ( !ent->speed ) { + ent->speed = 20; + } + + if ( !ent->health ) { + ent->health = 4; + } + + trap_LinkEntity( ent ); + +} + +/*QUAKED props_smokedust (.8 .46 .16) (-8 -8 -8) (8 8 8) +health = how many pieces 16 is default +*/ + +void smokedust_use( gentity_t *ent, gentity_t *self, gentity_t *activator ) { + int i; + gentity_t *tent; + vec3_t forward; + + AngleVectors( ent->r.currentAngles, forward, NULL, NULL ); + + for ( i = 0; i < ent->health; i++ ) + { + tent = G_TempEntity( ent->r.currentOrigin, EV_SMOKE ); + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + VectorCopy( forward, tent->s.origin2 ); + tent->s.time = 1000; + tent->s.time2 = 750; + tent->s.density = 3; + } +} + +void SP_SmokeDust( gentity_t *ent ) { + + ent->use = smokedust_use; + + G_SetOrigin( ent, ent->s.origin ); + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + + if ( !ent->health ) { + ent->health = 16; + } + trap_LinkEntity( ent ); +} + + +/*QUAKED props_dust (.7 .3 .16) (-8 -8 -8) (8 8 8) WHITE +you should give this ent a target use a not null +or you could set its angles in the editor +*/ + +void dust_use( gentity_t *ent, gentity_t *self, gentity_t *activator ) { + gentity_t *tent; + vec3_t forward; + + if ( ent->target ) { + tent = G_TempEntity( ent->r.currentOrigin, EV_DUST ); + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + VectorCopy( ent->r.currentAngles, tent->s.angles ); + if ( ent->spawnflags & 1 ) { + tent->s.density = 1; + } + } else + { + + AngleVectors( ent->r.currentAngles, forward, NULL, NULL ); + + tent = G_TempEntity( ent->r.currentOrigin, EV_DUST ); + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + VectorCopy( forward, tent->s.angles ); + if ( ent->spawnflags & 1 ) { + tent->s.density = 1; + } + } +} + +void dust_angles_think( gentity_t *ent ) { + gentity_t *target; + vec3_t vec; + + target = G_Find( NULL, FOFS( targetname ), ent->target ); + + if ( !target ) { + return; + } + + VectorSubtract( ent->s.origin, target->s.origin, vec ); + VectorCopy( vec, ent->r.currentAngles ); + trap_LinkEntity( ent ); + +} + +void SP_Dust( gentity_t *ent ) { + ent->use = dust_use; + G_SetOrigin( ent, ent->s.origin ); + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + + if ( ent->target ) { + ent->think = dust_angles_think; + ent->nextthink = level.time + FRAMETIME; + } + + trap_LinkEntity( ent ); +} + +////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////// + +extern void G_ExplodeMissile( gentity_t *ent ); + +void propExplosionLarge( gentity_t *ent ) { + gentity_t *bolt; + + bolt = G_Spawn(); + bolt->classname = "props_explosion_large"; + bolt->nextthink = level.time + FRAMETIME; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + bolt->s.weapon = WP_NONE; + + bolt->s.eFlags = EF_BOUNCE_HALF; + bolt->r.ownerNum = ent->s.number; + bolt->parent = ent; + bolt->damage = ent->health; + bolt->splashDamage = ent->health; + bolt->splashRadius = ent->health * 1.5; + bolt->methodOfDeath = MOD_GRENADE; + bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH; + bolt->clipmask = MASK_SHOT; + + VectorCopy( ent->r.currentOrigin, bolt->s.pos.trBase ); + VectorCopy( ent->r.currentOrigin, bolt->r.currentOrigin ); +} + +void propExplosion( gentity_t *ent ) { + gentity_t *bolt; + + extern void G_ExplodeMissile( gentity_t * ent ); + bolt = G_Spawn(); + bolt->classname = "props_explosion"; + bolt->nextthink = level.time + FRAMETIME; + bolt->think = G_ExplodeMissile; + bolt->s.eType = ET_MISSILE; + bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + bolt->s.weapon = WP_NONE; + + bolt->s.eFlags = EF_BOUNCE_HALF; + bolt->r.ownerNum = ent->s.number; + bolt->parent = ent; + bolt->damage = ent->health; + bolt->splashDamage = ent->health; + bolt->splashRadius = ent->health * 1.5; + bolt->methodOfDeath = MOD_GRENADE; + bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH; + bolt->clipmask = MASK_SHOT; + + VectorCopy( ent->r.currentOrigin, bolt->s.pos.trBase ); + VectorCopy( ent->r.currentOrigin, bolt->r.currentOrigin ); +} + +void InitProp( gentity_t *ent ) { + float light; + vec3_t color; + qboolean lightSet, colorSet; + char *sound; + + if ( !Q_stricmp( ent->classname, "props_bench" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/furniture/bench/bench_sm.md3" ); + } else if ( !Q_stricmp( ent->classname, "props_radio" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/mapobjects/electronics/radio1.md3" ); + } else if ( !Q_stricmp( ent->classname, "props_locker_tall" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/furniture/storage/lockertall.md3" ); + } else if ( !Q_stricmp( ent->classname, "props_flippy_table" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/furniture/table/woodflip.md3" ); + } else if ( !Q_stricmp( ent->classname, "props_crate_32x64" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/furniture/crate/crate32x64.md3" ); + } else if ( !Q_stricmp( ent->classname, "props_58x112tablew" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/furniture/table/56x112tablew.md3" ); + } else if ( !Q_stricmp( ent->classname, "props_castlebed" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/furniture/bed/castlebed.md3" ); + } else if ( !Q_stricmp( ent->classname, "props_radioSEVEN" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/mapobjects/electronics/radios.md3" ); + } + + // if the "loopsound" key is set, use a constant looping sound when moving + if ( G_SpawnString( "noise", "100", &sound ) ) { + ent->s.loopSound = G_SoundIndex( sound ); + } + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + if ( lightSet || colorSet ) { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + ent->isProp = qtrue; + + ent->moverState = MOVER_POS1; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); +} + +void props_bench_think( gentity_t *ent ) { + ent->s.frame++; + + if ( ent->s.frame < 28 ) { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } else + { + ent->clipmask = 0; + ent->r.contents = 0; + ent->takedamage = qfalse; + + G_UseTargets( ent, NULL ); + } + +} + +void props_bench_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + ent->think = props_bench_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED props_bench (.8 .6 .2) ? +requires an origin brush +health = 10 by default +*/ +void SP_Props_Bench( gentity_t *ent ) { + + trap_SetBrushModel( ent, ent->model ); + + InitProp( ent ); + + if ( !ent->health ) { + ent->health = 10; + } + + ent->takedamage = qtrue; + + ent->clipmask = CONTENTS_SOLID; + + ent->die = props_bench_die; + + trap_LinkEntity( ent ); +} + +void props_radio_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + + propExplosion( ent ); + + ent->takedamage = qfalse; + + G_UseTargets( ent, NULL ); + + G_FreeEntity( ent ); +} + +/*QUAKED props_radio (.8 .6 .2) ? +requires an origin brush +health = defaults to 100 +*/ +void SP_Props_Radio( gentity_t *ent ) { + + // Ridah, had to add this so I could load castle18dk7 + if ( !ent->model ) { + G_Printf( S_COLOR_RED "props_radio with NULL model\n" ); + return; + } + + trap_SetBrushModel( ent, ent->model ); + + InitProp( ent ); + + if ( !ent->health ) { + ent->health = 100; + } + + ent->takedamage = qtrue; + + ent->die = props_radio_die; + + trap_LinkEntity( ent ); + +} + + +void props_radio_dieSEVEN( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + + int i; + + propExplosion( ent ); + + for ( i = 0; i < 20; i++ ) + Spawn_Shard( ent, inflictor, 1, ent->count ); + + Prop_Break_Sound( ent ); + + ent->takedamage = qfalse; + ent->die = NULL; + + trap_LinkEntity( ent ); + + G_UseTargets( ent, NULL ); + + G_FreeEntity( ent ); +} + +/*QUAKED props_radioSEVEN (.8 .6 .2) ? +requires an origin brush +health = defaults to 100 + + + the models dims are + x 32 + y 136 + z 32 + + if you want more explosions you'll need func explosive + + it will fire all its targets upon death +*/ +void SP_Props_RadioSEVEN( gentity_t *ent ) { + + if ( !ent->model ) { + G_Printf( S_COLOR_RED "props_radio with NULL model\n" ); + return; + } + + trap_SetBrushModel( ent, ent->model ); + + InitProp( ent ); + + if ( !ent->health ) { + ent->health = 100; + } + + ent->takedamage = qtrue; + + ent->die = props_radio_dieSEVEN; + + ent->count = 2; // metal shard and sound + + trap_LinkEntity( ent ); + +} + + +void locker_tall_think( gentity_t *ent ) { + if ( ent->s.frame == 30 ) { + G_UseTargets( ent, NULL ); + + } else + { + ent->s.frame++; + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } + +} + +void props_locker_tall_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + ent->think = locker_tall_think; + ent->nextthink = level.time + FRAMETIME; + + ent->takedamage = qfalse; + + G_UseTargets( ent, NULL ); +} + +/*QUAKED props_locker_tall (.8 .6 .2) ? +requires an origin brush +*/ +void SP_Props_Locker_Tall( gentity_t *ent ) { + + // Ridah, had to add this so I could load castle18dk7 + if ( !ent->model ) { + G_Printf( S_COLOR_RED "props_locker_tall with NULL model\n" ); + return; + } + + trap_SetBrushModel( ent, ent->model ); + + InitProp( ent ); + + if ( !ent->health ) { + ent->health = 100; + } + + ent->takedamage = qtrue; + + ent->die = props_locker_tall_die; + + trap_LinkEntity( ent ); + +} + +/*QUAKED props_chair_chat(.8 .6 .2) (-16 -16 0) (16 16 32) +point entity +health = default = 10 +wait = defaults to 5 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + +/*QUAKED props_chair_chatarm(.8 .6 .2) (-16 -16 0) (16 16 32) +point entity +health = default = 10 +wait = defaults to 5 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + +/*QUAKED props_chair_side (.8 .6 .2) (-16 -16 0) (16 16 32) +point entity +health = default = 10 +wait = defaults to 5 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + + +/*QUAKED props_chair_hiback (.8 .6 .2) (-16 -16 0) (16 16 32) +point entity +health = default = 10 +wait = defaults to 5 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + +/*QUAKED props_chair (.8 .6 .2) (-16 -16 0) (16 16 32) +point entity +health = default = 10 +wait = defaults to 5 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ +void Props_Chair_Think( gentity_t *self ); +void Props_Chair_Touch( gentity_t *self, gentity_t *other, trace_t *trace ); +void Props_Chair_Die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); + +void Just_Got_Thrown( gentity_t *self ) { + float len; + vec3_t vec; + qboolean prop_hits = qfalse; + + len = 0; + + if ( self->s.groundEntityNum == -1 ) { + self->nextthink = level.time + FRAMETIME; + + if ( self->enemy ) { + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( player && player != self->enemy ) { + prop_hits = qtrue; + G_Damage( self->enemy, self, self, NULL, NULL, 5, 0, MOD_CRUSH ); + + self->die = Props_Chair_Die; + + self->die( self, self, NULL, 10, 0 ); + } + } + + return; + } else + { + // RF, alert AI of sound event + AICast_AudibleEvent( self->s.number, self->r.currentOrigin, 384 ); + + G_AddEvent( self, EV_GENERAL_SOUND, snd_chairhitground ); + VectorSubtract( self->r.currentOrigin, self->s.origin2, vec ); + len = VectorLength( vec ); + + { + trace_t trace; + vec3_t end; + gentity_t *traceEnt; + gentity_t *player; + + VectorCopy( self->r.currentOrigin, end ); + end[2] += 1; + + trap_Trace( &trace, self->r.currentOrigin, self->r.mins, self->r.maxs, end, self->s.number, MASK_SHOT ); + + traceEnt = &g_entities[ trace.entityNum ]; + + if ( trace.startsolid ) { + player = AICast_FindEntityForName( "player" ); + + if ( traceEnt == player && traceEnt->health >= 0 ) { + // pick the chair back up + self->active = qtrue; + self->r.ownerNum = player->s.number; + player->active = qtrue; + player->melee = self; + self->nextthink = level.time + 50; + + self->think = Props_Chair_Think; + self->touch = NULL; + self->die = Props_Chair_Die; + self->s.eType = ET_MOVER; + self->s.dmgFlags = HINT_CHAIR; // so client knows what kind of mover it is for cursorhints + + player->client->ps.eFlags |= EF_MELEE_ACTIVE; + + trap_LinkEntity( self ); + + return; + } else { + len = 9999; + } + } + } + + } + + self->think = Props_Chair_Think; + self->touch = Props_Chair_Touch; + self->die = Props_Chair_Die; + self->s.eType = ET_MOVER; + self->s.dmgFlags = HINT_CHAIR; // so client knows what kind of mover it is for cursorhints + + self->nextthink = level.time + FRAMETIME; + + self->r.ownerNum = self->s.number; + + if ( len > 256 ) { + self->die( self, self, NULL, 10, 0 ); + } + +} + +void Props_TurnLightsOff( gentity_t *self ) { + if ( !Q_stricmp( self->classname, "props_desklamp" ) ) { + if ( self->target ) { + G_UseTargets( self, NULL ); + self->target = NULL; + } + } +} + +void Props_Activated( gentity_t *self ) { + vec3_t angles; + vec3_t dest; + vec3_t forward, right; + vec3_t velocity; + vec3_t prop_ang; + + gentity_t *prop; + + gentity_t *owner; + + owner = &g_entities[self->r.ownerNum]; + + self->nextthink = level.time + 50; + + if ( !owner->client ) { + return; + } + + Props_TurnLightsOff( self ); + + if ( owner->active == qfalse ) { + + owner->melee = NULL; + + self->physicsObject = qtrue; + self->physicsBounce = 0.2; + + self->s.groundEntityNum = -1; + + self->s.pos.trType = TR_GRAVITY; + self->s.pos.trTime = level.time; + + self->active = qfalse; + + G_AddEvent( owner, EV_GENERAL_SOUND, snd_chairthrow ); + + AngleVectors( owner->client->ps.viewangles, velocity, NULL, NULL ); + VectorScale( velocity, 250, velocity ); + velocity[2] += 100 + crandom() * 25; + VectorCopy( velocity, self->s.pos.trDelta ); + + self->think = NULL; + self->nextthink = 0; + + prop = G_Spawn(); + prop->s.modelindex = self->s.modelindex; + G_SetOrigin( prop, self->r.currentOrigin ); + + VectorCopy( owner->client->ps.viewangles, prop_ang ); + prop_ang[0] = 0; + + G_SetAngle( prop, prop_ang ); + + prop->clipmask = CONTENTS_SOLID; + prop->r.contents = CONTENTS_SOLID; + prop->r.svFlags = SVF_USE_CURRENT_ORIGIN; + prop->isProp = qtrue; + + VectorSet( prop->r.mins, -12, -12, 0 ); + VectorSet( prop->r.maxs, 12, 12, 48 ); + + prop->physicsObject = qtrue; + prop->physicsBounce = 0.2; + + VectorCopy( owner->client->ps.origin, prop->s.pos.trBase ); + + VectorCopy( self->s.pos.trDelta, prop->s.pos.trDelta ); + + prop->s.pos.trType = TR_GRAVITY; + prop->s.pos.trTime = level.time; + + prop->active = qfalse; + + prop->health = self->health; + + prop->duration = self->health; + + prop->count = self->count; + + prop->think = Just_Got_Thrown; + prop->nextthink = level.time + FRAMETIME; + + prop->takedamage = qtrue; + + prop->wait = self->wait; + + prop->classname = self->classname; + + prop->s.groundEntityNum = -1; + + VectorCopy( self->r.currentOrigin, prop->s.origin2 ); + + prop->die = Props_Chair_Die; + + prop->r.ownerNum = owner->s.number; + + trap_LinkEntity( prop ); + + G_FreeEntity( self ); + + return; + } else + { + if ( !Q_stricmp( self->classname, "props_chair_hiback" ) ) { + self->s.frame = 23; + self->s.density = 1; + } else if ( !Q_stricmp( self->classname, "props_chair" ) ) { + self->s.frame = 28; + self->s.density = 1; + } else if ( !Q_stricmp( self->classname, "props_chair_side" ) ) { + self->s.frame = 23; + self->s.density = 1; + } + } + + trap_UnlinkEntity( self ); + + // move the entity in step with the activators movement + VectorCopy( owner->client->ps.viewangles, angles ); + angles[0] = 0; + + self->s.apos.trBase[YAW] = owner->client->ps.viewangles[YAW]; + + AngleVectors( angles, forward, right, NULL ); + VectorCopy( owner->r.currentOrigin, dest ); + + VectorCopy( dest, self->r.currentOrigin ); + VectorCopy( dest, self->s.pos.trBase ); + + self->s.eType = ET_PROP; + + trap_LinkEntity( self ); + +} + +void Prop_Check_Ground( gentity_t *self ); + +void Props_Chair_Think( gentity_t *self ) { + trace_t tr; + + if ( self->active ) { + Props_Activated( self ); + return; + } + + trap_UnlinkEntity( self ); + + BG_EvaluateTrajectory( &self->s.pos, level.time, self->s.pos.trBase ); + + if ( level.time > self->s.pos.trDuration ) { + VectorClear( self->s.pos.trDelta ); + self->s.pos.trDuration = 0; + self->s.pos.trType = TR_STATIONARY; + } else + { + vec3_t mins, maxs; + + VectorCopy( self->r.mins, mins ); + VectorCopy( self->r.maxs, maxs ); + + mins[2] += 1; + + trap_Trace( &tr, self->r.currentOrigin, mins, maxs, self->s.pos.trBase, self->s.number, MASK_SHOT ); + + if ( tr.fraction == 1 ) { + VectorCopy( self->s.pos.trBase, self->r.currentOrigin ); + } else + { + VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); + VectorClear( self->s.pos.trDelta ); + self->s.pos.trDuration = 0; + self->s.pos.trType = TR_STATIONARY; + } + + } + + if ( self->s.groundEntityNum == -1 ) { + + self->physicsObject = qtrue; + self->physicsBounce = 0.2; + + self->s.pos.trDelta[2] -= 200; + + self->s.pos.trType = TR_GRAVITY; + self->s.pos.trTime = level.time; + + self->active = qfalse; + + self->think = Just_Got_Thrown; + + if ( self->s.pos.trType != TR_GRAVITY ) { + self->s.pos.trType = TR_GRAVITY; + self->s.pos.trTime = level.time; + } + } + + + Prop_Check_Ground( self ); + + + self->nextthink = level.time + 50; + trap_LinkEntity( self ); +} + +qboolean Prop_Touch( gentity_t *self, gentity_t *other, vec3_t v ) { + + vec3_t forward; + vec3_t dest; + vec3_t angle; + vec3_t start, end; + vec3_t mins, maxs; + trace_t tr; + + if ( !other->client ) { + return qfalse; + } + + vectoangles( v, angle ); + angle[0] = 0; + AngleVectors( angle, forward, NULL, NULL ); + VectorClear( dest ); + VectorMA( dest, 128, forward, dest ); + VectorMA( self->r.currentOrigin, 32, forward, end ); + + VectorCopy( self->r.currentOrigin, start ); + end[2] += 8; + start[2] += 8; + + VectorCopy( self->r.mins, mins ); + VectorCopy( self->r.maxs, maxs ); + + mins[2] += 1; + + trap_Trace( &tr, start, mins, maxs, end, self->s.number, MASK_SHOT ); + + if ( tr.fraction != 1 ) { + return qfalse; + } + + VectorCopy( dest, self->s.pos.trDelta ); + VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); + + self->s.pos.trDuration = level.time + 100; + self->s.pos.trTime = level.time; + self->s.pos.trType = TR_LINEAR; + + self->physicsObject = qtrue; + + return qtrue; +} + +void Prop_Check_Ground( gentity_t *self ) { + vec3_t mins, maxs; + vec3_t start, end; + trace_t tr; + + VectorCopy( self->r.currentOrigin, start ); + VectorCopy( self->r.currentOrigin, end ); + + end[2] -= 4; + + VectorCopy( self->r.mins, mins ); + VectorCopy( self->r.maxs, maxs ); + + trap_Trace( &tr, start, mins, maxs, end, self->s.number, MASK_SHOT ); + + if ( tr.fraction == 1 ) { + self->s.groundEntityNum = -1; + } + +} + +void Props_Chair_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + vec3_t v; + qboolean has_moved; + + if ( !other->client ) { + return; + } + + if ( other->r.currentOrigin[2] > ( self->r.currentOrigin[2] + 10 + 15 ) ) { + return; + } + + if ( self->active ) { // someone has activated me + return; + } + + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, v ); + + has_moved = Prop_Touch( self, other, v ); + + if ( !has_moved && ( other->r.svFlags & SVF_CASTAI ) ) { + // RF, alert AI of sound event + AICast_AudibleEvent( self->s.number, self->r.currentOrigin, 384 ); + + // other could play kick animation here + Props_Chair_Die( self, other, other, 100, 0 ); + return; + } + + Prop_Check_Ground( self ); + + if ( level.time > self->random && has_moved ) { + // RF, alert AI of sound event + AICast_AudibleEvent( self->s.number, self->r.currentOrigin, 384 ); + + G_AddEvent( self, EV_GENERAL_SOUND, snd_chaircreak ); + self->random = level.time + 1000 + ( rand() % 200 ); + } + + if ( !Q_stricmp( self->classname, "props_desklamp" ) ) { + // player may have picked it up before + if ( self->target ) { + G_UseTargets( self, NULL ); + self->target = NULL; + } + } + +} + +void Props_Chair_Animate( gentity_t *ent ) { + + ent->touch = NULL; + + if ( !Q_stricmp( ent->classname, "props_chair" ) ) { + if ( ent->s.frame >= 27 ) { + ent->s.frame = 27; + G_UseTargets( ent, NULL ); + ent->think = G_FreeEntity; + ent->nextthink = level.time + 2000; + ent->s.time = level.time; + ent->s.time2 = level.time + 2000; + return; + } else + { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } + } else if ( + ( !Q_stricmp( ent->classname, "props_chair_side" ) ) || + ( !Q_stricmp( ent->classname, "props_chair_chat" ) ) || + ( !Q_stricmp( ent->classname, "props_chair_chatarm" ) ) || + ( !Q_stricmp( ent->classname, "props_chair_hiback" ) ) + ) { + if ( ent->s.frame >= 20 ) { + ent->s.frame = 20; + G_UseTargets( ent, NULL ); + ent->think = G_FreeEntity; + ent->nextthink = level.time + 2000; + ent->s.time = level.time; + ent->s.time2 = level.time + 2000; + return; + } else + { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } + } else if ( !Q_stricmp( ent->classname, "props_desklamp" ) ) { + if ( ent->s.frame >= 11 ) { + // player may have picked it up before + if ( ent->target ) { + G_UseTargets( ent, NULL ); + } + + ent->think = G_FreeEntity; + ent->nextthink = level.time + 2000; + ent->s.time = level.time; + ent->s.time2 = level.time + 2000; + return; + } else + { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } + } + + + ent->s.frame++; + + if ( ent->enemy ) { + float ratio; + vec3_t v; + + ratio = 2.5; + VectorSubtract( ent->r.currentOrigin, ent->enemy->r.currentOrigin, v ); + moveit( ent, vectoyaw( v ), ( ent->delay * ratio * FRAMETIME ) * .001 ); + } + +} + +void Spawn_Shard( gentity_t *ent, gentity_t *inflictor, int quantity, int type ) { + gentity_t *sfx; + vec3_t dir, start; + + VectorCopy( ent->r.currentOrigin, start ); + + if ( !Q_stricmp( ent->classname, "props_radioSEVEN" ) ) { + start[0] += crandom() * 32; + start[1] += crandom() * 32; + VectorSubtract( inflictor->r.currentOrigin, ent->r.currentOrigin, dir ); + VectorNormalize( dir ); + } else if ( inflictor ) { + VectorSubtract( inflictor->r.currentOrigin, ent->r.currentOrigin, dir ); + VectorNormalize( dir ); + VectorNegate( dir, dir ); + } else { + VectorSet( dir, 0,0,1 ); + } + + sfx = G_Spawn(); + + sfx->s.density = type; + + if ( type < 4 ) { + start[2] += 32; + } + + G_SetOrigin( sfx, start ); + G_SetAngle( sfx, ent->r.currentAngles ); + + G_AddEvent( sfx, EV_SHARD, DirToByte( dir ) ); + + sfx->think = G_FreeEntity; + + sfx->nextthink = level.time + 1000; + + sfx->s.frame = quantity; + + trap_LinkEntity( sfx ); +} + +void Prop_Break_Sound( gentity_t *ent ) { + switch ( ent->count ) + { + case shard_wood: + G_AddEvent( ent, EV_GENERAL_SOUND, snd_boardbreak ); + break; + case shard_glass: + G_AddEvent( ent, EV_GENERAL_SOUND, snd_glassbreak ); + break; + case shard_metal: + G_AddEvent( ent, EV_GENERAL_SOUND, snd_metalbreak ); + break; + case shard_ceramic: + G_AddEvent( ent, EV_GENERAL_SOUND, snd_ceramicbreak ); + break; + } +} + + +void Props_Chair_Die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + int quantity; + int type; + + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( player && player->melee == ent ) { + player->melee = NULL; + player->active = qfalse; + player->client->ps.eFlags &= ~EF_MELEE_ACTIVE; + + } else if ( player && player->s.number == ent->r.ownerNum ) { + player->active = qfalse; + player->melee = NULL; + player->client->ps.eFlags &= ~EF_MELEE_ACTIVE; + } + } + + ent->think = Props_Chair_Animate; + ent->nextthink = level.time + FRAMETIME; + + ent->health = ent->duration; + ent->delay = damage; + ent->takedamage = qfalse; +// ent->enemy = inflictor; + + quantity = ent->wait; + type = ent->count; + + Spawn_Shard( ent, inflictor, quantity, type ); + + Prop_Break_Sound( ent ); + + trap_UnlinkEntity( ent ); + + ent->clipmask = 0; + ent->r.contents = 0; + ent->s.eType = ET_GENERAL; + + trap_LinkEntity( ent ); + +} + +void Props_Chair_Skyboxtouch( gentity_t *ent ) { + + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( player && player->melee == ent ) { + player->melee = NULL; + player->active = qfalse; + player->client->ps.eFlags &= ~EF_MELEE_ACTIVE; + } else if ( player && player->s.number == ent->r.ownerNum ) { + player->active = qfalse; + player->melee = NULL; + player->client->ps.eFlags &= ~EF_MELEE_ACTIVE; + } + + ent->think = G_FreeEntity; + +} + +void SP_Props_Chair( gentity_t *ent ) { + int mass; + + ent->s.modelindex = G_ModelIndex( "models/furniture/chair/chair_office3.md3" ); + + ent->delay = 0; // inherits damage value + + if ( G_SpawnInt( "mass", "5", &mass ) ) { + ent->wait = mass; + } else { + ent->wait = 5; + } + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + ent->s.dmgFlags = HINT_CHAIR; // so client knows what kind of mover it is for cursorhints + + ent->isProp = qtrue; + + VectorSet( ent->r.mins, -12, -12, 0 ); + VectorSet( ent->r.maxs, 12, 12, 48 ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !ent->health ) { + ent->health = 10; + } + + ent->duration = ent->health; + + if ( !ent->count ) { + ent->count = 1; + } + + ent->think = Props_Chair_Think; + ent->nextthink = level.time + FRAMETIME; + + ent->touch = Props_Chair_Touch; + ent->die = Props_Chair_Die; + ent->takedamage = qtrue; + trap_LinkEntity( ent ); + + snd_boardbreak = G_SoundIndex( "sound/world/boardbreak.wav" ); + snd_glassbreak = G_SoundIndex( "sound/world/glassbreak.wav" ); + snd_metalbreak = G_SoundIndex( "sound/world/metalbreak.wav" ); + snd_ceramicbreak = G_SoundIndex( "sound/world/ceramicbreak.wav" ); + snd_chaircreak = G_SoundIndex( "sound/world/chaircreak.wav" ); + +} + +void SP_Props_ChairHiback( gentity_t *ent ) { + int mass; + + // ent->s.modelindex = G_ModelIndex( "models/furniture/chair/hiback.md3" ); + ent->s.modelindex = G_ModelIndex( "models/furniture/chair/hiback5.md3" ); + + ent->delay = 0; // inherits damage value + + if ( G_SpawnInt( "mass", "5", &mass ) ) { + ent->wait = mass; + } else { + ent->wait = 5; + } + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + ent->s.dmgFlags = HINT_CHAIR; // so client knows what kind of mover it is for cursorhints + + ent->isProp = qtrue; + + VectorSet( ent->r.mins, -12, -12, 0 ); + VectorSet( ent->r.maxs, 12, 12, 48 ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !ent->health ) { + ent->health = 10; + } + + ent->duration = ent->health; + + if ( !ent->count ) { + ent->count = 1; + } + + ent->think = Props_Chair_Think; + ent->nextthink = level.time + FRAMETIME; + + ent->touch = Props_Chair_Touch; + ent->die = Props_Chair_Die; + ent->takedamage = qtrue; + trap_LinkEntity( ent ); + + snd_boardbreak = G_SoundIndex( "sound/world/boardbreak.wav" ); + snd_glassbreak = G_SoundIndex( "sound/world/glassbreak.wav" ); + snd_metalbreak = G_SoundIndex( "sound/world/metalbreak.wav" ); + snd_ceramicbreak = G_SoundIndex( "sound/world/ceramicbreak.wav" ); + snd_chaircreak = G_SoundIndex( "sound/world/chaircreak.wav" ); +} + +void SP_Props_ChairSide( gentity_t *ent ) { + int mass; + + ent->s.modelindex = G_ModelIndex( "models/furniture/chair/sidechair3.md3" ); + + ent->delay = 0; // inherits damage value + + if ( G_SpawnInt( "mass", "5", &mass ) ) { + ent->wait = mass; + } else { + ent->wait = 5; + } + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + ent->s.dmgFlags = HINT_CHAIR; // so client knows what kind of mover it is for cursorhints + + ent->isProp = qtrue; + + VectorSet( ent->r.mins, -12, -12, 0 ); + VectorSet( ent->r.maxs, 12, 12, 48 ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !ent->health ) { + ent->health = 10; + } + + ent->duration = ent->health; + + if ( !ent->count ) { + ent->count = 1; + } + + ent->think = Props_Chair_Think; + ent->nextthink = level.time + FRAMETIME; + + ent->touch = Props_Chair_Touch; + ent->die = Props_Chair_Die; + ent->takedamage = qtrue; + trap_LinkEntity( ent ); + + snd_boardbreak = G_SoundIndex( "sound/world/boardbreak.wav" ); + snd_glassbreak = G_SoundIndex( "sound/world/glassbreak.wav" ); + snd_metalbreak = G_SoundIndex( "sound/world/metalbreak.wav" ); + snd_ceramicbreak = G_SoundIndex( "sound/world/ceramicbreak.wav" ); + snd_chaircreak = G_SoundIndex( "sound/world/chaircreak.wav" ); + snd_chairthrow = G_SoundIndex( "sound/props/throw/chairthudgrunt.wav" ); + snd_chairhitground = G_SoundIndex( "sound/props/chair/chairthud.wav" ); +} + +//----(SA) modified + +// can be one of two types, but they have the same animations/etc, so re-use what you can +/* +============== +SP_Props_ChateauChair +============== +*/ +void SP_Props_ChateauChair( gentity_t *ent ) { + int mass; + + ent->delay = 0; // inherits damage value + + if ( G_SpawnInt( "mass", "5", &mass ) ) { + ent->wait = mass; + } else { + ent->wait = 5; + } + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + ent->s.dmgFlags = HINT_CHAIR; // so client knows what kind of mover it is for cursorhints + + ent->isProp = qtrue; + + VectorSet( ent->r.mins, -12, -12, 0 ); + VectorSet( ent->r.maxs, 12, 12, 48 ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !ent->health ) { + ent->health = 10; + } + + ent->duration = ent->health; + + if ( !ent->count ) { + ent->count = 1; + } + + ent->think = Props_Chair_Think; + ent->nextthink = level.time + FRAMETIME; + + ent->touch = Props_Chair_Touch; + ent->die = Props_Chair_Die; + ent->takedamage = qtrue; + trap_LinkEntity( ent ); + + snd_boardbreak = G_SoundIndex( "sound/world/boardbreak.wav" ); + snd_glassbreak = G_SoundIndex( "sound/world/glassbreak.wav" ); + snd_metalbreak = G_SoundIndex( "sound/world/metalbreak.wav" ); + snd_ceramicbreak = G_SoundIndex( "sound/world/ceramicbreak.wav" ); + snd_chaircreak = G_SoundIndex( "sound/world/chaircreak.wav" ); + snd_chairthrow = G_SoundIndex( "sound/props/throw/chairthudgrunt.wav" ); + snd_chairhitground = G_SoundIndex( "sound/props/chair/chairthud.wav" ); +} + + +/* +============== +SP_Props_ChairChat +============== +*/ +void SP_Props_ChairChat( gentity_t *ent ) { + ent->s.modelindex = G_ModelIndex( "models/furniture/chair/chair_chat.md3" ); + SP_Props_ChateauChair( ent ); +} +/* +============== +SP_Props_ChairChatArm +============== +*/ +void SP_Props_ChairChatArm( gentity_t *ent ) { + ent->s.modelindex = G_ModelIndex( "models/furniture/chair/chair_chatarm.md3" ); + SP_Props_ChateauChair( ent ); +} + +//----(SA) end + + +void Use_DamageInflictor( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *daent; + + daent = NULL; + while ( ( daent = G_Find( daent, FOFS( targetname ), daent->target ) ) != NULL ) + { + if ( daent == ent ) { + G_Printf( "Use_DamageInflictor damaging self.\n" ); + } else + { + G_Damage( daent, ent, ent, NULL, NULL, 9999, 0, MOD_CRUSH ); + } + } + + G_FreeEntity( ent ); +} + +/*QUAKED props_damageinflictor (.8 .6 .6) (-8 -8 -8) (8 8 8) +this entity when used will cause 9999 damage to all entities it is targeting +then it will be removed +*/ +void SP_Props_DamageInflictor( gentity_t *ent ) { + G_SetOrigin( ent, ent->s.origin ); + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + + ent->use = Use_DamageInflictor; + trap_LinkEntity( ent ); +} + +/*QUAKED props_shard_generator (.8 .5 .1) (-4 -4 -4) (4 4 4) + +wait = defaults to 5 how many shards to spawn ( try not to exceed 20 ) + +shard = +shard_glass = 0, +shard_wood = 1, +shard_metal = 2, +shard_ceramic = 3 + +*/ + +void Use_Props_Shard_Generator( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + int quantity; + int type; + gentity_t *inflictor = NULL; + + type = ent->count; + quantity = ent->wait; + + inflictor = G_Find( NULL, FOFS( targetname ), ent->target ); + + if ( inflictor ) { + Spawn_Shard( ent, inflictor, quantity, type ); + } + + G_FreeEntity( ent ); +} + +void SP_props_shard_generator( gentity_t *ent ) { + G_SetOrigin( ent, ent->s.origin ); + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + ent->use = Use_Props_Shard_Generator; + + if ( !ent->count ) { + ent->count = shard_wood; + } + + if ( !ent->wait ) { + ent->wait = 5; + } + + trap_LinkEntity( ent ); +} + + +/*QUAKED props_desklamp (.8 .6 .2) (-16 -16 0) (16 16 32) +point entity +health = default = 10 +wait = defaults to 5 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ +void SP_Props_Desklamp( gentity_t *ent ) { + int mass; + + ent->s.modelindex = G_ModelIndex( "models/furniture/lights/desklamp.md3" ); + + ent->delay = 0; // inherits damage value + + if ( G_SpawnInt( "mass", "5", &mass ) ) { + ent->wait = mass; + } else { + ent->wait = 2; + } + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + + ent->isProp = qtrue; + ent->nopickup = qtrue; + + VectorSet( ent->r.mins, -6, -6, 0 ); + VectorSet( ent->r.maxs, 6, 6, 14 ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !ent->health ) { + ent->health = 10; + } + + ent->duration = ent->health; + + if ( !ent->count ) { + ent->count = 2; + } + + ent->think = Props_Chair_Think; + ent->nextthink = level.time + FRAMETIME; + + ent->touch = Props_Chair_Touch; + ent->die = Props_Chair_Die; + ent->takedamage = qtrue; + trap_LinkEntity( ent ); + + snd_boardbreak = G_SoundIndex( "sound/world/boardbreak.wav" ); + snd_glassbreak = G_SoundIndex( "sound/world/glassbreak.wav" ); + snd_metalbreak = G_SoundIndex( "sound/world/metalbreak.wav" ); + snd_ceramicbreak = G_SoundIndex( "sound/world/ceramicbreak.wav" ); + snd_chaircreak = G_SoundIndex( "sound/world/chaircreak.wav" ); +} + +/*QUAKED props_flamebarrel (.8 .6 .2) (-13 -13 0) (13 13 40) SMOKING NOLID OIL - +angle will determine which way the lid will fly off when it explodes + +when selecting the OIL spawnflag you have the option of giving it a target +this will ensure that the oil sprite will show up where you want it +( be sure to put it on the floor ) +the default is in the middle of the barrel on the floor +*/ +void Props_Barrel_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + return; // barrels cant move + + if ( !( self->spawnflags & 4 ) ) { + Props_Chair_Touch( self, other, trace ); + } +} + +void Props_Barrel_Animate( gentity_t *ent ) { + float ratio; + vec3_t v; + + if ( ent->s.frame == 14 ) { + if ( ent->spawnflags & 1 ) { + // G_UseTargets (ent, NULL); + ent->think = G_FreeEntity; + ent->nextthink = level.time + 25000; + return; + } else + { + // G_UseTargets (ent, NULL); + ent->think = G_FreeEntity; + ent->nextthink = level.time + 25000; + //ent->s.time = level.time; + //ent->s.time2 = level.time + 2000; + return; + } + } else + { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } + + ent->s.frame++; + + if ( !( ent->spawnflags & 1 ) ) { + ratio = 2.5; + VectorSubtract( ent->r.currentOrigin, ent->enemy->r.currentOrigin, v ); + moveit( ent, vectoyaw( v ), ( ent->delay * ratio * FRAMETIME ) * .001 ); + } + +} + +void barrel_smoke( gentity_t *ent ) { + gentity_t *tent; + vec3_t point; + + VectorCopy( ent->r.currentOrigin, point ); + + tent = G_TempEntity( point, EV_SMOKE ); + VectorCopy( point, tent->s.origin ); + tent->s.time = 4000; + tent->s.time2 = 1000; + tent->s.density = 0; + tent->s.angles2[0] = 8; + tent->s.angles2[1] = 64; + tent->s.angles2[2] = 50; + +} + +void smoker_think( gentity_t *ent ) { + ent->count--; + + if ( !ent->count ) { + G_FreeEntity( ent ); + } else + { + barrel_smoke( ent ); + ent->nextthink = level.time + FRAMETIME; + } + +} + +void SP_OilSlick( gentity_t *ent ) { + gentity_t *tent; + gentity_t *target = NULL; + vec3_t point; + + if ( ent->target ) { + target = G_Find( NULL, FOFS( targetname ), ent->target ); + } + + if ( target ) { + VectorCopy( target->s.origin, point ); + point[2] = ent->r.currentOrigin[2]; // just in case + } else { + VectorCopy( ent->r.currentOrigin, point ); + } + + tent = G_TempEntity( ent->r.currentOrigin, EV_OILSLICK ); + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + tent->s.angles2[0] = 16; + tent->s.angles2[1] = 48; + tent->s.angles2[2] = 10000; + tent->s.density = ent->s.number; + +} + +void OilParticles_think( gentity_t *ent ) { + gentity_t *tent; + gentity_t *owner; + + owner = &g_entities[ent->s.density]; + + if ( owner && owner->takedamage && ent->count2 > level.time - 5000 ) { + ent->nextthink = ( level.time + FRAMETIME / 2 ); + + tent = G_TempEntity( ent->r.currentOrigin, EV_OILPARTICLES ); + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + tent->s.time = ent->count2; + tent->s.density = ent->s.density; + VectorCopy( ent->rotate, tent->s.origin2 ); + } else { + G_FreeEntity( ent ); + } +} + +void Delayed_Leak_Think( gentity_t *ent ) { + vec3_t point; + gentity_t *tent; + + VectorCopy( ent->r.currentOrigin, point ); + + tent = G_TempEntity( point, EV_OILSLICK ); + VectorCopy( point, tent->s.origin ); + + tent->s.angles2[0] = 0; + tent->s.angles2[1] = 0; + tent->s.angles2[2] = 2000; + tent->s.density = ent->count; +} + +qboolean validOilSlickSpawnPoint( vec3_t point, gentity_t *ent ) { + trace_t tr; + vec3_t end; + gentity_t *traceEnt; + + VectorCopy( point, end ); + end[2] -= 9999; + + trap_Trace( &tr, point, NULL, NULL, end, ent->s.number, MASK_SHOT ); + + traceEnt = &g_entities[ tr.entityNum ]; + + if ( traceEnt && traceEnt->classname ) { + if ( !Q_stricmp( traceEnt->classname, "worldspawn" ) ) { + if ( tr.plane.normal[0] == 0 && tr.plane.normal[1] == 0 && tr.plane.normal[2] == 1 ) { + return qtrue; + } + } + } + + return qfalse; + +} + +void SP_OilParticles( gentity_t *ent ) { + gentity_t *OilLeak; + vec3_t point; + vec3_t vec; + vec3_t forward; + +// Note to self quick fix +// need to move this to client + return; + + OilLeak = G_Spawn(); + + VectorCopy( ent->r.currentOrigin, point ); + + point[2] = ent->pos3[2]; + + VectorSubtract( ent->pos3, point, vec ); + vectoangles( vec, vec ); + AngleVectors( vec, forward, NULL, NULL ); + VectorMA( point, 12, forward, point ); + + G_SetOrigin( OilLeak, point ); + + G_SetAngle( OilLeak, ent->r.currentAngles ); + + VectorCopy( forward, OilLeak->rotate ); + + OilLeak->think = OilParticles_think; + OilLeak->nextthink = level.time + FRAMETIME; + + OilLeak->s.density = ent->s.number; + OilLeak->count2 = level.time; + + trap_LinkEntity( OilLeak ); + +} + + +void Props_Barrel_Pain( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ) { + + if ( ent->health <= 0 ) { + return; + } + + if ( !( ent->spawnflags & 8 ) ) { + SP_OilSlick( ent ); + ent->spawnflags |= 8; + } + + ent->count2++; + + if ( ent->count2 < 6 ) { + SP_OilParticles( ent ); + } + +} + +void OilSlick_remove_think( gentity_t *ent ) { + gentity_t *tent; + + tent = G_TempEntity( ent->r.currentOrigin, EV_OILSLICKREMOVE ); + tent->s.density = ent->s.density; +} + +void OilSlick_remove( gentity_t *ent ) { + gentity_t *remove; + + remove = G_Spawn(); + remove->s.density = ent->s.number; + remove->think = OilSlick_remove_think; + remove->nextthink = level.time + 1000; + VectorCopy( ent->r.currentOrigin, remove->r.currentOrigin ); + trap_LinkEntity( remove ); +} + +void Props_Barrel_Die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + int quantity; + int type; + vec3_t dir; + + if ( ent->spawnflags & 1 ) { + ent->s.eFlags = EF_SMOKINGBLACK; + } + + G_UseTargets( ent, NULL ); + + if ( ent->spawnflags & 4 ) { + OilSlick_remove( ent ); + } + + ent->health = 100; + propExplosion( ent ); + ent->health = 0; + + ent->takedamage = qfalse; + + AngleVectors( ent->r.currentAngles, dir, NULL, NULL ); + dir[2] = 1; + + if ( !( ent->spawnflags & 2 ) ) { + fire_flamebarrel( ent, ent->r.currentOrigin, dir ); + } + + ent->touch = NULL; + + ent->think = Props_Barrel_Animate; + ent->nextthink = level.time + FRAMETIME; + + ent->health = ent->duration; + ent->delay = damage; + ent->enemy = inflictor; + + quantity = ent->wait; + type = ent->count; + + if ( inflictor ) { + Spawn_Shard( ent, inflictor, quantity, type ); + } + + Prop_Break_Sound( ent ); + + trap_UnlinkEntity( ent ); + + ent->clipmask = 0; + ent->r.contents = 0; + ent->s.eType = ET_GENERAL; + + trap_LinkEntity( ent ); +} + +void Props_OilSlickSlippery( gentity_t *ent ) { + gentity_t *player; + vec3_t vec, kvel, dir; + float len; + + player = AICast_FindEntityForName( "player" ); + + if ( player ) { + VectorSubtract( player->r.currentOrigin, ent->r.currentOrigin, vec ); + len = VectorLength( vec ); + + if ( len < 64 && player->s.groundEntityNum != -1 ) { + len = VectorLength( player->client->ps.velocity ); + + if ( len && !( player->client->ps.pm_time ) ) { + VectorSet( dir, fabs( crandom() ), fabs( crandom() ), 0 ); + VectorScale( dir, 32, kvel ); + VectorAdd( player->client->ps.velocity, kvel, player->client->ps.velocity ); + + { + int t; + + t = 32 * 2; + if ( t < 50 ) { + t = 50; + } + if ( t > 200 ) { + t = 200; + } + player->client->ps.pm_time = t; + player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + + } + + } + } +} + +void Props_Barrel_Think( gentity_t *self ) { + self->active = qfalse; + Props_Chair_Think( self ); + + if ( self->spawnflags & 8 ) { // there is an oil slick + Props_OilSlickSlippery( self ); + } +} + +void SP_Props_Flamebarrel( gentity_t *ent ) { + int mass; + + if ( ent->spawnflags & 4 ) { + ent->s.modelindex = G_ModelIndex( "models/furniture/barrel/barrel_c.md3" ); + } else if ( ent->spawnflags & 1 ) { + ent->s.modelindex = G_ModelIndex( "models/furniture/barrel/barrel_d.md3" ); + } else { + ent->s.modelindex = G_ModelIndex( "models/furniture/barrel/barrel_b.md3" ); + } + + ent->delay = 0; // inherits damage value + + if ( G_SpawnInt( "mass", "5", &mass ) ) { + ent->wait = mass; + } else { + ent->wait = 10; + } + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + + ent->isProp = qtrue; + ent->nopickup = qtrue; + + VectorSet( ent->r.mins, -13, -13, 0 ); + VectorSet( ent->r.maxs, 13, 13, 36 ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !ent->health ) { + ent->health = 20; + } + + ent->duration = ent->health; + + ent->count = 2; // metal shards + + ent->think = Props_Barrel_Think; + ent->nextthink = level.time + FRAMETIME; + + ent->touch = Props_Barrel_Touch; + + ent->die = Props_Barrel_Die; + + if ( ent->spawnflags & 4 ) { + ent->pain = Props_Barrel_Pain; + } + + ent->takedamage = qtrue; + trap_LinkEntity( ent ); +} + +/*QUAKED props_crate_64 (.8 .6 .2) (-32 -32 0) (32 32 64) +breakable pushable + + health = default = 20 +wait = defaults to 10 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + +/*QUAKED props_crate_32 (.8 .6 .2) (-16 -16 0) (16 16 32) +breakable pushable + + health = default = 20 +wait = defaults to 10 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + +/*QUAKED props_crate_32x64 (.8 .6 .2) ? +requires an origin brush + +breakable NOT pushable + +brushmodel only + + health = default = 20 +wait = defaults to 10 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + +void touch_crate_64( gentity_t *self, gentity_t *other, trace_t *trace ) { + float ratio; + vec3_t v; + + if ( other->r.currentOrigin[2] > ( self->r.currentOrigin[2] + 10 + 31 ) ) { + return; + } + + ratio = 1.5; + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, v ); + moveit( self, vectoyaw( v ), ( 20 * ratio * FRAMETIME ) * .001 ); +} + +void crate_animate( gentity_t *ent ) { + if ( ent->s.frame == 17 ) { + G_UseTargets( ent, NULL ); + ent->think = G_FreeEntity; + ent->nextthink = level.time + 2000; + ent->s.time = level.time; + ent->s.time2 = level.time + 2000; + return; + } + + ent->s.frame++; + ent->nextthink = level.time + ( FRAMETIME / 2 ); +} + +void crate_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + int quantity; + int type; + + quantity = ent->wait; + type = ent->count; + + Spawn_Shard( ent, inflictor, quantity, type ); + + ent->takedamage = qfalse; + ent->think = crate_animate; + ent->nextthink = level.time + FRAMETIME; + ent->touch = NULL; + + trap_UnlinkEntity( ent ); + + ent->clipmask = 0; + ent->r.contents = 0; + ent->s.eType = ET_GENERAL; + + trap_LinkEntity( ent ); + +} + +void SP_crate_64( gentity_t *self ) { + self->s.modelindex = G_ModelIndex( "models/furniture/crate/crate64.md3" ); + + self->clipmask = CONTENTS_SOLID; + self->r.contents = CONTENTS_SOLID; + self->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + VectorSet( self->r.mins, -32, -32, 0 ); + VectorSet( self->r.maxs, 32, 32, 64 ); + + self->s.eType = ET_MOVER; + + self->isProp = qtrue; + self->nopickup = qtrue; + G_SetOrigin( self, self->s.origin ); + G_SetAngle( self, self->s.angles ); + + self->touch = touch_crate_64; + self->die = crate_die; + + self->takedamage = qtrue; + + if ( !self->health ) { + self->health = 20; + } + + if ( !self->count ) { + self->count = 1; + } + + if ( !self->wait ) { + self->wait = 10; + } + + self->isProp = qtrue; + self->nopickup = qtrue; + + trap_LinkEntity( self ); + + self->think = DropToFloor; + self->nextthink = level.time + FRAMETIME; +} + +void SP_crate_32( gentity_t *self ) { + self->s.modelindex = G_ModelIndex( "models/furniture/crate/crate32.md3" ); + + self->clipmask = CONTENTS_SOLID; + self->r.contents = CONTENTS_SOLID; + self->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + VectorSet( self->r.mins, -16, -16, 0 ); + VectorSet( self->r.maxs, 16, 16, 32 ); + + self->s.eType = ET_MOVER; + + self->isProp = qtrue; + self->nopickup = qtrue; + G_SetOrigin( self, self->s.origin ); + G_SetAngle( self, self->s.angles ); + + self->touch = touch_crate_64; + self->die = crate_die; + + self->takedamage = qtrue; + + if ( !self->health ) { + self->health = 20; + } + + if ( !self->count ) { + self->count = 1; + } + + if ( !self->wait ) { + self->wait = 10; + } + + self->isProp = qtrue; + self->nopickup = qtrue; + + trap_LinkEntity( self ); + + self->think = DropToFloor; + self->nextthink = level.time + FRAMETIME; +} + +////////////////////////////////////////////// + +void props_crate32x64_think( gentity_t *ent ) { + ent->s.frame++; + + if ( ent->s.frame < 17 ) { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } else + { + ent->clipmask = 0; + ent->r.contents = 0; + ent->takedamage = qfalse; + + G_UseTargets( ent, NULL ); + } + +} + +void props_crate32x64_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + ent->think = props_crate32x64_think; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_Props_Crate32x64( gentity_t *ent ) { + + trap_SetBrushModel( ent, ent->model ); + + InitProp( ent ); + + if ( !ent->health ) { + ent->health = 10; + } + + ent->takedamage = qtrue; + + ent->clipmask = CONTENTS_SOLID; + + ent->die = props_crate32x64_die; + + trap_LinkEntity( ent ); +} + +/*QUAKED props_flippy_table (.8 .6 .2) ? - - X_AXIS Y_AXIS LEADER +this entity will need a leader and an origin brush +!!!!!!!!!!!!!! +just a reminder to put the origin brush in the proper location for the leader and the +slave so that the table will flip over correctly. +*/ + +void flippy_table_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + qboolean is_infront; + gentity_t *slave; + + // it would be odd to flip a table if your standing on it + if ( other && other->s.groundEntityNum == ent->s.number ) { + // G_Printf ("can't push table over while standing on it\n"); + return; + } + + ent->use = NULL; + + is_infront = infront( ent, other ); + + if ( is_infront ) { + // need to swap the team leader with the slave + for ( slave = ent ; slave ; slave = slave->teamchain ) + { + if ( slave == ent ) { + continue; + } + + slave->s.pos.trType = ent->s.pos.trType; + slave->s.pos.trTime = ent->s.pos.trTime; + slave->s.pos.trDuration = ent->s.pos.trDuration; + VectorCopy( ent->s.pos.trBase, slave->s.pos.trBase ); + VectorCopy( ent->s.pos.trDelta, slave->s.pos.trDelta ); + + slave->s.apos.trType = ent->s.apos.trType; + slave->s.apos.trTime = ent->s.apos.trTime; + slave->s.apos.trDuration = ent->s.apos.trDuration; + VectorCopy( ent->s.apos.trBase, slave->s.apos.trBase ); + VectorCopy( ent->s.apos.trDelta, slave->s.apos.trDelta ); + + slave->think = ent->think; + slave->nextthink = ent->nextthink; + + VectorCopy( ent->pos1, slave->pos1 ); + VectorCopy( ent->pos2, slave->pos2 ); + + slave->speed = ent->speed; + + slave->flags &= ~FL_TEAMSLAVE; + // make it visible + trap_LinkEntity( slave ); + + Use_BinaryMover( slave, other, other ); + } + + trap_UnlinkEntity( ent ); + } else { + Use_BinaryMover( ent, other, other ); + } + +} + +void flippy_table_animate( gentity_t *ent ) { + return; + + if ( ent->s.frame == 9 ) { + G_UseTargets( ent, NULL ); + ent->think = G_FreeEntity; + ent->nextthink = level.time + 2000; + } else + { + ent->s.frame++; + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } +} + +void props_flippy_table_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + ent->think = flippy_table_animate; + ent->nextthink = level.time + FRAMETIME; + + ent->takedamage = qfalse; + + G_UseTargets( ent, NULL ); +} + +void props_flippy_blocked( gentity_t *ent, gentity_t *other ) { + vec3_t velocity; + vec3_t angles; + vec3_t kvel; + + // just for now + float angle = ent->r.currentAngles[YAW]; + + if ( other->client ) { + // shoot the player off of it + VectorCopy( ent->s.apos.trBase, angles ); + angles[YAW] += angle; + angles[PITCH] = 0; // always forward + + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 24, velocity ); + velocity[2] += 100 + crandom() * 50; + + VectorScale( velocity, 32, kvel ); + VectorAdd( other->client->ps.velocity, kvel, other->client->ps.velocity ); + } else if ( other->s.eType == ET_ITEM ) { + VectorCopy( ent->s.apos.trBase, angles ); + angles[YAW] += angle; + angles[PITCH] = 0; // always forward + + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 150, velocity ); + velocity[2] += 300 + crandom() * 50; + + VectorScale( velocity, 8, kvel ); + other->s.pos.trType = TR_GRAVITY; + other->s.pos.trTime = level.time; + VectorCopy( kvel, other->s.pos.trDelta ); + + other->s.eFlags |= EF_BOUNCE; + } else + { + // just delete it or destroy it + G_TempEntity( other->s.origin, EV_ITEM_POP ); + G_FreeEntity( other ); + return; + } +} + +void SP_Props_Flipping_Table( gentity_t *ent ) { + + if ( !ent->model ) { + G_Printf( S_COLOR_RED "props_Flipping_Table with NULL model\n" ); + return; + } + + trap_SetBrushModel( ent, ent->model ); + + ent->speed = 500; + ent->angle = 90; + + // ent->spawnflags |= 8; + if ( !( ent->spawnflags & 4 ) && !( ent->spawnflags & 8 ) ) { + G_Printf( "you forgot to select the X or Y Axis\n" ); + } + + VectorClear( ent->rotate ); + + if ( ent->spawnflags & 4 ) { + ent->rotate[2] = 1; + } else if ( ent->spawnflags & 8 ) { + ent->rotate[0] = 1; + } else { ent->rotate[1] = 1;} + + ent->spawnflags |= 64; // stay open + + InitMoverRotate( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + VectorCopy( ent->s.apos.trBase, ent->r.currentAngles ); + + ent->blocked = props_flippy_blocked; + + if ( !ent->health ) { + ent->health = 100; + } + + ent->wait *= 1000; + + //ent->takedamage = qtrue; + + //ent->die = props_flippy_table_die; + + ent->use = flippy_table_use; + + trap_LinkEntity( ent ); + +} + + +/*QUAKED props_58x112tablew (.8 .6 .2) ? +dimensions are 58 x 112 x 32 (x,y,z) + +requires an origin brush + +breakable NOT pushable + +brushmodel only + + health = default = 10 +wait = defaults to 10 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + +void props_58x112tablew_think( gentity_t *ent ) { + ent->s.frame++; + + if ( ent->s.frame < 16 ) { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } else + { + + ent->clipmask = 0; + ent->r.contents = 0; + + G_UseTargets( ent, NULL ); + } + +} + +void props_58x112tablew_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + ent->think = props_58x112tablew_think; + ent->nextthink = level.time + FRAMETIME; + ent->takedamage = qfalse; +} + +void SP_Props_58x112tablew( gentity_t *ent ) { + + trap_SetBrushModel( ent, ent->model ); + + InitProp( ent ); + + if ( !ent->health ) { + ent->health = 10; + } + + ent->takedamage = qtrue; + + ent->clipmask = CONTENTS_SOLID; + + ent->die = props_58x112tablew_die; + + trap_LinkEntity( ent ); +} + +/*QUAKED props_castlebed (.8 .6 .2) ? +dimensions are 112 x 128 x 80 (x,y,z) + +requires an origin brush + +breakable NOT pushable + +brushmodel only + + health = default = 20 +wait = defaults to 10 how many shards to spawn ( try not to exceed 20 ) + +shard = + shard_glass = 0, + shard_wood = 1, + shard_metal = 2, + shard_ceramic = 3 + +*/ + +void props_castlebed_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( !other->client ) { + return; + } + + if ( other->client->ps.pm_flags & PMF_JUMP_HELD + && other->s.groundEntityNum == ent->s.number + && !other->client->ps.pm_time ) { + G_Damage( ent, other, other, NULL, NULL, 1, 0, MOD_CRUSH ); + + // TDB: need sound of bed springs for this + G_Printf( "SOUND sqweeky\n" ); + + other->client->ps.velocity[2] += 250; + + other->client->ps.pm_time = 250; + other->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + +} + +void props_castlebed_animate( gentity_t *ent ) { + ent->s.frame++; + + if ( ent->s.frame < 8 ) { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + } else + { + ent->clipmask = 0; + ent->r.contents = 0; + G_UseTargets( ent, NULL ); + } +} + +void props_castlebed_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + ent->think = props_castlebed_animate; + ent->nextthink = level.time + FRAMETIME; + ent->touch = NULL; + ent->takedamage = qfalse; + + ent->count = shard_wood; + Prop_Break_Sound( ent ); +} + +void SP_props_castlebed( gentity_t *ent ) { + trap_SetBrushModel( ent, ent->model ); + + InitProp( ent ); + + if ( !ent->health ) { + ent->health = 20; + } + + ent->takedamage = qtrue; + + ent->clipmask = CONTENTS_SOLID; + + ent->die = props_castlebed_die; + ent->touch = props_castlebed_touch; + + trap_LinkEntity( ent ); +} + +/*QUAKED props_snowGenerator (3 2 7) ? TOGGLE_ON ALWAYS_ON +entity brush need to be targeted to an info notnull this +will determine the direction the snow particles will travel. + +speed +gravity +turb + +count is the number of snowflurries 3 to 5 would be a good number + +duration is how long the effect will last 1 is 1 second +*/ + +void props_snowGenerator_think( gentity_t *ent ) { + gentity_t *tent; + float high, wide, deep; + int i; + vec3_t point; + + if ( !( ent->spawnflags & 1 ) ) { + return; + } + + high = ent->r.maxs[2] - ent->r.mins[2]; + wide = ent->r.maxs[1] - ent->r.mins[1]; + deep = ent->r.maxs[0] - ent->r.mins[0]; + + for ( i = 0; i < ent->count; i++ ) + { + VectorCopy( ent->pos1, point ); + + // we need to randomize to the extent of the brush + point[0] += crandom() * ( deep * 0.5 ); + point[1] += crandom() * ( wide * 0.5 ); + point[2] += crandom() * ( high * 0.5 ); + + tent = G_TempEntity( point, EV_SNOWFLURRY ); + VectorCopy( point, tent->s.origin ); + VectorCopy( ent->movedir, tent->s.angles ); + tent->s.time = 2000; // life time + tent->s.time2 = 1000; // alpha fade start + } + + if ( ent->spawnflags & 2 ) { + ent->nextthink = level.time + FRAMETIME; + } else if ( ent->wait < level.time ) { + ent->nextthink = level.time + FRAMETIME; + } +} + +void props_snowGenerator_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( !( ent->spawnflags & 1 ) ) { + ent->spawnflags |= 1; + ent->think = props_snowGenerator_think; + ent->nextthink = level.time + FRAMETIME; + ent->wait = level.time + ent->duration; + } else { + ent->spawnflags &= ~1; + } +} + +void SP_props_snowGenerator( gentity_t *ent ) { + vec3_t center; + gentity_t *target = NULL; + + trap_SetBrushModel( ent, ent->model ); + + VectorAdd( ent->r.absmin, ent->r.absmax, center ); + VectorScale( center, 0.5, center ); + + VectorCopy( center, ent->pos1 ); + + if ( !ent->target ) { + G_Printf( "snowGenerator at loc %s does not have a target\n", vtos( center ) ); + return; + } else + { + target = G_Find( target, FOFS( targetname ), ent->target ); + if ( !target ) { + G_Printf( "error snowGenerator at loc %s does cant find target %s\n", vtos( center ), ent->target ); + return; + } + + VectorSubtract( target->s.origin, ent->s.origin, ent->movedir ); + VectorNormalize( ent->movedir ); + } + + ent->r.contents = CONTENTS_TRIGGER; + ent->r.svFlags = SVF_NOCLIENT; + + if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) { + ent->think = props_snowGenerator_think; + ent->nextthink = level.time + FRAMETIME; + + if ( ent->spawnflags & 2 ) { + ent->spawnflags |= 1; + } + } + + ent->use = props_snowGenerator_use; + + if ( !( ent->delay ) ) { + ent->delay = 100; + } else { + ent->delay *= 100; + } + + if ( !( ent->count ) ) { + ent->count = 32; + } + + if ( !( ent->duration ) ) { + ent->duration = 1; + } + + ent->duration *= 1000; + + trap_LinkEntity( ent ); +} + +///////////////////////////// +// FIRES AND EXPLOSION PROPS +///////////////////////////// + +/*QUAKED props_FireColumn (.3 .2 .7) (-8 -8 -8) (8 8 8) CORKSCREW SMOKE GRAVITY HALFGRAVITY +this entity will require a target use an infonotnull to specifiy its direction + +defaults: + will leave a flaming trail by default + will not be affected by gravity + +radius = distance flame will corkscrew from origin +speed = default is 900 +duration = default is 3 sec + +start_size = default is 5 +end_size = defaults 7 thru 17 +count = defaults 100 thru 500 + +Pending: +delay before it happens again use trigger_relay for now +assign a model +*/ + +void propsFireColumnUse( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *tent; + vec3_t start, dir; + + VectorCopy( ent->s.origin, start ); + + AngleVectors( ent->r.currentAngles, dir, NULL, NULL ); + + tent = fire_flamebarrel( ent, start, dir ); + + if ( !tent ) { + return; + } + + if ( ent->spawnflags & 2 ) { + tent->s.eType = ET_FIRE_COLUMN_SMOKE; + } else { + tent->s.eType = ET_FIRE_COLUMN; + } + + if ( ent->spawnflags & 4 ) { + tent->s.pos.trType = TR_GRAVITY; + } else if ( ent->spawnflags & 8 ) { + tent->s.pos.trType = TR_GRAVITY_LOW; + } else { + tent->s.pos.trType = TR_LINEAR; + } + + if ( ent->spawnflags & 1 ) { + tent->s.density = ent->radius; // corkscrew effect + } + + tent->flags |= FL_NODRAW; + //tent->s.eFlags |= EF_NODRAW; + + // TBD + // lifetime + if ( ent->duration ) { + tent->nextthink = level.time + ent->duration; + } + + // speed + if ( ent->speed ) { + VectorClear( tent->s.pos.trDelta ); + VectorScale( dir, ent->speed + ( crandom() * 100 ), tent->s.pos.trDelta ); + SnapVector( tent->s.pos.trDelta ); + VectorCopy( start, tent->r.currentOrigin ); + } + + if ( ent->start_size ) { + tent->s.angles[1] = ent->start_size; + } + + if ( ent->end_size ) { + tent->s.angles[2] = ent->end_size; + } + + if ( ent->count ) { + tent->s.angles[0] = ent->count; + } + + G_SetAngle( tent, ent->r.currentAngles ); +} + +void propsFireColumnInit( gentity_t *ent ) { + gentity_t *target; + vec3_t vec; + vec3_t angles; + + if ( ent->target ) { + target = G_Find( NULL, FOFS( targetname ), ent->target ); + VectorSubtract( target->s.origin, ent->s.origin, vec ); + vectoangles( vec, angles ); + G_SetAngle( ent, angles ); + } else + { + // ok then just up + VectorSet( vec, 0, 0, 1 ); + vectoangles( vec, angles ); + G_SetAngle( ent, angles ); + } + + if ( ent->duration ) { + ent->duration = ent->duration * 1000; + } + + +} + +void SP_propsFireColumn( gentity_t *ent ) { + G_SetOrigin( ent, ent->s.origin ); + ent->think = propsFireColumnInit; + ent->nextthink = level.time + FRAMETIME; + ent->use = propsFireColumnUse; + trap_LinkEntity( ent ); +} + +/*QUAKED props_ExploPart (.3 .5 .7) (-8 -8 -16) (8 8 16) +"model" will load a discreet model +"noise" will load looping sound for the model +"target" point to an infonotnull to specify dir default will be up +"speed" default to 900 + +"type" wood concrete or stone +"count"in the absense of a model count will determine the piece to spawn +for wood: + it can be one of the following 64 48 32 24 16 8 +for concrete: +for stone: +*/ +#define EXPLOPARTPIECES 8 + +void props_ExploPartUse( gentity_t *ent, gentity_t *other, gentity_t *activator ) { +// int i, numpieces; + gentity_t *part; + vec3_t start, dir; + + VectorCopy( ent->s.origin, start ); + AngleVectors( ent->r.currentAngles, dir, NULL, NULL ); + + if ( ent->s.modelindex ) { + part = fire_flamebarrel( ent, start, dir ); + part->s.modelindex = ent->s.modelindex; + } else + { + G_Printf( "props_ExploPartUse has not been assigned a model\n" ); + return; + } + + if ( part ) { + part->s.pos.trType = TR_GRAVITY; + part->s.eType = ET_EXPLO_PART; + + G_SetAngle( part, ent->r.currentAngles ); + + if ( ent->speed ) { + VectorClear( part->s.pos.trDelta ); + VectorScale( dir, ent->speed + ( crandom() * 100 ), part->s.pos.trDelta ); + SnapVector( part->s.pos.trDelta ); + VectorCopy( start, part->r.currentOrigin ); + } + } + + G_UseTargets( ent, NULL ); +} + +void props_ExploPartInit( gentity_t *ent ) { + gentity_t *target; + vec3_t vec, angles; + + if ( ent->target ) { + target = G_Find( NULL, FOFS( targetname ), ent->target ); + VectorSubtract( target->s.origin, ent->s.origin, vec ); + vectoangles( vec, angles ); + G_SetAngle( ent, angles ); + } else + { + // ok then just up + VectorSet( vec, 0, 0, 1 ); + vectoangles( vec, angles ); + G_SetAngle( ent, angles ); + } +} + +void SP_props_ExploPart( gentity_t *ent ) { + char *sound; + char *type; +// float bbox; + + if ( ent->model ) { + ent->s.modelindex = G_ModelIndex( ent->model ); + } + + G_SpawnString( "type", "wood", &type ); + + if ( !Q_stricmp( type,"wood" ) ) { + if ( ent->count == 64 ) { + ent->s.modelindex = G_ModelIndex( "models/shards/2x4a.md3" ); + } else if ( ent->count == 48 ) { + ent->s.modelindex = G_ModelIndex( "models/shards/2x4b.md3" ); + } else if ( ent->count == 32 ) { + ent->s.modelindex = G_ModelIndex( "models/shards/2x4c.md3" ); + } else if ( ent->count == 24 ) { + ent->s.modelindex = G_ModelIndex( "models/shards/2x4d.md3" ); + } else if ( ent->count == 16 ) { + ent->s.modelindex = G_ModelIndex( "models/shards/2x4e.md3" ); + } else if ( ent->count == 8 ) { + ent->s.modelindex = G_ModelIndex( "models/shards/2x4f.md3" ); + } + } else if ( !Q_stricmp( type,"concrete" ) ) { + } else if ( !Q_stricmp( type,"stone" ) ) { + } + + if ( G_SpawnString( "noise", "100", &sound ) ) { + ent->s.loopSound = G_SoundIndex( sound ); + } + + ent->think = props_ExploPartInit; + ent->nextthink = level.time + FRAMETIME; + + ent->use = props_ExploPartUse; +} + +/*QUAKED props_decoration (.6 .7 .7) (-8 -8 0) (8 8 16) STARTINVIS DEBRIS ANIMATE KEEPBLOCK TOUCHACTIVATE LOOPING STARTON +"model2" will specify the model to load + +"noise" the looping sound entity is to make + +"type" type of debris ("glass", "wood", "metal", "ceramic", "rubble") default is "wood" +"count" how much debris ei. default 4 pieces + +you will need to specify the bounding box for the entity +"high" default is 4 +"wide" default is 4 + +"frames" how many frames of animation to play +"loop" when the animation is done start again on this frame +"startonframe" on what frame do you want to start the animation +*/ + +void props_decoration_animate( gentity_t *ent ) { + + ent->s.frame++; + ent->s.eType = ET_GENERAL; + + if ( ent->s.frame > ent->count2 ) { + if ( ent->spawnflags & 32 || ent->spawnflags & 64 ) { + ent->s.frame = ent->props_frame_state; + + if ( !( ent->spawnflags & 64 ) ) { + ent->takedamage = qfalse; + } + } else + { + ent->s.frame = ent->count2; + ent->takedamage = qfalse; + + return; + } + } + + ent->nextthink = level.time + 50; +} + +void props_decoration_death( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + if ( !( ent->spawnflags & 8 ) ) { + ent->clipmask = 0; + ent->r.contents = 0; + ent->s.eType = ET_GENERAL; + trap_LinkEntity( ent ); + } + + ent->takedamage = qfalse; + + G_UseTargets( ent, NULL ); + + if ( ent->spawnflags & 2 ) { + Spawn_Shard( ent, inflictor, ent->count, ent->key ); + } + + if ( ent->spawnflags & 4 ) { + ent->nextthink = level.time + 50; + ent->think = props_decoration_animate; + return; + } + + G_FreeEntity( ent ); + +} + +void Use_props_decoration( gentity_t *ent, gentity_t *self, gentity_t *activator ) { + if ( ent->spawnflags & 1 ) { + trap_LinkEntity( ent ); + ent->spawnflags &= ~1; + } else if ( ent->spawnflags & 4 ) { + ent->nextthink = level.time + 50; + ent->think = props_decoration_animate; + } else + { + trap_UnlinkEntity( ent ); + ent->spawnflags |= 1; + } + +} + +void props_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + if ( self->spawnflags & 16 ) { + props_decoration_death( self, other, other, 9999, MOD_CRUSH ); + } +} + +void SP_props_decoration( gentity_t *ent ) { + float light; + vec3_t color; + qboolean lightSet, colorSet; + char *sound; + char *type; + char *high; + char *wide; + char *frames; + float height; + float width; + float num_frames; + + char *loop; + + char *startonframe; + + if ( G_SpawnString( "startonframe", "0", &startonframe ) ) { + ent->s.frame = atoi( startonframe ); + } + + if ( ent->model2 ) { + ent->s.modelindex = G_ModelIndex( ent->model2 ); + } + + if ( G_SpawnString( "noise", "100", &sound ) ) { + ent->s.loopSound = G_SoundIndex( sound ); + } + + if ( ( ent->spawnflags & 32 ) && G_SpawnString( "loop", "100", &loop ) ) { + ent->props_frame_state = atoi( loop ); + } + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + if ( lightSet || colorSet ) { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + if ( ent->health ) { + ent->isProp = qtrue; + ent->takedamage = qtrue; + ent->die = props_decoration_death; + + G_SpawnString( "type", "wood", &type ); + if ( !Q_stricmp( type,"wood" ) ) { + ent->key = 1; + } else if ( !Q_stricmp( type,"glass" ) ) { + ent->key = 0; + } else if ( !Q_stricmp( type,"metal" ) ) { + ent->key = 2; + } else if ( !Q_stricmp( type,"ceramic" ) ) { + ent->key = 3; + } else if ( !Q_stricmp( type, "rubble" ) ) { + ent->key = 4; + } + + G_SpawnString( "high", "0", &high ); + height = atof( high ); + + if ( !height ) { + height = 4; + } + + G_SpawnString( "wide", "0", &wide ); + width = atof( wide ); + + if ( !width ) { + width = 4; + } + + width /= 2; + + if ( Q_stricmp( ent->classname, "props_decorBRUSH" ) ) { + VectorSet( ent->r.mins, -width, -width, 0 ); + VectorSet( ent->r.maxs, width, width, height ); + } + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->s.eType = ET_MOVER; + + G_SpawnString( "frames", "0", &frames ); + num_frames = atof( frames ); + + ent->count2 = num_frames; + + if ( ent->targetname ) { + ent->use = Use_props_decoration; + } + + ent->touch = props_touch; + + } else if ( !( ent->health ) && ent->spawnflags & 4 ) { + G_SpawnString( "frames", "0", &frames ); + num_frames = atof( frames ); + + ent->count2 = num_frames; + ent->use = Use_props_decoration; + } + + if ( ent->spawnflags & 64 ) { + ent->nextthink = level.time + 50; + ent->think = props_decoration_animate; + } + + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !( ent->spawnflags & 1 ) ) { + trap_LinkEntity( ent ); + } else { + ent->use = Use_props_decoration; + } + +} + +/*QUAKED props_decorBRUSH (.6 .7 .7) ? STARTINVIS DEBRIS ANIMATE KEEPBLOCK - LOOPING STARTON +ANIMATE animate on death +STARTON playanimation on death +must have an origin brush + +"model2" will specify the model to load + +"noise" the looping sound entity is to make + +"type" type of debris ("glass", "wood", "metal", "ceramic", "rubble") default is "wood" +"count" how much debris ei. default 4 pieces + +"frames" how many frames of animation to play +"loop" when the animation is done start again on this frame +"startonframe" on what frame do you want to start the animation +*/ + +void SP_props_decorBRUSH( gentity_t *self ) { + + trap_SetBrushModel( self, self->model ); + + SP_props_decoration( self ); + + if ( self->model2 ) { + self->s.modelindex2 = G_ModelIndex( self->model2 ); + } + +} + + +/*QUAKED props_decoration_scale (.6 .7 .7) (-8 -8 0) (8 8 16) STARTINVIS DEBRIS ANIMATE KEEPBLOCK TOUCHACTIVATE LOOPING STARTON + +"modelscale" - Scale multiplier (defaults to 1.0 and scales uniformly) +"modelscale_vec" - Set scale per-axis. Overrides "modelscale", so if you have both the "modelscale" is ignored + +"model2" will specify the model to load + +"noise" the looping sound entity is to make + +"type" type of debris ("glass", "wood", "metal", "ceramic", "rubble") default is "wood" +"count" how much debris ei. default 4 pieces + +you will need to specify the bounding box for the entity +"high" default is 4 +"wide" default is 4 + +"frames" how many frames of animation to play +"loop" when the animation is done start again on this frame +"startonframe" on what frame do you want to start the animation +*/ + +void SP_props_decor_Scale( gentity_t *ent ) { + + float scale[3] = {1,1,1}; + vec3_t scalevec; + + + SP_props_decoration( ent ); + + ent->s.eType = ET_GAMEMODEL; + + // look for general scaling + if ( G_SpawnFloat( "modelscale", "1", &scale[0] ) ) { + scale[2] = scale[1] = scale[0]; + } + + // look for axis specific scaling + if ( G_SpawnVector( "modelscale_vec", "1 1 1", &scalevec[0] ) ) { + VectorCopy( scalevec, scale ); + } + + // scale is stored in 'angles2' + VectorCopy( scale, ent->s.angles2 ); + + trap_LinkEntity( ent ); + +} + +/*QUAKED props_skyportal (.6 .7 .7) (-8 -8 0) (8 8 16) +"fov" for the skybox default is 90 +To have the portal sky fogged, enter any of the following values: +"fogcolor" (r g b) (values 0.0-1.0) +"fognear" distance from entity to start fogging +"fogfar" distance from entity that fog is opaque + +*/ +void SP_skyportal( gentity_t *ent ) { + char *fov; + vec3_t fogv; //----(SA) + int fogn; //----(SA) + int fogf; //----(SA) + int isfog = 0; // (SA) + + float fov_x; + + G_SpawnString( "fov", "90", &fov ); + fov_x = atof( fov ); + +//----(SA) modified + isfog += G_SpawnVector( "fogcolor", "0 0 0", fogv ); + isfog += G_SpawnInt( "fognear", "0", &fogn ); + isfog += G_SpawnInt( "fogfar", "300", &fogf ); + + trap_SetConfigstring( CS_SKYBOXORG, va( "%.2f %.2f %.2f %.1f %i %.2f %.2f %.2f %i %i", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], fov_x, (int)isfog, fogv[0], fogv[1], fogv[2], fogn, fogf ) ); +//----(SA) end +} + +/*QUAKED props_statue (.6 .3 .2) (-8 -8 0) (8 8 128) HURT DEBRIS ANIMATE KEEPBLOCK +"model2" will specify the model to load + +"noise" the sound entity is to make + +"type" type of debris ("glass", "wood", "metal", "ceramic", "rubble") default is "wood" +"count" how much debris ei. default 4 pieces + +you will need to specify the bounding box for the entity +"high" default is 4 +"wide" default is 4 + +"frames" how many frames of animation to play +"delay" how long of a delay before damage is inflicted ei. 0.5 sec or 2.7 sec + +"damage" amount of damage to be inflicted +*/ + +void props_statue_blocked( gentity_t *ent ) { + trace_t trace; + vec3_t start, end, mins, maxs; + vec3_t forward; + float dist; + gentity_t *traceEnt; + float grav = 128; + vec3_t kvel; + + if ( !Q_stricmp( ent->classname, "props_statueBRUSH" ) ) { + return; + } + + VectorCopy( ent->s.origin, start ); + start[2] += 24; + + VectorSet( mins, ent->r.mins[0], ent->r.mins[1], -23 ); + VectorSet( maxs, ent->r.maxs[0], ent->r.maxs[1], 23 ); + + AngleVectors( ent->r.currentAngles, forward, NULL, NULL ); + + VectorCopy( start, end ); + + dist = ( ( ent->r.maxs[2] + 16 ) / ent->count2 ) * ent->s.frame; + + VectorMA( end, dist, forward, end ); + + trap_Trace( &trace, start, mins, maxs, end, ent->s.number, MASK_SHOT ); + + if ( trace.surfaceFlags & SURF_NOIMPACT ) { // bogus test but just in case + return; + } + + traceEnt = &g_entities[ trace.entityNum ]; + + if ( traceEnt->takedamage && traceEnt->client ) { + G_Damage( traceEnt, ent, ent, NULL, trace.endpos, ent->damage, 0, MOD_CRUSH ); + + // TBD: push client back a bit + VectorScale( forward, grav, kvel ); + VectorAdd( traceEnt->client->ps.velocity, kvel, traceEnt->client->ps.velocity ); + + if ( !traceEnt->client->ps.pm_time ) { + int t; + + t = grav * 2; + if ( t < 50 ) { + t = 50; + } + if ( t > 200 ) { + t = 200; + } + traceEnt->client->ps.pm_time = t; + traceEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + + } else { + G_Damage( traceEnt, ent, ent, NULL, trace.endpos, 9999, 0, MOD_CRUSH ); + } + +} + +void props_statue_animate( gentity_t *ent ) { + + qboolean takeashot = qfalse; + + ent->s.frame++; + ent->s.eType = ET_GENERAL; + + if ( ent->s.frame > ent->count2 ) { + ent->s.frame = ent->count2; + ent->takedamage = qfalse; + } + + if ( ( ( ent->delay * 1000 ) + ent->timestamp ) > level.time ) { + ent->count = 0; + } else if ( ent->count == 5 ) { + takeashot = qtrue; + ent->count = 0; + } else { + ent->count++; + } + + if ( takeashot ) { + props_statue_blocked( ent ); + } + + if ( ent->s.frame < ent->count2 ) { + ent->nextthink = level.time + 50; + } +} + + +void props_statue_death( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + + ent->timestamp = level.time; + + G_AddEvent( ent, EV_GENERAL_SOUND, ent->noise_index ); + + if ( !( ent->spawnflags & 8 ) ) { + ent->clipmask = 0; + ent->r.contents = 0; + ent->s.eType = ET_GENERAL; + trap_LinkEntity( ent ); + } + + ent->takedamage = qfalse; + + G_UseTargets( ent, NULL ); + + if ( ent->spawnflags & 2 ) { + Spawn_Shard( ent, inflictor, ent->count, ent->key ); + } + + if ( ent->spawnflags & 4 ) { + ent->nextthink = level.time + 50; + ent->think = props_statue_animate; + return; + } + + G_FreeEntity( ent ); + +} + +void props_statue_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + props_statue_death( self, other, other, 9999, MOD_CRUSH ); +} + +void SP_props_statue( gentity_t *ent ) { + float light; + vec3_t color; + qboolean lightSet, colorSet; + char *sound; + char *type; + char *high; + char *wide; + char *frames; + float height; + float width; + float num_frames; + + if ( ent->model2 ) { + ent->s.modelindex = G_ModelIndex( ent->model2 ); + } + + if ( G_SpawnString( "noise", "100", &sound ) ) { + ent->noise_index = G_SoundIndex( sound ); + } + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + if ( lightSet || colorSet ) { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + ent->isProp = qtrue; + ent->takedamage = qtrue; + ent->die = props_statue_death; + + G_SpawnString( "type", "wood", &type ); + if ( !Q_stricmp( type,"wood" ) ) { + ent->key = 1; + } else if ( !Q_stricmp( type,"glass" ) ) { + ent->key = 0; + } else if ( !Q_stricmp( type,"metal" ) ) { + ent->key = 2; + } else if ( !Q_stricmp( type,"ceramic" ) ) { + ent->key = 3; + } else if ( !Q_stricmp( type, "rubble" ) ) { + ent->key = 4; + } + + G_SpawnString( "high", "0", &high ); + height = atof( high ); + if ( !height ) { + height = 4; + } + + G_SpawnString( "wide", "0", &wide ); + width = atof( wide ); + + if ( !width ) { + width = 4; + } + + width /= 2; + + if ( Q_stricmp( ent->classname, "props_statueBRUSH" ) ) { + VectorSet( ent->r.mins, -width, -width, 0 ); + VectorSet( ent->r.maxs, width, width, height ); + } + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->s.eType = ET_MOVER; + + G_SpawnString( "frames", "0", &frames ); + num_frames = atof( frames ); + + ent->count2 = num_frames; + + ent->touch = props_statue_touch; + + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !ent->damage ) { + ent->damage = 1; + } + + trap_LinkEntity( ent ); +} + + +/*QUAKED props_statueBRUSH (.6 .3 .2) ? HURT DEBRIS ANIMATE KEEPBLOCK +needs an origin brush + +"model2" will specify the model to load + +"noise" the sound entity is to make + +"type" type of debris ("glass", "wood", "metal", "ceramic", "rubble") default is "wood" +"count" how much debris ei. default 4 pieces + +"frames" how many frames of animation to play +"delay" how long of a delay before damage is inflicted ei. 0.5 sec or 2.7 sec + +THE damage has been disabled at the moment +"damage" amount of damage to be inflicted + +*/ + +void SP_props_statueBRUSH( gentity_t *self ) { + + trap_SetBrushModel( self, self->model ); + + SP_props_statue( self ); + + if ( self->model2 ) { + self->s.modelindex2 = G_ModelIndex( self->model2 ); + } + + if ( !( self->health ) ) { + self->health = 6; + } + +} + +////////////////////////////////////////////////// +// Lockers +////////////////////////////////////////////////// + +#define LOCKER_ANIM_USEEND 5 +#define LOCKER_ANIM_DEATHSTART 6 +#define LOCKER_ANIM_DEATHEND 11 + +////////////////////////////////////////////////// +void init_locker( gentity_t *ent ); +void props_locker_death( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); +////////////////////////////////////////////////// +#define MAX_LOCKER_DEBRIS 5 + +int locker_debris_model[MAX_LOCKER_DEBRIS]; +////////////////////////////////////////////////// + +void Spawn_Junk( gentity_t *ent ) { + gentity_t *sfx; + vec3_t dir, start; + + VectorCopy( ent->r.currentOrigin, start ); + + start[0] += crandom() * 32; + start[1] += crandom() * 32; + start[2] += 16; + + VectorSubtract( start, ent->r.currentOrigin, dir ); + VectorNormalize( dir ); + + sfx = G_Spawn(); + + G_SetOrigin( sfx, start ); + G_SetAngle( sfx, ent->r.currentAngles ); + + G_AddEvent( sfx, EV_JUNK, DirToByte( dir ) ); + + sfx->think = G_FreeEntity; + + sfx->nextthink = level.time + 1000; + + trap_LinkEntity( sfx ); +} + +/* +============== +props_locker_endrattle +============== +*/ +void props_locker_endrattle( gentity_t *ent ) { + ent->s.frame = 0; // idle + ent->think = 0; + ent->nextthink = 0; + ent->delay = 0; +} + + +void props_locker_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( !ent->delay ) { + ent->s.frame = 1; // rattle when pain starts + } + ent->delay = 1; + ent->think = props_locker_endrattle; + ent->nextthink = level.time + 1000; // rattle a sec +} + +void props_locker_pain( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ) { + props_locker_use( ent, attacker, attacker ); +} + + +void init_locker( gentity_t *ent ) { + ent->isProp = qtrue; + ent->takedamage = qtrue; + ent->delay = 0; + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + // TODO: change from 'trap' to something else. 'trap' is a misnomer. it's actually used for other stuff too + ent->s.eType = ET_TRAP; + + ent->s.frame = 0; // closed animation + + ent->count2 = LOCKER_ANIM_DEATHEND; + + ent->die = props_locker_death; + ent->use = props_locker_use; // trying it rattles the lock (could also allow 'waking' from trigger) + ent->pain = props_locker_pain; + + // drop origin down 8 so the designer can put the box entity on the floor rather than /in/ the floor + // remove if you get a new model from jason w/ the origin moved up 8 + ent->s.origin[2] -= 8; + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( !( ent->health ) ) { + ent->health = 1; + } + + trap_LinkEntity( ent ); + +} + +void props_locker_spawn_item( gentity_t *ent ) { + gitem_t *item; + gentity_t *drop = NULL; + + item = BG_FindItem( ent->spawnitem ); + + if ( !item ) { // empty + return; + } + +// drop = Drop_Item (ent, item, 0, qtrue); + drop = LaunchItem( item, ent->r.currentOrigin, tv( 0, 0, 20 ), ent->s.number ); + + + if ( !drop ) { + G_Printf( "-----> WARNING <-------\n" ); + G_Printf( "props_locker_spawn_item at %s failed!\n", vtos( ent->r.currentOrigin ) ); + } +} + +extern qhandle_t trap_R_GetShaderFromModel( qhandle_t modelid, int surfnum, int withlightmap ); + +void props_locker_mass( gentity_t *ent ) { + gentity_t *tent; + vec3_t start; + vec3_t dir; + + VectorCopy( ent->r.currentOrigin, start ); + + start[0] += crandom() * 32; + start[1] += crandom() * 32; + start[2] += 16; + + VectorSubtract( start, ent->r.currentOrigin, dir ); + VectorNormalize( dir ); + + tent = G_TempEntity( ent->r.currentOrigin, EV_EFFECT ); + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + VectorCopy( dir, tent->s.angles2 ); + + tent->s.dl_intensity = 0; + + trap_SetConfigstring( CS_TARGETEFFECT, ent->dl_shader ); //----(SA) allow shader to be set from entity + + tent->s.frame = ent->key; + + tent->s.eventParm = 8; + tent->s.density = 100; +} + +/*QUAKED props_footlocker (.6 .7 .3) (-12 -21 -12) (12 21 12) ? NO_JUNK +"noise" the sound entity is to make upon death +the default sounds are: + "wood" - "sound/world/boardbreak.wav" + "glass" - "sound/world/glassbreak.wav" + "metal" - "sound/world/metalbreak.wav" + "gibs" - "sound/player/gibsplit1.wav" + "brick" - "sound/world/brickfall.wav" + "stone" - "sound/world/stonefall.wav" + "fabric" - "sound/world/metalbreak.wav" // (SA) temp + +"locknoise" the locked sound to play +"wait" denotes how long the wait is going to be before the locked sound is played again default is 1 sec +"health" default is 1 + +"spawnitem" - will spawn this item unpon death use the pickup_name ei 9mm + +"type" - type of debris ("glass", "wood", "metal", "gibs", "brick", "rock", "fabric") default is "wood" +"mass" - defaults to 75. This determines how much debris is emitted. You get one large chunk per 100 of mass (up to 8) and one small chunk per 25 of mass (up to 16). So 800 gives the most. + +"dl_shader" needs to be set the same way as a target_effect + +TBD: the spawning of junk still pending and animation when used + +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/mapobjects/furniture/footlocker.md3" +*/ + +/* +============== +props_locker_death +============== +*/ +void props_locker_death( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + ent->takedamage = qfalse; + ent->s.frame = 2; // opening animation + ent->think = 0; + ent->nextthink = 0; + + trap_UnlinkEntity( ent ); + ent->r.maxs[2] = 11; // (SA) make the dead bb half height so the item can look like it's sitting inside + props_locker_spawn_item( ent ); + trap_LinkEntity( ent ); + +} + + +void SP_props_footlocker( gentity_t *self ) { + char *type; + char *sound; + char *locked; + int mass; + +// trap_SetBrushModel (self, self->model); + + // (SA) if angle is xx or yy, rotate the bounding box 90 deg to match + // NOTE: Non axis-aligned orientation not allowed. It will work, but + // the bounding box will not exactly match the model. + if ( self->s.angles[1] == 90 || self->s.angles[1] == 270 ) { + VectorSet( self->r.mins, -21, -12, 0 ); + VectorSet( self->r.maxs, 21, 12, 24 ); + } else { + VectorSet( self->r.mins, -12, -21, 0 ); + VectorSet( self->r.maxs, 12, 21, 24 ); + } + + self->s.modelindex = G_ModelIndex( "models/mapobjects/furniture/footlocker.md3" ); + + if ( G_SpawnString( "noise", "NOSOUND", &sound ) ) { + self->noise_index = G_SoundIndex( sound ); + } + + if ( G_SpawnString( "locknoise", "NOSOUND", &locked ) ) { + self->soundPos1 = G_SoundIndex( locked ); + } + + if ( !( self->wait ) ) { + self->wait = 1000; + } else { + self->wait *= 1000; + } + + if ( G_SpawnInt( "mass", "75", &mass ) ) { + self->count = mass; + } else { + self->count = 75; + } + + if ( G_SpawnString( "type", "wood", &type ) ) { + if ( !Q_stricmp( type,"wood" ) ) { + self->key = 0; + } else if ( !Q_stricmp( type,"glass" ) ) { + self->key = 1; + } else if ( !Q_stricmp( type,"metal" ) ) { + self->key = 2; + } else if ( !Q_stricmp( type,"gibs" ) ) { + self->key = 3; + } else if ( !Q_stricmp( type,"brick" ) ) { + self->key = 4; + } else if ( !Q_stricmp( type,"rock" ) ) { + self->key = 5; + } else if ( !Q_stricmp( type,"fabric" ) ) { + self->key = 6; + } + } else { + self->key = 0; + } + + self->delay = level.time + self->wait; + + init_locker( self ); + +} + +/*QUAKED props_flamethrower (.6 .7 .3) (-8 -8 -8) (8 8 8) TRACKING +the effect occurs when this entity is used +needs to aim at a info_notnull +"duration" how long the effect is going to last for example 1.2 sec 2.7 sec +"random" how long of a random variance so the effect isnt exactly the same each time for example 1.1 sec or 0.2 sec +"size" valid ranges are 1.0 to 0.1 +*/ +void props_flamethrower_think( gentity_t *ent ) { + vec3_t vec, angles; + gentity_t *target = NULL; + + if ( ent->spawnflags & 1 ) { // tracking + if ( ent->target ) { + target = G_Find( NULL, FOFS( targetname ), ent->target ); + } + + if ( !target ) { +// VectorSet (ent->r.currentAngles, 0, 0, 1); // (SA) wasn't working + VectorSet( ent->s.apos.trBase, 0, 0, 1 ); + } else + { + VectorSubtract( target->s.origin, ent->s.origin, vec ); + VectorNormalize( vec ); + vectoangles( vec, angles ); +// VectorCopy (angles, ent->r.currentAngles); // (SA) wasn't working + VectorCopy( angles, ent->s.apos.trBase ); + } + } + + if ( ( ent->timestamp + ent->duration ) > level.time ) { + G_AddEvent( ent, EV_FLAMETHROWER_EFFECT, 0 ); + + ent->nextthink = level.time + 50; + + { + int rval; + int rnd; + + if ( ent->random ) { + rval = ent->random * 1000; + rnd = rand() % rval; + } else { + rnd = 0; + } + + ent->timestamp = level.time + rnd; + ent->nextthink = ent->timestamp + 50; + } + } + +} + +void props_flamethrower_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + int rval; + int rnd; + + if ( ent->spawnflags & 2 ) { + ent->spawnflags &= ~2; + ent->think = NULL; // (SA) wasn't working + ent->nextthink = 0; + return; + } else + { + ent->spawnflags |= 2; + } + + if ( ent->random ) { + rval = ent->random * 1000; + rnd = rand() % rval; + } else { + rnd = 0; + } + + ent->timestamp = level.time + rnd; + + ent->think = props_flamethrower_think; + ent->nextthink = level.time + 50; + +} + +void props_flamethrower_init( gentity_t *ent ) { + gentity_t *target = NULL; + vec3_t vec; + vec3_t angles; + + if ( ent->target ) { + target = G_Find( NULL, FOFS( targetname ), ent->target ); + } + + if ( !target ) { +// VectorSet (ent->r.currentAngles, 0, 0, 1); //----(SA) + VectorSet( ent->s.apos.trBase, 0, 0, 1 ); + } else + { + VectorSubtract( target->s.origin, ent->s.origin, vec ); + VectorNormalize( vec ); + vectoangles( vec, angles ); +// VectorCopy (angles, ent->r.currentAngles); //----(SA) + VectorCopy( angles, ent->s.apos.trBase ); + } + + trap_LinkEntity( ent ); + +} + +void SP_props_flamethrower( gentity_t *ent ) { + char *size; + float dsize; + + ent->think = props_flamethrower_init; + ent->nextthink = level.time + 50; + ent->use = props_flamethrower_use; + + G_SetOrigin( ent, ent->s.origin ); + + if ( !( ent->duration ) ) { + ent->duration = 1000; + } else { + ent->duration *= 1000; + } + + + G_SpawnString( "size", "0", &size ); + dsize = atof( size ); + if ( !dsize ) { + dsize = 1; + } + ent->accuracy = dsize; + +} diff --git a/src/game/g_public.h b/src/game/g_public.h new file mode 100644 index 0000000..f0487ad --- /dev/null +++ b/src/game/g_public.h @@ -0,0 +1,470 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// g_public.h -- game module information visible to server + +#define GAME_API_VERSION 8 + +// entity->svFlags +// the server does not know how to interpret most of the values +// in entityStates (level eType), so the game must explicitly flag +// special server behaviors +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_VISDUMMY 0x00000004 // this ent is a "visibility dummy" and needs it's master to be sent to clients that can see it even if they can't see the master ent +#define SVF_BOT 0x00000008 +// Wolfenstein +#define SVF_CASTAI 0x00000010 +// done. +#define SVF_BROADCAST 0x00000020 // send to all connected clients +#define SVF_PORTAL 0x00000040 // merge a second pvs at origin2 into snapshots +#define SVF_USE_CURRENT_ORIGIN 0x00000080 // entity->r.currentOrigin instead of entity->s.origin + // for link position (missiles and movers) +// Ridah +#define SVF_NOFOOTSTEPS 0x00000100 +// done. +// MrE: +#define SVF_CAPSULE 0x00000200 // use capsule for collision detection + +#define SVF_VISDUMMY_MULTIPLE 0x00000400 // so that one vis dummy can add to snapshot multiple speakers + +// recent id changes +#define SVF_SINGLECLIENT 0x00000800 // only send to a single client (entityShared_t->singleClient) +#define SVF_NOSERVERINFO 0x00001000 // don't send CS_SERVERINFO updates to this client + // so that it can be updated for ping tools without + // lagging clients +#define SVF_NOTSINGLECLIENT 0x00002000 // send entity to everyone but one client + // (entityShared_t->singleClient) + +//=============================================================== + + +typedef struct { + entityState_t s; // communicated by server to clients + + qboolean linked; // qfalse if not in any good cluster + int linkcount; + + int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc + int singleClient; // only send to this client when SVF_SINGLECLIENT is set + + qboolean bmodel; // if false, assume an explicit mins / maxs bounding box + // only set by trap_SetBrushModel + vec3_t mins, maxs; + int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc + // a non-solid entity should set to 0 + + vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation + + // currentOrigin will be used for all collision detection and world linking. + // it will not necessarily be the same as the trajectory evaluation for the current + // time, because each entity must be moved one at a time after time is advanced + // to avoid simultanious collision issues + vec3_t currentOrigin; + vec3_t currentAngles; + + // when a trace call is made and passEntityNum != ENTITYNUM_NONE, + // an ent will be excluded from testing if: + // ent->s.number == passEntityNum (don't interact with self) + // ent->s.ownerNum = passEntityNum (don't interact with your own missiles) + // entity[ent->s.ownerNum].ownerNum = passEntityNum (don't interact with other missiles from owner) + int ownerNum; + int eventTime; + + int worldflags; // DHM - Nerve +} entityShared_t; + + + +// the server looks at a sharedEntity, which is the start of the game's gentity_t structure +typedef struct { + entityState_t s; // communicated by server to clients + entityShared_t r; // shared by both the server system and game +} sharedEntity_t; + + + +//=============================================================== + +// +// system traps provided by the main engine +// +typedef enum { + //============== general Quake services ================== + + G_PRINT, // ( const char *string ); + // print message on the local console + + G_ERROR, // ( const char *string ); + // abort the game + + G_MILLISECONDS, // ( void ); + // get current time for profiling reasons + // this should NOT be used for any game related tasks, + // because it is not journaled + + // console variable interaction + G_CVAR_REGISTER, // ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); + G_CVAR_UPDATE, // ( vmCvar_t *vmCvar ); + G_CVAR_SET, // ( const char *var_name, const char *value ); + G_CVAR_VARIABLE_INTEGER_VALUE, // ( const char *var_name ); + + G_CVAR_VARIABLE_STRING_BUFFER, // ( const char *var_name, char *buffer, int bufsize ); + + G_ARGC, // ( void ); + // ClientCommand and ServerCommand parameter access + + G_ARGV, // ( int n, char *buffer, int bufferLength ); + + G_FS_FOPEN_FILE, // ( const char *qpath, fileHandle_t *file, fsMode_t mode ); + G_FS_READ, // ( void *buffer, int len, fileHandle_t f ); + G_FS_WRITE, // ( const void *buffer, int len, fileHandle_t f ); + G_FS_RENAME, + G_FS_FCLOSE_FILE, // ( fileHandle_t f ); + + G_SEND_CONSOLE_COMMAND, // ( const char *text ); + // add commands to the console as if they were typed in + // for map changing, etc + + + //=========== server specific functionality ============= + + G_LOCATE_GAME_DATA, // ( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, + // playerState_t *clients, int sizeofGameClient ); + // the game needs to let the server system know where and how big the gentities + // are, so it can look at them directly without going through an interface + + G_DROP_CLIENT, // ( int clientNum, const char *reason ); + // kick a client off the server with a message + + G_SEND_SERVER_COMMAND, // ( int clientNum, const char *fmt, ... ); + // reliably sends a command string to be interpreted by the given + // client. If clientNum is -1, it will be sent to all clients + + G_SET_CONFIGSTRING, // ( int num, const char *string ); + // config strings hold all the index strings, and various other information + // that is reliably communicated to all clients + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + // All confgstrings are cleared at each level start. + + G_GET_CONFIGSTRING, // ( int num, char *buffer, int bufferSize ); + + G_GET_USERINFO, // ( int num, char *buffer, int bufferSize ); + // userinfo strings are maintained by the server system, so they + // are persistant across level loads, while all other game visible + // data is completely reset + + G_SET_USERINFO, // ( int num, const char *buffer ); + + G_GET_SERVERINFO, // ( char *buffer, int bufferSize ); + // the serverinfo info string has all the cvars visible to server browsers + + G_SET_BRUSH_MODEL, // ( gentity_t *ent, const char *name ); + // sets mins and maxs based on the brushmodel name + + G_TRACE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); + // collision detection against all linked entities + + G_POINT_CONTENTS, // ( const vec3_t point, int passEntityNum ); + // point contents against all linked entities + + G_IN_PVS, // ( const vec3_t p1, const vec3_t p2 ); + + G_IN_PVS_IGNORE_PORTALS, // ( const vec3_t p1, const vec3_t p2 ); + + G_ADJUST_AREA_PORTAL_STATE, // ( gentity_t *ent, qboolean open ); + + G_AREAS_CONNECTED, // ( int area1, int area2 ); + + G_LINKENTITY, // ( gentity_t *ent ); + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + + G_UNLINKENTITY, // ( gentity_t *ent ); + // call before removing an interactive entity + + G_ENTITIES_IN_BOX, // ( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount ); + // EntitiesInBox will return brush models based on their bounding box, + // so exact determination must still be done with EntityContact + + G_ENTITY_CONTACT, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); + // perform an exact check against inline brush models of non-square shape + + // access for bots to get and free a server client (FIXME?) + G_BOT_ALLOCATE_CLIENT, // ( void ); + + G_BOT_FREE_CLIENT, // ( int clientNum ); + + G_GET_USERCMD, // ( int clientNum, usercmd_t *cmd ) + + G_GET_ENTITY_TOKEN, // qboolean ( char *buffer, int bufferSize ) + // Retrieves the next string token from the entity spawn text, returning + // false when all tokens have been parsed. + // This should only be done at GAME_INIT time. + + G_FS_GETFILELIST, + G_DEBUG_POLYGON_CREATE, + G_DEBUG_POLYGON_DELETE, + G_REAL_TIME, + G_SNAPVECTOR, +// MrE: + + G_TRACECAPSULE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); + // collision detection using capsule against all linked entities + + G_ENTITY_CONTACTCAPSULE, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); + // perform an exact check against inline brush models of non-square shape +// done. + + G_GETTAG, + + BOTLIB_SETUP = 200, // ( void ); + BOTLIB_SHUTDOWN, // ( void ); + BOTLIB_LIBVAR_SET, + BOTLIB_LIBVAR_GET, + BOTLIB_PC_ADD_GLOBAL_DEFINE, + BOTLIB_START_FRAME, + BOTLIB_LOAD_MAP, + BOTLIB_UPDATENTITY, + BOTLIB_TEST, + + BOTLIB_GET_SNAPSHOT_ENTITY, // ( int client, int ent ); + BOTLIB_GET_CONSOLE_MESSAGE, // ( int client, char *message, int size ); + BOTLIB_USER_COMMAND, // ( int client, usercmd_t *ucmd ); + + BOTLIB_AAS_ENTITY_VISIBLE = 300, //FIXME: remove + BOTLIB_AAS_IN_FIELD_OF_VISION, //FIXME: remove + BOTLIB_AAS_VISIBLE_CLIENTS, //FIXME: remove + BOTLIB_AAS_ENTITY_INFO, + + BOTLIB_AAS_INITIALIZED, + BOTLIB_AAS_PRESENCE_TYPE_BOUNDING_BOX, + BOTLIB_AAS_TIME, + + // Ridah + BOTLIB_AAS_SETCURRENTWORLD, + // done. + + BOTLIB_AAS_POINT_AREA_NUM, + BOTLIB_AAS_TRACE_AREAS, + + BOTLIB_AAS_POINT_CONTENTS, + BOTLIB_AAS_NEXT_BSP_ENTITY, + BOTLIB_AAS_VALUE_FOR_BSP_EPAIR_KEY, + BOTLIB_AAS_VECTOR_FOR_BSP_EPAIR_KEY, + BOTLIB_AAS_FLOAT_FOR_BSP_EPAIR_KEY, + BOTLIB_AAS_INT_FOR_BSP_EPAIR_KEY, + + BOTLIB_AAS_AREA_REACHABILITY, + + BOTLIB_AAS_AREA_TRAVEL_TIME_TO_GOAL_AREA, + + BOTLIB_AAS_SWIMMING, + BOTLIB_AAS_PREDICT_CLIENT_MOVEMENT, + + // Ridah, route-tables + BOTLIB_AAS_RT_SHOWROUTE, + BOTLIB_AAS_RT_GETHIDEPOS, + BOTLIB_AAS_FINDATTACKSPOTWITHINRANGE, + BOTLIB_AAS_SETAASBLOCKINGENTITY, + // done. + + BOTLIB_EA_SAY = 400, + BOTLIB_EA_SAY_TEAM, + BOTLIB_EA_USE_ITEM, + BOTLIB_EA_DROP_ITEM, + BOTLIB_EA_USE_INV, + BOTLIB_EA_DROP_INV, + BOTLIB_EA_GESTURE, + BOTLIB_EA_COMMAND, + + BOTLIB_EA_SELECT_WEAPON, + BOTLIB_EA_TALK, + BOTLIB_EA_ATTACK, + BOTLIB_EA_RELOAD, + BOTLIB_EA_USE, + BOTLIB_EA_RESPAWN, + BOTLIB_EA_JUMP, + BOTLIB_EA_DELAYED_JUMP, + BOTLIB_EA_CROUCH, + BOTLIB_EA_MOVE_UP, + BOTLIB_EA_MOVE_DOWN, + BOTLIB_EA_MOVE_FORWARD, + BOTLIB_EA_MOVE_BACK, + BOTLIB_EA_MOVE_LEFT, + BOTLIB_EA_MOVE_RIGHT, + BOTLIB_EA_MOVE, + BOTLIB_EA_VIEW, + + BOTLIB_EA_END_REGULAR, + BOTLIB_EA_GET_INPUT, + BOTLIB_EA_RESET_INPUT, + + + BOTLIB_AI_LOAD_CHARACTER = 500, + BOTLIB_AI_FREE_CHARACTER, + BOTLIB_AI_CHARACTERISTIC_FLOAT, + BOTLIB_AI_CHARACTERISTIC_BFLOAT, + BOTLIB_AI_CHARACTERISTIC_INTEGER, + BOTLIB_AI_CHARACTERISTIC_BINTEGER, + BOTLIB_AI_CHARACTERISTIC_STRING, + + BOTLIB_AI_ALLOC_CHAT_STATE, + BOTLIB_AI_FREE_CHAT_STATE, + BOTLIB_AI_QUEUE_CONSOLE_MESSAGE, + BOTLIB_AI_REMOVE_CONSOLE_MESSAGE, + BOTLIB_AI_NEXT_CONSOLE_MESSAGE, + BOTLIB_AI_NUM_CONSOLE_MESSAGE, + BOTLIB_AI_INITIAL_CHAT, + BOTLIB_AI_REPLY_CHAT, + BOTLIB_AI_CHAT_LENGTH, + BOTLIB_AI_ENTER_CHAT, + BOTLIB_AI_STRING_CONTAINS, + BOTLIB_AI_FIND_MATCH, + BOTLIB_AI_MATCH_VARIABLE, + BOTLIB_AI_UNIFY_WHITE_SPACES, + BOTLIB_AI_REPLACE_SYNONYMS, + BOTLIB_AI_LOAD_CHAT_FILE, + BOTLIB_AI_SET_CHAT_GENDER, + BOTLIB_AI_SET_CHAT_NAME, + + BOTLIB_AI_RESET_GOAL_STATE, + BOTLIB_AI_RESET_AVOID_GOALS, + BOTLIB_AI_PUSH_GOAL, + BOTLIB_AI_POP_GOAL, + BOTLIB_AI_EMPTY_GOAL_STACK, + BOTLIB_AI_DUMP_AVOID_GOALS, + BOTLIB_AI_DUMP_GOAL_STACK, + BOTLIB_AI_GOAL_NAME, + BOTLIB_AI_GET_TOP_GOAL, + BOTLIB_AI_GET_SECOND_GOAL, + BOTLIB_AI_CHOOSE_LTG_ITEM, + BOTLIB_AI_CHOOSE_NBG_ITEM, + BOTLIB_AI_TOUCHING_GOAL, + BOTLIB_AI_ITEM_GOAL_IN_VIS_BUT_NOT_VISIBLE, + BOTLIB_AI_GET_LEVEL_ITEM_GOAL, + BOTLIB_AI_AVOID_GOAL_TIME, + BOTLIB_AI_INIT_LEVEL_ITEMS, + BOTLIB_AI_UPDATE_ENTITY_ITEMS, + BOTLIB_AI_LOAD_ITEM_WEIGHTS, + BOTLIB_AI_FREE_ITEM_WEIGHTS, + BOTLIB_AI_SAVE_GOAL_FUZZY_LOGIC, + BOTLIB_AI_ALLOC_GOAL_STATE, + BOTLIB_AI_FREE_GOAL_STATE, + + BOTLIB_AI_RESET_MOVE_STATE, + BOTLIB_AI_MOVE_TO_GOAL, + BOTLIB_AI_MOVE_IN_DIRECTION, + BOTLIB_AI_RESET_AVOID_REACH, + BOTLIB_AI_RESET_LAST_AVOID_REACH, + BOTLIB_AI_REACHABILITY_AREA, + BOTLIB_AI_MOVEMENT_VIEW_TARGET, + BOTLIB_AI_ALLOC_MOVE_STATE, + BOTLIB_AI_FREE_MOVE_STATE, + BOTLIB_AI_INIT_MOVE_STATE, + // Ridah + BOTLIB_AI_INIT_AVOID_REACH, + // done. + + BOTLIB_AI_CHOOSE_BEST_FIGHT_WEAPON, + BOTLIB_AI_GET_WEAPON_INFO, + BOTLIB_AI_LOAD_WEAPON_WEIGHTS, + BOTLIB_AI_ALLOC_WEAPON_STATE, + BOTLIB_AI_FREE_WEAPON_STATE, + BOTLIB_AI_RESET_WEAPON_STATE, + + BOTLIB_AI_GENETIC_PARENTS_AND_CHILD_SELECTION, + BOTLIB_AI_INTERBREED_GOAL_FUZZY_LOGIC, + BOTLIB_AI_MUTATE_GOAL_FUZZY_LOGIC, + BOTLIB_AI_GET_NEXT_CAMP_SPOT_GOAL, + BOTLIB_AI_GET_MAP_LOCATION_GOAL, + BOTLIB_AI_NUM_INITIAL_CHATS, + BOTLIB_AI_GET_CHAT_MESSAGE, + BOTLIB_AI_REMOVE_FROM_AVOID_GOALS, + BOTLIB_AI_PREDICT_VISIBLE_POSITION, + + BOTLIB_AI_SET_AVOID_GOAL_TIME, + BOTLIB_AI_ADD_AVOID_SPOT, + BOTLIB_AAS_ALTERNATIVE_ROUTE_GOAL, + BOTLIB_AAS_PREDICT_ROUTE, + BOTLIB_AAS_POINT_REACHABILITY_AREA_INDEX, + + BOTLIB_PC_LOAD_SOURCE, + BOTLIB_PC_FREE_SOURCE, + BOTLIB_PC_READ_TOKEN, + BOTLIB_PC_SOURCE_FILE_AND_LINE + +} gameImport_t; + + +// +// functions exported by the game subsystem +// +typedef enum { + GAME_INIT, // ( int levelTime, int randomSeed, int restart ); + // init and shutdown will be called every single level + // The game should call G_GET_ENTITY_TOKEN to parse through all the + // entity configuration text and spawn gentities. + + GAME_SHUTDOWN, // (void); + + GAME_CLIENT_CONNECT, // ( int clientNum, qboolean firstTime, qboolean isBot ); + // return NULL if the client is allowed to connect, otherwise return + // a text string with the reason for denial + + GAME_CLIENT_BEGIN, // ( int clientNum ); + + GAME_CLIENT_USERINFO_CHANGED, // ( int clientNum ); + + GAME_CLIENT_DISCONNECT, // ( int clientNum ); + + GAME_CLIENT_COMMAND, // ( int clientNum ); + + GAME_CLIENT_THINK, // ( int clientNum ); + + GAME_RUN_FRAME, // ( int levelTime ); + + GAME_CONSOLE_COMMAND, // ( void ); + // ConsoleCommand will be called when a command has been issued + // that is not recognized as a builtin function. + // The game can issue trap_argc() / trap_argv() commands to get the command + // and parameters. Return qfalse if the game doesn't recognize it as a command. + + BOTAI_START_FRAME // ( int time ); + + // Ridah, Cast AI + ,AICAST_VISIBLEFROMPOS + ,AICAST_CHECKATTACKATPOS + // done. + + ,GAME_RETRIEVE_MOVESPEEDS_FROM_CLIENT + +} gameExport_t; + diff --git a/src/game/g_save.c b/src/game/g_save.c new file mode 100644 index 0000000..4153ee6 --- /dev/null +++ b/src/game/g_save.c @@ -0,0 +1,1286 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: g_save.c + * + */ + +// Arnout: removed for multiplayer +#if 0 + +#include "../game/g_local.h" +#include "../game/q_shared.h" +#include "../game/botlib.h" //bot lib interface +#include "../game/be_aas.h" +#include "../game/be_ea.h" +#include "../game/be_ai_gen.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../botai/botai.h" //bot ai interface + +#include "ai_cast.h" + +/* +Wolf savegame system. + +Using the "checkpoint" system, we only need to save at specific locations, but various entities +may have changed behind us, so therefore we need to save as much as possible, but without going +overboard. + +For now, everything is saved from the entity, client and cast_state structures, except the fields +defined in the ignoreField structures below. Any pointer fields need to be specified in the +saveField structures below. + +!! NOTE: when working on Wolf patches, make sure you only add fields to the very end of the three + main structures saved here (entity, client, cast_state). If any fields are inserted in the middle + of these structures, savegames will become corrupted, and there is no way of checking for this, + so it'll just crash. +*/ + +#define SAVE_VERSION 7 + +typedef enum { + F_NONE, + F_STRING, + F_ENTITY, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_FUNCTION +} saveFieldtype_t; + +typedef struct { + int ofs; + saveFieldtype_t type; +} saveField_t; + +//....................................................................................... +// these are the fields that cannot be saved directly, so they need to be converted +static saveField_t gentityFields[] = { + {FOFS( client ), F_CLIENT}, + {FOFS( classname ), F_STRING}, + {FOFS( model ), F_STRING}, + {FOFS( model2 ), F_STRING}, + {FOFS( parent ), F_ENTITY}, + {FOFS( nextTrain ), F_ENTITY}, + {FOFS( prevTrain ), F_ENTITY}, + {FOFS( message ), F_STRING}, + {FOFS( target ), F_STRING}, + {FOFS( targetname ), F_STRING}, + {FOFS( team ), F_STRING}, + {FOFS( target_ent ), F_ENTITY}, + {FOFS( think ), F_FUNCTION}, + {FOFS( reached ), F_FUNCTION}, + {FOFS( blocked ), F_FUNCTION}, + {FOFS( touch ), F_FUNCTION}, + {FOFS( use ), F_FUNCTION}, + {FOFS( pain ), F_FUNCTION}, + {FOFS( die ), F_FUNCTION}, + {FOFS( chain ), F_ENTITY}, + {FOFS( enemy ), F_ENTITY}, + {FOFS( activator ), F_ENTITY}, + {FOFS( teamchain ), F_ENTITY}, + {FOFS( teammaster ), F_ENTITY}, + {FOFS( item ), F_ITEM}, + {FOFS( aiAttributes ),F_STRING}, + {FOFS( aiName ), F_STRING}, + {FOFS( AIScript_AlertEntity ), F_FUNCTION}, + {FOFS( aiSkin ), F_STRING}, + {FOFS( aihSkin ), F_STRING}, + {FOFS( dl_stylestring ), F_STRING}, + {FOFS( dl_shader ), F_STRING}, + {FOFS( melee ), F_ENTITY}, + {FOFS( spawnitem ), F_STRING}, + {FOFS( track ), F_STRING}, + {FOFS( scriptName ), F_STRING}, + {FOFS( scriptStatus.animatingParams ), F_STRING}, + {FOFS( tagName ), F_STRING}, + {FOFS( tagParent ), F_ENTITY}, + + {0, 0} +}; + +static saveField_t gclientFields[] = { + {CFOFS( hook ), F_ENTITY}, + + {0, 0} +}; + +static saveField_t castStateFields[] = { + {CSFOFS( aifunc ), F_FUNCTION}, + {CSFOFS( oldAifunc ), F_FUNCTION}, + {CSFOFS( painfunc ), F_FUNCTION}, + {CSFOFS( deathfunc ), F_FUNCTION}, + {CSFOFS( sightfunc ), F_FUNCTION}, + {CSFOFS( sightEnemy ), F_FUNCTION}, + {CSFOFS( sightFriend ), F_FUNCTION}, + {CSFOFS( activate ), F_FUNCTION}, + {CSFOFS( aifuncAttack1 ), F_FUNCTION}, + {CSFOFS( aifuncAttack2 ), F_FUNCTION}, + {CSFOFS( aifuncAttack3 ), F_FUNCTION}, + + {0, 0} +}; + +//....................................................................................... +// this is where we define fields or sections of structures that we should totally ignore +typedef struct { + int ofs; + int len; +} ignoreField_t; + +static ignoreField_t gentityIgnoreFields[] = { + // don't process events that have already occured before the game was saved + {FOFS( s.events[0] ), sizeof( int ) * MAX_EVENTS}, + {FOFS( s.eventParms[0] ), sizeof( int ) * MAX_EVENTS}, + {FOFS( s.eventSequence ), sizeof( int )}, + + {FOFS( numScriptEvents ), sizeof( int )}, + {FOFS( scriptEvents ), sizeof( g_script_event_t * ) }, // gets created upon parsing the script file, this is static while playing + + {0, 0} +}; + +static ignoreField_t gclientIgnoreFields[] = { + // don't process events that have already occured before the game was saved + {CFOFS( ps.events[0] ), sizeof( int ) * MAX_EVENTS}, + {CFOFS( ps.eventParms[0] ), sizeof( int ) * MAX_EVENTS}, + {CFOFS( ps.eventSequence ), sizeof( int )}, + {CFOFS( ps.oldEventSequence ),sizeof( int )}, + + {0, 0} +}; + +static ignoreField_t castStateIgnoreFields[] = { + {CSFOFS( bs ), sizeof( bot_state_t * )}, + {CSFOFS( numCastScriptEvents ), sizeof( int )}, + {CSFOFS( castScriptEvents ), sizeof( cast_script_event_t * ) }, // gets created upon parsing the script file, this is static while playing + {CSFOFS( castScriptStatusCurrent ), sizeof( cast_script_status_t * ) }, + {CSFOFS( weaponInfo ), sizeof( cast_weapon_info_t * )}, + {CSFOFS( totalPlayTime ), sizeof( int )}, + + {0, 0} +}; + +//....................................................................................... +// persistant data is optionally carried across level changes +// !! WARNING: cannot save pointer or string variables +typedef struct { + int ofs; + int len; +} persField_t; + +static persField_t gentityPersFields[] = { + {FOFS( health ), sizeof( int )}, + {0, 0} +}; + +static persField_t gclientPersFields[] = { + {CFOFS( ps.weapon ), sizeof( int )}, + {CFOFS( ps.ammo[0] ), sizeof( int ) * MAX_WEAPONS}, + {CFOFS( ps.ammoclip[0] ), sizeof( int ) * MAX_WEAPONS}, //----(SA) added for ammo in clip + {CFOFS( ps.persistant[0] ), sizeof( int ) * MAX_PERSISTANT}, + {CFOFS( ps.stats[0] ), sizeof( int ) * MAX_STATS}, + {CFOFS( ps.powerups[0] ), sizeof( int ) * MAX_POWERUPS}, + {0, 0} +}; + +static persField_t castStatePersFields[] = { + // TODO: will we be transporting AI's between levels? + // FIXME: if so, we can't save strings in here, so how are we going to create the new AI + // in the next level, with all his strings and pointers attached? + {0, 0} +}; + +//....................................................................................... +// this stores all functions in the game code +typedef struct { + char *funcStr; + byte *funcPtr; +} funcList_t; + +//----------------- +// MSVC likes to needlessly(?) warn about these defines, so disable certain warnings temporarily +#ifdef _WIN32 +#pragma warning( push ) +#pragma warning( disable : 4054 ) +#endif +//----------------- + +#include "g_func_decs.h" // declare all game functions + +funcList_t funcList[] = { + #include "g_funcs.h" +}; + +//----------------- +#ifdef _WIN32 +#pragma warning( pop ) // return previous warning state +#endif +//----------------- + + +//========================================================= + +/* +=============== +G_SaveWriteError +=============== +*/ +void G_SaveWriteError( void ) { +// TTimo +#ifdef __linux__ + G_Error( "Unable to save game.\n\nPlease check that you have at least 5mb free of disk space in your home directory." ); +#else + G_Error( "Unable to save game.\n\nPlease check that game drive has at least 5mb free space." ); +#endif +} + +//========================================================= + +funcList_t *G_FindFuncAtAddress( byte *adr ) { + int i; + + for ( i = 0; funcList[i].funcStr; i++ ) { + if ( funcList[i].funcPtr == adr ) { + return &funcList[i]; + } + } + return NULL; +} + +byte *G_FindFuncByName( char *name ) { + int i; + + for ( i = 0; funcList[i].funcStr; i++ ) { + if ( !strcmp( name, funcList[i].funcStr ) ) { + return funcList[i].funcPtr; + } + } + return NULL; +} + +void WriteField1( saveField_t *field, byte *base ) { + void *p; + int len; + int index; + funcList_t *func; + + p = ( void * )( base + field->ofs ); + switch ( field->type ) + { + case F_STRING: + if ( *(char **)p ) { + len = strlen( *(char **)p ) + 1; + } else { + len = 0; + } + *(int *)p = len; + break; + case F_ENTITY: + if ( *(gentity_t **)p == NULL ) { + index = -1; + } else { + index = *(gentity_t **)p - g_entities; + } + *(int *)p = index; + break; + case F_CLIENT: + if ( *(gclient_t **)p == NULL ) { + index = -1; + } else { + index = *(gclient_t **)p - level.clients; + } + *(int *)p = index; + break; + case F_ITEM: + if ( *(gitem_t **)p == NULL ) { + index = -1; + } else { + index = *(gitem_t **)p - bg_itemlist; + } + *(int *)p = index; + break; + + // match this with a function address in the function list, which is built using the + // "extractfuncs.bat" in the utils folder. We then save the string equivalent + // of the function. This effectively gives us cross-version save games. + case F_FUNCTION: + if ( *(byte **)p == NULL ) { + len = 0; + } else { + func = G_FindFuncAtAddress( *(byte **)p ); + if ( !func ) { + G_Error( "WriteField1: unknown function, cannot save game" ); + } + len = strlen( func->funcStr ) + 1; + } + *(int *)p = len; + break; + + default: + G_Error( "WriteField1: unknown field type" ); + } +} + + +void WriteField2( fileHandle_t f, saveField_t *field, byte *base ) { + int len; + void *p; + funcList_t *func; + + p = ( void * )( base + field->ofs ); + switch ( field->type ) + { + case F_STRING: + if ( *(char **)p ) { + len = strlen( *(char **)p ) + 1; + if ( !trap_FS_Write( *(char **)p, len, f ) ) { + G_SaveWriteError(); + } + } + break; + case F_FUNCTION: + if ( *(byte **)p ) { + func = G_FindFuncAtAddress( *(byte **)p ); + if ( !func ) { + G_Error( "WriteField1: unknown function, cannot save game" ); + } + len = strlen( func->funcStr ) + 1; + if ( !trap_FS_Write( func->funcStr, len, f ) ) { + G_SaveWriteError(); + } + } + break; + default: // TTimo F_NOE F_ENTITY F_ITEM F_CLIENT not handled in switch + break; + } +} + +void ReadField( fileHandle_t f, saveField_t *field, byte *base ) { + void *p; + int len; + int index; + char *funcStr; + + p = ( void * )( base + field->ofs ); + switch ( field->type ) + { + case F_STRING: + len = *(int *)p; + if ( !len ) { + *(char **)p = NULL; + } else + { + *(char **)p = G_Alloc( len ); + trap_FS_Read( *(char **)p, len, f ); + } + break; + case F_ENTITY: + index = *(int *)p; + if ( index == -1 ) { + *(gentity_t **)p = NULL; + } else { + *(gentity_t **)p = &g_entities[index]; + } + break; + case F_CLIENT: + index = *(int *)p; + if ( index == -1 ) { + *(gclient_t **)p = NULL; + } else { + *(gclient_t **)p = &level.clients[index]; + } + break; + case F_ITEM: + index = *(int *)p; + if ( index == -1 ) { + *(gitem_t **)p = NULL; + } else { + *(gitem_t **)p = &bg_itemlist[index]; + } + break; + + //relative to code segment + case F_FUNCTION: + len = *(int *)p; + if ( !len ) { + *(byte **)p = NULL; + } else + { + funcStr = G_Alloc( len ); + trap_FS_Read( funcStr, len, f ); + if ( !( *(byte **)p = G_FindFuncByName( funcStr ) ) ) { + G_Error( "ReadField: unknown function '%s'\ncannot load game", funcStr ); + } + } + break; + + default: + G_Error( "ReadField: unknown field type" ); + } +} + +//========================================================= + +/* +=============== +WriteClient +=============== +*/ +void WriteClient( fileHandle_t f, gclient_t *cl ) { + saveField_t *field; + gclient_t temp; + + // copy the structure across, then process the fields + temp = *cl; + + // first, kill all events (assume they have been processed) + memset( temp.ps.events, 0, sizeof( temp.ps.events ) ); + memset( temp.ps.eventParms, 0, sizeof( temp.ps.eventParms ) ); + temp.ps.eventSequence = 0; + + // change the pointers to lengths or indexes + for ( field = gclientFields ; field->type ; field++ ) + { + WriteField1( field, (byte *)&temp ); + } + + // write the block + if ( !trap_FS_Write( &temp, sizeof( temp ), f ) ) { + G_SaveWriteError(); + } + + // now write any allocated data following the edict + for ( field = gclientFields ; field->type ; field++ ) + { + WriteField2( f, field, (byte *)cl ); + } + +} + +/* +=============== +ReadClient +=============== +*/ +void ReadClient( fileHandle_t f, gclient_t *client, int size ) { + saveField_t *field; + ignoreField_t *ifield; + gclient_t temp; + gentity_t *ent; + + trap_FS_Read( &temp, size, f ); + + // convert any feilds back to the correct data + for ( field = gclientFields ; field->type ; field++ ) + { + ReadField( f, field, (byte *)&temp ); + } + + // backup any fields that we don't want to read in + for ( ifield = gclientIgnoreFields ; ifield->len ; ifield++ ) + { + memcpy( ( (byte *)&temp ) + ifield->ofs, ( (byte *)client ) + ifield->ofs, ifield->len ); + } + + // now copy the temp structure into the existing structure + memcpy( client, &temp, size ); + + // make sure they face the right way + //client->ps.pm_flags |= PMF_RESPAWNED; + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + ent = &g_entities[client->ps.clientNum]; + + // make sure they face the right way + // if it's the player, see if we need to put them at a mission marker + if ( !( ent->r.svFlags & SVF_CASTAI ) && ent->missionLevel > 0 ) { + gentity_t *trav; + + // TTimo gcc: suggest parentheses around assignment used as truth value + for ( trav = NULL; ( trav = G_Find( trav, FOFS( classname ), "info_player_checkpoint" ) ); ) { + if ( trav->missionLevel == ent->missionLevel && Distance( trav->s.origin, ent->r.currentOrigin ) < 800 ) { + G_SetOrigin( ent, trav->s.origin ); + VectorCopy( trav->s.origin, ent->client->ps.origin ); + + trap_GetUsercmd( ent->client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, trav->s.angles ); + break; + } + } + + if ( !trav ) { + trap_GetUsercmd( ent->client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + } else { + trap_GetUsercmd( ent->client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + trap_GetUsercmd( ent - g_entities, &ent->client->pers.cmd ); + ent->client->ps.commandTime = ent->client->pers.cmd.serverTime - 100; + ClientThink( ent - g_entities ); + + // tell the client to reset it's cgame stuff + if ( !( ent->r.svFlags & SVF_CASTAI ) ) { + trap_SendServerCommand( client->ps.clientNum, "map_restart\n" ); + } +} + +//========================================================= + +/* +=============== +WriteEntity +=============== +*/ +void WriteEntity( fileHandle_t f, gentity_t *ent ) { + saveField_t *field; + gentity_t temp; + + // copy the structure across, then process the fields + temp = *ent; + + // first, kill all events (assume they have been processed) + memset( temp.s.events, 0, sizeof( temp.s.events ) ); + memset( temp.s.eventParms, 0, sizeof( temp.s.eventParms ) ); + temp.s.eventSequence = 0; + + // change the pointers to lengths or indexes + for ( field = gentityFields ; field->type ; field++ ) + { + WriteField1( field, (byte *)&temp ); + } + + // write the block + if ( !trap_FS_Write( &temp, sizeof( temp ), f ) ) { + G_SaveWriteError(); + } + + // now write any allocated data following the edict + for ( field = gentityFields ; field->type ; field++ ) + { + WriteField2( f, field, (byte *)ent ); + } + +} + +/* +=============== +ReadEntity +=============== +*/ +void ReadEntity( fileHandle_t f, gentity_t *ent, int size ) { + saveField_t *field; + ignoreField_t *ifield; + gentity_t temp, backup; + + backup = *ent; + + trap_FS_Read( &temp, size, f ); + + // convert any feilds back to the correct data + for ( field = gentityFields ; field->type ; field++ ) + { + ReadField( f, field, (byte *)&temp ); + } + + // backup any fields that we don't want to read in + for ( ifield = gentityIgnoreFields ; ifield->len ; ifield++ ) + { + memcpy( ( (byte *)&temp ) + ifield->ofs, ( (byte *)ent ) + ifield->ofs, ifield->len ); + } + + // now copy the temp structure into the existing structure + memcpy( ent, &temp, size ); + + // notify server of changes in position/orientation + if ( ent->r.linked ) { + trap_LinkEntity( ent ); + } else { + trap_UnlinkEntity( ent ); + } + + // if this is a mover, check areaportals + if ( ent->s.eType == ET_MOVER && ent->moverState != backup.moverState ) { + if ( ent->teammaster == ent || !ent->teammaster ) { + if ( ent->moverState == MOVER_POS1ROTATE || ent->moverState == MOVER_POS1 ) { + // closed areaportal + trap_AdjustAreaPortalState( ent, qfalse ); + } else { // must be open + trap_AdjustAreaPortalState( ent, qtrue ); + } + } + } + + // check for blocking AAS at save time + if ( ent->AASblocking ) { + G_SetAASBlockingEntity( ent, qtrue ); + } + + // check for this being a tagconnect entity + if ( ent->tagName ) { + G_ProcessTagConnect( ent ); + } +} + +//========================================================= + +/* +=============== +WriteCastState +=============== +*/ +void WriteCastState( fileHandle_t f, cast_state_t *cs ) { + saveField_t *field; + cast_state_t temp; + + // copy the structure across, then process the fields + temp = *cs; + + // change the pointers to lengths or indexes + for ( field = castStateFields ; field->type ; field++ ) + { + WriteField1( field, (byte *)&temp ); + } + + // write the block + if ( !trap_FS_Write( &temp, sizeof( temp ), f ) ) { + G_SaveWriteError(); + } + + // now write any allocated data following the edict + for ( field = castStateFields ; field->type ; field++ ) + { + WriteField2( f, field, (byte *)cs ); + } + +} + +/* +=============== +ReadCastState +=============== +*/ +void ReadCastState( fileHandle_t f, cast_state_t *cs, int size ) { + saveField_t *field; + ignoreField_t *ifield; + cast_state_t temp; + + trap_FS_Read( &temp, size, f ); + + // convert any feilds back to the correct data + for ( field = castStateFields ; field->type ; field++ ) + { + ReadField( f, field, (byte *)&temp ); + } + + // backup any fields that we don't want to read in + for ( ifield = castStateIgnoreFields ; ifield->len ; ifield++ ) + { + memcpy( ( (byte *)&temp ) + ifield->ofs, ( (byte *)cs ) + ifield->ofs, ifield->len ); + } + + // now copy the temp structure into the existing structure + memcpy( cs, &temp, size ); +} + +//========================================================= + +/* +=============== +G_SaveGame + + returns qtrue if successful + + TODO: have trap_FS_Write return the number of byte's written, so if it doesn't + succeed, we can abort the save, and not save the file. This means we should + save to a temporary name, then copy it across to the real name after success, + so full disks don't result in lost saved games. +=============== +*/ +qboolean G_SaveGame( char *username ) { + char filename[MAX_QPATH]; + char mapstr[MAX_QPATH]; + vmCvar_t mapname; + fileHandle_t f; + int i; + gentity_t *ent; + gclient_t *cl; + cast_state_t *cs; + +// JPW NERVE -- save/load not supported in MP and causes problems during level loads if "pounding random keys" + return qfalse; +// jpw + + if ( !username ) { + username = "current"; + } + + // open the file + Com_sprintf( filename, MAX_QPATH, "save/temp.svg", username ); + if ( trap_FS_FOpenFile( filename, &f, FS_WRITE ) < 0 ) { + G_Error( "G_SaveGame: cannot open file for saving\n" ); + } + + // write the version + i = SAVE_VERSION; + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + + // write the mapname + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + Com_sprintf( mapstr, MAX_QPATH, mapname.string ); + if ( !trap_FS_Write( mapstr, MAX_QPATH, f ) ) { + G_SaveWriteError(); + } + + // write out the level time + if ( !trap_FS_Write( &level.time, sizeof( level.time ), f ) ) { + G_SaveWriteError(); + } + + // write the totalPlayTime + i = caststates[0].totalPlayTime; + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + + // write out the entity structures + i = sizeof( gentity_t ); + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + for ( i = 0 ; i < level.num_entities ; i++ ) + { + ent = &g_entities[i]; + if ( !ent->inuse || ent->s.number == ENTITYNUM_WORLD ) { + continue; + } + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + WriteEntity( f, ent ); + } + i = -1; + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + + // write out the client structures + i = sizeof( gclient_t ); + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) + { + cl = &level.clients[i]; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + WriteClient( f, cl ); + } + i = -1; + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + + // write out the cast_state structures + i = sizeof( cast_state_t ); + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + for ( i = 0 ; i < level.numConnectedClients ; i++ ) + { + cs = &caststates[i]; + if ( !cs->bs ) { + continue; + } + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + WriteCastState( f, cs ); + } + i = -1; + if ( !trap_FS_Write( &i, sizeof( i ), f ) ) { + G_SaveWriteError(); + } + + trap_FS_FCloseFile( f ); + + // now rename the file to the actual file + Com_sprintf( mapstr, MAX_QPATH, "save/%s.svg", username ); + trap_FS_Rename( filename, mapstr ); + + return qtrue; +} + +/* +=============== +G_UpdatePlayTime + + updates the total playing time in the current savegame +=============== +*/ +void G_UpdatePlayTime( void ) { + fileHandle_t f, of; + int i, len; + char mapname[MAX_QPATH]; + + // open the input file + if ( ( len = trap_FS_FOpenFile( "save/current.svg", &f, FS_READ ) ) < 0 ) { + G_Error( "G_SaveGame: cannot open file for saving\n" ); + } + + // open the output file + if ( trap_FS_FOpenFile( "save/current.tmp", &of, FS_WRITE ) < 0 ) { + G_Error( "G_SaveGame: cannot open file for saving\n" ); + } + + // start writing out the new file + + // read the version + trap_FS_Read( &i, sizeof( i ), f ); + if ( i != SAVE_VERSION ) { + G_Error( "G_LoadGame: savegame is wrong version (%i, should be %i)\n", i, SAVE_VERSION ); + } + if ( !trap_FS_Write( &i, sizeof( i ), of ) ) { + G_SaveWriteError(); + } + len -= sizeof( i ); + + // read the mapname + trap_FS_Read( mapname, MAX_QPATH, f ); + if ( !trap_FS_Write( mapname, MAX_QPATH, of ) ) { + G_SaveWriteError(); + } + len -= MAX_QPATH; + + // read the level time + trap_FS_Read( &i, sizeof( i ), f ); + if ( !trap_FS_Write( &i, sizeof( i ), of ) ) { + G_SaveWriteError(); + } + len -= sizeof( i ); + + // read the totalPlayTime + trap_FS_Read( &i, sizeof( i ), f ); + + // write the updated totalPlayTime + i = caststates[0].totalPlayTime; + if ( !trap_FS_Write( &i, sizeof( i ), of ) ) { + G_SaveWriteError(); + } + len -= sizeof( i ); + + // just copy the rest of the file + while ( len ) { + i = MAX_QPATH; + len -= i; + if ( len < 0 ) { + i -= -len; + len = 0; + } + trap_FS_Read( mapname, i, f ); + if ( !trap_FS_Write( mapname, i, of ) ) { + G_SaveWriteError(); + } + } + + trap_FS_FCloseFile( f ); + trap_FS_FCloseFile( of ); + + // now rename the temp file back to current + trap_FS_Rename( "save/current.tmp", "save/current.svg" ); + +/* + // open the input file + if ((len = trap_FS_FOpenFile( "save/current.tmp", &f, FS_READ )) < 0) { + G_Error( "G_SaveGame: cannot open file for saving\n" ); + } + + // open the output file + if (trap_FS_FOpenFile( "save/current.svg", &f, FS_WRITE ) < 0) { + G_Error( "G_SaveGame: cannot open file for saving\n" ); + } + + // just copy the rest of the file + while (len) { + i = MAX_QPATH; + len -= i; + if (len < 0) { + i -= -len; + len = 0; + } + trap_FS_Read (mapname, i, f); + trap_FS_Write (mapname, i, of); + } + + trap_FS_FCloseFile(f); + trap_FS_FCloseFile(of); + + // clear the temp file, since we can't delete it, just make it ZERO size + if (trap_FS_FOpenFile( "save/current.tmp", &of, FS_WRITE ) < 0) { + G_Error( "G_SaveGame: cannot open file for saving\n" ); + } + trap_FS_FCloseFile(of); +*/ +} + + +/* +=============== +G_LoadGame + + Always loads in "current.svg". So if loading a specific savegame, first copy it to that. +=============== +*/ +void G_LoadGame( char *filename ) { + char mapname[MAX_QPATH]; + fileHandle_t f; + int i, size, last; + gentity_t *ent; + gclient_t *cl; + cast_state_t *cs; + // TTimo might be used uninitialized + int totalPlayTime = 0; + int ver; + +// JPW NERVE -- save/load not permitted in MP and causes problems if players are "pounding keys" in unsupported configs during level load + return; +// jpw + + //if (!filename) { + filename = "save/current.svg"; + //} + + // open the file + if ( trap_FS_FOpenFile( filename, &f, FS_READ ) < 0 ) { + G_Error( "G_LoadGame: savegame '%s' not found\n", filename ); + } + + // read the version + trap_FS_Read( &i, sizeof( i ), f ); + if ( i != SAVE_VERSION && i != 6 ) { // special case, so I can debug savegames from our testing session + G_Error( "G_LoadGame: savegame '%s' is wrong version (%i, should be %i)\n", filename, i, SAVE_VERSION ); + } + ver = i; + + // read the mapname (this is only used in the sever exe, so just discard it) + trap_FS_Read( mapname, MAX_QPATH, f ); + + // read the level time + trap_FS_Read( &i, sizeof( i ), f ); + + if ( ver > 6 ) { + // read the totalPlayTime + trap_FS_Read( &i, sizeof( i ), f ); + totalPlayTime = i; + } + + // NOTE: do not change the above order without also changing the server code + + // reset all AAS blocking entities + trap_AAS_SetAASBlockingEntity( vec3_origin, vec3_origin, -1 ); + + // read the entity structures + trap_FS_Read( &i, sizeof( i ), f ); + size = i; + last = 0; + while ( 1 ) + { + trap_FS_Read( &i, sizeof( i ), f ); + if ( i < 0 ) { + break; + } + if ( i >= MAX_GENTITIES ) { + G_Error( "G_LoadGame: entitynum out of range (%i, MAX = %i)\n", i, MAX_GENTITIES ); + } + ent = &g_entities[i]; + ReadEntity( f, ent, size ); + // free all entities that we skipped + for ( ; last < i; last++ ) { + if ( g_entities[last].inuse && i != ENTITYNUM_WORLD ) { + if ( last < MAX_CLIENTS ) { + trap_DropClient( last, "" ); + } else { + G_FreeEntity( &g_entities[last] ); + } + } + } + last = i + 1; + } + + // read the client structures + trap_FS_Read( &i, sizeof( i ), f ); + size = i; + while ( 1 ) + { + trap_FS_Read( &i, sizeof( i ), f ); + if ( i < 0 ) { + break; + } + if ( i > MAX_CLIENTS ) { + G_Error( "G_LoadGame: clientnum out of range\n" ); + } + cl = &level.clients[i]; + if ( cl->pers.connected == CON_DISCONNECTED ) { + trap_FS_FCloseFile( f ); + G_Error( "G_LoadGame: client mis-match in savegame" ); + } + ReadClient( f, cl, size ); + } + + // read the cast_state structures + trap_FS_Read( &i, sizeof( i ), f ); + size = i; + while ( 1 ) + { + trap_FS_Read( &i, sizeof( i ), f ); + if ( i < 0 ) { + break; + } + if ( i > MAX_CLIENTS ) { + G_Error( "G_LoadGame: clientnum out of range\n" ); + } + cs = &caststates[i]; + ReadCastState( f, cs, size ); + } + + trap_FS_FCloseFile( f ); + + // now increment the attempts field, and save + ++caststates[0].attempts; + caststates[0].lastLoadTime = level.time; + caststates[0].totalPlayTime = totalPlayTime; + G_SaveGame( NULL ); +} + +//========================================================= + +/* +=============== +PersWriteClient +=============== +*/ +void PersWriteClient( fileHandle_t f, gclient_t *cl ) { + persField_t *field; + + // save the fields + for ( field = gclientPersFields ; field->len ; field++ ) + { // write the block + trap_FS_Write( ( void * )( (byte *)cl + field->ofs ), field->len, f ); + } +} + +/* +=============== +PersReadClient +=============== +*/ +void PersReadClient( fileHandle_t f, gclient_t *cl ) { + persField_t *field; + + // read the fields + for ( field = gclientPersFields ; field->len ; field++ ) + { // read the block + trap_FS_Read( ( void * )( (byte *)cl + field->ofs ), field->len, f ); + } +} + +//========================================================= + +/* +=============== +PersWriteEntity +=============== +*/ +void PersWriteEntity( fileHandle_t f, gentity_t *ent ) { + persField_t *field; + + // save the fields + for ( field = gentityPersFields ; field->len ; field++ ) + { // write the block + trap_FS_Write( ( void * )( (byte *)ent + field->ofs ), field->len, f ); + } +} + +/* +=============== +PersReadEntity +=============== +*/ +void PersReadEntity( fileHandle_t f, gentity_t *cl ) { + persField_t *field; + + // read the fields + for ( field = gentityPersFields ; field->len ; field++ ) + { // read the block + trap_FS_Read( ( void * )( (byte *)cl + field->ofs ), field->len, f ); + } +} + +//========================================================= + +/* +=============== +PersWriteCastState +=============== +*/ +void PersWriteCastState( fileHandle_t f, cast_state_t *cs ) { + persField_t *field; + + // save the fields + for ( field = castStatePersFields ; field->len ; field++ ) + { // write the block + trap_FS_Write( ( void * )( (byte *)cs + field->ofs ), field->len, f ); + } +} + +/* +=============== +PersReadCastState +=============== +*/ +void PersReadCastState( fileHandle_t f, cast_state_t *cs ) { + persField_t *field; + + // read the fields + for ( field = castStatePersFields ; field->len ; field++ ) + { // read the block + trap_FS_Read( ( void * )( (byte *)cs + field->ofs ), field->len, f ); + } +} + +//========================================================= + +/* +=============== +G_SavePersistant + + returns qtrue if successful + + NOTE: only saves the local player's data, doesn't support AI characters + + TODO: have trap_FS_Write return the number of byte's written, so if it doesn't + succeed, we can abort the save, and not save the file. This means we should + save to a temporary name, then copy it across to the real name after success, + so full disks don't result in lost saved games. +=============== +*/ +qboolean G_SavePersistant( char *nextmap ) { + char filename[MAX_QPATH]; + fileHandle_t f; + int persid; + + // open the file + Com_sprintf( filename, MAX_QPATH, "save/current.psw" ); + if ( trap_FS_FOpenFile( filename, &f, FS_WRITE ) < 0 ) { + G_Error( "G_SavePersistant: cannot open '%s' for saving\n", filename ); + } + + // write the mapname + trap_FS_Write( nextmap, MAX_QPATH, f ); + + // save out the pers id + persid = trap_Milliseconds(); + trap_FS_Write( &persid, sizeof( persid ), f ); + trap_Cvar_Set( "persid", va( "%i", persid ) ); + + // write out the entity structure + PersWriteEntity( f, &g_entities[0] ); + + // write out the client structure + PersWriteClient( f, &level.clients[0] ); + + // write out the cast_state structure + PersWriteCastState( f, AICast_GetCastState( 0 ) ); + + trap_FS_FCloseFile( f ); + + return qtrue; +} + +/* +=============== +G_LoadPersistant +=============== +*/ +void G_LoadPersistant( void ) { + fileHandle_t f; + char *filename; + char mapstr[MAX_QPATH]; + vmCvar_t cvar_mapname; + int persid; + + filename = "save/current.psw"; + + // open the file + if ( trap_FS_FOpenFile( filename, &f, FS_READ ) < 0 ) { + // not here, we shall assume they didn't want one + return; + } + + // read the mapname, if it's not the same, then ignore the file + trap_FS_Read( mapstr, MAX_QPATH, f ); + trap_Cvar_Register( &cvar_mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + if ( Q_strcasecmp( cvar_mapname.string, mapstr ) ) { + trap_FS_FCloseFile( f ); + return; + } + + // check the pers id + trap_FS_Read( &persid, sizeof( persid ), f ); + if ( persid != trap_Cvar_VariableIntegerValue( "persid" ) ) { + trap_FS_FCloseFile( f ); + return; + } + + // read the entity structure + PersReadEntity( f, &g_entities[0] ); + + // read the client structure + PersReadClient( f, &level.clients[0] ); + + // read the cast_state structure + PersReadCastState( f, AICast_GetCastState( 0 ) ); + + trap_FS_FCloseFile( f ); +} + +#endif // 0 diff --git a/src/game/g_script.c b/src/game/g_script.c new file mode 100644 index 0000000..3ca058e --- /dev/null +++ b/src/game/g_script.c @@ -0,0 +1,857 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: g_script.c +// Function: Wolfenstein Entity Scripting +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" + +/* +Scripting that allows the designers to control the behaviour of entities +according to each different scenario. +*/ + +vmCvar_t g_scriptDebug; + +// +//==================================================================== +// +// action functions need to be declared here so they can be accessed in the scriptAction table +qboolean G_ScriptAction_GotoMarker( gentity_t *ent, char *params ); +qboolean G_ScriptAction_Wait( gentity_t *ent, char *params ); +qboolean G_ScriptAction_Trigger( gentity_t *ent, char *params ); +qboolean G_ScriptAction_PlaySound( gentity_t *ent, char *params ); +qboolean G_ScriptAction_PlayAnim( gentity_t *ent, char *params ); +qboolean G_ScriptAction_AlertEntity( gentity_t *ent, char *params ); +qboolean G_ScriptAction_Accum( gentity_t *ent, char *params ); +qboolean G_ScriptAction_MissionFailed( gentity_t *ent, char *params ); +qboolean G_ScriptAction_MissionSuccess( gentity_t *ent, char *params ); +qboolean G_ScriptAction_Print( gentity_t *ent, char *params ); +qboolean G_ScriptAction_FaceAngles( gentity_t *ent, char *params ); +qboolean G_ScriptAction_ResetScript( gentity_t *ent, char *params ); +qboolean G_ScriptAction_TagConnect( gentity_t *ent, char *params ); +qboolean G_ScriptAction_Halt( gentity_t *ent, char *params ); +qboolean G_ScriptAction_StopSound( gentity_t *ent, char *params ); +qboolean G_ScriptAction_StartCam( gentity_t *ent, char *params ); +qboolean G_ScriptAction_EntityScriptName( gentity_t *ent, char *params ); +qboolean G_ScriptAction_AIScriptName( gentity_t *ent, char *params ); +// DHM - Nerve :: Multiplayer scripting commands +qboolean G_ScriptAction_MapDescription( gentity_t *ent, char *params ); +qboolean G_ScriptAction_AxisRespawntime( gentity_t *ent, char *params ); +qboolean G_ScriptAction_AlliedRespawntime( gentity_t *ent, char *params ); +qboolean G_ScriptAction_NumberofObjectives( gentity_t *ent, char *params ); +qboolean G_ScriptAction_ObjectiveAxisDesc( gentity_t *ent, char *params ); +qboolean G_ScriptAction_ObjectiveShortAxisDesc( gentity_t *ent, char *params ); // NERVE - SMF +qboolean G_ScriptAction_ObjectiveAlliedDesc( gentity_t *ent, char *params ); +qboolean G_ScriptAction_ObjectiveShortAlliedDesc( gentity_t *ent, char *params ); // NERVE - SMF +qboolean G_ScriptAction_ObjectiveImage( gentity_t *ent, char *params ); +qboolean G_ScriptAction_SetWinner( gentity_t *ent, char *params ); +qboolean G_ScriptAction_SetObjectiveStatus( gentity_t *ent, char *params ); +qboolean G_ScriptAction_SetDefendingTeam( gentity_t *ent, char *params ); // NERVE - SMF +qboolean G_ScriptAction_Announce( gentity_t *ent, char *params ); +qboolean G_ScriptAction_EndRound( gentity_t *ent, char *params ); +qboolean G_ScriptAction_SetRoundTimelimit( gentity_t *ent, char *params ); +qboolean G_ScriptAction_OverviewImage( gentity_t *ent, char *params ); // NERVE - SMF +qboolean G_ScriptAction_RemoveEntity( gentity_t *ent, char *params ); +// dhm + +// these are the actions that each event can call +g_script_stack_action_t gScriptActions[] = +{ + {"gotomarker", G_ScriptAction_GotoMarker}, + {"playsound", G_ScriptAction_PlaySound}, + {"playanim", G_ScriptAction_PlayAnim}, + {"wait", G_ScriptAction_Wait}, + {"trigger", G_ScriptAction_Trigger}, + {"alertentity", G_ScriptAction_AlertEntity}, + {"accum", G_ScriptAction_Accum}, + {"missionfailed", G_ScriptAction_MissionFailed}, + {"missionsuccess", G_ScriptAction_MissionSuccess}, + {"print", G_ScriptAction_Print}, + {"faceangles", G_ScriptAction_FaceAngles}, + {"resetscript", G_ScriptAction_ResetScript}, + {"attachtotag", G_ScriptAction_TagConnect}, + {"halt", G_ScriptAction_Halt}, + {"stopsound", G_ScriptAction_StopSound}, +// {"startcam", G_ScriptAction_StartCam}, + {"entityscriptname",G_ScriptAction_EntityScriptName}, + {"aiscriptname", G_ScriptAction_AIScriptName}, + // DHM - Nerve :: multiplayer scripting commands start with "wm_" (Wolf Multiplayer) + {"wm_mapdescription", G_ScriptAction_MapDescription}, + {"wm_axis_respawntime", G_ScriptAction_AxisRespawntime}, + {"wm_allied_respawntime", G_ScriptAction_AlliedRespawntime}, + {"wm_number_of_objectives", G_ScriptAction_NumberofObjectives}, + {"wm_objective_axis_desc", G_ScriptAction_ObjectiveAxisDesc}, + {"wm_objective_short_axis_desc", G_ScriptAction_ObjectiveShortAxisDesc}, // NERVE - SMF + {"wm_objective_allied_desc", G_ScriptAction_ObjectiveAlliedDesc}, + {"wm_objective_short_allied_desc", G_ScriptAction_ObjectiveShortAlliedDesc}, // NERVE - SMF + {"wm_objective_image", G_ScriptAction_ObjectiveImage}, + {"wm_setwinner", G_ScriptAction_SetWinner}, + {"wm_set_objective_status", G_ScriptAction_SetObjectiveStatus}, + {"wm_set_defending_team", G_ScriptAction_SetDefendingTeam}, + {"wm_announce", G_ScriptAction_Announce}, + {"wm_endround", G_ScriptAction_EndRound}, + {"wm_set_round_timelimit", G_ScriptAction_SetRoundTimelimit}, + {"wm_overview_image", G_ScriptAction_OverviewImage}, // NERVE - SMF + {"remove", G_ScriptAction_RemoveEntity}, + // dhm + + {NULL, NULL} +}; + +qboolean G_Script_EventMatch_StringEqual( g_script_event_t *event, char *eventParm ); +qboolean G_Script_EventMatch_IntInRange( g_script_event_t *event, char *eventParm ); + +// the list of events that can start an action sequence +g_script_event_define_t gScriptEvents[] = +{ + {"spawn", NULL}, // called as each character is spawned into the game + {"trigger", G_Script_EventMatch_StringEqual}, // something has triggered us (always followed by an identifier) + {"pain", G_Script_EventMatch_IntInRange}, // we've been hurt + {"death", NULL}, // RIP + {"activate", G_Script_EventMatch_StringEqual}, // something has triggered us (always followed by an identifier) + {"stopcam", NULL}, + + {NULL, NULL} +}; + +extern int numSecrets; + +/* +=============== +G_Script_EventMatch_StringEqual +=============== +*/ +qboolean G_Script_EventMatch_StringEqual( g_script_event_t *event, char *eventParm ) { + if ( eventParm && !Q_strcasecmp( event->params, eventParm ) ) { + return qtrue; + } else { + return qfalse; + } +} + +/* +=============== +G_Script_EventMatch_IntInRange +=============== +*/ +qboolean G_Script_EventMatch_IntInRange( g_script_event_t *event, char *eventParm ) { + char *pString, *token; + int int1, int2, eInt; + + // get the cast name + pString = eventParm; + token = COM_ParseExt( &pString, qfalse ); + int1 = atoi( token ); + token = COM_ParseExt( &pString, qfalse ); + int2 = atoi( token ); + + eInt = atoi( event->params ); + + if ( eventParm && eInt > int1 && eInt <= int2 ) { + return qtrue; + } else { + return qfalse; + } +} + +/* +=============== +G_Script_EventForString +=============== +*/ +int G_Script_EventForString( char *string ) { + int i; + + for ( i = 0; gScriptEvents[i].eventStr; i++ ) + { + if ( !Q_strcasecmp( string, gScriptEvents[i].eventStr ) ) { + return i; + } + } + + return -1; +} + +/* +=============== +G_Script_ActionForString +=============== +*/ +g_script_stack_action_t *G_Script_ActionForString( char *string ) { + int i; + + for ( i = 0; gScriptActions[i].actionString; i++ ) + { + if ( !Q_strcasecmp( string, gScriptActions[i].actionString ) ) { + if ( !Q_strcasecmp( string, "foundsecret" ) ) { + numSecrets++; + } + return &gScriptActions[i]; + } + } + + return NULL; +} + +/* +============= +G_Script_ScriptLoad + + Loads the script for the current level into the buffer +============= +*/ +void G_Script_ScriptLoad( void ) { + char filename[MAX_QPATH]; + vmCvar_t mapname; + fileHandle_t f; + int len; + + trap_Cvar_Register( &g_scriptDebug, "g_scriptDebug", "0", 0 ); + + level.scriptEntity = NULL; + + trap_Cvar_VariableStringBuffer( "g_scriptName", filename, sizeof( filename ) ); + if ( strlen( filename ) > 0 ) { + trap_Cvar_Register( &mapname, "g_scriptName", "", CVAR_ROM ); + } else { + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + } + Q_strncpyz( filename, "maps/", sizeof( filename ) ); + Q_strcat( filename, sizeof( filename ), mapname.string ); + // DHM - Nerve :: Support capture mode by loading appropriate script + if ( ( g_gametype.integer == GT_WOLF_CP ) || ( g_gametype.integer == GT_WOLF_CPH ) ) { // JPW NERVE added capture & hold + Q_strcat( filename, sizeof( filename ), "_cp" ); + } + // dhm - Nerve + Q_strcat( filename, sizeof( filename ), ".script" ); + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + + // make sure we clear out the temporary scriptname + trap_Cvar_Set( "g_scriptName", "" ); + + if ( len < 0 ) { + return; + } + + level.scriptEntity = G_Alloc( len ); + trap_FS_Read( level.scriptEntity, len, f ); + + trap_FS_FCloseFile( f ); +} + +/* +============== +G_Script_ScriptParse + + Parses the script for the given entity +============== +*/ +void G_Script_ScriptParse( gentity_t *ent ) { + #define MAX_SCRIPT_EVENTS 64 + char *pScript; + char *token; + qboolean wantName; + qboolean inScript; + int eventNum; + g_script_event_t events[MAX_SCRIPT_EVENTS]; + int numEventItems; + g_script_event_t *curEvent; + // DHM - Nerve :: Some of our multiplayer script commands have longer parameters + //char params[MAX_QPATH]; + char params[MAX_INFO_STRING]; + // dhm - end + g_script_stack_action_t *action; + int i; + int bracketLevel; + + if ( !ent->scriptName ) { + return; + } + if ( !level.scriptEntity ) { + return; + } + + pScript = level.scriptEntity; + wantName = qtrue; + inScript = qfalse; + COM_BeginParseSession( "G_Script_ScriptParse" ); + bracketLevel = 0; + numEventItems = 0; + + memset( events, 0, sizeof( events ) ); + + while ( 1 ) + { + token = COM_Parse( &pScript ); + + if ( !token[0] ) { + if ( !wantName ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); + } + break; + } + + // end of script + if ( token[0] == '}' ) { + if ( inScript ) { + break; + } + if ( wantName ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): '}' found, but not expected.\n", COM_GetCurrentParseLine() ); + } + wantName = qtrue; + } else if ( token[0] == '{' ) { + if ( wantName ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): '{' found, NAME expected.\n", COM_GetCurrentParseLine() ); + } + } else if ( wantName ) { + if ( !Q_strcasecmp( ent->scriptName, token ) ) { + inScript = qtrue; + numEventItems = 0; + } + wantName = qfalse; + } else if ( inScript ) { + //if ( !Q_strcasecmp( token, "attributes" ) ) { + // // read in all the attributes + // G_Script_CheckLevelAttributes( cs, ent, &pScript ); + // continue; + //} + eventNum = G_Script_EventForString( token ); + if ( eventNum < 0 ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): unknown event: %s.\n", COM_GetCurrentParseLine(), token ); + } + if ( numEventItems >= MAX_SCRIPT_EVENTS ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): MAX_SCRIPT_EVENTS reached (%d)\n", COM_GetCurrentParseLine(), MAX_SCRIPT_EVENTS ); + } + + curEvent = &events[numEventItems]; + curEvent->eventNum = eventNum; + memset( params, 0, sizeof( params ) ); + + // parse any event params before the start of this event's actions + while ( ( token = COM_Parse( &pScript ) ) && ( token[0] != '{' ) ) + { + if ( !token[0] ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); + } + + if ( strlen( params ) ) { // add a space between each param + Q_strcat( params, sizeof( params ), " " ); + } + Q_strcat( params, sizeof( params ), token ); + } + + if ( strlen( params ) ) { // copy the params into the event + curEvent->params = G_Alloc( strlen( params ) + 1 ); + Q_strncpyz( curEvent->params, params, strlen( params ) + 1 ); + } + + // parse the actions for this event + while ( ( token = COM_Parse( &pScript ) ) && ( token[0] != '}' ) ) + { + if ( !token[0] ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); + } + + action = G_Script_ActionForString( token ); + if ( !action ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): unknown action: %s.\n", COM_GetCurrentParseLine(), token ); + } + + curEvent->stack.items[curEvent->stack.numItems].action = action; + + memset( params, 0, sizeof( params ) ); + token = COM_ParseExt( &pScript, qfalse ); + for ( i = 0; token[0]; i++ ) + { + if ( strlen( params ) ) { // add a space between each param + Q_strcat( params, sizeof( params ), " " ); + } + + // Special case: playsound's need to be cached on startup to prevent in-game pauses + if ( ( i == 0 ) && !Q_stricmp( action->actionString, "playsound" ) ) { + G_SoundIndex( token ); + } + + if ( strrchr( token,' ' ) ) { // need to wrap this param in quotes since it has more than one word + Q_strcat( params, sizeof( params ), "\"" ); + } + + Q_strcat( params, sizeof( params ), token ); + + if ( strrchr( token,' ' ) ) { // need to wrap this param in quotes since it has more than one word + Q_strcat( params, sizeof( params ), "\"" ); + } + + token = COM_ParseExt( &pScript, qfalse ); + } + + if ( strlen( params ) ) { // copy the params into the event + curEvent->stack.items[curEvent->stack.numItems].params = G_Alloc( strlen( params ) + 1 ); + Q_strncpyz( curEvent->stack.items[curEvent->stack.numItems].params, params, strlen( params ) + 1 ); + } + + curEvent->stack.numItems++; + + if ( curEvent->stack.numItems >= G_MAX_SCRIPT_STACK_ITEMS ) { + G_Error( "G_Script_ScriptParse(): script exceeded MAX_SCRIPT_ITEMS (%d), line %d\n", G_MAX_SCRIPT_STACK_ITEMS, COM_GetCurrentParseLine() ); + } + } + + numEventItems++; + } else // skip this character completely + { + // TTimo gcc: suggest parentheses around assignment used as truth value + while ( ( token = COM_Parse( &pScript ) ) ) + { + if ( !token[0] ) { + G_Error( "G_Script_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() ); + } else if ( token[0] == '{' ) { + bracketLevel++; + } else if ( token[0] == '}' ) { + if ( !--bracketLevel ) { + break; + } + } + } + } + } + + // alloc and copy the events into the gentity_t for this cast + if ( numEventItems > 0 ) { + ent->scriptEvents = G_Alloc( sizeof( g_script_event_t ) * numEventItems ); + memcpy( ent->scriptEvents, events, sizeof( g_script_event_t ) * numEventItems ); + ent->numScriptEvents = numEventItems; + } +} + +/* +================ +G_Script_ScriptChange +================ +*/ +qboolean G_Script_ScriptRun( gentity_t *ent ); +void G_Script_ScriptChange( gentity_t *ent, int newScriptNum ) { + g_script_status_t scriptStatusBackup; + + // backup the current scripting + memcpy( &scriptStatusBackup, &ent->scriptStatus, sizeof( g_script_status_t ) ); + + // set the new script to this cast, and reset script status + ent->scriptStatus.scriptEventIndex = newScriptNum; + ent->scriptStatus.scriptStackHead = 0; + ent->scriptStatus.scriptStackChangeTime = level.time; + ent->scriptStatus.scriptId = scriptStatusBackup.scriptId + 1; + + // try and run the script, if it doesn't finish, then abort the current script (discard backup) + if ( G_Script_ScriptRun( ent ) ) { + // completed successfully + memcpy( &ent->scriptStatus, &scriptStatusBackup, sizeof( g_script_status_t ) ); + } +} + +/* +================ +G_Script_ScriptEvent + + An event has occured, for which a script may exist +================ +*/ +void G_Script_ScriptEvent( gentity_t *ent, char *eventStr, char *params ) { + int i, eventNum; + + eventNum = -1; + + // find out which event this is + for ( i = 0; gScriptEvents[i].eventStr; i++ ) + { + if ( !Q_strcasecmp( eventStr, gScriptEvents[i].eventStr ) ) { // match found + eventNum = i; + break; + } + } + + if ( eventNum < 0 ) { + if ( g_cheats.integer ) { // dev mode + G_Printf( "devmode-> G_Script_ScriptEvent(), unknown event: %s\n", eventStr ); + } + return; + } + + // see if this entity has this event + for ( i = 0; i < ent->numScriptEvents; i++ ) + { + if ( ent->scriptEvents[i].eventNum == eventNum ) { + if ( ( !ent->scriptEvents[i].params ) + || ( !gScriptEvents[eventNum].eventMatch || gScriptEvents[eventNum].eventMatch( &ent->scriptEvents[i], params ) ) ) { + G_Script_ScriptChange( ent, i ); + break; + } + } + } +} + +/* +============= +G_Script_ScriptRun + + returns qtrue if the script completed +============= +*/ +qboolean G_Script_ScriptRun( gentity_t *ent ) { + g_script_stack_t *stack; + + if ( saveGamePending ) { + return qfalse; + } + + if ( strlen( g_missionStats.string ) > 1 ) { + return qfalse; + } + + //if (!g_scripts.integer) + // return qtrue; + + trap_Cvar_Update( &g_scriptDebug ); + + if ( !ent->scriptEvents ) { + ent->scriptStatus.scriptEventIndex = -1; + return qtrue; + } + + // if we are still doing a gotomarker, process the movement + if ( ent->scriptStatus.scriptFlags & SCFL_GOING_TO_MARKER ) { + G_ScriptAction_GotoMarker( ent, NULL ); + } + + // if we are animating, do the animation + if ( ent->scriptStatus.scriptFlags & SCFL_ANIMATING ) { + G_ScriptAction_PlayAnim( ent, ent->scriptStatus.animatingParams ); + } + + if ( ent->scriptStatus.scriptEventIndex < 0 ) { + return qtrue; + } + + stack = &ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack; + + if ( !stack->numItems ) { + ent->scriptStatus.scriptEventIndex = -1; + return qtrue; + } + // + // show debugging info + if ( g_scriptDebug.integer && ent->scriptStatus.scriptStackChangeTime == level.time ) { + if ( ent->scriptStatus.scriptStackHead < stack->numItems ) { + G_Printf( "%i : (%s) GScript command: %s %s\n", level.time, ent->scriptName, stack->items[ent->scriptStatus.scriptStackHead].action->actionString, ( stack->items[ent->scriptStatus.scriptStackHead].params ? stack->items[ent->scriptStatus.scriptStackHead].params : "" ) ); + } + } + // + while ( ent->scriptStatus.scriptStackHead < stack->numItems ) + { + if ( !stack->items[ent->scriptStatus.scriptStackHead].action->actionFunc( ent, stack->items[ent->scriptStatus.scriptStackHead].params ) ) { + return qfalse; + } + // move to the next action in the script + ent->scriptStatus.scriptStackHead++; + // record the time that this new item became active + ent->scriptStatus.scriptStackChangeTime = level.time; + // + // show debugging info + if ( g_scriptDebug.integer ) { + if ( ent->scriptStatus.scriptStackHead < stack->numItems ) { + G_Printf( "%i : (%s) GScript command: %s %s\n", level.time, ent->scriptName, stack->items[ent->scriptStatus.scriptStackHead].action->actionString, ( stack->items[ent->scriptStatus.scriptStackHead].params ? stack->items[ent->scriptStatus.scriptStackHead].params : "" ) ); + } + } + } + + ent->scriptStatus.scriptEventIndex = -1; + + return qtrue; +} + +//================================================================================ +// Script Entities + +void script_linkentity( gentity_t *ent ) { + + // this is required since non-solid brushes need to be linked but not solid + trap_LinkEntity( ent ); + +// if ((ent->s.eType == ET_MOVER) && !(ent->spawnflags & 2)) { +// ent->s.solid = 0; +// } +} + +void script_mover_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + if ( self->spawnflags & 4 ) { + switch ( mod ) { + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: + case MOD_AIRSTRIKE: + break; + default: // no death from this weapon + self->health += damage; + return; + } + } + + G_Script_ScriptEvent( self, "death", "" ); + self->die = NULL; + + trap_UnlinkEntity( self ); + G_FreeEntity( self ); +} + +void script_mover_spawn( gentity_t *ent ) { + if ( ent->spawnflags & 2 ) { + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + } else { + ent->s.eFlags |= EF_NONSOLID_BMODEL; + ent->clipmask = 0; + ent->r.contents = 0; + } + //ent->s.eType = ET_GENERAL; + + //ent->s.modelindex = G_ModelIndex (ent->model); + //ent->s.frame = 0; + + //VectorCopy( ent->s.origin, ent->s.pos.trBase ); + //ent->s.pos.trType = TR_STATIONARY; + + script_linkentity( ent ); +} + +void script_mover_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + script_mover_spawn( ent ); +} + +void script_mover_blocked( gentity_t *ent, gentity_t *other ) { + // remove it, we must not stop for anything or it will screw up script timing + if ( !other->client && other->s.eType != ET_CORPSE ) { + // /me slaps nerve + // except CTF flags!!!! + if ( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) { + Team_DroppedFlagThink( other ); + return; + } + G_TempEntity( other->s.origin, EV_ITEM_POP ); + G_FreeEntity( other ); + return; + } + + // FIXME: we could have certain entities stop us, thereby "pausing" movement + // until they move out the way. then we can just call the GotoMarker() again, + // telling it that we are just now calling it for the first time, so it should + // start us on our way again (theoretically speaking). + + // kill them + G_Damage( other, ent, ent, NULL, NULL, 9999, 0, MOD_CRUSH ); +} + +/*QUAKED script_mover (0.5 0.25 1.0) ? TRIGGERSPAWN SOLID EXPLOSIVEDAMAGEONLY +Scripted brush entity. A simplified means of moving brushes around based on events. + +"modelscale" - Scale multiplier (defaults to 1, and scales uniformly) +"modelscale_vec" - Set scale per-axis. Overrides "modelscale", so if you have both the "modelscale" is ignored +"model2" optional md3 to draw over the solid clip brush +"scriptname" name used for scripting purposes (like aiName in AI scripting) +"health" optionally make this entity damagable +*/ +void SP_script_mover( gentity_t *ent ) { + + float scale[3] = {1,1,1}; + vec3_t scalevec; + + if ( !ent->model ) { + G_Error( "script_model_med must have a \"model\"\n" ); + } + if ( !ent->scriptName ) { + G_Error( "script_model_med must have a \"scriptname\"\n" ); + } + + ent->blocked = script_mover_blocked; + + // first position at start + VectorCopy( ent->s.origin, ent->pos1 ); + +// VectorCopy( ent->r.currentOrigin, ent->pos1 ); + VectorCopy( ent->pos1, ent->pos2 ); // don't go anywhere just yet + + trap_SetBrushModel( ent, ent->model ); + + InitMover( ent ); + ent->reached = NULL; + + if ( ent->spawnflags & 1 ) { + ent->use = script_mover_use; + trap_UnlinkEntity( ent ); // make sure it's not visible + return; + } + + G_SetAngle( ent, ent->s.angles ); + + G_SpawnInt( "health", "0", &ent->health ); + if ( ent->health ) { + ent->takedamage = qtrue; + } + + ent->die = script_mover_die; + + // look for general scaling + if ( G_SpawnFloat( "modelscale", "1", &scale[0] ) ) { + scale[2] = scale[1] = scale[0]; + } + + // look for axis specific scaling + if ( G_SpawnVector( "modelscale_vec", "1 1 1", &scalevec[0] ) ) { + VectorCopy( scalevec, scale ); + } + + if ( scale[0] != 1 || scale[1] != 1 || scale[2] != 1 ) { +// ent->s.eType = ET_MOVERSCALED; + ent->s.density = ET_MOVERSCALED; + // scale is stored in 'angles2' + VectorCopy( scale, ent->s.angles2 ); + } + + script_mover_spawn( ent ); +} + +//.............................................................................. + +void script_model_med_spawn( gentity_t *ent ) { + if ( ent->spawnflags & 2 ) { + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + } + ent->s.eType = ET_GENERAL; + + ent->s.modelindex = G_ModelIndex( ent->model ); + ent->s.frame = 0; + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + + trap_LinkEntity( ent ); +} + +void script_model_med_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + script_model_med_spawn( ent ); +} + +/*QUAKED script_model_med (0.5 0.25 1.0) (-16 -16 -24) (16 16 64) TriggerSpawn Solid +MEDIUM SIZED scripted entity, used for animating a model, moving it around, etc +SOLID spawnflag means this entity will clip the player and AI, otherwise they can walk +straight through it +"model" the full path of the model to use +"scriptname" name used for scripting purposes (like aiName in AI scripting) +*/ +void SP_script_model_med( gentity_t *ent ) { + if ( !ent->model ) { + G_Error( "script_model_med %s must have a \"model\"\n", ent->scriptName ); + } + if ( !ent->scriptName ) { + G_Error( "script_model_med must have a \"scriptname\"\n" ); + } + + ent->s.eType = ET_GENERAL; + ent->s.apos.trType = TR_STATIONARY; + ent->s.apos.trTime = 0; + ent->s.apos.trDuration = 0; + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + VectorClear( ent->s.apos.trDelta ); + + if ( ent->spawnflags & 1 ) { + ent->use = script_model_med_use; + trap_UnlinkEntity( ent ); // make sure it's not visible + return; + } + + script_model_med_spawn( ent ); +} + +//.............................................................................. + +/*QUAKED script_camera (1.0 0.25 1.0) (-8 -8 -8) (8 8 8) TriggerSpawn + + This is a camera entity. Used by the scripting to show cinematics, via special + camera commands. See scripting documentation. + +"scriptname" name used for scripting purposes (like aiName in AI scripting) +*/ +void SP_script_camera( gentity_t *ent ) { + if ( !ent->scriptName ) { + G_Error( "%s must have a \"scriptname\"\n", ent->classname ); + } + + ent->s.eType = ET_CAMERA; + ent->s.apos.trType = TR_STATIONARY; + ent->s.apos.trTime = 0; + ent->s.apos.trDuration = 0; + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + VectorClear( ent->s.apos.trDelta ); + + ent->s.frame = 0; + + ent->r.svFlags |= SVF_NOCLIENT; // only broadcast when in use +} + + +//..DHM-Nerve.................................................................. + +/*QUAKED script_multiplayer (1.0 0.25 1.0) (-8 -8 -8) (8 8 8) + + This is used to script multiplayer maps. Entity not displayed in game. + +"scriptname" name used for scripting purposes (REQUIRED) +*/ +void SP_script_multiplayer( gentity_t *ent ) { + if ( !ent->scriptName ) { + G_Error( "%s must have a \"scriptname\"\n", ent->classname ); + } + if ( Q_stricmp( ent->scriptName, "game_manager" ) ) { + G_Error( "%s must have a \"scriptname\" of 'game_manager'\n", ent->classname ); + } + + ent->s.eType = ET_INVISIBLE; + ent->r.svFlags |= SVF_NOCLIENT; +} + +// dhm diff --git a/src/game/g_script_actions.c b/src/game/g_script_actions.c new file mode 100644 index 0000000..d5348a8 --- /dev/null +++ b/src/game/g_script_actions.c @@ -0,0 +1,1545 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//=========================================================================== +// +// Name: g_script_actions.c +// Function: Wolfenstein Entity Scripting +// Programmer: Ridah +// Tab Size: 4 (real tabs) +//=========================================================================== + +#include "../game/g_local.h" +#include "../game/q_shared.h" + +/* +Contains the code to handle the various commands available with an event script. + +These functions will return true if the action has been performed, and the script +should proceed to the next item on the list. +*/ + +void script_linkentity( gentity_t *ent ); + +/* +=============== +G_ScriptAction_GotoMarker + + syntax: gotomarker [accel/deccel] [turntotarget] [wait] + + NOTE: speed may be modified to round the duration to the next 50ms for smooth + transitions +=============== +*/ +qboolean G_ScriptAction_GotoMarker( gentity_t *ent, char *params ) { + char *pString, *token; + gentity_t *target; + vec3_t vec; + float speed, dist; + qboolean wait = qfalse, turntotarget = qfalse; + int trType; + int duration, i; + vec3_t diff; + vec3_t angles; + + if ( params && ( ent->scriptStatus.scriptFlags & SCFL_GOING_TO_MARKER ) ) { + // we can't process a new movement until the last one has finished + return qfalse; + } + + if ( !params || ent->scriptStatus.scriptStackChangeTime < level.time ) { // we are waiting for it to reach destination + if ( ent->s.pos.trTime + ent->s.pos.trDuration <= level.time ) { // we made it + ent->scriptStatus.scriptFlags &= ~SCFL_GOING_TO_MARKER; + + // set the angles at the destination + BG_EvaluateTrajectory( &ent->s.apos, ent->s.apos.trTime + ent->s.apos.trDuration, ent->s.angles ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + VectorCopy( ent->s.angles, ent->r.currentAngles ); + ent->s.apos.trTime = level.time; + ent->s.apos.trDuration = 0; + ent->s.apos.trType = TR_STATIONARY; + VectorClear( ent->s.apos.trDelta ); + + // stop moving + BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->s.origin ); + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + ent->s.pos.trTime = level.time; + ent->s.pos.trDuration = 0; + ent->s.pos.trType = TR_STATIONARY; + VectorClear( ent->s.pos.trDelta ); + + script_linkentity( ent ); + + return qtrue; + } + } else { // we have just started this command + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "G_Scripting: gotomarker must have an targetname\n" ); + } + + // find the entity with the given "targetname" + target = G_Find( NULL, FOFS( targetname ), token ); + + if ( !target ) { + G_Error( "G_Scripting: can't find entity with \"targetname\" = \"%s\"\n", token ); + } + + VectorSubtract( target->r.currentOrigin, ent->r.currentOrigin, vec ); + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "G_Scripting: gotomarker must have a speed\n" ); + } + + speed = atof( token ); + trType = TR_LINEAR_STOP; + + while ( token[0] ) { + token = COM_ParseExt( &pString, qfalse ); + if ( token[0] ) { + if ( !Q_stricmp( token, "accel" ) ) { + trType = TR_ACCELERATE; + } else if ( !Q_stricmp( token, "deccel" ) ) { + trType = TR_DECCELERATE; + } else if ( !Q_stricmp( token, "wait" ) ) { + wait = qtrue; + } else if ( !Q_stricmp( token, "turntotarget" ) ) { + turntotarget = qtrue; + } + } + } + + // start the movement + if ( ent->s.eType == ET_MOVER ) { + + VectorCopy( vec, ent->movedir ); + VectorCopy( ent->r.currentOrigin, ent->pos1 ); + VectorCopy( target->r.currentOrigin, ent->pos2 ); + ent->speed = speed; + dist = VectorDistance( ent->pos1, ent->pos2 ); + // setup the movement with the new parameters + InitMover( ent ); + + // start the movement + + SetMoverState( ent, MOVER_1TO2, level.time ); + if ( trType != TR_LINEAR_STOP ) { // allow for acceleration/decceleration + ent->s.pos.trDuration = 1000.0 * dist / ( speed / 2.0 ); + ent->s.pos.trType = trType; + } + ent->reached = NULL; + + if ( turntotarget ) { + duration = ent->s.pos.trDuration; + VectorCopy( target->s.angles, angles ); + + for ( i = 0; i < 3; i++ ) { + diff[i] = AngleDifference( angles[i], ent->s.angles[i] ); + while ( diff[i] > 180 ) + diff[i] -= 360; + while ( diff[i] < -180 ) + diff[i] += 360; + } + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + if ( duration ) { + VectorScale( diff, 1000.0 / (float)duration, ent->s.apos.trDelta ); + } else { + VectorClear( ent->s.apos.trDelta ); + } + ent->s.apos.trDuration = duration; + ent->s.apos.trTime = level.time; + ent->s.apos.trType = TR_LINEAR_STOP; + if ( trType != TR_LINEAR_STOP ) { // allow for acceleration/decceleration + ent->s.pos.trDuration = 1000.0 * dist / ( speed / 2.0 ); + ent->s.pos.trType = trType; + } + } + + } else { + // calculate the trajectory + ent->s.pos.trType = TR_LINEAR_STOP; + ent->s.pos.trTime = level.time; + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + dist = VectorNormalize( vec ); + VectorScale( vec, speed, ent->s.pos.trDelta ); + ent->s.pos.trDuration = 1000 * ( dist / speed ); + + if ( turntotarget ) { + duration = ent->s.pos.trDuration; + VectorCopy( target->s.angles, angles ); + + for ( i = 0; i < 3; i++ ) { + diff[i] = AngleDifference( angles[i], ent->s.angles[i] ); + while ( diff[i] > 180 ) + diff[i] -= 360; + while ( diff[i] < -180 ) + diff[i] += 360; + } + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + if ( duration ) { + VectorScale( diff, 1000.0 / (float)duration, ent->s.apos.trDelta ); + } else { + VectorClear( ent->s.apos.trDelta ); + } + ent->s.apos.trDuration = duration; + ent->s.apos.trTime = level.time; + ent->s.apos.trType = TR_LINEAR_STOP; + } + + } + + if ( !wait ) { + // round the duration to the next 50ms + if ( ent->s.pos.trDuration % 50 ) { + float frac; + + frac = (float)( ( ( ent->s.pos.trDuration / 50 ) * 50 + 50 ) - ent->s.pos.trDuration ) / (float)( ent->s.pos.trDuration ); + if ( frac < 1 ) { + VectorScale( ent->s.pos.trDelta, 1.0 / ( 1.0 + frac ), ent->s.pos.trDelta ); + ent->s.pos.trDuration = ( ent->s.pos.trDuration / 50 ) * 50 + 50; + } + } + + // set the goto flag, so we can keep processing the move until we reach the destination + ent->scriptStatus.scriptFlags |= SCFL_GOING_TO_MARKER; + return qtrue; // continue to next command + } + + } + + BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin ); + BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles ); + script_linkentity( ent ); + + return qfalse; +} + +/* +================= +G_ScriptAction_Wait + + syntax: wait +================= +*/ +qboolean G_ScriptAction_Wait( gentity_t *ent, char *params ) { + char *pString, *token; + int duration; + + // get the duration + pString = params; + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "G_Scripting: wait must have a duration\n" ); + } + duration = atoi( token ); + + return ( ent->scriptStatus.scriptStackChangeTime + duration < level.time ); +} + +/* +================= +G_ScriptAction_Trigger + + syntax: trigger + + Calls the specified trigger for the given ai character or script entity +================= +*/ +qboolean G_ScriptAction_Trigger( gentity_t *ent, char *params ) { + gentity_t *trent; + char *pString, name[MAX_QPATH], trigger[MAX_QPATH], *token; + int oldId; + + // get the cast name + pString = params; + token = COM_ParseExt( &pString, qfalse ); + Q_strncpyz( name, token, sizeof( name ) ); + if ( !name[0] ) { + G_Error( "G_Scripting: trigger must have a name and an identifier\n" ); + } + + token = COM_ParseExt( &pString, qfalse ); + Q_strncpyz( trigger, token, sizeof( trigger ) ); + if ( !trigger[0] ) { + G_Error( "G_Scripting: trigger must have a name and an identifier\n" ); + } + + trent = AICast_FindEntityForName( name ); + if ( trent ) { // we are triggering an AI + //oldId = trent->scriptStatus.scriptId; + AICast_ScriptEvent( AICast_GetCastState( trent->s.number ), "trigger", trigger ); + return qtrue; + } + + // look for an entity + trent = G_Find( &g_entities[MAX_CLIENTS], FOFS( scriptName ), name ); + if ( trent ) { + oldId = trent->scriptStatus.scriptId; + G_Script_ScriptEvent( trent, "trigger", trigger ); + // if the script changed, return false so we don't muck with it's variables + return ( ( trent != ent ) || ( oldId == trent->scriptStatus.scriptId ) ); + } + + G_Error( "G_Scripting: trigger has unknown name: %s\n", name ); + return qfalse; // shutup the compiler +} + +/* +================ +G_ScriptAction_PlaySound + + syntax: playsound [LOOPING] + + Currently only allows playing on the VOICE channel, unless you use a sound script. + + Use the optional LOOPING paramater to attach the sound to the entities looping channel. +================ +*/ +qboolean G_ScriptAction_PlaySound( gentity_t *ent, char *params ) { + char *pString, *token; + char sound[MAX_QPATH]; + + if ( !params ) { + G_Error( "G_Scripting: syntax error\n\nplaysound \n" ); + } + + pString = params; + token = COM_ParseExt( &pString, qfalse ); + Q_strncpyz( sound, token, sizeof( sound ) ); + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] || Q_strcasecmp( token, "looping" ) ) { + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( sound ) ); + } else { // looping channel + ent->s.loopSound = G_SoundIndex( sound ); + } + + return qtrue; +} + +/* +================= +G_ScriptAction_PlayAnim + + syntax: playanim [looping ] [rate ] + + NOTE: all source animations must be at 20fps +================= +*/ +qboolean G_ScriptAction_PlayAnim( gentity_t *ent, char *params ) { + char *pString, *token, tokens[2][MAX_QPATH]; + int i; + // TTimo might be used uninitialized + int endtime = 0; + qboolean looping = qfalse, forever = qfalse; + int startframe, endframe, idealframe; + int rate = 20; + + if ( ( ent->scriptStatus.scriptFlags & SCFL_ANIMATING ) && ( ent->scriptStatus.scriptStackChangeTime == level.time ) ) { + // this is a new call, so cancel the previous animation + ent->scriptStatus.scriptFlags &= ~SCFL_ANIMATING; + } + + pString = params; + + for ( i = 0; i < 2; i++ ) { + token = COM_ParseExt( &pString, qfalse ); + if ( !token || !token[0] ) { + G_Printf( "G_Scripting: syntax error\n\nplayanim [LOOPING ]\n" ); + return qtrue; + } else { + Q_strncpyz( tokens[i], token, sizeof( tokens[i] ) ); + } + } + + startframe = atoi( tokens[0] ); + endframe = atoi( tokens[1] ); + + // check for optional parameters + token = COM_ParseExt( &pString, qfalse ); + if ( token[0] ) { + if ( !Q_strcasecmp( token, "looping" ) ) { + looping = qtrue; + + token = COM_ParseExt( &pString, qfalse ); + if ( !token || !token[0] ) { + G_Printf( "G_Scripting: syntax error\n\nplayanim [LOOPING ]\n" ); + return qtrue; + } + if ( !Q_strcasecmp( token, "untilreachmarker" ) ) { + if ( level.time < ent->s.pos.trTime + ent->s.pos.trDuration ) { + endtime = level.time + 100; + } else { + endtime = 0; + } + } else if ( !Q_strcasecmp( token, "forever" ) ) { + ent->scriptStatus.animatingParams = params; + ent->scriptStatus.scriptFlags |= SCFL_ANIMATING; + endtime = level.time + 100; // we don't care when it ends, since we are going forever! + forever = qtrue; + } else { + endtime = ent->scriptStatus.scriptStackChangeTime + atoi( token ); + } + + token = COM_ParseExt( &pString, qfalse ); + } + + if ( token[0] && !Q_strcasecmp( token, "rate" ) ) { + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "G_Scripting: playanim has RATE parameter without an actual rate specified" ); + } + rate = atoi( token ); + } + + if ( !looping ) { + endtime = ent->scriptStatus.scriptStackChangeTime + ( ( endframe - startframe ) * ( 1000 / 20 ) ); + } + } + + idealframe = startframe + (int)floor( (float)( level.time - ent->scriptStatus.scriptStackChangeTime ) / ( 1000.0 / (float)rate ) ); + if ( looping ) { + ent->s.frame = startframe + ( idealframe - startframe ) % ( endframe - startframe ); + } else { + if ( idealframe > endframe ) { + ent->s.frame = endframe; + } else { + ent->s.frame = idealframe; + } + } + + if ( forever ) { + return qtrue; // continue to the next command + } + + return ( endtime <= level.time ); +}; + +/* +================= +G_ScriptAction_AlertEntity + + syntax: alertentity + + Arnout: modified to target multiple entities with the same targetname +================= +*/ +qboolean G_ScriptAction_AlertEntity( gentity_t *ent, char *params ) { + gentity_t *alertent = NULL; + qboolean foundalertent = qfalse; + + if ( !params || !params[0] ) { + G_Error( "G_Scripting: alertentity without targetname\n" ); + } + + // find this targetname + while ( 1 ) { + alertent = G_Find( alertent, FOFS( targetname ), params ); + if ( !alertent ) { + if ( !foundalertent ) { + G_Error( "G_Scripting: alertentity cannot find targetname \"%s\"\n", params ); + } else { + break; + } + } + + foundalertent = qtrue; + + if ( alertent->client ) { + // call this entity's AlertEntity function + if ( !alertent->AIScript_AlertEntity ) { + G_Error( "G_Scripting: alertentity \"%s\" (classname = %s) doesn't have an \"AIScript_AlertEntity\" function\n", params, alertent->classname ); + } + alertent->AIScript_AlertEntity( alertent ); + } else { + if ( !alertent->use ) { + G_Error( "G_Scripting: alertentity \"%s\" (classname = %s) doesn't have a \"use\" function\n", params, alertent->classname ); + } + alertent->use( alertent, NULL, NULL ); + } + } + + return qtrue; +} + +/* +================= +G_ScriptAction_Accum + + syntax: accum + + Commands: + + accum inc + accum abort_if_less_than + accum abort_if_greater_than + accum abort_if_not_equal + accum abort_if_equal + accum set + accum random + accum bitset + accum bitreset + accum abort_if_bitset + accum abort_if_not_bitset +================= +*/ +qboolean G_ScriptAction_Accum( gentity_t *ent, char *params ) { + char *pString, *token, lastToken[MAX_QPATH]; + int bufferIndex; + + pString = params; + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "G_Scripting: accum without a buffer index\n" ); + } + + bufferIndex = atoi( token ); + if ( bufferIndex >= G_MAX_SCRIPT_ACCUM_BUFFERS ) { + G_Error( "G_Scripting: accum buffer is outside range (0 - %i)\n", G_MAX_SCRIPT_ACCUM_BUFFERS ); + } + + token = COM_ParseExt( &pString, qfalse ); + if ( !token[0] ) { + G_Error( "G_Scripting: accum without a command\n" ); + } + + Q_strncpyz( lastToken, token, sizeof( lastToken ) ); + token = COM_ParseExt( &pString, qfalse ); + + if ( !Q_stricmp( lastToken, "inc" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + ent->scriptAccumBuffer[bufferIndex] += atoi( token ); + } else if ( !Q_stricmp( lastToken, "abort_if_less_than" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( ent->scriptAccumBuffer[bufferIndex] < atoi( token ) ) { + // abort the current script + ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "abort_if_greater_than" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( ent->scriptAccumBuffer[bufferIndex] > atoi( token ) ) { + // abort the current script + ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "abort_if_not_equal" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( ent->scriptAccumBuffer[bufferIndex] != atoi( token ) ) { + // abort the current script + ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "abort_if_equal" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( ent->scriptAccumBuffer[bufferIndex] == atoi( token ) ) { + // abort the current script + ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "bitset" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + ent->scriptAccumBuffer[bufferIndex] |= ( 1 << atoi( token ) ); + } else if ( !Q_stricmp( lastToken, "bitreset" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + ent->scriptAccumBuffer[bufferIndex] &= ~( 1 << atoi( token ) ); + } else if ( !Q_stricmp( lastToken, "abort_if_bitset" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( ent->scriptAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) { + // abort the current script + ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "abort_if_not_bitset" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + if ( !( ent->scriptAccumBuffer[bufferIndex] & ( 1 << atoi( token ) ) ) ) { + // abort the current script + ent->scriptStatus.scriptStackHead = ent->scriptEvents[ent->scriptStatus.scriptEventIndex].stack.numItems; + } + } else if ( !Q_stricmp( lastToken, "set" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + ent->scriptAccumBuffer[bufferIndex] = atoi( token ); + } else if ( !Q_stricmp( lastToken, "random" ) ) { + if ( !token[0] ) { + G_Error( "Scripting: accum %s requires a parameter\n", lastToken ); + } + ent->scriptAccumBuffer[bufferIndex] = rand() % atoi( token ); + } else { + G_Error( "Scripting: accum %s: unknown command\n", params ); + } + + return qtrue; +} + +/* +================= +G_ScriptAction_MissionFailed + + syntax: missionfailed +================= +*/ +qboolean G_ScriptAction_MissionFailed( gentity_t *ent, char *params ) { + // todo!! (just kill the player for now) + gentity_t *player; + player = AICast_FindEntityForName( "player" ); + if ( player ) { + G_Damage( player, player, player, vec3_origin, vec3_origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE ); + } + + G_Printf( "Mission Failed...\n" ); // todo + + return qtrue; +} + +/* +================= +G_ScriptAction_MissionSuccess + + syntax: missionsuccess +================= +*/ +qboolean G_ScriptAction_MissionSuccess( gentity_t *ent, char *params ) { + gentity_t *player; + + if ( !params || !params[0] ) { + G_Error( "G_Scripting: missionsuccess requires a mission_level identifier\n" ); + } + + player = AICast_FindEntityForName( "player" ); + // double check that they are still alive + if ( player->health <= 0 ) { + return qfalse; // hold the script here + + } + player->missionLevel = atoi( params ); + + G_Printf( "Mission Success!!!!\n" ); // todo + +// G_SaveGame( NULL ); + + return qtrue; +} + +/* +================= +G_ScriptAction_Print + + syntax: print + + Mostly for debugging purposes +================= +*/ +qboolean G_ScriptAction_Print( gentity_t *ent, char *params ) { + if ( !params || !params[0] ) { + G_Error( "G_Scripting: print requires some text\n" ); + } + + G_Printf( "(G_Script) %s-> %s\n", ent->scriptName, params ); + return qtrue; +} + +/* +================= +G_ScriptAction_FaceAngles + + syntax: faceangles [ACCEL/DECCEL] + + The entity will face the given angles, taking to get their. If the + GOTOTIME is given instead of a timed duration, the duration calculated from the + last gotomarker command will be used instead. +================= +*/ +qboolean G_ScriptAction_FaceAngles( gentity_t *ent, char *params ) { + char *pString, *token; + int duration, i; + vec3_t diff; + vec3_t angles; + int trType = TR_LINEAR_STOP; + + if ( !params || !params[0] ) { + G_Error( "G_Scripting: syntax: faceangles \n" ); + } + + if ( ent->scriptStatus.scriptStackChangeTime == level.time ) { + pString = params; + for ( i = 0; i < 3; i++ ) { + token = COM_Parse( &pString ); + if ( !token || !token[0] ) { + G_Error( "G_Scripting: syntax: faceangles \n" ); + } + angles[i] = atoi( token ); + } + + token = COM_Parse( &pString ); + if ( !token || !token[0] ) { + G_Error( "G_Scripting: faceangles requires a \n" ); + } + if ( !Q_strcasecmp( token, "gototime" ) ) { + duration = ent->s.pos.trDuration; + } else { + duration = atoi( token ); + } + + token = COM_Parse( &pString ); + if ( token && token[0] ) { + if ( !Q_strcasecmp( token, "accel" ) ) { + trType = TR_ACCELERATE; + } + if ( !Q_strcasecmp( token, "deccel" ) ) { + trType = TR_DECCELERATE; + } + } + + for ( i = 0; i < 3; i++ ) { + diff[i] = AngleDifference( angles[i], ent->s.angles[i] ); + while ( diff[i] > 180 ) + diff[i] -= 360; + while ( diff[i] < -180 ) + diff[i] += 360; + } + + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + if ( duration ) { + VectorScale( diff, 1000.0 / (float)duration, ent->s.apos.trDelta ); + } else { + VectorClear( ent->s.apos.trDelta ); + } + ent->s.apos.trDuration = duration; + ent->s.apos.trTime = level.time; + ent->s.apos.trType = TR_LINEAR_STOP; + + if ( trType != TR_LINEAR_STOP ) { // accel / deccel logic + // calc the speed from duration and start/end delta + for ( i = 0; i < 3; i++ ) { + ent->s.apos.trDelta[i] = 2.0 * 1000.0 * diff[i] / (float)duration; + } + ent->s.apos.trType = trType; + } + + } else if ( ent->s.apos.trTime + ent->s.apos.trDuration <= level.time ) { + // finished turning + BG_EvaluateTrajectory( &ent->s.apos, ent->s.apos.trTime + ent->s.apos.trDuration, ent->s.angles ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + VectorCopy( ent->s.angles, ent->r.currentAngles ); + ent->s.apos.trTime = level.time; + ent->s.apos.trDuration = 0; + ent->s.apos.trType = TR_STATIONARY; + VectorClear( ent->s.apos.trDelta ); + + script_linkentity( ent ); + + return qtrue; + } + + BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles ); + script_linkentity( ent ); + + return qfalse; +} + +/* +=================== +G_ScriptAction_ResetScript + + causes any currently running scripts to abort, in favour of the current script +=================== +*/ +qboolean G_ScriptAction_ResetScript( gentity_t *ent, char *params ) { + if ( level.time == ent->scriptStatus.scriptStackChangeTime ) { + return qfalse; + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_TagConnect + + syntax: attachtotag + + connect this entity onto the tag of another entity +=================== +*/ +qboolean G_ScriptAction_TagConnect( gentity_t *ent, char *params ) { + char *pString, *token; + gentity_t *parent; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_TagConnect: syntax: attachtotag \n" ); + } + + parent = G_Find( NULL, FOFS( targetname ), token ); + if ( !parent ) { + parent = G_Find( NULL, FOFS( scriptName ), token ); + if ( !parent ) { + G_Error( "G_ScriptAction_TagConnect: unable to find entity with targetname \"%s\"", token ); + } + } + + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_TagConnect: syntax: attachtotag \n" ); + } + + ent->tagParent = parent; + ent->tagName = G_Alloc( strlen( token ) + 1 ); + Q_strncpyz( ent->tagName, token, strlen( token ) + 1 ); + + G_ProcessTagConnect( ent ); + + // clear out the angles so it always starts out facing the tag direction + VectorClear( ent->s.angles ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + ent->s.apos.trTime = level.time; + ent->s.apos.trDuration = 0; + ent->s.apos.trType = TR_STATIONARY; + VectorClear( ent->s.apos.trDelta ); + + return qtrue; +} + +/* +==================== +G_ScriptAction_Halt + + syntax: halt + + Stop moving. +==================== +*/ +qboolean G_ScriptAction_Halt( gentity_t *ent, char *params ) { + if ( level.time == ent->scriptStatus.scriptStackChangeTime ) { + ent->scriptStatus.scriptFlags &= ~SCFL_GOING_TO_MARKER; + + // stop the angles + BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->s.angles ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + VectorCopy( ent->s.angles, ent->r.currentAngles ); + ent->s.apos.trTime = level.time; + ent->s.apos.trDuration = 0; + ent->s.apos.trType = TR_STATIONARY; + VectorClear( ent->s.apos.trDelta ); + + // stop moving + BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->s.origin ); + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + ent->s.pos.trTime = level.time; + ent->s.pos.trDuration = 0; + ent->s.pos.trType = TR_STATIONARY; + VectorClear( ent->s.pos.trDelta ); + + script_linkentity( ent ); + + return qfalse; // kill any currently running script + } else { + return qtrue; + } +} + +/* +=================== +G_ScriptAction_StopSound + + syntax: stopsound + + Stops any looping sounds for this entity. +=================== +*/ +qboolean G_ScriptAction_StopSound( gentity_t *ent, char *params ) { + ent->s.loopSound = 0; + return qtrue; +} + +/* +=================== +G_ScriptAction_StartCam + + syntax: startcam +=================== +*/ +qboolean G_ScriptAction_StartCam( gentity_t *ent, char *params ) { + char *pString, *token; + gentity_t *player; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_Cam: filename parameter required\n" ); + } + + // turn off noclient flag + ent->r.svFlags &= ~SVF_NOCLIENT; + + // issue a start camera command to the client + player = AICast_FindEntityForName( "player" ); + if ( !player ) { + G_Error( "player not found, perhaps you should give them more time to spawn in" ); + } + trap_SendServerCommand( player->s.number, va( "startCam %s", token ) ); + + return qtrue; +} + +/* +================= +G_ScriptAction_EntityScriptName +================= +*/ +qboolean G_ScriptAction_EntityScriptName( gentity_t *ent, char *params ) { + trap_Cvar_Set( "g_scriptName", params ); + return qtrue; +} + + +/* +================= +G_ScriptAction_AIScriptName +================= +*/ +qboolean G_ScriptAction_AIScriptName( gentity_t *ent, char *params ) { + trap_Cvar_Set( "ai_scriptName", params ); + return qtrue; +} + +// ----------------------------------------------------------------------- + +// DHM - Nerve :: Multiplayer scripting commands +/* +=================== +G_ScriptAction_MapDescription + + syntax: wm_mapdescription <"long description of map in quotes"> +=================== +*/ +qboolean G_ScriptAction_MapDescription( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + pString = params; + token = COM_Parse( &pString ); + + trap_GetConfigstring( CS_MULTI_MAPDESC, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( cs, token ) ) { + trap_SetConfigstring( CS_MULTI_MAPDESC, token ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_OverviewImage + + syntax: wm_mapdescription +=================== +*/ +qboolean G_ScriptAction_OverviewImage( gentity_t *ent, char *params ) { // NERVE - SMF + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_OverviewImage: shader name required\n" ); + } + + trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "overviewimage" ), token ) ) { + Info_SetValueForKey( cs, "overviewimage", token ); + + trap_SetConfigstring( CS_MULTI_INFO, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_AxisRespawntime + + syntax: wm_axis_respawntime +=================== +*/ +qboolean G_ScriptAction_AxisRespawntime( gentity_t *ent, char *params ) { + char *pString, *token; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_AxisRespawntime: time parameter required\n" ); + } + + if ( g_userAxisRespawnTime.integer ) { + trap_Cvar_Set( "g_redlimbotime", va( "%i", g_userAxisRespawnTime.integer * 1000 ) ); + } else { + trap_Cvar_Set( "g_redlimbotime", va( "%s000", token ) ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_AlliedRespawntime + + syntax: wm_allied_respawntime +=================== +*/ +qboolean G_ScriptAction_AlliedRespawntime( gentity_t *ent, char *params ) { + char *pString, *token; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_AlliedRespawntime: time parameter required\n" ); + } + + if ( g_userAlliedRespawnTime.integer ) { + trap_Cvar_Set( "g_bluelimbotime", va( "%i", g_userAlliedRespawnTime.integer * 1000 ) ); + } else { + trap_Cvar_Set( "g_bluelimbotime", va( "%s000", token ) ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_NumberofObjectives + + syntax: wm_number_of_objectives +=================== +*/ +qboolean G_ScriptAction_NumberofObjectives( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + int num; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_NumberofObjectives: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < 1 || num > MAX_OBJECTIVES ) { + G_Error( "G_ScriptAction_NumberofObjectives: Invalid number of objectives\n" ); + } + + trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "numobjectives" ), token ) ) { + Info_SetValueForKey( cs, "numobjectives", token ); + + trap_SetConfigstring( CS_MULTI_INFO, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_ObjectiveAxisDesc + + syntax: wm_objective_axis_desc +=================== +*/ +qboolean G_ScriptAction_ObjectiveAxisDesc( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + int num, cs_obj = CS_MULTI_OBJECTIVE1; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveAxisDesc: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < 1 || num > MAX_OBJECTIVES ) { + G_Error( "G_ScriptAction_ObjectiveAxisDesc: Invalid objective number\n" ); + } + + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveAxisDesc: description parameter required\n" ); + } + + // Move to correct objective config string + cs_obj += ( num - 1 ); + + trap_GetConfigstring( cs_obj, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "axis_desc" ), token ) ) { + Info_SetValueForKey( cs, "axis_desc", token ); + + trap_SetConfigstring( cs_obj, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_ObjectiveShortAxisDesc + + syntax: wm_objective_short_axis_desc + + NERVE - SMF - this is the short, one-line description shown in scoreboard +=================== +*/ +qboolean G_ScriptAction_ObjectiveShortAxisDesc( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + int num, cs_obj = CS_MULTI_OBJECTIVE1; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveShortAxisDesc: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < 1 || num > MAX_OBJECTIVES ) { + G_Error( "G_ScriptAction_ObjectiveShortAxisDesc: Invalid objective number\n" ); + } + + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveShortAxisDesc: description parameter required\n" ); + } + + // Move to correct objective config string + cs_obj += ( num - 1 ); + + trap_GetConfigstring( cs_obj, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "short_axis_desc" ), token ) ) { + Info_SetValueForKey( cs, "short_axis_desc", token ); + + trap_SetConfigstring( cs_obj, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_ObjectiveAlliedDesc + + syntax: wm_objective_allied_desc +=================== +*/ +qboolean G_ScriptAction_ObjectiveAlliedDesc( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + int num, cs_obj = CS_MULTI_OBJECTIVE1; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveAlliedDesc: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < 1 || num > MAX_OBJECTIVES ) { + G_Error( "G_ScriptAction_ObjectiveAlliedDesc: Invalid objective number\n" ); + } + + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveAlliedDesc: description parameter required\n" ); + } + + // Move to correct objective config string + cs_obj += ( num - 1 ); + + trap_GetConfigstring( cs_obj, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "allied_desc" ), token ) ) { + Info_SetValueForKey( cs, "allied_desc", token ); + + trap_SetConfigstring( cs_obj, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_ObjectiveShortAlliedDesc + + syntax: wm_objective_short_allied_desc + + NERVE - SMF - this is the short, one-line description shown in scoreboard +=================== +*/ +qboolean G_ScriptAction_ObjectiveShortAlliedDesc( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + int num, cs_obj = CS_MULTI_OBJECTIVE1; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveShortAlliedDesc: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < 1 || num > MAX_OBJECTIVES ) { + G_Error( "G_ScriptAction_ObjectiveShortAlliedDesc: Invalid objective number\n" ); + } + + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveShortAlliedDesc: description parameter required\n" ); + } + + // Move to correct objective config string + cs_obj += ( num - 1 ); + + trap_GetConfigstring( cs_obj, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "short_allied_desc" ), token ) ) { + Info_SetValueForKey( cs, "short_allied_desc", token ); + + trap_SetConfigstring( cs_obj, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_ObjectiveImage + + syntax: wm_objective_image +=================== +*/ +qboolean G_ScriptAction_ObjectiveImage( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + int num, cs_obj = CS_MULTI_OBJECTIVE1; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveImage: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < 1 || num > MAX_OBJECTIVES ) { + G_Error( "G_ScriptAction_ObjectiveImage: Invalid objective number\n" ); + } + + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_ObjectiveImage: shadername parameter required\n" ); + } + + // Move to correct objective config string + cs_obj += ( num - 1 ); + + trap_GetConfigstring( cs_obj, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "image" ), token ) ) { + Info_SetValueForKey( cs, "image", token ); + + trap_SetConfigstring( cs_obj, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_SetWinner + + syntax: wm_setwinner + + team: 0==AXIS, 1==ALLIED +=================== +*/ +qboolean G_ScriptAction_SetWinner( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + int num; + + if ( level.intermissiontime ) { + return qtrue; + } + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_SetWinner: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < -1 || num > 1 ) { + G_Error( "G_ScriptAction_SetWinner: Invalid team number\n" ); + } + + trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "winner" ), token ) ) { + Info_SetValueForKey( cs, "winner", token ); + + trap_SetConfigstring( CS_MULTI_MAPWINNER, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_SetObjectiveStatus + + syntax: wm_set_objective_status + + status: -1==neutral, 0==held by axis, 1==held by allies +=================== +*/ +qboolean G_ScriptAction_SetObjectiveStatus( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + + int num, status, cs_obj = CS_MULTI_OBJ1_STATUS; + + if ( level.intermissiontime ) { + return qtrue; + } + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_SetObjectiveStatus: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < 1 || num > MAX_OBJECTIVES ) { + G_Error( "G_ScriptAction_SetObjectiveStatus: Invalid objective number\n" ); + } + + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_SetObjectiveStatus: status parameter required\n" ); + } + + status = atoi( token ); + if ( status < -1 || status > 1 ) { + G_Error( "G_ScriptAction_SetObjectiveStatus: Invalid status number\n" ); + } + + // Move to correct objective config string + cs_obj += ( num - 1 ); + + trap_GetConfigstring( cs_obj, cs, sizeof( cs ) ); + + // NERVE - SMF - compare before setting, so we don't spam the clients during map_restart + if ( Q_stricmp( Info_ValueForKey( cs, "status" ), token ) ) { + Info_SetValueForKey( cs, "status", token ); + + trap_SetConfigstring( cs_obj, cs ); + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_SetDefendingTeam + + syntax: wm_set_objective_status + + status: 0==axis, 1==allies + + NERVE - SMF - sets defending team for stopwatch mode +=================== +*/ +qboolean G_ScriptAction_SetDefendingTeam( gentity_t *ent, char *params ) { + char *pString, *token; + char cs[MAX_STRING_CHARS]; + int num; + + if ( level.intermissiontime ) { + return qtrue; + } + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_SetDefendingTeam: number parameter required\n" ); + } + + num = atoi( token ); + if ( num < 0 || num > 1 ) { + G_Error( "G_ScriptAction_SetDefendingTeam: Invalid team number\n" ); + } + + trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) ); + + Info_SetValueForKey( cs, "defender", token ); + + trap_SetConfigstring( CS_MULTI_INFO, cs ); + + return qtrue; +} + +/* +=================== +G_ScriptAction_Announce + + syntax: wm_announce <"text to send to all clients"> +=================== +*/ +qboolean G_ScriptAction_Announce( gentity_t *ent, char *params ) { + char *pString, *token; + + if ( level.intermissiontime ) { + return qtrue; + } + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_Announce: statement parameter required\n" ); + } + + trap_SendServerCommand( -1, va( "cp \"%s\" 2", token ) ); + + return qtrue; +} + +/* +=================== +G_ScriptAction_EndRound + + syntax: wm_endround <> +=================== +*/ + +extern void LogExit( const char *string ); + +qboolean G_ScriptAction_EndRound( gentity_t *ent, char *params ) { + if ( level.intermissiontime ) { + return qtrue; + } + + LogExit( "Wolf EndRound." ); + + return qtrue; +} + +/* +=================== +G_ScriptAction_SetRoundTimelimit + + syntax: wm_set_round_timelimit +=================== +*/ +qboolean G_ScriptAction_SetRoundTimelimit( gentity_t *ent, char *params ) { + char *pString, *token; + float nextTimeLimit; + + pString = params; + token = COM_Parse( &pString ); + if ( !token[0] ) { + G_Error( "G_ScriptAction_SetRoundTimelimit: number parameter required\n" ); + } + + // NERVE - SMF + nextTimeLimit = g_nextTimeLimit.value; + + if ( g_gametype.integer == GT_WOLF_STOPWATCH && nextTimeLimit ) { + trap_Cvar_Set( "timelimit", va( "%f", nextTimeLimit ) ); + } else { + if ( g_userTimeLimit.integer ) { + trap_Cvar_Set( "timelimit", va( "%i", g_userTimeLimit.integer ) ); + } else { + trap_Cvar_Set( "timelimit", token ); + } + } + + return qtrue; +} + +/* +=================== +G_ScriptAction_RemoveEntity + + syntax: remove +=================== +*/ +qboolean G_ScriptAction_RemoveEntity( gentity_t *ent, char *params ) { + ent->think = G_FreeEntity; + ent->nextthink = level.time + FRAMETIME; + + return qtrue; +} diff --git a/src/game/g_session.c b/src/game/g_session.c new file mode 100644 index 0000000..08d51b4 --- /dev/null +++ b/src/game/g_session.c @@ -0,0 +1,235 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + + +/* +======================================================================= + + SESSION DATA + +Session data is the only data that stays persistant across level loads +and tournament restarts. +======================================================================= +*/ + +/* +================ +G_WriteClientSessionData + +Called on game shutdown +================ +*/ +void G_WriteClientSessionData( gclient_t *client ) { + const char *s; + const char *var; + + s = va( "%i %i %i %i %i %i %i %i %i %i %i %i %i %i %i", // DHM - Nerve + client->sess.sessionTeam, + client->sess.spectatorTime, + client->sess.spectatorState, + client->sess.spectatorClient, + client->sess.wins, + client->sess.losses, + client->sess.playerType, // DHM - Nerve + client->sess.playerWeapon, // DHM - Nerve + client->sess.playerItem, // DHM - Nerve + client->sess.playerSkin, // DHM - Nerve + client->sess.spawnObjectiveIndex, // DHM - Nerve + client->sess.latchPlayerType, // DHM - Nerve + client->sess.latchPlayerWeapon, // DHM - Nerve + client->sess.latchPlayerItem, // DHM - Nerve + client->sess.latchPlayerSkin // DHM - Nerve + ); + + var = va( "session%i", client - level.clients ); + + trap_Cvar_Set( var, s ); +} + +/* +================ +G_ReadSessionData + +Called on a reconnect +================ +*/ +void G_ReadSessionData( gclient_t *client ) { + char s[MAX_STRING_CHARS]; + const char *var; + qboolean test; + + var = va( "session%i", client - level.clients ); + trap_Cvar_VariableStringBuffer( var, s, sizeof( s ) ); + + sscanf( s, "%i %i %i %i %i %i %i %i %i %i %i %i %i %i %i", // DHM - Nerve + (int *)&client->sess.sessionTeam, + &client->sess.spectatorTime, + (int *)&client->sess.spectatorState, + &client->sess.spectatorClient, + &client->sess.wins, + &client->sess.losses, + &client->sess.playerType, // DHM - Nerve + &client->sess.playerWeapon, // DHM - Nerve + &client->sess.playerItem, // DHM - Nerve + &client->sess.playerSkin, // DHM - Nerve + &client->sess.spawnObjectiveIndex, // DHM - Nerve + &client->sess.latchPlayerType, // DHM - Nerve + &client->sess.latchPlayerWeapon, // DHM - Nerve + &client->sess.latchPlayerItem, // DHM - Nerve + &client->sess.latchPlayerSkin // DHM - Nerve + ); + + // NERVE - SMF + if ( g_altStopwatchMode.integer ) { + test = qtrue; + } else { + test = g_currentRound.integer == 1; + } + + if ( g_gametype.integer == GT_WOLF_STOPWATCH && level.warmupTime > 0 && test ) { + if ( client->sess.sessionTeam == TEAM_RED ) { + client->sess.sessionTeam = TEAM_BLUE; + } else if ( client->sess.sessionTeam == TEAM_BLUE ) { + client->sess.sessionTeam = TEAM_RED; + } + } + + if ( g_swapteams.integer ) { + trap_Cvar_Set( "g_swapteams", "0" ); + + if ( client->sess.sessionTeam == TEAM_RED ) { + client->sess.sessionTeam = TEAM_BLUE; + } else if ( client->sess.sessionTeam == TEAM_BLUE ) { + client->sess.sessionTeam = TEAM_RED; + } + } +} + + +/* +================ +G_InitSessionData + +Called on a first-time connect +================ +*/ +void G_InitSessionData( gclient_t *client, char *userinfo ) { + clientSession_t *sess; + const char *value; + + sess = &client->sess; + + // initial team determination + if ( g_gametype.integer >= GT_TEAM ) { + // always spawn as spectator in team games + sess->sessionTeam = TEAM_SPECTATOR; + } else { + value = Info_ValueForKey( userinfo, "team" ); + if ( value[0] == 's' ) { + // a willing spectator, not a waiting-in-line + sess->sessionTeam = TEAM_SPECTATOR; + } else { + switch ( g_gametype.integer ) { + default: + case GT_FFA: + case GT_SINGLE_PLAYER: + if ( g_maxGameClients.integer > 0 && + level.numNonSpectatorClients >= g_maxGameClients.integer ) { + sess->sessionTeam = TEAM_SPECTATOR; + } else { + sess->sessionTeam = TEAM_FREE; + } + break; + case GT_TOURNAMENT: + // if the game is full, go into a waiting mode + if ( level.numNonSpectatorClients >= 2 ) { + sess->sessionTeam = TEAM_SPECTATOR; + } else { + sess->sessionTeam = TEAM_FREE; + } + break; + } + } + } + + sess->spectatorState = SPECTATOR_FREE; + sess->spectatorTime = level.time; + + // DHM - Nerve + sess->latchPlayerType = sess->playerType = 0; + sess->latchPlayerWeapon = sess->playerWeapon = 0; + sess->latchPlayerItem = sess->playerItem = 0; + sess->latchPlayerSkin = sess->playerSkin = 0; + + sess->spawnObjectiveIndex = 0; + // dhm - end + + G_WriteClientSessionData( client ); +} + + +/* +================== +G_InitWorldSession + +================== +*/ +void G_InitWorldSession( void ) { + char s[MAX_STRING_CHARS]; + int gt; + + trap_Cvar_VariableStringBuffer( "session", s, sizeof( s ) ); + gt = atoi( s ); + + // if the gametype changed since the last session, don't use any + // client sessions + if ( g_gametype.integer != gt ) { + level.newSession = qtrue; + G_Printf( "Gametype changed, clearing session data.\n" ); + } +} + +/* +================== +G_WriteSessionData + +================== +*/ +void G_WriteSessionData( void ) { + int i; + + trap_Cvar_Set( "session", va( "%i", g_gametype.integer ) ); + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected == CON_CONNECTED ) { + G_WriteClientSessionData( &level.clients[i] ); + } + } +} diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c new file mode 100644 index 0000000..6db6fb0 --- /dev/null +++ b/src/game/g_spawn.c @@ -0,0 +1,1076 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: g_spawn.c + * + * desc: + * +*/ + +#include "g_local.h" + +qboolean G_SpawnString( const char *key, const char *defaultString, char **out ) { + int i; + + if ( !level.spawning ) { + *out = (char *)defaultString; +// G_Error( "G_SpawnString() called while not spawning" ); + } + + for ( i = 0 ; i < level.numSpawnVars ; i++ ) { + if ( !strcmp( key, level.spawnVars[i][0] ) ) { + *out = level.spawnVars[i][1]; + return qtrue; + } + } + + *out = (char *)defaultString; + return qfalse; +} + +qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out ) { + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + *out = atof( s ); + return present; +} + +qboolean G_SpawnInt( const char *key, const char *defaultString, int *out ) { + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + *out = atoi( s ); + return present; +} + +qboolean G_SpawnVector( const char *key, const char *defaultString, float *out ) { + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + sscanf( s, "%f %f %f", &out[0], &out[1], &out[2] ); + return present; +} + + + +// +// fields are needed for spawning from the entity string +// +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_ENTITY, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + +field_t fields[] = { + {"classname", FOFS( classname ), F_LSTRING}, + {"origin", FOFS( s.origin ), F_VECTOR}, + {"model", FOFS( model ), F_LSTRING}, + {"model2", FOFS( model2 ), F_LSTRING}, + {"spawnflags", FOFS( spawnflags ), F_INT}, + {"speed", FOFS( speed ), F_FLOAT}, + {"closespeed", FOFS( closespeed ), F_FLOAT}, //----(SA) added + {"target", FOFS( target ), F_LSTRING}, + {"targetname", FOFS( targetname ), F_LSTRING}, + {"message", FOFS( message ), F_LSTRING}, + {"popup", FOFS( message ), F_LSTRING}, // (SA) mutually exclusive from 'message', but makes the ent more logical for the level designer + {"book", FOFS( message ), F_LSTRING}, // (SA) mutually exclusive from 'message', but makes the ent more logical for the level designer + {"team", FOFS( team ), F_LSTRING}, + {"wait", FOFS( wait ), F_FLOAT}, + {"random", FOFS( random ), F_FLOAT}, + {"count", FOFS( count ), F_INT}, + {"health", FOFS( health ), F_INT}, + {"light", 0, F_IGNORE}, + {"dmg", FOFS( damage ), F_INT}, + {"angles", FOFS( s.angles ), F_VECTOR}, + {"angle", FOFS( s.angles ), F_ANGLEHACK}, + // JOSEPH 9-27-99 + {"duration", FOFS( duration ), F_FLOAT}, + {"rotate", FOFS( rotate ), F_VECTOR}, + // END JOSEPH + {"degrees", FOFS( angle ), F_FLOAT}, + {"time", FOFS( speed ), F_FLOAT}, + + // Ridah, AI fields + {"aiattributes", FOFS( aiAttributes ), F_LSTRING}, + {"ainame", FOFS( aiName ), F_LSTRING}, + {"aiteam", FOFS( aiTeam ), F_INT}, + // done. + + //----(SA) additional ai field + {"skin", FOFS( aiSkin ), F_LSTRING}, + {"head", FOFS( aihSkin ), F_LSTRING}, + + //----(SA) done + + // (SA) dlight lightstyles (made all these unique variables for testing) + {"_color", FOFS( dl_color ), F_VECTOR}, // color of the light (the underscore is inserted by the color picker in QER) + {"color", FOFS( dl_color ), F_VECTOR}, // color of the light + {"stylestring", FOFS( dl_stylestring ), F_LSTRING}, // user defined stylestring "fffndlsfaaaaaa" for example + // done + + //----(SA) + {"shader", FOFS( dl_shader ), F_LSTRING}, // shader to use for a target_effect or dlight + + // (SA) for target_unlock + {"key", FOFS( key ), F_INT}, + // done + + // (SA) for item placement + {"stand", FOFS( s.frame ), F_INT}, + // (SA) end + + // Rafael - mg42 + {"harc", FOFS( harc ), F_FLOAT}, + {"varc", FOFS( varc ), F_FLOAT}, + // done. + + // Rafael - sniper + {"delay", FOFS( delay ), F_FLOAT}, + {"radius", FOFS( radius ), F_INT}, + + // Ridah, for reloading savegames at correct mission spot + {"missionlevel", FOFS( missionLevel ), F_INT}, + + // Rafel + {"start_size", FOFS( start_size ), F_INT}, + {"end_size", FOFS( end_size ), F_INT}, + + {"shard", FOFS( count ), F_INT}, + + // Rafael + {"spawnitem", FOFS( spawnitem ), F_LSTRING}, + + {"track", FOFS( track ), F_LSTRING}, + + {"scriptName", FOFS( scriptName ), F_LSTRING}, + + {NULL} +}; + + +typedef struct { + char *name; + void ( *spawn )( gentity_t *ent ); +} spawn_t; + +void SP_info_player_start( gentity_t *ent ); +void SP_info_player_checkpoint( gentity_t *ent ); +void SP_info_player_deathmatch( gentity_t *ent ); +void SP_info_player_intermission( gentity_t *ent ); +void SP_info_firstplace( gentity_t *ent ); +void SP_info_secondplace( gentity_t *ent ); +void SP_info_thirdplace( gentity_t *ent ); +void SP_info_podium( gentity_t *ent ); + +void SP_func_plat( gentity_t *ent ); +void SP_func_static( gentity_t *ent ); +void SP_func_leaky( gentity_t *ent ); //----(SA) added +void SP_func_rotating( gentity_t *ent ); +void SP_func_bobbing( gentity_t *ent ); +void SP_func_pendulum( gentity_t *ent ); +void SP_func_button( gentity_t *ent ); +void SP_func_explosive( gentity_t *ent ); +void SP_func_door( gentity_t *ent ); +void SP_func_train( gentity_t *ent ); +void SP_func_timer( gentity_t *self ); +// JOSEPH 1-26-00 +void SP_func_train_rotating( gentity_t *ent ); +void SP_func_secret( gentity_t *ent ); +// END JOSEPH +// Rafael +void SP_func_door_rotating( gentity_t *ent ); +// RF +void SP_func_bats( gentity_t *self ); + +void SP_trigger_always( gentity_t *ent ); +void SP_trigger_multiple( gentity_t *ent ); +void SP_trigger_push( gentity_t *ent ); +void SP_trigger_teleport( gentity_t *ent ); +void SP_trigger_hurt( gentity_t *ent ); + +//---- (SA) Wolf triggers +void SP_trigger_concussive_dust( gentity_t *ent ); // JPW NERVE +void SP_trigger_once( gentity_t *ent ); +//---- done + +void SP_target_remove_powerups( gentity_t *ent ); +void SP_target_give( gentity_t *ent ); +void SP_target_delay( gentity_t *ent ); +void SP_target_speaker( gentity_t *ent ); +void SP_target_print( gentity_t *ent ); +void SP_target_laser( gentity_t *self ); +void SP_target_character( gentity_t *ent ); +void SP_target_score( gentity_t *ent ); +void SP_target_teleporter( gentity_t *ent ); +void SP_target_relay( gentity_t *ent ); +void SP_target_kill( gentity_t *ent ); +void SP_target_position( gentity_t *ent ); +void SP_target_location( gentity_t *ent ); +void SP_target_push( gentity_t *ent ); +void SP_target_script_trigger( gentity_t *ent ); + +//---- (SA) Wolf targets +// targets +void SP_target_alarm( gentity_t *ent ); +void SP_target_counter( gentity_t *ent ); +void SP_target_lock( gentity_t *ent ); +void SP_target_effect( gentity_t *ent ); +void SP_target_fog( gentity_t *ent ); +void SP_target_autosave( gentity_t *ent ); + +// entity visibility dummy +void SP_misc_vis_dummy( gentity_t *ent ); +void SP_misc_vis_dummy_multiple( gentity_t *ent ); + +//----(SA) done + +void SP_light( gentity_t *self ); +void SP_info_null( gentity_t *self ); +void SP_info_notnull( gentity_t *self ); +void SP_info_camp( gentity_t *self ); +void SP_path_corner( gentity_t *self ); + +void SP_misc_teleporter_dest( gentity_t *self ); +void SP_misc_model( gentity_t *ent ); +void SP_misc_gamemodel( gentity_t *ent ); +void SP_misc_portal_camera( gentity_t *ent ); +void SP_misc_portal_surface( gentity_t *ent ); +void SP_misc_light_surface( gentity_t *ent ); +void SP_misc_grabber_trap( gentity_t *ent ); +void SP_misc_spotlight( gentity_t *ent ); //----(SA) added + +void SP_shooter_rocket( gentity_t *ent ); +void SP_shooter_grenade( gentity_t *ent ); + +void SP_team_CTF_redplayer( gentity_t *ent ); +void SP_team_CTF_blueplayer( gentity_t *ent ); + +void SP_team_CTF_redspawn( gentity_t *ent ); +void SP_team_CTF_bluespawn( gentity_t *ent ); + +// JPW NERVE for multiplayer spawnpoint selection +void SP_team_WOLF_objective( gentity_t *ent ); +// jpw + +void SP_team_WOLF_checkpoint( gentity_t *ent ); // DHM - Nerve + +// JOSEPH 1-18-00 +void SP_props_box_32( gentity_t *self ); +void SP_props_box_48( gentity_t *self ); +void SP_props_box_64( gentity_t *self ); +// END JOSEPH + +// Ridah +void SP_ai_soldier( gentity_t *ent ); +void SP_ai_american( gentity_t *ent ); +void SP_ai_zombie( gentity_t *ent ); +void SP_ai_warzombie( gentity_t *ent ); +void SP_ai_femzombie( gentity_t *ent ); +void SP_ai_undead( gentity_t *ent ); +void SP_ai_marker( gentity_t *ent ); +void SP_ai_effect( gentity_t *ent ); +void SP_ai_trigger( gentity_t *ent ); +void SP_ai_venom( gentity_t *ent ); +void SP_ai_loper( gentity_t *ent ); +void SP_ai_sealoper( gentity_t *ent ); +void SP_ai_boss_helga( gentity_t *ent ); +void SP_ai_boss_heinrich( gentity_t *ent ); //----(SA) added +void SP_ai_eliteguard( gentity_t *ent ); +void SP_ai_stimsoldier_dual( gentity_t *ent ); +void SP_ai_stimsoldier_rocket( gentity_t *ent ); +void SP_ai_stimsoldier_tesla( gentity_t *ent ); +void SP_ai_supersoldier( gentity_t *ent ); +void SP_ai_blackguard( gentity_t *ent ); +void SP_ai_protosoldier( gentity_t *ent ); +void SP_ai_rejectxcreature( gentity_t *ent ); +void SP_ai_frogman( gentity_t *ent ); +void SP_ai_partisan( gentity_t *ent ); +void SP_ai_civilian( gentity_t *ent ); +void SP_ai_chimp( gentity_t *ent ); //----(SA) added +// done. + +// Rafael particles +void SP_Snow( gentity_t *ent ); +void SP_target_smoke( gentity_t *ent ); +void SP_Bubbles( gentity_t *ent ); +// done. + +// (SA) dlights +void SP_dlight( gentity_t *ent ); +// done +void SP_corona( gentity_t *ent ); + +// Rafael mg42 +void SP_mg42( gentity_t *ent ); +// done. + +// Rafael sniper +void SP_shooter_sniper( gentity_t *ent ); +void SP_sniper_brush( gentity_t *ent ); +// done + +//----(SA) +void SP_shooter_zombiespit( gentity_t *ent ); +void SP_shooter_mortar( gentity_t *ent ); +void SP_shooter_tesla( gentity_t *ent ); + +// alarm +void SP_alarm_box( gentity_t *ent ); +//----(SA) end + +void SP_trigger_flagonly( gentity_t *ent ); // DHM - Nerve +void SP_trigger_objective_info( gentity_t *ent ); // DHM - Nerve + +void SP_gas( gentity_t *ent ); +void SP_target_rumble( gentity_t *ent ); +void SP_func_train_particles( gentity_t *ent ); + + +// Rafael +void SP_trigger_aidoor( gentity_t *ent ); +void SP_SmokeDust( gentity_t *ent ); +void SP_Dust( gentity_t *ent ); +void SP_props_sparks( gentity_t *ent ); +void SP_props_gunsparks( gentity_t *ent ); + +// Props +void SP_Props_Bench( gentity_t *ent ); +void SP_Props_Radio( gentity_t *ent ); +void SP_Props_Chair( gentity_t *ent ); +void SP_Props_ChairHiback( gentity_t *ent ); +void SP_Props_ChairSide( gentity_t *ent ); +void SP_Props_ChairChat( gentity_t *ent ); +void SP_Props_ChairChatArm( gentity_t *ent ); +void SP_Props_DamageInflictor( gentity_t *ent ); +void SP_Props_Locker_Tall( gentity_t *ent ); +void SP_Props_Desklamp( gentity_t *ent ); +void SP_Props_Flamebarrel( gentity_t *ent ); +void SP_crate_64( gentity_t *ent ); +void SP_Props_Flipping_Table( gentity_t *ent ); +void SP_crate_32( gentity_t *self ); +void SP_Props_Crate32x64( gentity_t *ent ); +void SP_Props_58x112tablew( gentity_t *ent ); +void SP_props_castlebed( gentity_t *ent ); +void SP_Props_RadioSEVEN( gentity_t *ent ); +void SP_propsFireColumn( gentity_t *ent ); +void SP_props_flamethrower( gentity_t *ent ); + +void SP_func_tramcar( gentity_t *ent ); +void func_invisible_user( gentity_t *ent ); + +void SP_lightJunior( gentity_t *self ); + +void SP_props_me109( gentity_t *ent ); +void SP_misc_flak( gentity_t *ent ); +void SP_plane_waypoint( gentity_t *self ); + +void SP_props_snowGenerator( gentity_t *ent ); +void SP_truck_cam( gentity_t *self ); + +void SP_screen_fade( gentity_t *ent ); +void SP_camera_reset_player( gentity_t *ent ); +void SP_camera_cam( gentity_t *ent ); +void SP_props_decoration( gentity_t *ent ); +void SP_props_decorBRUSH( gentity_t *ent ); +void SP_props_statue( gentity_t *ent ); +void SP_props_statueBRUSH( gentity_t *ent ); +void SP_skyportal( gentity_t *ent ); + +// RF, scripting +void SP_script_model_med( gentity_t *ent ); +void SP_script_mover( gentity_t *ent ); +void SP_script_multiplayer( gentity_t *ent ); // DHM - Nerve + +void SP_misc_mounted_gunner( gentity_t *ent ); +void SP_props_footlocker( gentity_t *self ); +void SP_misc_firetrails( gentity_t *ent ); +void SP_trigger_deathCheck( gentity_t *ent ); +void SP_misc_spawner( gentity_t *ent ); +void SP_props_decor_Scale( gentity_t *ent ); + +spawn_t spawns[] = { + // info entities don't do anything at all, but provide positional + // information for things controlled by other processes + {"info_player_start", SP_info_player_start}, + {"info_player_checkpoint", SP_info_player_checkpoint}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_intermission", SP_info_player_intermission}, + {"info_null", SP_info_null}, + {"info_notnull", SP_info_notnull}, // use target_position instead + {"info_camp", SP_info_camp}, + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_explosive", SP_func_explosive}, + {"func_door", SP_func_door}, + {"func_static", SP_func_static}, + {"func_leaky", SP_func_leaky}, + {"func_rotating", SP_func_rotating}, + {"func_bobbing", SP_func_bobbing}, + {"func_pendulum", SP_func_pendulum}, + {"func_train", SP_func_train}, + {"func_group", SP_info_null}, + // JOSEPH 1-26-00 + {"func_train_rotating", SP_func_train_rotating}, + {"func_secret", SP_func_secret}, + // END JOSEPH + // Rafael + {"func_door_rotating", SP_func_door_rotating}, + + {"func_train_particles", SP_func_train_particles}, + + {"func_timer", SP_func_timer}, // rename trigger_timer? + + {"func_tramcar", SP_func_tramcar}, + {"func_invisible_user", func_invisible_user}, + + {"func_bats", SP_func_bats}, + + // Triggers are brush objects that cause an effect when contacted + // by a living player, usually involving firing targets. + // While almost everything could be done with + // a single trigger class and different targets, triggered effects + // could not be client side predicted (push and teleport). + {"trigger_always", SP_trigger_always}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_push", SP_trigger_push}, + {"trigger_teleport", SP_trigger_teleport}, + {"trigger_hurt", SP_trigger_hurt}, + + //---- (SA) Wolf triggers + {"trigger_concussive_dust", SP_trigger_concussive_dust}, // JPW NERVE + {"trigger_once", SP_trigger_once}, + //---- done + + // Rafael + {"trigger_aidoor", SP_trigger_aidoor}, + {"trigger_deathCheck",SP_trigger_deathCheck}, + + // targets perform no action by themselves, but must be triggered + // by another entity + {"target_give", SP_target_give}, + {"target_remove_powerups", SP_target_remove_powerups}, + {"target_delay", SP_target_delay}, + {"target_speaker", SP_target_speaker}, + {"target_print", SP_target_print}, + {"target_laser", SP_target_laser}, + {"target_score", SP_target_score}, + {"target_teleporter", SP_target_teleporter}, + {"target_relay", SP_target_relay}, + {"target_kill", SP_target_kill}, + {"target_position", SP_target_position}, + {"target_location", SP_target_location}, + {"target_push", SP_target_push}, + {"target_script_trigger", SP_target_script_trigger}, + + //---- (SA) Wolf targets + {"target_alarm", SP_target_alarm}, + {"target_counter", SP_target_counter}, + {"target_lock", SP_target_lock}, + {"target_effect", SP_target_effect}, + {"target_fog", SP_target_fog}, + {"target_autosave", SP_target_autosave}, //----(SA) added + //---- done + + {"target_rumble", SP_target_rumble}, + + + {"light", SP_light}, + + {"lightJunior", SP_lightJunior}, + + {"path_corner", SP_path_corner}, + + {"misc_teleporter_dest", SP_misc_teleporter_dest}, + {"misc_model", SP_misc_model}, + {"misc_gamemodel", SP_misc_gamemodel}, + {"misc_portal_surface", SP_misc_portal_surface}, + {"misc_portal_camera", SP_misc_portal_camera}, + + //----(SA) Wolf misc + {"misc_vis_dummy", SP_misc_vis_dummy}, + {"misc_vis_dummy_multiple", SP_misc_vis_dummy_multiple}, + {"misc_light_surface", SP_misc_light_surface}, + {"misc_grabber_trap", SP_misc_grabber_trap}, + {"misc_spotlight", SP_misc_spotlight}, //----(SA) added + //----(SA) end + + + // Rafael mg42 + {"misc_mg42", SP_mg42}, + // done. + {"misc_flak", SP_misc_flak}, + {"misc_mounted_gunner",SP_misc_mounted_gunner}, + {"misc_firetrails", SP_misc_firetrails}, + + {"shooter_rocket", SP_shooter_rocket}, + {"shooter_grenade", SP_shooter_grenade}, + +//----(SA) + {"shooter_zombiespit", SP_shooter_zombiespit}, + {"shooter_mortar", SP_shooter_mortar}, + {"shooter_tesla", SP_shooter_tesla}, + + // alarm + {"alarm_box", SP_alarm_box}, +//----(SA) end + + // Rafael sniper + {"shooter_sniper", SP_shooter_sniper}, + {"sniper_brush", SP_sniper_brush}, + // done + + {"team_CTF_redplayer", SP_team_CTF_redplayer}, + {"team_CTF_blueplayer", SP_team_CTF_blueplayer}, + + {"team_CTF_redspawn", SP_team_CTF_redspawn}, + {"team_CTF_bluespawn", SP_team_CTF_bluespawn}, + +// JPW NERVE + {"team_WOLF_objective", SP_team_WOLF_objective}, +// jpw + + {"team_WOLF_checkpoint", SP_team_WOLF_checkpoint}, // DHM - Nerve + + // Ridah + {"ai_soldier", SP_ai_soldier}, + {"ai_american", SP_ai_american}, + {"ai_zombie", SP_ai_zombie}, + {"ai_warzombie", SP_ai_warzombie}, + {"ai_femzombie", SP_ai_femzombie}, + {"ai_undead", SP_ai_undead}, + {"ai_venom", SP_ai_venom}, + {"ai_loper", SP_ai_loper}, + {"ai_sealoper", SP_ai_sealoper}, + {"ai_boss_helga", SP_ai_boss_helga}, + {"ai_boss_heinrich", SP_ai_boss_heinrich}, //----(SA) + {"ai_eliteguard", SP_ai_eliteguard}, + {"ai_stimsoldier_dual", SP_ai_stimsoldier_dual}, + {"ai_stimsoldier_rocket", SP_ai_stimsoldier_rocket}, + {"ai_stimsoldier_tesla", SP_ai_stimsoldier_tesla}, + {"ai_supersoldier", SP_ai_supersoldier}, + {"ai_protosoldier", SP_ai_protosoldier}, + {"ai_rejectxcreature", SP_ai_rejectxcreature}, + {"ai_frogman", SP_ai_frogman}, + {"ai_blackguard", SP_ai_blackguard}, + {"ai_partisan", SP_ai_partisan}, + {"ai_civilian", SP_ai_civilian}, + {"ai_chimp", SP_ai_chimp}, //----(SA) added + + + {"ai_marker", SP_ai_marker}, + {"ai_effect", SP_ai_effect}, + {"ai_trigger", SP_ai_trigger}, + // done. + + // Rafael particles + {"misc_snow256", SP_Snow}, + {"misc_snow128", SP_Snow}, + {"misc_snow64", SP_Snow}, + {"misc_snow32", SP_Snow}, + {"target_smoke", SP_target_smoke}, + + {"misc_bubbles8", SP_Bubbles}, + {"misc_bubbles16", SP_Bubbles}, + {"misc_bubbles32", SP_Bubbles}, + {"misc_bubbles64", SP_Bubbles}, + // done. + + {"misc_spawner", SP_misc_spawner}, + + // JOSEPH 1-18-00 + {"props_box_32", SP_props_box_32}, + {"props_box_48", SP_props_box_48}, + {"props_box_64", SP_props_box_64}, + // END JOSEPH + + // Rafael + {"props_smokedust", SP_SmokeDust}, + {"props_dust", SP_Dust}, + {"props_sparks", SP_props_sparks}, + {"props_gunsparks", SP_props_gunsparks}, + + {"plane_waypoint", SP_plane_waypoint}, + + {"props_me109", SP_props_me109}, + + // Rafael - props + {"props_bench", SP_Props_Bench}, + {"props_radio", SP_Props_Radio}, + {"props_chair", SP_Props_Chair}, + {"props_chair_hiback", SP_Props_ChairHiback}, + {"props_chair_side", SP_Props_ChairSide}, + {"props_chair_chat", SP_Props_ChairChat}, + {"props_chair_chatarm", SP_Props_ChairChatArm}, + {"props_damageinflictor", SP_Props_DamageInflictor}, + {"props_locker_tall",SP_Props_Locker_Tall}, + {"props_desklamp", SP_Props_Desklamp}, + {"props_flamebarrel", SP_Props_Flamebarrel}, + {"props_crate_64", SP_crate_64}, + {"props_flippy_table", SP_Props_Flipping_Table}, + {"props_crate_32", SP_crate_32}, + {"props_crate_32x64", SP_Props_Crate32x64}, + {"props_58x112tablew", SP_Props_58x112tablew}, + {"props_castlebed", SP_props_castlebed}, + {"props_radioSEVEN", SP_Props_RadioSEVEN}, + {"props_snowGenerator", SP_props_snowGenerator}, + {"props_FireColumn", SP_propsFireColumn}, + {"props_decoration", SP_props_decoration}, + {"props_decorBRUSH", SP_props_decorBRUSH}, + {"props_statue", SP_props_statue}, + {"props_statueBRUSH", SP_props_statueBRUSH}, + {"props_skyportal", SP_skyportal}, + {"props_footlocker", SP_props_footlocker}, + {"props_flamethrower", SP_props_flamethrower}, + {"props_decoration_scale",SP_props_decor_Scale}, + + {"truck_cam", SP_truck_cam}, + + {"screen_fade", SP_screen_fade}, + {"camera_reset_player", SP_camera_reset_player}, + {"camera_cam",SP_camera_cam}, + + // (SA) dlight and dlightstyles + {"dlight", SP_dlight}, + + //----(SA) light coronas + {"corona", SP_corona}, + + {"test_gas", SP_gas}, + {"trigger_flagonly", SP_trigger_flagonly}, // DHM - Nerve + {"trigger_objective_info", SP_trigger_objective_info}, // DHM - Nerve + + // RF, scripting + {"script_model_med", SP_script_model_med}, + {"script_mover", SP_script_mover}, + {"script_multiplayer", SP_script_multiplayer}, // DHM - Nerve + + {0, 0} +}; + +/* +=============== +G_CallSpawn + +Finds the spawn function for the entity and calls it, +returning qfalse if not found +=============== +*/ +qboolean G_CallSpawn( gentity_t *ent ) { + spawn_t *s; + gitem_t *item; + + if ( !ent->classname ) { + G_Printf( "G_CallSpawn: NULL classname\n" ); + return qfalse; + } + + // check item spawn functions + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { + if ( !strcmp( item->classname, ent->classname ) ) { + // found it + // DHM - Nerve :: allow flags in GTWOLF + if ( item->giType == IT_TEAM && ( g_gametype.integer != GT_CTF && g_gametype.integer < GT_WOLF ) ) { + return qfalse; + } + G_SpawnItem( ent, item ); + return qtrue; + } + } + + // check normal spawn functions + for ( s = spawns ; s->name ; s++ ) { + if ( !strcmp( s->name, ent->classname ) ) { + // found it + s->spawn( ent ); + + // RF, entity scripting + if ( ent->s.number >= MAX_CLIENTS && ent->scriptName ) { + G_Script_ScriptParse( ent ); + G_Script_ScriptEvent( ent, "spawn", "" ); + } + + return qtrue; + } + } + G_Printf( "%s doesn't have a spawn function\n", ent->classname ); + return qfalse; +} + +/* +============= +G_NewString + +Builds a copy of the string, translating \n to real linefeeds +so message texts can be multi-line +============= +*/ +char *G_NewString( const char *string ) { + char *newb, *new_p; + int i,l; + + l = strlen( string ) + 1; + + newb = G_Alloc( l ); + + new_p = newb; + + // turn \n into a real linefeed + for ( i = 0 ; i < l ; i++ ) { + if ( string[i] == '\\' && i < l - 1 ) { + i++; + if ( string[i] == 'n' ) { + *new_p++ = '\n'; + } else { + *new_p++ = '\\'; + } + } else { + *new_p++ = string[i]; + } + } + + return newb; +} + + + + +/* +=============== +G_ParseField + +Takes a key/value pair and sets the binary values +in a gentity +=============== +*/ +void G_ParseField( const char *key, const char *value, gentity_t *ent ) { + field_t *f; + byte *b; + float v; + vec3_t vec; + + for ( f = fields ; f->name ; f++ ) { + if ( !Q_stricmp( f->name, key ) ) { + // found it + b = (byte *)ent; + + switch ( f->type ) { + case F_LSTRING: + *( char ** )( b + f->ofs ) = G_NewString( value ); + break; + case F_VECTOR: + sscanf( value, "%f %f %f", &vec[0], &vec[1], &vec[2] ); + ( ( float * )( b + f->ofs ) )[0] = vec[0]; + ( ( float * )( b + f->ofs ) )[1] = vec[1]; + ( ( float * )( b + f->ofs ) )[2] = vec[2]; + break; + case F_INT: + *( int * )( b + f->ofs ) = atoi( value ); + break; + case F_FLOAT: + *( float * )( b + f->ofs ) = atof( value ); + break; + case F_ANGLEHACK: + v = atof( value ); + ( ( float * )( b + f->ofs ) )[0] = 0; + ( ( float * )( b + f->ofs ) )[1] = v; + ( ( float * )( b + f->ofs ) )[2] = 0; + break; + default: + case F_IGNORE: + break; + } + return; + } + } +} + + + + +/* +=================== +G_SpawnGEntityFromSpawnVars + +Spawn an entity and fill in all of the level fields from +level.spawnVars[], then call the class specfic spawn function +=================== +*/ +void G_SpawnGEntityFromSpawnVars( void ) { + int i; + gentity_t *ent; + + // get the next free entity + ent = G_Spawn(); + + for ( i = 0 ; i < level.numSpawnVars ; i++ ) { + G_ParseField( level.spawnVars[i][0], level.spawnVars[i][1], ent ); + } + + // check for "notteam" / "notfree" flags + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + G_SpawnInt( "notsingle", "0", &i ); + if ( i ) { + G_FreeEntity( ent ); + return; + } + } + if ( g_gametype.integer >= GT_TEAM ) { + G_SpawnInt( "notteam", "0", &i ); + if ( i ) { + G_FreeEntity( ent ); + return; + } + } else { + G_SpawnInt( "notfree", "0", &i ); + if ( i ) { + G_FreeEntity( ent ); + return; + } + } + + // move editor origin to pos + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->r.currentOrigin ); + + // if we didn't get a classname, don't bother spawning anything + if ( !G_CallSpawn( ent ) ) { + G_FreeEntity( ent ); + } +} + + + +/* +==================== +G_AddSpawnVarToken +==================== +*/ +char *G_AddSpawnVarToken( const char *string ) { + int l; + char *dest; + + l = strlen( string ); + if ( level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) { + G_Error( "G_AddSpawnVarToken: MAX_SPAWN_VARS" ); + } + + dest = level.spawnVarChars + level.numSpawnVarChars; + memcpy( dest, string, l + 1 ); + + level.numSpawnVarChars += l + 1; + + return dest; +} + +/* +==================== +G_ParseSpawnVars + +Parses a brace bounded set of key / value pairs out of the +level's entity strings into level.spawnVars[] + +This does not actually spawn an entity. +==================== +*/ +qboolean G_ParseSpawnVars( void ) { + char keyname[MAX_TOKEN_CHARS]; + char com_token[MAX_TOKEN_CHARS]; + + level.numSpawnVars = 0; + level.numSpawnVarChars = 0; + + // parse the opening brace + if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) { + // end of spawn string + return qfalse; + } + if ( com_token[0] != '{' ) { + G_Error( "G_ParseSpawnVars: found %s when expecting {",com_token ); + } + + // go through all the key / value pairs + while ( 1 ) { + // parse key + if ( !trap_GetEntityToken( keyname, sizeof( keyname ) ) ) { + G_Error( "G_ParseSpawnVars: EOF without closing brace" ); + } + + if ( keyname[0] == '}' ) { + break; + } + + // parse value + if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) { + G_Error( "G_ParseSpawnVars: EOF without closing brace" ); + } + + if ( com_token[0] == '}' ) { + G_Error( "G_ParseSpawnVars: closing brace without data" ); + } + if ( level.numSpawnVars == MAX_SPAWN_VARS ) { + G_Error( "G_ParseSpawnVars: MAX_SPAWN_VARS" ); + } + level.spawnVars[ level.numSpawnVars ][0] = G_AddSpawnVarToken( keyname ); + level.spawnVars[ level.numSpawnVars ][1] = G_AddSpawnVarToken( com_token ); + level.numSpawnVars++; + } + + return qtrue; +} + + +/*QUAKED worldspawn (0 0 0) ? NO_GT_WOLF NO_STOPWATCH NO_CHECKPOINT + +Every map should have exactly one worldspawn. +"music" Music wav file +"gravity" 800 is default gravity +"message" Text to print during connection process +"ambient" Ambient light value (must use '_color') +"_color" Ambient light color (must be used with 'ambient') +"sun" Shader to use for 'sun' image +*/ +void SP_worldspawn( void ) { + char *s; + gitem_t *item; // JPW NERVE + + G_SpawnString( "classname", "", &s ); + if ( Q_stricmp( s, "worldspawn" ) ) { + G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" ); + } + + // make some data visible to connecting client + trap_SetConfigstring( CS_GAME_VERSION, GAME_VERSION ); + + trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) ); + + G_SpawnString( "music", "", &s ); + trap_SetConfigstring( CS_MUSIC, s ); + + G_SpawnString( "message", "", &s ); + trap_SetConfigstring( CS_MESSAGE, s ); // map specific message + + trap_SetConfigstring( CS_MOTD, g_motd.string ); // message of the day + + G_SpawnString( "gravity", "800", &s ); + trap_Cvar_Set( "g_gravity", s ); + + G_SpawnString( "spawnflags", "0", &s ); + g_entities[ENTITYNUM_WORLD].spawnflags = atoi( s ); + g_entities[ENTITYNUM_WORLD].r.worldflags = g_entities[ENTITYNUM_WORLD].spawnflags; + + g_entities[ENTITYNUM_WORLD].s.number = ENTITYNUM_WORLD; + g_entities[ENTITYNUM_WORLD].classname = "worldspawn"; + + // see if we want a warmup time + trap_SetConfigstring( CS_WARMUP, "" ); + if ( g_restarted.integer ) { + trap_Cvar_Set( "g_restarted", "0" ); + level.warmupTime = 0; + } + +// JPW NERVE change minigun overheat time for single player -- this array gets reloaded every time the server is reset, +// so this is as good a place as any to do stuff like this + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + int i; + ammoTable[WP_VENOM].maxHeat *= 0.25; + for ( i = 0; i < strlen( testid2 ); i++ ) + testid2[i] -= ( i + 1 ); + ammoTable[WP_DYNAMITE].uses = 0; // regens based on recharge time + // reset ammo for subs to be distinct for multiplayer (so running out of rifle ammo doesn't deplete sidearm) + // if player runs out of SMG ammunition, it shouldn't *also* deplete pistol ammunition. If you change this, change + // g_spawn.c as well + item = BG_FindItem( "Thompson" ); + item->giAmmoIndex = WP_THOMPSON; + item = BG_FindItem( "Sten" ); + item->giAmmoIndex = WP_STEN; + for ( i = 0; i < strlen( testid1 ); i++ ) + testid1[i] -= ( i + 1 ); + item = BG_FindItem( "MP40" ); + item->giAmmoIndex = WP_MP40; + ammoTable[WP_VENOM_FULL].nextShotTime = 500; + for ( i = 0; i < strlen( testid3 ); i++ ) + testid3[i] -= ( i + 1 ); + ammoTable[WP_PANZERFAUST].fireDelayTime = 750; + item = BG_FindItem( "Panzerfaust" ); // FIXME this don't work needs to go "sooner" different (shoulder-fired) panzerfaust model, 'cause the SP one is awful stubby and not proportionally right + item->world_model[4] = "models/multiplayer/panzerfaust/multi_pf.md3"; + } +// jpw + +} + + +/* +============== +G_SpawnEntitiesFromString + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +void G_SpawnEntitiesFromString( void ) { + // allow calls to G_Spawn*() + level.spawning = qtrue; + level.numSpawnVars = 0; + + // the worldspawn is not an actual entity, but it still + // has a "spawn" function to perform any global setup + // needed by a level (setting configstrings or cvars, etc) + if ( !G_ParseSpawnVars() ) { + G_Error( "SpawnEntities: no entities" ); + } + SP_worldspawn(); + + // parse ents + while ( G_ParseSpawnVars() ) { + G_SpawnGEntityFromSpawnVars(); + } + + level.spawning = qfalse; // any future calls to G_Spawn*() will be errors +} + diff --git a/src/game/g_svcmds.c b/src/game/g_svcmds.c new file mode 100644 index 0000000..789f98b --- /dev/null +++ b/src/game/g_svcmds.c @@ -0,0 +1,690 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + + +// this file holds commands that can be executed by the server console, but not remote clients + +#include "g_local.h" + + +/* +============================================================================== + +PACKET FILTERING + + +You can add or remove addresses from the filter list with: + +addip +removeip + +The ip address is specified in dot format, and you can use '*' to match any value +so you can specify an entire class C network with "addip 192.246.40.*" + +Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. + +listip +Prints the current list of filters. + +g_filterban <0 or 1> + +If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. + +If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. + +TTimo NOTE: GUID functions are copied over from the model of IP banning, +used to enforce max lives independently from server reconnect and team changes (Xian) + +TTimo NOTE: for persistence, bans are stored in g_banIPs cvar MAX_CVAR_VALUE_STRING +The size of the cvar string buffer is limiting the banning to around 20 masks +this could be improved by putting some g_banIPs2 g_banIps3 etc. maybe +still, you should rely on PB for banning instead + +============================================================================== +*/ + +typedef struct ipFilter_s +{ + unsigned mask; + unsigned compare; +} ipFilter_t; + +typedef struct ipGUID_s +{ + char compare[33]; +} ipGUID_t; + +#define MAX_IPFILTERS 1024 + +static ipFilter_t ipFilters[MAX_IPFILTERS]; +static ipGUID_t ipMaxLivesFilters[MAX_IPFILTERS]; +static int numIPFilters; +static int numMaxLivesFilters = 0; + +/* +================= +StringToFilter +================= +*/ +static qboolean StringToFilter( char *s, ipFilter_t *f ) { + char num[128]; + int i, j; + byte b[4]; + byte m[4]; + + for ( i = 0 ; i < 4 ; i++ ) + { + b[i] = 0; + m[i] = 0; + } + + for ( i = 0 ; i < 4 ; i++ ) + { + if ( *s < '0' || *s > '9' ) { + if ( *s == '*' ) { // 'match any' + // b[i] and m[i] to 0 + s++; + if ( !*s ) { + break; + } + s++; + continue; + } + G_Printf( "Bad filter address: %s\n", s ); + return qfalse; + } + + j = 0; + while ( *s >= '0' && *s <= '9' ) + { + num[j++] = *s++; + } + num[j] = 0; + b[i] = atoi( num ); + m[i] = 255; + + if ( !*s ) { + break; + } + s++; + } + + f->mask = *(unsigned *)m; + f->compare = *(unsigned *)b; + + return qtrue; +} + +/* +================= +UpdateIPBans +================= +*/ +static void UpdateIPBans( void ) { + byte b[4]; + byte m[4]; + int i,j; + char iplist_final[MAX_CVAR_VALUE_STRING]; + char ip[64]; + + *iplist_final = 0; + for ( i = 0 ; i < numIPFilters ; i++ ) + { + if ( ipFilters[i].compare == 0xffffffff ) { + continue; + } + + *(unsigned *)b = ipFilters[i].compare; + *(unsigned *)m = ipFilters[i].mask; + *ip = 0; + for ( j = 0 ; j < 4 ; j++ ) + { + if ( m[j] != 255 ) { + Q_strcat( ip, sizeof( ip ), "*" ); + } else { + Q_strcat( ip, sizeof( ip ), va( "%i", b[j] ) ); + } + Q_strcat( ip, sizeof( ip ), ( j < 3 ) ? "." : " " ); + } + if ( strlen( iplist_final ) + strlen( ip ) < MAX_CVAR_VALUE_STRING ) { + Q_strcat( iplist_final, sizeof( iplist_final ), ip ); + } else + { + Com_Printf( "g_banIPs overflowed at MAX_CVAR_VALUE_STRING\n" ); + break; + } + } + + trap_Cvar_Set( "g_banIPs", iplist_final ); +} + +void PrintMaxLivesGUID() { + int i; + + for ( i = 0 ; i < numMaxLivesFilters ; i++ ) + { + G_LogPrintf( "%i. %s\n", i, ipMaxLivesFilters[i].compare ); + } + G_LogPrintf( "--- End of list\n" ); +} + +/* +================= +G_FilterPacket +================= +*/ +qboolean G_FilterPacket( char *from ) { + int i; + unsigned in; + byte m[4]; + char *p; + + i = 0; + p = from; + while ( *p && i < 4 ) { + m[i] = 0; + while ( *p >= '0' && *p <= '9' ) { + m[i] = m[i] * 10 + ( *p - '0' ); + p++; + } + if ( !*p || *p == ':' ) { + break; + } + i++, p++; + } + + in = *(unsigned *)m; + + for ( i = 0 ; i < numIPFilters ; i++ ) + if ( ( in & ipFilters[i].mask ) == ipFilters[i].compare ) { + return g_filterBan.integer != 0; + } + + return g_filterBan.integer == 0; +} + +/* + Check to see if the user is trying to sneak back in with g_enforcemaxlives enabled +*/ +qboolean G_FilterMaxLivesPacket( char *from ) { + int i; + + for ( i = 0 ; i < numMaxLivesFilters ; i++ ) + { + if ( !Q_stricmp( ipMaxLivesFilters[i].compare, from ) ) { + return 1; + } + } + return 0; +} + +/* +================= +AddIP +================= +*/ +static void AddIP( char *str ) { + int i; + + for ( i = 0 ; i < numIPFilters ; i++ ) + if ( ipFilters[i].compare == 0xffffffff ) { + break; + } // free spot + if ( i == numIPFilters ) { + if ( numIPFilters == MAX_IPFILTERS ) { + G_Printf( "IP filter list is full\n" ); + return; + } + numIPFilters++; + } + + if ( !StringToFilter( str, &ipFilters[i] ) ) { + ipFilters[i].compare = 0xffffffffu; + } + + UpdateIPBans(); +} +/* +================= +AddMaxLivesGUID +Xian - with g_enforcemaxlives enabled, this adds a client GUID to a list +that prevents them from quitting a disconnecting +================= +*/ +void AddMaxLivesGUID( char *str ) { + if ( numMaxLivesFilters == MAX_IPFILTERS ) { + G_Printf( "MaxLives GUID filter list is full\n" ); + return; + } + Q_strncpyz( ipMaxLivesFilters[numMaxLivesFilters].compare, str, 33 ); + numMaxLivesFilters++; +} + + +/* +================= +G_ProcessIPBans +================= +*/ +void G_ProcessIPBans( void ) { + char *s, *t; + char str[MAX_CVAR_VALUE_STRING]; + + Q_strncpyz( str, g_banIPs.string, sizeof( str ) ); + + for ( t = s = g_banIPs.string; *t; /* */ ) { + s = strchr( s, ' ' ); + if ( !s ) { + break; + } + while ( *s == ' ' ) + *s++ = 0; + if ( *t ) { + AddIP( t ); + } + t = s; + } +} + + +/* +================= +Svcmd_AddIP_f +================= +*/ +void Svcmd_AddIP_f( void ) { + char str[MAX_TOKEN_CHARS]; + + if ( trap_Argc() < 2 ) { + G_Printf( "Usage: addip \n" ); + return; + } + + trap_Argv( 1, str, sizeof( str ) ); + + AddIP( str ); + +} + +/* +================= +Svcmd_RemoveIP_f +================= +*/ +void Svcmd_RemoveIP_f( void ) { + ipFilter_t f; + int i; + char str[MAX_TOKEN_CHARS]; + + if ( trap_Argc() < 2 ) { + G_Printf( "Usage: removeip \n" ); + return; + } + + trap_Argv( 1, str, sizeof( str ) ); + + if ( !StringToFilter( str, &f ) ) { + return; + } + + for ( i = 0 ; i < numIPFilters ; i++ ) { + if ( ipFilters[i].mask == f.mask && + ipFilters[i].compare == f.compare ) { + ipFilters[i].compare = 0xffffffffu; + G_Printf( "Removed.\n" ); + + UpdateIPBans(); + return; + } + } + + G_Printf( "Didn't find %s.\n", str ); +} + +/* + Xian - Clears out the entire list maxlives enforcement banlist +*/ +void ClearMaxLivesGUID() { + int i; + + for ( i = 0 ; i < numMaxLivesFilters ; i++ ) { + ipMaxLivesFilters[i].compare[0] = '\0'; + } + numMaxLivesFilters = 0; +} + +/* +=================== +Svcmd_EntityList_f +=================== +*/ +void Svcmd_EntityList_f( void ) { + int e; + gentity_t *check; + + check = g_entities + 1; + for ( e = 1; e < level.num_entities ; e++, check++ ) { + if ( !check->inuse ) { + continue; + } + G_Printf( "%3i:", e ); + switch ( check->s.eType ) { + case ET_GENERAL: + G_Printf( "ET_GENERAL " ); + break; + case ET_PLAYER: + G_Printf( "ET_PLAYER " ); + break; + case ET_ITEM: + G_Printf( "ET_ITEM " ); + break; + case ET_MISSILE: + G_Printf( "ET_MISSILE " ); + break; + case ET_MOVER: + G_Printf( "ET_MOVER " ); + break; + case ET_BEAM: + G_Printf( "ET_BEAM " ); + break; + case ET_PORTAL: + G_Printf( "ET_PORTAL " ); + break; + case ET_SPEAKER: + G_Printf( "ET_SPEAKER " ); + break; + case ET_PUSH_TRIGGER: + G_Printf( "ET_PUSH_TRIGGER " ); + break; +// JPW NERVE + case ET_CONCUSSIVE_TRIGGER: + G_Printf( "ET_CONCUSSIVE_TRIGGR" ); + break; +// jpw + case ET_TELEPORT_TRIGGER: + G_Printf( "ET_TELEPORT_TRIGGER " ); + break; + case ET_INVISIBLE: + G_Printf( "ET_INVISIBLE " ); + break; + case ET_GRAPPLE: + G_Printf( "ET_GRAPPLE " ); + break; + case ET_EXPLOSIVE: + G_Printf( "ET_EXPLOSIVE " ); + break; + case ET_EF_TESLA: + G_Printf( "ET_EF_TESLA " ); + break; + case ET_EF_SPOTLIGHT: + G_Printf( "ET_EF_SPOTLIGHT " ); + break; + case ET_EFFECT3: + G_Printf( "ET_EFFECT3 " ); + break; + case ET_ALARMBOX: + G_Printf( "ET_ALARMBOX " ); + break; + default: + G_Printf( "%3i ", check->s.eType ); + break; + } + + if ( check->classname ) { + G_Printf( "%s", check->classname ); + } + G_Printf( "\n" ); + } +} + +gclient_t *ClientForString( const char *s ) { + gclient_t *cl; + int i; + int idnum; + + // numeric values are just slot numbers + if ( s[0] >= '0' && s[0] <= '9' ) { + idnum = atoi( s ); + if ( idnum < 0 || idnum >= level.maxclients ) { + Com_Printf( "Bad client slot: %i\n", idnum ); + return NULL; + } + + cl = &level.clients[idnum]; + if ( cl->pers.connected == CON_DISCONNECTED ) { + G_Printf( "Client %i is not connected\n", idnum ); + return NULL; + } + return cl; + } + + // check for a name match + for ( i = 0 ; i < level.maxclients ; i++ ) { + cl = &level.clients[i]; + if ( cl->pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( !Q_stricmp( cl->pers.netname, s ) ) { + return cl; + } + } + + G_Printf( "User %s is not on the server\n", s ); + + return NULL; +} + +/* +=================== +Svcmd_ForceTeam_f + +forceteam +=================== +*/ +void Svcmd_ForceTeam_f( void ) { + gclient_t *cl; + char str[MAX_TOKEN_CHARS]; + + // find the player + trap_Argv( 1, str, sizeof( str ) ); + cl = ClientForString( str ); + if ( !cl ) { + return; + } + + // set the team + trap_Argv( 2, str, sizeof( str ) ); + SetTeam( &g_entities[cl - level.clients], str ); +} + +/* +============ +Svcmd_StartMatch_f + +NERVE - SMF - starts match if in tournament mode +============ +*/ +void Svcmd_StartMatch_f() { + if ( !g_noTeamSwitching.integer ) { + trap_SendServerCommand( -1, va( "print \"g_noTeamSwitching not activated.\n\"" ) ); + return; + } + + if ( level.numPlayingClients <= 1 ) { + trap_SendServerCommand( -1, va( "print \"Not enough playing clients to start match.\n\"" ) ); + return; + } + + if ( g_gamestate.integer == GS_PLAYING ) { + trap_SendServerCommand( -1, va( "print \"Match is already in progress.\n\"" ) ); + return; + } + + if ( g_gamestate.integer == GS_WAITING_FOR_PLAYERS ) { + trap_SendConsoleCommand( EXEC_APPEND, va( "map_restart 0 %i\n", GS_WARMUP ) ); + } +} + +/* +============ +Svcmd_ResetMatch_f + +NERVE - SMF - this has three behaviors +- if not in tournament mode, do a map_restart +- if in tournament mode, go back to waitingForPlayers mode +- if in stopwatch mode, reset back to first round +============ +*/ +void Svcmd_ResetMatch_f() { + if ( g_gametype.integer == GT_WOLF_STOPWATCH ) { + trap_Cvar_Set( "g_currentRound", "0" ); + trap_Cvar_Set( "g_nextTimeLimit", "0" ); + } + + if ( !g_noTeamSwitching.integer || ( g_minGameClients.integer > 1 && level.numPlayingClients >= g_minGameClients.integer ) ) { + trap_SendConsoleCommand( EXEC_APPEND, va( "map_restart 0 %i\n", GS_WARMUP ) ); + return; + } else { + trap_SendConsoleCommand( EXEC_APPEND, va( "map_restart 0 %i\n", GS_WAITING_FOR_PLAYERS ) ); + return; + } +} + +/* +============ +Svcmd_SwapTeams_f + +NERVE - SMF - swaps all clients to opposite team +============ +*/ +void Svcmd_SwapTeams_f() { +// if ( g_gamestate.integer != GS_PLAYING ) { + if ( ( g_gamestate.integer == GS_INITIALIZE ) || // JPW NERVE -- so teams can swap between checkpoint rounds + ( g_gamestate.integer == GS_WAITING_FOR_PLAYERS ) || + ( g_gamestate.integer == GS_RESET ) ) { + trap_SendServerCommand( -1, va( "print \"Match must be in progress to swap teams.\n\"" ) ); + return; + } + + if ( g_gametype.integer == GT_WOLF_STOPWATCH ) { + trap_Cvar_Set( "g_currentRound", "0" ); + trap_Cvar_Set( "g_nextTimeLimit", "0" ); + } + + trap_Cvar_Set( "g_swapteams", "1" ); + trap_SendConsoleCommand( EXEC_APPEND, va( "map_restart 0 %i\n", GS_WARMUP ) ); +} + + + +char *ConcatArgs( int start ); + +/* +================= +ConsoleCommand + +================= +*/ +qboolean ConsoleCommand( void ) { + char cmd[MAX_TOKEN_CHARS]; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + + if ( Q_stricmp( cmd, "entitylist" ) == 0 ) { + Svcmd_EntityList_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "forceteam" ) == 0 ) { + Svcmd_ForceTeam_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "game_memory" ) == 0 ) { + Svcmd_GameMem_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "addbot" ) == 0 ) { + Svcmd_AddBot_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "addip" ) == 0 ) { + Svcmd_AddIP_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "removeip" ) == 0 ) { + Svcmd_RemoveIP_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "listip" ) == 0 ) { + trap_SendConsoleCommand( EXEC_INSERT, "g_banIPs\n" ); + return qtrue; + } + + if ( Q_stricmp( cmd, "listmaxlivesip" ) == 0 ) { + PrintMaxLivesGUID(); + return qtrue; + } + + + // NERVE - SMF + if ( Q_stricmp( cmd, "start_match" ) == 0 ) { + Svcmd_StartMatch_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "reset_match" ) == 0 ) { + Svcmd_ResetMatch_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "swap_teams" ) == 0 ) { + Svcmd_SwapTeams_f(); + return qtrue; + } + // -NERVE - SMF + + if ( g_dedicated.integer ) { + if ( Q_stricmp( cmd, "say" ) == 0 ) { + trap_SendServerCommand( -1, va( "print \"server:[lof] %s\"", ConcatArgs( 1 ) ) ); + return qtrue; + } + // everything else will also be printed as a say command + trap_SendServerCommand( -1, va( "print \"server:[lof] %s\"", ConcatArgs( 0 ) ) ); + return qtrue; + } + + return qfalse; +} + diff --git a/src/game/g_syscalls.c b/src/game/g_syscalls.c new file mode 100644 index 0000000..3c59c78 --- /dev/null +++ b/src/game/g_syscalls.c @@ -0,0 +1,797 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + +// this file is only included when building a dll +// g_syscalls.asm is included instead when building a qvm + +static int ( QDECL * syscall )( int arg, ... ) = ( int ( QDECL * )( int, ... ) ) - 1; + +#if defined( __MACOS__ ) +#pragma export on +#endif +void dllEntry( int ( QDECL *syscallptr )( int arg,... ) ) { + syscall = syscallptr; +} +#if defined( __MACOS__ ) +#pragma export off +#endif + +int PASSFLOAT( float x ) { + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_Printf( const char *fmt ) { + syscall( G_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) { + syscall( G_ERROR, fmt ); +} + +int trap_Milliseconds( void ) { + return syscall( G_MILLISECONDS ); +} +int trap_Argc( void ) { + return syscall( G_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( G_ARGV, n, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( G_FS_FOPEN_FILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( G_FS_READ, buffer, len, f ); +} + +int trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + return syscall( G_FS_WRITE, buffer, len, f ); +} + +int trap_FS_Rename( const char *from, const char *to ) { + return syscall( G_FS_RENAME, from, to ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( G_FS_FCLOSE_FILE, f ); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + return syscall( G_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +void trap_SendConsoleCommand( int exec_when, const char *text ) { + syscall( G_SEND_CONSOLE_COMMAND, exec_when, text ); +} + +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) { + syscall( G_CVAR_REGISTER, cvar, var_name, value, flags ); +} + +void trap_Cvar_Update( vmCvar_t *cvar ) { + syscall( G_CVAR_UPDATE, cvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( G_CVAR_SET, var_name, value ); +} + +int trap_Cvar_VariableIntegerValue( const char *var_name ) { + return syscall( G_CVAR_VARIABLE_INTEGER_VALUE, var_name ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( G_CVAR_VARIABLE_STRING_BUFFER, var_name, buffer, bufsize ); +} + + +void trap_LocateGameData( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *clients, int sizeofGClient ) { + syscall( G_LOCATE_GAME_DATA, gEnts, numGEntities, sizeofGEntity_t, clients, sizeofGClient ); +} + +void trap_DropClient( int clientNum, const char *reason ) { + syscall( G_DROP_CLIENT, clientNum, reason ); +} + +void trap_SendServerCommand( int clientNum, const char *text ) { + syscall( G_SEND_SERVER_COMMAND, clientNum, text ); +} + +void trap_SetConfigstring( int num, const char *string ) { + syscall( G_SET_CONFIGSTRING, num, string ); +} + +void trap_GetConfigstring( int num, char *buffer, int bufferSize ) { + syscall( G_GET_CONFIGSTRING, num, buffer, bufferSize ); +} + +void trap_GetUserinfo( int num, char *buffer, int bufferSize ) { + syscall( G_GET_USERINFO, num, buffer, bufferSize ); +} + +void trap_SetUserinfo( int num, const char *buffer ) { + syscall( G_SET_USERINFO, num, buffer ); +} + +void trap_GetServerinfo( char *buffer, int bufferSize ) { + syscall( G_GET_SERVERINFO, buffer, bufferSize ); +} + +void trap_SetBrushModel( gentity_t *ent, const char *name ) { + syscall( G_SET_BRUSH_MODEL, ent, name ); +} + +void trap_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ) { + syscall( G_TRACE, results, start, mins, maxs, end, passEntityNum, contentmask ); +} + +void trap_TraceCapsule( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ) { + syscall( G_TRACECAPSULE, results, start, mins, maxs, end, passEntityNum, contentmask ); +} + +int trap_PointContents( const vec3_t point, int passEntityNum ) { + return syscall( G_POINT_CONTENTS, point, passEntityNum ); +} + + +qboolean trap_InPVS( const vec3_t p1, const vec3_t p2 ) { + return syscall( G_IN_PVS, p1, p2 ); +} + +qboolean trap_InPVSIgnorePortals( const vec3_t p1, const vec3_t p2 ) { + return syscall( G_IN_PVS_IGNORE_PORTALS, p1, p2 ); +} + +void trap_AdjustAreaPortalState( gentity_t *ent, qboolean open ) { + syscall( G_ADJUST_AREA_PORTAL_STATE, ent, open ); +} + +qboolean trap_AreasConnected( int area1, int area2 ) { + return syscall( G_AREAS_CONNECTED, area1, area2 ); +} + +void trap_LinkEntity( gentity_t *ent ) { + syscall( G_LINKENTITY, ent ); +} + +void trap_UnlinkEntity( gentity_t *ent ) { + syscall( G_UNLINKENTITY, ent ); +} + + +int trap_EntitiesInBox( const vec3_t mins, const vec3_t maxs, int *list, int maxcount ) { + return syscall( G_ENTITIES_IN_BOX, mins, maxs, list, maxcount ); +} + +qboolean trap_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ) { + return syscall( G_ENTITY_CONTACT, mins, maxs, ent ); +} + +qboolean trap_EntityContactCapsule( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ) { + return syscall( G_ENTITY_CONTACTCAPSULE, mins, maxs, ent ); +} + +int trap_BotAllocateClient( void ) { + return syscall( G_BOT_ALLOCATE_CLIENT ); +} + +void trap_BotFreeClient( int clientNum ) { + syscall( G_BOT_FREE_CLIENT, clientNum ); +} + +void trap_GetUsercmd( int clientNum, usercmd_t *cmd ) { + syscall( G_GET_USERCMD, clientNum, cmd ); +} + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { + return syscall( G_GET_ENTITY_TOKEN, buffer, bufferSize ); +} + +int trap_DebugPolygonCreate( int color, int numPoints, vec3_t *points ) { + return syscall( G_DEBUG_POLYGON_CREATE, color, numPoints, points ); +} + +void trap_DebugPolygonDelete( int id ) { + syscall( G_DEBUG_POLYGON_DELETE, id ); +} + +int trap_RealTime( qtime_t *qtime ) { + return syscall( G_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) { + syscall( G_SNAPVECTOR, v ); + return; +} + +qboolean trap_GetTag( int clientNum, char *tagName, orientation_t *or ) { + return syscall( G_GETTAG, clientNum, tagName, or ); +} + +// BotLib traps start here +int trap_BotLibSetup( void ) { + return syscall( BOTLIB_SETUP ); +} + +int trap_BotLibShutdown( void ) { + return syscall( BOTLIB_SHUTDOWN ); +} + +int trap_BotLibVarSet( char *var_name, char *value ) { + return syscall( BOTLIB_LIBVAR_SET, var_name, value ); +} + +int trap_BotLibVarGet( char *var_name, char *value, int size ) { + return syscall( BOTLIB_LIBVAR_GET, var_name, value, size ); +} + +int trap_BotLibDefine( char *string ) { + return syscall( BOTLIB_PC_ADD_GLOBAL_DEFINE, string ); +} + +int trap_BotLibStartFrame( float time ) { + return syscall( BOTLIB_START_FRAME, PASSFLOAT( time ) ); +} + +int trap_BotLibLoadMap( const char *mapname ) { + return syscall( BOTLIB_LOAD_MAP, mapname ); +} + +int trap_BotLibUpdateEntity( int ent, void /* struct bot_updateentity_s */ *bue ) { + return syscall( BOTLIB_UPDATENTITY, ent, bue ); +} + +int trap_BotLibTest( int parm0, char *parm1, vec3_t parm2, vec3_t parm3 ) { + return syscall( BOTLIB_TEST, parm0, parm1, parm2, parm3 ); +} + +int trap_BotGetSnapshotEntity( int clientNum, int sequence ) { + return syscall( BOTLIB_GET_SNAPSHOT_ENTITY, clientNum, sequence ); +} + +int trap_BotGetServerCommand( int clientNum, char *message, int size ) { + return syscall( BOTLIB_GET_CONSOLE_MESSAGE, clientNum, message, size ); +} + +void trap_BotUserCommand( int clientNum, usercmd_t *ucmd ) { + syscall( BOTLIB_USER_COMMAND, clientNum, ucmd ); +} + +void trap_AAS_EntityInfo( int entnum, void /* struct aas_entityinfo_s */ *info ) { + syscall( BOTLIB_AAS_ENTITY_INFO, entnum, info ); +} + +int trap_AAS_Initialized( void ) { + return syscall( BOTLIB_AAS_INITIALIZED ); +} + +void trap_AAS_PresenceTypeBoundingBox( int presencetype, vec3_t mins, vec3_t maxs ) { + syscall( BOTLIB_AAS_PRESENCE_TYPE_BOUNDING_BOX, presencetype, mins, maxs ); +} + +float trap_AAS_Time( void ) { + int temp; + temp = syscall( BOTLIB_AAS_TIME ); + return ( *(float*)&temp ); +} + +// Ridah, multiple AAS files +void trap_AAS_SetCurrentWorld( int index ) { + syscall( BOTLIB_AAS_SETCURRENTWORLD, index ); +} +// done. + +int trap_AAS_PointAreaNum( vec3_t point ) { + return syscall( BOTLIB_AAS_POINT_AREA_NUM, point ); +} + +int trap_AAS_TraceAreas( vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas ) { + return syscall( BOTLIB_AAS_TRACE_AREAS, start, end, areas, points, maxareas ); +} + +int trap_AAS_PointContents( vec3_t point ) { + return syscall( BOTLIB_AAS_POINT_CONTENTS, point ); +} + +int trap_AAS_NextBSPEntity( int ent ) { + return syscall( BOTLIB_AAS_NEXT_BSP_ENTITY, ent ); +} + +int trap_AAS_ValueForBSPEpairKey( int ent, char *key, char *value, int size ) { + return syscall( BOTLIB_AAS_VALUE_FOR_BSP_EPAIR_KEY, ent, key, value, size ); +} + +int trap_AAS_VectorForBSPEpairKey( int ent, char *key, vec3_t v ) { + return syscall( BOTLIB_AAS_VECTOR_FOR_BSP_EPAIR_KEY, ent, key, v ); +} + +int trap_AAS_FloatForBSPEpairKey( int ent, char *key, float *value ) { + return syscall( BOTLIB_AAS_FLOAT_FOR_BSP_EPAIR_KEY, ent, key, value ); +} + +int trap_AAS_IntForBSPEpairKey( int ent, char *key, int *value ) { + return syscall( BOTLIB_AAS_INT_FOR_BSP_EPAIR_KEY, ent, key, value ); +} + +int trap_AAS_AreaReachability( int areanum ) { + return syscall( BOTLIB_AAS_AREA_REACHABILITY, areanum ); +} + +int trap_AAS_AreaTravelTimeToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags ) { + return syscall( BOTLIB_AAS_AREA_TRAVEL_TIME_TO_GOAL_AREA, areanum, origin, goalareanum, travelflags ); +} + +int trap_AAS_Swimming( vec3_t origin ) { + return syscall( BOTLIB_AAS_SWIMMING, origin ); +} + +int trap_AAS_PredictClientMovement( void /* struct aas_clientmove_s */ *move, int entnum, vec3_t origin, int presencetype, int onground, vec3_t velocity, vec3_t cmdmove, int cmdframes, int maxframes, float frametime, int stopevent, int stopareanum, int visualize ) { + return syscall( BOTLIB_AAS_PREDICT_CLIENT_MOVEMENT, move, entnum, origin, presencetype, onground, velocity, cmdmove, cmdframes, maxframes, PASSFLOAT( frametime ), stopevent, stopareanum, visualize ); +} + +// Ridah, route-tables +void trap_AAS_RT_ShowRoute( vec3_t srcpos, int srcnum, int destnum ) { + syscall( BOTLIB_AAS_RT_SHOWROUTE, srcpos, srcnum, destnum ); +} + +qboolean trap_AAS_RT_GetHidePos( vec3_t srcpos, int srcnum, int srcarea, vec3_t destpos, int destnum, int destarea, vec3_t returnPos ) { + return syscall( BOTLIB_AAS_RT_GETHIDEPOS, srcpos, srcnum, srcarea, destpos, destnum, destarea, returnPos ); +} + +int trap_AAS_FindAttackSpotWithinRange( int srcnum, int rangenum, int enemynum, float rangedist, int travelflags, float *outpos ) { + return syscall( BOTLIB_AAS_FINDATTACKSPOTWITHINRANGE, srcnum, rangenum, enemynum, PASSFLOAT( rangedist ), travelflags, outpos ); +} + +void trap_AAS_SetAASBlockingEntity( vec3_t absmin, vec3_t absmax, qboolean blocking ) { + syscall( BOTLIB_AAS_SETAASBLOCKINGENTITY, absmin, absmax, blocking ); +} +// done. + +void trap_EA_Say( int client, char *str ) { + syscall( BOTLIB_EA_SAY, client, str ); +} + +void trap_EA_SayTeam( int client, char *str ) { + syscall( BOTLIB_EA_SAY_TEAM, client, str ); +} + +void trap_EA_UseItem( int client, char *it ) { + syscall( BOTLIB_EA_USE_ITEM, client, it ); +} + +void trap_EA_DropItem( int client, char *it ) { + syscall( BOTLIB_EA_DROP_ITEM, client, it ); +} + +void trap_EA_UseInv( int client, char *inv ) { + syscall( BOTLIB_EA_USE_INV, client, inv ); +} + +void trap_EA_DropInv( int client, char *inv ) { + syscall( BOTLIB_EA_DROP_INV, client, inv ); +} + +void trap_EA_Gesture( int client ) { + syscall( BOTLIB_EA_GESTURE, client ); +} + +void trap_EA_Command( int client, char *command ) { + syscall( BOTLIB_EA_COMMAND, client, command ); +} + +void trap_EA_SelectWeapon( int client, int weapon ) { + syscall( BOTLIB_EA_SELECT_WEAPON, client, weapon ); +} + +void trap_EA_Talk( int client ) { + syscall( BOTLIB_EA_TALK, client ); +} + +void trap_EA_Attack( int client ) { + syscall( BOTLIB_EA_ATTACK, client ); +} + +void trap_EA_Reload( int client ) { + syscall( BOTLIB_EA_RELOAD, client ); +} + +void trap_EA_Use( int client ) { + syscall( BOTLIB_EA_USE, client ); +} + +void trap_EA_Respawn( int client ) { + syscall( BOTLIB_EA_RESPAWN, client ); +} + +void trap_EA_Jump( int client ) { + syscall( BOTLIB_EA_JUMP, client ); +} + +void trap_EA_DelayedJump( int client ) { + syscall( BOTLIB_EA_DELAYED_JUMP, client ); +} + +void trap_EA_Crouch( int client ) { + syscall( BOTLIB_EA_CROUCH, client ); +} + +void trap_EA_MoveUp( int client ) { + syscall( BOTLIB_EA_MOVE_UP, client ); +} + +void trap_EA_MoveDown( int client ) { + syscall( BOTLIB_EA_MOVE_DOWN, client ); +} + +void trap_EA_MoveForward( int client ) { + syscall( BOTLIB_EA_MOVE_FORWARD, client ); +} + +void trap_EA_MoveBack( int client ) { + syscall( BOTLIB_EA_MOVE_BACK, client ); +} + +void trap_EA_MoveLeft( int client ) { + syscall( BOTLIB_EA_MOVE_LEFT, client ); +} + +void trap_EA_MoveRight( int client ) { + syscall( BOTLIB_EA_MOVE_RIGHT, client ); +} + +void trap_EA_Move( int client, vec3_t dir, float speed ) { + syscall( BOTLIB_EA_MOVE, client, dir, PASSFLOAT( speed ) ); +} + +void trap_EA_View( int client, vec3_t viewangles ) { + syscall( BOTLIB_EA_VIEW, client, viewangles ); +} + +void trap_EA_EndRegular( int client, float thinktime ) { + syscall( BOTLIB_EA_END_REGULAR, client, PASSFLOAT( thinktime ) ); +} + +void trap_EA_GetInput( int client, float thinktime, void /* struct bot_input_s */ *input ) { + syscall( BOTLIB_EA_GET_INPUT, client, PASSFLOAT( thinktime ), input ); +} + +void trap_EA_ResetInput( int client, void *init ) { + syscall( BOTLIB_EA_RESET_INPUT, client, init ); +} + +int trap_BotLoadCharacter( char *charfile, int skill ) { + return syscall( BOTLIB_AI_LOAD_CHARACTER, charfile, skill ); +} + +void trap_BotFreeCharacter( int character ) { + syscall( BOTLIB_AI_FREE_CHARACTER, character ); +} + +float trap_Characteristic_Float( int character, int index ) { + int temp; + temp = syscall( BOTLIB_AI_CHARACTERISTIC_FLOAT, character, index ); + return ( *(float*)&temp ); +} + +float trap_Characteristic_BFloat( int character, int index, float min, float max ) { + int temp; + temp = syscall( BOTLIB_AI_CHARACTERISTIC_BFLOAT, character, index, PASSFLOAT( min ), PASSFLOAT( max ) ); + return ( *(float*)&temp ); +} + +int trap_Characteristic_Integer( int character, int index ) { + return syscall( BOTLIB_AI_CHARACTERISTIC_INTEGER, character, index ); +} + +int trap_Characteristic_BInteger( int character, int index, int min, int max ) { + return syscall( BOTLIB_AI_CHARACTERISTIC_BINTEGER, character, index, min, max ); +} + +void trap_Characteristic_String( int character, int index, char *buf, int size ) { + syscall( BOTLIB_AI_CHARACTERISTIC_STRING, character, index, buf, size ); +} + +int trap_BotAllocChatState( void ) { + return syscall( BOTLIB_AI_ALLOC_CHAT_STATE ); +} + +void trap_BotFreeChatState( int handle ) { + syscall( BOTLIB_AI_FREE_CHAT_STATE, handle ); +} + +void trap_BotQueueConsoleMessage( int chatstate, int type, char *message ) { + syscall( BOTLIB_AI_QUEUE_CONSOLE_MESSAGE, chatstate, type, message ); +} + +void trap_BotRemoveConsoleMessage( int chatstate, int handle ) { + syscall( BOTLIB_AI_REMOVE_CONSOLE_MESSAGE, chatstate, handle ); +} + +int trap_BotNextConsoleMessage( int chatstate, void /* struct bot_consolemessage_s */ *cm ) { + return syscall( BOTLIB_AI_NEXT_CONSOLE_MESSAGE, chatstate, cm ); +} + +int trap_BotNumConsoleMessages( int chatstate ) { + return syscall( BOTLIB_AI_NUM_CONSOLE_MESSAGE, chatstate ); +} + +void trap_BotInitialChat( int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ) { + syscall( BOTLIB_AI_INITIAL_CHAT, chatstate, type, mcontext, var0, var1, var2, var3, var4, var5, var6, var7 ); +} + +int trap_BotNumInitialChats( int chatstate, char *type ) { + return syscall( BOTLIB_AI_NUM_INITIAL_CHATS, chatstate, type ); +} + +int trap_BotReplyChat( int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7 ) { + return syscall( BOTLIB_AI_REPLY_CHAT, chatstate, message, mcontext, vcontext, var0, var1, var2, var3, var4, var5, var6, var7 ); +} + +int trap_BotChatLength( int chatstate ) { + return syscall( BOTLIB_AI_CHAT_LENGTH, chatstate ); +} + +void trap_BotEnterChat( int chatstate, int client, int sendto ) { + syscall( BOTLIB_AI_ENTER_CHAT, chatstate, client, sendto ); +} + +void trap_BotGetChatMessage( int chatstate, char *buf, int size ) { + syscall( BOTLIB_AI_GET_CHAT_MESSAGE, chatstate, buf, size ); +} + +int trap_StringContains( char *str1, char *str2, int casesensitive ) { + return syscall( BOTLIB_AI_STRING_CONTAINS, str1, str2, casesensitive ); +} + +int trap_BotFindMatch( char *str, void /* struct bot_match_s */ *match, unsigned long int context ) { + return syscall( BOTLIB_AI_FIND_MATCH, str, match, context ); +} + +void trap_BotMatchVariable( void /* struct bot_match_s */ *match, int variable, char *buf, int size ) { + syscall( BOTLIB_AI_MATCH_VARIABLE, match, variable, buf, size ); +} + +void trap_UnifyWhiteSpaces( char *string ) { + syscall( BOTLIB_AI_UNIFY_WHITE_SPACES, string ); +} + +void trap_BotReplaceSynonyms( char *string, unsigned long int context ) { + syscall( BOTLIB_AI_REPLACE_SYNONYMS, string, context ); +} + +int trap_BotLoadChatFile( int chatstate, char *chatfile, char *chatname ) { + return syscall( BOTLIB_AI_LOAD_CHAT_FILE, chatstate, chatfile, chatname ); +} + +void trap_BotSetChatGender( int chatstate, int gender ) { + syscall( BOTLIB_AI_SET_CHAT_GENDER, chatstate, gender ); +} + +void trap_BotSetChatName( int chatstate, char *name ) { + syscall( BOTLIB_AI_SET_CHAT_NAME, chatstate, name ); +} + +void trap_BotResetGoalState( int goalstate ) { + syscall( BOTLIB_AI_RESET_GOAL_STATE, goalstate ); +} + +void trap_BotResetAvoidGoals( int goalstate ) { + syscall( BOTLIB_AI_RESET_AVOID_GOALS, goalstate ); +} + +void trap_BotRemoveFromAvoidGoals( int goalstate, int number ) { + syscall( BOTLIB_AI_REMOVE_FROM_AVOID_GOALS, goalstate, number ); +} + +void trap_BotPushGoal( int goalstate, void /* struct bot_goal_s */ *goal ) { + syscall( BOTLIB_AI_PUSH_GOAL, goalstate, goal ); +} + +void trap_BotPopGoal( int goalstate ) { + syscall( BOTLIB_AI_POP_GOAL, goalstate ); +} + +void trap_BotEmptyGoalStack( int goalstate ) { + syscall( BOTLIB_AI_EMPTY_GOAL_STACK, goalstate ); +} + +void trap_BotDumpAvoidGoals( int goalstate ) { + syscall( BOTLIB_AI_DUMP_AVOID_GOALS, goalstate ); +} + +void trap_BotDumpGoalStack( int goalstate ) { + syscall( BOTLIB_AI_DUMP_GOAL_STACK, goalstate ); +} + +void trap_BotGoalName( int number, char *name, int size ) { + syscall( BOTLIB_AI_GOAL_NAME, number, name, size ); +} + +int trap_BotGetTopGoal( int goalstate, void /* struct bot_goal_s */ *goal ) { + return syscall( BOTLIB_AI_GET_TOP_GOAL, goalstate, goal ); +} + +int trap_BotGetSecondGoal( int goalstate, void /* struct bot_goal_s */ *goal ) { + return syscall( BOTLIB_AI_GET_SECOND_GOAL, goalstate, goal ); +} + +int trap_BotChooseLTGItem( int goalstate, vec3_t origin, int *inventory, int travelflags ) { + return syscall( BOTLIB_AI_CHOOSE_LTG_ITEM, goalstate, origin, inventory, travelflags ); +} + +int trap_BotChooseNBGItem( int goalstate, vec3_t origin, int *inventory, int travelflags, void /* struct bot_goal_s */ *ltg, float maxtime ) { + return syscall( BOTLIB_AI_CHOOSE_NBG_ITEM, goalstate, origin, inventory, travelflags, ltg, PASSFLOAT( maxtime ) ); +} + +int trap_BotTouchingGoal( vec3_t origin, void /* struct bot_goal_s */ *goal ) { + return syscall( BOTLIB_AI_TOUCHING_GOAL, origin, goal ); +} + +int trap_BotItemGoalInVisButNotVisible( int viewer, vec3_t eye, vec3_t viewangles, void /* struct bot_goal_s */ *goal ) { + return syscall( BOTLIB_AI_ITEM_GOAL_IN_VIS_BUT_NOT_VISIBLE, viewer, eye, viewangles, goal ); +} + +int trap_BotGetLevelItemGoal( int index, char *classname, void /* struct bot_goal_s */ *goal ) { + return syscall( BOTLIB_AI_GET_LEVEL_ITEM_GOAL, index, classname, goal ); +} + +int trap_BotGetNextCampSpotGoal( int num, void /* struct bot_goal_s */ *goal ) { + return syscall( BOTLIB_AI_GET_NEXT_CAMP_SPOT_GOAL, num, goal ); +} + +int trap_BotGetMapLocationGoal( char *name, void /* struct bot_goal_s */ *goal ) { + return syscall( BOTLIB_AI_GET_MAP_LOCATION_GOAL, name, goal ); +} + +float trap_BotAvoidGoalTime( int goalstate, int number ) { + int temp; + temp = syscall( BOTLIB_AI_AVOID_GOAL_TIME, goalstate, number ); + return ( *(float*)&temp ); +} + +void trap_BotInitLevelItems( void ) { + syscall( BOTLIB_AI_INIT_LEVEL_ITEMS ); +} + +void trap_BotUpdateEntityItems( void ) { + syscall( BOTLIB_AI_UPDATE_ENTITY_ITEMS ); +} + +int trap_BotLoadItemWeights( int goalstate, char *filename ) { + return syscall( BOTLIB_AI_LOAD_ITEM_WEIGHTS, goalstate, filename ); +} + +void trap_BotFreeItemWeights( int goalstate ) { + syscall( BOTLIB_AI_FREE_ITEM_WEIGHTS, goalstate ); +} + +void trap_BotInterbreedGoalFuzzyLogic( int parent1, int parent2, int child ) { + syscall( BOTLIB_AI_INTERBREED_GOAL_FUZZY_LOGIC, parent1, parent2, child ); +} + +void trap_BotSaveGoalFuzzyLogic( int goalstate, char *filename ) { + syscall( BOTLIB_AI_SAVE_GOAL_FUZZY_LOGIC, goalstate, filename ); +} + +void trap_BotMutateGoalFuzzyLogic( int goalstate, float range ) { + syscall( BOTLIB_AI_MUTATE_GOAL_FUZZY_LOGIC, goalstate, range ); +} + +int trap_BotAllocGoalState( int state ) { + return syscall( BOTLIB_AI_ALLOC_GOAL_STATE, state ); +} + +void trap_BotFreeGoalState( int handle ) { + syscall( BOTLIB_AI_FREE_GOAL_STATE, handle ); +} + +void trap_BotResetMoveState( int movestate ) { + syscall( BOTLIB_AI_RESET_MOVE_STATE, movestate ); +} + +void trap_BotMoveToGoal( void /* struct bot_moveresult_s */ *result, int movestate, void /* struct bot_goal_s */ *goal, int travelflags ) { + syscall( BOTLIB_AI_MOVE_TO_GOAL, result, movestate, goal, travelflags ); +} + +int trap_BotMoveInDirection( int movestate, vec3_t dir, float speed, int type ) { + return syscall( BOTLIB_AI_MOVE_IN_DIRECTION, movestate, dir, PASSFLOAT( speed ), type ); +} + +void trap_BotResetAvoidReach( int movestate ) { + syscall( BOTLIB_AI_RESET_AVOID_REACH, movestate ); +} + +void trap_BotResetLastAvoidReach( int movestate ) { + syscall( BOTLIB_AI_RESET_LAST_AVOID_REACH,movestate ); +} + +int trap_BotReachabilityArea( vec3_t origin, int testground ) { + return syscall( BOTLIB_AI_REACHABILITY_AREA, origin, testground ); +} + +int trap_BotMovementViewTarget( int movestate, void /* struct bot_goal_s */ *goal, int travelflags, float lookahead, vec3_t target ) { + return syscall( BOTLIB_AI_MOVEMENT_VIEW_TARGET, movestate, goal, travelflags, PASSFLOAT( lookahead ), target ); +} + +int trap_BotPredictVisiblePosition( vec3_t origin, int areanum, void /* struct bot_goal_s */ *goal, int travelflags, vec3_t target ) { + return syscall( BOTLIB_AI_PREDICT_VISIBLE_POSITION, origin, areanum, goal, travelflags, target ); +} + +int trap_BotAllocMoveState( void ) { + return syscall( BOTLIB_AI_ALLOC_MOVE_STATE ); +} + +void trap_BotFreeMoveState( int handle ) { + syscall( BOTLIB_AI_FREE_MOVE_STATE, handle ); +} + +void trap_BotInitMoveState( int handle, void /* struct bot_initmove_s */ *initmove ) { + syscall( BOTLIB_AI_INIT_MOVE_STATE, handle, initmove ); +} + +// Ridah +void trap_BotInitAvoidReach( int handle ) { + syscall( BOTLIB_AI_INIT_AVOID_REACH, handle ); +} +// Done. + +int trap_BotChooseBestFightWeapon( int weaponstate, int *inventory ) { + return syscall( BOTLIB_AI_CHOOSE_BEST_FIGHT_WEAPON, weaponstate, inventory ); +} + +void trap_BotGetWeaponInfo( int weaponstate, int weapon, void /* struct weaponinfo_s */ *weaponinfo ) { + syscall( BOTLIB_AI_GET_WEAPON_INFO, weaponstate, weapon, weaponinfo ); +} + +int trap_BotLoadWeaponWeights( int weaponstate, char *filename ) { + return syscall( BOTLIB_AI_LOAD_WEAPON_WEIGHTS, weaponstate, filename ); +} + +int trap_BotAllocWeaponState( void ) { + return syscall( BOTLIB_AI_ALLOC_WEAPON_STATE ); +} + +void trap_BotFreeWeaponState( int weaponstate ) { + syscall( BOTLIB_AI_FREE_WEAPON_STATE, weaponstate ); +} + +void trap_BotResetWeaponState( int weaponstate ) { + syscall( BOTLIB_AI_RESET_WEAPON_STATE, weaponstate ); +} + +int trap_GeneticParentsAndChildSelection( int numranks, float *ranks, int *parent1, int *parent2, int *child ) { + return syscall( BOTLIB_AI_GENETIC_PARENTS_AND_CHILD_SELECTION, numranks, ranks, parent1, parent2, child ); +} diff --git a/src/game/g_target.c b/src/game/g_target.c new file mode 100644 index 0000000..aad6f48 --- /dev/null +++ b/src/game/g_target.c @@ -0,0 +1,1129 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: g_target.c + * + * desc: + * +*/ + +#include "g_local.h" + +//========================================================== + +/*QUAKED target_give (1 0 0) (-8 -8 -8) (8 8 8) +Gives the activator all the items pointed to. +*/ +void Use_Target_Give( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *t; + trace_t trace; + + if ( !activator->client ) { + return; + } + + if ( !ent->target ) { + return; + } + + memset( &trace, 0, sizeof( trace ) ); + t = NULL; + while ( ( t = G_Find( t, FOFS( targetname ), ent->target ) ) != NULL ) { + if ( !t->item ) { + continue; + } + Touch_Item( t, activator, &trace ); + + // make sure it isn't going to respawn or show any events + t->nextthink = 0; + trap_UnlinkEntity( t ); + } +} + +void SP_target_give( gentity_t *ent ) { + ent->use = Use_Target_Give; +} + + +//========================================================== + +/*QUAKED target_remove_powerups (1 0 0) (-8 -8 -8) (8 8 8) +takes away all the activators powerups. +Used to drop flight powerups into death puts. +*/ +void Use_target_remove_powerups( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( !activator->client ) { + return; + } + + if ( activator->client->ps.powerups[PW_REDFLAG] ) { + Team_ReturnFlag( TEAM_RED ); + } else if ( activator->client->ps.powerups[PW_BLUEFLAG] ) { + Team_ReturnFlag( TEAM_BLUE ); + } + + memset( activator->client->ps.powerups, 0, sizeof( activator->client->ps.powerups ) ); +} + +void SP_target_remove_powerups( gentity_t *ent ) { + ent->use = Use_target_remove_powerups; +} + + +//========================================================== + +/*QUAKED target_delay (1 1 0) (-8 -8 -8) (8 8 8) +"wait" seconds to pause before firing targets. +"random" delay variance, total delay = delay +/- random seconds +*/ +void Think_Target_Delay( gentity_t *ent ) { + G_UseTargets( ent, ent->activator ); +} + +void Use_Target_Delay( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000; + ent->think = Think_Target_Delay; + ent->activator = activator; +} + +void SP_target_delay( gentity_t *ent ) { + // check delay for backwards compatability + if ( !G_SpawnFloat( "delay", "0", &ent->wait ) ) { + G_SpawnFloat( "wait", "1", &ent->wait ); + } + + if ( !ent->wait ) { + ent->wait = 1; + } + ent->use = Use_Target_Delay; +} + + +//========================================================== + +/*QUAKED target_score (1 0 0) (-8 -8 -8) (8 8 8) +"count" number of points to add, default 1 + +The activator is given this many points. +*/ +void Use_Target_Score( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + AddScore( activator, ent->count ); +} + +void SP_target_score( gentity_t *ent ) { + if ( !ent->count ) { + ent->count = 1; + } + ent->use = Use_Target_Score; +} + + + +//========================================================== + +/*QUAKED target_print (1 0 0) (-8 -8 -8) (8 8 8) redteam blueteam private +"message" text to print +If "private", only the activator gets the message. If no checks, all clients get the message. +*/ +void Use_Target_Print( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( activator->client && ( ent->spawnflags & 4 ) ) { + trap_SendServerCommand( activator - g_entities, va( "cp \"%s\"", ent->message ) ); + return; + } + + if ( ent->spawnflags & 3 ) { + if ( ent->spawnflags & 1 ) { + G_TeamCommand( TEAM_RED, va( "cp \"%s\"", ent->message ) ); + } + if ( ent->spawnflags & 2 ) { + G_TeamCommand( TEAM_BLUE, va( "cp \"%s\"", ent->message ) ); + } + return; + } + + trap_SendServerCommand( -1, va( "cp \"%s\"", ent->message ) ); +} + +void SP_target_print( gentity_t *ent ) { + ent->use = Use_Target_Print; +} + + +//========================================================== + + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) LOOPED_ON LOOPED_OFF GLOBAL ACTIVATOR VIS_MULTIPLE NO_PVS +"noise" wav file to play + +A global sound will play full volume throughout the level. +Activator sounds will play on the player that activated the target. +Global and activator sounds can't be combined with looping. +Normal sounds play each time the target is used. +Looped sounds will be toggled by use functions. +Multiple identical looping sounds will just increase volume without any speed cost. +NO_PVS - this sound will not turn off when not in the player's PVS +"wait" : Seconds between auto triggerings, 0 = don't auto trigger +"random" : wait variance, default is 0 +*/ +void Use_Target_Speaker( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->spawnflags & 3 ) { // looping sound toggles + if ( ent->s.loopSound ) { + ent->s.loopSound = 0; // turn it off + } else { + ent->s.loopSound = ent->noise_index; // start it + } + } else { // normal sound + if ( ent->spawnflags & 8 ) { + G_AddEvent( activator, EV_GENERAL_SOUND, ent->noise_index ); + } else if ( ent->spawnflags & 4 ) { + G_AddEvent( ent, EV_GLOBAL_SOUND, ent->noise_index ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->noise_index ); + } + } +} + +void target_speaker_multiple( gentity_t *ent ) { + gentity_t *vis_dummy = NULL; + + if ( !( ent->target ) ) { + G_Error( "target_speaker missing target at pos %s", vtos( ent->s.origin ) ); + } + + vis_dummy = G_Find( NULL, FOFS( targetname ), ent->target ); + + if ( vis_dummy ) { + ent->s.otherEntityNum = vis_dummy->s.number; + } else { + G_Error( "target_speaker cant find vis_dummy_multiple %s", vtos( ent->s.origin ) ); + } + +} + +void SP_target_speaker( gentity_t *ent ) { + char buffer[MAX_QPATH]; + char *s; + + G_SpawnFloat( "wait", "0", &ent->wait ); + G_SpawnFloat( "random", "0", &ent->random ); + + if ( !G_SpawnString( "noise", "NOSOUND", &s ) ) { + G_Error( "target_speaker without a noise key at %s", vtos( ent->s.origin ) ); + } + + // force all client reletive sounds to be "activator" speakers that + // play on the entity that activates it + if ( s[0] == '*' ) { + ent->spawnflags |= 8; + } + + // Ridah, had to disable this so we can use sound scripts + // don't worry, if the script isn't found, it'll default back to + // .wav on the client-side + //if (!strstr( s, ".wav" )) { + // Com_sprintf (buffer, sizeof(buffer), "%s.wav", s ); + //} else { + Q_strncpyz( buffer, s, sizeof( buffer ) ); + //} + ent->noise_index = G_SoundIndex( buffer ); + + // a repeating speaker can be done completely client side + ent->s.eType = ET_SPEAKER; + ent->s.eventParm = ent->noise_index; + ent->s.frame = ent->wait * 10; + ent->s.clientNum = ent->random * 10; + + + // check for prestarted looping sound + if ( ent->spawnflags & 1 ) { + ent->s.loopSound = ent->noise_index; + } + + ent->use = Use_Target_Speaker; + + // GLOBAL + if ( ent->spawnflags & ( 4 | 32 ) ) { + ent->r.svFlags |= SVF_BROADCAST; + } + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + + if ( ent->spawnflags & 16 ) { + ent->think = target_speaker_multiple; + ent->nextthink = level.time + 50; + } + + // NO_PVS + if ( ent->spawnflags & 32 ) { + ent->s.density = 1; + } else { + ent->s.density = 0; + } + + if ( ent->radius ) { + ent->s.dmgFlags = ent->radius; // store radius in dmgflags + } else { + ent->s.dmgFlags = 0; + } + + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + trap_LinkEntity( ent ); +} + + + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON +When triggered, fires a laser. You can either set a target or a direction. +*/ +void target_laser_think( gentity_t *self ) { + vec3_t end; + trace_t tr; + vec3_t point; + + // if pointed at another entity, set movedir to point at it + if ( self->enemy ) { + VectorMA( self->enemy->s.origin, 0.5, self->enemy->r.mins, point ); + VectorMA( point, 0.5, self->enemy->r.maxs, point ); + VectorSubtract( point, self->s.origin, self->movedir ); + VectorNormalize( self->movedir ); + } + + // fire forward and see what we hit + VectorMA( self->s.origin, 2048, self->movedir, end ); + + trap_Trace( &tr, self->s.origin, NULL, NULL, end, self->s.number, CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_CORPSE ); + + if ( tr.entityNum ) { + // hurt it if we can + G_Damage( &g_entities[tr.entityNum], self, self->activator, self->movedir, + tr.endpos, self->damage, DAMAGE_NO_KNOCKBACK, MOD_TARGET_LASER ); + } + + VectorCopy( tr.endpos, self->s.origin2 ); + + trap_LinkEntity( self ); + self->nextthink = level.time + FRAMETIME; +} + +void target_laser_on( gentity_t *self ) { + if ( !self->activator ) { + self->activator = self; + } + target_laser_think( self ); +} + +void target_laser_off( gentity_t *self ) { + trap_UnlinkEntity( self ); + self->nextthink = 0; +} + +void target_laser_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + self->activator = activator; + if ( self->nextthink > 0 ) { + target_laser_off( self ); + } else { + target_laser_on( self ); + } +} + +void target_laser_start( gentity_t *self ) { + gentity_t *ent; + + self->s.eType = ET_BEAM; + + if ( self->target ) { + ent = G_Find( NULL, FOFS( targetname ), self->target ); + if ( !ent ) { + G_Printf( "%s at %s: %s is a bad target\n", self->classname, vtos( self->s.origin ), self->target ); + } + self->enemy = ent; + } else { + G_SetMovedir( self->s.angles, self->movedir ); + } + + self->use = target_laser_use; + self->think = target_laser_think; + + if ( !self->damage ) { + self->damage = 1; + } + + if ( self->spawnflags & 1 ) { + target_laser_on( self ); + } else { + target_laser_off( self ); + } +} + +void SP_target_laser( gentity_t *self ) { + // let everything else get spawned before we start firing + self->think = target_laser_start; + self->nextthink = level.time + FRAMETIME; +} + + +//========================================================== + +void target_teleporter_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + gentity_t *dest; + + if ( !activator->client ) { + return; + } + dest = G_PickTarget( self->target ); + if ( !dest ) { + G_Printf( "Couldn't find teleporter destination\n" ); + return; + } + + TeleportPlayer( activator, dest->s.origin, dest->s.angles ); +} + +/*QUAKED target_teleporter (1 0 0) (-8 -8 -8) (8 8 8) +The activator will be teleported away. +*/ +void SP_target_teleporter( gentity_t *self ) { + if ( !self->targetname ) { + G_Printf( "untargeted %s at %s\n", self->classname, vtos( self->s.origin ) ); + } + + self->use = target_teleporter_use; +} + +//========================================================== + + +/*QUAKED target_relay (1 1 0) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM NOKEY_ONLY TAKE_KEY NO_LOCKED_NOISE +This doesn't perform any actions except fire its targets. +The activator can be forced to be from a certain team. +if RANDOM is checked, only one of the targets will be fired, not all of them +"key" specifies an item you can be carrying that affects the operation of this relay +this key is currently an int (1-16) which matches the id of a key entity (key_key1 = 1, etc) +NOKEY_ONLY means "fire only if I do /not/ have the specified key" +TAKE_KEY removes the key from the players inventory +"lockednoise" specifies a .wav file to play if the relay is used and the player doesn't have the necessary key. +By default this sound is "sound/movers/doors/default_door_locked.wav" +NO_LOCKED_NOISE specifies that it will be silent if activated without proper key +*/ +void target_relay_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + if ( ( self->spawnflags & 1 ) && activator && activator->client + && activator->client->sess.sessionTeam != TEAM_RED ) { + return; + } + if ( ( self->spawnflags & 2 ) && activator && activator->client + && activator->client->sess.sessionTeam != TEAM_BLUE ) { + return; + } + + if ( self->spawnflags & 4 ) { + gentity_t *ent; + + ent = G_PickTarget( self->target ); + if ( ent && ent->use ) { + ent->use( ent, self, activator ); + } + return; + } + + if ( activator ) { // activator can be NULL if called from script + if ( self->key ) { + gitem_t *item; + + if ( self->key == -1 ) { // relay permanently locked + if ( self->soundPos1 ) { + G_Sound( self, self->soundPos1 ); //----(SA) added + } + return; + } + + item = BG_FindItemForKey( self->key, 0 ); + + if ( item ) { + if ( activator->client->ps.stats[STAT_KEYS] & ( 1 << item->giTag ) ) { // user has key + if ( self->spawnflags & 8 ) { // relay is NOKEY_ONLY and player has key + if ( self->soundPos1 ) { + G_Sound( self, self->soundPos1 ); //----(SA) added + } + return; + } + } else // user does not have key + { + if ( !( self->spawnflags & 8 ) ) { + if ( self->soundPos1 ) { + G_Sound( self, self->soundPos1 ); //----(SA) added + } + return; + } + } + } + + if ( self->spawnflags & 16 ) { // (SA) take key + activator->client->ps.stats[STAT_KEYS] &= ~( 1 << item->giTag ); + // (SA) TODO: "took inventory item" sound + } + } + } + + G_UseTargets( self, activator ); +} + + +void relay_AIScript_AlertEntity( gentity_t *self ) { + self->use( self, NULL, NULL ); +} + + +/* +============== +SP_target_relay +============== +*/ +void SP_target_relay( gentity_t *self ) { + char *sound; + + self->use = target_relay_use; + self->AIScript_AlertEntity = relay_AIScript_AlertEntity; + + if ( !( self->spawnflags & 32 ) ) { // !NO_LOCKED_NOISE + if ( G_SpawnString( "lockednoise", "0", &sound ) ) { + self->soundPos1 = G_SoundIndex( sound ); + } else { + self->soundPos1 = G_SoundIndex( "sound/movers/doors/default_door_locked.wav" ); + } + } + +} + + +//========================================================== + +/*QUAKED target_kill (.5 .5 .5) (-8 -8 -8) (8 8 8) kill_user_too +Kills the activator. (default) +If targets, they will be killed when this is fired +"kill_user_too" will still kill the activator when this ent has targets (default is only kill targets, not activator) +*/ +void target_kill_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + gentity_t *targ = NULL; + + if ( self->spawnflags & 1 ) { // kill usertoo + G_Damage( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG ); + } + + while ( ( targ = G_Find( targ, FOFS( targetname ), self->target ) ) != NULL ) { + if ( targ->aiCharacter ) { // (SA) if it's an ai character, free it nicely + targ->aiInactive = qtrue; + } else + { + // make sure it isn't going to respawn or show any events + targ->nextthink = 0; + if ( targ == activator ) { + continue; + } + + // RF, script_movers should die! + if ( !Q_stricmp( targ->classname, "script_mover" ) && targ->die ) { + targ->die( targ, self, self, targ->health, 0 ); + continue; + } + + trap_UnlinkEntity( targ ); + targ->use = 0; + targ->touch = 0; + targ->nextthink = level.time + FRAMETIME; + targ->think = G_FreeEntity; + } + } +} + +void SP_target_kill( gentity_t *self ) { + self->use = target_kill_use; +} + +/*DEFUNCT target_position (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for in-game calculation, like jumppad targets. +*/ +void SP_target_position( gentity_t *self ) { + G_SetOrigin( self, self->s.origin ); +} + +// Ridah, note to everyone: static functions can cause problems with the savegame code, so avoid +// using them unless necessary. if that function is then assigned to an entity, client or cast field, +// the game will not save. +//static void target_location_linkup(gentity_t *ent) +void target_location_linkup( gentity_t *ent ) { + int i; + int n; + + if ( level.locationLinked ) { + return; + } + + level.locationLinked = qtrue; + + level.locationHead = NULL; + + trap_SetConfigstring( CS_LOCATIONS, "unknown" ); + + for ( i = 0, ent = g_entities, n = 1; + i < level.num_entities; + i++, ent++ ) { + if ( ent->classname && !Q_stricmp( ent->classname, "target_location" ) ) { + // lets overload some variables! + ent->health = n; // use for location marking + trap_SetConfigstring( CS_LOCATIONS + n, ent->message ); + n++; + ent->nextTrain = level.locationHead; + level.locationHead = ent; + } + } + + // All linked together now +} + +/*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8) +Set "message" to the name of this location. +Set "count" to 0-7 for color. +0:white 1:red 2:green 3:yellow 4:blue 5:cyan 6:magenta 7:white + +Closest target_location in sight used for the location, if none +in site, closest in distance +*/ +void SP_target_location( gentity_t *self ) { + self->think = target_location_linkup; + self->nextthink = level.time + 200; // Let them all spawn first + + G_SetOrigin( self, self->s.origin ); +} + + + + + + + +//---- (SA) Wolf targets + +/* +============== +Use_Target_Autosave + save game for emergency backup or convienience +============== +*/ +/*void Use_Target_Autosave( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + G_SaveGame("autosave.svg"); +}*/ + + + +/* +============== +Use_Target_Counter +============== +*/ +void Use_Target_Counter( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->count < 0 ) { // if the count has already been hit, ignore this + return; + } + + ent->count -= 1; // dec count + +// G_Printf("count at: %d\n", ent->count); + + if ( !ent->count ) { // specified count is now hit +// G_Printf("firing!!\n"); + G_UseTargets( ent, other ); + } +} + +/* +============== +Use_Target_Lock +============== +*/ +void Use_Target_Lock( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *t = 0; + + while ( ( t = G_Find( t, FOFS( targetname ), ent->target ) ) != NULL ) + { +// G_Printf("target_lock locking entity with key: %d\n", ent->count); + t->key = ent->key; + G_SetAASBlockingEntity( t, t->key != 0 ); + } + +} + +//========================================================== + +/* +============== +Use_target_fog +============== +*/ +void Use_target_fog( gentity_t *ent, gentity_t *other, gentity_t *activator ) { +// CS_FOGVARS reads: +// near +// far +// density +// r,g,b +// time to complete + trap_SetConfigstring( CS_FOGVARS, va( "%f %f %f %f %f %f %i", 1.0f, (float)ent->s.density, 1.0f, (float)ent->dl_color[0], (float)ent->dl_color[1], (float)ent->dl_color[2], ent->s.time ) ); +} + +/*QUAKED target_fog (1 1 0) (-8 -8 -8) (8 8 8) +color picker chooses color of fog +"distance" sets fog distance. Use value '0' to give control back to the game (and use the fog values specified in the sky shader if present) +"time" time it takes to change fog to new value. default time is 1 sec +*/ +void SP_target_fog( gentity_t *ent ) { + int dist; + float ftime; + + ent->use = Use_target_fog; + + // ent->s.density will carry the 'distance' value + if ( G_SpawnInt( "distance", "0", &dist ) ) { + if ( dist >= 0 ) { + ent->s.density = dist; + } + } + + // ent->s.time will carry the 'time' value + if ( G_SpawnFloat( "time", "0.5", &ftime ) ) { + if ( ftime >= 0 ) { + ent->s.time = ftime * 1000; // sec to ms + } + } +} + +//========================================================== + +/*QUAKED target_counter (1 1 0) (-8 -8 -8) (8 8 8) +Increments the counter pointed to. +"count" is the key for the count value +*/ +void SP_target_counter( gentity_t *ent ) { +// G_Printf("target counter created with val of: %d\n", ent->count); + ent->use = Use_Target_Counter; +} + + + +/*QUAKED target_autosave (1 1 0) (-8 -8 -8) (8 8 8) +saves game to 'autosave.svg' when triggered then dies. +*/ +void SP_target_autosave( gentity_t *ent ) { + //ent->use = Use_Target_Autosave; + G_FreeEntity( ent ); +} + +//========================================================== + +/*QUAKED target_lock (1 1 0) (-8 -8 -8) (8 8 8) +Sets the door to a state requiring key n +"key" is the required key +so: +key:0 unlocks the door +key:-1 locks the door until a target_lock with key:0 +key:n means the door now requires key n +*/ +void SP_target_lock( gentity_t *ent ) { + ent->use = Use_Target_Lock; +} + + + +void Use_Target_Alarm( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + G_UseTargets( ent, other ); +} + +/*QUAKED target_alarm (1 1 0) (-4 -4 -4) (4 4 4) +does nothing yet (effectively a relay right now) +*/ +void SP_target_alarm( gentity_t *ent ) { + ent->use = Use_Target_Alarm; +} + +//---- end + +/*QUAKED target_smoke (1 0 0) (-32 -32 -16) (32 32 16) Black White SmokeON Gravity +1 second = 1000 +1 FRAME = 100 +delay = 100 = one millisecond default this is the maximum smoke that will show up +time = 5000 default before the smoke disipates +duration = 2000 before the smoke starts to alpha +start_size = 24 default +end_size = 96 default +wait = default is 50 the rate at which it will travel up +*/ + +/*void smoke_think (gentity_t *ent) +{ + gentity_t *tent; + + ent->nextthink = level.time + ent->delay; + + if (!(ent->spawnflags & 4)) + return; + + if (ent->health) + { + ent->health --; + if (!ent->health) + { + ent->think = G_FreeEntity; + ent->nextthink = level.time + FRAMETIME; + } + } + + tent = G_TempEntity (ent->r.currentOrigin, EV_SMOKE); + VectorCopy (ent->r.currentOrigin, tent->s.origin); + tent->s.time = ent->speed; + tent->s.time2 = ent->duration; + tent->s.density = ent->s.density; + + // this is used to set the size of the smoke particle + tent->s.angles2[0] = ent->start_size; + tent->s.angles2[1] = ent->end_size; + tent->s.angles2[2] = ent->wait; + + VectorCopy (ent->pos3, tent->s.origin2); + + if (ent->s.frame) // denotes reverse gravity effect + tent->s.frame = 1; + +}*/ + +void smoke_think( gentity_t *ent ) { + ent->nextthink = level.time + ent->s.constantLight; + + if ( !( ent->spawnflags & 4 ) ) { + return; + } + + if ( ent->s.dl_intensity ) { + ent->s.dl_intensity--; + if ( !ent->s.dl_intensity ) { + ent->think = G_FreeEntity; + ent->nextthink = level.time + FRAMETIME; + } + } +} + +void smoke_toggle( gentity_t *ent, gentity_t *self, gentity_t *activator ) { + if ( ent->spawnflags & 4 ) { // smoke is on turn it off + ent->spawnflags &= ~4; + trap_UnlinkEntity( ent ); + } else + { + ent->spawnflags |= 4; + trap_LinkEntity( ent ); + } +} + +void smoke_init( gentity_t *ent ) { + gentity_t *target; + vec3_t vec; + + ent->think = smoke_think; + ent->nextthink = level.time + FRAMETIME; + + if ( ent->target ) { + target = G_Find( NULL, FOFS( targetname ), ent->target ); + if ( target ) { + VectorSubtract( target->s.origin, ent->s.origin, vec ); + VectorCopy( vec, ent->pos3 ); + } else { + VectorSet( ent->s.origin2, 0, 0, 1 ); + } + } else + { + VectorSet( ent->s.origin2, 0, 0, 1 ); + } + + if ( ent->spawnflags & 4 ) { + trap_LinkEntity( ent ); + } +} + +void SP_target_smoke( gentity_t *ent ) { + + // (SA) don't use in multiplayer right now since it makes decyphering net messages almost impossible +// ent->think = G_FreeEntity; +// return; + // Arnout - modified this a lot to be sent to the client as one entity and then is shown at the client + + if ( !ent->delay ) { + ent->delay = 100; + } + + ent->use = smoke_toggle; + + ent->think = smoke_init; + ent->nextthink = level.time + FRAMETIME; + + G_SetOrigin( ent, ent->s.origin ); + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_SMOKER; + + if ( ent->spawnflags & 2 ) { + ent->s.density = 4; + } else { + ent->s.density = 0; + } + + // using "time" + ent->s.time = ent->speed; + if ( !ent->s.time ) { + ent->s.time = 5000; // 5 seconds + + } + ent->s.time2 = ent->duration; + if ( !ent->s.time2 ) { + ent->s.time2 = 2000; + } + + ent->s.angles2[0] = ent->start_size; + if ( !ent->s.angles2[0] ) { + ent->s.angles2[0] = 24; + } + + ent->s.angles2[1] = ent->end_size; + if ( !ent->s.angles2[1] ) { + ent->s.angles2[1] = 96; + } + + ent->s.angles2[2] = ent->wait; + if ( !ent->s.angles2[2] ) { + ent->s.angles2[2] = 50; + } + + // idiot check + if ( ent->s.time < ent->s.time2 ) { + ent->s.time = ent->s.time2 + 100; + } + + if ( ent->spawnflags & 8 ) { + ent->s.frame = 1; + } + + ent->s.dl_intensity = ent->health; + ent->s.constantLight = ent->delay; + + if ( ent->spawnflags & 4 ) { + trap_LinkEntity( ent ); + } + +} + + +/*QUAKED target_script_trigger (1 .7 .2) (-8 -8 -8) (8 8 8) +must have an aiName +must have a target + +when used it will fire its targets +*/ +void target_script_trigger_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *player; + + if ( ent->aiName ) { + player = AICast_FindEntityForName( "player" ); + if ( player ) { + AICast_ScriptEvent( AICast_GetCastState( player->s.number ), "trigger", ent->target ); + } + } + + // DHM - Nerve :: In multiplayer, we use the brush scripting only + if ( g_gametype.integer >= GT_WOLF && ent->scriptName ) { + G_Script_ScriptEvent( ent, "trigger", ent->target ); + } + + G_UseTargets( ent, other ); + +} + +void SP_target_script_trigger( gentity_t *ent ) { + G_SetOrigin( ent, ent->s.origin ); + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_GENERAL; + ent->use = target_script_trigger_use; +} + + +/*QUAKED target_rumble (0 0.75 0.8) (-8 -8 -8) (8 8 8) STARTOFF +wait = default is 2 seconds = time the entity will enable rumble effect +"pitch" value from 1 to 10 default is 5 +"yaw" value from 1 to 10 default is 5 + +"rampup" how much time it will take to reach maximum pitch and yaw in seconds +"rampdown" how long till effect ends after rampup is reached in seconds + +"startnoise" startingsound +"noise" the looping sound entity is to make +"endnoise" endsound + +"duration" the amount of time the effect is to last ei 1.0 sec 3.6 sec +*/ +int rumble_snd; + +void target_rumble_think( gentity_t * ent ) { + gentity_t *tent; + float ratio; + float time, time2; + float dapitch, dayaw; + qboolean validrumble = qtrue; + + if ( !( ent->count ) ) { + ent->timestamp = level.time; + ent->count++; + // start sound here + if ( ent->soundPos1 ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); + } + } else + { + // looping sound + ent->s.loopSound = ent->soundLoop; + } + + dapitch = ent->delay; + dayaw = ent->random; + ratio = 1.0f; + + if ( ent->start_size ) { + if ( level.time < ( ent->timestamp + ent->start_size ) ) { + time = level.time - ent->timestamp; + time2 = ( ent->timestamp + ent->start_size ) - ent->timestamp; + ratio = time / time2; + } else if ( level.time < ( ent->timestamp + ent->end_size + ent->start_size ) ) { + time = level.time - ent->timestamp; + time2 = ( ent->timestamp + ent->start_size + ent->end_size ) - ent->timestamp; + ratio = time2 / time; + } else { + validrumble = qfalse; + } + } + + if ( validrumble ) { + tent = G_TempEntity( ent->r.currentOrigin, EV_RUMBLE_EFX ); + + tent->s.angles[0] = dapitch * ratio; + tent->s.angles[1] = dayaw * ratio; + } + + // end sound + if ( level.time > ent->duration + ent->timestamp ) { + if ( ent->soundPos2 ) { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); + ent->s.loopSound = 0; + } + + ent->nextthink = 0; + } else { + ent->nextthink = level.time + 50; + } + +} + +void target_rumble_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->spawnflags & 1 ) { + ent->spawnflags &= ~1; + ent->think = target_rumble_think; + ent->count = 0; + ent->nextthink = level.time + 50; + } else + { + ent->spawnflags |= 1; + ent->think = NULL; + ent->count = 0; + } +} + +void SP_target_rumble( gentity_t *self ) { + char *pitch; + char *yaw; + char *rampup; + char *rampdown; + float dapitch; + float dayaw; + char *sound; + char *startsound; + char *endsound; + + if ( G_SpawnString( "noise", "100", &sound ) ) { + self->soundLoop = G_SoundIndex( sound ); + } + + if ( G_SpawnString( "startnoise", "100", &startsound ) ) { + self->soundPos1 = G_SoundIndex( startsound ); + } + + if ( G_SpawnString( "endnoise", "100", &endsound ) ) { + self->soundPos2 = G_SoundIndex( endsound ); + } + + self->use = target_rumble_use; + + G_SpawnString( "pitch", "0", &pitch ); + dapitch = atof( pitch ); + self->delay = dapitch; + if ( !( self->delay ) ) { + self->delay = 5; + } + + G_SpawnString( "yaw", "0", &yaw ); + dayaw = atof( yaw ); + self->random = dayaw; + if ( !( self->random ) ) { + self->random = 5; + } + + G_SpawnString( "rampup", "0", &rampup ); + self->start_size = atoi( rampup ) * 1000; + if ( !( self->start_size ) ) { + self->start_size = 1000; + } + + G_SpawnString( "rampdown", "0", &rampdown ); + self->end_size = atoi( rampdown ) * 1000; + if ( !( self->end_size ) ) { + self->end_size = 1000; + } + + if ( !( self->duration ) ) { + self->duration = 1000; + } else { + self->duration *= 1000; + } + + trap_LinkEntity( self ); +} diff --git a/src/game/g_team.c b/src/game/g_team.c new file mode 100644 index 0000000..4f40252 --- /dev/null +++ b/src/game/g_team.c @@ -0,0 +1,1589 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include + +#include "g_local.h" + +typedef struct teamgame_s +{ + float last_flag_capture; + int last_capture_team; +} teamgame_t; + +teamgame_t teamgame; + +void Team_InitGame( void ) { + memset( &teamgame, 0, sizeof teamgame ); +} + +int OtherTeam( int team ) { + if ( team == TEAM_RED ) { + return TEAM_BLUE; + } else if ( team == TEAM_BLUE ) { + return TEAM_RED; + } + return team; +} + +const char *TeamName( int team ) { + if ( team == TEAM_RED ) { + return "RED"; + } else if ( team == TEAM_BLUE ) { + return "BLUE"; + } else if ( team == TEAM_SPECTATOR ) { + return "SPECTATOR"; + } + return "FREE"; +} + +const char *OtherTeamName( int team ) { + if ( team == TEAM_RED ) { + return "BLUE"; + } else if ( team == TEAM_BLUE ) { + return "RED"; + } else if ( team == TEAM_SPECTATOR ) { + return "SPECTATOR"; + } + return "FREE"; +} + +const char *TeamColorString( int team ) { + if ( team == TEAM_RED ) { + return S_COLOR_RED; + } else if ( team == TEAM_BLUE ) { + return S_COLOR_BLUE; + } else if ( team == TEAM_SPECTATOR ) { + return S_COLOR_YELLOW; + } + return S_COLOR_WHITE; +} + +// NULL for everyone +void QDECL PrintMsg( gentity_t *ent, const char *fmt, ... ) { + char msg[1024]; + va_list argptr; + char *p; + + // NOTE: if buffer overflow, it's more likely to corrupt stack and crash than do a proper G_Error? + va_start( argptr,fmt ); + if ( vsprintf( msg, fmt, argptr ) > sizeof( msg ) ) { + G_Error( "PrintMsg overrun" ); + } + va_end( argptr ); + + // double quotes are bad + while ( ( p = strchr( msg, '"' ) ) != NULL ) + *p = '\''; + + trap_SendServerCommand( ( ( ent == NULL ) ? -1 : ent - g_entities ), va( "print \"%s\"", msg ) ); +} + +/* +============== +OnSameTeam +============== +*/ +qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ) { + if ( !ent1->client || !ent2->client ) { + return qfalse; + } + + if ( g_gametype.integer < GT_TEAM ) { + return qfalse; + } + + if ( ent1->client->sess.sessionTeam == ent2->client->sess.sessionTeam ) { + return qtrue; + } + + return qfalse; +} + +// JPW NERVE moved these up +#define WCP_ANIM_NOFLAG 0 +#define WCP_ANIM_RAISE_AXIS 1 +#define WCP_ANIM_RAISE_AMERICAN 2 +#define WCP_ANIM_AXIS_RAISED 3 +#define WCP_ANIM_AMERICAN_RAISED 4 +#define WCP_ANIM_AXIS_TO_AMERICAN 5 +#define WCP_ANIM_AMERICAN_TO_AXIS 6 +#define WCP_ANIM_AXIS_FALLING 7 +#define WCP_ANIM_AMERICAN_FALLING 8 +// jpw + +/* +================ +Team_FragBonuses + +Calculate the bonuses for flag defense, flag carrier defense, etc. +Note that bonuses are not cumlative. You get one, they are in importance +order. +================ +*/ +void Team_FragBonuses( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker ) { + int i; + gentity_t *ent; + int flag_pw, enemy_flag_pw; + int otherteam; + gentity_t *flag, *carrier = NULL; + char *c; + vec3_t v1, v2; + int team; + + // no bonus for fragging yourself + if ( !targ->client || !attacker->client || targ == attacker ) { + return; + } + + team = targ->client->sess.sessionTeam; + otherteam = OtherTeam( targ->client->sess.sessionTeam ); + if ( otherteam < 0 ) { + return; // whoever died isn't on a team + + } +// JPW NERVE -- no bonuses for fragging friendlies, penalties scored elsewhere + if ( team == attacker->client->sess.sessionTeam ) { + return; + } +// jpw + + // same team, if the flag at base, check to he has the enemy flag + if ( team == TEAM_RED ) { + flag_pw = PW_REDFLAG; + enemy_flag_pw = PW_BLUEFLAG; + } else { + flag_pw = PW_BLUEFLAG; + enemy_flag_pw = PW_REDFLAG; + } + + // did the attacker frag the flag carrier? + if ( targ->client->ps.powerups[enemy_flag_pw] ) { + attacker->client->pers.teamState.lastfraggedcarrier = level.time; + if ( g_gametype.integer >= GT_WOLF ) { + AddScore( attacker, WOLF_FRAG_CARRIER_BONUS ); + } else { + AddScore( attacker, CTF_FRAG_CARRIER_BONUS ); + PrintMsg( NULL, "%s" S_COLOR_WHITE " fragged %s's flag carrier!\n", + attacker->client->pers.netname, TeamName( team ) ); + } + attacker->client->pers.teamState.fragcarrier++; + + // the target had the flag, clear the hurt carrier + // field on the other team + for ( i = 0; i < g_maxclients.integer; i++ ) { + ent = g_entities + i; + if ( ent->inuse && ent->client->sess.sessionTeam == otherteam ) { + ent->client->pers.teamState.lasthurtcarrier = 0; + } + } + return; + } + if ( g_gametype.integer < GT_WOLF ) { // JPW NERVE no danger protect in wolf + if ( targ->client->pers.teamState.lasthurtcarrier && + level.time - targ->client->pers.teamState.lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT && + !attacker->client->ps.powerups[flag_pw] ) { + // attacker is on the same team as the flag carrier and + // fragged a guy who hurt our flag carrier + AddScore( attacker, CTF_CARRIER_DANGER_PROTECT_BONUS ); + + attacker->client->pers.teamState.carrierdefense++; + team = attacker->client->sess.sessionTeam; + PrintMsg( NULL, "%s" S_COLOR_WHITE " defends %s's flag carrier against an agressive enemy\n", + attacker->client->pers.netname, TeamName( team ) ); + return; + } + } + + // flag and flag carrier area defense bonuses + + // we have to find the flag and carrier entities + + // find the flag + switch ( attacker->client->sess.sessionTeam ) { + case TEAM_RED: + c = "team_CTF_redflag"; + break; + case TEAM_BLUE: + c = "team_CTF_blueflag"; + break; + default: + return; + } + + flag = NULL; + while ( ( flag = G_Find( flag, FOFS( classname ), c ) ) != NULL ) { + if ( !( flag->flags & FL_DROPPED_ITEM ) ) { + break; + } + } + + if ( flag ) { // JPW NERVE -- added some more stuff after this fn +// return; // can't find attacker's flag + + // find attacker's team's flag carrier + for ( i = 0; i < g_maxclients.integer; i++ ) { + carrier = g_entities + i; + if ( carrier->inuse && carrier->client->ps.powerups[flag_pw] ) { + break; + } + carrier = NULL; + } + + // ok we have the attackers flag and a pointer to the carrier + + // check to see if we are defending the base's flag + VectorSubtract( targ->client->ps.origin, flag->s.origin, v1 ); + VectorSubtract( attacker->client->ps.origin, flag->s.origin, v2 ); + + if ( ( VectorLength( v1 ) < CTF_TARGET_PROTECT_RADIUS || + VectorLength( v2 ) < CTF_TARGET_PROTECT_RADIUS || + CanDamage( flag, targ->client->ps.origin ) || CanDamage( flag, attacker->client->ps.origin ) ) && + attacker->client->sess.sessionTeam != targ->client->sess.sessionTeam ) { + // we defended the base flag + if ( g_gametype.integer >= GT_WOLF ) { // JPW NERVE FIXME -- don't report flag defense messages, change to gooder message + AddScore( attacker, WOLF_FLAG_DEFENSE_BONUS ); + } else { + AddScore( attacker, CTF_FLAG_DEFENSE_BONUS ); + if ( !flag->r.contents ) { + PrintMsg( NULL, "%s" S_COLOR_WHITE " defends the %s base.\n", + attacker->client->pers.netname, + TeamName( attacker->client->sess.sessionTeam ) ); + } else { + PrintMsg( NULL, "%s" S_COLOR_WHITE " defends the %s flag.\n", + attacker->client->pers.netname, + TeamName( attacker->client->sess.sessionTeam ) ); + } + } + attacker->client->pers.teamState.basedefense++; + return; + } + + if ( g_gametype.integer < GT_WOLF ) { // JPW NERVE no attacker protect in wolf MP + if ( carrier && carrier != attacker ) { + VectorSubtract( targ->s.origin, carrier->s.origin, v1 ); + VectorSubtract( attacker->s.origin, carrier->s.origin, v1 ); + + if ( VectorLength( v1 ) < CTF_ATTACKER_PROTECT_RADIUS || + VectorLength( v2 ) < CTF_ATTACKER_PROTECT_RADIUS || + CanDamage( carrier, targ->s.origin ) || CanDamage( carrier, attacker->s.origin ) ) { + AddScore( attacker, CTF_CARRIER_PROTECT_BONUS ); + attacker->client->pers.teamState.carrierdefense++; + PrintMsg( NULL, "%s" S_COLOR_WHITE " defends the %s's flag carrier.\n", + attacker->client->pers.netname, + TeamName( attacker->client->sess.sessionTeam ) ); + return; + } + } + } + + } // JPW NERVE + +// JPW NERVE -- look for nearby checkpoints and spawnpoints + flag = NULL; + while ( ( flag = G_Find( flag, FOFS( classname ), "team_WOLF_checkpoint" ) ) != NULL ) { + VectorSubtract( targ->client->ps.origin, flag->s.origin, v1 ); + if ( ( flag->s.frame != WCP_ANIM_NOFLAG ) && ( flag->count == attacker->client->sess.sessionTeam ) ) { + if ( VectorLength( v1 ) < WOLF_CP_PROTECT_RADIUS ) { + if ( flag->spawnflags & 1 ) { // protected spawnpoint + AddScore( attacker, WOLF_SP_PROTECT_BONUS ); + } else { + AddScore( attacker, WOLF_CP_PROTECT_BONUS ); // protected checkpoint + } + } + } + } +// jpw + +} + +/* +================ +Team_CheckHurtCarrier + +Check to see if attacker hurt the flag carrier. Needed when handing out bonuses for assistance to flag +carrier defense. +================ +*/ +void Team_CheckHurtCarrier( gentity_t *targ, gentity_t *attacker ) { + int flag_pw; + + if ( !targ->client || !attacker->client ) { + return; + } + + if ( targ->client->sess.sessionTeam == TEAM_RED ) { + flag_pw = PW_BLUEFLAG; + } else { + flag_pw = PW_REDFLAG; + } + + if ( targ->client->ps.powerups[flag_pw] && + targ->client->sess.sessionTeam != attacker->client->sess.sessionTeam ) { + attacker->client->pers.teamState.lasthurtcarrier = level.time; + } +} + + +gentity_t *Team_ResetFlag( int team ) { + char *c; + gentity_t *ent, *rent = NULL; + + switch ( team ) { + case TEAM_RED: + c = "team_CTF_redflag"; + break; + case TEAM_BLUE: + c = "team_CTF_blueflag"; + break; + default: + return NULL; + } + + ent = NULL; + while ( ( ent = G_Find( ent, FOFS( classname ), c ) ) != NULL ) { + if ( ent->flags & FL_DROPPED_ITEM ) { + G_FreeEntity( ent ); + } else { + rent = ent; + RespawnItem( ent ); + } + } + + return rent; +} + +void Team_ResetFlags( void ) { + Team_ResetFlag( TEAM_RED ); + Team_ResetFlag( TEAM_BLUE ); +} + +void Team_ReturnFlagSound( gentity_t *ent, int team ) { + // play powerup spawn sound to all clients + gentity_t *te; + + if ( ent == NULL ) { + G_Printf( "Warning: NULL passed to Team_ReturnFlagSound\n" ); + return; + } + + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); + te->s.eventParm = G_SoundIndex( team == TEAM_RED ? + "sound/multiplayer/axis/g-objective_secure.wav" : + "sound/multiplayer/allies/a-objective_secure.wav" ); + te->r.svFlags |= SVF_BROADCAST; +} + +void Team_ReturnFlag( int team ) { + Team_ReturnFlagSound( Team_ResetFlag( team ), team ); + PrintMsg( NULL, "The %s flag has returned!\n", TeamName( team ) ); +} + +void Team_FreeEntity( gentity_t *ent ) { + if ( ent->item->giTag == PW_REDFLAG ) { + Team_ReturnFlag( TEAM_RED ); + } else if ( ent->item->giTag == PW_BLUEFLAG ) { + Team_ReturnFlag( TEAM_BLUE ); + } +} + +/* +============== +Team_DroppedFlagThink + +Automatically set in Launch_Item if the item is one of the flags + +Flags are unique in that if they are dropped, the base flag must be respawned when they time out +============== +*/ +void Team_DroppedFlagThink( gentity_t *ent ) { + // TTimo might be used uninitialized + gentity_t *gm = NULL; + + if ( g_gametype.integer >= GT_WOLF ) { + gm = G_Find( NULL, FOFS( scriptName ), "game_manager" ); + } + + if ( ent->item->giTag == PW_REDFLAG ) { + Team_ReturnFlagSound( Team_ResetFlag( TEAM_RED ), TEAM_RED ); + if ( gm ) { + trap_SendServerCommand( -1, "cp \"Axis have returned the objective!\" 2" ); + G_Script_ScriptEvent( gm, "trigger", "axis_object_returned" ); + } + } else if ( ent->item->giTag == PW_BLUEFLAG ) { + Team_ReturnFlagSound( Team_ResetFlag( TEAM_BLUE ), TEAM_BLUE ); + if ( gm ) { + trap_SendServerCommand( -1, "cp \"Allies have returned the objective!\" 2" ); + G_Script_ScriptEvent( gm, "trigger", "allied_object_returned" ); + } + } + // Reset Flag will delete this entity +} + +int Team_TouchOurFlag( gentity_t *ent, gentity_t *other, int team ) { + int i; + gentity_t *player; + gclient_t *cl = other->client; + int our_flag, enemy_flag; + gentity_t *te, *gm; + + if ( cl->sess.sessionTeam == TEAM_RED ) { + our_flag = PW_REDFLAG; + enemy_flag = PW_BLUEFLAG; + } else { + our_flag = PW_BLUEFLAG; + enemy_flag = PW_REDFLAG; + } + + if ( ent->flags & FL_DROPPED_ITEM ) { + // hey, its not home. return it by teleporting it back +// JPW NERVE + if ( g_gametype.integer >= GT_WOLF ) { + AddScore( other, WOLF_SECURE_OBJ_BONUS ); + te = G_TempEntity( other->s.pos.trBase, EV_GLOBAL_SOUND ); + te->r.svFlags |= SVF_BROADCAST; + te->s.teamNum = cl->sess.sessionTeam; + + // DHM - Nerve :: Call trigger function in the 'game_manager' entity script + gm = G_Find( NULL, FOFS( scriptName ), "game_manager" ); + + if ( cl->sess.sessionTeam == TEAM_RED ) { + te->s.eventParm = G_SoundIndex( "sound/multiplayer/axis/g-objective_secure.wav" ); + trap_SendServerCommand( -1, va( "cp \"Axis have returned %s!\n\" 2", ent->message ) ); + if ( gm ) { + G_Script_ScriptEvent( gm, "trigger", "axis_object_returned" ); + } + } else { + te->s.eventParm = G_SoundIndex( "sound/multiplayer/allies/a-objective_secure.wav" ); + trap_SendServerCommand( -1, va( "cp \"Allies have returned %s!\n\" 2", ent->message ) ); + if ( gm ) { + G_Script_ScriptEvent( gm, "trigger", "allied_object_returned" ); + } + } + // dhm + } +// jpw 800 672 2420 + else { + PrintMsg( NULL, "%s" S_COLOR_WHITE " returned the %s flag!\n", + cl->pers.netname, TeamName( team ) ); + AddScore( other, CTF_RECOVERY_BONUS ); + } + other->client->pers.teamState.flagrecovery++; + other->client->pers.teamState.lastreturnedflag = level.time; + //ResetFlag will remove this entity! We must return zero + Team_ReturnFlagSound( Team_ResetFlag( team ), team ); + return 0; + } + + // DHM - Nerve :: GT_WOLF doesn't support capturing the flag + if ( g_gametype.integer >= GT_WOLF ) { + return 0; + } + + // the flag is at home base. if the player has the enemy + // flag, he's just won! + if ( !cl->ps.powerups[enemy_flag] ) { + return 0; // We don't have the flag + + } + PrintMsg( NULL, "%s" S_COLOR_WHITE " captured the %s flag!\n", + cl->pers.netname, TeamName( OtherTeam( team ) ) ); + + cl->ps.powerups[enemy_flag] = 0; + + teamgame.last_flag_capture = level.time; + teamgame.last_capture_team = team; + + // Increase the team's score + level.teamScores[ other->client->sess.sessionTeam ]++; + + other->client->pers.teamState.captures++; + + // other gets another 10 frag bonus + if ( g_gametype.integer >= GT_WOLF ) { + AddScore( other, WOLF_CAPTURE_BONUS ); + PrintMsg( NULL,"%s" S_COLOR_WHITE " captured enemy objective!\n",cl->pers.netname ); + } else { + AddScore( other, CTF_CAPTURE_BONUS ); + } + + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); + te->s.eventParm = G_SoundIndex( our_flag == PW_REDFLAG ? + "sound/teamplay/flagcap_red.wav" : + "sound/teamplay/flagcap_blu.wav" ); + te->r.svFlags |= SVF_BROADCAST; + + // Ok, let's do the player loop, hand out the bonuses + for ( i = 0; i < g_maxclients.integer; i++ ) { + player = &g_entities[i]; + if ( !player->inuse ) { + continue; + } + + if ( player->client->sess.sessionTeam != + cl->sess.sessionTeam ) { + player->client->pers.teamState.lasthurtcarrier = -5; + } else if ( player->client->sess.sessionTeam == + cl->sess.sessionTeam ) { + // TTimo gcc: suggest explicit braces to avoid ambiguous `else` + if ( player != other ) { +// JPW NERVE + if ( g_gametype.integer >= GT_WOLF ) { + AddScore( player, WOLF_CAPTURE_BONUS ); + } else { +// jpw + AddScore( player, CTF_CAPTURE_BONUS ); + } + } + // award extra points for capture assists +// JPW NERVE in non-wolf-mp only + if ( g_gametype.integer < GT_WOLF ) { + if ( player->client->pers.teamState.lastreturnedflag + + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time ) { + PrintMsg( NULL, + "%s" S_COLOR_WHITE " gets an assist for returning the %s flag!\n", + player->client->pers.netname, + TeamName( team ) ); + AddScore( player, CTF_RETURN_FLAG_ASSIST_BONUS ); + other->client->pers.teamState.assists++; + } + if ( player->client->pers.teamState.lastfraggedcarrier + + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time ) { + PrintMsg( NULL, "%s" S_COLOR_WHITE " gets an assist for fragging the %s flag carrier!\n", + player->client->pers.netname, + TeamName( OtherTeam( team ) ) ); + AddScore( player, CTF_FRAG_CARRIER_ASSIST_BONUS ); + other->client->pers.teamState.assists++; + } + } + } + } + Team_ResetFlags(); + + CalculateRanks(); + + return 0; // Do not respawn this automatically +} + +int Team_TouchEnemyFlag( gentity_t *ent, gentity_t *other, int team ) { + gclient_t *cl = other->client; + gentity_t *te, *gm; + + // hey, its not our flag, pick it up + if ( g_gametype.integer >= GT_WOLF ) { +// JPW NERVE + AddScore( other, WOLF_STEAL_OBJ_BONUS ); + te = G_TempEntity( other->s.pos.trBase, EV_GLOBAL_SOUND ); + te->r.svFlags |= SVF_BROADCAST; + te->s.teamNum = cl->sess.sessionTeam; + + // DHM - Nerve :: Call trigger function in the 'game_manager' entity script + gm = G_Find( NULL, FOFS( scriptName ), "game_manager" ); + + if ( cl->sess.sessionTeam == TEAM_RED ) { + te->s.eventParm = G_SoundIndex( "sound/multiplayer/axis/g-objective_taken.wav" ); + trap_SendServerCommand( -1, va( "cp \"Axis have stolen %s!\n\" 2", ent->message ) ); + if ( gm ) { + G_Script_ScriptEvent( gm, "trigger", "allied_object_stolen" ); + } + } else { + te->s.eventParm = G_SoundIndex( "sound/multiplayer/allies/a-objective_taken.wav" ); + trap_SendServerCommand( -1, va( "cp \"Allies have stolen %s!\n\" 2", ent->message ) ); + if ( gm ) { + G_Script_ScriptEvent( gm, "trigger", "axis_object_stolen" ); + } + } + // dhm +// jpw + } else { + PrintMsg( NULL, "%s" S_COLOR_WHITE " got the %s flag!\n", + other->client->pers.netname, TeamName( team ) ); + AddScore( other, CTF_FLAG_BONUS ); + } + + if ( team == TEAM_RED ) { + cl->ps.powerups[PW_REDFLAG] = INT_MAX; // flags never expire + } else { + cl->ps.powerups[PW_BLUEFLAG] = INT_MAX; // flags never expire + + } + cl->pers.teamState.flagsince = level.time; + + return -1; // Do not respawn this automatically, but do delete it if it was FL_DROPPED +} + +int Pickup_Team( gentity_t *ent, gentity_t *other ) { + int team; + gclient_t *cl = other->client; + + // figure out what team this flag is + if ( strcmp( ent->classname, "team_CTF_redflag" ) == 0 ) { + team = TEAM_RED; + } else if ( strcmp( ent->classname, "team_CTF_blueflag" ) == 0 ) { + team = TEAM_BLUE; + } else { + PrintMsg( other, "Don't know what team the flag is on.\n" ); + return 0; + } + +// JPW NERVE -- set flag model in carrying entity if multiplayer and flagmodel is set + if ( g_gametype.integer >= GT_WOLF ) { + other->message = ent->message; + other->s.otherEntityNum2 = ent->s.modelindex2; + } +// jpw + + return ( ( team == cl->sess.sessionTeam ) ? + Team_TouchOurFlag : Team_TouchEnemyFlag ) + ( ent, other, team ); +} + +/* +=========== +Team_GetLocation + +Report a location for the player. Uses placed nearby target_location entities +============ +*/ +gentity_t *Team_GetLocation( gentity_t *ent ) { + gentity_t *eloc, *best; + float bestlen, len; + vec3_t origin; + + best = NULL; + bestlen = 3 * 8192.0 * 8192.0; + + VectorCopy( ent->r.currentOrigin, origin ); + + for ( eloc = level.locationHead; eloc; eloc = eloc->nextTrain ) { + len = ( origin[0] - eloc->r.currentOrigin[0] ) * ( origin[0] - eloc->r.currentOrigin[0] ) + + ( origin[1] - eloc->r.currentOrigin[1] ) * ( origin[1] - eloc->r.currentOrigin[1] ) + + ( origin[2] - eloc->r.currentOrigin[2] ) * ( origin[2] - eloc->r.currentOrigin[2] ); + + if ( len > bestlen ) { + continue; + } + + if ( !trap_InPVS( origin, eloc->r.currentOrigin ) ) { + continue; + } + + bestlen = len; + best = eloc; + } + + return best; +} + + +/* +=========== +Team_GetLocation + +Report a location for the player. Uses placed nearby target_location entities +============ +*/ +qboolean Team_GetLocationMsg( gentity_t *ent, char *loc, int loclen ) { + gentity_t *best; + + best = Team_GetLocation( ent ); + + if ( !best ) { + return qfalse; + } + + if ( best->count ) { + if ( best->count < 0 ) { + best->count = 0; + } + if ( best->count > 7 ) { + best->count = 7; + } + Com_sprintf( loc, loclen, "%c%c[lon]%s[lof]" S_COLOR_WHITE, Q_COLOR_ESCAPE, best->count + '0', best->message ); + } else { + Com_sprintf( loc, loclen, "[lon]%s[lof]", best->message ); + } + + return qtrue; +} + + +/*---------------------------------------------------------------------------*/ + +// JPW NERVE +/* +======================= +FindFarthestObjectiveIndex + +pick MP objective farthest from passed in vector, return table index +======================= +*/ +int FindFarthestObjectiveIndex( vec3_t source ) { + int i,j = 0; + float dist = 0,tdist; + vec3_t tmp; +// int cs_obj = CS_MULTI_SPAWNTARGETS; +// char cs[MAX_STRING_CHARS]; +// char *objectivename; + + for ( i = 0; i < level.numspawntargets; i++ ) { + VectorSubtract( level.spawntargets[i],source,tmp ); + tdist = VectorLength( tmp ); + if ( tdist > dist ) { + dist = tdist; + j = i; + } + } + +/* + cs_obj += j; + trap_GetConfigstring( cs_obj, cs, sizeof(cs) ); + objectivename = Info_ValueForKey( cs, "spawn_targ"); + + G_Printf("got furthest dist (%f) at point %d (%s) of %d\n",dist,j,objectivename,i); +*/ + + return j; +} +// jpw + +// NERVE - SMF +/* +======================= +FindClosestObjectiveIndex + +NERVE - SMF - pick MP objective closest to the passed in vector, return table index +======================= +*/ +int FindClosestObjectiveIndex( vec3_t source ) { + int i,j = 0; + float dist = 10E20,tdist; + vec3_t tmp; + + for ( i = 0; i < level.numspawntargets; i++ ) { + VectorSubtract( level.spawntargets[i],source,tmp ); + tdist = VectorLength( tmp ); + if ( tdist < dist ) { + dist = tdist; + j = i; + } + } + + return j; +} +// -NERVE - SMF + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_TEAM_SPAWN_POINTS 32 +gentity_t *SelectRandomTeamSpawnPoint( int teamstate, team_t team, int spawnObjective ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_TEAM_SPAWN_POINTS]; + char *classname; + qboolean initialSpawn = qfalse; // DHM - Nerve + int i = 0,j; // JPW NERVE + int closest; // JPW NERVE + float shortest,tmp; // JPW NERVE + vec3_t target; // JPW NERVE + vec3_t farthest; // JPW NERVE FIXME this is temp + char cs[MAX_STRING_CHARS]; // NERVE - SMF + char *def; + int defendingTeam; + qboolean defender = qfalse; + + // NERVE - SMF - get defender + trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) ); + def = Info_ValueForKey( cs, "defender" ); + + if ( strlen( def ) > 0 ) { + defendingTeam = atoi( def ); + } else { + defendingTeam = -1; + } + + if ( defendingTeam && team == TEAM_BLUE ) { // allies + defender = qtrue; + } else if ( !defendingTeam && team == TEAM_RED ) { // axis + defender = qtrue; + } + + if ( teamstate == TEAM_BEGIN ) { + + // DHM - Nerve :: Don't check if spawn is active initially + initialSpawn = qtrue; + + if ( team == TEAM_RED ) { + classname = "team_CTF_redplayer"; + } else if ( team == TEAM_BLUE ) { + classname = "team_CTF_blueplayer"; + } else { + return NULL; + } + } else { + if ( team == TEAM_RED ) { + classname = "team_CTF_redspawn"; + } else if ( team == TEAM_BLUE ) { + classname = "team_CTF_bluespawn"; + } else { + return NULL; + } + } + count = 0; + + spot = NULL; + + while ( ( spot = G_Find( spot, FOFS( classname ), classname ) ) != NULL ) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } +// JPW NERVE + if ( g_gametype.integer >= GT_WOLF ) { + // Arnout - modified to allow intial spawnpoints to be disabled at gamestart + //if (!(spot->spawnflags & 2) && !initialSpawn ) + if ( ( initialSpawn && ( spot->spawnflags & 4 ) ) || + ( !initialSpawn && !( spot->spawnflags & 2 ) ) ) { + continue; + } + } +// jpw + spots[ count ] = spot; + if ( ++count == MAX_TEAM_SPAWN_POINTS ) { + break; + } + } + + if ( !count ) { // no spots that won't telefrag + return G_Find( NULL, FOFS( classname ), classname ); + } + +// JPW NERVE + if ( ( g_gametype.integer < GT_WOLF ) || ( !level.numspawntargets ) || initialSpawn ) { // no spawn targets or not wolf MP, do it the old way + selection = rand() % count; + return spots[ selection ]; + } else { + // If no spawnObjective, select target as farthest point from first team spawnpoint + // else replace this with the target coords pulled from the UI target selection + if ( spawnObjective ) { + i = spawnObjective - 1; + } else { + j = 0; + for ( j = 0; j < count; j++ ) { + if ( spots[j]->spawnflags & 1 ) { // only use spawnpoint if it's a permanent one + // NERVE - SMF - make defenders spawn all the way back by default + if ( defendingTeam < 0 ) { + i = FindFarthestObjectiveIndex( spots[j]->s.origin ); + } else if ( defender ) { + i = FindClosestObjectiveIndex( spots[j]->s.origin ); + } else { + i = FindFarthestObjectiveIndex( spots[j]->s.origin ); + } + + j = count; + } + } + } + VectorCopy( level.spawntargets[i],farthest ); +// G_Printf("using spawntarget %d (%f %f %f)\n",i,farthest[0],farthest[1],farthest[2]); + + // now that we've got farthest vector, figure closest spawnpoint to it + VectorSubtract( farthest,spots[0]->s.origin,target ); + shortest = VectorLength( target ); + closest = 0; + for ( i = 0; i < count; i++ ) { + VectorSubtract( farthest,spots[i]->s.origin,target ); + tmp = VectorLength( target ); + if ( ( spots[i]->spawnflags & 2 ) && ( tmp < shortest ) ) { + shortest = tmp; + closest = i; + } + } + return spots[closest]; + } +// jpw +} + + +/* +=========== +SelectCTFSpawnPoint + +============ +*/ +gentity_t *SelectCTFSpawnPoint( team_t team, int teamstate, vec3_t origin, vec3_t angles, int spawnObjective ) { + gentity_t *spot; + + spot = SelectRandomTeamSpawnPoint( teamstate, team, spawnObjective ); + + if ( !spot ) { + return SelectSpawnPoint( vec3_origin, origin, angles ); + } + + VectorCopy( spot->s.origin, origin ); + origin[2] += 9; + VectorCopy( spot->s.angles, angles ); + + return spot; +} + +/*---------------------------------------------------------------------------*/ + +/* +================== +TeamplayLocationsMessage + +Format: + clientNum location health armor weapon powerups + +================== +*/ +void TeamplayInfoMessage( gentity_t *ent ) { + int identClientNum, identHealth; // NERVE - SMF + char entry[1024]; + char string[1400]; + int stringlength; + int i, j; + gentity_t *player; + int cnt; + int h; + + // send the latest information on all clients + string[0] = 0; + stringlength = 0; + + for ( i = 0, cnt = 0; i < level.numConnectedClients && cnt < TEAM_MAXOVERLAY; i++ ) { + player = g_entities + level.sortedClients[i]; + if ( player->inuse && player->client->sess.sessionTeam == + ent->client->sess.sessionTeam ) { + + // DHM - Nerve :: If in LIMBO, don't show followee's health + if ( player->client->ps.pm_flags & PMF_LIMBO ) { + h = 0; + } else { + h = player->client->ps.stats[STAT_HEALTH]; + } + + if ( h < 0 ) { + h = 0; + } + + Com_sprintf( entry, sizeof( entry ), + " %i %i %i %i %i", + level.sortedClients[i], player->client->pers.teamState.location, h, player->s.powerups, player->client->ps.stats[STAT_PLAYER_CLASS] ); + j = strlen( entry ); + if ( stringlength + j > sizeof( string ) ) { + break; + } + strcpy( string + stringlength, entry ); + stringlength += j; + cnt++; + } + } + + // NERVE - SMF + identClientNum = ent->client->ps.identifyClient; + + if ( g_entities[identClientNum].team == ent->team && g_entities[identClientNum].client ) { + identHealth = g_entities[identClientNum].health; + } else { + identClientNum = -1; + identHealth = 0; + } + // -NERVE - SMF + + trap_SendServerCommand( ent - g_entities, va( "tinfo %i %i %i%s", identClientNum, identHealth, cnt, string ) ); +} + +void CheckTeamStatus( void ) { + int i; + gentity_t *loc, *ent; + + if ( level.time - level.lastTeamLocationTime > TEAM_LOCATION_UPDATE_TIME ) { + + level.lastTeamLocationTime = level.time; + + for ( i = 0; i < g_maxclients.integer; i++ ) { + ent = g_entities + i; + if ( ent->inuse && + ( ent->client->sess.sessionTeam == TEAM_RED || + ent->client->sess.sessionTeam == TEAM_BLUE ) ) { + loc = Team_GetLocation( ent ); + if ( loc ) { + ent->client->pers.teamState.location = loc->health; + } else { + ent->client->pers.teamState.location = 0; + } + } + } + + for ( i = 0; i < g_maxclients.integer; i++ ) { + ent = g_entities + i; + if ( ent->inuse && + ( ent->client->sess.sessionTeam == TEAM_RED || + ent->client->sess.sessionTeam == TEAM_BLUE ) ) { + TeamplayInfoMessage( ent ); + } + } + } +} + +/*-----------------------------------------------------------------*/ + +void Use_Team_InitialSpawnpoint( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->spawnflags & 4 ) { + ent->spawnflags &= ~4; + } else { + ent->spawnflags |= 4; + } +} + +void Use_Team_Spawnpoint( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->spawnflags & 2 ) { + ent->spawnflags &= ~2; + } else { + ent->spawnflags |= 2; + } +} + +/*QUAKED team_CTF_redplayer (1 0 0) (-16 -16 -16) (16 16 32) invulnerable unused startdisabled +Only in CTF games. Red players spawn here at game start. +*/ +void SP_team_CTF_redplayer( gentity_t *ent ) { + ent->use = Use_Team_InitialSpawnpoint; +} + + +/*QUAKED team_CTF_blueplayer (0 0 1) (-16 -16 -16) (16 16 32) invulnerable unused startdisabled +Only in CTF games. Blue players spawn here at game start. +*/ +void SP_team_CTF_blueplayer( gentity_t *ent ) { + ent->use = Use_Team_InitialSpawnpoint; +} + +// JPW NERVE edited quaked def +/*QUAKED team_CTF_redspawn (1 0 0) (-16 -16 -24) (16 16 32) invulnerable startactive +potential spawning position for axis team in wolfdm games. + +TODO: SelectRandomTeamSpawnPoint() will choose team_CTF_redspawn point that: + +1) has been activated (FL_SPAWNPOINT_ACTIVE) +2) isn't occupied and +3) is closest to team_WOLF_objective + +This allows spawnpoints to advance across the battlefield as new ones are +placed and/or activated. + +If target is set, point spawnpoint toward target activation +*/ +void SP_team_CTF_redspawn( gentity_t *ent ) { +// JPW NERVE + vec3_t dir; + + ent->enemy = G_PickTarget( ent->target ); + if ( ent->enemy ) { + VectorSubtract( ent->enemy->s.origin, ent->s.origin, dir ); + vectoangles( dir, ent->s.angles ); + } + + ent->use = Use_Team_Spawnpoint; +// jpw +} + +// JPW NERVE edited quaked def +/*QUAKED team_CTF_bluespawn (0 0 1) (-16 -16 -24) (16 16 32) invulnerable startactive +potential spawning position for allied team in wolfdm games. + +TODO: SelectRandomTeamSpawnPoint() will choose team_CTF_bluespawn point that: + +1) has been activated (active) +2) isn't occupied and +3) is closest to selected team_WOLF_objective + +This allows spawnpoints to advance across the battlefield as new ones are +placed and/or activated. + +If target is set, point spawnpoint toward target activation +*/ +void SP_team_CTF_bluespawn( gentity_t *ent ) { +// JPW NERVE + vec3_t dir; + + ent->enemy = G_PickTarget( ent->target ); + if ( ent->enemy ) { + VectorSubtract( ent->enemy->s.origin, ent->s.origin, dir ); + vectoangles( dir, ent->s.angles ); + } + + ent->use = Use_Team_Spawnpoint; +// jpw +} + +// JPW NERVE +/*QUAKED team_WOLF_objective (1 1 0.3) (-16 -16 -24) (16 16 32) +marker for objective + +This marker will be used for computing effective radius for +dynamite damage, as well as generating a list of objectives +that players can elect to spawn near to in the limbo spawn +screen. + +key "description" is short text key for objective name that +will appear in objective selection in limbo UI. +*/ +static int numobjectives = 0; // TTimo + +void objective_Register( gentity_t *self ) { + + char numspawntargets[128]; + int cs_obj = CS_MULTI_SPAWNTARGETS; + char cs[MAX_STRING_CHARS]; + + if ( numobjectives == MAX_MULTI_SPAWNTARGETS ) { + G_Error( "SP_team_WOLF_objective: exceeded MAX_MULTI_SPAWNTARGETS (%d)\n",MAX_MULTI_SPAWNTARGETS ); + } else { // Set config strings + cs_obj += numobjectives; + trap_GetConfigstring( cs_obj, cs, sizeof( cs ) ); + Info_SetValueForKey( cs, "spawn_targ", self->message ); + trap_SetConfigstring( cs_obj, cs ); + VectorCopy( self->s.origin, level.spawntargets[numobjectives] ); + } + + numobjectives++; + + // set current # spawntargets + level.numspawntargets = numobjectives; + trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) ); + sprintf( numspawntargets,"%d",numobjectives ); + Info_SetValueForKey( cs, "numspawntargets", numspawntargets ); + trap_SetConfigstring( CS_MULTI_INFO, cs ); +} + +void SP_team_WOLF_objective( gentity_t *ent ) { + char *desc; + + G_SpawnString( "description", "WARNING: No objective description set", &desc ); + ent->message = G_Alloc( strlen( desc ) + 1 ); + Q_strncpyz( ent->message, desc, strlen( desc ) + 1 ); + + // DHM - Nerve :: Give the script time to remove this ent if necessary + ent->nextthink = level.time + 150; + ent->think = objective_Register; +} +// jpw + + +// DHM - Nerve :: Capture and Hold Checkpoint flag +#define SPAWNPOINT 1 +#define CP_HOLD 2 +#define AXIS_ONLY 4 +#define ALLIED_ONLY 8 + +void checkpoint_touch( gentity_t *self, gentity_t *other, trace_t *trace ); + +void checkpoint_use_think( gentity_t *self ) { + + self->count2 = -1; + + if ( self->count == TEAM_RED ) { + self->health = 0; + } else { + self->health = 10; + } +} + +void checkpoint_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + + int holderteam; + int time; + + if ( !activator->client ) { + return; + } + + if ( ent->count < 0 ) { + checkpoint_touch( ent, activator, NULL ); + } + + holderteam = activator->client->sess.sessionTeam; + + if ( ent->count == holderteam ) { + return; + } + + if ( ent->count2 == level.time ) { + if ( holderteam == TEAM_RED ) { + time = ent->health / 2; + time++; + trap_SendServerCommand( activator - g_entities, va( "cp \"Flag will be held in %i seconds!\n\"", time ) ); + } else { + time = ( 10 - ent->health ) / 2; + time++; + trap_SendServerCommand( activator - g_entities, va( "cp \"Flag will be held in %i seconds!\n\"", time ) ); + } + return; + } + + if ( holderteam == TEAM_RED ) { + ent->health--; + if ( ent->health < 0 ) { + checkpoint_touch( ent, activator, NULL ); + return; + } + + time = ent->health / 2; + time++; + trap_SendServerCommand( activator - g_entities, va( "cp \"Flag will be held in %i seconds!\n\"", time ) ); + } else { + ent->health++; + if ( ent->health > 10 ) { + checkpoint_touch( ent, activator, NULL ); + return; + } + + time = ( 10 - ent->health ) / 2; + time++; + trap_SendServerCommand( activator - g_entities, va( "cp \"Flag will be held in %i seconds!\n\"", time ) ); + } + + ent->count2 = level.time; + ent->think = checkpoint_use_think; + ent->nextthink = level.time + 2000; +} + +void checkpoint_spawntouch( gentity_t *self, gentity_t *other, trace_t *trace ); // JPW NERVE + +// JPW NERVE +void checkpoint_hold_think( gentity_t *self ) { + switch ( self->s.frame ) { + case WCP_ANIM_RAISE_AXIS: + case WCP_ANIM_AXIS_RAISED: + level.capturetimes[TEAM_RED]++; + break; + case WCP_ANIM_RAISE_AMERICAN: + case WCP_ANIM_AMERICAN_RAISED: + level.capturetimes[TEAM_BLUE]++; + break; + default: + break; + } + self->nextthink = level.time + 5000; +} +// jpw + +void checkpoint_think( gentity_t *self ) { + + switch ( self->s.frame ) { + + case WCP_ANIM_NOFLAG: + break; + case WCP_ANIM_RAISE_AXIS: + self->s.frame = WCP_ANIM_AXIS_RAISED; + break; + case WCP_ANIM_RAISE_AMERICAN: + self->s.frame = WCP_ANIM_AMERICAN_RAISED; + break; + case WCP_ANIM_AXIS_RAISED: + break; + case WCP_ANIM_AMERICAN_RAISED: + break; + case WCP_ANIM_AXIS_TO_AMERICAN: + self->s.frame = WCP_ANIM_AMERICAN_RAISED; + break; + case WCP_ANIM_AMERICAN_TO_AXIS: + self->s.frame = WCP_ANIM_AXIS_RAISED; + break; + case WCP_ANIM_AXIS_FALLING: + self->s.frame = WCP_ANIM_NOFLAG; + break; + case WCP_ANIM_AMERICAN_FALLING: + self->s.frame = WCP_ANIM_NOFLAG; + break; + default: + break; + + } + +// JPW NERVE + if ( self->spawnflags & SPAWNPOINT ) { + self->touch = checkpoint_spawntouch; + } else if ( !( self->spawnflags & CP_HOLD ) ) { + self->touch = checkpoint_touch; + } + if ( ( g_gametype.integer == GT_WOLF_CPH ) && ( !( self->spawnflags & SPAWNPOINT ) ) ) { + self->think = checkpoint_hold_think; + self->nextthink = level.time + 5000; + } else { + self->nextthink = 0; + } +// jpw +} + +void checkpoint_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + + if ( self->count == other->client->sess.sessionTeam ) { + return; + } + +// JPW NERVE + if ( self->s.frame == WCP_ANIM_NOFLAG ) { + AddScore( other, WOLF_CP_CAPTURE ); + } else { + AddScore( other, WOLF_CP_RECOVER ); + } +// jpw + + // Set controlling team + self->count = other->client->sess.sessionTeam; + + // Set animation + if ( self->count == TEAM_RED ) { + if ( self->s.frame == WCP_ANIM_NOFLAG ) { + self->s.frame = WCP_ANIM_RAISE_AXIS; + } else if ( self->s.frame == WCP_ANIM_AMERICAN_RAISED ) { + self->s.frame = WCP_ANIM_AMERICAN_TO_AXIS; + } else { + self->s.frame = WCP_ANIM_AXIS_RAISED; + } + } else { + if ( self->s.frame == WCP_ANIM_NOFLAG ) { + self->s.frame = WCP_ANIM_RAISE_AMERICAN; + } else if ( self->s.frame == WCP_ANIM_AXIS_RAISED ) { + self->s.frame = WCP_ANIM_AXIS_TO_AMERICAN; + } else { + self->s.frame = WCP_ANIM_AMERICAN_RAISED; + } + } + + // Run script trigger + if ( self->count == TEAM_RED ) { + self->health = 0; + G_Script_ScriptEvent( self, "trigger", "axis_capture" ); + } else { + self->health = 10; + G_Script_ScriptEvent( self, "trigger", "allied_capture" ); + } + + // Play a sound + G_AddEvent( self, EV_GENERAL_SOUND, self->soundPos1 ); + + // Don't allow touch again until animation is finished + self->touch = NULL; + + self->think = checkpoint_think; + self->nextthink = level.time + 1000; +} + +// JPW NERVE -- if spawn flag is set, use this touch fn instead to turn on/off targeted spawnpoints +void checkpoint_spawntouch( gentity_t *self, gentity_t *other, trace_t *trace ) { + gentity_t *ent = NULL; + qboolean playsound = qtrue; + qboolean firsttime = qfalse; + + if ( self->count == other->client->sess.sessionTeam ) { + return; + } + +// JPW NERVE + if ( self->s.frame == WCP_ANIM_NOFLAG ) { + AddScore( other, WOLF_SP_CAPTURE ); + } else { + AddScore( other, WOLF_SP_RECOVER ); + } +// jpw + + if ( self->count < 0 ) { + firsttime = qtrue; + } + + // Set controlling team + self->count = other->client->sess.sessionTeam; + + // Set animation + if ( self->count == TEAM_RED ) { + if ( self->s.frame == WCP_ANIM_NOFLAG && !( self->spawnflags & ALLIED_ONLY ) ) { + self->s.frame = WCP_ANIM_RAISE_AXIS; + } else if ( self->s.frame == WCP_ANIM_NOFLAG ) { + self->s.frame = WCP_ANIM_NOFLAG; + playsound = qfalse; + } else if ( self->s.frame == WCP_ANIM_AMERICAN_RAISED && !( self->spawnflags & ALLIED_ONLY ) ) { + self->s.frame = WCP_ANIM_AMERICAN_TO_AXIS; + } else if ( self->s.frame == WCP_ANIM_AMERICAN_RAISED ) { + self->s.frame = WCP_ANIM_AMERICAN_FALLING; + } else { + self->s.frame = WCP_ANIM_AXIS_RAISED; + } + } else { + if ( self->s.frame == WCP_ANIM_NOFLAG && !( self->spawnflags & AXIS_ONLY ) ) { + self->s.frame = WCP_ANIM_RAISE_AMERICAN; + } else if ( self->s.frame == WCP_ANIM_NOFLAG ) { + self->s.frame = WCP_ANIM_NOFLAG; + playsound = qfalse; + } else if ( self->s.frame == WCP_ANIM_AXIS_RAISED && !( self->spawnflags & AXIS_ONLY ) ) { + self->s.frame = WCP_ANIM_AXIS_TO_AMERICAN; + } else if ( self->s.frame == WCP_ANIM_AXIS_RAISED ) { + self->s.frame = WCP_ANIM_AXIS_FALLING; + } else { + self->s.frame = WCP_ANIM_AMERICAN_RAISED; + } + } + + // If this is the first time it's being touched, and it was the opposing team + // touching a single-team reinforcement flag... don't do anything. + if ( firsttime && !playsound ) { + return; + } + + // Play a sound + if ( playsound ) { + G_AddEvent( self, EV_GENERAL_SOUND, self->soundPos1 ); + } + + // Run script trigger + if ( self->count == TEAM_RED ) { + G_Script_ScriptEvent( self, "trigger", "axis_capture" ); + } else { + G_Script_ScriptEvent( self, "trigger", "allied_capture" ); + } + + // Don't allow touch again until animation is finished + self->touch = NULL; + + self->think = checkpoint_think; + self->nextthink = level.time + 1000; + + // activate all targets + // Arnout - updated this to allow toggling of initial spawnpoints as well, plus now it only + // toggles spawnflags 2 for spawnpoint entities + if ( self->target ) { + while ( 1 ) { + ent = G_Find( ent, FOFS( targetname ), self->target ); + if ( !ent ) { + break; + } + if ( other->client->sess.sessionTeam == TEAM_RED ) { + if ( !strcmp( ent->classname,"team_CTF_redspawn" ) ) { + ent->spawnflags |= 2; + } else if ( !strcmp( ent->classname,"team_CTF_bluespawn" ) ) { + ent->spawnflags &= ~2; + } else if ( !strcmp( ent->classname,"team_CTF_redplayer" ) ) { + ent->spawnflags &= ~4; + } else if ( !strcmp( ent->classname,"team_CTF_blueplayer" ) ) { + ent->spawnflags |= 4; + } + } else { + if ( !strcmp( ent->classname,"team_CTF_bluespawn" ) ) { + ent->spawnflags |= 2; + } else if ( !strcmp( ent->classname,"team_CTF_redspawn" ) ) { + ent->spawnflags &= ~2; + } else if ( !strcmp( ent->classname,"team_CTF_blueplayer" ) ) { + ent->spawnflags &= ~4; + } else if ( !strcmp( ent->classname,"team_CTF_redplayer" ) ) { + ent->spawnflags |= 4; + } + } + } + } + +} +// jpw +/*QUAKED team_WOLF_checkpoint (.9 .3 .9) (-16 -16 0) (16 16 128) SPAWNPOINT CP_HOLD AXIS_ONLY ALLIED_ONLY +This is the flagpole players touch in Capture and Hold game scenarios. + +It will call specific trigger funtions in the map script for this object. +When allies capture, it will call "allied_capture". +When axis capture, it will call "axis_capture". + +// JPW NERVE if spawnpoint flag is set, think will turn on spawnpoints (specified as targets) +// for capture team and turn *off* targeted spawnpoints for opposing team +*/ +void SP_team_WOLF_checkpoint( gentity_t *ent ) { + char *capture_sound; + + if ( !ent->scriptName ) { + G_Error( "team_WOLF_checkpoint must have a \"scriptname\"\n" ); + } + + // Make sure the ET_TRAP entity type stays valid + ent->s.eType = ET_TRAP; + + // Model is user assignable, but it will always try and use the animations for flagpole.md3 + if ( ent->model ) { + ent->s.modelindex = G_ModelIndex( ent->model ); + } else { + ent->s.modelindex = G_ModelIndex( "models/multiplayer/flagpole/flagpole.md3" ); + } + + G_SpawnString( "noise", "sound/movers/doors/door6_open.wav", &capture_sound ); + ent->soundPos1 = G_SoundIndex( capture_sound ); + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + + VectorSet( ent->r.mins, -8, -8, 0 ); + VectorSet( ent->r.maxs, 8, 8, 128 ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + // s.frame is the animation number + ent->s.frame = WCP_ANIM_NOFLAG; + + // s.teamNum is which set of animations to use ( only 1 right now ) + ent->s.teamNum = 1; + + // Used later to set animations (and delay between captures) + ent->nextthink = 0; + + // Used to time how long it must be "held" to switch + ent->health = -1; + ent->count2 = -1; + + // 'count' signifies which team holds the checkpoint + ent->count = -1; + +// JPW NERVE + if ( ent->spawnflags & SPAWNPOINT ) { + ent->touch = checkpoint_spawntouch; + } else { + if ( ent->spawnflags & CP_HOLD ) { + ent->use = checkpoint_use; + } else { + ent->touch = checkpoint_touch; + } + } +// jpw + + trap_LinkEntity( ent ); +} diff --git a/src/game/g_team.h b/src/game/g_team.h new file mode 100644 index 0000000..cb5f772 --- /dev/null +++ b/src/game/g_team.h @@ -0,0 +1,99 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// JPW NERVE -- more #defs for GT_WOLF gametype +#define WOLF_CAPTURE_BONUS 15 // capturing major game objective +#define WOLF_STEAL_OBJ_BONUS 10 // stealing objective (first part of capture) +#define WOLF_SECURE_OBJ_BONUS 10 // securing objective from slain enemy +#define WOLF_MEDIC_BONUS 2 // medic resurrect teammate +#define WOLF_REPAIR_BONUS 2 // engineer repair (mg42 etc) bonus +#define WOLF_DYNAMITE_BONUS 5 // engineer dynamite barriacade (dynamite only flag) +#define WOLF_FRAG_CARRIER_BONUS 10 // bonus for fragging enemy carrier +#define WOLF_FLAG_DEFENSE_BONUS 5 // bonus for frag when shooter or target near flag position +#define WOLF_CP_CAPTURE 3 // uncapped checkpoint bonus +#define WOLF_CP_RECOVER 5 // capping an enemy-held checkpoint bonus +#define WOLF_SP_CAPTURE 1 // uncapped spawnpoint bonus +#define WOLF_SP_RECOVER 2 // recovering enemy-held spawnpoint +#define WOLF_CP_PROTECT_BONUS 3 // protect a capture point by shooting target near it +#define WOLF_SP_PROTECT_BONUS 1 // protect a spawnpoint +#define WOLF_FRIENDLY_PENALTY -3 // penalty for fragging teammate +#define WOLF_FRAG_BONUS 1 // score for fragging enemy soldier +#define WOLF_DYNAMITE_PLANT 5 // planted dynamite at objective +#define WOLF_DYNAMITE_DIFFUSE 5 // diffused dynamite at objective +#define WOLF_CP_PROTECT_RADIUS 600 // wolf capture protect radius +#define WOLF_AMMO_UP 1 // pt for giving ammo not to self +#define WOLF_HEALTH_UP 1 // pt for giving health not to self +#define AXIS_OBJECTIVE 1 +#define ALLIED_OBJECTIVE 2 +#define OBJECTIVE_DESTROYED 4 +// jpw + +#define CTF_CAPTURE_BONUS 5 // what you get for capture +#define CTF_TEAM_BONUS 0 // what your team gets for capture +#define CTF_RECOVERY_BONUS 1 // what you get for recovery +#define CTF_FLAG_BONUS 0 // what you get for picking up enemy flag +#define CTF_FRAG_CARRIER_BONUS 2 // what you get for fragging enemy flag carrier +#define CTF_FLAG_RETURN_TIME 40000 // seconds until auto return + +#define CTF_CARRIER_DANGER_PROTECT_BONUS 2 // bonus for fraggin someone who has recently hurt your flag carrier +#define CTF_CARRIER_PROTECT_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag carrier +#define CTF_FLAG_DEFENSE_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag +#define CTF_RETURN_FLAG_ASSIST_BONUS 1 // awarded for returning a flag that causes a capture to happen almost immediately +#define CTF_FRAG_CARRIER_ASSIST_BONUS 2 // award for fragging a flag carrier if a capture happens almost immediately + +#define CTF_TARGET_PROTECT_RADIUS 400 // the radius around an object being defended where a target will be worth extra frags +#define CTF_ATTACKER_PROTECT_RADIUS 400 // the radius around an object being defended where an attacker will get extra frags when making kills + +#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT 8 +#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT 10000 +#define CTF_RETURN_FLAG_ASSIST_TIMEOUT 10000 + +#define CTF_GRAPPLE_SPEED 750 // speed of grapple in flight +#define CTF_GRAPPLE_PULL_SPEED 750 // speed player is pulled at + +// Prototypes + +int OtherTeam( int team ); +const char *TeamName( int team ); +const char *OtherTeamName( int team ); +const char *TeamColorString( int team ); + +void Team_DroppedFlagThink( gentity_t *ent ); +void Team_FragBonuses( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker ); +void Team_CheckHurtCarrier( gentity_t *targ, gentity_t *attacker ); +void Team_InitGame( void ); +void Team_ReturnFlag( int team ); +void Team_FreeEntity( gentity_t *ent ); +gentity_t *SelectCTFSpawnPoint( team_t team, int teamstate, vec3_t origin, vec3_t angles, int spawnObjective ); +gentity_t *Team_GetLocation( gentity_t *ent ); +qboolean Team_GetLocationMsg( gentity_t *ent, char *loc, int loclen ); +void TeamplayInfoMessage( gentity_t *ent ); +void CheckTeamStatus( void ); + +int Pickup_Team( gentity_t *ent, gentity_t *other ); diff --git a/src/game/g_tramcar.c b/src/game/g_tramcar.c new file mode 100644 index 0000000..e4f1076 --- /dev/null +++ b/src/game/g_tramcar.c @@ -0,0 +1,1620 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + SP_Tramcar +*/ +#include "g_local.h" + +// defines +#define TRAMCAR_START_ON 1 +#define TRAMCAR_TOGGLE 2 +#define TRAMCAR_BLOCK_STOPS 4 +#define TRAMCAR_LEADER 8 + +void props_me109_think( gentity_t *ent ); +void ExplodePlaneSndFx( gentity_t *self ); + +void Think_SetupAirplaneWaypoints( gentity_t *ent ); +void truck_cam_think( gentity_t *ent ); + +// extern calls +extern void Think_SetupTrainTargets( gentity_t *ent ); +extern void Reached_BinaryMover( gentity_t *ent ); +extern void MatchTeam( gentity_t *teamLeader, int moverState, int time ); +extern void SetMoverState( gentity_t *ent, moverState_t moverState, int time ); +extern void Blocked_Door( gentity_t *ent, gentity_t *other ); +extern void Think_BeginMoving( gentity_t *ent ); +extern void propExplosionLarge( gentity_t *ent ); + +//////////////////////// +// truck states +//////////////////////// +typedef enum +{ + truck_nosound = 0, + truck_idle, + truck_gear1, + truck_gear2, + truck_gear3, + truck_reverse, + truck_moving, + truck_breaking, + truck_bouncy1, + truck_bouncy2, + truck_bouncy3 +} truck_states1; + +///////////////////////// +// truck sounds +///////////////////////// + +int truck_idle_snd; +int truck_gear1_snd; +int truck_gear2_snd; +int truck_gear3_snd; +int truck_reverse_snd; +int truck_moving_snd; +int truck_breaking_snd; +int truck_bouncy1_snd; +int truck_bouncy2_snd; +int truck_bouncy3_snd; +int truck_sound; + +//////////////////////// +// plane states +//////////////////////// +typedef enum +{ + plane_nosound = 0, + plane_idle, + plane_flyby1, + plane_flyby2, + plane_loop, + plane_choke, + plane_startup +} truck_states2; + +/////////////////////////// +// aircraft sounds +/////////////////////////// + +int fploop_snd; +int fpchoke_snd; +int fpattack_snd; +int fpexpdebris_snd; + +int fpflyby1_snd; +int fpflyby2_snd; +int fpidle_snd; +int fpstartup_snd; + +/////////////////////////// +// airplane parts +/////////////////////////// +int fuse_part; +int wing_part; +int tail_part; +int nose_part; +int crash_part; + +// functions to be added +void InitTramcar( gentity_t *ent ) { + vec3_t move; + float distance; + float light; + vec3_t color; + qboolean lightSet, colorSet; + char *sound; + + // This is here just for show + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if ( ent->model2 ) { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + } + + if ( !Q_stricmp( ent->classname, "props_me109" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/mapobjects/vehicles/m109s.md3" ); + } + + if ( !Q_stricmp( ent->classname, "truck_cam" ) ) { + ent->s.modelindex2 = G_ModelIndex( "models/mapobjects/vehicles/truck_base.md3" ); + } + + // if the "loopsound" key is set, use a constant looping sound when moving + if ( G_SpawnString( "noise", "100", &sound ) ) { + ent->s.loopSound = G_SoundIndex( sound ); + } + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + if ( lightSet || colorSet ) { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + ent->use = Use_BinaryMover; +// ent->reached = Reached_BinaryMover; + + ent->moverState = MOVER_POS1; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + + VectorCopy( ent->pos1, ent->r.currentOrigin ); + + trap_LinkEntity( ent ); + + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.pos.trBase ); + + // calculate time to reach second position from speed + VectorSubtract( ent->pos2, ent->pos1, move ); + distance = VectorLength( move ); + if ( !ent->speed ) { + ent->speed = 100; + } + VectorScale( move, ent->speed, ent->s.pos.trDelta ); + ent->s.pos.trDuration = distance * 1000 / ent->speed; + if ( ent->s.pos.trDuration <= 0 ) { + ent->s.pos.trDuration = 1; + } +} + +void Calc_Roll( gentity_t *ent ) { + gentity_t *target; + vec3_t vec; + vec3_t forward; + vec3_t right; + float dot; + float dot2; + vec3_t tang; + + target = ent->nextTrain; + + VectorCopy( ent->r.currentAngles, tang ); + tang[ROLL] = 0; + + AngleVectors( tang, forward, right, NULL ); + VectorSubtract( target->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec ); + VectorNormalize( vec ); + + dot = DotProduct( vec, forward ); + dot2 = DotProduct( vec, right ); + + ent->angle = (int) ent->angle; + + if ( dot2 > 0 ) { + if ( ent->s.apos.trBase[ROLL] < -( ent->angle * 2 ) ) { + ent->s.apos.trBase[ROLL] += 2; + } else if ( ent->s.apos.trBase[ROLL] > -( ent->angle * 2 ) ) { + ent->s.apos.trBase[ROLL] -= 2; + } + + if ( ent->s.apos.trBase[ROLL] > 90 ) { + ent->s.apos.trBase[ROLL] = 90; + } + } else if ( dot2 < 0 ) { + if ( ent->s.apos.trBase[ROLL] > -( ent->angle * 2 ) ) { + ent->s.apos.trBase[ROLL] -= 2; + } else if ( ent->s.apos.trBase[ROLL] < -( ent->angle * 2 ) ) { + ent->s.apos.trBase[ROLL] += 2; + } + + if ( ent->s.apos.trBase[ROLL] < -90 ) { + ent->s.apos.trBase[ROLL] = -90; + } + } else { + ent->s.apos.trBase[ROLL] = 0; + } + + +// G_Printf ("dot: %5.2f dot2: %5.2f\n", dot, dot2); + + +// VectorCopy (ent->r.currentAngles, ent->TargetAngles); + + trap_LinkEntity( ent ); + + ent->nextthink = level.time + 50; +} + + +#define MAXCHOICES 8 + +void GetNextTrack( gentity_t *ent ) { + gentity_t *track = NULL; + gentity_t *next; + gentity_t *choice[MAXCHOICES]; + int num_choices = 0; + int rval; + + next = ent->nextTrain; + + if ( !( next->track ) ) { + G_Printf( "NULL track name for %s on %s\n", ent->classname, next->targetname ); + return; + } + + while ( 1 ) + { + track = G_Find( track, FOFS( targetname ), next->track ); + + if ( !track ) { + break; + } + + choice[num_choices++] = track; + + if ( num_choices == MAXCHOICES ) { + break; + } + } + + if ( !num_choices ) { + G_Printf( "GetNextTrack didnt find a track\n" ); + return; + } + + rval = rand() % num_choices; + + ent->nextTrain = NULL; + ent->target = choice[rval]->targetname; + +} + +void Reached_Tramcar( gentity_t *ent ) { + gentity_t *next; + float speed; + vec3_t move; + float length; + + // copy the apropriate values + next = ent->nextTrain; + if ( !next || !next->nextTrain ) { + return; // just stop + } + + // Rafael + if ( next->wait == -1 && next->count ) { + // G_Printf ("stoped wait = -1 count %i\n",next->count); + return; + } + + if ( !Q_stricmp( ent->classname, "props_me109" ) ) { + vec3_t vec, angles; + float diff; + + if ( next->spawnflags & 8 ) { // laps + next->count--; + + if ( !next->count ) { + next->count = next->count2; + + GetNextTrack( ent ); + Think_SetupAirplaneWaypoints( ent ); + + next = ent->nextTrain; + + G_Printf( "changed track to %s\n", next->targetname ); + } else { + G_Printf( "%s lap %i\n", next->targetname, next->count ); + } + } else if ( ( next->spawnflags & 1 ) && !( next->count ) && ent->health > 0 ) { // SCRIPT flag + GetNextTrack( ent ); + Think_SetupAirplaneWaypoints( ent ); + } else if ( ( next->spawnflags & 2 ) && ( ent->spawnflags & 8 ) && ent->health <= 0 && ent->takedamage ) { // death path + ent->takedamage = qfalse; + + GetNextTrack( ent ); + Think_SetupAirplaneWaypoints( ent ); + } else if ( ( next->spawnflags & 4 ) ) { // explode the plane + ExplodePlaneSndFx( ent ); + + ent->s.modelindex = crash_part; + // spawn the wing at the player effect + + ent->nextTrain = NULL; + G_UseTargets( next, NULL ); + + return; + } + + VectorSubtract( ent->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec ); + vectoangles( vec, angles ); + + + diff = AngleSubtract( ent->r.currentAngles [YAW], angles[YAW] ); + // diff = AngleSubtract (ent->TargetAngles [YAW], angles[YAW]); + + ent->rotate[1] = 1; + ent->angle = -diff; + + //if (angles[YAW] == 0) + // ent->s.apos.trDuration = ent->s.pos.trDuration; + //else + // ent->s.apos.trDuration = 1000; + + { + VectorCopy( next->s.origin, ent->pos1 ); + VectorCopy( next->nextTrain->s.origin, ent->pos2 ); + + // if the path_corner has a speed, use that + if ( next->speed ) { + speed = next->speed; + } else { + // otherwise use the train's speed + speed = ent->speed; + } + if ( speed < 1 ) { + speed = 1; + } + + // calculate duration + VectorSubtract( ent->pos2, ent->pos1, move ); + length = VectorLength( move ); + + ent->s.apos.trDuration = length * 1000 / speed; + +//testing +// ent->gDuration = ent->s.apos.trDuration; + ent->gDurationBack = ent->gDuration = ent->s.apos.trDuration; +// ent->gDeltaBack = ent->gDelta = + + } + + VectorClear( ent->s.apos.trDelta ); + + SetMoverState( ent, MOVER_1TO2ROTATE, level.time ); + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + + trap_LinkEntity( ent ); + + ent->think = props_me109_think; + ent->nextthink = level.time + 50; + } else if ( !Q_stricmp( ent->classname, "truck_cam" ) ) { + G_Printf( "target: %s\n", next->targetname ); + + if ( next->spawnflags & 2 ) { // END + ent->s.loopSound = 0; // stop sound + ent->nextTrain = NULL; + return; + } else + { + vec3_t vec, angles; + float diff; + + if ( next->spawnflags & 4 ) { // reverse + ent->props_frame_state = truck_reverse; + VectorSubtract( ent->r.currentOrigin, ent->nextTrain->nextTrain->s.origin, vec ); + } else + { + ent->props_frame_state = truck_moving; + VectorSubtract( ent->nextTrain->nextTrain->s.origin, ent->r.currentOrigin, vec ); + } + + vectoangles( vec, angles ); + + diff = AngleSubtract( ent->r.currentAngles [YAW], angles[YAW] ); + + ent->rotate[1] = 1; + ent->angle = -diff; + + if ( angles[YAW] == 0 ) { + ent->s.apos.trDuration = ent->s.pos.trDuration; + } else { + ent->s.apos.trDuration = 1000; + } + +//testing + ent->gDuration = ent->s.pos.trDuration; + + VectorClear( ent->s.apos.trDelta ); + + SetMoverState( ent, MOVER_1TO2ROTATE, level.time ); + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + + trap_LinkEntity( ent ); + } + + if ( next->wait == -1 ) { + ent->props_frame_state = truck_idle; + } + + if ( next->count2 == 1 ) { + ent->props_frame_state = truck_gear1; + } else if ( next->count2 == 2 ) { + ent->props_frame_state = truck_gear2; + } else if ( next->count2 == 3 ) { + ent->props_frame_state = truck_gear3; + } + + switch ( ent->props_frame_state ) + { + case truck_idle: ent->s.loopSound = truck_idle_snd; break; + case truck_gear1: ent->s.loopSound = truck_gear1_snd; break; + case truck_gear2: ent->s.loopSound = truck_gear2_snd; break; + case truck_gear3: ent->s.loopSound = truck_gear3_snd; break; + case truck_reverse: ent->s.loopSound = truck_reverse_snd; break; + case truck_moving: ent->s.loopSound = truck_moving_snd; break; + case truck_breaking: ent->s.loopSound = truck_breaking_snd; break; + case truck_bouncy1: ent->s.loopSound = truck_bouncy1_snd; break; + case truck_bouncy2: ent->s.loopSound = truck_bouncy2_snd; break; + case truck_bouncy3: ent->s.loopSound = truck_bouncy3_snd; break; + } + +//testing + ent->s.loopSound = truck_sound; + ent->think = truck_cam_think; + ent->nextthink = level.time + ( FRAMETIME / 2 ); + + } else if ( !Q_stricmp( ent->classname, "camera_cam" ) ) { + + } + + // fire all other targets + G_UseTargets( next, NULL ); + + // set the new trajectory + ent->nextTrain = next->nextTrain; + + if ( next->wait == -1 ) { + next->count = 1; + } + + VectorCopy( next->s.origin, ent->pos1 ); + VectorCopy( next->nextTrain->s.origin, ent->pos2 ); + + // if the path_corner has a speed, use that + if ( next->speed ) { + speed = next->speed; + } else { + // otherwise use the train's speed + speed = ent->speed; + } + if ( speed < 1 ) { + speed = 1; + } + + // calculate duration + VectorSubtract( ent->pos2, ent->pos1, move ); + length = VectorLength( move ); + + ent->s.pos.trDuration = length * 1000 / speed; + +//testing +// ent->gDuration = ent->s.pos.trDuration; + ent->gDurationBack = ent->gDuration = ent->s.pos.trDuration; +// ent->gDeltaBack = ent->gDelta = ; + + // looping sound + if ( next->soundLoop ) { + ent->s.loopSound = next->soundLoop; + } + + // start it going + SetMoverState( ent, MOVER_1TO2, level.time ); + + // if there is a "wait" value on the target, don't start moving yet + // if ( next->wait ) + if ( next->wait && next->wait != -1 ) { + ent->nextthink = level.time + next->wait * 1000; + ent->think = Think_BeginMoving; + ent->s.pos.trType = TR_STATIONARY; + } +} + +extern void func_explosive_explode( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ); + +void Tramcar_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + gentity_t *slave; + + func_explosive_explode( self, self, inflictor, 0, 0 ); + + // link all teammembers + for ( slave = self ; slave ; slave = slave->teamchain ) + { + + if ( slave == self ) { + continue; + } + + // slaves need to inherit position + slave->nextTrain = self->nextTrain; + + slave->s.pos.trType = self->s.pos.trType; + slave->s.pos.trTime = self->s.pos.trTime; + slave->s.pos.trDuration = self->s.pos.trDuration; + VectorCopy( self->s.pos.trBase, slave->s.pos.trBase ); + VectorCopy( self->s.pos.trDelta, slave->s.pos.trDelta ); + + slave->s.apos.trType = self->s.apos.trType; + slave->s.apos.trTime = self->s.apos.trTime; + slave->s.apos.trDuration = self->s.apos.trDuration; + VectorCopy( self->s.apos.trBase, slave->s.apos.trBase ); + VectorCopy( self->s.apos.trDelta, slave->s.apos.trDelta ); + + slave->think = self->think; + slave->nextthink = self->nextthink; + + VectorCopy( self->pos1, slave->pos1 ); + VectorCopy( self->pos2, slave->pos2 ); + + slave->speed = self->speed; + + slave->flags &= ~FL_TEAMSLAVE; + // make it visible + + if ( self->use ) { + slave->use = self->use; + } + + trap_LinkEntity( slave ); + } + + self->use = NULL; + + self->is_dead = qtrue; + + self->takedamage = qfalse; + + if ( self->nextTrain ) { + self->nextTrain = 0; + } + + self->s.loopSound = 0; + + VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); + VectorCopy( self->r.currentAngles, self->s.apos.trBase ); + + self->flags |= FL_TEAMSLAVE; + trap_UnlinkEntity( self ); + +} + +void TramCarUse( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *next; + + if ( level.time >= ent->s.pos.trTime + ent->s.pos.trDuration ) { + + next = ent->nextTrain; + + if ( next->wait == -1 && next->count ) { + next->count = 0; + //G_Printf ("Moving next->count %i\n", next->count); + } + + Reached_Tramcar( ent ); + + } +// else +// G_Printf ("no can do havent reached yet\n"); + +} + + +void Blocked_Tramcar( gentity_t *ent, gentity_t *other ) { + // remove anything other than a client + if ( !other->client ) { + // except CTF flags!!!! + if ( other->s.eType == ET_ITEM && other->item->giType == IT_TEAM ) { + Team_DroppedFlagThink( other ); + return; + } + G_TempEntity( other->s.origin, EV_ITEM_POP ); + G_FreeEntity( other ); + return; + } + + if ( other->flags & FL_GODMODE ) { + other->flags &= ~FL_GODMODE; + other->client->ps.stats[STAT_HEALTH] = other->health = 0; + } + + G_Damage( other, ent, ent, NULL, NULL, 99999, 0, MOD_CRUSH ); + +} + +/*QUAKED func_tramcar (0 .5 .8) ? START_ON TOGGLE - LEADER +health value of 999 will designate the piece as non damageable + +The leader of the tramcar group must have the leader flag set or +you'll end up with co-planer poly heaven. When the health of the Leader +of the team hits zero it will unlink and the team will become visible + +A tramcar is a mover that moves between path_corner target points. +all tramcar parts MUST HAVE AN ORIGIN BRUSH. (this is true for all parts) + +The tramcar spawns at the first target it is pointing at. + +If you are going to have enemies ride the tramcar it must be placed in its ending +position when you bsp the map you can the start it by targeting the desired path_corner + +"model2" .md3 model to also draw +"speed" default 100 +"dmg" default 2 +"noise" looping sound to play when the train is in motion +"target" next path corner +"color" constantLight color +"light" constantLight radius + +"type" type of debris ("glass", "wood", "metal", "gibs") default is "wood" +"mass" defaults to 75. This determines how much debris is emitted when it explodes. You get one large chunk per 100 of mass (up to 8) and one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void SP_func_tramcar( gentity_t *self ) { + + int mass; + char *type; + char *s; + char buffer[MAX_QPATH]; + + VectorClear( self->s.angles ); + + //if (self->spawnflags & TRAMCAR_BLOCK_STOPS) { + // self->damage = 0; + // self->s.eFlags |= EF_MOVER_STOP; + //} + //else { + if ( !self->damage ) { + self->damage = 100; + } + //} + + if ( !self->speed ) { + self->speed = 100; + } + + if ( !self->target ) { + G_Printf( "func_tramcar without a target at %s\n", vtos( self->r.absmin ) ); + G_FreeEntity( self ); + return; + } + + if ( self->spawnflags & TRAMCAR_LEADER ) { + if ( !self->health ) { + self->health = 50; + } + + self->takedamage = qtrue; + self->die = Tramcar_die; + + if ( self->health < 999 ) { + self->isProp = qtrue; + } + } + + trap_SetBrushModel( self, self->model ); + + if ( G_SpawnInt( "mass", "75", &mass ) ) { + self->count = mass; + } else { + self->count = 75; + } + + G_SpawnString( "type", "wood", &type ); + if ( !Q_stricmp( type,"wood" ) ) { + self->key = 0; + } else if ( !Q_stricmp( type,"glass" ) ) { + self->key = 1; + } else if ( !Q_stricmp( type,"metal" ) ) { + self->key = 2; + } else if ( !Q_stricmp( type,"gibs" ) ) { + self->key = 3; + } + + if ( G_SpawnString( "noise", "NOSOUND", &s ) ) { + if ( Q_stricmp( s, "nosound" ) ) { + Q_strncpyz( buffer, s, sizeof( buffer ) ); + self->s.dl_intensity = G_SoundIndex( buffer ); + } + } else { + switch ( self->key ) + { + case 0: // "wood" + self->s.dl_intensity = G_SoundIndex( "sound/world/boardbreak.wav" ); + break; + case 1: // "glass" + self->s.dl_intensity = G_SoundIndex( "sound/world/glassbreak.wav" ); + break; + case 2: // "metal" + self->s.dl_intensity = G_SoundIndex( "sound/world/metalbreak.wav" ); + break; + case 3: // "gibs" + self->s.dl_intensity = G_SoundIndex( "sound/player/gibsplit1.wav" ); + break; + } + } + + self->s.density = self->count; // pass the "mass" to the client + + InitTramcar( self ); + + self->reached = Reached_Tramcar; + + self->nextthink = level.time + ( FRAMETIME / 2 ); + + self->think = Think_SetupTrainTargets; + + self->blocked = Blocked_Tramcar; + + if ( self->spawnflags & TRAMCAR_TOGGLE ) { + self->use = TramCarUse; + } + +} + + +//////////////////////////// +// me109 +//////////////////////////// + +void plane_AIScript_AlertEntity( gentity_t *ent ) { + + // when count reaches 0, the marker is active + ent->count--; + + if ( ent->count <= 0 ) { + ent->count = 0; + } +} + +/*QUAKED plane_waypoint (.5 .3 0) (-8 -8 -8) (8 8 8) SCRIPTS DIE EXPLODE LAPS ATTACK +"count" number of times this waypoint needs to be triggered by an AIScript "alertentity" call before the aircraft changes tracks +"track" tells it what track to branch off to there can be several track with the same track name +the program will pick one randomly there can be a maximum of eight tracks at any branch + +the entity will fire its target when reached +*/ +void SP_plane_waypoint( gentity_t *self ) { + + if ( !self->targetname ) { + G_Printf( "plane_waypoint with no targetname at %s\n", vtos( self->s.origin ) ); + G_FreeEntity( self ); + return; + } + + if ( self->spawnflags & 1 ) { + self->AIScript_AlertEntity = plane_AIScript_AlertEntity; + } + + if ( self->count ) { + self->count2 = self->count; + } + + if ( self->wait == -1 ) { + self->count = 1; + } +} + + +/*QUAKED props_me109 (.7 .3 .1) (-128 -128 0) (128 128 64) START_ON TOGGLE SPINNINGPROP FIXED_DIE +default health = 1000 +*/ + +void ExplodePlaneSndFx( gentity_t *self ) { + gentity_t *temp; + vec3_t dir; + gentity_t *part; + int i; + vec3_t start; + + temp = G_Spawn(); + + if ( !temp ) { + return; + } + + G_SetOrigin( temp, self->melee->s.pos.trBase ); + G_AddEvent( temp, EV_GLOBAL_SOUND, fpexpdebris_snd ); + temp->think = G_FreeEntity; + temp->nextthink = level.time + 10000; + trap_LinkEntity( temp ); + + // added this because plane may be parked on runway + // we may want to add some exotic deaths to parked aircraft + if ( self->nextTrain && self->nextTrain->spawnflags & 4 ) { // explode the plane + // spawn the wing at the player + gentity_t *player; + vec3_t vec, ang; + + player = AICast_FindEntityForName( "player" ); + + if ( !player ) { + return; + } + + VectorSubtract( player->s.origin, self->r.currentOrigin, vec ); + vectoangles( vec, ang ); + AngleVectors( ang, dir, NULL, NULL ); + + dir[2] = 1; + + VectorCopy( self->r.currentOrigin, start ); + + part = fire_flamebarrel( temp, start, dir ); + + if ( !part ) { + G_Printf( "ExplodePlaneSndFx Failed to spawn part\n" ); + return; + } + + part->s.eType = ET_FP_PARTS; + + part->s.modelindex = wing_part; + + return; + } + + AngleVectors( self->r.currentAngles, dir, NULL, NULL ); + + for ( i = 0; i < 4; i++ ) + { + VectorCopy( self->r.currentOrigin, start ); + + start[0] += crandom() * 64; + start[1] += crandom() * 64; + start[2] += crandom() * 32; + + part = fire_flamebarrel( temp, start, dir ); + + if ( !part ) { + continue; + } + + part->s.eType = ET_FP_PARTS; + + if ( i == 0 ) { + part->s.modelindex = fuse_part; + } else if ( i == 1 ) { + part->s.modelindex = wing_part; + } else if ( i == 2 ) { + part->s.modelindex = tail_part; + } else { + part->s.modelindex = nose_part; + } + } +} + +void props_me109_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { + G_Printf( "dead\n" ); + + VectorClear( self->rotate ); + VectorSet( self->rotate, 0, 1, 0 ); //sigh + + if ( self->spawnflags & 8 ) { // FIXED_DIE + return; + } + + propExplosionLarge( self ); + self->melee->s.loopSound = self->melee->noise_index = 0; + ExplodePlaneSndFx( self ); + G_FreeEntity( self ); +} + +void props_me109_pain( gentity_t *self, gentity_t *attacker, int damage, vec3_t point ) { + vec3_t temp; + + G_Printf( "pain: health = %i\n", self->health ); + + VectorCopy( self->r.currentOrigin, temp ); + VectorCopy( self->pos3, self->r.currentOrigin ); + Spawn_Shard( self, NULL, 6, 999 ); + VectorCopy( temp, self->r.currentOrigin ); + + VectorClear( self->rotate ); + VectorSet( self->rotate, 0, 1, 0 ); //sigh +} + +void Plane_Fire_Lead( gentity_t *self ) { + vec3_t dir, right; + vec3_t pos1, pos2; + vec3_t position; + + AngleVectors( self->r.currentAngles, dir, right, NULL ); + VectorCopy( self->r.currentOrigin, position ); + VectorMA( position, 64, right, pos1 ); + VectorMA( position, -64, right, pos2 ); + + fire_lead( self, pos1, dir, 12 ); + fire_lead( self, pos2, dir, 12 ); +} + +void Plane_Attack( gentity_t *self, qboolean in_PVS ) { + if ( self->nextTrain->spawnflags & 16 ) { + self->count++; + + if ( self->count == 3 ) { + self->s.density = 8, self->count = 0; + + if ( in_PVS ) { + G_AddEvent( self, EV_GLOBAL_SOUND, fpattack_snd ); + } else { + G_AddEvent( self, EV_GENERAL_SOUND, fpattack_snd ); + } + + Plane_Fire_Lead( self ); + } else { + self->s.density = 7; + } + } else if ( self->spawnflags & 4 ) { // spinning prop + self->s.density = 7; + } else { + self->s.density = 0; + } +} + +void props_me109_think( gentity_t *self ) { + + qboolean in_PVS = qfalse; + + { + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( player ) { + in_PVS = trap_InPVS( player->r.currentOrigin, self->s.pos.trBase ); + + if ( in_PVS ) { + self->melee->s.eType = ET_GENERAL; + + { + float len; + vec3_t vec; + vec3_t forward; + vec3_t dir; + vec3_t point; + + VectorCopy( player->r.currentOrigin, point ); + VectorSubtract( player->r.currentOrigin, self->r.currentOrigin, vec ); + len = VectorLength( vec ); + vectoangles( vec, dir ); + AngleVectors( dir, forward, NULL, NULL ); + VectorMA( point, len * 0.1, forward, point ); + + G_SetOrigin( self->melee, point ); + } + } else + { + self->melee->s.eType = ET_GENERAL; + } + + trap_LinkEntity( self->melee ); + } + } + + Plane_Attack( self, in_PVS ); + + Calc_Roll( self ); + + if ( self->health < 250 ) { + gentity_t *tent; + vec3_t point; + + VectorCopy( self->r.currentOrigin, point ); + tent = G_TempEntity( point, EV_SMOKE ); + VectorCopy( point, tent->s.origin ); + tent->s.time = 2000; + tent->s.time2 = 1000; + tent->s.density = 4; + tent->s.angles2[0] = 16; + tent->s.angles2[1] = 48; + tent->s.angles2[2] = 10; + + self->props_frame_state = plane_choke; + self->health--; + } + + if ( self->health > 0 ) { + self->nextthink = level.time + 50; + + if ( self->props_frame_state == plane_choke ) { + self->melee->s.loopSound = self->melee->noise_index = fpchoke_snd; + } else if ( self->props_frame_state == plane_startup ) { + self->melee->s.loopSound = self->melee->noise_index = fpstartup_snd; + } else if ( self->props_frame_state == plane_idle ) { + self->melee->s.loopSound = self->melee->noise_index = fpidle_snd; + } else if ( self->props_frame_state == plane_flyby1 ) { + self->melee->s.loopSound = self->melee->noise_index = fpflyby1_snd; + } else if ( self->props_frame_state == plane_flyby2 ) { + self->melee->s.loopSound = self->melee->noise_index = fpflyby2_snd; + } + } else + { + propExplosionLarge( self ); + self->melee->s.loopSound = self->melee->noise_index = 0; + + ExplodePlaneSndFx( self ); + G_FreeEntity( self->melee ); + G_FreeEntity( self ); + + + } + +} + +void Think_SetupAirplaneWaypoints( gentity_t *ent ) { + gentity_t *path, *next, *start; + + ent->nextTrain = G_Find( NULL, FOFS( targetname ), ent->target ); + if ( !ent->nextTrain ) { + G_Printf( "plane at %s with an unfound target\n", + vtos( ent->r.absmin ) ); + return; + } + + start = NULL; + for ( path = ent->nextTrain ; path != start ; path = next ) { + if ( !start ) { + start = path; + } + + if ( !path->target ) { + G_Printf( "plane at %s without a target\n", + vtos( path->s.origin ) ); + return; + } + + // find a path_corner among the targets + // there may also be other targets that get fired when the corner + // is reached + next = NULL; + do { + next = G_Find( next, FOFS( targetname ), path->target ); + if ( !next ) { + G_Printf( "plane at %s without a target path_corner\n", + vtos( path->s.origin ) ); + return; + } + } while ( strcmp( next->classname, "plane_waypoint" ) ); + + path->nextTrain = next; + } + + if ( ent->spawnflags & 2 ) { // Toggle + VectorCopy( ent->nextTrain->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->nextTrain->s.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } else { + Reached_Tramcar( ent ); + } +} + + +void PlaneUse( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *next; + + if ( level.time >= ent->s.pos.trTime + ent->s.pos.trDuration ) { + + next = ent->nextTrain; + + if ( next->wait == -1 && next->count ) { + next->count = 0; + //G_Printf ("Moving next->count %i\n", next->count); + } + + Reached_Tramcar( ent ); + + } +// else +// G_Printf ("no can do havent reached yet\n"); + +} + + +void InitPlaneSpeaker( gentity_t *ent ) { + gentity_t *snd; + + snd = G_Spawn(); + + snd->noise_index = fploop_snd; + + snd->s.eType = ET_SPEAKER; + snd->s.eventParm = snd->noise_index; + snd->s.frame = 0; + snd->s.clientNum = 0; + + snd->s.loopSound = snd->noise_index; + + snd->r.svFlags |= SVF_BROADCAST; + + VectorCopy( ent->s.origin, snd->s.pos.trBase ); + + ent->melee = snd; + + trap_LinkEntity( snd ); + +} + +void SP_props_me109( gentity_t *ent ) { + + VectorSet( ent->r.mins, -128, -128, -128 ); + VectorSet( ent->r.maxs, 128, 128, 128 ); + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + + ent->isProp = qtrue; + + ent->s.modelindex = G_ModelIndex( "models/mapobjects/vehicles/m109.md3" ); + + if ( !ent->health ) { + ent->health = 500; + } + + ent->takedamage = qtrue; + + ent->die = props_me109_die; + ent->pain = props_me109_pain; + + ent->reached = Reached_Tramcar; + + ent->nextthink = level.time + ( FRAMETIME / 2 ); + + ent->think = Think_SetupAirplaneWaypoints; + + ent->use = PlaneUse; + + if ( !( ent->speed ) ) { + ent->speed = 1000; + } + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + if ( ent->spawnflags & 4 ) { + ent->s.density = 7; + } + + trap_LinkEntity( ent ); + + fploop_snd = G_SoundIndex( "sound/fighterplane/fploop.wav" ); + fpchoke_snd = G_SoundIndex( "sound/fighterplane/fpchoke.wav" ); + fpattack_snd = G_SoundIndex( "sound/weapons/mg42/37mm.wav" ); + fpexpdebris_snd = G_SoundIndex( "sound/fighterplane/fpexpdebris.wav" ); + + + fpflyby1_snd = G_SoundIndex( "sound/fighterplane/fpflyby1.wav" ); + fpflyby2_snd = G_SoundIndex( "sound/fighterplane/fpflyby2.wav" ); + fpidle_snd = G_SoundIndex( "sound/fighterplane/fpidle.wav" ); + fpstartup_snd = G_SoundIndex( "sound/fighterplane/fpstartup.wav" ); + + + fuse_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_a.md3" ); + wing_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_b.md3" ); + tail_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_c.md3" ); + nose_part = G_ModelIndex( "models/mapobjects/vehicles/m109debris_d.md3" ); + + crash_part = G_ModelIndex( "models/mapobjects/vehicles/m109crash.md3" ); + + InitPlaneSpeaker( ent ); + +} + +///////////////////////// +// TRUCK DRIVE +///////////////////////// + +/*QUAKED truck_cam (.7 .3 .1) ? START_ON TOGGLE - - +*/ +void truck_cam_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( player && player != other ) { + // G_Printf ("other: %s\n", other->aiName); + return; + } + + if ( !self->nextTrain ) { + self->touch = NULL; + return; + } + + // lock the player to the moving truck + { + vec3_t point; + + trap_UnlinkEntity( other ); + + // VectorCopy ( self->r.currentOrigin, other->client->ps.origin ); + VectorCopy( self->r.currentOrigin, point ); + point[2] = other->client->ps.origin[2]; + VectorCopy( point, other->client->ps.origin ); + + // save results of pmove + BG_PlayerStateToEntityState( &other->client->ps, &other->s, qtrue ); + + // use the precise origin for linking + VectorCopy( other->client->ps.origin, other->r.currentOrigin ); + + other->client->ps.persistant[PERS_HWEAPON_USE] = 1; + + trap_LinkEntity( other ); + } + +} + +void truck_cam_think( gentity_t *ent ) { + ent->nextthink = level.time + ( FRAMETIME / 2 ); +} + +void SP_truck_cam( gentity_t *self ) { + int mass; + + VectorClear( self->s.angles ); + + if ( !self->speed ) { + self->speed = 100; + } + + if ( !self->target ) { + G_Printf( "truck_cam without a target at %s\n", vtos( self->r.absmin ) ); + G_FreeEntity( self ); + return; + } + + trap_SetBrushModel( self, self->model ); + + if ( G_SpawnInt( "mass", "20", &mass ) ) { + self->count = mass; + } else { + self->count = 20; + } + + InitTramcar( self ); + + self->nextthink = level.time + ( FRAMETIME / 2 ); + + self->think = Think_SetupTrainTargets; + + self->touch = truck_cam_touch; + + self->s.loopSound = 0; + self->props_frame_state = 0; + + self->clipmask = CONTENTS_SOLID; + + // G_SetOrigin (self, self->s.origin); + // G_SetAngle (self, self->s.angles); + + self->reached = Reached_Tramcar; + + self->s.density = 6; + + //start_drive_grind_gears + truck_sound = G_SoundIndex( "sound/vehicles/start_drive_grind_gears_01_11k.wav" ); + // truck_sound = G_SoundIndex ( "sound/vehicles/tankmove1.wav" ); + + truck_idle_snd = G_SoundIndex( "sound/vehicles/truckidle.wav" ); + truck_gear1_snd = G_SoundIndex( "sound/vehicles/truckgear1.wav" ); + truck_gear2_snd = G_SoundIndex( "sound/vehicles/truckgear2.wav" ); + truck_gear3_snd = G_SoundIndex( "sound/vehicles/truckgear3.wav" ); + truck_reverse_snd = G_SoundIndex( "sound/vehicles/truckreverse.wav" ); + truck_moving_snd = G_SoundIndex( "sound/vehicles/truckmoving.wav" ); + truck_breaking_snd = G_SoundIndex( "sound/vehicles/truckbreaking.wav" ); + truck_bouncy1_snd = G_SoundIndex( "sound/vehicles/truckbouncy1.wav" ); + truck_bouncy2_snd = G_SoundIndex( "sound/vehicles/truckbouncy2.wav" ); + truck_bouncy3_snd = G_SoundIndex( "sound/vehicles/truckbouncy3.wav" ); +} + +///////////////////////// +// camera cam +///////////////////////// + +/*QUAKED camera_cam (.5 .7 .3) (-8 -8 -8) (8 8 8) ON TRACKING MOVING - +"track" is the targetname of the entity providing the starting direction use an info_notnull +*/ + +void delayOnthink( gentity_t *ent ) { + if ( ent->melee ) { + ent->melee->use( ent->melee, NULL, NULL ); + } +} + +void Init_Camera( gentity_t *ent ) { + vec3_t move; + float distance; + + ent->moverState = MOVER_POS1; + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + + VectorCopy( ent->pos1, ent->r.currentOrigin ); + + trap_LinkEntity( ent ); + + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.pos.trBase ); + + // calculate time to reach second position from speed + VectorSubtract( ent->pos2, ent->pos1, move ); + distance = VectorLength( move ); + if ( !ent->speed ) { + ent->speed = 100; + } + VectorScale( move, ent->speed, ent->s.pos.trDelta ); + ent->s.pos.trDuration = distance * 1000 / ent->speed; + if ( ent->s.pos.trDuration <= 0 ) { + ent->s.pos.trDuration = 1; + } +} + +void camera_cam_think( gentity_t *ent ) { + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( !player ) { + return; + } + + if ( ent->spawnflags & 2 ) { // tracking + vec3_t point; + + trap_UnlinkEntity( player ); + + // VectorCopy ( self->r.currentOrigin, other->client->ps.origin ); + VectorCopy( ent->r.currentOrigin, point ); + point[2] = player->client->ps.origin[2]; + VectorCopy( point, player->client->ps.origin ); + + // save results of pmove + BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); + + // use the precise origin for linking + VectorCopy( player->client->ps.origin, player->r.currentOrigin ); + + // tracking + { + gentity_t *target = NULL; + vec3_t dang; + vec3_t vec; + + if ( ent->track ) { + target = G_Find( NULL, FOFS( targetname ), ent->track ); + } + + if ( target ) { + VectorSubtract( target->r.currentOrigin, ent->r.currentOrigin, vec ); + vectoangles( vec, dang ); + SetClientViewAngle( player, dang ); + + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + VectorCopy( dang, ent->s.apos.trBase ); + + trap_LinkEntity( ent ); + } + } + + trap_LinkEntity( player ); + } + + ent->nextthink = level.time + ( FRAMETIME / 2 ); +} + +void camera_cam_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( !player ) { + return; + } + + if ( !( ent->spawnflags & 1 ) ) { + ent->think = camera_cam_think; + ent->nextthink = level.time + ( FRAMETIME / 2 ); + ent->spawnflags |= 1; + { + player->client->ps.persistant[PERS_HWEAPON_USE] = 1; + player->client->ps.viewlocked = 4; + player->client->ps.viewlocked_entNum = ent->s.number; + } + } else + { + ent->spawnflags &= ~1; + ent->think = NULL; + { + player->client->ps.persistant[PERS_HWEAPON_USE] = 0; + player->client->ps.viewlocked = 0; + player->client->ps.viewlocked_entNum = 0; + } + } + +} + +void camera_cam_firstthink( gentity_t *ent ) { + gentity_t *target = NULL; + vec3_t dang; + vec3_t vec; + + if ( ent->track ) { + target = G_Find( NULL, FOFS( targetname ), ent->track ); + } + + if ( target ) { + VectorSubtract( target->s.origin, ent->r.currentOrigin, vec ); + vectoangles( vec, dang ); + G_SetAngle( ent, dang ); + } + + if ( ent->target ) { + ent->nextthink = level.time + ( FRAMETIME / 2 ); + ent->think = Think_SetupTrainTargets; + } +} + +void SP_camera_cam( gentity_t *ent ) { + Init_Camera( ent ); + + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngle( ent, ent->s.angles ); + + ent->reached = Reached_Tramcar; + + ent->nextthink = level.time + ( FRAMETIME / 2 ); + + ent->think = camera_cam_firstthink; + + ent->use = camera_cam_use; + + if ( ent->spawnflags & 1 ) { // On + gentity_t *delayOn; + + delayOn = G_Spawn(); + delayOn->think = delayOnthink; + delayOn->nextthink = level.time + 1000; + delayOn->melee = ent; + trap_LinkEntity( delayOn ); + } + +} + + +/*QUAKED screen_fade (.3 .7 .9) (-8 -8 -8) (8 8 8) +"wait" duration of fade out +"delay" duration of fade in + + 1 = 1 sec + .5 = .5 sec + +defaults are .5 sec +*/ +void screen_fade_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if ( ent->spawnflags & 1 ) { + // fade out + trap_SetConfigstring( CS_SCREENFADE, va( "1 %i %i", level.time + 100, (int) ent->wait ) ); + ent->spawnflags &= ~1; + } else + { + // fade in + trap_SetConfigstring( CS_SCREENFADE, va( "0 %i %i", level.time + 100, (int) ent->delay ) ); + ent->spawnflags |= 1; + } + +} + +void SP_screen_fade( gentity_t *ent ) { + ent->use = screen_fade_use; + + if ( !ent->wait ) { + ent->wait = 500; + } + if ( !ent->delay ) { + ent->delay = 500; + } + +} + +/*QUAKED camera_reset_player (.5 .7 .3) ? +touched will record the players position and fire off its targets and or cameras + +used will reset the player back to his last position +*/ + +void mark_players_pos( gentity_t *ent, gentity_t *other, trace_t *trace ) { + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( player == other ) { + VectorCopy( player->r.currentOrigin, ent->s.origin2 ); + VectorCopy( player->r.currentAngles, ent->s.angles2 ); + + G_UseTargets( ent, NULL ); + } + +} + +void reset_players_pos( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + + gentity_t *player; + + player = AICast_FindEntityForName( "player" ); + + if ( !player ) { + return; + } + + trap_UnlinkEntity( player ); + + VectorCopy( ent->s.origin2, player->client->ps.origin ); + + // save results of pmove + BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); + + // use the precise origin for linking + VectorCopy( player->client->ps.origin, player->r.currentOrigin ); + + SetClientViewAngle( player, ent->s.angles2 ); + + player->client->ps.persistant[PERS_HWEAPON_USE] = 0; + player->client->ps.viewlocked = 0; + player->client->ps.viewlocked_entNum = 0; + + trap_LinkEntity( player ); + +} + +extern void InitTrigger( gentity_t *self ); + +void SP_camera_reset_player( gentity_t *ent ) { + InitTrigger( ent ); + + ent->r.contents = CONTENTS_TRIGGER; + + ent->touch = mark_players_pos; + ent->use = reset_players_pos; + + trap_LinkEntity( ent ); +} diff --git a/src/game/g_trigger.c b/src/game/g_trigger.c new file mode 100644 index 0000000..1f5d00e --- /dev/null +++ b/src/game/g_trigger.c @@ -0,0 +1,1024 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + + +void InitTrigger( gentity_t *self ) { + if ( !VectorCompare( self->s.angles, vec3_origin ) ) { + G_SetMovedir( self->s.angles, self->movedir ); + } + + trap_SetBrushModel( self, self->model ); + + self->r.contents = CONTENTS_TRIGGER; // replaces the -1 from trap_SetBrushModel + self->r.svFlags = SVF_NOCLIENT; +} + + +// the wait time has passed, so set back up for another activation +void multi_wait( gentity_t *ent ) { + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger( gentity_t *ent, gentity_t *activator ) { + ent->activator = activator; + if ( ent->nextthink ) { + return; // can't retrigger until the wait is over + } + + G_UseTargets( ent, ent->activator ); + + if ( ent->wait > 0 ) { + ent->think = multi_wait; + ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000; + } else { + // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = 0; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEntity; + } +} + +void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + multi_trigger( ent, activator ); +} + +void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace ) { + if ( !other->client ) { + return; + } + + if ( !( self->spawnflags & 1 ) ) { // denotes AI_Touch flag + if ( other->aiCharacter ) { + return; + } + } + multi_trigger( self, other ); +} + +void Enable_Trigger_Touch( gentity_t *ent ) { + gentity_t *targ; + gentity_t *daent; + trace_t tr; + int mask = MASK_SHOT; + int targTemp1, targTemp2; + int entTemp1, entTemp2; + vec3_t dir, forward, kvel; + float angle; + qboolean thisone = qfalse; + + + // ent->touch = Touch_Multi; + + + // find the client number that uses this entity + targ = AICast_FindEntityForName( ent->aiName ); + if ( !targ ) { + return; + } else + { + // bail if GIBFLAG and targ has been jibbed + if ( targ->health <= GIB_HEALTH && ( ent->spawnflags & 2 ) ) { + return; + } + + // need to make the ent solid since it is a trigger + + entTemp1 = ent->clipmask; + entTemp2 = ent->r.contents; + + ent->clipmask = CONTENTS_SOLID; + ent->r.contents = CONTENTS_SOLID; + + trap_LinkEntity( ent ); + + // same with targ cause targ is dead + + targTemp1 = targ->clipmask; + targTemp2 = targ->r.contents; + + targ->clipmask = CONTENTS_SOLID; + targ->r.contents = CONTENTS_SOLID; + + trap_LinkEntity( targ ); + + trap_Trace( &tr, targ->client->ps.origin, targ->r.mins, targ->r.maxs, targ->client->ps.origin, targ->s.number, mask ); + + if ( tr.startsolid ) { + daent = &g_entities[ tr.entityNum ]; + + if ( daent == ent ) { // wooo hooo + multi_trigger( ent, targ ); + thisone = qtrue; + } + } + + // ok were done set it contents back + + ent->clipmask = entTemp1; + ent->r.contents = entTemp2; + + trap_LinkEntity( ent ); + + targ->clipmask = targTemp1; + targ->r.contents = targTemp2; + + trap_LinkEntity( targ ); + + if ( ent->s.angles2[YAW] && thisone ) { + angle = ent->s.angles2[YAW]; + + VectorClear( dir ); + VectorClear( targ->client->ps.velocity ); + + dir[YAW] = angle; + AngleVectors( dir, forward, NULL, NULL ); + + VectorScale( forward, 32, kvel ); + VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); + } + } + +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? AI_Touch +"wait" : Seconds between triggerings, 0.5 default, -1 = one time only. +"random" wait variance, default is 0 +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +*/ +void SP_trigger_multiple( gentity_t *ent ) { + G_SpawnFloat( "wait", "0.5", &ent->wait ); + G_SpawnFloat( "random", "0", &ent->random ); + + if ( ent->random >= ent->wait && ent->wait >= 0 ) { + ent->random = ent->wait - FRAMETIME; + G_Printf( "trigger_multiple has random >= wait\n" ); + } + + ent->touch = Touch_Multi; + ent->use = Use_Multi; + + InitTrigger( ent ); + trap_LinkEntity( ent ); +} + + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +void trigger_always_think( gentity_t *ent ) { + G_UseTargets( ent, ent ); + G_FreeEntity( ent ); +} + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always( gentity_t *ent ) { + // we must have some delay to make sure our use targets are present + ent->nextthink = level.time + 300; + ent->think = trigger_always_think; +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +void trigger_push_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + + if ( ( self->spawnflags & 4 ) && other->r.svFlags & SVF_CASTAI ) { + return; + } + + if ( !other->client ) { + return; + } + + if ( other->client->ps.pm_type != PM_NORMAL ) { + return; + } + if ( other->client->ps.powerups[PW_FLIGHT] ) { + return; + } + +//----(SA) commented out as we have no hook +// if (other->client && other->client->hook) +// Weapon_HookFree(other->client->hook); + + if ( other->client->ps.velocity[2] < 100 ) { + // don't play the event sound again if we are in a fat trigger + G_AddPredictableEvent( other, EV_JUMP_PAD, 0 ); + } + VectorCopy( self->s.origin2, other->client->ps.velocity ); + + if ( self->spawnflags & 2 ) { + G_FreeEntity( self ); + } +} + + +/* +================= +AimAtTarget + +Calculate origin2 so the target apogee will be hit +================= +*/ +void AimAtTarget( gentity_t *self ) { + gentity_t *ent; + vec3_t origin; + float height, gravity, time, forward; + float dist; + + VectorAdd( self->r.absmin, self->r.absmax, origin ); + VectorScale( origin, 0.5, origin ); + + ent = G_PickTarget( self->target ); + if ( !ent ) { + G_FreeEntity( self ); + return; + } + + height = ent->s.origin[2] - origin[2]; + gravity = g_gravity.value; + time = sqrt( fabs( height / ( 0.5f * gravity ) ) ); + if ( !time ) { + G_FreeEntity( self ); + return; + } + + // set s.origin2 to the push velocity + VectorSubtract( ent->s.origin, origin, self->s.origin2 ); + self->s.origin2[2] = 0; + dist = VectorNormalize( self->s.origin2 ); + + forward = dist / time; + VectorScale( self->s.origin2, forward, self->s.origin2 ); + + self->s.origin2[2] = time * gravity; +} + +void trigger_push_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + self->touch = trigger_push_touch; + trap_LinkEntity( self ); +} + +/*QUAKED trigger_push (.5 .5 .5) ? TOGGLE REMOVEAFTERTOUCH PUSHPLAYERONLY +Must point at a target_position, which will be the apex of the leap. +This will be client side predicted, unlike target_push +*/ +void SP_trigger_push( gentity_t *self ) { + InitTrigger( self ); + + // unlike other triggers, we need to send this one to the client + self->r.svFlags &= ~SVF_NOCLIENT; + + // make sure the client precaches this sound + G_SoundIndex( "sound/world/jumppad.wav" ); + + if ( !( self->spawnflags & 1 ) ) { // toggle + self->s.eType = ET_PUSH_TRIGGER; + } + + self->touch = trigger_push_touch; + self->think = AimAtTarget; + + if ( self->spawnflags & 1 ) { // toggle + self->use = trigger_push_use; + self->touch = NULL; + trap_UnlinkEntity( self ); + } else { + trap_LinkEntity( self ); + } + + self->nextthink = level.time + FRAMETIME; +// trap_LinkEntity (self); +} + + +void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) { + if ( !activator->client ) { + return; + } + + if ( activator->client->ps.pm_type != PM_NORMAL ) { + return; + } + if ( activator->client->ps.powerups[PW_FLIGHT] ) { + return; + } + + VectorCopy( self->s.origin2, activator->client->ps.velocity ); + + // play fly sound every 1.5 seconds + if ( activator->fly_sound_debounce_time < level.time ) { + activator->fly_sound_debounce_time = level.time + 1500; + G_Sound( activator, self->noise_index ); + } +} + +/*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad +Pushes the activator in the direction.of angle, or towards a target apex. +"speed" defaults to 1000 +if "bouncepad", play bounce noise instead of windfly +*/ +void SP_target_push( gentity_t *self ) { + if ( !self->speed ) { + self->speed = 1000; + } + G_SetMovedir( self->s.angles, self->s.origin2 ); + VectorScale( self->s.origin2, self->speed, self->s.origin2 ); + + if ( self->spawnflags & 1 ) { + self->noise_index = G_SoundIndex( "sound/world/jumppad.wav" ); + } else { + self->noise_index = G_SoundIndex( "sound/misc/windfly.wav" ); + } + if ( self->target ) { + VectorCopy( self->s.origin, self->r.absmin ); + VectorCopy( self->s.origin, self->r.absmax ); + self->think = AimAtTarget; + self->nextthink = level.time + FRAMETIME; + } + self->use = Use_target_push; +} + +/* +============================================================================== + +trigger_teleport + +============================================================================== +*/ + +void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + gentity_t *dest; + + if ( !other->client ) { + return; + } + if ( other->client->ps.pm_type == PM_DEAD ) { + return; + } + + dest = G_PickTarget( self->target ); + if ( !dest ) { + G_Printf( "Couldn't find teleporter destination\n" ); + return; + } + + TeleportPlayer( other, dest->s.origin, dest->s.angles ); +} + + +/*QUAKED trigger_teleport (.5 .5 .5) ? +Allows client side prediction of teleportation events. +Must point at a target_position, which will be the teleport destination. +*/ +void SP_trigger_teleport( gentity_t *self ) { + InitTrigger( self ); + + // unlike other triggers, we need to send this one to the client + self->r.svFlags &= ~SVF_NOCLIENT; + + // make sure the client precaches this sound + G_SoundIndex( "sound/world/jumppad.wav" ); + + self->s.eType = ET_TELEPORT_TRIGGER; + self->touch = trigger_teleporter_touch; + + trap_LinkEntity( self ); +} + + + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW ONCE +Any entity that touches this will be hurt. +It does dmg points of damage each server frame +Targeting the trigger will toggle its on / off state. + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +"life" time this brush will exist if value is zero will live for ever ei 0.5 sec 2.sec +default is zero + +the entity must be used first before it will count down its life +*/ +void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) { + int dflags; + + if ( !other->takedamage ) { + return; + } + + if ( self->timestamp > level.time ) { + return; + } + + if ( self->spawnflags & 16 ) { + self->timestamp = level.time + 1000; + } else { + self->timestamp = level.time + FRAMETIME; + } + + // play sound + if ( !( self->spawnflags & 4 ) ) { + G_Sound( other, self->noise_index ); + } + + if ( self->spawnflags & 8 ) { + dflags = DAMAGE_NO_PROTECTION; + } else { + dflags = 0; + } + G_Damage( other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT ); + + if ( self->spawnflags & 32 ) { + self->touch = NULL; + } +} + +void hurt_think( gentity_t *ent ) { + ent->nextthink = level.time + FRAMETIME; + + if ( ent->wait < level.time ) { + G_FreeEntity( ent ); + } + +} + +void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + if ( self->touch ) { + self->touch = NULL; + } else { + self->touch = hurt_touch; + } + + if ( self->delay ) { + self->nextthink = level.time + 50; + self->think = hurt_think; + self->wait = level.time + ( self->delay * 1000 ); + } +} + +/* +============== +SP_trigger_hurt +============== +*/ +void SP_trigger_hurt( gentity_t *self ) { + + char *life, *sound; // JPW NERVE + float dalife; + + InitTrigger( self ); + + G_SpawnString( "sound", "sound/world/electro.wav", &sound ); + + self->noise_index = G_SoundIndex( sound ); + + if ( !self->damage ) { + self->damage = 5; + } + + self->r.contents = CONTENTS_TRIGGER; + +//----(SA) +// if ( self->spawnflags & 2 ) { +// self->use = hurt_use; +// } + + self->use = hurt_use; + + // link in to the world if starting active + if ( !( self->spawnflags & 1 ) ) { + //----(SA) any reason this needs to be linked? (predicted?) +// trap_LinkEntity (self); + self->touch = hurt_touch; + } + + G_SpawnString( "life", "0", &life ); + dalife = atof( life ); + self->delay = dalife; + +} + + +/* +============================================================================== + +timer + +============================================================================== +*/ + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +This should be renamed trigger_timer... +Repeatedly fires its targets. +Can be turned on or off by using. + +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +*/ +void func_timer_think( gentity_t *self ) { + G_UseTargets( self, self->activator ); + // set time before next firing + self->nextthink = level.time + 1000 * ( self->wait + crandom() * self->random ); +} + +void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + self->activator = activator; + + // if on, turn it off + if ( self->nextthink ) { + self->nextthink = 0; + return; + } + + // turn it on + func_timer_think( self ); +} + +void SP_func_timer( gentity_t *self ) { + G_SpawnFloat( "random", "1", &self->random ); + G_SpawnFloat( "wait", "1", &self->wait ); + + self->use = func_timer_use; + self->think = func_timer_think; + + if ( self->random >= self->wait ) { + self->random = self->wait - FRAMETIME; + G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) ); + } + + if ( self->spawnflags & 1 ) { + self->nextthink = level.time + FRAMETIME; + self->activator = self; + } + + self->r.svFlags = SVF_NOCLIENT; +} + + + + +//---- (SA) Wolf triggers + + +/*QUAKED trigger_once (.5 .5 .5) ? AI_Touch +Must be targeted at one or more entities. +Once triggered, this entity is destroyed +(you can actually do the same thing with trigger_multiple with a wait of -1) +*/ +void SP_trigger_once( gentity_t *ent ) { + ent->wait = -1; // this will remove itself after one use + ent->touch = Touch_Multi; + ent->use = Use_Multi; + + InitTrigger( ent ); + trap_LinkEntity( ent ); +} + +//---- end + +/*QUAKED trigger_deathCheck (.5 .5 .5) ? - GIBFLAG +GIBFLAG entity will never fire its target(s) if aiName entity was gibbed +aiName entity making alertentity call + +this entity will test if aiName is in its volume + +Must be targeted at one or more entities. +Once triggered, this entity is destroyed +*/ +void SP_trigger_deathCheck( gentity_t *ent ) { + VectorCopy( ent->s.angles, ent->s.angles2 ); + + if ( !( ent->aiName ) ) { + G_Error( "trigger_once_enabledeath does not have an aiName \n" ); + } + + ent->wait = -1; // this will remove itself after one use + ent->AIScript_AlertEntity = Enable_Trigger_Touch; + ent->use = Use_Multi; + + InitTrigger( ent ); + + trap_LinkEntity( ent ); +} + + +/*QUAKED trigger_aidoor (.5 .5 .5) ? +These entities must be placed on all doors one for each side of the door +this will enable ai's to operate the door and help in preventing ai's and +the player from getting stuck when the door is deciding which way to open +*/ + +void trigger_aidoor_stayopen( gentity_t * ent, gentity_t * other, trace_t * trace ) { + gentity_t *door; + + // FIXME: port this code over to moving doors (use MOVER_POSx instead of MOVER_POSxROTATE) + if ( other->client && other->health > 0 ) { + if ( !ent->target || !( strlen( ent->target ) ) ) { + // ent->target of "" will crash game in Q_stricmp() + + // FIXME: commented out so it can be fixed + +// G_Printf( "trigger_aidoor at loc %s does not have a target door\n", vtos (ent->s.origin) ); + return; + } + + door = G_Find( NULL, FOFS( targetname ), ent->target ); + + if ( !door ) { + // FIXME: commented out so it can be fixed +// G_Printf( "trigger_aidoor at loc %s does not have a target door\n", vtos (ent->s.origin) ); + return; + } + + if ( door->moverState == MOVER_POS2ROTATE ) { // door is in open state waiting to close keep it open + door->nextthink = level.time + door->wait + 3000; + } + + // Ridah, door isn't ready, find a free ai_marker, and wait there until it's open + if ( other->r.svFlags & SVF_CASTAI ) { + + if ( door->key ) { // we dont have keys, so assume we are not trying to get through this door + return; + } + + G_Activate( door, other ); + + // if the door isn't currently opening for us, we should move out the way + // Ridah, had to change this, since it was causing AI to wait at door when the door is open, and won't close because they are sitting on the aidoor brush + //if (!(door->activator == other && (door->moverState == MOVER_1TO2ROTATE || door->moverState == MOVER_POS2ROTATE))) { + // NOTE TTimo: SP has a slightly different test? (this is prolly outdated) + if ( + !( + ( door->activator == other ) + && ( door->moverState != MOVER_POS1 ) + && ( door->moverState != MOVER_POS1ROTATE ) + ) + && ( door->moverState != MOVER_POS2ROTATE ) + && ( door->moverState != MOVER_POS2 ) ) { + // if we aren't already heading for an ai_marker, look for one we can go to + AICast_AIDoor_Touch( other, ent, door ); + } + } + } + +} + +void SP_trigger_aidoor( gentity_t *ent ) { + if ( !ent->targetname ) { + G_Printf( "trigger_aidoor at loc %s does not have a targetname for ai_marker assignments\n", vtos( ent->s.origin ) ); + } + + ent->touch = trigger_aidoor_stayopen; + InitTrigger( ent ); + trap_LinkEntity( ent ); +} + + +void gas_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) { + gentity_t *traceEnt; + trace_t tr; + vec3_t dir; + int damage = 1; + + if ( !other->client ) { + return; + } + + if ( ent->s.density == 5 ) { + ent->touch = NULL; + damage = 5; + } + + trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, other->r.currentOrigin, ent->s.number, MASK_SHOT ); + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + traceEnt = &g_entities[ tr.entityNum ]; + + if ( traceEnt->aiSkin && strstr( traceEnt->aiSkin, "venom" ) ) { + return; + } + + if ( traceEnt->takedamage ) { + + VectorClear( dir ); + + G_Damage( traceEnt, ent, ent, dir, tr.endpos, + damage, 0, MOD_POISONGAS ); + } +} + +void gas_think( gentity_t *ent ) { + gentity_t *tent; + + ent->count++; + + if ( ent->health < ent->count ) { + ent->think = G_FreeEntity; + if ( ent->s.density == 5 ) { + ent->nextthink = level.time + FRAMETIME; + } else { + ent->nextthink = level.time + 3000; + } + return; + } + + ent->r.maxs[0] = ent->r.maxs[1] = ent->r.maxs[2]++; + ent->r.mins[0] = ent->r.mins[1] = ent->r.mins[2]--; + + ent->nextthink = level.time + FRAMETIME; + + tent = G_TempEntity( ent->r.currentOrigin, EV_SMOKE ); + VectorCopy( ent->r.currentOrigin, tent->s.origin ); + + if ( ent->s.density == 5 ) { + tent->s.time = 500; + tent->s.time2 = 100; + tent->s.density = 5; + + tent->s.angles2[0] = 8; + tent->s.angles2[1] = 32; + } else + { + tent->s.time = 5000; + tent->s.time2 = 3000; + tent->s.density = 5; + + tent->s.angles2[0] = 24; + tent->s.angles2[1] = 96; + } + + trap_LinkEntity( ent ); +} + +/*QUAKED test_gas (0 0.5 0) (-4 -4 -4) (4 4 4) +*/ +void SP_gas( gentity_t *self ) { + self->think = gas_think; + self->nextthink = level.time + FRAMETIME; + self->r.contents = CONTENTS_TRIGGER; + self->touch = gas_touch; + trap_LinkEntity( self ); + + if ( !self->health ) { + self->health = 100; + } +} + + +// DHM - Nerve :: Multiplayer triggers + +#define RED_FLAG 1 +#define BLUE_FLAG 2 + +/*QUAKED trigger_flagonly (.5 .5 .5) ? RED_FLAG BLUE_FLAG +Player must be carrying the proper flag for it to trigger. +It will call the "death" function in the object's script. + +"scriptName" The object name in the script file +"score" score given to player for dropping flag in this field + (default 20) + +RED_FLAG -- only trigger if player is carrying red flag +BLUE_FLAG -- only trigger if player is carrying blue flag +*/ + +void Touch_flagonly( gentity_t *ent, gentity_t *other, trace_t *trace ) { + + if ( !other->client ) { + return; + } + + if ( ent->spawnflags & RED_FLAG && other->client->ps.powerups[ PW_REDFLAG ] ) { + + AddScore( other, ent->accuracy ); // JPW NERVE set from map, defaults to 20 + + G_Script_ScriptEvent( ent, "death", "" ); + + // Removes itself + ent->touch = NULL; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEntity; + } else if ( ent->spawnflags & BLUE_FLAG && other->client->ps.powerups[ PW_BLUEFLAG ] ) { + + AddScore( other, ent->accuracy ); // JPW NERVE set from map, defaults to 20 + + G_Script_ScriptEvent( ent, "death", "" ); + + // Removes itself + ent->touch = NULL; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEntity; + } +} + +void SP_trigger_flagonly( gentity_t *ent ) { + char *scorestring; // JPW NERVE + ent->touch = Touch_flagonly; + + InitTrigger( ent ); + + // JPW NERVE -- if this trigger has a "score" field set, then completing an objective + // inside of this field will add "score" to the right player team. storing this + // in ent->accuracy since that's unused. + G_SpawnString( "score", "20", &scorestring ); + ent->accuracy = atof( scorestring ); + // jpw + + trap_LinkEntity( ent ); +} + +// NERVE - SMF - spawn an explosive indicator +void explosive_indicator_think( gentity_t *ent ) { + gentity_t *parent; + + parent = &g_entities[ent->r.ownerNum]; + + if ( !parent->inuse || Q_stricmp( "trigger_objective_info", parent->classname ) ) { + ent->think = G_FreeEntity; + ent->nextthink = level.time + FRAMETIME; + return; + } + + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED trigger_objective_info (.5 .5 .5) ? AXIS_OBJECTIVE ALLIED_OBJECTIVE +Players in this field will see a message saying that they are near an objective. + + "track" Mandatory, this is the text that is appended to "You are near " +*/ +#define AXIS_OBJECTIVE 1 +#define ALLIED_OBJECTIVE 2 + +void SP_trigger_objective_info( gentity_t *ent ) { + char *scorestring; + + if ( !ent->track ) { + G_Error( "'trigger_objective_info' does not have a 'track' \n" ); + } + + if ( level.numOidTriggers >= MAX_OID_TRIGGERS ) { + G_Error( "Exceeded maximum number of 'trigger_objective_info' entities\n" ); + } + + // JPW NERVE -- if this trigger has a "score" field set, then blowing up an objective + // inside of this field will add "score" to the right player team. storing this + // in ent->accuracy since that's unused. + G_SpawnString( "score", "0", &scorestring ); + ent->accuracy = atof( scorestring ); + // jpw + + // Arnout: HACK HACK - someone at nerve forgot to add the score field to sub - have to + // hardcode it cause we don't want people to download the map again + { + char mapName[MAX_QPATH]; + trap_Cvar_VariableStringBuffer( "mapname", mapName, sizeof( mapName ) ); + if ( !Q_stricmp( mapName, "mp_sub" ) && !Q_stricmp( ent->track, "the Axis Submarine" ) ) { + ent->accuracy = 15; + } + } + + trap_SetConfigstring( CS_OID_TRIGGERS + level.numOidTriggers, ent->track ); + ent->s.teamNum = level.numOidTriggers; + + level.numOidTriggers++; + InitTrigger( ent ); + + // unlike other triggers, we need to send this one to the client + ent->r.svFlags &= ~SVF_NOCLIENT; + ent->s.eType = ET_OID_TRIGGER; + + trap_LinkEntity( ent ); + + // NERVE - SMF - spawn an explosive indicator + if ( ( ent->spawnflags & AXIS_OBJECTIVE ) || ( ent->spawnflags & ALLIED_OBJECTIVE ) ) { + gentity_t *e; + e = G_Spawn(); + + e->r.svFlags = SVF_BROADCAST; + e->classname = "explosive_indicator"; + e->s.eType = ET_EXPLOSIVE_INDICATOR; + e->s.pos.trType = TR_STATIONARY; + + if ( ent->spawnflags & AXIS_OBJECTIVE ) { + e->s.teamNum = 1; + } else if ( ent->spawnflags & ALLIED_OBJECTIVE ) { + e->s.teamNum = 2; + } + + e->r.ownerNum = ent->s.number; + e->think = explosive_indicator_think; + e->nextthink = level.time + FRAMETIME; + + VectorCopy( ent->r.mins, e->s.pos.trBase ); + VectorAdd( ent->r.maxs, e->s.pos.trBase, e->s.pos.trBase ); + VectorScale( e->s.pos.trBase, 0.5, e->s.pos.trBase ); + + SnapVector( e->s.pos.trBase ); + + trap_LinkEntity( e ); + } + // -NERVE - SMF +} + + +// dhm - end + +// JPW NERVE -- field which is acted upon (cgame side) by screenshakes to drop dust particles +void trigger_concussive_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) { + return; // FIXME this should be NULLed out in SP_trigger_concussive_dust after everything works + G_Printf( "hit concussive ent %d mins=%f,%f,%f maxs=%f,%f,%f\n",ent, + ent->r.mins[0], + ent->r.mins[1], + ent->r.mins[2], + ent->r.maxs[0], + ent->r.maxs[1], + ent->r.maxs[2] ); +} + +/*QUAKED trigger_concussive_dust (.5 .5 .5) ? +Allows client side prediction of teleportation events. +Must point at a target_position, which will be the teleport destination. +*/ +void SP_trigger_concussive_dust( gentity_t *self ) { + InitTrigger( self ); + + // unlike other triggers, we need to send this one to the client +// self->r.svFlags &= ~SVF_NOCLIENT; +// self->r.svFlags |= SVF_BROADCAST; + + self->s.eType = ET_CONCUSSIVE_TRIGGER; + self->touch = trigger_concussive_touch; + + trap_LinkEntity( self ); +} +// jpw + diff --git a/src/game/g_utils.c b/src/game/g_utils.c new file mode 100644 index 0000000..b30275c --- /dev/null +++ b/src/game/g_utils.c @@ -0,0 +1,797 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: g_utils.c + * + * desc: misc utility functions for game module + * +*/ + +#include "g_local.h" + +typedef struct { + char oldShader[MAX_QPATH]; + char newShader[MAX_QPATH]; + float timeOffset; +} shaderRemap_t; + +#define MAX_SHADER_REMAPS 128 + +int remapCount = 0; +shaderRemap_t remappedShaders[MAX_SHADER_REMAPS]; + +void AddRemap( const char *oldShader, const char *newShader, float timeOffset ) { + int i; + + for ( i = 0; i < remapCount; i++ ) { + if ( Q_stricmp( oldShader, remappedShaders[i].oldShader ) == 0 ) { + // found it, just update this one + strcpy( remappedShaders[i].newShader,newShader ); + remappedShaders[i].timeOffset = timeOffset; + return; + } + } + if ( remapCount < MAX_SHADER_REMAPS ) { + strcpy( remappedShaders[remapCount].newShader,newShader ); + strcpy( remappedShaders[remapCount].oldShader,oldShader ); + remappedShaders[remapCount].timeOffset = timeOffset; + remapCount++; + } +} + +const char *BuildShaderStateConfig() { + static char buff[MAX_STRING_CHARS * 4]; + char out[( MAX_QPATH * 2 ) + 5]; + int i; + + memset( buff, 0, MAX_STRING_CHARS ); + for ( i = 0; i < remapCount; i++ ) { + Com_sprintf( out, ( MAX_QPATH * 2 ) + 5, "%s=%s:%5.2f@", remappedShaders[i].oldShader, remappedShaders[i].newShader, remappedShaders[i].timeOffset ); + Q_strcat( buff, sizeof( buff ), out ); + } + return buff; +} + +/* +========================================================================= + +model / sound configstring indexes + +========================================================================= +*/ + +/* +================ +G_FindConfigstringIndex + +================ +*/ +int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ) { + int i; + char s[MAX_STRING_CHARS]; + + if ( !name || !name[0] ) { + return 0; + } + + for ( i = 1 ; i < max ; i++ ) { + trap_GetConfigstring( start + i, s, sizeof( s ) ); + if ( !s[0] ) { + break; + } + if ( !strcmp( s, name ) ) { + return i; + } + } + + if ( !create ) { + return 0; + } + + if ( i == max ) { + G_Error( "G_FindConfigstringIndex: overflow" ); + } + + trap_SetConfigstring( start + i, name ); + + return i; +} + + +int G_ModelIndex( char *name ) { + return G_FindConfigstringIndex( name, CS_MODELS, MAX_MODELS, qtrue ); +} + +int G_SoundIndex( const char *name ) { + return G_FindConfigstringIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue ); +} + +//===================================================================== + + +/* +================ +G_TeamCommand + +Broadcasts a command to only a specific team +================ +*/ +void G_TeamCommand( team_t team, char *cmd ) { + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected == CON_CONNECTED ) { + if ( level.clients[i].sess.sessionTeam == team ) { + trap_SendServerCommand( i, va( "%s", cmd ) ); + } + } + } +} + + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +gentity_t *G_Find( gentity_t *from, int fieldofs, const char *match ) { + char *s; + + if ( !from ) { + from = g_entities; + } else { + from++; + } + + for ( ; from < &g_entities[level.num_entities] ; from++ ) + { + if ( !from->inuse ) { + continue; + } + s = *( char ** )( (byte *)from + fieldofs ); + if ( !s ) { + continue; + } + if ( !Q_stricmp( s, match ) ) { + return from; + } + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Selects a random entity from among the targets +============= +*/ +#define MAXCHOICES 32 + +gentity_t *G_PickTarget( char *targetname ) { + gentity_t *ent = NULL; + int num_choices = 0; + gentity_t *choice[MAXCHOICES]; + + if ( !targetname ) { + //G_Printf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while ( 1 ) + { + ent = G_Find( ent, FOFS( targetname ), targetname ); + if ( !ent ) { + break; + } + choice[num_choices++] = ent; + if ( num_choices == MAXCHOICES ) { + break; + } + } + + if ( !num_choices ) { + G_Printf( "G_PickTarget: target %s not found\n", targetname ); + return NULL; + } + + return choice[rand() % num_choices]; +} + + +/* +============================== +G_UseTargets + +"activator" should be set to the entity that initiated the firing. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets( gentity_t *ent, gentity_t *activator ) { + gentity_t *t; + + if ( !ent ) { + return; + } + + if ( ent->targetShaderName && ent->targetShaderNewName ) { + float f = level.time * 0.001; + AddRemap( ent->targetShaderName, ent->targetShaderNewName, f ); + trap_SetConfigstring( CS_SHADERSTATE, BuildShaderStateConfig() ); + } + + if ( !ent->target ) { + return; + } + + t = NULL; + while ( ( t = G_Find( t, FOFS( targetname ), ent->target ) ) != NULL ) { + if ( t == ent ) { + G_Printf( "WARNING: Entity used itself.\n" ); + } else { + if ( t->use ) { + //G_Printf ("ent->classname %s ent->targetname %s t->targetname %s t->s.number %d\n", ent->classname, ent->targetname, t->targetname, t->s.number); + + t->flags |= ( ent->flags & FL_KICKACTIVATE ); // (SA) If 'ent' was kicked to activate, pass this along to it's targets. + // It may become handy to put a "KICKABLE" flag in ents so that it knows whether to pass this along or not + // Right now, the only situation where it would be weird would be an invisible_user that is a 'button' near + // a rotating door that it triggers. Kick the switch and the door next to it flies open. + + t->flags |= ( ent->flags & FL_SOFTACTIVATE ); // (SA) likewise for soft activation + + if ( activator && + ( ( Q_stricmp( t->classname, "func_door" ) == 0 ) || + ( Q_stricmp( t->classname, "func_door_rotating" ) == 0 ) + ) + ) { + // check door usage rules before allowing any entity to trigger a door open + G_TryDoor( t, ent, activator ); // (door,other,activator) + } else { + t->use( t, ent, activator ); + } + } + } + if ( !ent->inuse ) { + G_Printf( "entity was removed while using targets\n" ); + return; + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +/* +float *tv( float x, float y, float z ) { + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} +*/ + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos( const vec3_t v ) { + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = ( index + 1 ) & 7; + + Com_sprintf( s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2] ); + + return s; +} +char *vtosf( const vec3_t v ) { + static int index; + static char str[8][64]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = ( index + 1 ) & 7; + + Com_sprintf( s, 64, "(%f %f %f)", v[0], v[1], v[2] ); + + return s; +} + + +/* +=============== +G_SetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void G_SetMovedir( vec3_t angles, vec3_t movedir ) { + static vec3_t VEC_UP = {0, -1, 0}; + static vec3_t MOVEDIR_UP = {0, 0, 1}; + static vec3_t VEC_DOWN = {0, -2, 0}; + static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + + if ( VectorCompare( angles, VEC_UP ) ) { + VectorCopy( MOVEDIR_UP, movedir ); + } else if ( VectorCompare( angles, VEC_DOWN ) ) { + VectorCopy( MOVEDIR_DOWN, movedir ); + } else { + AngleVectors( angles, movedir, NULL, NULL ); + } + VectorClear( angles ); +} + + + +void G_InitGentity( gentity_t *e ) { + e->inuse = qtrue; + e->classname = "noclass"; + e->s.number = e - g_entities; + e->r.ownerNum = ENTITYNUM_NONE; + e->headshotDamageScale = 1.0; // RF, default value + + // RF, init scripting + e->scriptStatus.scriptEventIndex = -1; +} + +/* +================= +G_Spawn + +Either finds a free entity, or allocates a new one. + + The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will +never be used by anything else. + +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. +================= +*/ +gentity_t *G_Spawn( void ) { + int i, force; + gentity_t *e; + + e = NULL; // shut up warning + i = 0; // shut up warning + for ( force = 0 ; force < 2 ; force++ ) { + // if we go through all entities and can't find one to free, + // override the normal minimum times before use + e = &g_entities[MAX_CLIENTS]; + for ( i = MAX_CLIENTS ; i < level.num_entities ; i++, e++ ) { + if ( e->inuse ) { + continue; + } + + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if ( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 ) { + continue; + } + + // reuse this slot + G_InitGentity( e ); + return e; + } + if ( i != ENTITYNUM_MAX_NORMAL ) { + break; + } + } + if ( i == ENTITYNUM_MAX_NORMAL ) { + for ( i = 0; i < MAX_GENTITIES; i++ ) { + G_Printf( "%4i: %s\n", i, g_entities[i].classname ); + } + G_Error( "G_Spawn: no free entities" ); + } + + // open up a new slot + level.num_entities++; + + // let the server system know that there are more entities + trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), + &level.clients[0].ps, sizeof( level.clients[0] ) ); + + G_InitGentity( e ); + return e; +} + +/* +================= +G_EntitiesFree +================= +*/ +qboolean G_EntitiesFree( void ) { + int i; + gentity_t *e; + + e = &g_entities[MAX_CLIENTS]; + for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++ ) { + if ( e->inuse ) { + continue; + } + // slot available + return qtrue; + } + return qfalse; +} + + +/* +================= +G_FreeEntity + +Marks the entity as free +================= +*/ +void G_FreeEntity( gentity_t *ed ) { + trap_UnlinkEntity( ed ); // unlink from world + + if ( ed->neverFree ) { + return; + } + + memset( ed, 0, sizeof( *ed ) ); + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = qfalse; +} + +/* +================= +G_TempEntity + +Spawns an event entity that will be auto-removed +The origin will be snapped to save net bandwidth, so care +must be taken if the origin is right on a surface (snap towards start vector first) +================= +*/ +gentity_t *G_TempEntity( vec3_t origin, int event ) { + gentity_t *e; + vec3_t snapped; + + e = G_Spawn(); + e->s.eType = ET_EVENTS + event; + + e->classname = "tempEntity"; + e->eventTime = level.time; + e->r.eventTime = level.time; + e->freeAfterEvent = qtrue; + + VectorCopy( origin, snapped ); + SnapVector( snapped ); // save network bandwidth + G_SetOrigin( e, snapped ); + + // find cluster for PVS + trap_LinkEntity( e ); + + return e; +} + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +G_KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +void G_KillBox( gentity_t *ent ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for ( i = 0 ; i < num ; i++ ) { + hit = &g_entities[touch[i]]; + if ( !hit->client ) { + continue; + } + if ( !hit->r.linked ) { // RF, inactive AI shouldn't be gibbed + continue; + } + + // nail it + G_Damage( hit, ent, ent, NULL, NULL, + 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG ); + } + +} + +//============================================================================== + +/* +=============== +G_AddPredictableEvent + +Use for non-pmove events that would also be predicted on the +client side: jumppads and item pickups +Adds an event+parm and twiddles the event counter +=============== +*/ +void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) { + if ( !ent->client ) { + return; + } + BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps ); +} + + +/* +=============== +G_AddEvent + +Adds an event+parm and twiddles the event counter +=============== +*/ +void G_AddEvent( gentity_t *ent, int event, int eventParm ) { +// int bits; + + if ( !event ) { + G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number ); + return; + } + + // Ridah, use the sequential event list + if ( ent->client ) { + // NERVE - SMF - commented in - externalEvents not being handled properly in Wolf right now + ent->client->ps.events[ent->client->ps.eventSequence & ( MAX_EVENTS - 1 )] = event; + ent->client->ps.eventParms[ent->client->ps.eventSequence & ( MAX_EVENTS - 1 )] = eventParm; + ent->client->ps.eventSequence++; + // -NERVE - SMF + + // NERVE - SMF - commented out +// bits = ent->client->ps.externalEvent & EV_EVENT_BITS; +// bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS; +// ent->client->ps.externalEvent = event | bits; +// ent->client->ps.externalEventParm = eventParm; +// ent->client->ps.externalEventTime = level.time; + // -NERVE - SMF + } else { + // NERVE - SMF - commented in - externalEvents not being handled properly in Wolf right now + ent->s.events[ent->s.eventSequence & ( MAX_EVENTS - 1 )] = event; + ent->s.eventParms[ent->s.eventSequence & ( MAX_EVENTS - 1 )] = eventParm; + ent->s.eventSequence++; + // -NERVE - SMF + + // NERVE - SMF - commented out +// bits = ent->s.event & EV_EVENT_BITS; +// bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS; +// ent->s.event = event | bits; +// ent->s.eventParm = eventParm; + // -NERVE - SMF + } + ent->eventTime = level.time; + ent->r.eventTime = level.time; +} + + +/* +============= +G_Sound + + Ridah, removed channel parm, since it wasn't used, and could cause confusion +============= +*/ +void G_Sound( gentity_t *ent, int soundIndex ) { + gentity_t *te; + + te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND ); + te->s.eventParm = soundIndex; +} + +/* +============= +G_AnimScriptSound +============= +*/ +void G_AnimScriptSound( int soundIndex, vec3_t org, int client ) { + gentity_t *e; + e = &g_entities[client]; + G_AddEvent( e, EV_GENERAL_SOUND, soundIndex ); + AICast_RecordScriptSound( client ); +} + +//============================================================================== + + +/* +================ +G_SetOrigin + +Sets the pos trajectory for a fixed position +================ +*/ +void G_SetOrigin( gentity_t *ent, vec3_t origin ) { + VectorCopy( origin, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + ent->s.pos.trTime = 0; + ent->s.pos.trDuration = 0; + VectorClear( ent->s.pos.trDelta ); + + VectorCopy( origin, ent->r.currentOrigin ); +} + + +/* +============== +G_SetOrigin +============== +*/ +void G_SetAngle( gentity_t *ent, vec3_t angle ) { + + VectorCopy( angle, ent->s.apos.trBase ); + ent->s.apos.trType = TR_STATIONARY; + ent->s.apos.trTime = 0; + ent->s.apos.trDuration = 0; + VectorClear( ent->s.apos.trDelta ); + + VectorCopy( angle, ent->r.currentAngles ); + +// VectorCopy (ent->s.angles, ent->s.apos.trDelta ); + +} + +/* +==================== +infront +==================== +*/ + +qboolean infront( gentity_t *self, gentity_t *other ) { + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors( self->s.angles, forward, NULL, NULL ); + VectorSubtract( other->r.currentOrigin, self->r.currentOrigin, vec ); + VectorNormalize( vec ); + dot = DotProduct( vec, forward ); + // G_Printf( "other %5.2f\n", dot); + if ( dot > 0.0 ) { + return qtrue; + } + return qfalse; +} + +//RF, tag connections +/* +================== +G_ProcessTagConnect +================== +*/ +void G_ProcessTagConnect( gentity_t *ent ) { + if ( !ent->tagName ) { + G_Error( "G_ProcessTagConnect: NULL ent->tagName\n" ); + } + if ( !ent->tagParent ) { + G_Error( "G_ProcessTagConnect: NULL ent->tagParent\n" ); + } + G_FindConfigstringIndex( va( "%i %i %s", ent->s.number, ent->tagParent->s.number, ent->tagName ), CS_TAGCONNECTS, MAX_TAGCONNECTS, qtrue ); + ent->s.eFlags |= EF_TAGCONNECT; + + // clear out the angles so it always starts out facing the tag direction + VectorClear( ent->s.angles ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + ent->s.apos.trTime = level.time; + ent->s.apos.trDuration = 0; + ent->s.apos.trType = TR_STATIONARY; + VectorClear( ent->s.apos.trDelta ); + VectorClear( ent->r.currentAngles ); +} + +/* +================ +DebugLine + + debug polygons only work when running a local game + with r_debugSurface set to 2 +================ +*/ +int DebugLine( vec3_t start, vec3_t end, int color ) { + vec3_t points[4], dir, cross, up = {0, 0, 1}; + float dot; + + VectorCopy( start, points[0] ); + VectorCopy( start, points[1] ); + //points[1][2] -= 2; + VectorCopy( end, points[2] ); + //points[2][2] -= 2; + VectorCopy( end, points[3] ); + + + VectorSubtract( end, start, dir ); + VectorNormalize( dir ); + dot = DotProduct( dir, up ); + if ( dot > 0.99 || dot < -0.99 ) { + VectorSet( cross, 1, 0, 0 ); + } else { CrossProduct( dir, up, cross );} + + VectorNormalize( cross ); + + VectorMA( points[0], 2, cross, points[0] ); + VectorMA( points[1], -2, cross, points[1] ); + VectorMA( points[2], -2, cross, points[2] ); + VectorMA( points[3], 2, cross, points[3] ); + + return trap_DebugPolygonCreate( color, 4, points ); +} diff --git a/src/game/g_vehicles.c b/src/game/g_vehicles.c new file mode 100644 index 0000000..0db9f3b --- /dev/null +++ b/src/game/g_vehicles.c @@ -0,0 +1,31 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "g_local.h" + +// removed 3-2-2001 diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c new file mode 100644 index 0000000..1e4f411 --- /dev/null +++ b/src/game/g_weapon.c @@ -0,0 +1,2477 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: g_weapon.c + * + * desc: perform the server side effects of a weapon firing + * +*/ + + +#include "g_local.h" + +static float s_quadFactor; +static vec3_t forward, right, up; +static vec3_t muzzleEffect; +static vec3_t muzzleTrace; + + +// forward dec +void Bullet_Fire( gentity_t *ent, float spread, int damage ); +void Bullet_Fire_Extended( gentity_t *source, gentity_t *attacker, vec3_t start, vec3_t end, float spread, int damage ); + +int G_GetWeaponDamage( int weapon ); // JPW + +#define NUM_NAILSHOTS 10 + +/* +====================================================================== + +KNIFE/GAUNTLET (NOTE: gauntlet is now the Zombie melee) + +====================================================================== +*/ + +#define KNIFE_DIST 48 + +/* +============== +Weapon_Knife +============== +*/ +void Weapon_Knife( gentity_t *ent ) { + trace_t tr; + gentity_t *traceEnt, *tent; + int damage, mod; + vec3_t pforward, eforward; + + vec3_t end; + + if ( ent->s.weapon == WP_KNIFE2 ) { + mod = MOD_KNIFE2; + } else { + mod = MOD_KNIFE; + } + + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, ent->s.weapon, forward, right, up, muzzleTrace ); + VectorMA( muzzleTrace, KNIFE_DIST, forward, end ); + trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + // no contact + if ( tr.fraction == 1.0f ) { + return; + } + + if ( tr.entityNum >= MAX_CLIENTS ) { // world brush or non-player entity (no blood) + tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS ); + } else { // other player + tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); + } + + tent->s.otherEntityNum = tr.entityNum; + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + + if ( tr.entityNum == ENTITYNUM_WORLD ) { // don't worry about doing any damage + return; + } + + traceEnt = &g_entities[ tr.entityNum ]; + + if ( !( traceEnt->takedamage ) ) { + return; + } + + damage = G_GetWeaponDamage( ent->s.weapon ); // JPW // default knife damage for frontal attacks + + if ( traceEnt->client ) { + AngleVectors( ent->client->ps.viewangles, pforward, NULL, NULL ); + AngleVectors( traceEnt->client->ps.viewangles, eforward, NULL, NULL ); + + // (SA) TODO: neutralize pitch (so only yaw is considered) + if ( DotProduct( eforward, pforward ) > 0.9f ) { // from behind + + // if relaxed, the strike is almost assured a kill + // if not relaxed, but still from behind, it does 10x damage (50) + +// (SA) commented out right now as the ai's state always checks here as 'combat' + +// if(ent->s.aiState == AISTATE_RELAXED) { + damage = 100; // enough to drop a 'normal' (100 health) human with one jab + mod = MOD_KNIFE_STEALTH; +// } else { +// damage *= 10; +// } +//----(SA) end + } + } + + G_Damage( traceEnt, ent, ent, vec3_origin, tr.endpos, ( damage + rand() % 5 ) * s_quadFactor, 0, mod ); +} + +// JPW NERVE + +void MagicSink( gentity_t *self ) { + + self->clipmask = 0; + self->r.contents = 0; + + if ( self->timestamp < level.time ) { + self->think = G_FreeEntity; + self->nextthink = level.time + FRAMETIME; + return; + } + + self->s.pos.trBase[2] -= 0.5f; + self->nextthink = level.time + 50; +} + +/* +====================== + Weapon_Class_Special + class-specific in multiplayer +====================== +*/ +// JPW NERVE +void Weapon_Medic( gentity_t *ent ) { + gitem_t *item; + gentity_t *ent2; + vec3_t velocity, org, offset; + vec3_t angles,mins,maxs; + trace_t tr; + + // TTimo unused +// int mod = MOD_KNIFE; + + + if ( level.time - ent->client->ps.classWeaponTime >= g_medicChargeTime.integer * 0.25f ) { + if ( level.time - ent->client->ps.classWeaponTime > g_medicChargeTime.integer ) { + ent->client->ps.classWeaponTime = level.time - g_medicChargeTime.integer; + } + ent->client->ps.classWeaponTime += g_medicChargeTime.integer * 0.25; +// ent->client->ps.classWeaponTime = level.time; +// if (ent->client->ps.classWeaponTime > level.time) +// ent->client->ps.classWeaponTime = level.time; + item = BG_FindItem( "Med Health" ); + VectorCopy( ent->client->ps.viewangles, angles ); + + // clamp pitch + if ( angles[PITCH] < -30 ) { + angles[PITCH] = -30; + } else if ( angles[PITCH] > 30 ) { + angles[PITCH] = 30; + } + + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 64, offset ); + offset[2] += ent->client->ps.viewheight / 2; + VectorScale( velocity, 75, velocity ); + velocity[2] += 50 + crandom() * 25; + + VectorAdd( ent->client->ps.origin,offset,org ); + + VectorSet( mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); + VectorSet( maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS ); + + trap_Trace( &tr, ent->client->ps.origin, mins, maxs, org, ent->s.number, MASK_SOLID ); + VectorCopy( tr.endpos, org ); + + ent2 = LaunchItem( item, org, velocity, ent->s.number ); + ent2->think = MagicSink; + ent2->timestamp = level.time + 31200; + ent2->parent = ent; // JPW NERVE so we can score properly later + } +} +char testid1[] = "jfne"; // hash tables: don't touch +char testid2[] = "otyokg"; +char testid3[] = "jfgui"; +// jpw + +// JPW NERVE +/* +================== +Weapon_MagicAmmo +================== +*/ +void Weapon_MagicAmmo( gentity_t *ent ) { + gitem_t *item; + gentity_t *ent2; + vec3_t velocity, org, offset; + vec3_t angles,mins,maxs; + trace_t tr; + + // TTimo unused +// int mod = MOD_KNIFE; + + + if ( level.time - ent->client->ps.classWeaponTime >= g_LTChargeTime.integer * 0.25f ) { + if ( level.time - ent->client->ps.classWeaponTime > g_LTChargeTime.integer ) { + ent->client->ps.classWeaponTime = level.time - g_LTChargeTime.integer; + } + ent->client->ps.classWeaponTime += g_LTChargeTime.integer * 0.25; +// ent->client->ps.classWeaponTime = level.time; +// if (ent->client->ps.classWeaponTime > level.time) +// ent->client->ps.classWeaponTime = level.time; + item = BG_FindItem( "Ammo Pack" ); + VectorCopy( ent->client->ps.viewangles, angles ); + + // clamp pitch + if ( angles[PITCH] < -30 ) { + angles[PITCH] = -30; + } else if ( angles[PITCH] > 30 ) { + angles[PITCH] = 30; + } + + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 64, offset ); + offset[2] += ent->client->ps.viewheight / 2; + VectorScale( velocity, 75, velocity ); + velocity[2] += 50 + crandom() * 25; + + VectorAdd( ent->client->ps.origin,offset,org ); + + VectorSet( mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 ); + VectorSet( maxs, ITEM_RADIUS, ITEM_RADIUS, 2 * ITEM_RADIUS ); + + trap_Trace( &tr, ent->client->ps.origin, mins, maxs, org, ent->s.number, MASK_SOLID ); + VectorCopy( tr.endpos, org ); + + ent2 = LaunchItem( item, org, velocity, ent->s.number ); + ent2->think = MagicSink; + ent2->timestamp = level.time + 31200; + ent2->parent = ent; + } +} +// jpw + +// JPW NERVE Weapon_Syringe: +/* +====================== + Weapon_Syringe + shoot the syringe, do the old lazarus bit +====================== +*/ +void Weapon_Syringe( gentity_t *ent ) { + vec3_t end,org; + trace_t tr; + int healamt, headshot, oldweapon,oldweaponstate,oldclasstime = 0; + qboolean usedSyringe = qfalse; // DHM - Nerve + int ammo[MAX_WEAPONS]; // JPW NERVE total amount of ammo + int ammoclip[MAX_WEAPONS]; // JPW NERVE ammo in clip + int weapons[MAX_WEAPONS / ( sizeof( int ) * 8 )]; // JPW NERVE 64 bits for weapons held + gentity_t *traceEnt, *te; + + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePointForActivate( ent, forward, right, up, muzzleTrace ); + VectorMA( muzzleTrace, 48, forward, end ); // CH_ACTIVATE_DIST + //VectorMA (muzzleTrace, -16, forward, muzzleTrace); // DHM - Back up the start point in case medic is + // right on top of intended revivee. + trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); + + if ( tr.startsolid ) { + VectorMA( muzzleTrace, 8, forward, end ); // CH_ACTIVATE_DIST + trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); + } + + if ( tr.fraction < 1.0 ) { + traceEnt = &g_entities[ tr.entityNum ]; + if ( traceEnt->client != NULL ) { + + if ( ( traceEnt->client->ps.pm_type == PM_DEAD ) && ( traceEnt->client->sess.sessionTeam == ent->client->sess.sessionTeam ) ) { + + // heal the dude + // copy some stuff out that we'll wanna restore + VectorCopy( traceEnt->client->ps.origin, org ); + headshot = traceEnt->client->ps.eFlags & EF_HEADSHOT; + healamt = traceEnt->client->ps.stats[STAT_MAX_HEALTH] * 0.5; + oldweapon = traceEnt->client->ps.weapon; + oldweaponstate = traceEnt->client->ps.weaponstate; + + // keep class special weapon time to keep them from exploiting revives + oldclasstime = traceEnt->client->ps.classWeaponTime; + + memcpy( ammo,traceEnt->client->ps.ammo,sizeof( int ) * MAX_WEAPONS ); + memcpy( ammoclip,traceEnt->client->ps.ammoclip,sizeof( int ) * MAX_WEAPONS ); + memcpy( weapons,traceEnt->client->ps.weapons,sizeof( int ) * ( MAX_WEAPONS / ( sizeof( int ) * 8 ) ) ); + + ClientSpawn( traceEnt, qtrue ); + + memcpy( traceEnt->client->ps.ammo,ammo,sizeof( int ) * MAX_WEAPONS ); + memcpy( traceEnt->client->ps.ammoclip,ammoclip,sizeof( int ) * MAX_WEAPONS ); + memcpy( traceEnt->client->ps.weapons,weapons,sizeof( int ) * ( MAX_WEAPONS / ( sizeof( int ) * 8 ) ) ); + + if ( headshot ) { + traceEnt->client->ps.eFlags |= EF_HEADSHOT; + } + traceEnt->client->ps.weapon = oldweapon; + traceEnt->client->ps.weaponstate = oldweaponstate; + + traceEnt->client->ps.classWeaponTime = oldclasstime; + + traceEnt->health = healamt; + VectorCopy( org,traceEnt->s.origin ); + VectorCopy( org,traceEnt->r.currentOrigin ); + VectorCopy( org,traceEnt->client->ps.origin ); + + trap_Trace( &tr, traceEnt->client->ps.origin, traceEnt->client->ps.mins, traceEnt->client->ps.maxs, traceEnt->client->ps.origin, traceEnt->s.number, MASK_PLAYERSOLID ); + if ( tr.allsolid ) { + traceEnt->client->ps.pm_flags |= PMF_DUCKED; + } + + traceEnt->s.effect3Time = level.time; + traceEnt->r.contents = CONTENTS_CORPSE; + trap_LinkEntity( ent ); + + // DHM - Nerve :: Let the person being revived know about it + trap_SendServerCommand( traceEnt - g_entities, va( "cp \"You have been revived by [lof]%s!\n\"", ent->client->pers.netname ) ); + traceEnt->props_frame_state = ent->s.number; + + // DHM - Nerve :: Mark that the medicine was indeed dispensed + usedSyringe = qtrue; + + // sound + te = G_TempEntity( traceEnt->r.currentOrigin, EV_GENERAL_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/vo_revive.wav" ); + + // DHM - Nerve :: Play revive animation + + // Xian -- This was gay and I always hated it. + if ( g_fastres.integer > 0 ) { + BG_AnimScriptEvent( &traceEnt->client->ps, ANIM_ET_JUMP, qfalse, qtrue ); + } else { + BG_AnimScriptEvent( &traceEnt->client->ps, ANIM_ET_REVIVE, qfalse, qtrue ); + traceEnt->client->ps.pm_flags |= PMF_TIME_LOCKPLAYER; + traceEnt->client->ps.pm_time = 2100; + } + + + AddScore( ent, WOLF_MEDIC_BONUS ); // JPW NERVE props to the medic for the swift and dexterous bit o healitude + } + } + } + + // DHM - Nerve :: If the medicine wasn't used, give back the ammo + if ( !usedSyringe ) { + ent->client->ps.ammoclip[BG_FindClipForWeapon( WP_MEDIC_SYRINGE )] += 1; + } +} +// jpw + +void G_ExplodeMissile( gentity_t *ent ); +// DHM - Nerve +void Weapon_Engineer( gentity_t *ent ) { + trace_t tr; + gentity_t *traceEnt, *hit, *te; + // TTimo unused +// int mod = MOD_KNIFE; + vec3_t mins, maxs; // JPW NERVE + static vec3_t range = { 40, 40, 52 }; // JPW NERVE + int i,num,touch[MAX_GENTITIES],scored = 0; // JPW NERVE + int dynamiteDropTeam; + vec3_t end; + vec3_t origin; + + // DHM - Nerve :: Can't heal an MG42 if you're using one! + if ( ent->client->ps.persistant[PERS_HWEAPON_USE] ) { + return; + } + + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + VectorCopy( ent->client->ps.origin, muzzleTrace ); + muzzleTrace[2] += ent->client->ps.viewheight; + + VectorMA( muzzleTrace, 64, forward, end ); // CH_BREAKABLE_DIST + trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT | CONTENTS_TRIGGER ); + + if ( tr.entityNum < MAX_CLIENTS ) { + trap_UnlinkEntity( ent ); + traceEnt = &g_entities[ tr.entityNum ]; + trap_Trace( &tr, muzzleTrace, NULL, NULL, end, traceEnt->s.number, MASK_SHOT | CONTENTS_TRIGGER ); + trap_LinkEntity( ent ); + } + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + // no contact + if ( tr.fraction == 1.0f ) { + return; + } + + if ( tr.entityNum == ENTITYNUM_NONE || tr.entityNum == ENTITYNUM_WORLD ) { + return; + } + + traceEnt = &g_entities[ tr.entityNum ]; + if ( !traceEnt->takedamage && !Q_stricmp( traceEnt->classname, "misc_mg42" ) ) { + // "Ammo" for this weapon is time based + if ( ent->client->ps.classWeaponTime + g_engineerChargeTime.integer < level.time ) { + ent->client->ps.classWeaponTime = level.time - g_engineerChargeTime.integer; + } + ent->client->ps.classWeaponTime += 150; + + if ( ent->client->ps.classWeaponTime > level.time ) { + ent->client->ps.classWeaponTime = level.time; + return; // Out of "ammo" + } + + if ( traceEnt->health >= 255 ) { + traceEnt->s.frame = 0; + + if ( traceEnt->mg42BaseEnt > 0 ) { + g_entities[ traceEnt->mg42BaseEnt ].health = MG42_MULTIPLAYER_HEALTH; + g_entities[ traceEnt->mg42BaseEnt ].takedamage = qtrue; + traceEnt->health = 0; + } else { + traceEnt->health = MG42_MULTIPLAYER_HEALTH; + } + + AddScore( ent, WOLF_REPAIR_BONUS ); // JPW NERVE props to the E for the fixin' + + traceEnt->takedamage = qtrue; + traceEnt->s.eFlags &= ~EF_SMOKING; + + trap_SendServerCommand( ent - g_entities, "cp \"You have repaired the MG42!\n\"" ); +// JPW NERVE sound effect to go with fixing MG42 + G_AddEvent( ent, EV_MG42_FIXED, 0 ); +// jpw + } else { + traceEnt->health += 3; + } + } else { + trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + //no contact + if ( tr.fraction == 1.0f ) { + return; + } + if ( tr.entityNum == ENTITYNUM_NONE || tr.entityNum == ENTITYNUM_WORLD ) { + return; + } + traceEnt = &g_entities[ tr.entityNum ]; + + if ( traceEnt->methodOfDeath == MOD_DYNAMITE ) { + + // Not armed + if ( traceEnt->s.teamNum >= 4 ) { + + // Opposing team cannot accidentally arm it + if ( ( traceEnt->s.teamNum - 4 ) != ent->client->sess.sessionTeam ) { + return; + } + + trap_SendServerCommand( ent - g_entities, "cp \"Arming dynamite...\" 1" ); + + // Give health until it is full, don't continue + traceEnt->health += 7; + if ( traceEnt->health >= 250 ) { + traceEnt->health = 255; + } else { + return; + } + + // Don't allow disarming for sec (so guy that WAS arming doesn't start disarming it! + traceEnt->timestamp = level.time + 1000; + traceEnt->health = 5; + + // set teamnum so we can check it for drop/defuse exploit + traceEnt->s.teamNum = ent->client->sess.sessionTeam; + // For dynamic light pulsing + traceEnt->s.effect1Time = level.time; + + // ARM IT! + trap_SendServerCommand( ent - g_entities, "cp \"Dynamite is now armed with a 30 second timer!\" 1" ); + traceEnt->nextthink = level.time + 30000; + traceEnt->think = G_ExplodeMissile; + + // check if player is in trigger objective field + // NERVE - SMF - made this the actual bounding box of dynamite instead of range, also must snap origin to line up properly + VectorCopy( traceEnt->r.currentOrigin, origin ); + SnapVector( origin ); + VectorAdd( origin, traceEnt->r.mins, mins ); + VectorAdd( origin, traceEnt->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + VectorAdd( origin, traceEnt->r.mins, mins ); + VectorAdd( origin, traceEnt->r.maxs, maxs ); + + for ( i = 0 ; i < num ; i++ ) { + hit = &g_entities[touch[i]]; + + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + if ( !strcmp( hit->classname,"trigger_objective_info" ) ) { + + if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) { + continue; + } + + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); +// JPW NERVE + // TTimo gcc: suggest explicit braces to avoid ambiguous `else' + if ( ent->client != NULL ) { + if ( ( ent->client->sess.sessionTeam == TEAM_BLUE ) && ( hit->spawnflags & AXIS_OBJECTIVE ) ) { + te->s.eventParm = G_SoundIndex( "sound/multiplayer/allies/a-dynamite_planted.wav" ); + } else if ( ( ent->client->sess.sessionTeam == TEAM_RED ) && ( hit->spawnflags & ALLIED_OBJECTIVE ) ) { // redundant but added for code clarity + te->s.eventParm = G_SoundIndex( "sound/multiplayer/axis/g-dynamite_planted.wav" ); + } + } + + if ( hit->spawnflags & AXIS_OBJECTIVE ) { + te->s.teamNum = TEAM_RED; + if ( ent->client->sess.sessionTeam == TEAM_BLUE ) { // transfer score info if this is a bomb scoring objective + traceEnt->accuracy = hit->accuracy; + } + } else if ( hit->spawnflags & ALLIED_OBJECTIVE ) { + te->s.teamNum = TEAM_BLUE; + if ( ent->client->sess.sessionTeam == TEAM_RED ) { // ditto other team + traceEnt->accuracy = hit->accuracy; + } + } + te->r.svFlags |= SVF_BROADCAST; + + if ( ( ( hit->spawnflags & AXIS_OBJECTIVE ) && ( ent->client->sess.sessionTeam == TEAM_BLUE ) ) || + ( ( hit->spawnflags & ALLIED_OBJECTIVE ) && ( ent->client->sess.sessionTeam == TEAM_RED ) ) ) { + if ( hit->track ) { + trap_SendServerCommand( -1, va( "cp \"%s\" 1", va( "Dynamite planted near %s!", hit->track ) ) ); + } else { + trap_SendServerCommand( -1, va( "cp \"%s\" 1", va( "Dynamite planted near objective #%d!", hit->count ) ) ); + } + } + i = num; + + if ( ( !( hit->spawnflags & OBJECTIVE_DESTROYED ) ) && + te->s.teamNum && ( te->s.teamNum != ent->client->sess.sessionTeam ) ) { + AddScore( traceEnt->parent, WOLF_DYNAMITE_PLANT ); // give drop score to guy who dropped it + traceEnt->parent = ent; // give explode score to guy who armed it +// jpw pulled hit->spawnflags |= OBJECTIVE_DESTROYED; // this is pretty kludgy but we can't test it in explode fn + } +// jpw + } + } + } else { + if ( traceEnt->timestamp > level.time ) { + return; + } + if ( traceEnt->health >= 248 ) { // have to do this so we don't score multiple times + return; + } + dynamiteDropTeam = traceEnt->s.teamNum; // set this here since we wack traceent later but want teamnum for scoring + + traceEnt->health += 3; + + trap_SendServerCommand( ent - g_entities, "cp \"Defusing dynamite...\" 1" ); + + if ( traceEnt->health >= 248 ) { + traceEnt->health = 255; + // Need some kind of event/announcement here + + Add_Ammo( ent, WP_DYNAMITE, 1, qtrue ); + + traceEnt->think = G_FreeEntity; + traceEnt->nextthink = level.time + FRAMETIME; + // JPW NERVE -- more swipeage -- check if player is in trigger objective field + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + // don't report if not disarming *enemy* dynamite in field + if ( dynamiteDropTeam == ent->client->sess.sessionTeam ) { + return; + } + + for ( i = 0 ; i < num ; i++ ) { + hit = &g_entities[touch[i]]; + + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + if ( !strcmp( hit->classname,"trigger_objective_info" ) ) { + + if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) { + continue; + } + + traceEnt = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND ); + traceEnt->r.svFlags |= SVF_BROADCAST; + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + if ( ( hit->spawnflags & AXIS_OBJECTIVE ) && ( !scored ) ) { + AddScore( ent,WOLF_DYNAMITE_DIFFUSE ); // FIXME add team info to *dynamite* so we don't get points for diffusing own team dynamite + scored++; + hit->spawnflags &= ~OBJECTIVE_DESTROYED; // "re-activate" objective since it wasn't destroyed. kludgy, I know; see G_ExplodeMissile for the other half + } + trap_SendServerCommand( -1, "cp \"Axis engineer disarmed the Dynamite!\n\"" ); + traceEnt->s.eventParm = G_SoundIndex( "sound/multiplayer/axis/g-dynamite_defused.wav" ); + traceEnt->s.teamNum = TEAM_RED; + } else { // TEAM_BLUE + if ( ( hit->spawnflags & ALLIED_OBJECTIVE ) && ( !scored ) ) { + AddScore( ent,WOLF_DYNAMITE_DIFFUSE ); + scored++; + hit->spawnflags &= ~OBJECTIVE_DESTROYED; // "re-activate" objective since it wasn't destroyed + } + trap_SendServerCommand( -1, "cp \"Allied engineer disarmed the Dynamite!\n\"" ); + traceEnt->s.eventParm = G_SoundIndex( "sound/multiplayer/allies/a-dynamite_defused.wav" ); + traceEnt->s.teamNum = TEAM_BLUE; + } + } + } + } + // jpw + } + } + } +} + + +// JPW NERVE -- launch airstrike as line of bombs mostly-perpendicular to line of grenade travel +// (close air support should *always* drop parallel to friendly lines, tho accidents do happen) +extern void G_ExplodeMissile( gentity_t *ent ); + +void G_AirStrikeExplode( gentity_t *self ) { + + self->r.svFlags &= ~SVF_NOCLIENT; + self->r.svFlags |= SVF_BROADCAST; + + self->think = G_ExplodeMissile; + self->nextthink = level.time + 50; +} + +#define NUMBOMBS 10 +#define BOMBSPREAD 150 +extern void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, qboolean localize ); +void weapon_callAirStrike( gentity_t *ent ) { + int i; + vec3_t bombaxis, lookaxis, pos, bomboffset, fallaxis, temp; + gentity_t *bomb,*te; + trace_t tr; + float traceheight, bottomtraceheight; + + VectorCopy( ent->s.pos.trBase,bomboffset ); + bomboffset[2] += 4096; + + // cancel the airstrike if FF off and player joined spec + if ( !g_friendlyFire.integer && ent->parent->client && ent->parent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + ent->splashDamage = 0; // no damage + ent->think = G_ExplodeMissile; + ent->nextthink = level.time + crandom() * 50; + return; // do nothing, don't hurt anyone + } + + // turn off smoke grenade + ent->think = G_ExplodeMissile; + ent->nextthink = level.time + 950 + NUMBOMBS * 100 + crandom() * 50; // 3000 offset is for aircraft flyby + + trap_Trace( &tr, ent->s.pos.trBase, NULL, NULL, bomboffset, ent->s.number, MASK_SHOT ); + if ( ( tr.fraction < 1.0 ) && ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) ) { //SURF_SKY)) ) { // JPW NERVE changed for trenchtoast foggie prollem + G_SayTo( ent->parent, ent->parent, 2, COLOR_YELLOW, "Pilot: ", "Aborting, can't see target.", qtrue ); + + if ( ent->parent->client->sess.sessionTeam == TEAM_BLUE ) { + te = G_TempEntity( ent->parent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/allies/a-aborting.wav" ); + te->s.teamNum = ent->parent->s.clientNum; + } else { + te = G_TempEntity( ent->parent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/axis/g-aborting.wav" ); + te->s.teamNum = ent->parent->s.clientNum; + } + + return; + } + + if ( ent->parent->client->sess.sessionTeam == TEAM_BLUE ) { + te = G_TempEntity( ent->parent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/allies/a-affirmative_omw.wav" ); + te->s.teamNum = ent->parent->s.clientNum; + } else { + te = G_TempEntity( ent->parent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/axis/g-affirmative_omw.wav" ); + te->s.teamNum = ent->parent->s.clientNum; + } + + VectorCopy( tr.endpos, bomboffset ); + traceheight = bomboffset[2]; + bottomtraceheight = traceheight - 8192; + + VectorSubtract( ent->s.pos.trBase,ent->parent->client->ps.origin,lookaxis ); + lookaxis[2] = 0; + VectorNormalize( lookaxis ); + pos[0] = 0; + pos[1] = 0; + pos[2] = crandom(); // generate either up or down vector, + VectorNormalize( pos ); // which adds randomness to pass direction below + RotatePointAroundVector( bombaxis,pos,lookaxis,90 + crandom() * 30 ); // munge the axis line a bit so it's not totally perpendicular + VectorNormalize( bombaxis ); + + VectorCopy( bombaxis,pos ); + VectorScale( pos,(float)( -0.5f * BOMBSPREAD * NUMBOMBS ),pos ); + VectorAdd( ent->s.pos.trBase, pos, pos ); // first bomb position + VectorScale( bombaxis,BOMBSPREAD,bombaxis ); // bomb drop direction offset + + for ( i = 0; i < NUMBOMBS; i++ ) { + bomb = G_Spawn(); + bomb->nextthink = level.time + i * 100 + crandom() * 50 + 1000; // 1000 for aircraft flyby, other term for tumble stagger + bomb->think = G_AirStrikeExplode; + bomb->s.eType = ET_MISSILE; + bomb->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_NOCLIENT; + bomb->s.weapon = WP_ARTY; // might wanna change this + bomb->r.ownerNum = ent->s.number; + bomb->parent = ent->parent; + bomb->damage = 400; // maybe should un-hard-code these? + bomb->splashDamage = 400; + bomb->classname = "air strike"; + bomb->splashRadius = 400; + bomb->methodOfDeath = MOD_AIRSTRIKE; + bomb->splashMethodOfDeath = MOD_AIRSTRIKE; + bomb->clipmask = MASK_MISSILESHOT; + bomb->s.pos.trType = TR_STATIONARY; // was TR_GRAVITY, might wanna go back to this and drop from height + //bomb->s.pos.trTime = level.time; // move a bit on the very first frame + bomboffset[0] = crandom() * 0.5 * BOMBSPREAD; + bomboffset[1] = crandom() * 0.5 * BOMBSPREAD; + bomboffset[2] = 0; + VectorAdd( pos,bomboffset,bomb->s.pos.trBase ); + + VectorCopy( bomb->s.pos.trBase,bomboffset ); // make sure bombs fall "on top of" nonuniform scenery + bomboffset[2] = traceheight; + + VectorCopy( bomboffset, fallaxis ); + fallaxis[2] = bottomtraceheight; + + trap_Trace( &tr, bomboffset, NULL, NULL, fallaxis, ent->s.number, MASK_SHOT ); + if ( tr.fraction != 1.0 ) { + VectorCopy( tr.endpos,bomb->s.pos.trBase ); + } + + VectorClear( bomb->s.pos.trDelta ); + + // Snap origin! + VectorCopy( bomb->s.pos.trBase, temp ); + temp[2] += 2.f; + SnapVectorTowards( bomb->s.pos.trBase, temp ); // save net bandwidth + + VectorCopy( bomb->s.pos.trBase, bomb->r.currentOrigin ); + + // move pos for next bomb + VectorAdd( pos,bombaxis,pos ); + } +} + +// JPW NERVE -- sound effect for spotter round, had to do this as half-second bomb warning + +void artilleryThink_real( gentity_t *ent ) { + ent->freeAfterEvent = qtrue; + trap_LinkEntity( ent ); + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( "sound/multiplayer/artillery_01.wav" ) ); +} +void artilleryThink( gentity_t *ent ) { + ent->think = artilleryThink_real; + ent->nextthink = level.time + 100; + + ent->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST; +} + +// JPW NERVE -- makes smoke disappear after a bit (just unregisters stuff) +void artilleryGoAway( gentity_t *ent ) { + ent->freeAfterEvent = qtrue; + trap_LinkEntity( ent ); +} + +// JPW NERVE -- generates some smoke debris +void artillerySpotterThink( gentity_t *ent ) { + gentity_t *bomb; + vec3_t tmpdir; + int i; + ent->think = G_ExplodeMissile; + ent->nextthink = level.time + 1; + SnapVector( ent->s.pos.trBase ); + + for ( i = 0; i < 7; i++ ) { + bomb = G_Spawn(); + bomb->s.eType = ET_MISSILE; + bomb->r.svFlags = SVF_USE_CURRENT_ORIGIN; + bomb->r.ownerNum = ent->s.number; + bomb->parent = ent; + bomb->nextthink = level.time + 1000 + random() * 300; + bomb->classname = "WP"; // WP == White Phosphorous, so we can check for bounce noise in grenade bounce routine + bomb->damage = 000; // maybe should un-hard-code these? + bomb->splashDamage = 000; + bomb->splashRadius = 000; + bomb->s.weapon = WP_SMOKETRAIL; + bomb->think = artilleryGoAway; + bomb->s.eFlags |= EF_BOUNCE; + bomb->clipmask = MASK_MISSILESHOT; + bomb->s.pos.trType = TR_GRAVITY; // was TR_GRAVITY, might wanna go back to this and drop from height + bomb->s.pos.trTime = level.time; // move a bit on the very first frame + bomb->s.otherEntityNum2 = ent->s.otherEntityNum2; + VectorCopy( ent->s.pos.trBase,bomb->s.pos.trBase ); + tmpdir[0] = crandom(); + tmpdir[1] = crandom(); + tmpdir[2] = 1; + VectorNormalize( tmpdir ); + tmpdir[2] = 1; // extra up + VectorScale( tmpdir,500 + random() * 500,tmpdir ); + VectorCopy( tmpdir,bomb->s.pos.trDelta ); + SnapVector( bomb->s.pos.trDelta ); // save net bandwidth + VectorCopy( ent->s.pos.trBase,bomb->s.pos.trBase ); + VectorCopy( ent->s.pos.trBase,bomb->r.currentOrigin ); + } +} + + +// JPW NERVE +/* +================== +Weapon_Artillery +================== +*/ +void Weapon_Artillery( gentity_t *ent ) { + trace_t trace; + int i = 0; + vec3_t muzzlePoint,end,bomboffset,pos,fallaxis; + float traceheight, bottomtraceheight; + gentity_t *bomb,*bomb2,*te; + + if ( ent->client->ps.stats[STAT_PLAYER_CLASS] != PC_LT ) { + G_Printf( "not a lieutenant, you can't shoot this!\n" ); + return; + } + if ( level.time - ent->client->ps.classWeaponTime > g_LTChargeTime.integer ) { + + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + VectorCopy( ent->r.currentOrigin, muzzlePoint ); + muzzlePoint[2] += ent->client->ps.viewheight; + + VectorMA( muzzlePoint, 8192, forward, end ); + trap_Trace( &trace, muzzlePoint, NULL, NULL, end, ent->s.number, MASK_SHOT ); + + if ( trace.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + VectorCopy( trace.endpos,pos ); + VectorCopy( pos,bomboffset ); + bomboffset[2] += 4096; + + trap_Trace( &trace, pos, NULL, NULL, bomboffset, ent->s.number, MASK_SHOT ); + if ( ( trace.fraction < 1.0 ) && ( !( trace.surfaceFlags & SURF_NOIMPACT ) ) ) { // JPW NERVE was SURF_SKY)) ) { + G_SayTo( ent, ent, 2, COLOR_YELLOW, "Fire Mission: ", "Aborting, can't see target.", qtrue ); + + if ( ent->client->sess.sessionTeam == TEAM_BLUE ) { + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/allies/a-art_abort.wav" ); + te->s.teamNum = ent->s.clientNum; + } else { + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/axis/g-art_abort.wav" ); + te->s.teamNum = ent->s.clientNum; + } + return; + } + G_SayTo( ent, ent, 2, COLOR_YELLOW, "Fire Mission: ", "Firing for effect!", qtrue ); + + if ( ent->client->sess.sessionTeam == TEAM_BLUE ) { + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/allies/a-firing.wav" ); + te->s.teamNum = ent->s.clientNum; + } else { + te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_CLIENT_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/axis/g-firing.wav" ); + te->s.teamNum = ent->s.clientNum; + } + + VectorCopy( trace.endpos, bomboffset ); + traceheight = bomboffset[2]; + bottomtraceheight = traceheight - 8192; + + +// "spotter" round (i == 0) +// i == 1->4 is regular explosives + for ( i = 0; i < 5; i++ ) { + bomb = G_Spawn(); + bomb->think = G_AirStrikeExplode; + bomb->s.eType = ET_MISSILE; + bomb->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_NOCLIENT; + bomb->s.weapon = WP_ARTY; // might wanna change this + bomb->r.ownerNum = ent->s.number; + bomb->parent = ent; +/* + if (i == 0) { + bomb->nextthink = level.time + 4500; + bomb->think = artilleryThink; + } +*/ + if ( i == 0 ) { + bomb->nextthink = level.time + 5000; + bomb->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_BROADCAST; + bomb->classname = "props_explosion"; // was "air strike" + bomb->damage = 0; // maybe should un-hard-code these? + bomb->splashDamage = 90; + bomb->splashRadius = 50; +// bomb->s.weapon = WP_SMOKE_GRENADE; + // TTimo ambiguous else + if ( ent->client != NULL ) { // set team color on smoke + if ( ent->client->sess.sessionTeam == TEAM_RED ) { // store team so we can generate red or blue smoke + bomb->s.otherEntityNum2 = 1; + } else { + bomb->s.otherEntityNum2 = 0; + } + } + bomb->think = artillerySpotterThink; + } else { + bomb->nextthink = level.time + 8950 + 2000 * i + crandom() * 800; + bomb->classname = "air strike"; + bomb->damage = 0; + bomb->splashDamage = 400; + bomb->splashRadius = 400; + } + bomb->methodOfDeath = MOD_AIRSTRIKE; + bomb->splashMethodOfDeath = MOD_AIRSTRIKE; + bomb->clipmask = MASK_MISSILESHOT; + bomb->s.pos.trType = TR_STATIONARY; // was TR_GRAVITY, might wanna go back to this and drop from height + bomb->s.pos.trTime = level.time; // move a bit on the very first frame + if ( i ) { // spotter round is always dead on (OK, unrealistic but more fun) + bomboffset[0] = crandom() * 250; + bomboffset[1] = crandom() * 250; + } else { + bomboffset[0] = crandom() * 50; // was 0; changed per id request to prevent spotter round assassinations + bomboffset[1] = crandom() * 50; // was 0; + } + bomboffset[2] = 0; + VectorAdd( pos,bomboffset,bomb->s.pos.trBase ); + + VectorCopy( bomb->s.pos.trBase,bomboffset ); // make sure bombs fall "on top of" nonuniform scenery + bomboffset[2] = traceheight; + + VectorCopy( bomboffset, fallaxis ); + fallaxis[2] = bottomtraceheight; + + trap_Trace( &trace, bomboffset, NULL, NULL, fallaxis, ent->s.number, MASK_SHOT ); + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos,bomb->s.pos.trBase ); + } + + bomb->s.pos.trDelta[0] = 0; // might need to change this + bomb->s.pos.trDelta[1] = 0; + bomb->s.pos.trDelta[2] = 0; + SnapVector( bomb->s.pos.trDelta ); // save net bandwidth + VectorCopy( bomb->s.pos.trBase, bomb->r.currentOrigin ); + +// build arty falling sound effect in front of bomb drop + bomb2 = G_Spawn(); + bomb2->think = artilleryThink; + bomb2->s.eType = ET_MISSILE; + bomb2->r.svFlags = SVF_USE_CURRENT_ORIGIN | SVF_NOCLIENT; + bomb2->r.ownerNum = ent->s.number; + bomb2->parent = ent; + bomb2->damage = 0; + bomb2->nextthink = bomb->nextthink - 600; + bomb2->classname = "air strike"; + bomb2->clipmask = MASK_MISSILESHOT; + bomb2->s.pos.trType = TR_STATIONARY; // was TR_GRAVITY, might wanna go back to this and drop from height + bomb2->s.pos.trTime = level.time; // move a bit on the very first frame + VectorCopy( bomb->s.pos.trBase,bomb2->s.pos.trBase ); + VectorCopy( bomb->s.pos.trDelta,bomb2->s.pos.trDelta ); + VectorCopy( bomb->s.pos.trBase,bomb2->r.currentOrigin ); + } + ent->client->ps.classWeaponTime = level.time; + } + +} + +gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, int ownerNum ); +// jpw + +/* +============== +Weapon_Gauntlet +============== +*/ +void Weapon_Gauntlet( gentity_t *ent ) { + trace_t *tr; + // TTimo gcc: suggest parentheses around assignment used as truth value + if ( ( tr = CheckMeleeAttack( ent, 32, qfalse ) ) ) { + G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, + ( 10 + rand() % 5 ) * s_quadFactor, 0, MOD_GAUNTLET ); + } +} + +/* +=============== +CheckMeleeAttack + using 'isTest' to return hits to world surfaces +=============== +*/ +trace_t *CheckMeleeAttack( gentity_t *ent, float dist, qboolean isTest ) { + static trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePoint( ent, WP_GAUNTLET, forward, right, up, muzzleTrace ); + + VectorMA( muzzleTrace, dist, forward, end ); + + trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return NULL; + } + + // no contact + if ( tr.fraction == 1.0f ) { + return NULL; + } + + traceEnt = &g_entities[ tr.entityNum ]; + + // send blood impact + if ( traceEnt->takedamage && traceEnt->client ) { + tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); + tent->s.otherEntityNum = traceEnt->s.number; + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + } + +//----(SA) added + if ( isTest ) { + return &tr; + } +//----(SA) + + if ( !traceEnt->takedamage ) { + return NULL; + } + + if ( ent->client->ps.powerups[PW_QUAD] ) { + G_AddEvent( ent, EV_POWERUP_QUAD, 0 ); + s_quadFactor = g_quadfactor.value; + } else { + s_quadFactor = 1; + } + + return &tr; +} + + +/* +====================================================================== + +MACHINEGUN + +====================================================================== +*/ + +/* +====================== +SnapVectorTowards + +Round a vector to integers for more efficient network +transmission, but make sure that it rounds towards a given point +rather than blindly truncating. This prevents it from truncating +into a wall. +====================== +*/ + +// (SA) modified so it doesn't have trouble with negative locations (quadrant problems) +// (this was causing some problems with bullet marks appearing since snapping +// too far off the target surface causes the the distance between the transmitted impact +// point and the actual hit surface larger than the mark radius. (so nothing shows) ) + +void SnapVectorTowards( vec3_t v, vec3_t to ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + if ( to[i] <= v[i] ) { +// v[i] = (int)v[i]; + v[i] = floor( v[i] ); + } else { +// v[i] = (int)v[i] + 1; + v[i] = ceil( v[i] ); + } + } +} + +// JPW +// mechanism allows different weapon damage for single/multiplayer; we want "balanced" weapons +// in multiplayer but don't want to alter the existing single-player damage items that have already +// been changed +// +// KLUDGE/FIXME: also modded #defines below to become macros that call this fn for minimal impact elsewhere +// +int G_GetWeaponDamage( int weapon ) { + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + switch ( weapon ) { + case WP_LUGER: + case WP_SILENCER: return 6; + case WP_COLT: return 8; + case WP_AKIMBO: return 8; //----(SA) added + case WP_VENOM_FULL: + case WP_VENOM: return 12; // 15 ----(SA) slight modify for DM + case WP_MP40: return 6; + case WP_THOMPSON: return 8; + case WP_STEN: return 10; + case WP_FG42SCOPE: + case WP_FG42: return 15; + case WP_BAR: + case WP_BAR2: return 12; //----(SA) added + case WP_MAUSER: return 20; + case WP_GARAND: return 25; + case WP_SNIPERRIFLE: return 55; + case WP_SNOOPERSCOPE: return 25; + case WP_NONE: return 0; + case WP_KNIFE: + case WP_KNIFE2: return 5; + case WP_GRENADE_LAUNCHER: return 100; + case WP_GRENADE_PINEAPPLE: return 80; + case WP_DYNAMITE: + case WP_DYNAMITE2: return 300; + case WP_ROCKET_LAUNCHER: + case WP_PANZERFAUST: return 100; + case WP_MORTAR: return 100; + case WP_FLAMETHROWER: // FIXME -- not used in single player yet + case WP_TESLA: + case WP_SPEARGUN: + case WP_SPEARGUN_CO2: + case WP_CROSS: + case WP_GAUNTLET: + case WP_SNIPER: + default: return 1; + } + } else { // multiplayer damage + switch ( weapon ) { + case WP_LUGER: + case WP_SILENCER: + case WP_COLT: return 18; + case WP_AKIMBO: return 18; //----(SA) added + case WP_VENOM_FULL: return 10; + case WP_VENOM: return 20; + case WP_MP40: return 14; + case WP_THOMPSON: return 18; + case WP_STEN: return 14; + case WP_FG42SCOPE: + case WP_FG42: return 15; + case WP_BAR: + case WP_BAR2: return 12; //----(SA) added + case WP_MAUSER: return 80; // was 25 JPW + case WP_GARAND: return 75; // was 25 JPW + case WP_SNIPERRIFLE: return 80; + case WP_SNOOPERSCOPE: return 75; + case WP_NONE: return 0; + case WP_KNIFE: + case WP_KNIFE2: return 10; + case WP_SMOKE_GRENADE: return 140; // just enough to kill somebody standing on it + case WP_GRENADE_LAUNCHER: return 250; + case WP_GRENADE_PINEAPPLE: return 250; + case WP_DYNAMITE: + case WP_DYNAMITE2: return 600; + case WP_ROCKET_LAUNCHER: + case WP_PANZERFAUST: return 400; + case WP_MORTAR: return 250; + case WP_FLAMETHROWER: return 1; + case WP_TESLA: + case WP_SPEARGUN: + case WP_SPEARGUN_CO2: + case WP_CROSS: + case WP_GAUNTLET: + case WP_SNIPER: + default: return 1; + } + } +} +// JPW - this chunk appears to not be used, right? +/* +#define MACHINEGUN_SPREAD 200 +#define MACHINEGUN_DAMAGE G_GetWeaponDamage(WP_MACHINEGUN) // JPW +#define MACHINEGUN_TEAM_DAMAGE G_GetWeaponDamage(WP_MACHINEGUN) // JPW // wimpier MG in teamplay +*/ +// jpw + +// RF, wrote this so we can dynamically switch between old and new values while testing g_userAim +float G_GetWeaponSpread( int weapon ) { + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE -- don't affect SP game + if ( g_userAim.integer ) { + // these should be higher since they become erratic if aiming is out + switch ( weapon ) { + case WP_LUGER: return 600; + case WP_SILENCER: return 900; + case WP_COLT: return 700; + case WP_AKIMBO: return 700; //----(SA) added + case WP_VENOM: return 1000; + case WP_MP40: return 1000; + case WP_FG42SCOPE: + case WP_FG42: return 800; + case WP_BAR: + case WP_BAR2: return 800; + case WP_THOMPSON: return 1200; + case WP_STEN: return 1200; + case WP_MAUSER: return 400; + case WP_GARAND: return 500; + case WP_SNIPERRIFLE: return 300; + case WP_SNOOPERSCOPE: return 300; + } + } else { // old values + switch ( weapon ) { + case WP_LUGER: return 25; + case WP_SILENCER: return 150; + case WP_COLT: return 30; + case WP_AKIMBO: return 30; //----(SA) added + case WP_VENOM: return 200; + case WP_MP40: return 200; + case WP_FG42SCOPE: + case WP_FG42: return 150; + case WP_BAR: + case WP_BAR2: return 150; + case WP_THOMPSON: return 250; + case WP_STEN: return 300; + case WP_MAUSER: return 15; + case WP_GARAND: return 25; + case WP_SNIPERRIFLE: return 10; + case WP_SNOOPERSCOPE: return 10; + } + } + } else { // JPW NERVE but in multiplayer... new spreads and don't look at g_userAim + switch ( weapon ) { + case WP_LUGER: return 600; + case WP_SILENCER: return 900; + case WP_COLT: return 800; + case WP_AKIMBO: return 800; //----(SA)added + case WP_VENOM: return 600; + case WP_MP40: return 400; + case WP_FG42SCOPE: + case WP_FG42: return 500; + case WP_BAR: + case WP_BAR2: return 500; + case WP_THOMPSON: return 600; + case WP_STEN: return 200; + case WP_MAUSER: return 2000; + case WP_GARAND: return 600; + case WP_SNIPERRIFLE: return 700; // was 300 + case WP_SNOOPERSCOPE: return 700; + } + } + G_Printf( "shouldn't ever get here (weapon %d)\n",weapon ); + // jpw + return 0; // shouldn't get here +} + +#define LUGER_SPREAD G_GetWeaponSpread( WP_LUGER ) +#define LUGER_DAMAGE G_GetWeaponDamage( WP_LUGER ) // JPW +#define SILENCER_SPREAD G_GetWeaponSpread( WP_SILENCER ) +#define COLT_SPREAD G_GetWeaponSpread( WP_COLT ) +#define COLT_DAMAGE G_GetWeaponDamage( WP_COLT ) // JPW + +#define VENOM_SPREAD G_GetWeaponSpread( WP_VENOM ) +#define VENOM_DAMAGE G_GetWeaponDamage( WP_VENOM ) // JPW + +#define MP40_SPREAD G_GetWeaponSpread( WP_MP40 ) +#define MP40_DAMAGE G_GetWeaponDamage( WP_MP40 ) // JPW +#define THOMPSON_SPREAD G_GetWeaponSpread( WP_THOMPSON ) +#define THOMPSON_DAMAGE G_GetWeaponDamage( WP_THOMPSON ) // JPW +#define STEN_SPREAD G_GetWeaponSpread( WP_STEN ) +#define STEN_DAMAGE G_GetWeaponDamage( WP_STEN ) // JPW +#define FG42_SPREAD G_GetWeaponSpread( WP_FG42 ) +#define FG42_DAMAGE G_GetWeaponDamage( WP_FG42 ) // JPW +#define BAR_SPREAD G_GetWeaponSpread( WP_BAR ) +#define BAR_DAMAGE G_GetWeaponDamage( WP_BAR ) + +#define MAUSER_SPREAD G_GetWeaponSpread( WP_MAUSER ) +#define MAUSER_DAMAGE G_GetWeaponDamage( WP_MAUSER ) // JPW +#define GARAND_SPREAD G_GetWeaponSpread( WP_GARAND ) +#define GARAND_DAMAGE G_GetWeaponDamage( WP_GARAND ) // JPW + +#define SNIPER_SPREAD G_GetWeaponSpread( WP_SNIPERRIFLE ) +#define SNIPER_DAMAGE G_GetWeaponDamage( WP_SNIPERRIFLE ) // JPW + +#define SNOOPER_SPREAD G_GetWeaponSpread( WP_SNOOPERSCOPE ) +#define SNOOPER_DAMAGE G_GetWeaponDamage( WP_SNOOPERSCOPE ) // JPW + +/* +============== +SP5_Fire + + dead code +============== +*/ +void SP5_Fire( gentity_t *ent, float aimSpreadScale ) { + // TTimo unused +// static int seed = 0x92; + + float spread = 400; // these used to be passed in + int damage; + + trace_t tr; + vec3_t end; + float r; + float u; + gentity_t *tent; + gentity_t *traceEnt; + +/* + // first do a very short, high-accuracy trace + VectorMA (muzzleTrace, 128, forward, end); + trap_Trace (&tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT); + // then if that fails do a longer wild shot + if ( tr.fraction == 1 ) // didn't hit anything + { + { + vec3_t vec; + float len; + + VectorMA (muzzleTrace, 4096, forward, end); + trap_Trace (&tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT); + VectorSubtract (muzzleTrace, tr.endpos, vec); + len = VectorLength (vec); + + if (len > 400) + spread = 400; + else + spread = len; + + VectorClear (end); + } +*/ + spread *= aimSpreadScale; + + r = crandom() * spread; + u = crandom() * spread; + VectorMA( muzzleTrace, 4096, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + trap_Trace( &tr, muzzleTrace, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } +// } + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzleTrace ); + + // send bullet impact + if ( traceEnt->takedamage && traceEnt->client && !( traceEnt->flags & ( FL_DEFENSE_GUARD | FL_WARZOMBIECHARGE ) ) ) { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + if ( LogAccuracyHit( traceEnt, ent ) ) { + ent->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + } else if ( ( traceEnt->flags & FL_WARZOMBIECHARGE ) && ( rand() % 3 ) == 0 ) { // hit every other bullet when charging + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + if ( LogAccuracyHit( traceEnt, ent ) ) { + ent->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + } else { + // Ridah, bullet impact should reflect off surface + vec3_t reflect; + float dot; + + if ( traceEnt->flags & ( FL_DEFENSE_GUARD | FL_WARZOMBIECHARGE ) ) { + // reflect off sheild + VectorSubtract( tr.endpos, traceEnt->r.currentOrigin, reflect ); + VectorNormalize( reflect ); + VectorMA( traceEnt->r.currentOrigin, 15, reflect, reflect ); + tent = G_TempEntity( reflect, EV_BULLET_HIT_WALL ); + } else { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL ); + } + + dot = DotProduct( forward, tr.plane.normal ); + VectorMA( forward, -2 * dot, tr.plane.normal, reflect ); + VectorNormalize( reflect ); + + tent->s.eventParm = DirToByte( reflect ); + // done. + } + tent->s.otherEntityNum = ent->s.number; + + if ( traceEnt->takedamage ) { + qboolean reflectBool = qfalse; + vec3_t trDir; + + if ( traceEnt->flags & FL_DEFENSE_GUARD ) { + // if we are facing the direction the bullet came from, then reflect it + AngleVectors( traceEnt->s.apos.trBase, trDir, NULL, NULL ); + if ( DotProduct( forward, trDir ) < 0.6 ) { + reflectBool = qtrue; + } + } + + //----(SA) moved these up so damage sent in Bullet_Fire() will be valid + damage = G_GetWeaponDamage( WP_SILENCER ) + ( random() * 15 ); // JPW giving 40-55 + damage *= s_quadFactor; + + if ( reflectBool ) { + // reflect this bullet + G_AddEvent( traceEnt, EV_GENERAL_SOUND, level.bulletRicochetSound ); + CalcMuzzlePoints( traceEnt, traceEnt->s.weapon ); + Bullet_Fire( traceEnt, 1000, damage ); + } else { + // Ridah, don't hurt team-mates + // DHM - Nerve :: only in single player + if ( ent->client && traceEnt->client && g_gametype.integer == GT_SINGLE_PLAYER && ( traceEnt->r.svFlags & SVF_CASTAI ) && ( ent->r.svFlags & SVF_CASTAI ) && AICast_SameTeam( AICast_GetCastState( ent->s.number ), traceEnt->s.number ) ) { + // AI's don't hurt members of their own team + return; + } + // done. + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_SILENCER ); + } + } +} + + +void RubbleFlagCheck( gentity_t *ent, trace_t tr ) { + qboolean is_valid = qfalse; + int type = 0; + + // (SA) moving client-side + + return; + + + + + if ( tr.surfaceFlags & SURF_RUBBLE || tr.surfaceFlags & SURF_GRAVEL ) { + is_valid = qtrue; + type = 4; + } else if ( tr.surfaceFlags & SURF_METAL ) { +//----(SA) removed +// is_valid = qtrue; +// type = 2; + } else if ( tr.surfaceFlags & SURF_WOOD ) { + is_valid = qtrue; + type = 1; + } + + if ( is_valid && ent->client && ( ent->s.weapon == WP_VENOM + || ent->client->ps.persistant[PERS_HWEAPON_USE] ) ) { + if ( rand() % 100 > 75 ) { + gentity_t *sfx; + vec3_t start; + vec3_t dir; + + sfx = G_Spawn(); + + sfx->s.density = type; + + VectorCopy( tr.endpos, start ); + + VectorCopy( muzzleTrace, dir ); + VectorNegate( dir, dir ); + + G_SetOrigin( sfx, start ); + G_SetAngle( sfx, dir ); + + G_AddEvent( sfx, EV_SHARD, DirToByte( dir ) ); + + sfx->think = G_FreeEntity; + sfx->nextthink = level.time + 1000; + + sfx->s.frame = 3 + ( rand() % 3 ) ; + + trap_LinkEntity( sfx ); + + } + } + +} + +/* +============== +EmitterCheck + see if a new particle emitter should be created at the bullet impact point +============== +*/ +void EmitterCheck( gentity_t *ent, gentity_t *attacker, trace_t *tr ) { + gentity_t *tent; + vec3_t origin; + + VectorCopy( tr->endpos, origin ); + SnapVectorTowards( tr->endpos, attacker->s.origin ); + + if ( Q_stricmp( ent->classname, "func_explosive" ) == 0 ) { + } else if ( Q_stricmp( ent->classname, "func_leaky" ) == 0 ) { + + + tent = G_TempEntity( origin, EV_EMITTER ); + VectorCopy( origin, tent->s.origin ); + tent->s.time = 1234; + tent->s.density = 9876; + VectorCopy( tr->plane.normal, tent->s.origin2 ); + + } +} + + +void SniperSoundEFX( vec3_t pos ) { + gentity_t *sniperEnt; + sniperEnt = G_TempEntity( pos, EV_SNIPER_SOUND ); +} + + +/* +============== +Bullet_Endpos + find target end position for bullet trace based on entities weapon and accuracy +============== +*/ +void Bullet_Endpos( gentity_t *ent, float spread, vec3_t *end ) { + float r, u; + qboolean randSpread = qtrue; + int dist = 8192; + + r = crandom() * spread; + u = crandom() * spread; + + // Ridah, if this is an AI shooting, apply their accuracy + if ( ent->r.svFlags & SVF_CASTAI ) { + float accuracy; + accuracy = ( 1.0 - AICast_GetAccuracy( ent->s.number ) ) * AICAST_AIM_SPREAD; + r += crandom() * accuracy; + u += crandom() * ( accuracy * 1.25 ); + } else { + if ( ent->s.weapon == WP_SNOOPERSCOPE || ent->s.weapon == WP_SNIPERRIFLE ) { + // aim dir already accounted for sway of scoped weapons in CalcMuzzlePoints() + dist *= 2; + randSpread = qfalse; + } + } + + VectorMA( muzzleTrace, dist, forward, *end ); + + if ( randSpread ) { + VectorMA( *end, r, right, *end ); + VectorMA( *end, u, up, *end ); + } +} + +/* +============== +Bullet_Fire +============== +*/ +void Bullet_Fire( gentity_t *ent, float spread, int damage ) { + vec3_t end; + + Bullet_Endpos( ent, spread, &end ); + Bullet_Fire_Extended( ent, ent, muzzleTrace, end, spread, damage ); +} + + +/* +============== +Bullet_Fire_Extended + A modified Bullet_Fire with more parameters. + The original Bullet_Fire still passes through here and functions as it always has. + + uses for this include shooting through entities (windows, doors, other players, etc.) and reflecting bullets +============== +*/ +void Bullet_Fire_Extended( gentity_t *source, gentity_t *attacker, vec3_t start, vec3_t end, float spread, int damage ) { + trace_t tr; + gentity_t *tent; + gentity_t *traceEnt; + + damage *= s_quadFactor; + + G_HistoricalTrace( source, &tr, start, NULL, NULL, end, source->s.number, MASK_SHOT ); + + // DHM - Nerve :: only in single player + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { + AICast_ProcessBullet( attacker, start, tr.endpos ); + } + + // bullet debugging using Q3A's railtrail + if ( g_debugBullets.integer & 1 ) { + tent = G_TempEntity( start, EV_RAILTRAIL ); + VectorCopy( tr.endpos, tent->s.origin2 ); + tent->s.otherEntityNum2 = attacker->s.number; + } + + +//----(SA) commented out +// if ( tr.surfaceFlags & SURF_NOIMPACT ) { +// if (attacker->s.weapon == WP_MAUSER && attacker->r.svFlags & SVF_CASTAI) +// SniperSoundEFX (tr.endpos); +// +// return; +// } +//----(SA) end + + RubbleFlagCheck( attacker, tr ); + + traceEnt = &g_entities[ tr.entityNum ]; + + EmitterCheck( traceEnt, attacker, &tr ); + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, start ); + +//----(SA) commented out +// if (attacker->s.weapon == WP_MAUSER && attacker->r.svFlags & SVF_CASTAI) +// { +// SniperSoundEFX (tr.endpos); +// } +//----(SA) end + + // send bullet impact + if ( traceEnt->takedamage && traceEnt->client && !( traceEnt->flags & FL_DEFENSE_GUARD ) ) { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + if ( LogAccuracyHit( traceEnt, attacker ) ) { + attacker->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + +//----(SA) added + if ( g_debugBullets.integer >= 2 ) { // show hit player bb + gentity_t *bboxEnt; + vec3_t b1, b2; + VectorCopy( traceEnt->r.currentOrigin, b1 ); + VectorCopy( traceEnt->r.currentOrigin, b2 ); + VectorAdd( b1, traceEnt->r.mins, b1 ); + VectorAdd( b2, traceEnt->r.maxs, b2 ); + bboxEnt = G_TempEntity( b1, EV_RAILTRAIL ); + VectorCopy( b2, bboxEnt->s.origin2 ); + bboxEnt->s.dmgFlags = 1; // ("type") + } +//----(SA) end + + } else if ( traceEnt->takedamage && traceEnt->s.eType == ET_BAT ) { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + } else { + // Ridah, bullet impact should reflect off surface + vec3_t reflect; + float dot; + + if ( g_debugBullets.integer <= -2 ) { // show hit thing bb + gentity_t *bboxEnt; + vec3_t b1, b2; + VectorCopy( traceEnt->r.currentOrigin, b1 ); + VectorCopy( traceEnt->r.currentOrigin, b2 ); + VectorAdd( b1, traceEnt->r.mins, b1 ); + VectorAdd( b2, traceEnt->r.maxs, b2 ); + bboxEnt = G_TempEntity( b1, EV_RAILTRAIL ); + VectorCopy( b2, bboxEnt->s.origin2 ); + bboxEnt->s.dmgFlags = 1; // ("type") + } + + if ( traceEnt->flags & FL_DEFENSE_GUARD ) { + // reflect off sheild + VectorSubtract( tr.endpos, traceEnt->r.currentOrigin, reflect ); + VectorNormalize( reflect ); + VectorMA( traceEnt->r.currentOrigin, 15, reflect, reflect ); + tent = G_TempEntity( reflect, EV_BULLET_HIT_WALL ); + } else { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL ); + } + + dot = DotProduct( forward, tr.plane.normal ); + VectorMA( forward, -2 * dot, tr.plane.normal, reflect ); + VectorNormalize( reflect ); + + tent->s.eventParm = DirToByte( reflect ); + + if ( traceEnt->flags & FL_DEFENSE_GUARD ) { + tent->s.otherEntityNum2 = traceEnt->s.number; // force sparks + } else { + tent->s.otherEntityNum2 = ENTITYNUM_NONE; + } + // done. + } + tent->s.otherEntityNum = attacker->s.number; + + if ( traceEnt->takedamage ) { + qboolean reflectBool = qfalse; + vec3_t trDir; + + if ( traceEnt->flags & FL_DEFENSE_GUARD ) { + // if we are facing the direction the bullet came from, then reflect it + AngleVectors( traceEnt->s.apos.trBase, trDir, NULL, NULL ); + if ( DotProduct( forward, trDir ) < 0.6 ) { + reflectBool = qtrue; + } + } + + if ( reflectBool ) { + vec3_t reflect_end; + // reflect this bullet + G_AddEvent( traceEnt, EV_GENERAL_SOUND, level.bulletRicochetSound ); + CalcMuzzlePoints( traceEnt, traceEnt->s.weapon ); + +//----(SA) modified to use extended version so attacker would pass through +// Bullet_Fire( traceEnt, 1000, damage ); + Bullet_Endpos( traceEnt, spread, &reflect_end ); + Bullet_Fire_Extended( traceEnt, attacker, muzzleTrace, reflect_end, spread, damage ); +//----(SA) end + + } else { + // Ridah, don't hurt team-mates + // DHM - Nerve :: Only in single player + if ( attacker->client && traceEnt->client && g_gametype.integer == GT_SINGLE_PLAYER && ( traceEnt->r.svFlags & SVF_CASTAI ) && ( attacker->r.svFlags & SVF_CASTAI ) && AICast_SameTeam( AICast_GetCastState( attacker->s.number ), traceEnt->s.number ) ) { + // AI's don't hurt members of their own team + return; + } + // done. + + G_Damage( traceEnt, attacker, attacker, forward, tr.endpos, damage, 0, ammoTable[attacker->s.weapon].mod ); + + // allow bullets to "pass through" func_explosives if they break by taking another simultanious shot + if ( Q_stricmp( traceEnt->classname, "func_explosive" ) == 0 ) { + if ( traceEnt->health <= damage ) { + // start new bullet at position this hit the bmodel and continue to the end position (ignoring shot-through bmodel in next trace) + // spread = 0 as this is an extension of an already spread shot + Bullet_Fire_Extended( traceEnt, attacker, tr.endpos, end, 0, damage ); + } + } + + } + } +} + + + +/* +====================================================================== + +GRENADE LAUNCHER + + 700 has been the standard direction multiplier in fire_grenade() + +====================================================================== +*/ +extern void G_ExplodeMissilePoisonGas( gentity_t *ent ); + +gentity_t *weapon_grenadelauncher_fire( gentity_t *ent, int grenType ) { + gentity_t *m, *te; // JPW NERVE + trace_t tr; + vec3_t viewpos; + float upangle = 0, pitch; // start with level throwing and adjust based on angle + vec3_t tosspos; + qboolean underhand = qtrue; + + pitch = ent->s.apos.trBase[0]; + + // JPW NERVE -- smoke grenades always overhand + if ( pitch >= 0 ) { + forward[2] += 0.5f; + // Used later in underhand boost + pitch = 1.3f; + } else { + pitch = -pitch; + pitch = min( pitch, 30 ); + pitch /= 30.f; + pitch = 1 - pitch; + forward[2] += ( pitch * 0.5f ); + + // Used later in underhand boost + pitch *= 0.3f; + pitch += 1.f; + } + + VectorNormalizeFast( forward ); // make sure forward is normalized + + upangle = -( ent->s.apos.trBase[0] ); // this will give between -90 / 90 + upangle = min( upangle, 50 ); + upangle = max( upangle, -50 ); // now clamped to -50 / 50 (don't allow firing straight up/down) + upangle = upangle / 100.0f; // -0.5 / 0.5 + upangle += 0.5f; // 0.0 / 1.0 + + if ( upangle < .1 ) { + upangle = .1; + } + + // pineapples are not thrown as far as mashers + if ( grenType == WP_GRENADE_LAUNCHER ) { + upangle *= 900; + } else if ( grenType == WP_GRENADE_PINEAPPLE ) { + upangle *= 900; + } else if ( grenType == WP_SMOKE_GRENADE ) { + upangle *= 900; + } else { // WP_DYNAMITE + upangle *= 400; + } + + VectorCopy( muzzleEffect, tosspos ); + + if ( underhand ) { + // move a little bit more away from the player (so underhand tosses don't get caught on nearby lips) + VectorMA( muzzleEffect, 8, forward, tosspos ); + tosspos[2] -= 8; // lower origin for the underhand throw + upangle *= pitch; + SnapVector( tosspos ); + } + + VectorScale( forward, upangle, forward ); + + // check for valid start spot (so you don't throw through or get stuck in a wall) + VectorCopy( ent->s.pos.trBase, viewpos ); + viewpos[2] += ent->client->ps.viewheight; + + if ( grenType == WP_DYNAMITE ) { + trap_Trace( &tr, viewpos, tv( -12.f, -12.f, 0.f ), tv( 12.f, 12.f, 20.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); + } else { + trap_Trace( &tr, viewpos, tv( -4.f, -4.f, 0.f ), tv( 4.f, 4.f, 6.f ), tosspos, ent->s.number, MASK_MISSILESHOT ); + } + + if ( tr.fraction < 1 ) { // oops, bad launch spot + VectorCopy( tr.endpos, tosspos ); + SnapVectorTowards( tosspos, viewpos ); + } + + m = fire_grenade( ent, tosspos, forward, grenType ); + + m->damage = 0; // Ridah, grenade's don't explode on contact + m->splashDamage *= s_quadFactor; + + // JPW NERVE + if ( grenType == WP_SMOKE_GRENADE ) { + + if ( ent->client->sess.sessionTeam == TEAM_RED ) { // store team so we can generate red or blue smoke + m->s.otherEntityNum2 = 1; + } else { + m->s.otherEntityNum2 = 0; + } + m->nextthink = level.time + 4000; + m->think = weapon_callAirStrike; + + te = G_TempEntity( m->s.pos.trBase, EV_GLOBAL_SOUND ); + te->s.eventParm = G_SoundIndex( "sound/multiplayer/airstrike_01.wav" ); + te->r.svFlags |= SVF_BROADCAST | SVF_USE_CURRENT_ORIGIN; + } + // jpw + + //----(SA) adjust for movement of character. TODO: Probably comment in later, but only for forward/back not strafing + //VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics + + // let the AI know which grenade it has fired + ent->grenadeFired = m->s.number; + + // Ridah, return the grenade so we can do some prediction before deciding if we really want to throw it or not + return m; +} + +//----(SA) modified this entire "venom" section +/* +============================================================================ + +VENOM GUN TRACING + +============================================================================ +*/ +#define DEFAULT_VENOM_COUNT 10 +#define DEFAULT_VENOM_SPREAD 20 +#define DEFAULT_VENOM_DAMAGE 15 + +qboolean VenomPellet( vec3_t start, vec3_t end, gentity_t *ent ) { + trace_t tr; + int damage; + gentity_t *traceEnt; + + trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + + // send bullet impact + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return qfalse; + } + + if ( traceEnt->takedamage ) { + damage = DEFAULT_VENOM_DAMAGE * s_quadFactor; + + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_VENOM ); + if ( LogAccuracyHit( traceEnt, ent ) ) { + return qtrue; + } + } + return qfalse; +} + +// this should match CG_VenomPattern +void VenomPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) { + int i; + float r, u; + vec3_t end; + vec3_t forward, right, up; + int oldScore; + qboolean hitClient = qfalse; + + // derive the right and up vectors from the forward vector, because + // the client won't have any other information + VectorNormalize2( origin2, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + oldScore = ent->client->ps.persistant[PERS_SCORE]; + + // generate the "random" spread pattern + for ( i = 0 ; i < DEFAULT_VENOM_COUNT ; i++ ) { + r = Q_crandom( &seed ) * DEFAULT_VENOM_SPREAD; + u = Q_crandom( &seed ) * DEFAULT_VENOM_SPREAD; + VectorMA( origin, 8192, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + if ( VenomPellet( origin, end, ent ) && !hitClient ) { + hitClient = qtrue; + ent->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + } +} + +/* +====================================================================== + +NAILGUN + +====================================================================== +*/ + +void Weapon_Nailgun_Fire( gentity_t *ent ) { + gentity_t *m; + int count; + + for ( count = 0; count < NUM_NAILSHOTS; count++ ) { +// m = fire_nail (ent, muzzle, forward, right, up ); + m = fire_nail( ent, muzzleEffect, forward, right, up ); + m->damage *= s_quadFactor; + m->splashDamage *= s_quadFactor; + } + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + + +/* +====================================================================== + +PROXIMITY MINE LAUNCHER + +====================================================================== +*/ + +void weapon_proxlauncher_fire( gentity_t *ent ) { + gentity_t *m; + + // extra vertical velocity + forward[2] += 0.2f; + VectorNormalize( forward ); + + m = fire_prox( ent, muzzleTrace, forward ); + m->damage *= s_quadFactor; + m->splashDamage *= s_quadFactor; + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + +/* +============== +weapon_venom_fire +============== +*/ +void weapon_venom_fire( gentity_t *ent, qboolean fullmode, float aimSpreadScale ) { + gentity_t *tent; + + if ( fullmode ) { + tent = G_TempEntity( muzzleTrace, EV_VENOMFULL ); + } else { + tent = G_TempEntity( muzzleTrace, EV_VENOM ); + } + + VectorScale( forward, 4096, tent->s.origin2 ); + SnapVector( tent->s.origin2 ); + tent->s.eventParm = rand() & 255; // seed for spread pattern + tent->s.otherEntityNum = ent->s.number; + + if ( fullmode ) { + VenomPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent ); + } else { + Bullet_Fire( ent, VENOM_SPREAD * aimSpreadScale, VENOM_DAMAGE ); + } +} + + + + + +/* +====================================================================== + +ROCKET + +====================================================================== +*/ + +void Weapon_RocketLauncher_Fire( gentity_t *ent ) { + gentity_t *m; + + m = fire_rocket( ent, muzzleEffect, forward ); + m->damage *= s_quadFactor; + m->splashDamage *= s_quadFactor; + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + + +/* +====================================================================== + +SPEARGUN + +====================================================================== +*/ +void Weapon_Speargun_Fire( gentity_t *ent ) { + gentity_t *m; + + m = fire_speargun( ent, muzzleEffect, forward ); + m->damage *= s_quadFactor; +} + + +/* +====================================================================== + +LIGHTNING GUN + +====================================================================== +*/ + +// TTimo - extracted G_FlameDamage to unify with Weapon_FlamethrowerFire usage +void G_BurnMeGood( gentity_t *self, gentity_t *body ) { + // add the new damage + body->flameQuota += 5; + body->flameQuotaTime = level.time; + + // JPW NERVE -- yet another flamethrower damage model, trying to find a feels-good damage combo that isn't overpowered + if ( body->lastBurnedFrameNumber != level.framenum ) { + G_Damage( body,self->parent,self->parent,vec3_origin,self->r.currentOrigin,5,0,MOD_FLAMETHROWER ); // was 2 dmg in release ver, hit avg. 2.5 times per frame + body->lastBurnedFrameNumber = level.framenum; + } + // jpw + + // make em burn + if ( body->client && ( body->health <= 0 || body->flameQuota > 0 ) ) { // JPW NERVE was > FLAME_THRESHOLD + if ( body->s.onFireEnd < level.time ) { + body->s.onFireStart = level.time; + } + + body->s.onFireEnd = level.time + FIRE_FLASH_TIME; + body->flameBurnEnt = self->s.number; + // add to playerState for client-side effect + body->client->ps.onFireStart = level.time; + } +} + +// those are used in the cg_ traces calls +static vec3_t flameChunkMins = {-4, -4, -4}; +static vec3_t flameChunkMaxs = { 4, 4, 4}; + +#define SQR_SIN_T 0.44 // ~ sqr(sin(20)) + +void Weapon_FlamethrowerFire( gentity_t *ent ) { + gentity_t *traceEnt; + vec3_t start; + vec3_t trace_start; + vec3_t trace_end; + trace_t trace; + + VectorCopy( ent->r.currentOrigin, start ); + start[2] += ent->client->ps.viewheight; + VectorCopy( start, trace_start ); + + VectorMA( start, -8, forward, start ); + VectorMA( start, 10, right, start ); + VectorMA( start, -6, up, start ); + + // prevent flame thrower cheat, run & fire while aiming at the ground, don't get hurt + // 72 total box height, 18 xy -> 77 trace radius (from view point towards the ground) is enough to cover the area around the feet + VectorMA( trace_start, 77.0, forward, trace_end ); + trap_Trace( &trace, trace_start, flameChunkMins, flameChunkMaxs, trace_end, ent->s.number, MASK_SHOT | MASK_WATER ); + if ( trace.fraction != 1.0 ) { + // additional checks to filter out false positives + if ( trace.endpos[2] > ( ent->r.currentOrigin[2] + ent->r.mins[2] - 8 ) && trace.endpos[2] < ent->r.currentOrigin[2] ) { + // trigger in a 21 radius around origin + trace_start[0] -= trace.endpos[0]; + trace_start[1] -= trace.endpos[1]; + if ( trace_start[0] * trace_start[0] + trace_start[1] * trace_start[1] < 441 ) { + // set self in flames + G_BurnMeGood( ent, ent ); + } + } + } + + traceEnt = fire_flamechunk( ent, start, forward ); +} + +//====================================================================== + + +/* +============== +AddLean + add leaning offset +============== +*/ +void AddLean( gentity_t *ent, vec3_t point ) { + if ( ent->client ) { + if ( ent->client->ps.leanf ) { + vec3_t right; + AngleVectors( ent->client->ps.viewangles, NULL, right, NULL ); + VectorMA( point, ent->client->ps.leanf, right, point ); + } + } +} + +/* +=============== +LogAccuracyHit +=============== +*/ +qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) { + if ( !target->takedamage ) { + return qfalse; + } + + if ( target == attacker ) { + return qfalse; + } + + if ( !target->client ) { + return qfalse; + } + + if ( !attacker->client ) { + return qfalse; + } + + if ( target->client->ps.stats[STAT_HEALTH] <= 0 ) { + return qfalse; + } + + if ( OnSameTeam( target, attacker ) ) { + return qfalse; + } + + return qtrue; +} + + +/* +=============== +CalcMuzzlePoint + +set muzzle location relative to pivoting eye +=============== +*/ +void CalcMuzzlePoint( gentity_t *ent, int weapon, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) { + VectorCopy( ent->r.currentOrigin, muzzlePoint ); + muzzlePoint[2] += ent->client->ps.viewheight; + // Ridah, this puts the start point outside the bounding box, isn't necessary +// VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + // done. + + // Ridah, offset for more realistic firing from actual gun position + //----(SA) modified + switch ( weapon ) // Ridah, changed this so I can predict weapons + { + case WP_PANZERFAUST: + if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE + VectorMA( muzzlePoint, 14, right, muzzlePoint ); //----(SA) new first person rl position + VectorMA( muzzlePoint, -10, up, muzzlePoint ); + } +// JPW NERVE -- pfaust shoots into walls too much so we moved it to shoulder mount + else { + VectorMA( muzzlePoint,10,right,muzzlePoint ); +// VectorMA(muzzlePoint,10,up,muzzlePoint); + } +// jpw + break; + case WP_ROCKET_LAUNCHER: + // VectorMA( muzzlePoint, 20, right, muzzlePoint ); + // Rafael: note to Sherman had to move this in so that it wouldnt + // spawn into a wall and detonate + +// VectorMA( muzzlePoint, 16, right, muzzlePoint ); + VectorMA( muzzlePoint, 14, right, muzzlePoint ); //----(SA) new first person rl position + break; + case WP_DYNAMITE: + case WP_DYNAMITE2: + case WP_GRENADE_PINEAPPLE: + case WP_GRENADE_LAUNCHER: + VectorMA( muzzlePoint, 20, right, muzzlePoint ); + break; + default: + VectorMA( muzzlePoint, 6, right, muzzlePoint ); + VectorMA( muzzlePoint, -4, up, muzzlePoint ); + break; + } + + // done. + + // (SA) actually, this is sort of moot right now since + // you're not allowed to fire when leaning. Leave in + // in case we decide to enable some lean-firing. + // (SA) works with gl now + //AddLean(ent, muzzlePoint); + + // snap to integer coordinates for more efficient network bandwidth usage + SnapVector( muzzlePoint ); +} + +// Rafael - for activate +void CalcMuzzlePointForActivate( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) { + + VectorCopy( ent->s.pos.trBase, muzzlePoint ); + muzzlePoint[2] += ent->client->ps.viewheight; + + AddLean( ent, muzzlePoint ); + + // snap to integer coordinates for more efficient network bandwidth usage + SnapVector( muzzlePoint ); +} +// done. + +// Ridah +void CalcMuzzlePoints( gentity_t *ent, int weapon ) { + vec3_t viewang; + + VectorCopy( ent->client->ps.viewangles, viewang ); + + if ( !( ent->r.svFlags & SVF_CASTAI ) ) { // non ai's take into account scoped weapon 'sway' (just another way aimspread is visualized/utilized) + float spreadfrac, phase; + + if ( weapon == WP_SNIPERRIFLE || weapon == WP_SNOOPERSCOPE ) { + spreadfrac = ent->client->currentAimSpreadScale; + + // rotate 'forward' vector by the sway + phase = level.time / 1000.0 * ZOOM_PITCH_FREQUENCY * M_PI * 2; + viewang[PITCH] += ZOOM_PITCH_AMPLITUDE * sin( phase ) * ( spreadfrac + ZOOM_PITCH_MIN_AMPLITUDE ); + + phase = level.time / 1000.0 * ZOOM_YAW_FREQUENCY * M_PI * 2; + viewang[YAW] += ZOOM_YAW_AMPLITUDE * sin( phase ) * ( spreadfrac + ZOOM_YAW_MIN_AMPLITUDE ); + } + } + + + // set aiming directions + AngleVectors( viewang, forward, right, up ); + +//----(SA) modified the muzzle stuff so that weapons that need to fire down a perfect trace +// straight out of the camera (SP5, Mauser right now) can have that accuracy, but +// weapons that need an offset effect (bazooka/grenade/etc.) can still look like +// they came out of the weap. + CalcMuzzlePointForActivate( ent, forward, right, up, muzzleTrace ); + CalcMuzzlePoint( ent, weapon, forward, right, up, muzzleEffect ); +} + +/* +=============== +FireWeapon +=============== +*/ +void FireWeapon( gentity_t *ent ) { + float aimSpreadScale; + vec3_t viewang; // JPW NERVE + + // Rafael mg42 + if ( ent->client->ps.persistant[PERS_HWEAPON_USE] && ent->active ) { + return; + } + + if ( ent->client->ps.powerups[PW_QUAD] ) { + s_quadFactor = g_quadfactor.value; + } else { + s_quadFactor = 1; + } + + // Ridah, need to call this for AI prediction also + CalcMuzzlePoints( ent, ent->s.weapon ); + + if ( g_userAim.integer ) { + aimSpreadScale = ent->client->currentAimSpreadScale; + // Ridah, add accuracy factor for AI + if ( ent->aiCharacter ) { + float aim_accuracy; + aim_accuracy = AICast_GetAccuracy( ent->s.number ); + if ( aim_accuracy <= 0 ) { + aim_accuracy = 0.0001; + } + aimSpreadScale = ( 1.0 - aim_accuracy ) * 2.0; + } else { + aimSpreadScale += 0.15f; // (SA) just adding a temp /maximum/ accuracy for player (this will be re-visited in greater detail :) + if ( aimSpreadScale > 1 ) { + aimSpreadScale = 1.0f; // still cap at 1.0 + } + } + } else { + aimSpreadScale = 1.0; + } + +// JPW NERVE -- EARLY OUT: if I'm in multiplayer and I have binocs, try to use artillery and then early return b4 switch statement + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + if ( ( ent->client->ps.eFlags & EF_ZOOMING ) && ( ent->client->ps.stats[STAT_KEYS] & ( 1 << INV_BINOCS ) ) && + ( ent->s.weapon != WP_SNIPERRIFLE ) ) { + + if ( !( ent->client->ps.leanf ) ) { + Weapon_Artillery( ent ); + } + + return; + } + } +// jpw + +// JPW NERVE -- if jumping, make aim bite ass + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + if ( ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) { + aimSpreadScale = 2.0f; + } + } +// jpw + + // fire the specific weapon + switch ( ent->s.weapon ) { + case WP_KNIFE: + case WP_KNIFE2: + Weapon_Knife( ent ); + break; + // NERVE - SMF + case WP_MEDKIT: + Weapon_Medic( ent ); + break; + case WP_PLIERS: + Weapon_Engineer( ent ); + break; + case WP_SMOKE_GRENADE: + if ( level.time - ent->client->ps.classWeaponTime >= g_LTChargeTime.integer * 0.5f ) { + if ( level.time - ent->client->ps.classWeaponTime > g_LTChargeTime.integer ) { + ent->client->ps.classWeaponTime = level.time - g_LTChargeTime.integer; + } + ent->client->ps.classWeaponTime = level.time; //+= g_LTChargeTime.integer*0.5f; FIXME later + weapon_grenadelauncher_fire( ent,WP_SMOKE_GRENADE ); + } + break; + // -NERVE - SMF + case WP_ARTY: + G_Printf( "calling artilery\n" ); + break; + case WP_MEDIC_SYRINGE: + Weapon_Syringe( ent ); + break; + case WP_AMMO: + Weapon_MagicAmmo( ent ); + break; +// jpw + case WP_LUGER: + Bullet_Fire( ent, LUGER_SPREAD * aimSpreadScale, LUGER_DAMAGE ); + break; + case WP_COLT: + Bullet_Fire( ent, COLT_SPREAD * aimSpreadScale, COLT_DAMAGE ); + break; + case WP_VENOM: + weapon_venom_fire( ent, qfalse, aimSpreadScale ); + break; + case WP_SNIPERRIFLE: + Bullet_Fire( ent, SNIPER_SPREAD * aimSpreadScale, SNIPER_DAMAGE ); +// JPW NERVE -- added muzzle flip in multiplayer + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + VectorCopy( ent->client->ps.viewangles,viewang ); +// viewang[PITCH] -= 6; // handled in clientthink instead + ent->client->sniperRifleMuzzleYaw = crandom() * 0.5; // used in clientthink + ent->client->sniperRifleFiredTime = level.time; + SetClientViewAngle( ent,viewang ); + } +// jpw + break; + case WP_MAUSER: + if ( g_gametype.integer != GT_SINGLE_PLAYER ) { + aimSpreadScale = 1.0; + } + Bullet_Fire( ent, MAUSER_SPREAD * aimSpreadScale, MAUSER_DAMAGE ); + break; + case WP_STEN: + Bullet_Fire( ent, STEN_SPREAD * aimSpreadScale, STEN_DAMAGE ); + break; + case WP_MP40: + Bullet_Fire( ent, MP40_SPREAD * aimSpreadScale, MP40_DAMAGE ); + break; + case WP_THOMPSON: + Bullet_Fire( ent, THOMPSON_SPREAD * aimSpreadScale, THOMPSON_DAMAGE ); + break; + case WP_PANZERFAUST: + ent->client->ps.classWeaponTime = level.time; // JPW NERVE + Weapon_RocketLauncher_Fire( ent ); + if ( ent->client && !( ent->r.svFlags & SVF_CASTAI ) ) { + vec3_t forward; + AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( ent->client->ps.velocity, -64, forward, ent->client->ps.velocity ); + } + break; + case WP_GRENADE_LAUNCHER: + case WP_GRENADE_PINEAPPLE: + case WP_DYNAMITE: + case WP_DYNAMITE2: + if ( ent->s.weapon == WP_DYNAMITE ) { + ent->client->ps.classWeaponTime = level.time; // JPW NERVE + } + weapon_grenadelauncher_fire( ent, ent->s.weapon ); + break; + case WP_FLAMETHROWER: + // RF, this is done client-side only now + Weapon_FlamethrowerFire( ent ); + break; + case WP_MORTAR: + break; + default: + break; + } +} + + + + + + + + + + + diff --git a/src/game/game.def b/src/game/game.def new file mode 100644 index 0000000..290f891 --- /dev/null +++ b/src/game/game.def @@ -0,0 +1,3 @@ +EXPORTS + dllEntry + vmMain diff --git a/src/game/game.vcproj b/src/game/game.vcproj new file mode 100644 index 0000000..3c5bdaf --- /dev/null +++ b/src/game/game.vcproj @@ -0,0 +1,1448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/game/q_math.c b/src/game/q_math.c new file mode 100644 index 0000000..01f03d7 --- /dev/null +++ b/src/game/q_math.c @@ -0,0 +1,1395 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// q_math.c -- stateless support routines that are included in each code module +#include "q_shared.h" + + +vec3_t vec3_origin = {0,0,0}; +vec3_t axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + +vec4_t colorBlack = {0, 0, 0, 1}; +vec4_t colorRed = {1, 0, 0, 1}; +vec4_t colorGreen = {0, 1, 0, 1}; +vec4_t colorBlue = {0, 0, 1, 1}; +vec4_t colorYellow = {1, 1, 0, 1}; +vec4_t colorMagenta = {1, 0, 1, 1}; +vec4_t colorCyan = {0, 1, 1, 1}; +vec4_t colorWhite = {1, 1, 1, 1}; +vec4_t colorLtGrey = {0.75, 0.75, 0.75, 1}; +vec4_t colorMdGrey = {0.5, 0.5, 0.5, 1}; +vec4_t colorDkGrey = {0.25, 0.25, 0.25, 1}; + +vec4_t g_color_table[8] = +{ + {0.0, 0.0, 0.0, 1.0}, + {1.0, 0.0, 0.0, 1.0}, + {0.0, 1.0, 0.0, 1.0}, + {1.0, 1.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 1.0}, + {0.0, 1.0, 1.0, 1.0}, + {1.0, 0.0, 1.0, 1.0}, + {1.0, 1.0, 1.0, 1.0}, +}; + + +vec3_t bytedirs[NUMVERTEXNORMALS] = +{ + {-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} +}; + +//============================================================== + +int Q_rand( int *seed ) { + *seed = ( 69069 * *seed + 1 ); + return *seed; +} + +float Q_random( int *seed ) { + return ( Q_rand( seed ) & 0xffff ) / (float)0x10000; +} + +float Q_crandom( int *seed ) { + return 2.0 * ( Q_random( seed ) - 0.5 ); +} + + +//======================================================= + +signed char ClampChar( int i ) { + if ( i < -128 ) { + return -128; + } + if ( i > 127 ) { + return 127; + } + return i; +} + +signed short ClampShort( int i ) { + if ( i < -32768 ) { + return -32768; + } + if ( i > 0x7fff ) { + return 0x7fff; + } + return i; +} + + +// this isn't a real cheap function to call! +int DirToByte( vec3_t dir ) { + int i, best; + float d, bestd; + + if ( !dir ) { + return 0; + } + + bestd = 0; + best = 0; + for ( i = 0 ; i < NUMVERTEXNORMALS ; i++ ) + { + d = DotProduct( dir, bytedirs[i] ); + if ( d > bestd ) { + bestd = d; + best = i; + } + } + + return best; +} + +void ByteToDir( int b, vec3_t dir ) { + if ( b < 0 || b >= NUMVERTEXNORMALS ) { + VectorCopy( vec3_origin, dir ); + return; + } + VectorCopy( bytedirs[b], dir ); +} + + +unsigned ColorBytes3( float r, float g, float b ) { + unsigned i; + + ( (byte *)&i )[0] = r * 255; + ( (byte *)&i )[1] = g * 255; + ( (byte *)&i )[2] = b * 255; + + return i; +} + +unsigned ColorBytes4( float r, float g, float b, float a ) { + unsigned i; + + ( (byte *)&i )[0] = r * 255; + ( (byte *)&i )[1] = g * 255; + ( (byte *)&i )[2] = b * 255; + ( (byte *)&i )[3] = a * 255; + + return i; +} + +float NormalizeColor( const vec3_t in, vec3_t out ) { + float max; + + max = in[0]; + if ( in[1] > max ) { + max = in[1]; + } + if ( in[2] > max ) { + max = in[2]; + } + + if ( !max ) { + VectorClear( out ); + } else { + out[0] = in[0] / max; + out[1] = in[1] / max; + out[2] = in[2] / max; + } + return max; +} + + +/* +===================== +PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) { + vec3_t d1, d2; + + VectorSubtract( b, a, d1 ); + VectorSubtract( c, a, d2 ); + CrossProduct( d2, d1, plane ); + if ( VectorNormalize( plane ) == 0 ) { + return qfalse; + } + + plane[3] = DotProduct( a, plane ); + return qtrue; +} + +/* +=============== +RotatePointAroundVector + +This is not implemented very well... +=============== +*/ +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, + float degrees ) { + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + float rad; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + rad = DEG2RAD( degrees ); + zrot[0][0] = cos( rad ); + zrot[0][1] = sin( rad ); + zrot[1][0] = -sin( rad ); + zrot[1][1] = cos( rad ); + + MatrixMultiply( m, zrot, tmpmat ); + MatrixMultiply( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} + +/* +=============== +RotateAroundDirection +=============== +*/ +void RotateAroundDirection( vec3_t axis[3], float yaw ) { + + // create an arbitrary axis[1] + PerpendicularVector( axis[1], axis[0] ); + + // rotate it around axis[0] by yaw + if ( yaw ) { + vec3_t temp; + + VectorCopy( axis[1], temp ); + RotatePointAroundVector( axis[1], axis[0], temp, yaw ); + } + + // cross to get axis[2] + CrossProduct( axis[0], axis[1], axis[2] ); +} + + + +void vectoangles( const vec3_t value1, vec3_t angles ) { + float forward; + float yaw, pitch; + + if ( value1[1] == 0 && value1[0] == 0 ) { + yaw = 0; + if ( value1[2] > 0 ) { + pitch = 90; + } else { + pitch = 270; + } + } else { + if ( value1[0] ) { + yaw = ( atan2( value1[1], value1[0] ) * 180 / M_PI ); + } else if ( value1[1] > 0 ) { + yaw = 90; + } else { + yaw = 270; + } + if ( yaw < 0 ) { + yaw += 360; + } + + forward = sqrt( value1[0] * value1[0] + value1[1] * value1[1] ); + pitch = ( atan2( value1[2], forward ) * 180 / M_PI ); + if ( pitch < 0 ) { + pitch += 360; + } + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + + +/* +================= +AnglesToAxis +================= +*/ +void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ) { + vec3_t right; + + // angle vectors returns "right" instead of "y axis" + AngleVectors( angles, axis[0], right, axis[2] ); + VectorSubtract( vec3_origin, right, axis[1] ); +} + +void AxisClear( vec3_t axis[3] ) { + axis[0][0] = 1; + axis[0][1] = 0; + axis[0][2] = 0; + axis[1][0] = 0; + axis[1][1] = 1; + axis[1][2] = 0; + axis[2][0] = 0; + axis[2][1] = 0; + axis[2][2] = 1; +} + +void AxisCopy( vec3_t in[3], vec3_t out[3] ) { + VectorCopy( in[0], out[0] ); + VectorCopy( in[1], out[1] ); + VectorCopy( in[2], out[2] ); +} + +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]; +} + +/* +================ +MakeNormalVectors + +Given a normalized forward vector, create two +other perpendicular vectors +================ +*/ +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up ) { + float d; + + // this rotate and negate guarantees a vector + // not colinear with the original + right[1] = -forward[0]; + right[2] = forward[1]; + right[0] = forward[2]; + + d = DotProduct( right, forward ); + VectorMA( right, -d, forward, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); +} + + +void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out ) { + out[0] = DotProduct( in, matrix[0] ); + out[1] = DotProduct( in, matrix[1] ); + out[2] = DotProduct( in, matrix[2] ); +} + +//============================================================================ + +/* +** float q_rsqrt( float number ) +*/ +float Q_rsqrt( float number ) { + long i; + float x2, y; + const float threehalfs = 1.5F; + + x2 = number * 0.5F; + y = number; + i = *( long * ) &y; // evil floating point bit level hacking + i = 0x5f3759df - ( i >> 1 ); // what the fuck? + y = *( float * ) &i; + y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration +// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed + + return y; +} + +float Q_fabs( float f ) { + int tmp = *( int * ) &f; + tmp &= 0x7FFFFFFF; + return *( float * ) &tmp; +} + +//============================================================ + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle( float from, float to, float frac ) { + float a; + + if ( to - from > 180 ) { + to -= 360; + } + if ( to - from < -180 ) { + to += 360; + } + a = from + frac * ( to - from ); + + return a; +} + +/* +================= +LerpPosition + +================= +*/ + +void LerpPosition( vec3_t start, vec3_t end, float frac, vec3_t out ) { + vec3_t dist; + + VectorSubtract( end, start, dist ); + VectorMA( start, frac, dist, out ); +} + +/* +================= +AngleSubtract + +Always returns a value from -180 to 180 +================= +*/ +float AngleSubtract( float a1, float a2 ) { + float a; + + a = a1 - a2; + while ( a > 180 ) { + a -= 360; + } + while ( a < -180 ) { + a += 360; + } + return a; +} + + +void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ) { + v3[0] = AngleSubtract( v1[0], v2[0] ); + v3[1] = AngleSubtract( v1[1], v2[1] ); + v3[2] = AngleSubtract( v1[2], v2[2] ); +} + + +float AngleMod( float a ) { + a = ( 360.0 / 65536 ) * ( (int)( a * ( 65536 / 360.0 ) ) & 65535 ); + return a; +} + + +/* +================= +AngleNormalize360 + +returns angle normalized to the range [0 <= angle < 360] +================= +*/ +float AngleNormalize360( float angle ) { + return ( 360.0 / 65536 ) * ( (int)( angle * ( 65536 / 360.0 ) ) & 65535 ); +} + + +/* +================= +AngleNormalize180 + +returns angle normalized to the range [-180 < angle <= 180] +================= +*/ +float AngleNormalize180( float angle ) { + angle = AngleNormalize360( angle ); + if ( angle > 180.0 ) { + angle -= 360.0; + } + return angle; +} + + +/* +================= +AngleDelta + +returns the normalized delta from angle1 to angle2 +================= +*/ +float AngleDelta( float angle1, float angle2 ) { + return AngleNormalize180( angle1 - angle2 ); +} + + +//============================================================ + + +/* +================= +SetPlaneSignbits +================= +*/ +void SetPlaneSignbits( cplane_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 << j; + } + } + out->signbits = bits; +} + + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; + + return sides; +} + +================== +*/ +#if !( defined __linux__ && defined __i386__ && !defined C_ONLY ) +#if defined __LCC__ || defined C_ONLY || !id386 + +int BoxOnPlaneSide( vec3_t emins, vec3_t emaxs, struct cplane_s *p ) { + float dist1, dist2; + int sides; + +// 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; + } + +// 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 + break; + } + + sides = 0; + if ( dist1 >= p->dist ) { + sides = 1; + } + if ( dist2 < p->dist ) { + sides |= 2; + } + + return sides; +} +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide( vec3_t emins, vec3_t emaxs, struct cplane_s *p ) { + static int bops_initialized; + static int Ljmptab[8]; + + __asm { + + push ebx + + cmp bops_initialized, 1 + je initialized + mov bops_initialized, 1 + + mov Ljmptab[0 * 4], offset Lcase0 + mov Ljmptab[1 * 4], offset Lcase1 + mov Ljmptab[2 * 4], offset Lcase2 + mov Ljmptab[3 * 4], offset Lcase3 + mov Ljmptab[4 * 4], offset Lcase4 + mov Ljmptab[5 * 4], offset Lcase5 + mov Ljmptab[6 * 4], offset Lcase6 + mov Ljmptab[7 * 4], offset Lcase7 + +initialized: + + mov edx,dword ptr[4 + 12 + esp] + mov ecx,dword ptr[4 + 4 + esp] + xor eax,eax + mov ebx,dword ptr[4 + 8 + esp] + mov al,byte ptr[17 + edx] + cmp al,8 + jge Lerror + fld dword ptr[0 + edx] + fld st( 0 ) + jmp dword ptr[Ljmptab + eax * 4] + Lcase0 : + fmul dword ptr[ebx] + fld dword ptr[0 + 4 + edx] + fxch st( 2 ) + fmul dword ptr[ecx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[4 + ebx] + fld dword ptr[0 + 8 + edx] + fxch st( 2 ) + fmul dword ptr[4 + ecx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[8 + ebx] + fxch st( 5 ) + faddp st( 3 ),st( 0 ) + fmul dword ptr[8 + ecx] + fxch st( 1 ) + faddp st( 3 ),st( 0 ) + fxch st( 3 ) + faddp st( 2 ),st( 0 ) + jmp LSetSides + Lcase1 : + fmul dword ptr[ecx] + fld dword ptr[0 + 4 + edx] + fxch st( 2 ) + fmul dword ptr[ebx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[4 + ebx] + fld dword ptr[0 + 8 + edx] + fxch st( 2 ) + fmul dword ptr[4 + ecx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[8 + ebx] + fxch st( 5 ) + faddp st( 3 ),st( 0 ) + fmul dword ptr[8 + ecx] + fxch st( 1 ) + faddp st( 3 ),st( 0 ) + fxch st( 3 ) + faddp st( 2 ),st( 0 ) + jmp LSetSides + Lcase2 : + fmul dword ptr[ebx] + fld dword ptr[0 + 4 + edx] + fxch st( 2 ) + fmul dword ptr[ecx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[4 + ecx] + fld dword ptr[0 + 8 + edx] + fxch st( 2 ) + fmul dword ptr[4 + ebx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[8 + ebx] + fxch st( 5 ) + faddp st( 3 ),st( 0 ) + fmul dword ptr[8 + ecx] + fxch st( 1 ) + faddp st( 3 ),st( 0 ) + fxch st( 3 ) + faddp st( 2 ),st( 0 ) + jmp LSetSides + Lcase3 : + fmul dword ptr[ecx] + fld dword ptr[0 + 4 + edx] + fxch st( 2 ) + fmul dword ptr[ebx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[4 + ecx] + fld dword ptr[0 + 8 + edx] + fxch st( 2 ) + fmul dword ptr[4 + ebx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[8 + ebx] + fxch st( 5 ) + faddp st( 3 ),st( 0 ) + fmul dword ptr[8 + ecx] + fxch st( 1 ) + faddp st( 3 ),st( 0 ) + fxch st( 3 ) + faddp st( 2 ),st( 0 ) + jmp LSetSides + Lcase4 : + fmul dword ptr[ebx] + fld dword ptr[0 + 4 + edx] + fxch st( 2 ) + fmul dword ptr[ecx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[4 + ebx] + fld dword ptr[0 + 8 + edx] + fxch st( 2 ) + fmul dword ptr[4 + ecx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[8 + ecx] + fxch st( 5 ) + faddp st( 3 ),st( 0 ) + fmul dword ptr[8 + ebx] + fxch st( 1 ) + faddp st( 3 ),st( 0 ) + fxch st( 3 ) + faddp st( 2 ),st( 0 ) + jmp LSetSides + Lcase5 : + fmul dword ptr[ecx] + fld dword ptr[0 + 4 + edx] + fxch st( 2 ) + fmul dword ptr[ebx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[4 + ebx] + fld dword ptr[0 + 8 + edx] + fxch st( 2 ) + fmul dword ptr[4 + ecx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[8 + ecx] + fxch st( 5 ) + faddp st( 3 ),st( 0 ) + fmul dword ptr[8 + ebx] + fxch st( 1 ) + faddp st( 3 ),st( 0 ) + fxch st( 3 ) + faddp st( 2 ),st( 0 ) + jmp LSetSides + Lcase6 : + fmul dword ptr[ebx] + fld dword ptr[0 + 4 + edx] + fxch st( 2 ) + fmul dword ptr[ecx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[4 + ecx] + fld dword ptr[0 + 8 + edx] + fxch st( 2 ) + fmul dword ptr[4 + ebx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[8 + ecx] + fxch st( 5 ) + faddp st( 3 ),st( 0 ) + fmul dword ptr[8 + ebx] + fxch st( 1 ) + faddp st( 3 ),st( 0 ) + fxch st( 3 ) + faddp st( 2 ),st( 0 ) + jmp LSetSides + Lcase7 : + fmul dword ptr[ecx] + fld dword ptr[0 + 4 + edx] + fxch st( 2 ) + fmul dword ptr[ebx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[4 + ecx] + fld dword ptr[0 + 8 + edx] + fxch st( 2 ) + fmul dword ptr[4 + ebx] + fxch st( 2 ) + fld st( 0 ) + fmul dword ptr[8 + ecx] + fxch st( 5 ) + faddp st( 3 ),st( 0 ) + fmul dword ptr[8 + ebx] + fxch st( 1 ) + faddp st( 3 ),st( 0 ) + fxch st( 3 ) + faddp st( 2 ),st( 0 ) + LSetSides : + faddp st( 2 ),st( 0 ) + fcomp dword ptr[12 + edx] + xor ecx,ecx + fnstsw ax + fcomp dword ptr[12 + edx] + and ah,1 + xor ah,1 + add cl,ah + fnstsw ax + and ah,1 + add ah,ah + add cl,ah + pop ebx + mov eax,ecx + ret + Lerror : + int 3 + } +} +#pragma warning( default: 4035 ) + +#endif +#endif + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) { + int i; + vec3_t corner; + float a, b; + + for ( i = 0 ; i < 3 ; i++ ) { + a = fabs( mins[i] ); + b = fabs( maxs[i] ); + corner[i] = a > b ? a : b; + } + + return VectorLength( corner ); +} + + +void ClearBounds( vec3_t mins, vec3_t maxs ) { + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) { + if ( v[0] < mins[0] ) { + mins[0] = v[0]; + } + if ( v[0] > maxs[0] ) { + maxs[0] = v[0]; + } + + if ( v[1] < mins[1] ) { + mins[1] = v[1]; + } + if ( v[1] > maxs[1] ) { + maxs[1] = v[1]; + } + + if ( v[2] < mins[2] ) { + mins[2] = v[2]; + } + if ( v[2] > maxs[2] ) { + maxs[2] = v[2]; + } +} + + +int VectorCompare( const vec3_t v1, const vec3_t v2 ) { + if ( v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2] ) { + return 0; + } + + return 1; +} + + +vec_t VectorNormalize( vec3_t v ) { + float length, ilength; + + length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; + length = sqrt( length ); + + if ( length ) { + ilength = 1 / length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; +} + +// +// fast vector normalize routine that does not check to make sure +// that length != 0, nor does it return length +// +void VectorNormalizeFast( vec3_t v ) { + float ilength; + + ilength = Q_rsqrt( DotProduct( v, v ) ); + + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; +} + +vec_t VectorNormalize2( const vec3_t v, vec3_t out ) { + float length, ilength; + + length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; + length = sqrt( length ); + + if ( length ) { + ilength = 1 / length; + out[0] = v[0] * ilength; + out[1] = v[1] * ilength; + out[2] = v[2] * ilength; + } else { + VectorClear( out ); + } + + return length; + +} + +void _VectorMA( const vec3_t veca, float scale, const 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( const vec3_t v1, const vec3_t v2 ) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} + +void _VectorSubtract( const vec3_t veca, const 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( const vec3_t veca, const 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( const vec3_t in, vec3_t out ) { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale( const vec3_t in, vec_t scale, vec3_t out ) { + out[0] = in[0] * scale; + out[1] = in[1] * scale; + out[2] = in[2] * scale; +} + +void CrossProduct( const vec3_t v1, const 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( const vec3_t v ) { + return sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] ); +} + +vec_t VectorLengthSquared( const vec3_t v ) { + return ( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] ); +} + +vec_t Distance( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract( p2, p1, v ); + return VectorLength( v ); +} + +vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract( p2, p1, v ); + return v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; +} + + +void VectorInverse( vec3_t v ) { + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ) { + out[0] = in[0] * scale; + out[1] = in[1] * scale; + out[2] = in[2] * scale; + out[3] = in[3] * scale; +} + + +int Q_log2( int val ) { + int answer; + + answer = 0; + while ( ( val >>= 1 ) != 0 ) { + answer++; + } + return answer; +} + + + +/* +================= +PlaneTypeForNormal +================= +*/ +/* +int PlaneTypeForNormal (vec3_t normal) { + if ( normal[0] == 1.0 ) + return PLANE_X; + if ( normal[1] == 1.0 ) + return PLANE_Y; + if ( normal[2] == 1.0 ) + return PLANE_Z; + + return PLANE_NON_AXIAL; +} +*/ + + +/* +================ +MatrixMultiply +================ +*/ +void MatrixMultiply( 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]; +} + + +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ) { + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + 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 ); + + if ( forward ) { + forward[0] = cp * cy; + forward[1] = cp * sy; + forward[2] = -sp; + } + if ( right ) { + right[0] = ( -1 * sr * sp * cy + - 1 * cr * -sy ); + right[1] = ( -1 * sr * sp * sy + - 1 * cr * cy ); + right[2] = -1 * sr * cp; + } + if ( up ) { + up[0] = ( cr * sp * cy + - sr * -sy ); + up[1] = ( cr * sp * sy + - sr * cy ); + up[2] = cr * cp; + } +} + +/* +** 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 ); +} + +// Ridah +/* +================= +GetPerpendicularViewVector + + Used to find an "up" vector for drawing a sprite so that it always faces the view as best as possible +================= +*/ +void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, const vec3_t p2, vec3_t up ) { + vec3_t v1, v2; + + VectorSubtract( point, p1, v1 ); + VectorNormalize( v1 ); + + VectorSubtract( point, p2, v2 ); + VectorNormalize( v2 ); + + CrossProduct( v1, v2, up ); + VectorNormalize( up ); +} + +/* +================ +ProjectPointOntoVector +================ +*/ +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) { + vec3_t pVec, vec; + + VectorSubtract( point, vStart, pVec ); + VectorSubtract( vEnd, vStart, vec ); + VectorNormalize( vec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); +} + +float vectoyaw( const vec3_t vec ) { + float yaw; + + if ( vec[YAW] == 0 && vec[PITCH] == 0 ) { + yaw = 0; + } else { + if ( vec[PITCH] ) { + yaw = ( atan2( vec[YAW], vec[PITCH] ) * 180 / M_PI ); + } else if ( vec[YAW] > 0 ) { + yaw = 90; + } else { + yaw = 270; + } + if ( yaw < 0 ) { + yaw += 360; + } + } + + return yaw; +} + +/* +================= +AxisToAngles + + Used to convert the MD3 tag axis to MDC tag angles, which are much smaller + + This doesn't have to be fast, since it's only used for conversion in utils, try to avoid + using this during gameplay +================= +*/ +void AxisToAngles( vec3_t axis[3], vec3_t angles ) { + vec3_t right, roll_angles, tvec; + + // first get the pitch and yaw from the forward vector + vectoangles( axis[0], angles ); + + // now get the roll from the right vector + VectorCopy( axis[1], right ); + // get the angle difference between the tmpAxis[2] and axis[2] after they have been reverse-rotated + RotatePointAroundVector( tvec, axisDefault[2], right, -angles[YAW] ); + RotatePointAroundVector( right, axisDefault[1], tvec, -angles[PITCH] ); + // now find the angles, the PITCH is effectively our ROLL + vectoangles( right, roll_angles ); + roll_angles[PITCH] = AngleNormalize180( roll_angles[PITCH] ); + // if the yaw is more than 90 degrees difference, we should adjust the pitch + if ( DotProduct( right, axisDefault[1] ) < 0 ) { + if ( roll_angles[PITCH] < 0 ) { + roll_angles[PITCH] = -90 + ( -90 - roll_angles[PITCH] ); + } else { + roll_angles[PITCH] = 90 + ( 90 - roll_angles[PITCH] ); + } + } + + angles[ROLL] = -roll_angles[PITCH]; +} + +float VectorDistance( vec3_t v1, vec3_t v2 ) { + vec3_t dir; + + VectorSubtract( v2, v1, dir ); + return VectorLength( dir ); +} +// done. diff --git a/src/game/q_shared.c b/src/game/q_shared.c new file mode 100644 index 0000000..4947fa9 --- /dev/null +++ b/src/game/q_shared.c @@ -0,0 +1,1353 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// q_shared.c -- stateless support routines that are included in each code dll +#include "q_shared.h" + +// os x game bundles have no standard library links, and the defines are not always defined! + +#ifdef MACOS_X +int qmax( int x, int y ) { + return ( ( ( x ) > ( y ) ) ? ( x ) : ( y ) ); +} + +int qmin( int x, int y ) { + return ( ( ( x ) < ( y ) ) ? ( x ) : ( y ) ); +} +#endif + +float Com_Clamp( float min, float max, float value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + + +/* +============ +COM_SkipPath +============ +*/ +char *COM_SkipPath( char *pathname ) { + char *last; + + last = pathname; + while ( *pathname ) + { + if ( *pathname == '/' ) { + last = pathname + 1; + } + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension( const char *in, char *out ) { + while ( *in && *in != '.' ) { + *out++ = *in++; + } + *out = 0; +} + +/* +============ +COM_StripExtension2 +a safer version +============ +*/ +void COM_StripExtension2( const char *in, char *out, int destsize ) { + int len = 0; + while ( len < destsize - 1 && *in && *in != '.' ) { + *out++ = *in++; + len++; + } + *out = 0; +} + +void COM_StripFilename( char *in, char *out ) { + char *end; + Q_strncpyz( out, in, strlen( in ) ); + end = COM_SkipPath( out ); + *end = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension( char *path, int maxSize, const char *extension ) { + char oldPath[MAX_QPATH]; + char *src; + +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen( path ) - 1; + + while ( *src != '/' && src != path ) { + if ( *src == '.' ) { + return; // it has an extension + } + src--; + } + + Q_strncpyz( oldPath, path, sizeof( oldPath ) ); + Com_sprintf( path, maxSize, "%s%s", oldPath, extension ); +} + +//============================================================================ +/* +================== +COM_BitCheck + + Allows bit-wise checks on arrays with more than one item (> 32 bits) +================== +*/ +qboolean COM_BitCheck( const int array[], int bitNum ) { + int i; + + i = 0; + while ( bitNum > 31 ) { + i++; + bitNum -= 32; + } + + return ( ( array[i] & ( 1 << bitNum ) ) != 0 ); // (SA) heh, whoops. :) +} + +/* +================== +COM_BitSet + + Allows bit-wise SETS on arrays with more than one item (> 32 bits) +================== +*/ +void COM_BitSet( int array[], int bitNum ) { + int i; + + i = 0; + while ( bitNum > 31 ) { + i++; + bitNum -= 32; + } + + array[i] |= ( 1 << bitNum ); +} + +/* +================== +COM_BitClear + + Allows bit-wise CLEAR on arrays with more than one item (> 32 bits) +================== +*/ +void COM_BitClear( int array[], int bitNum ) { + int i; + + i = 0; + while ( bitNum > 31 ) { + i++; + bitNum -= 32; + } + + array[i] &= ~( 1 << bitNum ); +} +//============================================================================ + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +static short ( *_BigShort )( short l ); +static short ( *_LittleShort )( short l ); +static int ( *_BigLong )( int l ); +static int ( *_LittleLong )( int l ); +static qint64 ( *_BigLong64 )( qint64 l ); +static qint64 ( *_LittleLong64 )( qint64 l ); +static float ( *_BigFloat )( float l ); +static float ( *_LittleFloat )( float l ); + +#if __MACOS__ +short BigShort( short l ) {return l;} +short LittleShort( short l ) {return __lhbrx( &l, 0 );} +int BigLong( int l ) {return l;} +int LittleLong( int l ) {return __lwbrx( &l, 0 );} +qint64 BigLong64( qint64 l ) {return _BigLong64( l );} +qint64 LittleLong64( qint64 l ) {return _LittleLong64( l );} +float BigFloat( float l ) {return l;} +float LittleFloat( float l ) {return _LittleFloat( l );} +#else +short BigShort( short l ) {return _BigShort( l );} +short LittleShort( short l ) {return _LittleShort( l );} +int BigLong( int l ) {return _BigLong( l );} +int LittleLong( int l ) {return _LittleLong( l );} +qint64 BigLong64( qint64 l ) {return _BigLong64( l );} +qint64 LittleLong64( qint64 l ) {return _LittleLong64( l );} +float BigFloat( float l ) {return _BigFloat( l );} +float LittleFloat( float l ) {return _LittleFloat( l );} +#endif + +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; +} + +qint64 Long64Swap( qint64 ll ) { + qint64 result; + + result.b0 = ll.b7; + result.b1 = ll.b6; + result.b2 = ll.b5; + result.b3 = ll.b4; + result.b4 = ll.b3; + result.b5 = ll.b2; + result.b6 = ll.b1; + result.b7 = ll.b0; + + return result; +} + +qint64 Long64NoSwap( qint64 ll ) { + return ll; +} + +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; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init( void ) { + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1 ) { + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigLong64 = Long64Swap; + _LittleLong64 = Long64NoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } else + { + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigLong64 = Long64NoSwap; + _LittleLong64 = Long64Swap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + + +/* +============================================================================ + +PARSING + +============================================================================ +*/ + +static char com_token[MAX_TOKEN_CHARS]; +static char com_parsename[MAX_TOKEN_CHARS]; +static int com_lines; + +static int backup_lines; +static char *backup_text; + +void COM_BeginParseSession( const char *name ) { + com_lines = 0; + Com_sprintf( com_parsename, sizeof( com_parsename ), "%s", name ); +} + +void COM_BackupParseSession( char **data_p ) { + backup_lines = com_lines; + backup_text = *data_p; +} + +void COM_RestoreParseSession( char **data_p ) { + com_lines = backup_lines; + *data_p = backup_text; +} + +void COM_SetCurrentParseLine( int line ) { + com_lines = line; +} + +int COM_GetCurrentParseLine( void ) { + return com_lines; +} + +char *COM_Parse( char **data_p ) { + return COM_ParseExt( data_p, qtrue ); +} + +void COM_ParseError( char *format, ... ) { + va_list argptr; + static char string[4096]; + + va_start( argptr, format ); + Q_vsnprintf( string, sizeof( string ), format, argptr ); + va_end( argptr ); + + Com_Printf( "ERROR: %s, line %d: %s\n", com_parsename, com_lines, string ); +} + +void COM_ParseWarning( char *format, ... ) { + va_list argptr; + static char string[4096]; + + va_start( argptr, format ); + Q_vsnprintf( string, sizeof( string ), format, argptr ); + va_end( argptr ); + + Com_Printf( "WARNING: %s, line %d: %s\n", com_parsename, com_lines, string ); +} + +/* +============== +COM_Parse + +Parse a token out of a string +Will never return NULL, just empty strings + +If "allowLineBreaks" is qtrue then an empty +string will be returned if the next token is +a newline. +============== +*/ +static char *SkipWhitespace( char *data, qboolean *hasNewLines ) { + int c; + + while ( ( c = *data ) <= ' ' ) { + if ( !c ) { + return NULL; + } + if ( c == '\n' ) { + com_lines++; + *hasNewLines = qtrue; + } + data++; + } + + return data; +} + +int COM_Compress( char *data_p ) { + char *datai, *datao; + int c, pc, size; + qboolean ws = qfalse; + + size = 0; + pc = 0; + datai = datao = data_p; + if ( datai ) { + while ( ( c = *datai ) != 0 ) { + if ( c == 13 || c == 10 ) { + *datao = c; + datao++; + ws = qfalse; + pc = c; + datai++; + size++; + // skip double slash comments + } else if ( c == '/' && datai[1] == '/' ) { + while ( *datai && *datai != '\n' ) { + datai++; + } + ws = qfalse; + // skip /* */ comments + } else if ( c == '/' && datai[1] == '*' ) { + while ( *datai && ( *datai != '*' || datai[1] != '/' ) ) + { + datai++; + } + if ( *datai ) { + datai += 2; + } + ws = qfalse; + } else { + if ( ws ) { + *datao = ' '; + datao++; + } + *datao = c; + datao++; + datai++; + ws = qfalse; + pc = c; + size++; + } + } + } + *datao = 0; + return size; +} + +char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) { + int c = 0, len; + qboolean hasNewLines = qfalse; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + // RF, backup the session data so we can unget easily + COM_BackupParseSession( data_p ); + + while ( 1 ) + { + // skip whitespace + data = SkipWhitespace( data, &hasNewLines ); + if ( !data ) { + *data_p = NULL; + return com_token; + } + if ( hasNewLines && !allowLineBreaks ) { + *data_p = data; + return com_token; + } + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) { + data += 2; + while ( *data && *data != '\n' ) { + data++; + } + } + // skip /* */ comments + else if ( c == '/' && data[1] == '*' ) { + data += 2; + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) { + data += 2; + } + } else + { + break; + } + } + + // handle quoted strings + if ( c == '\"' ) { + data++; + while ( 1 ) + { + c = *data++; + if ( c == '\"' || !c ) { + com_token[len] = 0; + *data_p = ( char * ) data; + return com_token; + } + if ( len < MAX_TOKEN_CHARS ) { + com_token[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if ( len < MAX_TOKEN_CHARS ) { + com_token[len] = c; + len++; + } + data++; + c = *data; + if ( c == '\n' ) { + com_lines++; + } + } while ( c > 32 ); + + if ( len == MAX_TOKEN_CHARS ) { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + + + + +/* +================== +COM_MatchToken +================== +*/ +void COM_MatchToken( char **buf_p, char *match ) { + char *token; + + token = COM_Parse( buf_p ); + if ( strcmp( token, match ) ) { + Com_Error( ERR_DROP, "MatchToken: %s != %s", token, match ); + } +} + + +/* +================= +SkipBracedSection_Depth + +================= +*/ +void SkipBracedSection_Depth( char **program, int depth ) { + char *token; + + do { + token = COM_ParseExt( program, qtrue ); + if ( token[1] == 0 ) { + if ( token[0] == '{' ) { + depth++; + } else if ( token[0] == '}' ) { + depth--; + } + } + } while ( depth && *program ); +} + +/* +================= +SkipBracedSection + +The next token should be an open brace. +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +void SkipBracedSection( char **program ) { + SkipBracedSection_Depth( program, 0 ); +} + +/* +================= +SkipRestOfLine +================= +*/ +void SkipRestOfLine( char **data ) { + char *p; + int c; + + p = *data; + while ( ( c = *p++ ) != 0 ) { + if ( c == '\n' ) { + com_lines++; + break; + } + } + + *data = p; +} + + +void Parse1DMatrix( char **buf_p, int x, float *m ) { + char *token; + int i; + + COM_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < x ; i++ ) { + token = COM_Parse( buf_p ); + m[i] = atof( token ); + } + + COM_MatchToken( buf_p, ")" ); +} + +void Parse2DMatrix( char **buf_p, int y, int x, float *m ) { + int i; + + COM_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < y ; i++ ) { + Parse1DMatrix( buf_p, x, m + i * x ); + } + + COM_MatchToken( buf_p, ")" ); +} + +void Parse3DMatrix( char **buf_p, int z, int y, int x, float *m ) { + int i; + + COM_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < z ; i++ ) { + Parse2DMatrix( buf_p, y, x, m + i * x * y ); + } + + COM_MatchToken( buf_p, ")" ); +} + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +int Q_isprint( int c ) { + if ( c >= 0x20 && c <= 0x7E ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_islower( int c ) { + if ( c >= 'a' && c <= 'z' ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_isupper( int c ) { + if ( c >= 'A' && c <= 'Z' ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_isalpha( int c ) { + if ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) ) { + return ( 1 ); + } + return ( 0 ); +} + +char* Q_strrchr( const char* string, int c ) { + char cc = c; + char *s; + char *sp = (char *)0; + + s = (char*)string; + + while ( *s ) + { + if ( *s == cc ) { + sp = s; + } + s++; + } + if ( cc == 0 ) { + sp = s; + } + + return sp; +} + +/* +============= +Q_strncpyz + +Safe strncpy that ensures a trailing zero +============= +*/ +void Q_strncpyz( char *dest, const char *src, int destsize ) { + if ( !src ) { + Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" ); + } + if ( destsize < 1 ) { + Com_Error( ERR_FATAL,"Q_strncpyz: destsize < 1" ); + } + + strncpy( dest, src, destsize - 1 ); + dest[destsize - 1] = 0; +} + +int Q_stricmpn( const char *s1, const char *s2, int n ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + if ( c1 != c2 ) { + return c1 < c2 ? -1 : 1; + } + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_strncmp( const char *s1, const char *s2, int n ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + if ( c1 != c2 ) { + return c1 < c2 ? -1 : 1; + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_stricmp( const char *s1, const char *s2 ) { + return ( s1 && s2 ) ? Q_stricmpn( s1, s2, 99999 ) : -1; +} + + +char *Q_strlwr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = tolower( *s ); + s++; + } + return s1; +} + +char *Q_strupr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = toupper( *s ); + s++; + } + return s1; +} + + +// never goes past bounds or leaves without a terminating 0 +void Q_strcat( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + Com_Error( ERR_FATAL, "Q_strcat: already overflowed" ); + } + Q_strncpyz( dest + l1, src, size - l1 ); +} + + +int Q_PrintStrlen( const char *string ) { + int len; + const char *p; + + if ( !string ) { + return 0; + } + + len = 0; + p = string; + while ( *p ) { + if ( Q_IsColorString( p ) ) { + p += 2; + continue; + } + p++; + len++; + } + + return len; +} + + +char *Q_CleanStr( char *string ) { + char* d; + char* s; + int c; + + s = string; + d = string; + while ( ( c = *s ) != 0 ) { + if ( Q_IsColorString( s ) ) { + s++; + } else if ( c >= 0x20 && c <= 0x7E ) { + *d++ = c; + } + s++; + } + *d = '\0'; + + return string; +} + +void QDECL Com_sprintf( char *dest, int size, const char *fmt, ... ) { + int len; + va_list argptr; + + /* + C99 for vsnprintf: + return the number of characters (excluding the trailing '\0') + which would have been written to the final string if enough space had been available. + */ + va_start( argptr,fmt ); + len = Q_vsnprintf( dest, size, fmt, argptr ); + va_end( argptr ); + + if ( len >= size ) { + Com_Printf( "Com_sprintf: overflow of %i in %i\n", len, size ); + } +} + +// Ridah, ripped from l_bsp.c +int Q_strncasecmp( char *s1, char *s2, int n ) { + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + + } + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + if ( c1 != c2 ) { + return -1; // strings not equal + } + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_strcasecmp( char *s1, char *s2 ) { + return Q_strncasecmp( s1, s2, 99999 ); +} +// done. + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday + +Ridah, modified this into a circular list, to further prevent stepping on +previous strings +============ +*/ +char * QDECL va( char *format, ... ) { + va_list argptr; + #define MAX_VA_STRING 32000 + static char temp_buffer[MAX_VA_STRING]; + static char string[MAX_VA_STRING]; // in case va is called by nested functions + static int index = 0; + char *buf; + int len; + + + va_start( argptr, format ); + vsprintf( temp_buffer, format,argptr ); + va_end( argptr ); + + if ( ( len = strlen( temp_buffer ) ) >= MAX_VA_STRING ) { + Com_Error( ERR_DROP, "Attempted to overrun string in call to va()\n" ); + } + + if ( len + index >= MAX_VA_STRING - 1 ) { + index = 0; + } + + buf = &string[index]; + memcpy( buf, temp_buffer, len + 1 ); + + index += len + 1; + + return buf; +} + +/* +============= +TempVector + +(SA) this is straight out of g_utils.c around line 210 + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv( float x, float y, float z ) { + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = ( index + 1 ) & 7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +FIXME: overflow check? +=============== +*/ +char *Info_ValueForKey( const char *s, const char *key ) { + char pkey[BIG_INFO_KEY]; + static char value[2][BIG_INFO_VALUE]; // use two buffers so compares + // work without stomping on each other + static int valueindex = 0; + char *o; + + if ( !s || !key ) { + return ""; + } + + if ( strlen( s ) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" ); + } + + valueindex ^= 1; + if ( *s == '\\' ) { + s++; + } + while ( 1 ) + { + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) { + return ""; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while ( *s != '\\' && *s ) + { + *o++ = *s++; + } + *o = 0; + + if ( !Q_stricmp( key, pkey ) ) { + return value[valueindex]; + } + + if ( !*s ) { + break; + } + s++; + } + + return ""; +} + + +/* +=================== +Info_NextPair + +Used to itterate through all the key/value pairs in an info string +=================== +*/ +void Info_NextPair( const char **head, char *key, char *value ) { + char *o; + const char *s; + + s = *head; + + if ( *s == '\\' ) { + s++; + } + key[0] = 0; + value[0] = 0; + + o = key; + while ( *s != '\\' ) { + if ( !*s ) { + *o = 0; + *head = s; + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) { + *o++ = *s++; + } + *o = 0; + + *head = s; +} + + +/* +=================== +Info_RemoveKey +=================== +*/ +void Info_RemoveKey( char *s, const char *key ) { + char *start; + char pkey[MAX_INFO_KEY]; + char value[MAX_INFO_VALUE]; + char *o; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); + } + + if ( strchr( key, '\\' ) ) { + return; + } + + while ( 1 ) + { + start = s; + if ( *s == '\\' ) { + s++; + } + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) { + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) + { + if ( !*s ) { + return; + } + *o++ = *s++; + } + *o = 0; + + if ( !strcmp( key, pkey ) ) { + strcpy( start, s ); // remove this part + return; + } + + if ( !*s ) { + return; + } + } + +} + +/* +=================== +Info_RemoveKey_Big +=================== +*/ +void Info_RemoveKey_Big( char *s, const char *key ) { + char *start; + char pkey[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + char *o; + + if ( strlen( s ) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey_Big: oversize infostring" ); + } + + if ( strchr( key, '\\' ) ) { + return; + } + + while ( 1 ) + { + start = s; + if ( *s == '\\' ) { + s++; + } + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) { + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) + { + if ( !*s ) { + return; + } + *o++ = *s++; + } + *o = 0; + + if ( !strcmp( key, pkey ) ) { + strcpy( start, s ); // remove this part + return; + } + + if ( !*s ) { + return; + } + } + +} + + + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate( const char *s ) { + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* +================== +Info_SetValueForKey + +Changes or adds a key/value pair +================== +*/ +void Info_SetValueForKey( char *s, const char *key, const char *value ) { + char newi[MAX_INFO_STRING]; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + if ( strchr( key, '\\' ) || strchr( value, '\\' ) ) { + Com_Printf( "Can't use keys or values with a \\\n" ); + return; + } + + if ( strchr( key, ';' ) || strchr( value, ';' ) ) { + Com_Printf( "Can't use keys or values with a semicolon\n" ); + return; + } + + if ( strchr( key, '\"' ) || strchr( value, '\"' ) ) { + Com_Printf( "Can't use keys or values with a \"\n" ); + return; + } + + Info_RemoveKey( s, key ); + if ( !value || !strlen( value ) ) { + return; + } + + Com_sprintf( newi, sizeof( newi ), "\\%s\\%s", key, value ); + + if ( strlen( newi ) + strlen( s ) > MAX_INFO_STRING ) { + Com_Printf( "Info string length exceeded\n" ); + return; + } + + strcat( s, newi ); +} + +/* +================== +Info_SetValueForKey_Big + +Changes or adds a key/value pair +================== +*/ +void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) { + char newi[BIG_INFO_STRING]; + + if ( strlen( s ) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + if ( strchr( key, '\\' ) || strchr( value, '\\' ) ) { + Com_Printf( "Can't use keys or values with a \\\n" ); + return; + } + + if ( strchr( key, ';' ) || strchr( value, ';' ) ) { + Com_Printf( "Can't use keys or values with a semicolon\n" ); + return; + } + + if ( strchr( key, '\"' ) || strchr( value, '\"' ) ) { + Com_Printf( "Can't use keys or values with a \"\n" ); + return; + } + + Info_RemoveKey_Big( s, key ); + if ( !value || !strlen( value ) ) { + return; + } + + Com_sprintf( newi, sizeof( newi ), "\\%s\\%s", key, value ); + + if ( strlen( newi ) + strlen( s ) > BIG_INFO_STRING ) { + Com_Printf( "BIG Info string length exceeded\n" ); + return; + } + + strcat( s, newi ); +} + + + + +//==================================================================== diff --git a/src/game/q_shared.h b/src/game/q_shared.h new file mode 100644 index 0000000..9430140 --- /dev/null +++ b/src/game/q_shared.h @@ -0,0 +1,1588 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __Q_SHARED_H +#define __Q_SHARED_H + +// q_shared.h -- included first by ALL program modules. +// A user mod should never modify this file + +#define Q3_VERSION "Wolf 1.41b-MP" +// 1.41b-MP: fix autodl sploit +// 1.4-MP : (== 1.34) +// 1.3-MP : final for release +// 1.1b - TTimo SP linux release (+ MP updates) +// 1.1b5 - Mac update merge in + +#define NEW_ANIMS +#define MAX_TEAMNAME 32 + +// DHM - Nerve +//#define PRE_RELEASE_DEMO + +#ifdef _WIN32 + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4152) // nonstandard extension, function/data pointer conversion in expression +#pragma warning(disable : 4201) +#pragma warning(disable : 4214) +#pragma warning(disable : 4244) +//#pragma warning(disable : 4142) // benign redefinition +#pragma warning(disable : 4305) // truncation from const double to float +//#pragma warning(disable : 4310) // cast truncates constant value +//#pragma warning(disable : 4505) // unreferenced local function has been removed +#pragma warning(disable : 4514) +#pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters +#endif + +#if defined( ppc ) || defined( __ppc ) || defined( __ppc__ ) || defined( __POWERPC__ ) +#define idppc 1 +#endif + +/********************************************************************** + VM Considerations + + The VM can not use the standard system headers because we aren't really + using the compiler they were meant for. We use bg_lib.h which contains + prototypes for the functions we define for our own use in bg_lib.c. + + When writing mods, please add needed headers HERE, do not start including + stuff like in the various .c files that make up each of the VMs + since you will be including system headers files can will have issues. + + Remember, if you use a C library function that is not defined in bg_lib.c, + you will have to add your own version for support in the VM. + + **********************************************************************/ + +#ifdef Q3_VM + +#include "bg_lib.h" + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + +#ifdef _WIN32 + +//#pragma intrinsic( memset, memcpy ) + +#endif + + +// this is the define for determining if we have an asm version of a C function +#if ( defined _M_IX86 || defined __i386__ ) && !defined __sun__ && !defined __LCC__ +#define id386 1 +#else +#define id386 0 +#endif + +// for windows fastcall option + +#define QDECL + +//======================= WIN32 DEFINES ================================= + +#ifdef WIN32 + +#define MAC_STATIC + +#undef QDECL +#define QDECL __cdecl + +// buildstring will be incorporated into the version string +#ifdef NDEBUG +#ifdef _M_IX86 +#define CPUSTRING "win-x86" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP" +#endif +#else +#ifdef _M_IX86 +#define CPUSTRING "win-x86-debug" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP-debug" +#endif +#endif + + +#define PATH_SEP '\\' + +#endif + +//======================= MAC OS X SERVER DEFINES ===================== + +#if defined( MACOS_X ) + +#define MAC_STATIC + +#define CPUSTRING "MacOS_X" + +#define PATH_SEP '/' + +// Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, +// runs *much* faster than calling sqrt(). We'll use two Newton-Raphson +// refinement steps to get bunch more precision in the 1/sqrt() value for very little cost. +// We'll then multiply 1/sqrt times the original value to get the sqrt. +// This is about 12.4 times faster than sqrt() and according to my testing (not exhaustive) +// it returns fairly accurate results (error below 1.0e-5 up to 100000.0 in 0.1 increments). + +static inline float idSqrt( float x ) { + const float half = 0.5; + const float one = 1.0; + float B, y0, y1; + + // This'll NaN if it hits frsqrte. Handle both +0.0 and -0.0 + if ( fabs( x ) == 0.0 ) { + return x; + } + B = x; + +#ifdef __GNUC__ + asm ( "frsqrte %0,%1" : "=f" ( y0 ) : "f" ( B ) ); +#else + y0 = __frsqrte( B ); +#endif + /* First refinement step */ + + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + /* Second refinement step -- copy the output of the last step to the input of this step */ + + y0 = y1; + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + /* Get sqrt(x) from x * 1/sqrt(x) */ + return x * y1; +} +#define sqrt idSqrt + + +#endif + +//======================= MAC DEFINES ================================= + +#ifdef __MACOS__ + +#include +#define MAC_STATIC + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +void Sys_PumpEvents( void ); + +static inline float idSqrt( float x ) { + const float half = 0.5; + const float one = 1.0; + float B, y0, y1; + + // This'll NaN if it hits frsqrte. Handle both +0.0 and -0.0 + if ( fabs( x ) == 0.0 ) { + return x; + } + B = x; + +#ifdef __GNUC__ + asm ( "frsqrte %0,%1" : "=f" ( y0 ) : "f" ( B ) ); +#else + y0 = __frsqrte( B ); +#endif + /* First refinement step */ + + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + /* Second refinement step -- copy the output of the last step to the input of this step */ + + y0 = y1; + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + /* Get sqrt(x) from x * 1/sqrt(x) */ + return x * y1; +} +#define sqrt idSqrt + +#endif + +//======================= LINUX DEFINES ================================= + +// the mac compiler can't handle >32k of locals, so we +// just waste space and make big arrays static... +#ifdef __linux__ + +#define MAC_STATIC + +#ifdef __i386__ +#define CPUSTRING "linux-i386" +#elif defined __axp__ +#define CPUSTRING "linux-alpha" +#else +#define CPUSTRING "linux-other" +#endif + +#define PATH_SEP '/' + +#endif + +//============================================================= + + +typedef unsigned char byte; + +typedef enum {qfalse, qtrue} qboolean; + +typedef int qhandle_t; +typedef int sfxHandle_t; +typedef int fileHandle_t; +typedef int clipHandle_t; + +//#define SND_NORMAL 0x000 // (default) Allow sound to be cut off only by the same sound on this channel +#define SND_OKTOCUT 0x001 // Allow sound to be cut off by any following sounds on this channel +#define SND_REQUESTCUT 0x002 // Allow sound to be cut off by following sounds on this channel only for sounds who request cutoff +#define SND_CUTOFF 0x004 // Cut off sounds on this channel that are marked 'SND_REQUESTCUT' +#define SND_CUTOFF_ALL 0x008 // Cut off all sounds on this channel +#define SND_NOCUT 0x010 // Don't cut off. Always let finish (overridden by SND_CUTOFF_ALL) + + +#ifndef NULL +#define NULL ( (void *)0 ) +#endif + +#define MAX_QINT 0x7fffffff +#define MIN_QINT ( -MAX_QINT - 1 ) + +// TTimo gcc: was missing, added from Q3 source +#ifndef max +#define max( x, y ) ( ( ( x ) > ( y ) ) ? ( x ) : ( y ) ) +#define min( x, y ) ( ( ( x ) < ( y ) ) ? ( x ) : ( y ) ) +#endif + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +// RF, this is just here so different elements of the engine can be aware of this setting as it changes +#define MAX_SP_CLIENTS 64 // increasing this will increase memory usage significantly + +// the game guarantees that no string from the network will ever +// exceed MAX_STRING_CHARS +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 256 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 1024 // max length of an individual token + +#define MAX_INFO_STRING 1024 +#define MAX_INFO_KEY 1024 +#define MAX_INFO_VALUE 1024 + +#define BIG_INFO_STRING 8192 // used for system info key only +#define BIG_INFO_KEY 8192 +#define BIG_INFO_VALUE 8192 + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 256 // max length of a filesystem pathname + +#define MAX_NAME_LENGTH 32 // max length of a client name + +#define MAX_SAY_TEXT 150 + +// paramters for command buffer stuffing +typedef enum { + EXEC_NOW, // don't return until completed, a VM should NEVER use this, + // because some commands might cause the VM to be unloaded... + EXEC_INSERT, // insert at current position, but don't run yet + EXEC_APPEND // add to end of the command buffer (normal case) +} cbufExec_t; + + +// +// these aren't needed by any of the VMs. put in another header? +// +#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility + + +// print levels from renderer (FIXME: set up for game / cgame?) +typedef enum { + PRINT_ALL, + PRINT_DEVELOPER, // only print when "developer 1" + PRINT_WARNING, + PRINT_ERROR +} printParm_t; + +#ifdef ERR_FATAL +#undef ERR_FATAL // this is be defined in malloc.h +#endif + +// parameters to the main Error routine +typedef enum { + ERR_FATAL, // exit the entire game with a popup window + ERR_DROP, // print to console and disconnect from game + ERR_SERVERDISCONNECT, // don't kill server + ERR_DISCONNECT, // client disconnected from the server + ERR_NEED_CD // pop up the need-cd dialog +} errorParm_t; + + +// font rendering values used by ui and cgame + +#define PROP_GAP_WIDTH 3 +#define PROP_SPACE_WIDTH 8 +#define PROP_HEIGHT 27 +#define PROP_SMALL_SIZE_SCALE 0.75 + +#define BLINK_DIVISOR 200 +#define PULSE_DIVISOR 75 + +#define UI_LEFT 0x00000000 // default +#define UI_CENTER 0x00000001 +#define UI_RIGHT 0x00000002 +#define UI_FORMATMASK 0x00000007 +#define UI_SMALLFONT 0x00000010 +#define UI_BIGFONT 0x00000020 // default +#define UI_GIANTFONT 0x00000040 +#define UI_DROPSHADOW 0x00000800 +#define UI_BLINK 0x00001000 +#define UI_INVERSE 0x00002000 +#define UI_PULSE 0x00004000 +// JOSEPH 10-24-99 +#define UI_MENULEFT 0x00008000 +#define UI_MENURIGHT 0x00010000 +#define UI_EXSMALLFONT 0x00020000 +#define UI_MENUFULL 0x00080000 +// END JOSEPH + +#define UI_SMALLFONT75 0x00100000 + +#if defined( _DEBUG ) && !defined( BSPC ) + #define HUNK_DEBUG +#endif + +typedef enum { + h_high, + h_low, + h_dontcare +} ha_pref; + +#ifdef HUNK_DEBUG +#define Hunk_Alloc( size, preference ) Hunk_AllocDebug( size, preference, # size, __FILE__, __LINE__ ) +void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ); +#else +void *Hunk_Alloc( int size, ha_pref preference ); +#endif + +#ifdef __linux__ +// show_bug.cgi?id=371 +// custom Snd_Memset implementation for glibc memset bug workaround +void Snd_Memset( void* dest, const int val, const size_t count ); +#else +#define Snd_Memset Com_Memset +#endif + +void Com_Memset( void* dest, const int val, const size_t count ); +void Com_Memcpy( void* dest, const void* src, const size_t count ); + +#define CIN_system 1 +#define CIN_loop 2 +#define CIN_hold 4 +#define CIN_silent 8 +#define CIN_shader 16 + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + + +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef vec_t vec5_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846f // matches value in gcc v2 math.h +#endif + +#define NUMVERTEXNORMALS 162 +extern vec3_t bytedirs[NUMVERTEXNORMALS]; + +// all drawing is done to a 640*480 virtual screen size +// and will be automatically scaled to the real resolution +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define TINYCHAR_WIDTH ( SMALLCHAR_WIDTH ) +#define TINYCHAR_HEIGHT ( SMALLCHAR_HEIGHT / 2 ) + +#define SMALLCHAR_WIDTH 8 +#define SMALLCHAR_HEIGHT 16 + +#define BIGCHAR_WIDTH 16 +#define BIGCHAR_HEIGHT 16 + +#define GIANTCHAR_WIDTH 32 +#define GIANTCHAR_HEIGHT 48 + +extern vec4_t colorBlack; +extern vec4_t colorRed; +extern vec4_t colorGreen; +extern vec4_t colorBlue; +extern vec4_t colorYellow; +extern vec4_t colorMagenta; +extern vec4_t colorCyan; +extern vec4_t colorWhite; +extern vec4_t colorLtGrey; +extern vec4_t colorMdGrey; +extern vec4_t colorDkGrey; + +#define Q_COLOR_ESCAPE '^' +#define Q_IsColorString( p ) ( p && *( p ) == Q_COLOR_ESCAPE && *( ( p ) + 1 ) && *( ( p ) + 1 ) != Q_COLOR_ESCAPE ) + +#define COLOR_BLACK '0' +#define COLOR_RED '1' +#define COLOR_GREEN '2' +#define COLOR_YELLOW '3' +#define COLOR_BLUE '4' +#define COLOR_CYAN '5' +#define COLOR_MAGENTA '6' +#define COLOR_WHITE '7' +#define ColorIndex( c ) ( ( ( c ) - '0' ) & 7 ) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" + +extern vec4_t g_color_table[8]; + +#define MAKERGB( v, r, g, b ) v[0] = r; v[1] = g; v[2] = b +#define MAKERGBA( v, r, g, b, a ) v[0] = r; v[1] = g; v[2] = b; v[3] = a + +#define DEG2RAD( a ) ( ( ( a ) * M_PI ) / 180.0F ) +#define RAD2DEG( a ) ( ( ( a ) * 180.0f ) / M_PI ) + +struct cplane_s; + +extern vec3_t vec3_origin; +extern vec3_t axisDefault[3]; + +#define nanmask ( 255 << 23 ) + +#define IS_NAN( x ) ( ( ( *(int *)&x ) & nanmask ) == nanmask ) + +float Q_fabs( float f ); +float Q_rsqrt( float f ); // reciprocal square root + +#define SQRTFAST( x ) ( 1.0f / Q_rsqrt( x ) ) + +signed char ClampChar( int i ); +signed short ClampShort( int i ); + +// this isn't a real cheap function to call! +int DirToByte( vec3_t dir ); +void ByteToDir( int b, vec3_t dir ); + +#if 1 + +#define DotProduct( x,y ) ( ( x )[0] * ( y )[0] + ( x )[1] * ( y )[1] + ( 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] ) +#define VectorScale( v, s, o ) ( ( o )[0] = ( v )[0] * ( s ),( o )[1] = ( v )[1] * ( s ),( o )[2] = ( v )[2] * ( s ) ) +#define VectorMA( v, s, b, o ) ( ( o )[0] = ( v )[0] + ( b )[0] * ( s ),( o )[1] = ( v )[1] + ( b )[1] * ( s ),( o )[2] = ( v )[2] + ( b )[2] * ( s ) ) + +#else + +#define DotProduct( x,y ) _DotProduct( x,y ) +#define VectorSubtract( a,b,c ) _VectorSubtract( a,b,c ) +#define VectorAdd( a,b,c ) _VectorAdd( a,b,c ) +#define VectorCopy( a,b ) _VectorCopy( a,b ) +#define VectorScale( v, s, o ) _VectorScale( v,s,o ) +#define VectorMA( v, s, b, o ) _VectorMA( v,s,b,o ) + +#endif + +#ifdef __LCC__ +#ifdef VectorCopy +#undef VectorCopy +// this is a little hack to get more efficient copies in our interpreter +typedef struct { + float v[3]; +} vec3struct_t; +#define VectorCopy( a,b ) * (vec3struct_t *)b = *(vec3struct_t *)a; +#endif +#endif + +#define VectorClear( a ) ( ( a )[0] = ( a )[1] = ( a )[2] = 0 ) +#define VectorNegate( a,b ) ( ( b )[0] = -( a )[0],( b )[1] = -( a )[1],( b )[2] = -( a )[2] ) +#define VectorSet( v, x, y, z ) ( ( v )[0] = ( x ), ( v )[1] = ( y ), ( v )[2] = ( z ) ) + +#define Vector4Set( v, x, y, z, n ) ( ( v )[0] = ( x ),( v )[1] = ( y ),( v )[2] = ( z ),( v )[3] = ( n ) ) +#define Vector4Copy( a,b ) ( ( b )[0] = ( a )[0],( b )[1] = ( a )[1],( b )[2] = ( a )[2],( b )[3] = ( a )[3] ) +#define Vector4MA( v, s, b, o ) ( ( o )[0] = ( v )[0] + ( b )[0] * ( s ),( o )[1] = ( v )[1] + ( b )[1] * ( s ),( o )[2] = ( v )[2] + ( b )[2] * ( s ),( o )[3] = ( v )[3] + ( b )[3] * ( s ) ) +#define Vector4Average( v, b, s, o ) ( ( o )[0] = ( ( v )[0] * ( 1 - ( s ) ) ) + ( ( b )[0] * ( s ) ),( o )[1] = ( ( v )[1] * ( 1 - ( s ) ) ) + ( ( b )[1] * ( s ) ),( o )[2] = ( ( v )[2] * ( 1 - ( s ) ) ) + ( ( b )[2] * ( s ) ),( o )[3] = ( ( v )[3] * ( 1 - ( s ) ) ) + ( ( b )[3] * ( s ) ) ) + +#define SnapVector( v ) {v[0] = ( (int)( v[0] ) ); v[1] = ( (int)( v[1] ) ); v[2] = ( (int)( v[2] ) );} + +// just in case you do't want to use the macros +vec_t _DotProduct( const vec3_t v1, const vec3_t v2 ); +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ); +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ); +void _VectorCopy( const vec3_t in, vec3_t out ); +void _VectorScale( const vec3_t in, float scale, vec3_t out ); +void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc ); + +unsigned ColorBytes3( float r, float g, float b ); +unsigned ColorBytes4( float r, float g, float b, float a ); + +float NormalizeColor( const vec3_t in, vec3_t out ); + +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ); +void ClearBounds( vec3_t mins, vec3_t maxs ); +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); +int VectorCompare( const vec3_t v1, const vec3_t v2 ); +vec_t VectorLength( const vec3_t v ); +vec_t VectorLengthSquared( const vec3_t v ); +vec_t Distance( const vec3_t p1, const vec3_t p2 ); +vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ); +void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ); +vec_t VectorNormalize( vec3_t v ); // returns vector length +void VectorNormalizeFast( vec3_t v ); // does NOT return vector length, uses rsqrt approximation +vec_t VectorNormalize2( const vec3_t v, vec3_t out ); +void VectorInverse( vec3_t v ); +void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ); +void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out ); +int Q_log2( int val ); + +float Q_acos( float c ); + +int Q_rand( int *seed ); +float Q_random( int *seed ); +float Q_crandom( int *seed ); + +#define random() ( ( rand() & 0x7fff ) / ( (float)0x7fff ) ) +#define crandom() ( 2.0 * ( random() - 0.5 ) ) + +void vectoangles( const vec3_t value1, vec3_t angles ); +float vectoyaw( const vec3_t vec ); +void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ); +// TTimo: const vec_t ** would require explicit casts for ANSI C conformance +// see unix/const-arg.c +void AxisToAngles( /*const*/ vec3_t axis[3], vec3_t angles ); +float VectorDistance( vec3_t v1, vec3_t v2 ); + +void AxisClear( vec3_t axis[3] ); +void AxisCopy( vec3_t in[3], vec3_t out[3] ); + +void SetPlaneSignbits( struct cplane_s *out ); +int BoxOnPlaneSide( vec3_t emins, vec3_t emaxs, struct cplane_s *plane ); + +float AngleMod( float a ); +float LerpAngle( float from, float to, float frac ); +void LerpPosition( vec3_t start, vec3_t end, float frac, vec3_t out ); +float AngleSubtract( float a1, float a2 ); +void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ); + +float AngleNormalize360( float angle ); +float AngleNormalize180( float angle ); +float AngleDelta( float angle1, float angle2 ); + +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ); +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); +void RotateAroundDirection( vec3_t axis[3], float yaw ); +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up ); +// perpendicular vector could be replaced by this + +int PlaneTypeForNormal( vec3_t normal ); + +void MatrixMultiply( float in1[3][3], float in2[3][3], float out[3][3] ); +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ); +void PerpendicularVector( vec3_t dst, const vec3_t src ); + +// Ridah +void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, const vec3_t p2, vec3_t up ); +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ); +// done. + +//============================================= + +float Com_Clamp( float min, float max, float value ); + +char *COM_SkipPath( char *pathname ); +void COM_StripExtension( const char *in, char *out ); +void COM_StripExtension2( const char *in, char *out, int destsize ); +void COM_StripFilename( char *in, char *out ); +void COM_DefaultExtension( char *path, int maxSize, const char *extension ); + +void COM_BeginParseSession( const char *name ); +void COM_RestoreParseSession( char **data_p ); +void COM_SetCurrentParseLine( int line ); +int COM_GetCurrentParseLine( void ); +char *COM_Parse( char **data_p ); +char *COM_ParseExt( char **data_p, qboolean allowLineBreak ); +int COM_Compress( char *data_p ); +void COM_ParseError( char *format, ... ); +void COM_ParseWarning( char *format, ... ); +//int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] ); + +qboolean COM_BitCheck( const int array[], int bitNum ); +void COM_BitSet( int array[], int bitNum ); +void COM_BitClear( int array[], int bitNum ); + +#define MAX_TOKENLENGTH 1024 + +#ifndef TT_STRING +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation +#endif + +typedef struct pc_token_s +{ + int type; + int subtype; + int intvalue; + float floatvalue; + char string[MAX_TOKENLENGTH]; +} pc_token_t; + +// data is an in/out parm, returns a parsed out token + +void COM_MatchToken( char**buf_p, char *match ); + +void SkipBracedSection( char **program ); +void SkipBracedSection_Depth( char **program, int depth ); // start at given depth if already matching stuff +void SkipRestOfLine( char **data ); + +void Parse1DMatrix( char **buf_p, int x, float *m ); +void Parse2DMatrix( char **buf_p, int y, int x, float *m ); +void Parse3DMatrix( char **buf_p, int z, int y, int x, float *m ); + +void QDECL Com_sprintf( char *dest, int size, const char *fmt, ... ); + + +// mode parm for FS_FOpenFile +typedef enum { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC +} fsMode_t; + +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +//============================================= + +int Q_isprint( int c ); +int Q_islower( int c ); +int Q_isupper( int c ); +int Q_isalpha( int c ); + +// portable case insensitive compare +int Q_stricmp( const char *s1, const char *s2 ); +int Q_strncmp( const char *s1, const char *s2, int n ); +int Q_stricmpn( const char *s1, const char *s2, int n ); +char *Q_strlwr( char *s1 ); +char *Q_strupr( char *s1 ); +char *Q_strrchr( const char* string, int c ); + +#ifdef _WIN32 +#define Q_putenv _putenv +#else +#define Q_putenv putenv +#endif + +// buffer size safe library replacements +void Q_strncpyz( char *dest, const char *src, int destsize ); +void Q_strcat( char *dest, int size, const char *src ); + +// strlen that discounts Quake color sequences +int Q_PrintStrlen( const char *string ); +// removes color sequences from string +char *Q_CleanStr( char *string ); +// Ridah +int Q_strncasecmp( char *s1, char *s2, int n ); +int Q_strcasecmp( char *s1, char *s2 ); +// done. +// TTimo +// vsnprintf is ISO/IEC 9899:1999 +// abstracting this to make it portable +#ifdef WIN32 +#define Q_vsnprintf _vsnprintf +#else +// TODO: Mac define? +#define Q_vsnprintf vsnprintf +#endif + +//============================================= + +// 64-bit integers for global rankings interface +// implemented as a struct for qvm compatibility +typedef struct +{ + byte b0; + byte b1; + byte b2; + byte b3; + byte b4; + byte b5; + byte b6; + byte b7; +} qint64; + +//============================================= + +short BigShort( short l ); +short LittleShort( short l ); +int BigLong( int l ); +int LittleLong( int l ); +qint64 BigLong64( qint64 l ); +qint64 LittleLong64( qint64 l ); +float BigFloat( float l ); +float LittleFloat( float l ); + +void Swap_Init( void ); +char * QDECL va( char *format, ... ); +float *tv( float x, float y, float z ); + +//============================================= + +// +// key / value info strings +// +char *Info_ValueForKey( const char *s, const char *key ); +void Info_RemoveKey( char *s, const char *key ); +void Info_RemoveKey_big( char *s, const char *key ); +void Info_SetValueForKey( char *s, const char *key, const char *value ); +void Info_SetValueForKey_Big( char *s, const char *key, const char *value ); +qboolean Info_Validate( const char *s ); +void Info_NextPair( const char **s, char *key, char *value ); + +// this is only here so the functions in q_shared.c and bg_*.c can link +void QDECL Com_Error( int level, const char *error, ... ); +void QDECL Com_Printf( const char *msg, ... ); + + +/* +========================================================== + +CVARS (console variables) + +Many variables can be used for cheating purposes, so when +cheats is zero, force all unspecified variables to their +default values. +========================================================== +*/ + +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc + // used for system variables, not for player + // specific configurations +#define CVAR_USERINFO 2 // sent to server on connect or change +#define CVAR_SERVERINFO 4 // sent in response to front end requests +#define CVAR_SYSTEMINFO 8 // these cvars will be duplicated on all clients +#define CVAR_INIT 16 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 32 // will only change when C code next does + // a Cvar_Get(), so it can't be changed + // without proper initialization. modified + // will be set, even though the value hasn't + // changed yet +#define CVAR_ROM 64 // display only, cannot be set by user at all +#define CVAR_USER_CREATED 128 // created by a set command +#define CVAR_TEMP 256 // can be set even when cheats are disabled, but is not archived +#define CVAR_CHEAT 512 // can not be changed if cheats are disabled +#define CVAR_NORESTART 1024 // do not clear when a cvar_restart is issued +#define CVAR_WOLFINFO 2048 // DHM - NERVE :: Like userinfo, but for wolf multiplayer info + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s { + char *name; + char *string; + char *resetString; // cvar_restart will reset to this value + char *latchedString; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + int modificationCount; // incremented each time the cvar is changed + float value; // atof( string ) + int integer; // atoi( string ) + struct cvar_s *next; + struct cvar_s *hashNext; +} cvar_t; + +#define MAX_CVAR_VALUE_STRING 256 + +typedef int cvarHandle_t; + +// the modules that run in the virtual machine can't access the cvar_t directly, +// so they must ask for structured updates +typedef struct { + cvarHandle_t handle; + int modificationCount; + float value; + int integer; + char string[MAX_CVAR_VALUE_STRING]; +} vmCvar_t; + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +#include "surfaceflags.h" // shared with the q3map utility + +// plane types are used to speed some tests +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 +#define PLANE_NON_AXIAL 3 + + +/* +================= +PlaneTypeForNormal +================= +*/ + +#define PlaneTypeForNormal( x ) ( x[0] == 1.0 ? PLANE_X : ( x[1] == 1.0 ? PLANE_Y : ( x[2] == 1.0 ? PLANE_Z : PLANE_NON_AXIAL ) ) ) + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s { + vec3_t normal; + float dist; + byte type; // for fast side tests: 0,1,2 = axial, 3 = nonaxial + byte signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision + byte pad[2]; +} cplane_t; + + +// a trace is returned when a box is swept through the world +typedef struct { + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact, transformed to world space + int surfaceFlags; // surface hit + int contents; // contents on other side of surface hit + int entityNum; // entity the contacted sirface is a part of +} trace_t; + +// trace->entityNum can also be 0 to (MAX_GENTITIES-1) +// or ENTITYNUM_NONE, ENTITYNUM_WORLD + + +// markfragments are returned by CM_MarkFragments() +typedef struct { + int firstPoint; + int numPoints; +} markFragment_t; + + + +typedef struct { + vec3_t origin; + vec3_t axis[3]; +} orientation_t; + +//===================================================================== + + +// in order from highest priority to lowest +// if none of the catchers are active, bound key strings will be executed +#define KEYCATCH_CONSOLE 0x0001 +#define KEYCATCH_UI 0x0002 +#define KEYCATCH_MESSAGE 0x0004 +#define KEYCATCH_CGAME 0x0008 + + +// sound channels +// channel 0 never willingly overrides +// other channels will allways override a playing sound on that channel +typedef enum { + CHAN_AUTO, + CHAN_LOCAL, // menu sounds, etc + CHAN_WEAPON, + CHAN_VOICE, + CHAN_ITEM, + CHAN_BODY, + CHAN_LOCAL_SOUND, // chat messages, etc + CHAN_ANNOUNCER // announcer voices, etc +} soundChannel_t; + + +/* +======================================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +======================================================================== +*/ +#define ANIM_BITS 10 + +#define ANGLE2SHORT( x ) ( (int)( ( x ) * 65536 / 360 ) & 65535 ) +#define SHORT2ANGLE( x ) ( ( x ) * ( 360.0 / 65536 ) ) + +#define SNAPFLAG_RATE_DELAYED 1 +#define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies +#define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected + +// +// per-level limits +// +#define MAX_CLIENTS 64 // JPW NERVE back to q3ta default was 128 // absolute limit +#define MAX_LOCATIONS 64 + +#define GENTITYNUM_BITS 10 // JPW NERVE put q3ta default back for testing // don't need to send any more +//#define GENTITYNUM_BITS 11 // don't need to send any more (SA) upped 4/21/2001 adjusted: tr_local.h (802-822), tr_main.c (1501), sv_snapshot (206) +#define MAX_GENTITIES ( 1 << GENTITYNUM_BITS ) + +// entitynums are communicated with GENTITY_BITS, so any reserved +// values thatare going to be communcated over the net need to +// also be in this range +#define ENTITYNUM_NONE ( MAX_GENTITIES - 1 ) +#define ENTITYNUM_WORLD ( MAX_GENTITIES - 2 ) +#define ENTITYNUM_MAX_NORMAL ( MAX_GENTITIES - 2 ) + + +#define MAX_MODELS 256 // these are sent over the net as 8 bits +#define MAX_SOUNDS 256 // so they cannot be blindly increased + + +#define MAX_PARTICLES_AREAS 128 + +#define MAX_MULTI_SPAWNTARGETS 16 // JPW NERVE + +//#define MAX_CONFIGSTRINGS 1024 +#define MAX_CONFIGSTRINGS 2048 + +#define MAX_DLIGHT_CONFIGSTRINGS 128 +#define MAX_CLIPBOARD_CONFIGSTRINGS 64 +#define MAX_SPLINE_CONFIGSTRINGS 64 + +#define PARTICLE_SNOW128 1 +#define PARTICLE_SNOW64 2 +#define PARTICLE_SNOW32 3 +#define PARTICLE_SNOW256 0 + +#define PARTICLE_BUBBLE8 4 +#define PARTICLE_BUBBLE16 5 +#define PARTICLE_BUBBLE32 6 +#define PARTICLE_BUBBLE64 7 + +// these are the only configstrings that the system reserves, all the +// other ones are strictly for servergame to clientgame communication +#define CS_SERVERINFO 0 // an info string with all the serverinfo cvars +#define CS_SYSTEMINFO 1 // an info string for server system to client system configuration (timescale, etc) + +#define RESERVED_CONFIGSTRINGS 2 // game can't modify below this, only the system can + +#define MAX_GAMESTATE_CHARS 16000 +typedef struct { + int stringOffsets[MAX_CONFIGSTRINGS]; + char stringData[MAX_GAMESTATE_CHARS]; + int dataCount; +} gameState_t; + +#define REF_FORCE_DLIGHT ( 1 << 31 ) // RF, passed in through overdraw parameter, force this dlight under all conditions +#define REF_JUNIOR_DLIGHT ( 1 << 30 ) // (SA) this dlight does not light surfaces. it only affects dynamic light grid + +//========================================================= +// shared by AI and animation scripting +// +typedef enum +{ + // TTimo gcc: enums don't go <=0 unless you force a value + AISTATE_NULL = -1, + AISTATE_RELAXED, + AISTATE_QUERY, + AISTATE_ALERT, + AISTATE_COMBAT, + + MAX_AISTATES +} aistateEnum_t; + +//========================================================= + + +// weapon grouping +#define MAX_WEAP_BANKS 12 +#define MAX_WEAPS_IN_BANK 3 +// JPW NERVE +#define MAX_WEAPS_IN_BANK_MP 8 +#define MAX_WEAP_BANKS_MP 7 +// jpw +#define MAX_WEAP_ALTS WP_DYNAMITE2 + + +// bit field limits +#define MAX_STATS 16 +#define MAX_PERSISTANT 16 +#define MAX_POWERUPS 16 +#define MAX_WEAPONS 64 // (SA) and yet more! + +// Ridah, increased this +//#define MAX_PS_EVENTS 2 +// ACK: I'd really like to make this 4, but that seems to cause network problems +#define MAX_EVENTS 4 // max events per frame before we drop events +//#define MAX_EVENTS 2 // max events per frame before we drop events + + +#define PS_PMOVEFRAMECOUNTBITS 6 + +// playerState_t is the information needed by both the client and server +// to predict player motion and actions +// nothing outside of pmove should modify these, or some degree of prediction error +// will occur + +// you can't add anything to this without modifying the code in msg.c + +// playerState_t is a full superset of entityState_t as it is used by players, +// so if a playerState_t is transmitted, the entityState_t can be fully derived +// from it. +// +// NOTE: all fields in here must be 32 bits (or those within sub-structures) +typedef struct playerState_s { + int commandTime; // cmd->serverTime of last executed command + int pm_type; + int bobCycle; // for view bobbing and footstep generation + int pm_flags; // ducked, jump_held, etc + int pm_time; + + vec3_t origin; + vec3_t velocity; + int weaponTime; + int weaponDelay; // for weapons that don't fire immediately when 'fire' is hit (grenades, venom, ...) + int grenadeTimeLeft; // for delayed grenade throwing. this is set to a #define for grenade + // lifetime when the attack button goes down, then when attack is released + // this is the amount of time left before the grenade goes off (or if it + // gets to 0 while in players hand, it explodes) + + + int gravity; + float leanf; // amount of 'lean' when player is looking around corner //----(SA) added + + int speed; + int delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters + + int groundEntityNum; // ENTITYNUM_NONE = in air + + int legsTimer; // don't change low priority animations until this runs out + int legsAnim; // mask off ANIM_TOGGLEBIT + + int torsoTimer; // don't change low priority animations until this runs out + int torsoAnim; // mask off ANIM_TOGGLEBIT + + int movementDir; // a number 0 to 7 that represents the reletive angle + // of movement to the view angle (axial and diagonals) + // when at rest, the value will remain unchanged + // used to twist the legs during strafing + + + + int eFlags; // copied to entityState_t->eFlags + + int eventSequence; // pmove generated events + int events[MAX_EVENTS]; + int eventParms[MAX_EVENTS]; + int oldEventSequence; // so we can see which events have been added since we last converted to entityState_t + + int externalEvent; // events set on player from another source + int externalEventParm; + int externalEventTime; + + int clientNum; // ranges from 0 to MAX_CLIENTS-1 + + // weapon info + int weapon; // copied to entityState_t->weapon + int weaponstate; + + // item info + int item; + + vec3_t viewangles; // for fixed views + int viewheight; + + // damage feedback + int damageEvent; // when it changes, latch the other parms + int damageYaw; + int damagePitch; + int damageCount; + + int stats[MAX_STATS]; + int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death + int powerups[MAX_POWERUPS]; // level.time that the powerup runs out + int ammo[MAX_WEAPONS]; // total amount of ammo + int ammoclip[MAX_WEAPONS]; // ammo in clip + int holdable[16]; + int holding; // the current item in holdable[] that is selected (held) + int weapons[MAX_WEAPONS / ( sizeof( int ) * 8 )]; // 64 bits for weapons held + + // Ridah, allow for individual bounding boxes + vec3_t mins, maxs; + float crouchMaxZ; + float crouchViewHeight, standViewHeight, deadViewHeight; + // variable movement speed + float runSpeedScale, sprintSpeedScale, crouchSpeedScale; + // done. + + // Ridah, view locking for mg42 + int viewlocked; + int viewlocked_entNum; + + // Ridah, need this to fix friction problems with slow zombie's whereby + // the friction prevents them from accelerating to their full potential + float friction; + + // Ridah, AI character id is used for weapon association + int aiChar; + int teamNum; + + // Rafael + int gunfx; + + // RF, burning effect is required for view blending effect + int onFireStart; + + int serverCursorHint; // what type of cursor hint the server is dictating + int serverCursorHintVal; // a value (0-255) associated with the above + + trace_t serverCursorHintTrace; // not communicated over net, but used to store the current server-side cursorhint trace + + // ---------------------------------------------------------------------- + // not communicated over the net at all + // FIXME: this doesn't get saved between predicted frames on the clients-side (cg.predictedPlayerState) + // So to use persistent variables here, which don't need to come from the server, + // we could use a marker variable, and use that to store everything after it + // before we read in the new values for the predictedPlayerState, then restore them + // after copying the structure recieved from the server. + + // (SA) yeah. this is causing me a little bit of trouble too. can we go ahead with the above suggestion or find an alternative? + + int ping; // server to game info for scoreboard + int pmove_framecount; // FIXME: don't transmit over the network + int entityEventSequence; + + int sprintTime; + int sprintExertTime; + + // JPW NERVE -- value for all multiplayer classes with regenerating "class weapons" -- ie LT artillery, medic medpack, engineer build points, etc + int classWeaponTime; + int jumpTime; // used in MP to prevent jump accel + // jpw + + int weapAnimTimer; // don't change low priority animations until this runs out //----(SA) added + int weapAnim; // mask off ANIM_TOGGLEBIT //----(SA) added + + qboolean releasedFire; + + float aimSpreadScaleFloat; // (SA) the server-side aimspreadscale that lets it track finer changes but still only + // transmit the 8bit int to the client + int aimSpreadScale; // 0 - 255 increases with angular movement + int lastFireTime; // used by server to hold last firing frame briefly when randomly releasing trigger (AI) + + int quickGrenTime; + + int leanStopDebounceTime; + +//----(SA) added + + // seems like heat and aimspread could be tied together somehow, however, they (appear to) change at different rates and + // I can't currently see how to optimize this to one server->client transmission "weapstatus" value. + int weapHeat[MAX_WEAPONS]; // some weapons can overheat. this tracks (server-side) how hot each weapon currently is. + int curWeapHeat; // value for the currently selected weapon (for transmission to client) + + int venomTime; //----(SA) added +//----(SA) end + + aistateEnum_t aiState; + + int identifyClient; // NERVE - SMF +} playerState_t; + + +//==================================================================== + + +// +// usercmd_t->button bits, many of which are generated by the client system, +// so they aren't game/cgame only definitions +// +#define BUTTON_ATTACK 1 +#define BUTTON_TALK 2 // displays talk balloon and disables actions +#define BUTTON_USE_HOLDABLE 4 +#define BUTTON_GESTURE 8 +#define BUTTON_WALKING 16 // walking can't just be infered from MOVE_RUN + // because a key pressed late in the frame will + // only generate a small move value for that frame + // walking will use different animations and + // won't generate footsteps +//----(SA) added +#define BUTTON_SPRINT 32 +#define BUTTON_ACTIVATE 64 +//----(SA) end + +#define BUTTON_ANY 128 // any key whatsoever + + + + +//----(SA) wolf buttons +#define WBUTTON_ATTACK2 1 +#define WBUTTON_ZOOM 2 +#define WBUTTON_QUICKGREN 4 +#define WBUTTON_RELOAD 8 +#define WBUTTON_LEANLEFT 16 +#define WBUTTON_LEANRIGHT 32 +#define WBUTTON_DROP 64 // JPW NERVE + +// unused +#define WBUTTON_EXTRA7 128 +//----(SA) end + +#define MOVE_RUN 120 // if forwardmove or rightmove are >= MOVE_RUN, + // then BUTTON_WALKING should be set + +#define MP_TEAM_OFFSET 6 +#define MP_CLASS_OFFSET 4 +#define MP_WEAPON_OFFSET 0 + +#define MP_TEAM_BITS 2 +#define MP_CLASS_BITS 2 +#define MP_WEAPON_BITS 4 + +#define MP_TEAM_MASK 0xC0 +#define MP_CLASS_MASK 0x30 +#define MP_WEAPON_MASK 0x0F + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s { + int serverTime; + byte buttons; + byte wbuttons; + byte weapon; + byte holdable; //----(SA) added + int angles[3]; + + signed char forwardmove, rightmove, upmove; + signed char wolfkick; // RF, we should move this over to a wbutton, this is a huge waste of bandwidth + + char mpSetup; // NERVE - SMF + char identClient; // NERVE - SMF +} usercmd_t; + +//=================================================================== + +// if entityState->solid == SOLID_BMODEL, modelindex is an inline model number +#define SOLID_BMODEL 0xffffff + +typedef enum { + TR_STATIONARY, + TR_INTERPOLATE, // non-parametric, but interpolate between snapshots + TR_LINEAR, + TR_LINEAR_STOP, + TR_LINEAR_STOP_BACK, //----(SA) added. so reverse movement can be different than forward + TR_SINE, // value = base + sin( time / duration ) * delta + TR_GRAVITY, + // Ridah + TR_GRAVITY_LOW, + TR_GRAVITY_FLOAT, // super low grav with no gravity acceleration (floating feathers/fabric/leaves/...) + TR_GRAVITY_PAUSED, //----(SA) has stopped, but will still do a short trace to see if it should be switched back to TR_GRAVITY + TR_ACCELERATE, + TR_DECCELERATE +} trType_t; + +typedef struct { + trType_t trType; + int trTime; + int trDuration; // if non 0, trTime + trDuration = stop time +//----(SA) removed + vec3_t trBase; + vec3_t trDelta; // velocity, etc +//----(SA) removed +} trajectory_t; + +// RF, put this here so we have a central means of defining a Zombie (kind of a hack, but this is to minimize bandwidth usage) +#define SET_FLAMING_ZOMBIE( x,y ) ( x.frame = y ) +#define IS_FLAMING_ZOMBIE( x ) ( x.frame == 1 ) + +// entityState_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +// Different eTypes may use the information in different ways +// The messages are delta compressed, so it doesn't really matter if +// the structure size is fairly large +// +// NOTE: all fields in here must be 32 bits (or those within sub-structures) + +typedef struct entityState_s { + int number; // entity index + int eType; // entityType_t + int eFlags; + + trajectory_t pos; // for calculating position + trajectory_t apos; // for calculating angles + + int time; + int time2; + + vec3_t origin; + vec3_t origin2; + + vec3_t angles; + vec3_t angles2; + + int otherEntityNum; // shotgun sources, etc + int otherEntityNum2; + + int groundEntityNum; // -1 = in air + + int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24) + int dl_intensity; // used for coronas + int loopSound; // constantly loop this sound + + int modelindex; + int modelindex2; + int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses + int frame; + + int solid; // for client side prediction, trap_linkentity sets this properly + + // old style events, in for compatibility only + int event; + int eventParm; + + int eventSequence; // pmove generated events + int events[MAX_EVENTS]; + int eventParms[MAX_EVENTS]; + + // for players + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +// int weapAnim; // mask off ANIM_TOGGLEBIT //----(SA) removed (weap anims will be client-side only) + + int density; // for particle effects + + int dmgFlags; // to pass along additional information for damage effects for players/ Also used for cursorhints for non-player entities + + // Ridah + int onFireStart, onFireEnd; + + int aiChar, teamNum; + + int effect1Time, effect2Time, effect3Time; + + aistateEnum_t aiState; + + int animMovetype; // clients can't derive movetype of other clients for anim scripting system + + +} entityState_t; + +typedef enum { + CA_UNINITIALIZED, + CA_DISCONNECTED, // not talking to a server + CA_AUTHORIZING, // not used any more, was checking cd key + CA_CONNECTING, // sending request packets to the server + CA_CHALLENGING, // sending challenge packets to the server + CA_CONNECTED, // netchan_t established, getting gamestate + CA_LOADING, // only during cgame initialization, never during main loop + CA_PRIMED, // got gamestate, waiting for first frame + CA_ACTIVE, // game views should be displayed + CA_CINEMATIC // playing a cinematic or a static pic, not connected to a server +} connstate_t; + +// font support + +#define GLYPH_START 0 +#define GLYPH_END 255 +#define GLYPH_CHARSTART 32 +#define GLYPH_CHAREND 127 +#define GLYPHS_PER_FONT GLYPH_END - GLYPH_START + 1 +typedef struct { + int height; // number of scan lines + int top; // top of glyph in buffer + int bottom; // bottom of glyph in buffer + int pitch; // width for copying + int xSkip; // x adjustment + int imageWidth; // width of actual image + int imageHeight; // height of actual image + float s; // x offset in image where glyph starts + float t; // y offset in image where glyph starts + float s2; + float t2; + qhandle_t glyph; // handle to the shader with the glyph + char shaderName[32]; +} glyphInfo_t; + +typedef struct { + glyphInfo_t glyphs [GLYPHS_PER_FONT]; + float glyphScale; + char name[MAX_QPATH]; +} fontInfo_t; + +#define Square( x ) ( ( x ) * ( x ) ) + +// real time +//============================================= + + +typedef struct qtime_s { + int tm_sec; /* seconds after the minute - [0,59] */ + int tm_min; /* minutes after the hour - [0,59] */ + int tm_hour; /* hours since midnight - [0,23] */ + int tm_mday; /* day of the month - [1,31] */ + int tm_mon; /* months since January - [0,11] */ + int tm_year; /* years since 1900 */ + int tm_wday; /* days since Sunday - [0,6] */ + int tm_yday; /* days since January 1 - [0,365] */ + int tm_isdst; /* daylight savings time flag */ +} qtime_t; + + +// server browser sources +#define AS_LOCAL 0 +#define AS_GLOBAL 1 // NERVE - SMF - modified +#define AS_FAVORITES 2 +#define AS_MPLAYER 3 + + +// cinematic states +typedef enum { + FMV_IDLE, + FMV_PLAY, // play + FMV_EOF, // all other conditions, i.e. stop/EOF/abort + FMV_ID_BLT, + FMV_ID_IDLE, + FMV_LOOPED, + FMV_ID_WAIT +} e_status; + +typedef enum _flag_status { + FLAG_ATBASE = 0, + FLAG_TAKEN, // CTF + FLAG_TAKEN_RED, // One Flag CTF + FLAG_TAKEN_BLUE, // One Flag CTF + FLAG_DROPPED +} flagStatus_t; + + + +#define MAX_GLOBAL_SERVERS 2048 +#define MAX_OTHER_SERVERS 128 +#define MAX_PINGREQUESTS 16 +#define MAX_SERVERSTATUSREQUESTS 16 + +#define SAY_ALL 0 +#define SAY_TEAM 1 +#define SAY_TELL 2 + +#define CDKEY_LEN 16 +#define CDCHKSUM_LEN 2 + +// NERVE - SMF - localization +typedef enum { +#ifndef __MACOS__ //DAJ USA + LANGUAGE_FRENCH = 0, + LANGUAGE_GERMAN, + LANGUAGE_ITALIAN, + LANGUAGE_SPANISH, +#endif + MAX_LANGUAGES +} languages_t; + +// NERVE - SMF - wolf server/game states +typedef enum { + GS_INITIALIZE = -1, + GS_PLAYING, + GS_WARMUP_COUNTDOWN, + GS_WARMUP, + GS_INTERMISSION, + GS_WAITING_FOR_PLAYERS, + GS_RESET +} gamestate_t; + +// TTimo - voting config flags +#define VOTEFLAGS_RESTART ( 1 << 0 ) +#define VOTEFLAGS_RESETMATCH ( 1 << 1 ) +#define VOTEFLAGS_STARTMATCH ( 1 << 2 ) +#define VOTEFLAGS_NEXTMAP ( 1 << 3 ) +#define VOTEFLAGS_SWAP ( 1 << 4 ) +#define VOTEFLAGS_TYPE ( 1 << 5 ) +#define VOTEFLAGS_KICK ( 1 << 6 ) +#define VOTEFLAGS_MAP ( 1 << 7 ) + +#endif // __Q_SHARED_H diff --git a/src/game/surfaceflags.h b/src/game/surfaceflags.h new file mode 100644 index 0000000..d599af3 --- /dev/null +++ b/src/game/surfaceflags.h @@ -0,0 +1,125 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// This file must be identical in the quake and utils directories + + + + + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits + +// these definitions also need to be in q_shared.h! + +#define CONTENTS_SOLID 1 // an eye is never valid in a solid + +#define CONTENTS_LIGHTGRID 4 //----(SA) added + +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_FOG 64 + + +//----(SA) added +#define CONTENTS_MISSILECLIP 128 // 0x80 +#define CONTENTS_ITEM 256 // 0x100 +//----(SA) end +#define CONTENTS_MOVER 0x4000 +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +//bot specific contents types +#define CONTENTS_TELEPORTER 0x40000 +#define CONTENTS_JUMPPAD 0x80000 +#define CONTENTS_CLUSTERPORTAL 0x100000 +#define CONTENTS_DONOTENTER 0x200000 +#define CONTENTS_DONOTENTER_LARGE 0x400000 + + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_BODY 0x2000000 // should never be on a brush, only in game +#define CONTENTS_CORPSE 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes not used for the bsp + +#define CONTENTS_STRUCTURAL 0x10000000 // brushes used for the bsp +#define CONTENTS_TRANSLUCENT 0x20000000 // don't consume surface fragments inside +#define CONTENTS_TRIGGER 0x40000000 +#define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava) + +#define SURF_NODAMAGE 0x1 // never give falling damage +#define SURF_SLICK 0x2 // effects game physics +#define SURF_SKY 0x4 // lighting from environment map +#define SURF_LADDER 0x8 +#define SURF_NOIMPACT 0x10 // don't make missile explosions +#define SURF_NOMARKS 0x20 // don't leave missile marks +//#define SURF_FLESH 0x40 // make flesh sounds and effects +#define SURF_CERAMIC 0x40 // out of surf's, so replacing unused 'SURF_FLESH' +#define SURF_NODRAW 0x80 // don't generate a drawsurface at all +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes +#define SURF_NOLIGHTMAP 0x400 // surface doesn't need a lightmap +#define SURF_POINTLIGHT 0x800 // generate lighting info at vertexes +// JOSEPH 9-16-99 +#define SURF_METAL 0x1000 // clanking footsteps +// END JOSEPH +#define SURF_NOSTEPS 0x2000 // no footstep sounds +#define SURF_NONSOLID 0x4000 // don't collide against curves with this set +#define SURF_LIGHTFILTER 0x8000 // act as a light filter during q3map -light +#define SURF_ALPHASHADOW 0x10000 // do per-pixel light shadow casting in q3map +#define SURF_NODLIGHT 0x20000 // don't dlight even if solid (solid lava, skies) +// JOSEPH 9-16-99 +// Ridah, 11-01-99 (Q3 merge) +#define SURF_WOOD 0x40000 +#define SURF_GRASS 0x80000 +#define SURF_GRAVEL 0x100000 +// END JOSEPH + +// (SA) +//#define SURF_SMGROUP 0x200000 +#define SURF_GLASS 0x200000 // out of surf's, so replacing unused 'SURF_SMGROUP' +#define SURF_SNOW 0x400000 +#define SURF_ROOF 0x800000 + +//#define SURF_RUBBLE 0x1000000 // stole 'rubble' for +#define SURF_RUBBLE 0x1000000 +#define SURF_CARPET 0x2000000 + +#define SURF_MONSTERSLICK 0x4000000 // slick surf that only affects ai's +// #define SURF_DUST 0x8000000 // leave a dust trail when walking on this surface +#define SURF_MONSLICK_W 0x8000000 + +#define SURF_MONSLICK_N 0x10000000 +#define SURF_MONSLICK_E 0x20000000 +#define SURF_MONSLICK_S 0x40000000 + diff --git a/src/jpeg-6/README b/src/jpeg-6/README new file mode 100644 index 0000000..86cc206 --- /dev/null +++ b/src/jpeg-6/README @@ -0,0 +1,385 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 6b of 27-Mar-1998 +==================================== + +This distribution contains the sixth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +Serious users of this software (particularly those incorporating it into +larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to +our electronic mailing list. Mailing list members are notified of updates +and have a chance to participate in technical discussions, etc. + +This software is the work of Tom Lane, Philip Gladstone, Jim Boucher, +Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, +Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG +Group. + +IJG is not affiliated with the official ISO JPEG standards committee. + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +RELATED SOFTWARE Other stuff you should get. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.doc How to configure and install the IJG software. + usage.doc Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.doc). + wizard.doc Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.doc How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.doc Overview of the JPEG library's internal structure. + filelist.doc Road map of IJG files. + coderules.doc Coding style rules --- please read if you contribute code. + +Please read at least the files install.doc and usage.doc. Useful information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image compression and +decompression. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. JPEG is intended for compressing +"real-world" scenes; line drawings, cartoons and other non-realistic images +are not its strong suit. JPEG is lossy, meaning that the output image is not +exactly identical to the input image. Hence you must not use JPEG if you +have to have identical output bits. However, on typical photographic images, +very good compression levels can be obtained with no visible change, and +remarkably high compression levels are possible if you can tolerate a +low-quality image. For more details, see the references, or just experiment +with various compression settings. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +For legal reasons, we are not distributing code for the arithmetic-coding +variants of JPEG; see LEGAL ISSUES. We have made no provision for supporting +the hierarchical or lossless processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. We have also included +"jpegtran", a utility for lossless transcoding between different JPEG +processes, and "rdjpgcom" and "wrjpgcom", two simple applications for +inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-1998, Thomas G. Lane. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, +sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. +ansi2knr.c is NOT covered by the above copyright and conditions, but instead +by the usual distribution terms of the Free Software Foundation; principally, +that you must include source code if you redistribute it. (See the file +ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part +of any program generated from the IJG code, this does not limit you more than +the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltconfig, ltmain.sh). Another support script, install-sh, is copyright +by M.I.T. but is also freely distributable. + +It appears that the arithmetic coding option of the JPEG spec is covered by +patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot +legally be used without obtaining one or more licenses. For this reason, +support for arithmetic coding has been removed from the free JPEG software. +(Since arithmetic coding provides only a marginal gain over the unpatented +Huffman mode, it is unlikely that very many implementations will support it.) +So far as we are aware, there are no patent restrictions on the remaining +code. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent, GIF reading support has +been removed altogether, and the GIF writer has been simplified to produce +"uncompressed GIFs". This technique does not use the LZW algorithm; the +resulting GIF files are larger than usual, but are readable by all standard +GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We highly recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best full description of JPEG is the textbook "JPEG Still Image Data +Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published +by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. Price US$59.95, 638 pp. +The book includes the complete text of the ISO JPEG standards (DIS 10918-1 +and draft DIS 10918-2). This is by far the most complete exposition of JPEG +in existence, and we highly recommend it. + +The JPEG standard itself is not available electronically; you must order a +paper copy through ISO or ITU. (Unless you feel a need to own a certified +official copy, we recommend buying the Pennebaker and Mitchell book instead; +it's much cheaper and includes a great deal of useful explanatory material.) +In the USA, copies of the standard may be ordered from ANSI Sales at (212) +642-4900, or from Global Engineering Documents at (800) 854-7179. (ANSI +doesn't take credit card orders, but Global does.) It's not cheap: as of +1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7% +shipping/handling. The standard is divided into two parts, Part 1 being the +actual specification, while Part 2 covers compliance testing methods. Part 1 +is titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. + +Some extensions to the original JPEG standard are defined in JPEG Part 3, +a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84. IJG +currently does not support any Part 3 extensions. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. A copy of the JFIF spec is available from: + Literature Department + C-Cube Microsystems, Inc. + 1778 McCarthy Blvd. + Milpitas, CA 95035 + phone (408) 944-6300, fax (408) 944-6314 +A PostScript version of this document is available by FTP at +ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz. There is also a plain text +version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing +the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from ftp.sgi.com or +from ftp://ftp.uu.net/graphics/jpeg/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. libtiff is available +from ftp://ftp.sgi.com/graphics/tiff/. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is ftp.uu.net (Internet +address 192.48.96.9). The most recent released version can always be found +there in directory graphics/jpeg. This particular version will be archived +as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz. If you don't have +direct Internet access, UUNET's archives are also available via UUCP; contact +help@uunet.uu.net for information on retrieving files that way. + +Numerous Internet sites maintain copies of the UUNET files. However, only +ftp.uu.net is guaranteed to have the latest official version. + +You can also obtain this software in DOS-compatible "zip" archive format from +the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or +on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12 +"JPEG Tools". Again, these versions may sometimes lag behind the ftp.uu.net +release. + +The JPEG FAQ (Frequently Asked Questions) article is a useful source of +general information about JPEG. It is updated constantly and therefore is +not included in this distribution. The FAQ is posted every two weeks to +Usenet newsgroups comp.graphics.misc, news.answers, and other groups. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +RELATED SOFTWARE +================ + +Numerous viewing and image manipulation programs now support JPEG. (Quite a +few of them use this library to do so.) The JPEG FAQ described above lists +some of the more popular free and shareware viewers, and tells where to +obtain them on Internet. + +If you are on a Unix machine, we highly recommend Jef Poskanzer's free +PBMPLUS software, which provides many useful operations on PPM-format image +files. In particular, it can convert PPM images to and from a wide range of +other formats, thus making cjpeg/djpeg considerably more useful. The latest +version is distributed by the NetPBM group, and is available from numerous +sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/. +Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is; +you are likely to have difficulty making it work on any non-Unix machine. + +A different free JPEG implementation, written by the PVRG group at Stanford, +is available from ftp://havefun.stanford.edu/pub/jpeg/. This program +is designed for research and experimentation rather than production use; +it is slower, harder to use, and less portable than the IJG code, but it +is easier to read and modify. Also, the PVRG code supports lossless JPEG, +which we do not. (On the other hand, it doesn't do progressive JPEG.) + + +FILE FORMAT WARS +================ + +Some JPEG programs produce files that are not compatible with our library. +The root of the problem is that the ISO JPEG committee failed to specify a +concrete file format. Some vendors "filled in the blanks" on their own, +creating proprietary formats that no one else could read. (For example, none +of the early commercial JPEG implementations for the Macintosh were able to +exchange compressed files.) + +The file format we have adopted is called JFIF (see REFERENCES). This format +has been agreed to by a number of major commercial JPEG vendors, and it has +become the de facto standard. JFIF is a minimal or "low end" representation. +We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF +Technical Note #2) for "high end" applications that need to record a lot of +additional data about an image. TIFF/JPEG is fairly new and not yet widely +supported, unfortunately. + +The upcoming JPEG Part 3 standard defines a file format called SPIFF. +SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should +be able to read the most common variant of SPIFF. SPIFF has some technical +advantages over JFIF, but its major claim to fame is simply that it is an +official standard rather than an informal one. At this point it is unclear +whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto +standard. IJG intends to support SPIFF once the standard is frozen, but we +have not decided whether it should become our default output format or not. +(In any case, our decoder will remain capable of reading JFIF indefinitely.) + +Various proprietary file formats incorporating JPEG compression also exist. +We have little or no sympathy for the existence of these formats. Indeed, +one of the original reasons for developing this free software was to help +force convergence on common, open format standards for JPEG files. Don't +use a proprietary file format! + + +TO DO +===== + +The major thrust for v7 will probably be improvement of visual quality. +The current method for scaling the quantization tables is known not to be +very good at low Q values. We also intend to investigate block boundary +smoothing, "poor man's variable quantization", and other means of improving +quality-vs-file-size performance without sacrificing compatibility. + +In future versions, we are considering supporting some of the upcoming JPEG +Part 3 extensions --- principally, variable quantization and the SPIFF file +format. + +As always, speeding things up is of great interest. + +Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net. diff --git a/src/jpeg-6/jcapimin.c b/src/jpeg-6/jcapimin.c new file mode 100644 index 0000000..38aaac5 --- /dev/null +++ b/src/jpeg-6/jcapimin.c @@ -0,0 +1,229 @@ +/* + * jcapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-compression case or the transcoding-only + * case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jcapistd.c. But also see jcparam.c for + * parameter-setup helper routines, jcomapi.c for routines shared by + * compression and decompression, and jctrans.c for the transcoding case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG compression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_compress( j_compress_ptr cinfo ) { + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + MEMZERO( cinfo, SIZEOF( struct jpeg_compress_struct ) ); + cinfo->err = err; + } + cinfo->is_decompressor = FALSE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr( (j_common_ptr) cinfo ); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->dest = NULL; + + cinfo->comp_info = NULL; + + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) + cinfo->quant_tbl_ptrs[i] = NULL; + + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + cinfo->input_gamma = 1.0; /* in case application forgets */ + + /* OK, I'm ready */ + cinfo->global_state = CSTATE_START; +} + + +/* + * Destruction of a JPEG compression object + */ + +GLOBAL void +jpeg_destroy_compress( j_compress_ptr cinfo ) { + jpeg_destroy( (j_common_ptr) cinfo ); /* use common routine */ +} + + +/* + * Abort processing of a JPEG compression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_compress( j_compress_ptr cinfo ) { + jpeg_abort( (j_common_ptr) cinfo ); /* use common routine */ +} + + +/* + * Forcibly suppress or un-suppress all quantization and Huffman tables. + * Marks all currently defined tables as already written (if suppress) + * or not written (if !suppress). This will control whether they get emitted + * by a subsequent jpeg_start_compress call. + * + * This routine is exported for use by applications that want to produce + * abbreviated JPEG datastreams. It logically belongs in jcparam.c, but + * since it is called by jpeg_start_compress, we put it here --- otherwise + * jcparam.o would be linked whether the application used it or not. + */ + +GLOBAL void +jpeg_suppress_tables( j_compress_ptr cinfo, boolean suppress ) { + int i; + JQUANT_TBL * qtbl; + JHUFF_TBL * htbl; + + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) { + if ( ( qtbl = cinfo->quant_tbl_ptrs[i] ) != NULL ) { + qtbl->sent_table = suppress; + } + } + + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + if ( ( htbl = cinfo->dc_huff_tbl_ptrs[i] ) != NULL ) { + htbl->sent_table = suppress; + } + if ( ( htbl = cinfo->ac_huff_tbl_ptrs[i] ) != NULL ) { + htbl->sent_table = suppress; + } + } +} + + +/* + * Finish JPEG compression. + * + * If a multipass operating mode was selected, this may do a great deal of + * work including most of the actual output. + */ + +GLOBAL void +jpeg_finish_compress( j_compress_ptr cinfo ) { + JDIMENSION iMCU_row; + + if ( cinfo->global_state == CSTATE_SCANNING || + cinfo->global_state == CSTATE_RAW_OK ) { + /* Terminate first pass */ + if ( cinfo->next_scanline < cinfo->image_height ) { + ERREXIT( cinfo, JERR_TOO_LITTLE_DATA ); + } + ( *cinfo->master->finish_pass )( cinfo ); + } else if ( cinfo->global_state != CSTATE_WRCOEFS ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Perform any remaining passes */ + while ( !cinfo->master->is_last_pass ) { + ( *cinfo->master->prepare_for_pass )( cinfo ); + for ( iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++ ) { + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) iMCU_row; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + /* We bypass the main controller and invoke coef controller directly; + * all work is being done from the coefficient buffer. + */ + if ( !( *cinfo->coef->compress_data )( cinfo, (JSAMPIMAGE) NULL ) ) { + ERREXIT( cinfo, JERR_CANT_SUSPEND ); + } + } + ( *cinfo->master->finish_pass )( cinfo ); + } + /* Write EOI, do final cleanup */ + ( *cinfo->marker->write_file_trailer )( cinfo ); + ( *cinfo->dest->term_destination )( cinfo ); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort( (j_common_ptr) cinfo ); +} + + +/* + * Write a special marker. + * This is only recommended for writing COM or APPn markers. + * Must be called after jpeg_start_compress() and before + * first call to jpeg_write_scanlines() or jpeg_write_raw_data(). + */ + +GLOBAL void +jpeg_write_marker( j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen ) { + if ( cinfo->next_scanline != 0 || + ( cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS ) ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + ( *cinfo->marker->write_any_marker )( cinfo, marker, dataptr, datalen ); +} + + +/* + * Alternate compression function: just write an abbreviated table file. + * Before calling this, all parameters and a data destination must be set up. + * + * To produce a pair of files containing abbreviated tables and abbreviated + * image data, one would proceed as follows: + * + * initialize JPEG object + * set JPEG parameters + * set destination to table file + * jpeg_write_tables(cinfo); + * set destination to image file + * jpeg_start_compress(cinfo, FALSE); + * write data... + * jpeg_finish_compress(cinfo); + * + * jpeg_write_tables has the side effect of marking all tables written + * (same as jpeg_suppress_tables(..., TRUE)). Thus a subsequent start_compress + * will not re-emit the tables unless it is passed write_all_tables=TRUE. + */ + +GLOBAL void +jpeg_write_tables( j_compress_ptr cinfo ) { + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + /* (Re)initialize error mgr and destination modules */ + ( *cinfo->err->reset_error_mgr )( (j_common_ptr) cinfo ); + ( *cinfo->dest->init_destination )( cinfo ); + /* Initialize the marker writer ... bit of a crock to do it here. */ + jinit_marker_writer( cinfo ); + /* Write them tables! */ + ( *cinfo->marker->write_tables_only )( cinfo ); + /* And clean up. */ + ( *cinfo->dest->term_destination )( cinfo ); + /* We can use jpeg_abort to release memory. */ + jpeg_abort( (j_common_ptr) cinfo ); +} diff --git a/src/jpeg-6/jccoefct.c b/src/jpeg-6/jccoefct.c new file mode 100644 index 0000000..9a8bd0f --- /dev/null +++ b/src/jpeg-6/jccoefct.c @@ -0,0 +1,449 @@ +/* + * jccoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for compression. + * This controller is the top level of the JPEG compressor proper. + * The coefficient buffer lies between forward-DCT and entropy encoding steps. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* We use a full-image coefficient buffer when doing Huffman optimization, + * and also for writing multiple-scan JPEG files. In all cases, the DCT + * step is run during the first pass, and subsequent passes need only read + * the buffered coefficients. + */ +#ifdef ENTROPY_OPT_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#else +#ifdef C_MULTISCAN_FILES_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#endif +#endif + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* For single-pass compression, it's sufficient to buffer just one MCU + * (although this may prove a bit slow in practice). We allocate a + * workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each + * MCU constructed and sent. (On 80x86, the workspace is FAR even though + * it's not really very big; this is to keep the module interfaces unchanged + * when a large coefficient buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays. + */ + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +/* Forward declarations */ +METHODDEF boolean compress_data +JPP( ( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) ); +#ifdef FULL_COEF_BUFFER_SUPPORTED +METHODDEF boolean compress_first_pass +JPP( ( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) ); +METHODDEF boolean compress_output +JPP( ( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) ); +#endif + + +LOCAL void +start_iMCU_row( j_compress_ptr cinfo ) { +/* Reset within-iMCU-row counters for a new row */ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if ( cinfo->comps_in_scan > 1 ) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if ( coef->iMCU_row_num < ( cinfo->total_iMCU_rows - 1 ) ) { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + } else { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_coef( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + coef->iMCU_row_num = 0; + start_iMCU_row( cinfo ); + + switch ( pass_mode ) { + case JBUF_PASS_THRU: + if ( coef->whole_image[0] != NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + coef->pub.compress_data = compress_data; + break; +#ifdef FULL_COEF_BUFFER_SUPPORTED + case JBUF_SAVE_AND_PASS: + if ( coef->whole_image[0] == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + coef->pub.compress_data = compress_first_pass; + break; + case JBUF_CRANK_DEST: + if ( coef->whole_image[0] == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + coef->pub.compress_data = compress_output; + break; +#endif + default: + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + break; + } +} + + +/* + * Process some data in the single-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF boolean +compress_data( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, bi, ci, yindex, yoffset, blockcnt; + JDIMENSION ypos, xpos; + jpeg_component_info *compptr; + + /* Loop to write as much as one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++ ) { + /* Determine where data comes from in input_buf and do the DCT thing. + * Each call on forward_DCT processes a horizontal row of DCT blocks + * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks + * sequentially. Dummy blocks at the right or bottom edge are filled in + * specially. The data in them does not matter for image reconstruction, + * so we fill them with values that will encode to the smallest amount of + * data, viz: all zeroes in the AC entries, DC entries equal to previous + * block's DC value. (Thanks to Thomas Kinsman for this idea.) + */ + blkn = 0; + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + blockcnt = ( MCU_col_num < last_MCU_col ) ? compptr->MCU_width + : compptr->last_col_width; + xpos = MCU_col_num * compptr->MCU_sample_width; + ypos = yoffset * DCTSIZE; /* ypos == (yoffset+yindex) * DCTSIZE */ + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + if ( coef->iMCU_row_num < last_iMCU_row || + yoffset + yindex < compptr->last_row_height ) { + ( *cinfo->fdct->forward_DCT )( cinfo, compptr, + input_buf[ci], coef->MCU_buffer[blkn], + ypos, xpos, (JDIMENSION) blockcnt ); + if ( blockcnt < compptr->MCU_width ) { + /* Create some dummy blocks at the right edge of the image. */ + jzero_far( (void FAR *) coef->MCU_buffer[blkn + blockcnt], + ( compptr->MCU_width - blockcnt ) * SIZEOF( JBLOCK ) ); + for ( bi = blockcnt; bi < compptr->MCU_width; bi++ ) { + coef->MCU_buffer[blkn + bi][0][0] = coef->MCU_buffer[blkn + bi - 1][0][0]; + } + } + } else { + /* Create a row of dummy blocks at the bottom of the image. */ + jzero_far( (void FAR *) coef->MCU_buffer[blkn], + compptr->MCU_width * SIZEOF( JBLOCK ) ); + for ( bi = 0; bi < compptr->MCU_width; bi++ ) { + coef->MCU_buffer[blkn + bi][0][0] = coef->MCU_buffer[blkn - 1][0][0]; + } + } + blkn += compptr->MCU_width; + ypos += DCTSIZE; + } + } + /* Try to write the MCU. In event of a suspension failure, we will + * re-DCT the MCU on restart (a bit inefficient, could be fixed...) + */ + if ( !( *cinfo->entropy->encode_mcu )( cinfo, coef->MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row( cinfo ); + return TRUE; +} + + +#ifdef FULL_COEF_BUFFER_SUPPORTED + +/* + * Process some data in the first pass of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * This amount of data is read from the source buffer, DCT'd and quantized, + * and saved into the virtual arrays. We also generate suitable dummy blocks + * as needed at the right and lower edges. (The dummy blocks are constructed + * in the virtual arrays, which have been padded appropriately.) This makes + * it possible for subsequent passes not to worry about real vs. dummy blocks. + * + * We must also emit the data to the entropy encoder. This is conveniently + * done by calling compress_output() after we've loaded the current strip + * of the virtual arrays. + * + * NB: input_buf contains a plane for each component in image. All + * components are DCT'd and loaded into the virtual arrays in this pass. + * However, it may be that only a subset of the components are emitted to + * the entropy encoder during this first pass; be careful about looking + * at the scan-dependent variables (MCU dimensions, etc). + */ + +METHODDEF boolean +compress_first_pass( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION blocks_across, MCUs_across, MCUindex; + int bi, ci, h_samp_factor, block_row, block_rows, ndummy; + JCOEF lastDC; + jpeg_component_info *compptr; + JBLOCKARRAY buffer; + JBLOCKROW thisblockrow, lastblockrow; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Align the virtual buffer for this component. */ + buffer = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[ci], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE ); + /* Count non-dummy DCT block rows in this iMCU row. */ + if ( coef->iMCU_row_num < last_iMCU_row ) { + block_rows = compptr->v_samp_factor; + } else { + /* NB: can't use last_row_height here, since may not be set! */ + block_rows = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( block_rows == 0 ) { + block_rows = compptr->v_samp_factor; + } + } + blocks_across = compptr->width_in_blocks; + h_samp_factor = compptr->h_samp_factor; + /* Count number of dummy blocks to be added at the right margin. */ + ndummy = (int) ( blocks_across % h_samp_factor ); + if ( ndummy > 0 ) { + ndummy = h_samp_factor - ndummy; + } + /* Perform DCT for all non-dummy blocks in this iMCU row. Each call + * on forward_DCT processes a complete horizontal row of DCT blocks. + */ + for ( block_row = 0; block_row < block_rows; block_row++ ) { + thisblockrow = buffer[block_row]; + ( *cinfo->fdct->forward_DCT )( cinfo, compptr, + input_buf[ci], thisblockrow, + (JDIMENSION) ( block_row * DCTSIZE ), + (JDIMENSION) 0, blocks_across ); + if ( ndummy > 0 ) { + /* Create dummy blocks at the right edge of the image. */ + thisblockrow += blocks_across; /* => first dummy block */ + jzero_far( (void FAR *) thisblockrow, ndummy * SIZEOF( JBLOCK ) ); + lastDC = thisblockrow[-1][0]; + for ( bi = 0; bi < ndummy; bi++ ) { + thisblockrow[bi][0] = lastDC; + } + } + } + /* If at end of image, create dummy block rows as needed. + * The tricky part here is that within each MCU, we want the DC values + * of the dummy blocks to match the last real block's DC value. + * This squeezes a few more bytes out of the resulting file... + */ + if ( coef->iMCU_row_num == last_iMCU_row ) { + blocks_across += ndummy; /* include lower right corner */ + MCUs_across = blocks_across / h_samp_factor; + for ( block_row = block_rows; block_row < compptr->v_samp_factor; + block_row++ ) { + thisblockrow = buffer[block_row]; + lastblockrow = buffer[block_row - 1]; + jzero_far( (void FAR *) thisblockrow, + (size_t) ( blocks_across * SIZEOF( JBLOCK ) ) ); + for ( MCUindex = 0; MCUindex < MCUs_across; MCUindex++ ) { + lastDC = lastblockrow[h_samp_factor - 1][0]; + for ( bi = 0; bi < h_samp_factor; bi++ ) { + thisblockrow[bi][0] = lastDC; + } + thisblockrow += h_samp_factor; /* advance to next MCU in row */ + lastblockrow += h_samp_factor; + } + } + } + } + /* NB: compress_output will increment iMCU_row_num if successful. + * A suspension return will result in redoing all the work above next time. + */ + + /* Emit data to the entropy encoder, sharing code with subsequent passes */ + return compress_output( cinfo, input_buf ); +} + + +/* + * Process some data in subsequent passes of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF boolean +compress_output( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. + * NB: during first pass, this is safe only because the buffers will + * already be aligned properly, so jmemmgr.c won't need to do any I/O. + */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE ); + } + + /* Loop to process one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++ ) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + buffer_ptr = buffer[ci][yindex + yoffset] + start_col; + for ( xindex = 0; xindex < compptr->MCU_width; xindex++ ) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to write the MCU. */ + if ( !( *cinfo->entropy->encode_mcu )( cinfo, coef->MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row( cinfo ); + return TRUE; +} + +#endif /* FULL_COEF_BUFFER_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_c_coef_controller( j_compress_ptr cinfo, boolean need_full_buffer ) { + my_coef_ptr coef; + + coef = (my_coef_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_coef_controller ) ); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + + /* Create the coefficient buffer. */ + if ( need_full_buffer ) { +#ifdef FULL_COEF_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + int ci; + jpeg_component_info *compptr; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + coef->whole_image[ci] = ( *cinfo->mem->request_virt_barray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up( (long) compptr->width_in_blocks, + (long) compptr->h_samp_factor ), + (JDIMENSION) jround_up( (long) compptr->height_in_blocks, + (long) compptr->v_samp_factor ), + (JDIMENSION) compptr->v_samp_factor ); + } +#else + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + ( *cinfo->mem->alloc_large ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF( JBLOCK ) ); + for ( i = 0; i < C_MAX_BLOCKS_IN_MCU; i++ ) { + coef->MCU_buffer[i] = buffer + i; + } + coef->whole_image[0] = NULL; /* flag for no virtual arrays */ + } +} diff --git a/src/jpeg-6/jccolor.c b/src/jpeg-6/jccolor.c new file mode 100644 index 0000000..ea5ace0 --- /dev/null +++ b/src/jpeg-6/jccolor.c @@ -0,0 +1,467 @@ +/* + * jccolor.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_converter pub; /* public fields */ + + /* Private state for RGB->YCC conversion */ + INT32 * rgb_ycc_tab; /* => table for RGB to YCbCr conversion */ +} my_color_converter; + +typedef my_color_converter * my_cconvert_ptr; + + +/**************** RGB -> YCbCr conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * Y = 0.29900 * R + 0.58700 * G + 0.11400 * B + * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTERJSAMPLE + * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTERJSAMPLE + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * Note: older versions of the IJG code used a zero offset of MAXJSAMPLE/2, + * rather than CENTERJSAMPLE, for Cb and Cr. This gave equal positive and + * negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0) + * were not represented exactly. Now we sacrifice exact representation of + * maximum red and maximum blue in order to get exact grayscales. + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times R,G,B for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included + * in the tables to save adding them separately in the inner loop. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define CBCR_OFFSET ( (INT32) CENTERJSAMPLE << SCALEBITS ) +#define ONE_HALF ( (INT32) 1 << ( SCALEBITS - 1 ) ) +#define FIX( x ) ( (INT32) ( ( x ) * ( 1L << SCALEBITS ) + 0.5 ) ) + +/* We allocate one big table and divide it up into eight parts, instead of + * doing eight alloc_small requests. This lets us use a single table base + * address, which can be held in a register in the inner loops on many + * machines (more than can hold all eight addresses, anyway). + */ + +#define R_Y_OFF 0 /* offset to R => Y section */ +#define G_Y_OFF ( 1 * ( MAXJSAMPLE + 1 ) ) /* offset to G => Y section */ +#define B_Y_OFF ( 2 * ( MAXJSAMPLE + 1 ) ) /* etc. */ +#define R_CB_OFF ( 3 * ( MAXJSAMPLE + 1 ) ) +#define G_CB_OFF ( 4 * ( MAXJSAMPLE + 1 ) ) +#define B_CB_OFF ( 5 * ( MAXJSAMPLE + 1 ) ) +#define R_CR_OFF B_CB_OFF /* B=>Cb, R=>Cr are the same */ +#define G_CR_OFF ( 6 * ( MAXJSAMPLE + 1 ) ) +#define B_CR_OFF ( 7 * ( MAXJSAMPLE + 1 ) ) +#define TABLE_SIZE ( 8 * ( MAXJSAMPLE + 1 ) ) + + +/* + * Initialize for RGB->YCC colorspace conversion. + */ + +METHODDEF void +rgb_ycc_start( j_compress_ptr cinfo ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + INT32 * rgb_ycc_tab; + INT32 i; + + /* Allocate and fill in the conversion tables. */ + cconvert->rgb_ycc_tab = rgb_ycc_tab = ( INT32 * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( TABLE_SIZE * SIZEOF( INT32 ) ) ); + + for ( i = 0; i <= MAXJSAMPLE; i++ ) { + rgb_ycc_tab[i + R_Y_OFF] = FIX( 0.29900 ) * i; + rgb_ycc_tab[i + G_Y_OFF] = FIX( 0.58700 ) * i; + rgb_ycc_tab[i + B_Y_OFF] = FIX( 0.11400 ) * i + ONE_HALF; + rgb_ycc_tab[i + R_CB_OFF] = ( -FIX( 0.16874 ) ) * i; + rgb_ycc_tab[i + G_CB_OFF] = ( -FIX( 0.33126 ) ) * i; + /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr. + * This ensures that the maximum output will round to MAXJSAMPLE + * not MAXJSAMPLE+1, and thus that we don't have to range-limit. + */ + rgb_ycc_tab[i + B_CB_OFF] = FIX( 0.50000 ) * i + CBCR_OFFSET + ONE_HALF - 1; +/* B=>Cb and R=>Cr tables are the same + rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +*/ + rgb_ycc_tab[i + G_CR_OFF] = ( -FIX( 0.41869 ) ) * i; + rgb_ycc_tab[i + B_CR_OFF] = ( -FIX( 0.08131 ) ) * i; + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * + * Note that we change from the application's interleaved-pixel format + * to our internal noninterleaved, one-plane-per-component format. + * The input buffer is therefore three times as wide as the output buffer. + * + * A starting row offset is provided only for the output buffer. The caller + * can easily adjust the passed input_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +rgb_ycc_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while ( --num_rows >= 0 ) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + output_row++; + for ( col = 0; col < num_cols; col++ ) { + r = GETJSAMPLE( inptr[RGB_RED] ); + g = GETJSAMPLE( inptr[RGB_GREEN] ); + b = GETJSAMPLE( inptr[RGB_BLUE] ); + inptr += RGB_PIXELSIZE; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ( ( ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] + ctab[b + B_Y_OFF] ) + >> SCALEBITS ); + /* Cb */ + outptr1[col] = (JSAMPLE) + ( ( ctab[r + R_CB_OFF] + ctab[g + G_CB_OFF] + ctab[b + B_CB_OFF] ) + >> SCALEBITS ); + /* Cr */ + outptr2[col] = (JSAMPLE) + ( ( ctab[r + R_CR_OFF] + ctab[g + G_CR_OFF] + ctab[b + B_CR_OFF] ) + >> SCALEBITS ); + } + } +} + + +/**************** Cases other than RGB -> YCbCr **************/ + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles RGB->grayscale conversion, which is the same + * as the RGB->Y portion of RGB->YCbCr. + * We assume rgb_ycc_start has been called (we only use the Y tables). + */ + +METHODDEF void +rgb_gray_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while ( --num_rows >= 0 ) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for ( col = 0; col < num_cols; col++ ) { + r = GETJSAMPLE( inptr[RGB_RED] ); + g = GETJSAMPLE( inptr[RGB_GREEN] ); + b = GETJSAMPLE( inptr[RGB_BLUE] ); + inptr += RGB_PIXELSIZE; + /* Y */ + outptr[col] = (JSAMPLE) + ( ( ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] + ctab[b + B_Y_OFF] ) + >> SCALEBITS ); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles Adobe-style CMYK->YCCK conversion, + * where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same + * conversion as above, while passing K (black) unchanged. + * We assume rgb_ycc_start has been called. + */ + +METHODDEF void +cmyk_ycck_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2, outptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while ( --num_rows >= 0 ) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + outptr3 = output_buf[3][output_row]; + output_row++; + for ( col = 0; col < num_cols; col++ ) { + r = MAXJSAMPLE - GETJSAMPLE( inptr[0] ); + g = MAXJSAMPLE - GETJSAMPLE( inptr[1] ); + b = MAXJSAMPLE - GETJSAMPLE( inptr[2] ); + /* K passes through as-is */ + outptr3[col] = inptr[3]; /* don't need GETJSAMPLE here */ + inptr += 4; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ( ( ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] + ctab[b + B_Y_OFF] ) + >> SCALEBITS ); + /* Cb */ + outptr1[col] = (JSAMPLE) + ( ( ctab[r + R_CB_OFF] + ctab[g + G_CB_OFF] + ctab[b + B_CB_OFF] ) + >> SCALEBITS ); + /* Cr */ + outptr2[col] = (JSAMPLE) + ( ( ctab[r + R_CR_OFF] + ctab[g + G_CR_OFF] + ctab[b + B_CR_OFF] ) + >> SCALEBITS ); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles grayscale output with no conversion. + * The source can be either plain grayscale or YCbCr (since Y == gray). + */ + +METHODDEF void +grayscale_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + int instride = cinfo->input_components; + + while ( --num_rows >= 0 ) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for ( col = 0; col < num_cols; col++ ) { + outptr[col] = inptr[0]; /* don't need GETJSAMPLE() here */ + inptr += instride; + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles multi-component colorspaces without conversion. + * We assume input_components == num_components. + */ + +METHODDEF void +null_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + register int ci; + int nc = cinfo->num_components; + JDIMENSION num_cols = cinfo->image_width; + + while ( --num_rows >= 0 ) { + /* It seems fastest to make a separate pass for each component. */ + for ( ci = 0; ci < nc; ci++ ) { + inptr = *input_buf; + outptr = output_buf[ci][output_row]; + for ( col = 0; col < num_cols; col++ ) { + outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */ + inptr += nc; + } + } + input_buf++; + output_row++; + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +null_method( j_compress_ptr cinfo ) { + /* no work needed */ +} + + +/* + * Module initialization routine for input colorspace conversion. + */ + +GLOBAL void +jinit_color_converter( j_compress_ptr cinfo ) { + my_cconvert_ptr cconvert; + + cconvert = (my_cconvert_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_color_converter ) ); + cinfo->cconvert = (struct jpeg_color_converter *) cconvert; + /* set start_pass to null method until we find out differently */ + cconvert->pub.start_pass = null_method; + + /* Make sure input_components agrees with in_color_space */ + switch ( cinfo->in_color_space ) { + case JCS_GRAYSCALE: + if ( cinfo->input_components != 1 ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; + + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + if ( cinfo->input_components != RGB_PIXELSIZE ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; +#endif /* else share code with YCbCr */ + + case JCS_YCbCr: + if ( cinfo->input_components != 3 ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; + + case JCS_CMYK: + case JCS_YCCK: + if ( cinfo->input_components != 4 ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; + + default: /* JCS_UNKNOWN can be anything */ + if ( cinfo->input_components < 1 ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; + } + + /* Check num_components, set conversion method based on requested space */ + switch ( cinfo->jpeg_color_space ) { + case JCS_GRAYSCALE: + if ( cinfo->num_components != 1 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_GRAYSCALE ) { + cconvert->pub.color_convert = grayscale_convert; + } else if ( cinfo->in_color_space == JCS_RGB ) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_gray_convert; + } else if ( cinfo->in_color_space == JCS_YCbCr ) { + cconvert->pub.color_convert = grayscale_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_RGB: + if ( cinfo->num_components != 3 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_RGB && RGB_PIXELSIZE == 3 ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_YCbCr: + if ( cinfo->num_components != 3 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_RGB ) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_ycc_convert; + } else if ( cinfo->in_color_space == JCS_YCbCr ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_CMYK: + if ( cinfo->num_components != 4 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_CMYK ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_YCCK: + if ( cinfo->num_components != 4 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_CMYK ) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = cmyk_ycck_convert; + } else if ( cinfo->in_color_space == JCS_YCCK ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + default: /* allow null conversion of JCS_UNKNOWN */ + if ( cinfo->jpeg_color_space != cinfo->in_color_space || + cinfo->num_components != cinfo->input_components ) { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + cconvert->pub.color_convert = null_convert; + break; + } +} diff --git a/src/jpeg-6/jcdctmgr.c b/src/jpeg-6/jcdctmgr.c new file mode 100644 index 0000000..87121eb --- /dev/null +++ b/src/jpeg-6/jcdctmgr.c @@ -0,0 +1,383 @@ +/* + * jcdctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the forward-DCT management logic. + * This code selects a particular DCT implementation to be used, + * and it performs related housekeeping chores including coefficient + * quantization. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_forward_dct pub; /* public fields */ + + /* Pointer to the DCT routine actually in use */ + forward_DCT_method_ptr do_dct; + + /* The actual post-DCT divisors --- not identical to the quant table + * entries, because of scaling (especially for an unnormalized DCT). + * Each table is given in normal array order; note that this must + * be converted from the zigzag order of the quantization tables. + */ + DCTELEM * divisors[NUM_QUANT_TBLS]; + +#ifdef DCT_FLOAT_SUPPORTED + /* Same as above for the floating-point case. */ + float_DCT_method_ptr do_float_dct; + FAST_FLOAT * float_divisors[NUM_QUANT_TBLS]; +#endif +} my_fdct_controller; + +typedef my_fdct_controller * my_fdct_ptr; + + +/* + * Initialize for a processing pass. + * Verify that all referenced Q-tables are present, and set up + * the divisor table for each one. + * In the current implementation, DCT of all components is done during + * the first pass, even if only some components will be output in the + * first scan. Hence all components should be examined here. + */ + +METHODDEF void +start_pass_fdctmgr( j_compress_ptr cinfo ) { + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + int ci, qtblno, i; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; +#ifdef DCT_ISLOW_SUPPORTED + DCTELEM * dtbl; +#endif + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + qtblno = compptr->quant_tbl_no; + /* Make sure specified quantization table is present */ + if ( qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL ) { + ERREXIT1( cinfo, JERR_NO_QUANT_TABLE, qtblno ); + } + qtbl = cinfo->quant_tbl_ptrs[qtblno]; + /* Compute divisors for this quant table */ + /* We may do this more than once for same table, but it's not a big deal */ + switch ( cinfo->dct_method ) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + /* For LL&M IDCT method, divisors are equal to raw quantization + * coefficients multiplied by 8 (to counteract scaling). + */ + if ( fdct->divisors[qtblno] == NULL ) { + fdct->divisors[qtblno] = ( DCTELEM * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF( DCTELEM ) ); + } + dtbl = fdct->divisors[qtblno]; + for ( i = 0; i < DCTSIZE2; i++ ) { + dtbl[i] = ( (DCTELEM) qtbl->quantval[jpeg_zigzag_order[i]] ) << 3; + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + */ +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits: in natural order */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + if ( fdct->divisors[qtblno] == NULL ) { + fdct->divisors[qtblno] = ( DCTELEM * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF( DCTELEM ) ); + } + dtbl = fdct->divisors[qtblno]; + for ( i = 0; i < DCTSIZE2; i++ ) { + dtbl[i] = (DCTELEM) + DESCALE( MULTIPLY16V16( (INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i] ), + CONST_BITS - 3 ); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + * What's actually stored is 1/divisor so that the inner loop can + * use a multiplication rather than a division. + */ + FAST_FLOAT * fdtbl; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + if ( fdct->float_divisors[qtblno] == NULL ) { + fdct->float_divisors[qtblno] = ( FAST_FLOAT * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF( FAST_FLOAT ) ); + } + fdtbl = fdct->float_divisors[qtblno]; + i = 0; + for ( row = 0; row < DCTSIZE; row++ ) { + for ( col = 0; col < DCTSIZE; col++ ) { + fdtbl[i] = (FAST_FLOAT) + ( 1.0 / ( ( (double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col] * 8.0 ) ) ); + i++; + } + } + } + break; +#endif + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } + } +} + +// TTimo unused +#if 0 +/* + * Perform forward DCT on one or more blocks of a component. + * + * The input samples are taken from the sample_data[] array starting at + * position start_row/start_col, and moving to the right for any additional + * blocks. The quantized coefficients are returned in coef_blocks[]. + */ + +METHODDEF void +forward_DCT( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks ) { +/* This version is used for integer DCT implementations. */ +/* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + forward_DCT_method_ptr do_dct = fdct->do_dct; + DCTELEM * divisors = fdct->divisors[compptr->quant_tbl_no]; + DCTELEM workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for ( bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE ) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register DCTELEM *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for ( elemr = 0; elemr < DCTSIZE; elemr++ ) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; +#else + { register int elemc; + for ( elemc = DCTSIZE; elemc > 0; elemc-- ) { + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + }} +#endif + }} + + /* Perform the DCT */ + ( *do_dct )( workspace ); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register DCTELEM temp, qval; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for ( i = 0; i < DCTSIZE2; i++ ) { + qval = divisors[i]; + temp = workspace[i]; + /* Divide the coefficient value by qval, ensuring proper rounding. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * + * In most files, at least half of the output values will be zero + * (at default quantization settings, more like three-quarters...) + * so we should ensure that this case is fast. On many machines, + * a comparison is enough cheaper than a divide to make a special test + * a win. Since both inputs will be nonnegative, we need only test + * for a < b to discover whether a/b is 0. + * If your machine's division is fast enough, define FAST_DIVIDE. + */ +#ifdef FAST_DIVIDE +#define DIVIDE_BY( a,b ) a /= b +#else +#define DIVIDE_BY( a,b ) if ( a >= b ) {a /= b; \ +} else a = 0 +#endif + if ( temp < 0 ) { + temp = -temp; + temp += qval >> 1; /* for rounding */ + DIVIDE_BY( temp, qval ); + temp = -temp; + } else { + temp += qval >> 1; /* for rounding */ + DIVIDE_BY( temp, qval ); + } + output_ptr[i] = (JCOEF) temp; + }} + } +} +#endif + +#ifdef DCT_FLOAT_SUPPORTED + +METHODDEF void +forward_DCT_float( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks ) { +/* This version is used for floating-point DCT implementations. */ +/* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + float_DCT_method_ptr do_dct = fdct->do_float_dct; + FAST_FLOAT * divisors = fdct->float_divisors[compptr->quant_tbl_no]; + FAST_FLOAT workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for ( bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE ) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register FAST_FLOAT *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for ( elemr = 0; elemr < DCTSIZE; elemr++ ) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); +#else + { register int elemc; + for ( elemc = DCTSIZE; elemc > 0; elemc-- ) { + *workspaceptr++ = (FAST_FLOAT) + ( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + }} +#endif + }} + + /* Perform the DCT */ + ( *do_dct )( workspace ); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register FAST_FLOAT temp; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for ( i = 0; i < DCTSIZE2; i++ ) { + /* Apply the quantization and scaling factor */ + temp = workspace[i] * divisors[i]; + /* Round to nearest integer. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * The maximum coefficient size is +-16K (for 12-bit data), so this + * code should work for either 16-bit or 32-bit ints. + */ + output_ptr[i] = (JCOEF) ( (int) ( temp + (FAST_FLOAT) 16384.5 ) - 16384 ); + }} + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ + + +/* + * Initialize FDCT manager. + */ + +GLOBAL void +jinit_forward_dct( j_compress_ptr cinfo ) { + my_fdct_ptr fdct; + int i; + + fdct = (my_fdct_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_fdct_controller ) ); + cinfo->fdct = (struct jpeg_forward_dct *) fdct; + fdct->pub.start_pass = start_pass_fdctmgr; + + switch ( cinfo->dct_method ) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_islow; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_ifast; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + fdct->pub.forward_DCT = forward_DCT_float; + fdct->do_float_dct = jpeg_fdct_float; + break; +#endif + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } + + /* Mark divisor tables unallocated */ + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) { + fdct->divisors[i] = NULL; +#ifdef DCT_FLOAT_SUPPORTED + fdct->float_divisors[i] = NULL; +#endif + } +} diff --git a/src/jpeg-6/jchuff.c b/src/jpeg-6/jchuff.c new file mode 100644 index 0000000..a3e1eb8 --- /dev/null +++ b/src/jpeg-6/jchuff.c @@ -0,0 +1,860 @@ +/* + * jchuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines. + * + * Much of the complexity here has to do with supporting output suspension. + * If the data destination module demands suspension, we want to be able to + * back up to the start of the current MCU. To do this, we copy state + * variables into local working storage, and update them back to the + * permanent JPEG objects only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jcphuff.c */ + + +/* Expanded entropy encoder object for Huffman encoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE( dest,src ) ( ( dest ) = ( src ) ) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE( dest,src ) \ + ( ( dest ).put_buffer = ( src ).put_buffer, \ + ( dest ).put_bits = ( src ).put_bits, \ + ( dest ).last_dc_val[0] = ( src ).last_dc_val[0], \ + ( dest ).last_dc_val[1] = ( src ).last_dc_val[1], \ + ( dest ).last_dc_val[2] = ( src ).last_dc_val[2], \ + ( dest ).last_dc_val[3] = ( src ).last_dc_val[3] ) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + savable_state saved; /* Bit buffer & DC state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + c_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + c_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + +#ifdef ENTROPY_OPT_SUPPORTED /* Statistics tables for optimization */ + long * dc_count_ptrs[NUM_HUFF_TBLS]; + long * ac_count_ptrs[NUM_HUFF_TBLS]; +#endif +} huff_entropy_encoder; + +typedef huff_entropy_encoder * huff_entropy_ptr; + +/* Working state while writing an MCU. + * This struct contains all the fields that are needed by subroutines. + */ + +typedef struct { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + savable_state cur; /* Current bit buffer & DC state */ + j_compress_ptr cinfo; /* dump_buffer needs access to this */ +} working_state; + + +/* Forward declarations */ +METHODDEF boolean encode_mcu_huff JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF void finish_pass_huff JPP( (j_compress_ptr cinfo) ); +#ifdef ENTROPY_OPT_SUPPORTED +METHODDEF boolean encode_mcu_gather JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF void finish_pass_gather JPP( (j_compress_ptr cinfo) ); +#endif + + +/* + * Initialize for a Huffman-compressed scan. + * If gather_statistics is TRUE, we do not output anything during the scan, + * just count the Huffman symbols used and generate Huffman code tables. + */ + +METHODDEF void +start_pass_huff( j_compress_ptr cinfo, boolean gather_statistics ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + if ( gather_statistics ) { +#ifdef ENTROPY_OPT_SUPPORTED + entropy->pub.encode_mcu = encode_mcu_gather; + entropy->pub.finish_pass = finish_pass_gather; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + entropy->pub.encode_mcu = encode_mcu_huff; + entropy->pub.finish_pass = finish_pass_huff; + } + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + /* (In gather mode, tables need not be allocated yet) */ + if ( dctbl < 0 || dctbl >= NUM_HUFF_TBLS || + ( cinfo->dc_huff_tbl_ptrs[dctbl] == NULL && !gather_statistics ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, dctbl ); + } + if ( actbl < 0 || actbl >= NUM_HUFF_TBLS || + ( cinfo->ac_huff_tbl_ptrs[actbl] == NULL && !gather_statistics ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, actbl ); + } + if ( gather_statistics ) { +#ifdef ENTROPY_OPT_SUPPORTED + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if ( entropy->dc_count_ptrs[dctbl] == NULL ) { + entropy->dc_count_ptrs[dctbl] = ( long * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF( long ) ); + } + MEMZERO( entropy->dc_count_ptrs[dctbl], 257 * SIZEOF( long ) ); + if ( entropy->ac_count_ptrs[actbl] == NULL ) { + entropy->ac_count_ptrs[actbl] = ( long * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF( long ) ); + } + MEMZERO( entropy->ac_count_ptrs[actbl], 257 * SIZEOF( long ) ); +#endif + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl( cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + &entropy->dc_derived_tbls[dctbl] ); + jpeg_make_c_derived_tbl( cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + &entropy->ac_derived_tbls[actbl] ); + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bit buffer to empty */ + entropy->saved.put_buffer = 0; + entropy->saved.put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jcphuff.c. + */ + +GLOBAL void +jpeg_make_c_derived_tbl( j_compress_ptr cinfo, JHUFF_TBL * htbl, + c_derived_tbl ** pdtbl ) { + c_derived_tbl *dtbl; + int p, i, l, lastp, si; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if ( *pdtbl == NULL ) { + *pdtbl = ( c_derived_tbl * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( c_derived_tbl ) ); + } + dtbl = *pdtbl; + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for ( l = 1; l <= 16; l++ ) { + for ( i = 1; i <= (int) htbl->bits[l]; i++ ) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + lastp = p; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while ( huffsize[p] ) { + while ( ( (int) huffsize[p] ) == si ) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure C.3: generate encoding tables */ + /* These are code and size indexed by symbol value */ + + /* Set any codeless symbols to have code length 0; + * this allows emit_bits to detect any attempt to emit such symbols. + */ + MEMZERO( dtbl->ehufsi, SIZEOF( dtbl->ehufsi ) ); + + for ( p = 0; p < lastp; p++ ) { + dtbl->ehufco[htbl->huffval[p]] = huffcode[p]; + dtbl->ehufsi[htbl->huffval[p]] = huffsize[p]; + } +} + + +/* Outputting bytes to the file */ + +/* Emit a byte, taking 'action' if must suspend. */ +#define emit_byte( state,val,action ) \ + { *( state )->next_output_byte++ = (JOCTET) ( val ); \ + if ( --( state )->free_in_buffer == 0 ) { \ + if ( !dump_buffer( state ) ) \ + { action; }}} + + +LOCAL boolean +dump_buffer( working_state * state ) { +/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */ + struct jpeg_destination_mgr * dest = state->cinfo->dest; + + if ( !( *dest->empty_output_buffer )( state->cinfo ) ) { + return FALSE; + } + /* After a successful buffer dump, must reset buffer pointers */ + state->next_output_byte = dest->next_output_byte; + state->free_in_buffer = dest->free_in_buffer; + return TRUE; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL boolean +emit_bits( working_state * state, unsigned int code, int size ) { +/* Emit some bits; return TRUE if successful, FALSE if must suspend */ +/* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = state->cur.put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if ( size == 0 ) { + ERREXIT( state->cinfo, JERR_HUFF_MISSING_CODE ); + } + + put_buffer &= ( ( (INT32) 1 ) << size ) - 1; /* mask off any extra bits in code */ + + put_bits += size; /* new number of bits in buffer */ + + put_buffer <<= 24 - put_bits; /* align incoming bits */ + + put_buffer |= state->cur.put_buffer; /* and merge with old buffer contents */ + + while ( put_bits >= 8 ) { + int c = (int) ( ( put_buffer >> 16 ) & 0xFF ); + + emit_byte( state, c, return FALSE ); + if ( c == 0xFF ) { /* need to stuff a zero byte? */ + emit_byte( state, 0, return FALSE ); + } + put_buffer <<= 8; + put_bits -= 8; + } + + state->cur.put_buffer = put_buffer; /* update state variables */ + state->cur.put_bits = put_bits; + + return TRUE; +} + + +LOCAL boolean +flush_bits( working_state * state ) { + if ( !emit_bits( state, 0x7F, 7 ) ) { /* fill any partial byte with ones */ + return FALSE; + } + state->cur.put_buffer = 0; /* and reset bit-buffer to empty */ + state->cur.put_bits = 0; + return TRUE; +} + + +/* Encode a single block's worth of coefficients */ + +LOCAL boolean +encode_one_block( working_state * state, JCOEFPTR block, int last_dc_val, + c_derived_tbl *dctbl, c_derived_tbl *actbl ) { + register int temp, temp2; + register int nbits; + register int k, r, i; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = temp2 = block[0] - last_dc_val; + + if ( temp < 0 ) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while ( temp ) { + nbits++; + temp >>= 1; + } + + /* Emit the Huffman-coded symbol for the number of bits */ + if ( !emit_bits( state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits] ) ) { + return FALSE; + } + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if ( nbits ) { /* emit_bits rejects calls with size 0 */ + if ( !emit_bits( state, (unsigned int) temp2, nbits ) ) { + return FALSE; + } + } + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for ( k = 1; k < DCTSIZE2; k++ ) { + if ( ( temp = block[jpeg_natural_order[k]] ) == 0 ) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while ( r > 15 ) { + if ( !emit_bits( state, actbl->ehufco[0xF0], actbl->ehufsi[0xF0] ) ) { + return FALSE; + } + r -= 16; + } + + temp2 = temp; + if ( temp < 0 ) { + temp = -temp; /* temp is abs value of input */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ( ( temp >>= 1 ) ) + nbits++; + + /* Emit Huffman symbol for run length / number of bits */ + i = ( r << 4 ) + nbits; + if ( !emit_bits( state, actbl->ehufco[i], actbl->ehufsi[i] ) ) { + return FALSE; + } + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if ( !emit_bits( state, (unsigned int) temp2, nbits ) ) { + return FALSE; + } + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if ( r > 0 ) { + if ( !emit_bits( state, actbl->ehufco[0], actbl->ehufsi[0] ) ) { + return FALSE; + } + } + + return TRUE; +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL boolean +emit_restart( working_state * state, int restart_num ) { + int ci; + + if ( !flush_bits( state ) ) { + return FALSE; + } + + emit_byte( state, 0xFF, return FALSE ); + emit_byte( state, JPEG_RST0 + restart_num, return FALSE ); + + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < state->cinfo->comps_in_scan; ci++ ) + state->cur.last_dc_val[ci] = 0; + + /* The restart counter is not updated until we successfully write the MCU. */ + + return TRUE; +} + + +/* + * Encode and output one MCU's worth of Huffman-compressed coefficients. + */ + +METHODDEF boolean +encode_mcu_huff( j_compress_ptr cinfo, JBLOCKROW *MCU_data ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + int blkn, ci; + jpeg_component_info * compptr; + + /* Load up working state */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE( state.cur, entropy->saved ); + state.cinfo = cinfo; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + if ( !emit_restart( &state, entropy->next_restart_num ) ) { + return FALSE; + } + } + } + + /* Encode the MCU data blocks */ + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + if ( !encode_one_block( &state, + MCU_data[blkn][0], state.cur.last_dc_val[ci], + entropy->dc_derived_tbls[compptr->dc_tbl_no], + entropy->ac_derived_tbls[compptr->ac_tbl_no] ) ) { + return FALSE; + } + /* Update last_dc_val */ + state.cur.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + /* Completed MCU, so update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE( entropy->saved, state.cur ); + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed scan. + */ + +METHODDEF void +finish_pass_huff( j_compress_ptr cinfo ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + + /* Load up working state ... flush_bits needs it */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE( state.cur, entropy->saved ); + state.cinfo = cinfo; + + /* Flush out the last data */ + if ( !flush_bits( &state ) ) { + ERREXIT( cinfo, JERR_CANT_SUSPEND ); + } + + /* Update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE( entropy->saved, state.cur ); +} + + +/* + * Huffman coding optimization. + * + * This actually is optimization, in the sense that we find the best possible + * Huffman table(s) for the given data. We first scan the supplied data and + * count the number of uses of each symbol that is to be Huffman-coded. + * (This process must agree with the code above.) Then we build an + * optimal Huffman coding tree for the observed counts. + * + * The JPEG standard requires Huffman codes to be no more than 16 bits long. + * If some symbols have a very small but nonzero probability, the Huffman tree + * must be adjusted to meet the code length restriction. We currently use + * the adjustment method suggested in the JPEG spec. This method is *not* + * optimal; it may not choose the best possible limited-length code. But + * since the symbols involved are infrequently used, it's not clear that + * going to extra trouble is worthwhile. + */ + +#ifdef ENTROPY_OPT_SUPPORTED + + +/* Process a single block's worth of coefficients */ + +LOCAL void +htest_one_block( JCOEFPTR block, int last_dc_val, + long dc_counts[], long ac_counts[] ) { + register int temp; + register int nbits; + register int k, r; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = block[0] - last_dc_val; + if ( temp < 0 ) { + temp = -temp; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while ( temp ) { + nbits++; + temp >>= 1; + } + + /* Count the Huffman symbol for the number of bits */ + dc_counts[nbits]++; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for ( k = 1; k < DCTSIZE2; k++ ) { + if ( ( temp = block[jpeg_natural_order[k]] ) == 0 ) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while ( r > 15 ) { + ac_counts[0xF0]++; + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + if ( temp < 0 ) { + temp = -temp; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ( ( temp >>= 1 ) ) + nbits++; + + /* Count Huffman symbol for run length / number of bits */ + ac_counts[( r << 4 ) + nbits]++; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if ( r > 0 ) { + ac_counts[0]++; + } +} + + +/* + * Trial-encode one MCU's worth of Huffman-compressed coefficients. + * No data is actually output, so no suspension return is possible. + */ + +METHODDEF boolean +encode_mcu_gather( j_compress_ptr cinfo, JBLOCKROW *MCU_data ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn, ci; + jpeg_component_info * compptr; + + /* Take care of restart intervals if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) + entropy->saved.last_dc_val[ci] = 0; + /* Update restart state */ + entropy->restarts_to_go = cinfo->restart_interval; + } + entropy->restarts_to_go--; + } + + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + htest_one_block( MCU_data[blkn][0], entropy->saved.last_dc_val[ci], + entropy->dc_count_ptrs[compptr->dc_tbl_no], + entropy->ac_count_ptrs[compptr->ac_tbl_no] ); + entropy->saved.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + return TRUE; +} + + +/* + * Generate the optimal coding for the given counts, fill htbl. + * Note this is also used by jcphuff.c. + */ + +GLOBAL void +jpeg_gen_optimal_table( j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[] ) { +#define MAX_CLEN 32 /* assumed maximum initial code length */ + UINT8 bits[MAX_CLEN + 1]; /* bits[k] = # of symbols with code length k */ + int codesize[257]; /* codesize[k] = code length of symbol k */ + int others[257]; /* next symbol in current branch of tree */ + int c1, c2; + int p, i, j; + long v; + + /* This algorithm is explained in section K.2 of the JPEG standard */ + + MEMZERO( bits, SIZEOF( bits ) ); + MEMZERO( codesize, SIZEOF( codesize ) ); + for ( i = 0; i < 257; i++ ) + others[i] = -1; /* init links to empty */ + + freq[256] = 1; /* make sure there is a nonzero count */ + /* Including the pseudo-symbol 256 in the Huffman procedure guarantees + * that no real symbol is given code-value of all ones, because 256 + * will be placed in the largest codeword category. + */ + + /* Huffman's basic algorithm to assign optimal code lengths to symbols */ + + for (;; ) { + /* Find the smallest nonzero frequency, set c1 = its symbol */ + /* In case of ties, take the larger symbol number */ + c1 = -1; + v = 1000000000L; + for ( i = 0; i <= 256; i++ ) { + if ( freq[i] && freq[i] <= v ) { + v = freq[i]; + c1 = i; + } + } + + /* Find the next smallest nonzero frequency, set c2 = its symbol */ + /* In case of ties, take the larger symbol number */ + c2 = -1; + v = 1000000000L; + for ( i = 0; i <= 256; i++ ) { + if ( freq[i] && freq[i] <= v && i != c1 ) { + v = freq[i]; + c2 = i; + } + } + + /* Done if we've merged everything into one frequency */ + if ( c2 < 0 ) { + break; + } + + /* Else merge the two counts/trees */ + freq[c1] += freq[c2]; + freq[c2] = 0; + + /* Increment the codesize of everything in c1's tree branch */ + codesize[c1]++; + while ( others[c1] >= 0 ) { + c1 = others[c1]; + codesize[c1]++; + } + + others[c1] = c2; /* chain c2 onto c1's tree branch */ + + /* Increment the codesize of everything in c2's tree branch */ + codesize[c2]++; + while ( others[c2] >= 0 ) { + c2 = others[c2]; + codesize[c2]++; + } + } + + /* Now count the number of symbols of each code length */ + for ( i = 0; i <= 256; i++ ) { + if ( codesize[i] ) { + /* The JPEG standard seems to think that this can't happen, */ + /* but I'm paranoid... */ + if ( codesize[i] > MAX_CLEN ) { + ERREXIT( cinfo, JERR_HUFF_CLEN_OVERFLOW ); + } + + bits[codesize[i]]++; + } + } + + /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure + * Huffman procedure assigned any such lengths, we must adjust the coding. + * Here is what the JPEG spec says about how this next bit works: + * Since symbols are paired for the longest Huffman code, the symbols are + * removed from this length category two at a time. The prefix for the pair + * (which is one bit shorter) is allocated to one of the pair; then, + * skipping the BITS entry for that prefix length, a code word from the next + * shortest nonzero BITS entry is converted into a prefix for two code words + * one bit longer. + */ + + for ( i = MAX_CLEN; i > 16; i-- ) { + while ( bits[i] > 0 ) { + j = i - 2; /* find length of new prefix to be used */ + while ( bits[j] == 0 ) + j--; + + bits[i] -= 2; /* remove two symbols */ + bits[i - 1]++; /* one goes in this length */ + bits[j + 1] += 2; /* two new symbols in this length */ + bits[j]--; /* symbol of this length is now a prefix */ + } + } + + /* Remove the count for the pseudo-symbol 256 from the largest codelength */ + while ( bits[i] == 0 ) /* find largest codelength still in use */ + i--; + bits[i]--; + + /* Return final symbol counts (only for lengths 0..16) */ + MEMCOPY( htbl->bits, bits, SIZEOF( htbl->bits ) ); + + /* Return a list of the symbols sorted by code length */ + /* It's not real clear to me why we don't need to consider the codelength + * changes made above, but the JPEG spec seems to think this works. + */ + p = 0; + for ( i = 1; i <= MAX_CLEN; i++ ) { + for ( j = 0; j <= 255; j++ ) { + if ( codesize[j] == i ) { + htbl->huffval[p] = (UINT8) j; + p++; + } + } + } + + /* Set sent_table FALSE so updated table will be written to JPEG file. */ + htbl->sent_table = FALSE; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF void +finish_pass_gather( j_compress_ptr cinfo ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did_dc[NUM_HUFF_TBLS]; + boolean did_ac[NUM_HUFF_TBLS]; + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO( did_dc, SIZEOF( did_dc ) ); + MEMZERO( did_ac, SIZEOF( did_ac ) ); + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + if ( !did_dc[dctbl] ) { + htblptr = &cinfo->dc_huff_tbl_ptrs[dctbl]; + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + jpeg_gen_optimal_table( cinfo, *htblptr, entropy->dc_count_ptrs[dctbl] ); + did_dc[dctbl] = TRUE; + } + if ( !did_ac[actbl] ) { + htblptr = &cinfo->ac_huff_tbl_ptrs[actbl]; + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + jpeg_gen_optimal_table( cinfo, *htblptr, entropy->ac_count_ptrs[actbl] ); + did_ac[actbl] = TRUE; + } + } +} + + +#endif /* ENTROPY_OPT_SUPPORTED */ + + +/* + * Module initialization routine for Huffman entropy encoding. + */ + +GLOBAL void +jinit_huff_encoder( j_compress_ptr cinfo ) { + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( huff_entropy_encoder ) ); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_huff; + + /* Mark tables unallocated */ + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; +#ifdef ENTROPY_OPT_SUPPORTED + entropy->dc_count_ptrs[i] = entropy->ac_count_ptrs[i] = NULL; +#endif + } +} diff --git a/src/jpeg-6/jchuff.h b/src/jpeg-6/jchuff.h new file mode 100644 index 0000000..315e720 --- /dev/null +++ b/src/jpeg-6/jchuff.h @@ -0,0 +1,34 @@ +/* + * jchuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy encoding routines + * that are shared between the sequential encoder (jchuff.c) and the + * progressive encoder (jcphuff.c). No other modules need to see these. + */ + +/* Derived data constructed for each Huffman table */ + +typedef struct { + unsigned int ehufco[256]; /* code for each symbol */ + char ehufsi[256]; /* length of code for each symbol */ + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ +} c_derived_tbl; + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_c_derived_tbl jMkCDerived +#define jpeg_gen_optimal_table jGenOptTbl +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Expand a Huffman table definition into the derived format */ +EXTERN void jpeg_make_c_derived_tbl JPP( ( j_compress_ptr cinfo, + JHUFF_TBL * htbl, c_derived_tbl * *pdtbl ) ); + +/* Generate an optimal table definition given the specified counts */ +EXTERN void jpeg_gen_optimal_table JPP( ( j_compress_ptr cinfo, + JHUFF_TBL * htbl, long freq[] ) ); diff --git a/src/jpeg-6/jcinit.c b/src/jpeg-6/jcinit.c new file mode 100644 index 0000000..a739b45 --- /dev/null +++ b/src/jpeg-6/jcinit.c @@ -0,0 +1,72 @@ +/* + * jcinit.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains initialization logic for the JPEG compressor. + * This routine is in charge of selecting the modules to be executed and + * making an initialization call to each one. + * + * Logically, this code belongs in jcmaster.c. It's split out because + * linking this routine implies linking the entire compression library. + * For a transcoding-only application, we want to be able to use jcmaster.c + * without linking in the whole library. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Master selection of compression modules. + * This is done once at the start of processing an image. We determine + * which modules will be used and give them appropriate initialization calls. + */ + +GLOBAL void +jinit_compress_master( j_compress_ptr cinfo ) { + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control( cinfo, FALSE /* full compression */ ); + + /* Preprocessing */ + if ( !cinfo->raw_data_in ) { + jinit_color_converter( cinfo ); + jinit_downsampler( cinfo ); + jinit_c_prep_controller( cinfo, FALSE /* never need full buffer here */ ); + } + /* Forward DCT */ + jinit_forward_dct( cinfo ); + /* Entropy encoding: either Huffman or arithmetic coding. */ + if ( cinfo->arith_code ) { + ERREXIT( cinfo, JERR_ARITH_NOTIMPL ); + } else { + if ( cinfo->progressive_mode ) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_huff_encoder( cinfo ); + } + } + + /* Need a full-image coefficient buffer in any multi-pass mode. */ + jinit_c_coef_controller( cinfo, + ( cinfo->num_scans > 1 || cinfo->optimize_coding ) ); + jinit_c_main_controller( cinfo, FALSE /* never need full buffer here */ ); + + jinit_marker_writer( cinfo ); + + /* We can now tell the memory manager to allocate virtual arrays. */ + ( *cinfo->mem->realize_virt_arrays )( (j_common_ptr) cinfo ); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + ( *cinfo->marker->write_file_header )( cinfo ); +} diff --git a/src/jpeg-6/jcmainct.c b/src/jpeg-6/jcmainct.c new file mode 100644 index 0000000..7570eeb --- /dev/null +++ b/src/jpeg-6/jcmainct.c @@ -0,0 +1,299 @@ +/* + * jcmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for compression. + * The main buffer lies between the pre-processor and the JPEG + * compressor proper; it holds downsampled data in the JPEG colorspace. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Note: currently, there is no operating mode in which a full-image buffer + * is needed at this step. If there were, that mode could not be used with + * "raw data" input, since this module is bypassed in that case. However, + * we've left the code here for possible use in special applications. + */ +#undef FULL_MAIN_BUFFER_SUPPORTED + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_main_controller pub; /* public fields */ + + JDIMENSION cur_iMCU_row; /* number of current iMCU row */ + JDIMENSION rowgroup_ctr; /* counts row groups received in iMCU row */ + boolean suspended; /* remember if we suspended output */ + J_BUF_MODE pass_mode; /* current operating mode */ + + /* If using just a strip buffer, this points to the entire set of buffers + * (we allocate one for each component). In the full-image case, this + * points to the currently accessible strips of the virtual arrays. + */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* If using full-image storage, this array holds pointers to virtual-array + * control blocks for each component. Unused if not full-image storage. + */ + jvirt_sarray_ptr whole_image[MAX_COMPONENTS]; +#endif +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + + +/* Forward declarations */ +METHODDEF void process_data_simple_main +JPP( ( j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION * in_row_ctr, JDIMENSION in_rows_avail ) ); +#ifdef FULL_MAIN_BUFFER_SUPPORTED +METHODDEF void process_data_buffer_main +JPP( ( j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION * in_row_ctr, JDIMENSION in_rows_avail ) ); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) { + // TTimo - don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + + /* Do nothing in raw-data mode. */ + if ( cinfo->raw_data_in ) { + return; + } + + jmain->cur_iMCU_row = 0; /* initialize counters */ + jmain->rowgroup_ctr = 0; + jmain->suspended = FALSE; + jmain->pass_mode = pass_mode; /* save mode for use by process_data */ + + switch ( pass_mode ) { + case JBUF_PASS_THRU: +#ifdef FULL_MAIN_BUFFER_SUPPORTED + if ( jmain->whole_image[0] != NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } +#endif + jmain->pub.process_data = process_data_simple_main; + break; +#ifdef FULL_MAIN_BUFFER_SUPPORTED + case JBUF_SAVE_SOURCE: + case JBUF_CRANK_DEST: + case JBUF_SAVE_AND_PASS: + if ( jmain->whole_image[0] == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + jmain->pub.process_data = process_data_buffer_main; + break; +#endif + default: + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + break; + } +} + + +/* + * Process some data. + * This routine handles the simple pass-through mode, + * where we have only a strip buffer. + */ + +METHODDEF void +process_data_simple_main( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail ) { + // TTimo - don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + + while ( jmain->cur_iMCU_row < cinfo->total_iMCU_rows ) { + /* Read input data if we haven't filled the main buffer yet */ + if ( jmain->rowgroup_ctr < DCTSIZE ) { + ( *cinfo->prep->pre_process_data )( cinfo, + input_buf, in_row_ctr, in_rows_avail, + jmain->buffer, &jmain->rowgroup_ctr, + (JDIMENSION) DCTSIZE ); + } + + /* If we don't have a full iMCU row buffered, return to application for + * more data. Note that preprocessor will always pad to fill the iMCU row + * at the bottom of the image. + */ + if ( jmain->rowgroup_ctr != DCTSIZE ) { + return; + } + + /* Send the completed row to the compressor */ + if ( !( *cinfo->coef->compress_data )( cinfo, jmain->buffer ) ) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if ( !jmain->suspended ) { + ( *in_row_ctr )--; + jmain->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if ( jmain->suspended ) { + ( *in_row_ctr )++; + jmain->suspended = FALSE; + } + jmain->rowgroup_ctr = 0; + jmain->cur_iMCU_row++; + } +} + + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + +/* + * Process some data. + * This routine handles all of the modes that use a full-size buffer. + */ + +METHODDEF void +process_data_buffer_main( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail ) { + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci; + jpeg_component_info *compptr; + boolean writing = ( main->pass_mode != JBUF_CRANK_DEST ); + + while ( main->cur_iMCU_row < cinfo->total_iMCU_rows ) { + /* Realign the virtual buffers if at the start of an iMCU row. */ + if ( main->rowgroup_ctr == 0 ) { + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + main->buffer[ci] = ( *cinfo->mem->access_virt_sarray ) + ( (j_common_ptr) cinfo, main->whole_image[ci], + main->cur_iMCU_row * ( compptr->v_samp_factor * DCTSIZE ), + (JDIMENSION) ( compptr->v_samp_factor * DCTSIZE ), writing ); + } + /* In a read pass, pretend we just read some source data. */ + if ( !writing ) { + *in_row_ctr += cinfo->max_v_samp_factor * DCTSIZE; + main->rowgroup_ctr = DCTSIZE; + } + } + + /* If a write pass, read input data until the current iMCU row is full. */ + /* Note: preprocessor will pad if necessary to fill the last iMCU row. */ + if ( writing ) { + ( *cinfo->prep->pre_process_data )( cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE ); + /* Return to application if we need more data to fill the iMCU row. */ + if ( main->rowgroup_ctr < DCTSIZE ) { + return; + } + } + + /* Emit data, unless this is a sink-only pass. */ + if ( main->pass_mode != JBUF_SAVE_SOURCE ) { + if ( !( *cinfo->coef->compress_data )( cinfo, main->buffer ) ) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if ( !main->suspended ) { + ( *in_row_ctr )--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if ( main->suspended ) { + ( *in_row_ctr )++; + main->suspended = FALSE; + } + } + + /* If get here, we are done with this iMCU row. Mark buffer empty. */ + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + +#endif /* FULL_MAIN_BUFFER_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_c_main_controller( j_compress_ptr cinfo, boolean need_full_buffer ) { + // TTimo don't use main + my_main_ptr jmain; + int ci; + jpeg_component_info *compptr; + + jmain = (my_main_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_main_controller ) ); + cinfo->main = (struct jpeg_c_main_controller *) jmain; + jmain->pub.start_pass = start_pass_main; + + /* We don't need to create a buffer in raw-data mode. */ + if ( cinfo->raw_data_in ) { + return; + } + + /* Create the buffer. It holds downsampled data, so each component + * may be of a different size. + */ + if ( need_full_buffer ) { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component */ + /* Note we pad the bottom to a multiple of the iMCU height */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + main->whole_image[ci] = ( *cinfo->mem->request_virt_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) jround_up( (long) compptr->height_in_blocks, + (long) compptr->v_samp_factor ) * DCTSIZE, + (JDIMENSION) ( compptr->v_samp_factor * DCTSIZE ) ); + } +#else + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); +#endif + } else { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + jmain->whole_image[0] = NULL; /* flag for no virtual arrays */ +#endif + /* Allocate a strip buffer for each component */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + jmain->buffer[ci] = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) ( compptr->v_samp_factor * DCTSIZE ) ); + } + } +} diff --git a/src/jpeg-6/jcmarker.c b/src/jpeg-6/jcmarker.c new file mode 100644 index 0000000..1b29a73 --- /dev/null +++ b/src/jpeg-6/jcmarker.c @@ -0,0 +1,637 @@ +/* + * jcmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to write JPEG datastream markers. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Basic output routines. + * + * Note that we do not support suspension while writing a marker. + * Therefore, an application using suspension must ensure that there is + * enough buffer space for the initial markers (typ. 600-700 bytes) before + * calling jpeg_start_compress, and enough space to write the trailing EOI + * (a few bytes) before calling jpeg_finish_compress. Multipass compression + * modes are not supported at all with suspension, so those two are the only + * points where markers will be written. + */ + +LOCAL void +emit_byte( j_compress_ptr cinfo, int val ) { +/* Emit a byte */ + struct jpeg_destination_mgr * dest = cinfo->dest; + + *( dest->next_output_byte )++ = (JOCTET) val; + if ( --dest->free_in_buffer == 0 ) { + if ( !( *dest->empty_output_buffer )( cinfo ) ) { + ERREXIT( cinfo, JERR_CANT_SUSPEND ); + } + } +} + + +LOCAL void +emit_marker( j_compress_ptr cinfo, JPEG_MARKER mark ) { +/* Emit a marker code */ + emit_byte( cinfo, 0xFF ); + emit_byte( cinfo, (int) mark ); +} + + +LOCAL void +emit_2bytes( j_compress_ptr cinfo, int value ) { +/* Emit a 2-byte integer; these are always MSB first in JPEG files */ + emit_byte( cinfo, ( value >> 8 ) & 0xFF ); + emit_byte( cinfo, value & 0xFF ); +} + + +/* + * Routines to write specific marker types. + */ + +LOCAL int +emit_dqt( j_compress_ptr cinfo, int index ) { +/* Emit a DQT marker */ +/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */ + JQUANT_TBL * qtbl = cinfo->quant_tbl_ptrs[index]; + int prec; + int i; + + if ( qtbl == NULL ) { + ERREXIT1( cinfo, JERR_NO_QUANT_TABLE, index ); + } + + prec = 0; + for ( i = 0; i < DCTSIZE2; i++ ) { + if ( qtbl->quantval[i] > 255 ) { + prec = 1; + } + } + + if ( !qtbl->sent_table ) { + emit_marker( cinfo, M_DQT ); + + emit_2bytes( cinfo, prec ? DCTSIZE2 * 2 + 1 + 2 : DCTSIZE2 + 1 + 2 ); + + emit_byte( cinfo, index + ( prec << 4 ) ); + + for ( i = 0; i < DCTSIZE2; i++ ) { + if ( prec ) { + emit_byte( cinfo, qtbl->quantval[i] >> 8 ); + } + emit_byte( cinfo, qtbl->quantval[i] & 0xFF ); + } + + qtbl->sent_table = TRUE; + } + + return prec; +} + + +LOCAL void +emit_dht( j_compress_ptr cinfo, int index, boolean is_ac ) { +/* Emit a DHT marker */ + JHUFF_TBL * htbl; + int length, i; + + if ( is_ac ) { + htbl = cinfo->ac_huff_tbl_ptrs[index]; + index += 0x10; /* output index has AC bit set */ + } else { + htbl = cinfo->dc_huff_tbl_ptrs[index]; + } + + if ( htbl == NULL ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, index ); + } + + if ( !htbl->sent_table ) { + emit_marker( cinfo, M_DHT ); + + length = 0; + for ( i = 1; i <= 16; i++ ) + length += htbl->bits[i]; + + emit_2bytes( cinfo, length + 2 + 1 + 16 ); + emit_byte( cinfo, index ); + + for ( i = 1; i <= 16; i++ ) + emit_byte( cinfo, htbl->bits[i] ); + + for ( i = 0; i < length; i++ ) + emit_byte( cinfo, htbl->huffval[i] ); + + htbl->sent_table = TRUE; + } +} + + +LOCAL void +emit_dac( j_compress_ptr cinfo ) { +/* Emit a DAC marker */ +/* Since the useful info is so small, we want to emit all the tables in */ +/* one DAC marker. Therefore this routine does its own scan of the table. */ +#ifdef C_ARITH_CODING_SUPPORTED + char dc_in_use[NUM_ARITH_TBLS]; + char ac_in_use[NUM_ARITH_TBLS]; + int length, i; + jpeg_component_info *compptr; + + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) + dc_in_use[i] = ac_in_use[i] = 0; + + for ( i = 0; i < cinfo->comps_in_scan; i++ ) { + compptr = cinfo->cur_comp_info[i]; + dc_in_use[compptr->dc_tbl_no] = 1; + ac_in_use[compptr->ac_tbl_no] = 1; + } + + length = 0; + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) + length += dc_in_use[i] + ac_in_use[i]; + + emit_marker( cinfo, M_DAC ); + + emit_2bytes( cinfo, length * 2 + 2 ); + + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) { + if ( dc_in_use[i] ) { + emit_byte( cinfo, i ); + emit_byte( cinfo, cinfo->arith_dc_L[i] + ( cinfo->arith_dc_U[i] << 4 ) ); + } + if ( ac_in_use[i] ) { + emit_byte( cinfo, i + 0x10 ); + emit_byte( cinfo, cinfo->arith_ac_K[i] ); + } + } +#endif /* C_ARITH_CODING_SUPPORTED */ +} + + +LOCAL void +emit_dri( j_compress_ptr cinfo ) { +/* Emit a DRI marker */ + emit_marker( cinfo, M_DRI ); + + emit_2bytes( cinfo, 4 ); /* fixed length */ + + emit_2bytes( cinfo, (int) cinfo->restart_interval ); +} + + +LOCAL void +emit_sof( j_compress_ptr cinfo, JPEG_MARKER code ) { +/* Emit a SOF marker */ + int ci; + jpeg_component_info *compptr; + + emit_marker( cinfo, code ); + + emit_2bytes( cinfo, 3 * cinfo->num_components + 2 + 5 + 1 ); /* length */ + + /* Make sure image isn't bigger than SOF field can handle */ + if ( (long) cinfo->image_height > 65535L || + (long) cinfo->image_width > 65535L ) { + ERREXIT1( cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) 65535 ); + } + + emit_byte( cinfo, cinfo->data_precision ); + emit_2bytes( cinfo, (int) cinfo->image_height ); + emit_2bytes( cinfo, (int) cinfo->image_width ); + + emit_byte( cinfo, cinfo->num_components ); + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + emit_byte( cinfo, compptr->component_id ); + emit_byte( cinfo, ( compptr->h_samp_factor << 4 ) + compptr->v_samp_factor ); + emit_byte( cinfo, compptr->quant_tbl_no ); + } +} + + +LOCAL void +emit_sos( j_compress_ptr cinfo ) { +/* Emit a SOS marker */ + int i, td, ta; + jpeg_component_info *compptr; + + emit_marker( cinfo, M_SOS ); + + emit_2bytes( cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3 ); /* length */ + + emit_byte( cinfo, cinfo->comps_in_scan ); + + for ( i = 0; i < cinfo->comps_in_scan; i++ ) { + compptr = cinfo->cur_comp_info[i]; + emit_byte( cinfo, compptr->component_id ); + td = compptr->dc_tbl_no; + ta = compptr->ac_tbl_no; + if ( cinfo->progressive_mode ) { + /* Progressive mode: only DC or only AC tables are used in one scan; + * furthermore, Huffman coding of DC refinement uses no table at all. + * We emit 0 for unused field(s); this is recommended by the P&M text + * but does not seem to be specified in the standard. + */ + if ( cinfo->Ss == 0 ) { + ta = 0; /* DC scan */ + if ( cinfo->Ah != 0 && !cinfo->arith_code ) { + td = 0; /* no DC table either */ + } + } else { + td = 0; /* AC scan */ + } + } + emit_byte( cinfo, ( td << 4 ) + ta ); + } + + emit_byte( cinfo, cinfo->Ss ); + emit_byte( cinfo, cinfo->Se ); + emit_byte( cinfo, ( cinfo->Ah << 4 ) + cinfo->Al ); +} + + +LOCAL void +emit_jfif_app0( j_compress_ptr cinfo ) { +/* Emit a JFIF-compliant APP0 marker */ +/* + * Length of APP0 block (2 bytes) + * Block ID (4 bytes - ASCII "JFIF") + * Zero byte (1 byte to terminate the ID string) + * Version Major, Minor (2 bytes - 0x01, 0x01) + * Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm) + * Xdpu (2 bytes - dots per unit horizontal) + * Ydpu (2 bytes - dots per unit vertical) + * Thumbnail X size (1 byte) + * Thumbnail Y size (1 byte) + */ + + emit_marker( cinfo, M_APP0 ); + + emit_2bytes( cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1 ); /* length */ + + emit_byte( cinfo, 0x4A ); /* Identifier: ASCII "JFIF" */ + emit_byte( cinfo, 0x46 ); + emit_byte( cinfo, 0x49 ); + emit_byte( cinfo, 0x46 ); + emit_byte( cinfo, 0 ); + /* We currently emit version code 1.01 since we use no 1.02 features. + * This may avoid complaints from some older decoders. + */ + emit_byte( cinfo, 1 ); /* Major version */ + emit_byte( cinfo, 1 ); /* Minor version */ + emit_byte( cinfo, cinfo->density_unit ); /* Pixel size information */ + emit_2bytes( cinfo, (int) cinfo->X_density ); + emit_2bytes( cinfo, (int) cinfo->Y_density ); + emit_byte( cinfo, 0 ); /* No thumbnail image */ + emit_byte( cinfo, 0 ); +} + + +LOCAL void +emit_adobe_app14( j_compress_ptr cinfo ) { +/* Emit an Adobe APP14 marker */ +/* + * Length of APP14 block (2 bytes) + * Block ID (5 bytes - ASCII "Adobe") + * Version Number (2 bytes - currently 100) + * Flags0 (2 bytes - currently 0) + * Flags1 (2 bytes - currently 0) + * Color transform (1 byte) + * + * Although Adobe TN 5116 mentions Version = 101, all the Adobe files + * now in circulation seem to use Version = 100, so that's what we write. + * + * We write the color transform byte as 1 if the JPEG color space is + * YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with + * whether the encoder performed a transformation, which is pretty useless. + */ + + emit_marker( cinfo, M_APP14 ); + + emit_2bytes( cinfo, 2 + 5 + 2 + 2 + 2 + 1 ); /* length */ + + emit_byte( cinfo, 0x41 ); /* Identifier: ASCII "Adobe" */ + emit_byte( cinfo, 0x64 ); + emit_byte( cinfo, 0x6F ); + emit_byte( cinfo, 0x62 ); + emit_byte( cinfo, 0x65 ); + emit_2bytes( cinfo, 100 ); /* Version */ + emit_2bytes( cinfo, 0 ); /* Flags0 */ + emit_2bytes( cinfo, 0 ); /* Flags1 */ + switch ( cinfo->jpeg_color_space ) { + case JCS_YCbCr: + emit_byte( cinfo, 1 ); /* Color transform = 1 */ + break; + case JCS_YCCK: + emit_byte( cinfo, 2 ); /* Color transform = 2 */ + break; + default: + emit_byte( cinfo, 0 ); /* Color transform = 0 */ + break; + } +} + + +/* + * This routine is exported for possible use by applications. + * The intended use is to emit COM or APPn markers after calling + * jpeg_start_compress() and before the first jpeg_write_scanlines() call + * (hence, after write_file_header but before write_frame_header). + * Other uses are not guaranteed to produce desirable results. + */ + +METHODDEF void +write_any_marker( j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen ) { +/* Emit an arbitrary marker with parameters */ + if ( datalen <= (unsigned int) 65533 ) { /* safety check */ + emit_marker( cinfo, (JPEG_MARKER) marker ); + + emit_2bytes( cinfo, (int) ( datalen + 2 ) ); /* total length */ + + while ( datalen-- ) { + emit_byte( cinfo, *dataptr ); + dataptr++; + } + } +} + + +/* + * Write datastream header. + * This consists of an SOI and optional APPn markers. + * We recommend use of the JFIF marker, but not the Adobe marker, + * when using YCbCr or grayscale data. The JFIF marker should NOT + * be used for any other JPEG colorspace. The Adobe marker is helpful + * to distinguish RGB, CMYK, and YCCK colorspaces. + * Note that an application can write additional header markers after + * jpeg_start_compress returns. + */ + +METHODDEF void +write_file_header( j_compress_ptr cinfo ) { + emit_marker( cinfo, M_SOI ); /* first the SOI */ + + if ( cinfo->write_JFIF_header ) { /* next an optional JFIF APP0 */ + emit_jfif_app0( cinfo ); + } + if ( cinfo->write_Adobe_marker ) { /* next an optional Adobe APP14 */ + emit_adobe_app14( cinfo ); + } +} + + +/* + * Write frame header. + * This consists of DQT and SOFn markers. + * Note that we do not emit the SOF until we have emitted the DQT(s). + * This avoids compatibility problems with incorrect implementations that + * try to error-check the quant table numbers as soon as they see the SOF. + */ + +METHODDEF void +write_frame_header( j_compress_ptr cinfo ) { + int ci, prec; + boolean is_baseline; + jpeg_component_info *compptr; + + /* Emit DQT for each quantization table. + * Note that emit_dqt() suppresses any duplicate tables. + */ + prec = 0; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + prec += emit_dqt( cinfo, compptr->quant_tbl_no ); + } + /* now prec is nonzero iff there are any 16-bit quant tables. */ + + /* Check for a non-baseline specification. + * Note we assume that Huffman table numbers won't be changed later. + */ + if ( cinfo->arith_code || cinfo->progressive_mode || + cinfo->data_precision != 8 ) { + is_baseline = FALSE; + } else { + is_baseline = TRUE; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( compptr->dc_tbl_no > 1 || compptr->ac_tbl_no > 1 ) { + is_baseline = FALSE; + } + } + if ( prec && is_baseline ) { + is_baseline = FALSE; + /* If it's baseline except for quantizer size, warn the user */ + TRACEMS( cinfo, 0, JTRC_16BIT_TABLES ); + } + } + + /* Emit the proper SOF marker */ + if ( cinfo->arith_code ) { + emit_sof( cinfo, M_SOF9 ); /* SOF code for arithmetic coding */ + } else { + if ( cinfo->progressive_mode ) { + emit_sof( cinfo, M_SOF2 ); /* SOF code for progressive Huffman */ + } else if ( is_baseline ) { + emit_sof( cinfo, M_SOF0 ); /* SOF code for baseline implementation */ + } else { + emit_sof( cinfo, M_SOF1 ); /* SOF code for non-baseline Huffman file */ + } + } +} + + +/* + * Write scan header. + * This consists of DHT or DAC markers, optional DRI, and SOS. + * Compressed data will be written following the SOS. + */ + +METHODDEF void +write_scan_header( j_compress_ptr cinfo ) { + int i; + jpeg_component_info *compptr; + + if ( cinfo->arith_code ) { + /* Emit arith conditioning info. We may have some duplication + * if the file has multiple scans, but it's so small it's hardly + * worth worrying about. + */ + emit_dac( cinfo ); + } else { + /* Emit Huffman tables. + * Note that emit_dht() suppresses any duplicate tables. + */ + for ( i = 0; i < cinfo->comps_in_scan; i++ ) { + compptr = cinfo->cur_comp_info[i]; + if ( cinfo->progressive_mode ) { + /* Progressive mode: only DC or only AC tables are used in one scan */ + if ( cinfo->Ss == 0 ) { + if ( cinfo->Ah == 0 ) { /* DC needs no table for refinement scan */ + emit_dht( cinfo, compptr->dc_tbl_no, FALSE ); + } + } else { + emit_dht( cinfo, compptr->ac_tbl_no, TRUE ); + } + } else { + /* Sequential mode: need both DC and AC tables */ + emit_dht( cinfo, compptr->dc_tbl_no, FALSE ); + emit_dht( cinfo, compptr->ac_tbl_no, TRUE ); + } + } + } + + /* Emit DRI if required --- note that DRI value could change for each scan. + * If it doesn't, a tiny amount of space is wasted in multiple-scan files. + * We assume DRI will never be nonzero for one scan and zero for a later one. + */ + if ( cinfo->restart_interval ) { + emit_dri( cinfo ); + } + + emit_sos( cinfo ); +} + + +/* + * Write datastream trailer. + */ + +METHODDEF void +write_file_trailer( j_compress_ptr cinfo ) { + emit_marker( cinfo, M_EOI ); +} + + +/* + * Write an abbreviated table-specification datastream. + * This consists of SOI, DQT and DHT tables, and EOI. + * Any table that is defined and not marked sent_table = TRUE will be + * emitted. Note that all tables will be marked sent_table = TRUE at exit. + */ + +METHODDEF void +write_tables_only( j_compress_ptr cinfo ) { + int i; + + emit_marker( cinfo, M_SOI ); + + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) { + if ( cinfo->quant_tbl_ptrs[i] != NULL ) { + (void) emit_dqt( cinfo, i ); + } + } + + if ( !cinfo->arith_code ) { + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + if ( cinfo->dc_huff_tbl_ptrs[i] != NULL ) { + emit_dht( cinfo, i, FALSE ); + } + if ( cinfo->ac_huff_tbl_ptrs[i] != NULL ) { + emit_dht( cinfo, i, TRUE ); + } + } + } + + emit_marker( cinfo, M_EOI ); +} + + +/* + * Initialize the marker writer module. + */ + +GLOBAL void +jinit_marker_writer( j_compress_ptr cinfo ) { + /* Create the subobject */ + cinfo->marker = (struct jpeg_marker_writer *) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( struct jpeg_marker_writer ) ); + /* Initialize method pointers */ + cinfo->marker->write_any_marker = write_any_marker; + cinfo->marker->write_file_header = write_file_header; + cinfo->marker->write_frame_header = write_frame_header; + cinfo->marker->write_scan_header = write_scan_header; + cinfo->marker->write_file_trailer = write_file_trailer; + cinfo->marker->write_tables_only = write_tables_only; +} diff --git a/src/jpeg-6/jcmaster.c b/src/jpeg-6/jcmaster.c new file mode 100644 index 0000000..40aadbf --- /dev/null +++ b/src/jpeg-6/jcmaster.c @@ -0,0 +1,604 @@ +/* + * jcmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG compressor. + * These routines are concerned with parameter validation, initial setup, + * and inter-pass control (determining the number of passes and the work + * to be done in each pass). + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef enum { + main_pass, /* input data, also do first output step */ + huff_opt_pass, /* Huffman code optimization pass */ + output_pass /* data output pass */ +} c_pass_type; + +typedef struct { + struct jpeg_comp_master pub; /* public fields */ + + c_pass_type pass_type; /* the type of the current pass */ + + int pass_number; /* # of passes completed */ + int total_passes; /* total # of passes needed */ + + int scan_number; /* current index in scan_info[] */ +} my_comp_master; + +typedef my_comp_master * my_master_ptr; + + +/* + * Support routines that do various essential calculations. + */ + +LOCAL void +initial_setup( j_compress_ptr cinfo ) { +/* Do computations that are needed before master selection phase */ + int ci; + jpeg_component_info *compptr; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Sanity check on image dimensions */ + if ( cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0 || cinfo->input_components <= 0 ) { + ERREXIT( cinfo, JERR_EMPTY_IMAGE ); + } + + /* Make sure image isn't bigger than I can handle */ + if ( (long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION ) { + ERREXIT1( cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION ); + } + + /* Width of an input scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->image_width * (long) cinfo->input_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ( (long) jd_samplesperrow != samplesperrow ) { + ERREXIT( cinfo, JERR_WIDTH_OVERFLOW ); + } + + /* For now, precision must match compiled-in value... */ + if ( cinfo->data_precision != BITS_IN_JSAMPLE ) { + ERREXIT1( cinfo, JERR_BAD_PRECISION, cinfo->data_precision ); + } + + /* Check that number of components won't exceed internal array sizes */ + if ( cinfo->num_components > MAX_COMPONENTS ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS ); + } + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( compptr->h_samp_factor <= 0 || compptr->h_samp_factor > MAX_SAMP_FACTOR || + compptr->v_samp_factor <= 0 || compptr->v_samp_factor > MAX_SAMP_FACTOR ) { + ERREXIT( cinfo, JERR_BAD_SAMPLING ); + } + cinfo->max_h_samp_factor = MAX( cinfo->max_h_samp_factor, + compptr->h_samp_factor ); + cinfo->max_v_samp_factor = MAX( cinfo->max_v_samp_factor, + compptr->v_samp_factor ); + } + + /* Compute dimensions of components */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Fill in the correct component_index value; don't rely on application */ + compptr->component_index = ci; + /* For compression, we never do DCT scaling. */ + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor ); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor ); + /* Mark component needed (this flag isn't actually used for compression) */ + compptr->component_needed = TRUE; + } + + /* Compute number of fully interleaved MCU rows (number of times that + * main controller will call coefficient controller). + */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); +} + + +#ifdef C_MULTISCAN_FILES_SUPPORTED + +LOCAL void +validate_script( j_compress_ptr cinfo ) { +/* Verify that the scan script in cinfo->scan_info[] is valid; also + * determine whether it uses progressive JPEG, and set cinfo->progressive_mode. + */ + const jpeg_scan_info * scanptr; + int scanno, ncomps, ci, coefi, thisi; + int Ss, Se, Ah, Al; + boolean component_sent[MAX_COMPONENTS]; +#ifdef C_PROGRESSIVE_SUPPORTED + int * last_bitpos_ptr; + int last_bitpos[MAX_COMPONENTS][DCTSIZE2]; + /* -1 until that coefficient has been seen; then last Al for it */ +#endif + + if ( cinfo->num_scans <= 0 ) { + ERREXIT1( cinfo, JERR_BAD_SCAN_SCRIPT, 0 ); + } + + /* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1; + * for progressive JPEG, no scan can have this. + */ + scanptr = cinfo->scan_info; + if ( scanptr->Ss != 0 || scanptr->Se != DCTSIZE2 - 1 ) { +#ifdef C_PROGRESSIVE_SUPPORTED + cinfo->progressive_mode = TRUE; + last_bitpos_ptr = &last_bitpos[0][0]; + for ( ci = 0; ci < cinfo->num_components; ci++ ) + for ( coefi = 0; coefi < DCTSIZE2; coefi++ ) + *last_bitpos_ptr++ = -1; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + cinfo->progressive_mode = FALSE; + for ( ci = 0; ci < cinfo->num_components; ci++ ) + component_sent[ci] = FALSE; + } + + for ( scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++ ) { + /* Validate component indexes */ + ncomps = scanptr->comps_in_scan; + if ( ncomps <= 0 || ncomps > MAX_COMPS_IN_SCAN ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN ); + } + for ( ci = 0; ci < ncomps; ci++ ) { + thisi = scanptr->component_index[ci]; + if ( thisi < 0 || thisi >= cinfo->num_components ) { + ERREXIT1( cinfo, JERR_BAD_SCAN_SCRIPT, scanno ); + } + /* Components must appear in SOF order within each scan */ + if ( ci > 0 && thisi <= scanptr->component_index[ci - 1] ) { + ERREXIT1( cinfo, JERR_BAD_SCAN_SCRIPT, scanno ); + } + } + /* Validate progression parameters */ + Ss = scanptr->Ss; + Se = scanptr->Se; + Ah = scanptr->Ah; + Al = scanptr->Al; + if ( cinfo->progressive_mode ) { +#ifdef C_PROGRESSIVE_SUPPORTED + if ( Ss < 0 || Ss >= DCTSIZE2 || Se < Ss || Se >= DCTSIZE2 || + Ah < 0 || Ah > 13 || Al < 0 || Al > 13 ) { + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + if ( Ss == 0 ) { + if ( Se != 0 ) { /* DC and AC together not OK */ + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + } else { + if ( ncomps != 1 ) { /* AC scans must be for only one component */ + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + } + for ( ci = 0; ci < ncomps; ci++ ) { + last_bitpos_ptr = &last_bitpos[scanptr->component_index[ci]][0]; + if ( Ss != 0 && last_bitpos_ptr[0] < 0 ) { /* AC without prior DC scan */ + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + for ( coefi = Ss; coefi <= Se; coefi++ ) { + if ( last_bitpos_ptr[coefi] < 0 ) { + /* first scan of this coefficient */ + if ( Ah != 0 ) { + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + } else { + /* not first scan */ + if ( Ah != last_bitpos_ptr[coefi] || Al != Ah - 1 ) { + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + } + last_bitpos_ptr[coefi] = Al; + } + } +#endif + } else { + /* For sequential JPEG, all progression parameters must be these: */ + if ( Ss != 0 || Se != DCTSIZE2 - 1 || Ah != 0 || Al != 0 ) { + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + /* Make sure components are not sent twice */ + for ( ci = 0; ci < ncomps; ci++ ) { + thisi = scanptr->component_index[ci]; + if ( component_sent[thisi] ) { + ERREXIT1( cinfo, JERR_BAD_SCAN_SCRIPT, scanno ); + } + component_sent[thisi] = TRUE; + } + } + } + + /* Now verify that everything got sent. */ + if ( cinfo->progressive_mode ) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* For progressive mode, we only check that at least some DC data + * got sent for each component; the spec does not require that all bits + * of all coefficients be transmitted. Would it be wiser to enforce + * transmission of all coefficient bits?? + */ + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + if ( last_bitpos[ci][0] < 0 ) { + ERREXIT( cinfo, JERR_MISSING_DATA ); + } + } +#endif + } else { + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + if ( !component_sent[ci] ) { + ERREXIT( cinfo, JERR_MISSING_DATA ); + } + } + } +} + +#endif /* C_MULTISCAN_FILES_SUPPORTED */ + + +LOCAL void +select_scan_parameters( j_compress_ptr cinfo ) { +/* Set up the scan parameters for the current scan */ + int ci; + +#ifdef C_MULTISCAN_FILES_SUPPORTED + if ( cinfo->scan_info != NULL ) { + /* Prepare for current scan --- the script is already validated */ + my_master_ptr master = (my_master_ptr) cinfo->master; + const jpeg_scan_info * scanptr = cinfo->scan_info + master->scan_number; + + cinfo->comps_in_scan = scanptr->comps_in_scan; + for ( ci = 0; ci < scanptr->comps_in_scan; ci++ ) { + cinfo->cur_comp_info[ci] = + &cinfo->comp_info[scanptr->component_index[ci]]; + } + cinfo->Ss = scanptr->Ss; + cinfo->Se = scanptr->Se; + cinfo->Ah = scanptr->Ah; + cinfo->Al = scanptr->Al; + } else +#endif + { + /* Prepare for single sequential-JPEG scan containing all components */ + if ( cinfo->num_components > MAX_COMPS_IN_SCAN ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPS_IN_SCAN ); + } + cinfo->comps_in_scan = cinfo->num_components; + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci]; + } + cinfo->Ss = 0; + cinfo->Se = DCTSIZE2 - 1; + cinfo->Ah = 0; + cinfo->Al = 0; + } +} + + +LOCAL void +per_scan_setup( j_compress_ptr cinfo ) { +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if ( cinfo->comps_in_scan == 1 ) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = DCTSIZE; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( tmp == 0 ) { + tmp = compptr->v_samp_factor; + } + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if ( cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN ); + } + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + + cinfo->blocks_in_MCU = 0; + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * DCTSIZE; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) ( compptr->width_in_blocks % compptr->MCU_width ); + if ( tmp == 0 ) { + tmp = compptr->MCU_width; + } + compptr->last_col_width = tmp; + tmp = (int) ( compptr->height_in_blocks % compptr->MCU_height ); + if ( tmp == 0 ) { + tmp = compptr->MCU_height; + } + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if ( cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU ) { + ERREXIT( cinfo, JERR_BAD_MCU_SIZE ); + } + while ( mcublks-- > 0 ) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } + + /* Convert restart specified in rows to actual MCU count. */ + /* Note that count must fit in 16 bits, so we provide limiting. */ + if ( cinfo->restart_in_rows > 0 ) { + long nominal = (long) cinfo->restart_in_rows * (long) cinfo->MCUs_per_row; + cinfo->restart_interval = (unsigned int) MIN( nominal, 65535L ); + } +} + + +/* + * Per-pass setup. + * This is called at the beginning of each pass. We determine which modules + * will be active during this pass and give them appropriate start_pass calls. + * We also set is_last_pass to indicate whether any more passes will be + * required. + */ + +METHODDEF void +prepare_for_pass( j_compress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + switch ( master->pass_type ) { + case main_pass: + /* Initial pass: will collect input data, and do either Huffman + * optimization or data output for the first scan. + */ + select_scan_parameters( cinfo ); + per_scan_setup( cinfo ); + if ( !cinfo->raw_data_in ) { + ( *cinfo->cconvert->start_pass )( cinfo ); + ( *cinfo->downsample->start_pass )( cinfo ); + ( *cinfo->prep->start_pass )( cinfo, JBUF_PASS_THRU ); + } + ( *cinfo->fdct->start_pass )( cinfo ); + ( *cinfo->entropy->start_pass )( cinfo, cinfo->optimize_coding ); + ( *cinfo->coef->start_pass )( cinfo, + ( master->total_passes > 1 ? + JBUF_SAVE_AND_PASS : JBUF_PASS_THRU ) ); + ( *cinfo->main->start_pass )( cinfo, JBUF_PASS_THRU ); + if ( cinfo->optimize_coding ) { + /* No immediate data output; postpone writing frame/scan headers */ + master->pub.call_pass_startup = FALSE; + } else { + /* Will write frame/scan headers at first jpeg_write_scanlines call */ + master->pub.call_pass_startup = TRUE; + } + break; +#ifdef ENTROPY_OPT_SUPPORTED + case huff_opt_pass: + /* Do Huffman optimization for a scan after the first one. */ + select_scan_parameters( cinfo ); + per_scan_setup( cinfo ); + if ( cinfo->Ss != 0 || cinfo->Ah == 0 || cinfo->arith_code ) { + ( *cinfo->entropy->start_pass )( cinfo, TRUE ); + ( *cinfo->coef->start_pass )( cinfo, JBUF_CRANK_DEST ); + master->pub.call_pass_startup = FALSE; + break; + } + /* Special case: Huffman DC refinement scans need no Huffman table + * and therefore we can skip the optimization pass for them. + */ + master->pass_type = output_pass; + master->pass_number++; + /*FALLTHROUGH*/ +#endif + case output_pass: + /* Do a data-output pass. */ + /* We need not repeat per-scan setup if prior optimization pass did it. */ + if ( !cinfo->optimize_coding ) { + select_scan_parameters( cinfo ); + per_scan_setup( cinfo ); + } + ( *cinfo->entropy->start_pass )( cinfo, FALSE ); + ( *cinfo->coef->start_pass )( cinfo, JBUF_CRANK_DEST ); + /* We emit frame/scan headers now */ + if ( master->scan_number == 0 ) { + ( *cinfo->marker->write_frame_header )( cinfo ); + } + ( *cinfo->marker->write_scan_header )( cinfo ); + master->pub.call_pass_startup = FALSE; + break; + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + } + + master->pub.is_last_pass = ( master->pass_number == master->total_passes - 1 ); + + /* Set up progress monitor's pass info if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->total_passes; + } +} + + +/* + * Special start-of-pass hook. + * This is called by jpeg_write_scanlines if call_pass_startup is TRUE. + * In single-pass processing, we need this hook because we don't want to + * write frame/scan headers during jpeg_start_compress; we want to let the + * application write COM markers etc. between jpeg_start_compress and the + * jpeg_write_scanlines loop. + * In multi-pass processing, this routine is not used. + */ + +METHODDEF void +pass_startup( j_compress_ptr cinfo ) { + cinfo->master->call_pass_startup = FALSE; /* reset flag so call only once */ + + ( *cinfo->marker->write_frame_header )( cinfo ); + ( *cinfo->marker->write_scan_header )( cinfo ); +} + + +/* + * Finish up at end of pass. + */ + +METHODDEF void +finish_pass_master( j_compress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* The entropy coder always needs an end-of-pass call, + * either to analyze statistics or to flush its output buffer. + */ + ( *cinfo->entropy->finish_pass )( cinfo ); + + /* Update state for next pass */ + switch ( master->pass_type ) { + case main_pass: + /* next pass is either output of scan 0 (after optimization) + * or output of scan 1 (if no optimization). + */ + master->pass_type = output_pass; + if ( !cinfo->optimize_coding ) { + master->scan_number++; + } + break; + case huff_opt_pass: + /* next pass is always output of current scan */ + master->pass_type = output_pass; + break; + case output_pass: + /* next pass is either optimization or output of next scan */ + if ( cinfo->optimize_coding ) { + master->pass_type = huff_opt_pass; + } + master->scan_number++; + break; + } + + master->pass_number++; +} + + +/* + * Initialize master compression control. + */ + +GLOBAL void +jinit_c_master_control( j_compress_ptr cinfo, boolean transcode_only ) { + my_master_ptr master; + + master = (my_master_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_comp_master ) ); + cinfo->master = (struct jpeg_comp_master *) master; + master->pub.prepare_for_pass = prepare_for_pass; + master->pub.pass_startup = pass_startup; + master->pub.finish_pass = finish_pass_master; + master->pub.is_last_pass = FALSE; + + /* Validate parameters, determine derived values */ + initial_setup( cinfo ); + + if ( cinfo->scan_info != NULL ) { +#ifdef C_MULTISCAN_FILES_SUPPORTED + validate_script( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + cinfo->progressive_mode = FALSE; + cinfo->num_scans = 1; + } + + if ( cinfo->progressive_mode ) { /* TEMPORARY HACK ??? */ + cinfo->optimize_coding = TRUE; /* assume default tables no good for progressive mode */ + + } + /* Initialize my private state */ + if ( transcode_only ) { + /* no main pass in transcoding */ + if ( cinfo->optimize_coding ) { + master->pass_type = huff_opt_pass; + } else { + master->pass_type = output_pass; + } + } else { + /* for normal compression, first pass is always this type: */ + master->pass_type = main_pass; + } + master->scan_number = 0; + master->pass_number = 0; + if ( cinfo->optimize_coding ) { + master->total_passes = cinfo->num_scans * 2; + } else { + master->total_passes = cinfo->num_scans; + } +} diff --git a/src/jpeg-6/jcomapi.c b/src/jpeg-6/jcomapi.c new file mode 100644 index 0000000..9a4cf1b --- /dev/null +++ b/src/jpeg-6/jcomapi.c @@ -0,0 +1,91 @@ +/* + * jcomapi.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface routines that are used for both + * compression and decompression. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Abort processing of a JPEG compression or decompression operation, + * but don't destroy the object itself. + * + * For this, we merely clean up all the nonpermanent memory pools. + * Note that temp files (virtual arrays) are not allowed to belong to + * the permanent pool, so we will be able to close all temp files here. + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_abort( j_common_ptr cinfo ) { + int pool; + + /* Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for ( pool = JPOOL_NUMPOOLS - 1; pool > JPOOL_PERMANENT; pool-- ) { + ( *cinfo->mem->free_pool )( cinfo, pool ); + } + + /* Reset overall state for possible reuse of object */ + cinfo->global_state = ( cinfo->is_decompressor ? DSTATE_START : CSTATE_START ); +} + + +/* + * Destruction of a JPEG object. + * + * Everything gets deallocated except the master jpeg_compress_struct itself + * and the error manager struct. Both of these are supplied by the application + * and must be freed, if necessary, by the application. (Often they are on + * the stack and so don't need to be freed anyway.) + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_destroy( j_common_ptr cinfo ) { + /* We need only tell the memory manager to release everything. */ + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + if ( cinfo->mem != NULL ) { + ( *cinfo->mem->self_destruct )( cinfo ); + } + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0; /* mark it destroyed */ +} + + +/* + * Convenience routines for allocating quantization and Huffman tables. + * (Would jutils.c be a more reasonable place to put these?) + */ + +GLOBAL JQUANT_TBL * +jpeg_alloc_quant_table( j_common_ptr cinfo ) { + JQUANT_TBL *tbl; + + tbl = ( JQUANT_TBL * ) + ( *cinfo->mem->alloc_small )( cinfo, JPOOL_PERMANENT, SIZEOF( JQUANT_TBL ) ); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} + + +GLOBAL JHUFF_TBL * +jpeg_alloc_huff_table( j_common_ptr cinfo ) { + JHUFF_TBL *tbl; + + tbl = ( JHUFF_TBL * ) + ( *cinfo->mem->alloc_small )( cinfo, JPOOL_PERMANENT, SIZEOF( JHUFF_TBL ) ); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} diff --git a/src/jpeg-6/jconfig.h b/src/jpeg-6/jconfig.h new file mode 100644 index 0000000..87eca26 --- /dev/null +++ b/src/jpeg-6/jconfig.h @@ -0,0 +1,41 @@ +/* jconfig.wat --- jconfig.h for Watcom C/C++ on MS-DOS or OS/2. */ +/* see jconfig.doc for explanations */ + +#define HAVE_PROTOTYPES +#define HAVE_UNSIGNED_CHAR +#define HAVE_UNSIGNED_SHORT +/* #define void char */ +/* #define const */ +#define CHAR_IS_UNSIGNED +#define HAVE_STDDEF_H +#define HAVE_STDLIB_H +#undef NEED_BSD_STRINGS +#undef NEED_SYS_TYPES_H +#undef NEED_FAR_POINTERS /* Watcom uses flat 32-bit addressing */ +#undef NEED_SHORT_EXTERNAL_NAMES +#undef INCOMPLETE_TYPES_BROKEN + +#define JDCT_DEFAULT JDCT_FLOAT +#define JDCT_FASTEST JDCT_FLOAT + +#ifdef JPEG_INTERNALS + +#undef RIGHT_SHIFT_IS_UNSIGNED + +#endif /* JPEG_INTERNALS */ + +#ifdef JPEG_CJPEG_DJPEG + +#define BMP_SUPPORTED /* BMP image file format */ +#define GIF_SUPPORTED /* GIF image file format */ +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ +#undef RLE_SUPPORTED /* Utah RLE image file format */ +#define TARGA_SUPPORTED /* Targa image file format */ + +#undef TWO_FILE_COMMANDLINE /* optional */ +#define USE_SETMODE /* Needed to make one-file style work in Watcom */ +#undef NEED_SIGNAL_CATCHER /* Define this if you use jmemname.c */ +#undef DONT_USE_B_MODE +#undef PROGRESS_REPORT /* optional */ + +#endif /* JPEG_CJPEG_DJPEG */ diff --git a/src/jpeg-6/jcparam.c b/src/jpeg-6/jcparam.c new file mode 100644 index 0000000..88101d9 --- /dev/null +++ b/src/jpeg-6/jcparam.c @@ -0,0 +1,582 @@ +/* + * jcparam.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains optional default-setting code for the JPEG compressor. + * Applications do not have to use this file, but those that don't use it + * must know a lot more about the innards of the JPEG code. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Quantization table setup routines + */ + +GLOBAL void +jpeg_add_quant_table( j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, boolean force_baseline ) { +/* Define a quantization table equal to the basic_table times + * a scale factor (given as a percentage). + * If force_baseline is TRUE, the computed quantization table entries + * are limited to 1..255 for JPEG baseline compatibility. + */ + JQUANT_TBL ** qtblptr = &cinfo->quant_tbl_ptrs[which_tbl]; + int i; + long temp; + + /* Safety check to ensure start_compress not called yet. */ + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + if ( *qtblptr == NULL ) { + *qtblptr = jpeg_alloc_quant_table( (j_common_ptr) cinfo ); + } + + for ( i = 0; i < DCTSIZE2; i++ ) { + temp = ( (long) basic_table[i] * scale_factor + 50L ) / 100L; + /* limit the values to the valid range */ + if ( temp <= 0L ) { + temp = 1L; + } + if ( temp > 32767L ) { + temp = 32767L; /* max quantizer needed for 12 bits */ + } + if ( force_baseline && temp > 255L ) { + temp = 255L; /* limit to baseline range if requested */ + } + ( *qtblptr )->quantval[i] = (UINT16) temp; + } + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + ( *qtblptr )->sent_table = FALSE; +} + + +GLOBAL void +jpeg_set_linear_quality( j_compress_ptr cinfo, int scale_factor, + boolean force_baseline ) { +/* Set or change the 'quality' (quantization) setting, using default tables + * and a straight percentage-scaling quality scale. In most cases it's better + * to use jpeg_set_quality (below); this entry point is provided for + * applications that insist on a linear percentage scaling. + */ +/* This is the sample quantization table given in the JPEG spec section K.1, + * but expressed in zigzag order (as are all of our quant. tables). + * The spec says that the values given produce "good" quality, and + * when divided by 2, "very good" quality. + */ + static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = { + 16, 11, 12, 14, 12, 10, 16, 14, + 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, + 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, + 87, 69, 55, 56, 80, 109, 81, 87, + 95, 98, 103, 104, 103, 62, 77, 113, + 121, 112, 100, 120, 92, 101, 103, 99 + }; + static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = { + 17, 18, 18, 24, 21, 24, 47, 26, + 26, 47, 99, 66, 56, 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, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + }; + + /* Set up two quantization tables using the specified scaling */ + jpeg_add_quant_table( cinfo, 0, std_luminance_quant_tbl, + scale_factor, force_baseline ); + jpeg_add_quant_table( cinfo, 1, std_chrominance_quant_tbl, + scale_factor, force_baseline ); +} + + +GLOBAL int +jpeg_quality_scaling( int quality ) { +/* Convert a user-specified quality rating to a percentage scaling factor + * for an underlying quantization table, using our recommended scaling curve. + * The input 'quality' factor should be 0 (terrible) to 100 (very good). + */ +/* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ + if ( quality <= 0 ) { + quality = 1; + } + if ( quality > 100 ) { + quality = 100; + } + + /* The basic table is used as-is (scaling 100) for a quality of 50. + * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; + * note that at Q=100 the scaling is 0, which will cause j_add_quant_table + * to make all the table entries 1 (hence, no quantization loss). + * Qualities 1..50 are converted to scaling percentage 5000/Q. + */ + if ( quality < 50 ) { + quality = 5000 / quality; + } else { + quality = 200 - quality * 2; + } + + return quality; +} + + +GLOBAL void +jpeg_set_quality( j_compress_ptr cinfo, int quality, boolean force_baseline ) { +/* Set or change the 'quality' (quantization) setting, using default tables. + * This is the standard quality-adjusting entry point for typical user + * interfaces; only those who want detailed control over quantization tables + * would use the preceding three routines directly. + */ +/* Convert user 0-100 rating to percentage scaling */ + quality = jpeg_quality_scaling( quality ); + + /* Set up standard quality tables */ + jpeg_set_linear_quality( cinfo, quality, force_baseline ); +} + + +/* + * Huffman table setup routines + */ + +LOCAL void +add_huff_table( j_compress_ptr cinfo, + JHUFF_TBL **htblptr, const UINT8 *bits, const UINT8 *val ) { +/* Define a Huffman table */ + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + + MEMCOPY( ( *htblptr )->bits, bits, SIZEOF( ( *htblptr )->bits ) ); + MEMCOPY( ( *htblptr )->huffval, val, SIZEOF( ( *htblptr )->huffval ) ); + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + ( *htblptr )->sent_table = FALSE; +} + + +LOCAL void +std_huff_tables( j_compress_ptr cinfo ) { +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ + static const UINT8 bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_luminance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_chrominance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; + static const UINT8 val_ac_luminance[] = + { 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 UINT8 bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + static const UINT8 val_ac_chrominance[] = + { 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 }; + + add_huff_table( cinfo, &cinfo->dc_huff_tbl_ptrs[0], + bits_dc_luminance, val_dc_luminance ); + add_huff_table( cinfo, &cinfo->ac_huff_tbl_ptrs[0], + bits_ac_luminance, val_ac_luminance ); + add_huff_table( cinfo, &cinfo->dc_huff_tbl_ptrs[1], + bits_dc_chrominance, val_dc_chrominance ); + add_huff_table( cinfo, &cinfo->ac_huff_tbl_ptrs[1], + bits_ac_chrominance, val_ac_chrominance ); +} + + +/* + * Default parameter setup for compression. + * + * Applications that don't choose to use this routine must do their + * own setup of all these parameters. Alternately, you can call this + * to establish defaults and then alter parameters selectively. This + * is the recommended approach since, if we add any new parameters, + * your code will still work (they'll be set to reasonable defaults). + */ + +GLOBAL void +jpeg_set_defaults( j_compress_ptr cinfo ) { + int i; + + /* Safety check to ensure start_compress not called yet. */ + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + /* Allocate comp_info array large enough for maximum component count. + * Array is made permanent in case application wants to compress + * multiple images at same param settings. + */ + if ( cinfo->comp_info == NULL ) { + cinfo->comp_info = ( jpeg_component_info * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + MAX_COMPONENTS * SIZEOF( jpeg_component_info ) ); + } + + /* Initialize everything not dependent on the color space */ + + cinfo->data_precision = BITS_IN_JSAMPLE; + /* Set up two quantization tables using default quality of 75 */ + jpeg_set_quality( cinfo, 75, TRUE ); + /* Set up two Huffman tables */ + std_huff_tables( cinfo ); + + /* Initialize default arithmetic coding conditioning */ + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + + /* Default is no multiple-scan output */ + cinfo->scan_info = NULL; + cinfo->num_scans = 0; + + /* Expect normal source image, not raw downsampled data */ + cinfo->raw_data_in = FALSE; + + /* Use Huffman coding, not arithmetic coding, by default */ + cinfo->arith_code = FALSE; + + /* By default, don't do extra passes to optimize entropy coding */ + cinfo->optimize_coding = FALSE; + /* The standard Huffman tables are only valid for 8-bit data precision. + * If the precision is higher, force optimization on so that usable + * tables will be computed. This test can be removed if default tables + * are supplied that are valid for the desired precision. + */ + if ( cinfo->data_precision > 8 ) { + cinfo->optimize_coding = TRUE; + } + + /* By default, use the simpler non-cosited sampling alignment */ + cinfo->CCIR601_sampling = FALSE; + + /* No input smoothing */ + cinfo->smoothing_factor = 0; + + /* DCT algorithm preference */ + cinfo->dct_method = JDCT_DEFAULT; + + /* No restart markers */ + cinfo->restart_interval = 0; + cinfo->restart_in_rows = 0; + + /* Fill in default JFIF marker parameters. Note that whether the marker + * will actually be written is determined by jpeg_set_colorspace. + */ + cinfo->density_unit = 0; /* Pixel size is unknown by default */ + cinfo->X_density = 1; /* Pixel aspect ratio is square by default */ + cinfo->Y_density = 1; + + /* Choose JPEG colorspace based on input space, set defaults accordingly */ + + jpeg_default_colorspace( cinfo ); +} + + +/* + * Select an appropriate JPEG colorspace for in_color_space. + */ + +GLOBAL void +jpeg_default_colorspace( j_compress_ptr cinfo ) { + switch ( cinfo->in_color_space ) { + case JCS_GRAYSCALE: + jpeg_set_colorspace( cinfo, JCS_GRAYSCALE ); + break; + case JCS_RGB: + jpeg_set_colorspace( cinfo, JCS_YCbCr ); + break; + case JCS_YCbCr: + jpeg_set_colorspace( cinfo, JCS_YCbCr ); + break; + case JCS_CMYK: + jpeg_set_colorspace( cinfo, JCS_CMYK ); /* By default, no translation */ + break; + case JCS_YCCK: + jpeg_set_colorspace( cinfo, JCS_YCCK ); + break; + case JCS_UNKNOWN: + jpeg_set_colorspace( cinfo, JCS_UNKNOWN ); + break; + default: + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } +} + + +/* + * Set the JPEG colorspace, and choose colorspace-dependent default values. + */ + +GLOBAL void +jpeg_set_colorspace( j_compress_ptr cinfo, J_COLOR_SPACE colorspace ) { + jpeg_component_info * compptr; + int ci; + +#define SET_COMP( index,id,hsamp,vsamp,quant,dctbl,actbl ) \ + ( compptr = &cinfo->comp_info[index], \ + compptr->component_id = ( id ), \ + compptr->h_samp_factor = ( hsamp ), \ + compptr->v_samp_factor = ( vsamp ), \ + compptr->quant_tbl_no = ( quant ), \ + compptr->dc_tbl_no = ( dctbl ), \ + compptr->ac_tbl_no = ( actbl ) ) + + /* Safety check to ensure start_compress not called yet. */ + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + /* For all colorspaces, we use Q and Huff tables 0 for luminance components, + * tables 1 for chrominance components. + */ + + cinfo->jpeg_color_space = colorspace; + + cinfo->write_JFIF_header = FALSE; /* No marker for non-JFIF colorspaces */ + cinfo->write_Adobe_marker = FALSE; /* write no Adobe marker by default */ + + switch ( colorspace ) { + case JCS_GRAYSCALE: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 1; + /* JFIF specifies component ID 1 */ + SET_COMP( 0, 1, 1,1, 0, 0,0 ); + break; + case JCS_RGB: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag RGB */ + cinfo->num_components = 3; + SET_COMP( 0, 0x52 /* 'R' */, 1,1, 0, 0,0 ); + SET_COMP( 1, 0x47 /* 'G' */, 1,1, 0, 0,0 ); + SET_COMP( 2, 0x42 /* 'B' */, 1,1, 0, 0,0 ); + break; + case JCS_YCbCr: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 3; + /* JFIF specifies component IDs 1,2,3 */ + /* We default to 2x2 subsamples of chrominance */ + SET_COMP( 0, 1, 2,2, 0, 0,0 ); + SET_COMP( 1, 2, 1,1, 1, 1,1 ); + SET_COMP( 2, 3, 1,1, 1, 1,1 ); + break; + case JCS_CMYK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag CMYK */ + cinfo->num_components = 4; + SET_COMP( 0, 0x43 /* 'C' */, 1,1, 0, 0,0 ); + SET_COMP( 1, 0x4D /* 'M' */, 1,1, 0, 0,0 ); + SET_COMP( 2, 0x59 /* 'Y' */, 1,1, 0, 0,0 ); + SET_COMP( 3, 0x4B /* 'K' */, 1,1, 0, 0,0 ); + break; + case JCS_YCCK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag YCCK */ + cinfo->num_components = 4; + SET_COMP( 0, 1, 2,2, 0, 0,0 ); + SET_COMP( 1, 2, 1,1, 1, 1,1 ); + SET_COMP( 2, 3, 1,1, 1, 1,1 ); + SET_COMP( 3, 4, 2,2, 0, 0,0 ); + break; + case JCS_UNKNOWN: + cinfo->num_components = cinfo->input_components; + if ( cinfo->num_components < 1 || cinfo->num_components > MAX_COMPONENTS ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS ); + } + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + SET_COMP( ci, ci, 1,1, 0, 0,0 ); + } + break; + default: + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } +} + + +#ifdef C_PROGRESSIVE_SUPPORTED + +LOCAL jpeg_scan_info * +fill_a_scan( jpeg_scan_info * scanptr, int ci, + int Ss, int Se, int Ah, int Al ) { +/* Support routine: generate one scan for specified component */ + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + return scanptr; +} + +LOCAL jpeg_scan_info * +fill_scans( jpeg_scan_info * scanptr, int ncomps, + int Ss, int Se, int Ah, int Al ) { +/* Support routine: generate one scan for each component */ + int ci; + + for ( ci = 0; ci < ncomps; ci++ ) { + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } + return scanptr; +} + +LOCAL jpeg_scan_info * +fill_dc_scans( jpeg_scan_info * scanptr, int ncomps, int Ah, int Al ) { +/* Support routine: generate interleaved DC scan if possible, else N scans */ + int ci; + + if ( ncomps <= MAX_COMPS_IN_SCAN ) { + /* Single interleaved DC scan */ + scanptr->comps_in_scan = ncomps; + for ( ci = 0; ci < ncomps; ci++ ) + scanptr->component_index[ci] = ci; + scanptr->Ss = scanptr->Se = 0; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } else { + /* Noninterleaved DC scan for each component */ + scanptr = fill_scans( scanptr, ncomps, 0, 0, Ah, Al ); + } + return scanptr; +} + + +/* + * Create a recommended progressive-JPEG script. + * cinfo->num_components and cinfo->jpeg_color_space must be correct. + */ + +GLOBAL void +jpeg_simple_progression( j_compress_ptr cinfo ) { + int ncomps = cinfo->num_components; + int nscans; + jpeg_scan_info * scanptr; + + /* Safety check to ensure start_compress not called yet. */ + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + /* Figure space needed for script. Calculation must match code below! */ + if ( ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr ) { + /* Custom script for YCbCr color images. */ + nscans = 10; + } else { + /* All-purpose script for other color spaces. */ + if ( ncomps > MAX_COMPS_IN_SCAN ) { + nscans = 6 * ncomps; /* 2 DC + 4 AC scans per component */ + } else { + nscans = 2 + 4 * ncomps; /* 2 DC scans; 4 AC scans per component */ + } + } + + /* Allocate space for script. */ + /* We use permanent pool just in case application re-uses script. */ + scanptr = ( jpeg_scan_info * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + nscans * SIZEOF( jpeg_scan_info ) ); + cinfo->scan_info = scanptr; + cinfo->num_scans = nscans; + + if ( ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr ) { + /* Custom script for YCbCr color images. */ + /* Initial DC scan */ + scanptr = fill_dc_scans( scanptr, ncomps, 0, 1 ); + /* Initial AC scan: get some luma data out in a hurry */ + scanptr = fill_a_scan( scanptr, 0, 1, 5, 0, 2 ); + /* Chroma data is too small to be worth expending many scans on */ + scanptr = fill_a_scan( scanptr, 2, 1, 63, 0, 1 ); + scanptr = fill_a_scan( scanptr, 1, 1, 63, 0, 1 ); + /* Complete spectral selection for luma AC */ + scanptr = fill_a_scan( scanptr, 0, 6, 63, 0, 2 ); + /* Refine next bit of luma AC */ + scanptr = fill_a_scan( scanptr, 0, 1, 63, 2, 1 ); + /* Finish DC successive approximation */ + scanptr = fill_dc_scans( scanptr, ncomps, 1, 0 ); + /* Finish AC successive approximation */ + scanptr = fill_a_scan( scanptr, 2, 1, 63, 1, 0 ); + scanptr = fill_a_scan( scanptr, 1, 1, 63, 1, 0 ); + /* Luma bottom bit comes last since it's usually largest scan */ + scanptr = fill_a_scan( scanptr, 0, 1, 63, 1, 0 ); + } else { + /* All-purpose script for other color spaces. */ + /* Successive approximation first pass */ + scanptr = fill_dc_scans( scanptr, ncomps, 0, 1 ); + scanptr = fill_scans( scanptr, ncomps, 1, 5, 0, 2 ); + scanptr = fill_scans( scanptr, ncomps, 6, 63, 0, 2 ); + /* Successive approximation second pass */ + scanptr = fill_scans( scanptr, ncomps, 1, 63, 2, 1 ); + /* Successive approximation final pass */ + scanptr = fill_dc_scans( scanptr, ncomps, 1, 0 ); + scanptr = fill_scans( scanptr, ncomps, 1, 63, 1, 0 ); + } +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/src/jpeg-6/jcphuff.c b/src/jpeg-6/jcphuff.c new file mode 100644 index 0000000..0473554 --- /dev/null +++ b/src/jpeg-6/jcphuff.c @@ -0,0 +1,844 @@ +/* + * jcphuff.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines for progressive JPEG. + * + * We do not support output suspension in this module, since the library + * currently does not allow multiple-scan files to be written with output + * suspension. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jchuff.c */ + +#ifdef C_PROGRESSIVE_SUPPORTED + +/* Expanded entropy encoder object for progressive Huffman encoding. */ + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + /* Mode flag: TRUE for optimization, FALSE for actual data output */ + boolean gather_statistics; + + /* Bit-level coding status. + * next_output_byte/free_in_buffer are local copies of cinfo->dest fields. + */ + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + j_compress_ptr cinfo; /* link to cinfo (needed for dump_buffer) */ + + /* Coding status for DC components */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ + + /* Coding status for AC components */ + int ac_tbl_no; /* the table number of the single component */ + unsigned int EOBRUN; /* run length of EOBs */ + unsigned int BE; /* # of buffered correction bits before MCU */ + char * bit_buffer; /* buffer for correction bits (1 per char) */ + /* packing correction bits tightly would save some space but cost time... */ + + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan). + * Since any one scan codes only DC or only AC, we only need one set + * of tables, not one for DC and one for AC. + */ + c_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + /* Statistics tables for optimization; again, one set is enough */ + long * count_ptrs[NUM_HUFF_TBLS]; +} phuff_entropy_encoder; + +typedef phuff_entropy_encoder * phuff_entropy_ptr; + +/* MAX_CORR_BITS is the number of bits the AC refinement correction-bit + * buffer can hold. Larger sizes may slightly improve compression, but + * 1000 is already well into the realm of overkill. + * The minimum safe size is 64 bits. + */ + +#define MAX_CORR_BITS 1000 /* Max # of correction bits I can buffer */ + +/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32. + * We assume that int right shift is unsigned if INT32 right shift is, + * which should be safe. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS int ishift_temp; +#define IRIGHT_SHIFT( x,shft ) \ + ( ( ishift_temp = ( x ) ) < 0 ? \ + ( ishift_temp >> ( shft ) ) | ( ( ~0 ) << ( 16 - ( shft ) ) ) : \ + ( ishift_temp >> ( shft ) ) ) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT( x,shft ) ( ( x ) >> ( shft ) ) +#endif + +/* Forward declarations */ +METHODDEF boolean encode_mcu_DC_first JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean encode_mcu_AC_first JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean encode_mcu_DC_refine JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean encode_mcu_AC_refine JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF void finish_pass_phuff JPP( (j_compress_ptr cinfo) ); +METHODDEF void finish_pass_gather_phuff JPP( (j_compress_ptr cinfo) ); + + +/* + * Initialize for a Huffman-compressed scan using progressive JPEG. + */ + +METHODDEF void +start_pass_phuff( j_compress_ptr cinfo, boolean gather_statistics ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + + entropy->cinfo = cinfo; + entropy->gather_statistics = gather_statistics; + + is_DC_band = ( cinfo->Ss == 0 ); + + /* We assume jcmaster.c already validated the scan parameters. */ + + /* Select execution routines */ + if ( cinfo->Ah == 0 ) { + if ( is_DC_band ) { + entropy->pub.encode_mcu = encode_mcu_DC_first; + } else { + entropy->pub.encode_mcu = encode_mcu_AC_first; + } + } else { + if ( is_DC_band ) { + entropy->pub.encode_mcu = encode_mcu_DC_refine; + } else { + entropy->pub.encode_mcu = encode_mcu_AC_refine; + /* AC refinement needs a correction bit buffer */ + if ( entropy->bit_buffer == NULL ) { + entropy->bit_buffer = ( char * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + MAX_CORR_BITS * SIZEOF( char ) ); + } + } + } + if ( gather_statistics ) { + entropy->pub.finish_pass = finish_pass_gather_phuff; + } else { + entropy->pub.finish_pass = finish_pass_phuff; + } + + /* Only DC coefficients may be interleaved, so cinfo->comps_in_scan = 1 + * for AC coefficients. + */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Initialize DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + /* Make sure requested tables are present */ + /* (In gather mode, tables need not be allocated yet) */ + if ( is_DC_band ) { + if ( cinfo->Ah != 0 ) { /* DC refinement needs no table */ + continue; + } + tbl = compptr->dc_tbl_no; + if ( tbl < 0 || tbl >= NUM_HUFF_TBLS || + ( cinfo->dc_huff_tbl_ptrs[tbl] == NULL && !gather_statistics ) ) { + ERREXIT1( cinfo,JERR_NO_HUFF_TABLE, tbl ); + } + } else { + entropy->ac_tbl_no = tbl = compptr->ac_tbl_no; + if ( tbl < 0 || tbl >= NUM_HUFF_TBLS || + ( cinfo->ac_huff_tbl_ptrs[tbl] == NULL && !gather_statistics ) ) { + ERREXIT1( cinfo,JERR_NO_HUFF_TABLE, tbl ); + } + } + if ( gather_statistics ) { + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if ( entropy->count_ptrs[tbl] == NULL ) { + entropy->count_ptrs[tbl] = ( long * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF( long ) ); + } + MEMZERO( entropy->count_ptrs[tbl], 257 * SIZEOF( long ) ); + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + if ( is_DC_band ) { + jpeg_make_c_derived_tbl( cinfo, cinfo->dc_huff_tbl_ptrs[tbl], + &entropy->derived_tbls[tbl] ); + } else { + jpeg_make_c_derived_tbl( cinfo, cinfo->ac_huff_tbl_ptrs[tbl], + &entropy->derived_tbls[tbl] ); + } + } + } + + /* Initialize AC stuff */ + entropy->EOBRUN = 0; + entropy->BE = 0; + + /* Initialize bit buffer to empty */ + entropy->put_buffer = 0; + entropy->put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* Outputting bytes to the file. + * NB: these must be called only when actually outputting, + * that is, entropy->gather_statistics == FALSE. + */ + +/* Emit a byte */ +#define emit_byte( entropy,val ) \ + { *( entropy )->next_output_byte++ = (JOCTET) ( val ); \ + if ( --( entropy )->free_in_buffer == 0 ) { \ + dump_buffer( entropy );} } + + +LOCAL void +dump_buffer( phuff_entropy_ptr entropy ) { +/* Empty the output buffer; we do not support suspension in this module. */ + struct jpeg_destination_mgr * dest = entropy->cinfo->dest; + + if ( !( *dest->empty_output_buffer )( entropy->cinfo ) ) { + ERREXIT( entropy->cinfo, JERR_CANT_SUSPEND ); + } + /* After a successful buffer dump, must reset buffer pointers */ + entropy->next_output_byte = dest->next_output_byte; + entropy->free_in_buffer = dest->free_in_buffer; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL void +emit_bits( phuff_entropy_ptr entropy, unsigned int code, int size ) { +/* Emit some bits, unless we are in gather mode */ +/* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = entropy->put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if ( size == 0 ) { + ERREXIT( entropy->cinfo, JERR_HUFF_MISSING_CODE ); + } + + if ( entropy->gather_statistics ) { + return; /* do nothing if we're only getting stats */ + + } + put_buffer &= ( ( (INT32) 1 ) << size ) - 1; /* mask off any extra bits in code */ + + put_bits += size; /* new number of bits in buffer */ + + put_buffer <<= 24 - put_bits; /* align incoming bits */ + + put_buffer |= entropy->put_buffer; /* and merge with old buffer contents */ + + while ( put_bits >= 8 ) { + int c = (int) ( ( put_buffer >> 16 ) & 0xFF ); + + emit_byte( entropy, c ); + if ( c == 0xFF ) { /* need to stuff a zero byte? */ + emit_byte( entropy, 0 ); + } + put_buffer <<= 8; + put_bits -= 8; + } + + entropy->put_buffer = put_buffer; /* update variables */ + entropy->put_bits = put_bits; +} + + +LOCAL void +flush_bits( phuff_entropy_ptr entropy ) { + emit_bits( entropy, 0x7F, 7 ); /* fill any partial byte with ones */ + entropy->put_buffer = 0; /* and reset bit-buffer to empty */ + entropy->put_bits = 0; +} + + +/* + * Emit (or just count) a Huffman symbol. + */ + +INLINE +LOCAL void +emit_symbol( phuff_entropy_ptr entropy, int tbl_no, int symbol ) { + if ( entropy->gather_statistics ) { + entropy->count_ptrs[tbl_no][symbol]++; + } else { + c_derived_tbl * tbl = entropy->derived_tbls[tbl_no]; + emit_bits( entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol] ); + } +} + + +/* + * Emit bits from a correction bit buffer. + */ + +LOCAL void +emit_buffered_bits( phuff_entropy_ptr entropy, char * bufstart, + unsigned int nbits ) { + if ( entropy->gather_statistics ) { + return; /* no real work */ + + } + while ( nbits > 0 ) { + emit_bits( entropy, ( unsigned int )( *bufstart ), 1 ); + bufstart++; + nbits--; + } +} + + +/* + * Emit any pending EOBRUN symbol. + */ + +LOCAL void +emit_eobrun( phuff_entropy_ptr entropy ) { + register int temp, nbits; + + if ( entropy->EOBRUN > 0 ) { /* if there is any pending EOBRUN */ + temp = entropy->EOBRUN; + nbits = 0; + while ( ( temp >>= 1 ) ) + nbits++; + + emit_symbol( entropy, entropy->ac_tbl_no, nbits << 4 ); + if ( nbits ) { + emit_bits( entropy, entropy->EOBRUN, nbits ); + } + + entropy->EOBRUN = 0; + + /* Emit any buffered correction bits */ + emit_buffered_bits( entropy, entropy->bit_buffer, entropy->BE ); + entropy->BE = 0; + } +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL void +emit_restart( phuff_entropy_ptr entropy, int restart_num ) { + int ci; + + emit_eobrun( entropy ); + + if ( !entropy->gather_statistics ) { + flush_bits( entropy ); + emit_byte( entropy, 0xFF ); + emit_byte( entropy, JPEG_RST0 + restart_num ); + } + + if ( entropy->cinfo->Ss == 0 ) { + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < entropy->cinfo->comps_in_scan; ci++ ) + entropy->last_dc_val[ci] = 0; + } else { + /* Re-initialize all AC-related fields to 0 */ + entropy->EOBRUN = 0; + entropy->BE = 0; + } +} + + +/* + * MCU encoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +encode_mcu_DC_first( j_compress_ptr cinfo, JBLOCKROW *MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + int blkn, ci; + int Al = cinfo->Al; + JBLOCKROW block; + jpeg_component_info * compptr; + ISHIFT_TEMPS + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + emit_restart( entropy, entropy->next_restart_num ); + } + } + + /* Encode the MCU data blocks */ + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Compute the DC value after the required point transform by Al. + * This is simply an arithmetic right shift. + */ + temp2 = IRIGHT_SHIFT( (int) ( ( *block )[0] ), Al ); + + /* DC differences are figured on the point-transformed values. */ + temp = temp2 - entropy->last_dc_val[ci]; + entropy->last_dc_val[ci] = temp2; + + /* Encode the DC coefficient difference per section G.1.2.1 */ + temp2 = temp; + if ( temp < 0 ) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while ( temp ) { + nbits++; + temp >>= 1; + } + + /* Count/emit the Huffman-coded symbol for the number of bits */ + emit_symbol( entropy, compptr->dc_tbl_no, nbits ); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if ( nbits ) { /* emit_bits rejects calls with size 0 */ + emit_bits( entropy, (unsigned int) temp2, nbits ); + } + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +encode_mcu_AC_first( j_compress_ptr cinfo, JBLOCKROW *MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + register int r, k; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + emit_restart( entropy, entropy->next_restart_num ); + } + } + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */ + + r = 0; /* r = run length of zeros */ + + for ( k = cinfo->Ss; k <= Se; k++ ) { + if ( ( temp = ( *block )[jpeg_natural_order[k]] ) == 0 ) { + r++; + continue; + } + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value; so the code is + * interwoven with finding the abs value (temp) and output bits (temp2). + */ + if ( temp < 0 ) { + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ + temp2 = ~temp; + } else { + temp >>= Al; /* apply the point transform */ + temp2 = temp; + } + /* Watch out for case that nonzero coef is zero after point transform */ + if ( temp == 0 ) { + r++; + continue; + } + + /* Emit any pending EOBRUN */ + if ( entropy->EOBRUN > 0 ) { + emit_eobrun( entropy ); + } + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while ( r > 15 ) { + emit_symbol( entropy, entropy->ac_tbl_no, 0xF0 ); + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ( ( temp >>= 1 ) ) + nbits++; + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol( entropy, entropy->ac_tbl_no, ( r << 4 ) + nbits ); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + emit_bits( entropy, (unsigned int) temp2, nbits ); + + r = 0; /* reset zero run length */ + } + + if ( r > 0 ) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + if ( entropy->EOBRUN == 0x7FFF ) { + emit_eobrun( entropy ); /* force it out to avoid overflow */ + } + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF boolean +encode_mcu_DC_refine( j_compress_ptr cinfo, JBLOCKROW *MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + int blkn; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + emit_restart( entropy, entropy->next_restart_num ); + } + } + + /* Encode the MCU data blocks */ + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + block = MCU_data[blkn]; + + /* We simply emit the Al'th bit of the DC coefficient value. */ + temp = ( *block )[0]; + emit_bits( entropy, ( unsigned int )( temp >> Al ), 1 ); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC successive approximation refinement scan. + */ + +METHODDEF boolean +encode_mcu_AC_refine( j_compress_ptr cinfo, JBLOCKROW *MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + register int r, k; + int EOB; + char *BR_buffer; + unsigned int BR; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + int absvalues[DCTSIZE2]; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + emit_restart( entropy, entropy->next_restart_num ); + } + } + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* It is convenient to make a pre-pass to determine the transformed + * coefficients' absolute values and the EOB position. + */ + EOB = 0; + for ( k = cinfo->Ss; k <= Se; k++ ) { + temp = ( *block )[jpeg_natural_order[k]]; + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if ( temp < 0 ) { + temp = -temp; /* temp is abs value of input */ + } + temp >>= Al; /* apply the point transform */ + absvalues[k] = temp; /* save abs value for main pass */ + if ( temp == 1 ) { + EOB = k; /* EOB = index of last newly-nonzero coef */ + } + } + + /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */ + + r = 0; /* r = run length of zeros */ + BR = 0; /* BR = count of buffered bits added now */ + BR_buffer = entropy->bit_buffer + entropy->BE; /* Append bits to buffer */ + + for ( k = cinfo->Ss; k <= Se; k++ ) { + if ( ( temp = absvalues[k] ) == 0 ) { + r++; + continue; + } + + /* Emit any required ZRLs, but not if they can be folded into EOB */ + while ( r > 15 && k <= EOB ) { + /* emit any pending EOBRUN and the BE correction bits */ + emit_eobrun( entropy ); + /* Emit ZRL */ + emit_symbol( entropy, entropy->ac_tbl_no, 0xF0 ); + r -= 16; + /* Emit buffered correction bits that must be associated with ZRL */ + emit_buffered_bits( entropy, BR_buffer, BR ); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + } + + /* If the coef was previously nonzero, it only needs a correction bit. + * NOTE: a straight translation of the spec's figure G.7 would suggest + * that we also need to test r > 15. But if r > 15, we can only get here + * if k > EOB, which implies that this coefficient is not 1. + */ + if ( temp > 1 ) { + /* The correction bit is the next bit of the absolute value. */ + BR_buffer[BR++] = (char) ( temp & 1 ); + continue; + } + + /* Emit any pending EOBRUN and the BE correction bits */ + emit_eobrun( entropy ); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol( entropy, entropy->ac_tbl_no, ( r << 4 ) + 1 ); + + /* Emit output bit for newly-nonzero coef */ + temp = ( ( *block )[jpeg_natural_order[k]] < 0 ) ? 0 : 1; + emit_bits( entropy, (unsigned int) temp, 1 ); + + /* Emit buffered correction bits that must be associated with this code */ + emit_buffered_bits( entropy, BR_buffer, BR ); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + r = 0; /* reset zero run length */ + } + + if ( r > 0 || BR > 0 ) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + entropy->BE += BR; /* concat my correction bits to older ones */ + /* We force out the EOB if we risk either: + * 1. overflow of the EOB counter; + * 2. overflow of the correction bit buffer during the next MCU. + */ + if ( entropy->EOBRUN == 0x7FFF || entropy->BE > ( MAX_CORR_BITS - DCTSIZE2 + 1 ) ) { + emit_eobrun( entropy ); + } + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed progressive scan. + */ + +METHODDEF void +finish_pass_phuff( j_compress_ptr cinfo ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Flush out any buffered data */ + emit_eobrun( entropy ); + flush_bits( entropy ); + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF void +finish_pass_gather_phuff( j_compress_ptr cinfo ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did[NUM_HUFF_TBLS]; + + /* Flush out buffered data (all we care about is counting the EOB symbol) */ + emit_eobrun( entropy ); + + is_DC_band = ( cinfo->Ss == 0 ); + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO( did, SIZEOF( did ) ); + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + if ( is_DC_band ) { + if ( cinfo->Ah != 0 ) { /* DC refinement needs no table */ + continue; + } + tbl = compptr->dc_tbl_no; + } else { + tbl = compptr->ac_tbl_no; + } + if ( !did[tbl] ) { + if ( is_DC_band ) { + htblptr = &cinfo->dc_huff_tbl_ptrs[tbl]; + } else { + htblptr = &cinfo->ac_huff_tbl_ptrs[tbl]; + } + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + jpeg_gen_optimal_table( cinfo, *htblptr, entropy->count_ptrs[tbl] ); + did[tbl] = TRUE; + } + } +} + + +/* + * Module initialization routine for progressive Huffman entropy encoding. + */ + +GLOBAL void +jinit_phuff_encoder( j_compress_ptr cinfo ) { + phuff_entropy_ptr entropy; + int i; + + entropy = (phuff_entropy_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( phuff_entropy_encoder ) ); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_phuff; + + /* Mark tables unallocated */ + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + entropy->derived_tbls[i] = NULL; + entropy->count_ptrs[i] = NULL; + } + entropy->bit_buffer = NULL; /* needed only in AC refinement scan */ +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/src/jpeg-6/jcprepct.c b/src/jpeg-6/jcprepct.c new file mode 100644 index 0000000..739b9fd --- /dev/null +++ b/src/jpeg-6/jcprepct.c @@ -0,0 +1,370 @@ +/* + * jcprepct.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the compression preprocessing controller. + * This controller manages the color conversion, downsampling, + * and edge expansion steps. + * + * Most of the complexity here is associated with buffering input rows + * as required by the downsampler. See the comments at the head of + * jcsample.c for the downsampler's needs. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* At present, jcsample.c can request context rows only for smoothing. + * In the future, we might also need context rows for CCIR601 sampling + * or other more-complex downsampling procedures. The code to support + * context rows should be compiled only if needed. + */ +#ifdef INPUT_SMOOTHING_SUPPORTED +#define CONTEXT_ROWS_SUPPORTED +#endif + + +/* + * For the simple (no-context-row) case, we just need to buffer one + * row group's worth of pixels for the downsampling step. At the bottom of + * the image, we pad to a full row group by replicating the last pixel row. + * The downsampler's last output row is then replicated if needed to pad + * out to a full iMCU row. + * + * When providing context rows, we must buffer three row groups' worth of + * pixels. Three row groups are physically allocated, but the row pointer + * arrays are made five row groups high, with the extra pointers above and + * below "wrapping around" to point to the last and first real row groups. + * This allows the downsampler to access the proper context rows. + * At the top and bottom of the image, we create dummy context rows by + * copying the first or last real pixel row. This copying could be avoided + * by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the + * trouble on the compression side. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_prep_controller pub; /* public fields */ + + /* Downsampling input buffer. This buffer holds color-converted data + * until we have enough to do a downsample step. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + JDIMENSION rows_to_go; /* counts rows remaining in source image */ + int next_buf_row; /* index of next row to store in color_buf */ + +#ifdef CONTEXT_ROWS_SUPPORTED /* only needed for context case */ + int this_row_group; /* starting row index of group to process */ + int next_buf_stop; /* downsample when we reach this index */ +#endif +} my_prep_controller; + +typedef my_prep_controller * my_prep_ptr; + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_prep( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + + if ( pass_mode != JBUF_PASS_THRU ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + + /* Initialize total-height counter for detecting bottom of image */ + prep->rows_to_go = cinfo->image_height; + /* Mark the conversion buffer empty */ + prep->next_buf_row = 0; +#ifdef CONTEXT_ROWS_SUPPORTED + /* Preset additional state variables for context mode. + * These aren't used in non-context mode, so we needn't test which mode. + */ + prep->this_row_group = 0; + /* Set next_buf_stop to stop after two row groups have been read in. */ + prep->next_buf_stop = 2 * cinfo->max_v_samp_factor; +#endif +} + + +/* + * Expand an image vertically from height input_rows to height output_rows, + * by duplicating the bottom row. + */ + +LOCAL void +expand_bottom_edge( JSAMPARRAY image_data, JDIMENSION num_cols, + int input_rows, int output_rows ) { + register int row; + + for ( row = input_rows; row < output_rows; row++ ) { + jcopy_sample_rows( image_data, input_rows - 1, image_data, row, + 1, num_cols ); + } +} + + +/* + * Process some data in the simple no-context case. + * + * Preprocessor output data is counted in "row groups". A row group + * is defined to be v_samp_factor sample rows of each component. + * Downsampling will produce this much data from each max_v_samp_factor + * input rows. + */ + +METHODDEF void +pre_process_data( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail ) { + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while ( *in_row_ctr < in_rows_avail && + *out_row_group_ctr < out_row_groups_avail ) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = cinfo->max_v_samp_factor - prep->next_buf_row; + numrows = (int) MIN( (JDIMENSION) numrows, inrows ); + ( *cinfo->cconvert->color_convert )( cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows ); + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + /* If at bottom of image, pad to fill the conversion buffer. */ + if ( prep->rows_to_go == 0 && + prep->next_buf_row < cinfo->max_v_samp_factor ) { + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + expand_bottom_edge( prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, cinfo->max_v_samp_factor ); + } + prep->next_buf_row = cinfo->max_v_samp_factor; + } + /* If we've filled the conversion buffer, empty it. */ + if ( prep->next_buf_row == cinfo->max_v_samp_factor ) { + ( *cinfo->downsample->downsample )( cinfo, + prep->color_buf, (JDIMENSION) 0, + output_buf, *out_row_group_ctr ); + prep->next_buf_row = 0; + ( *out_row_group_ctr )++; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if ( prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail ) { + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + expand_bottom_edge( output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) ( *out_row_group_ctr * compptr->v_samp_factor ), + (int) ( out_row_groups_avail * compptr->v_samp_factor ) ); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +#ifdef CONTEXT_ROWS_SUPPORTED + +/* + * Process some data in the context case. + */ + +METHODDEF void +pre_process_context( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail ) { + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + int buf_height = cinfo->max_v_samp_factor * 3; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while ( *out_row_group_ctr < out_row_groups_avail ) { + if ( *in_row_ctr < in_rows_avail ) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = prep->next_buf_stop - prep->next_buf_row; + numrows = (int) MIN( (JDIMENSION) numrows, inrows ); + ( *cinfo->cconvert->color_convert )( cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows ); + /* Pad at top of image, if first time through */ + if ( prep->rows_to_go == cinfo->image_height ) { + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + int row; + for ( row = 1; row <= cinfo->max_v_samp_factor; row++ ) { + jcopy_sample_rows( prep->color_buf[ci], 0, + prep->color_buf[ci], -row, + 1, cinfo->image_width ); + } + } + } + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + } else { + /* Return for more data, unless we are at the bottom of the image. */ + if ( prep->rows_to_go != 0 ) { + break; + } + } + /* If at bottom of image, pad to fill the conversion buffer. */ + if ( prep->rows_to_go == 0 && + prep->next_buf_row < prep->next_buf_stop ) { + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + expand_bottom_edge( prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, prep->next_buf_stop ); + } + prep->next_buf_row = prep->next_buf_stop; + } + /* If we've gotten enough data, downsample a row group. */ + if ( prep->next_buf_row == prep->next_buf_stop ) { + ( *cinfo->downsample->downsample )( cinfo, + prep->color_buf, + (JDIMENSION) prep->this_row_group, + output_buf, *out_row_group_ctr ); + ( *out_row_group_ctr )++; + /* Advance pointers with wraparound as necessary. */ + prep->this_row_group += cinfo->max_v_samp_factor; + if ( prep->this_row_group >= buf_height ) { + prep->this_row_group = 0; + } + if ( prep->next_buf_row >= buf_height ) { + prep->next_buf_row = 0; + } + prep->next_buf_stop = prep->next_buf_row + cinfo->max_v_samp_factor; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if ( prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail ) { + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + expand_bottom_edge( output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) ( *out_row_group_ctr * compptr->v_samp_factor ), + (int) ( out_row_groups_avail * compptr->v_samp_factor ) ); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +/* + * Create the wrapped-around downsampling input buffer needed for context mode. + */ + +LOCAL void +create_context_buffer( j_compress_ptr cinfo ) { + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int rgroup_height = cinfo->max_v_samp_factor; + int ci, i; + jpeg_component_info * compptr; + JSAMPARRAY true_buffer, fake_buffer; + + /* Grab enough space for fake row pointers for all the components; + * we need five row groups' worth of pointers for each component. + */ + fake_buffer = (JSAMPARRAY) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( cinfo->num_components * 5 * rgroup_height ) * + SIZEOF( JSAMPROW ) ); + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Allocate the actual buffer space (3 row groups) for this component. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + true_buffer = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) ( ( (long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor ) / compptr->h_samp_factor ), + (JDIMENSION) ( 3 * rgroup_height ) ); + /* Copy true buffer row pointers into the middle of the fake row array */ + MEMCOPY( fake_buffer + rgroup_height, true_buffer, + 3 * rgroup_height * SIZEOF( JSAMPROW ) ); + /* Fill in the above and below wraparound pointers */ + for ( i = 0; i < rgroup_height; i++ ) { + fake_buffer[i] = true_buffer[2 * rgroup_height + i]; + fake_buffer[4 * rgroup_height + i] = true_buffer[i]; + } + prep->color_buf[ci] = fake_buffer + rgroup_height; + fake_buffer += 5 * rgroup_height; /* point to space for next component */ + } +} + +#endif /* CONTEXT_ROWS_SUPPORTED */ + + +/* + * Initialize preprocessing controller. + */ + +GLOBAL void +jinit_c_prep_controller( j_compress_ptr cinfo, boolean need_full_buffer ) { + my_prep_ptr prep; + int ci; + jpeg_component_info * compptr; + + if ( need_full_buffer ) { /* safety check */ + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + + prep = (my_prep_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_prep_controller ) ); + cinfo->prep = (struct jpeg_c_prep_controller *) prep; + prep->pub.start_pass = start_pass_prep; + + /* Allocate the color conversion buffer. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + if ( cinfo->downsample->need_context_rows ) { + /* Set up to provide context rows */ +#ifdef CONTEXT_ROWS_SUPPORTED + prep->pub.pre_process_data = pre_process_context; + create_context_buffer( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + /* No context, just make it tall enough for one row group */ + prep->pub.pre_process_data = pre_process_data; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + prep->color_buf[ci] = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) ( ( (long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor ) / compptr->h_samp_factor ), + (JDIMENSION) cinfo->max_v_samp_factor ); + } + } +} diff --git a/src/jpeg-6/jcsample.c b/src/jpeg-6/jcsample.c new file mode 100644 index 0000000..9d8bcaa --- /dev/null +++ b/src/jpeg-6/jcsample.c @@ -0,0 +1,512 @@ +/* + * jcsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains downsampling routines. + * + * Downsampling input data is counted in "row groups". A row group + * is defined to be max_v_samp_factor pixel rows of each component, + * from which the downsampler produces v_samp_factor sample rows. + * A single row group is processed in each call to the downsampler module. + * + * The downsampler is responsible for edge-expansion of its output data + * to fill an integral number of DCT blocks horizontally. The source buffer + * may be modified if it is helpful for this purpose (the source buffer is + * allocated wide enough to correspond to the desired output width). + * The caller (the prep controller) is responsible for vertical padding. + * + * The downsampler may request "context rows" by setting need_context_rows + * during startup. In this case, the input arrays will contain at least + * one row group's worth of pixels above and below the passed-in data; + * the caller will create dummy rows at image top and bottom by replicating + * the first or last real pixel row. + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + * + * The downsampling algorithm used here is a simple average of the source + * pixels covered by the output pixel. The hi-falutin sampling literature + * refers to this as a "box filter". In general the characteristics of a box + * filter are not very good, but for the specific cases we normally use (1:1 + * and 2:1 ratios) the box is equivalent to a "triangle filter" which is not + * nearly so bad. If you intend to use other sampling ratios, you'd be well + * advised to improve this code. + * + * A simple input-smoothing capability is provided. This is mainly intended + * for cleaning up color-dithered GIF input files (if you find it inadequate, + * we suggest using an external filtering program such as pnmconvol). When + * enabled, each input pixel P is replaced by a weighted sum of itself and its + * eight neighbors. P's weight is 1-8*SF and each neighbor's weight is SF, + * where SF = (smoothing_factor / 1024). + * Currently, smoothing is only supported for 2h2v sampling factors. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to downsample a single component */ +typedef JMETHOD ( void, downsample1_ptr, + ( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) ); + +/* Private subobject */ + +typedef struct { + struct jpeg_downsampler pub; /* public fields */ + + /* Downsampling method pointers, one per component */ + downsample1_ptr methods[MAX_COMPONENTS]; +} my_downsampler; + +typedef my_downsampler * my_downsample_ptr; + + +/* + * Initialize for a downsampling pass. + */ + +METHODDEF void +start_pass_downsample( j_compress_ptr cinfo ) { + /* no work for now */ +} + + +/* + * Expand a component horizontally from width input_cols to width output_cols, + * by duplicating the rightmost samples. + */ + +LOCAL void +expand_right_edge( JSAMPARRAY image_data, int num_rows, + JDIMENSION input_cols, JDIMENSION output_cols ) { + register JSAMPROW ptr; + register JSAMPLE pixval; + register int count; + int row; + int numcols = (int) ( output_cols - input_cols ); + + if ( numcols > 0 ) { + for ( row = 0; row < num_rows; row++ ) { + ptr = image_data[row] + input_cols; + pixval = ptr[-1]; /* don't need GETJSAMPLE() here */ + for ( count = numcols; count > 0; count-- ) + *ptr++ = pixval; + } + } +} + + +/* + * Do downsampling for a whole row group (all components). + * + * In this version we simply downsample each component independently. + */ + +METHODDEF void +sep_downsample( j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, JDIMENSION out_row_group_index ) { + my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample; + int ci; + jpeg_component_info * compptr; + JSAMPARRAY in_ptr, out_ptr; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + in_ptr = input_buf[ci] + in_row_index; + out_ptr = output_buf[ci] + ( out_row_group_index * compptr->v_samp_factor ); + ( *downsample->methods[ci] )( cinfo, compptr, in_ptr, out_ptr ); + } +} + + +/* + * Downsample pixel values of a single component. + * One row group is processed per call. + * This version handles arbitrary integral sampling ratios, without smoothing. + * Note that this version is not actually used for customary sampling ratios. + */ + +METHODDEF void +int_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int inrow, outrow, h_expand, v_expand, numpix, numpix2, h, v; + JDIMENSION outcol, outcol_h; /* outcol_h == outcol*h_expand */ + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + JSAMPROW inptr, outptr; + INT32 outvalue; + + h_expand = cinfo->max_h_samp_factor / compptr->h_samp_factor; + v_expand = cinfo->max_v_samp_factor / compptr->v_samp_factor; + numpix = h_expand * v_expand; + numpix2 = numpix / 2; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * h_expand ); + + inrow = 0; + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + for ( outcol = 0, outcol_h = 0; outcol < output_cols; + outcol++, outcol_h += h_expand ) { + outvalue = 0; + for ( v = 0; v < v_expand; v++ ) { + inptr = input_data[inrow + v] + outcol_h; + for ( h = 0; h < h_expand; h++ ) { + outvalue += (INT32) GETJSAMPLE( *inptr++ ); + } + } + *outptr++ = (JSAMPLE) ( ( outvalue + numpix2 ) / numpix ); + } + inrow += v_expand; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * without smoothing. + */ + +METHODDEF void +fullsize_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + /* Copy the data */ + jcopy_sample_rows( input_data, 0, output_data, 0, + cinfo->max_v_samp_factor, cinfo->image_width ); + /* Edge-expand */ + expand_right_edge( output_data, cinfo->max_v_samp_factor, + cinfo->image_width, compptr->width_in_blocks * DCTSIZE ); +} + + +/* + * Downsample pixel values of a single component. + * This version handles the common case of 2:1 horizontal and 1:1 vertical, + * without smoothing. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2 ); + + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + bias = 0; /* bias = 0,1,0,1,... for successive samples */ + for ( outcol = 0; outcol < output_cols; outcol++ ) { + *outptr++ = (JSAMPLE) ( ( GETJSAMPLE( *inptr ) + GETJSAMPLE( inptr[1] ) + + bias ) >> 1 ); + bias ^= 1; /* 0=>1, 1=>0 */ + inptr += 2; + } + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * without smoothing. + */ + +METHODDEF void +h2v2_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int inrow, outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2 ); + + inrow = 0; + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow + 1]; + bias = 1; /* bias = 1,2,1,2,... for successive samples */ + for ( outcol = 0; outcol < output_cols; outcol++ ) { + *outptr++ = (JSAMPLE) ( ( GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[1] ) + + bias ) >> 2 ); + bias ^= 3; /* 1=>2, 2=>1 */ + inptr0 += 2; inptr1 += 2; + } + inrow += 2; + } +} + + +#ifdef INPUT_SMOOTHING_SUPPORTED + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * with smoothing. One row of context is required. + */ + +METHODDEF void +h2v2_smooth_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int inrow, outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols * 2 ); + + /* We don't bother to form the individual "smoothed" input pixel values; + * we can directly compute the output which is the average of the four + * smoothed values. Each of the four member pixels contributes a fraction + * (1-8*SF) to its own smoothed image and a fraction SF to each of the three + * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final + * output. The four corner-adjacent neighbor pixels contribute a fraction + * SF to just one smoothed pixel, or SF/4 to the final output; while the + * eight edge-adjacent neighbors contribute SF to each of two smoothed + * pixels, or SF/2 overall. In order to use integer arithmetic, these + * factors are scaled by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 16384 - cinfo->smoothing_factor * 80; /* scaled (1-5*SF)/4 */ + neighscale = cinfo->smoothing_factor * 16; /* scaled SF/4 */ + + inrow = 0; + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow + 1]; + above_ptr = input_data[inrow - 1]; + below_ptr = input_data[inrow + 2]; + + /* Special case for first column: pretend column -1 is same as column 0 */ + membersum = GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[1] ); + neighsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( above_ptr[1] ) + + GETJSAMPLE( *below_ptr ) + GETJSAMPLE( below_ptr[1] ) + + GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[2] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[2] ); + neighsum += neighsum; + neighsum += GETJSAMPLE( *above_ptr ) + GETJSAMPLE( above_ptr[2] ) + + GETJSAMPLE( *below_ptr ) + GETJSAMPLE( below_ptr[2] ); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + + for ( colctr = output_cols - 2; colctr > 0; colctr-- ) { + /* sum of pixels directly mapped to this output element */ + membersum = GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[1] ); + /* sum of edge-neighbor pixels */ + neighsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( above_ptr[1] ) + + GETJSAMPLE( *below_ptr ) + GETJSAMPLE( below_ptr[1] ) + + GETJSAMPLE( inptr0[-1] ) + GETJSAMPLE( inptr0[2] ) + + GETJSAMPLE( inptr1[-1] ) + GETJSAMPLE( inptr1[2] ); + /* The edge-neighbors count twice as much as corner-neighbors */ + neighsum += neighsum; + /* Add in the corner-neighbors */ + neighsum += GETJSAMPLE( above_ptr[-1] ) + GETJSAMPLE( above_ptr[2] ) + + GETJSAMPLE( below_ptr[-1] ) + GETJSAMPLE( below_ptr[2] ); + /* form final output scaled up by 2^16 */ + membersum = membersum * memberscale + neighsum * neighscale; + /* round, descale and output it */ + *outptr++ = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + } + + /* Special case for last column */ + membersum = GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[1] ); + neighsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( above_ptr[1] ) + + GETJSAMPLE( *below_ptr ) + GETJSAMPLE( below_ptr[1] ) + + GETJSAMPLE( inptr0[-1] ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( inptr1[-1] ) + GETJSAMPLE( inptr1[1] ); + neighsum += neighsum; + neighsum += GETJSAMPLE( above_ptr[-1] ) + GETJSAMPLE( above_ptr[1] ) + + GETJSAMPLE( below_ptr[-1] ) + GETJSAMPLE( below_ptr[1] ); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + + inrow += 2; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * with smoothing. One row of context is required. + */ + +METHODDEF void +fullsize_smooth_downsample( j_compress_ptr cinfo, jpeg_component_info *compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + int colsum, lastcolsum, nextcolsum; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols ); + + /* Each of the eight neighbor pixels contributes a fraction SF to the + * smoothed pixel, while the main pixel contributes (1-8*SF). In order + * to use integer arithmetic, these factors are multiplied by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 65536L - cinfo->smoothing_factor * 512L; /* scaled 1-8*SF */ + neighscale = cinfo->smoothing_factor * 64; /* scaled SF */ + + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + above_ptr = input_data[outrow - 1]; + below_ptr = input_data[outrow + 1]; + + /* Special case for first column */ + colsum = GETJSAMPLE( *above_ptr++ ) + GETJSAMPLE( *below_ptr++ ) + + GETJSAMPLE( *inptr ); + membersum = GETJSAMPLE( *inptr++ ); + nextcolsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( *below_ptr ) + + GETJSAMPLE( *inptr ); + neighsum = colsum + ( colsum - membersum ) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + lastcolsum = colsum; colsum = nextcolsum; + + for ( colctr = output_cols - 2; colctr > 0; colctr-- ) { + membersum = GETJSAMPLE( *inptr++ ); + above_ptr++; below_ptr++; + nextcolsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( *below_ptr ) + + GETJSAMPLE( *inptr ); + neighsum = lastcolsum + ( colsum - membersum ) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + lastcolsum = colsum; colsum = nextcolsum; + } + + /* Special case for last column */ + membersum = GETJSAMPLE( *inptr ); + neighsum = lastcolsum + ( colsum - membersum ) + colsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + + } +} + +#endif /* INPUT_SMOOTHING_SUPPORTED */ + + +/* + * Module initialization routine for downsampling. + * Note that we must select a routine for each component. + */ + +GLOBAL void +jinit_downsampler( j_compress_ptr cinfo ) { + my_downsample_ptr downsample; + int ci; + jpeg_component_info * compptr; + boolean smoothok = TRUE; + + downsample = (my_downsample_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_downsampler ) ); + cinfo->downsample = (struct jpeg_downsampler *) downsample; + downsample->pub.start_pass = start_pass_downsample; + downsample->pub.downsample = sep_downsample; + downsample->pub.need_context_rows = FALSE; + + if ( cinfo->CCIR601_sampling ) { + ERREXIT( cinfo, JERR_CCIR601_NOTIMPL ); + } + + /* Verify we can handle the sampling factors, and set up method pointers */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( compptr->h_samp_factor == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor ) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if ( cinfo->smoothing_factor ) { + downsample->methods[ci] = fullsize_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = fullsize_downsample; + } else if ( compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor ) { + smoothok = FALSE; + downsample->methods[ci] = h2v1_downsample; + } else if ( compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor * 2 == cinfo->max_v_samp_factor ) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if ( cinfo->smoothing_factor ) { + downsample->methods[ci] = h2v2_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = h2v2_downsample; + } else if ( ( cinfo->max_h_samp_factor % compptr->h_samp_factor ) == 0 && + ( cinfo->max_v_samp_factor % compptr->v_samp_factor ) == 0 ) { + smoothok = FALSE; + downsample->methods[ci] = int_downsample; + } else { + ERREXIT( cinfo, JERR_FRACT_SAMPLE_NOTIMPL ); + } + } + +#ifdef INPUT_SMOOTHING_SUPPORTED + if ( cinfo->smoothing_factor && !smoothok ) { + TRACEMS( cinfo, 0, JTRC_SMOOTH_NOTIMPL ); + } +#endif +} diff --git a/src/jpeg-6/jctrans.c b/src/jpeg-6/jctrans.c new file mode 100644 index 0000000..7323926 --- /dev/null +++ b/src/jpeg-6/jctrans.c @@ -0,0 +1,373 @@ +/* + * jctrans.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding compression, + * that is, writing raw DCT coefficient arrays to an output JPEG file. + * The routines in jcapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL void transencode_master_selection +JPP( ( j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays ) ); +LOCAL void transencode_coef_controller +JPP( ( j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays ) ); + + +/* + * Compression initialization for writing raw-coefficient data. + * Before calling this, all parameters and a data destination must be set up. + * Call jpeg_finish_compress() to actually write the data. + * + * The number of passed virtual arrays must match cinfo->num_components. + * Note that the virtual arrays need not be filled or even realized at + * the time write_coefficients is called; indeed, if the virtual arrays + * were requested from this compression object's memory manager, they + * typically will be realized during this routine and filled afterwards. + */ + +GLOBAL void +jpeg_write_coefficients( j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays ) { + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Mark all tables to be written */ + jpeg_suppress_tables( cinfo, FALSE ); + /* (Re)initialize error mgr and destination modules */ + ( *cinfo->err->reset_error_mgr )( (j_common_ptr) cinfo ); + ( *cinfo->dest->init_destination )( cinfo ); + /* Perform master selection of active modules */ + transencode_master_selection( cinfo, coef_arrays ); + /* Wait for jpeg_finish_compress() call */ + cinfo->next_scanline = 0; /* so jpeg_write_marker works */ + cinfo->global_state = CSTATE_WRCOEFS; +} + + +/* + * Initialize the compression object with default parameters, + * then copy from the source object all parameters needed for lossless + * transcoding. Parameters that can be varied without loss (such as + * scan script and Huffman optimization) are left in their default states. + */ + +GLOBAL void +jpeg_copy_critical_parameters( j_decompress_ptr srcinfo, + j_compress_ptr dstinfo ) { + JQUANT_TBL ** qtblptr; + jpeg_component_info *incomp, *outcomp; + JQUANT_TBL *c_quant, *slot_quant; + int tblno, ci, coefi; + + /* Safety check to ensure start_compress not called yet. */ + if ( dstinfo->global_state != CSTATE_START ) { + ERREXIT1( dstinfo, JERR_BAD_STATE, dstinfo->global_state ); + } + /* Copy fundamental image dimensions */ + dstinfo->image_width = srcinfo->image_width; + dstinfo->image_height = srcinfo->image_height; + dstinfo->input_components = srcinfo->num_components; + dstinfo->in_color_space = srcinfo->jpeg_color_space; + /* Initialize all parameters to default values */ + jpeg_set_defaults( dstinfo ); + /* jpeg_set_defaults may choose wrong colorspace, eg YCbCr if input is RGB. + * Fix it to get the right header markers for the image colorspace. + */ + jpeg_set_colorspace( dstinfo, srcinfo->jpeg_color_space ); + dstinfo->data_precision = srcinfo->data_precision; + dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling; + /* Copy the source's quantization tables. */ + for ( tblno = 0; tblno < NUM_QUANT_TBLS; tblno++ ) { + if ( srcinfo->quant_tbl_ptrs[tblno] != NULL ) { + qtblptr = &dstinfo->quant_tbl_ptrs[tblno]; + if ( *qtblptr == NULL ) { + *qtblptr = jpeg_alloc_quant_table( (j_common_ptr) dstinfo ); + } + MEMCOPY( ( *qtblptr )->quantval, + srcinfo->quant_tbl_ptrs[tblno]->quantval, + SIZEOF( ( *qtblptr )->quantval ) ); + ( *qtblptr )->sent_table = FALSE; + } + } + /* Copy the source's per-component info. + * Note we assume jpeg_set_defaults has allocated the dest comp_info array. + */ + dstinfo->num_components = srcinfo->num_components; + if ( dstinfo->num_components < 1 || dstinfo->num_components > MAX_COMPONENTS ) { + ERREXIT2( dstinfo, JERR_COMPONENT_COUNT, dstinfo->num_components, + MAX_COMPONENTS ); + } + for ( ci = 0, incomp = srcinfo->comp_info, outcomp = dstinfo->comp_info; + ci < dstinfo->num_components; ci++, incomp++, outcomp++ ) { + outcomp->component_id = incomp->component_id; + outcomp->h_samp_factor = incomp->h_samp_factor; + outcomp->v_samp_factor = incomp->v_samp_factor; + outcomp->quant_tbl_no = incomp->quant_tbl_no; + /* Make sure saved quantization table for component matches the qtable + * slot. If not, the input file re-used this qtable slot. + * IJG encoder currently cannot duplicate this. + */ + tblno = outcomp->quant_tbl_no; + if ( tblno < 0 || tblno >= NUM_QUANT_TBLS || + srcinfo->quant_tbl_ptrs[tblno] == NULL ) { + ERREXIT1( dstinfo, JERR_NO_QUANT_TABLE, tblno ); + } + slot_quant = srcinfo->quant_tbl_ptrs[tblno]; + c_quant = incomp->quant_table; + if ( c_quant != NULL ) { + for ( coefi = 0; coefi < DCTSIZE2; coefi++ ) { + if ( c_quant->quantval[coefi] != slot_quant->quantval[coefi] ) { + ERREXIT1( dstinfo, JERR_MISMATCHED_QUANT_TABLE, tblno ); + } + } + } + /* Note: we do not copy the source's Huffman table assignments; + * instead we rely on jpeg_set_colorspace to have made a suitable choice. + */ + } +} + + +/* + * Master selection of compression modules for transcoding. + * This substitutes for jcinit.c's initialization of the full compressor. + */ + +LOCAL void +transencode_master_selection( j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays ) { + /* Although we don't actually use input_components for transcoding, + * jcmaster.c's initial_setup will complain if input_components is 0. + */ + cinfo->input_components = 1; + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control( cinfo, TRUE /* transcode only */ ); + + /* Entropy encoding: either Huffman or arithmetic coding. */ + if ( cinfo->arith_code ) { + ERREXIT( cinfo, JERR_ARITH_NOTIMPL ); + } else { + if ( cinfo->progressive_mode ) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_huff_encoder( cinfo ); + } + } + + /* We need a special coefficient buffer controller. */ + transencode_coef_controller( cinfo, coef_arrays ); + + jinit_marker_writer( cinfo ); + + /* We can now tell the memory manager to allocate virtual arrays. */ + ( *cinfo->mem->realize_virt_arrays )( (j_common_ptr) cinfo ); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + ( *cinfo->marker->write_file_header )( cinfo ); +} + + +/* + * The rest of this file is a special implementation of the coefficient + * buffer controller. This is similar to jccoefct.c, but it handles only + * output from presupplied virtual arrays. Furthermore, we generate any + * dummy padding blocks on-the-fly rather than expecting them to be present + * in the arrays. + */ + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* Virtual block array for each component. */ + jvirt_barray_ptr * whole_image; + + /* Workspace for constructing dummy blocks at right/bottom edges. */ + JBLOCKROW dummy_buffer[C_MAX_BLOCKS_IN_MCU]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +LOCAL void +start_iMCU_row( j_compress_ptr cinfo ) { +/* Reset within-iMCU-row counters for a new row */ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if ( cinfo->comps_in_scan > 1 ) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if ( coef->iMCU_row_num < ( cinfo->total_iMCU_rows - 1 ) ) { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + } else { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_coef( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + if ( pass_mode != JBUF_CRANK_DEST ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + + coef->iMCU_row_num = 0; + start_iMCU_row( cinfo ); +} + + +/* + * Process some data. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF boolean +compress_output( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, blockcnt; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE ); + } + + /* Loop to process one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++ ) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + blockcnt = ( MCU_col_num < last_MCU_col ) ? compptr->MCU_width + : compptr->last_col_width; + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + if ( coef->iMCU_row_num < last_iMCU_row || + yindex + yoffset < compptr->last_row_height ) { + /* Fill in pointers to real blocks in this row */ + buffer_ptr = buffer[ci][yindex + yoffset] + start_col; + for ( xindex = 0; xindex < blockcnt; xindex++ ) + MCU_buffer[blkn++] = buffer_ptr++; + } else { + /* At bottom of image, need a whole row of dummy blocks */ + xindex = 0; + } + /* Fill in any dummy blocks needed in this row. + * Dummy blocks are filled in the same way as in jccoefct.c: + * all zeroes in the AC entries, DC entries equal to previous + * block's DC value. The init routine has already zeroed the + * AC entries, so we need only set the DC entries correctly. + */ + for (; xindex < compptr->MCU_width; xindex++ ) { + MCU_buffer[blkn] = coef->dummy_buffer[blkn]; + MCU_buffer[blkn][0][0] = MCU_buffer[blkn - 1][0][0]; + blkn++; + } + } + } + /* Try to write the MCU. */ + if ( !( *cinfo->entropy->encode_mcu )( cinfo, MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row( cinfo ); + return TRUE; +} + + +/* + * Initialize coefficient buffer controller. + * + * Each passed coefficient array must be the right size for that + * coefficient: width_in_blocks wide and height_in_blocks high, + * with unitheight at least v_samp_factor. + */ + +LOCAL void +transencode_coef_controller( j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays ) { + my_coef_ptr coef; + JBLOCKROW buffer; + int i; + + coef = (my_coef_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_coef_controller ) ); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + coef->pub.compress_data = compress_output; + + /* Save pointer to virtual arrays */ + coef->whole_image = coef_arrays; + + /* Allocate and pre-zero space for dummy DCT blocks. */ + buffer = (JBLOCKROW) + ( *cinfo->mem->alloc_large ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF( JBLOCK ) ); + jzero_far( (void FAR *) buffer, C_MAX_BLOCKS_IN_MCU * SIZEOF( JBLOCK ) ); + for ( i = 0; i < C_MAX_BLOCKS_IN_MCU; i++ ) { + coef->dummy_buffer[i] = buffer + i; + } +} diff --git a/src/jpeg-6/jdapimin.c b/src/jpeg-6/jdapimin.c new file mode 100644 index 0000000..4ec2eda --- /dev/null +++ b/src/jpeg-6/jdapimin.c @@ -0,0 +1,395 @@ +/* + * jdapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-decompression case or the + * transcoding-only case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + * shared by compression and decompression, and jdtrans.c for the transcoding + * case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG decompression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_decompress( j_decompress_ptr cinfo ) { + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + MEMZERO( cinfo, SIZEOF( struct jpeg_decompress_struct ) ); + cinfo->err = err; + } + cinfo->is_decompressor = TRUE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr( (j_common_ptr) cinfo ); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->src = NULL; + + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) + cinfo->quant_tbl_ptrs[i] = NULL; + + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Initialize marker processor so application can override methods + * for COM, APPn markers before calling jpeg_read_header. + */ + jinit_marker_reader( cinfo ); + + /* And initialize the overall input controller. */ + jinit_input_controller( cinfo ); + + /* OK, I'm ready */ + cinfo->global_state = DSTATE_START; +} + + +/* + * Destruction of a JPEG decompression object + */ + +GLOBAL void +jpeg_destroy_decompress( j_decompress_ptr cinfo ) { + jpeg_destroy( (j_common_ptr) cinfo ); /* use common routine */ +} + + +/* + * Abort processing of a JPEG decompression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_decompress( j_decompress_ptr cinfo ) { + jpeg_abort( (j_common_ptr) cinfo ); /* use common routine */ +} + + +/* + * Install a special processing method for COM or APPn markers. + */ + +GLOBAL void +jpeg_set_marker_processor( j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine ) { + if ( marker_code == JPEG_COM ) { + cinfo->marker->process_COM = routine; + } else if ( marker_code >= JPEG_APP0 && marker_code <= JPEG_APP0 + 15 ) { + cinfo->marker->process_APPn[marker_code - JPEG_APP0] = routine; + } else { + ERREXIT1( cinfo, JERR_UNKNOWN_MARKER, marker_code ); + } +} + + +/* + * Set default decompression parameters. + */ + +LOCAL void +default_decompress_parms( j_decompress_ptr cinfo ) { + /* Guess the input colorspace, and set output colorspace accordingly. */ + /* (Wish JPEG committee had provided a real way to specify this...) */ + /* Note application may override our guesses. */ + switch ( cinfo->num_components ) { + case 1: + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + break; + + case 3: + if ( cinfo->saw_JFIF_marker ) { + cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ + } else if ( cinfo->saw_Adobe_marker ) { + switch ( cinfo->Adobe_transform ) { + case 0: + cinfo->jpeg_color_space = JCS_RGB; + break; + case 1: + cinfo->jpeg_color_space = JCS_YCbCr; + break; + default: + WARNMS1( cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform ); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + break; + } + } else { + /* Saw no special markers, try to guess from the component IDs */ + int cid0 = cinfo->comp_info[0].component_id; + int cid1 = cinfo->comp_info[1].component_id; + int cid2 = cinfo->comp_info[2].component_id; + + if ( cid0 == 1 && cid1 == 2 && cid2 == 3 ) { + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + } else if ( cid0 == 82 && cid1 == 71 && cid2 == 66 ) { + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + } else { + TRACEMS3( cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2 ); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + } + } + /* Always guess RGB is proper output colorspace. */ + cinfo->out_color_space = JCS_RGB; + break; + + case 4: + if ( cinfo->saw_Adobe_marker ) { + switch ( cinfo->Adobe_transform ) { + case 0: + cinfo->jpeg_color_space = JCS_CMYK; + break; + case 2: + cinfo->jpeg_color_space = JCS_YCCK; + break; + default: + WARNMS1( cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform ); + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + break; + } + } else { + /* No special markers, assume straight CMYK. */ + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + break; + + default: + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + break; + } + + /* Set defaults for other decompression parameters. */ + cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_denom = 1; + cinfo->output_gamma = 1.0; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + /* We set these in case application only sets quantize_colors. */ + cinfo->dither_mode = JDITHER_FS; +#ifdef QUANT_2PASS_SUPPORTED + cinfo->two_pass_quantize = TRUE; +#else + cinfo->two_pass_quantize = FALSE; +#endif + cinfo->desired_number_of_colors = 256; + cinfo->colormap = NULL; + /* Initialize for no mode change in buffered-image mode. */ + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; +} + + +/* + * Decompression startup: read start of JPEG datastream to see what's there. + * Need only initialize JPEG object and supply a data source before calling. + * + * This routine will read as far as the first SOS marker (ie, actual start of + * compressed data), and will save all tables and parameters in the JPEG + * object. It will also initialize the decompression parameters to default + * values, and finally return JPEG_HEADER_OK. On return, the application may + * adjust the decompression parameters and then call jpeg_start_decompress. + * (Or, if the application only wanted to determine the image parameters, + * the data need not be decompressed. In that case, call jpeg_abort or + * jpeg_destroy to release any temporary space.) + * If an abbreviated (tables only) datastream is presented, the routine will + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + * re-use the JPEG object to read the abbreviated image datastream(s). + * It is unnecessary (but OK) to call jpeg_abort in this case. + * The JPEG_SUSPENDED return code only occurs if the data source module + * requests suspension of the decompressor. In this case the application + * should load more source data and then re-call jpeg_read_header to resume + * processing. + * If a non-suspending data source is used and require_image is TRUE, then the + * return code need not be inspected since only JPEG_HEADER_OK is possible. + * + * This routine is now just a front end to jpeg_consume_input, with some + * extra error checking. + */ + +GLOBAL int +jpeg_read_header( j_decompress_ptr cinfo, boolean require_image ) { + int retcode; + + if ( cinfo->global_state != DSTATE_START && + cinfo->global_state != DSTATE_INHEADER ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + retcode = jpeg_consume_input( cinfo ); + + switch ( retcode ) { + case JPEG_REACHED_SOS: + retcode = JPEG_HEADER_OK; + break; + case JPEG_REACHED_EOI: + if ( require_image ) { /* Complain if application wanted an image */ + ERREXIT( cinfo, JERR_NO_IMAGE ); + } + /* Reset to start state; it would be safer to require the application to + * call jpeg_abort, but we can't change it now for compatibility reasons. + * A side effect is to free any temporary memory (there shouldn't be any). + */ + jpeg_abort( (j_common_ptr) cinfo ); /* sets state = DSTATE_START */ + retcode = JPEG_HEADER_TABLES_ONLY; + break; + case JPEG_SUSPENDED: + /* no work */ + break; + } + + return retcode; +} + + +/* + * Consume data in advance of what the decompressor requires. + * This can be called at any time once the decompressor object has + * been created and a data source has been set up. + * + * This routine is essentially a state machine that handles a couple + * of critical state-transition actions, namely initial setup and + * transition from header scanning to ready-for-start_decompress. + * All the actual input is done via the input controller's consume_input + * method. + */ + +GLOBAL int +jpeg_consume_input( j_decompress_ptr cinfo ) { + int retcode = JPEG_SUSPENDED; + + /* NB: every possible DSTATE value should be listed in this switch */ + switch ( cinfo->global_state ) { + case DSTATE_START: + /* Start-of-datastream actions: reset appropriate modules */ + ( *cinfo->inputctl->reset_input_controller )( cinfo ); + /* Initialize application's data source module */ + ( *cinfo->src->init_source )( cinfo ); + cinfo->global_state = DSTATE_INHEADER; + /*FALLTHROUGH*/ + case DSTATE_INHEADER: + retcode = ( *cinfo->inputctl->consume_input )( cinfo ); + if ( retcode == JPEG_REACHED_SOS ) { /* Found SOS, prepare to decompress */ + /* Set up default parameters based on header data */ + default_decompress_parms( cinfo ); + /* Set global state: ready for start_decompress */ + cinfo->global_state = DSTATE_READY; + } + break; + case DSTATE_READY: + /* Can't advance past first SOS until start_decompress is called */ + retcode = JPEG_REACHED_SOS; + break; + case DSTATE_PRELOAD: + case DSTATE_PRESCAN: + case DSTATE_SCANNING: + case DSTATE_RAW_OK: + case DSTATE_BUFIMAGE: + case DSTATE_BUFPOST: + case DSTATE_STOPPING: + retcode = ( *cinfo->inputctl->consume_input )( cinfo ); + break; + default: + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + return retcode; +} + + +/* + * Have we finished reading the input file? + */ + +GLOBAL boolean +jpeg_input_complete( j_decompress_ptr cinfo ) { + /* Check for valid jpeg object */ + if ( cinfo->global_state < DSTATE_START || + cinfo->global_state > DSTATE_STOPPING ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + return cinfo->inputctl->eoi_reached; +} + + +/* + * Is there more than one scan? + */ + +GLOBAL boolean +jpeg_has_multiple_scans( j_decompress_ptr cinfo ) { + /* Only valid after jpeg_read_header completes */ + if ( cinfo->global_state < DSTATE_READY || + cinfo->global_state > DSTATE_STOPPING ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + return cinfo->inputctl->has_multiple_scans; +} + + +/* + * Finish JPEG decompression. + * + * This will normally just verify the file trailer and release temp storage. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_decompress( j_decompress_ptr cinfo ) { + if ( ( cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK ) && !cinfo->buffered_image ) { + /* Terminate final pass of non-buffered mode */ + if ( cinfo->output_scanline < cinfo->output_height ) { + ERREXIT( cinfo, JERR_TOO_LITTLE_DATA ); + } + ( *cinfo->master->finish_output_pass )( cinfo ); + cinfo->global_state = DSTATE_STOPPING; + } else if ( cinfo->global_state == DSTATE_BUFIMAGE ) { + /* Finishing after a buffered-image operation */ + cinfo->global_state = DSTATE_STOPPING; + } else if ( cinfo->global_state != DSTATE_STOPPING ) { + /* STOPPING = repeat call after a suspension, anything else is error */ + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Read until EOI */ + while ( !cinfo->inputctl->eoi_reached ) { + if ( ( *cinfo->inputctl->consume_input )( cinfo ) == JPEG_SUSPENDED ) { + return FALSE; /* Suspend, come back later */ + } + } + /* Do final cleanup */ + ( *cinfo->src->term_source )( cinfo ); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort( (j_common_ptr) cinfo ); + return TRUE; +} diff --git a/src/jpeg-6/jdapistd.c b/src/jpeg-6/jdapistd.c new file mode 100644 index 0000000..bfd5900 --- /dev/null +++ b/src/jpeg-6/jdapistd.c @@ -0,0 +1,282 @@ +/* + * jdapistd.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-decompression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_decompress, it will end up linking in the entire decompressor. + * We thus must separate this file from jdapimin.c to avoid linking the + * whole decompression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL boolean output_pass_setup JPP( (j_decompress_ptr cinfo) ); + + +/* + * Decompression initialization. + * jpeg_read_header must be completed before calling this. + * + * If a multipass operating mode was selected, this will do all but the + * last pass, and thus may take a great deal of time. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_start_decompress( j_decompress_ptr cinfo ) { + if ( cinfo->global_state == DSTATE_READY ) { + /* First call: initialize master control, select active modules */ + jinit_master_decompress( cinfo ); + if ( cinfo->buffered_image ) { + /* No more work here; expecting jpeg_start_output next */ + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; + } + cinfo->global_state = DSTATE_PRELOAD; + } + if ( cinfo->global_state == DSTATE_PRELOAD ) { + /* If file has multiple scans, absorb them all into the coef buffer */ + if ( cinfo->inputctl->has_multiple_scans ) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + for (;; ) { + int retcode; + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + /* Absorb some more input */ + retcode = ( *cinfo->inputctl->consume_input )( cinfo ); + if ( retcode == JPEG_SUSPENDED ) { + return FALSE; + } + if ( retcode == JPEG_REACHED_EOI ) { + break; + } + /* Advance progress counter if appropriate */ + if ( cinfo->progress != NULL && + ( retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS ) ) { + if ( ++cinfo->progress->pass_counter >= cinfo->progress->pass_limit ) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + } + cinfo->output_scan_number = cinfo->input_scan_number; + } else if ( cinfo->global_state != DSTATE_PRESCAN ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Perform any dummy output passes, and set up for the final pass */ + return output_pass_setup( cinfo ); +} + + +/* + * Set up for an output pass, and perform any dummy pass(es) needed. + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + * Exit: If done, returns TRUE and sets global_state for proper output mode. + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + */ + +LOCAL boolean +output_pass_setup( j_decompress_ptr cinfo ) { + if ( cinfo->global_state != DSTATE_PRESCAN ) { + /* First call: do pass setup */ + ( *cinfo->master->prepare_for_output_pass )( cinfo ); + cinfo->output_scanline = 0; + cinfo->global_state = DSTATE_PRESCAN; + } + /* Loop over any required dummy passes */ + while ( cinfo->master->is_dummy_pass ) { +#ifdef QUANT_2PASS_SUPPORTED + /* Crank through the dummy pass */ + while ( cinfo->output_scanline < cinfo->output_height ) { + JDIMENSION last_scanline; + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + /* Process some data */ + last_scanline = cinfo->output_scanline; + ( *cinfo->main->process_data )( cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0 ); + if ( cinfo->output_scanline == last_scanline ) { + return FALSE; /* No progress made, must suspend */ + } + } + /* Finish up dummy pass, and set up for another one */ + ( *cinfo->master->finish_output_pass )( cinfo ); + ( *cinfo->master->prepare_for_output_pass )( cinfo ); + cinfo->output_scanline = 0; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif /* QUANT_2PASS_SUPPORTED */ + } + /* Ready for application to drive output pass through + * jpeg_read_scanlines or jpeg_read_raw_data. + */ + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + return TRUE; +} + + +/* + * Read some scanlines of data from the JPEG decompressor. + * + * The return value will be the number of lines actually read. + * This may be less than the number requested in several cases, + * including bottom of image, data source suspension, and operating + * modes that emit multiple scanlines at a time. + * + * Note: we warn about excess calls to jpeg_read_scanlines() since + * this likely signals an application programmer error. However, + * an oversize buffer (max_lines > scanlines remaining) is not an error. + */ + +GLOBAL JDIMENSION +jpeg_read_scanlines( j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines ) { + JDIMENSION row_ctr; + + if ( cinfo->global_state != DSTATE_SCANNING ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + if ( cinfo->output_scanline >= cinfo->output_height ) { + WARNMS( cinfo, JWRN_TOO_MUCH_DATA ); + return 0; + } + + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + + /* Process some data */ + row_ctr = 0; + ( *cinfo->main->process_data )( cinfo, scanlines, &row_ctr, max_lines ); + cinfo->output_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to read raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL JDIMENSION +jpeg_read_raw_data( j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines ) { + JDIMENSION lines_per_iMCU_row; + + if ( cinfo->global_state != DSTATE_RAW_OK ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + if ( cinfo->output_scanline >= cinfo->output_height ) { + WARNMS( cinfo, JWRN_TOO_MUCH_DATA ); + return 0; + } + + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + + /* Verify that at least one iMCU row can be returned. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size; + if ( max_lines < lines_per_iMCU_row ) { + ERREXIT( cinfo, JERR_BUFFER_SIZE ); + } + + /* Decompress directly into user's buffer. */ + if ( !( *cinfo->coef->decompress_data )( cinfo, data ) ) { + return 0; /* suspension forced, can do nothing more */ + + } + /* OK, we processed one iMCU row. */ + cinfo->output_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} + + +/* Additional entry points for buffered-image mode. */ + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Initialize for an output pass in buffered-image mode. + */ + +GLOBAL boolean +jpeg_start_output( j_decompress_ptr cinfo, int scan_number ) { + if ( cinfo->global_state != DSTATE_BUFIMAGE && + cinfo->global_state != DSTATE_PRESCAN ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Limit scan number to valid range */ + if ( scan_number <= 0 ) { + scan_number = 1; + } + if ( cinfo->inputctl->eoi_reached && + scan_number > cinfo->input_scan_number ) { + scan_number = cinfo->input_scan_number; + } + cinfo->output_scan_number = scan_number; + /* Perform any dummy output passes, and set up for the real pass */ + return output_pass_setup( cinfo ); +} + + +/* + * Finish up after an output pass in buffered-image mode. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_output( j_decompress_ptr cinfo ) { + if ( ( cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK ) && cinfo->buffered_image ) { + /* Terminate this pass. */ + /* We do not require the whole pass to have been completed. */ + ( *cinfo->master->finish_output_pass )( cinfo ); + cinfo->global_state = DSTATE_BUFPOST; + } else if ( cinfo->global_state != DSTATE_BUFPOST ) { + /* BUFPOST = repeat call after a suspension, anything else is error */ + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Read markers looking for SOS or EOI */ + while ( cinfo->input_scan_number <= cinfo->output_scan_number && + !cinfo->inputctl->eoi_reached ) { + if ( ( *cinfo->inputctl->consume_input )( cinfo ) == JPEG_SUSPENDED ) { + return FALSE; /* Suspend, come back later */ + } + } + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ diff --git a/src/jpeg-6/jdatadst.c b/src/jpeg-6/jdatadst.c new file mode 100644 index 0000000..2b0ddbe --- /dev/null +++ b/src/jpeg-6/jdatadst.c @@ -0,0 +1,150 @@ +/* + * jdatadst.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains compression data destination routines for the case of + * emitting JPEG data to a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * destination manager. + * IMPORTANT: we assume that fwrite() will correctly transcribe an array of + * JOCTETs into 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + FILE * outfile; /* target stream */ + JOCTET * buffer; /* start of buffer */ +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +METHODDEF void +init_destination( j_compress_ptr cinfo ) { + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + /* Allocate the output buffer --- it will be released when done with image */ + dest->buffer = ( JOCTET * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + OUTPUT_BUF_SIZE * SIZEOF( JOCTET ) ); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +METHODDEF boolean +empty_output_buffer( j_compress_ptr cinfo ) { + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + if ( JFWRITE( dest->outfile, dest->buffer, OUTPUT_BUF_SIZE ) != + (size_t) OUTPUT_BUF_SIZE ) { + ERREXIT( cinfo, JERR_FILE_WRITE ); + } + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; + + return TRUE; +} + + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_destination( j_compress_ptr cinfo ) { + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + /* Write any data remaining in the buffer */ + if ( datacount > 0 ) { + if ( JFWRITE( dest->outfile, dest->buffer, datacount ) != datacount ) { + ERREXIT( cinfo, JERR_FILE_WRITE ); + } + } + fflush( dest->outfile ); + /* Make sure we wrote the output file OK */ + if ( ferror( dest->outfile ) ) { + ERREXIT( cinfo, JERR_FILE_WRITE ); + } +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +GLOBAL void +jpeg_stdio_dest( j_compress_ptr cinfo, FILE * outfile ) { + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if ( cinfo->dest == NULL ) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF( my_destination_mgr ) ); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; +} diff --git a/src/jpeg-6/jdatasrc.c b/src/jpeg-6/jdatasrc.c new file mode 100644 index 0000000..acfb689 --- /dev/null +++ b/src/jpeg-6/jdatasrc.c @@ -0,0 +1,199 @@ +/* + * jdatasrc.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + unsigned char *infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF void +init_source( j_decompress_ptr cinfo ) { + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF boolean +fill_input_buffer( j_decompress_ptr cinfo ) { + my_src_ptr src = (my_src_ptr) cinfo->src; + + memcpy( src->buffer, src->infile, INPUT_BUF_SIZE ); + + src->infile += INPUT_BUF_SIZE; + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = INPUT_BUF_SIZE; + src->start_of_file = FALSE; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF void +skip_input_data( j_decompress_ptr cinfo, long num_bytes ) { + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if ( num_bytes > 0 ) { + while ( num_bytes > (long) src->pub.bytes_in_buffer ) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer( cinfo ); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_source( j_decompress_ptr cinfo ) { + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +GLOBAL void +jpeg_stdio_src( j_decompress_ptr cinfo, unsigned char *infile ) { + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if ( cinfo->src == NULL ) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF( my_source_mgr ) ); + src = (my_src_ptr) cinfo->src; + src->buffer = ( JOCTET * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF( JOCTET ) ); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} diff --git a/src/jpeg-6/jdcoefct.c b/src/jpeg-6/jdcoefct.c new file mode 100644 index 0000000..8f67b5d --- /dev/null +++ b/src/jpeg-6/jdcoefct.c @@ -0,0 +1,747 @@ +/* + * jdcoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for decompression. + * This controller is the top level of the JPEG decompressor proper. + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + * + * In buffered-image mode, this controller is the interface between + * input-oriented processing and output-oriented processing. + * Also, the input side (only) is used when reading a file for transcoding. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +/* Block smoothing is only applicable for progressive JPEG, so: */ +#ifndef D_PROGRESSIVE_SUPPORTED +#undef BLOCK_SMOOTHING_SUPPORTED +#endif + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_coef_controller pub; /* public fields */ + + /* These variables keep track of the current location of the input side. */ + /* cinfo->input_iMCU_row is also used for this. */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + /* In single-pass modes, it's sufficient to buffer just one MCU. + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + * and let the entropy decoder write into that workspace each time. + * (On 80x86, the workspace is FAR even though it's not really very big; + * this is to keep the module interfaces unchanged when a large coefficient + * buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays; it is used only by the input side. + */ + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* When doing block smoothing, we latch coefficient Al values here */ + int * coef_bits_latch; +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#endif +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + +/* Forward declarations */ +METHODDEF int decompress_onepass +JPP( ( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) ); +#ifdef D_MULTISCAN_FILES_SUPPORTED +METHODDEF int decompress_data +JPP( ( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) ); +#endif +#ifdef BLOCK_SMOOTHING_SUPPORTED +LOCAL boolean smoothing_ok JPP( (j_decompress_ptr cinfo) ); +METHODDEF int decompress_smooth_data +JPP( ( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) ); +#endif + + +LOCAL void +start_iMCU_row( j_decompress_ptr cinfo ) { +/* Reset within-iMCU-row counters for a new row (input side) */ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if ( cinfo->comps_in_scan > 1 ) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if ( cinfo->input_iMCU_row < ( cinfo->total_iMCU_rows - 1 ) ) { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + } else { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + } + + coef->MCU_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for an input processing pass. + */ + +METHODDEF void +start_input_pass( j_decompress_ptr cinfo ) { + cinfo->input_iMCU_row = 0; + start_iMCU_row( cinfo ); +} + + +/* + * Initialize for an output processing pass. + */ + +METHODDEF void +start_output_pass( j_decompress_ptr cinfo ) { +#ifdef BLOCK_SMOOTHING_SUPPORTED + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* If multipass, check to see whether to use block smoothing on this pass */ + if ( coef->pub.coef_arrays != NULL ) { + if ( cinfo->do_block_smoothing && smoothing_ok( cinfo ) ) { + coef->pub.decompress_data = decompress_smooth_data; + } else { + coef->pub.decompress_data = decompress_data; + } + } +#endif + cinfo->output_iMCU_row = 0; +} + + +/* + * Decompress and return some data in the single-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Input and output must run in lockstep since we have only a one-MCU buffer. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF int +decompress_onepass( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, useful_width; + JSAMPARRAY output_ptr; + JDIMENSION start_col, output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Loop to process as much as one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++ ) { + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + jzero_far( (void FAR *) coef->MCU_buffer[0], + (size_t) ( cinfo->blocks_in_MCU * SIZEOF( JBLOCK ) ) ); + if ( !( *cinfo->entropy->decode_mcu )( cinfo, coef->MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + /* Determine where data should go in output_buf and do the IDCT thing. + * We skip dummy blocks at the right and bottom edges (but blkn gets + * incremented past them!). Note the inner loop relies on having + * allocated the MCU_buffer[] blocks sequentially. + */ + blkn = 0; /* index of current DCT block within MCU */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if ( !compptr->component_needed ) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = ( MCU_col_num < last_MCU_col ) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[ci] + yoffset * compptr->DCT_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + if ( cinfo->input_iMCU_row < last_iMCU_row || + yoffset + yindex < compptr->last_row_height ) { + output_col = start_col; + for ( xindex = 0; xindex < useful_width; xindex++ ) { + ( *inverse_DCT )( cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn + xindex], + output_ptr, output_col ); + output_col += compptr->DCT_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_scaled_size; + } + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + cinfo->output_iMCU_row++; + if ( ++( cinfo->input_iMCU_row ) < cinfo->total_iMCU_rows ) { + start_iMCU_row( cinfo ); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + ( *cinfo->inputctl->finish_input_pass )( cinfo ); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Dummy consume-input routine for single-pass operation. + */ + +METHODDEF int +dummy_consume_data( j_decompress_ptr cinfo ) { + return JPEG_SUSPENDED; /* Always indicate nothing was done */ +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Consume input data and store it in the full-image coefficient buffer. + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + * ie, v_samp_factor block rows for each component in the scan. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + */ + +METHODDEF int +consume_data( j_decompress_ptr cinfo ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + cinfo->input_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE ); + /* Note: entropy decoder expects buffer to be zeroed, + * but this is handled automatically by the memory manager + * because we requested a pre-zeroed array. + */ + } + + /* Loop to process one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++ ) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + buffer_ptr = buffer[ci][yindex + yoffset] + start_col; + for ( xindex = 0; xindex < compptr->MCU_width; xindex++ ) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to fetch the MCU. */ + if ( !( *cinfo->entropy->decode_mcu )( cinfo, coef->MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + if ( ++( cinfo->input_iMCU_row ) < cinfo->total_iMCU_rows ) { + start_iMCU_row( cinfo ); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + ( *cinfo->inputctl->finish_input_pass )( cinfo ); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Decompress and return some data in the multi-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + */ + +METHODDEF int +decompress_data( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num; + int ci, block_row, block_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Force some input to be done if we are getting ahead of the input. */ + while ( cinfo->input_scan_number < cinfo->output_scan_number || + ( cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row ) ) { + if ( ( *cinfo->inputctl->consume_input )( cinfo ) == JPEG_SUSPENDED ) { + return JPEG_SUSPENDED; + } + } + + /* OK, output from the virtual arrays. */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Don't bother to IDCT an uninteresting component. */ + if ( !compptr->component_needed ) { + continue; + } + /* Align the virtual buffer for this component. */ + buffer = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[ci], + cinfo->output_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE ); + /* Count non-dummy DCT block rows in this iMCU row. */ + if ( cinfo->output_iMCU_row < last_iMCU_row ) { + block_rows = compptr->v_samp_factor; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( block_rows == 0 ) { + block_rows = compptr->v_samp_factor; + } + } + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for ( block_row = 0; block_row < block_rows; block_row++ ) { + buffer_ptr = buffer[block_row]; + output_col = 0; + for ( block_num = 0; block_num < compptr->width_in_blocks; block_num++ ) { + ( *inverse_DCT )( cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col ); + buffer_ptr++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if ( ++( cinfo->output_iMCU_row ) < cinfo->total_iMCU_rows ) { + return JPEG_ROW_COMPLETED; + } + return JPEG_SCAN_COMPLETED; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +/* + * This code applies interblock smoothing as described by section K.8 + * of the JPEG standard: the first 5 AC coefficients are estimated from + * the DC values of a DCT block and its 8 neighboring blocks. + * We apply smoothing only for progressive JPEG decoding, and only if + * the coefficients it can estimate are not yet known to full precision. + */ + +/* + * Determine whether block smoothing is applicable and safe. + * We also latch the current states of the coef_bits[] entries for the + * AC coefficients; otherwise, if the input side of the decompressor + * advances into a new scan, we might think the coefficients are known + * more accurately than they really are. + */ + +LOCAL boolean +smoothing_ok( j_decompress_ptr cinfo ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + boolean smoothing_useful = FALSE; + int ci, coefi; + jpeg_component_info *compptr; + JQUANT_TBL * qtable; + int * coef_bits; + int * coef_bits_latch; + + if ( !cinfo->progressive_mode || cinfo->coef_bits == NULL ) { + return FALSE; + } + + /* Allocate latch area if not already done */ + if ( coef->coef_bits_latch == NULL ) { + coef->coef_bits_latch = ( int * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * + ( SAVED_COEFS * SIZEOF( int ) ) ); + } + coef_bits_latch = coef->coef_bits_latch; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* All components' quantization values must already be latched. */ + if ( ( qtable = compptr->quant_table ) == NULL ) { + return FALSE; + } + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + for ( coefi = 0; coefi <= 5; coefi++ ) { + if ( qtable->quantval[coefi] == 0 ) { + return FALSE; + } + } + /* DC values must be at least partly known for all components. */ + coef_bits = cinfo->coef_bits[ci]; + if ( coef_bits[0] < 0 ) { + return FALSE; + } + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + for ( coefi = 1; coefi <= 5; coefi++ ) { + coef_bits_latch[coefi] = coef_bits[coefi]; + if ( coef_bits[coefi] != 0 ) { + smoothing_useful = TRUE; + } + } + coef_bits_latch += SAVED_COEFS; + } + + return smoothing_useful; +} + + +/* + * Variant of decompress_data for use when doing block smoothing. + */ + +METHODDEF int +decompress_smooth_data( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num, last_block_column; + int ci, block_row, block_rows, access_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + boolean first_row, last_row; + JBLOCK workspace; + int *coef_bits; + JQUANT_TBL *quanttbl; + INT32 Q00,Q01,Q02,Q10,Q11,Q20, num; + int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9; + int Al, pred; + + /* Force some input to be done if we are getting ahead of the input. */ + while ( cinfo->input_scan_number <= cinfo->output_scan_number && + !cinfo->inputctl->eoi_reached ) { + if ( cinfo->input_scan_number == cinfo->output_scan_number ) { + /* If input is working on current scan, we ordinarily want it to + * have completed the current row. But if input scan is DC, + * we want it to keep one row ahead so that next block row's DC + * values are up to date. + */ + JDIMENSION delta = ( cinfo->Ss == 0 ) ? 1 : 0; + if ( cinfo->input_iMCU_row > cinfo->output_iMCU_row + delta ) { + break; + } + } + if ( ( *cinfo->inputctl->consume_input )( cinfo ) == JPEG_SUSPENDED ) { + return JPEG_SUSPENDED; + } + } + + /* OK, output from the virtual arrays. */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Don't bother to IDCT an uninteresting component. */ + if ( !compptr->component_needed ) { + continue; + } + /* Count non-dummy DCT block rows in this iMCU row. */ + if ( cinfo->output_iMCU_row < last_iMCU_row ) { + block_rows = compptr->v_samp_factor; + access_rows = block_rows * 2; /* this and next iMCU row */ + last_row = FALSE; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( block_rows == 0 ) { + block_rows = compptr->v_samp_factor; + } + access_rows = block_rows; /* this iMCU row only */ + last_row = TRUE; + } + /* Align the virtual buffer for this component. */ + if ( cinfo->output_iMCU_row > 0 ) { + access_rows += compptr->v_samp_factor; /* prior iMCU row too */ + buffer = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[ci], + ( cinfo->output_iMCU_row - 1 ) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE ); + buffer += compptr->v_samp_factor; /* point to current iMCU row */ + first_row = FALSE; + } else { + buffer = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE ); + first_row = TRUE; + } + /* Fetch component-dependent info */ + coef_bits = coef->coef_bits_latch + ( ci * SAVED_COEFS ); + quanttbl = compptr->quant_table; + Q00 = quanttbl->quantval[0]; + Q01 = quanttbl->quantval[1]; + Q10 = quanttbl->quantval[2]; + Q20 = quanttbl->quantval[3]; + Q11 = quanttbl->quantval[4]; + Q02 = quanttbl->quantval[5]; + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for ( block_row = 0; block_row < block_rows; block_row++ ) { + buffer_ptr = buffer[block_row]; + if ( first_row && block_row == 0 ) { + prev_block_row = buffer_ptr; + } else { + prev_block_row = buffer[block_row - 1]; + } + if ( last_row && block_row == block_rows - 1 ) { + next_block_row = buffer_ptr; + } else { + next_block_row = buffer[block_row + 1]; + } + /* We fetch the surrounding DC values using a sliding-register approach. + * Initialize all nine here so as to do the right thing on narrow pics. + */ + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + output_col = 0; + last_block_column = compptr->width_in_blocks - 1; + for ( block_num = 0; block_num <= last_block_column; block_num++ ) { + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row( buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1 ); + /* Update DC values */ + if ( block_num < last_block_column ) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ( ( Al = coef_bits[1] ) != 0 && workspace[1] == 0 ) { + num = 36 * Q00 * ( DC4 - DC6 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q01 << 7 ) + num ) / ( Q01 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q01 << 7 ) - num ) / ( Q01 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[1] = (JCOEF) pred; + } + /* AC10 */ + if ( ( Al = coef_bits[2] ) != 0 && workspace[8] == 0 ) { + num = 36 * Q00 * ( DC2 - DC8 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q10 << 7 ) + num ) / ( Q10 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q10 << 7 ) - num ) / ( Q10 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[8] = (JCOEF) pred; + } + /* AC20 */ + if ( ( Al = coef_bits[3] ) != 0 && workspace[16] == 0 ) { + num = 9 * Q00 * ( DC2 + DC8 - 2 * DC5 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q20 << 7 ) + num ) / ( Q20 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q20 << 7 ) - num ) / ( Q20 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[16] = (JCOEF) pred; + } + /* AC11 */ + if ( ( Al = coef_bits[4] ) != 0 && workspace[9] == 0 ) { + num = 5 * Q00 * ( DC1 - DC3 - DC7 + DC9 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q11 << 7 ) + num ) / ( Q11 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q11 << 7 ) - num ) / ( Q11 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[9] = (JCOEF) pred; + } + /* AC02 */ + if ( ( Al = coef_bits[5] ) != 0 && workspace[2] == 0 ) { + num = 9 * Q00 * ( DC4 + DC6 - 2 * DC5 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q02 << 7 ) + num ) / ( Q02 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q02 << 7 ) - num ) / ( Q02 << 8 ) ); + if ( Al > 0 && pred >= ( 1 << Al ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[2] = (JCOEF) pred; + } + /* OK, do the IDCT */ + ( *inverse_DCT )( cinfo, compptr, (JCOEFPTR) workspace, + output_ptr, output_col ); + /* Advance for next column */ + DC1 = DC2; DC2 = DC3; + DC4 = DC5; DC5 = DC6; + DC7 = DC8; DC8 = DC9; + buffer_ptr++, prev_block_row++, next_block_row++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if ( ++( cinfo->output_iMCU_row ) < cinfo->total_iMCU_rows ) { + return JPEG_ROW_COMPLETED; + } + return JPEG_SCAN_COMPLETED; +} + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_d_coef_controller( j_decompress_ptr cinfo, boolean need_full_buffer ) { + my_coef_ptr coef; + + coef = (my_coef_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_coef_controller ) ); + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + coef->pub.start_input_pass = start_input_pass; + coef->pub.start_output_pass = start_output_pass; +#ifdef BLOCK_SMOOTHING_SUPPORTED + coef->coef_bits_latch = NULL; +#endif + + /* Create the coefficient buffer. */ + if ( need_full_buffer ) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + /* Note we ask for a pre-zeroed array. */ + int ci, access_rows; + jpeg_component_info *compptr; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + access_rows = compptr->v_samp_factor; +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* If block smoothing could be used, need a bigger window */ + if ( cinfo->progressive_mode ) { + access_rows *= 3; + } +#endif + coef->whole_image[ci] = ( *cinfo->mem->request_virt_barray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up( (long) compptr->width_in_blocks, + (long) compptr->h_samp_factor ), + (JDIMENSION) jround_up( (long) compptr->height_in_blocks, + (long) compptr->v_samp_factor ), + (JDIMENSION) access_rows ); + } + coef->pub.consume_data = consume_data; + coef->pub.decompress_data = decompress_data; + coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */ +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + ( *cinfo->mem->alloc_large ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + D_MAX_BLOCKS_IN_MCU * SIZEOF( JBLOCK ) ); + for ( i = 0; i < D_MAX_BLOCKS_IN_MCU; i++ ) { + coef->MCU_buffer[i] = buffer + i; + } + coef->pub.consume_data = dummy_consume_data; + coef->pub.decompress_data = decompress_onepass; + coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */ + } +} diff --git a/src/jpeg-6/jdcolor.c b/src/jpeg-6/jdcolor.c new file mode 100644 index 0000000..4b8e31e --- /dev/null +++ b/src/jpeg-6/jdcolor.c @@ -0,0 +1,369 @@ +/* + * jdcolor.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains output colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_deconverter pub; /* public fields */ + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ +} my_color_deconverter; + +typedef my_color_deconverter * my_cconvert_ptr; + + +/**************** YCbCr -> RGB conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * Notice that Y, being an integral input, does not contribute any fraction + * so it need not participate in the rounding. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times Cb and Cr for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + * values for the G calculation are left scaled up, since we must add them + * together before rounding. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ( (INT32) 1 << ( SCALEBITS - 1 ) ) +#define FIX( x ) ( (INT32) ( ( x ) * ( 1L << SCALEBITS ) + 0.5 ) ) + + +/* + * Initialize tables for YCC->RGB colorspace conversion. + */ + +LOCAL void +build_ycc_rgb_table( j_decompress_ptr cinfo ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + int i; + INT32 x; + SHIFT_TEMPS + + cconvert->Cr_r_tab = ( int * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( int ) ); + cconvert->Cb_b_tab = ( int * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( int ) ); + cconvert->Cr_g_tab = ( INT32 * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( INT32 ) ); + cconvert->Cb_g_tab = ( INT32 * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( INT32 ) ); + + for ( i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++ ) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + cconvert->Cr_r_tab[i] = (int) + RIGHT_SHIFT( FIX( 1.40200 ) * x + ONE_HALF, SCALEBITS ); + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert->Cb_b_tab[i] = (int) + RIGHT_SHIFT( FIX( 1.77200 ) * x + ONE_HALF, SCALEBITS ); + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert->Cr_g_tab[i] = ( -FIX( 0.71414 ) ) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + cconvert->Cb_g_tab[i] = ( -FIX( 0.34414 ) ) * x + ONE_HALF; + } +} + + +/* + * Convert some rows of samples to the output colorspace. + * + * Note that we change from noninterleaved, one-plane-per-component format + * to interleaved-pixel format. The output buffer is therefore three times + * as wide as the input buffer. + * A starting row offset is provided only for the input buffer. The caller + * can easily adjust the passed output_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +ycc_rgb_convert( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while ( --num_rows >= 0 ) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + input_row++; + outptr = *output_buf++; + for ( col = 0; col < num_cols; col++ ) { + y = GETJSAMPLE( inptr0[col] ); + cb = GETJSAMPLE( inptr1[col] ); + cr = GETJSAMPLE( inptr2[col] ); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + outptr[RGB_GREEN] = range_limit[y + + ( (int) RIGHT_SHIFT( Cbgtab[cb] + Crgtab[cr], + SCALEBITS ) )]; + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/**************** Cases other than YCbCr -> RGB **************/ + + +/* + * Color conversion for no colorspace change: just copy the data, + * converting from separate-planes to interleaved representation. + */ + +METHODDEF void +null_convert( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) { + register JSAMPROW inptr, outptr; + register JDIMENSION count; + register int num_components = cinfo->num_components; + JDIMENSION num_cols = cinfo->output_width; + int ci; + + while ( --num_rows >= 0 ) { + for ( ci = 0; ci < num_components; ci++ ) { + inptr = input_buf[ci][input_row]; + outptr = output_buf[0] + ci; + for ( count = num_cols; count > 0; count-- ) { + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + outptr += num_components; + } + } + input_row++; + output_buf++; + } +} + + +/* + * Color conversion for grayscale: just copy the data. + * This also works for YCbCr -> grayscale conversion, in which + * we just copy the Y (luminance) component and ignore chrominance. + */ + +METHODDEF void +grayscale_convert( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) { + jcopy_sample_rows( input_buf[0], (int) input_row, output_buf, 0, + num_rows, cinfo->output_width ); +} + + +/* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + +METHODDEF void +ycck_cmyk_convert( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while ( --num_rows >= 0 ) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + inptr3 = input_buf[3][input_row]; + input_row++; + outptr = *output_buf++; + for ( col = 0; col < num_cols; col++ ) { + y = GETJSAMPLE( inptr0[col] ); + cb = GETJSAMPLE( inptr1[col] ); + cr = GETJSAMPLE( inptr2[col] ); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[0] = range_limit[MAXJSAMPLE - ( y + Crrtab[cr] )]; /* red */ + outptr[1] = range_limit[MAXJSAMPLE - ( y + /* green */ + ( (int) RIGHT_SHIFT( Cbgtab[cb] + Crgtab[cr], + SCALEBITS ) ) )]; + outptr[2] = range_limit[MAXJSAMPLE - ( y + Cbbtab[cb] )]; /* blue */ + /* K passes through unchanged */ + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + outptr += 4; + } + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +start_pass_dcolor( j_decompress_ptr cinfo ) { + /* no work needed */ +} + + +/* + * Module initialization routine for output colorspace conversion. + */ + +GLOBAL void +jinit_color_deconverter( j_decompress_ptr cinfo ) { + my_cconvert_ptr cconvert; + int ci; + + cconvert = (my_cconvert_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_color_deconverter ) ); + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + cconvert->pub.start_pass = start_pass_dcolor; + + /* Make sure num_components agrees with jpeg_color_space */ + switch ( cinfo->jpeg_color_space ) { + case JCS_GRAYSCALE: + if ( cinfo->num_components != 1 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + break; + + case JCS_RGB: + case JCS_YCbCr: + if ( cinfo->num_components != 3 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + break; + + case JCS_CMYK: + case JCS_YCCK: + if ( cinfo->num_components != 4 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + break; + + default: /* JCS_UNKNOWN can be anything */ + if ( cinfo->num_components < 1 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + break; + } + + /* Set out_color_components and conversion method based on requested space. + * Also clear the component_needed flags for any unused components, + * so that earlier pipeline stages can avoid useless computation. + */ + + switch ( cinfo->out_color_space ) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + if ( cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr ) { + cconvert->pub.color_convert = grayscale_convert; + /* For color->grayscale conversion, only the Y (0) component is needed */ + for ( ci = 1; ci < cinfo->num_components; ci++ ) + cinfo->comp_info[ci].component_needed = FALSE; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_RGB: + cinfo->out_color_components = RGB_PIXELSIZE; + if ( cinfo->jpeg_color_space == JCS_YCbCr ) { + cconvert->pub.color_convert = ycc_rgb_convert; + build_ycc_rgb_table( cinfo ); + } else if ( cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3 ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_CMYK: + cinfo->out_color_components = 4; + if ( cinfo->jpeg_color_space == JCS_YCCK ) { + cconvert->pub.color_convert = ycck_cmyk_convert; + build_ycc_rgb_table( cinfo ); + } else if ( cinfo->jpeg_color_space == JCS_CMYK ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + default: + /* Permit null conversion to same output space */ + if ( cinfo->out_color_space == cinfo->jpeg_color_space ) { + cinfo->out_color_components = cinfo->num_components; + cconvert->pub.color_convert = null_convert; + } else { /* unsupported non-null conversion */ + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + } + + if ( cinfo->quantize_colors ) { + cinfo->output_components = 1; /* single colormapped output component */ + } else { + cinfo->output_components = cinfo->out_color_components; + } +} diff --git a/src/jpeg-6/jdct.h b/src/jpeg-6/jdct.h new file mode 100644 index 0000000..d841089 --- /dev/null +++ b/src/jpeg-6/jdct.h @@ -0,0 +1,176 @@ +/* + * jdct.h + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file contains common declarations for the forward and + * inverse DCT modules. These declarations are private to the DCT managers + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + * The individual DCT algorithms are kept in separate files to ease + * machine-dependent tuning (e.g., assembly coding). + */ + + +/* + * A forward DCT routine is given a pointer to a work area of type DCTELEM[]; + * the DCT is to be performed in-place in that buffer. Type DCTELEM is int + * for 8-bit samples, INT32 for 12-bit samples. (NOTE: Floating-point DCT + * implementations use an array of type FAST_FLOAT, instead.) + * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE). + * The DCT outputs are returned scaled up by a factor of 8; they therefore + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + * convention improves accuracy in integer implementations and saves some + * work in floating-point ones. + * Quantization of the output coefficients is done by jcdctmgr.c. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef int DCTELEM; /* 16 or 32 bits is fine */ +#else +typedef INT32 DCTELEM; /* must have 32 bits */ +#endif + +typedef JMETHOD ( void, forward_DCT_method_ptr, ( DCTELEM * data ) ); +typedef JMETHOD ( void, float_DCT_method_ptr, ( FAST_FLOAT * data ) ); + + +/* + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + * to an output sample array. The routine must dequantize the input data as + * well as perform the IDCT; for dequantization, it uses the multiplier table + * pointed to by compptr->dct_table. The output data is to be placed into the + * sample array starting at a specified column. (Any row offset needed will + * be applied to the array pointer before it is passed to the IDCT code.) + * Note that the number of samples emitted by the IDCT routine is + * DCT_scaled_size * DCT_scaled_size. + */ + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + +/* + * Each IDCT routine has its own ideas about the best dct_table element type. + */ + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ +#if BITS_IN_JSAMPLE == 8 +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#else +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +#endif +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + +/* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly. See the comments with + * prepare_range_limit_table (in jdmaster.c) for more info. + */ + +#define IDCT_range_limit( cinfo ) ( ( cinfo )->sample_range_limit + CENTERJSAMPLE ) + +#define RANGE_MASK ( MAXJSAMPLE * 4 + 3 ) /* 2 bits wider than legal samples */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Extern declarations for the forward and inverse DCT routines. */ + +EXTERN void jpeg_fdct_islow JPP( ( DCTELEM * data ) ); +EXTERN void jpeg_fdct_ifast JPP( ( DCTELEM * data ) ); +EXTERN void jpeg_fdct_float JPP( ( FAST_FLOAT * data ) ); + +EXTERN void jpeg_idct_islow +JPP( ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col ) ); +EXTERN void jpeg_idct_ifast +JPP( ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col ) ); +EXTERN void jpeg_idct_float +JPP( ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col ) ); +EXTERN void jpeg_idct_4x4 +JPP( ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col ) ); +EXTERN void jpeg_idct_2x2 +JPP( ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col ) ); +EXTERN void jpeg_idct_1x1 +JPP( ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col ) ); + + +/* + * Macros for handling fixed-point arithmetic; these are used by many + * but not all of the DCT/IDCT modules. + * + * All values are expected to be of type INT32. + * Fractional constants are scaled left by CONST_BITS bits. + * CONST_BITS is defined within each module using these macros, + * and may differ from one module to the next. + */ + +#define ONE ( (INT32) 1 ) +#define CONST_SCALE ( ONE << CONST_BITS ) + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + * thus causing a lot of useless floating-point operations at run time. + */ + +#define FIX( x ) ( (INT32) ( ( x ) * CONST_SCALE + 0.5 ) ) + +/* Descale and correctly round an INT32 value that's scaled by N bits. + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + * the fudge factor is correct for either sign of X. + */ + +#define DESCALE( x,n ) RIGHT_SHIFT( ( x ) + ( ONE << ( ( n ) - 1 ) ), n ) + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * This macro is used only when the two inputs will actually be no more than + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + * full 32x32 multiply. This provides a useful speedup on many machines. + * Unfortunately there is no way to specify a 16x16->32 multiply portably + * in C, but some C compilers will do the right thing if you provide the + * correct combination of casts. + */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16C16( var,const ) ( ( (INT16) ( var ) ) * ( (INT16) ( const ) ) ) +#endif +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#define MULTIPLY16C16( var,const ) ( ( (INT16) ( var ) ) * ( (INT32) ( const ) ) ) +#endif + +#ifndef MULTIPLY16C16 /* default definition */ +#define MULTIPLY16C16( var,const ) ( ( var ) * ( const ) ) +#endif + +/* Same except both inputs are variables. */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16V16( var1,var2 ) ( ( (INT16) ( var1 ) ) * ( (INT16) ( var2 ) ) ) +#endif + +#ifndef MULTIPLY16V16 /* default definition */ +#define MULTIPLY16V16( var1,var2 ) ( ( var1 ) * ( var2 ) ) +#endif diff --git a/src/jpeg-6/jddctmgr.c b/src/jpeg-6/jddctmgr.c new file mode 100644 index 0000000..66f49a4 --- /dev/null +++ b/src/jpeg-6/jddctmgr.c @@ -0,0 +1,270 @@ +/* + * jddctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the inverse-DCT management logic. + * This code selects a particular IDCT implementation to be used, + * and it performs related housekeeping chores. No code in this file + * is executed per IDCT step, only during output pass setup. + * + * Note that the IDCT routines are responsible for performing coefficient + * dequantization as well as the IDCT proper. This module sets up the + * dequantization multiplier table needed by the IDCT routine. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* + * The decompressor input side (jdinput.c) saves away the appropriate + * quantization table for each component at the start of the first scan + * involving that component. (This is necessary in order to correctly + * decode files that reuse Q-table slots.) + * When we are ready to make an output pass, the saved Q-table is converted + * to a multiplier table that will actually be used by the IDCT routine. + * The multiplier table contents are IDCT-method-dependent. To support + * application changes in IDCT method between scans, we can remake the + * multiplier tables if necessary. + * In buffered-image mode, the first output pass may occur before any data + * has been seen for some components, and thus before their Q-tables have + * been saved away. To handle this case, multiplier tables are preset + * to zeroes; the result of the IDCT will be a neutral gray level. + */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_inverse_dct pub; /* public fields */ + + /* This array contains the IDCT method code that each multiplier table + * is currently set up for, or -1 if it's not yet set up. + * The actual multiplier tables are pointed to by dct_table in the + * per-component comp_info structures. + */ + int cur_method[MAX_COMPONENTS]; +} my_idct_controller; + +typedef my_idct_controller * my_idct_ptr; + + +/* Allocated multiplier tables: big enough for any supported variant */ + +typedef union { + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; +#ifdef DCT_IFAST_SUPPORTED + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; +#endif +#ifdef DCT_FLOAT_SUPPORTED + FLOAT_MULT_TYPE float_array[DCTSIZE2]; +#endif +} multiplier_table; + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef IDCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Prepare for an output pass. + * Here we select the proper IDCT routine for each component and build + * a matching multiplier table. + */ + +METHODDEF void +start_pass( j_decompress_ptr cinfo ) { + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + int ci, i; + jpeg_component_info *compptr; + int method = 0; + inverse_DCT_method_ptr method_ptr = NULL; + JQUANT_TBL * qtbl; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Select the proper IDCT routine for this component's scaling */ + switch ( compptr->DCT_scaled_size ) { +#ifdef IDCT_SCALING_SUPPORTED + case 1: + method_ptr = jpeg_idct_1x1; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 2: + method_ptr = jpeg_idct_2x2; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 4: + method_ptr = jpeg_idct_4x4; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; +#endif + case DCTSIZE: + switch ( cinfo->dct_method ) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } + break; + default: + ERREXIT1( cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size ); + break; + } + idct->pub.inverse_DCT[ci] = method_ptr; + /* Create multiplier table from quant table. + * However, we can skip this if the component is uninteresting + * or if we already built the table. Also, if no quant table + * has yet been saved for the component, we leave the + * multiplier table all-zero; we'll be reading zeroes from the + * coefficient controller's buffer anyway. + */ + if ( !compptr->component_needed || idct->cur_method[ci] == method ) { + continue; + } + qtbl = compptr->quant_table; + if ( qtbl == NULL ) { /* happens if no data yet for component */ + continue; + } + idct->cur_method[ci] = method; + switch ( method ) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + { + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored in natural order as ints. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for ( i = 0; i < DCTSIZE2; i++ ) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[jpeg_zigzag_order[i]]; + } + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. The multipliers are stored in natural order. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for ( i = 0; i < DCTSIZE2; i++ ) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE( MULTIPLY16V16( (INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i] ), + CONST_BITS - IFAST_SCALE_BITS ); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * The multipliers are stored in natural order. + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for ( row = 0; row < DCTSIZE; row++ ) { + for ( col = 0; col < DCTSIZE; col++ ) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ( (double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col] ); + i++; + } + } + } + break; +#endif + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } + } +} + + +/* + * Initialize IDCT manager. + */ + +GLOBAL void +jinit_inverse_dct( j_decompress_ptr cinfo ) { + my_idct_ptr idct; + int ci; + jpeg_component_info *compptr; + + idct = (my_idct_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_idct_controller ) ); + cinfo->idct = (struct jpeg_inverse_dct *) idct; + idct->pub.start_pass = start_pass; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Allocate and pre-zero a multiplier table for each component */ + compptr->dct_table = + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( multiplier_table ) ); + MEMZERO( compptr->dct_table, SIZEOF( multiplier_table ) ); + /* Mark multiplier table not yet set up for any method */ + idct->cur_method[ci] = -1; + } +} diff --git a/src/jpeg-6/jdhuff.c b/src/jpeg-6/jdhuff.c new file mode 100644 index 0000000..e696e26 --- /dev/null +++ b/src/jpeg-6/jdhuff.c @@ -0,0 +1,581 @@ +/* + * jdhuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdphuff.c */ + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE( dest,src ) ( ( dest ) = ( src ) ) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE( dest,src ) \ + ( ( dest ).last_dc_val[0] = ( src ).last_dc_val[0], \ + ( dest ).last_dc_val[1] = ( src ).last_dc_val[1], \ + ( dest ).last_dc_val[2] = ( src ).last_dc_val[2], \ + ( dest ).last_dc_val[3] = ( src ).last_dc_val[3] ) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; +} huff_entropy_decoder; + +typedef huff_entropy_decoder * huff_entropy_ptr; + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF void +start_pass_huff_decoder( j_decompress_ptr cinfo ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if ( cinfo->Ss != 0 || cinfo->Se != DCTSIZE2 - 1 || + cinfo->Ah != 0 || cinfo->Al != 0 ) { + WARNMS( cinfo, JWRN_NOT_SEQUENTIAL ); + } + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + if ( dctbl < 0 || dctbl >= NUM_HUFF_TBLS || + cinfo->dc_huff_tbl_ptrs[dctbl] == NULL ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, dctbl ); + } + if ( actbl < 0 || actbl >= NUM_HUFF_TBLS || + cinfo->ac_huff_tbl_ptrs[actbl] == NULL ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, actbl ); + } + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_d_derived_tbl( cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + &entropy->dc_derived_tbls[dctbl] ); + jpeg_make_d_derived_tbl( cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + &entropy->ac_derived_tbls[actbl] ); + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->bitstate.printed_eod = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jdphuff.c. + */ + +GLOBAL void +jpeg_make_d_derived_tbl( j_decompress_ptr cinfo, JHUFF_TBL * htbl, + d_derived_tbl ** pdtbl ) { + d_derived_tbl *dtbl; + int p, i, l, si; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if ( *pdtbl == NULL ) { + *pdtbl = ( d_derived_tbl * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( d_derived_tbl ) ); + } + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for ( l = 1; l <= 16; l++ ) { + for ( i = 1; i <= (int) htbl->bits[l]; i++ ) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while ( huffsize[p] ) { + while ( ( (int) huffsize[p] ) == si ) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for ( l = 1; l <= 16; l++ ) { + if ( htbl->bits[l] ) { + dtbl->valptr[l] = p; /* huffval[] index of 1st symbol of code length l */ + dtbl->mincode[l] = huffcode[p]; /* minimum code of length l */ + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p - 1]; /* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO( dtbl->look_nbits, SIZEOF( dtbl->look_nbits ) ); + + p = 0; + for ( l = 1; l <= HUFF_LOOKAHEAD; l++ ) { + for ( i = 1; i <= (int) htbl->bits[l]; i++, p++ ) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << ( HUFF_LOOKAHEAD - l ); + for ( ctr = 1 << ( HUFF_LOOKAHEAD - l ); ctr > 0; ctr-- ) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } +} + + +/* + * Out-of-line code for bit fetching (shared with jdphuff.c). + * See jdhuff.h for info about usage. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS ( BIT_BUF_SIZE - 7 ) +#endif + + +GLOBAL boolean +jpeg_fill_bit_buffer( bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits ) { +/* Load up the bit buffer to a depth of at least nbits */ +/* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + register int c; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + + while ( bits_left < MIN_GET_BITS ) { + /* Attempt to read a byte */ + if ( state->unread_marker != 0 ) { + goto no_more_data; /* can't advance past a marker */ + + } + if ( bytes_in_buffer == 0 ) { + if ( !( *state->cinfo->src->fill_input_buffer )( state->cinfo ) ) { + return FALSE; + } + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET( *next_input_byte++ ); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if ( c == 0xFF ) { + do { + if ( bytes_in_buffer == 0 ) { + if ( !( *state->cinfo->src->fill_input_buffer )( state->cinfo ) ) { + return FALSE; + } + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET( *next_input_byte++ ); + } while ( c == 0xFF ); + + if ( c == 0 ) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. */ + /* Better put it back for use later */ + state->unread_marker = c; + +no_more_data: + /* There should be enough bits still left in the data segment; */ + /* if so, just break out of the outer while loop. */ + if ( bits_left >= nbits ) { + break; + } + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * Note that this code will be repeated for each byte demanded + * for the rest of the segment. We use a nonvolatile flag to ensure + * that only one warning message appears. + */ + if ( !*( state->printed_eod_ptr ) ) { + WARNMS( state->cinfo, JWRN_HIT_MARKER ); + *( state->printed_eod_ptr ) = TRUE; + } + c = 0; /* insert a zero byte into bit buffer */ + } + } + + /* OK, load c into get_buffer */ + get_buffer = ( get_buffer << 8 ) | c; + bits_left += 8; + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Out-of-line code for Huffman code decoding. + * See jdhuff.h for info about usage. + */ + +GLOBAL int +jpeg_huff_decode( bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits ) { + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER( *state, l, return -1 ); + code = GET_BITS( l ); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while ( code > htbl->maxcode[l] ) { + code <<= 1; + CHECK_BIT_BUFFER( *state, 1, return -1 ); + code |= GET_BITS( 1 ); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if ( l > 16 ) { + WARNMS( state->cinfo, JWRN_HUFF_BAD_CODE ); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ htbl->valptr[l] + + ( (int) ( code - htbl->mincode[l] ) ) ]; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND( x,s ) ( ( x ) < ( 1 << ( ( s ) - 1 ) ) ? ( x ) + ( ( ( -1 ) << ( s ) ) + 1 ) : ( x ) ) + +#else + +#define HUFF_EXTEND( x,s ) ( ( x ) < extend_test[s] ? ( x ) + extend_offset[s] : ( x ) ) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ +{ 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ +{ 0, ( ( -1 ) << 1 ) + 1, ( ( -1 ) << 2 ) + 1, ( ( -1 ) << 3 ) + 1, ( ( -1 ) << 4 ) + 1, + ( ( -1 ) << 5 ) + 1, ( ( -1 ) << 6 ) + 1, ( ( -1 ) << 7 ) + 1, ( ( -1 ) << 8 ) + 1, + ( ( -1 ) << 9 ) + 1, ( ( -1 ) << 10 ) + 1, ( ( -1 ) << 11 ) + 1, ( ( -1 ) << 12 ) + 1, + ( ( -1 ) << 13 ) + 1, ( ( -1 ) << 14 ) + 1, ( ( -1 ) << 15 ) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL boolean +process_restart( j_decompress_ptr cinfo ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if ( !( *cinfo->marker->read_restart_marker )( cinfo ) ) { + return FALSE; + } + + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) + entropy->saved.last_dc_val[ci] = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Next segment can get another out-of-data warning */ + entropy->bitstate.printed_eod = FALSE; + + return TRUE; +} + + +/* + * Decode and return one MCU's worth of Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * Returns FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * this module, since we'll just re-assign them on the next call.) + */ + +METHODDEF boolean +decode_mcu( j_decompress_ptr cinfo, JBLOCKROW *MCU_data ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int s, k, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * dctbl; + d_derived_tbl * actbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + if ( !process_restart( cinfo ) ) { + return FALSE; + } + } + } + + /* Load up working state */ + BITREAD_LOAD_STATE( cinfo,entropy->bitstate ); + ASSIGN_STATE( state, entropy->saved ); + + /* Outer loop handles each block in the MCU */ + + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + dctbl = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + actbl = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE( s, br_state, dctbl, return FALSE, label1 ); + if ( s ) { + CHECK_BIT_BUFFER( br_state, s, return FALSE ); + r = GET_BITS( s ); + s = HUFF_EXTEND( r, s ); + } + + /* Shortcut if component's values are not interesting */ + if ( !compptr->component_needed ) { + goto skip_ACs; + } + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + ( *block )[0] = (JCOEF) s; + + /* Do we need to decode the AC coefficients for this component? */ + if ( compptr->DCT_scaled_size > 1 ) { + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for ( k = 1; k < DCTSIZE2; k++ ) { + HUFF_DECODE( s, br_state, actbl, return FALSE, label2 ); + + r = s >> 4; + s &= 15; + + if ( s ) { + k += r; + CHECK_BIT_BUFFER( br_state, s, return FALSE ); + r = GET_BITS( s ); + s = HUFF_EXTEND( r, s ); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + ( *block )[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if ( r != 15 ) { + break; + } + k += 15; + } + } + + } else { +skip_ACs: + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for ( k = 1; k < DCTSIZE2; k++ ) { + HUFF_DECODE( s, br_state, actbl, return FALSE, label3 ); + + r = s >> 4; + s &= 15; + + if ( s ) { + k += r; + CHECK_BIT_BUFFER( br_state, s, return FALSE ); + DROP_BITS( s ); + } else { + if ( r != 15 ) { + break; + } + k += 15; + } + } + + } + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE( cinfo,entropy->bitstate ); + ASSIGN_STATE( entropy->saved, state ); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL void +jinit_huff_decoder( j_decompress_ptr cinfo ) { + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( huff_entropy_decoder ) ); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + entropy->pub.decode_mcu = decode_mcu; + + /* Mark tables unallocated */ + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } +} diff --git a/src/jpeg-6/jdhuff.h b/src/jpeg-6/jdhuff.h new file mode 100644 index 0000000..d56dc5c --- /dev/null +++ b/src/jpeg-6/jdhuff.h @@ -0,0 +1,202 @@ +/* + * jdhuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy decoding routines + * that are shared between the sequential decoder (jdhuff.c) and the + * progressive decoder (jdphuff.c). No other modules need to see these. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 mincode[17]; /* smallest code of length k */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + int valptr[17]; /* huffval[] index of 1st symbol of length k */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1 << HUFF_LOOKAHEAD]; /* # bits, or 0 if too long */ + UINT8 look_sym[1 << HUFF_LOOKAHEAD]; /* symbol, or unused */ +} d_derived_tbl; + +/* Expand a Huffman table definition into the derived format */ +EXTERN void jpeg_make_d_derived_tbl JPP( ( j_decompress_ptr cinfo, + JHUFF_TBL * htbl, d_derived_tbl * *pdtbl ) ); + + +/* + * Fetching the next N bits from the input stream is a time-critical operation + * for the Huffman decoders. We implement it with a combination of inline + * macros and out-of-line subroutines. Note that N (the number of bits + * demanded at one time) never exceeds 15 for JPEG use. + * + * We read source bytes into get_buffer and dole out bits as needed. + * If get_buffer already contains enough bits, they are fetched in-line + * by the macros CHECK_BIT_BUFFER and GET_BITS. When there aren't enough + * bits, jpeg_fill_bit_buffer is called; it will attempt to fill get_buffer + * as full as possible (not just to the number of bits needed; this + * prefetching reduces the overhead cost of calling jpeg_fill_bit_buffer). + * Note that jpeg_fill_bit_buffer may return FALSE to indicate suspension. + * On TRUE return, jpeg_fill_bit_buffer guarantees that get_buffer contains + * at least the requested number of bits --- dummy zeroes are inserted if + * necessary. + */ + +typedef INT32 bit_buf_type; /* type of bit-extraction buffer */ +#define BIT_BUF_SIZE 32 /* size of buffer in bits */ + +/* If long is > 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't do this with + * something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + boolean printed_eod; /* flag to suppress multiple warning msgs */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* current data source state */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + int unread_marker; /* nonzero if we have hit a marker */ + /* bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* pointers needed by jpeg_fill_bit_buffer */ + j_decompress_ptr cinfo; /* back link to decompress master record */ + boolean * printed_eod_ptr; /* => flag in permanent state */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE( cinfop,permstate ) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + br_state.unread_marker = cinfop->unread_marker; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; \ + br_state.printed_eod_ptr = &permstate.printed_eod + +#define BITREAD_SAVE_STATE( cinfop,permstate ) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + cinfop->unread_marker = br_state.unread_marker; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER( state,nbits,action ) \ + { if ( bits_left < ( nbits ) ) { \ + if ( !jpeg_fill_bit_buffer( &( state ),get_buffer,bits_left,nbits ) ) \ + { action; } \ + get_buffer = ( state ).get_buffer; bits_left = ( state ).bits_left; } } + +#define GET_BITS( nbits ) \ + ( ( (int) ( get_buffer >> ( bits_left -= ( nbits ) ) ) ) & ( ( 1 << ( nbits ) ) - 1 ) ) + +#define PEEK_BITS( nbits ) \ + ( ( (int) ( get_buffer >> ( bits_left - ( nbits ) ) ) ) & ( ( 1 << ( nbits ) ) - 1 ) ) + +#define DROP_BITS( nbits ) \ + ( bits_left -= ( nbits ) ) + +/* Load up the bit buffer to a depth of at least nbits */ +EXTERN boolean jpeg_fill_bit_buffer JPP( ( bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits ) ); + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE( result,state,htbl,failaction,slowlabel ) \ + { register int nb, look; \ + if ( bits_left < HUFF_LOOKAHEAD ) { \ + if ( !jpeg_fill_bit_buffer( &state,get_buffer,bits_left, 0 ) ) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if ( bits_left < HUFF_LOOKAHEAD ) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS( HUFF_LOOKAHEAD ); \ + if ( ( nb = htbl->look_nbits[look] ) != 0 ) { \ + DROP_BITS( nb ); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD + 1; \ +slowlabel: \ + if ( ( result = jpeg_huff_decode( &state,get_buffer,bits_left,htbl,nb ) ) < 0 ) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ + } + +/* Out-of-line case for Huffman code fetching */ +EXTERN int jpeg_huff_decode JPP( ( bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits ) ); diff --git a/src/jpeg-6/jdinput.c b/src/jpeg-6/jdinput.c new file mode 100644 index 0000000..ab68e85 --- /dev/null +++ b/src/jpeg-6/jdinput.c @@ -0,0 +1,392 @@ +/* + * jdinput.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input control logic for the JPEG decompressor. + * These routines are concerned with controlling the decompressor's input + * processing (marker reading and coefficient decoding). The actual input + * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_input_controller pub; /* public fields */ + + boolean inheaders; /* TRUE until first SOS is reached */ +} my_input_controller; + +typedef my_input_controller * my_inputctl_ptr; + + +/* Forward declarations */ +METHODDEF int consume_markers JPP( (j_decompress_ptr cinfo) ); + + +/* + * Routines to calculate various quantities related to the size of the image. + */ + +LOCAL void +initial_setup( j_decompress_ptr cinfo ) { +/* Called once, when first SOS marker is reached */ + int ci; + jpeg_component_info *compptr; + + /* Make sure image isn't bigger than I can handle */ + if ( (long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION ) { + ERREXIT1( cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION ); + } + + /* For now, precision must match compiled-in value... */ + if ( cinfo->data_precision != BITS_IN_JSAMPLE ) { + ERREXIT1( cinfo, JERR_BAD_PRECISION, cinfo->data_precision ); + } + + /* Check that number of components won't exceed internal array sizes */ + if ( cinfo->num_components > MAX_COMPONENTS ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS ); + } + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( compptr->h_samp_factor <= 0 || compptr->h_samp_factor > MAX_SAMP_FACTOR || + compptr->v_samp_factor <= 0 || compptr->v_samp_factor > MAX_SAMP_FACTOR ) { + ERREXIT( cinfo, JERR_BAD_SAMPLING ); + } + cinfo->max_h_samp_factor = MAX( cinfo->max_h_samp_factor, + compptr->h_samp_factor ); + cinfo->max_v_samp_factor = MAX( cinfo->max_v_samp_factor, + compptr->v_samp_factor ); + } + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. + * In the full decompressor, this will be overridden by jdmaster.c; + * but in the transcoder, jdmaster.c is not used, so we must do it here. + */ + cinfo->min_DCT_scaled_size = DCTSIZE; + + /* Compute dimensions of components */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + /* downsampled_width and downsampled_height will also be overridden by + * jdmaster.c if we are doing full decompression. The transcoder library + * doesn't use these values, but the calling application might. + */ + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor ); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor ); + /* Mark component needed, until color conversion says otherwise */ + compptr->component_needed = TRUE; + /* Mark no quantization table yet saved for component */ + compptr->quant_table = NULL; + } + + /* Compute number of fully interleaved MCU rows. */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + + /* Decide whether file contains multiple scans */ + if ( cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode ) { + cinfo->inputctl->has_multiple_scans = TRUE; + } else { + cinfo->inputctl->has_multiple_scans = FALSE; + } +} + + +LOCAL void +per_scan_setup( j_decompress_ptr cinfo ) { +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if ( cinfo->comps_in_scan == 1 ) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( tmp == 0 ) { + tmp = compptr->v_samp_factor; + } + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if ( cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN ); + } + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + + cinfo->blocks_in_MCU = 0; + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) ( compptr->width_in_blocks % compptr->MCU_width ); + if ( tmp == 0 ) { + tmp = compptr->MCU_width; + } + compptr->last_col_width = tmp; + tmp = (int) ( compptr->height_in_blocks % compptr->MCU_height ); + if ( tmp == 0 ) { + tmp = compptr->MCU_height; + } + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if ( cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU ) { + ERREXIT( cinfo, JERR_BAD_MCU_SIZE ); + } + while ( mcublks-- > 0 ) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } +} + + +/* + * Save away a copy of the Q-table referenced by each component present + * in the current scan, unless already saved during a prior scan. + * + * In a multiple-scan JPEG file, the encoder could assign different components + * the same Q-table slot number, but change table definitions between scans + * so that each component uses a different Q-table. (The IJG encoder is not + * currently capable of doing this, but other encoders might.) Since we want + * to be able to dequantize all the components at the end of the file, this + * means that we have to save away the table actually used for each component. + * We do this by copying the table at the start of the first scan containing + * the component. + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + * slot between scans of a component using that slot. If the encoder does so + * anyway, this decoder will simply use the Q-table values that were current + * at the start of the first scan for the component. + * + * The decompressor output side looks only at the saved quant tables, + * not at the current Q-table slots. + */ + +LOCAL void +latch_quant_tables( j_decompress_ptr cinfo ) { + int ci, qtblno; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* No work if we already saved Q-table for this component */ + if ( compptr->quant_table != NULL ) { + continue; + } + /* Make sure specified quantization table is present */ + qtblno = compptr->quant_tbl_no; + if ( qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL ) { + ERREXIT1( cinfo, JERR_NO_QUANT_TABLE, qtblno ); + } + /* OK, save away the quantization table */ + qtbl = ( JQUANT_TBL * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( JQUANT_TBL ) ); + MEMCOPY( qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF( JQUANT_TBL ) ); + compptr->quant_table = qtbl; + } +} + + +/* + * Initialize the input modules to read a scan of compressed data. + * The first call to this is done by jdmaster.c after initializing + * the entire decompressor (during jpeg_start_decompress). + * Subsequent calls come from consume_markers, below. + */ + +METHODDEF void +start_input_pass( j_decompress_ptr cinfo ) { + per_scan_setup( cinfo ); + latch_quant_tables( cinfo ); + ( *cinfo->entropy->start_pass )( cinfo ); + ( *cinfo->coef->start_input_pass )( cinfo ); + cinfo->inputctl->consume_input = cinfo->coef->consume_data; +} + + +/* + * Finish up after inputting a compressed-data scan. + * This is called by the coefficient controller after it's read all + * the expected data of the scan. + */ + +METHODDEF void +finish_input_pass( j_decompress_ptr cinfo ) { + cinfo->inputctl->consume_input = consume_markers; +} + + +/* + * Read JPEG markers before, between, or after compressed-data scans. + * Change state as necessary when a new scan is reached. + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * The consume_input method pointer points either here or to the + * coefficient controller's consume_data routine, depending on whether + * we are reading a compressed data segment or inter-segment markers. + */ + +METHODDEF int +consume_markers( j_decompress_ptr cinfo ) { + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + int val; + + if ( inputctl->pub.eoi_reached ) { /* After hitting EOI, read no further */ + return JPEG_REACHED_EOI; + } + + val = ( *cinfo->marker->read_markers )( cinfo ); + + switch ( val ) { + case JPEG_REACHED_SOS: /* Found SOS */ + if ( inputctl->inheaders ) { /* 1st SOS */ + initial_setup( cinfo ); + inputctl->inheaders = FALSE; + /* Note: start_input_pass must be called by jdmaster.c + * before any more input can be consumed. jdapi.c is + * responsible for enforcing this sequencing. + */ + } else { /* 2nd or later SOS marker */ + if ( !inputctl->pub.has_multiple_scans ) { + ERREXIT( cinfo, JERR_EOI_EXPECTED ); /* Oops, I wasn't expecting this! */ + } + start_input_pass( cinfo ); + } + break; + case JPEG_REACHED_EOI: /* Found EOI */ + inputctl->pub.eoi_reached = TRUE; + if ( inputctl->inheaders ) { /* Tables-only datastream, apparently */ + if ( cinfo->marker->saw_SOF ) { + ERREXIT( cinfo, JERR_SOF_NO_SOS ); + } + } else { + /* Prevent infinite loop in coef ctlr's decompress_data routine + * if user set output_scan_number larger than number of scans. + */ + if ( cinfo->output_scan_number > cinfo->input_scan_number ) { + cinfo->output_scan_number = cinfo->input_scan_number; + } + } + break; + case JPEG_SUSPENDED: + break; + } + + return val; +} + + +/* + * Reset state to begin a fresh datastream. + */ + +METHODDEF void +reset_input_controller( j_decompress_ptr cinfo ) { + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + inputctl->pub.consume_input = consume_markers; + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; + /* Reset other modules */ + ( *cinfo->err->reset_error_mgr )( (j_common_ptr) cinfo ); + ( *cinfo->marker->reset_marker_reader )( cinfo ); + /* Reset progression state -- would be cleaner if entropy decoder did this */ + cinfo->coef_bits = NULL; +} + + +/* + * Initialize the input controller module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_input_controller( j_decompress_ptr cinfo ) { + my_inputctl_ptr inputctl; + + /* Create subobject in permanent pool */ + inputctl = (my_inputctl_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF( my_input_controller ) ); + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + /* Initialize method pointers */ + inputctl->pub.consume_input = consume_markers; + inputctl->pub.reset_input_controller = reset_input_controller; + inputctl->pub.start_input_pass = start_input_pass; + inputctl->pub.finish_input_pass = finish_input_pass; + /* Initialize state: can't use reset_input_controller since we don't + * want to try to reset other modules yet. + */ + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; +} diff --git a/src/jpeg-6/jdmainct.c b/src/jpeg-6/jdmainct.c new file mode 100644 index 0000000..00f5fb0 --- /dev/null +++ b/src/jpeg-6/jdmainct.c @@ -0,0 +1,521 @@ +/* + * jdmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for decompression. + * The main buffer lies between the JPEG decompressor proper and the + * post-processor; it holds downsampled data in the JPEG colorspace. + * + * Note that this code is bypassed in raw-data mode, since the application + * supplies the equivalent of the main buffer in that case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * In the current system design, the main buffer need never be a full-image + * buffer; any full-height buffers will be found inside the coefficient or + * postprocessing controllers. Nonetheless, the main controller is not + * trivial. Its responsibility is to provide context rows for upsampling/ + * rescaling, and doing this in an efficient fashion is a bit tricky. + * + * Postprocessor input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. (We require DCT_scaled_size values to be + * chosen such that these numbers are integers. In practice DCT_scaled_size + * values will likely be powers of two, so we actually have the stronger + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + * Upsampling will typically produce max_v_samp_factor pixel rows from each + * row group (times any additional scale factor that the upsampler is + * applying). + * + * The coefficient controller will deliver data to us one iMCU row at a time; + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + * to one row of MCUs when the image is fully interleaved.) Note that the + * number of sample rows varies across components, but the number of row + * groups does not. Some garbage sample rows may be included in the last iMCU + * row at the bottom of the image. + * + * Depending on the vertical scaling algorithm used, the upsampler may need + * access to the sample row(s) above and below its current input row group. + * The upsampler is required to set need_context_rows TRUE at global selection + * time if so. When need_context_rows is FALSE, this controller can simply + * obtain one iMCU row at a time from the coefficient controller and dole it + * out as row groups to the postprocessor. + * + * When need_context_rows is TRUE, this controller guarantees that the buffer + * passed to postprocessing contains at least one row group's worth of samples + * above and below the row group(s) being processed. Note that the context + * rows "above" the first passed row group appear at negative row offsets in + * the passed buffer. At the top and bottom of the image, the required + * context rows are manufactured by duplicating the first or last real sample + * row; this avoids having special cases in the upsampling inner loops. + * + * The amount of context is fixed at one row group just because that's a + * convenient number for this controller to work with. The existing + * upsamplers really only need one sample row of context. An upsampler + * supporting arbitrary output rescaling might wish for more than one row + * group of context when shrinking the image; tough, we don't handle that. + * (This is justified by the assumption that downsizing will be handled mostly + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + * the upsample step needn't be much less than one.) + * + * To provide the desired context, we have to retain the last two row groups + * of one iMCU row while reading in the next iMCU row. (The last row group + * can't be processed until we have another row group for its below-context, + * and so we have to save the next-to-last group too for its above-context.) + * We could do this most simply by copying data around in our buffer, but + * that'd be very slow. We can avoid copying any data by creating a rather + * strange pointer structure. Here's how it works. We allocate a workspace + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + * of row groups per iMCU row). We create two sets of redundant pointers to + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + * pointer lists look like this: + * M+1 M-1 + * master pointer --> 0 master pointer --> 0 + * 1 1 + * ... ... + * M-3 M-3 + * M-2 M + * M-1 M+1 + * M M-2 + * M+1 M-1 + * 0 0 + * We read alternate iMCU rows using each master pointer; thus the last two + * row groups of the previous iMCU row remain un-overwritten in the workspace. + * The pointer lists are set up so that the required context rows appear to + * be adjacent to the proper places when we pass the pointer lists to the + * upsampler. + * + * The above pictures describe the normal state of the pointer lists. + * At top and bottom of the image, we diddle the pointer lists to duplicate + * the first or last sample row as necessary (this is cheaper than copying + * sample rows around). + * + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + * situation each iMCU row provides only one row group so the buffering logic + * must be different (eg, we must read two iMCU rows before we can emit the + * first row group). For now, we simply do not support providing context + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + * be worth providing --- if someone wants a 1/8th-size preview, they probably + * want it quick and dirty, so a context-free upsampler is sufficient. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_main_controller pub; /* public fields */ + + /* Pointer to allocated workspace (M or M+2 row groups). */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + + /* Remaining fields are only used in the context case. */ + + /* These are the master pointers to the funny-order pointer lists. */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + int whichptr; /* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + +/* context_state values: */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + +/* Forward declarations */ +METHODDEF void process_data_simple_main +JPP( ( j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION * out_row_ctr, JDIMENSION out_rows_avail ) ); +METHODDEF void process_data_context_main +JPP( ( j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION * out_row_ctr, JDIMENSION out_rows_avail ) ); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void process_data_crank_post +JPP( ( j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION * out_row_ctr, JDIMENSION out_rows_avail ) ); +#endif + + +LOCAL void +alloc_funny_pointers( j_decompress_ptr cinfo ) { +/* Allocate space for the funny pointer lists. + * This is done only once, not once per pass. + */ +// TTimo don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + int ci, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + /* Get top-level space for component array pointers. + * We alloc both arrays with one call to save a few cycles. + */ + jmain->xbuffer[0] = (JSAMPIMAGE) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * 2 * SIZEOF( JSAMPARRAY ) ); + jmain->xbuffer[1] = jmain->xbuffer[0] + cinfo->num_components; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + rgroup = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + /* Get space for pointer lists --- M+4 row groups in each list. + * We alloc both pointer lists with one call to save a few cycles. + */ + xbuf = (JSAMPARRAY) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + 2 * ( rgroup * ( M + 4 ) ) * SIZEOF( JSAMPROW ) ); + xbuf += rgroup; /* want one row group at negative offsets */ + jmain->xbuffer[0][ci] = xbuf; + xbuf += rgroup * ( M + 4 ); + jmain->xbuffer[1][ci] = xbuf; + } +} + + +LOCAL void +make_funny_pointers( j_decompress_ptr cinfo ) { +/* Create the funny pointer lists discussed in the comments above. + * The actual workspace is already allocated (in main->buffer), + * and the space for the pointer lists is allocated too. + * This routine just fills in the curiously ordered lists. + * This will be repeated at the beginning of each pass. + */ +// TTimo: don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY buf, xbuf0, xbuf1; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + rgroup = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = jmain->xbuffer[0][ci]; + xbuf1 = jmain->xbuffer[1][ci]; + /* First copy the workspace pointers as-is */ + buf = jmain->buffer[ci]; + for ( i = 0; i < rgroup * ( M + 2 ); i++ ) { + xbuf0[i] = xbuf1[i] = buf[i]; + } + /* In the second list, put the last four row groups in swapped order */ + for ( i = 0; i < rgroup * 2; i++ ) { + xbuf1[rgroup * ( M - 2 ) + i] = buf[rgroup * M + i]; + xbuf1[rgroup * M + i] = buf[rgroup * ( M - 2 ) + i]; + } + /* The wraparound pointers at top and bottom will be filled later + * (see set_wraparound_pointers, below). Initially we want the "above" + * pointers to duplicate the first actual data line. This only needs + * to happen in xbuffer[0]. + */ + for ( i = 0; i < rgroup; i++ ) { + xbuf0[i - rgroup] = xbuf0[0]; + } + } +} + + +LOCAL void +set_wraparound_pointers( j_decompress_ptr cinfo ) { +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + * This changes the pointer list state from top-of-image to the normal state. + */ +// TTimo: don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf0, xbuf1; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + rgroup = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = jmain->xbuffer[0][ci]; + xbuf1 = jmain->xbuffer[1][ci]; + for ( i = 0; i < rgroup; i++ ) { + xbuf0[i - rgroup] = xbuf0[rgroup * ( M + 1 ) + i]; + xbuf1[i - rgroup] = xbuf1[rgroup * ( M + 1 ) + i]; + xbuf0[rgroup * ( M + 2 ) + i] = xbuf0[i]; + xbuf1[rgroup * ( M + 2 ) + i] = xbuf1[i]; + } + } +} + + +LOCAL void +set_bottom_pointers( j_decompress_ptr cinfo ) { +/* Change the pointer lists to duplicate the last sample row at the bottom + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + */ + my_main_ptr jmain = (my_main_ptr) cinfo->main; + int ci, i, rgroup, iMCUheight, rows_left; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Count sample rows in one iMCU row and in one row group */ + iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size; + rgroup = iMCUheight / cinfo->min_DCT_scaled_size; + /* Count nondummy sample rows remaining for this component */ + rows_left = (int) ( compptr->downsampled_height % (JDIMENSION) iMCUheight ); + if ( rows_left == 0 ) { + rows_left = iMCUheight; + } + /* Count nondummy row groups. Should get same answer for each component, + * so we need only do it once. + */ + if ( ci == 0 ) { + jmain->rowgroups_avail = (JDIMENSION) ( ( rows_left - 1 ) / rgroup + 1 ); + } + /* Duplicate the last real sample row rgroup*2 times; this pads out the + * last partial rowgroup and ensures at least one full rowgroup of context. + */ + xbuf = jmain->xbuffer[jmain->whichptr][ci]; + for ( i = 0; i < rgroup * 2; i++ ) { + xbuf[rows_left + i] = xbuf[rows_left - 1]; + } + } +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main( j_decompress_ptr cinfo, J_BUF_MODE pass_mode ) { + // TTimo: don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + + switch ( pass_mode ) { + case JBUF_PASS_THRU: + if ( cinfo->upsample->need_context_rows ) { + jmain->pub.process_data = process_data_context_main; + make_funny_pointers( cinfo ); /* Create the xbuffer[] lists */ + jmain->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + jmain->context_state = CTX_PREPARE_FOR_IMCU; + jmain->iMCU_row_ctr = 0; + } else { + /* Simple case with no context needed */ + jmain->pub.process_data = process_data_simple_main; + } + jmain->buffer_full = FALSE; /* Mark buffer empty */ + jmain->rowgroup_ctr = 0; + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_CRANK_DEST: + /* For last pass of 2-pass quantization, just crank the postprocessor */ + jmain->pub.process_data = process_data_crank_post; + break; +#endif + default: + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + break; + } +} + + +/* + * Process some data. + * This handles the simple case where no context is required. + */ + +METHODDEF void +process_data_simple_main( j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) { + // TTimo: don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + JDIMENSION rowgroups_avail; + + /* Read input data if we haven't filled the main buffer yet */ + if ( !jmain->buffer_full ) { + if ( !( *cinfo->coef->decompress_data )( cinfo, jmain->buffer ) ) { + return; /* suspension forced, can do nothing more */ + } + jmain->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + } + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size; + /* Note: at the bottom of the image, we may pass extra garbage row groups + * to the postprocessor. The postprocessor has to check for bottom + * of image anyway (at row resolution), so no point in us doing it too. + */ + + /* Feed the postprocessor */ + ( *cinfo->post->post_process_data )( cinfo, jmain->buffer, + &jmain->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail ); + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + if ( jmain->rowgroup_ctr >= rowgroups_avail ) { + jmain->buffer_full = FALSE; + jmain->rowgroup_ctr = 0; + } +} + + +/* + * Process some data. + * This handles the case where context rows must be provided. + */ + +METHODDEF void +process_data_context_main( j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) { + // TTimo: don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + + /* Read input data if we haven't filled the main buffer yet */ + if ( !jmain->buffer_full ) { + if ( !( *cinfo->coef->decompress_data )( cinfo, + jmain->xbuffer[jmain->whichptr] ) ) { + return; /* suspension forced, can do nothing more */ + } + jmain->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + jmain->iMCU_row_ctr++; /* count rows received */ + } + + /* Postprocessor typically will not swallow all the input data it is handed + * in one call (due to filling the output buffer first). Must be prepared + * to exit and restart. This switch lets us keep track of how far we got. + * Note that each case falls through to the next on successful completion. + */ + switch ( jmain->context_state ) { + case CTX_POSTPONED_ROW: + /* Call postprocessor using previously set pointers for postponed row */ + ( *cinfo->post->post_process_data )( cinfo, jmain->xbuffer[jmain->whichptr], + &jmain->rowgroup_ctr, jmain->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail ); + if ( jmain->rowgroup_ctr < jmain->rowgroups_avail ) { + return; /* Need to suspend */ + } + jmain->context_state = CTX_PREPARE_FOR_IMCU; + if ( *out_row_ctr >= out_rows_avail ) { + return; /* Postprocessor exactly filled output buf */ + } + /*FALLTHROUGH*/ + case CTX_PREPARE_FOR_IMCU: + /* Prepare to process first M-1 row groups of this iMCU row */ + jmain->rowgroup_ctr = 0; + jmain->rowgroups_avail = (JDIMENSION) ( cinfo->min_DCT_scaled_size - 1 ); + /* Check for bottom of image: if so, tweak pointers to "duplicate" + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + */ + if ( jmain->iMCU_row_ctr == cinfo->total_iMCU_rows ) { + set_bottom_pointers( cinfo ); + } + jmain->context_state = CTX_PROCESS_IMCU; + /*FALLTHROUGH*/ + case CTX_PROCESS_IMCU: + /* Call postprocessor using previously set pointers */ + ( *cinfo->post->post_process_data )( cinfo, jmain->xbuffer[jmain->whichptr], + &jmain->rowgroup_ctr, jmain->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail ); + if ( jmain->rowgroup_ctr < jmain->rowgroups_avail ) { + return; /* Need to suspend */ + } + /* After the first iMCU, change wraparound pointers to normal state */ + if ( jmain->iMCU_row_ctr == 1 ) { + set_wraparound_pointers( cinfo ); + } + /* Prepare to load new iMCU row using other xbuffer list */ + jmain->whichptr ^= 1; /* 0=>1 or 1=>0 */ + jmain->buffer_full = FALSE; + /* Still need to process last row group of this iMCU row, */ + /* which is saved at index M+1 of the other xbuffer */ + jmain->rowgroup_ctr = (JDIMENSION) ( cinfo->min_DCT_scaled_size + 1 ); + jmain->rowgroups_avail = (JDIMENSION) ( cinfo->min_DCT_scaled_size + 2 ); + jmain->context_state = CTX_POSTPONED_ROW; + } +} + + +/* + * Process some data. + * Final pass of two-pass quantization: just call the postprocessor. + * Source data will be the postprocessor controller's internal buffer. + */ + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF void +process_data_crank_post( j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) { + ( *cinfo->post->post_process_data )( cinfo, (JSAMPIMAGE) NULL, + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail ); +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_d_main_controller( j_decompress_ptr cinfo, boolean need_full_buffer ) { + // TTimo: don't use main + my_main_ptr jmain; + int ci, rgroup, ngroups; + jpeg_component_info *compptr; + + jmain = (my_main_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_main_controller ) ); + cinfo->main = (struct jpeg_d_main_controller *) jmain; + jmain->pub.start_pass = start_pass_main; + + if ( need_full_buffer ) { /* shouldn't happen */ + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + + /* Allocate the workspace. + * ngroups is the number of row groups we need. + */ + if ( cinfo->upsample->need_context_rows ) { + if ( cinfo->min_DCT_scaled_size < 2 ) { /* unsupported, see comments above */ + ERREXIT( cinfo, JERR_NOTIMPL ); + } + alloc_funny_pointers( cinfo ); /* Alloc space for xbuffer[] lists */ + ngroups = cinfo->min_DCT_scaled_size + 2; + } else { + ngroups = cinfo->min_DCT_scaled_size; + } + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + rgroup = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + jmain->buffer[ci] = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_scaled_size, + (JDIMENSION) ( rgroup * ngroups ) ); + } +} diff --git a/src/jpeg-6/jdmarker.c b/src/jpeg-6/jdmarker.c new file mode 100644 index 0000000..3ed8441 --- /dev/null +++ b/src/jpeg-6/jdmarker.c @@ -0,0 +1,1080 @@ +/* + * jdmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS( cinfo ) \ + struct jpeg_source_mgr * datasrc = ( cinfo )->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC( cinfo ) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD( cinfo ) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL( cinfo,action ) \ + if ( bytes_in_buffer == 0 ) { \ + if ( !( *datasrc->fill_input_buffer )( cinfo ) ) \ + { action; } \ + INPUT_RELOAD( cinfo ); \ + } \ + bytes_in_buffer-- + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE( cinfo,V,action ) \ + MAKESTMT( MAKE_BYTE_AVAIL( cinfo,action ); \ + V = GETJOCTET( *next_input_byte++ ); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES( cinfo,V,action ) \ + MAKESTMT( MAKE_BYTE_AVAIL( cinfo,action ); \ + V = ( (unsigned int) GETJOCTET( *next_input_byte++ ) ) << 8; \ + MAKE_BYTE_AVAIL( cinfo,action ); \ + V += GETJOCTET( *next_input_byte++ ); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters can + * fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments, + * but we use skip_input_data to get past those, and thereby put the problem + * on the source manager's shoulders. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL boolean +get_soi( j_decompress_ptr cinfo ) { +/* Process an SOI marker */ + int i; + + TRACEMS( cinfo, 1, JTRC_SOI ); + + if ( cinfo->marker->saw_SOI ) { + ERREXIT( cinfo, JERR_SOI_DUPLICATE ); + } + + /* Reset all parameters that are defined to be reset by SOI */ + + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->density_unit = 0; /* set default JFIF APP0 values */ + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL boolean +get_sof( j_decompress_ptr cinfo, boolean is_prog, boolean is_arith ) { +/* Process a SOFn marker */ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS( cinfo ); + + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES( cinfo, length, return FALSE ); + + INPUT_BYTE( cinfo, cinfo->data_precision, return FALSE ); + INPUT_2BYTES( cinfo, cinfo->image_height, return FALSE ); + INPUT_2BYTES( cinfo, cinfo->image_width, return FALSE ); + INPUT_BYTE( cinfo, cinfo->num_components, return FALSE ); + + length -= 8; + + TRACEMS4( cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components ); + + if ( cinfo->marker->saw_SOF ) { + ERREXIT( cinfo, JERR_SOF_DUPLICATE ); + } + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if ( cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0 ) { + ERREXIT( cinfo, JERR_EMPTY_IMAGE ); + } + + if ( length != ( cinfo->num_components * 3 ) ) { + ERREXIT( cinfo, JERR_BAD_LENGTH ); + } + + if ( cinfo->comp_info == NULL ) { /* do only once, even if suspend */ + cinfo->comp_info = ( jpeg_component_info * )( *cinfo->mem->alloc_small ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF( jpeg_component_info ) ); + } + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + compptr->component_index = ci; + INPUT_BYTE( cinfo, compptr->component_id, return FALSE ); + INPUT_BYTE( cinfo, c, return FALSE ); + compptr->h_samp_factor = ( c >> 4 ) & 15; + compptr->v_samp_factor = ( c ) & 15; + INPUT_BYTE( cinfo, compptr->quant_tbl_no, return FALSE ); + + TRACEMS4( cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no ); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +get_sos( j_decompress_ptr cinfo ) { +/* Process a SOS marker */ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS( cinfo ); + + if ( !cinfo->marker->saw_SOF ) { + ERREXIT( cinfo, JERR_SOS_NO_SOF ); + } + + INPUT_2BYTES( cinfo, length, return FALSE ); + + INPUT_BYTE( cinfo, n, return FALSE ); /* Number of components */ + + if ( length != ( n * 2 + 6 ) || n < 1 || n > MAX_COMPS_IN_SCAN ) { + ERREXIT( cinfo, JERR_BAD_LENGTH ); + } + + TRACEMS1( cinfo, 1, JTRC_SOS, n ); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for ( i = 0; i < n; i++ ) { + INPUT_BYTE( cinfo, cc, return FALSE ); + INPUT_BYTE( cinfo, c, return FALSE ); + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( cc == compptr->component_id ) { + goto id_found; + } + } + + ERREXIT1( cinfo, JERR_BAD_COMPONENT_ID, cc ); + +id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = ( c >> 4 ) & 15; + compptr->ac_tbl_no = ( c ) & 15; + + TRACEMS3( cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no ); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE( cinfo, c, return FALSE ); + cinfo->Ss = c; + INPUT_BYTE( cinfo, c, return FALSE ); + cinfo->Se = c; + INPUT_BYTE( cinfo, c, return FALSE ); + cinfo->Ah = ( c >> 4 ) & 15; + cinfo->Al = ( c ) & 15; + + TRACEMS4( cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al ); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another SOS marker */ + cinfo->input_scan_number++; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +METHODDEF boolean +get_app0( j_decompress_ptr cinfo ) { +/* Process an APP0 marker */ +#define JFIF_LEN 14 + INT32 length; + UINT8 b[JFIF_LEN]; + int buffp; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + /* See if a JFIF APP0 marker is present */ + + if ( length >= JFIF_LEN ) { + for ( buffp = 0; buffp < JFIF_LEN; buffp++ ) + INPUT_BYTE( cinfo, b[buffp], return FALSE ); + length -= JFIF_LEN; + + if ( b[0] == 0x4A && b[1] == 0x46 && b[2] == 0x49 && b[3] == 0x46 && b[4] == 0 ) { + /* Found JFIF APP0 marker: check version */ + /* Major version must be 1, anything else signals an incompatible change. + * We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec. + * Minor version should be 0..2, but process anyway if newer. + */ + if ( b[5] != 1 ) { + WARNMS2( cinfo, JWRN_JFIF_MAJOR, b[5], b[6] ); + } else if ( b[6] > 2 ) { + TRACEMS2( cinfo, 1, JTRC_JFIF_MINOR, b[5], b[6] ); + } + /* Save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->density_unit = b[7]; + cinfo->X_density = ( b[8] << 8 ) + b[9]; + cinfo->Y_density = ( b[10] << 8 ) + b[11]; + TRACEMS3( cinfo, 1, JTRC_JFIF, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit ); + if ( b[12] | b[13] ) { + TRACEMS2( cinfo, 1, JTRC_JFIF_THUMBNAIL, b[12], b[13] ); + } + if ( length != ( (INT32) b[12] * (INT32) b[13] * (INT32) 3 ) ) { + TRACEMS1( cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) length ); + } + } else { + /* Start of APP0 does not match "JFIF" */ + TRACEMS1( cinfo, 1, JTRC_APP0, (int) length + JFIF_LEN ); + } + } else { + /* Too short to be JFIF marker */ + TRACEMS1( cinfo, 1, JTRC_APP0, (int) length ); + } + + INPUT_SYNC( cinfo ); + if ( length > 0 ) { /* skip any remaining data -- could be lots */ + ( *cinfo->src->skip_input_data )( cinfo, (long) length ); + } + + return TRUE; +} + + +METHODDEF boolean +get_app14( j_decompress_ptr cinfo ) { +/* Process an APP14 marker */ +#define ADOBE_LEN 12 + INT32 length; + UINT8 b[ADOBE_LEN]; + int buffp; + unsigned int version, flags0, flags1, transform; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + /* See if an Adobe APP14 marker is present */ + + if ( length >= ADOBE_LEN ) { + for ( buffp = 0; buffp < ADOBE_LEN; buffp++ ) + INPUT_BYTE( cinfo, b[buffp], return FALSE ); + length -= ADOBE_LEN; + + if ( b[0] == 0x41 && b[1] == 0x64 && b[2] == 0x6F && b[3] == 0x62 && b[4] == 0x65 ) { + /* Found Adobe APP14 marker */ + version = ( b[5] << 8 ) + b[6]; + flags0 = ( b[7] << 8 ) + b[8]; + flags1 = ( b[9] << 8 ) + b[10]; + transform = b[11]; + TRACEMS4( cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform ); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe" */ + TRACEMS1( cinfo, 1, JTRC_APP14, (int) length + ADOBE_LEN ); + } + } else { + /* Too short to be Adobe marker */ + TRACEMS1( cinfo, 1, JTRC_APP14, (int) length ); + } + + INPUT_SYNC( cinfo ); + if ( length > 0 ) { /* skip any remaining data -- could be lots */ + ( *cinfo->src->skip_input_data )( cinfo, (long) length ); + } + + return TRUE; +} + + +LOCAL boolean +get_dac( j_decompress_ptr cinfo ) { +/* Process a DAC marker */ + INT32 length; + int index, val; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + while ( length > 0 ) { + INPUT_BYTE( cinfo, index, return FALSE ); + INPUT_BYTE( cinfo, val, return FALSE ); + + length -= 2; + + TRACEMS2( cinfo, 1, JTRC_DAC, index, val ); + + if ( index < 0 || index >= ( 2 * NUM_ARITH_TBLS ) ) { + ERREXIT1( cinfo, JERR_DAC_INDEX, index ); + } + + if ( index >= NUM_ARITH_TBLS ) { /* define AC table */ + cinfo->arith_ac_K[index - NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) ( val & 0x0F ); + cinfo->arith_dc_U[index] = (UINT8) ( val >> 4 ); + if ( cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index] ) { + ERREXIT1( cinfo, JERR_DAC_VALUE, val ); + } + } + } + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +get_dht( j_decompress_ptr cinfo ) { +/* Process a DHT marker */ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL **htblptr; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + while ( length > 0 ) { + INPUT_BYTE( cinfo, index, return FALSE ); + + TRACEMS1( cinfo, 1, JTRC_DHT, index ); + + bits[0] = 0; + count = 0; + for ( i = 1; i <= 16; i++ ) { + INPUT_BYTE( cinfo, bits[i], return FALSE ); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8( cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8] ); + TRACEMS8( cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16] ); + + if ( count > 256 || ( (INT32) count ) > length ) { + ERREXIT( cinfo, JERR_DHT_COUNTS ); + } + + for ( i = 0; i < count; i++ ) + INPUT_BYTE( cinfo, huffval[i], return FALSE ); + + length -= count; + + if ( index & 0x10 ) { /* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if ( index < 0 || index >= NUM_HUFF_TBLS ) { + ERREXIT1( cinfo, JERR_DHT_INDEX, index ); + } + + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + + MEMCOPY( ( *htblptr )->bits, bits, SIZEOF( ( *htblptr )->bits ) ); + MEMCOPY( ( *htblptr )->huffval, huffval, SIZEOF( ( *htblptr )->huffval ) ); + } + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +get_dqt( j_decompress_ptr cinfo ) { +/* Process a DQT marker */ + INT32 length; + int n, i, prec; + unsigned int tmp; + JQUANT_TBL *quant_ptr; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + while ( length > 0 ) { + INPUT_BYTE( cinfo, n, return FALSE ); + prec = n >> 4; + n &= 0x0F; + + TRACEMS2( cinfo, 1, JTRC_DQT, n, prec ); + + if ( n >= NUM_QUANT_TBLS ) { + ERREXIT1( cinfo, JERR_DQT_INDEX, n ); + } + + if ( cinfo->quant_tbl_ptrs[n] == NULL ) { + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table( (j_common_ptr) cinfo ); + } + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + for ( i = 0; i < DCTSIZE2; i++ ) { + if ( prec ) { + INPUT_2BYTES( cinfo, tmp, return FALSE ); + } else { + INPUT_BYTE( cinfo, tmp, return FALSE ); + } + quant_ptr->quantval[i] = (UINT16) tmp; + } + + for ( i = 0; i < DCTSIZE2; i += 8 ) { + TRACEMS8( cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i ], quant_ptr->quantval[i + 1], + quant_ptr->quantval[i + 2], quant_ptr->quantval[i + 3], + quant_ptr->quantval[i + 4], quant_ptr->quantval[i + 5], + quant_ptr->quantval[i + 6], quant_ptr->quantval[i + 7] ); + } + + length -= DCTSIZE2 + 1; + if ( prec ) { + length -= DCTSIZE2; + } + } + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +get_dri( j_decompress_ptr cinfo ) { +/* Process a DRI marker */ + INT32 length; + unsigned int tmp; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + + if ( length != 4 ) { + ERREXIT( cinfo, JERR_BAD_LENGTH ); + } + + INPUT_2BYTES( cinfo, tmp, return FALSE ); + + TRACEMS1( cinfo, 1, JTRC_DRI, tmp ); + + cinfo->restart_interval = tmp; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +METHODDEF boolean +skip_variable( j_decompress_ptr cinfo ) { +/* Skip over an unknown or uninteresting variable-length marker */ + INT32 length; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + + TRACEMS2( cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length ); + + INPUT_SYNC( cinfo ); /* do before skip_input_data */ + ( *cinfo->src->skip_input_data )( cinfo, (long) length - 2L ); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL boolean +next_marker( j_decompress_ptr cinfo ) { + int c; + INPUT_VARS( cinfo ); + + for (;; ) { + INPUT_BYTE( cinfo, c, return FALSE ); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while ( c != 0xFF ) { + cinfo->marker->discarded_bytes++; + INPUT_SYNC( cinfo ); + INPUT_BYTE( cinfo, c, return FALSE ); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE( cinfo, c, return FALSE ); + } while ( c == 0xFF ); + if ( c != 0 ) { + break; /* found a valid marker, exit loop */ + } + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC( cinfo ); + } + + if ( cinfo->marker->discarded_bytes != 0 ) { + WARNMS2( cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c ); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +first_marker( j_decompress_ptr cinfo ) { +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ + int c, c2; + INPUT_VARS( cinfo ); + + INPUT_BYTE( cinfo, c, return FALSE ); + INPUT_BYTE( cinfo, c2, return FALSE ); + if ( c != 0xFF || c2 != (int) M_SOI ) { + ERREXIT2( cinfo, JERR_NO_SOI, c, c2 ); + } + + cinfo->unread_marker = c2; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + +METHODDEF int +read_markers( j_decompress_ptr cinfo ) { + /* Outer loop repeats once for each marker. */ + for (;; ) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if ( cinfo->unread_marker == 0 ) { + if ( !cinfo->marker->saw_SOI ) { + if ( !first_marker( cinfo ) ) { + return JPEG_SUSPENDED; + } + } else { + if ( !next_marker( cinfo ) ) { + return JPEG_SUSPENDED; + } + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch ( cinfo->unread_marker ) { + case M_SOI: + if ( !get_soi( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ + if ( !get_sof( cinfo, FALSE, FALSE ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_SOF2: /* Progressive, Huffman */ + if ( !get_sof( cinfo, TRUE, FALSE ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_SOF9: /* Extended sequential, arithmetic */ + if ( !get_sof( cinfo, FALSE, TRUE ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_SOF10: /* Progressive, arithmetic */ + if ( !get_sof( cinfo, TRUE, TRUE ) ) { + return JPEG_SUSPENDED; + } + break; + + /* Currently unsupported SOFn types */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + ERREXIT1( cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker ); + break; + + case M_SOS: + if ( !get_sos( cinfo ) ) { + return JPEG_SUSPENDED; + } + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS( cinfo, 1, JTRC_EOI ); + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if ( !get_dac( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_DHT: + if ( !get_dht( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_DQT: + if ( !get_dqt( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_DRI: + if ( !get_dri( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if ( !( *cinfo->marker->process_APPn[cinfo->unread_marker - (int) M_APP0] )( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_COM: + if ( !( *cinfo->marker->process_COM )( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_RST0: /* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1( cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker ); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if ( !skip_variable( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1( cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker ); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF boolean +read_restart_marker( j_decompress_ptr cinfo ) { + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if ( cinfo->unread_marker == 0 ) { + if ( !next_marker( cinfo ) ) { + return FALSE; + } + } + + if ( cinfo->unread_marker == + ( (int) M_RST0 + cinfo->marker->next_restart_num ) ) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1( cinfo, 2, JTRC_RST, cinfo->marker->next_restart_num ); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if ( !( *cinfo->src->resync_to_restart )( cinfo, + cinfo->marker->next_restart_num ) ) { + return FALSE; + } + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = ( cinfo->marker->next_restart_num + 1 ) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL boolean +jpeg_resync_to_restart( j_decompress_ptr cinfo, int desired ) { + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2( cinfo, JWRN_MUST_RESYNC, marker, desired ); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;; ) { + if ( marker < (int) M_SOF0 ) { + action = 2; /* invalid marker */ + } else if ( marker < (int) M_RST0 || marker > (int) M_RST7 ) { + action = 3; /* valid non-restart marker */ + } else { + if ( marker == ( (int) M_RST0 + ( ( desired + 1 ) & 7 ) ) || + marker == ( (int) M_RST0 + ( ( desired + 2 ) & 7 ) ) ) { + action = 3; /* one of the next two expected restarts */ + } else if ( marker == ( (int) M_RST0 + ( ( desired - 1 ) & 7 ) ) || + marker == ( (int) M_RST0 + ( ( desired - 2 ) & 7 ) ) ) { + action = 2; /* a prior restart, so advance */ + } else { + action = 1; /* desired restart or too far away */ + } + } + TRACEMS2( cinfo, 4, JTRC_RECOVERY_ACTION, marker, action ); + switch ( action ) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if ( !next_marker( cinfo ) ) { + return FALSE; + } + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF void +reset_marker_reader( j_decompress_ptr cinfo ) { + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + cinfo->marker->saw_SOI = FALSE; /* set internal state too */ + cinfo->marker->saw_SOF = FALSE; + cinfo->marker->discarded_bytes = 0; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_marker_reader( j_decompress_ptr cinfo ) { + int i; + + /* Create subobject in permanent pool */ + cinfo->marker = (struct jpeg_marker_reader *) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF( struct jpeg_marker_reader ) ); + /* Initialize method pointers */ + cinfo->marker->reset_marker_reader = reset_marker_reader; + cinfo->marker->read_markers = read_markers; + cinfo->marker->read_restart_marker = read_restart_marker; + cinfo->marker->process_COM = skip_variable; + for ( i = 0; i < 16; i++ ) + cinfo->marker->process_APPn[i] = skip_variable; + cinfo->marker->process_APPn[0] = get_app0; + cinfo->marker->process_APPn[14] = get_app14; + /* Reset marker processing state */ + reset_marker_reader( cinfo ); +} diff --git a/src/jpeg-6/jdmaster.c b/src/jpeg-6/jdmaster.c new file mode 100644 index 0000000..bfea134 --- /dev/null +++ b/src/jpeg-6/jdmaster.c @@ -0,0 +1,564 @@ +/* + * jdmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub; /* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL boolean +use_merged_upsample( j_decompress_ptr cinfo ) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if ( cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling ) { + return FALSE; + } + /* jdmerge.c only supports YCC=>RGB color conversion */ + if ( cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 || + cinfo->out_color_space != JCS_RGB || + cinfo->out_color_components != RGB_PIXELSIZE ) { + return FALSE; + } + /* and it only handles 2h1v or 2h2v sampling ratios */ + if ( cinfo->comp_info[0].h_samp_factor != 2 || + cinfo->comp_info[1].h_samp_factor != 1 || + cinfo->comp_info[2].h_samp_factor != 1 || + cinfo->comp_info[0].v_samp_factor > 2 || + cinfo->comp_info[1].v_samp_factor != 1 || + cinfo->comp_info[2].v_samp_factor != 1 ) { + return FALSE; + } + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if ( cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size ) { + return FALSE; + } + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ +#else + return FALSE; +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL void +jpeg_calc_output_dimensions( j_decompress_ptr cinfo ) { +/* Do computations that are needed before master selection phase */ +#if 0 // JDC: commented out to remove warning + int ci; + jpeg_component_info *compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if ( cinfo->global_state != DSTATE_READY ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + +#ifdef IDCT_SCALING_SUPPORTED + + /* Compute actual output image dimensions and DCT scaling choices. */ + if ( cinfo->scale_num * 8 <= cinfo->scale_denom ) { + /* Provide 1/8 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, 8L ); + cinfo->output_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, 8L ); + cinfo->min_DCT_scaled_size = 1; + } else if ( cinfo->scale_num * 4 <= cinfo->scale_denom ) { + /* Provide 1/4 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, 4L ); + cinfo->output_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, 4L ); + cinfo->min_DCT_scaled_size = 2; + } else if ( cinfo->scale_num * 2 <= cinfo->scale_denom ) { + /* Provide 1/2 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, 2L ); + cinfo->output_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, 2L ); + cinfo->min_DCT_scaled_size = 4; + } else { + /* Provide 1/1 scaling */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + cinfo->min_DCT_scaled_size = DCTSIZE; + } + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code assumes that the supported DCT scalings are powers of 2. + */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + int ssize = cinfo->min_DCT_scaled_size; + while ( ssize < DCTSIZE && + ( compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size ) && + ( compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size ) ) { + ssize = ssize * 2; + } + compptr->DCT_scaled_size = ssize; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * + (long) ( compptr->h_samp_factor * compptr->DCT_scaled_size ), + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * + (long) ( compptr->v_samp_factor * compptr->DCT_scaled_size ), + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch ( cinfo->out_color_space ) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = ( cinfo->quantize_colors ? 1 : + cinfo->out_color_components ); + + /* See if upsampler will want to emit more than one row at a time */ + if ( use_merged_upsample( cinfo ) ) { + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + } else { + cinfo->rec_outbuf_height = 1; + } +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL void +prepare_range_limit_table( j_decompress_ptr cinfo ) { +/* Allocate and fill in the sample_range_limit table */ + JSAMPLE * table; + int i; + + table = ( JSAMPLE * ) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( 5 * ( MAXJSAMPLE + 1 ) + CENTERJSAMPLE ) * SIZEOF( JSAMPLE ) ); + table += ( MAXJSAMPLE + 1 ); /* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO( table - ( MAXJSAMPLE + 1 ), ( MAXJSAMPLE + 1 ) * SIZEOF( JSAMPLE ) ); + /* Main part of "simple" table: limit[x] = x */ + for ( i = 0; i <= MAXJSAMPLE; i++ ) + table[i] = (JSAMPLE) i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for ( i = CENTERJSAMPLE; i < 2 * ( MAXJSAMPLE + 1 ); i++ ) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + MEMZERO( table + ( 2 * ( MAXJSAMPLE + 1 ) ), + ( 2 * ( MAXJSAMPLE + 1 ) - CENTERJSAMPLE ) * SIZEOF( JSAMPLE ) ); + MEMCOPY( table + ( 4 * ( MAXJSAMPLE + 1 ) - CENTERJSAMPLE ), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF( JSAMPLE ) ); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL void +master_selection( j_decompress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions( cinfo ); + prepare_range_limit_table( cinfo ); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ( (long) jd_samplesperrow != samplesperrow ) { + ERREXIT( cinfo, JERR_WIDTH_OVERFLOW ); + } + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample( cinfo ); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if ( !cinfo->quantize_colors || !cinfo->buffered_image ) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if ( cinfo->quantize_colors ) { + if ( cinfo->raw_data_out ) { + ERREXIT( cinfo, JERR_NOTIMPL ); + } + /* 2-pass quantizer only works in 3-component color space. */ + if ( cinfo->out_color_components != 3 ) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if ( cinfo->colormap != NULL ) { + cinfo->enable_external_quant = TRUE; + } else if ( cinfo->two_pass_quantize ) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if ( cinfo->enable_1pass_quant ) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer( cinfo ); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if ( cinfo->enable_2pass_quant || cinfo->enable_external_quant ) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer( cinfo ); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if ( !cinfo->raw_data_out ) { + if ( master->using_merged_upsample ) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler( cinfo ); /* does color conversion too */ +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_color_deconverter( cinfo ); + jinit_upsampler( cinfo ); + } + jinit_d_post_controller( cinfo, cinfo->enable_2pass_quant ); + } + /* Inverse DCT */ + jinit_inverse_dct( cinfo ); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if ( cinfo->arith_code ) { + ERREXIT( cinfo, JERR_ARITH_NOTIMPL ); + } else { + if ( cinfo->progressive_mode ) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_huff_decoder( cinfo ); + } + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller( cinfo, use_c_buffer ); + + if ( !cinfo->raw_data_out ) { + jinit_d_main_controller( cinfo, FALSE /* never need full buffer here */ ); + } + + /* We can now tell the memory manager to allocate virtual arrays. */ + ( *cinfo->mem->realize_virt_arrays )( (j_common_ptr) cinfo ); + + /* Initialize input side of decompressor to consume first scan. */ + ( *cinfo->inputctl->start_input_pass )( cinfo ); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if ( cinfo->progress != NULL && !cinfo->buffered_image && + cinfo->inputctl->has_multiple_scans ) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if ( cinfo->progressive_mode ) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = ( cinfo->enable_2pass_quant ? 3 : 2 ); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapi.c will crank the pass to completion.) + */ + +METHODDEF void +prepare_for_output_pass( j_decompress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + if ( master->pub.is_dummy_pass ) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + ( *cinfo->cquantize->start_pass )( cinfo, FALSE ); + ( *cinfo->post->start_pass )( cinfo, JBUF_CRANK_DEST ); + ( *cinfo->main->start_pass )( cinfo, JBUF_CRANK_DEST ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if ( cinfo->quantize_colors && cinfo->colormap == NULL ) { + /* Select new quantization method */ + if ( cinfo->two_pass_quantize && cinfo->enable_2pass_quant ) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if ( cinfo->enable_1pass_quant ) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT( cinfo, JERR_MODE_CHANGE ); + } + } + ( *cinfo->idct->start_pass )( cinfo ); + ( *cinfo->coef->start_output_pass )( cinfo ); + if ( !cinfo->raw_data_out ) { + if ( !master->using_merged_upsample ) { + ( *cinfo->cconvert->start_pass )( cinfo ); + } + ( *cinfo->upsample->start_pass )( cinfo ); + if ( cinfo->quantize_colors ) { + ( *cinfo->cquantize->start_pass )( cinfo, master->pub.is_dummy_pass ); + } + ( *cinfo->post->start_pass )( cinfo, + ( master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU ) ); + ( *cinfo->main->start_pass )( cinfo, JBUF_PASS_THRU ); + } + } + + /* Set up progress monitor's pass info if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + ( master->pub.is_dummy_pass ? 2 : 1 ); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if ( cinfo->buffered_image && !cinfo->inputctl->eoi_reached ) { + cinfo->progress->total_passes += ( cinfo->enable_2pass_quant ? 2 : 1 ); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF void +finish_output_pass( j_decompress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + if ( cinfo->quantize_colors ) { + ( *cinfo->cquantize->finish_pass )( cinfo ); + } + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL void +jpeg_new_colormap( j_decompress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if ( cinfo->global_state != DSTATE_BUFIMAGE ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + if ( cinfo->quantize_colors && cinfo->enable_external_quant && + cinfo->colormap != NULL ) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + ( *cinfo->cquantize->new_color_map )( cinfo ); + master->pub.is_dummy_pass = FALSE; /* just in case */ + } else { + ERREXIT( cinfo, JERR_MODE_CHANGE ); + } +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL void +jinit_master_decompress( j_decompress_ptr cinfo ) { + my_master_ptr master; + + master = (my_master_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_decomp_master ) ); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection( cinfo ); +} diff --git a/src/jpeg-6/jdpostct.c b/src/jpeg-6/jdpostct.c new file mode 100644 index 0000000..b90445c --- /dev/null +++ b/src/jpeg-6/jdpostct.c @@ -0,0 +1,290 @@ +/* + * jdpostct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the decompression postprocessing controller. + * This controller manages the upsampling, color conversion, and color + * quantization/reduction steps; specifically, it controls the buffering + * between upsample/color conversion and color quantization/reduction. + * + * If no color quantization/reduction is required, then this module has no + * work to do, and it just hands off to the upsample/color conversion code. + * An integrated upsample/convert/quantize process would replace this module + * entirely. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_post_controller pub; /* public fields */ + + /* Color quantization source buffer: this holds output data from + * the upsample/color conversion step to be passed to the quantizer. + * For two-pass color quantization, we need a full-image buffer; + * for one-pass operation, a strip buffer is sufficient. + */ + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ + /* for two-pass mode only: */ + JDIMENSION starting_row; /* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ +} my_post_controller; + +typedef my_post_controller * my_post_ptr; + + +/* Forward declarations */ +METHODDEF void post_process_1pass +JPP( ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) ); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void post_process_prepass +JPP( ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) ); +METHODDEF void post_process_2pass +JPP( ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) ); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_dpost( j_decompress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_post_ptr post = (my_post_ptr) cinfo->post; + + switch ( pass_mode ) { + case JBUF_PASS_THRU: + if ( cinfo->quantize_colors ) { + /* Single-pass processing with color quantization. */ + post->pub.post_process_data = post_process_1pass; + /* We could be doing buffered-image output before starting a 2-pass + * color quantization; in that case, jinit_d_post_controller did not + * allocate a strip buffer. Use the virtual-array buffer as workspace. + */ + if ( post->buffer == NULL ) { + post->buffer = ( *cinfo->mem->access_virt_sarray ) + ( (j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE ); + } + } else { + /* For single-pass processing without color quantization, + * I have no work to do; just call the upsampler directly. + */ + post->pub.post_process_data = cinfo->upsample->upsample; + } + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_SAVE_AND_PASS: + /* First pass of 2-pass quantization */ + if ( post->whole_image == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + post->pub.post_process_data = post_process_prepass; + break; + case JBUF_CRANK_DEST: + /* Second pass of 2-pass quantization */ + if ( post->whole_image == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + post->pub.post_process_data = post_process_2pass; + break; +#endif /* QUANT_2PASS_SUPPORTED */ + default: + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + break; + } + post->starting_row = post->next_row = 0; +} + + +/* + * Process some data in the one-pass (strip buffer) case. + * This is used for color precision reduction as well as one-pass quantization. + */ + +METHODDEF void +post_process_1pass( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) { + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Fill the buffer, but not more than what we can dump out in one go. */ + /* Note we rely on the upsampler to detect bottom of image. */ + max_rows = out_rows_avail - *out_row_ctr; + if ( max_rows > post->strip_height ) { + max_rows = post->strip_height; + } + num_rows = 0; + ( *cinfo->upsample->upsample )( cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows ); + /* Quantize and emit data. */ + ( *cinfo->cquantize->color_quantize )( cinfo, + post->buffer, output_buf + *out_row_ctr, (int) num_rows ); + *out_row_ctr += num_rows; +} + + +#ifdef QUANT_2PASS_SUPPORTED + +/* + * Process some data in the first pass of 2-pass quantization. + */ + +METHODDEF void +post_process_prepass( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) { + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION old_next_row, num_rows; + + /* Reposition virtual buffer if at start of strip. */ + if ( post->next_row == 0 ) { + post->buffer = ( *cinfo->mem->access_virt_sarray ) + ( (j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE ); + } + + /* Upsample some data (up to a strip height's worth). */ + old_next_row = post->next_row; + ( *cinfo->upsample->upsample )( cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height ); + + /* Allow quantizer to scan new data. No data is emitted, */ + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + if ( post->next_row > old_next_row ) { + num_rows = post->next_row - old_next_row; + ( *cinfo->cquantize->color_quantize )( cinfo, post->buffer + old_next_row, + (JSAMPARRAY) NULL, (int) num_rows ); + *out_row_ctr += num_rows; + } + + /* Advance if we filled the strip. */ + if ( post->next_row >= post->strip_height ) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + + +/* + * Process some data in the second pass of 2-pass quantization. + */ + +METHODDEF void +post_process_2pass( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) { + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Reposition virtual buffer if at start of strip. */ + if ( post->next_row == 0 ) { + post->buffer = ( *cinfo->mem->access_virt_sarray ) + ( (j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE ); + } + + /* Determine number of rows to emit. */ + num_rows = post->strip_height - post->next_row; /* available in strip */ + max_rows = out_rows_avail - *out_row_ctr; /* available in output area */ + if ( num_rows > max_rows ) { + num_rows = max_rows; + } + /* We have to check bottom of image here, can't depend on upsampler. */ + max_rows = cinfo->output_height - post->starting_row; + if ( num_rows > max_rows ) { + num_rows = max_rows; + } + + /* Quantize and emit data. */ + ( *cinfo->cquantize->color_quantize )( cinfo, + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows ); + *out_row_ctr += num_rows; + + /* Advance if we filled the strip. */ + post->next_row += num_rows; + if ( post->next_row >= post->strip_height ) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize postprocessing controller. + */ + +GLOBAL void +jinit_d_post_controller( j_decompress_ptr cinfo, boolean need_full_buffer ) { + my_post_ptr post; + + post = (my_post_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_post_controller ) ); + cinfo->post = (struct jpeg_d_post_controller *) post; + post->pub.start_pass = start_pass_dpost; + post->whole_image = NULL; /* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ + + /* Create the quantization buffer, if needed */ + if ( cinfo->quantize_colors ) { + /* The buffer strip height is max_v_samp_factor, which is typically + * an efficient number of rows for upsampling to return. + * (In the presence of output rescaling, we might want to be smarter?) + */ + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + if ( need_full_buffer ) { + /* Two-pass color quantization: need full-image storage. */ + /* We round up the number of rows to a multiple of the strip height. */ +#ifdef QUANT_2PASS_SUPPORTED + post->whole_image = ( *cinfo->mem->request_virt_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up( (long) cinfo->output_height, + (long) post->strip_height ), + post->strip_height ); +#else + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + /* One-pass color quantization: just make a strip buffer. */ + post->buffer = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height ); + } + } +} diff --git a/src/jpeg-6/jdsample.c b/src/jpeg-6/jdsample.c new file mode 100644 index 0000000..28092f2 --- /dev/null +++ b/src/jpeg-6/jdsample.c @@ -0,0 +1,476 @@ +/* + * jdsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains upsampling routines. + * + * Upsampling input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. Upsampling will normally produce + * max_v_samp_factor pixel rows from each row group (but this could vary + * if the upsampler is applying a scale factor of its own). + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to upsample a single component */ +typedef JMETHOD ( void, upsample1_ptr, + ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) ); + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Color conversion buffer. When using separate upsampling and color + * conversion steps, this buffer holds one upsampled row group until it + * has been color converted and output. + * Note: we do not allocate any storage for component(s) which are full-size, + * ie do not need rescaling. The corresponding entry of color_buf[] is + * simply set to point to the input data array, thereby avoiding copying. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + /* Per-component upsampling method pointers */ + upsample1_ptr methods[MAX_COMPONENTS]; + + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + /* Height of an input row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_expand need not + * recompute them each time. They are unused for other upsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF void +start_pass_upsample( j_decompress_ptr cinfo ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the conversion buffer empty */ + upsample->next_row_out = cinfo->max_v_samp_factor; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * In this version we upsample each component independently. + * We upsample one row group into the conversion buffer, then apply + * color conversion a row at a time. + */ + +METHODDEF void +sep_upsample( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int ci; + jpeg_component_info * compptr; + JDIMENSION num_rows; + + /* Fill the conversion buffer, if it's empty */ + if ( upsample->next_row_out >= cinfo->max_v_samp_factor ) { + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Invoke per-component upsample method. Notice we pass a POINTER + * to color_buf[ci], so that fullsize_upsample can change it. + */ + ( *upsample->methods[ci] )( cinfo, compptr, + input_buf[ci] + ( *in_row_group_ctr * upsample->rowgroup_height[ci] ), + upsample->color_buf + ci ); + } + upsample->next_row_out = 0; + } + + /* Color-convert and emit rows */ + + /* How many we have in the buffer: */ + num_rows = (JDIMENSION) ( cinfo->max_v_samp_factor - upsample->next_row_out ); + /* Not more than the distance to the end of the image. Need this test + * in case the image height is not a multiple of max_v_samp_factor: + */ + if ( num_rows > upsample->rows_to_go ) { + num_rows = upsample->rows_to_go; + } + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if ( num_rows > out_rows_avail ) { + num_rows = out_rows_avail; + } + + ( *cinfo->cconvert->color_convert )( cinfo, upsample->color_buf, + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows ); + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + upsample->next_row_out += num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if ( upsample->next_row_out >= cinfo->max_v_samp_factor ) { + ( *in_row_group_ctr )++; + } +} + + +/* + * These are the routines invoked by sep_upsample to upsample pixel values + * of a single component. One row group is processed per call. + */ + + +/* + * For full-size components, we just make color_buf[ci] point at the + * input buffer, and thus avoid copying any data. Note that this is + * safe only because sep_upsample doesn't declare the input row group + * "consumed" until we are done color converting and emitting it. + */ + +METHODDEF void +fullsize_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + *output_data_ptr = input_data; +} + + +/* + * This is a no-op version used for "uninteresting" components. + * These components will not be referenced by color conversion. + */ + +METHODDEF void +noop_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + *output_data_ptr = NULL; /* safety check */ +} + + +/* + * This version handles any integral sampling ratios. + * This is not used for typical JPEG files, so it need not be fast. + * Nor, for that matter, is it particularly accurate: the algorithm is + * simple replication of the input pixel onto the corresponding output + * pixels. The hi-falutin sampling literature refers to this as a + * "box filter". A box filter tends to introduce visible artifacts, + * so if you are actually going to use 3:1 or 4:1 sampling ratios + * you would be well advised to improve this code. + */ + +METHODDEF void +int_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + register int h; + JSAMPROW outend; + int h_expand, v_expand; + int inrow, outrow; + + h_expand = upsample->h_expand[compptr->component_index]; + v_expand = upsample->v_expand[compptr->component_index]; + + inrow = outrow = 0; + while ( outrow < cinfo->max_v_samp_factor ) { + /* Generate one output row with proper horizontal expansion */ + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while ( outptr < outend ) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + for ( h = h_expand; h > 0; h-- ) { + *outptr++ = invalue; + } + } + /* Generate any additional output rows by duplicating the first one */ + if ( v_expand > 1 ) { + jcopy_sample_rows( output_data, outrow, output_data, outrow + 1, + v_expand - 1, cinfo->output_width ); + } + inrow++; + outrow += v_expand; + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v1_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow; + + for ( inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++ ) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + outend = outptr + cinfo->output_width; + while ( outptr < outend ) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v2_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow, outrow; + + inrow = outrow = 0; + while ( outrow < cinfo->max_v_samp_factor ) { + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while ( outptr < outend ) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + jcopy_sample_rows( output_data, outrow, output_data, outrow + 1, + 1, cinfo->output_width ); + inrow++; + outrow += 2; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. + * + * The upsampling algorithm is linear interpolation between pixel centers, + * also known as a "triangle filter". This is a good compromise between + * speed and visual quality. The centers of the output pixels are 1/4 and 3/4 + * of the way between input pixel centers. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_fancy_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register int invalue; + register JDIMENSION colctr; + int inrow; + + for ( inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++ ) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + /* Special case for first column */ + invalue = GETJSAMPLE( *inptr++ ); + *outptr++ = (JSAMPLE) invalue; + *outptr++ = (JSAMPLE) ( ( invalue * 3 + GETJSAMPLE( *inptr ) + 2 ) >> 2 ); + + for ( colctr = compptr->downsampled_width - 2; colctr > 0; colctr-- ) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ + invalue = GETJSAMPLE( *inptr++ ) * 3; + *outptr++ = (JSAMPLE) ( ( invalue + GETJSAMPLE( inptr[-2] ) + 1 ) >> 2 ); + *outptr++ = (JSAMPLE) ( ( invalue + GETJSAMPLE( *inptr ) + 2 ) >> 2 ); + } + + /* Special case for last column */ + invalue = GETJSAMPLE( *inptr ); + *outptr++ = (JSAMPLE) ( ( invalue * 3 + GETJSAMPLE( inptr[-1] ) + 1 ) >> 2 ); + *outptr++ = (JSAMPLE) invalue; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. + * Again a triangle filter; see comments for h2v1 case, above. + * + * It is OK for us to reference the adjacent input rows because we demanded + * context from the main buffer controller (see initialization code). + */ + +METHODDEF void +h2v2_fancy_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr0, inptr1, outptr; +#if BITS_IN_JSAMPLE == 8 + register int thiscolsum, lastcolsum, nextcolsum; +#else + register INT32 thiscolsum, lastcolsum, nextcolsum; +#endif + register JDIMENSION colctr; + int inrow, outrow, v; + + inrow = outrow = 0; + while ( outrow < cinfo->max_v_samp_factor ) { + for ( v = 0; v < 2; v++ ) { + /* inptr0 points to nearest input row, inptr1 points to next nearest */ + inptr0 = input_data[inrow]; + if ( v == 0 ) { /* next nearest is row above */ + inptr1 = input_data[inrow - 1]; + } else { /* next nearest is row below */ + inptr1 = input_data[inrow + 1]; + } + outptr = output_data[outrow++]; + + /* Special case for first column */ + thiscolsum = GETJSAMPLE( *inptr0++ ) * 3 + GETJSAMPLE( *inptr1++ ); + nextcolsum = GETJSAMPLE( *inptr0++ ) * 3 + GETJSAMPLE( *inptr1++ ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 4 + 8 ) >> 4 ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 3 + nextcolsum + 7 ) >> 4 ); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + + for ( colctr = compptr->downsampled_width - 2; colctr > 0; colctr-- ) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + nextcolsum = GETJSAMPLE( *inptr0++ ) * 3 + GETJSAMPLE( *inptr1++ ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 3 + lastcolsum + 8 ) >> 4 ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 3 + nextcolsum + 7 ) >> 4 ); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + } + + /* Special case for last column */ + *outptr++ = (JSAMPLE) ( ( thiscolsum * 3 + lastcolsum + 8 ) >> 4 ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 4 + 7 ) >> 4 ); + } + inrow++; + } +} + + +/* + * Module initialization routine for upsampling. + */ + +GLOBAL void +jinit_upsampler( j_decompress_ptr cinfo ) { + my_upsample_ptr upsample; + int ci; + jpeg_component_info * compptr; + boolean need_buffer, do_fancy; + int h_in_group, v_in_group, h_out_group, v_out_group; + + upsample = (my_upsample_ptr) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_upsampler ) ); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_upsample; + upsample->pub.upsample = sep_upsample; + upsample->pub.need_context_rows = FALSE; /* until we find out differently */ + + if ( cinfo->CCIR601_sampling ) { /* this isn't supported */ + ERREXIT( cinfo, JERR_CCIR601_NOTIMPL ); + } + + /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, + * so don't ask for it. + */ + do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1; + + /* Verify we can handle the sampling factors, select per-component methods, + * and create storage as needed. + */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Compute size of an "input group" after IDCT scaling. This many samples + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + */ + h_in_group = ( compptr->h_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; + v_in_group = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; + h_out_group = cinfo->max_h_samp_factor; + v_out_group = cinfo->max_v_samp_factor; + upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ + need_buffer = TRUE; + if ( !compptr->component_needed ) { + /* Don't bother to upsample an uninteresting component. */ + upsample->methods[ci] = noop_upsample; + need_buffer = FALSE; + } else if ( h_in_group == h_out_group && v_in_group == v_out_group ) { + /* Fullsize components can be processed without any work. */ + upsample->methods[ci] = fullsize_upsample; + need_buffer = FALSE; + } else if ( h_in_group * 2 == h_out_group && + v_in_group == v_out_group ) { + /* Special cases for 2h1v upsampling */ + if ( do_fancy && compptr->downsampled_width > 2 ) { + upsample->methods[ci] = h2v1_fancy_upsample; + } else { + upsample->methods[ci] = h2v1_upsample; + } + } else if ( h_in_group * 2 == h_out_group && + v_in_group * 2 == v_out_group ) { + /* Special cases for 2h2v upsampling */ + if ( do_fancy && compptr->downsampled_width > 2 ) { + upsample->methods[ci] = h2v2_fancy_upsample; + upsample->pub.need_context_rows = TRUE; + } else { + upsample->methods[ci] = h2v2_upsample; + } + } else if ( ( h_out_group % h_in_group ) == 0 && + ( v_out_group % v_in_group ) == 0 ) { + /* Generic integral-factors upsampling method */ + upsample->methods[ci] = int_upsample; + upsample->h_expand[ci] = (UINT8) ( h_out_group / h_in_group ); + upsample->v_expand[ci] = (UINT8) ( v_out_group / v_in_group ); + } else { + ERREXIT( cinfo, JERR_FRACT_SAMPLE_NOTIMPL ); + } + if ( need_buffer ) { + upsample->color_buf[ci] = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up( (long) cinfo->output_width, + (long) cinfo->max_h_samp_factor ), + (JDIMENSION) cinfo->max_v_samp_factor ); + } + } +} diff --git a/src/jpeg-6/jdtrans.c b/src/jpeg-6/jdtrans.c new file mode 100644 index 0000000..3f77432 --- /dev/null +++ b/src/jpeg-6/jdtrans.c @@ -0,0 +1,125 @@ +/* + * jdtrans.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding decompression, + * that is, reading raw DCT coefficient arrays from an input JPEG file. + * The routines in jdapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL void transdecode_master_selection JPP( (j_decompress_ptr cinfo) ); + + +/* + * Read the coefficient arrays from a JPEG file. + * jpeg_read_header must be completed before calling this. + * + * The entire image is read into a set of virtual coefficient-block arrays, + * one per component. The return value is a pointer to the array of + * virtual-array descriptors. These can be manipulated directly via the + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + * To release the memory occupied by the virtual arrays, call + * jpeg_finish_decompress() when done with the data. + * + * Returns NULL if suspended. This case need be checked only if + * a suspending data source is used. + */ + +GLOBAL jvirt_barray_ptr * +jpeg_read_coefficients( j_decompress_ptr cinfo ) { + if ( cinfo->global_state == DSTATE_READY ) { + /* First call: initialize active modules */ + transdecode_master_selection( cinfo ); + cinfo->global_state = DSTATE_RDCOEFS; + } else if ( cinfo->global_state != DSTATE_RDCOEFS ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Absorb whole file into the coef buffer */ + for (;; ) { + int retcode; + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + /* Absorb some more input */ + retcode = ( *cinfo->inputctl->consume_input )( cinfo ); + if ( retcode == JPEG_SUSPENDED ) { + return NULL; + } + if ( retcode == JPEG_REACHED_EOI ) { + break; + } + /* Advance progress counter if appropriate */ + if ( cinfo->progress != NULL && + ( retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS ) ) { + if ( ++cinfo->progress->pass_counter >= cinfo->progress->pass_limit ) { + /* startup underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } + /* Set state so that jpeg_finish_decompress does the right thing */ + cinfo->global_state = DSTATE_STOPPING; + return cinfo->coef->coef_arrays; +} + + +/* + * Master selection of decompression modules for transcoding. + * This substitutes for jdmaster.c's initialization of the full decompressor. + */ + +LOCAL void +transdecode_master_selection( j_decompress_ptr cinfo ) { + /* Entropy decoding: either Huffman or arithmetic coding. */ + if ( cinfo->arith_code ) { + ERREXIT( cinfo, JERR_ARITH_NOTIMPL ); + } else { + if ( cinfo->progressive_mode ) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_huff_decoder( cinfo ); + } + } + + /* Always get a full-image coefficient buffer. */ + jinit_d_coef_controller( cinfo, TRUE ); + + /* We can now tell the memory manager to allocate virtual arrays. */ + ( *cinfo->mem->realize_virt_arrays )( (j_common_ptr) cinfo ); + + /* Initialize input side of decompressor to consume first scan. */ + ( *cinfo->inputctl->start_input_pass )( cinfo ); + + /* Initialize progress monitoring. */ + if ( cinfo->progress != NULL ) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if ( cinfo->progressive_mode ) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else if ( cinfo->inputctl->has_multiple_scans ) { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } else { + nscans = 1; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = 1; + } +} diff --git a/src/jpeg-6/jerror.c b/src/jpeg-6/jerror.c new file mode 100644 index 0000000..df4a1e9 --- /dev/null +++ b/src/jpeg-6/jerror.c @@ -0,0 +1,231 @@ +/* + * jerror.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * These routines are used by both the compression and decompression code. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +#include "../renderer/tr_local.h" + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE( code,string ) string, + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF void +error_exit( j_common_ptr cinfo ) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + ( *cinfo->err->format_message )( cinfo, buffer ); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy( cinfo ); + + ri.Error( ERR_FATAL, "%s\n", buffer ); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + */ + +METHODDEF void +output_message( j_common_ptr cinfo ) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + ( *cinfo->err->format_message )( cinfo, buffer ); + + /* Send it to stderr, adding a newline */ + ri.Printf( PRINT_ALL, "%s\n", buffer ); +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF void +emit_message( j_common_ptr cinfo, int msg_level ) { + struct jpeg_error_mgr * err = cinfo->err; + + if ( msg_level < 0 ) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if ( err->num_warnings == 0 || err->trace_level >= 3 ) { + ( *err->output_message )( cinfo ); + } + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if ( err->trace_level >= msg_level ) { + ( *err->output_message )( cinfo ); + } + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF void +format_message( j_common_ptr cinfo, char * buffer ) { + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if ( msg_code > 0 && msg_code <= err->last_jpeg_message ) { + msgtext = err->jpeg_message_table[msg_code]; + } else if ( err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message ) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if ( msgtext == NULL ) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ( ( ch = *msgptr++ ) != '\0' ) { + if ( ch == '%' ) { + if ( *msgptr == 's' ) { + isstring = TRUE; + } + break; + } + } + + /* Format the message into the passed buffer */ + if ( isstring ) { + sprintf( buffer, msgtext, err->msg_parm.s ); + } else { + sprintf( buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7] ); + } +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF void +reset_error_mgr( j_common_ptr cinfo ) { + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL struct jpeg_error_mgr * +jpeg_std_error( struct jpeg_error_mgr * err ) { + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0; /* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/src/jpeg-6/jerror.h b/src/jpeg-6/jerror.h new file mode 100644 index 0000000..11c7707 --- /dev/null +++ b/src/jpeg-6/jerror.h @@ -0,0 +1,273 @@ +/* + * jerror.h + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE( code,string ) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE( code,string ) code, + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE ( JMSG_NOMESSAGE, "Bogus message code %d" ) /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE ( JERR_ARITH_NOTIMPL, + "Sorry, there are legal restrictions on arithmetic coding" ) +JMESSAGE ( JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix" ) +JMESSAGE ( JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix" ) +JMESSAGE ( JERR_BAD_BUFFER_MODE, "Bogus buffer control mode" ) +JMESSAGE ( JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS" ) +JMESSAGE ( JERR_BAD_DCTSIZE, "IDCT output block size %d not supported" ) +JMESSAGE ( JERR_BAD_IN_COLORSPACE, "Bogus input colorspace" ) +JMESSAGE ( JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace" ) +JMESSAGE ( JERR_BAD_LENGTH, "Bogus marker length" ) +JMESSAGE ( JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan" ) +JMESSAGE ( JERR_BAD_POOL_ID, "Invalid memory pool code %d" ) +JMESSAGE ( JERR_BAD_PRECISION, "Unsupported JPEG data precision %d" ) +JMESSAGE ( JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d" ) +JMESSAGE ( JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d" ) +JMESSAGE ( JERR_BAD_SAMPLING, "Bogus sampling factors" ) +JMESSAGE ( JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d" ) +JMESSAGE ( JERR_BAD_STATE, "Improper call to JPEG library in state %d" ) +JMESSAGE ( JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access" ) +JMESSAGE ( JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small" ) +JMESSAGE ( JERR_CANT_SUSPEND, "Suspension not allowed here" ) +JMESSAGE ( JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet" ) +JMESSAGE ( JERR_COMPONENT_COUNT, "Too many color components: %d, max %d" ) +JMESSAGE ( JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request" ) +JMESSAGE ( JERR_DAC_INDEX, "Bogus DAC index %d" ) +JMESSAGE ( JERR_DAC_VALUE, "Bogus DAC value 0x%x" ) +JMESSAGE ( JERR_DHT_COUNTS, "Bogus DHT counts" ) +JMESSAGE ( JERR_DHT_INDEX, "Bogus DHT index %d" ) +JMESSAGE ( JERR_DQT_INDEX, "Bogus DQT index %d" ) +JMESSAGE ( JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)" ) +JMESSAGE ( JERR_EMS_READ, "Read from EMS failed" ) +JMESSAGE ( JERR_EMS_WRITE, "Write to EMS failed" ) +JMESSAGE ( JERR_EOI_EXPECTED, "Didn't expect more than one scan" ) +JMESSAGE ( JERR_FILE_READ, "Input file read error" ) +JMESSAGE ( JERR_FILE_WRITE, "Output file write error --- out of disk space?" ) +JMESSAGE ( JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet" ) +JMESSAGE ( JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow" ) +JMESSAGE ( JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry" ) +JMESSAGE ( JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels" ) +JMESSAGE ( JERR_INPUT_EMPTY, "Empty input file" ) +JMESSAGE ( JERR_INPUT_EOF, "Premature end of input file" ) +JMESSAGE ( JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d" ) +JMESSAGE ( JERR_MISSING_DATA, "Scan script does not transmit all data" ) +JMESSAGE ( JERR_MODE_CHANGE, "Invalid color quantization mode change" ) +JMESSAGE ( JERR_NOTIMPL, "Not implemented yet" ) +JMESSAGE ( JERR_NOT_COMPILED, "Requested feature was omitted at compile time" ) +JMESSAGE ( JERR_NO_BACKING_STORE, "Backing store not supported" ) +JMESSAGE ( JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined" ) +JMESSAGE ( JERR_NO_IMAGE, "JPEG datastream contains no image" ) +JMESSAGE ( JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined" ) +JMESSAGE ( JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x" ) +JMESSAGE ( JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)" ) +JMESSAGE ( JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components" ) +JMESSAGE ( JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors" ) +JMESSAGE ( JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors" ) +JMESSAGE ( JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers" ) +JMESSAGE ( JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker" ) +JMESSAGE ( JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x" ) +JMESSAGE ( JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers" ) +JMESSAGE ( JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF" ) +JMESSAGE ( JERR_TFILE_CREATE, "Failed to create temporary file %s" ) +JMESSAGE ( JERR_TFILE_READ, "Read failed on temporary file" ) +JMESSAGE ( JERR_TFILE_SEEK, "Seek failed on temporary file" ) +JMESSAGE ( JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?" ) +JMESSAGE ( JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines" ) +JMESSAGE ( JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x" ) +JMESSAGE ( JERR_VIRTUAL_BUG, "Virtual array controller messed up" ) +JMESSAGE ( JERR_WIDTH_OVERFLOW, "Image too wide for this implementation" ) +JMESSAGE ( JERR_XMS_READ, "Read from XMS failed" ) +JMESSAGE ( JERR_XMS_WRITE, "Write to XMS failed" ) +JMESSAGE ( JMSG_COPYRIGHT, JCOPYRIGHT ) +JMESSAGE ( JMSG_VERSION, JVERSION ) +JMESSAGE ( JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG" ) +JMESSAGE ( JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d" ) +JMESSAGE ( JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u" ) +JMESSAGE ( JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u" ) +JMESSAGE ( JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x" ) +JMESSAGE ( JTRC_DHT, "Define Huffman Table 0x%02x" ) +JMESSAGE ( JTRC_DQT, "Define Quantization Table %d precision %d" ) +JMESSAGE ( JTRC_DRI, "Define Restart Interval %u" ) +JMESSAGE ( JTRC_EMS_CLOSE, "Freed EMS handle %u" ) +JMESSAGE ( JTRC_EMS_OPEN, "Obtained EMS handle %u" ) +JMESSAGE ( JTRC_EOI, "End Of Image" ) +JMESSAGE ( JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d" ) +JMESSAGE ( JTRC_JFIF, "JFIF APP0 marker, density %dx%d %d" ) +JMESSAGE ( JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u" ) +JMESSAGE ( JTRC_JFIF_MINOR, "Unknown JFIF minor revision number %d.%02d" ) +JMESSAGE ( JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image" ) +JMESSAGE ( JTRC_MISC_MARKER, "Skipping marker 0x%02x, length %u" ) +JMESSAGE ( JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x" ) +JMESSAGE ( JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u" ) +JMESSAGE ( JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors" ) +JMESSAGE ( JTRC_QUANT_NCOLORS, "Quantizing to %d colors" ) +JMESSAGE ( JTRC_QUANT_SELECTED, "Selected %d colors for quantization" ) +JMESSAGE ( JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d" ) +JMESSAGE ( JTRC_RST, "RST%d" ) +JMESSAGE ( JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios" ) +JMESSAGE ( JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d" ) +JMESSAGE ( JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d" ) +JMESSAGE ( JTRC_SOI, "Start of Image" ) +JMESSAGE ( JTRC_SOS, "Start Of Scan: %d components" ) +JMESSAGE ( JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d" ) +JMESSAGE ( JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d" ) +JMESSAGE ( JTRC_TFILE_CLOSE, "Closed temporary file %s" ) +JMESSAGE ( JTRC_TFILE_OPEN, "Opened temporary file %s" ) +JMESSAGE ( JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr" ) +JMESSAGE ( JTRC_XMS_CLOSE, "Freed XMS handle %u" ) +JMESSAGE ( JTRC_XMS_OPEN, "Obtained XMS handle %u" ) +JMESSAGE ( JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d" ) +JMESSAGE ( JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d" ) +JMESSAGE ( JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x" ) +JMESSAGE ( JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment" ) +JMESSAGE ( JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code" ) +JMESSAGE ( JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d" ) +JMESSAGE ( JWRN_JPEG_EOF, "Premature end of JPEG file" ) +JMESSAGE ( JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d" ) +JMESSAGE ( JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG" ) +JMESSAGE ( JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines" ) + +#ifdef JMAKE_ENUM_LIST + +JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + + +#ifndef JERROR_H +#define JERROR_H + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT( cinfo,code ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( *( cinfo )->err->error_exit )( (j_common_ptr) ( cinfo ) ) ) +#define ERREXIT1( cinfo,code,p1 ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( cinfo )->err->msg_parm.i[0] = ( p1 ), \ + ( *( cinfo )->err->error_exit )( (j_common_ptr) ( cinfo ) ) ) +#define ERREXIT2( cinfo,code,p1,p2 ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( cinfo )->err->msg_parm.i[0] = ( p1 ), \ + ( cinfo )->err->msg_parm.i[1] = ( p2 ), \ + ( *( cinfo )->err->error_exit )( (j_common_ptr) ( cinfo ) ) ) +#define ERREXIT3( cinfo,code,p1,p2,p3 ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( cinfo )->err->msg_parm.i[0] = ( p1 ), \ + ( cinfo )->err->msg_parm.i[1] = ( p2 ), \ + ( cinfo )->err->msg_parm.i[2] = ( p3 ), \ + ( *( cinfo )->err->error_exit )( (j_common_ptr) ( cinfo ) ) ) +#define ERREXIT4( cinfo,code,p1,p2,p3,p4 ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( cinfo )->err->msg_parm.i[0] = ( p1 ), \ + ( cinfo )->err->msg_parm.i[1] = ( p2 ), \ + ( cinfo )->err->msg_parm.i[2] = ( p3 ), \ + ( cinfo )->err->msg_parm.i[3] = ( p4 ), \ + ( *( cinfo )->err->error_exit )( (j_common_ptr) ( cinfo ) ) ) +#define ERREXITS( cinfo,code,str ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + strncpy( ( cinfo )->err->msg_parm.s, ( str ), JMSG_STR_PARM_MAX ), \ + ( *( cinfo )->err->error_exit )( (j_common_ptr) ( cinfo ) ) ) + +#define MAKESTMT( stuff ) do { stuff } while ( 0 ) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS( cinfo,code ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), -1 ) ) +#define WARNMS1( cinfo,code,p1 ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( cinfo )->err->msg_parm.i[0] = ( p1 ), \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), -1 ) ) +#define WARNMS2( cinfo,code,p1,p2 ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( cinfo )->err->msg_parm.i[0] = ( p1 ), \ + ( cinfo )->err->msg_parm.i[1] = ( p2 ), \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), -1 ) ) + +/* Informational/debugging messages */ +#define TRACEMS( cinfo,lvl,code ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), ( lvl ) ) ) +#define TRACEMS1( cinfo,lvl,code,p1 ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( cinfo )->err->msg_parm.i[0] = ( p1 ), \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), ( lvl ) ) ) +#define TRACEMS2( cinfo,lvl,code,p1,p2 ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + ( cinfo )->err->msg_parm.i[0] = ( p1 ), \ + ( cinfo )->err->msg_parm.i[1] = ( p2 ), \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), ( lvl ) ) ) +#define TRACEMS3( cinfo,lvl,code,p1,p2,p3 ) \ + MAKESTMT( int * _mp = ( cinfo )->err->msg_parm.i; \ + _mp[0] = ( p1 ); _mp[1] = ( p2 ); _mp[2] = ( p3 ); \ + ( cinfo )->err->msg_code = ( code ); \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), ( lvl ) ); ) +#define TRACEMS4( cinfo,lvl,code,p1,p2,p3,p4 ) \ + MAKESTMT( int * _mp = ( cinfo )->err->msg_parm.i; \ + _mp[0] = ( p1 ); _mp[1] = ( p2 ); _mp[2] = ( p3 ); _mp[3] = ( p4 ); \ + ( cinfo )->err->msg_code = ( code ); \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), ( lvl ) ); ) +#define TRACEMS8( cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8 ) \ + MAKESTMT( int * _mp = ( cinfo )->err->msg_parm.i; \ + _mp[0] = ( p1 ); _mp[1] = ( p2 ); _mp[2] = ( p3 ); _mp[3] = ( p4 ); \ + _mp[4] = ( p5 ); _mp[5] = ( p6 ); _mp[6] = ( p7 ); _mp[7] = ( p8 ); \ + ( cinfo )->err->msg_code = ( code ); \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), ( lvl ) ); ) +#define TRACEMSS( cinfo,lvl,code,str ) \ + ( ( cinfo )->err->msg_code = ( code ), \ + strncpy( ( cinfo )->err->msg_parm.s, ( str ), JMSG_STR_PARM_MAX ), \ + ( *( cinfo )->err->emit_message )( (j_common_ptr) ( cinfo ), ( lvl ) ) ) + +#endif /* JERROR_H */ diff --git a/src/jpeg-6/jfdctflt.c b/src/jpeg-6/jfdctflt.c new file mode 100644 index 0000000..16eb81b --- /dev/null +++ b/src/jpeg-6/jfdctflt.c @@ -0,0 +1,167 @@ +/* + * jfdctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * forward DCT (Discrete Cosine Transform). + * + * This implementation should be more accurate than either of the integer + * DCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_float( FAST_FLOAT * data ) { + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + FAST_FLOAT *dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for ( ctr = DCTSIZE - 1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = ( tmp12 + tmp13 ) * ( (FAST_FLOAT) 0.707106781 ); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = 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 ) * ( (FAST_FLOAT) 0.382683433 ); /* c6 */ + z2 = ( (FAST_FLOAT) 0.541196100 ) * tmp10 + z5; /* c2-c6 */ + z4 = ( (FAST_FLOAT) 1.306562965 ) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ( (FAST_FLOAT) 0.707106781 ); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for ( ctr = DCTSIZE - 1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[DCTSIZE * 0] + dataptr[DCTSIZE * 7]; + tmp7 = dataptr[DCTSIZE * 0] - dataptr[DCTSIZE * 7]; + tmp1 = dataptr[DCTSIZE * 1] + dataptr[DCTSIZE * 6]; + tmp6 = dataptr[DCTSIZE * 1] - dataptr[DCTSIZE * 6]; + tmp2 = dataptr[DCTSIZE * 2] + dataptr[DCTSIZE * 5]; + tmp5 = dataptr[DCTSIZE * 2] - dataptr[DCTSIZE * 5]; + tmp3 = dataptr[DCTSIZE * 3] + dataptr[DCTSIZE * 4]; + tmp4 = dataptr[DCTSIZE * 3] - dataptr[DCTSIZE * 4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE * 0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE * 4] = tmp10 - tmp11; + + z1 = ( tmp12 + tmp13 ) * ( (FAST_FLOAT) 0.707106781 ); /* c4 */ + dataptr[DCTSIZE * 2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE * 6] = 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 ) * ( (FAST_FLOAT) 0.382683433 ); /* c6 */ + z2 = ( (FAST_FLOAT) 0.541196100 ) * tmp10 + z5; /* c2-c6 */ + z4 = ( (FAST_FLOAT) 1.306562965 ) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ( (FAST_FLOAT) 0.707106781 ); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE * 5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE * 3] = z13 - z2; + dataptr[DCTSIZE * 1] = z11 + z4; + dataptr[DCTSIZE * 7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/src/jpeg-6/jidctflt.c b/src/jpeg-6/jidctflt.c new file mode 100644 index 0000000..91d188d --- /dev/null +++ b/src/jpeg-6/jidctflt.c @@ -0,0 +1,240 @@ +/* + * jidctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * This implementation should be more accurate than either of the integer + * IDCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a float result. + */ + +#define DEQUANTIZE( coef,quantval ) ( ( (FAST_FLOAT) ( coef ) ) * ( quantval ) ) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_float( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col ) { + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z5, z10, z11, z12, z13; + JCOEFPTR inptr; + FLOAT_MULT_TYPE * quantptr; + FAST_FLOAT * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit( cinfo ); + int ctr; + FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for ( ctr = DCTSIZE; ctr > 0; ctr-- ) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ( ( inptr[DCTSIZE * 1] | inptr[DCTSIZE * 2] | inptr[DCTSIZE * 3] | + inptr[DCTSIZE * 4] | inptr[DCTSIZE * 5] | inptr[DCTSIZE * 6] | + inptr[DCTSIZE * 7] ) == 0 ) { + /* AC terms all zero */ + FAST_FLOAT dcval = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + + wsptr[DCTSIZE * 0] = dcval; + wsptr[DCTSIZE * 1] = dcval; + wsptr[DCTSIZE * 2] = dcval; + wsptr[DCTSIZE * 3] = dcval; + wsptr[DCTSIZE * 4] = dcval; + wsptr[DCTSIZE * 5] = dcval; + wsptr[DCTSIZE * 6] = dcval; + wsptr[DCTSIZE * 7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + tmp1 = DEQUANTIZE( inptr[DCTSIZE * 2], quantptr[DCTSIZE * 2] ); + tmp2 = DEQUANTIZE( inptr[DCTSIZE * 4], quantptr[DCTSIZE * 4] ); + tmp3 = DEQUANTIZE( inptr[DCTSIZE * 6], quantptr[DCTSIZE * 6] ); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = ( tmp1 - tmp3 ) * ( (FAST_FLOAT) 1.414213562 ) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE( inptr[DCTSIZE * 1], quantptr[DCTSIZE * 1] ); + tmp5 = DEQUANTIZE( inptr[DCTSIZE * 3], quantptr[DCTSIZE * 3] ); + tmp6 = DEQUANTIZE( inptr[DCTSIZE * 5], quantptr[DCTSIZE * 5] ); + tmp7 = DEQUANTIZE( inptr[DCTSIZE * 7], quantptr[DCTSIZE * 7] ); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = ( z11 - z13 ) * ( (FAST_FLOAT) 1.414213562 ); /* 2*c4 */ + + z5 = ( z10 + z12 ) * ( (FAST_FLOAT) 1.847759065 ); /* 2*c2 */ + tmp10 = ( (FAST_FLOAT) 1.082392200 ) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ( (FAST_FLOAT) -2.613125930 ) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE * 0] = tmp0 + tmp7; + wsptr[DCTSIZE * 7] = tmp0 - tmp7; + wsptr[DCTSIZE * 1] = tmp1 + tmp6; + wsptr[DCTSIZE * 6] = tmp1 - tmp6; + wsptr[DCTSIZE * 2] = tmp2 + tmp5; + wsptr[DCTSIZE * 5] = tmp2 - tmp5; + wsptr[DCTSIZE * 4] = tmp3 + tmp4; + wsptr[DCTSIZE * 3] = tmp3 - tmp4; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3. */ + + wsptr = workspace; + for ( ctr = 0; ctr < DCTSIZE; ctr++ ) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * And testing floats for zero is relatively expensive, so we don't bother. + */ + + /* Even part */ + + tmp10 = wsptr[0] + wsptr[4]; + tmp11 = wsptr[0] - wsptr[4]; + + tmp13 = wsptr[2] + wsptr[6]; + tmp12 = ( wsptr[2] - wsptr[6] ) * ( (FAST_FLOAT) 1.414213562 ) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = wsptr[5] + wsptr[3]; + z10 = wsptr[5] - wsptr[3]; + z11 = wsptr[1] + wsptr[7]; + z12 = wsptr[1] - wsptr[7]; + + tmp7 = z11 + z13; + tmp11 = ( z11 - z13 ) * ( (FAST_FLOAT) 1.414213562 ); + + z5 = ( z10 + z12 ) * ( (FAST_FLOAT) 1.847759065 ); /* 2*c2 */ + tmp10 = ( (FAST_FLOAT) 1.082392200 ) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ( (FAST_FLOAT) -2.613125930 ) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[(int) DESCALE( (INT32) ( tmp0 + tmp7 ), 3 ) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE( (INT32) ( tmp0 - tmp7 ), 3 ) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE( (INT32) ( tmp1 + tmp6 ), 3 ) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE( (INT32) ( tmp1 - tmp6 ), 3 ) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE( (INT32) ( tmp2 + tmp5 ), 3 ) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE( (INT32) ( tmp2 - tmp5 ), 3 ) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE( (INT32) ( tmp3 + tmp4 ), 3 ) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE( (INT32) ( tmp3 - tmp4 ), 3 ) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/src/jpeg-6/jinclude.h b/src/jpeg-6/jinclude.h new file mode 100644 index 0000000..6cd1248 --- /dev/null +++ b/src/jpeg-6/jinclude.h @@ -0,0 +1,116 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +#ifdef _WIN32 + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4152) // nonstandard extension, function/data pointer conversion in expression +#pragma warning(disable : 4201) +#pragma warning(disable : 4214) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable: 4505) // unreferenced local function has been removed +#pragma warning(disable : 4514) +#pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters +#pragma warning(disable : 4761) // integral size mismatch +#endif + +/* Include auto-config file to find out which system include files we need. */ + +#include "../jpeg-6/jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO( target,size ) bzero( (void *)( target ), (size_t)( size ) ) +#define MEMCOPY( dest,src,size ) bcopy( (const void *)( src ), (void *)( dest ), (size_t)( size ) ) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO( target,size ) memset( (void *)( target ), 0, (size_t)( size ) ) +#define MEMCOPY( dest,src,size ) memcpy( (void *)( dest ), (const void *)( src ), (size_t)( size ) ) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF( object ) ( (size_t) sizeof( object ) ) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD( file,buf,sizeofbuf ) \ + ( (size_t) fread( (void *) ( buf ), (size_t) 1, (size_t) ( sizeofbuf ), ( file ) ) ) +#define JFWRITE( file,buf,sizeofbuf ) \ + ( (size_t) fwrite( (const void *) ( buf ), (size_t) 1, (size_t) ( sizeofbuf ), ( file ) ) ) diff --git a/src/jpeg-6/jmemmgr.c b/src/jpeg-6/jmemmgr.c new file mode 100644 index 0000000..d3cd6c5 --- /dev/null +++ b/src/jpeg-6/jmemmgr.c @@ -0,0 +1,1143 @@ +/* + * jmemmgr.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the JPEG system-independent memory management + * routines. This code is usable across a wide variety of machines; most + * of the system dependencies have been isolated in a separate file. + * The major functions provided here are: + * * pool-based allocation and freeing of memory; + * * policy decisions about how to divide available memory among the + * virtual arrays; + * * control logic for swapping virtual arrays between main memory and + * backing storage. + * The separate system-dependent file provides the actual backing-storage + * access code, and it contains the policy decision about how much total + * main memory to use. + * This file is system-dependent in the sense that some of its functions + * are unnecessary in some systems. For example, if there is enough virtual + * memory so that backing storage will never be used, much of the virtual + * array control logic could be removed. (Of course, if you have that much + * memory then you shouldn't care about a little bit of unused code...) + */ + +#define JPEG_INTERNALS +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef NO_GETENV +#ifndef HAVE_STDLIB_H /* should declare getenv() */ +extern char * getenv JPP( (const char * name) ); +#endif +#endif + + +/* + * Some important notes: + * The allocation routines provided here must never return NULL. + * They should exit to error_exit if unsuccessful. + * + * It's not a good idea to try to merge the sarray and barray routines, + * even though they are textually almost the same, because samples are + * usually stored as bytes while coefficients are shorts or ints. Thus, + * in machines where byte pointers have a different representation from + * word pointers, the resulting machine code could not be the same. + */ + + +/* + * Many machines require storage alignment: longs must start on 4-byte + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + * always returns pointers that are multiples of the worst-case alignment + * requirement, and we had better do so too. + * There isn't any really portable way to determine the worst-case alignment + * requirement. This module assumes that the alignment requirement is + * multiples of sizeof(ALIGN_TYPE). + * By default, we define ALIGN_TYPE as double. This is necessary on some + * workstations (where doubles really do need 8-byte alignment) and will work + * fine on nearly everything. If your machine has lesser alignment needs, + * you can save a few bytes by making ALIGN_TYPE smaller. + * The only place I know of where this will NOT work is certain Macintosh + * 680x0 compilers that define double as a 10-byte IEEE extended float. + * Doing 10-byte alignment is counterproductive because longwords won't be + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + * such a compiler. + */ + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#define ALIGN_TYPE double +#endif + + +/* + * We allocate objects from "pools", where each pool is gotten with a single + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + * overhead within a pool, except for alignment padding. Each pool has a + * header with a link to the next pool of the same class. + * Small and large pool headers are identical except that the latter's + * link pointer must be FAR on 80x86 machines. + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + * of the alignment requirement of ALIGN_TYPE. + */ + +typedef union small_pool_struct * small_pool_ptr; + +typedef union small_pool_struct { + struct { + small_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} small_pool_hdr; + +typedef union large_pool_struct FAR * large_pool_ptr; + +typedef union large_pool_struct { + struct { + large_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} large_pool_hdr; + + +/* + * Here is the full definition of a memory manager object. + */ + +typedef struct { + struct jpeg_memory_mgr pub; /* public fields */ + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + /* Since we only have one lifetime class of virtual arrays, only one + * linked list is necessary (for each datatype). Note that the virtual + * array control blocks being linked together are actually stored somewhere + * in the small-pool list. + */ + jvirt_sarray_ptr virt_sarray_list; + jvirt_barray_ptr virt_barray_list; + + /* This counts total space obtained from jpeg_get_small/large */ + long total_space_allocated; + + /* alloc_sarray and alloc_barray set this value for use by virtual + * array routines. + */ + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ +} my_memory_mgr; + +typedef my_memory_mgr * my_mem_ptr; + + +/* + * The control blocks for virtual arrays. + * Note that these blocks are allocated in the "small" pool area. + * System-dependent info for the associated backing store (if any) is hidden + * inside the backing_store_info struct. + */ + +struct jvirt_sarray_control { + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + +struct jvirt_barray_control { + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next; /* link to next virtual barray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + +LOCAL void +print_mem_stats( j_common_ptr cinfo, int pool_id ) { + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + + /* Since this is only a debugging stub, we can cheat a little by using + * fprintf directly rather than going through the trace message code. + * This is helpful because message parm array can't handle longs. + */ + fprintf( stderr, "Freeing pool %d, total space = %ld\n", + pool_id, mem->total_space_allocated ); + + for ( lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + lhdr_ptr = lhdr_ptr->hdr.next ) { + fprintf( stderr, " Large chunk used %ld\n", + (long) lhdr_ptr->hdr.bytes_used ); + } + + for ( shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + shdr_ptr = shdr_ptr->hdr.next ) { + fprintf( stderr, " Small chunk used %ld free %ld\n", + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left ); + } +} + +#endif /* MEM_STATS */ + + +LOCAL void +out_of_memory( j_common_ptr cinfo, int which ) { +/* Report an out-of-memory error and stop execution */ +/* If we compiled MEM_STATS support, report alloc requests before dying */ +#ifdef MEM_STATS + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ +#endif + ERREXIT1( cinfo, JERR_OUT_OF_MEMORY, which ); +} + + +/* + * Allocation of "small" objects. + * + * For these, we use pooled storage. When a new pool must be created, + * we try to get enough space for the current request plus a "slop" factor, + * where the slop will be the amount of leftover space in the new pool. + * The speed vs. space tradeoff is largely determined by the slop values. + * A different slop value is provided for each pool class (lifetime), + * and we also distinguish the first pool of a class from later ones. + * NOTE: the values given work fairly well on both 16- and 32-bit-int + * machines, but may be too small if longs are 64 bits or more. + */ + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = +{ + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ +}; + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = +{ + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ +}; + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + +METHODDEF void * +alloc_small( j_common_ptr cinfo, int pool_id, size_t sizeofobject ) { +/* Allocate a "small" object */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr hdr_ptr, prev_hdr_ptr; + char * data_ptr; + size_t odd_bytes, min_request, slop; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if ( sizeofobject > (size_t) ( MAX_ALLOC_CHUNK - SIZEOF( small_pool_hdr ) ) ) { + out_of_memory( cinfo, 1 ); /* request exceeds malloc's ability */ + + } + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF( ALIGN_TYPE ); + if ( odd_bytes > 0 ) { + sizeofobject += SIZEOF( ALIGN_TYPE ) - odd_bytes; + } + + /* See if space is available in any existing pool */ + if ( pool_id < 0 || pool_id >= JPOOL_NUMPOOLS ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); /* safety check */ + } + prev_hdr_ptr = NULL; + hdr_ptr = mem->small_list[pool_id]; + while ( hdr_ptr != NULL ) { + if ( hdr_ptr->hdr.bytes_left >= sizeofobject ) { + break; /* found pool with enough space */ + } + prev_hdr_ptr = hdr_ptr; + hdr_ptr = hdr_ptr->hdr.next; + } + + /* Time to make a new pool? */ + if ( hdr_ptr == NULL ) { + /* min_request is what we need now, slop is what will be leftover */ + min_request = sizeofobject + SIZEOF( small_pool_hdr ); + if ( prev_hdr_ptr == NULL ) { /* first pool in class? */ + slop = first_pool_slop[pool_id]; + } else { + slop = extra_pool_slop[pool_id]; + } + /* Don't ask for more than MAX_ALLOC_CHUNK */ + if ( slop > (size_t) ( MAX_ALLOC_CHUNK - min_request ) ) { + slop = (size_t) ( MAX_ALLOC_CHUNK - min_request ); + } + /* Try to get space, if fail reduce slop and try again */ + for (;; ) { + hdr_ptr = (small_pool_ptr) jpeg_get_small( cinfo, min_request + slop ); + if ( hdr_ptr != NULL ) { + break; + } + slop /= 2; + if ( slop < MIN_SLOP ) { /* give up when it gets real small */ + out_of_memory( cinfo, 2 ); /* jpeg_get_small failed */ + } + } + mem->total_space_allocated += min_request + slop; + /* Success, initialize the new pool header and add to end of list */ + hdr_ptr->hdr.next = NULL; + hdr_ptr->hdr.bytes_used = 0; + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + if ( prev_hdr_ptr == NULL ) { /* first pool in class? */ + mem->small_list[pool_id] = hdr_ptr; + } else { + prev_hdr_ptr->hdr.next = hdr_ptr; + } + } + + /* OK, allocate the object from the current pool */ + data_ptr = ( char * )( hdr_ptr + 1 ); /* point to first data byte in pool */ + data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */ + hdr_ptr->hdr.bytes_used += sizeofobject; + hdr_ptr->hdr.bytes_left -= sizeofobject; + + return (void *) data_ptr; +} + + +/* + * Allocation of "large" objects. + * + * The external semantics of these are the same as "small" objects, + * except that FAR pointers are used on 80x86. However the pool + * management heuristics are quite different. We assume that each + * request is large enough that it may as well be passed directly to + * jpeg_get_large; the pool management just links everything together + * so that we can free it all on demand. + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + * structures. The routines that create these structures (see below) + * deliberately bunch rows together to ensure a large request size. + */ + +METHODDEF void FAR * +alloc_large( j_common_ptr cinfo, int pool_id, size_t sizeofobject ) { +/* Allocate a "large" object */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + large_pool_ptr hdr_ptr; + size_t odd_bytes; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if ( sizeofobject > (size_t) ( MAX_ALLOC_CHUNK - SIZEOF( large_pool_hdr ) ) ) { + out_of_memory( cinfo, 3 ); /* request exceeds malloc's ability */ + + } + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF( ALIGN_TYPE ); + if ( odd_bytes > 0 ) { + sizeofobject += SIZEOF( ALIGN_TYPE ) - odd_bytes; + } + + /* Always make a new pool */ + if ( pool_id < 0 || pool_id >= JPOOL_NUMPOOLS ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); /* safety check */ + + } + hdr_ptr = (large_pool_ptr) jpeg_get_large( cinfo, sizeofobject + + SIZEOF( large_pool_hdr ) ); + if ( hdr_ptr == NULL ) { + out_of_memory( cinfo, 4 ); /* jpeg_get_large failed */ + } + mem->total_space_allocated += sizeofobject + SIZEOF( large_pool_hdr ); + + /* Success, initialize the new pool header and add to list */ + hdr_ptr->hdr.next = mem->large_list[pool_id]; + /* We maintain space counts in each pool header for statistical purposes, + * even though they are not needed for allocation. + */ + hdr_ptr->hdr.bytes_used = sizeofobject; + hdr_ptr->hdr.bytes_left = 0; + mem->large_list[pool_id] = hdr_ptr; + + return (void FAR *) ( hdr_ptr + 1 ); /* point to first data byte in pool */ +} + + +/* + * Creation of 2-D sample arrays. + * The pointers are in near heap, the samples themselves in FAR heap. + * + * To minimize allocation overhead and to allow I/O of large contiguous + * blocks, we allocate the sample rows in groups of as many rows as possible + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + * NB: the virtual array control routines, later in this file, know about + * this chunking of rows. The rowsperchunk value is left in the mem manager + * object so that it can be saved away if this sarray is the workspace for + * a virtual array. + */ + +METHODDEF JSAMPARRAY +alloc_sarray( j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows ) { +/* Allocate a 2-D sample array */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JSAMPARRAY result; + JSAMPROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = ( MAX_ALLOC_CHUNK - SIZEOF( large_pool_hdr ) ) / + ( (long) samplesperrow * SIZEOF( JSAMPLE ) ); + if ( ltemp <= 0 ) { + ERREXIT( cinfo, JERR_WIDTH_OVERFLOW ); + } + if ( ltemp < (long) numrows ) { + rowsperchunk = (JDIMENSION) ltemp; + } else { + rowsperchunk = numrows; + } + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JSAMPARRAY) alloc_small( cinfo, pool_id, + (size_t) ( numrows * SIZEOF( JSAMPROW ) ) ); + + /* Get the rows themselves (large objects) */ + currow = 0; + while ( currow < numrows ) { + rowsperchunk = MIN( rowsperchunk, numrows - currow ); + workspace = (JSAMPROW) alloc_large( cinfo, pool_id, + (size_t) ( (size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF( JSAMPLE ) ) ); + for ( i = rowsperchunk; i > 0; i-- ) { + result[currow++] = workspace; + workspace += samplesperrow; + } + } + + return result; +} + + +/* + * Creation of 2-D coefficient-block arrays. + * This is essentially the same as the code for sample arrays, above. + */ + +METHODDEF JBLOCKARRAY +alloc_barray( j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows ) { +/* Allocate a 2-D coefficient-block array */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JBLOCKARRAY result; + JBLOCKROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = ( MAX_ALLOC_CHUNK - SIZEOF( large_pool_hdr ) ) / + ( (long) blocksperrow * SIZEOF( JBLOCK ) ); + if ( ltemp <= 0 ) { + ERREXIT( cinfo, JERR_WIDTH_OVERFLOW ); + } + if ( ltemp < (long) numrows ) { + rowsperchunk = (JDIMENSION) ltemp; + } else { + rowsperchunk = numrows; + } + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JBLOCKARRAY) alloc_small( cinfo, pool_id, + (size_t) ( numrows * SIZEOF( JBLOCKROW ) ) ); + + /* Get the rows themselves (large objects) */ + currow = 0; + while ( currow < numrows ) { + rowsperchunk = MIN( rowsperchunk, numrows - currow ); + workspace = (JBLOCKROW) alloc_large( cinfo, pool_id, + (size_t) ( (size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF( JBLOCK ) ) ); + for ( i = rowsperchunk; i > 0; i-- ) { + result[currow++] = workspace; + workspace += blocksperrow; + } + } + + return result; +} + + +/* + * About virtual array management: + * + * The above "normal" array routines are only used to allocate strip buffers + * (as wide as the image, but just a few rows high). Full-image-sized buffers + * are handled as "virtual" arrays. The array is still accessed a strip at a + * time, but the memory manager must save the whole array for repeated + * accesses. The intended implementation is that there is a strip buffer in + * memory (as high as is possible given the desired memory limit), plus a + * backing file that holds the rest of the array. + * + * The request_virt_array routines are told the total size of the image and + * the maximum number of rows that will be accessed at once. The in-memory + * buffer must be at least as large as the maxaccess value. + * + * The request routines create control blocks but not the in-memory buffers. + * That is postponed until realize_virt_arrays is called. At that time the + * total amount of space needed is known (approximately, anyway), so free + * memory can be divided up fairly. + * + * The access_virt_array routines are responsible for making a specific strip + * area accessible (after reading or writing the backing file, if necessary). + * Note that the access routines are told whether the caller intends to modify + * the accessed strip; during a read-only pass this saves having to rewrite + * data to disk. The access routines are also responsible for pre-zeroing + * any newly accessed rows, if pre-zeroing was requested. + * + * In current usage, the access requests are usually for nonoverlapping + * strips; that is, successive access start_row numbers differ by exactly + * num_rows = maxaccess. This means we can get good performance with simple + * buffer dump/reload logic, by making the in-memory buffer be a multiple + * of the access height; then there will never be accesses across bufferload + * boundaries. The code will still work with overlapping access requests, + * but it doesn't handle bufferload overlaps very efficiently. + */ + + +METHODDEF jvirt_sarray_ptr +request_virt_sarray( j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess ) { +/* Request a virtual 2-D sample array */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_sarray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if ( pool_id != JPOOL_IMAGE ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); /* safety check */ + + } + /* get control block */ + result = (jvirt_sarray_ptr) alloc_small( cinfo, pool_id, + SIZEOF( struct jvirt_sarray_control ) ); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->samplesperrow = samplesperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ + mem->virt_sarray_list = result; + + return result; +} + + +METHODDEF jvirt_barray_ptr +request_virt_barray( j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess ) { +/* Request a virtual 2-D coefficient-block array */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_barray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if ( pool_id != JPOOL_IMAGE ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); /* safety check */ + + } + /* get control block */ + result = (jvirt_barray_ptr) alloc_small( cinfo, pool_id, + SIZEOF( struct jvirt_barray_control ) ); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->blocksperrow = blocksperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_barray_list; /* add to list of virtual arrays */ + mem->virt_barray_list = result; + + return result; +} + + +METHODDEF void +realize_virt_arrays( j_common_ptr cinfo ) { +/* Allocate the in-memory buffers for any unrealized virtual arrays */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + long space_per_minheight, maximum_space, avail_mem; + long minheights, max_minheights; + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + /* Compute the minimum space needed (maxaccess rows in each buffer) + * and the maximum space needed (full image height in each buffer). + * These may be of use to the system-dependent jpeg_mem_available routine. + */ + space_per_minheight = 0; + maximum_space = 0; + for ( sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next ) { + if ( sptr->mem_buffer == NULL ) { /* if not realized yet */ + space_per_minheight += (long) sptr->maxaccess * + (long) sptr->samplesperrow * SIZEOF( JSAMPLE ); + maximum_space += (long) sptr->rows_in_array * + (long) sptr->samplesperrow * SIZEOF( JSAMPLE ); + } + } + for ( bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next ) { + if ( bptr->mem_buffer == NULL ) { /* if not realized yet */ + space_per_minheight += (long) bptr->maxaccess * + (long) bptr->blocksperrow * SIZEOF( JBLOCK ); + maximum_space += (long) bptr->rows_in_array * + (long) bptr->blocksperrow * SIZEOF( JBLOCK ); + } + } + + if ( space_per_minheight <= 0 ) { + return; /* no unrealized arrays, no work */ + + } + /* Determine amount of memory to actually use; this is system-dependent. */ + avail_mem = jpeg_mem_available( cinfo, space_per_minheight, maximum_space, + mem->total_space_allocated ); + + /* If the maximum space needed is available, make all the buffers full + * height; otherwise parcel it out with the same number of minheights + * in each buffer. + */ + if ( avail_mem >= maximum_space ) { + max_minheights = 1000000000L; + } else { + max_minheights = avail_mem / space_per_minheight; + /* If there doesn't seem to be enough space, try to get the minimum + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + */ + if ( max_minheights <= 0 ) { + max_minheights = 1; + } + } + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + for ( sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next ) { + if ( sptr->mem_buffer == NULL ) { /* if not realized yet */ + minheights = ( (long) sptr->rows_in_array - 1L ) / sptr->maxaccess + 1L; + if ( minheights <= max_minheights ) { + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) ( max_minheights * sptr->maxaccess ); + jpeg_open_backing_store( cinfo, &sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF( JSAMPLE ) ); + sptr->b_s_open = TRUE; + } + sptr->mem_buffer = alloc_sarray( cinfo, JPOOL_IMAGE, + sptr->samplesperrow, sptr->rows_in_mem ); + sptr->rowsperchunk = mem->last_rowsperchunk; + sptr->cur_start_row = 0; + sptr->first_undef_row = 0; + sptr->dirty = FALSE; + } + } + + for ( bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next ) { + if ( bptr->mem_buffer == NULL ) { /* if not realized yet */ + minheights = ( (long) bptr->rows_in_array - 1L ) / bptr->maxaccess + 1L; + if ( minheights <= max_minheights ) { + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) ( max_minheights * bptr->maxaccess ); + jpeg_open_backing_store( cinfo, &bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF( JBLOCK ) ); + bptr->b_s_open = TRUE; + } + bptr->mem_buffer = alloc_barray( cinfo, JPOOL_IMAGE, + bptr->blocksperrow, bptr->rows_in_mem ); + bptr->rowsperchunk = mem->last_rowsperchunk; + bptr->cur_start_row = 0; + bptr->first_undef_row = 0; + bptr->dirty = FALSE; + } + } +} + + +LOCAL void +do_sarray_io( j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing ) { +/* Do backing store read or write of a virtual sample array */ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->samplesperrow * SIZEOF( JSAMPLE ); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for ( i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk ) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN( (long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i ); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN( rows, (long) ptr->first_undef_row - thisrow ); + /* Transfer no more than fits in file */ + rows = MIN( rows, (long) ptr->rows_in_array - thisrow ); + if ( rows <= 0 ) { /* this chunk might be past end of file! */ + break; + } + byte_count = rows * bytesperrow; + if ( writing ) { + ( *ptr->b_s_info.write_backing_store )( cinfo, &ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count ); + } else { + ( *ptr->b_s_info.read_backing_store )( cinfo, &ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count ); + } + file_offset += byte_count; + } +} + + +LOCAL void +do_barray_io( j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing ) { +/* Do backing store read or write of a virtual coefficient-block array */ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->blocksperrow * SIZEOF( JBLOCK ); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for ( i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk ) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN( (long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i ); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN( rows, (long) ptr->first_undef_row - thisrow ); + /* Transfer no more than fits in file */ + rows = MIN( rows, (long) ptr->rows_in_array - thisrow ); + if ( rows <= 0 ) { /* this chunk might be past end of file! */ + break; + } + byte_count = rows * bytesperrow; + if ( writing ) { + ( *ptr->b_s_info.write_backing_store )( cinfo, &ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count ); + } else { + ( *ptr->b_s_info.read_backing_store )( cinfo, &ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count ); + } + file_offset += byte_count; + } +} + + +METHODDEF JSAMPARRAY +access_virt_sarray( j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable ) { +/* Access the part of a virtual sample array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if ( end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL ) { + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + + /* Make the desired part of the virtual array accessible */ + if ( start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row + ptr->rows_in_mem ) { + if ( !ptr->b_s_open ) { + ERREXIT( cinfo, JERR_VIRTUAL_BUG ); + } + /* Flush old buffer contents if necessary */ + if ( ptr->dirty ) { + do_sarray_io( cinfo, ptr, TRUE ); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if ( start_row > ptr->cur_start_row ) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if ( ltemp < 0 ) { + ltemp = 0; /* don't fall off front end of file */ + } + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_sarray_io( cinfo, ptr, FALSE ); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if ( ptr->first_undef_row < end_row ) { + if ( ptr->first_undef_row < start_row ) { + if ( writable ) { /* writer skipped over a section of array */ + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if ( writable ) { + ptr->first_undef_row = end_row; + } + if ( ptr->pre_zero ) { + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF( JSAMPLE ); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while ( undef_row < end_row ) { + jzero_far( (void FAR *) ptr->mem_buffer[undef_row], bytesperrow ); + undef_row++; + } + } else { + if ( !writable ) { /* reader looking at undefined data */ + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + } + } + /* Flag the buffer dirty if caller will write in it */ + if ( writable ) { + ptr->dirty = TRUE; + } + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + ( start_row - ptr->cur_start_row ); +} + + +METHODDEF JBLOCKARRAY +access_virt_barray( j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable ) { +/* Access the part of a virtual block array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if ( end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL ) { + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + + /* Make the desired part of the virtual array accessible */ + if ( start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row + ptr->rows_in_mem ) { + if ( !ptr->b_s_open ) { + ERREXIT( cinfo, JERR_VIRTUAL_BUG ); + } + /* Flush old buffer contents if necessary */ + if ( ptr->dirty ) { + do_barray_io( cinfo, ptr, TRUE ); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if ( start_row > ptr->cur_start_row ) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if ( ltemp < 0 ) { + ltemp = 0; /* don't fall off front end of file */ + } + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_barray_io( cinfo, ptr, FALSE ); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if ( ptr->first_undef_row < end_row ) { + if ( ptr->first_undef_row < start_row ) { + if ( writable ) { /* writer skipped over a section of array */ + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if ( writable ) { + ptr->first_undef_row = end_row; + } + if ( ptr->pre_zero ) { + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF( JBLOCK ); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while ( undef_row < end_row ) { + jzero_far( (void FAR *) ptr->mem_buffer[undef_row], bytesperrow ); + undef_row++; + } + } else { + if ( !writable ) { /* reader looking at undefined data */ + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + } + } + /* Flag the buffer dirty if caller will write in it */ + if ( writable ) { + ptr->dirty = TRUE; + } + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + ( start_row - ptr->cur_start_row ); +} + + +/* + * Release all objects belonging to a specified pool. + */ + +METHODDEF void +free_pool( j_common_ptr cinfo, int pool_id ) { + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + size_t space_freed; + + if ( pool_id < 0 || pool_id >= JPOOL_NUMPOOLS ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); /* safety check */ + + } +#ifdef MEM_STATS + if ( cinfo->err->trace_level > 1 ) { + print_mem_stats( cinfo, pool_id ); /* print pool's memory usage statistics */ + } +#endif + + /* If freeing IMAGE pool, close any virtual arrays first */ + if ( pool_id == JPOOL_IMAGE ) { + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + for ( sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next ) { + if ( sptr->b_s_open ) { /* there may be no backing store */ + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + ( *sptr->b_s_info.close_backing_store )( cinfo, &sptr->b_s_info ); + } + } + mem->virt_sarray_list = NULL; + for ( bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next ) { + if ( bptr->b_s_open ) { /* there may be no backing store */ + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + ( *bptr->b_s_info.close_backing_store )( cinfo, &bptr->b_s_info ); + } + } + mem->virt_barray_list = NULL; + } + + /* Release large objects */ + lhdr_ptr = mem->large_list[pool_id]; + mem->large_list[pool_id] = NULL; + + while ( lhdr_ptr != NULL ) { + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + space_freed = lhdr_ptr->hdr.bytes_used + + lhdr_ptr->hdr.bytes_left + + SIZEOF( large_pool_hdr ); + jpeg_free_large( cinfo, (void FAR *) lhdr_ptr, space_freed ); + mem->total_space_allocated -= space_freed; + lhdr_ptr = next_lhdr_ptr; + } + + /* Release small objects */ + shdr_ptr = mem->small_list[pool_id]; + mem->small_list[pool_id] = NULL; + + while ( shdr_ptr != NULL ) { + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + space_freed = shdr_ptr->hdr.bytes_used + + shdr_ptr->hdr.bytes_left + + SIZEOF( small_pool_hdr ); + jpeg_free_small( cinfo, (void *) shdr_ptr, space_freed ); + mem->total_space_allocated -= space_freed; + shdr_ptr = next_shdr_ptr; + } +} + + +/* + * Close up shop entirely. + * Note that this cannot be called unless cinfo->mem is non-NULL. + */ + +METHODDEF void +self_destruct( j_common_ptr cinfo ) { + int pool; + + /* Close all backing store, release all memory. + * Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for ( pool = JPOOL_NUMPOOLS - 1; pool >= JPOOL_PERMANENT; pool-- ) { + free_pool( cinfo, pool ); + } + + /* Release the memory manager control block too. */ + jpeg_free_small( cinfo, (void *) cinfo->mem, SIZEOF( my_memory_mgr ) ); + cinfo->mem = NULL; /* ensures I will be called only once */ + + jpeg_mem_term( cinfo ); /* system-dependent cleanup */ +} + + +/* + * Memory manager initialization. + * When this is called, only the error manager pointer is valid in cinfo! + */ + +GLOBAL void +jinit_memory_mgr( j_common_ptr cinfo ) { + my_mem_ptr mem; + long max_to_use; + int pool; + size_t test_mac; + + cinfo->mem = NULL; /* for safety if init fails */ + + /* Check for configuration errors. + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + * doesn't reflect any real hardware alignment requirement. + * The test is a little tricky: for X>0, X and X-1 have no one-bits + * in common if and only if X is a power of 2, ie has only one one-bit. + * Some compilers may give an "unreachable code" warning here; ignore it. + */ + if ( ( SIZEOF( ALIGN_TYPE ) & ( SIZEOF( ALIGN_TYPE ) - 1 ) ) != 0 ) { + ERREXIT( cinfo, JERR_BAD_ALIGN_TYPE ); + } + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + * a multiple of SIZEOF(ALIGN_TYPE). + * Again, an "unreachable code" warning may be ignored here. + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + */ + test_mac = (size_t) MAX_ALLOC_CHUNK; + if ( (long) test_mac != MAX_ALLOC_CHUNK || + ( MAX_ALLOC_CHUNK % SIZEOF( ALIGN_TYPE ) ) != 0 ) { + ERREXIT( cinfo, JERR_BAD_ALLOC_CHUNK ); + } + + max_to_use = jpeg_mem_init( cinfo ); /* system-dependent initialization */ + + /* Attempt to allocate memory manager's control block */ + mem = (my_mem_ptr) jpeg_get_small( cinfo, SIZEOF( my_memory_mgr ) ); + + if ( mem == NULL ) { + jpeg_mem_term( cinfo ); /* system-dependent cleanup */ + ERREXIT1( cinfo, JERR_OUT_OF_MEMORY, 0 ); + } + + /* OK, fill in the method pointers */ + mem->pub.alloc_small = alloc_small; + mem->pub.alloc_large = alloc_large; + mem->pub.alloc_sarray = alloc_sarray; + mem->pub.alloc_barray = alloc_barray; + mem->pub.request_virt_sarray = request_virt_sarray; + mem->pub.request_virt_barray = request_virt_barray; + mem->pub.realize_virt_arrays = realize_virt_arrays; + mem->pub.access_virt_sarray = access_virt_sarray; + mem->pub.access_virt_barray = access_virt_barray; + mem->pub.free_pool = free_pool; + mem->pub.self_destruct = self_destruct; + + /* Initialize working state */ + mem->pub.max_memory_to_use = max_to_use; + + for ( pool = JPOOL_NUMPOOLS - 1; pool >= JPOOL_PERMANENT; pool-- ) { + mem->small_list[pool] = NULL; + mem->large_list[pool] = NULL; + } + mem->virt_sarray_list = NULL; + mem->virt_barray_list = NULL; + + mem->total_space_allocated = SIZEOF( my_memory_mgr ); + + /* Declare ourselves open for business */ + cinfo->mem = &mem->pub; + + /* Check for an environment variable JPEGMEM; if found, override the + * default max_memory setting from jpeg_mem_init. Note that the + * surrounding application may again override this value. + * If your system doesn't support getenv(), define NO_GETENV to disable + * this feature. + */ +#ifndef NO_GETENV + { char * memenv; + + if ( ( memenv = getenv( "JPEGMEM" ) ) != NULL ) { + char ch = 'x'; + + if ( sscanf( memenv, "%ld%c", &max_to_use, &ch ) > 0 ) { + if ( ch == 'm' || ch == 'M' ) { + max_to_use *= 1000L; + } + mem->pub.max_memory_to_use = max_to_use * 1000L; + } + } + } +#endif + +} diff --git a/src/jpeg-6/jmemnobs.c b/src/jpeg-6/jmemnobs.c new file mode 100644 index 0000000..aaca95e --- /dev/null +++ b/src/jpeg-6/jmemnobs.c @@ -0,0 +1,97 @@ +/* + * jmemnobs.c + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a really simple implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that no backing-store files are needed: all required space + * can be obtained from ri.Z_Malloc(). + * This is very portable in the sense that it'll compile on almost anything, + * but you'd better have lots of main memory (or virtual memory) if you want + * to process big images. + * Note that the max_memory_to_use option is ignored by this implementation. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#include "../renderer/tr_local.h" + +/* + * Memory allocation and ri.Freeing are controlled by the regular library + * routines ri.Malloc() and ri.Free(). + */ + +GLOBAL void * +jpeg_get_small( j_common_ptr cinfo, size_t sizeofobject ) { + return (void *) ri.Z_Malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_small( j_common_ptr cinfo, void * object, size_t sizeofobject ) { + ri.Free( object ); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL void FAR * +jpeg_get_large( j_common_ptr cinfo, size_t sizeofobject ) { + return (void FAR *) ri.Z_Malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_large( j_common_ptr cinfo, void FAR * object, size_t sizeofobject ) { + ri.Free( object ); +} + + +/* + * This routine computes the total memory space available for allocation. + * Here we always say, "we got all you want bud!" + */ + +GLOBAL long +jpeg_mem_available( j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated ) { + return max_bytes_needed; +} + + +/* + * Backing store (temporary file) management. + * Since jpeg_mem_available always promised the moon, + * this should never be called and we can just error out. + */ + +GLOBAL void +jpeg_open_backing_store( j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed ) { + ERREXIT( cinfo, JERR_NO_BACKING_STORE ); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. Here, there isn't any. + */ + +GLOBAL long +jpeg_mem_init( j_common_ptr cinfo ) { + return 0; /* just set max_memory_to_use to 0 */ +} + +GLOBAL void +jpeg_mem_term( j_common_ptr cinfo ) { + /* no work */ +} diff --git a/src/jpeg-6/jmemsys.h b/src/jpeg-6/jmemsys.h new file mode 100644 index 0000000..4b0ed2b --- /dev/null +++ b/src/jpeg-6/jmemsys.h @@ -0,0 +1,182 @@ +/* + * jmemsys.h + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file defines the interface between the system-independent + * and system-dependent portions of the JPEG memory manager. No other + * modules need include it. (The system-independent portion is jmemmgr.c; + * there are several different versions of the system-dependent portion.) + * + * This file works as-is for the system-dependent memory managers supplied + * in the IJG distribution. You may need to modify it if you write a + * custom memory manager. If system-dependent changes are needed in + * this file, the best method is to #ifdef them based on a configuration + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * These two functions are used to allocate and release small chunks of + * memory. (Typically the total amount requested through jpeg_get_small is + * no more than 20K or so; this will be requested in chunks of a few K each.) + * Behavior should be the same as for the standard library functions malloc + * and free; in particular, jpeg_get_small must return NULL on failure. + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + * size of the object being freed, just in case it's needed. + * On an 80x86 machine using small-data memory model, these manage near heap. + */ + +EXTERN void * jpeg_get_small JPP( ( j_common_ptr cinfo, size_t sizeofobject ) ); +EXTERN void jpeg_free_small JPP( ( j_common_ptr cinfo, void * object, + size_t sizeofobject ) ); + +/* + * These two functions are used to allocate and release large chunks of + * memory (up to the total free space designated by jpeg_mem_available). + * The interface is the same as above, except that on an 80x86 machine, + * far pointers are used. On most other machines these are identical to + * the jpeg_get/free_small routines; but we keep them separate anyway, + * in case a different allocation strategy is desirable for large chunks. + */ + +EXTERN void FAR * jpeg_get_large JPP( ( j_common_ptr cinfo,size_t sizeofobject ) ); +EXTERN void jpeg_free_large JPP( ( j_common_ptr cinfo, void FAR * object, + size_t sizeofobject ) ); + +/* + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + * matter, but that case should never come into play). This macro is needed + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + * On those machines, we expect that jconfig.h will provide a proper value. + * On machines with 32-bit flat address spaces, any large constant may be used. + * + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + * size_t and will be a multiple of sizeof(align_type). + */ + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#define MAX_ALLOC_CHUNK 1000000000L +#endif + +/* + * This routine computes the total space still available for allocation by + * jpeg_get_large. If more space than this is needed, backing store will be + * used. NOTE: any memory already allocated must not be counted. + * + * There is a minimum space requirement, corresponding to the minimum + * feasible buffer sizes; jmemmgr.c will request that much space even if + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + * all working storage in memory, is also passed in case it is useful. + * Finally, the total space already allocated is passed. If no better + * method is available, cinfo->mem->max_memory_to_use - already_allocated + * is often a suitable calculation. + * + * It is OK for jpeg_mem_available to underestimate the space available + * (that'll just lead to more backing-store access than is really necessary). + * However, an overestimate will lead to failure. Hence it's wise to subtract + * a slop factor from the true available space. 5% should be enough. + * + * On machines with lots of virtual memory, any large constant may be returned. + * Conversely, zero may be returned to always use the minimum amount of memory. + */ + +EXTERN long jpeg_mem_available JPP( ( j_common_ptr cinfo, + long min_bytes_needed, + long max_bytes_needed, + long already_allocated ) ); + + +/* + * This structure holds whatever state is needed to access a single + * backing-store object. The read/write/close method pointers are called + * by jmemmgr.c to manipulate the backing-store object; all other fields + * are private to the system-dependent backing store routines. + */ + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ + +typedef union { + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ +} handle_union; + +#endif /* USE_MSDOS_MEMMGR */ + +typedef struct backing_store_struct * backing_store_ptr; + +typedef struct backing_store_struct { + /* Methods for reading/writing/closing this backing-store object */ + JMETHOD( void, read_backing_store, ( j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) ); + JMETHOD( void, write_backing_store, ( j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) ); + JMETHOD( void, close_backing_store, ( j_common_ptr cinfo, + backing_store_ptr info ) ); + + /* Private fields for system-dependent backing-store management */ +#ifdef USE_MSDOS_MEMMGR + /* For the MS-DOS manager (jmemdos.c), we need: */ + handle_union handle; /* reference to backing-store storage object */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else + /* For a typical implementation with temp files, we need: */ + FILE * temp_file; /* stdio reference to temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ +#endif +} backing_store_info; + +/* + * Initial opening of a backing-store object. This must fill in the + * read/write/close pointers in the object. The read/write routines + * may take an error exit if the specified maximum file size is exceeded. + * (If jpeg_mem_available always returns a large value, this routine can + * just take an error exit.) + */ + +EXTERN void jpeg_open_backing_store JPP( ( j_common_ptr cinfo, + backing_store_ptr info, + long total_bytes_needed ) ); + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. jpeg_mem_init will be called before anything is + * allocated (and, therefore, nothing in cinfo is of use except the error + * manager pointer). It should return a suitable default value for + * max_memory_to_use; this may subsequently be overridden by the surrounding + * application. (Note that max_memory_to_use is only important if + * jpeg_mem_available chooses to consult it ... no one else will.) + * jpeg_mem_term may assume that all requested memory has been freed and that + * all opened backing-store objects have been closed. + */ + +EXTERN long jpeg_mem_init JPP( (j_common_ptr cinfo) ); +EXTERN void jpeg_mem_term JPP( (j_common_ptr cinfo) ); diff --git a/src/jpeg-6/jmorecfg.h b/src/jpeg-6/jmorecfg.h new file mode 100644 index 0000000..a088112 --- /dev/null +++ b/src/jpeg-6/jmorecfg.h @@ -0,0 +1,348 @@ +/* + * jmorecfg.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of the JPEG spec, set this to 255. However, darn + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE( value ) ( (int) ( value ) ) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef CHAR_IS_UNSIGNED +#define GETJSAMPLE( value ) ( (int) ( value ) ) +#else +#define GETJSAMPLE( value ) ( (int) ( value ) & 0xFF ) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE( value ) ( (int) ( value ) ) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET( value ) ( value ) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef CHAR_IS_UNSIGNED +#define GETJOCTET( value ) ( value ) +#else +#define GETJOCTET( value ) ( ( value ) & 0xFF ) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef CHAR_IS_UNSIGNED +typedef char UINT8; +#else /* not CHAR_IS_UNSIGNED */ +typedef short UINT8; +#endif /* CHAR_IS_UNSIGNED */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +typedef long INT32; + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. */ + +//#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +//typedef long INT32; +//#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These defines are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +#define METHODDEF static /* a function called through method pointers */ +#define LOCAL static /* a function used only in its module */ +#define GLOBAL /* a function referenced thru EXTERNs */ +#define EXTERN extern /* a reference to a GLOBAL function */ + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + * by just saying "FAR *" where such a pointer is needed. In a few places + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + */ + +#ifdef NEED_FAR_POINTERS +#undef FAR +#define FAR far +#else +#undef FAR +#define FAR +#endif + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +//#ifndef HAVE_BOOLEAN +//typedef int boolean; +//#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Arithmetic coding is unsupported for legal reasons. Complaints to IBM. */ + +/* Capability options common to encoder and decoder: */ + +#undef DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#undef DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#undef C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#undef D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#undef D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#undef BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#undef IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#undef UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#undef QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#undef QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * Ordering of RGB data in scanlines passed to or from the application. + * If your application wants to deal with data in the order B,G,R, just + * change these macros. You can also deal with formats such as R,G,B,X + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + * the offsets will also change the order in which colormap data is organized. + * RESTRICTIONS: + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + * is not 3 (they don't understand about dummy color components!). So you + * can't use color quantization if you change that value. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 4 /* JSAMPLEs per RGB scanline element */ + + +/* Definitions for speed-related optimizations. */ + + +/* If your compiler supports inline functions, define INLINE + * as the inline keyword; otherwise define it as empty. + */ + +#ifndef INLINE +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#define INLINE __inline__ +#endif +#ifndef INLINE +#define INLINE /* default is to define it as empty */ +#endif +#endif + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#define MULTIPLIER int /* type for fastest integer multiply */ +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + * Typically, float is faster in ANSI C compilers, while double is faster in + * pre-ANSI compilers (because they insist on converting to double anyway). + * The code below therefore chooses float if we have ANSI-style prototypes. + */ + +#ifndef FAST_FLOAT +#ifdef HAVE_PROTOTYPES +#define FAST_FLOAT float +#else +#define FAST_FLOAT double +#endif +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/src/jpeg-6/jpegint.h b/src/jpeg-6/jpegint.h new file mode 100644 index 0000000..512e652 --- /dev/null +++ b/src/jpeg-6/jpegint.h @@ -0,0 +1,388 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD( void, prepare_for_pass, (j_compress_ptr cinfo) ); + JMETHOD( void, pass_startup, (j_compress_ptr cinfo) ); + JMETHOD( void, finish_pass, (j_compress_ptr cinfo) ); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD( void, start_pass, ( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) ); + JMETHOD( void, process_data, ( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail ) ); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD( void, start_pass, ( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) ); + JMETHOD( void, pre_process_data, ( j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail ) ); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD( void, start_pass, ( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) ); + JMETHOD( boolean, compress_data, ( j_compress_ptr cinfo, + JSAMPIMAGE input_buf ) ); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD( void, start_pass, (j_compress_ptr cinfo) ); + JMETHOD( void, color_convert, ( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) ); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD( void, start_pass, (j_compress_ptr cinfo) ); + JMETHOD( void, downsample, ( j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index ) ); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD( void, start_pass, (j_compress_ptr cinfo) ); + /* perhaps this should be an array??? */ + JMETHOD( void, forward_DCT, ( j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks ) ); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD( void, start_pass, ( j_compress_ptr cinfo, boolean gather_statistics ) ); + JMETHOD( boolean, encode_mcu, ( j_compress_ptr cinfo, JBLOCKROW *MCU_data ) ); + JMETHOD( void, finish_pass, (j_compress_ptr cinfo) ); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + /* write_any_marker is exported for use by applications */ + /* Probably only COM and APPn markers should be written */ + JMETHOD( void, write_any_marker, ( j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen ) ); + JMETHOD( void, write_file_header, (j_compress_ptr cinfo) ); + JMETHOD( void, write_frame_header, (j_compress_ptr cinfo) ); + JMETHOD( void, write_scan_header, (j_compress_ptr cinfo) ); + JMETHOD( void, write_file_trailer, (j_compress_ptr cinfo) ); + JMETHOD( void, write_tables_only, (j_compress_ptr cinfo) ); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD( void, prepare_for_output_pass, (j_decompress_ptr cinfo) ); + JMETHOD( void, finish_output_pass, (j_decompress_ptr cinfo) ); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD( int, consume_input, (j_decompress_ptr cinfo) ); + JMETHOD( void, reset_input_controller, (j_decompress_ptr cinfo) ); + JMETHOD( void, start_input_pass, (j_decompress_ptr cinfo) ); + JMETHOD( void, finish_input_pass, (j_decompress_ptr cinfo) ); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD( void, start_pass, ( j_decompress_ptr cinfo, J_BUF_MODE pass_mode ) ); + JMETHOD( void, process_data, ( j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) ); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD( void, start_input_pass, (j_decompress_ptr cinfo) ); + JMETHOD( int, consume_data, (j_decompress_ptr cinfo) ); + JMETHOD( void, start_output_pass, (j_decompress_ptr cinfo) ); + JMETHOD( int, decompress_data, ( j_decompress_ptr cinfo, + JSAMPIMAGE output_buf ) ); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD( void, start_pass, ( j_decompress_ptr cinfo, J_BUF_MODE pass_mode ) ); + JMETHOD( void, post_process_data, ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) ); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD( void, reset_marker_reader, (j_decompress_ptr cinfo) ); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD( int, read_markers, (j_decompress_ptr cinfo) ); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + /* Application-overridable marker processing methods */ + jpeg_marker_parser_method process_COM; + jpeg_marker_parser_method process_APPn[16]; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD( void, start_pass, (j_decompress_ptr cinfo) ); + JMETHOD( boolean, decode_mcu, ( j_decompress_ptr cinfo, + JBLOCKROW *MCU_data ) ); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD ( void, inverse_DCT_method_ptr, + ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col ) ); + +struct jpeg_inverse_dct { + JMETHOD( void, start_pass, (j_decompress_ptr cinfo) ); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD( void, start_pass, (j_decompress_ptr cinfo) ); + JMETHOD( void, upsample, ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail ) ); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD( void, start_pass, (j_decompress_ptr cinfo) ); + JMETHOD( void, color_convert, ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) ); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD( void, start_pass, ( j_decompress_ptr cinfo, boolean is_pre_scan ) ); + JMETHOD( void, color_quantize, ( j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows ) ); + JMETHOD( void, finish_pass, (j_decompress_ptr cinfo) ); + JMETHOD( void, new_color_map, (j_decompress_ptr cinfo) ); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX( a,b ) ( ( a ) > ( b ) ? ( a ) : ( b ) ) +#undef MIN +#define MIN( a,b ) ( ( a ) < ( b ) ? ( a ) : ( b ) ) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT( x,shft ) \ + ( ( shift_temp = ( x ) ) < 0 ? \ + ( shift_temp >> ( shft ) ) | ( ( ~( (INT32) 0 ) ) << ( 32 - ( shft ) ) ) : \ + ( shift_temp >> ( shft ) ) ) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT( x,shft ) ( ( x ) >> ( shft ) ) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN void jinit_compress_master JPP( (j_compress_ptr cinfo) ); +EXTERN void jinit_c_master_control JPP( ( j_compress_ptr cinfo, + boolean transcode_only ) ); +EXTERN void jinit_c_main_controller JPP( ( j_compress_ptr cinfo, + boolean need_full_buffer ) ); +EXTERN void jinit_c_prep_controller JPP( ( j_compress_ptr cinfo, + boolean need_full_buffer ) ); +EXTERN void jinit_c_coef_controller JPP( ( j_compress_ptr cinfo, + boolean need_full_buffer ) ); +EXTERN void jinit_color_converter JPP( (j_compress_ptr cinfo) ); +EXTERN void jinit_downsampler JPP( (j_compress_ptr cinfo) ); +EXTERN void jinit_forward_dct JPP( (j_compress_ptr cinfo) ); +EXTERN void jinit_huff_encoder JPP( (j_compress_ptr cinfo) ); +EXTERN void jinit_phuff_encoder JPP( (j_compress_ptr cinfo) ); +EXTERN void jinit_marker_writer JPP( (j_compress_ptr cinfo) ); +/* Decompression module initialization routines */ +EXTERN void jinit_master_decompress JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_d_main_controller JPP( ( j_decompress_ptr cinfo, + boolean need_full_buffer ) ); +EXTERN void jinit_d_coef_controller JPP( ( j_decompress_ptr cinfo, + boolean need_full_buffer ) ); +EXTERN void jinit_d_post_controller JPP( ( j_decompress_ptr cinfo, + boolean need_full_buffer ) ); +EXTERN void jinit_input_controller JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_marker_reader JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_huff_decoder JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_phuff_decoder JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_inverse_dct JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_upsampler JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_color_deconverter JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_1pass_quantizer JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_2pass_quantizer JPP( (j_decompress_ptr cinfo) ); +EXTERN void jinit_merged_upsampler JPP( (j_decompress_ptr cinfo) ); +/* Memory manager initialization */ +EXTERN void jinit_memory_mgr JPP( (j_common_ptr cinfo) ); + +/* Utility routines in jutils.c */ +EXTERN long jdiv_round_up JPP( ( long a, long b ) ); +EXTERN long jround_up JPP( ( long a, long b ) ); +EXTERN void jcopy_sample_rows JPP( ( JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols ) ); +EXTERN void jcopy_block_row JPP( ( JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks ) ); +EXTERN void jzero_far JPP( ( void FAR * target, size_t bytestozero ) ); +/* Constant tables in jutils.c */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/src/jpeg-6/jpeglib.h b/src/jpeg-6/jpeglib.h new file mode 100644 index 0000000..6c48f1d --- /dev/null +++ b/src/jpeg-6/jpeglib.h @@ -0,0 +1,1051 @@ +/* + * jpeglib.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +typedef unsigned char boolean; +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "../jpeg-6/jconfig.h" /* widely used configuration options */ +#endif +#include "../jpeg-6/jmorecfg.h" /* seldom changed options */ + + +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ + +#define JPEG_LIB_VERSION 60 /* Version 6 */ + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD( type,methodname,arglist ) type( *methodname ) arglist +#else +#define JMETHOD( type,methodname,arglist ) type ( *methodname )() +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This field directly represents the contents of a JPEG DQT marker. + * Note: the values are always given in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is not currently used by the compressor. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */ \ + struct jpeg_memory_mgr * mem; /* Memory manager module */ \ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */ \ + boolean is_decompressor; /* so common code can tell which is which */ \ + int global_state /* for checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant; /* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int(*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker: */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD( void, error_exit, (j_common_ptr cinfo) ); + /* Conditionally emit a trace or warning message */ + JMETHOD( void, emit_message, ( j_common_ptr cinfo, int msg_level ) ); + /* Routine that actually outputs a trace or error message */ + JMETHOD( void, output_message, (j_common_ptr cinfo) ); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD( void, format_message, ( j_common_ptr cinfo, char * buffer ) ); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD( void, reset_error_mgr, (j_common_ptr cinfo) ); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD( void, progress_monitor, (j_common_ptr cinfo) ); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD( void, init_destination, (j_compress_ptr cinfo) ); + JMETHOD( boolean, empty_output_buffer, (j_compress_ptr cinfo) ); + JMETHOD( void, term_destination, (j_compress_ptr cinfo) ); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD( void, init_source, (j_decompress_ptr cinfo) ); + JMETHOD( boolean, fill_input_buffer, (j_decompress_ptr cinfo) ); + JMETHOD( void, skip_input_data, ( j_decompress_ptr cinfo, long num_bytes ) ); + JMETHOD( boolean, resync_to_restart, ( j_decompress_ptr cinfo, int desired ) ); + JMETHOD( void, term_source, (j_decompress_ptr cinfo) ); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD( void *, alloc_small, ( j_common_ptr cinfo, int pool_id, + size_t sizeofobject ) ); + JMETHOD( void FAR *, alloc_large, ( j_common_ptr cinfo, int pool_id, + size_t sizeofobject ) ); + JMETHOD( JSAMPARRAY, alloc_sarray, ( j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows ) ); + JMETHOD( JBLOCKARRAY, alloc_barray, ( j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows ) ); + JMETHOD( jvirt_sarray_ptr, request_virt_sarray, ( j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess ) ); + JMETHOD( jvirt_barray_ptr, request_virt_barray, ( j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess ) ); + JMETHOD( void, realize_virt_arrays, (j_common_ptr cinfo) ); + JMETHOD( JSAMPARRAY, access_virt_sarray, ( j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable ) ); + JMETHOD( JBLOCKARRAY, access_virt_barray, ( j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable ) ); + JMETHOD( void, free_pool, ( j_common_ptr cinfo, int pool_id ) ); + JMETHOD( void, self_destruct, (j_common_ptr cinfo) ); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD ( boolean, jpeg_marker_parser_method, ( j_decompress_ptr cinfo ) ); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP( arglist ) arglist +#else +#define JPP( arglist ) ( ) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_create_compress jCreaCompress +#define jpeg_create_decompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN struct jpeg_error_mgr *jpeg_std_error JPP( (struct jpeg_error_mgr *err) ); + +/* Initialization and destruction of JPEG compression objects */ +/* NB: you must set up the error-manager BEFORE calling jpeg_create_xxx */ +EXTERN void jpeg_create_compress JPP( (j_compress_ptr cinfo) ); +EXTERN void jpeg_create_decompress JPP( (j_decompress_ptr cinfo) ); +EXTERN void jpeg_destroy_compress JPP( (j_compress_ptr cinfo) ); +EXTERN void jpeg_destroy_decompress JPP( (j_decompress_ptr cinfo) ); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN void jpeg_stdio_dest JPP( ( j_compress_ptr cinfo, FILE * outfile ) ); +EXTERN void jpeg_stdio_src JPP( ( j_decompress_ptr cinfo, unsigned char *infile ) ); + +/* Default parameter setup for compression */ +EXTERN void jpeg_set_defaults JPP( (j_compress_ptr cinfo) ); +/* Compression parameter setup aids */ +EXTERN void jpeg_set_colorspace JPP( ( j_compress_ptr cinfo, + J_COLOR_SPACE colorspace ) ); +EXTERN void jpeg_default_colorspace JPP( (j_compress_ptr cinfo) ); +EXTERN void jpeg_set_quality JPP( ( j_compress_ptr cinfo, int quality, + boolean force_baseline ) ); +EXTERN void jpeg_set_linear_quality JPP( ( j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline ) ); +EXTERN void jpeg_add_quant_table JPP( ( j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline ) ); +EXTERN int jpeg_quality_scaling JPP( (int quality) ); +EXTERN void jpeg_simple_progression JPP( (j_compress_ptr cinfo) ); +EXTERN void jpeg_suppress_tables JPP( ( j_compress_ptr cinfo, + boolean suppress ) ); +EXTERN JQUANT_TBL * jpeg_alloc_quant_table JPP( (j_common_ptr cinfo) ); +EXTERN JHUFF_TBL * jpeg_alloc_huff_table JPP( (j_common_ptr cinfo) ); + +/* Main entry points for compression */ +EXTERN void jpeg_start_compress JPP( ( j_compress_ptr cinfo, + boolean write_all_tables ) ); +EXTERN JDIMENSION jpeg_write_scanlines JPP( ( j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines ) ); +EXTERN void jpeg_finish_compress JPP( (j_compress_ptr cinfo) ); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN JDIMENSION jpeg_write_raw_data JPP( ( j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines ) ); + +/* Write a special marker. See libjpeg.doc concerning safe usage. */ +EXTERN void jpeg_write_marker JPP( ( j_compress_ptr cinfo, int marker, + const JOCTET * dataptr, unsigned int datalen ) ); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN void jpeg_write_tables JPP( (j_compress_ptr cinfo) ); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN int jpeg_read_header JPP( ( j_decompress_ptr cinfo, + boolean require_image ) ); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN boolean jpeg_start_decompress JPP( (j_decompress_ptr cinfo) ); +EXTERN JDIMENSION jpeg_read_scanlines JPP( ( j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines ) ); +EXTERN boolean jpeg_finish_decompress JPP( (j_decompress_ptr cinfo) ); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN JDIMENSION jpeg_read_raw_data JPP( ( j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines ) ); + +/* Additional entry points for buffered-image mode. */ +EXTERN boolean jpeg_has_multiple_scans JPP( (j_decompress_ptr cinfo) ); +EXTERN boolean jpeg_start_output JPP( ( j_decompress_ptr cinfo, + int scan_number ) ); +EXTERN boolean jpeg_finish_output JPP( (j_decompress_ptr cinfo) ); +EXTERN boolean jpeg_input_complete JPP( (j_decompress_ptr cinfo) ); +EXTERN void jpeg_new_colormap JPP( (j_decompress_ptr cinfo) ); +EXTERN int jpeg_consume_input JPP( (j_decompress_ptr cinfo) ); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN void jpeg_calc_output_dimensions JPP( (j_decompress_ptr cinfo) ); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN void jpeg_set_marker_processor JPP( ( j_decompress_ptr cinfo, + int marker_code, + jpeg_marker_parser_method routine ) ); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN jvirt_barray_ptr * jpeg_read_coefficients JPP( (j_decompress_ptr cinfo) ); +EXTERN void jpeg_write_coefficients JPP( ( j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays ) ); +EXTERN void jpeg_copy_critical_parameters JPP( ( j_decompress_ptr srcinfo, + j_compress_ptr dstinfo ) ); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN void jpeg_abort_compress JPP( (j_compress_ptr cinfo) ); +EXTERN void jpeg_abort_decompress JPP( (j_decompress_ptr cinfo) ); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN void jpeg_abort JPP( (j_common_ptr cinfo) ); +EXTERN void jpeg_destroy JPP( (j_common_ptr cinfo) ); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN boolean jpeg_resync_to_restart JPP( ( j_decompress_ptr cinfo, + int desired ) ); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "../jpeg-6/jpegint.h" /* fetch private declarations */ +#include "../jpeg-6/jerror.h" /* fetch error codes too */ +#endif + +#endif /* JPEGLIB_H */ diff --git a/src/jpeg-6/jutils.c b/src/jpeg-6/jutils.c new file mode 100644 index 0000000..8808b82 --- /dev/null +++ b/src/jpeg-6/jutils.c @@ -0,0 +1,170 @@ +/* + * jutils.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains tables and miscellaneous utility routines needed + * for both compression and decompression. + * Note we prefix all global names with "j" to minimize conflicts with + * a surrounding application. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + * of a DCT block read in natural order (left to right, top to bottom). + */ + +const int jpeg_zigzag_order[DCTSIZE2] = { + 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 +}; + +/* + * jpeg_natural_order[i] is the natural-order position of the i'th element + * of zigzag order. + * + * When reading corrupted data, the Huffman decoders could attempt + * to reference an entry beyond the end of this array (if the decoded + * zero run length reaches past the end of the block). To prevent + * wild stores without adding an inner-loop test, we put some extra + * "63"s after the real entries. This will cause the extra coefficient + * to be stored in location 63 of the block, not somewhere random. + * The worst case would be a run-length of 15, which means we need 16 + * fake entries. + */ + +const int jpeg_natural_order[DCTSIZE2 + 16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + + +/* + * Arithmetic utilities + */ + +GLOBAL long +jdiv_round_up( long a, long b ) { +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ + return ( a + b - 1L ) / b; +} + + +GLOBAL long +jround_up( long a, long b ) { +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ +/* Assumes a >= 0, b > 0 */ + a += b - 1L; + return a - ( a % b ); +} + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines below do it the hard way. (The performance cost + * is not all that great, because these routines aren't very heavily used.) + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define FMEMCOPY( dest,src,size ) MEMCOPY( dest,src,size ) +#define FMEMZERO( target,size ) MEMZERO( target,size ) +#else /* 80x86 case, define if we can */ +#ifdef USE_FMEM +#define FMEMCOPY( dest,src,size ) _fmemcpy( (void FAR *)( dest ), (const void FAR *)( src ), (size_t)( size ) ) +#define FMEMZERO( target,size ) _fmemset( (void FAR *)( target ), 0, (size_t)( size ) ) +#endif +#endif + + +GLOBAL void +jcopy_sample_rows( JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols ) { +/* Copy some rows of samples from one place to another. + * num_rows rows are copied from input_array[source_row++] + * to output_array[dest_row++]; these areas may overlap for duplication. + * The source and destination arrays must be at least as wide as num_cols. + */ + register JSAMPROW inptr, outptr; +#ifdef FMEMCOPY + register size_t count = (size_t) ( num_cols * SIZEOF( JSAMPLE ) ); +#else + register JDIMENSION count; +#endif + register int row; + + input_array += source_row; + output_array += dest_row; + + for ( row = num_rows; row > 0; row-- ) { + inptr = *input_array++; + outptr = *output_array++; +#ifdef FMEMCOPY + FMEMCOPY( outptr, inptr, count ); +#else + for ( count = num_cols; count > 0; count-- ) + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ +#endif + } +} + + +GLOBAL void +jcopy_block_row( JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks ) { +/* Copy a row of coefficient blocks from one place to another. */ +#ifdef FMEMCOPY + FMEMCOPY( output_row, input_row, num_blocks * ( DCTSIZE2 * SIZEOF( JCOEF ) ) ); +#else + register JCOEFPTR inptr, outptr; + register long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for ( count = (long) num_blocks * DCTSIZE2; count > 0; count-- ) { + *outptr++ = *inptr++; + } +#endif +} + + +GLOBAL void +jzero_far( void FAR * target, size_t bytestozero ) { +/* Zero out a chunk of FAR memory. */ +/* This might be sample-array data, block-array data, or alloc_large data. */ +#ifdef FMEMZERO + FMEMZERO( target, bytestozero ); +#else + register char FAR * ptr = (char FAR *) target; + register size_t count; + + for ( count = bytestozero; count > 0; count-- ) { + *ptr++ = 0; + } +#endif +} diff --git a/src/jpeg-6/jversion.h b/src/jpeg-6/jversion.h new file mode 100644 index 0000000..a60d876 --- /dev/null +++ b/src/jpeg-6/jversion.h @@ -0,0 +1,14 @@ +/* + * jversion.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains software version identification. + */ + + +#define JVERSION "6 2-Aug-95" + +#define JCOPYRIGHT "Copyright (C) 1995, Thomas G. Lane" diff --git a/src/macosx/BuildRelease b/src/macosx/BuildRelease new file mode 100644 index 0000000..b38e82d --- /dev/null +++ b/src/macosx/BuildRelease @@ -0,0 +1,17 @@ +#!/bin/zsh + +APPNAME="Quake3" +PACKAGENAME= Quake3 + +(cd $OMNI_SOURCE_ROOT; ./Build Quake3 install) + +rm -rf "/tmp/$APPNAME" +mkdir "/tmp/$APPNAME" +cp "Read Me.rtf" "/tmp/$APPNAME" + +cd /Users/Shared/$USER/InstalledProducts +gnutar cf - FAKK2.app | (cd "/tmp/$APPNAME"; gnutar xf -) + +cd "/tmp/$APPNAME" +sudo ~bungi/Unix/bin/files2image $PACKAGENAME ./* + diff --git a/src/macosx/CGMouseDeltaFix.h b/src/macosx/CGMouseDeltaFix.h new file mode 100644 index 0000000..91067ed --- /dev/null +++ b/src/macosx/CGMouseDeltaFix.h @@ -0,0 +1,34 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#import + +extern void CGFix_Initialize(); + +extern void CGFix_GetLastMouseDelta( CGMouseDelta *dx, CGMouseDelta *dy ); + diff --git a/src/macosx/CGMouseDeltaFix.m b/src/macosx/CGMouseDeltaFix.m new file mode 100644 index 0000000..1c71450 --- /dev/null +++ b/src/macosx/CGMouseDeltaFix.m @@ -0,0 +1,139 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#import "CGMouseDeltaFix.h" +#import "CGPrivateAPI.h" + +#import +#import + + +// We will try to automatically fall back to using the original CGGetLastMouseDelta when we are on a system new enough to have the fix. Any version of CoreGraphics past 1.93.0 will have the fixed version. + + +static BOOL originalVersionShouldWork = YES; +static CGMouseDelta CGFix_Mouse_DeltaX, CGFix_Mouse_DeltaY; + +static void CGFix_NotificationCallback( CGSNotificationType note, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg ); + +static CGSRegisterNotifyProcType registerNotifyProc = NULL; + +void CGFix_Initialize() { + NSAutoreleasePool *pool; + NSBundle *cgBundle; + NSString *version; + NSArray *components; + + if ( registerNotifyProc ) { + // We've already been called once and have registered our callbacks. If the original version works, this will be NULL, but we'll end up doing nothing (again, possibly). + return; + } + + //NSLog(@"CGFix_Initialize\n"); + + pool = [[NSAutoreleasePool alloc] init]; + cgBundle = [NSBundle bundleWithPath: @ "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework"]; + if ( !cgBundle ) { + // If it's moved, it must be newer than what we know about and should work + //NSLog(@"No /System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework\n"); + goto done; + } + + version = [[cgBundle infoDictionary] objectForKey: @ "CFBundleShortVersionString"]; + components = [version componentsSeparatedByString: @ "."]; + //NSLog(@"version = %@\n", version); + //NSLog(@"components = %@\n", components); + + + if ([components count] < 2 ) { + // We don't understand this versioning scheme. Must have changed. + goto done; + } + + if ( ![[components objectAtIndex : 0] isEqualToString : @ "1"] || [[components objectAtIndex : 1] intValue] > 93 ) { + // This version should be new enough to work + goto done; + } + + // Look up the function pointer we need to register our callback. + if ( !NSIsSymbolNameDefined( "_CGSRegisterNotifyProc" ) ) { + //NSLog(@"No _CGSRegisterNotifyProc\n"); + goto done; + } + + registerNotifyProc = NSAddressOfSymbol( NSLookupAndBindSymbol( "_CGSRegisterNotifyProc" ) ); + //NSLog(@"registerNotifyProc = 0x%08x", registerNotifyProc); + + // Must not work if we got here + originalVersionShouldWork = NO; + + // We want to catch all the events that could possible indicate mouse movement and sum them up + registerNotifyProc( CGFix_NotificationCallback, kCGSEventNotificationMouseMoved, NULL ); + registerNotifyProc( CGFix_NotificationCallback, kCGSEventNotificationLeftMouseDragged, NULL ); + registerNotifyProc( CGFix_NotificationCallback, kCGSEventNotificationRightMouseDragged, NULL ); + registerNotifyProc( CGFix_NotificationCallback, kCGSEventNotificationNotificationOtherMouseDragged, NULL ); + +done: + [pool release]; +} + +void CGFix_GetLastMouseDelta( CGMouseDelta *dx, CGMouseDelta *dy ) { + if ( originalVersionShouldWork ) { + CGGetLastMouseDelta( dx, dy ); + return; + } + + *dx = CGFix_Mouse_DeltaX; + *dy = CGFix_Mouse_DeltaY; + + CGFix_Mouse_DeltaX = CGFix_Mouse_DeltaY = 0; +} + +static void CGFix_NotificationCallback( CGSNotificationType note, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg ) { + CGSEventRecordPtr event; + + //fprintf(stderr, "CGFix_NotificationCallback(note=%d, date=0x%08x, dataLength=%d, arg=0x%08x)\n", note, data, dataLength, arg); + +#ifdef DEBUG + if ( ( note != kCGSEventNotificationMouseMoved && + note != kCGSEventNotificationLeftMouseDragged && + note != kCGSEventNotificationRightMouseDragged && + note != kCGSEventNotificationNotificationOtherMouseDragged ) || + dataLength != sizeof( CGSEventRecord ) ) { + fprintf( stderr, "Unexpected arguments to callback function CGFix_NotificationCallback(note=%d, date=0x%08x, dataLength=%d, arg=0x%08x)\n", note, data, dataLength, arg ); + } + abort(); +} +#endif + + event = (CGSEventRecordPtr)data; + + CGFix_Mouse_DeltaX += event->data.move.deltaX; + CGFix_Mouse_DeltaY += event->data.move.deltaY; + //fprintf(stderr, " dx += %d, dy += %d\n", event->data.move.deltaX, event->data.move.deltaY); +} diff --git a/src/macosx/CGPrivateAPI.h b/src/macosx/CGPrivateAPI.h new file mode 100644 index 0000000..8225767 --- /dev/null +++ b/src/macosx/CGPrivateAPI.h @@ -0,0 +1,192 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +typedef unsigned long CGSNotificationType; +typedef void * CGSNotificationData; +typedef void * CGSNotificationArg; +typedef void * CGSWindowID; +typedef void * CGSConnectionID; + +typedef unsigned long long CGSUInt64; +typedef long long CGSInt64; +typedef unsigned long CGSUInt32; +typedef long CGSInt32; +typedef unsigned short CGSUInt16; +typedef short CGSInt16; +typedef unsigned char CGSUInt8; +typedef char CGSInt8; +typedef float CGSFloat32; + +typedef CGSUInt32 CGSByteCount; +typedef CGSUInt16 CGSEventRecordVersion; +typedef unsigned long CGSEventType; +typedef CGSUInt64 CGSEventRecordTime; /* nanosecond timer */ +typedef unsigned long CGSEventFlag; +typedef CGSUInt32 CGSError; + + +typedef union { + struct { /* For mouse events */ + CGSUInt8 subx; /* sub-pixel position for x */ + CGSUInt8 suby; /* sub-pixel position for y */ + CGSInt16 eventNum; /* unique identifier for this button */ + CGSInt32 click; /* click state of this event */ + CGSUInt8 pressure; /* pressure value: 0=none, 255=full */ + CGSInt8 _reserved1; + CGSInt16 _reserved2; + CGSInt16 deltaX; + CGSInt16 deltaY; + CGSInt32 _padding[8]; + } mouse; + struct { /* For pointer movement events */ + CGSInt16 _obsolete_deltaX; /* Revert to subX, subY, eventNum */ + CGSInt16 _obsolete_deltaY; /* for Gonzo 1H */ + CGSInt32 click; /* click state of this event */ + CGSUInt8 pressure; /* pressure value: 0=none, 255=full */ + CGSInt8 _reserved1; + CGSInt16 _reserved2; + CGSInt16 deltaX; + CGSInt16 deltaY; + CGSInt32 _padding[8]; + } move; + struct { /* For key-down and key-up events */ + CGSInt16 reserved; + CGSInt16 repeat; /* for key-down: nonzero if really a repeat */ + CGSUInt16 charSet; /* character set code */ + CGSUInt16 charCode; /* character code in that set */ + CGSUInt16 keyCode; /* device-dependent virtual key code */ + CGSInt16 keyData; /* device-dependent info */ + CGSInt16 specialKey; /* CPSSpecialKeyID if kCGSFlagsMaskSpecialKey is set */ + CGSInt16 _pad; + CGSInt32 _padding[8]; + } key; + struct { /* For mouse-entered and mouse-exited events */ + CGSInt16 reserved; + CGSInt16 eventNum; /* unique identifier from mouse down event */ + CGSInt32 trackingNum; /* unique identifier from settrackingrect */ + CGSInt32 userData; /* unCGSInt32erpreted CGSInt32eger from settrackingrect */ + CGSInt32 _padding[9]; + } tracking; + struct { /* For process-related events */ + CGSUInt16 notifyCode; /* CPSNotificationCodes in CPSProcesses.h */ + CGSUInt16 flags; /* CPSEventFlags in CPSProcesses.h */ + CGSUInt32 targetHiPSN; /* hiword of PSN */ + CGSUInt32 targetLoPSN; /* loword of PSN */ + CGSInt32 status; /* operation result */ + CGSInt32 _padding[8]; + } process; + struct { /* For scroll wheel events */ + CGSInt16 deltaAxis1; + CGSInt16 deltaAxis2; + CGSInt16 deltaAxis3; + CGSInt16 reserved1; + CGSInt32 reserved2; + CGSInt32 _padding[9]; + } scrollWheel; + struct { + CGSInt32 x; /* absolute x coordinate in tablet space at full tablet resolution */ + CGSInt32 y; /* absolute y coordinate in tablet space at full tablet resolution */ + CGSInt32 z; /* absolute z coordinate in tablet space at full tablet resolution */ + CGSUInt16 buttons; /* one bit per button - bit 0 is first button - 1 = closed */ + CGSUInt16 pressure; /* scaled pressure value; MAXPRESSURE=(2^16)-1, MINPRESSURE=0 */ + struct { + CGSInt16 x; /* scaled tilt x value; range is -((2^15)-1) to (2^15)-1 (-32767 to 32767) */ + CGSInt16 y; /* scaled tilt y value; range is -((2^15)-1) to (2^15)-1 (-32767 to 32767) */ + } tilt; + CGSUInt16 rotation; /* Fixed-point representation of device rotation in a 10.6 format */ + CGSInt16 tangentialPressure; /* tangential pressure on the device; range same as tilt */ + CGSUInt16 deviceID; /* system-assigned unique device ID - matches to deviceID field in proximity event */ + CGSInt16 vendor1; /* vendor-defined signed 16-bit integer */ + CGSInt16 vendor2; /* vendor-defined signed 16-bit integer */ + CGSInt16 vendor3; /* vendor-defined signed 16-bit integer */ + CGSInt32 _padding[4]; + } tablet; + struct { + CGSUInt16 vendorID; /* vendor-defined ID - typically will be USB vendor ID */ + CGSUInt16 tabletID; /* vendor-defined tablet ID - typically will be USB product ID for the tablet */ + CGSUInt16 pointerID; /* vendor-defined ID of the specific pointing device */ + CGSUInt16 deviceID; /* system-assigned unique device ID - matches to deviceID field in tablet event */ + CGSUInt16 systemTabletID; /* system-assigned unique tablet ID */ + CGSUInt16 vendorPointerType; /* vendor-defined pointer type */ + CGSUInt32 pointerSerialNumber; /* vendor-defined serial number of the specific pointing device */ + CGSUInt64 uniqueID; /* vendor-defined unique ID for this pointer */ + CGSUInt32 capabilityMask; /* mask representing the capabilities of the device */ + CGSUInt8 pointerType; /* type of pointing device - enum to be defined */ + CGSUInt8 enterProximity; /* non-zero = entering; zero = leaving */ + CGSInt16 reserved1; + CGSInt32 _padding[4]; + } proximity; + struct { /* For AppKit-defined, sys-defined, and app-defined events */ + CGSInt16 reserved; + CGSInt16 subtype; /* event subtype for compound events */ + union { + CGSFloat32 F[11]; /* for use in compound events */ + CGSInt32 L[11]; /* for use in compound events */ + CGSInt16 S[22]; /* for use in compound events */ + CGSInt8 C[44]; /* for use in compound events */ + } misc; + } compound; +} CGSEventRecordData; + + +struct _CGSEventRecord { + CGSEventRecordVersion major; + CGSEventRecordVersion minor; + CGSByteCount length; /* Length of complete event record */ + CGSEventType type; /* An event type from above */ + CGPoint location; /* Base coordinates (global), from upper-left */ + CGPoint windowLocation; /* Coordinates relative to window */ + CGSEventRecordTime time; /* nanoseconds since startup */ + CGSEventFlag flags; /* key state flags */ + CGSWindowID window; /* window number of assigned window */ + CGSConnectionID connection; /* connection the event came from */ + CGSEventRecordData data; /* type-dependent data: 40 bytes */ +}; +typedef struct _CGSEventRecord CGSEventRecord; +typedef CGSEventRecord *CGSEventRecordPtr; + + +typedef void ( *CGSNotifyProcPtr )( CGSNotificationType type, + CGSNotificationData data, + CGSByteCount dataLength, + CGSNotificationArg arg ); + +// Define a type for the 'CGSRegisterNotifyProc' call. Don't reference it explicitly since we don't want link errors if Apple removes this private function. +typedef CGSError ( *CGSRegisterNotifyProcType )( CGSNotifyProcPtr proc, + CGSNotificationType type, + CGSNotificationArg arg ); + + +#define kCGSEventNotificationMouseMoved ( 710 + 5 ) +#define kCGSEventNotificationLeftMouseDragged ( 710 + 6 ) +#define kCGSEventNotificationRightMouseDragged ( 710 + 7 ) +#define kCGSEventNotificationNotificationOtherMouseDragged ( 710 + 27 ) + + diff --git a/src/macosx/GenerateQGL.pl b/src/macosx/GenerateQGL.pl new file mode 100644 index 0000000..5fc8f96 --- /dev/null +++ b/src/macosx/GenerateQGL.pl @@ -0,0 +1,146 @@ +#!/usr/bin/perl + +open(INPUT_FILE, ">/tmp/input-$$.h") || die "$!"; +print INPUT_FILE "#import \n"; +close INPUT_FILE; +open(CPP, "cpp /tmp/input-$$.h|") || die "$!"; + +print "/**** This file is autogenerated. Run GenerateQGL.pl to update it ****/\n\n"; + +print "#ifdef QGL_LOG_GL_CALLS\n"; +print "extern unsigned int QGLLogGLCalls;\n"; +print "extern FILE *QGLDebugFile(void);\n"; +print "#endif\n\n"; + +print "extern void QGLCheckError(const char *message);\n"; +print "extern unsigned int QGLBeginStarted;\n\n"; +print "// This has to be done to avoid infinite recursion between our glGetError wrapper and QGLCheckError()\n"; +print "static inline GLenum _glGetError(void) {\n"; +print " return glGetError();\n"; +print "}\n\n"; + +@functionNames = (); + +while () { + chop; + /^extern/ || next; + s/extern //; + print "// $_\n"; + + # This approach is necessary to deal with glGetString whos type isn't a single word + ($type, $rest) = m/(.+)\s+(gl.*)/; +# print "type='$type'\n"; +# print "rest='$rest'\n"; + + ($name, $argString) = ($rest =~ m/(\w+).*\s*\((.*)\)/); + $isVoid = ($type =~ m/void/); + push(@functionNames, $name); + +# print "name=$name\n"; +# print "argString=$argString\n"; +# print "argCount=$#args\n"; + + # Parse the argument list into two arrays, one of types and one of argument names + if ($argString =~ m/^void$/) { + @args = (); + } else { + @args = split(",", $argString); + } + @argTypes = (); + @argNames = (); + for $arg (@args) { + ($argType, $argName) = ($arg =~ m/(.*[ \*])([_a-zA-Z0-9]+)/); + $argType =~ s/^ *//; + $argType =~ s/ *$//; + + push(@argTypes, $argType); + push(@argNames, $argName); +# print "argType='$argType'\n"; +# print "argName='$argName'\n"; + } + + + print "static inline $type q$name($argString)\n"; + print "{\n"; + + if (! $isVoid) { + print " $type returnValue;\n"; + } + + print "#if !defined(NDEBUG) && defined(QGL_LOG_GL_CALLS)\n"; + print " if (QGLLogGLCalls)\n"; + print " fprintf(QGLDebugFile(), \"$name("; + + if ($#argTypes >= 0) { + for ($i = 0; $i <= $#argTypes; $i++) { + $argType = $argTypes[$i]; + $argName = $argNames[$i]; + $_ = $argType; + if (/^GLenum$/ || /^GLuint$/ || /^GLbitfield$/) { + print "$argName=%lu"; + } elsif (/^GLsizei$/ || /^GLint$/) { + print "$argName=%ld"; + } elsif (/^GLfloat$/ || /^GLdouble$/ || /^GLclampf$/ || /^GLclampd$/) { + print "$argName=%f"; + } elsif (/^GLbyte$/) { + print "$argName=%d"; + } elsif (/^GLubyte$/) { + print "$argName=%u"; + } elsif (/^GLshort$/) { + print "$argName=%d"; + } elsif (/^GLushort$/) { + print "$argName=%u"; + } elsif (/^GLboolean$/) { + print "$argName=%u"; + } elsif (/\*$/) { + # TJW -- Later we should look at the count specified in the function name, look at the basic type and print out an array. Or we could just special case them... + print "$argName=%p"; + } else { + print STDERR "Unknown type '$argType'\n"; + exit(1); + } + + print ", " if ($i != $#argTypes); + } + } else { + print "void"; + } + + print ")\\n\""; + print ", " if $#argTypes >= 0; + print join(", ", @argNames); + print ");\n"; + print "#endif\n"; + + if (! $isVoid) { + print " returnValue = "; + } else { + print " "; + } + print "$name(" . join(", ", @argNames) . ");\n"; + + print "#if !defined(NDEBUG) && defined(QGL_CHECK_GL_ERRORS)\n"; + if ($name eq "glBegin") { + print " QGLBeginStarted++;\n"; + } + if ($name eq "glEnd") { + print " QGLBeginStarted--;\n"; + } + print " if (!QGLBeginStarted)\n"; + print " QGLCheckError(\"$name\");\n"; + print "#endif\n"; + + if (! $isVoid) { + print " return returnValue;\n"; + } + + print "}\n\n"; +} + + +print "// Prevent calls to the 'normal' GL functions\n"; +for $name (@functionNames) { + print "#define $name CALL_THE_QGL_VERSION_OF_$name\n"; +} + + diff --git a/src/macosx/Performance.rtf b/src/macosx/Performance.rtf new file mode 100644 index 0000000..4ba0bfe --- /dev/null +++ b/src/macosx/Performance.rtf @@ -0,0 +1,114 @@ +{\rtf1\mac\ansicpg10000{\fonttbl\f0\fswiss\fcharset77 Helvetica;} +{\colortbl;\red255\green255\blue255;\red255\green0\blue16;\red255\green0\blue16;} +\paperw14240\paperh14700 +\pard\tx1440\tx2880\tx4320\tx5760\tx7200\ql\qnatural + +\f0\fs24 \cf0 \ + +\b +set timedemo 1 +demo die.dm3 +set s_initsound 0 +set r_enablerender 0 +set vm_cgame 0 +set vm_game 0\ +4865 frames, 154.3 seconds: 31.5 fps\ +\ +\ ++set timedemo 1 +demo die.dm3 +set s_initsound 0 +set r_enablerender 0 +set vm_cgame 2 +set vm_game 2\ +4865 frames, 199.8 seconds: 24.4 fps\ +\ +\ ++set timedemo 1 +demo demo001.dm3 +set s_initsound 0 +set r_enablerender 0 +set vm_cgame 0 +set vm_game 0\ +1346 frames, 10.1 seconds: 133.0 fps\ +\ +\ ++set timedemo 1 +demo demo001.dm3 +set s_initsound 0 +set r_enablerender 0 +set vm_cgame 2 +set vm_game 2\ +1346 frames, 12.8 seconds: 105.4 fps\ +\ +\ +\ +Starting point\ + +\b0 4865 frames, 154.5 seconds: 31.5 fps\ +[seconds spent locally, % of parent, % of total, # of samples]\ +[133.623469 -- 60130560955, 100.00%, 100.00%, 4866] Root\ + [126.853849 -- 57084231997, 94.93%, 94.93%, 4866] CL_Frame\ + [125.895845 -- 56653130083, 99.24%, 94.22%, 4918] SCR_UpdateScreen\ + [50.532841 -- 22739778533, 40.14%, 37.82%, 524036] RB_SurfaceMesh\ + [46.583051 -- 20962372767, 92.18%, 34.86%, 524036] LerpMeshVertexes\ + [8.465527 -- 3809487228, 18.17%, 6.34%, 455917] LerpMeshVertexes 1\ + +\b \cf2 [37.967433 -- 17085344910, 81.50%, 28.41%, 68119] LerpMeshVertexes 2\ + +\b0 \cf0 [0.32% spent locally]\ + [7.82% spent locally]\ + [59.86% spent locally]\ + [0.76% spent locally]\ +[5.07% spent locally]\ +\ + +\b Minor cleanup of local variables\ + +\b0 [seconds spent locally, % of parent, % of total, # of samples]\ +[133.121489 -- 59904670191, 100.00%, 100.00%, 4866] Root\ + [126.329343 -- 56848204176, 94.90%, 94.90%, 4866] CL_Frame\ + [125.402239 -- 56431007399, 99.27%, 94.20%, 4918] SCR_UpdateScreen\ + [50.013076 -- 22505884288, 39.88%, 37.57%, 524036] RB_SurfaceMesh\ + [46.085775 -- 20738598809, 92.15%, 34.62%, 524036] LerpMeshVertexes\ + [8.427565 -- 3792404277, 18.29%, 6.33%, 455917] LerpMeshVertexes 1\ +\pard\tx1440\tx2880\tx4320\tx5760\tx7200\ql\qnatural + +\b \cf3 [37.517092 -- 16882691281, 81.41%, 28.18%, 68119] LerpMeshVertexes 2\ +\pard\tx1440\tx2880\tx4320\tx5760\tx7200\ql\qnatural + +\b0 \cf0 [0.31% spent locally]\ + [7.85% spent locally]\ + [60.12% spent locally]\ + [0.73% spent locally]\ +[5.10% spent locally]\ +\ +\ + +\b Split out normalization of LERPed normals (i.e., all the sqrt calls)\ + +\b0 [seconds spent locally, % of parent, % of total, # of samples]\ +[133.110463 -- 59899708244, 100.00%, 100.00%, 4866] Root\ + [126.357393 -- 56860826689, 94.93%, 94.93%, 4866] CL_Frame\ + [125.364641 -- 56414088645, 99.21%, 94.18%, 4918] SCR_UpdateScreen\ + [49.854816 -- 22434667309, 39.77%, 37.45%, 524036] RB_SurfaceMesh\ + [45.981802 -- 20691810706, 92.23%, 34.54%, 524036] LerpMeshVertexes\ + [8.407983 -- 3783592133, 18.29%, 6.32%, 455917] LerpMeshVertexes 1\ + [37.432159 -- 16844471717, 81.41%, 28.12%, 68119] LerpMeshVertexes 2\ +\pard\tx1440\tx2880\tx4320\tx5760\tx7200\ql\qnatural + +\b \cf3 [30.288000 -- 13629599780, 80.91%, 22.75%, 68119] VectorArrayNormalize\ +\pard\tx1440\tx2880\tx4320\tx5760\tx7200\ql\qnatural + +\b0 \cf0 [19.09% spent locally]\ + [0.31% spent locally]\ + [7.77% spent locally]\ + [60.23% spent locally]\ + [0.79% spent locally]\ +[5.07% spent locally]\ +\ + +\b Rewrote VectorArrayNormalize to use PPC frsqrt instruction (with Newton-Rhapson refinement)\ + +\b0 4865 frames, 128.7 seconds: 37.8 fps\ +[seconds spent locally, % of parent, % of total, # of samples]\ +[103.972710 -- 46787719721, 100.00%, 100.00%, 4866] Root\ + [97.153160 -- 43718922078, 93.44%, 93.44%, 4866] CL_Frame\ + [96.219348 -- 43298706398, 99.04%, 92.54%, 4918] SCR_UpdateScreen\ + [20.873944 -- 9393274747, 21.69%, 20.08%, 524036] RB_SurfaceMesh\ + [17.053245 -- 7673960266, 81.70%, 16.40%, 524036] LerpMeshVertexes\ + [8.356579 -- 3760460537, 49.00%, 8.04%, 455917] LerpMeshVertexes 1\ + [8.560159 -- 3852071404, 50.20%, 8.23%, 68119] LerpMeshVertexes 2\ +\pard\tx1440\tx2880\tx4320\tx5760\tx7200\ql\qnatural + +\b \cf3 [1.429376 -- 643219234, 16.70%, 1.37%, 68119] VectorArrayNormalize\ +\pard\tx1440\tx2880\tx4320\tx5760\tx7200\ql\qnatural + +\b0 \cf0 [83.30% spent locally]\ + [0.80% spent locally]\ + [18.30% spent locally]\ + [78.31% spent locally]\ + [0.96% spent locally]\ +[6.56% spent locally]\ +\ +\ +} diff --git a/src/macosx/Q3Controller.h b/src/macosx/Q3Controller.h new file mode 100644 index 0000000..27fb583 --- /dev/null +++ b/src/macosx/Q3Controller.h @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// $Header$ + +#import + +@interface Q3Controller : NSObject +{ + IBOutlet NSPanel *bannerPanel; +} + +#ifndef DEDICATED +-(IBAction)paste : (id)sender; +-(IBAction)requestTerminate : (id)sender; + +-(void) showBanner; +#endif + +-(void)quakeMain; + +@end + diff --git a/src/macosx/Q3Controller.m b/src/macosx/Q3Controller.m new file mode 100644 index 0000000..2bc53c2 --- /dev/null +++ b/src/macosx/Q3Controller.m @@ -0,0 +1,427 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// $Header$ + +#import "Q3Controller.h" + +#import +#import + +#include "client.h" +#include "macosx_local.h" + +#ifdef OMNI_TIMER +#import "macosx_timers.h" +#endif + +#define MAX_ARGC 1024 + +static qboolean Sys_IsProcessingTerminationRequest = qfalse; +static void Sys_CreatePathToFile( NSString *path, NSDictionary *attributes ); + +@interface Q3Controller ( Private ) +- (void)quakeMain; +@end + +@implementation Q3Controller + +#ifndef DEDICATED + +- (void)applicationDidFinishLaunching : (NSNotification *)notification; +{ + NS_DURING { + [self quakeMain]; + } NS_HANDLER { + Sys_Error( "%@", [localException reason] ); + } NS_ENDHANDLER; + Sys_Quit(); +} + +-(void)applicationDidUnhide : (NSNotification *)notification; +{ + // Don't reactivate the game if we are asking whether to quit + if ( Sys_IsProcessingTerminationRequest ) { + return; + } + + if ( !Sys_Unhide() ) { + // Didn't work -- hide again so we should get another chance to unhide later + [NSApp hide : nil]; + } +} + +-(NSApplicationTerminateReply)applicationShouldTerminate : (NSApplication *)sender; +{ + int choice; + + if ( !Sys_IsHidden ) { + // We're terminating via -terminate: + return NSTerminateNow; + } + + // Avoid reactivating GL when we unhide due to this panel + Sys_IsProcessingTerminationRequest = qtrue; + choice = NSRunAlertPanel( nil, @ "Quit without saving?", @ "Don't Quit", @ "Quit", nil ); + Sys_IsProcessingTerminationRequest = qfalse; + + if ( choice == NSAlertAlternateReturn ) { + return NSTerminateNow; + } + + // Make sure we get re-hidden + [NSApp hide : nil]; + + return NSTerminateCancel; +} + +// Actions + +-(IBAction)paste : (id)sender; +{ + int shiftWasDown, insertWasDown; + unsigned int currentTime; + + currentTime = Sys_Milliseconds(); + // Save the original keyboard state + shiftWasDown = keys[K_SHIFT].down; + insertWasDown = keys[K_INS].down; + // Fake a Shift-Insert keyboard event + keys[K_SHIFT].down = qtrue; + Sys_QueEvent( currentTime, SE_KEY, K_INS, qtrue, 0, NULL ); + Sys_QueEvent( currentTime, SE_KEY, K_INS, qfalse, 0, NULL ); + // Restore the original keyboard state + keys[K_SHIFT].down = shiftWasDown; + keys[K_INS].down = insertWasDown; +} + +extern void CL_Quit_f( void ); + + +-(IBAction)requestTerminate : (id)sender; +{ + Com_Quit_f(); + // UI_QuitMenu(); +} + +-(void)showBanner; +{ + static BOOL hasShownBanner = NO; + + if ( !hasShownBanner ) { + cvar_t *showBanner; + + hasShownBanner = YES; + showBanner = Cvar_Get( "cl_showBanner", "1", 0 ); + if ( showBanner->integer != 0 ) { + NSPanel *splashPanel; + NSImage *bannerImage; + NSRect bannerRect; + NSImageView *bannerImageView; + + bannerImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@ "banner.jpg"]]; + bannerRect = NSMakeRect( 0.0, 0.0, [bannerImage size].width, [bannerImage size].height ); + + splashPanel = [[NSPanel alloc] initWithContentRect:bannerRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; + + bannerImageView = [[NSImageView alloc] initWithFrame:bannerRect]; + [bannerImageView setImage : bannerImage]; + [splashPanel setContentView : bannerImageView]; + [bannerImageView release]; + + [splashPanel center]; + [splashPanel setHasShadow : YES]; + [splashPanel orderFront : nil]; + [NSThread sleepUntilDate :[NSDate dateWithTimeIntervalSinceNow : 2.5]]; + [splashPanel close]; + + [bannerImage release]; + } + } +} + +// Services + +-(void)connectToServer : (NSPasteboard *)pasteboard userData : (NSString *)data error : (NSString **)error; +{ + NSArray *pasteboardTypes; + + pasteboardTypes = [pasteboard types]; + if ([pasteboardTypes containsObject : NSStringPboardType] ) { + NSString *requestedServer; + + requestedServer = [pasteboard stringForType:NSStringPboardType]; + if ( requestedServer ) { + Cbuf_AddText( va( "connect %s\n", [requestedServer cString] ) ); + return; + } + } + *error = @ "Unable to connect to server: could not find string on pasteboard"; +} + +-(void)performCommand : (NSPasteboard *)pasteboard userData : (NSString *)data error : (NSString **)error; +{ + NSArray *pasteboardTypes; + + pasteboardTypes = [pasteboard types]; + if ([pasteboardTypes containsObject : NSStringPboardType] ) { + NSString *requestedCommand; + + requestedCommand = [pasteboard stringForType:NSStringPboardType]; + if ( requestedCommand ) { + Cbuf_AddText( va( "%s\n", [requestedCommand cString] ) ); + return; + } + } + *error = @ "Unable to perform command: could not find string on pasteboard"; +} + +#endif + +- (void)quakeMain; +{ + NSAutoreleasePool *pool; + int argc = 0; + const char *argv[MAX_ARGC]; + NSProcessInfo *processInfo; + NSArray *arguments; + unsigned int argumentIndex, argumentCount; + NSFileManager *defaultManager; + unsigned int commandLineLength; + NSString *installationPathKey, *installationPath; + char *cmdline; + BOOL foundDirectory; + NSString *appName, *demoAppName, *selectButton; + + pool = [[NSAutoreleasePool alloc] init]; + + [NSApp setServicesProvider : self]; + + processInfo = [NSProcessInfo processInfo]; + arguments = [processInfo arguments]; + argumentCount = [arguments count]; + for ( argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++ ) { + NSString *arg; + + arg = [arguments objectAtIndex:argumentIndex]; + // Don't pass the Process Serial Number command line arg that the Window Server/Finder invokes us with + if ([arg hasPrefix : @ "-psn_"] ) { + continue; + } + + argv[argc++] = strdup([arg cString] ); + } + + // Figure out where the level data is stored. + installationPathKey = @ "RetailInstallationPath"; + + installationPath = [[NSUserDefaults standardUserDefaults] objectForKey:installationPathKey]; + if ( !installationPath ) { + // Default to the directory containing the executable (which is where most users will want to put it + installationPath = [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]; + installationPath = [installationPath stringByAppendingPathComponent: @ "wolfmp"]; + } + + appName = [[[NSBundle mainBundle] infoDictionary] objectForKey: @ "CFBundleName"]; +#ifdef DEDICATED +#warning TJW: We are hard coding the app name here since the dedicated server is a tool, not a app bundle and does not have access to the Info.plist that the client app does. Suck. + appName = @ "WolfensteinMP"; +#endif + demoAppName = appName; + + while ( YES ) { + NSString *dataPath; + NSOpenPanel *openPanel; + int result; + + foundDirectory = NO; + defaultManager = [NSFileManager defaultManager]; + NSLog( @ "Candidate installation path = %@", installationPath ); +#ifdef PRE_RELEASE_DEMO + dataPath = [installationPath stringByAppendingPathComponent: @ "demomain"]; +#else + dataPath = [installationPath stringByAppendingPathComponent: @ "main"]; +#endif + if ([defaultManager fileExistsAtPath : dataPath] ) { + // Check that the data directory contains at least one .pk3 file. We don't know what it will be named, so don't hard code a name (for example it might be named 'french.pk3' for a French release + NSArray *files; + unsigned int fileIndex; + + files = [defaultManager directoryContentsAtPath: dataPath]; + fileIndex = [files count]; + while ( fileIndex-- ) { + if ([[files objectAtIndex : fileIndex] hasSuffix : @ "pk3"] ) { + //NSLog(@"Found %@.", [files objectAtIndex: fileIndex]); + foundDirectory = YES; + break; + } + } + } + + if ( foundDirectory ) { + break; + } + +#ifdef DEDICATED +#warning TJW: We are hard coding the app name and default domain here since the dedicated server is a tool, not a app bundle and does not have access to the Info.plist that the client app does. Suck. + NSLog( @ "Unable to determine installation directory. Please move the executable into the '%@' installation directory or add a '%@' key in the 'WolfDedicated' defaults domain.", appName, installationPathKey, [[NSBundle mainBundle] bundleIdentifier] ); + break; +// Sys_Quit(); +// exit(1); +#else + selectButton = @ "Select Retail Installation..."; + + result = NSRunAlertPanel( demoAppName, @ "You need to select the installation directory for %@ (not any directory inside of it -- the installation directory itself).", selectButton, @ "Quit", nil, appName ); + switch ( result ) { + case NSAlertDefaultReturn: + break; + default: + Sys_Quit(); + break; + } + + openPanel = [NSOpenPanel openPanel]; + [openPanel setAllowsMultipleSelection : NO]; + [openPanel setCanChooseDirectories : YES]; + [openPanel setCanChooseFiles : NO]; + result = [openPanel runModalForDirectory:nil file:nil]; + if ( result == NSOKButton ) { + NSArray *filenames; + + filenames = [openPanel filenames]; + if ([filenames count] == 1 ) { + installationPath = [filenames objectAtIndex:0]; + [[NSUserDefaults standardUserDefaults] setObject : installationPath forKey : installationPathKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + } +#endif + } + + // Create the application support directory if it doesn't exist already + do { + NSArray *results; + NSString *libraryPath, *homePath, *filePath; + NSDictionary *attributes; + + results = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES ); + if ( ![results count] ) { + break; + } + + libraryPath = [results objectAtIndex: 0]; + homePath = [libraryPath stringByAppendingPathComponent: @ "Application Support"]; + homePath = [homePath stringByAppendingPathComponent: appName]; + filePath = [homePath stringByAppendingPathComponent: @ "foo"]; + + attributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt: 0750], NSFilePosixPermissions, nil]; + NS_DURING { + Sys_CreatePathToFile( filePath, attributes ); + Sys_SetDefaultHomePath([homePath fileSystemRepresentation] ); + } NS_HANDLER { + NSLog( @ "Exception: %@", localException ); +#ifndef DEDICATED + NSRunAlertPanel( nil, @ "Unable to create '%@'. Please make sure that you have permission to write to this folder and re-run the game.", @ "OK", nil, nil, homePath ); +#endif + Sys_Quit(); + } NS_ENDHANDLER; + } while ( 0 ); + + // Provoke the CD scanning code into looking up the CD. + Sys_CheckCD(); + + // Let the filesystem know where our local install is + Sys_SetDefaultInstallPath([installationPath cString] ); + + // merge the command line, this is kinda silly + for ( commandLineLength = 1, argumentIndex = 1; argumentIndex < argc; argumentIndex++ ) + commandLineLength += strlen( argv[argumentIndex] ) + 1; + cmdline = malloc( commandLineLength ); + *cmdline = '\0'; + for ( argumentIndex = 1; argumentIndex < argc; argumentIndex++ ) { + if ( argumentIndex > 1 ) { + strcat( cmdline, " " ); + } + strcat( cmdline, argv[argumentIndex] ); + } + Com_Init( cmdline ); + +#ifndef DEDICATED + [NSApp activateIgnoringOtherApps : YES]; +#endif + + while ( 1 ) { + Com_Frame(); + + // We should think about doing this less frequently than every frame + [pool release]; + pool = [[NSAutoreleasePool alloc] init]; + } + + [pool release]; +} + +@end + + + +// Creates any directories needed to be able to create a file at the specified path. Raises an exception on failure. +static void Sys_CreatePathToFile( NSString *path, NSDictionary *attributes ) { + NSArray *pathComponents; + unsigned int dirIndex, dirCount; + unsigned int startingIndex; + NSFileManager *manager; + + manager = [NSFileManager defaultManager]; + pathComponents = [path pathComponents]; + dirCount = [pathComponents count] - 1; + + startingIndex = 0; + for ( dirIndex = startingIndex; dirIndex < dirCount; dirIndex++ ) { + NSString *partialPath; + BOOL fileExists; + + partialPath = [NSString pathWithComponents:[pathComponents subarrayWithRange:NSMakeRange( 0, dirIndex + 1 )]]; + + // Don't use the 'fileExistsAtPath:isDirectory:' version since it doesn't traverse symlinks + fileExists = [manager fileExistsAtPath:partialPath]; + if ( !fileExists ) { + if ( ![manager createDirectoryAtPath : partialPath attributes : attributes] ) { + [NSException raise : NSGenericException format : @ "Unable to create a directory at path: %@", partialPath]; + } + } else { + NSDictionary *attributes; + + attributes = [manager fileAttributesAtPath:partialPath traverseLink:YES]; + if ( ![[attributes objectForKey : NSFileType] isEqualToString : NSFileTypeDirectory] ) { + [NSException raise : NSGenericException format : @ "Unable to write to path \"%@\" because \"%@\" is not a directory", + path, partialPath]; + } + } + } +} diff --git a/src/macosx/Quake3.icns b/src/macosx/Quake3.icns new file mode 100644 index 0000000..6a43196 Binary files /dev/null and b/src/macosx/Quake3.icns differ diff --git a/src/macosx/Quake3.nib/classes.nib b/src/macosx/Quake3.nib/classes.nib new file mode 100644 index 0000000..8436859 --- /dev/null +++ b/src/macosx/Quake3.nib/classes.nib @@ -0,0 +1,18 @@ +{ + IBClasses = ( + { + ACTIONS = {showHelp = id; }; + CLASS = FirstResponder; + LANGUAGE = ObjC; + SUPERCLASS = NSObject; + }, + { + ACTIONS = {paste = id; requestTerminate = id; }; + CLASS = Q3Controller; + LANGUAGE = ObjC; + OUTLETS = {bannerPanel = id; }; + SUPERCLASS = NSObject; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/src/macosx/Quake3.nib/info.nib b/src/macosx/Quake3.nib/info.nib new file mode 100644 index 0000000..3f1af9f --- /dev/null +++ b/src/macosx/Quake3.nib/info.nib @@ -0,0 +1,8 @@ + + + + + IBMainMenuLocation + 15 329 121 44 0 47 1152 801 + + diff --git a/src/macosx/Quake3.nib/objects.nib b/src/macosx/Quake3.nib/objects.nib new file mode 100644 index 0000000..22c5ba5 Binary files /dev/null and b/src/macosx/Quake3.nib/objects.nib differ diff --git a/src/macosx/RecordDemo.zsh b/src/macosx/RecordDemo.zsh new file mode 100644 index 0000000..273f4e6 --- /dev/null +++ b/src/macosx/RecordDemo.zsh @@ -0,0 +1,9 @@ +#!/bin/zsh -x + +/Local/Public/bungi/BuildOutput/Quake3.app/Contents/MacOS/Quake3 \ + +set sv_pure 0 \ + +set g_syncronousClients 1 \ + +map q3dm6 \ + +record foo + + diff --git a/src/macosx/Wolf.icns b/src/macosx/Wolf.icns new file mode 100644 index 0000000..68270fe Binary files /dev/null and b/src/macosx/Wolf.icns differ diff --git a/src/macosx/Wolf.pbproj/project.pbxproj b/src/macosx/Wolf.pbproj/project.pbxproj new file mode 100644 index 0000000..12958d9 --- /dev/null +++ b/src/macosx/Wolf.pbproj/project.pbxproj @@ -0,0 +1,7348 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 34; + objects = { + 00E9D914FEDB4D29C697A12F = { + isa = PBXFrameworkReference; + name = CoreAudio.framework; + path = /System/Library/Frameworks/CoreAudio.framework; + refType = 0; + }; + 00E9D91DFEDB5295C697A12F = { + fileRef = 00E9D914FEDB4D29C697A12F; + isa = PBXBuildFile; + settings = { + }; + }; + 00F5ED38FEBA95B7C697A12F = { + buildPhases = ( + 00F5ED39FEBA95B7C697A12F, + 00F5ED55FEBA95B7C697A12F, + 00F5ED56FEBA95B7C697A12F, + 00F5ED77FEBA95B7C697A12F, + 00F5ED78FEBA95B7C697A12F, + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "/Users/Shared/$(USER)/InstalledProducts"; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = "-DGAMEDLL"; + OTHER_LDFLAGS = "-bundle -undefined error"; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = qagame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wall -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + isa = PBXBundleTarget; + name = qagame; + productInstallPath = "/Users/Shared/$(USER)/InstalledProducts"; + productName = game; + productReference = 07F3F507FFE98E8EC697A10E; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + qagame + CFBundleGetInfoString + Quake 3 Arena (1.16) + CFBundleIconFile + Quake3.icns + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.16 + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + shouldUseHeadermap = 0; + }; + 00F5ED39FEBA95B7C697A12F = { + buildActionMask = 2147483647; + files = ( + 13380E0F00ADFAACC697A10E, + 13380E2500ADFCA7C697A10E, + F5ABC7E1013B0EAB01F6286D, + F5ABC7E2013B0EAB01F6286D, + F5ABC80F013B120B01F6286D, + F5ABC810013B120B01F6286D, + F5ABC811013B120B01F6286D, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + 00F5ED55FEBA95B7C697A12F = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + 00F5ED56FEBA95B7C697A12F = { + buildActionMask = 2147483647; + files = ( + 13380E0900ADF941C697A10E, + 13380E0B00ADFA16C697A10E, + 13380E0C00ADFA72C697A10E, + 13380E0D00ADFA94C697A10E, + 13380E0E00ADFA9EC697A10E, + 13380E1000ADFAACC697A10E, + 13380E1100ADFAACC697A10E, + 13380E2100ADFC59C697A10E, + 13380E2200ADFC78C697A10E, + 13380E2300ADFC85C697A10E, + 13380E2400ADFC9CC697A10E, + 13380E2600ADFCA7C697A10E, + 13380E2700ADFCBDC697A10E, + 13380E2800ADFCC8C697A10E, + 13380E2900ADFCD1C697A10E, + 13380E2A00ADFCEBC697A10E, + 13380E2B00ADFCF6C697A10E, + 13380E2C00ADFD01C697A10E, + 13380E2D00ADFD1FC697A10E, + 13380E2E00ADFD28C697A10E, + 13380E2F00ADFD38C697A10E, + 13380E3000ADFD46C697A10E, + 13380E3100ADFD58C697A10E, + 13380E3200ADFD71C697A10E, + 13380E3300ADFD85C697A10E, + F5ABC7E6013B0EAB01F6286D, + F5ABC7E7013B0EAB01F6286D, + F5ABC7E8013B0EAB01F6286D, + F5ABC7E9013B0EAB01F6286D, + F5ABC7EA013B0EAB01F6286D, + F5ABC7EB013B0EAB01F6286D, + F5ABC7EC013B0EAB01F6286D, + F5ABC7ED013B0EAB01F6286D, + F5ABC7EE013B0EAB01F6286D, + F5ABC7EF013B0EAB01F6286D, + F5ABC7F0013B0EAB01F6286D, + F5ABC7F1013B0EAB01F6286D, + F5ABC7F2013B0EAB01F6286D, + F5ABC7F3013B0EAB01F6286D, + F5ABC7F4013B0EAB01F6286D, + F5ABC7F5013B0EAB01F6286D, + F5ABC7F6013B0EAB01F6286D, + F5ABC7F7013B0EAB01F6286D, + F5ABC7F8013B0EAB01F6286D, + F5ABC817013B120B01F6286D, + F5ABC818013B120B01F6286D, + F5ABC819013B120B01F6286D, + F5ABC81A013B120B01F6286D, + F5ABC81B013B120B01F6286D, + F5ABC81C013B120B01F6286D, + F55DF57B01513A8101F6286D, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + 00F5ED77FEBA95B7C697A12F = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + 00F5ED78FEBA95B7C697A12F = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + 00F5ED90FEBA9615C697A12F = { + buildPhases = ( + 00F5ED91FEBA9615C697A12F, + 00F5ED92FEBA9615C697A12F, + 00F5ED93FEBA9615C697A12F, + 00F5ED94FEBA9615C697A12F, + 00F5ED95FEBA9615C697A12F, + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = "-DCGAMEDLL"; + OTHER_LDFLAGS = "-bundle -undefined error"; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = cgame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wall -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + isa = PBXBundleTarget; + name = cgame; + productName = cgame; + productReference = 07F3F508FFE98E8EC697A10E; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + BNDL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + shouldUseHeadermap = 0; + }; + 00F5ED91FEBA9615C697A12F = { + buildActionMask = 2147483647; + files = ( + 13380E3600ADFDCFC697A10E, + 13380E5100AE0235C697A10E, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + 00F5ED92FEBA9615C697A12F = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + 00F5ED93FEBA9615C697A12F = { + buildActionMask = 2147483647; + files = ( + 13380E3400ADFDA1C697A10E, + 13380E3500ADFDA1C697A10E, + 13380E3700ADFDCFC697A10E, + 13380E3800ADFDCFC697A10E, + 13380E3900ADFDE1C697A10E, + 13380E3B00ADFE0CC697A10E, + 13380E3C00ADFE1EC697A10E, + 13380E3D00ADFE24C697A10E, + 13380E3E00ADFE3DC697A10E, + 13380E3F00ADFE4AC697A10E, + 13380E4000ADFE58C697A10E, + 13380E4100ADFE6BC697A10E, + 13380E4200ADFE7AC697A10E, + 13380E4300ADFE88C697A10E, + 13380E4400ADFE97C697A10E, + 13380E4500ADFEA7C697A10E, + 13380E4600ADFEB8C697A10E, + 13380E4700ADFEC9C697A10E, + 13380E4800ADFED7C697A10E, + 13380E4900ADFEE7C697A10E, + 13380E4A00ADFEF5C697A10E, + 13380E4B00ADFF05C697A10E, + 13380E4C00ADFF27C697A10E, + 13380E4F00AE0112C697A10E, + 13380E5200AE0235C697A10E, + F5ABC821013B12E701F6286D, + F5ABC822013B12E701F6286D, + F5ABC823013B12E701F6286D, + F5ABC824013B12E701F6286D, + F5ABC825013B145701F6286D, + F5673BEF015686CC01F628AA, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + 00F5ED94FEBA9615C697A12F = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + 00F5ED95FEBA9615C697A12F = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; +//000 +//001 +//002 +//003 +//004 +//010 +//011 +//012 +//013 +//014 + 011F78F200B25B65C697A10E = { + isa = PBXFileReference; + path = macosx_qgl.h; + refType = 4; + }; + 011F78F300B25B66C697A10E = { + fileRef = 011F78F200B25B65C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 012AD90700868211C697A10E = { + children = ( + 012AD90800868211C697A10E, + 012AD90900868211C697A10E, + 012AD90A00868211C697A10E, + 012AD90B00868211C697A10E, + 012AD90C00868211C697A10E, + 012AD90D00868211C697A10E, + 012AD90E00868211C697A10E, + 012AD90F00868211C697A10E, + 012AD91000868211C697A10E, + 012AD91100868211C697A10E, + 012AD91200868211C697A10E, + 012AD91300868211C697A10E, + 012AD91400868211C697A10E, + 012AD91500868211C697A10E, + 012AD91600868211C697A10E, + 012AD91700868211C697A10E, + 012AD91800868211C697A10E, + 012AD91900868211C697A10E, + 012AD91A00868211C697A10E, + 012AD91B00868211C697A10E, + 012AD91C00868211C697A10E, + 012AD91D00868211C697A10E, + 012AD91E00868211C697A10E, + 012AD91F00868211C697A10E, + 012AD92000868211C697A10E, + 012AD92100868211C697A10E, + 012AD92200868211C697A10E, + 012AD92300868211C697A10E, + 012AD92400868211C697A10E, + 012AD92500868211C697A10E, + 012AD92600868211C697A10E, + 012AD92700868211C697A10E, + 012AD92800868211C697A10E, + 012AD92900868211C697A10E, + 012AD92A00868211C697A10E, + 012AD92B00868211C697A10E, + 012AD92C00868211C697A10E, + 012AD92D00868211C697A10E, + 012AD92E00868211C697A10E, + 012AD92F00868211C697A10E, + 012AD93000868211C697A10E, + 012AD93100868211C697A10E, + 012AD93200868211C697A10E, + 012AD93300868211C697A10E, + 012AD93400868211C697A10E, + 012AD93500868211C697A10E, + 012AD93600868211C697A10E, + 012AD93700868211C697A10E, + 012AD93800868211C697A10E, + 012AD93900868211C697A10E, + 012AD93A00868211C697A10E, + 012AD93B00868211C697A10E, + 012AD93C00868211C697A10E, + F5ABC7B5013B051201F6286D, + F5ABC7B6013B051201F6286D, + ); + isa = PBXGroup; + path = botlib; + refType = 4; + }; + 012AD90800868211C697A10E = { + isa = PBXFileReference; + path = aasfile.h; + refType = 4; + }; + 012AD90900868211C697A10E = { + isa = PBXFileReference; + path = be_aas_bsp.h; + refType = 4; + }; + 012AD90A00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_bspq3.c; + refType = 4; + }; + 012AD90B00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_cluster.c; + refType = 4; + }; + 012AD90C00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_cluster.h; + refType = 4; + }; + 012AD90D00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_debug.c; + refType = 4; + }; + 012AD90E00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_debug.h; + refType = 4; + }; + 012AD90F00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_def.h; + refType = 4; + }; + 012AD91000868211C697A10E = { + isa = PBXFileReference; + path = be_aas_entity.c; + refType = 4; + }; + 012AD91100868211C697A10E = { + isa = PBXFileReference; + path = be_aas_entity.h; + refType = 4; + }; + 012AD91200868211C697A10E = { + isa = PBXFileReference; + path = be_aas_file.c; + refType = 4; + }; + 012AD91300868211C697A10E = { + isa = PBXFileReference; + path = be_aas_file.h; + refType = 4; + }; + 012AD91400868211C697A10E = { + isa = PBXFileReference; + path = be_aas_funcs.h; + refType = 4; + }; + 012AD91500868211C697A10E = { + isa = PBXFileReference; + path = be_aas_main.c; + refType = 4; + }; + 012AD91600868211C697A10E = { + isa = PBXFileReference; + path = be_aas_main.h; + refType = 4; + }; + 012AD91700868211C697A10E = { + isa = PBXFileReference; + path = be_aas_move.c; + refType = 4; + }; + 012AD91800868211C697A10E = { + isa = PBXFileReference; + path = be_aas_move.h; + refType = 4; + }; + 012AD91900868211C697A10E = { + isa = PBXFileReference; + path = be_aas_optimize.c; + refType = 4; + }; + 012AD91A00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_optimize.h; + refType = 4; + }; + 012AD91B00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_reach.c; + refType = 4; + }; + 012AD91C00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_reach.h; + refType = 4; + }; + 012AD91D00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_route.c; + refType = 4; + }; + 012AD91E00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_route.h; + refType = 4; + }; + 012AD91F00868211C697A10E = { + isa = PBXFileReference; + path = be_aas_routealt.c; + refType = 4; + }; + 012AD92000868211C697A10E = { + isa = PBXFileReference; + path = be_aas_routealt.h; + refType = 4; + }; + 012AD92100868211C697A10E = { + isa = PBXFileReference; + path = be_aas_sample.c; + refType = 4; + }; + 012AD92200868211C697A10E = { + isa = PBXFileReference; + path = be_aas_sample.h; + refType = 4; + }; + 012AD92300868211C697A10E = { + isa = PBXFileReference; + path = be_ai_char.c; + refType = 4; + }; + 012AD92400868211C697A10E = { + isa = PBXFileReference; + path = be_ai_chat.c; + refType = 4; + }; + 012AD92500868211C697A10E = { + isa = PBXFileReference; + path = be_ai_gen.c; + refType = 4; + }; + 012AD92600868211C697A10E = { + isa = PBXFileReference; + path = be_ai_goal.c; + refType = 4; + }; + 012AD92700868211C697A10E = { + isa = PBXFileReference; + path = be_ai_move.c; + refType = 4; + }; + 012AD92800868211C697A10E = { + isa = PBXFileReference; + path = be_ai_weap.c; + refType = 4; + }; + 012AD92900868211C697A10E = { + isa = PBXFileReference; + path = be_ai_weight.c; + refType = 4; + }; + 012AD92A00868211C697A10E = { + isa = PBXFileReference; + path = be_ai_weight.h; + refType = 4; + }; + 012AD92B00868211C697A10E = { + isa = PBXFileReference; + path = be_ea.c; + refType = 4; + }; + 012AD92C00868211C697A10E = { + isa = PBXFileReference; + path = be_interface.c; + refType = 4; + }; + 012AD92D00868211C697A10E = { + isa = PBXFileReference; + path = be_interface.h; + refType = 4; + }; + 012AD92E00868211C697A10E = { + isa = PBXFileReference; + path = l_crc.c; + refType = 4; + }; + 012AD92F00868211C697A10E = { + isa = PBXFileReference; + path = l_crc.h; + refType = 4; + }; + 012AD93000868211C697A10E = { + isa = PBXFileReference; + path = l_libvar.c; + refType = 4; + }; + 012AD93100868211C697A10E = { + isa = PBXFileReference; + path = l_libvar.h; + refType = 4; + }; + 012AD93200868211C697A10E = { + isa = PBXFileReference; + path = l_log.c; + refType = 4; + }; + 012AD93300868211C697A10E = { + isa = PBXFileReference; + path = l_log.h; + refType = 4; + }; + 012AD93400868211C697A10E = { + isa = PBXFileReference; + path = l_memory.c; + refType = 4; + }; + 012AD93500868211C697A10E = { + isa = PBXFileReference; + path = l_memory.h; + refType = 4; + }; + 012AD93600868211C697A10E = { + isa = PBXFileReference; + path = l_precomp.c; + refType = 4; + }; + 012AD93700868211C697A10E = { + isa = PBXFileReference; + path = l_precomp.h; + refType = 4; + }; + 012AD93800868211C697A10E = { + isa = PBXFileReference; + path = l_script.c; + refType = 4; + }; + 012AD93900868211C697A10E = { + isa = PBXFileReference; + path = l_script.h; + refType = 4; + }; + 012AD93A00868211C697A10E = { + isa = PBXFileReference; + path = l_struct.c; + refType = 4; + }; + 012AD93B00868211C697A10E = { + isa = PBXFileReference; + path = l_struct.h; + refType = 4; + }; + 012AD93C00868211C697A10E = { + isa = PBXFileReference; + path = l_utils.h; + refType = 4; + }; + 012AD93D00868211C697A10E = { + children = ( + 012AD93E00868211C697A10E, + 012AD93F00868211C697A10E, + 012AD94000868211C697A10E, + 012AD94100868211C697A10E, + 012AD94200868211C697A10E, + 012AD94300868211C697A10E, + 012AD94400868211C697A10E, + 012AD94500868211C697A10E, + 012AD94600868211C697A10E, + 012AD94700868211C697A10E, + 012AD94800868211C697A10E, + 012AD94900868211C697A10E, + 012AD94A00868211C697A10E, + 012AD94B00868211C697A10E, + 012AD94C00868211C697A10E, + 012AD94D00868211C697A10E, + 012AD94E00868211C697A10E, + 012AD94F00868211C697A10E, + 012AD95000868211C697A10E, + 012AD95100868211C697A10E, + 012AD95200868211C697A10E, + 012AD95400868211C697A10E, + 012AD95500868211C697A10E, + 012AD95600868211C697A10E, + 012AD95700868211C697A10E, + 012AD95900868211C697A10E, + 012AD95A00868211C697A10E, + 012AD95C00868211C697A10E, + 012AD95D00868211C697A10E, + 012AD95E00868211C697A10E, + 012AD95F00868211C697A10E, + 012AD96000868211C697A10E, + 012AD96100868211C697A10E, + 012AD96200868211C697A10E, + 012AD96300868211C697A10E, + 012AD96400868211C697A10E, + 012AD96500868211C697A10E, + 012AD96600868211C697A10E, + 012AD96700868211C697A10E, + 012AD96800868211C697A10E, + 012AD96900868211C697A10E, + 012AD96A00868211C697A10E, + 012AD96B00868211C697A10E, + 012AD96C00868211C697A10E, + 012AD96D00868211C697A10E, + 012AD96E00868211C697A10E, + 012AD96F00868211C697A10E, + 012AD97000868211C697A10E, + 012AD97100868211C697A10E, + 012AD97200868211C697A10E, + 012AD97300868211C697A10E, + 012AD97400868211C697A10E, + 012AD97500868211C697A10E, + 012AD97600868211C697A10E, + 012AD97700868211C697A10E, + 012AD97800868211C697A10E, + 012AD97900868211C697A10E, + 012AD97A00868211C697A10E, + 012AD97B00868211C697A10E, + 012AD97C00868211C697A10E, + 012AD97D00868211C697A10E, + 012AD97E00868211C697A10E, + 012AD97F00868211C697A10E, + 012AD98000868211C697A10E, + 012AD98100868211C697A10E, + 012AD98200868211C697A10E, + 012AD98300868211C697A10E, + 012AD98400868211C697A10E, + 012AD98500868211C697A10E, + 012AD98600868211C697A10E, + 012AD98700868211C697A10E, + 012AD98800868211C697A10E, + 012AD98900868211C697A10E, + 012AD98A00868211C697A10E, + 012AD98B00868211C697A10E, + 012AD98C00868211C697A10E, + ); + isa = PBXGroup; + path = bspc; + refType = 4; + }; + 012AD93E00868211C697A10E = { + isa = PBXFileReference; + path = _files.c; + refType = 4; + }; + 012AD93F00868211C697A10E = { + isa = PBXFileReference; + path = aas_areamerging.c; + refType = 4; + }; + 012AD94000868211C697A10E = { + isa = PBXFileReference; + path = aas_areamerging.h; + refType = 4; + }; + 012AD94100868211C697A10E = { + isa = PBXFileReference; + path = aas_cfg.c; + refType = 4; + }; + 012AD94200868211C697A10E = { + isa = PBXFileReference; + path = aas_cfg.h; + refType = 4; + }; + 012AD94300868211C697A10E = { + isa = PBXFileReference; + path = aas_create.c; + refType = 4; + }; + 012AD94400868211C697A10E = { + isa = PBXFileReference; + path = aas_create.h; + refType = 4; + }; + 012AD94500868211C697A10E = { + isa = PBXFileReference; + path = aas_edgemelting.c; + refType = 4; + }; + 012AD94600868211C697A10E = { + isa = PBXFileReference; + path = aas_edgemelting.h; + refType = 4; + }; + 012AD94700868211C697A10E = { + isa = PBXFileReference; + path = aas_facemerging.c; + refType = 4; + }; + 012AD94800868211C697A10E = { + isa = PBXFileReference; + path = aas_facemerging.h; + refType = 4; + }; + 012AD94900868211C697A10E = { + isa = PBXFileReference; + path = aas_file.c; + refType = 4; + }; + 012AD94A00868211C697A10E = { + isa = PBXFileReference; + path = aas_file.h; + refType = 4; + }; + 012AD94B00868211C697A10E = { + isa = PBXFileReference; + path = aas_gsubdiv.c; + refType = 4; + }; + 012AD94C00868211C697A10E = { + isa = PBXFileReference; + path = aas_gsubdiv.h; + refType = 4; + }; + 012AD94D00868211C697A10E = { + isa = PBXFileReference; + path = aas_map.c; + refType = 4; + }; + 012AD94E00868211C697A10E = { + isa = PBXFileReference; + path = aas_map.h; + refType = 4; + }; + 012AD94F00868211C697A10E = { + isa = PBXFileReference; + path = aas_prunenodes.c; + refType = 4; + }; + 012AD95000868211C697A10E = { + isa = PBXFileReference; + path = aas_prunenodes.h; + refType = 4; + }; + 012AD95100868211C697A10E = { + isa = PBXFileReference; + path = aas_store.c; + refType = 4; + }; + 012AD95200868211C697A10E = { + isa = PBXFileReference; + path = aas_store.h; + refType = 4; + }; + 012AD95400868211C697A10E = { + isa = PBXFileReference; + path = be_aas_bspc.c; + refType = 4; + }; + 012AD95500868211C697A10E = { + isa = PBXFileReference; + path = be_aas_bspc.h; + refType = 4; + }; + 012AD95600868211C697A10E = { + isa = PBXFileReference; + path = brushbsp.c; + refType = 4; + }; + 012AD95700868211C697A10E = { + isa = PBXFileReference; + path = bspc.c; + refType = 4; + }; + 012AD95900868211C697A10E = { + isa = PBXFileReference; + path = csg.c; + refType = 4; + }; + 012AD95A00868211C697A10E = { + isa = PBXFileReference; + path = faces.c; + refType = 4; + }; + 012AD95C00868211C697A10E = { + isa = PBXFileReference; + path = glfile.c; + refType = 4; + }; + 012AD95D00868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_ent.c; + refType = 4; + }; + 012AD95E00868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_ent.h; + refType = 4; + }; + 012AD95F00868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_hl.c; + refType = 4; + }; + 012AD96000868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_hl.h; + refType = 4; + }; + 012AD96100868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_q1.c; + refType = 4; + }; + 012AD96200868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_q1.h; + refType = 4; + }; + 012AD96300868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_q2.c; + refType = 4; + }; + 012AD96400868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_q2.h; + refType = 4; + }; + 012AD96500868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_q3.c; + refType = 4; + }; + 012AD96600868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_q3.h; + refType = 4; + }; + 012AD96700868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_sin.c; + refType = 4; + }; + 012AD96800868211C697A10E = { + isa = PBXFileReference; + path = l_bsp_sin.h; + refType = 4; + }; + 012AD96900868211C697A10E = { + isa = PBXFileReference; + path = l_cmd.c; + refType = 4; + }; + 012AD96A00868211C697A10E = { + isa = PBXFileReference; + path = l_cmd.h; + refType = 4; + }; + 012AD96B00868211C697A10E = { + isa = PBXFileReference; + path = l_log.c; + refType = 4; + }; + 012AD96C00868211C697A10E = { + isa = PBXFileReference; + path = l_log.h; + refType = 4; + }; + 012AD96D00868211C697A10E = { + isa = PBXFileReference; + path = l_math.c; + refType = 4; + }; + 012AD96E00868211C697A10E = { + isa = PBXFileReference; + path = l_math.h; + refType = 4; + }; + 012AD96F00868211C697A10E = { + isa = PBXFileReference; + path = l_mem.c; + refType = 4; + }; + 012AD97000868211C697A10E = { + isa = PBXFileReference; + path = l_mem.h; + refType = 4; + }; + 012AD97100868211C697A10E = { + isa = PBXFileReference; + path = l_poly.c; + refType = 4; + }; + 012AD97200868211C697A10E = { + isa = PBXFileReference; + path = l_poly.h; + refType = 4; + }; + 012AD97300868211C697A10E = { + isa = PBXFileReference; + path = l_qfiles.c; + refType = 4; + }; + 012AD97400868211C697A10E = { + isa = PBXFileReference; + path = l_qfiles.h; + refType = 4; + }; + 012AD97500868211C697A10E = { + isa = PBXFileReference; + path = l_threads.c; + refType = 4; + }; + 012AD97600868211C697A10E = { + isa = PBXFileReference; + path = l_threads.h; + refType = 4; + }; + 012AD97700868211C697A10E = { + isa = PBXFileReference; + path = l_utils.c; + refType = 4; + }; + 012AD97800868211C697A10E = { + isa = PBXFileReference; + path = l_utils.h; + refType = 4; + }; + 012AD97900868211C697A10E = { + isa = PBXFileReference; + path = leakfile.c; + refType = 4; + }; + 012AD97A00868211C697A10E = { + isa = PBXFileReference; + path = map.c; + refType = 4; + }; + 012AD97B00868211C697A10E = { + isa = PBXFileReference; + path = map_hl.c; + refType = 4; + }; + 012AD97C00868211C697A10E = { + isa = PBXFileReference; + path = map_q1.c; + refType = 4; + }; + 012AD97D00868211C697A10E = { + isa = PBXFileReference; + path = map_q2.c; + refType = 4; + }; + 012AD97E00868211C697A10E = { + isa = PBXFileReference; + path = map_q3.c; + refType = 4; + }; + 012AD97F00868211C697A10E = { + isa = PBXFileReference; + path = map_sin.c; + refType = 4; + }; + 012AD98000868211C697A10E = { + isa = PBXFileReference; + path = nodraw.c; + refType = 4; + }; + 012AD98100868211C697A10E = { + isa = PBXFileReference; + path = portals.c; + refType = 4; + }; + 012AD98200868211C697A10E = { + isa = PBXFileReference; + path = prtfile.c; + refType = 4; + }; + 012AD98300868211C697A10E = { + isa = PBXFileReference; + path = q2files.h; + refType = 4; + }; + 012AD98400868211C697A10E = { + isa = PBXFileReference; + path = q3files.h; + refType = 4; + }; + 012AD98500868211C697A10E = { + isa = PBXFileReference; + path = qbsp.h; + refType = 4; + }; + 012AD98600868211C697A10E = { + isa = PBXFileReference; + path = qfiles.h; + refType = 4; + }; + 012AD98700868211C697A10E = { + isa = PBXFileReference; + path = sinfiles.h; + refType = 4; + }; + 012AD98800868211C697A10E = { + isa = PBXFileReference; + path = tetrahedron.c; + refType = 4; + }; + 012AD98900868211C697A10E = { + isa = PBXFileReference; + path = tetrahedron.h; + refType = 4; + }; + 012AD98A00868211C697A10E = { + isa = PBXFileReference; + path = textures.c; + refType = 4; + }; + 012AD98B00868211C697A10E = { + isa = PBXFileReference; + path = tree.c; + refType = 4; + }; + 012AD98C00868211C697A10E = { + isa = PBXFileReference; + path = writebsp.c; + refType = 4; + }; + 012AD98D00868211C697A10E = { + children = ( + 012AD98E00868211C697A10E, + 012AD98F00868211C697A10E, + 012AD99000868211C697A10E, + 012AD99100868211C697A10E, + 012AD99200868211C697A10E, + 012AD99300868211C697A10E, + 012AD99400868211C697A10E, + 012AD99500868211C697A10E, + 012AD99600868211C697A10E, + 012AD99700868211C697A10E, + 012AD99800868211C697A10E, + 012AD99A00868211C697A10E, + 012AD99B00868211C697A10E, + 012AD99C00868211C697A10E, + 012AD99D00868211C697A10E, + 012AD99E00868211C697A10E, + 012AD99F00868211C697A10E, + 012AD9A000868211C697A10E, + 012AD9A100868211C697A10E, + 012AD9A200868211C697A10E, + 012AD9A300868211C697A10E, + 012AD9A400868211C697A10E, + F5ABC81D013B12E701F6286D, + F5ABC81E013B12E701F6286D, + F5ABC81F013B12E701F6286D, + F5ABC820013B12E701F6286D, + F5673BEE015686CC01F628AA, + ); + isa = PBXGroup; + path = cgame; + refType = 4; + }; + 012AD98E00868211C697A10E = { + isa = PBXFileReference; + path = cg_consolecmds.c; + refType = 4; + }; + 012AD98F00868211C697A10E = { + isa = PBXFileReference; + path = cg_draw.c; + refType = 4; + }; + 012AD99000868211C697A10E = { + isa = PBXFileReference; + path = cg_drawtools.c; + refType = 4; + }; + 012AD99100868211C697A10E = { + isa = PBXFileReference; + path = cg_effects.c; + refType = 4; + }; + 012AD99200868211C697A10E = { + isa = PBXFileReference; + path = cg_ents.c; + refType = 4; + }; + 012AD99300868211C697A10E = { + isa = PBXFileReference; + path = cg_event.c; + refType = 4; + }; + 012AD99400868211C697A10E = { + isa = PBXFileReference; + path = cg_info.c; + refType = 4; + }; + 012AD99500868211C697A10E = { + isa = PBXFileReference; + path = cg_local.h; + refType = 4; + }; + 012AD99600868211C697A10E = { + isa = PBXFileReference; + path = cg_localents.c; + refType = 4; + }; + 012AD99700868211C697A10E = { + isa = PBXFileReference; + path = cg_main.c; + refType = 4; + }; + 012AD99800868211C697A10E = { + isa = PBXFileReference; + path = cg_marks.c; + refType = 4; + }; + 012AD99A00868211C697A10E = { + isa = PBXFileReference; + path = cg_players.c; + refType = 4; + }; + 012AD99B00868211C697A10E = { + isa = PBXFileReference; + path = cg_playerstate.c; + refType = 4; + }; + 012AD99C00868211C697A10E = { + isa = PBXFileReference; + path = cg_predict.c; + refType = 4; + }; + 012AD99D00868211C697A10E = { + isa = PBXFileReference; + path = cg_public.h; + refType = 4; + }; + 012AD99E00868211C697A10E = { + isa = PBXFileReference; + path = cg_scoreboard.c; + refType = 4; + }; + 012AD99F00868211C697A10E = { + isa = PBXFileReference; + path = cg_servercmds.c; + refType = 4; + }; + 012AD9A000868211C697A10E = { + isa = PBXFileReference; + path = cg_snapshot.c; + refType = 4; + }; + 012AD9A100868211C697A10E = { + isa = PBXFileReference; + path = cg_syscalls.c; + refType = 4; + }; + 012AD9A200868211C697A10E = { + isa = PBXFileReference; + path = cg_view.c; + refType = 4; + }; + 012AD9A300868211C697A10E = { + isa = PBXFileReference; + path = cg_weapons.c; + refType = 4; + }; + 012AD9A400868211C697A10E = { + isa = PBXFileReference; + path = tr_types.h; + refType = 4; + }; + 012AD9A500868211C697A10E = { + children = ( + 012AD9A600868211C697A10E, + 012AD9A700868211C697A10E, + 012AD9A800868211C697A10E, + 012AD9A900868211C697A10E, + 012AD9AA00868211C697A10E, + 012AD9AB00868211C697A10E, + 012AD9AC00868211C697A10E, + 012AD9AD00868211C697A10E, + 012AD9AE00868211C697A10E, + 012AD9AF00868211C697A10E, + 012AD9B000868211C697A10E, + 012AD9B100868211C697A10E, + 012AD9B200868211C697A10E, + 012AD9B300868211C697A10E, + 012AD9B400868211C697A10E, + 012AD9B500868211C697A10E, + 012AD9B600868211C697A10E, + 012AD9B700868211C697A10E, + 012AD9B800868211C697A10E, + ); + isa = PBXGroup; + path = client; + refType = 4; + }; + 012AD9A600868211C697A10E = { + isa = PBXFileReference; + path = cl_cgame.c; + refType = 4; + }; + 012AD9A700868211C697A10E = { + isa = PBXFileReference; + path = cl_cin.c; + refType = 4; + }; + 012AD9A800868211C697A10E = { + isa = PBXFileReference; + path = cl_console.c; + refType = 4; + }; + 012AD9A900868211C697A10E = { + isa = PBXFileReference; + path = cl_input.c; + refType = 4; + }; + 012AD9AA00868211C697A10E = { + isa = PBXFileReference; + path = cl_keys.c; + refType = 4; + }; + 012AD9AB00868211C697A10E = { + isa = PBXFileReference; + path = cl_main.c; + refType = 4; + }; + 012AD9AC00868211C697A10E = { + isa = PBXFileReference; + path = cl_net_chan.c; + refType = 4; + }; + 012AD9AD00868211C697A10E = { + isa = PBXFileReference; + path = cl_parse.c; + refType = 4; + }; + 012AD9AE00868211C697A10E = { + isa = PBXFileReference; + path = cl_scrn.c; + refType = 4; + }; + 012AD9AF00868211C697A10E = { + isa = PBXFileReference; + path = cl_ui.c; + refType = 4; + }; + 012AD9B000868211C697A10E = { + isa = PBXFileReference; + path = client.h; + refType = 4; + }; + 012AD9B100868211C697A10E = { + isa = PBXFileReference; + path = keys.h; + refType = 4; + }; + 012AD9B200868211C697A10E = { + isa = PBXFileReference; + path = snd_adpcm.c; + refType = 4; + }; + 012AD9B300868211C697A10E = { + isa = PBXFileReference; + path = snd_dma.c; + refType = 4; + }; + 012AD9B400868211C697A10E = { + isa = PBXFileReference; + path = snd_local.h; + refType = 4; + }; + 012AD9B500868211C697A10E = { + isa = PBXFileReference; + path = snd_mem.c; + refType = 4; + }; + 012AD9B600868211C697A10E = { + isa = PBXFileReference; + path = snd_mix.c; + refType = 4; + }; + 012AD9B700868211C697A10E = { + isa = PBXFileReference; + path = snd_public.h; + refType = 4; + }; + 012AD9B800868211C697A10E = { + isa = PBXFileReference; + path = snd_wavelet.c; + refType = 4; + }; + 012AD9B900868211C697A10E = { + children = ( + 012AD9BA00868211C697A10E, + 012AD9BB00868211C697A10E, + 012AD9BC00868211C697A10E, + 012AD9BD00868211C697A10E, + 012AD9BE00868211C697A10E, + 012AD9BF00868211C697A10E, + 012AD9C000868211C697A10E, + 012AD9C100868211C697A10E, + 012AD9C200868211C697A10E, + 012AD9C300868211C697A10E, + 012AD9C400868211C697A10E, + 012AD9C500868211C697A10E, + 012AD9C600868211C697A10E, + 012AD9C700868211C697A10E, + 012AD9C800868211C697A10E, + 012AD9C900868211C697A10E, + 012AD9CA00868211C697A10E, + 012AD9CB00868211C697A10E, + 012AD9CC00868211C697A10E, + 012AD9CD00868211C697A10E, + 012AD9CE00868211C697A10E, + 012AD9CF00868211C697A10E, + 012AD9D000868211C697A10E, + 012AD9D100868211C697A10E, + 012AD9D200868211C697A10E, + 012AD9D300868211C697A10E, + 012AD9D400868211C697A10E, + 012AD9D500868211C697A10E, + 012AD9D600868211C697A10E, + 012AD9D700868211C697A10E, + 012AD9D800868211C697A10E, + 012AD9D900868211C697A10E, + 012AD9DA00868211C697A10E, + 012AD9DB00868211C697A10E, + 012AD9DC00868211C697A10E, + 012AD9DD00868211C697A10E, + 012AD9DE00868211C697A10E, + 012AD9DF00868211C697A10E, + 012AD9E000868211C697A10E, + 012AD9E100868211C697A10E, + 012AD9E200868211C697A10E, + 012AD9E300868211C697A10E, + 012AD9E400868211C697A10E, + 012AD9E500868211C697A10E, + 012AD9E600868211C697A10E, + 012AD9E700868211C697A10E, + 012AD9E800868211C697A10E, + 012AD9E900868211C697A10E, + 012AD9EA00868211C697A10E, + 012AD9EB00868211C697A10E, + 012AD9EC00868211C697A10E, + 012AD9ED00868211C697A10E, + 012AD9EE00868211C697A10E, + 012AD9EF00868211C697A10E, + 012AD9F000868211C697A10E, + 012AD9F100868211C697A10E, + 012AD9F200868211C697A10E, + 012AD9F300868211C697A10E, + 012AD9F400868211C697A10E, + 012AD9F500868211C697A10E, + 012AD9F600868211C697A10E, + 012AD9F700868211C697A10E, + 012AD9F800868211C697A10E, + 012AD9F900868211C697A10E, + 012AD9FA00868211C697A10E, + 012AD9FB00868211C697A10E, + 012AD9FC00868211C697A10E, + 012AD9FD00868211C697A10E, + 012AD9FE00868211C697A10E, + 012AD9FF00868211C697A10E, + 012ADA0000868211C697A10E, + 012ADA0100868211C697A10E, + 012ADA0200868211C697A10E, + 012ADA0300868211C697A10E, + 012ADA0400868211C697A10E, + 012ADA0500868211C697A10E, + 012ADA0600868211C697A10E, + 012ADA0700868211C697A10E, + 012ADA0800868211C697A10E, + 012ADA0900868211C697A10E, + 012ADA0A00868211C697A10E, + 012ADA0B00868211C697A10E, + 012ADA0C00868211C697A10E, + 012ADA0D00868211C697A10E, + 012ADA0E00868211C697A10E, + 012ADA0F00868211C697A10E, + 012ADA1000868211C697A10E, + 012ADA1100868211C697A10E, + 012ADA1200868211C697A10E, + 012ADA1300868211C697A10E, + 012ADA1400868211C697A10E, + 012ADA1500868211C697A10E, + 012ADA1600868211C697A10E, + 012ADA1700868211C697A10E, + 012ADA1800868211C697A10E, + 012ADA1900868211C697A10E, + 012ADA1A00868211C697A10E, + 012ADA1B00868211C697A10E, + 012ADA1C00868211C697A10E, + 012ADA1D00868211C697A10E, + 012ADA1E00868211C697A10E, + 012ADA1F00868211C697A10E, + 012ADA2000868211C697A10E, + 012ADA2100868211C697A10E, + 012ADA2200868211C697A10E, + 012ADA2300868211C697A10E, + ); + isa = PBXGroup; + path = ft2; + refType = 4; + }; + 012AD9BA00868211C697A10E = { + isa = PBXFileReference; + path = ahangles.c; + refType = 4; + }; + 012AD9BB00868211C697A10E = { + isa = PBXFileReference; + path = ahangles.h; + refType = 4; + }; + 012AD9BC00868211C697A10E = { + isa = PBXFileReference; + path = ahglobal.c; + refType = 4; + }; + 012AD9BD00868211C697A10E = { + isa = PBXFileReference; + path = ahglobal.h; + refType = 4; + }; + 012AD9BE00868211C697A10E = { + isa = PBXFileReference; + path = ahglyph.c; + refType = 4; + }; + 012AD9BF00868211C697A10E = { + isa = PBXFileReference; + path = ahglyph.h; + refType = 4; + }; + 012AD9C000868211C697A10E = { + isa = PBXFileReference; + path = ahhint.c; + refType = 4; + }; + 012AD9C100868211C697A10E = { + isa = PBXFileReference; + path = ahhint.h; + refType = 4; + }; + 012AD9C200868211C697A10E = { + isa = PBXFileReference; + path = ahloader.h; + refType = 4; + }; + 012AD9C300868211C697A10E = { + isa = PBXFileReference; + path = ahmodule.c; + refType = 4; + }; + 012AD9C400868211C697A10E = { + isa = PBXFileReference; + path = ahmodule.h; + refType = 4; + }; + 012AD9C500868211C697A10E = { + isa = PBXFileReference; + path = ahoptim.c; + refType = 4; + }; + 012AD9C600868211C697A10E = { + isa = PBXFileReference; + path = ahoptim.h; + refType = 4; + }; + 012AD9C700868211C697A10E = { + isa = PBXFileReference; + path = ahtypes.h; + refType = 4; + }; + 012AD9C800868211C697A10E = { + isa = PBXFileReference; + path = autohint.c; + refType = 4; + }; + 012AD9C900868211C697A10E = { + isa = PBXFileReference; + path = autohint.h; + refType = 4; + }; + 012AD9CA00868211C697A10E = { + isa = PBXFileReference; + path = freetype.h; + refType = 4; + }; + 012AD9CB00868211C697A10E = { + isa = PBXFileReference; + path = ftbase.c; + refType = 4; + }; + 012AD9CC00868211C697A10E = { + isa = PBXFileReference; + path = ftbbox.c; + refType = 4; + }; + 012AD9CD00868211C697A10E = { + isa = PBXFileReference; + path = ftbbox.h; + refType = 4; + }; + 012AD9CE00868211C697A10E = { + isa = PBXFileReference; + path = ftcalc.c; + refType = 4; + }; + 012AD9CF00868211C697A10E = { + isa = PBXFileReference; + path = ftcalc.h; + refType = 4; + }; + 012AD9D000868211C697A10E = { + isa = PBXFileReference; + path = ftconfig.h; + refType = 4; + }; + 012AD9D100868211C697A10E = { + isa = PBXFileReference; + path = ftdebug.c; + refType = 4; + }; + 012AD9D200868211C697A10E = { + isa = PBXFileReference; + path = ftdebug.h; + refType = 4; + }; + 012AD9D300868211C697A10E = { + isa = PBXFileReference; + path = ftdriver.h; + refType = 4; + }; + 012AD9D400868211C697A10E = { + isa = PBXFileReference; + path = fterrors.h; + refType = 4; + }; + 012AD9D500868211C697A10E = { + isa = PBXFileReference; + path = ftextend.c; + refType = 4; + }; + 012AD9D600868211C697A10E = { + isa = PBXFileReference; + path = ftextend.h; + refType = 4; + }; + 012AD9D700868211C697A10E = { + isa = PBXFileReference; + path = ftglyph.c; + refType = 4; + }; + 012AD9D800868211C697A10E = { + isa = PBXFileReference; + path = ftglyph.h; + refType = 4; + }; + 012AD9D900868211C697A10E = { + isa = PBXFileReference; + path = ftgrays.c; + refType = 4; + }; + 012AD9DA00868211C697A10E = { + isa = PBXFileReference; + path = ftgrays.h; + refType = 4; + }; + 012AD9DB00868211C697A10E = { + isa = PBXFileReference; + path = ftimage.h; + refType = 4; + }; + 012AD9DC00868211C697A10E = { + isa = PBXFileReference; + path = ftinit.c; + refType = 4; + }; + 012AD9DD00868211C697A10E = { + isa = PBXFileReference; + path = ftlist.c; + refType = 4; + }; + 012AD9DE00868211C697A10E = { + isa = PBXFileReference; + path = ftlist.h; + refType = 4; + }; + 012AD9DF00868211C697A10E = { + isa = PBXFileReference; + path = ftmemory.h; + refType = 4; + }; + 012AD9E000868211C697A10E = { + isa = PBXFileReference; + path = ftmm.c; + refType = 4; + }; + 012AD9E100868211C697A10E = { + isa = PBXFileReference; + path = ftmm.h; + refType = 4; + }; + 012AD9E200868211C697A10E = { + isa = PBXFileReference; + path = ftmodule.h; + refType = 4; + }; + 012AD9E300868211C697A10E = { + isa = PBXFileReference; + path = ftnames.c; + refType = 4; + }; + 012AD9E400868211C697A10E = { + isa = PBXFileReference; + path = ftnames.h; + refType = 4; + }; + 012AD9E500868211C697A10E = { + isa = PBXFileReference; + path = ftobjs.c; + refType = 4; + }; + 012AD9E600868211C697A10E = { + isa = PBXFileReference; + path = ftobjs.h; + refType = 4; + }; + 012AD9E700868211C697A10E = { + isa = PBXFileReference; + path = ftoption.h; + refType = 4; + }; + 012AD9E800868211C697A10E = { + isa = PBXFileReference; + path = ftoutln.c; + refType = 4; + }; + 012AD9E900868211C697A10E = { + isa = PBXFileReference; + path = ftoutln.h; + refType = 4; + }; + 012AD9EA00868211C697A10E = { + isa = PBXFileReference; + path = ftraster.c; + refType = 4; + }; + 012AD9EB00868211C697A10E = { + isa = PBXFileReference; + path = ftraster.h; + refType = 4; + }; + 012AD9EC00868211C697A10E = { + isa = PBXFileReference; + path = ftrend1.c; + refType = 4; + }; + 012AD9ED00868211C697A10E = { + isa = PBXFileReference; + path = ftrend1.h; + refType = 4; + }; + 012AD9EE00868211C697A10E = { + isa = PBXFileReference; + path = ftrender.h; + refType = 4; + }; + 012AD9EF00868211C697A10E = { + isa = PBXFileReference; + path = ftsmooth.c; + refType = 4; + }; + 012AD9F000868211C697A10E = { + isa = PBXFileReference; + path = ftsmooth.h; + refType = 4; + }; + 012AD9F100868211C697A10E = { + isa = PBXFileReference; + path = ftstream.c; + refType = 4; + }; + 012AD9F200868211C697A10E = { + isa = PBXFileReference; + path = ftstream.h; + refType = 4; + }; + 012AD9F300868211C697A10E = { + isa = PBXFileReference; + path = ftsystem.c; + refType = 4; + }; + 012AD9F400868211C697A10E = { + isa = PBXFileReference; + path = ftsystem.h; + refType = 4; + }; + 012AD9F500868211C697A10E = { + isa = PBXFileReference; + path = fttypes.h; + refType = 4; + }; + 012AD9F600868211C697A10E = { + isa = PBXFileReference; + path = keys.h; + refType = 4; + }; + 012AD9F700868211C697A10E = { + isa = PBXFileReference; + path = psdriver.c; + refType = 4; + }; + 012AD9F800868211C697A10E = { + isa = PBXFileReference; + path = psdriver.h; + refType = 4; + }; + 012AD9F900868211C697A10E = { + isa = PBXFileReference; + path = psnames.c; + refType = 4; + }; + 012AD9FA00868211C697A10E = { + isa = PBXFileReference; + path = psnames.h; + refType = 4; + }; + 012AD9FB00868211C697A10E = { + isa = PBXFileReference; + path = pstables.h; + refType = 4; + }; + 012AD9FC00868211C697A10E = { + isa = PBXFileReference; + path = raster1.c; + refType = 4; + }; + 012AD9FD00868211C697A10E = { + isa = PBXFileReference; + path = sfconfig.h; + refType = 4; + }; + 012AD9FE00868211C697A10E = { + isa = PBXFileReference; + path = sfdriver.c; + refType = 4; + }; + 012AD9FF00868211C697A10E = { + isa = PBXFileReference; + path = sfdriver.h; + refType = 4; + }; + 012ADA0000868211C697A10E = { + isa = PBXFileReference; + path = sfnt.c; + refType = 4; + }; + 012ADA0100868211C697A10E = { + isa = PBXFileReference; + path = sfnt.h; + refType = 4; + }; + 012ADA0200868211C697A10E = { + isa = PBXFileReference; + path = sfobjs.c; + refType = 4; + }; + 012ADA0300868211C697A10E = { + isa = PBXFileReference; + path = sfobjs.h; + refType = 4; + }; + 012ADA0400868211C697A10E = { + isa = PBXFileReference; + path = smooth.c; + refType = 4; + }; + 012ADA0500868211C697A10E = { + isa = PBXFileReference; + path = t1errors.h; + refType = 4; + }; + 012ADA0600868211C697A10E = { + isa = PBXFileReference; + path = t1tables.h; + refType = 4; + }; + 012ADA0700868211C697A10E = { + isa = PBXFileReference; + path = t1types.h; + refType = 4; + }; + 012ADA0800868211C697A10E = { + isa = PBXFileReference; + path = t2errors.h; + refType = 4; + }; + 012ADA0900868211C697A10E = { + isa = PBXFileReference; + path = t2types.h; + refType = 4; + }; + 012ADA0A00868211C697A10E = { + isa = PBXFileReference; + path = truetype.c; + refType = 4; + }; + 012ADA0B00868211C697A10E = { + isa = PBXFileReference; + path = ttcmap.c; + refType = 4; + }; + 012ADA0C00868211C697A10E = { + isa = PBXFileReference; + path = ttcmap.h; + refType = 4; + }; + 012ADA0D00868211C697A10E = { + isa = PBXFileReference; + path = ttconfig.h; + refType = 4; + }; + 012ADA0E00868211C697A10E = { + isa = PBXFileReference; + path = ttdriver.c; + refType = 4; + }; + 012ADA0F00868211C697A10E = { + isa = PBXFileReference; + path = ttdriver.h; + refType = 4; + }; + 012ADA1000868211C697A10E = { + isa = PBXFileReference; + path = tterrors.h; + refType = 4; + }; + 012ADA1100868211C697A10E = { + isa = PBXFileReference; + path = ttgload.c; + refType = 4; + }; + 012ADA1200868211C697A10E = { + isa = PBXFileReference; + path = ttgload.h; + refType = 4; + }; + 012ADA1300868211C697A10E = { + isa = PBXFileReference; + path = ttinterp.c; + refType = 4; + }; + 012ADA1400868211C697A10E = { + isa = PBXFileReference; + path = ttinterp.h; + refType = 4; + }; + 012ADA1500868211C697A10E = { + isa = PBXFileReference; + path = ttload.c; + refType = 4; + }; + 012ADA1600868211C697A10E = { + isa = PBXFileReference; + path = ttload.h; + refType = 4; + }; + 012ADA1700868211C697A10E = { + isa = PBXFileReference; + path = ttnamedid.h; + refType = 4; + }; + 012ADA1800868211C697A10E = { + isa = PBXFileReference; + path = ttnameid.h; + refType = 4; + }; + 012ADA1900868211C697A10E = { + isa = PBXFileReference; + path = ttobjs.c; + refType = 4; + }; + 012ADA1A00868211C697A10E = { + isa = PBXFileReference; + path = ttobjs.h; + refType = 4; + }; + 012ADA1B00868211C697A10E = { + isa = PBXFileReference; + path = ttpload.c; + refType = 4; + }; + 012ADA1C00868211C697A10E = { + isa = PBXFileReference; + path = ttpload.h; + refType = 4; + }; + 012ADA1D00868211C697A10E = { + isa = PBXFileReference; + path = ttpost.c; + refType = 4; + }; + 012ADA1E00868211C697A10E = { + isa = PBXFileReference; + path = ttpost.h; + refType = 4; + }; + 012ADA1F00868211C697A10E = { + isa = PBXFileReference; + path = ttsbit.c; + refType = 4; + }; + 012ADA2000868211C697A10E = { + isa = PBXFileReference; + path = ttsbit.h; + refType = 4; + }; + 012ADA2100868211C697A10E = { + isa = PBXFileReference; + path = tttables.h; + refType = 4; + }; + 012ADA2200868211C697A10E = { + isa = PBXFileReference; + path = tttags.h; + refType = 4; + }; + 012ADA2300868211C697A10E = { + isa = PBXFileReference; + path = tttypes.h; + refType = 4; + }; + 012ADA2400868211C697A10E = { + children = ( + 012ADA3300868211C697A10E, + 012ADA3400868211C697A10E, + 012ADA3500868211C697A10E, + 012ADA3600868211C697A10E, + 012ADA3700868211C697A10E, + 012ADA3800868211C697A10E, + 012ADA3900868211C697A10E, + 012ADA3A00868211C697A10E, + 012ADA3B00868211C697A10E, + 012ADA3D00868211C697A10E, + 012ADA3E00868211C697A10E, + 012ADA3F00868211C697A10E, + 012ADA4000868211C697A10E, + 012ADA4100868211C697A10E, + 012ADA4200868211C697A10E, + 012ADA4400868211C697A10E, + 012ADA4600868211C697A10E, + 012ADA4700868211C697A10E, + 012ADA4800868211C697A10E, + 012ADA4900868211C697A10E, + 012ADA4A00868211C697A10E, + 012ADA4B00868211C697A10E, + 012ADA4C00868211C697A10E, + 012ADA4D00868211C697A10E, + 012ADA4E00868211C697A10E, + 012ADA4F00868211C697A10E, + 012ADA5000868211C697A10E, + 012ADA5100868211C697A10E, + 012ADA5400868211C697A10E, + 012ADA5500868211C697A10E, + 012ADA5600868211C697A10E, + 012ADA5700868211C697A10E, + 012ADA5800868211C697A10E, + 012ADA5900868211C697A10E, + 012ADA5A00868211C697A10E, + 012ADA5B00868211C697A10E, + 012ADA5C00868211C697A10E, + 012ADA5D00868211C697A10E, + 012ADA6000868211C697A10E, + 012ADA6100868211C697A10E, + 012ADA6200868211C697A10E, + 012ADA6300868211C697A10E, + F5ABC7C7013B0EAB01F6286D, + F5ABC7C8013B0EAB01F6286D, + F5ABC7C9013B0EAB01F6286D, + F5ABC7CA013B0EAB01F6286D, + F5ABC7CB013B0EAB01F6286D, + F5ABC7CC013B0EAB01F6286D, + F5ABC7CD013B0EAB01F6286D, + F5ABC7CE013B0EAB01F6286D, + F5ABC7CF013B0EAB01F6286D, + F5ABC7D0013B0EAB01F6286D, + F5ABC7D1013B0EAB01F6286D, + F5ABC7D2013B0EAB01F6286D, + F5ABC7D3013B0EAB01F6286D, + F5ABC7D4013B0EAB01F6286D, + F5ABC7D5013B0EAB01F6286D, + F5ABC7D6013B0EAB01F6286D, + F5ABC7D7013B0EAB01F6286D, + F5ABC7D8013B0EAB01F6286D, + F5ABC7D9013B0EAB01F6286D, + F5ABC7DA013B0EAB01F6286D, + F5ABC7DB013B0EAB01F6286D, + F5ABC7DC013B0EAB01F6286D, + F5ABC7DD013B0EAB01F6286D, + F5ABC7DE013B0EAB01F6286D, + F5ABC7FB013B120B01F6286D, + F5ABC7FD013B120B01F6286D, + F5ABC7FF013B120B01F6286D, + F5ABC801013B120B01F6286D, + F5ABC802013B120B01F6286D, + F5ABC803013B120B01F6286D, + F5ABC804013B120B01F6286D, + F5ABC805013B120B01F6286D, + F5ABC806013B120B01F6286D, + F55DF57A01513A8101F6286D, + ); + isa = PBXGroup; + path = game; + refType = 4; + }; + 012ADA3300868211C697A10E = { + isa = PBXFileReference; + path = be_aas.h; + refType = 4; + }; + 012ADA3400868211C697A10E = { + isa = PBXFileReference; + path = be_ai_char.h; + refType = 4; + }; + 012ADA3500868211C697A10E = { + isa = PBXFileReference; + path = be_ai_chat.h; + refType = 4; + }; + 012ADA3600868211C697A10E = { + isa = PBXFileReference; + path = be_ai_gen.h; + refType = 4; + }; + 012ADA3700868211C697A10E = { + isa = PBXFileReference; + path = be_ai_goal.h; + refType = 4; + }; + 012ADA3800868211C697A10E = { + isa = PBXFileReference; + path = be_ai_move.h; + refType = 4; + }; + 012ADA3900868211C697A10E = { + isa = PBXFileReference; + path = be_ai_weap.h; + refType = 4; + }; + 012ADA3A00868211C697A10E = { + isa = PBXFileReference; + path = be_ea.h; + refType = 4; + }; + 012ADA3B00868211C697A10E = { + isa = PBXFileReference; + path = bg_lib.c; + refType = 4; + }; + 012ADA3D00868211C697A10E = { + isa = PBXFileReference; + path = bg_local.h; + refType = 4; + }; + 012ADA3E00868211C697A10E = { + isa = PBXFileReference; + path = bg_misc.c; + refType = 4; + }; + 012ADA3F00868211C697A10E = { + isa = PBXFileReference; + path = bg_pmove.c; + refType = 4; + }; + 012ADA4000868211C697A10E = { + isa = PBXFileReference; + path = bg_public.h; + refType = 4; + }; + 012ADA4100868211C697A10E = { + isa = PBXFileReference; + path = bg_slidemove.c; + refType = 4; + }; + 012ADA4200868211C697A10E = { + isa = PBXFileReference; + path = botlib.h; + refType = 4; + }; + 012ADA4400868211C697A10E = { + isa = PBXFileReference; + path = g_active.c; + refType = 4; + }; + 012ADA4600868211C697A10E = { + isa = PBXFileReference; + path = g_bot.c; + refType = 4; + }; + 012ADA4700868211C697A10E = { + isa = PBXFileReference; + path = g_client.c; + refType = 4; + }; + 012ADA4800868211C697A10E = { + isa = PBXFileReference; + path = g_cmds.c; + refType = 4; + }; + 012ADA4900868211C697A10E = { + isa = PBXFileReference; + path = g_combat.c; + refType = 4; + }; + 012ADA4A00868211C697A10E = { + isa = PBXFileReference; + path = g_items.c; + refType = 4; + }; + 012ADA4B00868211C697A10E = { + isa = PBXFileReference; + path = g_local.h; + refType = 4; + }; + 012ADA4C00868211C697A10E = { + isa = PBXFileReference; + path = g_main.c; + refType = 4; + }; + 012ADA4D00868211C697A10E = { + isa = PBXFileReference; + path = g_mem.c; + refType = 4; + }; + 012ADA4E00868211C697A10E = { + isa = PBXFileReference; + path = g_misc.c; + refType = 4; + }; + 012ADA4F00868211C697A10E = { + isa = PBXFileReference; + path = g_missile.c; + refType = 4; + }; + 012ADA5000868211C697A10E = { + isa = PBXFileReference; + path = g_mover.c; + refType = 4; + }; + 012ADA5100868211C697A10E = { + isa = PBXFileReference; + path = g_public.h; + refType = 4; + }; + 012ADA5400868211C697A10E = { + isa = PBXFileReference; + path = g_session.c; + refType = 4; + }; + 012ADA5500868211C697A10E = { + isa = PBXFileReference; + path = g_spawn.c; + refType = 4; + }; + 012ADA5600868211C697A10E = { + isa = PBXFileReference; + path = g_svcmds.c; + refType = 4; + }; + 012ADA5700868211C697A10E = { + isa = PBXFileReference; + path = g_syscalls.c; + refType = 4; + }; + 012ADA5800868211C697A10E = { + isa = PBXFileReference; + path = g_target.c; + refType = 4; + }; + 012ADA5900868211C697A10E = { + isa = PBXFileReference; + path = g_team.c; + refType = 4; + }; + 012ADA5A00868211C697A10E = { + isa = PBXFileReference; + path = g_team.h; + refType = 4; + }; + 012ADA5B00868211C697A10E = { + isa = PBXFileReference; + path = g_trigger.c; + refType = 4; + }; + 012ADA5C00868211C697A10E = { + isa = PBXFileReference; + path = g_utils.c; + refType = 4; + }; + 012ADA5D00868211C697A10E = { + isa = PBXFileReference; + path = g_weapon.c; + refType = 4; + }; + 012ADA6000868211C697A10E = { + isa = PBXFileReference; + path = q_math.c; + refType = 4; + }; + 012ADA6100868211C697A10E = { + isa = PBXFileReference; + path = q_shared.c; + refType = 4; + }; + 012ADA6200868211C697A10E = { + isa = PBXFileReference; + path = q_shared.h; + refType = 4; + }; + 012ADA6300868211C697A10E = { + isa = PBXFileReference; + path = surfaceflags.h; + refType = 4; + }; + 012ADA6500868211C697A10E = { + children = ( + 012ADA6600868211C697A10E, + 012ADA6800868211C697A10E, + 012ADA6900868211C697A10E, + 012ADA6A00868211C697A10E, + 012ADA6B00868211C697A10E, + 012ADA6C00868211C697A10E, + 012ADA6D00868211C697A10E, + 012ADA6E00868211C697A10E, + 012ADA6F00868211C697A10E, + 012ADA7000868211C697A10E, + 012ADA7100868211C697A10E, + 012ADA7200868211C697A10E, + 012ADA7300868211C697A10E, + 012ADA7400868211C697A10E, + 012ADA7500868211C697A10E, + 012ADA7600868211C697A10E, + 012ADA7700868211C697A10E, + 012ADA7800868211C697A10E, + 012ADA7900868211C697A10E, + 012ADA7A00868211C697A10E, + 012ADA7B00868211C697A10E, + 012ADA7C00868211C697A10E, + 012ADA7D00868211C697A10E, + 012ADA7E00868211C697A10E, + 012ADA7F00868211C697A10E, + 012ADA8000868211C697A10E, + 012ADA8100868211C697A10E, + 012ADA8200868211C697A10E, + 012ADA8300868211C697A10E, + 012ADA8400868211C697A10E, + 012ADA8500868211C697A10E, + 012ADA8800868211C697A10E, + 012ADA8900868211C697A10E, + 012ADA8A00868211C697A10E, + 012ADA8B00868211C697A10E, + 012ADA8C00868211C697A10E, + 012ADA8D00868211C697A10E, + 012ADA9000868211C697A10E, + 012ADA9400868211C697A10E, + 012ADA9800868211C697A10E, + 012ADA9A00868211C697A10E, + 012ADA9B00868211C697A10E, + 012ADA9C00868211C697A10E, + 012ADA9D00868211C697A10E, + 012ADA9E00868211C697A10E, + 012ADAA200868211C697A10E, + 012ADAA300868211C697A10E, + ); + isa = PBXGroup; + path = "jpeg-6"; + refType = 4; + }; + 012ADA6600868211C697A10E = { + isa = PBXFileReference; + path = jcapimin.c; + refType = 4; + }; + 012ADA6800868211C697A10E = { + isa = PBXFileReference; + path = jccoefct.c; + refType = 4; + }; + 012ADA6900868211C697A10E = { + isa = PBXFileReference; + path = jccolor.c; + refType = 4; + }; + 012ADA6A00868211C697A10E = { + isa = PBXFileReference; + path = jcdctmgr.c; + refType = 4; + }; + 012ADA6B00868211C697A10E = { + isa = PBXFileReference; + path = jchuff.c; + refType = 4; + }; + 012ADA6C00868211C697A10E = { + isa = PBXFileReference; + path = jchuff.h; + refType = 4; + }; + 012ADA6D00868211C697A10E = { + isa = PBXFileReference; + path = jcinit.c; + refType = 4; + }; + 012ADA6E00868211C697A10E = { + isa = PBXFileReference; + path = jcmainct.c; + refType = 4; + }; + 012ADA6F00868211C697A10E = { + isa = PBXFileReference; + path = jcmarker.c; + refType = 4; + }; + 012ADA7000868211C697A10E = { + isa = PBXFileReference; + path = jcmaster.c; + refType = 4; + }; + 012ADA7100868211C697A10E = { + isa = PBXFileReference; + path = jcomapi.c; + refType = 4; + }; + 012ADA7200868211C697A10E = { + isa = PBXFileReference; + path = jconfig.h; + refType = 4; + }; + 012ADA7300868211C697A10E = { + isa = PBXFileReference; + path = jcparam.c; + refType = 4; + }; + 012ADA7400868211C697A10E = { + isa = PBXFileReference; + path = jcphuff.c; + refType = 4; + }; + 012ADA7500868211C697A10E = { + isa = PBXFileReference; + path = jcprepct.c; + refType = 4; + }; + 012ADA7600868211C697A10E = { + isa = PBXFileReference; + path = jcsample.c; + refType = 4; + }; + 012ADA7700868211C697A10E = { + isa = PBXFileReference; + path = jctrans.c; + refType = 4; + }; + 012ADA7800868211C697A10E = { + isa = PBXFileReference; + path = jdapimin.c; + refType = 4; + }; + 012ADA7900868211C697A10E = { + isa = PBXFileReference; + path = jdapistd.c; + refType = 4; + }; + 012ADA7A00868211C697A10E = { + isa = PBXFileReference; + path = jdatadst.c; + refType = 4; + }; + 012ADA7B00868211C697A10E = { + isa = PBXFileReference; + path = jdatasrc.c; + refType = 4; + }; + 012ADA7C00868211C697A10E = { + isa = PBXFileReference; + path = jdcoefct.c; + refType = 4; + }; + 012ADA7D00868211C697A10E = { + isa = PBXFileReference; + path = jdcolor.c; + refType = 4; + }; + 012ADA7E00868211C697A10E = { + isa = PBXFileReference; + path = jdct.h; + refType = 4; + }; + 012ADA7F00868211C697A10E = { + isa = PBXFileReference; + path = jddctmgr.c; + refType = 4; + }; + 012ADA8000868211C697A10E = { + isa = PBXFileReference; + path = jdhuff.c; + refType = 4; + }; + 012ADA8100868211C697A10E = { + isa = PBXFileReference; + path = jdhuff.h; + refType = 4; + }; + 012ADA8200868211C697A10E = { + isa = PBXFileReference; + path = jdinput.c; + refType = 4; + }; + 012ADA8300868211C697A10E = { + isa = PBXFileReference; + path = jdmainct.c; + refType = 4; + }; + 012ADA8400868211C697A10E = { + isa = PBXFileReference; + path = jdmarker.c; + refType = 4; + }; + 012ADA8500868211C697A10E = { + isa = PBXFileReference; + path = jdmaster.c; + refType = 4; + }; + 012ADA8800868211C697A10E = { + isa = PBXFileReference; + path = jdpostct.c; + refType = 4; + }; + 012ADA8900868211C697A10E = { + isa = PBXFileReference; + path = jdsample.c; + refType = 4; + }; + 012ADA8A00868211C697A10E = { + isa = PBXFileReference; + path = jdtrans.c; + refType = 4; + }; + 012ADA8B00868211C697A10E = { + isa = PBXFileReference; + path = jerror.c; + refType = 4; + }; + 012ADA8C00868211C697A10E = { + isa = PBXFileReference; + path = jerror.h; + refType = 4; + }; + 012ADA8D00868211C697A10E = { + isa = PBXFileReference; + path = jfdctflt.c; + refType = 4; + }; + 012ADA9000868211C697A10E = { + isa = PBXFileReference; + path = jidctflt.c; + refType = 4; + }; + 012ADA9400868211C697A10E = { + isa = PBXFileReference; + path = jinclude.h; + refType = 4; + }; + 012ADA9800868211C697A10E = { + isa = PBXFileReference; + path = jmemmgr.c; + refType = 4; + }; + 012ADA9A00868211C697A10E = { + isa = PBXFileReference; + path = jmemnobs.c; + refType = 4; + }; + 012ADA9B00868211C697A10E = { + isa = PBXFileReference; + path = jmemsys.h; + refType = 4; + }; + 012ADA9C00868211C697A10E = { + isa = PBXFileReference; + path = jmorecfg.h; + refType = 4; + }; + 012ADA9D00868211C697A10E = { + isa = PBXFileReference; + path = jpegint.h; + refType = 4; + }; + 012ADA9E00868211C697A10E = { + isa = PBXFileReference; + path = jpeglib.h; + refType = 4; + }; + 012ADAA200868211C697A10E = { + isa = PBXFileReference; + path = jutils.c; + refType = 4; + }; + 012ADAA300868211C697A10E = { + isa = PBXFileReference; + path = jversion.h; + refType = 4; + }; + 012ADAEC00868211C697A10E = { + children = ( + 012ADAED00868211C697A10E, + 012ADAEE00868211C697A10E, + 012ADAEF00868211C697A10E, + 012ADAF000868211C697A10E, + 012ADAF100868211C697A10E, + 012ADAF200868211C697A10E, + 012ADAF300868211C697A10E, + 012ADAF400868211C697A10E, + 012ADAF500868211C697A10E, + 012ADAF600868211C697A10E, + 012ADAF700868211C697A10E, + 012ADAF800868211C697A10E, + 012ADAF900868211C697A10E, + 016F1B6300ACDA9BC697A10E, + 012ADAFA00868211C697A10E, + 012ADAFB00868211C697A10E, + 012ADAFC00868211C697A10E, + 012ADAFD00868211C697A10E, + 012ADAFE00868211C697A10E, + 012ADAFF00868211C697A10E, + 012ADB0000868211C697A10E, + 012ADB0100868211C697A10E, + 012ADB0200868211C697A10E, + 012ADB0300868211C697A10E, + ); + isa = PBXGroup; + path = qcommon; + refType = 4; + }; + 012ADAED00868211C697A10E = { + isa = PBXFileReference; + path = cm_load.c; + refType = 4; + }; + 012ADAEE00868211C697A10E = { + isa = PBXFileReference; + path = cm_local.h; + refType = 4; + }; + 012ADAEF00868211C697A10E = { + isa = PBXFileReference; + path = cm_patch.c; + refType = 4; + }; + 012ADAF000868211C697A10E = { + isa = PBXFileReference; + path = cm_patch.h; + refType = 4; + }; + 012ADAF100868211C697A10E = { + isa = PBXFileReference; + path = cm_polylib.c; + refType = 4; + }; + 012ADAF200868211C697A10E = { + isa = PBXFileReference; + path = cm_polylib.h; + refType = 4; + }; + 012ADAF300868211C697A10E = { + isa = PBXFileReference; + path = cm_public.h; + refType = 4; + }; + 012ADAF400868211C697A10E = { + isa = PBXFileReference; + path = cm_test.c; + refType = 4; + }; + 012ADAF500868211C697A10E = { + isa = PBXFileReference; + path = cm_trace.c; + refType = 4; + }; + 012ADAF600868211C697A10E = { + isa = PBXFileReference; + path = cmd.c; + refType = 4; + }; + 012ADAF700868211C697A10E = { + isa = PBXFileReference; + path = common.c; + refType = 4; + }; + 012ADAF800868211C697A10E = { + isa = PBXFileReference; + path = cvar.c; + refType = 4; + }; + 012ADAF900868211C697A10E = { + isa = PBXFileReference; + path = files.c; + refType = 4; + }; + 012ADAFA00868211C697A10E = { + isa = PBXFileReference; + path = md4.c; + refType = 4; + }; + 012ADAFB00868211C697A10E = { + isa = PBXFileReference; + path = msg.c; + refType = 4; + }; + 012ADAFC00868211C697A10E = { + isa = PBXFileReference; + path = net_chan.c; + refType = 4; + }; + 012ADAFD00868211C697A10E = { + isa = PBXFileReference; + path = qcommon.h; + refType = 4; + }; + 012ADAFE00868211C697A10E = { + isa = PBXFileReference; + path = qfiles.h; + refType = 4; + }; + 012ADAFF00868211C697A10E = { + isa = PBXFileReference; + path = unzip.c; + refType = 4; + }; + 012ADB0000868211C697A10E = { + isa = PBXFileReference; + path = unzip.h; + refType = 4; + }; + 012ADB0100868211C697A10E = { + isa = PBXFileReference; + path = vm.c; + refType = 4; + }; + 012ADB0200868211C697A10E = { + isa = PBXFileReference; + path = vm_interpreted.c; + refType = 4; + }; + 012ADB0300868211C697A10E = { + isa = PBXFileReference; + path = vm_local.h; + refType = 4; + }; + 012ADB0600868211C697A10E = { + children = ( + 012ADB0800868211C697A10E, + 012ADB0A00868211C697A10E, + 012ADB0B00868211C697A10E, + 012ADB0C00868211C697A10E, + 012ADB0D00868211C697A10E, + 012ADB0E00868211C697A10E, + 012ADB0F00868211C697A10E, + 012ADB1000868211C697A10E, + 012ADB1100868211C697A10E, + 012ADB1200868211C697A10E, + 012ADB1300868211C697A10E, + 012ADB1400868211C697A10E, + 012ADB1500868211C697A10E, + 012ADB1600868211C697A10E, + 012ADB1700868211C697A10E, + 012ADB1800868211C697A10E, + 012ADB1900868211C697A10E, + 012ADB1A00868211C697A10E, + 012ADB1B00868211C697A10E, + 012ADB1C00868211C697A10E, + 012ADB1D00868211C697A10E, + 012ADB1E00868211C697A10E, + 012ADB1F00868211C697A10E, + 012ADB2000868211C697A10E, + 012ADB2100868211C697A10E, + 012ADB2200868211C697A10E, + F5ABC7B9013B054201F6286D, + ); + isa = PBXGroup; + path = renderer; + refType = 4; + }; + 012ADB0800868211C697A10E = { + isa = PBXFileReference; + path = qgl.h; + refType = 4; + }; + 012ADB0A00868211C697A10E = { + isa = PBXFileReference; + path = tr_animation.c; + refType = 4; + }; + 012ADB0B00868211C697A10E = { + isa = PBXFileReference; + path = tr_backend.c; + refType = 4; + }; + 012ADB0C00868211C697A10E = { + isa = PBXFileReference; + path = tr_bsp.c; + refType = 4; + }; + 012ADB0D00868211C697A10E = { + isa = PBXFileReference; + path = tr_cmds.c; + refType = 4; + }; + 012ADB0E00868211C697A10E = { + isa = PBXFileReference; + path = tr_curve.c; + refType = 4; + }; + 012ADB0F00868211C697A10E = { + isa = PBXFileReference; + path = tr_flares.c; + refType = 4; + }; + 012ADB1000868211C697A10E = { + isa = PBXFileReference; + path = tr_font.c; + refType = 4; + }; + 012ADB1100868211C697A10E = { + isa = PBXFileReference; + path = tr_image.c; + refType = 4; + }; + 012ADB1200868211C697A10E = { + isa = PBXFileReference; + path = tr_init.c; + refType = 4; + }; + 012ADB1300868211C697A10E = { + isa = PBXFileReference; + path = tr_light.c; + refType = 4; + }; + 012ADB1400868211C697A10E = { + isa = PBXFileReference; + path = tr_local.h; + refType = 4; + }; + 012ADB1500868211C697A10E = { + isa = PBXFileReference; + path = tr_main.c; + refType = 4; + }; + 012ADB1600868211C697A10E = { + isa = PBXFileReference; + path = tr_marks.c; + refType = 4; + }; + 012ADB1700868211C697A10E = { + isa = PBXFileReference; + path = tr_mesh.c; + refType = 4; + }; + 012ADB1800868211C697A10E = { + isa = PBXFileReference; + path = tr_model.c; + refType = 4; + }; + 012ADB1900868211C697A10E = { + isa = PBXFileReference; + path = tr_noise.c; + refType = 4; + }; + 012ADB1A00868211C697A10E = { + isa = PBXFileReference; + path = tr_public.h; + refType = 4; + }; + 012ADB1B00868211C697A10E = { + isa = PBXFileReference; + path = tr_scene.c; + refType = 4; + }; + 012ADB1C00868211C697A10E = { + isa = PBXFileReference; + path = tr_shade.c; + refType = 4; + }; + 012ADB1D00868211C697A10E = { + isa = PBXFileReference; + path = tr_shade_calc.c; + refType = 4; + }; + 012ADB1E00868211C697A10E = { + isa = PBXFileReference; + path = tr_shader.c; + refType = 4; + }; + 012ADB1F00868211C697A10E = { + isa = PBXFileReference; + path = tr_shadows.c; + refType = 4; + }; + 012ADB2000868211C697A10E = { + isa = PBXFileReference; + path = tr_sky.c; + refType = 4; + }; + 012ADB2100868211C697A10E = { + isa = PBXFileReference; + path = tr_surface.c; + refType = 4; + }; + 012ADB2200868211C697A10E = { + isa = PBXFileReference; + path = tr_world.c; + refType = 4; + }; + 012ADB2300868211C697A10E = { + children = ( + 012ADB2400868211C697A10E, + 012ADB2500868211C697A10E, + 012ADB2600868211C697A10E, + 012ADB2700868211C697A10E, + 012ADB2800868211C697A10E, + 012ADB2900868211C697A10E, + 012ADB2A00868211C697A10E, + 012ADB2B00868211C697A10E, + 012ADB2D00868211C697A10E, + 012ADB2E00868211C697A10E, + ); + isa = PBXGroup; + path = server; + refType = 4; + }; + 012ADB2400868211C697A10E = { + isa = PBXFileReference; + path = server.h; + refType = 4; + }; + 012ADB2500868211C697A10E = { + isa = PBXFileReference; + path = sv_bot.c; + refType = 4; + }; + 012ADB2600868211C697A10E = { + isa = PBXFileReference; + path = sv_ccmds.c; + refType = 4; + }; + 012ADB2700868211C697A10E = { + isa = PBXFileReference; + path = sv_client.c; + refType = 4; + }; + 012ADB2800868211C697A10E = { + isa = PBXFileReference; + path = sv_game.c; + refType = 4; + }; + 012ADB2900868211C697A10E = { + isa = PBXFileReference; + path = sv_init.c; + refType = 4; + }; + 012ADB2A00868211C697A10E = { + isa = PBXFileReference; + path = sv_main.c; + refType = 4; + }; + 012ADB2B00868211C697A10E = { + isa = PBXFileReference; + path = sv_net_chan.c; + refType = 4; + }; + 012ADB2D00868211C697A10E = { + isa = PBXFileReference; + path = sv_snapshot.c; + refType = 4; + }; + 012ADB2E00868211C697A10E = { + isa = PBXFileReference; + path = sv_world.c; + refType = 4; + }; + 012ADB2F00868211C697A10E = { + children = ( + 012ADB3000868211C697A10E, + 012ADB3100868211C697A10E, + 012ADB3200868211C697A10E, + 012ADB3300868211C697A10E, + 012ADB3400868211C697A10E, + 012ADB3500868211C697A10E, + 012ADB3600868211C697A10E, + 012ADB3700868211C697A10E, + 012ADB3800868211C697A10E, + 012ADB3900868211C697A10E, + 012ADB3A00868211C697A10E, + 012ADB3B00868211C697A10E, + 012ADB3C00868211C697A10E, + 012ADB3D00868211C697A10E, + 012ADB3E00868211C697A10E, + 012ADB3F00868211C697A10E, + ); + isa = PBXGroup; + path = splines; + refType = 4; + }; + 012ADB3000868211C697A10E = { + isa = PBXFileReference; + path = math_angles.cpp; + refType = 4; + }; + 012ADB3100868211C697A10E = { + isa = PBXFileReference; + path = math_angles.h; + refType = 4; + }; + 012ADB3200868211C697A10E = { + isa = PBXFileReference; + path = math_matrix.cpp; + refType = 4; + }; + 012ADB3300868211C697A10E = { + isa = PBXFileReference; + path = math_matrix.h; + refType = 4; + }; + 012ADB3400868211C697A10E = { + isa = PBXFileReference; + path = math_quaternion.cpp; + refType = 4; + }; + 012ADB3500868211C697A10E = { + isa = PBXFileReference; + path = math_quaternion.h; + refType = 4; + }; + 012ADB3600868211C697A10E = { + isa = PBXFileReference; + path = math_vector.cpp; + refType = 4; + }; + 012ADB3700868211C697A10E = { + isa = PBXFileReference; + path = math_vector.h; + refType = 4; + }; + 012ADB3800868211C697A10E = { + isa = PBXFileReference; + path = q_parse.cpp; + refType = 4; + }; + 012ADB3900868211C697A10E = { + isa = PBXFileReference; + path = q_shared.cpp; + refType = 4; + }; + 012ADB3A00868211C697A10E = { + isa = PBXFileReference; + path = q_shared.h; + refType = 4; + }; + 012ADB3B00868211C697A10E = { + isa = PBXFileReference; + path = splines.cpp; + refType = 4; + }; + 012ADB3C00868211C697A10E = { + isa = PBXFileReference; + path = splines.h; + refType = 4; + }; + 012ADB3D00868211C697A10E = { + isa = PBXFileReference; + path = util_list.h; + refType = 4; + }; + 012ADB3E00868211C697A10E = { + isa = PBXFileReference; + path = util_str.cpp; + refType = 4; + }; + 012ADB3F00868211C697A10E = { + isa = PBXFileReference; + path = util_str.h; + refType = 4; + }; + 012ADB4000868211C697A10E = { + children = ( + 012ADB4100868211C697A10E, + 012ADB4200868211C697A10E, + 012ADB4300868211C697A10E, + 012ADB4400868211C697A10E, + 012ADB4500868211C697A10E, + 012ADB4600868211C697A10E, + 012ADB4700868211C697A10E, + 012ADB4800868211C697A10E, + 012ADB4900868211C697A10E, + 012ADB4A00868211C697A10E, + 012ADB4B00868211C697A10E, + ); + isa = PBXGroup; + path = ui; + refType = 4; + }; + 012ADB4100868211C697A10E = { + isa = PBXFileReference; + path = keycodes.h; + refType = 4; + }; + 012ADB4200868211C697A10E = { + isa = PBXFileReference; + path = ui_atoms.c; + refType = 4; + }; + 012ADB4300868211C697A10E = { + isa = PBXFileReference; + path = ui_gameinfo.c; + refType = 4; + }; + 012ADB4400868211C697A10E = { + isa = PBXFileReference; + path = ui_local.h; + refType = 4; + }; + 012ADB4500868211C697A10E = { + isa = PBXFileReference; + path = ui_main.c; + refType = 4; + }; + 012ADB4600868211C697A10E = { + isa = PBXFileReference; + path = ui_players.c; + refType = 4; + }; + 012ADB4700868211C697A10E = { + isa = PBXFileReference; + path = ui_public.h; + refType = 4; + }; + 012ADB4800868211C697A10E = { + isa = PBXFileReference; + path = ui_shared.c; + refType = 4; + }; + 012ADB4900868211C697A10E = { + isa = PBXFileReference; + path = ui_shared.h; + refType = 4; + }; + 012ADB4A00868211C697A10E = { + isa = PBXFileReference; + path = ui_syscalls.c; + refType = 4; + }; + 012ADB4B00868211C697A10E = { + isa = PBXFileReference; + path = ui_util.c; + refType = 4; + }; + 012ADB4C00868211C697A10E = { + children = ( + 012ADB4D00868211C697A10E, + 012ADB4E00868211C697A10E, + 012ADB4F00868211C697A10E, + 012ADB5000868211C697A10E, + 012ADB5100868211C697A10E, + 012ADB5200868211C697A10E, + 012ADB5300868211C697A10E, + 012ADB5500868211C697A10E, + 012ADB5600868211C697A10E, + 012ADB5700868211C697A10E, + 012ADB5800868211C697A10E, + ); + isa = PBXGroup; + path = unix; + refType = 4; + }; + 012ADB4D00868211C697A10E = { + isa = PBXFileReference; + path = linux_common.c; + refType = 4; + }; + 012ADB4E00868211C697A10E = { + isa = PBXFileReference; + path = linux_glimp.c; + refType = 4; + }; + 012ADB4F00868211C697A10E = { + isa = PBXFileReference; + path = linux_joystick.c; + refType = 4; + }; + 012ADB5000868211C697A10E = { + isa = PBXFileReference; + path = linux_local.h; + refType = 4; + }; + 012ADB5100868211C697A10E = { + isa = PBXFileReference; + path = linux_qgl.c; + refType = 4; + }; + 012ADB5200868211C697A10E = { + isa = PBXFileReference; + path = linux_snd.c; + refType = 4; + }; + 012ADB5300868211C697A10E = { + isa = PBXFileReference; + path = qasm.h; + refType = 4; + }; + 012ADB5500868211C697A10E = { + isa = PBXFileReference; + path = unix_glw.h; + refType = 4; + }; + 012ADB5600868211C697A10E = { + isa = PBXFileReference; + path = unix_main.c; + refType = 4; + }; + 012ADB5700868211C697A10E = { + isa = PBXFileReference; + path = unix_net.c; + refType = 4; + }; + 012ADB5800868211C697A10E = { + isa = PBXFileReference; + path = unix_shared.c; + refType = 4; + }; + 015ECC0C00894EC0C697A10E = { + isa = PBXFileReference; + path = macosx_display.h; + refType = 4; + }; + 015ECC0D00894EC0C697A10E = { + isa = PBXFileReference; + path = macosx_display.m; + refType = 4; + }; + 015ECC0E00894EC0C697A10E = { + fileRef = 015ECC0C00894EC0C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 015ECC0F00894EC0C697A10E = { + fileRef = 015ECC0D00894EC0C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016B4A3B00ACCF9FC697A10E = { + isa = PBXFileReference; + path = macosx_glsmp_mutex.m; + refType = 4; + }; + 016B4A3C00ACCF9FC697A10E = { + isa = PBXFileReference; + path = macosx_glsmp_null.m; + refType = 4; + }; + 016B4A3D00ACCF9FC697A10E = { + isa = PBXFileReference; + path = macosx_glsmp_ports.m; + refType = 4; + }; + 016B4A3E00ACCF9FC697A10E = { + fileRef = 016B4A3B00ACCF9FC697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0200B4BDD1C697A10E = { + isa = PBXBundleReference; + path = ui.bundle; + refType = 3; + }; + 016EAE0300B4BDD1C697A10E = { + buildPhases = ( + 016EAE0400B4BDD1C697A10E, + 016EAE0900B4BDD1C697A10E, + 016EAE0A00B4BDD1C697A10E, + 016EAE1200B4BDD1C697A10E, + 016EAE1300B4BDD1C697A10E, + ); + buildSettings = { + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = "-bundle -undefined error"; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = ui; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + isa = PBXBundleTarget; + name = ui; + productName = ui; + productReference = 016EAE0200B4BDD1C697A10E; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + BNDL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + shouldUseHeadermap = 0; + }; + 016EAE0400B4BDD1C697A10E = { + buildActionMask = 2147483647; + files = ( + 016EAE0500B4BDD1C697A10E, + 016EAE0600B4BDD1C697A10E, + 016EAE0700B4BDD1C697A10E, + 016EAE0800B4BDD1C697A10E, + 016EAE1400B4BE42C697A10E, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + 016EAE0500B4BDD1C697A10E = { + fileRef = 012ADB4900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0600B4BDD1C697A10E = { + fileRef = 012ADB4700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0700B4BDD1C697A10E = { + fileRef = 012ADB4400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0800B4BDD1C697A10E = { + fileRef = 012ADB4100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0900B4BDD1C697A10E = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + 016EAE0A00B4BDD1C697A10E = { + buildActionMask = 2147483647; + files = ( + 016EAE0B00B4BDD1C697A10E, + 016EAE0C00B4BDD1C697A10E, + 016EAE0D00B4BDD1C697A10E, + 016EAE0E00B4BDD1C697A10E, + 016EAE0F00B4BDD1C697A10E, + 016EAE1000B4BDD1C697A10E, + 016EAE1100B4BDD1C697A10E, + 016EAE1500B4BE42C697A10E, + 016EAE1600B4BE42C697A10E, + 016EAE1700B4BE53C697A10E, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + 016EAE0B00B4BDD1C697A10E = { + fileRef = 012ADB4B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0C00B4BDD1C697A10E = { + fileRef = 012ADB4A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0D00B4BDD1C697A10E = { + fileRef = 012ADB4800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0E00B4BDD1C697A10E = { + fileRef = 012ADB4600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE0F00B4BDD1C697A10E = { + fileRef = 012ADB4500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE1000B4BDD1C697A10E = { + fileRef = 012ADB4300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE1100B4BDD1C697A10E = { + fileRef = 012ADB4200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE1200B4BDD1C697A10E = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + 016EAE1300B4BDD1C697A10E = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + 016EAE1400B4BE42C697A10E = { + fileRef = 012ADA6200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE1500B4BE42C697A10E = { + fileRef = 012ADA6000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE1600B4BE42C697A10E = { + fileRef = 012ADA6100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016EAE1700B4BE53C697A10E = { + fileRef = 012ADA3E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 016F1B6300ACDA9BC697A10E = { + isa = PBXFileReference; + path = huffman.c; + refType = 4; + }; + 016F1B6400ACDA9BC697A10E = { + fileRef = 016F1B6300ACDA9BC697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0170311C00B49352C697A10E = { + buildPhases = ( + ); + buildSettings = { + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = All; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + }; + dependencies = ( + 0170311D00B49352C697A10E, + F5738814013EBD9301F6286D, + F5738815013EBD9301F6286D, + F522BE590157EDB501F6286D, + F5738816013EBD9301F6286D, + ); + isa = PBXAggregateTarget; + name = All; + productName = All; + shouldUseHeadermap = 0; + }; + 0170311D00B49352C697A10E = { + isa = PBXTargetDependency; + target = 0654BA5CFE8ECEE0C697A12F; + }; +//010 +//011 +//012 +//013 +//014 +//040 +//041 +//042 +//043 +//044 + 043627A000868916C697A10E = { + children = ( + 043627A400868916C697A10E, + 043627A500868916C697A10E, + 015ECC0C00894EC0C697A10E, + 015ECC0D00894EC0C697A10E, + 011F78F200B25B65C697A10E, + 043627A600868916C697A10E, + 043627A700868916C697A10E, + 016B4A3B00ACCF9FC697A10E, + 016B4A3C00ACCF9FC697A10E, + 016B4A3D00ACCF9FC697A10E, + 043627A800868916C697A10E, + 043627A900868916C697A10E, + 043627AB00868916C697A10E, + 043627AD00868916C697A10E, + 043627AE00868916C697A10E, + 043627AF00868916C697A10E, + 043627B000868916C697A10E, + 043627B100868916C697A10E, + 043627B200868916C697A10E, + 043627B300868916C697A10E, + 043627B400868916C697A10E, + 043627B500868916C697A10E, + 043627B600868916C697A10E, + 043627B700868916C697A10E, + 043627B800868916C697A10E, + F54C3532015659F101F6286D, + ); + isa = PBXGroup; + name = "Mac OS X"; + path = ""; + refType = 4; + }; + 043627A400868916C697A10E = { + isa = PBXFileReference; + path = dlfcn.h; + refType = 4; + }; + 043627A500868916C697A10E = { + isa = PBXFileReference; + path = dlopen.c; + refType = 4; + }; + 043627A600868916C697A10E = { + isa = PBXFileReference; + path = macosx_glimp.h; + refType = 4; + }; + 043627A700868916C697A10E = { + isa = PBXFileReference; + path = macosx_glimp.m; + refType = 4; + }; + 043627A800868916C697A10E = { + isa = PBXFileReference; + path = macosx_input.m; + refType = 4; + }; + 043627A900868916C697A10E = { + isa = PBXFileReference; + path = macosx_local.h; + refType = 4; + }; + 043627AB00868916C697A10E = { + isa = PBXFileReference; + path = macosx_sndcore.m; + refType = 4; + }; + 043627AD00868916C697A10E = { + isa = PBXFileReference; + path = macosx_sys.m; + refType = 4; + }; + 043627AE00868916C697A10E = { + isa = PBXFileReference; + path = macosx_timers.h; + refType = 4; + }; + 043627AF00868916C697A10E = { + isa = PBXFileReference; + path = macosx_timers.m; + refType = 4; + }; + 043627B000868916C697A10E = { + isa = PBXFileReference; + path = Q3Controller.h; + refType = 4; + }; + 043627B100868916C697A10E = { + isa = PBXFileReference; + path = Q3Controller.m; + refType = 4; + }; + 043627B200868916C697A10E = { + isa = PBXFileReference; + path = Quake3.nib; + refType = 4; + }; + 043627B300868916C697A10E = { + isa = PBXFileReference; + path = Quake3.icns; + refType = 4; + }; + 043627B400868916C697A10E = { + isa = PBXFileReference; + path = banner.jpg; + refType = 4; + }; + 043627B500868916C697A10E = { + isa = PBXFileReference; + path = Performance.rtf; + refType = 4; + }; + 043627B600868916C697A10E = { + isa = PBXExecutableFileReference; + path = BuildRelease; + refType = 4; + }; + 043627B700868916C697A10E = { + isa = PBXExecutableFileReference; + path = RecordDemo.zsh; + refType = 4; + }; + 043627B800868916C697A10E = { + isa = PBXExecutableFileReference; + path = timedemo.zsh; + refType = 4; + }; + 043627BB00868916C697A10E = { + fileRef = 043627A400868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627BC00868916C697A10E = { + fileRef = 043627A600868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627BD00868916C697A10E = { + fileRef = 043627A900868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627BE00868916C697A10E = { + fileRef = 043627AE00868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627BF00868916C697A10E = { + fileRef = 043627B000868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627C100868916C697A10E = { + fileRef = 043627B200868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627C200868916C697A10E = { + fileRef = 043627B400868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627C400868916C697A10E = { + fileRef = 043627A500868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627C500868916C697A10E = { + fileRef = 043627A700868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627C600868916C697A10E = { + fileRef = 043627A800868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627C800868916C697A10E = { + fileRef = 043627AB00868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627CA00868916C697A10E = { + fileRef = 043627AD00868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627CB00868916C697A10E = { + fileRef = 043627AF00868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627CC00868916C697A10E = { + fileRef = 043627B100868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627D20086963EC697A10E = { + fileRef = 012ADAF600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627D30086965EC697A10E = { + fileRef = 012ADA6200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627D40086965EC697A10E = { + fileRef = 012ADA6100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627D50086965EC697A10E = { + fileRef = 012ADA6000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627D600869679C697A10E = { + fileRef = 012ADB1200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627D700869713C697A10E = { + fileRef = 012ADB0300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627D800869713C697A10E = { + fileRef = 012ADAF700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627D900869713C697A10E = { + fileRef = 012ADAF800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627DA00869713C697A10E = { + fileRef = 012ADAF900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627DB00869713C697A10E = { + fileRef = 012ADB0100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627DD00869713C697A10E = { + fileRef = 012ADB0200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627DE00869726C697A10E = { + fileRef = 012ADB0000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627DF00869726C697A10E = { + fileRef = 012ADAFF00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E00086978DC697A10E = { + fileRef = 012ADB5800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E100869827C697A10E = { + fileRef = 012ADAEE00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E200869827C697A10E = { + fileRef = 012ADAF000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E300869827C697A10E = { + fileRef = 012ADAF200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E400869827C697A10E = { + fileRef = 012ADAF300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E500869827C697A10E = { + fileRef = 012ADAFD00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E600869827C697A10E = { + fileRef = 012ADAFE00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E700869827C697A10E = { + fileRef = 012ADB0800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627E900869827C697A10E = { + fileRef = 012ADB1400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627EA00869827C697A10E = { + fileRef = 012ADB1A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627EB00869827C697A10E = { + fileRef = 012ADB2400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627EC00869827C697A10E = { + fileRef = 012AD9A600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627ED00869827C697A10E = { + fileRef = 012AD9A700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627EE00869827C697A10E = { + fileRef = 012AD9A800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627EF00869827C697A10E = { + fileRef = 012AD9A900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F000869827C697A10E = { + fileRef = 012AD9AA00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F100869827C697A10E = { + fileRef = 012AD9AB00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F200869827C697A10E = { + fileRef = 012AD9AC00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F300869827C697A10E = { + fileRef = 012AD9AD00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F400869827C697A10E = { + fileRef = 012AD9AE00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F500869827C697A10E = { + fileRef = 012AD9AF00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F600869827C697A10E = { + fileRef = 012ADAED00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F700869827C697A10E = { + fileRef = 012ADAEF00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F800869827C697A10E = { + fileRef = 012ADAF100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627F900869827C697A10E = { + fileRef = 012ADAF400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627FA00869827C697A10E = { + fileRef = 012ADAF500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627FB00869827C697A10E = { + fileRef = 012ADAFA00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627FC00869827C697A10E = { + fileRef = 012ADAFB00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627FD00869827C697A10E = { + fileRef = 012ADAFC00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627FE00869827C697A10E = { + fileRef = 012ADB0A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043627FF00869827C697A10E = { + fileRef = 012ADB0B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280000869827C697A10E = { + fileRef = 012ADB0C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280100869827C697A10E = { + fileRef = 012ADB0D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280200869827C697A10E = { + fileRef = 012ADB0E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280300869827C697A10E = { + fileRef = 012ADB0F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280400869827C697A10E = { + fileRef = 012ADB1000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280500869827C697A10E = { + fileRef = 012ADB1100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280600869827C697A10E = { + fileRef = 012ADB1300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280700869827C697A10E = { + fileRef = 012ADB1500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280800869827C697A10E = { + fileRef = 012ADB1600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280900869827C697A10E = { + fileRef = 012ADB1700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280A00869827C697A10E = { + fileRef = 012ADB1800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280B00869827C697A10E = { + fileRef = 012ADB1900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280C00869827C697A10E = { + fileRef = 012ADB1B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280D00869827C697A10E = { + fileRef = 012ADB1C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280E00869827C697A10E = { + fileRef = 012ADB1D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436280F00869827C697A10E = { + fileRef = 012ADB1E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281000869827C697A10E = { + fileRef = 012ADB1F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281100869827C697A10E = { + fileRef = 012ADB2000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281200869827C697A10E = { + fileRef = 012ADB2100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281300869827C697A10E = { + fileRef = 012ADB2200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281400869827C697A10E = { + fileRef = 012ADB2500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281500869827C697A10E = { + fileRef = 012ADB2600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281600869827C697A10E = { + fileRef = 012ADB2700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281700869827C697A10E = { + fileRef = 012ADB2800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281800869827C697A10E = { + fileRef = 012ADB2900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281900869827C697A10E = { + fileRef = 012ADB2A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281A00869827C697A10E = { + fileRef = 012ADB2B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281C00869827C697A10E = { + fileRef = 012ADB2D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281D00869827C697A10E = { + fileRef = 012ADB2E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281E00869827C697A10E = { + fileRef = 012ADB5700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436281F008698F8C697A10E = { + fileRef = 012AD9B000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 04362820008698F8C697A10E = { + fileRef = 012AD9B100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 04362821008698F8C697A10E = { + fileRef = 012AD9B400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 04362822008698F8C697A10E = { + fileRef = 012AD9B700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 04362823008698F8C697A10E = { + fileRef = 012AD9B200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 04362824008698F8C697A10E = { + fileRef = 012AD9B300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 04362825008698F8C697A10E = { + fileRef = 012AD9B500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 04362826008698F8C697A10E = { + fileRef = 012AD9B600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628270086991FC697A10E = { + fileRef = 012AD9B800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436282E00869A16C697A10E = { + fileRef = 012ADB3B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436282F00869A7FC697A10E = { + fileRef = 012ADB3800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436283000869A8EC697A10E = { + fileRef = 012ADB3F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436283100869A8EC697A10E = { + fileRef = 012ADB3E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436283300869AC5C697A10E = { + fileRef = 012ADA9800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436284900869B0BC697A10E = { + fileRef = 012ADA8C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436284B00869B0BC697A10E = { + fileRef = 012ADA9B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436286A00869C36C697A10E = { + fileRef = 012ADA8B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436286B00869C6FC697A10E = { + fileRef = 012ADA6600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436286C00869CB4C697A10E = { + fileRef = 012ADA7100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436286D00869CC9C697A10E = { + fileRef = 012ADAA200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436286E00869D0AC697A10E = { + fileRef = 012ADA6F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436286F00869D18C697A10E = { + fileRef = 012ADA7900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287000869D23C697A10E = { + fileRef = 012ADA6D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287100869D3BC697A10E = { + fileRef = 012ADA7400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287200869D48C697A10E = { + fileRef = 012ADA6C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287300869D48C697A10E = { + fileRef = 012ADA6B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287400869D56C697A10E = { + fileRef = 012ADA6A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287500869D61C697A10E = { + fileRef = 012ADA7600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287600869D71C697A10E = { + fileRef = 012ADA6900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287700869D7FC697A10E = { + fileRef = 012ADA7500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287800869D92C697A10E = { + fileRef = 012ADA6800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287900869DBBC697A10E = { + fileRef = 012ADA8D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287A00869DC7C697A10E = { + fileRef = 012ADA7000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287B00869DD4C697A10E = { + fileRef = 012ADA8500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287C00869DDDC697A10E = { + fileRef = 012ADA7800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287D00869DF1C697A10E = { + fileRef = 012ADA8400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287E00869DFAC697A10E = { + fileRef = 012ADA8200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436287F00869E04C697A10E = { + fileRef = 012ADA8900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288000869E10C697A10E = { + fileRef = 012ADA7F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288100869E21C697A10E = { + fileRef = 012ADA8100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288200869E21C697A10E = { + fileRef = 012ADA8000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288300869E3DC697A10E = { + fileRef = 012ADA9000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288400869E4CC697A10E = { + fileRef = 012ADA8800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288500869E56C697A10E = { + fileRef = 012ADA7300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288600869E60C697A10E = { + fileRef = 012ADA7B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288700869E80C697A10E = { + fileRef = 012ADA8300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288800869E8DC697A10E = { + fileRef = 012ADA7C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288900869E96C697A10E = { + fileRef = 012ADA7D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288A00869EA2C697A10E = { + fileRef = 012ADA6E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288B00869ED7C697A10E = { + fileRef = 012AD92C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288C00869EF3C697A10E = { + fileRef = 012AD92400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288D00869F06C697A10E = { + fileRef = 012AD93700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288E00869F06C697A10E = { + fileRef = 012AD93600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436288F00869F0FC697A10E = { + fileRef = 012AD93100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289000869F0FC697A10E = { + fileRef = 012AD93000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289100869F2AC697A10E = { + fileRef = 012AD93900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289200869F2AC697A10E = { + fileRef = 012AD93800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289300869F38C697A10E = { + fileRef = 012AD93500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289400869F38C697A10E = { + fileRef = 012AD93400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289500869F40C697A10E = { + fileRef = 012AD93300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289600869F40C697A10E = { + fileRef = 012AD93200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289700869F4BC697A10E = { + fileRef = 012AD92500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289800869F63C697A10E = { + fileRef = 012AD92B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289900869F7FC697A10E = { + fileRef = 012AD92300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289A00869F8EC697A10E = { + fileRef = 012AD92700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289B00869FAEC697A10E = { + fileRef = 012AD91E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289C00869FAEC697A10E = { + fileRef = 012AD91D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289D00869FBBC697A10E = { + fileRef = 012AD91800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289E00869FBBC697A10E = { + fileRef = 012AD91700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 0436289F00869FC3C697A10E = { + fileRef = 012AD92200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A000869FC3C697A10E = { + fileRef = 012AD92100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A100869FDEC697A10E = { + fileRef = 012AD90900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A200869FDEC697A10E = { + fileRef = 012AD90A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A300869FE8C697A10E = { + fileRef = 012AD90E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A400869FE8C697A10E = { + fileRef = 012AD90D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A500869FEFC697A10E = { + fileRef = 012AD91C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A600869FEFC697A10E = { + fileRef = 012AD91B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A70086A057C697A10E = { + fileRef = 012AD91600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A80086A057C697A10E = { + fileRef = 012AD91500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628A90086A061C697A10E = { + fileRef = 012AD92600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628AA0086A081C697A10E = { + fileRef = 012AD92A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628AB0086A081C697A10E = { + fileRef = 012AD92900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628AC0086A092C697A10E = { + fileRef = 012AD91A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628AD0086A092C697A10E = { + fileRef = 012AD91900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628AE0086A099C697A10E = { + fileRef = 012AD92000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628AF0086A099C697A10E = { + fileRef = 012AD91F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B00086A0B9C697A10E = { + fileRef = 012AD93B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B10086A0B9C697A10E = { + fileRef = 012AD92D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B20086A0B9C697A10E = { + fileRef = 012AD92F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B30086A0B9C697A10E = { + fileRef = 012AD93C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B40086A0B9C697A10E = { + fileRef = 012AD93A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B50086A0B9C697A10E = { + fileRef = 012AD92E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B60086A0BFC697A10E = { + fileRef = 012AD92800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B70086A0E6C697A10E = { + fileRef = 012AD91300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B80086A0E6C697A10E = { + fileRef = 012AD91200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628B90086A0F1C697A10E = { + fileRef = 012AD91100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628BA0086A0F1C697A10E = { + fileRef = 012AD91000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628BB0086A136C697A10E = { + fileRef = 012AD90C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628BC0086A136C697A10E = { + fileRef = 012AD90F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628BD0086A136C697A10E = { + fileRef = 012AD91400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628BE0086A136C697A10E = { + fileRef = 012AD90800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 043628BF0086A136C697A10E = { + fileRef = 012AD90B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; +//040 +//041 +//042 +//043 +//044 +//060 +//061 +//062 +//063 +//064 + 0654BA41FE8ECEE0C697A12F = { + buildStyles = ( + 07F3F50BFFE98E8EC697A10E, + 07F3F50CFFE98E8EC697A10E, + ); + isa = PBXProject; + mainGroup = 0654BA42FE8ECEE0C697A12F; + projectDirPath = ""; + targets = ( + 0170311C00B49352C697A10E, + 0654BA5CFE8ECEE0C697A12F, + 00F5ED38FEBA95B7C697A12F, + 00F5ED90FEBA9615C697A12F, + 016EAE0300B4BDD1C697A10E, + F50905BF0157C20601F6286D, + ); + }; + 0654BA42FE8ECEE0C697A12F = { + children = ( + 043627A000868916C697A10E, + 0654BA76FE8ED011C697A12F, + 0654BA57FE8ECEE0C697A12F, + 07FB599DFEB762C8C697A12F, + ); + isa = PBXGroup; + name = Quake3; + refType = 4; + }; + 0654BA57FE8ECEE0C697A12F = { + children = ( + F5DBFB970136AC6901F6286D, + 0654BA58FE8ECEE0C697A12F, + 0654BA59FE8ECEE0C697A12F, + 00E9D914FEDB4D29C697A12F, + 0654BA5AFE8ECEE0C697A12F, + 0654BA5BFE8ECEE0C697A12F, + ); + isa = PBXGroup; + name = "External Frameworks and Libraries"; + path = ""; + refType = 4; + }; + 0654BA58FE8ECEE0C697A12F = { + isa = PBXFrameworkReference; + name = OpenGL.framework; + path = /System/Library/Frameworks/OpenGL.framework; + refType = 0; + }; + 0654BA59FE8ECEE0C697A12F = { + isa = PBXFrameworkReference; + name = Carbon.framework; + path = /System/Library/Frameworks/Carbon.framework; + refType = 0; + }; + 0654BA5AFE8ECEE0C697A12F = { + isa = PBXFrameworkReference; + name = AppKit.framework; + path = /System/Library/Frameworks/AppKit.framework; + refType = 0; + }; + 0654BA5BFE8ECEE0C697A12F = { + isa = PBXFrameworkReference; + name = Foundation.framework; + path = /System/Library/Frameworks/Foundation.framework; + refType = 0; + }; + 0654BA5CFE8ECEE0C697A12F = { + buildPhases = ( + 0654BA5DFE8ECEE0C697A12F, + 0654BA61FE8ECEE0C697A12F, + 0654BA65FE8ECEE0C697A12F, + 0654BA6BFE8ECEE0C697A12F, + 0654BA70FE8ECEE0C697A12F, + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfensteinMP; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wall -Wno-four-char-constants -Wno-unknown-pragmas"; + WRAPPER_EXTENSION = app; + }; + dependencies = ( + F5ABC7BE013B094401F6286D, + F5ABC7BF013B094401F6286D, + F5ABC7C0013B094401F6286D, + ); + isa = PBXApplicationTarget; + name = "WolfMP (Application)"; + productName = Quake3; + productReference = 09143A93FF39F3EF11CA2562; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + WolfensteinMP + CFBundleGetInfoString + Return to Castle Wolfenstein + CFBundleIconFile + Wolf.icns + CFBundleIdentifier + com.idsoftware.WolfensteinMP + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Wolfenstein + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.30 + CFBundleSignature + idwp + CFBundleVersion + 1.30 + NSExtensions + + NSMainNibFile + Quake3.nib + NSPrincipalClass + NSApplication + NSServices + + + NSMenuItem + + default + Quake3/Connect To Server + + NSMessage + connectToServer + NSPortName + Quake3 + NSSendTypes + + NSStringPboardType + + + + NSMenuItem + + default + Quake3/Perform Command + + NSMessage + performCommand + NSPortName + Quake3 + NSSendTypes + + NSStringPboardType + + + + + +"; + shouldUseHeadermap = 0; + }; + 0654BA5DFE8ECEE0C697A12F = { + buildActionMask = 2147483647; + files = ( + 043627BB00868916C697A10E, + 043627BC00868916C697A10E, + 043627BD00868916C697A10E, + 043627BE00868916C697A10E, + 043627BF00868916C697A10E, + 043627D30086965EC697A10E, + 043627D700869713C697A10E, + 043627DE00869726C697A10E, + 043627E100869827C697A10E, + 043627E200869827C697A10E, + 043627E300869827C697A10E, + 043627E400869827C697A10E, + 043627E500869827C697A10E, + 043627E600869827C697A10E, + 043627E700869827C697A10E, + 043627E900869827C697A10E, + 043627EA00869827C697A10E, + 043627EB00869827C697A10E, + 0436281F008698F8C697A10E, + 04362820008698F8C697A10E, + 04362821008698F8C697A10E, + 04362822008698F8C697A10E, + 0436283000869A8EC697A10E, + 0436284900869B0BC697A10E, + 0436284B00869B0BC697A10E, + 0436287200869D48C697A10E, + 0436288100869E21C697A10E, + 0436288D00869F06C697A10E, + 0436288F00869F0FC697A10E, + 0436289100869F2AC697A10E, + 0436289300869F38C697A10E, + 0436289500869F40C697A10E, + 0436289B00869FAEC697A10E, + 0436289D00869FBBC697A10E, + 0436289F00869FC3C697A10E, + 043628A100869FDEC697A10E, + 043628A300869FE8C697A10E, + 043628A500869FEFC697A10E, + 043628A70086A057C697A10E, + 043628AA0086A081C697A10E, + 043628AC0086A092C697A10E, + 043628AE0086A099C697A10E, + 043628B00086A0B9C697A10E, + 043628B10086A0B9C697A10E, + 043628B20086A0B9C697A10E, + 043628B30086A0B9C697A10E, + 043628B70086A0E6C697A10E, + 043628B90086A0F1C697A10E, + 043628BB0086A136C697A10E, + 043628BC0086A136C697A10E, + 043628BD0086A136C697A10E, + 043628BE0086A136C697A10E, + 015ECC0E00894EC0C697A10E, + 011F78F300B25B66C697A10E, + F5ABC7B7013B051201F6286D, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + 0654BA61FE8ECEE0C697A12F = { + buildActionMask = 2147483647; + files = ( + 043627C100868916C697A10E, + 043627C200868916C697A10E, + F54C3533015659F201F6286D, + ); + isa = PBXResourcesBuildPhase; + name = "Bundle Resources"; + }; + 0654BA65FE8ECEE0C697A12F = { + buildActionMask = 2147483647; + files = ( + 043627C400868916C697A10E, + 043627C500868916C697A10E, + 043627C600868916C697A10E, + 043627C800868916C697A10E, + 043627CA00868916C697A10E, + 043627CB00868916C697A10E, + 043627CC00868916C697A10E, + 043627D20086963EC697A10E, + 043627D40086965EC697A10E, + 043627D50086965EC697A10E, + 043627D600869679C697A10E, + 043627D800869713C697A10E, + 043627D900869713C697A10E, + 043627DA00869713C697A10E, + 043627DB00869713C697A10E, + 043627DD00869713C697A10E, + 043627DF00869726C697A10E, + 043627E00086978DC697A10E, + 043627EC00869827C697A10E, + 043627ED00869827C697A10E, + 043627EE00869827C697A10E, + 043627EF00869827C697A10E, + 043627F000869827C697A10E, + 043627F100869827C697A10E, + 043627F200869827C697A10E, + 043627F300869827C697A10E, + 043627F400869827C697A10E, + 043627F500869827C697A10E, + 043627F600869827C697A10E, + 043627F700869827C697A10E, + 043627F800869827C697A10E, + 043627F900869827C697A10E, + 043627FA00869827C697A10E, + 043627FB00869827C697A10E, + 043627FC00869827C697A10E, + 043627FD00869827C697A10E, + 043627FE00869827C697A10E, + 043627FF00869827C697A10E, + 0436280000869827C697A10E, + 0436280100869827C697A10E, + 0436280200869827C697A10E, + 0436280300869827C697A10E, + 0436280400869827C697A10E, + 0436280500869827C697A10E, + 0436280600869827C697A10E, + 0436280700869827C697A10E, + 0436280800869827C697A10E, + 0436280900869827C697A10E, + 0436280A00869827C697A10E, + 0436280B00869827C697A10E, + 0436280C00869827C697A10E, + 0436280D00869827C697A10E, + 0436280E00869827C697A10E, + 0436280F00869827C697A10E, + 0436281000869827C697A10E, + 0436281100869827C697A10E, + 0436281200869827C697A10E, + 0436281300869827C697A10E, + 0436281400869827C697A10E, + 0436281500869827C697A10E, + 0436281600869827C697A10E, + 0436281700869827C697A10E, + 0436281800869827C697A10E, + 0436281900869827C697A10E, + 0436281A00869827C697A10E, + 0436281C00869827C697A10E, + 0436281D00869827C697A10E, + 0436281E00869827C697A10E, + 04362823008698F8C697A10E, + 04362824008698F8C697A10E, + 04362825008698F8C697A10E, + 04362826008698F8C697A10E, + 043628270086991FC697A10E, + 0436282E00869A16C697A10E, + 0436282F00869A7FC697A10E, + 0436283100869A8EC697A10E, + 0436283300869AC5C697A10E, + 0436286A00869C36C697A10E, + 0436286B00869C6FC697A10E, + 0436286C00869CB4C697A10E, + 0436286D00869CC9C697A10E, + 0436286E00869D0AC697A10E, + 0436286F00869D18C697A10E, + 0436287000869D23C697A10E, + 0436287100869D3BC697A10E, + 0436287300869D48C697A10E, + 0436287400869D56C697A10E, + 0436287500869D61C697A10E, + 0436287600869D71C697A10E, + 0436287700869D7FC697A10E, + 0436287800869D92C697A10E, + 0436287900869DBBC697A10E, + 0436287A00869DC7C697A10E, + 0436287B00869DD4C697A10E, + 0436287C00869DDDC697A10E, + 0436287D00869DF1C697A10E, + 0436287E00869DFAC697A10E, + 0436287F00869E04C697A10E, + 0436288000869E10C697A10E, + 0436288200869E21C697A10E, + 0436288300869E3DC697A10E, + 0436288400869E4CC697A10E, + 0436288500869E56C697A10E, + 0436288600869E60C697A10E, + 0436288700869E80C697A10E, + 0436288800869E8DC697A10E, + 0436288900869E96C697A10E, + 0436288A00869EA2C697A10E, + 0436288B00869ED7C697A10E, + 0436288C00869EF3C697A10E, + 0436288E00869F06C697A10E, + 0436289000869F0FC697A10E, + 0436289200869F2AC697A10E, + 0436289400869F38C697A10E, + 0436289600869F40C697A10E, + 0436289700869F4BC697A10E, + 0436289800869F63C697A10E, + 0436289900869F7FC697A10E, + 0436289A00869F8EC697A10E, + 0436289C00869FAEC697A10E, + 0436289E00869FBBC697A10E, + 043628A000869FC3C697A10E, + 043628A200869FDEC697A10E, + 043628A400869FE8C697A10E, + 043628A600869FEFC697A10E, + 043628A80086A057C697A10E, + 043628A90086A061C697A10E, + 043628AB0086A081C697A10E, + 043628AD0086A092C697A10E, + 043628AF0086A099C697A10E, + 043628B40086A0B9C697A10E, + 043628B50086A0B9C697A10E, + 043628B60086A0BFC697A10E, + 043628B80086A0E6C697A10E, + 043628BA0086A0F1C697A10E, + 043628BF0086A136C697A10E, + 015ECC0F00894EC0C697A10E, + 016B4A3E00ACCF9FC697A10E, + 016F1B6400ACDA9BC697A10E, + F5ABC7B8013B051201F6286D, + F5ABC7BA013B054201F6286D, + F5673BF90156953F01F628AA, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + 0654BA6BFE8ECEE0C697A12F = { + buildActionMask = 2147483647; + files = ( + 0654BA6CFE8ECEE0C697A12F, + 0654BA6EFE8ECEE0C697A12F, + 0654BA6FFE8ECEE0C697A12F, + 00E9D91DFEDB5295C697A12F, + F5DBFB980136ACB501F6286D, + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + 0654BA6CFE8ECEE0C697A12F = { + fileRef = 0654BA58FE8ECEE0C697A12F; + isa = PBXBuildFile; + settings = { + }; + }; + 0654BA6EFE8ECEE0C697A12F = { + fileRef = 0654BA5AFE8ECEE0C697A12F; + isa = PBXBuildFile; + settings = { + }; + }; + 0654BA6FFE8ECEE0C697A12F = { + fileRef = 0654BA5BFE8ECEE0C697A12F; + isa = PBXBuildFile; + settings = { + }; + }; + 0654BA70FE8ECEE0C697A12F = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + 0654BA76FE8ED011C697A12F = { + children = ( + 012AD90700868211C697A10E, + 012AD93D00868211C697A10E, + 012AD98D00868211C697A10E, + 012AD9A500868211C697A10E, + 012AD9B900868211C697A10E, + 012ADA2400868211C697A10E, + 012ADA6500868211C697A10E, + 012ADAEC00868211C697A10E, + 012ADB0600868211C697A10E, + 012ADB2300868211C697A10E, + 012ADB2F00868211C697A10E, + 012ADB4000868211C697A10E, + 012ADB4C00868211C697A10E, + ); + isa = PBXGroup; + name = "Id Source"; + path = ..; + refType = 4; + }; +//060 +//061 +//062 +//063 +//064 +//070 +//071 +//072 +//073 +//074 + 07F3F507FFE98E8EC697A10E = { + isa = PBXBundleReference; + path = qagame.bundle; + refType = 3; + }; + 07F3F508FFE98E8EC697A10E = { + isa = PBXBundleReference; + path = cgame.bundle; + refType = 3; + }; + 07F3F50BFFE98E8EC697A10E = { + buildRules = ( + ); + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -traditional-cpp -Wno-long-double"; + COPY_PHASE_STRIP = NO; + NO_OPT_CFLAGS = "-O0 -fno-default-inline -fno-inline -fno-inline-functions"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = "\U0001-g -Wall -DQGL_CHECK_GL_ERRORS $(NO_OPT_CFLAGS) $(COMMON_CFLAGS)"; + }; + isa = PBXBuildStyle; + name = Development; + }; + 07F3F50CFFE98E8EC697A10E = { + buildRules = ( + ); + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -traditional-cpp -Wno-long-double"; + COPY_PHASE_STRIP = YES; + OPT_CFLAGS = "-O3 -Wno-conversion -Wno-switch -Wno-unused -faltivec -ffast-math -funroll-loops"; + OTHER_CFLAGS = "\U0001-DNDEBUG $(OPT_CFLAGS) $(COMMON_CFLAGS) "; + }; + isa = PBXBuildStyle; + name = Deployment; + }; + 07FB599DFEB762C8C697A12F = { + children = ( + 09143A93FF39F3EF11CA2562, + 07F3F507FFE98E8EC697A10E, + 07F3F508FFE98E8EC697A10E, + 016EAE0200B4BDD1C697A10E, + F50905BE0157C20601F6286D, + ); + isa = PBXGroup; + name = Products; + refType = 4; + }; +//070 +//071 +//072 +//073 +//074 +//090 +//091 +//092 +//093 +//094 + 09143A93FF39F3EF11CA2562 = { + isa = PBXApplicationReference; + path = WolfensteinMP.app; + refType = 3; + }; +//090 +//091 +//092 +//093 +//094 +//130 +//131 +//132 +//133 +//134 + 13380E0900ADF941C697A10E = { + fileRef = 012ADA4C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E0B00ADFA16C697A10E = { + fileRef = 012ADA5700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E0C00ADFA72C697A10E = { + fileRef = 012ADA5600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E0D00ADFA94C697A10E = { + fileRef = 012ADA4D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E0E00ADFA9EC697A10E = { + fileRef = 012ADA4600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E0F00ADFAACC697A10E = { + fileRef = 012ADA6200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E1000ADFAACC697A10E = { + fileRef = 012ADA6000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E1100ADFAACC697A10E = { + fileRef = 012ADA6100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2100ADFC59C697A10E = { + fileRef = 012ADA5C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2200ADFC78C697A10E = { + fileRef = 012ADA4900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2300ADFC85C697A10E = { + fileRef = 012ADA3E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2400ADFC9CC697A10E = { + fileRef = 012ADA5D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2500ADFCA7C697A10E = { + fileRef = 012ADA5A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2600ADFCA7C697A10E = { + fileRef = 012ADA5900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2700ADFCBDC697A10E = { + fileRef = 012ADA4700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2800ADFCC8C697A10E = { + fileRef = 012ADA4A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2900ADFCD1C697A10E = { + fileRef = 012ADA4F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2A00ADFCEBC697A10E = { + fileRef = 012ADA5500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2B00ADFCF6C697A10E = { + fileRef = 012ADA5400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2C00ADFD01C697A10E = { + fileRef = 012ADA4800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2D00ADFD1FC697A10E = { + fileRef = 012ADA4E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2E00ADFD28C697A10E = { + fileRef = 012ADA5B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E2F00ADFD38C697A10E = { + fileRef = 012ADA5800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3000ADFD46C697A10E = { + fileRef = 012ADA5000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3100ADFD58C697A10E = { + fileRef = 012ADA4400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3200ADFD71C697A10E = { + fileRef = 012ADA3F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3300ADFD85C697A10E = { + fileRef = 012ADA4100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3400ADFDA1C697A10E = { + fileRef = 012AD99700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3500ADFDA1C697A10E = { + fileRef = 012AD9A100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3600ADFDCFC697A10E = { + fileRef = 012ADA6200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3700ADFDCFC697A10E = { + fileRef = 012ADA6100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3800ADFDCFC697A10E = { + fileRef = 012ADA6000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3900ADFDE1C697A10E = { + fileRef = 012ADA3E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3B00ADFE0CC697A10E = { + fileRef = 012AD99C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3C00ADFE1EC697A10E = { + fileRef = 012ADA3F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3D00ADFE24C697A10E = { + fileRef = 012ADA4100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3E00ADFE3DC697A10E = { + fileRef = 012AD99B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E3F00ADFE4AC697A10E = { + fileRef = 012AD99300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4000ADFE58C697A10E = { + fileRef = 012AD99100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4100ADFE6BC697A10E = { + fileRef = 012AD99600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4200ADFE7AC697A10E = { + fileRef = 012AD99800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4300ADFE88C697A10E = { + fileRef = 012AD9A300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4400ADFE97C697A10E = { + fileRef = 012AD99200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4500ADFEA7C697A10E = { + fileRef = 012AD99A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4600ADFEB8C697A10E = { + fileRef = 012AD99000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4700ADFEC9C697A10E = { + fileRef = 012AD9A200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4800ADFED7C697A10E = { + fileRef = 012AD9A000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4900ADFEE7C697A10E = { + fileRef = 012AD99F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4A00ADFEF5C697A10E = { + fileRef = 012AD99E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4B00ADFF05C697A10E = { + fileRef = 012AD99400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4C00ADFF27C697A10E = { + fileRef = 012AD98E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E4F00AE0112C697A10E = { + fileRef = 012AD98F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E5100AE0235C697A10E = { + fileRef = 012ADB4900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + 13380E5200AE0235C697A10E = { + fileRef = 012ADB4800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; +//130 +//131 +//132 +//133 +//134 +//F50 +//F51 +//F52 +//F53 +//F54 + F50905BE0157C20601F6286D = { + isa = PBXExecutableFileReference; + path = WolfDedicated; + refType = 3; + }; + F50905BF0157C20601F6286D = { + buildPhases = ( + F50905C00157C20601F6286D, + F50905C10157C20601F6286D, + F50905C20157C20601F6286D, + F50905C30157C20601F6286D, + ); + buildSettings = { + OTHER_CFLAGS = "-DDEDICATED"; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfDedicated; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + }; + dependencies = ( + ); + isa = PBXToolTarget; + name = WolfDedicated; + productInstallPath = /usr/local/bin; + productName = WolfDedicated; + productReference = F50905BE0157C20601F6286D; + shouldUseHeadermap = 0; + }; + F50905C00157C20601F6286D = { + buildActionMask = 2147483647; + files = ( + F5548C450157D33F01F6286D, + F5548C460157D33F01F6286D, + F5548C470157D33F01F6286D, + F5548C480157D33F01F6286D, + F5548C490157D33F01F6286D, + F5548C4A0157D33F01F6286D, + F5548C4B0157D33F01F6286D, + F5548C4C0157D33F01F6286D, + F5548C4D0157D33F01F6286D, + ); + isa = PBXHeadersBuildPhase; + name = Headers; + }; + F50905C10157C20601F6286D = { + buildActionMask = 2147483647; + files = ( + F5548C4E0157D33F01F6286D, + F5548C4F0157D33F01F6286D, + F5548C500157D33F01F6286D, + F5548C510157D33F01F6286D, + F5548C520157D33F01F6286D, + F5548C530157D33F01F6286D, + F5548C540157D33F01F6286D, + F5548C550157D33F01F6286D, + F5548C560157D33F01F6286D, + F5548C570157D33F01F6286D, + F5548C580157D33F01F6286D, + F5548C590157D33F01F6286D, + F5548C5A0157D33F01F6286D, + F5548C5B0157D33F01F6286D, + F5548C5C0157D33F01F6286D, + F5548C5D0157D33F01F6286D, + F5548C5E0157D33F01F6286D, + F5548C5F0157D33F01F6286D, + F5548C600157D33F01F6286D, + F5548C610157D33F01F6286D, + F5548C620157D33F01F6286D, + F5548C630157D33F01F6286D, + F5548C640157D33F01F6286D, + F5548C650157D33F01F6286D, + F5548C660157D33F01F6286D, + F5548C670157D33F01F6286D, + F5548C680157D33F01F6286D, + F5548C690157D33F01F6286D, + F5548C6A0157D33F01F6286D, + F5548C6B0157D33F01F6286D, + F5548C6C0157D33F01F6286D, + F5548C6D0157D33F01F6286D, + F5548C6E0157D33F01F6286D, + F5548C6F0157D33F01F6286D, + F5548C700157D33F01F6286D, + F5548C710157D33F01F6286D, + F5548C720157D33F01F6286D, + F5548C730157D33F01F6286D, + F5548C740157D33F01F6286D, + F5548C750157D33F01F6286D, + F5548C760157D33F01F6286D, + F5548C770157D33F01F6286D, + F5548C780157D33F01F6286D, + F5548C790157D33F01F6286D, + F5548C7A0157D33F01F6286D, + F5548C7B0157D33F01F6286D, + F5548C7C0157D33F01F6286D, + F5548C7D0157D33F01F6286D, + F5548C7E0157D33F01F6286D, + F5548C7F0157D33F01F6286D, + F5548C800157D33F01F6286D, + F5548C810157D33F01F6286D, + F5548C820157D33F01F6286D, + F5548C830157D33F01F6286D, + F5548C880157D33F01F6286D, + F5548C8A0157D33F01F6286D, + F5548C8B0157D33F01F6286D, + F5548C8C0157D33F01F6286D, + F5548C8D0157D33F01F6286D, + F5548C8E0157D33F01F6286D, + F5548C8F0157D33F01F6286D, + F5548C900157D33F01F6286D, + F5548C910157D33F01F6286D, + F5548C920157D33F01F6286D, + F5548C930157D33F01F6286D, + F5548C940157D33F01F6286D, + F5548C950157D33F01F6286D, + F5548C960157D33F01F6286D, + F5548C970157D33F01F6286D, + F5548C980157D33F01F6286D, + F5548C990157D33F01F6286D, + F5548C9A0157D33F01F6286D, + F5548C9B0157D33F01F6286D, + F5548C9C0157D33F01F6286D, + F5548C9D0157D33F01F6286D, + F5548C9E0157D33F01F6286D, + F5548C9F0157D33F01F6286D, + F5548CA00157D33F01F6286D, + F5548CA10157D33F01F6286D, + F5548CA20157D33F01F6286D, + F5548CA30157D33F01F6286D, + F5548CA40157D33F01F6286D, + F5548CA50157D33F01F6286D, + F5548CA60157D33F01F6286D, + F5548CA70157D33F01F6286D, + F5548CA80157D33F01F6286D, + F5548CA90157D33F01F6286D, + F5548CAA0157D33F01F6286D, + F5548CAB0157D33F01F6286D, + F5548CAC0157D33F01F6286D, + F5548CAD0157D33F01F6286D, + F5548CAE0157D33F01F6286D, + F5548CAF0157D33F01F6286D, + F5548CB00157D33F01F6286D, + F5548CB10157D33F01F6286D, + F5548CB20157D33F01F6286D, + F5548CB30157D33F01F6286D, + F5548CB40157D33F01F6286D, + F5548CB50157D33F01F6286D, + F5548CB60157D33F01F6286D, + F5548CB70157D33F01F6286D, + F5548CB80157D33F01F6286D, + F5548CB90157D33F01F6286D, + F5548CBA0157D33F01F6286D, + F5548CBB0157D33F01F6286D, + F5548CBC0157D33F01F6286D, + F5548CBD0157D33F01F6286D, + F5548CBE0157D33F01F6286D, + F5548CBF0157D33F01F6286D, + F5548CC00157D33F01F6286D, + F5548CC10157D33F01F6286D, + F5548CC20157D33F01F6286D, + F5548CC30157D36F01F6286D, + F5548CC40157D36F01F6286D, + F5548CC50157D36F01F6286D, + F5548CC60157D36F01F6286D, + F5548CC70157D36F01F6286D, + F5548CC80157D36F01F6286D, + F5548CC90157D36F01F6286D, + F5548CCA0157D36F01F6286D, + F5548CCB0157D36F01F6286D, + F5548CCC0157D36F01F6286D, + F5548CCD0157D36F01F6286D, + F5548CCE0157D36F01F6286D, + F5548CCF0157D36F01F6286D, + F5548CD00157D36F01F6286D, + F5548CD10157D36F01F6286D, + F5548CD20157D36F01F6286D, + F5548CD30157D36F01F6286D, + F5548CD40157D36F01F6286D, + F5548CDD0157D5CF01F6286D, + F5548CDE0157D5CF01F6286D, + F5548CDF0157D5CF01F6286D, + F5548CE00157D5CF01F6286D, + F5548CE10157D5CF01F6286D, + F5548CE20157D5CF01F6286D, + F5548CE30157D5CF01F6286D, + F5548CE40157D5CF01F6286D, + F5548CE50157D5CF01F6286D, + F5548CE60157D5CF01F6286D, + F5548CE70157D5CF01F6286D, + F5548CE80157D5CF01F6286D, + F5548CE90157D5CF01F6286D, + F5548CEA0157D5CF01F6286D, + F5548CEB0157D5CF01F6286D, + ); + isa = PBXSourcesBuildPhase; + name = Sources; + }; + F50905C20157C20601F6286D = { + buildActionMask = 2147483647; + files = ( + F5548CD80157D58D01F6286D, + F5548CD90157D58D01F6286D, + F5548CDA0157D58D01F6286D, + F5548CDB0157D58D01F6286D, + F5548CDC0157D58D01F6286D, + ); + isa = PBXFrameworksBuildPhase; + name = "Frameworks & Libraries"; + }; + F50905C30157C20601F6286D = { + buildActionMask = 2147483647; + files = ( + ); + isa = PBXRezBuildPhase; + name = "ResourceManager Resources"; + }; + F522BE590157EDB501F6286D = { + isa = PBXTargetDependency; + target = F50905BF0157C20601F6286D; + }; + F54C3532015659F101F6286D = { + isa = PBXFileReference; + path = Wolf.icns; + refType = 4; + }; + F54C3533015659F201F6286D = { + fileRef = F54C3532015659F101F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C450157D33F01F6286D = { + fileRef = 043627A400868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C460157D33F01F6286D = { + fileRef = 043627A600868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C470157D33F01F6286D = { + fileRef = 011F78F200B25B65C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C480157D33F01F6286D = { + fileRef = 015ECC0C00894EC0C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C490157D33F01F6286D = { + fileRef = 043627A900868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C4A0157D33F01F6286D = { + fileRef = 043627B000868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C4B0157D33F01F6286D = { + fileRef = 043627AE00868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C4C0157D33F01F6286D = { + fileRef = 012ADB1400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C4D0157D33F01F6286D = { + fileRef = 012ADB1A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C4E0157D33F01F6286D = { + fileRef = 043627A500868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C4F0157D33F01F6286D = { + fileRef = 016B4A3B00ACCF9FC697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C500157D33F01F6286D = { + fileRef = 043627A700868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C510157D33F01F6286D = { + fileRef = 015ECC0D00894EC0C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C520157D33F01F6286D = { + fileRef = 043627A800868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C530157D33F01F6286D = { + fileRef = 043627B100868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C540157D33F01F6286D = { + fileRef = 043627AF00868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C550157D33F01F6286D = { + fileRef = 043627AB00868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C560157D33F01F6286D = { + fileRef = 043627AD00868916C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C570157D33F01F6286D = { + fileRef = 012ADA6600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C580157D33F01F6286D = { + fileRef = 012ADA6800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C590157D33F01F6286D = { + fileRef = 012ADA6900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C5A0157D33F01F6286D = { + fileRef = 012ADA6B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C5B0157D33F01F6286D = { + fileRef = 012ADA6D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C5C0157D33F01F6286D = { + fileRef = 012ADA6A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C5D0157D33F01F6286D = { + fileRef = 012ADA6E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C5E0157D33F01F6286D = { + fileRef = 012ADA6F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C5F0157D33F01F6286D = { + fileRef = 012ADA7000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C600157D33F01F6286D = { + fileRef = 012ADA7100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C610157D33F01F6286D = { + fileRef = 012ADA7300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C620157D33F01F6286D = { + fileRef = 012ADA7400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C630157D33F01F6286D = { + fileRef = 012ADA7500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C640157D33F01F6286D = { + fileRef = 012ADA7600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C650157D33F01F6286D = { + fileRef = 012ADA7700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C660157D33F01F6286D = { + fileRef = 012ADA7800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C670157D33F01F6286D = { + fileRef = 012ADA7900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C680157D33F01F6286D = { + fileRef = 012ADA7A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C690157D33F01F6286D = { + fileRef = 012ADA7B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C6A0157D33F01F6286D = { + fileRef = 012ADA7C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C6B0157D33F01F6286D = { + fileRef = 012ADA7D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C6C0157D33F01F6286D = { + fileRef = 012ADA7F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C6D0157D33F01F6286D = { + fileRef = 012ADA8000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C6E0157D33F01F6286D = { + fileRef = 012ADA8200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C6F0157D33F01F6286D = { + fileRef = 012ADA8300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C700157D33F01F6286D = { + fileRef = 012ADA8400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C710157D33F01F6286D = { + fileRef = 012ADA8500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C720157D33F01F6286D = { + fileRef = 012ADA8800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C730157D33F01F6286D = { + fileRef = 012ADA8900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C740157D33F01F6286D = { + fileRef = 012ADA8A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C750157D33F01F6286D = { + fileRef = 012ADA8B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C760157D33F01F6286D = { + fileRef = 012ADA8D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C770157D33F01F6286D = { + fileRef = 012ADA9000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C780157D33F01F6286D = { + fileRef = 012ADA9800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C790157D33F01F6286D = { + fileRef = 012ADA9A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C7A0157D33F01F6286D = { + fileRef = 012ADAA200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C7B0157D33F01F6286D = { + fileRef = 012ADB2500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C7C0157D33F01F6286D = { + fileRef = 012ADB2600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C7D0157D33F01F6286D = { + fileRef = 012ADB2700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C7E0157D33F01F6286D = { + fileRef = 012ADB2800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C7F0157D33F01F6286D = { + fileRef = 012ADB2900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C800157D33F01F6286D = { + fileRef = 012ADB2A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C810157D33F01F6286D = { + fileRef = 012ADB2B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C820157D33F01F6286D = { + fileRef = 012ADB2D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C830157D33F01F6286D = { + fileRef = 012ADB2E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C880157D33F01F6286D = { + fileRef = 012ADB3800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C8A0157D33F01F6286D = { + fileRef = 012ADB3B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C8B0157D33F01F6286D = { + fileRef = 012ADB3E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C8C0157D33F01F6286D = { + fileRef = 012ADB0A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C8D0157D33F01F6286D = { + fileRef = 012ADB0B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C8E0157D33F01F6286D = { + fileRef = 012ADB0C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C8F0157D33F01F6286D = { + fileRef = 012ADB0D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C900157D33F01F6286D = { + fileRef = 012ADB0E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C910157D33F01F6286D = { + fileRef = 012ADB0F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C920157D33F01F6286D = { + fileRef = 012ADB1000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C930157D33F01F6286D = { + fileRef = 012ADB1100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C940157D33F01F6286D = { + fileRef = 012ADB1200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C950157D33F01F6286D = { + fileRef = 012ADB1300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C960157D33F01F6286D = { + fileRef = 012ADB1500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C970157D33F01F6286D = { + fileRef = 012ADB1600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C980157D33F01F6286D = { + fileRef = 012ADB1700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C990157D33F01F6286D = { + fileRef = 012ADB1800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C9A0157D33F01F6286D = { + fileRef = 012ADB1900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C9B0157D33F01F6286D = { + fileRef = 012ADB1B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C9C0157D33F01F6286D = { + fileRef = 012ADB1C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C9D0157D33F01F6286D = { + fileRef = 012ADB1D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C9E0157D33F01F6286D = { + fileRef = 012ADB1E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548C9F0157D33F01F6286D = { + fileRef = 012ADB1F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA00157D33F01F6286D = { + fileRef = 012ADB2000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA10157D33F01F6286D = { + fileRef = 012ADB2100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA20157D33F01F6286D = { + fileRef = F5ABC7B9013B054201F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA30157D33F01F6286D = { + fileRef = 012ADB2200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA40157D33F01F6286D = { + fileRef = 012ADB5700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA50157D33F01F6286D = { + fileRef = 012ADB5800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA60157D33F01F6286D = { + fileRef = 012AD90A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA70157D33F01F6286D = { + fileRef = 012AD90B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA80157D33F01F6286D = { + fileRef = 012AD90D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CA90157D33F01F6286D = { + fileRef = 012AD91000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CAA0157D33F01F6286D = { + fileRef = 012AD91200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CAB0157D33F01F6286D = { + fileRef = 012AD91500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CAC0157D33F01F6286D = { + fileRef = 012AD91700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CAD0157D33F01F6286D = { + fileRef = 012AD91900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CAE0157D33F01F6286D = { + fileRef = 012AD91B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CAF0157D33F01F6286D = { + fileRef = 012AD91D00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB00157D33F01F6286D = { + fileRef = 012AD91F00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB10157D33F01F6286D = { + fileRef = 012AD92100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB20157D33F01F6286D = { + fileRef = 012AD92400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB30157D33F01F6286D = { + fileRef = 012AD92300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB40157D33F01F6286D = { + fileRef = 012AD92500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB50157D33F01F6286D = { + fileRef = 012AD92600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB60157D33F01F6286D = { + fileRef = 012AD92700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB70157D33F01F6286D = { + fileRef = 012AD92800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB80157D33F01F6286D = { + fileRef = 012AD92900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CB90157D33F01F6286D = { + fileRef = 012AD92B00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CBA0157D33F01F6286D = { + fileRef = 012AD92C00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CBB0157D33F01F6286D = { + fileRef = 012AD92E00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CBC0157D33F01F6286D = { + fileRef = 012AD93000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CBD0157D33F01F6286D = { + fileRef = 012AD93200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CBE0157D33F01F6286D = { + fileRef = 012AD93400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CBF0157D33F01F6286D = { + fileRef = 012AD93600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC00157D33F01F6286D = { + fileRef = 012AD93800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC10157D33F01F6286D = { + fileRef = 012AD93A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC20157D33F01F6286D = { + fileRef = F5ABC7B5013B051201F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC30157D36F01F6286D = { + fileRef = 012ADA6100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC40157D36F01F6286D = { + fileRef = 012ADA6000868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC50157D36F01F6286D = { + fileRef = 012ADAED00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC60157D36F01F6286D = { + fileRef = 012ADAEF00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC70157D36F01F6286D = { + fileRef = 012ADAF100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC80157D36F01F6286D = { + fileRef = 012ADAF400868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CC90157D36F01F6286D = { + fileRef = 012ADAF500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CCA0157D36F01F6286D = { + fileRef = 012ADAF600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CCB0157D36F01F6286D = { + fileRef = 012ADAF700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CCC0157D36F01F6286D = { + fileRef = 012ADAF800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CCD0157D36F01F6286D = { + fileRef = 012ADAF900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CCE0157D36F01F6286D = { + fileRef = 016F1B6300ACDA9BC697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CCF0157D36F01F6286D = { + fileRef = 012ADAFA00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CD00157D36F01F6286D = { + fileRef = 012ADAFB00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CD10157D36F01F6286D = { + fileRef = 012ADAFC00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CD20157D36F01F6286D = { + fileRef = 012ADAFF00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CD30157D36F01F6286D = { + fileRef = 012ADB0100868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CD40157D36F01F6286D = { + fileRef = 012ADB0200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CD80157D58D01F6286D = { + fileRef = F5DBFB970136AC6901F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CD90157D58D01F6286D = { + fileRef = 0654BA58FE8ECEE0C697A12F; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CDA0157D58D01F6286D = { + fileRef = 00E9D914FEDB4D29C697A12F; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CDB0157D58D01F6286D = { + fileRef = 0654BA5AFE8ECEE0C697A12F; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CDC0157D58D01F6286D = { + fileRef = 0654BA5BFE8ECEE0C697A12F; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CDD0157D5CF01F6286D = { + fileRef = 012AD9AB00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CDE0157D5CF01F6286D = { + fileRef = 012AD9A600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CDF0157D5CF01F6286D = { + fileRef = 012AD9A700868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE00157D5CF01F6286D = { + fileRef = 012AD9A800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE10157D5CF01F6286D = { + fileRef = 012AD9A900868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE20157D5CF01F6286D = { + fileRef = 012AD9AA00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE30157D5CF01F6286D = { + fileRef = 012AD9AC00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE40157D5CF01F6286D = { + fileRef = 012AD9AD00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE50157D5CF01F6286D = { + fileRef = 012AD9AE00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE60157D5CF01F6286D = { + fileRef = 012AD9AF00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE70157D5CF01F6286D = { + fileRef = 012AD9B200868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE80157D5CF01F6286D = { + fileRef = 012AD9B300868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CE90157D5CF01F6286D = { + fileRef = 012AD9B500868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CEA0157D5CF01F6286D = { + fileRef = 012AD9B600868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5548CEB0157D5CF01F6286D = { + fileRef = 012AD9B800868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F55DF57A01513A8101F6286D = { + isa = PBXFileReference; + path = g_tramcar.c; + refType = 4; + }; + F55DF57B01513A8101F6286D = { + fileRef = F55DF57A01513A8101F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5673BEE015686CC01F628AA = { + isa = PBXFileReference; + path = cg_newDraw.c; + refType = 4; + }; + F5673BEF015686CC01F628AA = { + fileRef = F5673BEE015686CC01F628AA; + isa = PBXBuildFile; + settings = { + }; + }; + F5673BF90156953F01F628AA = { + fileRef = 012ADA9A00868211C697A10E; + isa = PBXBuildFile; + settings = { + }; + }; + F5738814013EBD9301F6286D = { + isa = PBXTargetDependency; + target = 00F5ED38FEBA95B7C697A12F; + }; + F5738815013EBD9301F6286D = { + isa = PBXTargetDependency; + target = 00F5ED90FEBA9615C697A12F; + }; + F5738816013EBD9301F6286D = { + isa = PBXTargetDependency; + target = 016EAE0300B4BDD1C697A10E; + }; + F5ABC7B5013B051201F6286D = { + isa = PBXFileReference; + path = be_aas_routetable.c; + refType = 4; + }; + F5ABC7B6013B051201F6286D = { + isa = PBXFileReference; + path = be_aas_routetable.h; + refType = 4; + }; + F5ABC7B7013B051201F6286D = { + fileRef = F5ABC7B6013B051201F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7B8013B051201F6286D = { + fileRef = F5ABC7B5013B051201F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7B9013B054201F6286D = { + isa = PBXFileReference; + path = tr_cmesh.c; + refType = 4; + }; + F5ABC7BA013B054201F6286D = { + fileRef = F5ABC7B9013B054201F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7BE013B094401F6286D = { + isa = PBXTargetDependency; + target = 00F5ED38FEBA95B7C697A12F; + }; + F5ABC7BF013B094401F6286D = { + isa = PBXTargetDependency; + target = 00F5ED90FEBA9615C697A12F; + }; + F5ABC7C0013B094401F6286D = { + isa = PBXTargetDependency; + target = 016EAE0300B4BDD1C697A10E; + }; + F5ABC7C7013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_characters.c; + refType = 4; + }; + F5ABC7C8013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_debug.c; + refType = 4; + }; + F5ABC7C9013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_events.c; + refType = 4; + }; + F5ABC7CA013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_fight.c; + refType = 4; + }; + F5ABC7CB013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_fight.h; + refType = 4; + }; + F5ABC7CC013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_func_attack.c; + refType = 4; + }; + F5ABC7CD013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_func_boss1.c; + refType = 4; + }; + F5ABC7CE013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_funcs.c; + refType = 4; + }; + F5ABC7CF013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_global.h; + refType = 4; + }; + F5ABC7D0013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_script_actions.c; + refType = 4; + }; + F5ABC7D1013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_script_ents.c; + refType = 4; + }; + F5ABC7D2013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_script.c; + refType = 4; + }; + F5ABC7D3013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_sight.c; + refType = 4; + }; + F5ABC7D4013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast_think.c; + refType = 4; + }; + F5ABC7D5013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast.c; + refType = 4; + }; + F5ABC7D6013B0EAB01F6286D = { + isa = PBXFileReference; + path = ai_cast.h; + refType = 4; + }; + F5ABC7D7013B0EAB01F6286D = { + isa = PBXFileReference; + path = bg_animation.c; + refType = 4; + }; + F5ABC7D8013B0EAB01F6286D = { + isa = PBXFileReference; + path = g_alarm.c; + refType = 4; + }; + F5ABC7D9013B0EAB01F6286D = { + isa = PBXFileReference; + path = g_func_decs.h; + refType = 4; + }; + F5ABC7DA013B0EAB01F6286D = { + isa = PBXFileReference; + path = g_funcs.h; + refType = 4; + }; + F5ABC7DB013B0EAB01F6286D = { + isa = PBXFileReference; + path = g_props.c; + refType = 4; + }; + F5ABC7DC013B0EAB01F6286D = { + isa = PBXFileReference; + path = g_save.c; + refType = 4; + }; + F5ABC7DD013B0EAB01F6286D = { + isa = PBXFileReference; + path = g_script_actions.c; + refType = 4; + }; + F5ABC7DE013B0EAB01F6286D = { + isa = PBXFileReference; + path = g_script.c; + refType = 4; + }; + F5ABC7E1013B0EAB01F6286D = { + fileRef = F5ABC7CB013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7E2013B0EAB01F6286D = { + fileRef = F5ABC7CF013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7E6013B0EAB01F6286D = { + fileRef = F5ABC7C7013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7E7013B0EAB01F6286D = { + fileRef = F5ABC7C8013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7E8013B0EAB01F6286D = { + fileRef = F5ABC7C9013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7E9013B0EAB01F6286D = { + fileRef = F5ABC7CA013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7EA013B0EAB01F6286D = { + fileRef = F5ABC7CC013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7EB013B0EAB01F6286D = { + fileRef = F5ABC7CD013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7EC013B0EAB01F6286D = { + fileRef = F5ABC7CE013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7ED013B0EAB01F6286D = { + fileRef = F5ABC7D0013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7EE013B0EAB01F6286D = { + fileRef = F5ABC7D1013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7EF013B0EAB01F6286D = { + fileRef = F5ABC7D2013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F0013B0EAB01F6286D = { + fileRef = F5ABC7D3013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F1013B0EAB01F6286D = { + fileRef = F5ABC7D4013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F2013B0EAB01F6286D = { + fileRef = F5ABC7D5013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F3013B0EAB01F6286D = { + fileRef = F5ABC7D7013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F4013B0EAB01F6286D = { + fileRef = F5ABC7D8013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F5013B0EAB01F6286D = { + fileRef = F5ABC7DB013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F6013B0EAB01F6286D = { + fileRef = F5ABC7DC013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F7013B0EAB01F6286D = { + fileRef = F5ABC7DD013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7F8013B0EAB01F6286D = { + fileRef = F5ABC7DE013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC7FB013B120B01F6286D = { + isa = PBXFileReference; + name = ai_chat.c; + path = ../botai/ai_chat.c; + refType = 2; + }; + F5ABC7FD013B120B01F6286D = { + isa = PBXFileReference; + name = ai_cmd.c; + path = ../botai/ai_cmd.c; + refType = 2; + }; + F5ABC7FF013B120B01F6286D = { + isa = PBXFileReference; + name = ai_dmnet.c; + path = ../botai/ai_dmnet.c; + refType = 2; + }; + F5ABC801013B120B01F6286D = { + isa = PBXFileReference; + name = ai_dmq3.c; + path = ../botai/ai_dmq3.c; + refType = 2; + }; + F5ABC802013B120B01F6286D = { + isa = PBXFileReference; + name = ai_dmq3.h; + path = ../botai/ai_dmq3.h; + refType = 2; + }; + F5ABC803013B120B01F6286D = { + isa = PBXFileReference; + name = ai_main.c; + path = ../botai/ai_main.c; + refType = 2; + }; + F5ABC804013B120B01F6286D = { + isa = PBXFileReference; + name = ai_main.h; + path = ../botai/ai_main.h; + refType = 2; + }; + F5ABC805013B120B01F6286D = { + isa = PBXFileReference; + name = ai_team.c; + path = ../botai/ai_team.c; + refType = 2; + }; + F5ABC806013B120B01F6286D = { + isa = PBXFileReference; + name = ai_team.h; + path = ../botai/ai_team.h; + refType = 2; + }; + F5ABC80F013B120B01F6286D = { + fileRef = F5ABC802013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC810013B120B01F6286D = { + fileRef = F5ABC804013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC811013B120B01F6286D = { + fileRef = F5ABC806013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC817013B120B01F6286D = { + fileRef = F5ABC7FB013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC818013B120B01F6286D = { + fileRef = F5ABC7FD013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC819013B120B01F6286D = { + fileRef = F5ABC7FF013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC81A013B120B01F6286D = { + fileRef = F5ABC801013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC81B013B120B01F6286D = { + fileRef = F5ABC803013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC81C013B120B01F6286D = { + fileRef = F5ABC805013B120B01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC81D013B12E701F6286D = { + isa = PBXFileReference; + path = cg_flamethrower.c; + refType = 4; + }; + F5ABC81E013B12E701F6286D = { + isa = PBXFileReference; + path = cg_particles.c; + refType = 4; + }; + F5ABC81F013B12E701F6286D = { + isa = PBXFileReference; + path = cg_sound.c; + refType = 4; + }; + F5ABC820013B12E701F6286D = { + isa = PBXFileReference; + path = cg_trails.c; + refType = 4; + }; + F5ABC821013B12E701F6286D = { + fileRef = F5ABC81D013B12E701F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC822013B12E701F6286D = { + fileRef = F5ABC81E013B12E701F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC823013B12E701F6286D = { + fileRef = F5ABC81F013B12E701F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC824013B12E701F6286D = { + fileRef = F5ABC820013B12E701F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5ABC825013B145701F6286D = { + fileRef = F5ABC7D7013B0EAB01F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + F5DBFB970136AC6901F6286D = { + isa = PBXFrameworkReference; + name = IOKit.framework; + path = /System/Library/Frameworks/IOKit.framework; + refType = 0; + }; + F5DBFB980136ACB501F6286D = { + fileRef = F5DBFB970136AC6901F6286D; + isa = PBXBuildFile; + settings = { + }; + }; + }; + rootObject = 0654BA41FE8ECEE0C697A12F; +} diff --git a/src/macosx/Wolf.xcodeproj/project.pbxproj b/src/macosx/Wolf.xcodeproj/project.pbxproj new file mode 100644 index 0000000..269be18 --- /dev/null +++ b/src/macosx/Wolf.xcodeproj/project.pbxproj @@ -0,0 +1,3496 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXAggregateTarget section */ + 0170311C00B49352C697A10E /* All */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 256D53D50A07EDF400E71389 /* Build configuration list for PBXAggregateTarget "All" */; + buildPhases = ( + ); + buildSettings = { + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = All; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + dependencies = ( + 0170311D00B49352C697A10E /* PBXTargetDependency */, + F5738814013EBD9301F6286D /* PBXTargetDependency */, + F5738815013EBD9301F6286D /* PBXTargetDependency */, + F522BE590157EDB501F6286D /* PBXTargetDependency */, + F5738816013EBD9301F6286D /* PBXTargetDependency */, + ); + name = All; + productName = All; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXApplicationTarget section */ + 0654BA5CFE8ECEE0C697A12F /* WolfMP (Application) */ = { + isa = PBXApplicationTarget; + buildConfigurationList = 256D53CD0A07EDF400E71389 /* Build configuration list for PBXApplicationTarget "WolfMP (Application)" */; + buildPhases = ( + 0654BA5DFE8ECEE0C697A12F /* Headers */, + 0654BA61FE8ECEE0C697A12F /* Resources */, + 0654BA65FE8ECEE0C697A12F /* Sources */, + 0654BA6BFE8ECEE0C697A12F /* Frameworks */, + 0654BA70FE8ECEE0C697A12F /* Rez */, + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfensteinMP; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = app; + }; + dependencies = ( + ); + name = "WolfMP (Application)"; + productName = Quake3; + productReference = 09143A93FF39F3EF11CA2562 /* WolfensteinMP.app */; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + WolfensteinMP + CFBundleGetInfoString + Return to Castle Wolfenstein + CFBundleIconFile + Wolf.icns + CFBundleIdentifier + com.idsoftware.WolfensteinMP + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Wolfenstein + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.30 + CFBundleSignature + idwp + CFBundleVersion + 1.30 + NSExtensions + + NSMainNibFile + Quake3.nib + NSPrincipalClass + NSApplication + NSServices + + + NSMenuItem + + default + Quake3/Connect To Server + + NSMessage + connectToServer + NSPortName + Quake3 + NSSendTypes + + NSStringPboardType + + + + NSMenuItem + + default + Quake3/Perform Command + + NSMessage + performCommand + NSPortName + Quake3 + NSSendTypes + + NSStringPboardType + + + + + +"; + }; +/* End PBXApplicationTarget section */ + +/* Begin PBXBuildFile section */ + 00E9D91DFEDB5295C697A12F /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00E9D914FEDB4D29C697A12F /* CoreAudio.framework */; }; + 011F78F300B25B66C697A10E /* macosx_qgl.h in Headers */ = {isa = PBXBuildFile; fileRef = 011F78F200B25B65C697A10E /* macosx_qgl.h */; }; + 015ECC0E00894EC0C697A10E /* macosx_display.h in Headers */ = {isa = PBXBuildFile; fileRef = 015ECC0C00894EC0C697A10E /* macosx_display.h */; }; + 015ECC0F00894EC0C697A10E /* macosx_display.m in Sources */ = {isa = PBXBuildFile; fileRef = 015ECC0D00894EC0C697A10E /* macosx_display.m */; }; + 016B4A3E00ACCF9FC697A10E /* macosx_glsmp_mutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 016B4A3B00ACCF9FC697A10E /* macosx_glsmp_mutex.m */; }; + 016EAE0500B4BDD1C697A10E /* ui_shared.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB4900868211C697A10E /* ui_shared.h */; }; + 016EAE0600B4BDD1C697A10E /* ui_public.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB4700868211C697A10E /* ui_public.h */; }; + 016EAE0700B4BDD1C697A10E /* ui_local.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB4400868211C697A10E /* ui_local.h */; }; + 016EAE0800B4BDD1C697A10E /* keycodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB4100868211C697A10E /* keycodes.h */; }; + 016EAE0B00B4BDD1C697A10E /* ui_util.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB4B00868211C697A10E /* ui_util.c */; }; + 016EAE0C00B4BDD1C697A10E /* ui_syscalls.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB4A00868211C697A10E /* ui_syscalls.c */; }; + 016EAE0D00B4BDD1C697A10E /* ui_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB4800868211C697A10E /* ui_shared.c */; }; + 016EAE0E00B4BDD1C697A10E /* ui_players.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB4600868211C697A10E /* ui_players.c */; }; + 016EAE0F00B4BDD1C697A10E /* ui_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB4500868211C697A10E /* ui_main.c */; }; + 016EAE1000B4BDD1C697A10E /* ui_gameinfo.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB4300868211C697A10E /* ui_gameinfo.c */; }; + 016EAE1100B4BDD1C697A10E /* ui_atoms.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB4200868211C697A10E /* ui_atoms.c */; }; + 016EAE1400B4BE42C697A10E /* q_shared.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA6200868211C697A10E /* q_shared.h */; }; + 016EAE1500B4BE42C697A10E /* q_math.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6000868211C697A10E /* q_math.c */; }; + 016EAE1600B4BE42C697A10E /* q_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6100868211C697A10E /* q_shared.c */; }; + 016EAE1700B4BE53C697A10E /* bg_misc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA3E00868211C697A10E /* bg_misc.c */; }; + 016F1B6400ACDA9BC697A10E /* huffman.c in Sources */ = {isa = PBXBuildFile; fileRef = 016F1B6300ACDA9BC697A10E /* huffman.c */; }; + 043627BB00868916C697A10E /* dlfcn.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627A400868916C697A10E /* dlfcn.h */; }; + 043627BC00868916C697A10E /* macosx_glimp.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627A600868916C697A10E /* macosx_glimp.h */; }; + 043627BD00868916C697A10E /* macosx_local.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627A900868916C697A10E /* macosx_local.h */; }; + 043627BE00868916C697A10E /* macosx_timers.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627AE00868916C697A10E /* macosx_timers.h */; }; + 043627BF00868916C697A10E /* Q3Controller.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627B000868916C697A10E /* Q3Controller.h */; }; + 043627C100868916C697A10E /* Quake3.nib in Resources */ = {isa = PBXBuildFile; fileRef = 043627B200868916C697A10E /* Quake3.nib */; }; + 043627C200868916C697A10E /* banner.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 043627B400868916C697A10E /* banner.jpg */; }; + 043627C400868916C697A10E /* dlopen.c in Sources */ = {isa = PBXBuildFile; fileRef = 043627A500868916C697A10E /* dlopen.c */; }; + 043627C500868916C697A10E /* macosx_glimp.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627A700868916C697A10E /* macosx_glimp.m */; }; + 043627C600868916C697A10E /* macosx_input.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627A800868916C697A10E /* macosx_input.m */; }; + 043627C800868916C697A10E /* macosx_sndcore.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627AB00868916C697A10E /* macosx_sndcore.m */; }; + 043627CA00868916C697A10E /* macosx_sys.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627AD00868916C697A10E /* macosx_sys.m */; }; + 043627CB00868916C697A10E /* macosx_timers.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627AF00868916C697A10E /* macosx_timers.m */; }; + 043627CC00868916C697A10E /* Q3Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627B100868916C697A10E /* Q3Controller.m */; }; + 043627D20086963EC697A10E /* cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF600868211C697A10E /* cmd.c */; }; + 043627D30086965EC697A10E /* q_shared.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA6200868211C697A10E /* q_shared.h */; }; + 043627D40086965EC697A10E /* q_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6100868211C697A10E /* q_shared.c */; }; + 043627D50086965EC697A10E /* q_math.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6000868211C697A10E /* q_math.c */; }; + 043627D600869679C697A10E /* tr_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1200868211C697A10E /* tr_init.c */; }; + 043627D700869713C697A10E /* vm_local.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB0300868211C697A10E /* vm_local.h */; }; + 043627D800869713C697A10E /* common.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF700868211C697A10E /* common.c */; }; + 043627D900869713C697A10E /* cvar.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF800868211C697A10E /* cvar.c */; }; + 043627DA00869713C697A10E /* files.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF900868211C697A10E /* files.c */; }; + 043627DB00869713C697A10E /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0100868211C697A10E /* vm.c */; }; + 043627DD00869713C697A10E /* vm_interpreted.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0200868211C697A10E /* vm_interpreted.c */; }; + 043627DE00869726C697A10E /* unzip.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB0000868211C697A10E /* unzip.h */; }; + 043627DF00869726C697A10E /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAFF00868211C697A10E /* unzip.c */; }; + 043627E00086978DC697A10E /* unix_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB5800868211C697A10E /* unix_shared.c */; }; + 043627E100869827C697A10E /* cm_local.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADAEE00868211C697A10E /* cm_local.h */; }; + 043627E200869827C697A10E /* cm_patch.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADAF000868211C697A10E /* cm_patch.h */; }; + 043627E300869827C697A10E /* cm_polylib.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADAF200868211C697A10E /* cm_polylib.h */; }; + 043627E400869827C697A10E /* cm_public.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADAF300868211C697A10E /* cm_public.h */; }; + 043627E500869827C697A10E /* qcommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADAFD00868211C697A10E /* qcommon.h */; }; + 043627E600869827C697A10E /* qfiles.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADAFE00868211C697A10E /* qfiles.h */; }; + 043627E700869827C697A10E /* qgl.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB0800868211C697A10E /* qgl.h */; }; + 043627E900869827C697A10E /* tr_local.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB1400868211C697A10E /* tr_local.h */; }; + 043627EA00869827C697A10E /* tr_public.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB1A00868211C697A10E /* tr_public.h */; }; + 043627EB00869827C697A10E /* server.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB2400868211C697A10E /* server.h */; }; + 043627EC00869827C697A10E /* cl_cgame.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A600868211C697A10E /* cl_cgame.c */; }; + 043627ED00869827C697A10E /* cl_cin.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A700868211C697A10E /* cl_cin.c */; }; + 043627EE00869827C697A10E /* cl_console.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A800868211C697A10E /* cl_console.c */; }; + 043627EF00869827C697A10E /* cl_input.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A900868211C697A10E /* cl_input.c */; }; + 043627F000869827C697A10E /* cl_keys.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AA00868211C697A10E /* cl_keys.c */; }; + 043627F100869827C697A10E /* cl_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AB00868211C697A10E /* cl_main.c */; }; + 043627F200869827C697A10E /* cl_net_chan.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AC00868211C697A10E /* cl_net_chan.c */; }; + 043627F300869827C697A10E /* cl_parse.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AD00868211C697A10E /* cl_parse.c */; }; + 043627F400869827C697A10E /* cl_scrn.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AE00868211C697A10E /* cl_scrn.c */; }; + 043627F500869827C697A10E /* cl_ui.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AF00868211C697A10E /* cl_ui.c */; }; + 043627F600869827C697A10E /* cm_load.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAED00868211C697A10E /* cm_load.c */; }; + 043627F700869827C697A10E /* cm_patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAEF00868211C697A10E /* cm_patch.c */; }; + 043627F800869827C697A10E /* cm_polylib.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF100868211C697A10E /* cm_polylib.c */; }; + 043627F900869827C697A10E /* cm_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF400868211C697A10E /* cm_test.c */; }; + 043627FA00869827C697A10E /* cm_trace.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF500868211C697A10E /* cm_trace.c */; }; + 043627FB00869827C697A10E /* md4.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAFA00868211C697A10E /* md4.c */; }; + 043627FC00869827C697A10E /* msg.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAFB00868211C697A10E /* msg.c */; }; + 043627FD00869827C697A10E /* net_chan.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAFC00868211C697A10E /* net_chan.c */; }; + 043627FE00869827C697A10E /* tr_animation.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0A00868211C697A10E /* tr_animation.c */; }; + 043627FF00869827C697A10E /* tr_backend.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0B00868211C697A10E /* tr_backend.c */; }; + 0436280000869827C697A10E /* tr_bsp.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0C00868211C697A10E /* tr_bsp.c */; }; + 0436280100869827C697A10E /* tr_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0D00868211C697A10E /* tr_cmds.c */; }; + 0436280200869827C697A10E /* tr_curve.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0E00868211C697A10E /* tr_curve.c */; }; + 0436280300869827C697A10E /* tr_flares.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0F00868211C697A10E /* tr_flares.c */; }; + 0436280400869827C697A10E /* tr_font.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1000868211C697A10E /* tr_font.c */; }; + 0436280500869827C697A10E /* tr_image.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1100868211C697A10E /* tr_image.c */; }; + 0436280600869827C697A10E /* tr_light.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1300868211C697A10E /* tr_light.c */; }; + 0436280700869827C697A10E /* tr_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1500868211C697A10E /* tr_main.c */; }; + 0436280800869827C697A10E /* tr_marks.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1600868211C697A10E /* tr_marks.c */; }; + 0436280900869827C697A10E /* tr_mesh.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1700868211C697A10E /* tr_mesh.c */; }; + 0436280A00869827C697A10E /* tr_model.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1800868211C697A10E /* tr_model.c */; }; + 0436280B00869827C697A10E /* tr_noise.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1900868211C697A10E /* tr_noise.c */; }; + 0436280C00869827C697A10E /* tr_scene.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1B00868211C697A10E /* tr_scene.c */; }; + 0436280D00869827C697A10E /* tr_shade.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1C00868211C697A10E /* tr_shade.c */; }; + 0436280E00869827C697A10E /* tr_shade_calc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1D00868211C697A10E /* tr_shade_calc.c */; }; + 0436280F00869827C697A10E /* tr_shader.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1E00868211C697A10E /* tr_shader.c */; }; + 0436281000869827C697A10E /* tr_shadows.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1F00868211C697A10E /* tr_shadows.c */; }; + 0436281100869827C697A10E /* tr_sky.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2000868211C697A10E /* tr_sky.c */; }; + 0436281200869827C697A10E /* tr_surface.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2100868211C697A10E /* tr_surface.c */; }; + 0436281300869827C697A10E /* tr_world.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2200868211C697A10E /* tr_world.c */; }; + 0436281400869827C697A10E /* sv_bot.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2500868211C697A10E /* sv_bot.c */; }; + 0436281500869827C697A10E /* sv_ccmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2600868211C697A10E /* sv_ccmds.c */; }; + 0436281600869827C697A10E /* sv_client.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2700868211C697A10E /* sv_client.c */; }; + 0436281700869827C697A10E /* sv_game.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2800868211C697A10E /* sv_game.c */; }; + 0436281800869827C697A10E /* sv_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2900868211C697A10E /* sv_init.c */; }; + 0436281900869827C697A10E /* sv_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2A00868211C697A10E /* sv_main.c */; }; + 0436281A00869827C697A10E /* sv_net_chan.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2B00868211C697A10E /* sv_net_chan.c */; }; + 0436281C00869827C697A10E /* sv_snapshot.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2D00868211C697A10E /* sv_snapshot.c */; }; + 0436281D00869827C697A10E /* sv_world.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2E00868211C697A10E /* sv_world.c */; }; + 0436281E00869827C697A10E /* unix_net.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB5700868211C697A10E /* unix_net.c */; }; + 0436281F008698F8C697A10E /* client.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD9B000868211C697A10E /* client.h */; }; + 04362820008698F8C697A10E /* keys.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD9B100868211C697A10E /* keys.h */; }; + 04362821008698F8C697A10E /* snd_local.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD9B400868211C697A10E /* snd_local.h */; }; + 04362822008698F8C697A10E /* snd_public.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD9B700868211C697A10E /* snd_public.h */; }; + 04362823008698F8C697A10E /* snd_adpcm.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B200868211C697A10E /* snd_adpcm.c */; }; + 04362824008698F8C697A10E /* snd_dma.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B300868211C697A10E /* snd_dma.c */; }; + 04362825008698F8C697A10E /* snd_mem.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B500868211C697A10E /* snd_mem.c */; }; + 04362826008698F8C697A10E /* snd_mix.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B600868211C697A10E /* snd_mix.c */; }; + 043628270086991FC697A10E /* snd_wavelet.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B800868211C697A10E /* snd_wavelet.c */; }; + 0436282E00869A16C697A10E /* splines.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB3B00868211C697A10E /* splines.cpp */; }; + 0436282F00869A7FC697A10E /* q_parse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB3800868211C697A10E /* q_parse.cpp */; }; + 0436283000869A8EC697A10E /* util_str.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB3F00868211C697A10E /* util_str.h */; }; + 0436283100869A8EC697A10E /* util_str.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB3E00868211C697A10E /* util_str.cpp */; }; + 0436283300869AC5C697A10E /* jmemmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA9800868211C697A10E /* jmemmgr.c */; }; + 0436284900869B0BC697A10E /* jerror.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA8C00868211C697A10E /* jerror.h */; }; + 0436284B00869B0BC697A10E /* jmemsys.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA9B00868211C697A10E /* jmemsys.h */; }; + 0436286A00869C36C697A10E /* jerror.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8B00868211C697A10E /* jerror.c */; }; + 0436286B00869C6FC697A10E /* jcapimin.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6600868211C697A10E /* jcapimin.c */; }; + 0436286C00869CB4C697A10E /* jcomapi.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7100868211C697A10E /* jcomapi.c */; }; + 0436286D00869CC9C697A10E /* jutils.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAA200868211C697A10E /* jutils.c */; }; + 0436286E00869D0AC697A10E /* jcmarker.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6F00868211C697A10E /* jcmarker.c */; }; + 0436286F00869D18C697A10E /* jdapistd.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7900868211C697A10E /* jdapistd.c */; }; + 0436287000869D23C697A10E /* jcinit.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6D00868211C697A10E /* jcinit.c */; }; + 0436287100869D3BC697A10E /* jcphuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7400868211C697A10E /* jcphuff.c */; }; + 0436287200869D48C697A10E /* jchuff.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA6C00868211C697A10E /* jchuff.h */; }; + 0436287300869D48C697A10E /* jchuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6B00868211C697A10E /* jchuff.c */; }; + 0436287400869D56C697A10E /* jcdctmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6A00868211C697A10E /* jcdctmgr.c */; }; + 0436287500869D61C697A10E /* jcsample.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7600868211C697A10E /* jcsample.c */; }; + 0436287600869D71C697A10E /* jccolor.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6900868211C697A10E /* jccolor.c */; }; + 0436287700869D7FC697A10E /* jcprepct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7500868211C697A10E /* jcprepct.c */; }; + 0436287800869D92C697A10E /* jccoefct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6800868211C697A10E /* jccoefct.c */; }; + 0436287900869DBBC697A10E /* jfdctflt.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8D00868211C697A10E /* jfdctflt.c */; }; + 0436287A00869DC7C697A10E /* jcmaster.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7000868211C697A10E /* jcmaster.c */; }; + 0436287B00869DD4C697A10E /* jdmaster.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8500868211C697A10E /* jdmaster.c */; }; + 0436287C00869DDDC697A10E /* jdapimin.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7800868211C697A10E /* jdapimin.c */; }; + 0436287D00869DF1C697A10E /* jdmarker.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8400868211C697A10E /* jdmarker.c */; }; + 0436287E00869DFAC697A10E /* jdinput.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8200868211C697A10E /* jdinput.c */; }; + 0436287F00869E04C697A10E /* jdsample.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8900868211C697A10E /* jdsample.c */; }; + 0436288000869E10C697A10E /* jddctmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7F00868211C697A10E /* jddctmgr.c */; }; + 0436288100869E21C697A10E /* jdhuff.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA8100868211C697A10E /* jdhuff.h */; }; + 0436288200869E21C697A10E /* jdhuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8000868211C697A10E /* jdhuff.c */; }; + 0436288300869E3DC697A10E /* jidctflt.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA9000868211C697A10E /* jidctflt.c */; }; + 0436288400869E4CC697A10E /* jdpostct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8800868211C697A10E /* jdpostct.c */; }; + 0436288500869E56C697A10E /* jcparam.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7300868211C697A10E /* jcparam.c */; }; + 0436288600869E60C697A10E /* jdatasrc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7B00868211C697A10E /* jdatasrc.c */; }; + 0436288700869E80C697A10E /* jdmainct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8300868211C697A10E /* jdmainct.c */; }; + 0436288800869E8DC697A10E /* jdcoefct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7C00868211C697A10E /* jdcoefct.c */; }; + 0436288900869E96C697A10E /* jdcolor.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7D00868211C697A10E /* jdcolor.c */; }; + 0436288A00869EA2C697A10E /* jcmainct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6E00868211C697A10E /* jcmainct.c */; }; + 0436288B00869ED7C697A10E /* be_interface.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92C00868211C697A10E /* be_interface.c */; }; + 0436288C00869EF3C697A10E /* be_ai_chat.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92400868211C697A10E /* be_ai_chat.c */; }; + 0436288D00869F06C697A10E /* l_precomp.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD93700868211C697A10E /* l_precomp.h */; }; + 0436288E00869F06C697A10E /* l_precomp.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93600868211C697A10E /* l_precomp.c */; }; + 0436288F00869F0FC697A10E /* l_libvar.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD93100868211C697A10E /* l_libvar.h */; }; + 0436289000869F0FC697A10E /* l_libvar.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93000868211C697A10E /* l_libvar.c */; }; + 0436289100869F2AC697A10E /* l_script.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD93900868211C697A10E /* l_script.h */; }; + 0436289200869F2AC697A10E /* l_script.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93800868211C697A10E /* l_script.c */; }; + 0436289300869F38C697A10E /* l_memory.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD93500868211C697A10E /* l_memory.h */; }; + 0436289400869F38C697A10E /* l_memory.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93400868211C697A10E /* l_memory.c */; }; + 0436289500869F40C697A10E /* l_log.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD93300868211C697A10E /* l_log.h */; }; + 0436289600869F40C697A10E /* l_log.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93200868211C697A10E /* l_log.c */; }; + 0436289700869F4BC697A10E /* be_ai_gen.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92500868211C697A10E /* be_ai_gen.c */; }; + 0436289800869F63C697A10E /* be_ea.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92B00868211C697A10E /* be_ea.c */; }; + 0436289900869F7FC697A10E /* be_ai_char.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92300868211C697A10E /* be_ai_char.c */; }; + 0436289A00869F8EC697A10E /* be_ai_move.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92700868211C697A10E /* be_ai_move.c */; }; + 0436289B00869FAEC697A10E /* be_aas_route.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD91E00868211C697A10E /* be_aas_route.h */; }; + 0436289C00869FAEC697A10E /* be_aas_route.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91D00868211C697A10E /* be_aas_route.c */; }; + 0436289D00869FBBC697A10E /* be_aas_move.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD91800868211C697A10E /* be_aas_move.h */; }; + 0436289E00869FBBC697A10E /* be_aas_move.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91700868211C697A10E /* be_aas_move.c */; }; + 0436289F00869FC3C697A10E /* be_aas_sample.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD92200868211C697A10E /* be_aas_sample.h */; }; + 043628A000869FC3C697A10E /* be_aas_sample.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92100868211C697A10E /* be_aas_sample.c */; }; + 043628A100869FDEC697A10E /* be_aas_bsp.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD90900868211C697A10E /* be_aas_bsp.h */; }; + 043628A200869FDEC697A10E /* be_aas_bspq3.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD90A00868211C697A10E /* be_aas_bspq3.c */; }; + 043628A300869FE8C697A10E /* be_aas_debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD90E00868211C697A10E /* be_aas_debug.h */; }; + 043628A400869FE8C697A10E /* be_aas_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD90D00868211C697A10E /* be_aas_debug.c */; }; + 043628A500869FEFC697A10E /* be_aas_reach.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD91C00868211C697A10E /* be_aas_reach.h */; }; + 043628A600869FEFC697A10E /* be_aas_reach.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91B00868211C697A10E /* be_aas_reach.c */; }; + 043628A70086A057C697A10E /* be_aas_main.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD91600868211C697A10E /* be_aas_main.h */; }; + 043628A80086A057C697A10E /* be_aas_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91500868211C697A10E /* be_aas_main.c */; }; + 043628A90086A061C697A10E /* be_ai_goal.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92600868211C697A10E /* be_ai_goal.c */; }; + 043628AA0086A081C697A10E /* be_ai_weight.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD92A00868211C697A10E /* be_ai_weight.h */; }; + 043628AB0086A081C697A10E /* be_ai_weight.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92900868211C697A10E /* be_ai_weight.c */; }; + 043628AC0086A092C697A10E /* be_aas_optimize.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD91A00868211C697A10E /* be_aas_optimize.h */; }; + 043628AD0086A092C697A10E /* be_aas_optimize.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91900868211C697A10E /* be_aas_optimize.c */; }; + 043628AE0086A099C697A10E /* be_aas_routealt.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD92000868211C697A10E /* be_aas_routealt.h */; }; + 043628AF0086A099C697A10E /* be_aas_routealt.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91F00868211C697A10E /* be_aas_routealt.c */; }; + 043628B00086A0B9C697A10E /* l_struct.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD93B00868211C697A10E /* l_struct.h */; }; + 043628B10086A0B9C697A10E /* be_interface.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD92D00868211C697A10E /* be_interface.h */; }; + 043628B20086A0B9C697A10E /* l_crc.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD92F00868211C697A10E /* l_crc.h */; }; + 043628B30086A0B9C697A10E /* l_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD93C00868211C697A10E /* l_utils.h */; }; + 043628B40086A0B9C697A10E /* l_struct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93A00868211C697A10E /* l_struct.c */; }; + 043628B50086A0B9C697A10E /* l_crc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92E00868211C697A10E /* l_crc.c */; }; + 043628B60086A0BFC697A10E /* be_ai_weap.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92800868211C697A10E /* be_ai_weap.c */; }; + 043628B70086A0E6C697A10E /* be_aas_file.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD91300868211C697A10E /* be_aas_file.h */; }; + 043628B80086A0E6C697A10E /* be_aas_file.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91200868211C697A10E /* be_aas_file.c */; }; + 043628B90086A0F1C697A10E /* be_aas_entity.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD91100868211C697A10E /* be_aas_entity.h */; }; + 043628BA0086A0F1C697A10E /* be_aas_entity.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91000868211C697A10E /* be_aas_entity.c */; }; + 043628BB0086A136C697A10E /* be_aas_cluster.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD90C00868211C697A10E /* be_aas_cluster.h */; }; + 043628BC0086A136C697A10E /* be_aas_def.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD90F00868211C697A10E /* be_aas_def.h */; }; + 043628BD0086A136C697A10E /* be_aas_funcs.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD91400868211C697A10E /* be_aas_funcs.h */; }; + 043628BE0086A136C697A10E /* aasfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 012AD90800868211C697A10E /* aasfile.h */; }; + 043628BF0086A136C697A10E /* be_aas_cluster.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD90B00868211C697A10E /* be_aas_cluster.c */; }; + 0654BA6CFE8ECEE0C697A12F /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0654BA58FE8ECEE0C697A12F /* OpenGL.framework */; }; + 0654BA6EFE8ECEE0C697A12F /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0654BA5AFE8ECEE0C697A12F /* AppKit.framework */; }; + 0654BA6FFE8ECEE0C697A12F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0654BA5BFE8ECEE0C697A12F /* Foundation.framework */; }; + 13380E0900ADF941C697A10E /* g_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4C00868211C697A10E /* g_main.c */; }; + 13380E0B00ADFA16C697A10E /* g_syscalls.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5700868211C697A10E /* g_syscalls.c */; }; + 13380E0C00ADFA72C697A10E /* g_svcmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5600868211C697A10E /* g_svcmds.c */; }; + 13380E0D00ADFA94C697A10E /* g_mem.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4D00868211C697A10E /* g_mem.c */; }; + 13380E0E00ADFA9EC697A10E /* g_bot.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4600868211C697A10E /* g_bot.c */; }; + 13380E0F00ADFAACC697A10E /* q_shared.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA6200868211C697A10E /* q_shared.h */; }; + 13380E1000ADFAACC697A10E /* q_math.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6000868211C697A10E /* q_math.c */; }; + 13380E1100ADFAACC697A10E /* q_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6100868211C697A10E /* q_shared.c */; }; + 13380E2100ADFC59C697A10E /* g_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5C00868211C697A10E /* g_utils.c */; }; + 13380E2200ADFC78C697A10E /* g_combat.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4900868211C697A10E /* g_combat.c */; }; + 13380E2300ADFC85C697A10E /* bg_misc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA3E00868211C697A10E /* bg_misc.c */; }; + 13380E2400ADFC9CC697A10E /* g_weapon.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5D00868211C697A10E /* g_weapon.c */; }; + 13380E2500ADFCA7C697A10E /* g_team.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA5A00868211C697A10E /* g_team.h */; }; + 13380E2600ADFCA7C697A10E /* g_team.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5900868211C697A10E /* g_team.c */; }; + 13380E2700ADFCBDC697A10E /* g_client.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4700868211C697A10E /* g_client.c */; }; + 13380E2800ADFCC8C697A10E /* g_items.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4A00868211C697A10E /* g_items.c */; }; + 13380E2900ADFCD1C697A10E /* g_missile.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4F00868211C697A10E /* g_missile.c */; }; + 13380E2A00ADFCEBC697A10E /* g_spawn.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5500868211C697A10E /* g_spawn.c */; }; + 13380E2B00ADFCF6C697A10E /* g_session.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5400868211C697A10E /* g_session.c */; }; + 13380E2C00ADFD01C697A10E /* g_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4800868211C697A10E /* g_cmds.c */; }; + 13380E2D00ADFD1FC697A10E /* g_misc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4E00868211C697A10E /* g_misc.c */; }; + 13380E2E00ADFD28C697A10E /* g_trigger.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5B00868211C697A10E /* g_trigger.c */; }; + 13380E2F00ADFD38C697A10E /* g_target.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5800868211C697A10E /* g_target.c */; }; + 13380E3000ADFD46C697A10E /* g_mover.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA5000868211C697A10E /* g_mover.c */; }; + 13380E3100ADFD58C697A10E /* g_active.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4400868211C697A10E /* g_active.c */; }; + 13380E3200ADFD71C697A10E /* bg_pmove.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA3F00868211C697A10E /* bg_pmove.c */; }; + 13380E3300ADFD85C697A10E /* bg_slidemove.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4100868211C697A10E /* bg_slidemove.c */; }; + 13380E3400ADFDA1C697A10E /* cg_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99700868211C697A10E /* cg_main.c */; }; + 13380E3500ADFDA1C697A10E /* cg_syscalls.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A100868211C697A10E /* cg_syscalls.c */; }; + 13380E3600ADFDCFC697A10E /* q_shared.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADA6200868211C697A10E /* q_shared.h */; }; + 13380E3700ADFDCFC697A10E /* q_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6100868211C697A10E /* q_shared.c */; }; + 13380E3800ADFDCFC697A10E /* q_math.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6000868211C697A10E /* q_math.c */; }; + 13380E3900ADFDE1C697A10E /* bg_misc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA3E00868211C697A10E /* bg_misc.c */; }; + 13380E3B00ADFE0CC697A10E /* cg_predict.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99C00868211C697A10E /* cg_predict.c */; }; + 13380E3C00ADFE1EC697A10E /* bg_pmove.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA3F00868211C697A10E /* bg_pmove.c */; }; + 13380E3D00ADFE24C697A10E /* bg_slidemove.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA4100868211C697A10E /* bg_slidemove.c */; }; + 13380E3E00ADFE3DC697A10E /* cg_playerstate.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99B00868211C697A10E /* cg_playerstate.c */; }; + 13380E3F00ADFE4AC697A10E /* cg_event.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99300868211C697A10E /* cg_event.c */; }; + 13380E4000ADFE58C697A10E /* cg_effects.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99100868211C697A10E /* cg_effects.c */; }; + 13380E4100ADFE6BC697A10E /* cg_localents.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99600868211C697A10E /* cg_localents.c */; }; + 13380E4200ADFE7AC697A10E /* cg_marks.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99800868211C697A10E /* cg_marks.c */; }; + 13380E4300ADFE88C697A10E /* cg_weapons.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A300868211C697A10E /* cg_weapons.c */; }; + 13380E4400ADFE97C697A10E /* cg_ents.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99200868211C697A10E /* cg_ents.c */; }; + 13380E4500ADFEA7C697A10E /* cg_players.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99A00868211C697A10E /* cg_players.c */; }; + 13380E4600ADFEB8C697A10E /* cg_drawtools.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99000868211C697A10E /* cg_drawtools.c */; }; + 13380E4700ADFEC9C697A10E /* cg_view.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A200868211C697A10E /* cg_view.c */; }; + 13380E4800ADFED7C697A10E /* cg_snapshot.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A000868211C697A10E /* cg_snapshot.c */; }; + 13380E4900ADFEE7C697A10E /* cg_servercmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99F00868211C697A10E /* cg_servercmds.c */; }; + 13380E4A00ADFEF5C697A10E /* cg_scoreboard.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99E00868211C697A10E /* cg_scoreboard.c */; }; + 13380E4B00ADFF05C697A10E /* cg_info.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD99400868211C697A10E /* cg_info.c */; }; + 13380E4C00ADFF27C697A10E /* cg_consolecmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD98E00868211C697A10E /* cg_consolecmds.c */; }; + 13380E4F00AE0112C697A10E /* cg_draw.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD98F00868211C697A10E /* cg_draw.c */; }; + 13380E5100AE0235C697A10E /* ui_shared.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB4900868211C697A10E /* ui_shared.h */; }; + 13380E5200AE0235C697A10E /* ui_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB4800868211C697A10E /* ui_shared.c */; }; + F54C3533015659F201F6286D /* Wolf.icns in Resources */ = {isa = PBXBuildFile; fileRef = F54C3532015659F101F6286D /* Wolf.icns */; }; + F5548C450157D33F01F6286D /* dlfcn.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627A400868916C697A10E /* dlfcn.h */; }; + F5548C460157D33F01F6286D /* macosx_glimp.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627A600868916C697A10E /* macosx_glimp.h */; }; + F5548C470157D33F01F6286D /* macosx_qgl.h in Headers */ = {isa = PBXBuildFile; fileRef = 011F78F200B25B65C697A10E /* macosx_qgl.h */; }; + F5548C480157D33F01F6286D /* macosx_display.h in Headers */ = {isa = PBXBuildFile; fileRef = 015ECC0C00894EC0C697A10E /* macosx_display.h */; }; + F5548C490157D33F01F6286D /* macosx_local.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627A900868916C697A10E /* macosx_local.h */; }; + F5548C4A0157D33F01F6286D /* Q3Controller.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627B000868916C697A10E /* Q3Controller.h */; }; + F5548C4B0157D33F01F6286D /* macosx_timers.h in Headers */ = {isa = PBXBuildFile; fileRef = 043627AE00868916C697A10E /* macosx_timers.h */; }; + F5548C4C0157D33F01F6286D /* tr_local.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB1400868211C697A10E /* tr_local.h */; }; + F5548C4D0157D33F01F6286D /* tr_public.h in Headers */ = {isa = PBXBuildFile; fileRef = 012ADB1A00868211C697A10E /* tr_public.h */; }; + F5548C4E0157D33F01F6286D /* dlopen.c in Sources */ = {isa = PBXBuildFile; fileRef = 043627A500868916C697A10E /* dlopen.c */; }; + F5548C4F0157D33F01F6286D /* macosx_glsmp_mutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 016B4A3B00ACCF9FC697A10E /* macosx_glsmp_mutex.m */; }; + F5548C500157D33F01F6286D /* macosx_glimp.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627A700868916C697A10E /* macosx_glimp.m */; }; + F5548C510157D33F01F6286D /* macosx_display.m in Sources */ = {isa = PBXBuildFile; fileRef = 015ECC0D00894EC0C697A10E /* macosx_display.m */; }; + F5548C520157D33F01F6286D /* macosx_input.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627A800868916C697A10E /* macosx_input.m */; }; + F5548C530157D33F01F6286D /* Q3Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627B100868916C697A10E /* Q3Controller.m */; }; + F5548C540157D33F01F6286D /* macosx_timers.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627AF00868916C697A10E /* macosx_timers.m */; }; + F5548C550157D33F01F6286D /* macosx_sndcore.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627AB00868916C697A10E /* macosx_sndcore.m */; }; + F5548C560157D33F01F6286D /* macosx_sys.m in Sources */ = {isa = PBXBuildFile; fileRef = 043627AD00868916C697A10E /* macosx_sys.m */; }; + F5548C570157D33F01F6286D /* jcapimin.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6600868211C697A10E /* jcapimin.c */; }; + F5548C580157D33F01F6286D /* jccoefct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6800868211C697A10E /* jccoefct.c */; }; + F5548C590157D33F01F6286D /* jccolor.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6900868211C697A10E /* jccolor.c */; }; + F5548C5A0157D33F01F6286D /* jchuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6B00868211C697A10E /* jchuff.c */; }; + F5548C5B0157D33F01F6286D /* jcinit.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6D00868211C697A10E /* jcinit.c */; }; + F5548C5C0157D33F01F6286D /* jcdctmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6A00868211C697A10E /* jcdctmgr.c */; }; + F5548C5D0157D33F01F6286D /* jcmainct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6E00868211C697A10E /* jcmainct.c */; }; + F5548C5E0157D33F01F6286D /* jcmarker.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6F00868211C697A10E /* jcmarker.c */; }; + F5548C5F0157D33F01F6286D /* jcmaster.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7000868211C697A10E /* jcmaster.c */; }; + F5548C600157D33F01F6286D /* jcomapi.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7100868211C697A10E /* jcomapi.c */; }; + F5548C610157D33F01F6286D /* jcparam.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7300868211C697A10E /* jcparam.c */; }; + F5548C620157D33F01F6286D /* jcphuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7400868211C697A10E /* jcphuff.c */; }; + F5548C630157D33F01F6286D /* jcprepct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7500868211C697A10E /* jcprepct.c */; }; + F5548C640157D33F01F6286D /* jcsample.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7600868211C697A10E /* jcsample.c */; }; + F5548C650157D33F01F6286D /* jctrans.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7700868211C697A10E /* jctrans.c */; }; + F5548C660157D33F01F6286D /* jdapimin.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7800868211C697A10E /* jdapimin.c */; }; + F5548C670157D33F01F6286D /* jdapistd.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7900868211C697A10E /* jdapistd.c */; }; + F5548C680157D33F01F6286D /* jdatadst.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7A00868211C697A10E /* jdatadst.c */; }; + F5548C690157D33F01F6286D /* jdatasrc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7B00868211C697A10E /* jdatasrc.c */; }; + F5548C6A0157D33F01F6286D /* jdcoefct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7C00868211C697A10E /* jdcoefct.c */; }; + F5548C6B0157D33F01F6286D /* jdcolor.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7D00868211C697A10E /* jdcolor.c */; }; + F5548C6C0157D33F01F6286D /* jddctmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA7F00868211C697A10E /* jddctmgr.c */; }; + F5548C6D0157D33F01F6286D /* jdhuff.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8000868211C697A10E /* jdhuff.c */; }; + F5548C6E0157D33F01F6286D /* jdinput.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8200868211C697A10E /* jdinput.c */; }; + F5548C6F0157D33F01F6286D /* jdmainct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8300868211C697A10E /* jdmainct.c */; }; + F5548C700157D33F01F6286D /* jdmarker.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8400868211C697A10E /* jdmarker.c */; }; + F5548C710157D33F01F6286D /* jdmaster.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8500868211C697A10E /* jdmaster.c */; }; + F5548C720157D33F01F6286D /* jdpostct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8800868211C697A10E /* jdpostct.c */; }; + F5548C730157D33F01F6286D /* jdsample.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8900868211C697A10E /* jdsample.c */; }; + F5548C740157D33F01F6286D /* jdtrans.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8A00868211C697A10E /* jdtrans.c */; }; + F5548C750157D33F01F6286D /* jerror.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8B00868211C697A10E /* jerror.c */; }; + F5548C760157D33F01F6286D /* jfdctflt.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA8D00868211C697A10E /* jfdctflt.c */; }; + F5548C770157D33F01F6286D /* jidctflt.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA9000868211C697A10E /* jidctflt.c */; }; + F5548C780157D33F01F6286D /* jmemmgr.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA9800868211C697A10E /* jmemmgr.c */; }; + F5548C790157D33F01F6286D /* jmemnobs.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA9A00868211C697A10E /* jmemnobs.c */; }; + F5548C7A0157D33F01F6286D /* jutils.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAA200868211C697A10E /* jutils.c */; }; + F5548C7B0157D33F01F6286D /* sv_bot.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2500868211C697A10E /* sv_bot.c */; }; + F5548C7C0157D33F01F6286D /* sv_ccmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2600868211C697A10E /* sv_ccmds.c */; }; + F5548C7D0157D33F01F6286D /* sv_client.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2700868211C697A10E /* sv_client.c */; }; + F5548C7E0157D33F01F6286D /* sv_game.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2800868211C697A10E /* sv_game.c */; }; + F5548C7F0157D33F01F6286D /* sv_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2900868211C697A10E /* sv_init.c */; }; + F5548C800157D33F01F6286D /* sv_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2A00868211C697A10E /* sv_main.c */; }; + F5548C810157D33F01F6286D /* sv_net_chan.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2B00868211C697A10E /* sv_net_chan.c */; }; + F5548C820157D33F01F6286D /* sv_snapshot.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2D00868211C697A10E /* sv_snapshot.c */; }; + F5548C830157D33F01F6286D /* sv_world.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2E00868211C697A10E /* sv_world.c */; }; + F5548C880157D33F01F6286D /* q_parse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB3800868211C697A10E /* q_parse.cpp */; }; + F5548C8A0157D33F01F6286D /* splines.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB3B00868211C697A10E /* splines.cpp */; }; + F5548C8B0157D33F01F6286D /* util_str.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB3E00868211C697A10E /* util_str.cpp */; }; + F5548C8C0157D33F01F6286D /* tr_animation.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0A00868211C697A10E /* tr_animation.c */; }; + F5548C8D0157D33F01F6286D /* tr_backend.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0B00868211C697A10E /* tr_backend.c */; }; + F5548C8E0157D33F01F6286D /* tr_bsp.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0C00868211C697A10E /* tr_bsp.c */; }; + F5548C8F0157D33F01F6286D /* tr_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0D00868211C697A10E /* tr_cmds.c */; }; + F5548C900157D33F01F6286D /* tr_curve.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0E00868211C697A10E /* tr_curve.c */; }; + F5548C910157D33F01F6286D /* tr_flares.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0F00868211C697A10E /* tr_flares.c */; }; + F5548C920157D33F01F6286D /* tr_font.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1000868211C697A10E /* tr_font.c */; }; + F5548C930157D33F01F6286D /* tr_image.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1100868211C697A10E /* tr_image.c */; }; + F5548C940157D33F01F6286D /* tr_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1200868211C697A10E /* tr_init.c */; }; + F5548C950157D33F01F6286D /* tr_light.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1300868211C697A10E /* tr_light.c */; }; + F5548C960157D33F01F6286D /* tr_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1500868211C697A10E /* tr_main.c */; }; + F5548C970157D33F01F6286D /* tr_marks.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1600868211C697A10E /* tr_marks.c */; }; + F5548C980157D33F01F6286D /* tr_mesh.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1700868211C697A10E /* tr_mesh.c */; }; + F5548C990157D33F01F6286D /* tr_model.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1800868211C697A10E /* tr_model.c */; }; + F5548C9A0157D33F01F6286D /* tr_noise.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1900868211C697A10E /* tr_noise.c */; }; + F5548C9B0157D33F01F6286D /* tr_scene.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1B00868211C697A10E /* tr_scene.c */; }; + F5548C9C0157D33F01F6286D /* tr_shade.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1C00868211C697A10E /* tr_shade.c */; }; + F5548C9D0157D33F01F6286D /* tr_shade_calc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1D00868211C697A10E /* tr_shade_calc.c */; }; + F5548C9E0157D33F01F6286D /* tr_shader.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1E00868211C697A10E /* tr_shader.c */; }; + F5548C9F0157D33F01F6286D /* tr_shadows.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB1F00868211C697A10E /* tr_shadows.c */; }; + F5548CA00157D33F01F6286D /* tr_sky.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2000868211C697A10E /* tr_sky.c */; }; + F5548CA10157D33F01F6286D /* tr_surface.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2100868211C697A10E /* tr_surface.c */; }; + F5548CA20157D33F01F6286D /* tr_cmesh.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7B9013B054201F6286D /* tr_cmesh.c */; }; + F5548CA30157D33F01F6286D /* tr_world.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB2200868211C697A10E /* tr_world.c */; }; + F5548CA40157D33F01F6286D /* unix_net.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB5700868211C697A10E /* unix_net.c */; }; + F5548CA50157D33F01F6286D /* unix_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB5800868211C697A10E /* unix_shared.c */; }; + F5548CA60157D33F01F6286D /* be_aas_bspq3.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD90A00868211C697A10E /* be_aas_bspq3.c */; }; + F5548CA70157D33F01F6286D /* be_aas_cluster.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD90B00868211C697A10E /* be_aas_cluster.c */; }; + F5548CA80157D33F01F6286D /* be_aas_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD90D00868211C697A10E /* be_aas_debug.c */; }; + F5548CA90157D33F01F6286D /* be_aas_entity.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91000868211C697A10E /* be_aas_entity.c */; }; + F5548CAA0157D33F01F6286D /* be_aas_file.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91200868211C697A10E /* be_aas_file.c */; }; + F5548CAB0157D33F01F6286D /* be_aas_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91500868211C697A10E /* be_aas_main.c */; }; + F5548CAC0157D33F01F6286D /* be_aas_move.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91700868211C697A10E /* be_aas_move.c */; }; + F5548CAD0157D33F01F6286D /* be_aas_optimize.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91900868211C697A10E /* be_aas_optimize.c */; }; + F5548CAE0157D33F01F6286D /* be_aas_reach.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91B00868211C697A10E /* be_aas_reach.c */; }; + F5548CAF0157D33F01F6286D /* be_aas_route.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91D00868211C697A10E /* be_aas_route.c */; }; + F5548CB00157D33F01F6286D /* be_aas_routealt.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD91F00868211C697A10E /* be_aas_routealt.c */; }; + F5548CB10157D33F01F6286D /* be_aas_sample.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92100868211C697A10E /* be_aas_sample.c */; }; + F5548CB20157D33F01F6286D /* be_ai_chat.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92400868211C697A10E /* be_ai_chat.c */; }; + F5548CB30157D33F01F6286D /* be_ai_char.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92300868211C697A10E /* be_ai_char.c */; }; + F5548CB40157D33F01F6286D /* be_ai_gen.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92500868211C697A10E /* be_ai_gen.c */; }; + F5548CB50157D33F01F6286D /* be_ai_goal.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92600868211C697A10E /* be_ai_goal.c */; }; + F5548CB60157D33F01F6286D /* be_ai_move.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92700868211C697A10E /* be_ai_move.c */; }; + F5548CB70157D33F01F6286D /* be_ai_weap.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92800868211C697A10E /* be_ai_weap.c */; }; + F5548CB80157D33F01F6286D /* be_ai_weight.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92900868211C697A10E /* be_ai_weight.c */; }; + F5548CB90157D33F01F6286D /* be_ea.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92B00868211C697A10E /* be_ea.c */; }; + F5548CBA0157D33F01F6286D /* be_interface.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92C00868211C697A10E /* be_interface.c */; }; + F5548CBB0157D33F01F6286D /* l_crc.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD92E00868211C697A10E /* l_crc.c */; }; + F5548CBC0157D33F01F6286D /* l_libvar.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93000868211C697A10E /* l_libvar.c */; }; + F5548CBD0157D33F01F6286D /* l_log.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93200868211C697A10E /* l_log.c */; }; + F5548CBE0157D33F01F6286D /* l_memory.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93400868211C697A10E /* l_memory.c */; }; + F5548CBF0157D33F01F6286D /* l_precomp.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93600868211C697A10E /* l_precomp.c */; }; + F5548CC00157D33F01F6286D /* l_script.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93800868211C697A10E /* l_script.c */; }; + F5548CC10157D33F01F6286D /* l_struct.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD93A00868211C697A10E /* l_struct.c */; }; + F5548CC20157D33F01F6286D /* be_aas_routetable.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7B5013B051201F6286D /* be_aas_routetable.c */; }; + F5548CC30157D36F01F6286D /* q_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6100868211C697A10E /* q_shared.c */; }; + F5548CC40157D36F01F6286D /* q_math.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA6000868211C697A10E /* q_math.c */; }; + F5548CC50157D36F01F6286D /* cm_load.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAED00868211C697A10E /* cm_load.c */; }; + F5548CC60157D36F01F6286D /* cm_patch.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAEF00868211C697A10E /* cm_patch.c */; }; + F5548CC70157D36F01F6286D /* cm_polylib.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF100868211C697A10E /* cm_polylib.c */; }; + F5548CC80157D36F01F6286D /* cm_test.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF400868211C697A10E /* cm_test.c */; }; + F5548CC90157D36F01F6286D /* cm_trace.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF500868211C697A10E /* cm_trace.c */; }; + F5548CCA0157D36F01F6286D /* cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF600868211C697A10E /* cmd.c */; }; + F5548CCB0157D36F01F6286D /* common.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF700868211C697A10E /* common.c */; }; + F5548CCC0157D36F01F6286D /* cvar.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF800868211C697A10E /* cvar.c */; }; + F5548CCD0157D36F01F6286D /* files.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAF900868211C697A10E /* files.c */; }; + F5548CCE0157D36F01F6286D /* huffman.c in Sources */ = {isa = PBXBuildFile; fileRef = 016F1B6300ACDA9BC697A10E /* huffman.c */; }; + F5548CCF0157D36F01F6286D /* md4.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAFA00868211C697A10E /* md4.c */; }; + F5548CD00157D36F01F6286D /* msg.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAFB00868211C697A10E /* msg.c */; }; + F5548CD10157D36F01F6286D /* net_chan.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAFC00868211C697A10E /* net_chan.c */; }; + F5548CD20157D36F01F6286D /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADAFF00868211C697A10E /* unzip.c */; }; + F5548CD30157D36F01F6286D /* vm.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0100868211C697A10E /* vm.c */; }; + F5548CD40157D36F01F6286D /* vm_interpreted.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADB0200868211C697A10E /* vm_interpreted.c */; }; + F5548CD80157D58D01F6286D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5DBFB970136AC6901F6286D /* IOKit.framework */; }; + F5548CD90157D58D01F6286D /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0654BA58FE8ECEE0C697A12F /* OpenGL.framework */; }; + F5548CDA0157D58D01F6286D /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00E9D914FEDB4D29C697A12F /* CoreAudio.framework */; }; + F5548CDB0157D58D01F6286D /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0654BA5AFE8ECEE0C697A12F /* AppKit.framework */; }; + F5548CDC0157D58D01F6286D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0654BA5BFE8ECEE0C697A12F /* Foundation.framework */; }; + F5548CDD0157D5CF01F6286D /* cl_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AB00868211C697A10E /* cl_main.c */; }; + F5548CDE0157D5CF01F6286D /* cl_cgame.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A600868211C697A10E /* cl_cgame.c */; }; + F5548CDF0157D5CF01F6286D /* cl_cin.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A700868211C697A10E /* cl_cin.c */; }; + F5548CE00157D5CF01F6286D /* cl_console.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A800868211C697A10E /* cl_console.c */; }; + F5548CE10157D5CF01F6286D /* cl_input.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9A900868211C697A10E /* cl_input.c */; }; + F5548CE20157D5CF01F6286D /* cl_keys.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AA00868211C697A10E /* cl_keys.c */; }; + F5548CE30157D5CF01F6286D /* cl_net_chan.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AC00868211C697A10E /* cl_net_chan.c */; }; + F5548CE40157D5CF01F6286D /* cl_parse.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AD00868211C697A10E /* cl_parse.c */; }; + F5548CE50157D5CF01F6286D /* cl_scrn.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AE00868211C697A10E /* cl_scrn.c */; }; + F5548CE60157D5CF01F6286D /* cl_ui.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9AF00868211C697A10E /* cl_ui.c */; }; + F5548CE70157D5CF01F6286D /* snd_adpcm.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B200868211C697A10E /* snd_adpcm.c */; }; + F5548CE80157D5CF01F6286D /* snd_dma.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B300868211C697A10E /* snd_dma.c */; }; + F5548CE90157D5CF01F6286D /* snd_mem.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B500868211C697A10E /* snd_mem.c */; }; + F5548CEA0157D5CF01F6286D /* snd_mix.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B600868211C697A10E /* snd_mix.c */; }; + F5548CEB0157D5CF01F6286D /* snd_wavelet.c in Sources */ = {isa = PBXBuildFile; fileRef = 012AD9B800868211C697A10E /* snd_wavelet.c */; }; + F55DF57B01513A8101F6286D /* g_tramcar.c in Sources */ = {isa = PBXBuildFile; fileRef = F55DF57A01513A8101F6286D /* g_tramcar.c */; }; + F5673BEF015686CC01F628AA /* cg_newDraw.c in Sources */ = {isa = PBXBuildFile; fileRef = F5673BEE015686CC01F628AA /* cg_newDraw.c */; }; + F5673BF90156953F01F628AA /* jmemnobs.c in Sources */ = {isa = PBXBuildFile; fileRef = 012ADA9A00868211C697A10E /* jmemnobs.c */; }; + F5ABC7B7013B051201F6286D /* be_aas_routetable.h in Headers */ = {isa = PBXBuildFile; fileRef = F5ABC7B6013B051201F6286D /* be_aas_routetable.h */; }; + F5ABC7B8013B051201F6286D /* be_aas_routetable.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7B5013B051201F6286D /* be_aas_routetable.c */; }; + F5ABC7BA013B054201F6286D /* tr_cmesh.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7B9013B054201F6286D /* tr_cmesh.c */; }; + F5ABC7E1013B0EAB01F6286D /* ai_cast_fight.h in Headers */ = {isa = PBXBuildFile; fileRef = F5ABC7CB013B0EAB01F6286D /* ai_cast_fight.h */; }; + F5ABC7E2013B0EAB01F6286D /* ai_cast_global.h in Headers */ = {isa = PBXBuildFile; fileRef = F5ABC7CF013B0EAB01F6286D /* ai_cast_global.h */; }; + F5ABC7E6013B0EAB01F6286D /* ai_cast_characters.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7C7013B0EAB01F6286D /* ai_cast_characters.c */; }; + F5ABC7E7013B0EAB01F6286D /* ai_cast_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7C8013B0EAB01F6286D /* ai_cast_debug.c */; }; + F5ABC7E8013B0EAB01F6286D /* ai_cast_events.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7C9013B0EAB01F6286D /* ai_cast_events.c */; }; + F5ABC7E9013B0EAB01F6286D /* ai_cast_fight.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7CA013B0EAB01F6286D /* ai_cast_fight.c */; }; + F5ABC7EA013B0EAB01F6286D /* ai_cast_func_attack.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7CC013B0EAB01F6286D /* ai_cast_func_attack.c */; }; + F5ABC7EB013B0EAB01F6286D /* ai_cast_func_boss1.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7CD013B0EAB01F6286D /* ai_cast_func_boss1.c */; }; + F5ABC7EC013B0EAB01F6286D /* ai_cast_funcs.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7CE013B0EAB01F6286D /* ai_cast_funcs.c */; }; + F5ABC7ED013B0EAB01F6286D /* ai_cast_script_actions.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D0013B0EAB01F6286D /* ai_cast_script_actions.c */; }; + F5ABC7EE013B0EAB01F6286D /* ai_cast_script_ents.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D1013B0EAB01F6286D /* ai_cast_script_ents.c */; }; + F5ABC7EF013B0EAB01F6286D /* ai_cast_script.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D2013B0EAB01F6286D /* ai_cast_script.c */; }; + F5ABC7F0013B0EAB01F6286D /* ai_cast_sight.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D3013B0EAB01F6286D /* ai_cast_sight.c */; }; + F5ABC7F1013B0EAB01F6286D /* ai_cast_think.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D4013B0EAB01F6286D /* ai_cast_think.c */; }; + F5ABC7F2013B0EAB01F6286D /* ai_cast.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D5013B0EAB01F6286D /* ai_cast.c */; }; + F5ABC7F3013B0EAB01F6286D /* bg_animation.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D7013B0EAB01F6286D /* bg_animation.c */; }; + F5ABC7F4013B0EAB01F6286D /* g_alarm.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D8013B0EAB01F6286D /* g_alarm.c */; }; + F5ABC7F5013B0EAB01F6286D /* g_props.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7DB013B0EAB01F6286D /* g_props.c */; }; + F5ABC7F6013B0EAB01F6286D /* g_save.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7DC013B0EAB01F6286D /* g_save.c */; }; + F5ABC7F7013B0EAB01F6286D /* g_script_actions.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7DD013B0EAB01F6286D /* g_script_actions.c */; }; + F5ABC7F8013B0EAB01F6286D /* g_script.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7DE013B0EAB01F6286D /* g_script.c */; }; + F5ABC80F013B120B01F6286D /* ai_dmq3.h in Headers */ = {isa = PBXBuildFile; fileRef = F5ABC802013B120B01F6286D /* ai_dmq3.h */; }; + F5ABC810013B120B01F6286D /* ai_main.h in Headers */ = {isa = PBXBuildFile; fileRef = F5ABC804013B120B01F6286D /* ai_main.h */; }; + F5ABC811013B120B01F6286D /* ai_team.h in Headers */ = {isa = PBXBuildFile; fileRef = F5ABC806013B120B01F6286D /* ai_team.h */; }; + F5ABC817013B120B01F6286D /* ai_chat.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7FB013B120B01F6286D /* ai_chat.c */; }; + F5ABC818013B120B01F6286D /* ai_cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7FD013B120B01F6286D /* ai_cmd.c */; }; + F5ABC819013B120B01F6286D /* ai_dmnet.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7FF013B120B01F6286D /* ai_dmnet.c */; }; + F5ABC81A013B120B01F6286D /* ai_dmq3.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC801013B120B01F6286D /* ai_dmq3.c */; }; + F5ABC81B013B120B01F6286D /* ai_main.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC803013B120B01F6286D /* ai_main.c */; }; + F5ABC81C013B120B01F6286D /* ai_team.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC805013B120B01F6286D /* ai_team.c */; }; + F5ABC821013B12E701F6286D /* cg_flamethrower.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC81D013B12E701F6286D /* cg_flamethrower.c */; }; + F5ABC822013B12E701F6286D /* cg_particles.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC81E013B12E701F6286D /* cg_particles.c */; }; + F5ABC823013B12E701F6286D /* cg_sound.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC81F013B12E701F6286D /* cg_sound.c */; }; + F5ABC824013B12E701F6286D /* cg_trails.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC820013B12E701F6286D /* cg_trails.c */; }; + F5ABC825013B145701F6286D /* bg_animation.c in Sources */ = {isa = PBXBuildFile; fileRef = F5ABC7D7013B0EAB01F6286D /* bg_animation.c */; }; + F5DBFB980136ACB501F6286D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5DBFB970136AC6901F6286D /* IOKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXBuildStyle section */ + 07F3F50BFFE98E8EC697A10E /* Development */ = { + isa = PBXBuildStyle; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + NO_OPT_CFLAGS = "-O0 -fno-default-inline -fno-inline -fno-inline-functions"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ( + "\U0001-g", + "-Wall", + "-DQGL_CHECK_GL_ERRORS", + "$(NO_OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + ZERO_LINK = YES; + }; + name = Development; + }; + 07F3F50CFFE98E8EC697A10E /* Deployment */ = { + isa = PBXBuildStyle; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + OPT_CFLAGS = "-O3 -Wno-conversion -Wno-switch -Wno-unused -faltivec -ffast-math -funroll-loops"; + OTHER_CFLAGS = ( + "\U0001-DNDEBUG", + "$(OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + ZERO_LINK = NO; + }; + name = Deployment; + }; +/* End PBXBuildStyle section */ + +/* Begin PBXBundleTarget section */ + 00F5ED38FEBA95B7C697A12F /* qagame */ = { + isa = PBXBundleTarget; + buildConfigurationList = 256D53C10A07EDF400E71389 /* Build configuration list for PBXBundleTarget "qagame" */; + buildPhases = ( + 00F5ED39FEBA95B7C697A12F /* Headers */, + 00F5ED55FEBA95B7C697A12F /* Resources */, + 00F5ED56FEBA95B7C697A12F /* Sources */, + 00F5ED77FEBA95B7C697A12F /* Frameworks */, + 00F5ED78FEBA95B7C697A12F /* Rez */, + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "/Users/Shared/$(USER)/InstalledProducts"; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = "-DGAMEDLL"; + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = qagame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + name = qagame; + productInstallPath = "/Users/Shared/$(USER)/InstalledProducts"; + productName = game; + productReference = 07F3F507FFE98E8EC697A10E /* qagame.bundle */; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + qagame + CFBundleGetInfoString + Quake 3 Arena (1.16) + CFBundleIconFile + Quake3.icns + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.16 + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + }; + 00F5ED90FEBA9615C697A12F /* cgame */ = { + isa = PBXBundleTarget; + buildConfigurationList = 256D53C50A07EDF400E71389 /* Build configuration list for PBXBundleTarget "cgame" */; + buildPhases = ( + 00F5ED91FEBA9615C697A12F /* Headers */, + 00F5ED92FEBA9615C697A12F /* Resources */, + 00F5ED93FEBA9615C697A12F /* Sources */, + 00F5ED94FEBA9615C697A12F /* Frameworks */, + 00F5ED95FEBA9615C697A12F /* Rez */, + ); + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = "-DCGAMEDLL"; + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = cgame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + name = cgame; + productName = cgame; + productReference = 07F3F508FFE98E8EC697A10E /* cgame.bundle */; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + BNDL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + }; + 016EAE0300B4BDD1C697A10E /* ui */ = { + isa = PBXBundleTarget; + buildConfigurationList = 256D53C90A07EDF400E71389 /* Build configuration list for PBXBundleTarget "ui" */; + buildPhases = ( + 016EAE0400B4BDD1C697A10E /* Headers */, + 016EAE0900B4BDD1C697A10E /* Resources */, + 016EAE0A00B4BDD1C697A10E /* Sources */, + 016EAE1200B4BDD1C697A10E /* Frameworks */, + 016EAE1300B4BDD1C697A10E /* Rez */, + ); + buildSettings = { + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = ui; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + }; + dependencies = ( + ); + name = ui; + productName = ui; + productReference = 016EAE0200B4BDD1C697A10E /* ui.bundle */; + productSettingsXML = " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + BNDL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + 0.0.1d1 + + +"; + }; +/* End PBXBundleTarget section */ + +/* Begin PBXContainerItemProxy section */ + 256D53A50A07EDC000E71389 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0654BA41FE8ECEE0C697A12F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 016EAE0300B4BDD1C697A10E; + remoteInfo = ui; + }; + 256D53A60A07EDC000E71389 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0654BA41FE8ECEE0C697A12F /* Project object */; + proxyType = 1; + remoteGlobalIDString = F50905BF0157C20601F6286D; + remoteInfo = WolfDedicated; + }; + 256D53A70A07EDC000E71389 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0654BA41FE8ECEE0C697A12F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 00F5ED90FEBA9615C697A12F; + remoteInfo = cgame; + }; + 256D53A80A07EDC000E71389 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0654BA41FE8ECEE0C697A12F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 00F5ED38FEBA95B7C697A12F; + remoteInfo = qagame; + }; + 256D53A90A07EDC000E71389 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0654BA41FE8ECEE0C697A12F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0654BA5CFE8ECEE0C697A12F; + remoteInfo = "WolfMP (Application)"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 00E9D914FEDB4D29C697A12F /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = /System/Library/Frameworks/CoreAudio.framework; sourceTree = ""; }; + 011F78F200B25B65C697A10E /* macosx_qgl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macosx_qgl.h; sourceTree = ""; }; + 012AD90800868211C697A10E /* aasfile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aasfile.h; sourceTree = ""; }; + 012AD90900868211C697A10E /* be_aas_bsp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_bsp.h; sourceTree = ""; }; + 012AD90A00868211C697A10E /* be_aas_bspq3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_bspq3.c; sourceTree = ""; }; + 012AD90B00868211C697A10E /* be_aas_cluster.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_cluster.c; sourceTree = ""; }; + 012AD90C00868211C697A10E /* be_aas_cluster.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_cluster.h; sourceTree = ""; }; + 012AD90D00868211C697A10E /* be_aas_debug.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_debug.c; sourceTree = ""; }; + 012AD90E00868211C697A10E /* be_aas_debug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_debug.h; sourceTree = ""; }; + 012AD90F00868211C697A10E /* be_aas_def.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_def.h; sourceTree = ""; }; + 012AD91000868211C697A10E /* be_aas_entity.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_entity.c; sourceTree = ""; }; + 012AD91100868211C697A10E /* be_aas_entity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_entity.h; sourceTree = ""; }; + 012AD91200868211C697A10E /* be_aas_file.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_file.c; sourceTree = ""; }; + 012AD91300868211C697A10E /* be_aas_file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_file.h; sourceTree = ""; }; + 012AD91400868211C697A10E /* be_aas_funcs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_funcs.h; sourceTree = ""; }; + 012AD91500868211C697A10E /* be_aas_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_main.c; sourceTree = ""; }; + 012AD91600868211C697A10E /* be_aas_main.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_main.h; sourceTree = ""; }; + 012AD91700868211C697A10E /* be_aas_move.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_move.c; sourceTree = ""; }; + 012AD91800868211C697A10E /* be_aas_move.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_move.h; sourceTree = ""; }; + 012AD91900868211C697A10E /* be_aas_optimize.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_optimize.c; sourceTree = ""; }; + 012AD91A00868211C697A10E /* be_aas_optimize.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_optimize.h; sourceTree = ""; }; + 012AD91B00868211C697A10E /* be_aas_reach.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_reach.c; sourceTree = ""; }; + 012AD91C00868211C697A10E /* be_aas_reach.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_reach.h; sourceTree = ""; }; + 012AD91D00868211C697A10E /* be_aas_route.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_route.c; sourceTree = ""; }; + 012AD91E00868211C697A10E /* be_aas_route.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_route.h; sourceTree = ""; }; + 012AD91F00868211C697A10E /* be_aas_routealt.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_routealt.c; sourceTree = ""; }; + 012AD92000868211C697A10E /* be_aas_routealt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_routealt.h; sourceTree = ""; }; + 012AD92100868211C697A10E /* be_aas_sample.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_sample.c; sourceTree = ""; }; + 012AD92200868211C697A10E /* be_aas_sample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_sample.h; sourceTree = ""; }; + 012AD92300868211C697A10E /* be_ai_char.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_ai_char.c; sourceTree = ""; }; + 012AD92400868211C697A10E /* be_ai_chat.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_ai_chat.c; sourceTree = ""; }; + 012AD92500868211C697A10E /* be_ai_gen.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_ai_gen.c; sourceTree = ""; }; + 012AD92600868211C697A10E /* be_ai_goal.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_ai_goal.c; sourceTree = ""; }; + 012AD92700868211C697A10E /* be_ai_move.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_ai_move.c; sourceTree = ""; }; + 012AD92800868211C697A10E /* be_ai_weap.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_ai_weap.c; sourceTree = ""; }; + 012AD92900868211C697A10E /* be_ai_weight.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_ai_weight.c; sourceTree = ""; }; + 012AD92A00868211C697A10E /* be_ai_weight.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_ai_weight.h; sourceTree = ""; }; + 012AD92B00868211C697A10E /* be_ea.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_ea.c; sourceTree = ""; }; + 012AD92C00868211C697A10E /* be_interface.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_interface.c; sourceTree = ""; }; + 012AD92D00868211C697A10E /* be_interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_interface.h; sourceTree = ""; }; + 012AD92E00868211C697A10E /* l_crc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_crc.c; sourceTree = ""; }; + 012AD92F00868211C697A10E /* l_crc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_crc.h; sourceTree = ""; }; + 012AD93000868211C697A10E /* l_libvar.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_libvar.c; sourceTree = ""; }; + 012AD93100868211C697A10E /* l_libvar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_libvar.h; sourceTree = ""; }; + 012AD93200868211C697A10E /* l_log.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_log.c; sourceTree = ""; }; + 012AD93300868211C697A10E /* l_log.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_log.h; sourceTree = ""; }; + 012AD93400868211C697A10E /* l_memory.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_memory.c; sourceTree = ""; }; + 012AD93500868211C697A10E /* l_memory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_memory.h; sourceTree = ""; }; + 012AD93600868211C697A10E /* l_precomp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_precomp.c; sourceTree = ""; }; + 012AD93700868211C697A10E /* l_precomp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_precomp.h; sourceTree = ""; }; + 012AD93800868211C697A10E /* l_script.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_script.c; sourceTree = ""; }; + 012AD93900868211C697A10E /* l_script.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_script.h; sourceTree = ""; }; + 012AD93A00868211C697A10E /* l_struct.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_struct.c; sourceTree = ""; }; + 012AD93B00868211C697A10E /* l_struct.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_struct.h; sourceTree = ""; }; + 012AD93C00868211C697A10E /* l_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_utils.h; sourceTree = ""; }; + 012AD93E00868211C697A10E /* _files.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = _files.c; sourceTree = ""; }; + 012AD93F00868211C697A10E /* aas_areamerging.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_areamerging.c; sourceTree = ""; }; + 012AD94000868211C697A10E /* aas_areamerging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_areamerging.h; sourceTree = ""; }; + 012AD94100868211C697A10E /* aas_cfg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_cfg.c; sourceTree = ""; }; + 012AD94200868211C697A10E /* aas_cfg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_cfg.h; sourceTree = ""; }; + 012AD94300868211C697A10E /* aas_create.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_create.c; sourceTree = ""; }; + 012AD94400868211C697A10E /* aas_create.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_create.h; sourceTree = ""; }; + 012AD94500868211C697A10E /* aas_edgemelting.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_edgemelting.c; sourceTree = ""; }; + 012AD94600868211C697A10E /* aas_edgemelting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_edgemelting.h; sourceTree = ""; }; + 012AD94700868211C697A10E /* aas_facemerging.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_facemerging.c; sourceTree = ""; }; + 012AD94800868211C697A10E /* aas_facemerging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_facemerging.h; sourceTree = ""; }; + 012AD94900868211C697A10E /* aas_file.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_file.c; sourceTree = ""; }; + 012AD94A00868211C697A10E /* aas_file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_file.h; sourceTree = ""; }; + 012AD94B00868211C697A10E /* aas_gsubdiv.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_gsubdiv.c; sourceTree = ""; }; + 012AD94C00868211C697A10E /* aas_gsubdiv.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_gsubdiv.h; sourceTree = ""; }; + 012AD94D00868211C697A10E /* aas_map.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_map.c; sourceTree = ""; }; + 012AD94E00868211C697A10E /* aas_map.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_map.h; sourceTree = ""; }; + 012AD94F00868211C697A10E /* aas_prunenodes.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_prunenodes.c; sourceTree = ""; }; + 012AD95000868211C697A10E /* aas_prunenodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_prunenodes.h; sourceTree = ""; }; + 012AD95100868211C697A10E /* aas_store.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = aas_store.c; sourceTree = ""; }; + 012AD95200868211C697A10E /* aas_store.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = aas_store.h; sourceTree = ""; }; + 012AD95400868211C697A10E /* be_aas_bspc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_bspc.c; sourceTree = ""; }; + 012AD95500868211C697A10E /* be_aas_bspc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_bspc.h; sourceTree = ""; }; + 012AD95600868211C697A10E /* brushbsp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = brushbsp.c; sourceTree = ""; }; + 012AD95700868211C697A10E /* bspc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bspc.c; sourceTree = ""; }; + 012AD95900868211C697A10E /* csg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = csg.c; sourceTree = ""; }; + 012AD95A00868211C697A10E /* faces.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = faces.c; sourceTree = ""; }; + 012AD95C00868211C697A10E /* glfile.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = glfile.c; sourceTree = ""; }; + 012AD95D00868211C697A10E /* l_bsp_ent.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_bsp_ent.c; sourceTree = ""; }; + 012AD95E00868211C697A10E /* l_bsp_ent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_bsp_ent.h; sourceTree = ""; }; + 012AD95F00868211C697A10E /* l_bsp_hl.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_bsp_hl.c; sourceTree = ""; }; + 012AD96000868211C697A10E /* l_bsp_hl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_bsp_hl.h; sourceTree = ""; }; + 012AD96100868211C697A10E /* l_bsp_q1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_bsp_q1.c; sourceTree = ""; }; + 012AD96200868211C697A10E /* l_bsp_q1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_bsp_q1.h; sourceTree = ""; }; + 012AD96300868211C697A10E /* l_bsp_q2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_bsp_q2.c; sourceTree = ""; }; + 012AD96400868211C697A10E /* l_bsp_q2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_bsp_q2.h; sourceTree = ""; }; + 012AD96500868211C697A10E /* l_bsp_q3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_bsp_q3.c; sourceTree = ""; }; + 012AD96600868211C697A10E /* l_bsp_q3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_bsp_q3.h; sourceTree = ""; }; + 012AD96700868211C697A10E /* l_bsp_sin.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_bsp_sin.c; sourceTree = ""; }; + 012AD96800868211C697A10E /* l_bsp_sin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_bsp_sin.h; sourceTree = ""; }; + 012AD96900868211C697A10E /* l_cmd.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_cmd.c; sourceTree = ""; }; + 012AD96A00868211C697A10E /* l_cmd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_cmd.h; sourceTree = ""; }; + 012AD96B00868211C697A10E /* l_log.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_log.c; sourceTree = ""; }; + 012AD96C00868211C697A10E /* l_log.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_log.h; sourceTree = ""; }; + 012AD96D00868211C697A10E /* l_math.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_math.c; sourceTree = ""; }; + 012AD96E00868211C697A10E /* l_math.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_math.h; sourceTree = ""; }; + 012AD96F00868211C697A10E /* l_mem.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_mem.c; sourceTree = ""; }; + 012AD97000868211C697A10E /* l_mem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_mem.h; sourceTree = ""; }; + 012AD97100868211C697A10E /* l_poly.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_poly.c; sourceTree = ""; }; + 012AD97200868211C697A10E /* l_poly.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_poly.h; sourceTree = ""; }; + 012AD97300868211C697A10E /* l_qfiles.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_qfiles.c; sourceTree = ""; }; + 012AD97400868211C697A10E /* l_qfiles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_qfiles.h; sourceTree = ""; }; + 012AD97500868211C697A10E /* l_threads.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_threads.c; sourceTree = ""; }; + 012AD97600868211C697A10E /* l_threads.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_threads.h; sourceTree = ""; }; + 012AD97700868211C697A10E /* l_utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = l_utils.c; sourceTree = ""; }; + 012AD97800868211C697A10E /* l_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = l_utils.h; sourceTree = ""; }; + 012AD97900868211C697A10E /* leakfile.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = leakfile.c; sourceTree = ""; }; + 012AD97A00868211C697A10E /* map.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = map.c; sourceTree = ""; }; + 012AD97B00868211C697A10E /* map_hl.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = map_hl.c; sourceTree = ""; }; + 012AD97C00868211C697A10E /* map_q1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = map_q1.c; sourceTree = ""; }; + 012AD97D00868211C697A10E /* map_q2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = map_q2.c; sourceTree = ""; }; + 012AD97E00868211C697A10E /* map_q3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = map_q3.c; sourceTree = ""; }; + 012AD97F00868211C697A10E /* map_sin.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = map_sin.c; sourceTree = ""; }; + 012AD98000868211C697A10E /* nodraw.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nodraw.c; sourceTree = ""; }; + 012AD98100868211C697A10E /* portals.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = portals.c; sourceTree = ""; }; + 012AD98200868211C697A10E /* prtfile.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = prtfile.c; sourceTree = ""; }; + 012AD98300868211C697A10E /* q2files.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = q2files.h; sourceTree = ""; }; + 012AD98400868211C697A10E /* q3files.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = q3files.h; sourceTree = ""; }; + 012AD98500868211C697A10E /* qbsp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = qbsp.h; sourceTree = ""; }; + 012AD98600868211C697A10E /* qfiles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = qfiles.h; sourceTree = ""; }; + 012AD98700868211C697A10E /* sinfiles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sinfiles.h; sourceTree = ""; }; + 012AD98800868211C697A10E /* tetrahedron.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = tetrahedron.c; sourceTree = ""; }; + 012AD98900868211C697A10E /* tetrahedron.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = tetrahedron.h; sourceTree = ""; }; + 012AD98A00868211C697A10E /* textures.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = textures.c; sourceTree = ""; }; + 012AD98B00868211C697A10E /* tree.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tree.c; sourceTree = ""; }; + 012AD98C00868211C697A10E /* writebsp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = writebsp.c; sourceTree = ""; }; + 012AD98E00868211C697A10E /* cg_consolecmds.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_consolecmds.c; sourceTree = ""; }; + 012AD98F00868211C697A10E /* cg_draw.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_draw.c; sourceTree = ""; }; + 012AD99000868211C697A10E /* cg_drawtools.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_drawtools.c; sourceTree = ""; }; + 012AD99100868211C697A10E /* cg_effects.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_effects.c; sourceTree = ""; }; + 012AD99200868211C697A10E /* cg_ents.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_ents.c; sourceTree = ""; }; + 012AD99300868211C697A10E /* cg_event.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_event.c; sourceTree = ""; }; + 012AD99400868211C697A10E /* cg_info.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_info.c; sourceTree = ""; }; + 012AD99500868211C697A10E /* cg_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cg_local.h; sourceTree = ""; }; + 012AD99600868211C697A10E /* cg_localents.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_localents.c; sourceTree = ""; }; + 012AD99700868211C697A10E /* cg_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_main.c; sourceTree = ""; }; + 012AD99800868211C697A10E /* cg_marks.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_marks.c; sourceTree = ""; }; + 012AD99A00868211C697A10E /* cg_players.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_players.c; sourceTree = ""; }; + 012AD99B00868211C697A10E /* cg_playerstate.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_playerstate.c; sourceTree = ""; }; + 012AD99C00868211C697A10E /* cg_predict.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_predict.c; sourceTree = ""; }; + 012AD99D00868211C697A10E /* cg_public.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cg_public.h; sourceTree = ""; }; + 012AD99E00868211C697A10E /* cg_scoreboard.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_scoreboard.c; sourceTree = ""; }; + 012AD99F00868211C697A10E /* cg_servercmds.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_servercmds.c; sourceTree = ""; }; + 012AD9A000868211C697A10E /* cg_snapshot.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_snapshot.c; sourceTree = ""; }; + 012AD9A100868211C697A10E /* cg_syscalls.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_syscalls.c; sourceTree = ""; }; + 012AD9A200868211C697A10E /* cg_view.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_view.c; sourceTree = ""; }; + 012AD9A300868211C697A10E /* cg_weapons.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_weapons.c; sourceTree = ""; }; + 012AD9A400868211C697A10E /* tr_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tr_types.h; sourceTree = ""; }; + 012AD9A600868211C697A10E /* cl_cgame.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_cgame.c; sourceTree = ""; }; + 012AD9A700868211C697A10E /* cl_cin.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_cin.c; sourceTree = ""; }; + 012AD9A800868211C697A10E /* cl_console.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_console.c; sourceTree = ""; }; + 012AD9A900868211C697A10E /* cl_input.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_input.c; sourceTree = ""; }; + 012AD9AA00868211C697A10E /* cl_keys.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_keys.c; sourceTree = ""; }; + 012AD9AB00868211C697A10E /* cl_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_main.c; sourceTree = ""; }; + 012AD9AC00868211C697A10E /* cl_net_chan.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_net_chan.c; sourceTree = ""; }; + 012AD9AD00868211C697A10E /* cl_parse.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_parse.c; sourceTree = ""; }; + 012AD9AE00868211C697A10E /* cl_scrn.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_scrn.c; sourceTree = ""; }; + 012AD9AF00868211C697A10E /* cl_ui.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cl_ui.c; sourceTree = ""; }; + 012AD9B000868211C697A10E /* client.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = client.h; sourceTree = ""; }; + 012AD9B100868211C697A10E /* keys.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = keys.h; sourceTree = ""; }; + 012AD9B200868211C697A10E /* snd_adpcm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = snd_adpcm.c; sourceTree = ""; }; + 012AD9B300868211C697A10E /* snd_dma.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = snd_dma.c; sourceTree = ""; }; + 012AD9B400868211C697A10E /* snd_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = snd_local.h; sourceTree = ""; }; + 012AD9B500868211C697A10E /* snd_mem.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = snd_mem.c; sourceTree = ""; }; + 012AD9B600868211C697A10E /* snd_mix.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = snd_mix.c; sourceTree = ""; }; + 012AD9B700868211C697A10E /* snd_public.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = snd_public.h; sourceTree = ""; }; + 012AD9B800868211C697A10E /* snd_wavelet.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = snd_wavelet.c; sourceTree = ""; }; + 012AD9BA00868211C697A10E /* ahangles.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ahangles.c; sourceTree = ""; }; + 012AD9BB00868211C697A10E /* ahangles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ahangles.h; sourceTree = ""; }; + 012AD9BC00868211C697A10E /* ahglobal.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ahglobal.c; sourceTree = ""; }; + 012AD9BD00868211C697A10E /* ahglobal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ahglobal.h; sourceTree = ""; }; + 012AD9BE00868211C697A10E /* ahglyph.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ahglyph.c; sourceTree = ""; }; + 012AD9BF00868211C697A10E /* ahglyph.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ahglyph.h; sourceTree = ""; }; + 012AD9C000868211C697A10E /* ahhint.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ahhint.c; sourceTree = ""; }; + 012AD9C100868211C697A10E /* ahhint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ahhint.h; sourceTree = ""; }; + 012AD9C200868211C697A10E /* ahloader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ahloader.h; sourceTree = ""; }; + 012AD9C300868211C697A10E /* ahmodule.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ahmodule.c; sourceTree = ""; }; + 012AD9C400868211C697A10E /* ahmodule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ahmodule.h; sourceTree = ""; }; + 012AD9C500868211C697A10E /* ahoptim.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ahoptim.c; sourceTree = ""; }; + 012AD9C600868211C697A10E /* ahoptim.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ahoptim.h; sourceTree = ""; }; + 012AD9C700868211C697A10E /* ahtypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ahtypes.h; sourceTree = ""; }; + 012AD9C800868211C697A10E /* autohint.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = autohint.c; sourceTree = ""; }; + 012AD9C900868211C697A10E /* autohint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = autohint.h; sourceTree = ""; }; + 012AD9CA00868211C697A10E /* freetype.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = freetype.h; sourceTree = ""; }; + 012AD9CB00868211C697A10E /* ftbase.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = ftbase.c; sourceTree = ""; }; + 012AD9CC00868211C697A10E /* ftbbox.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = ftbbox.c; sourceTree = ""; }; + 012AD9CD00868211C697A10E /* ftbbox.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = ftbbox.h; sourceTree = ""; }; + 012AD9CE00868211C697A10E /* ftcalc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftcalc.c; sourceTree = ""; }; + 012AD9CF00868211C697A10E /* ftcalc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftcalc.h; sourceTree = ""; }; + 012AD9D000868211C697A10E /* ftconfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftconfig.h; sourceTree = ""; }; + 012AD9D100868211C697A10E /* ftdebug.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftdebug.c; sourceTree = ""; }; + 012AD9D200868211C697A10E /* ftdebug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftdebug.h; sourceTree = ""; }; + 012AD9D300868211C697A10E /* ftdriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftdriver.h; sourceTree = ""; }; + 012AD9D400868211C697A10E /* fterrors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fterrors.h; sourceTree = ""; }; + 012AD9D500868211C697A10E /* ftextend.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftextend.c; sourceTree = ""; }; + 012AD9D600868211C697A10E /* ftextend.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftextend.h; sourceTree = ""; }; + 012AD9D700868211C697A10E /* ftglyph.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftglyph.c; sourceTree = ""; }; + 012AD9D800868211C697A10E /* ftglyph.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftglyph.h; sourceTree = ""; }; + 012AD9D900868211C697A10E /* ftgrays.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftgrays.c; sourceTree = ""; }; + 012AD9DA00868211C697A10E /* ftgrays.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftgrays.h; sourceTree = ""; }; + 012AD9DB00868211C697A10E /* ftimage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftimage.h; sourceTree = ""; }; + 012AD9DC00868211C697A10E /* ftinit.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftinit.c; sourceTree = ""; }; + 012AD9DD00868211C697A10E /* ftlist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftlist.c; sourceTree = ""; }; + 012AD9DE00868211C697A10E /* ftlist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftlist.h; sourceTree = ""; }; + 012AD9DF00868211C697A10E /* ftmemory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftmemory.h; sourceTree = ""; }; + 012AD9E000868211C697A10E /* ftmm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftmm.c; sourceTree = ""; }; + 012AD9E100868211C697A10E /* ftmm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftmm.h; sourceTree = ""; }; + 012AD9E200868211C697A10E /* ftmodule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftmodule.h; sourceTree = ""; }; + 012AD9E300868211C697A10E /* ftnames.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftnames.c; sourceTree = ""; }; + 012AD9E400868211C697A10E /* ftnames.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftnames.h; sourceTree = ""; }; + 012AD9E500868211C697A10E /* ftobjs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftobjs.c; sourceTree = ""; }; + 012AD9E600868211C697A10E /* ftobjs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftobjs.h; sourceTree = ""; }; + 012AD9E700868211C697A10E /* ftoption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftoption.h; sourceTree = ""; }; + 012AD9E800868211C697A10E /* ftoutln.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftoutln.c; sourceTree = ""; }; + 012AD9E900868211C697A10E /* ftoutln.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftoutln.h; sourceTree = ""; }; + 012AD9EA00868211C697A10E /* ftraster.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftraster.c; sourceTree = ""; }; + 012AD9EB00868211C697A10E /* ftraster.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftraster.h; sourceTree = ""; }; + 012AD9EC00868211C697A10E /* ftrend1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftrend1.c; sourceTree = ""; }; + 012AD9ED00868211C697A10E /* ftrend1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftrend1.h; sourceTree = ""; }; + 012AD9EE00868211C697A10E /* ftrender.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftrender.h; sourceTree = ""; }; + 012AD9EF00868211C697A10E /* ftsmooth.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftsmooth.c; sourceTree = ""; }; + 012AD9F000868211C697A10E /* ftsmooth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftsmooth.h; sourceTree = ""; }; + 012AD9F100868211C697A10E /* ftstream.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftstream.c; sourceTree = ""; }; + 012AD9F200868211C697A10E /* ftstream.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftstream.h; sourceTree = ""; }; + 012AD9F300868211C697A10E /* ftsystem.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ftsystem.c; sourceTree = ""; }; + 012AD9F400868211C697A10E /* ftsystem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ftsystem.h; sourceTree = ""; }; + 012AD9F500868211C697A10E /* fttypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fttypes.h; sourceTree = ""; }; + 012AD9F600868211C697A10E /* keys.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = keys.h; sourceTree = ""; }; + 012AD9F700868211C697A10E /* psdriver.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = psdriver.c; sourceTree = ""; }; + 012AD9F800868211C697A10E /* psdriver.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = psdriver.h; sourceTree = ""; }; + 012AD9F900868211C697A10E /* psnames.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = psnames.c; sourceTree = ""; }; + 012AD9FA00868211C697A10E /* psnames.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = psnames.h; sourceTree = ""; }; + 012AD9FB00868211C697A10E /* pstables.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = pstables.h; sourceTree = ""; }; + 012AD9FC00868211C697A10E /* raster1.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = raster1.c; sourceTree = ""; }; + 012AD9FD00868211C697A10E /* sfconfig.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = sfconfig.h; sourceTree = ""; }; + 012AD9FE00868211C697A10E /* sfdriver.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sfdriver.c; sourceTree = ""; }; + 012AD9FF00868211C697A10E /* sfdriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sfdriver.h; sourceTree = ""; }; + 012ADA0000868211C697A10E /* sfnt.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = sfnt.c; sourceTree = ""; }; + 012ADA0100868211C697A10E /* sfnt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sfnt.h; sourceTree = ""; }; + 012ADA0200868211C697A10E /* sfobjs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sfobjs.c; sourceTree = ""; }; + 012ADA0300868211C697A10E /* sfobjs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sfobjs.h; sourceTree = ""; }; + 012ADA0400868211C697A10E /* smooth.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = smooth.c; sourceTree = ""; }; + 012ADA0500868211C697A10E /* t1errors.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = t1errors.h; sourceTree = ""; }; + 012ADA0600868211C697A10E /* t1tables.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = t1tables.h; sourceTree = ""; }; + 012ADA0700868211C697A10E /* t1types.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = t1types.h; sourceTree = ""; }; + 012ADA0800868211C697A10E /* t2errors.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = t2errors.h; sourceTree = ""; }; + 012ADA0900868211C697A10E /* t2types.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = t2types.h; sourceTree = ""; }; + 012ADA0A00868211C697A10E /* truetype.c */ = {isa = PBXFileReference; lastKnownFileType = file; path = truetype.c; sourceTree = ""; }; + 012ADA0B00868211C697A10E /* ttcmap.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttcmap.c; sourceTree = ""; }; + 012ADA0C00868211C697A10E /* ttcmap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttcmap.h; sourceTree = ""; }; + 012ADA0D00868211C697A10E /* ttconfig.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = ttconfig.h; sourceTree = ""; }; + 012ADA0E00868211C697A10E /* ttdriver.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttdriver.c; sourceTree = ""; }; + 012ADA0F00868211C697A10E /* ttdriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttdriver.h; sourceTree = ""; }; + 012ADA1000868211C697A10E /* tterrors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tterrors.h; sourceTree = ""; }; + 012ADA1100868211C697A10E /* ttgload.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttgload.c; sourceTree = ""; }; + 012ADA1200868211C697A10E /* ttgload.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttgload.h; sourceTree = ""; }; + 012ADA1300868211C697A10E /* ttinterp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttinterp.c; sourceTree = ""; }; + 012ADA1400868211C697A10E /* ttinterp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttinterp.h; sourceTree = ""; }; + 012ADA1500868211C697A10E /* ttload.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttload.c; sourceTree = ""; }; + 012ADA1600868211C697A10E /* ttload.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttload.h; sourceTree = ""; }; + 012ADA1700868211C697A10E /* ttnamedid.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = ttnamedid.h; sourceTree = ""; }; + 012ADA1800868211C697A10E /* ttnameid.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttnameid.h; sourceTree = ""; }; + 012ADA1900868211C697A10E /* ttobjs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttobjs.c; sourceTree = ""; }; + 012ADA1A00868211C697A10E /* ttobjs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttobjs.h; sourceTree = ""; }; + 012ADA1B00868211C697A10E /* ttpload.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttpload.c; sourceTree = ""; }; + 012ADA1C00868211C697A10E /* ttpload.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttpload.h; sourceTree = ""; }; + 012ADA1D00868211C697A10E /* ttpost.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttpost.c; sourceTree = ""; }; + 012ADA1E00868211C697A10E /* ttpost.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttpost.h; sourceTree = ""; }; + 012ADA1F00868211C697A10E /* ttsbit.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ttsbit.c; sourceTree = ""; }; + 012ADA2000868211C697A10E /* ttsbit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ttsbit.h; sourceTree = ""; }; + 012ADA2100868211C697A10E /* tttables.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tttables.h; sourceTree = ""; }; + 012ADA2200868211C697A10E /* tttags.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tttags.h; sourceTree = ""; }; + 012ADA2300868211C697A10E /* tttypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tttypes.h; sourceTree = ""; }; + 012ADA3300868211C697A10E /* be_aas.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas.h; sourceTree = ""; }; + 012ADA3400868211C697A10E /* be_ai_char.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_ai_char.h; sourceTree = ""; }; + 012ADA3500868211C697A10E /* be_ai_chat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_ai_chat.h; sourceTree = ""; }; + 012ADA3600868211C697A10E /* be_ai_gen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_ai_gen.h; sourceTree = ""; }; + 012ADA3700868211C697A10E /* be_ai_goal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_ai_goal.h; sourceTree = ""; }; + 012ADA3800868211C697A10E /* be_ai_move.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_ai_move.h; sourceTree = ""; }; + 012ADA3900868211C697A10E /* be_ai_weap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_ai_weap.h; sourceTree = ""; }; + 012ADA3A00868211C697A10E /* be_ea.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_ea.h; sourceTree = ""; }; + 012ADA3B00868211C697A10E /* bg_lib.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bg_lib.c; sourceTree = ""; }; + 012ADA3D00868211C697A10E /* bg_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bg_local.h; sourceTree = ""; }; + 012ADA3E00868211C697A10E /* bg_misc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bg_misc.c; sourceTree = ""; }; + 012ADA3F00868211C697A10E /* bg_pmove.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bg_pmove.c; sourceTree = ""; }; + 012ADA4000868211C697A10E /* bg_public.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bg_public.h; sourceTree = ""; }; + 012ADA4100868211C697A10E /* bg_slidemove.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bg_slidemove.c; sourceTree = ""; }; + 012ADA4200868211C697A10E /* botlib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = botlib.h; sourceTree = ""; }; + 012ADA4400868211C697A10E /* g_active.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_active.c; sourceTree = ""; }; + 012ADA4600868211C697A10E /* g_bot.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_bot.c; sourceTree = ""; }; + 012ADA4700868211C697A10E /* g_client.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_client.c; sourceTree = ""; }; + 012ADA4800868211C697A10E /* g_cmds.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_cmds.c; sourceTree = ""; }; + 012ADA4900868211C697A10E /* g_combat.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_combat.c; sourceTree = ""; }; + 012ADA4A00868211C697A10E /* g_items.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_items.c; sourceTree = ""; }; + 012ADA4B00868211C697A10E /* g_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = g_local.h; sourceTree = ""; }; + 012ADA4C00868211C697A10E /* g_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_main.c; sourceTree = ""; }; + 012ADA4D00868211C697A10E /* g_mem.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_mem.c; sourceTree = ""; }; + 012ADA4E00868211C697A10E /* g_misc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_misc.c; sourceTree = ""; }; + 012ADA4F00868211C697A10E /* g_missile.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_missile.c; sourceTree = ""; }; + 012ADA5000868211C697A10E /* g_mover.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_mover.c; sourceTree = ""; }; + 012ADA5100868211C697A10E /* g_public.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = g_public.h; sourceTree = ""; }; + 012ADA5400868211C697A10E /* g_session.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_session.c; sourceTree = ""; }; + 012ADA5500868211C697A10E /* g_spawn.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_spawn.c; sourceTree = ""; }; + 012ADA5600868211C697A10E /* g_svcmds.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_svcmds.c; sourceTree = ""; }; + 012ADA5700868211C697A10E /* g_syscalls.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_syscalls.c; sourceTree = ""; }; + 012ADA5800868211C697A10E /* g_target.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_target.c; sourceTree = ""; }; + 012ADA5900868211C697A10E /* g_team.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_team.c; sourceTree = ""; }; + 012ADA5A00868211C697A10E /* g_team.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = g_team.h; sourceTree = ""; }; + 012ADA5B00868211C697A10E /* g_trigger.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_trigger.c; sourceTree = ""; }; + 012ADA5C00868211C697A10E /* g_utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_utils.c; sourceTree = ""; }; + 012ADA5D00868211C697A10E /* g_weapon.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_weapon.c; sourceTree = ""; }; + 012ADA6000868211C697A10E /* q_math.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = q_math.c; sourceTree = ""; }; + 012ADA6100868211C697A10E /* q_shared.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = q_shared.c; sourceTree = ""; }; + 012ADA6200868211C697A10E /* q_shared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = q_shared.h; sourceTree = ""; }; + 012ADA6300868211C697A10E /* surfaceflags.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = surfaceflags.h; sourceTree = ""; }; + 012ADA6600868211C697A10E /* jcapimin.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcapimin.c; sourceTree = ""; }; + 012ADA6800868211C697A10E /* jccoefct.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jccoefct.c; sourceTree = ""; }; + 012ADA6900868211C697A10E /* jccolor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jccolor.c; sourceTree = ""; }; + 012ADA6A00868211C697A10E /* jcdctmgr.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcdctmgr.c; sourceTree = ""; }; + 012ADA6B00868211C697A10E /* jchuff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jchuff.c; sourceTree = ""; }; + 012ADA6C00868211C697A10E /* jchuff.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jchuff.h; sourceTree = ""; }; + 012ADA6D00868211C697A10E /* jcinit.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcinit.c; sourceTree = ""; }; + 012ADA6E00868211C697A10E /* jcmainct.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcmainct.c; sourceTree = ""; }; + 012ADA6F00868211C697A10E /* jcmarker.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcmarker.c; sourceTree = ""; }; + 012ADA7000868211C697A10E /* jcmaster.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcmaster.c; sourceTree = ""; }; + 012ADA7100868211C697A10E /* jcomapi.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcomapi.c; sourceTree = ""; }; + 012ADA7200868211C697A10E /* jconfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jconfig.h; sourceTree = ""; }; + 012ADA7300868211C697A10E /* jcparam.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcparam.c; sourceTree = ""; }; + 012ADA7400868211C697A10E /* jcphuff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcphuff.c; sourceTree = ""; }; + 012ADA7500868211C697A10E /* jcprepct.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcprepct.c; sourceTree = ""; }; + 012ADA7600868211C697A10E /* jcsample.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jcsample.c; sourceTree = ""; }; + 012ADA7700868211C697A10E /* jctrans.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jctrans.c; sourceTree = ""; }; + 012ADA7800868211C697A10E /* jdapimin.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdapimin.c; sourceTree = ""; }; + 012ADA7900868211C697A10E /* jdapistd.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdapistd.c; sourceTree = ""; }; + 012ADA7A00868211C697A10E /* jdatadst.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdatadst.c; sourceTree = ""; }; + 012ADA7B00868211C697A10E /* jdatasrc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdatasrc.c; sourceTree = ""; }; + 012ADA7C00868211C697A10E /* jdcoefct.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdcoefct.c; sourceTree = ""; }; + 012ADA7D00868211C697A10E /* jdcolor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdcolor.c; sourceTree = ""; }; + 012ADA7E00868211C697A10E /* jdct.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jdct.h; sourceTree = ""; }; + 012ADA7F00868211C697A10E /* jddctmgr.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jddctmgr.c; sourceTree = ""; }; + 012ADA8000868211C697A10E /* jdhuff.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdhuff.c; sourceTree = ""; }; + 012ADA8100868211C697A10E /* jdhuff.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jdhuff.h; sourceTree = ""; }; + 012ADA8200868211C697A10E /* jdinput.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdinput.c; sourceTree = ""; }; + 012ADA8300868211C697A10E /* jdmainct.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdmainct.c; sourceTree = ""; }; + 012ADA8400868211C697A10E /* jdmarker.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdmarker.c; sourceTree = ""; }; + 012ADA8500868211C697A10E /* jdmaster.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdmaster.c; sourceTree = ""; }; + 012ADA8800868211C697A10E /* jdpostct.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdpostct.c; sourceTree = ""; }; + 012ADA8900868211C697A10E /* jdsample.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdsample.c; sourceTree = ""; }; + 012ADA8A00868211C697A10E /* jdtrans.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jdtrans.c; sourceTree = ""; }; + 012ADA8B00868211C697A10E /* jerror.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jerror.c; sourceTree = ""; }; + 012ADA8C00868211C697A10E /* jerror.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jerror.h; sourceTree = ""; }; + 012ADA8D00868211C697A10E /* jfdctflt.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jfdctflt.c; sourceTree = ""; }; + 012ADA9000868211C697A10E /* jidctflt.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jidctflt.c; sourceTree = ""; }; + 012ADA9400868211C697A10E /* jinclude.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jinclude.h; sourceTree = ""; }; + 012ADA9800868211C697A10E /* jmemmgr.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jmemmgr.c; sourceTree = ""; }; + 012ADA9A00868211C697A10E /* jmemnobs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jmemnobs.c; sourceTree = ""; }; + 012ADA9B00868211C697A10E /* jmemsys.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jmemsys.h; sourceTree = ""; }; + 012ADA9C00868211C697A10E /* jmorecfg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jmorecfg.h; sourceTree = ""; }; + 012ADA9D00868211C697A10E /* jpegint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jpegint.h; sourceTree = ""; }; + 012ADA9E00868211C697A10E /* jpeglib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jpeglib.h; sourceTree = ""; }; + 012ADAA200868211C697A10E /* jutils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = jutils.c; sourceTree = ""; }; + 012ADAA300868211C697A10E /* jversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jversion.h; sourceTree = ""; }; + 012ADAED00868211C697A10E /* cm_load.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cm_load.c; sourceTree = ""; }; + 012ADAEE00868211C697A10E /* cm_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cm_local.h; sourceTree = ""; }; + 012ADAEF00868211C697A10E /* cm_patch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cm_patch.c; sourceTree = ""; }; + 012ADAF000868211C697A10E /* cm_patch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cm_patch.h; sourceTree = ""; }; + 012ADAF100868211C697A10E /* cm_polylib.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cm_polylib.c; sourceTree = ""; }; + 012ADAF200868211C697A10E /* cm_polylib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cm_polylib.h; sourceTree = ""; }; + 012ADAF300868211C697A10E /* cm_public.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cm_public.h; sourceTree = ""; }; + 012ADAF400868211C697A10E /* cm_test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cm_test.c; sourceTree = ""; }; + 012ADAF500868211C697A10E /* cm_trace.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cm_trace.c; sourceTree = ""; }; + 012ADAF600868211C697A10E /* cmd.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cmd.c; sourceTree = ""; }; + 012ADAF700868211C697A10E /* common.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = common.c; sourceTree = ""; }; + 012ADAF800868211C697A10E /* cvar.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cvar.c; sourceTree = ""; }; + 012ADAF900868211C697A10E /* files.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = files.c; sourceTree = ""; }; + 012ADAFA00868211C697A10E /* md4.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = md4.c; sourceTree = ""; }; + 012ADAFB00868211C697A10E /* msg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = msg.c; sourceTree = ""; }; + 012ADAFC00868211C697A10E /* net_chan.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = net_chan.c; sourceTree = ""; }; + 012ADAFD00868211C697A10E /* qcommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = qcommon.h; sourceTree = ""; }; + 012ADAFE00868211C697A10E /* qfiles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = qfiles.h; sourceTree = ""; }; + 012ADAFF00868211C697A10E /* unzip.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = ""; }; + 012ADB0000868211C697A10E /* unzip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = unzip.h; sourceTree = ""; }; + 012ADB0100868211C697A10E /* vm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = vm.c; sourceTree = ""; }; + 012ADB0200868211C697A10E /* vm_interpreted.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = vm_interpreted.c; sourceTree = ""; }; + 012ADB0300868211C697A10E /* vm_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vm_local.h; sourceTree = ""; }; + 012ADB0800868211C697A10E /* qgl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = qgl.h; sourceTree = ""; }; + 012ADB0A00868211C697A10E /* tr_animation.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_animation.c; sourceTree = ""; }; + 012ADB0B00868211C697A10E /* tr_backend.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_backend.c; sourceTree = ""; }; + 012ADB0C00868211C697A10E /* tr_bsp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_bsp.c; sourceTree = ""; }; + 012ADB0D00868211C697A10E /* tr_cmds.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_cmds.c; sourceTree = ""; }; + 012ADB0E00868211C697A10E /* tr_curve.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_curve.c; sourceTree = ""; }; + 012ADB0F00868211C697A10E /* tr_flares.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_flares.c; sourceTree = ""; }; + 012ADB1000868211C697A10E /* tr_font.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_font.c; sourceTree = ""; }; + 012ADB1100868211C697A10E /* tr_image.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_image.c; sourceTree = ""; }; + 012ADB1200868211C697A10E /* tr_init.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_init.c; sourceTree = ""; }; + 012ADB1300868211C697A10E /* tr_light.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_light.c; sourceTree = ""; }; + 012ADB1400868211C697A10E /* tr_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tr_local.h; sourceTree = ""; }; + 012ADB1500868211C697A10E /* tr_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_main.c; sourceTree = ""; }; + 012ADB1600868211C697A10E /* tr_marks.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_marks.c; sourceTree = ""; }; + 012ADB1700868211C697A10E /* tr_mesh.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_mesh.c; sourceTree = ""; }; + 012ADB1800868211C697A10E /* tr_model.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_model.c; sourceTree = ""; }; + 012ADB1900868211C697A10E /* tr_noise.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_noise.c; sourceTree = ""; }; + 012ADB1A00868211C697A10E /* tr_public.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tr_public.h; sourceTree = ""; }; + 012ADB1B00868211C697A10E /* tr_scene.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_scene.c; sourceTree = ""; }; + 012ADB1C00868211C697A10E /* tr_shade.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_shade.c; sourceTree = ""; }; + 012ADB1D00868211C697A10E /* tr_shade_calc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_shade_calc.c; sourceTree = ""; }; + 012ADB1E00868211C697A10E /* tr_shader.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_shader.c; sourceTree = ""; }; + 012ADB1F00868211C697A10E /* tr_shadows.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_shadows.c; sourceTree = ""; }; + 012ADB2000868211C697A10E /* tr_sky.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_sky.c; sourceTree = ""; }; + 012ADB2100868211C697A10E /* tr_surface.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_surface.c; sourceTree = ""; }; + 012ADB2200868211C697A10E /* tr_world.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_world.c; sourceTree = ""; }; + 012ADB2400868211C697A10E /* server.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = server.h; sourceTree = ""; }; + 012ADB2500868211C697A10E /* sv_bot.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_bot.c; sourceTree = ""; }; + 012ADB2600868211C697A10E /* sv_ccmds.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_ccmds.c; sourceTree = ""; }; + 012ADB2700868211C697A10E /* sv_client.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_client.c; sourceTree = ""; }; + 012ADB2800868211C697A10E /* sv_game.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_game.c; sourceTree = ""; }; + 012ADB2900868211C697A10E /* sv_init.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_init.c; sourceTree = ""; }; + 012ADB2A00868211C697A10E /* sv_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_main.c; sourceTree = ""; }; + 012ADB2B00868211C697A10E /* sv_net_chan.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_net_chan.c; sourceTree = ""; }; + 012ADB2D00868211C697A10E /* sv_snapshot.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_snapshot.c; sourceTree = ""; }; + 012ADB2E00868211C697A10E /* sv_world.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sv_world.c; sourceTree = ""; }; + 012ADB3000868211C697A10E /* math_angles.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = math_angles.cpp; sourceTree = ""; }; + 012ADB3100868211C697A10E /* math_angles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = math_angles.h; sourceTree = ""; }; + 012ADB3200868211C697A10E /* math_matrix.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = math_matrix.cpp; sourceTree = ""; }; + 012ADB3300868211C697A10E /* math_matrix.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = math_matrix.h; sourceTree = ""; }; + 012ADB3400868211C697A10E /* math_quaternion.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = math_quaternion.cpp; sourceTree = ""; }; + 012ADB3500868211C697A10E /* math_quaternion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = math_quaternion.h; sourceTree = ""; }; + 012ADB3600868211C697A10E /* math_vector.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = math_vector.cpp; sourceTree = ""; }; + 012ADB3700868211C697A10E /* math_vector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = math_vector.h; sourceTree = ""; }; + 012ADB3800868211C697A10E /* q_parse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = q_parse.cpp; sourceTree = ""; }; + 012ADB3900868211C697A10E /* q_shared.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = q_shared.cpp; sourceTree = ""; }; + 012ADB3A00868211C697A10E /* q_shared.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = q_shared.h; sourceTree = ""; }; + 012ADB3B00868211C697A10E /* splines.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = splines.cpp; sourceTree = ""; }; + 012ADB3C00868211C697A10E /* splines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = splines.h; sourceTree = ""; }; + 012ADB3D00868211C697A10E /* util_list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = util_list.h; sourceTree = ""; }; + 012ADB3E00868211C697A10E /* util_str.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = util_str.cpp; sourceTree = ""; }; + 012ADB3F00868211C697A10E /* util_str.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = util_str.h; sourceTree = ""; }; + 012ADB4100868211C697A10E /* keycodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = keycodes.h; sourceTree = ""; }; + 012ADB4200868211C697A10E /* ui_atoms.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ui_atoms.c; sourceTree = ""; }; + 012ADB4300868211C697A10E /* ui_gameinfo.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ui_gameinfo.c; sourceTree = ""; }; + 012ADB4400868211C697A10E /* ui_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ui_local.h; sourceTree = ""; }; + 012ADB4500868211C697A10E /* ui_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ui_main.c; sourceTree = ""; }; + 012ADB4600868211C697A10E /* ui_players.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ui_players.c; sourceTree = ""; }; + 012ADB4700868211C697A10E /* ui_public.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ui_public.h; sourceTree = ""; }; + 012ADB4800868211C697A10E /* ui_shared.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ui_shared.c; sourceTree = ""; }; + 012ADB4900868211C697A10E /* ui_shared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ui_shared.h; sourceTree = ""; }; + 012ADB4A00868211C697A10E /* ui_syscalls.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ui_syscalls.c; sourceTree = ""; }; + 012ADB4B00868211C697A10E /* ui_util.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ui_util.c; sourceTree = ""; }; + 012ADB4D00868211C697A10E /* linux_common.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = linux_common.c; sourceTree = ""; }; + 012ADB4E00868211C697A10E /* linux_glimp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = linux_glimp.c; sourceTree = ""; }; + 012ADB4F00868211C697A10E /* linux_joystick.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = linux_joystick.c; sourceTree = ""; }; + 012ADB5000868211C697A10E /* linux_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = linux_local.h; sourceTree = ""; }; + 012ADB5100868211C697A10E /* linux_qgl.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = linux_qgl.c; sourceTree = ""; }; + 012ADB5200868211C697A10E /* linux_snd.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = linux_snd.c; sourceTree = ""; }; + 012ADB5300868211C697A10E /* qasm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = qasm.h; sourceTree = ""; }; + 012ADB5500868211C697A10E /* unix_glw.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = unix_glw.h; sourceTree = ""; }; + 012ADB5600868211C697A10E /* unix_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = unix_main.c; sourceTree = ""; }; + 012ADB5700868211C697A10E /* unix_net.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = unix_net.c; sourceTree = ""; }; + 012ADB5800868211C697A10E /* unix_shared.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = unix_shared.c; sourceTree = ""; }; + 015ECC0C00894EC0C697A10E /* macosx_display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macosx_display.h; sourceTree = ""; }; + 015ECC0D00894EC0C697A10E /* macosx_display.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_display.m; sourceTree = ""; }; + 016B4A3B00ACCF9FC697A10E /* macosx_glsmp_mutex.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_glsmp_mutex.m; sourceTree = ""; }; + 016B4A3C00ACCF9FC697A10E /* macosx_glsmp_null.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_glsmp_null.m; sourceTree = ""; }; + 016B4A3D00ACCF9FC697A10E /* macosx_glsmp_ports.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_glsmp_ports.m; sourceTree = ""; }; + 016EAE0200B4BDD1C697A10E /* ui.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = ui.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 016F1B6300ACDA9BC697A10E /* huffman.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = huffman.c; sourceTree = ""; }; + 043627A400868916C697A10E /* dlfcn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dlfcn.h; sourceTree = ""; }; + 043627A500868916C697A10E /* dlopen.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dlopen.c; sourceTree = ""; }; + 043627A600868916C697A10E /* macosx_glimp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macosx_glimp.h; sourceTree = ""; }; + 043627A700868916C697A10E /* macosx_glimp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_glimp.m; sourceTree = ""; }; + 043627A800868916C697A10E /* macosx_input.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_input.m; sourceTree = ""; }; + 043627A900868916C697A10E /* macosx_local.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macosx_local.h; sourceTree = ""; }; + 043627AB00868916C697A10E /* macosx_sndcore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_sndcore.m; sourceTree = ""; }; + 043627AD00868916C697A10E /* macosx_sys.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_sys.m; sourceTree = ""; }; + 043627AE00868916C697A10E /* macosx_timers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = macosx_timers.h; sourceTree = ""; }; + 043627AF00868916C697A10E /* macosx_timers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = macosx_timers.m; sourceTree = ""; }; + 043627B000868916C697A10E /* Q3Controller.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Q3Controller.h; sourceTree = ""; }; + 043627B100868916C697A10E /* Q3Controller.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Q3Controller.m; sourceTree = ""; }; + 043627B200868916C697A10E /* Quake3.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; path = Quake3.nib; sourceTree = ""; }; + 043627B300868916C697A10E /* Quake3.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Quake3.icns; sourceTree = ""; }; + 043627B400868916C697A10E /* banner.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = banner.jpg; sourceTree = ""; }; + 043627B500868916C697A10E /* Performance.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Performance.rtf; sourceTree = ""; }; + 043627B600868916C697A10E /* BuildRelease */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = BuildRelease; sourceTree = ""; }; + 043627B700868916C697A10E /* RecordDemo.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = RecordDemo.zsh; sourceTree = ""; }; + 043627B800868916C697A10E /* timedemo.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = timedemo.zsh; sourceTree = ""; }; + 0654BA58FE8ECEE0C697A12F /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = /System/Library/Frameworks/OpenGL.framework; sourceTree = ""; }; + 0654BA59FE8ECEE0C697A12F /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; + 0654BA5AFE8ECEE0C697A12F /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; + 0654BA5BFE8ECEE0C697A12F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 07F3F507FFE98E8EC697A10E /* qagame.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = qagame.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 07F3F508FFE98E8EC697A10E /* cgame.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = cgame.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 09143A93FF39F3EF11CA2562 /* WolfensteinMP.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; path = WolfensteinMP.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F50905BE0157C20601F6286D /* WolfDedicated */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; path = WolfDedicated; sourceTree = BUILT_PRODUCTS_DIR; }; + F54C3532015659F101F6286D /* Wolf.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Wolf.icns; sourceTree = ""; }; + F55DF57A01513A8101F6286D /* g_tramcar.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_tramcar.c; sourceTree = ""; }; + F5673BEE015686CC01F628AA /* cg_newDraw.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_newDraw.c; sourceTree = ""; }; + F5ABC7B5013B051201F6286D /* be_aas_routetable.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = be_aas_routetable.c; sourceTree = ""; }; + F5ABC7B6013B051201F6286D /* be_aas_routetable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = be_aas_routetable.h; sourceTree = ""; }; + F5ABC7B9013B054201F6286D /* tr_cmesh.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = tr_cmesh.c; sourceTree = ""; }; + F5ABC7C7013B0EAB01F6286D /* ai_cast_characters.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_characters.c; sourceTree = ""; }; + F5ABC7C8013B0EAB01F6286D /* ai_cast_debug.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_debug.c; sourceTree = ""; }; + F5ABC7C9013B0EAB01F6286D /* ai_cast_events.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_events.c; sourceTree = ""; }; + F5ABC7CA013B0EAB01F6286D /* ai_cast_fight.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_fight.c; sourceTree = ""; }; + F5ABC7CB013B0EAB01F6286D /* ai_cast_fight.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ai_cast_fight.h; sourceTree = ""; }; + F5ABC7CC013B0EAB01F6286D /* ai_cast_func_attack.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_func_attack.c; sourceTree = ""; }; + F5ABC7CD013B0EAB01F6286D /* ai_cast_func_boss1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_func_boss1.c; sourceTree = ""; }; + F5ABC7CE013B0EAB01F6286D /* ai_cast_funcs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_funcs.c; sourceTree = ""; }; + F5ABC7CF013B0EAB01F6286D /* ai_cast_global.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ai_cast_global.h; sourceTree = ""; }; + F5ABC7D0013B0EAB01F6286D /* ai_cast_script_actions.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_script_actions.c; sourceTree = ""; }; + F5ABC7D1013B0EAB01F6286D /* ai_cast_script_ents.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_script_ents.c; sourceTree = ""; }; + F5ABC7D2013B0EAB01F6286D /* ai_cast_script.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_script.c; sourceTree = ""; }; + F5ABC7D3013B0EAB01F6286D /* ai_cast_sight.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_sight.c; sourceTree = ""; }; + F5ABC7D4013B0EAB01F6286D /* ai_cast_think.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast_think.c; sourceTree = ""; }; + F5ABC7D5013B0EAB01F6286D /* ai_cast.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ai_cast.c; sourceTree = ""; }; + F5ABC7D6013B0EAB01F6286D /* ai_cast.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ai_cast.h; sourceTree = ""; }; + F5ABC7D7013B0EAB01F6286D /* bg_animation.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bg_animation.c; sourceTree = ""; }; + F5ABC7D8013B0EAB01F6286D /* g_alarm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_alarm.c; sourceTree = ""; }; + F5ABC7D9013B0EAB01F6286D /* g_func_decs.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = g_func_decs.h; sourceTree = ""; }; + F5ABC7DA013B0EAB01F6286D /* g_funcs.h */ = {isa = PBXFileReference; lastKnownFileType = file; path = g_funcs.h; sourceTree = ""; }; + F5ABC7DB013B0EAB01F6286D /* g_props.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_props.c; sourceTree = ""; }; + F5ABC7DC013B0EAB01F6286D /* g_save.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_save.c; sourceTree = ""; }; + F5ABC7DD013B0EAB01F6286D /* g_script_actions.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_script_actions.c; sourceTree = ""; }; + F5ABC7DE013B0EAB01F6286D /* g_script.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = g_script.c; sourceTree = ""; }; + F5ABC7FB013B120B01F6286D /* ai_chat.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ai_chat.c; path = ../botai/ai_chat.c; sourceTree = SOURCE_ROOT; }; + F5ABC7FD013B120B01F6286D /* ai_cmd.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ai_cmd.c; path = ../botai/ai_cmd.c; sourceTree = SOURCE_ROOT; }; + F5ABC7FF013B120B01F6286D /* ai_dmnet.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ai_dmnet.c; path = ../botai/ai_dmnet.c; sourceTree = SOURCE_ROOT; }; + F5ABC801013B120B01F6286D /* ai_dmq3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ai_dmq3.c; path = ../botai/ai_dmq3.c; sourceTree = SOURCE_ROOT; }; + F5ABC802013B120B01F6286D /* ai_dmq3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ai_dmq3.h; path = ../botai/ai_dmq3.h; sourceTree = SOURCE_ROOT; }; + F5ABC803013B120B01F6286D /* ai_main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ai_main.c; path = ../botai/ai_main.c; sourceTree = SOURCE_ROOT; }; + F5ABC804013B120B01F6286D /* ai_main.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ai_main.h; path = ../botai/ai_main.h; sourceTree = SOURCE_ROOT; }; + F5ABC805013B120B01F6286D /* ai_team.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ai_team.c; path = ../botai/ai_team.c; sourceTree = SOURCE_ROOT; }; + F5ABC806013B120B01F6286D /* ai_team.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ai_team.h; path = ../botai/ai_team.h; sourceTree = SOURCE_ROOT; }; + F5ABC81D013B12E701F6286D /* cg_flamethrower.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_flamethrower.c; sourceTree = ""; }; + F5ABC81E013B12E701F6286D /* cg_particles.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_particles.c; sourceTree = ""; }; + F5ABC81F013B12E701F6286D /* cg_sound.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_sound.c; sourceTree = ""; }; + F5ABC820013B12E701F6286D /* cg_trails.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cg_trails.c; sourceTree = ""; }; + F5DBFB970136AC6901F6286D /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00F5ED77FEBA95B7C697A12F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 00F5ED94FEBA9615C697A12F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 016EAE1200B4BDD1C697A10E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0654BA6BFE8ECEE0C697A12F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0654BA6CFE8ECEE0C697A12F /* OpenGL.framework in Frameworks */, + 0654BA6EFE8ECEE0C697A12F /* AppKit.framework in Frameworks */, + 0654BA6FFE8ECEE0C697A12F /* Foundation.framework in Frameworks */, + 00E9D91DFEDB5295C697A12F /* CoreAudio.framework in Frameworks */, + F5DBFB980136ACB501F6286D /* IOKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F50905C20157C20601F6286D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F5548CD80157D58D01F6286D /* IOKit.framework in Frameworks */, + F5548CD90157D58D01F6286D /* OpenGL.framework in Frameworks */, + F5548CDA0157D58D01F6286D /* CoreAudio.framework in Frameworks */, + F5548CDB0157D58D01F6286D /* AppKit.framework in Frameworks */, + F5548CDC0157D58D01F6286D /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 012AD90700868211C697A10E /* botlib */ = { + isa = PBXGroup; + children = ( + 012AD90800868211C697A10E /* aasfile.h */, + 012AD90900868211C697A10E /* be_aas_bsp.h */, + 012AD90A00868211C697A10E /* be_aas_bspq3.c */, + 012AD90B00868211C697A10E /* be_aas_cluster.c */, + 012AD90C00868211C697A10E /* be_aas_cluster.h */, + 012AD90D00868211C697A10E /* be_aas_debug.c */, + 012AD90E00868211C697A10E /* be_aas_debug.h */, + 012AD90F00868211C697A10E /* be_aas_def.h */, + 012AD91000868211C697A10E /* be_aas_entity.c */, + 012AD91100868211C697A10E /* be_aas_entity.h */, + 012AD91200868211C697A10E /* be_aas_file.c */, + 012AD91300868211C697A10E /* be_aas_file.h */, + 012AD91400868211C697A10E /* be_aas_funcs.h */, + 012AD91500868211C697A10E /* be_aas_main.c */, + 012AD91600868211C697A10E /* be_aas_main.h */, + 012AD91700868211C697A10E /* be_aas_move.c */, + 012AD91800868211C697A10E /* be_aas_move.h */, + 012AD91900868211C697A10E /* be_aas_optimize.c */, + 012AD91A00868211C697A10E /* be_aas_optimize.h */, + 012AD91B00868211C697A10E /* be_aas_reach.c */, + 012AD91C00868211C697A10E /* be_aas_reach.h */, + 012AD91D00868211C697A10E /* be_aas_route.c */, + 012AD91E00868211C697A10E /* be_aas_route.h */, + 012AD91F00868211C697A10E /* be_aas_routealt.c */, + 012AD92000868211C697A10E /* be_aas_routealt.h */, + 012AD92100868211C697A10E /* be_aas_sample.c */, + 012AD92200868211C697A10E /* be_aas_sample.h */, + 012AD92300868211C697A10E /* be_ai_char.c */, + 012AD92400868211C697A10E /* be_ai_chat.c */, + 012AD92500868211C697A10E /* be_ai_gen.c */, + 012AD92600868211C697A10E /* be_ai_goal.c */, + 012AD92700868211C697A10E /* be_ai_move.c */, + 012AD92800868211C697A10E /* be_ai_weap.c */, + 012AD92900868211C697A10E /* be_ai_weight.c */, + 012AD92A00868211C697A10E /* be_ai_weight.h */, + 012AD92B00868211C697A10E /* be_ea.c */, + 012AD92C00868211C697A10E /* be_interface.c */, + 012AD92D00868211C697A10E /* be_interface.h */, + 012AD92E00868211C697A10E /* l_crc.c */, + 012AD92F00868211C697A10E /* l_crc.h */, + 012AD93000868211C697A10E /* l_libvar.c */, + 012AD93100868211C697A10E /* l_libvar.h */, + 012AD93200868211C697A10E /* l_log.c */, + 012AD93300868211C697A10E /* l_log.h */, + 012AD93400868211C697A10E /* l_memory.c */, + 012AD93500868211C697A10E /* l_memory.h */, + 012AD93600868211C697A10E /* l_precomp.c */, + 012AD93700868211C697A10E /* l_precomp.h */, + 012AD93800868211C697A10E /* l_script.c */, + 012AD93900868211C697A10E /* l_script.h */, + 012AD93A00868211C697A10E /* l_struct.c */, + 012AD93B00868211C697A10E /* l_struct.h */, + 012AD93C00868211C697A10E /* l_utils.h */, + F5ABC7B5013B051201F6286D /* be_aas_routetable.c */, + F5ABC7B6013B051201F6286D /* be_aas_routetable.h */, + ); + path = botlib; + sourceTree = ""; + }; + 012AD93D00868211C697A10E /* bspc */ = { + isa = PBXGroup; + children = ( + 012AD93E00868211C697A10E /* _files.c */, + 012AD93F00868211C697A10E /* aas_areamerging.c */, + 012AD94000868211C697A10E /* aas_areamerging.h */, + 012AD94100868211C697A10E /* aas_cfg.c */, + 012AD94200868211C697A10E /* aas_cfg.h */, + 012AD94300868211C697A10E /* aas_create.c */, + 012AD94400868211C697A10E /* aas_create.h */, + 012AD94500868211C697A10E /* aas_edgemelting.c */, + 012AD94600868211C697A10E /* aas_edgemelting.h */, + 012AD94700868211C697A10E /* aas_facemerging.c */, + 012AD94800868211C697A10E /* aas_facemerging.h */, + 012AD94900868211C697A10E /* aas_file.c */, + 012AD94A00868211C697A10E /* aas_file.h */, + 012AD94B00868211C697A10E /* aas_gsubdiv.c */, + 012AD94C00868211C697A10E /* aas_gsubdiv.h */, + 012AD94D00868211C697A10E /* aas_map.c */, + 012AD94E00868211C697A10E /* aas_map.h */, + 012AD94F00868211C697A10E /* aas_prunenodes.c */, + 012AD95000868211C697A10E /* aas_prunenodes.h */, + 012AD95100868211C697A10E /* aas_store.c */, + 012AD95200868211C697A10E /* aas_store.h */, + 012AD95400868211C697A10E /* be_aas_bspc.c */, + 012AD95500868211C697A10E /* be_aas_bspc.h */, + 012AD95600868211C697A10E /* brushbsp.c */, + 012AD95700868211C697A10E /* bspc.c */, + 012AD95900868211C697A10E /* csg.c */, + 012AD95A00868211C697A10E /* faces.c */, + 012AD95C00868211C697A10E /* glfile.c */, + 012AD95D00868211C697A10E /* l_bsp_ent.c */, + 012AD95E00868211C697A10E /* l_bsp_ent.h */, + 012AD95F00868211C697A10E /* l_bsp_hl.c */, + 012AD96000868211C697A10E /* l_bsp_hl.h */, + 012AD96100868211C697A10E /* l_bsp_q1.c */, + 012AD96200868211C697A10E /* l_bsp_q1.h */, + 012AD96300868211C697A10E /* l_bsp_q2.c */, + 012AD96400868211C697A10E /* l_bsp_q2.h */, + 012AD96500868211C697A10E /* l_bsp_q3.c */, + 012AD96600868211C697A10E /* l_bsp_q3.h */, + 012AD96700868211C697A10E /* l_bsp_sin.c */, + 012AD96800868211C697A10E /* l_bsp_sin.h */, + 012AD96900868211C697A10E /* l_cmd.c */, + 012AD96A00868211C697A10E /* l_cmd.h */, + 012AD96B00868211C697A10E /* l_log.c */, + 012AD96C00868211C697A10E /* l_log.h */, + 012AD96D00868211C697A10E /* l_math.c */, + 012AD96E00868211C697A10E /* l_math.h */, + 012AD96F00868211C697A10E /* l_mem.c */, + 012AD97000868211C697A10E /* l_mem.h */, + 012AD97100868211C697A10E /* l_poly.c */, + 012AD97200868211C697A10E /* l_poly.h */, + 012AD97300868211C697A10E /* l_qfiles.c */, + 012AD97400868211C697A10E /* l_qfiles.h */, + 012AD97500868211C697A10E /* l_threads.c */, + 012AD97600868211C697A10E /* l_threads.h */, + 012AD97700868211C697A10E /* l_utils.c */, + 012AD97800868211C697A10E /* l_utils.h */, + 012AD97900868211C697A10E /* leakfile.c */, + 012AD97A00868211C697A10E /* map.c */, + 012AD97B00868211C697A10E /* map_hl.c */, + 012AD97C00868211C697A10E /* map_q1.c */, + 012AD97D00868211C697A10E /* map_q2.c */, + 012AD97E00868211C697A10E /* map_q3.c */, + 012AD97F00868211C697A10E /* map_sin.c */, + 012AD98000868211C697A10E /* nodraw.c */, + 012AD98100868211C697A10E /* portals.c */, + 012AD98200868211C697A10E /* prtfile.c */, + 012AD98300868211C697A10E /* q2files.h */, + 012AD98400868211C697A10E /* q3files.h */, + 012AD98500868211C697A10E /* qbsp.h */, + 012AD98600868211C697A10E /* qfiles.h */, + 012AD98700868211C697A10E /* sinfiles.h */, + 012AD98800868211C697A10E /* tetrahedron.c */, + 012AD98900868211C697A10E /* tetrahedron.h */, + 012AD98A00868211C697A10E /* textures.c */, + 012AD98B00868211C697A10E /* tree.c */, + 012AD98C00868211C697A10E /* writebsp.c */, + ); + path = bspc; + sourceTree = ""; + }; + 012AD98D00868211C697A10E /* cgame */ = { + isa = PBXGroup; + children = ( + 012AD98E00868211C697A10E /* cg_consolecmds.c */, + 012AD98F00868211C697A10E /* cg_draw.c */, + 012AD99000868211C697A10E /* cg_drawtools.c */, + 012AD99100868211C697A10E /* cg_effects.c */, + 012AD99200868211C697A10E /* cg_ents.c */, + 012AD99300868211C697A10E /* cg_event.c */, + 012AD99400868211C697A10E /* cg_info.c */, + 012AD99500868211C697A10E /* cg_local.h */, + 012AD99600868211C697A10E /* cg_localents.c */, + 012AD99700868211C697A10E /* cg_main.c */, + 012AD99800868211C697A10E /* cg_marks.c */, + 012AD99A00868211C697A10E /* cg_players.c */, + 012AD99B00868211C697A10E /* cg_playerstate.c */, + 012AD99C00868211C697A10E /* cg_predict.c */, + 012AD99D00868211C697A10E /* cg_public.h */, + 012AD99E00868211C697A10E /* cg_scoreboard.c */, + 012AD99F00868211C697A10E /* cg_servercmds.c */, + 012AD9A000868211C697A10E /* cg_snapshot.c */, + 012AD9A100868211C697A10E /* cg_syscalls.c */, + 012AD9A200868211C697A10E /* cg_view.c */, + 012AD9A300868211C697A10E /* cg_weapons.c */, + 012AD9A400868211C697A10E /* tr_types.h */, + F5ABC81D013B12E701F6286D /* cg_flamethrower.c */, + F5ABC81E013B12E701F6286D /* cg_particles.c */, + F5ABC81F013B12E701F6286D /* cg_sound.c */, + F5ABC820013B12E701F6286D /* cg_trails.c */, + F5673BEE015686CC01F628AA /* cg_newDraw.c */, + ); + path = cgame; + sourceTree = ""; + }; + 012AD9A500868211C697A10E /* client */ = { + isa = PBXGroup; + children = ( + 012AD9A600868211C697A10E /* cl_cgame.c */, + 012AD9A700868211C697A10E /* cl_cin.c */, + 012AD9A800868211C697A10E /* cl_console.c */, + 012AD9A900868211C697A10E /* cl_input.c */, + 012AD9AA00868211C697A10E /* cl_keys.c */, + 012AD9AB00868211C697A10E /* cl_main.c */, + 012AD9AC00868211C697A10E /* cl_net_chan.c */, + 012AD9AD00868211C697A10E /* cl_parse.c */, + 012AD9AE00868211C697A10E /* cl_scrn.c */, + 012AD9AF00868211C697A10E /* cl_ui.c */, + 012AD9B000868211C697A10E /* client.h */, + 012AD9B100868211C697A10E /* keys.h */, + 012AD9B200868211C697A10E /* snd_adpcm.c */, + 012AD9B300868211C697A10E /* snd_dma.c */, + 012AD9B400868211C697A10E /* snd_local.h */, + 012AD9B500868211C697A10E /* snd_mem.c */, + 012AD9B600868211C697A10E /* snd_mix.c */, + 012AD9B700868211C697A10E /* snd_public.h */, + 012AD9B800868211C697A10E /* snd_wavelet.c */, + ); + path = client; + sourceTree = ""; + }; + 012AD9B900868211C697A10E /* ft2 */ = { + isa = PBXGroup; + children = ( + 012AD9BA00868211C697A10E /* ahangles.c */, + 012AD9BB00868211C697A10E /* ahangles.h */, + 012AD9BC00868211C697A10E /* ahglobal.c */, + 012AD9BD00868211C697A10E /* ahglobal.h */, + 012AD9BE00868211C697A10E /* ahglyph.c */, + 012AD9BF00868211C697A10E /* ahglyph.h */, + 012AD9C000868211C697A10E /* ahhint.c */, + 012AD9C100868211C697A10E /* ahhint.h */, + 012AD9C200868211C697A10E /* ahloader.h */, + 012AD9C300868211C697A10E /* ahmodule.c */, + 012AD9C400868211C697A10E /* ahmodule.h */, + 012AD9C500868211C697A10E /* ahoptim.c */, + 012AD9C600868211C697A10E /* ahoptim.h */, + 012AD9C700868211C697A10E /* ahtypes.h */, + 012AD9C800868211C697A10E /* autohint.c */, + 012AD9C900868211C697A10E /* autohint.h */, + 012AD9CA00868211C697A10E /* freetype.h */, + 012AD9CB00868211C697A10E /* ftbase.c */, + 012AD9CC00868211C697A10E /* ftbbox.c */, + 012AD9CD00868211C697A10E /* ftbbox.h */, + 012AD9CE00868211C697A10E /* ftcalc.c */, + 012AD9CF00868211C697A10E /* ftcalc.h */, + 012AD9D000868211C697A10E /* ftconfig.h */, + 012AD9D100868211C697A10E /* ftdebug.c */, + 012AD9D200868211C697A10E /* ftdebug.h */, + 012AD9D300868211C697A10E /* ftdriver.h */, + 012AD9D400868211C697A10E /* fterrors.h */, + 012AD9D500868211C697A10E /* ftextend.c */, + 012AD9D600868211C697A10E /* ftextend.h */, + 012AD9D700868211C697A10E /* ftglyph.c */, + 012AD9D800868211C697A10E /* ftglyph.h */, + 012AD9D900868211C697A10E /* ftgrays.c */, + 012AD9DA00868211C697A10E /* ftgrays.h */, + 012AD9DB00868211C697A10E /* ftimage.h */, + 012AD9DC00868211C697A10E /* ftinit.c */, + 012AD9DD00868211C697A10E /* ftlist.c */, + 012AD9DE00868211C697A10E /* ftlist.h */, + 012AD9DF00868211C697A10E /* ftmemory.h */, + 012AD9E000868211C697A10E /* ftmm.c */, + 012AD9E100868211C697A10E /* ftmm.h */, + 012AD9E200868211C697A10E /* ftmodule.h */, + 012AD9E300868211C697A10E /* ftnames.c */, + 012AD9E400868211C697A10E /* ftnames.h */, + 012AD9E500868211C697A10E /* ftobjs.c */, + 012AD9E600868211C697A10E /* ftobjs.h */, + 012AD9E700868211C697A10E /* ftoption.h */, + 012AD9E800868211C697A10E /* ftoutln.c */, + 012AD9E900868211C697A10E /* ftoutln.h */, + 012AD9EA00868211C697A10E /* ftraster.c */, + 012AD9EB00868211C697A10E /* ftraster.h */, + 012AD9EC00868211C697A10E /* ftrend1.c */, + 012AD9ED00868211C697A10E /* ftrend1.h */, + 012AD9EE00868211C697A10E /* ftrender.h */, + 012AD9EF00868211C697A10E /* ftsmooth.c */, + 012AD9F000868211C697A10E /* ftsmooth.h */, + 012AD9F100868211C697A10E /* ftstream.c */, + 012AD9F200868211C697A10E /* ftstream.h */, + 012AD9F300868211C697A10E /* ftsystem.c */, + 012AD9F400868211C697A10E /* ftsystem.h */, + 012AD9F500868211C697A10E /* fttypes.h */, + 012AD9F600868211C697A10E /* keys.h */, + 012AD9F700868211C697A10E /* psdriver.c */, + 012AD9F800868211C697A10E /* psdriver.h */, + 012AD9F900868211C697A10E /* psnames.c */, + 012AD9FA00868211C697A10E /* psnames.h */, + 012AD9FB00868211C697A10E /* pstables.h */, + 012AD9FC00868211C697A10E /* raster1.c */, + 012AD9FD00868211C697A10E /* sfconfig.h */, + 012AD9FE00868211C697A10E /* sfdriver.c */, + 012AD9FF00868211C697A10E /* sfdriver.h */, + 012ADA0000868211C697A10E /* sfnt.c */, + 012ADA0100868211C697A10E /* sfnt.h */, + 012ADA0200868211C697A10E /* sfobjs.c */, + 012ADA0300868211C697A10E /* sfobjs.h */, + 012ADA0400868211C697A10E /* smooth.c */, + 012ADA0500868211C697A10E /* t1errors.h */, + 012ADA0600868211C697A10E /* t1tables.h */, + 012ADA0700868211C697A10E /* t1types.h */, + 012ADA0800868211C697A10E /* t2errors.h */, + 012ADA0900868211C697A10E /* t2types.h */, + 012ADA0A00868211C697A10E /* truetype.c */, + 012ADA0B00868211C697A10E /* ttcmap.c */, + 012ADA0C00868211C697A10E /* ttcmap.h */, + 012ADA0D00868211C697A10E /* ttconfig.h */, + 012ADA0E00868211C697A10E /* ttdriver.c */, + 012ADA0F00868211C697A10E /* ttdriver.h */, + 012ADA1000868211C697A10E /* tterrors.h */, + 012ADA1100868211C697A10E /* ttgload.c */, + 012ADA1200868211C697A10E /* ttgload.h */, + 012ADA1300868211C697A10E /* ttinterp.c */, + 012ADA1400868211C697A10E /* ttinterp.h */, + 012ADA1500868211C697A10E /* ttload.c */, + 012ADA1600868211C697A10E /* ttload.h */, + 012ADA1700868211C697A10E /* ttnamedid.h */, + 012ADA1800868211C697A10E /* ttnameid.h */, + 012ADA1900868211C697A10E /* ttobjs.c */, + 012ADA1A00868211C697A10E /* ttobjs.h */, + 012ADA1B00868211C697A10E /* ttpload.c */, + 012ADA1C00868211C697A10E /* ttpload.h */, + 012ADA1D00868211C697A10E /* ttpost.c */, + 012ADA1E00868211C697A10E /* ttpost.h */, + 012ADA1F00868211C697A10E /* ttsbit.c */, + 012ADA2000868211C697A10E /* ttsbit.h */, + 012ADA2100868211C697A10E /* tttables.h */, + 012ADA2200868211C697A10E /* tttags.h */, + 012ADA2300868211C697A10E /* tttypes.h */, + ); + path = ft2; + sourceTree = ""; + }; + 012ADA2400868211C697A10E /* game */ = { + isa = PBXGroup; + children = ( + 012ADA3300868211C697A10E /* be_aas.h */, + 012ADA3400868211C697A10E /* be_ai_char.h */, + 012ADA3500868211C697A10E /* be_ai_chat.h */, + 012ADA3600868211C697A10E /* be_ai_gen.h */, + 012ADA3700868211C697A10E /* be_ai_goal.h */, + 012ADA3800868211C697A10E /* be_ai_move.h */, + 012ADA3900868211C697A10E /* be_ai_weap.h */, + 012ADA3A00868211C697A10E /* be_ea.h */, + 012ADA3B00868211C697A10E /* bg_lib.c */, + 012ADA3D00868211C697A10E /* bg_local.h */, + 012ADA3E00868211C697A10E /* bg_misc.c */, + 012ADA3F00868211C697A10E /* bg_pmove.c */, + 012ADA4000868211C697A10E /* bg_public.h */, + 012ADA4100868211C697A10E /* bg_slidemove.c */, + 012ADA4200868211C697A10E /* botlib.h */, + 012ADA4400868211C697A10E /* g_active.c */, + 012ADA4600868211C697A10E /* g_bot.c */, + 012ADA4700868211C697A10E /* g_client.c */, + 012ADA4800868211C697A10E /* g_cmds.c */, + 012ADA4900868211C697A10E /* g_combat.c */, + 012ADA4A00868211C697A10E /* g_items.c */, + 012ADA4B00868211C697A10E /* g_local.h */, + 012ADA4C00868211C697A10E /* g_main.c */, + 012ADA4D00868211C697A10E /* g_mem.c */, + 012ADA4E00868211C697A10E /* g_misc.c */, + 012ADA4F00868211C697A10E /* g_missile.c */, + 012ADA5000868211C697A10E /* g_mover.c */, + 012ADA5100868211C697A10E /* g_public.h */, + 012ADA5400868211C697A10E /* g_session.c */, + 012ADA5500868211C697A10E /* g_spawn.c */, + 012ADA5600868211C697A10E /* g_svcmds.c */, + 012ADA5700868211C697A10E /* g_syscalls.c */, + 012ADA5800868211C697A10E /* g_target.c */, + 012ADA5900868211C697A10E /* g_team.c */, + 012ADA5A00868211C697A10E /* g_team.h */, + 012ADA5B00868211C697A10E /* g_trigger.c */, + 012ADA5C00868211C697A10E /* g_utils.c */, + 012ADA5D00868211C697A10E /* g_weapon.c */, + 012ADA6000868211C697A10E /* q_math.c */, + 012ADA6100868211C697A10E /* q_shared.c */, + 012ADA6200868211C697A10E /* q_shared.h */, + 012ADA6300868211C697A10E /* surfaceflags.h */, + F5ABC7C7013B0EAB01F6286D /* ai_cast_characters.c */, + F5ABC7C8013B0EAB01F6286D /* ai_cast_debug.c */, + F5ABC7C9013B0EAB01F6286D /* ai_cast_events.c */, + F5ABC7CA013B0EAB01F6286D /* ai_cast_fight.c */, + F5ABC7CB013B0EAB01F6286D /* ai_cast_fight.h */, + F5ABC7CC013B0EAB01F6286D /* ai_cast_func_attack.c */, + F5ABC7CD013B0EAB01F6286D /* ai_cast_func_boss1.c */, + F5ABC7CE013B0EAB01F6286D /* ai_cast_funcs.c */, + F5ABC7CF013B0EAB01F6286D /* ai_cast_global.h */, + F5ABC7D0013B0EAB01F6286D /* ai_cast_script_actions.c */, + F5ABC7D1013B0EAB01F6286D /* ai_cast_script_ents.c */, + F5ABC7D2013B0EAB01F6286D /* ai_cast_script.c */, + F5ABC7D3013B0EAB01F6286D /* ai_cast_sight.c */, + F5ABC7D4013B0EAB01F6286D /* ai_cast_think.c */, + F5ABC7D5013B0EAB01F6286D /* ai_cast.c */, + F5ABC7D6013B0EAB01F6286D /* ai_cast.h */, + F5ABC7D7013B0EAB01F6286D /* bg_animation.c */, + F5ABC7D8013B0EAB01F6286D /* g_alarm.c */, + F5ABC7D9013B0EAB01F6286D /* g_func_decs.h */, + F5ABC7DA013B0EAB01F6286D /* g_funcs.h */, + F5ABC7DB013B0EAB01F6286D /* g_props.c */, + F5ABC7DC013B0EAB01F6286D /* g_save.c */, + F5ABC7DD013B0EAB01F6286D /* g_script_actions.c */, + F5ABC7DE013B0EAB01F6286D /* g_script.c */, + F5ABC7FB013B120B01F6286D /* ai_chat.c */, + F5ABC7FD013B120B01F6286D /* ai_cmd.c */, + F5ABC7FF013B120B01F6286D /* ai_dmnet.c */, + F5ABC801013B120B01F6286D /* ai_dmq3.c */, + F5ABC802013B120B01F6286D /* ai_dmq3.h */, + F5ABC803013B120B01F6286D /* ai_main.c */, + F5ABC804013B120B01F6286D /* ai_main.h */, + F5ABC805013B120B01F6286D /* ai_team.c */, + F5ABC806013B120B01F6286D /* ai_team.h */, + F55DF57A01513A8101F6286D /* g_tramcar.c */, + ); + path = game; + sourceTree = ""; + }; + 012ADA6500868211C697A10E /* jpeg-6 */ = { + isa = PBXGroup; + children = ( + 012ADA6600868211C697A10E /* jcapimin.c */, + 012ADA6800868211C697A10E /* jccoefct.c */, + 012ADA6900868211C697A10E /* jccolor.c */, + 012ADA6A00868211C697A10E /* jcdctmgr.c */, + 012ADA6B00868211C697A10E /* jchuff.c */, + 012ADA6C00868211C697A10E /* jchuff.h */, + 012ADA6D00868211C697A10E /* jcinit.c */, + 012ADA6E00868211C697A10E /* jcmainct.c */, + 012ADA6F00868211C697A10E /* jcmarker.c */, + 012ADA7000868211C697A10E /* jcmaster.c */, + 012ADA7100868211C697A10E /* jcomapi.c */, + 012ADA7200868211C697A10E /* jconfig.h */, + 012ADA7300868211C697A10E /* jcparam.c */, + 012ADA7400868211C697A10E /* jcphuff.c */, + 012ADA7500868211C697A10E /* jcprepct.c */, + 012ADA7600868211C697A10E /* jcsample.c */, + 012ADA7700868211C697A10E /* jctrans.c */, + 012ADA7800868211C697A10E /* jdapimin.c */, + 012ADA7900868211C697A10E /* jdapistd.c */, + 012ADA7A00868211C697A10E /* jdatadst.c */, + 012ADA7B00868211C697A10E /* jdatasrc.c */, + 012ADA7C00868211C697A10E /* jdcoefct.c */, + 012ADA7D00868211C697A10E /* jdcolor.c */, + 012ADA7E00868211C697A10E /* jdct.h */, + 012ADA7F00868211C697A10E /* jddctmgr.c */, + 012ADA8000868211C697A10E /* jdhuff.c */, + 012ADA8100868211C697A10E /* jdhuff.h */, + 012ADA8200868211C697A10E /* jdinput.c */, + 012ADA8300868211C697A10E /* jdmainct.c */, + 012ADA8400868211C697A10E /* jdmarker.c */, + 012ADA8500868211C697A10E /* jdmaster.c */, + 012ADA8800868211C697A10E /* jdpostct.c */, + 012ADA8900868211C697A10E /* jdsample.c */, + 012ADA8A00868211C697A10E /* jdtrans.c */, + 012ADA8B00868211C697A10E /* jerror.c */, + 012ADA8C00868211C697A10E /* jerror.h */, + 012ADA8D00868211C697A10E /* jfdctflt.c */, + 012ADA9000868211C697A10E /* jidctflt.c */, + 012ADA9400868211C697A10E /* jinclude.h */, + 012ADA9800868211C697A10E /* jmemmgr.c */, + 012ADA9A00868211C697A10E /* jmemnobs.c */, + 012ADA9B00868211C697A10E /* jmemsys.h */, + 012ADA9C00868211C697A10E /* jmorecfg.h */, + 012ADA9D00868211C697A10E /* jpegint.h */, + 012ADA9E00868211C697A10E /* jpeglib.h */, + 012ADAA200868211C697A10E /* jutils.c */, + 012ADAA300868211C697A10E /* jversion.h */, + ); + path = "jpeg-6"; + sourceTree = ""; + }; + 012ADAEC00868211C697A10E /* qcommon */ = { + isa = PBXGroup; + children = ( + 012ADAED00868211C697A10E /* cm_load.c */, + 012ADAEE00868211C697A10E /* cm_local.h */, + 012ADAEF00868211C697A10E /* cm_patch.c */, + 012ADAF000868211C697A10E /* cm_patch.h */, + 012ADAF100868211C697A10E /* cm_polylib.c */, + 012ADAF200868211C697A10E /* cm_polylib.h */, + 012ADAF300868211C697A10E /* cm_public.h */, + 012ADAF400868211C697A10E /* cm_test.c */, + 012ADAF500868211C697A10E /* cm_trace.c */, + 012ADAF600868211C697A10E /* cmd.c */, + 012ADAF700868211C697A10E /* common.c */, + 012ADAF800868211C697A10E /* cvar.c */, + 012ADAF900868211C697A10E /* files.c */, + 016F1B6300ACDA9BC697A10E /* huffman.c */, + 012ADAFA00868211C697A10E /* md4.c */, + 012ADAFB00868211C697A10E /* msg.c */, + 012ADAFC00868211C697A10E /* net_chan.c */, + 012ADAFD00868211C697A10E /* qcommon.h */, + 012ADAFE00868211C697A10E /* qfiles.h */, + 012ADAFF00868211C697A10E /* unzip.c */, + 012ADB0000868211C697A10E /* unzip.h */, + 012ADB0100868211C697A10E /* vm.c */, + 012ADB0200868211C697A10E /* vm_interpreted.c */, + 012ADB0300868211C697A10E /* vm_local.h */, + ); + path = qcommon; + sourceTree = ""; + }; + 012ADB0600868211C697A10E /* renderer */ = { + isa = PBXGroup; + children = ( + 012ADB0800868211C697A10E /* qgl.h */, + 012ADB0A00868211C697A10E /* tr_animation.c */, + 012ADB0B00868211C697A10E /* tr_backend.c */, + 012ADB0C00868211C697A10E /* tr_bsp.c */, + 012ADB0D00868211C697A10E /* tr_cmds.c */, + 012ADB0E00868211C697A10E /* tr_curve.c */, + 012ADB0F00868211C697A10E /* tr_flares.c */, + 012ADB1000868211C697A10E /* tr_font.c */, + 012ADB1100868211C697A10E /* tr_image.c */, + 012ADB1200868211C697A10E /* tr_init.c */, + 012ADB1300868211C697A10E /* tr_light.c */, + 012ADB1400868211C697A10E /* tr_local.h */, + 012ADB1500868211C697A10E /* tr_main.c */, + 012ADB1600868211C697A10E /* tr_marks.c */, + 012ADB1700868211C697A10E /* tr_mesh.c */, + 012ADB1800868211C697A10E /* tr_model.c */, + 012ADB1900868211C697A10E /* tr_noise.c */, + 012ADB1A00868211C697A10E /* tr_public.h */, + 012ADB1B00868211C697A10E /* tr_scene.c */, + 012ADB1C00868211C697A10E /* tr_shade.c */, + 012ADB1D00868211C697A10E /* tr_shade_calc.c */, + 012ADB1E00868211C697A10E /* tr_shader.c */, + 012ADB1F00868211C697A10E /* tr_shadows.c */, + 012ADB2000868211C697A10E /* tr_sky.c */, + 012ADB2100868211C697A10E /* tr_surface.c */, + 012ADB2200868211C697A10E /* tr_world.c */, + F5ABC7B9013B054201F6286D /* tr_cmesh.c */, + ); + path = renderer; + sourceTree = ""; + }; + 012ADB2300868211C697A10E /* server */ = { + isa = PBXGroup; + children = ( + 012ADB2400868211C697A10E /* server.h */, + 012ADB2500868211C697A10E /* sv_bot.c */, + 012ADB2600868211C697A10E /* sv_ccmds.c */, + 012ADB2700868211C697A10E /* sv_client.c */, + 012ADB2800868211C697A10E /* sv_game.c */, + 012ADB2900868211C697A10E /* sv_init.c */, + 012ADB2A00868211C697A10E /* sv_main.c */, + 012ADB2B00868211C697A10E /* sv_net_chan.c */, + 012ADB2D00868211C697A10E /* sv_snapshot.c */, + 012ADB2E00868211C697A10E /* sv_world.c */, + ); + path = server; + sourceTree = ""; + }; + 012ADB2F00868211C697A10E /* splines */ = { + isa = PBXGroup; + children = ( + 012ADB3000868211C697A10E /* math_angles.cpp */, + 012ADB3100868211C697A10E /* math_angles.h */, + 012ADB3200868211C697A10E /* math_matrix.cpp */, + 012ADB3300868211C697A10E /* math_matrix.h */, + 012ADB3400868211C697A10E /* math_quaternion.cpp */, + 012ADB3500868211C697A10E /* math_quaternion.h */, + 012ADB3600868211C697A10E /* math_vector.cpp */, + 012ADB3700868211C697A10E /* math_vector.h */, + 012ADB3800868211C697A10E /* q_parse.cpp */, + 012ADB3900868211C697A10E /* q_shared.cpp */, + 012ADB3A00868211C697A10E /* q_shared.h */, + 012ADB3B00868211C697A10E /* splines.cpp */, + 012ADB3C00868211C697A10E /* splines.h */, + 012ADB3D00868211C697A10E /* util_list.h */, + 012ADB3E00868211C697A10E /* util_str.cpp */, + 012ADB3F00868211C697A10E /* util_str.h */, + ); + path = splines; + sourceTree = ""; + }; + 012ADB4000868211C697A10E /* ui */ = { + isa = PBXGroup; + children = ( + 012ADB4100868211C697A10E /* keycodes.h */, + 012ADB4200868211C697A10E /* ui_atoms.c */, + 012ADB4300868211C697A10E /* ui_gameinfo.c */, + 012ADB4400868211C697A10E /* ui_local.h */, + 012ADB4500868211C697A10E /* ui_main.c */, + 012ADB4600868211C697A10E /* ui_players.c */, + 012ADB4700868211C697A10E /* ui_public.h */, + 012ADB4800868211C697A10E /* ui_shared.c */, + 012ADB4900868211C697A10E /* ui_shared.h */, + 012ADB4A00868211C697A10E /* ui_syscalls.c */, + 012ADB4B00868211C697A10E /* ui_util.c */, + ); + path = ui; + sourceTree = ""; + }; + 012ADB4C00868211C697A10E /* unix */ = { + isa = PBXGroup; + children = ( + 012ADB4D00868211C697A10E /* linux_common.c */, + 012ADB4E00868211C697A10E /* linux_glimp.c */, + 012ADB4F00868211C697A10E /* linux_joystick.c */, + 012ADB5000868211C697A10E /* linux_local.h */, + 012ADB5100868211C697A10E /* linux_qgl.c */, + 012ADB5200868211C697A10E /* linux_snd.c */, + 012ADB5300868211C697A10E /* qasm.h */, + 012ADB5500868211C697A10E /* unix_glw.h */, + 012ADB5600868211C697A10E /* unix_main.c */, + 012ADB5700868211C697A10E /* unix_net.c */, + 012ADB5800868211C697A10E /* unix_shared.c */, + ); + path = unix; + sourceTree = ""; + }; + 043627A000868916C697A10E /* Mac OS X */ = { + isa = PBXGroup; + children = ( + 043627A400868916C697A10E /* dlfcn.h */, + 043627A500868916C697A10E /* dlopen.c */, + 015ECC0C00894EC0C697A10E /* macosx_display.h */, + 015ECC0D00894EC0C697A10E /* macosx_display.m */, + 011F78F200B25B65C697A10E /* macosx_qgl.h */, + 043627A600868916C697A10E /* macosx_glimp.h */, + 043627A700868916C697A10E /* macosx_glimp.m */, + 016B4A3B00ACCF9FC697A10E /* macosx_glsmp_mutex.m */, + 016B4A3C00ACCF9FC697A10E /* macosx_glsmp_null.m */, + 016B4A3D00ACCF9FC697A10E /* macosx_glsmp_ports.m */, + 043627A800868916C697A10E /* macosx_input.m */, + 043627A900868916C697A10E /* macosx_local.h */, + 043627AB00868916C697A10E /* macosx_sndcore.m */, + 043627AD00868916C697A10E /* macosx_sys.m */, + 043627AE00868916C697A10E /* macosx_timers.h */, + 043627AF00868916C697A10E /* macosx_timers.m */, + 043627B000868916C697A10E /* Q3Controller.h */, + 043627B100868916C697A10E /* Q3Controller.m */, + 043627B200868916C697A10E /* Quake3.nib */, + 043627B300868916C697A10E /* Quake3.icns */, + 043627B400868916C697A10E /* banner.jpg */, + 043627B500868916C697A10E /* Performance.rtf */, + 043627B600868916C697A10E /* BuildRelease */, + 043627B700868916C697A10E /* RecordDemo.zsh */, + 043627B800868916C697A10E /* timedemo.zsh */, + F54C3532015659F101F6286D /* Wolf.icns */, + ); + name = "Mac OS X"; + sourceTree = ""; + }; + 0654BA42FE8ECEE0C697A12F /* Quake3 */ = { + isa = PBXGroup; + children = ( + 043627A000868916C697A10E /* Mac OS X */, + 0654BA76FE8ED011C697A12F /* Id Source */, + 0654BA57FE8ECEE0C697A12F /* External Frameworks and Libraries */, + 07FB599DFEB762C8C697A12F /* Products */, + ); + name = Quake3; + sourceTree = ""; + }; + 0654BA57FE8ECEE0C697A12F /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + F5DBFB970136AC6901F6286D /* IOKit.framework */, + 0654BA58FE8ECEE0C697A12F /* OpenGL.framework */, + 0654BA59FE8ECEE0C697A12F /* Carbon.framework */, + 00E9D914FEDB4D29C697A12F /* CoreAudio.framework */, + 0654BA5AFE8ECEE0C697A12F /* AppKit.framework */, + 0654BA5BFE8ECEE0C697A12F /* Foundation.framework */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + 0654BA76FE8ED011C697A12F /* Id Source */ = { + isa = PBXGroup; + children = ( + 012AD90700868211C697A10E /* botlib */, + 012AD93D00868211C697A10E /* bspc */, + 012AD98D00868211C697A10E /* cgame */, + 012AD9A500868211C697A10E /* client */, + 012AD9B900868211C697A10E /* ft2 */, + 012ADA2400868211C697A10E /* game */, + 012ADA6500868211C697A10E /* jpeg-6 */, + 012ADAEC00868211C697A10E /* qcommon */, + 012ADB0600868211C697A10E /* renderer */, + 012ADB2300868211C697A10E /* server */, + 012ADB2F00868211C697A10E /* splines */, + 012ADB4000868211C697A10E /* ui */, + 012ADB4C00868211C697A10E /* unix */, + ); + name = "Id Source"; + path = ..; + sourceTree = ""; + }; + 07FB599DFEB762C8C697A12F /* Products */ = { + isa = PBXGroup; + children = ( + 09143A93FF39F3EF11CA2562 /* WolfensteinMP.app */, + 07F3F507FFE98E8EC697A10E /* qagame.bundle */, + 07F3F508FFE98E8EC697A10E /* cgame.bundle */, + 016EAE0200B4BDD1C697A10E /* ui.bundle */, + F50905BE0157C20601F6286D /* WolfDedicated */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 00F5ED39FEBA95B7C697A12F /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 13380E0F00ADFAACC697A10E /* q_shared.h in Headers */, + 13380E2500ADFCA7C697A10E /* g_team.h in Headers */, + F5ABC7E1013B0EAB01F6286D /* ai_cast_fight.h in Headers */, + F5ABC7E2013B0EAB01F6286D /* ai_cast_global.h in Headers */, + F5ABC80F013B120B01F6286D /* ai_dmq3.h in Headers */, + F5ABC810013B120B01F6286D /* ai_main.h in Headers */, + F5ABC811013B120B01F6286D /* ai_team.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 00F5ED91FEBA9615C697A12F /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 13380E3600ADFDCFC697A10E /* q_shared.h in Headers */, + 13380E5100AE0235C697A10E /* ui_shared.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 016EAE0400B4BDD1C697A10E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 016EAE0500B4BDD1C697A10E /* ui_shared.h in Headers */, + 016EAE0600B4BDD1C697A10E /* ui_public.h in Headers */, + 016EAE0700B4BDD1C697A10E /* ui_local.h in Headers */, + 016EAE0800B4BDD1C697A10E /* keycodes.h in Headers */, + 016EAE1400B4BE42C697A10E /* q_shared.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0654BA5DFE8ECEE0C697A12F /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 043627BB00868916C697A10E /* dlfcn.h in Headers */, + 043627BC00868916C697A10E /* macosx_glimp.h in Headers */, + 043627BD00868916C697A10E /* macosx_local.h in Headers */, + 043627BE00868916C697A10E /* macosx_timers.h in Headers */, + 043627BF00868916C697A10E /* Q3Controller.h in Headers */, + 043627D30086965EC697A10E /* q_shared.h in Headers */, + 043627D700869713C697A10E /* vm_local.h in Headers */, + 043627DE00869726C697A10E /* unzip.h in Headers */, + 043627E100869827C697A10E /* cm_local.h in Headers */, + 043627E200869827C697A10E /* cm_patch.h in Headers */, + 043627E300869827C697A10E /* cm_polylib.h in Headers */, + 043627E400869827C697A10E /* cm_public.h in Headers */, + 043627E500869827C697A10E /* qcommon.h in Headers */, + 043627E600869827C697A10E /* qfiles.h in Headers */, + 043627E700869827C697A10E /* qgl.h in Headers */, + 043627E900869827C697A10E /* tr_local.h in Headers */, + 043627EA00869827C697A10E /* tr_public.h in Headers */, + 043627EB00869827C697A10E /* server.h in Headers */, + 0436281F008698F8C697A10E /* client.h in Headers */, + 04362820008698F8C697A10E /* keys.h in Headers */, + 04362821008698F8C697A10E /* snd_local.h in Headers */, + 04362822008698F8C697A10E /* snd_public.h in Headers */, + 0436283000869A8EC697A10E /* util_str.h in Headers */, + 0436284900869B0BC697A10E /* jerror.h in Headers */, + 0436284B00869B0BC697A10E /* jmemsys.h in Headers */, + 0436287200869D48C697A10E /* jchuff.h in Headers */, + 0436288100869E21C697A10E /* jdhuff.h in Headers */, + 0436288D00869F06C697A10E /* l_precomp.h in Headers */, + 0436288F00869F0FC697A10E /* l_libvar.h in Headers */, + 0436289100869F2AC697A10E /* l_script.h in Headers */, + 0436289300869F38C697A10E /* l_memory.h in Headers */, + 0436289500869F40C697A10E /* l_log.h in Headers */, + 0436289B00869FAEC697A10E /* be_aas_route.h in Headers */, + 0436289D00869FBBC697A10E /* be_aas_move.h in Headers */, + 0436289F00869FC3C697A10E /* be_aas_sample.h in Headers */, + 043628A100869FDEC697A10E /* be_aas_bsp.h in Headers */, + 043628A300869FE8C697A10E /* be_aas_debug.h in Headers */, + 043628A500869FEFC697A10E /* be_aas_reach.h in Headers */, + 043628A70086A057C697A10E /* be_aas_main.h in Headers */, + 043628AA0086A081C697A10E /* be_ai_weight.h in Headers */, + 043628AC0086A092C697A10E /* be_aas_optimize.h in Headers */, + 043628AE0086A099C697A10E /* be_aas_routealt.h in Headers */, + 043628B00086A0B9C697A10E /* l_struct.h in Headers */, + 043628B10086A0B9C697A10E /* be_interface.h in Headers */, + 043628B20086A0B9C697A10E /* l_crc.h in Headers */, + 043628B30086A0B9C697A10E /* l_utils.h in Headers */, + 043628B70086A0E6C697A10E /* be_aas_file.h in Headers */, + 043628B90086A0F1C697A10E /* be_aas_entity.h in Headers */, + 043628BB0086A136C697A10E /* be_aas_cluster.h in Headers */, + 043628BC0086A136C697A10E /* be_aas_def.h in Headers */, + 043628BD0086A136C697A10E /* be_aas_funcs.h in Headers */, + 043628BE0086A136C697A10E /* aasfile.h in Headers */, + 015ECC0E00894EC0C697A10E /* macosx_display.h in Headers */, + 011F78F300B25B66C697A10E /* macosx_qgl.h in Headers */, + F5ABC7B7013B051201F6286D /* be_aas_routetable.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F50905C00157C20601F6286D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F5548C450157D33F01F6286D /* dlfcn.h in Headers */, + F5548C460157D33F01F6286D /* macosx_glimp.h in Headers */, + F5548C470157D33F01F6286D /* macosx_qgl.h in Headers */, + F5548C480157D33F01F6286D /* macosx_display.h in Headers */, + F5548C490157D33F01F6286D /* macosx_local.h in Headers */, + F5548C4A0157D33F01F6286D /* Q3Controller.h in Headers */, + F5548C4B0157D33F01F6286D /* macosx_timers.h in Headers */, + F5548C4C0157D33F01F6286D /* tr_local.h in Headers */, + F5548C4D0157D33F01F6286D /* tr_public.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXProject section */ + 0654BA41FE8ECEE0C697A12F /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 256D53D90A07EDF400E71389 /* Build configuration list for PBXProject "Wolf" */; + buildSettings = { + }; + buildStyles = ( + 07F3F50BFFE98E8EC697A10E /* Development */, + 07F3F50CFFE98E8EC697A10E /* Deployment */, + ); + hasScannedForEncodings = 0; + mainGroup = 0654BA42FE8ECEE0C697A12F /* Quake3 */; + projectDirPath = ""; + targets = ( + 0170311C00B49352C697A10E /* All */, + 0654BA5CFE8ECEE0C697A12F /* WolfMP (Application) */, + 00F5ED38FEBA95B7C697A12F /* qagame */, + 00F5ED90FEBA9615C697A12F /* cgame */, + 016EAE0300B4BDD1C697A10E /* ui */, + F50905BF0157C20601F6286D /* WolfDedicated */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 00F5ED55FEBA95B7C697A12F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 00F5ED92FEBA9615C697A12F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 016EAE0900B4BDD1C697A10E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0654BA61FE8ECEE0C697A12F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 043627C100868916C697A10E /* Quake3.nib in Resources */, + 043627C200868916C697A10E /* banner.jpg in Resources */, + F54C3533015659F201F6286D /* Wolf.icns in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXRezBuildPhase section */ + 00F5ED78FEBA95B7C697A12F /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 00F5ED95FEBA9615C697A12F /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 016EAE1300B4BDD1C697A10E /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0654BA70FE8ECEE0C697A12F /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F50905C30157C20601F6286D /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXRezBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00F5ED56FEBA95B7C697A12F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13380E0900ADF941C697A10E /* g_main.c in Sources */, + 13380E0B00ADFA16C697A10E /* g_syscalls.c in Sources */, + 13380E0C00ADFA72C697A10E /* g_svcmds.c in Sources */, + 13380E0D00ADFA94C697A10E /* g_mem.c in Sources */, + 13380E0E00ADFA9EC697A10E /* g_bot.c in Sources */, + 13380E1000ADFAACC697A10E /* q_math.c in Sources */, + 13380E1100ADFAACC697A10E /* q_shared.c in Sources */, + 13380E2100ADFC59C697A10E /* g_utils.c in Sources */, + 13380E2200ADFC78C697A10E /* g_combat.c in Sources */, + 13380E2300ADFC85C697A10E /* bg_misc.c in Sources */, + 13380E2400ADFC9CC697A10E /* g_weapon.c in Sources */, + 13380E2600ADFCA7C697A10E /* g_team.c in Sources */, + 13380E2700ADFCBDC697A10E /* g_client.c in Sources */, + 13380E2800ADFCC8C697A10E /* g_items.c in Sources */, + 13380E2900ADFCD1C697A10E /* g_missile.c in Sources */, + 13380E2A00ADFCEBC697A10E /* g_spawn.c in Sources */, + 13380E2B00ADFCF6C697A10E /* g_session.c in Sources */, + 13380E2C00ADFD01C697A10E /* g_cmds.c in Sources */, + 13380E2D00ADFD1FC697A10E /* g_misc.c in Sources */, + 13380E2E00ADFD28C697A10E /* g_trigger.c in Sources */, + 13380E2F00ADFD38C697A10E /* g_target.c in Sources */, + 13380E3000ADFD46C697A10E /* g_mover.c in Sources */, + 13380E3100ADFD58C697A10E /* g_active.c in Sources */, + 13380E3200ADFD71C697A10E /* bg_pmove.c in Sources */, + 13380E3300ADFD85C697A10E /* bg_slidemove.c in Sources */, + F5ABC7E6013B0EAB01F6286D /* ai_cast_characters.c in Sources */, + F5ABC7E7013B0EAB01F6286D /* ai_cast_debug.c in Sources */, + F5ABC7E8013B0EAB01F6286D /* ai_cast_events.c in Sources */, + F5ABC7E9013B0EAB01F6286D /* ai_cast_fight.c in Sources */, + F5ABC7EA013B0EAB01F6286D /* ai_cast_func_attack.c in Sources */, + F5ABC7EB013B0EAB01F6286D /* ai_cast_func_boss1.c in Sources */, + F5ABC7EC013B0EAB01F6286D /* ai_cast_funcs.c in Sources */, + F5ABC7ED013B0EAB01F6286D /* ai_cast_script_actions.c in Sources */, + F5ABC7EE013B0EAB01F6286D /* ai_cast_script_ents.c in Sources */, + F5ABC7EF013B0EAB01F6286D /* ai_cast_script.c in Sources */, + F5ABC7F0013B0EAB01F6286D /* ai_cast_sight.c in Sources */, + F5ABC7F1013B0EAB01F6286D /* ai_cast_think.c in Sources */, + F5ABC7F2013B0EAB01F6286D /* ai_cast.c in Sources */, + F5ABC7F3013B0EAB01F6286D /* bg_animation.c in Sources */, + F5ABC7F4013B0EAB01F6286D /* g_alarm.c in Sources */, + F5ABC7F5013B0EAB01F6286D /* g_props.c in Sources */, + F5ABC7F6013B0EAB01F6286D /* g_save.c in Sources */, + F5ABC7F7013B0EAB01F6286D /* g_script_actions.c in Sources */, + F5ABC7F8013B0EAB01F6286D /* g_script.c in Sources */, + F5ABC817013B120B01F6286D /* ai_chat.c in Sources */, + F5ABC818013B120B01F6286D /* ai_cmd.c in Sources */, + F5ABC819013B120B01F6286D /* ai_dmnet.c in Sources */, + F5ABC81A013B120B01F6286D /* ai_dmq3.c in Sources */, + F5ABC81B013B120B01F6286D /* ai_main.c in Sources */, + F5ABC81C013B120B01F6286D /* ai_team.c in Sources */, + F55DF57B01513A8101F6286D /* g_tramcar.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 00F5ED93FEBA9615C697A12F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13380E3400ADFDA1C697A10E /* cg_main.c in Sources */, + 13380E3500ADFDA1C697A10E /* cg_syscalls.c in Sources */, + 13380E3700ADFDCFC697A10E /* q_shared.c in Sources */, + 13380E3800ADFDCFC697A10E /* q_math.c in Sources */, + 13380E3900ADFDE1C697A10E /* bg_misc.c in Sources */, + 13380E3B00ADFE0CC697A10E /* cg_predict.c in Sources */, + 13380E3C00ADFE1EC697A10E /* bg_pmove.c in Sources */, + 13380E3D00ADFE24C697A10E /* bg_slidemove.c in Sources */, + 13380E3E00ADFE3DC697A10E /* cg_playerstate.c in Sources */, + 13380E3F00ADFE4AC697A10E /* cg_event.c in Sources */, + 13380E4000ADFE58C697A10E /* cg_effects.c in Sources */, + 13380E4100ADFE6BC697A10E /* cg_localents.c in Sources */, + 13380E4200ADFE7AC697A10E /* cg_marks.c in Sources */, + 13380E4300ADFE88C697A10E /* cg_weapons.c in Sources */, + 13380E4400ADFE97C697A10E /* cg_ents.c in Sources */, + 13380E4500ADFEA7C697A10E /* cg_players.c in Sources */, + 13380E4600ADFEB8C697A10E /* cg_drawtools.c in Sources */, + 13380E4700ADFEC9C697A10E /* cg_view.c in Sources */, + 13380E4800ADFED7C697A10E /* cg_snapshot.c in Sources */, + 13380E4900ADFEE7C697A10E /* cg_servercmds.c in Sources */, + 13380E4A00ADFEF5C697A10E /* cg_scoreboard.c in Sources */, + 13380E4B00ADFF05C697A10E /* cg_info.c in Sources */, + 13380E4C00ADFF27C697A10E /* cg_consolecmds.c in Sources */, + 13380E4F00AE0112C697A10E /* cg_draw.c in Sources */, + 13380E5200AE0235C697A10E /* ui_shared.c in Sources */, + F5ABC821013B12E701F6286D /* cg_flamethrower.c in Sources */, + F5ABC822013B12E701F6286D /* cg_particles.c in Sources */, + F5ABC823013B12E701F6286D /* cg_sound.c in Sources */, + F5ABC824013B12E701F6286D /* cg_trails.c in Sources */, + F5ABC825013B145701F6286D /* bg_animation.c in Sources */, + F5673BEF015686CC01F628AA /* cg_newDraw.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 016EAE0A00B4BDD1C697A10E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 016EAE0B00B4BDD1C697A10E /* ui_util.c in Sources */, + 016EAE0C00B4BDD1C697A10E /* ui_syscalls.c in Sources */, + 016EAE0D00B4BDD1C697A10E /* ui_shared.c in Sources */, + 016EAE0E00B4BDD1C697A10E /* ui_players.c in Sources */, + 016EAE0F00B4BDD1C697A10E /* ui_main.c in Sources */, + 016EAE1000B4BDD1C697A10E /* ui_gameinfo.c in Sources */, + 016EAE1100B4BDD1C697A10E /* ui_atoms.c in Sources */, + 016EAE1500B4BE42C697A10E /* q_math.c in Sources */, + 016EAE1600B4BE42C697A10E /* q_shared.c in Sources */, + 016EAE1700B4BE53C697A10E /* bg_misc.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0654BA65FE8ECEE0C697A12F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 043627C400868916C697A10E /* dlopen.c in Sources */, + 043627C500868916C697A10E /* macosx_glimp.m in Sources */, + 043627C600868916C697A10E /* macosx_input.m in Sources */, + 043627C800868916C697A10E /* macosx_sndcore.m in Sources */, + 043627CA00868916C697A10E /* macosx_sys.m in Sources */, + 043627CB00868916C697A10E /* macosx_timers.m in Sources */, + 043627CC00868916C697A10E /* Q3Controller.m in Sources */, + 043627D20086963EC697A10E /* cmd.c in Sources */, + 043627D40086965EC697A10E /* q_shared.c in Sources */, + 043627D50086965EC697A10E /* q_math.c in Sources */, + 043627D600869679C697A10E /* tr_init.c in Sources */, + 043627D800869713C697A10E /* common.c in Sources */, + 043627D900869713C697A10E /* cvar.c in Sources */, + 043627DA00869713C697A10E /* files.c in Sources */, + 043627DB00869713C697A10E /* vm.c in Sources */, + 043627DD00869713C697A10E /* vm_interpreted.c in Sources */, + 043627DF00869726C697A10E /* unzip.c in Sources */, + 043627E00086978DC697A10E /* unix_shared.c in Sources */, + 043627EC00869827C697A10E /* cl_cgame.c in Sources */, + 043627ED00869827C697A10E /* cl_cin.c in Sources */, + 043627EE00869827C697A10E /* cl_console.c in Sources */, + 043627EF00869827C697A10E /* cl_input.c in Sources */, + 043627F000869827C697A10E /* cl_keys.c in Sources */, + 043627F100869827C697A10E /* cl_main.c in Sources */, + 043627F200869827C697A10E /* cl_net_chan.c in Sources */, + 043627F300869827C697A10E /* cl_parse.c in Sources */, + 043627F400869827C697A10E /* cl_scrn.c in Sources */, + 043627F500869827C697A10E /* cl_ui.c in Sources */, + 043627F600869827C697A10E /* cm_load.c in Sources */, + 043627F700869827C697A10E /* cm_patch.c in Sources */, + 043627F800869827C697A10E /* cm_polylib.c in Sources */, + 043627F900869827C697A10E /* cm_test.c in Sources */, + 043627FA00869827C697A10E /* cm_trace.c in Sources */, + 043627FB00869827C697A10E /* md4.c in Sources */, + 043627FC00869827C697A10E /* msg.c in Sources */, + 043627FD00869827C697A10E /* net_chan.c in Sources */, + 043627FE00869827C697A10E /* tr_animation.c in Sources */, + 043627FF00869827C697A10E /* tr_backend.c in Sources */, + 0436280000869827C697A10E /* tr_bsp.c in Sources */, + 0436280100869827C697A10E /* tr_cmds.c in Sources */, + 0436280200869827C697A10E /* tr_curve.c in Sources */, + 0436280300869827C697A10E /* tr_flares.c in Sources */, + 0436280400869827C697A10E /* tr_font.c in Sources */, + 0436280500869827C697A10E /* tr_image.c in Sources */, + 0436280600869827C697A10E /* tr_light.c in Sources */, + 0436280700869827C697A10E /* tr_main.c in Sources */, + 0436280800869827C697A10E /* tr_marks.c in Sources */, + 0436280900869827C697A10E /* tr_mesh.c in Sources */, + 0436280A00869827C697A10E /* tr_model.c in Sources */, + 0436280B00869827C697A10E /* tr_noise.c in Sources */, + 0436280C00869827C697A10E /* tr_scene.c in Sources */, + 0436280D00869827C697A10E /* tr_shade.c in Sources */, + 0436280E00869827C697A10E /* tr_shade_calc.c in Sources */, + 0436280F00869827C697A10E /* tr_shader.c in Sources */, + 0436281000869827C697A10E /* tr_shadows.c in Sources */, + 0436281100869827C697A10E /* tr_sky.c in Sources */, + 0436281200869827C697A10E /* tr_surface.c in Sources */, + 0436281300869827C697A10E /* tr_world.c in Sources */, + 0436281400869827C697A10E /* sv_bot.c in Sources */, + 0436281500869827C697A10E /* sv_ccmds.c in Sources */, + 0436281600869827C697A10E /* sv_client.c in Sources */, + 0436281700869827C697A10E /* sv_game.c in Sources */, + 0436281800869827C697A10E /* sv_init.c in Sources */, + 0436281900869827C697A10E /* sv_main.c in Sources */, + 0436281A00869827C697A10E /* sv_net_chan.c in Sources */, + 0436281C00869827C697A10E /* sv_snapshot.c in Sources */, + 0436281D00869827C697A10E /* sv_world.c in Sources */, + 0436281E00869827C697A10E /* unix_net.c in Sources */, + 04362823008698F8C697A10E /* snd_adpcm.c in Sources */, + 04362824008698F8C697A10E /* snd_dma.c in Sources */, + 04362825008698F8C697A10E /* snd_mem.c in Sources */, + 04362826008698F8C697A10E /* snd_mix.c in Sources */, + 043628270086991FC697A10E /* snd_wavelet.c in Sources */, + 0436282E00869A16C697A10E /* splines.cpp in Sources */, + 0436282F00869A7FC697A10E /* q_parse.cpp in Sources */, + 0436283100869A8EC697A10E /* util_str.cpp in Sources */, + 0436283300869AC5C697A10E /* jmemmgr.c in Sources */, + 0436286A00869C36C697A10E /* jerror.c in Sources */, + 0436286B00869C6FC697A10E /* jcapimin.c in Sources */, + 0436286C00869CB4C697A10E /* jcomapi.c in Sources */, + 0436286D00869CC9C697A10E /* jutils.c in Sources */, + 0436286E00869D0AC697A10E /* jcmarker.c in Sources */, + 0436286F00869D18C697A10E /* jdapistd.c in Sources */, + 0436287000869D23C697A10E /* jcinit.c in Sources */, + 0436287100869D3BC697A10E /* jcphuff.c in Sources */, + 0436287300869D48C697A10E /* jchuff.c in Sources */, + 0436287400869D56C697A10E /* jcdctmgr.c in Sources */, + 0436287500869D61C697A10E /* jcsample.c in Sources */, + 0436287600869D71C697A10E /* jccolor.c in Sources */, + 0436287700869D7FC697A10E /* jcprepct.c in Sources */, + 0436287800869D92C697A10E /* jccoefct.c in Sources */, + 0436287900869DBBC697A10E /* jfdctflt.c in Sources */, + 0436287A00869DC7C697A10E /* jcmaster.c in Sources */, + 0436287B00869DD4C697A10E /* jdmaster.c in Sources */, + 0436287C00869DDDC697A10E /* jdapimin.c in Sources */, + 0436287D00869DF1C697A10E /* jdmarker.c in Sources */, + 0436287E00869DFAC697A10E /* jdinput.c in Sources */, + 0436287F00869E04C697A10E /* jdsample.c in Sources */, + 0436288000869E10C697A10E /* jddctmgr.c in Sources */, + 0436288200869E21C697A10E /* jdhuff.c in Sources */, + 0436288300869E3DC697A10E /* jidctflt.c in Sources */, + 0436288400869E4CC697A10E /* jdpostct.c in Sources */, + 0436288500869E56C697A10E /* jcparam.c in Sources */, + 0436288600869E60C697A10E /* jdatasrc.c in Sources */, + 0436288700869E80C697A10E /* jdmainct.c in Sources */, + 0436288800869E8DC697A10E /* jdcoefct.c in Sources */, + 0436288900869E96C697A10E /* jdcolor.c in Sources */, + 0436288A00869EA2C697A10E /* jcmainct.c in Sources */, + 0436288B00869ED7C697A10E /* be_interface.c in Sources */, + 0436288C00869EF3C697A10E /* be_ai_chat.c in Sources */, + 0436288E00869F06C697A10E /* l_precomp.c in Sources */, + 0436289000869F0FC697A10E /* l_libvar.c in Sources */, + 0436289200869F2AC697A10E /* l_script.c in Sources */, + 0436289400869F38C697A10E /* l_memory.c in Sources */, + 0436289600869F40C697A10E /* l_log.c in Sources */, + 0436289700869F4BC697A10E /* be_ai_gen.c in Sources */, + 0436289800869F63C697A10E /* be_ea.c in Sources */, + 0436289900869F7FC697A10E /* be_ai_char.c in Sources */, + 0436289A00869F8EC697A10E /* be_ai_move.c in Sources */, + 0436289C00869FAEC697A10E /* be_aas_route.c in Sources */, + 0436289E00869FBBC697A10E /* be_aas_move.c in Sources */, + 043628A000869FC3C697A10E /* be_aas_sample.c in Sources */, + 043628A200869FDEC697A10E /* be_aas_bspq3.c in Sources */, + 043628A400869FE8C697A10E /* be_aas_debug.c in Sources */, + 043628A600869FEFC697A10E /* be_aas_reach.c in Sources */, + 043628A80086A057C697A10E /* be_aas_main.c in Sources */, + 043628A90086A061C697A10E /* be_ai_goal.c in Sources */, + 043628AB0086A081C697A10E /* be_ai_weight.c in Sources */, + 043628AD0086A092C697A10E /* be_aas_optimize.c in Sources */, + 043628AF0086A099C697A10E /* be_aas_routealt.c in Sources */, + 043628B40086A0B9C697A10E /* l_struct.c in Sources */, + 043628B50086A0B9C697A10E /* l_crc.c in Sources */, + 043628B60086A0BFC697A10E /* be_ai_weap.c in Sources */, + 043628B80086A0E6C697A10E /* be_aas_file.c in Sources */, + 043628BA0086A0F1C697A10E /* be_aas_entity.c in Sources */, + 043628BF0086A136C697A10E /* be_aas_cluster.c in Sources */, + 015ECC0F00894EC0C697A10E /* macosx_display.m in Sources */, + 016B4A3E00ACCF9FC697A10E /* macosx_glsmp_mutex.m in Sources */, + 016F1B6400ACDA9BC697A10E /* huffman.c in Sources */, + F5ABC7B8013B051201F6286D /* be_aas_routetable.c in Sources */, + F5ABC7BA013B054201F6286D /* tr_cmesh.c in Sources */, + F5673BF90156953F01F628AA /* jmemnobs.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F50905C10157C20601F6286D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F5548C4E0157D33F01F6286D /* dlopen.c in Sources */, + F5548C4F0157D33F01F6286D /* macosx_glsmp_mutex.m in Sources */, + F5548C500157D33F01F6286D /* macosx_glimp.m in Sources */, + F5548C510157D33F01F6286D /* macosx_display.m in Sources */, + F5548C520157D33F01F6286D /* macosx_input.m in Sources */, + F5548C530157D33F01F6286D /* Q3Controller.m in Sources */, + F5548C540157D33F01F6286D /* macosx_timers.m in Sources */, + F5548C550157D33F01F6286D /* macosx_sndcore.m in Sources */, + F5548C560157D33F01F6286D /* macosx_sys.m in Sources */, + F5548C570157D33F01F6286D /* jcapimin.c in Sources */, + F5548C580157D33F01F6286D /* jccoefct.c in Sources */, + F5548C590157D33F01F6286D /* jccolor.c in Sources */, + F5548C5A0157D33F01F6286D /* jchuff.c in Sources */, + F5548C5B0157D33F01F6286D /* jcinit.c in Sources */, + F5548C5C0157D33F01F6286D /* jcdctmgr.c in Sources */, + F5548C5D0157D33F01F6286D /* jcmainct.c in Sources */, + F5548C5E0157D33F01F6286D /* jcmarker.c in Sources */, + F5548C5F0157D33F01F6286D /* jcmaster.c in Sources */, + F5548C600157D33F01F6286D /* jcomapi.c in Sources */, + F5548C610157D33F01F6286D /* jcparam.c in Sources */, + F5548C620157D33F01F6286D /* jcphuff.c in Sources */, + F5548C630157D33F01F6286D /* jcprepct.c in Sources */, + F5548C640157D33F01F6286D /* jcsample.c in Sources */, + F5548C650157D33F01F6286D /* jctrans.c in Sources */, + F5548C660157D33F01F6286D /* jdapimin.c in Sources */, + F5548C670157D33F01F6286D /* jdapistd.c in Sources */, + F5548C680157D33F01F6286D /* jdatadst.c in Sources */, + F5548C690157D33F01F6286D /* jdatasrc.c in Sources */, + F5548C6A0157D33F01F6286D /* jdcoefct.c in Sources */, + F5548C6B0157D33F01F6286D /* jdcolor.c in Sources */, + F5548C6C0157D33F01F6286D /* jddctmgr.c in Sources */, + F5548C6D0157D33F01F6286D /* jdhuff.c in Sources */, + F5548C6E0157D33F01F6286D /* jdinput.c in Sources */, + F5548C6F0157D33F01F6286D /* jdmainct.c in Sources */, + F5548C700157D33F01F6286D /* jdmarker.c in Sources */, + F5548C710157D33F01F6286D /* jdmaster.c in Sources */, + F5548C720157D33F01F6286D /* jdpostct.c in Sources */, + F5548C730157D33F01F6286D /* jdsample.c in Sources */, + F5548C740157D33F01F6286D /* jdtrans.c in Sources */, + F5548C750157D33F01F6286D /* jerror.c in Sources */, + F5548C760157D33F01F6286D /* jfdctflt.c in Sources */, + F5548C770157D33F01F6286D /* jidctflt.c in Sources */, + F5548C780157D33F01F6286D /* jmemmgr.c in Sources */, + F5548C790157D33F01F6286D /* jmemnobs.c in Sources */, + F5548C7A0157D33F01F6286D /* jutils.c in Sources */, + F5548C7B0157D33F01F6286D /* sv_bot.c in Sources */, + F5548C7C0157D33F01F6286D /* sv_ccmds.c in Sources */, + F5548C7D0157D33F01F6286D /* sv_client.c in Sources */, + F5548C7E0157D33F01F6286D /* sv_game.c in Sources */, + F5548C7F0157D33F01F6286D /* sv_init.c in Sources */, + F5548C800157D33F01F6286D /* sv_main.c in Sources */, + F5548C810157D33F01F6286D /* sv_net_chan.c in Sources */, + F5548C820157D33F01F6286D /* sv_snapshot.c in Sources */, + F5548C830157D33F01F6286D /* sv_world.c in Sources */, + F5548C880157D33F01F6286D /* q_parse.cpp in Sources */, + F5548C8A0157D33F01F6286D /* splines.cpp in Sources */, + F5548C8B0157D33F01F6286D /* util_str.cpp in Sources */, + F5548C8C0157D33F01F6286D /* tr_animation.c in Sources */, + F5548C8D0157D33F01F6286D /* tr_backend.c in Sources */, + F5548C8E0157D33F01F6286D /* tr_bsp.c in Sources */, + F5548C8F0157D33F01F6286D /* tr_cmds.c in Sources */, + F5548C900157D33F01F6286D /* tr_curve.c in Sources */, + F5548C910157D33F01F6286D /* tr_flares.c in Sources */, + F5548C920157D33F01F6286D /* tr_font.c in Sources */, + F5548C930157D33F01F6286D /* tr_image.c in Sources */, + F5548C940157D33F01F6286D /* tr_init.c in Sources */, + F5548C950157D33F01F6286D /* tr_light.c in Sources */, + F5548C960157D33F01F6286D /* tr_main.c in Sources */, + F5548C970157D33F01F6286D /* tr_marks.c in Sources */, + F5548C980157D33F01F6286D /* tr_mesh.c in Sources */, + F5548C990157D33F01F6286D /* tr_model.c in Sources */, + F5548C9A0157D33F01F6286D /* tr_noise.c in Sources */, + F5548C9B0157D33F01F6286D /* tr_scene.c in Sources */, + F5548C9C0157D33F01F6286D /* tr_shade.c in Sources */, + F5548C9D0157D33F01F6286D /* tr_shade_calc.c in Sources */, + F5548C9E0157D33F01F6286D /* tr_shader.c in Sources */, + F5548C9F0157D33F01F6286D /* tr_shadows.c in Sources */, + F5548CA00157D33F01F6286D /* tr_sky.c in Sources */, + F5548CA10157D33F01F6286D /* tr_surface.c in Sources */, + F5548CA20157D33F01F6286D /* tr_cmesh.c in Sources */, + F5548CA30157D33F01F6286D /* tr_world.c in Sources */, + F5548CA40157D33F01F6286D /* unix_net.c in Sources */, + F5548CA50157D33F01F6286D /* unix_shared.c in Sources */, + F5548CA60157D33F01F6286D /* be_aas_bspq3.c in Sources */, + F5548CA70157D33F01F6286D /* be_aas_cluster.c in Sources */, + F5548CA80157D33F01F6286D /* be_aas_debug.c in Sources */, + F5548CA90157D33F01F6286D /* be_aas_entity.c in Sources */, + F5548CAA0157D33F01F6286D /* be_aas_file.c in Sources */, + F5548CAB0157D33F01F6286D /* be_aas_main.c in Sources */, + F5548CAC0157D33F01F6286D /* be_aas_move.c in Sources */, + F5548CAD0157D33F01F6286D /* be_aas_optimize.c in Sources */, + F5548CAE0157D33F01F6286D /* be_aas_reach.c in Sources */, + F5548CAF0157D33F01F6286D /* be_aas_route.c in Sources */, + F5548CB00157D33F01F6286D /* be_aas_routealt.c in Sources */, + F5548CB10157D33F01F6286D /* be_aas_sample.c in Sources */, + F5548CB20157D33F01F6286D /* be_ai_chat.c in Sources */, + F5548CB30157D33F01F6286D /* be_ai_char.c in Sources */, + F5548CB40157D33F01F6286D /* be_ai_gen.c in Sources */, + F5548CB50157D33F01F6286D /* be_ai_goal.c in Sources */, + F5548CB60157D33F01F6286D /* be_ai_move.c in Sources */, + F5548CB70157D33F01F6286D /* be_ai_weap.c in Sources */, + F5548CB80157D33F01F6286D /* be_ai_weight.c in Sources */, + F5548CB90157D33F01F6286D /* be_ea.c in Sources */, + F5548CBA0157D33F01F6286D /* be_interface.c in Sources */, + F5548CBB0157D33F01F6286D /* l_crc.c in Sources */, + F5548CBC0157D33F01F6286D /* l_libvar.c in Sources */, + F5548CBD0157D33F01F6286D /* l_log.c in Sources */, + F5548CBE0157D33F01F6286D /* l_memory.c in Sources */, + F5548CBF0157D33F01F6286D /* l_precomp.c in Sources */, + F5548CC00157D33F01F6286D /* l_script.c in Sources */, + F5548CC10157D33F01F6286D /* l_struct.c in Sources */, + F5548CC20157D33F01F6286D /* be_aas_routetable.c in Sources */, + F5548CC30157D36F01F6286D /* q_shared.c in Sources */, + F5548CC40157D36F01F6286D /* q_math.c in Sources */, + F5548CC50157D36F01F6286D /* cm_load.c in Sources */, + F5548CC60157D36F01F6286D /* cm_patch.c in Sources */, + F5548CC70157D36F01F6286D /* cm_polylib.c in Sources */, + F5548CC80157D36F01F6286D /* cm_test.c in Sources */, + F5548CC90157D36F01F6286D /* cm_trace.c in Sources */, + F5548CCA0157D36F01F6286D /* cmd.c in Sources */, + F5548CCB0157D36F01F6286D /* common.c in Sources */, + F5548CCC0157D36F01F6286D /* cvar.c in Sources */, + F5548CCD0157D36F01F6286D /* files.c in Sources */, + F5548CCE0157D36F01F6286D /* huffman.c in Sources */, + F5548CCF0157D36F01F6286D /* md4.c in Sources */, + F5548CD00157D36F01F6286D /* msg.c in Sources */, + F5548CD10157D36F01F6286D /* net_chan.c in Sources */, + F5548CD20157D36F01F6286D /* unzip.c in Sources */, + F5548CD30157D36F01F6286D /* vm.c in Sources */, + F5548CD40157D36F01F6286D /* vm_interpreted.c in Sources */, + F5548CDD0157D5CF01F6286D /* cl_main.c in Sources */, + F5548CDE0157D5CF01F6286D /* cl_cgame.c in Sources */, + F5548CDF0157D5CF01F6286D /* cl_cin.c in Sources */, + F5548CE00157D5CF01F6286D /* cl_console.c in Sources */, + F5548CE10157D5CF01F6286D /* cl_input.c in Sources */, + F5548CE20157D5CF01F6286D /* cl_keys.c in Sources */, + F5548CE30157D5CF01F6286D /* cl_net_chan.c in Sources */, + F5548CE40157D5CF01F6286D /* cl_parse.c in Sources */, + F5548CE50157D5CF01F6286D /* cl_scrn.c in Sources */, + F5548CE60157D5CF01F6286D /* cl_ui.c in Sources */, + F5548CE70157D5CF01F6286D /* snd_adpcm.c in Sources */, + F5548CE80157D5CF01F6286D /* snd_dma.c in Sources */, + F5548CE90157D5CF01F6286D /* snd_mem.c in Sources */, + F5548CEA0157D5CF01F6286D /* snd_mix.c in Sources */, + F5548CEB0157D5CF01F6286D /* snd_wavelet.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0170311D00B49352C697A10E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0654BA5CFE8ECEE0C697A12F /* WolfMP (Application) */; + targetProxy = 256D53A90A07EDC000E71389 /* PBXContainerItemProxy */; + }; + F522BE590157EDB501F6286D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F50905BF0157C20601F6286D /* WolfDedicated */; + targetProxy = 256D53A60A07EDC000E71389 /* PBXContainerItemProxy */; + }; + F5738814013EBD9301F6286D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 00F5ED38FEBA95B7C697A12F /* qagame */; + targetProxy = 256D53A80A07EDC000E71389 /* PBXContainerItemProxy */; + }; + F5738815013EBD9301F6286D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 00F5ED90FEBA9615C697A12F /* cgame */; + targetProxy = 256D53A70A07EDC000E71389 /* PBXContainerItemProxy */; + }; + F5738816013EBD9301F6286D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 016EAE0300B4BDD1C697A10E /* ui */; + targetProxy = 256D53A50A07EDC000E71389 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXToolTarget section */ + F50905BF0157C20601F6286D /* WolfDedicated */ = { + isa = PBXToolTarget; + buildConfigurationList = 256D53D10A07EDF400E71389 /* Build configuration list for PBXToolTarget "WolfDedicated" */; + buildPhases = ( + F50905C00157C20601F6286D /* Headers */, + F50905C10157C20601F6286D /* Sources */, + F50905C20157C20601F6286D /* Frameworks */, + F50905C30157C20601F6286D /* Rez */, + ); + buildSettings = { + OTHER_CFLAGS = "-DDEDICATED"; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfDedicated; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + dependencies = ( + ); + name = WolfDedicated; + productInstallPath = /usr/local/bin; + productName = WolfDedicated; + productReference = F50905BE0157C20601F6286D /* WolfDedicated */; + }; +/* End PBXToolTarget section */ + +/* Begin XCBuildConfiguration section */ + 256D53C20A07EDF400E71389 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "/Users/Shared/$(USER)/InstalledProducts"; + LIBRARY_SEARCH_PATHS = ""; + NO_OPT_CFLAGS = "-O0 -fno-default-inline -fno-inline -fno-inline-functions"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ( + "-DGAMEDLL", + "-g", + "-Wall", + "-DQGL_CHECK_GL_ERRORS", + "$(NO_OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = qagame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + ZERO_LINK = YES; + }; + name = Development; + }; + 256D53C30A07EDF400E71389 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = YES; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "/Users/Shared/$(USER)/InstalledProducts"; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OPT_CFLAGS = "-O3 -Wno-conversion -Wno-switch -Wno-unused -faltivec -ffast-math -funroll-loops"; + OTHER_CFLAGS = ( + "-DGAMEDLL", + "-DNDEBUG", + "$(OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = qagame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Deployment; + }; + 256D53C40A07EDF400E71389 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + INSTALL_PATH = "/Users/Shared/$(USER)/InstalledProducts"; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = "-DGAMEDLL"; + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = qagame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + }; + name = Default; + }; + 256D53C60A07EDF400E71389 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + NO_OPT_CFLAGS = "-O0 -fno-default-inline -fno-inline -fno-inline-functions"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ( + "-DCGAMEDLL", + "-g", + "-Wall", + "-DQGL_CHECK_GL_ERRORS", + "$(NO_OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = cgame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + ZERO_LINK = YES; + }; + name = Development; + }; + 256D53C70A07EDF400E71389 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = YES; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OPT_CFLAGS = "-O3 -Wno-conversion -Wno-switch -Wno-unused -faltivec -ffast-math -funroll-loops"; + OTHER_CFLAGS = ( + "-DCGAMEDLL", + "-DNDEBUG", + "$(OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = cgame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Deployment; + }; + 256D53C80A07EDF400E71389 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = "-DCGAMEDLL"; + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = cgame; + PROFILE_FLAGS = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + }; + name = Default; + }; + 256D53CA0A07EDF400E71389 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + NO_OPT_CFLAGS = "-O0 -fno-default-inline -fno-inline -fno-inline-functions"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ( + "-g", + "-Wall", + "-DQGL_CHECK_GL_ERRORS", + "$(NO_OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = ui; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + ZERO_LINK = YES; + }; + name = Development; + }; + 256D53CB0A07EDF400E71389 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + OPTIMIZATION_CFLAGS = "-O0"; + OPT_CFLAGS = "-O3 -Wno-conversion -Wno-switch -Wno-unused -faltivec -ffast-math -funroll-loops"; + OTHER_CFLAGS = ( + "-DNDEBUG", + "$(OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = ui; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Deployment; + }; + 256D53CC0A07EDF400E71389 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-bundle", + "-undefined", + error, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = ui; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = bundle; + }; + name = Default; + }; + 256D53CE0A07EDF400E71389 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + NO_OPT_CFLAGS = "-O0 -fno-default-inline -fno-inline -fno-inline-functions"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ( + "-g", + "-Wall", + "-DQGL_CHECK_GL_ERRORS", + "$(NO_OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfensteinMP; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = app; + ZERO_LINK = YES; + }; + name = Development; + }; + 256D53CF0A07EDF400E71389 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = YES; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OPT_CFLAGS = "-O3 -Wno-conversion -Wno-switch -Wno-unused -faltivec -ffast-math -funroll-loops"; + OTHER_CFLAGS = ( + "-DNDEBUG", + "$(OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfensteinMP; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = app; + ZERO_LINK = NO; + }; + name = Deployment; + }; + 256D53D00A07EDF400E71389 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ""; + HEADER_SEARCH_PATHS = ""; + LIBRARY_SEARCH_PATHS = ""; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfensteinMP; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wall", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + WRAPPER_EXTENSION = app; + }; + name = Default; + }; + 256D53D20A07EDF400E71389 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + NO_OPT_CFLAGS = "-O0 -fno-default-inline -fno-inline -fno-inline-functions"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ( + "-DDEDICATED", + "-g", + "-Wall", + "-DQGL_CHECK_GL_ERRORS", + "$(NO_OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfDedicated; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = YES; + }; + name = Development; + }; + 256D53D30A07EDF400E71389 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + OPT_CFLAGS = "-O3 -Wno-conversion -Wno-switch -Wno-unused -faltivec -ffast-math -funroll-loops"; + OTHER_CFLAGS = ( + "-DDEDICATED", + "-DNDEBUG", + "$(OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfDedicated; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = NO; + }; + name = Deployment; + }; + 256D53D40A07EDF400E71389 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_CFLAGS = "-DDEDICATED"; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = WolfDedicated; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + name = Default; + }; + 256D53D60A07EDF400E71389 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + NO_OPT_CFLAGS = "-O0 -fno-default-inline -fno-inline -fno-inline-functions"; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ( + "-g", + "-Wall", + "-DQGL_CHECK_GL_ERRORS", + "$(NO_OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = All; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = YES; + }; + name = Development; + }; + 256D53D70A07EDF400E71389 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMMON_CFLAGS = "-DDLL_ONLY -DBOTLIB -DMACOS_X -force_cpusubtype_ALL -Wno-long-double"; + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + OPT_CFLAGS = "-O3 -Wno-conversion -Wno-switch -Wno-unused -faltivec -ffast-math -funroll-loops"; + OTHER_CFLAGS = ( + "-DNDEBUG", + "$(OPT_CFLAGS)", + "$(COMMON_CFLAGS)", + ); + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = All; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = NO; + }; + name = Deployment; + }; + 256D53D80A07EDF400E71389 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = All; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + name = Default; + }; + 256D53DA0A07EDF400E71389 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Development; + }; + 256D53DB0A07EDF400E71389 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Deployment; + }; + 256D53DC0A07EDF400E71389 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Default; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 256D53C10A07EDF400E71389 /* Build configuration list for PBXBundleTarget "qagame" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 256D53C20A07EDF400E71389 /* Development */, + 256D53C30A07EDF400E71389 /* Deployment */, + 256D53C40A07EDF400E71389 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 256D53C50A07EDF400E71389 /* Build configuration list for PBXBundleTarget "cgame" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 256D53C60A07EDF400E71389 /* Development */, + 256D53C70A07EDF400E71389 /* Deployment */, + 256D53C80A07EDF400E71389 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 256D53C90A07EDF400E71389 /* Build configuration list for PBXBundleTarget "ui" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 256D53CA0A07EDF400E71389 /* Development */, + 256D53CB0A07EDF400E71389 /* Deployment */, + 256D53CC0A07EDF400E71389 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 256D53CD0A07EDF400E71389 /* Build configuration list for PBXApplicationTarget "WolfMP (Application)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 256D53CE0A07EDF400E71389 /* Development */, + 256D53CF0A07EDF400E71389 /* Deployment */, + 256D53D00A07EDF400E71389 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 256D53D10A07EDF400E71389 /* Build configuration list for PBXToolTarget "WolfDedicated" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 256D53D20A07EDF400E71389 /* Development */, + 256D53D30A07EDF400E71389 /* Deployment */, + 256D53D40A07EDF400E71389 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 256D53D50A07EDF400E71389 /* Build configuration list for PBXAggregateTarget "All" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 256D53D60A07EDF400E71389 /* Development */, + 256D53D70A07EDF400E71389 /* Deployment */, + 256D53D80A07EDF400E71389 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 256D53D90A07EDF400E71389 /* Build configuration list for PBXProject "Wolf" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 256D53DA0A07EDF400E71389 /* Development */, + 256D53DB0A07EDF400E71389 /* Deployment */, + 256D53DC0A07EDF400E71389 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0654BA41FE8ECEE0C697A12F /* Project object */; +} diff --git a/src/macosx/banner.jpg b/src/macosx/banner.jpg new file mode 100644 index 0000000..487e7d5 Binary files /dev/null and b/src/macosx/banner.jpg differ diff --git a/src/macosx/macosx_display.h b/src/macosx/macosx_display.h new file mode 100644 index 0000000..6371781 --- /dev/null +++ b/src/macosx/macosx_display.h @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "tr_local.h" +#include "macosx_local.h" + +@class NSDictionary; + +extern NSDictionary *Sys_GetMatchingDisplayMode( qboolean allowStretchedModes ); + +extern void Sys_StoreGammaTables(); +extern void Sys_GetGammaTable( glwgamma_t *table ); +extern void Sys_SetScreenFade( glwgamma_t *table, float fraction ); + +extern void Sys_FadeScreens(); +extern void Sys_FadeScreen( CGDirectDisplayID display ); +extern void Sys_UnfadeScreens(); +extern void Sys_UnfadeScreen( CGDirectDisplayID display, glwgamma_t *table ); +extern void Sys_ReleaseAllDisplays(); + diff --git a/src/macosx/macosx_display.m b/src/macosx/macosx_display.m new file mode 100644 index 0000000..40fc62d --- /dev/null +++ b/src/macosx/macosx_display.m @@ -0,0 +1,394 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// $Header$ + +#import +#import // for interpreting the kCGDisplayIOFlags element of the display mode + + +#import "macosx_display.h" + +#include "tr_local.h" +#import "macosx_local.h" + +NSDictionary *Sys_GetMatchingDisplayMode( qboolean allowStretchedModes ) { + NSArray *displayModes; + NSDictionary *mode; + unsigned int modeIndex, modeCount, bestModeIndex; + int verbose; + cvar_t *cMinFreq, *cMaxFreq; + int minFreq, maxFreq; + unsigned int colorDepth; + + verbose = r_verbose->integer; + + colorDepth = r_colorbits->integer; + if ( colorDepth < 16 || !r_fullscreen->integer ) { + colorDepth = [[glw_state.desktopMode objectForKey: (id)kCGDisplayBitsPerPixel] intValue]; + } + + cMinFreq = ri.Cvar_Get( "r_minDisplayRefresh", "0", CVAR_ARCHIVE ); + cMaxFreq = ri.Cvar_Get( "r_maxDisplayRefresh", "0", CVAR_ARCHIVE ); + + if ( cMinFreq && cMaxFreq && cMinFreq->integer && cMaxFreq->integer && + cMinFreq->integer > cMaxFreq->integer ) { + ri.Error( ERR_FATAL, "r_minDisplayRefresh must be less than or equal to r_maxDisplayRefresh" ); + } + + minFreq = cMinFreq ? cMinFreq->integer : 0; + maxFreq = cMaxFreq ? cMaxFreq->integer : 0; + + displayModes = (NSArray *)CGDisplayAvailableModes( glw_state.display ); + if ( !displayModes ) { + ri.Error( ERR_FATAL, "CGDisplayAvailableModes returned NULL -- 0x%0x is an invalid display", glw_state.display ); + } + + modeCount = [displayModes count]; + if ( verbose ) { + ri.Printf( PRINT_ALL, "%d modes avaliable\n", modeCount ); + ri.Printf( PRINT_ALL, "Current mode is %s\n", [[(id)CGDisplayCurrentMode( glw_state.display ) description] cString] ); + } + + // Default to the current desktop mode + bestModeIndex = 0xFFFFFFFF; + + for ( modeIndex = 0; modeIndex < modeCount; ++modeIndex ) { + id object; + int refresh; + + mode = [displayModes objectAtIndex: modeIndex]; + if ( verbose ) { + ri.Printf( PRINT_ALL, " mode %d -- %s\n", modeIndex, [[mode description] cString] ); + } + + // Make sure we get the right size + object = [mode objectForKey: (id)kCGDisplayWidth]; + + if ([[mode objectForKey : (id)kCGDisplayWidth] intValue] != glConfig.vidWidth || + [[mode objectForKey : (id)kCGDisplayHeight] intValue] != glConfig.vidHeight ) { + if ( verbose ) { + ri.Printf( PRINT_ALL, " -- bad size\n" ); + } + continue; + } + + if ( !allowStretchedModes ) { + if ([[mode objectForKey : (id)kCGDisplayIOFlags] intValue] & kDisplayModeStretchedFlag ) { + if ( verbose ) { + ri.Printf( PRINT_ALL, " -- stretched modes disallowed\n" ); + } + continue; + } + } + + // Make sure that our frequency restrictions are observed + refresh = [[mode objectForKey: (id)kCGDisplayRefreshRate] intValue]; + if ( minFreq && refresh < minFreq ) { + if ( verbose ) { + ri.Printf( PRINT_ALL, " -- refresh too low\n" ); + } + continue; + } + + if ( maxFreq && refresh > maxFreq ) { + if ( verbose ) { + ri.Printf( PRINT_ALL, " -- refresh too high\n" ); + } + continue; + } + + if ([[mode objectForKey : (id)kCGDisplayBitsPerPixel] intValue] != colorDepth ) { + if ( verbose ) { + ri.Printf( PRINT_ALL, " -- bad depth\n" ); + } + continue; + } + + bestModeIndex = modeIndex; + if ( verbose ) { + ri.Printf( PRINT_ALL, " -- OK\n", bestModeIndex ); + } + } + + if ( verbose ) { + ri.Printf( PRINT_ALL, " bestModeIndex = %d\n", bestModeIndex ); + } + + if ( bestModeIndex == 0xFFFFFFFF ) { + ri.Printf( PRINT_ALL, "No suitable display mode available.\n" ); + return nil; + } + + return [displayModes objectAtIndex : bestModeIndex]; +} + + +#define MAX_DISPLAYS 128 + +void Sys_GetGammaTable( glwgamma_t *table ) { + CGTableCount tableSize = 512; + CGDisplayErr err; + + table->tableSize = tableSize; + if ( table->red ) { + free( table->red ); + } + table->red = malloc( tableSize * sizeof( *table->red ) ); + if ( table->green ) { + free( table->green ); + } + table->green = malloc( tableSize * sizeof( *table->green ) ); + if ( table->blue ) { + free( table->blue ); + } + table->blue = malloc( tableSize * sizeof( *table->blue ) ); + + // TJW: We _could_ loop here if we get back the same size as our table, increasing the table size. + err = CGGetDisplayTransferByTable( table->display, tableSize, table->red, table->green, table->blue, + &table->tableSize ); + if ( err != CGDisplayNoErr ) { + Com_Printf( "GLimp_Init: CGGetDisplayTransferByTable returned %d.\n", err ); + table->tableSize = 0; + } +} + +void Sys_SetGammaTable( glwgamma_t *table ) { +} + + +void Sys_StoreGammaTables() { + // Store the original gamma for all monitors so that we can fade and unfade them all + CGDirectDisplayID displays[MAX_DISPLAYS]; + CGDisplayCount displayIndex; + CGDisplayErr err; + + err = CGGetActiveDisplayList( MAX_DISPLAYS, displays, &glw_state.displayCount ); + if ( err != CGDisplayNoErr ) { + Sys_Error( "Cannot get display list -- CGGetActiveDisplayList returned %d.\n", err ); + } + + glw_state.originalDisplayGammaTables = calloc( glw_state.displayCount, sizeof( *glw_state.originalDisplayGammaTables ) ); + for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) { + glwgamma_t *table; + + table = &glw_state.originalDisplayGammaTables[displayIndex]; + table->display = displays[displayIndex]; + Sys_GetGammaTable( table ); + } +} + + +// This isn't a mathematically correct fade, but we don't care that much. +void Sys_SetScreenFade( glwgamma_t *table, float fraction ) { + CGTableCount tableSize; + CGGammaValue *red, *blue, *green; + CGTableCount gammaIndex; + + if ( !glConfig.deviceSupportsGamma ) { + return; + } + + if ( !( tableSize = table->tableSize ) ) { + // we couldn't get the table for this display for some reason + return; + } + +// Com_Printf("0x%08x %f\n", table->display, fraction); + + red = glw_state.tempTable.red; + green = glw_state.tempTable.green; + blue = glw_state.tempTable.blue; + if ( glw_state.tempTable.tableSize < tableSize ) { + glw_state.tempTable.tableSize = tableSize; + red = realloc( red, sizeof( *red ) * tableSize ); + green = realloc( green, sizeof( *green ) * tableSize ); + blue = realloc( blue, sizeof( *blue ) * tableSize ); + glw_state.tempTable.red = red; + glw_state.tempTable.green = green; + glw_state.tempTable.blue = blue; + } + + for ( gammaIndex = 0; gammaIndex < table->tableSize; gammaIndex++ ) { + red[gammaIndex] = table->red[gammaIndex] * fraction; + blue[gammaIndex] = table->blue[gammaIndex] * fraction; + green[gammaIndex] = table->green[gammaIndex] * fraction; + } + + CGSetDisplayTransferByTable( table->display, table->tableSize, red, green, blue ); +} + +// Fades all the active displays at the same time. + +#define FADE_DURATION 0.5 +void Sys_FadeScreens() { + CGDisplayCount displayIndex; + int stepIndex; + glwgamma_t *table; + NSTimeInterval start, current; + float time; + + if ( !glConfig.deviceSupportsGamma ) { + return; + } + + Com_Printf( "Fading all displays\n" ); + + start = [NSDate timeIntervalSinceReferenceDate]; + time = 0.0; + while ( time != FADE_DURATION ) { + current = [NSDate timeIntervalSinceReferenceDate]; + time = current - start; + if ( time > FADE_DURATION ) { + time = FADE_DURATION; + } + + for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) { + table = &glw_state.originalDisplayGammaTables[displayIndex]; + Sys_SetScreenFade( table, 1.0 - time / FADE_DURATION ); + } + } +} + +void Sys_FadeScreen( CGDirectDisplayID display ) { + CGDisplayCount displayIndex; + glwgamma_t *table; + int stepIndex; + + if ( !glConfig.deviceSupportsGamma ) { + return; + } + + Com_Printf( "Fading display 0x%08x\n", display ); + + for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) { + if ( display == glw_state.originalDisplayGammaTables[displayIndex].display ) { + NSTimeInterval start, current; + float time; + + start = [NSDate timeIntervalSinceReferenceDate]; + time = 0.0; + + table = &glw_state.originalDisplayGammaTables[displayIndex]; + while ( time != FADE_DURATION ) { + current = [NSDate timeIntervalSinceReferenceDate]; + time = current - start; + if ( time > FADE_DURATION ) { + time = FADE_DURATION; + } + + Sys_SetScreenFade( table, 1.0 - time / FADE_DURATION ); + } + return; + } + } + + Com_Printf( "Unable to find display to fade it\n" ); +} + +void Sys_UnfadeScreens() { + CGDisplayCount displayIndex; + int stepIndex; + glwgamma_t *table; + NSTimeInterval start, current; + float time; + + if ( !glConfig.deviceSupportsGamma ) { + return; + } + + Com_Printf( "Unfading all displays\n" ); + + start = [NSDate timeIntervalSinceReferenceDate]; + time = 0.0; + while ( time != FADE_DURATION ) { + current = [NSDate timeIntervalSinceReferenceDate]; + time = current - start; + if ( time > FADE_DURATION ) { + time = FADE_DURATION; + } + + for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) { + table = &glw_state.originalDisplayGammaTables[displayIndex]; + Sys_SetScreenFade( table, time / FADE_DURATION ); + } + } +} + +void Sys_UnfadeScreen( CGDirectDisplayID display, glwgamma_t *table ) { + CGDisplayCount displayIndex; + int stepIndex; + + if ( !glConfig.deviceSupportsGamma ) { + return; + } + + Com_Printf( "Unfading display 0x%08x\n", display ); + + if ( table ) { + CGTableCount i; + + Com_Printf( "Given table:\n" ); + for ( i = 0; i < table->tableSize; i++ ) { + Com_Printf( " %f %f %f\n", table->red[i], table->blue[i], table->green[i] ); + } + } + + // Search for the original gamma table for the display + if ( !table ) { + for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) { + if ( display == glw_state.originalDisplayGammaTables[displayIndex].display ) { + table = &glw_state.originalDisplayGammaTables[displayIndex]; + break; + } + } + } + + if ( table ) { + NSTimeInterval start, current; + float time; + + start = [NSDate timeIntervalSinceReferenceDate]; + time = 0.0; + + while ( time != FADE_DURATION ) { + current = [NSDate timeIntervalSinceReferenceDate]; + time = current - start; + if ( time > FADE_DURATION ) { + time = FADE_DURATION; + } + Sys_SetScreenFade( table, time / FADE_DURATION ); + } + return; + } + + Com_Printf( "Unable to find display to unfade it\n" ); +} + + + diff --git a/src/macosx/macosx_glimp.h b/src/macosx/macosx_glimp.h new file mode 100644 index 0000000..916b312 --- /dev/null +++ b/src/macosx/macosx_glimp.h @@ -0,0 +1,44 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include +#include +#include +#ifndef GL_EXT_abgr +#include +#endif + +// This can be defined to use the CGLMacro.h support which avoids looking up +// the current context. +//#define USE_CGLMACROS + +#ifdef USE_CGLMACROS +#include "macosx_local.h" +#define cgl_ctx glw_state._cgl_ctx +#include +#endif diff --git a/src/macosx/macosx_glimp.m b/src/macosx/macosx_glimp.m new file mode 100644 index 0000000..b45a77d --- /dev/null +++ b/src/macosx/macosx_glimp.m @@ -0,0 +1,1073 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#import +#import + +#import +#import +#import + +#import "macosx_glimp.h" + +#include "tr_local.h" + +#import "macosx_local.h" +#import "macosx_display.h" +#import "macosx_timers.h" + +cvar_t *r_allowSoftwareGL; // don't abort out if the pixelformat claims software +cvar_t *r_enablerender; // Enable actual rendering +cvar_t *r_appleTransformHint; // Enable Apple transform hint + +static void GLW_InitExtensions( void ); +static qboolean CreateGameWindow( qboolean isSecondTry ); +static unsigned long Sys_QueryVideoMemory(); + + +glwstate_t glw_state; +qboolean Sys_IsHidden = qfalse; + +#ifdef OMNI_TIMER +OTStampList glThreadStampList; +#endif + +@interface NSOpenGLContext ( CGLContextAccess ) +- (CGLContextObj) cglContext; +@end + +@implementation NSOpenGLContext ( CGLContextAccess ) +- (CGLContextObj) cglContext; +{ + return _contextAuxiliary; +} +@end + +/* +============ +CheckErrors +============ +*/ +void CheckErrors( void ) { + GLenum err; + + err = qglGetError(); + if ( err != GL_NO_ERROR ) { + ri.Error( ERR_FATAL, "glGetError: %s\n", qglGetString( err ) ); + } +} + +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + +unsigned int QGLBeginStarted = 0; + +void QGLErrorBreak( void ) { +} + +void QGLCheckError( const char *message ) { + GLenum error; + static unsigned int errorCount = 0; + + error = _glGetError(); + if ( error != GL_NO_ERROR ) { + if ( errorCount == 100 ) { + Com_Printf( "100 GL errors printed ... disabling further error reporting.\n" ); + } else if ( errorCount < 100 ) { + if ( errorCount == 0 ) { + fprintf( stderr, "BREAK ON QGLErrorBreak to stop at the GL errors\n" ); + } + fprintf( stderr, "OpenGL Error(%s): 0x%04x -- %s\n", message, (int)error, gluErrorString( error ) ); + QGLErrorBreak(); + } + errorCount++; + } +} +#endif + +/* +** GLimp_SetMode +*/ + +qboolean GLimp_SetMode( qboolean isSecondTry ) { + if ( !CreateGameWindow( isSecondTry ) ) { + ri.Printf( PRINT_ALL, "GLimp_Init: window could not be created!\n" ); + return qfalse; + } + + // draw something to show that GL is alive + if ( r_enablerender->integer ) { + qglClearColor( 0.5, 0.5, 0.7, 0 ); + qglClear( GL_COLOR_BUFFER_BIT ); + GLimp_EndFrame(); + + qglClearColor( 0.5, 0.5, 0.7, 0 ); + qglClear( GL_COLOR_BUFFER_BIT ); + GLimp_EndFrame(); + } + + Sys_UnfadeScreen( Sys_DisplayToUse(), NULL ); + + CheckErrors(); + + return qtrue; +} + +/* + ================= + GetPixelAttributes + ================= + */ + +#define ADD_ATTR( x ) \ + do { \ + if ( attributeIndex >= attributeSize ) { \ + attributeSize *= 2; \ + pixelAttributes = NSZoneRealloc( NULL, pixelAttributes, sizeof( *pixelAttributes ) * attributeSize ); \ + } \ + pixelAttributes[attributeIndex] = x; \ + attributeIndex ++; \ + if ( verbose ) { \ + ri.Printf( PRINT_ALL, "Adding pixel attribute: %d (%s)\n", x, # x ); \ + } \ + } while ( 0 ) + +static NSOpenGLPixelFormatAttribute *GetPixelAttributes() { + NSOpenGLPixelFormatAttribute *pixelAttributes; + unsigned int attributeIndex = 0; + unsigned int attributeSize = 128; + int verbose = 0; + unsigned int colorDepth; + + verbose = r_verbose->integer; + + pixelAttributes = NSZoneMalloc( NULL, sizeof( *pixelAttributes ) * attributeSize ); + + if ( r_fullscreen->integer ) { + ADD_ATTR( NSOpenGLPFAFullScreen ); + + // Since we are fullscreen, specify the screen that we need. + ADD_ATTR( NSOpenGLPFAScreenMask ); + ADD_ATTR( CGDisplayIDToOpenGLDisplayMask( Sys_DisplayToUse() ) ); + } + + // Require hardware acceleration unless otherwise directed + if ( !r_allowSoftwareGL->integer ) { + ADD_ATTR( NSOpenGLPFAAccelerated ); + } + + // Require double-buffer + ADD_ATTR( NSOpenGLPFADoubleBuffer ); + + // Specify the number of color bits. If we don't have a valid specified value or we are not full screen, use the current display mode's value. + ADD_ATTR( NSOpenGLPFAColorSize ); + colorDepth = r_colorbits->integer; + if ( colorDepth < 16 ) { + colorDepth = 16; + } else if ( colorDepth > 16 ) { + colorDepth = 32; + } + if ( !r_fullscreen->integer ) { + colorDepth = [[glw_state.desktopMode objectForKey: (id)kCGDisplayBitsPerPixel] intValue]; + } + ADD_ATTR( colorDepth ); + + // Specify the number of depth bits + ADD_ATTR( NSOpenGLPFADepthSize ); + ADD_ATTR( r_depthbits->integer ? r_depthbits->integer : 16 ); + + // Specify the number of stencil bits + if ( r_stencilbits->integer ) { + ADD_ATTR( NSOpenGLPFAStencilSize ); + ADD_ATTR( r_stencilbits->integer ); + } + + // Terminate the list + ADD_ATTR( 0 ); + + return pixelAttributes; +} + +// Needs to be visible to Q3Controller.m. +void Sys_UpdateWindowMouseInputRect( void ) { + NSRect windowRect, screenRect; + NSScreen *screen; + + // It appears we need to flip the coordinate system here. This means we need + // to know the size of the screen. + screen = [glw_state.window screen]; + screenRect = [screen frame]; + windowRect = [glw_state.window frame]; + windowRect.origin.y = screenRect.size.height - ( windowRect.origin.y + windowRect.size.height ); + + Sys_SetMouseInputRect( CGRectMake( windowRect.origin.x, windowRect.origin.y, + windowRect.size.width, windowRect.size.height ) ); +} + +// This is needed since CGReleaseAllDisplays() restores the gamma on the displays and we want to fade it up rather than just flickering all the displays +static void ReleaseAllDisplays() { + CGDisplayCount displayIndex; + + Com_Printf( "Releasing displays\n" ); + for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) { + CGDisplayRelease( glw_state.originalDisplayGammaTables[displayIndex].display ); + } +} + +/* +================= +CreateGameWindow +================= +*/ +static qboolean CreateGameWindow( qboolean isSecondTry ) { + const char *windowed[] = { "Windowed", "Fullscreen" }; + int current_mode; + NSOpenGLPixelFormatAttribute *pixelAttributes; + NSOpenGLPixelFormat *pixelFormat; + CGDisplayErr err; + + + // get mode info + current_mode = r_mode->integer; + glConfig.isFullscreen = ( r_fullscreen->integer != 0 ); + + glw_state.desktopMode = (NSDictionary *)CGDisplayCurrentMode( glw_state.display ); + if ( !glw_state.desktopMode ) { + ri.Error( ERR_FATAL, "Could not get current graphics mode for display 0x%08x\n", glw_state.display ); + } + +#if 0 + ri.Printf( PRINT_ALL, "... desktop mode %d = %dx%d %s\n", glw_state.desktopMode, + glw_state.desktopDesc.width, glw_state.desktopDesc.height, + depthStrings[glw_state.desktopDesc.depth] ); +#endif + + ri.Printf( PRINT_ALL, "...setting mode %d:\n", current_mode ); + if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect, current_mode ) ) { + ri.Printf( PRINT_ALL, " invalid mode\n" ); + return qfalse; + } + ri.Printf( PRINT_ALL, " %d %d %s\n", glConfig.vidWidth, glConfig.vidHeight, windowed[glConfig.isFullscreen] ); + + if ( glConfig.isFullscreen ) { + + // We'll set up the screen resolution first in case that effects the list of pixel + // formats that are available (for example, a smaller frame buffer might mean more + // bits for depth/stencil buffers). Allow stretched video modes if we are in fallback mode. + glw_state.gameMode = Sys_GetMatchingDisplayMode( isSecondTry ); + if ( !glw_state.gameMode ) { + ri.Printf( PRINT_ALL, "Unable to find requested display mode.\n" ); + return qfalse; + } + + // Fade all screens to black + Sys_FadeScreens(); + + err = CGCaptureAllDisplays(); + if ( err != CGDisplayNoErr ) { + CGDisplayRestoreColorSyncSettings(); + ri.Printf( PRINT_ALL, " Unable to capture displays err = %d\n", err ); + return qfalse; + } + + err = CGDisplaySwitchToMode( glw_state.display, (CFDictionaryRef)glw_state.gameMode ); + if ( err != CGDisplayNoErr ) { + CGDisplayRestoreColorSyncSettings(); + ReleaseAllDisplays(); + ri.Printf( PRINT_ALL, " Unable to set display mode, err = %d\n", err ); + return qfalse; + } + } else { + glw_state.gameMode = glw_state.desktopMode; + } + + + // Get the GL pixel format + pixelAttributes = GetPixelAttributes(); + pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes: pixelAttributes] autorelease]; + NSZoneFree( NULL, pixelAttributes ); + + if ( !pixelFormat ) { + CGDisplayRestoreColorSyncSettings(); + CGDisplaySwitchToMode( glw_state.display, (CFDictionaryRef)glw_state.desktopMode ); + ReleaseAllDisplays(); + ri.Printf( PRINT_ALL, " No pixel format found\n" ); + return qfalse; + } + + // Create a context with the desired pixel attributes + OSX_SetGLContext([[NSOpenGLContext alloc] initWithFormat : pixelFormat shareContext : nil] ); + if ( !OSX_GetNSGLContext() ) { + CGDisplayRestoreColorSyncSettings(); + CGDisplaySwitchToMode( glw_state.display, (CFDictionaryRef)glw_state.desktopMode ); + ReleaseAllDisplays(); + ri.Printf( PRINT_ALL, "... +[NSOpenGLContext createWithFormat:share:] failed.\n" ); + return qfalse; + } + + if ( !glConfig.isFullscreen ) { + cvar_t *vid_xpos; + cvar_t *vid_ypos; + NSRect windowRect; + + vid_xpos = ri.Cvar_Get( "vid_xpos", "100", CVAR_ARCHIVE ); + vid_ypos = ri.Cvar_Get( "vid_ypos", "100", CVAR_ARCHIVE ); + + // Create a window of the desired size + windowRect.origin.x = vid_xpos->integer; + windowRect.origin.y = vid_ypos->integer; + windowRect.size.width = glConfig.vidWidth; + windowRect.size.height = glConfig.vidHeight; + + glw_state.window = [[NSWindow alloc] initWithContentRect:windowRect + styleMask:NSTitledWindowMask + backing:NSBackingStoreRetained + defer:NO]; + +#warning TJW: might be nice to change the title of the window to have the version number, current server or something like that (maybe even preferences for what to put in the title). + [glw_state.window setTitle : @ "Return to Castle Wolfenstein"]; + + [glw_state.window orderFront : nil]; + + // Always get mouse moved events (if mouse support is turned off (rare) + // the event system will filter them out. + [glw_state.window setAcceptsMouseMovedEvents : YES]; + + // Direct the context to draw in this window + [OSX_GetNSGLContext() setView : [glw_state.window contentView]]; + + // Sync input rect with where the window actually is... + Sys_UpdateWindowMouseInputRect(); + } else { + CGLError err; + + err = CGLSetFullScreen( OSX_GetCGLContext() ); + if ( err ) { + CGDisplayRestoreColorSyncSettings(); + CGDisplaySwitchToMode( glw_state.display, (CFDictionaryRef)glw_state.desktopMode ); + ReleaseAllDisplays(); + Com_Printf( "CGLSetFullScreen -> %d (%s)\n", err, CGLErrorString( err ) ); + return qfalse; + } + + Sys_SetMouseInputRect( CGDisplayBounds( glw_state.display ) ); + } + + +#ifndef USE_CGLMACROS + // Make this the current context + OSX_GLContextSetCurrent(); +#endif + + // Store off the pixel format attributes that we actually got + [pixelFormat getValues : (long *) &glConfig.colorBits forAttribute : NSOpenGLPFAColorSize forVirtualScreen : 0]; + [pixelFormat getValues : (long *) &glConfig.depthBits forAttribute : NSOpenGLPFADepthSize forVirtualScreen : 0]; + [pixelFormat getValues : (long *) &glConfig.stencilBits forAttribute : NSOpenGLPFAStencilSize forVirtualScreen : 0]; + + glConfig.displayFrequency = [[glw_state.gameMode objectForKey: (id)kCGDisplayRefreshRate] intValue]; + + + ri.Printf( PRINT_ALL, "ok\n" ); + + return qtrue; +} + +// This can be used to temporarily disassociate the GL context from the screen so that CoreGraphics can be used to draw to the screen. +void Sys_PauseGL() { + if ( !glw_state.glPauseCount ) { + qglFinish(); // must do this to ensure the queue is complete + + // Have to call both to actually deallocate kernel resources and free the NSSurface + CGLClearDrawable( OSX_GetCGLContext() ); + [OSX_GetNSGLContext() clearDrawable]; + } + glw_state.glPauseCount++; +} + +// This can be used to reverse the pausing caused by Sys_PauseGL() +void Sys_ResumeGL() { + if ( glw_state.glPauseCount ) { + glw_state.glPauseCount--; + if ( !glw_state.glPauseCount ) { + if ( !glConfig.isFullscreen ) { + [OSX_GetNSGLContext() setView : [glw_state.window contentView]]; + } else { + CGLError err; + + err = CGLSetFullScreen( OSX_GetCGLContext() ); + if ( err ) { + Com_Printf( "CGLSetFullScreen -> %d (%s)\n", err, CGLErrorString( err ) ); + } + } + } + } +} + +/* +=================== +GLimp_Init + +Don't return unless OpenGL has been properly initialized +=================== +*/ + +static void GLImp_Toggle_Renderer_f( void ) { + ri.Cvar_Set( "r_enablerender", r_enablerender->integer ? "0" : "1" ); +} + +#ifdef OMNI_TIMER +static void GLImp_Dump_Stamp_List_f( void ) { + OTStampListDumpToFile( glThreadStampList, "/tmp/gl_stamps" ); +} +#endif + +void GLimp_Init( void ) { + static BOOL addedCommands = NO; + cvar_t *lastValidRenderer = ri.Cvar_Get( "r_lastValidRenderer", "(uninitialized)", CVAR_ARCHIVE ); + char *buf; + + if ( !addedCommands ) { + addedCommands = YES; + +#ifdef OMNI_TIMER + glThreadStampList = OTStampListCreate( 64 ); + Cmd_AddCommand( "dump_stamp_list", GLImp_Dump_Stamp_List_f ); +#endif + Cmd_AddCommand( "toggle_renderer", GLImp_Toggle_Renderer_f ); + } + + ri.Printf( PRINT_ALL, "Initializing OpenGL subsystem\n" ); + ri.Printf( PRINT_ALL, " Last renderer was '%s'\n", lastValidRenderer->string ); + ri.Printf( PRINT_ALL, " r_fullscreen = %d\n", r_fullscreen->integer ); + + memset( &glConfig, 0, sizeof( glConfig ) ); + + // We only allow changing the gamma if we are full screen + glConfig.deviceSupportsGamma = ( r_fullscreen->integer != 0 ); + if ( glConfig.deviceSupportsGamma ) { + Sys_StoreGammaTables(); + } + + r_allowSoftwareGL = ri.Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH ); + r_enablerender = ri.Cvar_Get( "r_enablerender", "1", 0 ); + + if ( Sys_QueryVideoMemory() == 0 && !r_allowSoftwareGL->integer ) { + ri.Error( ERR_FATAL, "Could not initialize OpenGL. There does not appear to be an OpenGL-supported video card in your system.\n" ); + } + + if ( !GLimp_SetMode( qfalse ) ) { + // fall back to the known-good mode + ri.Cvar_Set( "r_fullscreen", "1" ); + ri.Cvar_Set( "r_mode", "3" ); + ri.Cvar_Set( "r_stereo", "0" ); + ri.Cvar_Set( "r_depthBits", "16" ); + ri.Cvar_Set( "r_colorBits", "16" ); + ri.Cvar_Set( "r_stencilBits", "0" ); + if ( GLimp_SetMode( qtrue ) ) { + ri.Printf( PRINT_ALL, "------------------\n" ); + return; + } + + ri.Error( ERR_FATAL, "Could not initialize OpenGL\n" ); + return; + } + + ri.Printf( PRINT_ALL, "------------------\n" ); + + // get our config strings + Q_strncpyz( glConfig.vendor_string, (const char *)qglGetString( GL_VENDOR ), sizeof( glConfig.vendor_string ) ); + Q_strncpyz( glConfig.renderer_string, (const char *)qglGetString( GL_RENDERER ), sizeof( glConfig.renderer_string ) ); + Q_strncpyz( glConfig.version_string, (const char *)qglGetString( GL_VERSION ), sizeof( glConfig.version_string ) ); + Q_strncpyz( glConfig.extensions_string, (const char *)qglGetString( GL_EXTENSIONS ), sizeof( glConfig.extensions_string ) ); + + // + // chipset specific configuration + // + buf = malloc( strlen( glConfig.renderer_string ) + 1 ); + strcpy( buf, glConfig.renderer_string ); + Q_strlwr( buf ); + + ri.Cvar_Set( "r_lastValidRenderer", glConfig.renderer_string ); + free( buf ); + + GLW_InitExtensions(); + +#ifndef USE_CGLMACROS + if ( !r_enablerender->integer ) { + OSX_GLContextClearCurrent(); + } +#endif +} + + +/* +** GLimp_EndFrame +** +** Responsible for doing a swapbuffers and possibly for other stuff +** as yet to be determined. Probably better not to make this a GLimp +** function and instead do a call to GLimp_SwapBuffers. +*/ +void GLimp_EndFrame( void ) { + GLSTAMP( "GLimp_EndFrame start", 0 ); + + // + // swapinterval stuff + // + if ( r_swapInterval->modified ) { + r_swapInterval->modified = qfalse; + + if ( !glConfig.stereoEnabled ) { // why? + [[NSOpenGLContext currentContext] setValues : (long *)&r_swapInterval->integer +forParameter: NSOpenGLCPSwapInterval]; + } + } + +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + QGLCheckError( "GLimp_EndFrame" ); +#endif + + if ( !glw_state.glPauseCount && !Sys_IsHidden ) { + glw_state.bufferSwapCount++; + [OSX_GetNSGLContext() flushBuffer]; + } + + // Enable turning off GL at any point for performance testing + if ( OSX_GLContextIsCurrent() != r_enablerender->integer ) { + if ( r_enablerender->integer ) { + Com_Printf( "--- Enabling Renderer ---\n" ); + OSX_GLContextSetCurrent(); + } else { + Com_Printf( "--- Disabling Renderer ---\n" ); + OSX_GLContextClearCurrent(); + } + } + + GLSTAMP( "GLimp_EndFrame end", 0 ); +} + +/* +** GLimp_Shutdown +** +** This routine does all OS specific shutdown procedures for the OpenGL +** subsystem. Under OpenGL this means NULLing out the current DC and +** HGLRC, deleting the rendering context, and releasing the DC acquired +** for the window. The state structure is also nulled out. +** +*/ + +static void _GLimp_RestoreOriginalVideoSettings() { + CGDisplayErr err; + + // CGDisplayCurrentMode lies because we've captured the display and thus we won't + // get any notifications about what the current display mode really is. For now, + // we just always force it back to what mode we remember the desktop being in. + if ( glConfig.isFullscreen ) { + err = CGDisplaySwitchToMode( glw_state.display, (CFDictionaryRef)glw_state.desktopMode ); + if ( err != CGDisplayNoErr ) { + ri.Printf( PRINT_ALL, " Unable to restore display mode!\n" ); + } + + ReleaseAllDisplays(); + } +} + +void GLimp_Shutdown( void ) { + CGDisplayCount displayIndex; + + Com_Printf( "----- Shutting down GL -----\n" ); + + Sys_FadeScreen( Sys_DisplayToUse() ); + + if ( OSX_GetNSGLContext() ) { +#ifndef USE_CGLMACROS + OSX_GLContextClearCurrent(); +#endif + // Have to call both to actually deallocate kernel resources and free the NSSurface + CGLClearDrawable( OSX_GetCGLContext() ); + [OSX_GetNSGLContext() clearDrawable]; + + [OSX_GetNSGLContext() release]; + OSX_SetGLContext( (id)nil ); + } + + _GLimp_RestoreOriginalVideoSettings(); + + Sys_UnfadeScreens(); + + // Restore the original gamma if needed. + if ( glConfig.deviceSupportsGamma ) { + Com_Printf( "Restoring ColorSync settings\n" ); + CGDisplayRestoreColorSyncSettings(); + } + + if ( glw_state.window ) { + [glw_state.window release]; + glw_state.window = nil; + } + + if ( glw_state.log_fp ) { + fclose( glw_state.log_fp ); + glw_state.log_fp = 0; + } + + for ( displayIndex = 0; displayIndex < glw_state.displayCount; displayIndex++ ) { + free( glw_state.originalDisplayGammaTables[displayIndex].red ); + free( glw_state.originalDisplayGammaTables[displayIndex].blue ); + free( glw_state.originalDisplayGammaTables[displayIndex].green ); + } + free( glw_state.originalDisplayGammaTables ); + if ( glw_state.tempTable.red ) { + free( glw_state.tempTable.red ); + free( glw_state.tempTable.blue ); + free( glw_state.tempTable.green ); + } + if ( glw_state.inGameTable.red ) { + free( glw_state.inGameTable.red ); + free( glw_state.inGameTable.blue ); + free( glw_state.inGameTable.green ); + } + + memset( &glConfig, 0, sizeof( glConfig ) ); + memset( &glState, 0, sizeof( glState ) ); + memset( &glw_state, 0, sizeof( glw_state ) ); + + Com_Printf( "----- Done shutting down GL -----\n" ); +} + +/* +=============== +GLimp_LogComment + +=============== +*/ +void GLimp_LogComment( char *comment ) { +} + +/* +=============== +GLimp_SetGamma + +=============== +*/ +void GLimp_SetGamma( unsigned char red[256], + unsigned char green[256], + unsigned char blue[256] ) { + CGGammaValue redGamma[256], greenGamma[256], blueGamma[256]; + CGTableCount i; + CGDisplayErr err; + + if ( !glConfig.deviceSupportsGamma ) { + return; + } + + for ( i = 0; i < 256; i++ ) { + redGamma[i] = red[i] / 255.0; + greenGamma[i] = green[i] / 255.0; + blueGamma[i] = blue[i] / 255.0; + } + + err = CGSetDisplayTransferByTable( glw_state.display, 256, redGamma, greenGamma, blueGamma ); + if ( err != CGDisplayNoErr ) { + Com_Printf( "GLimp_SetGamma: CGSetDisplayTransferByByteTable returned %d.\n", err ); + } + + // Store the gamma table that we ended up using so we can reapply it later when unhiding or to work around the bug where if you leave the game sitting and the monitor sleeps, when it wakes, the gamma isn't reset. + glw_state.inGameTable.display = glw_state.display; + Sys_GetGammaTable( &glw_state.inGameTable ); +} + +qboolean GLimp_ChangeMode( int mode ) { + qboolean result; + int oldvalue = r_mode->integer; + + Com_Printf( "*** GLimp_ChangeMode\n" ); + r_mode->integer = mode; + if ( !( result = GLimp_SetMode( qfalse ) ) ) { + r_mode->integer = oldvalue; + } + + return result; +} + +/*****************************************************************************/ + +void *qwglGetProcAddress( const char *name ) { + NSSymbol symbol; + char *symbolName; + + // Prepend a '_' for the Unix C symbol mangling convention + symbolName = malloc( strlen( name ) + 2 ); + strcpy( symbolName + 1, name ); + symbolName[0] = '_'; + + if ( NSIsSymbolNameDefined( symbolName ) ) { + symbol = NSLookupAndBindSymbol( symbolName ); + } else { + symbol = NULL; + } + + free( symbolName ); + + if ( !symbol ) { + // shouldn't happen ... + return NULL; + } + + return NSAddressOfSymbol( symbol ); +} + +/* +** GLW_InitExtensions +*/ +static void GLW_InitExtensions( void ) { + if ( !r_allowExtensions->integer ) { + ri.Printf( PRINT_ALL, "*** IGNORING OPENGL EXTENSIONS ***\n" ); + return; + } + + ri.Printf( PRINT_ALL, "Initializing OpenGL extensions\n" ); + ri.Printf( PRINT_ALL, "... Supported extensions are %s\n", glConfig.extensions_string ); + + // GL_S3_s3tc + glConfig.textureCompression = TC_NONE; + if ( strstr( glConfig.extensions_string, "GL_S3_s3tc" ) ) { + if ( r_ext_compressed_textures->integer ) { + glConfig.textureCompression = TC_S3TC; + ri.Printf( PRINT_ALL, "...using GL_S3_s3tc\n" ); + } else + { + glConfig.textureCompression = TC_NONE; + ri.Printf( PRINT_ALL, "...ignoring GL_S3_s3tc\n" ); + } + } else + { + ri.Printf( PRINT_ALL, "...GL_S3_s3tc not found\n" ); + } + + +#ifdef GL_EXT_texture_env_add + // GL_EXT_texture_env_add + glConfig.textureEnvAddAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "GL_EXT_texture_env_add" ) ) { + if ( r_ext_texture_env_add->integer ) { + glConfig.textureEnvAddAvailable = qtrue; + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_env_add\n" ); + } else + { + glConfig.textureEnvAddAvailable = qfalse; + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_env_add\n" ); + } + } else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_env_add not found\n" ); + } +#endif + +#ifdef GL_ARB_texture_env_add + // GL_ARB_texture_env_add -- only if we didn't find GL_EXT_texture_env_add + if ( !glConfig.textureEnvAddAvailable ) { + if ( strstr( glConfig.extensions_string, "GL_ARB_texture_env_add" ) ) { + if ( r_ext_texture_env_add->integer ) { + glConfig.textureEnvAddAvailable = qtrue; + ri.Printf( PRINT_ALL, "...using GL_ARB_texture_env_add\n" ); + } else + { + glConfig.textureEnvAddAvailable = qfalse; + ri.Printf( PRINT_ALL, "...ignoring GL_ARB_texture_env_add\n" ); + } + } else + { + ri.Printf( PRINT_ALL, "...GL_ARB_texture_env_add not found\n" ); + } + } +#endif + + +#if 0 // Win32 does this differently than we do -- I'll provide a C function that looks the same + // that will do the correct ObjC stuff + // WGL_EXT_swap_control + qwglSwapIntervalEXT = ( BOOL ( WINAPI * )(int) )qwglGetProcAddress( "wglSwapIntervalEXT" ); + if ( qwglSwapIntervalEXT ) { + ri.Printf( PRINT_ALL, "...using WGL_EXT_swap_control\n" ); + r_swapInterval->modified = qtrue; // force a set next frame + } else + { + ri.Printf( PRINT_ALL, "...WGL_EXT_swap_control not found\n" ); + } +#else + if ( r_swapInterval ) { + ri.Printf( PRINT_ALL, "...using +[NSOpenGLContext setParameter:] for qwglSwapIntervalEXT\n" ); + r_swapInterval->modified = qtrue; // force a set next frame + } +#endif + + // GL_ARB_multitexture + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + if ( strstr( glConfig.extensions_string, "GL_ARB_multitexture" ) ) { + if ( r_ext_multitexture->integer ) { + qglMultiTexCoord2fARB = ( PFNGLMULTITEXCOORD2FARBPROC ) qwglGetProcAddress( "glMultiTexCoord2fARB" ); + qglActiveTextureARB = ( PFNGLACTIVETEXTUREARBPROC ) qwglGetProcAddress( "glActiveTextureARB" ); + qglClientActiveTextureARB = ( PFNGLCLIENTACTIVETEXTUREARBPROC ) qwglGetProcAddress( "glClientActiveTextureARB" ); + + if ( qglActiveTextureARB ) { + qglGetIntegerv( GL_MAX_ACTIVE_TEXTURES_ARB, (GLint *)&glConfig.maxActiveTextures ); + + if ( glConfig.maxActiveTextures > 1 ) { + ri.Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" ); + } else + { + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + ri.Printf( PRINT_ALL, "...not using GL_ARB_multitexture, < 2 texture units\n" ); + } + } + } else + { + ri.Printf( PRINT_ALL, "...ignoring GL_ARB_multitexture\n" ); + } + } else + { + ri.Printf( PRINT_ALL, "...GL_ARB_multitexture not found\n" ); + } + + // GL_EXT_compiled_vertex_array + qglLockArraysEXT = NULL; + qglUnlockArraysEXT = NULL; + if ( strstr( glConfig.extensions_string, "GL_EXT_compiled_vertex_array" ) && ( glConfig.hardwareType != GLHW_RIVA128 ) ) { + if ( r_ext_compiled_vertex_array->integer ) { + ri.Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" ); + qglLockArraysEXT = ( void ( APIENTRY * )( GLint, GLint ) )qwglGetProcAddress( "glLockArraysEXT" ); + qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) )qwglGetProcAddress( "glUnlockArraysEXT" ); + if ( !qglLockArraysEXT || !qglUnlockArraysEXT ) { + ri.Error( ERR_FATAL, "bad getprocaddress\n" ); + } + } else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n" ); + } + } else + { + ri.Printf( PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n" ); + } + +#ifdef GL_APPLE_transform_hint + if ( strstr( glConfig.extensions_string, "GL_APPLE_transform_hint" ) ) { + r_appleTransformHint = ri.Cvar_Get( "r_appleTransformHint", "1", CVAR_ARCHIVE ); + if ( r_appleTransformHint->value ) { + ri.Printf( PRINT_ALL, "...using GL_APPLE_transform_hint\n" ); + qglHint( GL_TRANSFORM_HINT_APPLE, GL_FASTEST ); + CheckErrors(); + } else { + ri.Printf( PRINT_ALL, "...ignoring using GL_APPLE_transform_hint\n" ); + } + } else { + ri.Printf( PRINT_ALL, "...GL_APPLE_transform_hint not found\n" ); + } +#endif +} + + +#define MAX_RENDERER_INFO_COUNT 128 + +// Returns zero if there are no hardware renderers. Otherwise, returns the max memory across all renderers (on the presumption that the screen that we'll use has the most memory). +static unsigned long Sys_QueryVideoMemory() { + CGLError err; + CGLRendererInfoObj rendererInfo, rendererInfos[MAX_RENDERER_INFO_COUNT]; + long rendererInfoIndex, rendererInfoCount = MAX_RENDERER_INFO_COUNT; + long rendererIndex, rendererCount; + long maxVRAM = 0, vram; + long accelerated; + long rendererID; + long totalRenderers = 0; + + err = CGLQueryRendererInfo( CGDisplayIDToOpenGLDisplayMask( Sys_DisplayToUse() ), rendererInfos, &rendererInfoCount ); + if ( err ) { + Com_Printf( "CGLQueryRendererInfo -> %d\n", err ); + return vram; + } + + //Com_Printf("rendererInfoCount = %d\n", rendererInfoCount); + for ( rendererInfoIndex = 0; rendererInfoIndex < rendererInfoCount && totalRenderers < rendererInfoCount; rendererInfoIndex++ ) { + rendererInfo = rendererInfos[rendererInfoIndex]; + //Com_Printf("rendererInfo: 0x%08x\n", rendererInfo); + + + err = CGLDescribeRenderer( rendererInfo, 0, kCGLRPRendererCount, &rendererCount ); + if ( err ) { + Com_Printf( "CGLDescribeRenderer(kCGLRPRendererID) -> %d\n", err ); + continue; + } + //Com_Printf(" rendererCount: %d\n", rendererCount); + + for ( rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++ ) { + totalRenderers++; + //Com_Printf(" rendererIndex: %d\n", rendererIndex); + + rendererID = 0xffffffff; + err = CGLDescribeRenderer( rendererInfo, rendererIndex, kCGLRPRendererID, &rendererID ); + if ( err ) { + Com_Printf( "CGLDescribeRenderer(kCGLRPRendererID) -> %d\n", err ); + continue; + } + //Com_Printf(" rendererID: 0x%08x\n", rendererID); + + accelerated = 0; + err = CGLDescribeRenderer( rendererInfo, rendererIndex, kCGLRPAccelerated, &accelerated ); + if ( err ) { + Com_Printf( "CGLDescribeRenderer(kCGLRPAccelerated) -> %d\n", err ); + continue; + } + //Com_Printf(" accelerated: %d\n", accelerated); + if ( !accelerated ) { + continue; + } + + vram = 0; + err = CGLDescribeRenderer( rendererInfo, rendererIndex, kCGLRPVideoMemory, &vram ); + if ( err ) { + Com_Printf( "CGLDescribeRenderer -> %d\n", err ); + continue; + } + //Com_Printf(" vram: 0x%08x\n", vram); + + // presumably we'll be running on the best card, so we'll take the max of the vrams + if ( vram > maxVRAM ) { + maxVRAM = vram; + } + } + +#if 0 + err = CGLDestroyRendererInfo( rendererInfo ); + if ( err ) { + Com_Printf( "CGLDestroyRendererInfo -> %d\n", err ); + } +#endif + } + + return maxVRAM; +} + + +// We will set the Sys_IsHidden global to cause input to be handle differently (we'll just let NSApp handle events in this case). We also will unbind the GL context and restore the video mode. +qboolean Sys_Hide() { + if ( Sys_IsHidden ) { + // Eh? + return qfalse; + } + + if ( !r_fullscreen->integer ) { + // We only support hiding in fullscreen mode right now + return qfalse; + } + + Sys_IsHidden = qtrue; + + // Don't need to store the current gamma since we always keep it around in glw_state.inGameTable. + + Sys_FadeScreen( Sys_DisplayToUse() ); + + // Disassociate the GL context from the screen + // Have to call both to actually deallocate kernel resources and free the NSSurface + CGLClearDrawable( OSX_GetCGLContext() ); + [OSX_GetNSGLContext() clearDrawable]; + + // Restore the original video mode + _GLimp_RestoreOriginalVideoSettings(); + + // Restore the original gamma if needed. + if ( glConfig.deviceSupportsGamma ) { + CGDisplayRestoreColorSyncSettings(); + } + + // Release the screen(s) + ReleaseAllDisplays(); + + Sys_UnfadeScreens(); + + // Shut down the input system so the mouse and keyboard settings are restore to normal + Sys_ShutdownInput(); + + // Hide the application so that when the user clicks on our app icon, we'll get an unhide notification + [NSApp hide : nil]; + + return qtrue; +} + +qboolean Sys_Unhide() { + CGDisplayErr err; + CGLError glErr; + + if ( !Sys_IsHidden ) { + // Eh? + return qfalse; + } + + Sys_FadeScreens(); + + // Capture the screen(s) + err = CGCaptureAllDisplays(); + if ( err != CGDisplayNoErr ) { + Sys_UnfadeScreens(); + ri.Printf( PRINT_ALL, "Unhide failed -- cannot capture the display again.\n" ); + return qfalse; + } + + // Restore the game mode + err = CGDisplaySwitchToMode( glw_state.display, (CFDictionaryRef)glw_state.gameMode ); + if ( err != CGDisplayNoErr ) { + ReleaseAllDisplays(); + Sys_UnfadeScreens(); + ri.Printf( PRINT_ALL, "Unhide failed -- Unable to set display mode\n" ); + return qfalse; + } + + // Reassociate the GL context and the screen + glErr = CGLSetFullScreen( OSX_GetCGLContext() ); + if ( err ) { + ReleaseAllDisplays(); + Sys_UnfadeScreens(); + ri.Printf( PRINT_ALL, "Unhide failed: CGLSetFullScreen -> %d (%s)\n", err, CGLErrorString( err ) ); + return qfalse; + } + + // Restore the current context + [OSX_GetNSGLContext() makeCurrentContext]; + + // Restore the gamma that the game had set + Sys_UnfadeScreen( Sys_DisplayToUse(), &glw_state.inGameTable ); + + // Restore the input system (last so if something goes wrong we don't eat the mouse) + Sys_InitInput(); + + Sys_IsHidden = qfalse; + return qtrue; +} + + diff --git a/src/macosx/macosx_glsmp_mutex.m b/src/macosx/macosx_glsmp_mutex.m new file mode 100644 index 0000000..6bd7ba9 --- /dev/null +++ b/src/macosx/macosx_glsmp_mutex.m @@ -0,0 +1,178 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#import +#import +#import + +#import "macosx_glimp.h" + +#include "tr_local.h" +#import "macosx_local.h" +#import "macosx_display.h" + +// +// The main Q3 SMP API +// + +static pthread_mutex_t smpMutex; +static pthread_cond_t mainThreadCondition; +static pthread_cond_t renderThreadCondition; + +static volatile qboolean smpDataChanged; +static volatile void *smpData; + + +static void *GLimp_RenderThreadWrapper( void *arg ) { + Com_Printf( "Render thread starting\n" ); + + ( ( void( * ) () )arg )(); + +#ifndef USE_CGLMACROS + // Unbind the context before we die + OSX_GLContextClearCurrent(); +#endif + + Com_Printf( "Render thread terminating\n" ); + + return arg; +} + +qboolean GLimp_SpawnRenderThread( void ( *function )( void ) ) { + pthread_t renderThread; + int rc; + + pthread_mutex_init( &smpMutex, NULL ); + pthread_cond_init( &mainThreadCondition, NULL ); + pthread_cond_init( &renderThreadCondition, NULL ); + + rc = pthread_create( &renderThread, NULL, GLimp_RenderThreadWrapper, function ); + if ( rc ) { + ri.Printf( PRINT_ALL, "pthread_create returned %d: %s", rc, strerror( rc ) ); + return qfalse; + } else { + rc = pthread_detach( renderThread ); + if ( rc ) { + ri.Printf( PRINT_ALL, "pthread_detach returned %d: %s", rc, strerror( rc ) ); + } + } + + return qtrue; +} + +// Called in the rendering thread to wait until a command buffer is ready. +// The command buffer returned might be NULL, indicating that the rendering thread should exit. +void *GLimp_RendererSleep( void ) { + void *data; + + GLSTAMP( "GLimp_RendererSleep start", 0 ); + +#ifndef USE_CGLMACROS + // Clear the current context while we sleep so the main thread can access it + OSX_GLContextClearCurrent(); +#endif + + pthread_mutex_lock( &smpMutex ); { + // Clear out any data we had and signal the main thread that we are no longer busy + smpData = NULL; + smpDataChanged = qfalse; + pthread_cond_signal( &mainThreadCondition ); + + // Wait until we get something new to work on + while ( !smpDataChanged ) + pthread_cond_wait( &renderThreadCondition, &smpMutex ); + + // Record the data (if any). + data = smpData; + } pthread_mutex_unlock( &smpMutex ); + +#ifndef USE_CGLMACROS + // We are going to render a frame... retake the context + OSX_GLContextSetCurrent(); +#endif + + GLSTAMP( "GLimp_RendererSleep end", 0 ); + + return (void *)data; +} + +// Called from the main thread to wait until the rendering thread is done with the command buffer. +void GLimp_FrontEndSleep( void ) { + GLSTAMP( "GLimp_FrontEndSleep start", 0 ); + + pthread_mutex_lock( &smpMutex ); { + while ( smpData ) { +#if 0 + struct timespec ts; + int result; + + ts.tv_sec = 1; + ts.tv_nsec = 0; + result = pthread_cond_timedwait_relative_np( &mainThreadCondition, &smpMutex, &ts ); + if ( result ) { + Com_Printf( "GLimp_FrontEndSleep timed out. Probably due to R_SyncRenderThread called due to Com_Error being called\n" ); + break; + } +#else + pthread_cond_wait( &mainThreadCondition, &smpMutex ); +#endif + } + } pthread_mutex_unlock( &smpMutex ); + + +#ifndef USE_CGLMACROS + // We are done waiting for the background thread, take the current context back. + OSX_GLContextSetCurrent(); +#endif + + GLSTAMP( "GLimp_FrontEndSleep end", 0 ); +} + +// This is called in the main thread to issue another command +// buffer to the rendering thread. This is always called AFTER +// GLimp_FrontEndSleep, so we know that there is no command +// pending in 'smpData'. +void GLimp_WakeRenderer( void *data ) { + GLSTAMP( "GLimp_WakeRenderer start", data ); + +#ifndef USE_CGLMACROS + // We want the background thread to draw stuff. Give up the current context + OSX_GLContextClearCurrent(); +#endif + + pthread_mutex_lock( &smpMutex ); { + // Store the new data pointer and wake up the rendering thread + assert( smpData == NULL ); + smpData = data; + smpDataChanged = qtrue; + pthread_cond_signal( &renderThreadCondition ); + } pthread_mutex_unlock( &smpMutex ); + + GLSTAMP( "GLimp_WakeRenderer end", data ); +} + diff --git a/src/macosx/macosx_glsmp_null.m b/src/macosx/macosx_glsmp_null.m new file mode 100644 index 0000000..35a9faa --- /dev/null +++ b/src/macosx/macosx_glsmp_null.m @@ -0,0 +1,49 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#import "macosx_glimp.h" + +#include "tr_local.h" +#import "macosx_local.h" + +qboolean GLimp_SpawnRenderThread( void ( *function )( void ) ) { + return qfalse; +} + +void *GLimp_RendererSleep( void ) { + return NULL; +} + +void GLimp_FrontEndSleep( void ) { + +} + +void GLimp_WakeRenderer( void *data ) { + +} + diff --git a/src/macosx/macosx_glsmp_ports.m b/src/macosx/macosx_glsmp_ports.m new file mode 100644 index 0000000..6015582 --- /dev/null +++ b/src/macosx/macosx_glsmp_ports.m @@ -0,0 +1,425 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#import "macosx_glimp.h" + +#include "tr_local.h" +#import "macosx_local.h" +#import "macosx_display.h" + +#import +#import +#import +#import + + +#warning Using Mach Ports SMP acceleration implementation + +/* +=========================================================== + +SMP acceleration + +=========================================================== +*/ + +#import + +#define USE_MACH_PORTS 1 + +// This is a small cover layer that makes for easier calling + +typedef struct _MsgPort { +#if USE_MACH_PORTS + mach_port_t port; + id nsPort; +#else + pthread_mutex_t mutex; + pthread_cond_t condition; + volatile unsigned int status; + unsigned int msgCode; + void *msgData; +#endif +} MsgPort; + +static BOOL portsInited = NO; +static pthread_mutex_t logMutex; + +static unsigned int renderMsgOutstanding; +static unsigned int rendererProcessingCommand; + +static MsgPort rendererMsgPort; +static MsgPort frontEndMsgPort; + +enum { + MsgNone, + MsgPending, +}; + +enum { + MsgCodeInvalid = 0, + RenderCommandMsg = 1, + RenderCompletedMsg = 2, +}; + +static /*inline*/ void MsgPortInit( MsgPort *port ) { +#if USE_MACH_PORTS + port->nsPort = [[NSMachPort alloc] init]; + port->port = [port->nsPort machPort]; + + //rc = mach_port_allocate(mach_task_self(), MACH_PORT_TYPE_SEND_RECEIVE, &port->port); + //if (rc) { + // fprintf(stderr, "MsgPortInit: mach_port_allocate returned: %d: %s \n",rc, mach_error_string(rc)); + // } +#else + int rc; + rc = pthread_mutex_init( &port->mutex, NULL ); + if ( rc ) { + ri.Printf( PRINT_ALL, "MsgPortInit: pthread_mutex_init returned: %d: %s\n", rc, strerror( rc ) ); + } + rc = pthread_cond_init( &port->condition, NULL ); + if ( rc ) { + ri.Printf( PRINT_ALL, "EventInit: pthread_cond_init returned %d: %s\n", rc, strerror( rc ) ); + } + port->status = MsgNone; + port->msgCode = MsgCodeInvalid; + port->msgData = NULL; +#endif +} + +static /*inline*/ void _SendMsg( MsgPort *port, unsigned int msgCode, void *msgData, + const char *functionName, const char *portName, const char *msgName ) { + int rc; + +#if USE_MACH_PORTS + mach_msg_header_t msg; + + //printf("SendMsg: %s %s %s (%d %08lx)\n",functionName, portName, msgName, msgCode, msgData); +/* + typedef struct + { + mach_msg_bits_t msgh_bits; + mach_msg_size_t msgh_size; + mach_port_t msgh_remote_port; + mach_port_t msgh_local_port; + mach_msg_size_t msgh_reserved; + mach_msg_id_t msgh_id; + } mach_msg_header_t; +*/ + msg.msgh_bits = MACH_MSGH_BITS( MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE ); + msg.msgh_size = sizeof( msg ); + //msg.msg_type=MSG_TYPE_NORMAL; + msg.msgh_local_port = MACH_PORT_NULL; + msg.msgh_remote_port = port->port; + msg.msgh_reserved = 0; + msg.msgh_id = (mach_msg_id_t)msgData; // HACK + + rc = mach_msg_send( &msg ); + if ( rc ) { + fprintf( stderr,"SendMsg: mach_msg_send returned %d: %s\n", rc, mach_error_string( rc ) ); + } +#else + //printf("SendMsg: %s %s %s (%d %08lx)\n",functionName, portName, msgName, msgCode, msgData); + rc = pthread_mutex_lock( &port->mutex ); + if ( rc ) { + fprintf( stderr,"SendMsg: pthread_mutex_lock returned %d: %s\n", rc, strerror( rc ) ); + } + + /* Block until port is empty */ + while ( port->status != MsgNone ) { + //fprintf(stderr, "SendMsg: %s blocking until port %s is empty\n", functionName, portName); + rc = pthread_cond_wait( &port->condition, &port->mutex ); + if ( rc ) { + fprintf( stderr, "SendMsg: pthread_cond_wait returned %d: %s\n", rc, strerror( rc ) ); + } + } + + /* Queue msg */ + port->msgCode = msgCode; + port->msgData = msgData; + port->status = MsgPending; + + /* Unlock port */ + rc = pthread_mutex_unlock( &port->mutex ); + if ( rc ) { + fprintf( stderr, "SendMsg: pthread_mutex_unlock returned %d: %s\n", rc, strerror( rc ) ); + } + + /* Wake up any threads blocked waiting for a message */ + rc = pthread_cond_broadcast( &port->condition ); + if ( rc ) { + fprintf( stderr, "SendMsg: pthread_cond_broadcast returned %d: %s\n", rc, strerror( rc ) ); + } +#endif +} + +static /*inline*/ void _WaitMsg( MsgPort *port, unsigned int *msgCode, void **msgData, + const char *functionName, const char *portName ) { + int rc; +#if USE_MACH_PORTS + mach_msg_empty_rcv_t msg; + + //printf("WaitMsg: %s %s\n",functionName, portName); + + msg.header.msgh_bits = MACH_MSGH_BITS( MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE ); + msg.header.msgh_size = sizeof( msg ); + //msg.msg_type=MSG_TYPE_NORMAL; + msg.header.msgh_local_port = port->port; + msg.header.msgh_remote_port = MACH_PORT_NULL; + msg.header.msgh_reserved = 0; + msg.header.msgh_id = (mach_msg_id_t)msgData; // HACK + + rc = mach_msg_receive( &msg.header ); + if ( rc ) { + fprintf( stderr,"SendMsg: mach_msg_receive returned %d: %s\n", rc, mach_error_string( rc ) ); + } + + *msgData = (void *)msg.header.msgh_id; + //printf("WaitMsg: %s %s got %08lx\n",functionName, portName, *msgData); +#else + //printf("WaitMsg: %s %s\n",functionName, portName); + + rc = pthread_mutex_lock( &port->mutex ); + if ( rc ) { + fprintf( stderr, "WaitMsg: pthread_mutex_lock returned %d: %s\n", rc, strerror( rc ) ); + } + + /* Block until port is empty */ + while ( port->status != MsgPending ) { + rc = pthread_cond_wait( &port->condition, &port->mutex ); + if ( rc ) { + fprintf( stderr, "WaitMsg: pthread_cond_wait returned %d: %s\n", rc, strerror( rc ) ); + } + } + + /* Remove msg */ + *msgCode = port->msgCode; + *msgData = port->msgData; + + //printf("WaitMsg: %s %s got %d %08lx\n",functionName, portName, *msgCode, *msgData); + + port->status = MsgNone; + port->msgCode = 0; + port->msgData = NULL; + + rc = pthread_mutex_unlock( &port->mutex ); + if ( rc ) { + fprintf( stderr, "WaitMsg: pthread_mutex_unlock returned %d: %s\n", rc, strerror( rc ) ); + } + + /* Wake up any threads blocked waiting for port to be empty. */ + rc = pthread_cond_broadcast( &port->condition ); + if ( rc ) { + fprintf( stderr, "SendMsg: pthread_cond_broadcast returned %d: %s\n", rc, strerror( rc ) ); + } +#endif +} + + +#define SendMsg( p, c, d ) _SendMsg( p, c, d, __PRETTY_FUNCTION__, # p, # c ) +#define WaitMsg( p, c, d ) _WaitMsg( p, c, d, __PRETTY_FUNCTION__, # p ) + +#if 0 +static void _Log( const char *msg ) { + int rc; + + rc = pthread_mutex_lock( &logMutex ); + if ( rc ) { + ri.Printf( PRINT_ALL, "_Log: pthread_mutex_lock returned %d: %s\n", rc, strerror( rc ) ); + } + + fputs( msg,stderr ); + fflush( stderr ); + + rc = pthread_mutex_unlock( &logMutex ); + if ( rc ) { + ri.Printf( PRINT_ALL, "_Log: pthread_mutex_unlock returned %d: %s\n", rc, strerror( rc ) ); + } +} +#endif + + +// +// The main Q3 SMP API +// + +static void ( *glimpRenderThread )( void ) = NULL; + +static void *GLimp_RenderThreadWrapper( void *arg ) { + Com_Printf( "Render thread starting\n" ); + + glimpRenderThread(); + +#ifndef USE_CGLMACROS + // Unbind the context before we die + OSX_GLContextClearCurrent(); +#endif + + // Send one last message back to front end before we die... + // This is somewhat of a hack.. fixme. + if ( rendererProcessingCommand ) { + SendMsg( &frontEndMsgPort, RenderCompletedMsg, NULL ); + rendererProcessingCommand = NO; + } + + Com_Printf( "Render thread terminating\n" ); + + return arg; +} + +qboolean GLimp_SpawnRenderThread( void ( *function )( void ) ) { + pthread_t renderThread; + int rc; + + if ( !portsInited ) { + portsInited = YES; + MsgPortInit( &rendererMsgPort ); + MsgPortInit( &frontEndMsgPort ); + renderMsgOutstanding = NO; + rendererProcessingCommand = NO; + pthread_mutex_init( &logMutex, NULL ); + } + + glimpRenderThread = function; + + rc = pthread_create( &renderThread, + NULL, // attributes + GLimp_RenderThreadWrapper, + NULL ); // argument + if ( rc ) { + ri.Printf( PRINT_ALL, "pthread_create returned %d: %s", rc, strerror( rc ) ); + return qfalse; + } else { + rc = pthread_detach( renderThread ); + if ( rc ) { + ri.Printf( PRINT_ALL, "pthread_detach returned %d: %s", rc, strerror( rc ) ); + } + } + + return qtrue; +} + +static volatile void *smpData; + +// TJW - This is calling in the rendering thread to wait until another +// command buffer is ready. The command buffer returned might be NULL, +// indicating that the rendering thread should exit. +void *GLimp_RendererSleep( void ) { + //_Log(__PRETTY_FUNCTION__ " entered"); + unsigned int msgCode; + void *msgData; + + GLSTAMP( "GLimp_RendererSleep start", 0 ); + +#ifndef USE_CGLMACROS + // Clear the current context while we sleep so the main thread can access it + OSX_GLContextClearCurrent(); +#endif + + // Let the main thread we are idle and that no work is queued + //_Log("rs0\n"); + /* If we actually had some work to do, then tell the front end we completed it. */ + if ( rendererProcessingCommand ) { + SendMsg( &frontEndMsgPort, RenderCompletedMsg, NULL ); + rendererProcessingCommand = NO; + } + + // Wait for new msg + for (;; ) { + WaitMsg( &rendererMsgPort, &msgCode, &msgData ); + if ( 1 || msgCode == RenderCommandMsg ) { + smpData = msgData; + break; + } else { + printf( "renderer received unknown message: %d\n",msgCode ); + } + } + +#ifndef USE_CGLMACROS + // We are going to render a frame... retake the context + OSX_GLContextSetCurrent(); +#endif + + rendererProcessingCommand = YES; + + GLSTAMP( "GLimp_RendererSleep end", 0 ); + + return (void *)smpData; +} + + +// TJW - This is from the main thread to wait until the rendering thread +// has completed the command buffer that it has +void GLimp_FrontEndSleep( void ) { + unsigned int msgCode; + void *msgData; + + GLSTAMP( "GLimp_FrontEndSleep start", 1 ); + + if ( renderMsgOutstanding ) { + for (;; ) { + WaitMsg( &frontEndMsgPort, &msgCode, &msgData ); + if ( 1 || msgCode == RenderCompletedMsg ) { + break; + } else { + printf( "front end received unknown message: %d\n",msgCode ); + } + } + renderMsgOutstanding = NO; + } + +#ifndef USE_CGLMACROS + // We are done waiting for the background thread, take the current context back. + OSX_GLContextSetCurrent(); +#endif + + GLSTAMP( "GLimp_FrontEndSleep end", 1 ); +} + + +// TJW - This is called in the main thread to issue another command +// buffer to the rendering thread. This is always called AFTER +// GLimp_FrontEndSleep, so we know that there is no command +// pending in 'smpData'. +void GLimp_WakeRenderer( void *data ) { + GLSTAMP( "GLimp_WakeRenderer start", 1 ); + +#ifndef USE_CGLMACROS + // We want the background thread to draw stuff. Give up the current context + OSX_GLContextClearCurrent(); +#endif + + SendMsg( &rendererMsgPort, RenderCommandMsg, data ); + + // Don't set flag saying that the renderer is processing something if it's just + // being told to exit. + //if(data != NULL) + renderMsgOutstanding = YES; + + GLSTAMP( "GLimp_WakeRenderer end", 1 ); +} diff --git a/src/macosx/macosx_input.m b/src/macosx/macosx_input.m new file mode 100644 index 0000000..5c53757 --- /dev/null +++ b/src/macosx/macosx_input.m @@ -0,0 +1,928 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#import +#import +#include + +#import "../client/client.h" +#import "macosx_local.h" +#import "../renderer/tr_local.h" + +#import "Q3Controller.h" +//#import "CGMouseDeltaFix.h" +#import "macosx_timers.h" +#import "macosx_display.h" // For Sys_SetScreenFade + +#import +#import +#import +#import + + +static qboolean inputActive; + +static NSDate *distantPast; + +static cvar_t *in_nomouse; +static cvar_t *in_showevents; +static cvar_t *in_mouseLowEndSlope; +static cvar_t *in_mouseHighEndCutoff; + +static void Sys_StartMouseInput(); +static void Sys_StopMouseInput(); +static qboolean mouseactive = qfalse; +static BOOL inputRectValid = NO; +static CGRect inputRect; +static NXMouseScaling originalScaling; + +static double originalRepeatInterval; +static double originalRepeatThreshold; + +static unsigned int currentModifierFlags; + + + +static void Sys_PreventMouseMovement( CGPoint point ) { + CGEventErr err; + + //Com_Printf("**** Calling CGAssociateMouseAndMouseCursorPosition(false)\n"); + err = CGAssociateMouseAndMouseCursorPosition( false ); + if ( err != CGEventNoErr ) { + Sys_Error( "Could not disable mouse movement, CGAssociateMouseAndMouseCursorPosition returned %d\n", err ); + } + + // Put the mouse in the position we want to leave it at + err = CGWarpMouseCursorPosition( point ); + if ( err != CGEventNoErr ) { + Sys_Error( "Could not disable mouse movement, CGWarpMouseCursorPosition returned %d\n", err ); + } +} + +static void Sys_ReenableMouseMovement() { + CGEventErr err; + + //Com_Printf("**** Calling CGAssociateMouseAndMouseCursorPosition(true)\n"); + + err = CGAssociateMouseAndMouseCursorPosition( true ); + if ( err != CGEventNoErr ) { + Sys_Error( "Could not reenable mouse movement, CGAssociateMouseAndMouseCursorPosition returned %d\n", err ); + } + + // Leave the mouse where it was -- don't warp here. +} + + +void Sys_InitInput( void ) { + // no input with dedicated servers + if ( com_dedicated->integer ) { + return; + } + + // The Cvars don't seem to work really early. + [(Q3Controller *)[NSApp delegate] showBanner]; + + Com_Printf( "------- Input Initialization -------\n" ); + + if ( !distantPast ) { + distantPast = [[NSDate distantPast] retain]; + } + + // For hide support. If we don't do this, then the command key will get stuck on when we hide (since we won't get the flags changed event when it goes up). + currentModifierFlags = 0; + +// CGFix_Initialize(); + +#warning TJW: r_fullscreen is not normally set by the time we get here, so we have to initialize it with copy-n-paste from tr_init.c + r_fullscreen = Cvar_Get( "r_fullscreen", "0", CVAR_ARCHIVE | CVAR_LATCH ); //DAJ DEBUG was "1" + in_nomouse = Cvar_Get( "in_nomouse", "0", 0 ); + in_showevents = Cvar_Get( "in_showevents", "0", 0 ); + + // these defaults were arrived at via emprical testing between a Windows box and a Mac OS X box +#define ACT_LIKE_WINDOWS +#ifdef ACT_LIKE_WINDOWS + in_mouseLowEndSlope = Cvar_Get( "in_mouseLowEndSlope", "3.5", CVAR_ARCHIVE ); + if ( in_mouseLowEndSlope->value < 1 ) { + Cvar_Set( "in_mouseLowEndSlope", "1" ); + } +#else + in_mouseLowEndSlope = Cvar_Get( "in_mouseLowEndSlope", "1", CVAR_ARCHIVE ); + if ( in_mouseLowEndSlope->value < 1 ) { + Cvar_Set( "in_mouseLowEndSlope", "1" ); + } +#endif + + in_mouseHighEndCutoff = Cvar_Get( "in_mouseHighEndCutoff", "20", CVAR_ARCHIVE ); + if ( in_mouseLowEndSlope->value < 1 ) { + Cvar_Set( "in_mouseHighEndCutoff", "1" ); + } + + glw_state.display = Sys_DisplayToUse(); + + inputActive = qtrue; + + if ( in_nomouse->integer == 0 ) { + Sys_StartMouseInput(); + } else { + Com_Printf( " in_nomouse is set, skipping.\n" ); + } + + Com_Printf( "------------------------------------\n" ); +} + +void Sys_ShutdownInput( void ) { + // no input with dedicated servers + if ( com_dedicated && com_dedicated->integer ) { + return; + } + + Com_Printf( "------- Input Shutdown -------\n" ); + if ( !inputActive ) { + return; + } + inputActive = qfalse; + + if ( mouseactive ) { + Sys_StopMouseInput(); + } + + Com_Printf( "------------------------------\n" ); +} + +static void Sys_LockMouseInInputRect( CGRect rect ) { + CGPoint center; + + center.x = rect.origin.x + rect.size.width / 2.0; + center.y = rect.origin.y + rect.size.height / 2.0; + + // Now, put the mouse in the middle of the input rect (anywhere over it would do) + // and don't allow it to move. This means that the user won't be able to accidentally + // select another application. + Sys_PreventMouseMovement( center ); +} + +extern void Sys_UpdateWindowMouseInputRect( void ); + +static void Sys_StartMouseInput() { + NXEventHandle eventStatus; + CGMouseDelta dx, dy; + + if ( mouseactive ) { + //Com_Printf("**** Attempted to start mouse input while already started\n"); + return; + } + + Com_Printf( "Starting mouse input\n" ); + + mouseactive = qtrue; + if ( inputRectValid && !glConfig.isFullscreen ) { + // Make sure that if window moved we don't hose the user... + Sys_UpdateWindowMouseInputRect(); + } + +//DAJ TEST Sys_LockMouseInInputRect(inputRect); + + // Grab any mouse delta information to reset the last delta buffer +// CGFix_GetLastMouseDelta(&dx, &dy); + CGGetLastMouseDelta( &dx, &dy ); + + // Turn off mouse scaling + if ( ( eventStatus = NXOpenEventStatus() ) ) { + NXMouseScaling newScaling; + + NXGetMouseScaling( eventStatus, &originalScaling ); + newScaling.numScaleLevels = 1; + newScaling.scaleThresholds[0] = 1; + newScaling.scaleFactors[0] = -1; + NXSetMouseScaling( eventStatus, &newScaling ); + + // Do the effing keyboard too, so we don't get flooded with key repeat events. + originalRepeatInterval = NXKeyRepeatInterval( eventStatus ); + originalRepeatThreshold = NXKeyRepeatThreshold( eventStatus ); +/* + // Make really damned sure we don't get repeat events... + NXSetKeyRepeatInterval(eventStatus, 86400.0f); + NXSetKeyRepeatThreshold(eventStatus, 86400.0f); +*/ + NXCloseEventStatus( eventStatus ); + } + + [NSCursor hide]; +} + +static void Sys_StopMouseInput() { + NXEventHandle eventStatus; + + if ( !mouseactive ) { + //Com_Printf("**** Attempted to stop mouse input while already stopped\n"); + return; + } + + Com_Printf( "Stopping mouse input\n" ); + + mouseactive = qfalse; + Sys_ReenableMouseMovement(); + + [NSCursor unhide]; + + // Restore mouse scaling + if ( ( eventStatus = NXOpenEventStatus() ) ) { + NXSetMouseScaling( eventStatus, &originalScaling ); + NXSetKeyRepeatInterval( eventStatus,originalRepeatInterval ); + NXSetKeyRepeatThreshold( eventStatus,originalRepeatThreshold ); + NXCloseEventStatus( eventStatus ); + } +} + +//=========================================================================== + +#include +#include +#include + +static char *Sys_ConsoleInput( void ) { + extern qboolean stdin_active; + static char text[256]; + int len; + fd_set fdset; + struct timeval timeout; + + if ( !com_dedicated || !com_dedicated->integer ) { + return NULL; + } + + if ( !stdin_active ) { + return NULL; + } + + FD_ZERO( &fdset ); + FD_SET( fileno( stdin ), &fdset ); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if ( select( 1, &fdset, NULL, NULL, &timeout ) == -1 || !FD_ISSET( fileno( stdin ), &fdset ) ) { + return NULL; + } + + len = read( fileno( stdin ), text, sizeof( text ) ); + if ( len == 0 ) { // eof! + stdin_active = qfalse; + return NULL; + } + + if ( len < 1 ) { + return NULL; + } + text[len - 1] = 0; // rip off the /n and terminate + + return text; +} + +//=========================================================================== +// Mouse input +//=========================================================================== + +#define MAX_DISPLAYS 128 + +CGDirectDisplayID Sys_DisplayToUse( void ) { + static BOOL gotDisplay = NO; + static CGDirectDisplayID displayToUse; + + cvar_t *vid_screen; + CGDisplayErr err; + CGDirectDisplayID displays[MAX_DISPLAYS]; + CGDisplayCount displayCount; + int displayIndex; + + if ( gotDisplay ) { + return displayToUse; + } + gotDisplay = YES; + + err = CGGetActiveDisplayList( MAX_DISPLAYS, displays, &displayCount ); + if ( err != CGDisplayNoErr ) { + Sys_Error( "Cannot get display list -- CGGetActiveDisplayList returned %d.\n", err ); + } + + // -1, the default, means to use the main screen + if ( ( vid_screen = Cvar_Get( "vid_screen", "-1", CVAR_ARCHIVE ) ) ) { + displayIndex = vid_screen->integer; + } else { + displayIndex = -1; + } + + if ( displayIndex < 0 || displayIndex >= displayCount ) { + // This is documented (in CGDirectDisplay.h) to be the main display. We want to + // return this instead of kCGDirectMainDisplay since this will allow us to compare + // display IDs. + displayToUse = displays[0]; + } else { + displayToUse = displays[displayIndex]; + } + + return displayToUse; +} + +void Sys_SetMouseInputRect( CGRect newRect ) { + inputRectValid = YES; + inputRect = newRect; + //Com_Printf("**** inputRect = (%f, %f, %f, %f)\n", newRect.origin.x, newRect.origin.y, newRect.size.width, newRect.size.height); + +//DAJ TEST if (mouseactive) +//DAJ TEST Sys_LockMouseInInputRect(inputRect); +} + + +static void Sys_ProcessMouseMovedEvent( NSEvent *mouseMovedEvent, int currentTime ) { + CGMouseDelta dx, dy; + + if ( !mouseactive ) { + return; + } + + // The AppKit NSEvent doesn't support delta mouse movement. We can (and have in + // the past) kept track of the previous position and on each event computed the + // delta. When the mouse approached the edge of the window rect, we'd warp + // it back to the center. This was ugly and had a bunch of edge cases. + // Now, we just use the new CG API for getting the real mouse delta. +// CGFix_GetLastMouseDelta(&dx, &dy); + CGGetLastMouseDelta( &dx, &dy ); + + if ( in_showevents->integer ) { + Com_Printf( "MOUSE MOVED: %d, %d\n", dx, dy ); + } + if ( dx || dy ) { +#if 0 + CGMouseDelta distSqr; + float m0, N; + + distSqr = dx * dx + dy * dy; + //Com_Printf("distSqr = %d\n", distSqr); + + /* This code is here to help people that like the feel of the Logitech USB Gaming Mouse with the Win98 drivers. By empirical testing, the Windows drivers seem to be more heavily accelerated at the low end of the curve. */ + N = in_mouseHighEndCutoff->value; + + if ( distSqr < N * N ) { + float dist, accel, scale; + + m0 = in_mouseLowEndSlope->value; + dist = sqrt( distSqr ); + accel = ( ( ( m0 - 1.0 ) / ( N * N ) * dist + ( 2.0 - 2.0 * m0 ) / N ) * dist + m0 ) * dist; + + scale = accel / dist; + //Com_Printf("dx = %d, dy = %d, dist = %f, accel = %f, scale = %f\n", dx, dy, dist, accel, scale); + + dx *= scale; + dy *= scale; + } +#endif + Sys_QueEvent( currentTime, SE_MOUSE, dx, dy, 0, NULL ); + } +} + +// If we are 'paused' (i.e., in any state that our normal key bindings aren't in effect), then interpret cmd-h and cmd-tab as hiding the application. +static qboolean maybeHide() { + + qboolean menuUp = qfalse; + + if ( ( currentModifierFlags & NSCommandKeyMask ) == 0 ) { + return qfalse; + } + + // + // hide? + // + menuUp = qtrue; + + if ( menuUp ) { + return Sys_Hide(); + } + return qfalse; +} + +static inline void sendEventForCharacter( NSEvent *event, unichar character, qboolean keyDownFlag, int currentTime ) { + if ( in_showevents->integer ) { + Com_Printf( "CHARACTER: 0x%02x down=%d\n", character, keyDownFlag ); + } + +#ifdef OMNI_TIMER + if ( character == NSF9FunctionKey && !keyDownFlag ) { + // Log and reset the root timer. We should currently only have the root on the stack. + OTStackPopRoot(); + OTStackReportResults( NULL ); + OTStackReset(); + OTStackPushRoot( rootNode ); + } +#endif + + switch ( character ) { + case '\b': + case '\177': + Sys_QueEvent( currentTime, SE_KEY, K_BACKSPACE, keyDownFlag, 0, NULL ); + if ( keyDownFlag ) { + Sys_QueEvent( currentTime, SE_CHAR, '\b', 0, 0, NULL ); + } + break; + case '\t': + if ( maybeHide() ) { + return; + } + Sys_QueEvent( currentTime, SE_KEY, K_TAB, keyDownFlag, 0, NULL ); + if ( keyDownFlag ) { + Sys_QueEvent( currentTime, SE_CHAR, '\t', 0, 0, NULL ); + } + break; + case '\r': + case '\n': + Sys_QueEvent( currentTime, SE_KEY, K_ENTER, keyDownFlag, 0, NULL ); + if ( keyDownFlag ) { + Sys_QueEvent( currentTime, SE_CHAR, '\r', 0, 0, NULL ); + } + break; + case '\033': + Sys_QueEvent( currentTime, SE_KEY, K_ESCAPE, keyDownFlag, 0, NULL ); + break; + case ' ': + Sys_QueEvent( currentTime, SE_KEY, K_SPACE, keyDownFlag, 0, NULL ); + if ( keyDownFlag ) { + Sys_QueEvent( currentTime, SE_CHAR, ' ', 0, 0, NULL ); + } + break; + case NSUpArrowFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_UPARROW, keyDownFlag, 0, NULL ); + break; + case NSDownArrowFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_DOWNARROW, keyDownFlag, 0, NULL ); + break; + case NSLeftArrowFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_LEFTARROW, keyDownFlag, 0, NULL ); + break; + case NSRightArrowFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_RIGHTARROW, keyDownFlag, 0, NULL ); + break; + case NSF1FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F1, keyDownFlag, 0, NULL ); + break; + case NSF2FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F2, keyDownFlag, 0, NULL ); + break; + case NSF3FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F3, keyDownFlag, 0, NULL ); + break; + case NSF4FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F4, keyDownFlag, 0, NULL ); + break; + case NSF5FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F5, keyDownFlag, 0, NULL ); + break; + case NSF6FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F6, keyDownFlag, 0, NULL ); + break; + case NSF7FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F7, keyDownFlag, 0, NULL ); + break; + case NSF8FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F8, keyDownFlag, 0, NULL ); + break; + case NSF9FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F9, keyDownFlag, 0, NULL ); + break; + case NSF10FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F10, keyDownFlag, 0, NULL ); + break; + case NSF11FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F11, keyDownFlag, 0, NULL ); + break; + case NSF12FunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_F12, keyDownFlag, 0, NULL ); + break; + case NSInsertFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_INS, keyDownFlag, 0, NULL ); + break; + case NSDeleteFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_DEL, keyDownFlag, 0, NULL ); + break; + case NSPageDownFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_PGDN, keyDownFlag, 0, NULL ); + break; + case NSPageUpFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_PGUP, keyDownFlag, 0, NULL ); + break; + case NSHomeFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_HOME, keyDownFlag, 0, NULL ); + break; + case NSEndFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_END, keyDownFlag, 0, NULL ); + break; + // TODO: Keypad keys + case NSPauseFunctionKey: + Sys_QueEvent( currentTime, SE_KEY, K_PAUSE, keyDownFlag, 0, NULL ); + break; + default: + if ( character >= 'a' && character <= 'z' ) { + if ( character == 'h' ) { + if ( maybeHide() ) { + return; + } + } + Sys_QueEvent( currentTime, SE_KEY, character, keyDownFlag, 0, NULL ); + if ( keyDownFlag ) { + Sys_QueEvent( currentTime, SE_CHAR, (char)character, 0, 0, NULL ); + } + } else if ( character >= 'A' && character <= 'Z' ) { + Sys_QueEvent( currentTime, SE_KEY, 'a' + ( character - 'A' ), keyDownFlag, 0, NULL ); + if ( keyDownFlag ) { + Sys_QueEvent( currentTime, SE_CHAR, character, 0, 0, NULL ); + } + } else if ( character >= 32 && character < 127 ) { + Sys_QueEvent( currentTime, SE_KEY, character, keyDownFlag, 0, NULL ); + if ( keyDownFlag ) { + Sys_QueEvent( currentTime, SE_CHAR, (char)character, 0, 0, NULL ); + } + } else { + //NSLog(@"TODO: Implement character %d", (int)character); + } + break; + } +} + +static inline void processKeyEvent( NSEvent *keyEvent, qboolean keyDownFlag, int currentTime ) { + NSEventType eventType; + NSString *characters; + unsigned int characterIndex, characterCount; + + eventType = [keyEvent type]; + characters = [keyEvent charactersIgnoringModifiers]; + characterCount = [characters length]; + + for ( characterIndex = 0; characterIndex < characterCount; characterIndex++ ) { + sendEventForCharacter( keyEvent, [characters characterAtIndex : characterIndex], keyDownFlag, currentTime ); + } +} + +static inline void sendEventForMaskChangeInFlags( int quakeKey, unsigned int modifierMask, unsigned int newModifierFlags, int currentTime ) { + BOOL oldHadModifier, newHasModifier; + + oldHadModifier = ( currentModifierFlags & modifierMask ) != 0; + newHasModifier = ( newModifierFlags & modifierMask ) != 0; + if ( oldHadModifier != newHasModifier ) { + // NSLog(@"Key %d posted for modifier mask modifierMask", quakeKey); + Sys_QueEvent( currentTime, SE_KEY, quakeKey, newHasModifier, 0, NULL ); + } +} + +static inline void processFlagsChangedEvent( NSEvent *flagsChangedEvent, int currentTime ) { + int newModifierFlags; + + newModifierFlags = [flagsChangedEvent modifierFlags]; + sendEventForMaskChangeInFlags( K_COMMAND, NSCommandKeyMask, newModifierFlags, currentTime ); + sendEventForMaskChangeInFlags( K_CAPSLOCK, NSAlphaShiftKeyMask, newModifierFlags, currentTime ); + sendEventForMaskChangeInFlags( K_ALT, NSAlternateKeyMask, newModifierFlags, currentTime ); + sendEventForMaskChangeInFlags( K_CTRL, NSControlKeyMask, newModifierFlags, currentTime ); + sendEventForMaskChangeInFlags( K_SHIFT, NSShiftKeyMask, newModifierFlags, currentTime ); + currentModifierFlags = newModifierFlags; +} + +static inline void processSystemDefinedEvent( NSEvent *systemDefinedEvent, int currentTime ) { + static int oldButtons = 0; + int buttonsDelta; + int buttons; + int isDown; + + if ([systemDefinedEvent subtype] == 7 ) { + + if ( !mouseactive ) { + return; + } + + + buttons = [systemDefinedEvent data2]; + buttonsDelta = oldButtons ^ buttons; + + //Com_Printf("uberbuttons: %08lx %08lx\n",buttonsDelta,buttons); + + + if ( buttonsDelta & 1 ) { + isDown = buttons & 1; + Sys_QueEvent( currentTime, SE_KEY, K_MOUSE1, isDown, 0, NULL ); + if ( in_showevents->integer ) { + Com_Printf( "MOUSE2: %s\n", isDown ? "down" : "up" ); + } + } + + if ( buttonsDelta & 2 ) { + isDown = buttons & 2; + Sys_QueEvent( currentTime, SE_KEY, K_MOUSE2, isDown, 0, NULL ); + if ( in_showevents->integer ) { + Com_Printf( "MOUSE3: %s\n", isDown ? "down" : "up" ); + } + } + + if ( buttonsDelta & 4 ) { + isDown = buttons & 4; + Sys_QueEvent( currentTime, SE_KEY, K_MOUSE3, isDown, 0, NULL ); + if ( in_showevents->integer ) { + Com_Printf( "MOUSE1: %s\n", isDown ? "down" : "up" ); + } + } + + if ( buttonsDelta & 8 ) { + isDown = buttons & 8; + Sys_QueEvent( currentTime, SE_KEY, K_MOUSE4, isDown, 0, NULL ); + if ( in_showevents->integer ) { + Com_Printf( "MOUSE4: %s\n", isDown ? "down" : "up" ); + } + } + + if ( buttonsDelta & 16 ) { + isDown = buttons & 16; + Sys_QueEvent( currentTime, SE_KEY, K_MOUSE5, isDown, 0, NULL ); + if ( in_showevents->integer ) { + Com_Printf( "MOUSE5: %s\n", isDown ? "down" : "up" ); + } + } + + oldButtons = buttons; + } +} + +static inline void processEvent( NSEvent *event, int currentTime ) { + NSEventType eventType; + + if ( !inputActive ) { + return; + } + + eventType = [event type]; + +#if 0 + if ( eventType != NSAppKitDefined && eventType != NSSystemDefined ) { + if ([event window] != in_mouseInputWindow ) { + + Com_Printf( "processEvent -- wrong window. Expected 0x%08x, got 0x%08x.\n", + in_mouseInputWindow, [event window] ); + NSLog( @ "event = %@", event ); + } else { + Com_Printf( "Got right window\n" ); + } + } +#endif + + if ( in_showevents->integer ) { + NSLog( @ "event = %@", event ); + } + + switch ( eventType ) { + // These six event types are ignored since we do all of our mouse down/up process via the uber-mouse system defined event. We have to accept these events however since they get enqueued and the queue will fill up if we don't. + case NSLeftMouseDown: + //Sys_QueEvent(currentTime, SE_KEY, K_MOUSE1, qtrue, 0, NULL); + return; + case NSLeftMouseUp: + //Sys_QueEvent(currentTime, SE_KEY, K_MOUSE1, qfalse, 0, NULL); + return; + case NSRightMouseDown: + //Sys_QueEvent(currentTime, SE_KEY, K_MOUSE2, qtrue, 0, NULL); + return; + case NSRightMouseUp: + //Sys_QueEvent(currentTime, SE_KEY, K_MOUSE2, qfalse, 0, NULL); + return; + case 25: // other mouse down + return; + case 26: // other mouse up + return; + + case NSMouseMoved: + case NSLeftMouseDragged: + case NSRightMouseDragged: + case 27: // other mouse dragged + Sys_ProcessMouseMovedEvent( event, currentTime ); + return; + case NSKeyDown: + case NSKeyUp: + processKeyEvent( event, eventType == NSKeyDown, currentTime ); + return; + case NSFlagsChanged: + processFlagsChangedEvent( event, currentTime ); + return; + case NSSystemDefined: + processSystemDefinedEvent( event, currentTime ); + return; + case NSScrollWheel: + if ([event deltaY] < 0.0 ) { + Sys_QueEvent( currentTime, SE_KEY, K_MWHEELDOWN, qtrue, 0, NULL ); + Sys_QueEvent( currentTime, SE_KEY, K_MWHEELDOWN, qfalse, 0, NULL ); + } else { + Sys_QueEvent( currentTime, SE_KEY, K_MWHEELUP, qtrue, 0, NULL ); + Sys_QueEvent( currentTime, SE_KEY, K_MWHEELUP, qfalse, 0, NULL ); + } + return; + default: + break; + } + [NSApp sendEvent : event]; +} + +static void Sys_SendKeyEvents( int currentTime ) { +#ifndef DEDICATED + NSEvent *event; + NSDate *timeout; + extern float SNDDMA_GetBufferDuration(); + + timeout = distantPast; + if ( Sys_IsHidden ) { + timeout = [NSDate dateWithTimeIntervalSinceNow: 0.25 * SNDDMA_GetBufferDuration()]; + } + + // This gets call regardless of whether inputActive is true or not. This is important since we need to be poking the event queue in order for the unhide event to make its way through the system. This means that when we hide, we can just shut down the input system and reeanbled it when we unhide. + while ( ( event = [NSApp nextEventMatchingMask: NSAnyEventMask + untilDate: timeout + inMode: NSDefaultRunLoopMode + dequeue:YES] ) ) { + if ( Sys_IsHidden ) { + // Just let NSApp handle events so that we'll get the app activation event + [NSApp sendEvent : event]; + timeout = [NSDate dateWithTimeIntervalSinceNow: 0.1]; + } else { + static int lastEventTime = 0; + static BOOL lastEventTimeValid = NO; + + // Mac OS X 10.0.3 has a bug where the if the monitor goes to sleep in fullscreen GL mode, the gamma won't be restored. We'll restore the gamma if there is a pause while in the game of more than 10 seconds. We don't do this on the 'Sys_IsHidden' branch since unhiding will restore the monitor gamma. + if ( ( currentTime - lastEventTime > 1 * 1000 ) && lastEventTimeValid ) { + //Com_Printf("Restoring monitor gamma after being idle for %f seconds.\n", (currentTime - lastEventTime) / 1000.0); + [NSCursor hide]; + Sys_SetScreenFade( &glw_state.inGameTable, 1.0 ); + } + lastEventTime = currentTime; + lastEventTimeValid = YES; + + processEvent( event, currentTime ); + } + } +#endif +} + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +extern qboolean Sys_GetPacket( netadr_t *net_from, msg_t *net_message ); + +#define MAX_QUED_EVENTS 256 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) + +static sysEvent_t eventQue[MAX_QUED_EVENTS]; +static int eventHead, eventTail; +static byte sys_packetReceived[MAX_MSGLEN]; + +/* +================ +Sys_QueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) { + sysEvent_t *ev; + +#ifndef DEDICATED + if ( in_showevents->integer ) { + NSLog( @ "EVENT ENQUEUE: type=%d value=0x%08x value2=0x%08x\n", type, value, value2 ); + } +#endif + + ev = &eventQue[ eventHead & MASK_QUED_EVENTS ]; + if ( eventHead - eventTail >= MAX_QUED_EVENTS ) { + Com_Printf( "Sys_QueEvent: overflow\n" ); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) { + Z_Free( ev->evPtr ); + ev->evPtr = NULL; + } + eventTail++; + } + eventHead++; + + if ( time == 0 ) { + time = Sys_Milliseconds(); + } + + ev->evTime = time; + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + + +/* +================ +Sys_GetEvent + +================ +*/ +sysEvent_t Sys_GetEvent( void ) { + sysEvent_t ev; + char *s; + msg_t netmsg; + netadr_t adr; + int currentTime; + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // The queue must be empty. Check all of the event sources. If the events are + // already in the queue, we can't imply any real ordering, so we'll avoid extra + // system calls and give them all the same time. + currentTime = Sys_Milliseconds(); + + // Check for mouse and keyboard events + Sys_SendKeyEvents( currentTime ); + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) { + char *b; + int len; + + len = strlen( s ) + 1; + b = Z_Malloc( len ); + strcpy( b, s ); + Sys_QueEvent( currentTime, SE_CONSOLE, 0, 0, len, b ); + } + + + // During debugging it is sometimes usefull to be able to start/stop mouse input. + // Don't turn on the input when we've disabled it because we're hidden, however. + if ( !com_dedicated->integer ) { + if ( in_nomouse->integer == mouseactive && !Sys_IsHidden ) { + if ( in_nomouse->integer ) { + Sys_StopMouseInput(); + } else { + Sys_StartMouseInput(); + } + } + } + + // check for network packets + MSG_Init( &netmsg, sys_packetReceived, sizeof( sys_packetReceived ) ); + if ( Sys_GetPacket( &adr, &netmsg ) ) { + netadr_t *buf; + int len; + + // copy out to a seperate buffer for qeueing + len = sizeof( netadr_t ) + netmsg.cursize; + buf = Z_Malloc( len ); + *buf = adr; + memcpy( buf + 1, netmsg.data, netmsg.cursize ); + Sys_QueEvent( currentTime, SE_PACKET, 0, 0, len, buf ); + } + + // If we got an event, return it + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // Otherwise, return an empty event to indicate that there are no events pending. + memset( &ev, 0, sizeof( ev ) ); + ev.evTime = currentTime; + + return ev; +} + + + diff --git a/src/macosx/macosx_local.h b/src/macosx/macosx_local.h new file mode 100644 index 0000000..1dea7f7 --- /dev/null +++ b/src/macosx/macosx_local.h @@ -0,0 +1,136 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __macosx_local_h +#define __macosx_local_h + +#include "qcommon.h" + +#ifdef __cplusplus +typedef void NSDictionary; +typedef void NSOpenGLContext; +typedef void NSWindow; + +extern "C" { +#else +#import +@class NSEvent, NSOpenGLContext, NSWindow; +#endif + +#include +#include + +// In macosx_input.m +extern void Sys_InitInput( void ); +extern void Sys_ShutdownInput( void ); +extern void Sys_SetMouseInputRect( CGRect newRect ); +extern CGDirectDisplayID Sys_DisplayToUse( void ); + +// In macosx_sys.m +extern void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); +extern void Sys_AnnoyingBanner(); + +// In macosx_glimp.m +extern qboolean Sys_IsHidden; +extern qboolean Sys_Hide(); +extern qboolean Sys_Unhide(); + +typedef struct { + CGDirectDisplayID display; + CGTableCount tableSize; + CGGammaValue *red; + CGGammaValue *blue; + CGGammaValue *green; +} glwgamma_t; + +typedef struct +{ + CGDirectDisplayID display; + NSDictionary *desktopMode; + NSDictionary *gameMode; + + CGDisplayCount displayCount; + glwgamma_t *originalDisplayGammaTables; + glwgamma_t inGameTable; + glwgamma_t tempTable; + + NSOpenGLContext *_ctx; + CGLContextObj _cgl_ctx; + qboolean _ctx_is_current; + NSWindow *window; + + FILE *log_fp; + + unsigned int bufferSwapCount; + unsigned int glPauseCount; +} glwstate_t; + +extern glwstate_t glw_state; + +#define OSX_SetGLContext( context ) \ + do { \ + NSOpenGLContext *_context = ( context ); \ + glw_state._ctx = _context; \ + glw_state._cgl_ctx = [_context cglContext]; \ + } while ( 0 ) + +#define OSX_GetNSGLContext() glw_state._ctx +#define OSX_GetCGLContext() glw_state._cgl_ctx + +#define OSX_GLContextIsCurrent() glw_state._ctx_is_current +#define OSX_GLContextSetCurrent() \ + do { \ + [glw_state._ctx makeCurrentContext]; \ + glw_state._ctx_is_current = ( glw_state._ctx != nil ); \ + } while ( 0 ) + +#define OSX_GLContextClearCurrent() \ + do { \ + [NSOpenGLContext clearCurrentContext]; \ + glw_state._ctx_is_current = NO; \ + } while ( 0 ) + + +extern void Sys_PauseGL(); +extern void Sys_ResumeGL(); + + +#import "macosx_timers.h" + +#ifdef OMNI_TIMER +extern OTStampList glThreadStampList; +#define GLSTAMP( name, data ) OTStampListAddStamp( glThreadStampList, name, data ) +#else +#define GLSTAMP( name, data ) +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __macosx_local_h diff --git a/src/macosx/macosx_qgl.h b/src/macosx/macosx_qgl.h new file mode 100644 index 0000000..df84f8a --- /dev/null +++ b/src/macosx/macosx_qgl.h @@ -0,0 +1,5448 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/**** This file is autogenerated. Run GenerateQGL.pl to update it ****/ + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef QGL_LOG_GL_CALLS +extern unsigned int QGLLogGLCalls; +extern FILE *QGLDebugFile( void ); +#endif + +extern void QGLCheckError( const char *message ); +extern unsigned int QGLBeginStarted; + +// This has to be done to avoid infinite recursion between our glGetError wrapper and QGLCheckError() +static inline GLenum _glGetError( void ) { + return glGetError(); +} + +// void glAccum (GLenum op, GLfloat value); +static inline void qglAccum( GLenum op, GLfloat value ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glAccum(op=%lu, value=%f)\n", op, value ); + } +#endif + glAccum( op, value ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glAccum" ); + } +#endif +} + +// void glAlphaFunc (GLenum func, GLclampf ref); +static inline void qglAlphaFunc( GLenum func, GLclampf ref ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glAlphaFunc(func=%lu, ref=%f)\n", func, ref ); + } +#endif + glAlphaFunc( func, ref ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glAlphaFunc" ); + } +#endif +} + +// GLboolean glAreTexturesResident (GLsizei n, const GLuint *textures, GLboolean *residences); +static inline GLboolean qglAreTexturesResident( GLsizei n, const GLuint *textures, GLboolean *residences ) { + GLboolean returnValue; +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glAreTexturesResident(n=%ld, textures=%p, residences=%p)\n", n, textures, residences ); + } +#endif + returnValue = glAreTexturesResident( n, textures, residences ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glAreTexturesResident" ); + } +#endif + return returnValue; +} + +// void glArrayElement (GLint i); +static inline void qglArrayElement( GLint i ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glArrayElement(i=%ld)\n", i ); + } +#endif + glArrayElement( i ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glArrayElement" ); + } +#endif +} + +// void glBegin (GLenum mode); +static inline void qglBegin( GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glBegin(mode=%lu)\n", mode ); + } +#endif + glBegin( mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + QGLBeginStarted++; + if ( !QGLBeginStarted ) { + QGLCheckError( "glBegin" ); + } +#endif +} + +// void glBindTexture (GLenum target, GLuint texture); +static inline void qglBindTexture( GLenum target, GLuint texture ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glBindTexture(target=%lu, texture=%lu)\n", target, texture ); + } +#endif + glBindTexture( target, texture ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glBindTexture" ); + } +#endif +} + +// void glBitmap (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +static inline void qglBitmap( GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glBitmap(width=%ld, height=%ld, xorig=%f, yorig=%f, xmove=%f, ymove=%f, bitmap=%p)\n", width, height, xorig, yorig, xmove, ymove, bitmap ); + } +#endif + glBitmap( width, height, xorig, yorig, xmove, ymove, bitmap ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glBitmap" ); + } +#endif +} + +// void glBlendFunc (GLenum sfactor, GLenum dfactor); +static inline void qglBlendFunc( GLenum sfactor, GLenum dfactor ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glBlendFunc(sfactor=%lu, dfactor=%lu)\n", sfactor, dfactor ); + } +#endif + glBlendFunc( sfactor, dfactor ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glBlendFunc" ); + } +#endif +} + +// void glCallList (GLuint list); +static inline void qglCallList( GLuint list ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glCallList(list=%lu)\n", list ); + } +#endif + glCallList( list ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glCallList" ); + } +#endif +} + +// void glCallLists (GLsizei n, GLenum type, const GLvoid *lists); +static inline void qglCallLists( GLsizei n, GLenum type, const GLvoid *lists ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glCallLists(n=%ld, type=%lu, lists=%p)\n", n, type, lists ); + } +#endif + glCallLists( n, type, lists ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glCallLists" ); + } +#endif +} + +// void glClear (GLbitfield mask); +static inline void qglClear( GLbitfield mask ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glClear(mask=%lu)\n", mask ); + } +#endif + glClear( mask ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glClear" ); + } +#endif +} + +// void glClearAccum (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static inline void qglClearAccum( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glClearAccum(red=%f, green=%f, blue=%f, alpha=%f)\n", red, green, blue, alpha ); + } +#endif + glClearAccum( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glClearAccum" ); + } +#endif +} + +// void glClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +static inline void qglClearColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glClearColor(red=%f, green=%f, blue=%f, alpha=%f)\n", red, green, blue, alpha ); + } +#endif + glClearColor( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glClearColor" ); + } +#endif +} + +// void glClearDepth (GLclampd depth); +static inline void qglClearDepth( GLclampd depth ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glClearDepth(depth=%f)\n", depth ); + } +#endif + glClearDepth( depth ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glClearDepth" ); + } +#endif +} + +// void glClearIndex (GLfloat c); +static inline void qglClearIndex( GLfloat c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glClearIndex(c=%f)\n", c ); + } +#endif + glClearIndex( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glClearIndex" ); + } +#endif +} + +// void glClearStencil (GLint s); +static inline void qglClearStencil( GLint s ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glClearStencil(s=%ld)\n", s ); + } +#endif + glClearStencil( s ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glClearStencil" ); + } +#endif +} + +// void glClipPlane (GLenum plane, const GLdouble *equation); +static inline void qglClipPlane( GLenum plane, const GLdouble *equation ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glClipPlane(plane=%lu, equation=%p)\n", plane, equation ); + } +#endif + glClipPlane( plane, equation ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glClipPlane" ); + } +#endif +} + +// void glColor3b (GLbyte red, GLbyte green, GLbyte blue); +static inline void qglColor3b( GLbyte red, GLbyte green, GLbyte blue ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3b(red=%d, green=%d, blue=%d)\n", red, green, blue ); + } +#endif + glColor3b( red, green, blue ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3b" ); + } +#endif +} + +// void glColor3bv (const GLbyte *v); +static inline void qglColor3bv( const GLbyte *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3bv(v=%p)\n", v ); + } +#endif + glColor3bv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3bv" ); + } +#endif +} + +// void glColor3d (GLdouble red, GLdouble green, GLdouble blue); +static inline void qglColor3d( GLdouble red, GLdouble green, GLdouble blue ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3d(red=%f, green=%f, blue=%f)\n", red, green, blue ); + } +#endif + glColor3d( red, green, blue ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3d" ); + } +#endif +} + +// void glColor3dv (const GLdouble *v); +static inline void qglColor3dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3dv(v=%p)\n", v ); + } +#endif + glColor3dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3dv" ); + } +#endif +} + +// void glColor3f (GLfloat red, GLfloat green, GLfloat blue); +static inline void qglColor3f( GLfloat red, GLfloat green, GLfloat blue ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3f(red=%f, green=%f, blue=%f)\n", red, green, blue ); + } +#endif + glColor3f( red, green, blue ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3f" ); + } +#endif +} + +// void glColor3fv (const GLfloat *v); +static inline void qglColor3fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3fv(v=%p)\n", v ); + } +#endif + glColor3fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3fv" ); + } +#endif +} + +// void glColor3i (GLint red, GLint green, GLint blue); +static inline void qglColor3i( GLint red, GLint green, GLint blue ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3i(red=%ld, green=%ld, blue=%ld)\n", red, green, blue ); + } +#endif + glColor3i( red, green, blue ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3i" ); + } +#endif +} + +// void glColor3iv (const GLint *v); +static inline void qglColor3iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3iv(v=%p)\n", v ); + } +#endif + glColor3iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3iv" ); + } +#endif +} + +// void glColor3s (GLshort red, GLshort green, GLshort blue); +static inline void qglColor3s( GLshort red, GLshort green, GLshort blue ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3s(red=%d, green=%d, blue=%d)\n", red, green, blue ); + } +#endif + glColor3s( red, green, blue ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3s" ); + } +#endif +} + +// void glColor3sv (const GLshort *v); +static inline void qglColor3sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3sv(v=%p)\n", v ); + } +#endif + glColor3sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3sv" ); + } +#endif +} + +// void glColor3ub (GLubyte red, GLubyte green, GLubyte blue); +static inline void qglColor3ub( GLubyte red, GLubyte green, GLubyte blue ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3ub(red=%u, green=%u, blue=%u)\n", red, green, blue ); + } +#endif + glColor3ub( red, green, blue ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3ub" ); + } +#endif +} + +// void glColor3ubv (const GLubyte *v); +static inline void qglColor3ubv( const GLubyte *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3ubv(v=%p)\n", v ); + } +#endif + glColor3ubv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3ubv" ); + } +#endif +} + +// void glColor3ui (GLuint red, GLuint green, GLuint blue); +static inline void qglColor3ui( GLuint red, GLuint green, GLuint blue ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3ui(red=%lu, green=%lu, blue=%lu)\n", red, green, blue ); + } +#endif + glColor3ui( red, green, blue ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3ui" ); + } +#endif +} + +// void glColor3uiv (const GLuint *v); +static inline void qglColor3uiv( const GLuint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3uiv(v=%p)\n", v ); + } +#endif + glColor3uiv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3uiv" ); + } +#endif +} + +// void glColor3us (GLushort red, GLushort green, GLushort blue); +static inline void qglColor3us( GLushort red, GLushort green, GLushort blue ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3us(red=%u, green=%u, blue=%u)\n", red, green, blue ); + } +#endif + glColor3us( red, green, blue ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3us" ); + } +#endif +} + +// void glColor3usv (const GLushort *v); +static inline void qglColor3usv( const GLushort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor3usv(v=%p)\n", v ); + } +#endif + glColor3usv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor3usv" ); + } +#endif +} + +// void glColor4b (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +static inline void qglColor4b( GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4b(red=%d, green=%d, blue=%d, alpha=%d)\n", red, green, blue, alpha ); + } +#endif + glColor4b( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4b" ); + } +#endif +} + +// void glColor4bv (const GLbyte *v); +static inline void qglColor4bv( const GLbyte *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4bv(v=%p)\n", v ); + } +#endif + glColor4bv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4bv" ); + } +#endif +} + +// void glColor4d (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +static inline void qglColor4d( GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4d(red=%f, green=%f, blue=%f, alpha=%f)\n", red, green, blue, alpha ); + } +#endif + glColor4d( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4d" ); + } +#endif +} + +// void glColor4dv (const GLdouble *v); +static inline void qglColor4dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4dv(v=%p)\n", v ); + } +#endif + glColor4dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4dv" ); + } +#endif +} + +// void glColor4f (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static inline void qglColor4f( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4f(red=%f, green=%f, blue=%f, alpha=%f)\n", red, green, blue, alpha ); + } +#endif + glColor4f( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4f" ); + } +#endif +} + +// void glColor4fv (const GLfloat *v); +static inline void qglColor4fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4fv(v=%p)\n", v ); + } +#endif + glColor4fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4fv" ); + } +#endif +} + +// void glColor4i (GLint red, GLint green, GLint blue, GLint alpha); +static inline void qglColor4i( GLint red, GLint green, GLint blue, GLint alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4i(red=%ld, green=%ld, blue=%ld, alpha=%ld)\n", red, green, blue, alpha ); + } +#endif + glColor4i( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4i" ); + } +#endif +} + +// void glColor4iv (const GLint *v); +static inline void qglColor4iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4iv(v=%p)\n", v ); + } +#endif + glColor4iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4iv" ); + } +#endif +} + +// void glColor4s (GLshort red, GLshort green, GLshort blue, GLshort alpha); +static inline void qglColor4s( GLshort red, GLshort green, GLshort blue, GLshort alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4s(red=%d, green=%d, blue=%d, alpha=%d)\n", red, green, blue, alpha ); + } +#endif + glColor4s( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4s" ); + } +#endif +} + +// void glColor4sv (const GLshort *v); +static inline void qglColor4sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4sv(v=%p)\n", v ); + } +#endif + glColor4sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4sv" ); + } +#endif +} + +// void glColor4ub (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +static inline void qglColor4ub( GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4ub(red=%u, green=%u, blue=%u, alpha=%u)\n", red, green, blue, alpha ); + } +#endif + glColor4ub( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4ub" ); + } +#endif +} + +// void glColor4ubv (const GLubyte *v); +static inline void qglColor4ubv( const GLubyte *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4ubv(v=%p)\n", v ); + } +#endif + glColor4ubv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4ubv" ); + } +#endif +} + +// void glColor4ui (GLuint red, GLuint green, GLuint blue, GLuint alpha); +static inline void qglColor4ui( GLuint red, GLuint green, GLuint blue, GLuint alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4ui(red=%lu, green=%lu, blue=%lu, alpha=%lu)\n", red, green, blue, alpha ); + } +#endif + glColor4ui( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4ui" ); + } +#endif +} + +// void glColor4uiv (const GLuint *v); +static inline void qglColor4uiv( const GLuint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4uiv(v=%p)\n", v ); + } +#endif + glColor4uiv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4uiv" ); + } +#endif +} + +// void glColor4us (GLushort red, GLushort green, GLushort blue, GLushort alpha); +static inline void qglColor4us( GLushort red, GLushort green, GLushort blue, GLushort alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4us(red=%u, green=%u, blue=%u, alpha=%u)\n", red, green, blue, alpha ); + } +#endif + glColor4us( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4us" ); + } +#endif +} + +// void glColor4usv (const GLushort *v); +static inline void qglColor4usv( const GLushort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColor4usv(v=%p)\n", v ); + } +#endif + glColor4usv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColor4usv" ); + } +#endif +} + +// void glColorMask (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +static inline void qglColorMask( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColorMask(red=%u, green=%u, blue=%u, alpha=%u)\n", red, green, blue, alpha ); + } +#endif + glColorMask( red, green, blue, alpha ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColorMask" ); + } +#endif +} + +// void glColorMaterial (GLenum face, GLenum mode); +static inline void qglColorMaterial( GLenum face, GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColorMaterial(face=%lu, mode=%lu)\n", face, mode ); + } +#endif + glColorMaterial( face, mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColorMaterial" ); + } +#endif +} + +// void glColorPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static inline void qglColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glColorPointer(size=%ld, type=%lu, stride=%ld, pointer=%p)\n", size, type, stride, pointer ); + } +#endif + glColorPointer( size, type, stride, pointer ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glColorPointer" ); + } +#endif +} + +// void glCopyPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +static inline void qglCopyPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum type ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glCopyPixels(x=%ld, y=%ld, width=%ld, height=%ld, type=%lu)\n", x, y, width, height, type ); + } +#endif + glCopyPixels( x, y, width, height, type ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glCopyPixels" ); + } +#endif +} + +// void glCopyTexImage1D (GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +static inline void qglCopyTexImage1D( GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glCopyTexImage1D(target=%lu, level=%ld, internalFormat=%lu, x=%ld, y=%ld, width=%ld, border=%ld)\n", target, level, internalFormat, x, y, width, border ); + } +#endif + glCopyTexImage1D( target, level, internalFormat, x, y, width, border ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glCopyTexImage1D" ); + } +#endif +} + +// void glCopyTexImage2D (GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +static inline void qglCopyTexImage2D( GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glCopyTexImage2D(target=%lu, level=%ld, internalFormat=%lu, x=%ld, y=%ld, width=%ld, height=%ld, border=%ld)\n", target, level, internalFormat, x, y, width, height, border ); + } +#endif + glCopyTexImage2D( target, level, internalFormat, x, y, width, height, border ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glCopyTexImage2D" ); + } +#endif +} + +// void glCopyTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +static inline void qglCopyTexSubImage1D( GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glCopyTexSubImage1D(target=%lu, level=%ld, xoffset=%ld, x=%ld, y=%ld, width=%ld)\n", target, level, xoffset, x, y, width ); + } +#endif + glCopyTexSubImage1D( target, level, xoffset, x, y, width ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glCopyTexSubImage1D" ); + } +#endif +} + +// void glCopyTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +static inline void qglCopyTexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glCopyTexSubImage2D(target=%lu, level=%ld, xoffset=%ld, yoffset=%ld, x=%ld, y=%ld, width=%ld, height=%ld)\n", target, level, xoffset, yoffset, x, y, width, height ); + } +#endif + glCopyTexSubImage2D( target, level, xoffset, yoffset, x, y, width, height ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glCopyTexSubImage2D" ); + } +#endif +} + +// void glCullFace (GLenum mode); +static inline void qglCullFace( GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glCullFace(mode=%lu)\n", mode ); + } +#endif + glCullFace( mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glCullFace" ); + } +#endif +} + +// void glDeleteLists (GLuint list, GLsizei range); +static inline void qglDeleteLists( GLuint list, GLsizei range ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDeleteLists(list=%lu, range=%ld)\n", list, range ); + } +#endif + glDeleteLists( list, range ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDeleteLists" ); + } +#endif +} + +// void glDeleteTextures (GLsizei n, const GLuint *textures); +static inline void qglDeleteTextures( GLsizei n, const GLuint *textures ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDeleteTextures(n=%ld, textures=%p)\n", n, textures ); + } +#endif + glDeleteTextures( n, textures ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDeleteTextures" ); + } +#endif +} + +// void glDepthFunc (GLenum func); +static inline void qglDepthFunc( GLenum func ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDepthFunc(func=%lu)\n", func ); + } +#endif + glDepthFunc( func ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDepthFunc" ); + } +#endif +} + +// void glDepthMask (GLboolean flag); +static inline void qglDepthMask( GLboolean flag ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDepthMask(flag=%u)\n", flag ); + } +#endif + glDepthMask( flag ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDepthMask" ); + } +#endif +} + +// void glDepthRange (GLclampd zNear, GLclampd zFar); +static inline void qglDepthRange( GLclampd zNear, GLclampd zFar ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDepthRange(zNear=%f, zFar=%f)\n", zNear, zFar ); + } +#endif + glDepthRange( zNear, zFar ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDepthRange" ); + } +#endif +} + +// void glDisable (GLenum cap); +static inline void qglDisable( GLenum cap ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDisable(cap=%lu)\n", cap ); + } +#endif + glDisable( cap ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDisable" ); + } +#endif +} + +// void glDisableClientState (GLenum array); +static inline void qglDisableClientState( GLenum array ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDisableClientState(array=%lu)\n", array ); + } +#endif + glDisableClientState( array ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDisableClientState" ); + } +#endif +} + +// void glDrawArrays (GLenum mode, GLint first, GLsizei count); +static inline void qglDrawArrays( GLenum mode, GLint first, GLsizei count ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDrawArrays(mode=%lu, first=%ld, count=%ld)\n", mode, first, count ); + } +#endif + glDrawArrays( mode, first, count ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDrawArrays" ); + } +#endif +} + +// void glDrawBuffer (GLenum mode); +static inline void qglDrawBuffer( GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDrawBuffer(mode=%lu)\n", mode ); + } +#endif + glDrawBuffer( mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDrawBuffer" ); + } +#endif +} + +// void glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +static inline void qglDrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDrawElements(mode=%lu, count=%ld, type=%lu, indices=%p)\n", mode, count, type, indices ); + } +#endif + glDrawElements( mode, count, type, indices ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDrawElements" ); + } +#endif +} + +// void glDrawPixels (GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static inline void qglDrawPixels( GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glDrawPixels(width=%ld, height=%ld, format=%lu, type=%lu, pixels=%p)\n", width, height, format, type, pixels ); + } +#endif + glDrawPixels( width, height, format, type, pixels ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glDrawPixels" ); + } +#endif +} + +// void glEdgeFlag (GLboolean flag); +static inline void qglEdgeFlag( GLboolean flag ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEdgeFlag(flag=%u)\n", flag ); + } +#endif + glEdgeFlag( flag ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEdgeFlag" ); + } +#endif +} + +// void glEdgeFlagPointer (GLsizei stride, const GLvoid *pointer); +static inline void qglEdgeFlagPointer( GLsizei stride, const GLvoid *pointer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEdgeFlagPointer(stride=%ld, pointer=%p)\n", stride, pointer ); + } +#endif + // glEdgeFlagPointer(stride, pointer); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEdgeFlagPointer" ); + } +#endif +} + +// void glEdgeFlagv (const GLboolean *flag); +static inline void qglEdgeFlagv( const GLboolean *flag ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEdgeFlagv(flag=%p)\n", flag ); + } +#endif + glEdgeFlagv( flag ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEdgeFlagv" ); + } +#endif +} + +// void glEnable (GLenum cap); +static inline void qglEnable( GLenum cap ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEnable(cap=%lu)\n", cap ); + } +#endif + glEnable( cap ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEnable" ); + } +#endif +} + +// void glEnableClientState (GLenum array); +static inline void qglEnableClientState( GLenum array ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEnableClientState(array=%lu)\n", array ); + } +#endif + glEnableClientState( array ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEnableClientState" ); + } +#endif +} + +// void glEnd (void); +static inline void qglEnd( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEnd(void)\n" ); + } +#endif + glEnd(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + QGLBeginStarted--; + if ( !QGLBeginStarted ) { + QGLCheckError( "glEnd" ); + } +#endif +} + +// void glEndList (void); +static inline void qglEndList( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEndList(void)\n" ); + } +#endif + glEndList(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEndList" ); + } +#endif +} + +// void glEvalCoord1d (GLdouble u); +static inline void qglEvalCoord1d( GLdouble u ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalCoord1d(u=%f)\n", u ); + } +#endif + glEvalCoord1d( u ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalCoord1d" ); + } +#endif +} + +// void glEvalCoord1dv (const GLdouble *u); +static inline void qglEvalCoord1dv( const GLdouble *u ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalCoord1dv(u=%p)\n", u ); + } +#endif + glEvalCoord1dv( u ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalCoord1dv" ); + } +#endif +} + +// void glEvalCoord1f (GLfloat u); +static inline void qglEvalCoord1f( GLfloat u ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalCoord1f(u=%f)\n", u ); + } +#endif + glEvalCoord1f( u ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalCoord1f" ); + } +#endif +} + +// void glEvalCoord1fv (const GLfloat *u); +static inline void qglEvalCoord1fv( const GLfloat *u ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalCoord1fv(u=%p)\n", u ); + } +#endif + glEvalCoord1fv( u ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalCoord1fv" ); + } +#endif +} + +// void glEvalCoord2d (GLdouble u, GLdouble v); +static inline void qglEvalCoord2d( GLdouble u, GLdouble v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalCoord2d(u=%f, v=%f)\n", u, v ); + } +#endif + glEvalCoord2d( u, v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalCoord2d" ); + } +#endif +} + +// void glEvalCoord2dv (const GLdouble *u); +static inline void qglEvalCoord2dv( const GLdouble *u ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalCoord2dv(u=%p)\n", u ); + } +#endif + glEvalCoord2dv( u ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalCoord2dv" ); + } +#endif +} + +// void glEvalCoord2f (GLfloat u, GLfloat v); +static inline void qglEvalCoord2f( GLfloat u, GLfloat v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalCoord2f(u=%f, v=%f)\n", u, v ); + } +#endif + glEvalCoord2f( u, v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalCoord2f" ); + } +#endif +} + +// void glEvalCoord2fv (const GLfloat *u); +static inline void qglEvalCoord2fv( const GLfloat *u ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalCoord2fv(u=%p)\n", u ); + } +#endif + glEvalCoord2fv( u ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalCoord2fv" ); + } +#endif +} + +// void glEvalMesh1 (GLenum mode, GLint i1, GLint i2); +static inline void qglEvalMesh1( GLenum mode, GLint i1, GLint i2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalMesh1(mode=%lu, i1=%ld, i2=%ld)\n", mode, i1, i2 ); + } +#endif + glEvalMesh1( mode, i1, i2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalMesh1" ); + } +#endif +} + +// void glEvalMesh2 (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +static inline void qglEvalMesh2( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalMesh2(mode=%lu, i1=%ld, i2=%ld, j1=%ld, j2=%ld)\n", mode, i1, i2, j1, j2 ); + } +#endif + glEvalMesh2( mode, i1, i2, j1, j2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalMesh2" ); + } +#endif +} + +// void glEvalPoint1 (GLint i); +static inline void qglEvalPoint1( GLint i ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalPoint1(i=%ld)\n", i ); + } +#endif + glEvalPoint1( i ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalPoint1" ); + } +#endif +} + +// void glEvalPoint2 (GLint i, GLint j); +static inline void qglEvalPoint2( GLint i, GLint j ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glEvalPoint2(i=%ld, j=%ld)\n", i, j ); + } +#endif + glEvalPoint2( i, j ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glEvalPoint2" ); + } +#endif +} + +// void glFeedbackBuffer (GLsizei size, GLenum type, GLfloat *buffer); +static inline void qglFeedbackBuffer( GLsizei size, GLenum type, GLfloat *buffer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFeedbackBuffer(size=%ld, type=%lu, buffer=%p)\n", size, type, buffer ); + } +#endif + glFeedbackBuffer( size, type, buffer ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFeedbackBuffer" ); + } +#endif +} + +// void glFinish (void); +static inline void qglFinish( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFinish(void)\n" ); + } +#endif + glFinish(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFinish" ); + } +#endif +} + +// void glFlush (void); +static inline void qglFlush( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFlush(void)\n" ); + } +#endif + glFlush(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFlush" ); + } +#endif +} + +// void glFogf (GLenum pname, GLfloat param); +static inline void qglFogf( GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFogf(pname=%lu, param=%f)\n", pname, param ); + } +#endif + glFogf( pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFogf" ); + } +#endif +} + +// void glFogfv (GLenum pname, const GLfloat *params); +static inline void qglFogfv( GLenum pname, const GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFogfv(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glFogfv( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFogfv" ); + } +#endif +} + +// void glFogi (GLenum pname, GLint param); +static inline void qglFogi( GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFogi(pname=%lu, param=%ld)\n", pname, param ); + } +#endif + glFogi( pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFogi" ); + } +#endif +} + +// void glFogiv (GLenum pname, const GLint *params); +static inline void qglFogiv( GLenum pname, const GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFogiv(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glFogiv( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFogiv" ); + } +#endif +} + +// void glFrontFace (GLenum mode); +static inline void qglFrontFace( GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFrontFace(mode=%lu)\n", mode ); + } +#endif + glFrontFace( mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFrontFace" ); + } +#endif +} + +// void glFrustum (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +static inline void qglFrustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glFrustum(left=%f, right=%f, bottom=%f, top=%f, zNear=%f, zFar=%f)\n", left, right, bottom, top, zNear, zFar ); + } +#endif + glFrustum( left, right, bottom, top, zNear, zFar ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glFrustum" ); + } +#endif +} + +// GLuint glGenLists (GLsizei range); +static inline GLuint qglGenLists( GLsizei range ) { + GLuint returnValue; +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGenLists(range=%ld)\n", range ); + } +#endif + returnValue = glGenLists( range ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGenLists" ); + } +#endif + return returnValue; +} + +// void glGenTextures (GLsizei n, GLuint *textures); +static inline void qglGenTextures( GLsizei n, GLuint *textures ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGenTextures(n=%ld, textures=%p)\n", n, textures ); + } +#endif + glGenTextures( n, textures ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGenTextures" ); + } +#endif +} + +// void glGetBooleanv (GLenum pname, GLboolean *params); +static inline void qglGetBooleanv( GLenum pname, GLboolean *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetBooleanv(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glGetBooleanv( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetBooleanv" ); + } +#endif +} + +// void glGetClipPlane (GLenum plane, GLdouble *equation); +static inline void qglGetClipPlane( GLenum plane, GLdouble *equation ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetClipPlane(plane=%lu, equation=%p)\n", plane, equation ); + } +#endif + glGetClipPlane( plane, equation ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetClipPlane" ); + } +#endif +} + +// void glGetDoublev (GLenum pname, GLdouble *params); +static inline void qglGetDoublev( GLenum pname, GLdouble *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetDoublev(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glGetDoublev( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetDoublev" ); + } +#endif +} + +// GLenum glGetError (void); +static inline GLenum qglGetError( void ) { + GLenum returnValue; +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetError(void)\n" ); + } +#endif + returnValue = glGetError(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetError" ); + } +#endif + return returnValue; +} + +// void glGetFloatv (GLenum pname, GLfloat *params); +static inline void qglGetFloatv( GLenum pname, GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetFloatv(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glGetFloatv( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetFloatv" ); + } +#endif +} + +// void glGetIntegerv (GLenum pname, GLint *params); +static inline void qglGetIntegerv( GLenum pname, GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetIntegerv(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glGetIntegerv( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetIntegerv" ); + } +#endif +} + +// void glGetLightfv (GLenum light, GLenum pname, GLfloat *params); +static inline void qglGetLightfv( GLenum light, GLenum pname, GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetLightfv(light=%lu, pname=%lu, params=%p)\n", light, pname, params ); + } +#endif + glGetLightfv( light, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetLightfv" ); + } +#endif +} + +// void glGetLightiv (GLenum light, GLenum pname, GLint *params); +static inline void qglGetLightiv( GLenum light, GLenum pname, GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetLightiv(light=%lu, pname=%lu, params=%p)\n", light, pname, params ); + } +#endif + glGetLightiv( light, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetLightiv" ); + } +#endif +} + +// void glGetMapdv (GLenum target, GLenum query, GLdouble *v); +static inline void qglGetMapdv( GLenum target, GLenum query, GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetMapdv(target=%lu, query=%lu, v=%p)\n", target, query, v ); + } +#endif + glGetMapdv( target, query, v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetMapdv" ); + } +#endif +} + +// void glGetMapfv (GLenum target, GLenum query, GLfloat *v); +static inline void qglGetMapfv( GLenum target, GLenum query, GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetMapfv(target=%lu, query=%lu, v=%p)\n", target, query, v ); + } +#endif + glGetMapfv( target, query, v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetMapfv" ); + } +#endif +} + +// void glGetMapiv (GLenum target, GLenum query, GLint *v); +static inline void qglGetMapiv( GLenum target, GLenum query, GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetMapiv(target=%lu, query=%lu, v=%p)\n", target, query, v ); + } +#endif + glGetMapiv( target, query, v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetMapiv" ); + } +#endif +} + +// void glGetMaterialfv (GLenum face, GLenum pname, GLfloat *params); +static inline void qglGetMaterialfv( GLenum face, GLenum pname, GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetMaterialfv(face=%lu, pname=%lu, params=%p)\n", face, pname, params ); + } +#endif + glGetMaterialfv( face, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetMaterialfv" ); + } +#endif +} + +// void glGetMaterialiv (GLenum face, GLenum pname, GLint *params); +static inline void qglGetMaterialiv( GLenum face, GLenum pname, GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetMaterialiv(face=%lu, pname=%lu, params=%p)\n", face, pname, params ); + } +#endif + glGetMaterialiv( face, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetMaterialiv" ); + } +#endif +} + +// void glGetPixelMapfv (GLenum map, GLfloat *values); +static inline void qglGetPixelMapfv( GLenum map, GLfloat *values ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetPixelMapfv(map=%lu, values=%p)\n", map, values ); + } +#endif + glGetPixelMapfv( map, values ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetPixelMapfv" ); + } +#endif +} + +// void glGetPixelMapuiv (GLenum map, GLuint *values); +static inline void qglGetPixelMapuiv( GLenum map, GLuint *values ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetPixelMapuiv(map=%lu, values=%p)\n", map, values ); + } +#endif + glGetPixelMapuiv( map, values ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetPixelMapuiv" ); + } +#endif +} + +// void glGetPixelMapusv (GLenum map, GLushort *values); +static inline void qglGetPixelMapusv( GLenum map, GLushort *values ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetPixelMapusv(map=%lu, values=%p)\n", map, values ); + } +#endif + glGetPixelMapusv( map, values ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetPixelMapusv" ); + } +#endif +} + +// void glGetPointerv (GLenum pname, GLvoid* *params); +static inline void qglGetPointerv( GLenum pname, GLvoid* *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetPointerv(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glGetPointerv( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetPointerv" ); + } +#endif +} + +// void glGetPolygonStipple (GLubyte *mask); +static inline void qglGetPolygonStipple( GLubyte *mask ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetPolygonStipple(mask=%p)\n", mask ); + } +#endif + glGetPolygonStipple( mask ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetPolygonStipple" ); + } +#endif +} + +// const GLubyte * glGetString (GLenum name); +static inline const GLubyte * qglGetString( GLenum name ) { + const GLubyte * returnValue; +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetString(name=%lu)\n", name ); + } +#endif + returnValue = glGetString( name ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetString" ); + } +#endif + return returnValue; +} + +// void glGetTexEnvfv (GLenum target, GLenum pname, GLfloat *params); +static inline void qglGetTexEnvfv( GLenum target, GLenum pname, GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexEnvfv(target=%lu, pname=%lu, params=%p)\n", target, pname, params ); + } +#endif + glGetTexEnvfv( target, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexEnvfv" ); + } +#endif +} + +// void glGetTexEnviv (GLenum target, GLenum pname, GLint *params); +static inline void qglGetTexEnviv( GLenum target, GLenum pname, GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexEnviv(target=%lu, pname=%lu, params=%p)\n", target, pname, params ); + } +#endif + glGetTexEnviv( target, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexEnviv" ); + } +#endif +} + +// void glGetTexGendv (GLenum coord, GLenum pname, GLdouble *params); +static inline void qglGetTexGendv( GLenum coord, GLenum pname, GLdouble *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexGendv(coord=%lu, pname=%lu, params=%p)\n", coord, pname, params ); + } +#endif + glGetTexGendv( coord, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexGendv" ); + } +#endif +} + +// void glGetTexGenfv (GLenum coord, GLenum pname, GLfloat *params); +static inline void qglGetTexGenfv( GLenum coord, GLenum pname, GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexGenfv(coord=%lu, pname=%lu, params=%p)\n", coord, pname, params ); + } +#endif + glGetTexGenfv( coord, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexGenfv" ); + } +#endif +} + +// void glGetTexGeniv (GLenum coord, GLenum pname, GLint *params); +static inline void qglGetTexGeniv( GLenum coord, GLenum pname, GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexGeniv(coord=%lu, pname=%lu, params=%p)\n", coord, pname, params ); + } +#endif + glGetTexGeniv( coord, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexGeniv" ); + } +#endif +} + +// void glGetTexImage (GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +static inline void qglGetTexImage( GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexImage(target=%lu, level=%ld, format=%lu, type=%lu, pixels=%p)\n", target, level, format, type, pixels ); + } +#endif + glGetTexImage( target, level, format, type, pixels ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexImage" ); + } +#endif +} + +// void glGetTexLevelParameterfv (GLenum target, GLint level, GLenum pname, GLfloat *params); +static inline void qglGetTexLevelParameterfv( GLenum target, GLint level, GLenum pname, GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexLevelParameterfv(target=%lu, level=%ld, pname=%lu, params=%p)\n", target, level, pname, params ); + } +#endif + glGetTexLevelParameterfv( target, level, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexLevelParameterfv" ); + } +#endif +} + +// void glGetTexLevelParameteriv (GLenum target, GLint level, GLenum pname, GLint *params); +static inline void qglGetTexLevelParameteriv( GLenum target, GLint level, GLenum pname, GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexLevelParameteriv(target=%lu, level=%ld, pname=%lu, params=%p)\n", target, level, pname, params ); + } +#endif + glGetTexLevelParameteriv( target, level, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexLevelParameteriv" ); + } +#endif +} + +// void glGetTexParameterfv (GLenum target, GLenum pname, GLfloat *params); +static inline void qglGetTexParameterfv( GLenum target, GLenum pname, GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexParameterfv(target=%lu, pname=%lu, params=%p)\n", target, pname, params ); + } +#endif + glGetTexParameterfv( target, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexParameterfv" ); + } +#endif +} + +// void glGetTexParameteriv (GLenum target, GLenum pname, GLint *params); +static inline void qglGetTexParameteriv( GLenum target, GLenum pname, GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glGetTexParameteriv(target=%lu, pname=%lu, params=%p)\n", target, pname, params ); + } +#endif + glGetTexParameteriv( target, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glGetTexParameteriv" ); + } +#endif +} + +// void glHint (GLenum target, GLenum mode); +static inline void qglHint( GLenum target, GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glHint(target=%lu, mode=%lu)\n", target, mode ); + } +#endif + glHint( target, mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glHint" ); + } +#endif +} + +// void glIndexMask (GLuint mask); +static inline void qglIndexMask( GLuint mask ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexMask(mask=%lu)\n", mask ); + } +#endif + glIndexMask( mask ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexMask" ); + } +#endif +} + +// void glIndexPointer (GLenum type, GLsizei stride, const GLvoid *pointer); +static inline void qglIndexPointer( GLenum type, GLsizei stride, const GLvoid *pointer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexPointer(type=%lu, stride=%ld, pointer=%p)\n", type, stride, pointer ); + } +#endif + glIndexPointer( type, stride, pointer ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexPointer" ); + } +#endif +} + +// void glIndexd (GLdouble c); +static inline void qglIndexd( GLdouble c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexd(c=%f)\n", c ); + } +#endif + glIndexd( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexd" ); + } +#endif +} + +// void glIndexdv (const GLdouble *c); +static inline void qglIndexdv( const GLdouble *c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexdv(c=%p)\n", c ); + } +#endif + glIndexdv( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexdv" ); + } +#endif +} + +// void glIndexf (GLfloat c); +static inline void qglIndexf( GLfloat c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexf(c=%f)\n", c ); + } +#endif + glIndexf( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexf" ); + } +#endif +} + +// void glIndexfv (const GLfloat *c); +static inline void qglIndexfv( const GLfloat *c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexfv(c=%p)\n", c ); + } +#endif + glIndexfv( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexfv" ); + } +#endif +} + +// void glIndexi (GLint c); +static inline void qglIndexi( GLint c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexi(c=%ld)\n", c ); + } +#endif + glIndexi( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexi" ); + } +#endif +} + +// void glIndexiv (const GLint *c); +static inline void qglIndexiv( const GLint *c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexiv(c=%p)\n", c ); + } +#endif + glIndexiv( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexiv" ); + } +#endif +} + +// void glIndexs (GLshort c); +static inline void qglIndexs( GLshort c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexs(c=%d)\n", c ); + } +#endif + glIndexs( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexs" ); + } +#endif +} + +// void glIndexsv (const GLshort *c); +static inline void qglIndexsv( const GLshort *c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexsv(c=%p)\n", c ); + } +#endif + glIndexsv( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexsv" ); + } +#endif +} + +// void glIndexub (GLubyte c); +static inline void qglIndexub( GLubyte c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexub(c=%u)\n", c ); + } +#endif + glIndexub( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexub" ); + } +#endif +} + +// void glIndexubv (const GLubyte *c); +static inline void qglIndexubv( const GLubyte *c ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIndexubv(c=%p)\n", c ); + } +#endif + glIndexubv( c ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIndexubv" ); + } +#endif +} + +// void glInitNames (void); +static inline void qglInitNames( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glInitNames(void)\n" ); + } +#endif + glInitNames(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glInitNames" ); + } +#endif +} + +// void glInterleavedArrays (GLenum format, GLsizei stride, const GLvoid *pointer); +static inline void qglInterleavedArrays( GLenum format, GLsizei stride, const GLvoid *pointer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glInterleavedArrays(format=%lu, stride=%ld, pointer=%p)\n", format, stride, pointer ); + } +#endif + glInterleavedArrays( format, stride, pointer ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glInterleavedArrays" ); + } +#endif +} + +// GLboolean glIsEnabled (GLenum cap); +static inline GLboolean qglIsEnabled( GLenum cap ) { + GLboolean returnValue; +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIsEnabled(cap=%lu)\n", cap ); + } +#endif + returnValue = glIsEnabled( cap ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIsEnabled" ); + } +#endif + return returnValue; +} + +// GLboolean glIsList (GLuint list); +static inline GLboolean qglIsList( GLuint list ) { + GLboolean returnValue; +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIsList(list=%lu)\n", list ); + } +#endif + returnValue = glIsList( list ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIsList" ); + } +#endif + return returnValue; +} + +// GLboolean glIsTexture (GLuint texture); +static inline GLboolean qglIsTexture( GLuint texture ) { + GLboolean returnValue; +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glIsTexture(texture=%lu)\n", texture ); + } +#endif + returnValue = glIsTexture( texture ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glIsTexture" ); + } +#endif + return returnValue; +} + +// void glLightModelf (GLenum pname, GLfloat param); +static inline void qglLightModelf( GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLightModelf(pname=%lu, param=%f)\n", pname, param ); + } +#endif + glLightModelf( pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLightModelf" ); + } +#endif +} + +// void glLightModelfv (GLenum pname, const GLfloat *params); +static inline void qglLightModelfv( GLenum pname, const GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLightModelfv(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glLightModelfv( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLightModelfv" ); + } +#endif +} + +// void glLightModeli (GLenum pname, GLint param); +static inline void qglLightModeli( GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLightModeli(pname=%lu, param=%ld)\n", pname, param ); + } +#endif + glLightModeli( pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLightModeli" ); + } +#endif +} + +// void glLightModeliv (GLenum pname, const GLint *params); +static inline void qglLightModeliv( GLenum pname, const GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLightModeliv(pname=%lu, params=%p)\n", pname, params ); + } +#endif + glLightModeliv( pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLightModeliv" ); + } +#endif +} + +// void glLightf (GLenum light, GLenum pname, GLfloat param); +static inline void qglLightf( GLenum light, GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLightf(light=%lu, pname=%lu, param=%f)\n", light, pname, param ); + } +#endif + glLightf( light, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLightf" ); + } +#endif +} + +// void glLightfv (GLenum light, GLenum pname, const GLfloat *params); +static inline void qglLightfv( GLenum light, GLenum pname, const GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLightfv(light=%lu, pname=%lu, params=%p)\n", light, pname, params ); + } +#endif + glLightfv( light, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLightfv" ); + } +#endif +} + +// void glLighti (GLenum light, GLenum pname, GLint param); +static inline void qglLighti( GLenum light, GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLighti(light=%lu, pname=%lu, param=%ld)\n", light, pname, param ); + } +#endif + glLighti( light, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLighti" ); + } +#endif +} + +// void glLightiv (GLenum light, GLenum pname, const GLint *params); +static inline void qglLightiv( GLenum light, GLenum pname, const GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLightiv(light=%lu, pname=%lu, params=%p)\n", light, pname, params ); + } +#endif + glLightiv( light, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLightiv" ); + } +#endif +} + +// void glLineStipple (GLint factor, GLushort pattern); +static inline void qglLineStipple( GLint factor, GLushort pattern ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLineStipple(factor=%ld, pattern=%u)\n", factor, pattern ); + } +#endif + glLineStipple( factor, pattern ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLineStipple" ); + } +#endif +} + +// void glLineWidth (GLfloat width); +static inline void qglLineWidth( GLfloat width ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLineWidth(width=%f)\n", width ); + } +#endif + glLineWidth( width ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLineWidth" ); + } +#endif +} + +// void glListBase (GLuint base); +static inline void qglListBase( GLuint base ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glListBase(base=%lu)\n", base ); + } +#endif + glListBase( base ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glListBase" ); + } +#endif +} + +// void glLoadIdentity (void); +static inline void qglLoadIdentity( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLoadIdentity(void)\n" ); + } +#endif + glLoadIdentity(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLoadIdentity" ); + } +#endif +} + +// void glLoadMatrixd (const GLdouble *m); +static inline void qglLoadMatrixd( const GLdouble *m ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLoadMatrixd(m=%p)\n", m ); + } +#endif + glLoadMatrixd( m ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLoadMatrixd" ); + } +#endif +} + +// void glLoadMatrixf (const GLfloat *m); +static inline void qglLoadMatrixf( const GLfloat *m ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLoadMatrixf(m=%p)\n", m ); + } +#endif + glLoadMatrixf( m ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLoadMatrixf" ); + } +#endif +} + +// void glLoadName (GLuint name); +static inline void qglLoadName( GLuint name ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLoadName(name=%lu)\n", name ); + } +#endif + glLoadName( name ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLoadName" ); + } +#endif +} + +// void glLogicOp (GLenum opcode); +static inline void qglLogicOp( GLenum opcode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glLogicOp(opcode=%lu)\n", opcode ); + } +#endif + glLogicOp( opcode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glLogicOp" ); + } +#endif +} + +// void glMap1d (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +static inline void qglMap1d( GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMap1d(target=%lu, u1=%f, u2=%f, stride=%ld, order=%ld, points=%p)\n", target, u1, u2, stride, order, points ); + } +#endif + glMap1d( target, u1, u2, stride, order, points ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMap1d" ); + } +#endif +} + +// void glMap1f (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +static inline void qglMap1f( GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMap1f(target=%lu, u1=%f, u2=%f, stride=%ld, order=%ld, points=%p)\n", target, u1, u2, stride, order, points ); + } +#endif + glMap1f( target, u1, u2, stride, order, points ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMap1f" ); + } +#endif +} + +// void glMap2d (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +static inline void qglMap2d( GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMap2d(target=%lu, u1=%f, u2=%f, ustride=%ld, uorder=%ld, v1=%f, v2=%f, vstride=%ld, vorder=%ld, points=%p)\n", target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); + } +#endif + glMap2d( target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMap2d" ); + } +#endif +} + +// void glMap2f (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +static inline void qglMap2f( GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMap2f(target=%lu, u1=%f, u2=%f, ustride=%ld, uorder=%ld, v1=%f, v2=%f, vstride=%ld, vorder=%ld, points=%p)\n", target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); + } +#endif + glMap2f( target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMap2f" ); + } +#endif +} + +// void glMapGrid1d (GLint un, GLdouble u1, GLdouble u2); +static inline void qglMapGrid1d( GLint un, GLdouble u1, GLdouble u2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMapGrid1d(un=%ld, u1=%f, u2=%f)\n", un, u1, u2 ); + } +#endif + glMapGrid1d( un, u1, u2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMapGrid1d" ); + } +#endif +} + +// void glMapGrid1f (GLint un, GLfloat u1, GLfloat u2); +static inline void qglMapGrid1f( GLint un, GLfloat u1, GLfloat u2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMapGrid1f(un=%ld, u1=%f, u2=%f)\n", un, u1, u2 ); + } +#endif + glMapGrid1f( un, u1, u2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMapGrid1f" ); + } +#endif +} + +// void glMapGrid2d (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +static inline void qglMapGrid2d( GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMapGrid2d(un=%ld, u1=%f, u2=%f, vn=%ld, v1=%f, v2=%f)\n", un, u1, u2, vn, v1, v2 ); + } +#endif + glMapGrid2d( un, u1, u2, vn, v1, v2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMapGrid2d" ); + } +#endif +} + +// void glMapGrid2f (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +static inline void qglMapGrid2f( GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMapGrid2f(un=%ld, u1=%f, u2=%f, vn=%ld, v1=%f, v2=%f)\n", un, u1, u2, vn, v1, v2 ); + } +#endif + glMapGrid2f( un, u1, u2, vn, v1, v2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMapGrid2f" ); + } +#endif +} + +// void glMaterialf (GLenum face, GLenum pname, GLfloat param); +static inline void qglMaterialf( GLenum face, GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMaterialf(face=%lu, pname=%lu, param=%f)\n", face, pname, param ); + } +#endif + glMaterialf( face, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMaterialf" ); + } +#endif +} + +// void glMaterialfv (GLenum face, GLenum pname, const GLfloat *params); +static inline void qglMaterialfv( GLenum face, GLenum pname, const GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMaterialfv(face=%lu, pname=%lu, params=%p)\n", face, pname, params ); + } +#endif + glMaterialfv( face, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMaterialfv" ); + } +#endif +} + +// void glMateriali (GLenum face, GLenum pname, GLint param); +static inline void qglMateriali( GLenum face, GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMateriali(face=%lu, pname=%lu, param=%ld)\n", face, pname, param ); + } +#endif + glMateriali( face, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMateriali" ); + } +#endif +} + +// void glMaterialiv (GLenum face, GLenum pname, const GLint *params); +static inline void qglMaterialiv( GLenum face, GLenum pname, const GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMaterialiv(face=%lu, pname=%lu, params=%p)\n", face, pname, params ); + } +#endif + glMaterialiv( face, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMaterialiv" ); + } +#endif +} + +// void glMatrixMode (GLenum mode); +static inline void qglMatrixMode( GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMatrixMode(mode=%lu)\n", mode ); + } +#endif + glMatrixMode( mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMatrixMode" ); + } +#endif +} + +// void glMultMatrixd (const GLdouble *m); +static inline void qglMultMatrixd( const GLdouble *m ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMultMatrixd(m=%p)\n", m ); + } +#endif + glMultMatrixd( m ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMultMatrixd" ); + } +#endif +} + +// void glMultMatrixf (const GLfloat *m); +static inline void qglMultMatrixf( const GLfloat *m ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glMultMatrixf(m=%p)\n", m ); + } +#endif + glMultMatrixf( m ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glMultMatrixf" ); + } +#endif +} + +// void glNewList (GLuint list, GLenum mode); +static inline void qglNewList( GLuint list, GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNewList(list=%lu, mode=%lu)\n", list, mode ); + } +#endif + glNewList( list, mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNewList" ); + } +#endif +} + +// void glNormal3b (GLbyte nx, GLbyte ny, GLbyte nz); +static inline void qglNormal3b( GLbyte nx, GLbyte ny, GLbyte nz ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3b(nx=%d, ny=%d, nz=%d)\n", nx, ny, nz ); + } +#endif + glNormal3b( nx, ny, nz ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3b" ); + } +#endif +} + +// void glNormal3bv (const GLbyte *v); +static inline void qglNormal3bv( const GLbyte *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3bv(v=%p)\n", v ); + } +#endif + glNormal3bv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3bv" ); + } +#endif +} + +// void glNormal3d (GLdouble nx, GLdouble ny, GLdouble nz); +static inline void qglNormal3d( GLdouble nx, GLdouble ny, GLdouble nz ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3d(nx=%f, ny=%f, nz=%f)\n", nx, ny, nz ); + } +#endif + glNormal3d( nx, ny, nz ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3d" ); + } +#endif +} + +// void glNormal3dv (const GLdouble *v); +static inline void qglNormal3dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3dv(v=%p)\n", v ); + } +#endif + glNormal3dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3dv" ); + } +#endif +} + +// void glNormal3f (GLfloat nx, GLfloat ny, GLfloat nz); +static inline void qglNormal3f( GLfloat nx, GLfloat ny, GLfloat nz ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3f(nx=%f, ny=%f, nz=%f)\n", nx, ny, nz ); + } +#endif + glNormal3f( nx, ny, nz ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3f" ); + } +#endif +} + +// void glNormal3fv (const GLfloat *v); +static inline void qglNormal3fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3fv(v=%p)\n", v ); + } +#endif + glNormal3fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3fv" ); + } +#endif +} + +// void glNormal3i (GLint nx, GLint ny, GLint nz); +static inline void qglNormal3i( GLint nx, GLint ny, GLint nz ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3i(nx=%ld, ny=%ld, nz=%ld)\n", nx, ny, nz ); + } +#endif + glNormal3i( nx, ny, nz ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3i" ); + } +#endif +} + +// void glNormal3iv (const GLint *v); +static inline void qglNormal3iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3iv(v=%p)\n", v ); + } +#endif + glNormal3iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3iv" ); + } +#endif +} + +// void glNormal3s (GLshort nx, GLshort ny, GLshort nz); +static inline void qglNormal3s( GLshort nx, GLshort ny, GLshort nz ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3s(nx=%d, ny=%d, nz=%d)\n", nx, ny, nz ); + } +#endif + glNormal3s( nx, ny, nz ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3s" ); + } +#endif +} + +// void glNormal3sv (const GLshort *v); +static inline void qglNormal3sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormal3sv(v=%p)\n", v ); + } +#endif + glNormal3sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormal3sv" ); + } +#endif +} + +// void glNormalPointer (GLenum type, GLsizei stride, const GLvoid *pointer); +static inline void qglNormalPointer( GLenum type, GLsizei stride, const GLvoid *pointer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glNormalPointer(type=%lu, stride=%ld, pointer=%p)\n", type, stride, pointer ); + } +#endif + glNormalPointer( type, stride, pointer ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glNormalPointer" ); + } +#endif +} + +// void glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +static inline void qglOrtho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glOrtho(left=%f, right=%f, bottom=%f, top=%f, zNear=%f, zFar=%f)\n", left, right, bottom, top, zNear, zFar ); + } +#endif + glOrtho( left, right, bottom, top, zNear, zFar ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glOrtho" ); + } +#endif +} + +// void glPassThrough (GLfloat token); +static inline void qglPassThrough( GLfloat token ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPassThrough(token=%f)\n", token ); + } +#endif + glPassThrough( token ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPassThrough" ); + } +#endif +} + +// void glPixelMapfv (GLenum map, GLsizei mapsize, const GLfloat *values); +static inline void qglPixelMapfv( GLenum map, GLsizei mapsize, const GLfloat *values ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPixelMapfv(map=%lu, mapsize=%ld, values=%p)\n", map, mapsize, values ); + } +#endif + glPixelMapfv( map, mapsize, values ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPixelMapfv" ); + } +#endif +} + +// void glPixelMapuiv (GLenum map, GLsizei mapsize, const GLuint *values); +static inline void qglPixelMapuiv( GLenum map, GLsizei mapsize, const GLuint *values ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPixelMapuiv(map=%lu, mapsize=%ld, values=%p)\n", map, mapsize, values ); + } +#endif + glPixelMapuiv( map, mapsize, values ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPixelMapuiv" ); + } +#endif +} + +// void glPixelMapusv (GLenum map, GLsizei mapsize, const GLushort *values); +static inline void qglPixelMapusv( GLenum map, GLsizei mapsize, const GLushort *values ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPixelMapusv(map=%lu, mapsize=%ld, values=%p)\n", map, mapsize, values ); + } +#endif + glPixelMapusv( map, mapsize, values ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPixelMapusv" ); + } +#endif +} + +// void glPixelStoref (GLenum pname, GLfloat param); +static inline void qglPixelStoref( GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPixelStoref(pname=%lu, param=%f)\n", pname, param ); + } +#endif + glPixelStoref( pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPixelStoref" ); + } +#endif +} + +// void glPixelStorei (GLenum pname, GLint param); +static inline void qglPixelStorei( GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPixelStorei(pname=%lu, param=%ld)\n", pname, param ); + } +#endif + glPixelStorei( pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPixelStorei" ); + } +#endif +} + +// void glPixelTransferf (GLenum pname, GLfloat param); +static inline void qglPixelTransferf( GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPixelTransferf(pname=%lu, param=%f)\n", pname, param ); + } +#endif + glPixelTransferf( pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPixelTransferf" ); + } +#endif +} + +// void glPixelTransferi (GLenum pname, GLint param); +static inline void qglPixelTransferi( GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPixelTransferi(pname=%lu, param=%ld)\n", pname, param ); + } +#endif + glPixelTransferi( pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPixelTransferi" ); + } +#endif +} + +// void glPixelZoom (GLfloat xfactor, GLfloat yfactor); +static inline void qglPixelZoom( GLfloat xfactor, GLfloat yfactor ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPixelZoom(xfactor=%f, yfactor=%f)\n", xfactor, yfactor ); + } +#endif + glPixelZoom( xfactor, yfactor ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPixelZoom" ); + } +#endif +} + +// void glPointSize (GLfloat size); +static inline void qglPointSize( GLfloat size ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPointSize(size=%f)\n", size ); + } +#endif + glPointSize( size ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPointSize" ); + } +#endif +} + +// void glPolygonMode (GLenum face, GLenum mode); +static inline void qglPolygonMode( GLenum face, GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPolygonMode(face=%lu, mode=%lu)\n", face, mode ); + } +#endif + glPolygonMode( face, mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPolygonMode" ); + } +#endif +} + +// void glPolygonOffset (GLfloat factor, GLfloat units); +static inline void qglPolygonOffset( GLfloat factor, GLfloat units ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPolygonOffset(factor=%f, units=%f)\n", factor, units ); + } +#endif + glPolygonOffset( factor, units ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPolygonOffset" ); + } +#endif +} + +// void glPolygonStipple (const GLubyte *mask); +static inline void qglPolygonStipple( const GLubyte *mask ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPolygonStipple(mask=%p)\n", mask ); + } +#endif + glPolygonStipple( mask ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPolygonStipple" ); + } +#endif +} + +// void glPopAttrib (void); +static inline void qglPopAttrib( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPopAttrib(void)\n" ); + } +#endif + glPopAttrib(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPopAttrib" ); + } +#endif +} + +// void glPopClientAttrib (void); +static inline void qglPopClientAttrib( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPopClientAttrib(void)\n" ); + } +#endif + glPopClientAttrib(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPopClientAttrib" ); + } +#endif +} + +// void glPopMatrix (void); +static inline void qglPopMatrix( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPopMatrix(void)\n" ); + } +#endif + glPopMatrix(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPopMatrix" ); + } +#endif +} + +// void glPopName (void); +static inline void qglPopName( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPopName(void)\n" ); + } +#endif + glPopName(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPopName" ); + } +#endif +} + +// void glPrioritizeTextures (GLsizei n, const GLuint *textures, const GLclampf *priorities); +static inline void qglPrioritizeTextures( GLsizei n, const GLuint *textures, const GLclampf *priorities ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPrioritizeTextures(n=%ld, textures=%p, priorities=%p)\n", n, textures, priorities ); + } +#endif + glPrioritizeTextures( n, textures, priorities ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPrioritizeTextures" ); + } +#endif +} + +// void glPushAttrib (GLbitfield mask); +static inline void qglPushAttrib( GLbitfield mask ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPushAttrib(mask=%lu)\n", mask ); + } +#endif + glPushAttrib( mask ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPushAttrib" ); + } +#endif +} + +// void glPushClientAttrib (GLbitfield mask); +static inline void qglPushClientAttrib( GLbitfield mask ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPushClientAttrib(mask=%lu)\n", mask ); + } +#endif + glPushClientAttrib( mask ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPushClientAttrib" ); + } +#endif +} + +// void glPushMatrix (void); +static inline void qglPushMatrix( void ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPushMatrix(void)\n" ); + } +#endif + glPushMatrix(); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPushMatrix" ); + } +#endif +} + +// void glPushName (GLuint name); +static inline void qglPushName( GLuint name ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glPushName(name=%lu)\n", name ); + } +#endif + glPushName( name ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glPushName" ); + } +#endif +} + +// void glRasterPos2d (GLdouble x, GLdouble y); +static inline void qglRasterPos2d( GLdouble x, GLdouble y ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos2d(x=%f, y=%f)\n", x, y ); + } +#endif + glRasterPos2d( x, y ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos2d" ); + } +#endif +} + +// void glRasterPos2dv (const GLdouble *v); +static inline void qglRasterPos2dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos2dv(v=%p)\n", v ); + } +#endif + glRasterPos2dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos2dv" ); + } +#endif +} + +// void glRasterPos2f (GLfloat x, GLfloat y); +static inline void qglRasterPos2f( GLfloat x, GLfloat y ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos2f(x=%f, y=%f)\n", x, y ); + } +#endif + glRasterPos2f( x, y ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos2f" ); + } +#endif +} + +// void glRasterPos2fv (const GLfloat *v); +static inline void qglRasterPos2fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos2fv(v=%p)\n", v ); + } +#endif + glRasterPos2fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos2fv" ); + } +#endif +} + +// void glRasterPos2i (GLint x, GLint y); +static inline void qglRasterPos2i( GLint x, GLint y ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos2i(x=%ld, y=%ld)\n", x, y ); + } +#endif + glRasterPos2i( x, y ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos2i" ); + } +#endif +} + +// void glRasterPos2iv (const GLint *v); +static inline void qglRasterPos2iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos2iv(v=%p)\n", v ); + } +#endif + glRasterPos2iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos2iv" ); + } +#endif +} + +// void glRasterPos2s (GLshort x, GLshort y); +static inline void qglRasterPos2s( GLshort x, GLshort y ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos2s(x=%d, y=%d)\n", x, y ); + } +#endif + glRasterPos2s( x, y ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos2s" ); + } +#endif +} + +// void glRasterPos2sv (const GLshort *v); +static inline void qglRasterPos2sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos2sv(v=%p)\n", v ); + } +#endif + glRasterPos2sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos2sv" ); + } +#endif +} + +// void glRasterPos3d (GLdouble x, GLdouble y, GLdouble z); +static inline void qglRasterPos3d( GLdouble x, GLdouble y, GLdouble z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos3d(x=%f, y=%f, z=%f)\n", x, y, z ); + } +#endif + glRasterPos3d( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos3d" ); + } +#endif +} + +// void glRasterPos3dv (const GLdouble *v); +static inline void qglRasterPos3dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos3dv(v=%p)\n", v ); + } +#endif + glRasterPos3dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos3dv" ); + } +#endif +} + +// void glRasterPos3f (GLfloat x, GLfloat y, GLfloat z); +static inline void qglRasterPos3f( GLfloat x, GLfloat y, GLfloat z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos3f(x=%f, y=%f, z=%f)\n", x, y, z ); + } +#endif + glRasterPos3f( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos3f" ); + } +#endif +} + +// void glRasterPos3fv (const GLfloat *v); +static inline void qglRasterPos3fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos3fv(v=%p)\n", v ); + } +#endif + glRasterPos3fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos3fv" ); + } +#endif +} + +// void glRasterPos3i (GLint x, GLint y, GLint z); +static inline void qglRasterPos3i( GLint x, GLint y, GLint z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos3i(x=%ld, y=%ld, z=%ld)\n", x, y, z ); + } +#endif + glRasterPos3i( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos3i" ); + } +#endif +} + +// void glRasterPos3iv (const GLint *v); +static inline void qglRasterPos3iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos3iv(v=%p)\n", v ); + } +#endif + glRasterPos3iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos3iv" ); + } +#endif +} + +// void glRasterPos3s (GLshort x, GLshort y, GLshort z); +static inline void qglRasterPos3s( GLshort x, GLshort y, GLshort z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos3s(x=%d, y=%d, z=%d)\n", x, y, z ); + } +#endif + glRasterPos3s( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos3s" ); + } +#endif +} + +// void glRasterPos3sv (const GLshort *v); +static inline void qglRasterPos3sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos3sv(v=%p)\n", v ); + } +#endif + glRasterPos3sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos3sv" ); + } +#endif +} + +// void glRasterPos4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static inline void qglRasterPos4d( GLdouble x, GLdouble y, GLdouble z, GLdouble w ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos4d(x=%f, y=%f, z=%f, w=%f)\n", x, y, z, w ); + } +#endif + glRasterPos4d( x, y, z, w ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos4d" ); + } +#endif +} + +// void glRasterPos4dv (const GLdouble *v); +static inline void qglRasterPos4dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos4dv(v=%p)\n", v ); + } +#endif + glRasterPos4dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos4dv" ); + } +#endif +} + +// void glRasterPos4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static inline void qglRasterPos4f( GLfloat x, GLfloat y, GLfloat z, GLfloat w ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos4f(x=%f, y=%f, z=%f, w=%f)\n", x, y, z, w ); + } +#endif + glRasterPos4f( x, y, z, w ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos4f" ); + } +#endif +} + +// void glRasterPos4fv (const GLfloat *v); +static inline void qglRasterPos4fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos4fv(v=%p)\n", v ); + } +#endif + glRasterPos4fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos4fv" ); + } +#endif +} + +// void glRasterPos4i (GLint x, GLint y, GLint z, GLint w); +static inline void qglRasterPos4i( GLint x, GLint y, GLint z, GLint w ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos4i(x=%ld, y=%ld, z=%ld, w=%ld)\n", x, y, z, w ); + } +#endif + glRasterPos4i( x, y, z, w ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos4i" ); + } +#endif +} + +// void glRasterPos4iv (const GLint *v); +static inline void qglRasterPos4iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos4iv(v=%p)\n", v ); + } +#endif + glRasterPos4iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos4iv" ); + } +#endif +} + +// void glRasterPos4s (GLshort x, GLshort y, GLshort z, GLshort w); +static inline void qglRasterPos4s( GLshort x, GLshort y, GLshort z, GLshort w ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos4s(x=%d, y=%d, z=%d, w=%d)\n", x, y, z, w ); + } +#endif + glRasterPos4s( x, y, z, w ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos4s" ); + } +#endif +} + +// void glRasterPos4sv (const GLshort *v); +static inline void qglRasterPos4sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRasterPos4sv(v=%p)\n", v ); + } +#endif + glRasterPos4sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRasterPos4sv" ); + } +#endif +} + +// void glReadBuffer (GLenum mode); +static inline void qglReadBuffer( GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glReadBuffer(mode=%lu)\n", mode ); + } +#endif + glReadBuffer( mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glReadBuffer" ); + } +#endif +} + +// void glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +static inline void qglReadPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glReadPixels(x=%ld, y=%ld, width=%ld, height=%ld, format=%lu, type=%lu, pixels=%p)\n", x, y, width, height, format, type, pixels ); + } +#endif + glReadPixels( x, y, width, height, format, type, pixels ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glReadPixels" ); + } +#endif +} + +// void glRectd (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +static inline void qglRectd( GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRectd(x1=%f, y1=%f, x2=%f, y2=%f)\n", x1, y1, x2, y2 ); + } +#endif + glRectd( x1, y1, x2, y2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRectd" ); + } +#endif +} + +// void glRectdv (const GLdouble *v1, const GLdouble *v2); +static inline void qglRectdv( const GLdouble *v1, const GLdouble *v2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRectdv(v1=%p, v2=%p)\n", v1, v2 ); + } +#endif + glRectdv( v1, v2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRectdv" ); + } +#endif +} + +// void glRectf (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +static inline void qglRectf( GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRectf(x1=%f, y1=%f, x2=%f, y2=%f)\n", x1, y1, x2, y2 ); + } +#endif + glRectf( x1, y1, x2, y2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRectf" ); + } +#endif +} + +// void glRectfv (const GLfloat *v1, const GLfloat *v2); +static inline void qglRectfv( const GLfloat *v1, const GLfloat *v2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRectfv(v1=%p, v2=%p)\n", v1, v2 ); + } +#endif + glRectfv( v1, v2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRectfv" ); + } +#endif +} + +// void glRecti (GLint x1, GLint y1, GLint x2, GLint y2); +static inline void qglRecti( GLint x1, GLint y1, GLint x2, GLint y2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRecti(x1=%ld, y1=%ld, x2=%ld, y2=%ld)\n", x1, y1, x2, y2 ); + } +#endif + glRecti( x1, y1, x2, y2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRecti" ); + } +#endif +} + +// void glRectiv (const GLint *v1, const GLint *v2); +static inline void qglRectiv( const GLint *v1, const GLint *v2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRectiv(v1=%p, v2=%p)\n", v1, v2 ); + } +#endif + glRectiv( v1, v2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRectiv" ); + } +#endif +} + +// void glRects (GLshort x1, GLshort y1, GLshort x2, GLshort y2); +static inline void qglRects( GLshort x1, GLshort y1, GLshort x2, GLshort y2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRects(x1=%d, y1=%d, x2=%d, y2=%d)\n", x1, y1, x2, y2 ); + } +#endif + glRects( x1, y1, x2, y2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRects" ); + } +#endif +} + +// void glRectsv (const GLshort *v1, const GLshort *v2); +static inline void qglRectsv( const GLshort *v1, const GLshort *v2 ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRectsv(v1=%p, v2=%p)\n", v1, v2 ); + } +#endif + glRectsv( v1, v2 ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRectsv" ); + } +#endif +} + +// GLint glRenderMode (GLenum mode); +static inline GLint qglRenderMode( GLenum mode ) { + GLint returnValue; +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRenderMode(mode=%lu)\n", mode ); + } +#endif + returnValue = glRenderMode( mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRenderMode" ); + } +#endif + return returnValue; +} + +// void glRotated (GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +static inline void qglRotated( GLdouble angle, GLdouble x, GLdouble y, GLdouble z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRotated(angle=%f, x=%f, y=%f, z=%f)\n", angle, x, y, z ); + } +#endif + glRotated( angle, x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRotated" ); + } +#endif +} + +// void glRotatef (GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +static inline void qglRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glRotatef(angle=%f, x=%f, y=%f, z=%f)\n", angle, x, y, z ); + } +#endif + glRotatef( angle, x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glRotatef" ); + } +#endif +} + +// void glScaled (GLdouble x, GLdouble y, GLdouble z); +static inline void qglScaled( GLdouble x, GLdouble y, GLdouble z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glScaled(x=%f, y=%f, z=%f)\n", x, y, z ); + } +#endif + glScaled( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glScaled" ); + } +#endif +} + +// void glScalef (GLfloat x, GLfloat y, GLfloat z); +static inline void qglScalef( GLfloat x, GLfloat y, GLfloat z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glScalef(x=%f, y=%f, z=%f)\n", x, y, z ); + } +#endif + glScalef( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glScalef" ); + } +#endif +} + +// void glScissor (GLint x, GLint y, GLsizei width, GLsizei height); +static inline void qglScissor( GLint x, GLint y, GLsizei width, GLsizei height ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glScissor(x=%ld, y=%ld, width=%ld, height=%ld)\n", x, y, width, height ); + } +#endif + glScissor( x, y, width, height ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glScissor" ); + } +#endif +} + +// void glSelectBuffer (GLsizei size, GLuint *buffer); +static inline void qglSelectBuffer( GLsizei size, GLuint *buffer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glSelectBuffer(size=%ld, buffer=%p)\n", size, buffer ); + } +#endif + glSelectBuffer( size, buffer ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glSelectBuffer" ); + } +#endif +} + +// void glShadeModel (GLenum mode); +static inline void qglShadeModel( GLenum mode ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glShadeModel(mode=%lu)\n", mode ); + } +#endif + glShadeModel( mode ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glShadeModel" ); + } +#endif +} + +// void glStencilFunc (GLenum func, GLint ref, GLuint mask); +static inline void qglStencilFunc( GLenum func, GLint ref, GLuint mask ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glStencilFunc(func=%lu, ref=%ld, mask=%lu)\n", func, ref, mask ); + } +#endif + glStencilFunc( func, ref, mask ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glStencilFunc" ); + } +#endif +} + +// void glStencilMask (GLuint mask); +static inline void qglStencilMask( GLuint mask ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glStencilMask(mask=%lu)\n", mask ); + } +#endif + glStencilMask( mask ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glStencilMask" ); + } +#endif +} + +// void glStencilOp (GLenum fail, GLenum zfail, GLenum zpass); +static inline void qglStencilOp( GLenum fail, GLenum zfail, GLenum zpass ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glStencilOp(fail=%lu, zfail=%lu, zpass=%lu)\n", fail, zfail, zpass ); + } +#endif + glStencilOp( fail, zfail, zpass ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glStencilOp" ); + } +#endif +} + +// void glTexCoord1d (GLdouble s); +static inline void qglTexCoord1d( GLdouble s ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord1d(s=%f)\n", s ); + } +#endif + glTexCoord1d( s ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord1d" ); + } +#endif +} + +// void glTexCoord1dv (const GLdouble *v); +static inline void qglTexCoord1dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord1dv(v=%p)\n", v ); + } +#endif + glTexCoord1dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord1dv" ); + } +#endif +} + +// void glTexCoord1f (GLfloat s); +static inline void qglTexCoord1f( GLfloat s ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord1f(s=%f)\n", s ); + } +#endif + glTexCoord1f( s ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord1f" ); + } +#endif +} + +// void glTexCoord1fv (const GLfloat *v); +static inline void qglTexCoord1fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord1fv(v=%p)\n", v ); + } +#endif + glTexCoord1fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord1fv" ); + } +#endif +} + +// void glTexCoord1i (GLint s); +static inline void qglTexCoord1i( GLint s ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord1i(s=%ld)\n", s ); + } +#endif + glTexCoord1i( s ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord1i" ); + } +#endif +} + +// void glTexCoord1iv (const GLint *v); +static inline void qglTexCoord1iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord1iv(v=%p)\n", v ); + } +#endif + glTexCoord1iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord1iv" ); + } +#endif +} + +// void glTexCoord1s (GLshort s); +static inline void qglTexCoord1s( GLshort s ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord1s(s=%d)\n", s ); + } +#endif + glTexCoord1s( s ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord1s" ); + } +#endif +} + +// void glTexCoord1sv (const GLshort *v); +static inline void qglTexCoord1sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord1sv(v=%p)\n", v ); + } +#endif + glTexCoord1sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord1sv" ); + } +#endif +} + +// void glTexCoord2d (GLdouble s, GLdouble t); +static inline void qglTexCoord2d( GLdouble s, GLdouble t ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord2d(s=%f, t=%f)\n", s, t ); + } +#endif + glTexCoord2d( s, t ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord2d" ); + } +#endif +} + +// void glTexCoord2dv (const GLdouble *v); +static inline void qglTexCoord2dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord2dv(v=%p)\n", v ); + } +#endif + glTexCoord2dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord2dv" ); + } +#endif +} + +// void glTexCoord2f (GLfloat s, GLfloat t); +static inline void qglTexCoord2f( GLfloat s, GLfloat t ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord2f(s=%f, t=%f)\n", s, t ); + } +#endif + glTexCoord2f( s, t ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord2f" ); + } +#endif +} + +// void glTexCoord2fv (const GLfloat *v); +static inline void qglTexCoord2fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord2fv(v=%p)\n", v ); + } +#endif + glTexCoord2fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord2fv" ); + } +#endif +} + +// void glTexCoord2i (GLint s, GLint t); +static inline void qglTexCoord2i( GLint s, GLint t ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord2i(s=%ld, t=%ld)\n", s, t ); + } +#endif + glTexCoord2i( s, t ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord2i" ); + } +#endif +} + +// void glTexCoord2iv (const GLint *v); +static inline void qglTexCoord2iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord2iv(v=%p)\n", v ); + } +#endif + glTexCoord2iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord2iv" ); + } +#endif +} + +// void glTexCoord2s (GLshort s, GLshort t); +static inline void qglTexCoord2s( GLshort s, GLshort t ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord2s(s=%d, t=%d)\n", s, t ); + } +#endif + glTexCoord2s( s, t ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord2s" ); + } +#endif +} + +// void glTexCoord2sv (const GLshort *v); +static inline void qglTexCoord2sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord2sv(v=%p)\n", v ); + } +#endif + glTexCoord2sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord2sv" ); + } +#endif +} + +// void glTexCoord3d (GLdouble s, GLdouble t, GLdouble r); +static inline void qglTexCoord3d( GLdouble s, GLdouble t, GLdouble r ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord3d(s=%f, t=%f, r=%f)\n", s, t, r ); + } +#endif + glTexCoord3d( s, t, r ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord3d" ); + } +#endif +} + +// void glTexCoord3dv (const GLdouble *v); +static inline void qglTexCoord3dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord3dv(v=%p)\n", v ); + } +#endif + glTexCoord3dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord3dv" ); + } +#endif +} + +// void glTexCoord3f (GLfloat s, GLfloat t, GLfloat r); +static inline void qglTexCoord3f( GLfloat s, GLfloat t, GLfloat r ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord3f(s=%f, t=%f, r=%f)\n", s, t, r ); + } +#endif + glTexCoord3f( s, t, r ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord3f" ); + } +#endif +} + +// void glTexCoord3fv (const GLfloat *v); +static inline void qglTexCoord3fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord3fv(v=%p)\n", v ); + } +#endif + glTexCoord3fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord3fv" ); + } +#endif +} + +// void glTexCoord3i (GLint s, GLint t, GLint r); +static inline void qglTexCoord3i( GLint s, GLint t, GLint r ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord3i(s=%ld, t=%ld, r=%ld)\n", s, t, r ); + } +#endif + glTexCoord3i( s, t, r ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord3i" ); + } +#endif +} + +// void glTexCoord3iv (const GLint *v); +static inline void qglTexCoord3iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord3iv(v=%p)\n", v ); + } +#endif + glTexCoord3iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord3iv" ); + } +#endif +} + +// void glTexCoord3s (GLshort s, GLshort t, GLshort r); +static inline void qglTexCoord3s( GLshort s, GLshort t, GLshort r ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord3s(s=%d, t=%d, r=%d)\n", s, t, r ); + } +#endif + glTexCoord3s( s, t, r ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord3s" ); + } +#endif +} + +// void glTexCoord3sv (const GLshort *v); +static inline void qglTexCoord3sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord3sv(v=%p)\n", v ); + } +#endif + glTexCoord3sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord3sv" ); + } +#endif +} + +// void glTexCoord4d (GLdouble s, GLdouble t, GLdouble r, GLdouble q); +static inline void qglTexCoord4d( GLdouble s, GLdouble t, GLdouble r, GLdouble q ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord4d(s=%f, t=%f, r=%f, q=%f)\n", s, t, r, q ); + } +#endif + glTexCoord4d( s, t, r, q ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord4d" ); + } +#endif +} + +// void glTexCoord4dv (const GLdouble *v); +static inline void qglTexCoord4dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord4dv(v=%p)\n", v ); + } +#endif + glTexCoord4dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord4dv" ); + } +#endif +} + +// void glTexCoord4f (GLfloat s, GLfloat t, GLfloat r, GLfloat q); +static inline void qglTexCoord4f( GLfloat s, GLfloat t, GLfloat r, GLfloat q ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord4f(s=%f, t=%f, r=%f, q=%f)\n", s, t, r, q ); + } +#endif + glTexCoord4f( s, t, r, q ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord4f" ); + } +#endif +} + +// void glTexCoord4fv (const GLfloat *v); +static inline void qglTexCoord4fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord4fv(v=%p)\n", v ); + } +#endif + glTexCoord4fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord4fv" ); + } +#endif +} + +// void glTexCoord4i (GLint s, GLint t, GLint r, GLint q); +static inline void qglTexCoord4i( GLint s, GLint t, GLint r, GLint q ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord4i(s=%ld, t=%ld, r=%ld, q=%ld)\n", s, t, r, q ); + } +#endif + glTexCoord4i( s, t, r, q ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord4i" ); + } +#endif +} + +// void glTexCoord4iv (const GLint *v); +static inline void qglTexCoord4iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord4iv(v=%p)\n", v ); + } +#endif + glTexCoord4iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord4iv" ); + } +#endif +} + +// void glTexCoord4s (GLshort s, GLshort t, GLshort r, GLshort q); +static inline void qglTexCoord4s( GLshort s, GLshort t, GLshort r, GLshort q ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord4s(s=%d, t=%d, r=%d, q=%d)\n", s, t, r, q ); + } +#endif + glTexCoord4s( s, t, r, q ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord4s" ); + } +#endif +} + +// void glTexCoord4sv (const GLshort *v); +static inline void qglTexCoord4sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoord4sv(v=%p)\n", v ); + } +#endif + glTexCoord4sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoord4sv" ); + } +#endif +} + +// void glTexCoordPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static inline void qglTexCoordPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexCoordPointer(size=%ld, type=%lu, stride=%ld, pointer=%p)\n", size, type, stride, pointer ); + } +#endif + glTexCoordPointer( size, type, stride, pointer ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexCoordPointer" ); + } +#endif +} + +// void glTexEnvf (GLenum target, GLenum pname, GLfloat param); +static inline void qglTexEnvf( GLenum target, GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexEnvf(target=%lu, pname=%lu, param=%f)\n", target, pname, param ); + } +#endif + glTexEnvf( target, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexEnvf" ); + } +#endif +} + +// void glTexEnvfv (GLenum target, GLenum pname, const GLfloat *params); +static inline void qglTexEnvfv( GLenum target, GLenum pname, const GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexEnvfv(target=%lu, pname=%lu, params=%p)\n", target, pname, params ); + } +#endif + glTexEnvfv( target, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexEnvfv" ); + } +#endif +} + +// void glTexEnvi (GLenum target, GLenum pname, GLint param); +static inline void qglTexEnvi( GLenum target, GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexEnvi(target=%lu, pname=%lu, param=%ld)\n", target, pname, param ); + } +#endif + glTexEnvi( target, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexEnvi" ); + } +#endif +} + +// void glTexEnviv (GLenum target, GLenum pname, const GLint *params); +static inline void qglTexEnviv( GLenum target, GLenum pname, const GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexEnviv(target=%lu, pname=%lu, params=%p)\n", target, pname, params ); + } +#endif + glTexEnviv( target, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexEnviv" ); + } +#endif +} + +// void glTexGend (GLenum coord, GLenum pname, GLdouble param); +static inline void qglTexGend( GLenum coord, GLenum pname, GLdouble param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexGend(coord=%lu, pname=%lu, param=%f)\n", coord, pname, param ); + } +#endif + glTexGend( coord, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexGend" ); + } +#endif +} + +// void glTexGendv (GLenum coord, GLenum pname, const GLdouble *params); +static inline void qglTexGendv( GLenum coord, GLenum pname, const GLdouble *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexGendv(coord=%lu, pname=%lu, params=%p)\n", coord, pname, params ); + } +#endif + glTexGendv( coord, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexGendv" ); + } +#endif +} + +// void glTexGenf (GLenum coord, GLenum pname, GLfloat param); +static inline void qglTexGenf( GLenum coord, GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexGenf(coord=%lu, pname=%lu, param=%f)\n", coord, pname, param ); + } +#endif + glTexGenf( coord, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexGenf" ); + } +#endif +} + +// void glTexGenfv (GLenum coord, GLenum pname, const GLfloat *params); +static inline void qglTexGenfv( GLenum coord, GLenum pname, const GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexGenfv(coord=%lu, pname=%lu, params=%p)\n", coord, pname, params ); + } +#endif + glTexGenfv( coord, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexGenfv" ); + } +#endif +} + +// void glTexGeni (GLenum coord, GLenum pname, GLint param); +static inline void qglTexGeni( GLenum coord, GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexGeni(coord=%lu, pname=%lu, param=%ld)\n", coord, pname, param ); + } +#endif + glTexGeni( coord, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexGeni" ); + } +#endif +} + +// void glTexGeniv (GLenum coord, GLenum pname, const GLint *params); +static inline void qglTexGeniv( GLenum coord, GLenum pname, const GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexGeniv(coord=%lu, pname=%lu, params=%p)\n", coord, pname, params ); + } +#endif + glTexGeniv( coord, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexGeniv" ); + } +#endif +} + +// void glTexImage1D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static inline void qglTexImage1D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexImage1D(target=%lu, level=%ld, internalformat=%ld, width=%ld, border=%ld, format=%lu, type=%lu, pixels=%p)\n", target, level, internalformat, width, border, format, type, pixels ); + } +#endif + glTexImage1D( target, level, internalformat, width, border, format, type, pixels ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexImage1D" ); + } +#endif +} + +// void glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static inline void qglTexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexImage2D(target=%lu, level=%ld, internalformat=%ld, width=%ld, height=%ld, border=%ld, format=%lu, type=%lu, pixels=%p)\n", target, level, internalformat, width, height, border, format, type, pixels ); + } +#endif + glTexImage2D( target, level, internalformat, width, height, border, format, type, pixels ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexImage2D" ); + } +#endif +} + +// void glTexParameterf (GLenum target, GLenum pname, GLfloat param); +static inline void qglTexParameterf( GLenum target, GLenum pname, GLfloat param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexParameterf(target=%lu, pname=%lu, param=%f)\n", target, pname, param ); + } +#endif + glTexParameterf( target, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexParameterf" ); + } +#endif +} + +// void glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params); +static inline void qglTexParameterfv( GLenum target, GLenum pname, const GLfloat *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexParameterfv(target=%lu, pname=%lu, params=%p)\n", target, pname, params ); + } +#endif + glTexParameterfv( target, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexParameterfv" ); + } +#endif +} + +// void glTexParameteri (GLenum target, GLenum pname, GLint param); +static inline void qglTexParameteri( GLenum target, GLenum pname, GLint param ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexParameteri(target=%lu, pname=%lu, param=%ld)\n", target, pname, param ); + } +#endif + glTexParameteri( target, pname, param ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexParameteri" ); + } +#endif +} + +// void glTexParameteriv (GLenum target, GLenum pname, const GLint *params); +static inline void qglTexParameteriv( GLenum target, GLenum pname, const GLint *params ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexParameteriv(target=%lu, pname=%lu, params=%p)\n", target, pname, params ); + } +#endif + glTexParameteriv( target, pname, params ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexParameteriv" ); + } +#endif +} + +// void glTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +static inline void qglTexSubImage1D( GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexSubImage1D(target=%lu, level=%ld, xoffset=%ld, width=%ld, format=%lu, type=%lu, pixels=%p)\n", target, level, xoffset, width, format, type, pixels ); + } +#endif + glTexSubImage1D( target, level, xoffset, width, format, type, pixels ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexSubImage1D" ); + } +#endif +} + +// void glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static inline void qglTexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTexSubImage2D(target=%lu, level=%ld, xoffset=%ld, yoffset=%ld, width=%ld, height=%ld, format=%lu, type=%lu, pixels=%p)\n", target, level, xoffset, yoffset, width, height, format, type, pixels ); + } +#endif + glTexSubImage2D( target, level, xoffset, yoffset, width, height, format, type, pixels ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTexSubImage2D" ); + } +#endif +} + +// void glTranslated (GLdouble x, GLdouble y, GLdouble z); +static inline void qglTranslated( GLdouble x, GLdouble y, GLdouble z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTranslated(x=%f, y=%f, z=%f)\n", x, y, z ); + } +#endif + glTranslated( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTranslated" ); + } +#endif +} + +// void glTranslatef (GLfloat x, GLfloat y, GLfloat z); +static inline void qglTranslatef( GLfloat x, GLfloat y, GLfloat z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glTranslatef(x=%f, y=%f, z=%f)\n", x, y, z ); + } +#endif + glTranslatef( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glTranslatef" ); + } +#endif +} + +// void glVertex2d (GLdouble x, GLdouble y); +static inline void qglVertex2d( GLdouble x, GLdouble y ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex2d(x=%f, y=%f)\n", x, y ); + } +#endif + glVertex2d( x, y ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex2d" ); + } +#endif +} + +// void glVertex2dv (const GLdouble *v); +static inline void qglVertex2dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex2dv(v=%p)\n", v ); + } +#endif + glVertex2dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex2dv" ); + } +#endif +} + +// void glVertex2f (GLfloat x, GLfloat y); +static inline void qglVertex2f( GLfloat x, GLfloat y ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex2f(x=%f, y=%f)\n", x, y ); + } +#endif + glVertex2f( x, y ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex2f" ); + } +#endif +} + +// void glVertex2fv (const GLfloat *v); +static inline void qglVertex2fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex2fv(v=%p)\n", v ); + } +#endif + glVertex2fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex2fv" ); + } +#endif +} + +// void glVertex2i (GLint x, GLint y); +static inline void qglVertex2i( GLint x, GLint y ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex2i(x=%ld, y=%ld)\n", x, y ); + } +#endif + glVertex2i( x, y ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex2i" ); + } +#endif +} + +// void glVertex2iv (const GLint *v); +static inline void qglVertex2iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex2iv(v=%p)\n", v ); + } +#endif + glVertex2iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex2iv" ); + } +#endif +} + +// void glVertex2s (GLshort x, GLshort y); +static inline void qglVertex2s( GLshort x, GLshort y ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex2s(x=%d, y=%d)\n", x, y ); + } +#endif + glVertex2s( x, y ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex2s" ); + } +#endif +} + +// void glVertex2sv (const GLshort *v); +static inline void qglVertex2sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex2sv(v=%p)\n", v ); + } +#endif + glVertex2sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex2sv" ); + } +#endif +} + +// void glVertex3d (GLdouble x, GLdouble y, GLdouble z); +static inline void qglVertex3d( GLdouble x, GLdouble y, GLdouble z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex3d(x=%f, y=%f, z=%f)\n", x, y, z ); + } +#endif + glVertex3d( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex3d" ); + } +#endif +} + +// void glVertex3dv (const GLdouble *v); +static inline void qglVertex3dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex3dv(v=%p)\n", v ); + } +#endif + glVertex3dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex3dv" ); + } +#endif +} + +// void glVertex3f (GLfloat x, GLfloat y, GLfloat z); +static inline void qglVertex3f( GLfloat x, GLfloat y, GLfloat z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex3f(x=%f, y=%f, z=%f)\n", x, y, z ); + } +#endif + glVertex3f( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex3f" ); + } +#endif +} + +// void glVertex3fv (const GLfloat *v); +static inline void qglVertex3fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex3fv(v=%p)\n", v ); + } +#endif + glVertex3fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex3fv" ); + } +#endif +} + +// void glVertex3i (GLint x, GLint y, GLint z); +static inline void qglVertex3i( GLint x, GLint y, GLint z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex3i(x=%ld, y=%ld, z=%ld)\n", x, y, z ); + } +#endif + glVertex3i( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex3i" ); + } +#endif +} + +// void glVertex3iv (const GLint *v); +static inline void qglVertex3iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex3iv(v=%p)\n", v ); + } +#endif + glVertex3iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex3iv" ); + } +#endif +} + +// void glVertex3s (GLshort x, GLshort y, GLshort z); +static inline void qglVertex3s( GLshort x, GLshort y, GLshort z ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex3s(x=%d, y=%d, z=%d)\n", x, y, z ); + } +#endif + glVertex3s( x, y, z ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex3s" ); + } +#endif +} + +// void glVertex3sv (const GLshort *v); +static inline void qglVertex3sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex3sv(v=%p)\n", v ); + } +#endif + glVertex3sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex3sv" ); + } +#endif +} + +// void glVertex4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static inline void qglVertex4d( GLdouble x, GLdouble y, GLdouble z, GLdouble w ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex4d(x=%f, y=%f, z=%f, w=%f)\n", x, y, z, w ); + } +#endif + glVertex4d( x, y, z, w ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex4d" ); + } +#endif +} + +// void glVertex4dv (const GLdouble *v); +static inline void qglVertex4dv( const GLdouble *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex4dv(v=%p)\n", v ); + } +#endif + glVertex4dv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex4dv" ); + } +#endif +} + +// void glVertex4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static inline void qglVertex4f( GLfloat x, GLfloat y, GLfloat z, GLfloat w ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex4f(x=%f, y=%f, z=%f, w=%f)\n", x, y, z, w ); + } +#endif + glVertex4f( x, y, z, w ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex4f" ); + } +#endif +} + +// void glVertex4fv (const GLfloat *v); +static inline void qglVertex4fv( const GLfloat *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex4fv(v=%p)\n", v ); + } +#endif + glVertex4fv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex4fv" ); + } +#endif +} + +// void glVertex4i (GLint x, GLint y, GLint z, GLint w); +static inline void qglVertex4i( GLint x, GLint y, GLint z, GLint w ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex4i(x=%ld, y=%ld, z=%ld, w=%ld)\n", x, y, z, w ); + } +#endif + glVertex4i( x, y, z, w ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex4i" ); + } +#endif +} + +// void glVertex4iv (const GLint *v); +static inline void qglVertex4iv( const GLint *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex4iv(v=%p)\n", v ); + } +#endif + glVertex4iv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex4iv" ); + } +#endif +} + +// void glVertex4s (GLshort x, GLshort y, GLshort z, GLshort w); +static inline void qglVertex4s( GLshort x, GLshort y, GLshort z, GLshort w ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex4s(x=%d, y=%d, z=%d, w=%d)\n", x, y, z, w ); + } +#endif + glVertex4s( x, y, z, w ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex4s" ); + } +#endif +} + +// void glVertex4sv (const GLshort *v); +static inline void qglVertex4sv( const GLshort *v ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertex4sv(v=%p)\n", v ); + } +#endif + glVertex4sv( v ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertex4sv" ); + } +#endif +} + +// void glVertexPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static inline void qglVertexPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glVertexPointer(size=%ld, type=%lu, stride=%ld, pointer=%p)\n", size, type, stride, pointer ); + } +#endif + glVertexPointer( size, type, stride, pointer ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glVertexPointer" ); + } +#endif +} + +// void glViewport (GLint x, GLint y, GLsizei width, GLsizei height); +static inline void qglViewport( GLint x, GLint y, GLsizei width, GLsizei height ) { +#if !defined( NDEBUG ) && defined( QGL_LOG_GL_CALLS ) + if ( QGLLogGLCalls ) { + fprintf( QGLDebugFile(), "glViewport(x=%ld, y=%ld, width=%ld, height=%ld)\n", x, y, width, height ); + } +#endif + glViewport( x, y, width, height ); +#if !defined( NDEBUG ) && defined( QGL_CHECK_GL_ERRORS ) + if ( !QGLBeginStarted ) { + QGLCheckError( "glViewport" ); + } +#endif +} + +// Prevent calls to the 'normal' GL functions +#define glAccum CALL_THE_QGL_VERSION_OF_glAccum +#define glAlphaFunc CALL_THE_QGL_VERSION_OF_glAlphaFunc +#define glAreTexturesResident CALL_THE_QGL_VERSION_OF_glAreTexturesResident +#define glArrayElement CALL_THE_QGL_VERSION_OF_glArrayElement +#define glBegin CALL_THE_QGL_VERSION_OF_glBegin +#define glBindTexture CALL_THE_QGL_VERSION_OF_glBindTexture +#define glBitmap CALL_THE_QGL_VERSION_OF_glBitmap +#define glBlendFunc CALL_THE_QGL_VERSION_OF_glBlendFunc +#define glCallList CALL_THE_QGL_VERSION_OF_glCallList +#define glCallLists CALL_THE_QGL_VERSION_OF_glCallLists +#define glClear CALL_THE_QGL_VERSION_OF_glClear +#define glClearAccum CALL_THE_QGL_VERSION_OF_glClearAccum +#define glClearColor CALL_THE_QGL_VERSION_OF_glClearColor +#define glClearDepth CALL_THE_QGL_VERSION_OF_glClearDepth +#define glClearIndex CALL_THE_QGL_VERSION_OF_glClearIndex +#define glClearStencil CALL_THE_QGL_VERSION_OF_glClearStencil +#define glClipPlane CALL_THE_QGL_VERSION_OF_glClipPlane +#define glColor3b CALL_THE_QGL_VERSION_OF_glColor3b +#define glColor3bv CALL_THE_QGL_VERSION_OF_glColor3bv +#define glColor3d CALL_THE_QGL_VERSION_OF_glColor3d +#define glColor3dv CALL_THE_QGL_VERSION_OF_glColor3dv +#define glColor3f CALL_THE_QGL_VERSION_OF_glColor3f +#define glColor3fv CALL_THE_QGL_VERSION_OF_glColor3fv +#define glColor3i CALL_THE_QGL_VERSION_OF_glColor3i +#define glColor3iv CALL_THE_QGL_VERSION_OF_glColor3iv +#define glColor3s CALL_THE_QGL_VERSION_OF_glColor3s +#define glColor3sv CALL_THE_QGL_VERSION_OF_glColor3sv +#define glColor3ub CALL_THE_QGL_VERSION_OF_glColor3ub +#define glColor3ubv CALL_THE_QGL_VERSION_OF_glColor3ubv +#define glColor3ui CALL_THE_QGL_VERSION_OF_glColor3ui +#define glColor3uiv CALL_THE_QGL_VERSION_OF_glColor3uiv +#define glColor3us CALL_THE_QGL_VERSION_OF_glColor3us +#define glColor3usv CALL_THE_QGL_VERSION_OF_glColor3usv +#define glColor4b CALL_THE_QGL_VERSION_OF_glColor4b +#define glColor4bv CALL_THE_QGL_VERSION_OF_glColor4bv +#define glColor4d CALL_THE_QGL_VERSION_OF_glColor4d +#define glColor4dv CALL_THE_QGL_VERSION_OF_glColor4dv +#define glColor4f CALL_THE_QGL_VERSION_OF_glColor4f +#define glColor4fv CALL_THE_QGL_VERSION_OF_glColor4fv +#define glColor4i CALL_THE_QGL_VERSION_OF_glColor4i +#define glColor4iv CALL_THE_QGL_VERSION_OF_glColor4iv +#define glColor4s CALL_THE_QGL_VERSION_OF_glColor4s +#define glColor4sv CALL_THE_QGL_VERSION_OF_glColor4sv +#define glColor4ub CALL_THE_QGL_VERSION_OF_glColor4ub +#define glColor4ubv CALL_THE_QGL_VERSION_OF_glColor4ubv +#define glColor4ui CALL_THE_QGL_VERSION_OF_glColor4ui +#define glColor4uiv CALL_THE_QGL_VERSION_OF_glColor4uiv +#define glColor4us CALL_THE_QGL_VERSION_OF_glColor4us +#define glColor4usv CALL_THE_QGL_VERSION_OF_glColor4usv +#define glColorMask CALL_THE_QGL_VERSION_OF_glColorMask +#define glColorMaterial CALL_THE_QGL_VERSION_OF_glColorMaterial +#define glColorPointer CALL_THE_QGL_VERSION_OF_glColorPointer +#define glCopyPixels CALL_THE_QGL_VERSION_OF_glCopyPixels +#define glCopyTexImage1D CALL_THE_QGL_VERSION_OF_glCopyTexImage1D +#define glCopyTexImage2D CALL_THE_QGL_VERSION_OF_glCopyTexImage2D +#define glCopyTexSubImage1D CALL_THE_QGL_VERSION_OF_glCopyTexSubImage1D +#define glCopyTexSubImage2D CALL_THE_QGL_VERSION_OF_glCopyTexSubImage2D +#define glCullFace CALL_THE_QGL_VERSION_OF_glCullFace +#define glDeleteLists CALL_THE_QGL_VERSION_OF_glDeleteLists +#define glDeleteTextures CALL_THE_QGL_VERSION_OF_glDeleteTextures +#define glDepthFunc CALL_THE_QGL_VERSION_OF_glDepthFunc +#define glDepthMask CALL_THE_QGL_VERSION_OF_glDepthMask +#define glDepthRange CALL_THE_QGL_VERSION_OF_glDepthRange +#define glDisable CALL_THE_QGL_VERSION_OF_glDisable +#define glDisableClientState CALL_THE_QGL_VERSION_OF_glDisableClientState +#define glDrawArrays CALL_THE_QGL_VERSION_OF_glDrawArrays +#define glDrawBuffer CALL_THE_QGL_VERSION_OF_glDrawBuffer +#define glDrawElements CALL_THE_QGL_VERSION_OF_glDrawElements +#define glDrawPixels CALL_THE_QGL_VERSION_OF_glDrawPixels +#define glEdgeFlag CALL_THE_QGL_VERSION_OF_glEdgeFlag +#define glEdgeFlagPointer CALL_THE_QGL_VERSION_OF_glEdgeFlagPointer +#define glEdgeFlagv CALL_THE_QGL_VERSION_OF_glEdgeFlagv +#define glEnable CALL_THE_QGL_VERSION_OF_glEnable +#define glEnableClientState CALL_THE_QGL_VERSION_OF_glEnableClientState +#define glEnd CALL_THE_QGL_VERSION_OF_glEnd +#define glEndList CALL_THE_QGL_VERSION_OF_glEndList +#define glEvalCoord1d CALL_THE_QGL_VERSION_OF_glEvalCoord1d +#define glEvalCoord1dv CALL_THE_QGL_VERSION_OF_glEvalCoord1dv +#define glEvalCoord1f CALL_THE_QGL_VERSION_OF_glEvalCoord1f +#define glEvalCoord1fv CALL_THE_QGL_VERSION_OF_glEvalCoord1fv +#define glEvalCoord2d CALL_THE_QGL_VERSION_OF_glEvalCoord2d +#define glEvalCoord2dv CALL_THE_QGL_VERSION_OF_glEvalCoord2dv +#define glEvalCoord2f CALL_THE_QGL_VERSION_OF_glEvalCoord2f +#define glEvalCoord2fv CALL_THE_QGL_VERSION_OF_glEvalCoord2fv +#define glEvalMesh1 CALL_THE_QGL_VERSION_OF_glEvalMesh1 +#define glEvalMesh2 CALL_THE_QGL_VERSION_OF_glEvalMesh2 +#define glEvalPoint1 CALL_THE_QGL_VERSION_OF_glEvalPoint1 +#define glEvalPoint2 CALL_THE_QGL_VERSION_OF_glEvalPoint2 +#define glFeedbackBuffer CALL_THE_QGL_VERSION_OF_glFeedbackBuffer +#define glFinish CALL_THE_QGL_VERSION_OF_glFinish +#define glFlush CALL_THE_QGL_VERSION_OF_glFlush +#define glFogf CALL_THE_QGL_VERSION_OF_glFogf +#define glFogfv CALL_THE_QGL_VERSION_OF_glFogfv +#define glFogi CALL_THE_QGL_VERSION_OF_glFogi +#define glFogiv CALL_THE_QGL_VERSION_OF_glFogiv +#define glFrontFace CALL_THE_QGL_VERSION_OF_glFrontFace +#define glFrustum CALL_THE_QGL_VERSION_OF_glFrustum +#define glGenLists CALL_THE_QGL_VERSION_OF_glGenLists +#define glGenTextures CALL_THE_QGL_VERSION_OF_glGenTextures +#define glGetBooleanv CALL_THE_QGL_VERSION_OF_glGetBooleanv +#define glGetClipPlane CALL_THE_QGL_VERSION_OF_glGetClipPlane +#define glGetDoublev CALL_THE_QGL_VERSION_OF_glGetDoublev +#define glGetError CALL_THE_QGL_VERSION_OF_glGetError +#define glGetFloatv CALL_THE_QGL_VERSION_OF_glGetFloatv +#define glGetIntegerv CALL_THE_QGL_VERSION_OF_glGetIntegerv +#define glGetLightfv CALL_THE_QGL_VERSION_OF_glGetLightfv +#define glGetLightiv CALL_THE_QGL_VERSION_OF_glGetLightiv +#define glGetMapdv CALL_THE_QGL_VERSION_OF_glGetMapdv +#define glGetMapfv CALL_THE_QGL_VERSION_OF_glGetMapfv +#define glGetMapiv CALL_THE_QGL_VERSION_OF_glGetMapiv +#define glGetMaterialfv CALL_THE_QGL_VERSION_OF_glGetMaterialfv +#define glGetMaterialiv CALL_THE_QGL_VERSION_OF_glGetMaterialiv +#define glGetPixelMapfv CALL_THE_QGL_VERSION_OF_glGetPixelMapfv +#define glGetPixelMapuiv CALL_THE_QGL_VERSION_OF_glGetPixelMapuiv +#define glGetPixelMapusv CALL_THE_QGL_VERSION_OF_glGetPixelMapusv +#define glGetPointerv CALL_THE_QGL_VERSION_OF_glGetPointerv +#define glGetPolygonStipple CALL_THE_QGL_VERSION_OF_glGetPolygonStipple +#define glGetString CALL_THE_QGL_VERSION_OF_glGetString +#define glGetTexEnvfv CALL_THE_QGL_VERSION_OF_glGetTexEnvfv +#define glGetTexEnviv CALL_THE_QGL_VERSION_OF_glGetTexEnviv +#define glGetTexGendv CALL_THE_QGL_VERSION_OF_glGetTexGendv +#define glGetTexGenfv CALL_THE_QGL_VERSION_OF_glGetTexGenfv +#define glGetTexGeniv CALL_THE_QGL_VERSION_OF_glGetTexGeniv +#define glGetTexImage CALL_THE_QGL_VERSION_OF_glGetTexImage +#define glGetTexLevelParameterfv CALL_THE_QGL_VERSION_OF_glGetTexLevelParameterfv +#define glGetTexLevelParameteriv CALL_THE_QGL_VERSION_OF_glGetTexLevelParameteriv +#define glGetTexParameterfv CALL_THE_QGL_VERSION_OF_glGetTexParameterfv +#define glGetTexParameteriv CALL_THE_QGL_VERSION_OF_glGetTexParameteriv +#define glHint CALL_THE_QGL_VERSION_OF_glHint +#define glIndexMask CALL_THE_QGL_VERSION_OF_glIndexMask +#define glIndexPointer CALL_THE_QGL_VERSION_OF_glIndexPointer +#define glIndexd CALL_THE_QGL_VERSION_OF_glIndexd +#define glIndexdv CALL_THE_QGL_VERSION_OF_glIndexdv +#define glIndexf CALL_THE_QGL_VERSION_OF_glIndexf +#define glIndexfv CALL_THE_QGL_VERSION_OF_glIndexfv +#define glIndexi CALL_THE_QGL_VERSION_OF_glIndexi +#define glIndexiv CALL_THE_QGL_VERSION_OF_glIndexiv +#define glIndexs CALL_THE_QGL_VERSION_OF_glIndexs +#define glIndexsv CALL_THE_QGL_VERSION_OF_glIndexsv +#define glIndexub CALL_THE_QGL_VERSION_OF_glIndexub +#define glIndexubv CALL_THE_QGL_VERSION_OF_glIndexubv +#define glInitNames CALL_THE_QGL_VERSION_OF_glInitNames +#define glInterleavedArrays CALL_THE_QGL_VERSION_OF_glInterleavedArrays +#define glIsEnabled CALL_THE_QGL_VERSION_OF_glIsEnabled +#define glIsList CALL_THE_QGL_VERSION_OF_glIsList +#define glIsTexture CALL_THE_QGL_VERSION_OF_glIsTexture +#define glLightModelf CALL_THE_QGL_VERSION_OF_glLightModelf +#define glLightModelfv CALL_THE_QGL_VERSION_OF_glLightModelfv +#define glLightModeli CALL_THE_QGL_VERSION_OF_glLightModeli +#define glLightModeliv CALL_THE_QGL_VERSION_OF_glLightModeliv +#define glLightf CALL_THE_QGL_VERSION_OF_glLightf +#define glLightfv CALL_THE_QGL_VERSION_OF_glLightfv +#define glLighti CALL_THE_QGL_VERSION_OF_glLighti +#define glLightiv CALL_THE_QGL_VERSION_OF_glLightiv +#define glLineStipple CALL_THE_QGL_VERSION_OF_glLineStipple +#define glLineWidth CALL_THE_QGL_VERSION_OF_glLineWidth +#define glListBase CALL_THE_QGL_VERSION_OF_glListBase +#define glLoadIdentity CALL_THE_QGL_VERSION_OF_glLoadIdentity +#define glLoadMatrixd CALL_THE_QGL_VERSION_OF_glLoadMatrixd +#define glLoadMatrixf CALL_THE_QGL_VERSION_OF_glLoadMatrixf +#define glLoadName CALL_THE_QGL_VERSION_OF_glLoadName +#define glLogicOp CALL_THE_QGL_VERSION_OF_glLogicOp +#define glMap1d CALL_THE_QGL_VERSION_OF_glMap1d +#define glMap1f CALL_THE_QGL_VERSION_OF_glMap1f +#define glMap2d CALL_THE_QGL_VERSION_OF_glMap2d +#define glMap2f CALL_THE_QGL_VERSION_OF_glMap2f +#define glMapGrid1d CALL_THE_QGL_VERSION_OF_glMapGrid1d +#define glMapGrid1f CALL_THE_QGL_VERSION_OF_glMapGrid1f +#define glMapGrid2d CALL_THE_QGL_VERSION_OF_glMapGrid2d +#define glMapGrid2f CALL_THE_QGL_VERSION_OF_glMapGrid2f +#define glMaterialf CALL_THE_QGL_VERSION_OF_glMaterialf +#define glMaterialfv CALL_THE_QGL_VERSION_OF_glMaterialfv +#define glMateriali CALL_THE_QGL_VERSION_OF_glMateriali +#define glMaterialiv CALL_THE_QGL_VERSION_OF_glMaterialiv +#define glMatrixMode CALL_THE_QGL_VERSION_OF_glMatrixMode +#define glMultMatrixd CALL_THE_QGL_VERSION_OF_glMultMatrixd +#define glMultMatrixf CALL_THE_QGL_VERSION_OF_glMultMatrixf +#define glNewList CALL_THE_QGL_VERSION_OF_glNewList +#define glNormal3b CALL_THE_QGL_VERSION_OF_glNormal3b +#define glNormal3bv CALL_THE_QGL_VERSION_OF_glNormal3bv +#define glNormal3d CALL_THE_QGL_VERSION_OF_glNormal3d +#define glNormal3dv CALL_THE_QGL_VERSION_OF_glNormal3dv +#define glNormal3f CALL_THE_QGL_VERSION_OF_glNormal3f +#define glNormal3fv CALL_THE_QGL_VERSION_OF_glNormal3fv +#define glNormal3i CALL_THE_QGL_VERSION_OF_glNormal3i +#define glNormal3iv CALL_THE_QGL_VERSION_OF_glNormal3iv +#define glNormal3s CALL_THE_QGL_VERSION_OF_glNormal3s +#define glNormal3sv CALL_THE_QGL_VERSION_OF_glNormal3sv +#define glNormalPointer CALL_THE_QGL_VERSION_OF_glNormalPointer +#define glOrtho CALL_THE_QGL_VERSION_OF_glOrtho +#define glPassThrough CALL_THE_QGL_VERSION_OF_glPassThrough +#define glPixelMapfv CALL_THE_QGL_VERSION_OF_glPixelMapfv +#define glPixelMapuiv CALL_THE_QGL_VERSION_OF_glPixelMapuiv +#define glPixelMapusv CALL_THE_QGL_VERSION_OF_glPixelMapusv +#define glPixelStoref CALL_THE_QGL_VERSION_OF_glPixelStoref +#define glPixelStorei CALL_THE_QGL_VERSION_OF_glPixelStorei +#define glPixelTransferf CALL_THE_QGL_VERSION_OF_glPixelTransferf +#define glPixelTransferi CALL_THE_QGL_VERSION_OF_glPixelTransferi +#define glPixelZoom CALL_THE_QGL_VERSION_OF_glPixelZoom +#define glPointSize CALL_THE_QGL_VERSION_OF_glPointSize +#define glPolygonMode CALL_THE_QGL_VERSION_OF_glPolygonMode +#define glPolygonOffset CALL_THE_QGL_VERSION_OF_glPolygonOffset +#define glPolygonStipple CALL_THE_QGL_VERSION_OF_glPolygonStipple +#define glPopAttrib CALL_THE_QGL_VERSION_OF_glPopAttrib +#define glPopClientAttrib CALL_THE_QGL_VERSION_OF_glPopClientAttrib +#define glPopMatrix CALL_THE_QGL_VERSION_OF_glPopMatrix +#define glPopName CALL_THE_QGL_VERSION_OF_glPopName +#define glPrioritizeTextures CALL_THE_QGL_VERSION_OF_glPrioritizeTextures +#define glPushAttrib CALL_THE_QGL_VERSION_OF_glPushAttrib +#define glPushClientAttrib CALL_THE_QGL_VERSION_OF_glPushClientAttrib +#define glPushMatrix CALL_THE_QGL_VERSION_OF_glPushMatrix +#define glPushName CALL_THE_QGL_VERSION_OF_glPushName +#define glRasterPos2d CALL_THE_QGL_VERSION_OF_glRasterPos2d +#define glRasterPos2dv CALL_THE_QGL_VERSION_OF_glRasterPos2dv +#define glRasterPos2f CALL_THE_QGL_VERSION_OF_glRasterPos2f +#define glRasterPos2fv CALL_THE_QGL_VERSION_OF_glRasterPos2fv +#define glRasterPos2i CALL_THE_QGL_VERSION_OF_glRasterPos2i +#define glRasterPos2iv CALL_THE_QGL_VERSION_OF_glRasterPos2iv +#define glRasterPos2s CALL_THE_QGL_VERSION_OF_glRasterPos2s +#define glRasterPos2sv CALL_THE_QGL_VERSION_OF_glRasterPos2sv +#define glRasterPos3d CALL_THE_QGL_VERSION_OF_glRasterPos3d +#define glRasterPos3dv CALL_THE_QGL_VERSION_OF_glRasterPos3dv +#define glRasterPos3f CALL_THE_QGL_VERSION_OF_glRasterPos3f +#define glRasterPos3fv CALL_THE_QGL_VERSION_OF_glRasterPos3fv +#define glRasterPos3i CALL_THE_QGL_VERSION_OF_glRasterPos3i +#define glRasterPos3iv CALL_THE_QGL_VERSION_OF_glRasterPos3iv +#define glRasterPos3s CALL_THE_QGL_VERSION_OF_glRasterPos3s +#define glRasterPos3sv CALL_THE_QGL_VERSION_OF_glRasterPos3sv +#define glRasterPos4d CALL_THE_QGL_VERSION_OF_glRasterPos4d +#define glRasterPos4dv CALL_THE_QGL_VERSION_OF_glRasterPos4dv +#define glRasterPos4f CALL_THE_QGL_VERSION_OF_glRasterPos4f +#define glRasterPos4fv CALL_THE_QGL_VERSION_OF_glRasterPos4fv +#define glRasterPos4i CALL_THE_QGL_VERSION_OF_glRasterPos4i +#define glRasterPos4iv CALL_THE_QGL_VERSION_OF_glRasterPos4iv +#define glRasterPos4s CALL_THE_QGL_VERSION_OF_glRasterPos4s +#define glRasterPos4sv CALL_THE_QGL_VERSION_OF_glRasterPos4sv +#define glReadBuffer CALL_THE_QGL_VERSION_OF_glReadBuffer +#define glReadPixels CALL_THE_QGL_VERSION_OF_glReadPixels +#define glRectd CALL_THE_QGL_VERSION_OF_glRectd +#define glRectdv CALL_THE_QGL_VERSION_OF_glRectdv +#define glRectf CALL_THE_QGL_VERSION_OF_glRectf +#define glRectfv CALL_THE_QGL_VERSION_OF_glRectfv +#define glRecti CALL_THE_QGL_VERSION_OF_glRecti +#define glRectiv CALL_THE_QGL_VERSION_OF_glRectiv +#define glRects CALL_THE_QGL_VERSION_OF_glRects +#define glRectsv CALL_THE_QGL_VERSION_OF_glRectsv +#define glRenderMode CALL_THE_QGL_VERSION_OF_glRenderMode +#define glRotated CALL_THE_QGL_VERSION_OF_glRotated +#define glRotatef CALL_THE_QGL_VERSION_OF_glRotatef +#define glScaled CALL_THE_QGL_VERSION_OF_glScaled +#define glScalef CALL_THE_QGL_VERSION_OF_glScalef +#define glScissor CALL_THE_QGL_VERSION_OF_glScissor +#define glSelectBuffer CALL_THE_QGL_VERSION_OF_glSelectBuffer +#define glShadeModel CALL_THE_QGL_VERSION_OF_glShadeModel +#define glStencilFunc CALL_THE_QGL_VERSION_OF_glStencilFunc +#define glStencilMask CALL_THE_QGL_VERSION_OF_glStencilMask +#define glStencilOp CALL_THE_QGL_VERSION_OF_glStencilOp +#define glTexCoord1d CALL_THE_QGL_VERSION_OF_glTexCoord1d +#define glTexCoord1dv CALL_THE_QGL_VERSION_OF_glTexCoord1dv +#define glTexCoord1f CALL_THE_QGL_VERSION_OF_glTexCoord1f +#define glTexCoord1fv CALL_THE_QGL_VERSION_OF_glTexCoord1fv +#define glTexCoord1i CALL_THE_QGL_VERSION_OF_glTexCoord1i +#define glTexCoord1iv CALL_THE_QGL_VERSION_OF_glTexCoord1iv +#define glTexCoord1s CALL_THE_QGL_VERSION_OF_glTexCoord1s +#define glTexCoord1sv CALL_THE_QGL_VERSION_OF_glTexCoord1sv +#define glTexCoord2d CALL_THE_QGL_VERSION_OF_glTexCoord2d +#define glTexCoord2dv CALL_THE_QGL_VERSION_OF_glTexCoord2dv +#define glTexCoord2f CALL_THE_QGL_VERSION_OF_glTexCoord2f +#define glTexCoord2fv CALL_THE_QGL_VERSION_OF_glTexCoord2fv +#define glTexCoord2i CALL_THE_QGL_VERSION_OF_glTexCoord2i +#define glTexCoord2iv CALL_THE_QGL_VERSION_OF_glTexCoord2iv +#define glTexCoord2s CALL_THE_QGL_VERSION_OF_glTexCoord2s +#define glTexCoord2sv CALL_THE_QGL_VERSION_OF_glTexCoord2sv +#define glTexCoord3d CALL_THE_QGL_VERSION_OF_glTexCoord3d +#define glTexCoord3dv CALL_THE_QGL_VERSION_OF_glTexCoord3dv +#define glTexCoord3f CALL_THE_QGL_VERSION_OF_glTexCoord3f +#define glTexCoord3fv CALL_THE_QGL_VERSION_OF_glTexCoord3fv +#define glTexCoord3i CALL_THE_QGL_VERSION_OF_glTexCoord3i +#define glTexCoord3iv CALL_THE_QGL_VERSION_OF_glTexCoord3iv +#define glTexCoord3s CALL_THE_QGL_VERSION_OF_glTexCoord3s +#define glTexCoord3sv CALL_THE_QGL_VERSION_OF_glTexCoord3sv +#define glTexCoord4d CALL_THE_QGL_VERSION_OF_glTexCoord4d +#define glTexCoord4dv CALL_THE_QGL_VERSION_OF_glTexCoord4dv +#define glTexCoord4f CALL_THE_QGL_VERSION_OF_glTexCoord4f +#define glTexCoord4fv CALL_THE_QGL_VERSION_OF_glTexCoord4fv +#define glTexCoord4i CALL_THE_QGL_VERSION_OF_glTexCoord4i +#define glTexCoord4iv CALL_THE_QGL_VERSION_OF_glTexCoord4iv +#define glTexCoord4s CALL_THE_QGL_VERSION_OF_glTexCoord4s +#define glTexCoord4sv CALL_THE_QGL_VERSION_OF_glTexCoord4sv +#define glTexCoordPointer CALL_THE_QGL_VERSION_OF_glTexCoordPointer +#define glTexEnvf CALL_THE_QGL_VERSION_OF_glTexEnvf +#define glTexEnvfv CALL_THE_QGL_VERSION_OF_glTexEnvfv +#define glTexEnvi CALL_THE_QGL_VERSION_OF_glTexEnvi +#define glTexEnviv CALL_THE_QGL_VERSION_OF_glTexEnviv +#define glTexGend CALL_THE_QGL_VERSION_OF_glTexGend +#define glTexGendv CALL_THE_QGL_VERSION_OF_glTexGendv +#define glTexGenf CALL_THE_QGL_VERSION_OF_glTexGenf +#define glTexGenfv CALL_THE_QGL_VERSION_OF_glTexGenfv +#define glTexGeni CALL_THE_QGL_VERSION_OF_glTexGeni +#define glTexGeniv CALL_THE_QGL_VERSION_OF_glTexGeniv +#define glTexImage1D CALL_THE_QGL_VERSION_OF_glTexImage1D +#define glTexImage2D CALL_THE_QGL_VERSION_OF_glTexImage2D +#define glTexParameterf CALL_THE_QGL_VERSION_OF_glTexParameterf +#define glTexParameterfv CALL_THE_QGL_VERSION_OF_glTexParameterfv +#define glTexParameteri CALL_THE_QGL_VERSION_OF_glTexParameteri +#define glTexParameteriv CALL_THE_QGL_VERSION_OF_glTexParameteriv +#define glTexSubImage1D CALL_THE_QGL_VERSION_OF_glTexSubImage1D +#define glTexSubImage2D CALL_THE_QGL_VERSION_OF_glTexSubImage2D +#define glTranslated CALL_THE_QGL_VERSION_OF_glTranslated +#define glTranslatef CALL_THE_QGL_VERSION_OF_glTranslatef +#define glVertex2d CALL_THE_QGL_VERSION_OF_glVertex2d +#define glVertex2dv CALL_THE_QGL_VERSION_OF_glVertex2dv +#define glVertex2f CALL_THE_QGL_VERSION_OF_glVertex2f +#define glVertex2fv CALL_THE_QGL_VERSION_OF_glVertex2fv +#define glVertex2i CALL_THE_QGL_VERSION_OF_glVertex2i +#define glVertex2iv CALL_THE_QGL_VERSION_OF_glVertex2iv +#define glVertex2s CALL_THE_QGL_VERSION_OF_glVertex2s +#define glVertex2sv CALL_THE_QGL_VERSION_OF_glVertex2sv +#define glVertex3d CALL_THE_QGL_VERSION_OF_glVertex3d +#define glVertex3dv CALL_THE_QGL_VERSION_OF_glVertex3dv +#define glVertex3f CALL_THE_QGL_VERSION_OF_glVertex3f +#define glVertex3fv CALL_THE_QGL_VERSION_OF_glVertex3fv +#define glVertex3i CALL_THE_QGL_VERSION_OF_glVertex3i +#define glVertex3iv CALL_THE_QGL_VERSION_OF_glVertex3iv +#define glVertex3s CALL_THE_QGL_VERSION_OF_glVertex3s +#define glVertex3sv CALL_THE_QGL_VERSION_OF_glVertex3sv +#define glVertex4d CALL_THE_QGL_VERSION_OF_glVertex4d +#define glVertex4dv CALL_THE_QGL_VERSION_OF_glVertex4dv +#define glVertex4f CALL_THE_QGL_VERSION_OF_glVertex4f +#define glVertex4fv CALL_THE_QGL_VERSION_OF_glVertex4fv +#define glVertex4i CALL_THE_QGL_VERSION_OF_glVertex4i +#define glVertex4iv CALL_THE_QGL_VERSION_OF_glVertex4iv +#define glVertex4s CALL_THE_QGL_VERSION_OF_glVertex4s +#define glVertex4sv CALL_THE_QGL_VERSION_OF_glVertex4sv +#define glVertexPointer CALL_THE_QGL_VERSION_OF_glVertexPointer +#define glViewport CALL_THE_QGL_VERSION_OF_glViewport + +#ifdef __cplusplus +} +#endif + diff --git a/src/macosx/macosx_sndcore.m b/src/macosx/macosx_sndcore.m new file mode 100644 index 0000000..00449c9 --- /dev/null +++ b/src/macosx/macosx_sndcore.m @@ -0,0 +1,313 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// mac_snddma.c +// all other sound mixing is portable + +#include +#include +#include + +#include "../client/snd_local.h" + +// For 'ri' +#include "../renderer/tr_local.h" + +#import +#import + +static unsigned int submissionChunk; +static unsigned int maxMixedSamples; +static short *s_mixedSamples; +static int s_chunkCount; // number of chunks submitted +static qboolean s_isRunning; + +static AudioDeviceID outputDeviceID; +static AudioStreamBasicDescription outputStreamBasicDescription; + +/* +=============== +audioDeviceIOProc +=============== +*/ + +OSStatus audioDeviceIOProc( AudioDeviceID inDevice, + const AudioTimeStamp *inNow, + const AudioBufferList *inInputData, + const AudioTimeStamp *inInputTime, + AudioBufferList *outOutputData, + const AudioTimeStamp *inOutputTime, + void *inClientData ) { + int offset; + short *samples; + unsigned int sampleIndex; + float *outBuffer; + float scale, temp; + + offset = ( s_chunkCount * submissionChunk ) % maxMixedSamples; + samples = s_mixedSamples + offset; + + assert( outOutputData->mNumberBuffers == 1 ); + assert( outOutputData->mBuffers[0].mNumberChannels == 2 ); +// assert(outOutputData->mBuffers[0].mDataByteSize == (dma.submission_chunk * sizeof(float))); + + outBuffer = (float *)outOutputData->mBuffers[0].mData; + + // If we have run out of samples, return silence + if ( s_chunkCount * submissionChunk > dma.channels * s_paintedtime ) { + memset( outBuffer, 0, sizeof( *outBuffer ) * dma.submission_chunk ); + } else { + scale = ( 1.0f / SHRT_MAX ); + if ( outputStreamBasicDescription.mSampleRate == 44100 ) { + for ( sampleIndex = 0; sampleIndex < dma.submission_chunk; sampleIndex++ ) { + // Convert the samples from shorts to floats. Scale the floats to be [-1..1]. + temp = samples[sampleIndex] * scale; + outBuffer[( sampleIndex << 1 ) + 0] = temp; + outBuffer[( sampleIndex << 1 ) + 1] = temp; + } + } else { + for ( sampleIndex = 0; sampleIndex < dma.submission_chunk; sampleIndex++ ) { + // Convert the samples from shorts to floats. Scale the floats to be [-1..1]. + outBuffer[sampleIndex] = samples[sampleIndex] * scale; + } + } + } + + s_chunkCount++; // this is the next buffer we will submit + return 0; +} + + +/* +=============== +S_MakeTestPattern +=============== +*/ +void S_MakeTestPattern( void ) { + int i; + float v; + int sample; + + for ( i = 0 ; i < dma.samples / 2 ; i++ ) { + v = sin( M_PI * 2 * i / 64 ); + sample = v * 0x4000; + ( (short *)dma.buffer )[i * 2] = sample; + ( (short *)dma.buffer )[i * 2 + 1] = sample; + } +} + +/* +=============== +SNDDMA_Init +=============== +*/ +qboolean SNDDMA_Init( void ) { + cvar_t *bufferSize; + cvar_t *chunkSize; + OSStatus status; + UInt32 propertySize, bufferByteCount; + + if ( s_isRunning ) { + return qtrue; + } + + chunkSize = ri.Cvar_Get( "s_chunksize", "512", CVAR_ARCHIVE ); + bufferSize = ri.Cvar_Get( "s_buffersize", "16384", CVAR_ARCHIVE ); + Com_Printf( " Chunk size = %d\n", chunkSize->integer ); + Com_Printf( "Buffer size = %d\n", bufferSize->integer ); + + if ( !chunkSize->integer ) { + ri.Error( ERR_FATAL, "s_chunksize must be non-zero\n" ); + } + if ( !bufferSize->integer ) { + ri.Error( ERR_FATAL, "s_buffersize must be non-zero\n" ); + } + if ( chunkSize->integer >= bufferSize->integer ) { + ri.Error( ERR_FATAL, "s_chunksize must be less than s_buffersize\n" ); + } + if ( bufferSize->integer % chunkSize->integer ) { + ri.Error( ERR_FATAL, "s_buffersize must be an even multiple of s_chunksize\n" ); + } + + // Get the output device + propertySize = sizeof( outputDeviceID ); + status = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID ); + if ( status ) { + Com_Printf( "AudioHardwareGetProperty returned %d\n", status ); + return qfalse; + } + + if ( outputDeviceID == kAudioDeviceUnknown ) { + Com_Printf( "AudioHardwareGetProperty: outputDeviceID is kAudioDeviceUnknown\n" ); + return qfalse; + } + + // Configure the output device + propertySize = sizeof( bufferByteCount ); + bufferByteCount = chunkSize->integer * sizeof( float ); + status = AudioDeviceSetProperty( outputDeviceID, NULL, 0, NO, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount ); + if ( status ) { + Com_Printf( "AudioDeviceSetProperty: returned %d when setting kAudioDevicePropertyBufferSize to %d\n", status, chunkSize->integer ); + return qfalse; + } + + propertySize = sizeof( bufferByteCount ); + status = AudioDeviceGetProperty( outputDeviceID, 0, NO, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount ); + if ( status ) { + Com_Printf( "AudioDeviceGetProperty: returned %d when setting kAudioDevicePropertyBufferSize\n", status ); + return qfalse; + } + + // Print out the device status + propertySize = sizeof( outputStreamBasicDescription ); + status = AudioDeviceGetProperty( outputDeviceID, 0, NO, kAudioDevicePropertyStreamFormat, &propertySize, &outputStreamBasicDescription ); + if ( status ) { + Com_Printf( "AudioDeviceGetProperty: returned %d when getting kAudioDevicePropertyStreamFormat\n", status ); + return qfalse; + } + + Com_Printf( "Hardware format:\n" ); + Com_Printf( " %f mSampleRate\n", outputStreamBasicDescription.mSampleRate ); + Com_Printf( " %c%c%c%c mFormatID\n", + ( outputStreamBasicDescription.mFormatID & 0xff000000 ) >> 24, + ( outputStreamBasicDescription.mFormatID & 0x00ff0000 ) >> 16, + ( outputStreamBasicDescription.mFormatID & 0x0000ff00 ) >> 8, + ( outputStreamBasicDescription.mFormatID & 0x000000ff ) >> 0 ); + Com_Printf( " %5d mBytesPerPacket\n", outputStreamBasicDescription.mBytesPerPacket ); + Com_Printf( " %5d mFramesPerPacket\n", outputStreamBasicDescription.mFramesPerPacket ); + Com_Printf( " %5d mBytesPerFrame\n", outputStreamBasicDescription.mBytesPerFrame ); + Com_Printf( " %5d mChannelsPerFrame\n", outputStreamBasicDescription.mChannelsPerFrame ); + Com_Printf( " %5d mBitsPerChannel\n", outputStreamBasicDescription.mBitsPerChannel ); + + if ( outputStreamBasicDescription.mFormatID != kAudioFormatLinearPCM ) { + Com_Printf( "Default Audio Device doesn't support Linear PCM!" ); + return qfalse; + } + + // Start sound running + status = AudioDeviceAddIOProc( outputDeviceID, audioDeviceIOProc, NULL ); + if ( status ) { + Com_Printf( "AudioDeviceAddIOProc: returned %d\n", status ); + return qfalse; + } + + submissionChunk = chunkSize->integer; + if ( outputStreamBasicDescription.mSampleRate == 44100 ) { + submissionChunk = chunkSize->integer / 2; + } + maxMixedSamples = bufferSize->integer; + s_mixedSamples = calloc( 1, sizeof( *s_mixedSamples ) * maxMixedSamples ); + Com_Printf( "Chunk Count = %d\n", ( maxMixedSamples / submissionChunk ) ); + + // Tell the main app what we expect from it + dma.samples = maxMixedSamples; + dma.submission_chunk = submissionChunk; + dma.samplebits = 16; + dma.buffer = (byte *)s_mixedSamples; + dma.channels = outputStreamBasicDescription.mChannelsPerFrame; + dma.speed = 22050; //(unsigned long)outputStreamBasicDescription.mSampleRate; + + // We haven't enqueued anything yet + s_chunkCount = 0; + + status = AudioDeviceStart( outputDeviceID, audioDeviceIOProc ); + if ( status ) { + Com_Printf( "AudioDeviceStart: returned %d\n", status ); + return qfalse; + } + + s_isRunning = qtrue; + + return qtrue; +} + +/* +=============== +SNDDMA_GetBufferDuration +=============== +*/ +float SNDDMA_GetBufferDuration( void ) { + return (float)dma.samples / (float)( dma.channels * dma.speed ); +} + +/* +=============== +SNDDMA_GetDMAPos +=============== +*/ +int SNDDMA_GetDMAPos( void ) { + return s_chunkCount * dma.submission_chunk; +} + +/* +=============== +SNDDMA_Shutdown +=============== +*/ +void SNDDMA_Shutdown( void ) { + OSStatus status; + + if ( !s_isRunning ) { + return; + } + + status = AudioDeviceStop( outputDeviceID, audioDeviceIOProc ); + if ( status ) { + Com_Printf( "AudioDeviceStop: returned %d\n", status ); + return; + } + + s_isRunning = qfalse; + + status = AudioDeviceRemoveIOProc( outputDeviceID, audioDeviceIOProc ); + if ( status ) { + Com_Printf( "AudioDeviceRemoveIOProc: returned %d\n", status ); + return; + } + + free( s_mixedSamples ); + s_mixedSamples = NULL; + dma.samples = NULL; +} + +/* +=============== +SNDDMA_BeginPainting +=============== +*/ +void SNDDMA_BeginPainting( void ) { +} + +/* +=============== +SNDDMA_Submit +=============== +*/ +void SNDDMA_Submit( void ) { +} diff --git a/src/macosx/macosx_snddma.m b/src/macosx/macosx_snddma.m new file mode 100644 index 0000000..a21d81b --- /dev/null +++ b/src/macosx/macosx_snddma.m @@ -0,0 +1,210 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// mac_snddma.c +// all other sound mixing is portable + +#include "../client/snd_local.h" +#include + +// For 'ri' +#include "../renderer/tr_local.h" + +#import + +// TJW - Different versions of SoundManager have different DMA buffer sizes. On MacOS X DP2, +// the buffer size is 8K. On MacOS 9 it is much smaller. The SoundManager guy at Apple says +// that the size of the buffer will be decreasing for final release to help get rid of latency. +//#define MAX_MIXED_SAMPLES (0x8000 * 64) +//#define SUBMISSION_CHUNK (0x100 * 64) + +// Original MacOS 9 sizes +//#define MAX_MIXED_SAMPLES 0x8000 +//#define SUBMISSION_CHUNK 0x100 + + +static unsigned int submissionChunk; +static unsigned int maxMixedSamples; + +static short *s_mixedSamples; +static int s_chunkCount; // number of chunks submitted +static SndChannel *s_sndChan; +static ExtSoundHeader s_sndHeader; + +/* +=============== +S_Callback +=============== +*/ +void S_Callback( SndChannel *sc, SndCommand *cmd ) { + SndCommand mySndCmd; + SndCommand mySndCmd2; + int offset; + + offset = ( s_chunkCount * submissionChunk ) & ( maxMixedSamples - 1 ); + + // queue up another sound buffer + memset( &s_sndHeader, 0, sizeof( s_sndHeader ) ); + s_sndHeader.samplePtr = ( void * )( s_mixedSamples + offset ); + s_sndHeader.numChannels = 2; + s_sndHeader.sampleRate = rate22khz; + s_sndHeader.loopStart = 0; + s_sndHeader.loopEnd = 0; + s_sndHeader.encode = extSH; + s_sndHeader.baseFrequency = 1; + s_sndHeader.numFrames = submissionChunk / 2; + s_sndHeader.markerChunk = NULL; + s_sndHeader.instrumentChunks = NULL; + s_sndHeader.AESRecording = NULL; + s_sndHeader.sampleSize = 16; + + mySndCmd.cmd = bufferCmd; + mySndCmd.param1 = 0; + mySndCmd.param2 = (int)&s_sndHeader; + SndDoCommand( sc, &mySndCmd, true ); + + // and another callback + mySndCmd2.cmd = callBackCmd; + mySndCmd2.param1 = 0; + mySndCmd2.param2 = 0; + SndDoCommand( sc, &mySndCmd2, true ); + + s_chunkCount++; // this is the next buffer we will submit +} + +/* +=============== +S_MakeTestPattern +=============== +*/ +void S_MakeTestPattern( void ) { + int i; + float v; + int sample; + + for ( i = 0 ; i < dma.samples / 2 ; i++ ) { + v = sin( M_PI * 2 * i / 64 ); + sample = v * 0x4000; + ( (short *)dma.buffer )[i * 2] = sample; + ( (short *)dma.buffer )[i * 2 + 1] = sample; + } +} + +/* +=============== +SNDDMA_Init +=============== +*/ +qboolean SNDDMA_Init( void ) { + int err; + cvar_t *bufferSize; + cvar_t *chunkSize; + + chunkSize = ri.Cvar_Get( "s_chunksize", "8192", CVAR_ARCHIVE ); + bufferSize = ri.Cvar_Get( "s_buffersize", "65536", CVAR_ARCHIVE ); + + if ( !chunkSize->integer ) { + ri.Error( ERR_FATAL, "snd_chunkSize must be non-zero\n" ); + } + + if ( !bufferSize->integer ) { + ri.Error( ERR_FATAL, "snd_bufferSize must be non-zero\n" ); + } + + if ( chunkSize->integer >= bufferSize->integer ) { + ri.Error( ERR_FATAL, "snd_chunkSize must be less than snd_bufferSize\n" ); + } + + if ( bufferSize->integer % chunkSize->integer ) { + ri.Error( ERR_FATAL, "snd_bufferSize must be an even multiple of snd_chunkSize\n" ); + } + + // create a sound channel + s_sndChan = NULL; + err = SndNewChannel( &s_sndChan, sampledSynth, initStereo, NewSndCallBackProc( S_Callback ) ); + if ( err ) { + return false; + } + + submissionChunk = chunkSize->integer; + maxMixedSamples = bufferSize->integer; + + s_mixedSamples = NSZoneMalloc( NULL, sizeof( *s_mixedSamples ) * maxMixedSamples ); + + dma.channels = 2; + dma.samples = maxMixedSamples; + dma.submission_chunk = submissionChunk; + dma.samplebits = 16; + dma.speed = 22050; + dma.buffer = (byte *)s_mixedSamples; + + // que up the first submission-chunk sized buffer + s_chunkCount = 0; + + S_Callback( s_sndChan, NULL ); + + return qtrue; +} + +/* +=============== +SNDDMA_GetDMAPos +=============== +*/ +int SNDDMA_GetDMAPos( void ) { + return s_chunkCount * submissionChunk; +} + +/* +=============== +SNDDMA_Shutdown +=============== +*/ +void SNDDMA_Shutdown( void ) { + if ( s_sndChan ) { + SndDisposeChannel( s_sndChan, true ); + s_sndChan = NULL; + } +} + +/* +=============== +SNDDMA_BeginPainting +=============== +*/ +void SNDDMA_BeginPainting( void ) { +} + +/* +=============== +SNDDMA_Submit +=============== +*/ +void SNDDMA_Submit( void ) { +} diff --git a/src/macosx/macosx_sys.m b/src/macosx/macosx_sys.m new file mode 100644 index 0000000..e0bd4d1 --- /dev/null +++ b/src/macosx/macosx_sys.m @@ -0,0 +1,532 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#import "dlfcn.h" +#import "Q3Controller.h" + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import + +#import "../client/client.h" +#import "macosx_local.h" + +#ifdef OMNI_TIMER +#import "macosx_timers.h" +#endif + +qboolean stdin_active = qfalse; + +//=========================================================================== + +int main( int argc, const char *argv[] ) { +#ifdef DEDICATED + Q3Controller *controller; + + stdin_active = qtrue; + controller = [[Q3Controller alloc] init]; + [controller quakeMain]; + return 0; +#else + return NSApplicationMain( argc, argv ); +#endif +} + +//=========================================================================== + +/* +================= +Sys_UnloadDll + +================= +*/ +void Sys_UnloadDll( void *dllHandle ) { + if ( !dllHandle ) { + return; + } + dlclose( dllHandle ); +} + +/* +================= +Sys_LoadDll + +Used to load a development dll instead of a virtual machine +================= +*/ +extern char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ); + +void *Sys_LoadDll( const char *name, int( **entryPoint ) ( int, ... ), + int ( *systemcalls )( int, ... ) ) { + void *libHandle; + void ( *dllEntry )( int ( *syscallptr )( int, ... ) ); + NSString *bundlePath, *libraryPath, *installationPath; + const char *path; + + bundlePath = [[NSBundle mainBundle] pathForResource: [NSString stringWithCString: name] ofType: @ "bundle"]; +// libraryPath = [NSString stringWithFormat: @"%@/Contents/MacOS/%s", bundlePath, name]; + installationPath = [[NSUserDefaults standardUserDefaults] objectForKey:@ "RetailInstallationPath"]; + if ( !installationPath ) { + // Default to the directory containing the executable (which is where most users will want to put it + installationPath = [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]; + installationPath = [installationPath stringByAppendingPathComponent: @ "wolfmp"]; + } + + libraryPath = [NSString stringWithFormat: @ "%@/%s.bundle/Contents/MacOS/%s", installationPath, name, name]; + if ( !libraryPath ) { + return NULL; + } + + path = [libraryPath cString]; + Com_Printf( "Loading '%s'.\n", path ); + libHandle = dlopen( [libraryPath cString], RTLD_LAZY ); + if ( !libHandle ) { + libHandle = dlopen( name, RTLD_LAZY ); + if ( !libHandle ) { + Com_Printf( "Error loading dll: %s\n", dlerror() ); + return NULL; + } + } + + dllEntry = dlsym( libHandle, "_dllEntry" ); + if ( !dllEntry ) { + Com_Printf( "Error loading dll: No dllEntry symbol.\n" ); + dlclose( libHandle ); + return NULL; + } + + *entryPoint = dlsym( libHandle, "_vmMain" ); + if ( !*entryPoint ) { + Com_Printf( "Error loading dll: No vmMain symbol.\n" ); + dlclose( libHandle ); + return NULL; + } + + dllEntry( systemcalls ); + return libHandle; +} + +//=========================================================================== + +char *Sys_GetClipboardData( void ) { // FIXME + NSPasteboard *pasteboard; + NSArray *pasteboardTypes; + + pasteboard = [NSPasteboard generalPasteboard]; + pasteboardTypes = [pasteboard types]; + if ([pasteboardTypes containsObject : NSStringPboardType] ) { + NSString *clipboardString; + + clipboardString = [pasteboard stringForType:NSStringPboardType]; + if ( clipboardString && [clipboardString length] > 0 ) { + return strdup([clipboardString cString] ); + } + } + return NULL; +} + +char *Sys_GetWholeClipboard( void ) { + return NULL; +} + +void Sys_SetClipboard( const char *contents ) { +} + + +//=========================================================================== + +void Sys_BeginProfiling( void ) { +} + +void Sys_EndProfiling( void ) { +} + +//=========================================================================== + +/* +================ +Sys_Init + +The cvar and file system has been setup, so configurations are loaded +================ +*/ +void Sys_Init( void ) { +#ifdef OMNI_TIMER + InitializeTimers(); + OTStackPushRoot( rootNode ); +#endif + + NET_Init(); + Sys_InitInput(); +} + +/* +================= +Sys_Shutdown +================= +*/ +void Sys_Shutdown( void ) { + Com_Printf( "----- Sys_Shutdown -----\n" ); + Sys_EndProfiling(); + Sys_ShutdownInput(); + Com_Printf( "------------------------\n" ); +} + +void Sys_Error( const char *error, ... ) { + va_list argptr; + NSString *formattedString; + + Sys_Shutdown(); + + va_start( argptr,error ); + formattedString = [[NSString alloc] initWithFormat:[NSString stringWithCString:error] arguments:argptr]; + va_end( argptr ); + + NSLog( @ "Sys_Error: %@", formattedString ); + NSRunAlertPanel( @ "Wolfenstein Error", formattedString, nil, nil, nil ); + + Sys_Quit(); +} + +void Sys_Quit( void ) { + Sys_Shutdown(); + [NSApp terminate : nil]; +} + +/* +================ +Sys_Print + +This is called for all console output, even if the game is running +full screen and the dedicated console window is hidden. +================ +*/ + +char *ansiColors[8] = +{ "\033[30m", /* ANSI Black */ + "\033[31m", /* ANSI Red */ + "\033[32m", /* ANSI Green */ + "\033[33m", /* ANSI Yellow */ + "\033[34m", /* ANSI Blue */ + "\033[36m", /* ANSI Cyan */ + "\033[35m", /* ANSI Magenta */ + "\033[37m" }; /* ANSI White */ + +void Sys_Print( const char *text ) { +#if 0 + /* Okay, this is a stupid hack, but what the hell, I was bored. ;) */ + const char *scan = text; + int index; + + /* Make sure terminal mode is reset at the start of the line... */ + fputs( "\033[0m", stdout ); + + while ( *scan ) { + /* See if we have a color control code. If so, snarf the character, + print what we have so far, print the ANSI Terminal color code, + skip over the color control code and continue */ + if ( Q_IsColorString( scan ) ) { + index = ColorIndex( scan[1] ); + + /* Flush current message */ + if ( scan != text ) { + fwrite( text, scan - text, 1, stdout ); + } + + /* Write ANSI color code */ + fputs( ansiColors[index], stdout ); + + /* Reset search */ + text = scan + 2; + scan = text; + continue; + } + scan++; + } + + /* Flush whatever's left */ + fputs( text, stdout ); + + /* Make sure terminal mode is reset at the end of the line too... */ + fputs( "\033[0m", stdout ); + +#else + fputs( text, stdout ); +#endif +} + + + +/* +================ +Sys_CheckCD + +Return true if the proper CD is in the drive +================ +*/ + +qboolean Sys_ObjectIsCDRomDevice( io_object_t object ) { + CFStringRef value; + kern_return_t krc; + CFDictionaryRef properties; + qboolean isCDROM = qfalse; + io_iterator_t parentIterator; + io_object_t parent; + + krc = IORegistryEntryCreateCFProperties( object, &properties, kCFAllocatorDefault, (IOOptionBits)0 ); + if ( krc != KERN_SUCCESS ) { + fprintf( stderr, "IORegistryEntryCreateCFProperties returned 0x%08x -- %s\n", krc, mach_error_string( krc ) ); + return qfalse; + } + + //NSLog(@"properties = %@", properties); + + // See if this is a CD-ROM + value = CFDictionaryGetValue( properties, CFSTR( kIOCDMediaTypeKey ) ); + if ( value && CFStringCompare( value, CFSTR( "CD-ROM" ), 0 ) == kCFCompareEqualTo ) { + isCDROM = qtrue; + } + CFRelease( properties ); + + // If it isn't check each of its parents. It seems that the parent enumerator only returns the immediate parent. Maybe the plural indicates that an object can have multiple direct parents. So, we'll call ourselves recursively for each parent. + if ( !isCDROM ) { + krc = IORegistryEntryGetParentIterator( object, kIOServicePlane, &parentIterator ); + if ( krc != KERN_SUCCESS ) { + fprintf( stderr, "IOServiceGetMatchingServices returned 0x%08x -- %s\n", + krc, mach_error_string( krc ) ); + } else { + while ( !isCDROM && ( parent = IOIteratorNext( parentIterator ) ) ) { + if ( Sys_ObjectIsCDRomDevice( parent ) ) { + isCDROM = qtrue; + } + IOObjectRelease( parent ); + } + + IOObjectRelease( parentIterator ); + } + } + + //NSLog(@"Sys_ObjectIsCDRomDevice -> %d", isCDROM); + return isCDROM; +} + +qboolean Sys_IsCDROMDevice( const char *deviceName ) { + kern_return_t krc; + io_iterator_t deviceIterator; + mach_port_t masterPort; + io_object_t object; + qboolean isCDROM = qfalse; + + krc = IOMasterPort( bootstrap_port, &masterPort ); + if ( krc != KERN_SUCCESS ) { + fprintf( stderr, "IOMasterPort returned 0x%08x -- %s\n", krc, mach_error_string( krc ) ); + return qfalse; + } + + // Get an iterator for this BSD device. If it is a CD, it will likely only be one partition of the larger CD-ROM device. + krc = IOServiceGetMatchingServices( masterPort, + IOBSDNameMatching( masterPort, 0, deviceName ), + &deviceIterator ); + if ( krc != KERN_SUCCESS ) { + fprintf( stderr, "IOServiceGetMatchingServices returned 0x%08x -- %s\n", + krc, mach_error_string( krc ) ); + return qfalse; + } + + while ( !isCDROM && ( object = IOIteratorNext( deviceIterator ) ) ) { + if ( Sys_ObjectIsCDRomDevice( object ) ) { + isCDROM = qtrue; + } + IOObjectRelease( object ); + } + + IOObjectRelease( deviceIterator ); + + //NSLog(@"Sys_IsCDROMDevice -> %d", isCDROM); + return isCDROM; +} + +qboolean Sys_CheckCD( void ) { + // DO NOT just return success here if we have a library directory. + // Actually look for the CD. + + // We'll look through the actual mount points rather than just looking + // for a particular directory since (a) the mount point may change + // between OS version (/foo in Public Beta, /Volumes/foo after Public Beta) + // and (b) this way someone can't just create a directory and warez the files. + + unsigned int mountCount; + struct statfs *mounts; + + mountCount = getmntinfo( &mounts, MNT_NOWAIT ); + if ( mountCount <= 0 ) { + perror( "getmntinfo" ); +#if 1 // Q3:TA doesn't need a CD, but we still need to locate it to allow for partial installs + return qtrue; +#else + return qfalse; +#endif + } + + while ( mountCount-- ) { + const char *lastComponent; + + if ( ( mounts[mountCount].f_flags & MNT_RDONLY ) != MNT_RDONLY ) { + // Should have been a read only CD... this isn't it + continue; + } + + if ( ( mounts[mountCount].f_flags & MNT_LOCAL ) != MNT_LOCAL ) { + // Should have been a local filesystem + continue; + } + + lastComponent = strrchr( mounts[mountCount].f_mntonname, '/' ); + if ( !lastComponent ) { + // No slash in the mount point! How is that possible? + continue; + } + + // Skip the slash and look for the game name + lastComponent++; + if ( ( strcasecmp( lastComponent, "WolfensteinMP" ) != 0 ) ) { + continue; + } + + +#if 0 + fprintf( stderr, "f_bsize: %d\n", mounts[mountCount].f_bsize ); + fprintf( stderr, "f_blocks: %d\n", mounts[mountCount].f_blocks ); + fprintf( stderr, "type: %d\n", mounts[mountCount].f_type ); + fprintf( stderr, "flags: %d\n", mounts[mountCount].f_flags ); + fprintf( stderr, "fstype: %s\n", mounts[mountCount].f_fstypename ); + fprintf( stderr, "f_mntonname: %s\n", mounts[mountCount].f_mntonname ); + fprintf( stderr, "f_mntfromname: %s\n", mounts[mountCount].f_mntfromname ); + fprintf( stderr, "\n\n" ); +#endif + + lastComponent = strrchr( mounts[mountCount].f_mntfromname, '/' ); + if ( !lastComponent ) { + // No slash in the device name! How is that possible? + continue; + } + lastComponent++; + if ( !Sys_IsCDROMDevice( lastComponent ) ) { + continue; + } + + // This looks good + Sys_SetDefaultCDPath( mounts[mountCount].f_mntonname ); + return qtrue; + } + +#if 1 // Q3:TA doesn't need a CD, but we still need to locate it to allow for partial installs + return qtrue; +#else + return qfalse; +#endif +} + + +//=================================================================== + +void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) { +} + +void Sys_EndStreamedFile( fileHandle_t f ) { +} + +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ) { + return FS_Read( buffer, size * count, f ); +} + +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ) { + FS_Seek( f, offset, origin ); +} + + +void OutputDebugString( char * s ) { +#ifdef DEBUG + fprintf( stderr, "%s", s ); +#endif +} + +/* +================== +Sys_LowPhysicalMemory() +================== +*/ +#define MEM_THRESHOLD 96 * 1024 * 1024 + +qboolean Sys_LowPhysicalMemory() { + return NSRealMemoryAvailable() <= MEM_THRESHOLD; +} + +static unsigned int _Sys_ProcessorCount = 0; + +unsigned int Sys_ProcessorCount() { + return 1; + if ( !_Sys_ProcessorCount ) { + int name[] = {CTL_HW, HW_NCPU}; + size_t size; + + size = sizeof( _Sys_ProcessorCount ); + if ( sysctl( name, 2, &_Sys_ProcessorCount, &size, NULL, 0 ) < 0 ) { + perror( "sysctl" ); + _Sys_ProcessorCount = 1; + } else { + Com_Printf( "System processor count is %d\n", _Sys_ProcessorCount ); + } + } + + return _Sys_ProcessorCount; +} + +/* +================== +Sys_StartProcess +================== +*/ +void Sys_StartProcess( char *exeName, qboolean doexit ) { + // FIXME TTimo + // this is only used for WolfSP / WolfMP spawning for now + if ( doexit ) { + exit( 0 ); + } +} + + diff --git a/src/macosx/macosx_timers.h b/src/macosx/macosx_timers.h new file mode 100644 index 0000000..2662092 --- /dev/null +++ b/src/macosx/macosx_timers.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifdef OMNI_TIMER + +#import + +#define OTSTART( node ) OTStackPush( node ) +#define OTSTOP( node ) OTStackPop() + +extern OTStackNode *rootNode; +extern OTStackNode *markFragmentsNode1; +extern OTStackNode *markFragmentsNode2; +extern OTStackNode *markFragmentsGrid; +extern OTStackNode *markFragmentsNode4; +extern OTStackNode *addMarkFragmentsNode; +extern OTStackNode *chopPolyNode; +extern OTStackNode *boxTraceNode; +extern OTStackNode *boxOnPlaneSideNode; +extern OTStackNode *recursiveWorldNode; +extern OTStackNode *surfaceAnimNode; +extern OTStackNode *surfaceFaceNode; +extern OTStackNode *surfaceMeshNode; +extern OTStackNode *surfaceEndNode; +extern OTStackNode *shadowEndNode; +extern OTStackNode *stageIteratorGenericNode; +extern OTStackNode *computeColorAndTexNode; +extern OTStackNode *mp3DecodeNode; + +extern void InitializeTimers(); + +#else + +#define OTSTART( node ) +#define OTSTOP( node ) + +#endif + diff --git a/src/macosx/macosx_timers.m b/src/macosx/macosx_timers.m new file mode 100644 index 0000000..7932c20 --- /dev/null +++ b/src/macosx/macosx_timers.m @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifdef OMNI_TIMER + +#import "macosx_timers.h" + +#import +#import + +OTStackNode *rootNode; +OTStackNode *markFragmentsNode1; +OTStackNode *markFragmentsNode2; +OTStackNode *markFragmentsGrid; +OTStackNode *markFragmentsNode4; +OTStackNode *addMarkFragmentsNode; +OTStackNode *chopPolyNode; +OTStackNode *boxTraceNode; +OTStackNode *boxOnPlaneSideNode; +OTStackNode *recursiveWorldNode; +OTStackNode *surfaceAnimNode; +OTStackNode *surfaceFaceNode; +OTStackNode *surfaceMeshNode; +OTStackNode *surfaceEndNode; +OTStackNode *shadowEndNode; +OTStackNode *stageIteratorGenericNode; +OTStackNode *computeColorAndTexNode; +OTStackNode *mp3DecodeNode; + +void InitializeTimers() { + const char *env; + + OTSetup(); + + rootNode = OTStackNodeCreate( "root" ); + markFragmentsNode1 = OTStackNodeCreate( "R_MarkFragments 1" ); + markFragmentsNode2 = OTStackNodeCreate( "R_MarkFragments 2" ); + markFragmentsGrid = OTStackNodeCreate( "R_MarkFragmentsGrid" ); + markFragmentsNode4 = OTStackNodeCreate( "R_MarkFragments 4" ); + addMarkFragmentsNode = OTStackNodeCreate( "R_AddMarkFragments" ); + chopPolyNode = OTStackNodeCreate( "R_ChopPolyBehindPlane" ); + boxTraceNode = OTStackNodeCreate( "CM_BoxTrace" ); + boxOnPlaneSideNode = OTStackNodeCreate( "BoxOnPlaneSide" ); + recursiveWorldNode = OTStackNodeCreate( "R_RecursiveWorldNode" ); + surfaceAnimNode = OTStackNodeCreate( "RB_SurfaceAnim" ); + surfaceFaceNode = OTStackNodeCreate( "RB_SurfaceFace" ); + surfaceMeshNode = OTStackNodeCreate( "RB_SurfaceMesh" ); + surfaceEndNode = OTStackNodeCreate( "RB_EndSurface" ); + shadowEndNode = OTStackNodeCreate( "RB_ShadowTessEnd" ); + stageIteratorGenericNode = OTStackNodeCreate( "RB_StageIteratorGeneric" ); + computeColorAndTexNode = OTStackNodeCreate( "ComputeColors & ComputeTexCoords" ); + mp3DecodeNode = OTStackNodeCreate( "MP3Stream_Decode" ); +} + +#endif // OMNI_TIMER + diff --git a/src/macosx/pWolf.icns b/src/macosx/pWolf.icns new file mode 100644 index 0000000..b623a35 Binary files /dev/null and b/src/macosx/pWolf.icns differ diff --git a/src/macosx/timedemo.zsh b/src/macosx/timedemo.zsh new file mode 100644 index 0000000..14e834a --- /dev/null +++ b/src/macosx/timedemo.zsh @@ -0,0 +1,26 @@ +#!/bin/zsh + +buildRoot=./build +executable=$buildRoot/Quake3.app/Contents/MacOS/Quake3 +ls -l $executable + +flags="$flags +set timedemo 1" +flags="$flags +set s_initsound 0" +flags="$flags +set vm_cgame 1" +flags="$flags +set vm_game 1" +flags="$flags +set r_texturebits 16" +flags="$flags +set r_depthbits 16" +flags="$flags +set r_colorbits 16" +flags="$flags +set stencilbits 8" + +flags="$flags +set r_appleTransformHint 1" + +echo flags=$flags + +function demo { + echo Demo $* + $executable $flags +demo $* |& egrep "(seconds|VM)" +} + +demo foo + diff --git a/src/null/null_client.c b/src/null/null_client.c new file mode 100644 index 0000000..e009682 --- /dev/null +++ b/src/null/null_client.c @@ -0,0 +1,106 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "../client/client.h" + +cvar_t *cl_shownet; +// TTimo: win32 dedicated +cvar_t *cl_language; + +void CL_Shutdown( void ) { +} + +void CL_Init( void ) { + cl_shownet = Cvar_Get( "cl_shownet", "0", CVAR_TEMP ); + // TTimo: localisation, prolly not any use in dedicated / null client + cl_language = Cvar_Get( "cl_language", "0", CVAR_ARCHIVE ); +} + +void CL_MouseEvent( int dx, int dy, int time ) { +} + +void Key_WriteBindings( fileHandle_t f ) { +} + +void CL_Frame( int msec ) { +} + +void CL_PacketEvent( netadr_t from, msg_t *msg ) { +} + +void CL_CharEvent( int key ) { +} + +void CL_Disconnect( qboolean showMainMenu ) { +} + +void CL_MapLoading( void ) { +} + +qboolean CL_GameCommand( void ) { + return qfalse; // bk001204 - non-void +} + +void CL_KeyEvent( int key, qboolean down, unsigned time ) { +} + +qboolean UI_GameCommand( void ) { + return qfalse; +} + +void CL_ForwardCommandToServer( const char *string ) { +} + +void CL_ConsolePrint( char *txt ) { +} + +void CL_JoystickEvent( int axis, int value, int time ) { +} + +void CL_InitKeyCommands( void ) { +} + +void CL_CDDialog( void ) { +} + +void CL_FlushMemory( void ) { +} + +void CL_StartHunkUsers( void ) { +} + +// bk001119 - added new dummy for sv_init.c +void CL_ShutdownAll( void ) {}; + +// bk001208 - added new dummy (RC4) +qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { return qtrue; } + +// TTimo added for win32 dedicated +void Key_ClearStates( void ) { +} diff --git a/src/null/null_input.c b/src/null/null_input.c new file mode 100644 index 0000000..5ca3605 --- /dev/null +++ b/src/null/null_input.c @@ -0,0 +1,51 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../client/client.h" + +void IN_Init( void ) { +} + +void IN_Frame( void ) { +} + +void IN_Shutdown( void ) { +} + +void Sys_SendKeyEvents( void ) { +} + +// TTimo: added for win32 dedicated +void IN_MouseEvent( int mstate ) { +} + +void IN_Activate( qboolean active ) { +} + + + diff --git a/src/null/null_snddma.c b/src/null/null_snddma.c new file mode 100644 index 0000000..730896d --- /dev/null +++ b/src/null/null_snddma.c @@ -0,0 +1,65 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// snddma_null.c +// all other sound mixing is portable + +#include "../client/client.h" + +qboolean SNDDMA_Init( void ) { + return qfalse; +} + +int SNDDMA_GetDMAPos( void ) { + return 0; +} + +void SNDDMA_Shutdown( void ) { +} + +void SNDDMA_BeginPainting( void ) { +} + +void SNDDMA_Submit( void ) { +} + +// bk001119 - added boolean flag, match client/snd_public.h +sfxHandle_t S_RegisterSound( const char *name, qboolean compressed ) { + return 0; +} + +void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { +} + +void S_ClearSoundBuffer( void ) { +} + +// TTimo: added for win32 dedicated +void SNDDMA_Activate( void ) { +} diff --git a/src/qcommon/cm_load.c b/src/qcommon/cm_load.c new file mode 100644 index 0000000..9db5a2a --- /dev/null +++ b/src/qcommon/cm_load.c @@ -0,0 +1,880 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cmodel.c -- model loading + +#include "cm_local.h" + +#ifdef BSPC + +#include "../bspc/l_qfiles.h" + +void SetPlaneSignbits( cplane_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 << j; + } + } + out->signbits = bits; +} +#endif //BSPC + +// to allow boxes to be treated as brush models, we allocate +// some extra indexes along with those needed by the map +#define BOX_BRUSHES 1 +#define BOX_SIDES 6 +#define BOX_LEAFS 2 +#define BOX_PLANES 12 + +#define LL( x ) x = LittleLong( x ) + + +clipMap_t cm; +int c_pointcontents; +int c_traces, c_brush_traces, c_patch_traces; + + +byte *cmod_base; + +#ifndef BSPC +cvar_t *cm_noAreas; +cvar_t *cm_noCurves; +cvar_t *cm_playerCurveClip; +#endif + +cmodel_t box_model; +cplane_t *box_planes; +cbrush_t *box_brush; + + + +void CM_InitBoxHull( void ); +void CM_FloodAreaConnections( void ); + + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ + +/* +================= +CMod_LoadShaders +================= +*/ +void CMod_LoadShaders( lump_t *l ) { + dshader_t *in, *out; + int i, count; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "CMod_LoadShaders: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + if ( count < 1 ) { + Com_Error( ERR_DROP, "Map with no shaders" ); + } + cm.shaders = Hunk_Alloc( count * sizeof( *cm.shaders ), h_high ); + cm.numShaders = count; + + memcpy( cm.shaders, in, count * sizeof( *cm.shaders ) ); + + if ( LittleLong( 1 ) != 1 ) { + out = cm.shaders; + for ( i = 0 ; i < count ; i++, in++, out++ ) { + out->contentFlags = LittleLong( out->contentFlags ); + out->surfaceFlags = LittleLong( out->surfaceFlags ); + } + } +} + + +/* +================= +CMod_LoadSubmodels +================= +*/ +void CMod_LoadSubmodels( lump_t *l ) { + dmodel_t *in; + cmodel_t *out; + int i, j, count; + int *indexes; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "CMod_LoadSubmodels: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + if ( count < 1 ) { + Com_Error( ERR_DROP, "Map with no models" ); + } + cm.cmodels = Hunk_Alloc( count * sizeof( *cm.cmodels ), h_high ); + cm.numSubModels = count; + + if ( count > MAX_SUBMODELS ) { + Com_Error( ERR_DROP, "MAX_SUBMODELS exceeded" ); + } + + for ( i = 0 ; i < count ; i++, in++, out++ ) + { + out = &cm.cmodels[i]; + + for ( j = 0 ; j < 3 ; j++ ) + { // spread the mins / maxs by a pixel + out->mins[j] = LittleFloat( in->mins[j] ) - 1; + out->maxs[j] = LittleFloat( in->maxs[j] ) + 1; + } + + if ( i == 0 ) { + continue; // world model doesn't need other info + } + + // make a "leaf" just to hold the model's brushes and surfaces + out->leaf.numLeafBrushes = LittleLong( in->numBrushes ); + indexes = Hunk_Alloc( out->leaf.numLeafBrushes * 4, h_high ); + out->leaf.firstLeafBrush = indexes - cm.leafbrushes; + for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) { + indexes[j] = LittleLong( in->firstBrush ) + j; + } + + out->leaf.numLeafSurfaces = LittleLong( in->numSurfaces ); + indexes = Hunk_Alloc( out->leaf.numLeafSurfaces * 4, h_high ); + out->leaf.firstLeafSurface = indexes - cm.leafsurfaces; + for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) { + indexes[j] = LittleLong( in->firstSurface ) + j; + } + } +} + + +/* +================= +CMod_LoadNodes + +================= +*/ +void CMod_LoadNodes( lump_t *l ) { + dnode_t *in; + int child; + cNode_t *out; + int i, j, count; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + if ( count < 1 ) { + Com_Error( ERR_DROP, "Map has no nodes" ); + } + cm.nodes = Hunk_Alloc( count * sizeof( *cm.nodes ), h_high ); + cm.numNodes = count; + + out = cm.nodes; + + for ( i = 0 ; i < count ; i++, out++, in++ ) + { + out->plane = cm.planes + LittleLong( in->planeNum ); + for ( j = 0 ; j < 2 ; j++ ) + { + child = LittleLong( in->children[j] ); + out->children[j] = child; + } + } + +} + +/* +================= +CM_BoundBrush + +================= +*/ +void CM_BoundBrush( cbrush_t *b ) { + b->bounds[0][0] = -b->sides[0].plane->dist; + b->bounds[1][0] = b->sides[1].plane->dist; + + b->bounds[0][1] = -b->sides[2].plane->dist; + b->bounds[1][1] = b->sides[3].plane->dist; + + b->bounds[0][2] = -b->sides[4].plane->dist; + b->bounds[1][2] = b->sides[5].plane->dist; +} + + +/* +================= +CMod_LoadBrushes + +================= +*/ +void CMod_LoadBrushes( lump_t *l ) { + dbrush_t *in; + cbrush_t *out; + int i, count; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + cm.brushes = Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), h_high ); + cm.numBrushes = count; + + out = cm.brushes; + + for ( i = 0 ; i < count ; i++, out++, in++ ) { + out->sides = cm.brushsides + LittleLong( in->firstSide ); + out->numsides = LittleLong( in->numSides ); + + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum ); + } + out->contents = cm.shaders[out->shaderNum].contentFlags; + + CM_BoundBrush( out ); + } + +} + +/* +================= +CMod_LoadLeafs +================= +*/ +void CMod_LoadLeafs( lump_t *l ) { + int i; + cLeaf_t *out; + dleaf_t *in; + int count; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + if ( count < 1 ) { + Com_Error( ERR_DROP, "Map with no leafs" ); + } + + cm.leafs = Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), h_high ); + cm.numLeafs = count; + + out = cm.leafs; + for ( i = 0 ; i < count ; i++, in++, out++ ) + { + out->cluster = LittleLong( in->cluster ); + out->area = LittleLong( in->area ); + out->firstLeafBrush = LittleLong( in->firstLeafBrush ); + out->numLeafBrushes = LittleLong( in->numLeafBrushes ); + out->firstLeafSurface = LittleLong( in->firstLeafSurface ); + out->numLeafSurfaces = LittleLong( in->numLeafSurfaces ); + + if ( out->cluster >= cm.numClusters ) { + cm.numClusters = out->cluster + 1; + } + if ( out->area >= cm.numAreas ) { + cm.numAreas = out->area + 1; + } + } + + cm.areas = Hunk_Alloc( cm.numAreas * sizeof( *cm.areas ), h_high ); + cm.areaPortals = Hunk_Alloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), h_high ); +} + +/* +================= +CMod_LoadPlanes +================= +*/ +void CMod_LoadPlanes( lump_t *l ) { + int i, j; + cplane_t *out; + dplane_t *in; + int count; + int bits; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + if ( count < 1 ) { + Com_Error( ERR_DROP, "Map with no planes" ); + } + cm.planes = Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), h_high ); + cm.numPlanes = count; + + out = cm.planes; + + for ( i = 0 ; i < count ; i++, in++, out++ ) + { + bits = 0; + for ( j = 0 ; j < 3 ; j++ ) + { + out->normal[j] = LittleFloat( in->normal[j] ); + if ( out->normal[j] < 0 ) { + bits |= 1 << j; + } + } + + out->dist = LittleFloat( in->dist ); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +CMod_LoadLeafBrushes +================= +*/ +void CMod_LoadLeafBrushes( lump_t *l ) { + int i; + int *out; + int *in; + int count; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + cm.leafbrushes = Hunk_Alloc( count * sizeof( *cm.leafbrushes ), h_high ); + cm.numLeafBrushes = count; + + out = cm.leafbrushes; + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + *out = LittleLong( *in ); + } +} + +/* +================= +CMod_LoadLeafSurfaces +================= +*/ +void CMod_LoadLeafSurfaces( lump_t *l ) { + int i; + int *out; + int *in; + int count; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + cm.leafsurfaces = Hunk_Alloc( count * sizeof( *cm.leafsurfaces ), h_high ); + cm.numLeafSurfaces = count; + + out = cm.leafsurfaces; + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + *out = LittleLong( *in ); + } +} + +/* +================= +CMod_LoadBrushSides +================= +*/ +void CMod_LoadBrushSides( lump_t *l ) { + int i; + cbrushside_t *out; + dbrushside_t *in; + int count; + int num; + + in = ( void * )( cmod_base + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + count = l->filelen / sizeof( *in ); + + cm.brushsides = Hunk_Alloc( ( BOX_SIDES + count ) * sizeof( *cm.brushsides ), h_high ); + cm.numBrushSides = count; + + out = cm.brushsides; + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + num = LittleLong( in->planeNum ); + out->plane = &cm.planes[num]; + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum ); + } + out->surfaceFlags = cm.shaders[out->shaderNum].surfaceFlags; + } +} + + +/* +================= +CMod_LoadEntityString +================= +*/ +void CMod_LoadEntityString( lump_t *l ) { + cm.entityString = Hunk_Alloc( l->filelen, h_high ); + cm.numEntityChars = l->filelen; + memcpy( cm.entityString, cmod_base + l->fileofs, l->filelen ); +} + +/* +================= +CMod_LoadVisibility +================= +*/ +#define VIS_HEADER 8 +void CMod_LoadVisibility( lump_t *l ) { + int len; + byte *buf; + + len = l->filelen; + if ( !len ) { + cm.clusterBytes = ( cm.numClusters + 31 ) & ~31; + cm.visibility = Hunk_Alloc( cm.clusterBytes, h_high ); + memset( cm.visibility, 255, cm.clusterBytes ); + return; + } + buf = cmod_base + l->fileofs; + + cm.vised = qtrue; + cm.visibility = Hunk_Alloc( len, h_high ); + cm.numClusters = LittleLong( ( (int *)buf )[0] ); + cm.clusterBytes = LittleLong( ( (int *)buf )[1] ); + memcpy( cm.visibility, buf + VIS_HEADER, len - VIS_HEADER ); +} + +//================================================================== + + +/* +================= +CMod_LoadPatches +================= +*/ +#define MAX_PATCH_VERTS 1024 +void CMod_LoadPatches( lump_t *surfs, lump_t *verts ) { + drawVert_t *dv, *dv_p; + dsurface_t *in; + int count; + int i, j; + int c; + cPatch_t *patch; + vec3_t points[MAX_PATCH_VERTS]; + int width, height; + int shaderNum; + + in = ( void * )( cmod_base + surfs->fileofs ); + if ( surfs->filelen % sizeof( *in ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + cm.numSurfaces = count = surfs->filelen / sizeof( *in ); + cm.surfaces = Hunk_Alloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), h_high ); + + dv = ( void * )( cmod_base + verts->fileofs ); + if ( verts->filelen % sizeof( *dv ) ) { + Com_Error( ERR_DROP, "MOD_LoadBmodel: funny lump size" ); + } + + // scan through all the surfaces, but only load patches, + // not planar faces + for ( i = 0 ; i < count ; i++, in++ ) { + if ( LittleLong( in->surfaceType ) != MST_PATCH ) { + continue; // ignore other surfaces + } + // FIXME: check for non-colliding patches + + cm.surfaces[ i ] = patch = Hunk_Alloc( sizeof( *patch ), h_high ); + + // load the full drawverts onto the stack + width = LittleLong( in->patchWidth ); + height = LittleLong( in->patchHeight ); + c = width * height; + if ( c > MAX_PATCH_VERTS ) { + Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" ); + } + + dv_p = dv + LittleLong( in->firstVert ); + for ( j = 0 ; j < c ; j++, dv_p++ ) { + points[j][0] = LittleFloat( dv_p->xyz[0] ); + points[j][1] = LittleFloat( dv_p->xyz[1] ); + points[j][2] = LittleFloat( dv_p->xyz[2] ); + } + + shaderNum = LittleLong( in->shaderNum ); + patch->contents = cm.shaders[shaderNum].contentFlags; + patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags; + + // create the internal facet structure + patch->pc = CM_GeneratePatchCollide( width, height, points ); + } +} + +//================================================================== + + +#if 0 //BSPC +/* +================== +CM_FreeMap + +Free any loaded map and all submodels +================== +*/ +void CM_FreeMap( void ) { + memset( &cm, 0, sizeof( cm ) ); + Hunk_ClearHigh(); + CM_ClearLevelPatches(); +} +#endif //BSPC + +unsigned CM_LumpChecksum( lump_t *lump ) { + return LittleLong( Com_BlockChecksum( cmod_base + lump->fileofs, lump->filelen ) ); +} + +unsigned CM_Checksum( dheader_t *header ) { + unsigned checksums[16]; + checksums[0] = CM_LumpChecksum( &header->lumps[LUMP_SHADERS] ); + checksums[1] = CM_LumpChecksum( &header->lumps[LUMP_LEAFS] ); + checksums[2] = CM_LumpChecksum( &header->lumps[LUMP_LEAFBRUSHES] ); + checksums[3] = CM_LumpChecksum( &header->lumps[LUMP_LEAFSURFACES] ); + checksums[4] = CM_LumpChecksum( &header->lumps[LUMP_PLANES] ); + checksums[5] = CM_LumpChecksum( &header->lumps[LUMP_BRUSHSIDES] ); + checksums[6] = CM_LumpChecksum( &header->lumps[LUMP_BRUSHES] ); + checksums[7] = CM_LumpChecksum( &header->lumps[LUMP_MODELS] ); + checksums[8] = CM_LumpChecksum( &header->lumps[LUMP_NODES] ); + checksums[9] = CM_LumpChecksum( &header->lumps[LUMP_SURFACES] ); + checksums[10] = CM_LumpChecksum( &header->lumps[LUMP_DRAWVERTS] ); + + return LittleLong( Com_BlockChecksum( checksums, 11 * 4 ) ); +} + +/* +================== +CM_LoadMap + +Loads in the map and all submodels +================== +*/ +void CM_LoadMap( const char *name, qboolean clientload, int *checksum ) { + int *buf; + int i; + dheader_t header; + int length; + static unsigned last_checksum; + + if ( !name || !name[0] ) { + Com_Error( ERR_DROP, "CM_LoadMap: NULL name" ); + } + +#ifndef BSPC + cm_noAreas = Cvar_Get( "cm_noAreas", "0", CVAR_CHEAT ); + cm_noCurves = Cvar_Get( "cm_noCurves", "0", CVAR_CHEAT ); + cm_playerCurveClip = Cvar_Get( "cm_playerCurveClip", "1", CVAR_ARCHIVE | CVAR_CHEAT ); +#endif + Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload ); + + if ( !strcmp( cm.name, name ) && clientload ) { + *checksum = last_checksum; + return; + } + + // free old stuff + memset( &cm, 0, sizeof( cm ) ); + CM_ClearLevelPatches(); + + if ( !name[0] ) { + cm.numLeafs = 1; + cm.numClusters = 1; + cm.numAreas = 1; + cm.cmodels = Hunk_Alloc( sizeof( *cm.cmodels ), h_high ); + *checksum = 0; + return; + } + + // + // load the file + // +#ifndef BSPC + length = FS_ReadFile( name, (void **)&buf ); +#else + length = LoadQuakeFile( (quakefile_t *) name, (void **)&buf ); +#endif + + if ( !buf ) { + Com_Error( ERR_DROP, "Couldn't load %s", name ); + } + + last_checksum = LittleLong( Com_BlockChecksum( buf, length ) ); + *checksum = last_checksum; + + header = *(dheader_t *)buf; + for ( i = 0 ; i < sizeof( dheader_t ) / 4 ; i++ ) { + ( (int *)&header )[i] = LittleLong( ( (int *)&header )[i] ); + } + + if ( header.version != BSP_VERSION ) { + Com_Error( ERR_DROP, "CM_LoadMap: %s has wrong version number (%i should be %i)" + , name, header.version, BSP_VERSION ); + } + + cmod_base = (byte *)buf; + + // load into heap + CMod_LoadShaders( &header.lumps[LUMP_SHADERS] ); + CMod_LoadLeafs( &header.lumps[LUMP_LEAFS] ); + CMod_LoadLeafBrushes( &header.lumps[LUMP_LEAFBRUSHES] ); + CMod_LoadLeafSurfaces( &header.lumps[LUMP_LEAFSURFACES] ); + CMod_LoadPlanes( &header.lumps[LUMP_PLANES] ); + CMod_LoadBrushSides( &header.lumps[LUMP_BRUSHSIDES] ); + CMod_LoadBrushes( &header.lumps[LUMP_BRUSHES] ); + CMod_LoadSubmodels( &header.lumps[LUMP_MODELS] ); + CMod_LoadNodes( &header.lumps[LUMP_NODES] ); + CMod_LoadEntityString( &header.lumps[LUMP_ENTITIES] ); + CMod_LoadVisibility( &header.lumps[LUMP_VISIBILITY] ); + CMod_LoadPatches( &header.lumps[LUMP_SURFACES], &header.lumps[LUMP_DRAWVERTS] ); + + // we are NOT freeing the file, because it is cached for the ref + FS_FreeFile( buf ); + + CM_InitBoxHull(); + + CM_FloodAreaConnections(); + + // allow this to be cached if it is loaded by the server + if ( !clientload ) { + Q_strncpyz( cm.name, name, sizeof( cm.name ) ); + } +} + +/* +================== +CM_ClearMap +================== +*/ +void CM_ClearMap( void ) { + Com_Memset( &cm, 0, sizeof( cm ) ); + CM_ClearLevelPatches(); +} + +/* +================== +CM_ClipHandleToModel +================== +*/ +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle ) { + if ( handle < 0 ) { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle ); + } + if ( handle < cm.numSubModels ) { + return &cm.cmodels[handle]; + } + if ( handle == BOX_MODEL_HANDLE || handle == CAPSULE_MODEL_HANDLE ) { + return &box_model; + } + if ( handle < MAX_SUBMODELS ) { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i", + cm.numSubModels, handle, MAX_SUBMODELS ); + } + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS ); + + return NULL; + +} + +/* +================== +CM_InlineModel +================== +*/ +clipHandle_t CM_InlineModel( int index ) { + if ( index < 0 || index >= cm.numSubModels ) { + Com_Error( ERR_DROP, "CM_InlineModel: bad number" ); + } + return index; +} + +int CM_NumClusters( void ) { + return cm.numClusters; +} + +int CM_NumInlineModels( void ) { + return cm.numSubModels; +} + +char *CM_EntityString( void ) { + return cm.entityString; +} + +int CM_LeafCluster( int leafnum ) { + if ( leafnum < 0 || leafnum >= cm.numLeafs ) { + Com_Error( ERR_DROP, "CM_LeafCluster: bad number" ); + } + return cm.leafs[leafnum].cluster; +} + +int CM_LeafArea( int leafnum ) { + if ( leafnum < 0 || leafnum >= cm.numLeafs ) { + Com_Error( ERR_DROP, "CM_LeafArea: bad number" ); + } + return cm.leafs[leafnum].area; +} + +//======================================================================= + + +/* +=================== +CM_InitBoxHull + +Set up the planes and nodes so that the six floats of a bounding box +can just be stored out and get a proper clipping hull structure. +=================== +*/ +void CM_InitBoxHull( void ) { + int i; + int side; + cplane_t *p; + cbrushside_t *s; + + box_planes = &cm.planes[cm.numPlanes]; + + box_brush = &cm.brushes[cm.numBrushes]; + box_brush->numsides = 6; + box_brush->sides = cm.brushsides + cm.numBrushSides; + box_brush->contents = CONTENTS_BODY; + + box_model.leaf.numLeafBrushes = 1; +// box_model.leaf.firstLeafBrush = cm.numBrushes; + box_model.leaf.firstLeafBrush = cm.numLeafBrushes; + cm.leafbrushes[cm.numLeafBrushes] = cm.numBrushes; + + for ( i = 0 ; i < 6 ; i++ ) + { + side = i & 1; + + // brush sides + s = &cm.brushsides[cm.numBrushSides + i]; + s->plane = cm.planes + ( cm.numPlanes + i * 2 + side ); + s->surfaceFlags = 0; + + // planes + p = &box_planes[i * 2]; + p->type = i >> 1; + p->signbits = 0; + VectorClear( p->normal ); + p->normal[i >> 1] = 1; + + p = &box_planes[i * 2 + 1]; + p->type = 3 + ( i >> 1 ); + p->signbits = 0; + VectorClear( p->normal ); + p->normal[i >> 1] = -1; + + SetPlaneSignbits( p ); + } +} + +/* +=================== +CM_TempBoxModel + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +Capsules are handled differently though. +=================== +*/ +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ) { + + VectorCopy( mins, box_model.mins ); + VectorCopy( maxs, box_model.maxs ); + + if ( capsule ) { + return CAPSULE_MODEL_HANDLE; + } + + box_planes[0].dist = maxs[0]; + box_planes[1].dist = -maxs[0]; + box_planes[2].dist = mins[0]; + box_planes[3].dist = -mins[0]; + box_planes[4].dist = maxs[1]; + box_planes[5].dist = -maxs[1]; + box_planes[6].dist = mins[1]; + box_planes[7].dist = -mins[1]; + box_planes[8].dist = maxs[2]; + box_planes[9].dist = -maxs[2]; + box_planes[10].dist = mins[2]; + box_planes[11].dist = -mins[2]; + + VectorCopy( mins, box_brush->bounds[0] ); + VectorCopy( maxs, box_brush->bounds[1] ); + + return BOX_MODEL_HANDLE; +} + +// DHM - Nerve +void CM_SetTempBoxModelContents( int contents ) { + + box_brush->contents = contents; +} +// dhm + +/* +=================== +CM_ModelBounds +=================== +*/ +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + VectorCopy( cmod->mins, mins ); + VectorCopy( cmod->maxs, maxs ); +} + + diff --git a/src/qcommon/cm_local.h b/src/qcommon/cm_local.h new file mode 100644 index 0000000..99db68c --- /dev/null +++ b/src/qcommon/cm_local.h @@ -0,0 +1,206 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "cm_polylib.h" + + +// (SA) DM needs more than 256 since this includes func_static and func_explosives +//#define MAX_SUBMODELS 256 +//#define BOX_MODEL_HANDLE 255 + +#define MAX_SUBMODELS 512 +#define BOX_MODEL_HANDLE 511 +#define CAPSULE_MODEL_HANDLE 510 + + +typedef struct { + cplane_t *plane; + int children[2]; // negative numbers are leafs +} cNode_t; + +typedef struct { + int cluster; + int area; + + int firstLeafBrush; + int numLeafBrushes; + + int firstLeafSurface; + int numLeafSurfaces; +} cLeaf_t; + +typedef struct cmodel_s { + vec3_t mins, maxs; + cLeaf_t leaf; // submodels don't reference the main tree +} cmodel_t; + +typedef struct { + cplane_t *plane; + int surfaceFlags; + int shaderNum; +} cbrushside_t; + +typedef struct { + int shaderNum; // the shader that determined the contents + int contents; + vec3_t bounds[2]; + int numsides; + cbrushside_t *sides; + int checkcount; // to avoid repeated testings +} cbrush_t; + + +typedef struct { + int checkcount; // to avoid repeated testings + int surfaceFlags; + int contents; + struct patchCollide_s *pc; +} cPatch_t; + + +typedef struct { + int floodnum; + int floodvalid; +} cArea_t; + +typedef struct { + char name[MAX_QPATH]; + + int numShaders; + dshader_t *shaders; + + int numBrushSides; + cbrushside_t *brushsides; + + int numPlanes; + cplane_t *planes; + + int numNodes; + cNode_t *nodes; + + int numLeafs; + cLeaf_t *leafs; + + int numLeafBrushes; + int *leafbrushes; + + int numLeafSurfaces; + int *leafsurfaces; + + int numSubModels; + cmodel_t *cmodels; + + int numBrushes; + cbrush_t *brushes; + + int numClusters; + int clusterBytes; + byte *visibility; + qboolean vised; // if false, visibility is just a single cluster of ffs + + int numEntityChars; + char *entityString; + + int numAreas; + cArea_t *areas; + int *areaPortals; // [ numAreas*numAreas ] reference counts + + int numSurfaces; + cPatch_t **surfaces; // non-patches will be NULL + + int floodvalid; + int checkcount; // incremented on each trace +} clipMap_t; + + +// keep 1/8 unit away to keep the position valid before network snapping +// and to avoid various numeric issues +#define SURFACE_CLIP_EPSILON ( 0.125 ) + +extern clipMap_t cm; +extern int c_pointcontents; +extern int c_traces, c_brush_traces, c_patch_traces; +extern cvar_t *cm_noAreas; +extern cvar_t *cm_noCurves; +extern cvar_t *cm_playerCurveClip; + +// cm_test.c + +// Used for oriented capsule collision detection +typedef struct +{ + qboolean use; + float radius; + float halfheight; + vec3_t offset; +} sphere_t; + +typedef struct { + vec3_t start; + vec3_t end; + vec3_t size[2]; // size of the box being swept through the model + vec3_t offsets[8]; // [signbits][x] = either size[0][x] or size[1][x] + float maxOffset; // longest corner length from origin + vec3_t extents; // greatest of abs(size[0]) and abs(size[1]) + vec3_t bounds[2]; // enclosing box of start and end surrounding by size + vec3_t modelOrigin; // origin of the model tracing through + int contents; // ored contents of the model tracing through + qboolean isPoint; // optimized case + trace_t trace; // returned from trace call + sphere_t sphere; // sphere for oriendted capsule collision +} traceWork_t; + +typedef struct leafList_s { + int count; + int maxcount; + qboolean overflowed; + int *list; + vec3_t bounds[2]; + int lastLeaf; // for overflows where each leaf can't be stored individually + void ( *storeLeafs )( struct leafList_s *ll, int nodenum ); +} leafList_t; + + +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ); + +void CM_StoreLeafs( leafList_t *ll, int nodenum ); +void CM_StoreBrushes( leafList_t *ll, int nodenum ); + +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ); + +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle ); + +// cm_patch.c + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_ClearLevelPatches( void ); diff --git a/src/qcommon/cm_patch.c b/src/qcommon/cm_patch.c new file mode 100644 index 0000000..6a4baa2 --- /dev/null +++ b/src/qcommon/cm_patch.c @@ -0,0 +1,1886 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "cm_local.h" +#include "cm_patch.h" + +//#define CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + +/* +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + qboolean borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 +*/ + +#define ADDBEVELS + +int c_totalPatchBlocks; +int c_totalPatchSurfaces; +int c_totalPatchEdges; + +static const patchCollide_t *debugPatchCollide; +static const facet_t *debugFacet; +static qboolean debugBlock; +static vec3_t debugBlockPoints[4]; + +/* +================= +CM_ClearLevelPatches +================= +*/ +void CM_ClearLevelPatches( void ) { + debugPatchCollide = NULL; + debugFacet = NULL; +} + +/* +================= +CM_SignbitsForNormal +================= +*/ +static int CM_SignbitsForNormal( vec3_t normal ) { + int bits, j; + + bits = 0; + for ( j = 0 ; j < 3 ; j++ ) { + if ( normal[j] < 0 ) { + bits |= 1 << j; + } + } + return bits; +} + +/* +===================== +CM_PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +static qboolean CM_PlaneFromPoints( vec4_t plane, vec3_t a, vec3_t b, vec3_t c ) { + vec3_t d1, d2; + + VectorSubtract( b, a, d1 ); + VectorSubtract( c, a, d2 ); + CrossProduct( d2, d1, plane ); + if ( VectorNormalize( plane ) == 0 ) { + return qfalse; + } + + plane[3] = DotProduct( a, plane ); + return qtrue; +} + + +/* +================================================================================ + +GRID SUBDIVISION + +================================================================================ +*/ + +/* +================= +CM_NeedsSubdivision + +Returns true if the given quadratic curve is not flat enough for our +collision detection purposes +================= +*/ +static qboolean CM_NeedsSubdivision( vec3_t a, vec3_t b, vec3_t c ) { + vec3_t cmid; + vec3_t lmid; + vec3_t delta; + float dist; + int i; + + // calculate the linear midpoint + for ( i = 0 ; i < 3 ; i++ ) { + lmid[i] = 0.5 * ( a[i] + c[i] ); + } + + // calculate the exact curve midpoint + for ( i = 0 ; i < 3 ; i++ ) { + cmid[i] = 0.5 * ( 0.5 * ( a[i] + b[i] ) + 0.5 * ( b[i] + c[i] ) ); + } + + // see if the curve is far enough away from the linear mid + VectorSubtract( cmid, lmid, delta ); + dist = VectorLength( delta ); + + return dist >= SUBDIVIDE_DISTANCE; +} + +/* +=============== +CM_Subdivide + +a, b, and c are control points. +the subdivided sequence will be: a, out1, out2, out3, c +=============== +*/ +static void CM_Subdivide( vec3_t a, vec3_t b, vec3_t c, vec3_t out1, vec3_t out2, vec3_t out3 ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + out1[i] = 0.5 * ( a[i] + b[i] ); + out3[i] = 0.5 * ( b[i] + c[i] ); + out2[i] = 0.5 * ( out1[i] + out3[i] ); + } +} + +/* +================= +CM_TransposeGrid + +Swaps the rows and columns in place +================= +*/ +static void CM_TransposeGrid( cGrid_t *grid ) { + int i, j, l; + vec3_t temp; + qboolean tempWrap; + + if ( grid->width > grid->height ) { + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = i + 1 ; j < grid->width ; j++ ) { + if ( j < grid->height ) { + // swap the value + VectorCopy( grid->points[i][j], temp ); + VectorCopy( grid->points[j][i], grid->points[i][j] ); + VectorCopy( temp, grid->points[j][i] ); + } else { + // just copy + VectorCopy( grid->points[j][i], grid->points[i][j] ); + } + } + } + } else { + for ( i = 0 ; i < grid->width ; i++ ) { + for ( j = i + 1 ; j < grid->height ; j++ ) { + if ( j < grid->width ) { + // swap the value + VectorCopy( grid->points[j][i], temp ); + VectorCopy( grid->points[i][j], grid->points[j][i] ); + VectorCopy( temp, grid->points[i][j] ); + } else { + // just copy + VectorCopy( grid->points[i][j], grid->points[j][i] ); + } + } + } + } + + l = grid->width; + grid->width = grid->height; + grid->height = l; + + tempWrap = grid->wrapWidth; + grid->wrapWidth = grid->wrapHeight; + grid->wrapHeight = tempWrap; +} + +/* +=================== +CM_SetGridWrapWidth + +If the left and right columns are exactly equal, set grid->wrapWidth qtrue +=================== +*/ +static void CM_SetGridWrapWidth( cGrid_t *grid ) { + int i, j; + float d; + + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + d = grid->points[0][i][j] - grid->points[grid->width - 1][i][j]; + if ( d < -WRAP_POINT_EPSILON || d > WRAP_POINT_EPSILON ) { + break; + } + } + if ( j != 3 ) { + break; + } + } + if ( i == grid->height ) { + grid->wrapWidth = qtrue; + } else { + grid->wrapWidth = qfalse; + } +} + + +/* +================= +CM_SubdivideGridColumns + +Adds columns as necessary to the grid until +all the aproximating points are within SUBDIVIDE_DISTANCE +from the true curve +================= +*/ +static void CM_SubdivideGridColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 2 ; ) { + // grid->points[i][x] is an interpolating control point + // grid->points[i+1][x] is an aproximating control point + // grid->points[i+2][x] is an interpolating control point + + // + // first see if we can collapse the aproximating collumn away + // + for ( j = 0 ; j < grid->height ; j++ ) { + if ( CM_NeedsSubdivision( grid->points[i][j], grid->points[i + 1][j], grid->points[i + 2][j] ) ) { + break; + } + } + if ( j == grid->height ) { + // all of the points were close enough to the linear midpoints + // that we can collapse the entire column away + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k - 1][j] ); + } + } + + grid->width--; + + // go to the next curve segment + i++; + continue; + } + + // + // we need to subdivide the curve + // + for ( j = 0 ; j < grid->height ; j++ ) { + vec3_t prev, mid, next; + + // save the control points now + VectorCopy( grid->points[i][j], prev ); + VectorCopy( grid->points[i + 1][j], mid ); + VectorCopy( grid->points[i + 2][j], next ); + + // make room for two additional columns in the grid + // columns i+1 will be replaced, column i+2 will become i+4 + // i+1, i+2, and i+3 will be generated + for ( k = grid->width - 1 ; k > i + 1 ; k-- ) { + VectorCopy( grid->points[k][j], grid->points[k + 2][j] ); + } + + // generate the subdivided points + CM_Subdivide( prev, mid, next, grid->points[i + 1][j], grid->points[i + 2][j], grid->points[i + 3][j] ); + } + + grid->width += 2; + + // the new aproximating point at i+1 may need to be removed + // or subdivided farther, so don't advance i + } +} + + +#define POINT_EPSILON 0.1 +/* +====================== +CM_ComparePoints +====================== +*/ +static qboolean CM_ComparePoints( float *a, float *b ) { + float d; + + d = a[0] - b[0]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[1] - b[1]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[2] - b[2]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + return qtrue; +} + +/* +================= +CM_RemoveDegenerateColumns + +If there are any identical columns, remove them +================= +*/ +static void CM_RemoveDegenerateColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height ; j++ ) { + if ( !CM_ComparePoints( grid->points[i][j], grid->points[i + 1][j] ) ) { + break; + } + } + + if ( j != grid->height ) { + continue; // not degenerate + } + + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k - 1][j] ); + } + } + grid->width--; + + // check against the next column + i--; + } +} + +/* +================================================================================ + +PATCH COLLIDE GENERATION + +================================================================================ +*/ + +static int numPlanes; +static patchPlane_t planes[MAX_PATCH_PLANES]; + +static int numFacets; +static facet_t facets[MAX_PATCH_PLANES]; //maybe MAX_FACETS ?? + +#define NORMAL_EPSILON 0.0001 +#define DIST_EPSILON 0.02 + +int CM_PlaneEqual( patchPlane_t *p, float plane[4], int *flipped ) { + float invplane[4]; + + if ( + fabs( p->plane[0] - plane[0] ) < NORMAL_EPSILON + && fabs( p->plane[1] - plane[1] ) < NORMAL_EPSILON + && fabs( p->plane[2] - plane[2] ) < NORMAL_EPSILON + && fabs( p->plane[3] - plane[3] ) < DIST_EPSILON ) { + *flipped = qfalse; + return qtrue; + } + + VectorNegate( plane, invplane ); + invplane[3] = -plane[3]; + + if ( + fabs( p->plane[0] - invplane[0] ) < NORMAL_EPSILON + && fabs( p->plane[1] - invplane[1] ) < NORMAL_EPSILON + && fabs( p->plane[2] - invplane[2] ) < NORMAL_EPSILON + && fabs( p->plane[3] - invplane[3] ) < DIST_EPSILON ) { + *flipped = qtrue; + return qtrue; + } + + return qfalse; +} + +void CM_SnapVector( vec3_t normal ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( fabs( normal[i] - 1 ) < NORMAL_EPSILON ) { + VectorClear( normal ); + normal[i] = 1; + break; + } + if ( fabs( normal[i] - -1 ) < NORMAL_EPSILON ) { + VectorClear( normal ); + normal[i] = -1; + break; + } + } +} + +int CM_FindPlane2( float plane[4], int *flipped ) { + int i; + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if ( CM_PlaneEqual( &planes[i], plane, flipped ) ) { + return i; + } + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + *flipped = qfalse; + + return numPlanes - 1; +} + +/* +================== +CM_FindPlane +================== +*/ +static int CM_FindPlane( float *p1, float *p2, float *p3 ) { + float plane[4]; + int i; + float d; + + if ( !CM_PlaneFromPoints( plane, p1, p2, p3 ) ) { + return -1; + } + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if ( DotProduct( plane, planes[i].plane ) < 0 ) { + continue; // allow backwards planes? + } + + d = DotProduct( p1, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p2, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p3, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + // found it + return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + return numPlanes - 1; +} + + +/* +================== +CM_PointOnPlaneSide +================== +*/ +static int CM_PointOnPlaneSide( float *p, int planeNum ) { + float *plane; + float d; + + if ( planeNum == -1 ) { + return SIDE_ON; + } + plane = planes[ planeNum ].plane; + + d = DotProduct( p, plane ) - plane[3]; + + if ( d > PLANE_TRI_EPSILON ) { + return SIDE_FRONT; + } + + if ( d < -PLANE_TRI_EPSILON ) { + return SIDE_BACK; + } + + return SIDE_ON; +} + +static int CM_GridPlane( int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int tri ) { + int p; + + p = gridPlanes[i][j][tri]; + if ( p != -1 ) { + return p; + } + p = gridPlanes[i][j][!tri]; + if ( p != -1 ) { + return p; + } + + // should never happen + Com_Printf( "WARNING: CM_GridPlane unresolvable\n" ); + return -1; +} + +/* +================== +CM_EdgePlaneNum +================== +*/ +static int CM_EdgePlaneNum( cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int k ) { + float *p1, *p2; + vec3_t up; + int p; + + switch ( k ) { + case 0: // top border + p1 = grid->points[i][j]; + p2 = grid->points[i + 1][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 2: // bottom border + p1 = grid->points[i][j + 1]; + p2 = grid->points[i + 1][j + 1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 3: // left border + p1 = grid->points[i][j]; + p2 = grid->points[i][j + 1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 1: // right border + p1 = grid->points[i + 1][j]; + p2 = grid->points[i + 1][j + 1]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 4: // diagonal out of triangle 0 + p1 = grid->points[i + 1][j + 1]; + p2 = grid->points[i][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 5: // diagonal out of triangle 1 + p1 = grid->points[i][j]; + p2 = grid->points[i + 1][j + 1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + } + + Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" ); + return -1; +} + +/* +=================== +CM_SetBorderInward +=================== +*/ +static void CM_SetBorderInward( facet_t *facet, cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], + int i, int j, int which ) { + int k, l; + float *points[4]; + int numPoints; + + switch ( which ) { + case - 1: + points[0] = grid->points[i][j]; + points[1] = grid->points[i + 1][j]; + points[2] = grid->points[i + 1][j + 1]; + points[3] = grid->points[i][j + 1]; + numPoints = 4; + break; + case 0: + points[0] = grid->points[i][j]; + points[1] = grid->points[i + 1][j]; + points[2] = grid->points[i + 1][j + 1]; + numPoints = 3; + break; + case 1: + points[0] = grid->points[i + 1][j + 1]; + points[1] = grid->points[i][j + 1]; + points[2] = grid->points[i][j]; + numPoints = 3; + break; + default: + Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" ); + numPoints = 0; + break; + } + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + int front, back; + + front = 0; + back = 0; + + for ( l = 0 ; l < numPoints ; l++ ) { + int side; + + side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] ); + if ( side == SIDE_FRONT ) { + front++; + } + if ( side == SIDE_BACK ) { + back++; + } + } + + if ( front && !back ) { + facet->borderInward[k] = qtrue; + } else if ( back && !front ) { + facet->borderInward[k] = qfalse; + } else if ( !front && !back ) { + // flat side border + facet->borderPlanes[k] = -1; + } else { + // bisecting side border + Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" ); + facet->borderInward[k] = qfalse; + if ( !debugBlock ) { + debugBlock = qtrue; + VectorCopy( grid->points[i][j], debugBlockPoints[0] ); + VectorCopy( grid->points[i + 1][j], debugBlockPoints[1] ); + VectorCopy( grid->points[i + 1][j + 1], debugBlockPoints[2] ); + VectorCopy( grid->points[i][j + 1], debugBlockPoints[3] ); + } + } + } +} + +/* +================== +CM_ValidateFacet + +If the facet isn't bounded by its borders, we screwed up. +================== +*/ +static qboolean CM_ValidateFacet( facet_t *facet ) { + float plane[4]; + int j; + winding_t *w; + vec3_t bounds[2]; + + if ( facet->surfacePlane == -1 ) { + return qfalse; + } + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if ( facet->borderPlanes[j] == -1 ) { + return qfalse; + } + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + + if ( !w ) { + return qfalse; // winding was completely chopped away + } + + // see if the facet is unreasonably large + WindingBounds( w, bounds[0], bounds[1] ); + FreeWinding( w ); + + for ( j = 0 ; j < 3 ; j++ ) { + if ( bounds[1][j] - bounds[0][j] > 4096 ) { + return qfalse; // we must be missing a plane + } + } + return qtrue; // winding is fine +} + +/* +================== +CM_AddFacetBevels +================== +*/ + +void CM_AddFacetBevels( facet_t *facet ) { + + int i, j, k, l; + int axis, dir, order, flipped; + float plane[4], d, newplane[4]; + winding_t *w, *w2; + vec3_t mins, maxs, vec, vec2; + +#ifndef ADDBEVELS + return; +#endif + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if ( facet->borderPlanes[j] == facet->surfacePlane ) { + continue; + } + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( !w ) { + return; + } + + WindingBounds( w, mins, maxs ); + + // add the axial planes + order = 0; + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) + { + VectorClear( plane ); + plane[axis] = dir; + if ( dir == 1 ) { + plane[3] = maxs[axis]; + } else { + plane[3] = -mins[axis]; + } + //if it's the surface plane + if ( CM_PlaneEqual( &planes[facet->surfacePlane], plane, &flipped ) ) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if ( CM_PlaneEqual( &planes[facet->borderPlanes[i]], plane, &flipped ) ) { + break; + } + } + + if ( i == facet->numBorders ) { + if ( facet->numBorders > 4 + 6 + 16 ) { + Com_Printf( "ERROR: too many bevels\n" ); + } + facet->borderPlanes[facet->numBorders] = CM_FindPlane2( plane, &flipped ); + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + facet->numBorders++; + } + } + } + // + // add the edge bevels + // + // test the non-axial plane edges + for ( j = 0 ; j < w->numpoints ; j++ ) + { + k = ( j + 1 ) % w->numpoints; + VectorSubtract( w->p[j], w->p[k], vec ); + //if it's a degenerate edge + if ( VectorNormalize( vec ) < 0.5 ) { + continue; + } + CM_SnapVector( vec ); + for ( k = 0; k < 3 ; k++ ) + if ( vec[k] == -1 || vec[k] == 1 ) { + break; + } // axial + if ( k < 3 ) { + continue; // only test non-axial edges + + } + // try the six possible slanted axials from this edge + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + // construct a plane + VectorClear( vec2 ); + vec2[axis] = dir; + CrossProduct( vec, vec2, plane ); + if ( VectorNormalize( plane ) < 0.5 ) { + continue; + } + plane[3] = DotProduct( w->p[j], plane ); + + // if all the points of the facet winding are + // behind this plane, it is a proper edge bevel + for ( l = 0 ; l < w->numpoints ; l++ ) + { + d = DotProduct( w->p[l], plane ) - plane[3]; + if ( d > 0.1 ) { + break; // point in front + } + } + if ( l < w->numpoints ) { + continue; + } + + //if it's the surface plane + if ( CM_PlaneEqual( &planes[facet->surfacePlane], plane, &flipped ) ) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if ( CM_PlaneEqual( &planes[facet->borderPlanes[i]], plane, &flipped ) ) { + break; + } + } + + if ( i == facet->numBorders ) { + if ( facet->numBorders > 4 + 6 + 16 ) { + Com_Printf( "ERROR: too many bevels\n" ); + } + facet->borderPlanes[facet->numBorders] = CM_FindPlane2( plane, &flipped ); + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + if ( facet->borderPlanes[facet->numBorders] == + facet->borderPlanes[k] ) { + Com_Printf( "WARNING: bevel plane already used\n" ); + } + } + + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + // + w2 = CopyWinding( w ); + Vector4Copy( planes[facet->borderPlanes[facet->numBorders]].plane, newplane ); + if ( !facet->borderInward[facet->numBorders] ) { + VectorNegate( newplane, newplane ); + newplane[3] = -newplane[3]; + } //end if + ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f ); + if ( !w2 ) { + // TTimo - can't stand this, useless and noisy + //Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n"); + continue; + } else { + FreeWinding( w2 ); + } + // + facet->numBorders++; + //already got a bevel +// break; + } + } + } + } + FreeWinding( w ); + +#ifndef BSPC + //add opposite plane + facet->borderPlanes[facet->numBorders] = facet->surfacePlane; + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = qtrue; + facet->numBorders++; +#endif //BSPC + +} + +typedef enum { + EN_TOP, + EN_RIGHT, + EN_BOTTOM, + EN_LEFT +} edgeName_t; + +/* +================== +CM_PatchCollideFromGrid +================== +*/ +static void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf ) { + int i, j; + float *p1, *p2, *p3; + MAC_STATIC int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2]; + facet_t *facet; + int borders[4]; + int noAdjust[4]; + + numPlanes = 0; + numFacets = 0; + + // find the planes for each triangle of the grid + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + p1 = grid->points[i][j]; + p2 = grid->points[i + 1][j]; + p3 = grid->points[i + 1][j + 1]; + gridPlanes[i][j][0] = CM_FindPlane( p1, p2, p3 ); + + p1 = grid->points[i + 1][j + 1]; + p2 = grid->points[i][j + 1]; + p3 = grid->points[i][j]; + gridPlanes[i][j][1] = CM_FindPlane( p1, p2, p3 ); + } + } + + // create the borders for each facet + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + + borders[EN_TOP] = -1; + if ( j > 0 ) { + borders[EN_TOP] = gridPlanes[i][j - 1][1]; + } else if ( grid->wrapHeight ) { + borders[EN_TOP] = gridPlanes[i][grid->height - 2][1]; + } + noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i][j][0] ); + if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) { + borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 ); + } + + borders[EN_BOTTOM] = -1; + if ( j < grid->height - 2 ) { + borders[EN_BOTTOM] = gridPlanes[i][j + 1][0]; + } else if ( grid->wrapHeight ) { + borders[EN_BOTTOM] = gridPlanes[i][0][0]; + } + noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i][j][1] ); + if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) { + borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 ); + } + + borders[EN_LEFT] = -1; + if ( i > 0 ) { + borders[EN_LEFT] = gridPlanes[i - 1][j][0]; + } else if ( grid->wrapWidth ) { + borders[EN_LEFT] = gridPlanes[grid->width - 2][j][0]; + } + noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i][j][1] ); + if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) { + borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 ); + } + + borders[EN_RIGHT] = -1; + if ( i < grid->width - 2 ) { + borders[EN_RIGHT] = gridPlanes[i + 1][j][1]; + } else if ( grid->wrapWidth ) { + borders[EN_RIGHT] = gridPlanes[0][j][1]; + } + noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i][j][0] ); + if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) { + borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 ); + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + memset( facet, 0, sizeof( *facet ) ); + + if ( gridPlanes[i][j][0] == gridPlanes[i][j][1] ) { + if ( gridPlanes[i][j][0] == -1 ) { + continue; // degenrate + } + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 4; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = borders[EN_BOTTOM]; + facet->borderNoAdjust[2] = noAdjust[EN_BOTTOM]; + facet->borderPlanes[3] = borders[EN_LEFT]; + facet->borderNoAdjust[3] = noAdjust[EN_LEFT]; + CM_SetBorderInward( facet, grid, gridPlanes, i, j, -1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } else { + // two seperate triangles + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = gridPlanes[i][j][1]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_BOTTOM]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 0 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + memset( facet, 0, sizeof( *facet ) ); + + facet->surfacePlane = gridPlanes[i][j][1]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_BOTTOM]; + facet->borderNoAdjust[0] = noAdjust[EN_BOTTOM]; + facet->borderPlanes[1] = borders[EN_LEFT]; + facet->borderNoAdjust[1] = noAdjust[EN_LEFT]; + facet->borderPlanes[2] = gridPlanes[i][j][0]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_TOP]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } + } + } + + // copy the results out + pf->numPlanes = numPlanes; + pf->numFacets = numFacets; + pf->facets = Hunk_Alloc( numFacets * sizeof( *pf->facets ), h_high ); + memcpy( pf->facets, facets, numFacets * sizeof( *pf->facets ) ); + pf->planes = Hunk_Alloc( numPlanes * sizeof( *pf->planes ), h_high ); + memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) ); +} + + +/* +=================== +CM_GeneratePatchCollide + +Creates an internal structure that will be used to perform +collision detection with a patch mesh. + +Points is packed as concatenated rows. +=================== +*/ +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ) { + patchCollide_t *pf; + MAC_STATIC cGrid_t grid; + int i, j; + + if ( width <= 2 || height <= 2 || !points ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)", + width, height, points ); + } + + if ( !( width & 1 ) || !( height & 1 ) ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" ); + } + + if ( width > MAX_GRID_SIZE || height > MAX_GRID_SIZE ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > MAX_GRID_SIZE" ); + } + + // build a grid + grid.width = width; + grid.height = height; + grid.wrapWidth = qfalse; + grid.wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + VectorCopy( points[j * width + i], grid.points[i][j] ); + } + } + + // subdivide the grid + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + CM_TransposeGrid( &grid ); + + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + // we now have a grid of points exactly on the curve + // the aproximate surface defined by these points will be + // collided against + pf = Hunk_Alloc( sizeof( *pf ), h_high ); + ClearBounds( pf->bounds[0], pf->bounds[1] ); + for ( i = 0 ; i < grid.width ; i++ ) { + for ( j = 0 ; j < grid.height ; j++ ) { + AddPointToBounds( grid.points[i][j], pf->bounds[0], pf->bounds[1] ); + } + } + + c_totalPatchBlocks += ( grid.width - 1 ) * ( grid.height - 1 ); + + // generate a bsp tree for the surface + CM_PatchCollideFromGrid( &grid, pf ); + + // expand by one unit for epsilon purposes + pf->bounds[0][0] -= 1; + pf->bounds[0][1] -= 1; + pf->bounds[0][2] -= 1; + + pf->bounds[1][0] += 1; + pf->bounds[1][1] += 1; + pf->bounds[1][2] += 1; + + return pf; +} + +/* +================================================================================ + +TRACE TESTING + +================================================================================ +*/ + +/* +==================== +CM_TraceThroughPatchCollide + +Modifies tr->tr if any of the facets effect the trace +==================== +*/ + +/* +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) +{ + int i, j, n; + float d1, d2, offset, planedist, fraction; + vec3_t v1, v2, normal, point; + patchPlane_t *planes; + facet_t *facet; + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) + { + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, normal); + for (n = 0; n < 3; n++) + { + if (normal[n] > 0) v1[n] = tw->size[0][n]; + else v1[n] = tw->size[1][n]; + } //end for + VectorNegate(normal, v2); + offset = DotProduct(v1, v2); + //offset = 0; + // + planedist = planes->plane[3] + offset; + // + d1 = DotProduct( tw->start, normal ) - planedist; + d2 = DotProduct( tw->end, normal ) - planedist; + + // if completely in front of face, no intersection with the entire patch + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + continue; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + continue; + } + + // crosses face + if (d1 > d2) { // enter + fraction = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( fraction < 0 ) { + fraction = 0; + } + for (j = 0; j < 3; j++) + point[j] = tw->start[j] + (tw->end[j] - tw->start[j]) * fraction; + } + else { + continue; + } + // + for (j = 0; j < facet->numBorders; j++) + { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (!facet->borderInward[j]) + { + VectorNegate(planes->plane, normal); + planedist = -planes->plane[3]; + } //end if + else + { + VectorCopy(planes->plane, normal); + planedist = planes->plane[3]; + } //end else + for (n = 0; n < 3; n++) + { + if (normal[n] > 0) v1[n] = tw->size[0][n]; + else v1[n] = tw->size[1][n]; + } //end for + VectorNegate(normal, v2); + offset = DotProduct(v1, v2); + //offset = 0; + planedist -= offset; + //the hit point should be in front of the (inward facing) border plane + if (DotProduct(point, normal) - planedist < -ON_EPSILON) break; + } //end for + if (j < facet->numBorders) continue; + // + if (fraction < tw->trace.fraction) + { + debugPatchCollide = pc; + debugFacet = facet; + + tw->trace.fraction = fraction; + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy( planes->plane, tw->trace.plane.normal ); + tw->trace.plane.dist = planes->plane[3]; + } //end if + } //end for +} //end of the function CM_TraceThroughPatchCollide*/ + +/* +==================== +CM_TracePointThroughPatchCollide + + special case for point traces because the patch collide "brushes" have no volume +==================== +*/ +void CM_TracePointThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + qboolean frontFacing[MAX_PATCH_PLANES]; + float intersection[MAX_PATCH_PLANES]; + float intersect; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d1, d2; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef BSPC + if ( !cm_playerCurveClip->integer && !tw->isPoint ) { + return; // FIXME: until I get player sized clipping working right + } +#endif + + // determine the trace's relationship to all planes + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + if ( d1 <= 0 ) { + frontFacing[i] = qfalse; + } else { + frontFacing[i] = qtrue; + } + if ( d1 == d2 ) { + intersection[i] = 99999; + } else { + intersection[i] = d1 / ( d1 - d2 ); + if ( intersection[i] <= 0 ) { + intersection[i] = 99999; + } + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + if ( !frontFacing[facet->surfacePlane] ) { + continue; + } + intersect = intersection[facet->surfacePlane]; + if ( intersect < 0 ) { + continue; // surface is behind the starting point + } + if ( intersect > tw->trace.fraction ) { + continue; // already hit something closer + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->borderPlanes[j]; + if ( frontFacing[k] ^ facet->borderInward[j] ) { + if ( intersection[k] > intersect ) { + break; + } + } else { + if ( intersection[k] < intersect ) { + break; + } + } + } + if ( j == facet->numBorders ) { + // we hit this facet +#ifndef BSPC + if ( !cv ) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if ( cv->integer ) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + planes = &pc->planes[facet->surfacePlane]; + + // calculate intersection with a slight pushoff + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + tw->trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if ( tw->trace.fraction < 0 ) { + tw->trace.fraction = 0; + } + + VectorCopy( planes->plane, tw->trace.plane.normal ); + tw->trace.plane.dist = planes->plane[3]; + } + } +} + +/* +==================== +CM_CheckFacetPlane +==================== +*/ +int CM_CheckFacetPlane( float *plane, vec3_t start, vec3_t end, float *enterFrac, float *leaveFrac, int *hit ) { + float d1, d2, f; + + *hit = qfalse; + + d1 = DotProduct( start, plane ) - plane[3]; + d2 = DotProduct( end, plane ) - plane[3]; + + // if completely in front of face, no intersection with the entire facet + if ( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { +// if (d1 > 0 && d2 > 0) { + return qfalse; + } + + // if it doesn't cross the plane, the plane isn't relevent + if ( d1 <= 0 && d2 <= 0 ) { + return qtrue; + } + + // crosses face + if ( d1 > d2 ) { // enter + f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + if ( f < 0 ) { + f = 0; + } + //always favor previous plane hits and thus also the surface plane hit + if ( f > *enterFrac ) { + *enterFrac = f; + *hit = qtrue; + } + } else { // leave + f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + if ( f > 1 ) { + f = 1; + } + if ( f < *leaveFrac ) { + *leaveFrac = f; + } + } + return qtrue; +} + +/* +==================== +CM_TraceThroughPatchCollide +==================== +*/ +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int i, j, hit, hitnum; + float offset, enterFrac, leaveFrac, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4], bestplane[4]; + vec3_t startp, endp; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + + if ( tw->isPoint ) { + CM_TracePointThroughPatchCollide( tw, pc ); + return; + } +#ifndef ADDBEVELS + CM_TracePointThroughPatchCollide( tw, pc ); + return; +#endif + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + enterFrac = -1.0; + leaveFrac = 1.0; + hitnum = -1; + // + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy( planes->plane, plane ); + plane[3] = planes->plane[3]; + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0 ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane ); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + // + if ( !CM_CheckFacetPlane( plane, startp, endp, &enterFrac, &leaveFrac, &hit ) ) { + continue; + } + if ( hit ) { + Vector4Copy( plane, bestplane ); + } + // + for ( j = 0 ; j < facet->numBorders ; j++ ) { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if ( facet->borderInward[j] ) { + VectorNegate( planes->plane, plane ); + plane[3] = -planes->plane[3]; + } else { + VectorCopy( planes->plane, plane ); + plane[3] = planes->plane[3]; + } + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0 ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane ); + plane[3] += fabs( offset ); + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + // + if ( !CM_CheckFacetPlane( plane, startp, endp, &enterFrac, &leaveFrac, &hit ) ) { + break; + } + if ( hit ) { + hitnum = j; + Vector4Copy( plane, bestplane ); + } + } + if ( j < facet->numBorders ) { + continue; + } + //never clip against the back side + if ( hitnum == facet->numBorders - 1 ) { + continue; + } + // + if ( enterFrac < leaveFrac && enterFrac >= 0 ) { + if ( enterFrac < tw->trace.fraction ) { + if ( enterFrac < 0 ) { + enterFrac = 0; + } +#ifndef BSPC + if ( !cv ) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if ( cv && cv->integer ) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif BSPC + + tw->trace.fraction = enterFrac; + VectorCopy( bestplane, tw->trace.plane.normal ); + tw->trace.plane.dist = bestplane[3]; + } + } + } +} + + +/* +======================================================================= + +POSITION DETECTION + +======================================================================= +*/ + +#define BOX_FRONT 0 +#define BOX_BACK 1 +#define BOX_CROSS 2 + +/* +==================== +CM_PositionTestInPatchCollide + +Modifies tr->tr if any of the facets effect the trace +==================== +*/ +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int cross[MAX_PATCH_PLANES]; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d; + +//return qfalse; + +#ifndef CULL_BBOX + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw->bounds[0][i] > pc->bounds[1][i] + || tw->bounds[1][i] < pc->bounds[0][i] ) { + return qfalse; + } + } +#endif + + // determine if the box is in front, behind, or crossing each plane + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + d = DotProduct( tw->start, planes->plane ) - planes->plane[3]; + offset = fabs( DotProduct( tw->offsets[ planes->signbits ], planes->plane ) ); + if ( d < -offset ) { + cross[i] = BOX_FRONT; + } else if ( d > offset ) { + cross[i] = BOX_BACK; + } else { + cross[i] = BOX_CROSS; + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + // the facet plane must be in a cross state + if ( cross[facet->surfacePlane] != BOX_CROSS ) { + continue; + } + // all of the boundaries must be either cross or back + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->borderPlanes[j]; + if ( cross[ k ] == BOX_CROSS ) { + continue; + } + if ( cross[k] ^ facet->borderInward[j] ) { + break; + } + } + // if we passed all borders, we are definately in this facet + if ( j == facet->numBorders ) { + return qtrue; + } + } + + return qfalse; +} + + +/* +======================================================================= + +DEBUGGING + +======================================================================= +*/ + + +/* +================== +CM_DrawDebugSurface + +Called from the renderer +================== +*/ +#ifndef BSPC +void BotDrawDebugPolygons( void ( *drawPoly )( int color, int numPoints, float *points ), int value ); +#endif + +void CM_DrawDebugSurface( void ( *drawPoly )( int color, int numPoints, float *points ) ) { + static cvar_t *cv; +#ifndef BSPC + static cvar_t *cv2; +#endif + const patchCollide_t *pc; + facet_t *facet; + winding_t *w; + int i, j, k, n; + int curplanenum, planenum, curinward, inward; + float plane[4]; + vec3_t mins = {-15, -15, -28}, maxs = {15, 15, 28}; + //vec3_t mins = {0, 0, 0}, maxs = {0, 0, 0}; + vec3_t v1, v2; + +#ifndef BSPC + if ( !cv2 ) { + cv2 = Cvar_Get( "r_debugSurface", "0", 0 ); + } + + if ( cv2->integer != 1 ) { + BotDrawDebugPolygons( drawPoly, cv2->integer ); + return; + } +#endif + + if ( !debugPatchCollide ) { + return; + } + +#ifndef BSPC + if ( !cv ) { + cv = Cvar_Get( "cm_debugSize", "2", 0 ); + } +#endif + pc = debugPatchCollide; + + for ( i = 0, facet = pc->facets ; i < pc->numFacets ; i++, facet++ ) { + + for ( k = 0 ; k < facet->numBorders + 1; k++ ) { + // + if ( k < facet->numBorders ) { + planenum = facet->borderPlanes[k]; + inward = facet->borderInward[k]; + } else { + planenum = facet->surfacePlane; + inward = qfalse; + //continue; + } + + Vector4Copy( pc->planes[ planenum ].plane, plane ); + + //planenum = facet->surfacePlane; + if ( inward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + plane[3] += cv->value; + //* + for ( n = 0; n < 3; n++ ) + { + if ( plane[n] > 0 ) { + v1[n] = maxs[n]; + } else { v1[n] = mins[n];} + } //end for + VectorNegate( plane, v2 ); + plane[3] += fabs( DotProduct( v1, v2 ) ); + //*/ + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders + 1 && w; j++ ) { + // + if ( j < facet->numBorders ) { + curplanenum = facet->borderPlanes[j]; + curinward = facet->borderInward[j]; + } else { + curplanenum = facet->surfacePlane; + curinward = qfalse; + //continue; + } + // + if ( curplanenum == planenum ) { + continue; + } + + Vector4Copy( pc->planes[ curplanenum ].plane, plane ); + if ( !curinward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + // if ( !facet->borderNoAdjust[j] ) { + plane[3] -= cv->value; + // } + for ( n = 0; n < 3; n++ ) + { + if ( plane[n] > 0 ) { + v1[n] = maxs[n]; + } else { v1[n] = mins[n];} + } //end for + VectorNegate( plane, v2 ); + plane[3] -= fabs( DotProduct( v1, v2 ) ); + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( w ) { + if ( facet == debugFacet ) { + drawPoly( 4, w->numpoints, w->p[0] ); + //Com_Printf("blue facet has %d border planes\n", facet->numBorders); + } else { + drawPoly( 1, w->numpoints, w->p[0] ); + } + FreeWinding( w ); + } else { + Com_Printf( "winding chopped away by border planes\n" ); + } + } + } + + // draw the debug block + { + vec3_t v[3]; + + VectorCopy( debugBlockPoints[0], v[0] ); + VectorCopy( debugBlockPoints[1], v[1] ); + VectorCopy( debugBlockPoints[2], v[2] ); + drawPoly( 2, 3, v[0] ); + + VectorCopy( debugBlockPoints[2], v[0] ); + VectorCopy( debugBlockPoints[3], v[1] ); + VectorCopy( debugBlockPoints[0], v[2] ); + drawPoly( 2, 3, v[0] ); + } + +#if 0 + vec3_t v[4]; + + v[0][0] = pc->bounds[1][0]; + v[0][1] = pc->bounds[1][1]; + v[0][2] = pc->bounds[1][2]; + + v[1][0] = pc->bounds[1][0]; + v[1][1] = pc->bounds[0][1]; + v[1][2] = pc->bounds[1][2]; + + v[2][0] = pc->bounds[0][0]; + v[2][1] = pc->bounds[0][1]; + v[2][2] = pc->bounds[1][2]; + + v[3][0] = pc->bounds[0][0]; + v[3][1] = pc->bounds[1][1]; + v[3][2] = pc->bounds[1][2]; + + drawPoly( 4, v[0] ); +#endif +} diff --git a/src/qcommon/cm_patch.h b/src/qcommon/cm_patch.h new file mode 100644 index 0000000..36be2a7 --- /dev/null +++ b/src/qcommon/cm_patch.h @@ -0,0 +1,110 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +//#define CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + + +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4 + 6 + 16]; + int borderInward[4 + 6 + 16]; + qboolean borderNoAdjust[4 + 6 + 16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 + + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); diff --git a/src/qcommon/cm_polylib.c b/src/qcommon/cm_polylib.c new file mode 100644 index 0000000..d6ac99e --- /dev/null +++ b/src/qcommon/cm_polylib.c @@ -0,0 +1,740 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// this is only used for visualization tools in cm_ debug functions + + +#include "cm_local.h" + + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +#define BOGUS_RANGE 8192 + +void pw( winding_t *w ) { + int i; + for ( i = 0 ; i < w->numpoints ; i++ ) + printf( "(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2] ); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding( int points ) { + winding_t *w; + int s; + + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if ( c_active_windings > c_peak_windings ) { + c_peak_windings = c_active_windings; + } + + s = sizeof( vec_t ) * 3 * points + sizeof( int ); + w = Z_Malloc( s ); + memset( w, 0, s ); + return w; +} + +void FreeWinding( winding_t *w ) { + if ( *(unsigned *)w == 0xdeaddead ) { + Com_Error( ERR_FATAL, "FreeWinding: freed a freed winding" ); + } + *(unsigned *)w = 0xdeaddead; + + c_active_windings--; + Z_Free( w ); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints( winding_t *w ) { + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + j = ( i + 1 ) % w->numpoints; + k = ( i + w->numpoints - 1 ) % w->numpoints; + VectorSubtract( w->p[j], w->p[i], v1 ); + VectorSubtract( w->p[i], w->p[k], v2 ); + VectorNormalize2( v1,v1 ); + VectorNormalize2( v2,v2 ); + if ( DotProduct( v1, v2 ) < 0.999 ) { + VectorCopy( w->p[i], p[nump] ); + nump++; + } + } + + if ( nump == w->numpoints ) { + return; + } + + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy( w->p, p, nump * sizeof( p[0] ) ); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ) { + vec3_t v1, v2; + + VectorSubtract( w->p[1], w->p[0], v1 ); + VectorSubtract( w->p[2], w->p[0], v2 ); + CrossProduct( v2, v1, normal ); + VectorNormalize2( normal, normal ); + *dist = DotProduct( w->p[0], normal ); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea( winding_t *w ) { + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for ( i = 2 ; i < w->numpoints ; i++ ) + { + VectorSubtract( w->p[i - 1], w->p[0], d1 ); + VectorSubtract( w->p[i], w->p[0], d2 ); + CrossProduct( d1, d2, cross ); + total += 0.5 * VectorLength( cross ); + } + return total; +} + +void WindingBounds( winding_t *w, vec3_t mins, vec3_t maxs ) { + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + v = w->p[i][j]; + if ( v < mins[j] ) { + mins[j] = v; + } + if ( v > maxs[j] ) { + maxs[j] = v; + } + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter( winding_t *w, vec3_t center ) { + int i; + float scale; + + VectorCopy( vec3_origin, center ); + for ( i = 0 ; i < w->numpoints ; i++ ) + VectorAdd( w->p[i], center, center ); + + scale = 1.0 / w->numpoints; + VectorScale( center, scale, center ); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane( vec3_t normal, vec_t dist ) { + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for ( i = 0 ; i < 3; i++ ) + { + v = fabs( normal[i] ); + if ( v > max ) { + x = i; + max = v; + } + } + if ( x == -1 ) { + Com_Error( ERR_DROP, "BaseWindingForPlane: no axis found" ); + } + + VectorCopy( vec3_origin, vup ); + switch ( x ) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct( vup, normal ); + VectorMA( vup, -v, normal, vup ); + VectorNormalize2( vup, vup ); + + VectorScale( normal, dist, org ); + + CrossProduct( vup, normal, vright ); + + VectorScale( vup, 8192, vup ); + VectorScale( vright, 8192, vright ); + +// project a really big axis aligned box onto the plane + w = AllocWinding( 4 ); + + VectorSubtract( org, vright, w->p[0] ); + VectorAdd( w->p[0], vup, w->p[0] ); + + VectorAdd( org, vright, w->p[1] ); + VectorAdd( w->p[1], vup, w->p[1] ); + + VectorAdd( org, vright, w->p[2] ); + VectorSubtract( w->p[2], vup, w->p[2] ); + + VectorSubtract( org, vright, w->p[3] ); + VectorSubtract( w->p[3], vup, w->p[3] ); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding( winding_t *w ) { + int size; + winding_t *c; + + c = AllocWinding( w->numpoints ); + size = (int)( (winding_t *)0 )->p[w->numpoints]; + memcpy( c, w, size ); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding( winding_t *w ) { + int i; + winding_t *c; + + c = AllocWinding( w->numpoints ); + for ( i = 0 ; i < w->numpoints ; i++ ) + { + VectorCopy( w->p[w->numpoints - 1 - i], c->p[i] ); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon( winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back ) { + vec_t dists[MAX_POINTS_ON_WINDING + 4]; + int sides[MAX_POINTS_ON_WINDING + 4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for ( i = 0 ; i < in->numpoints ; i++ ) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if ( !counts[0] ) { + *back = CopyWinding( in ); + return; + } + if ( !counts[1] ) { + *front = CopyWinding( in ); + return; + } + + maxpts = in->numpoints + 4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding( maxpts ); + *back = b = AllocWinding( maxpts ); + + for ( i = 0 ; i < in->numpoints ; i++ ) + { + p1 = in->p[i]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + VectorCopy( p1, b->p[b->numpoints] ); + b->numpoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + if ( sides[i] == SIDE_BACK ) { + VectorCopy( p1, b->p[b->numpoints] ); + b->numpoints++; + } + + if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = in->p[( i + 1 ) % in->numpoints]; + + dot = dists[i] / ( dists[i] - dists[i + 1] ); + for ( j = 0 ; j < 3 ; j++ ) + { // avoid round off error when possible + if ( normal[j] == 1 ) { + mid[j] = dist; + } else if ( normal[j] == -1 ) { + mid[j] = -dist; + } else { + mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + } + + VectorCopy( mid, f->p[f->numpoints] ); + f->numpoints++; + VectorCopy( mid, b->p[b->numpoints] ); + b->numpoints++; + } + + if ( f->numpoints > maxpts || b->numpoints > maxpts ) { + Com_Error( ERR_DROP, "ClipWinding: points exceeded estimate" ); + } + if ( f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING ) { + Com_Error( ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING" ); + } +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace( winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon ) { + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING + 4]; + int sides[MAX_POINTS_ON_WINDING + 4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for ( i = 0 ; i < in->numpoints ; i++ ) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if ( !counts[0] ) { + FreeWinding( in ); + *inout = NULL; + return; + } + if ( !counts[1] ) { + return; // inout stays the same + + } + maxpts = in->numpoints + 4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding( maxpts ); + + for ( i = 0 ; i < in->numpoints ; i++ ) + { + p1 = in->p[i]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + + if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = in->p[( i + 1 ) % in->numpoints]; + + dot = dists[i] / ( dists[i] - dists[i + 1] ); + for ( j = 0 ; j < 3 ; j++ ) + { // avoid round off error when possible + if ( normal[j] == 1 ) { + mid[j] = dist; + } else if ( normal[j] == -1 ) { + mid[j] = -dist; + } else { + mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + } + + VectorCopy( mid, f->p[f->numpoints] ); + f->numpoints++; + } + + if ( f->numpoints > maxpts ) { + Com_Error( ERR_DROP, "ClipWinding: points exceeded estimate" ); + } + if ( f->numpoints > MAX_POINTS_ON_WINDING ) { + Com_Error( ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING" ); + } + + FreeWinding( in ); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding( winding_t *in, vec3_t normal, vec_t dist ) { + winding_t *f, *b; + + ClipWindingEpsilon( in, normal, dist, ON_EPSILON, &f, &b ); + FreeWinding( in ); + if ( b ) { + FreeWinding( b ); + } + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding( winding_t *w ) { + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if ( w->numpoints < 3 ) { + Com_Error( ERR_DROP, "CheckWinding: %i points",w->numpoints ); + } + + area = WindingArea( w ); + if ( area < 1 ) { + Com_Error( ERR_DROP, "CheckWinding: %f area", area ); + } + + WindingPlane( w, facenormal, &facedist ); + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + p1 = w->p[i]; + + for ( j = 0 ; j < 3 ; j++ ) + if ( p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE ) { + Com_Error( ERR_DROP, "CheckFace: BUGUS_RANGE: %f",p1[j] ); + } + + j = i + 1 == w->numpoints ? 0 : i + 1; + + // check the point is on the face plane + d = DotProduct( p1, facenormal ) - facedist; + if ( d < -ON_EPSILON || d > ON_EPSILON ) { + Com_Error( ERR_DROP, "CheckWinding: point off plane" ); + } + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract( p2, p1, dir ); + + if ( VectorLength( dir ) < ON_EPSILON ) { + Com_Error( ERR_DROP, "CheckWinding: degenerate edge" ); + } + + CrossProduct( facenormal, dir, edgenormal ); + VectorNormalize2( edgenormal, edgenormal ); + edgedist = DotProduct( p1, edgenormal ); + edgedist += ON_EPSILON; + + // all other points must be on front side + for ( j = 0 ; j < w->numpoints ; j++ ) + { + if ( j == i ) { + continue; + } + d = DotProduct( w->p[j], edgenormal ); + if ( d > edgedist ) { + Com_Error( ERR_DROP, "CheckWinding: non-convex" ); + } + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide( winding_t *w, vec3_t normal, vec_t dist ) { + qboolean front, back; + int i; + vec_t d; + + front = qfalse; + back = qfalse; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + d = DotProduct( w->p[i], normal ) - dist; + if ( d < -ON_EPSILON ) { + if ( front ) { + return SIDE_CROSS; + } + back = qtrue; + continue; + } + if ( d > ON_EPSILON ) { + if ( back ) { + return SIDE_CROSS; + } + front = qtrue; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + + +/* +================= +AddWindingToConvexHull + +Both w and *hull are on the same plane +================= +*/ +#define MAX_HULL_POINTS 128 +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) { + int i, j, k; + float *p, *copy; + vec3_t dir; + float d; + int numHullPoints, numNew; + vec3_t hullPoints[MAX_HULL_POINTS]; + vec3_t newHullPoints[MAX_HULL_POINTS]; + vec3_t hullDirs[MAX_HULL_POINTS]; + qboolean hullSide[MAX_HULL_POINTS]; + qboolean outside; + + if ( !*hull ) { + *hull = CopyWinding( w ); + return; + } + + numHullPoints = ( *hull )->numpoints; + memcpy( hullPoints, ( *hull )->p, numHullPoints * sizeof( vec3_t ) ); + + for ( i = 0 ; i < w->numpoints ; i++ ) { + p = w->p[i]; + + // calculate hull side vectors + for ( j = 0 ; j < numHullPoints ; j++ ) { + k = ( j + 1 ) % numHullPoints; + + VectorSubtract( hullPoints[k], hullPoints[j], dir ); + VectorNormalize2( dir, dir ); + CrossProduct( normal, dir, hullDirs[j] ); + } + + outside = qfalse; + for ( j = 0 ; j < numHullPoints ; j++ ) { + VectorSubtract( p, hullPoints[j], dir ); + d = DotProduct( dir, hullDirs[j] ); + if ( d >= ON_EPSILON ) { + outside = qtrue; + } + if ( d >= -ON_EPSILON ) { + hullSide[j] = qtrue; + } else { + hullSide[j] = qfalse; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0 ; j < numHullPoints ; j++ ) { + if ( !hullSide[ j % numHullPoints ] && hullSide[ ( j + 1 ) % numHullPoints ] ) { + break; + } + } + if ( j == numHullPoints ) { + continue; + } + + // insert the point here + VectorCopy( p, newHullPoints[0] ); + numNew = 1; + + // copy over all points that aren't double fronts + j = ( j + 1 ) % numHullPoints; + for ( k = 0 ; k < numHullPoints ; k++ ) { + if ( hullSide[ ( j + k ) % numHullPoints ] && hullSide[ ( j + k + 1 ) % numHullPoints ] ) { + continue; + } + copy = hullPoints[ ( j + k + 1 ) % numHullPoints ]; + VectorCopy( copy, newHullPoints[numNew] ); + numNew++; + } + + numHullPoints = numNew; + memcpy( hullPoints, newHullPoints, numHullPoints * sizeof( vec3_t ) ); + } + + FreeWinding( *hull ); + w = AllocWinding( numHullPoints ); + w->numpoints = numHullPoints; + *hull = w; + memcpy( w->p, hullPoints, numHullPoints * sizeof( vec3_t ) ); +} + + diff --git a/src/qcommon/cm_polylib.h b/src/qcommon/cm_polylib.h new file mode 100644 index 0000000..be6e5b4 --- /dev/null +++ b/src/qcommon/cm_polylib.h @@ -0,0 +1,73 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +// this is only used for visualization tools in cm_ debug functions + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +#define CLIP_EPSILON 0.1f + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1f +#endif + +winding_t *AllocWinding( int points ); +vec_t WindingArea( winding_t *w ); +void WindingCenter( winding_t *w, vec3_t center ); +void ClipWindingEpsilon( winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back ); +winding_t *ChopWinding( winding_t *in, vec3_t normal, vec_t dist ); +winding_t *CopyWinding( winding_t *w ); +winding_t *ReverseWinding( winding_t *w ); +winding_t *BaseWindingForPlane( vec3_t normal, vec_t dist ); +void CheckWinding( winding_t *w ); +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ); +void RemoveColinearPoints( winding_t *w ); +int WindingOnPlaneSide( winding_t *w, vec3_t normal, vec_t dist ); +void FreeWinding( winding_t *w ); +void WindingBounds( winding_t *w, vec3_t mins, vec3_t maxs ); + +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ); + +void ChopWindingInPlace( winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon ); +// frees the original if clipped + +void pw( winding_t *w ); diff --git a/src/qcommon/cm_public.h b/src/qcommon/cm_public.h new file mode 100644 index 0000000..0d0dfcb --- /dev/null +++ b/src/qcommon/cm_public.h @@ -0,0 +1,85 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "qfiles.h" + +#include "../cgame/tr_types.h" + +void CM_LoadMap( const char *name, qboolean clientload, int *checksum ); +void CM_ClearMap( void ); + +clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ); +void CM_SetTempBoxModelContents( int contents ); // DHM - Nerve + +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); + +int CM_NumClusters( void ); +int CM_NumInlineModels( void ); +char *CM_EntityString( void ); + +// returns an ORed contents mask +int CM_PointContents( const vec3_t p, clipHandle_t model ); +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); + +void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, int capsule ); +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, int capsule ); + +byte *CM_ClusterPVS( int cluster ); + +int CM_PointLeafnum( const vec3_t p ); + +// only returns non-solid leafs +// overflow if return listsize and if *lastLeaf != list[listsize-1] +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, + int listsize, int *lastLeaf ); + +int CM_LeafCluster( int leafnum ); +int CM_LeafArea( int leafnum ); + +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ); +qboolean CM_AreasConnected( int area1, int area2 ); + +int CM_WriteAreaBits( byte *buffer, int area ); + +// cm_tag.c +int CM_LerpTag( orientation_t *tag, const refEntity_t *refent, const char *tagName, int startIndex ); + + +// cm_marks.c +int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + +// cm_patch.c +void CM_DrawDebugSurface( void ( *drawPoly )( int color, int numPoints, float *points ) ); diff --git a/src/qcommon/cm_test.c b/src/qcommon/cm_test.c new file mode 100644 index 0000000..cf353db --- /dev/null +++ b/src/qcommon/cm_test.c @@ -0,0 +1,486 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "cm_local.h" + + +/* +================== +CM_PointLeafnum_r + +================== +*/ +int CM_PointLeafnum_r( const vec3_t p, int num ) { + float d; + cNode_t *node; + cplane_t *plane; + + while ( num >= 0 ) + { + node = cm.nodes + num; + plane = node->plane; + + if ( plane->type < 3 ) { + d = p[plane->type] - plane->dist; + } else { + d = DotProduct( plane->normal, p ) - plane->dist; + } + if ( d < 0 ) { + num = node->children[1]; + } else { + num = node->children[0]; + } + } + + c_pointcontents++; // optimize counter + + return -1 - num; +} + +int CM_PointLeafnum( const vec3_t p ) { + if ( !cm.numNodes ) { // map not loaded + return 0; + } + return CM_PointLeafnum_r( p, 0 ); +} + + +/* +====================================================================== + +LEAF LISTING + +====================================================================== +*/ + + +void CM_StoreLeafs( leafList_t *ll, int nodenum ) { + int leafNum; + + leafNum = -1 - nodenum; + + // store the lastLeaf even if the list is overflowed + if ( cm.leafs[ leafNum ].cluster != -1 ) { + ll->lastLeaf = leafNum; + } + + if ( ll->count >= ll->maxcount ) { + ll->overflowed = qtrue; + return; + } + ll->list[ ll->count++ ] = leafNum; +} + +void CM_StoreBrushes( leafList_t *ll, int nodenum ) { + int i, k; + int leafnum; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + + leafnum = -1 - nodenum; + + leaf = &cm.leafs[leafnum]; + + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush + k]; + b = &cm.brushes[brushnum]; + if ( b->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + for ( i = 0 ; i < 3 ; i++ ) { + if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) { + break; + } + } + if ( i != 3 ) { + continue; + } + if ( ll->count >= ll->maxcount ) { + ll->overflowed = qtrue; + return; + } + ( (cbrush_t **)ll->list )[ ll->count++ ] = b; + } +#if 0 + // store patches? + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstleafsurface + k ] ]; + if ( !patch ) { + continue; + } + } +#endif +} + +/* +============= +CM_BoxLeafnums + +Fills in a list of all the leafs touched +============= +*/ +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) { + cplane_t *plane; + cNode_t *node; + int s; + + while ( 1 ) { + if ( nodenum < 0 ) { + ll->storeLeafs( ll, nodenum ); + return; + } + + node = &cm.nodes[nodenum]; + plane = node->plane; + s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane ); + if ( s == 1 ) { + nodenum = node->children[0]; + } else if ( s == 2 ) { + nodenum = node->children[1]; + } else { + // go down both + CM_BoxLeafnums_r( ll, node->children[0] ); + nodenum = node->children[1]; + } + + } +} + +/* +================== +CM_BoxLeafnums +================== +*/ +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *lastLeaf ) { + leafList_t ll; + + cm.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = list; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + CM_BoxLeafnums_r( &ll, 0 ); + + *lastLeaf = ll.lastLeaf; + return ll.count; +} + +/* +================== +CM_BoxBrushes +================== +*/ +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **list, int listsize ) { + leafList_t ll; + + cm.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = (void *)list; + ll.storeLeafs = CM_StoreBrushes; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + CM_BoxLeafnums_r( &ll, 0 ); + + return ll.count; +} + + +//==================================================================== + + +/* +================== +CM_PointContents + +================== +*/ +int CM_PointContents( const vec3_t p, clipHandle_t model ) { + int leafnum; + int i, k; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + int contents; + float d; + cmodel_t *clipm; + + if ( !cm.numNodes ) { // map not loaded + return 0; + } + + if ( model ) { + clipm = CM_ClipHandleToModel( model ); + leaf = &clipm->leaf; + } else { + leafnum = CM_PointLeafnum_r( p, 0 ); + leaf = &cm.leafs[leafnum]; + } + + contents = 0; + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush + k]; + b = &cm.brushes[brushnum]; + + // see if the point is in the brush + for ( i = 0 ; i < b->numsides ; i++ ) { + d = DotProduct( p, b->sides[i].plane->normal ); +// FIXME test for Cash +// if ( d >= b->sides[i].plane->dist ) { + if ( d > b->sides[i].plane->dist ) { + break; + } + } + + if ( i == b->numsides ) { + contents |= b->contents; + } + } + + return contents; +} + +/* +================== +CM_TransformedPointContents + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + vec3_t p_l; + vec3_t temp; + vec3_t forward, right, up; + + // subtract origin offset + VectorSubtract( p, origin, p_l ); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + ( angles[0] || angles[1] || angles[2] ) ) { + AngleVectors( angles, forward, right, up ); + + VectorCopy( p_l, temp ); + p_l[0] = DotProduct( temp, forward ); + p_l[1] = -DotProduct( temp, right ); + p_l[2] = DotProduct( temp, up ); + } + + return CM_PointContents( p_l, model ); +} + + + +/* +=============================================================================== + +PVS + +=============================================================================== +*/ + +byte *CM_ClusterPVS( int cluster ) { + if ( cluster < 0 || cluster >= cm.numClusters || !cm.vised ) { + return cm.visibility; + } + + return cm.visibility + cluster * cm.clusterBytes; +} + + + +/* +=============================================================================== + +AREAPORTALS + +=============================================================================== +*/ + +void CM_FloodArea_r( int areaNum, int floodnum ) { + int i; + cArea_t *area; + int *con; + + area = &cm.areas[ areaNum ]; + + if ( area->floodvalid == cm.floodvalid ) { + if ( area->floodnum == floodnum ) { + return; + } + Com_Error( ERR_DROP, "FloodArea_r: reflooded" ); + } + + area->floodnum = floodnum; + area->floodvalid = cm.floodvalid; + con = cm.areaPortals + areaNum * cm.numAreas; + for ( i = 0 ; i < cm.numAreas ; i++ ) { + if ( con[i] > 0 ) { + CM_FloodArea_r( i, floodnum ); + } + } +} + +/* +==================== +CM_FloodAreaConnections + +==================== +*/ +void CM_FloodAreaConnections( void ) { + int i; + cArea_t *area; + int floodnum; + + // all current floods are now invalid + cm.floodvalid++; + floodnum = 0; + + area = cm.areas; // Ridah, optimization + for ( i = 0 ; i < cm.numAreas ; i++, area++ ) { + if ( area->floodvalid == cm.floodvalid ) { + continue; // already flooded into + } + floodnum++; + CM_FloodArea_r( i, floodnum ); + } + +} + +/* +==================== +CM_AdjustAreaPortalState + +==================== +*/ +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ) { + if ( area1 < 0 || area2 < 0 ) { + return; + } + + if ( area1 >= cm.numAreas || area2 >= cm.numAreas ) { + Com_Error( ERR_DROP, "CM_ChangeAreaPortalState: bad area number" ); + } + + if ( open ) { + cm.areaPortals[ area1 * cm.numAreas + area2 ]++; + cm.areaPortals[ area2 * cm.numAreas + area1 ]++; + } else if ( cm.areaPortals[ area2 * cm.numAreas + area1 ] ) { // Ridah, fixes loadgame issue + cm.areaPortals[ area1 * cm.numAreas + area2 ]--; + cm.areaPortals[ area2 * cm.numAreas + area1 ]--; + if ( cm.areaPortals[ area2 * cm.numAreas + area1 ] < 0 ) { + Com_Error( ERR_DROP, "CM_AdjustAreaPortalState: negative reference count" ); + } + } + + CM_FloodAreaConnections(); +} + +/* +==================== +CM_AreasConnected + +==================== +*/ +qboolean CM_AreasConnected( int area1, int area2 ) { +#ifndef BSPC + if ( cm_noAreas->integer ) { + return qtrue; + } +#endif + + if ( area1 < 0 || area2 < 0 ) { + return qfalse; + } + + if ( area1 >= cm.numAreas || area2 >= cm.numAreas ) { + Com_Error( ERR_DROP, "area >= cm.numAreas" ); + } + + if ( cm.areas[area1].floodnum == cm.areas[area2].floodnum ) { + return qtrue; + } + return qfalse; +} + + +/* +================= +CM_WriteAreaBits + +Writes a bit vector of all the areas +that are in the same flood as the area parameter +Returns the number of bytes needed to hold all the bits. + +The bits are OR'd in, so you can CM_WriteAreaBits from multiple +viewpoints and get the union of all visible areas. + +This is used to cull non-visible entities from snapshots +================= +*/ +int CM_WriteAreaBits( byte *buffer, int area ) { + int i; + int floodnum; + int bytes; + + bytes = ( cm.numAreas + 7 ) >> 3; + +#ifndef BSPC + if ( cm_noAreas->integer || area == -1 ) +#else + if ( area == -1 ) +#endif + { // for debugging, send everything + memset( buffer, 255, bytes ); + } else + { + floodnum = cm.areas[area].floodnum; + for ( i = 0 ; i < cm.numAreas ; i++ ) + { + if ( cm.areas[i].floodnum == floodnum || area == -1 ) { + buffer[i >> 3] |= 1 << ( i & 7 ); + } + } + } + + return bytes; +} + diff --git a/src/qcommon/cm_trace.c b/src/qcommon/cm_trace.c new file mode 100644 index 0000000..2f8e1c7 --- /dev/null +++ b/src/qcommon/cm_trace.c @@ -0,0 +1,1455 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "cm_local.h" + +// always use bbox vs. bbox collision and never capsule vs. bbox or vice versa +//#define ALWAYS_BBOX_VS_BBOX +// always use capsule vs. capsule collision and never capsule vs. bbox or vice versa +//#define ALWAYS_CAPSULE_VS_CAPSULE + +//#define CAPSULE_DEBUG + +/* +=============================================================================== + +BASIC MATH + +=============================================================================== +*/ + +/* +================ +RotatePoint +================ +*/ +void RotatePoint( vec3_t point, const vec3_t matrix[3] ) { + vec3_t tvec; + + VectorCopy( point, tvec ); + point[0] = DotProduct( matrix[0], tvec ); + point[1] = DotProduct( matrix[1], tvec ); + point[2] = DotProduct( matrix[2], tvec ); +} + +/* +================ +TransposeMatrix +================ +*/ +void TransposeMatrix( const vec3_t matrix[3], vec3_t transpose[3] ) { + int i, j; + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + transpose[i][j] = matrix[j][i]; + } + } +} + +/* +================ +CreateRotationMatrix +================ +*/ +void CreateRotationMatrix( const vec3_t angles, vec3_t matrix[3] ) { + AngleVectors( angles, matrix[0], matrix[1], matrix[2] ); + VectorInverse( matrix[1] ); +} + +/* +================ +CM_ProjectPointOntoVector +================ +*/ +void CM_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vDir, vec3_t vProj ) { + vec3_t pVec; + + VectorSubtract( point, vStart, pVec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vDir ), vDir, vProj ); +} + +/* +================ +CM_DistanceFromLineSquared +================ +*/ +float CM_DistanceFromLineSquared( vec3_t p, vec3_t lp1, vec3_t lp2, vec3_t dir ) { + vec3_t proj, t; + int j; + + CM_ProjectPointOntoVector( p, lp1, dir, proj ); + for ( j = 0; j < 3; j++ ) + if ( ( proj[j] > lp1[j] && proj[j] > lp2[j] ) || + ( proj[j] < lp1[j] && proj[j] < lp2[j] ) ) { + break; + } + if ( j < 3 ) { + if ( fabs( proj[j] - lp1[j] ) < fabs( proj[j] - lp2[j] ) ) { + VectorSubtract( p, lp1, t ); + } else { + VectorSubtract( p, lp2, t ); + } + return VectorLengthSquared( t ); + } + VectorSubtract( p, proj, t ); + return VectorLengthSquared( t ); +} + +/* +================ +CM_VectorDistanceSquared +================ +*/ +float CM_VectorDistanceSquared( vec3_t p1, vec3_t p2 ) { + vec3_t dir; + + VectorSubtract( p2, p1, dir ); + return VectorLengthSquared( dir ); +} + +/* +================ +SquareRootFloat +================ +*/ +float SquareRootFloat( float number ) { + long i; + float x, y; + const float f = 1.5F; + + x = number * 0.5F; + y = number; + i = *( long * ) &y; + i = 0x5f3759df - ( i >> 1 ); + y = *( float * ) &i; + y = y * ( f - ( x * y * y ) ); + y = y * ( f - ( x * y * y ) ); + return number * y; +} + + +/* +=============================================================================== + +POSITION TESTING + +=============================================================================== +*/ + +/* +================ +CM_TestBoxInBrush +================ +*/ +void CM_TestBoxInBrush( traceWork_t *tw, cbrush_t *brush ) { + int i; + cplane_t *plane; + float dist; + float d1; + cbrushside_t *side; + float t; + vec3_t startp; + + if ( !brush->numsides ) { + return; + } + + // special test for axial + // the first 6 brush planes are always axial + if ( tw->bounds[0][0] > brush->bounds[1][0] + || tw->bounds[0][1] > brush->bounds[1][1] + || tw->bounds[0][2] > brush->bounds[1][2] + || tw->bounds[1][0] < brush->bounds[0][0] + || tw->bounds[1][1] < brush->bounds[0][1] + || tw->bounds[1][2] < brush->bounds[0][2] + ) { + return; + } + + if ( tw->sphere.use ) { + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for radius + dist = plane->dist + tw->sphere.radius; + // find the closest point on the capsule to the plane + t = DotProduct( plane->normal, tw->sphere.offset ); + if ( t > 0 ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + d1 = DotProduct( startp, plane->normal ) - dist; + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + } else { + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + } + + // inside this brush + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = brush->contents; +} + + + +/* +================ +CM_TestInLeaf +================ +*/ +void CM_TestInLeaf( traceWork_t *tw, cLeaf_t *leaf ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // test box position against all brushes in the leaf + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush + k]; + b = &cm.brushes[brushnum]; + if ( b->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + + if ( !( b->contents & tw->contents ) ) { + continue; + } + + CM_TestBoxInBrush( tw, b ); + if ( tw->trace.allsolid ) { + return; + } + } + + // test against all patches +#ifdef BSPC + if ( 1 ) { +#else + if ( !cm_noCurves->integer ) { +#endif //BSPC + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if ( !patch ) { + continue; + } + if ( patch->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + patch->checkcount = cm.checkcount; + + if ( !( patch->contents & tw->contents ) ) { + continue; + } + + if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + return; + } + } + } +} + +/* +================== +CM_TestCapsuleInCapsule + +capsule inside capsule check +================== +*/ +void CM_TestCapsuleInCapsule( traceWork_t *tw, clipHandle_t model ) { + int i; + vec3_t mins, maxs; + vec3_t top, bottom; + vec3_t p1, p2, tmp; + vec3_t offset, symetricSize[2]; + float radius, halfwidth, halfheight, offs, r; + + CM_ModelBounds( model, mins, maxs ); + + VectorAdd( tw->start, tw->sphere.offset, top ); + VectorSubtract( tw->start, tw->sphere.offset, bottom ); + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + } + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + offs = halfheight - radius; + + r = Square( tw->sphere.radius + radius ); + // check if any of the spheres overlap + VectorCopy( offset, p1 ); + p1[2] += offs; + VectorSubtract( p1, top, tmp ); + if ( VectorLengthSquared( tmp ) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorSubtract( p1, bottom, tmp ); + if ( VectorLengthSquared( tmp ) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorCopy( offset, p2 ); + p2[2] -= offs; + VectorSubtract( p2, top, tmp ); + if ( VectorLengthSquared( tmp ) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + VectorSubtract( p2, bottom, tmp ); + if ( VectorLengthSquared( tmp ) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + // if between cylinder up and lower bounds + if ( ( top[2] >= p1[2] && top[2] <= p2[2] ) || + ( bottom[2] >= p1[2] && bottom[2] <= p2[2] ) ) { + // 2d coordinates + top[2] = p1[2] = 0; + // if the cylinders overlap + VectorSubtract( top, p1, tmp ); + if ( VectorLengthSquared( tmp ) < r ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + } +} + +/* +================== +CM_TestBoundingBoxInCapsule + +bounding box inside capsule check +================== +*/ +void CM_TestBoundingBoxInCapsule( traceWork_t *tw, clipHandle_t model ) { + vec3_t mins, maxs, offset, size[2]; + clipHandle_t h; + cmodel_t *cmod; + int i; + + // mins maxs of the capsule + CM_ModelBounds( model, mins, maxs ); + + // offset for capsule center + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + size[0][i] = mins[i] - offset[i]; + size[1][i] = maxs[i] - offset[i]; + tw->start[i] -= offset[i]; + tw->end[i] -= offset[i]; + } + + // replace the bounding box with the capsule + tw->sphere.use = qtrue; + tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2] : size[1][0]; + tw->sphere.halfheight = size[1][2]; + VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + + // replace the capsule with the bounding box + h = CM_TempBoxModel( tw->size[0], tw->size[1], qfalse ); + // calculate collision + cmod = CM_ClipHandleToModel( h ); + CM_TestInLeaf( tw, &cmod->leaf ); +} + +/* +================== +CM_PositionTest +================== +*/ +#define MAX_POSITION_LEAFS 1024 +void CM_PositionTest( traceWork_t *tw ) { + int leafs[MAX_POSITION_LEAFS]; + int i; + leafList_t ll; + + // identify the leafs we are touching + VectorAdd( tw->start, tw->size[0], ll.bounds[0] ); + VectorAdd( tw->start, tw->size[1], ll.bounds[1] ); + + for ( i = 0 ; i < 3 ; i++ ) { + ll.bounds[0][i] -= 1; + ll.bounds[1][i] += 1; + } + + ll.count = 0; + ll.maxcount = MAX_POSITION_LEAFS; + ll.list = leafs; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + cm.checkcount++; + + CM_BoxLeafnums_r( &ll, 0 ); + + + cm.checkcount++; + + // test the contents of the leafs + for ( i = 0 ; i < ll.count ; i++ ) { + CM_TestInLeaf( tw, &cm.leafs[leafs[i]] ); + if ( tw->trace.allsolid ) { + break; + } + } +} + +/* +=============================================================================== + +TRACING + +=============================================================================== +*/ + + +/* +================ +CM_TraceThroughPatch +================ +*/ + +void CM_TraceThroughPatch( traceWork_t *tw, cPatch_t *patch ) { + float oldFrac; + + c_patch_traces++; + + oldFrac = tw->trace.fraction; + + CM_TraceThroughPatchCollide( tw, patch->pc ); + + if ( tw->trace.fraction < oldFrac ) { + tw->trace.surfaceFlags = patch->surfaceFlags; + tw->trace.contents = patch->contents; + } +} + +/* +================ +CM_TraceThroughBrush +================ +*/ +void CM_TraceThroughBrush( traceWork_t *tw, cbrush_t *brush ) { + int i; + cplane_t *plane, *clipplane; + float dist; + float enterFrac, leaveFrac; + float d1, d2; + qboolean getout, startout; + float f; + cbrushside_t *side, *leadside; + float t; + vec3_t startp; + vec3_t endp; + + enterFrac = -1.0; + leaveFrac = 1.0; + clipplane = NULL; + + if ( !brush->numsides ) { + return; + } + + c_brush_traces++; + + getout = qfalse; + startout = qfalse; + + leadside = NULL; + + if ( tw->sphere.use ) { + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for ( i = 0; i < brush->numsides; i++ ) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for radius + dist = plane->dist + tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane->normal, tw->sphere.offset ); + if ( t > 0 ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + + d1 = DotProduct( startp, plane->normal ) - dist; + d2 = DotProduct( endp, plane->normal ) - dist; + + if ( d2 > 0 ) { + getout = qtrue; // endpoint is not in solid + } + if ( d1 > 0 ) { + startout = qtrue; + } + + // if completely in front of face, no intersection with the entire brush + if ( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return; + } + + // if it doesn't cross the plane, the plane isn't relevent + if ( d1 <= 0 && d2 <= 0 ) { + continue; + } + + // crosses face + if ( d1 > d2 ) { // enter + f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + if ( f < 0 ) { + f = 0; + } + if ( f > enterFrac ) { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } else { // leave + f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + if ( f > 1 ) { + f = 1; + } + if ( f < leaveFrac ) { + leaveFrac = f; + } + } + } + } else { + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for ( i = 0; i < brush->numsides; i++ ) { + side = brush->sides + i; + plane = side->plane; + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + d2 = DotProduct( tw->end, plane->normal ) - dist; + + if ( d2 > 0 ) { + getout = qtrue; // endpoint is not in solid + } + if ( d1 > 0 ) { + startout = qtrue; + } + + // if completely in front of face, no intersection with the entire brush + if ( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return; + } + + // if it doesn't cross the plane, the plane isn't relevent + if ( d1 <= 0 && d2 <= 0 ) { + continue; + } + + // crosses face + if ( d1 > d2 ) { // enter + f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + if ( f < 0 ) { + f = 0; + } + if ( f > enterFrac ) { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } else { // leave + f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + if ( f > 1 ) { + f = 1; + } + if ( f < leaveFrac ) { + leaveFrac = f; + } + } + } + } + + // + // all planes have been checked, and the trace was not + // completely outside the brush + // + if ( !startout ) { // original point was inside brush + tw->trace.startsolid = qtrue; + if ( !getout ) { + tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + return; + } + + if ( enterFrac < leaveFrac ) { + if ( enterFrac > -1 && enterFrac < tw->trace.fraction ) { + if ( enterFrac < 0 ) { + enterFrac = 0; + } + tw->trace.fraction = enterFrac; + tw->trace.plane = *clipplane; + tw->trace.surfaceFlags = leadside->surfaceFlags; + tw->trace.contents = brush->contents; + } + } +} + +/* +================ +CM_TraceThroughLeaf +================ +*/ +void CM_TraceThroughLeaf( traceWork_t *tw, cLeaf_t *leaf ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // trace line against all brushes in the leaf + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cm.leafbrushes[leaf->firstLeafBrush + k]; + + b = &cm.brushes[brushnum]; + if ( b->checkcount == cm.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cm.checkcount; + + if ( !( b->contents & tw->contents ) ) { + continue; + } + + CM_TraceThroughBrush( tw, b ); + if ( !tw->trace.fraction ) { + return; + } + } + + // trace line against all patches in the leaf +#ifdef BSPC + if ( 1 ) { +#else + if ( !cm_noCurves->integer ) { +#endif + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if ( !patch ) { + continue; + } + if ( patch->checkcount == cm.checkcount ) { + continue; // already checked this patch in another leaf + } + patch->checkcount = cm.checkcount; + + if ( !( patch->contents & tw->contents ) ) { + continue; + } + + CM_TraceThroughPatch( tw, patch ); + if ( !tw->trace.fraction ) { + return; + } + } + } +} + +#define RADIUS_EPSILON 1.0f + +/* +================ +CM_TraceThroughSphere + +get the first intersection of the ray with the sphere +================ +*/ +void CM_TraceThroughSphere( traceWork_t *tw, vec3_t origin, float radius, vec3_t start, vec3_t end ) { + float l1, l2, length, scale, fraction; + float a, b, c, d, sqrtd; + vec3_t v1, dir, intersection; + + // if inside the sphere + VectorSubtract( start, origin, dir ); + l1 = VectorLengthSquared( dir ); + if ( l1 < Square( radius ) ) { + tw->trace.fraction = 0; + tw->trace.startsolid = qtrue; + // test for allsolid + VectorSubtract( end, origin, dir ); + l1 = VectorLengthSquared( dir ); + if ( l1 < Square( radius ) ) { + tw->trace.allsolid = qtrue; + } + return; + } + // + VectorSubtract( end, start, dir ); + length = VectorNormalize( dir ); + // + l1 = CM_DistanceFromLineSquared( origin, start, end, dir ); + VectorSubtract( end, origin, v1 ); + l2 = VectorLengthSquared( v1 ); + // if no intersection with the sphere and the end point is at least an epsilon away + if ( l1 >= Square( radius ) && l2 > Square( radius + SURFACE_CLIP_EPSILON ) ) { + return; + } + // + // | origin - (start + t * dir) | = radius + // a = dir[0]^2 + dir[1]^2 + dir[2]^2; + // b = 2 * (dir[0] * (start[0] - origin[0]) + dir[1] * (start[1] - origin[1]) + dir[2] * (start[2] - origin[2])); + // c = (start[0] - origin[0])^2 + (start[1] - origin[1])^2 + (start[2] - origin[2])^2 - radius^2; + // + VectorSubtract( start, origin, v1 ); + // dir is normalized so a = 1 + a = 1.0f; //dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]; + b = 2.0f * ( dir[0] * v1[0] + dir[1] * v1[1] + dir[2] * v1[2] ); + c = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] - ( radius + RADIUS_EPSILON ) * ( radius + RADIUS_EPSILON ); + + d = b * b - 4.0f * c; // * a; + if ( d > 0 ) { + sqrtd = SquareRootFloat( d ); + // = (- b + sqrtd) * 0.5f; // / (2.0f * a); + fraction = ( -b - sqrtd ) * 0.5f; // / (2.0f * a); + // + if ( fraction < 0 ) { + fraction = 0; + } else { + fraction /= length; + } + if ( fraction < tw->trace.fraction ) { + tw->trace.fraction = fraction; + VectorSubtract( end, start, dir ); + VectorMA( start, fraction, dir, intersection ); + VectorSubtract( intersection, origin, dir ); + #ifdef CAPSULE_DEBUG + l2 = VectorLength( dir ); + if ( l2 < radius ) { + int bah = 1; + } + #endif + scale = 1 / ( radius + RADIUS_EPSILON ); + VectorScale( dir, scale, dir ); + VectorCopy( dir, tw->trace.plane.normal ); + VectorAdd( tw->modelOrigin, intersection, intersection ); + tw->trace.plane.dist = DotProduct( tw->trace.plane.normal, intersection ); + tw->trace.contents = CONTENTS_BODY; + } + } else if ( d == 0 ) { + //t1 = (- b ) / 2; + // slide along the sphere + } + // no intersection at all +} + +/* +================ +CM_TraceThroughVerticalCylinder + +get the first intersection of the ray with the cylinder +the cylinder extends halfheight above and below the origin +================ +*/ +void CM_TraceThroughVerticalCylinder( traceWork_t *tw, vec3_t origin, float radius, float halfheight, vec3_t start, vec3_t end ) { + float length, scale, fraction, l1, l2; + float a, b, c, d, sqrtd; + vec3_t v1, dir, start2d, end2d, org2d, intersection; + + // 2d coordinates + VectorSet( start2d, start[0], start[1], 0 ); + VectorSet( end2d, end[0], end[1], 0 ); + VectorSet( org2d, origin[0], origin[1], 0 ); + // if between lower and upper cylinder bounds + if ( start[2] <= origin[2] + halfheight && + start[2] >= origin[2] - halfheight ) { + // if inside the cylinder + VectorSubtract( start2d, org2d, dir ); + l1 = VectorLengthSquared( dir ); + if ( l1 < Square( radius ) ) { + tw->trace.fraction = 0; + tw->trace.startsolid = qtrue; + VectorSubtract( end2d, org2d, dir ); + l1 = VectorLengthSquared( dir ); + if ( l1 < Square( radius ) ) { + tw->trace.allsolid = qtrue; + } + return; + } + } + // + VectorSubtract( end2d, start2d, dir ); + length = VectorNormalize( dir ); + // + l1 = CM_DistanceFromLineSquared( org2d, start2d, end2d, dir ); + VectorSubtract( end2d, org2d, v1 ); + l2 = VectorLengthSquared( v1 ); + // if no intersection with the cylinder and the end point is at least an epsilon away + if ( l1 >= Square( radius ) && l2 > Square( radius + SURFACE_CLIP_EPSILON ) ) { + return; + } + // + // + // (start[0] - origin[0] - t * dir[0]) ^ 2 + (start[1] - origin[1] - t * dir[1]) ^ 2 = radius ^ 2 + // (v1[0] + t * dir[0]) ^ 2 + (v1[1] + t * dir[1]) ^ 2 = radius ^ 2; + // v1[0] ^ 2 + 2 * v1[0] * t * dir[0] + (t * dir[0]) ^ 2 + + // v1[1] ^ 2 + 2 * v1[1] * t * dir[1] + (t * dir[1]) ^ 2 = radius ^ 2 + // t ^ 2 * (dir[0] ^ 2 + dir[1] ^ 2) + t * (2 * v1[0] * dir[0] + 2 * v1[1] * dir[1]) + + // v1[0] ^ 2 + v1[1] ^ 2 - radius ^ 2 = 0 + // + VectorSubtract( start, origin, v1 ); + // dir is normalized so we can use a = 1 + a = 1.0f; // * (dir[0] * dir[0] + dir[1] * dir[1]); + b = 2.0f * ( v1[0] * dir[0] + v1[1] * dir[1] ); + c = v1[0] * v1[0] + v1[1] * v1[1] - ( radius + RADIUS_EPSILON ) * ( radius + RADIUS_EPSILON ); + + d = b * b - 4.0f * c; // * a; + if ( d > 0 ) { + sqrtd = SquareRootFloat( d ); + // = (- b + sqrtd) * 0.5f;// / (2.0f * a); + fraction = ( -b - sqrtd ) * 0.5f; // / (2.0f * a); + // + if ( fraction < 0 ) { + fraction = 0; + } else { + fraction /= length; + } + if ( fraction < tw->trace.fraction ) { + VectorSubtract( end, start, dir ); + VectorMA( start, fraction, dir, intersection ); + // if the intersection is between the cylinder lower and upper bound + if ( intersection[2] <= origin[2] + halfheight && + intersection[2] >= origin[2] - halfheight ) { + // + tw->trace.fraction = fraction; + VectorSubtract( intersection, origin, dir ); + dir[2] = 0; + #ifdef CAPSULE_DEBUG + l2 = VectorLength( dir ); + if ( l2 <= radius ) { + int bah = 1; + } + #endif + scale = 1 / ( radius + RADIUS_EPSILON ); + VectorScale( dir, scale, dir ); + VectorCopy( dir, tw->trace.plane.normal ); + VectorAdd( tw->modelOrigin, intersection, intersection ); + tw->trace.plane.dist = DotProduct( tw->trace.plane.normal, intersection ); + tw->trace.contents = CONTENTS_BODY; + } + } + } else if ( d == 0 ) { + //t[0] = (- b ) / 2 * a; + // slide along the cylinder + } + // no intersection at all +} + +/* +================ +CM_TraceCapsuleThroughCapsule + +capsule vs. capsule collision (not rotated) +================ +*/ +void CM_TraceCapsuleThroughCapsule( traceWork_t *tw, clipHandle_t model ) { + int i; + vec3_t mins, maxs; + vec3_t top, bottom, starttop, startbottom, endtop, endbottom; + vec3_t offset, symetricSize[2]; + float radius, halfwidth, halfheight, offs, h; + + CM_ModelBounds( model, mins, maxs ); + // test trace bounds vs. capsule bounds + if ( tw->bounds[0][0] > maxs[0] + RADIUS_EPSILON + || tw->bounds[0][1] > maxs[1] + RADIUS_EPSILON + || tw->bounds[0][2] > maxs[2] + RADIUS_EPSILON + || tw->bounds[1][0] < mins[0] - RADIUS_EPSILON + || tw->bounds[1][1] < mins[1] - RADIUS_EPSILON + || tw->bounds[1][2] < mins[2] - RADIUS_EPSILON + ) { + return; + } + // top origin and bottom origin of each sphere at start and end of trace + VectorAdd( tw->start, tw->sphere.offset, starttop ); + VectorSubtract( tw->start, tw->sphere.offset, startbottom ); + VectorAdd( tw->end, tw->sphere.offset, endtop ); + VectorSubtract( tw->end, tw->sphere.offset, endbottom ); + + // calculate top and bottom of the capsule spheres to collide with + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + } + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + offs = halfheight - radius; + VectorCopy( offset, top ); + top[2] += offs; + VectorCopy( offset, bottom ); + bottom[2] -= offs; + // expand radius of spheres + radius += tw->sphere.radius; + // if there is horizontal movement + if ( tw->start[0] != tw->end[0] || tw->start[1] != tw->end[1] ) { + // height of the expanded cylinder is the height of both cylinders minus the radius of both spheres + h = halfheight + tw->sphere.halfheight - radius; + // if the cylinder has a height + if ( h > 0 ) { + // test for collisions between the cylinders + CM_TraceThroughVerticalCylinder( tw, offset, radius, h, tw->start, tw->end ); + } + } + // test for collision between the spheres + CM_TraceThroughSphere( tw, top, radius, startbottom, endbottom ); + CM_TraceThroughSphere( tw, bottom, radius, starttop, endtop ); +} + +/* +================ +CM_TraceBoundingBoxThroughCapsule + +bounding box vs. capsule collision +================ +*/ +void CM_TraceBoundingBoxThroughCapsule( traceWork_t *tw, clipHandle_t model ) { + vec3_t mins, maxs, offset, size[2]; + clipHandle_t h; + cmodel_t *cmod; + int i; + + // mins maxs of the capsule + CM_ModelBounds( model, mins, maxs ); + + // offset for capsule center + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + size[0][i] = mins[i] - offset[i]; + size[1][i] = maxs[i] - offset[i]; + tw->start[i] -= offset[i]; + tw->end[i] -= offset[i]; + } + + // replace the bounding box with the capsule + tw->sphere.use = qtrue; + tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2] : size[1][0]; + tw->sphere.halfheight = size[1][2]; + VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + + // replace the capsule with the bounding box + h = CM_TempBoxModel( tw->size[0], tw->size[1], qfalse ); + // calculate collision + cmod = CM_ClipHandleToModel( h ); + CM_TraceThroughLeaf( tw, &cmod->leaf ); +} + +//========================================================================================= + +/* +================== +CM_TraceThroughTree + +Traverse all the contacted leafs from the start to the end position. +If the trace is a point, they will be exactly in order, but for larger +trace volumes it is possible to hit something in a later leaf with +a smaller intercept fraction. +================== +*/ +void CM_TraceThroughTree( traceWork_t *tw, int num, float p1f, float p2f, vec3_t p1, vec3_t p2 ) { + cNode_t *node; + cplane_t *plane; + float t1, t2, offset; + float frac, frac2; + float idist; + vec3_t mid; + int side; + float midf; + + if ( tw->trace.fraction <= p1f ) { + return; // already hit something nearer + } + + // if < 0, we are in a leaf node + if ( num < 0 ) { + CM_TraceThroughLeaf( tw, &cm.leafs[-1 - num] ); + return; + } + + // + // find the point distances to the seperating plane + // and the offset for the size of the box + // + node = cm.nodes + num; + plane = node->plane; + + // adjust the plane distance apropriately for mins/maxs + if ( plane->type < 3 ) { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + offset = tw->extents[plane->type]; + } else { + t1 = DotProduct( plane->normal, p1 ) - plane->dist; + t2 = DotProduct( plane->normal, p2 ) - plane->dist; + if ( tw->isPoint ) { + offset = 0; + } else { + /* + // an axial brush right behind a slanted bsp plane + // will poke through when expanded, so adjust + // by sqrt(3) + offset = fabs(tw->extents[0]*plane->normal[0]) + + fabs(tw->extents[1]*plane->normal[1]) + + fabs(tw->extents[2]*plane->normal[2]); + + offset *= 2; + offset = tw->maxOffset; + */ + // this is silly + offset = 2048; + } + } + + // see which sides we need to consider + if ( t1 >= offset + 1 && t2 >= offset + 1 ) { + CM_TraceThroughTree( tw, node->children[0], p1f, p2f, p1, p2 ); + return; + } + if ( t1 < -offset - 1 && t2 < -offset - 1 ) { + CM_TraceThroughTree( tw, node->children[1], p1f, p2f, p1, p2 ); + return; + } + + // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side + if ( t1 < t2 ) { + idist = 1.0 / ( t1 - t2 ); + side = 1; + frac2 = ( t1 + offset + SURFACE_CLIP_EPSILON ) * idist; + frac = ( t1 - offset + SURFACE_CLIP_EPSILON ) * idist; + } else if ( t1 > t2 ) { + idist = 1.0 / ( t1 - t2 ); + side = 0; + frac2 = ( t1 - offset - SURFACE_CLIP_EPSILON ) * idist; + frac = ( t1 + offset + SURFACE_CLIP_EPSILON ) * idist; + } else { + side = 0; + frac = 1; + frac2 = 0; + } + + // move up to the node + if ( frac < 0 ) { + frac = 0; + } + if ( frac > 1 ) { + frac = 1; + } + + midf = p1f + ( p2f - p1f ) * frac; + + mid[0] = p1[0] + frac * ( p2[0] - p1[0] ); + mid[1] = p1[1] + frac * ( p2[1] - p1[1] ); + mid[2] = p1[2] + frac * ( p2[2] - p1[2] ); + + CM_TraceThroughTree( tw, node->children[side], p1f, midf, p1, mid ); + + + // go past the node + if ( frac2 < 0 ) { + frac2 = 0; + } + if ( frac2 > 1 ) { + frac2 = 1; + } + + midf = p1f + ( p2f - p1f ) * frac2; + + mid[0] = p1[0] + frac2 * ( p2[0] - p1[0] ); + mid[1] = p1[1] + frac2 * ( p2[1] - p1[1] ); + mid[2] = p1[2] + frac2 * ( p2[2] - p1[2] ); + + CM_TraceThroughTree( tw, node->children[side ^ 1], midf, p2f, mid, p2 ); +} + + +//====================================================================== + + +/* +================== +CM_Trace +================== +*/ +void CM_Trace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, const vec3_t origin, int brushmask, int capsule, sphere_t *sphere ) { + int i; + traceWork_t tw; + vec3_t offset; + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + + cm.checkcount++; // for multi-check avoidance + + c_traces++; // for statistics, may be zeroed + + // fill in a default trace + memset( &tw, 0, sizeof( tw ) ); + tw.trace.fraction = 1; // assume it goes the entire distance until shown otherwise + VectorCopy( origin, tw.modelOrigin ); + + if ( !cm.numNodes ) { + *results = tw.trace; + + return; // map not loaded, shouldn't happen + } + + // allow NULL to be passed in for 0,0,0 + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // set basic parms + tw.contents = brushmask; + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + tw.size[0][i] = mins[i] - offset[i]; + tw.size[1][i] = maxs[i] - offset[i]; + tw.start[i] = start[i] + offset[i]; + tw.end[i] = end[i] + offset[i]; + } + + // if a sphere is already specified + if ( sphere ) { + tw.sphere = *sphere; + } else { + tw.sphere.use = capsule; + tw.sphere.radius = ( tw.size[1][0] > tw.size[1][2] ) ? tw.size[1][2] : tw.size[1][0]; + tw.sphere.halfheight = tw.size[1][2]; + VectorSet( tw.sphere.offset, 0, 0, tw.size[1][2] - tw.sphere.radius ); + } + + tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2]; + + // tw.offsets[signbits] = vector to apropriate corner from origin + tw.offsets[0][0] = tw.size[0][0]; + tw.offsets[0][1] = tw.size[0][1]; + tw.offsets[0][2] = tw.size[0][2]; + + tw.offsets[1][0] = tw.size[1][0]; + tw.offsets[1][1] = tw.size[0][1]; + tw.offsets[1][2] = tw.size[0][2]; + + tw.offsets[2][0] = tw.size[0][0]; + tw.offsets[2][1] = tw.size[1][1]; + tw.offsets[2][2] = tw.size[0][2]; + + tw.offsets[3][0] = tw.size[1][0]; + tw.offsets[3][1] = tw.size[1][1]; + tw.offsets[3][2] = tw.size[0][2]; + + tw.offsets[4][0] = tw.size[0][0]; + tw.offsets[4][1] = tw.size[0][1]; + tw.offsets[4][2] = tw.size[1][2]; + + tw.offsets[5][0] = tw.size[1][0]; + tw.offsets[5][1] = tw.size[0][1]; + tw.offsets[5][2] = tw.size[1][2]; + + tw.offsets[6][0] = tw.size[0][0]; + tw.offsets[6][1] = tw.size[1][1]; + tw.offsets[6][2] = tw.size[1][2]; + + tw.offsets[7][0] = tw.size[1][0]; + tw.offsets[7][1] = tw.size[1][1]; + tw.offsets[7][2] = tw.size[1][2]; + + // + // calculate bounds + // + if ( tw.sphere.use ) { + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] - fabs( tw.sphere.offset[i] ) - tw.sphere.radius; + tw.bounds[1][i] = tw.end[i] + fabs( tw.sphere.offset[i] ) + tw.sphere.radius; + } else { + tw.bounds[0][i] = tw.end[i] - fabs( tw.sphere.offset[i] ) - tw.sphere.radius; + tw.bounds[1][i] = tw.start[i] + fabs( tw.sphere.offset[i] ) + tw.sphere.radius; + } + } + } else { + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i]; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i]; + } + } + } + + // + // check for position test special case + // + if ( start[0] == end[0] && start[1] == end[1] && start[2] == end[2] ) { + if ( model ) { +#ifdef ALWAYS_BBOX_VS_BBOX + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE ) { + tw.sphere.use = qfalse; + CM_TestInLeaf( &tw, &cmod->leaf ); + } else +#elif defined( ALWAYS_CAPSULE_VS_CAPSULE ) + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE ) { + CM_TestCapsuleInCapsule( &tw, model ); + } else +#endif + if ( model == CAPSULE_MODEL_HANDLE ) { + if ( tw.sphere.use ) { + CM_TestCapsuleInCapsule( &tw, model ); + } else { + CM_TestBoundingBoxInCapsule( &tw, model ); + } + } else { + CM_TestInLeaf( &tw, &cmod->leaf ); + } + } else { + CM_PositionTest( &tw ); + } + } else { + // + // check for point special case + // + if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 ) { + tw.isPoint = qtrue; + VectorClear( tw.extents ); + } else { + tw.isPoint = qfalse; + tw.extents[0] = tw.size[1][0]; + tw.extents[1] = tw.size[1][1]; + tw.extents[2] = tw.size[1][2]; + } + + // + // general sweeping through world + // + if ( model ) { +#ifdef ALWAYS_BBOX_VS_BBOX + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE ) { + tw.sphere.use = qfalse; + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + } else +#elif defined( ALWAYS_CAPSULE_VS_CAPSULE ) + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE ) { + CM_TraceCapsuleThroughCapsule( &tw, model ); + } else +#endif + if ( model == CAPSULE_MODEL_HANDLE ) { + if ( tw.sphere.use ) { + CM_TraceCapsuleThroughCapsule( &tw, model ); + } else { + CM_TraceBoundingBoxThroughCapsule( &tw, model ); + } + } else { + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + } + } else { + CM_TraceThroughTree( &tw, 0, 0, 1, tw.start, tw.end ); + } + } + + // generate endpos from the original, unmodified start/end + if ( tw.trace.fraction == 1 ) { + VectorCopy( end, tw.trace.endpos ); + } else { + for ( i = 0 ; i < 3 ; i++ ) { + tw.trace.endpos[i] = start[i] + tw.trace.fraction * ( end[i] - start[i] ); + } + } + + *results = tw.trace; +} + +/* +================== +CM_BoxTrace +================== +*/ +void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, int capsule ) { + CM_Trace( results, start, end, mins, maxs, model, vec3_origin, brushmask, capsule, NULL ); +} + +/* +================== +CM_TransformedBoxTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, int capsule ) { + trace_t trace; + vec3_t start_l, end_l; + qboolean rotated; + vec3_t offset; + vec3_t symetricSize[2]; + vec3_t matrix[3], transpose[3]; + int i; + float halfwidth; + float halfheight; + float t; + sphere_t sphere; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + start_l[i] = start[i] + offset[i]; + end_l[i] = end[i] + offset[i]; + } + + // subtract origin offset + VectorSubtract( start_l, origin, start_l ); + VectorSubtract( end_l, origin, end_l ); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + ( angles[0] || angles[1] || angles[2] ) ) { + rotated = qtrue; + } else { + rotated = qfalse; + } + + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + + sphere.use = capsule; + sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + sphere.halfheight = halfheight; + t = halfheight - sphere.radius; + + if ( rotated ) { + // rotation on trace line (start-end) instead of rotating the bmodel + // NOTE: This is still incorrect for bounding boxes because the actual bounding + // box that is swept through the model is not rotated. We cannot rotate + // the bounding box or the bmodel because that would make all the brush + // bevels invalid. + // However this is correct for capsules since a capsule itself is rotated too. + CreateRotationMatrix( angles, matrix ); + // NOTE TTimo gcc doesn't like the vec3_t m[3] to const vec3_t m[3] casting + RotatePoint( start_l, (const vec3_t *)matrix ); + RotatePoint( end_l, (const vec3_t *)matrix ); + // rotated sphere offset for capsule + sphere.offset[0] = matrix[0][ 2 ] * t; + sphere.offset[1] = -matrix[1][ 2 ] * t; + sphere.offset[2] = matrix[2][ 2 ] * t; + } else { + VectorSet( sphere.offset, 0, 0, t ); + } + + // sweep the box through the model + CM_Trace( &trace, start_l, end_l, symetricSize[0], symetricSize[1], model, origin, brushmask, capsule, &sphere ); + + // if the bmodel was rotated and there was a collision + if ( rotated && trace.fraction != 1.0 ) { + // rotation of bmodel collision plane + TransposeMatrix( (const vec3_t *)matrix, transpose ); + RotatePoint( trace.plane.normal, (const vec3_t *)transpose ); + } + + // re-calculate the end position of the trace because the trace.endpos + // calculated by CM_Trace could be rotated and have an offset + trace.endpos[0] = start[0] + trace.fraction * ( end[0] - start[0] ); + trace.endpos[1] = start[1] + trace.fraction * ( end[1] - start[1] ); + trace.endpos[2] = start[2] + trace.fraction * ( end[2] - start[2] ); + + *results = trace; +} diff --git a/src/qcommon/cmd.c b/src/qcommon/cmd.c new file mode 100644 index 0000000..3b5fbc7 --- /dev/null +++ b/src/qcommon/cmd.c @@ -0,0 +1,713 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cmd.c -- Quake script command processing module + +#include "../game/q_shared.h" +#include "qcommon.h" + +#define MAX_CMD_BUFFER 16384 +#define MAX_CMD_LINE 1024 + +typedef struct { + byte *data; + int maxsize; + int cursize; +} cmd_t; + +int cmd_wait; +cmd_t cmd_text; +byte cmd_text_buf[MAX_CMD_BUFFER]; + + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster" +============ +*/ +void Cmd_Wait_f( void ) { + if ( Cmd_Argc() == 2 ) { + cmd_wait = atoi( Cmd_Argv( 1 ) ); + } else { + cmd_wait = 1; + } +} + + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init( void ) { + cmd_text.data = cmd_text_buf; + cmd_text.maxsize = MAX_CMD_BUFFER; + cmd_text.cursize = 0; +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer, does NOT add a final \n +============ +*/ +void Cbuf_AddText( const char *text ) { + int l; + + l = strlen( text ); + + if ( cmd_text.cursize + l >= cmd_text.maxsize ) { + Com_Printf( "Cbuf_AddText: overflow\n" ); + return; + } + memcpy( &cmd_text.data[cmd_text.cursize], text, l ); + cmd_text.cursize += l; +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void Cbuf_InsertText( const char *text ) { + int len; + int i; + + len = strlen( text ) + 1; + if ( len + cmd_text.cursize > cmd_text.maxsize ) { + Com_Printf( "Cbuf_InsertText overflowed\n" ); + return; + } + + // move the existing command text + for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) { + cmd_text.data[ i + len ] = cmd_text.data[ i ]; + } + + // copy the new text in + memcpy( cmd_text.data, text, len - 1 ); + + // add a \n + cmd_text.data[ len - 1 ] = '\n'; + + cmd_text.cursize += len; +} + + +/* +============ +Cbuf_ExecuteText +============ +*/ +void Cbuf_ExecuteText( int exec_when, const char *text ) { + switch ( exec_when ) + { + case EXEC_NOW: + if ( text && strlen( text ) > 0 ) { + Cmd_ExecuteString( text ); + } else { + Cbuf_Execute(); + } + break; + case EXEC_INSERT: + Cbuf_InsertText( text ); + break; + case EXEC_APPEND: + Cbuf_AddText( text ); + break; + default: + Com_Error( ERR_FATAL, "Cbuf_ExecuteText: bad exec_when" ); + } +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute( void ) { + int i; + char *text; + char line[MAX_CMD_LINE]; + int quotes; + + while ( cmd_text.cursize ) + { + if ( cmd_wait ) { + // skip out while text still remains in buffer, leaving it + // for next frame + cmd_wait--; + break; + } + + // 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' || text[i] == '\r' ) { + break; + } + } + + if ( i >= ( MAX_CMD_LINE - 1 ) ) { + i = MAX_CMD_LINE - 1; + } + + 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) 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 ); + } +} + + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + + +/* +=============== +Cmd_Exec_f +=============== +*/ +void Cmd_Exec_f( void ) { + char *f; + int len; + char filename[MAX_QPATH]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "exec : execute a script file\n" ); + return; + } + + Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + len = FS_ReadFile( filename, (void **)&f ); + if ( !f ) { + Com_Printf( "couldn't exec %s\n",Cmd_Argv( 1 ) ); + return; + } + Com_Printf( "execing %s\n",Cmd_Argv( 1 ) ); + + Cbuf_InsertText( f ); + + FS_FreeFile( f ); +} + + +/* +=============== +Cmd_Vstr_f + +Inserts the current value of a variable as command text +=============== +*/ +void Cmd_Vstr_f( void ) { + char *v; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "vstr : execute a variable command\n" ); + return; + } + + v = Cvar_VariableString( Cmd_Argv( 1 ) ); + Cbuf_InsertText( va( "%s\n", v ) ); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f( void ) { + int i; + + for ( i = 1 ; i < Cmd_Argc() ; i++ ) + Com_Printf( "%s ",Cmd_Argv( i ) ); + Com_Printf( "\n" ); +} + + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + char *name; + xcommand_t function; +} cmd_function_t; + + +static int cmd_argc; +static char *cmd_argv[MAX_STRING_TOKENS]; // points into cmd_tokenized +static char cmd_tokenized[BIG_INFO_STRING + MAX_STRING_TOKENS]; // will have 0 bytes inserted +static char cmd_cmd[BIG_INFO_STRING]; // the original command we received (no token processing) + +static cmd_function_t *cmd_functions; // possible commands to execute + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc( void ) { + return cmd_argc; +} + +/* +============ +Cmd_Argv +============ +*/ +char *Cmd_Argv( int arg ) { + if ( (unsigned)arg >= cmd_argc ) { + return ""; + } + return cmd_argv[arg]; +} + +/* +============ +Cmd_ArgvBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength ); +} + + +/* +============ +Cmd_Args + +Returns a single string containing argv(1) to argv(argc()-1) +============ +*/ +char *Cmd_Args( void ) { + static char cmd_args[MAX_STRING_CHARS]; + int i; + + cmd_args[0] = 0; + for ( i = 1 ; i < cmd_argc ; i++ ) { + strcat( cmd_args, cmd_argv[i] ); + if ( i != cmd_argc - 1 ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + +/* +============ +Cmd_Args + +Returns a single string containing argv(arg) to argv(argc()-1) +============ +*/ +char *Cmd_ArgsFrom( int arg ) { + static char cmd_args[BIG_INFO_STRING]; + int i; + + cmd_args[0] = 0; + if ( arg < 0 ) { + arg = 0; + } + for ( i = arg ; i < cmd_argc ; i++ ) { + strcat( cmd_args, cmd_argv[i] ); + if ( i != cmd_argc - 1 ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + +/* +============ +Cmd_ArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgsBuffer( char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Args(), bufferLength ); +} + +/* +============ +Cmd_Cmd + +Retrieve the unmodified command string +For rcon use when you want to transmit without altering quoting +ATVI Wolfenstein Misc #284 +============ +*/ +char *Cmd_Cmd() { + return cmd_cmd; +} + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +The text is copied to a seperate buffer and 0 characters +are inserted in the apropriate place, The argv array +will point into this temporary buffer. +============ +*/ +void Cmd_TokenizeString( const char *text_in ) { + const char *text; + char *textOut; + + // clear previous args + cmd_argc = 0; + + if ( !text_in ) { + return; + } + + Q_strncpyz( cmd_cmd, text_in, sizeof( cmd_cmd ) ); + + text = text_in; + textOut = cmd_tokenized; + + while ( 1 ) { + if ( cmd_argc == MAX_STRING_TOKENS ) { + return; // this is usually something malicious + } + + while ( 1 ) { + // skip whitespace + while ( *text && *text <= ' ' ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + + // skip // comments + if ( text[0] == '/' && text[1] == '/' ) { + return; // all tokens parsed + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] == '*' ) { + while ( *text && ( text[0] != '*' || text[1] != '/' ) ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + text += 2; + } else { + break; // we are ready to parse a token + } + } + + // handle quoted strings + if ( *text == '"' ) { + cmd_argv[cmd_argc] = textOut; + cmd_argc++; + text++; + while ( *text && *text != '"' ) { + *textOut++ = *text++; + } + *textOut++ = 0; + if ( !*text ) { + return; // all tokens parsed + } + text++; + continue; + } + + // regular token + cmd_argv[cmd_argc] = textOut; + cmd_argc++; + + // skip until whitespace, quote, or command + while ( *text > ' ' ) { + if ( text[0] == '"' ) { + break; + } + + if ( text[0] == '/' && text[1] == '/' ) { + break; + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] == '*' ) { + break; + } + + *textOut++ = *text++; + } + + *textOut++ = 0; + + if ( !*text ) { + return; // all tokens parsed + } + } + +} + + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { + cmd_function_t *cmd; + + // fail if the command already exists + for ( cmd = cmd_functions ; cmd ; cmd = cmd->next ) { + if ( !strcmp( cmd_name, cmd->name ) ) { + // allow completion-only commands to be silently doubled + if ( function != NULL ) { + Com_Printf( "Cmd_AddCommand: %s already defined\n", cmd_name ); + } + return; + } + } + + // use a small malloc to avoid zone fragmentation + cmd = S_Malloc( sizeof( cmd_function_t ) ); + cmd->name = CopyString( cmd_name ); + cmd->function = function; + cmd->next = cmd_functions; + cmd_functions = cmd; +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *cmd_name ) { + cmd_function_t *cmd, **back; + + back = &cmd_functions; + while ( 1 ) { + cmd = *back; + if ( !cmd ) { + // command wasn't active + return; + } + if ( !strcmp( cmd_name, cmd->name ) ) { + *back = cmd->next; + if ( cmd->name ) { + Z_Free( cmd->name ); + } + Z_Free( cmd ); + return; + } + back = &cmd->next; + } +} + + +/* +============ +Cmd_CommandCompletion +============ +*/ +void Cmd_CommandCompletion( void ( *callback )(const char *s) ) { + cmd_function_t *cmd; + + for ( cmd = cmd_functions ; cmd ; cmd = cmd->next ) { + callback( cmd->name ); + } +} + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void Cmd_ExecuteString( const char *text ) { + cmd_function_t *cmd, **prev; + + // execute the command line + Cmd_TokenizeString( text ); + if ( !Cmd_Argc() ) { + return; // no tokens + } + + // check registered command functions + for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) { + cmd = *prev; + if ( !Q_stricmp( cmd_argv[0],cmd->name ) ) { + // rearrange the links so that the command will be + // near the head of the list next time it is used + *prev = cmd->next; + cmd->next = cmd_functions; + cmd_functions = cmd; + + // perform the action + if ( !cmd->function ) { + // let the cgame or game handle it + break; + } else { + cmd->function(); + } + return; + } + } + + // check cvars + if ( Cvar_Command() ) { + return; + } + + // check client game commands + if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) { + return; + } + + // check server game commands + if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) { + return; + } + + // check ui commands + if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) { + return; + } + + // send it as a server command if we are connected + // this will usually result in a chat message + CL_ForwardCommandToServer( text ); +} + +/* +============ +Cmd_List_f +============ +*/ +void Cmd_List_f( void ) { + cmd_function_t *cmd; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for ( cmd = cmd_functions ; cmd ; cmd = cmd->next ) { + if ( match && !Com_Filter( match, cmd->name, qfalse ) ) { + continue; + } + + Com_Printf( "%s\n", cmd->name ); + i++; + } + Com_Printf( "%i commands\n", i ); +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init( void ) { + Cmd_AddCommand( "cmdlist",Cmd_List_f ); + Cmd_AddCommand( "exec",Cmd_Exec_f ); + Cmd_AddCommand( "vstr",Cmd_Vstr_f ); + Cmd_AddCommand( "echo",Cmd_Echo_f ); + Cmd_AddCommand( "wait", Cmd_Wait_f ); +} + diff --git a/src/qcommon/common.c b/src/qcommon/common.c new file mode 100644 index 0000000..06afa2e --- /dev/null +++ b/src/qcommon/common.c @@ -0,0 +1,3399 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// common.c -- misc functions used in client and server + +#include "../game/q_shared.h" +#include "qcommon.h" +#include + +#define MAX_NUM_ARGVS 50 + +#define MIN_DEDICATED_COMHUNKMEGS 1 +#define MIN_COMHUNKMEGS 42 // JPW NERVE changed this to 42 for MP, was 56 for team arena and 75 for wolfSP +#define DEF_COMHUNKMEGS "56" // RF, increased this, some maps are exceeding 56mb // JPW NERVE changed this for multiplayer back to 42, 56 for depot/mp_cpdepot, 42 for everything else +#define DEF_COMZONEMEGS "16" // JPW NERVE cut this back too was 30 + +int com_argc; +char *com_argv[MAX_NUM_ARGVS + 1]; + +jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame + + +FILE *debuglogfile; +static fileHandle_t logfile; +fileHandle_t com_journalFile; // events are written here +fileHandle_t com_journalDataFile; // config files are written here + +cvar_t *com_viewlog; +cvar_t *com_speeds; +cvar_t *com_developer; +cvar_t *com_dedicated; +cvar_t *com_timescale; +cvar_t *com_fixedtime; +cvar_t *com_dropsim; // 0.0 to 1.0, simulated packet drops +cvar_t *com_journal; +cvar_t *com_maxfps; +cvar_t *com_timedemo; +cvar_t *com_sv_running; +cvar_t *com_cl_running; +cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print +cvar_t *com_showtrace; +cvar_t *com_version; +cvar_t *com_blood; +cvar_t *com_buildScript; // for automated data building scripts +cvar_t *com_introPlayed; +cvar_t *cl_paused; +cvar_t *sv_paused; +cvar_t *com_cameraMode; +#if defined( _WIN32 ) && defined( _DEBUG ) +cvar_t *com_noErrorInterrupt; +#endif +cvar_t *com_recommendedSet; + +// Rafael Notebook +cvar_t *cl_notebook; + +cvar_t *com_hunkused; // Ridah + +// com_speeds times +int time_game; +int time_frontend; // renderer frontend time +int time_backend; // renderer backend time + +int com_frameTime; +int com_frameMsec; +int com_frameNumber; + +qboolean com_errorEntered; +qboolean com_fullyInitialized; + +char com_errorMessage[MAXPRINTMSG]; + +void Com_WriteConfig_f( void ); +void CIN_CloseAllVideos(); + +//============================================================================ + +static char *rd_buffer; +static int rd_buffersize; +static void ( *rd_flush )( char *buffer ); + +void Com_BeginRedirect( char *buffer, int buffersize, void ( *flush )( char *) ) { + if ( !buffer || !buffersize || !flush ) { + return; + } + rd_buffer = buffer; + rd_buffersize = buffersize; + rd_flush = flush; + + *rd_buffer = 0; +} + +void Com_EndRedirect( void ) { + if ( rd_flush ) { + rd_flush( rd_buffer ); + } + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; +} + +/* +============= +Com_Printf + +Both client and server can use this, and it will output +to the apropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +============= +*/ +void QDECL Com_Printf( const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + static qboolean opening_qconsole = qfalse; + + va_start( argptr,fmt ); + Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ); + va_end( argptr ); + + if ( rd_buffer ) { + if ( ( strlen( msg ) + strlen( rd_buffer ) ) > ( rd_buffersize - 1 ) ) { + rd_flush( rd_buffer ); + *rd_buffer = 0; + } + Q_strcat( rd_buffer, rd_buffersize, msg ); + // show_bug.cgi?id=51 + // only flush the rcon buffer when it's necessary, avoid fragmenting + //rd_flush(rd_buffer); + //*rd_buffer = 0; + return; + } + + // echo to console if we're not a dedicated server + if ( com_dedicated && !com_dedicated->integer ) { + CL_ConsolePrint( msg ); + } + + // echo to dedicated console and early console + Sys_Print( msg ); + + // logfile + if ( com_logfile && com_logfile->integer ) { + // TTimo: only open the qconsole.log if the filesystem is in an initialized state + // also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on) + if ( !logfile && FS_Initialized() && !opening_qconsole ) { + struct tm *newtime; + time_t aclock; + + opening_qconsole = qtrue; + + time( &aclock ); + newtime = localtime( &aclock ); + +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'TEXT'; + _fcreator = 'R*ch'; + } +#endif + logfile = FS_FOpenFileWrite( "rtcwconsole.log" ); + Com_Printf( "logfile opened on %s\n", asctime( newtime ) ); + if ( com_logfile->integer > 1 ) { + // force it to not buffer so we get valid + // data even if we are crashing + FS_ForceFlush( logfile ); + } + + opening_qconsole = qfalse; + } + if ( logfile && FS_Initialized() ) { + FS_Write( msg, strlen( msg ), logfile ); + } + } +} + + +/* +================ +Com_DPrintf + +A Com_Printf that only shows up if the "developer" cvar is set +================ +*/ +void QDECL Com_DPrintf( const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + + if ( !com_developer || !com_developer->integer ) { + return; // don't confuse non-developers with techie stuff... + } + + va_start( argptr,fmt ); + Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ); + va_end( argptr ); + + Com_Printf( "%s", msg ); +} + +/* +============= +Com_Error + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void QDECL Com_Error( int code, const char *fmt, ... ) { + va_list argptr; + static int lastErrorTime; + static int errorCount; + int currentTime; + +#if 0 //#if defined(_WIN32) && defined(_DEBUG) + if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) { + if ( !com_noErrorInterrupt->integer ) { + __asm { + int 0x03 + } + } + } +#endif + + // when we are running automated scripts, make sure we + // know if anything failed + if ( com_buildScript && com_buildScript->integer ) { + code = ERR_FATAL; + } + + // make sure we can get at our local stuff + FS_PureServerSetLoadedPaks( "", "" ); + + // if we are getting a solid stream of ERR_DROP, do an ERR_FATAL + currentTime = Sys_Milliseconds(); + if ( currentTime - lastErrorTime < 100 ) { + if ( ++errorCount > 3 ) { + code = ERR_FATAL; + } + } else { + errorCount = 0; + } + lastErrorTime = currentTime; + + if ( com_errorEntered ) { + Sys_Error( "recursive error after: %s", com_errorMessage ); + } + com_errorEntered = qtrue; + + va_start( argptr,fmt ); + Q_vsnprintf( com_errorMessage, sizeof( com_errorMessage ), fmt, argptr ); + va_end( argptr ); + + if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) { + Cvar_Set( "com_errorMessage", com_errorMessage ); + } + + if ( code == ERR_SERVERDISCONNECT ) { + CL_Disconnect( qtrue ); + CL_FlushMemory(); + com_errorEntered = qfalse; + longjmp( abortframe, -1 ); + } else if ( code == ERR_DROP || code == ERR_DISCONNECT ) { + Com_Printf( "********************\nERROR: %s\n********************\n", com_errorMessage ); + SV_Shutdown( va( "Server crashed: %s\n", com_errorMessage ) ); + CL_Disconnect( qtrue ); + CL_FlushMemory(); + com_errorEntered = qfalse; + longjmp( abortframe, -1 ); + } else if ( code == ERR_NEED_CD ) { + SV_Shutdown( "Server didn't have CD\n" ); + if ( com_cl_running && com_cl_running->integer ) { + CL_Disconnect( qtrue ); + CL_FlushMemory(); + com_errorEntered = qfalse; + CL_CDDialog(); + } else { + Com_Printf( "Server didn't have CD\n" ); + } + longjmp( abortframe, -1 ); + } else { + CL_Shutdown(); + SV_Shutdown( va( "Server fatal crashed: %s\n", com_errorMessage ) ); + } + + Com_Shutdown(); + + Sys_Error( "%s", com_errorMessage ); +} + + +/* +============= +Com_Quit_f + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void Com_Quit_f( void ) { + // don't try to shutdown if we are in a recursive error + if ( !com_errorEntered ) { + SV_Shutdown( "Server quit\n" ); + CL_Shutdown(); + Com_Shutdown(); + FS_Shutdown( qtrue ); + } + Sys_Quit(); +} + + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters seperate the commandLine string into multiple console +command lines. + +All of these are valid: + +quake3 +set test blah +map test +quake3 set test blah+map test +quake3 set test blah + map test + +============================================================================ +*/ + +#define MAX_CONSOLE_LINES 32 +int com_numConsoleLines; +char *com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +Com_ParseCommandLine + +Break it up into multiple console lines +================== +*/ +void Com_ParseCommandLine( char *commandLine ) { + com_consoleLines[0] = commandLine; + com_numConsoleLines = 1; + + while ( *commandLine ) { + // look for a + seperating character + // if commandLine came from a file, we might have real line seperators + if ( *commandLine == '+' || *commandLine == '\n' ) { + if ( com_numConsoleLines == MAX_CONSOLE_LINES ) { + return; + } + com_consoleLines[com_numConsoleLines] = commandLine + 1; + com_numConsoleLines++; + *commandLine = 0; + } + commandLine++; + } +} + + +/* +=================== +Com_SafeMode + +Check for "safe" on the command line, which will +skip loading of wolfconfig.cfg +=================== +*/ +qboolean Com_SafeMode( void ) { + int i; + + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( !Q_stricmp( Cmd_Argv( 0 ), "safe" ) + || !Q_stricmp( Cmd_Argv( 0 ), "cvar_restart" ) ) { + com_consoleLines[i][0] = 0; + return qtrue; + } + } + return qfalse; +} + + +/* +=============== +Com_StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets shouls +be after execing the config and default. +=============== +*/ +void Com_StartupVariable( const char *match ) { + int i; + char *s; + cvar_t *cv; + + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( strcmp( Cmd_Argv( 0 ), "set" ) ) { + continue; + } + + s = Cmd_Argv( 1 ); + if ( !match || !strcmp( s, match ) ) { + Cvar_Set( s, Cmd_Argv( 2 ) ); + cv = Cvar_Get( s, "", 0 ); + cv->flags |= CVAR_USER_CREATED; +// com_consoleLines[i] = 0; + } + } +} + + +/* +================= +Com_AddStartupCommands + +Adds command line parameters as script statements +Commands are seperated by + signs + +Returns qtrue if any late commands were added, which +will keep the demoloop from immediately starting +================= +*/ +qboolean Com_AddStartupCommands( void ) { + int i; + qboolean added; + + added = qfalse; + // quote every token, so args with semicolons can work + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) { + continue; + } + + // set commands won't override menu startup + if ( Q_stricmpn( com_consoleLines[i], "set", 3 ) ) { + added = qtrue; + } + Cbuf_AddText( com_consoleLines[i] ); + Cbuf_AddText( "\n" ); + } + + return added; +} + + +//============================================================================ + +void Info_Print( const char *s ) { + char key[512]; + char value[512]; + char *o; + int l; + + if ( *s == '\\' ) { + s++; + } + while ( *s ) + { + o = key; + while ( *s && *s != '\\' ) + *o++ = *s++; + + l = o - key; + if ( l < 20 ) { + memset( o, ' ', 20 - l ); + key[20] = 0; + } else { + *o = 0; + } + Com_Printf( "%s", key ); + + if ( !*s ) { + Com_Printf( "MISSING VALUE\n" ); + return; + } + + o = value; + s++; + while ( *s && *s != '\\' ) + *o++ = *s++; + *o = 0; + + if ( *s ) { + s++; + } + Com_Printf( "%s\n", value ); + } +} + +/* +============ +Com_StringContains +============ +*/ +char *Com_StringContains( char *str1, char *str2, int casesensitive ) { + int len, i, j; + + len = strlen( str1 ) - strlen( str2 ); + for ( i = 0; i <= len; i++, str1++ ) { + for ( j = 0; str2[j]; j++ ) { + if ( casesensitive ) { + if ( str1[j] != str2[j] ) { + break; + } + } else { + if ( toupper( str1[j] ) != toupper( str2[j] ) ) { + break; + } + } + } + if ( !str2[j] ) { + return str1; + } + } + return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter( char *filter, char *name, int casesensitive ) { + char buf[MAX_TOKEN_CHARS]; + char *ptr; + int i, found; + + while ( *filter ) { + if ( *filter == '*' ) { + filter++; + for ( i = 0; *filter; i++ ) { + if ( *filter == '*' || *filter == '?' ) { + break; + } + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if ( strlen( buf ) ) { + ptr = Com_StringContains( name, buf, casesensitive ); + if ( !ptr ) { + return qfalse; + } + name = ptr + strlen( buf ); + } + } else if ( *filter == '?' ) { + filter++; + name++; + } else if ( *filter == '[' && *( filter + 1 ) == '[' ) { + filter++; + } else if ( *filter == '[' ) { + filter++; + found = qfalse; + while ( *filter && !found ) { + if ( *filter == ']' && *( filter + 1 ) != ']' ) { + break; + } + if ( *( filter + 1 ) == '-' && *( filter + 2 ) && ( *( filter + 2 ) != ']' || *( filter + 3 ) == ']' ) ) { + if ( casesensitive ) { + if ( *name >= *filter && *name <= *( filter + 2 ) ) { + found = qtrue; + } + } else { + if ( toupper( *name ) >= toupper( *filter ) && + toupper( *name ) <= toupper( *( filter + 2 ) ) ) { + found = qtrue; + } + } + filter += 3; + } else { + if ( casesensitive ) { + if ( *filter == *name ) { + found = qtrue; + } + } else { + if ( toupper( *filter ) == toupper( *name ) ) { + found = qtrue; + } + } + filter++; + } + } + if ( !found ) { + return qfalse; + } + while ( *filter ) { + if ( *filter == ']' && *( filter + 1 ) != ']' ) { + break; + } + filter++; + } + filter++; + name++; + } else { + if ( casesensitive ) { + if ( *filter != *name ) { + return qfalse; + } + } else { + if ( toupper( *filter ) != toupper( *name ) ) { + return qfalse; + } + } + filter++; + name++; + } + } + return qtrue; +} + +/* +============ +Com_FilterPath +============ +*/ +int Com_FilterPath( char *filter, char *name, int casesensitive ) { + int i; + char new_filter[MAX_QPATH]; + char new_name[MAX_QPATH]; + + for ( i = 0; i < MAX_QPATH - 1 && filter[i]; i++ ) { + if ( filter[i] == '\\' || filter[i] == ':' ) { + new_filter[i] = '/'; + } else { + new_filter[i] = filter[i]; + } + } + new_filter[i] = '\0'; + for ( i = 0; i < MAX_QPATH - 1 && name[i]; i++ ) { + if ( name[i] == '\\' || name[i] == ':' ) { + new_name[i] = '/'; + } else { + new_name[i] = name[i]; + } + } + new_name[i] = '\0'; + return Com_Filter( new_filter, new_name, casesensitive ); +} + +/* +============ +Com_HashKey +============ +*/ +int Com_HashKey( char *string, int maxlen ) { + int register hash, i; + + hash = 0; + for ( i = 0; i < maxlen && string[i] != '\0'; i++ ) { + hash += string[i] * ( 119 + i ); + } + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ); + return hash; +} + +/* +================ +Com_RealTime +================ +*/ +int Com_RealTime( qtime_t *qtime ) { + time_t t; + struct tm *tms; + + t = time( NULL ); + if ( !qtime ) { + return t; + } + tms = localtime( &t ); + if ( tms ) { + qtime->tm_sec = tms->tm_sec; + qtime->tm_min = tms->tm_min; + qtime->tm_hour = tms->tm_hour; + qtime->tm_mday = tms->tm_mday; + qtime->tm_mon = tms->tm_mon; + qtime->tm_year = tms->tm_year; + qtime->tm_wday = tms->tm_wday; + qtime->tm_yday = tms->tm_yday; + qtime->tm_isdst = tms->tm_isdst; + } + return t; +} + + +/* +============================================================================== + + 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. +============================================================================== +*/ + +#define ZONEID 0x1d4a11 +#define MINFRAGMENT 64 + +typedef struct zonedebug_s { + char *label; + char *file; + int line; + int allocSize; +} zonedebug_t; + +typedef struct memblock_s { + int size; // including the header and possibly tiny fragments + int tag; // a tag of 0 is a free block + struct memblock_s *next, *prev; + int id; // should be ZONEID +#ifdef ZONE_DEBUG + zonedebug_t d; +#endif +} memblock_t; + +typedef struct { + int size; // total bytes malloced, including header + int used; // total bytes used + memblock_t blocklist; // start / end cap for linked list + memblock_t *rover; +} memzone_t; + +// main zone for all "dynamic" memory allocation +memzone_t *mainzone; +// we also have a small zone for small allocations that would only +// fragment the main zone (think of cvar and cmd strings) +memzone_t *smallzone; + + +void Z_CheckHeap( void ); + +/* +======================== +Z_ClearZone +======================== +*/ +void Z_ClearZone( 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; + zone->size = size; + zone->used = 0; + + block->prev = block->next = &zone->blocklist; + block->tag = 0; // free block + block->id = ZONEID; + block->size = size - sizeof( memzone_t ); +} + + +/* +======================== +Z_Free +======================== +*/ +void Z_Free( void *ptr ) { + memblock_t *block, *other; + memzone_t *zone; + + if ( !ptr ) { + Com_Error( ERR_DROP, "Z_Free: NULL pointer" ); + } + + block = ( memblock_t * )( (byte *)ptr - sizeof( memblock_t ) ); + if ( block->id != ZONEID ) { + Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" ); + } + if ( block->tag == 0 ) { + Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" ); + } + // if static memory + if ( block->tag == TAG_STATIC ) { + return; + } + + // check the memory trash tester + if ( *( int * )( (byte *)block + block->size - 4 ) != ZONEID ) { + Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" ); + } + + if ( block->tag == TAG_SMALL ) { + zone = smallzone; + } else { + zone = mainzone; + } + + zone->used -= block->size; + // set the block to something that should cause problems + // if it is referenced... + memset( ptr, 0xaa, block->size - sizeof( *block ) ); + + 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 == zone->rover ) { + zone->rover = other; + } + block = other; + } + + zone->rover = block; + + 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 == zone->rover ) { + zone->rover = block; + } + } +} + + +/* +================ +Z_FreeTags +================ +*/ +void Z_FreeTags( int tag ) { + int count; + memzone_t *zone; + + if ( tag == TAG_SMALL ) { + zone = smallzone; + } else { + zone = mainzone; + } + count = 0; + // use the rover as our pointer, because + // Z_Free automatically adjusts it + zone->rover = zone->blocklist.next; + do { + if ( zone->rover->tag == tag ) { + count++; + Z_Free( ( void * )( zone->rover + 1 ) ); + continue; + } + zone->rover = zone->rover->next; + } while ( zone->rover != &zone->blocklist ); +} + +/* +================ +Z_TagMalloc +================ +*/ + +memblock_t *debugblock; // RF, jusy so we can track a block to find out when it's getting trashed + +#ifdef ZONE_DEBUG +void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ) { +#else +void *Z_TagMalloc( int size, int tag ) { +#endif + int extra, allocSize; + memblock_t *start, *rover, *new, *base; + memzone_t *zone; + + if ( !tag ) { + Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use a 0 tag" ); + } + + if ( tag == TAG_SMALL ) { + zone = smallzone; + } else { + zone = mainzone; + } + + allocSize = size; + // + // 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 + 3 ) & ~3; // align to 32 bit boundary + + base = rover = zone->rover; + start = base->prev; + + do { + if ( rover == start ) { +#ifdef ZONE_DEBUG + Z_LogHeap(); +#endif + // scaned all the way around the list + Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone", + size, zone == smallzone ? "small" : "main" ); + 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 + new = ( memblock_t * )( (byte *)base + size ); + new->size = extra; + new->tag = 0; // free block + new->prev = base; + new->id = ZONEID; + new->next = base->next; + new->next->prev = new; + base->next = new; + base->size = size; + } + + base->tag = tag; // no longer a free block + + zone->rover = base->next; // next allocation will start looking here + zone->used += base->size; // + + base->id = ZONEID; + +#ifdef ZONE_DEBUG + base->d.label = label; + base->d.file = file; + base->d.line = line; + base->d.allocSize = allocSize; +#endif + + // marker for memory trash testing + *( int * )( (byte *)base + base->size - 4 ) = ZONEID; + + return ( void * )( (byte *)base + sizeof( memblock_t ) ); +} + +/* +======================== +Z_Malloc +======================== +*/ +#ifdef ZONE_DEBUG +void *Z_MallocDebug( int size, char *label, char *file, int line ) { +#else +void *Z_Malloc( int size ) { +#endif + void *buf; + + //Z_CheckHeap (); // DEBUG + +#ifdef ZONE_DEBUG + buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line ); +#else + buf = Z_TagMalloc( size, TAG_GENERAL ); +#endif + Com_Memset( buf, 0, size ); + + return buf; +} + +#ifdef ZONE_DEBUG +void *S_MallocDebug( int size, char *label, char *file, int line ) { + return Z_TagMallocDebug( size, TAG_SMALL, label, file, line ); +} +#else +void *S_Malloc( int size ) { + return Z_TagMalloc( size, TAG_SMALL ); +} +#endif + +/* +======================== +Z_CheckHeap +======================== +*/ +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 ) { + Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block\n" ); + } + if ( block->next->prev != block ) { + Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link\n" ); + } + if ( !block->tag && !block->next->tag ) { + Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks\n" ); + } + } +} + +/* +======================== +Z_LogZoneHeap +======================== +*/ +void Z_LogZoneHeap( memzone_t *zone, char *name ) { +#ifdef ZONE_DEBUG + char dump[32], *ptr; + int i, j; +#endif + memblock_t *block; + char buf[4096]; + int size, allocSize, numBlocks; + + if ( !logfile || !FS_Initialized() ) { + return; + } + size = allocSize = numBlocks = 0; + Com_sprintf( buf, sizeof( buf ), "\r\n================\r\n%s log\r\n================\r\n", name ); + FS_Write( buf, strlen( buf ), logfile ); + for ( block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next ) { + if ( block->tag ) { +#ifdef ZONE_DEBUG + ptr = ( (char *) block ) + sizeof( memblock_t ); + j = 0; + for ( i = 0; i < 20 && i < block->d.allocSize; i++ ) { + if ( ptr[i] >= 32 && ptr[i] < 127 ) { + dump[j++] = ptr[i]; + } else { + dump[j++] = '_'; + } + } + dump[j] = '\0'; + Com_sprintf( buf, sizeof( buf ), "size = %8d: %s, line: %d (%s) [%s]\r\n", block->d.allocSize, block->d.file, block->d.line, block->d.label, dump ); + FS_Write( buf, strlen( buf ), logfile ); + allocSize += block->d.allocSize; +#endif + size += block->size; + numBlocks++; + } + } +#ifdef ZONE_DEBUG + // subtract debug memory + size -= numBlocks * sizeof( zonedebug_t ); +#else + allocSize = numBlocks * sizeof( memblock_t ); // + 32 bit alignment +#endif + Com_sprintf( buf, sizeof( buf ), "%d %s memory in %d blocks\r\n", size, name, numBlocks ); + FS_Write( buf, strlen( buf ), logfile ); + Com_sprintf( buf, sizeof( buf ), "%d %s memory overhead\r\n", size - allocSize, name ); + FS_Write( buf, strlen( buf ), logfile ); +} + +/* +======================== +Z_LogHeap +======================== +*/ +void Z_LogHeap( void ) { + Z_LogZoneHeap( mainzone, "MAIN" ); + Z_LogZoneHeap( smallzone, "SMALL" ); +} + +// static mem blocks to reduce a lot of small zone overhead +typedef struct memstatic_s { + memblock_t b; + byte mem[2]; +} memstatic_t; + +// bk001204 - initializer brackets +memstatic_t emptystring = +{ {( sizeof( memblock_t ) + 2 + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'} }; +memstatic_t numberstring[] = { + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} }, + { {( sizeof( memstatic_t ) + 3 ) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} } +}; + +/* +======================== +CopyString + + NOTE: never write over the memory CopyString returns because + memory from a memstatic_t might be returned +======================== +*/ +char *CopyString( const char *in ) { + char *out; + + if ( !in[0] ) { + return ( (char *)&emptystring ) + sizeof( memblock_t ); + } else if ( !in[1] ) { + if ( in[0] >= '0' && in[0] <= '9' ) { + return ( (char *)&numberstring[in[0] - '0'] ) + sizeof( memblock_t ); + } + } + out = S_Malloc( strlen( in ) + 1 ); + strcpy( out, in ); + return out; +} + +/* +============================================================================== + +Goals: + reproducable without history effects -- no out of memory errors on weird map to map changes + allow restarting of the client without fragmentation + minimize total pages in use at run time + minimize total pages needed during load time + + Single block of memory with stack allocators coming from both ends towards the middle. + + One side is designated the temporary memory allocator. + + Temporary memory can be allocated and freed in any order. + + A highwater mark is kept of the most in use at any time. + + When there is no temporary memory allocated, the permanent and temp sides + can be switched, allowing the already touched temp memory to be used for + permanent storage. + + Temp memory must never be allocated on two ends at once, or fragmentation + could occur. + + If we have any in-use temp memory, additional temp allocations must come from + that side. + + If not, we can choose to make either side the new temp side and push future + permanent allocations to the other side. Permanent allocations should be + kept on the side that has the current greatest wasted highwater mark. + +============================================================================== +*/ + + +#define HUNK_MAGIC 0x89537892 +#define HUNK_FREE_MAGIC 0x89537893 + +typedef struct { + int magic; + int size; +} hunkHeader_t; + +typedef struct { + int mark; + int permanent; + int temp; + int tempHighwater; +} hunkUsed_t; + +typedef struct hunkblock_s { + int size; + byte printed; + struct hunkblock_s *next; + char *label; + char *file; + int line; +} hunkblock_t; + +static hunkblock_t *hunkblocks; + +static hunkUsed_t hunk_low, hunk_high; +static hunkUsed_t *hunk_permanent, *hunk_temp; + +static byte *s_hunkData = NULL; +static int s_hunkTotal; + +static int s_zoneTotal; +static int s_smallZoneTotal; + + +/* +================= +Com_Meminfo_f +================= +*/ +void Com_Meminfo_f( void ) { + memblock_t *block; + int zoneBytes, zoneBlocks; + int smallZoneBytes, smallZoneBlocks; + int botlibBytes, rendererBytes; + int unused; + + zoneBytes = 0; + botlibBytes = 0; + rendererBytes = 0; + zoneBlocks = 0; + for ( block = mainzone->blocklist.next ; ; block = block->next ) { + if ( Cmd_Argc() != 1 ) { + Com_Printf( "block:%p size:%7i tag:%3i\n", + block, block->size, block->tag ); + } + if ( block->tag ) { + zoneBytes += block->size; + zoneBlocks++; + if ( block->tag == TAG_BOTLIB ) { + botlibBytes += block->size; + } else if ( block->tag == TAG_RENDERER ) { + rendererBytes += block->size; + } + } + + if ( block->next == &mainzone->blocklist ) { + break; // all blocks have been hit + } + if ( (byte *)block + block->size != (byte *)block->next ) { + Com_Printf( "ERROR: block size does not touch the next block\n" ); + } + if ( block->next->prev != block ) { + Com_Printf( "ERROR: next block doesn't have proper back link\n" ); + } + if ( !block->tag && !block->next->tag ) { + Com_Printf( "ERROR: two consecutive free blocks\n" ); + } + } + + smallZoneBytes = 0; + smallZoneBlocks = 0; + for ( block = smallzone->blocklist.next ; ; block = block->next ) { + if ( block->tag ) { + smallZoneBytes += block->size; + smallZoneBlocks++; + } + + if ( block->next == &smallzone->blocklist ) { + break; // all blocks have been hit + } + } + + Com_Printf( "%8i bytes total hunk\n", s_hunkTotal ); + Com_Printf( "%8i bytes total zone\n", s_zoneTotal ); + Com_Printf( "\n" ); + Com_Printf( "%8i low mark\n", hunk_low.mark ); + Com_Printf( "%8i low permanent\n", hunk_low.permanent ); + if ( hunk_low.temp != hunk_low.permanent ) { + Com_Printf( "%8i low temp\n", hunk_low.temp ); + } + Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater ); + Com_Printf( "\n" ); + Com_Printf( "%8i high mark\n", hunk_high.mark ); + Com_Printf( "%8i high permanent\n", hunk_high.permanent ); + if ( hunk_high.temp != hunk_high.permanent ) { + Com_Printf( "%8i high temp\n", hunk_high.temp ); + } + Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater ); + Com_Printf( "\n" ); + Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent ); + unused = 0; + if ( hunk_low.tempHighwater > hunk_low.permanent ) { + unused += hunk_low.tempHighwater - hunk_low.permanent; + } + if ( hunk_high.tempHighwater > hunk_high.permanent ) { + unused += hunk_high.tempHighwater - hunk_high.permanent; + } + Com_Printf( "%8i unused highwater\n", unused ); + Com_Printf( "\n" ); + Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks ); + Com_Printf( " %8i bytes in dynamic botlib\n", botlibBytes ); + Com_Printf( " %8i bytes in dynamic renderer\n", rendererBytes ); + Com_Printf( " %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) ); + Com_Printf( " %8i bytes in small Zone memory\n", smallZoneBytes ); +} + +/* +=============== +Com_TouchMemory + +Touch all known used data to make sure it is paged in +=============== +*/ +void Com_TouchMemory( void ) { + int start, end; + int i, j; + int sum; + memblock_t *block; + + Z_CheckHeap(); + + start = Sys_Milliseconds(); + + sum = 0; + + j = hunk_low.permanent >> 2; + for ( i = 0 ; i < j ; i += 64 ) { // only need to touch each page + sum += ( (int *)s_hunkData )[i]; + } + + i = ( s_hunkTotal - hunk_high.permanent ) >> 2; + j = hunk_high.permanent >> 2; + for ( ; i < j ; i += 64 ) { // only need to touch each page + sum += ( (int *)s_hunkData )[i]; + } + + for ( block = mainzone->blocklist.next ; ; block = block->next ) { + if ( block->tag ) { + j = block->size >> 2; + for ( i = 0 ; i < j ; i += 64 ) { // only need to touch each page + sum += ( (int *)block )[i]; + } + } + if ( block->next == &mainzone->blocklist ) { + break; // all blocks have been hit + } + } + + end = Sys_Milliseconds(); + + Com_Printf( "Com_TouchMemory: %i msec\n", end - start ); +} + + + +/* +================= +Com_InitZoneMemory +================= +*/ +void Com_InitSmallZoneMemory( void ) { + s_smallZoneTotal = 512 * 1024; + // bk001205 - was malloc + smallzone = calloc( s_smallZoneTotal, 1 ); + if ( !smallzone ) { + Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / ( 1024 * 1024 ) ); + } + Z_ClearZone( smallzone, s_smallZoneTotal ); + + return; +} + +/* +void Com_InitZoneMemory( void ) { + cvar_t *cv; + s_smallZoneTotal = 512 * 1024; + smallzone = malloc( s_smallZoneTotal ); + if ( !smallzone ) { + Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024) ); + } + Z_ClearZone( smallzone, s_smallZoneTotal ); + + // allocate the random block zone + cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS, CVAR_LATCH | CVAR_ARCHIVE ); + + if ( cv->integer < 16 ) { + s_zoneTotal = 1024 * 1024 * 16; + } else { + s_zoneTotal = cv->integer * 1024 * 1024; + } + + mainzone = malloc( s_zoneTotal ); + if ( !mainzone ) { + Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / (1024*1024) ); + } + Z_ClearZone( mainzone, s_zoneTotal ); +} +*/ +void Com_InitZoneMemory( void ) { + cvar_t *cv; + // allocate the random block zone + cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS, CVAR_LATCH | CVAR_ARCHIVE ); + +#ifndef __MACOS__ //DAJ HOG + if ( cv->integer < 16 ) { + s_zoneTotal = 1024 * 1024 * 16; + } else +#endif + { + s_zoneTotal = cv->integer * 1024 * 1024; + } + + // bk001205 - was malloc + mainzone = calloc( s_zoneTotal, 1 ); + if ( !mainzone ) { + Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / ( 1024 * 1024 ) ); + } + Z_ClearZone( mainzone, s_zoneTotal ); + +} + +/* +================= +Hunk_Log +================= +*/ +void Hunk_Log( void ) { + hunkblock_t *block; + char buf[4096]; + int size, numBlocks; + + if ( !logfile || !FS_Initialized() ) { + return; + } + size = 0; + numBlocks = 0; + Com_sprintf( buf, sizeof( buf ), "\r\n================\r\nHunk log\r\n================\r\n" ); + FS_Write( buf, strlen( buf ), logfile ); + for ( block = hunkblocks ; block; block = block->next ) { +#ifdef HUNK_DEBUG + Com_sprintf( buf, sizeof( buf ), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label ); + FS_Write( buf, strlen( buf ), logfile ); +#endif + size += block->size; + numBlocks++; + } + Com_sprintf( buf, sizeof( buf ), "%d Hunk memory\r\n", size ); + FS_Write( buf, strlen( buf ), logfile ); + Com_sprintf( buf, sizeof( buf ), "%d hunk blocks\r\n", numBlocks ); + FS_Write( buf, strlen( buf ), logfile ); +} + +/* +================= +Hunk_SmallLog +================= +*/ +void Hunk_SmallLog( void ) { + hunkblock_t *block, *block2; + char buf[4096]; + int size, locsize, numBlocks; + + if ( !logfile || !FS_Initialized() ) { + return; + } + for ( block = hunkblocks ; block; block = block->next ) { + block->printed = qfalse; + } + size = 0; + numBlocks = 0; + Com_sprintf( buf, sizeof( buf ), "\r\n================\r\nHunk Small log\r\n================\r\n" ); + FS_Write( buf, strlen( buf ), logfile ); + for ( block = hunkblocks; block; block = block->next ) { + if ( block->printed ) { + continue; + } + locsize = block->size; + for ( block2 = block->next; block2; block2 = block2->next ) { + if ( block->line != block2->line ) { + continue; + } + if ( Q_stricmp( block->file, block2->file ) ) { + continue; + } + size += block2->size; + locsize += block2->size; + block2->printed = qtrue; + } +#ifdef HUNK_DEBUG + Com_sprintf( buf, sizeof( buf ), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label ); + FS_Write( buf, strlen( buf ), logfile ); +#endif + size += block->size; + numBlocks++; + } + Com_sprintf( buf, sizeof( buf ), "%d Hunk memory\r\n", size ); + FS_Write( buf, strlen( buf ), logfile ); + Com_sprintf( buf, sizeof( buf ), "%d hunk blocks\r\n", numBlocks ); + FS_Write( buf, strlen( buf ), logfile ); +} + +/* +================= +Com_InitZoneMemory +================= +*/ +void Com_InitHunkMemory( void ) { + cvar_t *cv; + int nMinAlloc; + char *pMsg = NULL; + + // make sure the file system has allocated and "not" freed any temp blocks + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if ( FS_LoadStack() != 0 ) { + Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero" ); + } + + // allocate the stack based hunk allocator + cv = Cvar_Get( "com_hunkMegs", DEF_COMHUNKMEGS, CVAR_LATCH | CVAR_ARCHIVE ); + + // if we are not dedicated min allocation is 56, otherwise min is 1 + if ( com_dedicated && com_dedicated->integer ) { + nMinAlloc = MIN_DEDICATED_COMHUNKMEGS; + pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n"; + } else { + nMinAlloc = MIN_COMHUNKMEGS; + pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n"; + } + + if ( cv->integer < nMinAlloc ) { + s_hunkTotal = 1024 * 1024 * nMinAlloc; + Com_Printf( pMsg, nMinAlloc, s_hunkTotal / ( 1024 * 1024 ) ); + } else { + s_hunkTotal = cv->integer * 1024 * 1024; + } + + + s_hunkData = malloc( s_hunkTotal + 31 ); + if ( !s_hunkData ) { + Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / ( 1024 * 1024 ) ); + } + // cacheline align + s_hunkData = ( byte * )( ( (int)s_hunkData + 31 ) & ~31 ); + Hunk_Clear(); + + Cmd_AddCommand( "meminfo", Com_Meminfo_f ); +#ifdef ZONE_DEBUG + Cmd_AddCommand( "zonelog", Z_LogHeap ); +#endif +#ifdef HUNK_DEBUG + Cmd_AddCommand( "hunklog", Hunk_Log ); + Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog ); +#endif +} + +/* +==================== +Hunk_MemoryRemaining +==================== +*/ +int Hunk_MemoryRemaining( void ) { + int low, high; + + low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp; + high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp; + + return s_hunkTotal - ( low + high ); +} + +/* +=================== +Hunk_SetMark + +The server calls this after the level and game VM have been loaded +=================== +*/ +void Hunk_SetMark( void ) { + hunk_low.mark = hunk_low.permanent; + hunk_high.mark = hunk_high.permanent; +} + +/* +================= +Hunk_ClearToMark + +The client calls this before starting a vid_restart or snd_restart +================= +*/ +void Hunk_ClearToMark( void ) { + hunk_low.permanent = hunk_low.temp = hunk_low.mark; + hunk_high.permanent = hunk_high.temp = hunk_high.mark; +} + +/* +================= +Hunk_CheckMark +================= +*/ +qboolean Hunk_CheckMark( void ) { + if ( hunk_low.mark || hunk_high.mark ) { + return qtrue; + } + return qfalse; +} + +void CL_ShutdownCGame( void ); +void CL_ShutdownUI( void ); +void SV_ShutdownGameProgs( void ); + +/* +================= +Hunk_Clear + +The server calls this before shutting down or loading a new map +================= +*/ +void Hunk_Clear( void ) { + +#ifndef DEDICATED + CL_ShutdownCGame(); + CL_ShutdownUI(); +#endif + SV_ShutdownGameProgs(); +#ifndef DEDICATED + CIN_CloseAllVideos(); +#endif + hunk_low.mark = 0; + hunk_low.permanent = 0; + hunk_low.temp = 0; + hunk_low.tempHighwater = 0; + + hunk_high.mark = 0; + hunk_high.permanent = 0; + hunk_high.temp = 0; + hunk_high.tempHighwater = 0; + + hunk_permanent = &hunk_low; + hunk_temp = &hunk_high; + + Cvar_Set( "com_hunkused", va( "%i", hunk_low.permanent + hunk_high.permanent ) ); + Com_Printf( "Hunk_Clear: reset the hunk ok\n" ); + VM_Clear(); // (SA) FIXME:TODO: was commented out in wolf +#ifdef HUNK_DEBUG + hunkblocks = NULL; +#endif +} + +static void Hunk_SwapBanks( void ) { + hunkUsed_t *swap; + + // can't swap banks if there is any temp already allocated + if ( hunk_temp->temp != hunk_temp->permanent ) { + return; + } + + // if we have a larger highwater mark on this side, start making + // our permanent allocations here and use the other side for temp + if ( hunk_temp->tempHighwater - hunk_temp->permanent > + hunk_permanent->tempHighwater - hunk_permanent->permanent ) { + swap = hunk_temp; + hunk_temp = hunk_permanent; + hunk_permanent = swap; + } +} + +/* +================= +Hunk_Alloc + +Allocate permanent (until the hunk is cleared) memory +================= +*/ +#ifdef HUNK_DEBUG +void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ) { +#else +void *Hunk_Alloc( int size, ha_pref preference ) { +#endif + void *buf; + + if ( s_hunkData == NULL ) { + Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" ); + } + + Hunk_SwapBanks(); + +#ifdef HUNK_DEBUG + size += sizeof( hunkblock_t ); +#endif + + // round to cacheline + size = ( size + 31 ) & ~31; + + if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) { +#ifdef HUNK_DEBUG + Hunk_Log(); + Hunk_SmallLog(); +#endif + Com_Error( ERR_DROP, "Hunk_Alloc failed on %i", size ); + } + + if ( hunk_permanent == &hunk_low ) { + buf = ( void * )( s_hunkData + hunk_permanent->permanent ); + hunk_permanent->permanent += size; + } else { + hunk_permanent->permanent += size; + buf = ( void * )( s_hunkData + s_hunkTotal - hunk_permanent->permanent ); + } + + hunk_permanent->temp = hunk_permanent->permanent; + + memset( buf, 0, size ); + +#ifdef HUNK_DEBUG + { + hunkblock_t *block; + + block = (hunkblock_t *) buf; + block->size = size - sizeof( hunkblock_t ); + block->file = file; + block->label = label; + block->line = line; + block->next = hunkblocks; + hunkblocks = block; + buf = ( (byte *) buf ) + sizeof( hunkblock_t ); + } +#endif + // Ridah, update the com_hunkused cvar in increments, so we don't update it too often, since this cvar call isn't very efficent + if ( ( hunk_low.permanent + hunk_high.permanent ) > com_hunkused->integer + 10000 ) { + Cvar_Set( "com_hunkused", va( "%i", hunk_low.permanent + hunk_high.permanent ) ); + } + + return buf; +} + +/* +================= +Hunk_AllocateTempMemory + +This is used by the file loading system. +Multiple files can be loaded in temporary memory. +When the files-in-use count reaches zero, all temp memory will be deleted +================= +*/ +void *Hunk_AllocateTempMemory( int size ) { + void *buf; + hunkHeader_t *hdr; + + // return a Z_Malloc'd block if the hunk has not been initialized + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if ( s_hunkData == NULL ) { + return Z_Malloc( size ); + } + + Hunk_SwapBanks(); + + size = ( ( size + 3 ) & ~3 ) + sizeof( hunkHeader_t ); + + if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) { + Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size ); + } + + if ( hunk_temp == &hunk_low ) { + buf = ( void * )( s_hunkData + hunk_temp->temp ); + hunk_temp->temp += size; + } else { + hunk_temp->temp += size; + buf = ( void * )( s_hunkData + s_hunkTotal - hunk_temp->temp ); + } + + if ( hunk_temp->temp > hunk_temp->tempHighwater ) { + hunk_temp->tempHighwater = hunk_temp->temp; + } + + hdr = (hunkHeader_t *)buf; + buf = ( void * )( hdr + 1 ); + + hdr->magic = HUNK_MAGIC; + hdr->size = size; + + // don't bother clearing, because we are going to load a file over it + return buf; +} + + +/* +================== +Hunk_FreeTempMemory +================== +*/ +void Hunk_FreeTempMemory( void *buf ) { + hunkHeader_t *hdr; + + // free with Z_Free if the hunk has not been initialized + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if ( s_hunkData == NULL ) { + Z_Free( buf ); + return; + } + + + hdr = ( (hunkHeader_t *)buf ) - 1; + if ( hdr->magic != HUNK_MAGIC ) { + Com_Error( ERR_FATAL, "Hunk_FreeTempMemory: bad magic" ); + } + + hdr->magic = HUNK_FREE_MAGIC; + + // this only works if the files are freed in stack order, + // otherwise the memory will stay around until Hunk_ClearTempMemory + if ( hunk_temp == &hunk_low ) { + if ( hdr == ( void * )( s_hunkData + hunk_temp->temp - hdr->size ) ) { + hunk_temp->temp -= hdr->size; + } else { + Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); + } + } else { + if ( hdr == ( void * )( s_hunkData + s_hunkTotal - hunk_temp->temp ) ) { + hunk_temp->temp -= hdr->size; + } else { + Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); + } + } +} + + +/* +================= +Hunk_ClearTempMemory + +The temp space is no longer needed. If we have left more +touched but unused memory on this side, have future +permanent allocs use this side. +================= +*/ +void Hunk_ClearTempMemory( void ) { + if ( s_hunkData != NULL ) { + hunk_temp->temp = hunk_temp->permanent; + } +} + +/* +================= +Hunk_Trash +================= +*/ +void Hunk_Trash( void ) { + int length, i, rnd; + char *buf, value; + + return; + + if ( s_hunkData == NULL ) { + return; + } + +#ifdef _DEBUG + Com_Error( ERR_DROP, "hunk trashed\n" ); + return; +#endif + + Cvar_Set( "com_jp", "1" ); + Hunk_SwapBanks(); + + if ( hunk_permanent == &hunk_low ) { + buf = ( void * )( s_hunkData + hunk_permanent->permanent ); + } else { + buf = ( void * )( s_hunkData + s_hunkTotal - hunk_permanent->permanent ); + } + length = hunk_permanent->permanent; + + if ( length > 0x7FFFF ) { + //randomly trash data within buf + rnd = random() * ( length - 0x7FFFF ); + value = 31; + for ( i = 0; i < 0x7FFFF; i++ ) { + value *= 109; + buf[rnd + i] ^= value; + } + } +} + +/* +=================================================================== + +EVENTS AND JOURNALING + +In addition to these events, .cfg files are also copied to the +journaled file +=================================================================== +*/ + +// bk001129 - here we go again: upped from 64 +#define MAX_PUSHED_EVENTS 256 +// bk001129 - init, also static +static int com_pushedEventsHead = 0; +static int com_pushedEventsTail = 0; +// bk001129 - static +static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; + +/* +================= +Com_InitJournaling +================= +*/ +void Com_InitJournaling( void ) { + Com_StartupVariable( "journal" ); + com_journal = Cvar_Get( "journal", "0", CVAR_INIT ); + if ( !com_journal->integer ) { + return; + } + + if ( com_journal->integer == 1 ) { +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'TEXT'; + _fcreator = 'WlfM'; + } +#endif + Com_Printf( "Journaling events\n" ); + com_journalFile = FS_FOpenFileWrite( "journal.dat" ); + com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" ); + } else if ( com_journal->integer == 2 ) { + Com_Printf( "Replaying journaled events\n" ); + FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue ); + FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, qtrue ); + } + + if ( !com_journalFile || !com_journalDataFile ) { + Cvar_Set( "com_journal", "0" ); + com_journalFile = 0; + com_journalDataFile = 0; + Com_Printf( "Couldn't open journal files\n" ); + } +} + +/* +================= +Com_GetRealEvent +================= +*/ +sysEvent_t Com_GetRealEvent( void ) { + int r; + sysEvent_t ev; + + // either get an event from the system or the journal file + if ( com_journal->integer == 2 ) { + r = FS_Read( &ev, sizeof( ev ), com_journalFile ); + if ( r != sizeof( ev ) ) { + Com_Error( ERR_FATAL, "Error reading from journal file" ); + } + if ( ev.evPtrLength ) { + ev.evPtr = Z_Malloc( ev.evPtrLength ); + r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) { + Com_Error( ERR_FATAL, "Error reading from journal file" ); + } + } + } else { + ev = Sys_GetEvent(); + + // write the journal value out if needed + if ( com_journal->integer == 1 ) { + r = FS_Write( &ev, sizeof( ev ), com_journalFile ); + if ( r != sizeof( ev ) ) { + Com_Error( ERR_FATAL, "Error writing to journal file" ); + } + if ( ev.evPtrLength ) { + r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) { + Com_Error( ERR_FATAL, "Error writing to journal file" ); + } + } + } + } + + return ev; +} + + +/* +================= +Com_InitPushEvent +================= +*/ +// bk001129 - added +void Com_InitPushEvent( void ) { + // clear the static buffer array + // this requires SE_NONE to be accepted as a valid but NOP event + memset( com_pushedEvents, 0, sizeof( com_pushedEvents ) ); + // reset counters while we are at it + // beware: GetEvent might still return an SE_NONE from the buffer + com_pushedEventsHead = 0; + com_pushedEventsTail = 0; +} + + +/* +================= +Com_PushEvent +================= +*/ +void Com_PushEvent( sysEvent_t *event ) { + sysEvent_t *ev; + static int printedWarning = 0; // bk001129 - init, bk001204 - explicit int + + ev = &com_pushedEvents[ com_pushedEventsHead & ( MAX_PUSHED_EVENTS - 1 ) ]; + + if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) { + + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) { + printedWarning = qtrue; + Com_Printf( "WARNING: Com_PushEvent overflow\n" ); + } + + if ( ev->evPtr ) { + Z_Free( ev->evPtr ); + } + com_pushedEventsTail++; + } else { + printedWarning = qfalse; + } + + *ev = *event; + com_pushedEventsHead++; +} + +/* +================= +Com_GetEvent +================= +*/ +sysEvent_t Com_GetEvent( void ) { + if ( com_pushedEventsHead > com_pushedEventsTail ) { + com_pushedEventsTail++; + return com_pushedEvents[ ( com_pushedEventsTail - 1 ) & ( MAX_PUSHED_EVENTS - 1 ) ]; + } + return Com_GetRealEvent(); +} + +/* +================= +Com_RunAndTimeServerPacket +================= +*/ +void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) { + int t1, t2, msec; + + t1 = 0; + + if ( com_speeds->integer ) { + t1 = Sys_Milliseconds(); + } + + SV_PacketEvent( *evFrom, buf ); + + if ( com_speeds->integer ) { + t2 = Sys_Milliseconds(); + msec = t2 - t1; + if ( com_speeds->integer == 3 ) { + Com_Printf( "SV_PacketEvent time: %i\n", msec ); + } + } +} + +/* +================= +Com_EventLoop + +Returns last event time +================= +*/ +int Com_EventLoop( void ) { + sysEvent_t ev; + netadr_t evFrom; + byte bufData[MAX_MSGLEN]; + msg_t buf; + + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + while ( 1 ) { + ev = Com_GetEvent(); + + // if no more events are available + if ( ev.evType == SE_NONE ) { + // manually send packet events for the loopback channel + while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) { + CL_PacketEvent( evFrom, &buf ); + } + + while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) { + // if the server just shut down, flush the events + if ( com_sv_running->integer ) { + Com_RunAndTimeServerPacket( &evFrom, &buf ); + } + } + + return ev.evTime; + } + + + switch ( ev.evType ) { + default: + // bk001129 - was ev.evTime + Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType ); + break; + case SE_NONE: + break; + case SE_KEY: + CL_KeyEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CHAR: + CL_CharEvent( ev.evValue ); + break; + case SE_MOUSE: + CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_JOYSTICK_AXIS: + CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CONSOLE: + Cbuf_AddText( (char *)ev.evPtr ); + Cbuf_AddText( "\n" ); + break; + case SE_PACKET: + // this cvar allows simulation of connections that + // drop a lot of packets. Note that loopback connections + // don't go through here at all. + if ( com_dropsim->value > 0 ) { + static int seed; + + if ( Q_random( &seed ) < com_dropsim->value ) { + break; // drop this packet + } + } + + evFrom = *(netadr_t *)ev.evPtr; + buf.cursize = ev.evPtrLength - sizeof( evFrom ); + + // we must copy the contents of the message out, because + // the event buffers are only large enough to hold the + // exact payload, but channel messages need to be large + // enough to hold fragment reassembly + if ( (unsigned)buf.cursize > buf.maxsize ) { + Com_Printf( "Com_EventLoop: oversize packet\n" ); + continue; + } + memcpy( buf.data, ( byte * )( (netadr_t *)ev.evPtr + 1 ), buf.cursize ); + if ( com_sv_running->integer ) { + Com_RunAndTimeServerPacket( &evFrom, &buf ); + } else { + CL_PacketEvent( evFrom, &buf ); + } + break; + } + + // free any block data + if ( ev.evPtr ) { + Z_Free( ev.evPtr ); + } + } + + return 0; // never reached +} + +/* +================ +Com_Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int Com_Milliseconds( void ) { + sysEvent_t ev; + + // get events and push them until we get a null event with the current time + do { + + ev = Com_GetRealEvent(); + if ( ev.evType != SE_NONE ) { + Com_PushEvent( &ev ); + } + } while ( ev.evType != SE_NONE ); + + return ev.evTime; +} + +//============================================================================ + +/* +============= +Com_Error_f + +Just throw a fatal error to +test error shutdown procedures +============= +*/ +static void Com_Error_f( void ) { + if ( Cmd_Argc() > 1 ) { + Com_Error( ERR_DROP, "Testing drop error" ); + } else { + Com_Error( ERR_FATAL, "Testing fatal error" ); + } +} + + +/* +============= +Com_Freeze_f + +Just freeze in place for a given number of seconds to test +error recovery +============= +*/ +static void Com_Freeze_f( void ) { + float s; + int start, now; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "freeze \n" ); + return; + } + s = atof( Cmd_Argv( 1 ) ); + + start = Com_Milliseconds(); + + while ( 1 ) { + now = Com_Milliseconds(); + if ( ( now - start ) * 0.001 > s ) { + break; + } + } +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +static void Com_Crash_f( void ) { + *( int * ) 0 = 0x12345678; +} + +qboolean CL_CDKeyValidate( const char *key, const char *checksum ); + +// TTimo: centralizing the cl_cdkey stuff after I discovered a buffer overflow problem with the dedicated server version +// not sure it's necessary to have different defaults for regular and dedicated, but I don't want to take the risk +#ifndef DEDICATED +char cl_cdkey[34] = " "; +#else +char cl_cdkey[34] = "123456789"; +#endif + +/* +================= +Com_ReadCDKey +================= +*/ +void Com_ReadCDKey( const char *filename ) { + fileHandle_t f; + char buffer[33]; + char fbuffer[MAX_OSPATH]; + + sprintf( fbuffer, "%s/rtcwkey", filename ); + + FS_SV_FOpenFileRead( fbuffer, &f ); + if ( !f ) { + Q_strncpyz( cl_cdkey, " ", 17 ); + return; + } + + Com_Memset( buffer, 0, sizeof( buffer ) ); + + FS_Read( buffer, 16, f ); + FS_FCloseFile( f ); + + if ( CL_CDKeyValidate( buffer, NULL ) ) { + Q_strncpyz( cl_cdkey, buffer, 17 ); + } else { + Q_strncpyz( cl_cdkey, " ", 17 ); + } +} + +/* +================= +Com_ReadCDKey +================= +*/ +void Com_AppendCDKey( const char *filename ) { + fileHandle_t f; + char buffer[33]; + char fbuffer[MAX_OSPATH]; + + sprintf( fbuffer, "%s/rtcwkey", filename ); + + FS_SV_FOpenFileRead( fbuffer, &f ); + if ( !f ) { + Q_strncpyz( &cl_cdkey[16], " ", 17 ); + return; + } + + Com_Memset( buffer, 0, sizeof( buffer ) ); + + FS_Read( buffer, 16, f ); + FS_FCloseFile( f ); + + if ( CL_CDKeyValidate( buffer, NULL ) ) { + strcat( &cl_cdkey[16], buffer ); + } else { + Q_strncpyz( &cl_cdkey[16], " ", 17 ); + } +} + +#ifndef DEDICATED // bk001204 +/* +================= +Com_WriteCDKey +================= +*/ +static void Com_WriteCDKey( const char *filename, const char *ikey ) { + fileHandle_t f; + char fbuffer[MAX_OSPATH]; + char key[17]; + + + sprintf( fbuffer, "%s/rtcwkey", filename ); + + + Q_strncpyz( key, ikey, 17 ); + + if ( !CL_CDKeyValidate( key, NULL ) ) { + return; + } + +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'TEXT'; + _fcreator = 'WlfM'; + } +#endif + + f = FS_SV_FOpenFileWrite( fbuffer ); + if ( !f ) { + Com_Printf( "Couldn't write %s.\n", filename ); + return; + } + + FS_Write( key, 16, f ); + + FS_Printf( f, "\n// generated by RTCW, do not modify\r\n" ); + FS_Printf( f, "// Do not give this file to ANYONE.\r\n" ); +#ifdef __MACOS__ + FS_Printf( f, "// Aspyr will NOT ask you to send this file to them.\r\n" ); +#else + FS_Printf( f, "// id Software and Activision will NOT ask you to send this file to them.\r\n" ); +#endif + FS_FCloseFile( f ); +} +#endif + +void Com_SetRecommended() { + cvar_t *cv; + qboolean goodVideo; + qboolean goodCPU; + // will use this for recommended settings as well.. do i outside the lower check so it gets done even with command line stuff + cv = Cvar_Get( "r_highQualityVideo", "1", CVAR_ARCHIVE ); + goodVideo = ( cv && cv->integer ); + goodCPU = Sys_GetHighQualityCPU(); + + if ( goodVideo && goodCPU ) { + Com_Printf( "Found high quality video and CPU\n" ); + Cbuf_AddText( "exec highVidhighCPU.cfg\n" ); + } else if ( goodVideo && !goodCPU ) { + Cbuf_AddText( "exec highVidlowCPU.cfg\n" ); + Com_Printf( "Found high quality video and low quality CPU\n" ); + } else if ( !goodVideo && goodCPU ) { + Cbuf_AddText( "exec lowVidhighCPU.cfg\n" ); + Com_Printf( "Found low quality video and high quality CPU\n" ); + } else { + Cbuf_AddText( "exec lowVidlowCPU.cfg\n" ); + Com_Printf( "Found low quality video and low quality CPU\n" ); + } + +// (SA) set the cvar so the menu will reflect this on first run +// Cvar_Set("ui_glCustom", "999"); // 'recommended' +} + +/* +================= +Com_Init +================= +*/ +void Com_Init( char *commandLine ) { + char *s; + // TTimo gcc warning: variable `safeMode' might be clobbered by `longjmp' or `vfork' + volatile qboolean safeMode = qtrue; + + Com_Printf( "%s %s %s\n", Q3_VERSION, CPUSTRING, __DATE__ ); + + if ( setjmp( abortframe ) ) { + Sys_Error( "Error during initialization" ); + } + + // bk001129 - do this before anything else decides to push events + Com_InitPushEvent(); + + Com_InitSmallZoneMemory(); + Cvar_Init(); + + // prepare enough of the subsystems to handle + // cvar and command buffer management + Com_ParseCommandLine( commandLine ); + + Swap_Init(); + Cbuf_Init(); + + Com_InitZoneMemory(); + Cmd_Init(); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + // get the developer cvar set as early as possible + Com_StartupVariable( "developer" ); + + // done early so bind command exists + CL_InitKeyCommands(); + + FS_InitFilesystem(); + + Com_InitJournaling(); + + // DHM - Nerve +#ifndef UPDATE_SERVER + Cbuf_AddText( "exec default.cfg\n" ); + Cbuf_AddText( "exec language.cfg\n" ); // NERVE - SMF + + // skip the q3config.cfg if "safe" is on the command line + if ( !Com_SafeMode() ) { + safeMode = qfalse; + Cbuf_AddText( "exec wolfconfig_mp.cfg\n" ); + } + + Cbuf_AddText( "exec autoexec.cfg\n" ); +#endif + + Cbuf_Execute(); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + // get dedicated here for proper hunk megs initialization +#ifdef UPDATE_SERVER + com_dedicated = Cvar_Get( "dedicated", "1", CVAR_LATCH ); +#elif DEDICATED + // TTimo: default to internet dedicated, not LAN dedicated + com_dedicated = Cvar_Get( "dedicated", "2", CVAR_ROM ); +#else + com_dedicated = Cvar_Get( "dedicated", "0", CVAR_LATCH ); +#endif + // allocate the stack based hunk allocator + Com_InitHunkMemory(); + + // if any archived cvars are modified after this, we will trigger a writing + // of the config file + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + // + // init commands and vars + // + com_maxfps = Cvar_Get( "com_maxfps", "85", CVAR_ARCHIVE | CVAR_LATCH ); + com_blood = Cvar_Get( "com_blood", "1", CVAR_ARCHIVE ); + + com_developer = Cvar_Get( "developer", "0", CVAR_TEMP ); + com_logfile = Cvar_Get( "logfile", "0", CVAR_TEMP ); + + com_timescale = Cvar_Get( "timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO ); + com_fixedtime = Cvar_Get( "fixedtime", "0", CVAR_CHEAT ); + com_showtrace = Cvar_Get( "com_showtrace", "0", CVAR_CHEAT ); + com_dropsim = Cvar_Get( "com_dropsim", "0", CVAR_CHEAT ); + com_viewlog = Cvar_Get( "viewlog", "0", CVAR_CHEAT ); + com_speeds = Cvar_Get( "com_speeds", "0", 0 ); + com_timedemo = Cvar_Get( "timedemo", "0", CVAR_CHEAT ); + com_cameraMode = Cvar_Get( "com_cameraMode", "0", CVAR_CHEAT ); + + cl_paused = Cvar_Get( "cl_paused", "0", CVAR_ROM ); + sv_paused = Cvar_Get( "sv_paused", "0", CVAR_ROM ); + com_sv_running = Cvar_Get( "sv_running", "0", CVAR_ROM ); + com_cl_running = Cvar_Get( "cl_running", "0", CVAR_ROM ); + com_buildScript = Cvar_Get( "com_buildScript", "0", 0 ); + + com_introPlayed = Cvar_Get( "com_introplayed", "0", CVAR_ARCHIVE ); + com_recommendedSet = Cvar_Get( "com_recommendedSet", "0", CVAR_ARCHIVE ); + +#if defined( _WIN32 ) && defined( _DEBUG ) + com_noErrorInterrupt = Cvar_Get( "com_noErrorInterrupt", "0", 0 ); +#endif + + com_hunkused = Cvar_Get( "com_hunkused", "0", 0 ); + + if ( com_dedicated->integer ) { + if ( !com_viewlog->integer ) { + Cvar_Set( "viewlog", "1" ); + } + } + + if ( com_developer && com_developer->integer ) { + Cmd_AddCommand( "error", Com_Error_f ); + Cmd_AddCommand( "crash", Com_Crash_f ); + Cmd_AddCommand( "freeze", Com_Freeze_f ); + } + Cmd_AddCommand( "quit", Com_Quit_f ); + Cmd_AddCommand( "changeVectors", MSG_ReportChangeVectors_f ); + Cmd_AddCommand( "writeconfig", Com_WriteConfig_f ); + + s = va( "%s %s %s", Q3_VERSION, CPUSTRING, __DATE__ ); + com_version = Cvar_Get( "version", s, CVAR_ROM | CVAR_SERVERINFO ); + + Sys_Init(); + Netchan_Init( Com_Milliseconds() & 0xffff ); // pick a port value that should be nice and random + VM_Init(); + SV_Init(); + + com_dedicated->modified = qfalse; + if ( !com_dedicated->integer ) { + CL_Init(); + Sys_ShowConsole( com_viewlog->integer, qfalse ); + } + + // set com_frameTime so that if a map is started on the + // command line it will still be able to count on com_frameTime + // being random enough for a serverid + com_frameTime = Com_Milliseconds(); + + // add + commands from command line + if ( !Com_AddStartupCommands() ) { + // if the user didn't give any commands, run default action + } + + // start in full screen ui mode + Cvar_Set( "r_uiFullScreen", "1" ); + + CL_StartHunkUsers(); + + // delay this so potential wicked3d dll can find a wolf window + if ( !com_dedicated->integer ) { + Sys_ShowConsole( com_viewlog->integer, qfalse ); + } + + // NERVE - SMF - force recommendedSet and don't do vid_restart if in safe mode + if ( !com_recommendedSet->integer && !safeMode ) { + Com_SetRecommended(); + Cbuf_ExecuteText( EXEC_APPEND, "vid_restart\n" ); + } + Cvar_Set( "com_recommendedSet", "1" ); + + if ( !com_dedicated->integer ) { + Cbuf_AddText( "cinematic gmlogo.RoQ\n" ); + if ( !com_introPlayed->integer ) { + Cvar_Set( com_introPlayed->name, "1" ); + Cvar_Set( "nextmap", "cinematic wolfintro.RoQ" ); + } + } + + com_fullyInitialized = qtrue; + Com_Printf( "--- Common Initialization Complete ---\n" ); +} + +//================================================================== + +void Com_WriteConfigToFile( const char *filename ) { + fileHandle_t f; + +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'TEXT'; + _fcreator = 'R*ch'; + } +#endif + f = FS_FOpenFileWrite( filename ); + if ( !f ) { + Com_Printf( "Couldn't write %s.\n", filename ); + return; + } + + FS_Printf( f, "// generated by RTCW, do not modify\n" ); + Key_WriteBindings( f ); + Cvar_WriteVariables( f ); + FS_FCloseFile( f ); +} + + +/* +=============== +Com_WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void Com_WriteConfiguration( void ) { +#ifndef DEDICATED // bk001204 + cvar_t *fs; +#endif + // if we are quiting without fully initializing, make sure + // we don't write out anything + if ( !com_fullyInitialized ) { + return; + } + + if ( !( cvar_modifiedFlags & CVAR_ARCHIVE ) ) { + return; + } + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + Com_WriteConfigToFile( "wolfconfig_mp.cfg" ); + + // bk001119 - tentative "not needed for dedicated" +#ifndef DEDICATED + fs = Cvar_Get( "fs_game", "", CVAR_INIT | CVAR_SYSTEMINFO ); + if ( UI_usesUniqueCDKey() && fs && fs->string[0] != 0 ) { + Com_WriteCDKey( fs->string, &cl_cdkey[16] ); + } else { + Com_WriteCDKey( "main", cl_cdkey ); + } +#endif +} + + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +void Com_WriteConfig_f( void ) { + char filename[MAX_QPATH]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: writeconfig \n" ); + return; + } + + Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + Com_Printf( "Writing %s.\n", filename ); + Com_WriteConfigToFile( filename ); +} + +/* +================ +Com_ModifyMsec +================ +*/ +int Com_ModifyMsec( int msec ) { + int clampTime; + + // + // modify time for debugging values + // + if ( com_fixedtime->integer ) { + msec = com_fixedtime->integer; + } else if ( com_timescale->value ) { + msec *= com_timescale->value; +// } else if (com_cameraMode->integer) { +// msec *= com_timescale->value; + } + + // don't let it scale below 1 msec + if ( msec < 1 && com_timescale->value ) { + msec = 1; + } + + if ( com_dedicated->integer ) { + // dedicated servers don't want to clamp for a much longer + // period, because it would mess up all the client's views + // of time. + if ( msec > 500 && msec < 500000 ) { + Com_Printf( "Hitch warning: %i msec frame time\n", msec ); + } + clampTime = 5000; + } else + if ( !com_sv_running->integer ) { + // clients of remote servers do not want to clamp time, because + // it would skew their view of the server's time temporarily + clampTime = 5000; + } else { + // for local single player gaming + // we may want to clamp the time to prevent players from + // flying off edges when something hitches. + clampTime = 200; + } + + if ( msec > clampTime ) { + msec = clampTime; + } + + return msec; +} + +/* +================= +Com_Frame +================= +*/ +void Com_Frame( void ) { + + int msec, minMsec; + static int lastTime; + int key; + + int timeBeforeFirstEvents; + int timeBeforeServer; + int timeBeforeEvents; + int timeBeforeClient; + int timeAfter; + + + + + + if ( setjmp( abortframe ) ) { + return; // an ERR_DROP was thrown + } + + // bk001204 - init to zero. + // also: might be clobbered by `longjmp' or `vfork' + timeBeforeFirstEvents = 0; + timeBeforeServer = 0; + timeBeforeEvents = 0; + timeBeforeClient = 0; + timeAfter = 0; + + + // old net chan encryption key + key = 0x87243987; + + // DHM - Nerve :: Don't write config on Update Server +#ifndef UPDATE_SERVER + // write config file if anything changed + Com_WriteConfiguration(); +#endif + + // if "viewlog" has been modified, show or hide the log console + if ( com_viewlog->modified ) { + if ( !com_dedicated->value ) { + Sys_ShowConsole( com_viewlog->integer, qfalse ); + } + com_viewlog->modified = qfalse; + } + + // + // main event loop + // + if ( com_speeds->integer ) { + timeBeforeFirstEvents = Sys_Milliseconds(); + } + + // we may want to spin here if things are going too fast + if ( !com_dedicated->integer && com_maxfps->integer > 0 && !com_timedemo->integer ) { + minMsec = 1000 / com_maxfps->integer; + } else { + minMsec = 1; + } + do { + com_frameTime = Com_EventLoop(); + if ( lastTime > com_frameTime ) { + lastTime = com_frameTime; // possible on first frame + } + msec = com_frameTime - lastTime; + } while ( msec < minMsec ); + Cbuf_Execute(); + + lastTime = com_frameTime; + + // mess with msec if needed + com_frameMsec = msec; + msec = Com_ModifyMsec( msec ); + + // + // server side + // + if ( com_speeds->integer ) { + timeBeforeServer = Sys_Milliseconds(); + } + + SV_Frame( msec ); + + // if "dedicated" has been modified, start up + // or shut down the client system. + // Do this after the server may have started, + // but before the client tries to auto-connect + if ( com_dedicated->modified ) { + // get the latched value + Cvar_Get( "dedicated", "0", 0 ); + com_dedicated->modified = qfalse; + if ( !com_dedicated->integer ) { + CL_Init(); + Sys_ShowConsole( com_viewlog->integer, qfalse ); + } else { + CL_Shutdown(); + Sys_ShowConsole( 1, qtrue ); + } + } + + // + // client system + // + if ( !com_dedicated->integer ) { + // + // run event loop a second time to get server to client packets + // without a frame of latency + // + if ( com_speeds->integer ) { + timeBeforeEvents = Sys_Milliseconds(); + } + Com_EventLoop(); + Cbuf_Execute(); + + // + // client side + // + if ( com_speeds->integer ) { + timeBeforeClient = Sys_Milliseconds(); + } + + CL_Frame( msec ); + + if ( com_speeds->integer ) { + timeAfter = Sys_Milliseconds(); + } + } + + // + // report timing information + // + if ( com_speeds->integer ) { + int all, sv, ev, cl; + + all = timeAfter - timeBeforeServer; + sv = timeBeforeEvents - timeBeforeServer; + ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents; + cl = timeAfter - timeBeforeClient; + sv -= time_game; + cl -= time_frontend + time_backend; + + Com_Printf( "frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n", + com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend ); + } + + // + // trace optimization tracking + // + if ( com_showtrace->integer ) { + + extern int c_traces, c_brush_traces, c_patch_traces; + extern int c_pointcontents; + + Com_Printf( "%4i traces (%ib %ip) %4i points\n", c_traces, + c_brush_traces, c_patch_traces, c_pointcontents ); + c_traces = 0; + c_brush_traces = 0; + c_patch_traces = 0; + c_pointcontents = 0; + } + + // old net chan encryption key + key = lastTime * 0x87243987; + + com_frameNumber++; +} + +/* +================= +Com_Shutdown +================= +*/ +void Com_Shutdown( void ) { + if ( logfile ) { + FS_FCloseFile( logfile ); + logfile = 0; + } + + if ( com_journalFile ) { + FS_FCloseFile( com_journalFile ); + com_journalFile = 0; + } + +} + +#if !( defined __linux__ || defined __FreeBSD__ ) // r010123 - include FreeBSD +#if ( ( !id386 ) && ( !defined __i386__ ) ) // rcg010212 - for PPC + +void Com_Memcpy( void* dest, const void* src, const size_t count ) { + memcpy( dest, src, count ); +} + +void Com_Memset( void* dest, const int val, const size_t count ) { + memset( dest, val, count ); +} + +#else + +typedef enum +{ + PRE_READ, // prefetch assuming that buffer is used for reading only + PRE_WRITE, // prefetch assuming that buffer is used for writing only + PRE_READ_WRITE // prefetch assuming that buffer is used for both reading and writing +} e_prefetch; + +void Com_Prefetch( const void *s, const unsigned int bytes, e_prefetch type ); + +#define EMMS_INSTRUCTION __asm emms + +void _copyDWord( unsigned int* dest, const unsigned int constant, const unsigned int count ) { + __asm + { + mov edx,dest + mov eax,constant + mov ecx,count + and ecx,~7 + jz padding + sub ecx,8 + jmp loopu + align 16 +loopu: + test [edx + ecx * 4 + 28],ebx // fetch next block destination to L1 cache + mov [edx + ecx * 4 + 0],eax + mov [edx + ecx * 4 + 4],eax + mov [edx + ecx * 4 + 8],eax + mov [edx + ecx * 4 + 12],eax + mov [edx + ecx * 4 + 16],eax + mov [edx + ecx * 4 + 20],eax + mov [edx + ecx * 4 + 24],eax + mov [edx + ecx * 4 + 28],eax + sub ecx,8 + jge loopu +padding: mov ecx,count + mov ebx,ecx + and ecx,7 + jz outta + and ebx,~7 + lea edx,[edx + ebx * 4] // advance dest pointer + test [edx + 0],eax // fetch destination to L1 cache + cmp ecx,4 + jl skip4 + mov [edx + 0],eax + mov [edx + 4],eax + mov [edx + 8],eax + mov [edx + 12],eax + add edx,16 + sub ecx,4 +skip4: cmp ecx,2 + jl skip2 + mov [edx + 0],eax + mov [edx + 4],eax + add edx,8 + sub ecx,2 +skip2: cmp ecx,1 + jl outta + mov [edx + 0],eax +outta: + } +} + +// optimized memory copy routine that handles all alignment +// cases and block sizes efficiently +void Com_Memcpy( void* dest, const void* src, const size_t count ) { + Com_Prefetch( src, count, PRE_READ ); + __asm + { + push edi + push esi + mov ecx,count + cmp ecx,0 // count = 0 check (just to be on the safe side) + je outta + mov edx,dest + mov ebx,src + cmp ecx,32 // padding only? + jl padding + + mov edi,ecx + and edi,~31 // edi = count&~31 + sub edi,32 + + align 16 +loopMisAligned: + mov eax,[ebx + edi + 0 + 0 * 8] + mov esi,[ebx + edi + 4 + 0 * 8] + mov [edx + edi + 0 + 0 * 8],eax + mov [edx + edi + 4 + 0 * 8],esi + mov eax,[ebx + edi + 0 + 1 * 8] + mov esi,[ebx + edi + 4 + 1 * 8] + mov [edx + edi + 0 + 1 * 8],eax + mov [edx + edi + 4 + 1 * 8],esi + mov eax,[ebx + edi + 0 + 2 * 8] + mov esi,[ebx + edi + 4 + 2 * 8] + mov [edx + edi + 0 + 2 * 8],eax + mov [edx + edi + 4 + 2 * 8],esi + mov eax,[ebx + edi + 0 + 3 * 8] + mov esi,[ebx + edi + 4 + 3 * 8] + mov [edx + edi + 0 + 3 * 8],eax + mov [edx + edi + 4 + 3 * 8],esi + sub edi,32 + jge loopMisAligned + + mov edi,ecx + and edi,~31 + add ebx,edi // increase src pointer + add edx,edi // increase dst pointer + and ecx,31 // new count + jz outta // if count = 0, get outta here + +padding: + cmp ecx,16 + jl skip16 + mov eax,dword ptr [ebx] + mov dword ptr [edx],eax + mov eax,dword ptr [ebx + 4] + mov dword ptr [edx + 4],eax + mov eax,dword ptr [ebx + 8] + mov dword ptr [edx + 8],eax + mov eax,dword ptr [ebx + 12] + mov dword ptr [edx + 12],eax + sub ecx,16 + add ebx,16 + add edx,16 +skip16: + cmp ecx,8 + jl skip8 + mov eax,dword ptr [ebx] + mov dword ptr [edx],eax + mov eax,dword ptr [ebx + 4] + sub ecx,8 + mov dword ptr [edx + 4],eax + add ebx,8 + add edx,8 +skip8: + cmp ecx,4 + jl skip4 + mov eax,dword ptr [ebx] // here 4-7 bytes + add ebx,4 + sub ecx,4 + mov dword ptr [edx],eax + add edx,4 +skip4: // 0-3 remaining bytes + cmp ecx,2 + jl skip2 + mov ax,word ptr [ebx] // two bytes + cmp ecx,3 // less than 3? + mov word ptr [edx],ax + jl outta + mov al,byte ptr [ebx + 2] // last byte + mov byte ptr [edx + 2],al + jmp outta +skip2: + cmp ecx,1 + jl outta + mov al,byte ptr [ebx] + mov byte ptr [edx],al +outta: + pop esi + pop edi + } +} + +void Com_Memset( void* dest, const int val, const size_t count ) { + unsigned int fillval; + + if ( count < 8 ) { + __asm + { + mov edx,dest + mov eax, val + mov ah,al + mov ebx,eax + and ebx, 0xffff + shl eax,16 + add eax,ebx // eax now contains pattern + mov ecx,count + cmp ecx,4 + jl skip4 + mov [edx],eax // copy first dword + add edx,4 + sub ecx,4 +skip4: cmp ecx,2 + jl skip2 + mov word ptr [edx],ax // copy 2 bytes + add edx,2 + sub ecx,2 +skip2: cmp ecx,0 + je skip1 + mov byte ptr [edx],al // copy single byte +skip1: + } + return; + } + + fillval = val; + + fillval = fillval | ( fillval << 8 ); + fillval = fillval | ( fillval << 16 ); // fill dword with 8-bit pattern + + _copyDWord( (unsigned int*)( dest ),fillval, count / 4 ); + + __asm // padding of 0-3 bytes + { + mov ecx,count + mov eax,ecx + and ecx,3 + jz skipA + and eax,~3 + mov ebx,dest + add ebx,eax + mov eax,fillval + cmp ecx,2 + jl skipB + mov word ptr [ebx],ax + cmp ecx,2 + je skipA + mov byte ptr [ebx + 2],al + jmp skipA +skipB: + cmp ecx,0 + je skipA + mov byte ptr [ebx],al +skipA: + } +} + +qboolean Com_Memcmp( const void *src0, const void *src1, const unsigned int count ) { + unsigned int i; + // MMX version anyone? + + if ( count >= 16 ) { + unsigned int *dw = (unsigned int*)( src0 ); + unsigned int *sw = (unsigned int*)( src1 ); + + unsigned int nm2 = count / 16; + for ( i = 0; i < nm2; i += 4 ) + { + unsigned int tmp = ( dw[i + 0] - sw[i + 0] ) | ( dw[i + 1] - sw[i + 1] ) | + ( dw[i + 2] - sw[i + 2] ) | ( dw[i + 3] - sw[i + 3] ); + if ( tmp ) { + return qfalse; + } + } + } + if ( count & 15 ) { + byte *d = (byte*)src0; + byte *s = (byte*)src1; + for ( i = count & 0xfffffff0; i < count; i++ ) + if ( d[i] != s[i] ) { + return qfalse; + } + } + + return qtrue; +} + +void Com_Prefetch( const void *s, const unsigned int bytes, e_prefetch type ) { + // write buffer prefetching is performed only if + // the processor benefits from it. Read and read/write + // prefetching is always performed. + + switch ( type ) + { + case PRE_WRITE: break; + case PRE_READ: + case PRE_READ_WRITE: + + __asm + { + mov ebx,s + mov ecx,bytes + cmp ecx,4096 // clamp to 4kB + jle skipClamp + mov ecx,4096 +skipClamp: + add ecx,0x1f + shr ecx,5 // number of cache lines + jz skip + jmp loopie + + align 16 +loopie: test byte ptr [ebx],al + add ebx,32 + dec ecx + jnz loopie +skip: + } + + break; + } +} + +#endif +#endif // bk001208 - memset/memcpy assembly, Q_acos needed (RC4) +//------------------------------------------------------------------------ + + +/* +===================== +Q_acos + +the msvc acos doesn't always return a value between -PI and PI: + +int i; +i = 1065353246; +acos(*(float*) &i) == -1.#IND0 + + This should go in q_math but it is too late to add new traps + to game and ui +===================== +*/ +float Q_acos( float c ) { + float angle; + + angle = acos( c ); + + if ( angle > M_PI ) { + return (float)M_PI; + } + if ( angle < -M_PI ) { + return (float)M_PI; + } + return angle; +} + +/* +=========================================== +command line completion +=========================================== +*/ + +/* +================== +Field_Clear +================== +*/ +void Field_Clear( field_t *edit ) { + memset( edit->buffer, 0, MAX_EDIT_LINE ); + edit->cursor = 0; + edit->scroll = 0; +} + +static const char *completionString; +static char shortestMatch[MAX_TOKEN_CHARS]; +static int matchCount; +// field we are working on, passed to Field_CompleteCommand (&g_consoleCommand for instance) +static field_t *completionField; + +/* +=============== +FindMatches + +=============== +*/ +static void FindMatches( const char *s ) { + int i; + + if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) { + return; + } + matchCount++; + if ( matchCount == 1 ) { + Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) ); + return; + } + + // cut shortestMatch to the amount common with s + for ( i = 0 ; s[i] ; i++ ) { + if ( tolower( shortestMatch[i] ) != tolower( s[i] ) ) { + shortestMatch[i] = 0; + } + } +} + +/* +=============== +PrintMatches + +=============== +*/ +static void PrintMatches( const char *s ) { + if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) { + Com_Printf( " %s\n", s ); + } +} + +static void keyConcatArgs( void ) { + int i; + char *arg; + + for ( i = 1 ; i < Cmd_Argc() ; i++ ) { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); + arg = Cmd_Argv( i ); + while ( *arg ) { + if ( *arg == ' ' ) { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), "\"" ); + break; + } + arg++; + } + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), Cmd_Argv( i ) ); + if ( *arg == ' ' ) { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), "\"" ); + } + } +} + +static void ConcatRemaining( const char *src, const char *start ) { + char *str; + + str = strstr( src, start ); + if ( !str ) { + keyConcatArgs(); + return; + } + + str += strlen( start ); + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), str ); +} + +/* +=============== +Field_CompleteCommand + +perform Tab expansion +NOTE TTimo this was originally client code only + moved to common code when writing tty console for *nix dedicated server +=============== +*/ +void Field_CompleteCommand( field_t *field ) { + field_t temp; + completionField = field; + + // only look at the first token for completion purposes + Cmd_TokenizeString( completionField->buffer ); + + completionString = Cmd_Argv( 0 ); + if ( completionString[0] == '\\' || completionString[0] == '/' ) { + completionString++; + } + matchCount = 0; + shortestMatch[0] = 0; + + if ( strlen( completionString ) == 0 ) { + return; + } + + Cmd_CommandCompletion( FindMatches ); + Cvar_CommandCompletion( FindMatches ); + + if ( matchCount == 0 ) { + return; // no matches + } + + Com_Memcpy( &temp, completionField, sizeof( field_t ) ); + + if ( matchCount == 1 ) { + Com_sprintf( completionField->buffer, sizeof( completionField->buffer ), "\\%s", shortestMatch ); + if ( Cmd_Argc() == 1 ) { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); + } else { + ConcatRemaining( temp.buffer, completionString ); + } + completionField->cursor = strlen( completionField->buffer ); + return; + } + + // multiple matches, complete to shortest + Com_sprintf( completionField->buffer, sizeof( completionField->buffer ), "\\%s", shortestMatch ); + completionField->cursor = strlen( completionField->buffer ); + ConcatRemaining( temp.buffer, completionString ); + + Com_Printf( "]%s\n", completionField->buffer ); + + // run through again, printing matches + Cmd_CommandCompletion( PrintMatches ); + Cvar_CommandCompletion( PrintMatches ); +} diff --git a/src/qcommon/cvar.c b/src/qcommon/cvar.c new file mode 100644 index 0000000..ee8f924 --- /dev/null +++ b/src/qcommon/cvar.c @@ -0,0 +1,953 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// cvar.c -- dynamic variable tracking + +#include "../game/q_shared.h" +#include "qcommon.h" + +cvar_t *cvar_vars; +cvar_t *cvar_cheats; +int cvar_modifiedFlags; + +#define MAX_CVARS 1024 +cvar_t cvar_indexes[MAX_CVARS]; +int cvar_numIndexes; + +#define FILE_HASH_SIZE 256 +static cvar_t* hashTable[FILE_HASH_SIZE]; + +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ); + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + if ( !fname ) { + Com_Error( ERR_DROP, "null name in generateHashValue" ); //gjd + } + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( FILE_HASH_SIZE - 1 ); + return hash; +} + +/* +============ +Cvar_ValidateString +============ +*/ +static qboolean Cvar_ValidateString( const char *s ) { + if ( !s ) { + return qfalse; + } + if ( strchr( s, '\\' ) ) { + return qfalse; + } + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* +============ +Cvar_FindVar +============ +*/ +static cvar_t *Cvar_FindVar( const char *var_name ) { + cvar_t *var; + long hash; + + hash = generateHashValue( var_name ); + + for ( var = hashTable[hash] ; var ; var = var->hashNext ) { + if ( !Q_stricmp( var_name, var->name ) ) { + return var; + } + } + + return NULL; +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar( var_name ); + if ( !var ) { + return 0; + } + return var->value; +} + + +/* +============ +Cvar_VariableIntegerValue +============ +*/ +int Cvar_VariableIntegerValue( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar( var_name ); + if ( !var ) { + return 0; + } + return var->integer; +} + + +/* +============ +Cvar_VariableString +============ +*/ +char *Cvar_VariableString( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar( var_name ); + if ( !var ) { + return ""; + } + return var->string; +} + + +/* +============ +Cvar_VariableStringBuffer +============ +*/ +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + cvar_t *var; + + var = Cvar_FindVar( var_name ); + if ( !var ) { + *buffer = 0; + } else { + Q_strncpyz( buffer, var->string, bufsize ); + } +} + + +/* +============ +Cvar_CommandCompletion +============ +*/ +void Cvar_CommandCompletion( void ( *callback )(const char *s) ) { + cvar_t *cvar; + + for ( cvar = cvar_vars ; cvar ; cvar = cvar->next ) { + callback( cvar->name ); + } +} + +/* +============ +Cvar_ClearForeignCharacters +some cvar values need to be safe from foreign characters +============ +*/ +char *Cvar_ClearForeignCharacters( const char *value ) { + static char clean[MAX_CVAR_VALUE_STRING]; + int i,j; + + j = 0; + for ( i = 0; value[i] != '\0'; i++ ) + { + if ( !( value[i] & 128 ) ) { + clean[j] = value[i]; + j++; + } + } + clean[j] = '\0'; + + return clean; +} + +/* +============ +Cvar_Get + +If the variable already exists, the value will not be set unless CVAR_ROM +The flags will be or'ed in if the variable exists. +============ +*/ +cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) { + cvar_t *var; + long hash; + + if ( !var_name || !var_value ) { + Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" ); + } + + if ( !Cvar_ValidateString( var_name ) ) { + Com_Printf( "invalid cvar name string: %s\n", var_name ); + var_name = "BADNAME"; + } + +#if 0 // FIXME: values with backslash happen + if ( !Cvar_ValidateString( var_value ) ) { + Com_Printf( "invalid cvar value string: %s\n", var_value ); + var_value = "BADVALUE"; + } +#endif + + var = Cvar_FindVar( var_name ); + if ( var ) { + // if the C code is now specifying a variable that the user already + // set a value for, take the new value as the reset value + if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED ) + && var_value[0] ) { + var->flags &= ~CVAR_USER_CREATED; + Z_Free( var->resetString ); + var->resetString = CopyString( var_value ); + + // ZOID--needs to be set so that cvars the game sets as + // SERVERINFO get sent to clients + cvar_modifiedFlags |= flags; + } + + var->flags |= flags; + // only allow one non-empty reset string without a warning + if ( !var->resetString[0] ) { + // we don't have a reset string yet + Z_Free( var->resetString ); + var->resetString = CopyString( var_value ); + } else if ( var_value[0] && strcmp( var->resetString, var_value ) ) { + Com_DPrintf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", + var_name, var->resetString, var_value ); + } + // if we have a latched string, take that value now + if ( var->latchedString ) { + char *s; + + s = var->latchedString; + var->latchedString = NULL; // otherwise cvar_set2 would free it + Cvar_Set2( var_name, s, qtrue ); + Z_Free( s ); + } + + // TTimo + // if CVAR_USERINFO was toggled on for an existing cvar, check wether the value needs to be cleaned from foreigh characters + // (for instance, seta name "name-with-foreign-chars" in the config file, and toggle to CVAR_USERINFO happens later in CL_Init) + if ( flags & CVAR_USERINFO ) { + char *cleaned = Cvar_ClearForeignCharacters( var->string ); // NOTE: it is probably harmless to call Cvar_Set2 in all cases, but I don't want to risk it + if ( strcmp( var->string, cleaned ) ) { + Cvar_Set2( var->name, var->string, qfalse ); // call Cvar_Set2 with the value to be cleaned up for verbosity + } + } + +// use a CVAR_SET for rom sets, get won't override +#if 0 + // CVAR_ROM always overrides + if ( flags & CVAR_ROM ) { + Cvar_Set2( var_name, var_value, qtrue ); + } +#endif + return var; + } + + // + // allocate a new cvar + // + if ( cvar_numIndexes >= MAX_CVARS ) { + Com_Error( ERR_FATAL, "MAX_CVARS" ); + } + var = &cvar_indexes[cvar_numIndexes]; + cvar_numIndexes++; + var->name = CopyString( var_name ); + var->string = CopyString( var_value ); + var->modified = qtrue; + var->modificationCount = 1; + var->value = atof( var->string ); + var->integer = atoi( var->string ); + var->resetString = CopyString( var_value ); + + // link the variable in + var->next = cvar_vars; + cvar_vars = var; + + var->flags = flags; + + hash = generateHashValue( var_name ); + var->hashNext = hashTable[hash]; + hashTable[hash] = var; + + return var; +} + +/* +============ +Cvar_Set2 +============ +*/ +#define FOREIGN_MSG "Foreign characters are not allowed in userinfo variables.\n" +#ifndef DEDICATED +const char* CL_TranslateStringBuf( const char *string ); +#endif +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) { + cvar_t *var; + + Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value ); + + if ( !Cvar_ValidateString( var_name ) ) { + Com_Printf( "invalid cvar name string: %s\n", var_name ); + var_name = "BADNAME"; + } + + var = Cvar_FindVar( var_name ); + if ( !var ) { + if ( !value ) { + return NULL; + } + // create it + if ( !force ) { + return Cvar_Get( var_name, value, CVAR_USER_CREATED ); + } else { + return Cvar_Get( var_name, value, 0 ); + } + } + + if ( !value ) { + value = var->resetString; + } + + if ( var->flags & CVAR_USERINFO ) { + char *cleaned = Cvar_ClearForeignCharacters( value ); + if ( strcmp( value, cleaned ) ) { + #ifdef DEDICATED + Com_Printf( FOREIGN_MSG ); + #else + Com_Printf( CL_TranslateStringBuf( FOREIGN_MSG ) ); + #endif + Com_Printf( "Using %s instead of %s\n", cleaned, value ); + return Cvar_Set2( var_name, cleaned, force ); + } + } + + if ( !strcmp( value,var->string ) ) { + return var; + } + // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) + cvar_modifiedFlags |= var->flags; + + if ( !force ) { + if ( var->flags & CVAR_ROM ) { + Com_Printf( "%s is read only.\n", var_name ); + return var; + } + + if ( var->flags & CVAR_INIT ) { + Com_Printf( "%s is write protected.\n", var_name ); + return var; + } + + if ( ( var->flags & CVAR_CHEAT ) && !cvar_cheats->integer ) { + Com_Printf( "%s is cheat protected.\n", var_name ); + return var; + } + + if ( var->flags & CVAR_LATCH ) { + if ( var->latchedString ) { + if ( strcmp( value, var->latchedString ) == 0 ) { + return var; + } + Z_Free( var->latchedString ); + } else + { + if ( strcmp( value, var->string ) == 0 ) { + return var; + } + } + + Com_Printf( "%s will be changed upon restarting.\n", var_name ); + var->latchedString = CopyString( value ); + var->modified = qtrue; + var->modificationCount++; + return var; + } + + } else + { + if ( var->latchedString ) { + Z_Free( var->latchedString ); + var->latchedString = NULL; + } + } + + if ( !strcmp( value, var->string ) ) { + return var; // not changed + + } + var->modified = qtrue; + var->modificationCount++; + + Z_Free( var->string ); // free the old value string + + var->string = CopyString( value ); + var->value = atof( var->string ); + var->integer = atoi( var->string ); + + return var; +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set( const char *var_name, const char *value ) { + Cvar_Set2( var_name, value, qtrue ); +} + +/* +============ +Cvar_SetLatched +============ +*/ +void Cvar_SetLatched( const char *var_name, const char *value ) { + Cvar_Set2( var_name, value, qfalse ); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue( const char *var_name, float value ) { + char val[32]; + + if ( value == (int)value ) { + Com_sprintf( val, sizeof( val ), "%i",(int)value ); + } else { + Com_sprintf( val, sizeof( val ), "%f",value ); + } + Cvar_Set( var_name, val ); +} + + +/* +============ +Cvar_Reset +============ +*/ +void Cvar_Reset( const char *var_name ) { + Cvar_Set2( var_name, NULL, qfalse ); +} + + +/* +============ +Cvar_SetCheatState + +Any testing variables will be reset to the safe values +============ +*/ +void Cvar_SetCheatState( void ) { + cvar_t *var; + + // set all default vars to the safe value + for ( var = cvar_vars ; var ; var = var->next ) { + if ( var->flags & CVAR_CHEAT ) { + if ( strcmp( var->resetString,var->string ) ) { + Cvar_Set( var->name, var->resetString ); + } + } + } +} + +/* +============ +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 qfalse; + } + + // perform a variable print or set + if ( Cmd_Argc() == 1 ) { + Com_Printf( "\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n", v->name, v->string, v->resetString ); + if ( v->latchedString ) { + Com_Printf( "latched: \"%s\"\n", v->latchedString ); + } + return qtrue; + } + + // set the value if forcing isn't required + Cvar_Set2( v->name, Cmd_Argv( 1 ), qfalse ); + return qtrue; +} + + +/* +============ +Cvar_Toggle_f + +Toggles a cvar for easy single key binding +============ +*/ +void Cvar_Toggle_f( void ) { + int v; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: toggle \n" ); + return; + } + + v = Cvar_VariableValue( Cmd_Argv( 1 ) ); + v = !v; + + Cvar_Set2( Cmd_Argv( 1 ), va( "%i", v ), qfalse ); +} + +/* +============ +Cvar_Set_f + +Allows setting and defining of arbitrary cvars from console, even if they +weren't declared in C code. +============ +*/ +void Cvar_Set_f( void ) { + int i, c, l, len; + char combined[MAX_STRING_TOKENS]; + + c = Cmd_Argc(); + if ( c < 3 ) { + Com_Printf( "usage: set \n" ); + return; + } + + combined[0] = 0; + l = 0; + for ( i = 2 ; i < c ; i++ ) { + len = strlen( Cmd_Argv( i ) + 1 ); + if ( l + len >= MAX_STRING_TOKENS - 2 ) { + break; + } + strcat( combined, Cmd_Argv( i ) ); + if ( i != c - 1 ) { + strcat( combined, " " ); + } + l += len; + } + Cvar_Set2( Cmd_Argv( 1 ), combined, qfalse ); +} + +/* +============ +Cvar_SetU_f + +As Cvar_Set, but also flags it as userinfo +============ +*/ +void Cvar_SetU_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf( "usage: setu \n" ); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_USERINFO; +} + +/* +============ +Cvar_SetS_f + +As Cvar_Set, but also flags it as serverinfo +============ +*/ +void Cvar_SetS_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf( "usage: sets \n" ); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_SERVERINFO; +} + +/* +============ +Cvar_SetA_f + +As Cvar_Set, but also flags it as archived +============ +*/ +void Cvar_SetA_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf( "usage: seta \n" ); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_ARCHIVE; +} + +/* +============ +Cvar_Reset_f +============ +*/ +void Cvar_Reset_f( void ) { + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: reset \n" ); + return; + } + Cvar_Reset( Cmd_Argv( 1 ) ); +} + +/* +============ +Cvar_WriteVariables + +Appends lines containing "set variable value" for all variables +with the archive flag set to qtrue. +============ +*/ +void Cvar_WriteVariables( fileHandle_t f ) { + cvar_t *var; + char buffer[1024]; + + for ( var = cvar_vars ; var ; var = var->next ) { + if ( Q_stricmp( var->name, "cl_cdkey" ) == 0 ) { + continue; + } + if ( var->flags & CVAR_ARCHIVE ) { + // write the latched value, even if it hasn't taken effect yet + if ( var->latchedString ) { + Com_sprintf( buffer, sizeof( buffer ), "seta %s \"%s\"\n", var->name, var->latchedString ); + } else { + Com_sprintf( buffer, sizeof( buffer ), "seta %s \"%s\"\n", var->name, var->string ); + } + FS_Printf( f, "%s", buffer ); + } + } +} + +/* +============ +Cvar_List_f +============ +*/ +void Cvar_List_f( void ) { + cvar_t *var; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for ( var = cvar_vars ; var ; var = var->next, i++ ) + { + if ( match && !Com_Filter( match, var->name, qfalse ) ) { + continue; + } + + if ( var->flags & CVAR_SERVERINFO ) { + Com_Printf( "S" ); + } else { + Com_Printf( " " ); + } + if ( var->flags & CVAR_USERINFO ) { + Com_Printf( "U" ); + } else { + Com_Printf( " " ); + } + if ( var->flags & CVAR_ROM ) { + Com_Printf( "R" ); + } else { + Com_Printf( " " ); + } + if ( var->flags & CVAR_INIT ) { + Com_Printf( "I" ); + } else { + Com_Printf( " " ); + } + if ( var->flags & CVAR_ARCHIVE ) { + Com_Printf( "A" ); + } else { + Com_Printf( " " ); + } + if ( var->flags & CVAR_LATCH ) { + Com_Printf( "L" ); + } else { + Com_Printf( " " ); + } + if ( var->flags & CVAR_CHEAT ) { + Com_Printf( "C" ); + } else { + Com_Printf( " " ); + } + + Com_Printf( " %s \"%s\"\n", var->name, var->string ); + } + + Com_Printf( "\n%i total cvars\n", i ); + Com_Printf( "%i cvar indexes\n", cvar_numIndexes ); +} + +/* +============ +Cvar_Restart_f + +Resets all cvars to their hardcoded values +============ +*/ +void Cvar_Restart_f( void ) { + cvar_t *var; + cvar_t **prev; + + prev = &cvar_vars; + while ( 1 ) { + var = *prev; + if ( !var ) { + break; + } + + // don't mess with rom values, or some inter-module + // communication will get broken (com_cl_running, etc) + if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) { + prev = &var->next; + continue; + } + + // throw out any variables the user created + if ( var->flags & CVAR_USER_CREATED ) { + *prev = var->next; + if ( var->name ) { + Z_Free( var->name ); + } + if ( var->string ) { + Z_Free( var->string ); + } + if ( var->latchedString ) { + Z_Free( var->latchedString ); + } + if ( var->resetString ) { + Z_Free( var->resetString ); + } + // clear the var completely, since we + // can't remove the index from the list + memset( var, 0, sizeof( var ) ); + continue; + } + + Cvar_Set( var->name, var->resetString ); + + prev = &var->next; + } +} + + + +/* +===================== +Cvar_InfoString +===================== +*/ +char *Cvar_InfoString( int bit ) { + static char info[MAX_INFO_STRING]; + cvar_t *var; + + info[0] = 0; + + for ( var = cvar_vars ; var ; var = var->next ) { + if ( var->flags & bit ) { + Info_SetValueForKey( info, var->name, var->string ); + } + } + return info; +} + +/* +===================== +Cvar_InfoString_Big + + handles large info strings ( CS_SYSTEMINFO ) +===================== +*/ +char *Cvar_InfoString_Big( int bit ) { + static char info[BIG_INFO_STRING]; + cvar_t *var; + + info[0] = 0; + + for ( var = cvar_vars ; var ; var = var->next ) { + if ( var->flags & bit ) { + Info_SetValueForKey_Big( info, var->name, var->string ); + } + } + return info; +} + + + +/* +===================== +Cvar_InfoStringBuffer +===================== +*/ +void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) { + Q_strncpyz( buff,Cvar_InfoString( bit ),buffsize ); +} + +/* +===================== +Cvar_Register + +basically a slightly modified Cvar_Get for the interpreted modules +===================== +*/ +void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { + cvar_t *cv; + + cv = Cvar_Get( varName, defaultValue, flags ); + if ( !vmCvar ) { + return; + } + vmCvar->handle = cv - cvar_indexes; + vmCvar->modificationCount = -1; + Cvar_Update( vmCvar ); +} + + +/* +===================== +Cvar_Update + +updates an interpreted modules' version of a cvar +===================== +*/ +void Cvar_Update( vmCvar_t *vmCvar ) { + cvar_t *cv = NULL; // bk001129 + assert( vmCvar ); // bk + + if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) { + Com_Error( ERR_DROP, "Cvar_Update: handle out of range" ); + } + + cv = cvar_indexes + vmCvar->handle; + + if ( cv->modificationCount == vmCvar->modificationCount ) { + return; + } + if ( !cv->string ) { + return; // variable might have been cleared by a cvar_restart + } + vmCvar->modificationCount = cv->modificationCount; + // bk001129 - mismatches. + if ( strlen( cv->string ) + 1 > MAX_CVAR_VALUE_STRING ) { + Com_Error( ERR_DROP, "Cvar_Update: src %s length %d exceeds MAX_CVAR_VALUE_STRING", + cv->string, + strlen( cv->string ), + sizeof( vmCvar->string ) ); + } + // bk001212 - Q_strncpyz guarantees zero padding and dest[MAX_CVAR_VALUE_STRING-1]==0 + // bk001129 - paranoia. Never trust the destination string. + // bk001129 - beware, sizeof(char*) is always 4 (for cv->string). + // sizeof(vmCvar->string) always MAX_CVAR_VALUE_STRING + //Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) ); // id + Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING ); + + vmCvar->value = cv->value; + vmCvar->integer = cv->integer; +} + + +/* +============ +Cvar_Init + +Reads in all archived cvars +============ +*/ +void Cvar_Init( void ) { + cvar_cheats = Cvar_Get( "sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO ); + + Cmd_AddCommand( "toggle", Cvar_Toggle_f ); + Cmd_AddCommand( "set", Cvar_Set_f ); + Cmd_AddCommand( "sets", Cvar_SetS_f ); + Cmd_AddCommand( "setu", Cvar_SetU_f ); + Cmd_AddCommand( "seta", Cvar_SetA_f ); + Cmd_AddCommand( "reset", Cvar_Reset_f ); + Cmd_AddCommand( "cvarlist", Cvar_List_f ); + Cmd_AddCommand( "cvar_restart", Cvar_Restart_f ); + + // NERVE - SMF - can't rely on autoexec to do this + Cvar_Get( "devdll", "1", CVAR_ROM ); +} diff --git a/src/qcommon/files.c b/src/qcommon/files.c new file mode 100644 index 0000000..72fc6cf --- /dev/null +++ b/src/qcommon/files.c @@ -0,0 +1,4003 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/***************************************************************************** + * name: files.c + * + * desc: handle based filesystem for Quake III Arena + * + * + *****************************************************************************/ + + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "unzip.h" + +/* +============================================================================= + +QUAKE3 FILESYSTEM + +All of Quake's data access is through a hierarchical file system, but the contents of +the file system can be transparently merged from several sources. + +A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include +a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any +references outside the quake directory system. + +The "base path" is the path to the directory holding all the game directories and usually +the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3" +command line to allow code debugging in a different directory. Basepath cannot +be modified at all after startup. Any files that are created (demos, screenshots, +etc) will be created reletive to the base path, so base path should usually be writable. + +The "cd path" is the path to an alternate hierarchy that will be searched if a file +is not located in the base path. A user can do a partial install that copies some +data to a base path created on their hard drive and leave the rest on the cd. Files +are never writen to the cd path. It defaults to a value set by the installer, like +"e:\quake3", but it can be overridden with "+set ds_cdpath g:\quake3". + +If a user runs the game directly from a CD, the base path would be on the CD. This +should still function correctly, but all file writes will fail (harmlessly). + +The "home path" is the path used for all write access. On win32 systems we have "base path" +== "home path", but on *nix systems the base installation is usually readonly, and +"home path" points to ~/.q3a or similar + +The user can also install custom mods and content in "home path", so it should be searched +along with "home path" and "cd path" for game content. + + +The "base game" is the directory under the paths where data comes from by default, and +can be either "baseq3" or "demoq3". + +The "current game" may be the same as the base game, or it may be the name of another +directory under the paths that should be searched for files before looking in the base game. +This is the basis for addons. + +Clients automatically set the game directory after receiving a gamestate from a server, +so only servers need to worry about +set fs_game. + +No other directories outside of the base game and current game will ever be referenced by +filesystem functions. + +To save disk space and speed loading, directory trees can be collapsed into zip files. +The files use a ".pk3" extension to prevent users from unzipping them accidentally, but +otherwise the are simply normal uncompressed zip files. A game directory can have multiple +zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order +from the highest number to the lowest, and will always take precedence over the filesystem. +This allows a pk3 distributed as a patch to override all existing data. + +Because we will have updated executables freely available online, there is no point to +trying to restrict demo / oem versions of the game with code changes. Demo / oem versions +should be exactly the same executables as release versions, but with different data that +automatically restricts where game media can come from to prevent add-ons from working. + +After the paths are initialized, quake will look for the product.txt file. If not +found and verified, the game will run in restricted mode. In restricted mode, only +files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is +verified to not have been modified. A single exception is made for q3config.cfg. Files +can still be written out in restricted mode, so screenshots and demos are allowed. +Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even +if there is a valid product.txt under the basepath or cdpath. + +If not running in restricted mode, and a file is not found in any local filesystem, +an attempt will be made to download it and save it under the base path. + +If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd +path, it will be copied over to the base path. This is a development aid to help build +test releases and to copy working sets over slow network links. + +File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths +structure and stop on the first successful hit. fs_searchpaths is built with successive +calls to FS_AddGameDirectory + +Additionaly, we search in several subdirectories: +current game is the current mode +base game is a variable to allow mods based on other mods +(such as baseq3 + missionpack content combination in a mod for instance) +BASEGAME is the hardcoded base game ("baseq3") + +e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places: + +home path + current game's zip files +home path + current game's directory +base path + current game's zip files +base path + current game's directory +cd path + current game's zip files +cd path + current game's directory + +home path + base game's zip file +home path + base game's directory +base path + base game's zip file +base path + base game's directory +cd path + base game's zip file +cd path + base game's directory + +home path + BASEGAME's zip file +home path + BASEGAME's directory +base path + BASEGAME's zip file +base path + BASEGAME's directory +cd path + BASEGAME's zip file +cd path + BASEGAME's directory + +server download, to be written to home path + current game's directory + + +The filesystem can be safely shutdown and reinitialized with different +basedir / cddir / game combinations, but all other subsystems that rely on it +(sound, video) must also be forced to restart. + +Because the same files are loaded by both the clip model (CM_) and renderer (TR_) +subsystems, a simple single-file caching scheme is used. The CM_ subsystems will +load the file with a request to cache. Only one file will be kept cached at a time, +so any models that are going to be referenced by both subsystems should alternate +between the CM_ load function and the ref load function. + +TODO: A qpath that starts with a leading slash will always refer to the base game, even if another +game is currently active. This allows character models, skins, and sounds to be downloaded +to a common directory no matter which game is active. + +How to prevent downloading zip files? +Pass pk3 file names in systeminfo, and download before FS_Restart()? + +Aborting a download disconnects the client from the server. + +How to mark files as downloadable? Commercial add-ons won't be downloadable. + +Non-commercial downloads will want to download the entire zip file. +the game would have to be reset to actually read the zip in + +Auto-update information + +Path separators + +Casing + + separate server gamedir and client gamedir, so if the user starts + a local game after having connected to a network game, it won't stick + with the network game. + + allow menu options for game selection? + +Read / write config to floppy option. + +Different version coexistance? + +When building a pak file, make sure a wolfconfig.cfg isn't present in it, +or configs will never get loaded from disk! + + todo: + + downloading (outside fs?) + game directory passing and restarting + +============================================================================= + +*/ + +// TTimo: moved to qcommon.h +// NOTE: could really do with a cvar +//#define BASEGAME "main" +#define DEMOGAME "demomain" + +// every time a new demo pk3 file is built, this checksum must be updated. +// the easiest way to get it is to just run the game and see what it spits out +//DHM - Nerve :: Wolf Multiplayer demo checksum +// NOTE TTimo: always needs the 'u' for unsigned int (gcc) +#define DEMO_PAK_CHECKSUM 2031778175u + +#define MAX_ZPATH 256 +#define MAX_SEARCH_PATHS 4096 +#define MAX_FILEHASH_SIZE 1024 + +typedef struct fileInPack_s { + char *name; // name of the file + unsigned long pos; // file info position in zip + struct fileInPack_s* next; // next file in the hash +} fileInPack_t; + +typedef struct { + char pakFilename[MAX_OSPATH]; // c:\quake3\baseq3\pak0.pk3 + char pakBasename[MAX_OSPATH]; // pak0 + char pakGamename[MAX_OSPATH]; // baseq3 + unzFile handle; // handle to zip file + int checksum; // regular checksum + int pure_checksum; // checksum for pure + int numfiles; // number of files in pk3 + int referenced; // referenced file flags + int hashSize; // hash table size (power of 2) + fileInPack_t* *hashTable; // hash table + fileInPack_t* buildBuffer; // buffer with the filenames etc. +} pack_t; + +typedef struct { + char path[MAX_OSPATH]; // c:\quake3 + char gamedir[MAX_OSPATH]; // baseq3 +} directory_t; + +typedef struct searchpath_s { + struct searchpath_s *next; + + pack_t *pack; // only one of pack / dir will be non NULL + directory_t *dir; +} searchpath_t; + +static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators +static cvar_t *fs_debug; +static cvar_t *fs_homepath; +static cvar_t *fs_basepath; +static cvar_t *fs_basegame; +static cvar_t *fs_cdpath; +static cvar_t *fs_copyfiles; +static cvar_t *fs_gamedirvar; +static cvar_t *fs_restrict; +static searchpath_t *fs_searchpaths; +static int fs_readCount; // total bytes read +static int fs_loadCount; // total files read +static int fs_loadStack; // total files in memory +static int fs_packFiles; // total number of files in packs + +static int fs_fakeChkSum; +static int fs_checksumFeed; + +typedef union qfile_gus { + FILE* o; + unzFile z; +} qfile_gut; + +typedef struct qfile_us { + qfile_gut file; + qboolean unique; +} qfile_ut; + +typedef struct { + qfile_ut handleFiles; + qboolean handleSync; + int baseOffset; + int fileSize; + int zipFilePos; + qboolean zipFile; + qboolean streamed; + char name[MAX_ZPATH]; +} fileHandleData_t; + +static fileHandleData_t fsh[MAX_FILE_HANDLES]; + +// TTimo - show_bug.cgi?id=540 +// wether we did a reorder on the current search path when joining the server +static qboolean fs_reordered; + +// never load anything from pk3 files that are not present at the server when pure +// ex: when fs_numServerPaks != 0, FS_FOpenFileRead won't load anything outside of pk3 except .cfg .menu .game .dat +static int fs_numServerPaks; +static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums +static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// only used for autodownload, to make sure the client has at least +// all the pk3 files that are referenced at the server side +static int fs_numServerReferencedPaks; +static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums +static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// last valid game folder used +char lastValidBase[MAX_OSPATH]; +char lastValidGame[MAX_OSPATH]; + +#ifdef FS_MISSING +FILE* missingFiles = NULL; +#endif + +/* +============== +FS_Initialized +============== +*/ + +qboolean FS_Initialized() { + return ( fs_searchpaths != NULL ); +} + +/* +================= +FS_PakIsPure +================= +*/ +qboolean FS_PakIsPure( pack_t *pack ) { + int i; + + if ( fs_numServerPaks ) { + for ( i = 0 ; i < fs_numServerPaks ; i++ ) { + // FIXME: also use hashed file names + // NOTE TTimo: a pk3 with same checksum but different name would be validated too + // I don't see this as allowing for any exploit, it would only happen if the client does manips of it's file names 'not a bug' + if ( pack->checksum == fs_serverPaks[i] ) { + return qtrue; // on the approved list + } + } + return qfalse; // not on the pure server pak list + } + return qtrue; +} + + +/* +================= +FS_LoadStack +return load stack +================= +*/ +int FS_LoadStack() { + return fs_loadStack; +} + +/* +================ +return a hash value for the filename +================ +*/ +static long FS_HashFileName( const char *fname, int hashSize ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter == '\\' ) { + letter = '/'; // damn path names + } + if ( letter == PATH_SEP ) { + letter = '/'; // damn path names + } + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ); + hash &= ( hashSize - 1 ); + return hash; +} + +static fileHandle_t FS_HandleForFile( void ) { + int i; + + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { + if ( fsh[i].handleFiles.file.o == NULL ) { + return i; + } + } + Com_Error( ERR_DROP, "FS_HandleForFile: none free" ); + return 0; +} + +static FILE *FS_FileForHandle( fileHandle_t f ) { + if ( f < 0 || f > MAX_FILE_HANDLES ) { + Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" ); + } + if ( fsh[f].zipFile == qtrue ) { + Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" ); + } + if ( !fsh[f].handleFiles.file.o ) { + Com_Error( ERR_DROP, "FS_FileForHandle: NULL" ); + } + + return fsh[f].handleFiles.file.o; +} + +void FS_ForceFlush( fileHandle_t f ) { + FILE *file; + + file = FS_FileForHandle( f ); + setvbuf( file, NULL, _IONBF, 0 ); +} + +/* +================ +FS_filelength + +If this is called on a non-unique FILE (from a pak file), +it will return the size of the pak file, not the expected +size of the file. +================ +*/ +int FS_filelength( fileHandle_t f ) { + int pos; + int end; + FILE* h; + + h = FS_FileForHandle( f ); + pos = ftell( h ); + fseek( h, 0, SEEK_END ); + end = ftell( h ); + fseek( h, pos, SEEK_SET ); + + return end; +} + +/* +==================== +FS_ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +static void FS_ReplaceSeparators( char *path ) { + char *s; + + for ( s = path ; *s ; s++ ) { + if ( *s == '/' || *s == '\\' ) { + *s = PATH_SEP; + } + } +} + +/* +=================== +FS_BuildOSPath + +Qpath may have either forward or backwards slashes +=================== +*/ +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) { + char temp[MAX_OSPATH]; + static char ospath[2][MAX_OSPATH]; + static int toggle; + + toggle ^= 1; // flip-flop to allow two returns without clash + + if ( !game || !game[0] ) { + game = fs_gamedir; + } + + Com_sprintf( temp, sizeof( temp ), "/%s/%s", game, qpath ); + FS_ReplaceSeparators( temp ); + Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp ); + + return ospath[toggle]; +} + + +/* +============ +FS_CreatePath + +Creates any directories needed to store the given filename +============ +*/ +static qboolean FS_CreatePath( char *OSPath ) { + char *ofs; + + // make absolutely sure that it can't back up the path + // FIXME: is c: allowed??? + if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { + Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath ); + return qtrue; + } + + for ( ofs = OSPath + 1 ; *ofs ; ofs++ ) { + if ( *ofs == PATH_SEP ) { + // create the directory + *ofs = 0; + Sys_Mkdir( OSPath ); + *ofs = PATH_SEP; + } + } + return qfalse; +} + +/* +================= +FS_CopyFile + +Copy a fully specified file from one place to another +================= +*/ +void FS_CopyFile( char *fromOSPath, char *toOSPath ) { + FILE *f; + int len; + byte *buf; + + Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath ); + + if ( strstr( fromOSPath, "journal.dat" ) || strstr( fromOSPath, "journaldata.dat" ) ) { + Com_Printf( "Ignoring journal files\n" ); + return; + } + + f = fopen( fromOSPath, "rb" ); + if ( !f ) { + return; + } + fseek( f, 0, SEEK_END ); + len = ftell( f ); + fseek( f, 0, SEEK_SET ); + + // we are using direct malloc instead of Z_Malloc here, so it + // probably won't work on a mac... Its only for developers anyway... + buf = malloc( len ); + if ( fread( buf, 1, len, f ) != len ) { + Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" ); + } + fclose( f ); + + if ( FS_CreatePath( toOSPath ) ) { + return; + } + + f = fopen( toOSPath, "wb" ); + if ( !f ) { + free( buf ); //DAJ free as well + return; + } + if ( fwrite( buf, 1, len, f ) != len ) { + Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" ); + } + fclose( f ); + free( buf ); +} + +/* +=========== +FS_Remove + +=========== +*/ +static void FS_Remove( const char *osPath ) { + remove( osPath ); +} + +/* +================ +FS_FileExists + +Tests if the file exists in the current gamedir, this DOES NOT +search the paths. This is to determine if opening a file to write +(which always goes into the current gamedir) will cause any overwrites. +NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards +================ +*/ +qboolean FS_FileExists( const char *file ) { + FILE *f; + char *testpath; + + testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file ); + + f = fopen( testpath, "rb" ); + if ( f ) { + fclose( f ); + return qtrue; + } + return qfalse; +} + +/* +================ +FS_SV_FileExists + +Tests if the file exists +================ +*/ +qboolean FS_SV_FileExists( const char *file ) { + FILE *f; + char *testpath; + + testpath = FS_BuildOSPath( fs_homepath->string, file, "" ); + testpath[strlen( testpath ) - 1] = '\0'; + + f = fopen( testpath, "rb" ); + if ( f ) { + fclose( f ); + return qtrue; + } + return qfalse; +} + + +/* +=========== +FS_SV_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); + ospath[strlen( ospath ) - 1] = '\0'; + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath ); + } + + if ( FS_CreatePath( ospath ) ) { + return 0; + } + + Com_DPrintf( "writing to: %s\n", ospath ); + fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + fsh[f].handleSync = qfalse; + if ( !fsh[f].handleFiles.file.o ) { + f = 0; + } + return f; +} + +/* +=========== +FS_SV_FOpenFileRead +search for a file somewhere below the home path, base path or cd path +we search in that order, matching FS_SV_FOpenFileRead order +=========== +*/ +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) { + char *ospath; + fileHandle_t f = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + + // search homepath + ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); + // remove trailing slash + ospath[strlen( ospath ) - 1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + if ( !fsh[f].handleFiles.file.o ) { + // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid + if ( Q_stricmp( fs_homepath->string,fs_basepath->string ) ) { + // search basepath + ospath = FS_BuildOSPath( fs_basepath->string, filename, "" ); + ospath[strlen( ospath ) - 1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + + if ( !fsh[f].handleFiles.file.o ) { + f = 0; + } + } + } + + if ( !fsh[f].handleFiles.file.o ) { + // search cd path + ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" ); + ospath[strlen( ospath ) - 1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + + if ( !fsh[f].handleFiles.file.o ) { + f = 0; + } + } + + *fp = f; + if ( f ) { + return FS_filelength( f ); + } + return 0; +} + + +/* +=========== +FS_SV_Rename + +=========== +*/ +void FS_SV_Rename( const char *from, const char *to ) { + char *from_ospath, *to_ospath; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + // don't let sound stutter + S_ClearSoundBuffer(); + + from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" ); + to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" ); + from_ospath[strlen( from_ospath ) - 1] = '\0'; + to_ospath[strlen( to_ospath ) - 1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath ); + } + + if ( rename( from_ospath, to_ospath ) ) { + // Failed, try copying it and deleting the original + FS_CopyFile( from_ospath, to_ospath ); + FS_Remove( from_ospath ); + } +} + + + +/* +=========== +FS_Rename + +=========== +*/ +void FS_Rename( const char *from, const char *to ) { + char *from_ospath, *to_ospath; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + // don't let sound stutter + S_ClearSoundBuffer(); + + from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from ); + to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath ); + } + + if ( rename( from_ospath, to_ospath ) ) { + // Failed, try copying it and deleting the original + FS_CopyFile( from_ospath, to_ospath ); + FS_Remove( from_ospath ); + } +} + +/* +============== +FS_FCloseFile + +If the FILE pointer is an open pak file, leave it open. + +For some reason, other dll's can't just cal fclose() +on files returned by FS_FOpenFile... +============== +*/ +void FS_FCloseFile( fileHandle_t f ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( fsh[f].streamed ) { + Sys_EndStreamedFile( f ); + } + if ( fsh[f].zipFile == qtrue ) { + unzCloseCurrentFile( fsh[f].handleFiles.file.z ); + if ( fsh[f].handleFiles.unique ) { + unzClose( fsh[f].handleFiles.file.z ); + } + Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); + return; + } + + // we didn't find it as a pak, so close it as a unique file + if ( fsh[f].handleFiles.file.o ) { + fclose( fsh[f].handleFiles.file.o ); + } + Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); +} + +/* +=========== +FS_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_FOpenFileWrite( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileWrite: %s\n", ospath ); + } + + if ( FS_CreatePath( ospath ) ) { + return 0; + } + + // enabling the following line causes a recursive function call loop + // when running with +set logfile 1 +set developer 1 + //Com_DPrintf( "writing to: %s\n", ospath ); + fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + fsh[f].handleSync = qfalse; + if ( !fsh[f].handleFiles.file.o ) { + f = 0; + } + return f; +} + +/* +=========== +FS_FOpenFileAppend + +=========== +*/ +fileHandle_t FS_FOpenFileAppend( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + + ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileAppend: %s\n", ospath ); + } + + if ( FS_CreatePath( ospath ) ) { + return 0; + } + + fsh[f].handleFiles.file.o = fopen( ospath, "ab" ); + fsh[f].handleSync = qfalse; + if ( !fsh[f].handleFiles.file.o ) { + f = 0; + } + return f; +} + +/* +=========== +FS_FilenameCompare + +Ignore case and seprator char distinctions +=========== +*/ +qboolean FS_FilenameCompare( const char *s1, const char *s2 ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if ( c1 != c2 ) { + return -1; // strings not equal + } + } while ( c1 ); + + return 0; // strings are equal +} + +/* +=========== +FS_ShiftedStrStr +=========== +*/ +char *FS_ShiftedStrStr( const char *string, const char *substring, int shift ) { + char buf[MAX_STRING_TOKENS]; + int i; + + for ( i = 0; substring[i]; i++ ) { + buf[i] = substring[i] + shift; + } + buf[i] = '\0'; + return strstr( string, buf ); +} + +/* +========== +FS_ShiftStr +perform simple string shifting to avoid scanning from the exe +========== +*/ +char *FS_ShiftStr( const char *string, int shift ) { + static char buf[MAX_STRING_CHARS]; + int i,l; + + l = strlen( string ); + for ( i = 0; i < l; i++ ) { + buf[i] = string[i] + shift; + } + buf[i] = '\0'; + return buf; +} + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +extern qboolean com_fullyInitialized; + +// see FS_FOpenFileRead_Filtered +static int fs_filter_flag = 0; + +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) { + searchpath_t *search; + char *netpath; + pack_t *pak; + fileInPack_t *pakFile; + directory_t *dir; + long hash; + unz_s *zfi; + FILE *temp; + int l; + char demoExt[16]; + + hash = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + // TTimo - NOTE + // when checking for file existence, it's probably safer to use FS_FileExists, as I'm not + // sure this chunk of code is really up to date with everything + if ( file == NULL ) { + // just wants to see if file is there + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if ( search->pack ) { + hash = FS_HashFileName( filename, search->pack->hashSize ); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + if ( fs_filter_flag & FS_EXCLUDE_PK3 ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + // found it! + return qtrue; + } + pakFile = pakFile->next; + } while ( pakFile != NULL ); + } else if ( search->dir ) { + if ( fs_filter_flag & FS_EXCLUDE_DIR ) { + continue; + } + + dir = search->dir; + + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); + temp = fopen( netpath, "rb" ); + if ( !temp ) { + continue; + } + fclose( temp ); + return qtrue; + } + } + return qfalse; + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d",PROTOCOL_VERSION ); + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { + *file = 0; + return -1; + } + + // make sure the q3key file is only readable by the quake3.exe at initialization + // any other time the key should only be accessed in memory using the provided functions + if ( com_fullyInitialized && strstr( filename, "rtcwkey" ) ) { + *file = 0; + return -1; + } + + // + // search through the path, one element at a time + // + + *file = FS_HandleForFile(); + fsh[*file].handleFiles.unique = uniqueFILE; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if ( search->pack ) { + hash = FS_HashFileName( filename, search->pack->hashSize ); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + if ( fs_filter_flag & FS_EXCLUDE_PK3 ) { + continue; + } + + // disregard if it doesn't match one of the allowed pure pak files + if ( !FS_PakIsPure( search->pack ) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + // found it! + + // mark the pak as having been referenced and mark specifics on cgame and ui + // shaders, txt, arena files by themselves do not count as a reference as + // these are loaded from all pk3s + // from every pk3 file.. + l = strlen( filename ); + if ( !( pak->referenced & FS_GENERAL_REF ) ) { + if ( Q_stricmp( filename + l - 7, ".shader" ) != 0 && + Q_stricmp( filename + l - 4, ".txt" ) != 0 && + Q_stricmp( filename + l - 4, ".cfg" ) != 0 && + Q_stricmp( filename + l - 7, ".config" ) != 0 && + strstr( filename, "levelshots" ) == NULL && + Q_stricmp( filename + l - 4, ".bot" ) != 0 && + Q_stricmp( filename + l - 6, ".arena" ) != 0 && + Q_stricmp( filename + l - 5, ".menu" ) != 0 ) { + pak->referenced |= FS_GENERAL_REF; + } + } + + // for OS client/server interoperability, we expect binaries for .so and .dll to be in the same pk3 + // so that when we reference the DLL files on any platform, this covers everyone else + + #if 0 // TTimo: use that stuff for shitted strings + Com_Printf( "SYS_DLLNAME_QAGAME + %d: '%s'\n", SYS_DLLNAME_QAGAME_SHIFT, FS_ShiftStr( "qagame_mp_x86.dll" /*"qagame.mp.i386.so"*/, SYS_DLLNAME_QAGAME_SHIFT ) ); + Com_Printf( "SYS_DLLNAME_CGAME + %d: '%s'\n", SYS_DLLNAME_CGAME_SHIFT, FS_ShiftStr( "cgame_mp_x86.dll" /*"cgame.mp.i386.so"*/, SYS_DLLNAME_CGAME_SHIFT ) ); + Com_Printf( "SYS_DLLNAME_UI + %d: '%s'\n", SYS_DLLNAME_UI_SHIFT, FS_ShiftStr( "ui_mp_x86.dll" /*"ui.mp.i386.so"*/, SYS_DLLNAME_UI_SHIFT ) ); + #endif + // qagame dll + if ( !( pak->referenced & FS_QAGAME_REF ) && FS_ShiftedStrStr( filename, SYS_DLLNAME_QAGAME, -SYS_DLLNAME_QAGAME_SHIFT ) ) { + pak->referenced |= FS_QAGAME_REF; + } + // cgame dll + if ( !( pak->referenced & FS_CGAME_REF ) && FS_ShiftedStrStr( filename, SYS_DLLNAME_CGAME, -SYS_DLLNAME_CGAME_SHIFT ) ) { + pak->referenced |= FS_CGAME_REF; + } + // ui dll + if ( !( pak->referenced & FS_UI_REF ) && FS_ShiftedStrStr( filename, SYS_DLLNAME_UI, -SYS_DLLNAME_UI_SHIFT ) ) { + pak->referenced |= FS_UI_REF; + } + +#if !defined( PRE_RELEASE_DEMO ) && !defined( DO_LIGHT_DEDICATED ) + // DHM -- Nerve :: Don't allow maps to be loaded from pak0 (singleplayer) + if ( Q_stricmp( filename + l - 4, ".bsp" ) == 0 && + Q_stricmp( pak->pakBasename, "pak0" ) == 0 ) { + + *file = 0; + return -1; + } +#endif + + if ( uniqueFILE ) { + // open a new file on the pakfile + fsh[*file].handleFiles.file.z = unzReOpen( pak->pakFilename, pak->handle ); + if ( fsh[*file].handleFiles.file.z == NULL ) { + Com_Error( ERR_FATAL, "Couldn't reopen %s", pak->pakFilename ); + } + } else { + fsh[*file].handleFiles.file.z = pak->handle; + } + Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); + fsh[*file].zipFile = qtrue; + zfi = (unz_s *)fsh[*file].handleFiles.file.z; + // in case the file was new + temp = zfi->file; + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition( pak->handle, pakFile->pos ); + // copy the file info into the unzip structure + Com_Memcpy( zfi, pak->handle, sizeof( unz_s ) ); + // we copy this back into the structure + zfi->file = temp; + // open the file in the zip + unzOpenCurrentFile( fsh[*file].handleFiles.file.z ); + fsh[*file].zipFilePos = pakFile->pos; + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", + filename, pak->pakFilename ); + } + return zfi->cur_file_info.uncompressed_size; + } + pakFile = pakFile->next; + } while ( pakFile != NULL ); + } else if ( search->dir ) { + if ( fs_filter_flag & FS_EXCLUDE_DIR ) { + continue; + } + + // check a file in the directory tree + + // if we are running restricted, or if the filesystem is configured for pure (fs_numServerPaks) + // the only files we will allow to come from the directory are .cfg files + l = strlen( filename ); + if ( fs_restrict->integer || fs_numServerPaks ) { + + if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files + && Q_stricmp( filename + l - 5, ".menu" ) // menu files + && Q_stricmp( filename + l - 5, ".game" ) // menu files + && Q_stricmp( filename + l - strlen( demoExt ), demoExt ) // menu files + && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files + continue; + } + } + + dir = search->dir; + + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); + fsh[*file].handleFiles.file.o = fopen( netpath, "rb" ); + if ( !fsh[*file].handleFiles.file.o ) { + continue; + } + + if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files + && Q_stricmp( filename + l - 5, ".menu" ) // menu files + && Q_stricmp( filename + l - 5, ".game" ) // menu files + && Q_stricmp( filename + l - strlen( demoExt ), demoExt ) // menu files + && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files + fs_fakeChkSum = random(); + } + + Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); + fsh[*file].zipFile = qfalse; + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename, + dir->path, dir->gamedir ); + } + + // if we are getting it from the cdpath, optionally copy it + // to the basepath + if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) { + char *copypath; + + copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename ); + FS_CopyFile( netpath, copypath ); + } + + return FS_filelength( *file ); + } + } + + Com_DPrintf( "Can't find %s\n", filename ); +#ifdef FS_MISSING + if ( missingFiles ) { + fprintf( missingFiles, "%s\n", filename ); + } +#endif + *file = 0; + return -1; +} + +int FS_FOpenFileRead_Filtered( const char *qpath, fileHandle_t *file, qboolean uniqueFILE, int filter_flag ) { + int ret; + + fs_filter_flag = filter_flag; + ret = FS_FOpenFileRead( qpath, file, uniqueFILE ); + fs_filter_flag = 0; + + return ret; +} + +// TTimo +// relevant to client only +#if !defined( DEDICATED ) +/* +================== +FS_CL_ExtractFromPakFile + +NERVE - SMF - Extracts the latest file from a pak file. + +Compares packed file against extracted file. If no differences, does not copy. +This is necessary for exe/dlls which may or may not be locked. + +NOTE TTimo: + fullpath gives the full OS path to the dll that will potentially be loaded + on win32 it's always in fs_basepath// + on linux it can be in fs_homepath// or fs_basepath// + the dll is extracted to fs_homepath (== fs_basepath on win32) if needed + + the return value doesn't tell wether file was extracted or not, it just says wether it's ok to continue + (i.e. either the right file was extracted successfully, or it was already present) + + cvar_lastVersion is the optional name of a CVAR_ARCHIVE used to store the wolf version for the last extracted .so + show_bug.cgi?id=463 + +================== +*/ +qboolean FS_CL_ExtractFromPakFile( const char *fullpath, const char *gamedir, const char *filename, const char *cvar_lastVersion ) { + int srcLength; + int destLength; + unsigned char *srcData; + unsigned char *destData; + qboolean needToCopy; + FILE *destHandle; + + needToCopy = qtrue; + + // read in compressed file + srcLength = FS_ReadFile( filename, (void **)&srcData ); + + // if its not in the pak, we bail + if ( srcLength == -1 ) { + return qfalse; + } + + // read in local file + destHandle = fopen( fullpath, "rb" ); + + // if we have a local file, we need to compare the two + if ( destHandle ) { + fseek( destHandle, 0, SEEK_END ); + destLength = ftell( destHandle ); + fseek( destHandle, 0, SEEK_SET ); + + if ( destLength > 0 ) { + destData = (unsigned char*)Z_Malloc( destLength ); + + fread( destData, 1, destLength, destHandle ); + + // compare files + if ( destLength == srcLength ) { + int i; + + for ( i = 0; i < destLength; i++ ) { + if ( destData[i] != srcData[i] ) { + break; + } + } + + if ( i == destLength ) { + needToCopy = qfalse; + } + } + + Z_Free( destData ); // TTimo + } + + fclose( destHandle ); + } + + // write file + if ( needToCopy ) { + fileHandle_t f; + + // Com_DPrintf("FS_ExtractFromPakFile: FS_FOpenFileWrite '%s'\n", filename); + f = FS_FOpenFileWrite( filename ); + if ( !f ) { + Com_Printf( "Failed to open %s\n", filename ); + return qfalse; + } + + FS_Write( srcData, srcLength, f ); + + FS_FCloseFile( f ); + +#ifdef __linux__ + // show_bug.cgi?id=463 + // need to keep track of what versions we extract + if ( cvar_lastVersion ) { + Cvar_Set( cvar_lastVersion, Cvar_VariableString( "version" ) ); + } +#endif + } + + FS_FreeFile( srcData ); + return qtrue; +} +#endif + +/* +============== +FS_Delete +TTimo - this was not in the 1.30 filesystem code +using fs_homepath for the file to remove +============== +*/ +int FS_Delete( char *filename ) { + char *ospath; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !filename || filename[0] == 0 ) { + return 0; + } + + // for safety, only allow deletion from the save directory + if ( Q_strncmp( filename, "save/", 5 ) != 0 ) { + return 0; + } + + ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + + if ( remove( ospath ) != -1 ) { // success + return 1; + } + + + return 0; +} + + +/* +================= +FS_Read + +Properly handles partial reads +================= +*/ +int FS_Read2( void *buffer, int len, fileHandle_t f ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !f ) { + return 0; + } + if ( fsh[f].streamed ) { + int r; + fsh[f].streamed = qfalse; + r = Sys_StreamedRead( buffer, len, 1, f ); + fsh[f].streamed = qtrue; + return r; + } else { + return FS_Read( buffer, len, f ); + } +} + +int FS_Read( void *buffer, int len, fileHandle_t f ) { + int block, remaining; + int read; + byte *buf; + int tries; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !f ) { + return 0; + } + + buf = (byte *)buffer; + fs_readCount += len; + + if ( fsh[f].zipFile == qfalse ) { + remaining = len; + tries = 0; + while ( remaining ) { + block = remaining; + read = fread( buf, 1, block, fsh[f].handleFiles.file.o ); + if ( read == 0 ) { + // we might have been trying to read from a CD, which + // sometimes returns a 0 read on windows + if ( !tries ) { + tries = 1; + } else { + return len - remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read"); + } + } + + if ( read == -1 ) { + Com_Error( ERR_FATAL, "FS_Read: -1 bytes read" ); + } + + remaining -= read; + buf += read; + } + return len; + } else { + return unzReadCurrentFile( fsh[f].handleFiles.file.z, buffer, len ); + } +} + +/* +================= +FS_Write + +Properly handles partial writes +================= +*/ +int FS_Write( const void *buffer, int len, fileHandle_t h ) { + int block, remaining; + int written; + byte *buf; + int tries; + FILE *f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !h ) { + return 0; + } + + f = FS_FileForHandle( h ); + buf = (byte *)buffer; + + remaining = len; + tries = 0; + while ( remaining ) { + block = remaining; + written = fwrite( buf, 1, block, f ); + if ( written == 0 ) { + if ( !tries ) { + tries = 1; + } else { + Com_Printf( "FS_Write: 0 bytes written\n" ); + return 0; + } + } + + if ( written == -1 ) { + Com_Printf( "FS_Write: -1 bytes written\n" ); + return 0; + } + + remaining -= written; + buf += written; + } + if ( fsh[h].handleSync ) { + fflush( f ); + } + return len; +} + +void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start( argptr,fmt ); + Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ); + va_end( argptr ); + + FS_Write( msg, strlen( msg ), h ); +} + +/* +================= +FS_Seek + +================= +*/ +int FS_Seek( fileHandle_t f, long offset, int origin ) { + int _origin; + char foo[65536]; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + return -1; + } + + if ( fsh[f].streamed ) { + fsh[f].streamed = qfalse; + Sys_StreamSeek( f, offset, origin ); + fsh[f].streamed = qtrue; + } + + if ( fsh[f].zipFile == qtrue ) { + if ( offset == 0 && origin == FS_SEEK_SET ) { + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition( fsh[f].handleFiles.file.z, fsh[f].zipFilePos ); + return unzOpenCurrentFile( fsh[f].handleFiles.file.z ); + } else if ( offset < 65536 ) { + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition( fsh[f].handleFiles.file.z, fsh[f].zipFilePos ); + unzOpenCurrentFile( fsh[f].handleFiles.file.z ); + return FS_Read( foo, offset, f ); + } else { + Com_Error( ERR_FATAL, "ZIP FILE FSEEK NOT YET IMPLEMENTED\n" ); + return -1; + } + } else { + FILE *file; + file = FS_FileForHandle( f ); + switch ( origin ) { + case FS_SEEK_CUR: + _origin = SEEK_CUR; + break; + case FS_SEEK_END: + _origin = SEEK_END; + break; + case FS_SEEK_SET: + _origin = SEEK_SET; + break; + default: + _origin = SEEK_CUR; + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + break; + } + + return fseek( file, offset, _origin ); + } +} + + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +int FS_FileIsInPAK( const char *filename, int *pChecksum ) { + searchpath_t *search; + pack_t *pak; + fileInPack_t *pakFile; + long hash = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { + return -1; + } + + // + // search through the path, one element at a time + // + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if ( search->pack ) { + hash = FS_HashFileName( filename, search->pack->hashSize ); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // disregard if it doesn't match one of the allowed pure pak files + if ( !FS_PakIsPure( search->pack ) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + if ( pChecksum ) { + *pChecksum = pak->pure_checksum; + } + return 1; + } + pakFile = pakFile->next; + } while ( pakFile != NULL ); + } + } + return -1; +} + +/* +============ +FS_ReadFile + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +int FS_ReadFile( const char *qpath, void **buffer ) { + fileHandle_t h; + byte* buf; + qboolean isConfig; + int len; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !qpath || !qpath[0] ) { + Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" ); + } + + buf = NULL; // quiet compiler warning + + // if this is a .cfg file and we are playing back a journal, read + // it from the journal file + if ( strstr( qpath, ".cfg" ) ) { + isConfig = qtrue; + if ( com_journal && com_journal->integer == 2 ) { + int r; + + Com_DPrintf( "Loading %s from journal file.\n", qpath ); + r = FS_Read( &len, sizeof( len ), com_journalDataFile ); + if ( r != sizeof( len ) ) { + if ( buffer != NULL ) { + *buffer = NULL; + } + return -1; + } + // if the file didn't exist when the journal was created + if ( !len ) { + if ( buffer == NULL ) { + return 1; // hack for old journal files + } + *buffer = NULL; + return -1; + } + if ( buffer == NULL ) { + return len; + } + + buf = Hunk_AllocateTempMemory( len + 1 ); + *buffer = buf; + + r = FS_Read( buf, len, com_journalDataFile ); + if ( r != len ) { + Com_Error( ERR_FATAL, "Read from journalDataFile failed" ); + } + + fs_loadCount++; + fs_loadStack++; + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + + return len; + } + } else { + isConfig = qfalse; + } + + // look for it in the filesystem or pack files + len = FS_FOpenFileRead( qpath, &h, qfalse ); + if ( h == 0 ) { + if ( buffer ) { + *buffer = NULL; + } + // if we are journalling and it is a config file, write a zero to the journal file + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing zero for %s to journal file.\n", qpath ); + len = 0; + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + return -1; + } + + if ( !buffer ) { + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing len for %s to journal file.\n", qpath ); + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + FS_FCloseFile( h ); + return len; + } + + fs_loadCount++; + fs_loadStack++; + + buf = Hunk_AllocateTempMemory( len + 1 ); + *buffer = buf; + + FS_Read( buf, len, h ); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + FS_FCloseFile( h ); + + // if we are journalling and it is a config file, write it to the journal file + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing %s to journal file.\n", qpath ); + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Write( buf, len, com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + return len; +} + +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile( void *buffer ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + if ( !buffer ) { + Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); + } + fs_loadStack--; + + Hunk_FreeTempMemory( buffer ); + + // if all of our temp files are free, clear all of our space + if ( fs_loadStack == 0 ) { + Hunk_ClearTempMemory(); + } +} + +/* +============ +FS_WriteFile + +Filename are reletive to the quake search path +============ +*/ +void FS_WriteFile( const char *qpath, const void *buffer, int size ) { + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !qpath || !buffer ) { + Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" ); + } + + f = FS_FOpenFileWrite( qpath ); + if ( !f ) { + Com_Printf( "Failed to open %s\n", qpath ); + return; + } + + FS_Write( buffer, size, f ); + + FS_FCloseFile( f ); +} + + + +/* +========================================================================== + +ZIP FILE LOADING + +========================================================================== +*/ + +/* +================= +FS_LoadZipFile + +Creates a new pak_t in the search chain for the contents +of a zip file. +================= +*/ +static pack_t *FS_LoadZipFile( char *zipfile, const char *basename ) { + fileInPack_t *buildBuffer; + pack_t *pack; + unzFile uf; + int err; + unz_global_info gi; + char filename_inzip[MAX_ZPATH]; + unz_file_info file_info; + int i, len; + long hash; + int fs_numHeaderLongs; + int *fs_headerLongs; + char *namePtr; + + fs_numHeaderLongs = 0; + + uf = unzOpen( zipfile ); + err = unzGetGlobalInfo( uf,&gi ); + + if ( err != UNZ_OK ) { + return NULL; + } + + fs_packFiles += gi.number_entry; + + len = 0; + unzGoToFirstFile( uf ); + for ( i = 0; i < gi.number_entry; i++ ) + { + err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 ); + if ( err != UNZ_OK ) { + break; + } + len += strlen( filename_inzip ) + 1; + unzGoToNextFile( uf ); + } + + buildBuffer = Z_Malloc( ( gi.number_entry * sizeof( fileInPack_t ) ) + len ); + namePtr = ( (char *) buildBuffer ) + gi.number_entry * sizeof( fileInPack_t ); + fs_headerLongs = Z_Malloc( gi.number_entry * sizeof( int ) ); + + // get the hash table size from the number of files in the zip + // because lots of custom pk3 files have less than 32 or 64 files + for ( i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1 ) { + if ( i > gi.number_entry ) { + break; + } + } + + pack = Z_Malloc( sizeof( pack_t ) + i * sizeof( fileInPack_t * ) ); + pack->hashSize = i; + pack->hashTable = ( fileInPack_t ** )( ( (char *) pack ) + sizeof( pack_t ) ); + for ( i = 0; i < pack->hashSize; i++ ) { + pack->hashTable[i] = NULL; + } + + Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) ); + Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) ); + + // strip .pk3 if needed + if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) { + pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0; + } + + pack->handle = uf; + pack->numfiles = gi.number_entry; + unzGoToFirstFile( uf ); + + for ( i = 0; i < gi.number_entry; i++ ) + { + err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 ); + if ( err != UNZ_OK ) { + break; + } + if ( file_info.uncompressed_size > 0 ) { + fs_headerLongs[fs_numHeaderLongs++] = LittleLong( file_info.crc ); + } + Q_strlwr( filename_inzip ); + hash = FS_HashFileName( filename_inzip, pack->hashSize ); + buildBuffer[i].name = namePtr; + strcpy( buildBuffer[i].name, filename_inzip ); + namePtr += strlen( filename_inzip ) + 1; + // store the file position in the zip + unzGetCurrentFileInfoPosition( uf, &buildBuffer[i].pos ); + // + buildBuffer[i].next = pack->hashTable[hash]; + pack->hashTable[hash] = &buildBuffer[i]; + unzGoToNextFile( uf ); + } + + pack->checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs ); + pack->pure_checksum = Com_BlockChecksumKey( fs_headerLongs, 4 * fs_numHeaderLongs, LittleLong( fs_checksumFeed ) ); + // TTimo: DO_LIGHT_DEDICATED + // curious about the size of those + //Com_DPrintf("Com_BlockChecksumKey: %s %u\n", pack->pakBasename, 4 * fs_numHeaderLongs); + // cumulated for light dedicated: 21558 bytes + pack->checksum = LittleLong( pack->checksum ); + pack->pure_checksum = LittleLong( pack->pure_checksum ); + + Z_Free( fs_headerLongs ); + + pack->buildBuffer = buildBuffer; + return pack; +} + +/* +================================================================================= + +DIRECTORY SCANNING FUNCTIONS + +================================================================================= +*/ + +#define MAX_FOUND_FILES 0x1000 + +static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) { + int len, at, newdep; + + newdep = 0; + zpath[0] = 0; + len = 0; + at = 0; + + while ( zname[at] != 0 ) + { + if ( zname[at] == '/' || zname[at] == '\\' ) { + len = at; + newdep++; + } + at++; + } + strcpy( zpath, zname ); + zpath[len] = 0; + *depth = newdep; + + return len; +} + +/* +================== +FS_AddFileToList +================== +*/ +static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) { + int i; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + for ( i = 0 ; i < nfiles ; i++ ) { + if ( !Q_stricmp( name, list[i] ) ) { + return nfiles; // allready in list + } + } + list[nfiles] = CopyString( name ); + nfiles++; + + return nfiles; +} + +/* +=============== +FS_ListFilteredFiles + +Returns a uniqued list of files that match the given criteria +from all search paths +=============== +*/ +char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) { + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + searchpath_t *search; + int i; + int pathLength; + int extensionLength; + int length, pathDepth, temp; + pack_t *pak; + fileInPack_t *buildBuffer; + char zpath[MAX_ZPATH]; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !path ) { + *numfiles = 0; + return NULL; + } + if ( !extension ) { + extension = ""; + } + + pathLength = strlen( path ); + if ( path[pathLength - 1] == '\\' || path[pathLength - 1] == '/' ) { + pathLength--; + } + extensionLength = strlen( extension ); + nfiles = 0; + FS_ReturnPath( path, zpath, &pathDepth ); + + // + // search through the path, one element at a time, adding to list + // + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + + //ZOID: If we are pure, don't search for files on paks that + // aren't on the pure list + if ( !FS_PakIsPure( search->pack ) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + buildBuffer = pak->buildBuffer; + for ( i = 0; i < pak->numfiles; i++ ) { + char *name; + int zpathLen, depth; + + // check for directory match + name = buildBuffer[i].name; + // + if ( filter ) { + // case insensitive + if ( !Com_FilterPath( filter, name, qfalse ) ) { + continue; + } + // unique the match + nfiles = FS_AddFileToList( name, list, nfiles ); + } else { + + zpathLen = FS_ReturnPath( name, zpath, &depth ); + + if ( ( depth - pathDepth ) > 2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) { + continue; + } + + // check for extension match + length = strlen( name ); + if ( length < extensionLength ) { + continue; + } + + if ( Q_stricmp( name + length - extensionLength, extension ) ) { + continue; + } + // unique the match + + temp = pathLength; + if ( pathLength ) { + temp++; // include the '/' + } + nfiles = FS_AddFileToList( name + temp, list, nfiles ); + } + } + } else if ( search->dir ) { // scan for files in the filesystem + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + + // don't scan directories for files if we are pure or restricted + if ( fs_restrict->integer || fs_numServerPaks ) { + continue; + } else { + netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path ); + sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse ); + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToList( name, list, nfiles ); + } + Sys_FreeFileList( sysFiles ); + } + } + } + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + +/* +================= +FS_ListFiles +================= +*/ +char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) { + return FS_ListFilteredFiles( path, extension, NULL, numfiles ); +} + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList( char **list ) { + int i; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !list ) { + return; + } + + for ( i = 0 ; list[i] ; i++ ) { + Z_Free( list[i] ); + } + + Z_Free( list ); +} + + +/* +================ +FS_GetFileList +================ +*/ +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + int nFiles, i, nTotal, nLen; + char **pFiles = NULL; + + *listbuf = 0; + nFiles = 0; + nTotal = 0; + + if ( Q_stricmp( path, "$modlist" ) == 0 ) { + return FS_GetModList( listbuf, bufsize ); + } + + pFiles = FS_ListFiles( path, extension, &nFiles ); + + for ( i = 0; i < nFiles; i++ ) { + nLen = strlen( pFiles[i] ) + 1; + if ( nTotal + nLen + 1 < bufsize ) { + strcpy( listbuf, pFiles[i] ); + listbuf += nLen; + nTotal += nLen; + } else { + nFiles = i; + break; + } + } + + FS_FreeFileList( pFiles ); + + return nFiles; +} + +/* +======================= +Sys_ConcatenateFileLists + +mkv: Naive implementation. Concatenates three lists into a + new list, and frees the old lists from the heap. +bk001129 - from cvs1.17 (mkv) + +FIXME TTimo those two should move to common.c next to Sys_ListFiles +======================= + */ +static unsigned int Sys_CountFileList( char **list ) { + int i = 0; + + if ( list ) { + while ( *list ) + { + list++; + i++; + } + } + return i; +} + +static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 ) { + int totalLength = 0; + char** cat = NULL, **dst, **src; + + totalLength += Sys_CountFileList( list0 ); + totalLength += Sys_CountFileList( list1 ); + totalLength += Sys_CountFileList( list2 ); + + /* Create new list. */ + dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) ); + + /* Copy over lists. */ + if ( list0 ) { + for ( src = list0; *src; src++, dst++ ) + *dst = *src; + } + if ( list1 ) { + for ( src = list1; *src; src++, dst++ ) + *dst = *src; + } + if ( list2 ) { + for ( src = list2; *src; src++, dst++ ) + *dst = *src; + } + + // Terminate the list + *dst = NULL; + + // Free our old lists. + // NOTE: not freeing their content, it's been merged in dst and still being used + if ( list0 ) { + Z_Free( list0 ); + } + if ( list1 ) { + Z_Free( list1 ); + } + if ( list2 ) { + Z_Free( list2 ); + } + + return cat; +} + +/* +================ +FS_GetModList + +Returns a list of mod directory names +A mod directory is a peer to baseq3 with a pk3 in it +The directories are searched in base path, cd path and home path +================ +*/ +int FS_GetModList( char *listbuf, int bufsize ) { + int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen; + char **pFiles = NULL; + char **pPaks = NULL; + char *name, *path; + char descPath[MAX_OSPATH]; + fileHandle_t descHandle; + + int dummy; + char **pFiles0 = NULL; + char **pFiles1 = NULL; + char **pFiles2 = NULL; + qboolean bDrop = qfalse; + + *listbuf = 0; + nMods = nPotential = nTotal = 0; + + pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue ); + pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue ); + + // DHM - Nerve :: Don't add blank paths (root) + if ( fs_cdpath->string[0] ) { + pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue ); + } + + // we searched for mods in the three paths + // it is likely that we have duplicate names now, which we will cleanup below + pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 ); + nPotential = Sys_CountFileList( pFiles ); + + for ( i = 0 ; i < nPotential ; i++ ) { + name = pFiles[i]; + // NOTE: cleaner would involve more changes + // ignore duplicate mod directories + if ( i != 0 ) { + bDrop = qfalse; + for ( j = 0; j < i; j++ ) + { + if ( Q_stricmp( pFiles[j],name ) == 0 ) { + // this one can be dropped + bDrop = qtrue; + break; + } + } + } + if ( bDrop ) { + continue; + } + // we drop "baseq3" "." and ".." + if ( Q_stricmp( name, "main" ) && Q_stricmpn( name, ".", 1 ) ) { + // now we need to find some .pk3 files to validate the mod + // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?) + // we didn't keep the information when we merged the directory names, as to what OS Path it was found under + // so it could be in base path, cd path or home path + // we will try each three of them here (yes, it's a bit messy) + path = FS_BuildOSPath( fs_basepath->string, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); + Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present + + /* Try on cd path */ + if ( nPaks <= 0 ) { + path = FS_BuildOSPath( fs_cdpath->string, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); + Sys_FreeFileList( pPaks ); + } + + /* try on home path */ + if ( nPaks <= 0 ) { + path = FS_BuildOSPath( fs_homepath->string, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); + Sys_FreeFileList( pPaks ); + } + + if ( nPaks > 0 ) { + nLen = strlen( name ) + 1; + // nLen is the length of the mod path + // we need to see if there is a description available + descPath[0] = '\0'; + strcpy( descPath, name ); + strcat( descPath, "/description.txt" ); + nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle ); + if ( nDescLen > 0 && descHandle ) { + FILE *file; + file = FS_FileForHandle( descHandle ); + Com_Memset( descPath, 0, sizeof( descPath ) ); + nDescLen = fread( descPath, 1, 48, file ); + if ( nDescLen >= 0 ) { + descPath[nDescLen] = '\0'; + } + FS_FCloseFile( descHandle ); + } else { + strcpy( descPath, name ); + } + nDescLen = strlen( descPath ) + 1; + + if ( nTotal + nLen + 1 + nDescLen + 1 < bufsize ) { + strcpy( listbuf, name ); + listbuf += nLen; + strcpy( listbuf, descPath ); + listbuf += nDescLen; + nTotal += nLen + nDescLen; + nMods++; + } else { + break; + } + } + } + } + Sys_FreeFileList( pFiles ); + + return nMods; +} + + + + +//============================================================================ + +/* +================ +FS_Dir_f +================ +*/ +void FS_Dir_f( void ) { + char *path; + char *extension; + char **dirnames; + int ndirs; + int i; + + if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) { + Com_Printf( "usage: dir [extension]\n" ); + return; + } + + if ( Cmd_Argc() == 2 ) { + path = Cmd_Argv( 1 ); + extension = ""; + } else { + path = Cmd_Argv( 1 ); + extension = Cmd_Argv( 2 ); + } + + Com_Printf( "Directory of %s %s\n", path, extension ); + Com_Printf( "---------------\n" ); + + dirnames = FS_ListFiles( path, extension, &ndirs ); + + for ( i = 0; i < ndirs; i++ ) { + Com_Printf( "%s\n", dirnames[i] ); + } + FS_FreeFileList( dirnames ); +} + +/* +=========== +FS_ConvertPath +=========== +*/ +void FS_ConvertPath( char *s ) { + while ( *s ) { + if ( *s == '\\' || *s == ':' ) { + *s = '/'; + } + s++; + } +} + +/* +=========== +FS_PathCmp + +Ignore case and seprator char distinctions +=========== +*/ +int FS_PathCmp( const char *s1, const char *s2 ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if ( c1 < c2 ) { + return -1; // strings not equal + } + if ( c1 > c2 ) { + return 1; + } + } while ( c1 ); + + return 0; // strings are equal +} + +/* +================ +FS_SortFileList +================ +*/ +void FS_SortFileList( char **filelist, int numfiles ) { + int i, j, k, numsortedfiles; + char **sortedlist; + + sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) ); + sortedlist[0] = NULL; + numsortedfiles = 0; + for ( i = 0; i < numfiles; i++ ) { + for ( j = 0; j < numsortedfiles; j++ ) { + if ( FS_PathCmp( filelist[i], sortedlist[j] ) < 0 ) { + break; + } + } + for ( k = numsortedfiles; k > j; k-- ) { + sortedlist[k] = sortedlist[k - 1]; + } + sortedlist[j] = filelist[i]; + numsortedfiles++; + } + Com_Memcpy( filelist, sortedlist, numfiles * sizeof( *filelist ) ); + Z_Free( sortedlist ); +} + +/* +================ +FS_NewDir_f +================ +*/ +void FS_NewDir_f( void ) { + char *filter; + char **dirnames; + int ndirs; + int i; + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "usage: fdir \n" ); + Com_Printf( "example: fdir *q3dm*.bsp\n" ); + return; + } + + filter = Cmd_Argv( 1 ); + + Com_Printf( "---------------\n" ); + + dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs ); + + FS_SortFileList( dirnames, ndirs ); + + for ( i = 0; i < ndirs; i++ ) { + FS_ConvertPath( dirnames[i] ); + Com_Printf( "%s\n", dirnames[i] ); + } + Com_Printf( "%d files listed\n", ndirs ); + FS_FreeFileList( dirnames ); +} + +/* +============ +FS_Path_f + +============ +*/ +void FS_Path_f( void ) { + searchpath_t *s; + int i; + + Com_Printf( "Current search path:\n" ); + for ( s = fs_searchpaths; s; s = s->next ) { + if ( s->pack ) { + Com_Printf( "%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles ); + if ( fs_numServerPaks ) { + if ( !FS_PakIsPure( s->pack ) ) { + Com_Printf( " not on the pure list\n" ); + } else { + Com_Printf( " on the pure list\n" ); + } + } + } else { + Com_Printf( "%s/%s\n", s->dir->path, s->dir->gamedir ); + } + } + + Com_Printf( "\n" ); + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { + if ( fsh[i].handleFiles.file.o ) { + Com_Printf( "handle %i: %s\n", i, fsh[i].name ); + } + } +} + +/* +============ +FS_TouchFile_f + +The only purpose of this function is to allow game script files to copy +arbitrary files furing an "fs_copyfiles 1" run. +============ +*/ +void FS_TouchFile_f( void ) { + fileHandle_t f; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: touchFile \n" ); + return; + } + + FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } +} + +//=========================================================================== + + +static int QDECL paksort( const void *a, const void *b ) { + char *aa, *bb; + + aa = *(char **)a; + bb = *(char **)b; + + return FS_PathCmp( aa, bb ); +} + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads the zip headers +================ +*/ +#define MAX_PAKFILES 1024 +static void FS_AddGameDirectory( const char *path, const char *dir ) { + searchpath_t *sp; + int i; + searchpath_t *search; + pack_t *pak; + char *pakfile; + int numfiles; + char **pakfiles; + char *sorted[MAX_PAKFILES]; +// JPW NERVE + char mpsppakfilestring[4]; + + sprintf( mpsppakfilestring,"msp" ); +// jpw + + // this fixes the case where fs_basepath is the same as fs_cdpath + // which happens on full installs + for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { + if ( sp->dir && !Q_stricmp( sp->dir->path, path ) && !Q_stricmp( sp->dir->gamedir, dir ) ) { + return; // we've already got this one + } + } + + Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) ); + + // + // add the directory to the search path + // + search = Z_Malloc( sizeof( searchpath_t ) ); + search->dir = Z_Malloc( sizeof( *search->dir ) ); + + Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) ); + Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) ); + search->next = fs_searchpaths; + fs_searchpaths = search; + + // find all pak files in this directory + pakfile = FS_BuildOSPath( path, dir, "" ); + pakfile[ strlen( pakfile ) - 1 ] = 0; // strip the trailing slash + + pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse ); + + // sort them so that later alphabetic matches override + // earlier ones. This makes pak1.pk3 override pak0.pk3 + if ( numfiles > MAX_PAKFILES ) { + numfiles = MAX_PAKFILES; + } + for ( i = 0 ; i < numfiles ; i++ ) { + sorted[i] = pakfiles[i]; +// JPW NERVE KLUDGE: sorry, temp mod mp_* to _p_* so "mp_pak*" gets alphabetically sorted before "pak*" + + if ( !Q_strncmp( sorted[i],"mp_",3 ) ) { + memcpy( sorted[i],"zz",2 ); + } + +// jpw + } + + qsort( sorted, numfiles, 4, paksort ); + + for ( i = 0 ; i < numfiles ; i++ ) { + if ( Q_strncmp( sorted[i],"sp_",3 ) ) { // JPW NERVE -- exclude sp_* +// JPW NERVE KLUDGE: fix filenames broken in mp/sp/pak sort above + + if ( !Q_strncmp( sorted[i],"zz_",3 ) ) { + memcpy( sorted[i],"mp",2 ); + } + +// jpw + pakfile = FS_BuildOSPath( path, dir, sorted[i] ); + if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 ) { + continue; + } + // store the game name for downloading + strcpy( pak->pakGamename, dir ); + + search = Z_Malloc( sizeof( searchpath_t ) ); + search->pack = pak; + search->next = fs_searchpaths; + fs_searchpaths = search; + } + } + + // done + Sys_FreeFileList( pakfiles ); +} + +/* +================ +FS_idPak +================ +*/ +qboolean FS_idPak( char *pak, char *base ) { + int i; + + if ( !FS_FilenameCompare( pak, va( "%s/mp_bin", base ) ) ) { + return qtrue; + } + + for ( i = 0; i < NUM_ID_PAKS; i++ ) { + if ( !FS_FilenameCompare( pak, va( "%s/pak%d", base, i ) ) ) { + break; + } +// JPW NERVE -- this fn prevents external sources from downloading/overwriting official files, so exclude both SP and MP files from this list as well + if ( !FS_FilenameCompare( pak, va( "%s/mp_pak%d",base,i ) ) ) { + break; + } + if ( !FS_FilenameCompare( pak, va( "%s/sp_pak%d",base,i ) ) ) { + break; + } +// jpw + } + if ( i < NUM_ID_PAKS ) { + return qtrue; + } + return qfalse; +} + +/* +================ +FS_ComparePaks + +---------------- +dlstring == qtrue + +Returns a list of pak files that we should download from the server. They all get stored +in the current gamedir and an FS_Restart will be fired up after we download them all. + +The string is the format: + +@remotename@localname [repeat] + +static int fs_numServerReferencedPaks; +static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; +static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; + +---------------- +dlstring == qfalse + +we are not interested in a download string format, we want something human-readable +(this is used for diagnostics while connecting to a pure server) + +================ +*/ +qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { + searchpath_t *sp; + qboolean havepak, badchecksum; + int i; + + if ( !fs_numServerReferencedPaks ) { + return qfalse; // Server didn't send any pack information along + } + + *neededpaks = 0; + + for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) { + // Ok, see if we have this pak file + badchecksum = qfalse; + havepak = qfalse; + + // never autodownload any of the id paks + if ( FS_idPak( fs_serverReferencedPakNames[i], "main" ) ) { + continue; + } + + for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { + if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) { + havepak = qtrue; // This is it! + break; + } + } + + if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { + // Don't got it + + if ( dlstring ) { + // Remote name + Q_strcat( neededpaks, len, "@" ); + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + + // Local name + Q_strcat( neededpaks, len, "@" ); + // Do we have one with the same name? + if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) { + char st[MAX_ZPATH]; + // We already have one called this, we need to download it to another name + // Make something up with the checksum in it + Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] ); + Q_strcat( neededpaks, len, st ); + } else + { + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + } + } else + { + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + // Do we have one with the same name? + if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) { + Q_strcat( neededpaks, len, " (local file exists with wrong checksum)" ); + } + Q_strcat( neededpaks, len, "\n" ); + } + } + } + + if ( *neededpaks ) { + Com_Printf( "Need paks: %s\n", neededpaks ); + return qtrue; + } + + return qfalse; // We have them all +} + +/* +================ +FS_Shutdown + +Frees all resources and closes all files +================ +*/ +void FS_Shutdown( qboolean closemfp ) { + searchpath_t *p, *next; + int i; + + for ( i = 0; i < MAX_FILE_HANDLES; i++ ) { + if ( fsh[i].fileSize ) { + FS_FCloseFile( i ); + } + } + + // free everything + for ( p = fs_searchpaths ; p ; p = next ) { + next = p->next; + + if ( p->pack ) { + unzClose( p->pack->handle ); + Z_Free( p->pack->buildBuffer ); + Z_Free( p->pack ); + } + if ( p->dir ) { + Z_Free( p->dir ); + } + Z_Free( p ); + } + + // any FS_ calls will now be an error until reinitialized + fs_searchpaths = NULL; + + Cmd_RemoveCommand( "path" ); + Cmd_RemoveCommand( "dir" ); + Cmd_RemoveCommand( "fdir" ); + Cmd_RemoveCommand( "touchFile" ); + +#ifdef FS_MISSING + if ( closemfp ) { + fclose( missingFiles ); + } +#endif +} + +/* +================ +FS_ReorderPurePaks +NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*) + this can lead to misleading situations, see show_bug.cgi?id=540 +================ +*/ +static void FS_ReorderPurePaks() { + searchpath_t *s; + int i; + searchpath_t **p_insert_index, // for linked list reordering + **p_previous; // when doing the scan + + // only relevant when connected to pure server + if ( !fs_numServerPaks ) { + return; + } + + fs_reordered = qfalse; + + p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list + for ( i = 0 ; i < fs_numServerPaks ; i++ ) { + p_previous = p_insert_index; // track the pointer-to-current-item + for ( s = *p_insert_index; s; s = s->next ) { // the part of the list before p_insert_index has been sorted already + if ( s->pack && fs_serverPaks[i] == s->pack->checksum ) { + fs_reordered = qtrue; + // move this element to the insert list + *p_previous = s->next; + s->next = *p_insert_index; + *p_insert_index = s; + // increment insert list + p_insert_index = &s->next; + break; // iterate to next server pack + } + p_previous = &s->next; + } + } + +} + +/* +================ +FS_Startup +================ +*/ +static void FS_Startup( const char *gameName ) { + const char *homePath; + cvar_t *fs; + + Com_Printf( "----- FS_Startup -----\n" ); + + fs_debug = Cvar_Get( "fs_debug", "0", 0 ); + fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT ); + fs_cdpath = Cvar_Get( "fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT ); + fs_basepath = Cvar_Get( "fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT ); + fs_basegame = Cvar_Get( "fs_basegame", "", CVAR_INIT ); + homePath = Sys_DefaultHomePath(); + if ( !homePath || !homePath[0] ) { + homePath = fs_basepath->string; + } + fs_homepath = Cvar_Get( "fs_homepath", homePath, CVAR_INIT ); + fs_gamedirvar = Cvar_Get( "fs_game", "", CVAR_INIT | CVAR_SYSTEMINFO ); + fs_restrict = Cvar_Get( "fs_restrict", "", CVAR_INIT ); + + // add search path elements in reverse priority order + if ( fs_cdpath->string[0] ) { + FS_AddGameDirectory( fs_cdpath->string, gameName ); + } + if ( fs_basepath->string[0] ) { + FS_AddGameDirectory( fs_basepath->string, gameName ); + } + // fs_homepath is somewhat particular to *nix systems, only add if relevant + // NOTE: same filtering below for mods and basegame + if ( fs_basepath->string[0] && Q_stricmp( fs_homepath->string,fs_basepath->string ) ) { + FS_AddGameDirectory( fs_homepath->string, gameName ); + } + + // check for additional base game so mods can be based upon other mods + if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) { + if ( fs_cdpath->string[0] ) { + FS_AddGameDirectory( fs_cdpath->string, fs_basegame->string ); + } + if ( fs_basepath->string[0] ) { + FS_AddGameDirectory( fs_basepath->string, fs_basegame->string ); + } + if ( fs_homepath->string[0] && Q_stricmp( fs_homepath->string,fs_basepath->string ) ) { + FS_AddGameDirectory( fs_homepath->string, fs_basegame->string ); + } + } + + // check for additional game folder for mods + if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) { + if ( fs_cdpath->string[0] ) { + FS_AddGameDirectory( fs_cdpath->string, fs_gamedirvar->string ); + } + if ( fs_basepath->string[0] ) { + FS_AddGameDirectory( fs_basepath->string, fs_gamedirvar->string ); + } + if ( fs_homepath->string[0] && Q_stricmp( fs_homepath->string,fs_basepath->string ) ) { + FS_AddGameDirectory( fs_homepath->string, fs_gamedirvar->string ); + } + } + + Com_ReadCDKey( BASEGAME ); + fs = Cvar_Get( "fs_game", "", CVAR_INIT | CVAR_SYSTEMINFO ); + if ( fs && fs->string[0] != 0 ) { + Com_AppendCDKey( fs->string ); + } + + // add our commands + Cmd_AddCommand( "path", FS_Path_f ); + Cmd_AddCommand( "dir", FS_Dir_f ); + Cmd_AddCommand( "fdir", FS_NewDir_f ); + Cmd_AddCommand( "touchFile", FS_TouchFile_f ); + + // show_bug.cgi?id=506 + // reorder the pure pk3 files according to server order + FS_ReorderPurePaks(); + + // print the current search paths + FS_Path_f(); + + fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified + + Com_Printf( "----------------------\n" ); + +#ifdef FS_MISSING + if ( missingFiles == NULL ) { + missingFiles = fopen( "\\missing.txt", "ab" ); + } +#endif + Com_Printf( "%d files in pk3 files\n", fs_packFiles ); +} + + +/* +=================== +FS_SetRestrictions + +Looks for product keys and restricts media add on ability +if the full version is not found +=================== +*/ +static void FS_SetRestrictions( void ) { + searchpath_t *path; + +#ifndef PRE_RELEASE_DEMO + // if fs_restrict is set, don't even look for the id file, + // which allows the demo release to be tested even if + // the full game is present + if ( !fs_restrict->integer ) { + // look for the full game id + + // NO RESTRICTIONS IN RETAIL GAME + return; + } +#endif + Cvar_Set( "fs_restrict", "1" ); + + Com_Printf( "\nRunning in restricted demo mode.\n\n" ); + + // restart the filesystem with just the demo directory + FS_Shutdown( qfalse ); + +#ifdef PRE_RELEASE_DEMO + FS_Startup( DEMOGAME ); +#else + FS_Startup( BASEGAME ); +#endif + + // make sure that the pak file has the header checksum we expect + for ( path = fs_searchpaths ; path ; path = path->next ) { + if ( path->pack ) { + // a tiny attempt to keep the checksum from being scannable from the exe + if ( ( path->pack->checksum ^ 0x02261994u ) + != ( DEMO_PAK_CHECKSUM ^ 0x02261994u ) ) { + Com_Error( ERR_FATAL, "Corrupted pak0.pk3: %u", path->pack->checksum ); + } + } + } +} + +/* +===================== +FS_GamePureChecksum +Returns the checksum of the pk3 from which the server loaded the qagame.qvm +NOTE TTimo: this is not used in RTCW so far +===================== +*/ +const char *FS_GamePureChecksum( void ) { + static char info[MAX_STRING_TOKENS]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if ( search->pack->referenced & FS_QAGAME_REF ) { + Com_sprintf( info, sizeof( info ), "%d", search->pack->checksum ); + } + } + } + + return info; +} + +#if !defined( DO_LIGHT_DEDICATED ) +/* +===================== +FS_LoadedPakChecksums + +Returns a space separated string containing the checksums of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + Q_strcat( info, sizeof( info ), va( "%i ", search->pack->checksum ) ); + } + + return info; +} + +/* +===================== +FS_LoadedPakNames + +Returns a space separated string containing the names of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakNames( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + if ( *info ) { + Q_strcat( info, sizeof( info ), " " ); + } + Q_strcat( info, sizeof( info ), search->pack->pakBasename ); + } + + return info; +} + +/* +===================== +FS_LoadedPakPureChecksums + +Returns a space separated string containing the pure checksums of all loaded pk3 files. +Servers with sv_pure use these checksums to compare with the checksums the clients send +back to the server. +===================== +*/ +const char *FS_LoadedPakPureChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + Q_strcat( info, sizeof( info ), va( "%i ", search->pack->pure_checksum ) ); + } + + // DO_LIGHT_DEDICATED + // only comment out when you need a new pure checksums string + //Com_DPrintf("FS_LoadPakPureChecksums: %s\n", info); + + return info; +} + +/* +===================== +FS_ReferencedPakChecksums + +Returns a space separated string containing the checksums of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. +===================== +*/ +const char *FS_ReferencedPakChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if ( search->pack->referenced || Q_stricmpn( search->pack->pakGamename, BASEGAME, strlen( BASEGAME ) ) ) { + Q_strcat( info, sizeof( info ), va( "%i ", search->pack->checksum ) ); + } + } + } + + return info; +} + +/* +===================== +FS_ReferencedPakNames + +Returns a space separated string containing the names of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. +===================== +*/ +const char *FS_ReferencedPakNames( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + // we want to return ALL pk3's from the fs_game path + // and referenced one's from baseq3 + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if ( *info ) { + Q_strcat( info, sizeof( info ), " " ); + } + if ( search->pack->referenced || Q_stricmpn( search->pack->pakGamename, BASEGAME, strlen( BASEGAME ) ) ) { + Q_strcat( info, sizeof( info ), search->pack->pakGamename ); + Q_strcat( info, sizeof( info ), "/" ); + Q_strcat( info, sizeof( info ), search->pack->pakBasename ); + } + } + } + + return info; +} + +/* +===================== +FS_ReferencedPakPureChecksums + +Returns a space separated string containing the pure checksums of all referenced pk3 files. +Servers with sv_pure set will get this string back from clients for pure validation + +The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..." + +NOTE TTimo - DO_LIGHT_DEDICATED +this function is only used by the client to build the string sent back to server +we don't have any need of overriding it for light, but it's useless in dedicated +===================== +*/ +const char *FS_ReferencedPakPureChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + int nFlags, numPaks, checksum; + + info[0] = 0; + + checksum = fs_checksumFeed; + + numPaks = 0; + for ( nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1 ) { + if ( nFlags & FS_GENERAL_REF ) { + // add a delimter between must haves and general refs + //Q_strcat(info, sizeof(info), "@ "); + info[strlen( info ) + 1] = '\0'; + info[strlen( info ) + 2] = '\0'; + info[strlen( info )] = '@'; + info[strlen( info )] = ' '; + } + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file and has it been referenced based on flag? + if ( search->pack && ( search->pack->referenced & nFlags ) ) { + Q_strcat( info, sizeof( info ), va( "%i ", search->pack->pure_checksum ) ); + if ( nFlags & ( FS_CGAME_REF | FS_UI_REF ) ) { + break; + } + checksum ^= search->pack->pure_checksum; + numPaks++; + } + } + if ( fs_fakeChkSum != 0 ) { + // only added if a non-pure file is referenced + Q_strcat( info, sizeof( info ), va( "%i ", fs_fakeChkSum ) ); + } + } + // last checksum is the encoded number of referenced pk3s + checksum ^= numPaks; + Q_strcat( info, sizeof( info ), va( "%i ", checksum ) ); + + return info; +} +#else // DO_LIGHT_DEDICATED implementation follows + +/* +========================================================================================= +DO_LIGHT_DEDICATED, general notes +we are going to fake the checksums sent to the clients +that only matters to the pk3 we have replaced by their lighter version, currently: + +Cvar_Set2: sv_pakNames mp_pakmaps0 mp_pak2 mp_pak1 mp_pak0 pak0 +Cvar_Set2: sv_paks -1153491798 125907563 -1023558518 764840216 1886207346 + +all the files above have their 'server required' content collapsed into a single pak0.pk3 + +the other .pk3 files should be handled as usual + +more details are in unix/dedicated-only.txt + +========================================================================================= +*/ + +// our target faked checksums +// those don't need to be encrypted or anything, that's what you see in the +set developer 1 +static const char* pak_checksums = "-137448799 131270674 125907563 -1023558518 764840216 1886207346"; +static const char* pak_names = "mp_pak4 mp_pak3 mp_pak2 mp_pak1 mp_pak0 pak0"; + +/* +this is the pure checksum string for a constant value of fs_checksumFeed we have choosen (see SV_SpawnServer) +to obtain the new string for a different fs_checksumFeed value, run a regular server and enable the relevant +verbosity code in SV_SpawnServer and FS_LoadedPakPureChecksums (the full server version of course) + +NOTE: if you have an mp_bin in the middle, you need to take out it's checksum + (we keep mp_bin out of the faked stuff because we don't want to have to update those feeds too often heh) + +once you have the clear versions, you can shift them by commenting out the code chunk in FS_RandChecksumFeed +you need to use the right line in FS_LoadedPakPureChecksums wether you are running on clear strings, or shifted ones +*/ + +/* +// clear checksums, rebuild those from a regular server and you will shift them next +static const int feeds[5] = { + 0xd6009839, 0x636bb1d5, 0x198df4c9, 0x7ffa631b, 0x8f89a69e +}; + +static const char* pak_purechecksums[5] = { + "943814896 694898104 1407923890 242847633 1117823230 -1543700213", + "-111135514 976363775 -1066586315 -509503305 226888806 623380740", + "465689568 1394972621 1593048073 488347192 -238809598 -396332776", + "-2000534548 253432450 -1505880367 682854303 -1183636432 -1745648892", + "-588301396 -637070806 -49124646 831116909 -666702847 1152718748" +}; +*/ + +static const int feeds[5] = { + 0xd6009839, 0x636bb1d5, 0x198df4c9, 0x7ffa631b, 0x8f89a69e +}; + +// shifted strings, so that it's not directly scannable from exe +// see FS_RandChecksumFeed to generate them +static const char* pak_purechecksums[5] = { + "FA@E>AEFC-CFAEFE>=A->A=DF?@EF=-?A?EADC@@->>>DE?@?@=-:>BA@D==?>@", + ";????ACC?B.GEDADAEEC.;?>DDCFDA?C.;C>GC>AA>C.@@DFFFF>D.D@AAF>EB>", + "CEDEGHDEG/@BHCHFAEA@/@DHB?CG?FB/CGGBCF@HA/FIIDABDJG1>GDHAHAIAG1>EJBCEGEG1IDBBBGJAJ1>GGGHACIEH1BBFCHBIHEI" +}; + +// counter to walk through the randomized list +static int feed_index = -1; + +static int lookup_randomized[5] = { 0, 1, 2, 3, 4 }; + +/* +===================== +randomize the order of the 5 checksums we rely on +5 random swaps of the table +===================== +*/ +void FS_InitRandomFeed() { + int i, swap, aux; + for ( i = 0; i < 5; i++ ) + { + swap = (int)( 5.0 * rand() / ( RAND_MAX + 1.0 ) ); + aux = lookup_randomized[i]; lookup_randomized[i] = lookup_randomized[swap]; lookup_randomized[swap] = aux; + } +} + +/* +===================== +FS_RandChecksumFeed + +Return a random checksum feed among our list +we keep the seed and use it when requested for the pure checksum +===================== +*/ +int FS_RandChecksumFeed() { + /* + // use this to dump shifted versions of the pure checksum strings + int i; + for(i=0;i<5;i++) + { + Com_Printf("FS_RandChecksumFeed: %s\n", FS_ShiftStr(pak_purechecksums[i], 13+i)); + } + */ + if ( feed_index == -1 ) { + FS_InitRandomFeed(); + } + feed_index = ( feed_index + 1 ) % 5; + return feeds[lookup_randomized[feed_index]]; +} + +/* +===================== +FS_LoadedPakChecksums + +Returns a space separated string containing the checksums of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. + +DO_LIGHT_DEDICATED: +drop lightweight pak0 checksum, put the faked pk3s checksums instead +===================== +*/ +const char *FS_LoadedPakChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + if ( strcmp( search->pack->pakBasename,"pak0" ) ) { + // this is a regular pk3 + Q_strcat( info, sizeof( info ), va( "%i ", search->pack->checksum ) ); + } else + { + // this is the light pk3 + Q_strcat( info, sizeof( info ), va( "%s ", pak_checksums ) ); + } + } + + return info; +} + +/* +===================== +FS_LoadedPakNames + +Returns a space separated string containing the names of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. + +DO_LIGHT_DEDICATED: +drop lightweight pak0 name, put the faked pk3s names instead +===================== +*/ +const char *FS_LoadedPakNames( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + if ( *info ) { + Q_strcat( info, sizeof( info ), " " ); + } + if ( strcmp( search->pack->pakBasename,"pak0" ) ) { + // regular pk3 + Q_strcat( info, sizeof( info ), search->pack->pakBasename ); + } else + { + // light pk3 + Q_strcat( info, sizeof( info ), pak_names ); + } + } + + return info; +} + +/* +===================== +FS_LoadedPakPureChecksums + +Returns a space separated string containing the pure checksums of all loaded pk3 files. +Servers with sv_pure use these checksums to compare with the checksums the clients send +back to the server. + +DO_LIGHT_DEDICATED: +FS_LoadPakChecksums to send the pak string to the client +FS_LoadPakPureChecksums is used locally to compare against what the client sends back + +the pure_checksums are computed by Com_MemoryBlockChecksum with a random key (fs_checksumFeed) +since we can't do this on restricted server, we always use the same fs_checksumFeed value + +drop lightweight pak0 checksum, put the faked pk3s pure checksums instead + +===================== +*/ +const char *FS_LoadedPakPureChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + if ( strcmp( search->pack->pakBasename,"pak0" ) ) { + // this is a regular pk3 + Q_strcat( info, sizeof( info ), va( "%i ", search->pack->pure_checksum ) ); + } else + { + // this is the light pk3 + // use this if you are running on shifted strings + Q_strcat( info, sizeof( info ), va( "%s ", FS_ShiftStr( pak_purechecksums[lookup_randomized[feed_index]], -13 - lookup_randomized[feed_index] ) ) ); + // use this if you are running on clear checksum strings instead of shifted ones + //Q_strcat( info, sizeof( info ), va("%s ", pak_purechecksums[lookup_randomized[feed_index]] ) ); + } + } + + return info; +} + +/* +===================== +FS_ReferencedPakChecksums + +Returns a space separated string containing the checksums of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. + +DO_LIGHT_DEDICATED: +don't send the checksum of pak0 (even if it's referenced) + +NOTE: +do we need to fake referenced paks too? +those are Id paks, so you can't download them +mp_pakmaps0 would be a worthy candidate for download though, but we don't have it anyway +the only thing if we omit sending of some referenced stuff, you don't get the console message that says "you're missing this" +===================== +*/ +const char *FS_ReferencedPakChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if ( search->pack->referenced ) { + if ( strcmp( search->pack->pakBasename, "pak0" ) ) { + // this is not the light pk3 + Q_strcat( info, sizeof( info ), va( "%i ", search->pack->checksum ) ); + } + } + } + } + + return info; +} + +/* +===================== +FS_ReferencedPakNames + +Returns a space separated string containing the names of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. + +DO_LIGHT_DEDICATED: +don't send pak0 see above for details +===================== +*/ +const char *FS_ReferencedPakNames( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + // we want to return ALL pk3's from the fs_game path + // and referenced one's from baseq3 + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if ( *info ) { + Q_strcat( info, sizeof( info ), " " ); + } + if ( search->pack->referenced ) { + if ( strcmp( search->pack->pakBasename, "pak0" ) ) { + // this is not the light pk3 + Q_strcat( info, sizeof( info ), search->pack->pakGamename ); + Q_strcat( info, sizeof( info ), "/" ); + Q_strcat( info, sizeof( info ), search->pack->pakBasename ); + } + } + } + } + + return info; +} + +#endif + +/* +===================== +FS_ClearPakReferences +===================== +*/ +void FS_ClearPakReferences( int flags ) { + searchpath_t *search; + + if ( !flags ) { + flags = -1; + } + for ( search = fs_searchpaths; search; search = search->next ) { + // is the element a pak file and has it been referenced? + if ( search->pack ) { + search->pack->referenced &= ~flags; + } + } +} + + +/* +===================== +FS_PureServerSetLoadedPaks + +If the string is empty, all data sources will be allowed. +If not empty, only pk3 files that match one of the space +separated checksums will be checked for files, with the +exception of .cfg and .dat files. +===================== +*/ +void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) { + int i, c, d; + + Cmd_TokenizeString( pakSums ); + + c = Cmd_Argc(); + if ( c > MAX_SEARCH_PATHS ) { + c = MAX_SEARCH_PATHS; + } + + fs_numServerPaks = c; + + for ( i = 0 ; i < c ; i++ ) { + fs_serverPaks[i] = atoi( Cmd_Argv( i ) ); + } + + if ( fs_numServerPaks ) { + Com_DPrintf( "Connected to a pure server.\n" ); + } else + { + if ( fs_reordered ) { + // show_bug.cgi?id=540 + // force a restart to make sure the search order will be correct + Com_DPrintf( "FS search reorder is required\n" ); + FS_Restart( fs_checksumFeed ); + return; + } + } + + for ( i = 0 ; i < c ; i++ ) { + if ( fs_serverPakNames[i] ) { + Z_Free( fs_serverPakNames[i] ); + } + fs_serverPakNames[i] = NULL; + } + if ( pakNames && *pakNames ) { + Cmd_TokenizeString( pakNames ); + + d = Cmd_Argc(); + if ( d > MAX_SEARCH_PATHS ) { + d = MAX_SEARCH_PATHS; + } + + for ( i = 0 ; i < d ; i++ ) { + fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) ); + } + } +} + +/* +===================== +FS_PureServerSetReferencedPaks + +The checksums and names of the pk3 files referenced at the server +are sent to the client and stored here. The client will use these +checksums to see if any pk3 files need to be auto-downloaded. +===================== +*/ +void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) { + int i, c, d; + + Cmd_TokenizeString( pakSums ); + + c = Cmd_Argc(); + if ( c > MAX_SEARCH_PATHS ) { + c = MAX_SEARCH_PATHS; + } + + fs_numServerReferencedPaks = c; + + for ( i = 0 ; i < c ; i++ ) { + fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) ); + } + + for ( i = 0 ; i < c ; i++ ) { + if ( fs_serverReferencedPakNames[i] ) { + Z_Free( fs_serverReferencedPakNames[i] ); + } + fs_serverReferencedPakNames[i] = NULL; + } + if ( pakNames && *pakNames ) { + Cmd_TokenizeString( pakNames ); + + d = Cmd_Argc(); + if ( d > MAX_SEARCH_PATHS ) { + d = MAX_SEARCH_PATHS; + } + + for ( i = 0 ; i < d ; i++ ) { + fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) ); + } + } +} + +/* +================ +FS_InitFilesystem + +Called only at inital startup, not when the filesystem +is resetting due to a game change +================ +*/ +void FS_InitFilesystem( void ) { + // allow command line parms to override our defaults + // we have to specially handle this, because normal command + // line variable sets don't happen until after the filesystem + // has already been initialized + Com_StartupVariable( "fs_cdpath" ); + Com_StartupVariable( "fs_basepath" ); + Com_StartupVariable( "fs_homepath" ); + Com_StartupVariable( "fs_game" ); + Com_StartupVariable( "fs_copyfiles" ); + Com_StartupVariable( "fs_restrict" ); + + // try to start up normally + FS_Startup( BASEGAME ); + + // see if we are going to allow add-ons + FS_SetRestrictions(); + +#ifndef UPDATE_SERVER + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { + // TTimo - added some verbosity, 'couldn't load default.cfg' confuses the hell out of users + Com_Error( ERR_FATAL, "Couldn't load default.cfg - I am missing essential files - verify your installation?" ); + } +#endif + + Q_strncpyz( lastValidBase, fs_basepath->string, sizeof( lastValidBase ) ); + Q_strncpyz( lastValidGame, fs_gamedirvar->string, sizeof( lastValidGame ) ); +} + + +/* +================ +FS_Restart +================ +*/ +void FS_Restart( int checksumFeed ) { + + // free anything we currently have loaded + FS_Shutdown( qfalse ); + + // set the checksum feed + fs_checksumFeed = checksumFeed; + + // clear pak references + FS_ClearPakReferences( 0 ); + + // try to start up normally + FS_Startup( BASEGAME ); + + // see if we are going to allow add-ons + FS_SetRestrictions(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { + // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3 + // (for instance a TA demo server) + if ( lastValidBase[0] ) { + FS_PureServerSetLoadedPaks( "", "" ); + Cvar_Set( "fs_basepath", lastValidBase ); + Cvar_Set( "fs_gamedirvar", lastValidGame ); + lastValidBase[0] = '\0'; + lastValidGame[0] = '\0'; + Cvar_Set( "fs_restrict", "0" ); + FS_Restart( checksumFeed ); + Com_Error( ERR_DROP, "Invalid game folder\n" ); + return; + } + Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); + } + + // bk010116 - new check before safeMode + if ( Q_stricmp( fs_gamedirvar->string, lastValidGame ) ) { + // skip the wolfconfig.cfg if "safe" is on the command line + if ( !Com_SafeMode() ) { + Cbuf_AddText( "exec wolfconfig_mp.cfg\n" ); + } + } + + Q_strncpyz( lastValidBase, fs_basepath->string, sizeof( lastValidBase ) ); + Q_strncpyz( lastValidGame, fs_gamedirvar->string, sizeof( lastValidGame ) ); + +} + +/* +================= +FS_ConditionalRestart +restart if necessary + + FIXME TTimo +this doesn't catch all cases where an FS_Restart is necessary +see show_bug.cgi?id=478 +================= +*/ +qboolean FS_ConditionalRestart( int checksumFeed ) { + if ( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) { + FS_Restart( checksumFeed ); + return qtrue; + } + return qfalse; +} + +/* +======================================================================================== + +Handle based file calls for virtual machines + +======================================================================================== +*/ + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + int r; + qboolean sync; + + sync = qfalse; + + switch ( mode ) { + case FS_READ: + r = FS_FOpenFileRead( qpath, f, qtrue ); + break; + case FS_WRITE: +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'WlfB'; + _fcreator = 'WlfM'; + } +#endif + *f = FS_FOpenFileWrite( qpath ); + r = 0; + if ( *f == 0 ) { + r = -1; + } + break; + case FS_APPEND_SYNC: + sync = qtrue; + case FS_APPEND: +#ifdef __MACOS__ //DAJ MacOS file typing + { + extern _MSL_IMP_EXP_C long _fcreator, _ftype; + _ftype = 'WlfB'; + _fcreator = 'WlfM'; + } +#endif + *f = FS_FOpenFileAppend( qpath ); + r = 0; + if ( *f == 0 ) { + r = -1; + } + break; + default: + Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" ); + return -1; + } + + if ( !f ) { + return r; + } + + if ( *f ) { + if ( fsh[*f].zipFile == qtrue ) { + fsh[*f].baseOffset = unztell( fsh[*f].handleFiles.file.z ); + } else { + fsh[*f].baseOffset = ftell( fsh[*f].handleFiles.file.o ); + } + fsh[*f].fileSize = r; + fsh[*f].streamed = qfalse; + + // uncommenting this makes fs_reads + // use the background threads -- + // MAY be faster for loading levels depending on the use of file io + // q3a not faster + // wolf not faster + +// if (mode == FS_READ) { +// Sys_BeginStreamedFile( *f, 0x4000 ); +// fsh[*f].streamed = qtrue; +// } + } + fsh[*f].handleSync = sync; + + return r; +} + +int FS_FTell( fileHandle_t f ) { + int pos; + if ( fsh[f].zipFile == qtrue ) { + pos = unztell( fsh[f].handleFiles.file.z ); + } else { + pos = ftell( fsh[f].handleFiles.file.o ); + } + return pos; +} + +void FS_Flush( fileHandle_t f ) { + fflush( fsh[f].handleFiles.file.o ); +} + +// CVE-2006-2082 +// compared requested pak against the names as we built them in FS_ReferencedPakNames +qboolean FS_VerifyPak( const char *pak ) { + char teststring[ BIG_INFO_STRING ]; + searchpath_t *search; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + if ( search->pack ) { + Q_strncpyz( teststring, search->pack->pakGamename, sizeof( teststring ) ); + Q_strcat( teststring, sizeof( teststring ), "/" ); + Q_strcat( teststring, sizeof( teststring ), search->pack->pakBasename ); + Q_strcat( teststring, sizeof( teststring ), ".pk3" ); + if ( !Q_stricmp( teststring, pak ) ) { + return qtrue; + } + } + } + return qfalse; +} diff --git a/src/qcommon/huffman.c b/src/qcommon/huffman.c new file mode 100644 index 0000000..591d94f --- /dev/null +++ b/src/qcommon/huffman.c @@ -0,0 +1,444 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book. The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +#include "../game/q_shared.h" +#include "qcommon.h" + +static int bloc = 0; + +void Huff_putBit( int bit, byte *fout, int *offset ) { + bloc = *offset; + if ( ( bloc & 7 ) == 0 ) { + fout[( bloc >> 3 )] = 0; + } + fout[( bloc >> 3 )] |= bit << ( bloc & 7 ); + bloc++; + *offset = bloc; +} + +int Huff_getBit( byte *fin, int *offset ) { + int t; + bloc = *offset; + t = ( fin[( bloc >> 3 )] >> ( bloc & 7 ) ) & 0x1; + bloc++; + *offset = bloc; + return t; +} + +/* Add a bit to the output file (buffered) */ +static void add_bit( char bit, byte *fout ) { + if ( ( bloc & 7 ) == 0 ) { + fout[( bloc >> 3 )] = 0; + } + fout[( bloc >> 3 )] |= bit << ( bloc & 7 ); + bloc++; +} + +/* Receive one bit from the input file (buffered) */ +static int get_bit( byte *fin ) { + int t; + t = ( fin[( bloc >> 3 )] >> ( bloc & 7 ) ) & 0x1; + bloc++; + return t; +} + +static node_t **get_ppnode( huff_t* huff ) { + node_t **tppnode; + if ( !huff->freelist ) { + return &( huff->nodePtrs[huff->blocPtrs++] ); + } else { + tppnode = huff->freelist; + huff->freelist = (node_t **)*tppnode; + return tppnode; + } +} + +static void free_ppnode( huff_t* huff, node_t **ppnode ) { + *ppnode = (node_t *)huff->freelist; + huff->freelist = ppnode; +} + +/* Swap the location of these two nodes in the tree */ +static void swap( huff_t* huff, node_t *node1, node_t *node2 ) { + node_t *par1, *par2; + + par1 = node1->parent; + par2 = node2->parent; + + if ( par1 ) { + if ( par1->left == node1 ) { + par1->left = node2; + } else { + par1->right = node2; + } + } else { + huff->tree = node2; + } + + if ( par2 ) { + if ( par2->left == node2 ) { + par2->left = node1; + } else { + par2->right = node1; + } + } else { + huff->tree = node1; + } + + node1->parent = par2; + node2->parent = par1; +} + +/* Swap these two nodes in the linked list (update ranks) */ +static void swaplist( node_t *node1, node_t *node2 ) { + node_t *par1; + + par1 = node1->next; + node1->next = node2->next; + node2->next = par1; + + par1 = node1->prev; + node1->prev = node2->prev; + node2->prev = par1; + + if ( node1->next == node1 ) { + node1->next = node2; + } + if ( node2->next == node2 ) { + node2->next = node1; + } + if ( node1->next ) { + node1->next->prev = node1; + } + if ( node2->next ) { + node2->next->prev = node2; + } + if ( node1->prev ) { + node1->prev->next = node1; + } + if ( node2->prev ) { + node2->prev->next = node2; + } +} + +/* Do the increments */ +static void increment( huff_t* huff, node_t *node ) { + node_t *lnode; + + if ( !node ) { + return; + } + + if ( node->next != NULL && node->next->weight == node->weight ) { + lnode = *node->head; + if ( lnode != node->parent ) { + swap( huff, lnode, node ); + } + swaplist( lnode, node ); + } + if ( node->prev && node->prev->weight == node->weight ) { + *node->head = node->prev; + } else { + *node->head = NULL; + free_ppnode( huff, node->head ); + } + node->weight++; + if ( node->next && node->next->weight == node->weight ) { + node->head = node->next->head; + } else { + node->head = get_ppnode( huff ); + *node->head = node; + } + if ( node->parent ) { + increment( huff, node->parent ); + if ( node->prev == node->parent ) { + swaplist( node, node->parent ); + if ( *node->head == node ) { + *node->head = node->parent; + } + } + } +} + +void Huff_addRef( huff_t* huff, byte ch ) { + node_t *tnode, *tnode2; + if ( huff->loc[ch] == NULL ) { /* if this is the first transmission of this node */ + tnode = &( huff->nodeList[huff->blocNode++] ); + tnode2 = &( huff->nodeList[huff->blocNode++] ); + + tnode2->symbol = INTERNAL_NODE; + tnode2->weight = 1; + tnode2->next = huff->lhead->next; + if ( huff->lhead->next ) { + huff->lhead->next->prev = tnode2; + if ( huff->lhead->next->weight == 1 ) { + tnode2->head = huff->lhead->next->head; + } else { + tnode2->head = get_ppnode( huff ); + *tnode2->head = tnode2; + } + } else { + tnode2->head = get_ppnode( huff ); + *tnode2->head = tnode2; + } + huff->lhead->next = tnode2; + tnode2->prev = huff->lhead; + + tnode->symbol = ch; + tnode->weight = 1; + tnode->next = huff->lhead->next; + if ( huff->lhead->next ) { + huff->lhead->next->prev = tnode; + if ( huff->lhead->next->weight == 1 ) { + tnode->head = huff->lhead->next->head; + } else { + /* this should never happen */ + tnode->head = get_ppnode( huff ); + *tnode->head = tnode2; + } + } else { + /* this should never happen */ + tnode->head = get_ppnode( huff ); + *tnode->head = tnode; + } + huff->lhead->next = tnode; + tnode->prev = huff->lhead; + tnode->left = tnode->right = NULL; + + if ( huff->lhead->parent ) { + if ( huff->lhead->parent->left == huff->lhead ) { /* lhead is guaranteed to by the NYT */ + huff->lhead->parent->left = tnode2; + } else { + huff->lhead->parent->right = tnode2; + } + } else { + huff->tree = tnode2; + } + + tnode2->right = tnode; + tnode2->left = huff->lhead; + + tnode2->parent = huff->lhead->parent; + huff->lhead->parent = tnode->parent = tnode2; + + huff->loc[ch] = tnode; + + increment( huff, tnode2->parent ); + } else { + increment( huff, huff->loc[ch] ); + } +} + +/* Get a symbol */ +int Huff_Receive( node_t *node, int *ch, byte *fin ) { + while ( node && node->symbol == INTERNAL_NODE ) { + if ( get_bit( fin ) ) { + node = node->right; + } else { + node = node->left; + } + } + if ( !node ) { + return 0; +// Com_Error(ERR_DROP, "Illegal tree!\n"); + } + return ( *ch = node->symbol ); +} + +/* Get a symbol */ +void Huff_offsetReceive( node_t *node, int *ch, byte *fin, int *offset ) { + bloc = *offset; + while ( node && node->symbol == INTERNAL_NODE ) { + if ( get_bit( fin ) ) { + node = node->right; + } else { + node = node->left; + } + } + if ( !node ) { + *ch = 0; + return; +// Com_Error(ERR_DROP, "Illegal tree!\n"); + } + *ch = node->symbol; + *offset = bloc; +} + +/* Send the prefix code for this node */ +static void send( node_t *node, node_t *child, byte *fout ) { + if ( node->parent ) { + send( node->parent, node, fout ); + } + if ( child ) { + if ( node->right == child ) { + add_bit( 1, fout ); + } else { + add_bit( 0, fout ); + } + } +} + +/* Send a symbol */ +void Huff_transmit( huff_t *huff, int ch, byte *fout ) { + int i; + if ( huff->loc[ch] == NULL ) { + /* node_t hasn't been transmitted, send a NYT, then the symbol */ + Huff_transmit( huff, NYT, fout ); + for ( i = 7; i >= 0; i-- ) { + add_bit( (char)( ( ch >> i ) & 0x1 ), fout ); + } + } else { + send( huff->loc[ch], NULL, fout ); + } +} + +void Huff_offsetTransmit( huff_t *huff, int ch, byte *fout, int *offset ) { + bloc = *offset; + send( huff->loc[ch], NULL, fout ); + *offset = bloc; +} + +void Huff_Decompress( msg_t *mbuf, int offset ) { + int ch, cch, i, j, size; + byte seq[65536]; + byte* buffer; + huff_t huff; + + size = mbuf->cursize - offset; + buffer = mbuf->data + offset; + + if ( size <= 0 ) { + return; + } + + Com_Memset( &huff, 0, sizeof( huff_t ) ); + // Initialize the tree & list with the NYT node + huff.tree = huff.lhead = huff.ltail = huff.loc[NYT] = &( huff.nodeList[huff.blocNode++] ); + huff.tree->symbol = NYT; + huff.tree->weight = 0; + huff.lhead->next = huff.lhead->prev = NULL; + huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + + cch = buffer[0] * 256 + buffer[1]; + // don't overflow with bad messages + if ( cch > mbuf->maxsize - offset ) { + cch = mbuf->maxsize - offset; + } + bloc = 16; + + for ( j = 0; j < cch; j++ ) { + ch = 0; + // don't overflow reading from the messages + // FIXME: would it be better to have a overflow check in get_bit ? + if ( ( bloc >> 3 ) > size ) { + seq[j] = 0; + break; + } + Huff_Receive( huff.tree, &ch, buffer ); /* Get a character */ + if ( ch == NYT ) { /* We got a NYT, get the symbol associated with it */ + ch = 0; + for ( i = 0; i < 8; i++ ) { + ch = ( ch << 1 ) + get_bit( buffer ); + } + } + + seq[j] = ch; /* Write symbol */ + + Huff_addRef( &huff, (byte)ch ); /* Increment node */ + } + mbuf->cursize = cch + offset; + Com_Memcpy( mbuf->data + offset, seq, cch ); +} + +extern int oldsize; + +void Huff_Compress( msg_t *mbuf, int offset ) { + int i, ch, size; + byte seq[65536]; + byte* buffer; + huff_t huff; + + size = mbuf->cursize - offset; + buffer = mbuf->data + + offset; + + if ( size <= 0 ) { + return; + } + + Com_Memset( &huff, 0, sizeof( huff_t ) ); + // Add the NYT (not yet transmitted) node into the tree/list */ + huff.tree = huff.lhead = huff.loc[NYT] = &( huff.nodeList[huff.blocNode++] ); + huff.tree->symbol = NYT; + huff.tree->weight = 0; + huff.lhead->next = huff.lhead->prev = NULL; + huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + huff.loc[NYT] = huff.tree; + + seq[0] = ( size >> 8 ); + seq[1] = size & 0xff; + + bloc = 16; + + for ( i = 0; i < size; i++ ) { + ch = buffer[i]; + Huff_transmit( &huff, ch, seq ); /* Transmit symbol */ + Huff_addRef( &huff, (byte)ch ); /* Do update */ + } + + bloc += 8; // next byte + + mbuf->cursize = ( bloc >> 3 ) + offset; + Com_Memcpy( mbuf->data + offset, seq, ( bloc >> 3 ) ); +} + +void Huff_Init( huffman_t *huff ) { + + Com_Memset( &huff->compressor, 0, sizeof( huff_t ) ); + Com_Memset( &huff->decompressor, 0, sizeof( huff_t ) ); + + // Initialize the tree & list with the NYT node + huff->decompressor.tree = huff->decompressor.lhead = huff->decompressor.ltail = huff->decompressor.loc[NYT] = &( huff->decompressor.nodeList[huff->decompressor.blocNode++] ); + huff->decompressor.tree->symbol = NYT; + huff->decompressor.tree->weight = 0; + huff->decompressor.lhead->next = huff->decompressor.lhead->prev = NULL; + huff->decompressor.tree->parent = huff->decompressor.tree->left = huff->decompressor.tree->right = NULL; + + // Add the NYT (not yet transmitted) node into the tree/list */ + huff->compressor.tree = huff->compressor.lhead = huff->compressor.loc[NYT] = &( huff->compressor.nodeList[huff->compressor.blocNode++] ); + huff->compressor.tree->symbol = NYT; + huff->compressor.tree->weight = 0; + huff->compressor.lhead->next = huff->compressor.lhead->prev = NULL; + huff->compressor.tree->parent = huff->compressor.tree->left = huff->compressor.tree->right = NULL; + huff->compressor.loc[NYT] = huff->compressor.tree; +} + diff --git a/src/qcommon/md4.c b/src/qcommon/md4.c new file mode 100644 index 0000000..3bb1b4b --- /dev/null +++ b/src/qcommon/md4.c @@ -0,0 +1,317 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* GLOBAL.H - RSAREF types and constants */ + +#include + +#ifdef WIN32 + +#pragma warning(disable : 4711) // selected for automatic inline expansion + +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + + +/* MD4.H - header file for MD4C.C */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. + +All rights reserved. + +License to copy and use this software is granted provided that it is identified as the RSA Data Security, Inc. MD4 Message-Digest Algorithm in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided as is without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +void MD4Init( MD4_CTX * ); +void MD4Update( MD4_CTX *, const unsigned char *, unsigned int ); +void MD4Final( unsigned char [16], MD4_CTX * ); + +void Com_Memset( void* dest, const int val, const size_t count ); +void Com_Memcpy( void* dest, const void* src, const size_t count ); + +/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */ +/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it is identified as the +RSA Data Security, Inc. MD4 Message-Digest Algorithm + in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as +derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm +in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided +as is without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static void MD4Transform( UINT4 [4], const unsigned char [64] ); +static void Encode( unsigned char *, UINT4 *, unsigned int ); +static void Decode( UINT4 *, const unsigned char *, unsigned int ); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F( x, y, z ) ( ( ( x ) & ( y ) ) | ( ( ~x ) & ( z ) ) ) +#define G( x, y, z ) ( ( ( x ) & ( y ) ) | ( ( x ) & ( z ) ) | ( ( y ) & ( z ) ) ) +#define H( x, y, z ) ( ( x ) ^ ( y ) ^ ( z ) ) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT( x, n ) ( ( ( x ) << ( n ) ) | ( ( x ) >> ( 32 - ( n ) ) ) ) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF( a, b, c, d, x, s ) {( a ) += F( ( b ), ( c ), ( d ) ) + ( x ); ( a ) = ROTATE_LEFT( ( a ), ( s ) );} + +#define GG( a, b, c, d, x, s ) {( a ) += G( ( b ), ( c ), ( d ) ) + ( x ) + (UINT4)0x5a827999; ( a ) = ROTATE_LEFT( ( a ), ( s ) );} + +#define HH( a, b, c, d, x, s ) {( a ) += H( ( b ), ( c ), ( d ) ) + ( x ) + (UINT4)0x6ed9eba1; ( a ) = ROTATE_LEFT( ( a ), ( s ) );} + + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4Init( MD4_CTX *context ) { + context->count[0] = context->count[1] = 0; + +/* Load magic initialization constants.*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4Update( MD4_CTX *context, const unsigned char *input, unsigned int inputLen ) { + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = ( unsigned int )( ( context->count[0] >> 3 ) & 0x3F ); + + /* Update number of bits */ + if ( ( context->count[0] += ( (UINT4)inputLen << 3 ) ) < ( (UINT4)inputLen << 3 ) ) { + context->count[1]++; + } + + context->count[1] += ( (UINT4)inputLen >> 29 ); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if ( inputLen >= partLen ) { + Com_Memcpy( (POINTER)&context->buffer[index], (POINTER)input, partLen ); + MD4Transform( context->state, context->buffer ); + + for ( i = partLen; i + 63 < inputLen; i += 64 ) + MD4Transform( context->state, &input[i] ); + + index = 0; + } else { + i = 0; + } + + /* Buffer remaining input */ + Com_Memcpy( (POINTER)&context->buffer[index], (POINTER)&input[i], inputLen - i ); +} + + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */ +void MD4Final( unsigned char digest[16], MD4_CTX *context ) { + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode( bits, context->count, 8 ); + + /* Pad out to 56 mod 64.*/ + index = ( unsigned int )( ( context->count[0] >> 3 ) & 0x3f ); + padLen = ( index < 56 ) ? ( 56 - index ) : ( 120 - index ); + MD4Update( context, PADDING, padLen ); + + /* Append length (before padding) */ + MD4Update( context, bits, 8 ); + + /* Store state in digest */ + Encode( digest, context->state, 16 ); + + /* Zeroize sensitive information.*/ + Com_Memset( (POINTER)context, 0, sizeof( *context ) ); +} + + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4Transform( UINT4 state[4], const unsigned char block[64] ) { + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode( x, block, 64 ); + +/* Round 1 */ + FF( a, b, c, d, x[ 0], S11 ); /* 1 */ + FF( d, a, b, c, x[ 1], S12 ); /* 2 */ + FF( c, d, a, b, x[ 2], S13 ); /* 3 */ + FF( b, c, d, a, x[ 3], S14 ); /* 4 */ + FF( a, b, c, d, x[ 4], S11 ); /* 5 */ + FF( d, a, b, c, x[ 5], S12 ); /* 6 */ + FF( c, d, a, b, x[ 6], S13 ); /* 7 */ + FF( b, c, d, a, x[ 7], S14 ); /* 8 */ + FF( a, b, c, d, x[ 8], S11 ); /* 9 */ + FF( d, a, b, c, x[ 9], S12 ); /* 10 */ + FF( c, d, a, b, x[10], S13 ); /* 11 */ + FF( b, c, d, a, x[11], S14 ); /* 12 */ + FF( a, b, c, d, x[12], S11 ); /* 13 */ + FF( d, a, b, c, x[13], S12 ); /* 14 */ + FF( c, d, a, b, x[14], S13 ); /* 15 */ + FF( b, c, d, a, x[15], S14 ); /* 16 */ + +/* Round 2 */ + GG( a, b, c, d, x[ 0], S21 ); /* 17 */ + GG( d, a, b, c, x[ 4], S22 ); /* 18 */ + GG( c, d, a, b, x[ 8], S23 ); /* 19 */ + GG( b, c, d, a, x[12], S24 ); /* 20 */ + GG( a, b, c, d, x[ 1], S21 ); /* 21 */ + GG( d, a, b, c, x[ 5], S22 ); /* 22 */ + GG( c, d, a, b, x[ 9], S23 ); /* 23 */ + GG( b, c, d, a, x[13], S24 ); /* 24 */ + GG( a, b, c, d, x[ 2], S21 ); /* 25 */ + GG( d, a, b, c, x[ 6], S22 ); /* 26 */ + GG( c, d, a, b, x[10], S23 ); /* 27 */ + GG( b, c, d, a, x[14], S24 ); /* 28 */ + GG( a, b, c, d, x[ 3], S21 ); /* 29 */ + GG( d, a, b, c, x[ 7], S22 ); /* 30 */ + GG( c, d, a, b, x[11], S23 ); /* 31 */ + GG( b, c, d, a, x[15], S24 ); /* 32 */ + +/* Round 3 */ + HH( a, b, c, d, x[ 0], S31 ); /* 33 */ + HH( d, a, b, c, x[ 8], S32 ); /* 34 */ + HH( c, d, a, b, x[ 4], S33 ); /* 35 */ + HH( b, c, d, a, x[12], S34 ); /* 36 */ + HH( a, b, c, d, x[ 2], S31 ); /* 37 */ + HH( d, a, b, c, x[10], S32 ); /* 38 */ + HH( c, d, a, b, x[ 6], S33 ); /* 39 */ + HH( b, c, d, a, x[14], S34 ); /* 40 */ + HH( a, b, c, d, x[ 1], S31 ); /* 41 */ + HH( d, a, b, c, x[ 9], S32 ); /* 42 */ + HH( c, d, a, b, x[ 5], S33 ); /* 43 */ + HH( b, c, d, a, x[13], S34 ); /* 44 */ + HH( a, b, c, d, x[ 3], S31 ); /* 45 */ + HH( d, a, b, c, x[11], S32 ); /* 46 */ + HH( c, d, a, b, x[ 7], S33 ); /* 47 */ + HH( b, c, d, a, x[15], S34 ); /* 48 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information.*/ + Com_Memset( (POINTER)x, 0, sizeof( x ) ); +} + + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode( unsigned char *output, UINT4 *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[j] = ( unsigned char )( input[i] & 0xff ); + output[j + 1] = ( unsigned char )( ( input[i] >> 8 ) & 0xff ); + output[j + 2] = ( unsigned char )( ( input[i] >> 16 ) & 0xff ); + output[j + 3] = ( unsigned char )( ( input[i] >> 24 ) & 0xff ); + } +} + + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode( UINT4 *output, const unsigned char *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) + output[i] = ( (UINT4)input[j] ) | ( ( (UINT4)input[j + 1] ) << 8 ) | ( ( (UINT4)input[j + 2] ) << 16 ) | ( ( (UINT4)input[j + 3] ) << 24 ); +} + +//=================================================================== + +unsigned Com_BlockChecksum( void *buffer, int length ) { + int digest[4]; + unsigned val; + MD4_CTX ctx; + + MD4Init( &ctx ); + MD4Update( &ctx, (unsigned char *)buffer, length ); + MD4Final( (unsigned char *)digest, &ctx ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} + +unsigned Com_BlockChecksumKey( void *buffer, int length, int key ) { + int digest[4]; + unsigned val; + MD4_CTX ctx; + + MD4Init( &ctx ); + MD4Update( &ctx, (unsigned char *)&key, 4 ); + MD4Update( &ctx, (unsigned char *)buffer, length ); + MD4Final( (unsigned char *)digest, &ctx ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/src/qcommon/msg.c b/src/qcommon/msg.c new file mode 100644 index 0000000..c198d4b --- /dev/null +++ b/src/qcommon/msg.c @@ -0,0 +1,2122 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../game/q_shared.h" +#include "qcommon.h" + +static huffman_t msgHuff; +static qboolean msgInit = qfalse; + +int pcount[256]; + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles byte ordering and avoids alignment errors +============================================================================== +*/ + +int oldsize = 0; + +void MSG_initHuffman(); + +void MSG_Init( msg_t *buf, byte *data, int length ) { + if ( !msgInit ) { + MSG_initHuffman(); + } + memset( buf, 0, sizeof( *buf ) ); + buf->data = data; + buf->maxsize = length; +} + +void MSG_InitOOB( msg_t *buf, byte *data, int length ) { + if ( !msgInit ) { + MSG_initHuffman(); + } + memset( buf, 0, sizeof( *buf ) ); + buf->data = data; + buf->maxsize = length; + buf->oob = qtrue; +} + +void MSG_Clear( msg_t *buf ) { + buf->cursize = 0; + buf->overflowed = qfalse; + buf->bit = 0; //<- in bits +} + + +void MSG_Bitstream( msg_t *buf ) { + buf->oob = qfalse; +} + +void MSG_BeginReading( msg_t *msg ) { + msg->readcount = 0; + msg->bit = 0; + msg->oob = qfalse; +} + +void MSG_BeginReadingOOB( msg_t *msg ) { + msg->readcount = 0; + msg->bit = 0; + msg->oob = qtrue; +} + +void MSG_Copy( msg_t *buf, byte *data, int length, msg_t *src ) { + if ( length < src->cursize ) { + Com_Error( ERR_DROP, "MSG_Copy: can't copy into a smaller msg_t buffer" ); + } + Com_Memcpy( buf, src, sizeof( msg_t ) ); + buf->data = data; + Com_Memcpy( buf->data, src->data, src->cursize ); +} + +/* +============================================================================= + +bit functions + +============================================================================= +*/ + +int overflows; + +// negative bit values include signs +void MSG_WriteBits( msg_t *msg, int value, int bits ) { + int i; +// FILE* fp; + + oldsize += bits; + + msg->uncompsize += bits; // NERVE - SMF - net debugging + + // this isn't an exact overflow check, but close enough + if ( msg->maxsize - msg->cursize < 4 ) { + msg->overflowed = qtrue; + return; + } + + if ( bits == 0 || bits < -31 || bits > 32 ) { + Com_Error( ERR_DROP, "MSG_WriteBits: bad bits %i", bits ); + } + + // check for overflows + if ( bits != 32 ) { + if ( bits > 0 ) { + if ( value > ( ( 1 << bits ) - 1 ) || value < 0 ) { + overflows++; + } + } else { + int r; + + r = 1 << ( bits - 1 ); + + if ( value > r - 1 || value < -r ) { + overflows++; + } + } + } + if ( bits < 0 ) { + bits = -bits; + } + if ( msg->oob ) { + if ( bits == 8 ) { + msg->data[msg->cursize] = value; + msg->cursize += 1; + msg->bit += 8; + } else if ( bits == 16 ) { + unsigned short *sp = (unsigned short *)&msg->data[msg->cursize]; + *sp = LittleShort( value ); + msg->cursize += 2; + msg->bit += 16; + } else if ( bits == 32 ) { + unsigned int *ip = (unsigned int *)&msg->data[msg->cursize]; + *ip = LittleLong( value ); + msg->cursize += 4; + msg->bit += 8; + } else { + Com_Error( ERR_DROP, "can't read %d bits\n", bits ); + } + } else { +// fp = fopen("c:\\netchan.bin", "a"); + value &= ( 0xffffffff >> ( 32 - bits ) ); + if ( bits & 7 ) { + int nbits; + nbits = bits & 7; + for ( i = 0; i < nbits; i++ ) { + Huff_putBit( ( value & 1 ), msg->data, &msg->bit ); + value = ( value >> 1 ); + } + bits = bits - nbits; + } + if ( bits ) { + for ( i = 0; i < bits; i += 8 ) { +// fwrite(bp, 1, 1, fp); + Huff_offsetTransmit( &msgHuff.compressor, ( value & 0xff ), msg->data, &msg->bit ); + value = ( value >> 8 ); + } + } + msg->cursize = ( msg->bit >> 3 ) + 1; +// fclose(fp); + } +} + +int MSG_ReadBits( msg_t *msg, int bits ) { + int value; + int get; + qboolean sgn; + int i, nbits; +// FILE* fp; + + value = 0; + + if ( bits < 0 ) { + bits = -bits; + sgn = qtrue; + } else { + sgn = qfalse; + } + + if ( msg->oob ) { + if ( bits == 8 ) { + value = msg->data[msg->readcount]; + msg->readcount += 1; + msg->bit += 8; + } else if ( bits == 16 ) { + unsigned short *sp = (unsigned short *)&msg->data[msg->readcount]; + value = LittleShort( *sp ); + msg->readcount += 2; + msg->bit += 16; + } else if ( bits == 32 ) { + unsigned int *ip = (unsigned int *)&msg->data[msg->readcount]; + value = LittleLong( *ip ); + msg->readcount += 4; + msg->bit += 32; + } else { + Com_Error( ERR_DROP, "can't read %d bits\n", bits ); + } + } else { + nbits = 0; + if ( bits & 7 ) { + nbits = bits & 7; + for ( i = 0; i < nbits; i++ ) { + value |= ( Huff_getBit( msg->data, &msg->bit ) << i ); + } + bits = bits - nbits; + } + if ( bits ) { +// fp = fopen("c:\\netchan.bin", "a"); + for ( i = 0; i < bits; i += 8 ) { + Huff_offsetReceive( msgHuff.decompressor.tree, &get, msg->data, &msg->bit ); +// fwrite(&get, 1, 1, fp); + value |= ( get << ( i + nbits ) ); + } +// fclose(fp); + } + msg->readcount = ( msg->bit >> 3 ) + 1; + } + if ( sgn ) { + if ( value & ( 1 << ( bits - 1 ) ) ) { + value |= -1 ^ ( ( 1 << bits ) - 1 ); + } + } + + return value; +} + + + +//================================================================================ + +// +// writing functions +// + +void MSG_WriteChar( msg_t *sb, int c ) { +#ifdef PARANOID + if ( c < -128 || c > 127 ) { + Com_Error( ERR_FATAL, "MSG_WriteChar: range error" ); + } +#endif + + MSG_WriteBits( sb, c, 8 ); +} + +void MSG_WriteByte( msg_t *sb, int c ) { +#ifdef PARANOID + if ( c < 0 || c > 255 ) { + Com_Error( ERR_FATAL, "MSG_WriteByte: range error" ); + } +#endif + + MSG_WriteBits( sb, c, 8 ); +} + +void MSG_WriteData( msg_t *buf, const void *data, int length ) { + int i; + for ( i = 0; i < length; i++ ) { + MSG_WriteByte( buf, ( (byte *)data )[i] ); + } +} + +void MSG_WriteShort( msg_t *sb, int c ) { +#ifdef PARANOID + if ( c < ( (short)0x8000 ) || c > (short)0x7fff ) { + Com_Error( ERR_FATAL, "MSG_WriteShort: range error" ); + } +#endif + + MSG_WriteBits( sb, c, 16 ); +} + +void MSG_WriteLong( msg_t *sb, int c ) { + MSG_WriteBits( sb, c, 32 ); +} + +void MSG_WriteFloat( msg_t *sb, float f ) { + union { + float f; + int l; + } dat; + + dat.f = f; + MSG_WriteBits( sb, dat.l, 32 ); +} + +void MSG_WriteString( msg_t *sb, const char *s ) { + if ( !s ) { + MSG_WriteData( sb, "", 1 ); + } else { + int l,i; + char string[MAX_STRING_CHARS]; + + l = strlen( s ); + if ( l >= MAX_STRING_CHARS ) { + Com_Printf( "MSG_WriteString: MAX_STRING_CHARS" ); + MSG_WriteData( sb, "", 1 ); + return; + } + Q_strncpyz( string, s, sizeof( string ) ); + + // get rid of 0xff chars, because old clients don't like them + for ( i = 0 ; i < l ; i++ ) { + if ( ( (byte *)string )[i] > 127 ) { + string[i] = '.'; + } + } + + MSG_WriteData( sb, string, l + 1 ); + } +} + +void MSG_WriteBigString( msg_t *sb, const char *s ) { + if ( !s ) { + MSG_WriteData( sb, "", 1 ); + } else { + int l, i; + char string[BIG_INFO_STRING]; + + l = strlen( s ); + if ( l >= BIG_INFO_STRING ) { + Com_Printf( "MSG_WriteString: BIG_INFO_STRING" ); + MSG_WriteData( sb, "", 1 ); + return; + } + Q_strncpyz( string, s, sizeof( string ) ); + + // get rid of 0xff chars, because old clients don't like them + for ( i = 0 ; i < l ; i++ ) { + if ( ( (byte *)string )[i] > 127 ) { + string[i] = '.'; + } + } + + MSG_WriteData( sb, string, l + 1 ); + } +} + +void MSG_WriteAngle( msg_t *sb, float f ) { + MSG_WriteByte( sb, (int)( f * 256 / 360 ) & 255 ); +} + +void MSG_WriteAngle16( msg_t *sb, float f ) { + MSG_WriteShort( sb, ANGLE2SHORT( f ) ); +} + + +//============================================================ + +// +// reading functions +// + +// returns -1 if no more characters are available +int MSG_ReadChar( msg_t *msg ) { + int c; + + c = (signed char)MSG_ReadBits( msg, 8 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +int MSG_ReadByte( msg_t *msg ) { + int c; + + c = (unsigned char)MSG_ReadBits( msg, 8 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + return c; +} + +int MSG_ReadShort( msg_t *msg ) { + int c; + + c = (short)MSG_ReadBits( msg, 16 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +int MSG_ReadLong( msg_t *msg ) { + int c; + + c = MSG_ReadBits( msg, 32 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +float MSG_ReadFloat( msg_t *msg ) { + union { + byte b[4]; + float f; + int l; + } dat; + + dat.l = MSG_ReadBits( msg, 32 ); + if ( msg->readcount > msg->cursize ) { + dat.f = -1; + } + + return dat.f; +} + +char *MSG_ReadString( msg_t *msg ) { + static char string[MAX_STRING_CHARS]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte( msg ); // use ReadByte so -1 is out of bounds + if ( c == -1 || c == 0 ) { + break; + } + // translate all fmt spec to avoid crash bugs + if ( c == '%' ) { + c = '.'; + } + // don't allow higher ascii values + if ( c > 127 ) { + c = '.'; + } + + string[l] = c; + l++; + } while ( l < sizeof( string ) - 1 ); + + string[l] = 0; + + return string; +} + +char *MSG_ReadBigString( msg_t *msg ) { + static char string[BIG_INFO_STRING]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte( msg ); // use ReadByte so -1 is out of bounds + if ( c == -1 || c == 0 ) { + break; + } + // translate all fmt spec to avoid crash bugs + if ( c == '%' ) { + c = '.'; + } + + string[l] = c; + l++; + } while ( l < sizeof( string ) - 1 ); + + string[l] = 0; + + return string; +} + +char *MSG_ReadStringLine( msg_t *msg ) { + static char string[MAX_STRING_CHARS]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte( msg ); // use ReadByte so -1 is out of bounds + if ( c == -1 || c == 0 || c == '\n' ) { + break; + } + // translate all fmt spec to avoid crash bugs + if ( c == '%' ) { + c = '.'; + } + string[l] = c; + l++; + } while ( l < sizeof( string ) - 1 ); + + string[l] = 0; + + return string; +} + +float MSG_ReadAngle16( msg_t *msg ) { + return SHORT2ANGLE( MSG_ReadShort( msg ) ); +} + +void MSG_ReadData( msg_t *msg, void *data, int len ) { + int i; + + for ( i = 0 ; i < len ; i++ ) { + ( (byte *)data )[i] = MSG_ReadByte( msg ); + } +} + + +/* +============================================================================= + +delta functions + +============================================================================= +*/ + +extern cvar_t *cl_shownet; + +#define LOG( x ) if ( cl_shownet && cl_shownet->integer == 4 ) { Com_Printf( "%s ", x ); }; + +void MSG_WriteDelta( msg_t *msg, int oldV, int newV, int bits ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, newV, bits ); +} + +int MSG_ReadDelta( msg_t *msg, int oldV, int bits ) { + if ( MSG_ReadBits( msg, 1 ) ) { + return MSG_ReadBits( msg, bits ); + } + return oldV; +} + +void MSG_WriteDeltaFloat( msg_t *msg, float oldV, float newV ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *(int *)&newV, 32 ); +} + +float MSG_ReadDeltaFloat( msg_t *msg, float oldV ) { + if ( MSG_ReadBits( msg, 1 ) ) { + float newV; + + *(int *)&newV = MSG_ReadBits( msg, 32 ); + return newV; + } + return oldV; +} + +/* +============================================================================= + +delta functions with keys + +============================================================================= +*/ + +int kbitmask[32] = { + 0x00000001, 0x00000003, 0x00000007, 0x0000000F, + 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, + 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, + 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, + 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, + 0x001FFFFf, 0x003FFFFF, 0x007FFFFF, 0x00FFFFFF, + 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, + 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, +}; + +void MSG_WriteDeltaKey( msg_t *msg, int key, int oldV, int newV, int bits ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, newV ^ key, bits ); +} + +int MSG_ReadDeltaKey( msg_t *msg, int key, int oldV, int bits ) { + if ( MSG_ReadBits( msg, 1 ) ) { + return MSG_ReadBits( msg, bits ) ^ ( key & kbitmask[bits] ); + } + return oldV; +} + +void MSG_WriteDeltaKeyFloat( msg_t *msg, int key, float oldV, float newV ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, ( *(int *)&newV ) ^ key, 32 ); +} + +float MSG_ReadDeltaKeyFloat( msg_t *msg, int key, float oldV ) { + if ( MSG_ReadBits( msg, 1 ) ) { + float newV; + + *(int *)&newV = MSG_ReadBits( msg, 32 ) ^ key; + return newV; + } + return oldV; +} + + +/* +============================================================================ + +usercmd_t communication + +============================================================================ +*/ + +// ms is allways sent, the others are optional +#define CM_ANGLE1 ( 1 << 0 ) +#define CM_ANGLE2 ( 1 << 1 ) +#define CM_ANGLE3 ( 1 << 2 ) +#define CM_FORWARD ( 1 << 3 ) +#define CM_SIDE ( 1 << 4 ) +#define CM_UP ( 1 << 5 ) +#define CM_BUTTONS ( 1 << 6 ) +#define CM_WEAPON ( 1 << 7 ) + +/* +===================== +MSG_WriteDeltaUsercmd +===================== +*/ +void MSG_WriteDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { + if ( to->serverTime - from->serverTime < 256 ) { + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 ); + } else { + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, to->serverTime, 32 ); + } + MSG_WriteDelta( msg, from->angles[0], to->angles[0], 16 ); + MSG_WriteDelta( msg, from->angles[1], to->angles[1], 16 ); + MSG_WriteDelta( msg, from->angles[2], to->angles[2], 16 ); + MSG_WriteDelta( msg, from->forwardmove, to->forwardmove, 8 ); + MSG_WriteDelta( msg, from->rightmove, to->rightmove, 8 ); + MSG_WriteDelta( msg, from->upmove, to->upmove, 8 ); + MSG_WriteDelta( msg, from->buttons, to->buttons, 8 ); + MSG_WriteDelta( msg, from->wbuttons, to->wbuttons, 8 ); + MSG_WriteDelta( msg, from->weapon, to->weapon, 8 ); + MSG_WriteDelta( msg, from->holdable, to->holdable, 8 ); //----(SA) modified + MSG_WriteDelta( msg, from->wolfkick, to->wolfkick, 8 ); + MSG_WriteDelta( msg, from->mpSetup, to->mpSetup, 8 ); // NERVE - SMF + MSG_WriteDelta( msg, from->identClient, to->identClient, 8 ); // NERVE - SMF +} + + +/* +===================== +MSG_ReadDeltaUsercmd +===================== +*/ +void MSG_ReadDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { + if ( MSG_ReadBits( msg, 1 ) ) { + to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 ); + } else { + to->serverTime = MSG_ReadBits( msg, 32 ); + } + to->angles[0] = MSG_ReadDelta( msg, from->angles[0], 16 ); + to->angles[1] = MSG_ReadDelta( msg, from->angles[1], 16 ); + to->angles[2] = MSG_ReadDelta( msg, from->angles[2], 16 ); + to->forwardmove = MSG_ReadDelta( msg, from->forwardmove, 8 ); + to->rightmove = MSG_ReadDelta( msg, from->rightmove, 8 ); + to->upmove = MSG_ReadDelta( msg, from->upmove, 8 ); + to->buttons = MSG_ReadDelta( msg, from->buttons, 8 ); + to->wbuttons = MSG_ReadDelta( msg, from->wbuttons, 8 ); + to->weapon = MSG_ReadDelta( msg, from->weapon, 8 ); + to->holdable = MSG_ReadDelta( msg, from->holdable, 8 ); //----(SA) modified + to->wolfkick = MSG_ReadDelta( msg, from->wolfkick, 8 ); + to->mpSetup = MSG_ReadDelta( msg, from->mpSetup, 8 ); // NERVE - SMF + to->identClient = MSG_ReadDelta( msg, from->identClient, 8 ); // NERVE - SMF +} + +/* +===================== +MSG_WriteDeltaUsercmd +===================== +*/ +void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) { + if ( to->serverTime - from->serverTime < 256 ) { + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 ); + } else { + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, to->serverTime, 32 ); + } + if ( from->angles[0] == to->angles[0] && + from->angles[1] == to->angles[1] && + from->angles[2] == to->angles[2] && + from->forwardmove == to->forwardmove && + from->rightmove == to->rightmove && + from->upmove == to->upmove && + from->buttons == to->buttons && + from->wbuttons == to->wbuttons && + from->weapon == to->weapon && + from->holdable == to->holdable && + from->wolfkick == to->wolfkick && + from->mpSetup == to->mpSetup && // NERVE - SMF + from->identClient == to->identClient ) { // NERVE - SMF + MSG_WriteBits( msg, 0, 1 ); // no change + oldsize += 7; + return; + } + key ^= to->serverTime; + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 ); + MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 ); + MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 ); + MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 8 ); + MSG_WriteDeltaKey( msg, key, from->wbuttons, to->wbuttons, 8 ); + MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 ); + MSG_WriteDeltaKey( msg, key, from->holdable, to->holdable, 8 ); + MSG_WriteDeltaKey( msg, key, from->wolfkick, to->wolfkick, 8 ); + MSG_WriteDeltaKey( msg, key, from->mpSetup, to->mpSetup, 8 ); // NERVE - SMF + MSG_WriteDeltaKey( msg, key, from->identClient, to->identClient, 8 ); // NERVE - SMF +} + + +/* +===================== +MSG_ReadDeltaUsercmd +===================== +*/ +void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) { + if ( MSG_ReadBits( msg, 1 ) ) { + to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 ); + } else { + to->serverTime = MSG_ReadBits( msg, 32 ); + } + if ( MSG_ReadBits( msg, 1 ) ) { + key ^= to->serverTime; + to->angles[0] = MSG_ReadDeltaKey( msg, key, from->angles[0], 16 ); + to->angles[1] = MSG_ReadDeltaKey( msg, key, from->angles[1], 16 ); + to->angles[2] = MSG_ReadDeltaKey( msg, key, from->angles[2], 16 ); + to->forwardmove = MSG_ReadDeltaKey( msg, key, from->forwardmove, 8 ); + to->rightmove = MSG_ReadDeltaKey( msg, key, from->rightmove, 8 ); + to->upmove = MSG_ReadDeltaKey( msg, key, from->upmove, 8 ); + to->buttons = MSG_ReadDeltaKey( msg, key, from->buttons, 8 ); + to->wbuttons = MSG_ReadDeltaKey( msg, key, from->wbuttons, 8 ); + to->weapon = MSG_ReadDeltaKey( msg, key, from->weapon, 8 ); + to->holdable = MSG_ReadDeltaKey( msg, key, from->holdable, 8 ); + to->wolfkick = MSG_ReadDeltaKey( msg, key, from->wolfkick, 8 ); + to->mpSetup = MSG_ReadDeltaKey( msg, key, from->mpSetup, 8 ); // NERVE - SMF + to->identClient = MSG_ReadDeltaKey( msg, key, from->identClient, 8 ); // NERVE - SMF + } else { + to->angles[0] = from->angles[0]; + to->angles[1] = from->angles[1]; + to->angles[2] = from->angles[2]; + to->forwardmove = from->forwardmove; + to->rightmove = from->rightmove; + to->upmove = from->upmove; + to->buttons = from->buttons; + to->wbuttons = from->wbuttons; + to->weapon = from->weapon; + to->holdable = from->holdable; + to->wolfkick = from->wolfkick; + to->mpSetup = from->mpSetup; // NERVE - SMF + to->identClient = from->identClient; // NERVE - SMF + } +} + + +/* +============================================================================= + +entityState_t communication + +============================================================================= +*/ + +/* +================= +MSG_ReportChangeVectors_f + +Prints out a table from the current statistics for copying to code +================= +*/ +void MSG_ReportChangeVectors_f( void ) { + int i; + for ( i = 0; i < 256; i++ ) { + if ( pcount[i] ) { + Com_Printf( "%d used %d\n", i, pcount[i] ); + } + } +} + +typedef struct { + char *name; + int offset; + int bits; // 0 = float +} netField_t; + +// using the stringizing operator to save typing... +#define NETF( x ) # x,(int)&( (entityState_t*)0 )->x + +netField_t entityStateFields[] = +{ + { NETF( eType ), 8 }, + { NETF( eFlags ), 24 }, + { NETF( pos.trType ), 8 }, + { NETF( pos.trTime ), 32 }, + { NETF( pos.trDuration ), 32 }, + { NETF( pos.trBase[0] ), 0 }, + { NETF( pos.trBase[1] ), 0 }, + { NETF( pos.trBase[2] ), 0 }, + { NETF( pos.trDelta[0] ), 0 }, + { NETF( pos.trDelta[1] ), 0 }, + { NETF( pos.trDelta[2] ), 0 }, + { NETF( apos.trType ), 8 }, + { NETF( apos.trTime ), 32 }, + { NETF( apos.trDuration ), 32 }, + { NETF( apos.trBase[0] ), 0 }, + { NETF( apos.trBase[1] ), 0 }, + { NETF( apos.trBase[2] ), 0 }, + { NETF( apos.trDelta[0] ), 0 }, + { NETF( apos.trDelta[1] ), 0 }, + { NETF( apos.trDelta[2] ), 0 }, + { NETF( time ), 32 }, + { NETF( time2 ), 32 }, + { NETF( origin[0] ), 0 }, + { NETF( origin[1] ), 0 }, + { NETF( origin[2] ), 0 }, + { NETF( origin2[0] ), 0 }, + { NETF( origin2[1] ), 0 }, + { NETF( origin2[2] ), 0 }, + { NETF( angles[0] ), 0 }, + { NETF( angles[1] ), 0 }, + { NETF( angles[2] ), 0 }, + { NETF( angles2[0] ), 0 }, + { NETF( angles2[1] ), 0 }, + { NETF( angles2[2] ), 0 }, + { NETF( otherEntityNum ), GENTITYNUM_BITS }, + { NETF( otherEntityNum2 ), GENTITYNUM_BITS }, + { NETF( groundEntityNum ), GENTITYNUM_BITS }, + { NETF( loopSound ), 8 }, + { NETF( constantLight ), 32 }, + { NETF( dl_intensity ), 32 }, //----(SA) longer now to carry the corona colors + { NETF( modelindex ), 9 }, + { NETF( modelindex2 ), 9 }, + { NETF( frame ), 16 }, + { NETF( clientNum ), 8 }, + { NETF( solid ), 24 }, + { NETF( event ), 10 }, + { NETF( eventParm ), 8 }, + { NETF( eventSequence ), 8 }, // warning: need to modify cg_event.c at "// check the sequencial list" if you change this + { NETF( events[0] ), 8 }, + { NETF( events[1] ), 8 }, + { NETF( events[2] ), 8 }, + { NETF( events[3] ), 8 }, + { NETF( eventParms[0] ), 8 }, + { NETF( eventParms[1] ), 8 }, + { NETF( eventParms[2] ), 8 }, + { NETF( eventParms[3] ), 8 }, + { NETF( powerups ), 16 }, + { NETF( weapon ), 8 }, + { NETF( legsAnim ), ANIM_BITS }, + { NETF( torsoAnim ), ANIM_BITS }, + { NETF( density ), 10}, + { NETF( dmgFlags ), 32 }, //----(SA) additional info flags for damage + { NETF( onFireStart ), 32}, + { NETF( onFireEnd ), 32}, + { NETF( aiChar ), 8}, + { NETF( teamNum ), 8}, + { NETF( effect1Time ), 32}, + { NETF( effect2Time ), 32}, + { NETF( effect3Time ), 32}, + { NETF( aiState ), 2}, + { NETF( animMovetype ), 4}, +}; + + +// if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS ) +// the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent +#define FLOAT_INT_BITS 13 +#define FLOAT_INT_BIAS ( 1 << ( FLOAT_INT_BITS - 1 ) ) + +/* +================== +MSG_WriteDeltaEntity + +Writes part of a packetentities message, including the entity number. +Can delta from either a baseline or a previous packet_entity +If to is NULL, a remove entity update will be sent +If force is not set, then nothing at all will be generated if the entity is +identical, under the assumption that the in-order delta code will catch it. +================== +*/ +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to, + qboolean force ) { + int i, lc; + int numFields; + netField_t *field; + int trunc; + float fullFloat; + int *fromF, *toF; + + numFields = sizeof( entityStateFields ) / sizeof( entityStateFields[0] ); + + // all fields should be 32 bits to avoid any compiler packing issues + // the "number" field is not part of the field list + // if this assert fails, someone added a field to the entityState_t + // struct without updating the message fields + assert( numFields + 1 == sizeof( *from ) / 4 ); + + // a NULL to is a delta remove message + if ( to == NULL ) { + if ( from == NULL ) { + return; + } + if ( cl_shownet && ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) ) { + Com_Printf( "W|%3i: #%-3i remove\n", msg->cursize, from->number ); + } + MSG_WriteBits( msg, from->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 1, 1 ); + return; + } + + if ( to->number < 0 || to->number >= MAX_GENTITIES ) { + Com_Error( ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number ); + } + + lc = 0; + // build the change vector as bytes so it is endien independent + for ( i = 0, field = entityStateFields ; i < numFields ; i++, field++ ) { + fromF = ( int * )( (byte *)from + field->offset ); + toF = ( int * )( (byte *)to + field->offset ); + if ( *fromF != *toF ) { + lc = i + 1; + } + } + + if ( lc == 0 ) { + // nothing at all changed + if ( !force ) { + return; // nothing at all + } + // write two bits for no change + MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // not removed + MSG_WriteBits( msg, 0, 1 ); // no delta + return; + } + + MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // not removed + MSG_WriteBits( msg, 1, 1 ); // we have a delta + + MSG_WriteByte( msg, lc ); // # of changes + + oldsize += numFields; + + for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) { + fromF = ( int * )( (byte *)from + field->offset ); + toF = ( int * )( (byte *)to + field->offset ); + + if ( *fromF == *toF ) { + MSG_WriteBits( msg, 0, 1 ); // no change + continue; + } + + MSG_WriteBits( msg, 1, 1 ); // changed + + if ( field->bits == 0 ) { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if ( fullFloat == 0.0f ) { + MSG_WriteBits( msg, 0, 1 ); + oldsize += FLOAT_INT_BITS; + } else { + MSG_WriteBits( msg, 1, 1 ); + if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && + trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { + // send as small integer + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); +/* if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + }*/ + } else { + // send as full floating point value + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *toF, 32 ); +/* if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + }*/ + } + } + } else { + if ( *toF == 0 ) { + MSG_WriteBits( msg, 0, 1 ); + } else { + MSG_WriteBits( msg, 1, 1 ); + // integer + MSG_WriteBits( msg, *toF, field->bits ); +/* if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + }*/ + } + } + } + +/* + c = msg->cursize - c; + + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->cursize * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->cursize - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } +*/ +} + +/* +================== +MSG_ReadDeltaEntity + +The entity number has already been read from the message, which +is how the from state is identified. + +If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1 + +Can go from either a baseline or a previous packet_entity +================== +*/ +extern cvar_t *cl_shownet; + +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, + int number ) { + int i, lc; + int numFields; + netField_t *field; + int *fromF, *toF; + int print; + int trunc; + int startBit, endBit; + + if ( number < 0 || number >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "Bad delta entity number: %i", number ); + } + + if ( msg->bit == 0 ) { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // check for a remove + if ( MSG_ReadBits( msg, 1 ) == 1 ) { + memset( to, 0, sizeof( *to ) ); + to->number = MAX_GENTITIES - 1; + if ( cl_shownet && ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) ) { + Com_Printf( "%3i: #%-3i remove\n", msg->readcount, number ); + } + return; + } + + // check for no delta + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *to = *from; + to->number = number; + return; + } + + numFields = sizeof( entityStateFields ) / sizeof( entityStateFields[0] ); + lc = MSG_ReadByte( msg ); + + if ( lc > numFields ) { //DAJ FIXME have gotten lc = 76 which is bigger than numFields! + lc = numFields; + } + + // shownet 2/3 will interleave with other printed info, -1 will + // just print the delta records` + if ( cl_shownet && ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) ) { + print = 1; + Com_Printf( "%3i: #%-3i ", msg->readcount, to->number ); + } else { + print = 0; + } + + to->number = number; + + for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) { + fromF = ( int * )( (byte *)from + field->offset ); + toF = ( int * )( (byte *)to + field->offset ); + + if ( !MSG_ReadBits( msg, 1 ) ) { + // no change + *toF = *fromF; + } else { + if ( field->bits == 0 ) { + // float + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *(float *)toF = 0.0f; + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + // integral float + trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + } + } else { + // full floating point value + *toF = MSG_ReadBits( msg, 32 ); + if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + } + } + } + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *toF = 0; + } else { + // integer + *toF = MSG_ReadBits( msg, field->bits ); + if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + } + } + } +// pcount[i]++; + } + } + for ( i = lc, field = &entityStateFields[lc] ; i < numFields ; i++, field++ ) { + fromF = ( int * )( (byte *)from + field->offset ); + toF = ( int * )( (byte *)to + field->offset ); + // no change + *toF = *fromF; + } + + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } +} + + +/* +============================================================================ + +player_state_t communication + +============================================================================ +*/ + +// using the stringizing operator to save typing... +#define PSF( x ) # x,(int)&( (playerState_t*)0 )->x + +netField_t playerStateFields[] = +{ + { PSF( commandTime ), 32 }, + { PSF( pm_type ), 8 }, + { PSF( bobCycle ), 8 }, + { PSF( pm_flags ), 16 }, + { PSF( pm_time ), -16 }, + { PSF( origin[0] ), 0 }, + { PSF( origin[1] ), 0 }, + { PSF( origin[2] ), 0 }, + { PSF( velocity[0] ), 0 }, + { PSF( velocity[1] ), 0 }, + { PSF( velocity[2] ), 0 }, + { PSF( weaponTime ), -16 }, + { PSF( weaponDelay ), -16 }, + { PSF( grenadeTimeLeft ), -16 }, + { PSF( gravity ), 16 }, + { PSF( leanf ), 0 }, + { PSF( speed ), 16 }, + { PSF( delta_angles[0] ), 16 }, + { PSF( delta_angles[1] ), 16 }, + { PSF( delta_angles[2] ), 16 }, + { PSF( groundEntityNum ), GENTITYNUM_BITS }, + { PSF( legsTimer ), 16 }, + { PSF( torsoTimer ), 16 }, + { PSF( legsAnim ), ANIM_BITS }, + { PSF( torsoAnim ), ANIM_BITS }, + { PSF( movementDir ), 8 }, + { PSF( eFlags ), 24 }, + { PSF( eventSequence ), 8 }, + { PSF( events[0] ), 8 }, + { PSF( events[1] ), 8 }, + { PSF( events[2] ), 8 }, + { PSF( events[3] ), 8 }, + { PSF( eventParms[0] ), 8 }, + { PSF( eventParms[1] ), 8 }, + { PSF( eventParms[2] ), 8 }, + { PSF( eventParms[3] ), 8 }, + { PSF( clientNum ), 8 }, + { PSF( weapons[0] ), 32 }, + { PSF( weapons[1] ), 32 }, + { PSF( weapon ), 7 }, // (SA) yup, even more + { PSF( weaponstate ), 4 }, + { PSF( weapAnim ), 10 }, + { PSF( viewangles[0] ), 0 }, + { PSF( viewangles[1] ), 0 }, + { PSF( viewangles[2] ), 0 }, + { PSF( viewheight ), -8 }, + { PSF( damageEvent ), 8 }, + { PSF( damageYaw ), 8 }, + { PSF( damagePitch ), 8 }, + { PSF( damageCount ), 8 }, + { PSF( mins[0] ), 0 }, + { PSF( mins[1] ), 0 }, + { PSF( mins[2] ), 0 }, + { PSF( maxs[0] ), 0 }, + { PSF( maxs[1] ), 0 }, + { PSF( maxs[2] ), 0 }, + { PSF( crouchMaxZ ), 0 }, + { PSF( crouchViewHeight ), 0 }, + { PSF( standViewHeight ), 0 }, + { PSF( deadViewHeight ), 0 }, + { PSF( runSpeedScale ), 0 }, + { PSF( sprintSpeedScale ), 0 }, + { PSF( crouchSpeedScale ), 0 }, + { PSF( friction ), 0 }, + { PSF( viewlocked ), 8 }, + { PSF( viewlocked_entNum ), 16 }, + { PSF( aiChar ), 8 }, + { PSF( teamNum ), 8 }, + { PSF( gunfx ), 8}, + { PSF( onFireStart ), 32}, + { PSF( curWeapHeat ), 8 }, + { PSF( sprintTime ), 16}, // FIXME: to be removed + { PSF( aimSpreadScale ), 8}, + { PSF( aiState ), 2}, + { PSF( serverCursorHint ), 8}, //----(SA) added + { PSF( serverCursorHintVal ), 8}, //----(SA) added + { PSF( classWeaponTime ), 32}, // JPW NERVE +}; + +/* +============= +MSG_WriteDeltaPlayerstate + +============= +*/ +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ) { + int i, j, lc; + playerState_t dummy; + int statsbits; + int persistantbits; + int ammobits[4]; //----(SA) modified + int clipbits; //----(SA) added + int powerupbits; + int holdablebits; + int numFields; + int c; + netField_t *field; + int *fromF, *toF; + float fullFloat; + int trunc; + int startBit, endBit; + int print; + + if ( !from ) { + from = &dummy; + memset( &dummy, 0, sizeof( dummy ) ); + } + + if ( msg->bit == 0 ) { + startBit = msg->cursize * 8 - GENTITYNUM_BITS; + } else { + startBit = ( msg->cursize - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // shownet 2/3 will interleave with other printed info, -2 will + // just print the delta records + if ( cl_shownet && ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) ) { + print = 1; + Com_Printf( "W|%3i: playerstate ", msg->cursize ); + } else { + print = 0; + } + + c = msg->cursize; + + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); + + lc = 0; + for ( i = 0, field = playerStateFields ; i < numFields ; i++, field++ ) { + fromF = ( int * )( (byte *)from + field->offset ); + toF = ( int * )( (byte *)to + field->offset ); + if ( *fromF != *toF ) { + lc = i + 1; + } + } + + MSG_WriteByte( msg, lc ); // # of changes + + oldsize += numFields - lc; + + for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) { + fromF = ( int * )( (byte *)from + field->offset ); + toF = ( int * )( (byte *)to + field->offset ); + + if ( *fromF == *toF ) { + MSG_WriteBits( msg, 0, 1 ); // no change + continue; + } + + MSG_WriteBits( msg, 1, 1 ); // changed +// pcount[i]++; + + if ( field->bits == 0 ) { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && + trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { + // send as small integer + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); + if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + } + } else { + // send as full floating point value + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *toF, 32 ); + if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + } + } + } else { + // integer + MSG_WriteBits( msg, *toF, field->bits ); + if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + } + } + } + c = msg->cursize - c; + + + // + // send the arrays + // + statsbits = 0; + for ( i = 0 ; i < 16 ; i++ ) { + if ( to->stats[i] != from->stats[i] ) { + statsbits |= 1 << i; + } + } + persistantbits = 0; + for ( i = 0 ; i < 16 ; i++ ) { + if ( to->persistant[i] != from->persistant[i] ) { + persistantbits |= 1 << i; + } + } + holdablebits = 0; + for ( i = 0 ; i < 16 ; i++ ) { + if ( to->holdable[i] != from->holdable[i] ) { + holdablebits |= 1 << i; + } + } + powerupbits = 0; + for ( i = 0 ; i < 16 ; i++ ) { + if ( to->powerups[i] != from->powerups[i] ) { + powerupbits |= 1 << i; + } + } + + + if ( statsbits || persistantbits || holdablebits || powerupbits ) { + + MSG_WriteBits( msg, 1, 1 ); // something changed + + if ( statsbits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, statsbits ); + for ( i = 0 ; i < 16 ; i++ ) + if ( statsbits & ( 1 << i ) ) { + // RF, changed to long to allow more flexibility +// MSG_WriteLong (msg, to->stats[i]); + MSG_WriteShort( msg, to->stats[i] ); //----(SA) back to short since weapon bits are handled elsewhere now + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change to stats + } + + + if ( persistantbits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, persistantbits ); + for ( i = 0 ; i < 16 ; i++ ) + if ( persistantbits & ( 1 << i ) ) { + MSG_WriteShort( msg, to->persistant[i] ); + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change to persistant + } + + + if ( holdablebits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, holdablebits ); + for ( i = 0 ; i < 16 ; i++ ) + if ( holdablebits & ( 1 << i ) ) { + MSG_WriteShort( msg, to->holdable[i] ); + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change to holdables + } + + + if ( powerupbits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, powerupbits ); + for ( i = 0 ; i < 16 ; i++ ) + if ( powerupbits & ( 1 << i ) ) { + MSG_WriteLong( msg, to->powerups[i] ); + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change to powerups + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change to any + oldsize += 4; + } + + +#if 0 +// RF, optimization +// Send a single bit to signify whether or not the ammo/clip info changed. +// If it did, send individual segments specifying offset values for each item. + { + int ammo_ofs; + int clip_ofs; + + ammobits = 0; + + // ammo + for ( i = 0 ; i < 32 ; i++ ) { + if ( to->ammo[i] != from->ammo[i] ) { + ammobits |= 1 << i; + } + } + // ammoclip (just add these changes to the ammo changes. if either changes, we should send both, since they are likely to both change at once anyway) + for ( i = 0 ; i < 32 ; i++ ) { + if ( to->ammoclip[i] != from->ammoclip[i] ) { + ammobits |= 1 << i; + } + } + + if ( ammobits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + + // send each changed item + for ( i = 0 ; i < 32 ; i++ ) { + if ( ammobits & ( 1 << i ) ) { + ammo_ofs = to->ammo[i] - from->ammo[i]; + clip_ofs = to->ammoclip[i] - from->ammoclip[i]; + + while ( ammo_ofs || clip_ofs ) { + MSG_WriteBits( msg, 1, 1 ); // signify that another index is present + MSG_WriteBits( msg, i, 5 ); // index number + + // ammo + if ( abs( ammo_ofs ) > 127 ) { + if ( ammo_ofs > 0 ) { + MSG_WriteChar( msg, 127 ); + ammo_ofs -= 127; + } else { + MSG_WriteChar( msg, -127 ); + ammo_ofs += 127; + } + } else { + MSG_WriteChar( msg, ammo_ofs ); + ammo_ofs = 0; + } + + // clip + if ( abs( clip_ofs ) > 127 ) { + if ( clip_ofs > 0 ) { + MSG_WriteChar( msg, 127 ); + clip_ofs -= 127; + } else { + MSG_WriteChar( msg, -127 ); + clip_ofs += 127; + } + } else { + MSG_WriteChar( msg, clip_ofs ); + clip_ofs = 0; + } + } + } + } + + // signify the end of changes + MSG_WriteBits( msg, 0, 1 ); + + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + } + +#else +//----(SA) I split this into two groups using shorts so it wouldn't have +// to use a long every time ammo changed for any weap. +// this seemed like a much friendlier option than making it +// read/write a long for any ammo change. + + // j == 0 : weaps 0-15 + // j == 1 : weaps 16-31 + // j == 2 : weaps 32-47 //----(SA) now up to 64 (but still pretty net-friendly) + // j == 3 : weaps 48-63 + + // ammo stored + for ( j = 0; j < 4; j++ ) { //----(SA) modified for 64 weaps + ammobits[j] = 0; + for ( i = 0 ; i < 16 ; i++ ) { + if ( to->ammo[i + ( j * 16 )] != from->ammo[i + ( j * 16 )] ) { + ammobits[j] |= 1 << i; + } + } + } + +//----(SA) also encapsulated ammo changes into one check. clip values will change frequently, + // but ammo will not. (only when you get ammo/reload rather than each shot) + if ( ammobits[0] || ammobits[1] || ammobits[2] || ammobits[3] ) { // if any were set... + MSG_WriteBits( msg, 1, 1 ); // changed + for ( j = 0; j < 4; j++ ) { + if ( ammobits[j] ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, ammobits[j] ); + for ( i = 0 ; i < 16 ; i++ ) + if ( ammobits[j] & ( 1 << i ) ) { + MSG_WriteShort( msg, to->ammo[i + ( j * 16 )] ); + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + // ammo in clip + for ( j = 0; j < 4; j++ ) { //----(SA) modified for 64 weaps + clipbits = 0; + for ( i = 0 ; i < 16 ; i++ ) { + if ( to->ammoclip[i + ( j * 16 )] != from->ammoclip[i + ( j * 16 )] ) { + clipbits |= 1 << i; + } + } + if ( clipbits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, clipbits ); + for ( i = 0 ; i < 16 ; i++ ) + if ( clipbits & ( 1 << i ) ) { + MSG_WriteShort( msg, to->ammoclip[i + ( j * 16 )] ); + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + } +#endif + + + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->cursize * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->cursize - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } + +} + + +/* +=================== +MSG_ReadDeltaPlayerstate +=================== +*/ +void MSG_ReadDeltaPlayerstate( msg_t *msg, playerState_t *from, playerState_t *to ) { + int i, j, lc; + int bits; + netField_t *field; + int numFields; + int startBit, endBit; + int print; + int *fromF, *toF; + int trunc; + playerState_t dummy; + + if ( !from ) { + from = &dummy; + memset( &dummy, 0, sizeof( dummy ) ); + } + *to = *from; + + if ( msg->bit == 0 ) { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // shownet 2/3 will interleave with other printed info, -2 will + // just print the delta records + if ( cl_shownet && ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) ) { + print = 1; + Com_Printf( "%3i: playerstate ", msg->readcount ); + } else { + print = 0; + } + + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); + lc = MSG_ReadByte( msg ); + + for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) { + fromF = ( int * )( (byte *)from + field->offset ); + toF = ( int * )( (byte *)to + field->offset ); + + if ( !MSG_ReadBits( msg, 1 ) ) { + // no change + *toF = *fromF; + } else { + if ( field->bits == 0 ) { + // float + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + // integral float + trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + } + } else { + // full floating point value + *toF = MSG_ReadBits( msg, 32 ); + if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + } + } + } else { + // integer + *toF = MSG_ReadBits( msg, field->bits ); + if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + } + } + } + } + for ( i = lc,field = &playerStateFields[lc]; i < numFields; i++, field++ ) { + fromF = ( int * )( (byte *)from + field->offset ); + toF = ( int * )( (byte *)to + field->offset ); + // no change + *toF = *fromF; + } + + + // read the arrays + if ( MSG_ReadBits( msg, 1 ) ) { // one general bit tells if any of this infrequently changing stuff has changed + // parse stats + if ( MSG_ReadBits( msg, 1 ) ) { + LOG( "PS_STATS" ); + bits = MSG_ReadShort( msg ); + for ( i = 0 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + // RF, changed to long to allow more flexibility +// to->stats[i] = MSG_ReadLong(msg); + to->stats[i] = MSG_ReadShort( msg ); //----(SA) back to short since weapon bits are handled elsewhere now + + } + } + } + + // parse persistant stats + if ( MSG_ReadBits( msg, 1 ) ) { + LOG( "PS_PERSISTANT" ); + bits = MSG_ReadShort( msg ); + for ( i = 0 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + to->persistant[i] = MSG_ReadShort( msg ); + } + } + } + + // parse holdable stats + if ( MSG_ReadBits( msg, 1 ) ) { + LOG( "PS_HOLDABLE" ); + bits = MSG_ReadShort( msg ); + for ( i = 0 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + to->holdable[i] = MSG_ReadShort( msg ); + } + } + } + + // parse powerups + if ( MSG_ReadBits( msg, 1 ) ) { + LOG( "PS_POWERUPS" ); + bits = MSG_ReadShort( msg ); + for ( i = 0 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + to->powerups[i] = MSG_ReadLong( msg ); + } + } + } + } + +#if 0 +// RF, optimization +// Send a single bit to signify whether or not the ammo/clip info changed. +// If it did, send individual segments specifying offset values for each item. + + if ( MSG_ReadBits( msg, 1 ) ) { // it changed + while ( MSG_ReadBits( msg, 1 ) ) { + i = MSG_ReadBits( msg, 5 ); // read the index number + // now read the offsets + to->ammo[i] += MSG_ReadChar( msg ); + to->ammoclip[i] += MSG_ReadChar( msg ); + } + } + +#else +//----(SA) I split this into two groups using shorts so it wouldn't have +// to use a long every time ammo changed for any weap. +// this seemed like a much friendlier option than making it +// read/write a long for any ammo change. + + // parse ammo + + // j == 0 : weaps 0-15 + // j == 1 : weaps 16-31 + // j == 2 : weaps 32-47 //----(SA) now up to 64 (but still pretty net-friendly) + // j == 3 : weaps 48-63 + + // ammo stored + if ( MSG_ReadBits( msg, 1 ) ) { // check for any ammo change (0-63) + for ( j = 0; j < 4; j++ ) { + if ( MSG_ReadBits( msg, 1 ) ) { + LOG( "PS_AMMO" ); + bits = MSG_ReadShort( msg ); + for ( i = 0 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + to->ammo[i + ( j * 16 )] = MSG_ReadShort( msg ); + } + } + } + } + } + + // ammo in clip + for ( j = 0; j < 4; j++ ) { + if ( MSG_ReadBits( msg, 1 ) ) { + LOG( "PS_AMMOCLIP" ); + bits = MSG_ReadShort( msg ); + for ( i = 0 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + to->ammoclip[i + ( j * 16 )] = MSG_ReadShort( msg ); + } + } + } + } + +#endif + + + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } +} + +int msg_hData[256] = { + 250315, // 0 + 41193, // 1 + 6292, // 2 + 7106, // 3 + 3730, // 4 + 3750, // 5 + 6110, // 6 + 23283, // 7 + 33317, // 8 + 6950, // 9 + 7838, // 10 + 9714, // 11 + 9257, // 12 + 17259, // 13 + 3949, // 14 + 1778, // 15 + 8288, // 16 + 1604, // 17 + 1590, // 18 + 1663, // 19 + 1100, // 20 + 1213, // 21 + 1238, // 22 + 1134, // 23 + 1749, // 24 + 1059, // 25 + 1246, // 26 + 1149, // 27 + 1273, // 28 + 4486, // 29 + 2805, // 30 + 3472, // 31 + 21819, // 32 + 1159, // 33 + 1670, // 34 + 1066, // 35 + 1043, // 36 + 1012, // 37 + 1053, // 38 + 1070, // 39 + 1726, // 40 + 888, // 41 + 1180, // 42 + 850, // 43 + 960, // 44 + 780, // 45 + 1752, // 46 + 3296, // 47 + 10630, // 48 + 4514, // 49 + 5881, // 50 + 2685, // 51 + 4650, // 52 + 3837, // 53 + 2093, // 54 + 1867, // 55 + 2584, // 56 + 1949, // 57 + 1972, // 58 + 940, // 59 + 1134, // 60 + 1788, // 61 + 1670, // 62 + 1206, // 63 + 5719, // 64 + 6128, // 65 + 7222, // 66 + 6654, // 67 + 3710, // 68 + 3795, // 69 + 1492, // 70 + 1524, // 71 + 2215, // 72 + 1140, // 73 + 1355, // 74 + 971, // 75 + 2180, // 76 + 1248, // 77 + 1328, // 78 + 1195, // 79 + 1770, // 80 + 1078, // 81 + 1264, // 82 + 1266, // 83 + 1168, // 84 + 965, // 85 + 1155, // 86 + 1186, // 87 + 1347, // 88 + 1228, // 89 + 1529, // 90 + 1600, // 91 + 2617, // 92 + 2048, // 93 + 2546, // 94 + 3275, // 95 + 2410, // 96 + 3585, // 97 + 2504, // 98 + 2800, // 99 + 2675, // 100 + 6146, // 101 + 3663, // 102 + 2840, // 103 + 14253, // 104 + 3164, // 105 + 2221, // 106 + 1687, // 107 + 3208, // 108 + 2739, // 109 + 3512, // 110 + 4796, // 111 + 4091, // 112 + 3515, // 113 + 5288, // 114 + 4016, // 115 + 7937, // 116 + 6031, // 117 + 5360, // 118 + 3924, // 119 + 4892, // 120 + 3743, // 121 + 4566, // 122 + 4807, // 123 + 5852, // 124 + 6400, // 125 + 6225, // 126 + 8291, // 127 + 23243, // 128 + 7838, // 129 + 7073, // 130 + 8935, // 131 + 5437, // 132 + 4483, // 133 + 3641, // 134 + 5256, // 135 + 5312, // 136 + 5328, // 137 + 5370, // 138 + 3492, // 139 + 2458, // 140 + 1694, // 141 + 1821, // 142 + 2121, // 143 + 1916, // 144 + 1149, // 145 + 1516, // 146 + 1367, // 147 + 1236, // 148 + 1029, // 149 + 1258, // 150 + 1104, // 151 + 1245, // 152 + 1006, // 153 + 1149, // 154 + 1025, // 155 + 1241, // 156 + 952, // 157 + 1287, // 158 + 997, // 159 + 1713, // 160 + 1009, // 161 + 1187, // 162 + 879, // 163 + 1099, // 164 + 929, // 165 + 1078, // 166 + 951, // 167 + 1656, // 168 + 930, // 169 + 1153, // 170 + 1030, // 171 + 1262, // 172 + 1062, // 173 + 1214, // 174 + 1060, // 175 + 1621, // 176 + 930, // 177 + 1106, // 178 + 912, // 179 + 1034, // 180 + 892, // 181 + 1158, // 182 + 990, // 183 + 1175, // 184 + 850, // 185 + 1121, // 186 + 903, // 187 + 1087, // 188 + 920, // 189 + 1144, // 190 + 1056, // 191 + 3462, // 192 + 2240, // 193 + 4397, // 194 + 12136, // 195 + 7758, // 196 + 1345, // 197 + 1307, // 198 + 3278, // 199 + 1950, // 200 + 886, // 201 + 1023, // 202 + 1112, // 203 + 1077, // 204 + 1042, // 205 + 1061, // 206 + 1071, // 207 + 1484, // 208 + 1001, // 209 + 1096, // 210 + 915, // 211 + 1052, // 212 + 995, // 213 + 1070, // 214 + 876, // 215 + 1111, // 216 + 851, // 217 + 1059, // 218 + 805, // 219 + 1112, // 220 + 923, // 221 + 1103, // 222 + 817, // 223 + 1899, // 224 + 1872, // 225 + 976, // 226 + 841, // 227 + 1127, // 228 + 956, // 229 + 1159, // 230 + 950, // 231 + 7791, // 232 + 954, // 233 + 1289, // 234 + 933, // 235 + 1127, // 236 + 3207, // 237 + 1020, // 238 + 927, // 239 + 1355, // 240 + 768, // 241 + 1040, // 242 + 745, // 243 + 952, // 244 + 805, // 245 + 1073, // 246 + 740, // 247 + 1013, // 248 + 805, // 249 + 1008, // 250 + 796, // 251 + 996, // 252 + 1057, // 253 + 11457, // 254 + 13504, // 255 +}; + +void MSG_initHuffman() { + int i,j; + + msgInit = qtrue; + Huff_Init( &msgHuff ); + for ( i = 0; i < 256; i++ ) { + for ( j = 0; j < msg_hData[i]; j++ ) { + Huff_addRef( &msgHuff.compressor, (byte)i ); /* Do update */ + Huff_addRef( &msgHuff.decompressor, (byte)i ); /* Do update */ + } + } +} + +/* +void MSG_NUinitHuffman() { + byte *data; + int size, i, ch; + int array[256]; + + msgInit = qtrue; + + Huff_Init(&msgHuff); + // load it in + size = FS_ReadFile( "netchan/netchan.bin", (void **)&data ); + + for(i=0;i<256;i++) { + array[i] = 0; + } + for(i=0;i. + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "../game/q_shared.h" +#include "qcommon.h" + +/* + +packet header +------------- +4 outgoing sequence. high bit will be set if this is a fragmented message +[2 qport (only for client to server)] +[2 fragment start byte] +[2 fragment length. if < FRAGMENT_SIZE, this is the last fragment] + +if the sequence number is -1, the packet should be handled as an out-of-band +message instead of as part of a netcon. + +All fragments will have the same sequence numbers. + +The qport field is a workaround for bad address translating routers that +sometimes remap the client's source port on a packet during gameplay. + +If the base part of the net address matches and the qport matches, then the +channel matches even if the IP port differs. The IP port should be updated +to the new value before sending out any replies. + +*/ + + +#define MAX_PACKETLEN 1400 // max size of a network packet + +#define FRAGMENT_SIZE ( MAX_PACKETLEN - 100 ) +#define PACKET_HEADER 10 // two ints and a short + +#define FRAGMENT_BIT ( 1 << 31 ) + +cvar_t *showpackets; +cvar_t *showdrop; +cvar_t *qport; + +static char *netsrcString[2] = { + "client", + "server" +}; + +/* +=============== +Netchan_Init + +=============== +*/ +void Netchan_Init( int port ) { + port &= 0xffff; + showpackets = Cvar_Get( "showpackets", "0", CVAR_TEMP ); + showdrop = Cvar_Get( "showdrop", "0", CVAR_TEMP ); + qport = Cvar_Get( "net_qport", va( "%i", port ), CVAR_INIT ); +} + +/* +============== +Netchan_Setup + +called to open a channel to a remote system +============== +*/ +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { + memset( chan, 0, sizeof( *chan ) ); + + chan->sock = sock; + chan->remoteAddress = adr; + chan->qport = qport; + chan->incomingSequence = 0; + chan->outgoingSequence = 1; +} + +/* +================= +Netchan_TransmitNextFragment + +Send one fragment of the current message +================= +*/ +void Netchan_TransmitNextFragment( netchan_t *chan ) { + msg_t send; + byte send_buf[MAX_PACKETLEN]; + int fragmentLength; + + // write the packet header + MSG_InitOOB( &send, send_buf, sizeof( send_buf ) ); // <-- only do the oob here + + MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { + MSG_WriteShort( &send, qport->integer ); + } + + // copy the reliable message to the packet first + fragmentLength = FRAGMENT_SIZE; + if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) { + fragmentLength = chan->unsentLength - chan->unsentFragmentStart; + } + + MSG_WriteShort( &send, chan->unsentFragmentStart ); + MSG_WriteShort( &send, fragmentLength ); + MSG_WriteData( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength ); + + // send the datagram + NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + + if ( showpackets->integer ) { + Com_Printf( "%s send %4i : s=%i fragment=%i,%i\n" + , netsrcString[ chan->sock ] + , send.cursize + , chan->outgoingSequence + , chan->unsentFragmentStart, fragmentLength ); + } + + chan->unsentFragmentStart += fragmentLength; + + // this exit condition is a little tricky, because a packet + // that is exactly the fragment length still needs to send + // a second packet of zero length so that the other side + // can tell there aren't more to follow + if ( chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE ) { + chan->outgoingSequence++; + chan->unsentFragments = qfalse; + } +} + + +/* +=============== +Netchan_Transmit + +Sends a message to a connection, fragmenting if necessary +A 0 length will still generate a packet. +================ +*/ +void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { + msg_t send; + byte send_buf[MAX_PACKETLEN]; + + if ( length > MAX_MSGLEN ) { + Com_Error( ERR_DROP, "Netchan_Transmit: length = %i", length ); + } + chan->unsentFragmentStart = 0; + + // fragment large reliable messages + if ( length >= FRAGMENT_SIZE ) { + chan->unsentFragments = qtrue; + chan->unsentLength = length; + Com_Memcpy( chan->unsentBuffer, data, length ); + + // only send the first fragment now + Netchan_TransmitNextFragment( chan ); + + return; + } + + // write the packet header + MSG_InitOOB( &send, send_buf, sizeof( send_buf ) ); + + MSG_WriteLong( &send, chan->outgoingSequence ); + chan->outgoingSequence++; + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { + MSG_WriteShort( &send, qport->integer ); + } + + MSG_WriteData( &send, data, length ); + + // send the datagram + NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + + if ( showpackets->integer ) { + Com_Printf( "%s send %4i : s=%i ack=%i\n" + , netsrcString[ chan->sock ] + , send.cursize + , chan->outgoingSequence - 1 + , chan->incomingSequence ); + } +} + +/* +================= +Netchan_Process + +Returns qfalse if the message should not be processed due to being +out of order or a fragment. + +Msg must be large enough to hold MAX_MSGLEN, because if this is the +final fragment of a multi-part message, the entire thing will be +copied out. +================= +*/ +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { + int sequence; + int qport; + int fragmentStart, fragmentLength; + qboolean fragmented; + + // get sequence numbers + MSG_BeginReadingOOB( msg ); + sequence = MSG_ReadLong( msg ); + + // check for fragment information + if ( sequence & FRAGMENT_BIT ) { + sequence &= ~FRAGMENT_BIT; + fragmented = qtrue; + } else { + fragmented = qfalse; + } + + // read the qport if we are a server + if ( chan->sock == NS_SERVER ) { + qport = MSG_ReadShort( msg ); + } + + // read the fragment information + if ( fragmented ) { + fragmentStart = MSG_ReadShort( msg ); + fragmentLength = MSG_ReadShort( msg ); + } else { + fragmentStart = 0; // stop warning message + fragmentLength = 0; + } + + if ( showpackets->integer ) { + if ( fragmented ) { + Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n" + , netsrcString[ chan->sock ] + , msg->cursize + , sequence + , fragmentStart, fragmentLength ); + } else { + Com_Printf( "%s recv %4i : s=%i\n" + , netsrcString[ chan->sock ] + , msg->cursize + , sequence ); + } + } + + // + // discard out of order or duplicated packets + // + if ( sequence <= chan->incomingSequence ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Out of order packet %i at %i\n" + , NET_AdrToString( chan->remoteAddress ) + , sequence + , chan->incomingSequence ); + } + return qfalse; + } + + // + // dropped packets don't keep the message from being used + // + chan->dropped = sequence - ( chan->incomingSequence + 1 ); + if ( chan->dropped > 0 ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Dropped %i packets at %i\n" + , NET_AdrToString( chan->remoteAddress ) + , chan->dropped + , sequence ); + } + } + + + // + // if this is the final framgent of a reliable message, + // bump incoming_reliable_sequence + // + if ( fragmented ) { + // TTimo + // make sure we add the fragments in correct order + // either a packet was dropped, or we received this one too soon + // we don't reconstruct the fragments. we will wait till this fragment gets to us again + // (NOTE: we could probably try to rebuild by out of order chunks if needed) + if ( sequence != chan->fragmentSequence ) { + chan->fragmentSequence = sequence; + chan->fragmentLength = 0; + } + + // if we missed a fragment, dump the message + if ( fragmentStart != chan->fragmentLength ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Dropped a message fragment\n" + , NET_AdrToString( chan->remoteAddress ) + , sequence ); + } + // we can still keep the part that we have so far, + // so we don't need to clear chan->fragmentLength + return qfalse; + } + + // copy the fragment to the fragment buffer + if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize || + chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:illegal fragment length\n" + , NET_AdrToString( chan->remoteAddress ) ); + } + return qfalse; + } + + memcpy( chan->fragmentBuffer + chan->fragmentLength, + msg->data + msg->readcount, fragmentLength ); + + chan->fragmentLength += fragmentLength; + + // if this wasn't the last fragment, don't process anything + if ( fragmentLength == FRAGMENT_SIZE ) { + return qfalse; + } + + if ( chan->fragmentLength > msg->maxsize ) { + Com_Printf( "%s:fragmentLength %i > msg->maxsize\n" + , NET_AdrToString( chan->remoteAddress ), + chan->fragmentLength ); + return qfalse; + } + + // copy the full message over the partial fragment + + // make sure the sequence number is still there + *(int *)msg->data = LittleLong( sequence ); + + memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength ); + msg->cursize = chan->fragmentLength + 4; + chan->fragmentLength = 0; + msg->readcount = 4; // past the sequence number + msg->bit = 32; // past the sequence number + + + // TTimo + // clients were not acking fragmented messages + chan->incomingSequence = sequence; + + return qtrue; + } + + // + // the message can now be read from the current message pointer + // + chan->incomingSequence = sequence; + + return qtrue; +} + + +//============================================================================== + +/* +=================== +NET_CompareBaseAdr + +Compares without the port +=================== +*/ +qboolean NET_CompareBaseAdr( netadr_t a, netadr_t b ) { + if ( a.type != b.type ) { + return qfalse; + } + + if ( a.type == NA_LOOPBACK ) { + return qtrue; + } + + if ( a.type == NA_IP ) { + if ( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] ) { + return qtrue; + } + return qfalse; + } + + if ( a.type == NA_IPX ) { + if ( ( memcmp( a.ipx, b.ipx, 10 ) == 0 ) ) { + return qtrue; + } + return qfalse; + } + + + Com_Printf( "NET_CompareBaseAdr: bad address type\n" ); + return qfalse; +} + +const char *NET_AdrToString( netadr_t a ) { + static char s[64]; + + if ( a.type == NA_LOOPBACK ) { + Com_sprintf( s, sizeof( s ), "loopback" ); + } else if ( a.type == NA_BOT ) { + Com_sprintf( s, sizeof( s ), "bot" ); + } else if ( a.type == NA_IP ) { + Com_sprintf( s, sizeof( s ), "%i.%i.%i.%i:%hu", + a.ip[0], a.ip[1], a.ip[2], a.ip[3], BigShort( a.port ) ); + } else { + Com_sprintf( s, sizeof( s ), "%02x%02x%02x%02x.%02x%02x%02x%02x%02x%02x:%hu", + a.ipx[0], a.ipx[1], a.ipx[2], a.ipx[3], a.ipx[4], a.ipx[5], a.ipx[6], a.ipx[7], a.ipx[8], a.ipx[9], + BigShort( a.port ) ); + } + + return s; +} + + +qboolean NET_CompareAdr( netadr_t a, netadr_t b ) { + if ( a.type != b.type ) { + return qfalse; + } + + if ( a.type == NA_LOOPBACK ) { + return qtrue; + } + + if ( a.type == NA_IP ) { + if ( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port ) { + return qtrue; + } + return qfalse; + } + + if ( a.type == NA_IPX ) { + if ( ( memcmp( a.ipx, b.ipx, 10 ) == 0 ) && a.port == b.port ) { + return qtrue; + } + return qfalse; + } + + Com_Printf( "NET_CompareAdr: bad address type\n" ); + return qfalse; +} + + +qboolean NET_IsLocalAddress( netadr_t adr ) { + return adr.type == NA_LOOPBACK; +} + + + +/* +============================================================================= + +LOOPBACK BUFFERS FOR LOCAL PLAYER + +============================================================================= +*/ + +// there needs to be enough loopback messages to hold a complete +// gamestate of maximum size +#define MAX_LOOPBACK 16 + +typedef struct { + byte data[MAX_PACKETLEN]; + int datalen; +} loopmsg_t; + +typedef struct { + loopmsg_t msgs[MAX_LOOPBACK]; + int get, send; +} loopback_t; + +loopback_t loopbacks[2]; + + +qboolean NET_GetLoopPacket( netsrc_t sock, netadr_t *net_from, msg_t *net_message ) { + int i; + loopback_t *loop; + + loop = &loopbacks[sock]; + + if ( loop->send - loop->get > MAX_LOOPBACK ) { + loop->get = loop->send - MAX_LOOPBACK; + } + + if ( loop->get >= loop->send ) { + return qfalse; + } + + i = loop->get & ( MAX_LOOPBACK - 1 ); + loop->get++; + + memcpy( net_message->data, loop->msgs[i].data, loop->msgs[i].datalen ); + net_message->cursize = loop->msgs[i].datalen; + memset( net_from, 0, sizeof( *net_from ) ); + net_from->type = NA_LOOPBACK; + return qtrue; + +} + + +void NET_SendLoopPacket( netsrc_t sock, int length, const void *data, netadr_t to ) { + int i; + loopback_t *loop; + + loop = &loopbacks[sock ^ 1]; + + i = loop->send & ( MAX_LOOPBACK - 1 ); + loop->send++; + + memcpy( loop->msgs[i].data, data, length ); + loop->msgs[i].datalen = length; +} + +//============================================================================= + + +void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ) { + + // sequenced packets are shown in netchan, so just show oob + if ( showpackets->integer && *(int *)data == -1 ) { + Com_Printf( "send packet %4i\n", length ); + } + + if ( to.type == NA_LOOPBACK ) { + NET_SendLoopPacket( sock, length, data, to ); + return; + } + if ( to.type == NA_BOT ) { + return; + } + if ( to.type == NA_BAD ) { + return; + } + + Sys_SendPacket( length, data, to ); +} + +/* +=============== +NET_OutOfBandPrint + +Sends a text message in an out-of-band datagram +================ +*/ +void QDECL NET_OutOfBandPrint( netsrc_t sock, netadr_t adr, const char *format, ... ) { + va_list argptr; + char string[MAX_MSGLEN]; + + // set the header + string[0] = -1; + string[1] = -1; + string[2] = -1; + string[3] = -1; + + va_start( argptr, format ); + Q_vsnprintf( string + 4, sizeof( string ) - 4, format, argptr ); + va_end( argptr ); + + // send the datagram + NET_SendPacket( sock, strlen( string ), string, adr ); +} + + + +/* +=============== +NET_OutOfBandPrint + +Sends a data message in an out-of-band datagram (only used for "connect") +================ +*/ +void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ) { + byte string[MAX_MSGLEN * 2]; + int i; + msg_t mbuf; + + // set the header + string[0] = 0xff; + string[1] = 0xff; + string[2] = 0xff; + string[3] = 0xff; + + for ( i = 0; i < len; i++ ) { + string[i + 4] = format[i]; + } + + mbuf.data = string; + mbuf.cursize = len + 4; + Huff_Compress( &mbuf, 12 ); + // send the datagram + NET_SendPacket( sock, mbuf.cursize, mbuf.data, adr ); +} + + +/* +============= +NET_StringToAdr + +Traps "localhost" for loopback, passes everything else to system +============= +*/ +qboolean NET_StringToAdr( const char *s, netadr_t *a ) { + qboolean r; + char base[MAX_STRING_CHARS]; + char *port; + + if ( !strcmp( s, "localhost" ) ) { + memset( a, 0, sizeof( *a ) ); + a->type = NA_LOOPBACK; + return qtrue; + } + + // look for a port number + Q_strncpyz( base, s, sizeof( base ) ); + port = strstr( base, ":" ); + if ( port ) { + *port = 0; + port++; + } + + r = Sys_StringToAdr( base, a ); + + if ( !r ) { + a->type = NA_BAD; + return qfalse; + } + + // inet_addr returns this if out of range + if ( a->ip[0] == 255 && a->ip[1] == 255 && a->ip[2] == 255 && a->ip[3] == 255 ) { + a->type = NA_BAD; + return qfalse; + } + + if ( port ) { + a->port = BigShort( (short)atoi( port ) ); + } else { + a->port = BigShort( PORT_SERVER ); + } + + return qtrue; +} + diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h new file mode 100644 index 0000000..62b7003 --- /dev/null +++ b/src/qcommon/qcommon.h @@ -0,0 +1,1223 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// qcommon.h -- definitions common between client and server, but not game.or ref modules +#ifndef _QCOMMON_H_ +#define _QCOMMON_H_ + +#include "../qcommon/cm_public.h" + +//#define PRE_RELEASE_DEMO + +//============================================================================ + +// +// msg.c +// +typedef struct { + qboolean allowoverflow; // if false, do a Com_Error + qboolean overflowed; // set to true if the buffer size failed (with allowoverflow set) + qboolean oob; // set to true if the buffer size failed (with allowoverflow set) + byte *data; + int maxsize; + int cursize; + int uncompsize; // NERVE - SMF - net debugging + int readcount; + int bit; // for bitwise reads and writes +} msg_t; + +void MSG_Init( msg_t *buf, byte *data, int length ); +void MSG_InitOOB( msg_t *buf, byte *data, int length ); +void MSG_Clear( msg_t *buf ); +void *MSG_GetSpace( msg_t *buf, int length ); +void MSG_WriteData( msg_t *buf, const void *data, int length ); +void MSG_Bitstream( msg_t *buf ); + +// TTimo +// copy a msg_t in case we need to store it as is for a bit +// (as I needed this to keep an msg_t from a static var for later use) +// sets data buffer as MSG_Init does prior to do the copy +void MSG_Copy( msg_t *buf, byte *data, int length, msg_t *src ); + +struct usercmd_s; +struct entityState_s; +struct playerState_s; + +void MSG_WriteBits( msg_t *msg, int value, int bits ); + +void MSG_WriteChar( msg_t *sb, int c ); +void MSG_WriteByte( msg_t *sb, int c ); +void MSG_WriteShort( msg_t *sb, int c ); +void MSG_WriteLong( msg_t *sb, int c ); +void MSG_WriteFloat( msg_t *sb, float f ); +void MSG_WriteString( msg_t *sb, const char *s ); +void MSG_WriteBigString( msg_t *sb, const char *s ); +void MSG_WriteAngle16( msg_t *sb, float f ); + +void MSG_BeginReading( msg_t *sb ); +void MSG_BeginReadingOOB( msg_t *sb ); + +int MSG_ReadBits( msg_t *msg, int bits ); + +int MSG_ReadChar( msg_t *sb ); +int MSG_ReadByte( msg_t *sb ); +int MSG_ReadShort( msg_t *sb ); +int MSG_ReadLong( msg_t *sb ); +float MSG_ReadFloat( msg_t *sb ); +char *MSG_ReadString( msg_t *sb ); +char *MSG_ReadBigString( msg_t *sb ); +char *MSG_ReadStringLine( msg_t *sb ); +float MSG_ReadAngle16( msg_t *sb ); +void MSG_ReadData( msg_t *sb, void *buffer, int size ); + + +void MSG_WriteDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to ); +void MSG_ReadDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to ); + +void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ); +void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ); + +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to + , qboolean force ); +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, + int number ); + +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ); +void MSG_ReadDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ); + + +void MSG_ReportChangeVectors_f( void ); + +//============================================================================ + +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 ); + + +/* +============================================================== + +NET + +============================================================== +*/ + +#define PACKET_BACKUP 32 // number of old messages that must be kept on client and + // server for delta comrpession and ping estimation +#define PACKET_MASK ( PACKET_BACKUP - 1 ) + +#define MAX_PACKET_USERCMDS 32 // max number of usercmd_t in a packet + +#define PORT_ANY -1 + +// RF, increased this, seems to keep causing problems when set to 64, especially when loading +// a savegame, which is hard to fix on that side, since we can't really spread out a loadgame +// among several frames +//#define MAX_RELIABLE_COMMANDS 64 // max string commands buffered for restransmit +//#define MAX_RELIABLE_COMMANDS 128 // max string commands buffered for restransmit +#define MAX_RELIABLE_COMMANDS 256 // bigger! + +typedef enum { + NA_BOT, + NA_BAD, // an address lookup failed + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + NA_IPX, + NA_BROADCAST_IPX +} netadrtype_t; + +typedef enum { + NS_CLIENT, + NS_SERVER +} netsrc_t; + +typedef struct { + netadrtype_t type; + + byte ip[4]; + byte ipx[10]; + + unsigned short port; +} netadr_t; + +void NET_Init( void ); +void NET_Shutdown( void ); +void NET_Restart( void ); +void NET_Config( qboolean enableNetworking ); + +void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ); +void QDECL NET_OutOfBandPrint( netsrc_t net_socket, netadr_t adr, const char *format, ... ); +void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ); + +qboolean NET_CompareAdr( netadr_t a, netadr_t b ); +qboolean NET_CompareBaseAdr( netadr_t a, netadr_t b ); +qboolean NET_IsLocalAddress( netadr_t adr ); +const char *NET_AdrToString( netadr_t a ); +qboolean NET_StringToAdr( const char *s, netadr_t *a ); +qboolean NET_GetLoopPacket( netsrc_t sock, netadr_t *net_from, msg_t *net_message ); +void NET_Sleep( int msec ); + + +//----(SA) increased for larger submodel entity counts +#define MAX_MSGLEN 32768 // max length of a message, which may +//#define MAX_MSGLEN 16384 // max length of a message, which may +// be fragmented into multiple packets +#define MAX_DOWNLOAD_WINDOW 8 // max of eight download frames +#define MAX_DOWNLOAD_BLKSIZE 2048 // 2048 byte block chunks + + +/* +Netchan handles packet fragmentation and out of order / duplicate suppression +*/ + +typedef struct { + netsrc_t sock; + + int dropped; // between last packet and previous + + netadr_t remoteAddress; + int qport; // qport value to write when transmitting + + // sequencing variables + int incomingSequence; + int outgoingSequence; + + // incoming fragment assembly buffer + int fragmentSequence; + int fragmentLength; + byte fragmentBuffer[MAX_MSGLEN]; + + // outgoing fragment buffer + // we need to space out the sending of large fragmented messages + qboolean unsentFragments; + int unsentFragmentStart; + int unsentLength; + byte unsentBuffer[MAX_MSGLEN]; +} netchan_t; + +void Netchan_Init( int qport ); +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ); + +void Netchan_Transmit( netchan_t *chan, int length, const byte *data ); +void Netchan_TransmitNextFragment( netchan_t *chan ); + +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ); + + +/* +============================================================== + +PROTOCOL + +============================================================== +*/ + +// sent by the server, printed on connection screen, works for all clients +// (restrictions: does not handle \n, no more than 256 chars) +#define PROTOCOL_MISMATCH_ERROR "ERROR: Protocol Mismatch Between Client and Server.\ +The server you are attempting to join is running an incompatible version of the game." + +// long version used by the client in diagnostic window +#define PROTOCOL_MISMATCH_ERROR_LONG "ERROR: Protocol Mismatch Between Client and Server.\n\n\ +The server you attempted to join is running an incompatible version of the game.\n\ +You or the server may be running older versions of the game. Press the auto-update\ + button if it appears on the Main Menu screen." + +#ifndef PRE_RELEASE_DEMO +// 1.33 - protocol 59 +// 1.4 - protocol 60 +#define PROTOCOL_VERSION 60 +#define GAMENAME_STRING "wolfmp" +#else +// the demo uses a different protocol version for independant browsing + #define PROTOCOL_VERSION 50 // NERVE - SMF - wolfMP protocol version +#endif + +// NERVE - SMF - wolf multiplayer master servers +#define UPDATE_SERVER_NAME "wolfmotd.idsoftware.com" // 192.246.40.65 +#define MASTER_SERVER_NAME "wolfmaster.idsoftware.com" +#define AUTHORIZE_SERVER_NAME "wolfauthorize.idsoftware.com" + +// TTimo: allow override for easy dev/testing.. +// see cons -- update_server=myhost +#if !defined( AUTOUPDATE_SERVER_NAME ) + #define AUTOUPDATE_SERVER1_NAME "au2rtcw1.activision.com" // DHM - Nerve + #define AUTOUPDATE_SERVER2_NAME "au2rtcw2.activision.com" // DHM - Nerve + #define AUTOUPDATE_SERVER3_NAME "au2rtcw3.activision.com" // DHM - Nerve + #define AUTOUPDATE_SERVER4_NAME "au2rtcw4.activision.com" // DHM - Nerve + #define AUTOUPDATE_SERVER5_NAME "au2rtcw5.activision.com" // DHM - Nerve +#else + #define AUTOUPDATE_SERVER1_NAME AUTOUPDATE_SERVER_NAME + #define AUTOUPDATE_SERVER2_NAME AUTOUPDATE_SERVER_NAME + #define AUTOUPDATE_SERVER3_NAME AUTOUPDATE_SERVER_NAME + #define AUTOUPDATE_SERVER4_NAME AUTOUPDATE_SERVER_NAME + #define AUTOUPDATE_SERVER5_NAME AUTOUPDATE_SERVER_NAME +#endif + +#define PORT_MASTER 27950 +#define PORT_UPDATE 27951 +#define PORT_AUTHORIZE 27952 +#define PORT_SERVER 27960 +#define NUM_SERVER_PORTS 4 // broadcast scan this many ports after + // PORT_SERVER so a single machine can + // run multiple servers + + +// the svc_strings[] array in cl_parse.c should mirror this +// +// server to client +// +enum svc_ops_e { + svc_bad, + svc_nop, + svc_gamestate, + svc_configstring, // [short] [string] only in gamestate messages + svc_baseline, // only in gamestate messages + svc_serverCommand, // [string] to be executed by client game module + svc_download, // [short] size [size bytes] + svc_snapshot, + svc_EOF +}; + + +// +// client to server +// +enum clc_ops_e { + clc_bad, + clc_nop, + clc_move, // [[usercmd_t] + clc_moveNoDelta, // [[usercmd_t] + clc_clientCommand, // [string] message + clc_EOF +}; + +/* +============================================================== + +VIRTUAL MACHINE + +============================================================== +*/ + +typedef struct vm_s vm_t; + +typedef enum { + VMI_NATIVE, + VMI_BYTECODE, + VMI_COMPILED +} vmInterpret_t; + +typedef enum { + TRAP_MEMSET = 100, + TRAP_MEMCPY, + TRAP_STRNCPY, + TRAP_SIN, + TRAP_COS, + TRAP_ATAN2, + TRAP_SQRT, + TRAP_MATRIXMULTIPLY, + TRAP_ANGLEVECTORS, + TRAP_PERPENDICULARVECTOR, + TRAP_FLOOR, + TRAP_CEIL, + + TRAP_TESTPRINTINT, + TRAP_TESTPRINTFLOAT +} sharedTraps_t; + +void VM_Init( void ); +vm_t *VM_Create( const char *module, int ( *systemCalls )( int * ), + vmInterpret_t interpret ); +// module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm" + +void VM_Free( vm_t *vm ); +void VM_Clear( void ); +vm_t *VM_Restart( vm_t *vm ); + +int QDECL VM_Call( vm_t *vm, int callNum, ... ); + +void VM_Debug( int level ); + +void *VM_ArgPtr( int intValue ); +void *VM_ExplicitArgPtr( vm_t *vm, int intValue ); + +/* +============================================================== + +CMD + +Command text buffering 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 entire text +files can be execed. + +*/ + +void Cbuf_Init( void ); +// allocates an initial text buffer that will grow as needed + +void Cbuf_AddText( const char *text ); +// Adds command text at the end of the buffer, does NOT add a final \n + +void Cbuf_ExecuteText( int exec_when, const char *text ); +// this can be used in place of either Cbuf_AddText or Cbuf_InsertText + +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, or current args will be destroyed. + +//=========================================================================== + +/* + +Command execution takes a null terminated string, breaks it into tokens, +then searches for a command or variable that matches the first token. + +*/ + +typedef void ( *xcommand_t )( void ); + +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 +// if function is NULL, the command will be forwarded to the server +// as a clc_clientCommand instead of executed locally + +void Cmd_RemoveCommand( const char *cmd_name ); + +void Cmd_CommandCompletion( void ( *callback )( const char *s ) ); +// callback with each valid string + +int Cmd_Argc( void ); +char *Cmd_Argv( int arg ); +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ); +char *Cmd_Args( void ); +char *Cmd_ArgsFrom( int arg ); +void Cmd_ArgsBuffer( char *buffer, int bufferLength ); +char *Cmd_Cmd( 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. + +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 ); +// Parses a single line of text into arguments and tries to execute it +// as if it was typed at the console + + +/* +============================================================== + +CVAR + +============================================================== +*/ + +/* + +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. + +The user can access cvars from the console in three ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 +set r_draworder 0 as above, but creates the cvar if not present + +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. + +The are also occasionally used to communicated information between different +modules of the program. + +*/ + +cvar_t *Cvar_Get( const char *var_name, const char *value, int flags ); +// creates the variable if it doesn't exist, or returns the existing one +// if it exists, the value will not be changed, but flags will be ORed in +// that allows variables to be unarchived without needing bitflags +// if value is "", the value will not override a previously set value. + +void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +// basically a slightly modified Cvar_Get for the interpreted modules + +void Cvar_Update( vmCvar_t *vmCvar ); +// updates an interpreted modules' version of a cvar + +void Cvar_Set( const char *var_name, const char *value ); +// will create the variable with no flags if it doesn't exist + +void Cvar_SetLatched( const char *var_name, const char *value ); +// don't set the cvar immediately + +void Cvar_SetValue( const char *var_name, float value ); +// expands value to a string and calls Cvar_Set + +float Cvar_VariableValue( const char *var_name ); +int Cvar_VariableIntegerValue( const char *var_name ); +// returns 0 if not defined or non numeric + +char *Cvar_VariableString( const char *var_name ); +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +// returns an empty string if not defined + +void Cvar_CommandCompletion( void ( *callback )( const char *s ) ); +// callback with each valid string + +void Cvar_Reset( const char *var_name ); + +void Cvar_SetCheatState( void ); +// reset all testing vars to a safe value + +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( fileHandle_t f ); +// writes lines containing "set variable value" for all variables +// with the archive flag set to true. + +void Cvar_Init( void ); + +char *Cvar_InfoString( int bit ); +char *Cvar_InfoString_Big( int bit ); +// returns an info string containing all the cvars that have the given bit set +// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc ) +void Cvar_InfoStringBuffer( int bit, char *buff, int buffsize ); + +void Cvar_Restart_f( void ); + +extern int cvar_modifiedFlags; +// whenever a cvar is modifed, its flags will be OR'd into this, so +// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO, +// etc, variables have been modified since the last check. The bit +// can then be cleared to allow another change detection. + +/* +============================================================== + +FILESYSTEM + +No stdio calls should be used by any part of the game, because +we need to deal with all sorts of directory and seperator char +issues. +============================================================== +*/ + +#define BASEGAME "main" + +// referenced flags +// these are in loop specific order so don't change the order +#define FS_GENERAL_REF 0x01 +#define FS_UI_REF 0x02 +#define FS_CGAME_REF 0x04 +#define FS_QAGAME_REF 0x08 +// number of id paks that will never be autodownloaded from baseq3 +#define NUM_ID_PAKS 9 + +#define MAX_FILE_HANDLES 64 + +qboolean FS_Initialized(); + +void FS_InitFilesystem( void ); +void FS_Shutdown( qboolean closemfp ); + +qboolean FS_ConditionalRestart( int checksumFeed ); +void FS_Restart( int checksumFeed ); +// shutdown and restart the filesystem so changes to fs_gamedir can take effect + +char **FS_ListFiles( const char *directory, const char *extension, int *numfiles ); +// directory should not have either a leading or trailing / +// if extension is "/", only subdirectories will be returned +// the returned files will not include any directories or / + +void FS_FreeFileList( char **list ); + +qboolean FS_FileExists( const char *file ); + +int FS_LoadStack(); + +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int FS_GetModList( char *listbuf, int bufsize ); + +fileHandle_t FS_FOpenFileWrite( const char *qpath ); +// will properly create any needed paths and deal with seperater character issues + +int FS_filelength( fileHandle_t f ); +fileHandle_t FS_SV_FOpenFileWrite( const char *filename ); +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ); +void FS_SV_Rename( const char *from, const char *to ); +int FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE ); +/* +if uniqueFILE is true, then a new FILE will be fopened even if the file +is found in an already open pak file. If uniqueFILE is false, you must call +FS_FCloseFile instead of fclose, otherwise the pak FILE would be improperly closed +It is generally safe to always set uniqueFILE to true, because the majority of +file IO goes through FS_ReadFile, which Does The Right Thing already. +*/ +/* TTimo +show_bug.cgi?id=506 +added exclude flag to filter out regular dirs or pack files on demand +would rather have used FS_FOpenFileRead(..., int filter_flag = 0) +but that's a C++ construct .. +*/ +#define FS_EXCLUDE_DIR 0x1 +#define FS_EXCLUDE_PK3 0x2 +int FS_FOpenFileRead_Filtered( const char *qpath, fileHandle_t *file, qboolean uniqueFILE, int filter_flag ); + +int FS_FileIsInPAK( const char *filename, int *pChecksum ); +// returns 1 if a file is in the PAK file, otherwise -1 + +int FS_Delete( char *filename ); // only works inside the 'save' directory (for deleting savegames/images) + +int FS_Write( const void *buffer, int len, fileHandle_t f ); + +int FS_Read( void *buffer, int len, fileHandle_t f ); +// properly handles partial reads and reads from other dlls + +void FS_FCloseFile( fileHandle_t f ); +// note: you can't just fclose from another DLL, due to MS libc issues + +int FS_ReadFile( const char *qpath, void **buffer ); +// returns the length of the file +// a null buffer will just return the file length without loading +// as a quick check for existance. -1 length == not present +// A 0 byte will always be appended at the end, so string ops are safe. +// the buffer should be considered read-only, because it may be cached +// for other uses. + +void FS_ForceFlush( fileHandle_t f ); +// forces flush on files we're writing to. + +void FS_FreeFile( void *buffer ); +// frees the memory returned by FS_ReadFile + +void FS_WriteFile( const char *qpath, const void *buffer, int size ); +// writes a complete file, creating any subdirectories needed + +int FS_filelength( fileHandle_t f ); +// doesn't work for files that are opened from a pack file + +int FS_FTell( fileHandle_t f ); +// where are we? + +void FS_Flush( fileHandle_t f ); + +void QDECL FS_Printf( fileHandle_t f, const char *fmt, ... ); +// like fprintf + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ); +// opens a file for reading, writing, or appending depending on the value of mode + +int FS_Seek( fileHandle_t f, long offset, int origin ); +// seek on a file (doesn't work for zip files!!!!!!!!) + +qboolean FS_FilenameCompare( const char *s1, const char *s2 ); + +const char *FS_GamePureChecksum( void ); +// Returns the checksum of the pk3 from which the server loaded the qagame.qvm + +const char *FS_LoadedPakNames( void ); +const char *FS_LoadedPakChecksums( void ); +const char *FS_LoadedPakPureChecksums( void ); +// Returns a space separated string containing the checksums of all loaded pk3 files. +// Servers with sv_pure set will get this string and pass it to clients. + +const char *FS_ReferencedPakNames( void ); +const char *FS_ReferencedPakChecksums( void ); +const char *FS_ReferencedPakPureChecksums( void ); +// Returns a space separated string containing the checksums of all loaded +// AND referenced pk3 files. Servers with sv_pure set will get this string +// back from clients for pure validation + +void FS_ClearPakReferences( int flags ); +// clears referenced booleans on loaded pk3s + +void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ); +void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ); +// If the string is empty, all data sources will be allowed. +// If not empty, only pk3 files that match one of the space +// separated checksums will be checked for files, with the +// sole exception of .cfg files. + +qboolean FS_idPak( char *pak, char *base ); +qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ); + +void FS_Rename( const char *from, const char *to ); + +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ); + +#if !defined( DEDICATED ) +extern int cl_connectedToPureServer; +qboolean FS_CL_ExtractFromPakFile( const char *path, const char *gamedir, const char *filename, const char *cvar_lastVersion ); +#endif + +#if defined( DO_LIGHT_DEDICATED ) +int FS_RandChecksumFeed(); +#endif + +char *FS_ShiftedStrStr( const char *string, const char *substring, int shift ); +char *FS_ShiftStr( const char *string, int shift ); + +void FS_CopyFile( char *fromOSPath, char *toOSPath ); + +qboolean FS_VerifyPak( const char *pak ); + +/* +============================================================== + +Edit fields and command line history/completion + +============================================================== +*/ + +#define MAX_EDIT_LINE 256 +typedef struct { + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; +} field_t; + +void Field_Clear( field_t *edit ); +void Field_CompleteCommand( field_t *edit ); + +/* +============================================================== + +MISC + +============================================================== +*/ + +// centralizing the declarations for cl_cdkey +// (old code causing buffer overflows) +extern char cl_cdkey[34]; +void Com_AppendCDKey( const char *filename ); +void Com_ReadCDKey( const char *filename ); + +// returnbed by Sys_GetProcessorId +#define CPUID_GENERIC 0 // any unrecognized processor + +#define CPUID_AXP 0x10 + +#define CPUID_INTEL_UNSUPPORTED 0x20 // Intel 386/486 +#define CPUID_INTEL_PENTIUM 0x21 // Intel Pentium or PPro +#define CPUID_INTEL_MMX 0x22 // Intel Pentium/MMX or P2/MMX +#define CPUID_INTEL_KATMAI 0x23 // Intel Katmai + +#define CPUID_AMD_3DNOW 0x30 // AMD K6 3DNOW! + +// TTimo +// centralized and cleaned, that's the max string you can send to a Com_Printf / Com_DPrintf (above gets truncated) +#define MAXPRINTMSG 4096 + +char *CopyString( const char *in ); +void Info_Print( const char *s ); + +void Com_BeginRedirect( char *buffer, int buffersize, void ( *flush )( char * ) ); +void Com_EndRedirect( void ); +void QDECL Com_Printf( const char *fmt, ... ); +void QDECL Com_DPrintf( const char *fmt, ... ); +void QDECL Com_Error( int code, const char *fmt, ... ); +void Com_Quit_f( void ); +int Com_EventLoop( void ); +int Com_Milliseconds( void ); // will be journaled properly +unsigned Com_BlockChecksum( const void *buffer, int length ); +unsigned Com_BlockChecksumKey( void *buffer, int length, int key ); +int Com_HashKey( char *string, int maxlen ); +int Com_Filter( char *filter, char *name, int casesensitive ); +int Com_FilterPath( char *filter, char *name, int casesensitive ); +int Com_RealTime( qtime_t *qtime ); +qboolean Com_SafeMode( void ); + +void Com_StartupVariable( const char *match ); +void Com_SetRecommended(); +// checks for and removes command line "+set var arg" constructs +// if match is NULL, all set commands will be executed, otherwise +// only a set with the exact name. Only used during startup. + + +extern cvar_t *com_developer; +extern cvar_t *com_dedicated; +extern cvar_t *com_speeds; +extern cvar_t *com_timescale; +extern cvar_t *com_sv_running; +extern cvar_t *com_cl_running; +extern cvar_t *com_viewlog; // 0 = hidden, 1 = visible, 2 = minimized +extern cvar_t *com_version; +extern cvar_t *com_blood; +extern cvar_t *com_buildScript; // for building release pak files +extern cvar_t *com_journal; +extern cvar_t *com_cameraMode; + +// both client and server must agree to pause +extern cvar_t *cl_paused; +extern cvar_t *sv_paused; + +// com_speeds times +extern int time_game; +extern int time_frontend; +extern int time_backend; // renderer backend time + +extern int com_frameTime; +extern int com_frameMsec; + +extern qboolean com_errorEntered; + +extern fileHandle_t com_journalFile; +extern fileHandle_t com_journalDataFile; + +typedef enum { + TAG_FREE, + TAG_GENERAL, + TAG_BOTLIB, + TAG_RENDERER, + TAG_SMALL, + TAG_STATIC +} memtag_t; + +/* + +--- low memory ---- +server vm +server clipmap +---mark--- +renderer initialization (shaders, etc) +UI vm +cgame vm +renderer map +renderer models + +---free--- + +temp file loading +--- high memory --- + +*/ + +#if defined( _DEBUG ) && !defined( BSPC ) +#define ZONE_DEBUG +#endif + +#ifdef ZONE_DEBUG +#define Z_TagMalloc( size, tag ) Z_TagMallocDebug( size, tag, # size, __FILE__, __LINE__ ) +#define Z_Malloc( size ) Z_MallocDebug( size, # size, __FILE__, __LINE__ ) +#define S_Malloc( size ) S_MallocDebug( size, # size, __FILE__, __LINE__ ) +void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ); // NOT 0 filled memory +void *Z_MallocDebug( int size, char *label, char *file, int line ); // returns 0 filled memory +void *S_MallocDebug( int size, char *label, char *file, int line ); // returns 0 filled memory +#else +void *Z_TagMalloc( int size, int tag ); // NOT 0 filled memory +void *Z_Malloc( int size ); // returns 0 filled memory +void *S_Malloc( int size ); // NOT 0 filled memory only for small allocations +#endif +void Z_Free( void *ptr ); +void Z_FreeTags( int tag ); +void Z_LogHeap( void ); + +void Hunk_Clear( void ); +void Hunk_ClearToMark( void ); +void Hunk_SetMark( void ); +qboolean Hunk_CheckMark( void ); +//void *Hunk_Alloc( int size ); +// void *Hunk_Alloc( int size, ha_pref preference ); +void Hunk_ClearTempMemory( void ); +void *Hunk_AllocateTempMemory( int size ); +void Hunk_FreeTempMemory( void *buf ); +int Hunk_MemoryRemaining( void ); +void Hunk_SmallLog( void ); +void Hunk_Log( void ); + +void Com_TouchMemory( void ); + +// commandLine should not include the executable name (argv[0]) +void Com_Init( char *commandLine ); +void Com_Frame( void ); +void Com_Shutdown( void ); + + +/* +============================================================== + +CLIENT / SERVER SYSTEMS + +============================================================== +*/ + +// +// client interface +// +void CL_InitKeyCommands( void ); +// the keyboard binding interface must be setup before execing +// config files, but the rest of client startup will happen later + +void CL_Init( void ); +void CL_Disconnect( qboolean showMainMenu ); +void CL_Shutdown( void ); +void CL_Frame( int msec ); +qboolean CL_GameCommand( void ); +void CL_KeyEvent( int key, qboolean down, unsigned time ); + +void CL_CharEvent( int key ); +// char events are for field typing, not game control + +void CL_MouseEvent( int dx, int dy, int time ); + +void CL_JoystickEvent( int axis, int value, int time ); + +void CL_PacketEvent( netadr_t from, msg_t *msg ); + +void CL_ConsolePrint( char *text ); + +void CL_MapLoading( void ); +// do a screen update before starting to load a map +// when the server is going to load a new map, the entire hunk +// will be cleared, so the client must shutdown cgame, ui, and +// the renderer + +void CL_ForwardCommandToServer( const char *string ); +// adds the current command line as a clc_clientCommand 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 CL_CDDialog( void ); +// bring up the "need a cd to play" dialog + +void CL_ShutdownAll( void ); +// shutdown all the client stuff + +void CL_FlushMemory( void ); +// dump all memory on an error + +void CL_StartHunkUsers( void ); +// start all the client stuff using the hunk + + +#ifndef UPDATE_SERVER +void CL_CheckAutoUpdate( void ); +// Send a message to auto-update server +#endif + +void Key_WriteBindings( fileHandle_t f ); +// for writing the config files + +void S_ClearSoundBuffer( void ); +// call before filesystem access + +void SCR_DebugGraph( float value, int color ); // FIXME: move logging to common? + + +// +// server interface +// +void SV_Init( void ); +void SV_Shutdown( char *finalmsg ); +void SV_Frame( int msec ); +void SV_PacketEvent( netadr_t from, msg_t *msg ); +qboolean SV_GameCommand( void ); + + +// +// UI interface +// +qboolean UI_GameCommand( void ); +qboolean UI_usesUniqueCDKey(); + +/* +============================================================== + +NON-PORTABLE SYSTEM SERVICES + +============================================================== +*/ + +typedef enum { + AXIS_SIDE, + AXIS_FORWARD, + AXIS_UP, + AXIS_ROLL, + AXIS_YAW, + AXIS_PITCH, + MAX_JOYSTICK_AXIS +} joystickAxis_t; + +typedef enum { + // bk001129 - make sure SE_NONE is zero + SE_NONE = 0, // evTime is still valid + SE_KEY, // evValue is a key code, evValue2 is the down flag + SE_CHAR, // evValue is an ascii char + SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_CONSOLE, // evPtr is a char* + SE_PACKET // evPtr is a netadr_t followed by data bytes to evPtrLength +} sysEventType_t; + +typedef struct { + int evTime; + sysEventType_t evType; + int evValue, evValue2; + int evPtrLength; // bytes of data pointed to by evPtr, for journaling + void *evPtr; // this must be manually freed if not NULL +} sysEvent_t; + +sysEvent_t Sys_GetEvent( void ); + +void Sys_Init( void ); + +void *Sys_InitializeCriticalSection(); +void Sys_EnterCriticalSection( void *ptr ); +void Sys_LeaveCriticalSection( void *ptr ); + +// FIXME: wants win32 implementation +char* Sys_GetDLLName( const char *name ); +// fqpath param added 2/15/02 by T.Ray - Sys_LoadDll is only called in vm.c at this time +void * QDECL Sys_LoadDll( const char *name, char *fqpath, int( QDECL * *entryPoint ) ( int, ... ), + int ( QDECL * systemcalls )( int, ... ) ); +void Sys_UnloadDll( void *dllHandle ); + +void Sys_UnloadGame( void ); +void *Sys_GetGameAPI( void *parms ); + +void Sys_UnloadCGame( void ); +void *Sys_GetCGameAPI( void ); + +void Sys_UnloadUI( void ); +void *Sys_GetUIAPI( void ); + +//bot libraries +void Sys_UnloadBotLib( void ); +void *Sys_GetBotLibAPI( void *parms ); + +char *Sys_GetCurrentUser( void ); + +void QDECL Sys_Error( const char *error, ... ); +void Sys_Quit( void ); +char *Sys_GetClipboardData( void ); // note that this isn't journaled... + +void Sys_Print( const char *msg ); + + +// Sys_Milliseconds should only be used for profiling purposes, +// any game related timing information should come from event timestamps +int Sys_Milliseconds( void ); + +void Sys_SnapVector( float *v ); + +// the system console is shown when a dedicated server is running +void Sys_DisplaySystemConsole( qboolean show ); + +int Sys_GetProcessorId( void ); + +void Sys_BeginStreamedFile( fileHandle_t f, int readahead ); +void Sys_EndStreamedFile( fileHandle_t f ); +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ); +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ); + +void Sys_ShowConsole( int level, qboolean quitOnClose ); +void Sys_SetErrorText( const char *text ); + +void Sys_SendPacket( int length, const void *data, netadr_t to ); + +qboolean Sys_StringToAdr( const char *s, netadr_t *a ); +//Does NOT parse port numbers, only base addresses. + +qboolean Sys_IsLANAddress( netadr_t adr ); +void Sys_ShowIP( void ); + +qboolean Sys_CheckCD( void ); + +void Sys_Mkdir( const char *path ); +char *Sys_Cwd( void ); +char *Sys_DefaultCDPath( void ); +char *Sys_DefaultBasePath( void ); +char *Sys_DefaultInstallPath( void ); +char *Sys_DefaultHomePath( void ); + +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ); +void Sys_FreeFileList( char **list ); + +void Sys_BeginProfiling( void ); +void Sys_EndProfiling( void ); + +qboolean Sys_LowPhysicalMemory(); +unsigned int Sys_ProcessorCount(); + +// NOTE TTimo - on win32 the cwd is prepended .. non portable behaviour +void Sys_StartProcess( char *exeName, qboolean doexit ); // NERVE - SMF +void Sys_OpenURL( const char *url, qboolean doexit ); // NERVE - SMF +int Sys_GetHighQualityCPU(); + +#ifdef __linux__ +// TTimo only on linux .. maybe on Mac too? +// will OR with the existing mode (chmod ..+..) +void Sys_Chmod( char *file, int mode ); +#endif + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book. The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +#define NYT HMAX /* NYT = Not Yet Transmitted */ +#define INTERNAL_NODE ( HMAX + 1 ) + +typedef struct nodetype { + struct nodetype *left, *right, *parent; /* tree structure */ + struct nodetype *next, *prev; /* doubly-linked list */ + struct nodetype **head; /* highest ranked node in block */ + int weight; + int symbol; +} node_t; + +#define HMAX 256 /* Maximum symbol */ + +typedef struct { + int blocNode; + int blocPtrs; + + node_t* tree; + node_t* lhead; + node_t* ltail; + node_t* loc[HMAX + 1]; + node_t** freelist; + + node_t nodeList[768]; + node_t* nodePtrs[768]; +} huff_t; + +typedef struct { + huff_t compressor; + huff_t decompressor; +} huffman_t; + +void Huff_Compress( msg_t *buf, int offset ); +void Huff_Decompress( msg_t *buf, int offset ); +void Huff_Init( huffman_t *huff ); +void Huff_addRef( huff_t* huff, byte ch ); +int Huff_Receive( node_t *node, int *ch, byte *fin ); +void Huff_transmit( huff_t *huff, int ch, byte *fout ); +void Huff_offsetReceive( node_t *node, int *ch, byte *fin, int *offset ); +void Huff_offsetTransmit( huff_t *huff, int ch, byte *fout, int *offset ); +void Huff_putBit( int bit, byte *fout, int *offset ); +int Huff_getBit( byte *fout, int *offset ); + +extern huffman_t clientHuffTables; + +#define SV_ENCODE_START 4 +#define SV_DECODE_START 12 +#define CL_ENCODE_START 12 +#define CL_DECODE_START 4 + +// TTimo +// dll checksuming stuff, centralizing OS-dependent parts +// *_SHIFT is the shifting we applied to the reference string + +#if defined( _WIN32 ) + +// qagame_mp_x86.dll +#define SYS_DLLNAME_QAGAME_SHIFT 6 +#define SYS_DLLNAME_QAGAME "wgmgskesve~><4jrr" + +// cgame_mp_x86.dll +#define SYS_DLLNAME_CGAME_SHIFT 2 +#define SYS_DLLNAME_CGAME "eicogaoraz:80fnn" + +// ui_mp_x86.dll +#define SYS_DLLNAME_UI_SHIFT 5 +#define SYS_DLLNAME_UI "zndrud}=;3iqq" + +#elif defined( __linux__ ) + +// qagame.mp.i386.so +#define SYS_DLLNAME_QAGAME_SHIFT 6 +#define SYS_DLLNAME_QAGAME "wgmgsk4sv4o9><4yu" + +// cgame.mp.i386.so +#define SYS_DLLNAME_CGAME_SHIFT 2 +#define SYS_DLLNAME_CGAME "eicog0or0k5:80uq" + +// ui.mp.i386.so +#define SYS_DLLNAME_UI_SHIFT 5 +#define SYS_DLLNAME_UI "zn3ru3n8=;3xt" + +#elif defined( __MACOS__ ) + +#if 1 //DAJ +// qagame.mp.i386.so +#define SYS_DLLNAME_QAGAME_SHIFT 6 +#define SYS_DLLNAME_QAGAME "wgmgsk4sv4o9><4yu" + +// cgame.mp.i386.so +#define SYS_DLLNAME_CGAME_SHIFT 2 +#define SYS_DLLNAME_CGAME "eicog0or0k5:80uq" + +// ui.mp.i386.so +#define SYS_DLLNAME_UI_SHIFT 5 +#define SYS_DLLNAME_UI "zn3ru3n8=;3xt" + +#else //DAJ +// qagame.mp.i386.so +#define SYS_DLLNAME_QAGAME_SHIFT 0 +#define SYS_DLLNAME_QAGAME "qgame_MP.dll" + +// cgame.mp.i386.so +#define SYS_DLLNAME_CGAME_SHIFT 0 +#define SYS_DLLNAME_CGAME "cgame_MP.dll" + +// ui.mp.i386.so +#define SYS_DLLNAME_UI_SHIFT 0 +#define SYS_DLLNAME_UI "ui_MP.dll" +#endif //DAJ + +#else +#error unknown OS +#endif + +#endif // _QCOMMON_H_ + diff --git a/src/qcommon/qfiles.h b/src/qcommon/qfiles.h new file mode 100644 index 0000000..11297a4 --- /dev/null +++ b/src/qcommon/qfiles.h @@ -0,0 +1,731 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 // JPW NERVE was 4000, 1000 in q3ta +#define SHADER_MAX_INDEXES ( 6 * SHADER_MAX_VERTEXES ) + + +// the maximum size of game reletive pathnames +#define MAX_QPATH 64 + +/* +======================================================================== + +QVM files + +======================================================================== +*/ + +#define VM_MAGIC 0x12721444 +typedef struct { + int vmMagic; + + int instructionCount; + + int codeOffset; + int codeLength; + + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength +} vmHeader_t; + + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT ( ( '3' << 24 ) + ( 'P' << 16 ) + ( 'D' << 8 ) + 'I' ) +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 1 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE ( 1.0 / 64 ) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_QPATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + +// Ridah, mesh compression +/* +============================================================================== + +MDC file format + +============================================================================== +*/ + +#define MDC_IDENT ( ( 'C' << 24 ) + ( 'P' << 16 ) + ( 'D' << 8 ) + 'I' ) +#define MDC_VERSION 2 + +// version history: +// 1 - original +// 2 - changed tag structure so it only lists the names once + +typedef struct { + unsigned int ofsVec; // offset direction from the last base frame +// unsigned short ofsVec; +} mdcXyzCompressed_t; + +typedef struct { + char name[MAX_QPATH]; // tag name +} mdcTagName_t; + +#define MDC_TAG_ANGLE_SCALE ( 360.0 / 32700.0 ) + +typedef struct { + short xyz[3]; + short angles[3]; +} mdcTag_t; + +/* +** mdcSurface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numBaseFrames +** XyzCompressed sizeof( mdcXyzCompressed ) * numVerts * numCompFrames +** frameBaseFrames sizeof( short ) * numFrames +** frameCompFrames sizeof( short ) * numFrames (-1 if frame is a baseFrame) +*/ +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numCompFrames; // all surfaces in a model should have the same + int numBaseFrames; // ditto + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numBaseFrames + int ofsXyzCompressed; // numVerts * numCompFrames + + int ofsFrameBaseFrames; // numFrames + int ofsFrameCompFrames; // numFrames + + int ofsEnd; // next surface follows +} mdcSurface_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame, stores the bounds and localOrigin + int ofsTagNames; // numTags + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} mdcHeader_t; +// done. + +/* +============================================================================== + +MD4 file format + +============================================================================== +*/ + +#define MD4_IDENT ( ( '4' << 24 ) + ( 'P' << 16 ) + ( 'D' << 8 ) + 'I' ) +#define MD4_VERSION 1 +#define MD4_MAX_BONES 128 + +typedef struct { + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list + vec3_t offset; +} md4Weight_t; + +typedef struct { + vec3_t normal; + vec2_t texCoords; + int numWeights; + md4Weight_t weights[1]; // variable sized +} md4Vertex_t; + +typedef struct { + int indexes[3]; +} md4Triangle_t; + +typedef struct { + int ident; + + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use + + int ofsHeader; // this will be a negative number + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows +} md4Surface_t; + +typedef struct { + float matrix[3][4]; +} md4Bone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + char name[16]; + md4Bone_t bones[1]; // [numBones] +} md4Frame_t; + +typedef struct { + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows +} md4LOD_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsFrames; // md4Frame_t[numFrames] + + // each level of detail has completely separate sets of surfaces + int numLODs; + int ofsLODs; + + int ofsEnd; // end of file +} md4Header_t; + + +/* +============================================================================== + +MDS file format (Wolfenstein Skeletal Format) + +============================================================================== +*/ + +#define MDS_IDENT ( ( 'W' << 24 ) + ( 'S' << 16 ) + ( 'D' << 8 ) + 'M' ) +#define MDS_VERSION 4 +#define MDS_MAX_VERTS 6000 +#define MDS_MAX_TRIANGLES 8192 +#define MDS_MAX_BONES 128 +#define MDS_MAX_SURFACES 32 +#define MDS_MAX_TAGS 128 + +#define MDS_TRANSLATION_SCALE ( 1.0 / 64 ) + +typedef struct { + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list + vec3_t offset; +} mdsWeight_t; + +typedef struct { + vec3_t normal; + vec2_t texCoords; + int numWeights; + int fixedParent; // stay equi-distant from this parent + float fixedDist; + mdsWeight_t weights[1]; // variable sized +} mdsVertex_t; + +typedef struct { + int indexes[3]; +} mdsTriangle_t; + +typedef struct { + int ident; + + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use + + int minLod; + + int ofsHeader; // this will be a negative number + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + int ofsCollapseMap; // numVerts * int + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows +} mdsSurface_t; + +typedef struct { + //float angles[3]; + //float ofsAngles[2]; + short angles[4]; // to be converted to axis at run-time (this is also better for lerping) + short ofsAngles[2]; // PITCH/YAW, head in this direction from parent to go to the offset position +} mdsBoneFrameCompressed_t; + +// NOTE: this only used at run-time +typedef struct { + float matrix[3][3]; // 3x3 rotation + vec3_t translation; // translation vector +} mdsBoneFrame_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + vec3_t parentOffset; // one bone is an ascendant of all other bones, it starts the hierachy at this position + mdsBoneFrameCompressed_t bones[1]; // [numBones] +} mdsFrame_t; + +typedef struct { + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows +} mdsLOD_t; + +typedef struct { + char name[MAX_QPATH]; // name of tag + float torsoWeight; + int boneIndex; // our index in the bones +} mdsTag_t; + +#define BONEFLAG_TAG 1 // this bone is actually a tag + +typedef struct { + char name[MAX_QPATH]; // name of bone + int parent; // not sure if this is required, no harm throwing it in + float torsoWeight; // scale torso rotation about torsoParent by this + float parentDist; + int flags; +} mdsBoneInfo_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + float lodScale; + float lodBias; + + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsFrames; // md4Frame_t[numFrames] + int ofsBones; // mdsBoneInfo_t[numBones] + int torsoParent; // index of bone that is the parent of the torso + + int numSurfaces; + int ofsSurfaces; + + // tag data + int numTags; + int ofsTags; // mdsTag_t[numTags] + + int ofsEnd; // end of file +} mdsHeader_t; + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + + +#define BSP_IDENT ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) +// little-endian "IBSP" + +#define BSP_VERSION 47 + + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +//#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_MODELS 0x800 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x20000 +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 0x800000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +#define MAX_WORLD_COORD ( 128 * 1024 ) +#define MIN_WORLD_COORD ( -128 * 1024 ) +#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +//============================================================================= + + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define HEADER_LUMPS 17 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; +} drawVert_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; + +//----(SA) added so I didn't change the dsurface_t struct (and thereby the bsp format) for something that doesn't need to be stored in the bsp +typedef struct { + char *lighttarg; +} drsurfaceInternal_t; +//----(SA) end + +#endif diff --git a/src/qcommon/unzip.c b/src/qcommon/unzip.c new file mode 100644 index 0000000..82609b6 --- /dev/null +++ b/src/qcommon/unzip.c @@ -0,0 +1,4329 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/***************************************************************************** + * name: unzip.c + * + * desc: IO on .zip files using portions of zlib + * + * + *****************************************************************************/ + +#include "../client/client.h" +#include "unzip.h" + +/* unzip.h -- IO for uncompress .zip files using zlib + Version 0.15 beta, Mar 19th, 1998, + + Copyright (C) 1998 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Encryption and multi volume ZipFile (span) are not supported. + Old compressions used by old PKZip 1.x are not supported + + THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE + CAN CHANGE IN FUTURE VERSION !! + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + 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. + + +*/ +/* for more info about .ZIP format, see + ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip */ + +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.1.3, July 9th, 1998 + + Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + + 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. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + + +#ifndef _ZCONF_H +#define _ZCONF_H + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +#define OF(args) args +#endif + +typedef unsigned char Byte; /* 8 bits */ +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ +typedef Byte *voidp; + +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#endif /* _ZCONF_H */ + +#define ZLIB_VERSION "1.1.3" + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +/* Allowed flush values; see deflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_ASCII 1 +#define Z_UNKNOWN 2 +/* Possible values of the data_type field */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +// static const char * zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +int deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +// static int deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + the compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + 0.1% larger than avail_in plus 12 bytes. If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so (that is, total_in bytes). + + deflate() may update data_type if it can make a good guess about + the input data type (Z_ASCII or Z_BINARY). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). +*/ + + +// static int deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +int inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +static int inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may some + introduce some output latency (reading input without producing any output) + except when forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much + output as possible to the output buffer. The flushing behavior of inflate is + not specified for values of the flush parameter other than Z_SYNC_FLUSH + and Z_FINISH, but the current implementation actually flushes as much output + as possible anyway. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster routine + may be used for the single inflate() call. + + If a preset dictionary is needed at this point (see inflateSetDictionary + below), inflate sets strm-adler to the adler32 checksum of the + dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise + it sets strm->adler to the adler32 checksum of all output produced + so (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or + an error code as described below. At the end of the stream, inflate() + checks that its computed adler32 checksum is equal to that saved by the + compressor and returns Z_STREAM_END only if the checksum is correct. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect + adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent + (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if no progress is possible or if there was not + enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR + case, the application may then call inflateSync to look for a good + compression block. +*/ + + +static int inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +int deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), or Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match). Filtered data consists mostly of small values with a + somewhat random distribution. In this case, the compression algorithm is + tuned to compress them better. The effect of Z_FILTERED is to force more + Huffman coding and less string matching; it is somewhat intermediate + between Z_DEFAULT and Z_HUFFMAN_ONLY. The strategy parameter only affects + the compression ratio but not the correctness of the compressed output even + if it is not set appropriately. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +/* +static int deflateSetDictionary OF((z_streamp strm, + const Byte *dictionary, + uInt dictLength)); +*/ +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. + + Upon return of this function, strm->adler is set to the Adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The Adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +/* +static int deflateCopy OF((z_streamp dest, + z_streamp source)); +*/ +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +// static int deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +/* +static int deflateParams OF((z_streamp strm, + int level, + int strategy)); +*/ +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +/* +int inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. If a compressed stream with a larger window size is given as + input, inflate() will return with the error code Z_DATA_ERROR instead of + trying to allocate a larger window. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative + memLevel). msg is set to null if there is no error message. inflateInit2 + does not perform any decompression apart from reading the zlib header if + present: this will be done by inflate(). (So next_in and avail_in may be + modified, but next_out and avail_out are unchanged.) +*/ + +/* +static int inflateSetDictionary OF((z_streamp strm, + const Byte *dictionary, + uInt dictLength)); +*/ +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate + if this call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the Adler32 value returned by this call of + inflate. The compressor and decompressor must use exactly the same + dictionary (see deflateSetDictionary). + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect Adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +// static int inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +static int inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +/* +static int compress OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen)); +*/ +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least 0.1% larger than + sourceLen plus 12 bytes. Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +/* +static int compress2 OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen, + int level)); +*/ +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +/* +static int uncompress OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen)); +*/ +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ + + +typedef voidp gzFile; + +gzFile gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h". (See the description + of deflateInit2 for more information about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +gzFile gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +int gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +int gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +int gzwrite OF((gzFile file, + const voidp buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +int QDECL gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). +*/ + +int gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +char * gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +int gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +int gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +int gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +long gzseek OF((gzFile file, + long offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +int gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +long gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +int gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +int gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +// static const char * gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +static uLong adler32 OF((uLong adler, const Byte *buf, uInt len)); + +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +/* +static int deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +static int inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +static int deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +*/ +static int inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + + +// static const char * zError OF((int err)); +// static int inflateSyncPoint OF((z_streamp z)); +// static const uLong * get_crc_table OF((void)); + +typedef unsigned char uch; +typedef unsigned short ush; +typedef unsigned long ulg; + +// static const char *z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + + /* Common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#ifdef HAVE_STRERROR + extern char *strerror OF((int)); +# define zstrerror(errnum) strerror(errnum) +#else +# define zstrerror(errnum) "" +#endif + +#define zmemcpy Com_Memcpy +#define zmemcmp memcmp +#define zmemzero(dest, len) Com_Memset(dest, 0, len) + +/* Diagnostic functions */ +#ifdef _ZIP_DEBUG_ + int z_verbose = 0; +# define Assert(cond,msg) assert(cond); + //{if(!(cond)) Sys_Error(msg);} +# define Trace(x) {if (z_verbose>=0) Sys_Error x ;} +# define Tracev(x) {if (z_verbose>0) Sys_Error x ;} +# define Tracevv(x) {if (z_verbose>1) Sys_Error x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) Sys_Error x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) Sys_Error x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + + +typedef uLong (*check_func) OF((uLong check, const Byte *buf, uInt len)); +static voidp zcalloc OF((voidp opaque, unsigned items, unsigned size)); +static void zcfree OF((voidp opaque, voidp ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidp)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + + +#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \ + !defined(CASESENSITIVITYDEFAULT_NO) +#define CASESENSITIVITYDEFAULT_NO +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (65536) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (Z_Malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) Z_Free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + +/* +static int unzlocal_getByte(FILE *fin,int *pi) +{ + unsigned char c; + int err = fread(&c, 1, 1, fin); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ferror(fin)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} +*/ + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +static int unzlocal_getShort (FILE* fin, uLong *pX) +{ + short v; + + fread( &v, sizeof(v), 1, fin ); + + *pX = LittleShort( v); + return UNZ_OK; + +/* + uLong x ; + int i; + int err; + + err = unzlocal_getByte(fin,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +*/ +} + +static int unzlocal_getLong (FILE *fin, uLong *pX) +{ + int v; + + fread( &v, sizeof(v), 1, fin ); + + *pX = LittleLong( v); + return UNZ_OK; + +/* + uLong x ; + int i; + int err; + + err = unzlocal_getByte(fin,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +*/ +} + + +/* My own strcmpi / strcasecmp */ +static int strcmpcasenosensitive_internal (const char* fileName1,const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int unzStringFileNameCompare (const char* fileName1,const char* fileName2,int iCaseSensitivity) +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#define BUFREADCOMMENT (0x400) + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +extern uLong unzlocal_SearchCentralDir(FILE *fin) +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (fseek(fin,0,SEEK_END) != 0) + return 0; + + + uSizeFile = ftell( fin ); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (fseek(fin,uReadPos,SEEK_SET)!=0) + break; + + if (fread(buf,(uInt)uReadSize,1,fin)!=1) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +extern unzFile unzReOpen (const char* path, unzFile file) +{ + unz_s *s; + FILE * fin; + + fin=fopen(path,"rb"); + if (fin==NULL) + return NULL; + + s=(unz_s*)ALLOC(sizeof(unz_s)); + Com_Memcpy(s, (unz_s*)file, sizeof(unz_s)); + + s->file = fin; + s->pfile_in_zip_read = NULL; + return (unzFile)s; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer + "zlib/zlib109.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile unzOpen (const char* path) +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + FILE * fin ; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + fin=fopen(path,"rb"); + if (fin==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(fin); + if (central_pos==0) + err=UNZ_ERRNO; + + if (fseek(fin,central_pos,SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(fin,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + fclose(s->file); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int unzGetGlobalInfo (unzFile file,unz_global_info *pglobal_info) +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +static int unzlocal_GetCurrentFileInfoInternal (unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (fseek(s->file,s->pos_in_central_dir+s->byte_before_the_zipfile,SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) { + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + } + if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (fread(szFileName,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extrafile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) { + if (fread(extraField,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + } + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentfile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) { + if (fread(szComment,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + } + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int unzGetCurrentFileInfo ( unzFile file, unz_file_info *pfile_info, + char *szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char *szComment, uLong commentBufferSize) +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int unzGoToNextFile (unzFile file) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ) +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + *pos = s->pos_in_central_dir; + return UNZ_OK; +} + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return UNZ_OK; +} + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz_s* s; + int err; + + + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + return err; +} + + +/* + Read the static header of the current zipfile + Check the coherency of the static header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in static header + (filename and size of extra field data) +*/ +static int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar, + uLong *poffset_local_extrafield, + uInt *psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (fseek(s->file,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) { + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + } + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int unzOpenCurrentFile (unzFile file) +{ + int err=UNZ_OK; + int Store; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the static extra field */ + uInt size_local_extrafield; /* size of the static extra field */ + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) + ALLOC(sizeof(file_in_zip_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + Store = s->cur_file_info.compression_method==0; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->file=s->file; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if (!Store) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidp)0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + + s->pfile_in_zip_read = pfile_in_zip_read_info; + return UNZ_OK; +} + + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Byte*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if (len>pfile_in_zip_read_info->rest_read_uncompressed) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (s->cur_file_info.compressed_size == pfile_in_zip_read_info->rest_read_compressed) + if (fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + if (fread(pfile_in_zip_read_info->read_buffer,uReadThis,1, + pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Byte*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if (pfile_in_zip_read_info->compression_method==0) + { + uInt uDoCopy,i ; + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + +// pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, +// pfile_in_zip_read_info->stream.next_out, +// uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Byte *bufBefore; + uLong uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + +// pfile_in_zip_read_info->crc32 = +// crc32(pfile_in_zip_read_info->crc32,bufBefore, +// (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern long unztell (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (long)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int unzeof (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the static-header version of the extra field (sometimes, there is + more info in the static-header version than in the central-header) + + if buf==NULL, it return the size of the static extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int unzGetLocalExtrafield (unzFile file,void *buf,unsigned len) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (fread(buf,(uInt)size_to_read,1,pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + +/* + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } +*/ + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf) +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (fseek(s->file,s->central_pos+22,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (fread(szComment,(uInt)uReadThis,1,s->file)!=1) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_blocks_state; +typedef struct inflate_blocks_state inflate_blocks_statef; + +static inflate_blocks_statef * inflate_blocks_new OF(( + z_streamp z, + check_func c, /* check function */ + uInt w)); /* window size */ + +static int inflate_blocks OF(( + inflate_blocks_statef *, + z_streamp , + int)); /* initial return code */ + +static void inflate_blocks_reset OF(( + inflate_blocks_statef *, + z_streamp , + uLong *)); /* check value on output */ + +static int inflate_blocks_free OF(( + inflate_blocks_statef *, + z_streamp)); + +#if 0 +static void inflate_set_dictionary OF(( + inflate_blocks_statef *s, + const Byte *d, /* dictionary */ + uInt n)); /* dictionary length */ + +static int inflate_blocks_sync_point OF(( + inflate_blocks_statef *s)); +#endif + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* Table for deflate from PKZIP's appnote.txt. */ +static const uInt border[] = { /* Order of the bit length code lengths */ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Huffman code lookup table entry--this entry is four bytes for machines + that have 16-bit pointers (e.g. PC's in the small or medium model). */ + +typedef struct inflate_huft_s inflate_huft; + +struct inflate_huft_s { + union { + struct { + Byte Exop; /* number of extra bits or operation */ + Byte Bits; /* number of bits in this code or subcode */ + } what; + uInt pad; /* pad structure to a power of 2 (4 bytes for */ + } word; /* 16-bit, 8 bytes for 32-bit int's) */ + uInt base; /* literal, length base, distance base, + or table offset */ +}; + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1004 huft structures (850 for length/literals + and 154 for distances, the latter actually the result of an + exhaustive search). The actual maximum is not known, but the + value below is more than safe. */ +#define MANY 1440 + +static int inflate_trees_bits OF(( + uInt *, /* 19 code lengths */ + uInt *, /* bits tree desired/actual depth */ + inflate_huft * *, /* bits tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +static int inflate_trees_dynamic OF(( + uInt, /* number of literal/length codes */ + uInt, /* number of distance codes */ + uInt *, /* that many (total) code lengths */ + uInt *, /* literal desired/actual bit depth */ + uInt *, /* distance desired/actual bit depth */ + inflate_huft * *, /* literal/length tree result */ + inflate_huft * *, /* distance tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +static int inflate_trees_fixed OF(( + uInt *, /* literal desired/actual bit depth */ + uInt *, /* distance desired/actual bit depth */ + inflate_huft * *, /* literal/length tree result */ + inflate_huft * *, /* distance tree result */ + z_streamp)); /* for memory allocation */ + + +/* infcodes.h -- header to use infcodes.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_codes_state; +typedef struct inflate_codes_state inflate_codes_statef; + +static inflate_codes_statef *inflate_codes_new OF(( + uInt, uInt, + inflate_huft *, inflate_huft *, + z_streamp )); + +static int inflate_codes OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +static void inflate_codes_free OF(( + inflate_codes_statef *, + z_streamp )); + +/* infutil.h -- types and macros common to blocks and codes + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFUTIL_H +#define _INFUTIL_H + +typedef enum { + TYPE, /* get type bits (3, including end bit) */ + LENS, /* get lengths for stored */ + STORED, /* processing stored block */ + TABLE, /* get table lengths */ + BTREE, /* get bit lengths tree for a dynamic block */ + DTREE, /* get length, distance trees for a dynamic block */ + CODES, /* processing fixed or dynamic block */ + DRY, /* output remaining window bytes */ + DONE, /* finished last block, done */ + BAD} /* got a data error--stuck here */ +inflate_block_mode; + +/* inflate blocks semi-private state */ +struct inflate_blocks_state { + + /* mode */ + inflate_block_mode mode; /* current inflate_block mode */ + + /* mode dependent information */ + union { + uInt left; /* if STORED, bytes left to copy */ + struct { + uInt table; /* table lengths (14 bits) */ + uInt index; /* index into blens (or border) */ + uInt *blens; /* bit lengths of codes */ + uInt bb; /* bit length tree depth */ + inflate_huft *tb; /* bit length decoding tree */ + } trees; /* if DTREE, decoding info for trees */ + struct { + inflate_codes_statef + *codes; + } decode; /* if CODES, current state */ + } sub; /* submode */ + uInt last; /* true if this block is the last block */ + + /* mode independent information */ + uInt bitk; /* bits in bit buffer */ + uLong bitb; /* bit buffer */ + inflate_huft *hufts; /* single malloc for tree space */ + Byte *window; /* sliding window */ + Byte *end; /* one byte after sliding window */ + Byte *read; /* window read pointer */ + Byte *write; /* window write pointer */ + check_func checkfn; /* check function */ + uLong check; /* check on output */ + +}; + + +/* defines for inflate input/output */ +/* update pointers and return */ +#define UPDBITS {s->bitb=b;s->bitk=k;} +#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} +#define UPDOUT {s->write=q;} +#define UPDATE {UPDBITS UPDIN UPDOUT} +#define LEAVE {UPDATE return inflate_flush(s,z,r);} +/* get bytes and bits */ +#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} +#define NEEDBYTE {if(n)r=Z_OK;else LEAVE} +#define NEXTBYTE (n--,*p++) +#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<>=(j);k-=(j);} +/* output bytes */ +#define WAVAIL (uInt)(qread?s->read-q-1:s->end-q) +#define LOADOUT {q=s->write;m=(uInt)WAVAIL;} +#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}} +#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT} +#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;} +#define OUTBYTE(a) {*q++=(Byte)(a);m--;} +/* load static pointers */ +#define LOAD {LOADIN LOADOUT} + +/* masks for lower bits (size given to avoid silly warnings with Visual C++) */ +static uInt inflate_mask[17]; + +/* copy as much as possible from the sliding window to the output area */ +static int inflate_flush OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +#endif + + +/* + Notes beyond the 1.93a appnote.txt: + + 1. Distance pointers never point before the beginning of the output + stream. + 2. Distance pointers can point back across blocks, up to 32k away. + 3. There is an implied maximum of 7 bits for the bit length table and + 15 bits for the actual data. + 4. If only one code exists, then it is encoded using one bit. (Zero + would be more efficient, but perhaps a little confusing.) If two + codes exist, they are coded using one bit each (0 and 1). + 5. There is no way of sending zero distance codes--a dummy must be + sent if there are none. (History: a pre 2.0 version of PKZIP would + store blocks with no distance codes, but this was discovered to be + too harsh a criterion.) Valid only for 1.93a. 2.04c does allow + zero distance codes, which is sent as one code of zero bits in + length. + 6. There are up to 286 literal/length codes. Code 256 represents the + end-of-block. Note however that the static length tree defines + 288 codes just to fill out the Huffman codes. Codes 286 and 287 + cannot be used though, since there is no length base or extra bits + defined for them. Similarily, there are up to 30 distance codes. + However, static trees define 32 codes (all 5 bits) to fill out the + Huffman codes, but the last two had better not show up in the data. + 7. Unzip can check dynamic Huffman blocks for complete code sets. + The exception is that a single code would not be complete (see #4). + 8. The five bits following the block type is really the number of + literal codes sent minus 257. + 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits + (1+6+6). Therefore, to output three times the length, you output + three codes (1+1+1), whereas to output four times the same length, + you only need two codes (1+3). Hmm. + 10. In the tree reconstruction algorithm, Code = Code + Increment + only if BitLength(i) is not zero. (Pretty obvious.) + 11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19) + 12. Note: length code 284 can represent 227-258, but length code 285 + really is 258. The last length deserves its own, short code + since it gets used a lot in very redundant files. The length + 258 is special since 258 - 3 (the min match length) is 255. + 13. The literal/length and distance code bit lengths are read as a + single stream of lengths. It is possible (and advantageous) for + a repeat code (16, 17, or 18) to go across the boundary between + the two sets of lengths. + */ + + +void inflate_blocks_reset(inflate_blocks_statef *s, z_streamp z, uLong *c) +{ + if (c != Z_NULL) + *c = s->check; + if (s->mode == BTREE || s->mode == DTREE) + ZFREE(z, s->sub.trees.blens); + if (s->mode == CODES) + inflate_codes_free(s->sub.decode.codes, z); + s->mode = TYPE; + s->bitk = 0; + s->bitb = 0; + s->read = s->write = s->window; + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(0L, (const Byte *)Z_NULL, 0); + Tracev(("inflate: blocks reset\n")); +} + + +inflate_blocks_statef *inflate_blocks_new(z_streamp z, check_func c, uInt w) +{ + inflate_blocks_statef *s; + + if ((s = (inflate_blocks_statef *)ZALLOC + (z,1,sizeof(struct inflate_blocks_state))) == Z_NULL) + return s; + if ((s->hufts = + (inflate_huft *)ZALLOC(z, sizeof(inflate_huft), MANY)) == Z_NULL) + { + ZFREE(z, s); + return Z_NULL; + } + if ((s->window = (Byte *)ZALLOC(z, 1, w)) == Z_NULL) + { + ZFREE(z, s->hufts); + ZFREE(z, s); + return Z_NULL; + } + s->end = s->window + w; + s->checkfn = c; + s->mode = TYPE; + Tracev(("inflate: blocks allocated\n")); + inflate_blocks_reset(s, z, Z_NULL); + return s; +} + + +int inflate_blocks(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt t; /* temporary storage */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + + /* copy input/output information to locals (UPDATE macro restores) */ + LOAD + + /* process input based on current state */ + while (1) switch (s->mode) + { + case TYPE: + NEEDBITS(3) + t = (uInt)b & 7; + s->last = t & 1; + switch (t >> 1) + { + case 0: /* stored */ + Tracev(("inflate: stored block%s\n", + s->last ? " (last)" : "")); + DUMPBITS(3) + t = k & 7; /* go to byte boundary */ + DUMPBITS(t) + s->mode = LENS; /* get length of stored block */ + break; + case 1: /* fixed */ + Tracev(("inflate: fixed codes block%s\n", + s->last ? " (last)" : "")); + { + uInt bl, bd; + inflate_huft *tl, *td; + + inflate_trees_fixed(&bl, &bd, &tl, &td, z); + s->sub.decode.codes = inflate_codes_new(bl, bd, tl, td, z); + if (s->sub.decode.codes == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + } + DUMPBITS(3) + s->mode = CODES; + break; + case 2: /* dynamic */ + Tracev(("inflate: dynamic codes block%s\n", + s->last ? " (last)" : "")); + DUMPBITS(3) + s->mode = TABLE; + break; + case 3: /* illegal */ + DUMPBITS(3) + s->mode = BAD; + z->msg = (char*)"invalid block type"; + r = Z_DATA_ERROR; + LEAVE + } + break; + case LENS: + NEEDBITS(32) + if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) + { + s->mode = BAD; + z->msg = (char*)"invalid stored block lengths"; + r = Z_DATA_ERROR; + LEAVE + } + s->sub.left = (uInt)b & 0xffff; + b = k = 0; /* dump bits */ + Tracev(("inflate: stored length %u\n", s->sub.left)); + s->mode = s->sub.left ? STORED : (s->last ? DRY : TYPE); + break; + case STORED: + if (n == 0) + LEAVE + NEEDOUT + t = s->sub.left; + if (t > n) t = n; + if (t > m) t = m; + zmemcpy(q, p, t); + p += t; n -= t; + q += t; m -= t; + if ((s->sub.left -= t) != 0) + break; + Tracev(("inflate: stored end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + s->mode = s->last ? DRY : TYPE; + break; + case TABLE: + NEEDBITS(14) + s->sub.trees.table = t = (uInt)b & 0x3fff; +#ifndef PKZIP_BUG_WORKAROUND + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) + { + s->mode = BAD; + z->msg = (char*)"too many length or distance symbols"; + r = Z_DATA_ERROR; + LEAVE + } +#endif + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if ((s->sub.trees.blens = (uInt*)ZALLOC(z, t, sizeof(uInt))) == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + DUMPBITS(14) + s->sub.trees.index = 0; + Tracev(("inflate: table sizes ok\n")); + s->mode = BTREE; + case BTREE: + while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10)) + { + NEEDBITS(3) + s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt)b & 7; + DUMPBITS(3) + } + while (s->sub.trees.index < 19) + s->sub.trees.blens[border[s->sub.trees.index++]] = 0; + s->sub.trees.bb = 7; + t = inflate_trees_bits(s->sub.trees.blens, &s->sub.trees.bb, + &s->sub.trees.tb, s->hufts, z); + if (t != Z_OK) + { + ZFREE(z, s->sub.trees.blens); + r = t; + if (r == Z_DATA_ERROR) + s->mode = BAD; + LEAVE + } + s->sub.trees.index = 0; + Tracev(("inflate: bits tree ok\n")); + s->mode = DTREE; + case DTREE: + while (t = s->sub.trees.table, + s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) + { + inflate_huft *h; + uInt i, j, c; + + t = s->sub.trees.bb; + NEEDBITS(t) + h = s->sub.trees.tb + ((uInt)b & inflate_mask[t]); + t = h->bits; + c = h->base; + if (c < 16) + { + DUMPBITS(t) + s->sub.trees.blens[s->sub.trees.index++] = c; + } + else /* c == 16..18 */ + { + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + NEEDBITS(t + i) + DUMPBITS(t) + j += (uInt)b & inflate_mask[i]; + DUMPBITS(i) + i = s->sub.trees.index; + t = s->sub.trees.table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)) + { + ZFREE(z, s->sub.trees.blens); + s->mode = BAD; + z->msg = (char*)"invalid bit length repeat"; + r = Z_DATA_ERROR; + LEAVE + } + c = c == 16 ? s->sub.trees.blens[i - 1] : 0; + do { + s->sub.trees.blens[i++] = c; + } while (--j); + s->sub.trees.index = i; + } + } + s->sub.trees.tb = Z_NULL; + { + uInt bl, bd; + inflate_huft *tl, *td; + inflate_codes_statef *c; + + bl = 9; /* must be <= 9 for lookahead assumptions */ + bd = 6; /* must be <= 9 for lookahead assumptions */ + t = s->sub.trees.table; + t = inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), + s->sub.trees.blens, &bl, &bd, &tl, &td, + s->hufts, z); + ZFREE(z, s->sub.trees.blens); + if (t != Z_OK) + { + if (t == (uInt)Z_DATA_ERROR) + s->mode = BAD; + r = t; + LEAVE + } + Tracev(("inflate: trees ok\n")); + if ((c = inflate_codes_new(bl, bd, tl, td, z)) == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + s->sub.decode.codes = c; + } + s->mode = CODES; + case CODES: + UPDATE + if ((r = inflate_codes(s, z, r)) != Z_STREAM_END) + return inflate_flush(s, z, r); + r = Z_OK; + inflate_codes_free(s->sub.decode.codes, z); + LOAD + Tracev(("inflate: codes end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + if (!s->last) + { + s->mode = TYPE; + break; + } + s->mode = DRY; + case DRY: + FLUSH + if (s->read != s->write) + LEAVE + s->mode = DONE; + case DONE: + r = Z_STREAM_END; + LEAVE + case BAD: + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +} + + +int inflate_blocks_free(inflate_blocks_statef *s, z_streamp z) +{ + inflate_blocks_reset(s, z, Z_NULL); + ZFREE(z, s->window); + ZFREE(z, s->hufts); + ZFREE(z, s); + Tracev(("inflate: blocks freed\n")); + return Z_OK; +} + +#if 0 +void inflate_set_dictionary(inflate_blocks_statef *s, const Byte *d, uInt n) +{ + zmemcpy(s->window, d, n); + s->read = s->write = s->window + n; +} + + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. + * IN assertion: s != Z_NULL + */ +int inflate_blocks_sync_point(inflate_blocks_statef *s) +{ + return s->mode == LENS; +} +#endif + + +/* And'ing with mask[n] masks the lower n bits */ +static uInt inflate_mask[17] = { + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + + +/* copy as much as possible from the sliding window to the output area */ +int inflate_flush(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt n; + Byte *p; + Byte *q; + + /* static copies of source and destination pointers */ + p = z->next_out; + q = s->read; + + /* compute number of bytes to copy as as end of window */ + n = (uInt)((q <= s->write ? s->write : s->end) - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + /* update counters */ + z->avail_out -= n; + z->total_out += n; + + /* update check information */ + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + /* copy as as end of window */ + zmemcpy(p, q, n); + p += n; + q += n; + + /* see if more to copy at beginning of window */ + if (q == s->end) + { + /* wrap pointers */ + q = s->window; + if (s->write == s->end) + s->write = s->window; + + /* compute bytes to copy */ + n = (uInt)(s->write - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + /* update counters */ + z->avail_out -= n; + z->total_out += n; + + /* update check information */ + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + /* copy */ + zmemcpy(p, q, n); + p += n; + q += n; + } + + /* update pointers */ + z->next_out = p; + s->read = q; + + /* done */ + return r; +} + +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +static const char inflate_copyright[] = + " inflate 1.1.3 Copyright 1995-1998 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + + +static int huft_build OF(( + uInt *, /* code lengths in bits */ + uInt, /* number of codes */ + uInt, /* number of "simple" codes */ + const uInt *, /* list of base values for non-simple codes */ + const uInt *, /* list of extra bits for non-simple codes */ + inflate_huft **, /* result: starting table */ + uInt *, /* maximum lookup bits (returns actual) */ + inflate_huft *, /* space for trees */ + uInt *, /* hufts used in space */ + uInt * )); /* space for values */ + +/* Tables for deflate from PKZIP's appnote.txt. */ +static const uInt cplens[31] = { /* Copy lengths for literal codes 257..285 */ + 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, 0, 0}; + /* see note #13 above about 258 */ +static const uInt cplext[31] = { /* Extra bits for literal codes 257..285 */ + 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, 112, 112}; /* 112==invalid */ +static const uInt cpdist[30] = { /* Copy offsets for distance codes 0..29 */ + 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}; +static const uInt cpdext[30] = { /* Extra bits for distance codes */ + 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}; + +/* + Huffman code decoding is performed using a multi-level table lookup. + The fastest way to decode is to simply build a lookup table whose + size is determined by the longest code. However, the time it takes + to build this table can also be a factor if the data being decoded + is not very long. The most common codes are necessarily the + shortest codes, so those codes dominate the decoding time, and hence + the speed. The idea is you can have a shorter table that decodes the + shorter, more probable codes, and then point to subsidiary tables for + the longer codes. The time it costs to decode the longer codes is + then traded against the time it takes to make longer tables. + + This results of this trade are in the variables lbits and dbits + below. lbits is the number of bits the first level table for literal/ + length codes can decode in one step, and dbits is the same thing for + the distance codes. Subsequent tables are also less than or equal to + those sizes. These values may be adjusted either when all of the + codes are shorter than that, in which case the longest code length in + bits is used, or when the shortest code is *longer* than the requested + table size, in which case the length of the shortest code in bits is + used. + + There are two different values for the two tables, since they code a + different number of possibilities each. The literal/length table + codes 286 possible values, or in a flat code, a little over eight + bits. The distance table codes 30 possible values, or a little less + than five bits, flat. The optimum values for speed end up being + about one bit more than those, so lbits is 8+1 and dbits is 5+1. + The optimum values may differ though from machine to machine, and + possibly even between compilers. Your mileage may vary. + */ + + +/* If BMAX needs to be larger than 16, then h and x[] should be uLong. */ +#define BMAX 15 /* maximum bit length of any code */ + +static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e, inflate_huft ** t, uInt *m, inflate_huft *hp, uInt *hn, uInt *v) +//uInt *b; /* code lengths in bits (all assumed <= BMAX) */ +//uInt n; /* number of codes (assumed <= 288) */ +//uInt s; /* number of simple-valued codes (0..s-1) */ +//const uInt *d; /* list of base values for non-simple codes */ +//const uInt *e; /* list of extra bits for non-simple codes */ +//inflate_huft ** t; /* result: starting table */ +//uInt *m; /* maximum lookup bits, returns actual */ +//inflate_huft *hp; /* space for trees */ +//uInt *hn; /* hufts used in space */ +//uInt *v; /* working area: values in order of bit length */ +/* Given a list of code lengths and a maximum table size, make a set of + tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + if the given code set is incomplete (the tables are still built in this + case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + lengths), or Z_MEM_ERROR if not enough memory. */ +{ + + uInt a; /* counter for codes of length k */ + uInt c[BMAX+1]; /* bit length count table */ + uInt f; /* i repeats in table every f entries */ + int g; /* maximum code length */ + int h; /* table level */ + register uInt i; /* counter, current code */ + register uInt j; /* counter */ + register int k; /* number of bits in current code */ + int l; /* bits per table (returned in m) */ + uInt mask; /* (1 << w) - 1, to avoid cc -O bug on HP */ + register uInt *p; /* pointer into c[], b[], or v[] */ + inflate_huft *q; /* points to current table */ + struct inflate_huft_s r; /* table entry for structure assignment */ + inflate_huft *u[BMAX]; /* table stack */ + register int w; /* bits before this table == (l * h) */ + uInt x[BMAX+1]; /* bit offsets, then code stack */ + uInt *xp; /* pointer into x */ + int y; /* number of dummy codes added */ + uInt z; /* number of entries in current table */ + + + /* Generate counts for each bit length */ + p = c; +#define C0 *p++ = 0; +#define C2 C0 C0 C0 C0 +#define C4 C2 C2 C2 C2 + C4 /* clear c[]--assume BMAX+1 is 16 */ + p = b; i = n; + do { + c[*p++]++; /* assume all entries <= BMAX */ + } while (--i); + if (c[0] == n) /* null input--all zero length codes */ + { + *t = (inflate_huft *)Z_NULL; + *m = 0; + return Z_OK; + } + + + /* Find minimum and maximum length, bound *m by those */ + l = *m; + for (j = 1; j <= BMAX; j++) + if (c[j]) + break; + k = j; /* minimum code length */ + if ((uInt)l < j) + l = j; + for (i = BMAX; i; i--) + if (c[i]) + break; + g = i; /* maximum code length */ + if ((uInt)l > i) + l = i; + *m = l; + + + /* Adjust last length count to fill out codes, if needed */ + for (y = 1 << j; j < i; j++, y <<= 1) + if ((y -= c[j]) < 0) + return Z_DATA_ERROR; + if ((y -= c[i]) < 0) + return Z_DATA_ERROR; + c[i] += y; + + + /* Generate starting offsets into the value table for each length */ + x[1] = j = 0; + p = c + 1; xp = x + 2; + while (--i) { /* note that i == g from above */ + *xp++ = (j += *p++); + } + + + /* Make a table of values in order of bit lengths */ + p = b; i = 0; + do { + if ((j = *p++) != 0) + v[x[j]++] = i; + } while (++i < n); + n = x[g]; /* set n to length of v */ + + + /* Generate the Huffman codes and for each, make the table entries */ + x[0] = i = 0; /* first Huffman code is zero */ + p = v; /* grab values in bit order */ + h = -1; /* no tables yet--level -1 */ + w = -l; /* bits decoded == (l * h) */ + u[0] = (inflate_huft *)Z_NULL; /* just to keep compilers happy */ + q = (inflate_huft *)Z_NULL; /* ditto */ + z = 0; /* ditto */ + + /* go through the bit lengths (k already is bits in shortest code) */ + for (; k <= g; k++) + { + a = c[k]; + while (a--) + { + /* here i is the Huffman code of length k bits for value *p */ + /* make tables up to required level */ + while (k > w + l) + { + h++; + w += l; /* previous table always l bits */ + + /* compute minimum size table less than or equal to l bits */ + z = g - w; + z = z > (uInt)l ? l : z; /* table size upper limit */ + if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */ + { /* too few codes for k-w bit table */ + f -= a + 1; /* deduct codes from patterns left */ + xp = c + k; + if (j < z) + while (++j < z) /* try smaller tables up to z bits */ + { + if ((f <<= 1) <= *++xp) + break; /* enough codes to use up j bits */ + f -= *xp; /* else deduct codes from patterns */ + } + } + z = 1 << j; /* table entries for j-bit table */ + + /* allocate new table */ + if (*hn + z > MANY) /* (note: doesn't matter for fixed) */ + return Z_MEM_ERROR; /* not enough memory */ + u[h] = q = hp + *hn; + *hn += z; + + /* connect to last table, if there is one */ + if (h) + { + x[h] = i; /* save pattern for backing up */ + r.bits = (Byte)l; /* bits to dump before this table */ + r.exop = (Byte)j; /* bits in this table */ + j = i >> (w - l); + r.base = (uInt)(q - u[h-1] - j); /* offset to this table */ + u[h-1][j] = r; /* connect to last table */ + } + else + *t = q; /* first table is returned result */ + } + + /* set up table entry in r */ + r.bits = (Byte)(k - w); + if (p >= v + n) + r.exop = 128 + 64; /* out of values--invalid code */ + else if (*p < s) + { + r.exop = (Byte)(*p < 256 ? 0 : 32 + 64); /* 256 is end-of-block */ + r.base = *p++; /* simple code is just the value */ + } + else + { + r.exop = (Byte)(e[*p - s] + 16 + 64);/* non-simple--look up in lists */ + r.base = d[*p++ - s]; + } + + /* fill code-like entries with r */ + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) + q[j] = r; + + /* backwards increment the k-bit code i */ + for (j = 1 << (k - 1); i & j; j >>= 1) + i ^= j; + i ^= j; + + /* backup over finished tables */ + mask = (1 << w) - 1; /* needed on HP, cc -O bug */ + while ((i & mask) != x[h]) + { + h--; /* don't need to update q */ + w -= l; + mask = (1 << w) - 1; + } + } + } + + + /* Return Z_BUF_ERROR if we were given an incomplete table */ + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; +} + + +int inflate_trees_bits(uInt *c, uInt *bb, inflate_huft * *tb, inflate_huft *hp, z_streamp z) +//uInt *c; /* 19 code lengths */ +//uInt *bb; /* bits tree desired/actual depth */ +//inflate_huft * *tb; /* bits tree result */ +//inflate_huft *hp; /* space for trees */ +//z_streamp z; /* for messages */ +{ + int r; + uInt hn = 0; /* hufts used in space */ + uInt *v; /* work area for huft_build */ + + if ((v = (uInt*)ZALLOC(z, 19, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + r = huft_build(c, 19, 19, (uInt*)Z_NULL, (uInt*)Z_NULL, + tb, bb, hp, &hn, v); + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed dynamic bit lengths tree"; + else if (r == Z_BUF_ERROR || *bb == 0) + { + z->msg = (char*)"incomplete dynamic bit lengths tree"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; +} + + +int inflate_trees_dynamic(uInt nl, uInt nd, uInt *c, uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, inflate_huft *hp, z_streamp z) +//uInt nl; /* number of literal/length codes */ +//uInt nd; /* number of distance codes */ +//uInt *c; /* that many (total) code lengths */ +//uInt *bl; /* literal desired/actual bit depth */ +//uInt *bd; /* distance desired/actual bit depth */ +//inflate_huft * *tl; /* literal/length tree result */ +//inflate_huft * *td; /* distance tree result */ +//inflate_huft *hp; /* space for trees */ +//z_streamp z; /* for messages */ +{ + int r; + uInt hn = 0; /* hufts used in space */ + uInt *v; /* work area for huft_build */ + + /* allocate work area */ + if ((v = (uInt*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + + /* build literal/length tree */ + r = huft_build(c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v); + if (r != Z_OK || *bl == 0) + { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed literal/length tree"; + else if (r != Z_MEM_ERROR) + { + z->msg = (char*)"incomplete literal/length tree"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; + } + + /* build distance tree */ + r = huft_build(c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v); + if (r != Z_OK || (*bd == 0 && nl > 257)) + { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed distance tree"; + else if (r == Z_BUF_ERROR) { +#ifdef PKZIP_BUG_WORKAROUND + r = Z_OK; + } +#else + z->msg = (char*)"incomplete distance tree"; + r = Z_DATA_ERROR; + } + else if (r != Z_MEM_ERROR) + { + z->msg = (char*)"empty distance tree with lengths"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; +#endif + } + + /* done */ + ZFREE(z, v); + return Z_OK; +} + +/* inffixed.h -- table for decoding fixed codes + * Generated automatically by the maketree.c program + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +static uInt fixed_bl = 9; +static uInt fixed_bd = 5; +static inflate_huft fixed_tl[] = { + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},192}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},160}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},224}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},144}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},208}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},176}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},240}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},200}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},168}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},232}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},152}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},216}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},184}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},248}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},196}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},164}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},228}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},148}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},212}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},180}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},244}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},204}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},172}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},236}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},156}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},220}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},188}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},252}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},194}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},162}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},226}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},146}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},210}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},178}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},242}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},202}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},170}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},234}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},154}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},218}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},186}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},250}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},198}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},166}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},230}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},150}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},214}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},182}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},246}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},206}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},174}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},238}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},158}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},222}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},190}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},254}, + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},193}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},161}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},225}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},145}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},209}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},177}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},241}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},201}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},169}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},233}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},153}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},217}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},185}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},249}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},197}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},165}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},229}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},149}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},213}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},181}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},245}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},205}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},173}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},237}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},157}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},221}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},189}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},253}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},195}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},163}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},227}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},147}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},211}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},179}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},243}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},203}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},171}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},235}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},155}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},219}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},187}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},251}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},199}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},167}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},231}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},151}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},215}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},183}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},247}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},207}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},175}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},239}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},159}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},223}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},191}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},255} + }; +static inflate_huft fixed_td[] = { + {{{80,5}},1}, {{{87,5}},257}, {{{83,5}},17}, {{{91,5}},4097}, + {{{81,5}},5}, {{{89,5}},1025}, {{{85,5}},65}, {{{93,5}},16385}, + {{{80,5}},3}, {{{88,5}},513}, {{{84,5}},33}, {{{92,5}},8193}, + {{{82,5}},9}, {{{90,5}},2049}, {{{86,5}},129}, {{{192,5}},24577}, + {{{80,5}},2}, {{{87,5}},385}, {{{83,5}},25}, {{{91,5}},6145}, + {{{81,5}},7}, {{{89,5}},1537}, {{{85,5}},97}, {{{93,5}},24577}, + {{{80,5}},4}, {{{88,5}},769}, {{{84,5}},49}, {{{92,5}},12289}, + {{{82,5}},13}, {{{90,5}},3073}, {{{86,5}},193}, {{{192,5}},24577} + }; + +int inflate_trees_fixed(uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, z_streamp z) +//uInt *bl; /* literal desired/actual bit depth */ +//uInt *bd; /* distance desired/actual bit depth */ +//inflate_huft * *tl; /* literal/length tree result */ +//inflate_huft * *td; /* distance tree result */ +//z_streamp z; /* for memory allocation */ +{ + *bl = fixed_bl; + *bd = fixed_bd; + *tl = fixed_tl; + *td = fixed_td; + return Z_OK; +} + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* macros for bit input with no checking and for returning unused bytes */ +#define GRABBITS(j) {while(k<(j)){b|=((uLong)NEXTBYTE)<avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;} + +/* Called with number of bytes left to write in window at least 258 + (the maximum string length) and number of input bytes available + at least ten. The ten bytes are six bytes for the longest length/ + distance pair plus four bytes for overloading the bit buffer. */ + +static int inflate_fast(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, inflate_blocks_statef *s, z_streamp z) +{ + inflate_huft *t; /* temporary pointer */ + uInt e; /* extra bits or operation */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + uInt ml; /* mask for literal/length tree */ + uInt md; /* mask for distance tree */ + uInt c; /* bytes to copy */ + uInt d; /* distance back to copy from */ + Byte *r; /* copy source pointer */ + + /* load input, output, bit values */ + LOAD + + /* initialize masks */ + ml = inflate_mask[bl]; + md = inflate_mask[bd]; + + /* do until not enough input or output space for fast loop */ + do { /* assume called with m >= 258 && n >= 10 */ + /* get literal/length code */ + GRABBITS(20) /* max bits for literal/length code */ + if ((e = (t = tl + ((uInt)b & ml))->exop) == 0) + { + DUMPBITS(t->bits) + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte)t->base; + m--; + continue; + } + do { + DUMPBITS(t->bits) + if (e & 16) + { + /* get extra bits for length */ + e &= 15; + c = t->base + ((uInt)b & inflate_mask[e]); + DUMPBITS(e) + Tracevv(("inflate: * length %u\n", c)); + + /* decode distance base of block to copy */ + GRABBITS(15); /* max bits for distance code */ + e = (t = td + ((uInt)b & md))->exop; + do { + DUMPBITS(t->bits) + if (e & 16) + { + /* get extra bits to add to distance base */ + e &= 15; + GRABBITS(e) /* get extra bits (up to 13) */ + d = t->base + ((uInt)b & inflate_mask[e]); + DUMPBITS(e) + Tracevv(("inflate: * distance %u\n", d)); + + /* do the copy */ + m -= c; + if ((uInt)(q - s->window) >= d) /* offset before dest */ + { /* just copy */ + r = q - d; + *q++ = *r++; c--; /* minimum count is three, */ + *q++ = *r++; c--; /* so unroll loop a little */ + } + else /* else offset after destination */ + { + e = d - (uInt)(q - s->window); /* bytes from offset to end */ + r = s->end - e; /* pointer to offset */ + if (c > e) /* if source crosses, */ + { + c -= e; /* copy to end of window */ + do { + *q++ = *r++; + } while (--e); + r = s->window; /* copy rest from start of window */ + } + } + do { /* copy all or what's left */ + *q++ = *r++; + } while (--c); + break; + } + else if ((e & 64) == 0) + { + t += t->base; + e = (t += ((uInt)b & inflate_mask[e]))->exop; + } + else + { + z->msg = (char*)"invalid distance code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + } while (1); + break; + } + if ((e & 64) == 0) + { + t += t->base; + if ((e = (t += ((uInt)b & inflate_mask[e]))->exop) == 0) + { + DUMPBITS(t->bits) + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte)t->base; + m--; + break; + } + } + else if (e & 32) + { + Tracevv(("inflate: * end of block\n")); + UNGRAB + UPDATE + return Z_STREAM_END; + } + else + { + z->msg = (char*)"invalid literal/length code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + } while (1); + } while (m >= 258 && n >= 10); + + /* not enough input or output--restore pointers and return */ + UNGRAB + UPDATE + return Z_OK; +} + +/* infcodes.c -- process literals and length/distance pairs + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +typedef enum { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ + START, /* x: set up for LEN */ + LEN, /* i: get length/literal/eob next */ + LENEXT, /* i: getting length extra (have base) */ + DIST, /* i: get distance next */ + DISTEXT, /* i: getting distance extra */ + COPY, /* o: copying bytes in window, waiting for space */ + LIT, /* o: got literal, waiting for output space */ + WASH, /* o: got eob, possibly still output waiting */ + END, /* x: got eob and all data flushed */ + BADCODE} /* x: got error */ +inflate_codes_mode; + +/* inflate codes private state */ +struct inflate_codes_state { + + /* mode */ + inflate_codes_mode mode; /* current inflate_codes mode */ + + /* mode dependent information */ + uInt len; + union { + struct { + inflate_huft *tree; /* pointer into tree */ + uInt need; /* bits needed */ + } code; /* if LEN or DIST, where in tree */ + uInt lit; /* if LIT, literal */ + struct { + uInt get; /* bits to get for extra */ + uInt dist; /* distance back to copy from */ + } copy; /* if EXT or COPY, where and how much */ + } sub; /* submode */ + + /* mode independent information */ + Byte lbits; /* ltree bits decoded per branch */ + Byte dbits; /* dtree bits decoder per branch */ + inflate_huft *ltree; /* literal/length/eob tree */ + inflate_huft *dtree; /* distance tree */ + +}; + + +inflate_codes_statef *inflate_codes_new(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, z_streamp z) +{ + inflate_codes_statef *c; + + if ((c = (inflate_codes_statef *) + ZALLOC(z,1,sizeof(struct inflate_codes_state))) != Z_NULL) + { + c->mode = START; + c->lbits = (Byte)bl; + c->dbits = (Byte)bd; + c->ltree = tl; + c->dtree = td; + Tracev(("inflate: codes new\n")); + } + return c; +} + + +int inflate_codes(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt j; /* temporary storage */ + inflate_huft *t; /* temporary pointer */ + uInt e; /* extra bits or operation */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + Byte *f; /* pointer to copy strings from */ + inflate_codes_statef *c = s->sub.decode.codes; /* codes state */ + + /* copy input/output information to locals (UPDATE macro restores) */ + LOAD + + /* process input and output based on current state */ + while (1) switch (c->mode) + { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ + case START: /* x: set up for LEN */ +#ifndef SLOW + if (m >= 258 && n >= 10) + { + UPDATE + r = inflate_fast(c->lbits, c->dbits, c->ltree, c->dtree, s, z); + LOAD + if (r != Z_OK) + { + c->mode = r == Z_STREAM_END ? WASH : BADCODE; + break; + } + } +#endif /* !SLOW */ + c->sub.code.need = c->lbits; + c->sub.code.tree = c->ltree; + c->mode = LEN; + case LEN: /* i: get length/literal/eob next */ + j = c->sub.code.need; + NEEDBITS(j) + t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); + DUMPBITS(t->bits) + e = (uInt)(t->exop); + if (e == 0) /* literal */ + { + c->sub.lit = t->base; + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", t->base)); + c->mode = LIT; + break; + } + if (e & 16) /* length */ + { + c->sub.copy.get = e & 15; + c->len = t->base; + c->mode = LENEXT; + break; + } + if ((e & 64) == 0) /* next table */ + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + if (e & 32) /* end of block */ + { + Tracevv(("inflate: end of block\n")); + c->mode = WASH; + break; + } + c->mode = BADCODE; /* invalid code */ + z->msg = (char*)"invalid literal/length code"; + r = Z_DATA_ERROR; + LEAVE + case LENEXT: /* i: getting length extra (have base) */ + j = c->sub.copy.get; + NEEDBITS(j) + c->len += (uInt)b & inflate_mask[j]; + DUMPBITS(j) + c->sub.code.need = c->dbits; + c->sub.code.tree = c->dtree; + Tracevv(("inflate: length %u\n", c->len)); + c->mode = DIST; + case DIST: /* i: get distance next */ + j = c->sub.code.need; + NEEDBITS(j) + t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); + DUMPBITS(t->bits) + e = (uInt)(t->exop); + if (e & 16) /* distance */ + { + c->sub.copy.get = e & 15; + c->sub.copy.dist = t->base; + c->mode = DISTEXT; + break; + } + if ((e & 64) == 0) /* next table */ + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + c->mode = BADCODE; /* invalid code */ + z->msg = (char*)"invalid distance code"; + r = Z_DATA_ERROR; + LEAVE + case DISTEXT: /* i: getting distance extra */ + j = c->sub.copy.get; + NEEDBITS(j) + c->sub.copy.dist += (uInt)b & inflate_mask[j]; + DUMPBITS(j) + Tracevv(("inflate: distance %u\n", c->sub.copy.dist)); + c->mode = COPY; + case COPY: /* o: copying bytes in window, waiting for space */ +#ifndef __TURBOC__ /* Turbo C bug for following expression */ + f = (uInt)(q - s->window) < c->sub.copy.dist ? + s->end - (c->sub.copy.dist - (q - s->window)) : + q - c->sub.copy.dist; +#else + f = q - c->sub.copy.dist; + if ((uInt)(q - s->window) < c->sub.copy.dist) + f = s->end - (c->sub.copy.dist - (uInt)(q - s->window)); +#endif + while (c->len) + { + NEEDOUT + OUTBYTE(*f++) + if (f == s->end) + f = s->window; + c->len--; + } + c->mode = START; + break; + case LIT: /* o: got literal, waiting for output space */ + NEEDOUT + OUTBYTE(c->sub.lit) + c->mode = START; + break; + case WASH: /* o: got eob, possibly more output */ + if (k > 7) /* return unused byte, if any */ + { + Assert(k < 16, "inflate_codes grabbed too many bytes") + k -= 8; + n++; + p--; /* can always return one */ + } + FLUSH + if (s->read != s->write) + LEAVE + c->mode = END; + case END: + r = Z_STREAM_END; + LEAVE + case BADCODE: /* x: got error */ + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +#ifdef NEED_DUMMY_RETURN + return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ +#endif +} + + +void inflate_codes_free(inflate_codes_statef *c, z_streamp z) +{ + ZFREE(z, c); + Tracev(("inflate: codes free\n")); +} + +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#define BASE 65521L /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#undef DO1 +#undef DO2 +#undef DO4 +#undef DO8 + +#define DO1(buf,i) {s1 += buf[i]; s2 += s1;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* ========================================================================= */ +static uLong adler32(uLong adler, const Byte *buf, uInt len) +{ + unsigned long s1 = adler & 0xffff; + unsigned long s2 = (adler >> 16) & 0xffff; + int k; + + if (buf == Z_NULL) return 1L; + + while (len > 0) { + k = len < NMAX ? len : NMAX; + len -= k; + while (k >= 16) { + DO16(buf); + buf += 16; + k -= 16; + } + if (k != 0) do { + s1 += *buf++; + s2 += s1; + } while (--k); + s1 %= BASE; + s2 %= BASE; + } + return (s2 << 16) | s1; +} + + +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +static inflate_blocks_statef * inflate_blocks_new OF(( + z_streamp z, + check_func c, /* check function */ + uInt w)); /* window size */ + +static int inflate_blocks OF(( + inflate_blocks_statef *, + z_streamp , + int)); /* initial return code */ + +static void inflate_blocks_reset OF(( + inflate_blocks_statef *, + z_streamp , + uLong *)); /* check value on output */ + +static int inflate_blocks_free OF(( + inflate_blocks_statef *, + z_streamp)); + +#if 0 +static void inflate_set_dictionary OF(( + inflate_blocks_statef *s, + const Byte *d, /* dictionary */ + uInt n)); /* dictionary length */ + +static int inflate_blocks_sync_point OF(( + inflate_blocks_statef *s)); +#endif + +typedef enum { + imMETHOD, /* waiting for method byte */ + imFLAG, /* waiting for flag byte */ + imDICT4, /* four dictionary check bytes to go */ + imDICT3, /* three dictionary check bytes to go */ + imDICT2, /* two dictionary check bytes to go */ + imDICT1, /* one dictionary check byte to go */ + imDICT0, /* waiting for inflateSetDictionary */ + imBLOCKS, /* decompressing blocks */ + imCHECK4, /* four check bytes to go */ + imCHECK3, /* three check bytes to go */ + imCHECK2, /* two check bytes to go */ + imCHECK1, /* one check byte to go */ + imDONE, /* finished check, done */ + imBAD} /* got an error--stay here */ +inflate_mode; + +/* inflate private state */ +struct internal_state { + + /* mode */ + inflate_mode mode; /* current inflate mode */ + + /* mode dependent information */ + union { + uInt method; /* if FLAGS, method byte */ + struct { + uLong was; /* computed check value */ + uLong need; /* stream check value */ + } check; /* if CHECK, check values to compare */ + uInt marker; /* if BAD, inflateSync's marker bytes count */ + } sub; /* submode */ + + /* mode independent information */ + int nowrap; /* flag for no wrapper */ + uInt wbits; /* log2(window size) (8..15, defaults to 15) */ + inflate_blocks_statef + *blocks; /* current inflate_blocks state */ + +}; + + +int inflateReset(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL) + return Z_STREAM_ERROR; + z->total_in = z->total_out = 0; + z->msg = Z_NULL; + z->state->mode = z->state->nowrap ? imBLOCKS : imMETHOD; + inflate_blocks_reset(z->state->blocks, z, Z_NULL); + Tracev(("inflate: reset\n")); + return Z_OK; +} + + +int inflateEnd(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL || z->zfree == Z_NULL) + return Z_STREAM_ERROR; + if (z->state->blocks != Z_NULL) + inflate_blocks_free(z->state->blocks, z); + ZFREE(z, z->state); + z->state = Z_NULL; + Tracev(("inflate: end\n")); + return Z_OK; +} + + + +int inflateInit2_(z_streamp z, int w, const char *version, int stream_size) +{ + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != sizeof(z_stream)) + return Z_VERSION_ERROR; + + /* initialize state */ + if (z == Z_NULL) + return Z_STREAM_ERROR; + z->msg = Z_NULL; + if (z->zalloc == Z_NULL) + { + z->zalloc = (void *(*)(void *, unsigned, unsigned))zcalloc; + z->opaque = (voidp)0; + } + if (z->zfree == Z_NULL) z->zfree = (void (*)(void *, void *))zcfree; + if ((z->state = (struct internal_state *) + ZALLOC(z,1,sizeof(struct internal_state))) == Z_NULL) + return Z_MEM_ERROR; + z->state->blocks = Z_NULL; + + /* handle undocumented nowrap option (no zlib header or check) */ + z->state->nowrap = 0; + if (w < 0) + { + w = - w; + z->state->nowrap = 1; + } + + /* set window size */ + if (w < 8 || w > 15) + { + inflateEnd(z); + return Z_STREAM_ERROR; + } + z->state->wbits = (uInt)w; + + /* create inflate_blocks state */ + if ((z->state->blocks = + inflate_blocks_new(z, z->state->nowrap ? Z_NULL : adler32, (uInt)1 << w)) + == Z_NULL) + { + inflateEnd(z); + return Z_MEM_ERROR; + } + Tracev(("inflate: allocated\n")); + + /* reset state */ + inflateReset(z); + return Z_OK; +} + +#if 0 +int inflateInit_(z_streamp z, const char *version, int stream_size) +{ + return inflateInit2_(z, DEF_WBITS, version, stream_size); +} +#endif + +#define iNEEDBYTE {if(z->avail_in==0)return r;r=f;} +#define iNEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++) + +int inflate(z_streamp z, int f) +{ + int r; + uInt b; + + if (z == Z_NULL || z->state == Z_NULL || z->next_in == Z_NULL) + return Z_STREAM_ERROR; + f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; + r = Z_BUF_ERROR; + while (1) switch (z->state->mode) + { + case imMETHOD: + iNEEDBYTE + if (((z->state->sub.method = iNEXTBYTE) & 0xf) != Z_DEFLATED) + { + z->state->mode = imBAD; + z->msg = (char*)"unknown compression method"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + if ((z->state->sub.method >> 4) + 8 > z->state->wbits) + { + z->state->mode = imBAD; + z->msg = (char*)"invalid window size"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + z->state->mode = imFLAG; + case imFLAG: + iNEEDBYTE + b = iNEXTBYTE; + if (((z->state->sub.method << 8) + b) % 31) + { + z->state->mode = imBAD; + z->msg = (char*)"incorrect header check"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + Tracev(("inflate: zlib header ok\n")); + if (!(b & PRESET_DICT)) + { + z->state->mode = imBLOCKS; + break; + } + z->state->mode = imDICT4; + case imDICT4: + iNEEDBYTE + z->state->sub.check.need = (uLong)iNEXTBYTE << 24; + z->state->mode = imDICT3; + case imDICT3: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 16; + z->state->mode = imDICT2; + case imDICT2: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 8; + z->state->mode = imDICT1; + case imDICT1: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE; + z->adler = z->state->sub.check.need; + z->state->mode = imDICT0; + return Z_NEED_DICT; + case imDICT0: + z->state->mode = imBAD; + z->msg = (char*)"need dictionary"; + z->state->sub.marker = 0; /* can try inflateSync */ + return Z_STREAM_ERROR; + case imBLOCKS: + r = inflate_blocks(z->state->blocks, z, r); + if (r == Z_DATA_ERROR) + { + z->state->mode = imBAD; + z->state->sub.marker = 0; /* can try inflateSync */ + break; + } + if (r == Z_OK) + r = f; + if (r != Z_STREAM_END) + return r; + r = f; + inflate_blocks_reset(z->state->blocks, z, &z->state->sub.check.was); + if (z->state->nowrap) + { + z->state->mode = imDONE; + break; + } + z->state->mode = imCHECK4; + case imCHECK4: + iNEEDBYTE + z->state->sub.check.need = (uLong)iNEXTBYTE << 24; + z->state->mode = imCHECK3; + case imCHECK3: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 16; + z->state->mode = imCHECK2; + case imCHECK2: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 8; + z->state->mode = imCHECK1; + case imCHECK1: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE; + + if (z->state->sub.check.was != z->state->sub.check.need) + { + z->state->mode = imBAD; + z->msg = (char*)"incorrect data check"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + Tracev(("inflate: zlib check ok\n")); + z->state->mode = imDONE; + case imDONE: + return Z_STREAM_END; + case imBAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } +#ifdef NEED_DUMMY_RETURN + return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ +#endif +} + + +// defined but not used +#if 0 +int inflateSetDictionary(z_streamp z, const Byte *dictionary, uInt dictLength) +{ + uInt length = dictLength; + + if (z == Z_NULL || z->state == Z_NULL || z->state->mode != imDICT0) + return Z_STREAM_ERROR; + + if (adler32(1L, dictionary, dictLength) != z->adler) return Z_DATA_ERROR; + z->adler = 1L; + + if (length >= ((uInt)1<state->wbits)) + { + length = (1<state->wbits)-1; + dictionary += dictLength - length; + } + inflate_set_dictionary(z->state->blocks, dictionary, length); + z->state->mode = imBLOCKS; + return Z_OK; +} + + +int inflateSync(z_streamp z) +{ + uInt n; /* number of bytes to look at */ + Byte *p; /* pointer to bytes */ + uInt m; /* number of marker bytes found in a row */ + uLong r, w; /* temporaries to save total_in and total_out */ + + /* set up */ + if (z == Z_NULL || z->state == Z_NULL) + return Z_STREAM_ERROR; + if (z->state->mode != imBAD) + { + z->state->mode = imBAD; + z->state->sub.marker = 0; + } + if ((n = z->avail_in) == 0) + return Z_BUF_ERROR; + p = z->next_in; + m = z->state->sub.marker; + + /* search */ + while (n && m < 4) + { + static const Byte mark[4] = {0, 0, 0xff, 0xff}; + if (*p == mark[m]) + m++; + else if (*p) + m = 0; + else + m = 4 - m; + p++, n--; + } + + /* restore */ + z->total_in += p - z->next_in; + z->next_in = p; + z->avail_in = n; + z->state->sub.marker = m; + + /* return no joy or set up to restart on a new block */ + if (m != 4) + return Z_DATA_ERROR; + r = z->total_in; w = z->total_out; + inflateReset(z); + z->total_in = r; z->total_out = w; + z->state->mode = imBLOCKS; + return Z_OK; +} + + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + * implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH + * but removes the length bytes of the resulting empty stored block. When + * decompressing, PPP checks that at the end of input packet, inflate is + * waiting for these length bytes. + */ +int inflateSyncPoint(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL || z->state->blocks == Z_NULL) + return Z_STREAM_ERROR; + return inflate_blocks_sync_point(z->state->blocks); +} +#endif + +voidp zcalloc (voidp opaque, unsigned items, unsigned size) +{ + if (opaque) items += size - size; /* make compiler happy */ + return (voidp)Z_Malloc(items*size); +} + +void zcfree (voidp opaque, voidp ptr) +{ + Z_Free(ptr); + if (opaque) return; /* make compiler happy */ +} + + diff --git a/src/qcommon/unzip.h b/src/qcommon/unzip.h new file mode 100644 index 0000000..23c7ea2 --- /dev/null +++ b/src/qcommon/unzip.h @@ -0,0 +1,341 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#if defined( STRICTUNZIP ) || defined( STRICTZIPUNZIP ) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef void* unzFile; +#endif + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + unsigned int tm_sec; /* seconds after the minute - [0,59] */ + unsigned int tm_min; /* minutes after the hour - [0,59] */ + unsigned int tm_hour; /* hours since midnight - [0,23] */ + unsigned int tm_mday; /* day of the month - [1,31] */ + unsigned int tm_mon; /* months since January - [0,11] */ + unsigned int tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + unsigned long number_entry; /* total number of entries in the central dir on this disk */ + unsigned long size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + unsigned long version; /* version made by 2 unsigned chars */ + unsigned long version_needed; /* version needed to extract 2 unsigned chars */ + unsigned long flag; /* general purpose bit flag 2 unsigned chars */ + unsigned long compression_method; /* compression method 2 unsigned chars */ + unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */ + unsigned long crc; /* crc-32 4 unsigned chars */ + unsigned long compressed_size; /* compressed size 4 unsigned chars */ + unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */ + unsigned long size_filename; /* filename length 2 unsigned chars */ + unsigned long size_file_extra; /* extra field length 2 unsigned chars */ + unsigned long size_file_comment; /* file comment length 2 unsigned chars */ + + unsigned long disk_num_start; /* disk number start 2 unsigned chars */ + unsigned long internal_fa; /* internal file attributes 2 unsigned chars */ + unsigned long external_fa; /* external file attributes 4 unsigned chars */ + + tm_unz tmu_date; +} unz_file_info; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + unsigned long offset_curfile; /* relative offset of static header 4 unsigned chars */ +} unz_file_info_internal; + +typedef void* ( *alloc_func )( void* opaque, unsigned int items, unsigned int size ); +typedef void ( *free_func )( void* opaque, void* address ); + +struct internal_state; + +typedef struct z_stream_s { + unsigned char *next_in; /* next input unsigned char */ + unsigned int avail_in; /* number of unsigned chars available at next_in */ + unsigned long total_in; /* total nb of input unsigned chars read so */ + + unsigned char *next_out; /* next output unsigned char should be put there */ + unsigned int avail_out; /* remaining free space at next_out */ + unsigned long total_out; /* total nb of unsigned chars output so */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + unsigned char* opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: ascii or binary */ + unsigned long adler; /* adler32 value of the uncompressed data */ + unsigned long reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream *z_streamp; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/ + unsigned long stream_initialised; /* flag set if stream structure is initialised*/ + + unsigned long offset_local_extrafield; /* offset of the static extra field */ + unsigned int size_local_extrafield; /* size of the static extra field */ + unsigned long pos_local_extrafield; /* position in the static extra field in read*/ + + unsigned long crc32; /* crc32 of all data uncompressed */ + unsigned long crc32_wait; /* crc32 we must obtain after decompress all */ + unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */ + unsigned long rest_read_uncompressed; /*number of unsigned char to be obtained after decomp*/ + FILE* file; /* io structore of the zipfile */ + unsigned long compression_method; /* compression method (0==store) */ + unsigned long byte_before_the_zipfile; /* unsigned char before the zipfile, (>0 for sfx)*/ +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + FILE* file; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + unsigned long byte_before_the_zipfile; /* unsigned char before the zipfile, (>0 for sfx)*/ + unsigned long num_file; /* number of the current file in the zipfile*/ + unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/ + unsigned long current_file_ok; /* flag about the usability of the current file*/ + unsigned long central_pos; /* position of the beginning of the central dir*/ + + unsigned long size_central_dir; /* size of the central directory */ + unsigned long offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ +} unz_s; + +#define UNZ_OK ( 0 ) +#define UNZ_END_OF_LIST_OF_FILE ( -100 ) +#define UNZ_ERRNO ( Z_ERRNO ) +#define UNZ_EOF ( 0 ) +#define UNZ_PARAMERROR ( -102 ) +#define UNZ_BADZIPFILE ( -103 ) +#define UNZ_INTERNALERROR ( -104 ) +#define UNZ_CRCERROR ( -105 ) + +#define UNZ_CASESENSITIVE 1 +#define UNZ_NOTCASESENSITIVE 2 +#define UNZ_OSDEFAULTCASE 0 + +extern int unzStringFileNameCompare( const char* fileName1, const char* fileName2, int iCaseSensitivity ); + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + +extern unzFile unzOpen( const char *path ); +extern unzFile unzReOpen( const char* path, unzFile file ); + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer + "zlib/zlib111.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern int unzClose( unzFile file ); + +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int unzGetGlobalInfo( unzFile file, unz_global_info *pglobal_info ); + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int unzGetGlobalComment( unzFile file, char *szComment, unsigned long uSizeBuf ); + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of unsigned char copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int unzGoToFirstFile( unzFile file ); + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int unzGoToNextFile( unzFile file ); + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int unzGetCurrentFileInfoPosition( unzFile file, unsigned long *pos ); + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzSetCurrentFileInfoPosition( unzFile file, unsigned long pos ); + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzLocateFile( unzFile file, const char *szFileName, int iCaseSensitivity ); + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +extern int unzGetCurrentFileInfo( unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize ); + +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int unzOpenCurrentFile( unzFile file ); + +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int unzCloseCurrentFile( unzFile file ); + +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + + +extern int unzReadCurrentFile( unzFile file, void* buf, unsigned len ); + +/* + Read unsigned chars from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of unsigned char copied if somes unsigned chars are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern long unztell( unzFile file ); + +/* + Give the current position in uncompressed data +*/ + +extern int unzeof( unzFile file ); + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int unzGetLocalExtrafield( unzFile file, void* buf, unsigned len ); + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of unsigned chars copied in buf, or (if <0) + the error code +*/ diff --git a/src/qcommon/vm.c b/src/qcommon/vm.c new file mode 100644 index 0000000..c9d60df --- /dev/null +++ b/src/qcommon/vm.c @@ -0,0 +1,849 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// vm.c -- virtual machine + +/* + + +intermix code and data +symbol table + +a dll has one imported function: VM_SystemCall +and one exported function: Perform + + +*/ + +#include "vm_local.h" + + +vm_t *currentVM = NULL; // bk001212 +vm_t *lastVM = NULL; // bk001212 +int vm_debugLevel; + +#define MAX_VM 3 +vm_t vmTable[MAX_VM]; + + +void VM_VmInfo_f( void ); +void VM_VmProfile_f( void ); + + +// converts a VM pointer to a C pointer and +// checks to make sure that the range is acceptable +void *VM_VM2C( vmptr_t p, int length ) { + return (void *)p; +} + +void VM_Debug( int level ) { + vm_debugLevel = level; +} + +/* +============== +VM_Init +============== +*/ +void VM_Init( void ) { + Cvar_Get( "vm_cgame", "0", CVAR_ARCHIVE ); + Cvar_Get( "vm_game", "0", CVAR_ARCHIVE ); + Cvar_Get( "vm_ui", "0", CVAR_ARCHIVE ); + + Cmd_AddCommand( "vmprofile", VM_VmProfile_f ); + Cmd_AddCommand( "vminfo", VM_VmInfo_f ); + + Com_Memset( vmTable, 0, sizeof( vmTable ) ); +} + + +/* +=============== +VM_ValueToSymbol + +Assumes a program counter value +=============== +*/ +const char *VM_ValueToSymbol( vm_t *vm, int value ) { + vmSymbol_t *sym; + static char text[MAX_TOKEN_CHARS]; + + sym = vm->symbols; + if ( !sym ) { + return "NO SYMBOLS"; + } + + // find the symbol + while ( sym->next && sym->next->symValue <= value ) { + sym = sym->next; + } + + if ( value == sym->symValue ) { + return sym->symName; + } + + Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue ); + + return text; +} + +/* +=============== +VM_ValueToFunctionSymbol + +For profiling, find the symbol behind this value +=============== +*/ +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) { + vmSymbol_t *sym; + static vmSymbol_t nullSym; + + sym = vm->symbols; + if ( !sym ) { + return &nullSym; + } + + while ( sym->next && sym->next->symValue <= value ) { + sym = sym->next; + } + + return sym; +} + + +/* +=============== +VM_SymbolToValue +=============== +*/ +int VM_SymbolToValue( vm_t *vm, const char *symbol ) { + vmSymbol_t *sym; + + for ( sym = vm->symbols ; sym ; sym = sym->next ) { + if ( !strcmp( symbol, sym->symName ) ) { + return sym->symValue; + } + } + return 0; +} + + +/* +===================== +VM_SymbolForCompiledPointer +===================== +*/ +const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) { + int i; + + if ( code < (void *)vm->codeBase ) { + return "Before code block"; + } + if ( code >= ( void * )( vm->codeBase + vm->codeLength ) ) { + return "After code block"; + } + + // find which original instruction it is after + for ( i = 0 ; i < vm->codeLength ; i++ ) { + if ( (void *)vm->instructionPointers[i] > code ) { + break; + } + } + i--; + + // now look up the bytecode instruction pointer + return VM_ValueToSymbol( vm, i ); +} + + + +/* +=============== +ParseHex +=============== +*/ +int ParseHex( const char *text ) { + int value; + int c; + + value = 0; + while ( ( c = *text++ ) != 0 ) { + if ( c >= '0' && c <= '9' ) { + value = value * 16 + c - '0'; + continue; + } + if ( c >= 'a' && c <= 'f' ) { + value = value * 16 + 10 + c - 'a'; + continue; + } + if ( c >= 'A' && c <= 'F' ) { + value = value * 16 + 10 + c - 'A'; + continue; + } + } + + return value; +} + +/* +=============== +VM_LoadSymbols +=============== +*/ +void VM_LoadSymbols( vm_t *vm ) { + int len; + char *mapfile, *text_p, *token; + char name[MAX_QPATH]; + char symbols[MAX_QPATH]; + vmSymbol_t **prev, *sym; + int count; + int value; + int chars; + int segment; + int numInstructions; + + // don't load symbols if not developer + if ( !com_developer->integer ) { + return; + } + + COM_StripExtension2( vm->name, name, sizeof( name ) ); + Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name ); + len = FS_ReadFile( symbols, (void **)&mapfile ); + if ( !mapfile ) { + Com_Printf( "Couldn't load symbol file: %s\n", symbols ); + return; + } + + numInstructions = vm->instructionPointersLength >> 2; + + // parse the symbols + text_p = mapfile; + prev = &vm->symbols; + count = 0; + + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + segment = ParseHex( token ); + if ( segment ) { + COM_Parse( &text_p ); + COM_Parse( &text_p ); + continue; // only load code segment values + } + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + value = ParseHex( token ); + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + chars = strlen( token ); + sym = Hunk_Alloc( sizeof( *sym ) + chars, h_high ); + *prev = sym; + prev = &sym->next; + sym->next = NULL; + + // convert value from an instruction number to a code offset + if ( value >= 0 && value < numInstructions ) { + value = vm->instructionPointers[value]; + } + + sym->symValue = value; + Q_strncpyz( sym->symName, token, chars + 1 ); + + count++; + } + + vm->numSymbols = count; + Com_Printf( "%i symbols parsed from %s\n", count, symbols ); + FS_FreeFile( mapfile ); +} + +/* +============ +VM_DllSyscall + +Dlls will call this directly + + rcg010206 The horror; the horror. + + The syscall mechanism relies on stack manipulation to get it's args. + This is likely due to C's inability to pass "..." parameters to + a function in one clean chunk. On PowerPC Linux, these parameters + are not necessarily passed on the stack, so while (&arg[0] == arg) + is true, (&arg[1] == 2nd function parameter) is not necessarily + accurate, as arg's value might have been stored to the stack or + other piece of scratch memory to give it a valid address, but the + next parameter might still be sitting in a register. + + Quake's syscall system also assumes that the stack grows downward, + and that any needed types can be squeezed, safely, into a signed int. + + This hack below copies all needed values for an argument to a + array in memory, so that Quake can get the correct values. This can + also be used on systems where the stack grows upwards, as the + presumably standard and safe stdargs.h macros are used. + + As for having enough space in a signed int for your datatypes, well, + it might be better to wait for DOOM 3 before you start porting. :) + + The original code, while probably still inherently dangerous, seems + to work well enough for the platforms it already works on. Rather + than add the performance hit for those platforms, the original code + is still in use there. + + For speed, we just grab 15 arguments, and don't worry about exactly + how many the syscall actually needs; the extra is thrown away. + +============ +*/ +int QDECL VM_DllSyscall( int arg, ... ) { +#if ( ( defined __linux__ ) && ( defined __powerpc__ ) ) //|| (defined MACOS_X) + // rcg010206 - see commentary above + int args[16]; + int i; + va_list ap; + + args[0] = arg; + + va_start( ap, arg ); + for ( i = 1; i < sizeof( args ) / sizeof( args[i] ); i++ ) + args[i] = va_arg( ap, int ); + va_end( ap ); + + return currentVM->systemCall( args ); +#else // original id code + return currentVM->systemCall( &arg ); +#endif +} + +/* +================= +VM_Restart + +Reload the data, but leave everything else in place +This allows a server to do a map_restart without changing memory allocation +================= +*/ +vm_t *VM_Restart( vm_t *vm ) { + vmHeader_t *header; + int length; + int dataLength; + int i; + char filename[MAX_QPATH]; + + // DLL's can't be restarted in place + if ( vm->dllHandle ) { + char name[MAX_QPATH]; + int ( *systemCall )( int *parms ); + + systemCall = vm->systemCall; + Q_strncpyz( name, vm->name, sizeof( name ) ); + + VM_Free( vm ); + + vm = VM_Create( name, systemCall, VMI_NATIVE ); + return vm; + } + + // load the image + Com_Printf( "VM_Restart()\n", filename ); + Com_sprintf( filename, sizeof( filename ), "vm/%s.qvm", vm->name ); + Com_Printf( "Loading vm file %s.\n", filename ); + length = FS_ReadFile( filename, (void **)&header ); + if ( !header ) { + Com_Error( ERR_DROP, "VM_Restart failed.\n" ); + } + + // byte swap the header + for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) { + ( (int *)header )[i] = LittleLong( ( (int *)header )[i] ); + } + + // validate + if ( header->vmMagic != VM_MAGIC + || header->bssLength < 0 + || header->dataLength < 0 + || header->litLength < 0 + || header->codeLength <= 0 ) { + VM_Free( vm ); + Com_Error( ERR_FATAL, "%s has bad header", filename ); + } + + // round up to next power of 2 so all data operations can + // be mask protected + dataLength = header->dataLength + header->litLength + header->bssLength; + for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { + } + dataLength = 1 << i; + + // clear the data + Com_Memset( vm->dataBase, 0, dataLength ); + + // copy the intialized data + Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength ); + + // byte swap the longs + for ( i = 0 ; i < header->dataLength ; i += 4 ) { + *( int * )( vm->dataBase + i ) = LittleLong( *( int * )( vm->dataBase + i ) ); + } + + // free the original file + FS_FreeFile( header ); + + return vm; +} + +/* +================ +VM_Create + +If image ends in .qvm it will be interpreted, otherwise +it will attempt to load as a system dll +================ +*/ + +#define STACK_SIZE 0x20000 + +vm_t *VM_Create( const char *module, int ( *systemCalls )(int *), + vmInterpret_t interpret ) { + vm_t *vm; + vmHeader_t *header; + int length; + int dataLength; + int i, remaining; + char filename[MAX_QPATH]; + + if ( !module || !module[0] || !systemCalls ) { + Com_Error( ERR_FATAL, "VM_Create: bad parms" ); + } + + remaining = Hunk_MemoryRemaining(); + + // see if we already have the VM + for ( i = 0 ; i < MAX_VM ; i++ ) { + if ( !Q_stricmp( vmTable[i].name, module ) ) { + vm = &vmTable[i]; + return vm; + } + } + + // find a free vm + for ( i = 0 ; i < MAX_VM ; i++ ) { + if ( !vmTable[i].name[0] ) { + break; + } + } + + if ( i == MAX_VM ) { + Com_Error( ERR_FATAL, "VM_Create: no free vm_t" ); + } + + vm = &vmTable[i]; + + Q_strncpyz( vm->name, module, sizeof( vm->name ) ); + vm->systemCall = systemCalls; + + if ( interpret == VMI_NATIVE ) { + // try to load as a system dll + vm->dllHandle = Sys_LoadDll( module, vm->fqpath, &vm->entryPoint, VM_DllSyscall ); + if ( vm->dllHandle ) { + return vm; + } + + Com_Printf( "Failed to load dll, looking for qvm.\n" ); + interpret = VMI_COMPILED; + } + + // load the image + Com_sprintf( filename, sizeof( filename ), "vm/%s.qvm", vm->name ); + Com_Printf( "Loading vm file %s.\n", filename ); + length = FS_ReadFile( filename, (void **)&header ); + if ( !header ) { + Com_Printf( "Failed.\n" ); + VM_Free( vm ); + return NULL; + } + + // byte swap the header + for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) { + ( (int *)header )[i] = LittleLong( ( (int *)header )[i] ); + } + + // validate + if ( header->vmMagic != VM_MAGIC + || header->bssLength < 0 + || header->dataLength < 0 + || header->litLength < 0 + || header->codeLength <= 0 ) { + VM_Free( vm ); + Com_Error( ERR_FATAL, "%s has bad header", filename ); + } + + // round up to next power of 2 so all data operations can + // be mask protected + dataLength = header->dataLength + header->litLength + header->bssLength; + for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { + } + dataLength = 1 << i; + + // allocate zero filled space for initialized and uninitialized data + vm->dataBase = Hunk_Alloc( dataLength, h_high ); + vm->dataMask = dataLength - 1; + + // copy the intialized data + Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength ); + + // byte swap the longs + for ( i = 0 ; i < header->dataLength ; i += 4 ) { + *( int * )( vm->dataBase + i ) = LittleLong( *( int * )( vm->dataBase + i ) ); + } + + // allocate space for the jump targets, which will be filled in by the compile/prep functions + vm->instructionPointersLength = header->instructionCount * 4; + vm->instructionPointers = Hunk_Alloc( vm->instructionPointersLength, h_high ); + + // copy or compile the instructions + vm->codeLength = header->codeLength; + + if ( interpret >= VMI_COMPILED ) { + vm->compiled = qtrue; + VM_Compile( vm, header ); + } else { + vm->compiled = qfalse; + VM_PrepareInterpreter( vm, header ); + } + + // free the original file + FS_FreeFile( header ); + + // load the map file + VM_LoadSymbols( vm ); + + // the stack is implicitly at the end of the image + vm->programStack = vm->dataMask + 1; + vm->stackBottom = vm->programStack - STACK_SIZE; + + Com_Printf( "%s loaded in %d bytes on the hunk\n", module, remaining - Hunk_MemoryRemaining() ); + + return vm; +} + +/* +============== +VM_Free +============== +*/ +void VM_Free( vm_t *vm ) { + + if ( vm->dllHandle ) { + Sys_UnloadDll( vm->dllHandle ); + Com_Memset( vm, 0, sizeof( *vm ) ); + } +#if 0 // now automatically freed by hunk + if ( vm->codeBase ) { + Z_Free( vm->codeBase ); + } + if ( vm->dataBase ) { + Z_Free( vm->dataBase ); + } + if ( vm->instructionPointers ) { + Z_Free( vm->instructionPointers ); + } +#endif + Com_Memset( vm, 0, sizeof( *vm ) ); + + currentVM = NULL; + lastVM = NULL; +} + +void VM_Clear( void ) { + int i; + for ( i = 0; i < MAX_VM; i++ ) { + if ( vmTable[i].dllHandle ) { + Sys_UnloadDll( vmTable[i].dllHandle ); + } + Com_Memset( &vmTable[i], 0, sizeof( vm_t ) ); + } + currentVM = NULL; + lastVM = NULL; +} + +void *VM_ArgPtr( int intValue ) { + if ( !intValue ) { + return NULL; + } + // bk001220 - currentVM is missing on reconnect + if ( currentVM == NULL ) { + return NULL; + } + + if ( currentVM->entryPoint ) { + return ( void * )( currentVM->dataBase + intValue ); + } else { + return ( void * )( currentVM->dataBase + ( intValue & currentVM->dataMask ) ); + } +} + +void *VM_ExplicitArgPtr( vm_t *vm, int intValue ) { + if ( !intValue ) { + return NULL; + } + + // bk010124 - currentVM is missing on reconnect here as well? + if ( currentVM == NULL ) { + return NULL; + } + + // + if ( vm->entryPoint ) { + return ( void * )( vm->dataBase + intValue ); + } else { + return ( void * )( vm->dataBase + ( intValue & vm->dataMask ) ); + } +} + + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return value +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ +#define MAX_STACK 256 +#define STACK_MASK ( MAX_STACK - 1 ) + +int QDECL VM_Call( vm_t *vm, int callnum, ... ) { + vm_t *oldVM; + int r; + //rcg010207 see dissertation at top of VM_DllSyscall() in this file. +#if ( ( defined __linux__ ) && ( defined __powerpc__ ) ) || ( defined MACOS_X ) + int i; + int args[16]; + va_list ap; +#endif + + // DHM - Nerve +#ifdef UPDATE_SERVER + return 0; +#endif + + if ( !vm ) { + Com_Error( ERR_FATAL, "VM_Call with NULL vm" ); + } + + oldVM = currentVM; + currentVM = vm; + lastVM = vm; + + if ( vm_debugLevel ) { + Com_Printf( "VM_Call( %i )\n", callnum ); + } + + // if we have a dll loaded, call it directly + if ( vm->entryPoint ) { + //rcg010207 - see dissertation at top of VM_DllSyscall() in this file. +#if ( ( defined __linux__ ) && ( defined __powerpc__ ) ) || ( defined MACOS_X ) + va_start( ap, callnum ); + for ( i = 0; i < sizeof( args ) / sizeof( args[i] ); i++ ) + args[i] = va_arg( ap, int ); + va_end( ap ); + + r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7], + args[8], args[9], args[10], args[11], + args[12], args[13], args[14], args[15] ); +#else // PPC above, original id code below + r = vm->entryPoint( ( &callnum )[0], ( &callnum )[1], ( &callnum )[2], ( &callnum )[3], + ( &callnum )[4], ( &callnum )[5], ( &callnum )[6], ( &callnum )[7], + ( &callnum )[8], ( &callnum )[9], ( &callnum )[10], ( &callnum )[11], ( &callnum )[12] ); +#endif + } else if ( vm->compiled ) { + r = VM_CallCompiled( vm, &callnum ); + } else { + r = VM_CallInterpreted( vm, &callnum ); + } + + if ( oldVM != NULL ) { // bk001220 - assert(currentVM!=NULL) for oldVM==NULL + currentVM = oldVM; + } + return r; +} + +//================================================================= + +static int QDECL VM_ProfileSort( const void *a, const void *b ) { + vmSymbol_t *sa, *sb; + + sa = *(vmSymbol_t **)a; + sb = *(vmSymbol_t **)b; + + if ( sa->profileCount < sb->profileCount ) { + return -1; + } + if ( sa->profileCount > sb->profileCount ) { + return 1; + } + return 0; +} + +/* +============== +VM_VmProfile_f + +============== +*/ +void VM_VmProfile_f( void ) { + vm_t *vm; + vmSymbol_t **sorted, *sym; + int i; + double total; + + if ( !lastVM ) { + return; + } + + vm = lastVM; + + if ( !vm->numSymbols ) { + return; + } + + sorted = Z_Malloc( vm->numSymbols * sizeof( *sorted ) ); + sorted[0] = vm->symbols; + total = sorted[0]->profileCount; + for ( i = 1 ; i < vm->numSymbols ; i++ ) { + sorted[i] = sorted[i - 1]->next; + total += sorted[i]->profileCount; + } + + qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort ); + + for ( i = 0 ; i < vm->numSymbols ; i++ ) { + int perc; + + sym = sorted[i]; + + perc = 100 * (float) sym->profileCount / total; + Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName ); + sym->profileCount = 0; + } + + Com_Printf( " %9.0f total\n", total ); + + Z_Free( sorted ); +} + +/* +============== +VM_VmInfo_f + +============== +*/ +void VM_VmInfo_f( void ) { + vm_t *vm; + int i; + + Com_Printf( "Registered virtual machines:\n" ); + for ( i = 0 ; i < MAX_VM ; i++ ) { + vm = &vmTable[i]; + if ( !vm->name[0] ) { + break; + } + Com_Printf( "%s : ", vm->name ); + if ( vm->dllHandle ) { + Com_Printf( "native\n" ); + continue; + } + if ( vm->compiled ) { + Com_Printf( "compiled on load\n" ); + } else { + Com_Printf( "interpreted\n" ); + } + Com_Printf( " code length : %7i\n", vm->codeLength ); + Com_Printf( " table length: %7i\n", vm->instructionPointersLength ); + Com_Printf( " data length : %7i\n", vm->dataMask + 1 ); + } +} + +/* +=============== +VM_LogSyscalls + +Insert calls to this while debugging the vm compiler +=============== +*/ +void VM_LogSyscalls( int *args ) { + static int callnum; + static FILE *f; + + if ( !f ) { + f = fopen( "syscalls.log", "w" ); + } + callnum++; + fprintf( f, "%i: %i (%i) = %i %i %i %i\n", callnum, args - (int *)currentVM->dataBase, + args[0], args[1], args[2], args[3], args[4] ); +} + +#if defined( __MACOS__ ) +#define DLL_ONLY //DAJ +#endif + +#ifdef DLL_ONLY // bk010215 - for DLL_ONLY dedicated servers/builds w/o VM +int VM_CallCompiled( vm_t *vm, int *args ) { + return( 0 ); +} + +void VM_Compile( vm_t *vm, vmHeader_t *header ) {} +#endif // DLL_ONLY diff --git a/src/qcommon/vm_interpreted.c b/src/qcommon/vm_interpreted.c new file mode 100644 index 0000000..87ce71a --- /dev/null +++ b/src/qcommon/vm_interpreted.c @@ -0,0 +1,896 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "vm_local.h" + +#ifdef DEBUG_VM // bk001204 +static char *opnames[256] = { + "OP_UNDEF", + + "OP_IGNORE", + + "OP_BREAK", + + "OP_ENTER", + "OP_LEAVE", + "OP_CALL", + "OP_PUSH", + "OP_POP", + + "OP_CONST", + + "OP_LOCAL", + + "OP_JUMP", + + //------------------- + + "OP_EQ", + "OP_NE", + + "OP_LTI", + "OP_LEI", + "OP_GTI", + "OP_GEI", + + "OP_LTU", + "OP_LEU", + "OP_GTU", + "OP_GEU", + + "OP_EQF", + "OP_NEF", + + "OP_LTF", + "OP_LEF", + "OP_GTF", + "OP_GEF", + + //------------------- + + "OP_LOAD1", + "OP_LOAD2", + "OP_LOAD4", + "OP_STORE1", + "OP_STORE2", + "OP_STORE4", + "OP_ARG", + + "OP_BLOCK_COPY", + + //------------------- + + "OP_SEX8", + "OP_SEX16", + + "OP_NEGI", + "OP_ADD", + "OP_SUB", + "OP_DIVI", + "OP_DIVU", + "OP_MODI", + "OP_MODU", + "OP_MULI", + "OP_MULU", + + "OP_BAND", + "OP_BOR", + "OP_BXOR", + "OP_BCOM", + + "OP_LSH", + "OP_RSHI", + "OP_RSHU", + + "OP_NEGF", + "OP_ADDF", + "OP_SUBF", + "OP_DIVF", + "OP_MULF", + + "OP_CVIF", + "OP_CVFI" +}; +#endif + +#if defined( idppc ) + #if defined( __GNUC__ ) +static inline unsigned int loadWord( void *addr ) { + unsigned int word; + + asm ( "lwbrx %0,0,%1" : "=r" ( word ) : "r" ( addr ) ); + return word; +} + #else + #define loadWord( addr ) __lwbrx( addr,0 ) + #endif +#else + #define loadWord( addr ) *( (int *)addr ) +#endif + +char *VM_Indent( vm_t *vm ) { + static char *string = " "; + if ( vm->callLevel > 20 ) { + return string; + } + return string + 2 * ( 20 - vm->callLevel ); +} + +void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) { + int count; + + count = 0; + do { + Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) ); + programStack = *(int *)&vm->dataBase[programStack + 4]; + programCounter = *(int *)&vm->dataBase[programStack]; + } while ( programCounter != -1 && ++count < 32 ); + +} + + +/* +==================== +VM_PrepareInterpreter +==================== +*/ +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) { + int op; + int pc; + byte *code; + int instruction; + int *codeBase; + + vm->codeBase = Hunk_Alloc( vm->codeLength * 4, h_high ); // we're now int aligned +// memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength ); + + // we don't need to translate the instructions, but we still need + // to find each instructions starting point for jumps + pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + codeBase = (int *)vm->codeBase; + + while ( instruction < header->instructionCount ) { + vm->instructionPointers[ instruction ] = pc; + instruction++; + + op = code[ pc ]; + codeBase[pc] = op; + if ( pc > header->codeLength ) { + Com_Error( ERR_FATAL, "VM_PrepareInterpreter: pc > header->codeLength" ); + } + + pc++; + + // these are the only opcodes that aren't a single byte + switch ( op ) { + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + case OP_BLOCK_COPY: + codeBase[pc + 0] = loadWord( &code[pc] ); + pc += 4; + break; + case OP_ARG: + codeBase[pc + 0] = code[pc]; + pc += 1; + break; + default: + break; + } + + } + pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + codeBase = (int *)vm->codeBase; + + while ( instruction < header->instructionCount ) { + op = code[ pc ]; + instruction++; + pc++; + switch ( op ) { + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + case OP_BLOCK_COPY: + switch ( op ) { + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + codeBase[pc] = vm->instructionPointers[codeBase[pc]]; + break; + default: + break; + } + pc += 4; + break; + case OP_ARG: + pc += 1; + break; + default: + break; + } + + } +} + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return stack +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ +#define MAX_STACK 256 +#define STACK_MASK ( MAX_STACK - 1 ) +//#define DEBUG_VM + +#define DEBUGSTR va( "%s%i", VM_Indent( vm ), opStack - stack ) + +int VM_CallInterpreted( vm_t *vm, int *args ) { + int stack[MAX_STACK]; + int *opStack; + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + int *codeImage; + int v1; + int dataMask; +#ifdef DEBUG_VM + vmSymbol_t *profileSymbol; +#endif + + // interpret the code + vm->currentlyInterpreting = qtrue; + + // we might be called recursively, so this might not be the very top + programStack = stackOnEntry = vm->programStack; + +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, 0 ); + // uncomment this for debugging breakpoints + vm->breakFunction = 0; +#endif + // set up the stack frame + + image = vm->dataBase; + codeImage = (int *)vm->codeBase; + dataMask = vm->dataMask; + + // leave a free spot at start of stack so + // that as long as opStack is valid, opStack-1 will + // not corrupt anything + opStack = stack; + programCounter = 0; + + programStack -= 48; + + *(int *)&image[ programStack + 44] = args[9]; + *(int *)&image[ programStack + 40] = args[8]; + *(int *)&image[ programStack + 36] = args[7]; + *(int *)&image[ programStack + 32] = args[6]; + *(int *)&image[ programStack + 28] = args[5]; + *(int *)&image[ programStack + 24] = args[4]; + *(int *)&image[ programStack + 20] = args[3]; + *(int *)&image[ programStack + 16] = args[2]; + *(int *)&image[ programStack + 12] = args[1]; + *(int *)&image[ programStack + 8 ] = args[0]; + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + vm->callLevel = 0; + + VM_Debug( 0 ); + +// vm_debugLevel=2; + // main interpreter loop, will exit when a LEAVE instruction + // grabs the -1 program counter + +#define r2 codeImage[programCounter] + + while ( 1 ) { + int opcode, r0, r1; +// unsigned int r2; + +nextInstruction: + r0 = ( (int *)opStack )[0]; + r1 = ( (int *)opStack )[-1]; +nextInstruction2: + opcode = codeImage[ programCounter++ ]; +#ifdef DEBUG_VM + if ( (unsigned)programCounter > vm->codeLength ) { + Com_Error( ERR_DROP, "VM pc out of range" ); + } + + if ( opStack < stack ) { + Com_Error( ERR_DROP, "VM opStack underflow" ); + } + if ( opStack >= stack + MAX_STACK ) { + Com_Error( ERR_DROP, "VM opStack overflow" ); + } + + if ( programStack <= vm->stackBottom ) { + Com_Error( ERR_DROP, "VM stack overflow" ); + } + + if ( programStack & 3 ) { + Com_Error( ERR_DROP, "VM program stack misaligned" ); + } + + if ( vm_debugLevel > 1 ) { + Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] ); + } + profileSymbol->profileCount++; +#endif + + switch ( opcode ) { +#ifdef DEBUG_VM + default: + Com_Error( ERR_DROP, "Bad VM instruction" ); // this should be scanned on load! +#endif + case OP_BREAK: + vm->breakCount++; + goto nextInstruction2; + case OP_CONST: + opStack++; + r1 = r0; + r0 = *opStack = r2; + + programCounter += 4; + goto nextInstruction2; + case OP_LOCAL: + opStack++; + r1 = r0; + r0 = *opStack = r2 + programStack; + + programCounter += 4; + goto nextInstruction2; + + case OP_LOAD4: +#ifdef DEBUG_VM + if ( *opStack & 3 ) { + Com_Error( ERR_DROP, "OP_LOAD4 misaligned" ); + } +#endif + r0 = *opStack = *(int *)&image[ r0 & dataMask ]; + goto nextInstruction2; + case OP_LOAD2: + r0 = *opStack = *(unsigned short *)&image[ r0 & dataMask ]; + goto nextInstruction2; + case OP_LOAD1: + r0 = *opStack = image[ r0 & dataMask ]; + goto nextInstruction2; + + case OP_STORE4: + *(int *)&image[ r1 & ( dataMask & ~3 ) ] = r0; + opStack -= 2; + goto nextInstruction; + case OP_STORE2: + *(short *)&image[ r1 & ( dataMask & ~1 ) ] = r0; + opStack -= 2; + goto nextInstruction; + case OP_STORE1: + image[ r1 & dataMask ] = r0; + opStack -= 2; + goto nextInstruction; + + case OP_ARG: + // single byte offset from programStack + *(int *)&image[ codeImage[programCounter] + programStack ] = r0; + opStack--; + programCounter += 1; + goto nextInstruction; + + case OP_BLOCK_COPY: + { + int *src, *dest; + int i, count, srci, desti; + + count = r2; + // MrE: copy range check + srci = r0 & dataMask; + desti = r1 & dataMask; + count = ( ( srci + count ) & dataMask ) - srci; + count = ( ( desti + count ) & dataMask ) - desti; + + src = (int *)&image[ r0 & dataMask ]; + dest = (int *)&image[ r1 & dataMask ]; + if ( ( (int)src | (int)dest | count ) & 3 ) { + Com_Error( ERR_DROP, "OP_BLOCK_COPY not dword aligned" ); + } + count >>= 2; + for ( i = count - 1 ; i >= 0 ; i-- ) { + dest[i] = src[i]; + } + programCounter += 4; + opStack -= 2; + } + goto nextInstruction; + + case OP_CALL: + // save current program counter + *(int *)&image[ programStack ] = programCounter; + + // jump to the location on the stack + programCounter = r0; + opStack--; + if ( programCounter < 0 ) { + // system call + int r; + int temp; +#ifdef DEBUG_VM + int stomped; + + if ( vm_debugLevel ) { + Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter ); + } +#endif + // save the stack to allow recursive VM entry + temp = vm->callLevel; + vm->programStack = programStack - 4; +#ifdef DEBUG_VM + stomped = *(int *)&image[ programStack + 4 ]; +#endif + *(int *)&image[ programStack + 4 ] = -1 - programCounter; + +//VM_LogSyscalls( (int *)&image[ programStack + 4 ] ); + r = vm->systemCall( (int *)&image[ programStack + 4 ] ); + +#ifdef DEBUG_VM + // this is just our stack frame pointer, only needed + // for debugging + *(int *)&image[ programStack + 4 ] = stomped; +#endif + + // save return value + opStack++; + *opStack = r; + programCounter = *(int *)&image[ programStack ]; + vm->callLevel = temp; +#ifdef DEBUG_VM + if ( vm_debugLevel ) { + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } +#endif + } else { + programCounter = vm->instructionPointers[ programCounter ]; + } + goto nextInstruction; + + // push and pop are only needed for discarded or bad function return values + case OP_PUSH: + opStack++; + goto nextInstruction; + case OP_POP: + opStack--; + goto nextInstruction; + + case OP_ENTER: +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); +#endif + // get size of stack frame + v1 = r2; + + programCounter += 4; + programStack -= v1; +#ifdef DEBUG_VM + // save old stack frame for debugging traces + *(int *)&image[programStack + 4] = programStack + v1; + if ( vm_debugLevel ) { + Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) ); + if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) { + // this is to allow setting breakpoints here in the debugger + vm->breakCount++; +// vm_debugLevel = 2; +// VM_StackTrace( vm, programCounter, programStack ); + } + vm->callLevel++; + } +#endif + goto nextInstruction; + case OP_LEAVE: + // remove our stack frame + v1 = r2; + + programStack += v1; + + // grab the saved program counter + programCounter = *(int *)&image[ programStack ]; +#ifdef DEBUG_VM + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); + if ( vm_debugLevel ) { + vm->callLevel--; + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } +#endif + // check for leaving the VM + if ( programCounter == -1 ) { + goto done; + } + goto nextInstruction; + + /* + =================================================================== + BRANCHES + =================================================================== + */ + + case OP_JUMP: + programCounter = r0; + programCounter = vm->instructionPointers[ programCounter ]; + opStack--; + goto nextInstruction; + + case OP_EQ: + opStack -= 2; + if ( r1 == r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_NE: + opStack -= 2; + if ( r1 != r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_LTI: + opStack -= 2; + if ( r1 < r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_LEI: + opStack -= 2; + if ( r1 <= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_GTI: + opStack -= 2; + if ( r1 > r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_GEI: + opStack -= 2; + if ( r1 >= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_LTU: + opStack -= 2; + if ( ( (unsigned)r1 ) < ( (unsigned)r0 ) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_LEU: + opStack -= 2; + if ( ( (unsigned)r1 ) <= ( (unsigned)r0 ) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_GTU: + opStack -= 2; + if ( ( (unsigned)r1 ) > ( (unsigned)r0 ) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_GEU: + opStack -= 2; + if ( ( (unsigned)r1 ) >= ( (unsigned)r0 ) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_EQF: + if ( ( (float *)opStack )[-1] == *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_NEF: + if ( ( (float *)opStack )[-1] != *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_LTF: + if ( ( (float *)opStack )[-1] < *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_LEF: + if ( ( (float *)opStack )[-1] <= *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_GTF: + if ( ( (float *)opStack )[-1] > *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_GEF: + if ( ( (float *)opStack )[-1] >= *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + + //=================================================================== + + case OP_NEGI: + *opStack = -r0; + goto nextInstruction; + case OP_ADD: + opStack[-1] = r1 + r0; + opStack--; + goto nextInstruction; + case OP_SUB: + opStack[-1] = r1 - r0; + opStack--; + goto nextInstruction; + case OP_DIVI: + opStack[-1] = r1 / r0; + opStack--; + goto nextInstruction; + case OP_DIVU: + opStack[-1] = ( (unsigned)r1 ) / ( (unsigned)r0 ); + opStack--; + goto nextInstruction; + case OP_MODI: + opStack[-1] = r1 % r0; + opStack--; + goto nextInstruction; + case OP_MODU: + opStack[-1] = ( (unsigned)r1 ) % (unsigned)r0; + opStack--; + goto nextInstruction; + case OP_MULI: + opStack[-1] = r1 * r0; + opStack--; + goto nextInstruction; + case OP_MULU: + opStack[-1] = ( (unsigned)r1 ) * ( (unsigned)r0 ); + opStack--; + goto nextInstruction; + + case OP_BAND: + opStack[-1] = ( (unsigned)r1 ) & ( (unsigned)r0 ); + opStack--; + goto nextInstruction; + case OP_BOR: + opStack[-1] = ( (unsigned)r1 ) | ( (unsigned)r0 ); + opStack--; + goto nextInstruction; + case OP_BXOR: + opStack[-1] = ( (unsigned)r1 ) ^ ( (unsigned)r0 ); + opStack--; + goto nextInstruction; + case OP_BCOM: + opStack[-1] = ~( (unsigned)r0 ); + goto nextInstruction; + + case OP_LSH: + opStack[-1] = r1 << r0; + opStack--; + goto nextInstruction; + case OP_RSHI: + opStack[-1] = r1 >> r0; + opStack--; + goto nextInstruction; + case OP_RSHU: + opStack[-1] = ( (unsigned)r1 ) >> r0; + opStack--; + goto nextInstruction; + + case OP_NEGF: + *(float *)opStack = -*(float *)opStack; + goto nextInstruction; + case OP_ADDF: + *( float * )( opStack - 1 ) = *( float * )( opStack - 1 ) + *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_SUBF: + *( float * )( opStack - 1 ) = *( float * )( opStack - 1 ) - *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_DIVF: + *( float * )( opStack - 1 ) = *( float * )( opStack - 1 ) / *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_MULF: + *( float * )( opStack - 1 ) = *( float * )( opStack - 1 ) * *(float *)opStack; + opStack--; + goto nextInstruction; + + case OP_CVIF: + *(float *)opStack = (float)*opStack; + goto nextInstruction; + case OP_CVFI: + *opStack = (int) *(float *)opStack; + goto nextInstruction; + case OP_SEX8: + *opStack = (signed char)*opStack; + goto nextInstruction; + case OP_SEX16: + *opStack = (short)*opStack; + goto nextInstruction; + } + } + +done: + vm->currentlyInterpreting = qfalse; + + if ( opStack != &stack[1] ) { + Com_Error( ERR_DROP, "Interpreter error: opStack = %i", opStack - stack ); + } + + vm->programStack = stackOnEntry; + + // return the result + return *opStack; +} diff --git a/src/qcommon/vm_local.h b/src/qcommon/vm_local.h new file mode 100644 index 0000000..12faf72 --- /dev/null +++ b/src/qcommon/vm_local.h @@ -0,0 +1,187 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../game/q_shared.h" +#include "qcommon.h" + +typedef enum { + OP_UNDEF, + + OP_IGNORE, + + OP_BREAK, + + OP_ENTER, + OP_LEAVE, + OP_CALL, + OP_PUSH, + OP_POP, + + OP_CONST, + OP_LOCAL, + + OP_JUMP, + + //------------------- + + OP_EQ, + OP_NE, + + OP_LTI, + OP_LEI, + OP_GTI, + OP_GEI, + + OP_LTU, + OP_LEU, + OP_GTU, + OP_GEU, + + OP_EQF, + OP_NEF, + + OP_LTF, + OP_LEF, + OP_GTF, + OP_GEF, + + //------------------- + + OP_LOAD1, + OP_LOAD2, + OP_LOAD4, + OP_STORE1, + OP_STORE2, + OP_STORE4, // *(stack[top-1]) = stack[top] + OP_ARG, + + OP_BLOCK_COPY, + + //------------------- + + OP_SEX8, + OP_SEX16, + + OP_NEGI, + OP_ADD, + OP_SUB, + OP_DIVI, + OP_DIVU, + OP_MODI, + OP_MODU, + OP_MULI, + OP_MULU, + + OP_BAND, + OP_BOR, + OP_BXOR, + OP_BCOM, + + OP_LSH, + OP_RSHI, + OP_RSHU, + + OP_NEGF, + OP_ADDF, + OP_SUBF, + OP_DIVF, + OP_MULF, + + OP_CVIF, + OP_CVFI +} opcode_t; + + + +typedef int vmptr_t; + +typedef struct vmSymbol_s { + struct vmSymbol_s *next; + int symValue; + int profileCount; + char symName[1]; // variable sized +} vmSymbol_t; + +#define VM_OFFSET_PROGRAM_STACK 0 +#define VM_OFFSET_SYSTEM_CALL 4 + +struct vm_s { + // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES + // USED BY THE ASM CODE + int programStack; // the vm may be recursively entered + int ( *systemCall )( int *parms ); + + //------------------------------------ + + char name[MAX_QPATH]; + +// fqpath member added 2/15/02 by T.Ray + char fqpath[MAX_QPATH + 1] ; + + // for dynamic linked modules + void *dllHandle; + int ( QDECL *entryPoint )( int callNum, ... ); + + // for interpreted modules + qboolean currentlyInterpreting; + + qboolean compiled; + byte *codeBase; + int codeLength; + + int *instructionPointers; + int instructionPointersLength; + + byte *dataBase; + int dataMask; + + int stackBottom; // if programStack < stackBottom, error + + int numSymbols; + struct vmSymbol_s *symbols; + + int callLevel; // for debug indenting + int breakFunction; // increment breakCount on function entry to this + int breakCount; +}; + + +extern vm_t *currentVM; +extern int vm_debugLevel; + +void VM_Compile( vm_t *vm, vmHeader_t *header ); +int VM_CallCompiled( vm_t *vm, int *args ); + +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ); +int VM_CallInterpreted( vm_t *vm, int *args ); + +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ); +int VM_SymbolToValue( vm_t *vm, const char *symbol ); +const char *VM_ValueToSymbol( vm_t *vm, int value ); +void VM_LogSyscalls( int *args ); + diff --git a/src/qcommon/vm_x86.c b/src/qcommon/vm_x86.c new file mode 100644 index 0000000..a5e841f --- /dev/null +++ b/src/qcommon/vm_x86.c @@ -0,0 +1,826 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// vm_x86.c -- load time compiler and execution environment for x86 + +#include "vm_local.h" + +#ifndef _WIN32 +#include // for PROT_ stuff +#endif + +/* + + eax scratch + ebx scratch + ecx scratch (required for shifts) + edx scratch (required for divisions) + esi program stack + edi opstack + +*/ + +static byte *buf; +static int compiledOfs; +static byte *code; +static int pc; + +static int *instructionPointers; + +#ifdef _WIN32 +void AsmCall( void ); +int _ftol( float ); + +//static int ftolPtr = (int)_ftol; +static int asmCallPtr = (int)AsmCall; + +#else + +void doAsmCall( void ); + +static int asmCallPtr = (int)doAsmCall; +#endif + + +/* +================= +AsmCall +================= +*/ +#ifdef _WIN32 +__declspec( naked ) void AsmCall( void ) { + static int programStack; + static int *opStack; + static int syscallNum; + + __asm { + mov eax, dword ptr [edi] + sub edi, 4 + or eax,eax + jl systemCall + // calling another vm function + shl eax,2 + add eax, dword ptr [instructionPointers] + call dword ptr [eax] + ret +systemCall: + + // convert negative num to system call number + // and store right before the first arg + neg eax + dec eax + + mov dword ptr syscallNum, eax // so C code can get at it + mov dword ptr programStack, esi // so C code can get at it + mov dword ptr opStack, edi + + push ecx + push esi // we may call recursively, so the + push edi // statics aren't guaranteed to be around + } + + // save the stack to allow recursive VM entry + currentVM->programStack = programStack - 4; + *( int * )( (byte *)currentVM->dataBase + programStack + 4 ) = syscallNum; +//VM_LogSyscalls( (int *)((byte *)currentVM->dataBase + programStack + 4) ); + *( opStack + 1 ) = currentVM->systemCall( ( int * )( (byte *)currentVM->dataBase + programStack + 4 ) ); + + _asm { + pop edi + pop esi + pop ecx + add edi, 4 // we added the return value + + ret + } + +} + +#else + +static int callProgramStack; +static int *callOpStack; +static int callSyscallNum; + +void callAsmCall( void ) { + // save the stack to allow recursive VM entry + currentVM->programStack = callProgramStack - 4; + *( int * )( (byte *)currentVM->dataBase + callProgramStack + 4 ) = callSyscallNum; +//VM_LogSyscalls( (int *)((byte *)currentVM->dataBase + programStack + 4) ); + *( callOpStack + 1 ) = currentVM->systemCall( ( int * )( (byte *)currentVM->dataBase + callProgramStack + 4 ) ); +} + +void AsmCall( void ) { + __asm__( "doAsmCall: \n\t"\ + " movl (%%edi),%%eax \n\t"\ + " subl $4,%%edi \n\t"\ + " orl %%eax,%%eax \n\t"\ + " jl systemCall \n\t"\ + " shll $2,%%eax \n\t"\ + " addl %3,%%eax \n\t"\ + " call *(%%eax) \n\t"\ + " jmp doret \n\t"\ + "systemCall: \n\t"\ + " negl %%eax \n\t"\ + " decl %%eax \n\t"\ + " movl %%eax,%0 \n\t"\ + " movl %%esi,%1 \n\t"\ + " movl %%edi,%2 \n\t"\ + " pushl %%ecx \n\t"\ + " pushl %%esi \n\t"\ + " pushl %%edi \n\t"\ + " call callAsmCall \n\t"\ + " popl %%edi \n\t"\ + " popl %%esi \n\t"\ + " popl %%ecx \n\t"\ + " addl $4,%%edi \n\t"\ + "doret: \n\t"\ + " ret \n\t"\ + : "=rm" ( callSyscallNum ), "=rm" ( callProgramStack ), "=rm" ( callOpStack ) \ + : "rm" ( instructionPointers ) \ + : "ax", "di", "si", "cx" \ + ); +} +#endif + + +static int Constant4( void ) { + int v; + + v = code[pc] | ( code[pc + 1] << 8 ) | ( code[pc + 2] << 16 ) | ( code[pc + 3] << 24 ); + pc += 4; + return v; +} + +static int Constant1( void ) { + int v; + + v = code[pc]; + pc += 1; + return v; +} + +static void Emit1( int v ) { + buf[ compiledOfs ] = v; + compiledOfs++; +} +#if 0 +static void Emit2( int v ) { + Emit1( v & 255 ); + Emit1( ( v >> 8 ) & 255 ); +} +#endif +static void Emit4( int v ) { + Emit1( v & 255 ); + Emit1( ( v >> 8 ) & 255 ); + Emit1( ( v >> 16 ) & 255 ); + Emit1( ( v >> 24 ) & 255 ); +} + +static int Hex( int c ) { + if ( c >= 'a' && c <= 'f' ) { + return 10 + c - 'a'; + } + if ( c >= 'A' && c <= 'F' ) { + return 10 + c - 'A'; + } + if ( c >= '0' && c <= '9' ) { + return c - '0'; + } + + Com_Error( ERR_DROP, "Hex: bad char '%c'", c ); + + return 0; +} +static void EmitString( const char *string ) { + int c1, c2; + int v; + + while ( 1 ) { + c1 = string[0]; + c2 = string[1]; + + v = ( Hex( c1 ) << 4 ) | Hex( c2 ); + Emit1( v ); + + if ( !string[2] ) { + break; + } + string += 3; + } +} + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { + int op; + int maxLength; + int v; + int i; + int instruction; + int lastConst; + + // allocate a very large temp buffer, we will shrink it later + maxLength = header->codeLength * 8; + buf = Z_Malloc( maxLength ); + + // translate all instructions + pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + compiledOfs = 0; + + while ( instruction < header->instructionCount ) { + if ( compiledOfs > maxLength - 16 ) { + Com_Error( ERR_FATAL, "VM_CompileX86: maxLength exceeded" ); + } + + vm->instructionPointers[ instruction ] = compiledOfs; + instruction++; + + op = code[ pc ]; + if ( pc > header->codeLength ) { + Com_Error( ERR_FATAL, "VM_CompileX86: pc > header->codeLength" ); + } + + pc++; + switch ( op ) { + case 0: + break; + case OP_BREAK: + EmitString( "CC" ); // int 3 + break; + case OP_ENTER: + EmitString( "81 EE" ); // sub esi, 0x12345678 + Emit4( Constant4() ); + break; + case OP_CONST: + EmitString( "83 C7 04" ); // add edi,4 + EmitString( "C7 07" ); // mov dword ptr [edi], 0x12345678 + lastConst = Constant4(); + Emit4( lastConst ); + break; + case OP_LOCAL: + EmitString( "83 C7 04" ); // add edi,4 + EmitString( "8D 86" ); // lea eax, [0x12345678 + esi] + Emit4( Constant4() ); + EmitString( "89 07" ); // mov dword ptr [edi], eax + break; + case OP_ARG: + EmitString( "8B 07" ); // mov eax,dword ptr [edi] + EmitString( "89 86" ); // mov dword ptr [esi+database],eax + // FIXME: range check + Emit4( Constant1() + (int)vm->dataBase ); + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_CALL: + EmitString( "C7 86" ); // mov dword ptr [esi+database],0x12345678 + Emit4( (int)vm->dataBase ); + Emit4( pc ); + EmitString( "FF 15" ); // call asmCallPtr + Emit4( (int)&asmCallPtr ); + break; + case OP_PUSH: + EmitString( "83 C7 04" ); // add edi,4 + break; + case OP_POP: + EmitString( "83 EF 04" ); // sub edi, 4 + break; + case OP_LEAVE: + v = Constant4(); + EmitString( "81 C6" ); // add esi, 0x12345678 + Emit4( v ); + EmitString( "C3" ); // ret + break; + case OP_LOAD4: + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + EmitString( "81 E3" ); // and ebx, 0x12345678 + Emit4( vm->dataMask ); + EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitString( "89 07" ); // mov dword ptr [edi], eax + break; + case OP_LOAD2: + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + EmitString( "81 E3" ); // and ebx, 0x12345678 + Emit4( vm->dataMask ); + EmitString( "0F B7 83" ); // movzx eax, word ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitString( "89 07" ); // mov dword ptr [edi], eax + break; + case OP_LOAD1: + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + EmitString( "81 E3" ); // and ebx, 0x12345678 + Emit4( vm->dataMask ); + EmitString( "0F B6 83" ); // movzx eax, byte ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitString( "89 07" ); // mov dword ptr [edi], eax + break; + case OP_STORE4: + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] + EmitString( "81 E3" ); // and ebx, 0x12345678 + Emit4( vm->dataMask & ~3 ); + EmitString( "8B 07" ); // mov eax, dword ptr [edi]; + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitString( "83 EF 08" ); // sub edi, 8 + break; + case OP_STORE2: + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] + EmitString( "81 E3" ); // and ebx, 0x12345678 + Emit4( vm->dataMask & ~1 ); + EmitString( "8B 07" ); // mov eax, dword ptr [edi]; + EmitString( "66 89 83" ); // mov word ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitString( "83 EF 08" ); // sub edi, 8 + break; + case OP_STORE1: + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] + EmitString( "81 E3" ); // and ebx, 0x12345678 + Emit4( vm->dataMask ); + EmitString( "8B 07" ); // mov eax, dword ptr [edi]; + EmitString( "88 83" ); // mov byte ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitString( "83 EF 08" ); // sub edi, 8 + break; + + case OP_EQ: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_NE: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_LTI: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7D 06" ); // jnl +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_LEI: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7F 06" ); // jnle +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_GTI: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7E 06" ); // jng +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_GEI: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7C 06" ); // jnge +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_LTU: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "73 06" ); // jnb +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_LEU: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "77 06" ); // jnbe +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_GTU: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "76 06" ); // jna +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_GEU: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "72 06" ); // jnae +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_EQF: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 40" ); // test ah,0x40 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_NEF: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 40" ); // test ah,0x40 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_LTF: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 01" ); // test ah,0x01 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_LEF: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 41" ); // test ah,0x41 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_GTF: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 41" ); // test ah,0x41 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_GEF: + EmitString( "83 EF 08" ); // sub edi,8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 01" ); // test ah,0x01 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + Emit4( (int)vm->instructionPointers + Constant4() * 4 ); + break; + case OP_NEGI: + EmitString( "F7 1F" ); // neg dword ptr [edi] + break; + case OP_ADD: + EmitString( "8B 07" ); // mov eax, dword ptr [edi] + EmitString( "01 47 FC" ); // add dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_SUB: + EmitString( "8B 07" ); // mov eax, dword ptr [edi] + EmitString( "29 47 FC" ); // sub dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_DIVI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "99" ); // cdq + EmitString( "F7 3F" ); // idiv dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_DIVU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "33 D2" ); // xor edx, edx + EmitString( "F7 37" ); // div dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_MODI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "99" ); // cdq + EmitString( "F7 3F" ); // idiv dword ptr [edi] + EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_MODU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "33 D2" ); // xor edx, edx + EmitString( "F7 37" ); // div dword ptr [edi] + EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_MULI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "F7 2F" ); // imul dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_MULU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "F7 27" ); // mul dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_BAND: + EmitString( "8B 07" ); // mov eax, dword ptr [edi] + EmitString( "21 47 FC" ); // and dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_BOR: + EmitString( "8B 07" ); // mov eax, dword ptr [edi] + EmitString( "09 47 FC" ); // or dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_BXOR: + EmitString( "8B 07" ); // mov eax, dword ptr [edi] + EmitString( "31 47 FC" ); // xor dword ptr [edi-4],eax + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_BCOM: + EmitString( "F7 17" ); // not dword ptr [edi] + break; + case OP_LSH: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 67 FC" ); // shl dword ptr [edi-4], cl + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_RSHI: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 7F FC" ); // sar dword ptr [edi-4], cl + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_RSHU: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 6F FC" ); // shr dword ptr [edi-4], cl + EmitString( "83 EF 04" ); // sub edi,4 + break; + case OP_NEGF: + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D9 E0" ); // fchs + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_ADDF: + EmitString( "83 EF 04" ); // sub edi,4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 47 04" ); // fadd dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_SUBF: + EmitString( "83 EF 04" ); // sub edi,4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 67 04" ); // fsub dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_DIVF: + EmitString( "83 EF 04" ); // sub edi,4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 77 04" ); // fdiv dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_MULF: + EmitString( "83 EF 04" ); // sub edi,4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 4f 04" ); // fmul dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_CVIF: + EmitString( "DB 07" ); // fild dword ptr [edi] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_CVFI: + EmitString( "D9 07" ); // fld dword ptr [edi] +#if 1 + // not IEEE complient, but simple and fast + EmitString( "DB 1F" ); // fistp dword ptr [edi] +#else + // call the library conversion function + EmitString( "FF 15" ); // call ftolPtr + Emit4( (int)&ftolPtr ); + EmitString( "89 07" ); // mov dword ptr [edi], eax +#endif + break; + case OP_SEX8: + EmitString( "0F BE 07" ); // movsx eax, byte ptr [edi] + EmitString( "89 07" ); // mov dword ptr [edi], eax + break; + case OP_SEX16: + EmitString( "0F BF 07" ); // movsx eax, word ptr [edi] + EmitString( "89 07" ); // mov dword ptr [edi], eax + break; + + case OP_BLOCK_COPY: + // FIXME: range check + EmitString( "56" ); // push esi + EmitString( "57" ); // push edi + EmitString( "8B 37" ); // mov esi,[edi] + EmitString( "8B 7F FC" ); // mov edi,[edi-4] + EmitString( "B9" ); // mov ecx,0x12345678 + Emit4( Constant4() >> 2 ); + EmitString( "B8" ); // mov eax, datamask + Emit4( vm->dataMask ); + EmitString( "BB" ); // mov ebx, database + Emit4( (int)vm->dataBase ); + EmitString( "23 F0" ); // and esi, eax + EmitString( "03 F3" ); // add esi, ebx + EmitString( "23 F8" ); // and edi, eax + EmitString( "03 FB" ); // add edi, ebx + EmitString( "F3 A5" ); // rep movsd + EmitString( "5F" ); // pop edi + EmitString( "5E" ); // pop esi + EmitString( "83 EF 08" ); // sub edi,8 + break; + + case OP_JUMP: + EmitString( "83 EF 04" ); // sub edi,4 + EmitString( "8B 47 04" ); // mov eax,dword ptr [edi+4] + // FIXME: range check + EmitString( "FF 24 85" ); // jmp dword ptr [instructionPointers + eax * 4] + Emit4( (int)vm->instructionPointers ); + break; + default: + Com_Error( ERR_DROP, "VM_CompileX86: bad opcode %i at offset %i", op, pc ); + } + + } + + // copy to an exact size buffer on the hunk + vm->codeLength = compiledOfs; + vm->codeBase = Hunk_Alloc( compiledOfs, h_low ); + memcpy( vm->codeBase, buf, compiledOfs ); + Z_Free( buf ); + Com_Printf( "VM file %s compiled to %i bytes of code\n", vm->name, compiledOfs ); + + // offset all the instruction pointers for the new location + for ( i = 0 ; i < header->instructionCount ; i++ ) { + vm->instructionPointers[i] += (int)vm->codeBase; + } + +#if 0 // ndef _WIN32 + // Must make the newly generated code executable + { + int r; + unsigned long addr; + int psize = getpagesize(); + + addr = ( (int)vm->codeBase & ~( psize - 1 ) ) - psize; + + r = mprotect( (char*)addr, vm->codeLength + (int)vm->codeBase - addr + psize, + PROT_READ | PROT_WRITE | PROT_EXEC ); + + if ( r < 0 ) { + Com_Error( ERR_FATAL, "mprotect failed to change PROT_EXEC" ); + } + } +#endif + +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ +int VM_CallCompiled( vm_t *vm, int *args ) { + int stack[1024]; + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + void *entryPoint; + void *opStack; + int *oldInstructionPointers; + + oldInstructionPointers = instructionPointers; + + currentVM = vm; + instructionPointers = vm->instructionPointers; + + // interpret the code + vm->currentlyInterpreting = qtrue; + + // we might be called recursively, so this might not be the very top + programStack = vm->programStack; + stackOnEntry = programStack; + + // set up the stack frame + image = vm->dataBase; + + programCounter = 0; + + programStack -= 48; + + *(int *)&image[ programStack + 44] = args[9]; + *(int *)&image[ programStack + 40] = args[8]; + *(int *)&image[ programStack + 36] = args[7]; + *(int *)&image[ programStack + 32] = args[6]; + *(int *)&image[ programStack + 28] = args[5]; + *(int *)&image[ programStack + 24] = args[4]; + *(int *)&image[ programStack + 20] = args[3]; + *(int *)&image[ programStack + 16] = args[2]; + *(int *)&image[ programStack + 12] = args[1]; + *(int *)&image[ programStack + 8 ] = args[0]; + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + // off we go into generated code... + entryPoint = vm->codeBase; + opStack = &stack; + +#ifdef _WIN32 + __asm { + pushad + mov esi, programStack; + mov edi, opStack + call entryPoint + mov programStack, esi + mov opStack, edi + popad + } +#else + { + static int memProgramStack; + static void *memOpStack; + static void *memEntryPoint; + + memProgramStack = programStack; + memOpStack = opStack; + memEntryPoint = entryPoint; + + __asm__( " pushal \r\n"\ + " movl %0,%%esi \r\n"\ + " movl %1,%%edi \r\n"\ + " call *%2 \r\n"\ + " movl %%esi,%0 \r\n"\ + " movl %%edi,%1 \r\n"\ + " popal \r\n"\ + : "=m" ( memProgramStack ), "=m" ( memOpStack ) \ + : "m" ( memEntryPoint ), "0" ( memProgramStack ), "1" ( memOpStack ) \ + : "si", "di" \ + ); + + programStack = memProgramStack; + opStack = memOpStack; + } +#endif + + if ( opStack != &stack[1] ) { + Com_Error( ERR_DROP, "opStack corrupted in compiled code" ); + } + if ( programStack != stackOnEntry - 48 ) { + Com_Error( ERR_DROP, "programStack corrupted in compiled code" ); + } + + vm->programStack = stackOnEntry; + + // in case we were recursively called by another vm + instructionPointers = oldInstructionPointers; + + return *(int *)opStack; +} + diff --git a/src/renderer/anorms256.h b/src/renderer/anorms256.h new file mode 100644 index 0000000..93bd7ed --- /dev/null +++ b/src/renderer/anorms256.h @@ -0,0 +1,284 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +{1.000000, 0.000000, 0.000000}, +{0.980785, 0.195090, 0.000000}, +{0.923880, 0.382683, 0.000000}, +{0.831470, 0.555570, 0.000000}, +{0.707107, 0.707107, 0.000000}, +{0.555570, 0.831470, 0.000000}, +{0.382683, 0.923880, 0.000000}, +{0.195090, 0.980785, 0.000000}, +{-0.000000, 1.000000, 0.000000}, +{-0.195090, 0.980785, 0.000000}, +{-0.382683, 0.923880, 0.000000}, +{-0.555570, 0.831470, 0.000000}, +{-0.707107, 0.707107, 0.000000}, +{-0.831470, 0.555570, 0.000000}, +{-0.923880, 0.382683, 0.000000}, +{-0.980785, 0.195090, 0.000000}, +{-1.000000, -0.000000, 0.000000}, +{-0.980785, -0.195090, 0.000000}, +{-0.923880, -0.382683, 0.000000}, +{-0.831470, -0.555570, 0.000000}, +{-0.707107, -0.707107, 0.000000}, +{-0.555570, -0.831469, 0.000000}, +{-0.382684, -0.923880, 0.000000}, +{-0.195090, -0.980785, 0.000000}, +{0.000000, -1.000000, 0.000000}, +{0.195090, -0.980785, 0.000000}, +{0.382684, -0.923879, 0.000000}, +{0.555570, -0.831470, 0.000000}, +{0.707107, -0.707107, 0.000000}, +{0.831470, -0.555570, 0.000000}, +{0.923880, -0.382683, 0.000000}, +{0.980785, -0.195090, 0.000000}, +{0.980785, 0.000000, -0.195090}, +{0.956195, 0.218245, -0.195090}, +{0.883657, 0.425547, -0.195090}, +{0.766809, 0.611510, -0.195090}, +{0.611510, 0.766809, -0.195090}, +{0.425547, 0.883657, -0.195090}, +{0.218245, 0.956195, -0.195090}, +{-0.000000, 0.980785, -0.195090}, +{-0.218245, 0.956195, -0.195090}, +{-0.425547, 0.883657, -0.195090}, +{-0.611510, 0.766809, -0.195090}, +{-0.766809, 0.611510, -0.195090}, +{-0.883657, 0.425547, -0.195090}, +{-0.956195, 0.218245, -0.195090}, +{-0.980785, -0.000000, -0.195090}, +{-0.956195, -0.218245, -0.195090}, +{-0.883657, -0.425547, -0.195090}, +{-0.766809, -0.611510, -0.195090}, +{-0.611510, -0.766809, -0.195090}, +{-0.425547, -0.883657, -0.195090}, +{-0.218245, -0.956195, -0.195090}, +{0.000000, -0.980785, -0.195090}, +{0.218245, -0.956195, -0.195090}, +{0.425547, -0.883657, -0.195090}, +{0.611510, -0.766809, -0.195090}, +{0.766809, -0.611510, -0.195090}, +{0.883657, -0.425547, -0.195090}, +{0.956195, -0.218245, -0.195090}, +{0.923880, 0.000000, -0.382683}, +{0.892399, 0.239118, -0.382683}, +{0.800103, 0.461940, -0.382683}, +{0.653281, 0.653281, -0.382683}, +{0.461940, 0.800103, -0.382683}, +{0.239118, 0.892399, -0.382683}, +{-0.000000, 0.923880, -0.382683}, +{-0.239118, 0.892399, -0.382683}, +{-0.461940, 0.800103, -0.382683}, +{-0.653281, 0.653281, -0.382683}, +{-0.800103, 0.461940, -0.382683}, +{-0.892399, 0.239118, -0.382683}, +{-0.923880, -0.000000, -0.382683}, +{-0.892399, -0.239118, -0.382683}, +{-0.800103, -0.461940, -0.382683}, +{-0.653282, -0.653281, -0.382683}, +{-0.461940, -0.800103, -0.382683}, +{-0.239118, -0.892399, -0.382683}, +{0.000000, -0.923880, -0.382683}, +{0.239118, -0.892399, -0.382683}, +{0.461940, -0.800103, -0.382683}, +{0.653281, -0.653282, -0.382683}, +{0.800103, -0.461940, -0.382683}, +{0.892399, -0.239117, -0.382683}, +{0.831470, 0.000000, -0.555570}, +{0.790775, 0.256938, -0.555570}, +{0.672673, 0.488726, -0.555570}, +{0.488726, 0.672673, -0.555570}, +{0.256938, 0.790775, -0.555570}, +{-0.000000, 0.831470, -0.555570}, +{-0.256938, 0.790775, -0.555570}, +{-0.488726, 0.672673, -0.555570}, +{-0.672673, 0.488726, -0.555570}, +{-0.790775, 0.256938, -0.555570}, +{-0.831470, -0.000000, -0.555570}, +{-0.790775, -0.256938, -0.555570}, +{-0.672673, -0.488726, -0.555570}, +{-0.488725, -0.672673, -0.555570}, +{-0.256938, -0.790775, -0.555570}, +{0.000000, -0.831470, -0.555570}, +{0.256938, -0.790775, -0.555570}, +{0.488725, -0.672673, -0.555570}, +{0.672673, -0.488726, -0.555570}, +{0.790775, -0.256938, -0.555570}, +{0.707107, 0.000000, -0.707107}, +{0.653281, 0.270598, -0.707107}, +{0.500000, 0.500000, -0.707107}, +{0.270598, 0.653281, -0.707107}, +{-0.000000, 0.707107, -0.707107}, +{-0.270598, 0.653282, -0.707107}, +{-0.500000, 0.500000, -0.707107}, +{-0.653281, 0.270598, -0.707107}, +{-0.707107, -0.000000, -0.707107}, +{-0.653281, -0.270598, -0.707107}, +{-0.500000, -0.500000, -0.707107}, +{-0.270598, -0.653281, -0.707107}, +{0.000000, -0.707107, -0.707107}, +{0.270598, -0.653281, -0.707107}, +{0.500000, -0.500000, -0.707107}, +{0.653282, -0.270598, -0.707107}, +{0.555570, 0.000000, -0.831470}, +{0.481138, 0.277785, -0.831470}, +{0.277785, 0.481138, -0.831470}, +{-0.000000, 0.555570, -0.831470}, +{-0.277785, 0.481138, -0.831470}, +{-0.481138, 0.277785, -0.831470}, +{-0.555570, -0.000000, -0.831470}, +{-0.481138, -0.277785, -0.831470}, +{-0.277785, -0.481138, -0.831470}, +{0.000000, -0.555570, -0.831470}, +{0.277785, -0.481138, -0.831470}, +{0.481138, -0.277785, -0.831470}, +{0.382683, 0.000000, -0.923880}, +{0.270598, 0.270598, -0.923880}, +{-0.000000, 0.382683, -0.923880}, +{-0.270598, 0.270598, -0.923880}, +{-0.382683, -0.000000, -0.923880}, +{-0.270598, -0.270598, -0.923880}, +{0.000000, -0.382683, -0.923880}, +{0.270598, -0.270598, -0.923880}, +{0.195090, 0.000000, -0.980785}, +{-0.000000, 0.195090, -0.980785}, +{-0.195090, -0.000000, -0.980785}, +{0.000000, -0.195090, -0.980785}, +{0.980785, 0.000000, 0.195090}, +{0.956195, 0.218245, 0.195090}, +{0.883657, 0.425547, 0.195090}, +{0.766809, 0.611510, 0.195090}, +{0.611510, 0.766809, 0.195090}, +{0.425547, 0.883657, 0.195090}, +{0.218245, 0.956195, 0.195090}, +{-0.000000, 0.980785, 0.195090}, +{-0.218245, 0.956195, 0.195090}, +{-0.425547, 0.883657, 0.195090}, +{-0.611510, 0.766809, 0.195090}, +{-0.766809, 0.611510, 0.195090}, +{-0.883657, 0.425547, 0.195090}, +{-0.956195, 0.218245, 0.195090}, +{-0.980785, -0.000000, 0.195090}, +{-0.956195, -0.218245, 0.195090}, +{-0.883657, -0.425547, 0.195090}, +{-0.766809, -0.611510, 0.195090}, +{-0.611510, -0.766809, 0.195090}, +{-0.425547, -0.883657, 0.195090}, +{-0.218245, -0.956195, 0.195090}, +{0.000000, -0.980785, 0.195090}, +{0.218245, -0.956195, 0.195090}, +{0.425547, -0.883657, 0.195090}, +{0.611510, -0.766809, 0.195090}, +{0.766809, -0.611510, 0.195090}, +{0.883657, -0.425547, 0.195090}, +{0.956195, -0.218245, 0.195090}, +{0.923880, 0.000000, 0.382683}, +{0.892399, 0.239118, 0.382683}, +{0.800103, 0.461940, 0.382683}, +{0.653281, 0.653281, 0.382683}, +{0.461940, 0.800103, 0.382683}, +{0.239118, 0.892399, 0.382683}, +{-0.000000, 0.923880, 0.382683}, +{-0.239118, 0.892399, 0.382683}, +{-0.461940, 0.800103, 0.382683}, +{-0.653281, 0.653281, 0.382683}, +{-0.800103, 0.461940, 0.382683}, +{-0.892399, 0.239118, 0.382683}, +{-0.923880, -0.000000, 0.382683}, +{-0.892399, -0.239118, 0.382683}, +{-0.800103, -0.461940, 0.382683}, +{-0.653282, -0.653281, 0.382683}, +{-0.461940, -0.800103, 0.382683}, +{-0.239118, -0.892399, 0.382683}, +{0.000000, -0.923880, 0.382683}, +{0.239118, -0.892399, 0.382683}, +{0.461940, -0.800103, 0.382683}, +{0.653281, -0.653282, 0.382683}, +{0.800103, -0.461940, 0.382683}, +{0.892399, -0.239117, 0.382683}, +{0.831470, 0.000000, 0.555570}, +{0.790775, 0.256938, 0.555570}, +{0.672673, 0.488726, 0.555570}, +{0.488726, 0.672673, 0.555570}, +{0.256938, 0.790775, 0.555570}, +{-0.000000, 0.831470, 0.555570}, +{-0.256938, 0.790775, 0.555570}, +{-0.488726, 0.672673, 0.555570}, +{-0.672673, 0.488726, 0.555570}, +{-0.790775, 0.256938, 0.555570}, +{-0.831470, -0.000000, 0.555570}, +{-0.790775, -0.256938, 0.555570}, +{-0.672673, -0.488726, 0.555570}, +{-0.488725, -0.672673, 0.555570}, +{-0.256938, -0.790775, 0.555570}, +{0.000000, -0.831470, 0.555570}, +{0.256938, -0.790775, 0.555570}, +{0.488725, -0.672673, 0.555570}, +{0.672673, -0.488726, 0.555570}, +{0.790775, -0.256938, 0.555570}, +{0.707107, 0.000000, 0.707107}, +{0.653281, 0.270598, 0.707107}, +{0.500000, 0.500000, 0.707107}, +{0.270598, 0.653281, 0.707107}, +{-0.000000, 0.707107, 0.707107}, +{-0.270598, 0.653282, 0.707107}, +{-0.500000, 0.500000, 0.707107}, +{-0.653281, 0.270598, 0.707107}, +{-0.707107, -0.000000, 0.707107}, +{-0.653281, -0.270598, 0.707107}, +{-0.500000, -0.500000, 0.707107}, +{-0.270598, -0.653281, 0.707107}, +{0.000000, -0.707107, 0.707107}, +{0.270598, -0.653281, 0.707107}, +{0.500000, -0.500000, 0.707107}, +{0.653282, -0.270598, 0.707107}, +{0.555570, 0.000000, 0.831470}, +{0.481138, 0.277785, 0.831470}, +{0.277785, 0.481138, 0.831470}, +{-0.000000, 0.555570, 0.831470}, +{-0.277785, 0.481138, 0.831470}, +{-0.481138, 0.277785, 0.831470}, +{-0.555570, -0.000000, 0.831470}, +{-0.481138, -0.277785, 0.831470}, +{-0.277785, -0.481138, 0.831470}, +{0.000000, -0.555570, 0.831470}, +{0.277785, -0.481138, 0.831470}, +{0.481138, -0.277785, 0.831470}, +{0.382683, 0.000000, 0.923880}, +{0.270598, 0.270598, 0.923880}, +{-0.000000, 0.382683, 0.923880}, +{-0.270598, 0.270598, 0.923880}, +{-0.382683, -0.000000, 0.923880}, +{-0.270598, -0.270598, 0.923880}, +{0.000000, -0.382683, 0.923880}, +{0.270598, -0.270598, 0.923880}, +{0.195090, 0.000000, 0.980785}, +{-0.000000, 0.195090, 0.980785}, +{-0.195090, -0.000000, 0.980785}, +{0.000000, -0.195090, 0.980785}, diff --git a/src/renderer/qgl.h b/src/renderer/qgl.h new file mode 100644 index 0000000..6947a11 --- /dev/null +++ b/src/renderer/qgl.h @@ -0,0 +1,625 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#if defined( __LINT__ ) + +#include + +#elif defined( _WIN32 ) + +#pragma warning (disable: 4201) +#pragma warning (disable: 4214) +#pragma warning (disable: 4514) +#pragma warning (disable: 4032) +#pragma warning (disable: 4201) +#pragma warning (disable: 4214) +#include +#include + +#elif defined( MACOS_X ) + +#include "macosx_glimp.h" + +#elif defined( __linux__ ) + +#include +#include +// bk001129 - from cvs1.17 (mkv) +#if defined( __FX__ ) +#include +#endif + +#elif defined( __FreeBSD__ ) // rb010123 + +#include +#include +#if defined( __FX__ ) +#include +#endif + +#else + +#include + +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef WINAPI +#define WINAPI +#endif + + +//=========================================================================== + +/* +** multitexture extension definitions +*/ +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_ACTIVE_TEXTURES_ARB 0x84E2 + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 + +// TTimo: FIXME +// linux needs those prototypes +// GL_VERSION_1_2 is defined after #include +#if !defined( GL_VERSION_1_2 ) /* || defined(__linux__) */ || defined( MACOS_X ) +typedef void ( APIENTRY * PFNGLMULTITEXCOORD1DARBPROC )( GLenum target, GLdouble s ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD1DVARBPROC )( GLenum target, const GLdouble *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD1FARBPROC )( GLenum target, GLfloat s ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD1FVARBPROC )( GLenum target, const GLfloat *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD1IARBPROC )( GLenum target, GLint s ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD1IVARBPROC )( GLenum target, const GLint *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD1SARBPROC )( GLenum target, GLshort s ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD1SVARBPROC )( GLenum target, const GLshort *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD2DARBPROC )( GLenum target, GLdouble s, GLdouble t ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD2DVARBPROC )( GLenum target, const GLdouble *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD2FARBPROC )( GLenum target, GLfloat s, GLfloat t ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD2FVARBPROC )( GLenum target, const GLfloat *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD2IARBPROC )( GLenum target, GLint s, GLint t ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD2IVARBPROC )( GLenum target, const GLint *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD2SARBPROC )( GLenum target, GLshort s, GLshort t ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD2SVARBPROC )( GLenum target, const GLshort *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD3DARBPROC )( GLenum target, GLdouble s, GLdouble t, GLdouble r ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD3DVARBPROC )( GLenum target, const GLdouble *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD3FARBPROC )( GLenum target, GLfloat s, GLfloat t, GLfloat r ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD3FVARBPROC )( GLenum target, const GLfloat *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD3IARBPROC )( GLenum target, GLint s, GLint t, GLint r ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD3IVARBPROC )( GLenum target, const GLint *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD3SARBPROC )( GLenum target, GLshort s, GLshort t, GLshort r ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD3SVARBPROC )( GLenum target, const GLshort *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD4DARBPROC )( GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD4DVARBPROC )( GLenum target, const GLdouble *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD4FARBPROC )( GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD4FVARBPROC )( GLenum target, const GLfloat *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD4IARBPROC )( GLenum target, GLint s, GLint t, GLint r, GLint q ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD4IVARBPROC )( GLenum target, const GLint *v ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD4SARBPROC )( GLenum target, GLshort s, GLshort t, GLshort r, GLshort q ); +typedef void ( APIENTRY * PFNGLMULTITEXCOORD4SVARBPROC )( GLenum target, const GLshort *v ); +typedef void ( APIENTRY * PFNGLACTIVETEXTUREARBPROC )( GLenum target ); +typedef void ( APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC )( GLenum target ); +#endif + +/* +** extension constants +*/ + +// GL_ATI_pn_triangles +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#ifndef __MACOS__ //DAJ BUGFIX changed the numbers +#define GL_PN_TRIANGLES_ATI 0x6090 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x6091 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x6092 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x6093 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x6094 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x6095 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x6096 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x6097 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x6098 +#else +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +#endif +typedef void ( APIENTRY * PFNGLPNTRIANGLESIATIPROC )( GLenum pname, GLint param ); +typedef void ( APIENTRY * PFNGLPNTRIANGLESFATIPROC )( GLenum pname, GLfloat param ); +#endif + +// for NV fog distance +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +// S3TC compression constants +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 + +// GL_EXT_texture_compression_s3tc constants +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + +// GL_EXT_texture_filter_anisotropic constants +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +// extensions will be function pointers on all platforms + +extern void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +extern void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +extern void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +extern void ( APIENTRY * qglLockArraysEXT )( GLint, GLint ); +extern void ( APIENTRY * qglUnlockArraysEXT )( void ); + +//----(SA) added +extern void ( APIENTRY * qglPNTrianglesiATI )( GLenum pname, GLint param ); +extern void ( APIENTRY * qglPNTrianglesfATI )( GLenum pname, GLfloat param ); +//----(SA) end + +//=========================================================================== + +// non-windows systems will just redefine qgl* to gl* +#if !defined( _WIN32 ) && !defined( MACOS_X ) && !defined( __linux__ ) && !defined( __FreeBSD__ ) // rb010123 + +#include "qgl_linked.h" + +#elif defined( MACOS_X ) +// This includes #ifdefs for optional logging and GL error checking after every GL call as well as #defines to prevent incorrect usage of the non-'qgl' versions of the GL API. +#include "macosx_qgl.h" + +#else + +// windows systems use a function pointer for each call so we can load minidrivers + +extern void ( APIENTRY * qglAccum )( GLenum op, GLfloat value ); +extern void ( APIENTRY * qglAlphaFunc )( GLenum func, GLclampf ref ); +extern GLboolean ( APIENTRY * qglAreTexturesResident )( GLsizei n, const GLuint *textures, GLboolean *residences ); +extern void ( APIENTRY * qglArrayElement )( GLint i ); +extern void ( APIENTRY * qglBegin )( GLenum mode ); +extern void ( APIENTRY * qglBindTexture )( GLenum target, GLuint texture ); +extern void ( APIENTRY * qglBitmap )( GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap ); +extern void ( APIENTRY * qglBlendFunc )( GLenum sfactor, GLenum dfactor ); +extern void ( APIENTRY * qglCallList )( GLuint list ); +extern void ( APIENTRY * qglCallLists )( GLsizei n, GLenum type, const GLvoid *lists ); +extern void ( APIENTRY * qglClear )( GLbitfield mask ); +extern void ( APIENTRY * qglClearAccum )( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha ); +extern void ( APIENTRY * qglClearColor )( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ); +extern void ( APIENTRY * qglClearDepth )( GLclampd depth ); +extern void ( APIENTRY * qglClearIndex )( GLfloat c ); +extern void ( APIENTRY * qglClearStencil )( GLint s ); +extern void ( APIENTRY * qglClipPlane )( GLenum plane, const GLdouble *equation ); +extern void ( APIENTRY * qglColor3b )( GLbyte red, GLbyte green, GLbyte blue ); +extern void ( APIENTRY * qglColor3bv )( const GLbyte *v ); +extern void ( APIENTRY * qglColor3d )( GLdouble red, GLdouble green, GLdouble blue ); +extern void ( APIENTRY * qglColor3dv )( const GLdouble *v ); +extern void ( APIENTRY * qglColor3f )( GLfloat red, GLfloat green, GLfloat blue ); +extern void ( APIENTRY * qglColor3fv )( const GLfloat *v ); +extern void ( APIENTRY * qglColor3i )( GLint red, GLint green, GLint blue ); +extern void ( APIENTRY * qglColor3iv )( const GLint *v ); +extern void ( APIENTRY * qglColor3s )( GLshort red, GLshort green, GLshort blue ); +extern void ( APIENTRY * qglColor3sv )( const GLshort *v ); +extern void ( APIENTRY * qglColor3ub )( GLubyte red, GLubyte green, GLubyte blue ); +extern void ( APIENTRY * qglColor3ubv )( const GLubyte *v ); +extern void ( APIENTRY * qglColor3ui )( GLuint red, GLuint green, GLuint blue ); +extern void ( APIENTRY * qglColor3uiv )( const GLuint *v ); +extern void ( APIENTRY * qglColor3us )( GLushort red, GLushort green, GLushort blue ); +extern void ( APIENTRY * qglColor3usv )( const GLushort *v ); +extern void ( APIENTRY * qglColor4b )( GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha ); +extern void ( APIENTRY * qglColor4bv )( const GLbyte *v ); +extern void ( APIENTRY * qglColor4d )( GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha ); +extern void ( APIENTRY * qglColor4dv )( const GLdouble *v ); +extern void ( APIENTRY * qglColor4f )( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha ); +extern void ( APIENTRY * qglColor4fv )( const GLfloat *v ); +extern void ( APIENTRY * qglColor4i )( GLint red, GLint green, GLint blue, GLint alpha ); +extern void ( APIENTRY * qglColor4iv )( const GLint *v ); +extern void ( APIENTRY * qglColor4s )( GLshort red, GLshort green, GLshort blue, GLshort alpha ); +extern void ( APIENTRY * qglColor4sv )( const GLshort *v ); +extern void ( APIENTRY * qglColor4ub )( GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha ); +extern void ( APIENTRY * qglColor4ubv )( const GLubyte *v ); +extern void ( APIENTRY * qglColor4ui )( GLuint red, GLuint green, GLuint blue, GLuint alpha ); +extern void ( APIENTRY * qglColor4uiv )( const GLuint *v ); +extern void ( APIENTRY * qglColor4us )( GLushort red, GLushort green, GLushort blue, GLushort alpha ); +extern void ( APIENTRY * qglColor4usv )( const GLushort *v ); +extern void ( APIENTRY * qglColorMask )( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha ); +extern void ( APIENTRY * qglColorMaterial )( GLenum face, GLenum mode ); +extern void ( APIENTRY * qglColorPointer )( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ); +extern void ( APIENTRY * qglCopyPixels )( GLint x, GLint y, GLsizei width, GLsizei height, GLenum type ); +extern void ( APIENTRY * qglCopyTexImage1D )( GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border ); +extern void ( APIENTRY * qglCopyTexImage2D )( GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border ); +extern void ( APIENTRY * qglCopyTexSubImage1D )( GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width ); +extern void ( APIENTRY * qglCopyTexSubImage2D )( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height ); +extern void ( APIENTRY * qglCullFace )( GLenum mode ); +extern void ( APIENTRY * qglDeleteLists )( GLuint list, GLsizei range ); +extern void ( APIENTRY * qglDeleteTextures )( GLsizei n, const GLuint *textures ); +extern void ( APIENTRY * qglDepthFunc )( GLenum func ); +extern void ( APIENTRY * qglDepthMask )( GLboolean flag ); +extern void ( APIENTRY * qglDepthRange )( GLclampd zNear, GLclampd zFar ); +extern void ( APIENTRY * qglDisable )( GLenum cap ); +extern void ( APIENTRY * qglDisableClientState )( GLenum array ); +extern void ( APIENTRY * qglDrawArrays )( GLenum mode, GLint first, GLsizei count ); +extern void ( APIENTRY * qglDrawBuffer )( GLenum mode ); +extern void ( APIENTRY * qglDrawElements )( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ); +extern void ( APIENTRY * qglDrawPixels )( GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ); +extern void ( APIENTRY * qglEdgeFlag )( GLboolean flag ); +extern void ( APIENTRY * qglEdgeFlagPointer )( GLsizei stride, const GLvoid *pointer ); +extern void ( APIENTRY * qglEdgeFlagv )( const GLboolean *flag ); +extern void ( APIENTRY * qglEnable )( GLenum cap ); +extern void ( APIENTRY * qglEnableClientState )( GLenum array ); +extern void ( APIENTRY * qglEnd )( void ); +extern void ( APIENTRY * qglEndList )( void ); +extern void ( APIENTRY * qglEvalCoord1d )( GLdouble u ); +extern void ( APIENTRY * qglEvalCoord1dv )( const GLdouble *u ); +extern void ( APIENTRY * qglEvalCoord1f )( GLfloat u ); +extern void ( APIENTRY * qglEvalCoord1fv )( const GLfloat *u ); +extern void ( APIENTRY * qglEvalCoord2d )( GLdouble u, GLdouble v ); +extern void ( APIENTRY * qglEvalCoord2dv )( const GLdouble *u ); +extern void ( APIENTRY * qglEvalCoord2f )( GLfloat u, GLfloat v ); +extern void ( APIENTRY * qglEvalCoord2fv )( const GLfloat *u ); +extern void ( APIENTRY * qglEvalMesh1 )( GLenum mode, GLint i1, GLint i2 ); +extern void ( APIENTRY * qglEvalMesh2 )( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 ); +extern void ( APIENTRY * qglEvalPoint1 )( GLint i ); +extern void ( APIENTRY * qglEvalPoint2 )( GLint i, GLint j ); +extern void ( APIENTRY * qglFeedbackBuffer )( GLsizei size, GLenum type, GLfloat *buffer ); +extern void ( APIENTRY * qglFinish )( void ); +extern void ( APIENTRY * qglFlush )( void ); +extern void ( APIENTRY * qglFogf )( GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglFogfv )( GLenum pname, const GLfloat *params ); +extern void ( APIENTRY * qglFogi )( GLenum pname, GLint param ); +extern void ( APIENTRY * qglFogiv )( GLenum pname, const GLint *params ); +extern void ( APIENTRY * qglFrontFace )( GLenum mode ); +extern void ( APIENTRY * qglFrustum )( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar ); +extern GLuint ( APIENTRY * qglGenLists )( GLsizei range ); +extern void ( APIENTRY * qglGenTextures )( GLsizei n, GLuint *textures ); +extern void ( APIENTRY * qglGetBooleanv )( GLenum pname, GLboolean *params ); +extern void ( APIENTRY * qglGetClipPlane )( GLenum plane, GLdouble *equation ); +extern void ( APIENTRY * qglGetDoublev )( GLenum pname, GLdouble *params ); +extern GLenum ( APIENTRY * qglGetError )( void ); +extern void ( APIENTRY * qglGetFloatv )( GLenum pname, GLfloat *params ); +extern void ( APIENTRY * qglGetIntegerv )( GLenum pname, GLint *params ); +extern void ( APIENTRY * qglGetLightfv )( GLenum light, GLenum pname, GLfloat *params ); +extern void ( APIENTRY * qglGetLightiv )( GLenum light, GLenum pname, GLint *params ); +extern void ( APIENTRY * qglGetMapdv )( GLenum target, GLenum query, GLdouble *v ); +extern void ( APIENTRY * qglGetMapfv )( GLenum target, GLenum query, GLfloat *v ); +extern void ( APIENTRY * qglGetMapiv )( GLenum target, GLenum query, GLint *v ); +extern void ( APIENTRY * qglGetMaterialfv )( GLenum face, GLenum pname, GLfloat *params ); +extern void ( APIENTRY * qglGetMaterialiv )( GLenum face, GLenum pname, GLint *params ); +extern void ( APIENTRY * qglGetPixelMapfv )( GLenum map, GLfloat *values ); +extern void ( APIENTRY * qglGetPixelMapuiv )( GLenum map, GLuint *values ); +extern void ( APIENTRY * qglGetPixelMapusv )( GLenum map, GLushort *values ); +extern void ( APIENTRY * qglGetPointerv )( GLenum pname, GLvoid* *params ); +extern void ( APIENTRY * qglGetPolygonStipple )( GLubyte *mask ); +extern const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +extern void ( APIENTRY * qglGetTexEnvfv )( GLenum target, GLenum pname, GLfloat *params ); +extern void ( APIENTRY * qglGetTexEnviv )( GLenum target, GLenum pname, GLint *params ); +extern void ( APIENTRY * qglGetTexGendv )( GLenum coord, GLenum pname, GLdouble *params ); +extern void ( APIENTRY * qglGetTexGenfv )( GLenum coord, GLenum pname, GLfloat *params ); +extern void ( APIENTRY * qglGetTexGeniv )( GLenum coord, GLenum pname, GLint *params ); +extern void ( APIENTRY * qglGetTexImage )( GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels ); +extern void ( APIENTRY * qglGetTexLevelParameterfv )( GLenum target, GLint level, GLenum pname, GLfloat *params ); +extern void ( APIENTRY * qglGetTexLevelParameteriv )( GLenum target, GLint level, GLenum pname, GLint *params ); +extern void ( APIENTRY * qglGetTexParameterfv )( GLenum target, GLenum pname, GLfloat *params ); +extern void ( APIENTRY * qglGetTexParameteriv )( GLenum target, GLenum pname, GLint *params ); +extern void ( APIENTRY * qglHint )( GLenum target, GLenum mode ); +extern void ( APIENTRY * qglIndexMask )( GLuint mask ); +extern void ( APIENTRY * qglIndexPointer )( GLenum type, GLsizei stride, const GLvoid *pointer ); +extern void ( APIENTRY * qglIndexd )( GLdouble c ); +extern void ( APIENTRY * qglIndexdv )( const GLdouble *c ); +extern void ( APIENTRY * qglIndexf )( GLfloat c ); +extern void ( APIENTRY * qglIndexfv )( const GLfloat *c ); +extern void ( APIENTRY * qglIndexi )( GLint c ); +extern void ( APIENTRY * qglIndexiv )( const GLint *c ); +extern void ( APIENTRY * qglIndexs )( GLshort c ); +extern void ( APIENTRY * qglIndexsv )( const GLshort *c ); +extern void ( APIENTRY * qglIndexub )( GLubyte c ); +extern void ( APIENTRY * qglIndexubv )( const GLubyte *c ); +extern void ( APIENTRY * qglInitNames )( void ); +extern void ( APIENTRY * qglInterleavedArrays )( GLenum format, GLsizei stride, const GLvoid *pointer ); +extern GLboolean ( APIENTRY * qglIsEnabled )( GLenum cap ); +extern GLboolean ( APIENTRY * qglIsList )( GLuint list ); +extern GLboolean ( APIENTRY * qglIsTexture )( GLuint texture ); +extern void ( APIENTRY * qglLightModelf )( GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglLightModelfv )( GLenum pname, const GLfloat *params ); +extern void ( APIENTRY * qglLightModeli )( GLenum pname, GLint param ); +extern void ( APIENTRY * qglLightModeliv )( GLenum pname, const GLint *params ); +extern void ( APIENTRY * qglLightf )( GLenum light, GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglLightfv )( GLenum light, GLenum pname, const GLfloat *params ); +extern void ( APIENTRY * qglLighti )( GLenum light, GLenum pname, GLint param ); +extern void ( APIENTRY * qglLightiv )( GLenum light, GLenum pname, const GLint *params ); +extern void ( APIENTRY * qglLineStipple )( GLint factor, GLushort pattern ); +extern void ( APIENTRY * qglLineWidth )( GLfloat width ); +extern void ( APIENTRY * qglListBase )( GLuint base ); +extern void ( APIENTRY * qglLoadIdentity )( void ); +extern void ( APIENTRY * qglLoadMatrixd )( const GLdouble *m ); +extern void ( APIENTRY * qglLoadMatrixf )( const GLfloat *m ); +extern void ( APIENTRY * qglLoadName )( GLuint name ); +extern void ( APIENTRY * qglLogicOp )( GLenum opcode ); +extern void ( APIENTRY * qglMap1d )( GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points ); +extern void ( APIENTRY * qglMap1f )( GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points ); +extern void ( APIENTRY * qglMap2d )( GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points ); +extern void ( APIENTRY * qglMap2f )( GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points ); +extern void ( APIENTRY * qglMapGrid1d )( GLint un, GLdouble u1, GLdouble u2 ); +extern void ( APIENTRY * qglMapGrid1f )( GLint un, GLfloat u1, GLfloat u2 ); +extern void ( APIENTRY * qglMapGrid2d )( GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2 ); +extern void ( APIENTRY * qglMapGrid2f )( GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2 ); +extern void ( APIENTRY * qglMaterialf )( GLenum face, GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglMaterialfv )( GLenum face, GLenum pname, const GLfloat *params ); +extern void ( APIENTRY * qglMateriali )( GLenum face, GLenum pname, GLint param ); +extern void ( APIENTRY * qglMaterialiv )( GLenum face, GLenum pname, const GLint *params ); +extern void ( APIENTRY * qglMatrixMode )( GLenum mode ); +extern void ( APIENTRY * qglMultMatrixd )( const GLdouble *m ); +extern void ( APIENTRY * qglMultMatrixf )( const GLfloat *m ); +extern void ( APIENTRY * qglNewList )( GLuint list, GLenum mode ); +extern void ( APIENTRY * qglNormal3b )( GLbyte nx, GLbyte ny, GLbyte nz ); +extern void ( APIENTRY * qglNormal3bv )( const GLbyte *v ); +extern void ( APIENTRY * qglNormal3d )( GLdouble nx, GLdouble ny, GLdouble nz ); +extern void ( APIENTRY * qglNormal3dv )( const GLdouble *v ); +extern void ( APIENTRY * qglNormal3f )( GLfloat nx, GLfloat ny, GLfloat nz ); +extern void ( APIENTRY * qglNormal3fv )( const GLfloat *v ); +extern void ( APIENTRY * qglNormal3i )( GLint nx, GLint ny, GLint nz ); +extern void ( APIENTRY * qglNormal3iv )( const GLint *v ); +extern void ( APIENTRY * qglNormal3s )( GLshort nx, GLshort ny, GLshort nz ); +extern void ( APIENTRY * qglNormal3sv )( const GLshort *v ); +extern void ( APIENTRY * qglNormalPointer )( GLenum type, GLsizei stride, const GLvoid *pointer ); +extern void ( APIENTRY * qglOrtho )( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar ); +extern void ( APIENTRY * qglPassThrough )( GLfloat token ); +extern void ( APIENTRY * qglPixelMapfv )( GLenum map, GLsizei mapsize, const GLfloat *values ); +extern void ( APIENTRY * qglPixelMapuiv )( GLenum map, GLsizei mapsize, const GLuint *values ); +extern void ( APIENTRY * qglPixelMapusv )( GLenum map, GLsizei mapsize, const GLushort *values ); +extern void ( APIENTRY * qglPixelStoref )( GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglPixelStorei )( GLenum pname, GLint param ); +extern void ( APIENTRY * qglPixelTransferf )( GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglPixelTransferi )( GLenum pname, GLint param ); +extern void ( APIENTRY * qglPixelZoom )( GLfloat xfactor, GLfloat yfactor ); +extern void ( APIENTRY * qglPointSize )( GLfloat size ); +extern void ( APIENTRY * qglPolygonMode )( GLenum face, GLenum mode ); +extern void ( APIENTRY * qglPolygonOffset )( GLfloat factor, GLfloat units ); +extern void ( APIENTRY * qglPolygonStipple )( const GLubyte *mask ); +extern void ( APIENTRY * qglPopAttrib )( void ); +extern void ( APIENTRY * qglPopClientAttrib )( void ); +extern void ( APIENTRY * qglPopMatrix )( void ); +extern void ( APIENTRY * qglPopName )( void ); +extern void ( APIENTRY * qglPrioritizeTextures )( GLsizei n, const GLuint *textures, const GLclampf *priorities ); +extern void ( APIENTRY * qglPushAttrib )( GLbitfield mask ); +extern void ( APIENTRY * qglPushClientAttrib )( GLbitfield mask ); +extern void ( APIENTRY * qglPushMatrix )( void ); +extern void ( APIENTRY * qglPushName )( GLuint name ); +extern void ( APIENTRY * qglRasterPos2d )( GLdouble x, GLdouble y ); +extern void ( APIENTRY * qglRasterPos2dv )( const GLdouble *v ); +extern void ( APIENTRY * qglRasterPos2f )( GLfloat x, GLfloat y ); +extern void ( APIENTRY * qglRasterPos2fv )( const GLfloat *v ); +extern void ( APIENTRY * qglRasterPos2i )( GLint x, GLint y ); +extern void ( APIENTRY * qglRasterPos2iv )( const GLint *v ); +extern void ( APIENTRY * qglRasterPos2s )( GLshort x, GLshort y ); +extern void ( APIENTRY * qglRasterPos2sv )( const GLshort *v ); +extern void ( APIENTRY * qglRasterPos3d )( GLdouble x, GLdouble y, GLdouble z ); +extern void ( APIENTRY * qglRasterPos3dv )( const GLdouble *v ); +extern void ( APIENTRY * qglRasterPos3f )( GLfloat x, GLfloat y, GLfloat z ); +extern void ( APIENTRY * qglRasterPos3fv )( const GLfloat *v ); +extern void ( APIENTRY * qglRasterPos3i )( GLint x, GLint y, GLint z ); +extern void ( APIENTRY * qglRasterPos3iv )( const GLint *v ); +extern void ( APIENTRY * qglRasterPos3s )( GLshort x, GLshort y, GLshort z ); +extern void ( APIENTRY * qglRasterPos3sv )( const GLshort *v ); +extern void ( APIENTRY * qglRasterPos4d )( GLdouble x, GLdouble y, GLdouble z, GLdouble w ); +extern void ( APIENTRY * qglRasterPos4dv )( const GLdouble *v ); +extern void ( APIENTRY * qglRasterPos4f )( GLfloat x, GLfloat y, GLfloat z, GLfloat w ); +extern void ( APIENTRY * qglRasterPos4fv )( const GLfloat *v ); +extern void ( APIENTRY * qglRasterPos4i )( GLint x, GLint y, GLint z, GLint w ); +extern void ( APIENTRY * qglRasterPos4iv )( const GLint *v ); +extern void ( APIENTRY * qglRasterPos4s )( GLshort x, GLshort y, GLshort z, GLshort w ); +extern void ( APIENTRY * qglRasterPos4sv )( const GLshort *v ); +extern void ( APIENTRY * qglReadBuffer )( GLenum mode ); +extern void ( APIENTRY * qglReadPixels )( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels ); +extern void ( APIENTRY * qglRectd )( GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2 ); +extern void ( APIENTRY * qglRectdv )( const GLdouble *v1, const GLdouble *v2 ); +extern void ( APIENTRY * qglRectf )( GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2 ); +extern void ( APIENTRY * qglRectfv )( const GLfloat *v1, const GLfloat *v2 ); +extern void ( APIENTRY * qglRecti )( GLint x1, GLint y1, GLint x2, GLint y2 ); +extern void ( APIENTRY * qglRectiv )( const GLint *v1, const GLint *v2 ); +extern void ( APIENTRY * qglRects )( GLshort x1, GLshort y1, GLshort x2, GLshort y2 ); +extern void ( APIENTRY * qglRectsv )( const GLshort *v1, const GLshort *v2 ); +extern GLint ( APIENTRY * qglRenderMode )( GLenum mode ); +extern void ( APIENTRY * qglRotated )( GLdouble angle, GLdouble x, GLdouble y, GLdouble z ); +extern void ( APIENTRY * qglRotatef )( GLfloat angle, GLfloat x, GLfloat y, GLfloat z ); +extern void ( APIENTRY * qglScaled )( GLdouble x, GLdouble y, GLdouble z ); +extern void ( APIENTRY * qglScalef )( GLfloat x, GLfloat y, GLfloat z ); +extern void ( APIENTRY * qglScissor )( GLint x, GLint y, GLsizei width, GLsizei height ); +extern void ( APIENTRY * qglSelectBuffer )( GLsizei size, GLuint *buffer ); +extern void ( APIENTRY * qglShadeModel )( GLenum mode ); +extern void ( APIENTRY * qglStencilFunc )( GLenum func, GLint ref, GLuint mask ); +extern void ( APIENTRY * qglStencilMask )( GLuint mask ); +extern void ( APIENTRY * qglStencilOp )( GLenum fail, GLenum zfail, GLenum zpass ); +extern void ( APIENTRY * qglTexCoord1d )( GLdouble s ); +extern void ( APIENTRY * qglTexCoord1dv )( const GLdouble *v ); +extern void ( APIENTRY * qglTexCoord1f )( GLfloat s ); +extern void ( APIENTRY * qglTexCoord1fv )( const GLfloat *v ); +extern void ( APIENTRY * qglTexCoord1i )( GLint s ); +extern void ( APIENTRY * qglTexCoord1iv )( const GLint *v ); +extern void ( APIENTRY * qglTexCoord1s )( GLshort s ); +extern void ( APIENTRY * qglTexCoord1sv )( const GLshort *v ); +extern void ( APIENTRY * qglTexCoord2d )( GLdouble s, GLdouble t ); +extern void ( APIENTRY * qglTexCoord2dv )( const GLdouble *v ); +extern void ( APIENTRY * qglTexCoord2f )( GLfloat s, GLfloat t ); +extern void ( APIENTRY * qglTexCoord2fv )( const GLfloat *v ); +extern void ( APIENTRY * qglTexCoord2i )( GLint s, GLint t ); +extern void ( APIENTRY * qglTexCoord2iv )( const GLint *v ); +extern void ( APIENTRY * qglTexCoord2s )( GLshort s, GLshort t ); +extern void ( APIENTRY * qglTexCoord2sv )( const GLshort *v ); +extern void ( APIENTRY * qglTexCoord3d )( GLdouble s, GLdouble t, GLdouble r ); +extern void ( APIENTRY * qglTexCoord3dv )( const GLdouble *v ); +extern void ( APIENTRY * qglTexCoord3f )( GLfloat s, GLfloat t, GLfloat r ); +extern void ( APIENTRY * qglTexCoord3fv )( const GLfloat *v ); +extern void ( APIENTRY * qglTexCoord3i )( GLint s, GLint t, GLint r ); +extern void ( APIENTRY * qglTexCoord3iv )( const GLint *v ); +extern void ( APIENTRY * qglTexCoord3s )( GLshort s, GLshort t, GLshort r ); +extern void ( APIENTRY * qglTexCoord3sv )( const GLshort *v ); +extern void ( APIENTRY * qglTexCoord4d )( GLdouble s, GLdouble t, GLdouble r, GLdouble q ); +extern void ( APIENTRY * qglTexCoord4dv )( const GLdouble *v ); +extern void ( APIENTRY * qglTexCoord4f )( GLfloat s, GLfloat t, GLfloat r, GLfloat q ); +extern void ( APIENTRY * qglTexCoord4fv )( const GLfloat *v ); +extern void ( APIENTRY * qglTexCoord4i )( GLint s, GLint t, GLint r, GLint q ); +extern void ( APIENTRY * qglTexCoord4iv )( const GLint *v ); +extern void ( APIENTRY * qglTexCoord4s )( GLshort s, GLshort t, GLshort r, GLshort q ); +extern void ( APIENTRY * qglTexCoord4sv )( const GLshort *v ); +extern void ( APIENTRY * qglTexCoordPointer )( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ); +extern void ( APIENTRY * qglTexEnvf )( GLenum target, GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglTexEnvfv )( GLenum target, GLenum pname, const GLfloat *params ); +extern void ( APIENTRY * qglTexEnvi )( GLenum target, GLenum pname, GLint param ); +extern void ( APIENTRY * qglTexEnviv )( GLenum target, GLenum pname, const GLint *params ); +extern void ( APIENTRY * qglTexGend )( GLenum coord, GLenum pname, GLdouble param ); +extern void ( APIENTRY * qglTexGendv )( GLenum coord, GLenum pname, const GLdouble *params ); +extern void ( APIENTRY * qglTexGenf )( GLenum coord, GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglTexGenfv )( GLenum coord, GLenum pname, const GLfloat *params ); +extern void ( APIENTRY * qglTexGeni )( GLenum coord, GLenum pname, GLint param ); +extern void ( APIENTRY * qglTexGeniv )( GLenum coord, GLenum pname, const GLint *params ); +extern void ( APIENTRY * qglTexImage1D )( GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels ); +extern void ( APIENTRY * qglTexImage2D )( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ); +extern void ( APIENTRY * qglTexParameterf )( GLenum target, GLenum pname, GLfloat param ); +extern void ( APIENTRY * qglTexParameterfv )( GLenum target, GLenum pname, const GLfloat *params ); +extern void ( APIENTRY * qglTexParameteri )( GLenum target, GLenum pname, GLint param ); +extern void ( APIENTRY * qglTexParameteriv )( GLenum target, GLenum pname, const GLint *params ); +extern void ( APIENTRY * qglTexSubImage1D )( GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels ); +extern void ( APIENTRY * qglTexSubImage2D )( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ); +extern void ( APIENTRY * qglTranslated )( GLdouble x, GLdouble y, GLdouble z ); +extern void ( APIENTRY * qglTranslatef )( GLfloat x, GLfloat y, GLfloat z ); +extern void ( APIENTRY * qglVertex2d )( GLdouble x, GLdouble y ); +extern void ( APIENTRY * qglVertex2dv )( const GLdouble *v ); +extern void ( APIENTRY * qglVertex2f )( GLfloat x, GLfloat y ); +extern void ( APIENTRY * qglVertex2fv )( const GLfloat *v ); +extern void ( APIENTRY * qglVertex2i )( GLint x, GLint y ); +extern void ( APIENTRY * qglVertex2iv )( const GLint *v ); +extern void ( APIENTRY * qglVertex2s )( GLshort x, GLshort y ); +extern void ( APIENTRY * qglVertex2sv )( const GLshort *v ); +extern void ( APIENTRY * qglVertex3d )( GLdouble x, GLdouble y, GLdouble z ); +extern void ( APIENTRY * qglVertex3dv )( const GLdouble *v ); +extern void ( APIENTRY * qglVertex3f )( GLfloat x, GLfloat y, GLfloat z ); +extern void ( APIENTRY * qglVertex3fv )( const GLfloat *v ); +extern void ( APIENTRY * qglVertex3i )( GLint x, GLint y, GLint z ); +extern void ( APIENTRY * qglVertex3iv )( const GLint *v ); +extern void ( APIENTRY * qglVertex3s )( GLshort x, GLshort y, GLshort z ); +extern void ( APIENTRY * qglVertex3sv )( const GLshort *v ); +extern void ( APIENTRY * qglVertex4d )( GLdouble x, GLdouble y, GLdouble z, GLdouble w ); +extern void ( APIENTRY * qglVertex4dv )( const GLdouble *v ); +extern void ( APIENTRY * qglVertex4f )( GLfloat x, GLfloat y, GLfloat z, GLfloat w ); +extern void ( APIENTRY * qglVertex4fv )( const GLfloat *v ); +extern void ( APIENTRY * qglVertex4i )( GLint x, GLint y, GLint z, GLint w ); +extern void ( APIENTRY * qglVertex4iv )( const GLint *v ); +extern void ( APIENTRY * qglVertex4s )( GLshort x, GLshort y, GLshort z, GLshort w ); +extern void ( APIENTRY * qglVertex4sv )( const GLshort *v ); +extern void ( APIENTRY * qglVertexPointer )( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ); +extern void ( APIENTRY * qglViewport )( GLint x, GLint y, GLsizei width, GLsizei height ); + +#if defined( _WIN32 ) + +extern int ( WINAPI * qwglChoosePixelFormat )( HDC, CONST PIXELFORMATDESCRIPTOR * ); +extern int ( WINAPI * qwglDescribePixelFormat )( HDC, int, UINT, LPPIXELFORMATDESCRIPTOR ); +extern int ( WINAPI * qwglGetPixelFormat )( HDC ); +extern BOOL ( WINAPI * qwglSetPixelFormat )( HDC, int, CONST PIXELFORMATDESCRIPTOR * ); +extern BOOL ( WINAPI * qwglSwapBuffers )( HDC ); + +extern BOOL ( WINAPI * qwglGetDeviceGammaRamp3DFX )( HDC, LPVOID ); +extern BOOL ( WINAPI * qwglSetDeviceGammaRamp3DFX )( HDC, LPVOID ); + +extern BOOL ( WINAPI * qwglCopyContext )( HGLRC, HGLRC, UINT ); +extern HGLRC ( WINAPI * qwglCreateContext )( HDC ); +extern HGLRC ( WINAPI * qwglCreateLayerContext )( HDC, int ); +extern BOOL ( WINAPI * qwglDeleteContext )( HGLRC ); +extern HGLRC ( WINAPI * qwglGetCurrentContext )( VOID ); +extern HDC ( WINAPI * qwglGetCurrentDC )( VOID ); +extern PROC ( WINAPI * qwglGetProcAddress )( LPCSTR ); +extern BOOL ( WINAPI * qwglMakeCurrent )( HDC, HGLRC ); +extern BOOL ( WINAPI * qwglShareLists )( HGLRC, HGLRC ); +extern BOOL ( WINAPI * qwglUseFontBitmaps )( HDC, DWORD, DWORD, DWORD ); + +extern BOOL ( WINAPI * qwglUseFontOutlines )( HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT ); + +extern BOOL ( WINAPI * qwglDescribeLayerPlane )( HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR ); +extern int ( WINAPI * qwglSetLayerPaletteEntries )( HDC, int, int, int, + CONST COLORREF * ); +extern int ( WINAPI * qwglGetLayerPaletteEntries )( HDC, int, int, int, + COLORREF * ); +extern BOOL ( WINAPI * qwglRealizeLayerPalette )( HDC, int, BOOL ); +extern BOOL ( WINAPI * qwglSwapLayerBuffers )( HDC, UINT ); + +extern BOOL ( WINAPI * qwglSwapIntervalEXT )( int interval ); + +#endif // _WIN32 + +#if ( ( defined __linux__ ) || ( defined __FreeBSD__ ) ) // rb010123 + +//FX Mesa Functions +// bk001129 - from cvs1.17 (mkv) +#if defined ( __FX__ ) +extern fxMesaContext ( *qfxMesaCreateContext )( GLuint win, GrScreenResolution_t, GrScreenRefresh_t, const GLint attribList[] ); +extern fxMesaContext ( *qfxMesaCreateBestContext )( GLuint win, GLint width, GLint height, const GLint attribList[] ); +extern void ( *qfxMesaDestroyContext )( fxMesaContext ctx ); +extern void ( *qfxMesaMakeCurrent )( fxMesaContext ctx ); +extern fxMesaContext ( *qfxMesaGetCurrentContext )( void ); +extern void ( *qfxMesaSwapBuffers )( void ); +#endif + +//GLX Functions +extern XVisualInfo * ( *qglXChooseVisual )( Display * dpy, int screen, int *attribList ); +extern GLXContext ( *qglXCreateContext )( Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct ); +extern void ( *qglXDestroyContext )( Display *dpy, GLXContext ctx ); +extern Bool ( *qglXMakeCurrent )( Display *dpy, GLXDrawable drawable, GLXContext ctx ); +extern void ( *qglXCopyContext )( Display *dpy, GLXContext src, GLXContext dst, GLuint mask ); +extern void ( *qglXSwapBuffers )( Display *dpy, GLXDrawable drawable ); + +#endif // __linux__ || __FreeBSD__ // rb010123 + +#endif // _WIN32 && __linux__ + +#endif diff --git a/src/renderer/qgl_linked.h b/src/renderer/qgl_linked.h new file mode 100644 index 0000000..6adaf9d --- /dev/null +++ b/src/renderer/qgl_linked.h @@ -0,0 +1,29 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#define qglAccum glAccum # define qglAlphaFunc glAlphaFunc # define qglAreTexturesResident glAreTexturesResident # define qglArrayElement glArrayElement # define qglBegin glBegin # define qglBindTexture glBindTexture # define qglBitmap glBitmap # define qglBlendFunc glBlendFunc # define qglCallList glCallList # define qglCallLists glCallLists # define qglClear glClear # define qglClearAccum glClearAccum # define qglClearColor glClearColor # define qglClearDepth glClearDepth # define qglClearIndex glClearIndex # define qglClearStencil glClearStencil # define qglClipPlane glClipPlane # define qglColor3b glColor3b # define qglColor3bv glColor3bv # define qglColor3d glColor3d # define qglColor3dv glColor3dv # define qglColor3f glColor3f # define qglColor3fv glColor3fv # define qglColor3i glColor3i # define qglColor3iv glColor3iv # define qglColor3s glColor3s # define qglColor3sv glColor3sv # define qglColor3ub glColor3ub # define qglColor3ubv glColor3ubv # define qglColor3ui glColor3ui # define qglColor3uiv glColor3uiv # define qglColor3us glColor3us # define qglColor3usv glColor3usv # define qglColor4b glColor4b # define qglColor4bv glColor4bv # define qglColor4d glColor4d # define qglColor4dv glColor4dv # define qglColor4f glColor4f # define qglColor4fv glColor4fv # define qglColor4i glColor4i # define qglColor4iv glColor4iv # define qglColor4s glColor4s # define qglColor4sv glColor4sv # define qglColor4ub glColor4ub # define qglColor4ubv glColor4ubv # define qglColor4ui glColor4ui # define qglColor4uiv glColor4uiv # define qglColor4us glColor4us # define qglColor4usv glColor4usv # define qglColorMask glColorMask # define qglColorMaterial glColorMaterial # define qglColorPointer glColorPointer # define qglCopyPixels glCopyPixels # define qglCopyTexImage1D glCopyTexImage1D # define qglCopyTexImage2D glCopyTexImage2D # define qglCopyTexSubImage1D glCopyTexSubImage1D # define qglCopyTexSubImage2D glCopyTexSubImage2D # define qglCullFace glCullFace # define qglDeleteLists glDeleteLists # define qglDeleteTextures glDeleteTextures # define qglDepthFunc glDepthFunc # define qglDepthMask glDepthMask # define qglDepthRange glDepthRange # define qglDisable glDisable # define qglDisableClientState glDisableClientState # define qglDrawArrays glDrawArrays # define qglDrawBuffer glDrawBuffer # define qglDrawElements glDrawElements # define qglDrawPixels glDrawPixels # define qglEdgeFlag glEdgeFlag # define qglEdgeFlagPointer glEdgeFlagPointer # define qglEdgeFlagv glEdgeFlagv # define qglEnable glEnable # define qglEnableClientState glEnableClientState # define qglEnd glEnd # define qglEndList glEndList # define qglEvalCoord1d glEvalCoord1d # define qglEvalCoord1dv glEvalCoord1dv # define qglEvalCoord1f glEvalCoord1f # define qglEvalCoord1fv glEvalCoord1fv # define qglEvalCoord2d glEvalCoord2d # define qglEvalCoord2dv glEvalCoord2dv # define qglEvalCoord2f glEvalCoord2f # define qglEvalCoord2fv glEvalCoord2fv # define qglEvalMesh1 glEvalMesh1 # define qglEvalMesh2 glEvalMesh2 # define qglEvalPoint1 glEvalPoint1 # define qglEvalPoint2 glEvalPoint2 # define qglFeedbackBuffer glFeedbackBuffer # define qglFinish glFinish # define qglFlush glFlush # define qglFogf glFogf # define qglFogfv glFogfv # define qglFogi glFogi # define qglFogiv glFogiv # define qglFrontFace glFrontFace # define qglFrustum glFrustum # define qglGenLists glGenLists # define qglGenTextures glGenTextures # define qglGetBooleanv glGetBooleanv # define qglGetClipPlane glGetClipPlane # define qglGetDoublev glGetDoublev # define qglGetError glGetError # define qglGetFloatv glGetFloatv # define qglGetIntegerv glGetIntegerv # define qglGetLightfv glGetLightfv # define qglGetLightiv glGetLightiv # define qglGetMapdv glGetMapdv # define qglGetMapfv glGetMapfv # define qglGetMapiv glGetMapiv # define qglGetMaterialfv glGetMaterialfv # define qglGetMaterialiv glGetMaterialiv # define qglGetPixelMapfv glGetPixelMapfv # define qglGetPixelMapuiv glGetPixelMapuiv # define qglGetPixelMapusv glGetPixelMapusv # define qglGetPointerv glGetPointerv # define qglGetPolygonStipple glGetPolygonStipple # define qglGetString glGetString # define qglGetTexGendv glGetTexGendv # define qglGetTexGenfv glGetTexGenfv # define qglGetTexGeniv glGetTexGeniv # define qglGetTexImage glGetTexImage # define qglGetTexLevelParameterfv glGetTexLevelParameterfv # define qglGetTexLevelParameteriv glGetTexLevelParameteriv # define qglGetTexParameterfv glGetTexParameterfv # define qglGetTexParameteriv glGetTexParameteriv # define qglHint glHint # define qglIndexMask glIndexMask # define qglIndexPointer glIndexPointer # define qglIndexd glIndexd # define qglIndexdv glIndexdv # define qglIndexf glIndexf # define qglIndexfv glIndexfv # define qglIndexi glIndexi # define qglIndexiv glIndexiv # define qglIndexs glIndexs # define qglIndexsv glIndexsv # define qglIndexub glIndexub # define qglIndexubv glIndexubv # define qglInitNames glInitNames # define qglInterleavedArrays glInterleavedArrays # define qglIsEnabled glIsEnabled # define qglIsList glIsList # define qglIsTexture glIsTexture # define qglLightModelf glLightModelf # define qglLightModelfv glLightModelfv # define qglLightModeli glLightModeli # define qglLightModeliv glLightModeliv # define qglLightf glLightf # define qglLightfv glLightfv # define qglLighti glLighti # define qglLightiv glLightiv # define qglLineStipple glLineStipple # define qglLineWidth glLineWidth # define qglListBase glListBase # define qglLoadIdentity glLoadIdentity # define qglLoadMatrixd glLoadMatrixd # define qglLoadMatrixf glLoadMatrixf # define qglLoadName glLoadName # define qglLogicOp glLogicOp # define qglMap1d glMap1d # define qglMap1f glMap1f # define qglMap2d glMap2d # define qglMap2f glMap2f # define qglMapGrid1d glMapGrid1d # define qglMapGrid1f glMapGrid1f # define qglMapGrid2d glMapGrid2d # define qglMapGrid2f glMapGrid2f # define qglMaterialf glMaterialf # define qglMaterialfv glMaterialfv # define qglMateriali glMateriali # define qglMaterialiv glMaterialiv # define qglMatrixMode glMatrixMode # define qglMultMatrixd glMultMatrixd # define qglMultMatrixf glMultMatrixf # define qglNewList glNewList # define qglNormal3b glNormal3b # define qglNormal3bv glNormal3bv # define qglNormal3d glNormal3d # define qglNormal3dv glNormal3dv # define qglNormal3f glNormal3f # define qglNormal3fv glNormal3fv # define qglNormal3i glNormal3i # define qglNormal3iv glNormal3iv # define qglNormal3s glNormal3s # define qglNormal3sv glNormal3sv # define qglNormalPointer glNormalPointer # define qglOrtho glOrtho # define qglPassThrough glPassThrough # define qglPixelMapfv glPixelMapfv # define qglPixelMapuiv glPixelMapuiv # define qglPixelMapusv glPixelMapusv # define qglPixelStoref glPixelStoref # define qglPixelStorei glPixelStorei # define qglPixelTransferf glPixelTransferf # define qglPixelTransferi glPixelTransferi # define qglPixelZoom glPixelZoom # define qglPointSize glPointSize # define qglPolygonMode glPolygonMode # define qglPolygonOffset glPolygonOffset # define qglPolygonStipple glPolygonStipple # define qglPopAttrib glPopAttrib # define qglPopClientAttrib glPopClientAttrib # define qglPopMatrix glPopMatrix # define qglPopName glPopName # define qglPrioritizeTextures glPrioritizeTextures # define qglPushAttrib glPushAttrib # define qglPushClientAttrib glPushClientAttrib # define qglPushMatrix glPushMatrix # define qglPushName glPushName # define qglRasterPos2d glRasterPos2d # define qglRasterPos2dv glRasterPos2dv # define qglRasterPos2f glRasterPos2f # define qglRasterPos2fv glRasterPos2fv # define qglRasterPos2i glRasterPos2i # define qglRasterPos2iv glRasterPos2iv # define qglRasterPos2s glRasterPos2s # define qglRasterPos2sv glRasterPos2sv # define qglRasterPos3d glRasterPos3d # define qglRasterPos3dv glRasterPos3dv # define qglRasterPos3f glRasterPos3f # define qglRasterPos3fv glRasterPos3fv # define qglRasterPos3i glRasterPos3i # define qglRasterPos3iv glRasterPos3iv # define qglRasterPos3s glRasterPos3s # define qglRasterPos3sv glRasterPos3sv # define qglRasterPos4d glRasterPos4d # define qglRasterPos4dv glRasterPos4dv # define qglRasterPos4f glRasterPos4f # define qglRasterPos4fv glRasterPos4fv # define qglRasterPos4i glRasterPos4i # define qglRasterPos4iv glRasterPos4iv # define qglRasterPos4s glRasterPos4s # define qglRasterPos4sv glRasterPos4sv # define qglReadBuffer glReadBuffer # define qglReadPixels glReadPixels # define qglRectd glRectd # define qglRectdv glRectdv # define qglRectf glRectf # define qglRectfv glRectfv # define qglRecti glRecti # define qglRectiv glRectiv # define qglRects glRects # define qglRectsv glRectsv # define qglRenderMode glRenderMode # define qglRotated glRotated # define qglRotatef glRotatef # define qglScaled glScaled # define qglScalef glScalef # define qglScissor glScissor # define qglSelectBuffer glSelectBuffer # define qglShadeModel glShadeModel # define qglStencilFunc glStencilFunc # define qglStencilMask glStencilMask # define qglStencilOp glStencilOp # define qglTexCoord1d glTexCoord1d # define qglTexCoord1dv glTexCoord1dv # define qglTexCoord1f glTexCoord1f # define qglTexCoord1fv glTexCoord1fv # define qglTexCoord1i glTexCoord1i # define qglTexCoord1iv glTexCoord1iv # define qglTexCoord1s glTexCoord1s # define qglTexCoord1sv glTexCoord1sv # define qglTexCoord2d glTexCoord2d # define qglTexCoord2dv glTexCoord2dv # define qglTexCoord2f glTexCoord2f # define qglTexCoord2fv glTexCoord2fv # define qglTexCoord2i glTexCoord2i # define qglTexCoord2iv glTexCoord2iv # define qglTexCoord2s glTexCoord2s # define qglTexCoord2sv glTexCoord2sv # define qglTexCoord3d glTexCoord3d # define qglTexCoord3dv glTexCoord3dv # define qglTexCoord3f glTexCoord3f # define qglTexCoord3fv glTexCoord3fv # define qglTexCoord3i glTexCoord3i # define qglTexCoord3iv glTexCoord3iv # define qglTexCoord3s glTexCoord3s # define qglTexCoord3sv glTexCoord3sv # define qglTexCoord4d glTexCoord4d # define qglTexCoord4dv glTexCoord4dv # define qglTexCoord4f glTexCoord4f # define qglTexCoord4fv glTexCoord4fv # define qglTexCoord4i glTexCoord4i # define qglTexCoord4iv glTexCoord4iv # define qglTexCoord4s glTexCoord4s # define qglTexCoord4sv glTexCoord4sv # define qglTexCoordPointer glTexCoordPointer # define qglTexEnvf glTexEnvf # define qglTexEnvfv glTexEnvfv # define qglTexEnvi glTexEnvi # define qglTexEnviv glTexEnviv # define qglTexGend glTexGend # define qglTexGendv glTexGendv # define qglTexGenf glTexGenf # define qglTexGenfv glTexGenfv # define qglTexGeni glTexGeni # define qglTexGeniv glTexGeniv # define qglTexImage1D glTexImage1D # define qglTexImage2D glTexImage2D # define qglTexParameterf glTexParameterf # define qglTexParameterfv glTexParameterfv # define qglTexParameteri glTexParameteri # define qglTexParameteriv glTexParameteriv # define qglTexSubImage1D glTexSubImage1D # define qglTexSubImage2D glTexSubImage2D # define qglTranslated glTranslated # define qglTranslatef glTranslatef # define qglVertex2d glVertex2d # define qglVertex2dv glVertex2dv # define qglVertex2f glVertex2f # define qglVertex2fv glVertex2fv # define qglVertex2i glVertex2i # define qglVertex2iv glVertex2iv # define qglVertex2s glVertex2s # define qglVertex2sv glVertex2sv # define qglVertex3d glVertex3d # define qglVertex3dv glVertex3dv # define qglVertex3f glVertex3f # define qglVertex3fv glVertex3fv # define qglVertex3i glVertex3i # define qglVertex3iv glVertex3iv # define qglVertex3s glVertex3s # define qglVertex3sv glVertex3sv # define qglVertex4d glVertex4d # define qglVertex4dv glVertex4dv # define qglVertex4f glVertex4f # define qglVertex4fv glVertex4fv # define qglVertex4i glVertex4i # define qglVertex4iv glVertex4iv # define qglVertex4s glVertex4s # define qglVertex4sv glVertex4sv # define qglVertexPointer glVertexPointer # define qglViewport glViewport \ No newline at end of file diff --git a/src/renderer/ref_trin.def b/src/renderer/ref_trin.def new file mode 100644 index 0000000..cfbb471 --- /dev/null +++ b/src/renderer/ref_trin.def @@ -0,0 +1,2 @@ +EXPORTS + GetRefAPI diff --git a/src/renderer/renderer.vcproj b/src/renderer/renderer.vcproj new file mode 100644 index 0000000..2cba0ae --- /dev/null +++ b/src/renderer/renderer.vcproj @@ -0,0 +1,1805 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/tr_animation.c b/src/renderer/tr_animation.c new file mode 100644 index 0000000..49bf847 --- /dev/null +++ b/src/renderer/tr_animation.c @@ -0,0 +1,1498 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "tr_local.h" + +/* + +All bones should be an identity orientation to display the mesh exactly +as it is specified. + +For all other frames, the bones represent the transformation from the +orientation of the bone in the base frame to the orientation in this +frame. + +*/ + +//#define HIGH_PRECISION_BONES // enable this for 32bit precision bones +//#define DBG_PROFILE_BONES + +//----------------------------------------------------------------------------- +// Static Vars, ugly but easiest (and fastest) means of seperating RB_SurfaceAnim +// and R_CalcBones + +static float frontlerp, backlerp; +static float torsoFrontlerp, torsoBacklerp; +static int *triangles, *boneRefs, *pIndexes; +static int indexes; +static int baseIndex, baseVertex, oldIndexes; +static int numVerts; +static mdsVertex_t *v; +static mdsBoneFrame_t bones[MDS_MAX_BONES], rawBones[MDS_MAX_BONES], oldBones[MDS_MAX_BONES]; +static char validBones[MDS_MAX_BONES]; +static char newBones[ MDS_MAX_BONES ]; +static mdsBoneFrame_t *bonePtr, *bone, *parentBone; +static mdsBoneFrameCompressed_t *cBonePtr, *cTBonePtr, *cOldBonePtr, *cOldTBonePtr, *cBoneList, *cOldBoneList, *cBoneListTorso, *cOldBoneListTorso; +static mdsBoneInfo_t *boneInfo, *thisBoneInfo, *parentBoneInfo; +static mdsFrame_t *frame, *torsoFrame; +static mdsFrame_t *oldFrame, *oldTorsoFrame; +static int frameSize; +static short *sh, *sh2; +static float *pf; +static vec3_t angles, tangles, torsoParentOffset, torsoAxis[3], tmpAxis[3]; +static float *tempVert, *tempNormal; +static vec3_t vec, v2, dir; +static float diff, a1, a2; +static int render_count; +static float lodRadius, lodScale; +static int *collapse_map, *pCollapseMap; +static int collapse[ MDS_MAX_VERTS ], *pCollapse; +static int p0, p1, p2; +static qboolean isTorso, fullTorso; +static vec4_t m1[4], m2[4]; +// static vec4_t m3[4], m4[4]; // TTimo unused +// static vec4_t tmp1[4], tmp2[4]; // TTimo unused +static vec3_t t; +static refEntity_t lastBoneEntity; + +static int totalrv, totalrt, totalv, totalt; //----(SA) + +//----------------------------------------------------------------------------- + +static float ProjectRadius( float r, vec3_t location ) { + float pr; + float dist; + float c; + vec3_t p; + float projected[4]; + + c = DotProduct( tr.viewParms.or.axis[0], tr.viewParms.or.origin ); + dist = DotProduct( tr.viewParms.or.axis[0], location ) - c; + + if ( dist <= 0 ) { + return 0; + } + + p[0] = 0; + p[1] = fabs( r ); + p[2] = -dist; + + projected[0] = p[0] * tr.viewParms.projectionMatrix[0] + + p[1] * tr.viewParms.projectionMatrix[4] + + p[2] * tr.viewParms.projectionMatrix[8] + + tr.viewParms.projectionMatrix[12]; + + projected[1] = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + projected[2] = p[0] * tr.viewParms.projectionMatrix[2] + + p[1] * tr.viewParms.projectionMatrix[6] + + p[2] * tr.viewParms.projectionMatrix[10] + + tr.viewParms.projectionMatrix[14]; + + projected[3] = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + + pr = projected[1] / projected[3]; + + if ( pr > 1.0f ) { + pr = 1.0f; + } + + return pr; +} + +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( mdsHeader_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + mdsFrame_t *oldFrame, *newFrame; + int i, frameSize; + + frameSize = (int) ( sizeof( mdsFrame_t ) - sizeof( mdsBoneFrameCompressed_t ) + header->numBones * sizeof( mdsBoneFrameCompressed_t ) ); + + // compute frame pointers + newFrame = ( mdsFrame_t * )( ( byte * ) header + header->ofsFrames + ent->e.frame * frameSize ); + oldFrame = ( mdsFrame_t * )( ( byte * ) header + header->ofsFrames + ent->e.oldframe * frameSize ); + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) { + if ( ent->e.frame == ent->e.oldframe ) { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) { + if ( sphereCull == CULL_OUT ) { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } else if ( sphereCull == CULL_IN ) { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +R_CalcMDSLod + +================= +*/ +float R_CalcMDSLod( refEntity_t *refent, vec3_t origin, float radius, float modelBias, float modelScale ) { + float flod, lodScale; + float projectedRadius; + + // compute projected bounding sphere and use that as a criteria for selecting LOD + + projectedRadius = ProjectRadius( radius, origin ); + if ( projectedRadius != 0 ) { + +// ri.Printf (PRINT_ALL, "projected radius: %f\n", projectedRadius); + + lodScale = r_lodscale->value; // fudge factor since MDS uses a much smoother method of LOD + flod = projectedRadius * lodScale * modelScale; + } else + { + // object intersects near view plane, e.g. view weapon + flod = 1.0f; + } + + if ( refent->reFlags & REFLAG_FORCE_LOD ) { + flod *= 0.5; + } +//----(SA) like reflag_force_lod, but separate for the moment + if ( refent->reFlags & REFLAG_DEAD_LOD ) { + flod *= 0.8; + } + + flod -= 0.25 * ( r_lodbias->value ) + modelBias; + + if ( flod < 0.0 ) { + flod = 0.0; + } else if ( flod > 1.0f ) { + flod = 1.0f; + } + + return flod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +static int R_ComputeFogNum( mdsHeader_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + mdsFrame_t *mdsFrame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + mdsFrame = ( mdsFrame_t * )( ( byte * ) header + header->ofsFrames + ( sizeof( mdsFrame_t ) + sizeof( mdsBoneFrameCompressed_t ) * ( header->numBones - 1 ) ) * ent->e.frame ); + VectorAdd( ent->e.origin, mdsFrame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - mdsFrame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + mdsFrame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +============== +R_AddAnimSurfaces +============== +*/ +void R_AddAnimSurfaces( trRefEntity_t *ent ) { + mdsHeader_t *header; + mdsSurface_t *surface; + shader_t *shader = 0; + int i, fogNum, cull; + qboolean personalModel; + + // don't add third_person objects if not in a portal + personalModel = ( ent->e.renderfx & RF_THIRD_PERSON ) && !tr.viewParms.isPortal; + + header = tr.currentModel->mds; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel( header, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( header, ent ); + + surface = ( mdsSurface_t * )( (byte *)header + header->ofsSurfaces ); + for ( i = 0 ; i < header->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) { + shader = R_GetShaderByHandle( ent->e.customShader ); + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + + if ( ent->e.renderfx & RF_BLINK ) { + const char *s = va( "%s_b", surface->name ); // append '_b' for 'blink' + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + if ( !strcmp( skin->surfaces[j]->name, s ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + } + + if ( shader == tr.defaultShader ) { // blink reference in skin was not found + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + + if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + } + + if ( shader == tr.defaultShader ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name ); + } else if ( shader->defaultShader ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name ); + } + } else { + shader = R_GetShaderByHandle( surface->shaderIndex ); + } + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { + R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse ); + } + + surface = ( mdsSurface_t * )( (byte *)surface + surface->ofsEnd ); + } +} + +__inline void LocalMatrixTransformVector( vec3_t in, vec3_t mat[ 3 ], vec3_t out ) { + out[ 0 ] = in[ 0 ] * mat[ 0 ][ 0 ] + in[ 1 ] * mat[ 0 ][ 1 ] + in[ 2 ] * mat[ 0 ][ 2 ]; + out[ 1 ] = in[ 0 ] * mat[ 1 ][ 0 ] + in[ 1 ] * mat[ 1 ][ 1 ] + in[ 2 ] * mat[ 1 ][ 2 ]; + out[ 2 ] = in[ 0 ] * mat[ 2 ][ 0 ] + in[ 1 ] * mat[ 2 ][ 1 ] + in[ 2 ] * mat[ 2 ][ 2 ]; +} + +__inline void LocalMatrixTransformVectorTranslate( vec3_t in, vec3_t mat[ 3 ], vec3_t tr, vec3_t out ) { + out[ 0 ] = in[ 0 ] * mat[ 0 ][ 0 ] + in[ 1 ] * mat[ 0 ][ 1 ] + in[ 2 ] * mat[ 0 ][ 2 ] + tr[ 0 ]; + out[ 1 ] = in[ 0 ] * mat[ 1 ][ 0 ] + in[ 1 ] * mat[ 1 ][ 1 ] + in[ 2 ] * mat[ 1 ][ 2 ] + tr[ 1 ]; + out[ 2 ] = in[ 0 ] * mat[ 2 ][ 0 ] + in[ 1 ] * mat[ 2 ][ 1 ] + in[ 2 ] * mat[ 2 ][ 2 ] + tr[ 2 ]; +} + +__inline void LocalScaledMatrixTransformVector( vec3_t in, float s, vec3_t mat[ 3 ], vec3_t out ) { + out[ 0 ] = ( 1.0f - s ) * in[ 0 ] + s * ( in[ 0 ] * mat[ 0 ][ 0 ] + in[ 1 ] * mat[ 0 ][ 1 ] + in[ 2 ] * mat[ 0 ][ 2 ] ); + out[ 1 ] = ( 1.0f - s ) * in[ 1 ] + s * ( in[ 0 ] * mat[ 1 ][ 0 ] + in[ 1 ] * mat[ 1 ][ 1 ] + in[ 2 ] * mat[ 1 ][ 2 ] ); + out[ 2 ] = ( 1.0f - s ) * in[ 2 ] + s * ( in[ 0 ] * mat[ 2 ][ 0 ] + in[ 1 ] * mat[ 2 ][ 1 ] + in[ 2 ] * mat[ 2 ][ 2 ] ); +} + +__inline void LocalScaledMatrixTransformVectorTranslate( vec3_t in, float s, vec3_t mat[ 3 ], vec3_t tr, vec3_t out ) { + out[ 0 ] = ( 1.0f - s ) * in[ 0 ] + s * ( in[ 0 ] * mat[ 0 ][ 0 ] + in[ 1 ] * mat[ 0 ][ 1 ] + in[ 2 ] * mat[ 0 ][ 2 ] + tr[ 0 ] ); + out[ 1 ] = ( 1.0f - s ) * in[ 1 ] + s * ( in[ 0 ] * mat[ 1 ][ 0 ] + in[ 1 ] * mat[ 1 ][ 1 ] + in[ 2 ] * mat[ 1 ][ 2 ] + tr[ 1 ] ); + out[ 2 ] = ( 1.0f - s ) * in[ 2 ] + s * ( in[ 0 ] * mat[ 2 ][ 0 ] + in[ 1 ] * mat[ 2 ][ 1 ] + in[ 2 ] * mat[ 2 ][ 2 ] + tr[ 2 ] ); +} + +__inline void LocalScaledMatrixTransformVectorFullTranslate( vec3_t in, float s, vec3_t mat[ 3 ], vec3_t tr, vec3_t out ) { + out[ 0 ] = ( 1.0f - s ) * in[ 0 ] + s * ( in[ 0 ] * mat[ 0 ][ 0 ] + in[ 1 ] * mat[ 0 ][ 1 ] + in[ 2 ] * mat[ 0 ][ 2 ] ) + tr[ 0 ]; + out[ 1 ] = ( 1.0f - s ) * in[ 1 ] + s * ( in[ 0 ] * mat[ 1 ][ 0 ] + in[ 1 ] * mat[ 1 ][ 1 ] + in[ 2 ] * mat[ 1 ][ 2 ] ) + tr[ 1 ]; + out[ 2 ] = ( 1.0f - s ) * in[ 2 ] + s * ( in[ 0 ] * mat[ 2 ][ 0 ] + in[ 1 ] * mat[ 2 ][ 1 ] + in[ 2 ] * mat[ 2 ][ 2 ] ) + tr[ 2 ]; +} + +__inline void LocalAddScaledMatrixTransformVectorFullTranslate( vec3_t in, float s, vec3_t mat[ 3 ], vec3_t tr, vec3_t out ) { + out[ 0 ] += s * ( in[ 0 ] * mat[ 0 ][ 0 ] + in[ 1 ] * mat[ 0 ][ 1 ] + in[ 2 ] * mat[ 0 ][ 2 ] ) + tr[ 0 ]; + out[ 1 ] += s * ( in[ 0 ] * mat[ 1 ][ 0 ] + in[ 1 ] * mat[ 1 ][ 1 ] + in[ 2 ] * mat[ 1 ][ 2 ] ) + tr[ 1 ]; + out[ 2 ] += s * ( in[ 0 ] * mat[ 2 ][ 0 ] + in[ 1 ] * mat[ 2 ][ 1 ] + in[ 2 ] * mat[ 2 ][ 2 ] ) + tr[ 2 ]; +} + +__inline void LocalAddScaledMatrixTransformVectorTranslate( vec3_t in, float s, vec3_t mat[ 3 ], vec3_t tr, vec3_t out ) { + out[ 0 ] += s * ( in[ 0 ] * mat[ 0 ][ 0 ] + in[ 1 ] * mat[ 0 ][ 1 ] + in[ 2 ] * mat[ 0 ][ 2 ] + tr[ 0 ] ); + out[ 1 ] += s * ( in[ 0 ] * mat[ 1 ][ 0 ] + in[ 1 ] * mat[ 1 ][ 1 ] + in[ 2 ] * mat[ 1 ][ 2 ] + tr[ 1 ] ); + out[ 2 ] += s * ( in[ 0 ] * mat[ 2 ][ 0 ] + in[ 1 ] * mat[ 2 ][ 1 ] + in[ 2 ] * mat[ 2 ][ 2 ] + tr[ 2 ] ); +} + +__inline void LocalAddScaledMatrixTransformVector( vec3_t in, float s, vec3_t mat[ 3 ], vec3_t out ) { + out[ 0 ] += s * ( in[ 0 ] * mat[ 0 ][ 0 ] + in[ 1 ] * mat[ 0 ][ 1 ] + in[ 2 ] * mat[ 0 ][ 2 ] ); + out[ 1 ] += s * ( in[ 0 ] * mat[ 1 ][ 0 ] + in[ 1 ] * mat[ 1 ][ 1 ] + in[ 2 ] * mat[ 1 ][ 2 ] ); + out[ 2 ] += s * ( in[ 0 ] * mat[ 2 ][ 0 ] + in[ 1 ] * mat[ 2 ][ 1 ] + in[ 2 ] * mat[ 2 ][ 2 ] ); +} + +static float LAVangle; +static float sp, sy, cp, cy; +//static float sr, cr;// TTimo: unused + +__inline void LocalAngleVector( vec3_t angles, vec3_t forward ) { + LAVangle = angles[YAW] * ( M_PI * 2 / 360 ); + sy = sin( LAVangle ); + cy = cos( LAVangle ); + LAVangle = angles[PITCH] * ( M_PI * 2 / 360 ); + sp = sin( LAVangle ); + cp = cos( LAVangle ); + + forward[0] = cp * cy; + forward[1] = cp * sy; + forward[2] = -sp; +} + +__inline void LocalVectorMA( vec3_t org, float dist, vec3_t vec, vec3_t out ) { + out[0] = org[0] + dist * vec[0]; + out[1] = org[1] + dist * vec[1]; + out[2] = org[2] + dist * vec[2]; +} + +#define ANGLES_SHORT_TO_FLOAT( pf, sh ) { *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); } + +__inline void SLerp_Normal( vec3_t from, vec3_t to, float tt, vec3_t out ) { + float ft = 1.0 - tt; + + out[0] = from[0] * ft + to[0] * tt; + out[1] = from[1] * ft + to[1] * tt; + out[2] = from[2] * ft + to[2] * tt; + + VectorNormalize( out ); +} + +/* +=============================================================================== + +4x4 Matrices + +=============================================================================== +*/ + +__inline void Matrix4Multiply( const vec4_t a[4], const vec4_t b[4], vec4_t dst[4] ) { + dst[0][0] = a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0] + a[0][3] * b[3][0]; + dst[0][1] = a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1] + a[0][3] * b[3][1]; + dst[0][2] = a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2] + a[0][3] * b[3][2]; + dst[0][3] = a[0][0] * b[0][3] + a[0][1] * b[1][3] + a[0][2] * b[2][3] + a[0][3] * b[3][3]; + + dst[1][0] = a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0] + a[1][3] * b[3][0]; + dst[1][1] = a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1] + a[1][3] * b[3][1]; + dst[1][2] = a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2] + a[1][3] * b[3][2]; + dst[1][3] = a[1][0] * b[0][3] + a[1][1] * b[1][3] + a[1][2] * b[2][3] + a[1][3] * b[3][3]; + + dst[2][0] = a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0] + a[2][3] * b[3][0]; + dst[2][1] = a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1] + a[2][3] * b[3][1]; + dst[2][2] = a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2] + a[2][3] * b[3][2]; + dst[2][3] = a[2][0] * b[0][3] + a[2][1] * b[1][3] + a[2][2] * b[2][3] + a[2][3] * b[3][3]; + + dst[3][0] = a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + a[3][3] * b[3][0]; + dst[3][1] = a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + a[3][3] * b[3][1]; + dst[3][2] = a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + a[3][3] * b[3][2]; + dst[3][3] = a[3][0] * b[0][3] + a[3][1] * b[1][3] + a[3][2] * b[2][3] + a[3][3] * b[3][3]; +} + +// TTimo: const usage would require an explicit cast, non ANSI C +// see unix/const-arg.c +__inline void Matrix4MultiplyInto3x3AndTranslation( /*const*/ vec4_t a[4], /*const*/ vec4_t b[4], vec3_t dst[3], vec3_t t ) { + dst[0][0] = a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0] + a[0][3] * b[3][0]; + dst[0][1] = a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1] + a[0][3] * b[3][1]; + dst[0][2] = a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2] + a[0][3] * b[3][2]; + t[0] = a[0][0] * b[0][3] + a[0][1] * b[1][3] + a[0][2] * b[2][3] + a[0][3] * b[3][3]; + + dst[1][0] = a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0] + a[1][3] * b[3][0]; + dst[1][1] = a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1] + a[1][3] * b[3][1]; + dst[1][2] = a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2] + a[1][3] * b[3][2]; + t[1] = a[1][0] * b[0][3] + a[1][1] * b[1][3] + a[1][2] * b[2][3] + a[1][3] * b[3][3]; + + dst[2][0] = a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0] + a[2][3] * b[3][0]; + dst[2][1] = a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1] + a[2][3] * b[3][1]; + dst[2][2] = a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2] + a[2][3] * b[3][2]; + t[2] = a[2][0] * b[0][3] + a[2][1] * b[1][3] + a[2][2] * b[2][3] + a[2][3] * b[3][3]; +} + +__inline void Matrix4Transpose( const vec4_t matrix[4], vec4_t transpose[4] ) { + int i, j; + for ( i = 0; i < 4; i++ ) { + for ( j = 0; j < 4; j++ ) { + transpose[i][j] = matrix[j][i]; + } + } +} + +__inline void Matrix4FromAxis( const vec3_t axis[3], vec4_t dst[4] ) { + int i, j; + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + dst[i][j] = axis[i][j]; + } + dst[3][i] = 0; + dst[i][3] = 0; + } + dst[3][3] = 1; +} + +__inline void Matrix4FromScaledAxis( const vec3_t axis[3], const float scale, vec4_t dst[4] ) { + int i, j; + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + dst[i][j] = scale * axis[i][j]; + if ( i == j ) { + dst[i][j] += 1.0f - scale; + } + } + dst[3][i] = 0; + dst[i][3] = 0; + } + dst[3][3] = 1; +} + +__inline void Matrix4FromTranslation( const vec3_t t, vec4_t dst[4] ) { + int i, j; + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( i == j ) { + dst[i][j] = 1; + } else { + dst[i][j] = 0; + } + } + dst[i][3] = t[i]; + dst[3][i] = 0; + } + dst[3][3] = 1; +} + +// can put an axis rotation followed by a translation directly into one matrix +// TTimo: const usage would require an explicit cast, non ANSI C +// see unix/const-arg.c +__inline void Matrix4FromAxisPlusTranslation( /*const*/ vec3_t axis[3], const vec3_t t, vec4_t dst[4] ) { + int i, j; + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + dst[i][j] = axis[i][j]; + } + dst[3][i] = 0; + dst[i][3] = t[i]; + } + dst[3][3] = 1; +} + +// can put a scaled axis rotation followed by a translation directly into one matrix +// TTimo: const usage would require an explicit cast, non ANSI C +// see unix/const-arg.c +__inline void Matrix4FromScaledAxisPlusTranslation( /*const*/ vec3_t axis[3], const float scale, const vec3_t t, vec4_t dst[4] ) { + int i, j; + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + dst[i][j] = scale * axis[i][j]; + if ( i == j ) { + dst[i][j] += 1.0f - scale; + } + } + dst[3][i] = 0; + dst[i][3] = t[i]; + } + dst[3][3] = 1; +} + +__inline void Matrix4FromScale( const float scale, vec4_t dst[4] ) { + int i, j; + + for ( i = 0; i < 4; i++ ) { + for ( j = 0; j < 4; j++ ) { + if ( i == j ) { + dst[i][j] = scale; + } else { + dst[i][j] = 0; + } + } + } + dst[3][3] = 1; +} + +__inline void Matrix4TransformVector( const vec4_t m[4], const vec3_t src, vec3_t dst ) { + dst[0] = m[0][0] * src[0] + m[0][1] * src[1] + m[0][2] * src[2] + m[0][3]; + dst[1] = m[1][0] * src[0] + m[1][1] * src[1] + m[1][2] * src[2] + m[1][3]; + dst[2] = m[2][0] * src[0] + m[2][1] * src[1] + m[2][2] * src[2] + m[2][3]; +} + +/* +=============================================================================== + +3x3 Matrices + +=============================================================================== +*/ + +__inline void Matrix3Transpose( const vec3_t matrix[3], vec3_t transpose[3] ) { + int i, j; + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + transpose[i][j] = matrix[j][i]; + } + } +} + + +/* +============== +R_CalcBone +============== +*/ +void R_CalcBone( mdsHeader_t *header, const refEntity_t *refent, int boneNum ) { + int j; + + thisBoneInfo = &boneInfo[boneNum]; + if ( thisBoneInfo->torsoWeight ) { + cTBonePtr = &cBoneListTorso[boneNum]; + isTorso = qtrue; + if ( thisBoneInfo->torsoWeight == 1.0f ) { + fullTorso = qtrue; + } + } else { + isTorso = qfalse; + fullTorso = qfalse; + } + cBonePtr = &cBoneList[boneNum]; + + bonePtr = &bones[ boneNum ]; + + // we can assume the parent has already been uncompressed for this frame + lerp + if ( thisBoneInfo->parent >= 0 ) { + parentBone = &bones[ thisBoneInfo->parent ]; + parentBoneInfo = &boneInfo[ thisBoneInfo->parent ]; + } else { + parentBone = NULL; + parentBoneInfo = NULL; + } + +#ifdef HIGH_PRECISION_BONES + // rotation + if ( fullTorso ) { + VectorCopy( cTBonePtr->angles, angles ); + } else { + VectorCopy( cBonePtr->angles, angles ); + if ( isTorso ) { + VectorCopy( cTBonePtr->angles, tangles ); + // blend the angles together + for ( j = 0; j < 3; j++ ) { + diff = tangles[j] - angles[j]; + if ( fabs( diff ) > 180 ) { + diff = AngleNormalize180( diff ); + } + angles[j] = angles[j] + thisBoneInfo->torsoWeight * diff; + } + } + } +#else + // rotation + if ( fullTorso ) { + sh = (short *)cTBonePtr->angles; + pf = angles; + ANGLES_SHORT_TO_FLOAT( pf, sh ); + } else { + sh = (short *)cBonePtr->angles; + pf = angles; + ANGLES_SHORT_TO_FLOAT( pf, sh ); + if ( isTorso ) { + sh = (short *)cTBonePtr->angles; + pf = tangles; + ANGLES_SHORT_TO_FLOAT( pf, sh ); + // blend the angles together + for ( j = 0; j < 3; j++ ) { + diff = tangles[j] - angles[j]; + if ( fabs( diff ) > 180 ) { + diff = AngleNormalize180( diff ); + } + angles[j] = angles[j] + thisBoneInfo->torsoWeight * diff; + } + } + } +#endif + AnglesToAxis( angles, bonePtr->matrix ); + + // translation + if ( parentBone ) { + +#ifdef HIGH_PRECISION_BONES + if ( fullTorso ) { + angles[0] = cTBonePtr->ofsAngles[0]; + angles[1] = cTBonePtr->ofsAngles[1]; + angles[2] = 0; + LocalAngleVector( angles, vec ); + LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, vec, bonePtr->translation ); + } else { + + angles[0] = cBonePtr->ofsAngles[0]; + angles[1] = cBonePtr->ofsAngles[1]; + angles[2] = 0; + LocalAngleVector( angles, vec ); + + if ( isTorso ) { + tangles[0] = cTBonePtr->ofsAngles[0]; + tangles[1] = cTBonePtr->ofsAngles[1]; + tangles[2] = 0; + LocalAngleVector( tangles, v2 ); + + // blend the angles together + SLerp_Normal( vec, v2, thisBoneInfo->torsoWeight, vec ); + LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, vec, bonePtr->translation ); + + } else { // legs bone + LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, vec, bonePtr->translation ); + } + } +#else + if ( fullTorso ) { + sh = (short *)cTBonePtr->ofsAngles; pf = angles; + *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); *( pf++ ) = 0; + LocalAngleVector( angles, vec ); + LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, vec, bonePtr->translation ); + } else { + + sh = (short *)cBonePtr->ofsAngles; pf = angles; + *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); *( pf++ ) = 0; + LocalAngleVector( angles, vec ); + + if ( isTorso ) { + sh = (short *)cTBonePtr->ofsAngles; + pf = tangles; + *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); *( pf++ ) = 0; + LocalAngleVector( tangles, v2 ); + + // blend the angles together + SLerp_Normal( vec, v2, thisBoneInfo->torsoWeight, vec ); + LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, vec, bonePtr->translation ); + + } else { // legs bone + LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, vec, bonePtr->translation ); + } + } +#endif + } else { // just use the frame position + bonePtr->translation[0] = frame->parentOffset[0]; + bonePtr->translation[1] = frame->parentOffset[1]; + bonePtr->translation[2] = frame->parentOffset[2]; + } + // + if ( boneNum == header->torsoParent ) { // this is the torsoParent + VectorCopy( bonePtr->translation, torsoParentOffset ); + } + // + validBones[boneNum] = 1; + // + rawBones[boneNum] = *bonePtr; + newBones[boneNum] = 1; + +} + +/* +============== +R_CalcBoneLerp +============== +*/ +void R_CalcBoneLerp( mdsHeader_t *header, const refEntity_t *refent, int boneNum ) { + int j; + + if ( !refent || !header || boneNum < 0 || boneNum >= MDS_MAX_BONES ) { + return; + } + + + thisBoneInfo = &boneInfo[boneNum]; + + if ( !thisBoneInfo ) { + return; + } + + if ( thisBoneInfo->parent >= 0 ) { + parentBone = &bones[ thisBoneInfo->parent ]; + parentBoneInfo = &boneInfo[ thisBoneInfo->parent ]; + } else { + parentBone = NULL; + parentBoneInfo = NULL; + } + + if ( thisBoneInfo->torsoWeight ) { + cTBonePtr = &cBoneListTorso[boneNum]; + cOldTBonePtr = &cOldBoneListTorso[boneNum]; + isTorso = qtrue; + if ( thisBoneInfo->torsoWeight == 1.0f ) { + fullTorso = qtrue; + } + } else { + isTorso = qfalse; + fullTorso = qfalse; + } + cBonePtr = &cBoneList[boneNum]; + cOldBonePtr = &cOldBoneList[boneNum]; + + bonePtr = &bones[boneNum]; + + newBones[ boneNum ] = 1; + + // rotation (take into account 170 to -170 lerps, which need to take the shortest route) + if ( fullTorso ) { + + sh = (short *)cTBonePtr->angles; + sh2 = (short *)cOldTBonePtr->angles; + pf = angles; + + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - torsoBacklerp * diff; + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - torsoBacklerp * diff; + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - torsoBacklerp * diff; + + } else { + + sh = (short *)cBonePtr->angles; + sh2 = (short *)cOldBonePtr->angles; + pf = angles; + + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - backlerp * diff; + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - backlerp * diff; + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - backlerp * diff; + + if ( isTorso ) { + + sh = (short *)cTBonePtr->angles; + sh2 = (short *)cOldTBonePtr->angles; + pf = tangles; + + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - torsoBacklerp * diff; + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - torsoBacklerp * diff; + a1 = SHORT2ANGLE( *( sh++ ) ); a2 = SHORT2ANGLE( *( sh2++ ) ); diff = AngleNormalize180( a1 - a2 ); + *( pf++ ) = a1 - torsoBacklerp * diff; + + // blend the angles together + for ( j = 0; j < 3; j++ ) { + diff = tangles[j] - angles[j]; + if ( fabs( diff ) > 180 ) { + diff = AngleNormalize180( diff ); + } + angles[j] = angles[j] + thisBoneInfo->torsoWeight * diff; + } + + } + + } + AnglesToAxis( angles, bonePtr->matrix ); + + if ( parentBone ) { + + if ( fullTorso ) { + sh = (short *)cTBonePtr->ofsAngles; + sh2 = (short *)cOldTBonePtr->ofsAngles; + } else { + sh = (short *)cBonePtr->ofsAngles; + sh2 = (short *)cOldBonePtr->ofsAngles; + } + + pf = angles; + *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); + *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); + *( pf++ ) = 0; + LocalAngleVector( angles, v2 ); // new + + pf = angles; + *( pf++ ) = SHORT2ANGLE( *( sh2++ ) ); + *( pf++ ) = SHORT2ANGLE( *( sh2++ ) ); + *( pf++ ) = 0; + LocalAngleVector( angles, vec ); // old + + // blend the angles together + if ( fullTorso ) { + SLerp_Normal( vec, v2, torsoFrontlerp, dir ); + } else { + SLerp_Normal( vec, v2, frontlerp, dir ); + } + + // translation + if ( !fullTorso && isTorso ) { // partial legs/torso, need to lerp according to torsoWeight + + // calc the torso frame + sh = (short *)cTBonePtr->ofsAngles; + sh2 = (short *)cOldTBonePtr->ofsAngles; + + pf = angles; + *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); + *( pf++ ) = SHORT2ANGLE( *( sh++ ) ); + *( pf++ ) = 0; + LocalAngleVector( angles, v2 ); // new + + pf = angles; + *( pf++ ) = SHORT2ANGLE( *( sh2++ ) ); + *( pf++ ) = SHORT2ANGLE( *( sh2++ ) ); + *( pf++ ) = 0; + LocalAngleVector( angles, vec ); // old + + // blend the angles together + SLerp_Normal( vec, v2, torsoFrontlerp, v2 ); + + // blend the torso/legs together + SLerp_Normal( dir, v2, thisBoneInfo->torsoWeight, dir ); + + } + + LocalVectorMA( parentBone->translation, thisBoneInfo->parentDist, dir, bonePtr->translation ); + + } else { // just interpolate the frame positions + + bonePtr->translation[0] = frontlerp * frame->parentOffset[0] + backlerp * oldFrame->parentOffset[0]; + bonePtr->translation[1] = frontlerp * frame->parentOffset[1] + backlerp * oldFrame->parentOffset[1]; + bonePtr->translation[2] = frontlerp * frame->parentOffset[2] + backlerp * oldFrame->parentOffset[2]; + + } + // + if ( boneNum == header->torsoParent ) { // this is the torsoParent + VectorCopy( bonePtr->translation, torsoParentOffset ); + } + validBones[boneNum] = 1; + // + rawBones[boneNum] = *bonePtr; + newBones[boneNum] = 1; + +} + + +/* +============== +R_CalcBones + + The list of bones[] should only be built and modified from within here +============== +*/ +void R_CalcBones( mdsHeader_t *header, const refEntity_t *refent, int *boneList, int numBones ) { + + int i; + int *boneRefs; + float torsoWeight; + + // + // if the entity has changed since the last time the bones were built, reset them + // + if ( memcmp( &lastBoneEntity, refent, sizeof( refEntity_t ) ) ) { + // different, cached bones are not valid + memset( validBones, 0, header->numBones ); + lastBoneEntity = *refent; + + // (SA) also reset these counter statics +//----(SA) print stats for the complete model (not per-surface) + if ( r_bonesDebug->integer == 4 && totalrt ) { + ri.Printf( PRINT_ALL, "Lod %.2f verts %4d/%4d tris %4d/%4d (%.2f%%)\n", + lodScale, + totalrv, + totalv, + totalrt, + totalt, + ( float )( 100.0 * totalrt ) / (float) totalt ); + } +//----(SA) end + totalrv = totalrt = totalv = totalt = 0; + + } + + memset( newBones, 0, header->numBones ); + + if ( refent->oldframe == refent->frame ) { + backlerp = 0; + frontlerp = 1; + } else { + backlerp = refent->backlerp; + frontlerp = 1.0f - backlerp; + } + + if ( refent->oldTorsoFrame == refent->torsoFrame ) { + torsoBacklerp = 0; + torsoFrontlerp = 1; + } else { + torsoBacklerp = refent->torsoBacklerp; + torsoFrontlerp = 1.0f - torsoBacklerp; + } + + frameSize = (int) ( sizeof( mdsFrame_t ) + ( header->numBones - 1 ) * sizeof( mdsBoneFrameCompressed_t ) ); + + frame = ( mdsFrame_t * )( (byte *)header + header->ofsFrames + + refent->frame * frameSize ); + torsoFrame = ( mdsFrame_t * )( (byte *)header + header->ofsFrames + + refent->torsoFrame * frameSize ); + oldFrame = ( mdsFrame_t * )( (byte *)header + header->ofsFrames + + refent->oldframe * frameSize ); + oldTorsoFrame = ( mdsFrame_t * )( (byte *)header + header->ofsFrames + + refent->oldTorsoFrame * frameSize ); + + // + // lerp all the needed bones (torsoParent is always the first bone in the list) + // + cBoneList = frame->bones; + cBoneListTorso = torsoFrame->bones; + + boneInfo = ( mdsBoneInfo_t * )( (byte *)header + header->ofsBones ); + boneRefs = boneList; + // + Matrix3Transpose( refent->torsoAxis, torsoAxis ); + +#ifdef HIGH_PRECISION_BONES + if ( qtrue ) { +#else + if ( !backlerp && !torsoBacklerp ) { +#endif + + for ( i = 0; i < numBones; i++, boneRefs++ ) { + + if ( validBones[*boneRefs] ) { + // this bone is still in the cache + bones[*boneRefs] = rawBones[*boneRefs]; + continue; + } + + // find our parent, and make sure it has been calculated + if ( ( boneInfo[*boneRefs].parent >= 0 ) && ( !validBones[boneInfo[*boneRefs].parent] && !newBones[boneInfo[*boneRefs].parent] ) ) { + R_CalcBone( header, refent, boneInfo[*boneRefs].parent ); + } + + R_CalcBone( header, refent, *boneRefs ); + + } + + } else { // interpolated + + cOldBoneList = oldFrame->bones; + cOldBoneListTorso = oldTorsoFrame->bones; + + for ( i = 0; i < numBones; i++, boneRefs++ ) { + + if ( validBones[*boneRefs] ) { + // this bone is still in the cache + bones[*boneRefs] = rawBones[*boneRefs]; + continue; + } + + // find our parent, and make sure it has been calculated + if ( ( boneInfo[*boneRefs].parent >= 0 ) && ( !validBones[boneInfo[*boneRefs].parent] && !newBones[boneInfo[*boneRefs].parent] ) ) { + R_CalcBoneLerp( header, refent, boneInfo[*boneRefs].parent ); + } + + R_CalcBoneLerp( header, refent, *boneRefs ); + + } + + } + + // adjust for torso rotations + torsoWeight = 0; + boneRefs = boneList; + for ( i = 0; i < numBones; i++, boneRefs++ ) { + + thisBoneInfo = &boneInfo[ *boneRefs ]; + bonePtr = &bones[ *boneRefs ]; + // add torso rotation + if ( thisBoneInfo->torsoWeight > 0 ) { + + if ( !newBones[ *boneRefs ] ) { + // just copy it back from the previous calc + bones[ *boneRefs ] = oldBones[ *boneRefs ]; + continue; + } + + if ( !( thisBoneInfo->flags & BONEFLAG_TAG ) ) { + + // 1st multiply with the bone->matrix + // 2nd translation for rotation relative to bone around torso parent offset + VectorSubtract( bonePtr->translation, torsoParentOffset, t ); + Matrix4FromAxisPlusTranslation( bonePtr->matrix, t, m1 ); + // 3rd scaled rotation + // 4th translate back to torso parent offset + // use previously created matrix if available for the same weight + if ( torsoWeight != thisBoneInfo->torsoWeight ) { + Matrix4FromScaledAxisPlusTranslation( torsoAxis, thisBoneInfo->torsoWeight, torsoParentOffset, m2 ); + torsoWeight = thisBoneInfo->torsoWeight; + } + // multiply matrices to create one matrix to do all calculations + Matrix4MultiplyInto3x3AndTranslation( m2, m1, bonePtr->matrix, bonePtr->translation ); + + } else { // tag's require special handling + + // rotate each of the axis by the torsoAngles + LocalScaledMatrixTransformVector( bonePtr->matrix[0], thisBoneInfo->torsoWeight, torsoAxis, tmpAxis[0] ); + LocalScaledMatrixTransformVector( bonePtr->matrix[1], thisBoneInfo->torsoWeight, torsoAxis, tmpAxis[1] ); + LocalScaledMatrixTransformVector( bonePtr->matrix[2], thisBoneInfo->torsoWeight, torsoAxis, tmpAxis[2] ); + memcpy( bonePtr->matrix, tmpAxis, sizeof( tmpAxis ) ); + + // rotate the translation around the torsoParent + VectorSubtract( bonePtr->translation, torsoParentOffset, t ); + LocalScaledMatrixTransformVector( t, thisBoneInfo->torsoWeight, torsoAxis, bonePtr->translation ); + VectorAdd( bonePtr->translation, torsoParentOffset, bonePtr->translation ); + + } + } + } + + // backup the final bones + memcpy( oldBones, bones, sizeof( bones[0] ) * header->numBones ); +} + +#ifdef DBG_PROFILE_BONES +#define DBG_SHOWTIME Com_Printf( "%i: %i, ", di++, ( dt = ri.Milliseconds() ) - ldt ); ldt = dt; +#else +#define DBG_SHOWTIME ; +#endif + +/* +============== +RB_SurfaceAnim +============== +*/ +void RB_SurfaceAnim( mdsSurface_t *surface ) { + int i, j, k; + refEntity_t *refent; + int *boneList; + mdsHeader_t *header; + +#ifdef DBG_PROFILE_BONES + int di = 0, dt, ldt; + + dt = ri.Milliseconds(); + ldt = dt; +#endif + + refent = &backEnd.currentEntity->e; + boneList = ( int * )( (byte *)surface + surface->ofsBoneReferences ); + header = ( mdsHeader_t * )( (byte *)surface + surface->ofsHeader ); + + R_CalcBones( header, (const refEntity_t *)refent, boneList, surface->numBoneReferences ); + + DBG_SHOWTIME + + // + // calculate LOD + // + // TODO: lerp the radius and origin + VectorAdd( refent->origin, frame->localOrigin, vec ); + lodRadius = frame->radius; + lodScale = R_CalcMDSLod( refent, vec, lodRadius, header->lodBias, header->lodScale ); + + +//DBG_SHOWTIME + +//----(SA) modification to allow dead skeletal bodies to go below minlod (experiment) + if ( refent->reFlags & REFLAG_DEAD_LOD ) { + if ( lodScale < 0.35 ) { // allow dead to lod down to 35% (even if below surf->minLod) (%35 is arbitrary and probably not good generally. worked for the blackguard/infantry as a test though) + lodScale = 0.35; + } + render_count = (int)( (float) surface->numVerts * lodScale ); + + } else { + render_count = (int)( (float) surface->numVerts * lodScale ); + if ( render_count < surface->minLod ) { + if ( !( refent->reFlags & REFLAG_DEAD_LOD ) ) { + render_count = surface->minLod; + } + } + } +//----(SA) end + + + if ( render_count > surface->numVerts ) { + render_count = surface->numVerts; + } + + RB_CheckOverflow( render_count, surface->numTriangles ); + +//DBG_SHOWTIME + + // + // setup triangle list + // + RB_CheckOverflow( surface->numVerts, surface->numTriangles * 3 ); + +//DBG_SHOWTIME + + collapse_map = ( int * )( ( byte * )surface + surface->ofsCollapseMap ); + triangles = ( int * )( (byte *)surface + surface->ofsTriangles ); + indexes = surface->numTriangles * 3; + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + oldIndexes = baseIndex; + + tess.numVertexes += render_count; + + pIndexes = (int*)&tess.indexes[baseIndex]; + +//DBG_SHOWTIME + + if ( render_count == surface->numVerts ) { + memcpy( pIndexes, triangles, sizeof( triangles[0] ) * indexes ); + if ( baseVertex ) { + int *indexesEnd; + for ( indexesEnd = pIndexes + indexes ; pIndexes < indexesEnd ; pIndexes++ ) { + *pIndexes += baseVertex; + } + } + tess.numIndexes += indexes; + } else + { + int *collapseEnd; + + pCollapse = collapse; + for ( j = 0; j < render_count; pCollapse++, j++ ) + { + *pCollapse = j; + } + + pCollapseMap = &collapse_map[render_count]; + for ( collapseEnd = collapse + surface->numVerts ; pCollapse < collapseEnd; pCollapse++, pCollapseMap++ ) + { + *pCollapse = collapse[ *pCollapseMap ]; + } + + for ( j = 0 ; j < indexes ; j += 3 ) + { + p0 = collapse[ *( triangles++ ) ]; + p1 = collapse[ *( triangles++ ) ]; + p2 = collapse[ *( triangles++ ) ]; + + // FIXME + // note: serious optimization opportunity here, + // by sorting the triangles the following "continue" + // could have been made into a "break" statement. + if ( p0 == p1 || p1 == p2 || p2 == p0 ) { + continue; + } + + *( pIndexes++ ) = baseVertex + p0; + *( pIndexes++ ) = baseVertex + p1; + *( pIndexes++ ) = baseVertex + p2; + tess.numIndexes += 3; + } + + baseIndex = tess.numIndexes; + } + +//DBG_SHOWTIME + + // + // deform the vertexes by the lerped bones + // + numVerts = surface->numVerts; + v = ( mdsVertex_t * )( (byte *)surface + surface->ofsVerts ); + tempVert = ( float * )( tess.xyz + baseVertex ); + tempNormal = ( float * )( tess.normal + baseVertex ); + for ( j = 0; j < render_count; j++, tempVert += 4, tempNormal += 4 ) { + mdsWeight_t *w; + + VectorClear( tempVert ); + + w = v->weights; + for ( k = 0 ; k < v->numWeights ; k++, w++ ) { + bone = &bones[w->boneIndex]; + LocalAddScaledMatrixTransformVectorTranslate( w->offset, w->boneWeight, bone->matrix, bone->translation, tempVert ); + } + + LocalMatrixTransformVector( v->normal, bones[v->weights[0].boneIndex].matrix, tempNormal ); + + tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; + tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; + + v = (mdsVertex_t *)&v->weights[v->numWeights]; + } + + DBG_SHOWTIME + + if ( r_bonesDebug->integer ) { + if ( r_bonesDebug->integer < 3 ) { + // DEBUG: show the bones as a stick figure with axis at each bone + boneRefs = ( int * )( (byte *)surface + surface->ofsBoneReferences ); + for ( i = 0; i < surface->numBoneReferences; i++, boneRefs++ ) { + bonePtr = &bones[*boneRefs]; + + GL_Bind( tr.whiteImage ); + qglLineWidth( 1 ); + qglBegin( GL_LINES ); + for ( j = 0; j < 3; j++ ) { + VectorClear( vec ); + vec[j] = 1; + qglColor3fv( vec ); + qglVertex3fv( bonePtr->translation ); + VectorMA( bonePtr->translation, 5, bonePtr->matrix[j], vec ); + qglVertex3fv( vec ); + } + qglEnd(); + + // connect to our parent if it's valid + if ( validBones[boneInfo[*boneRefs].parent] ) { + qglLineWidth( 2 ); + qglBegin( GL_LINES ); + qglColor3f( .6,.6,.6 ); + qglVertex3fv( bonePtr->translation ); + qglVertex3fv( bones[boneInfo[*boneRefs].parent].translation ); + qglEnd(); + } + + qglLineWidth( 1 ); + } + } + + if ( r_bonesDebug->integer == 3 || r_bonesDebug->integer == 4 ) { + int render_indexes = ( tess.numIndexes - oldIndexes ); + + // show mesh edges + tempVert = ( float * )( tess.xyz + baseVertex ); + tempNormal = ( float * )( tess.normal + baseVertex ); + + GL_Bind( tr.whiteImage ); + qglLineWidth( 1 ); + qglBegin( GL_LINES ); + qglColor3f( .0,.0,.8 ); + + pIndexes = (int*)&tess.indexes[oldIndexes]; + for ( j = 0; j < render_indexes / 3; j++, pIndexes += 3 ) { + qglVertex3fv( tempVert + 4 * pIndexes[0] ); + qglVertex3fv( tempVert + 4 * pIndexes[1] ); + + qglVertex3fv( tempVert + 4 * pIndexes[1] ); + qglVertex3fv( tempVert + 4 * pIndexes[2] ); + + qglVertex3fv( tempVert + 4 * pIndexes[2] ); + qglVertex3fv( tempVert + 4 * pIndexes[0] ); + } + + qglEnd(); + +//----(SA) track debug stats + if ( r_bonesDebug->integer == 4 ) { + totalrv += render_count; + totalrt += render_indexes / 3; + totalv += surface->numVerts; + totalt += surface->numTriangles; + } +//----(SA) end + + if ( r_bonesDebug->integer == 3 ) { + ri.Printf( PRINT_ALL, "Lod %.2f verts %4d/%4d tris %4d/%4d (%.2f%%)\n", lodScale, render_count, surface->numVerts, render_indexes / 3, surface->numTriangles, + ( float )( 100.0 * render_indexes / 3 ) / (float) surface->numTriangles ); + } + } + } + + if ( r_bonesDebug->integer > 1 ) { + // dont draw the actual surface + tess.numIndexes = oldIndexes; + tess.numVertexes = baseVertex; + return; + } + +#ifdef DBG_PROFILE_BONES + Com_Printf( "\n" ); +#endif + +} + +/* +=============== +R_RecursiveBoneListAdd +=============== +*/ +void R_RecursiveBoneListAdd( int bi, int *boneList, int *numBones, mdsBoneInfo_t *boneInfoList ) { + + if ( boneInfoList[ bi ].parent >= 0 ) { + + R_RecursiveBoneListAdd( boneInfoList[ bi ].parent, boneList, numBones, boneInfoList ); + + } + + boneList[ ( *numBones )++ ] = bi; + +} + +/* +=============== +R_GetBoneTag +=============== +*/ +int R_GetBoneTag( orientation_t *outTag, mdsHeader_t *mds, int startTagIndex, const refEntity_t *refent, const char *tagName ) { + + int i; + mdsTag_t *pTag; + mdsBoneInfo_t *boneInfoList; + int boneList[ MDS_MAX_BONES ]; + int numBones; + + if ( startTagIndex > mds->numTags ) { + memset( outTag, 0, sizeof( *outTag ) ); + return -1; + } + + // find the correct tag + + pTag = ( mdsTag_t * )( (byte *)mds + mds->ofsTags ); + + pTag += startTagIndex; + + for ( i = startTagIndex; i < mds->numTags; i++, pTag++ ) { + if ( !strcmp( pTag->name, tagName ) ) { + break; + } + } + + if ( i >= mds->numTags ) { + memset( outTag, 0, sizeof( *outTag ) ); + return -1; + } + + // now build the list of bones we need to calc to get this tag's bone information + + boneInfoList = ( mdsBoneInfo_t * )( (byte *)mds + mds->ofsBones ); + numBones = 0; + + R_RecursiveBoneListAdd( pTag->boneIndex, boneList, &numBones, boneInfoList ); + + // calc the bones + + R_CalcBones( (mdsHeader_t *)mds, refent, boneList, numBones ); + + // now extract the orientation for the bone that represents our tag + + memcpy( outTag->axis, bones[ pTag->boneIndex ].matrix, sizeof( outTag->axis ) ); + VectorCopy( bones[ pTag->boneIndex ].translation, outTag->origin ); + +/* code not functional, not in backend + if (r_bonesDebug->integer == 4) { + int j; + // DEBUG: show the tag position/axis + GL_Bind( tr.whiteImage ); + qglLineWidth( 2 ); + qglBegin( GL_LINES ); + for (j=0; j<3; j++) { + VectorClear(vec); + vec[j] = 1; + qglColor3fv( vec ); + qglVertex3fv( outTag->origin ); + VectorMA( outTag->origin, 8, outTag->axis[j], vec ); + qglVertex3fv( vec ); + } + qglEnd(); + + qglLineWidth( 1 ); + } +*/ + + return i; +} diff --git a/src/renderer/tr_backend.c b/src/renderer/tr_backend.c new file mode 100644 index 0000000..37b06ef --- /dev/null +++ b/src/renderer/tr_backend.c @@ -0,0 +1,1367 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "tr_local.h" + +backEndData_t *backEndData[SMP_FRAMES]; +backEndState_t backEnd; + + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +/* +** GL_Bind +*/ +void GL_Bind( image_t *image ) { + int texnum; + + if ( !image ) { + ri.Printf( PRINT_WARNING, "GL_Bind: NULL image\n" ); + texnum = tr.defaultImage->texnum; + } else { + texnum = image->texnum; + } + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[glState.currenttmu] != texnum ) { + image->frameUsed = tr.frameCount; + glState.currenttextures[glState.currenttmu] = texnum; + qglBindTexture( GL_TEXTURE_2D, texnum ); + } +} + +/* +** GL_SelectTexture +*/ +void GL_SelectTexture( int unit ) { + if ( glState.currenttmu == unit ) { + return; + } + + if ( unit == 0 ) { + qglActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + } else if ( unit == 1 ) { + qglActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + } else { + ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit ); + } + + glState.currenttmu = unit; +} + + +/* +** GL_BindMultitexture +*/ +void GL_BindMultitexture( image_t *image0, GLuint env0, image_t *image1, GLuint env1 ) { + int texnum0, texnum1; + + texnum0 = image0->texnum; + texnum1 = image1->texnum; + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum0 = texnum1 = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[1] != texnum1 ) { + GL_SelectTexture( 1 ); + image1->frameUsed = tr.frameCount; + glState.currenttextures[1] = texnum1; + qglBindTexture( GL_TEXTURE_2D, texnum1 ); + } + if ( glState.currenttextures[0] != texnum0 ) { + GL_SelectTexture( 0 ); + image0->frameUsed = tr.frameCount; + glState.currenttextures[0] = texnum0; + qglBindTexture( GL_TEXTURE_2D, texnum0 ); + } +} + + +/* +** GL_Cull +*/ +void GL_Cull( int cullType ) { + if ( glState.faceCulling == cullType ) { + return; + } + + glState.faceCulling = cullType; + + if ( cullType == CT_TWO_SIDED ) { + qglDisable( GL_CULL_FACE ); + } else + { + qglEnable( GL_CULL_FACE ); + + if ( cullType == CT_BACK_SIDED ) { + if ( backEnd.viewParms.isMirror ) { + qglCullFace( GL_FRONT ); + } else + { + qglCullFace( GL_BACK ); + } + } else + { + if ( backEnd.viewParms.isMirror ) { + qglCullFace( GL_BACK ); + } else + { + qglCullFace( GL_FRONT ); + } + } + } +} + +/* +** GL_TexEnv +*/ +void GL_TexEnv( int env ) { + if ( env == glState.texEnv[glState.currenttmu] ) { + return; + } + + glState.texEnv[glState.currenttmu] = env; + + + switch ( env ) + { + case GL_MODULATE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + break; + case GL_REPLACE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + break; + case GL_DECAL: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + break; + case GL_ADD: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + break; + default: + ri.Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed\n", env ); + break; + } +} + +/* +** GL_State +** +** This routine is responsible for setting the most commonly changed state +** in Q3. +*/ +void GL_State( unsigned long stateBits ) { + unsigned long diff = stateBits ^ glState.glStateBits; + + if ( !diff ) { + return; + } + + // + // check depthFunc bits + // + if ( diff & GLS_DEPTHFUNC_EQUAL ) { + if ( stateBits & GLS_DEPTHFUNC_EQUAL ) { + qglDepthFunc( GL_EQUAL ); + } else + { + qglDepthFunc( GL_LEQUAL ); + } + } + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) { + GLenum srcFactor, dstFactor; + + if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) { + switch ( stateBits & GLS_SRCBLEND_BITS ) + { + case GLS_SRCBLEND_ZERO: + srcFactor = GL_ZERO; + break; + case GLS_SRCBLEND_ONE: + srcFactor = GL_ONE; + break; + case GLS_SRCBLEND_DST_COLOR: + srcFactor = GL_DST_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: + srcFactor = GL_ONE_MINUS_DST_COLOR; + break; + case GLS_SRCBLEND_SRC_ALPHA: + srcFactor = GL_SRC_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: + srcFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_SRCBLEND_DST_ALPHA: + srcFactor = GL_DST_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: + srcFactor = GL_ONE_MINUS_DST_ALPHA; + break; + case GLS_SRCBLEND_ALPHA_SATURATE: + srcFactor = GL_SRC_ALPHA_SATURATE; + break; + default: + srcFactor = GL_ONE; // to get warning to shut up + ri.Error( ERR_DROP, "GL_State: invalid src blend state bits\n" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) + { + case GLS_DSTBLEND_ZERO: + dstFactor = GL_ZERO; + break; + case GLS_DSTBLEND_ONE: + dstFactor = GL_ONE; + break; + case GLS_DSTBLEND_SRC_COLOR: + dstFactor = GL_SRC_COLOR; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: + dstFactor = GL_ONE_MINUS_SRC_COLOR; + break; + case GLS_DSTBLEND_SRC_ALPHA: + dstFactor = GL_SRC_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_DSTBLEND_DST_ALPHA: + dstFactor = GL_DST_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: + dstFactor = GL_ONE_MINUS_DST_ALPHA; + break; + default: + dstFactor = GL_ONE; // to get warning to shut up + ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits\n" ); + break; + } + + qglEnable( GL_BLEND ); + qglBlendFunc( srcFactor, dstFactor ); + } else + { + qglDisable( GL_BLEND ); + } + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK_TRUE ) { + if ( stateBits & GLS_DEPTHMASK_TRUE ) { + qglDepthMask( GL_TRUE ); + } else + { + qglDepthMask( GL_FALSE ); + } + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) { + if ( stateBits & GLS_POLYMODE_LINE ) { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } else + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // depthtest + // + if ( diff & GLS_DEPTHTEST_DISABLE ) { + if ( stateBits & GLS_DEPTHTEST_DISABLE ) { + qglDisable( GL_DEPTH_TEST ); + } else + { + qglEnable( GL_DEPTH_TEST ); + } + } + + // + // alpha test + // + if ( diff & GLS_ATEST_BITS ) { + switch ( stateBits & GLS_ATEST_BITS ) + { + case 0: + qglDisable( GL_ALPHA_TEST ); + break; + case GLS_ATEST_GT_0: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GREATER, 0.0f ); + break; + case GLS_ATEST_LT_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_LESS, 0.5f ); + break; + case GLS_ATEST_GE_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.5f ); + break; + default: + assert( 0 ); + break; + } + } + + glState.glStateBits = stateBits; +} + + + +/* +================ +RB_Hyperspace + +A player has predicted a teleport, but hasn't arrived yet +================ +*/ +static void RB_Hyperspace( void ) { + float c; + + if ( !backEnd.isHyperspace ) { + // do initialization shit + } + + c = ( backEnd.refdef.time & 255 ) / 255.0f; + qglClearColor( c, c, c, 1 ); + qglClear( GL_COLOR_BUFFER_BIT ); + + backEnd.isHyperspace = qtrue; +} + + +static void SetViewportAndScissor( void ) { + qglMatrixMode( GL_PROJECTION ); + qglLoadMatrixf( backEnd.viewParms.projectionMatrix ); + qglMatrixMode( GL_MODELVIEW ); + + // set the window clipping + qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); +} + +/* +================= +RB_BeginDrawingView + +Any mirrored or portaled views have already been drawn, so prepare +to actually render the visible surfaces for this view +================= +*/ +void RB_BeginDrawingView( void ) { + int clearBits = 0; + + // sync with gl if needed + if ( r_finish->integer == 1 && !glState.finishCalled ) { + qglFinish(); + glState.finishCalled = qtrue; + } + if ( r_finish->integer == 0 ) { + glState.finishCalled = qtrue; + } + + // we will need to change the projection matrix before drawing + // 2D images again + backEnd.projection2D = qfalse; + + // + // set the modelview matrix for the viewer + // + SetViewportAndScissor(); + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + + +////////// (SA) modified to ensure one glclear() per frame at most + + // clear relevant buffers + clearBits = 0; + + if ( r_measureOverdraw->integer || r_shadows->integer == 2 ) { + clearBits |= GL_STENCIL_BUFFER_BIT; + } + + if ( r_uiFullScreen->integer ) { + clearBits = GL_DEPTH_BUFFER_BIT; // (SA) always just clear depth for menus + + } else if ( skyboxportal ) { + if ( backEnd.refdef.rdflags & RDF_SKYBOXPORTAL ) { // portal scene, clear whatever is necessary + + clearBits |= GL_DEPTH_BUFFER_BIT; + + if ( r_fastsky->integer || backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) { // fastsky: clear color + + // try clearing first with the portal sky fog color, then the world fog color, then finally a default + clearBits |= GL_COLOR_BUFFER_BIT; + if ( glfogsettings[FOG_PORTALVIEW].registered ) { + qglClearColor( glfogsettings[FOG_PORTALVIEW].color[0], glfogsettings[FOG_PORTALVIEW].color[1], glfogsettings[FOG_PORTALVIEW].color[2], glfogsettings[FOG_PORTALVIEW].color[3] ); + } else if ( glfogNum > FOG_NONE && glfogsettings[FOG_CURRENT].registered ) { + qglClearColor( glfogsettings[FOG_CURRENT].color[0], glfogsettings[FOG_CURRENT].color[1], glfogsettings[FOG_CURRENT].color[2], glfogsettings[FOG_CURRENT].color[3] ); + } else { +// qglClearColor ( 1.0, 0.0, 0.0, 1.0 ); // red clear for testing portal sky clear + qglClearColor( 0.5, 0.5, 0.5, 1.0 ); + } + } else { // rendered sky (either clear color or draw quake sky) + if ( glfogsettings[FOG_PORTALVIEW].registered ) { + qglClearColor( glfogsettings[FOG_PORTALVIEW].color[0], glfogsettings[FOG_PORTALVIEW].color[1], glfogsettings[FOG_PORTALVIEW].color[2], glfogsettings[FOG_PORTALVIEW].color[3] ); + + if ( glfogsettings[FOG_PORTALVIEW].clearscreen ) { // portal fog requests a screen clear (distance fog rather than quake sky) + clearBits |= GL_COLOR_BUFFER_BIT; + } + } + + } + } else { // world scene with portal sky, don't clear any buffers, just set the fog color if there is one + + clearBits |= GL_DEPTH_BUFFER_BIT; // this will go when I get the portal sky rendering way out in the zbuffer (or not writing to zbuffer at all) + + if ( glfogNum > FOG_NONE && glfogsettings[FOG_CURRENT].registered ) { + if ( backEnd.refdef.rdflags & RDF_UNDERWATER ) { + if ( glfogsettings[FOG_CURRENT].mode == GL_LINEAR ) { + clearBits |= GL_COLOR_BUFFER_BIT; + } + + } else if ( !( r_portalsky->integer ) ) { // portal skies have been manually turned off, clear bg color + clearBits |= GL_COLOR_BUFFER_BIT; + } + + qglClearColor( glfogsettings[FOG_CURRENT].color[0], glfogsettings[FOG_CURRENT].color[1], glfogsettings[FOG_CURRENT].color[2], glfogsettings[FOG_CURRENT].color[3] ); + } + } + } else { // world scene with no portal sky + clearBits |= GL_DEPTH_BUFFER_BIT; + + // NERVE - SMF - we don't want to clear the buffer when no world model is specified + if ( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) { + clearBits &= ~GL_COLOR_BUFFER_BIT; + } + // -NERVE - SMF + else if ( r_fastsky->integer || backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) { + + clearBits |= GL_COLOR_BUFFER_BIT; + + if ( glfogsettings[FOG_CURRENT].registered ) { // try to clear fastsky with current fog color + qglClearColor( glfogsettings[FOG_CURRENT].color[0], glfogsettings[FOG_CURRENT].color[1], glfogsettings[FOG_CURRENT].color[2], glfogsettings[FOG_CURRENT].color[3] ); + } else { +// qglClearColor ( 0.0, 0.0, 1.0, 1.0 ); // blue clear for testing world sky clear + qglClearColor( 0.05, 0.05, 0.05, 1.0 ); // JPW NERVE changed per id req was 0.5s + } + } else { // world scene, no portal sky, not fastsky, clear color if fog says to, otherwise, just set the clearcolor + if ( glfogsettings[FOG_CURRENT].registered ) { // try to clear fastsky with current fog color + qglClearColor( glfogsettings[FOG_CURRENT].color[0], glfogsettings[FOG_CURRENT].color[1], glfogsettings[FOG_CURRENT].color[2], glfogsettings[FOG_CURRENT].color[3] ); + + if ( glfogsettings[FOG_CURRENT].clearscreen ) { // world fog requests a screen clear (distance fog rather than quake sky) + clearBits |= GL_COLOR_BUFFER_BIT; + } + } + } + } + + + if ( clearBits ) { + qglClear( clearBits ); + } + +//----(SA) done + + if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) ) { + RB_Hyperspace(); + return; + } else + { + backEnd.isHyperspace = qfalse; + } + + glState.faceCulling = -1; // force face culling to set next time + + // we will only draw a sun if there was sky rendered in this view + backEnd.skyRenderedThisView = qfalse; + + // clip to the plane of the portal + if ( backEnd.viewParms.isPortal ) { + float plane[4]; + double plane2[4]; + + plane[0] = backEnd.viewParms.portalPlane.normal[0]; + plane[1] = backEnd.viewParms.portalPlane.normal[1]; + plane[2] = backEnd.viewParms.portalPlane.normal[2]; + plane[3] = backEnd.viewParms.portalPlane.dist; + + plane2[0] = DotProduct( backEnd.viewParms.or.axis[0], plane ); + plane2[1] = DotProduct( backEnd.viewParms.or.axis[1], plane ); + plane2[2] = DotProduct( backEnd.viewParms.or.axis[2], plane ); + plane2[3] = DotProduct( plane, backEnd.viewParms.or.origin ) - plane[3]; + + qglLoadMatrixf( s_flipMatrix ); + qglClipPlane( GL_CLIP_PLANE0, plane2 ); + qglEnable( GL_CLIP_PLANE0 ); + } else { + qglDisable( GL_CLIP_PLANE0 ); + } +} + +#define MAC_EVENT_PUMP_MSEC 5 + +/* +================== +RB_RenderDrawSurfList +================== +*/ +void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader, *oldShader; + int fogNum, oldFogNum; + int entityNum, oldEntityNum; + int dlighted, oldDlighted; + qboolean depthRange, oldDepthRange; + int i; + drawSurf_t *drawSurf; + int oldSort; + float originalTime; +#ifdef __MACOS__ + int macEventTime; + + Sys_PumpEvents(); // crutch up the mac's limited buffer queue size + + // we don't want to pump the event loop too often and waste time, so + // we are going to check every shader change + macEventTime = ri.Milliseconds() + MAC_EVENT_PUMP_MSEC; +#endif + + // save original time for entity shader offsets + originalTime = backEnd.refdef.floatTime; + + // clear the z buffer, set the modelview, etc + RB_BeginDrawingView(); + + // draw everything + oldEntityNum = -1; + backEnd.currentEntity = &tr.worldEntity; + oldShader = NULL; + oldFogNum = -1; + oldDepthRange = qfalse; + oldDlighted = qfalse; + oldSort = -1; + depthRange = qfalse; + + backEnd.pc.c_surfaces += numDrawSurfs; + + for ( i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++ ) { + if ( drawSurf->sort == oldSort ) { + // fast path, same as previous sort + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + continue; + } + oldSort = drawSurf->sort; + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + + // + // change the tess parameters if needed + // a "entityMergable" shader is a shader that can have surfaces from seperate + // entities merged into a single batch, like smoke and blood puff sprites + if ( shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted + || ( entityNum != oldEntityNum && !shader->entityMergable ) ) { + if ( oldShader != NULL ) { +#ifdef __MACOS__ // crutch up the mac's limited buffer queue size + int t; + + t = ri.Milliseconds(); + if ( t > macEventTime ) { + macEventTime = t + MAC_EVENT_PUMP_MSEC; + Sys_PumpEvents(); + } +#endif + RB_EndSurface(); + } + RB_BeginSurface( shader, fogNum ); + oldShader = shader; + oldFogNum = fogNum; + oldDlighted = dlighted; + } + + // + // change the modelview matrix if needed + // + if ( entityNum != oldEntityNum ) { + depthRange = qfalse; + + if ( entityNum != ENTITYNUM_WORLD ) { + backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; + backEnd.refdef.floatTime = originalTime; // - backEnd.currentEntity->e.shaderTime; // JPW NERVE pulled this to match q3ta + + // we have to reset the shaderTime as well otherwise image animations start + // from the wrong frame +// tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.or ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) { + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or ); + } + + if ( backEnd.currentEntity->e.renderfx & RF_DEPTHHACK ) { + // hack the depth range to prevent view model from poking into walls + depthRange = qtrue; + } + } else { + backEnd.currentEntity = &tr.worldEntity; + backEnd.refdef.floatTime = originalTime; + backEnd.or = backEnd.viewParms.world; + + // we have to reset the shaderTime as well otherwise image animations on + // the world (like water) continue with the wrong frame +// tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or ); + } + + qglLoadMatrixf( backEnd.or.modelMatrix ); + + // + // change depthrange if needed + // + if ( oldDepthRange != depthRange ) { + if ( depthRange ) { + qglDepthRange( 0, 0.3 ); + } else { + qglDepthRange( 0, 1 ); + } + oldDepthRange = depthRange; + } + + oldEntityNum = entityNum; + } + + // add the triangles for this surface + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + } + + // draw the contents of the last shader batch + if ( oldShader != NULL ) { + RB_EndSurface(); + } + + // go back to the world modelview matrix + backEnd.currentEntity = &tr.worldEntity; + backEnd.refdef.floatTime = originalTime; + backEnd.or = backEnd.viewParms.world; + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or ); + + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + if ( depthRange ) { + qglDepthRange( 0, 1 ); + } + + // (SA) draw sun + RB_DrawSun(); + + + // darken down any stencil shadows + RB_ShadowFinish(); + + // add light flares on lights that aren't obscured + RB_RenderFlares(); + +#ifdef __MACOS__ + Sys_PumpEvents(); // crutch up the mac's limited buffer queue size +#endif +} + + +/* +============================================================================ + +RENDER BACK END THREAD FUNCTIONS + +============================================================================ +*/ + +/* +================ +RB_SetGL2D + +================ +*/ +void RB_SetGL2D( void ) { + backEnd.projection2D = qtrue; + + // set 2D virtual screen size + qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglMatrixMode( GL_PROJECTION ); + qglLoadIdentity(); + qglOrtho( 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1 ); + qglMatrixMode( GL_MODELVIEW ); + qglLoadIdentity(); + + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglDisable( GL_CULL_FACE ); + qglDisable( GL_CLIP_PLANE0 ); + + // set time for 2D shaders + backEnd.refdef.time = ri.Milliseconds(); + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; +} + + +/* +============= +RE_StretchRaw + +FIXME: not exactly backend +Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. +Used for cinematics. +============= +*/ +void RE_StretchRaw( int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty ) { + int i, j; + int start, end; + + if ( !tr.registered ) { + return; + } + R_SyncRenderThread(); + + // we definately want to sync every frame for the cinematics + qglFinish(); + + start = end = 0; + if ( r_speeds->integer ) { + start = ri.Milliseconds(); + } + + // make sure rows and cols are powers of 2 + for ( i = 0 ; ( 1 << i ) < cols ; i++ ) { + } + for ( j = 0 ; ( 1 << j ) < rows ; j++ ) { + } + if ( ( 1 << i ) != cols || ( 1 << j ) != rows ) { + ri.Error( ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows ); + } + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; + tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; + qglTexImage2D( GL_TEXTURE_2D, 0, 3, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + } else { + if ( dirty ) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } + + if ( r_speeds->integer ) { + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); + } + + RB_SetGL2D(); + + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglBegin( GL_QUADS ); + qglTexCoord2f( 0.5f / cols, 0.5f / rows ); + qglVertex2f( x, y ); + qglTexCoord2f( ( cols - 0.5f ) / cols, 0.5f / rows ); + qglVertex2f( x + w, y ); + qglTexCoord2f( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows ); + qglVertex2f( x + w, y + h ); + qglTexCoord2f( 0.5f / cols, ( rows - 0.5f ) / rows ); + qglVertex2f( x, y + h ); + qglEnd(); +} + + +void RE_UploadCinematic( int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty ) { + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; + tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; + qglTexImage2D( GL_TEXTURE_2D, 0, 3, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + } else { + if ( dirty ) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } +} + + +/* +============= +RB_SetColor + +============= +*/ +const void *RB_SetColor( const void *data ) { + const setColorCommand_t *cmd; + + cmd = (const setColorCommand_t *)data; + + backEnd.color2D[0] = cmd->color[0] * 255; + backEnd.color2D[1] = cmd->color[1] * 255; + backEnd.color2D[2] = cmd->color[2] * 255; + backEnd.color2D[3] = cmd->color[3] * 255; + + return (const void *)( cmd + 1 ); +} + +/* +============= +RB_StretchPic +============= +*/ +const void *RB_StretchPic( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + + cmd = (const stretchPicCommand_t *)data; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0 ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + + *(int *)tess.vertexColors[ numVerts ] = + *(int *)tess.vertexColors[ numVerts + 1 ] = + *(int *)tess.vertexColors[ numVerts + 2 ] = + *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D; + + tess.xyz[ numVerts ][0] = cmd->x; + tess.xyz[ numVerts ][1] = cmd->y; + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0][0] = cmd->s1; + tess.texCoords[ numVerts ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 1 ][1] = cmd->y; + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2; + + tess.xyz[ numVerts + 3 ][0] = cmd->x; + tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2; + + return (const void *)( cmd + 1 ); +} + +// NERVE - SMF +/* +============= +RB_RotatedPic +============= +*/ +const void *RB_RotatedPic( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + float angle; + float pi2 = M_PI * 2; + + cmd = (const stretchPicCommand_t *)data; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0 ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + + *(int *)tess.vertexColors[ numVerts ] = + *(int *)tess.vertexColors[ numVerts + 1 ] = + *(int *)tess.vertexColors[ numVerts + 2 ] = + *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D; + + angle = cmd->angle * pi2; + tess.xyz[ numVerts ][0] = cmd->x + ( cos( angle ) * cmd->w ); + tess.xyz[ numVerts ][1] = cmd->y + ( sin( angle ) * cmd->h ); + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0][0] = cmd->s1; + tess.texCoords[ numVerts ][0][1] = cmd->t1; + + angle = cmd->angle * pi2 + 0.25 * pi2; + tess.xyz[ numVerts + 1 ][0] = cmd->x + ( cos( angle ) * cmd->w ); + tess.xyz[ numVerts + 1 ][1] = cmd->y + ( sin( angle ) * cmd->h ); + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1; + + angle = cmd->angle * pi2 + 0.50 * pi2; + tess.xyz[ numVerts + 2 ][0] = cmd->x + ( cos( angle ) * cmd->w ); + tess.xyz[ numVerts + 2 ][1] = cmd->y + ( sin( angle ) * cmd->h ); + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2; + + angle = cmd->angle * pi2 + 0.75 * pi2; + tess.xyz[ numVerts + 3 ][0] = cmd->x + ( cos( angle ) * cmd->w ); + tess.xyz[ numVerts + 3 ][1] = cmd->y + ( sin( angle ) * cmd->h ); + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2; + + return (const void *)( cmd + 1 ); +} +// -NERVE - SMF + +/* +============== +RB_StretchPicGradient +============== +*/ +const void *RB_StretchPicGradient( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + + cmd = (const stretchPicCommand_t *)data; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0 ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + +// *(int *)tess.vertexColors[ numVerts ] = +// *(int *)tess.vertexColors[ numVerts + 1 ] = +// *(int *)tess.vertexColors[ numVerts + 2 ] = +// *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D; + + *(int *)tess.vertexColors[ numVerts ] = + *(int *)tess.vertexColors[ numVerts + 1 ] = *(int *)backEnd.color2D; + + *(int *)tess.vertexColors[ numVerts + 2 ] = + *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)cmd->gradientColor; + + tess.xyz[ numVerts ][0] = cmd->x; + tess.xyz[ numVerts ][1] = cmd->y; + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0][0] = cmd->s1; + tess.texCoords[ numVerts ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 1 ][1] = cmd->y; + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2; + + tess.xyz[ numVerts + 3 ][0] = cmd->x; + tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2; + + return (const void *)( cmd + 1 ); +} + + +/* +============= +RB_DrawSurfs + +============= +*/ +const void *RB_DrawSurfs( const void *data ) { + const drawSurfsCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + cmd = (const drawSurfsCommand_t *)data; + + backEnd.refdef = cmd->refdef; + backEnd.viewParms = cmd->viewParms; + + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + + return (const void *)( cmd + 1 ); +} + + +/* +============= +RB_DrawBuffer + +============= +*/ +const void *RB_DrawBuffer( const void *data ) { + const drawBufferCommand_t *cmd; + + cmd = (const drawBufferCommand_t *)data; + + qglDrawBuffer( cmd->buffer ); + + // clear screen for debugging + if ( r_clear->integer ) { + qglClearColor( 1, 0, 0.5, 1 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + + return (const void *)( cmd + 1 ); +} + +/* +=============== +RB_ShowImages + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. + +Also called by RE_EndRegistration +=============== +*/ +void RB_ShowImages( void ) { + int i; + image_t *image; + float x, y, w, h; + int start, end; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + qglClear( GL_COLOR_BUFFER_BIT ); + + qglFinish(); + + + start = ri.Milliseconds(); + + for ( i = 0 ; i < tr.numImages ; i++ ) { + image = tr.images[i]; + + w = glConfig.vidWidth / 40; + h = glConfig.vidHeight / 30; + + x = i % 40 * w; + y = i / 30 * h; + + // show in proportional size in mode 2 + if ( r_showImages->integer == 2 ) { + w *= image->uploadWidth / 512.0f; + h *= image->uploadHeight / 512.0f; + } + + GL_Bind( image ); + qglBegin( GL_QUADS ); + qglTexCoord2f( 0, 0 ); + qglVertex2f( x, y ); + qglTexCoord2f( 1, 0 ); + qglVertex2f( x + w, y ); + qglTexCoord2f( 1, 1 ); + qglVertex2f( x + w, y + h ); + qglTexCoord2f( 0, 1 ); + qglVertex2f( x, y + h ); + qglEnd(); + } + + qglFinish(); + + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start ); + +} + + +/* +============= +RB_SwapBuffers + +============= +*/ +const void *RB_SwapBuffers( const void *data ) { + const swapBuffersCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // texture swapping test + if ( r_showImages->integer ) { + RB_ShowImages(); + } + + cmd = (const swapBuffersCommand_t *)data; + + // we measure overdraw by reading back the stencil buffer and + // counting up the number of increments that have happened + if ( r_measureOverdraw->integer ) { + int i; + long sum = 0; + unsigned char *stencilReadback; + + stencilReadback = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + sum += stencilReadback[i]; + } + + backEnd.pc.c_overDraw += sum; + ri.Hunk_FreeTempMemory( stencilReadback ); + } + + + if ( !glState.finishCalled ) { + qglFinish(); + } + + GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" ); + + GLimp_EndFrame(); + + backEnd.projection2D = qfalse; + + return (const void *)( cmd + 1 ); +} + +/* +==================== +RB_ExecuteRenderCommands + +This function will be called synchronously if running without +smp extensions, or asynchronously by another thread. +==================== +*/ +void RB_ExecuteRenderCommands( const void *data ) { + int t1, t2; + + t1 = ri.Milliseconds(); + + if ( !r_smp->integer || data == backEndData[0]->commands.cmds ) { + backEnd.smpFrame = 0; + } else { + backEnd.smpFrame = 1; + } + + while ( 1 ) { + switch ( *(const int *)data ) { + case RC_SET_COLOR: + data = RB_SetColor( data ); + break; + case RC_STRETCH_PIC: + data = RB_StretchPic( data ); + break; + case RC_ROTATED_PIC: + data = RB_RotatedPic( data ); + break; + case RC_STRETCH_PIC_GRADIENT: + data = RB_StretchPicGradient( data ); + break; + case RC_DRAW_SURFS: + data = RB_DrawSurfs( data ); + break; + case RC_DRAW_BUFFER: + data = RB_DrawBuffer( data ); + break; + case RC_SWAP_BUFFERS: + data = RB_SwapBuffers( data ); + break; + + case RC_END_OF_LIST: + default: + // stop rendering on this thread + t2 = ri.Milliseconds(); + backEnd.pc.msec = t2 - t1; + return; + } + } + +} + + +/* +================ +RB_RenderThread +================ +*/ +void RB_RenderThread( void ) { + const void *data; + + // wait for either a rendering command or a quit command + while ( 1 ) { + // sleep until we have work to do + data = GLimp_RendererSleep(); + + if ( !data ) { + return; // all done, renderer is shutting down + } + + renderThreadActive = qtrue; + + RB_ExecuteRenderCommands( data ); + + renderThreadActive = qfalse; + } +} + diff --git a/src/renderer/tr_bsp.c b/src/renderer/tr_bsp.c new file mode 100644 index 0000000..09088ac --- /dev/null +++ b/src/renderer/tr_bsp.c @@ -0,0 +1,2262 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_map.c + +#include "tr_local.h" + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +static world_t s_worldData; +static byte *fileBase; + +int c_subdivisions; +int c_gridVerts; + +//=============================================================================== + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) { + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { + int shift, r, g, b; + + // shift the color data based on overbright range + shift = r_mapOverBrightBits->integer - tr.overbrightBits; + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define LIGHTMAP_SIZE 128 +static void R_LoadLightmaps( lump_t *l ) { + byte *buf, *buf_p; + int len; + MAC_STATIC byte image[LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4]; + int i, j; + float maxIntensity = 0; + double sumIntensity = 0; + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + // we are about to upload textures + R_SyncRenderThread(); + + // create all the lightmaps + tr.numLightmaps = len / ( LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3 ); + if ( tr.numLightmaps == 1 ) { + //FIXME: HACK: maps with only one lightmap turn up fullbright for some reason. + //this avoids this, but isn't the correct solution. + tr.numLightmaps++; + } + + // if we are in r_vertexLight mode, we don't need the lightmaps at all + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + return; + } + + for ( i = 0 ; i < tr.numLightmaps ; i++ ) { + // expand the 24 bit on-disk to 32 bit + buf_p = buf + i * LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3; + + if ( r_lightmap->integer == 2 ) { // color code by intensity as development tool (FIXME: check range) + for ( j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) + { + float r = buf_p[j * 3 + 0]; + float g = buf_p[j * 3 + 1]; + float b = buf_p[j * 3 + 2]; + float intensity; + float out[3]; + + intensity = 0.33f * r + 0.685f * g + 0.063f * b; + + if ( intensity > 255 ) { + intensity = 1.0f; + } else { + intensity /= 255.0f; + } + + if ( intensity > maxIntensity ) { + maxIntensity = intensity; + } + + HSVtoRGB( intensity, 1.00, 0.50, out ); + + image[j * 4 + 0] = out[0] * 255; + image[j * 4 + 1] = out[1] * 255; + image[j * 4 + 2] = out[2] * 255; + image[j * 4 + 3] = 255; + + sumIntensity += intensity; + } + } else { + for ( j = 0 ; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) { + R_ColorShiftLightingBytes( &buf_p[j * 3], &image[j * 4] ); + image[j * 4 + 3] = 255; + } + } + tr.lightmaps[i] = R_CreateImage( va( "*lightmap%d",i ), image, + LIGHTMAP_SIZE, LIGHTMAP_SIZE, qfalse, qfalse, GL_CLAMP ); + } + + if ( r_lightmap->integer == 2 ) { + ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) ); + } +} + + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( const byte *vis ) { + tr.externalVisData = vis; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( lump_t *l ) { + int len; + byte *buf; + + len = ( s_worldData.numClusters + 63 ) & ~63; + s_worldData.novis = ri.Hunk_Alloc( len, h_low ); + memset( s_worldData.novis, 0xff, len ); + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + s_worldData.numClusters = LittleLong( ( (int *)buf )[0] ); + s_worldData.clusterBytes = LittleLong( ( (int *)buf )[1] ); + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + if ( tr.externalVisData ) { + s_worldData.vis = tr.externalVisData; + } else { + byte *dest; + + dest = ri.Hunk_Alloc( len - 8, h_low ); + memcpy( dest, buf + 8, len - 8 ); + s_worldData.vis = dest; + } +} + +//=============================================================================== + + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) { + shader_t *shader; + dshader_t *dsh; + + shaderNum = LittleLong( shaderNum ); + if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders ) { + ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum ); + } + dsh = &s_worldData.shaders[ shaderNum ]; + + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + lightmapNum = LIGHTMAP_BY_VERTEX; + } + +// JPW NERVE removed per atvi request +/* + if ( r_fullbright->integer ) { + lightmapNum = LIGHTMAP_WHITEIMAGE; + } +*/ + shader = R_FindShader( dsh->shader, lightmapNum, qtrue ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +// Ridah, optimizations here +// memory block for use by surfaces +static byte *surfHunkPtr; +static int surfHunkSize; +#define SURF_HUNK_MAXSIZE 0x40000 +#define LL( x ) LittleLong( x ) + +/* +============== +R_InitSurfMemory +============== +*/ +void R_InitSurfMemory( void ) { + // allocate a new chunk + surfHunkPtr = ri.Hunk_Alloc( SURF_HUNK_MAXSIZE, h_low ); + surfHunkSize = 0; +} + +/* +============== +R_GetSurfMemory +============== +*/ +void *R_GetSurfMemory( int size ) { + byte *retval; + + // round to cacheline + size = ( size + 31 ) & ~31; + + surfHunkSize += size; + if ( surfHunkSize >= SURF_HUNK_MAXSIZE ) { + // allocate a new chunk + R_InitSurfMemory(); + surfHunkSize += size; // since it just got reset + } + retval = surfHunkPtr; + surfHunkPtr += size; + + return (void *)retval; +} + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + int i, j; + srfSurfaceFace_t *cv; + int numPoints, numIndexes; + int lightmapNum; + int sfaceSize, ofsIndexes; + + lightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numPoints = LittleLong( ds->numVerts ); + if ( numPoints > MAX_FACE_POINTS ) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numPoints ); + numPoints = MAX_FACE_POINTS; + surf->shader = tr.defaultShader; + } + + numIndexes = LittleLong( ds->numIndexes ); + + // create the srfSurfaceFace_t + sfaceSize = ( int ) &( (srfSurfaceFace_t *)0 )->points[numPoints]; + ofsIndexes = sfaceSize; + sfaceSize += sizeof( int ) * numIndexes; + + //cv = ri.Hunk_Alloc( sfaceSize ); + cv = R_GetSurfMemory( sfaceSize ); + + cv->surfaceType = SF_FACE; + cv->numPoints = numPoints; + cv->numIndices = numIndexes; + cv->ofsIndices = ofsIndexes; + + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + cv->points[i][j] = LittleFloat( verts[i].xyz[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + cv->points[i][3 + j] = LittleFloat( verts[i].st[j] ); + cv->points[i][5 + j] = LittleFloat( verts[i].lightmap[j] ); + } + R_ColorShiftLightingBytes( verts[i].color, (byte *)&cv->points[i][7] ); + } + + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + ( ( int * )( (byte *)cv + cv->ofsIndices ) )[i] = LittleLong( indexes[ i ] ); + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } + cv->plane.dist = DotProduct( cv->points[0], cv->plane.normal ); + SetPlaneSignbits( &cv->plane ); + cv->plane.type = PlaneTypeForNormal( cv->plane.normal ); + + surf->data = (surfaceType_t *)cv; +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh( dsurface_t *ds, drawVert_t *verts, msurface_t *surf ) { + srfGridMesh_t *grid; + int i, j; + int width, height, numPoints; + MAC_STATIC drawVert_t points[MAX_PATCH_SIZE * MAX_PATCH_SIZE]; + int lightmapNum; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + + lightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = LittleLong( ds->patchWidth ); + height = LittleLong( ds->patchHeight ); + + verts += LittleLong( ds->firstVert ); + numPoints = width * height; + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + points[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + points[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + points[i].st[j] = LittleFloat( verts[i].st[j] ); + points[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] ); + } + R_ColorShiftLightingBytes( verts[i].color, points[i].color ); + } + + // pre-tesseleate + grid = R_SubdividePatchToGrid( width, height, points ); + surf->data = (surfaceType_t *)grid; + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] ); + bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] ); + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + srfTriangles_t *tri; + int i, j; + int numVerts, numIndexes; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong( ds->numVerts ); + numIndexes = LittleLong( ds->numIndexes ); + + //tri = ri.Hunk_Alloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) + // + numIndexes * sizeof( tri->indexes[0] ) ); + tri = R_GetSurfMemory( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) + + numIndexes * sizeof( tri->indexes[0] ) ); + + tri->surfaceType = SF_TRIANGLES; + tri->numVerts = numVerts; + tri->numIndexes = numIndexes; + tri->verts = ( drawVert_t * )( tri + 1 ); + tri->indexes = ( int * )( tri->verts + tri->numVerts ); + + surf->data = (surfaceType_t *)tri; + + // copy vertexes + ClearBounds( tri->bounds[0], tri->bounds[1] ); + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numVerts ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tri->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + tri->verts[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] ); + for ( j = 0 ; j < 2 ; j++ ) { + tri->verts[i].st[j] = LittleFloat( verts[i].st[j] ); + tri->verts[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] ); + } + + R_ColorShiftLightingBytes( verts[i].color, tri->verts[i].color ); + } + + // copy indexes + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + tri->indexes[i] = LittleLong( indexes[i] ); + if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) { + ri.Error( ERR_DROP, "Bad index in triangle surface" ); + } + } +} + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + srfFlare_t *flare; + int i; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + flare = ri.Hunk_Alloc( sizeof( *flare ), h_low ); + flare->surfaceType = SF_FLARE; + + surf->data = (surfaceType_t *)flare; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] ); + flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] ); + flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } +} + + +/* +================= +R_MergedWidthPoints + +returns true if there are grid points merged on a width edge +================= +*/ +int R_MergedWidthPoints( srfGridMesh_t *grid, int offset ) { + int i, j; + + for ( i = 1; i < grid->width - 1; i++ ) { + for ( j = i + 1; j < grid->width - 1; j++ ) { + if ( fabs( grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0] ) > .1 ) { + continue; + } + if ( fabs( grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1] ) > .1 ) { + continue; + } + if ( fabs( grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2] ) > .1 ) { + continue; + } + return qtrue; + } + } + return qfalse; +} + +/* +================= +R_MergedHeightPoints + +returns true if there are grid points merged on a height edge +================= +*/ +int R_MergedHeightPoints( srfGridMesh_t *grid, int offset ) { + int i, j; + + for ( i = 1; i < grid->height - 1; i++ ) { + for ( j = i + 1; j < grid->height - 1; j++ ) { + if ( fabs( grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0] ) > .1 ) { + continue; + } + if ( fabs( grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1] ) > .1 ) { + continue; + } + if ( fabs( grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2] ) > .1 ) { + continue; + } + return qtrue; + } + } + return qfalse; +} + +/* +================= +R_FixSharedVertexLodError_r + +NOTE: never sync LoD through grid edges with merged points! + +FIXME: write generalized version that also avoids cracks between a patch and one that meets half way? +================= +*/ +void R_FixSharedVertexLodError_r( int start, srfGridMesh_t *grid1 ) { + int j, k, l, m, n, offset1, offset2, touch; + srfGridMesh_t *grid2; + + for ( j = start; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) { + continue; + } + // if the LOD errors are already fixed for this patch + if ( grid2->lodFixed == 2 ) { + continue; + } + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) { + continue; + } + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) { + continue; + } + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) { + continue; + } + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) { + continue; + } + // + touch = qfalse; + for ( n = 0; n < 2; n++ ) { + // + if ( n ) { + offset1 = ( grid1->height - 1 ) * grid1->width; + } else { offset1 = 0;} + if ( R_MergedWidthPoints( grid1, offset1 ) ) { + continue; + } + for ( k = 1; k < grid1->width - 1; k++ ) { + for ( m = 0; m < 2; m++ ) { + + if ( m ) { + offset2 = ( grid2->height - 1 ) * grid2->width; + } else { offset2 = 0;} + if ( R_MergedWidthPoints( grid2, offset2 ) ) { + continue; + } + for ( l = 1; l < grid2->width - 1; l++ ) { + // + if ( fabs( grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0] ) > .1 ) { + continue; + } + if ( fabs( grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1] ) > .1 ) { + continue; + } + if ( fabs( grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2] ) > .1 ) { + continue; + } + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->widthLodError[k]; + touch = qtrue; + } + } + for ( m = 0; m < 2; m++ ) { + + if ( m ) { + offset2 = grid2->width - 1; + } else { offset2 = 0;} + if ( R_MergedHeightPoints( grid2, offset2 ) ) { + continue; + } + for ( l = 1; l < grid2->height - 1; l++ ) { + // + if ( fabs( grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0] ) > .1 ) { + continue; + } + if ( fabs( grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1] ) > .1 ) { + continue; + } + if ( fabs( grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2] ) > .1 ) { + continue; + } + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->widthLodError[k]; + touch = qtrue; + } + } + } + } + for ( n = 0; n < 2; n++ ) { + // + if ( n ) { + offset1 = grid1->width - 1; + } else { offset1 = 0;} + if ( R_MergedHeightPoints( grid1, offset1 ) ) { + continue; + } + for ( k = 1; k < grid1->height - 1; k++ ) { + for ( m = 0; m < 2; m++ ) { + + if ( m ) { + offset2 = ( grid2->height - 1 ) * grid2->width; + } else { offset2 = 0;} + if ( R_MergedWidthPoints( grid2, offset2 ) ) { + continue; + } + for ( l = 1; l < grid2->width - 1; l++ ) { + // + if ( fabs( grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0] ) > .1 ) { + continue; + } + if ( fabs( grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1] ) > .1 ) { + continue; + } + if ( fabs( grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2] ) > .1 ) { + continue; + } + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->heightLodError[k]; + touch = qtrue; + } + } + for ( m = 0; m < 2; m++ ) { + + if ( m ) { + offset2 = grid2->width - 1; + } else { offset2 = 0;} + if ( R_MergedHeightPoints( grid2, offset2 ) ) { + continue; + } + for ( l = 1; l < grid2->height - 1; l++ ) { + // + if ( fabs( grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0] ) > .1 ) { + continue; + } + if ( fabs( grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1] ) > .1 ) { + continue; + } + if ( fabs( grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2] ) > .1 ) { + continue; + } + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->heightLodError[k]; + touch = qtrue; + } + } + } + } + if ( touch ) { + grid2->lodFixed = 2; + R_FixSharedVertexLodError_r( start, grid2 ); + //NOTE: this would be correct but makes things really slow + //grid2->lodFixed = 1; + } + } +} + +/* +================= +R_FixSharedVertexLodError + +This function assumes that all patches in one group are nicely stitched together for the highest LoD. +If this is not the case this function will still do its job but won't fix the highest LoD cracks. +================= +*/ +void R_FixSharedVertexLodError( void ) { + int i; + srfGridMesh_t *grid1; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) { + continue; + } + // + if ( grid1->lodFixed ) { + continue; + } + // + grid1->lodFixed = 2; + // recursively fix other patches in the same LOD group + R_FixSharedVertexLodError_r( i + 1, grid1 ); + } +} + + +/* +=============== +R_StitchPatches +=============== +*/ +int R_StitchPatches( int grid1num, int grid2num ) { + int k, l, m, n, offset1, offset2, row, column; + srfGridMesh_t *grid1, *grid2; + float *v1, *v2; + + grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data; + grid2 = (srfGridMesh_t *) s_worldData.surfaces[grid2num].data; + for ( n = 0; n < 2; n++ ) { + // + if ( n ) { + offset1 = ( grid1->height - 1 ) * grid1->width; + } else { offset1 = 0;} + if ( R_MergedWidthPoints( grid1, offset1 ) ) { + continue; + } + for ( k = 0; k < grid1->width - 2; k += 2 ) { + + for ( m = 0; m < 2; m++ ) { + + if ( grid2->width >= MAX_GRID_SIZE ) { + break; + } + if ( m ) { + offset2 = ( grid2->height - 1 ) * grid2->width; + } else { offset2 = 0;} + //if (R_MergedWidthPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->width - 1; l++ ) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) < .01 && + fabs( v1[1] - v2[1] ) < .01 && + fabs( v1[2] - v2[2] ) < .01 ) { + continue; + } + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if ( m ) { + row = grid2->height - 1; + } else { row = 0;} + grid2 = R_GridInsertColumn( grid2, l + 1, row, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k + 1] ); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for ( m = 0; m < 2; m++ ) { + + if ( grid2->height >= MAX_GRID_SIZE ) { + break; + } + if ( m ) { + offset2 = grid2->width - 1; + } else { offset2 = 0;} + //if (R_MergedHeightPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->height - 1; l++ ) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * ( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * ( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) < .01 && + fabs( v1[1] - v2[1] ) < .01 && + fabs( v1[2] - v2[2] ) < .01 ) { + continue; + } + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if ( m ) { + column = grid2->width - 1; + } else { column = 0;} + grid2 = R_GridInsertRow( grid2, l + 1, column, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k + 1] ); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for ( n = 0; n < 2; n++ ) { + // + if ( n ) { + offset1 = grid1->width - 1; + } else { offset1 = 0;} + if ( R_MergedHeightPoints( grid1, offset1 ) ) { + continue; + } + for ( k = 0; k < grid1->height - 2; k += 2 ) { + for ( m = 0; m < 2; m++ ) { + + if ( grid2->width >= MAX_GRID_SIZE ) { + break; + } + if ( m ) { + offset2 = ( grid2->height - 1 ) * grid2->width; + } else { offset2 = 0;} + //if (R_MergedWidthPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->width - 1; l++ ) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + + v1 = grid1->verts[grid1->width * ( k + 2 ) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) < .01 && + fabs( v1[1] - v2[1] ) < .01 && + fabs( v1[2] - v2[2] ) < .01 ) { + continue; + } + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if ( m ) { + row = grid2->height - 1; + } else { row = 0;} + grid2 = R_GridInsertColumn( grid2, l + 1, row, + grid1->verts[grid1->width * ( k + 1 ) + offset1].xyz, grid1->heightLodError[k + 1] ); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for ( m = 0; m < 2; m++ ) { + + if ( grid2->height >= MAX_GRID_SIZE ) { + break; + } + if ( m ) { + offset2 = grid2->width - 1; + } else { offset2 = 0;} + //if (R_MergedHeightPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->height - 1; l++ ) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + + v1 = grid1->verts[grid1->width * ( k + 2 ) + offset1].xyz; + v2 = grid2->verts[grid2->width * ( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * ( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) < .01 && + fabs( v1[1] - v2[1] ) < .01 && + fabs( v1[2] - v2[2] ) < .01 ) { + continue; + } + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if ( m ) { + column = grid2->width - 1; + } else { column = 0;} + grid2 = R_GridInsertRow( grid2, l + 1, column, + grid1->verts[grid1->width * ( k + 1 ) + offset1].xyz, grid1->heightLodError[k + 1] ); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for ( n = 0; n < 2; n++ ) { + // + if ( n ) { + offset1 = ( grid1->height - 1 ) * grid1->width; + } else { offset1 = 0;} + if ( R_MergedWidthPoints( grid1, offset1 ) ) { + continue; + } + for ( k = grid1->width - 1; k > 1; k -= 2 ) { + + for ( m = 0; m < 2; m++ ) { + + if ( grid2->width >= MAX_GRID_SIZE ) { + break; + } + if ( m ) { + offset2 = ( grid2->height - 1 ) * grid2->width; + } else { offset2 = 0;} + //if (R_MergedWidthPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->width - 1; l++ ) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) < .01 && + fabs( v1[1] - v2[1] ) < .01 && + fabs( v1[2] - v2[2] ) < .01 ) { + continue; + } + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if ( m ) { + row = grid2->height - 1; + } else { row = 0;} + grid2 = R_GridInsertColumn( grid2, l + 1, row, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k + 1] ); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for ( m = 0; m < 2; m++ ) { + + if ( grid2->height >= MAX_GRID_SIZE ) { + break; + } + if ( m ) { + offset2 = grid2->width - 1; + } else { offset2 = 0;} + //if (R_MergedHeightPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->height - 1; l++ ) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * ( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * ( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) < .01 && + fabs( v1[1] - v2[1] ) < .01 && + fabs( v1[2] - v2[2] ) < .01 ) { + continue; + } + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if ( m ) { + column = grid2->width - 1; + } else { column = 0;} + grid2 = R_GridInsertRow( grid2, l + 1, column, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k + 1] ); + if ( !grid2 ) { + break; + } + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for ( n = 0; n < 2; n++ ) { + // + if ( n ) { + offset1 = grid1->width - 1; + } else { offset1 = 0;} + if ( R_MergedHeightPoints( grid1, offset1 ) ) { + continue; + } + for ( k = grid1->height - 1; k > 1; k -= 2 ) { + for ( m = 0; m < 2; m++ ) { + + if ( grid2->width >= MAX_GRID_SIZE ) { + break; + } + if ( m ) { + offset2 = ( grid2->height - 1 ) * grid2->width; + } else { offset2 = 0;} + //if (R_MergedWidthPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->width - 1; l++ ) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + + v1 = grid1->verts[grid1->width * ( k - 2 ) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) < .01 && + fabs( v1[1] - v2[1] ) < .01 && + fabs( v1[2] - v2[2] ) < .01 ) { + continue; + } + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if ( m ) { + row = grid2->height - 1; + } else { row = 0;} + grid2 = R_GridInsertColumn( grid2, l + 1, row, + grid1->verts[grid1->width * ( k - 1 ) + offset1].xyz, grid1->heightLodError[k + 1] ); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for ( m = 0; m < 2; m++ ) { + + if ( grid2->height >= MAX_GRID_SIZE ) { + break; + } + if ( m ) { + offset2 = grid2->width - 1; + } else { offset2 = 0;} + //if (R_MergedHeightPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->height - 1; l++ ) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + + v1 = grid1->verts[grid1->width * ( k - 2 ) + offset1].xyz; + v2 = grid2->verts[grid2->width * ( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) > .1 ) { + continue; + } + if ( fabs( v1[1] - v2[1] ) > .1 ) { + continue; + } + if ( fabs( v1[2] - v2[2] ) > .1 ) { + continue; + } + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * ( l + 1 ) + offset2].xyz; + if ( fabs( v1[0] - v2[0] ) < .01 && + fabs( v1[1] - v2[1] ) < .01 && + fabs( v1[2] - v2[2] ) < .01 ) { + continue; + } + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if ( m ) { + column = grid2->width - 1; + } else { column = 0;} + grid2 = R_GridInsertRow( grid2, l + 1, column, + grid1->verts[grid1->width * ( k - 1 ) + offset1].xyz, grid1->heightLodError[k + 1] ); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + return qfalse; +} + +/* +=============== +R_TryStitchPatch + +This function will try to stitch patches in the same LoD group together for the highest LoD. + +Only single missing vertice cracks will be fixed. + +Vertices will be joined at the patch side a crack is first found, at the other side +of the patch (on the same row or column) the vertices will not be joined and cracks +might still appear at that side. +=============== +*/ +int R_TryStitchingPatch( int grid1num ) { + int j, numstitches; + srfGridMesh_t *grid1, *grid2; + + numstitches = 0; + grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data; + for ( j = 0; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) { + continue; + } + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) { + continue; + } + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) { + continue; + } + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) { + continue; + } + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) { + continue; + } + // + while ( R_StitchPatches( grid1num, j ) ) + { + numstitches++; + } + } + return numstitches; +} + +/* +=============== +R_StitchAllPatches +=============== +*/ +void R_StitchAllPatches( void ) { + int i, stitched, numstitches; + srfGridMesh_t *grid1; + + numstitches = 0; + do + { + stitched = qfalse; + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) { + continue; + } + // + if ( grid1->lodStitched ) { + continue; + } + // + grid1->lodStitched = qtrue; + stitched = qtrue; + // + numstitches += R_TryStitchingPatch( i ); + } + } + while ( stitched ); + ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches ); +} + +/* +=============== +R_MovePatchSurfacesToHunk +=============== +*/ +void R_MovePatchSurfacesToHunk( void ) { + int i, size; + srfGridMesh_t *grid, *hunkgrid; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid->surfaceType != SF_GRID ) { + continue; + } + // + size = ( grid->width * grid->height - 1 ) * sizeof( drawVert_t ) + sizeof( *grid ); + hunkgrid = ri.Hunk_Alloc( size, h_low ); + Com_Memcpy( hunkgrid, grid, size ); + + hunkgrid->widthLodError = ri.Hunk_Alloc( grid->width * 4, h_low ); + Com_Memcpy( hunkgrid->widthLodError, grid->widthLodError, grid->width * 4 ); + + hunkgrid->heightLodError = ri.Hunk_Alloc( grid->height * 4, h_low ); + Com_Memcpy( grid->heightLodError, grid->heightLodError, grid->height * 4 ); + + R_FreeSurfaceGridMesh( grid ); + + s_worldData.surfaces[i].data = (void *) hunkgrid; + } +} + +/* +=============== +R_LoadSurfaces +=============== +*/ +static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) { + dsurface_t *in; + msurface_t *out; + drawVert_t *dv; + int *indexes; + int count; + int numFaces, numMeshes, numTriSurfs, numFlares; + int i; + + numFaces = 0; + numMeshes = 0; + numTriSurfs = 0; + numFlares = 0; + + in = ( void * )( fileBase + surfs->fileofs ); + if ( surfs->filelen % sizeof( *in ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + count = surfs->filelen / sizeof( *in ); + + dv = ( void * )( fileBase + verts->fileofs ); + if ( verts->filelen % sizeof( *dv ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + + indexes = ( void * )( fileBase + indexLump->fileofs ); + if ( indexLump->filelen % sizeof( *indexes ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + + out = ri.Hunk_Alloc( count * sizeof( *out ), h_low ); + + s_worldData.surfaces = out; + s_worldData.numsurfaces = count; + + // Ridah, init the surface memory. This is optimization, so we don't have to + // look for memory for each surface, we allocate a big block and just chew it up + // as we go + R_InitSurfMemory(); + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + ParseMesh( in, dv, out ); + numMeshes++; + break; + case MST_TRIANGLE_SOUP: + ParseTriSurf( in, dv, out, indexes ); + numTriSurfs++; + break; + case MST_PLANAR: + ParseFace( in, dv, out, indexes ); + numFaces++; + break; + case MST_FLARE: + ParseFlare( in, dv, out, indexes ); + numFlares++; + break; + default: + ri.Error( ERR_DROP, "Bad surfaceType" ); + } + } + +#ifdef PATCH_STITCHING + R_StitchAllPatches(); +#endif + + R_FixSharedVertexLodError(); + +#ifdef PATCH_STITCHING + R_MovePatchSurfacesToHunk(); +#endif + + ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n", + numFaces, numMeshes, numTriSurfs, numFlares ); +} + + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( lump_t *l ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = ( void * )( fileBase + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + count = l->filelen / sizeof( *in ); + + s_worldData.bmodels = out = ri.Hunk_Alloc( count * sizeof( *out ), h_low ); + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + model_t *model; + + model = R_AllocModel(); + + assert( model != NULL ); // this should never happen + + model->type = MOD_BRUSH; + model->bmodel = out; + Com_sprintf( model->name, sizeof( model->name ), "*%d", i ); + + for ( j = 0 ; j < 3 ; j++ ) { + out->bounds[0][j] = LittleFloat( in->mins[j] ); + out->bounds[1][j] = LittleFloat( in->maxs[j] ); + } + + out->firstSurface = s_worldData.surfaces + LittleLong( in->firstSurface ); + out->numSurfaces = LittleLong( in->numSurfaces ); + } +} + + + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent( mnode_t *node, mnode_t *parent ) { + node->parent = parent; + if ( node->contents != -1 ) { + return; + } + R_SetParent( node->children[0], node ); + R_SetParent( node->children[1], node ); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs( lump_t *nodeLump, lump_t *leafLump ) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *out; + int numNodes, numLeafs; + + in = ( void * )( fileBase + nodeLump->fileofs ); + if ( nodeLump->filelen % sizeof( dnode_t ) || + leafLump->filelen % sizeof( dleaf_t ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + numNodes = nodeLump->filelen / sizeof( dnode_t ); + numLeafs = leafLump->filelen / sizeof( dleaf_t ); + + out = ri.Hunk_Alloc( ( numNodes + numLeafs ) * sizeof( *out ), h_low ); + + s_worldData.nodes = out; + s_worldData.numnodes = numNodes + numLeafs; + s_worldData.numDecisionNodes = numNodes; + + // load nodes + for ( i = 0 ; i < numNodes; i++, in++, out++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + out->mins[j] = LittleLong( in->mins[j] ); + out->maxs[j] = LittleLong( in->maxs[j] ); + } + + p = LittleLong( in->planeNum ); + out->plane = s_worldData.planes + p; + + out->contents = CONTENTS_NODE; // differentiate from leafs + + for ( j = 0 ; j < 2 ; j++ ) + { + p = LittleLong( in->children[j] ); + if ( p >= 0 ) { + out->children[j] = s_worldData.nodes + p; + } else { + out->children[j] = s_worldData.nodes + numNodes + ( -1 - p ); + } + } + } + + // load leafs + inLeaf = ( void * )( fileBase + leafLump->fileofs ); + for ( i = 0 ; i < numLeafs ; i++, inLeaf++, out++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + out->mins[j] = LittleLong( inLeaf->mins[j] ); + out->maxs[j] = LittleLong( inLeaf->maxs[j] ); + } + + out->cluster = LittleLong( inLeaf->cluster ); + out->area = LittleLong( inLeaf->area ); + + if ( out->cluster >= s_worldData.numClusters ) { + s_worldData.numClusters = out->cluster + 1; + } + + out->firstmarksurface = s_worldData.marksurfaces + + LittleLong( inLeaf->firstLeafSurface ); + out->nummarksurfaces = LittleLong( inLeaf->numLeafSurfaces ); + } + + // chain decendants + R_SetParent( s_worldData.nodes, NULL ); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +static void R_LoadShaders( lump_t *l ) { + int i, count; + dshader_t *in, *out; + + in = ( void * )( fileBase + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + count = l->filelen / sizeof( *in ); + out = ri.Hunk_Alloc( count * sizeof( *out ), h_low ); + + s_worldData.shaders = out; + s_worldData.numShaders = count; + + memcpy( out, in, count * sizeof( *out ) ); + + for ( i = 0 ; i < count ; i++ ) { + out[i].surfaceFlags = LittleLong( out[i].surfaceFlags ); + out[i].contentFlags = LittleLong( out[i].contentFlags ); + } +} + + +/* +================= +R_LoadMarksurfaces +================= +*/ +static void R_LoadMarksurfaces( lump_t *l ) { + int i, j, count; + int *in; + msurface_t **out; + + in = ( void * )( fileBase + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + count = l->filelen / sizeof( *in ); + out = ri.Hunk_Alloc( count * sizeof( *out ), h_low ); + + s_worldData.marksurfaces = out; + s_worldData.nummarksurfaces = count; + + for ( i = 0 ; i < count ; i++ ) + { + j = LittleLong( in[i] ); + out[i] = s_worldData.surfaces + j; + } +} + + +/* +================= +R_LoadPlanes +================= +*/ +static void R_LoadPlanes( lump_t *l ) { + int i, j; + cplane_t *out; + dplane_t *in; + int count; + int bits; + + in = ( void * )( fileBase + l->fileofs ); + if ( l->filelen % sizeof( *in ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + count = l->filelen / sizeof( *in ); + out = ri.Hunk_Alloc( count * 2 * sizeof( *out ), h_low ); + + s_worldData.planes = out; + s_worldData.numplanes = count; + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + bits = 0; + for ( j = 0 ; j < 3 ; j++ ) { + out->normal[j] = LittleFloat( in->normal[j] ); + if ( out->normal[j] < 0 ) { + bits |= 1 << j; + } + } + + out->dist = LittleFloat( in->dist ); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide; + + fogs = ( void * )( fileBase + l->fileofs ); + if ( l->filelen % sizeof( *fogs ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + count = l->filelen / sizeof( *fogs ); + + // create fog strucutres for them + s_worldData.numfogs = count + 1; + s_worldData.fogs = ri.Hunk_Alloc( s_worldData.numfogs * sizeof( *out ), h_low ); + out = s_worldData.fogs + 1; + + if ( !count ) { + return; + } + + brushes = ( void * )( fileBase + brushesLump->fileofs ); + if ( brushesLump->filelen % sizeof( *brushes ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + brushesCount = brushesLump->filelen / sizeof( *brushes ); + + sides = ( void * )( fileBase + sidesLump->fileofs ); + if ( sidesLump->filelen % sizeof( *sides ) ) { + ri.Error( ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name ); + } + sidesCount = sidesLump->filelen / sizeof( *sides ); + + for ( i = 0 ; i < count ; i++, fogs++ ) { + out->originalBrushNumber = LittleLong( fogs->brushNum ); + + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + ri.Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = LittleLong( brush->firstSide ); + + if ( (unsigned)firstSide > sidesCount - 6 ) { + ri.Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][0] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][1] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][2] = s_worldData.planes[ planeNum ].dist; + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, qtrue ); + + out->parms = shader->fogParms; + + out->colorInt = ColorBytes4( shader->fogParms.color[0] * tr.identityLight, + shader->fogParms.color[1] * tr.identityLight, + shader->fogParms.color[2] * tr.identityLight, 1.0 ); + + d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque; + out->tcScale = 1.0f / ( d * 8 ); + + // set the gradient vector + sideNum = LittleLong( fogs->visibleSide ); + + if ( sideNum == -1 ) { + out->hasSurface = qfalse; + } else { + out->hasSurface = qtrue; + planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum ); + VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -s_worldData.planes[ planeNum ].dist; + } + + out++; + } + +} + + +/* +============== +R_FindLightGridBounds +============== +*/ +void R_FindLightGridBounds( vec3_t mins, vec3_t maxs ) { + world_t *w; + msurface_t *surf; + srfSurfaceFace_t *surfFace; +// cplane_t *plane; + struct shader_s *shd; + + qboolean foundGridBrushes = qfalse; + int i,j; + + w = &s_worldData; + +//----(SA) temp - disable this whole thing for now + VectorCopy( w->bmodels[0].bounds[0], mins ); + VectorCopy( w->bmodels[0].bounds[1], maxs ); + return; +//----(SA) temp + + + + + ClearBounds( mins, maxs ); + +// wrong! + for ( i = 0; i < w->bmodels[0].numSurfaces; i++ ) { + surf = w->bmodels[0].firstSurface + i; + shd = surf->shader; + + if ( !( *surf->data == SF_FACE ) ) { + continue; + } + + if ( !( shd->contentFlags & CONTENTS_LIGHTGRID ) ) { + continue; + } + + foundGridBrushes = qtrue; + } + + +// wrong! + for ( i = 0; i < w->numsurfaces; i++ ) { + surf = &w->surfaces[i]; + shd = surf->shader; + if ( !( *surf->data == SF_FACE ) ) { + continue; + } + + if ( !( shd->contentFlags & CONTENTS_LIGHTGRID ) ) { + continue; + } + + foundGridBrushes = qtrue; + + surfFace = ( srfSurfaceFace_t * )surf->data; + + for ( j = 0; j < surfFace->numPoints; j++ ) { + AddPointToBounds( surfFace->points[j], mins, maxs ); + } + + } + + // go through brushes looking for lightgrid +// for ( i = 0 ; i < numbrushes ; i++ ) { +// db = &dbrushes[i]; +// +// if (!(dshaders[db->shaderNum].contentFlags & CONTENTS_LIGHTGRID)) { +// continue; +// } +// +// foundGridBrushes = qtrue; +// +// // go through light grid surfaces for bounds +// for ( j = 0 ; j < db->numSides ; j++ ) { +// s = &dbrushsides[ db->firstSide + j ]; +// +// surfmin[0] = -dplanes[ dbrushsides[ db->firstSide + 0 ].planeNum ].dist - 1; +// surfmin[1] = -dplanes[ dbrushsides[ db->firstSide + 2 ].planeNum ].dist - 1; +// surfmin[2] = -dplanes[ dbrushsides[ db->firstSide + 4 ].planeNum ].dist - 1; +// surfmax[0] = dplanes[ dbrushsides[ db->firstSide + 1 ].planeNum ].dist + 1; +// surfmax[1] = dplanes[ dbrushsides[ db->firstSide + 3 ].planeNum ].dist + 1; +// surfmax[2] = dplanes[ dbrushsides[ db->firstSide + 5 ].planeNum ].dist + 1; +// AddPointToBounds (surfmin, mins, maxs); +// AddPointToBounds (surfmax, mins, maxs); +// } +// } + + +//----(SA) temp + foundGridBrushes = qfalse; // disable this whole thing for now +//----(SA) temp + + if ( !foundGridBrushes ) { + VectorCopy( w->bmodels[0].bounds[0], mins ); + VectorCopy( w->bmodels[0].bounds[1], maxs ); + } +} + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( lump_t *l ) { + int i; + vec3_t maxs; + int numGridPoints; + world_t *w; +// float *wMins, *wMaxs; + vec3_t wMins, wMaxs; + + w = &s_worldData; + + w->lightGridInverseSize[0] = 1.0 / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0 / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0 / w->lightGridSize[2]; + +//----(SA) modified + R_FindLightGridBounds( wMins, wMaxs ); +// wMins = w->bmodels[0].bounds[0]; +// wMaxs = w->bmodels[0].bounds[1]; +//----(SA) end + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = ( maxs[i] - w->lightGridOrigin[i] ) / w->lightGridSize[i] + 1; + } + + numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( l->filelen != numGridPoints * 8 ) { + ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridData = ri.Hunk_Alloc( l->filelen, h_low ); + memcpy( w->lightGridData, ( void * )( fileBase + l->fileofs ), l->filelen ); + + // deal with overbright bits + for ( i = 0 ; i < numGridPoints ; i++ ) { + R_ColorShiftLightingBytes( &w->lightGridData[i * 8], &w->lightGridData[i * 8] ); + R_ColorShiftLightingBytes( &w->lightGridData[i * 8 + 3], &w->lightGridData[i * 8 + 3] ); + } +} + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( lump_t *l ) { + char *p, *token, *s; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + + w = &s_worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + p = ( char * )( fileBase + l->fileofs ); + + // store for reference by the cgame + w->entityString = ri.Hunk_Alloc( l->filelen + 1, h_low ); + strcpy( w->entityString, p ); + w->entityParsePoint = w->entityString; + + token = COM_ParseExt( &p, qtrue ); + if ( !*token || *token != '{' ) { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz( keyname, token, sizeof( keyname ) ); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz( value, token, sizeof( value ) ); + + // check for remapping of shaders for vertex lighting + s = "vertexremapshader"; + if ( !Q_strncmp( keyname, s, strlen( s ) ) ) { + s = strchr( value, ';' ); + if ( !s ) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value ); + break; + } + *s++ = 0; + // NERVE - SMF - temp fix, don't allow remapping of shader + // - fixes not drawing terrain surfaces when r_vertexLight is true even when remapped shader is present +// if (r_vertexLight->integer) { +// R_RemapShader(value, s, "0"); +// } + continue; + } + // check for remapping of shaders + s = "remapshader"; + if ( !Q_strncmp( keyname, s, strlen( s ) ) ) { + s = strchr( value, ';' ); + if ( !s ) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in shaderremap '%s'\n", value ); + break; + } + *s++ = 0; + R_RemapShader( value, s, "0" ); + continue; + } + // check for a different grid size + if ( !Q_stricmp( keyname, "gridsize" ) ) { + sscanf( value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + } +} + +/* +================= +R_GetEntityToken +================= +*/ +qboolean R_GetEntityToken( char *buffer, int size ) { + const char *s; + + s = COM_Parse( &s_worldData.entityParsePoint ); + Q_strncpyz( buffer, s, size ); + if ( !s_worldData.entityParsePoint || !s[0] ) { + s_worldData.entityParsePoint = s_worldData.entityString; + return qfalse; + } else { + return qtrue; + } +} + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap( const char *name ) { + int i; + dheader_t *header; + byte *buffer; + byte *startMarker; + + skyboxportal = 0; + + if ( tr.worldMapLoaded ) { + ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" ); + } + + // set default sun direction to be used if it isn't + // overridden by a shader + tr.sunDirection[0] = 0.45; + tr.sunDirection[1] = 0.3; + tr.sunDirection[2] = 0.9; + + tr.sunShader = 0; // clear sunshader so it's not there if the level doesn't specify it + + // inalidate fogs (likely to be re-initialized to new values by the current map) + // TODO:(SA)this is sort of silly. I'm going to do a general cleanup on fog stuff + // now that I can see how it's been used. (functionality can narrow since + // it's not used as much as it's designed for.) + R_SetFog( FOG_SKY, 0, 0, 0, 0, 0, 0 ); + R_SetFog( FOG_PORTALVIEW,0, 0, 0, 0, 0, 0 ); + R_SetFog( FOG_HUD, 0, 0, 0, 0, 0, 0 ); + R_SetFog( FOG_MAP, 0, 0, 0, 0, 0, 0 ); + R_SetFog( FOG_CURRENT, 0, 0, 0, 0, 0, 0 ); + R_SetFog( FOG_TARGET, 0, 0, 0, 0, 0, 0 ); + R_SetFog( FOG_WATER, 0, 0, 0, 0, 0, 0 ); + R_SetFog( FOG_SERVER, 0, 0, 0, 0, 0, 0 ); + + VectorNormalize( tr.sunDirection ); + + tr.worldMapLoaded = qtrue; + + // load it + ri.FS_ReadFile( name, (void **)&buffer ); + if ( !buffer ) { + ri.Error( ERR_DROP, "RE_LoadWorldMap: %s not found", name ); + } + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + + memset( &s_worldData, 0, sizeof( s_worldData ) ); + Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); + + Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) ); + COM_StripExtension( s_worldData.baseName, s_worldData.baseName ); + + startMarker = ri.Hunk_Alloc( 0, h_low ); + c_gridVerts = 0; + + header = (dheader_t *)buffer; + fileBase = (byte *)header; + + i = LittleLong( header->version ); + if ( i != BSP_VERSION ) { + ri.Error( ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", + name, i, BSP_VERSION ); + } + + // swap all the lumps + for ( i = 0 ; i < sizeof( dheader_t ) / 4 ; i++ ) { + ( (int *)header )[i] = LittleLong( ( (int *)header )[i] ); + } + + // load into heap + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadShaders( &header->lumps[LUMP_SHADERS] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadPlanes( &header->lumps[LUMP_PLANES] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadMarksurfaces( &header->lumps[LUMP_LEAFSURFACES] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadNodesAndLeafs( &header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadSubmodels( &header->lumps[LUMP_MODELS] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadEntities( &header->lumps[LUMP_ENTITIES] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] ); + ri.Cmd_ExecuteText( EXEC_NOW, "updatescreen\n" ); + + s_worldData.dataSize = (byte *)ri.Hunk_Alloc( 0, h_low ) - startMarker; + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &s_worldData; + + // reset fog to world fog (if present) + R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP,20,0,0,0,0 ); + +//----(SA) set the sun shader if there is one + if ( tr.sunShaderName ) { + tr.sunShader = R_FindShader( tr.sunShaderName, LIGHTMAP_NONE, qtrue ); + } + +//----(SA) end + ri.FS_FreeFile( buffer ); +} + diff --git a/src/renderer/tr_cmds.c b/src/renderer/tr_cmds.c new file mode 100644 index 0000000..a853adb --- /dev/null +++ b/src/renderer/tr_cmds.c @@ -0,0 +1,543 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "tr_local.h" + +volatile renderCommandList_t *renderCommandList; + +volatile qboolean renderThreadActive; + + +/* +===================== +R_PerformanceCounters +===================== +*/ +void R_PerformanceCounters( void ) { + if ( !r_speeds->integer ) { + // clear the counters even if we aren't printing + memset( &tr.pc, 0, sizeof( tr.pc ) ); + memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); + return; + } + + if ( r_speeds->integer == 1 ) { + ri.Printf( PRINT_ALL, "%i/%i shaders/surfs %i leafs %i verts %i/%i tris %.2f mtex %.2f dc\n", + backEnd.pc.c_shaders, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes, + backEnd.pc.c_indexes / 3, backEnd.pc.c_totalIndexes / 3, + R_SumOfUsedImages() / ( 1000000.0f ), backEnd.pc.c_overDraw / (float)( glConfig.vidWidth * glConfig.vidHeight ) ); + } else if ( r_speeds->integer == 2 ) { + ri.Printf( PRINT_ALL, "(patch) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out, + tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out ); + ri.Printf( PRINT_ALL, "(md3) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out, + tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out ); + } else if ( r_speeds->integer == 3 ) { + ri.Printf( PRINT_ALL, "viewcluster: %i\n", tr.viewCluster ); + } else if ( r_speeds->integer == 4 ) { + if ( backEnd.pc.c_dlightVertexes ) { + ri.Printf( PRINT_ALL, "dlight srf:%i culled:%i verts:%i tris:%i\n", + tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled, + backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 ); + } + } +//----(SA) this is unnecessary since it will always show 2048. I moved this to where it is accurate for the world +// else if (r_speeds->integer == 5 ) +// { +// ri.Printf( PRINT_ALL, "zFar: %.0f\n", tr.viewParms.zFar ); +// } + else if ( r_speeds->integer == 6 ) { + ri.Printf( PRINT_ALL, "flare adds:%i tests:%i renders:%i\n", + backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders ); + } + + memset( &tr.pc, 0, sizeof( tr.pc ) ); + memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + + +/* +==================== +R_InitCommandBuffers +==================== +*/ +void R_InitCommandBuffers( void ) { + glConfig.smpActive = qfalse; + if ( r_smp->integer ) { + ri.Printf( PRINT_ALL, "Trying SMP acceleration...\n" ); + if ( GLimp_SpawnRenderThread( RB_RenderThread ) ) { + ri.Printf( PRINT_ALL, "...succeeded.\n" ); + glConfig.smpActive = qtrue; + } else { + ri.Printf( PRINT_ALL, "...failed.\n" ); + } + } +} + +/* +==================== +R_ShutdownCommandBuffers +==================== +*/ +void R_ShutdownCommandBuffers( void ) { + // kill the rendering thread + if ( glConfig.smpActive ) { + GLimp_WakeRenderer( NULL ); + glConfig.smpActive = qfalse; + } +} + +/* +==================== +R_IssueRenderCommands +==================== +*/ +int c_blockedOnRender; +int c_blockedOnMain; + +void R_IssueRenderCommands( qboolean runPerformanceCounters ) { + renderCommandList_t *cmdList; + + if ( !tr.registered ) { //DAJ BUGFIX + return; + } + cmdList = &backEndData[tr.smpFrame]->commands; + assert( cmdList ); // bk001205 + // add an end-of-list command + *( int * )( cmdList->cmds + cmdList->used ) = RC_END_OF_LIST; + + // clear it out, in case this is a sync and not a buffer flip + cmdList->used = 0; + + if ( glConfig.smpActive ) { + // if the render thread is not idle, wait for it + if ( renderThreadActive ) { + c_blockedOnRender++; + if ( r_showSmp->integer ) { + ri.Printf( PRINT_ALL, "R" ); + } + } else { + c_blockedOnMain++; + if ( r_showSmp->integer ) { + ri.Printf( PRINT_ALL, "." ); + } + } + + // sleep until the renderer has completed + GLimp_FrontEndSleep(); + } + + // at this point, the back end thread is idle, so it is ok + // to look at it's performance counters + if ( runPerformanceCounters ) { + R_PerformanceCounters(); + } + + // actually start the commands going + if ( !r_skipBackEnd->integer ) { + // let it start on the new batch + if ( !glConfig.smpActive ) { + RB_ExecuteRenderCommands( cmdList->cmds ); + } else { + GLimp_WakeRenderer( cmdList ); + } + } +} + + +/* +==================== +R_SyncRenderThread + +Issue any pending commands and wait for them to complete. +After exiting, the render thread will have completed its work +and will remain idle and the main thread is free to issue +OpenGL calls until R_IssueRenderCommands is called. +==================== +*/ +void R_SyncRenderThread( void ) { + if ( !tr.registered ) { + return; + } + R_IssueRenderCommands( qfalse ); + + if ( !glConfig.smpActive ) { + return; + } + GLimp_FrontEndSleep(); +} + +/* +============ +R_GetCommandBuffer + +make sure there is enough command space, waiting on the +render thread if needed. +============ +*/ +void *R_GetCommandBuffer( int bytes ) { + renderCommandList_t *cmdList; + + if ( !tr.registered ) { //DAJ BUGFIX + return NULL; + } + cmdList = &backEndData[tr.smpFrame]->commands; + + // always leave room for the end of list command + if ( cmdList->used + bytes + 4 > MAX_RENDER_COMMANDS ) { + if ( bytes > MAX_RENDER_COMMANDS - 4 ) { + ri.Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes ); + } + // if we run out of room, just start dropping commands + return NULL; + } + + cmdList->used += bytes; + + return cmdList->cmds + cmdList->used - bytes; +} + + +/* +============= +R_AddDrawSurfCmd + +============= +*/ +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) { + drawSurfsCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_SURFS; + + cmd->drawSurfs = drawSurfs; + cmd->numDrawSurfs = numDrawSurfs; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; +} + + +/* +============= +RE_SetColor + +Passing NULL will set the color to white +============= +*/ +void RE_SetColor( const float *rgba ) { + setColorCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SET_COLOR; + if ( !rgba ) { + static float colorWhite[4] = { 1, 1, 1, 1 }; + + rgba = colorWhite; + } + + cmd->color[0] = rgba[0]; + cmd->color[1] = rgba[1]; + cmd->color[2] = rgba[2]; + cmd->color[3] = rgba[3]; +} + + +/* +============= +RE_StretchPic +============= +*/ +void RE_StretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + stretchPicCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_STRETCH_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; +} + + +/* +============= +RE_RotatedPic +============= +*/ +void RE_RotatedPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, float angle ) { + stretchPicCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_ROTATED_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + + // fixup + cmd->w /= 2; + cmd->h /= 2; + cmd->x += cmd->w; + cmd->y += cmd->h; + cmd->w = sqrt( ( cmd->w * cmd->w ) + ( cmd->h * cmd->h ) ); + cmd->h = cmd->w; + + cmd->angle = angle; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; +} + +//----(SA) added +/* +============== +RE_StretchPicGradient +============== +*/ +void RE_StretchPicGradient( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, const float *gradientColor, int gradientType ) { + stretchPicCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_STRETCH_PIC_GRADIENT; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; + + if ( !gradientColor ) { + static float colorWhite[4] = { 1, 1, 1, 1 }; + + gradientColor = colorWhite; + } + + cmd->gradientColor[0] = gradientColor[0] * 255; + cmd->gradientColor[1] = gradientColor[1] * 255; + cmd->gradientColor[2] = gradientColor[2] * 255; + cmd->gradientColor[3] = gradientColor[3] * 255; + cmd->gradientType = gradientType; +} +//----(SA) end + + +/* +==================== +RE_BeginFrame + +If running in stereo, RE_BeginFrame will be called twice +for each RE_EndFrame +==================== +*/ +void RE_BeginFrame( stereoFrame_t stereoFrame ) { + drawBufferCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + glState.finishCalled = qfalse; + + tr.frameCount++; + tr.frameSceneNum = 0; + + // + // do overdraw measurement + // + if ( r_measureOverdraw->integer ) { + if ( glConfig.stencilBits < 4 ) { + ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } else if ( r_shadows->integer == 2 ) { + ri.Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } else + { + R_SyncRenderThread(); + qglEnable( GL_STENCIL_TEST ); + qglStencilMask( ~0U ); + qglClearStencil( 0U ); + qglStencilFunc( GL_ALWAYS, 0U, ~0U ); + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + r_measureOverdraw->modified = qfalse; + } else + { + // this is only reached if it was on and is now off + if ( r_measureOverdraw->modified ) { + R_SyncRenderThread(); + qglDisable( GL_STENCIL_TEST ); + } + r_measureOverdraw->modified = qfalse; + } + + // + // texturemode stuff + // + if ( r_textureMode->modified ) { + R_SyncRenderThread(); + GL_TextureMode( r_textureMode->string ); + r_textureMode->modified = qfalse; + } + + // + // NVidia stuff + // + + // fog control + if ( glConfig.NVFogAvailable && r_nv_fogdist_mode->modified ) { + r_nv_fogdist_mode->modified = qfalse; + if ( !Q_stricmp( r_nv_fogdist_mode->string, "GL_EYE_PLANE_ABSOLUTE_NV" ) ) { + glConfig.NVFogMode = (int)GL_EYE_PLANE_ABSOLUTE_NV; + } else if ( !Q_stricmp( r_nv_fogdist_mode->string, "GL_EYE_PLANE" ) ) { + glConfig.NVFogMode = (int)GL_EYE_PLANE; + } else if ( !Q_stricmp( r_nv_fogdist_mode->string, "GL_EYE_RADIAL_NV" ) ) { + glConfig.NVFogMode = (int)GL_EYE_RADIAL_NV; + } else { + // in case this was really 'else', store a valid value for next time + glConfig.NVFogMode = (int)GL_EYE_RADIAL_NV; + ri.Cvar_Set( "r_nv_fogdist_mode", "GL_EYE_RADIAL_NV" ); + } + } + + // + // gamma stuff + // + if ( r_gamma->modified ) { + r_gamma->modified = qfalse; + + R_SyncRenderThread(); + R_SetColorMappings(); + } + + // check for errors + if ( !r_ignoreGLErrors->integer ) { + int err; + + R_SyncRenderThread(); + if ( ( err = qglGetError() ) != GL_NO_ERROR ) { + ri.Error( ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!\n", err ); + } + } + + // + // draw buffer stuff + // + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_BUFFER; + + if ( glConfig.stereoEnabled ) { + if ( stereoFrame == STEREO_LEFT ) { + cmd->buffer = (int)GL_BACK_LEFT; + } else if ( stereoFrame == STEREO_RIGHT ) { + cmd->buffer = (int)GL_BACK_RIGHT; + } else { + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + } + } else { + if ( stereoFrame != STEREO_CENTER ) { + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); + } + if ( !Q_stricmp( r_drawBuffer->string, "GL_FRONT" ) ) { + cmd->buffer = (int)GL_FRONT; + } else { + cmd->buffer = (int)GL_BACK; + } + } +} + + +/* +============= +RE_EndFrame + +Returns the number of msec spent in the back end +============= +*/ +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) { + swapBuffersCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SWAP_BUFFERS; + + R_IssueRenderCommands( qtrue ); + + // use the other buffers next frame, because another CPU + // may still be rendering into the current ones + R_ToggleSmpFrame(); + + if ( frontEndMsec ) { + *frontEndMsec = tr.frontEndMsec; + } + tr.frontEndMsec = 0; + if ( backEndMsec ) { + *backEndMsec = backEnd.pc.msec; + } + backEnd.pc.msec = 0; +} + diff --git a/src/renderer/tr_cmesh.c b/src/renderer/tr_cmesh.c new file mode 100644 index 0000000..f323236 --- /dev/null +++ b/src/renderer/tr_cmesh.c @@ -0,0 +1,426 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_cmesh.c: compressed triangle model functions +// +// This is ripped from tr_mesh.c, and converted to use the compressed mesh format + +#include "tr_local.h" + +static float ProjectRadius( float r, vec3_t location ) { + float pr; + float dist; + float c; + vec3_t p; + float projected[4]; + + c = DotProduct( tr.viewParms.or.axis[0], tr.viewParms.or.origin ); + dist = DotProduct( tr.viewParms.or.axis[0], location ) - c; + + if ( dist <= 0 ) { + return 0; + } + + p[0] = 0; + p[1] = fabs( r ); + p[2] = -dist; + + projected[0] = p[0] * tr.viewParms.projectionMatrix[0] + + p[1] * tr.viewParms.projectionMatrix[4] + + p[2] * tr.viewParms.projectionMatrix[8] + + tr.viewParms.projectionMatrix[12]; + + projected[1] = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + projected[2] = p[0] * tr.viewParms.projectionMatrix[2] + + p[1] * tr.viewParms.projectionMatrix[6] + + p[2] * tr.viewParms.projectionMatrix[10] + + tr.viewParms.projectionMatrix[14]; + + projected[3] = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + + pr = projected[1] / projected[3]; + + if ( pr > 1.0f ) { + pr = 1.0f; + } + + return pr; +} + +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( mdcHeader_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + md3Frame_t *oldFrame, *newFrame; + int i; + + // compute frame pointers + newFrame = ( md3Frame_t * )( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + oldFrame = ( md3Frame_t * )( ( byte * ) header + header->ofsFrames ) + ent->e.oldframe; + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) { + if ( ent->e.frame == ent->e.oldframe ) { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) { + if ( sphereCull == CULL_OUT ) { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } else if ( sphereCull == CULL_IN ) { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + + +/* +================= +R_ComputeLOD + +================= +*/ +static int R_ComputeLOD( trRefEntity_t *ent ) { + float radius; + float flod, lodscale; + float projectedRadius; + md3Frame_t *frame; + int lod; + + if ( tr.currentModel->numLods < 2 ) { + // model has only 1 LOD level, skip computations and bias + lod = 0; + } else + { + // multiple LODs exist, so compute projected bounding sphere + // and use that as a criteria for selecting LOD + + // RF, checked for a forced lowest LOD + if ( ent->e.reFlags & REFLAG_FORCE_LOD ) { + return ( tr.currentModel->numLods - 1 ); + } + + frame = ( md3Frame_t * )( ( ( unsigned char * ) tr.currentModel->mdc[0] ) + tr.currentModel->mdc[0]->ofsFrames ); + + frame += ent->e.frame; + + radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] ); + + //----(SA) testing + if ( ent->e.reFlags & REFLAG_ORIENT_LOD ) { + // right now this is for trees, and pushes the lod distance way in. + // this is not the intended purpose, but is helpful for the new + // terrain level that has loads of trees +// radius = radius/2.0f; + } + //----(SA) end + + if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 ) { + lodscale = r_lodscale->value; + if ( lodscale > 20 ) { + lodscale = 20; + } + flod = 1.0f - projectedRadius * lodscale; + } else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } + + flod *= tr.currentModel->numLods; + lod = myftol( flod ); + + if ( lod < 0 ) { + lod = 0; + } else if ( lod >= tr.currentModel->numLods ) { + lod = tr.currentModel->numLods - 1; + } + } + + lod += r_lodbias->integer; + + if ( lod >= tr.currentModel->numLods ) { + lod = tr.currentModel->numLods - 1; + } + if ( lod < 0 ) { + lod = 0; + } + + return lod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +static int R_ComputeFogNum( mdcHeader_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + md3Frame_t *md3Frame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + md3Frame = ( md3Frame_t * )( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + VectorAdd( ent->e.origin, md3Frame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - md3Frame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + md3Frame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddMDCSurfaces + +================= +*/ +void R_AddMDCSurfaces( trRefEntity_t *ent ) { + int i; + mdcHeader_t *header = 0; + mdcSurface_t *surface = 0; + md3Shader_t *md3Shader = 0; + shader_t *shader = 0; + int cull; + int lod; + int fogNum; + qboolean personalModel; + + // don't add third_person objects if not in a portal + personalModel = ( ent->e.renderfx & RF_THIRD_PERSON ) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->mdc[0]->numFrames; + ent->e.oldframe %= tr.currentModel->mdc[0]->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( ( ent->e.frame >= tr.currentModel->mdc[0]->numFrames ) + || ( ent->e.frame < 0 ) + || ( ent->e.oldframe >= tr.currentModel->mdc[0]->numFrames ) + || ( ent->e.oldframe < 0 ) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddMDCSurfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // compute LOD + // + lod = R_ComputeLOD( ent ); + + header = tr.currentModel->mdc[lod]; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel( header, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( header, ent ); + + // + // draw all surfaces + // + surface = ( mdcSurface_t * )( (byte *)header + header->ofsSurfaces ); + for ( i = 0 ; i < header->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) { + shader = R_GetShaderByHandle( ent->e.customShader ); + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; +//----(SA) added blink + if ( ent->e.renderfx & RF_BLINK ) { + const char *s = va( "%s_b", surface->name ); // append '_b' for 'blink' + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + if ( !strcmp( skin->surfaces[j]->name, s ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + } + + if ( shader == tr.defaultShader ) { // blink reference in skin was not found + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + + if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + } +//----(SA) end + } else if ( surface->numShaders <= 0 ) { + shader = tr.defaultShader; + } else { + md3Shader = ( md3Shader_t * )( (byte *)surface + surface->ofsShaders ); + md3Shader += ent->e.skinNum % surface->numShaders; + shader = tr.shaders[ md3Shader->shaderIndex ]; + } + + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !( ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && ( ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse ); + } + +//----(SA) for testing polygon shadows (on /all/ models) + if ( r_shadows->integer == 4 ) { + R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse ); + } + +//----(SA) done testing + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { + R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse ); + } + + surface = ( mdcSurface_t * )( (byte *)surface + surface->ofsEnd ); + } + +} + diff --git a/src/renderer/tr_curve.c b/src/renderer/tr_curve.c new file mode 100644 index 0000000..35e672a --- /dev/null +++ b/src/renderer/tr_curve.c @@ -0,0 +1,634 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "tr_local.h" + +/* + +This file does all of the processing necessary to turn a raw grid of points +read from the map file into a srfGridMesh_t ready for rendering. + +The level of detail solution is direction independent, based only on subdivided +distance from the true curve. + +Only a single entry point: + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + +*/ + + +/* +============ +LerpDrawVert +============ +*/ +static void LerpDrawVert( drawVert_t *a, drawVert_t *b, drawVert_t *out ) { + out->xyz[0] = 0.5f * ( a->xyz[0] + b->xyz[0] ); + out->xyz[1] = 0.5f * ( a->xyz[1] + b->xyz[1] ); + out->xyz[2] = 0.5f * ( a->xyz[2] + b->xyz[2] ); + + out->st[0] = 0.5f * ( a->st[0] + b->st[0] ); + out->st[1] = 0.5f * ( a->st[1] + b->st[1] ); + + out->lightmap[0] = 0.5f * ( a->lightmap[0] + b->lightmap[0] ); + out->lightmap[1] = 0.5f * ( a->lightmap[1] + b->lightmap[1] ); + + out->color[0] = ( a->color[0] + b->color[0] ) >> 1; + out->color[1] = ( a->color[1] + b->color[1] ) >> 1; + out->color[2] = ( a->color[2] + b->color[2] ) >> 1; + out->color[3] = ( a->color[3] + b->color[3] ) >> 1; +} + +/* +============ +Transpose +============ +*/ +static void Transpose( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j][i]; + ctrl[j][i] = ctrl[i][j]; + ctrl[i][j] = temp; + } else { + // just copy + ctrl[j][i] = ctrl[i][j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[j][i]; + ctrl[j][i] = temp; + } else { + // just copy + ctrl[i][j] = ctrl[j][i]; + } + } + } + } + +} + + +/* +================= +MakeMeshNormals + +Handles all the complicated wrapping and degenerate cases +================= +*/ +static void MakeMeshNormals( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count; + vec3_t base; + vec3_t delta; + int x, y; + drawVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; + static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = qfalse; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i][0].xyz, ctrl[i][width - 1].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0][i].xyz, ctrl[height - 1][i].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width ) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j][i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y][x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[( k + 1 ) & 7] ) { + continue; // didn't get two points + } + CrossProduct( around[( k + 1 ) & 7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + if ( count == 0 ) { +//printf("bad normal\n"); + count = 1; + } + VectorNormalize2( sum, dv->normal ); + } + } +} + +/* +============ +InvertCtrl +============ +*/ +static void InvertCtrl( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width / 2 ; j++ ) { + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[i][width - 1 - j]; + ctrl[i][width - 1 - j] = temp; + } + } +} + + +/* +================= +InvertErrorTable +================= +*/ +static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1][i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0][i] = copy[1][height - 1 - i]; + } + +} + +/* +================== +PutPointsOnCurve +================== +*/ +static void PutPointsOnCurve( drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], + int width, int height ) { + int i, j; + drawVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j + 1][i], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j - 1][i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j][i + 1], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j][i - 1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } +} + + +/* +================= +R_CreateSurfaceGridMesh +================= +*/ +srfGridMesh_t *R_CreateSurfaceGridMesh( int width, int height, + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE] ) { + int i, j, size; + drawVert_t *vert; + vec3_t tmpVec; + srfGridMesh_t *grid; + + // copy the results out to a grid + size = ( width * height - 1 ) * sizeof( drawVert_t ) + sizeof( *grid ); + +#ifdef PATCH_STITCHING + grid = /*ri.Hunk_Alloc*/ ri.Z_Malloc( size ); + Com_Memset( grid, 0, size ); + + grid->widthLodError = /*ri.Hunk_Alloc*/ ri.Z_Malloc( width * 4 ); + memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = /*ri.Hunk_Alloc*/ ri.Z_Malloc( height * 4 ); + memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#else + grid = ri.Hunk_Alloc( size, h_low ); + memset( grid, 0, size ); + + grid->widthLodError = ri.Hunk_Alloc( width * 4, h_low ); + memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = ri.Hunk_Alloc( height * 4, h_low ); + memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#endif + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j * width + i]; + *vert = ctrl[j][i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + // + return grid; +} + +/* +================= +R_FreeSurfaceGridMesh +================= +*/ +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) { + ri.Free( grid->widthLodError ); + ri.Free( grid->heightLodError ); + ri.Free( grid ); +} + +/* +================= +R_SubdividePatchToGrid +================= +*/ +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + int i, j, k, l; + drawVert_t prev, next, mid; + float len, maxLen; + int dir; + int t; + MAC_STATIC drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j][i] = points[j * width + i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir][j] = 0; + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < width ; j += 2 ) { + // check subdivided midpoints against control points + + // FIXME: also check midpoints of adjacent patches against the control points + // this would basically stitch all patches in the same LOD group together. + + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = ( ctrl[i][j].xyz[l] + ctrl[i][j + 1].xyz[l] * 2 + + ctrl[i][j + 2].xyz[l] ) * 0.25f; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz ); + VectorSubtract( ctrl[i][j + 2].xyz, ctrl[i][j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz ); + len = VectorLengthSquared( midxyz ); // we will do the sqrt later + + if ( len > maxLen ) { + maxLen = len; + } + } + + maxLen = sqrt( maxLen ); + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1f ) { + errorTable[dir][j + 1] = 999; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir][j + 1] = 1.0f / maxLen; + continue; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir][j + 1] = 1.0f / maxLen; + continue; // didn't need subdivision + } + + errorTable[dir][j + 2] = 1.0f / maxLen; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i][j], &ctrl[i][j + 1], &prev ); + LerpDrawVert( &ctrl[i][j + 1], &ctrl[i][j + 2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i][k] = ctrl[i][k - 2]; + } + ctrl[i][j + 1] = prev; + ctrl[i][j + 2] = mid; + ctrl[i][j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width - 1 ; i++ ) { + if ( errorTable[0][i] != 999 ) { + continue; + } + for ( j = i + 1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k][j - 1] = ctrl[k][j]; + } + errorTable[0][j - 1] = errorTable[0][j]; + } + width--; + } + + for ( i = 1 ; i < height - 1 ; i++ ) { + if ( errorTable[1][i] != 999 ) { + continue; + } + for ( j = i + 1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[j - 1][k] = ctrl[j][k]; + } + errorTable[1][j - 1] = errorTable[1][j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + return R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); +} + +/* +=============== +R_GridInsertColumn +=============== +*/ +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ) { + int i, j; + int width, height, oldwidth; + MAC_STATIC drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + + oldwidth = 0; + width = grid->width + 1; + if ( width > MAX_GRID_SIZE ) { + return NULL; + } + height = grid->height; + for ( i = 0; i < width; i++ ) { + if ( i == column ) { + //insert new column + for ( j = 0; j < grid->height; j++ ) { + LerpDrawVert( &grid->verts[j * grid->width + i - 1], &grid->verts[j * grid->width + i], &ctrl[j][i] ); + if ( j == row ) { + VectorCopy( point, ctrl[j][i].xyz ); + } + } + errorTable[0][i] = loderror; + continue; + } + errorTable[0][i] = grid->widthLodError[oldwidth]; + for ( j = 0; j < grid->height; j++ ) { + ctrl[j][i] = grid->verts[j * grid->width + oldwidth]; + } + oldwidth++; + } + for ( j = 0; j < grid->height; j++ ) { + errorTable[1][j] = grid->heightLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy( grid->lodOrigin, lodOrigin ); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh( grid ); + // create a new grid + grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); + grid->lodRadius = lodRadius; + VectorCopy( lodOrigin, grid->lodOrigin ); + return grid; +} + +/* +=============== +R_GridInsertRow +=============== +*/ +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ) { + int i, j; + int width, height, oldheight; + MAC_STATIC drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + + oldheight = 0; + width = grid->width; + height = grid->height + 1; + if ( height > MAX_GRID_SIZE ) { + return NULL; + } + for ( i = 0; i < height; i++ ) { + if ( i == row ) { + //insert new row + for ( j = 0; j < grid->width; j++ ) { + LerpDrawVert( &grid->verts[( i - 1 ) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] ); + if ( j == column ) { + VectorCopy( point, ctrl[i][j].xyz ); + } + } + errorTable[1][i] = loderror; + continue; + } + errorTable[1][i] = grid->heightLodError[oldheight]; + for ( j = 0; j < grid->width; j++ ) { + ctrl[i][j] = grid->verts[oldheight * grid->width + j]; + } + oldheight++; + } + for ( j = 0; j < grid->width; j++ ) { + errorTable[0][j] = grid->widthLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy( grid->lodOrigin, lodOrigin ); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh( grid ); + // create a new grid + grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); + grid->lodRadius = lodRadius; + VectorCopy( lodOrigin, grid->lodOrigin ); + return grid; +} diff --git a/src/renderer/tr_flares.c b/src/renderer/tr_flares.c new file mode 100644 index 0000000..e40e079 --- /dev/null +++ b/src/renderer/tr_flares.c @@ -0,0 +1,537 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_flares.c + +#include "tr_local.h" + +/* +============================================================================= + +LIGHT FLARES + +A light flare is an effect that takes place inside the eye when bright light +sources are visible. The size of the flare reletive to the screen is nearly +constant, irrespective of distance, but the intensity should be proportional to the +projected area of the light source. + +A surface that has been flagged as having a light flare will calculate the depth +buffer value that it's midpoint should have when the surface is added. + +After all opaque surfaces have been rendered, the depth buffer is read back for +each flare in view. If the point has not been obscured by a closer surface, the +flare should be drawn. + +Surfaces that have a repeated texture should never be flagged as flaring, because +there will only be a single flare added at the midpoint of the polygon. + +To prevent abrupt popping, the intensity of the flare is interpolated up and +down as it changes visibility. This involves scene to scene state, unlike almost +all other aspects of the renderer, and is complicated by the fact that a single +frame may have multiple scenes. + +RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially +up to five or more times in a frame with 3D status bar icons). + +============================================================================= +*/ + + +// flare states maintain visibility over multiple frames for fading +// layers: view, mirror, menu +typedef struct flare_s { + struct flare_s *next; // for active chain + + int addedFrame; + + qboolean inPortal; // true if in a portal view of the scene + int frameSceneNum; + void *surface; + int fogNum; + + int fadeTime; + + qboolean cgvisible; // for coronas, the client determines current visibility, but it's still inserted so it will fade out properly + qboolean visible; // state of last test + float drawIntensity; // may be non 0 even if !visible due to fading + + int windowX, windowY; + float eyeZ; + + vec3_t color; + float scale; + + int id; +} flare_t; + +#define MAX_FLARES 128 + +flare_t r_flareStructs[MAX_FLARES]; +flare_t *r_activeFlares, *r_inactiveFlares; + + +/* +================== +R_ClearFlares +================== +*/ +void R_ClearFlares( void ) { + int i; + + memset( r_flareStructs, 0, sizeof( r_flareStructs ) ); + r_activeFlares = NULL; + r_inactiveFlares = NULL; + + for ( i = 0 ; i < MAX_FLARES ; i++ ) { + r_flareStructs[i].next = r_inactiveFlares; + r_inactiveFlares = &r_flareStructs[i]; + } +} + + +/* +================== +RB_AddFlare + +This is called at surface tesselation time +================== +*/ +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, float scale, vec3_t normal, int id, qboolean cgvisible ) { //----(SA) added scale. added id. added visible + int i; + flare_t *f, *oldest; + vec3_t local; + float d; + vec4_t eye, clip, normalized, window; + + backEnd.pc.c_flareAdds++; + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.or.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + //ri.Printf(PRINT_ALL, "src: %f %f %f \n", point[0], point[1], point[2]); + //ri.Printf(PRINT_ALL, "eye: %f %f %f %f\n", eye[0], eye[1], eye[2], eye[3]); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + //ri.Printf(PRINT_ALL, "window: %f %f %f \n", window[0], window[1], window[2]); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + + // see if a flare with a matching surface, scene, and view exists + oldest = r_flareStructs; + for ( f = r_activeFlares ; f ; f = f->next ) { +// if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum && f->inPortal == backEnd.viewParms.isPortal ) { + + // (SA) added back in more checks for different scenes + if ( f->id == id && f->frameSceneNum == backEnd.viewParms.frameSceneNum && f->inPortal == backEnd.viewParms.isPortal ) { + break; + } + } + + // allocate a new one + if ( !f ) { + if ( !r_inactiveFlares ) { + // the list is completely full + return; + } + f = r_inactiveFlares; + r_inactiveFlares = r_inactiveFlares->next; + f->next = r_activeFlares; + r_activeFlares = f; + + f->surface = surface; + f->frameSceneNum = backEnd.viewParms.frameSceneNum; + f->inPortal = backEnd.viewParms.isPortal; + f->addedFrame = -1; + f->id = id; + } + + f->cgvisible = cgvisible; + + if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 2000; + } + + f->addedFrame = backEnd.viewParms.frameCount; + f->fogNum = fogNum; + + VectorCopy( color, f->color ); + + f->scale = scale; //----(SA) + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + if ( normal ) { + VectorSubtract( backEnd.viewParms.or.origin, point, local ); + VectorNormalizeFast( local ); + d = DotProduct( local, normal ); + VectorScale( f->color, d, f->color ); + } + + // save info needed to test + f->windowX = backEnd.viewParms.viewportX + window[0]; + f->windowY = backEnd.viewParms.viewportY + window[1]; + + f->eyeZ = eye[2]; +} + +/* +================== +RB_AddDlightFlares +================== +*/ +void RB_AddDlightFlares( void ) { + dlight_t *l; + int i, j, k; + int id = 0; + fog_t *fog; + + if ( r_flares->integer < 2 ) { + return; + } + + l = backEnd.refdef.dlights; + fog = tr.world->fogs; + for ( i = 0 ; i < backEnd.refdef.num_dlights ; i++, l++ ) { + + // find which fog volume the light is in + for ( j = 1 ; j < tr.world->numfogs ; j++ ) { + fog = &tr.world->fogs[j]; + for ( k = 0 ; k < 3 ; k++ ) { + if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j == tr.world->numfogs ) { + j = 0; + } + + RB_AddFlare( (void *)l, j, l->origin, l->color, 1.0f, NULL, id++, qtrue ); //----(SA) also set scale + } +} + + +/* +============== +RB_AddCoronaFlares +============== +*/ +void RB_AddCoronaFlares( void ) { + corona_t *cor; + int i, j, k; + fog_t *fog; + + if ( r_flares->integer != 1 && r_flares->integer != 3 ) { + return; + } + + if ( !( tr.world ) ) { // (SA) possible currently at the player model selection menu + return; + } + + cor = backEnd.refdef.coronas; + fog = tr.world->fogs; + for ( i = 0 ; i < backEnd.refdef.num_coronas ; i++, cor++ ) { + + // find which fog volume the corona is in + for ( j = 1 ; j < tr.world->numfogs ; j++ ) { + fog = &tr.world->fogs[j]; + for ( k = 0 ; k < 3 ; k++ ) { + if ( cor->origin[k] < fog->bounds[0][k] || cor->origin[k] > fog->bounds[1][k] ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j == tr.world->numfogs ) { + j = 0; + } + RB_AddFlare( (void *)cor, j, cor->origin, cor->color, cor->scale, NULL, cor->id, cor->visible ); + } +} + +/* +=============================================================================== + +FLARE BACK END + +=============================================================================== +*/ + +/* +================== +RB_TestFlare +================== +*/ +void RB_TestFlare( flare_t *f ) { +// float depth; + qboolean visible; + float fade; +// float screenZ; + + backEnd.pc.c_flareTests++; + + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync +// glState.finishCalled = qfalse; +// glState.finishCalled = qtrue; // (SA) Hmm, shouldn't this be true? + + // read back the z buffer contents +// qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); +// screenZ = backEnd.viewParms.projectionMatrix[14] / +// ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + //----(SA) 24 was way to low tolerance. It gave Dan problems with free standing light fixtures + //----(SA) I will monitor to see if changing this screws up any other situations + //----(SA) and 2 was way to high tolerance +// visible = ( -f->eyeZ - -screenZ ) < 2; +// visible = ( -f->eyeZ - -screenZ ) < 24; +// visible = ( -f->eyeZ - -screenZ ) < 6; + +// visible = qtrue; + visible = f->cgvisible; + + if ( visible ) { + if ( !f->visible ) { + f->visible = qtrue; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value; + } else { + if ( f->visible ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value; + } + + if ( fade < 0 ) { + fade = 0; + } + if ( fade > 1 ) { + fade = 1; + } + + f->drawIntensity = fade; +} + + +/* +================== +RB_RenderFlare +================== +*/ +void RB_RenderFlare( flare_t *f ) { + float size; + vec3_t color; + int iColor[3]; + + backEnd.pc.c_flareRenders++; + +//----(SA) changed to use alpha blend rather than additive blend +// this is to accomidate the fact we can't right now do +// additive blends and have them fog correctly with our distance fog. +// /when/ we fix the blend problems with distance fog, this should +// be changed back to additive since there's nearly no hit for that +// but the alpha blend is noticably slower. + +// VectorScale( f->color, f->drawIntensity*tr.identityLight, color ); + VectorScale( f->color, tr.identityLight, color ); //----(SA) mod for alpha blend rather than additive + + iColor[0] = color[0] * 255; + iColor[1] = color[1] * 255; + iColor[2] = color[2] * 255; + + size = backEnd.viewParms.viewportWidth * ( ( r_flareSize->value * f->scale ) / 640.0 + 8 / -f->eyeZ ); + + RB_BeginSurface( tr.flareShader, f->fogNum ); + + // FIXME: use quadstamp? + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = f->drawIntensity * 255; //----(SA) mod for alpha blend rather than additive +// tess.vertexColors[tess.numVertexes][3] = 255; //----(SA) mod for alpha blend rather than additive + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = f->drawIntensity * 255; //----(SA) mod for alpha blend rather than additive +// tess.vertexColors[tess.numVertexes][3] = 255; //----(SA) mod for alpha blend rather than additive + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = f->drawIntensity * 255; //----(SA) mod for alpha blend rather than additive +// tess.vertexColors[tess.numVertexes][3] = 255; //----(SA) mod for alpha blend rather than additive + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = f->drawIntensity * 255; //----(SA) mod for alpha blend rather than additive +// tess.vertexColors[tess.numVertexes][3] = 255; //----(SA) mod for alpha blend rather than additive + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); +} + +/* +================== +RB_RenderFlares + +Because flares are simulating an occular effect, they should be drawn after +everything (all views) in the entire frame has been drawn. + +Because of the way portals use the depth buffer to mark off areas, the +needed information would be lost after each view, so we are forced to draw +flares after each view. + +The resulting artifact is that flares in mirrors or portals don't dim properly +when occluded by something in the main view, and portal flares that should +extend past the portal edge will be overwritten. +================== +*/ +void RB_RenderFlares( void ) { + flare_t *f; + flare_t **prev; + qboolean draw; + + if ( !r_flares->integer ) { + return; + } + + // (SA) turned light flares back on. must evaluate problem id had with this + RB_AddDlightFlares(); + RB_AddCoronaFlares(); + + // perform z buffer readback on each flare in this view + draw = qfalse; + prev = &r_activeFlares; + while ( ( f = *prev ) != NULL ) { + // throw out any flares that weren't added last frame + if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) { + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + + // don't draw any here that aren't from this scene / portal + f->drawIntensity = 0; + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + RB_TestFlare( f ); + if ( f->drawIntensity ) { + draw = qtrue; + } else { + // this flare has completely faded out, so remove it from the chain + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + } + + prev = &f->next; + } + + if ( !draw ) { + return; // none visible + } + + if ( backEnd.viewParms.isPortal ) { + qglDisable( GL_CLIP_PLANE0 ); + } + + qglPushMatrix(); + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, + backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, + -99999, 99999 ); + + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal + && f->drawIntensity ) { + RB_RenderFlare( f ); + } + } + + qglPopMatrix(); + qglMatrixMode( GL_MODELVIEW ); + qglPopMatrix(); +} + diff --git a/src/renderer/tr_font.c b/src/renderer/tr_font.c new file mode 100644 index 0000000..45f54bd --- /dev/null +++ b/src/renderer/tr_font.c @@ -0,0 +1,550 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_font.c +// +// +// The font system uses FreeType 2.x to render TrueType fonts for use within the game. +// As of this writing ( Nov, 2000 ) Team Arena uses these fonts for all of the ui and +// about 90% of the cgame presentation. A few areas of the CGAME were left uses the old +// fonts since the code is shared with standard Q3A. +// +// If you include this font rendering code in a commercial product you MUST include the +// following somewhere with your product, see www.freetype.org for specifics or changes. +// The Freetype code also uses some hinting techniques that MIGHT infringe on patents +// held by apple so be aware of that also. +// +// As of Q3A 1.25+ and Team Arena, we are shipping the game with the font rendering code +// disabled. This removes any potential patent issues and it keeps us from having to +// distribute an actual TrueTrype font which is 1. expensive to do and 2. seems to require +// an act of god to accomplish. +// +// What we did was pre-render the fonts using FreeType ( which is why we leave the FreeType +// credit in the credits ) and then saved off the glyph data and then hand touched up the +// font bitmaps so they scale a bit better in GL. +// +// There are limitations in the way fonts are saved and reloaded in that it is based on +// point size and not name. So if you pre-render Helvetica in 18 point and Impact in 18 point +// you will end up with a single 18 point data file and image set. Typically you will want to +// choose 3 sizes to best approximate the scaling you will be doing in the ui scripting system +// +// In the UI Scripting code, a scale of 1.0 is equal to a 48 point font. In Team Arena, we +// use three or four scales, most of them exactly equaling the specific rendered size. We +// rendered three sizes in Team Arena, 12, 16, and 20. +// +// To generate new font data you need to go through the following steps. +// 1. delete the fontImage_x_xx.tga files and fontImage_xx.dat files from the fonts path. +// 2. in a ui script, specificy a font, smallFont, and bigFont keyword with font name and +// point size. the original TrueType fonts must exist in fonts at this point. +// 3. run the game, you should see things normally. +// 4. Exit the game and there will be three dat files and at least three tga files. The +// tga's are in 256x256 pages so if it takes three images to render a 24 point font you +// will end up with fontImage_0_24.tga through fontImage_2_24.tga +// 5. You will need to flip the tga's in Photoshop as the tga output code writes them upside +// down. +// 6. In future runs of the game, the system looks for these images and data files when a s +// specific point sized font is rendered and loads them for use. +// 7. Because of the original beta nature of the FreeType code you will probably want to hand +// touch the font bitmaps. +// +// Currently a define in the project turns on or off the FreeType code which is currently +// defined out. To pre-render new fonts you need enable the define ( BUILD_FREETYPE ) and +// uncheck the exclude from build check box in the FreeType2 area of the Renderer project. + + + +#include "tr_local.h" +#include "../qcommon/qcommon.h" + +//#define BUILD_FREETYPE +#ifdef BUILD_FREETYPE +#include "../ft2/fterrors.h" +#include "../ft2/ftsystem.h" +#include "../ft2/ftimage.h" +#include "../ft2/freetype.h" +#include "../ft2/ftoutln.h" + +#define _FLOOR( x ) ( ( x ) & - 64 ) +#define _CEIL( x ) ( ( ( x ) + 63 ) & - 64 ) +#define _TRUNC( x ) ( ( x ) >> 6 ) + +FT_Library ftLibrary = NULL; +#endif + +#define MAX_FONTS 6 +static int registeredFontCount = 0; +static fontInfo_t registeredFont[MAX_FONTS]; + +#ifdef BUILD_FREETYPE +void R_GetGlyphInfo( FT_GlyphSlot glyph, int *left, int *right, int *width, int *top, int *bottom, int *height, int *pitch ) { + + *left = _FLOOR( glyph->metrics.horiBearingX ); + *right = _CEIL( glyph->metrics.horiBearingX + glyph->metrics.width ); + *width = _TRUNC( *right - *left ); + + *top = _CEIL( glyph->metrics.horiBearingY ); + *bottom = _FLOOR( glyph->metrics.horiBearingY - glyph->metrics.height ); + *height = _TRUNC( *top - *bottom ); + *pitch = ( qtrue ? ( *width + 3 ) & - 4 : ( *width + 7 ) >> 3 ); +} + + +FT_Bitmap *R_RenderGlyph( FT_GlyphSlot glyph, glyphInfo_t* glyphOut ) { + + FT_Bitmap *bit2; + int left, right, width, top, bottom, height, pitch, size; + + R_GetGlyphInfo( glyph, &left, &right, &width, &top, &bottom, &height, &pitch ); + + if ( glyph->format == ft_glyph_format_outline ) { + size = pitch * height; + + bit2 = Z_Malloc( sizeof( FT_Bitmap ) ); + + bit2->width = width; + bit2->rows = height; + bit2->pitch = pitch; + bit2->pixel_mode = ft_pixel_mode_grays; + //bit2->pixel_mode = ft_pixel_mode_mono; + bit2->buffer = Z_Malloc( pitch * height ); + bit2->num_grays = 256; + + Com_Memset( bit2->buffer, 0, size ); + + FT_Outline_Translate( &glyph->outline, -left, -bottom ); + + FT_Outline_Get_Bitmap( ftLibrary, &glyph->outline, bit2 ); + + glyphOut->height = height; + glyphOut->pitch = pitch; + glyphOut->top = ( glyph->metrics.horiBearingY >> 6 ) + 1; + glyphOut->bottom = bottom; + + return bit2; + } else { + ri.Printf( PRINT_ALL, "Non-outline fonts are not supported\n" ); + } + return NULL; +} + +void WriteTGA( char *filename, byte *data, int width, int height ) { + byte *buffer; + int i, c; + + buffer = Z_Malloc( width * height * 4 + 18 ); + Com_Memset( buffer, 0, 18 ); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 32; // pixel size + + // swap rgb to bgr + c = 18 + width * height * 4; + for ( i = 18 ; i < c ; i += 4 ) + { + buffer[i] = data[i - 18 + 2]; // blue + buffer[i + 1] = data[i - 18 + 1]; // green + buffer[i + 2] = data[i - 18 + 0]; // red + buffer[i + 3] = data[i - 18 + 3]; // alpha + } + + ri.FS_WriteFile( filename, buffer, c ); + + //f = fopen (filename, "wb"); + //fwrite (buffer, 1, c, f); + //fclose (f); + + Z_Free( buffer ); +} + +static glyphInfo_t *RE_ConstructGlyphInfo( unsigned char *imageOut, int *xOut, int *yOut, int *maxHeight, FT_Face face, const unsigned char c, qboolean calcHeight ) { + int i; + static glyphInfo_t glyph; + unsigned char *src, *dst; + float scaled_width, scaled_height; + FT_Bitmap *bitmap = NULL; + + Com_Memset( &glyph, 0, sizeof( glyphInfo_t ) ); + // make sure everything is here + if ( face != NULL ) { + FT_Load_Glyph( face, FT_Get_Char_Index( face, c ), FT_LOAD_DEFAULT ); + bitmap = R_RenderGlyph( face->glyph, &glyph ); + if ( bitmap ) { + glyph.xSkip = ( face->glyph->metrics.horiAdvance >> 6 ) + 1; + } else { + return &glyph; + } + + if ( glyph.height > *maxHeight ) { + *maxHeight = glyph.height; + } + + if ( calcHeight ) { + Z_Free( bitmap->buffer ); + Z_Free( bitmap ); + return &glyph; + } + +/* + // need to convert to power of 2 sizes so we do not get + // any scaling from the gl upload + for (scaled_width = 1 ; scaled_width < glyph.pitch ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < glyph.height ; scaled_height<<=1) + ; +*/ + + scaled_width = glyph.pitch; + scaled_height = glyph.height; + + // we need to make sure we fit + if ( *xOut + scaled_width + 1 >= 255 ) { + if ( *yOut + *maxHeight + 1 >= 255 ) { + *yOut = -1; + *xOut = -1; + Z_Free( bitmap->buffer ); + Z_Free( bitmap ); + return &glyph; + } else { + *xOut = 0; + *yOut += *maxHeight + 1; + } + } else if ( *yOut + *maxHeight + 1 >= 255 ) { + *yOut = -1; + *xOut = -1; + Z_Free( bitmap->buffer ); + Z_Free( bitmap ); + return &glyph; + } + + + src = bitmap->buffer; + dst = imageOut + ( *yOut * 256 ) + *xOut; + + if ( bitmap->pixel_mode == ft_pixel_mode_mono ) { + for ( i = 0; i < glyph.height; i++ ) { + int j; + unsigned char *_src = src; + unsigned char *_dst = dst; + unsigned char mask = 0x80; + unsigned char val = *_src; + for ( j = 0; j < glyph.pitch; j++ ) { + if ( mask == 0x80 ) { + val = *_src++; + } + if ( val & mask ) { + *_dst = 0xff; + } + mask >>= 1; + + if ( mask == 0 ) { + mask = 0x80; + } + _dst++; + } + + src += glyph.pitch; + dst += 256; + + } + } else { + for ( i = 0; i < glyph.height; i++ ) { + memcpy( dst, src, glyph.pitch ); + src += glyph.pitch; + dst += 256; + } + } + + // we now have an 8 bit per pixel grey scale bitmap + // that is width wide and pf->ftSize->metrics.y_ppem tall + + glyph.imageHeight = scaled_height; + glyph.imageWidth = scaled_width; + glyph.s = (float)*xOut / 256; + glyph.t = (float)*yOut / 256; + glyph.s2 = glyph.s + (float)scaled_width / 256; + glyph.t2 = glyph.t + (float)scaled_height / 256; + + *xOut += scaled_width + 1; + } + + Z_Free( bitmap->buffer ); + Z_Free( bitmap ); + + return &glyph; +} +#endif + +static int fdOffset; +static byte *fdFile; + +int readInt() { + int i = fdFile[fdOffset] + ( fdFile[fdOffset + 1] << 8 ) + ( fdFile[fdOffset + 2] << 16 ) + ( fdFile[fdOffset + 3] << 24 ); + fdOffset += 4; + return i; +} + +typedef union { + byte fred[4]; + float ffred; +} poor; + +float readFloat() { + poor me; +#ifdef idppc + me.fred[0] = fdFile[fdOffset + 3]; + me.fred[1] = fdFile[fdOffset + 2]; + me.fred[2] = fdFile[fdOffset + 1]; + me.fred[3] = fdFile[fdOffset + 0]; +#else + me.fred[0] = fdFile[fdOffset + 0]; + me.fred[1] = fdFile[fdOffset + 1]; + me.fred[2] = fdFile[fdOffset + 2]; + me.fred[3] = fdFile[fdOffset + 3]; +#endif + fdOffset += 4; + return me.ffred; +} + +void RE_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ) { +#ifdef BUILD_FREETYPE + FT_Face face; + int j, k, xOut, yOut, lastStart, imageNumber; + int scaledSize, newSize, maxHeight, left, satLevels; + unsigned char *out, *imageBuff; + glyphInfo_t *glyph; + image_t *image; + qhandle_t h; + float max; +#endif + void *faceData; + int i, len; + char name[1024]; + float dpi = 72; // + float glyphScale = 72.0f / dpi; // change the scale to be relative to 1 based on 72 dpi ( so dpi of 144 means a scale of .5 ) + + if ( pointSize <= 0 ) { + pointSize = 12; + } + // we also need to adjust the scale based on point size relative to 48 points as the ui scaling is based on a 48 point font + glyphScale *= 48.0f / pointSize; + + // make sure the render thread is stopped + R_SyncRenderThread(); + + if ( registeredFontCount >= MAX_FONTS ) { + ri.Printf( PRINT_ALL, "RE_RegisterFont: Too many fonts registered already.\n" ); + return; + } + + Com_sprintf( name, sizeof( name ), "fonts/fontImage_%i.dat",pointSize ); + for ( i = 0; i < registeredFontCount; i++ ) { + if ( Q_stricmp( name, registeredFont[i].name ) == 0 ) { + memcpy( font, ®isteredFont[i], sizeof( fontInfo_t ) ); + return; + } + } + + len = ri.FS_ReadFile( name, NULL ); + if ( len == sizeof( fontInfo_t ) ) { + ri.FS_ReadFile( name, &faceData ); + fdOffset = 0; + fdFile = faceData; + for ( i = 0; i < GLYPHS_PER_FONT; i++ ) { + font->glyphs[i].height = readInt(); + font->glyphs[i].top = readInt(); + font->glyphs[i].bottom = readInt(); + font->glyphs[i].pitch = readInt(); + font->glyphs[i].xSkip = readInt(); + font->glyphs[i].imageWidth = readInt(); + font->glyphs[i].imageHeight = readInt(); + font->glyphs[i].s = readFloat(); + font->glyphs[i].t = readFloat(); + font->glyphs[i].s2 = readFloat(); + font->glyphs[i].t2 = readFloat(); + font->glyphs[i].glyph = readInt(); + memcpy( font->glyphs[i].shaderName, &fdFile[fdOffset], 32 ); + fdOffset += 32; + } + font->glyphScale = readFloat(); + memcpy( font->name, &fdFile[fdOffset], MAX_QPATH ); + +// memcpy(font, faceData, sizeof(fontInfo_t)); + Q_strncpyz( font->name, name, sizeof( font->name ) ); + for ( i = GLYPH_START; i < GLYPH_END; i++ ) { + font->glyphs[i].glyph = RE_RegisterShaderNoMip( font->glyphs[i].shaderName ); + } + memcpy( ®isteredFont[registeredFontCount++], font, sizeof( fontInfo_t ) ); + return; + } + +#ifndef BUILD_FREETYPE + ri.Printf( PRINT_DEVELOPER, "RE_RegisterFont: FreeType code not available\n" ); // JPW NERVE was PRINT_ALL +#else + if ( ftLibrary == NULL ) { + ri.Printf( PRINT_DEVELOPER, "RE_RegisterFont: FreeType not initialized.\n" ); // JPW NERVE was PRINT_ALL + return; + } + + len = ri.FS_ReadFile( fontName, &faceData ); + if ( len <= 0 ) { + ri.Printf( PRINT_ALL, "RE_RegisterFont: Unable to read font file\n" ); + return; + } + + // allocate on the stack first in case we fail + if ( FT_New_Memory_Face( ftLibrary, faceData, len, 0, &face ) ) { + ri.Printf( PRINT_ALL, "RE_RegisterFont: FreeType2, unable to allocate new face.\n" ); + return; + } + + + if ( FT_Set_Char_Size( face, pointSize << 6, pointSize << 6, dpi, dpi ) ) { + ri.Printf( PRINT_ALL, "RE_RegisterFont: FreeType2, Unable to set face char size.\n" ); + return; + } + + //*font = ®isteredFonts[registeredFontCount++]; + + // make a 256x256 image buffer, once it is full, register it, clean it and keep going + // until all glyphs are rendered + + out = Z_Malloc( 1024 * 1024 ); + if ( out == NULL ) { + ri.Printf( PRINT_ALL, "RE_RegisterFont: Z_Malloc failure during output image creation.\n" ); + return; + } + Com_Memset( out, 0, 1024 * 1024 ); + + maxHeight = 0; + + for ( i = GLYPH_START; i < GLYPH_END; i++ ) { + glyph = RE_ConstructGlyphInfo( out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qtrue ); + } + + xOut = 0; + yOut = 0; + i = GLYPH_START; + lastStart = i; + imageNumber = 0; + + while ( i <= GLYPH_END ) { + + glyph = RE_ConstructGlyphInfo( out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qfalse ); + + if ( xOut == -1 || yOut == -1 || i == GLYPH_END ) { + // ran out of room + // we need to create an image from the bitmap, set all the handles in the glyphs to this point + // + + scaledSize = 256 * 256; + newSize = scaledSize * 4; + imageBuff = Z_Malloc( newSize ); + left = 0; + max = 0; + satLevels = 255; + for ( k = 0; k < ( scaledSize ) ; k++ ) { + if ( max < out[k] ) { + max = out[k]; + } + } + + if ( max > 0 ) { + max = 255 / max; + } + + for ( k = 0; k < ( scaledSize ) ; k++ ) { + imageBuff[left++] = 255; + imageBuff[left++] = 255; + imageBuff[left++] = 255; + + imageBuff[left++] = ( (float)out[k] * max ); + } + + Com_sprintf( name, sizeof( name ), "fonts/fontImage_%i_%i.tga", imageNumber++, pointSize ); + if ( r_saveFontData->integer ) { + WriteTGA( name, imageBuff, 256, 256 ); + } + + //Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i", imageNumber++, pointSize); + image = R_CreateImage( name, imageBuff, 256, 256, qfalse, qfalse, GL_CLAMP ); + h = RE_RegisterShaderFromImage( name, LIGHTMAP_2D, image, qfalse ); + for ( j = lastStart; j < i; j++ ) { + font->glyphs[j].glyph = h; + Q_strncpyz( font->glyphs[j].shaderName, name, sizeof( font->glyphs[j].shaderName ) ); + } + lastStart = i; + Com_Memset( out, 0, 1024 * 1024 ); + xOut = 0; + yOut = 0; + Z_Free( imageBuff ); + i++; + } else { + memcpy( &font->glyphs[i], glyph, sizeof( glyphInfo_t ) ); + i++; + } + } + + registeredFont[registeredFontCount].glyphScale = glyphScale; + font->glyphScale = glyphScale; + memcpy( ®isteredFont[registeredFontCount++], font, sizeof( fontInfo_t ) ); + + if ( r_saveFontData->integer ) { + ri.FS_WriteFile( va( "fonts/fontImage_%i.dat", pointSize ), font, sizeof( fontInfo_t ) ); + } + + Z_Free( out ); + + ri.FS_FreeFile( faceData ); +#endif +} + + + +void R_InitFreeType() { +#ifdef BUILD_FREETYPE + if ( FT_Init_FreeType( &ftLibrary ) ) { + ri.Printf( PRINT_ALL, "R_InitFreeType: Unable to initialize FreeType.\n" ); + } +#endif + registeredFontCount = 0; +} + + +void R_DoneFreeType() { +#ifdef BUILD_FREETYPE + if ( ftLibrary ) { + FT_Done_FreeType( ftLibrary ); + ftLibrary = NULL; + } +#endif + registeredFontCount = 0; +} + diff --git a/src/renderer/tr_image.c b/src/renderer/tr_image.c new file mode 100644 index 0000000..18eb1b4 --- /dev/null +++ b/src/renderer/tr_image.c @@ -0,0 +1,3659 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: tr_image.c + * + * desc: + * +*/ + +#include "tr_local.h" + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +#define JPEG_INTERNALS +#include "../jpeg-6/jpeglib.h" + + +static void LoadBMP( const char *name, byte **pic, int *width, int *height ); +static void LoadTGA( const char *name, byte **pic, int *width, int *height ); +static void LoadJPG( const char *name, byte **pic, int *width, int *height ); + +static byte s_intensitytable[256]; +static unsigned char s_gammatable[256]; + +int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; +int gl_filter_max = GL_LINEAR; + +#define FILE_HASH_SIZE 4096 +static image_t* hashTable[FILE_HASH_SIZE]; + +// Ridah, in order to prevent zone fragmentation, all images will +// be read into this buffer. In order to keep things as fast as possible, +// we'll give it a starting value, which will account for the majority of +// images, but allow it to grow if the buffer isn't big enough +#define R_IMAGE_BUFFER_SIZE ( 512 * 512 * 4 ) // 512 x 512 x 32bit + +typedef enum { + BUFFER_IMAGE, + BUFFER_SCALED, + BUFFER_RESAMPLED, + BUFFER_MAX_TYPES +} bufferMemType_t; + +int imageBufferSize[BUFFER_MAX_TYPES] = {0,0,0}; +void *imageBufferPtr[BUFFER_MAX_TYPES] = {NULL,NULL,NULL}; + +void *R_GetImageBuffer( int size, bufferMemType_t bufferType ) { + if ( imageBufferSize[bufferType] < R_IMAGE_BUFFER_SIZE && size <= imageBufferSize[bufferType] ) { + imageBufferSize[bufferType] = R_IMAGE_BUFFER_SIZE; + imageBufferPtr[bufferType] = malloc( imageBufferSize[bufferType] ); +//DAJ TEST imageBufferPtr[bufferType] = Z_Malloc( imageBufferSize[bufferType] ); + } + if ( size > imageBufferSize[bufferType] ) { // it needs to grow + if ( imageBufferPtr[bufferType] ) { + free( imageBufferPtr[bufferType] ); + } +//DAJ TEST Z_Free( imageBufferPtr[bufferType] ); + imageBufferSize[bufferType] = size; + imageBufferPtr[bufferType] = malloc( imageBufferSize[bufferType] ); +//DAJ TEST imageBufferPtr[bufferType] = Z_Malloc( imageBufferSize[bufferType] ); + } + + return imageBufferPtr[bufferType]; +} + +void R_FreeImageBuffer( void ) { + int bufferType; + for ( bufferType = 0; bufferType < BUFFER_MAX_TYPES; bufferType++ ) { + if ( !imageBufferPtr[bufferType] ) { + return; + } + free( imageBufferPtr[bufferType] ); +//DAJ TEST Z_Free( imageBufferPtr[bufferType] ); + imageBufferSize[bufferType] = 0; + imageBufferPtr[bufferType] = NULL; + } +} + +/* +** R_GammaCorrect +*/ +void R_GammaCorrect( byte *buffer, int bufSize ) { + int i; + + for ( i = 0; i < bufSize; i++ ) { + buffer[i] = s_gammatable[buffer[i]]; + } +} + +typedef struct { + char *name; + int minimize, maximize; +} textureMode_t; + +textureMode_t modes[] = { + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} +}; + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter == '\\' ) { + letter = '/'; // damn path names + } + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( FILE_HASH_SIZE - 1 ); + return hash; +} + +/* +=============== +GL_TextureMode +=============== +*/ +void GL_TextureMode( const char *string ) { + int i; + image_t *glt; + + for ( i = 0 ; i < 6 ; i++ ) { + if ( !Q_stricmp( modes[i].name, string ) ) { + break; + } + } + + // hack to prevent trilinear from being set on voodoo, + // because their driver freaks... + if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D ) { + ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" ); + i = 3; + } + + + if ( i == 6 ) { + ri.Printf( PRINT_ALL, "bad filter name\n" ); + return; + } + + gl_filter_min = modes[i].minimize; + gl_filter_max = modes[i].maximize; + + // change all the existing mipmap texture objects + for ( i = 0 ; i < tr.numImages ; i++ ) { + glt = tr.images[ i ]; + if ( glt->mipmap ) { + GL_Bind( glt ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max ); + } + } +} + +/* +=============== +R_SumOfUsedImages +=============== +*/ +int R_SumOfUsedImages( void ) { + int total; + int i, fc = ( tr.frameCount - 1 ); + + total = 0; + for ( i = 0; i < tr.numImages; i++ ) { + if ( tr.images[i]->frameUsed == fc ) { + total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight; + } + } + + return total; +} + +/* +=============== +R_ImageList_f +=============== +*/ +void R_ImageList_f( void ) { + int i; + image_t *image; + int texels; + const char *yesno[] = { + "no ", "yes" + }; + + ri.Printf( PRINT_ALL, "\n -w-- -h-- -mm- -TMU- -if-- wrap --name-------\n" ); + texels = 0; + + for ( i = 0 ; i < tr.numImages ; i++ ) { + image = tr.images[ i ]; + + texels += image->uploadWidth * image->uploadHeight; + ri.Printf( PRINT_ALL, "%4i: %4i %4i %s %d ", + i, image->uploadWidth, image->uploadHeight, yesno[image->mipmap], image->TMU ); + switch ( image->internalFormat ) { + case 1: + ri.Printf( PRINT_ALL, "I " ); + break; + case 2: + ri.Printf( PRINT_ALL, "IA " ); + break; + case 3: + ri.Printf( PRINT_ALL, "RGB " ); + break; + case 4: + ri.Printf( PRINT_ALL, "RGBA " ); + break; + case GL_RGBA8: + ri.Printf( PRINT_ALL, "RGBA8" ); + break; + case GL_RGB8: + ri.Printf( PRINT_ALL, "RGB8" ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + ri.Printf( PRINT_ALL, "DXT5 " ); + break; + case GL_RGB4_S3TC: + ri.Printf( PRINT_ALL, "S3TC4" ); + break; + case GL_RGBA4: + ri.Printf( PRINT_ALL, "RGBA4" ); + break; + case GL_RGB5: + ri.Printf( PRINT_ALL, "RGB5 " ); + break; + default: + ri.Printf( PRINT_ALL, "???? " ); + } + + switch ( image->wrapClampMode ) { + case GL_REPEAT: + ri.Printf( PRINT_ALL, "rept " ); + break; + case GL_CLAMP: + ri.Printf( PRINT_ALL, "clmp " ); + break; + default: + ri.Printf( PRINT_ALL, "%4i ", image->wrapClampMode ); + break; + } + + ri.Printf( PRINT_ALL, " %s\n", image->imgName ); + } + ri.Printf( PRINT_ALL, " ---------\n" ); + ri.Printf( PRINT_ALL, " %i total texels (not including mipmaps)\n", texels ); + ri.Printf( PRINT_ALL, " %i total images\n\n", tr.numImages ); +} + +//======================================================================= + +/* +================ +ResampleTexture + +Used to resample images in a more general than quartering fashion. + +This will only be filtered properly if the resampled size +is greater than half the original size. + +If a larger shrinking is needed, use the mipmap function +before or after. +================ +*/ +static void ResampleTexture( unsigned *in, int inwidth, int inheight, unsigned *out, + int outwidth, int outheight ) { + int i, j; + unsigned *inrow, *inrow2; + unsigned frac, fracstep; + unsigned p1[2048], p2[2048]; + byte *pix1, *pix2, *pix3, *pix4; + + if ( outwidth > 2048 ) { + ri.Error( ERR_DROP, "ResampleTexture: max width" ); + } + + fracstep = inwidth * 0x10000 / outwidth; + + frac = fracstep >> 2; + for ( i = 0 ; i < outwidth ; i++ ) { + p1[i] = 4 * ( frac >> 16 ); + frac += fracstep; + } + frac = 3 * ( fracstep >> 2 ); + for ( i = 0 ; i < outwidth ; i++ ) { + p2[i] = 4 * ( frac >> 16 ); + frac += fracstep; + } + + for ( i = 0 ; i < outheight ; i++, out += outwidth ) { + inrow = in + inwidth * (int)( ( i + 0.25 ) * inheight / outheight ); + inrow2 = in + inwidth * (int)( ( i + 0.75 ) * inheight / outheight ); + frac = fracstep >> 1; + for ( j = 0 ; j < outwidth ; j++ ) { + pix1 = (byte *)inrow + p1[j]; + pix2 = (byte *)inrow + p2[j]; + pix3 = (byte *)inrow2 + p1[j]; + pix4 = (byte *)inrow2 + p2[j]; + ( ( byte * )( out + j ) )[0] = ( pix1[0] + pix2[0] + pix3[0] + pix4[0] ) >> 2; + ( ( byte * )( out + j ) )[1] = ( pix1[1] + pix2[1] + pix3[1] + pix4[1] ) >> 2; + ( ( byte * )( out + j ) )[2] = ( pix1[2] + pix2[2] + pix3[2] + pix4[2] ) >> 2; + ( ( byte * )( out + j ) )[3] = ( pix1[3] + pix2[3] + pix3[3] + pix4[3] ) >> 2; + } + } +} + +/* +================ +R_LightScaleTexture + +Scale up the pixel values in a texture to increase the +lighting range +================ +*/ +void R_LightScaleTexture( unsigned *in, int inwidth, int inheight, qboolean only_gamma ) { + if ( only_gamma ) { + if ( !glConfig.deviceSupportsGamma ) { + int i, c; + byte *p; + + p = (byte *)in; + + c = inwidth * inheight; + for ( i = 0 ; i < c ; i++, p += 4 ) + { + p[0] = s_gammatable[p[0]]; + p[1] = s_gammatable[p[1]]; + p[2] = s_gammatable[p[2]]; + } + } + } else + { + int i, c; + byte *p; + + p = (byte *)in; + + c = inwidth * inheight; + + if ( glConfig.deviceSupportsGamma ) { + for ( i = 0 ; i < c ; i++, p += 4 ) + { + p[0] = s_intensitytable[p[0]]; + p[1] = s_intensitytable[p[1]]; + p[2] = s_intensitytable[p[2]]; + } + } else + { + for ( i = 0 ; i < c ; i++, p += 4 ) + { + p[0] = s_gammatable[s_intensitytable[p[0]]]; + p[1] = s_gammatable[s_intensitytable[p[1]]]; + p[2] = s_gammatable[s_intensitytable[p[2]]]; + } + } + } +} + + +/* +================ +R_MipMap2 + +Operates in place, quartering the size of the texture +Proper linear filter +================ +*/ +static void R_MipMap2( unsigned *in, int inWidth, int inHeight ) { + int i, j, k; + byte *outpix; + int inWidthMask, inHeightMask; + int total; + int outWidth, outHeight; + unsigned *temp; + + outWidth = inWidth >> 1; + outHeight = inHeight >> 1; + temp = ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 ); + + inWidthMask = inWidth - 1; + inHeightMask = inHeight - 1; + + for ( i = 0 ; i < outHeight ; i++ ) { + for ( j = 0 ; j < outWidth ; j++ ) { + outpix = ( byte * )( temp + i * outWidth + j ); + for ( k = 0 ; k < 4 ; k++ ) { + total = + 1 * ( (byte *)&in[ ( ( i * 2 - 1 ) & inHeightMask ) * inWidth + ( ( j * 2 - 1 ) & inWidthMask ) ] )[k] + + 2 * ( (byte *)&in[ ( ( i * 2 - 1 ) & inHeightMask ) * inWidth + ( ( j * 2 ) & inWidthMask ) ] )[k] + + 2 * ( (byte *)&in[ ( ( i * 2 - 1 ) & inHeightMask ) * inWidth + ( ( j * 2 + 1 ) & inWidthMask ) ] )[k] + + 1 * ( (byte *)&in[ ( ( i * 2 - 1 ) & inHeightMask ) * inWidth + ( ( j * 2 + 2 ) & inWidthMask ) ] )[k] + + + 2 * ( (byte *)&in[ ( ( i * 2 ) & inHeightMask ) * inWidth + ( ( j * 2 - 1 ) & inWidthMask ) ] )[k] + + 4 * ( (byte *)&in[ ( ( i * 2 ) & inHeightMask ) * inWidth + ( ( j * 2 ) & inWidthMask ) ] )[k] + + 4 * ( (byte *)&in[ ( ( i * 2 ) & inHeightMask ) * inWidth + ( ( j * 2 + 1 ) & inWidthMask ) ] )[k] + + 2 * ( (byte *)&in[ ( ( i * 2 ) & inHeightMask ) * inWidth + ( ( j * 2 + 2 ) & inWidthMask ) ] )[k] + + + 2 * ( (byte *)&in[ ( ( i * 2 + 1 ) & inHeightMask ) * inWidth + ( ( j * 2 - 1 ) & inWidthMask ) ] )[k] + + 4 * ( (byte *)&in[ ( ( i * 2 + 1 ) & inHeightMask ) * inWidth + ( ( j * 2 ) & inWidthMask ) ] )[k] + + 4 * ( (byte *)&in[ ( ( i * 2 + 1 ) & inHeightMask ) * inWidth + ( ( j * 2 + 1 ) & inWidthMask ) ] )[k] + + 2 * ( (byte *)&in[ ( ( i * 2 + 1 ) & inHeightMask ) * inWidth + ( ( j * 2 + 2 ) & inWidthMask ) ] )[k] + + + 1 * ( (byte *)&in[ ( ( i * 2 + 2 ) & inHeightMask ) * inWidth + ( ( j * 2 - 1 ) & inWidthMask ) ] )[k] + + 2 * ( (byte *)&in[ ( ( i * 2 + 2 ) & inHeightMask ) * inWidth + ( ( j * 2 ) & inWidthMask ) ] )[k] + + 2 * ( (byte *)&in[ ( ( i * 2 + 2 ) & inHeightMask ) * inWidth + ( ( j * 2 + 1 ) & inWidthMask ) ] )[k] + + 1 * ( (byte *)&in[ ( ( i * 2 + 2 ) & inHeightMask ) * inWidth + ( ( j * 2 + 2 ) & inWidthMask ) ] )[k]; + outpix[k] = total / 36; + } + } + } + + memcpy( in, temp, outWidth * outHeight * 4 ); + ri.Hunk_FreeTempMemory( temp ); +} + +/* +================ +R_MipMap + +Operates in place, quartering the size of the texture +================ +*/ +static void R_MipMap( byte *in, int width, int height ) { + int i, j; + byte *out; + int row; + + if ( !r_simpleMipMaps->integer ) { + R_MipMap2( (unsigned *)in, width, height ); + return; + } + + if ( width == 1 && height == 1 ) { + return; + } + + row = width * 4; + out = in; + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for ( i = 0 ; i < width ; 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; + } + + for ( i = 0 ; i < height ; i++, in += row ) { + for ( j = 0 ; j < width ; j++, out += 4, in += 8 ) { + out[0] = ( in[0] + in[4] + in[row + 0] + in[row + 4] ) >> 2; + out[1] = ( in[1] + in[5] + in[row + 1] + in[row + 5] ) >> 2; + out[2] = ( in[2] + in[6] + in[row + 2] + in[row + 6] ) >> 2; + out[3] = ( in[3] + in[7] + in[row + 3] + in[row + 7] ) >> 2; + } + } +} + +/* +================ +R_MipMap + +Operates in place, quartering the size of the texture +================ +*/ +static float R_RMSE( byte *in, int width, int height ) { + int i, j; + float out, rmse, rtemp; + int row; + + rmse = 0.0f; + + if ( width <= 32 || height <= 32 ) { + return 9999.0f; + } + + row = width * 4; + + width >>= 1; + height >>= 1; + + for ( i = 0 ; i < height ; i++, in += row ) { + for ( j = 0 ; j < width ; j++, out += 4, in += 8 ) { + out = ( in[0] + in[4] + in[row + 0] + in[row + 4] ) >> 2; + rtemp = ( ( fabs( out - in[0] ) + fabs( out - in[4] ) + fabs( out - in[row + 0] ) + fabs( out - in[row + 4] ) ) ); + rtemp = rtemp * rtemp; + rmse += rtemp; + out = ( in[1] + in[5] + in[row + 1] + in[row + 5] ) >> 2; + rtemp = ( ( fabs( out - in[1] ) + fabs( out - in[5] ) + fabs( out - in[row + 1] ) + fabs( out - in[row + 5] ) ) ); + rtemp = rtemp * rtemp; + rmse += rtemp; + out = ( in[2] + in[6] + in[row + 2] + in[row + 6] ) >> 2; + rtemp = ( ( fabs( out - in[2] ) + fabs( out - in[6] ) + fabs( out - in[row + 2] ) + fabs( out - in[row + 6] ) ) ); + rtemp = rtemp * rtemp; + rmse += rtemp; + out = ( in[3] + in[7] + in[row + 3] + in[row + 7] ) >> 2; + rtemp = ( ( fabs( out - in[3] ) + fabs( out - in[7] ) + fabs( out - in[row + 3] ) + fabs( out - in[row + 7] ) ) ); + rtemp = rtemp * rtemp; + rmse += rtemp; + } + } + rmse = sqrt( rmse / ( height * width * 4 ) ); + return rmse; +} + + +/* +================== +R_BlendOverTexture + +Apply a color blend over a set of pixels +================== +*/ +static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) { + int i; + int inverseAlpha; + int premult[3]; + + inverseAlpha = 255 - blend[3]; + premult[0] = blend[0] * blend[3]; + premult[1] = blend[1] * blend[3]; + premult[2] = blend[2] * blend[3]; + + for ( i = 0 ; i < pixelCount ; i++, data += 4 ) { + data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + +byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + + +/* +=============== +Upload32 + +=============== +*/ +static void Upload32( unsigned *data, + int width, int height, + qboolean mipmap, + qboolean picmip, + qboolean lightMap, + int *format, + int *pUploadWidth, int *pUploadHeight, + qboolean noCompress ) { + int samples; + int scaled_width, scaled_height; + unsigned *scaledBuffer = NULL; + unsigned *resampledBuffer = NULL; + int i, c; + byte *scan; + GLenum internalFormat = GL_RGB; + float rMax = 0, gMax = 0, bMax = 0; + static int rmse_saved = 0; + + // do the root mean square error stuff first + if ( r_rmse->value ) { + while ( R_RMSE( (byte *)data, width, height ) < r_rmse->value ) { + rmse_saved += ( height * width * 4 ) - ( ( width >> 1 ) * ( height >> 1 ) * 4 ); + resampledBuffer = R_GetImageBuffer( ( width >> 1 ) * ( height >> 1 ) * 4, BUFFER_RESAMPLED ); + ResampleTexture( data, width, height, resampledBuffer, width >> 1, height >> 1 ); + data = resampledBuffer; + width = width >> 1; + height = height >> 1; + ri.Printf( PRINT_ALL, "r_rmse of %f has saved %dkb\n", r_rmse->value, ( rmse_saved / 1024 ) ); + } + } + // + // convert to exact power of 2 sizes + // + for ( scaled_width = 1 ; scaled_width < width ; scaled_width <<= 1 ) + ; + for ( scaled_height = 1 ; scaled_height < height ; scaled_height <<= 1 ) + ; + if ( r_roundImagesDown->integer && scaled_width > width ) { + scaled_width >>= 1; + } + if ( r_roundImagesDown->integer && scaled_height > height ) { + scaled_height >>= 1; + } + + if ( scaled_width != width || scaled_height != height ) { + //resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 ); + resampledBuffer = R_GetImageBuffer( scaled_width * scaled_height * 4, BUFFER_RESAMPLED ); + ResampleTexture( data, width, height, resampledBuffer, scaled_width, scaled_height ); + data = resampledBuffer; + width = scaled_width; + height = scaled_height; + } + + // + // perform optional picmip operation + // + if ( picmip ) { + scaled_width >>= r_picmip->integer; + scaled_height >>= r_picmip->integer; + } + + // + // clamp to minimum size + // + if ( scaled_width < 1 ) { + scaled_width = 1; + } + if ( scaled_height < 1 ) { + scaled_height = 1; + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( scaled_width > glConfig.maxTextureSize + || scaled_height > glConfig.maxTextureSize ) { + scaled_width >>= 1; + scaled_height >>= 1; + } + + //scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height ); + scaledBuffer = R_GetImageBuffer( sizeof( unsigned ) * scaled_width * scaled_height, BUFFER_SCALED ); + + // + // scan the texture for each channel's max values + // and verify if the alpha channel is being used or not + // + c = width * height; + scan = ( (byte *)data ); + samples = 3; + if ( !lightMap ) { + for ( i = 0; i < c; i++ ) + { + if ( scan[i * 4 + 0] > rMax ) { + rMax = scan[i * 4 + 0]; + } + if ( scan[i * 4 + 1] > gMax ) { + gMax = scan[i * 4 + 1]; + } + if ( scan[i * 4 + 2] > bMax ) { + bMax = scan[i * 4 + 2]; + } + if ( scan[i * 4 + 3] != 255 ) { + samples = 4; + break; + } + } + // select proper internal format + if ( samples == 3 ) { + if ( !noCompress && glConfig.textureCompression == TC_EXT_COMP_S3TC ) { + // TODO: which format is best for which textures? + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } else if ( !noCompress && glConfig.textureCompression == TC_S3TC ) { + internalFormat = GL_RGB4_S3TC; + } else if ( r_texturebits->integer == 16 ) { + internalFormat = GL_RGB5; + } else if ( r_texturebits->integer == 32 ) { + internalFormat = GL_RGB8; + } else + { + internalFormat = 3; + } + } else if ( samples == 4 ) { + if ( !noCompress && glConfig.textureCompression == TC_EXT_COMP_S3TC ) { + // TODO: which format is best for which textures? + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } else if ( r_texturebits->integer == 16 ) { + internalFormat = GL_RGBA4; + } else if ( r_texturebits->integer == 32 ) { + internalFormat = GL_RGBA8; + } else + { + internalFormat = 4; + } + } + } else { + internalFormat = 3; + } + // copy or resample data as appropriate for first MIP level + if ( ( scaled_width == width ) && + ( scaled_height == height ) ) { + if ( !mipmap ) { + qglTexImage2D( GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + *format = internalFormat; + + goto done; + } + memcpy( scaledBuffer, data, width * height * 4 ); + } else + { + // use the normal mip-mapping function to go down from here + while ( width > scaled_width || height > scaled_height ) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if ( width < 1 ) { + width = 1; + } + if ( height < 1 ) { + height = 1; + } + } + memcpy( scaledBuffer, data, width * height * 4 ); + } + + R_LightScaleTexture( scaledBuffer, scaled_width, scaled_height, !mipmap ); + + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + *format = internalFormat; + + qglTexImage2D( GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + + if ( mipmap ) { + int miplevel; + + miplevel = 0; + while ( scaled_width > 1 || scaled_height > 1 ) + { + R_MipMap( (byte *)scaledBuffer, scaled_width, scaled_height ); + scaled_width >>= 1; + scaled_height >>= 1; + if ( scaled_width < 1 ) { + scaled_width = 1; + } + if ( scaled_height < 1 ) { + scaled_height = 1; + } + miplevel++; + + if ( r_colorMipLevels->integer ) { + R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] ); + } + + qglTexImage2D( GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + } + } +done: + + if ( mipmap ) { + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max ); + } else + { + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); + + //if ( scaledBuffer != 0 ) + // ri.Hunk_FreeTempMemory( scaledBuffer ); + //if ( resampledBuffer != 0 ) + // ri.Hunk_FreeTempMemory( resampledBuffer ); +} + +/* +================ +R_CreateImage + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, + qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ) { + image_t *image; + qboolean isLightmap = qfalse; + long hash; + qboolean noCompress = qfalse; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Error( ERR_DROP, "R_CreateImage: \"%s\" is too long\n", name ); + } + if ( !strncmp( name, "*lightmap", 9 ) ) { + isLightmap = qtrue; + noCompress = qtrue; + } + if ( !noCompress && strstr( name, "skies" ) ) { + noCompress = qtrue; + } + if ( !noCompress && strstr( name, "weapons" ) ) { // don't compress view weapon skins + noCompress = qtrue; + } + // RF, if the shader hasn't specifically asked for it, don't allow compression + if ( r_ext_compressed_textures->integer == 2 && ( tr.allowCompress != qtrue ) ) { + noCompress = qtrue; + } else if ( r_ext_compressed_textures->integer == 1 && ( tr.allowCompress < 0 ) ) { + noCompress = qtrue; + } + + if ( tr.numImages == MAX_DRAWIMAGES ) { + ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit\n" ); + } + + // Ridah + image = tr.images[tr.numImages] = R_CacheImageAlloc( sizeof( image_t ) ); + + image->texnum = 1024 + tr.numImages; + + // Ridah + if ( r_cacheShaders->integer ) { + R_FindFreeTexnum( image ); + } + // done. + + tr.numImages++; + + image->mipmap = mipmap; + image->allowPicmip = allowPicmip; + + strcpy( image->imgName, name ); + + image->width = width; + image->height = height; + image->wrapClampMode = glWrapClampMode; + + // lightmaps are always allocated on TMU 1 + if ( qglActiveTextureARB && isLightmap ) { + image->TMU = 1; + } else { + image->TMU = 0; + } + + if ( qglActiveTextureARB ) { + GL_SelectTexture( image->TMU ); + } + + GL_Bind( image ); + + Upload32( (unsigned *)pic, + image->width, image->height, + image->mipmap, + allowPicmip, + isLightmap, + &image->internalFormat, + &image->uploadWidth, + &image->uploadHeight, + noCompress ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode ); + + qglBindTexture( GL_TEXTURE_2D, 0 ); + + if ( image->TMU == 1 ) { + GL_SelectTexture( 0 ); + } + + hash = generateHashValue( name ); + image->next = hashTable[hash]; + hashTable[hash] = image; + + // Ridah + image->hash = hash; + + return image; +} + + +/* +========================================================= + +BMP LOADING + +========================================================= +*/ +typedef struct +{ + char id[2]; + unsigned long fileSize; + unsigned long reserved0; + unsigned long bitmapDataOffset; + unsigned long bitmapHeaderSize; + unsigned long width; + unsigned long height; + unsigned short planes; + unsigned short bitsPerPixel; + unsigned long compression; + unsigned long bitmapDataSize; + unsigned long hRes; + unsigned long vRes; + unsigned long colors; + unsigned long importantColors; + unsigned char palette[256][4]; +} BMPHeader_t; + +static void LoadBMP( const char *name, byte **pic, int *width, int *height ) { + int columns, rows, numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *buffer; + int length; + BMPHeader_t bmpHeader; + byte *bmpRGBA; + + *pic = NULL; + + // + // load the file + // + length = ri.FS_ReadFile( ( char * ) name, (void **)&buffer ); + if ( !buffer ) { + return; + } + + buf_p = buffer; + + bmpHeader.id[0] = *buf_p++; + bmpHeader.id[1] = *buf_p++; + bmpHeader.fileSize = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.reserved0 = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataOffset = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapHeaderSize = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.width = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.height = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.planes = LittleShort( *( short * ) buf_p ); + buf_p += 2; + bmpHeader.bitsPerPixel = LittleShort( *( short * ) buf_p ); + buf_p += 2; + bmpHeader.compression = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataSize = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.hRes = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.vRes = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.colors = LittleLong( *( long * ) buf_p ); + buf_p += 4; + bmpHeader.importantColors = LittleLong( *( long * ) buf_p ); + buf_p += 4; + + memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); + + if ( bmpHeader.bitsPerPixel == 8 ) { + buf_p += 1024; + } + + if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) { + ri.Error( ERR_DROP, "LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + } + if ( bmpHeader.fileSize != length ) { + ri.Error( ERR_DROP, "LoadBMP: header size does not match file size (%d vs. %d) (%s)\n", bmpHeader.fileSize, length, name ); + } + if ( bmpHeader.compression != 0 ) { + ri.Error( ERR_DROP, "LoadBMP: only uncompressed BMP files supported (%s)\n", name ); + } + if ( bmpHeader.bitsPerPixel < 8 ) { + ri.Error( ERR_DROP, "LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name ); + } + + columns = bmpHeader.width; + rows = bmpHeader.height; + if ( rows < 0 ) { + rows = -rows; + } + numPixels = columns * rows; + + if ( width ) { + *width = columns; + } + if ( height ) { + *height = rows; + } + + bmpRGBA = R_GetImageBuffer( numPixels * 4, BUFFER_IMAGE ); + + *pic = bmpRGBA; + + + for ( row = rows - 1; row >= 0; row-- ) + { + pixbuf = bmpRGBA + row * columns * 4; + + for ( column = 0; column < columns; column++ ) + { + unsigned char red, green, blue, alpha; + int palIndex; + unsigned short shortPixel; + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + palIndex = *buf_p++; + *pixbuf++ = bmpHeader.palette[palIndex][2]; + *pixbuf++ = bmpHeader.palette[palIndex][1]; + *pixbuf++ = bmpHeader.palette[palIndex][0]; + *pixbuf++ = 0xff; + break; + case 16: + shortPixel = *( unsigned short * ) pixbuf; + pixbuf += 2; + *pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7; + *pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2; + *pixbuf++ = ( shortPixel & ( 31 ) ) << 3; + *pixbuf++ = 0xff; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + default: + ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel, name ); + break; + } + } + } + + ri.FS_FreeFile( buffer ); + +} + + +/* +================================================================= + +PCX LOADING + +================================================================= +*/ + + +/* +============== +LoadPCX +============== +*/ +static void LoadPCX( const char *filename, byte **pic, byte **palette, int *width, int *height ) { + byte *raw; + pcx_t *pcx; + int x, y; + int len; + int dataByte, runLength; + byte *out, *pix; + int xmax, ymax; + + *pic = NULL; + *palette = NULL; + + // + // load the file + // + len = ri.FS_ReadFile( ( char * ) filename, (void **)&raw ); + if ( !raw ) { + return; + } + + // + // parse the PCX file + // + pcx = (pcx_t *)raw; + raw = &pcx->data; + + xmax = LittleShort( pcx->xmax ); + ymax = LittleShort( pcx->ymax ); + + if ( pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->bits_per_pixel != 8 + || xmax >= 1024 + || ymax >= 1024 ) { + ri.Printf( PRINT_ALL, "Bad pcx file %s (%i x %i) (%i x %i)\n", filename, xmax + 1, ymax + 1, pcx->xmax, pcx->ymax ); + return; + } + + out = R_GetImageBuffer( ( ymax + 1 ) * ( xmax + 1 ), BUFFER_IMAGE ); + + *pic = out; + + pix = out; + + if ( palette ) { + *palette = ri.Z_Malloc( 768 ); + memcpy( *palette, (byte *)pcx + len - 768, 768 ); + } + + if ( width ) { + *width = xmax + 1; + } + if ( height ) { + *height = ymax + 1; + } +// FIXME: use bytes_per_line here? + + for ( y = 0 ; y <= ymax ; y++, pix += xmax + 1 ) + { + for ( x = 0 ; x <= xmax ; ) + { + dataByte = *raw++; + + if ( ( dataByte & 0xC0 ) == 0xC0 ) { + runLength = dataByte & 0x3F; + dataByte = *raw++; + } else { + runLength = 1; + } + + while ( runLength-- > 0 ) + pix[x++] = dataByte; + } + + } + + if ( raw - (byte *)pcx > len ) { + ri.Printf( PRINT_DEVELOPER, "PCX file %s was malformed", filename ); + ri.Free( *pic ); + *pic = NULL; + } + + ri.FS_FreeFile( pcx ); +} + + +/* +============== +LoadPCX32 +============== +*/ +static void LoadPCX32( const char *filename, byte **pic, int *width, int *height ) { + byte *palette; + byte *pic8; + int i, c, p; + byte *pic32; + + LoadPCX( filename, &pic8, &palette, width, height ); + if ( !pic8 ) { + *pic = NULL; + return; + } + + c = ( *width ) * ( *height ); + pic32 = *pic = R_GetImageBuffer( 4 * c, BUFFER_IMAGE ); + for ( i = 0 ; i < c ; i++ ) { + p = pic8[i]; + pic32[0] = palette[p * 3]; + pic32[1] = palette[p * 3 + 1]; + pic32[2] = palette[p * 3 + 2]; + pic32[3] = 255; + pic32 += 4; + } + + ri.Free( pic8 ); + ri.Free( palette ); +} + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ + +/* +============= +LoadTGA +============= +*/ +void LoadTGA( const char *name, byte **pic, int *width, int *height ) { + int columns, rows, numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *buffer; + TargaHeader targa_header; + byte *targa_rgba; + + *pic = NULL; + + // + // load the file + // + ri.FS_ReadFile( ( char * ) name, (void **)&buffer ); + if ( !buffer ) { + return; + } + + buf_p = buffer; + + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = LittleShort( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_length = LittleShort( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_size = *buf_p++; + targa_header.x_origin = LittleShort( *(short *)buf_p ); + buf_p += 2; + targa_header.y_origin = LittleShort( *(short *)buf_p ); + buf_p += 2; + targa_header.width = LittleShort( *(short *)buf_p ); + buf_p += 2; + targa_header.height = LittleShort( *(short *)buf_p ); + buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + + if ( targa_header.image_type != 2 + && targa_header.image_type != 10 + && targa_header.image_type != 3 ) { + ri.Error( ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n" ); + } + + if ( targa_header.colormap_type != 0 ) { + ri.Error( ERR_DROP, "LoadTGA: colormaps not supported\n" ); + } + + if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) { + ri.Error( ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n" ); + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + + if ( width ) { + *width = columns; + } + if ( height ) { + *height = rows; + } + + targa_rgba = R_GetImageBuffer( numPixels * 4, BUFFER_IMAGE ); + *pic = targa_rgba; + + if ( targa_header.id_length != 0 ) { + buf_p += targa_header.id_length; // skip TARGA image comment + + } + if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) { + // Uncompressed RGB or gray scale image + for ( row = rows - 1; row >= 0; row-- ) + { + pixbuf = targa_rgba + row * columns * 4; + for ( column = 0; column < columns; column++ ) + { + unsigned char red,green,blue,alphabyte; + switch ( targa_header.pixel_size ) + { + + case 8: + blue = *buf_p++; + green = blue; + red = blue; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name ); + break; + } + } + } + } else if ( targa_header.image_type == 10 ) { // Runlength encoded RGB images + unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j; + + red = 0; + green = 0; + blue = 0; + alphabyte = 0xff; + + for ( row = rows - 1; row >= 0; row-- ) { + pixbuf = targa_rgba + row * columns * 4; + for ( column = 0; column < columns; ) { + packetHeader = *buf_p++; + packetSize = 1 + ( packetHeader & 0x7f ); + if ( packetHeader & 0x80 ) { // run-length packet + switch ( targa_header.pixel_size ) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + break; + default: + ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name ); + break; + } + + for ( j = 0; j < packetSize; j++ ) { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + column++; + if ( column == columns ) { // run spans across rows + column = 0; + if ( row > 0 ) { + row--; + } else { + goto breakOut; + } + pixbuf = targa_rgba + row * columns * 4; + } + } + } else { // non run-length packet + for ( j = 0; j < packetSize; j++ ) { + switch ( targa_header.pixel_size ) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name ); + break; + } + column++; + if ( column == columns ) { // pixel packet run spans across rows + column = 0; + if ( row > 0 ) { + row--; + } else { + goto breakOut; + } + pixbuf = targa_rgba + row * columns * 4; + } + } + } + } +breakOut:; + } + } + + ri.FS_FreeFile( buffer ); +} + +static void LoadJPG( const char *filename, unsigned char **pic, int *width, int *height ) { + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + unsigned char *out; + byte *fbuffer; + byte *bbuf; + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + ri.FS_ReadFile( ( char * ) filename, (void **)&fbuffer ); + if ( !fbuffer ) { + return; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error( &jerr ); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress( &cinfo ); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src( &cinfo, fbuffer ); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header( &cinfo, TRUE ); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress( &cinfo ); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + + out = R_GetImageBuffer( cinfo.output_width * cinfo.output_height * cinfo.output_components, BUFFER_IMAGE ); + + *pic = out; + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while ( cinfo.output_scanline < cinfo.output_height ) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + bbuf = ( ( out + ( row_stride * cinfo.output_scanline ) ) ); + buffer = &bbuf; + (void) jpeg_read_scanlines( &cinfo, buffer, 1 ); + } + + // clear all the alphas to 255 + { + int i, j; + byte *buf; + + buf = *pic; + + j = cinfo.output_width * cinfo.output_height * 4; + for ( i = 3 ; i < j ; i += 4 ) { + buf[i] = 255; + } + } + + /* Step 7: Finish decompression */ + + (void) jpeg_finish_decompress( &cinfo ); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress( &cinfo ); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + ri.FS_FreeFile( fbuffer ); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ +} + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + byte* outfile; /* target stream */ + int size; +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +void init_destination( j_compress_ptr cinfo ) { + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + dest->pub.next_output_byte = dest->outfile; + dest->pub.free_in_buffer = dest->size; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +boolean empty_output_buffer( j_compress_ptr cinfo ) { + return TRUE; +} + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object. Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default. This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images. Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL void +jpeg_start_compress( j_compress_ptr cinfo, boolean write_all_tables ) { + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + if ( write_all_tables ) { + jpeg_suppress_tables( cinfo, FALSE ); /* mark all tables to be written */ + + } + /* (Re)initialize error mgr and destination modules */ + ( *cinfo->err->reset_error_mgr )( (j_common_ptr) cinfo ); + ( *cinfo->dest->init_destination )( cinfo ); + /* Perform master selection of active modules */ + jinit_compress_master( cinfo ); + /* Set up for the first pass */ + ( *cinfo->master->prepare_for_pass )( cinfo ); + /* Ready for application to drive first pass through jpeg_write_scanlines + * or jpeg_write_raw_data. + */ + cinfo->next_scanline = 0; + cinfo->global_state = ( cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING ); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error. However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL JDIMENSION +jpeg_write_scanlines( j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines ) { + JDIMENSION row_ctr, rows_left; + + if ( cinfo->global_state != CSTATE_SCANNING ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + if ( cinfo->next_scanline >= cinfo->image_height ) { + WARNMS( cinfo, JWRN_TOO_MUCH_DATA ); + } + + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_scanlines. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_scanlines. + */ + if ( cinfo->master->call_pass_startup ) { + ( *cinfo->master->pass_startup )( cinfo ); + } + + /* Ignore any extra scanlines at bottom of image. */ + rows_left = cinfo->image_height - cinfo->next_scanline; + if ( num_lines > rows_left ) { + num_lines = rows_left; + } + + row_ctr = 0; + ( *cinfo->main->process_data )( cinfo, scanlines, &row_ctr, num_lines ); + cinfo->next_scanline += row_ctr; + return row_ctr; +} + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +static int hackSize; + +void term_destination( j_compress_ptr cinfo ) { + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = dest->size - dest->pub.free_in_buffer; + hackSize = datacount; +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +void jpegDest( j_compress_ptr cinfo, byte* outfile, int size ) { + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if ( cinfo->dest == NULL ) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof( my_destination_mgr ) ); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; + dest->size = size; +} + +void SaveJPG( char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer ) { + /* This struct contains the JPEG compression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + * It is possible to have several such structures, representing multiple + * compression/decompression processes, in existence at once. We refer + * to any one struct (and its associated working data) as a "JPEG object". + */ + struct jpeg_compress_struct cinfo; + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + int row_stride; /* physical row width in image buffer */ + unsigned char *out; + + /* Step 1: allocate and initialize JPEG compression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error( &jerr ); + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress( &cinfo ); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + + /* Here we use the library-supplied code to send compressed data to a + * stdio stream. You can also write your own code to do something else. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to write binary files. + */ + out = ri.Hunk_AllocateTempMemory( image_width * image_height * 4 ); + jpegDest( &cinfo, out, image_width * image_height * 4 ); + + /* Step 3: set parameters for compression */ + + /* First we supply a description of the input image. + * Four fields of the cinfo struct must be filled in: + */ + cinfo.image_width = image_width; /* image width and height, in pixels */ + cinfo.image_height = image_height; + cinfo.input_components = 4; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + /* Now use the library's routine to set default compression parameters. + * (You must set at least cinfo.in_color_space before calling this, + * since the defaults depend on the source color space.) + */ + jpeg_set_defaults( &cinfo ); + /* Now you can set any non-default parameters you wish to. + * Here we just illustrate the use of quality (quantization table) scaling: + */ + jpeg_set_quality( &cinfo, quality, TRUE /* limit to baseline-JPEG values */ ); + + /* Step 4: Start compressor */ + + /* TRUE ensures that we will write a complete interchange-JPEG file. + * Pass TRUE unless you are very sure of what you're doing. + */ + jpeg_start_compress( &cinfo, TRUE ); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + + /* Here we use the library's state variable cinfo.next_scanline as the + * loop counter, so that we don't have to keep track ourselves. + * To keep things simple, we pass one scanline per call; you can pass + * more if you wish, though. + */ + row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */ + + while ( cinfo.next_scanline < cinfo.image_height ) { + /* jpeg_write_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could pass + * more than one scanline at a time if that's more convenient. + */ + row_pointer[0] = &image_buffer[( ( cinfo.image_height - 1 ) * row_stride ) - cinfo.next_scanline * row_stride]; + (void) jpeg_write_scanlines( &cinfo, row_pointer, 1 ); + } + + /* Step 6: Finish compression */ + + jpeg_finish_compress( &cinfo ); + /* After finish_compress, we can close the output file. */ + ri.FS_WriteFile( filename, out, hackSize ); + + ri.Hunk_FreeTempMemory( out ); + + /* Step 7: release JPEG compression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_compress( &cinfo ); + + /* And we're done! */ +} + +//=================================================================== + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +void R_LoadImage( const char *name, byte **pic, int *width, int *height ) { + int len; + + *pic = NULL; + *width = 0; + *height = 0; + + len = strlen( name ); + if ( len < 5 ) { + return; + } + + if ( !Q_stricmp( name + len - 4, ".tga" ) ) { + LoadTGA( name, pic, width, height ); // try tga first + if ( !*pic ) { + char altname[MAX_QPATH]; // try jpg in place of tga + strcpy( altname, name ); + len = strlen( altname ); + altname[len - 3] = 'j'; + altname[len - 2] = 'p'; + altname[len - 1] = 'g'; + LoadJPG( altname, pic, width, height ); + } + } else if ( !Q_stricmp( name + len - 4, ".pcx" ) ) { + LoadPCX32( name, pic, width, height ); + } else if ( !Q_stricmp( name + len - 4, ".bmp" ) ) { + LoadBMP( name, pic, width, height ); + } else if ( !Q_stricmp( name + len - 4, ".jpg" ) ) { + LoadJPG( name, pic, width, height ); + } +} + + +/* +=============== +R_FindImageFile + +Finds or loads the given image. +Returns NULL if it fails, not a default image. +============== +*/ +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ) { + image_t *image; + int width, height; + byte *pic; + long hash; + + if ( !name ) { + return NULL; + } + + hash = generateHashValue( name ); + + // Ridah, caching + if ( r_cacheGathering->integer ) { + ri.Cmd_ExecuteText( EXEC_NOW, va( "cache_usedfile image %s %i %i %i\n", name, mipmap, allowPicmip, glWrapClampMode ) ); + } + + // + // see if the image is already loaded + // + for ( image = hashTable[hash]; image; image = image->next ) { + if ( !strcmp( name, image->imgName ) ) { + // the white image can be used with any set of parms, but other mismatches are errors + if ( strcmp( name, "*white" ) ) { + if ( image->mipmap != mipmap ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed mipmap parm\n", name ); + } + if ( image->allowPicmip != allowPicmip ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed allowPicmip parm\n", name ); + } + if ( image->wrapClampMode != glWrapClampMode ) { + ri.Printf( PRINT_ALL, "WARNING: reused image %s with mixed glWrapClampMode parm\n", name ); + } + } + return image; + } + } + + // Ridah, check the cache + // TTimo: assignment used as truth value + if ( ( image = R_FindCachedImage( name, hash ) ) ) { + return image; + } + // done. + + // + // load the pic from disk + // + R_LoadImage( name, &pic, &width, &height ); + if ( pic == NULL ) { // if we dont get a successful load +// TTimo: Duane changed to _DEBUG in all cases +// I'd still want that code in the release builds on linux +// (possibly for mod authors) +// /me maintained off for win32, using otherwise but printing diagnostics as developer +#if !defined( _WIN32 ) + char altname[MAX_QPATH]; // copy the name + int len; // + strcpy( altname, name ); // + len = strlen( altname ); // + altname[len - 3] = toupper( altname[len - 3] ); // and try upper case extension for unix systems + altname[len - 2] = toupper( altname[len - 2] ); // + altname[len - 1] = toupper( altname[len - 1] ); // + ri.Printf( PRINT_DEVELOPER, "trying %s...", altname ); // + R_LoadImage( altname, &pic, &width, &height ); // + if ( pic == NULL ) { // if that fails + ri.Printf( PRINT_DEVELOPER, "no\n" ); // + return NULL; // bail + } // + ri.Printf( PRINT_DEVELOPER, "yes\n" ); // +#else + return NULL; +#endif + } + + image = R_CreateImage( ( char * ) name, pic, width, height, mipmap, allowPicmip, glWrapClampMode ); + //ri.Free( pic ); + return image; +} + + +/* +================ +R_CreateDlightImage +================ +*/ +#define DLIGHT_SIZE 16 +static void R_CreateDlightImage( void ) { + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int b; + + // make a centered inverse-square falloff blob for dynamic lighting + for ( x = 0 ; x < DLIGHT_SIZE ; x++ ) { + for ( y = 0 ; y < DLIGHT_SIZE ; y++ ) { + float d; + + d = ( DLIGHT_SIZE / 2 - 0.5f - x ) * ( DLIGHT_SIZE / 2 - 0.5f - x ) + + ( DLIGHT_SIZE / 2 - 0.5f - y ) * ( DLIGHT_SIZE / 2 - 0.5f - y ); + b = 4000 / d; + if ( b > 255 ) { + b = 255; + } else if ( b < 75 ) { + b = 0; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + tr.dlightImage = R_CreateImage( "*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, qfalse, qfalse, GL_CLAMP ); +} + + +/* +================= +R_InitFogTable +================= +*/ +void R_InitFogTable( void ) { + int i; + float d; + float exp; + + exp = 0.5; + + for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) { + d = pow( (float)i / ( FOG_TABLE_SIZE - 1 ), exp ); + + tr.fogTable[i] = d; + } +} + +/* +================ +R_FogFactor + +Returns a 0.0 to 1.0 fog density value +This is called for each texel of the fog texture on startup +and for each vertex of transparent shaders in fog dynamically +================ +*/ +float R_FogFactor( float s, float t ) { + float d; + + s -= 1.0 / 512; + if ( s < 0 ) { + return 0; + } + if ( t < 1.0 / 32 ) { + return 0; + } + if ( t < 31.0 / 32 ) { + s *= ( t - 1.0f / 32.0f ) / ( 30.0f / 32.0f ); + } + + // we need to leave a lot of clamp range + s *= 8; + + if ( s > 1.0 ) { + s = 1.0; + } + + d = tr.fogTable[ (int)( s * ( FOG_TABLE_SIZE - 1 ) ) ]; + + return d; +} + +/* +================ +R_CreateFogImage +================ +*/ +#define FOG_S 256 +#define FOG_T 32 +static void R_CreateFogImage( void ) { + int x,y; + byte *data; + float g; + float d; + float borderColor[4]; + + data = ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 ); + + g = 2.0; + + // S is distance, T is depth + for ( x = 0 ; x < FOG_S ; x++ ) { + for ( y = 0 ; y < FOG_T ; y++ ) { + d = R_FogFactor( ( x + 0.5f ) / FOG_S, ( y + 0.5f ) / FOG_T ); + + data[( y * FOG_S + x ) * 4 + 0] = + data[( y * FOG_S + x ) * 4 + 1] = + data[( y * FOG_S + x ) * 4 + 2] = 255; + data[( y * FOG_S + x ) * 4 + 3] = 255 * d; + } + } + // standard openGL clamping doesn't really do what we want -- it includes + // the border color at the edges. OpenGL 1.2 has clamp-to-edge, which does + // what we want. + tr.fogImage = R_CreateImage( "*fog", (byte *)data, FOG_S, FOG_T, qfalse, qfalse, GL_CLAMP ); + ri.Hunk_FreeTempMemory( data ); + + borderColor[0] = 1.0; + borderColor[1] = 1.0; + borderColor[2] = 1.0; + borderColor[3] = 1; + + qglTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor ); +} + +/* +================== +R_CreateDefaultImage +================== +*/ +#define DEFAULT_SIZE 16 +static void R_CreateDefaultImage( void ) { + int x; + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + // the default image will be a box, to allow you to see the mapping coordinates + memset( data, 32, sizeof( data ) ); + for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) { + data[0][x][0] = + data[0][x][1] = + data[0][x][2] = 0; //----(SA) to make the default grid noticable but not blinding + data[0][x][3] = 255; + + data[x][0][0] = + data[x][0][1] = + data[x][0][2] = 0; //----(SA) to make the default grid noticable but not blinding + data[x][0][3] = 255; + + data[DEFAULT_SIZE - 1][x][0] = + data[DEFAULT_SIZE - 1][x][1] = + data[DEFAULT_SIZE - 1][x][2] = 0; //----(SA) to make the default grid noticable but not blinding + data[DEFAULT_SIZE - 1][x][3] = 255; + + data[x][DEFAULT_SIZE - 1][0] = + data[x][DEFAULT_SIZE - 1][1] = + data[x][DEFAULT_SIZE - 1][2] = 0; //----(SA) to make the default grid noticable but not blinding + data[x][DEFAULT_SIZE - 1][3] = 255; + } + tr.defaultImage = R_CreateImage( "*default", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, qtrue, qfalse, GL_REPEAT ); +} + +/* +================== +R_CreateBuiltinImages +================== +*/ +void R_CreateBuiltinImages( void ) { + int x,y; + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + R_CreateDefaultImage(); + + // we use a solid white image instead of disabling texturing + memset( data, 255, sizeof( data ) ); + tr.whiteImage = R_CreateImage( "*white", (byte *)data, 8, 8, qfalse, qfalse, GL_REPEAT ); + + // with overbright bits active, we need an image which is some fraction of full color, + // for default lightmaps, etc + for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) { + for ( y = 0 ; y < DEFAULT_SIZE ; y++ ) { + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = tr.identityLightByte; + data[y][x][3] = 255; + } + } + + tr.identityLightImage = R_CreateImage( "*identityLight", (byte *)data, 8, 8, qfalse, qfalse, GL_REPEAT ); + + + for ( x = 0; x < 32; x++ ) { + // scratchimage is usually used for cinematic drawing + tr.scratchImage[x] = R_CreateImage( "*scratch", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, qfalse, qtrue, GL_CLAMP ); + } + + R_CreateDlightImage(); + R_CreateFogImage(); +} + + +/* +=============== +R_SetColorMappings +=============== +*/ +void R_SetColorMappings( void ) { + int i, j; + float g; + int inf; + int shift; + + // setup the overbright lighting + tr.overbrightBits = r_overBrightBits->integer; + if ( !glConfig.deviceSupportsGamma ) { + tr.overbrightBits = 0; // need hardware gamma for overbright + } + + // never overbright in windowed mode + if ( !glConfig.isFullscreen ) { + tr.overbrightBits = 0; + } + + // allow 2 overbright bits in 24 bit, but only 1 in 16 bit + if ( glConfig.colorBits > 16 ) { + if ( tr.overbrightBits > 2 ) { + tr.overbrightBits = 2; + } + } else { + if ( tr.overbrightBits > 1 ) { + tr.overbrightBits = 1; + } + } + if ( tr.overbrightBits < 0 ) { + tr.overbrightBits = 0; + } + + tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + tr.identityLightByte = 255 * tr.identityLight; + + + if ( r_intensity->value <= 1 ) { + ri.Cvar_Set( "r_intensity", "1" ); + } + + if ( r_gamma->value < 0.5f ) { + ri.Cvar_Set( "r_gamma", "0.5" ); + } else if ( r_gamma->value > 3.0f ) { + ri.Cvar_Set( "r_gamma", "3.0" ); + } + + g = r_gamma->value; + + shift = tr.overbrightBits; + + for ( i = 0; i < 256; i++ ) { + if ( g == 1 ) { + inf = i; + } else { + inf = 255 * pow( i / 255.0f, 1.0f / g ) + 0.5f; + } + inf <<= shift; + if ( inf < 0 ) { + inf = 0; + } + if ( inf > 255 ) { + inf = 255; + } + s_gammatable[i] = inf; + } + + for ( i = 0 ; i < 256 ; i++ ) { + j = i * r_intensity->value; + if ( j > 255 ) { + j = 255; + } + s_intensitytable[i] = j; + } + + if ( glConfig.deviceSupportsGamma ) { + GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); + } +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + memset( hashTable, 0, sizeof( hashTable ) ); + + // Ridah, caching system + R_InitTexnumImages( qfalse ); + // done. + + // build brightness translation tables + R_SetColorMappings(); + + // create default texture and white texture + R_CreateBuiltinImages(); + + // Ridah, load the cache media, if they were loaded previously, they'll be restored from the backupImages + R_LoadCacheImages(); + // done. +} + +/* +=============== +R_DeleteTextures +=============== +*/ +void R_DeleteTextures( void ) { + int i; + + for ( i = 0; i < tr.numImages ; i++ ) { + qglDeleteTextures( 1, &tr.images[i]->texnum ); + } + memset( tr.images, 0, sizeof( tr.images ) ); + // Ridah + R_InitTexnumImages( qtrue ); + // done. + + memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglBindTexture ) { + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } else { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + } +} + +/* +============================================================================ + +SKINS + +============================================================================ +*/ + +/* +================== +CommaParse + +This is unfortunate, but the skin files aren't +compatable with our normal parsing rules. +================== +*/ +static char *CommaParse( char **data_p ) { + int c = 0, len; + char *data; + static char com_token[MAX_TOKEN_CHARS]; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + while ( 1 ) { + // skip whitespace + while ( ( c = *data ) <= ' ' ) { + if ( !c ) { + break; + } + data++; + } + + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) { + while ( *data && *data != '\n' ) + data++; + } + // skip /* */ comments + else if ( c == '/' && data[1] == '*' ) { + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) { + data += 2; + } + } else + { + break; + } + } + + if ( c == 0 ) { + return ""; + } + + // handle quoted strings + if ( c == '\"' ) { + data++; + while ( 1 ) + { + c = *data++; + if ( c == '\"' || !c ) { + com_token[len] = 0; + *data_p = ( char * ) data; + return com_token; + } + if ( len < MAX_TOKEN_CHARS ) { + com_token[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if ( len < MAX_TOKEN_CHARS ) { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while ( c > 32 && c != ',' ); + + if ( len == MAX_TOKEN_CHARS ) { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + + +//----(SA) added so client can see what model or scale for the model was specified in a skin +/* +============== +RE_GetSkinModel +============== +*/ +qboolean RE_GetSkinModel( qhandle_t skinid, const char *type, char *name ) { + int i; + skin_t *bar; + + bar = tr.skins[skinid]; + + if ( !Q_stricmp( type, "playerscale" ) ) { // client is requesting scale from the skin rather than a model + Com_sprintf( name, MAX_QPATH, "%.2f %.2f %.2f", bar->scale[0], bar->scale[1], bar->scale[2] ); + return qtrue; + } + + for ( i = 0; i < bar->numModels; i++ ) + { + if ( !Q_stricmp( bar->models[i]->type, type ) ) { // (SA) whoops, should've been this way + Q_strncpyz( name, bar->models[i]->model, sizeof( bar->models[i]->model ) ); + return qtrue; + } + } + return qfalse; +} + +/* +============== +RE_GetShaderFromModel + return a shader index for a given model's surface + 'withlightmap' set to '0' will create a new shader that is a copy of the one found + on the model, without the lighmap stage, if the shader has a lightmap stage + + NOTE: only works for bmodels right now. Could modify for other models (md3's etc.) +============== +*/ +qhandle_t RE_GetShaderFromModel( qhandle_t modelid, int surfnum, int withlightmap ) { + model_t *model; + bmodel_t *bmodel; + msurface_t *surf; + shader_t *shd; + + if ( surfnum < 0 ) { + surfnum = 0; + } + + model = R_GetModelByHandle( modelid ); // (SA) should be correct now + + if ( model ) { + bmodel = model->bmodel; + if ( bmodel && bmodel->firstSurface ) { + if ( surfnum >= bmodel->numSurfaces ) { // if it's out of range, return the first surface + surfnum = 0; + } + + surf = bmodel->firstSurface + surfnum; +// if(surf->shader->lightmapIndex != LIGHTMAP_NONE) { + if ( surf->shader->lightmapIndex > LIGHTMAP_NONE ) { + image_t *image; + long hash; + qboolean mip = qtrue; // mip generation on by default + + // get mipmap info for original texture + hash = generateHashValue( surf->shader->name ); + for ( image = hashTable[hash]; image; image = image->next ) { + if ( !strcmp( surf->shader->name, image->imgName ) ) { + mip = image->mipmap; + break; + } + } + shd = R_FindShader( surf->shader->name, LIGHTMAP_NONE, mip ); + shd->stages[0]->rgbGen = CGEN_LIGHTING_DIFFUSE; // (SA) new + } else { + shd = surf->shader; + } + + return shd->index; + } + } + + return 0; +} + +//----(SA) end + +/* +=============== +RE_RegisterSkin + +=============== +*/ +qhandle_t RE_RegisterSkin( const char *name ) { + qhandle_t hSkin; + skin_t *skin; + skinSurface_t *surf; + skinModel_t *model; //----(SA) added + char *text, *text_p; + char *token; + char surfName[MAX_QPATH]; + + if ( !name || !name[0] ) { + Com_Printf( "Empty name passed to RE_RegisterSkin\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Skin name exceeds MAX_QPATH\n" ); + return 0; + } + + + // see if the skin is already loaded + for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { + skin = tr.skins[hSkin]; + if ( !Q_stricmp( skin->name, name ) ) { + if ( skin->numSurfaces == 0 ) { + return 0; // default skin + } + return hSkin; + } + } + + // allocate a new skin + if ( tr.numSkins == MAX_SKINS ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); + return 0; + } + + +//----(SA) moved things around slightly to fix the problem where you restart +// a map that has ai characters who had invalid skin names entered +// in thier "skin" or "head" field + + // make sure the render thread is stopped + R_SyncRenderThread(); + + // If not a .skin file, load as a single shader + if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { + tr.numSkins++; + skin = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); + skin->numSurfaces = 0; + skin->numModels = 0; //----(SA) added + skin->numSurfaces = 1; + skin->surfaces[0] = ri.Hunk_Alloc( sizeof( skin->surfaces[0] ), h_low ); + skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue ); + return hSkin; + } + + // load and parse the skin file + ri.FS_ReadFile( name, (void **)&text ); + if ( !text ) { + return 0; + } + + tr.numSkins++; + skin = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); + skin->numSurfaces = 0; + skin->numModels = 0; //----(SA) added + +//----(SA) end + + text_p = text; + while ( text_p && *text_p ) { + // get surface name + token = CommaParse( &text_p ); + Q_strncpyz( surfName, token, sizeof( surfName ) ); + + if ( !token[0] ) { + break; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surfName ); + + if ( *text_p == ',' ) { + text_p++; + } + + if ( strstr( token, "tag_" ) ) { + continue; + } + + if ( strstr( token, "md3_" ) ) { // this is specifying a model + model = skin->models[ skin->numModels ] = ri.Hunk_Alloc( sizeof( *skin->models[0] ), h_low ); + Q_strncpyz( model->type, token, sizeof( model->type ) ); + + // get the model name + token = CommaParse( &text_p ); + + Q_strncpyz( model->model, token, sizeof( model->model ) ); + + skin->numModels++; + continue; + } + +//----(SA) added + if ( strstr( token, "playerscale" ) ) { + token = CommaParse( &text_p ); + skin->scale[0] = atof( token ); // uniform scaling for now + skin->scale[1] = atof( token ); + skin->scale[2] = atof( token ); + continue; + } +//----(SA) end + + // parse the shader name + token = CommaParse( &text_p ); + + surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue ); + skin->numSurfaces++; + } + + ri.FS_FreeFile( text ); + + + // never let a skin have 0 shaders + + //----(SA) allow this for the (current) special case of the loper's upper body + // (it's upper body has no surfaces, only tags) + if ( skin->numSurfaces == 0 ) { + if ( !( strstr( name, "loper" ) && strstr( name, "upper" ) ) ) { + return 0; // use default skin + } + } + + return hSkin; +} + + +/* +=============== +R_InitSkins +=============== +*/ +void R_InitSkins( void ) { + skin_t *skin; + + tr.numSkins = 1; + + // make the default skin have all default shaders + skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + Q_strncpyz( skin->name, "", sizeof( skin->name ) ); + skin->numSurfaces = 1; + skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces ), h_low ); + skin->surfaces[0]->shader = tr.defaultShader; +} + +/* +=============== +R_GetSkinByHandle +=============== +*/ +skin_t *R_GetSkinByHandle( qhandle_t hSkin ) { + if ( hSkin < 1 || hSkin >= tr.numSkins ) { + return tr.skins[0]; + } + return tr.skins[ hSkin ]; +} + +/* +=============== +R_SkinList_f +=============== +*/ +void R_SkinList_f( void ) { + int i, j; + skin_t *skin; + + ri.Printf( PRINT_ALL, "------------------\n" ); + + for ( i = 0 ; i < tr.numSkins ; i++ ) { + skin = tr.skins[i]; + + ri.Printf( PRINT_ALL, "%3i:%s\n", i, skin->name ); + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + ri.Printf( PRINT_ALL, " %s = %s\n", + skin->surfaces[j]->name, skin->surfaces[j]->shader->name ); + } + } + ri.Printf( PRINT_ALL, "------------------\n" ); +} + +// Ridah, utility for automatically cropping and numbering a bunch of images in a directory +/* +============= +SaveTGA + + saves out to 24 bit uncompressed format (no alpha) +============= +*/ +void SaveTGA( char *name, byte **pic, int width, int height ) { + byte *inpixel, *outpixel; + byte *outbuf, *b; + + outbuf = ri.Hunk_AllocateTempMemory( width * height * 4 + 18 ); + b = outbuf; + + memset( b, 0, 18 ); + b[2] = 2; // uncompressed type + b[12] = width & 255; + b[13] = width >> 8; + b[14] = height & 255; + b[15] = height >> 8; + b[16] = 24; // pixel size + + { + int row, col; + int rows, cols; + + rows = ( height ); + cols = ( width ); + + outpixel = b + 18; + + for ( row = ( rows - 1 ); row >= 0; row-- ) + { + inpixel = ( ( *pic ) + ( row * cols ) * 4 ); + + for ( col = 0; col < cols; col++ ) + { + *outpixel++ = *( inpixel + 2 ); // blue + *outpixel++ = *( inpixel + 1 ); // green + *outpixel++ = *( inpixel + 0 ); // red + //*outpixel++ = *(inpixel + 3); // alpha + + inpixel += 4; + } + } + } + + ri.FS_WriteFile( name, outbuf, (int)( outpixel - outbuf ) ); + + ri.Hunk_FreeTempMemory( outbuf ); + +} + +/* +============= +SaveTGAAlpha + + saves out to 32 bit uncompressed format (with alpha) +============= +*/ +void SaveTGAAlpha( char *name, byte **pic, int width, int height ) { + byte *inpixel, *outpixel; + byte *outbuf, *b; + + outbuf = ri.Hunk_AllocateTempMemory( width * height * 4 + 18 ); + b = outbuf; + + memset( b, 0, 18 ); + b[2] = 2; // uncompressed type + b[12] = width & 255; + b[13] = width >> 8; + b[14] = height & 255; + b[15] = height >> 8; + b[16] = 32; // pixel size + + { + int row, col; + int rows, cols; + + rows = ( height ); + cols = ( width ); + + outpixel = b + 18; + + for ( row = ( rows - 1 ); row >= 0; row-- ) + { + inpixel = ( ( *pic ) + ( row * cols ) * 4 ); + + for ( col = 0; col < cols; col++ ) + { + *outpixel++ = *( inpixel + 2 ); // blue + *outpixel++ = *( inpixel + 1 ); // green + *outpixel++ = *( inpixel + 0 ); // red + *outpixel++ = *( inpixel + 3 ); // alpha + + inpixel += 4; + } + } + } + + ri.FS_WriteFile( name, outbuf, (int)( outpixel - outbuf ) ); + + ri.Hunk_FreeTempMemory( outbuf ); + +} + +/* +============== +R_CropImage +============== +*/ +#define CROPIMAGES_ENABLED +//#define FUNNEL_HACK +#define RESIZE +//#define QUICKTIME_BANNER +#define TWILTB2_HACK + +qboolean R_CropImage( char *name, byte **pic, int border, int *width, int *height, int lastBox[2] ) { +#ifdef CROPIMAGES_ENABLED + int row, col; + int rows, cols; + byte *inpixel, *temppic, *outpixel; + int mins[2], maxs[2]; + int diff[2]; + //int newWidth; + int /*a, max,*/ i; + int alpha; + //int *center; + qboolean /*invalid,*/ skip; + vec3_t fCol, fScale; + qboolean filterColors = qfalse; + int fCount; + float f,c; +#define FADE_BORDER_RANGE ( ( *width ) / 40 ) + + rows = ( *height ); + cols = ( *width ); + + mins[0] = 99999; + mins[1] = 99999; + maxs[0] = -99999; + maxs[1] = -99999; + +#ifdef TWILTB2_HACK + // find the filter color (use first few pixels) + filterColors = qtrue; + inpixel = ( *pic ); + VectorClear( fCol ); + for ( fCount = 0; fCount < 8; fCount++, inpixel += 4 ) { + for ( i = 0; i < 3; i++ ) { + if ( *( inpixel + i ) > 70 ) { + continue; // too bright, cant be noise + } + if ( fCol[i] < *( inpixel + i ) ) { + fCol[i] = *( inpixel + i ); + } + } + } + //VectorScale( fCol, 1.0/fCount, fCol ); + for ( i = 0; i < 3; i++ ) { + fCol[i] += 4; + fScale[i] = 255.0 / ( 255.0 - fCol[i] ); + } +#endif + + for ( row = 0; row < rows; row++ ) + { + inpixel = ( ( *pic ) + ( row * cols ) * 4 ); + + for ( col = 0; col < cols; col++, inpixel += 4 ) + { + if ( filterColors ) { + // special code for filtering the twiltb series + for ( i = 0; i < 3; i++ ) { + f = *( inpixel + i ); + f -= fCol[i]; + if ( f <= 0 ) { + f = 0; + } else { f *= fScale[i];} + if ( f > 255 ) { + f = 255; + } + *( inpixel + i ) = f; + } + // if this pixel is near the edge, then fade it out (just do a brightness fade) + if ( (int *)inpixel ) { + // calc the fade scale + f = (float)row / FADE_BORDER_RANGE; + if ( f > (float)( rows - row - 1 ) / FADE_BORDER_RANGE ) { + f = (float)( rows - row - 1 ) / FADE_BORDER_RANGE; + } + if ( f > (float)( cols - col - 1 ) / FADE_BORDER_RANGE ) { + f = (float)( cols - col - 1 ) / FADE_BORDER_RANGE; + } + if ( f > (float)( col ) / FADE_BORDER_RANGE ) { + f = (float)( col ) / FADE_BORDER_RANGE; + } + if ( f < 1.0 ) { + if ( f <= 0 ) { + *( (int *)inpixel ) = 0; + } else { + f += 0.2 * crandom(); + if ( f < 1.0 ) { + if ( f <= 0 ) { + *( (int *)inpixel ) = 0; + } else { + f = 1.0 - f; + for ( i = 0; i < 3; i++ ) { + c = *( inpixel + i ); + c -= f * c; + if ( c < 0 ) { + c = 0; + } + *( inpixel + i ) = c; + } + } + } + } + } + } + continue; + } + + skip = qfalse; +#ifdef QUICKTIME_BANNER + // hack for quicktime ripped images + if ( ( col > cols - 3 || row > rows - 36 ) ) { + // filter this pixel out + *( inpixel + 0 ) = 0; + *( inpixel + 1 ) = 0; + *( inpixel + 2 ) = 0; + skip = qtrue; + } +#endif + + if ( !skip ) { + if ( ( *( inpixel + 0 ) > 20 ) // blue + || ( *( inpixel + 1 ) > 20 ) // green + || ( *( inpixel + 2 ) > 20 ) ) { // red + + if ( col < mins[0] ) { + mins[0] = col; + } + if ( col > maxs[0] ) { + maxs[0] = col; + } + if ( row < mins[1] ) { + mins[1] = row; + } + if ( row > maxs[1] ) { + maxs[1] = row; + } + + } else { + // filter this pixel out + *( inpixel + 0 ) = 0; + *( inpixel + 1 ) = 0; + *( inpixel + 2 ) = 0; + } + } + +#ifdef FUNNEL_HACK // scale brightness down + for ( i = 0; i < 3; i++ ) { + alpha = *( inpixel + i ); + if ( ( alpha -= 20 ) < 0 ) { + alpha = 0; + } + *( inpixel + i ) = alpha; + } +#endif + + // set the alpha component + alpha = *( inpixel + 0 ); // + *(inpixel + 1) + *(inpixel + 2); + if ( *( inpixel + 1 ) > alpha ) { + alpha = *( inpixel + 1 ); + } + if ( *( inpixel + 2 ) > alpha ) { + alpha = *( inpixel + 2 ); + } + //alpha = (int)((float)alpha / 3.0); + //alpha /= 3; + if ( alpha > 255 ) { + alpha = 255; + } + *( inpixel + 3 ) = (byte)alpha; + } + } + +#ifdef RESIZE + return qtrue; +#endif + + // convert it so that the center is the center of the image + // this is used for some explosions + /* + for (i=0; i<2; i++) { + if (i==0) center = width; + else center = height; + + if ((*center/2 - mins[i]) > (maxs[i] - *center/2)) { + maxs[i] = *center/2 + (*center/2 - mins[i]); + } else { + mins[i] = *center/2 - (maxs[i] - *center/2); + } + } + */ + + // HACK for funnel +#ifdef FUNNEL_HACK + mins[0] = 210; + maxs[0] = 430; + mins[1] = 0; + maxs[1] = *height - 1; + + for ( i = 0; i < 2; i++ ) { + diff[i] = maxs[i] - mins[i]; + } +#else + +#ifndef RESIZE + // apply the border + for ( i = 0; i < 2; i++ ) { + mins[i] -= border; + if ( mins[i] < 0 ) { + mins[i] = 0; + } + maxs[i] += border; + if ( i == 0 ) { + if ( maxs[i] > *width - 1 ) { + maxs[i] = *width - 1; + } + } else { + if ( maxs[i] > *height - 1 ) { + maxs[i] = *height - 1; + } + } + } + + // we have the mins/maxs, so work out the best square to crop to + for ( i = 0; i < 2; i++ ) { + diff[i] = maxs[i] - mins[i]; + } + + // widen the axis that has the smallest diff + a = -1; + if ( diff[1] > diff[0] ) { + a = 0; + max = *width - 1; + } else if ( diff[0] > diff[1] ) { + a = 1; + max = *height - 1; + } + if ( a >= 0 ) { + invalid = qfalse; + while ( diff[a] < diff[!a] ) { + if ( invalid ) { + Com_Printf( "unable to find a good crop size\n" ); + return qfalse; + } + invalid = qtrue; + if ( mins[a] > 0 ) { + mins[a] -= 1; + diff[a] = maxs[a] - mins[a]; + invalid = qfalse; + } + if ( ( diff[a] < diff[!a] ) && ( maxs[a] < max ) ) { + maxs[a] += 1; + diff[a] = maxs[a] - mins[a]; + invalid = qfalse; + } + } + } + + // make sure it's bigger or equal to the last one + for ( i = 0; i < 2; i++ ) { + if ( ( maxs[i] - mins[i] ) < lastBox[i] ) { + if ( i == 0 ) { + center = width; + } else { center = height;} + + maxs[i] = *center / 2 + ( lastBox[i] / 2 ); + mins[i] = maxs[i] - lastBox[i]; + diff[i] = lastBox[i]; + } + } +#else + for ( i = 0; i < 2; i++ ) { + diff[i] = maxs[i] - mins[i]; + lastBox[i] = diff[i]; + } +#endif // RESIZE +#endif // FUNNEL_HACK + + temppic = ri.Z_Malloc( sizeof( unsigned int ) * diff[0] * diff[1] ); + outpixel = temppic; + + for ( row = mins[1]; row < maxs[1]; row++ ) + { + inpixel = ( ( *pic ) + ( row * cols ) * 4 ); + inpixel += mins[0] * 4; + + for ( col = mins[0]; col < maxs[0]; col++ ) + { + *outpixel++ = *( inpixel + 0 ); // blue + *outpixel++ = *( inpixel + 1 ); // green + *outpixel++ = *( inpixel + 2 ); // red + *outpixel++ = *( inpixel + 3 ); // alpha + + inpixel += 4; + } + } + + // for some reason this causes memory drop, not worth investigating (dev command only) + //ri.Free( *pic ); + + *pic = temppic; + + *width = diff[0]; + *height = diff[1]; + + return qtrue; +#else + return qtrue; // shutup the compiler +#endif +} + + +/* +=============== +R_CropAndNumberImagesInDirectory +=============== +*/ +void R_CropAndNumberImagesInDirectory( char *dir, char *ext, int maxWidth, int maxHeight, int withAlpha ) { +#ifdef CROPIMAGES_ENABLED + char **fileList; + int numFiles, j; +#ifndef RESIZE + int i; +#endif + byte *pic, *temppic; + int width, height, newWidth, newHeight; + char *pch; + int b,c,d,lastNumber; + int lastBox[2] = {0,0}; + + fileList = ri.FS_ListFiles( dir, ext, &numFiles ); + + if ( !numFiles ) { + ri.Printf( PRINT_ALL, "no '%s' files in directory '%s'\n", ext, dir ); + return; + } + + ri.Printf( PRINT_ALL, "%i files found, beginning processing..\n", numFiles ); + + for ( j = 0; j < numFiles; j++ ) { + char filename[MAX_QPATH], outfilename[MAX_QPATH]; + + if ( !Q_strncmp( fileList[j], "spr", 3 ) ) { + continue; + } + + Com_sprintf( filename, sizeof( filename ), "%s/%s", dir, fileList[j] ); + ri.Printf( PRINT_ALL, "...cropping '%s'.. ", filename ); + + R_LoadImage( filename, &pic, &width, &height ); + if ( !pic ) { + ri.Printf( PRINT_ALL, "error reading file, ignoring.\n" ); + continue; + } + + // file has been read, crop it, resize it down to a power of 2, then save + if ( !R_CropImage( filename, &pic, 6, &width, &height, lastBox ) ) { + ri.Printf( PRINT_ALL, "unable to crop image.\n" ); + //ri.Free( pic ); + break; + } +#ifndef RESIZE + for ( i = 2; ( 1 << i ) <= maxWidth; i++ ) { + if ( ( width < ( 1 << i ) ) && ( width > ( 1 << ( i - 1 ) ) ) ) { + newWidth = ( 1 << ( i - 1 ) ); + if ( newWidth > maxWidth ) { + newWidth = maxWidth; + } + newHeight = newWidth; + temppic = ri.Z_Malloc( sizeof( unsigned int ) * newWidth * newHeight ); + ResampleTexture( (unsigned int *)pic, width, height, (unsigned int *)temppic, newWidth, newHeight ); + memcpy( pic, temppic, sizeof( unsigned int ) * newWidth * newHeight ); + ri.Free( temppic ); + width = height = newWidth; + break; + } + } + if ( width > maxWidth ) { + // we need to force the scale downwards + newWidth = maxWidth; + newHeight = maxWidth; + temppic = ri.Z_Malloc( sizeof( unsigned int ) * newWidth * newHeight ); + ResampleTexture( (unsigned int *)pic, width, height, (unsigned int *)temppic, newWidth, newHeight ); + memcpy( pic, temppic, sizeof( unsigned int ) * newWidth * newHeight ); + ri.Free( temppic ); + width = newWidth; + height = newHeight; + } +#else + newWidth = maxWidth; + newHeight = maxHeight; + temppic = ri.Z_Malloc( sizeof( unsigned int ) * newWidth * newHeight ); + ResampleTexture( (unsigned int *)pic, width, height, (unsigned int *)temppic, newWidth, newHeight ); + memcpy( pic, temppic, sizeof( unsigned int ) * newWidth * newHeight ); + ri.Free( temppic ); + width = newWidth; + height = newHeight; +#endif + + // set the new filename + pch = strrchr( filename, '/' ); + *pch = '\0'; + lastNumber = j; + b = lastNumber / 100; + lastNumber -= b * 100; + c = lastNumber / 10; + lastNumber -= c * 10; + d = lastNumber; + Com_sprintf( outfilename, sizeof( outfilename ), "%s/spr%i%i%i.tga", filename, b, c, d ); + + if ( withAlpha ) { + SaveTGAAlpha( outfilename, &pic, width, height ); + } else { + SaveTGA( outfilename, &pic, width, height ); + } + + // free the pixel data + //ri.Free( pic ); + + ri.Printf( PRINT_ALL, "done.\n" ); + } +#endif +} + +/* +============== +R_CropImages_f +============== +*/ +void R_CropImages_f( void ) { +#ifdef CROPIMAGES_ENABLED + if ( ri.Cmd_Argc() < 5 ) { + ri.Printf( PRINT_ALL, "syntax: cropimages \neg: 'cropimages sprites/fire1 .tga 64 64 0'\n" ); + return; + } + R_CropAndNumberImagesInDirectory( ri.Cmd_Argv( 1 ), ri.Cmd_Argv( 2 ), atoi( ri.Cmd_Argv( 3 ) ), atoi( ri.Cmd_Argv( 4 ) ), atoi( ri.Cmd_Argv( 5 ) ) ); +#else + ri.Printf( PRINT_ALL, "This command has been disabled.\n" ); +#endif +} +// done. + +//========================================================================================== +// Ridah, caching system + +static int numBackupImages = 0; +static image_t *backupHashTable[FILE_HASH_SIZE]; + +static image_t *texnumImages[MAX_DRAWIMAGES * 2]; + +/* +=============== +R_CacheImageAlloc + + this will only get called to allocate the image_t structures, not that actual image pixels +=============== +*/ +void *R_CacheImageAlloc( int size ) { + if ( r_cache->integer && r_cacheShaders->integer ) { + return malloc( size ); +//DAJ TEST return ri.Z_Malloc( size ); //DAJ was CO + } else { + return ri.Hunk_Alloc( size, h_low ); + } +} + +/* +=============== +R_CacheImageFree +=============== +*/ +void R_CacheImageFree( void *ptr ) { + if ( r_cache->integer && r_cacheShaders->integer ) { + free( ptr ); +//DAJ TEST ri.Free( ptr ); //DAJ was CO + } +} + +/* +=============== +R_TouchImage + + remove this image from the backupHashTable and make sure it doesn't get overwritten +=============== +*/ +qboolean R_TouchImage( image_t *inImage ) { + image_t *bImage, *bImagePrev; + int hash; + char *name; + + if ( inImage == tr.dlightImage || + inImage == tr.whiteImage || + inImage == tr.defaultImage || + inImage->imgName[0] == '*' ) { // can't use lightmaps since they might have the same name, but different maps will have different actual lightmap pixels + return qfalse; + } + + hash = inImage->hash; + name = inImage->imgName; + + bImage = backupHashTable[hash]; + bImagePrev = NULL; + while ( bImage ) { + + if ( bImage == inImage ) { + // add it to the current images + if ( tr.numImages == MAX_DRAWIMAGES ) { + ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit\n" ); + } + + tr.images[tr.numImages] = bImage; + + // remove it from the backupHashTable + if ( bImagePrev ) { + bImagePrev->next = bImage->next; + } else { + backupHashTable[hash] = bImage->next; + } + + // add it to the hashTable + bImage->next = hashTable[hash]; + hashTable[hash] = bImage; + + // get the new texture + tr.numImages++; + + return qtrue; + } + + bImagePrev = bImage; + bImage = bImage->next; + } + + return qtrue; +} + +/* +=============== +R_PurgeImage +=============== +*/ +void R_PurgeImage( image_t *image ) { + + texnumImages[image->texnum - 1024] = NULL; + + qglDeleteTextures( 1, &image->texnum ); + + R_CacheImageFree( image ); + + memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglBindTexture ) { + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } else { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + } +} + + +/* +=============== +R_PurgeBackupImages + + Can specify the number of Images to purge this call (used for background purging) +=============== +*/ +void R_PurgeBackupImages( int purgeCount ) { + int i, cnt; + static int lastPurged = 0; + image_t *image; + + if ( !numBackupImages ) { + // nothing to purge + lastPurged = 0; + return; + } + + R_SyncRenderThread(); + + cnt = 0; + for ( i = lastPurged; i < FILE_HASH_SIZE; ) { + lastPurged = i; + // TTimo: assignment used as truth value + if ( ( image = backupHashTable[i] ) ) { + // kill it + backupHashTable[i] = image->next; + R_PurgeImage( image ); + cnt++; + + if ( cnt >= purgeCount ) { + return; + } + } else { + i++; // no images in this slot, so move to the next one + } + } + + // all done + numBackupImages = 0; + lastPurged = 0; +} + +/* +=============== +R_BackupImages +=============== +*/ +void R_BackupImages( void ) { + + if ( !r_cache->integer ) { + return; + } + if ( !r_cacheShaders->integer ) { + return; + } + + // backup the hashTable + memcpy( backupHashTable, hashTable, sizeof( backupHashTable ) ); + + // pretend we have cleared the list + numBackupImages = tr.numImages; + tr.numImages = 0; + + memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglBindTexture ) { + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } else { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + } +} + +/* +============= +R_FindCachedImage +============= +*/ +image_t *R_FindCachedImage( const char *name, int hash ) { + image_t *bImage, *bImagePrev; + + if ( !r_cacheShaders->integer ) { + return NULL; + } + + if ( !numBackupImages ) { + return NULL; + } + + bImage = backupHashTable[hash]; + bImagePrev = NULL; + while ( bImage ) { + + if ( !Q_stricmp( name, bImage->imgName ) ) { + // add it to the current images + if ( tr.numImages == MAX_DRAWIMAGES ) { + ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit\n" ); + } + + R_TouchImage( bImage ); + return bImage; + } + + bImagePrev = bImage; + bImage = bImage->next; + } + + return NULL; +} + +/* +=============== +R_InitTexnumImages +=============== +*/ +static int last_i; +void R_InitTexnumImages( qboolean force ) { + if ( force || !numBackupImages ) { + memset( texnumImages, 0, sizeof( texnumImages ) ); + last_i = 0; + } +} + +/* +============ +R_FindFreeTexnum +============ +*/ +void R_FindFreeTexnum( image_t *inImage ) { + int i, max; + image_t **image; + + max = ( MAX_DRAWIMAGES * 2 ); + if ( last_i && !texnumImages[last_i + 1] ) { + i = last_i + 1; + } else { + i = 0; + image = (image_t **)&texnumImages; + while ( i < max && *( image++ ) ) { + i++; + } + } + + if ( i < max ) { + if ( i < max - 1 ) { + last_i = i; + } else { + last_i = 0; + } + inImage->texnum = 1024 + i; + texnumImages[i] = inImage; + } else { + ri.Error( ERR_DROP, "R_FindFreeTexnum: MAX_DRAWIMAGES hit\n" ); + } +} + +/* +=============== +R_LoadCacheImages +=============== +*/ +void R_LoadCacheImages( void ) { + int len; + byte *buf; + char *token, *pString; + char name[MAX_QPATH]; + int parms[3], i; + + if ( numBackupImages ) { + return; + } + + len = ri.FS_ReadFile( "image.cache", NULL ); + + if ( len <= 0 ) { + return; + } + + buf = (byte *)ri.Hunk_AllocateTempMemory( len ); + ri.FS_ReadFile( "image.cache", (void **)&buf ); + pString = buf; + + while ( ( token = COM_ParseExt( &pString, qtrue ) ) && token[0] ) { + Q_strncpyz( name, token, sizeof( name ) ); + for ( i = 0; i < 3; i++ ) { + token = COM_ParseExt( &pString, qfalse ); + parms[i] = atoi( token ); + } + R_FindImageFile( name, parms[0], parms[1], parms[2] ); + } + + ri.Hunk_FreeTempMemory( buf ); +} +// done. +//========================================================================================== diff --git a/src/renderer/tr_init.c b/src/renderer/tr_init.c new file mode 100644 index 0000000..2cf79ad --- /dev/null +++ b/src/renderer/tr_init.c @@ -0,0 +1,1370 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_init.c -- functions that are not called every frame + +#include "tr_local.h" + +//#ifdef __USEA3D +//// Defined in snd_a3dg_refcommon.c +//void RE_A3D_RenderGeometry (void *pVoidA3D, void *pVoidGeom, void *pVoidMat, void *pVoidGeomStatus); +//#endif + +glconfig_t glConfig; +glstate_t glState; + +static void GfxInfo_f( void ); + +cvar_t *r_flareSize; +cvar_t *r_flareFade; + +cvar_t *r_railWidth; +cvar_t *r_railCoreWidth; +cvar_t *r_railSegmentLength; + +cvar_t *r_ignoreFastPath; + +cvar_t *r_verbose; +cvar_t *r_ignore; + +cvar_t *r_displayRefresh; + +cvar_t *r_detailTextures; + +cvar_t *r_znear; +cvar_t *r_zfar; + +cvar_t *r_smp; +cvar_t *r_showSmp; +cvar_t *r_skipBackEnd; + +cvar_t *r_ignorehwgamma; +cvar_t *r_measureOverdraw; + +cvar_t *r_inGameVideo; +cvar_t *r_fastsky; +cvar_t *r_drawSun; +cvar_t *r_dynamiclight; +cvar_t *r_dlightBacks; + +cvar_t *r_lodbias; +cvar_t *r_lodscale; + +cvar_t *r_norefresh; +cvar_t *r_drawentities; +cvar_t *r_drawworld; +cvar_t *r_speeds; +//cvar_t *r_fullbright; // JPW NERVE removed per atvi request +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_facePlaneCull; +cvar_t *r_showcluster; +cvar_t *r_nocurves; + +cvar_t *r_allowExtensions; + +cvar_t *r_ext_compressed_textures; +cvar_t *r_ext_gamma_control; +cvar_t *r_ext_multitexture; +cvar_t *r_ext_compiled_vertex_array; +cvar_t *r_ext_texture_env_add; + +//----(SA) added +cvar_t *r_ext_texture_filter_anisotropic; + +cvar_t *r_ext_NV_fog_dist; +cvar_t *r_nv_fogdist_mode; + +cvar_t *r_ext_ATI_pntriangles; +cvar_t *r_ati_truform_tess; // +cvar_t *r_ati_truform_normalmode; // linear/quadratic +cvar_t *r_ati_truform_pointmode; // linear/cubic +//----(SA) end + +cvar_t *r_ati_fsaa_samples; //DAJ valids are 1, 2, 4 + +cvar_t *r_ignoreGLErrors; +cvar_t *r_logFile; + +cvar_t *r_stencilbits; +cvar_t *r_depthbits; +cvar_t *r_colorbits; +cvar_t *r_stereo; +cvar_t *r_primitives; +cvar_t *r_texturebits; + +cvar_t *r_drawBuffer; +cvar_t *r_glDriver; +cvar_t *r_glIgnoreWicked3D; +cvar_t *r_lightmap; +cvar_t *r_vertexLight; +cvar_t *r_uiFullScreen; +cvar_t *r_shadows; +cvar_t *r_portalsky; //----(SA) added +cvar_t *r_flares; +cvar_t *r_mode; +cvar_t *r_nobind; +cvar_t *r_singleShader; +cvar_t *r_roundImagesDown; +cvar_t *r_colorMipLevels; +cvar_t *r_picmip; +cvar_t *r_showtris; +cvar_t *r_showsky; +cvar_t *r_shownormals; +cvar_t *r_finish; +cvar_t *r_clear; +cvar_t *r_swapInterval; +cvar_t *r_textureMode; +cvar_t *r_offsetFactor; +cvar_t *r_offsetUnits; +cvar_t *r_gamma; +cvar_t *r_intensity; +cvar_t *r_lockpvs; +cvar_t *r_noportals; +cvar_t *r_portalOnly; + +cvar_t *r_subdivisions; +cvar_t *r_lodCurveError; + +cvar_t *r_fullscreen; + +cvar_t *r_customwidth; +cvar_t *r_customheight; +cvar_t *r_customaspect; + +cvar_t *r_overBrightBits; +cvar_t *r_mapOverBrightBits; + +cvar_t *r_debugSurface; +cvar_t *r_simpleMipMaps; + +cvar_t *r_showImages; + +cvar_t *r_ambientScale; +cvar_t *r_directedScale; +cvar_t *r_debugLight; +cvar_t *r_debugSort; +cvar_t *r_printShaders; +cvar_t *r_saveFontData; + +// Ridah +cvar_t *r_cache; +cvar_t *r_cacheShaders; +cvar_t *r_cacheModels; +cvar_t *r_compressModels; +cvar_t *r_exportCompressedModels; + +cvar_t *r_cacheGathering; + +cvar_t *r_buildScript; + +cvar_t *r_bonesDebug; +// done. + +// Rafael - wolf fog +cvar_t *r_wolffog; +// done + +cvar_t *r_highQualityVideo; +cvar_t *r_rmse; + +cvar_t *r_maxpolys; +int max_polys; +cvar_t *r_maxpolyverts; +int max_polyverts; + +void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +void ( APIENTRY * qglLockArraysEXT )( GLint, GLint ); +void ( APIENTRY * qglUnlockArraysEXT )( void ); + +//----(SA) added +void ( APIENTRY * qglPNTrianglesiATI )( GLenum pname, GLint param ); +void ( APIENTRY * qglPNTrianglesfATI )( GLenum pname, GLfloat param ); +/* +The tessellation level and normal generation mode are specified with: + + void qglPNTriangles{if}ATI(enum pname, T param) + + If is: + GL_PN_TRIANGLES_NORMAL_MODE_ATI - + must be one of the symbolic constants: + - GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI or + - GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI + which will select linear or quadratic normal interpolation respectively. + GL_PN_TRIANGLES_POINT_MODE_ATI - + must be one of the symbolic constants: + - GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI or + - GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI + which will select linear or cubic interpolation respectively. + GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI - + should be a value specifying the number of evaluation points on each edge. This value must be + greater than 0 and less than or equal to the value given by GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI. + + An INVALID_VALUE error will be generated if the value for is less than zero or greater than the max value. + +Associated 'gets': +Get Value Get Command Type Minimum Value Attribute +--------- ----------- ---- ------------ --------- +PN_TRIANGLES_ATI IsEnabled B False PN Triangles/enable +PN_TRIANGLES_NORMAL_MODE_ATI GetIntegerv Z2 PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI PN Triangles +PN_TRIANGLES_POINT_MODE_ATI GetIntegerv Z2 PN_TRIANGLES_POINT_MODE_CUBIC_ATI PN Triangles +PN_TRIANGLES_TESSELATION_LEVEL_ATI GetIntegerv Z+ 1 PN Triangles +MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI GetIntegerv Z+ 1 - + + + + +*/ +//----(SA) end + + +static void AssertCvarRange( cvar_t *cv, float minVal, float maxVal, qboolean shouldBeIntegral ) { + if ( shouldBeIntegral ) { + if ( ( int ) cv->value != cv->integer ) { + ri.Printf( PRINT_WARNING, "WARNING: cvar '%s' must be integral (%f)\n", cv->name, cv->value ); + ri.Cvar_Set( cv->name, va( "%d", cv->integer ) ); + } + } + + if ( cv->value < minVal ) { + ri.Printf( PRINT_WARNING, "WARNING: cvar '%s' out of range (%f < %f)\n", cv->name, cv->value, minVal ); + ri.Cvar_Set( cv->name, va( "%f", minVal ) ); + } else if ( cv->value > maxVal ) { + ri.Printf( PRINT_WARNING, "WARNING: cvar '%s' out of range (%f > %f)\n", cv->name, cv->value, maxVal ); + ri.Cvar_Set( cv->name, va( "%f", maxVal ) ); + } +} + + +/* +** InitOpenGL +** +** This function is responsible for initializing a valid OpenGL subsystem. This +** is done by calling GLimp_Init (which gives us a working OGL subsystem) then +** setting variables, checking GL constants, and reporting the gfx system config +** to the user. +*/ +static void InitOpenGL( void ) { + char renderer_buffer[1024]; + + // + // initialize OS specific portions of the renderer + // + // GLimp_Init directly or indirectly references the following cvars: + // - r_fullscreen + // - r_glDriver + // - r_mode + // - r_(color|depth|stencil)bits + // - r_ignorehwgamma + // - r_gamma + // + + if ( glConfig.vidWidth == 0 ) { + GLint temp; + + GLimp_Init(); + + strcpy( renderer_buffer, glConfig.renderer_string ); + Q_strlwr( renderer_buffer ); + + // OpenGL driver constants + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp ); + glConfig.maxTextureSize = temp; + + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) { + glConfig.maxTextureSize = 0; + } + } + + // init command buffers and SMP + R_InitCommandBuffers(); + + // print info + GfxInfo_f(); + + // set default state + GL_SetDefaultState(); +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrors( void ) { + int err; + char s[64]; + + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + if ( r_ignoreGLErrors->integer ) { + return; + } + switch ( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + Com_sprintf( s, sizeof( s ), "%i", err ); + break; + } + + ri.Error( ERR_FATAL, "GL_CheckErrors: %s", s ); +} + + +/* +** R_GetModeInfo +*/ +typedef struct vidmode_s +{ + const char *description; + int width, height; + float pixelAspect; // pixel width / height +} vidmode_t; + +vidmode_t r_vidModes[] = +{ + { "Mode 0: 320x240", 320, 240, 1 }, + { "Mode 1: 400x300", 400, 300, 1 }, + { "Mode 2: 512x384", 512, 384, 1 }, + { "Mode 3: 640x480", 640, 480, 1 }, + { "Mode 4: 800x600", 800, 600, 1 }, + { "Mode 5: 960x720", 960, 720, 1 }, + { "Mode 6: 1024x768", 1024, 768, 1 }, + { "Mode 7: 1152x864", 1152, 864, 1 }, + { "Mode 8: 1280x1024", 1280, 1024, 1 }, + { "Mode 9: 1600x1200", 1600, 1200, 1 }, + { "Mode 10: 2048x1536", 2048, 1536, 1 }, + { "Mode 11: 856x480 (wide)",856, 480, 1 } +}; +static int s_numVidModes = ( sizeof( r_vidModes ) / sizeof( r_vidModes[0] ) ); + +qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode ) { + vidmode_t *vm; + + if ( mode < -1 ) { + return qfalse; + } + if ( mode >= s_numVidModes ) { + return qfalse; + } + + if ( mode == -1 ) { + *width = r_customwidth->integer; + *height = r_customheight->integer; + *windowAspect = r_customaspect->value; + return qtrue; + } + + vm = &r_vidModes[mode]; + + *width = vm->width; + *height = vm->height; + *windowAspect = (float)vm->width / ( vm->height * vm->pixelAspect ); + + return qtrue; +} + +/* +** R_ModeList_f +*/ +static void R_ModeList_f( void ) { + int i; + + ri.Printf( PRINT_ALL, "\n" ); + for ( i = 0; i < s_numVidModes; i++ ) + { + ri.Printf( PRINT_ALL, "%s\n", r_vidModes[i].description ); + } + ri.Printf( PRINT_ALL, "\n" ); +} + + +/* +============================================================================== + + SCREEN SHOTS + +============================================================================== +*/ + +/* +================== +R_TakeScreenshot +================== +*/ +void R_TakeScreenshot( int x, int y, int width, int height, char *fileName ) { + byte *buffer; + int i, c, temp; + + buffer = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight * 3 + 18 ); + + memset( buffer, 0, 18 ); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + qglReadPixels( x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer + 18 ); + + // swap rgb to bgr + c = 18 + width * height * 3; + for ( i = 18 ; i < c ; i += 3 ) { + temp = buffer[i]; + buffer[i] = buffer[i + 2]; + buffer[i + 2] = temp; + } + + // gamma correct + if ( ( tr.overbrightBits > 0 ) && glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, glConfig.vidWidth * glConfig.vidHeight * 3 ); + } + + ri.FS_WriteFile( fileName, buffer, c ); + + ri.Hunk_FreeTempMemory( buffer ); +} + +/* +============== +R_TakeScreenshotJPEG +============== +*/ +void R_TakeScreenshotJPEG( int x, int y, int width, int height, char *fileName ) { + byte *buffer; + + buffer = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight * 4 ); + + qglReadPixels( x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer ); + + // gamma correct + if ( ( tr.overbrightBits > 0 ) && glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer, glConfig.vidWidth * glConfig.vidHeight * 4 ); + } + + ri.FS_WriteFile( fileName, buffer, 1 ); // create path + SaveJPG( fileName, 95, glConfig.vidWidth, glConfig.vidHeight, buffer ); + + ri.Hunk_FreeTempMemory( buffer ); +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilename( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.tga" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a * 1000; + b = lastNumber / 100; + lastNumber -= b * 100; + c = lastNumber / 10; + lastNumber -= c * 10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.tga" + , a, b, c, d ); +} + +/* +============== +R_ScreenshotFilenameJPEG +============== +*/ +void R_ScreenshotFilenameJPEG( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.jpg" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a * 1000; + b = lastNumber / 100; + lastNumber -= b * 100; + c = lastNumber / 10; + lastNumber -= c * 10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.jpg" + , a, b, c, d ); +} + +/* +==================== +R_LevelShot + +levelshots are specialized 128*128 thumbnails for +the menu system, sampled down from full screen distorted images +==================== +*/ +void R_LevelShot( void ) { + char checkname[MAX_OSPATH]; + byte *buffer; + byte *source; + byte *src, *dst; + int x, y; + int r, g, b; + float xScale, yScale; + int xx, yy; + + sprintf( checkname, "levelshots/%s.tga", tr.world->baseName ); + + source = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight * 3 ); + + buffer = ri.Hunk_AllocateTempMemory( 128 * 128 * 3 + 18 ); + memset( buffer, 0, 18 ); + buffer[2] = 2; // uncompressed type + buffer[12] = 128; + buffer[14] = 128; + buffer[16] = 24; // pixel size + + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGB, GL_UNSIGNED_BYTE, source ); + + // resample from source + xScale = glConfig.vidWidth / 512.0f; + yScale = glConfig.vidHeight / 384.0f; + for ( y = 0 ; y < 128 ; y++ ) { + for ( x = 0 ; x < 128 ; x++ ) { + r = g = b = 0; + for ( yy = 0 ; yy < 3 ; yy++ ) { + for ( xx = 0 ; xx < 4 ; xx++ ) { + src = source + 3 * ( glConfig.vidWidth * (int)( ( y * 3 + yy ) * yScale ) + (int)( ( x * 4 + xx ) * xScale ) ); + r += src[0]; + g += src[1]; + b += src[2]; + } + } + dst = buffer + 18 + 3 * ( y * 128 + x ); + dst[0] = b / 12; + dst[1] = g / 12; + dst[2] = r / 12; + } + } + + // gamma correct + if ( ( tr.overbrightBits > 0 ) && glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, 128 * 128 * 3 ); + } + + ri.FS_WriteFile( checkname, buffer, 128 * 128 * 3 + 18 ); + + ri.Hunk_FreeTempMemory( buffer ); + ri.Hunk_FreeTempMemory( source ); + + ri.Printf( PRINT_ALL, "Wrote %s\n", checkname ); +} + +/* +================== +R_ScreenShot_f + +screenshot +screenshot [silent] +screenshot [levelshot] +screenshot [filename] + +Doesn't print the pacifier message if there is a second arg +================== +*/ +void R_ScreenShot_f( void ) { + char checkname[MAX_OSPATH]; + int len; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( ri.Cmd_Argv( 1 ), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv( 1 ), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname ); + + len = ri.FS_ReadFile( checkname, NULL ); + if ( len <= 0 ) { + break; // file doesn't exist + } + } + + if ( lastNumber >= 9999 ) { + ri.Printf( PRINT_ALL, "ScreenShot: Couldn't create a file\n" ); + return; + } + + lastNumber++; + } + + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname ); + + if ( !silent ) { + ri.Printf( PRINT_ALL, "Wrote %s\n", checkname ); + } +} + +void R_ScreenShotJPEG_f( void ) { + char checkname[MAX_OSPATH]; + int len; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( ri.Cmd_Argv( 1 ), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv( 1 ), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilenameJPEG( lastNumber, checkname ); + + len = ri.FS_ReadFile( checkname, NULL ); + if ( len <= 0 ) { + break; // file doesn't exist + } + } + + if ( lastNumber == 10000 ) { + ri.Printf( PRINT_ALL, "ScreenShot: Couldn't create a file\n" ); + return; + } + + lastNumber++; + } + + + R_TakeScreenshotJPEG( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname ); + + if ( !silent ) { + ri.Printf( PRINT_ALL, "Wrote %s\n", checkname ); + } +} + +//============================================================================ + +/* +** GL_SetDefaultState +*/ +void GL_SetDefaultState( void ) { + qglClearDepth( 1.0f ); + + qglCullFace( GL_FRONT ); + + qglColor4f( 1,1,1,1 ); + + // initialize downstream texture unit if we're running + // in a multitexture environment + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture( 0 ); + } + + qglEnable( GL_TEXTURE_2D ); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + + qglShadeModel( GL_SMOOTH ); + qglDepthFunc( GL_LEQUAL ); + + // the vertex array is always enabled, but the color and texture + // arrays are enabled and disabled around the compiled vertex array call + qglEnableClientState( GL_VERTEX_ARRAY ); + + // + // make sure our GL state vector is set correctly + // + glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE; + + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + qglDepthMask( GL_TRUE ); + qglDisable( GL_DEPTH_TEST ); + qglEnable( GL_SCISSOR_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_BLEND ); + +//----(SA) added. + // ATI pn_triangles + if ( qglPNTrianglesiATI ) { + int maxtess; + // get max supported tesselation + qglGetIntegerv( GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI, (GLint*)&maxtess ); +#ifdef __MACOS__ + glConfig.ATIMaxTruformTess = 7; +#else + glConfig.ATIMaxTruformTess = maxtess; +#endif + // cap if necessary + if ( r_ati_truform_tess->value > maxtess ) { + ri.Cvar_Set( "r_ati_truform_tess", va( "%d", maxtess ) ); + } + + // set Wolf defaults + qglPNTrianglesiATI( GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI, r_ati_truform_tess->value ); + } + + if ( glConfig.anisotropicAvailable ) { + float maxAnisotropy; + + qglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy ); + glConfig.maxAnisotropy = maxAnisotropy; + + // set when rendering +// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, glConfig.maxAnisotropy); + } + +//----(SA) end +} + + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( void ) { + cvar_t *sys_cpustring = ri.Cvar_Get( "sys_cpustring", "", 0 ); + const char *enablestrings[] = + { + "disabled", + "enabled" + }; + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + ri.Printf( PRINT_ALL, "\nGL_VENDOR: %s\n", glConfig.vendor_string ); + ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string ); + ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string ); + ri.Printf( PRINT_ALL, "GL_EXTENSIONS: %s\n", glConfig.extensions_string ); + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + ri.Printf( PRINT_ALL, "GL_MAX_ACTIVE_TEXTURES_ARB: %d\n", glConfig.maxActiveTextures ); + ri.Printf( PRINT_ALL, "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + ri.Printf( PRINT_ALL, "MODE: %d, %d x %d %s hz:", r_mode->integer, glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] ); + if ( glConfig.displayFrequency ) { + ri.Printf( PRINT_ALL, "%d\n", glConfig.displayFrequency ); + } else + { + ri.Printf( PRINT_ALL, "N/A\n" ); + } + if ( glConfig.deviceSupportsGamma ) { + ri.Printf( PRINT_ALL, "GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits ); + } else + { + ri.Printf( PRINT_ALL, "GAMMA: software w/ %d overbright bits\n", tr.overbrightBits ); + } + ri.Printf( PRINT_ALL, "CPU: %s\n", sys_cpustring->string ); + + // rendering primitives + { + int primitives; + + // default is to use triangles if compiled vertex arrays are present + ri.Printf( PRINT_ALL, "rendering primitives: " ); + primitives = r_primitives->integer; + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + if ( primitives == -1 ) { + ri.Printf( PRINT_ALL, "none\n" ); + } else if ( primitives == 2 ) { + ri.Printf( PRINT_ALL, "single glDrawElements\n" ); + } else if ( primitives == 1 ) { + ri.Printf( PRINT_ALL, "multiple glArrayElement\n" ); + } else if ( primitives == 3 ) { + ri.Printf( PRINT_ALL, "multiple glColor4ubv + glTexCoord2fv + glVertex3fv\n" ); + } + } + + ri.Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string ); + ri.Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer ); + ri.Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer ); + ri.Printf( PRINT_ALL, "multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] ); + ri.Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] ); + ri.Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] ); + ri.Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression != TC_NONE] ); + + ri.Printf( PRINT_ALL, "NV distance fog: %s\n", enablestrings[glConfig.NVFogAvailable != 0] ); + if ( glConfig.NVFogAvailable ) { + ri.Printf( PRINT_ALL, "Fog Mode: %s\n", r_nv_fogdist_mode->string ); + } + + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + ri.Printf( PRINT_ALL, "HACK: using vertex lightmap approximation\n" ); + } + if ( glConfig.hardwareType == GLHW_RAGEPRO ) { + ri.Printf( PRINT_ALL, "HACK: ragePro approximations\n" ); + } + if ( glConfig.hardwareType == GLHW_RIVA128 ) { + ri.Printf( PRINT_ALL, "HACK: riva128 approximations\n" ); + } + if ( glConfig.smpActive ) { + ri.Printf( PRINT_ALL, "Using dual processor acceleration\n" ); + } + if ( r_finish->integer ) { + ri.Printf( PRINT_ALL, "Forcing glFinish\n" ); + } +} + +/* +=============== +R_Register +=============== +*/ +void R_Register( void ) { + // + // latched and archived variables + // + r_glDriver = ri.Cvar_Get( "r_glDriver", OPENGL_DRIVER_NAME, CVAR_ARCHIVE | CVAR_LATCH ); + r_allowExtensions = ri.Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_textures = ri.Cvar_Get( "r_ext_compressed_textures", "1", CVAR_ARCHIVE | CVAR_LATCH ); // (SA) ew, a spelling change I missed from the missionpack + r_ext_gamma_control = ri.Cvar_Get( "r_ext_gamma_control", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multitexture = ri.Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compiled_vertex_array = ri.Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_glIgnoreWicked3D = ri.Cvar_Get( "r_glIgnoreWicked3D", "0", CVAR_ARCHIVE | CVAR_LATCH ); + +//----(SA) added + r_ext_ATI_pntriangles = ri.Cvar_Get( "r_ext_ATI_pntriangles", "0", CVAR_ARCHIVE | CVAR_LATCH ); //----(SA) default to '0' + r_ati_truform_tess = ri.Cvar_Get( "r_ati_truform_tess", "1", CVAR_ARCHIVE ); + r_ati_truform_normalmode = ri.Cvar_Get( "r_ati_truform_normalmode", "GL_PN_TRIANGLES_NORMAL_MODE_LINEAR", CVAR_ARCHIVE ); + r_ati_truform_pointmode = ri.Cvar_Get( "r_ati_truform_pointmode", "GL_PN_TRIANGLES_POINT_MODE_LINEAR", CVAR_ARCHIVE ); + + r_ati_fsaa_samples = ri.Cvar_Get( "r_ati_fsaa_samples", "1", CVAR_ARCHIVE ); //DAJ valids are 1, 2, 4 + + r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic", "0", CVAR_ARCHIVE ); + + r_ext_NV_fog_dist = ri.Cvar_Get( "r_ext_NV_fog_dist", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_nv_fogdist_mode = ri.Cvar_Get( "r_nv_fogdist_mode", "GL_EYE_RADIAL_NV", CVAR_ARCHIVE ); // default to 'looking good' +//----(SA) end + +#ifdef __linux__ // broken on linux + r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#else + r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH ); +#endif + + r_picmip = ri.Cvar_Get( "r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH ); //----(SA) mod for DM and DK for id build. was "1" // JPW NERVE pushed back to 1 + r_roundImagesDown = ri.Cvar_Get( "r_roundImagesDown", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_rmse = ri.Cvar_Get( "r_rmse", "0.0", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorMipLevels = ri.Cvar_Get( "r_colorMipLevels", "0", CVAR_LATCH ); + AssertCvarRange( r_picmip, 0, 16, qtrue ); + r_detailTextures = ri.Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebits = ri.Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorbits = ri.Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_stereo = ri.Cvar_Get( "r_stereo", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#ifdef __linux__ + r_stencilbits = ri.Cvar_Get( "r_stencilbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#else + r_stencilbits = ri.Cvar_Get( "r_stencilbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#endif + r_depthbits = ri.Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_overBrightBits = ri.Cvar_Get( "r_overBrightBits", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ignorehwgamma = ri.Cvar_Get( "r_ignorehwgamma", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_mode = ri.Cvar_Get( "r_mode", "3", CVAR_ARCHIVE | CVAR_LATCH ); + r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_customwidth = ri.Cvar_Get( "r_customwidth", "1600", CVAR_ARCHIVE | CVAR_LATCH ); + r_customheight = ri.Cvar_Get( "r_customheight", "1024", CVAR_ARCHIVE | CVAR_LATCH ); + r_customaspect = ri.Cvar_Get( "r_customaspect", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_simpleMipMaps = ri.Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_vertexLight = ri.Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_uiFullScreen = ri.Cvar_Get( "r_uifullscreen", "0", 0 ); + r_subdivisions = ri.Cvar_Get( "r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH ); +#ifdef MACOS_X + // Default to using SMP on Mac OS X if we have multiple processors + r_smp = ri.Cvar_Get( "r_smp", Sys_ProcessorCount() > 1 ? "1" : "0", CVAR_ARCHIVE | CVAR_LATCH ); +#else + r_smp = ri.Cvar_Get( "r_smp", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#endif + r_ignoreFastPath = ri.Cvar_Get( "r_ignoreFastPath", "1", CVAR_ARCHIVE | CVAR_LATCH ); +#if MAC_STVEF_HM || MAC_WOLF2_MP + r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic", "0", CVAR_ARCHIVE | CVAR_LATCH ); //DAJ + r_ati_fsaa_samples = ri.Cvar_Get( "r_ati_fsaa_samples", "1", CVAR_ARCHIVE ); //DAJ valids are 1, 2, 4 +#endif + // + // temporary latched variables that can only change over a restart + // + r_displayRefresh = ri.Cvar_Get( "r_displayRefresh", "0", CVAR_LATCH ); + AssertCvarRange( r_displayRefresh, 0, 200, qtrue ); +// r_fullbright = ri.Cvar_Get ("r_fullbright", "0", CVAR_LATCH|CVAR_CHEAT ); // JPW NERVE removed per atvi request + r_mapOverBrightBits = ri.Cvar_Get( "r_mapOverBrightBits", "2", CVAR_LATCH ); + r_intensity = ri.Cvar_Get( "r_intensity", "1", CVAR_LATCH ); + r_singleShader = ri.Cvar_Get( "r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH ); + + // + // archived variables that can change at any time + // + r_lodCurveError = ri.Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE ); + r_lodbias = ri.Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE ); + r_flares = ri.Cvar_Get( "r_flares", "1", CVAR_ARCHIVE ); + r_znear = ri.Cvar_Get( "r_znear", "4", CVAR_CHEAT ); + AssertCvarRange( r_znear, 0.001f, 200, qtrue ); +//----(SA) added + r_zfar = ri.Cvar_Get( "r_zfar", "0", CVAR_CHEAT ); +//----(SA) end + r_ignoreGLErrors = ri.Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE ); + r_fastsky = ri.Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE ); + r_inGameVideo = ri.Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE ); + r_drawSun = ri.Cvar_Get( "r_drawSun", "1", CVAR_ARCHIVE ); + r_dynamiclight = ri.Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE ); + r_dlightBacks = ri.Cvar_Get( "r_dlightBacks", "1", CVAR_ARCHIVE ); + r_finish = ri.Cvar_Get( "r_finish", "0", CVAR_ARCHIVE ); + r_textureMode = ri.Cvar_Get( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE ); + r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0", CVAR_ARCHIVE ); +#ifdef __MACOS__ + r_gamma = ri.Cvar_Get( "r_gamma", "1.2", CVAR_ARCHIVE ); +#else + r_gamma = ri.Cvar_Get( "r_gamma", "1.3", CVAR_ARCHIVE ); +#endif + r_facePlaneCull = ri.Cvar_Get( "r_facePlaneCull", "1", CVAR_ARCHIVE ); + + r_railWidth = ri.Cvar_Get( "r_railWidth", "16", CVAR_ARCHIVE ); + r_railCoreWidth = ri.Cvar_Get( "r_railCoreWidth", "1", CVAR_ARCHIVE ); + r_railSegmentLength = ri.Cvar_Get( "r_railSegmentLength", "32", CVAR_ARCHIVE ); + + r_primitives = ri.Cvar_Get( "r_primitives", "0", CVAR_ARCHIVE ); + + r_ambientScale = ri.Cvar_Get( "r_ambientScale", "0.5", CVAR_CHEAT ); + r_directedScale = ri.Cvar_Get( "r_directedScale", "1", CVAR_CHEAT ); + + // + // temporary variables that can change at any time + // + r_showImages = ri.Cvar_Get( "r_showImages", "0", CVAR_TEMP ); + + r_debugLight = ri.Cvar_Get( "r_debuglight", "0", CVAR_TEMP ); + r_debugSort = ri.Cvar_Get( "r_debugSort", "0", CVAR_CHEAT ); + r_printShaders = ri.Cvar_Get( "r_printShaders", "0", 0 ); + r_saveFontData = ri.Cvar_Get( "r_saveFontData", "0", 0 ); + + // Ridah + // TTimo show_bug.cgi?id=440 + // with r_cache enabled, non-win32 OSes were leaking 24Mb per R_Init.. + r_cache = ri.Cvar_Get( "r_cache", "1", CVAR_LATCH ); // leaving it as this for backwards compability. but it caches models and shaders also + // TTimo show_bug.cgi?id=570 + r_cacheShaders = ri.Cvar_Get( "r_cacheShaders", "1", CVAR_LATCH ); + + r_cacheModels = ri.Cvar_Get( "r_cacheModels", "1", CVAR_LATCH ); + r_compressModels = ri.Cvar_Get( "r_compressModels", "0", 0 ); // converts MD3 -> MDC at run-time + r_exportCompressedModels = ri.Cvar_Get( "r_exportCompressedModels", "0", 0 ); // saves compressed models + r_cacheGathering = ri.Cvar_Get( "cl_cacheGathering", "0", 0 ); + r_buildScript = ri.Cvar_Get( "com_buildscript", "0", 0 ); + r_bonesDebug = ri.Cvar_Get( "r_bonesDebug", "0", CVAR_CHEAT ); + // done. + + // Rafael - wolf fog + r_wolffog = ri.Cvar_Get( "r_wolffog", "1", CVAR_CHEAT ); // JPW NERVE cheat protected per id request + // done + + r_nocurves = ri.Cvar_Get( "r_nocurves", "0", CVAR_CHEAT ); + r_drawworld = ri.Cvar_Get( "r_drawworld", "1", CVAR_CHEAT ); + r_lightmap = ri.Cvar_Get( "r_lightmap", "0", CVAR_CHEAT ); // DHM - NERVE :: cheat protect + r_portalOnly = ri.Cvar_Get( "r_portalOnly", "0", CVAR_CHEAT ); + + r_flareSize = ri.Cvar_Get( "r_flareSize", "40", CVAR_CHEAT ); + ri.Cvar_Set( "r_flareFade", "5" ); // to force this when people already have "7" in their config + r_flareFade = ri.Cvar_Get( "r_flareFade", "5", CVAR_CHEAT ); + + r_showSmp = ri.Cvar_Get( "r_showSmp", "0", CVAR_CHEAT ); + r_skipBackEnd = ri.Cvar_Get( "r_skipBackEnd", "0", CVAR_CHEAT ); + + r_measureOverdraw = ri.Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT ); + r_lodscale = ri.Cvar_Get( "r_lodscale", "5", CVAR_CHEAT ); + r_norefresh = ri.Cvar_Get( "r_norefresh", "0", CVAR_CHEAT ); + r_drawentities = ri.Cvar_Get( "r_drawentities", "1", CVAR_CHEAT ); + r_ignore = ri.Cvar_Get( "r_ignore", "1", CVAR_CHEAT ); + r_nocull = ri.Cvar_Get( "r_nocull", "0", CVAR_CHEAT ); + r_novis = ri.Cvar_Get( "r_novis", "0", CVAR_CHEAT ); + r_showcluster = ri.Cvar_Get( "r_showcluster", "0", CVAR_CHEAT ); + r_speeds = ri.Cvar_Get( "r_speeds", "0", CVAR_CHEAT ); + r_verbose = ri.Cvar_Get( "r_verbose", "0", CVAR_CHEAT ); + r_logFile = ri.Cvar_Get( "r_logFile", "0", CVAR_CHEAT ); + r_debugSurface = ri.Cvar_Get( "r_debugSurface", "0", CVAR_CHEAT ); + r_nobind = ri.Cvar_Get( "r_nobind", "0", CVAR_CHEAT ); + r_showtris = ri.Cvar_Get( "r_showtris", "0", CVAR_CHEAT ); + r_showsky = ri.Cvar_Get( "r_showsky", "0", CVAR_CHEAT ); + r_shownormals = ri.Cvar_Get( "r_shownormals", "0", CVAR_CHEAT ); + r_clear = ri.Cvar_Get( "r_clear", "0", CVAR_CHEAT ); + r_offsetFactor = ri.Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT ); + r_offsetUnits = ri.Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT ); + r_drawBuffer = ri.Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT ); + r_lockpvs = ri.Cvar_Get( "r_lockpvs", "0", CVAR_CHEAT ); + r_noportals = ri.Cvar_Get( "r_noportals", "0", CVAR_CHEAT ); + r_shadows = ri.Cvar_Get( "cg_shadows", "1", 0 ); + r_shadows = ri.Cvar_Get( "cg_shadows", "1", 0 ); + r_portalsky = ri.Cvar_Get( "cg_skybox", "1", 0 ); + + r_maxpolys = ri.Cvar_Get( "r_maxpolys", va( "%d", MAX_POLYS ), 0 ); + r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va( "%d", MAX_POLYVERTS ), 0 ); + + r_highQualityVideo = ri.Cvar_Get( "r_highQualityVideo", "1", CVAR_ARCHIVE ); + // make sure all the commands added here are also + // removed in R_Shutdown + ri.Cmd_AddCommand( "imagelist", R_ImageList_f ); + ri.Cmd_AddCommand( "shaderlist", R_ShaderList_f ); + ri.Cmd_AddCommand( "skinlist", R_SkinList_f ); + ri.Cmd_AddCommand( "modellist", R_Modellist_f ); + ri.Cmd_AddCommand( "modelist", R_ModeList_f ); + ri.Cmd_AddCommand( "screenshot", R_ScreenShot_f ); + ri.Cmd_AddCommand( "screenshotJPEG", R_ScreenShotJPEG_f ); + ri.Cmd_AddCommand( "gfxinfo", GfxInfo_f ); + ri.Cmd_AddCommand( "taginfo", R_TagInfo_f ); + + // Ridah + { + void R_CropImages_f( void ); + ri.Cmd_AddCommand( "cropimages", R_CropImages_f ); + } + // done. +} + +/* +=============== +R_Init +=============== +*/ +void R_Init( void ) { + int err; + int i; + + ri.Printf( PRINT_ALL, "----- R_Init -----\n" ); + + // clear all our internal state + memset( &tr, 0, sizeof( tr ) ); + memset( &backEnd, 0, sizeof( backEnd ) ); + memset( &tess, 0, sizeof( tess ) ); + + Swap_Init(); + + if ( (int)tess.xyz & 15 ) { + Com_Printf( "WARNING: tess.xyz not 16 byte aligned\n" ); + } + memset( tess.constantColor255, 255, sizeof( tess.constantColor255 ) ); + + // + // init function tables + // + for ( i = 0; i < FUNCTABLE_SIZE; i++ ) + { + tr.sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) ); + tr.squareTable[i] = ( i < FUNCTABLE_SIZE / 2 ) ? 1.0f : -1.0f; + tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE; + tr.inverseSawToothTable[i] = 1.0f - tr.sawToothTable[i]; + + if ( i < FUNCTABLE_SIZE / 2 ) { + if ( i < FUNCTABLE_SIZE / 4 ) { + tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 ); + } else + { + tr.triangleTable[i] = 1.0f - tr.triangleTable[i - FUNCTABLE_SIZE / 4]; + } + } else + { + tr.triangleTable[i] = -tr.triangleTable[i - FUNCTABLE_SIZE / 2]; + } + } + + // Ridah, init the virtual memory + R_Hunk_Begin(); + + R_InitFogTable(); + + R_NoiseInit(); + + R_Register(); + + max_polys = r_maxpolys->integer; + if ( max_polys < MAX_POLYS ) { + max_polys = MAX_POLYS; + } + + max_polyverts = r_maxpolyverts->integer; + if ( max_polyverts < MAX_POLYVERTS ) { + max_polyverts = MAX_POLYVERTS; + } + +// backEndData[0] = ri.Hunk_Alloc( sizeof( *backEndData[0] ), h_low ); + backEndData[0] = ri.Hunk_Alloc( sizeof( *backEndData[0] ) + sizeof( srfPoly_t ) * max_polys + sizeof( polyVert_t ) * max_polyverts, h_low ); + + if ( r_smp->integer ) { +// backEndData[1] = ri.Hunk_Alloc( sizeof( *backEndData[1] ), h_low ); + backEndData[1] = ri.Hunk_Alloc( sizeof( *backEndData[1] ) + sizeof( srfPoly_t ) * max_polys + sizeof( polyVert_t ) * max_polyverts, h_low ); + } else { + backEndData[1] = NULL; + } + R_ToggleSmpFrame(); + + InitOpenGL(); + + R_InitImages(); + + R_InitShaders(); + + R_InitSkins(); + + R_ModelInit(); + + R_InitFreeType(); + + err = qglGetError(); + if ( err != GL_NO_ERROR ) { + ri.Printf( PRINT_ALL, "glGetError() = 0x%x\n", err ); + } + + ri.Printf( PRINT_ALL, "----- finished R_Init -----\n" ); +} + +/* +=============== +RE_Shutdown +=============== +*/ +void RE_Shutdown( qboolean destroyWindow ) { + + ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow ); + + ri.Cmd_RemoveCommand( "modellist" ); + ri.Cmd_RemoveCommand( "screenshotJPEG" ); + ri.Cmd_RemoveCommand( "screenshot" ); + ri.Cmd_RemoveCommand( "imagelist" ); + ri.Cmd_RemoveCommand( "shaderlist" ); + ri.Cmd_RemoveCommand( "skinlist" ); + ri.Cmd_RemoveCommand( "gfxinfo" ); + ri.Cmd_RemoveCommand( "modelist" ); + ri.Cmd_RemoveCommand( "shaderstate" ); + ri.Cmd_RemoveCommand( "taginfo" ); + + // Ridah + ri.Cmd_RemoveCommand( "cropimages" ); + // done. + + R_ShutdownCommandBuffers(); + + // Ridah, keep a backup of the current images if possible + // clean out any remaining unused media from the last backup + R_PurgeShaders( 9999999 ); + R_PurgeBackupImages( 9999999 ); + R_PurgeModels( 9999999 ); + + if ( r_cache->integer ) { + if ( tr.registered ) { + if ( destroyWindow ) { + R_SyncRenderThread(); + R_ShutdownCommandBuffers(); + R_DeleteTextures(); + } else { + // backup the current media + R_ShutdownCommandBuffers(); + + R_BackupModels(); + R_BackupShaders(); + R_BackupImages(); + } + } + } else if ( tr.registered ) { + R_SyncRenderThread(); + R_ShutdownCommandBuffers(); + R_DeleteTextures(); + } + + R_DoneFreeType(); + + // shut down platform specific OpenGL stuff + if ( destroyWindow ) { + GLimp_Shutdown(); + + // Ridah, release the virtual memory + R_Hunk_End(); + R_FreeImageBuffer(); + ri.Tag_Free(); // wipe all render alloc'd zone memory + } + + tr.registered = qfalse; +} + + +/* +============= +RE_EndRegistration + +Touch all images to make sure they are resident +============= +*/ +void RE_EndRegistration( void ) { + R_SyncRenderThread(); + if ( !Sys_LowPhysicalMemory() ) { + RB_ShowImages(); + } +} + + +/* +@@@@@@@@@@@@@@@@@@@@@ +GetRefAPI + +@@@@@@@@@@@@@@@@@@@@@ +*/ +refexport_t *GetRefAPI( int apiVersion, refimport_t *rimp ) { + static refexport_t re; + + ri = *rimp; + + memset( &re, 0, sizeof( re ) ); + + if ( apiVersion != REF_API_VERSION ) { + ri.Printf( PRINT_ALL, "Mismatched REF_API_VERSION: expected %i, got %i\n", + REF_API_VERSION, apiVersion ); + return NULL; + } + + // the RE_ functions are Renderer Entry points + + re.Shutdown = RE_Shutdown; + + re.BeginRegistration = RE_BeginRegistration; + re.RegisterModel = RE_RegisterModel; + re.RegisterSkin = RE_RegisterSkin; +//----(SA) added + re.GetSkinModel = RE_GetSkinModel; + re.GetShaderFromModel = RE_GetShaderFromModel; +//----(SA) end + re.RegisterShader = RE_RegisterShader; + re.RegisterShaderNoMip = RE_RegisterShaderNoMip; + re.LoadWorld = RE_LoadWorldMap; + re.SetWorldVisData = RE_SetWorldVisData; + re.EndRegistration = RE_EndRegistration; + + re.BeginFrame = RE_BeginFrame; + re.EndFrame = RE_EndFrame; + + re.MarkFragments = R_MarkFragments; + re.LerpTag = R_LerpTag; + re.ModelBounds = R_ModelBounds; + + re.ClearScene = RE_ClearScene; + re.AddRefEntityToScene = RE_AddRefEntityToScene; + re.AddPolyToScene = RE_AddPolyToScene; + // Ridah + re.AddPolysToScene = RE_AddPolysToScene; + // done. + re.AddLightToScene = RE_AddLightToScene; +//----(SA) + re.AddCoronaToScene = RE_AddCoronaToScene; + re.SetFog = R_SetFog; +//----(SA) + re.RenderScene = RE_RenderScene; + + re.SetColor = RE_SetColor; + re.DrawStretchPic = RE_StretchPic; + re.DrawRotatedPic = RE_RotatedPic; // NERVE - SMF + re.DrawStretchPicGradient = RE_StretchPicGradient; + re.DrawStretchRaw = RE_StretchRaw; + re.UploadCinematic = RE_UploadCinematic; + re.RegisterFont = RE_RegisterFont; + re.RemapShader = R_RemapShader; + re.GetEntityToken = R_GetEntityToken; + + return &re; +} + diff --git a/src/renderer/tr_light.c b/src/renderer/tr_light.c new file mode 100644 index 0000000..98c8bd4 --- /dev/null +++ b/src/renderer/tr_light.c @@ -0,0 +1,424 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_light.c + +#include "tr_local.h" + +#define DLIGHT_AT_RADIUS 16 +// at the edge of a dlight's influence, this amount of light will be added + +#define DLIGHT_MINIMUM_RADIUS 16 +// never calculate a range less than this to prevent huge light numbers + + +/* +=============== +R_TransformDlights + +Transforms the origins of an array of dlights. +Used by both the front end (for DlightBmodel) and +the back end (before doing the lighting calculation) +=============== +*/ +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or ) { + int i; + vec3_t temp; + + for ( i = 0 ; i < count ; i++, dl++ ) { + VectorSubtract( dl->origin, or->origin, temp ); + dl->transformed[0] = DotProduct( temp, or->axis[0] ); + dl->transformed[1] = DotProduct( temp, or->axis[1] ); + dl->transformed[2] = DotProduct( temp, or->axis[2] ); + } +} + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +void R_DlightBmodel( bmodel_t *bmodel ) { + int i, j; + dlight_t *dl; + int mask; + msurface_t *surf; + + // transform all the lights + R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.or ); + + mask = 0; + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + dl = &tr.refdef.dlights[i]; + + // see if the point is close enough to the bounds to matter + for ( j = 0 ; j < 3 ; j++ ) { + if ( dl->transformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + if ( j < 3 ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + + // RF, this is why some dlights wouldn't light up bmodels + + //tr.currentEntity->needDlights = (mask != 0); + + // (SA) isn't this dangerous to do to an enumerated type? (setting it to an int) + // meaning, shouldn't ->needDlights be changed to an int rather than a qbool? + + tr.currentEntity->needDlights = mask; + + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = bmodel->firstSurface + i; + + if ( *surf->data == SF_FACE ) { + ( (srfSurfaceFace_t *)surf->data )->dlightBits[ tr.smpFrame ] = mask; + } else if ( *surf->data == SF_GRID ) { + ( (srfGridMesh_t *)surf->data )->dlightBits[ tr.smpFrame ] = mask; + } else if ( *surf->data == SF_TRIANGLES ) { + ( (srfTriangles_t *)surf->data )->dlightBits[ tr.smpFrame ] = mask; + } + } +} + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +extern cvar_t *r_ambientScale; +extern cvar_t *r_directedScale; +extern cvar_t *r_debugLight; + +/* +================= +R_SetupEntityLightingGrid + +================= +*/ +static void R_SetupEntityLightingGrid( trRefEntity_t *ent ) { + vec3_t lightOrigin; + int pos[3]; + int i, j; + byte *gridData; + float frac[3]; + int gridStep[3]; + vec3_t direction; + float totalFactor; + + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + VectorSubtract( lightOrigin, tr.world->lightGridOrigin, lightOrigin ); + for ( i = 0 ; i < 3 ; i++ ) { + float v; + + v = lightOrigin[i] * tr.world->lightGridInverseSize[i]; + pos[i] = floor( v ); + frac[i] = v - pos[i]; + if ( pos[i] < 0 ) { + pos[i] = 0; + } else if ( pos[i] >= tr.world->lightGridBounds[i] - 1 ) { + pos[i] = tr.world->lightGridBounds[i] - 1; + } + } + + VectorClear( ent->ambientLight ); + VectorClear( ent->directedLight ); + VectorClear( direction ); + + assert( tr.world->lightGridData ); // bk010103 - NULL with -nolight maps + + // trilerp the light value + gridStep[0] = 8; + gridStep[1] = 8 * tr.world->lightGridBounds[0]; + gridStep[2] = 8 * tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1]; + gridData = tr.world->lightGridData + pos[0] * gridStep[0] + + pos[1] * gridStep[1] + pos[2] * gridStep[2]; + + totalFactor = 0; + for ( i = 0 ; i < 8 ; i++ ) { + float factor; + byte *data; + int lat, lng; + vec3_t normal; + + factor = 1.0; + data = gridData; + for ( j = 0 ; j < 3 ; j++ ) { + if ( i & ( 1 << j ) ) { + factor *= frac[j]; + data += gridStep[j]; + } else { + factor *= ( 1.0f - frac[j] ); + } + } + + if ( !( data[0] + data[1] + data[2] ) ) { + continue; // ignore samples in walls + } + totalFactor += factor; + + ent->ambientLight[0] += factor * data[0]; + ent->ambientLight[1] += factor * data[1]; + ent->ambientLight[2] += factor * data[2]; + + ent->directedLight[0] += factor * data[3]; + ent->directedLight[1] += factor * data[4]; + ent->directedLight[2] += factor * data[5]; + + lat = data[7]; + lng = data[6]; + lat *= ( FUNCTABLE_SIZE / 256 ); + lng *= ( FUNCTABLE_SIZE / 256 ); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + normal[0] = tr.sinTable[( lat + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK] * tr.sinTable[lng]; + normal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + normal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK]; + + VectorMA( direction, factor, normal, direction ); + } + + if ( totalFactor > 0 && totalFactor < 0.99 ) { + totalFactor = 1.0f / totalFactor; + VectorScale( ent->ambientLight, totalFactor, ent->ambientLight ); + VectorScale( ent->directedLight, totalFactor, ent->directedLight ); + } + + VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight ); + VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight ); + +//----(SA) added + // cheats? check for single player? + if ( tr.lightGridMulDirected ) { + VectorScale( ent->directedLight, tr.lightGridMulDirected, ent->directedLight ); + } + if ( tr.lightGridMulAmbient ) { + VectorScale( ent->ambientLight, tr.lightGridMulAmbient, ent->ambientLight ); + } +//----(SA) end + + VectorNormalize2( direction, ent->lightDir ); +} + + +/* +=============== +LogLight +=============== +*/ +static void LogLight( trRefEntity_t *ent ) { + int max1, max2; + + if ( !( ent->e.renderfx & RF_FIRST_PERSON ) ) { + return; + } + + max1 = ent->ambientLight[0]; + if ( ent->ambientLight[1] > max1 ) { + max1 = ent->ambientLight[1]; + } else if ( ent->ambientLight[2] > max1 ) { + max1 = ent->ambientLight[2]; + } + + max2 = ent->directedLight[0]; + if ( ent->directedLight[1] > max2 ) { + max2 = ent->directedLight[1]; + } else if ( ent->directedLight[2] > max2 ) { + max2 = ent->directedLight[2]; + } + + ri.Printf( PRINT_ALL, "amb:%i dir:%i\n", max1, max2 ); +} + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { + int i; + dlight_t *dl; + float power; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t lightOrigin; +// qboolean highlighted = qfalse; // TTimo: unused + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = qtrue; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !( refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + VectorCopy( tr.sunDirection, ent->lightDir ); + } + + if ( ent->e.hilightIntensity ) { + // level of intensity was set because the item was looked at + ent->ambientLight[0] += tr.identityLight * 128 * ent->e.hilightIntensity; + ent->ambientLight[1] += tr.identityLight * 128 * ent->e.hilightIntensity; + ent->ambientLight[2] += tr.identityLight * 128 * ent->e.hilightIntensity; + } else if ( ent->e.renderfx & RF_MINLIGHT ) { + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + } + + + if ( ent->e.entityNum < MAX_CLIENTS && ( refdef->rdflags & RDF_SNOOPERVIEW ) ) { + VectorSet( ent->ambientLight, 245, 245, 245 ); // allow a little room for flicker from directed light + } + + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + + for ( i = 0 ; i < refdef->num_dlights ; i++ ) { + dl = &refdef->dlights[i]; + + if ( dl->dlshader ) { //----(SA) if the dlight has a diff shader specified, you don't know what it does, so don't let it affect entities lighting + continue; + } + + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + + d = power / ( d * d ); + + VectorMA( ent->directedLight, d, dl->color, ent->directedLight ); + VectorMA( lightDir, d, dir, lightDir ); + } + + // clamp ambient + for ( i = 0 ; i < 3 ; i++ ) { + if ( ent->ambientLight[i] > tr.identityLightByte ) { + ent->ambientLight[i] = tr.identityLightByte; + } + } + + if ( r_debugLight->integer ) { + LogLight( ent ); + } + + // save out the byte packet version + ( (byte *)&ent->ambientLightInt )[0] = myftol( ent->ambientLight[0] ); + ( (byte *)&ent->ambientLightInt )[1] = myftol( ent->ambientLight[1] ); + ( (byte *)&ent->ambientLightInt )[2] = myftol( ent->ambientLight[2] ); + ( (byte *)&ent->ambientLightInt )[3] = 0xff; + + // transform the direction to local space + VectorNormalize( lightDir ); + ent->lightDir[0] = DotProduct( lightDir, ent->e.axis[0] ); + ent->lightDir[1] = DotProduct( lightDir, ent->e.axis[1] ); + ent->lightDir[2] = DotProduct( lightDir, ent->e.axis[2] ); +} + +/* +================= +R_LightForPoint +================= +*/ +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { + trRefEntity_t ent; + + // bk010103 - this segfaults with -nolight maps + if ( tr.world->lightGridData == NULL ) { + return qfalse; + } + + Com_Memset( &ent, 0, sizeof( ent ) ); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent ); + VectorCopy( ent.ambientLight, ambientLight ); + VectorCopy( ent.directedLight, directedLight ); + VectorCopy( ent.lightDir, lightDir ); + + return qtrue; +} diff --git a/src/renderer/tr_local.h b/src/renderer/tr_local.h new file mode 100644 index 0000000..60219fe --- /dev/null +++ b/src/renderer/tr_local.h @@ -0,0 +1,1831 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + +#ifndef TR_LOCAL_H +#define TR_LOCAL_H + +#include "../game/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" +#include "tr_public.h" +#include "qgl.h" + +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef unsigned int glIndex_t; + +// fast float to int conversion +#if id386 && !( ( defined __linux__ || defined __FreeBSD__ ) && ( defined __i386__ ) ) // rb010123 +long myftol( float f ); +#else +#define myftol( x ) ( (int)( x ) ) +#endif + + +// everything that is needed by the backend needs +// to be double buffered to allow it to run in +// parallel on a dual cpu machine +#define SMP_FRAMES 2 + +#define MAX_SHADERS 8192 + +#define MAX_SHADER_STATES 2048 +#define MAX_STATES_PER_SHADER 32 +#define MAX_STATE_NAME 32 + +// can't be increased without changing bit packing for drawsurfs + + +// a trRefEntity_t has all the information passed in by +// the client game, as well as some locally derived info +typedef struct { + refEntity_t e; + + float axisLength; // compensate for non-normalized axis + + qboolean needDlights; // true for bmodels that touch a dlight + qboolean lightingCalculated; + vec3_t lightDir; // normalized direction towards light + vec3_t ambientLight; // color normalized to 0-255 + int ambientLightInt; // 32 bit rgba packed + vec3_t directedLight; + float brightness; +} trRefEntity_t; + +typedef struct { + vec3_t origin; // in world coordinates + vec3_t axis[3]; // orientation in world + vec3_t viewOrigin; // viewParms->or.origin in local coordinates + float modelMatrix[16]; +} orientationr_t; + +typedef struct image_s { + char imgName[MAX_QPATH]; // game path, including extension + int width, height; // source image + int uploadWidth, uploadHeight; // after power of two and picmip but not including clamp to MAX_TEXTURE_SIZE + GLuint texnum; // gl texture binding + + int frameUsed; // for texture usage in frame statistics + + int internalFormat; + int TMU; // only needed for voodoo2 + + qboolean mipmap; + qboolean allowPicmip; + int wrapClampMode; // GL_CLAMP or GL_REPEAT + + int hash; // for fast building of the backupHash + + struct image_s* next; +} image_t; + +//=============================================================================== + +typedef enum { + SS_BAD, + SS_PORTAL, // mirrors, portals, viewscreens + SS_ENVIRONMENT, // sky box + SS_OPAQUE, // opaque + + SS_DECAL, // scorch marks, etc. + SS_SEE_THROUGH, // ladders, grates, grills that may have small blended edges + // in addition to alpha test + SS_BANNER, + + SS_FOG, + + SS_UNDERWATER, // for items that should be drawn in front of the water plane + + SS_BLEND0, // regular transparency and filters + SS_BLEND1, // generally only used for additive type effects + SS_BLEND2, + SS_BLEND3, + + SS_BLEND6, + SS_STENCIL_SHADOW, + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST // blood blobs +} shaderSort_t; + + +#define MAX_SHADER_STAGES 8 + +typedef enum { + GF_NONE, + + GF_SIN, + GF_SQUARE, + GF_TRIANGLE, + GF_SAWTOOTH, + GF_INVERSE_SAWTOOTH, + + GF_NOISE + +} genFunc_t; + + +typedef enum { + DEFORM_NONE, + DEFORM_WAVE, + DEFORM_NORMALS, + DEFORM_BULGE, + DEFORM_MOVE, + DEFORM_PROJECTION_SHADOW, + DEFORM_AUTOSPRITE, + DEFORM_AUTOSPRITE2, + DEFORM_TEXT0, + DEFORM_TEXT1, + DEFORM_TEXT2, + DEFORM_TEXT3, + DEFORM_TEXT4, + DEFORM_TEXT5, + DEFORM_TEXT6, + DEFORM_TEXT7 +} deform_t; + +typedef enum { + AGEN_IDENTITY, + AGEN_SKIP, + AGEN_ENTITY, + AGEN_ONE_MINUS_ENTITY, + AGEN_NORMALZFADE, // Ridah + AGEN_VERTEX, + AGEN_ONE_MINUS_VERTEX, + AGEN_LIGHTING_SPECULAR, + AGEN_WAVEFORM, + AGEN_PORTAL, + AGEN_CONST +} alphaGen_t; + +typedef enum { + CGEN_BAD, + CGEN_IDENTITY_LIGHTING, // tr.identityLight + CGEN_IDENTITY, // always (1,1,1,1) + CGEN_ENTITY, // grabbed from entity's modulate field + CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate + CGEN_EXACT_VERTEX, // tess.vertexColors + CGEN_VERTEX, // tess.vertexColors * tr.identityLight + CGEN_ONE_MINUS_VERTEX, + CGEN_WAVEFORM, // programmatically generated + CGEN_LIGHTING_DIFFUSE, + CGEN_FOG, // standard fog + CGEN_CONST // fixed color +} colorGen_t; + +typedef enum { + TCGEN_BAD, + TCGEN_IDENTITY, // clear to 0,0 + TCGEN_LIGHTMAP, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FIRERISEENV_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR // S and T from world coordinates +} texCoordGen_t; + +typedef enum { + ACFF_NONE, + ACFF_MODULATE_RGB, + ACFF_MODULATE_RGBA, + ACFF_MODULATE_ALPHA +} acff_t; + +typedef struct { + genFunc_t func; + + float base; + float amplitude; + float phase; + float frequency; +} waveForm_t; + +#define TR_MAX_TEXMODS 4 + +typedef enum { + TMOD_NONE, + TMOD_TRANSFORM, + TMOD_TURBULENT, + TMOD_SCROLL, + TMOD_SCALE, + TMOD_STRETCH, + TMOD_ROTATE, + TMOD_ENTITY_TRANSLATE, + TMOD_SWAP +} texMod_t; + +#define MAX_SHADER_DEFORMS 3 +typedef struct { + deform_t deformation; // vertex coordinate modification type + + vec3_t moveVector; + waveForm_t deformationWave; + float deformationSpread; + + float bulgeWidth; + float bulgeHeight; + float bulgeSpeed; +} deformStage_t; + + +typedef struct { + texMod_t type; + + // used for TMOD_TURBULENT and TMOD_STRETCH + waveForm_t wave; + + // used for TMOD_TRANSFORM + float matrix[2][2]; // s' = s * m[0][0] + t * m[1][0] + trans[0] + float translate[2]; // t' = s * m[0][1] + t * m[0][1] + trans[1] + + // used for TMOD_SCALE + float scale[2]; // s *= scale[0] + // t *= scale[1] + + // used for TMOD_SCROLL + float scroll[2]; // s' = s + scroll[0] * time + // t' = t + scroll[1] * time + + // + = clockwise + // - = counterclockwise + float rotateSpeed; + +} texModInfo_t; + + +// RF increased this for onfire animation +//#define MAX_IMAGE_ANIMATIONS 8 +#define MAX_IMAGE_ANIMATIONS 16 + +typedef struct { + image_t *image[MAX_IMAGE_ANIMATIONS]; + int numImageAnimations; + float imageAnimationSpeed; + + texCoordGen_t tcGen; + vec3_t tcGenVectors[2]; + + int numTexMods; + texModInfo_t *texMods; + + int videoMapHandle; + qboolean isLightmap; + qboolean vertexLightmap; + qboolean isVideoMap; +} textureBundle_t; + +#define NUM_TEXTURE_BUNDLES 2 + +typedef struct { + qboolean active; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + colorGen_t rgbGen; + + waveForm_t alphaWave; + alphaGen_t alphaGen; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned stateBits; // GLS_xxxx mask + + acff_t adjustColorsForFog; + + // Ridah + float zFadeBounds[2]; + + qboolean isDetail; + qboolean isFogged; // used only for shaders that have fog disabled, so we can enable it for individual stages +} shaderStage_t; + +struct shaderCommands_s; + +#define LIGHTMAP_2D -4 // shader is for 2D rendering +#define LIGHTMAP_BY_VERTEX -3 // pre-lit triangle models +#define LIGHTMAP_WHITEIMAGE -2 +#define LIGHTMAP_NONE -1 + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +typedef enum { + FP_NONE, // surface is translucent and will just be adjusted properly + FP_EQUAL, // surface is opaque but possibly alpha tested + FP_LE // surface is trnaslucent, but still needs a fog pass (fog surface) +} fogPass_t; + +typedef struct { + float cloudHeight; + image_t *outerbox[6], *innerbox[6]; +} skyParms_t; + +typedef struct { + vec3_t color; + float depthForOpaque; +} fogParms_t; + + +typedef struct shader_s { + char name[MAX_QPATH]; // game path, including extension + int lightmapIndex; // for a shader to match, both name and lightmapIndex must match + + int index; // this shader == tr.shaders[index] + int sortedIndex; // this shader == tr.sortedShaders[sortedIndex] + + float sort; // lower numbered shaders draw before higher numbered + + qboolean defaultShader; // we want to return index 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + + qboolean explicitlyDefined; // found in a .shader file + + int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags + int contentFlags; + + qboolean entityMergable; // merge across entites optimizable (smoke, blood) + + qboolean isSky; + skyParms_t sky; + fogParms_t fogParms; + + float portalRange; // distance to fog out at + + int multitextureEnv; // 0, GL_MODULATE, GL_ADD (FIXME: put in stage) + + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + qboolean polygonOffset; // set for decals and other items that must be offset + qboolean noMipMaps; // for console fonts, 2D elements, etc. + qboolean noPicMip; // for images that must always be full resolution + + fogPass_t fogPass; // draw a blended pass, possibly with depth test equals + + qboolean needsNormal; // not all shaders will need all data to be gathered + qboolean needsST1; + qboolean needsST2; + qboolean needsColor; + + // Ridah + qboolean noFog; + + int numDeforms; + deformStage_t deforms[MAX_SHADER_DEFORMS]; + + int numUnfoggedPasses; + shaderStage_t *stages[MAX_SHADER_STAGES]; + + void ( *optimalStageIteratorFunc )( void ); + + float clampTime; // time this shader is clamped to + float timeOffset; // current time offset for this shader + + int numStates; // if non-zero this is a state shader + struct shader_s *currentShader; // current state if this is a state shader + struct shader_s *parentShader; // current state if this is a state shader + int currentState; // current state index for cycle purposes + long expireTime; // time in milliseconds this expires + + struct shader_s *remappedShader; // current shader this one is remapped too + + int shaderStates[MAX_STATES_PER_SHADER]; // index to valid shader states + + struct shader_s *next; +} shader_t; + +typedef struct corona_s { + vec3_t origin; + vec3_t color; // range from 0.0 to 1.0, should be color normalized + vec3_t transformed; // origin in local coordinate system + float scale; // uses r_flaresize as the baseline (1.0) + int id; + qboolean visible; // still send the corona request, even if not visible, for proper fading +} corona_t; + +typedef struct dlight_s { + vec3_t origin; + vec3_t color; // range from 0.0 to 1.0, should be color normalized + float radius; + + vec3_t transformed; // origin in local coordinate system + + // Ridah + int overdraw; + // done. + + shader_t *dlshader; //----(SA) adding a shader to dlights, so, if desired, we can change the blend or texture of a dlight + + qboolean forced; //----(SA) use this dlight when r_dynamiclight is either 1 or 2 (rather than just 1) for "important" gameplay lights (alarm lights, etc) + //done + +} dlight_t; + +// trRefdef_t holds everything that comes in refdef_t, +// as well as the locally generated scene information +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + int time; // time in milliseconds for shader effects and other time dependent rendering issues + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + qboolean areamaskModified; // qtrue if areamask changed since last scene + + float floatTime; // tr.refdef.time / 1000.0 + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + int num_entities; + trRefEntity_t *entities; + + int num_dlights; + struct dlight_s *dlights; + + int num_coronas; + struct corona_s *coronas; + + int numPolys; + struct srfPoly_s *polys; + + int numDrawSurfs; + struct drawSurf_s *drawSurfs; +} trRefdef_t; + + +//================================================================================= + +// skins allow models to be retextured without modifying the model file +typedef struct { + char name[MAX_QPATH]; + shader_t *shader; +} skinSurface_t; + +//----(SA) modified +#define MAX_PART_MODELS 5 + +typedef struct { + char type[MAX_QPATH]; // md3_lower, md3_lbelt, md3_rbelt, etc. + char model[MAX_QPATH]; // lower.md3, belt1.md3, etc. +} skinModel_t; + +typedef struct skin_s { + char name[MAX_QPATH]; // game path, including extension + int numSurfaces; + int numModels; + skinSurface_t *surfaces[MD3_MAX_SURFACES]; + skinModel_t *models[MAX_PART_MODELS]; + vec3_t scale; //----(SA) added +} skin_t; +//----(SA) end + +typedef struct { + int originalBrushNumber; + vec3_t bounds[2]; + + unsigned colorInt; // in packed byte format + float tcScale; // texture coordinate vector scales + fogParms_t parms; + + // for clipping distance in fog when outside + qboolean hasSurface; + float surface[4]; +} fog_t; + +typedef struct { + orientationr_t or; + orientationr_t world; + vec3_t pvsOrigin; // may be different than or.origin for portals + qboolean isPortal; // true if this view is through a portal + qboolean isMirror; // the portal is a mirror, invert the face culling + int frameSceneNum; // copied from tr.frameSceneNum + int frameCount; // copied from tr.frameCount + cplane_t portalPlane; // clip anything behind this if mirroring + int viewportX, viewportY, viewportWidth, viewportHeight; + float fovX, fovY; + float projectionMatrix[16]; + cplane_t frustum[4]; + vec3_t visBounds[2]; + float zFar; + + glfog_t glFog; // fog parameters //----(SA) added + +} viewParms_t; + + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ + +// any changes in surfaceType must be mirrored in rb_surfaceTable[] +// NOTE: also mirror changes to max2skl.c +typedef enum { + SF_BAD, + SF_SKIP, // ignore + SF_FACE, + SF_GRID, + SF_TRIANGLES, + SF_POLY, + SF_MD3, + SF_MDC, + SF_MDS, + SF_FLARE, + SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity + SF_DISPLAY_LIST, + + SF_NUM_SURFACE_TYPES, + SF_MAX = 0xffffffff // ensures that sizeof( surfaceType_t ) == sizeof( int ) +} surfaceType_t; + +typedef struct drawSurf_s { + unsigned sort; // bit combination for fast compares + surfaceType_t *surface; // any of surface*_t +} drawSurf_t; + +#define MAX_FACE_POINTS 64 + +#define MAX_PATCH_SIZE 32 // max dimensions of a patch mesh in map file +#define MAX_GRID_SIZE 65 // max dimensions of a grid mesh in memory + +// when cgame directly specifies a polygon, it becomes a srfPoly_t +// as soon as it is called +typedef struct srfPoly_s { + surfaceType_t surfaceType; + qhandle_t hShader; + int fogIndex; + int numVerts; + polyVert_t *verts; +} srfPoly_t; + +typedef struct srfDisplayList_s { + surfaceType_t surfaceType; + int listNum; +} srfDisplayList_t; + + +typedef struct srfFlare_s { + surfaceType_t surfaceType; + vec3_t origin; + vec3_t normal; + vec3_t color; +} srfFlare_t; + +typedef struct srfGridMesh_s { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + + // culling information + vec3_t meshBounds[2]; + vec3_t localOrigin; + float meshRadius; + + // lod information, which may be different + // than the culling information to allow for + // groups of curves that LOD as a unit + vec3_t lodOrigin; + float lodRadius; + int lodFixed; + int lodStitched; + + // vertexes + int width, height; + float *widthLodError; + float *heightLodError; + drawVert_t verts[1]; // variable sized +} srfGridMesh_t; + + + +#define VERTEXSIZE 8 +typedef struct { + surfaceType_t surfaceType; + cplane_t plane; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + + // triangle definitions (no normals at points) + int numPoints; + int numIndices; + int ofsIndices; + float points[1][VERTEXSIZE]; // variable sized + // there is a variable length list of indices here also +} srfSurfaceFace_t; + + +// misc_models in maps are turned into direct geometry by q3map +typedef struct { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + + // culling information (FIXME: use this!) + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + + // triangle definitions + int numIndexes; + int *indexes; + + int numVerts; + drawVert_t *verts; +} srfTriangles_t; + + +extern void( *rb_surfaceTable[SF_NUM_SURFACE_TYPES] ) ( void * ); + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + +typedef struct msurface_s { + int viewCount; // if == tr.viewCount, already added + struct shader_s *shader; + int fogIndex; + + surfaceType_t *data; // any of srf*_t +} msurface_t; + + + +#define CONTENTS_NODE -1 +typedef struct mnode_s { + // common with leaf and node + int contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + vec3_t mins, maxs; // for bounding box culling + struct mnode_s *parent; + + // node specific + cplane_t *plane; + struct mnode_s *children[2]; + + // leaf specific + int cluster; + int area; + + msurface_t **firstmarksurface; + int nummarksurfaces; +} mnode_t; + +typedef struct { + vec3_t bounds[2]; // for culling + msurface_t *firstSurface; + int numSurfaces; +} bmodel_t; + +typedef struct { + char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp + char baseName[MAX_QPATH]; // ie: tim_dm2 + + int dataSize; + + int numShaders; + dshader_t *shaders; + + bmodel_t *bmodels; + + int numplanes; + cplane_t *planes; + + int numnodes; // includes leafs + int numDecisionNodes; + mnode_t *nodes; + + int numsurfaces; + msurface_t *surfaces; + + int nummarksurfaces; + msurface_t **marksurfaces; + + int numfogs; + fog_t *fogs; + + vec3_t lightGridOrigin; + vec3_t lightGridSize; + vec3_t lightGridInverseSize; + int lightGridBounds[3]; + byte *lightGridData; + + int numClusters; + int clusterBytes; + const byte *vis; // may be passed in by CM_LoadMap to save space + + byte *novis; // clusterBytes of 0xff + + char *entityString; + char *entityParsePoint; +} world_t; + +//====================================================================== + +typedef enum { + MOD_BAD, + MOD_BRUSH, + MOD_MESH, + MOD_MDS, + MOD_MDC // Ridah +} modtype_t; + +typedef struct model_s { + char name[MAX_QPATH]; + modtype_t type; + int index; // model = tr.models[model->index] + + int dataSize; // just for listing purposes + bmodel_t *bmodel; // only if type == MOD_BRUSH + md3Header_t *md3[MD3_MAX_LODS]; // only if type == MOD_MESH + mdsHeader_t *mds; // only if type == MOD_MDS + mdcHeader_t *mdc[MD3_MAX_LODS]; // only if type == MOD_MDC + + int numLods; +} model_t; + + +#define MAX_MOD_KNOWN 2048 + +void R_ModelInit( void ); +model_t *R_GetModelByHandle( qhandle_t hModel ); +int R_LerpTag( orientation_t *tag, const refEntity_t *refent, const char *tagName, int startIndex ); +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ); + +void R_Modellist_f( void ); + +//==================================================== +extern refimport_t ri; + +#define MAX_DRAWIMAGES 2048 +#define MAX_LIGHTMAPS 256 +#define MAX_SKINS 1024 + + +#define MAX_DRAWSURFS 0x10000 +#define DRAWSURF_MASK ( MAX_DRAWSURFS - 1 ) + +/* + +the drawsurf sort data is packed into a single 32 bit value so it can be +compared quickly during the qsorting process + +the bits are allocated as follows: + +(SA) modified for Wolf (11 bits of entity num) + +old: + +22 - 31 : sorted shader index +12 - 21 : entity index +3 - 7 : fog index +2 : used to be clipped flag +0 - 1 : dlightmap index + +#define QSORT_SHADERNUM_SHIFT 22 +#define QSORT_ENTITYNUM_SHIFT 12 +#define QSORT_FOGNUM_SHIFT 3 + +new: + +22 - 31 : sorted shader index +11 - 21 : entity index +2 - 6 : fog index +removed : used to be clipped flag +0 - 1 : dlightmap index + +newest: (fixes shader index not having enough bytes) + +18 - 31 : sorted shader index +7 - 17 : entity index +2 - 6 : fog index +0 - 1 : dlightmap index + +*/ +#define QSORT_SHADERNUM_SHIFT 18 +#define QSORT_ENTITYNUM_SHIFT 7 +#define QSORT_FOGNUM_SHIFT 2 + +extern int gl_filter_min, gl_filter_max; + +/* +** performanceCounters_t +*/ +typedef struct { + int c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out; + int c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out; + int c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out; + int c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out; + + int c_leafs; + int c_dlightSurfaces; + int c_dlightSurfacesCulled; +} frontEndCounters_t; + +#define FOG_TABLE_SIZE 256 +#define FUNCTABLE_SIZE 1024 +#define FUNCTABLE_SIZE2 10 +#define FUNCTABLE_MASK ( FUNCTABLE_SIZE - 1 ) + + +// the renderer front end should never modify glstate_t +typedef struct { + int currenttextures[2]; + int currenttmu; + qboolean finishCalled; + int texEnv[2]; + int faceCulling; + unsigned long glStateBits; +} glstate_t; + + +typedef struct { + int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes; + float c_overDraw; + + int c_dlightVertexes; + int c_dlightIndexes; + + int c_flareAdds; + int c_flareTests; + int c_flareRenders; + + int msec; // total msec for backend run +} backEndCounters_t; + +// all state modified by the back end is seperated +// from the front end state +typedef struct { + int smpFrame; + trRefdef_t refdef; + viewParms_t viewParms; + orientationr_t or; + backEndCounters_t pc; + qboolean isHyperspace; + trRefEntity_t *currentEntity; + qboolean skyRenderedThisView; // flag for drawing sun + + qboolean projection2D; // if qtrue, drawstretchpic doesn't need to change modes + byte color2D[4]; + qboolean vertexes2D; // shader needs to be finished + trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering +} backEndState_t; + +/* +** trGlobals_t +** +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ +typedef struct { + qboolean registered; // cleared at shutdown, set at beginRegistration + + int visCount; // incremented every time a new vis cluster is entered + int frameCount; // incremented every frame + int sceneCount; // incremented every scene + int viewCount; // incremented every view (twice a scene if portaled) + // and every R_MarkFragments call + + int smpFrame; // toggles from 0 to 1 every endFrame + + int frameSceneNum; // zeroed at RE_BeginFrame + + qboolean worldMapLoaded; + world_t *world; + + const byte *externalVisData; // from RE_SetWorldVisData, shared with CM_Load + + image_t *defaultImage; + image_t *scratchImage[32]; + image_t *fogImage; + image_t *dlightImage; // inverse-square highlight for projective adding + image_t *flareImage; + image_t *whiteImage; // full of 0xff + image_t *identityLightImage; // full of tr.identityLightByte + + shader_t *defaultShader; + shader_t *shadowShader; + shader_t *projectionShadowShader; + shader_t *dlightShader; //----(SA) added + + shader_t *flareShader; + char *sunShaderName; + shader_t *sunShader; + shader_t *sunflareShader[6]; //----(SA) for the camera lens flare effect for sun + + int numLightmaps; + image_t *lightmaps[MAX_LIGHTMAPS]; + + trRefEntity_t *currentEntity; + trRefEntity_t worldEntity; // point currentEntity at this when rendering world + int currentEntityNum; + int shiftedEntityNum; // currentEntityNum << QSORT_ENTITYNUM_SHIFT + model_t *currentModel; + + viewParms_t viewParms; + + float identityLight; // 1.0 / ( 1 << overbrightBits ) + int identityLightByte; // identityLight * 255 + int overbrightBits; // r_overbrightBits->integer, but set to 0 if no hw gamma + + orientationr_t or; // for current entity + + trRefdef_t refdef; + + int viewCluster; + + vec3_t sunLight; // from the sky shader for this level + vec3_t sunDirection; + +//----(SA) added + float lightGridMulAmbient; // lightgrid multipliers specified in sky shader + float lightGridMulDirected; // +//----(SA) end + +// qboolean levelGLFog; + + frontEndCounters_t pc; + int frontEndMsec; // not in pc due to clearing issue + + // + // put large tables at the end, so most elements will be + // within the +/32K indexed range on risc processors + // + model_t *models[MAX_MOD_KNOWN]; + int numModels; + + int numImages; + image_t *images[MAX_DRAWIMAGES]; + // Ridah + int numCacheImages; + + // shader indexes from other modules will be looked up in tr.shaders[] + // shader indexes from drawsurfs will be looked up in sortedShaders[] + // lower indexed sortedShaders must be rendered first (opaque surfaces before translucent) + int numShaders; + shader_t *shaders[MAX_SHADERS]; + shader_t *sortedShaders[MAX_SHADERS]; + + int numSkins; + skin_t *skins[MAX_SKINS]; + + float sinTable[FUNCTABLE_SIZE]; + float squareTable[FUNCTABLE_SIZE]; + float triangleTable[FUNCTABLE_SIZE]; + float sawToothTable[FUNCTABLE_SIZE]; + float inverseSawToothTable[FUNCTABLE_SIZE]; + float fogTable[FOG_TABLE_SIZE]; + + // RF, temp var used while parsing shader only + int allowCompress; + +} trGlobals_t; + +extern backEndState_t backEnd; +extern trGlobals_t tr; +extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init +extern glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init + + +// +// cvars +// +extern cvar_t *r_flareSize; +extern cvar_t *r_flareFade; + +extern cvar_t *r_railWidth; +extern cvar_t *r_railCoreWidth; +extern cvar_t *r_railSegmentLength; + +extern cvar_t *r_ignore; // used for debugging anything +extern cvar_t *r_verbose; // used for verbose debug spew +extern cvar_t *r_ignoreFastPath; // allows us to ignore our Tess fast paths + +extern cvar_t *r_znear; // near Z clip plane +extern cvar_t *r_zfar; // far Z clip plane + +extern cvar_t *r_stencilbits; // number of desired stencil bits +extern cvar_t *r_depthbits; // number of desired depth bits +extern cvar_t *r_colorbits; // number of desired color bits, only relevant for fullscreen +extern cvar_t *r_stereo; // desired pixelformat stereo flag +extern cvar_t *r_texturebits; // number of desired texture bits + // 0 = use framebuffer depth + // 16 = use 16-bit textures + // 32 = use 32-bit textures + // all else = error + +extern cvar_t *r_measureOverdraw; // enables stencil buffer overdraw measurement + +extern cvar_t *r_lodbias; // push/pull LOD transitions +extern cvar_t *r_lodscale; + +extern cvar_t *r_primitives; // "0" = based on compiled vertex array existance + // "1" = glDrawElemet tristrips + // "2" = glDrawElements triangles + // "-1" = no drawing + +extern cvar_t *r_inGameVideo; // controls whether in game video should be draw +extern cvar_t *r_fastsky; // controls whether sky should be cleared or drawn +extern cvar_t *r_drawSun; // controls drawing of sun quad + // "0" no sun + // "1" draw sun + // "2" also draw lens flare effect centered on sun +extern cvar_t *r_dynamiclight; // dynamic lights enabled/disabled +extern cvar_t *r_dlightBacks; // dlight non-facing surfaces for continuity + +extern cvar_t *r_norefresh; // bypasses the ref rendering +extern cvar_t *r_drawentities; // disable/enable entity rendering +extern cvar_t *r_drawworld; // disable/enable world rendering +extern cvar_t *r_speeds; // various levels of information display +extern cvar_t *r_detailTextures; // enables/disables detail texturing stages +extern cvar_t *r_novis; // disable/enable usage of PVS +extern cvar_t *r_nocull; +extern cvar_t *r_facePlaneCull; // enables culling of planar surfaces with back side test +extern cvar_t *r_nocurves; +extern cvar_t *r_showcluster; + +extern cvar_t *r_mode; // video mode +extern cvar_t *r_fullscreen; +extern cvar_t *r_gamma; +extern cvar_t *r_displayRefresh; // optional display refresh option +extern cvar_t *r_ignorehwgamma; // overrides hardware gamma capabilities + +extern cvar_t *r_allowExtensions; // global enable/disable of OpenGL extensions +extern cvar_t *r_ext_compressed_textures; // these control use of specific extensions +extern cvar_t *r_ext_gamma_control; +extern cvar_t *r_ext_texenv_op; +extern cvar_t *r_ext_multitexture; +extern cvar_t *r_ext_compiled_vertex_array; +extern cvar_t *r_ext_texture_env_add; +extern cvar_t *r_ext_texture_filter_anisotropic; //DAJ from EF + +//----(SA) added +extern cvar_t *r_ext_NV_fog_dist; +extern cvar_t *r_nv_fogdist_mode; + +extern cvar_t *r_ext_ATI_pntriangles; +extern cvar_t *r_ati_truform_tess; // +extern cvar_t *r_ati_truform_normalmode; // linear/quadratic +extern cvar_t *r_ati_truform_pointmode; // linear/cubic +//----(SA) end + +extern cvar_t *r_ati_fsaa_samples; //DAJ + +extern cvar_t *r_nobind; // turns off binding to appropriate textures +extern cvar_t *r_singleShader; // make most world faces use default shader +extern cvar_t *r_roundImagesDown; +extern cvar_t *r_rmse; // reduces textures to this root mean square error +extern cvar_t *r_colorMipLevels; // development aid to see texture mip usage +extern cvar_t *r_picmip; // controls picmip values +extern cvar_t *r_finish; +extern cvar_t *r_drawBuffer; +extern cvar_t *r_glDriver; +extern cvar_t *r_glIgnoreWicked3D; +extern cvar_t *r_swapInterval; +extern cvar_t *r_textureMode; +extern cvar_t *r_offsetFactor; +extern cvar_t *r_offsetUnits; + +//extern cvar_t *r_fullbright; // avoid lightmap pass // JPW NERVE removed per atvi request +extern cvar_t *r_lightmap; // render lightmaps only +extern cvar_t *r_vertexLight; // vertex lighting mode for better performance +extern cvar_t *r_uiFullScreen; // ui is running fullscreen + +extern cvar_t *r_logFile; // number of frames to emit GL logs +extern cvar_t *r_showtris; // enables wireframe rendering of the world +extern cvar_t *r_showsky; // forces sky in front of all surfaces +extern cvar_t *r_shownormals; // draws wireframe normals +extern cvar_t *r_clear; // force screen clear every frame + +extern cvar_t *r_shadows; // controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection +extern cvar_t *r_flares; // light flares + +extern cvar_t *r_portalsky; // (SA) added +extern cvar_t *r_intensity; + +extern cvar_t *r_lockpvs; +extern cvar_t *r_noportals; +extern cvar_t *r_portalOnly; + +extern cvar_t *r_subdivisions; +extern cvar_t *r_lodCurveError; +extern cvar_t *r_smp; +extern cvar_t *r_showSmp; +extern cvar_t *r_skipBackEnd; + +extern cvar_t *r_ignoreGLErrors; + +extern cvar_t *r_overBrightBits; +extern cvar_t *r_mapOverBrightBits; + +extern cvar_t *r_debugSurface; +extern cvar_t *r_simpleMipMaps; + +extern cvar_t *r_showImages; +extern cvar_t *r_debugSort; + +extern cvar_t *r_printShaders; +extern cvar_t *r_saveFontData; + +// Ridah +extern cvar_t *r_cache; +extern cvar_t *r_cacheShaders; +extern cvar_t *r_cacheModels; + +extern cvar_t *r_cacheGathering; + +extern cvar_t *r_bonesDebug; +// done. + +// Rafael - wolf fog +extern cvar_t *r_wolffog; +// done + +extern cvar_t *r_highQualityVideo; +//==================================================================== + +float R_NoiseGet4f( float x, float y, float z, float t ); +void R_NoiseInit( void ); + +void R_SwapBuffers( int ); + +void R_RenderView( viewParms_t *parms ); + +void R_AddMD3Surfaces( trRefEntity_t *e ); +void R_AddNullModelSurfaces( trRefEntity_t *e ); +void R_AddBeamSurfaces( trRefEntity_t *e ); +void R_AddRailSurfaces( trRefEntity_t *e, qboolean isUnderwater ); +void R_AddLightningBoltSurfaces( trRefEntity_t *e ); + +void R_TagInfo_f( void ); + +void R_AddPolygonSurfaces( void ); + +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ); + +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, int fogIndex, int dlightMap ); + + +#define CULL_IN 0 // completely unclipped +#define CULL_CLIP 1 // clipped by one or more planes +#define CULL_OUT 2 // completely outside the clipping planes +void R_LocalNormalToWorld( vec3_t local, vec3_t world ); +void R_LocalPointToWorld( vec3_t local, vec3_t world ); +int R_CullLocalBox( vec3_t bounds[2] ); +int R_CullPointAndRadius( vec3_t origin, float radius ); +int R_CullLocalPointAndRadius( vec3_t origin, float radius ); + +void R_RotateForEntity( const trRefEntity_t * ent, const viewParms_t * viewParms, orientationr_t * or ); + +/* +** GL wrapper/helper functions +*/ +void GL_Bind( image_t *image ); +void GL_SetDefaultState( void ); +void GL_SelectTexture( int unit ); +void GL_TextureMode( const char *string ); +void GL_CheckErrors( void ); +void GL_State( unsigned long stateVector ); +void GL_TexEnv( int env ); +void GL_Cull( int cullType ); + +#define GLS_SRCBLEND_ZERO 0x00000001 +#define GLS_SRCBLEND_ONE 0x00000002 +#define GLS_SRCBLEND_DST_COLOR 0x00000003 +#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR 0x00000004 +#define GLS_SRCBLEND_SRC_ALPHA 0x00000005 +#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA 0x00000006 +#define GLS_SRCBLEND_DST_ALPHA 0x00000007 +#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA 0x00000008 +#define GLS_SRCBLEND_ALPHA_SATURATE 0x00000009 +#define GLS_SRCBLEND_BITS 0x0000000f + +#define GLS_DSTBLEND_ZERO 0x00000010 +#define GLS_DSTBLEND_ONE 0x00000020 +#define GLS_DSTBLEND_SRC_COLOR 0x00000030 +#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR 0x00000040 +#define GLS_DSTBLEND_SRC_ALPHA 0x00000050 +#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA 0x00000060 +#define GLS_DSTBLEND_DST_ALPHA 0x00000070 +#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA 0x00000080 +#define GLS_DSTBLEND_BITS 0x000000f0 + +#define GLS_DEPTHMASK_TRUE 0x00000100 + +#define GLS_POLYMODE_LINE 0x00001000 + +#define GLS_DEPTHTEST_DISABLE 0x00010000 +#define GLS_DEPTHFUNC_EQUAL 0x00020000 + +#define GLS_ATEST_GT_0 0x10000000 +#define GLS_ATEST_LT_80 0x20000000 +#define GLS_ATEST_GE_80 0x40000000 +#define GLS_ATEST_BITS 0x70000000 + +#define GLS_DEFAULT GLS_DEPTHMASK_TRUE + +void RE_StretchRaw( int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty ); +void RE_UploadCinematic( int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty ); + +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_BeginRegistration( glconfig_t *glconfig ); +void RE_LoadWorldMap( const char *mapname ); +void RE_SetWorldVisData( const byte *vis ); +qhandle_t RE_RegisterModel( const char *name ); +qhandle_t RE_RegisterSkin( const char *name ); +void RE_Shutdown( qboolean destroyWindow ); + +qboolean R_GetEntityToken( char *buffer, int size ); + +//----(SA) +qboolean RE_GetSkinModel( qhandle_t skinid, const char *type, char *name ); +qhandle_t RE_GetShaderFromModel( qhandle_t modelid, int surfnum, int withlightmap ); //----(SA) +//----(SA) end + +model_t *R_AllocModel( void ); + +void R_Init( void ); +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ); + +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, qboolean mipmap + , qboolean allowPicmip, int wrapClampMode ); +qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode ); + +void R_SetColorMappings( void ); +void R_GammaCorrect( byte *buffer, int bufSize ); + +void R_ImageList_f( void ); +void R_SkinList_f( void ); +void R_ScreenShot_f( void ); +void R_ScreenShotJPEG_f( void ); + +void R_InitFogTable( void ); +float R_FogFactor( float s, float t ); +void R_InitImages( void ); +void R_DeleteTextures( void ); +int R_SumOfUsedImages( void ); +void R_InitSkins( void ); +skin_t *R_GetSkinByHandle( qhandle_t hSkin ); + + +// +// tr_shader.c +// +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ); +qhandle_t RE_RegisterShader( const char *name ); +qhandle_t RE_RegisterShaderNoMip( const char *name ); +qhandle_t RE_RegisterShaderFromImage( const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage ); + +shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ); +shader_t *R_GetShaderByHandle( qhandle_t hShader ); +shader_t *R_GetShaderByState( int index, long *cycleTime ); +shader_t *R_FindShaderByName( const char *name ); +void R_InitShaders( void ); +void R_ShaderList_f( void ); +void R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +void GLimp_Init( void ); +void GLimp_Shutdown( void ); +void GLimp_EndFrame( void ); + +qboolean GLimp_SpawnRenderThread( void ( *function )( void ) ); +void *GLimp_RendererSleep( void ); +void GLimp_FrontEndSleep( void ); +void GLimp_WakeRenderer( void *data ); + +void GLimp_LogComment( char *comment ); + +void GLimp_SetGamma( unsigned char red[256], + unsigned char green[256], + unsigned char blue[256] ); + + +/* +==================================================================== + +TESSELATOR/SHADER DECLARATIONS + +==================================================================== +*/ +typedef byte color4ub_t[4]; + +typedef struct stageVars +{ + color4ub_t colors[SHADER_MAX_VERTEXES]; + vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; +} stageVars_t; + +typedef struct shaderCommands_s +{ + glIndex_t indexes[SHADER_MAX_INDEXES]; + vec4_t xyz[SHADER_MAX_VERTEXES]; + vec4_t normal[SHADER_MAX_VERTEXES]; + vec2_t texCoords[SHADER_MAX_VERTEXES][2]; + color4ub_t vertexColors[SHADER_MAX_VERTEXES]; + int vertexDlightBits[SHADER_MAX_VERTEXES]; + + stageVars_t svars; + + color4ub_t constantColor255[SHADER_MAX_VERTEXES]; + + shader_t *shader; + float shaderTime; + int fogNum; + + int dlightBits; // or together of all vertexDlightBits + + int numIndexes; + int numVertexes; + + // info extracted from current shader + int numPasses; + void ( *currentStageIteratorFunc )( void ); + shaderStage_t **xstages; +} shaderCommands_t; + +extern shaderCommands_t tess; + +void RB_BeginSurface( shader_t *shader, int fogNum ); +void RB_EndSurface( void ); +void RB_CheckOverflow( int verts, int indexes ); +#define RB_CHECKOVERFLOW( v,i ) if ( tess.numVertexes + ( v ) >= SHADER_MAX_VERTEXES || tess.numIndexes + ( i ) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow( v,i );} + +void RB_StageIteratorGeneric( void ); +void RB_StageIteratorSky( void ); +void RB_StageIteratorVertexLitTexture( void ); +void RB_StageIteratorLightmappedMultitexture( void ); + +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ); +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ); +void RB_AddQuadStampFadingCornersExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ); + +void RB_ShowImages( void ); + + +/* +============================================================ + +WORLD MAP + +============================================================ +*/ + +void R_AddBrushModelSurfaces( trRefEntity_t *e ); +void R_AddWorldSurfaces( void ); + + +/* +============================================================ + +FLARES + +============================================================ +*/ + +void R_ClearFlares( void ); + +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, float scale, vec3_t normal, int id, qboolean visible ); //----(SA) added scale. added id. added visible +void RB_AddDlightFlares( void ); +void RB_RenderFlares( void ); + +/* +============================================================ + +LIGHTS + +============================================================ +*/ + +void R_DlightBmodel( bmodel_t *bmodel ); +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); +void R_TransformDlights( int count, dlight_t * dl, orientationr_t * or ); +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + + +/* +============================================================ + +SHADOWS + +============================================================ +*/ + +void RB_ShadowTessEnd( void ); +void RB_ShadowFinish( void ); +void RB_ProjectionShadowDeform( void ); + +/* +============================================================ + +SKIES + +============================================================ +*/ + +void R_BuildCloudData( shaderCommands_t *shader ); +void R_InitSkyTexCoords( float cloudLayerHeight ); +void R_DrawSkyBox( shaderCommands_t *shader ); +void RB_DrawSun( void ); +void RB_ClipSkyPolygons( shaderCommands_t *shader ); + +/* +============================================================ + +CURVE TESSELATION + +============================================================ +*/ + +#define PATCH_STITCHING + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE * MAX_PATCH_SIZE] ); +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ); +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ); +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ); + +/* +============================================================ + +MARKERS, POLYGON PROJECTION ON WORLD POLYGONS + +============================================================ +*/ + +int R_MarkFragments( int orientation, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + +/* +============================================================ + +SCENE GENERATION + +============================================================ +*/ + +void R_ToggleSmpFrame( void ); + +void RE_ClearScene( void ); +void RE_AddRefEntityToScene( const refEntity_t *ent ); +void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts ); +// Ridah +void RE_AddPolysToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ); +// done. +// Ridah +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b, int overdraw ); +// done. +//----(SA) +void RE_AddCoronaToScene( const vec3_t org, float r, float g, float b, float scale, int id, qboolean visible ); +//----(SA) +void RE_RenderScene( const refdef_t *fd ); + +/* +============================================================= + +ANIMATED MODELS + +============================================================= +*/ + +void R_MakeAnimModel( model_t *model ); +void R_AddAnimSurfaces( trRefEntity_t *ent ); +void RB_SurfaceAnim( mdsSurface_t *surfType ); +int R_GetBoneTag( orientation_t *outTag, mdsHeader_t *mds, int startTagIndex, const refEntity_t *refent, const char *tagName ); + +/* +============================================================= +============================================================= +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ); +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ); + +void RB_DeformTessGeometry( void ); + +void RB_CalcEnvironmentTexCoords( float *dstTexCoords ); +void RB_CalcFireRiseEnvTexCoords( float *st ); +void RB_CalcFogTexCoords( float *dstTexCoords ); +void RB_CalcScrollTexCoords( const float scroll[2], float *dstTexCoords ); +void RB_CalcRotateTexCoords( float rotSpeed, float *dstTexCoords ); +void RB_CalcScaleTexCoords( const float scale[2], float *dstTexCoords ); +void RB_CalcSwapTexCoords( float *dstTexCoords ); +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *dstTexCoords ); +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *dstTexCoords ); +void RB_CalcModulateColorsByFog( unsigned char *dstColors ); +void RB_CalcModulateAlphasByFog( unsigned char *dstColors ); +void RB_CalcModulateRGBAsByFog( unsigned char *dstColors ); +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcAlphaFromEntity( unsigned char *dstColors ); +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *texCoords ); +void RB_CalcColorFromEntity( unsigned char *dstColors ); +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcSpecularAlpha( unsigned char *alphas ); +void RB_CalcDiffuseColor( unsigned char *colors ); + +/* +============================================================= + +RENDERER BACK END FUNCTIONS + +============================================================= +*/ + +void RB_RenderThread( void ); +void RB_ExecuteRenderCommands( const void *data ); + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +============================================================= +*/ + +#define MAX_RENDER_COMMANDS 0x40000 + +typedef struct { + byte cmds[MAX_RENDER_COMMANDS]; + int used; +} renderCommandList_t; + +typedef struct { + int commandId; + float color[4]; +} setColorCommand_t; + +typedef struct { + int commandId; + int buffer; +} drawBufferCommand_t; + +typedef struct { + int commandId; + image_t *image; + int width; + int height; + void *data; +} subImageCommand_t; + +typedef struct { + int commandId; +} swapBuffersCommand_t; + +typedef struct { + int commandId; + int buffer; +} endFrameCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; + + byte gradientColor[4]; // color values 0-255 + int gradientType; //----(SA) added + float angle; // NERVE - SMF +} stretchPicCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; + drawSurf_t *drawSurfs; + int numDrawSurfs; +} drawSurfsCommand_t; + +typedef enum { + RC_END_OF_LIST, + RC_SET_COLOR, + RC_STRETCH_PIC, + RC_ROTATED_PIC, + RC_STRETCH_PIC_GRADIENT, // (SA) added + RC_DRAW_SURFS, + RC_DRAW_BUFFER, + RC_SWAP_BUFFERS +} renderCommand_t; + + +// these are sort of arbitrary limits. +// the limits apply to the sum of all scenes in a frame -- +// the main view, all the 3D icons, etc + +// Ridah, these aren't enough for cool effects +//#define MAX_POLYS 256 +//#define MAX_POLYVERTS 1024 +#define MAX_POLYS 4096 +#define MAX_POLYVERTS 8192 +// done. + +// all of the information needed by the back end must be +// contained in a backEndData_t. This entire structure is +// duplicated so the front and back end can run in parallel +// on an SMP machine +typedef struct { + drawSurf_t drawSurfs[MAX_DRAWSURFS]; + dlight_t dlights[MAX_DLIGHTS]; + corona_t coronas[MAX_CORONAS]; //----(SA) + trRefEntity_t entities[MAX_ENTITIES]; + srfPoly_t polys[MAX_POLYS]; + polyVert_t polyVerts[MAX_POLYVERTS]; + renderCommandList_t commands; +} backEndData_t; + +extern int max_polys; +extern int max_polyverts; + +extern backEndData_t *backEndData[SMP_FRAMES]; // the second one may not be allocated + +extern volatile renderCommandList_t *renderCommandList; + +extern volatile qboolean renderThreadActive; + + +void *R_GetCommandBuffer( int bytes ); +void RB_ExecuteRenderCommands( const void *data ); + +void R_InitCommandBuffers( void ); +void R_ShutdownCommandBuffers( void ); + +void R_SyncRenderThread( void ); + +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ); + +void RE_SetColor( const float *rgba ); +void RE_StretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void RE_RotatedPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, float angle ); // NERVE - SMF +void RE_StretchPicGradient( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, const float *gradientColor, int gradientType ); +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ); +void SaveJPG( char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer ); + +// font stuff +void R_InitFreeType(); +void R_DoneFreeType(); +void RE_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ); + +// Ridah, caching system +// NOTE: to disable this for development, set "r_cache 0" in autoexec.cfg +void R_InitTexnumImages( qboolean force ); + +void *R_CacheModelAlloc( int size ); +void R_CacheModelFree( void *ptr ); +void R_PurgeModels( int count ); +void R_BackupModels( void ); +qboolean R_FindCachedModel( const char *name, model_t *newmod ); +void R_LoadCacheModels( void ); + +void *R_CacheImageAlloc( int size ); +void R_CacheImageFree( void *ptr ); +qboolean R_TouchImage( image_t *inImage ); +image_t *R_FindCachedImage( const char *name, int hash ); +void R_FindFreeTexnum( image_t *image ); +void R_LoadCacheImages( void ); +void R_PurgeBackupImages( int purgeCount ); +void R_BackupImages( void ); + +void *R_CacheShaderAlloc( int size ); +void R_CacheShaderFree( void *ptr ); +shader_t *R_FindCachedShader( const char *name, int lightmapIndex, int hash ); +void R_BackupShaders( void ); +void R_PurgeShaders( int count ); +void R_LoadCacheShaders( void ); +// done. + +//------------------------------------------------------------------------------ +// Ridah, mesh compression +#define NUMMDCVERTEXNORMALS 256 + +extern float r_anormals[NUMMDCVERTEXNORMALS][3]; + +// NOTE: MDC_MAX_ERROR is effectively the compression level. the lower this value, the higher +// the accuracy, but with lower compression ratios. +#define MDC_MAX_ERROR 0.1 // if any compressed vert is off by more than this from the + // actual vert, make this a baseframe + +#define MDC_DIST_SCALE 0.05 // lower for more accuracy, but less range + +// note: we are locked in at 8 or less bits since changing to byte-encoded normals +#define MDC_BITS_PER_AXIS 8 +#define MDC_MAX_OFS 127.0 // to be safe + +#define MDC_MAX_DIST ( MDC_MAX_OFS * MDC_DIST_SCALE ) + +#if 0 +void R_MDC_DecodeXyzCompressed( mdcXyzCompressed_t *xyzComp, vec3_t out, vec3_t normal ); +#else // optimized version +#define R_MDC_DecodeXyzCompressed( ofsVec, out, normal ) \ + ( out )[0] = ( (float)( ( ofsVec ) & 255 ) - MDC_MAX_OFS ) * MDC_DIST_SCALE; \ + ( out )[1] = ( (float)( ( ofsVec >> 8 ) & 255 ) - MDC_MAX_OFS ) * MDC_DIST_SCALE; \ + ( out )[2] = ( (float)( ( ofsVec >> 16 ) & 255 ) - MDC_MAX_OFS ) * MDC_DIST_SCALE; \ + VectorCopy( ( r_anormals )[( ofsVec >> 24 )], normal ); +#endif + +void R_AddMDCSurfaces( trRefEntity_t *ent ); +// done. +//------------------------------------------------------------------------------ + +void R_LatLongToNormal( vec3_t outNormal, short latLong ); + + +/* +============================================================ + +GL FOG + +============================================================ +*/ + +//extern glfog_t glfogCurrent; +extern glfog_t glfogsettings[NUM_FOGS]; // [0] never used (FOG_NONE) +extern glfogType_t glfogNum; // fog type to use (from the fog_t enum list) + +extern qboolean fogIsOn; + +extern void R_FogOff( void ); +extern void R_FogOn( void ); + +extern void R_SetFog( int fogvar, int var1, int var2, float r, float g, float b, float density ); + +extern int skyboxportal; + + +// Ridah, virtual memory +void *R_Hunk_Begin( void ); +void R_Hunk_End( void ); +void R_FreeImageBuffer( void ); + +#endif //TR_LOCAL_H (THIS MUST BE LAST!!) diff --git a/src/renderer/tr_main.c b/src/renderer/tr_main.c new file mode 100644 index 0000000..b9efd73 --- /dev/null +++ b/src/renderer/tr_main.c @@ -0,0 +1,1818 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: tr_main.c + * + * desc: main control flow for each frame + * +*/ + +#include "tr_local.h" + +trGlobals_t tr; + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +refimport_t ri; + +// entities that will have procedurally generated surfaces will just +// point at this for their sorting surface +surfaceType_t entitySurface = SF_ENTITY; + +// fog stuff +glfog_t glfogsettings[NUM_FOGS]; +glfogType_t glfogNum = FOG_NONE; +qboolean fogIsOn = qfalse; + + +/* +================= +R_Fog (void) +================= +*/ +void R_Fog( glfog_t *curfog ) { + static glfog_t setfog; + + if ( !r_wolffog->integer ) { + R_FogOff(); + return; + } + + if ( !curfog->registered ) { //----(SA) + R_FogOff(); + return; + } + + //----(SA) assme values of '0' for these parameters means 'use default' + if ( !curfog->density ) { + curfog->density = 1; + } + if ( !curfog->hint ) { + curfog->hint = GL_DONT_CARE; + } + if ( !curfog->mode ) { + curfog->mode = GL_LINEAR; + } + //----(SA) end + + + R_FogOn(); + + // only send changes if necessary + +// if(curfog->mode != setfog.mode || !setfog.registered) { + qglFogi( GL_FOG_MODE, curfog->mode ); +// setfog.mode = curfog->mode; +// } +// if(curfog->color[0] != setfog.color[0] || curfog->color[1] != setfog.color[1] || curfog->color[2] != setfog.color[2] || !setfog.registered) { + qglFogfv( GL_FOG_COLOR, curfog->color ); +// VectorCopy(setfog.color, curfog->color); +// } +// if(curfog->density != setfog.density || !setfog.registered) { + qglFogf( GL_FOG_DENSITY, curfog->density ); +// setfog.density = curfog->density; +// } +// if(curfog->hint != setfog.hint || !setfog.registered) { + qglHint( GL_FOG_HINT, curfog->hint ); +// setfog.hint = curfog->hint; +// } +// if(curfog->start != setfog.start || !setfog.registered) { + qglFogf( GL_FOG_START, curfog->start ); +// setfog.start = curfog->start; +// } + + if ( r_zfar->value ) { // (SA) allow override for helping level designers test fog distances +// if(setfog.end != r_zfar->value || !setfog.registered) { + qglFogf( GL_FOG_END, r_zfar->value ); +// setfog.end = r_zfar->value; +// } + } else { +// if(curfog->end != setfog.end || !setfog.registered) { + qglFogf( GL_FOG_END, curfog->end ); +// setfog.end = curfog->end; +// } + } + +// TTimo - from SP NV fog code + // NV fog mode + if ( glConfig.NVFogAvailable ) { + qglFogi( GL_FOG_DISTANCE_MODE_NV, glConfig.NVFogMode ); + } +// end + + setfog.registered = qtrue; + + qglClearColor( curfog->color[0], curfog->color[1], curfog->color[2], curfog->color[3] ); + + +} + +// Ridah, allow disabling fog temporarily +void R_FogOff( void ) { + if ( !fogIsOn ) { + return; + } + qglDisable( GL_FOG ); + fogIsOn = qfalse; +} + +void R_FogOn( void ) { + if ( fogIsOn ) { + return; + } + + if ( r_uiFullScreen->integer ) { // don't fog in the menu + R_FogOff(); + return; + } + + if ( !r_wolffog->integer ) { + return; + } + +// if(backEnd.viewParms.isGLFogged) { +// if(!(backEnd.viewParms.glFog.registered)) +// return; +// } + + if ( backEnd.refdef.rdflags & RDF_SKYBOXPORTAL ) { // don't force world fog on portal sky + if ( !( glfogsettings[FOG_PORTALVIEW].registered ) ) { + return; + } + } else if ( !glfogNum ) { + return; + } + + qglEnable( GL_FOG ); + fogIsOn = qtrue; +} +// done. + + + +//----(SA) +/* +============== +R_SetFog + + if fogvar == FOG_CMD_SWITCHFOG { + fogvar is the command + var1 is the fog to switch to + var2 is the time to transition + } + else { + fogvar is the fog that's being set + var1 is the near fog z value + var2 is the far fog z value + rgb = color + density is density, and is used to derive the values of 'mode', 'drawsky', and 'clearscreen' + } +============== +*/ +void R_SetFog( int fogvar, int var1, int var2, float r, float g, float b, float density ) { + if ( fogvar != FOG_CMD_SWITCHFOG ) { // just set the parameters and return + + if ( var1 == 0 && var2 == 0 ) { // clear this fog + glfogsettings[fogvar].registered = qfalse; + return; + } + + glfogsettings[fogvar].color[0] = r; + glfogsettings[fogvar].color[1] = g; + glfogsettings[fogvar].color[2] = b; + glfogsettings[fogvar].color[3] = 1; + glfogsettings[fogvar].start = var1; + glfogsettings[fogvar].end = var2; + if ( density > 1 ) { + glfogsettings[fogvar].mode = GL_LINEAR; + glfogsettings[fogvar].drawsky = qfalse; + glfogsettings[fogvar].clearscreen = qtrue; + glfogsettings[fogvar].density = 1.0; + } else + { + glfogsettings[fogvar].mode = GL_EXP; + glfogsettings[fogvar].drawsky = qtrue; + glfogsettings[fogvar].clearscreen = qfalse; + glfogsettings[fogvar].density = density; + } + glfogsettings[fogvar].hint = GL_DONT_CARE; + glfogsettings[fogvar].registered = qtrue; + + return; + } + + // don't switch to invalid fogs + if ( glfogsettings[var1].registered != qtrue ) { + return; + } + + glfogNum = var1; + + // transitioning to new fog, store the current values as the 'from' + + if ( glfogsettings[FOG_CURRENT].registered ) { + memcpy( &glfogsettings[FOG_LAST], &glfogsettings[FOG_CURRENT], sizeof( glfog_t ) ); + } else { + // if no current fog fall back to world fog + // FIXME: handle transition if there is no FOG_MAP fog + memcpy( &glfogsettings[FOG_LAST], &glfogsettings[FOG_MAP], sizeof( glfog_t ) ); + } + + memcpy( &glfogsettings[FOG_TARGET], &glfogsettings[glfogNum], sizeof( glfog_t ) ); + + // setup transition times + glfogsettings[FOG_TARGET].startTime = tr.refdef.time; + glfogsettings[FOG_TARGET].finishTime = tr.refdef.time + var2; +} + +//----(SA) end + +/* +================= +R_CullLocalBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullLocalBox( vec3_t bounds[2] ) { + int i, j; + vec3_t transformed[8]; + float dists[8]; + vec3_t v; + cplane_t *frust; + int anyBack; + int front, back; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // transform into world space + for ( i = 0 ; i < 8 ; i++ ) { + v[0] = bounds[i & 1][0]; + v[1] = bounds[( i >> 1 ) & 1][1]; + v[2] = bounds[( i >> 2 ) & 1][2]; + + VectorCopy( tr.or.origin, transformed[i] ); + VectorMA( transformed[i], v[0], tr.or.axis[0], transformed[i] ); + VectorMA( transformed[i], v[1], tr.or.axis[1], transformed[i] ); + VectorMA( transformed[i], v[2], tr.or.axis[2], transformed[i] ); + } + + // check against frustum planes + anyBack = 0; + for ( i = 0 ; i < 4 ; i++ ) { + frust = &tr.viewParms.frustum[i]; + + front = back = 0; + for ( j = 0 ; j < 8 ; j++ ) { + dists[j] = DotProduct( transformed[j], frust->normal ); + if ( dists[j] > frust->dist ) { + front = 1; + if ( back ) { + break; // a point is in front + } + } else { + back = 1; + } + } + if ( !front ) { + // all points were behind one of the planes + return CULL_OUT; + } + anyBack |= back; + } + + if ( !anyBack ) { + return CULL_IN; // completely inside frustum + } + + return CULL_CLIP; // partially clipped +} + +/* +** R_CullLocalPointAndRadius +*/ +int R_CullLocalPointAndRadius( vec3_t pt, float radius ) { + vec3_t transformed; + + R_LocalPointToWorld( pt, transformed ); + + return R_CullPointAndRadius( transformed, radius ); +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadius( vec3_t pt, float radius ) { + int i; + float dist; + cplane_t *frust; + qboolean mightBeClipped = qfalse; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // check against frustum planes + for ( i = 0 ; i < 4 ; i++ ) + { + frust = &tr.viewParms.frustum[i]; + + dist = DotProduct( pt, frust->normal ) - frust->dist; + if ( dist < -radius ) { + return CULL_OUT; + } else if ( dist <= radius ) { + mightBeClipped = qtrue; + } + } + + if ( mightBeClipped ) { + return CULL_CLIP; + } + + return CULL_IN; // completely inside frustum +} + + +/* +================= +R_LocalNormalToWorld + +================= +*/ +void R_LocalNormalToWorld( vec3_t local, vec3_t world ) { + world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0]; + world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1]; + world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2]; +} + +/* +================= +R_LocalPointToWorld + +================= +*/ +void R_LocalPointToWorld( vec3_t local, vec3_t world ) { + world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0] + tr.or.origin[0]; + world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1] + tr.or.origin[1]; + world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2] + tr.or.origin[2]; +} + +/* +================= +R_WorldToLocal + +================= +*/ +void R_WorldToLocal( vec3_t world, vec3_t local ) { + local[0] = DotProduct( world, tr.or.axis[0] ); + local[1] = DotProduct( world, tr.or.axis[1] ); + local[2] = DotProduct( world, tr.or.axis[2] ); +} + +/* +========================== +R_TransformModelToClip + +========================== +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ) { + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + eye[i] = + src[0] * modelMatrix[ i + 0 * 4 ] + + src[1] * modelMatrix[ i + 1 * 4 ] + + src[2] * modelMatrix[ i + 2 * 4 ] + + 1 * modelMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i++ ) { + dst[i] = + eye[0] * projectionMatrix[ i + 0 * 4 ] + + eye[1] * projectionMatrix[ i + 1 * 4 ] + + eye[2] * projectionMatrix[ i + 2 * 4 ] + + eye[3] * projectionMatrix[ i + 3 * 4 ]; + } +} + +/* +========================== +R_TransformClipToWindow + +========================== +*/ +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) { + normalized[0] = clip[0] / clip[3]; + normalized[1] = clip[1] / clip[3]; + normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] ); + + window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth; + window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight; + window[2] = normalized[2]; + + window[0] = (int) ( window[0] + 0.5 ); + window[1] = (int) ( window[1] + 0.5 ); +} + + +/* +========================== +myGlMultMatrix + +========================== +*/ +void myGlMultMatrix( const float *a, const float *b, float *out ) { + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[ i * 4 + j ] = + a [ i * 4 + 0 ] * b [ 0 * 4 + j ] + + a [ i * 4 + 1 ] * b [ 1 * 4 + j ] + + a [ i * 4 + 2 ] * b [ 2 * 4 + j ] + + a [ i * 4 + 3 ] * b [ 3 * 4 + j ]; + } + } +} + +/* +================= +R_RotateForEntity + +Generates an orientation for an entity and viewParms +Does NOT produce any GL calls +Called by both the front end and the back end +================= +*/ +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, + orientationr_t *or ) { + float glMatrix[16]; + vec3_t delta; + float axisLength; + + if ( ent->e.reType != RT_MODEL ) { + *or = viewParms->world; + return; + } + + VectorCopy( ent->e.origin, or->origin ); + + VectorCopy( ent->e.axis[0], or->axis[0] ); + VectorCopy( ent->e.axis[1], or->axis[1] ); + VectorCopy( ent->e.axis[2], or->axis[2] ); + + glMatrix[0] = or->axis[0][0]; + glMatrix[4] = or->axis[1][0]; + glMatrix[8] = or->axis[2][0]; + glMatrix[12] = or->origin[0]; + + glMatrix[1] = or->axis[0][1]; + glMatrix[5] = or->axis[1][1]; + glMatrix[9] = or->axis[2][1]; + glMatrix[13] = or->origin[1]; + + glMatrix[2] = or->axis[0][2]; + glMatrix[6] = or->axis[1][2]; + glMatrix[10] = or->axis[2][2]; + glMatrix[14] = or->origin[2]; + + glMatrix[3] = 0; + glMatrix[7] = 0; + glMatrix[11] = 0; + glMatrix[15] = 1; + + myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, or->modelMatrix ); + + // calculate the viewer origin in the model's space + // needed for fog, specular, and environment mapping + VectorSubtract( viewParms->or.origin, or->origin, delta ); + + // compensate for scale in the axes if necessary + if ( ent->e.nonNormalizedAxes ) { + axisLength = VectorLength( ent->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + } else { + axisLength = 1.0f; + } + + or->viewOrigin[0] = DotProduct( delta, or->axis[0] ) * axisLength; + or->viewOrigin[1] = DotProduct( delta, or->axis[1] ) * axisLength; + or->viewOrigin[2] = DotProduct( delta, or->axis[2] ) * axisLength; +} + +/* +================= +R_RotateForViewer + +Sets up the modelview matrix for a given viewParm +================= +*/ +void R_RotateForViewer( void ) { + float viewerMatrix[16]; + vec3_t origin; + + memset( &tr.or, 0, sizeof( tr.or ) ); + tr.or.axis[0][0] = 1; + tr.or.axis[1][1] = 1; + tr.or.axis[2][2] = 1; + VectorCopy( tr.viewParms.or.origin, tr.or.viewOrigin ); + + // transform by the camera placement + VectorCopy( tr.viewParms.or.origin, origin ); + + viewerMatrix[0] = tr.viewParms.or.axis[0][0]; + viewerMatrix[4] = tr.viewParms.or.axis[0][1]; + viewerMatrix[8] = tr.viewParms.or.axis[0][2]; + viewerMatrix[12] = -origin[0] * viewerMatrix[0] + - origin[1] * viewerMatrix[4] + - origin[2] * viewerMatrix[8]; + + viewerMatrix[1] = tr.viewParms.or.axis[1][0]; + viewerMatrix[5] = tr.viewParms.or.axis[1][1]; + viewerMatrix[9] = tr.viewParms.or.axis[1][2]; + viewerMatrix[13] = -origin[0] * viewerMatrix[1] + - origin[1] * viewerMatrix[5] + - origin[2] * viewerMatrix[9]; + + viewerMatrix[2] = tr.viewParms.or.axis[2][0]; + viewerMatrix[6] = tr.viewParms.or.axis[2][1]; + viewerMatrix[10] = tr.viewParms.or.axis[2][2]; + viewerMatrix[14] = -origin[0] * viewerMatrix[2] + - origin[1] * viewerMatrix[6] + - origin[2] * viewerMatrix[10]; + + viewerMatrix[3] = 0; + viewerMatrix[7] = 0; + viewerMatrix[11] = 0; + viewerMatrix[15] = 1; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.or.modelMatrix ); + + tr.viewParms.world = tr.or; + +} + + + +/* +============== +R_SetFrameFog +============== +*/ +void R_SetFrameFog( void ) { + + if ( r_speeds->integer == 5 ) { + if ( !glfogsettings[FOG_TARGET].registered ) { + ri.Printf( PRINT_ALL, "no fog - calc zFar: %0.1f\n", tr.viewParms.zFar ); + return; + } + } + + // DHM - Nerve :: If fog is not valid, don't use it + if ( !glfogsettings[FOG_TARGET].registered ) { + return; + } + + // still fading + if ( glfogsettings[FOG_TARGET].finishTime && glfogsettings[FOG_TARGET].finishTime >= tr.refdef.time ) { + float lerpPos; + int fadeTime; + + // transitioning from density to distance + if ( glfogsettings[FOG_LAST].mode == GL_EXP && glfogsettings[FOG_TARGET].mode == GL_LINEAR ) { + // for now just fast transition to the target when dissimilar fogs are + memcpy( &glfogsettings[FOG_CURRENT], &glfogsettings[FOG_TARGET], sizeof( glfog_t ) ); + glfogsettings[FOG_TARGET].finishTime = 0; + } + // transitioning from distance to density + else if ( glfogsettings[FOG_LAST].mode == GL_LINEAR && glfogsettings[FOG_TARGET].mode == GL_EXP ) { + memcpy( &glfogsettings[FOG_CURRENT], &glfogsettings[FOG_TARGET], sizeof( glfog_t ) ); + glfogsettings[FOG_TARGET].finishTime = 0; + } + // transitioning like fog modes + else { + + fadeTime = glfogsettings[FOG_TARGET].finishTime - glfogsettings[FOG_TARGET].startTime; + if ( fadeTime <= 0 ) { + fadeTime = 1; // avoid divide by zero + + + } + lerpPos = (float)( tr.refdef.time - glfogsettings[FOG_TARGET].startTime ) / (float)fadeTime; + if ( lerpPos > 1 ) { + lerpPos = 1; + } + + // lerp near/far + glfogsettings[FOG_CURRENT].start = glfogsettings[FOG_LAST].start + ( ( glfogsettings[FOG_TARGET].start - glfogsettings[FOG_LAST].start ) * lerpPos ); + glfogsettings[FOG_CURRENT].end = glfogsettings[FOG_LAST].end + ( ( glfogsettings[FOG_TARGET].end - glfogsettings[FOG_LAST].end ) * lerpPos ); + + // lerp color + glfogsettings[FOG_CURRENT].color[0] = glfogsettings[FOG_LAST].color[0] + ( ( glfogsettings[FOG_TARGET].color[0] - glfogsettings[FOG_LAST].color[0] ) * lerpPos ); + glfogsettings[FOG_CURRENT].color[1] = glfogsettings[FOG_LAST].color[1] + ( ( glfogsettings[FOG_TARGET].color[1] - glfogsettings[FOG_LAST].color[1] ) * lerpPos ); + glfogsettings[FOG_CURRENT].color[2] = glfogsettings[FOG_LAST].color[2] + ( ( glfogsettings[FOG_TARGET].color[2] - glfogsettings[FOG_LAST].color[2] ) * lerpPos ); + + glfogsettings[FOG_CURRENT].density = glfogsettings[FOG_TARGET].density; + glfogsettings[FOG_CURRENT].mode = glfogsettings[FOG_TARGET].mode; + glfogsettings[FOG_CURRENT].registered = qtrue; + + // if either fog in the transition clears the screen, clear the background this frame to avoid hall of mirrors + glfogsettings[FOG_CURRENT].clearscreen = ( glfogsettings[FOG_TARGET].clearscreen || glfogsettings[FOG_LAST].clearscreen ); + } + } else { + // probably usually not necessary to copy the whole thing. + // potential FIXME: since this is the most common occurance, diff first and only set changes + memcpy( &glfogsettings[FOG_CURRENT], &glfogsettings[FOG_TARGET], sizeof( glfog_t ) ); + } + + + // shorten the far clip if the fog opaque distance is closer than the procedural farcip dist + + if ( glfogsettings[FOG_CURRENT].mode == GL_LINEAR ) { + if ( glfogsettings[FOG_CURRENT].end < tr.viewParms.zFar ) { + tr.viewParms.zFar = glfogsettings[FOG_CURRENT].end; + } + } +// else +// glfogsettings[FOG_CURRENT].end = 5; + + + if ( r_speeds->integer == 5 ) { + if ( glfogsettings[FOG_CURRENT].mode == GL_LINEAR ) { + ri.Printf( PRINT_ALL, "farclip fog - den: %0.1f calc zFar: %0.1f fog zfar: %0.1f\n", glfogsettings[FOG_CURRENT].density, tr.viewParms.zFar, glfogsettings[FOG_CURRENT].end ); + } else { + ri.Printf( PRINT_ALL, "density fog - den: %0.4f calc zFar: %0.1f fog zFar: %0.1f\n", glfogsettings[FOG_CURRENT].density, tr.viewParms.zFar, glfogsettings[FOG_CURRENT].end ); + } + } +} + + +/* +============== +SetFarClip +============== +*/ +static void SetFarClip( void ) { + float farthestCornerDistance = 0; + int i; + + // if not rendering the world (icons, menus, etc) + // set a 2k far clip plane + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + tr.viewParms.zFar = 2048; + return; + } + + //----(SA) this lets you use r_zfar from the command line to experiment with different + // distances, but setting it back to 0 uses the map (or procedurally generated) default + if ( r_zfar->value ) { + + tr.viewParms.zFar = r_zfar->integer; + R_SetFrameFog(); + + if ( r_speeds->integer == 5 ) { + ri.Printf( PRINT_ALL, "r_zfar value forcing farclip at: %f\n", tr.viewParms.zFar ); + } + + return; + } + + // + // set far clipping planes dynamically + // + farthestCornerDistance = 0; + for ( i = 0; i < 8; i++ ) + { + vec3_t v; + vec3_t vecTo; + float distance; + + if ( i & 1 ) { + v[0] = tr.viewParms.visBounds[0][0]; + } else + { + v[0] = tr.viewParms.visBounds[1][0]; + } + + if ( i & 2 ) { + v[1] = tr.viewParms.visBounds[0][1]; + } else + { + v[1] = tr.viewParms.visBounds[1][1]; + } + + if ( i & 4 ) { + v[2] = tr.viewParms.visBounds[0][2]; + } else + { + v[2] = tr.viewParms.visBounds[1][2]; + } + + VectorSubtract( v, tr.viewParms.or.origin, vecTo ); + + distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2]; + + if ( distance > farthestCornerDistance ) { + farthestCornerDistance = distance; + } + } + + tr.viewParms.zFar = sqrt( farthestCornerDistance ); + R_SetFrameFog(); +} + + +/* +=============== +R_SetupProjection +=============== +*/ +void R_SetupProjection( void ) { + float xmin, xmax, ymin, ymax; + float width, height, depth; + float zNear, zFar; + + // dynamically compute far clip plane distance + SetFarClip(); + + // + // set up projection matrix + // + zNear = r_znear->value; + if ( r_zfar->value ) { + zFar = r_zfar->value; // (SA) allow override for helping level designers test fog distances + } else { + zFar = tr.viewParms.zFar; + } + + ymax = zNear * tan( tr.refdef.fov_y * M_PI / 360.0f ); + ymin = -ymax; + + xmax = zNear * tan( tr.refdef.fov_x * M_PI / 360.0f ); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + depth = zFar - zNear; + + tr.viewParms.projectionMatrix[0] = 2 * zNear / width; + tr.viewParms.projectionMatrix[4] = 0; + tr.viewParms.projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0 + tr.viewParms.projectionMatrix[12] = 0; + + tr.viewParms.projectionMatrix[1] = 0; + tr.viewParms.projectionMatrix[5] = 2 * zNear / height; + tr.viewParms.projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + tr.viewParms.projectionMatrix[13] = 0; + + tr.viewParms.projectionMatrix[2] = 0; + tr.viewParms.projectionMatrix[6] = 0; + tr.viewParms.projectionMatrix[10] = -( zFar + zNear ) / depth; + tr.viewParms.projectionMatrix[14] = -2 * zFar * zNear / depth; + + tr.viewParms.projectionMatrix[3] = 0; + tr.viewParms.projectionMatrix[7] = 0; + tr.viewParms.projectionMatrix[11] = -1; + tr.viewParms.projectionMatrix[15] = 0; +} + +/* +================= +R_SetupFrustum + +Setup that culling frustum planes for the current view +================= +*/ +void R_SetupFrustum( void ) { + int i; + float xs, xc; + float ang; + + ang = tr.viewParms.fovX / 180 * M_PI * 0.5f; + xs = sin( ang ); + xc = cos( ang ); + + VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[0].normal ); + VectorMA( tr.viewParms.frustum[0].normal, xc, tr.viewParms.or.axis[1], tr.viewParms.frustum[0].normal ); + + VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[1].normal ); + VectorMA( tr.viewParms.frustum[1].normal, -xc, tr.viewParms.or.axis[1], tr.viewParms.frustum[1].normal ); + + ang = tr.viewParms.fovY / 180 * M_PI * 0.5f; + xs = sin( ang ); + xc = cos( ang ); + + VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[2].normal ); + VectorMA( tr.viewParms.frustum[2].normal, xc, tr.viewParms.or.axis[2], tr.viewParms.frustum[2].normal ); + + VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[3].normal ); + VectorMA( tr.viewParms.frustum[3].normal, -xc, tr.viewParms.or.axis[2], tr.viewParms.frustum[3].normal ); + + for ( i = 0 ; i < 4 ; i++ ) { + tr.viewParms.frustum[i].type = PLANE_NON_AXIAL; + tr.viewParms.frustum[i].dist = DotProduct( tr.viewParms.or.origin, tr.viewParms.frustum[i].normal ); + SetPlaneSignbits( &tr.viewParms.frustum[i] ); + } +} + + +/* +================= +R_MirrorPoint +================= +*/ +void R_MirrorPoint( vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out ) { + int i; + vec3_t local; + vec3_t transformed; + float d; + + VectorSubtract( in, surface->origin, local ); + + VectorClear( transformed ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct( local, surface->axis[i] ); + VectorMA( transformed, d, camera->axis[i], transformed ); + } + + VectorAdd( transformed, camera->origin, out ); +} + +void R_MirrorVector( vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out ) { + int i; + float d; + + VectorClear( out ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct( in, surface->axis[i] ); + VectorMA( out, d, camera->axis[i], out ); + } +} + + +/* +============= +R_PlaneForSurface +============= +*/ +void R_PlaneForSurface( surfaceType_t *surfType, cplane_t *plane ) { + srfTriangles_t *tri; + srfPoly_t *poly; + drawVert_t *v1, *v2, *v3; + vec4_t plane4; + + if ( !surfType ) { + memset( plane, 0, sizeof( *plane ) ); + plane->normal[0] = 1; + return; + } + switch ( *surfType ) { + case SF_FACE: + *plane = ( (srfSurfaceFace_t *)surfType )->plane; + return; + case SF_TRIANGLES: + tri = (srfTriangles_t *)surfType; + v1 = tri->verts + tri->indexes[0]; + v2 = tri->verts + tri->indexes[1]; + v3 = tri->verts + tri->indexes[2]; + PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + case SF_POLY: + poly = (srfPoly_t *)surfType; + PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + default: + memset( plane, 0, sizeof( *plane ) ); + plane->normal[0] = 1; + return; + } +} + +/* +================= +R_GetPortalOrientation + +entityNum is the entity that the portal surface is a part of, which may +be moving and rotating. + +Returns qtrue if it should be mirrored +================= +*/ +qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum, + orientation_t *surface, orientation_t *camera, + vec3_t pvsOrigin, qboolean *mirror ) { + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + vec3_t transformed; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != ENTITYNUM_WORLD ) { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin ); + } else { + plane = originalPlane; + } + + VectorCopy( plane.normal, surface->axis[0] ); + PerpendicularVector( surface->axis[1], surface->axis[0] ); + CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] ); + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64 ) { + continue; + } + + // get the pvsOrigin from the entity + VectorCopy( e->e.oldorigin, pvsOrigin ); + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) { + VectorScale( plane.normal, plane.dist, surface->origin ); + VectorCopy( surface->origin, camera->origin ); + VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] ); + VectorCopy( surface->axis[1], camera->axis[1] ); + VectorCopy( surface->axis[2], camera->axis[2] ); + + *mirror = qtrue; + return qtrue; + } + + // project the origin onto the surface plane to get + // an origin point we can rotate around + d = DotProduct( e->e.origin, plane.normal ) - plane.dist; + VectorMA( e->e.origin, -d, surface->axis[0], surface->origin ); + + // now get the camera origin and orientation + VectorCopy( e->e.oldorigin, camera->origin ); + AxisCopy( e->e.axis, camera->axis ); + VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] ); + VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] ); + + // optionally rotate + if ( e->e.oldframe ) { + // if a speed is specified + if ( e->e.frame ) { + // continuous rotate + d = ( tr.refdef.time / 1000.0f ) * e->e.frame; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } else { + // bobbing rotate, with skinNum being the rotation offset + d = sin( tr.refdef.time * 0.003f ); + d = e->e.skinNum + d * 4; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + } else if ( e->e.skinNum ) { + d = e->e.skinNum; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + *mirror = qfalse; + return qtrue; + } + + // if we didn't locate a portal entity, don't render anything. + // We don't want to just treat it as a mirror, because without a + // portal entity the server won't have communicated a proper entity set + // in the snapshot + + // unfortunately, with local movement prediction it is easily possible + // to see a surface before the server has communicated the matching + // portal surface entity, so we don't want to print anything here... + + //ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" ); + + return qfalse; +} + +static qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum ) { + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != ENTITYNUM_WORLD ) { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin ); + } else + { + plane = originalPlane; + } + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) + { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64 ) { + continue; + } + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) { + return qtrue; + } + + return qfalse; + } + return qfalse; +} + +/* +** SurfIsOffscreen +** +** Determines if a surface is completely offscreen. +*/ +static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) { + float shortest = 100000000; + int entityNum; + int numTriangles; + shader_t *shader; + int fogNum; + int dlighted; + vec4_t clip, eye; + int i; + unsigned int pointOr = 0; + unsigned int pointAnd = (unsigned int)~0; + + if ( glConfig.smpActive ) { // FIXME! we can't do RB_BeginSurface/RB_EndSurface stuff with smp! + return qfalse; + } + + R_RotateForViewer(); + + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + RB_BeginSurface( shader, fogNum ); + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + + assert( tess.numVertexes < 128 ); + + for ( i = 0; i < tess.numVertexes; i++ ) + { + int j; + unsigned int pointFlags = 0; + + R_TransformModelToClip( tess.xyz[i], tr.or.modelMatrix, tr.viewParms.projectionMatrix, eye, clip ); + + for ( j = 0; j < 3; j++ ) + { + if ( clip[j] >= clip[3] ) { + pointFlags |= ( 1 << ( j * 2 ) ); + } else if ( clip[j] <= -clip[3] ) { + pointFlags |= ( 1 << ( j * 2 + 1 ) ); + } + } + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd ) { + return qtrue; + } + + // determine if this surface is backfaced and also determine the distance + // to the nearest vertex so we can cull based on portal range. Culling + // based on vertex distance isn't 100% correct (we should be checking for + // range to the surface), but it's good enough for the types of portals + // we have in the game right now. + numTriangles = tess.numIndexes / 3; + + for ( i = 0; i < tess.numIndexes; i += 3 ) + { + vec3_t normal; + float dot; + float len; + + VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.or.origin, normal ); + + len = VectorLengthSquared( normal ); // lose the sqrt + if ( len < shortest ) { + shortest = len; + } + + if ( ( dot = DotProduct( normal, tess.normal[tess.indexes[i]] ) ) >= 0 ) { + numTriangles--; + } + } + if ( !numTriangles ) { + return qtrue; + } + + // mirrors can early out at this point, since we don't do a fade over distance + // with them (although we could) + if ( IsMirror( drawSurf, entityNum ) ) { + return qfalse; + } + + if ( shortest > ( tess.shader->portalRange * tess.shader->portalRange ) ) { + return qtrue; + } + + return qfalse; +} + +/* +======================== +R_MirrorViewBySurface + +Returns qtrue if another view has been rendered +======================== +*/ +qboolean R_MirrorViewBySurface( drawSurf_t *drawSurf, int entityNum ) { + vec4_t clipDest[128]; + viewParms_t newParms; + viewParms_t oldParms; + orientation_t surface, camera; + + // don't recursively mirror + if ( tr.viewParms.isPortal ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" ); + return qfalse; + } + +// if ( r_noportals->integer || r_fastsky->integer || tr.levelGLFog) { + if ( r_noportals->integer || r_fastsky->integer ) { + return qfalse; + } + + // trivially reject portal/mirror + if ( SurfIsOffscreen( drawSurf, clipDest ) ) { + return qfalse; + } + + // save old viewParms so we can return to it after the mirror view + oldParms = tr.viewParms; + + newParms = tr.viewParms; + newParms.isPortal = qtrue; + if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera, + newParms.pvsOrigin, &newParms.isMirror ) ) { + return qfalse; // bad portal, no portalentity + } + + R_MirrorPoint( oldParms.or.origin, &surface, &camera, newParms.or.origin ); + + VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal ); + newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal ); + + R_MirrorVector( oldParms.or.axis[0], &surface, &camera, newParms.or.axis[0] ); + R_MirrorVector( oldParms.or.axis[1], &surface, &camera, newParms.or.axis[1] ); + R_MirrorVector( oldParms.or.axis[2], &surface, &camera, newParms.or.axis[2] ); + + // OPTIMIZE: restrict the viewport on the mirrored view + + // render the mirror view + R_RenderView( &newParms ); + + tr.viewParms = oldParms; + + return qtrue; +} + +/* +================= +R_SpriteFogNum + +See if a sprite is inside a fog volume +================= +*/ +int R_SpriteFogNum( trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) { + break; + } + if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +========================================================================================== + +DRAWSURF SORTING + +========================================================================================== +*/ + +/* +================= +qsort replacement + +================= +*/ +#define SWAP_DRAW_SURF( a,b ) temp = ( (int *)a )[0]; ( (int *)a )[0] = ( (int *)b )[0]; ( (int *)b )[0] = temp; temp = ( (int *)a )[1]; ( (int *)a )[1] = ( (int *)b )[1]; ( (int *)b )[1] = temp; + +/* this parameter defines the cutoff between using quick sort and + insertion sort for arrays; arrays with lengths shorter or equal to the + below value use insertion sort */ + +#define CUTOFF 8 /* testing shows that this is good value */ + +static void shortsort( drawSurf_t *lo, drawSurf_t *hi ) { + drawSurf_t *p, *max; + int temp; + + while ( hi > lo ) { + max = lo; + for ( p = lo + 1; p <= hi; p++ ) { + if ( p->sort > max->sort ) { + max = p; + } + } + SWAP_DRAW_SURF( max, hi ); + hi--; + } +} + + +/* sort the array between lo and hi (inclusive) +FIXME: this was lifted and modified from the microsoft lib source... + */ + +void qsortFast( + void *base, + unsigned num, + unsigned width + ) { + char *lo, *hi; /* ends of sub-array currently sorting */ + char *mid; /* points to middle of subarray */ + char *loguy, *higuy; /* traveling pointers for partition step */ + unsigned size; /* size of the sub-array */ + char *lostk[30], *histk[30]; + int stkptr; /* stack for saving sub-array to be processed */ + int temp; + + if ( sizeof( drawSurf_t ) != 8 ) { + ri.Error( ERR_DROP, "change SWAP_DRAW_SURF macro" ); + } + + /* Note: the number of stack entries required is no more than + 1 + log2(size), so 30 is sufficient for any array */ + + if ( num < 2 || width == 0 ) { + return; /* nothing to do */ + + } + stkptr = 0; /* initialize stack */ + + lo = base; + hi = (char *)base + width * ( num - 1 ); /* initialize limits */ + + /* this entry point is for pseudo-recursion calling: setting + lo and hi and jumping to here is like recursion, but stkptr is + prserved, locals aren't, so we preserve stuff on the stack */ +recurse: + + size = ( hi - lo ) / width + 1; /* number of el's to sort */ + + /* below a certain size, it is faster to use a O(n^2) sorting method */ + if ( size <= CUTOFF ) { + shortsort( (drawSurf_t *)lo, (drawSurf_t *)hi ); + } else { + /* First we pick a partititioning element. The efficiency of the + algorithm demands that we find one that is approximately the + median of the values, but also that we select one fast. Using + the first one produces bad performace if the array is already + sorted, so we use the middle one, which would require a very + wierdly arranged array for worst case performance. Testing shows + that a median-of-three algorithm does not, in general, increase + performance. */ + + mid = lo + ( size / 2 ) * width; /* find middle element */ + SWAP_DRAW_SURF( mid, lo ); /* swap it to beginning of array */ + + /* We now wish to partition the array into three pieces, one + consisiting of elements <= partition element, one of elements + equal to the parition element, and one of element >= to it. This + is done below; comments indicate conditions established at every + step. */ + + loguy = lo; + higuy = hi + width; + + /* Note that higuy decreases and loguy increases on every iteration, + so loop must terminate. */ + for (;; ) { + /* lo <= loguy < hi, lo < higuy <= hi + 1, + A[i] <= A[lo] for lo <= i <= loguy, + A[i] >= A[lo] for higuy <= i <= hi */ + + do { + loguy += width; + } while ( loguy <= hi && + ( ( (drawSurf_t *)loguy )->sort <= ( (drawSurf_t *)lo )->sort ) ); + + /* lo < loguy <= hi+1, A[i] <= A[lo] for lo <= i < loguy, + either loguy > hi or A[loguy] > A[lo] */ + + do { + higuy -= width; + } while ( higuy > lo && + ( ( (drawSurf_t *)higuy )->sort >= ( (drawSurf_t *)lo )->sort ) ); + + /* lo-1 <= higuy <= hi, A[i] >= A[lo] for higuy < i <= hi, + either higuy <= lo or A[higuy] < A[lo] */ + + if ( higuy < loguy ) { + break; + } + + /* if loguy > hi or higuy <= lo, then we would have exited, so + A[loguy] > A[lo], A[higuy] < A[lo], + loguy < hi, highy > lo */ + + SWAP_DRAW_SURF( loguy, higuy ); + + /* A[loguy] < A[lo], A[higuy] > A[lo]; so condition at top + of loop is re-established */ + } + + /* A[i] >= A[lo] for higuy < i <= hi, + A[i] <= A[lo] for lo <= i < loguy, + higuy < loguy, lo <= higuy <= hi + implying: + A[i] >= A[lo] for loguy <= i <= hi, + A[i] <= A[lo] for lo <= i <= higuy, + A[i] = A[lo] for higuy < i < loguy */ + + SWAP_DRAW_SURF( lo, higuy ); /* put partition element in place */ + + /* OK, now we have the following: + A[i] >= A[higuy] for loguy <= i <= hi, + A[i] <= A[higuy] for lo <= i < higuy + A[i] = A[lo] for higuy <= i < loguy */ + + /* We've finished the partition, now we want to sort the subarrays + [lo, higuy-1] and [loguy, hi]. + We do the smaller one first to minimize stack usage. + We only sort arrays of length 2 or more.*/ + + if ( higuy - 1 - lo >= hi - loguy ) { + if ( lo + width < higuy ) { + lostk[stkptr] = lo; + histk[stkptr] = higuy - width; + ++stkptr; + } /* save big recursion for later */ + + if ( loguy < hi ) { + lo = loguy; + goto recurse; /* do small recursion */ + } + } else { + if ( loguy < hi ) { + lostk[stkptr] = loguy; + histk[stkptr] = hi; + ++stkptr; /* save big recursion for later */ + } + + if ( lo + width < higuy ) { + hi = higuy - width; + goto recurse; /* do small recursion */ + } + } + } + + /* We have sorted the array, except for any pending sorts on the stack. + Check if there are any, and do them. */ + + --stkptr; + if ( stkptr >= 0 ) { + lo = lostk[stkptr]; + hi = histk[stkptr]; + goto recurse; /* pop subarray from stack */ + } else { + return; /* all subarrays done */ + } +} + + +//========================================================================================== + +/* +================= +R_AddDrawSurf +================= +*/ +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, + int fogIndex, int dlightMap ) { + int index; + + // instead of checking for overflow, we just mask the index + // so it wraps around + index = tr.refdef.numDrawSurfs & DRAWSURF_MASK; + // the sort data is packed into a single 32 bit value so it can be + // compared quickly during the qsorting process + tr.refdef.drawSurfs[index].sort = ( shader->sortedIndex << QSORT_SHADERNUM_SHIFT ) + | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) | (int)dlightMap; + tr.refdef.drawSurfs[index].surface = surface; + tr.refdef.numDrawSurfs++; +} + +/* +================= +R_DecomposeSort +================= +*/ +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ) { + *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31; + *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & ( MAX_SHADERS - 1 ) ]; +// *entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & 1023; + *entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & ( MAX_GENTITIES - 1 ); // (SA) uppded entity count for Wolf to 11 bits + *dlightMap = sort & 3; +} + +/* +================= +R_SortDrawSurfs +================= +*/ +void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader; + int fogNum; + int entityNum; + int dlighted; + int i; + + // it is possible for some views to not have any surfaces + if ( numDrawSurfs < 1 ) { + // we still need to add it for hyperspace cases + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // if we overflowed MAX_DRAWSURFS, the drawsurfs + // wrapped around in the buffer and we will be missing + // the first surfaces, not the last ones + if ( numDrawSurfs > MAX_DRAWSURFS ) { + numDrawSurfs = MAX_DRAWSURFS; + } + + // sort the drawsurfs by sort type, then orientation, then shader + qsortFast( drawSurfs, numDrawSurfs, sizeof( drawSurf_t ) ); + + // check for any pass through drawing, which + // may cause another view to be rendered first + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + R_DecomposeSort( ( drawSurfs + i )->sort, &entityNum, &shader, &fogNum, &dlighted ); + + if ( shader->sort > SS_PORTAL ) { + break; + } + + // no shader should ever have this sort type + if ( shader->sort == SS_BAD ) { + ri.Error( ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name ); + } + + // if the mirror was completely clipped away, we may need to check another surface + if ( R_MirrorViewBySurface( ( drawSurfs + i ), entityNum ) ) { + // this is a debug option to see exactly what is being mirrored + if ( r_portalOnly->integer ) { + return; + } + break; // only one mirror view at a time + } + } + + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); +} + +/* +============= +R_AddEntitySurfaces +============= +*/ +void R_AddEntitySurfaces( void ) { + trRefEntity_t *ent; + shader_t *shader; + + if ( !r_drawentities->integer ) { + return; + } + + for ( tr.currentEntityNum = 0; + tr.currentEntityNum < tr.refdef.num_entities; + tr.currentEntityNum++ ) { + ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum]; + + ent->needDlights = qfalse; + + // preshift the value we are going to OR into the drawsurf sort + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // + // the weapon model must be handled special -- + // we don't want the hacked weapon position showing in + // mirrors, because the true body position will already be drawn + // + if ( ( ent->e.renderfx & RF_FIRST_PERSON ) && tr.viewParms.isPortal ) { + continue; + } + + // simple generated models, like sprites and beams, are not culled + switch ( ent->e.reType ) { + case RT_PORTALSURFACE: + break; // don't draw anything + case RT_SPRITE: + case RT_SPLASH: + case RT_BEAM: + case RT_LIGHTNING: + case RT_RAIL_CORE: + case RT_RAIL_CORE_TAPER: + case RT_RAIL_RINGS: + // self blood sprites, talk balloons, etc should not be drawn in the primary + // view. We can't just do this check for all entities, because md3 + // entities may still want to cast shadows from them + if ( ( ent->e.renderfx & RF_THIRD_PERSON ) && !tr.viewParms.isPortal ) { + continue; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0 ); + break; + + case RT_MODEL: + // we must set up parts of tr.or for model culling + R_RotateForEntity( ent, &tr.viewParms, &tr.or ); + + tr.currentModel = R_GetModelByHandle( ent->e.hModel ); + if ( !tr.currentModel ) { + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 ); + } else { + switch ( tr.currentModel->type ) { + case MOD_MESH: + R_AddMD3Surfaces( ent ); + break; + // Ridah + case MOD_MDC: + R_AddMDCSurfaces( ent ); + break; + // done. + case MOD_MDS: + R_AddAnimSurfaces( ent ); + break; + case MOD_BRUSH: + R_AddBrushModelSurfaces( ent ); + break; + case MOD_BAD: // null model axis + if ( ( ent->e.renderfx & RF_THIRD_PERSON ) && !tr.viewParms.isPortal ) { + break; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 ); + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" ); + break; + } + } + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" ); + } + } + +} + + +/* +==================== +R_GenerateDrawSurfs +==================== +*/ +void R_GenerateDrawSurfs( void ) { + R_AddWorldSurfaces(); + + R_AddPolygonSurfaces(); + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + R_SetupProjection(); + + R_AddEntitySurfaces(); +} + +/* +================ +R_DebugPolygon +================ +*/ +void R_DebugPolygon( int color, int numPoints, float *points ) { + int i; + + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // draw solid shade + + qglColor3f( color & 1, ( color >> 1 ) & 1, ( color >> 2 ) & 1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + + // draw wireframe outline + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + qglDepthRange( 0, 0 ); + qglColor3f( 1, 1, 1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + qglDepthRange( 0, 1 ); +} + +/* +==================== +R_DebugGraphics + +Visualization aid for movement clipping debugging +==================== +*/ +void R_DebugGraphics( void ) { + if ( !r_debugSurface->integer ) { + return; + } + + R_FogOff(); // moved this in here to keep from /always/ doing the fog state change + + // the render thread can't make callbacks to the main thread + R_SyncRenderThread(); + + GL_Bind( tr.whiteImage ); + GL_Cull( CT_FRONT_SIDED ); + ri.CM_DrawDebugSurface( R_DebugPolygon ); +} + + +/* +================ +R_RenderView + +A view may be either the actual camera view, +or a mirror / remote location +================ +*/ +void R_RenderView( viewParms_t *parms ) { + int firstDrawSurf; + + if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) { + return; + } + + // Ridah, purge media that were left over from the last level + if ( r_cache->integer ) { + extern void R_PurgeBackupImages( int purgeCount ); + static int lastTime; + + if ( ( lastTime > tr.refdef.time ) || ( lastTime < ( tr.refdef.time - 200 ) ) ) { + R_FreeImageBuffer(); // clear all image buffers + R_PurgeShaders( 10 ); + R_PurgeBackupImages( 1 ); + R_PurgeModels( 1 ); + lastTime = tr.refdef.time; + } + } + // done. + + tr.viewCount++; + + tr.viewParms = *parms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer(); + + R_SetupFrustum(); + + R_GenerateDrawSurfs(); + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + + // draw main system development information (surface outlines, etc) + R_FogOff(); + R_DebugGraphics(); + R_FogOn(); + +} diff --git a/src/renderer/tr_marks.c b/src/renderer/tr_marks.c new file mode 100644 index 0000000..1566702 --- /dev/null +++ b/src/renderer/tr_marks.c @@ -0,0 +1,822 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_marks.c -- polygon projection on the world polygons + +#include "tr_local.h" +//#include "assert.h" + +#define MAX_VERTS_ON_POLY 64 + +#define MARKER_OFFSET 0 // 1 + +// Ridah, just make these global to prevent having to add more paramaters, which add overhead +static vec3_t bestnormal; +static float bestdist; + +/* +============= +R_ChopPolyBehindPlane + +Out must have space for two more vertexes than in +============= +*/ +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY], + int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY], + vec3_t normal, vec_t dist, vec_t epsilon ) { + float dists[MAX_VERTS_ON_POLY + 4]; + int sides[MAX_VERTS_ON_POLY + 4]; + int counts[3]; + float dot; + int i, j; + float *p1, *p2, *clip; + float d; + + // don't clip if it might overflow + if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) { + *numOutPoints = 0; + return; + } + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0 ; i < numInPoints ; i++ ) { + dot = DotProduct( inPoints[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *numOutPoints = 0; + + if ( !counts[0] ) { + return; + } + if ( !counts[1] ) { + *numOutPoints = numInPoints; + memcpy( outPoints, inPoints, numInPoints * sizeof( vec3_t ) ); + return; + } + + for ( i = 0 ; i < numInPoints ; i++ ) { + p1 = inPoints[i]; + clip = outPoints[ *numOutPoints ]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, clip ); + ( *numOutPoints )++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, clip ); + ( *numOutPoints )++; + clip = outPoints[ *numOutPoints ]; + } + + if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = inPoints[ ( i + 1 ) % numInPoints ]; + + d = dists[i] - dists[i + 1]; + if ( d == 0 ) { + dot = 0; + } else { + dot = dists[i] / d; + } + + // clip xyz + + for ( j = 0 ; j < 3 ; j++ ) { + clip[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + ( *numOutPoints )++; + } +} + +/* +================= +R_BoxSurfaces_r + +================= +*/ +void R_BoxSurfaces_r( mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir ) { + + int s, c; + msurface_t *surf, **mark; + + // RF, if this node hasn't been rendered recently, ignore it + if ( node->visframe < tr.visCount - 2 ) { // allow us to be a few frames behind + return; + } + + // do the tail recursion in a loop + while ( node->contents == -1 ) { + s = BoxOnPlaneSide( mins, maxs, node->plane ); + if ( s == 1 ) { + node = node->children[0]; + } else if ( s == 2 ) { + node = node->children[1]; + } else { + R_BoxSurfaces_r( node->children[0], mins, maxs, list, listsize, listlength, dir ); + node = node->children[1]; + } + } + + // Ridah, don't mark alpha surfaces + if ( node->contents & CONTENTS_TRANSLUCENT ) { + return; + } + + // add the individual surfaces + mark = node->firstmarksurface; + c = node->nummarksurfaces; + while ( c-- ) { + // + if ( *listlength >= listsize ) { + break; + } + // + surf = *mark; + // check if the surface has NOIMPACT or NOMARKS set + if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) ) + || ( surf->shader->contentFlags & CONTENTS_FOG ) ) { + surf->viewCount = tr.viewCount; + } + // extra check for surfaces to avoid list overflows + else if ( *( surf->data ) == SF_FACE ) { + // the face plane should go through the box + s = BoxOnPlaneSide( mins, maxs, &( ( srfSurfaceFace_t * ) surf->data )->plane ); + if ( s == 1 || s == 2 ) { + surf->viewCount = tr.viewCount; + } else if ( DotProduct( ( ( srfSurfaceFace_t * ) surf->data )->plane.normal, dir ) < -0.5 ) { + // don't add faces that make sharp angles with the projection direction + surf->viewCount = tr.viewCount; + } + } else if ( *( surfaceType_t * )( surf->data ) != SF_GRID ) { + surf->viewCount = tr.viewCount; + } + // check the viewCount because the surface may have + // already been added if it spans multiple leafs + if ( surf->viewCount != tr.viewCount ) { + surf->viewCount = tr.viewCount; + list[*listlength] = (surfaceType_t *) surf->data; + ( *listlength )++; + } + mark++; + } +} + +/* +================= +R_AddMarkFragments + +================= +*/ +void R_AddMarkFragments( int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY], + int numPlanes, vec3_t *normals, float *dists, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer, + int *returnedPoints, int *returnedFragments, + vec3_t mins, vec3_t maxs ) { + int pingPong, i; + markFragment_t *mf; + + // chop the surface by all the bounding planes of the to be projected polygon + pingPong = 0; + + for ( i = 0 ; i < numPlanes ; i++ ) { + + R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong], + &numClipPoints, clipPoints[!pingPong], + normals[i], dists[i], 0.5 ); + pingPong ^= 1; + if ( numClipPoints == 0 ) { + break; + } + } + // completely clipped away? + if ( numClipPoints == 0 ) { + return; + } + + // add this fragment to the returned list + if ( numClipPoints + ( *returnedPoints ) > maxPoints ) { + return; // not enough space for this polygon + } + /* + // all the clip points should be within the bounding box + for ( i = 0 ; i < numClipPoints ; i++ ) { + int j; + for ( j = 0 ; j < 3 ; j++ ) { + if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break; + if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break; + } + if (j < 3) break; + } + if (i < numClipPoints) return; + */ + + mf = fragmentBuffer + ( *returnedFragments ); + mf->firstPoint = ( *returnedPoints ); + mf->numPoints = numClipPoints; + //memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) ); + for ( i = 0; i < numClipPoints; i++ ) { + VectorCopy( clipPoints[pingPong][i], (float *)pointBuffer + 5 * ( *returnedPoints + i ) ); + } + + ( *returnedPoints ) += numClipPoints; + ( *returnedFragments )++; +} + +/* +================= +R_OldMarkFragments + +================= +*/ +int R_OldMarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { + int numsurfaces, numPlanes; + int i, j, k, m, n; + surfaceType_t *surfaces[64]; + vec3_t mins, maxs; + int returnedFragments; + int returnedPoints; + vec3_t normals[MAX_VERTS_ON_POLY + 2]; + float dists[MAX_VERTS_ON_POLY + 2]; + vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; + int numClipPoints; + float *v; + srfSurfaceFace_t *surf; + srfGridMesh_t *cv; + drawVert_t *dv; + vec3_t normal; + vec3_t projectionDir; + vec3_t v1, v2; + int *indexes; + + //increment view count for double check prevention + tr.viewCount++; + + // + VectorNormalize2( projection, projectionDir ); + // find all the brushes that are to be considered + ClearBounds( mins, maxs ); + for ( i = 0 ; i < numPoints ; i++ ) { + vec3_t temp; + + AddPointToBounds( points[i], mins, maxs ); + VectorAdd( points[i], projection, temp ); + AddPointToBounds( temp, mins, maxs ); + // make sure we get all the leafs (also the one(s) in front of the hit surface) + VectorMA( points[i], -20, projectionDir, temp ); + AddPointToBounds( temp, mins, maxs ); + } + + if ( numPoints > MAX_VERTS_ON_POLY ) { + numPoints = MAX_VERTS_ON_POLY; + } + // create the bounding planes for the to be projected polygon + for ( i = 0 ; i < numPoints ; i++ ) { + VectorSubtract( points[( i + 1 ) % numPoints], points[i], v1 ); + VectorAdd( points[i], projection, v2 ); + VectorSubtract( points[i], v2, v2 ); + CrossProduct( v1, v2, normals[i] ); + VectorNormalizeFast( normals[i] ); + dists[i] = DotProduct( normals[i], points[i] ); + } + // add near and far clipping planes for projection + VectorCopy( projectionDir, normals[numPoints] ); + dists[numPoints] = DotProduct( normals[numPoints], points[0] ) - 32; + VectorCopy( projectionDir, normals[numPoints + 1] ); + VectorInverse( normals[numPoints + 1] ); + dists[numPoints + 1] = DotProduct( normals[numPoints + 1], points[0] ) - 20; + numPlanes = numPoints + 2; + + numsurfaces = 0; + R_BoxSurfaces_r( tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir ); + //assert(numsurfaces <= 64); + //assert(numsurfaces != 64); + + returnedPoints = 0; + returnedFragments = 0; + + for ( i = 0 ; i < numsurfaces ; i++ ) { + + if ( *surfaces[i] == SF_GRID ) { + + cv = (srfGridMesh_t *) surfaces[i]; + for ( m = 0 ; m < cv->height - 1 ; m++ ) { + for ( n = 0 ; n < cv->width - 1 ; n++ ) { + // We triangulate the grid and chop all triangles within + // the bounding planes of the to be projected polygon. + // LOD is not taken into account, not such a big deal though. + // + // It's probably much nicer to chop the grid itself and deal + // with this grid as a normal SF_GRID surface so LOD will + // be applied. However the LOD of that chopped grid must + // be synced with the LOD of the original curve. + // One way to do this; the chopped grid shares vertices with + // the original curve. When LOD is applied to the original + // curve the unused vertices are flagged. Now the chopped curve + // should skip the flagged vertices. This still leaves the + // problems with the vertices at the chopped grid edges. + // + // To avoid issues when LOD applied to "hollow curves" (like + // the ones around many jump pads) we now just add a 2 unit + // offset to the triangle vertices. + // The offset is added in the vertex normal vector direction + // so all triangles will still fit together. + // The 2 unit offset should avoid pretty much all LOD problems. + + numClipPoints = 3; + + dv = cv->verts + m * cv->width + n; + + VectorCopy( dv[0].xyz, clipPoints[0][0] ); + VectorMA( clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0] ); + VectorCopy( dv[cv->width].xyz, clipPoints[0][1] ); + VectorMA( clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1] ); + VectorCopy( dv[1].xyz, clipPoints[0][2] ); + VectorMA( clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2] ); + // check the normal of this triangle + VectorSubtract( clipPoints[0][0], clipPoints[0][1], v1 ); + VectorSubtract( clipPoints[0][2], clipPoints[0][1], v2 ); + CrossProduct( v1, v2, normal ); + VectorNormalizeFast( normal ); + if ( DotProduct( normal, projectionDir ) < -0.1 ) { + // add the fragments of this triangle + R_AddMarkFragments( numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs ); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + VectorCopy( dv[1].xyz, clipPoints[0][0] ); + VectorMA( clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0] ); + VectorCopy( dv[cv->width].xyz, clipPoints[0][1] ); + VectorMA( clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1] ); + VectorCopy( dv[cv->width + 1].xyz, clipPoints[0][2] ); + VectorMA( clipPoints[0][2], MARKER_OFFSET, dv[cv->width + 1].normal, clipPoints[0][2] ); + // check the normal of this triangle + VectorSubtract( clipPoints[0][0], clipPoints[0][1], v1 ); + VectorSubtract( clipPoints[0][2], clipPoints[0][1], v2 ); + CrossProduct( v1, v2, normal ); + VectorNormalizeFast( normal ); + if ( DotProduct( normal, projectionDir ) < -0.05 ) { + // add the fragments of this triangle + R_AddMarkFragments( numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs ); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + } + } else if ( *surfaces[i] == SF_FACE ) { + + surf = ( srfSurfaceFace_t * ) surfaces[i]; + // check the normal of this face + if ( DotProduct( surf->plane.normal, projectionDir ) > -0.5 ) { + continue; + } + + /* + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalize(normal); + if (DotProduct(normal, projectionDir) > -0.5) continue; + */ + indexes = ( int * )( (byte *)surf + surf->ofsIndices ); + for ( k = 0 ; k < surf->numIndices ; k += 3 ) { + for ( j = 0 ; j < 3 ; j++ ) { + v = surf->points[0] + VERTEXSIZE * indexes[k + j];; + VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); + } + // add the fragments of this face + R_AddMarkFragments( 3, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs ); + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + continue; + } else { + // ignore all other world surfaces + // might be cool to also project polygons on a triangle soup + // however this will probably create huge amounts of extra polys + // even more than the projection onto curves + continue; + } + } + return returnedFragments; +} + +/* +================= +R_MarkFragments + +================= +*/ +int R_MarkFragments( int orientation, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { + int numsurfaces, numPlanes; + int i, j, k, m, n; + surfaceType_t *surfaces[4096]; + vec3_t mins, maxs; + int returnedFragments; + int returnedPoints; + vec3_t normals[MAX_VERTS_ON_POLY + 2]; + float dists[MAX_VERTS_ON_POLY + 2]; + vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; + int numClipPoints; + float *v; + srfSurfaceFace_t *surf; + srfGridMesh_t *cv; + drawVert_t *dv; + vec3_t normal; + vec3_t projectionDir; + vec3_t v1, v2; + int *indexes; + float radius; + vec3_t center; // center of original mark + //vec3_t bestCenter; // center point projected onto the closest surface + float texCoordScale; + //float dot; + int numPoints = 4; // Ridah, we were only ever passing in 4, so I made this local and used the parameter for the orientation + qboolean oldMapping = qfalse; + + //increment view count for double check prevention + tr.viewCount++; + + // RF, negative maxFragments means we want original mapping + if ( maxFragments < 0 ) { + maxFragments = -maxFragments; + //return R_OldMarkFragments( numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); + oldMapping = qtrue; + } + + VectorClear( center ); + for ( i = 0 ; i < numPoints ; i++ ) { + VectorAdd( points[i], center, center ); + } + VectorScale( center, 1.0 / numPoints, center ); + // + radius = VectorNormalize2( projection, projectionDir ) / 2.0; + bestdist = 0; + VectorNegate( projectionDir, bestnormal ); + // find all the brushes that are to be considered + ClearBounds( mins, maxs ); + for ( i = 0 ; i < numPoints ; i++ ) { + vec3_t temp; + + AddPointToBounds( points[i], mins, maxs ); + VectorMA( points[i], 1 * ( 1 + oldMapping * radius * 4 ), projection, temp ); + AddPointToBounds( temp, mins, maxs ); + // make sure we get all the leafs (also the one(s) in front of the hit surface) + VectorMA( points[i], -20 * ( 1.0 + (float)oldMapping * ( radius / 20.0 ) * 4 ), projectionDir, temp ); + AddPointToBounds( temp, mins, maxs ); + } + + if ( numPoints > MAX_VERTS_ON_POLY ) { + numPoints = MAX_VERTS_ON_POLY; + } + // create the bounding planes for the to be projected polygon + for ( i = 0 ; i < numPoints ; i++ ) { + VectorSubtract( points[( i + 1 ) % numPoints], points[i], v1 ); + VectorAdd( points[i], projection, v2 ); + VectorSubtract( points[i], v2, v2 ); + CrossProduct( v1, v2, normals[i] ); + VectorNormalize( normals[i] ); + dists[i] = DotProduct( normals[i], points[i] ); + } + // add near and far clipping planes for projection + VectorCopy( projectionDir, normals[numPoints] ); + dists[numPoints] = DotProduct( normals[numPoints], points[0] ) - radius * ( 1 + oldMapping * 10 ); + VectorCopy( projectionDir, normals[numPoints + 1] ); + VectorInverse( normals[numPoints + 1] ); + dists[numPoints + 1] = DotProduct( normals[numPoints + 1], points[0] ) - radius * ( 1 + oldMapping * 10 ); + numPlanes = numPoints + 2; + + numsurfaces = 0; + R_BoxSurfaces_r( tr.world->nodes, mins, maxs, surfaces, 4096, &numsurfaces, projectionDir ); + //assert(numsurfaces <= 64); + //assert(numsurfaces != 64); + + texCoordScale = 0.5 * 1.0 / radius; + + returnedPoints = 0; + returnedFragments = 0; + + // find the closest surface to center the decal there, and wrap around other surfaces + if ( !oldMapping ) { +/* + for ( i = 0 ; i < numsurfaces ; i++ ) { + if (*surfaces[i] == SF_FACE) { + surf = ( srfSurfaceFace_t * ) surfaces[i]; + // Ridah, check if this is the closest surface + dot = DotProduct( center, surf->plane.normal ); + dot -= surf->plane.dist; + if (!bestdist) { + if (dot < 0) + bestdist = fabs(dot) + 1000; // avoid this surface, since the point is behind it + else + bestdist = dot; + VectorCopy( surf->plane.normal, bestnormal ); + VectorMA( center, -dot, surf->plane.normal, bestCenter ); + } else if (dot >= 0 && dot < bestdist) { + bestdist = dot; + VectorCopy( surf->plane.normal, bestnormal ); + VectorMA( center, -dot, surf->plane.normal, bestCenter ); + } + } + } + // bestCenter is now the real center + VectorCopy( bestCenter, center ); +Com_Printf("bestnormal: %1.1f %1.1f %1.1f \n", bestnormal[0], bestnormal[1], bestnormal[2] ); +*/ + VectorNegate( bestnormal, bestnormal ); + } + + for ( i = 0 ; i < numsurfaces ; i++ ) { + + if ( *surfaces[i] == SF_GRID ) { + + cv = (srfGridMesh_t *) surfaces[i]; + for ( m = 0 ; m < cv->height - 1 ; m++ ) { + for ( n = 0 ; n < cv->width - 1 ; n++ ) { + // We triangulate the grid and chop all triangles within + // the bounding planes of the to be projected polygon. + // LOD is not taken into account, not such a big deal though. + // + // It's probably much nicer to chop the grid itself and deal + // with this grid as a normal SF_GRID surface so LOD will + // be applied. However the LOD of that chopped grid must + // be synced with the LOD of the original curve. + // One way to do this; the chopped grid shares vertices with + // the original curve. When LOD is applied to the original + // curve the unused vertices are flagged. Now the chopped curve + // should skip the flagged vertices. This still leaves the + // problems with the vertices at the chopped grid edges. + // + // To avoid issues when LOD applied to "hollow curves" (like + // the ones around many jump pads) we now just add a 2 unit + // offset to the triangle vertices. + // The offset is added in the vertex normal vector direction + // so all triangles will still fit together. + // The 2 unit offset should avoid pretty much all LOD problems. + + numClipPoints = 3; + + dv = cv->verts + m * cv->width + n; + + VectorCopy( dv[0].xyz, clipPoints[0][0] ); + VectorMA( clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0] ); + VectorCopy( dv[cv->width].xyz, clipPoints[0][1] ); + VectorMA( clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1] ); + VectorCopy( dv[1].xyz, clipPoints[0][2] ); + VectorMA( clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2] ); + // check the normal of this triangle + VectorSubtract( clipPoints[0][0], clipPoints[0][1], v1 ); + VectorSubtract( clipPoints[0][2], clipPoints[0][1], v2 ); + CrossProduct( v1, v2, normal ); + VectorNormalize( normal ); + if ( DotProduct( normal, projectionDir ) < -0.1 ) { + // add the fragments of this triangle + R_AddMarkFragments( numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs ); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + VectorCopy( dv[1].xyz, clipPoints[0][0] ); + VectorMA( clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0] ); + VectorCopy( dv[cv->width].xyz, clipPoints[0][1] ); + VectorMA( clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1] ); + VectorCopy( dv[cv->width + 1].xyz, clipPoints[0][2] ); + VectorMA( clipPoints[0][2], MARKER_OFFSET, dv[cv->width + 1].normal, clipPoints[0][2] ); + // check the normal of this triangle + VectorSubtract( clipPoints[0][0], clipPoints[0][1], v1 ); + VectorSubtract( clipPoints[0][2], clipPoints[0][1], v2 ); + CrossProduct( v1, v2, normal ); + VectorNormalize( normal ); + if ( DotProduct( normal, projectionDir ) < -0.05 ) { + // add the fragments of this triangle + R_AddMarkFragments( numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs ); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + } + } else if ( *surfaces[i] == SF_FACE ) { + extern float VectorDistance( vec3_t v1, vec3_t v2 ); + vec3_t axis[3]; + float texCoordScale, dot; + vec3_t originalPoints[4]; + vec3_t newCenter, delta; + int oldNumPoints; + float epsilon = 0.5; + // duplicated so we don't mess with the original clips for the curved surfaces + vec3_t lnormals[MAX_VERTS_ON_POLY + 2]; + float ldists[MAX_VERTS_ON_POLY + 2]; + vec3_t lmins, lmaxs; + + surf = ( srfSurfaceFace_t * ) surfaces[i]; + + if ( !oldMapping ) { + + // Ridah, create a new clip box such that this decal surface is mapped onto + // the current surface without distortion. To find the center of the new clip box, + // we project the center of the original impact center out along the projection vector, + // onto the current surface + + // find the center of the new decal + dot = DotProduct( center, surf->plane.normal ); + dot -= surf->plane.dist; + // check the normal of this face + if ( dot < -epsilon && DotProduct( surf->plane.normal, projectionDir ) >= 0.01 ) { + continue; + } else if ( fabs( dot ) > radius ) { + continue; + } + // if the impact point is behind the surface, subtract the projection, otherwise add it + VectorMA( center, -dot, bestnormal, newCenter ); + + // recalc dot from the offset position + dot = DotProduct( newCenter, surf->plane.normal ); + dot -= surf->plane.dist; + VectorMA( newCenter, -dot, surf->plane.normal, newCenter ); + + VectorMA( newCenter, MARKER_OFFSET, surf->plane.normal, newCenter ); + + // create the texture axis + VectorNormalize2( surf->plane.normal, axis[0] ); + PerpendicularVector( axis[1], axis[0] ); + RotatePointAroundVector( axis[2], axis[0], axis[1], (float)orientation ); + CrossProduct( axis[0], axis[2], axis[1] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for ( j = 0 ; j < 3 ; j++ ) { + originalPoints[0][j] = newCenter[j] - radius * axis[1][j] - radius * axis[2][j]; + originalPoints[1][j] = newCenter[j] + radius * axis[1][j] - radius * axis[2][j]; + originalPoints[2][j] = newCenter[j] + radius * axis[1][j] + radius * axis[2][j]; + originalPoints[3][j] = newCenter[j] - radius * axis[1][j] + radius * axis[2][j]; + } + + ClearBounds( lmins, lmaxs ); + + // create the bounding planes for the to be projected polygon + for ( j = 0 ; j < 4 ; j++ ) { + AddPointToBounds( originalPoints[j], lmins, lmaxs ); + + VectorSubtract( originalPoints[( j + 1 ) % numPoints], originalPoints[j], v1 ); + VectorSubtract( originalPoints[j], surf->plane.normal, v2 ); + VectorSubtract( originalPoints[j], v2, v2 ); + CrossProduct( v1, v2, lnormals[j] ); + VectorNormalize( lnormals[j] ); + ldists[j] = DotProduct( lnormals[j], originalPoints[j] ); + } + numPlanes = numPoints; + + // done. + + indexes = ( int * )( (byte *)surf + surf->ofsIndices ); + for ( k = 0 ; k < surf->numIndices ; k += 3 ) { + for ( j = 0 ; j < 3 ; j++ ) { + v = surf->points[0] + VERTEXSIZE * indexes[k + j]; + VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); + } + + oldNumPoints = returnedPoints; + + // add the fragments of this face + R_AddMarkFragments( 3, clipPoints, + numPlanes, lnormals, ldists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, lmins, lmaxs ); + + if ( oldNumPoints != returnedPoints ) { + // flag this surface as already having computed ST's + fragmentBuffer[returnedFragments - 1].numPoints *= -1; + + // Ridah, calculate ST's + for ( j = 0 ; j < ( returnedPoints - oldNumPoints ) ; j++ ) { + VectorSubtract( (float *)pointBuffer + 5 * ( oldNumPoints + j ), newCenter, delta ); + *( (float *)pointBuffer + 5 * ( oldNumPoints + j ) + 3 ) = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; + *( (float *)pointBuffer + 5 * ( oldNumPoints + j ) + 4 ) = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; + } + } + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + } else { // old mapping + + // check the normal of this face + //if (DotProduct(surf->plane.normal, projectionDir) > 0.0) { + // continue; + //} + + indexes = ( int * )( (byte *)surf + surf->ofsIndices ); + for ( k = 0 ; k < surf->numIndices ; k += 3 ) { + for ( j = 0 ; j < 3 ; j++ ) { + v = surf->points[0] + VERTEXSIZE * indexes[k + j];; + VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); + } + // add the fragments of this face + R_AddMarkFragments( 3, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs ); + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + } + + continue; + } else { + // ignore all other world surfaces + // might be cool to also project polygons on a triangle soup + // however this will probably create huge amounts of extra polys + // even more than the projection onto curves + continue; + } + } + return returnedFragments; +} + diff --git a/src/renderer/tr_mesh.c b/src/renderer/tr_mesh.c new file mode 100644 index 0000000..0150d9f --- /dev/null +++ b/src/renderer/tr_mesh.c @@ -0,0 +1,433 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_mesh.c: triangle model functions +// +// !!! NOTE: Any changes made here must be duplicated in tr_cmesh.c for MDC support + +#include "tr_local.h" + +static float ProjectRadius( float r, vec3_t location ) { + float pr; + float dist; + float c; + vec3_t p; + float projected[4]; + + c = DotProduct( tr.viewParms.or.axis[0], tr.viewParms.or.origin ); + dist = DotProduct( tr.viewParms.or.axis[0], location ) - c; + + if ( dist <= 0 ) { + return 0; + } + + p[0] = 0; + p[1] = fabs( r ); + p[2] = -dist; + + projected[0] = p[0] * tr.viewParms.projectionMatrix[0] + + p[1] * tr.viewParms.projectionMatrix[4] + + p[2] * tr.viewParms.projectionMatrix[8] + + tr.viewParms.projectionMatrix[12]; + + projected[1] = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + projected[2] = p[0] * tr.viewParms.projectionMatrix[2] + + p[1] * tr.viewParms.projectionMatrix[6] + + p[2] * tr.viewParms.projectionMatrix[10] + + tr.viewParms.projectionMatrix[14]; + + projected[3] = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + + pr = projected[1] / projected[3]; + + if ( pr > 1.0f ) { + pr = 1.0f; + } + + return pr; +} + +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( md3Header_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + md3Frame_t *oldFrame, *newFrame; + int i; + + // compute frame pointers + newFrame = ( md3Frame_t * )( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + oldFrame = ( md3Frame_t * )( ( byte * ) header + header->ofsFrames ) + ent->e.oldframe; + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) { + if ( ent->e.frame == ent->e.oldframe ) { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) { + if ( sphereCull == CULL_OUT ) { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } else if ( sphereCull == CULL_IN ) { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + + +/* +================= +R_ComputeLOD + +================= +*/ +int R_ComputeLOD( trRefEntity_t *ent ) { + float radius; + float flod, lodscale; + float projectedRadius; + md3Frame_t *frame; + int lod; + + if ( tr.currentModel->numLods < 2 ) { + // model has only 1 LOD level, skip computations and bias + lod = 0; + } else + { + // multiple LODs exist, so compute projected bounding sphere + // and use that as a criteria for selecting LOD + + // RF, checked for a forced lowest LOD + if ( ent->e.reFlags & REFLAG_FORCE_LOD ) { + return ( tr.currentModel->numLods - 1 ); + } + + frame = ( md3Frame_t * )( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames ); + + frame += ent->e.frame; + + radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] ); + + //----(SA) testing + if ( ent->e.reFlags & REFLAG_ORIENT_LOD ) { + // right now this is for trees, and pushes the lod distance way in. + // this is not the intended purpose, but is helpful for the new + // terrain level that has loads of trees +// radius = radius/2.0f; + } + //----(SA) end + + if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 ) { + lodscale = r_lodscale->value; + if ( lodscale > 20 ) { + lodscale = 20; + } + flod = 1.0f - projectedRadius * lodscale; + } else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } + + flod *= tr.currentModel->numLods; + lod = myftol( flod ); + + if ( lod < 0 ) { + lod = 0; + } else if ( lod >= tr.currentModel->numLods ) { + lod = tr.currentModel->numLods - 1; + } + } + + lod += r_lodbias->integer; + + if ( lod >= tr.currentModel->numLods ) { + lod = tr.currentModel->numLods - 1; + } + if ( lod < 0 ) { + lod = 0; + } + + return lod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +static int R_ComputeFogNum( md3Header_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + md3Frame_t *md3Frame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + md3Frame = ( md3Frame_t * )( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + VectorAdd( ent->e.origin, md3Frame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - md3Frame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + md3Frame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddMD3Surfaces + +================= +*/ +void R_AddMD3Surfaces( trRefEntity_t *ent ) { + int i; + md3Header_t *header = 0; + md3Surface_t *surface = 0; + md3Shader_t *md3Shader = 0; + shader_t *shader = 0; + int cull; + int lod; + int fogNum; + qboolean personalModel; + + // don't add third_person objects if not in a portal + personalModel = ( ent->e.renderfx & RF_THIRD_PERSON ) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->md3[0]->numFrames; + ent->e.oldframe %= tr.currentModel->md3[0]->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( ( ent->e.frame >= tr.currentModel->md3[0]->numFrames ) + || ( ent->e.frame < 0 ) + || ( ent->e.oldframe >= tr.currentModel->md3[0]->numFrames ) + || ( ent->e.oldframe < 0 ) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // compute LOD + // + lod = R_ComputeLOD( ent ); + + header = tr.currentModel->md3[lod]; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel( header, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( header, ent ); + + // + // draw all surfaces + // + surface = ( md3Surface_t * )( (byte *)header + header->ofsSurfaces ); + for ( i = 0 ; i < header->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) { + shader = R_GetShaderByHandle( ent->e.customShader ); + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + +//----(SA) added blink + if ( ent->e.renderfx & RF_BLINK ) { + const char *s = va( "%s_b", surface->name ); // append '_b' for 'blink' + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + if ( !strcmp( skin->surfaces[j]->name, s ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + } + + if ( shader == tr.defaultShader ) { // blink reference in skin was not found + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + + if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + } +//----(SA) end + + if ( shader == tr.defaultShader ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name ); + } else if ( shader->defaultShader ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name ); + } + } else if ( surface->numShaders <= 0 ) { + shader = tr.defaultShader; + } else { + md3Shader = ( md3Shader_t * )( (byte *)surface + surface->ofsShaders ); + md3Shader += ent->e.skinNum % surface->numShaders; + shader = tr.shaders[ md3Shader->shaderIndex ]; + } + + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !( ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && ( ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse ); + } + + + // for testing polygon shadows (on /all/ models) + if ( r_shadows->integer == 4 ) { + R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse ); + } + + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { + R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse ); + } + + surface = ( md3Surface_t * )( (byte *)surface + surface->ofsEnd ); + } + +} + diff --git a/src/renderer/tr_model.c b/src/renderer/tr_model.c new file mode 100644 index 0000000..8227aab --- /dev/null +++ b/src/renderer/tr_model.c @@ -0,0 +1,2101 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_models.c -- model loading and caching + +#include "tr_local.h" + +#define LL( x ) x = LittleLong( x ) + +// Ridah +static qboolean R_LoadMDC( model_t *mod, int lod, void *buffer, const char *mod_name ); +// done. +static qboolean R_LoadMD3( model_t *mod, int lod, void *buffer, const char *name ); +static qboolean R_LoadMDS( model_t *mod, void *buffer, const char *name ); + +model_t *loadmodel; + +extern cvar_t *r_compressModels; +extern cvar_t *r_exportCompressedModels; +extern cvar_t *r_buildScript; + +/* +** R_GetModelByHandle +*/ +model_t *R_GetModelByHandle( qhandle_t index ) { + model_t *mod; + + // out of range gets the defualt model + if ( index < 1 || index >= tr.numModels ) { + return tr.models[0]; + } + + mod = tr.models[index]; + + return mod; +} + +//=============================================================================== + +/* +** R_AllocModel +*/ +model_t *R_AllocModel( void ) { + model_t *mod; + + if ( tr.numModels == MAX_MOD_KNOWN ) { + return NULL; + } + + mod = ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low ); + mod->index = tr.numModels; + tr.models[tr.numModels] = mod; + tr.numModels++; + + return mod; +} + +/* +==================== +RE_RegisterModel + +Loads in a model for the given name + +Zero will be returned if the model fails to load. +An entry will be retained for failed models as an +optimization to prevent disk rescanning if they are +asked for again. +==================== +*/ +qhandle_t RE_RegisterModel( const char *name ) { + model_t *mod; + unsigned *buf; + int lod; + int ident = 0; // TTimo: init + qboolean loaded; + qhandle_t hModel; + int numLoaded; + + if ( !name || !name[0] ) { + // Ridah, disabled this, we can see models that can't be found because they won't be there + //ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Model name exceeds MAX_QPATH\n" ); + return 0; + } + + // Ridah, caching + if ( r_cacheGathering->integer ) { + ri.Cmd_ExecuteText( EXEC_NOW, va( "cache_usedfile model %s\n", name ) ); + } + + // + // search the currently loaded models + // + for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) { + mod = tr.models[hModel]; + if ( !Q_stricmp( mod->name, name ) ) { + if ( mod->type == MOD_BAD ) { + return 0; + } + return hModel; + } + } + + // allocate a new model_t + + if ( ( mod = R_AllocModel() ) == NULL ) { + ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name ); + return 0; + } + + // only set the name after the model has been successfully loaded + Q_strncpyz( mod->name, name, sizeof( mod->name ) ); + + + // make sure the render thread is stopped + R_SyncRenderThread(); + + // Ridah, look for it cached + if ( R_FindCachedModel( name, mod ) ) { + return mod->index; + } + // done. + + mod->numLods = 0; + + // + // load the files + // + numLoaded = 0; + + if ( strstr( name, ".mds" ) ) { // try loading skeletal file + loaded = qfalse; + ri.FS_ReadFile( name, (void **)&buf ); + if ( buf ) { + loadmodel = mod; + + ident = LittleLong( *(unsigned *)buf ); + if ( ident == MDS_IDENT ) { + loaded = R_LoadMDS( mod, buf, name ); + } + + ri.FS_FreeFile( buf ); + } + + if ( loaded ) { + return mod->index; + } + } + + for ( lod = MD3_MAX_LODS - 1 ; lod >= 0 ; lod-- ) { + char filename[1024]; + + strcpy( filename, name ); + + if ( lod != 0 ) { + char namebuf[80]; + + if ( strrchr( filename, '.' ) ) { + *strrchr( filename, '.' ) = 0; + } + sprintf( namebuf, "_%d.md3", lod ); + strcat( filename, namebuf ); + } + + if ( r_compressModels->integer ) { + filename[strlen( filename ) - 1] = '3'; // try MD3 first + } else { + filename[strlen( filename ) - 1] = 'c'; // try MDC first + } + ri.FS_ReadFile( filename, (void **)&buf ); + + if ( !buf ) { + if ( r_compressModels->integer ) { + filename[strlen( filename ) - 1] = 'c'; // try MDC second + } else { + filename[strlen( filename ) - 1] = '3'; // try MD3 second + } + ri.FS_ReadFile( filename, (void **)&buf ); + if ( !buf ) { + continue; + } + } + + loadmodel = mod; + + ident = LittleLong( *(unsigned *)buf ); + // Ridah, mesh compression + if ( ident != MD3_IDENT && ident != MDC_IDENT ) { + ri.Printf( PRINT_WARNING,"RE_RegisterModel: unknown fileid for %s\n", name ); + goto fail; + } + + if ( ident == MD3_IDENT ) { + loaded = R_LoadMD3( mod, lod, buf, name ); + if ( r_compressModels->integer && r_exportCompressedModels->integer && mod->mdc[lod] ) { + // save it out + filename[strlen( filename ) - 1] = 'c'; + ri.FS_WriteFile( filename, mod->mdc[lod], mod->mdc[lod]->ofsEnd ); + // if building, open the file so it gets copied + if ( r_buildScript->integer ) { + ri.FS_ReadFile( filename, NULL ); + } + } + } else { + loaded = R_LoadMDC( mod, lod, buf, name ); + } + // done. + + ri.FS_FreeFile( buf ); + + if ( !loaded ) { + if ( lod == 0 ) { + goto fail; + } else { + break; + } + } else { + mod->numLods++; + numLoaded++; + // if we have a valid model and are biased + // so that we won't see any higher detail ones, + // stop loading them + if ( lod <= r_lodbias->integer ) { + break; + } + } + } + + + if ( numLoaded ) { + // duplicate into higher lod spots that weren't + // loaded, in case the user changes r_lodbias on the fly + for ( lod-- ; lod >= 0 ; lod-- ) { + mod->numLods++; + // Ridah, mesh compression + // this check for mod->md3[0] could leave mod->md3[0] == 0x0000000 if r_lodbias is set to anything except '0' + // which causes trouble in tr_mesh.c in R_AddMD3Surfaces() and other locations since it checks md3[0] + // for various things. + if ( ident == MD3_IDENT ) { //----(SA) modified +// if (mod->md3[0]) //----(SA) end + mod->md3[lod] = mod->md3[lod + 1]; + } else { + mod->mdc[lod] = mod->mdc[lod + 1]; + } + // done. + } + + return mod->index; + } + +fail: + // we still keep the model_t around, so if the model name is asked for + // again, we won't bother scanning the filesystem + mod->type = MOD_BAD; + return 0; +} + +//------------------------------------------------------------------------------- +// Ridah, mesh compression +float r_anormals[NUMMDCVERTEXNORMALS][3] = { +#include "anorms256.h" +}; + +/* +============= +R_MDC_GetVec +============= +*/ +void R_MDC_GetVec( unsigned char anorm, vec3_t dir ) { + VectorCopy( r_anormals[anorm], dir ); +} + +/* +============= +R_MDC_GetAnorm +============= +*/ +unsigned char R_MDC_GetAnorm( const vec3_t dir ) { + int i, best_start_i[3], next_start, next_end; + int best = 0; // TTimo: init + float best_diff, group_val, this_val, diff; + float *this_norm; + + // find best Z match + + if ( dir[2] > 0.097545f ) { + next_start = 144; + next_end = NUMMDCVERTEXNORMALS; + } else + { + next_start = 0; + next_end = 144; + } + + best_diff = 999; + this_val = -999; + + for ( i = next_start ; i < next_end ; i++ ) + { + if ( r_anormals[i][2] == this_val ) { + continue; + } else { + this_val = r_anormals[i][2]; + } + + if ( ( diff = fabs( dir[2] - r_anormals[i][2] ) ) < best_diff ) { + best_diff = diff; + best_start_i[2] = i; + + } + + if ( next_start ) { + if ( r_anormals[i][2] > dir[2] ) { + break; // we've gone passed the dir[2], so we can't possibly find a better match now + } + } else { + if ( r_anormals[i][2] < dir[2] ) { + break; // we've gone passed the dir[2], so we can't possibly find a better match now + } + } + } + + best_diff = -999; + + // find best match within the Z group + + for ( i = best_start_i[2], group_val = r_anormals[i][2]; i < NUMMDCVERTEXNORMALS; i++ ) + { + this_norm = r_anormals[i]; + + if ( this_norm[2] != group_val ) { + break; // done checking the group + } +/* + if ( (this_norm[0] < 0 && dir[0] > 0) + || (this_norm[0] > 0 && dir[0] < 0) + || (this_norm[1] < 0 && dir[1] > 0) + || (this_norm[1] > 0 && dir[1] < 0)) + continue; +*/ + diff = DotProduct( dir, this_norm ); + + if ( diff > best_diff ) { + best_diff = diff; + best = i; + } + } + + return (unsigned char)best; +} + +/* +================= +R_MDC_EncodeOfsVec +================= +*/ +qboolean R_MDC_EncodeXyzCompressed( const vec3_t vec, const vec3_t normal, mdcXyzCompressed_t *out ) { + mdcXyzCompressed_t retval; + int i; + unsigned char anorm; + + i = sizeof( mdcXyzCompressed_t ); + + retval.ofsVec = 0; + for ( i = 0; i < 3; i++ ) { + if ( fabs( vec[i] ) >= MDC_MAX_DIST ) { + return qfalse; + } + retval.ofsVec += ( ( (int)fabs( ( vec[i] + MDC_DIST_SCALE * 0.5 ) * ( 1.0 / MDC_DIST_SCALE ) + MDC_MAX_OFS ) ) << ( i * MDC_BITS_PER_AXIS ) ); + } + anorm = R_MDC_GetAnorm( normal ); + retval.ofsVec |= ( (int)anorm ) << 24; + + *out = retval; + return qtrue; +} + +/* +================= +R_MDC_DecodeXyzCompressed +================= +*/ +#if 0 // unoptimized version, used for finding right settings +void R_MDC_DecodeXyzCompressed( mdcXyzCompressed_t *xyzComp, vec3_t out, vec3_t normal ) { + int i; + + for ( i = 0; i < 3; i++ ) { + out[i] = ( (float)( ( xyzComp->ofsVec >> ( i * MDC_BITS_PER_AXIS ) ) & ( ( 1 << MDC_BITS_PER_AXIS ) - 1 ) ) - MDC_MAX_OFS ) * MDC_DIST_SCALE; + } + R_MDC_GetVec( ( unsigned char )( xyzComp->ofsVec >> 24 ), normal ); +} +#endif + +/* +================= +R_MDC_GetXyzCompressed +================= +*/ +static qboolean R_MDC_GetXyzCompressed( md3Header_t *md3, md3XyzNormal_t *newXyz, vec3_t oldPos, mdcXyzCompressed_t *out, qboolean verify ) { + vec3_t newPos, vec; + int i; + vec3_t pos, dir, norm, outnorm; + + for ( i = 0; i < 3; i++ ) { + newPos[i] = (float)newXyz->xyz[i] * MD3_XYZ_SCALE; + } + + VectorSubtract( newPos, oldPos, vec ); + R_LatLongToNormal( norm, newXyz->normal ); + if ( !R_MDC_EncodeXyzCompressed( vec, norm, out ) ) { + return qfalse; + } + + // calculate the uncompressed position + R_MDC_DecodeXyzCompressed( out->ofsVec, dir, outnorm ); + VectorAdd( oldPos, dir, pos ); + + if ( verify ) { + if ( Distance( newPos, pos ) > MDC_MAX_ERROR ) { + return qfalse; + } + } + + return qtrue; +} + + +/* +================= +R_MDC_CompressSurfaceFrame +================= +*/ +static qboolean R_MDC_CompressSurfaceFrame( md3Header_t *md3, md3Surface_t *surf, int frame, int lastBaseFrame, mdcXyzCompressed_t *out ) { + int i, j; + md3XyzNormal_t *xyz, *baseXyz; + vec3_t oldPos; + + xyz = ( md3XyzNormal_t * )( (byte *)surf + surf->ofsXyzNormals ); + baseXyz = xyz + ( lastBaseFrame * surf->numVerts ); + xyz += ( frame * surf->numVerts ); + + for ( i = 0; i < surf->numVerts; i++ ) { + for ( j = 0; j < 3; j++ ) { + oldPos[j] = (float)baseXyz[i].xyz[j] * MD3_XYZ_SCALE; + } + if ( !R_MDC_GetXyzCompressed( md3, &xyz[i], oldPos, &out[i], qtrue ) ) { + return qfalse; + } + } + + return qtrue; +} + +/* +================= +R_MDC_CanCompressSurfaceFrame +================= +*/ +static qboolean R_MDC_CanCompressSurfaceFrame( md3Header_t *md3, md3Surface_t *surf, int frame, int lastBaseFrame ) { + int i, j; + md3XyzNormal_t *xyz, *baseXyz; + mdcXyzCompressed_t xyzComp; + vec3_t oldPos; + + xyz = ( md3XyzNormal_t * )( (byte *)surf + surf->ofsXyzNormals ); + baseXyz = xyz + ( lastBaseFrame * surf->numVerts ); + xyz += ( frame * surf->numVerts ); + + for ( i = 0; i < surf->numVerts; i++ ) { + for ( j = 0; j < 3; j++ ) { + oldPos[j] = (float)baseXyz[i].xyz[j] * MD3_XYZ_SCALE; + } + if ( !R_MDC_GetXyzCompressed( md3, &xyz[i], oldPos, &xyzComp, qtrue ) ) { + return qfalse; + } + } + + return qtrue; +} + +/* +================= +R_MD3toMDC + + Converts a model_t from md3 to mdc format +================= +*/ +static qboolean R_MDC_ConvertMD3( model_t *mod, int lod, const char *mod_name ) { + int i, j, f, c, k; + md3Surface_t *surf; + md3Header_t *md3; + int *baseFrames; + int numBaseFrames; + + qboolean foundBase; + + mdcHeader_t *mdc, mdcHeader; + mdcSurface_t *cSurf; + short *frameBaseFrames, *frameCompFrames; + + mdcTag_t *mdcTag; + md3Tag_t *md3Tag; + + vec3_t axis[3], angles; + float ftemp; + + md3 = mod->md3[lod]; + + baseFrames = ri.Hunk_AllocateTempMemory( sizeof( *baseFrames ) * md3->numFrames ); + + // the first frame is always a base frame + numBaseFrames = 0; + memset( baseFrames, 0, sizeof( *baseFrames ) * md3->numFrames ); + baseFrames[numBaseFrames++] = 0; + + // first calculate how many baseframes we need, and which frames they are on + // we need to treat the entire model as a single surface, if we compress some surfaces, and not others, + // we may get tearing between surfaces + + for ( f = 1; f < md3->numFrames; f++ ) { + + surf = ( md3Surface_t * )( (byte *)md3 + md3->ofsSurfaces ); + foundBase = qfalse; + for ( i = 0 ; i < md3->numSurfaces ; i++ ) { + + // process the verts in this surface, checking to see if the compressed + // version will be close enough to the actual vert + if ( !foundBase && !R_MDC_CanCompressSurfaceFrame( md3, surf, f, baseFrames[numBaseFrames - 1] ) ) { + baseFrames[numBaseFrames++] = f; + foundBase = qtrue; + } + + // find the next surface + surf = ( md3Surface_t * )( (byte *)surf + surf->ofsEnd ); + } + + } + + // success, so fill in the necessary data to the model_t + mdcHeader.ident = MDC_IDENT; + mdcHeader.version = MDC_VERSION; + Q_strncpyz( mdcHeader.name, md3->name, sizeof( mdcHeader.name ) ); + mdcHeader.flags = md3->flags; + mdcHeader.numFrames = md3->numFrames; + mdcHeader.numTags = md3->numTags; + mdcHeader.numSurfaces = md3->numSurfaces; + mdcHeader.numSkins = md3->numSkins; + mdcHeader.ofsFrames = sizeof( mdcHeader_t ); + mdcHeader.ofsTagNames = mdcHeader.ofsFrames + mdcHeader.numFrames * sizeof( md3Frame_t ); + mdcHeader.ofsTags = mdcHeader.ofsTagNames + mdcHeader.numTags * sizeof( mdcTagName_t ); + mdcHeader.ofsSurfaces = mdcHeader.ofsTags + mdcHeader.numTags * mdcHeader.numFrames * sizeof( mdcTag_t ); + mdcHeader.ofsEnd = mdcHeader.ofsSurfaces; + + surf = ( md3Surface_t * )( (byte *)md3 + md3->ofsSurfaces ); + for ( f = 0; f < md3->numSurfaces; f++ ) { + mdcHeader.ofsEnd += sizeof( mdcSurface_t ) + + surf->numShaders * sizeof( md3Shader_t ) + + surf->numTriangles * sizeof( md3Triangle_t ) + + surf->numVerts * sizeof( md3St_t ) + + surf->numVerts * numBaseFrames * sizeof( md3XyzNormal_t ) + + surf->numVerts * ( md3->numFrames - numBaseFrames ) * sizeof( mdcXyzCompressed_t ) + + sizeof( short ) * md3->numFrames + + sizeof( short ) * md3->numFrames; + + surf = ( md3Surface_t * )( (byte *)surf + surf->ofsEnd ); + } + + // report the memory differences + Com_Printf( "Compressed %s. Old = %i, New = %i\n", mod_name, md3->ofsEnd, mdcHeader.ofsEnd ); + + mdc = ri.Hunk_Alloc( mdcHeader.ofsEnd, h_low ); + mod->mdc[lod] = mdc; + + // we have the memory allocated, so lets fill it in + + // header info + *mdc = mdcHeader; + // frames + memcpy( ( md3Frame_t * )( (byte *)mdc + mdc->ofsFrames ), ( md3Frame_t * )( (byte *)md3 + md3->ofsFrames ), mdcHeader.numFrames * sizeof( md3Frame_t ) ); + // tag names + for ( j = 0; j < md3->numTags; j++ ) { + memcpy( ( mdcTagName_t * )( (byte *)mdc + mdc->ofsTagNames ) + j, ( ( md3Tag_t * )( (byte *)md3 + md3->ofsTags ) + j )->name, sizeof( mdcTagName_t ) ); + } + // tags + mdcTag = ( ( mdcTag_t * )( (byte *)mdc + mdc->ofsTags ) ); + md3Tag = ( ( md3Tag_t * )( (byte *)md3 + md3->ofsTags ) ); + for ( f = 0; f < md3->numFrames; f++ ) { + for ( j = 0; j < md3->numTags; j++, mdcTag++, md3Tag++ ) { + for ( k = 0; k < 3; k++ ) { + // origin + ftemp = md3Tag->origin[k] / MD3_XYZ_SCALE; + mdcTag->xyz[k] = (short)ftemp; + // axis + VectorCopy( md3Tag->axis[k], axis[k] ); + } + // convert the axis to angles + AxisToAngles( axis, angles ); + // copy them into the new tag + for ( k = 0; k < 3; k++ ) { + mdcTag->angles[k] = angles[k] / MDC_TAG_ANGLE_SCALE; + } + } + } + // surfaces + surf = ( md3Surface_t * )( (byte *)md3 + md3->ofsSurfaces ); + cSurf = ( mdcSurface_t * )( (byte *)mdc + mdc->ofsSurfaces ); + for ( j = 0 ; j < md3->numSurfaces ; j++ ) { + + cSurf->ident = SF_MDC; + Q_strncpyz( cSurf->name, surf->name, sizeof( cSurf->name ) ); + cSurf->flags = surf->flags; + cSurf->numCompFrames = ( mdc->numFrames - numBaseFrames ); + cSurf->numBaseFrames = numBaseFrames; + cSurf->numShaders = surf->numShaders; + cSurf->numVerts = surf->numVerts; + cSurf->numTriangles = surf->numTriangles; + cSurf->ofsTriangles = sizeof( mdcSurface_t ); + cSurf->ofsShaders = cSurf->ofsTriangles + cSurf->numTriangles * sizeof( md3Triangle_t ); + cSurf->ofsSt = cSurf->ofsShaders + cSurf->numShaders * sizeof( md3Shader_t ); + cSurf->ofsXyzNormals = cSurf->ofsSt + cSurf->numVerts * sizeof( md3St_t ); + cSurf->ofsXyzCompressed = cSurf->ofsXyzNormals + cSurf->numVerts * numBaseFrames * sizeof( md3XyzNormal_t ); + cSurf->ofsFrameBaseFrames = cSurf->ofsXyzCompressed + cSurf->numVerts * ( mdc->numFrames - numBaseFrames ) * sizeof( mdcXyzCompressed_t ); + cSurf->ofsFrameCompFrames = cSurf->ofsFrameBaseFrames + mdc->numFrames * sizeof( short ); + cSurf->ofsEnd = cSurf->ofsFrameCompFrames + mdc->numFrames * sizeof( short ); + + // triangles + memcpy( (byte *)cSurf + cSurf->ofsTriangles, (byte *)surf + surf->ofsTriangles, cSurf->numTriangles * sizeof( md3Triangle_t ) ); + // shaders + memcpy( (byte *)cSurf + cSurf->ofsShaders, (byte *)surf + surf->ofsShaders, cSurf->numShaders * sizeof( md3Shader_t ) ); + // st + memcpy( (byte *)cSurf + cSurf->ofsSt, (byte *)surf + surf->ofsSt, cSurf->numVerts * sizeof( md3St_t ) ); + + // rest + frameBaseFrames = ( short * )( (byte *)cSurf + cSurf->ofsFrameBaseFrames ); + frameCompFrames = ( short * )( (byte *)cSurf + cSurf->ofsFrameCompFrames ); + for ( f = 0, i = 0, c = 0; f < md3->numFrames; f++ ) { + if ( i < numBaseFrames && f == baseFrames[i] ) { + // copy this baseFrame from the md3 + memcpy( (byte *)cSurf + cSurf->ofsXyzNormals + ( sizeof( md3XyzNormal_t ) * cSurf->numVerts * i ), + (byte *)surf + surf->ofsXyzNormals + ( sizeof( md3XyzNormal_t ) * cSurf->numVerts * f ), + sizeof( md3XyzNormal_t ) * cSurf->numVerts ); + i++; + frameCompFrames[f] = -1; + frameBaseFrames[f] = i - 1; + } else { + if ( !R_MDC_CompressSurfaceFrame( md3, surf, f, baseFrames[i - 1], ( mdcXyzCompressed_t * )( (byte *)cSurf + cSurf->ofsXyzCompressed + sizeof( mdcXyzCompressed_t ) * cSurf->numVerts * c ) ) ) { + ri.Error( ERR_DROP, "R_MDC_ConvertMD3: tried to compress an unsuitable frame\n" ); + } + frameCompFrames[f] = c; + frameBaseFrames[f] = i - 1; + c++; + } + } + + // find the next surface + surf = ( md3Surface_t * )( (byte *)surf + surf->ofsEnd ); + cSurf = ( mdcSurface_t * )( (byte *)cSurf + cSurf->ofsEnd ); + } + + mod->type = MOD_MDC; + + // free allocated memory + ri.Hunk_FreeTempMemory( baseFrames ); + + // kill the md3 memory + ri.Hunk_FreeTempMemory( md3 ); + mod->md3[lod] = NULL; + + return qtrue; +} + +/* +================= +R_LoadMDC +================= +*/ +static qboolean R_LoadMDC( model_t *mod, int lod, void *buffer, const char *mod_name ) { + int i, j; + mdcHeader_t *pinmodel; + md3Frame_t *frame; + mdcSurface_t *surf; + md3Shader_t *shader; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + mdcXyzCompressed_t *xyzComp; + mdcTag_t *tag; + short *ps; + int version; + int size; + + pinmodel = (mdcHeader_t *)buffer; + + version = LittleLong( pinmodel->version ); + if ( version != MDC_VERSION ) { + ri.Printf( PRINT_WARNING, "R_LoadMDC: %s has wrong version (%i should be %i)\n", + mod_name, version, MDC_VERSION ); + return qfalse; + } + + mod->type = MOD_MDC; + size = LittleLong( pinmodel->ofsEnd ); + mod->dataSize += size; + mod->mdc[lod] = ri.Hunk_Alloc( size, h_low ); + + memcpy( mod->mdc[lod], buffer, LittleLong( pinmodel->ofsEnd ) ); + + LL( mod->mdc[lod]->ident ); + LL( mod->mdc[lod]->version ); + LL( mod->mdc[lod]->numFrames ); + LL( mod->mdc[lod]->numTags ); + LL( mod->mdc[lod]->numSurfaces ); + LL( mod->mdc[lod]->ofsFrames ); + LL( mod->mdc[lod]->ofsTagNames ); + LL( mod->mdc[lod]->ofsTags ); + LL( mod->mdc[lod]->ofsSurfaces ); + LL( mod->mdc[lod]->ofsEnd ); + LL( mod->mdc[lod]->flags ); + LL( mod->mdc[lod]->numSkins ); + + + if ( mod->mdc[lod]->numFrames < 1 ) { + ri.Printf( PRINT_WARNING, "R_LoadMDC: %s has no frames\n", mod_name ); + return qfalse; + } + + // swap all the frames + frame = ( md3Frame_t * )( (byte *)mod->mdc[lod] + mod->mdc[lod]->ofsFrames ); + for ( i = 0 ; i < mod->mdc[lod]->numFrames ; i++, frame++ ) { + frame->radius = LittleFloat( frame->radius ); + if ( strstr( mod->name,"sherman" ) || strstr( mod->name, "mg42" ) ) { + frame->radius = 256; + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = 128; + frame->bounds[1][j] = -128; + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } else + { + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + } + + // swap all the tags + tag = ( mdcTag_t * )( (byte *)mod->mdc[lod] + mod->mdc[lod]->ofsTags ); + if ( LittleLong( 1 ) != 1 ) { + for ( i = 0 ; i < mod->mdc[lod]->numTags * mod->mdc[lod]->numFrames ; i++, tag++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->xyz[j] = LittleShort( tag->xyz[j] ); + tag->angles[j] = LittleShort( tag->angles[j] ); + } + } + } + + // swap all the surfaces + surf = ( mdcSurface_t * )( (byte *)mod->mdc[lod] + mod->mdc[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->mdc[lod]->numSurfaces ; i++ ) { + + LL( surf->ident ); + LL( surf->flags ); + LL( surf->numBaseFrames ); + LL( surf->numCompFrames ); + LL( surf->numShaders ); + LL( surf->numTriangles ); + LL( surf->ofsTriangles ); + LL( surf->numVerts ); + LL( surf->ofsShaders ); + LL( surf->ofsSt ); + LL( surf->ofsXyzNormals ); + LL( surf->ofsXyzCompressed ); + LL( surf->ofsFrameBaseFrames ); + LL( surf->ofsFrameCompFrames ); + LL( surf->ofsEnd ); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + ri.Error( ERR_DROP, "R_LoadMDC: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles * 3 > SHADER_MAX_INDEXES ) { + ri.Error( ERR_DROP, "R_LoadMDC: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MDC; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j - 2] == '_' ) { + surf->name[j - 2] = 0; + } + + // register the shaders + shader = ( md3Shader_t * )( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + } + + // Ridah, optimization, only do the swapping if we really need to + if ( LittleShort( 1 ) != 1 ) { + + // swap all the triangles + tri = ( md3Triangle_t * )( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL( tri->indexes[0] ); + LL( tri->indexes[1] ); + LL( tri->indexes[2] ); + } + + // swap all the ST + st = ( md3St_t * )( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = ( md3XyzNormal_t * )( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numBaseFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } + + // swap all the XyzCompressed + xyzComp = ( mdcXyzCompressed_t * )( (byte *)surf + surf->ofsXyzCompressed ); + for ( j = 0 ; j < surf->numVerts * surf->numCompFrames ; j++, xyzComp++ ) + { + LL( xyzComp->ofsVec ); + } + + // swap the frameBaseFrames + ps = ( short * )( (byte *)surf + surf->ofsFrameBaseFrames ); + for ( j = 0; j < mod->mdc[lod]->numFrames; j++, ps++ ) + { + *ps = LittleShort( *ps ); + } + + // swap the frameCompFrames + ps = ( short * )( (byte *)surf + surf->ofsFrameCompFrames ); + for ( j = 0; j < mod->mdc[lod]->numFrames; j++, ps++ ) + { + *ps = LittleShort( *ps ); + } + } + // done. + + // find the next surface + surf = ( mdcSurface_t * )( (byte *)surf + surf->ofsEnd ); + } + + return qtrue; +} + +// done. +//------------------------------------------------------------------------------- + +/* +================= +R_LoadMD3 +================= +*/ +static qboolean R_LoadMD3( model_t *mod, int lod, void *buffer, const char *mod_name ) { + int i, j; + md3Header_t *pinmodel; + md3Frame_t *frame; + md3Surface_t *surf; + md3Shader_t *shader; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + md3Tag_t *tag; + int version; + int size; + qboolean fixRadius = qfalse; + + pinmodel = (md3Header_t *)buffer; + + version = LittleLong( pinmodel->version ); + if ( version != MD3_VERSION ) { + ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n", + mod_name, version, MD3_VERSION ); + return qfalse; + } + + mod->type = MOD_MESH; + size = LittleLong( pinmodel->ofsEnd ); + mod->dataSize += size; + // Ridah, convert to compressed format + if ( !r_compressModels->integer ) { + mod->md3[lod] = ri.Hunk_Alloc( size, h_low ); + } else { + mod->md3[lod] = ri.Hunk_AllocateTempMemory( size ); + } + // done. + + memcpy( mod->md3[lod], buffer, LittleLong( pinmodel->ofsEnd ) ); + + LL( mod->md3[lod]->ident ); + LL( mod->md3[lod]->version ); + LL( mod->md3[lod]->numFrames ); + LL( mod->md3[lod]->numTags ); + LL( mod->md3[lod]->numSurfaces ); + LL( mod->md3[lod]->ofsFrames ); + LL( mod->md3[lod]->ofsTags ); + LL( mod->md3[lod]->ofsSurfaces ); + LL( mod->md3[lod]->ofsEnd ); + + if ( mod->md3[lod]->numFrames < 1 ) { + ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has no frames\n", mod_name ); + return qfalse; + } + + if ( strstr( mod->name,"sherman" ) || strstr( mod->name, "mg42" ) ) { + fixRadius = qtrue; + } + + // swap all the frames + frame = ( md3Frame_t * )( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames ); + for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++ ) { + frame->radius = LittleFloat( frame->radius ); + if ( fixRadius ) { + frame->radius = 256; + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = 128; + frame->bounds[1][j] = -128; + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + // Hack for Bug using plugin generated model + else if ( frame->radius == 1 ) { + frame->radius = 256; + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = 128; + frame->bounds[1][j] = -128; + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } else + { + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + } + + // swap all the tags + tag = ( md3Tag_t * )( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags ); + for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->origin[j] = LittleFloat( tag->origin[j] ); + tag->axis[0][j] = LittleFloat( tag->axis[0][j] ); + tag->axis[1][j] = LittleFloat( tag->axis[1][j] ); + tag->axis[2][j] = LittleFloat( tag->axis[2][j] ); + } + } + + // swap all the surfaces + surf = ( md3Surface_t * )( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++ ) { + + LL( surf->ident ); + LL( surf->flags ); + LL( surf->numFrames ); + LL( surf->numShaders ); + LL( surf->numTriangles ); + LL( surf->ofsTriangles ); + LL( surf->numVerts ); + LL( surf->ofsShaders ); + LL( surf->ofsSt ); + LL( surf->ofsXyzNormals ); + LL( surf->ofsEnd ); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + ri.Error( ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles * 3 > SHADER_MAX_INDEXES ) { + ri.Error( ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MD3; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j - 2] == '_' ) { + surf->name[j - 2] = 0; + } + + // register the shaders + shader = ( md3Shader_t * )( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + } + + // Ridah, optimization, only do the swapping if we really need to + if ( LittleShort( 1 ) != 1 ) { + + // swap all the triangles + tri = ( md3Triangle_t * )( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL( tri->indexes[0] ); + LL( tri->indexes[1] ); + LL( tri->indexes[2] ); + } + + // swap all the ST + st = ( md3St_t * )( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = ( md3XyzNormal_t * )( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } + + } + // done. + + // find the next surface + surf = ( md3Surface_t * )( (byte *)surf + surf->ofsEnd ); + } + + // Ridah, convert to compressed format + if ( r_compressModels->integer ) { + R_MDC_ConvertMD3( mod, lod, mod_name ); + } + // done. + + return qtrue; +} + + + +/* +================= +R_LoadMDS +================= +*/ +static qboolean R_LoadMDS( model_t *mod, void *buffer, const char *mod_name ) { + int i, j, k; + mdsHeader_t *pinmodel, *mds; + mdsFrame_t *frame; + mdsSurface_t *surf; + mdsTriangle_t *tri; + mdsVertex_t *v; + mdsBoneInfo_t *bi; + mdsTag_t *tag; + int version; + int size; + shader_t *sh; + int frameSize; + int *collapseMap, *boneref; + + pinmodel = (mdsHeader_t *)buffer; + + version = LittleLong( pinmodel->version ); + if ( version != MDS_VERSION ) { + ri.Printf( PRINT_WARNING, "R_LoadMDS: %s has wrong version (%i should be %i)\n", + mod_name, version, MDS_VERSION ); + return qfalse; + } + + mod->type = MOD_MDS; + size = LittleLong( pinmodel->ofsEnd ); + mod->dataSize += size; + mds = mod->mds = ri.Hunk_Alloc( size, h_low ); + + memcpy( mds, buffer, LittleLong( pinmodel->ofsEnd ) ); + + LL( mds->ident ); + LL( mds->version ); + LL( mds->numFrames ); + LL( mds->numBones ); + LL( mds->numTags ); + LL( mds->numSurfaces ); + LL( mds->ofsFrames ); + LL( mds->ofsBones ); + LL( mds->ofsTags ); + LL( mds->ofsEnd ); + LL( mds->ofsSurfaces ); + mds->lodBias = LittleFloat( mds->lodBias ); + mds->lodScale = LittleFloat( mds->lodScale ); + LL( mds->torsoParent ); + + if ( mds->numFrames < 1 ) { + ri.Printf( PRINT_WARNING, "R_LoadMDS: %s has no frames\n", mod_name ); + return qfalse; + } + + if ( LittleLong( 1 ) != 1 ) { + // swap all the frames + //frameSize = (int)( &((mdsFrame_t *)0)->bones[ mds->numBones ] ); + frameSize = (int) ( sizeof( mdsFrame_t ) - sizeof( mdsBoneFrameCompressed_t ) + mds->numBones * sizeof( mdsBoneFrameCompressed_t ) ); + for ( i = 0 ; i < mds->numFrames ; i++, frame++ ) { + frame = ( mdsFrame_t * )( (byte *)mds + mds->ofsFrames + i * frameSize ); + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + frame->parentOffset[j] = LittleFloat( frame->parentOffset[j] ); + } + for ( j = 0 ; j < mds->numBones * sizeof( mdsBoneFrameCompressed_t ) / sizeof( short ) ; j++ ) { + ( (short *)frame->bones )[j] = LittleShort( ( (short *)frame->bones )[j] ); + } + } + + // swap all the tags + tag = ( mdsTag_t * )( (byte *)mds + mds->ofsTags ); + for ( i = 0 ; i < mds->numTags ; i++, tag++ ) { + LL( tag->boneIndex ); + tag->torsoWeight = LittleFloat( tag->torsoWeight ); + } + + // swap all the bones + for ( i = 0 ; i < mds->numBones ; i++, bi++ ) { + bi = ( mdsBoneInfo_t * )( (byte *)mds + mds->ofsBones + i * sizeof( mdsBoneInfo_t ) ); + LL( bi->parent ); + bi->torsoWeight = LittleFloat( bi->torsoWeight ); + bi->parentDist = LittleFloat( bi->parentDist ); + LL( bi->flags ); + } + } + + // swap all the surfaces + surf = ( mdsSurface_t * )( (byte *)mds + mds->ofsSurfaces ); + for ( i = 0 ; i < mds->numSurfaces ; i++ ) { + if ( LittleLong( 1 ) != 1 ) { + LL( surf->ident ); + LL( surf->shaderIndex ); + LL( surf->minLod ); + LL( surf->ofsHeader ); + LL( surf->ofsCollapseMap ); + LL( surf->numTriangles ); + LL( surf->ofsTriangles ); + LL( surf->numVerts ); + LL( surf->ofsVerts ); + LL( surf->numBoneReferences ); + LL( surf->ofsBoneReferences ); + LL( surf->ofsEnd ); + } + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + ri.Error( ERR_DROP, "R_LoadMDS: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles * 3 > SHADER_MAX_INDEXES ) { + ri.Error( ERR_DROP, "R_LoadMDS: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // register the shaders + if ( surf->shader[0] ) { + sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + surf->shaderIndex = 0; + } else { + surf->shaderIndex = sh->index; + } + } else { + surf->shaderIndex = 0; + } + + if ( LittleLong( 1 ) != 1 ) { + // swap all the triangles + tri = ( mdsTriangle_t * )( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL( tri->indexes[0] ); + LL( tri->indexes[1] ); + LL( tri->indexes[2] ); + } + + // swap all the vertexes + v = ( mdsVertex_t * )( (byte *)surf + surf->ofsVerts ); + for ( j = 0 ; j < surf->numVerts ; j++ ) { + v->normal[0] = LittleFloat( v->normal[0] ); + v->normal[1] = LittleFloat( v->normal[1] ); + v->normal[2] = LittleFloat( v->normal[2] ); + + v->texCoords[0] = LittleFloat( v->texCoords[0] ); + v->texCoords[1] = LittleFloat( v->texCoords[1] ); + + v->numWeights = LittleLong( v->numWeights ); + + for ( k = 0 ; k < v->numWeights ; k++ ) { + v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex ); + v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight ); + v->weights[k].offset[0] = LittleFloat( v->weights[k].offset[0] ); + v->weights[k].offset[1] = LittleFloat( v->weights[k].offset[1] ); + v->weights[k].offset[2] = LittleFloat( v->weights[k].offset[2] ); + } + + // find the fixedParent for this vert (if exists) + v->fixedParent = -1; + if ( v->numWeights == 2 ) { + // find the closest parent + if ( VectorLength( v->weights[0].offset ) < VectorLength( v->weights[1].offset ) ) { + v->fixedParent = 0; + } else { + v->fixedParent = 1; + } + v->fixedDist = VectorLength( v->weights[v->fixedParent].offset ); + } + + v = (mdsVertex_t *)&v->weights[v->numWeights]; + } + + // swap the collapse map + collapseMap = ( int * )( (byte *)surf + surf->ofsCollapseMap ); + for ( j = 0; j < surf->numVerts; j++, collapseMap++ ) { + *collapseMap = LittleLong( *collapseMap ); + } + + // swap the bone references + boneref = ( int * )( ( byte *)surf + surf->ofsBoneReferences ); + for ( j = 0; j < surf->numBoneReferences; j++, boneref++ ) { + *boneref = LittleLong( *boneref ); + } + } + + // find the next surface + surf = ( mdsSurface_t * )( (byte *)surf + surf->ofsEnd ); + } + + return qtrue; +} + + + + +//============================================================================= + +/* +** RE_BeginRegistration +*/ +void RE_BeginRegistration( glconfig_t *glconfigOut ) { + ri.Hunk_Clear(); // (SA) MEM NOTE: not in missionpack + + R_Init(); + *glconfigOut = glConfig; + + R_SyncRenderThread(); + + tr.viewCluster = -1; // force markleafs to regenerate + R_ClearFlares(); + RE_ClearScene(); + + tr.registered = qtrue; + + // NOTE: this sucks, for some reason the first stretch pic is never drawn + // without this we'd see a white flash on a level load because the very + // first time the level shot would not be drawn + RE_StretchPic( 0, 0, 0, 0, 0, 0, 1, 1, 0 ); +} + +/* +=============== +R_ModelInit +=============== +*/ +void R_ModelInit( void ) { + model_t *mod; + + // leave a space for NULL model + tr.numModels = 0; + + mod = R_AllocModel(); + mod->type = MOD_BAD; + + // Ridah, load in the cacheModels + R_LoadCacheModels(); + // done. +} + + +/* +================ +R_Modellist_f +================ +*/ + +void R_Modellist_f( void ) { + int i, j; + model_t *mod; + int total; + int lods; + + total = 0; + for ( i = 1 ; i < tr.numModels; i++ ) { + mod = tr.models[i]; + lods = 1; + for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) { + if ( mod->md3[j] && mod->md3[j] != mod->md3[j - 1] ) { + lods++; + } + } + ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name ); + total += mod->dataSize; + } + ri.Printf( PRINT_ALL, "%8i : Total models\n", total ); + +#if 0 // not working right with new hunk + if ( tr.world ) { + ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name ); + } +#endif +} + + +//============================================================================= + + +/* +================ +R_GetTag +================ +*/ +static int R_GetTag( byte *mod, int frame, const char *tagName, int startTagIndex, md3Tag_t **outTag ) { + md3Tag_t *tag; + int i; + md3Header_t *md3; + + md3 = (md3Header_t *) mod; + + if ( frame >= md3->numFrames ) { + // it is possible to have a bad frame while changing models, so don't error + frame = md3->numFrames - 1; + } + + if ( startTagIndex > md3->numTags ) { + *outTag = NULL; + return -1; + } + + tag = ( md3Tag_t * )( (byte *)mod + md3->ofsTags ) + frame * md3->numTags; + for ( i = 0 ; i < md3->numTags ; i++, tag++ ) { + if ( ( i >= startTagIndex ) && !strcmp( tag->name, tagName ) ) { + + // if we are looking for an indexed tag, wait until we find the correct number of matches + //if (startTagIndex) { + // startTagIndex--; + // continue; + //} + + *outTag = tag; + return i; // found it + } + } + + *outTag = NULL; + return -1; +} + +/* +================ +R_GetMDCTag +================ +*/ +static int R_GetMDCTag( byte *mod, int frame, const char *tagName, int startTagIndex, mdcTag_t **outTag ) { + mdcTag_t *tag; + mdcTagName_t *pTagName; + int i; + mdcHeader_t *mdc; + + mdc = (mdcHeader_t *) mod; + + if ( frame >= mdc->numFrames ) { + // it is possible to have a bad frame while changing models, so don't error + frame = mdc->numFrames - 1; + } + + if ( startTagIndex > mdc->numTags ) { + *outTag = NULL; + return -1; + } + + pTagName = ( mdcTagName_t * )( (byte *)mod + mdc->ofsTagNames ); + for ( i = 0 ; i < mdc->numTags ; i++, pTagName++ ) { + if ( ( i >= startTagIndex ) && !strcmp( pTagName->name, tagName ) ) { + break; // found it + } + } + + if ( i >= mdc->numTags ) { + *outTag = NULL; + return -1; + } + + tag = ( mdcTag_t * )( (byte *)mod + mdc->ofsTags ) + frame * mdc->numTags + i; + *outTag = tag; + return i; +} + +/* +================ +R_GetMDSTag +================ +*/ +/* +// TTimo: unused +static int R_GetMDSTag( byte *mod, const char *tagName, int startTagIndex, mdsTag_t **outTag ) { + mdsTag_t *tag; + int i; + mdsHeader_t *mds; + + mds = (mdsHeader_t *) mod; + + if (startTagIndex > mds->numTags) { + *outTag = NULL; + return -1; + } + + tag = (mdsTag_t *)((byte *)mod + mds->ofsTags); + for ( i = 0 ; i < mds->numTags ; i++ ) { + if ( (i >= startTagIndex) && !strcmp( tag->name, tagName ) ) { + break; // found it + } + + tag = (mdsTag_t *) ((byte *)tag + sizeof(mdsTag_t) - sizeof(mdsBoneFrameCompressed_t) + mds->numFrames * sizeof(mdsBoneFrameCompressed_t) ); + } + + if (i >= mds->numTags) { + *outTag = NULL; + return -1; + } + + *outTag = tag; + return i; +} +*/ + +/* +================ +R_LerpTag + + returns the index of the tag it found, for cycling through tags with the same name +================ +*/ +int R_LerpTag( orientation_t *tag, const refEntity_t *refent, const char *tagNameIn, int startIndex ) { + md3Tag_t *start, *end; + md3Tag_t ustart, uend; + int i; + float frontLerp, backLerp; + model_t *model; + vec3_t sangles, eangles; + char tagName[MAX_QPATH]; //, *ch; + int retval; + qhandle_t handle; + int startFrame, endFrame; + float frac; + + handle = refent->hModel; + startFrame = refent->oldframe; + endFrame = refent->frame; + frac = 1.0 - refent->backlerp; + + Q_strncpyz( tagName, tagNameIn, MAX_QPATH ); +/* + // if the tagName has a space in it, then it is passing through the starting tag number + if (ch = strrchr(tagName, ' ')) { + *ch = 0; + ch++; + startIndex = atoi(ch); + } +*/ + model = R_GetModelByHandle( handle ); + if ( !model->md3[0] && !model->mdc[0] && !model->mds ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return -1; + } + + frontLerp = frac; + backLerp = 1.0 - frac; + + if ( model->type == MOD_MESH ) { + // old MD3 style + retval = R_GetTag( (byte *)model->md3[0], startFrame, tagName, startIndex, &start ); + retval = R_GetTag( (byte *)model->md3[0], endFrame, tagName, startIndex, &end ); + + } else if ( model->type == MOD_MDS ) { // use bone lerping + + retval = R_GetBoneTag( tag, model->mds, startIndex, refent, tagNameIn ); + + if ( retval >= 0 ) { + return retval; + } + + // failed + return -1; + + } else { + // psuedo-compressed MDC tags + mdcTag_t *cstart, *cend; + + retval = R_GetMDCTag( (byte *)model->mdc[0], startFrame, tagName, startIndex, &cstart ); + retval = R_GetMDCTag( (byte *)model->mdc[0], endFrame, tagName, startIndex, &cend ); + + // uncompress the MDC tags into MD3 style tags + if ( cstart && cend ) { + for ( i = 0; i < 3; i++ ) { + ustart.origin[i] = (float)cstart->xyz[i] * MD3_XYZ_SCALE; + uend.origin[i] = (float)cend->xyz[i] * MD3_XYZ_SCALE; + sangles[i] = (float)cstart->angles[i] * MDC_TAG_ANGLE_SCALE; + eangles[i] = (float)cend->angles[i] * MDC_TAG_ANGLE_SCALE; + } + + AnglesToAxis( sangles, ustart.axis ); + AnglesToAxis( eangles, uend.axis ); + + start = &ustart; + end = &uend; + } else { + start = NULL; + end = NULL; + } + + } + + if ( !start || !end ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return -1; + } + + for ( i = 0 ; i < 3 ; i++ ) { + tag->origin[i] = start->origin[i] * backLerp + end->origin[i] * frontLerp; + tag->axis[0][i] = start->axis[0][i] * backLerp + end->axis[0][i] * frontLerp; + tag->axis[1][i] = start->axis[1][i] * backLerp + end->axis[1][i] * frontLerp; + tag->axis[2][i] = start->axis[2][i] * backLerp + end->axis[2][i] * frontLerp; + } + + VectorNormalize( tag->axis[0] ); + VectorNormalize( tag->axis[1] ); + VectorNormalize( tag->axis[2] ); + + return retval; +} + +/* +=============== +R_TagInfo_f +=============== +*/ +void R_TagInfo_f( void ) { + + Com_Printf( "command not functional\n" ); + +/* + int handle; + orientation_t tag; + int frame = -1; + + if (ri.Cmd_Argc() < 3) { + Com_Printf("usage: taginfo \n"); + return; + } + + handle = RE_RegisterModel( ri.Cmd_Argv(1) ); + + if (handle) { + Com_Printf("found model %s..\n", ri.Cmd_Argv(1)); + } else { + Com_Printf("cannot find model %s\n", ri.Cmd_Argv(1)); + return; + } + + if (ri.Cmd_Argc() < 3) { + frame = 0; + } else { + frame = atoi(ri.Cmd_Argv(3)); + } + + Com_Printf("using frame %i..\n", frame); + + R_LerpTag( &tag, handle, frame, frame, 0.0, (const char *)ri.Cmd_Argv(2) ); + + Com_Printf("%s at position: %.1f %.1f %.1f\n", ri.Cmd_Argv(2), tag.origin[0], tag.origin[1], tag.origin[2] ); +*/ +} + +/* +==================== +R_ModelBounds +==================== +*/ +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) { + model_t *model; + md3Header_t *header; + md3Frame_t *frame; + + model = R_GetModelByHandle( handle ); + + if ( model->bmodel ) { + VectorCopy( model->bmodel->bounds[0], mins ); + VectorCopy( model->bmodel->bounds[1], maxs ); + return; + } + + // Ridah + if ( model->md3[0] ) { + header = model->md3[0]; + + frame = ( md3Frame_t * )( (byte *)header + header->ofsFrames ); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + return; + } else if ( model->mdc[0] ) { + frame = ( md3Frame_t * )( (byte *)model->mdc[0] + model->mdc[0]->ofsFrames ); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + return; + } + + VectorClear( mins ); + VectorClear( maxs ); + // done. +} + +//--------------------------------------------------------------------------- +// Virtual Memory, used for model caching, since we can't allocate them +// in the main Hunk (since it gets cleared on level changes), and they're +// too large to go into the Zone, we have a special memory chunk just for +// caching models in between levels. +// +// Optimized for Win32 systems, so that they'll grow the swapfile at startup +// if needed, but won't actually commit it until it's needed. +// +// GOAL: reserve a big chunk of virtual memory for the media cache, and only +// use it when we actually need it. This will make sure the swap file grows +// at startup if needed, rather than each allocation we make. +byte *membase = NULL; +int hunkmaxsize; +int cursize; + +#define R_HUNK_MEGS 24 +#define R_HUNK_SIZE ( R_HUNK_MEGS*1024*1024 ) + +void *R_Hunk_Begin( void ) { + int maxsize = R_HUNK_SIZE; + + //Com_Printf("R_Hunk_Begin\n"); + + // reserve a huge chunk of memory, but don't commit any yet + cursize = 0; + hunkmaxsize = maxsize; + +#ifdef _WIN32 + + // this will "reserve" a chunk of memory for use by this application + // it will not be "committed" just yet, but the swap file will grow + // now if needed + membase = VirtualAlloc( NULL, maxsize, MEM_RESERVE, PAGE_NOACCESS ); + +#else + + // show_bug.cgi?id=440 + // if not win32, then just allocate it now + // it is possible that we have been allocated already, in case we don't do anything + if ( !membase ) { + membase = malloc( maxsize ); + // TTimo NOTE: initially, I was doing the memset even if we had an existing membase + // but this breaks some shaders (i.e. /map mp_beach, then go back to the main menu .. some shaders are missing) + // I assume the shader missing is because we don't clear memory either on win32 + // meaning even on win32 we are using memory that is still reserved but was uncommited .. it works out of pure luck + memset( membase, 0, maxsize ); + } + +#endif + + if ( !membase ) { + ri.Error( ERR_DROP, "R_Hunk_Begin: reserve failed" ); + } + + return (void *)membase; +} + +void *R_Hunk_Alloc( int size ) { +#ifdef _WIN32 + void *buf; +#endif + + //Com_Printf("R_Hunk_Alloc(%d)\n", size); + + // round to cacheline + size = ( size + 31 ) & ~31; + +#ifdef _WIN32 + + // commit pages as needed + buf = VirtualAlloc( membase, cursize + size, MEM_COMMIT, PAGE_READWRITE ); + + if ( !buf ) { + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPTSTR) &buf, 0, NULL ); + ri.Error( ERR_DROP, "VirtualAlloc commit failed.\n%s", buf ); + } + +#endif + + cursize += size; + if ( cursize > hunkmaxsize ) { + ri.Error( ERR_DROP, "R_Hunk_Alloc overflow" ); + } + + return ( void * )( membase + cursize - size ); +} + +// this is only called when we shutdown GL +void R_Hunk_End( void ) { + //Com_Printf("R_Hunk_End\n"); + + if ( membase ) { +#ifdef _WIN32 + VirtualFree( membase, 0, MEM_RELEASE ); +#else + free( membase ); +#endif + } + + membase = NULL; +} + +void R_Hunk_Reset( void ) { + //Com_Printf("R_Hunk_Reset\n"); + + if ( !membase ) { + ri.Error( ERR_DROP, "R_Hunk_Reset called without a membase!" ); + } + +#ifdef _WIN32 + // mark the existing committed pages as reserved, but not committed + VirtualFree( membase, cursize, MEM_DECOMMIT ); +#endif + // on non win32 OS, we keep the allocated chunk as is, just start again to curzise = 0 + + // start again at the top + cursize = 0; +} + +//============================================================================= +// Ridah, model caching + +// TODO: convert the Hunk_Alloc's in the model loading to malloc's, so we don't have +// to move so much memory around during transitions + +static model_t backupModels[MAX_MOD_KNOWN]; +static int numBackupModels = 0; + +/* +=============== +R_CacheModelAlloc +=============== +*/ +void *R_CacheModelAlloc( int size ) { + if ( r_cache->integer && r_cacheModels->integer ) { + return R_Hunk_Alloc( size ); + } else { + return ri.Hunk_Alloc( size, h_low ); + } +} + +/* +=============== +R_CacheModelFree +=============== +*/ +void R_CacheModelFree( void *ptr ) { + if ( r_cache->integer && r_cacheModels->integer ) { + // TTimo: it's in the hunk, leave it there, next R_Hunk_Begin will clear it all + } else + { + // if r_cache 0, this is never supposed to get called anyway + Com_Printf( "FIXME: unexpected R_CacheModelFree call (r_cache 0)\n" ); + } +} + +/* +=============== +R_PurgeModels +=============== +*/ +void R_PurgeModels( int count ) { + static int lastPurged = 0; + + if ( !numBackupModels ) { + return; + } + + lastPurged = 0; + numBackupModels = 0; + + // note: we can only do this since we only use the virtual memory for the model caching! + R_Hunk_Reset(); +} + +/* +=============== +R_BackupModels +=============== +*/ +void R_BackupModels( void ) { + int i, j; + model_t *mod, *modBack; + + if ( !r_cache->integer ) { + return; + } + if ( !r_cacheModels->integer ) { + return; + } + + if ( numBackupModels ) { + R_PurgeModels( numBackupModels + 1 ); // get rid of them all + } + + // copy each model in memory across to the backupModels + modBack = backupModels; + for ( i = 0; i < tr.numModels; i++ ) { + mod = tr.models[i]; + + if ( mod->type && mod->type != MOD_BRUSH && mod->type != MOD_MDS ) { + memcpy( modBack, mod, sizeof( *mod ) ); + switch ( mod->type ) { + case MOD_MESH: + for ( j = MD3_MAX_LODS - 1; j >= 0; j-- ) { + if ( j < mod->numLods && mod->md3[j] ) { + if ( ( j == MD3_MAX_LODS - 1 ) || ( mod->md3[j] != mod->md3[j + 1] ) ) { + modBack->md3[j] = R_CacheModelAlloc( mod->md3[j]->ofsEnd ); + memcpy( modBack->md3[j], mod->md3[j], mod->md3[j]->ofsEnd ); + } else { + modBack->md3[j] = modBack->md3[j + 1]; + } + } + } + break; + case MOD_MDC: + for ( j = MD3_MAX_LODS - 1; j >= 0; j-- ) { + if ( j < mod->numLods && mod->mdc[j] ) { + if ( ( j == MD3_MAX_LODS - 1 ) || ( mod->mdc[j] != mod->mdc[j + 1] ) ) { + modBack->mdc[j] = R_CacheModelAlloc( mod->mdc[j]->ofsEnd ); + memcpy( modBack->mdc[j], mod->mdc[j], mod->mdc[j]->ofsEnd ); + } else { + modBack->mdc[j] = modBack->mdc[j + 1]; + } + } + } + break; + default: + break; // MOD_BAD MOD_BRUSH MOD_MDS not handled + } + modBack++; + numBackupModels++; + } + } +} + + +/* +================= +R_RegisterMDCShaders +================= +*/ +static void R_RegisterMDCShaders( model_t *mod, int lod ) { + mdcSurface_t *surf; + md3Shader_t *shader; + int i, j; + + // swap all the surfaces + surf = ( mdcSurface_t * )( (byte *)mod->mdc[lod] + mod->mdc[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->mdc[lod]->numSurfaces ; i++ ) { + // register the shaders + shader = ( md3Shader_t * )( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + } + // find the next surface + surf = ( mdcSurface_t * )( (byte *)surf + surf->ofsEnd ); + } +} + +/* +================= +R_RegisterMD3Shaders +================= +*/ +static void R_RegisterMD3Shaders( model_t *mod, int lod ) { + md3Surface_t *surf; + md3Shader_t *shader; + int i, j; + + // swap all the surfaces + surf = ( md3Surface_t * )( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++ ) { + // register the shaders + shader = ( md3Shader_t * )( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + } + // find the next surface + surf = ( md3Surface_t * )( (byte *)surf + surf->ofsEnd ); + } +} + +/* +=============== +R_FindCachedModel + + look for the given model in the list of backupModels +=============== +*/ +qboolean R_FindCachedModel( const char *name, model_t *newmod ) { + int i,j, index; + model_t *mod; + + // NOTE TTimo + // would need an r_cache check here too? + + if ( !r_cacheModels->integer ) { + return qfalse; + } + + if ( !numBackupModels ) { + return qfalse; + } + + mod = backupModels; + for ( i = 0; i < numBackupModels; i++, mod++ ) { + if ( !Q_strncmp( mod->name, name, sizeof( mod->name ) ) ) { + // copy it to a new slot + index = newmod->index; + memcpy( newmod, mod, sizeof( model_t ) ); + newmod->index = index; + switch ( mod->type ) { + case MOD_MDS: + return qfalse; // not supported yet + case MOD_MESH: + for ( j = MD3_MAX_LODS - 1; j >= 0; j-- ) { + if ( j < mod->numLods && mod->md3[j] ) { + if ( ( j == MD3_MAX_LODS - 1 ) || ( mod->md3[j] != mod->md3[j + 1] ) ) { + newmod->md3[j] = ri.Hunk_Alloc( mod->md3[j]->ofsEnd, h_low ); + memcpy( newmod->md3[j], mod->md3[j], mod->md3[j]->ofsEnd ); + R_RegisterMD3Shaders( newmod, j ); + R_CacheModelFree( mod->md3[j] ); + } else { + newmod->md3[j] = mod->md3[j + 1]; + } + } + } + break; + case MOD_MDC: + for ( j = MD3_MAX_LODS - 1; j >= 0; j-- ) { + if ( j < mod->numLods && mod->mdc[j] ) { + if ( ( j == MD3_MAX_LODS - 1 ) || ( mod->mdc[j] != mod->mdc[j + 1] ) ) { + newmod->mdc[j] = ri.Hunk_Alloc( mod->mdc[j]->ofsEnd, h_low ); + memcpy( newmod->mdc[j], mod->mdc[j], mod->mdc[j]->ofsEnd ); + R_RegisterMDCShaders( newmod, j ); + R_CacheModelFree( mod->mdc[j] ); + } else { + newmod->mdc[j] = mod->mdc[j + 1]; + } + } + } + break; + default: + break; // MOD_BAD MOD_BRUSH + } + + mod->type = MOD_BAD; // don't try and purge it later + mod->name[0] = 0; + return qtrue; + } + } + + return qfalse; +} + +/* +=============== +R_LoadCacheModels +=============== +*/ +void R_LoadCacheModels( void ) { + int len; + byte *buf; + char *token, *pString; + char name[MAX_QPATH]; + + if ( !r_cacheModels->integer ) { + return; + } + + // don't load the cache list in between level loads, only on startup, or after a vid_restart + if ( numBackupModels > 0 ) { + return; + } + + len = ri.FS_ReadFile( "model.cache", NULL ); + + if ( len <= 0 ) { + return; + } + + buf = (byte *)ri.Hunk_AllocateTempMemory( len ); + ri.FS_ReadFile( "model.cache", (void **)&buf ); + pString = buf; + + while ( ( token = COM_ParseExt( &pString, qtrue ) ) && token[0] ) { + Q_strncpyz( name, token, sizeof( name ) ); + RE_RegisterModel( name ); + } + + ri.Hunk_FreeTempMemory( buf ); +} +// done. +//======================================================================== diff --git a/src/renderer/tr_noise.c b/src/renderer/tr_noise.c new file mode 100644 index 0000000..9c578c8 --- /dev/null +++ b/src/renderer/tr_noise.c @@ -0,0 +1,99 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_noise.c +#include "tr_local.h" + +#define NOISE_SIZE 256 +#define NOISE_MASK ( NOISE_SIZE - 1 ) + +#define VAL( a ) s_noise_perm[ ( a ) & ( NOISE_MASK )] +#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) + +static float s_noise_table[NOISE_SIZE]; +static int s_noise_perm[NOISE_SIZE]; + +#define LERP( a, b, w ) ( a * ( 1.0f - w ) + b * w ) + +static float GetNoiseValue( int x, int y, int z, int t ) { + int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); + + return s_noise_table[index]; +} + +void R_NoiseInit( void ) { + int i; + + srand( 1001 ); + + for ( i = 0; i < NOISE_SIZE; i++ ) + { + s_noise_table[i] = ( float ) ( ( ( rand() / ( float ) RAND_MAX ) * 2.0 - 1.0 ) ); + s_noise_perm[i] = ( unsigned char )( rand() / ( float ) RAND_MAX * 255 ); + } +} + +float R_NoiseGet4f( float x, float y, float z, float t ) { + int i; + int ix, iy, iz, it; + float fx, fy, fz, ft; + float front[4]; + float back[4]; + float fvalue, bvalue, value[2], finalvalue; + + ix = ( int ) floor( x ); + fx = x - ix; + iy = ( int ) floor( y ); + fy = y - iy; + iz = ( int ) floor( z ); + fz = z - iz; + it = ( int ) floor( t ); + ft = t - it; + + for ( i = 0; i < 2; i++ ) + { + front[0] = GetNoiseValue( ix, iy, iz, it + i ); + front[1] = GetNoiseValue( ix + 1, iy, iz, it + i ); + front[2] = GetNoiseValue( ix, iy + 1, iz, it + i ); + front[3] = GetNoiseValue( ix + 1, iy + 1, iz, it + i ); + + back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); + back[1] = GetNoiseValue( ix + 1, iy, iz + 1, it + i ); + back[2] = GetNoiseValue( ix, iy + 1, iz + 1, it + i ); + back[3] = GetNoiseValue( ix + 1, iy + 1, iz + 1, it + i ); + + fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); + bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); + + value[i] = LERP( fvalue, bvalue, fz ); + } + + finalvalue = LERP( value[0], value[1], ft ); + + return finalvalue; +} diff --git a/src/renderer/tr_public.h b/src/renderer/tr_public.h new file mode 100644 index 0000000..3009b33 --- /dev/null +++ b/src/renderer/tr_public.h @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __TR_PUBLIC_H +#define __TR_PUBLIC_H + +#include "../cgame/tr_types.h" + +#define REF_API_VERSION 8 + +// +// these are the functions exported by the refresh module +// +typedef struct { + // called before the library is unloaded + // if the system is just reconfiguring, pass destroyWindow = qfalse, + // which will keep the screen from flashing to the desktop. + void ( *Shutdown )( qboolean destroyWindow ); + + // All data that will be used in a level should be + // registered before rendering any frames to prevent disk hits, + // but they can still be registered at a later time + // if necessary. + // + // BeginRegistration makes any existing media pointers invalid + // and returns the current gl configuration, including screen width + // and height, which can be used by the client to intelligently + // size display elements + void ( *BeginRegistration )( glconfig_t *config ); + qhandle_t ( *RegisterModel )( const char *name ); + qhandle_t ( *RegisterSkin )( const char *name ); + qhandle_t ( *RegisterShader )( const char *name ); + qhandle_t ( *RegisterShaderNoMip )( const char *name ); + void ( *RegisterFont )( const char *fontName, int pointSize, fontInfo_t *font ); + + void ( *LoadWorld )( const char *name ); + qboolean ( *GetSkinModel )( qhandle_t skinid, const char *type, char *name ); //----(SA) added + qhandle_t ( *GetShaderFromModel )( qhandle_t modelid, int surfnum, int withlightmap ); //----(SA) added + + // the vis data is a large enough block of data that we go to the trouble + // of sharing it with the clipmodel subsystem + void ( *SetWorldVisData )( const byte *vis ); + + // EndRegistration will draw a tiny polygon with each texture, forcing + // them to be loaded into card memory + void ( *EndRegistration )( void ); + + // a scene is built up by calls to R_ClearScene and the various R_Add functions. + // Nothing is drawn until R_RenderScene is called. + void ( *ClearScene )( void ); + void ( *AddRefEntityToScene )( const refEntity_t *re ); + int ( *LightForPoint )( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + void ( *AddPolyToScene )( qhandle_t hShader, int numVerts, const polyVert_t *verts ); + // Ridah + void ( *AddPolysToScene )( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ); + // done. + void ( *AddLightToScene )( const vec3_t org, float intensity, float r, float g, float b, int overdraw ); +//----(SA) + void ( *AddCoronaToScene )( const vec3_t org, float r, float g, float b, float scale, int id, qboolean visible ); + void ( *SetFog )( int fogvar, int var1, int var2, float r, float g, float b, float density ); +//----(SA) + void ( *RenderScene )( const refdef_t *fd ); + + void ( *SetColor )( const float *rgba ); // NULL = 1,1,1,1 + void ( *DrawStretchPic )( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); // 0 = white + void ( *DrawRotatedPic )( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, float angle ); // NERVE - SMF + void ( *DrawStretchPicGradient )( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader, const float *gradientColor, int gradientType ); + + // Draw images for cinematic rendering, pass as 32 bit rgba + void ( *DrawStretchRaw )( int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty ); + void ( *UploadCinematic )( int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty ); + + void ( *BeginFrame )( stereoFrame_t stereoFrame ); + + // if the pointers are not NULL, timing info will be returned + void ( *EndFrame )( int *frontEndMsec, int *backEndMsec ); + + + int ( *MarkFragments )( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + int ( *LerpTag )( orientation_t *tag, const refEntity_t *refent, const char *tagName, int startIndex ); + void ( *ModelBounds )( qhandle_t model, vec3_t mins, vec3_t maxs ); + + void ( *RemapShader )( const char *oldShader, const char *newShader, const char *offsetTime ); + + qboolean ( *GetEntityToken )( char *buffer, int size ); +} refexport_t; + +// +// these are the functions imported by the refresh module +// +typedef struct { + // print message on the local console + void ( QDECL * Printf )( int printLevel, const char *fmt, ... ); + + // abort the game + void ( QDECL * Error )( int errorLevel, const char *fmt, ... ); + + // milliseconds should only be used for profiling, never + // for anything game related. Get time from the refdef + int ( *Milliseconds )( void ); + + // stack based memory allocation for per-level things that + // won't be freed + void ( *Hunk_Clear )( void ); +#ifdef HUNK_DEBUG + void *( *Hunk_AllocDebug )( int size, ha_pref pref, char *label, char *file, int line ); +#else + void *( *Hunk_Alloc )( int size, ha_pref pref ); +#endif + void *( *Hunk_AllocateTempMemory )( int size ); + void ( *Hunk_FreeTempMemory )( void *block ); + + // dynamic memory allocator for things that need to be freed +#ifdef ZONE_DEBUG + void *( *Z_MallocDebug )( int bytes, char *label, char *file, int line ); +#else + void *( *Z_Malloc )( int bytes ); +#endif + void ( *Free )( void *buf ); + void ( *Tag_Free )( void ); + + cvar_t *( *Cvar_Get )( const char *name, const char *value, int flags ); + void ( *Cvar_Set )( const char *name, const char *value ); + + void ( *Cmd_AddCommand )( const char *name, void( *cmd ) ( void ) ); + void ( *Cmd_RemoveCommand )( const char *name ); + + int ( *Cmd_Argc )( void ); + char *( *Cmd_Argv )( int i ); + + void ( *Cmd_ExecuteText )( int exec_when, const char *text ); + + // visualization for debugging collision detection + void ( *CM_DrawDebugSurface )( void( *drawPoly ) ( int color, int numPoints, float *points ) ); + + // a -1 return means the file does not exist + // NULL can be passed for buf to just determine existance + int ( *FS_FileIsInPAK )( const char *name, int *pChecksum ); + int ( *FS_ReadFile )( const char *name, void **buf ); + void ( *FS_FreeFile )( void *buf ); + char ** ( *FS_ListFiles )( const char *name, const char *extension, int *numfilesfound ); + void ( *FS_FreeFileList )( char **filelist ); + void ( *FS_WriteFile )( const char *qpath, const void *buffer, int size ); + qboolean ( *FS_FileExists )( const char *file ); + + // cinematic stuff + void ( *CIN_UploadCinematic )( int handle ); + int ( *CIN_PlayCinematic )( const char *arg0, int xpos, int ypos, int width, int height, int bits ); + e_status ( *CIN_RunCinematic )( int handle ); + +} refimport_t; + + +// this is the only function actually exported at the linker level +// If the module can't init to a valid rendering state, NULL will be +// returned. +refexport_t*GetRefAPI( int apiVersion, refimport_t *rimp ); + +#endif // __TR_PUBLIC_H diff --git a/src/renderer/tr_scene.c b/src/renderer/tr_scene.c new file mode 100644 index 0000000..ddd1aa1 --- /dev/null +++ b/src/renderer/tr_scene.c @@ -0,0 +1,528 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "tr_local.h" + +int r_firstSceneDrawSurf; + +int r_numdlights; +int r_firstSceneDlight; + +int r_numcoronas; +int r_firstSceneCorona; + +int r_numentities; +int r_firstSceneEntity; + +int r_numpolys; +int r_firstScenePoly; + +int r_numpolyverts; + +int skyboxportal; +/* +==================== +R_ToggleSmpFrame + +==================== +*/ +void R_ToggleSmpFrame( void ) { + if ( r_smp->integer ) { + // use the other buffers next frame, because another CPU + // may still be rendering into the current ones + tr.smpFrame ^= 1; + } else { + tr.smpFrame = 0; + } + + backEndData[tr.smpFrame]->commands.used = 0; + + r_firstSceneDrawSurf = 0; + + r_numdlights = 0; + r_firstSceneDlight = 0; + + r_numcoronas = 0; + r_firstSceneCorona = 0; + + r_numentities = 0; + r_firstSceneEntity = 0; + + r_numpolys = 0; + r_firstScenePoly = 0; + + r_numpolyverts = 0; +} + + +/* +==================== +RE_ClearScene + +==================== +*/ +void RE_ClearScene( void ) { + r_firstSceneDlight = r_numdlights; + r_firstSceneCorona = r_numcoronas; + r_firstSceneEntity = r_numentities; + r_firstScenePoly = r_numpolys; +} + +/* +=========================================================================== + +DISCRETE POLYS + +=========================================================================== +*/ + +/* +===================== +R_AddPolygonSurfaces + +Adds all the scene's polys into this view's drawsurf list +===================== +*/ +void R_AddPolygonSurfaces( void ) { + int i; + shader_t *sh; + srfPoly_t *poly; + + tr.currentEntityNum = ENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) { + sh = R_GetShaderByHandle( poly->hShader ); + R_AddDrawSurf( ( void * )poly, sh, poly->fogIndex, qfalse ); + } +} + +/* +===================== +RE_AddPolyToScene + +===================== +*/ +void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts ) { + srfPoly_t *poly; + int i; + int fogIndex; + fog_t *fog; + vec3_t bounds[2]; + + if ( !tr.registered ) { + return; + } + + if ( !hShader ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n" ); + return; + } + + if ( ( ( r_numpolyverts + numVerts ) > max_polyverts ) || ( r_numpolys >= max_polys ) ) { + return; + } + + poly = &backEndData[tr.smpFrame]->polys[r_numpolys]; + poly->surfaceType = SF_POLY; + poly->hShader = hShader; + poly->numVerts = numVerts; + poly->verts = &backEndData[tr.smpFrame]->polyVerts[r_numpolyverts]; + + memcpy( poly->verts, verts, numVerts * sizeof( *verts ) ); + // Ridah + if ( glConfig.hardwareType == GLHW_RAGEPRO ) { + poly->verts->modulate[0] = 255; + poly->verts->modulate[1] = 255; + poly->verts->modulate[2] = 255; + poly->verts->modulate[3] = 255; + } + // done. + r_numpolys++; + r_numpolyverts += numVerts; + + // see if it is in a fog volume + if ( tr.world->numfogs == 1 ) { + fogIndex = 0; + } else { + // find which fog volume the poly is in + VectorCopy( poly->verts[0].xyz, bounds[0] ); + VectorCopy( poly->verts[0].xyz, bounds[1] ); + for ( i = 1 ; i < poly->numVerts ; i++ ) { + AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] ); + } + for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) { + fog = &tr.world->fogs[fogIndex]; + if ( bounds[1][0] >= fog->bounds[0][0] + && bounds[1][1] >= fog->bounds[0][1] + && bounds[1][2] >= fog->bounds[0][2] + && bounds[0][0] <= fog->bounds[1][0] + && bounds[0][1] <= fog->bounds[1][1] + && bounds[0][2] <= fog->bounds[1][2] ) { + break; + } + } + if ( fogIndex == tr.world->numfogs ) { + fogIndex = 0; + } + } + poly->fogIndex = fogIndex; +} + +// Ridah +/* +===================== +RE_AddPolysToScene + +===================== +*/ +void RE_AddPolysToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) { + srfPoly_t *poly; + int i; + int fogIndex; + fog_t *fog; + vec3_t bounds[2]; + int j; + + if ( !tr.registered ) { + return; + } + + if ( !hShader ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolysToScene: NULL poly shader\n" ); + return; + } + + for ( j = 0; j < numPolys; j++ ) { + if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) { +// ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolysToScene: MAX_POLYS or MAX_POLYVERTS reached\n"); + return; + } + + poly = &backEndData[tr.smpFrame]->polys[r_numpolys]; + poly->surfaceType = SF_POLY; + poly->hShader = hShader; + poly->numVerts = numVerts; + poly->verts = &backEndData[tr.smpFrame]->polyVerts[r_numpolyverts]; + + memcpy( poly->verts, &verts[numVerts * j], numVerts * sizeof( *verts ) ); + // Ridah + if ( glConfig.hardwareType == GLHW_RAGEPRO ) { + poly->verts->modulate[0] = 255; + poly->verts->modulate[1] = 255; + poly->verts->modulate[2] = 255; + poly->verts->modulate[3] = 255; + } + // done. + r_numpolys++; + r_numpolyverts += numVerts; + + // if no world is loaded + if ( tr.world == NULL ) { + fogIndex = 0; + } + // see if it is in a fog volume + else if ( tr.world->numfogs == 1 ) { + fogIndex = 0; + } else { + // find which fog volume the poly is in + VectorCopy( poly->verts[0].xyz, bounds[0] ); + VectorCopy( poly->verts[0].xyz, bounds[1] ); + for ( i = 1 ; i < poly->numVerts ; i++ ) { + AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] ); + } + for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) { + fog = &tr.world->fogs[fogIndex]; + if ( bounds[1][0] >= fog->bounds[0][0] + && bounds[1][1] >= fog->bounds[0][1] + && bounds[1][2] >= fog->bounds[0][2] + && bounds[0][0] <= fog->bounds[1][0] + && bounds[0][1] <= fog->bounds[1][1] + && bounds[0][2] <= fog->bounds[1][2] ) { + break; + } + } + if ( fogIndex == tr.world->numfogs ) { + fogIndex = 0; + } + } + poly->fogIndex = fogIndex; + } +} +// done. + + +//================================================================================= + + +/* +===================== +RE_AddRefEntityToScene + +===================== +*/ +void RE_AddRefEntityToScene( const refEntity_t *ent ) { + if ( !tr.registered ) { + return; + } + // show_bug.cgi?id=402 + if ( r_numentities >= ENTITYNUM_WORLD ) { + return; + } + if ( ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) { + ri.Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType ); + } + + backEndData[tr.smpFrame]->entities[r_numentities].e = *ent; + backEndData[tr.smpFrame]->entities[r_numentities].lightingCalculated = qfalse; + + r_numentities++; +} + +// Ridah, added support for overdraw field +/* +===================== +RE_AddLightToScene + +===================== +*/ +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b, int overdraw ) { + dlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( r_numdlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + // these cards don't have the correct blend mode + if ( glConfig.hardwareType == GLHW_RIVA128 || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + return; + } + // RF, allow us to force some dlights under all circumstances + if ( !( overdraw & REF_FORCE_DLIGHT ) ) { + if ( r_dynamiclight->integer == 0 ) { + return; + } + if ( r_dynamiclight->integer == 2 && !( backEndData[tr.smpFrame]->dlights[r_numdlights].forced ) ) { + return; + } + } + + overdraw &= ~REF_FORCE_DLIGHT; + overdraw &= ~REF_JUNIOR_DLIGHT; //----(SA) added + + dl = &backEndData[tr.smpFrame]->dlights[r_numdlights++]; + VectorCopy( org, dl->origin ); + dl->radius = intensity; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; + dl->dlshader = NULL; + dl->overdraw = 0; + + if ( overdraw == 10 ) { // sorry, hijacking 10 for a quick hack (SA) + dl->dlshader = R_GetShaderByHandle( RE_RegisterShader( "negdlightshader" ) ); + } else if ( overdraw == 11 ) { // 11 is flames + dl->dlshader = R_GetShaderByHandle( RE_RegisterShader( "flamedlightshader" ) ); + } else { + dl->overdraw = overdraw; + } +} +// done. + + +/* +============== +RE_AddCoronaToScene +============== +*/ +void RE_AddCoronaToScene( const vec3_t org, float r, float g, float b, float scale, int id, qboolean visible ) { + corona_t *cor; + + if ( !tr.registered ) { + return; + } + if ( r_numcoronas >= MAX_CORONAS ) { + return; + } + + cor = &backEndData[tr.smpFrame]->coronas[r_numcoronas++]; + VectorCopy( org, cor->origin ); + cor->color[0] = r; + cor->color[1] = g; + cor->color[2] = b; + cor->scale = scale; + cor->id = id; + cor->visible = visible; +} + +/* +@@@@@@@@@@@@@@@@@@@@@ +RE_RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +@@@@@@@@@@@@@@@@@@@@@ +*/ +void RE_RenderScene( const refdef_t *fd ) { + viewParms_t parms; + int startTime; + + if ( !tr.registered ) { + return; + } + GLimp_LogComment( "====== RE_RenderScene =====\n" ); + + if ( r_norefresh->integer ) { + return; + } + + startTime = ri.Milliseconds(); + + if ( !tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) { + ri.Error( ERR_DROP, "R_RenderScene: NULL worldmodel" ); + } + + memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) ); + + tr.refdef.x = fd->x; + tr.refdef.y = fd->y; + tr.refdef.width = fd->width; + tr.refdef.height = fd->height; + tr.refdef.fov_x = fd->fov_x; + tr.refdef.fov_y = fd->fov_y; + + VectorCopy( fd->vieworg, tr.refdef.vieworg ); + VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] ); + VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] ); + VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] ); + + tr.refdef.time = fd->time; + tr.refdef.rdflags = fd->rdflags; + + if ( fd->rdflags & RDF_SKYBOXPORTAL ) { + skyboxportal = 1; + } + + // copy the areamask data over and note if it has changed, which + // will force a reset of the visible leafs even if the view hasn't moved + tr.refdef.areamaskModified = qfalse; + if ( !( tr.refdef.rdflags & RDF_NOWORLDMODEL ) ) { + int areaDiff; + int i; + + // compare the area bits + areaDiff = 0; + for ( i = 0 ; i < MAX_MAP_AREA_BYTES / 4 ; i++ ) { + areaDiff |= ( (int *)tr.refdef.areamask )[i] ^ ( (int *)fd->areamask )[i]; + ( (int *)tr.refdef.areamask )[i] = ( (int *)fd->areamask )[i]; + } + + if ( areaDiff ) { + // a door just opened or something + tr.refdef.areamaskModified = qtrue; + } + } + + + // derived info + + tr.refdef.floatTime = tr.refdef.time * 0.001f; + + tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; + tr.refdef.drawSurfs = backEndData[tr.smpFrame]->drawSurfs; + + tr.refdef.num_entities = r_numentities - r_firstSceneEntity; + tr.refdef.entities = &backEndData[tr.smpFrame]->entities[r_firstSceneEntity]; + + tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight; + tr.refdef.dlights = &backEndData[tr.smpFrame]->dlights[r_firstSceneDlight]; + + tr.refdef.num_coronas = r_numcoronas - r_firstSceneCorona; + tr.refdef.coronas = &backEndData[tr.smpFrame]->coronas[r_firstSceneCorona]; + + tr.refdef.numPolys = r_numpolys - r_firstScenePoly; + tr.refdef.polys = &backEndData[tr.smpFrame]->polys[r_firstScenePoly]; + + // turn off dynamic lighting globally by clearing all the + // dlights if it needs to be disabled or if vertex lighting is enabled + if ( /*r_dynamiclight->integer == 0 || // RF, disabled so we can force things like lightning dlights + r_vertexLight->integer == 1 ||*/ + glConfig.hardwareType == GLHW_PERMEDIA2 ) { + tr.refdef.num_dlights = 0; + } + + // a single frame may have multiple scenes draw inside it -- + // a 3D game view, 3D status bar renderings, 3D menus, etc. + // They need to be distinguished by the light flare code, because + // the visibility state for a given surface may be different in + // each scene / view. + tr.frameSceneNum++; + tr.sceneCount++; + + // setup view parms for the initial view + // + // set up viewport + // The refdef takes 0-at-the-top y coordinates, so + // convert to GL's 0-at-the-bottom space + // + memset( &parms, 0, sizeof( parms ) ); + parms.viewportX = tr.refdef.x; + parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height ); + parms.viewportWidth = tr.refdef.width; + parms.viewportHeight = tr.refdef.height; + parms.isPortal = qfalse; + + parms.fovX = tr.refdef.fov_x; + parms.fovY = tr.refdef.fov_y; + + VectorCopy( fd->vieworg, parms.or.origin ); + VectorCopy( fd->viewaxis[0], parms.or.axis[0] ); + VectorCopy( fd->viewaxis[1], parms.or.axis[1] ); + VectorCopy( fd->viewaxis[2], parms.or.axis[2] ); + + VectorCopy( fd->vieworg, parms.pvsOrigin ); + + R_RenderView( &parms ); + + // the next scene rendered in this frame will tack on after this one + r_firstSceneDrawSurf = tr.refdef.numDrawSurfs; + r_firstSceneEntity = r_numentities; + r_firstSceneDlight = r_numdlights; + r_firstScenePoly = r_numpolys; + + tr.frontEndMsec += ri.Milliseconds() - startTime; +} diff --git a/src/renderer/tr_shade.c b/src/renderer/tr_shade.c new file mode 100644 index 0000000..6a291fb --- /dev/null +++ b/src/renderer/tr_shade.c @@ -0,0 +1,1583 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_shade.c + +#include "tr_local.h" + +/* + + THIS ENTIRE FILE IS BACK END + + This file deals with applying shaders to surface data in the tess struct. +*/ + +/* +================ +R_ArrayElementDiscrete + +This is just for OpenGL conformance testing, it should never be the fastest +================ +*/ +static void APIENTRY R_ArrayElementDiscrete( GLint index ) { + qglColor4ubv( tess.svars.colors[ index ] ); + if ( glState.currenttmu ) { + qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] ); + qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] ); + } else { + qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] ); + } + qglVertex3fv( tess.xyz[ index ] ); +} + +/* +=================== +R_DrawStripElements + +=================== +*/ +static int c_vertexes; // for seeing how long our average strips are +static int c_begins; +static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )( GLint ) ) { + int i; + int last[3] = { -1, -1, -1 }; + qboolean even; + + c_begins++; + + if ( numIndexes <= 0 ) { + return; + } + + qglBegin( GL_TRIANGLE_STRIP ); + + // prime the strip + element( indexes[0] ); + element( indexes[1] ); + element( indexes[2] ); + c_vertexes += 3; + + last[0] = indexes[0]; + last[1] = indexes[1]; + last[2] = indexes[2]; + + even = qfalse; + + for ( i = 3; i < numIndexes; i += 3 ) + { + // odd numbered triangle in potential strip + if ( !even ) { + // check previous triangle to see if we're continuing a strip + if ( ( indexes[i + 0] == last[2] ) && ( indexes[i + 1] == last[1] ) ) { + element( indexes[i + 2] ); + c_vertexes++; + assert( indexes[i + 2] < tess.numVertexes ); + even = qtrue; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i + 0] ); + element( indexes[i + 1] ); + element( indexes[i + 2] ); + + c_vertexes += 3; + + even = qfalse; + } + } else + { + // check previous triangle to see if we're continuing a strip + if ( ( last[2] == indexes[i + 1] ) && ( last[0] == indexes[i + 0] ) ) { + element( indexes[i + 2] ); + c_vertexes++; + + even = qfalse; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i + 0] ); + element( indexes[i + 1] ); + element( indexes[i + 2] ); + c_vertexes += 3; + + even = qfalse; + } + } + + // cache the last three vertices + last[0] = indexes[i + 0]; + last[1] = indexes[i + 1]; + last[2] = indexes[i + 2]; + } + + qglEnd(); +} + + + +/* +================== +R_DrawElements + +Optionally performs our own glDrawElements that looks for strip conditions +instead of using the single glDrawElements call that may be inefficient +without compiled vertex arrays. +================== +*/ +static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) { + int primitives; + + primitives = r_primitives->integer; + + // default is to use triangles if compiled vertex arrays are present + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + + + if ( primitives == 2 ) { + qglDrawElements( GL_TRIANGLES, + numIndexes, + GL_INDEX_TYPE, + indexes ); + return; + } + + if ( primitives == 1 ) { + R_DrawStripElements( numIndexes, indexes, qglArrayElement ); + return; + } + + if ( primitives == 3 ) { + R_DrawStripElements( numIndexes, indexes, R_ArrayElementDiscrete ); + return; + } + + // anything else will cause no drawing +} + + +/* +============================================================= + +SURFACE SHADERS + +============================================================= +*/ + +shaderCommands_t tess; +static qboolean setArraysOnce; + +/* +================= +R_BindAnimatedImage + +================= +*/ +static void R_BindAnimatedImage( textureBundle_t *bundle ) { + int index; + + if ( bundle->isVideoMap ) { + ri.CIN_RunCinematic( bundle->videoMapHandle ); + ri.CIN_UploadCinematic( bundle->videoMapHandle ); + return; + } + + if ( bundle->numImageAnimations <= 1 ) { + if ( bundle->isLightmap && ( backEnd.refdef.rdflags & RDF_SNOOPERVIEW ) ) { + GL_Bind( tr.whiteImage ); + } else { + GL_Bind( bundle->image[0] ); + } + return; + } + + // it is necessary to do this messy calc to make sure animations line up + // exactly with waveforms of the same frequency + index = myftol( tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE ); + index >>= FUNCTABLE_SIZE2; + + if ( index < 0 ) { + index = 0; // may happen with shader time offsets + } + index %= bundle->numImageAnimations; + + if ( bundle->isLightmap && ( backEnd.refdef.rdflags & RDF_SNOOPERVIEW ) ) { + GL_Bind( tr.whiteImage ); + } else { + GL_Bind( bundle->image[ index ] ); + } +} + +/* +================ +DrawTris + +Draws triangle outlines for debugging +================ +*/ +static void DrawTris( shaderCommands_t *input ) { + GL_Bind( tr.whiteImage ); + qglColor3f( 1,1,1 ); + + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + qglDepthRange( 0, 0 ); + + qglDisableClientState( GL_COLOR_ARRAY ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + qglVertexPointer( 3, GL_FLOAT, 16, input->xyz ); // padded for SIMD + + if ( qglLockArraysEXT ) { + qglLockArraysEXT( 0, input->numVertexes ); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + if ( qglUnlockArraysEXT ) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + qglDepthRange( 0, 1 ); +} + + +/* +================ +DrawNormals + +Draws vertex normals for debugging +================ +*/ +static void DrawNormals( shaderCommands_t *input ) { + int i; + vec3_t temp; + + GL_Bind( tr.whiteImage ); + qglColor3f( 1,1,1 ); + qglDepthRange( 0, 0 ); // never occluded + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + + qglBegin( GL_LINES ); + for ( i = 0 ; i < input->numVertexes ; i++ ) { + qglVertex3fv( input->xyz[i] ); + VectorMA( input->xyz[i], 2, input->normal[i], temp ); + qglVertex3fv( temp ); + } + qglEnd(); + + qglDepthRange( 0, 1 ); +} + +/* +============== +RB_BeginSurface + +We must set some things up before beginning any tesselation, +because a surface may be forced to perform a RB_End due +to overflow. +============== +*/ +void RB_BeginSurface( shader_t *shader, int fogNum ) { + + shader_t *state = ( shader->remappedShader ) ? shader->remappedShader : shader; + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.shader = state; + tess.fogNum = fogNum; + tess.dlightBits = 0; // will be OR'd in by surface functions + tess.xstages = state->stages; + tess.numPasses = state->numUnfoggedPasses; + tess.currentStageIteratorFunc = state->optimalStageIteratorFunc; + + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + if ( tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime ) { + tess.shaderTime = tess.shader->clampTime; + } + // done. +} + +/* +=================== +DrawMultitextured + +output = t0 * t1 or t0 + t1 + +t0 = most upstream according to spec +t1 = most downstream according to spec +=================== +*/ +static void DrawMultitextured( shaderCommands_t *input, int stage ) { + shaderStage_t *pStage; + + pStage = tess.xstages[stage]; + + // Ridah + if ( tess.shader->noFog && pStage->isFogged ) { + R_FogOn(); + } else if ( tess.shader->noFog && !pStage->isFogged ) { + R_FogOff(); // turn it back off + } else { // make sure it's on + R_FogOn(); + } + // done. + + GL_State( pStage->stateBits ); + + // this is an ugly hack to work around a GeForce driver + // bug with multitexture and clip planes + if ( backEnd.viewParms.isPortal ) { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + + // + // base + // + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + R_BindAnimatedImage( &pStage->bundle[0] ); + + // + // lightmap/secondary pass + // + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + + if ( r_lightmap->integer ) { + GL_TexEnv( GL_REPLACE ); + } else { + GL_TexEnv( tess.shader->multitextureEnv ); + } + + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] ); + + R_BindAnimatedImage( &pStage->bundle[1] ); + + R_DrawElements( input->numIndexes, input->indexes ); + + // + // disable texturing on TEXTURE1, then select TEXTURE0 + // + //qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + qglDisable( GL_TEXTURE_2D ); + + GL_SelectTexture( 0 ); +} + + + +/* +=================== +ProjectDlightTexture + +Perform dynamic lighting with another rendering pass +=================== +*/ +static void ProjectDlightTexture( void ) { + int i, l; + vec3_t origin; + float *texCoords; + byte *colors; + byte clipBits[SHADER_MAX_VERTEXES]; + MAC_STATIC float texCoordsArray[SHADER_MAX_VERTEXES][2]; + byte colorArray[SHADER_MAX_VERTEXES][4]; + unsigned hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float scale; + float radius; + vec3_t floatColor; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + + if ( backEnd.refdef.rdflags & RDF_SNOOPERVIEW ) { // no dlights for snooper + return; + } + + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + texCoords = texCoordsArray[0]; + colors = colorArray[0]; + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + scale = 1.0f / radius; + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + + for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { + vec3_t dist; + int clip; + float modulate; + + if ( 0 ) { + clipBits[i] = 255; // definately not dlighted + continue; + } + + backEnd.pc.c_dlightVertexes++; + + VectorSubtract( origin, tess.xyz[i], dist ); + texCoords[0] = 0.5f + dist[0] * scale; + texCoords[1] = 0.5f + dist[1] * scale; + + clip = 0; + if ( texCoords[0] < 0.0f ) { + clip |= 1; + } else if ( texCoords[0] > 1.0f ) { + clip |= 2; + } + if ( texCoords[1] < 0.0f ) { + clip |= 4; + } else if ( texCoords[1] > 1.0f ) { + clip |= 8; + } + // modulate the strength based on the height and color + if ( dist[2] > radius ) { + clip |= 16; + modulate = 0.0f; + } else if ( dist[2] < -radius ) { + clip |= 32; + modulate = 0.0f; + } else { + dist[2] = Q_fabs( dist[2] ); + if ( dist[2] < radius * 0.5f ) { + modulate = 1.0f; + } else { + modulate = 2.0f * ( radius - dist[2] ) * scale; + } + } + clipBits[i] = clip; + + colors[0] = myftol( floatColor[0] * modulate ); + colors[1] = myftol( floatColor[1] * modulate ); + colors[2] = myftol( floatColor[2] * modulate ); + colors[3] = 255; + } + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i + 1]; + c = tess.indexes[i + 2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) { + continue; // not lighted + } + hitIndexes[numIndexes] = a; + hitIndexes[numIndexes + 1] = b; + hitIndexes[numIndexes + 2] = c; + numIndexes += 3; + } + + if ( !numIndexes ) { + continue; + } + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + //----(SA) creating dlight shader to allow for special blends or alternate dlight texture + { + shader_t *dls = dl->dlshader; + if ( dls ) { +// if (!qglActiveTextureARB || dls->numUnfoggedPasses < 2) { + for ( i = 0; i < dls->numUnfoggedPasses; i++ ) + { + shaderStage_t *stage = dls->stages[i]; + R_BindAnimatedImage( &dls->stages[i]->bundle[0] ); + GL_State( stage->stateBits | GLS_DEPTHFUNC_EQUAL ); + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +/* + } else { // optimize for multitexture + + for(i=0;inumUnfoggedPasses;) + { + shaderStage_t *stage = dls->stages[i]; + + GL_State(stage->stateBits | GLS_DEPTHFUNC_EQUAL); + + // setup each TMU + for (tmu=0; tmunumUnfoggedPasses; tmu++, i++) { + + GL_SelectTexture( tmu ); + + if (tmu) { + qglEnable( GL_TEXTURE_2D ); + } + + R_BindAnimatedImage( &dls->stages[i]->bundle[0] ); + } + + // draw the elements + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } + + // turn off unused TMU's + for (tmu=1; tmuoverdraw || !qglActiveTextureARB) { + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + + // Ridah, overdraw lights several times, rather than sending + // multiple lights through + for ( i = 0; i < dl->overdraw; i++ ) { + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +/* + } else { // optimize for multitexture + + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + + // setup each TMU (use all available TMU's) + for (tmu=0; tmuoverdraw+1); tmu++) { + GL_SelectTexture( tmu ); + if (tmu) { + qglEnable( GL_TEXTURE_2D ); + GL_TexEnv( GL_ADD ); + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + } + GL_Bind( tr.dlightImage ); + } + + // draw each bundle + for(i=0; i<(dl->overdraw+1); i+=glConfig.maxActiveTextures) + { + // make sure we dont draw with too many TMU's + if (i+glConfig.maxActiveTextures>(dl->overdraw+1)) { + for (tmu=0; tmu=(dl->overdraw+1)) { + GL_SelectTexture( tmu ); + qglDisable( GL_TEXTURE_2D ); + } + } + } + // draw the elements + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } + + // turn off unused TMU's + for (tmu=1; tmufogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + *( int * )&tess.svars.colors[i] = fog->colorInt; + } + + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[0] ); + + GL_Bind( tr.fogImage ); + + if ( tess.shader->fogPass == FP_EQUAL ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + } else { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + + R_DrawElements( tess.numIndexes, tess.indexes ); +} + +/* +=============== +ComputeColors +=============== +*/ +static void ComputeColors( shaderStage_t *pStage ) { + int i; + + // + // rgbGen + // + switch ( pStage->rgbGen ) + { + case CGEN_IDENTITY: + memset( tess.svars.colors, 0xff, tess.numVertexes * 4 ); + break; + default: + case CGEN_IDENTITY_LIGHTING: + memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 ); + break; + case CGEN_LIGHTING_DIFFUSE: + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_EXACT_VERTEX: + memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + break; + case CGEN_CONST: + for ( i = 0; i < tess.numVertexes; i++ ) { + *(int *)tess.svars.colors[i] = *(int *)pStage->constantColor; + } + break; + case CGEN_VERTEX: + if ( tr.identityLight == 1 ) { + memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + } else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight; + tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight; + tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight; + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case CGEN_ONE_MINUS_VERTEX: + if ( tr.identityLight == 1 ) { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0]; + tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1]; + tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2]; + } + } else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight; + tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight; + tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight; + } + } + break; + case CGEN_FOG: + { + fog_t *fog; + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + *( int * )&tess.svars.colors[i] = fog->colorInt; + } + } + break; + case CGEN_WAVEFORM: + RB_CalcWaveColor( &pStage->rgbWave, ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_ENTITY: + RB_CalcColorFromEntity( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_ONE_MINUS_ENTITY: + RB_CalcColorFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + } + + // + // alphaGen + // + switch ( pStage->alphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_IDENTITY: + if ( pStage->rgbGen != CGEN_IDENTITY ) { + if ( ( pStage->rgbGen == CGEN_VERTEX && tr.identityLight != 1 ) || + pStage->rgbGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = 0xff; + } + } + } + break; + case AGEN_CONST: + if ( pStage->rgbGen != CGEN_CONST ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = pStage->constantColor[3]; + } + } + break; + case AGEN_WAVEFORM: + RB_CalcWaveAlpha( &pStage->alphaWave, ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_LIGHTING_SPECULAR: + RB_CalcSpecularAlpha( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ENTITY: + RB_CalcAlphaFromEntity( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ONE_MINUS_ENTITY: + RB_CalcAlphaFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + // Ridah + case AGEN_NORMALZFADE: + { + float alpha, range, lowest, highest, dot; + vec3_t worldUp; + qboolean zombieEffect = qfalse; + + if ( VectorCompare( backEnd.currentEntity->e.fireRiseDir, vec3_origin ) ) { + VectorSet( backEnd.currentEntity->e.fireRiseDir, 0, 0, 1 ); + } + + if ( backEnd.currentEntity->e.hModel ) { // world surfaces dont have an axis + VectorRotate( backEnd.currentEntity->e.fireRiseDir, backEnd.currentEntity->e.axis, worldUp ); + } else { + VectorCopy( backEnd.currentEntity->e.fireRiseDir, worldUp ); + } + + lowest = pStage->zFadeBounds[0]; + if ( lowest == -1000 ) { // use entity alpha + lowest = backEnd.currentEntity->e.shaderTime; + zombieEffect = qtrue; + } + highest = pStage->zFadeBounds[1]; + if ( highest == -1000 ) { // use entity alpha + highest = backEnd.currentEntity->e.shaderTime; + zombieEffect = qtrue; + } + range = highest - lowest; + for ( i = 0; i < tess.numVertexes; i++ ) { + dot = DotProduct( tess.normal[i], worldUp ); + + // special handling for Zombie fade effect + if ( zombieEffect ) { + alpha = (float)backEnd.currentEntity->e.shaderRGBA[3] * ( dot + 1.0 ) / 2.0; + alpha += ( 2.0 * (float)backEnd.currentEntity->e.shaderRGBA[3] ) * ( 1.0 - ( dot + 1.0 ) / 2.0 ); + if ( alpha > 255.0 ) { + alpha = 255.0; + } else if ( alpha < 0.0 ) { + alpha = 0.0; + } + tess.svars.colors[i][3] = (byte)( alpha ); + continue; + } + + if ( dot < highest ) { + if ( dot > lowest ) { + if ( dot < lowest + range / 2 ) { + alpha = ( (float)pStage->constantColor[3] * ( ( dot - lowest ) / ( range / 2 ) ) ); + } else { + alpha = ( (float)pStage->constantColor[3] * ( 1.0 - ( ( dot - lowest - range / 2 ) / ( range / 2 ) ) ) ); + } + if ( alpha > 255.0 ) { + alpha = 255.0; + } else if ( alpha < 0.0 ) { + alpha = 0.0; + } + + // finally, scale according to the entity's alpha + if ( backEnd.currentEntity->e.hModel ) { + alpha *= (float)backEnd.currentEntity->e.shaderRGBA[3] / 255.0; + } + + tess.svars.colors[i][3] = (byte)( alpha ); + } else { + tess.svars.colors[i][3] = 0; + } + } else { + tess.svars.colors[i][3] = 0; + } + } + } + break; + // done. + case AGEN_VERTEX: + if ( pStage->rgbGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case AGEN_ONE_MINUS_VERTEX: + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3]; + } + break; + case AGEN_PORTAL: + { + unsigned char alpha; + + for ( i = 0; i < tess.numVertexes; i++ ) + { + float len; + vec3_t v; + + VectorSubtract( tess.xyz[i], backEnd.viewParms.or.origin, v ); + len = VectorLength( v ); + + len /= tess.shader->portalRange; + + if ( len < 0 ) { + alpha = 0; + } else if ( len > 1 ) { + alpha = 0xff; + } else + { + alpha = len * 0xff; + } + + tess.svars.colors[i][3] = alpha; + } + } + break; + } + + // + // fog adjustment for colors to fade out as fog increases + // + if ( tess.fogNum ) { + switch ( pStage->adjustColorsForFog ) + { + case ACFF_MODULATE_RGB: + RB_CalcModulateColorsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_ALPHA: + RB_CalcModulateAlphasByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_RGBA: + RB_CalcModulateRGBAsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_NONE: + break; + } + } +} + +/* +=============== +ComputeTexCoords +=============== +*/ +static void ComputeTexCoords( shaderStage_t *pStage ) { + int i; + int b; + + for ( b = 0; b < NUM_TEXTURE_BUNDLES; b++ ) { + int tm; + + // + // generate the texture coordinates + // + switch ( pStage->bundle[b].tcGen ) + { + case TCGEN_IDENTITY: + memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes ); + break; + case TCGEN_TEXTURE: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1]; + } + break; + case TCGEN_LIGHTMAP: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][1][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][1][1]; + } + break; + case TCGEN_VECTOR: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[0] ); + tess.svars.texcoords[b][i][1] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[1] ); + } + break; + case TCGEN_FOG: + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_ENVIRONMENT_MAPPED: + RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_FIRERISEENV_MAPPED: + RB_CalcFireRiseEnvTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_BAD: + return; + } + + // + // alter texture coordinates + // + for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) { + switch ( pStage->bundle[b].texMods[tm].type ) + { + case TMOD_NONE: + tm = TR_MAX_TEXMODS; // break out of for loop + break; + + case TMOD_SWAP: + RB_CalcSwapTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_TURBULENT: + RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ENTITY_TRANSLATE: + RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCROLL: + RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].scroll, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCALE: + RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].scale, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_STRETCH: + RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_TRANSFORM: + RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm], + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ROTATE: + RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].rotateSpeed, + ( float * ) tess.svars.texcoords[b] ); + break; + + default: + ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", pStage->bundle[b].texMods[tm].type, tess.shader->name ); + break; + } + } + } +} + + + +extern void R_Fog( glfog_t *curfog ); + +/* +============== +SetIteratorFog + set the fog parameters for this pass +============== +*/ +void SetIteratorFog( void ) { + // changed for problem when you start the game with r_fastsky set to '1' +// if(r_fastsky->integer || backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) { + if ( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) { + R_FogOff(); + return; + } + + if ( backEnd.refdef.rdflags & RDF_DRAWINGSKY ) { + if ( glfogsettings[FOG_SKY].registered ) { + R_Fog( &glfogsettings[FOG_SKY] ); + } else { + R_FogOff(); + } + + return; + } + + if ( skyboxportal && backEnd.refdef.rdflags & RDF_SKYBOXPORTAL ) { + if ( glfogsettings[FOG_PORTALVIEW].registered ) { + R_Fog( &glfogsettings[FOG_PORTALVIEW] ); + } else { + R_FogOff(); + } + } else { + if ( glfogNum > FOG_NONE ) { + R_Fog( &glfogsettings[FOG_CURRENT] ); + } else { + R_FogOff(); + } + } +} + + +/* +** RB_IterateStagesGeneric +*/ +static void RB_IterateStagesGeneric( shaderCommands_t *input ) { + int stage; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) + { + shaderStage_t *pStage = tess.xstages[stage]; + + if ( !pStage ) { + break; + } + + ComputeColors( pStage ); + ComputeTexCoords( pStage ); + + if ( !setArraysOnce ) { + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors ); + } + + // + // do multitexture + // + if ( pStage->bundle[1].image[0] != 0 ) { + DrawMultitextured( input, stage ); + } else + { + int fadeStart, fadeEnd; + + if ( !setArraysOnce ) { + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + } + + // + // set state + // + if ( pStage->bundle[0].vertexLightmap && ( ( r_vertexLight->integer && !r_uiFullScreen->integer ) || glConfig.hardwareType == GLHW_PERMEDIA2 ) && r_lightmap->integer ) { + GL_Bind( tr.whiteImage ); + } else { + R_BindAnimatedImage( &pStage->bundle[0] ); + } + + // Ridah, per stage fogging (detail textures) + if ( tess.shader->noFog && pStage->isFogged ) { + R_FogOn(); + } else if ( tess.shader->noFog && !pStage->isFogged ) { + R_FogOff(); // turn it back off + } else { // make sure it's on + R_FogOn(); + } + // done. + + //----(SA) fading model stuff + fadeStart = backEnd.currentEntity->e.fadeStartTime; + + if ( fadeStart ) { + fadeEnd = backEnd.currentEntity->e.fadeEndTime; + if ( fadeStart > tr.refdef.time ) { // has not started to fade yet + GL_State( pStage->stateBits ); + } else + { + int i; + unsigned int tempState; + float alphaval; + + if ( fadeEnd < tr.refdef.time ) { // entity faded out completely + continue; + } + + alphaval = (float)( fadeEnd - tr.refdef.time ) / (float)( fadeEnd - fadeStart ); + + tempState = pStage->stateBits; + // remove the current blend, and don't write to Z buffer + tempState &= ~( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS | GLS_DEPTHMASK_TRUE ); + // set the blend to src_alpha, dst_one_minus_src_alpha + tempState |= ( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + GL_State( tempState ); + GL_Cull( CT_FRONT_SIDED ); + // modulate the alpha component of each vertex in the render list + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][0] *= alphaval; + tess.svars.colors[i][1] *= alphaval; + tess.svars.colors[i][2] *= alphaval; + tess.svars.colors[i][3] *= alphaval; + } + } + } else { + GL_State( pStage->stateBits ); + } + //----(SA) end + + // + // draw + // + R_DrawElements( input->numIndexes, input->indexes ); + } + // allow skipping out to show just lightmaps during development + if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap ) ) { + break; + } + } +} + + +/* +** RB_StageIteratorGeneric +*/ +void RB_StageIteratorGeneric( void ) { + shaderCommands_t *input; + + input = &tess; + + RB_DeformTessGeometry(); + + // + // log this call + // + if ( r_logFile->integer ) { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va( "--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name ) ); + } + + // set GL fog + SetIteratorFog(); + + + // + // set face culling appropriately + // + GL_Cull( input->shader->cullType ); + + // set polygon offset if necessary + if ( input->shader->polygonOffset ) { + qglEnable( GL_POLYGON_OFFSET_FILL ); + qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value ); + } + + // + // if there is only a single pass then we can enable color + // and texture arrays before we compile, otherwise we need + // to avoid compiling those arrays since they will change + // during multipass rendering + // + if ( tess.numPasses > 1 || input->shader->multitextureEnv ) { + setArraysOnce = qfalse; + qglDisableClientState( GL_COLOR_ARRAY ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + } else + { + setArraysOnce = qtrue; + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + } + + // + // lock XYZ + // + qglVertexPointer( 3, GL_FLOAT, 16, input->xyz ); // padded for SIMD + if ( qglLockArraysEXT ) { + qglLockArraysEXT( 0, input->numVertexes ); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + // + // enable color and texcoord arrays after the lock if necessary + // + if ( !setArraysOnce ) { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnableClientState( GL_COLOR_ARRAY ); + } + + // + // call shader function + // + RB_IterateStagesGeneric( input ); + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE + && !( tess.shader->surfaceFlags & ( SURF_NODLIGHT | SURF_SKY ) ) ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if ( qglUnlockArraysEXT ) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } +} + + +/* +** RB_StageIteratorVertexLitTexture +*/ +void RB_StageIteratorVertexLitTexture( void ) { + shaderCommands_t *input; + shader_t *shader; + + input = &tess; + + shader = input->shader; + + // + // compute colors + // + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); + + // + // log this call + // + if ( r_logFile->integer ) { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va( "--- RB_StageIteratorVertexLitTexturedUnfogged( %s ) ---\n", tess.shader->name ) ); + } + + + // set GL fog + SetIteratorFog(); + + // + // set face culling appropriately + // + GL_Cull( input->shader->cullType ); + + // + // set arrays and lock + // + qglEnableClientState( GL_COLOR_ARRAY ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] ); + qglVertexPointer( 3, GL_FLOAT, 16, input->xyz ); + + if ( qglLockArraysEXT ) { + qglLockArraysEXT( 0, input->numVertexes ); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + // + // call special shade routine + // + R_BindAnimatedImage( &tess.xstages[0]->bundle[0] ); + GL_State( tess.xstages[0]->stateBits ); + R_DrawElements( input->numIndexes, input->indexes ); + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if ( qglUnlockArraysEXT ) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } +} + +//define REPLACE_MODE + +void RB_StageIteratorLightmappedMultitexture( void ) { + shaderCommands_t *input; + + input = &tess; + + // + // log this call + // + if ( r_logFile->integer ) { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va( "--- RB_StageIteratorLightmappedMultitexture( %s ) ---\n", tess.shader->name ) ); + } + + // set GL fog + SetIteratorFog(); + + // + // set face culling appropriately + // + GL_Cull( input->shader->cullType ); + + // + // set color, pointers, and lock + // + GL_State( GLS_DEFAULT ); + qglVertexPointer( 3, GL_FLOAT, 16, input->xyz ); + +#ifdef REPLACE_MODE + qglDisableClientState( GL_COLOR_ARRAY ); + qglColor3f( 1, 1, 1 ); + qglShadeModel( GL_FLAT ); +#else + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.constantColor255 ); +#endif + + // + // select base stage + // + GL_SelectTexture( 0 ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + R_BindAnimatedImage( &tess.xstages[0]->bundle[0] ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] ); + + // + // configure second stage + // + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + if ( r_lightmap->integer ) { + GL_TexEnv( GL_REPLACE ); + } else { + GL_TexEnv( GL_MODULATE ); + } + +//----(SA) modified for snooper + if ( tess.xstages[0]->bundle[1].isLightmap && ( backEnd.refdef.rdflags & RDF_SNOOPERVIEW ) ) { + GL_Bind( tr.whiteImage ); + } else { + R_BindAnimatedImage( &tess.xstages[0]->bundle[1] ); + } + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][1] ); + + // + // lock arrays + // + if ( qglLockArraysEXT ) { + qglLockArraysEXT( 0, input->numVertexes ); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + // + // disable texturing on TEXTURE1, then select TEXTURE0 + // + qglDisable( GL_TEXTURE_2D ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + GL_SelectTexture( 0 ); +#ifdef REPLACE_MODE + GL_TexEnv( GL_MODULATE ); + qglShadeModel( GL_SMOOTH ); +#endif + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) { + ProjectDlightTexture(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // unlock arrays + // + if ( qglUnlockArraysEXT ) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } +} + +/* +** RB_EndSurface +*/ +void RB_EndSurface( void ) { + shaderCommands_t *input; + + input = &tess; + + if ( input->numIndexes == 0 ) { + return; + } + + if ( input->indexes[SHADER_MAX_INDEXES - 1] != 0 ) { + ri.Error( ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit" ); + } + if ( input->xyz[SHADER_MAX_VERTEXES - 1][0] != 0 ) { + ri.Error( ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit" ); + } + + if ( tess.shader == tr.shadowShader ) { + RB_ShadowTessEnd(); + return; + } + + // for debugging of sort order issues, stop rendering after a given sort value + if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) { + return; + } + + // + // update performance counters + // + backEnd.pc.c_shaders++; + backEnd.pc.c_vertexes += tess.numVertexes; + backEnd.pc.c_indexes += tess.numIndexes; + backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses; + + // + // call off to shader specific tess end function + // + tess.currentStageIteratorFunc(); + + // + // draw debugging stuff + // + if ( r_showtris->integer ) { + DrawTris( input ); + } + if ( r_shownormals->integer ) { + DrawNormals( input ); + } + + + // clear shader so we can tell we don't have any unclosed surfaces + tess.numIndexes = 0; + + GLimp_LogComment( "----------\n" ); +} + diff --git a/src/renderer/tr_shade_calc.c b/src/renderer/tr_shade_calc.c new file mode 100644 index 0000000..101bdd9 --- /dev/null +++ b/src/renderer/tr_shade_calc.c @@ -0,0 +1,1232 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_shade_calc.c + +#include "tr_local.h" + + +#define WAVEVALUE( table, base, amplitude, phase, freq ) ( ( base ) + table[ myftol( ( ( ( phase ) + tess.shaderTime * ( freq ) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * ( amplitude ) ) + +static float *TableForFunc( genFunc_t func ) { + switch ( func ) + { + case GF_SIN: + return tr.sinTable; + case GF_TRIANGLE: + return tr.triangleTable; + case GF_SQUARE: + return tr.squareTable; + case GF_SAWTOOTH: + return tr.sawToothTable; + case GF_INVERSE_SAWTOOTH: + return tr.inverseSawToothTable; + case GF_NONE: + default: + break; + } + + ri.Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'\n", func, tess.shader->name ); + return NULL; +} + +/* +** EvalWaveForm +** +** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly +*/ +static float EvalWaveForm( const waveForm_t *wf ) { + float *table; + + table = TableForFunc( wf->func ); + + return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency ); +} + +static float EvalWaveFormClamped( const waveForm_t *wf ) { + float glow = EvalWaveForm( wf ); + + if ( glow < 0 ) { + return 0; + } + + if ( glow > 1 ) { + return 1; + } + + return glow; +} + +/* +** RB_CalcStretchTexCoords +*/ +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st ) { + float p; + texModInfo_t tmi; + + p = 1.0f / EvalWaveForm( wf ); + + tmi.matrix[0][0] = p; + tmi.matrix[1][0] = 0; + tmi.translate[0] = 0.5f - 0.5f * p; + + tmi.matrix[0][1] = 0; + tmi.matrix[1][1] = p; + tmi.translate[1] = 0.5f - 0.5f * p; + + RB_CalcTransformTexCoords( &tmi, st ); +} + +/* +==================================================================== + +DEFORMATIONS + +==================================================================== +*/ + +/* +======================== +RB_CalcDeformVertexes + +======================== +*/ +void RB_CalcDeformVertexes( deformStage_t *ds ) { + int i; + vec3_t offset; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float *table; + + // Ridah + if ( ds->deformationWave.frequency < 0 ) { + qboolean inverse = qfalse; + vec3_t worldUp; + //static vec3_t up = {0,0,1}; + + if ( VectorCompare( backEnd.currentEntity->e.fireRiseDir, vec3_origin ) ) { + VectorSet( backEnd.currentEntity->e.fireRiseDir, 0, 0, 1 ); + } + + // get the world up vector in local coordinates + if ( backEnd.currentEntity->e.hModel ) { // world surfaces dont have an axis + VectorRotate( backEnd.currentEntity->e.fireRiseDir, backEnd.currentEntity->e.axis, worldUp ); + } else { + VectorCopy( backEnd.currentEntity->e.fireRiseDir, worldUp ); + } + // don't go so far if sideways, since they must be moving + VectorScale( worldUp, 0.4 + 0.6 * fabs( backEnd.currentEntity->e.fireRiseDir[2] ), worldUp ); + + ds->deformationWave.frequency *= -1; + if ( ds->deformationWave.frequency > 999 ) { // hack for negative Z deformation (ack) + inverse = qtrue; + ds->deformationWave.frequency -= 999; + } + + table = TableForFunc( ds->deformationWave.func ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; + float dot; + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase + off, + ds->deformationWave.frequency ); + + dot = DotProduct( worldUp, normal ); + + if ( dot * scale > 0 ) { + if ( inverse ) { + scale *= -1; + } + VectorMA( xyz, dot * scale, worldUp, xyz ); + } + } + + if ( inverse ) { + ds->deformationWave.frequency += 999; + } + ds->deformationWave.frequency *= -1; + } + // done. + else if ( ds->deformationWave.frequency == 0 ) { + scale = EvalWaveForm( &ds->deformationWave ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } else + { + table = TableForFunc( ds->deformationWave.func ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase + off, + ds->deformationWave.frequency ); + + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } +} + +/* +========================= +RB_CalcDeformNormals + +Wiggle the normals for wavy environment mapping +========================= +*/ +void RB_CalcDeformNormals( deformStage_t *ds ) { + int i; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { + scale = 0.98f; + scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 0 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 1 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 2 ] += ds->deformationWave.amplitude * scale; + + VectorNormalizeFast( normal ); + } +} + +/* +======================== +RB_CalcBulgeVertexes + +======================== +*/ +void RB_CalcBulgeVertexes( deformStage_t *ds ) { + int i; + const float *st = ( const float * ) tess.texCoords[0]; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float now; + + now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 4, normal += 4 ) { + int off; + float scale; + + off = (float)( FUNCTABLE_SIZE / ( M_PI * 2 ) ) * ( st[0] * ds->bulgeWidth + now ); + + scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; + + xyz[0] += normal[0] * scale; + xyz[1] += normal[1] * scale; + xyz[2] += normal[2] * scale; + } +} + + +/* +====================== +RB_CalcMoveVertexes + +A deformation that can move an entire surface along a wave path +====================== +*/ +void RB_CalcMoveVertexes( deformStage_t *ds ) { + int i; + float *xyz; + float *table; + float scale; + vec3_t offset; + + table = TableForFunc( ds->deformationWave.func ); + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase, + ds->deformationWave.frequency ); + + VectorScale( ds->moveVector, scale, offset ); + + xyz = ( float * ) tess.xyz; + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + VectorAdd( xyz, offset, xyz ); + } +} + + +/* +============= +DeformText + +Change a polygon into a bunch of text polygons +============= +*/ +void DeformText( const char *text ) { + int i; + vec3_t origin, width, height; + int len; + int ch; + byte color[4]; + float bottom, top; + vec3_t mid; + + height[0] = 0; + height[1] = 0; + height[2] = -1; + CrossProduct( tess.normal[0], height, width ); + + // find the midpoint of the box + VectorClear( mid ); + bottom = 999999; + top = -999999; + for ( i = 0 ; i < 4 ; i++ ) { + VectorAdd( tess.xyz[i], mid, mid ); + if ( tess.xyz[i][2] < bottom ) { + bottom = tess.xyz[i][2]; + } + if ( tess.xyz[i][2] > top ) { + top = tess.xyz[i][2]; + } + } + VectorScale( mid, 0.25f, origin ); + + // determine the individual character size + height[0] = 0; + height[1] = 0; + height[2] = ( top - bottom ) * 0.5f; + + VectorScale( width, height[2] * -0.75f, width ); + + // determine the starting position + len = strlen( text ); + VectorMA( origin, ( len - 1 ), width, origin ); + + // clear the shader indexes + tess.numIndexes = 0; + tess.numVertexes = 0; + + color[0] = color[1] = color[2] = color[3] = 255; + + // draw each character + for ( i = 0 ; i < len ; i++ ) { + ch = text[i]; + ch &= 255; + + if ( ch != ' ' ) { + int row, col; + float frow, fcol, size; + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625f; + fcol = col * 0.0625f; + size = 0.0625f; + + RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size ); + } + VectorMA( origin, -2, width, origin ); + } +} + +/* +================== +GlobalVectorToLocal +================== +*/ +void GlobalVectorToLocal( const vec3_t in, vec3_t out ) { + out[0] = DotProduct( in, backEnd.or.axis[0] ); + out[1] = DotProduct( in, backEnd.or.axis[1] ); + out[2] = DotProduct( in, backEnd.or.axis[2] ); +} + +/* +===================== +AutospriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites +===================== +*/ +static void AutospriteDeform( void ) { + int i; + int oldVerts; + float *xyz; + vec3_t mid, delta; + float radius; + vec3_t left, up; + vec3_t leftDir, upDir; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count", tess.shader->name ); + } + + oldVerts = tess.numVertexes; + tess.numVertexes = 0; + tess.numIndexes = 0; + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.or.axis[1], leftDir ); + GlobalVectorToLocal( backEnd.viewParms.or.axis[2], upDir ); + } else { + VectorCopy( backEnd.viewParms.or.axis[1], leftDir ); + VectorCopy( backEnd.viewParms.or.axis[2], upDir ); + } + + for ( i = 0 ; i < oldVerts ; i += 4 ) { + // find the midpoint + xyz = tess.xyz[i]; + + mid[0] = 0.25f * ( xyz[0] + xyz[4] + xyz[8] + xyz[12] ); + mid[1] = 0.25f * ( xyz[1] + xyz[5] + xyz[9] + xyz[13] ); + mid[2] = 0.25f * ( xyz[2] + xyz[6] + xyz[10] + xyz[14] ); + + VectorSubtract( xyz, mid, delta ); + radius = VectorLength( delta ) * 0.707f; // / sqrt(2) + + VectorScale( leftDir, radius, left ); + VectorScale( upDir, radius, up ); + + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + // compensate for scale in the axes if necessary + if ( backEnd.currentEntity->e.nonNormalizedAxes ) { + float axisLength; + axisLength = VectorLength( backEnd.currentEntity->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + VectorScale( left, axisLength, left ); + VectorScale( up, axisLength, up ); + } + + RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] ); + } +} + + +/* +===================== +Autosprite2Deform + +Autosprite2 will pivot a rectangular quad along the center of its long axis +===================== +*/ +int edgeVerts[6][2] = { + { 0, 1 }, + { 0, 2 }, + { 0, 3 }, + { 1, 2 }, + { 1, 3 }, + { 2, 3 } +}; + +static void Autosprite2Deform( void ) { + int i, j, k; + int indexes; + float *xyz; + vec3_t forward; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count", tess.shader->name ); + } + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.or.axis[0], forward ); + } else { + VectorCopy( backEnd.viewParms.or.axis[0], forward ); + } + + // this is a lot of work for two triangles... + // we could precalculate a lot of it is an issue, but it would mess up + // the shader abstraction + for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i += 4, indexes += 6 ) { + float lengths[2]; + int nums[2]; + vec3_t mid[2]; + vec3_t major, minor; + float *v1, *v2; + + // find the midpoint + xyz = tess.xyz[i]; + + // identify the two shortest edges + nums[0] = nums[1] = 0; + lengths[0] = lengths[1] = 999999; + + for ( j = 0 ; j < 6 ; j++ ) { + float l; + vec3_t temp; + + v1 = xyz + 4 * edgeVerts[j][0]; + v2 = xyz + 4 * edgeVerts[j][1]; + + VectorSubtract( v1, v2, temp ); + + l = DotProduct( temp, temp ); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + for ( j = 0 ; j < 2 ; j++ ) { + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + mid[j][0] = 0.5f * ( v1[0] + v2[0] ); + mid[j][1] = 0.5f * ( v1[1] + v2[1] ); + mid[j][2] = 0.5f * ( v1[2] + v2[2] ); + } + + // find the vector of the major axis + VectorSubtract( mid[1], mid[0], major ); + + // cross this with the view direction to get minor axis + CrossProduct( major, forward, minor ); + VectorNormalize( minor ); + + // re-project the points + for ( j = 0 ; j < 2 ; j++ ) { + float l; + + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + l = 0.5f * sqrt( lengths[j] ); + + // we need to see which direction this edge + // is used to determine direction of projection + for ( k = 0 ; k < 5 ; k++ ) { + if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] + && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) { + break; + } + } + + if ( k == 5 ) { + VectorMA( mid[j], l, minor, v1 ); + VectorMA( mid[j], -l, minor, v2 ); + } else { + VectorMA( mid[j], -l, minor, v1 ); + VectorMA( mid[j], l, minor, v2 ); + } + } + } +} + + +/* +===================== +RB_DeformTessGeometry + +===================== +*/ +void RB_DeformTessGeometry( void ) { + int i; + deformStage_t *ds; + + for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) { + ds = &tess.shader->deforms[ i ]; + + switch ( ds->deformation ) { + case DEFORM_NONE: + break; + case DEFORM_NORMALS: + RB_CalcDeformNormals( ds ); + break; + case DEFORM_WAVE: + RB_CalcDeformVertexes( ds ); + break; + case DEFORM_BULGE: + RB_CalcBulgeVertexes( ds ); + break; + case DEFORM_MOVE: + RB_CalcMoveVertexes( ds ); + break; + case DEFORM_PROJECTION_SHADOW: + RB_ProjectionShadowDeform(); + break; + case DEFORM_AUTOSPRITE: + AutospriteDeform(); + break; + case DEFORM_AUTOSPRITE2: + Autosprite2Deform(); + break; + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: + DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] ); + break; + } + } +} + +/* +==================================================================== + +COLORS + +==================================================================== +*/ + + +/* +** RB_CalcColorFromEntity +*/ +void RB_CalcColorFromEntity( unsigned char *dstColors ) { + int i; + int *pColors = ( int * ) dstColors; + int c; + + if ( !backEnd.currentEntity ) { + return; + } + + c = *( int * ) backEnd.currentEntity->e.shaderRGBA; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = c; + } +} + +/* +** RB_CalcColorFromOneMinusEntity +*/ +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ) { + int i; + int *pColors = ( int * ) dstColors; + unsigned char invModulate[3]; + int c; + + if ( !backEnd.currentEntity ) { + return; + } + + invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; + invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; + invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; + invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it + + c = *( int * ) invModulate; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = *( int * ) invModulate; + } +} + +/* +** RB_CalcAlphaFromEntity +*/ +void RB_CalcAlphaFromEntity( unsigned char *dstColors ) { + int i; + + if ( !backEnd.currentEntity ) { + return; + } + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +/* +** RB_CalcAlphaFromOneMinusEntity +*/ +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ) { + int i; + + if ( !backEnd.currentEntity ) { + return; + } + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +/* +** RB_CalcWaveColor +*/ +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ) { + int i; + int v; + float glow; + int *colors = ( int * ) dstColors; + byte color[4]; + + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } else if ( glow > 1 ) { + glow = 1; + } + + v = myftol( 255 * glow ); + color[0] = color[1] = color[2] = v; + color[3] = 255; + v = *(int *)color; + + for ( i = 0; i < tess.numVertexes; i++, colors++ ) { + *colors = v; + } +} + +/* +** RB_CalcWaveAlpha +*/ +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ) { + int i; + int v; + float glow; + + glow = EvalWaveFormClamped( wf ); + + v = 255 * glow; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + dstColors[3] = v; + } +} + +/* +** RB_CalcModulateColorsByFog +*/ +void RB_CalcModulateColorsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + } +} + +/* +** RB_CalcModulateAlphasByFog +*/ +void RB_CalcModulateAlphasByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[3] *= f; + } +} + +/* +** RB_CalcModulateRGBAsByFog +*/ +void RB_CalcModulateRGBAsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + colors[3] *= f; + } +} + + +/* +==================================================================== + +TEX COORDS + +==================================================================== +*/ + +/* +======================== +RB_CalcFogTexCoords + +To do the clipped fog plane really correctly, we should use +projected textures, but I don't trust the drivers and it +doesn't fit our shader data. +======================== +*/ +void RB_CalcFogTexCoords( float *st ) { + int i; + float *v; + float s, t; + float eyeT; + qboolean eyeOutside; + fog_t *fog; + vec3_t local; + vec4_t fogDistanceVector, fogDepthVector; + + fog = tr.world->fogs + tess.fogNum; + + // all fogging distance is based on world Z units + VectorSubtract( backEnd.or.origin, backEnd.viewParms.or.origin, local ); + fogDistanceVector[0] = -backEnd.or.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.or.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.or.modelMatrix[10]; + fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.or.axis[0] ); + + // scale the fog vectors based on the fog's thickness + fogDistanceVector[0] *= fog->tcScale; + fogDistanceVector[1] *= fog->tcScale; + fogDistanceVector[2] *= fog->tcScale; + fogDistanceVector[3] *= fog->tcScale; + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.or.axis[0][0] + + fog->surface[1] * backEnd.or.axis[0][1] + fog->surface[2] * backEnd.or.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.or.axis[1][0] + + fog->surface[1] * backEnd.or.axis[1][1] + fog->surface[2] * backEnd.or.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.or.axis[2][0] + + fog->surface[1] * backEnd.or.axis[2][1] + fog->surface[2] * backEnd.or.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.or.origin, fog->surface ); + + eyeT = DotProduct( backEnd.or.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + eyeT = 1; // non-surface fog always has eye inside + } + + // see if the viewpoint is outside + // this is needed for clipping distance even for constant fog + + if ( eyeT < 0 ) { + eyeOutside = qtrue; + } else { + eyeOutside = qfalse; + } + + fogDistanceVector[3] += 1.0 / 512; + + // calculate density for each point + for ( i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4 ) { + // calculate the length in fog + s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3]; + t = DotProduct( v, fogDepthVector ) + fogDepthVector[3]; + + // partially clipped fogs use the T axis + if ( eyeOutside ) { + if ( t < 1.0 ) { + t = 1.0 / 32; // point is outside, so no fogging + } else { + t = 1.0 / 32 + 30.0 / 32 * t / ( t - eyeT ); // cut the distance at the fog plane + } + } else { + if ( t < 0 ) { + t = 1.0 / 32; // point is outside, so no fogging + } else { + t = 31.0 / 32; + } + } + + st[0] = s; + st[1] = t; + st += 2; + } +} + + + +/* +** RB_CalcEnvironmentTexCoords +*/ +void RB_CalcEnvironmentTexCoords( float *st ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float d; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + for ( i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) + { + VectorSubtract( backEnd.or.viewOrigin, v, viewer ); + VectorNormalizeFast( viewer ); + + d = DotProduct( normal, viewer ); + + reflected[0] = normal[0] * 2 * d - viewer[0]; + reflected[1] = normal[1] * 2 * d - viewer[1]; + reflected[2] = normal[2] * 2 * d - viewer[2]; + + st[0] = 0.5 + reflected[1] * 0.5; + st[1] = 0.5 - reflected[2] * 0.5; + } +} + +/* +** RB_CalcFireRiseEnvTexCoords +*/ +void RB_CalcFireRiseEnvTexCoords( float *st ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float d; + + v = tess.xyz[0]; + normal = tess.normal[0]; + VectorNegate( backEnd.currentEntity->e.fireRiseDir, viewer ); + + for ( i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) + { + VectorNormalizeFast( viewer ); + + d = DotProduct( normal, viewer ); + + reflected[0] = normal[0] * 2 * d - viewer[0]; + reflected[1] = normal[1] * 2 * d - viewer[1]; + reflected[2] = normal[2] * 2 * d - viewer[2]; + + st[0] = 0.5 + reflected[1] * 0.5; + st[1] = 0.5 - reflected[2] * 0.5; + } +} + + +/* +** RB_CalcSwapTexCoords +*/ +void RB_CalcSwapTexCoords( float *st ) { + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = t; + st[1] = 1.0 - s; // err, flaming effect needs this + } +} + +/* +** RB_CalcTurbulentTexCoords +*/ +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st ) { + int i; + float now; + + now = ( wf->phase + tess.shaderTime * wf->frequency ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] ) * 1.0 / 128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0 / 128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + } +} + +/* +** RB_CalcScaleTexCoords +*/ +void RB_CalcScaleTexCoords( const float scale[2], float *st ) { + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] *= scale[0]; + st[1] *= scale[1]; + } +} + +/* +** RB_CalcScrollTexCoords +*/ +void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st ) { + int i; + float timeScale = tess.shaderTime; + float adjustedScrollS, adjustedScrollT; + + adjustedScrollS = scrollSpeed[0] * timeScale; + adjustedScrollT = scrollSpeed[1] * timeScale; + + // clamp so coordinates don't continuously get larger, causing problems + // with hardware limits + adjustedScrollS = adjustedScrollS - floor( adjustedScrollS ); + adjustedScrollT = adjustedScrollT - floor( adjustedScrollT ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] += adjustedScrollS; + st[1] += adjustedScrollT; + } +} + +/* +** RB_CalcTransformTexCoords +*/ +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st ) { + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0]; + st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1]; + } +} + +/* +** RB_CalcRotateTexCoords +*/ +void RB_CalcRotateTexCoords( float degsPerSecond, float *st ) { + float timeScale = tess.shaderTime; + float degs; + int index; + float sinValue, cosValue; + texModInfo_t tmi; + + degs = -degsPerSecond * timeScale; + index = degs * ( FUNCTABLE_SIZE / 360.0f ); + + sinValue = tr.sinTable[ index & FUNCTABLE_MASK ]; + cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; + + tmi.matrix[0][0] = cosValue; + tmi.matrix[1][0] = -sinValue; + tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; + + tmi.matrix[0][1] = sinValue; + tmi.matrix[1][1] = cosValue; + tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; + + RB_CalcTransformTexCoords( &tmi, st ); +} + + + + + + +#if id386 && !( ( defined __linux__ || defined __FreeBSD__ ) && ( defined __i386__ ) ) // rb010123 + +long myftol( float f ) { + static int tmp; + __asm fld f + __asm fistp tmp + __asm mov eax, tmp +} + +#endif + +/* +** RB_CalcSpecularAlpha +** +** Calculates specular coefficient and places it in the alpha channel +*/ +vec3_t lightOrigin = { -960, 1980, 96 }; // FIXME: track dynamically + +void RB_CalcSpecularAlpha( unsigned char *alphas ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float l, d; + int b; + vec3_t lightDir; + int numVertexes; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + alphas += 3; + + numVertexes = tess.numVertexes; + for ( i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4 ) { + float ilength; + + VectorSubtract( lightOrigin, v, lightDir ); +// ilength = Q_rsqrt( DotProduct( lightDir, lightDir ) ); + VectorNormalizeFast( lightDir ); + + // calculate the specular color + d = DotProduct( normal, lightDir ); +// d *= ilength; + + // we don't optimize for the d < 0 case since this tends to + // cause visual artifacts such as faceted "snapping" + reflected[0] = normal[0] * 2 * d - lightDir[0]; + reflected[1] = normal[1] * 2 * d - lightDir[1]; + reflected[2] = normal[2] * 2 * d - lightDir[2]; + + VectorSubtract( backEnd.or.viewOrigin, v, viewer ); + ilength = Q_rsqrt( DotProduct( viewer, viewer ) ); + l = DotProduct( reflected, viewer ); + l *= ilength; + + if ( l < 0 ) { + b = 0; + } else { + l = l * l; + l = l * l; + b = l * 255; + if ( b > 255 ) { + b = 255; + } + } + + *alphas = b; + } +} + +/* +** RB_CalcDiffuseColor +** +** The basic vertex lighting calc +*/ +void RB_CalcDiffuseColor( unsigned char *colors ) { + int i, j; + float *v, *normal; + float incoming; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + int numVertexes; + + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + VectorCopy( ent->ambientLight, ambientLight ); + VectorCopy( ent->directedLight, directedLight ); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + for ( i = 0 ; i < numVertexes ; i++, v += 4, normal += 4 ) { + incoming = DotProduct( normal, lightDir ); + if ( incoming <= 0 ) { + *(int *)&colors[i * 4] = ambientLightInt; + continue; + } + j = myftol( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + colors[i * 4 + 0] = j; + + j = myftol( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + colors[i * 4 + 1] = j; + + j = myftol( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + colors[i * 4 + 2] = j; + + colors[i * 4 + 3] = 255; + } +} + diff --git a/src/renderer/tr_shader.c b/src/renderer/tr_shader.c new file mode 100644 index 0000000..2cd8c2c --- /dev/null +++ b/src/renderer/tr_shader.c @@ -0,0 +1,3376 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "tr_local.h" + +// tr_shader.c -- this file deals with the parsing and definition of shaders + +static char *s_shaderText; + +// the shader is parsed into these global variables, then copied into +// dynamically allocated memory if it is valid. +static shaderStage_t stages[MAX_SHADER_STAGES]; +static shader_t shader; +static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS]; +static qboolean deferLoad; + +#define FILE_HASH_SIZE 4096 + +static shader_t* hashTable[FILE_HASH_SIZE]; + +// Ridah +// Table containing string indexes for each shader found in the scripts, referenced by their checksum +// values. +typedef struct shaderStringPointer_s +{ + char *pStr; + struct shaderStringPointer_s *next; +} shaderStringPointer_t; +// +shaderStringPointer_t shaderChecksumLookup[FILE_HASH_SIZE]; +// done. + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter == '\\' ) { + letter = '/'; // damn path names + } + if ( letter == PATH_SEP ) { + letter = '/'; // damn path names + } + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( FILE_HASH_SIZE - 1 ); + return hash; +} + +void R_RemapShader( const char *shaderName, const char *newShaderName, const char *timeOffset ) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh, *sh2; + qhandle_t h; + + sh = R_FindShaderByName( shaderName ); + if ( sh == NULL || sh == tr.defaultShader ) { + h = RE_RegisterShaderLightMap( shaderName, 0 ); + sh = R_GetShaderByHandle( h ); + } + if ( sh == NULL || sh == tr.defaultShader ) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName ); + return; + } + + sh2 = R_FindShaderByName( newShaderName ); + if ( sh2 == NULL || sh2 == tr.defaultShader ) { + h = RE_RegisterShaderLightMap( newShaderName, 0 ); + sh2 = R_GetShaderByHandle( h ); + } + + if ( sh2 == NULL || sh2 == tr.defaultShader ) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName ); + return; + } + + // remap all the shaders with the given name + // even tho they might have different lightmaps + COM_StripExtension2( shaderName, strippedName, sizeof( strippedName ) ); + hash = generateHashValue( strippedName ); + for ( sh = hashTable[hash]; sh; sh = sh->next ) { + if ( Q_stricmp( sh->name, strippedName ) == 0 ) { + if ( sh != sh2 ) { + sh->remappedShader = sh2; + } else { + sh->remappedShader = NULL; + } + } + } + if ( timeOffset ) { + sh2->timeOffset = atof( timeOffset ); + } +} + +/* +=============== +ParseVector +=============== +*/ +static qboolean ParseVector( char **text, int count, float *v ) { + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return qfalse; + } + + for ( i = 0 ; i < count ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name ); + return qfalse; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +NameToAFunc +=============== +*/ +static unsigned NameToAFunc( const char *funcname ) { + if ( !Q_stricmp( funcname, "GT0" ) ) { + return GLS_ATEST_GT_0; + } else if ( !Q_stricmp( funcname, "LT128" ) ) { + return GLS_ATEST_LT_80; + } else if ( !Q_stricmp( funcname, "GE128" ) ) { + return GLS_ATEST_GE_80; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name ); + return 0; +} + + +/* +=============== +NameToSrcBlendMode +=============== +*/ +static int NameToSrcBlendMode( const char *name ) { + if ( !Q_stricmp( name, "GL_ONE" ) ) { + return GLS_SRCBLEND_ONE; + } else if ( !Q_stricmp( name, "GL_ZERO" ) ) { + return GLS_SRCBLEND_ZERO; + } else if ( !Q_stricmp( name, "GL_DST_COLOR" ) ) { + return GLS_SRCBLEND_DST_COLOR; + } else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) ) { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) { + return GLS_SRCBLEND_SRC_ALPHA; + } else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) { + return GLS_SRCBLEND_DST_ALPHA; + } else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) { + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) ) { + return GLS_SRCBLEND_ALPHA_SATURATE; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_SRCBLEND_ONE; +} + +/* +=============== +NameToDstBlendMode +=============== +*/ +static int NameToDstBlendMode( const char *name ) { + if ( !Q_stricmp( name, "GL_ONE" ) ) { + return GLS_DSTBLEND_ONE; + } else if ( !Q_stricmp( name, "GL_ZERO" ) ) { + return GLS_DSTBLEND_ZERO; + } else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) { + return GLS_DSTBLEND_SRC_ALPHA; + } else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) { + return GLS_DSTBLEND_DST_ALPHA; + } else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) { + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) ) { + return GLS_DSTBLEND_SRC_COLOR; + } else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) ) { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_DSTBLEND_ONE; +} + +/* +=============== +NameToGenFunc +=============== +*/ +static genFunc_t NameToGenFunc( const char *funcname ) { + if ( !Q_stricmp( funcname, "sin" ) ) { + return GF_SIN; + } else if ( !Q_stricmp( funcname, "square" ) ) { + return GF_SQUARE; + } else if ( !Q_stricmp( funcname, "triangle" ) ) { + return GF_TRIANGLE; + } else if ( !Q_stricmp( funcname, "sawtooth" ) ) { + return GF_SAWTOOTH; + } else if ( !Q_stricmp( funcname, "inversesawtooth" ) ) { + return GF_INVERSE_SAWTOOTH; + } else if ( !Q_stricmp( funcname, "noise" ) ) { + return GF_NOISE; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name ); + return GF_SIN; +} + + +/* +=================== +ParseWaveForm +=================== +*/ +static void ParseWaveForm( char **text, waveForm_t *wave ) { + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->func = NameToGenFunc( token ); + + // BASE, AMP, PHASE, FREQ + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->frequency = atof( token ); +} + + +/* +=================== +ParseTexMod +=================== +*/ +static void ParseTexMod( char *_text, shaderStage_t *stage ) { + const char *token; + char **text = &_text; + texModInfo_t *tmi; + + if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) { + ri.Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name ); + return; + } + + tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; + stage->bundle[0].numTexMods++; + + token = COM_ParseExt( text, qfalse ); + + // + // swap + // + if ( !Q_stricmp( token, "swap" ) ) { // swap S/T coords (rotate 90d) + tmi->type = TMOD_SWAP; + } + // + // turb + // + // (SA) added 'else' so it wouldn't claim 'swap' was unknown. + else if ( !Q_stricmp( token, "turb" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_TURBULENT; + } + // + // scale + // + else if ( !Q_stricmp( token, "scale" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[1] = atof( token ); + tmi->type = TMOD_SCALE; + } + // + // scroll + // + else if ( !Q_stricmp( token, "scroll" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[1] = atof( token ); + tmi->type = TMOD_SCROLL; + } + // + // stretch + // + else if ( !Q_stricmp( token, "stretch" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.func = NameToGenFunc( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_STRETCH; + } + // + // transform + // + else if ( !Q_stricmp( token, "transform" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); + + tmi->type = TMOD_TRANSFORM; + } + // + // rotate + // + else if ( !Q_stricmp( token, "rotate" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name ); + return; + } + tmi->rotateSpeed = atof( token ); + tmi->type = TMOD_ROTATE; + } + // + // entityTranslate + // + else if ( !Q_stricmp( token, "entityTranslate" ) ) { + tmi->type = TMOD_ENTITY_TRANSLATE; + } else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name ); + } +} + + +/* +=================== +ParseStage +=================== +*/ +static qboolean ParseStage( shaderStage_t *stage, char **text ) { + char *token; + int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0; + qboolean depthMaskExplicit = qfalse; + + stage->active = qtrue; + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" ); + return qfalse; + } + + if ( token[0] == '}' ) { + break; + } + // + // check special case for map16/map32/mapcomp/mapnocomp (compression enabled) + if ( !Q_stricmp( token, "map16" ) ) { // only use this texture if 16 bit color depth + if ( glConfig.colorBits <= 16 ) { + token = "map"; // use this map + } else { + COM_ParseExt( text, qfalse ); // ignore the map + continue; + } + } else if ( !Q_stricmp( token, "map32" ) ) { // only use this texture if 16 bit color depth + if ( glConfig.colorBits > 16 ) { + token = "map"; // use this map + } else { + COM_ParseExt( text, qfalse ); // ignore the map + continue; + } + } else if ( !Q_stricmp( token, "mapcomp" ) ) { // only use this texture if compression is enabled + if ( glConfig.textureCompression && r_ext_compressed_textures->integer ) { + token = "map"; // use this map + } else { + COM_ParseExt( text, qfalse ); // ignore the map + continue; + } + } else if ( !Q_stricmp( token, "mapnocomp" ) ) { // only use this texture if compression is not available or disabled + if ( !glConfig.textureCompression ) { + token = "map"; // use this map + } else { + COM_ParseExt( text, qfalse ); // ignore the map + continue; + } + } else if ( !Q_stricmp( token, "animmapcomp" ) ) { // only use this texture if compression is enabled + if ( glConfig.textureCompression && r_ext_compressed_textures->integer ) { + token = "animmap"; // use this map + } else { + while ( token[0] ) + COM_ParseExt( text, qfalse ); // ignore the map + continue; + } + } else if ( !Q_stricmp( token, "animmapnocomp" ) ) { // only use this texture if compression is not available or disabled + if ( !glConfig.textureCompression ) { + token = "animmap"; // use this map + } else { + while ( token[0] ) + COM_ParseExt( text, qfalse ); // ignore the map + continue; + } + } + // + // map + // + if ( !Q_stricmp( token, "map" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + +//----(SA) fixes startup error and allows polygon shadows to work again + if ( !Q_stricmp( token, "$whiteimage" ) || !Q_stricmp( token, "*white" ) ) { +//----(SA) end + stage->bundle[0].image[0] = tr.whiteImage; + continue; + } +//----(SA) added + else if ( !Q_stricmp( token, "$dlight" ) ) { + stage->bundle[0].image[0] = tr.dlightImage; + continue; + } +//----(SA) end + else if ( !Q_stricmp( token, "$lightmap" ) ) { + stage->bundle[0].isLightmap = qtrue; + if ( shader.lightmapIndex < 0 ) { + stage->bundle[0].image[0] = tr.whiteImage; + } else { + stage->bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + } + continue; + } else + { + stage->bundle[0].image[0] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT ); + if ( !stage->bundle[0].image[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + } + // + // clampmap + // + else if ( !Q_stricmp( token, "clampmap" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + stage->bundle[0].image[0] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_CLAMP ); + if ( !stage->bundle[0].image[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + // + // animMap .... + // + else if ( !Q_stricmp( token, "animMap" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + stage->bundle[0].imageAnimationSpeed = atof( token ); + + // parse up to MAX_IMAGE_ANIMATIONS animations + while ( 1 ) { + int num; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + break; + } + num = stage->bundle[0].numImageAnimations; + if ( num < MAX_IMAGE_ANIMATIONS ) { + stage->bundle[0].image[num] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT ); + if ( !stage->bundle[0].image[num] ) { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + stage->bundle[0].numImageAnimations++; + } + } + } else if ( !Q_stricmp( token, "videoMap" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic( token, 0, 0, 256, 256, ( CIN_loop | CIN_silent | CIN_shader ) ); + if ( stage->bundle[0].videoMapHandle != -1 ) { + stage->bundle[0].isVideoMap = qtrue; + stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } + } + // + // alphafunc + // + else if ( !Q_stricmp( token, "alphaFunc" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + atestBits = NameToAFunc( token ); + } + // + // depthFunc + // + else if ( !Q_stricmp( token, "depthfunc" ) ) { + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "lequal" ) ) { + depthFuncBits = 0; + } else if ( !Q_stricmp( token, "equal" ) ) { + depthFuncBits = GLS_DEPTHFUNC_EQUAL; + } else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // detail + // + else if ( !Q_stricmp( token, "detail" ) ) { + stage->isDetail = qtrue; + } + // + // fog + // + else if ( !Q_stricmp( token, "fog" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for fog in shader '%s'\n", shader.name ); + continue; + } + if ( !Q_stricmp( token, "on" ) ) { + stage->isFogged = qtrue; + } else { + stage->isFogged = qfalse; + } + } + // + // blendfunc + // or blendfunc + // + else if ( !Q_stricmp( token, "blendfunc" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + // check for "simple" blends first + if ( !Q_stricmp( token, "add" ) ) { + blendSrcBits = GLS_SRCBLEND_ONE; + blendDstBits = GLS_DSTBLEND_ONE; + } else if ( !Q_stricmp( token, "filter" ) ) { + blendSrcBits = GLS_SRCBLEND_DST_COLOR; + blendDstBits = GLS_DSTBLEND_ZERO; + } else if ( !Q_stricmp( token, "blend" ) ) { + blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; + blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else { + // complex double blends + blendSrcBits = NameToSrcBlendMode( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + blendDstBits = NameToDstBlendMode( token ); + } + + // clear depth mask for blended surfaces + if ( !depthMaskExplicit ) { + depthMaskBits = 0; + } + } + // + // rgbGen + // + else if ( !Q_stricmp( token, "rgbGen" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) { + ParseWaveForm( text, &stage->rgbWave ); + stage->rgbGen = CGEN_WAVEFORM; + } else if ( !Q_stricmp( token, "const" ) ) { + vec3_t color; + + ParseVector( text, 3, color ); + stage->constantColor[0] = 255 * color[0]; + stage->constantColor[1] = 255 * color[1]; + stage->constantColor[2] = 255 * color[2]; + + stage->rgbGen = CGEN_CONST; + } else if ( !Q_stricmp( token, "identity" ) ) { + stage->rgbGen = CGEN_IDENTITY; + } else if ( !Q_stricmp( token, "identityLighting" ) ) { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } else if ( !Q_stricmp( token, "entity" ) ) { + stage->rgbGen = CGEN_ENTITY; + } else if ( !Q_stricmp( token, "oneMinusEntity" ) ) { + stage->rgbGen = CGEN_ONE_MINUS_ENTITY; + } else if ( !Q_stricmp( token, "vertex" ) ) { + stage->rgbGen = CGEN_VERTEX; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } else if ( !Q_stricmp( token, "exactVertex" ) ) { + stage->rgbGen = CGEN_EXACT_VERTEX; + } else if ( !Q_stricmp( token, "lightingDiffuse" ) ) { + stage->rgbGen = CGEN_LIGHTING_DIFFUSE; + } else if ( !Q_stricmp( token, "oneMinusVertex" ) ) { + stage->rgbGen = CGEN_ONE_MINUS_VERTEX; + } else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // alphaGen + // + else if ( !Q_stricmp( token, "alphaGen" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) { + ParseWaveForm( text, &stage->alphaWave ); + stage->alphaGen = AGEN_WAVEFORM; + } else if ( !Q_stricmp( token, "const" ) ) { + token = COM_ParseExt( text, qfalse ); + stage->constantColor[3] = 255 * atof( token ); + stage->alphaGen = AGEN_CONST; + } else if ( !Q_stricmp( token, "identity" ) ) { + stage->alphaGen = AGEN_IDENTITY; + } else if ( !Q_stricmp( token, "entity" ) ) { + stage->alphaGen = AGEN_ENTITY; + } else if ( !Q_stricmp( token, "oneMinusEntity" ) ) { + stage->alphaGen = AGEN_ONE_MINUS_ENTITY; + } + // Ridah + else if ( !Q_stricmp( token, "normalzfade" ) ) { + stage->alphaGen = AGEN_NORMALZFADE; + token = COM_ParseExt( text, qfalse ); + if ( token[0] ) { + stage->constantColor[3] = 255 * atof( token ); + } else { + stage->constantColor[3] = 255; + } + + token = COM_ParseExt( text, qfalse ); + if ( token[0] ) { + stage->zFadeBounds[0] = atof( token ); // lower range + token = COM_ParseExt( text, qfalse ); + stage->zFadeBounds[1] = atof( token ); // upper range + } else { + stage->zFadeBounds[0] = -1.0; // lower range + stage->zFadeBounds[1] = 1.0; // upper range + } + + } + // done. + else if ( !Q_stricmp( token, "vertex" ) ) { + stage->alphaGen = AGEN_VERTEX; + } else if ( !Q_stricmp( token, "lightingSpecular" ) ) { + stage->alphaGen = AGEN_LIGHTING_SPECULAR; + } else if ( !Q_stricmp( token, "oneMinusVertex" ) ) { + stage->alphaGen = AGEN_ONE_MINUS_VERTEX; + } else if ( !Q_stricmp( token, "portal" ) ) { + stage->alphaGen = AGEN_PORTAL; + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + shader.portalRange = 256; + ri.Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name ); + } else + { + shader.portalRange = atof( token ); + } + } else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // tcGen + // + else if ( !Q_stricmp( token, "texgen" ) || !Q_stricmp( token, "tcGen" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "environment" ) ) { + stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED; + } else if ( !Q_stricmp( token, "firerisenv" ) ) { + stage->bundle[0].tcGen = TCGEN_FIRERISEENV_MAPPED; + } else if ( !Q_stricmp( token, "lightmap" ) ) { + stage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) ) { + stage->bundle[0].tcGen = TCGEN_TEXTURE; + } else if ( !Q_stricmp( token, "vector" ) ) { + ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] ); + ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] ); + + stage->bundle[0].tcGen = TCGEN_VECTOR; + } else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name ); + } + } + // + // tcMod <...> + // + else if ( !Q_stricmp( token, "tcMod" ) ) { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + break; + } + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseTexMod( buffer, stage ); + + continue; + } + // + // depthmask + // + else if ( !Q_stricmp( token, "depthwrite" ) ) { + depthMaskBits = GLS_DEPTHMASK_TRUE; + depthMaskExplicit = qtrue; + + continue; + } else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // if cgen isn't explicitly specified, use either identity or identitylighting + // + if ( stage->rgbGen == CGEN_BAD ) { + if ( blendSrcBits == 0 || + blendSrcBits == GLS_SRCBLEND_ONE || + blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } else { + stage->rgbGen = CGEN_IDENTITY; + } + } + + + // + // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending + // + if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && + ( blendDstBits == GLS_DSTBLEND_ZERO ) ) { + blendDstBits = blendSrcBits = 0; + depthMaskBits = GLS_DEPTHMASK_TRUE; + } + + // decide which agens we can skip + if ( stage->alphaGen == CGEN_IDENTITY ) { + if ( stage->rgbGen == CGEN_IDENTITY + || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) { + stage->alphaGen = AGEN_SKIP; + } + } + + // + // compute state bits + // + stage->stateBits = depthMaskBits | + blendSrcBits | blendDstBits | + atestBits | + depthFuncBits; + + return qtrue; +} + +/* +=============== +ParseDeform + +deformVertexes wave +deformVertexes normal +deformVertexes move +deformVertexes bulge +deformVertexes projectionShadow +deformVertexes autoSprite +deformVertexes autoSprite2 +deformVertexes text[0-7] +=============== +*/ +static void ParseDeform( char **text ) { + char *token; + deformStage_t *ds; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name ); + return; + } + + if ( shader.numDeforms == MAX_SHADER_DEFORMS ) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name ); + return; + } + + ds = &shader.deforms[ shader.numDeforms ]; + shader.numDeforms++; + + if ( !Q_stricmp( token, "projectionShadow" ) ) { + ds->deformation = DEFORM_PROJECTION_SHADOW; + return; + } + + if ( !Q_stricmp( token, "autosprite" ) ) { + ds->deformation = DEFORM_AUTOSPRITE; + return; + } + + if ( !Q_stricmp( token, "autosprite2" ) ) { + ds->deformation = DEFORM_AUTOSPRITE2; + return; + } + + if ( !Q_stricmpn( token, "text", 4 ) ) { + int n; + + n = token[4] - '0'; + if ( n < 0 || n > 7 ) { + n = 0; + } + ds->deformation = DEFORM_TEXT0 + n; + return; + } + + if ( !Q_stricmp( token, "bulge" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeWidth = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeHeight = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeSpeed = atof( token ); + + ds->deformation = DEFORM_BULGE; + return; + } + + if ( !Q_stricmp( token, "wave" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + + if ( atof( token ) != 0 ) { + ds->deformationSpread = 1.0f / atof( token ); + } else + { + ds->deformationSpread = 100.0f; + ri.Printf( PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_WAVE; + return; + } + + if ( !Q_stricmp( token, "normal" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.frequency = atof( token ); + + ds->deformation = DEFORM_NORMALS; + return; + } + + if ( !Q_stricmp( token, "move" ) ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->moveVector[i] = atof( token ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_MOVE; + return; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name ); +} + + +/* +=============== +ParseSkyParms + +skyParms +=============== +*/ +static void ParseSkyParms( char **text ) { + char *token; + static char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; + char pathname[MAX_QPATH]; + int i; + + // outerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for ( i = 0 ; i < 6 ; i++ ) { + Com_sprintf( pathname, sizeof( pathname ), "%s_%s.tga" + , token, suf[i] ); + shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_CLAMP ); + if ( !shader.sky.outerbox[i] ) { + shader.sky.outerbox[i] = tr.defaultImage; + } + } + } + + // cloudheight + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + shader.sky.cloudHeight = atof( token ); + if ( !shader.sky.cloudHeight ) { + shader.sky.cloudHeight = 512; + } + R_InitSkyTexCoords( shader.sky.cloudHeight ); + + + // innerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for ( i = 0 ; i < 6 ; i++ ) { + Com_sprintf( pathname, sizeof( pathname ), "%s_%s.tga" + , token, suf[i] ); + shader.sky.innerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_CLAMP ); + if ( !shader.sky.innerbox[i] ) { + shader.sky.innerbox[i] = tr.defaultImage; + } + } + } + + shader.isSky = qtrue; +} + + +/* +================= +ParseSort +================= +*/ +void ParseSort( char **text ) { + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name ); + return; + } + + if ( !Q_stricmp( token, "portal" ) ) { + shader.sort = SS_PORTAL; + } else if ( !Q_stricmp( token, "sky" ) ) { + shader.sort = SS_ENVIRONMENT; + } else if ( !Q_stricmp( token, "opaque" ) ) { + shader.sort = SS_OPAQUE; + } else if ( !Q_stricmp( token, "decal" ) ) { + shader.sort = SS_DECAL; + } else if ( !Q_stricmp( token, "seeThrough" ) ) { + shader.sort = SS_SEE_THROUGH; + } else if ( !Q_stricmp( token, "banner" ) ) { + shader.sort = SS_BANNER; + } else if ( !Q_stricmp( token, "additive" ) ) { + shader.sort = SS_BLEND1; + } else if ( !Q_stricmp( token, "nearest" ) ) { + shader.sort = SS_NEAREST; + } else if ( !Q_stricmp( token, "underwater" ) ) { + shader.sort = SS_UNDERWATER; + } else { + shader.sort = atof( token ); + } +} + + + +// this table is also present in q3map + +typedef struct { + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + +infoParm_t infoParms[] = { + // server relevant contents + +//----(SA) modified + {"clipmissile", 1, 0, CONTENTS_MISSILECLIP}, // impact only specific weapons (rl, gl) +//----(SA) end + + {"water", 1, 0, CONTENTS_WATER }, + {"slag", 1, 0, CONTENTS_SLIME }, // uses the CONTENTS_SLIME flag, but the shader reference is changed to 'slag' + // to idendify that this doesn't work the same as 'slime' did. + // (slime hurts instantly, slag doesn't) +// {"slime", 1, 0, CONTENTS_SLIME }, // mildly damaging + {"lava", 1, 0, CONTENTS_LAVA }, // very damaging + {"playerclip", 1, 0, CONTENTS_PLAYERCLIP }, + {"monsterclip", 1, 0, CONTENTS_MONSTERCLIP }, + {"nodrop", 1, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"nonsolid", 1, SURF_NONSOLID, 0}, // clears the solid flag + + // utility relevant attributes + {"origin", 1, 0, CONTENTS_ORIGIN }, // center of rotating brushes + {"trans", 0, 0, CONTENTS_TRANSLUCENT }, // don't eat contained surfaces + {"detail", 0, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"structural", 0, 0, CONTENTS_STRUCTURAL }, // force into structural bsp even if trnas + {"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas + {"clusterportal", 1,0, CONTENTS_CLUSTERPORTAL }, // for bots + {"donotenter", 1, 0, CONTENTS_DONOTENTER }, // for bots + + // Rafael - nopass + {"donotenterlarge", 1, 0, CONTENTS_DONOTENTER_LARGE }, // for larger bots + + {"fog", 1, 0, CONTENTS_FOG}, // carves surfaces entering + {"sky", 0, SURF_SKY, 0 }, // emit light from an environment map + {"lightfilter", 0, SURF_LIGHTFILTER, 0 }, // filter light going through it + {"alphashadow", 0, SURF_ALPHASHADOW, 0 }, // test light on a per-pixel basis + {"hint", 0, SURF_HINT, 0 }, // use as a primary splitter + + // server attributes + {"slick", 0, SURF_SLICK, 0 }, + {"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks + {"nomarks", 0, SURF_NOMARKS, 0 }, // don't make impact marks, but still explode + {"ladder", 0, SURF_LADDER, 0 }, + {"nodamage", 0, SURF_NODAMAGE, 0 }, + + {"monsterslick", 0, SURF_MONSTERSLICK, 0}, // surf only slick for monsters + +// {"flesh", 0, SURF_FLESH, 0 }, + {"glass", 0, SURF_GLASS, 0 }, //----(SA) added + {"ceramic", 0, SURF_CERAMIC, 0 }, //----(SA) added + + // steps + {"metal", 0, SURF_METAL, 0 }, + {"metalsteps", 0, SURF_METAL, 0 }, // retain bw compatibility with Q3A metal shaders... (SA) + {"nosteps", 0, SURF_NOSTEPS, 0 }, + {"woodsteps", 0, SURF_WOOD, 0 }, + {"grasssteps", 0, SURF_GRASS, 0 }, + {"gravelsteps", 0, SURF_GRAVEL, 0 }, + {"carpetsteps", 0, SURF_CARPET, 0 }, + {"snowsteps", 0, SURF_SNOW, 0 }, + {"roofsteps", 0, SURF_ROOF, 0 }, // tile roof + + {"rubble", 0, SURF_RUBBLE, 0 }, + + // drawsurf attributes + {"nodraw", 0, SURF_NODRAW, 0 }, // don't generate a drawsurface (or a lightmap) + {"pointlight", 0, SURF_POINTLIGHT, 0 }, // sample lighting at vertexes + {"nolightmap", 0, SURF_NOLIGHTMAP,0 }, // don't generate a lightmap + {"nodlight", 0, SURF_NODLIGHT, 0 }, // don't ever add dynamic lights + + {"monsterslicknorth", 0, SURF_MONSLICK_N,0}, + {"monsterslickeast", 0, SURF_MONSLICK_E,0}, + {"monsterslicksouth", 0, SURF_MONSLICK_S,0}, + {"monsterslickwest", 0, SURF_MONSLICK_W,0} + +}; + + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ +static void ParseSurfaceParm( char **text ) { + char *token; + int numInfoParms = sizeof( infoParms ) / sizeof( infoParms[0] ); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numInfoParms ; i++ ) { + if ( !Q_stricmp( token, infoParms[i].name ) ) { + shader.surfaceFlags |= infoParms[i].surfaceFlags; + shader.contentFlags |= infoParms[i].contents; +#if 0 + if ( infoParms[i].clearSolid ) { + si->contents &= ~CONTENTS_SOLID; + } +#endif + break; + } + } +} + +/* +================= +ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. Later functions +will optimize it. +================= +*/ +static qboolean ParseShader( char **text ) { + char *token; + int s; + + s = 0; + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) { + ri.Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name ); + return qfalse; + } + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name ); + return qfalse; + } + + // end of shader definition + if ( token[0] == '}' ) { + break; + } + // stage definition + else if ( token[0] == '{' ) { + if ( !ParseStage( &stages[s], text ) ) { + return qfalse; + } + stages[s].active = qtrue; + s++; + continue; + } + // skip stuff that only the QuakeEdRadient needs + else if ( !Q_stricmpn( token, "qer", 3 ) ) { + SkipRestOfLine( text ); + continue; + } + // sun parms + else if ( !Q_stricmp( token, "q3map_sun" ) ) { + float a, b; + + token = COM_ParseExt( text, qfalse ); + tr.sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[2] = atof( token ); + + VectorNormalize( tr.sunLight ); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + VectorScale( tr.sunLight, a, tr.sunLight ); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + a = a / 180 * M_PI; + + token = COM_ParseExt( text, qfalse ); + b = atof( token ); + b = b / 180 * M_PI; + + tr.sunDirection[0] = cos( a ) * cos( b ); + tr.sunDirection[1] = sin( a ) * cos( b ); + tr.sunDirection[2] = sin( b ); + } else if ( !Q_stricmp( token, "deformVertexes" ) ) { + ParseDeform( text ); + continue; + } else if ( !Q_stricmp( token, "tesssize" ) ) { + SkipRestOfLine( text ); + continue; + } else if ( !Q_stricmp( token, "clampTime" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] ) { + shader.clampTime = atof( token ); + } + } + // skip stuff that only the q3map needs + else if ( !Q_stricmpn( token, "q3map", 5 ) ) { + SkipRestOfLine( text ); + continue; + } + // skip stuff that only q3map or the server needs + else if ( !Q_stricmp( token, "surfaceParm" ) ) { + ParseSurfaceParm( text ); + continue; + } + // no mip maps + else if ( ( !Q_stricmp( token, "nomipmaps" ) ) || ( !Q_stricmp( token,"nomipmap" ) ) ) { + shader.noMipMaps = qtrue; + shader.noPicMip = qtrue; + continue; + } + // no picmip adjustment + else if ( !Q_stricmp( token, "nopicmip" ) ) { + shader.noPicMip = qtrue; + continue; + } + // polygonOffset + else if ( !Q_stricmp( token, "polygonOffset" ) ) { + shader.polygonOffset = qtrue; + continue; + } + // entityMergable, allowing sprite surfaces from multiple entities + // to be merged into one batch. This is a savings for smoke + // puffs and blood, but can't be used for anything where the + // shader calcs (not the surface function) reference the entity color or scroll + else if ( !Q_stricmp( token, "entityMergable" ) ) { + shader.entityMergable = qtrue; + continue; + } + // fogParms + else if ( !Q_stricmp( token, "fogParms" ) ) { + if ( !ParseVector( text, 3, shader.fogParms.color ) ) { + return qfalse; + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name ); + continue; + } + shader.fogParms.depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( text ); + continue; + } + // portal + else if ( !Q_stricmp( token, "portal" ) ) { + shader.sort = SS_PORTAL; + continue; + } + // skyparms + else if ( !Q_stricmp( token, "skyparms" ) ) { + ParseSkyParms( text ); + continue; + } + // This is fixed fog for the skybox/clouds determined solely by the shader + // it will not change in a level and will not be necessary + // to force clients to use a sky fog the server says to. + // skyfogvars <(r,g,b)> + else if ( !Q_stricmp( token, "skyfogvars" ) ) { + vec3_t fogColor; + + if ( !ParseVector( text, 3, fogColor ) ) { + return qfalse; + } + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing density value for sky fog\n" ); + continue; + } + + if ( atof( token ) > 1 ) { + ri.Printf( PRINT_WARNING, "WARNING: last value for skyfogvars is 'density' which needs to be 0.0-1.0\n" ); + continue; + } + + R_SetFog( FOG_SKY, 0, 5, fogColor[0], fogColor[1], fogColor[2], atof( token ) ); + continue; + } else if ( !Q_stricmp( token, "sunshader" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing shader name for 'sunshader'\n" ); + continue; + } + tr.sunShaderName = CopyString( token ); + } +//----(SA) added + else if ( !Q_stricmp( token, "lightgridmulamb" ) ) { // ambient multiplier for lightgrid + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing value for 'lightgrid ambient multiplier'\n" ); + continue; + } + if ( atof( token ) > 0 ) { + tr.lightGridMulAmbient = atof( token ); + } + } else if ( !Q_stricmp( token, "lightgridmuldir" ) ) { // directional multiplier for lightgrid + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing value for 'lightgrid directional multiplier'\n" ); + continue; + } + if ( atof( token ) > 0 ) { + tr.lightGridMulDirected = atof( token ); + } + } +//----(SA) end + else if ( !Q_stricmp( token, "waterfogvars" ) ) { + vec3_t watercolor; + float fogvar; + + if ( !ParseVector( text, 3, watercolor ) ) { + return qfalse; + } + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing density/distance value for water fog\n" ); + continue; + } + + fogvar = atof( token ); + + //----(SA) right now allow one water color per map. I'm sure this will need + // to change at some point, but I'm not sure how to track fog parameters + // on a "per-water volume" basis yet. + + if ( fogvar == 0 ) { // '0' specifies "use the map values for everything except the fog color + // TODO + } else if ( fogvar > 1 ) { // distance "linear" fog + R_SetFog( FOG_WATER, 0, fogvar, watercolor[0], watercolor[1], watercolor[2], 1.1 ); + } else { // density "exp" fog + R_SetFog( FOG_WATER, 0, 5, watercolor[0], watercolor[1], watercolor[2], fogvar ); + } + + continue; + } + // fogvars + else if ( !Q_stricmp( token, "fogvars" ) ) { + vec3_t fogColor; + float fogDensity; + int fogFar; + + if ( !ParseVector( text, 3, fogColor ) ) { + return qfalse; + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing density value for the fog\n" ); + continue; + } + + + //----(SA) NOTE: fogFar > 1 means the shader is setting the farclip, < 1 means setting + // density (so old maps or maps that just need softening fog don't have to care about farclip) + + fogDensity = atof( token ); + if ( fogDensity > 1 ) { // linear + fogFar = fogDensity; + } else { + fogFar = 5; + } + + R_SetFog( FOG_MAP, 0, fogFar, fogColor[0], fogColor[1], fogColor[2], fogDensity ); + R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP, 50, 0, 0, 0, 0 ); + + continue; + } + // done. + // Ridah, allow disable fog for some shaders + else if ( !Q_stricmp( token, "nofog" ) ) { + shader.noFog = qtrue; + continue; + } + // done. + // RF, allow each shader to permit compression if available + else if ( !Q_stricmp( token, "allowcompress" ) ) { + tr.allowCompress = qtrue; + continue; + } else if ( !Q_stricmp( token, "nocompress" ) ) { + tr.allowCompress = -1; + continue; + } + // done. + // light determines flaring in q3map, not needed here + else if ( !Q_stricmp( token, "light" ) ) { + token = COM_ParseExt( text, qfalse ); + continue; + } + // cull + else if ( !Q_stricmp( token, "cull" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "twosided" ) || !Q_stricmp( token, "disable" ) ) { + shader.cullType = CT_TWO_SIDED; + } else if ( !Q_stricmp( token, "back" ) || !Q_stricmp( token, "backside" ) || !Q_stricmp( token, "backsided" ) ) { + shader.cullType = CT_BACK_SIDED; + } else + { + ri.Printf( PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name ); + } + continue; + } + // sort + else if ( !Q_stricmp( token, "sort" ) ) { + ParseSort( text ); + continue; + } else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // ignore shaders that don't have any stages, unless it is a sky or fog + // + if ( s == 0 && !shader.isSky && !( shader.contentFlags & CONTENTS_FOG ) ) { + return qfalse; + } + + shader.explicitlyDefined = qtrue; + + return qtrue; +} + +/* +======================================================================================== + +SHADER OPTIMIZATION AND FOGGING + +======================================================================================== +*/ + +/* +=================== +ComputeStageIteratorFunc + +See if we can use on of the simple fastpath stage functions, +otherwise set to the generic stage function +=================== +*/ +static void ComputeStageIteratorFunc( void ) { + shader.optimalStageIteratorFunc = RB_StageIteratorGeneric; + + // + // see if this should go into the sky path + // + if ( shader.isSky ) { + shader.optimalStageIteratorFunc = RB_StageIteratorSky; + goto done; + } + + if ( r_ignoreFastPath->integer ) { + return; + } + + // + // see if this can go into the vertex lit fast path + // + if ( shader.numUnfoggedPasses == 1 ) { + if ( stages[0].rgbGen == CGEN_LIGHTING_DIFFUSE ) { + if ( stages[0].alphaGen == AGEN_IDENTITY ) { + if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE ) { + if ( !shader.polygonOffset ) { + if ( !shader.multitextureEnv ) { + if ( !shader.numDeforms ) { + shader.optimalStageIteratorFunc = RB_StageIteratorVertexLitTexture; + goto done; + } + } + } + } + } + } + } + + // + // see if this can go into an optimized LM, multitextured path + // + if ( shader.numUnfoggedPasses == 1 ) { + if ( ( stages[0].rgbGen == CGEN_IDENTITY ) && ( stages[0].alphaGen == AGEN_IDENTITY ) ) { + if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE && + stages[0].bundle[1].tcGen == TCGEN_LIGHTMAP ) { + if ( !shader.polygonOffset ) { + if ( !shader.numDeforms ) { + if ( shader.multitextureEnv ) { + shader.optimalStageIteratorFunc = RB_StageIteratorLightmappedMultitexture; + goto done; + } + } + } + } + } + } + +done: + return; +} + +typedef struct { + int blendA; + int blendB; + + int multitextureEnv; + int multitextureBlend; +} collapse_t; + +static collapse_t collapse[] = { + { 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, 0 }, + + { 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, 0 }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, 0 }, + + { GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE }, +#if 0 + { 0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA, + GL_DECAL, 0 }, +#endif + { -1 } +}; + +/* +================ +CollapseMultitexture + +Attempt to combine two stages into a single multitexture stage +FIXME: I think modulated add + modulated add collapses incorrectly +================= +*/ +static qboolean CollapseMultitexture( void ) { + int abits, bbits; + int i; + textureBundle_t tmpBundle; + + if ( !qglActiveTextureARB ) { + return qfalse; + } + + // make sure both stages are active + if ( !stages[0].active || !stages[1].active ) { + return qfalse; + } + + // on voodoo2, don't combine different tmus + if ( glConfig.driverType == GLDRV_VOODOO ) { + if ( stages[0].bundle[0].image[0]->TMU == + stages[1].bundle[0].image[0]->TMU ) { + return qfalse; + } + } + + abits = stages[0].stateBits; + bbits = stages[1].stateBits; + + // make sure that both stages have identical state other than blend modes + if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) != + ( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) { + return qfalse; + } + + abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + // search for a valid multitexture blend function + for ( i = 0; collapse[i].blendA != -1 ; i++ ) { + if ( abits == collapse[i].blendA + && bbits == collapse[i].blendB ) { + break; + } + } + + // nothing found + if ( collapse[i].blendA == -1 ) { + return qfalse; + } + + // GL_ADD is a separate extension + if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) { + return qfalse; + } + + // make sure waveforms have identical parameters + if ( ( stages[0].rgbGen != stages[1].rgbGen ) || + ( stages[0].alphaGen != stages[1].alphaGen ) ) { + return qfalse; + } + + // an add collapse can only have identity colors + if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) { + return qfalse; + } + + if ( stages[0].rgbGen == CGEN_WAVEFORM ) { + if ( memcmp( &stages[0].rgbWave, + &stages[1].rgbWave, + sizeof( stages[0].rgbWave ) ) ) { + return qfalse; + } + } + if ( stages[0].alphaGen == CGEN_WAVEFORM ) { + if ( memcmp( &stages[0].alphaWave, + &stages[1].alphaWave, + sizeof( stages[0].alphaWave ) ) ) { + return qfalse; + } + } + + + // make sure that lightmaps are in bundle 1 for 3dfx + if ( stages[0].bundle[0].isLightmap ) { + tmpBundle = stages[0].bundle[0]; + stages[0].bundle[0] = stages[1].bundle[0]; + stages[0].bundle[1] = tmpBundle; + } else + { + stages[0].bundle[1] = stages[1].bundle[0]; + } + + // set the new blend state bits + shader.multitextureEnv = collapse[i].multitextureEnv; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= collapse[i].multitextureBlend; + + // + // move down subsequent shaders + // + memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) ); + memset( &stages[MAX_SHADER_STAGES - 1], 0, sizeof( stages[0] ) ); + + return qtrue; +} + +/* +============= +FixRenderCommandList + +Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated +but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces +to be rendered with bad shaders. To fix this, need to go through all render commands and fix +sortedIndex. +============== +*/ +static void FixRenderCommandList( int newShader ) { + renderCommandList_t *cmdList = &backEndData[tr.smpFrame]->commands; + + if ( cmdList ) { + const void *curCmd = cmdList->cmds; + + while ( 1 ) { + switch ( *(const int *)curCmd ) { + case RC_SET_COLOR: + { + const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd; + curCmd = (const void *)( sc_cmd + 1 ); + break; + } + case RC_STRETCH_PIC: + case RC_ROTATED_PIC: + case RC_STRETCH_PIC_GRADIENT: + { + const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd; + curCmd = (const void *)( sp_cmd + 1 ); + break; + } + case RC_DRAW_SURFS: + { + int i; + drawSurf_t *drawSurf; + shader_t *shader; + int fogNum; + int entityNum; + int dlightMap; + int sortedIndex; + const drawSurfsCommand_t *ds_cmd = (const drawSurfsCommand_t *)curCmd; + + for ( i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++ ) { + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap ); + sortedIndex = ( ( drawSurf->sort >> QSORT_SHADERNUM_SHIFT ) & ( MAX_SHADERS - 1 ) ); + if ( sortedIndex >= newShader ) { + sortedIndex++; + drawSurf->sort = ( sortedIndex << QSORT_SHADERNUM_SHIFT ) | entityNum | ( fogNum << QSORT_FOGNUM_SHIFT ) | (int)dlightMap; + } + } + curCmd = (const void *)( ds_cmd + 1 ); + break; + } + case RC_DRAW_BUFFER: + { + const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd; + curCmd = (const void *)( db_cmd + 1 ); + break; + } + case RC_SWAP_BUFFERS: + { + const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd; + curCmd = (const void *)( sb_cmd + 1 ); + break; + } + case RC_END_OF_LIST: + default: + return; + } + } + } +} + +/* +============== +SortNewShader + +Positions the most recently created shader in the tr.sortedShaders[] +array so that the shader->sort key is sorted reletive to the other +shaders. + +Sets shader->sortedIndex +============== +*/ +static void SortNewShader( void ) { + int i; + float sort; + shader_t *newShader; + + newShader = tr.shaders[ tr.numShaders - 1 ]; + sort = newShader->sort; + + for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) { + if ( tr.sortedShaders[ i ]->sort <= sort ) { + break; + } + tr.sortedShaders[i + 1] = tr.sortedShaders[i]; + tr.sortedShaders[i + 1]->sortedIndex++; + } + + // Arnout: fix rendercommandlist + FixRenderCommandList( i + 1 ); + + newShader->sortedIndex = i + 1; + tr.sortedShaders[i + 1] = newShader; +} + + +/* +==================== +GeneratePermanentShader +==================== +*/ +static shader_t *GeneratePermanentShader( void ) { + shader_t *newShader; + int i, b; + int size, hash; + + if ( tr.numShaders == MAX_SHADERS ) { + ri.Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n" ); + return tr.defaultShader; + } + + // Ridah, caching system + newShader = R_CacheShaderAlloc( sizeof( shader_t ) ); + + *newShader = shader; + + if ( shader.sort <= SS_OPAQUE ) { + newShader->fogPass = FP_EQUAL; + } else if ( shader.contentFlags & CONTENTS_FOG ) { + newShader->fogPass = FP_LE; + } + + tr.shaders[ tr.numShaders ] = newShader; + newShader->index = tr.numShaders; + + tr.sortedShaders[ tr.numShaders ] = newShader; + newShader->sortedIndex = tr.numShaders; + + tr.numShaders++; + + for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) { + if ( !stages[i].active ) { + newShader->stages[i] = NULL; // Ridah, make sure it's null + break; + } + // Ridah, caching system + newShader->stages[i] = R_CacheShaderAlloc( sizeof( stages[i] ) ); + + *newShader->stages[i] = stages[i]; + + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + if ( !newShader->stages[i]->bundle[b].numTexMods ) { + // make sure unalloc'd texMods aren't pointing to some random point in memory + newShader->stages[i]->bundle[b].texMods = NULL; + continue; + } + size = newShader->stages[i]->bundle[b].numTexMods * sizeof( texModInfo_t ); + // Ridah, caching system + newShader->stages[i]->bundle[b].texMods = R_CacheShaderAlloc( size ); + + memcpy( newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size ); + } + } + + SortNewShader(); + + hash = generateHashValue( newShader->name ); + newShader->next = hashTable[hash]; + hashTable[hash] = newShader; + + return newShader; +} + +/* +================= +VertexLightingCollapse + +If vertex lighting is enabled, only render a single +pass, trying to guess which is the correct one to best aproximate +what it is supposed to look like. +================= +*/ +static void VertexLightingCollapse( void ) { + int stage; + shaderStage_t *bestStage; + int bestImageRank; + int rank; + + // if we aren't opaque, just use the first pass + if ( shader.sort == SS_OPAQUE ) { + + // pick the best texture for the single pass + bestStage = &stages[0]; + bestImageRank = -999999; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + rank = 0; + + if ( pStage->bundle[0].isLightmap ) { + rank -= 100; + } + if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) { + rank -= 5; + } + if ( pStage->bundle[0].numTexMods ) { + rank -= 5; + } + if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) { + rank -= 3; + } + + if ( rank > bestImageRank ) { + bestImageRank = rank; + bestStage = pStage; + } + } + + stages[0].bundle[0] = bestStage->bundle[0]; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= GLS_DEPTHMASK_TRUE; + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + } else { + stages[0].rgbGen = CGEN_EXACT_VERTEX; + } + stages[0].alphaGen = AGEN_SKIP; + } else { + // don't use a lightmap (tesla coils) + if ( stages[0].bundle[0].isLightmap ) { + stages[0] = stages[1]; + } + + // if we were in a cross-fade cgen, hack it to normal + if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + } + + for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + memset( pStage, 0, sizeof( *pStage ) ); + } +} + +/* +========================= +FinishShader + +Returns a freshly allocated shader with all the needed info +from the current global working shader +========================= +*/ +static shader_t *FinishShader( void ) { + int stage, i; + qboolean hasLightmapStage; + qboolean vertexLightmap; + + hasLightmapStage = qfalse; + vertexLightmap = qfalse; + + // + // set sky stuff appropriate + // + if ( shader.isSky ) { + shader.sort = SS_ENVIRONMENT; + } + + // + // set polygon offset + // + if ( shader.polygonOffset && !shader.sort ) { + shader.sort = SS_DECAL; + } + + // + // set appropriate stage information + // + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + // check for a missing texture + if ( !pStage->bundle[0].image[0] ) { + ri.Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name ); + pStage->active = qfalse; + continue; + } + + // + // ditch this stage if it's detail and detail textures are disabled + // + if ( pStage->isDetail && !r_detailTextures->integer ) { + if ( stage < ( MAX_SHADER_STAGES - 1 ) ) { + memmove( pStage, pStage + 1, sizeof( *pStage ) * ( MAX_SHADER_STAGES - stage - 1 ) ); + // kill the last stage, since it's now a duplicate + for ( i = MAX_SHADER_STAGES - 1; i > stage; i-- ) { + if ( stages[i].active ) { + memset( &stages[i], 0, sizeof( *pStage ) ); + break; + } + } + stage--; // the next stage is now the current stage, so check it again + } else { + memset( pStage, 0, sizeof( *pStage ) ); + } + continue; + } + + // + // default texture coordinate generation + // + if ( pStage->bundle[0].isLightmap ) { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + hasLightmapStage = qtrue; + } else { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_TEXTURE; + } + } + + + // not a true lightmap but we want to leave existing + // behaviour in place and not print out a warning + //if (pStage->rgbGen == CGEN_VERTEX) { + // vertexLightmap = qtrue; + //} + + + + // + // determine sort order and fog color adjustment + // + if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) && + ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) { + int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS; + int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS; + + // fog color adjustment only works for blend modes that have a contribution + // that aproaches 0 as the modulate values aproach 0 -- + // GL_ONE, GL_ONE + // GL_ZERO, GL_ONE_MINUS_SRC_COLOR + // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA + + // modulate, additive + if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) || + ( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_RGB; + } + // strict blend + else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA; + } + // premultiplied alpha + else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_RGBA; + } else { + // we can't adjust this one correctly, so it won't be exactly correct in fog + } + + // don't screw with sort order if this is a portal or environment + if ( !shader.sort ) { + // see through item, like a grill or grate + if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) { + shader.sort = SS_SEE_THROUGH; + } else { + shader.sort = SS_BLEND0; + } + } + } + } + + // there are times when you will need to manually apply a sort to + // opaque alpha tested shaders that have later blend passes + if ( !shader.sort ) { + shader.sort = SS_OPAQUE; + } + + // + // if we are in r_vertexLight mode, never use a lightmap texture + // + // NERVE - SMF - temp fix, terrain is having problems with lighting collapse + if ( 0 && ( stage > 1 && ( ( r_vertexLight->integer && !r_uiFullScreen->integer ) || glConfig.hardwareType == GLHW_PERMEDIA2 ) ) ) { + VertexLightingCollapse(); + stage = 1; + hasLightmapStage = qfalse; + } + + // + // look for multitexture potential + // + if ( stage > 1 && CollapseMultitexture() ) { + stage--; + } + + if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) { + if ( vertexLightmap ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name ); + } else { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name ); + shader.lightmapIndex = LIGHTMAP_NONE; + } + } + + + // + // compute number of passes + // + shader.numUnfoggedPasses = stage; + + // fogonly shaders don't have any normal passes + if ( stage == 0 ) { + shader.sort = SS_FOG; + } + + // determine which stage iterator function is appropriate + ComputeStageIteratorFunc(); + + // RF default back to no compression for next shader + if ( r_ext_compressed_textures->integer == 2 ) { + tr.allowCompress = qfalse; + } + + return GeneratePermanentShader(); +} + +//======================================================================================== + +/* +==================== +FindShaderInShaderText + +Scans the combined text description of all the shader files for +the given shader name. + +return NULL if not found + +If found, it will return a valid shader +===================== +*/ +static char *FindShaderInShaderText( const char *shadername ) { + char *p = s_shaderText; + char *token; + + if ( !p ) { + return NULL; + } + + // Ridah, optimized shader loading + if ( r_cacheShaders->integer ) { + /*if (strstr( shadername, "/" ) && !strstr( shadername, "." ))*/ { + unsigned short int checksum; + shaderStringPointer_t *pShaderString; + + checksum = generateHashValue( shadername ); + + // if it's known, skip straight to it's position + pShaderString = &shaderChecksumLookup[checksum]; + while ( pShaderString && pShaderString->pStr ) { + p = pShaderString->pStr; + + token = COM_ParseExt( &p, qtrue ); + + if ( ( token[0] != 0 ) && !Q_stricmp( token, shadername ) ) { + return p; + } + + pShaderString = pShaderString->next; + } + + // it's not even in our list, so it mustn't exist + return NULL; + } + } + // done. + + // look for label + // note that this could get confused if a shader name is used inside + // another shader definition + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + if ( token[0] == '{' ) { + // skip the definition +// SkipBracedSection_Depth( &p, 1 ); + SkipBracedSection( &p ); + } else if ( !Q_stricmp( token, shadername ) ) { + return p; + } else { + // skip to end of line + SkipRestOfLine( &p ); + } + } + + return NULL; +} + +/* +================== +R_FindShaderByName + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. +================== +*/ +shader_t *R_FindShaderByName( const char *name ) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh; + + if ( ( name == NULL ) || ( name[0] == 0 ) ) { // bk001205 + return tr.defaultShader; + } + + COM_StripExtension2( name, strippedName, sizeof( strippedName ) ); + + hash = generateHashValue( strippedName ); + + // + // see if the shader is already loaded + // + for ( sh = hashTable[hash]; sh; sh = sh->next ) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( Q_stricmp( sh->name, strippedName ) == 0 ) { + // match found + return sh; + } + } + + return tr.defaultShader; +} + + +/* +=============== +R_FindShader + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. + +In the interest of not requiring an explicit shader text entry to +be defined for every single image used in the game, three default +shader behaviors can be auto-created for any image: + +If lightmapIndex == LIGHTMAP_NONE, then the image will have +dynamic diffuse lighting applied to it, as apropriate for most +entity skin surfaces. + +If lightmapIndex == LIGHTMAP_2D, then the image will be used +for 2D rendering unless an explicit shader is found + +If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use +the vertex rgba modulate values, as apropriate for misc_model +pre-lit surfaces. + +Other lightmapIndex values will have a lightmap stage created +and src*dest blending applied with the texture, as apropriate for +most world construction surfaces. + +=============== +*/ +shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ) { + char strippedName[MAX_QPATH]; + char fileName[MAX_QPATH]; + int i, hash; + char *shaderText; + image_t *image; + shader_t *sh; + + if ( name[0] == 0 ) { + return tr.defaultShader; + } + + // use (fullbright) vertex lighting if the bsp file doesn't have + // lightmaps + if ( lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_BY_VERTEX; + } + + COM_StripExtension2( name, strippedName, sizeof( strippedName ) ); + + hash = generateHashValue( strippedName ); + + // + // see if the shader is already loaded + // +#if 1 + for ( sh = hashTable[hash]; sh; sh = sh->next ) { + // index by name + + // Ridah, modified this so we don't keep trying to load an invalid lightmap shader +/* + if ( sh->lightmapIndex == lightmapIndex && + !Q_stricmp(sh->name, strippedName)) { + // match found + return sh; + } +*/ + if ( ( ( sh->lightmapIndex == lightmapIndex ) || ( sh->lightmapIndex < 0 && lightmapIndex >= 0 ) ) && + !Q_stricmp( sh->name, strippedName ) ) { + // match found + return sh; + } + } +#else + for ( sh = hashTable[hash]; sh; sh = sh->next ) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( ( sh->lightmapIndex == lightmapIndex || sh->defaultShader ) && + !Q_stricmp( sh->name, strippedName ) ) { + // match found + return sh; + } + } +#endif + + // make sure the render thread is stopped, because we are probably + // going to have to upload an image + if ( r_smp->integer ) { + R_SyncRenderThread(); + } + + // Ridah, check the cache + // assignment used as truth value + if ( ( sh = R_FindCachedShader( strippedName, lightmapIndex, hash ) ) ) { + return sh; + } + // done. + + // clear the global shader + memset( &shader, 0, sizeof( shader ) ); + memset( &stages, 0, sizeof( stages ) ); + Q_strncpyz( shader.name, strippedName, sizeof( shader.name ) ); + shader.lightmapIndex = lightmapIndex; + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + + // FIXME: set these "need" values apropriately + shader.needsNormal = qtrue; + shader.needsST1 = qtrue; + shader.needsST2 = qtrue; + shader.needsColor = qtrue; + + // + // attempt to define shader from an explicit parameter file + // + shaderText = FindShaderInShaderText( strippedName ); + if ( shaderText ) { + // enable this when building a pak file to get a global list + // of all explicit shaders + if ( r_printShaders->integer ) { + ri.Printf( PRINT_ALL, "*SHADER* %s\n", name ); + } + + if ( !ParseShader( &shaderText ) ) { + // had errors, so use default shader + shader.defaultShader = qtrue; + } + sh = FinishShader(); + return sh; + } + + + // + // if not defined in the in-memory shader descriptions, + // look for a single TGA, BMP, or PCX + // + Q_strncpyz( fileName, name, sizeof( fileName ) ); + COM_DefaultExtension( fileName, sizeof( fileName ), ".tga" ); + image = R_FindImageFile( fileName, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP ); + if ( !image ) { + ri.Printf( PRINT_DEVELOPER, "Couldn't find image for shader %s\n", name ); + shader.defaultShader = qtrue; + return FinishShader(); + } + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = qtrue; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + return FinishShader(); +} + + +qhandle_t RE_RegisterShaderFromImage( const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage ) { + int i, hash; + shader_t *sh; + + hash = generateHashValue( name ); + + // + // see if the shader is already loaded + // + for ( sh = hashTable[hash]; sh; sh = sh->next ) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( ( sh->lightmapIndex == lightmapIndex || sh->defaultShader ) && + // index by name + !Q_stricmp( sh->name, name ) ) { + // match found + return sh->index; + } + } + + // make sure the render thread is stopped, because we are probably + // going to have to upload an image + if ( r_smp->integer ) { + R_SyncRenderThread(); + } + + // clear the global shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + Q_strncpyz( shader.name, name, sizeof( shader.name ) ); + shader.lightmapIndex = lightmapIndex; + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + + // FIXME: set these "need" values apropriately + shader.needsNormal = qtrue; + shader.needsST1 = qtrue; + shader.needsST2 = qtrue; + shader.needsColor = qtrue; + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = qtrue; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + sh = FinishShader(); + return sh->index; +} + + +/* +==================== +RE_RegisterShaderLightMap + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmapIndex, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShader( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShaderNoMip + +For menu graphics that should never be picmiped +==================== +*/ +qhandle_t RE_RegisterShaderNoMip( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, qfalse ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +R_GetShaderByHandle + +When a handle is passed in by another module, this range checks +it and returns a valid (possibly default) shader_t to be used internally. +==================== +*/ +shader_t *R_GetShaderByHandle( qhandle_t hShader ) { + if ( hShader < 0 ) { + ri.Printf( PRINT_DEVELOPER, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); // bk: FIXME name + return tr.defaultShader; + } + if ( hShader >= tr.numShaders ) { + ri.Printf( PRINT_DEVELOPER, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + return tr.shaders[hShader]; +} + +/* +=============== +R_ShaderList_f + +Dump information on all valid shaders to the console +A second parameter will cause it to print in sorted order +=============== +*/ +void R_ShaderList_f( void ) { + int i; + int count; + shader_t *shader; + + ri.Printf( PRINT_ALL, "-----------------------\n" ); + + count = 0; + for ( i = 0 ; i < tr.numShaders ; i++ ) { + if ( ri.Cmd_Argc() > 1 ) { + shader = tr.sortedShaders[i]; + } else { + shader = tr.shaders[i]; + } + + ri.Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses ); + + if ( shader->lightmapIndex >= 0 ) { + ri.Printf( PRINT_ALL, "L " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + if ( shader->multitextureEnv == GL_ADD ) { + ri.Printf( PRINT_ALL, "MT(a) " ); + } else if ( shader->multitextureEnv == GL_MODULATE ) { + ri.Printf( PRINT_ALL, "MT(m) " ); + } else if ( shader->multitextureEnv == GL_DECAL ) { + ri.Printf( PRINT_ALL, "MT(d) " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + if ( shader->explicitlyDefined ) { + ri.Printf( PRINT_ALL, "E " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->optimalStageIteratorFunc == RB_StageIteratorGeneric ) { + ri.Printf( PRINT_ALL, "gen " ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorSky ) { + ri.Printf( PRINT_ALL, "sky " ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorLightmappedMultitexture ) { + ri.Printf( PRINT_ALL, "lmmt" ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorVertexLitTexture ) { + ri.Printf( PRINT_ALL, "vlt " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->defaultShader ) { + ri.Printf( PRINT_ALL, ": %s (DEFAULTED)\n", shader->name ); + } else { + ri.Printf( PRINT_ALL, ": %s\n", shader->name ); + } + count++; + } + ri.Printf( PRINT_ALL, "%i total shaders\n", count ); + ri.Printf( PRINT_ALL, "------------------\n" ); +} + +// Ridah, optimized shader loading + +#define MAX_SHADER_STRING_POINTERS 100000 +shaderStringPointer_t shaderStringPointerList[MAX_SHADER_STRING_POINTERS]; + +/* +==================== +BuildShaderChecksumLookup +==================== +*/ +static void BuildShaderChecksumLookup( void ) { + char *p = s_shaderText, *pOld; + char *token; + unsigned short int checksum; + int numShaderStringPointers = 0; + + // initialize the checksums + memset( shaderChecksumLookup, 0, sizeof( shaderChecksumLookup ) ); + + if ( !p ) { + return; + } + + // loop for all labels + while ( 1 ) { + + pOld = p; + + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + if ( !Q_stricmp( token, "{" ) ) { + // skip braced section +// SkipBracedSection_Depth( &p, 1 ); + SkipBracedSection( &p ); + continue; + } + + // get it's checksum + checksum = generateHashValue( token ); + + // if it's not currently used + if ( !shaderChecksumLookup[checksum].pStr ) { + shaderChecksumLookup[checksum].pStr = pOld; + } else { + // create a new list item + shaderStringPointer_t *newStrPtr; + + if ( numShaderStringPointers >= MAX_SHADER_STRING_POINTERS ) { + ri.Error( ERR_DROP, "MAX_SHADER_STRING_POINTERS exceeded, too many shaders" ); + } + + newStrPtr = &shaderStringPointerList[numShaderStringPointers++]; //ri.Hunk_Alloc( sizeof( shaderStringPointer_t ), h_low ); + newStrPtr->pStr = pOld; + newStrPtr->next = shaderChecksumLookup[checksum].next; + shaderChecksumLookup[checksum].next = newStrPtr; + } + } +} +// done. + + +/* +==================== +ScanAndLoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names +===================== +*/ +#define MAX_SHADER_FILES 4096 +static void ScanAndLoadShaderFiles( void ) { + char **shaderFiles; + char *buffers[MAX_SHADER_FILES]; + char *p; + int numShaders; + int i; + + long sum = 0; + // scan for shader files + shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaders ); + + if ( !shaderFiles || !numShaders ) { + ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" ); + return; + } + + if ( numShaders > MAX_SHADER_FILES ) { + numShaders = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaders; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); + ri.Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename ); // JPW NERVE was PRINT_ALL + sum += ri.FS_ReadFile( filename, (void **)&buffers[i] ); + if ( !buffers[i] ) { + ri.Error( ERR_DROP, "Couldn't load %s", filename ); + } + } + + // build single large buffer + s_shaderText = ri.Hunk_Alloc( sum + numShaders * 2, h_low ); + + // free in reverse order, so the temp files are all dumped + for ( i = numShaders - 1; i >= 0 ; i-- ) { + strcat( s_shaderText, "\n" ); + p = &s_shaderText[strlen( s_shaderText )]; + strcat( s_shaderText, buffers[i] ); + ri.FS_FreeFile( buffers[i] ); + buffers[i] = p; +// COM_Compress(p); + } + + // free up memory + ri.FS_FreeFileList( shaderFiles ); + + // Ridah, optimized shader loading (18ms on a P3-500 for sfm1.bsp) + if ( r_cacheShaders->integer ) { + BuildShaderChecksumLookup(); + } + // done. +} + + +/* +==================== +CreateInternalShaders +==================== +*/ +static void CreateInternalShaders( void ) { + tr.numShaders = 0; + + // init the default shader + memset( &shader, 0, sizeof( shader ) ); + memset( &stages, 0, sizeof( stages ) ); + + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + + shader.lightmapIndex = LIGHTMAP_NONE; + stages[0].bundle[0].image[0] = tr.defaultImage; + stages[0].active = qtrue; + stages[0].stateBits = GLS_DEFAULT; + tr.defaultShader = FinishShader(); + + // shadow shader is just a marker + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + shader.sort = SS_STENCIL_SHADOW; + tr.shadowShader = FinishShader(); +} + +static void CreateExternalShaders( void ) { + tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, qtrue ); + tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, qtrue ); +// tr.sunShader = R_FindShader( "sun", LIGHTMAP_NONE, qtrue ); //----(SA) let sky shader set this + tr.sunflareShader[0] = R_FindShader( "sunflare1", LIGHTMAP_NONE, qtrue ); + tr.dlightShader = R_FindShader( "dlightshader", LIGHTMAP_NONE, qtrue ); +} + +//============================================================================= +// Ridah, shader caching +static int numBackupShaders = 0; +static shader_t *backupShaders[MAX_SHADERS]; +static shader_t *backupHashTable[FILE_HASH_SIZE]; + +/* +=============== +R_CacheShaderAlloc +=============== +*/ +void *R_CacheShaderAlloc( int size ) { + if ( r_cache->integer && r_cacheShaders->integer ) { + //return malloc( size ); + return ri.Z_Malloc( size ); + } else { + return ri.Hunk_Alloc( size, h_low ); + } +} + +/* +=============== +R_CacheShaderFree +=============== +*/ +void R_CacheShaderFree( void *ptr ) { + if ( r_cache->integer && r_cacheShaders->integer ) { + //free( ptr ); + ri.Free( ptr ); + } +} + +/* +=============== +R_PurgeShaders +=============== +*/ +void R_PurgeShaders( int count ) { + int i, j, c, b; + shader_t **sh; + static int lastPurged = 0; + + if ( !numBackupShaders ) { + lastPurged = 0; + return; + } + + // find the first shader still in memory + c = 0; + sh = (shader_t **)&backupShaders; + for ( i = lastPurged; i < numBackupShaders; i++, sh++ ) { + if ( *sh ) { + // free all memory associated with this shader + for ( j = 0 ; j < ( *sh )->numUnfoggedPasses ; j++ ) { + if ( !( *sh )->stages[j] ) { + break; + } + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + if ( ( *sh )->stages[j]->bundle[b].texMods ) { + R_CacheShaderFree( ( *sh )->stages[j]->bundle[b].texMods ); + } + } + R_CacheShaderFree( ( *sh )->stages[j] ); + } + R_CacheShaderFree( *sh ); + *sh = NULL; + + if ( ++c >= count ) { + lastPurged = i; + return; + } + } + } + lastPurged = 0; + numBackupShaders = 0; +} + +/* +=============== +R_BackupShaders +=============== +*/ +void R_BackupShaders( void ) { + + if ( !r_cache->integer ) { + return; + } + if ( !r_cacheShaders->integer ) { + return; + } + + // copy each model in memory across to the backupModels + memcpy( backupShaders, tr.shaders, sizeof( backupShaders ) ); + // now backup the hashTable + memcpy( backupHashTable, hashTable, sizeof( hashTable ) ); + + numBackupShaders = tr.numShaders; +} + +/* +================= +R_RegisterShaderImages + + Make sure all images that belong to this shader remain valid +================= +*/ +static qboolean R_RegisterShaderImages( shader_t *sh ) { + int i,j,b; + + if ( sh->isSky ) { + return qfalse; + } + + for ( i = 0; i < sh->numUnfoggedPasses; i++ ) { + if ( sh->stages[i] && sh->stages[i]->active ) { + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + for ( j = 0; sh->stages[i]->bundle[b].image[j] && j < MAX_IMAGE_ANIMATIONS; j++ ) { + if ( !R_TouchImage( sh->stages[i]->bundle[b].image[j] ) ) { + return qfalse; + } + } + } + } + } + return qtrue; +} + +/* +=============== +R_FindCachedShader + + look for the given shader in the list of backupShaders +=============== +*/ +shader_t *R_FindCachedShader( const char *name, int lightmapIndex, int hash ) { + shader_t *sh, *shPrev; + + if ( !r_cacheShaders->integer ) { + return NULL; + } + + if ( !numBackupShaders ) { + return NULL; + } + + if ( !name ) { + return NULL; + } + + sh = backupHashTable[hash]; + shPrev = NULL; + while ( sh ) { + if ( sh->lightmapIndex == lightmapIndex && !Q_stricmp( sh->name, name ) ) { + + // make sure the images stay valid + if ( !R_RegisterShaderImages( sh ) ) { + return NULL; + } + + // this is the one, so move this shader into the current list + + if ( !shPrev ) { + backupHashTable[hash] = sh->next; + } else { + shPrev->next = sh->next; + } + + sh->next = hashTable[hash]; + hashTable[hash] = sh; + + backupShaders[sh->index] = NULL; // make sure we don't try and free it + + // set the index up, and add it to the current list + tr.shaders[ tr.numShaders ] = sh; + sh->index = tr.numShaders; + + tr.sortedShaders[ tr.numShaders ] = sh; + sh->sortedIndex = tr.numShaders; + + tr.numShaders++; + + SortNewShader(); // make sure it renders in the right order + + return sh; + } + + shPrev = sh; + sh = sh->next; + } + + return NULL; +} + +/* +=============== +R_LoadCacheShaders +=============== +*/ +void R_LoadCacheShaders( void ) { + int len; + byte *buf; + char *token, *pString; + char name[MAX_QPATH]; + + if ( !r_cacheShaders->integer ) { + return; + } + + // don't load the cache list in between level loads, only on startup, or after a vid_restart + if ( numBackupShaders > 0 ) { + return; + } + + len = ri.FS_ReadFile( "shader.cache", NULL ); + + if ( len <= 0 ) { + return; + } + + buf = (byte *)ri.Hunk_AllocateTempMemory( len ); + ri.FS_ReadFile( "shader.cache", (void **)&buf ); + pString = buf; + + while ( ( token = COM_ParseExt( &pString, qtrue ) ) && token[0] ) { + Q_strncpyz( name, token, sizeof( name ) ); + RE_RegisterModel( name ); + } + + ri.Hunk_FreeTempMemory( buf ); +} +// done. +//============================================================================= + +/* +================== +R_InitShaders +================== +*/ +void R_InitShaders( void ) { + + glfogNum = FOG_NONE; + + ri.Printf( PRINT_ALL, "Initializing Shaders\n" ); + + memset( hashTable, 0, sizeof( hashTable ) ); + deferLoad = qfalse; + + CreateInternalShaders(); + + ScanAndLoadShaderFiles(); + + CreateExternalShaders(); + + // Ridah + R_LoadCacheShaders(); +} diff --git a/src/renderer/tr_shadows.c b/src/renderer/tr_shadows.c new file mode 100644 index 0000000..599b706 --- /dev/null +++ b/src/renderer/tr_shadows.c @@ -0,0 +1,348 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "tr_local.h" + + +/* + + for a projection shadow: + + point[x] += light vector * ( z - shadow plane ) + point[y] += + point[z] = shadow plane + + 1 0 light[x] / light[z] + +*/ + +typedef struct { + int i2; + int facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 32 + +static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; +static int numEdgeDefs[SHADER_MAX_VERTEXES]; +static int facing[SHADER_MAX_INDEXES / 3]; + +void R_AddEdgeDef( int i1, int i2, int facing ) { + int c; + + c = numEdgeDefs[ i1 ]; + if ( c == MAX_EDGE_DEFS ) { + return; // overflow + } + edgeDefs[ i1 ][ c ].i2 = i2; + edgeDefs[ i1 ][ c ].facing = facing; + + numEdgeDefs[ i1 ]++; +} + +void R_RenderShadowEdges( void ) { + int i; + +#if 0 + int numTris; + + // dumb way -- render every triangle's edges + numTris = tess.numIndexes / 3; + + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + + if ( !facing[i] ) { + continue; + } + + i1 = tess.indexes[ i * 3 + 0 ]; + i2 = tess.indexes[ i * 3 + 1 ]; + i3 = tess.indexes[ i * 3 + 2 ]; + + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i3 ] ); + qglVertex3fv( tess.xyz[ i3 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] ); + qglEnd(); + } +#else + int c, c2; + int j, k; + int i2; + int c_edges, c_rejected; + int hit[2]; + + // an edge is NOT a silhouette edge if its face doesn't face the light, + // or if it has a reverse paired edge that also faces the light. + // A well behaved polyhedron would have exactly two faces for each edge, + // but lots of models have dangling edges or overfanned edges + c_edges = 0; + c_rejected = 0; + + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + c = numEdgeDefs[ i ]; + for ( j = 0 ; j < c ; j++ ) { + if ( !edgeDefs[ i ][ j ].facing ) { + continue; + } + + hit[0] = 0; + hit[1] = 0; + + i2 = edgeDefs[ i ][ j ].i2; + c2 = numEdgeDefs[ i2 ]; + for ( k = 0 ; k < c2 ; k++ ) { + if ( edgeDefs[ i2 ][ k ].i2 == i ) { + hit[ edgeDefs[ i2 ][ k ].facing ]++; + } + } + + // if it doesn't share the edge with another front facing + // triangle, it is a sil edge + if ( hit[ 1 ] == 0 ) { + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( tess.xyz[ i + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglEnd(); + c_edges++; + } else { + c_rejected++; + } + } + } +#endif +} + +/* +================= +RB_ShadowTessEnd + +triangleFromEdge[ v1 ][ v2 ] + + + set triangle from edge( v1, v2, tri ) + if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) { + } +================= +*/ +void RB_ShadowTessEnd( void ) { + int i; + int numTris; + vec3_t lightDir; + + // we can only do this if we have enough space in the vertex buffers + if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) { + return; + } + + if ( glConfig.stencilBits < 4 ) { + return; + } + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i + tess.numVertexes] ); + } + + // decide which triangles face the light + memset( numEdgeDefs, 0, 4 * tess.numVertexes ); + + numTris = tess.numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + vec3_t d1, d2, normal; + float *v1, *v2, *v3; + float d; + + i1 = tess.indexes[ i * 3 + 0 ]; + i2 = tess.indexes[ i * 3 + 1 ]; + i3 = tess.indexes[ i * 3 + 2 ]; + + v1 = tess.xyz[ i1 ]; + v2 = tess.xyz[ i2 ]; + v3 = tess.xyz[ i3 ]; + + VectorSubtract( v2, v1, d1 ); + VectorSubtract( v3, v1, d2 ); + CrossProduct( d1, d2, normal ); + + d = DotProduct( normal, lightDir ); + if ( d > 0 ) { + facing[ i ] = 1; + } else { + facing[ i ] = 0; + } + + // create the edges + R_AddEdgeDef( i1, i2, facing[ i ] ); + R_AddEdgeDef( i2, i3, facing[ i ] ); + R_AddEdgeDef( i3, i1, facing[ i ] ); + } + + // draw the silhouette edges + + GL_Bind( tr.whiteImage ); + qglEnable( GL_CULL_FACE ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + qglColor3f( 0.2f, 0.2f, 0.2f ); + + // don't write to the color buffer + qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + // mirrors have the culling order reversed + if ( backEnd.viewParms.isMirror ) { + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } else { + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } + + + // reenable writing to the color buffer + qglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); +} + + +/* +================= +RB_ShadowFinish + +Darken everything that is is a shadow volume. +We have to delay this until everything has been shadowed, +because otherwise shadows from different body parts would +overlap and double darken. +================= +*/ +void RB_ShadowFinish( void ) { + if ( r_shadows->integer != 2 ) { + return; + } + if ( glConfig.stencilBits < 4 ) { + return; + } + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_NOTEQUAL, 0, 255 ); + + qglDisable( GL_CLIP_PLANE0 ); + qglDisable( GL_CULL_FACE ); + + GL_Bind( tr.whiteImage ); + + qglLoadIdentity(); + + qglColor3f( 0.6f, 0.6f, 0.6f ); + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); + +// qglColor3f( 1, 0, 0 ); +// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + + qglBegin( GL_QUADS ); + qglVertex3f( -100, 100, -10 ); + qglVertex3f( 100, 100, -10 ); + qglVertex3f( 100, -100, -10 ); + qglVertex3f( -100, -100, -10 ); + qglEnd(); + + qglColor4f( 1,1,1,1 ); + qglDisable( GL_STENCIL_TEST ); +} + + +/* +================= +RB_ProjectionShadowDeform + +================= +*/ +void RB_ProjectionShadowDeform( void ) { + float *xyz; + int i; + float h; + vec3_t ground; + vec3_t light; + float groundDist; + float d; + vec3_t lightDir; + + xyz = ( float * ) tess.xyz; + + ground[0] = backEnd.or.axis[0][2]; + ground[1] = backEnd.or.axis[1][2]; + ground[2] = backEnd.or.axis[2][2]; + + groundDist = backEnd.or.origin[2] - backEnd.currentEntity->e.shadowPlane; + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + d = DotProduct( lightDir, ground ); + // don't let the shadows get too long or go negative + if ( d < 0.5 ) { + VectorMA( lightDir, ( 0.5 - d ), ground, lightDir ); + d = DotProduct( lightDir, ground ); + } + d = 1.0 / d; + + light[0] = lightDir[0] * d; + light[1] = lightDir[1] * d; + light[2] = lightDir[2] * d; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + h = DotProduct( xyz, ground ) + groundDist; + + xyz[0] -= light[0] * h; + xyz[1] -= light[1] * h; + xyz[2] -= light[2] * h; + } +} diff --git a/src/renderer/tr_sky.c b/src/renderer/tr_sky.c new file mode 100644 index 0000000..91ce508 --- /dev/null +++ b/src/renderer/tr_sky.c @@ -0,0 +1,1035 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_sky.c +#include "tr_local.h" + +#define SKY_SUBDIVISIONS 8 +#define HALF_SKY_SUBDIVISIONS ( SKY_SUBDIVISIONS / 2 ) + +static float s_cloudTexCoords[6][SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1][2]; +static float s_cloudTexP[6][SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1]; + +/* +=================================================================================== + +POLYGON TO BOX SIDE PROJECTION + +=================================================================================== +*/ + +static vec3_t sky_clip[6] = +{ + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +static float sky_mins[2][6], sky_maxs[2][6]; +static float sky_min, sky_max; + +/* +================ +AddSkyPolygon +================ +*/ +static void AddSkyPolygon( int nump, vec3_t vecs ) { + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + // s = [0]/[2], t = [1]/[2] + static int vec_to_st[6][3] = + { + {-2,3,1}, + {2,3,-1}, + + {1,3,2}, + {-1,3,-2}, + + {-2,-1,3}, + {-2,1,-3} + + // {-1,2,3}, + // {1,2,-3} + }; + + // decide which face it maps to + VectorCopy( vec3_origin, v ); + for ( i = 0, vp = vecs ; i < nump ; i++, vp += 3 ) + { + VectorAdd( vp, v, v ); + } + av[0] = fabs( v[0] ); + av[1] = fabs( v[1] ); + av[2] = fabs( v[2] ); + if ( av[0] > 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 < nump ; i++, vecs += 3 ) + { + j = vec_to_st[axis][2]; + if ( j > 0 ) { + dv = vecs[j - 1]; + } else { + dv = -vecs[-j - 1]; + } + if ( dv < 0.001 ) { + continue; // don't divide by zero + } + 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 < sky_mins[0][axis] ) { + sky_mins[0][axis] = s; + } + if ( t < sky_mins[1][axis] ) { + sky_mins[1][axis] = t; + } + if ( s > sky_maxs[0][axis] ) { + sky_maxs[0][axis] = s; + } + if ( t > sky_maxs[1][axis] ) { + sky_maxs[1][axis] = t; + } + } +} + +#define ON_EPSILON 0.1f // point on plane side epsilon +#define MAX_CLIP_VERTS 64 +/* +================ +ClipSkyPolygon +================ +*/ +static void ClipSkyPolygon( 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 ) { + ri.Error( ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS" ); + } + if ( stage == 6 ) { // fully clipped, so draw it + AddSkyPolygon( nump, vecs ); + return; + } + + front = back = qfalse; + norm = sky_clip[stage]; + for ( i = 0, v = vecs ; i < nump ; i++, v += 3 ) + { + d = DotProduct( v, norm ); + if ( d > ON_EPSILON ) { + front = qtrue; + sides[i] = SIDE_FRONT; + } else if ( d < -ON_EPSILON ) { + back = qtrue; + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + dists[i] = d; + } + + if ( !front || !back ) { // not clipped + ClipSkyPolygon( 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 ; i < nump ; i++, v += 3 ) + { + switch ( sides[i] ) + { + case SIDE_FRONT: + VectorCopy( v, newv[0][newc[0]] ); + newc[0]++; + break; + case SIDE_BACK: + VectorCopy( v, newv[1][newc[1]] ); + newc[1]++; + break; + case SIDE_ON: + VectorCopy( v, newv[0][newc[0]] ); + newc[0]++; + VectorCopy( v, newv[1][newc[1]] ); + newc[1]++; + break; + } + + if ( sides[i] == SIDE_ON || sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + d = dists[i] / ( dists[i] - dists[i + 1] ); + for ( j = 0 ; j < 3 ; j++ ) + { + e = v[j] + d * ( v[j + 3] - v[j] ); + newv[0][newc[0]][j] = e; + newv[1][newc[1]][j] = e; + } + newc[0]++; + newc[1]++; + } + + // continue + ClipSkyPolygon( newc[0], newv[0][0], stage + 1 ); + ClipSkyPolygon( newc[1], newv[1][0], stage + 1 ); +} + +/* +============== +ClearSkyBox +============== +*/ +static void ClearSkyBox( void ) { + int i; + + for ( i = 0 ; i < 6 ; i++ ) { + sky_mins[0][i] = sky_mins[1][i] = 9999; + sky_maxs[0][i] = sky_maxs[1][i] = -9999; + } +} + +/* +================ +RB_ClipSkyPolygons +================ +*/ +void RB_ClipSkyPolygons( shaderCommands_t *input ) { + vec3_t p[5]; // need one extra point for clipping + int i, j; + + ClearSkyBox(); + + for ( i = 0; i < input->numIndexes; i += 3 ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + VectorSubtract( input->xyz[input->indexes[i + j]], + backEnd.viewParms.or.origin, + p[j] ); + } + ClipSkyPolygon( 3, p[0], 0 ); + } +} + +/* +=================================================================================== + +CLOUD VERTEX GENERATION + +=================================================================================== +*/ + +/* +** MakeSkyVec +** +** Parms: s, t range from -1 to 1 +*/ +static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ ) { + // 1 = s, 2 = t, 3 = 2048 + static int st_to_vec[6][3] = + { + {3,-1,2}, + {-3,1,2}, + + {1,3,2}, + {-1,-3,2}, + + {-2,-1,3}, // 0 degrees yaw, look straight up + {2,-1,-3} // look straight down + }; + + vec3_t b; + int j, k; + float boxSize; + +// JPW NERVE swiped from Sherman SP fix +// if(glfogNum > FOG_NONE && glfogsettings[FOG_CURRENT].mode == GL_EXP) { + if ( glfogsettings[FOG_SKY].registered ) { // (SA) trying this... +/// boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) +// boxSize = glfogsettings[FOG_CURRENT].end / 1.75; + boxSize = glfogsettings[FOG_SKY].end; // (SA) trying this... +// jpw + } else { + boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + + } +// JPW NERVE swiped from Sherman + // make sure the sky is not near clipped + if ( boxSize < r_znear->value * 2.0 ) { + boxSize = r_znear->value * 2.0; + } +// jpw + b[0] = s * boxSize; + b[1] = t * boxSize; + b[2] = boxSize; + + for ( j = 0 ; j < 3 ; j++ ) + { + k = st_to_vec[axis][j]; + if ( k < 0 ) { + outXYZ[j] = -b[-k - 1]; + } else + { + outXYZ[j] = b[k - 1]; + } + } + + // avoid bilerp seam + s = ( s + 1 ) * 0.5; + t = ( t + 1 ) * 0.5; + if ( s < sky_min ) { + s = sky_min; + } else if ( s > sky_max ) { + s = sky_max; + } + + if ( t < sky_min ) { + t = sky_min; + } else if ( t > sky_max ) { + t = sky_max; + } + + t = 1.0 - t; + + + if ( outSt ) { + outSt[0] = s; + outSt[1] = t; + } +} + +static int sky_texorder[6] = {0,2,1,3,4,5}; +static vec3_t s_skyPoints[SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1]; +static float s_skyTexCoords[SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1][2]; + +static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] ) { + int s, t; + + GL_Bind( image ); + + for ( t = mins[1] + HALF_SKY_SUBDIVISIONS; t < maxs[1] + HALF_SKY_SUBDIVISIONS; t++ ) + { + qglBegin( GL_TRIANGLE_STRIP ); + + for ( s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++ ) + { + qglTexCoord2fv( s_skyTexCoords[t][s] ); + qglVertex3fv( s_skyPoints[t][s] ); + + qglTexCoord2fv( s_skyTexCoords[t + 1][s] ); + qglVertex3fv( s_skyPoints[t + 1][s] ); + } + + qglEnd(); + } +} + +static void DrawSkySideInner( struct image_s *image, const int mins[2], const int maxs[2] ) { + int s, t; + + GL_Bind( image ); + + //qglDisable (GL_BLEND); + qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + qglEnable( GL_BLEND ); + GL_TexEnv( GL_MODULATE ); + + for ( t = mins[1] + HALF_SKY_SUBDIVISIONS; t < maxs[1] + HALF_SKY_SUBDIVISIONS; t++ ) + { + qglBegin( GL_TRIANGLE_STRIP ); + + for ( s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++ ) + { + qglTexCoord2fv( s_skyTexCoords[t][s] ); + qglVertex3fv( s_skyPoints[t][s] ); + + qglTexCoord2fv( s_skyTexCoords[t + 1][s] ); + qglVertex3fv( s_skyPoints[t + 1][s] ); + } + + qglEnd(); + } + + qglDisable( GL_BLEND ); +} + +static void DrawSkyBox( shader_t *shader ) { + int i; + + memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) ); + + sky_min = 0; + sky_max = 1; + + for ( i = 0 ; i < 6 ; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) { + continue; + } + + sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; + sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + } + if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + } + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + } + if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + } + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1] + HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1] + HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0] + HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0] + HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + s_skyTexCoords[t][s], + s_skyPoints[t][s] ); + } + } + + DrawSkySide( shader->sky.outerbox[sky_texorder[i]], + sky_mins_subd, + sky_maxs_subd ); + } + +} + + +static void DrawSkyBoxInner( shader_t *shader ) { + int i; + + memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) ); + + for ( i = 0 ; i < 6 ; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) { + continue; + } + + sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; + sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + } + if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + } + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + } + if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + } + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1] + HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1] + HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0] + HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0] + HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + s_skyTexCoords[t][s], + s_skyPoints[t][s] ); + } + } + + DrawSkySideInner( shader->sky.innerbox[sky_texorder[i]], + sky_mins_subd, + sky_maxs_subd ); + } + +} + +static void FillCloudySkySide( const int mins[2], const int maxs[2], qboolean addIndexes ) { + int s, t; + int vertexStart = tess.numVertexes; + int tHeight, sWidth; + + tHeight = maxs[1] - mins[1] + 1; + sWidth = maxs[0] - mins[0] + 1; + + for ( t = mins[1] + HALF_SKY_SUBDIVISIONS; t <= maxs[1] + HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++ ) + { + VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0]; + tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1]; + + tess.numVertexes++; + + if ( tess.numVertexes >= SHADER_MAX_VERTEXES ) { + ri.Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()\n" ); + } + } + } + + // only add indexes for one pass, otherwise it would draw multiple times for each pass + if ( addIndexes ) { + for ( t = 0; t < tHeight - 1; t++ ) + { + for ( s = 0; s < sWidth - 1; s++ ) + { + tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + } + } + } +} + +static void FillCloudBox( const shader_t *shader, int stage ) { + int i; + + for ( i = 0; i < 6; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + float MIN_T; + + if ( 1 ) { // FIXME? shader->sky.fullClouds ) + MIN_T = -HALF_SKY_SUBDIVISIONS; + + // still don't want to draw the bottom, even if fullClouds + if ( i == 5 ) { + continue; + } + } else + { + switch ( i ) + { + case 0: + case 1: + case 2: + case 3: + MIN_T = -1; + break; + case 5: + // don't draw clouds beneath you + continue; + case 4: // top + default: + MIN_T = -HALF_SKY_SUBDIVISIONS; + break; + } + } + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) { + continue; + } + + sky_mins_subd[0] = myftol( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ); + sky_mins_subd[1] = myftol( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ); + sky_maxs_subd[0] = myftol( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ); + sky_maxs_subd[1] = myftol( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ); + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + } + if ( sky_mins_subd[1] < MIN_T ) { + sky_mins_subd[1] = MIN_T; + } else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) { + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + } + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + } else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + } + if ( sky_maxs_subd[1] < MIN_T ) { + sky_maxs_subd[1] = MIN_T; + } else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) { + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + } + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1] + HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1] + HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0] + HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0] + HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + s_skyPoints[t][s] ); + + s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0]; + s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1]; + } + } + + // only add indexes for first stage + FillCloudySkySide( sky_mins_subd, sky_maxs_subd, ( stage == 0 ) ); + } +} + +/* +** R_BuildCloudData +*/ +void R_BuildCloudData( shaderCommands_t *input ) { + int i; + shader_t *shader; + + shader = input->shader; + + assert( shader->isSky ); + + sky_min = 1.0 / 256.0f; // FIXME: not correct? + sky_max = 255.0 / 256.0f; + + // set up for drawing + tess.numIndexes = 0; + tess.numVertexes = 0; + + if ( input->shader->sky.cloudHeight ) { + for ( i = 0; i < MAX_SHADER_STAGES; i++ ) + { + if ( !tess.xstages[i] ) { + break; + } + FillCloudBox( input->shader, i ); + } + } +} + +/* +** R_InitSkyTexCoords +** Called when a sky shader is parsed +*/ +#define SQR( a ) ( ( a ) * ( a ) ) +void R_InitSkyTexCoords( float heightCloud ) { + int i, s, t; + float radiusWorld = 4096; + float p; + float sRad, tRad; + vec3_t skyVec; + vec3_t v; + + // init zfar so MakeSkyVec works even though + // a world hasn't been bounded + backEnd.viewParms.zFar = 1024; + + for ( i = 0; i < 6; i++ ) + { + for ( t = 0; t <= SKY_SUBDIVISIONS; t++ ) + { + for ( s = 0; s <= SKY_SUBDIVISIONS; s++ ) + { + // compute vector from view origin to sky side integral point + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + skyVec ); + + // compute parametric value 'p' that intersects with cloud layer + p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) * + ( -2 * skyVec[2] * radiusWorld + + 2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) + + 2 * SQR( skyVec[0] ) * radiusWorld * heightCloud + + SQR( skyVec[0] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud + + SQR( skyVec[1] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud + + SQR( skyVec[2] ) * SQR( heightCloud ) ) ); + + s_cloudTexP[i][t][s] = p; + + // compute intersection point based on p + VectorScale( skyVec, p, v ); + v[2] += radiusWorld; + + // compute vector from world origin to intersection point 'v' + VectorNormalize( v ); + + sRad = Q_acos( v[0] ); + tRad = Q_acos( v[1] ); + + s_cloudTexCoords[i][t][s][0] = sRad; + s_cloudTexCoords[i][t][s][1] = tRad; + } + } + } +} + +//====================================================================================== + +/* +============== +RB_DrawSun + (SA) FIXME: sun should render behind clouds, so passing dark areas cover it up +============== +*/ +void RB_DrawSun( void ) { + float size; + float dist; + vec3_t origin, vec1, vec2; + vec3_t temp; + byte color[4]; + + if ( !tr.sunShader ) { + return; + } + + if ( !backEnd.skyRenderedThisView ) { + return; + } + if ( !r_drawSun->integer ) { + return; + } + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + qglTranslatef( backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2] ); + + dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + + // (SA) shrunk the size of the sun + size = dist * 0.2; + + VectorScale( tr.sunDirection, dist, origin ); + PerpendicularVector( vec1, tr.sunDirection ); + CrossProduct( tr.sunDirection, vec1, vec2 ); + + VectorScale( vec1, size, vec1 ); + VectorScale( vec2, size, vec2 ); + + // farthest depth range + qglDepthRange( 1.0, 1.0 ); + + color[0] = color[1] = color[2] = color[3] = 255; + + // (SA) simpler sun drawing + RB_BeginSurface( tr.sunShader, tess.fogNum ); + + RB_AddQuadStamp( origin, vec1, vec2, color ); +/* + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; +*/ + RB_EndSurface(); + + + if ( r_drawSun->integer > 1 ) { // draw flare effect + // (SA) FYI: This is cheezy and was only a test so far. + // If we decide to use the flare business I will /definatly/ improve all this + + // get a point a little closer + dist = dist * 0.7; + VectorScale( tr.sunDirection, dist, origin ); + + // and make the flare a little smaller + VectorScale( vec1, 0.5f, vec1 ); + VectorScale( vec2, 0.5f, vec2 ); + + // add the vectors to give an 'off angle' result + VectorAdd( tr.sunDirection, backEnd.viewParms.or.axis[0], temp ); + VectorNormalize( temp ); + + // amplify the result + origin[0] += temp[0] * 500.0; + origin[1] += temp[1] * 500.0; + origin[2] += temp[2] * 500.0; + + // (SA) FIXME: todo: flare effect should render last (on top of everything else) and only when sun is in view (sun moving out of camera past degree n should start to cause flare dimming until view angle to sun is off by angle n + x. + + // draw the flare + RB_BeginSurface( tr.sunflareShader[0], tess.fogNum ); + RB_AddQuadStamp( origin, vec1, vec2, color ); + RB_EndSurface(); + } + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); +} + + + +extern void R_Fog( glfog_t *curfog ); + +/* +================ +RB_StageIteratorSky + +All of the visible sky triangles are in tess + +Other things could be stuck in here, like birds in the sky, etc +================ +*/ +void RB_StageIteratorSky( void ) { + if ( r_fastsky->integer ) { + return; + } + + // when portal sky exists, only render skybox for the portal sky scene + if ( skyboxportal && !( backEnd.refdef.rdflags & RDF_SKYBOXPORTAL ) ) { + return; + } + + // does the current fog require fastsky? + if ( backEnd.viewParms.glFog.registered ) { + if ( !backEnd.viewParms.glFog.drawsky ) { + return; + } + } else if ( glfogNum > FOG_NONE ) { + if ( !glfogsettings[FOG_CURRENT].drawsky ) { + return; + } + } + + + backEnd.refdef.rdflags |= RDF_DRAWINGSKY; + + + // go through all the polygons and project them onto + // the sky box to see which blocks on each side need + // to be drawn + RB_ClipSkyPolygons( &tess ); + + // r_showsky will let all the sky blocks be drawn in + // front of everything to allow developers to see how + // much sky is getting sucked in + if ( r_showsky->integer ) { + qglDepthRange( 0.0, 0.0 ); + } else { + qglDepthRange( 1.0, 1.0 ); + } + + // draw the outer skybox + if ( tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage ) { + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglPushMatrix(); + GL_State( 0 ); + qglTranslatef( backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2] ); + + DrawSkyBox( tess.shader ); + + qglPopMatrix(); + } + + // generate the vertexes for all the clouds, which will be drawn + // by the generic shader routine + R_BuildCloudData( &tess ); + + RB_StageIteratorGeneric(); + + // draw the inner skybox + // Rafael - drawing inner skybox + if ( tess.shader->sky.innerbox[0] && tess.shader->sky.innerbox[0] != tr.defaultImage ) { + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglPushMatrix(); + GL_State( 0 ); + qglTranslatef( backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2] ); + + DrawSkyBoxInner( tess.shader ); + + qglPopMatrix(); + } + // Rafael - end + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); + + backEnd.refdef.rdflags &= ~RDF_DRAWINGSKY; + + // note that sky was drawn so we will draw a sun later + backEnd.skyRenderedThisView = qtrue; +} + diff --git a/src/renderer/tr_surface.c b/src/renderer/tr_surface.c new file mode 100644 index 0000000..b444798 --- /dev/null +++ b/src/renderer/tr_surface.c @@ -0,0 +1,1503 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// tr_surf.c +#include "tr_local.h" + +/* + + THIS ENTIRE FILE IS BACK END + +backEnd.currentEntity will be valid. + +Tess_Begin has already been called for the surface's shader. + +The modelview matrix will be set. + +It is safe to actually issue drawing commands here if you don't want to +use the shader system. +*/ + + +//============================================================================ + + +/* +============== +RB_CheckOverflow +============== +*/ +void RB_CheckOverflow( int verts, int indexes ) { + if ( tess.numVertexes + verts < SHADER_MAX_VERTEXES + && tess.numIndexes + indexes < SHADER_MAX_INDEXES ) { + return; + } + + RB_EndSurface(); + + if ( verts >= SHADER_MAX_VERTEXES ) { + ri.Error( ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES ); + } + if ( indexes >= SHADER_MAX_INDEXES ) { + ri.Error( ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES ); + } + + RB_BeginSurface( tess.shader, tess.fogNum ); +} + +/* +============== +RB_AddQuadStampFadingCornersExt + + Creates a sprite with the center at colors[3] alpha, and the corners all 0 alpha +============== +*/ +void RB_AddQuadStampFadingCornersExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ) { + vec3_t normal; + int ndx; + byte lColor[4]; + + RB_CHECKOVERFLOW( 5, 12 ); + + ndx = tess.numVertexes; + + // triangle indexes for a simple quad + tess.indexes[ tess.numIndexes + 0 ] = ndx + 0; + tess.indexes[ tess.numIndexes + 1 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 2 ] = ndx + 4; + + tess.indexes[ tess.numIndexes + 3 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 4 ] = ndx + 2; + tess.indexes[ tess.numIndexes + 5 ] = ndx + 4; + + tess.indexes[ tess.numIndexes + 6 ] = ndx + 2; + tess.indexes[ tess.numIndexes + 7 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 8 ] = ndx + 4; + + tess.indexes[ tess.numIndexes + 9 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 10] = ndx + 0; + tess.indexes[ tess.numIndexes + 11] = ndx + 4; + + tess.xyz[ndx][0] = origin[0] + left[0] + up[0]; + tess.xyz[ndx][1] = origin[1] + left[1] + up[1]; + tess.xyz[ndx][2] = origin[2] + left[2] + up[2]; + + tess.xyz[ndx + 1][0] = origin[0] - left[0] + up[0]; + tess.xyz[ndx + 1][1] = origin[1] - left[1] + up[1]; + tess.xyz[ndx + 1][2] = origin[2] - left[2] + up[2]; + + tess.xyz[ndx + 2][0] = origin[0] - left[0] - up[0]; + tess.xyz[ndx + 2][1] = origin[1] - left[1] - up[1]; + tess.xyz[ndx + 2][2] = origin[2] - left[2] - up[2]; + + tess.xyz[ndx + 3][0] = origin[0] + left[0] - up[0]; + tess.xyz[ndx + 3][1] = origin[1] + left[1] - up[1]; + tess.xyz[ndx + 3][2] = origin[2] + left[2] - up[2]; + + tess.xyz[ndx + 4][0] = origin[0]; + tess.xyz[ndx + 4][1] = origin[1]; + tess.xyz[ndx + 4][2] = origin[2]; + + + // constant normal all the way around + VectorSubtract( vec3_origin, backEnd.viewParms.or.axis[0], normal ); + + tess.normal[ndx][0] = tess.normal[ndx + 1][0] = tess.normal[ndx + 2][0] = tess.normal[ndx + 3][0] = tess.normal[ndx + 4][0] = normal[0]; + tess.normal[ndx][1] = tess.normal[ndx + 1][1] = tess.normal[ndx + 2][1] = tess.normal[ndx + 3][1] = tess.normal[ndx + 4][1] = normal[1]; + tess.normal[ndx][2] = tess.normal[ndx + 1][2] = tess.normal[ndx + 2][2] = tess.normal[ndx + 3][2] = tess.normal[ndx + 4][2] = normal[2]; + + // standard square texture coordinates + tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1; + tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1; + + tess.texCoords[ndx + 1][0][0] = tess.texCoords[ndx + 1][1][0] = s2; + tess.texCoords[ndx + 1][0][1] = tess.texCoords[ndx + 1][1][1] = t1; + + tess.texCoords[ndx + 2][0][0] = tess.texCoords[ndx + 2][1][0] = s2; + tess.texCoords[ndx + 2][0][1] = tess.texCoords[ndx + 2][1][1] = t2; + + tess.texCoords[ndx + 3][0][0] = tess.texCoords[ndx + 3][1][0] = s1; + tess.texCoords[ndx + 3][0][1] = tess.texCoords[ndx + 3][1][1] = t2; + + tess.texCoords[ndx + 4][0][0] = tess.texCoords[ndx + 4][1][0] = ( s1 + s2 ) / 2.0; + tess.texCoords[ndx + 4][0][1] = tess.texCoords[ndx + 4][1][1] = ( t1 + t2 ) / 2.0; + + // center uses full alpha + *( unsigned int * ) &tess.vertexColors[ndx + 4] = + *( unsigned int * )color; + + // fade around edges + memcpy( lColor, color, sizeof( byte ) * 4 ); + lColor[3] = 0; + *( unsigned int * ) &tess.vertexColors[ndx] = + *( unsigned int * ) &tess.vertexColors[ndx + 1] = + *( unsigned int * ) &tess.vertexColors[ndx + 2] = + *( unsigned int * ) &tess.vertexColors[ndx + 3] = + *( unsigned int * )lColor; + + + tess.numVertexes += 5; + tess.numIndexes += 12; +} + +/* +============== +RB_AddQuadStampExt +============== +*/ +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ) { + vec3_t normal; + int ndx; + + RB_CHECKOVERFLOW( 4, 6 ); + + ndx = tess.numVertexes; + + // triangle indexes for a simple quad + tess.indexes[ tess.numIndexes ] = ndx; + tess.indexes[ tess.numIndexes + 1 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 2 ] = ndx + 3; + + tess.indexes[ tess.numIndexes + 3 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 4 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 5 ] = ndx + 2; + + tess.xyz[ndx][0] = origin[0] + left[0] + up[0]; + tess.xyz[ndx][1] = origin[1] + left[1] + up[1]; + tess.xyz[ndx][2] = origin[2] + left[2] + up[2]; + + tess.xyz[ndx + 1][0] = origin[0] - left[0] + up[0]; + tess.xyz[ndx + 1][1] = origin[1] - left[1] + up[1]; + tess.xyz[ndx + 1][2] = origin[2] - left[2] + up[2]; + + tess.xyz[ndx + 2][0] = origin[0] - left[0] - up[0]; + tess.xyz[ndx + 2][1] = origin[1] - left[1] - up[1]; + tess.xyz[ndx + 2][2] = origin[2] - left[2] - up[2]; + + tess.xyz[ndx + 3][0] = origin[0] + left[0] - up[0]; + tess.xyz[ndx + 3][1] = origin[1] + left[1] - up[1]; + tess.xyz[ndx + 3][2] = origin[2] + left[2] - up[2]; + + + // constant normal all the way around + VectorSubtract( vec3_origin, backEnd.viewParms.or.axis[0], normal ); + + tess.normal[ndx][0] = tess.normal[ndx + 1][0] = tess.normal[ndx + 2][0] = tess.normal[ndx + 3][0] = normal[0]; + tess.normal[ndx][1] = tess.normal[ndx + 1][1] = tess.normal[ndx + 2][1] = tess.normal[ndx + 3][1] = normal[1]; + tess.normal[ndx][2] = tess.normal[ndx + 1][2] = tess.normal[ndx + 2][2] = tess.normal[ndx + 3][2] = normal[2]; + + // standard square texture coordinates + tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1; + tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1; + + tess.texCoords[ndx + 1][0][0] = tess.texCoords[ndx + 1][1][0] = s2; + tess.texCoords[ndx + 1][0][1] = tess.texCoords[ndx + 1][1][1] = t1; + + tess.texCoords[ndx + 2][0][0] = tess.texCoords[ndx + 2][1][0] = s2; + tess.texCoords[ndx + 2][0][1] = tess.texCoords[ndx + 2][1][1] = t2; + + tess.texCoords[ndx + 3][0][0] = tess.texCoords[ndx + 3][1][0] = s1; + tess.texCoords[ndx + 3][0][1] = tess.texCoords[ndx + 3][1][1] = t2; + + // constant color all the way around + // should this be identity and let the shader specify from entity? + *( unsigned int * ) &tess.vertexColors[ndx] = + *( unsigned int * ) &tess.vertexColors[ndx + 1] = + *( unsigned int * ) &tess.vertexColors[ndx + 2] = + *( unsigned int * ) &tess.vertexColors[ndx + 3] = + *( unsigned int * )color; + + + tess.numVertexes += 4; + tess.numIndexes += 6; +} + +/* +============== +RB_AddQuadStamp +============== +*/ +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ) { + RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 ); +} + +/* +============== +RB_SurfaceSplash +============== +*/ +static void RB_SurfaceSplash( void ) { + vec3_t left, up; + float radius; + + // calculate the xyz locations for the four corners + radius = backEnd.currentEntity->e.radius; + + VectorSet( left, -radius, 0, 0 ); + VectorSet( up, 0, radius, 0 ); + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + +/* +============== +RB_SurfaceSprite +============== +*/ +static void RB_SurfaceSprite( void ) { + vec3_t left, up; + float radius; + + // calculate the xyz locations for the four corners + radius = backEnd.currentEntity->e.radius; + if ( backEnd.currentEntity->e.rotation == 0 ) { + VectorScale( backEnd.viewParms.or.axis[1], radius, left ); + VectorScale( backEnd.viewParms.or.axis[2], radius, up ); + } else { + float s, c; + float ang; + + ang = M_PI * backEnd.currentEntity->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.or.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.or.axis[2], left ); + + VectorScale( backEnd.viewParms.or.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.or.axis[1], up ); + } + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + + +/* +============= +RB_SurfacePolychain +============= +*/ +void RB_SurfacePolychain( srfPoly_t *p ) { + int i; + int numv; + + RB_CHECKOVERFLOW( p->numVerts, 3 * ( p->numVerts - 2 ) ); + + // fan triangles into the tess array + numv = tess.numVertexes; + for ( i = 0; i < p->numVerts; i++ ) { + VectorCopy( p->verts[i].xyz, tess.xyz[numv] ); + tess.texCoords[numv][0][0] = p->verts[i].st[0]; + tess.texCoords[numv][0][1] = p->verts[i].st[1]; + *(int *)&tess.vertexColors[numv] = *(int *)p->verts[ i ].modulate; + + numv++; + } + + // generate fan indexes into the tess array + for ( i = 0; i < p->numVerts - 2; i++ ) { + tess.indexes[tess.numIndexes + 0] = tess.numVertexes; + tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; + tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; + tess.numIndexes += 3; + } + + tess.numVertexes = numv; +} + + +/* +============= +RB_SurfaceTriangles +============= +*/ +void RB_SurfaceTriangles( srfTriangles_t *srf ) { + int i; + drawVert_t *dv; + float *xyz, *normal, *texCoords; + byte *color; + int dlightBits; + qboolean needsNormal; + + dlightBits = srf->dlightBits[backEnd.smpFrame]; + tess.dlightBits |= dlightBits; + + RB_CHECKOVERFLOW( srf->numVerts, srf->numIndexes ); + + for ( i = 0 ; i < srf->numIndexes ; i += 3 ) { + tess.indexes[ tess.numIndexes + i + 0 ] = tess.numVertexes + srf->indexes[ i + 0 ]; + tess.indexes[ tess.numIndexes + i + 1 ] = tess.numVertexes + srf->indexes[ i + 1 ]; + tess.indexes[ tess.numIndexes + i + 2 ] = tess.numVertexes + srf->indexes[ i + 2 ]; + } + tess.numIndexes += srf->numIndexes; + + dv = srf->verts; + xyz = tess.xyz[ tess.numVertexes ]; + normal = tess.normal[ tess.numVertexes ]; + texCoords = tess.texCoords[ tess.numVertexes ][0]; + color = tess.vertexColors[ tess.numVertexes ]; + needsNormal = tess.shader->needsNormal; + + for ( i = 0 ; i < srf->numVerts ; i++, dv++, xyz += 4, normal += 4, texCoords += 4, color += 4 ) { + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + + if ( needsNormal ) { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + + texCoords[2] = dv->lightmap[0]; + texCoords[3] = dv->lightmap[1]; + + *(int *)color = *(int *)dv->color; + } + + for ( i = 0 ; i < srf->numVerts ; i++ ) { + tess.vertexDlightBits[ tess.numVertexes + i] = dlightBits; + } + + tess.numVertexes += srf->numVerts; +} + + + +/* +============== +RB_SurfaceBeam +============== +*/ +void RB_SurfaceBeam( void ) { +#define NUM_BEAM_SEGS 6 + refEntity_t *e; + int i; + vec3_t perpvec; + vec3_t direction, normalized_direction; + vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; + vec3_t oldorigin, origin; + + e = &backEnd.currentEntity->e; + + oldorigin[0] = e->oldorigin[0]; + oldorigin[1] = e->oldorigin[1]; + oldorigin[2] = e->oldorigin[2]; + + origin[0] = e->origin[0]; + origin[1] = e->origin[1]; + origin[2] = e->origin[2]; + + normalized_direction[0] = direction[0] = oldorigin[0] - origin[0]; + normalized_direction[1] = direction[1] = oldorigin[1] - origin[1]; + normalized_direction[2] = direction[2] = oldorigin[2] - origin[2]; + + if ( VectorNormalize( normalized_direction ) == 0 ) { + return; + } + + PerpendicularVector( perpvec, normalized_direction ); + + VectorScale( perpvec, 4, perpvec ); + + for ( i = 0; i < NUM_BEAM_SEGS ; i++ ) + { + RotatePointAroundVector( start_points[i], normalized_direction, perpvec, ( 360.0 / NUM_BEAM_SEGS ) * i ); +// VectorAdd( start_points[i], origin, start_points[i] ); + VectorAdd( start_points[i], direction, end_points[i] ); + } + + GL_Bind( tr.whiteImage ); + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + qglColor3f( 1, 0, 0 ); + + qglBegin( GL_TRIANGLE_STRIP ); + for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) { + qglVertex3fv( start_points[ i % NUM_BEAM_SEGS] ); + qglVertex3fv( end_points[ i % NUM_BEAM_SEGS] ); + } + qglEnd(); +} + +//================================================================================ + +static void DoRailCore( const vec3_t start, const vec3_t end, const vec3_t up, float len, float spanWidth ) { + float spanWidth2; + int vbase; + float t = len / 256.0f; + + vbase = tess.numVertexes; + + spanWidth2 = -spanWidth; + + // FIXME: use quad stamp? + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 0.25; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 0.25; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 0.25; + tess.numVertexes++; + + VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = t; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = t; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +static void DoRailDiscs( int numSegs, const vec3_t start, const vec3_t dir, const vec3_t right, const vec3_t up ) { + int i; + vec3_t pos[4]; + vec3_t v; + int spanWidth = r_railWidth->integer; + float c, s; + float scale; + + if ( numSegs > 1 ) { + numSegs--; + } + if ( !numSegs ) { + return; + } + + scale = 0.25; + + for ( i = 0; i < 4; i++ ) + { + c = cos( DEG2RAD( 45 + i * 90 ) ); + s = sin( DEG2RAD( 45 + i * 90 ) ); + v[0] = ( right[0] * c + up[0] * s ) * scale * spanWidth; + v[1] = ( right[1] * c + up[1] * s ) * scale * spanWidth; + v[2] = ( right[2] * c + up[2] * s ) * scale * spanWidth; + VectorAdd( start, v, pos[i] ); + + if ( numSegs > 1 ) { + // offset by 1 segment if we're doing a long distance shot + VectorAdd( pos[i], dir, pos[i] ); + } + } + + for ( i = 0; i < numSegs; i++ ) + { + int j; + + RB_CHECKOVERFLOW( 4, 6 ); + + for ( j = 0; j < 4; j++ ) + { + VectorCopy( pos[j], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = ( j < 2 ); + tess.texCoords[tess.numVertexes][0][1] = ( j && j != 3 ); + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.numVertexes++; + + VectorAdd( pos[j], dir, pos[j] ); + } + + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 0; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 2; + } +} + +/* +** RB_SurfaceRailRinges +*/ +void RB_SurfaceRailRings( void ) { + refEntity_t *e; + int numSegs; + int len; + vec3_t vec; + vec3_t right, up; + vec3_t start, end; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + MakeNormalVectors( vec, right, up ); + numSegs = ( len ) / r_railSegmentLength->value; + if ( numSegs <= 0 ) { + numSegs = 1; + } + + VectorScale( vec, r_railSegmentLength->value, vec ); + + DoRailDiscs( numSegs, start, vec, right, up ); +} + +/* +** RB_SurfaceRailCore +*/ +void RB_SurfaceRailCore( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.or.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.or.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + DoRailCore( start, end, right, len, r_railCoreWidth->integer ); +} + +/* +** RB_SurfaceLightningBolt +*/ +void RB_SurfaceLightningBolt( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + int i; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, end ); + VectorCopy( e->origin, start ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.or.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.or.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + for ( i = 0 ; i < 4 ; i++ ) { + vec3_t temp; + + DoRailCore( start, end, right, len, 8 ); + RotatePointAroundVector( temp, vec, right, 45 ); + VectorCopy( temp, right ); + } +} + +/* +** VectorArrayNormalize +* +* The inputs to this routing seem to always be close to length = 1.0 (about 0.6 to 2.0) +* This means that we don't have to worry about zero length or enormously long vectors. +*/ +static void VectorArrayNormalize( vec4_t *normals, unsigned int count ) { +// assert(count); + +#if idppc + { + register float half = 0.5; + register float one = 1.0; + float *components = (float *)normals; + + // Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, + // runs *much* faster than calling sqrt(). We'll use a single Newton-Raphson + // refinement step to get a little more precision. This seems to yeild results + // that are correct to 3 decimal places and usually correct to at least 4 (sometimes 5). + // (That is, for the given input range of about 0.6 to 2.0). + do { + float x, y, z; + float B, y0, y1; + + x = components[0]; + y = components[1]; + z = components[2]; + components += 4; + B = x * x + y * y + z * z; + +#ifdef __GNUC__ + asm ( "frsqrte %0,%1" : "=f" ( y0 ) : "f" ( B ) ); +#else + y0 = __frsqrte( B ); +#endif + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + x = x * y1; + y = y * y1; + components[-4] = x; + z = z * y1; + components[-3] = y; + components[-2] = z; + } while ( count-- ); + } +#else // No assembly version for this architecture, or C_ONLY defined + // given the input, it's safe to call VectorNormalizeFast + while ( count-- ) { + VectorNormalizeFast( normals[0] ); + normals++; + } +#endif + +} + + + +/* +** LerpMeshVertexes +*/ +static void LerpMeshVertexes( md3Surface_t *surf, float backlerp ) { + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale, newXyzScale; + float oldNormalScale, newNormalScale; + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = ( short * )( (byte *)surf + surf->ofsXyzNormals ) + + ( backEnd.currentEntity->e.frame * surf->numVerts * 4 ); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * ( 1.0 - backlerp ); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for ( vertNum = 0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4 ) + { + + outXyz[0] = newXyz[0] * newXyzScale; + outXyz[1] = newXyz[1] * newXyzScale; + outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= ( FUNCTABLE_SIZE / 256 ); + lng *= ( FUNCTABLE_SIZE / 256 ); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + outNormal[0] = tr.sinTable[( lat + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK]; + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = ( short * )( (byte *)surf + surf->ofsXyzNormals ) + + ( backEnd.currentEntity->e.oldframe * surf->numVerts * 4 ); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for ( vertNum = 0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4 ) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[( lat + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[( lat + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + +// VectorNormalize (outNormal); + } + VectorArrayNormalize( (vec4_t *)tess.normal[tess.numVertexes], numVerts ); + } +} + +/* +============= +RB_SurfaceMesh +============= +*/ +void RB_SurfaceMesh( md3Surface_t *surface ) { + int j; + float backlerp; + int *triangles; + float *texCoords; + int indexes; + int Bob, Doug; + int numVerts; + + // RF, check for REFLAG_HANDONLY + if ( backEnd.currentEntity->e.reFlags & REFLAG_ONLYHAND ) { + if ( !strstr( surface->name, "hand" ) ) { + return; + } + } + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + } + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles * 3 ); + + LerpMeshVertexes( surface, backlerp ); + + triangles = ( int * )( (byte *)surface + surface->ofsTriangles ); + indexes = surface->numTriangles * 3; + Bob = tess.numIndexes; + Doug = tess.numVertexes; + for ( j = 0 ; j < indexes ; j++ ) { + tess.indexes[Bob + j] = Doug + triangles[j]; + } + tess.numIndexes += indexes; + + texCoords = ( float * )( (byte *)surface + surface->ofsSt ); + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tess.texCoords[Doug + j][0][0] = texCoords[j * 2 + 0]; + tess.texCoords[Doug + j][0][1] = texCoords[j * 2 + 1]; + // FIXME: fill in lightmapST for completeness? + } + + tess.numVertexes += surface->numVerts; + +} + +/* +** R_LatLongToNormal +*/ +void R_LatLongToNormal( vec3_t outNormal, short latLong ) { + unsigned lat, lng; + + lat = ( latLong >> 8 ) & 0xff; + lng = ( latLong & 0xff ); + lat *= ( FUNCTABLE_SIZE / 256 ); + lng *= ( FUNCTABLE_SIZE / 256 ); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + outNormal[0] = tr.sinTable[( lat + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK]; +} + +// Ridah +/* +** LerpCMeshVertexes +*/ +static void LerpCMeshVertexes( mdcSurface_t *surf, float backlerp ) { + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale, newXyzScale; + float oldNormalScale, newNormalScale; + int vertNum; + unsigned lat, lng; + int numVerts; + + int oldBase, newBase; + short *oldComp = NULL, *newComp = NULL; // TTimo: init + mdcXyzCompressed_t *oldXyzComp = NULL, *newXyzComp = NULL; // TTimo: init + vec3_t oldOfsVec, newOfsVec; + + qboolean hasComp; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newBase = (int)*( ( short * )( (byte *)surf + surf->ofsFrameBaseFrames ) + backEnd.currentEntity->e.frame ); + newXyz = ( short * )( (byte *)surf + surf->ofsXyzNormals ) + + ( newBase * surf->numVerts * 4 ); + newNormals = newXyz + 3; + + hasComp = ( surf->numCompFrames > 0 ); + if ( hasComp ) { + newComp = ( ( short * )( (byte *)surf + surf->ofsFrameCompFrames ) + backEnd.currentEntity->e.frame ); + if ( *newComp >= 0 ) { + newXyzComp = ( mdcXyzCompressed_t * )( (byte *)surf + surf->ofsXyzCompressed ) + + ( *newComp * surf->numVerts ); + } + } + + newXyzScale = MD3_XYZ_SCALE * ( 1.0 - backlerp ); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for ( vertNum = 0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4 ) + { + + outXyz[0] = newXyz[0] * newXyzScale; + outXyz[1] = newXyz[1] * newXyzScale; + outXyz[2] = newXyz[2] * newXyzScale; + + // add the compressed ofsVec + if ( hasComp && *newComp >= 0 ) { + R_MDC_DecodeXyzCompressed( newXyzComp->ofsVec, newOfsVec, outNormal ); + newXyzComp++; + VectorAdd( outXyz, newOfsVec, outXyz ); + } else { + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + outNormal[0] = tr.sinTable[( lat + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK]; + } + } + } else { + // + // interpolate and copy the vertex and normal + // + oldBase = (int)*( ( short * )( (byte *)surf + surf->ofsFrameBaseFrames ) + backEnd.currentEntity->e.oldframe ); + oldXyz = ( short * )( (byte *)surf + surf->ofsXyzNormals ) + + ( oldBase * surf->numVerts * 4 ); + oldNormals = oldXyz + 3; + + if ( hasComp ) { + oldComp = ( ( short * )( (byte *)surf + surf->ofsFrameCompFrames ) + backEnd.currentEntity->e.oldframe ); + if ( *oldComp >= 0 ) { + oldXyzComp = ( mdcXyzCompressed_t * )( (byte *)surf + surf->ofsXyzCompressed ) + + ( *oldComp * surf->numVerts ); + } + } + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for ( vertNum = 0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4 ) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + // add the compressed ofsVec + if ( hasComp && *newComp >= 0 ) { + R_MDC_DecodeXyzCompressed( newXyzComp->ofsVec, newOfsVec, uncompressedNewNormal ); + newXyzComp++; + VectorMA( outXyz, 1.0 - backlerp, newOfsVec, outXyz ); + } else { + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedNewNormal[0] = tr.sinTable[( lat + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK]; + } + + if ( hasComp && *oldComp >= 0 ) { + R_MDC_DecodeXyzCompressed( oldXyzComp->ofsVec, oldOfsVec, uncompressedOldNormal ); + oldXyzComp++; + VectorMA( outXyz, backlerp, oldOfsVec, outXyz ); + } else { + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[( lat + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[( lng + ( FUNCTABLE_SIZE / 4 ) ) & FUNCTABLE_MASK]; + } + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + + VectorNormalize( outNormal ); + } + } +} + +/* +============= +RB_SurfaceCMesh +============= +*/ +void RB_SurfaceCMesh( mdcSurface_t *surface ) { + int j; + float backlerp; + int *triangles; + float *texCoords; + int indexes; + int Bob, Doug; + int numVerts; + + // RF, check for REFLAG_HANDONLY + if ( backEnd.currentEntity->e.reFlags & REFLAG_ONLYHAND ) { + if ( !strstr( surface->name, "hand" ) ) { + return; + } + } + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + } + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles * 3 ); + + LerpCMeshVertexes( surface, backlerp ); + + triangles = ( int * )( (byte *)surface + surface->ofsTriangles ); + indexes = surface->numTriangles * 3; + Bob = tess.numIndexes; + Doug = tess.numVertexes; + for ( j = 0 ; j < indexes ; j++ ) { + tess.indexes[Bob + j] = Doug + triangles[j]; + } + tess.numIndexes += indexes; + + texCoords = ( float * )( (byte *)surface + surface->ofsSt ); + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tess.texCoords[Doug + j][0][0] = texCoords[j * 2 + 0]; + tess.texCoords[Doug + j][0][1] = texCoords[j * 2 + 1]; + // FIXME: fill in lightmapST for completeness? + } + + tess.numVertexes += surface->numVerts; + +} +// done. + +/* +============== +RB_SurfaceFace +============== +*/ +void RB_SurfaceFace( srfSurfaceFace_t *surf ) { + int i; + unsigned *indices, *tessIndexes; + float *v; + float *normal; + int ndx; + int Bob; + int numPoints; + int dlightBits; + + RB_CHECKOVERFLOW( surf->numPoints, surf->numIndices ); + + dlightBits = surf->dlightBits[backEnd.smpFrame]; + tess.dlightBits |= dlightBits; + + indices = ( unsigned * )( ( ( char * ) surf ) + surf->ofsIndices ); + + Bob = tess.numVertexes; + tessIndexes = tess.indexes + tess.numIndexes; + for ( i = surf->numIndices - 1 ; i >= 0 ; i-- ) { + tessIndexes[i] = indices[i] + Bob; + } + + tess.numIndexes += surf->numIndices; + + v = surf->points[0]; + + ndx = tess.numVertexes; + + numPoints = surf->numPoints; + + if ( tess.shader->needsNormal ) { + normal = surf->plane.normal; + for ( i = 0, ndx = tess.numVertexes; i < numPoints; i++, ndx++ ) { + VectorCopy( normal, tess.normal[ndx] ); + } + } + + for ( i = 0, v = surf->points[0], ndx = tess.numVertexes; i < numPoints; i++, v += VERTEXSIZE, ndx++ ) { + VectorCopy( v, tess.xyz[ndx] ); + tess.texCoords[ndx][0][0] = v[3]; + tess.texCoords[ndx][0][1] = v[4]; + tess.texCoords[ndx][1][0] = v[5]; + tess.texCoords[ndx][1][1] = v[6]; + *( unsigned int * ) &tess.vertexColors[ndx] = *( unsigned int * ) &v[7]; + tess.vertexDlightBits[ndx] = dlightBits; + } + + + tess.numVertexes += surf->numPoints; +} + + +static float LodErrorForVolume( vec3_t local, float radius ) { + vec3_t world; + float d; + + // never let it go negative + if ( r_lodCurveError->value < 0 ) { + return 0; + } + + world[0] = local[0] * backEnd.or.axis[0][0] + local[1] * backEnd.or.axis[1][0] + + local[2] * backEnd.or.axis[2][0] + backEnd.or.origin[0]; + world[1] = local[0] * backEnd.or.axis[0][1] + local[1] * backEnd.or.axis[1][1] + + local[2] * backEnd.or.axis[2][1] + backEnd.or.origin[1]; + world[2] = local[0] * backEnd.or.axis[0][2] + local[1] * backEnd.or.axis[1][2] + + local[2] * backEnd.or.axis[2][2] + backEnd.or.origin[2]; + + VectorSubtract( world, backEnd.viewParms.or.origin, world ); + d = DotProduct( world, backEnd.viewParms.or.axis[0] ); + + if ( d < 0 ) { + d = -d; + } + d -= radius; + if ( d < 1 ) { + d = 1; + } + + return r_lodCurveError->value / d; +} + +/* +============= +RB_SurfaceGrid + +Just copy the grid of points and triangulate +============= +*/ +void RB_SurfaceGrid( srfGridMesh_t *cv ) { + int i, j; + float *xyz; + float *texCoords; + float *normal; + unsigned char *color; + drawVert_t *dv; + int rows, irows, vrows; + int used; + int widthTable[MAX_GRID_SIZE]; + int heightTable[MAX_GRID_SIZE]; + float lodError; + int lodWidth, lodHeight; + int numVertexes; + int dlightBits; + int *vDlightBits; + qboolean needsNormal; + + dlightBits = cv->dlightBits[backEnd.smpFrame]; + tess.dlightBits |= dlightBits; + + // determine the allowable discrepance + lodError = LodErrorForVolume( cv->lodOrigin, cv->lodRadius ); + + // determine which rows and columns of the subdivision + // we are actually going to use + widthTable[0] = 0; + lodWidth = 1; + for ( i = 1 ; i < cv->width - 1 ; i++ ) { + if ( cv->widthLodError[i] <= lodError ) { + widthTable[lodWidth] = i; + lodWidth++; + } + } + widthTable[lodWidth] = cv->width - 1; + lodWidth++; + + heightTable[0] = 0; + lodHeight = 1; + for ( i = 1 ; i < cv->height - 1 ; i++ ) { + if ( cv->heightLodError[i] <= lodError ) { + heightTable[lodHeight] = i; + lodHeight++; + } + } + heightTable[lodHeight] = cv->height - 1; + lodHeight++; + + + // very large grids may have more points or indexes than can be fit + // in the tess structure, so we may have to issue it in multiple passes + + used = 0; + rows = 0; + while ( used < lodHeight - 1 ) { + // see how many rows of both verts and indexes we can add without overflowing + do { + vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth; + irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 ); + + // if we don't have enough space for at least one strip, flush the buffer + if ( vrows < 2 || irows < 1 ) { + RB_EndSurface(); + RB_BeginSurface( tess.shader, tess.fogNum ); + } else { + break; + } + } while ( 1 ); + + rows = irows; + if ( vrows < irows + 1 ) { + rows = vrows - 1; + } + if ( used + rows > lodHeight ) { + rows = lodHeight - used; + } + + numVertexes = tess.numVertexes; + + xyz = tess.xyz[numVertexes]; + normal = tess.normal[numVertexes]; + texCoords = tess.texCoords[numVertexes][0]; + color = ( unsigned char * ) &tess.vertexColors[numVertexes]; + vDlightBits = &tess.vertexDlightBits[numVertexes]; + needsNormal = tess.shader->needsNormal; + + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = cv->verts + heightTable[ used + i ] * cv->width + + widthTable[ j ]; + + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + texCoords[2] = dv->lightmap[0]; + texCoords[3] = dv->lightmap[1]; + if ( needsNormal ) { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + *( unsigned int * ) color = *( unsigned int * ) dv->color; + *vDlightBits++ = dlightBits; + xyz += 4; + normal += 4; + texCoords += 4; + color += 4; + } + } + + + // add the indexes + { + int numIndexes; + int w, h; + + h = rows - 1; + w = lodWidth - 1; + numIndexes = tess.numIndexes; + for ( i = 0 ; i < h ; i++ ) { + for ( j = 0 ; j < w ; j++ ) { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = numVertexes + i * lodWidth + j + 1; + v2 = v1 - 1; + v3 = v2 + lodWidth; + v4 = v3 + 1; + + tess.indexes[numIndexes] = v2; + tess.indexes[numIndexes + 1] = v3; + tess.indexes[numIndexes + 2] = v1; + + tess.indexes[numIndexes + 3] = v1; + tess.indexes[numIndexes + 4] = v3; + tess.indexes[numIndexes + 5] = v4; + numIndexes += 6; + } + } + + tess.numIndexes = numIndexes; + } + + tess.numVertexes += rows * lodWidth; + + used += rows - 1; + } +} + + +/* +=========================================================================== + +NULL MODEL + +=========================================================================== +*/ + +/* +=================== +RB_SurfaceAxis + +Draws x/y/z lines from the origin for orientation debugging +=================== +*/ +void RB_SurfaceAxis( void ) { + GL_Bind( tr.whiteImage ); + qglLineWidth( 3 ); + qglBegin( GL_LINES ); + qglColor3f( 1,0,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 16,0,0 ); + qglColor3f( 0,1,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,16,0 ); + qglColor3f( 0,0,1 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,0,16 ); + qglEnd(); + qglLineWidth( 1 ); +} + +//=========================================================================== + +/* +==================== +RB_SurfaceEntity + +Entities that have a single procedurally generated surface +==================== +*/ +void RB_SurfaceEntity( surfaceType_t *surfType ) { + switch ( backEnd.currentEntity->e.reType ) { + case RT_SPLASH: + RB_SurfaceSplash(); + break; + case RT_SPRITE: + RB_SurfaceSprite(); + break; + case RT_BEAM: + RB_SurfaceBeam(); + break; + case RT_RAIL_CORE: + RB_SurfaceRailCore(); + break; + case RT_RAIL_RINGS: + RB_SurfaceRailRings(); + break; + case RT_LIGHTNING: + RB_SurfaceLightningBolt(); + break; + default: + RB_SurfaceAxis(); + break; + } + return; +} + +void RB_SurfaceBad( surfaceType_t *surfType ) { + ri.Printf( PRINT_ALL, "Bad surface tesselated.\n" ); +} + +#if 0 + +void RB_SurfaceFlare( srfFlare_t *surf ) { + vec3_t left, up; + float radius; + byte color[4]; + vec3_t dir; + vec3_t origin; + float d; + + // calculate the xyz locations for the four corners + radius = 30; + VectorScale( backEnd.viewParms.or.axis[1], radius, left ); + VectorScale( backEnd.viewParms.or.axis[2], radius, up ); + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + color[0] = color[1] = color[2] = color[3] = 255; + + VectorMA( surf->origin, 3, surf->normal, origin ); + VectorSubtract( origin, backEnd.viewParms.or.origin, dir ); + VectorNormalize( dir ); + VectorMA( origin, r_ignore->value, dir, origin ); + + d = -DotProduct( dir, surf->normal ); + if ( d < 0 ) { + return; + } +#if 0 + color[0] *= d; + color[1] *= d; + color[2] *= d; +#endif + + RB_AddQuadStamp( origin, left, up, color ); +} + +#else + +void RB_SurfaceFlare( srfFlare_t *surf ) { +#if 0 + vec3_t left, up; + byte color[4]; + + color[0] = surf->color[0] * 255; + color[1] = surf->color[1] * 255; + color[2] = surf->color[2] * 255; + color[3] = 255; + + VectorClear( left ); + VectorClear( up ); + + left[0] = r_ignore->value; + + up[1] = r_ignore->value; + + RB_AddQuadStampExt( surf->origin, left, up, color, 0, 0, 1, 1 ); +#endif +} + +#endif + + + +void RB_SurfaceDisplayList( srfDisplayList_t *surf ) { + // all apropriate state must be set in RB_BeginSurface + // this isn't implemented yet... + qglCallList( surf->listNum ); +} + +void RB_SurfaceSkip( void *surf ) { +} + + +void( *rb_surfaceTable[SF_NUM_SURFACE_TYPES] ) ( void * ) = { + ( void( * ) ( void* ) )RB_SurfaceBad, // SF_BAD, + ( void( * ) ( void* ) )RB_SurfaceSkip, // SF_SKIP, + ( void( * ) ( void* ) )RB_SurfaceFace, // SF_FACE, + ( void( * ) ( void* ) )RB_SurfaceGrid, // SF_GRID, + ( void( * ) ( void* ) )RB_SurfaceTriangles, // SF_TRIANGLES, + ( void( * ) ( void* ) )RB_SurfacePolychain, // SF_POLY, + ( void( * ) ( void* ) )RB_SurfaceMesh, // SF_MD3, + ( void( * ) ( void* ) )RB_SurfaceCMesh, // SF_MDC, + ( void( * ) ( void* ) )RB_SurfaceAnim, // SF_MDS, + ( void( * ) ( void* ) )RB_SurfaceFlare, // SF_FLARE, + ( void( * ) ( void* ) )RB_SurfaceEntity, // SF_ENTITY + ( void( * ) ( void* ) )RB_SurfaceDisplayList // SF_DISPLAY_LIST +}; diff --git a/src/renderer/tr_world.c b/src/renderer/tr_world.c new file mode 100644 index 0000000..2d2120a --- /dev/null +++ b/src/renderer/tr_world.c @@ -0,0 +1,714 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "tr_local.h" + + + +/* +================= +R_CullTriSurf + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static qboolean R_CullTriSurf( srfTriangles_t *cv ) { + int boxCull; + + boxCull = R_CullLocalBox( cv->bounds ); + + if ( boxCull == CULL_OUT ) { + return qtrue; + } + return qfalse; +} + +/* +================= +R_CullGrid + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static qboolean R_CullGrid( srfGridMesh_t *cv ) { + int boxCull; + int sphereCull; + + if ( r_nocurves->integer ) { + return qtrue; + } + + if ( tr.currentEntityNum != ENTITYNUM_WORLD ) { + sphereCull = R_CullLocalPointAndRadius( cv->localOrigin, cv->meshRadius ); + } else { + sphereCull = R_CullPointAndRadius( cv->localOrigin, cv->meshRadius ); + } + boxCull = CULL_OUT; + + // check for trivial reject + if ( sphereCull == CULL_OUT ) { + tr.pc.c_sphere_cull_patch_out++; + return qtrue; + } + // check bounding box if necessary + else if ( sphereCull == CULL_CLIP ) { + tr.pc.c_sphere_cull_patch_clip++; + + boxCull = R_CullLocalBox( cv->meshBounds ); + + if ( boxCull == CULL_OUT ) { + tr.pc.c_box_cull_patch_out++; + return qtrue; + } else if ( boxCull == CULL_IN ) { + tr.pc.c_box_cull_patch_in++; + } else + { + tr.pc.c_box_cull_patch_clip++; + } + } else + { + tr.pc.c_sphere_cull_patch_in++; + } + + return qfalse; +} + + +/* +================ +R_CullSurface + +Tries to back face cull surfaces before they are lighted or +added to the sorting list. + +This will also allow mirrors on both sides of a model without recursion. +================ +*/ +static qboolean R_CullSurface( surfaceType_t *surface, shader_t *shader ) { + srfSurfaceFace_t *sface; + float d; + + if ( r_nocull->integer ) { + return qfalse; + } + + if ( *surface == SF_GRID ) { + return R_CullGrid( (srfGridMesh_t *)surface ); + } + + if ( *surface == SF_TRIANGLES ) { + return R_CullTriSurf( (srfTriangles_t *)surface ); + } + + if ( *surface != SF_FACE ) { + return qfalse; + } + + if ( shader->cullType == CT_TWO_SIDED ) { + return qfalse; + } + + // face culling + if ( !r_facePlaneCull->integer ) { + return qfalse; + } + + sface = ( srfSurfaceFace_t * ) surface; + d = DotProduct( tr.or.viewOrigin, sface->plane.normal ); + + // don't cull exactly on the plane, because there are levels of rounding + // through the BSP, ICD, and hardware that may cause pixel gaps if an + // epsilon isn't allowed here + if ( shader->cullType == CT_FRONT_SIDED ) { + if ( d < sface->plane.dist - 8 ) { + return qtrue; + } + } else { + if ( d > sface->plane.dist + 8 ) { + return qtrue; + } + } + + return qfalse; +} + + +static int R_DlightFace( srfSurfaceFace_t *face, int dlightBits ) { + float d; + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( !( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist; + if ( d < -dl->radius || d > dl->radius ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + face->dlightBits[ tr.smpFrame ] = dlightBits; + return dlightBits; +} + +static int R_DlightGrid( srfGridMesh_t *grid, int dlightBits ) { + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( !( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits[ tr.smpFrame ] = dlightBits; + return dlightBits; +} + + +static int R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ) { + // FIXME: more dlight culling to trisurfs... + surf->dlightBits[ tr.smpFrame ] = dlightBits; + return dlightBits; +#if 0 + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( !( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits[ tr.smpFrame ] = dlightBits; + return dlightBits; +#endif +} + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +static int R_DlightSurface( msurface_t *surf, int dlightBits ) { + if ( *surf->data == SF_FACE ) { + dlightBits = R_DlightFace( (srfSurfaceFace_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_GRID ) { + dlightBits = R_DlightGrid( (srfGridMesh_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_TRIANGLES ) { + dlightBits = R_DlightTrisurf( (srfTriangles_t *)surf->data, dlightBits ); + } else { + dlightBits = 0; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } + + return dlightBits; +} + + + +/* +====================== +R_AddWorldSurface +====================== +*/ +static void R_AddWorldSurface( msurface_t *surf, shader_t *shader, int dlightBits ) { + if ( surf->viewCount == tr.viewCount ) { + return; // already in this view + } + + surf->viewCount = tr.viewCount; + // FIXME: bmodel fog? + + // try to cull before dlighting or adding + if ( R_CullSurface( surf->data, shader ) ) { + return; + } + + // check for dlighting + if ( dlightBits ) { + dlightBits = R_DlightSurface( surf, dlightBits ); + dlightBits = ( dlightBits != 0 ); + } + + R_AddDrawSurf( surf->data, shader, surf->fogIndex, dlightBits ); +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +//----(SA) added + +/* +================= +R_BmodelFogNum + +See if a sprite is inside a fog volume +Return positive with /any part/ of the brush falling within a fog volume +================= +*/ +int R_BmodelFogNum( trRefEntity_t *re, bmodel_t *bmodel ) { + int i, j; + fog_t *fog; + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( re->e.origin[j] + bmodel->bounds[0][j] > fog->bounds[1][j] ) { + break; + } + if ( re->e.origin[j] + bmodel->bounds[0][j] < fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + for ( j = 0 ; j < 3 ; j++ ) { + if ( re->e.origin[j] + bmodel->bounds[1][j] > fog->bounds[1][j] ) { + break; + } + if ( bmodel->bounds[1][j] < fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +//----(SA) done + + +/* +================= +R_AddBrushModelSurfaces +================= +*/ +void R_AddBrushModelSurfaces( trRefEntity_t *ent ) { + bmodel_t *bmodel; + int clip; + model_t *pModel; + int i; + int fognum; + + pModel = R_GetModelByHandle( ent->e.hModel ); + + bmodel = pModel->bmodel; + + clip = R_CullLocalBox( bmodel->bounds ); + if ( clip == CULL_OUT ) { + return; + } + + R_DlightBmodel( bmodel ); + +//----(SA) modified + // determine if in fog + fognum = R_BmodelFogNum( ent, bmodel ); + + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + ( bmodel->firstSurface + i )->fogIndex = fognum; + // Arnout: custom shader support for brushmodels + if ( ent->e.customShader ) { + R_AddWorldSurface( bmodel->firstSurface + i, R_GetShaderByHandle( ent->e.customShader ), tr.currentEntity->needDlights ); + } else { + R_AddWorldSurface( bmodel->firstSurface + i, ( ( msurface_t * )( bmodel->firstSurface + i ) )->shader, tr.currentEntity->needDlights ); + } + } +//----(SA) end +} + + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ + + +/* +================ +R_RecursiveWorldNode +================ +*/ +static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ) { + + do { + int newDlights[2]; + + // if the node wasn't marked as potentially visible, exit + if ( node->visframe != tr.visCount ) { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + + if ( !r_nocull->integer ) { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide( node->mins, node->maxs, &tr.viewParms.frustum[0] ); + if ( r == 2 ) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide( node->mins, node->maxs, &tr.viewParms.frustum[1] ); + if ( r == 2 ) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide( node->mins, node->maxs, &tr.viewParms.frustum[2] ); + if ( r == 2 ) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide( node->mins, node->maxs, &tr.viewParms.frustum[3] ); + if ( r == 2 ) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + } + + if ( node->contents != -1 ) { + break; + } + + // node is just a decision point, so go down both sides + // since we don't care about sort orders, just go positive to negative + // determine which dlights are needed + newDlights[0] = 0; + newDlights[1] = 0; +/* +// if ( dlightBits ) + { + int i; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + dlight_t *dl; + float dist; + +// if ( dlightBits & ( 1 << i ) ) { + dl = &tr.refdef.dlights[i]; + dist = DotProduct( dl->origin, node->plane->normal ) - node->plane->dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } +// } + } + } +*/ + // recurse down the children, front side first + R_RecursiveWorldNode( node->children[0], planeBits, newDlights[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + msurface_t *surf, **mark; + + // RF, hack, dlight elimination above is unreliable + dlightBits = 0xffffffff; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add the individual surfaces + mark = node->firstmarksurface; + c = node->nummarksurfaces; + while ( c-- ) { + // the surface may have already been added if it + // spans multiple leafs + surf = *mark; + R_AddWorldSurface( surf, surf->shader, dlightBits ); + mark++; + } + } + +} + + +/* +=============== +R_PointInLeaf +=============== +*/ +static mnode_t *R_PointInLeaf( vec3_t p ) { + mnode_t *node; + float d; + cplane_t *plane; + + if ( !tr.world ) { + ri.Error( ERR_DROP, "R_PointInLeaf: bad model" ); + } + + node = tr.world->nodes; + while ( 1 ) { + if ( node->contents != -1 ) { + break; + } + plane = node->plane; + d = DotProduct( p,plane->normal ) - plane->dist; + if ( d > 0 ) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + + return node; +} + +/* +============== +R_ClusterPVS +============== +*/ +static const byte *R_ClusterPVS( int cluster ) { + if ( !tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) { + return tr.world->novis; + } + + return tr.world->vis + cluster * tr.world->clusterBytes; +} + + + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current +cluster +=============== +*/ +static void R_MarkLeaves( void ) { + const byte *vis; + mnode_t *leaf, *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + leaf = R_PointInLeaf( tr.viewParms.pvsOrigin ); + cluster = leaf->cluster; + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + // if r_showcluster was just turned on, remark everything + if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified + && !r_showcluster->modified ) { + return; + } + + if ( r_showcluster->modified || r_showcluster->integer ) { + r_showcluster->modified = qfalse; + if ( r_showcluster->integer ) { + ri.Printf( PRINT_ALL, "cluster:%i area:%i\n", cluster, leaf->area ); + } + } + + tr.visCount++; + tr.viewCluster = cluster; + + if ( r_novis->integer || tr.viewCluster == -1 ) { + for ( i = 0 ; i < tr.world->numnodes ; i++ ) { + if ( tr.world->nodes[i].contents != CONTENTS_SOLID ) { + tr.world->nodes[i].visframe = tr.visCount; + } + } + return; + } + + vis = R_ClusterPVS( tr.viewCluster ); + + for ( i = 0,leaf = tr.world->nodes ; i < tr.world->numnodes ; i++, leaf++ ) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( !( vis[cluster >> 3] & ( 1 << ( cluster & 7 ) ) ) ) { + continue; + } + + // check for door connection + if ( ( tr.refdef.areamask[leaf->area >> 3] & ( 1 << ( leaf->area & 7 ) ) ) ) { + continue; // not visible + } + + parent = leaf; + do { + if ( parent->visframe == tr.visCount ) { + break; + } + parent->visframe = tr.visCount; + parent = parent->parent; + } while ( parent ); + } +} + + +/* +============= +R_AddWorldSurfaces +============= +*/ +void R_AddWorldSurfaces( void ) { + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = ENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // determine which leaves are in the PVS / areamask + R_MarkLeaves(); + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and add all the potentially visible surfaces + if ( tr.refdef.num_dlights > 32 ) { + tr.refdef.num_dlights = 32 ; + } + R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << tr.refdef.num_dlights ) - 1 ); +} diff --git a/src/server/server.h b/src/server/server.h new file mode 100644 index 0000000..613b413 --- /dev/null +++ b/src/server/server.h @@ -0,0 +1,463 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// server.h + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../game/g_public.h" +#include "../game/bg_public.h" + +//============================================================================= + +#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND + // GAME BOTH REFERENCE !!! + +#define MAX_ENT_CLUSTERS 16 + +#define MAX_BPS_WINDOW 20 // NERVE - SMF - net debugging + +typedef struct svEntity_s { + struct worldSector_s *worldSector; + struct svEntity_s *nextEntityInWorldSector; + + entityState_t baseline; // for delta compression of initial sighting + int numClusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int lastCluster; // if all the clusters don't fit in clusternums + int areanum, areanum2; + int snapshotCounter; // used to prevent double adding from portal views +} svEntity_t; + +typedef enum { + SS_DEAD, // no map loaded + SS_LOADING, // spawning level entities + SS_GAME // actively running +} serverState_t; + +typedef struct { + serverState_t state; + qboolean restarting; // if true, send configstring changes during SS_LOADING + int serverId; // changes each server start + int restartedServerId; // serverId before a map_restart + int checksumFeed; // the feed key that we use to compute the pure checksum strings + // show_bug.cgi?id=475 + // the serverId associated with the current checksumFeed (always <= serverId) + int checksumFeedServerId; + int snapshotCounter; // incremented for each snapshot built + int timeResidual; // <= 1000 / sv_frame->value + int nextFrameTime; // when time > nextFrameTime, process world + struct cmodel_s *models[MAX_MODELS]; + char *configstrings[MAX_CONFIGSTRINGS]; + svEntity_t svEntities[MAX_GENTITIES]; + + char *entityParsePoint; // used during game VM init + + // the game virtual machine will update these on init and changes + sharedEntity_t *gentities; + int gentitySize; + int num_entities; // current number, <= MAX_GENTITIES + + playerState_t *gameClients; + int gameClientSize; // will be > sizeof(playerState_t) due to game private data + + int restartTime; + + // NERVE - SMF - net debugging + int bpsWindow[MAX_BPS_WINDOW]; + int bpsWindowSteps; + int bpsTotalBytes; + int bpsMaxBytes; + + int ubpsWindow[MAX_BPS_WINDOW]; + int ubpsTotalBytes; + int ubpsMaxBytes; + + float ucompAve; + int ucompNum; + // -NERVE - SMF +} server_t; + + + + + +typedef struct { + int areabytes; + byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + playerState_t ps; + int num_entities; + int first_entity; // into the circular sv_packet_entities[] + // the entities MUST be in increasing state number + // order, otherwise the delta compression will fail + int messageSent; // time the message was transmitted + int messageAcked; // time the message was acked + int messageSize; // used to rate drop packets +} clientSnapshot_t; + +typedef enum { + CS_FREE, // can be reused for a new connection + CS_ZOMBIE, // client has been disconnected, but don't reuse connection for a couple seconds + CS_CONNECTED, // has been assigned to a client_t, but no gamestate yet + CS_PRIMED, // gamestate has been sent, but client hasn't sent a usercmd + CS_ACTIVE // client is fully in game +} clientState_t; + +typedef struct netchan_buffer_s { + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + struct netchan_buffer_s *next; +} netchan_buffer_t; + +typedef struct client_s { + clientState_t state; + char userinfo[MAX_INFO_STRING]; // name, etc + + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet + int reliableAcknowledge; // last acknowledged reliable message + int reliableSent; // last sent reliable message, not necesarily acknowledged yet + int messageAcknowledge; + + int gamestateMessageNum; // netchan->outgoingSequence of gamestate + int challenge; + + usercmd_t lastUsercmd; + int lastMessageNum; // for delta compression + int lastClientCommand; // reliable client message sequence + char lastClientCommandString[MAX_STRING_CHARS]; + sharedEntity_t *gentity; // SV_GentityNum(clientnum) + char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked + + // downloading + char downloadName[MAX_QPATH]; // if not empty string, we are downloading + fileHandle_t download; // file being downloaded + int downloadSize; // total bytes (can't use EOF because of paks) + int downloadCount; // bytes sent + int downloadClientBlock; // last block we sent to the client, awaiting ack + int downloadCurrentBlock; // current block number + int downloadXmitBlock; // last block we xmited + unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks + int downloadBlockSize[MAX_DOWNLOAD_WINDOW]; + qboolean downloadEOF; // We have sent the EOF block + int downloadSendTime; // time we last got an ack from the client + + int deltaMessage; // frame last client usercmd message + int nextReliableTime; // svs.time when another reliable command will be allowed + int lastPacketTime; // svs.time when packet was last received + int lastConnectTime; // svs.time when connection started + int nextSnapshotTime; // send another snapshot when svs.time >= nextSnapshotTime + qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec + int timeoutCount; // must timeout a few frames in a row so debugging doesn't break + clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here + int ping; + int rate; // bytes / second + int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked + int pureAuthentic; + qboolean gotCP; // TTimo - additional flag to distinguish between a bad pure checksum, and no cp command at all + netchan_t netchan; + // TTimo + // queuing outgoing fragmented messages to send them properly, without udp packet bursts + // in case large fragmented messages are stacking up + // buffer them into this queue, and hand them out to netchan as needed + netchan_buffer_t *netchan_start_queue; + netchan_buffer_t **netchan_end_queue; +} client_t; + +//============================================================================= + + +// MAX_CHALLENGES is made large to prevent a denial +// of service attack that could cycle all of them +// out before legitimate users connected +#define MAX_CHALLENGES 1024 + +#define AUTHORIZE_TIMEOUT 5000 + +typedef struct { + netadr_t adr; + int challenge; + int time; // time the last packet was sent to the autherize server + int pingTime; // time the challenge response was sent to client + int firstTime; // time the adr was first used, for authorize timeout checks + int firstPing; // Used for min and max ping checks + qboolean connected; +} challenge_t; + + +#define MAX_MASTERS 8 // max recipients for heartbeat packets + + +// this structure will be cleared only when the game dll changes +typedef struct { + qboolean initialized; // sv_init has completed + + int time; // will be strictly increasing across level changes + + int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer() + + client_t *clients; // [sv_maxclients->integer]; + int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES + int nextSnapshotEntities; // next snapshotEntities to use + entityState_t *snapshotEntities; // [numSnapshotEntities] + int nextHeartbeatTime; + challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + netadr_t redirectAddress; // for rcon return messages + + netadr_t authorizeAddress; // for rcon return messages +} serverStatic_t; + +//================ +// DHM - Nerve +#ifdef UPDATE_SERVER + +typedef struct { + char version[MAX_QPATH]; + char platform[MAX_QPATH]; + char installer[MAX_QPATH]; +} versionMapping_t; + + +#define MAX_UPDATE_VERSIONS 128 +extern versionMapping_t versionMap[MAX_UPDATE_VERSIONS]; +extern int numVersions; +// Maps client version to appropriate installer + +#endif +// DHM - Nerve + +//============================================================================= + +extern serverStatic_t svs; // persistant server info across maps +extern server_t sv; // cleared each map +extern vm_t *gvm; // game virtual machine + + +#define MAX_MASTER_SERVERS 5 + +extern cvar_t *sv_fps; +extern cvar_t *sv_timeout; +extern cvar_t *sv_zombietime; +extern cvar_t *sv_rconPassword; +extern cvar_t *sv_privatePassword; +extern cvar_t *sv_allowDownload; +extern cvar_t *sv_friendlyFire; // NERVE - SMF +extern cvar_t *sv_maxlives; // NERVE - SMF +extern cvar_t *sv_tourney; // NERVE - SMF +extern cvar_t *sv_maxclients; + +extern cvar_t *sv_privateClients; +extern cvar_t *sv_hostname; +extern cvar_t *sv_master[MAX_MASTER_SERVERS]; +extern cvar_t *sv_reconnectlimit; +extern cvar_t *sv_showloss; +extern cvar_t *sv_padPackets; +extern cvar_t *sv_killserver; +extern cvar_t *sv_mapname; +extern cvar_t *sv_mapChecksum; +extern cvar_t *sv_serverid; +extern cvar_t *sv_maxRate; +extern cvar_t *sv_minPing; +extern cvar_t *sv_maxPing; +extern cvar_t *sv_gametype; +extern cvar_t *sv_pure; +extern cvar_t *sv_floodProtect; +extern cvar_t *sv_allowAnonymous; +extern cvar_t *sv_lanForceRate; +extern cvar_t *sv_onlyVisibleClients; + +extern cvar_t *sv_showAverageBPS; // NERVE - SMF - net debugging + +// Rafael gameskill +extern cvar_t *sv_gameskill; +// done + +// TTimo - autodl +extern cvar_t *sv_dl_maxRate; + + +//=========================================================== + +// +// sv_main.c +// +void SV_FinalMessage( char *message ); +void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ... ); + + +void SV_AddOperatorCommands( void ); +void SV_RemoveOperatorCommands( void ); + + +void SV_MasterHeartbeat( const char *hbname ); +void SV_MasterShutdown( void ); + +void SV_MasterGameCompleteStatus(); // NERVE - SMF + + + +// +// sv_init.c +// +void SV_SetConfigstring( int index, const char *val ); +void SV_GetConfigstring( int index, char *buffer, int bufferSize ); + +void SV_SetUserinfo( int index, const char *val ); +void SV_GetUserinfo( int index, char *buffer, int bufferSize ); + +void SV_ChangeMaxClients( void ); +void SV_SpawnServer( char *server, qboolean killBots ); + + + +// +// sv_client.c +// +void SV_GetChallenge( netadr_t from ); + +void SV_DirectConnect( netadr_t from ); + +void SV_AuthorizeIpPacket( netadr_t from ); + +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); +void SV_UserinfoChanged( client_t *cl ); + +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); +void SV_DropClient( client_t *drop, const char *reason ); + +void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ); +void SV_ClientThink( client_t *cl, usercmd_t *cmd ); + +void SV_WriteDownloadToClient( client_t *cl, msg_t *msg ); + +// +// sv_ccmds.c +// +void SV_Heartbeat_f( void ); + +// +// sv_snapshot.c +// +void SV_AddServerCommand( client_t *client, const char *cmd ); +void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ); +void SV_WriteFrameToClient( client_t *client, msg_t *msg ); +void SV_SendMessageToClient( msg_t *msg, client_t *client ); +void SV_SendClientMessages( void ); +void SV_SendClientSnapshot( client_t *client ); + +// +// sv_game.c +// +int SV_NumForGentity( sharedEntity_t *ent ); +sharedEntity_t *SV_GentityNum( int num ); +playerState_t *SV_GameClientNum( int num ); +svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ); +sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ); +void SV_InitGameProgs( void ); +void SV_ShutdownGameProgs( void ); +void SV_RestartGameProgs( void ); +qboolean SV_inPVS( const vec3_t p1, const vec3_t p2 ); +qboolean SV_GetTag( int clientNum, char *tagname, orientation_t * or ); + +// +// sv_bot.c +// +void SV_BotFrame( int time ); +int SV_BotAllocateClient( void ); +void SV_BotFreeClient( int clientNum ); + +void SV_BotInitCvars( void ); +int SV_BotLibSetup( void ); +int SV_BotLibShutdown( void ); +int SV_BotGetSnapshotEntity( int client, int ent ); +int SV_BotGetConsoleMessage( int client, char *buf, int size ); + +int BotImport_DebugPolygonCreate( int color, int numPoints, vec3_t *points ); +void BotImport_DebugPolygonDelete( int id ); + +//============================================================ +// +// high level object sorting to reduce interaction tests +// + +void SV_ClearWorld( void ); +// called after the world model has been loaded, before linking any entities + +void SV_UnlinkEntity( sharedEntity_t *ent ); +// call before removing an entity, and before trying to move one, +// so it doesn't clip against itself + +void SV_LinkEntity( sharedEntity_t *ent ); +// Needs to be called any time an entity changes origin, mins, maxs, +// or solid. Automatically unlinks if needed. +// sets ent->v.absmin and ent->v.absmax +// sets ent->leafnums[] for pvs determination even if the entity +// is not solid + + +clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ); + + +void SV_SectorList_f( void ); + + +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ); +// fills in a table of entity numbers with entities that have bounding boxes +// that intersect the given area. It is possible for a non-axial bmodel +// to be returned that doesn't actually intersect the area on an exact +// test. +// returns the number of pointers filled in +// The world entity is never returned in this list. + + +int SV_PointContents( const vec3_t p, int passEntityNum ); +// returns the CONTENTS_* value from the world and all entities at the given point. + + +void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule ); +// mins and maxs are relative + +// if the entire move stays in a solid volume, trace.allsolid will be set, +// trace.startsolid will be set, and trace.fraction will be 0 + +// if the starting point is in a solid, it will be allowed to move out +// to an open area + +// passEntityNum is explicitly excluded from clipping checks (normally ENTITYNUM_NONE) + + +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ); +// clip to a specific entity + +// +// sv_net_chan.c +// +void SV_Netchan_Transmit( client_t *client, msg_t *msg ); +void SV_Netchan_TransmitNextFragment( client_t *client ); +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ); + diff --git a/src/server/sv_bot.c b/src/server/sv_bot.c new file mode 100644 index 0000000..5b375a0 --- /dev/null +++ b/src/server/sv_bot.c @@ -0,0 +1,709 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// sv_bot.c + +#include "server.h" +#include "../game/botlib.h" +#include "../botai/botai.h" + +#define MAX_DEBUGPOLYS 128 + +typedef struct bot_debugpoly_s +{ + int inuse; + int color; + int numPoints; + vec3_t points[128]; +} bot_debugpoly_t; + +static bot_debugpoly_t debugpolygons[MAX_DEBUGPOLYS]; + +extern botlib_export_t *botlib_export; +int bot_enable; + +/* +================== +SV_BotAllocateClient +================== +*/ +int SV_BotAllocateClient( void ) { + int i; + client_t *cl; + + // find a client slot + for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { + // Wolfenstein, never use the first slot, otherwise if a bot connects before the first client on a listen server, game won't start + if ( i < 1 ) { + continue; + } + // done. + if ( cl->state == CS_FREE ) { + break; + } + } + + if ( i == sv_maxclients->integer ) { + return -1; + } + + cl->gentity = SV_GentityNum( i ); + cl->gentity->s.number = i; + cl->state = CS_ACTIVE; + cl->lastPacketTime = svs.time; + cl->netchan.remoteAddress.type = NA_BOT; + cl->rate = 16384; + + return i; +} + +/* +================== +SV_BotFreeClient +================== +*/ +void SV_BotFreeClient( int clientNum ) { + client_t *cl; + + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_BotFreeClient: bad clientNum: %i", clientNum ); + } + cl = &svs.clients[clientNum]; + cl->state = CS_FREE; + cl->name[0] = 0; + if ( cl->gentity ) { + cl->gentity->r.svFlags &= ~SVF_BOT; + } +} + +/* +================== +BotDrawDebugPolygons +================== +*/ +void BotDrawDebugPolygons( void ( *drawPoly )( int color, int numPoints, float *points ), int value ) { + static cvar_t *bot_debug, *bot_groundonly, *bot_reachability, *bot_highlightarea; + static cvar_t *bot_testhidepos; + bot_debugpoly_t *poly; + int i, parm0; + +#ifdef PRE_RELEASE_DEMO + return; +#endif + + if ( !bot_enable ) { + return; + } + //bot debugging + if ( !bot_debug ) { + bot_debug = Cvar_Get( "bot_debug", "0", 0 ); + } + //show reachabilities + if ( !bot_reachability ) { + bot_reachability = Cvar_Get( "bot_reachability", "0", 0 ); + } + //show ground faces only + if ( !bot_groundonly ) { + bot_groundonly = Cvar_Get( "bot_groundonly", "1", 0 ); + } + //get the hightlight area + if ( !bot_highlightarea ) { + bot_highlightarea = Cvar_Get( "bot_highlightarea", "0", 0 ); + } + // + if ( !bot_testhidepos ) { + bot_testhidepos = Cvar_Get( "bot_testhidepos", "0", 0 ); + } + // + if ( bot_debug->integer ) { + parm0 = 0; + if ( svs.clients[0].lastUsercmd.buttons & BUTTON_ATTACK ) { + parm0 |= 1; + } + if ( bot_reachability->integer ) { + parm0 |= 2; + } + if ( bot_groundonly->integer ) { + parm0 |= 4; + } + botlib_export->BotLibVarSet( "bot_highlightarea", bot_highlightarea->string ); + botlib_export->BotLibVarSet( "bot_testhidepos", bot_testhidepos->string ); + botlib_export->Test( parm0, NULL, svs.clients[0].gentity->r.currentOrigin, + svs.clients[0].gentity->r.currentAngles ); + } //end if + for ( i = 0; i < MAX_DEBUGPOLYS; i++ ) { + poly = &debugpolygons[i]; + if ( !poly->inuse ) { + continue; + } + drawPoly( poly->color, poly->numPoints, (float *) poly->points ); + //Com_Printf("poly %i, numpoints = %d\n", i, poly->numPoints); + } +} + +/* +================== +BotImport_Print +================== +*/ +void QDECL BotImport_Print( int type, char *fmt, ... ) { + char str[2048]; + va_list ap; + + va_start( ap, fmt ); + Q_vsnprintf( str, sizeof( str ), fmt, ap ); + va_end( ap ); + + switch ( type ) { + case PRT_MESSAGE: { + Com_Printf( "%s", str ); + break; + } + case PRT_WARNING: { + Com_Printf( S_COLOR_YELLOW "Warning: %s", str ); + break; + } + case PRT_ERROR: { + Com_Printf( S_COLOR_RED "Error: %s", str ); + break; + } + case PRT_FATAL: { + Com_Printf( S_COLOR_RED "Fatal: %s", str ); + break; + } + case PRT_EXIT: { + Com_Error( ERR_DROP, S_COLOR_RED "Exit: %s", str ); + break; + } + default: { + Com_Printf( "unknown print type\n" ); + break; + } + } +} + +/* +================== +BotImport_Trace +================== +*/ +void BotImport_Trace( bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask ) { + trace_t trace; + + // always use bounding box for bot stuff ? + SV_Trace( &trace, start, mins, maxs, end, passent, contentmask, qfalse ); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy( trace.endpos, bsptrace->endpos ); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy( trace.plane.normal, bsptrace->plane.normal ); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + +/* +================== +BotImport_EntityTrace +================== +*/ +void BotImport_EntityTrace( bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask ) { + trace_t trace; + + // always use bounding box for bot stuff ? + SV_ClipToEntity( &trace, start, mins, maxs, end, entnum, contentmask, qfalse ); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy( trace.endpos, bsptrace->endpos ); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy( trace.plane.normal, bsptrace->plane.normal ); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + + +/* +================== +BotImport_PointContents +================== +*/ +int BotImport_PointContents( vec3_t point ) { + return SV_PointContents( point, -1 ); +} + +/* +================== +BotImport_inPVS +================== +*/ +int BotImport_inPVS( vec3_t p1, vec3_t p2 ) { + return SV_inPVS( p1, p2 ); +} + +/* +================== +BotImport_BSPEntityData +================== +*/ +char *BotImport_BSPEntityData( void ) { + return CM_EntityString(); +} + +/* +================== +BotImport_BSPModelMinsMaxsOrigin +================== +*/ +void BotImport_BSPModelMinsMaxsOrigin( int modelnum, vec3_t angles, vec3_t outmins, vec3_t outmaxs, vec3_t origin ) { + clipHandle_t h; + vec3_t mins, maxs; + float max; + int i; + + h = CM_InlineModel( modelnum ); + CM_ModelBounds( h, mins, maxs ); + //if the model is rotated + if ( ( angles[0] || angles[1] || angles[2] ) ) { + // expand for rotation + + max = RadiusFromBounds( mins, maxs ); + for ( i = 0; i < 3; i++ ) { + mins[i] = -max; + maxs[i] = max; + } + } + if ( outmins ) { + VectorCopy( mins, outmins ); + } + if ( outmaxs ) { + VectorCopy( maxs, outmaxs ); + } + if ( origin ) { + VectorClear( origin ); + } +} + +/* +================== +BotImport_GetMemory +================== +*/ +void *BotImport_GetMemory( int size ) { + void *ptr; + + ptr = Z_TagMalloc( size, TAG_BOTLIB ); + return ptr; +} + +/* +================== +BotImport_FreeMemory +================== +*/ +void BotImport_FreeMemory( void *ptr ) { + Z_Free( ptr ); +} + +/* +================== +BotImport_FreeZoneMemory +================== +*/ +void BotImport_FreeZoneMemory( void ) { + Z_FreeTags( TAG_BOTLIB ); +} + +/* +================= +BotImport_HunkAlloc +================= +*/ +void *BotImport_HunkAlloc( int size ) { + if ( Hunk_CheckMark() ) { + Com_Error( ERR_DROP, "SV_Bot_HunkAlloc: Alloc with marks already set\n" ); + } + return Hunk_Alloc( size, h_high ); +} + +/* +================== +BotImport_DebugPolygonCreate +================== +*/ +int BotImport_DebugPolygonCreate( int color, int numPoints, vec3_t *points ) { + bot_debugpoly_t *poly; + int i; + + for ( i = 1; i < MAX_DEBUGPOLYS; i++ ) { + if ( !debugpolygons[i].inuse ) { + break; + } + } + if ( i >= MAX_DEBUGPOLYS ) { + return 0; + } + poly = &debugpolygons[i]; + poly->inuse = qtrue; + poly->color = color; + poly->numPoints = numPoints; + memcpy( poly->points, points, numPoints * sizeof( vec3_t ) ); + // + return i; +} + +/* +================== +BotImport_DebugPolygonShow +================== +*/ +void BotImport_DebugPolygonShow( int id, int color, int numPoints, vec3_t *points ) { + bot_debugpoly_t *poly; + + poly = &debugpolygons[id]; + poly->inuse = qtrue; + poly->color = color; + poly->numPoints = numPoints; + memcpy( poly->points, points, numPoints * sizeof( vec3_t ) ); +} + +/* +================== +BotImport_DebugPolygonDelete +================== +*/ +void BotImport_DebugPolygonDelete( int id ) { + debugpolygons[id].inuse = qfalse; +} + +/* +================== +BotImport_DebugLineCreate +================== +*/ +int BotImport_DebugLineCreate( void ) { + vec3_t points[1]; + return BotImport_DebugPolygonCreate( 0, 0, points ); +} + +/* +================== +BotImport_DebugLineDelete +================== +*/ +void BotImport_DebugLineDelete( int line ) { + BotImport_DebugPolygonDelete( line ); +} + +/* +================== +BotImport_DebugLineShow +================== +*/ +void BotImport_DebugLineShow( int line, vec3_t start, vec3_t end, int color ) { + vec3_t points[4], dir, cross, up = {0, 0, 1}; + float dot; + + VectorCopy( start, points[0] ); + VectorCopy( start, points[1] ); + //points[1][2] -= 2; + VectorCopy( end, points[2] ); + //points[2][2] -= 2; + VectorCopy( end, points[3] ); + + + VectorSubtract( end, start, dir ); + VectorNormalize( dir ); + dot = DotProduct( dir, up ); + if ( dot > 0.99 || dot < -0.99 ) { + VectorSet( cross, 1, 0, 0 ); + } else { CrossProduct( dir, up, cross );} + + VectorNormalize( cross ); + + VectorMA( points[0], 2, cross, points[0] ); + VectorMA( points[1], -2, cross, points[1] ); + VectorMA( points[2], -2, cross, points[2] ); + VectorMA( points[3], 2, cross, points[3] ); + + BotImport_DebugPolygonShow( line, color, 4, points ); +} + +/* +================== +SV_BotClientCommand +================== +*/ +void BotClientCommand( int client, char *command ) { + SV_ExecuteClientCommand( &svs.clients[client], command, qtrue ); +} + +/* +================== +SV_BotFrame +================== +*/ +void SV_BotFrame( int time ) { + +#ifdef PRE_RELEASE_DEMO + return; +#endif + + if ( !bot_enable ) { + return; + } + //NOTE: maybe the game is already shutdown + if ( !gvm ) { + return; + } + VM_Call( gvm, BOTAI_START_FRAME, time ); +} + +/* +=============== +SV_BotLibSetup +=============== +*/ +int SV_BotLibSetup( void ) { + +#ifdef PRE_RELEASE_DEMO + return 0; +#endif + + if ( !bot_enable ) { + return 0; + } + + if ( !botlib_export ) { + Com_Printf( S_COLOR_RED "Error: SV_BotLibSetup without SV_BotInitBotLib\n" ); + return -1; + } + + return botlib_export->BotLibSetup(); +} + +/* +=============== +SV_ShutdownBotLib + +Called when either the entire server is being killed, or +it is changing to a different game directory. +=============== +*/ +int SV_BotLibShutdown( void ) { + + if ( !botlib_export ) { + return -1; + } + + return botlib_export->BotLibShutdown(); +} + +/* +================== +SV_BotInitCvars +================== +*/ +void SV_BotInitCvars( void ) { + + // DHM - Nerve :: bot_enable defaults to 0 + Cvar_Get( "bot_enable", "0", 0 ); //enable the bot + Cvar_Get( "bot_developer", "0", 0 ); //bot developer mode + Cvar_Get( "bot_debug", "0", 0 ); //enable bot debugging + Cvar_Get( "bot_groundonly", "1", 0 ); //only show ground faces of areas + Cvar_Get( "bot_reachability", "0", 0 ); //show all reachabilities to other areas + Cvar_Get( "bot_thinktime", "100", 0 ); //msec the bots thinks + Cvar_Get( "bot_reloadcharacters", "0", 0 ); //reload the bot characters each time + Cvar_Get( "bot_testichat", "0", 0 ); //test ichats + Cvar_Get( "bot_testrchat", "0", 0 ); //test rchats + Cvar_Get( "bot_fastchat", "0", 0 ); //fast chatting bots + Cvar_Get( "bot_nochat", "0", 0 ); //disable chats + Cvar_Get( "bot_grapple", "0", 0 ); //enable grapple + Cvar_Get( "bot_rocketjump", "1", 0 ); //enable rocket jumping + Cvar_Get( "bot_miniplayers", "0", 0 ); //minimum players in a team or the game +} + +// Ridah, Cast AI +/* +=============== +BotImport_AICast_VisibleFromPos +=============== +*/ +qboolean BotImport_AICast_VisibleFromPos( vec3_t srcpos, int srcnum, + vec3_t destpos, int destnum, qboolean updateVisPos ) { + return VM_Call( gvm, AICAST_VISIBLEFROMPOS, (int)srcpos, srcnum, (int)destpos, destnum, updateVisPos ); +} + +/* +=============== +BotImport_AICast_CheckAttackAtPos +=============== +*/ +qboolean BotImport_AICast_CheckAttackAtPos( int entnum, int enemy, vec3_t pos, qboolean ducking, qboolean allowHitWorld ) { + return VM_Call( gvm, AICAST_CHECKATTACKATPOS, entnum, enemy, (int)pos, ducking, allowHitWorld ); +} +// done. + +/* +================== +SV_BotInitBotLib +================== +*/ +void SV_BotInitBotLib( void ) { + botlib_import_t botlib_import; + +#if COPY_PROTECT + if ( !Cvar_VariableValue( "fs_restrict" ) && !Sys_CheckCD() ) { + Com_Error( ERR_NEED_CD, "Game CD not in drive" ); + } +#else + Com_Printf( "Bypassing CD checks\n" ); +#endif + + botlib_import.Print = BotImport_Print; + botlib_import.Trace = BotImport_Trace; + botlib_import.EntityTrace = BotImport_EntityTrace; + botlib_import.PointContents = BotImport_PointContents; + botlib_import.inPVS = BotImport_inPVS; + botlib_import.BSPEntityData = BotImport_BSPEntityData; + botlib_import.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin; + botlib_import.BotClientCommand = BotClientCommand; + + //memory management + botlib_import.GetMemory = BotImport_GetMemory; + botlib_import.FreeMemory = BotImport_FreeMemory; + botlib_import.FreeZoneMemory = BotImport_FreeZoneMemory; + botlib_import.HunkAlloc = BotImport_HunkAlloc; + + // file system acess + botlib_import.FS_FOpenFile = FS_FOpenFileByMode; + botlib_import.FS_Read = FS_Read; + botlib_import.FS_Write = FS_Write; + botlib_import.FS_FCloseFile = FS_FCloseFile; + botlib_import.FS_Seek = FS_Seek; + + //debug lines + botlib_import.DebugLineCreate = BotImport_DebugLineCreate; + botlib_import.DebugLineDelete = BotImport_DebugLineDelete; + botlib_import.DebugLineShow = BotImport_DebugLineShow; + + //debug polygons + botlib_import.DebugPolygonCreate = BotImport_DebugPolygonCreate; + botlib_import.DebugPolygonDelete = BotImport_DebugPolygonDelete; + + // Ridah, Cast AI + botlib_import.AICast_VisibleFromPos = BotImport_AICast_VisibleFromPos; + botlib_import.AICast_CheckAttackAtPos = BotImport_AICast_CheckAttackAtPos; + // done. + + botlib_export = (botlib_export_t *)GetBotLibAPI( BOTLIB_API_VERSION, &botlib_import ); +} + + +// +// * * * BOT AI CODE IS BELOW THIS POINT * * * +// + +/* +================== +SV_BotGetConsoleMessage +================== +*/ +int SV_BotGetConsoleMessage( int client, char *buf, int size ) { + client_t *cl; + int index; + + cl = &svs.clients[client]; + cl->lastPacketTime = svs.time; + + if ( cl->reliableAcknowledge == cl->reliableSequence ) { + return qfalse; + } + + cl->reliableAcknowledge++; + index = cl->reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 ); + + if ( !cl->reliableCommands[index][0] ) { + return qfalse; + } + + Q_strncpyz( buf, cl->reliableCommands[index], size ); + return qtrue; +} + +#if 0 +/* +================== +EntityInPVS +================== +*/ +int EntityInPVS( int client, int entityNum ) { + client_t *cl; + clientSnapshot_t *frame; + int i; + + cl = &svs.clients[client]; + frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; + for ( i = 0; i < frame->num_entities; i++ ) { + if ( svs.snapshotEntities[( frame->first_entity + i ) % svs.numSnapshotEntities].number == entityNum ) { + return qtrue; + } + } + return qfalse; +} +#endif + +/* +================== +SV_BotGetSnapshotEntity +================== +*/ +int SV_BotGetSnapshotEntity( int client, int sequence ) { + client_t *cl; + clientSnapshot_t *frame; + + cl = &svs.clients[client]; + frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; + if ( sequence < 0 || sequence >= frame->num_entities ) { + return -1; + } + return svs.snapshotEntities[( frame->first_entity + sequence ) % svs.numSnapshotEntities].number; +} + diff --git a/src/server/sv_ccmds.c b/src/server/sv_ccmds.c new file mode 100644 index 0000000..75e60ce --- /dev/null +++ b/src/server/sv_ccmds.c @@ -0,0 +1,967 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "server.h" + +/* +=============================================================================== + +OPERATOR CONSOLE ONLY COMMANDS + +These commands can only be entered from stdin or by a remote operator datagram +=============================================================================== +*/ + + +/* +================== +SV_GetPlayerByName + +Returns the player with name from Cmd_Argv(1) +================== +*/ +static client_t *SV_GetPlayerByName( void ) { + client_t *cl; + int i; + char *s; + char cleanName[64]; + + // make sure server is running + if ( !com_sv_running->integer ) { + return NULL; + } + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "No player specified.\n" ); + return NULL; + } + + s = Cmd_Argv( 1 ); + + // check for a name match + for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( !cl->state ) { + continue; + } + if ( !Q_stricmp( cl->name, s ) ) { + return cl; + } + + Q_strncpyz( cleanName, cl->name, sizeof( cleanName ) ); + Q_CleanStr( cleanName ); + if ( !Q_stricmp( cleanName, s ) ) { + return cl; + } + } + + Com_Printf( "Player %s is not on the server\n", s ); + + return NULL; +} + +/* +================== +SV_GetPlayerByNum + +Returns the player with idnum from Cmd_Argv(1) +================== +*/ +static client_t *SV_GetPlayerByNum( void ) { + client_t *cl; + int i; + int idnum; + char *s; + + // make sure server is running + if ( !com_sv_running->integer ) { + return NULL; + } + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "No player specified.\n" ); + return NULL; + } + + s = Cmd_Argv( 1 ); + + for ( i = 0; s[i]; i++ ) { + if ( s[i] < '0' || s[i] > '9' ) { + Com_Printf( "Bad slot number: %s\n", s ); + return NULL; + } + } + idnum = atoi( s ); + if ( idnum < 0 || idnum >= sv_maxclients->integer ) { + Com_Printf( "Bad client slot: %i\n", idnum ); + return NULL; + } + + cl = &svs.clients[idnum]; + if ( !cl->state ) { + Com_Printf( "Client %i is not active\n", idnum ); + return NULL; + } + return cl; + + return NULL; +} + +//========================================================= + + +/* +================== +SV_Map_f + +Restart the server on a different map +================== +*/ +static void SV_Map_f( void ) { + char *cmd; + char *map; + char mapname[MAX_QPATH]; + qboolean killBots, cheat; + char expanded[MAX_QPATH]; + // TTimo: unused +// int savegameTime = -1; + + map = Cmd_Argv( 1 ); + if ( !map ) { + return; + } + + // make sure the level exists before trying to change, so that + // a typo at the server console won't end the game + Com_sprintf( expanded, sizeof( expanded ), "maps/%s.bsp", map ); + if ( FS_ReadFile( expanded, NULL ) == -1 ) { + Com_Printf( "Can't find map %s\n", expanded ); + return; + } + + Cvar_Set( "gamestate", va( "%i", GS_INITIALIZE ) ); // NERVE - SMF - reset gamestate on map/devmap + Cvar_Set( "savegame_loading", "0" ); // make sure it's turned off + + Cvar_Set( "g_currentRound", "0" ); // NERVE - SMF - reset the current round + Cvar_Set( "g_nextTimeLimit", "0" ); // NERVE - SMF - reset the next time limit + + // force latched values to get set + // DHM - Nerve :: default to GT_WOLF + Cvar_Get( "g_gametype", "5", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH ); + + // Rafael gameskill + Cvar_Get( "g_gameskill", "3", CVAR_SERVERINFO | CVAR_LATCH ); + // done + + cmd = Cmd_Argv( 0 ); + if ( Q_stricmpn( cmd, "sp", 2 ) == 0 ) { + Cvar_SetValue( "g_gametype", GT_SINGLE_PLAYER ); + Cvar_SetValue( "g_doWarmup", 0 ); + // may not set sv_maxclients directly, always set latched +#ifdef __MACOS__ + Cvar_SetLatched( "sv_maxclients", "16" ); //DAJ HOG +#else + Cvar_SetLatched( "sv_maxclients", "32" ); // Ridah, modified this +#endif + cmd += 2; + killBots = qtrue; + if ( !Q_stricmp( cmd, "devmap" ) ) { + cheat = qtrue; + } else { + cheat = qfalse; + } + } else { + if ( !Q_stricmp( cmd, "devmap" ) ) { + cheat = qtrue; + killBots = qtrue; + } else { + cheat = qfalse; + killBots = qfalse; + } + if ( sv_gametype->integer == GT_SINGLE_PLAYER ) { + Cvar_SetValue( "g_gametype", GT_FFA ); + } + } + + // save the map name here cause on a map restart we reload the q3config.cfg + // and thus nuke the arguments of the map command + Q_strncpyz( mapname, map, sizeof( mapname ) ); + + // start up the map + SV_SpawnServer( mapname, killBots ); + + // set the cheat value + // if the level was started with "map ", then + // cheats will not be allowed. If started with "devmap " + // then cheats will be allowed + if ( cheat ) { + Cvar_Set( "sv_cheats", "1" ); + } else { + Cvar_Set( "sv_cheats", "0" ); + } +} + +/* +================ +SV_CheckTransitionGameState + +NERVE - SMF +================ +*/ +static qboolean SV_CheckTransitionGameState( gamestate_t new_gs, gamestate_t old_gs ) { + if ( old_gs == new_gs && new_gs != GS_PLAYING ) { + return qfalse; + } + +// if ( old_gs == GS_WARMUP && new_gs != GS_WARMUP_COUNTDOWN ) +// return qfalse; + +// if ( old_gs == GS_WARMUP_COUNTDOWN && new_gs != GS_PLAYING ) +// return qfalse; + + if ( old_gs == GS_WAITING_FOR_PLAYERS && new_gs != GS_WARMUP ) { + return qfalse; + } + + if ( old_gs == GS_INTERMISSION && new_gs != GS_WARMUP ) { + return qfalse; + } + + if ( old_gs == GS_RESET && ( new_gs != GS_WAITING_FOR_PLAYERS && new_gs != GS_WARMUP ) ) { + return qfalse; + } + + return qtrue; +} + +/* +================ +SV_TransitionGameState + +NERVE - SMF +================ +*/ +static qboolean SV_TransitionGameState( gamestate_t new_gs, gamestate_t old_gs, int delay ) { + // we always do a warmup before starting match + if ( old_gs == GS_INTERMISSION && new_gs == GS_PLAYING ) { + new_gs = GS_WARMUP; + } + + // check if its a valid state transition + if ( !SV_CheckTransitionGameState( new_gs, old_gs ) ) { + return qfalse; + } + + if ( new_gs == GS_RESET ) { + if ( atoi( Cvar_VariableString( "g_noTeamSwitching" ) ) ) { + new_gs = GS_WAITING_FOR_PLAYERS; + } else { + new_gs = GS_WARMUP; + } + } + + Cvar_Set( "gamestate", va( "%i", new_gs ) ); + + return qtrue; +} + +/* +================ +SV_MapRestart_f + +Completely restarts a level, but doesn't send a new gamestate to the clients. +This allows fair starts with variable load times. +================ +*/ +static void SV_MapRestart_f( void ) { + int i; + client_t *client; + char *denied; + qboolean isBot; + int delay = 0; + gamestate_t new_gs, old_gs; // NERVE - SMF + int worldspawnflags; // DHM - Nerve + int nextgt; // DHM - Nerve + sharedEntity_t *world; + + // make sure we aren't restarting twice in the same frame + if ( com_frameTime == sv.serverId ) { + return; + } + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( sv.restartTime ) { + return; + } + + // DHM - Nerve :: Check for invalid gametype + sv_gametype = Cvar_Get( "g_gametype", "5", CVAR_SERVERINFO | CVAR_LATCH ); + nextgt = sv_gametype->integer; + + world = SV_GentityNum( ENTITYNUM_WORLD ); + worldspawnflags = world->r.worldflags; + if ( + ( nextgt == GT_WOLF && ( worldspawnflags & 1 ) ) || + ( nextgt == GT_WOLF_STOPWATCH && ( worldspawnflags & 2 ) ) || + ( ( nextgt == GT_WOLF_CP || nextgt == GT_WOLF_CPH ) && ( worldspawnflags & 4 ) ) + ) { + + if ( !( worldspawnflags & 1 ) ) { + Cvar_Set( "g_gametype", "5" ); + } else { + Cvar_Set( "g_gametype", "7" ); + } + + sv_gametype = Cvar_Get( "g_gametype", "5", CVAR_SERVERINFO | CVAR_LATCH ); + } + // dhm + + if ( Cmd_Argc() > 1 ) { + delay = atoi( Cmd_Argv( 1 ) ); + } + + if ( delay ) { + sv.restartTime = svs.time + delay * 1000; + SV_SetConfigstring( CS_WARMUP, va( "%i", sv.restartTime ) ); + return; + } + + // NERVE - SMF - read in gamestate or just default to GS_PLAYING + old_gs = atoi( Cvar_VariableString( "gamestate" ) ); + + if ( Cmd_Argc() > 2 ) { + new_gs = atoi( Cmd_Argv( 2 ) ); + } else { + new_gs = GS_PLAYING; + } + + if ( !SV_TransitionGameState( new_gs, old_gs, delay ) ) { + return; + } + + // check for changes in variables that can't just be restarted + // check for maxclients change + if ( sv_maxclients->modified ) { + char mapname[MAX_QPATH]; + + Com_Printf( "sv_maxclients variable change -- restarting.\n" ); + // restart the map the slow way + Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) ); + + SV_SpawnServer( mapname, qfalse ); + return; + } + + // toggle the server bit so clients can detect that a + // map_restart has happened + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + // generate a new serverid + // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart + sv.serverId = com_frameTime; + Cvar_Set( "sv_serverid", va( "%i", sv.serverId ) ); + + // reset all the vm data in place without changing memory allocation + // note that we do NOT set sv.state = SS_LOADING, so configstrings that + // had been changed from their default values will generate broadcast updates + sv.state = SS_LOADING; + sv.restarting = qtrue; + + Cvar_Set( "sv_serverRestarting", "1" ); + + SV_RestartGameProgs(); + + // run a few frames to allow everything to settle + for ( i = 0 ; i < 3 ; i++ ) { + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + svs.time += 100; + } + + sv.state = SS_GAME; + sv.restarting = qfalse; + + // connect and begin all the clients + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + client = &svs.clients[i]; + + // send the new gamestate to all connected clients + if ( client->state < CS_CONNECTED ) { + continue; + } + + if ( client->netchan.remoteAddress.type == NA_BOT ) { + isBot = qtrue; + } else { + isBot = qfalse; + } + + // add the map_restart command + SV_AddServerCommand( client, "map_restart\n" ); + + // connect the client again, without the firstTime flag + denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( client, denied ); + Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i ); // bk010125 + continue; + } + + client->state = CS_ACTIVE; + + SV_ClientEnterWorld( client, &client->lastUsercmd ); + } + + // run another frame to allow things to look at all the players + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + svs.time += 100; + + Cvar_Set( "sv_serverRestarting", "0" ); +} + +/* +================= +SV_LoadGame_f +================= +*/ +void SV_LoadGame_f( void ) { + char filename[MAX_QPATH], mapname[MAX_QPATH]; + byte *buffer; + int size; + + Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) ); + if ( !filename[0] ) { + Com_Printf( "You must specify a savegame to load\n" ); + return; + } + if ( Q_strncmp( filename, "save/", 5 ) && Q_strncmp( filename, "save\\", 5 ) ) { + Q_strncpyz( filename, va( "save/%s", filename ), sizeof( filename ) ); + } + if ( !strstr( filename, ".svg" ) ) { + Q_strcat( filename, sizeof( filename ), ".svg" ); + } + + size = FS_ReadFile( filename, NULL ); + if ( size < 0 ) { + Com_Printf( "Can't find savegame %s\n", filename ); + return; + } + + buffer = Hunk_AllocateTempMemory( size ); + FS_ReadFile( filename, (void **)&buffer ); + + // read the mapname, if it is the same as the current map, then do a fast load + Com_sprintf( mapname, sizeof( mapname ), buffer + sizeof( int ) ); + + if ( com_sv_running->integer && ( com_frameTime != sv.serverId ) ) { + // check mapname + if ( !Q_stricmp( mapname, sv_mapname->string ) ) { // same + + if ( Q_stricmp( filename, "save/current.svg" ) != 0 ) { + // copy it to the current savegame file + FS_WriteFile( "save/current.svg", buffer, size ); + } + + Hunk_FreeTempMemory( buffer ); + + Cvar_Set( "savegame_loading", "2" ); // 2 means it's a restart, so stop rendering until we are loaded + SV_MapRestart_f(); // savegame will be loaded after restart + + return; + } + } + + Hunk_FreeTempMemory( buffer ); + + // otherwise, do a slow load + if ( Cvar_VariableIntegerValue( "sv_cheats" ) ) { + Cbuf_ExecuteText( EXEC_APPEND, va( "spdevmap %s", filename ) ); + } else { // no cheats + Cbuf_ExecuteText( EXEC_APPEND, va( "spmap %s", filename ) ); + } +} + +//=============================================================== + +/* +================== +SV_Kick_f + +Kick a user off of the server FIXME: move to game +================== +*/ +static void SV_Kick_f( void ) { + client_t *cl; + int i; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc() != 2 ) { +// Com_Printf ("Usage: kick \nkick all = kick everyone\nkick allbots = kick all bots\n"); + Com_Printf( "Usage: kick \nkick all = kick everyone\n" ); + return; + } + + cl = SV_GetPlayerByName(); + if ( !cl ) { + if ( !Q_stricmp( Cmd_Argv( 1 ), "all" ) ) { + for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( !cl->state ) { + continue; + } + if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + continue; + } + SV_DropClient( cl, "player kicked" ); // JPW NERVE to match front menu message + cl->lastPacketTime = svs.time; // in case there is a funny zombie + } + } else if ( !Q_stricmp( Cmd_Argv( 1 ), "allbots" ) ) { + for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( !cl->state ) { + continue; + } + if ( cl->netchan.remoteAddress.type != NA_BOT ) { + continue; + } + SV_DropClient( cl, "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie + } + } + return; + } + if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + SV_SendServerCommand( NULL, "print \"%s\"", "Cannot kick host player\n" ); + return; + } + + SV_DropClient( cl, "player kicked" ); // JPW NERVE to match front menu message + cl->lastPacketTime = svs.time; // in case there is a funny zombie +} + +/* +================== +SV_Ban_f + +Ban a user from being able to play on this server through the auth +server +================== +*/ +static void SV_Ban_f( void ) { + client_t *cl; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: banUser \n" ); + return; + } + + cl = SV_GetPlayerByName(); + + if ( !cl ) { + return; + } + + if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + SV_SendServerCommand( NULL, "print \"%s\"", "Cannot kick host player\n" ); + return; + } + + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], + cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); + Com_Printf( "%s was banned from coming back\n", cl->name ); + } +} + +/* +================== +SV_BanNum_f + +Ban a user from being able to play on this server through the auth +server +================== +*/ +static void SV_BanNum_f( void ) { + client_t *cl; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: banClient \n" ); + return; + } + + cl = SV_GetPlayerByNum(); + if ( !cl ) { + return; + } + if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + SV_SendServerCommand( NULL, "print \"%s\"", "Cannot kick host player\n" ); + return; + } + + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], + cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); + Com_Printf( "%s was banned from coming back\n", cl->name ); + } +} + +/* +================== +SV_KickNum_f + +Kick a user off of the server FIXME: move to game +================== +*/ +static void SV_KickNum_f( void ) { + client_t *cl; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: kicknum \n" ); + return; + } + + cl = SV_GetPlayerByNum(); + if ( !cl ) { + return; + } + if ( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + SV_SendServerCommand( NULL, "print \"%s\"", "Cannot kick host player\n" ); + return; + } + + SV_DropClient( cl, "player kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie +} + +/* +================ +SV_Status_f +================ +*/ +static void SV_Status_f( void ) { + int i, j, l; + client_t *cl; + playerState_t *ps; + const char *s; + int ping; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + Com_Printf( "map: %s\n", sv_mapname->string ); + + Com_Printf( "num score ping name lastmsg address qport rate\n" ); + Com_Printf( "--- ----- ---- --------------- ------- --------------------- ----- -----\n" ); + for ( i = 0,cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) + { + if ( !cl->state ) { + continue; + } + Com_Printf( "%3i ", i ); + ps = SV_GameClientNum( i ); + Com_Printf( "%5i ", ps->persistant[PERS_SCORE] ); + + if ( cl->state == CS_CONNECTED ) { + Com_Printf( "CNCT " ); + } else if ( cl->state == CS_ZOMBIE ) { + Com_Printf( "ZMBI " ); + } else + { + ping = cl->ping < 9999 ? cl->ping : 9999; + Com_Printf( "%4i ", ping ); + } + + Com_Printf( "%s", cl->name ); + l = 16 - strlen( cl->name ); + for ( j = 0 ; j < l ; j++ ) + Com_Printf( " " ); + + Com_Printf( "%7i ", svs.time - cl->lastPacketTime ); + + s = NET_AdrToString( cl->netchan.remoteAddress ); + Com_Printf( "%s", s ); + l = 22 - strlen( s ); + for ( j = 0 ; j < l ; j++ ) + Com_Printf( " " ); + + Com_Printf( "%5i", cl->netchan.qport ); + + Com_Printf( " %5i", cl->rate ); + + Com_Printf( "\n" ); + } + Com_Printf( "\n" ); +} + +/* +================== +SV_ConSay_f +================== +*/ +static void SV_ConSay_f( void ) { + char *p; + char text[1024]; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc() < 2 ) { + return; + } + + strcpy( text, "console: " ); + p = Cmd_Args(); + + if ( *p == '"' ) { + p++; + p[strlen( p ) - 1] = 0; + } + + strcat( text, p ); + + SV_SendServerCommand( NULL, "chat \"%s\n\"", text ); +} + + +/* +================== +SV_Heartbeat_f + +Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer +================== +*/ +void SV_Heartbeat_f( void ) { + svs.nextHeartbeatTime = -9999999; +} + + +/* +=========== +SV_Serverinfo_f + +Examine the serverinfo string +=========== +*/ +static void SV_Serverinfo_f( void ) { + Com_Printf( "Server info settings:\n" ); + Info_Print( Cvar_InfoString( CVAR_SERVERINFO ) ); +} + + +/* +=========== +SV_Systeminfo_f + +Examine or change the serverinfo string +=========== +*/ +static void SV_Systeminfo_f( void ) { + Com_Printf( "System info settings:\n" ); + Info_Print( Cvar_InfoString( CVAR_SYSTEMINFO ) ); +} + + +/* +=========== +SV_DumpUser_f + +Examine all a users info strings FIXME: move to game +=========== +*/ +static void SV_DumpUser_f( void ) { + client_t *cl; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: info \n" ); + return; + } + + cl = SV_GetPlayerByName(); + if ( !cl ) { + return; + } + + Com_Printf( "userinfo\n" ); + Com_Printf( "--------\n" ); + Info_Print( cl->userinfo ); +} + + +/* +================= +SV_KillServer +================= +*/ +static void SV_KillServer_f( void ) { + SV_Shutdown( "killserver" ); +} + +/* +================= +SV_GameCompleteStatus_f + +NERVE - SMF +================= +*/ +void SV_GameCompleteStatus_f( void ) { + SV_MasterGameCompleteStatus(); +} + +//=========================================================== + +/* +================== +SV_AddOperatorCommands +================== +*/ +void SV_AddOperatorCommands( void ) { + static qboolean initialized; + + if ( initialized ) { + return; + } + initialized = qtrue; + + Cmd_AddCommand( "heartbeat", SV_Heartbeat_f ); + Cmd_AddCommand( "kick", SV_Kick_f ); + Cmd_AddCommand( "banUser", SV_Ban_f ); + Cmd_AddCommand( "banClient", SV_BanNum_f ); + Cmd_AddCommand( "clientkick", SV_KickNum_f ); + Cmd_AddCommand( "status", SV_Status_f ); + Cmd_AddCommand( "serverinfo", SV_Serverinfo_f ); + Cmd_AddCommand( "systeminfo", SV_Systeminfo_f ); + Cmd_AddCommand( "dumpuser", SV_DumpUser_f ); + Cmd_AddCommand( "map_restart", SV_MapRestart_f ); + Cmd_AddCommand( "sectorlist", SV_SectorList_f ); + Cmd_AddCommand( "map", SV_Map_f ); + Cmd_AddCommand( "gameCompleteStatus", SV_GameCompleteStatus_f ); // NERVE - SMF +#ifndef PRE_RELEASE_DEMO + Cmd_AddCommand( "devmap", SV_Map_f ); + Cmd_AddCommand( "spmap", SV_Map_f ); + Cmd_AddCommand( "spdevmap", SV_Map_f ); +#endif + Cmd_AddCommand( "loadgame", SV_LoadGame_f ); + Cmd_AddCommand( "killserver", SV_KillServer_f ); + if ( com_dedicated->integer ) { + Cmd_AddCommand( "say", SV_ConSay_f ); + } +} + +/* +================== +SV_RemoveOperatorCommands +================== +*/ +void SV_RemoveOperatorCommands( void ) { +#if 0 + // removing these won't let the server start again + Cmd_RemoveCommand( "heartbeat" ); + Cmd_RemoveCommand( "kick" ); + Cmd_RemoveCommand( "banUser" ); + Cmd_RemoveCommand( "banClient" ); + Cmd_RemoveCommand( "status" ); + Cmd_RemoveCommand( "serverinfo" ); + Cmd_RemoveCommand( "systeminfo" ); + Cmd_RemoveCommand( "dumpuser" ); + Cmd_RemoveCommand( "map_restart" ); + Cmd_RemoveCommand( "sectorlist" ); + Cmd_RemoveCommand( "say" ); +#endif +} + diff --git a/src/server/sv_client.c b/src/server/sv_client.c new file mode 100644 index 0000000..1110957 --- /dev/null +++ b/src/server/sv_client.c @@ -0,0 +1,1625 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// sv_client.c -- server code for dealing with clients + +#include "server.h" + +static void SV_CloseDownload( client_t *cl ); + +/* +================= +SV_GetChallenge + +A "getchallenge" OOB command has been received +Returns a challenge number that can be used +in a subsequent connectResponse command. +We do this to prevent denial of service attacks that +flood the server with invalid connection IPs. With a +challenge, they must give a valid IP address. + +If we are authorizing, a challenge request will cause a packet +to be sent to the authorize server. + +When an authorizeip is returned, a challenge response will be +sent to that ip. +================= +*/ +void SV_GetChallenge( netadr_t from ) { + int i; + int oldest; + int oldestTime; + challenge_t *challenge; + + // ignore if we are in single player + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) { + return; + } + + oldest = 0; + oldestTime = 0x7fffffff; + + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + for ( i = 0 ; i < MAX_CHALLENGES ; i++, challenge++ ) { + if ( !challenge->connected && NET_CompareAdr( from, challenge->adr ) ) { + break; + } + if ( challenge->time < oldestTime ) { + oldestTime = challenge->time; + oldest = i; + } + } + + if ( i == MAX_CHALLENGES ) { + // this is the first time this client has asked for a challenge + challenge = &svs.challenges[oldest]; + + challenge->challenge = ( ( rand() << 16 ) ^ rand() ) ^ svs.time; + challenge->adr = from; + challenge->firstTime = svs.time; + challenge->firstPing = 0; + challenge->time = svs.time; + challenge->connected = qfalse; + i = oldest; + } + + // if they are on a lan address, send the challengeResponse immediately + if ( Sys_IsLANAddress( from ) ) { + challenge->pingTime = svs.time; + if ( sv_onlyVisibleClients->integer ) { + NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i %i", challenge->challenge, sv_onlyVisibleClients->integer ); + } else { + NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i", challenge->challenge ); + } + return; + } + + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // if they have been challenging for a long time and we + // haven't heard anything from the authoirze server, go ahead and + // let them in, assuming the id server is down + if ( svs.time - challenge->firstTime > AUTHORIZE_TIMEOUT ) { + Com_DPrintf( "authorize server timed out\n" ); + + challenge->pingTime = svs.time; + if ( sv_onlyVisibleClients->integer ) { + NET_OutOfBandPrint( NS_SERVER, challenge->adr, + "challengeResponse %i %i", challenge->challenge, sv_onlyVisibleClients->integer ); + } else { + NET_OutOfBandPrint( NS_SERVER, challenge->adr, + "challengeResponse %i", challenge->challenge ); + } + + return; + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + cvar_t *fs; + char game[1024]; + + game[0] = 0; + fs = Cvar_Get( "fs_game", "", CVAR_INIT | CVAR_SYSTEMINFO ); + if ( fs && fs->string[0] != 0 ) { + strcpy( game, fs->string ); + } + Com_DPrintf( "sending getIpAuthorize for %s\n", NET_AdrToString( from ) ); + fs = Cvar_Get( "sv_allowAnonymous", "0", CVAR_SERVERINFO ); + + // NERVE - SMF - fixed parsing on sv_allowAnonymous + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "getIpAuthorize %i %i.%i.%i.%i %s %i", svs.challenges[i].challenge, + from.ip[0], from.ip[1], from.ip[2], from.ip[3], game, fs->integer ); + } +} + +/* +==================== +SV_AuthorizeIpPacket + +A packet has been returned from the authorize server. +If we have a challenge adr for that ip, send the +challengeResponse to it +==================== +*/ +void SV_AuthorizeIpPacket( netadr_t from ) { + int challenge; + int i; + char *s; + char *r; + char ret[1024]; + + if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) { + Com_Printf( "SV_AuthorizeIpPacket: not from authorize server\n" ); + return; + } + + challenge = atoi( Cmd_Argv( 1 ) ); + + for ( i = 0 ; i < MAX_CHALLENGES ; i++ ) { + if ( svs.challenges[i].challenge == challenge ) { + break; + } + } + if ( i == MAX_CHALLENGES ) { + Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" ); + return; + } + + // send a packet back to the original client + svs.challenges[i].pingTime = svs.time; + s = Cmd_Argv( 2 ); + r = Cmd_Argv( 3 ); // reason + + if ( !Q_stricmp( s, "demo" ) ) { + if ( Cvar_VariableValue( "fs_restrict" ) ) { + // a demo client connecting to a demo server + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, + "challengeResponse %i", svs.challenges[i].challenge ); + return; + } + // they are a demo client trying to connect to a real server + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nServer is not a demo server\n" ); + // clear the challenge record so it won't timeout and let them through + memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + return; + } + if ( !Q_stricmp( s, "accept" ) ) { + if ( sv_onlyVisibleClients->integer ) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, + "challengeResponse %i %i", svs.challenges[i].challenge, sv_onlyVisibleClients->integer ); + } else { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, + "challengeResponse %i", svs.challenges[i].challenge ); + } + return; + } + if ( !Q_stricmp( s, "unknown" ) ) { + if ( !r ) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nAwaiting CD key authorization\n" ); + } else { + sprintf( ret, "print\n%s\n", r ); + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); + } + // clear the challenge record so it won't timeout and let them through + memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + return; + } + + // authorization failed + if ( !r ) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nSomeone is using this CD Key\n" ); + } else { + sprintf( ret, "print\n%s\n", r ); + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); + } + + // clear the challenge record so it won't timeout and let them through + memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); +} + +/* +================== +SV_DirectConnect + +A "connect" OOB command has been received +================== +*/ + +#define PB_MESSAGE "PunkBuster Anti-Cheat software must be installed " \ + "and Enabled in order to join this server. An updated game patch can be downloaded from " \ + "www.castlewolfenstein.com.\n" + +void SV_DirectConnect( netadr_t from ) { + char userinfo[MAX_INFO_STRING]; + int i; + client_t *cl, *newcl; + MAC_STATIC client_t temp; + sharedEntity_t *ent; + int clientNum; +#ifndef UPDATE_SERVER + int version; +#endif + int qport; + int challenge; + char *password; + int startIndex; + char *denied; + int count; + + Com_DPrintf( "SVC_DirectConnect ()\n" ); + + Q_strncpyz( userinfo, Cmd_Argv( 1 ), sizeof( userinfo ) ); + + // DHM - Nerve :: Update Server allows any protocol to connect +#ifndef UPDATE_SERVER + version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); + if ( version != PROTOCOL_VERSION ) { + if ( version <= 59 ) { + // old clients, don't send them the [err_drop] tag + NET_OutOfBandPrint( NS_SERVER, from, "print\n" PROTOCOL_MISMATCH_ERROR ); + } else { + NET_OutOfBandPrint( NS_SERVER, from, "print\n[err_prot]" PROTOCOL_MISMATCH_ERROR ); + } + Com_DPrintf( " rejected connect from version %i\n", version ); + return; + } +#endif + + challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); + qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); + + // quick reject + for ( i = 0,cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) { + if ( ( svs.time - cl->lastConnectTime ) + < ( sv_reconnectlimit->integer * 1000 ) ) { + Com_DPrintf( "%s:reconnect rejected : too soon\n", NET_AdrToString( from ) ); + return; + } + break; + } + } + + // see if the challenge is valid (LAN clients don't need to challenge) + if ( !NET_IsLocalAddress( from ) ) { + int ping; + + for ( i = 0 ; i < MAX_CHALLENGES ; i++ ) { + if ( NET_CompareAdr( from, svs.challenges[i].adr ) ) { + if ( challenge == svs.challenges[i].challenge ) { + break; // good + } + } + } + if ( i == MAX_CHALLENGES ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for address.\n" ); + return; + } + // force the IP key/value pair so the game can filter based on ip + Info_SetValueForKey( userinfo, "ip", NET_AdrToString( from ) ); + + if ( svs.challenges[i].firstPing == 0 ) { + ping = svs.time - svs.challenges[i].pingTime; + svs.challenges[i].firstPing = ping; + } else { + ping = svs.challenges[i].firstPing; + } + + Com_Printf( "Client %i connecting with %i challenge ping\n", i, ping ); + svs.challenges[i].connected = qtrue; + + // never reject a LAN client based on ping + if ( !Sys_IsLANAddress( from ) ) { + if ( sv_minPing->value && ping < sv_minPing->value ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for high pings only\n" ); + Com_DPrintf( "Client %i rejected on a too low ping\n", i ); + return; + } + if ( sv_maxPing->value && ping > sv_maxPing->value ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is for low pings only\n" ); + Com_DPrintf( "Client %i rejected on a too high ping: %i\n", i, ping ); + return; + } + } + } else { + // force the "ip" info key to "localhost" + Info_SetValueForKey( userinfo, "ip", "localhost" ); + } + + newcl = &temp; + memset( newcl, 0, sizeof( client_t ) ); + + // if there is already a slot for this ip, reuse it + for ( i = 0,cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( cl->state == CS_FREE ) { + continue; + } + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) { + Com_Printf( "%s:reconnect\n", NET_AdrToString( from ) ); + newcl = cl; + + goto gotnewcl; + } + } + + // find a client slot + // if "sv_privateClients" is set > 0, then that number + // of client slots will be reserved for connections that + // have "password" set to the value of "sv_privatePassword" + // Info requests will report the maxclients as if the private + // slots didn't exist, to prevent people from trying to connect + // to a full server. + // This is to allow us to reserve a couple slots here on our + // servers so we can play without having to kick people. + + // check for privateClient password + password = Info_ValueForKey( userinfo, "password" ); + if ( !strcmp( password, sv_privatePassword->string ) ) { + startIndex = 0; + } else { + // skip past the reserved slots + startIndex = sv_privateClients->integer; + } + + newcl = NULL; + for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if ( cl->state == CS_FREE ) { + newcl = cl; + break; + } + } + + if ( !newcl ) { + if ( NET_IsLocalAddress( from ) ) { + count = 0; + for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if ( cl->netchan.remoteAddress.type == NA_BOT ) { + count++; + } + } + // if they're all bots + if ( count >= sv_maxclients->integer - startIndex ) { + SV_DropClient( &svs.clients[sv_maxclients->integer - 1], "only bots on server" ); + newcl = &svs.clients[sv_maxclients->integer - 1]; + } else { + Com_Error( ERR_FATAL, "server is full on local connect\n" ); + return; + } + } else { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" ); + Com_DPrintf( "Rejected a connection.\n" ); + return; + } + } + + // we got a newcl, so reset the reliableSequence and reliableAcknowledge + cl->reliableAcknowledge = 0; + cl->reliableSequence = 0; + +gotnewcl: + // build a new connection + // accept the new client + // this is the only place a client_t is ever initialized + *newcl = temp; + clientNum = newcl - svs.clients; + ent = SV_GentityNum( clientNum ); + newcl->gentity = ent; + + // save the challenge + newcl->challenge = challenge; + + // save the address + Netchan_Setup( NS_SERVER, &newcl->netchan, from, qport ); + // init the netchan queue + newcl->netchan_end_queue = &newcl->netchan_start_queue; + + // save the userinfo + Q_strncpyz( newcl->userinfo, userinfo, sizeof( newcl->userinfo ) ); + + // get the game a chance to reject this connection or modify the userinfo + denied = (char *)VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue + if ( denied ) { + // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call + denied = VM_ExplicitArgPtr( gvm, (int)denied ); + + NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); + Com_DPrintf( "Game rejected a connection: %s.\n", denied ); + return; + } + + SV_UserinfoChanged( newcl ); + + // DHM - Nerve :: Clear out firstPing now that client is connected + svs.challenges[i].firstPing = 0; + + // send the connect packet to the client + NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); + + Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); + + newcl->state = CS_CONNECTED; + newcl->nextSnapshotTime = svs.time; + newcl->lastPacketTime = svs.time; + newcl->lastConnectTime = svs.time; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + newcl->gamestateMessageNum = -1; + + // if this was the first client on the server, or the last client + // the server can hold, send a heartbeat to the master. + count = 0; + for ( i = 0,cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + if ( count == 1 || count == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + + +/* +===================== +SV_DropClient + +Called when the player is totally leaving the server, either willingly +or unwillingly. This is NOT called if the entire server is quiting +or crashing -- SV_FinalMessage() will handle that +===================== +*/ +void SV_DropClient( client_t *drop, const char *reason ) { + int i; + challenge_t *challenge; + + if ( drop->state == CS_ZOMBIE ) { + return; // already dropped + } + + if ( !drop->gentity || !( drop->gentity->r.svFlags & SVF_BOT ) ) { + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + + for ( i = 0 ; i < MAX_CHALLENGES ; i++, challenge++ ) { + if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) { + challenge->connected = qfalse; + break; + } + } + } + + // Kill any download + SV_CloseDownload( drop ); + + // tell everyone why they got dropped + SV_SendServerCommand( NULL, "print \"[lof]%s" S_COLOR_WHITE " [lon]%s\n\"", drop->name, reason ); + + Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); + drop->state = CS_ZOMBIE; // become free in a few seconds + + if ( drop->download ) { + FS_FCloseFile( drop->download ); + drop->download = 0; + } + + // call the prog function for removing a client + // this will remove the body, among other things + VM_Call( gvm, GAME_CLIENT_DISCONNECT, drop - svs.clients ); + + // add the disconnect command + SV_SendServerCommand( drop, "disconnect \"%s\"", reason ); + + if ( drop->netchan.remoteAddress.type == NA_BOT ) { + SV_BotFreeClient( drop - svs.clients ); + } + + // nuke user info + SV_SetUserinfo( drop - svs.clients, "" ); + + + // if this was the last client on the server, send a heartbeat + // to the master so it is known the server is empty + // send a heartbeat now so the master will get up to date info + // if there is already a slot for this ip, reuse it + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + break; + } + } + if ( i == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + +/* +================ +SV_SendClientGameState + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each new map load. + +It will be resent if the client acknowledges a later message but has +the wrong gamestate. +================ +*/ +void SV_SendClientGameState( client_t *client ) { + int start; + entityState_t *base, nullstate; + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + Com_DPrintf( "SV_SendClientGameState() for %s\n", client->name ); + Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); + client->state = CS_PRIMED; + client->pureAuthentic = 0; + client->gotCP = qfalse; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + client->gamestateMessageNum = client->netchan.outgoingSequence; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // send the gamestate + MSG_WriteByte( &msg, svc_gamestate ); + MSG_WriteLong( &msg, client->reliableSequence ); + + // write the configstrings + for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { + if ( sv.configstrings[start][0] ) { + MSG_WriteByte( &msg, svc_configstring ); + MSG_WriteShort( &msg, start ); + MSG_WriteBigString( &msg, sv.configstrings[start] ); + } + } + + // write the baselines + memset( &nullstate, 0, sizeof( nullstate ) ); + for ( start = 0 ; start < MAX_GENTITIES; start++ ) { + base = &sv.svEntities[start].baseline; + if ( !base->number ) { + continue; + } + MSG_WriteByte( &msg, svc_baseline ); + MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); + } + + MSG_WriteByte( &msg, svc_EOF ); + + MSG_WriteLong( &msg, client - svs.clients ); + + // write the checksum feed + MSG_WriteLong( &msg, sv.checksumFeed ); + + // NERVE - SMF - debug info + Com_DPrintf( "Sending %i bytes in gamestate to client: %i\n", msg.cursize, client - svs.clients ); + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + + +/* +================== +SV_ClientEnterWorld +================== +*/ +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { + int clientNum; + sharedEntity_t *ent; + + Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); + client->state = CS_ACTIVE; + + // set up the entity for the client + clientNum = client - svs.clients; + ent = SV_GentityNum( clientNum ); + ent->s.number = clientNum; + client->gentity = ent; + + client->deltaMessage = -1; + client->nextSnapshotTime = svs.time; // generate a snapshot immediately + client->lastUsercmd = *cmd; + + // call the game begin function + VM_Call( gvm, GAME_CLIENT_BEGIN, client - svs.clients ); +} + +/* +============================================================ + +CLIENT COMMAND EXECUTION + +============================================================ +*/ + +/* +================== +SV_CloseDownload + +clear/free any download vars +================== +*/ +static void SV_CloseDownload( client_t *cl ) { + int i; + + // EOF + if ( cl->download ) { + FS_FCloseFile( cl->download ); + } + cl->download = 0; + *cl->downloadName = 0; + + // Free the temporary buffer space + for ( i = 0; i < MAX_DOWNLOAD_WINDOW; i++ ) { + if ( cl->downloadBlocks[i] ) { + Z_Free( cl->downloadBlocks[i] ); + cl->downloadBlocks[i] = NULL; + } + } + +} + +/* +================== +SV_StopDownload_f + +Abort a download if in progress +================== +*/ +void SV_StopDownload_f( client_t *cl ) { + if ( *cl->downloadName ) { + Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", cl - svs.clients, cl->downloadName ); + } + + SV_CloseDownload( cl ); +} + +/* +================== +SV_DoneDownload_f + +Downloads are finished +================== +*/ +void SV_DoneDownload_f( client_t *cl ) { + Com_DPrintf( "clientDownload: %s Done\n", cl->name ); + // resend the game state to update any clients that entered during the download + SV_SendClientGameState( cl ); +} + +/* +================== +SV_NextDownload_f + +The argument will be the last acknowledged block from the client, it should be +the same as cl->downloadClientBlock +================== +*/ +void SV_NextDownload_f( client_t *cl ) { + int block = atoi( Cmd_Argv( 1 ) ); + + if ( block == cl->downloadClientBlock ) { + Com_DPrintf( "clientDownload: %d : client acknowledge of block %d\n", cl - svs.clients, block ); + + // Find out if we are done. A zero-length block indicates EOF + if ( cl->downloadBlockSize[cl->downloadClientBlock % MAX_DOWNLOAD_WINDOW] == 0 ) { + Com_Printf( "clientDownload: %d : file \"%s\" completed\n", cl - svs.clients, cl->downloadName ); + SV_CloseDownload( cl ); + return; + } + + cl->downloadSendTime = svs.time; + cl->downloadClientBlock++; + return; + } + // We aren't getting an acknowledge for the correct block, drop the client + // FIXME: this is bad... the client will never parse the disconnect message + // because the cgame isn't loaded yet + SV_DropClient( cl, "broken download" ); +} + +/* +================== +SV_BeginDownload_f +================== +*/ +void SV_BeginDownload_f( client_t *cl ) { + + // Kill any existing download + SV_CloseDownload( cl ); + + // cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open + // the file itself + Q_strncpyz( cl->downloadName, Cmd_Argv( 1 ), sizeof( cl->downloadName ) ); +} + +/* +================== +SV_WriteDownloadToClient + +Check to see if the client wants a file, open it if needed and start pumping the client +Fill up msg with data +================== +*/ + +void SV_WriteDownloadToClient( client_t *cl, msg_t *msg ) { + int curindex; + int rate; + int blockspersnap; + int idPack; + char errorMessage[1024]; + +#ifdef UPDATE_SERVER + int i; + char testname[MAX_QPATH]; +#endif + + qboolean bTellRate = qfalse; // verbosity + + if ( !*cl->downloadName ) { + return; // Nothing being downloaded + } + + // CVE-2006-2082 + // validate the download against the list of pak files + if ( !FS_VerifyPak( cl->downloadName ) ) { + // will drop the client and leave it hanging on the other side. good for him + SV_DropClient( cl, "illegal download request" ); + return; + } + + if ( !cl->download ) { + // We open the file here + + Com_Printf( "clientDownload: %d : begining \"%s\"\n", cl - svs.clients, cl->downloadName ); + + idPack = FS_idPak( cl->downloadName, "main" ); + + // DHM - Nerve :: Update server only allows files that are in versionmap.cfg to download +#ifdef UPDATE_SERVER + for ( i = 0; i < numVersions; i++ ) { + + strcpy( testname, "updates/" ); + Q_strcat( testname, MAX_QPATH, versionMap[ i ].installer ); + + if ( !Q_stricmp( cl->downloadName, testname ) ) { + break; + } + } + + if ( i == numVersions ) { + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, 0 ); // client is expecting block zero + MSG_WriteLong( msg, -1 ); // illegal file size + + Com_sprintf( errorMessage, sizeof( errorMessage ), "Invalid download from update server" ); + MSG_WriteString( msg, errorMessage ); + + *cl->downloadName = 0; + + SV_DropClient( cl, "Invalid download from update server" ); + return; + } +#endif + // DHM - Nerve + + if ( !sv_allowDownload->integer || idPack || + ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) { + // cannot auto-download file + if ( idPack ) { + Com_Printf( "clientDownload: %d : \"%s\" cannot download id pk3 files\n", cl - svs.clients, cl->downloadName ); + Com_sprintf( errorMessage, sizeof( errorMessage ), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName ); + } else if ( !sv_allowDownload->integer ) { + Com_Printf( "clientDownload: %d : \"%s\" download disabled", cl - svs.clients, cl->downloadName ); + if ( sv_pure->integer ) { + Com_sprintf( errorMessage, sizeof( errorMessage ), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "You will need to get this file elsewhere before you " + "can connect to this pure server.\n", cl->downloadName ); + } else { + Com_sprintf( errorMessage, sizeof( errorMessage ), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "Set autodownload to No in your settings and you might be " + "able to connect even if you do have the file.\n", cl->downloadName ); + } + } else { + Com_Printf( "clientDownload: %d : \"%s\" file not found on server\n", cl - svs.clients, cl->downloadName ); + Com_sprintf( errorMessage, sizeof( errorMessage ), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName ); + } + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, 0 ); // client is expecting block zero + MSG_WriteLong( msg, -1 ); // illegal file size + MSG_WriteString( msg, errorMessage ); + + *cl->downloadName = 0; + return; + } + + // Init + cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; + cl->downloadCount = 0; + cl->downloadEOF = qfalse; + + bTellRate = qtrue; + } + + // Perform any reads that we need to + while ( cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && + cl->downloadSize != cl->downloadCount ) { + + curindex = ( cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW ); + + if ( !cl->downloadBlocks[curindex] ) { + cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE ); + } + + cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); + + if ( cl->downloadBlockSize[curindex] < 0 ) { + // EOF right now + cl->downloadCount = cl->downloadSize; + break; + } + + cl->downloadCount += cl->downloadBlockSize[curindex]; + + // Load in next block + cl->downloadCurrentBlock++; + } + + // Check to see if we have eof condition and add the EOF block + if ( cl->downloadCount == cl->downloadSize && + !cl->downloadEOF && + cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW ) { + + cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; + cl->downloadCurrentBlock++; + + cl->downloadEOF = qtrue; // We have added the EOF block + } + + // Loop up to window size times based on how many blocks we can fit in the + // client snapMsec and rate + + // based on the rate, how many bytes can we fit in the snapMsec time of the client + // normal rate / snapshotMsec calculation + rate = cl->rate; + + // show_bug.cgi?id=509 + // for autodownload, we use a seperate max rate value + // we do this everytime because the client might change it's rate during the download + if ( sv_dl_maxRate->integer < rate ) { + rate = sv_dl_maxRate->integer; + if ( bTellRate ) { + Com_Printf( "'%s' downloading at sv_dl_maxrate (%d)\n", cl->name, sv_dl_maxRate->integer ); + } + } else + if ( bTellRate ) { + Com_Printf( "'%s' downloading at rate %d\n", cl->name, rate ); + } + + if ( !rate ) { + blockspersnap = 1; + } else { + blockspersnap = ( ( rate * cl->snapshotMsec ) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / + MAX_DOWNLOAD_BLKSIZE; + } + + if ( blockspersnap < 0 ) { + blockspersnap = 1; + } + + while ( blockspersnap-- ) { + + // Write out the next section of the file, if we have already reached our window, + // automatically start retransmitting + + if ( cl->downloadClientBlock == cl->downloadCurrentBlock ) { + return; // Nothing to transmit + + } + if ( cl->downloadXmitBlock == cl->downloadCurrentBlock ) { + // We have transmitted the complete window, should we start resending? + + //FIXME: This uses a hardcoded one second timeout for lost blocks + //the timeout should be based on client rate somehow + if ( svs.time - cl->downloadSendTime > 1000 ) { + cl->downloadXmitBlock = cl->downloadClientBlock; + } else { + return; + } + } + + // Send current block + curindex = ( cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW ); + + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, cl->downloadXmitBlock ); + + // block zero is special, contains file size + if ( cl->downloadXmitBlock == 0 ) { + MSG_WriteLong( msg, cl->downloadSize ); + } + + MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); + + // Write the block + if ( cl->downloadBlockSize[curindex] ) { + MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] ); + } + + Com_DPrintf( "clientDownload: %d : writing block %d\n", cl - svs.clients, cl->downloadXmitBlock ); + + // Move on to the next block + // It will get sent with next snap shot. The rate will keep us in line. + cl->downloadXmitBlock++; + + cl->downloadSendTime = svs.time; + } +} + +/* +================= +SV_Disconnect_f + +The client is going to disconnect, so remove the connection immediately FIXME: move to game? +================= +*/ +static void SV_Disconnect_f( client_t *cl ) { + SV_DropClient( cl, "disconnected" ); +} + +/* +================= +SV_VerifyPaks_f + +If we are pure, disconnect the client if they do no meet the following conditions: + +1. the first two checksums match our view of cgame and ui DLLs + Wolf specific: the checksum is the checksum of the pk3 we found the DLL in +2. there are no any additional checksums that we do not have + +This routine would be a bit simpler with a goto but i abstained + +================= +*/ +static void SV_VerifyPaks_f( client_t *cl ) { + int nChkSum1, nChkSum2, nClientPaks, nServerPaks, i, j, nCurArg; + int nClientChkSum[1024]; + int nServerChkSum[1024]; + const char *pPaks, *pArg; + qboolean bGood = qtrue; + + // if we are pure, we "expect" the client to load certain things from + // certain pk3 files, namely we want the client to have loaded the + // ui and cgame that we think should be loaded based on the pure setting + if ( sv_pure->integer != 0 ) { + + bGood = qtrue; + nChkSum1 = nChkSum2 = 0; + + bGood = ( FS_FileIsInPAK( FS_ShiftStr( SYS_DLLNAME_CGAME, -SYS_DLLNAME_CGAME_SHIFT ), &nChkSum1 ) == 1 ); + if ( bGood ) { + bGood = ( FS_FileIsInPAK( FS_ShiftStr( SYS_DLLNAME_UI, -SYS_DLLNAME_UI_SHIFT ), &nChkSum2 ) == 1 ); + } + + nClientPaks = Cmd_Argc(); + + // start at arg 2 ( skip serverId cl_paks ) + nCurArg = 1; + + pArg = Cmd_Argv( nCurArg++ ); + + if ( !pArg ) { + bGood = qfalse; + } else + { + // show_bug.cgi?id=475 + // we may get incoming cp sequences from a previous checksumFeed, which we need to ignore + // since serverId is a frame count, it always goes up + if ( atoi( pArg ) < sv.checksumFeedServerId ) { + Com_DPrintf( "ignoring outdated cp command from client %s\n", cl->name ); + return; + } + } + + // we basically use this while loop to avoid using 'goto' :) + while ( bGood ) { + + // must be at least 6: "cl_paks cgame ui @ firstref ... numChecksums" + // numChecksums is encoded + if ( nClientPaks < 6 ) { + bGood = qfalse; + break; + } + // verify first to be the cgame checksum + pArg = Cmd_Argv( nCurArg++ ); + if ( !pArg || *pArg == '@' || atoi( pArg ) != nChkSum1 ) { + bGood = qfalse; + break; + } + // verify the second to be the ui checksum + pArg = Cmd_Argv( nCurArg++ ); + if ( !pArg || *pArg == '@' || atoi( pArg ) != nChkSum2 ) { + bGood = qfalse; + break; + } + // should be sitting at the delimeter now + pArg = Cmd_Argv( nCurArg++ ); + if ( *pArg != '@' ) { + bGood = qfalse; + break; + } + // store checksums since tokenization is not re-entrant + for ( i = 0; nCurArg < nClientPaks; i++ ) { + nClientChkSum[i] = atoi( Cmd_Argv( nCurArg++ ) ); + } + + // store number to compare against (minus one cause the last is the number of checksums) + nClientPaks = i - 1; + + // make sure none of the client check sums are the same + // so the client can't send 5 the same checksums + for ( i = 0; i < nClientPaks; i++ ) { + for ( j = 0; j < nClientPaks; j++ ) { + if ( i == j ) { + continue; + } + if ( nClientChkSum[i] == nClientChkSum[j] ) { + bGood = qfalse; + break; + } + } + if ( bGood == qfalse ) { + break; + } + } + if ( bGood == qfalse ) { + break; + } + + // get the pure checksums of the pk3 files loaded by the server + pPaks = FS_LoadedPakPureChecksums(); + Cmd_TokenizeString( pPaks ); + nServerPaks = Cmd_Argc(); + if ( nServerPaks > 1024 ) { + nServerPaks = 1024; + } + + for ( i = 0; i < nServerPaks; i++ ) { + nServerChkSum[i] = atoi( Cmd_Argv( i ) ); + } + + // check if the client has provided any pure checksums of pk3 files not loaded by the server + for ( i = 0; i < nClientPaks; i++ ) { + for ( j = 0; j < nServerPaks; j++ ) { + if ( nClientChkSum[i] == nServerChkSum[j] ) { + break; + } + } + if ( j >= nServerPaks ) { + bGood = qfalse; + break; + } + } + if ( bGood == qfalse ) { + break; + } + + // check if the number of checksums was correct + nChkSum1 = sv.checksumFeed; + for ( i = 0; i < nClientPaks; i++ ) { + nChkSum1 ^= nClientChkSum[i]; + } + nChkSum1 ^= nClientPaks; + if ( nChkSum1 != nClientChkSum[nClientPaks] ) { + bGood = qfalse; + break; + } + + // break out + break; + } + + cl->gotCP = qtrue; + + if ( bGood ) { + cl->pureAuthentic = 1; + } else { + cl->pureAuthentic = 0; + cl->nextSnapshotTime = -1; + cl->state = CS_ACTIVE; + SV_SendClientSnapshot( cl ); + SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" ); + } + } +} + +/* +================= +SV_ResetPureClient_f +================= +*/ +static void SV_ResetPureClient_f( client_t *cl ) { + cl->pureAuthentic = 0; + cl->gotCP = qfalse; +} + +/* +================= +SV_UserinfoChanged + +Pull specific info from a newly changed userinfo string +into a more C friendly form. +================= +*/ +void SV_UserinfoChanged( client_t *cl ) { + char *val; + int i; + + // name for C code + Q_strncpyz( cl->name, Info_ValueForKey( cl->userinfo, "name" ), sizeof( cl->name ) ); + + // rate command + + // if the client is on the same subnet as the server and we aren't running an + // internet public server, assume they don't need a rate choke + if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 && sv_lanForceRate->integer == 1 ) { + cl->rate = 99999; // lans should not rate limit + } else { + val = Info_ValueForKey( cl->userinfo, "rate" ); + if ( strlen( val ) ) { + i = atoi( val ); + cl->rate = i; + if ( cl->rate < 1000 ) { + cl->rate = 1000; + } else if ( cl->rate > 90000 ) { + cl->rate = 90000; + } + } else { + cl->rate = 5000; + } + } + val = Info_ValueForKey( cl->userinfo, "handicap" ); + if ( strlen( val ) ) { + i = atoi( val ); + if ( i <= 0 || i > 100 || strlen( val ) > 4 ) { + Info_SetValueForKey( cl->userinfo, "handicap", "100" ); + } + } + + // snaps command + val = Info_ValueForKey( cl->userinfo, "snaps" ); + if ( strlen( val ) ) { + i = atoi( val ); + if ( i < 1 ) { + i = 1; + } else if ( i > 30 ) { + i = 30; + } + cl->snapshotMsec = 1000 / i; + } else { + cl->snapshotMsec = 50; + } + + // TTimo + // maintain the IP information + // this is set in SV_DirectConnect (directly on the server, not transmitted), may be lost when client updates it's userinfo + // the banning code relies on this being consistently present + val = Info_ValueForKey( cl->userinfo, "ip" ); + if ( !val[0] ) { + //Com_DPrintf("Maintain IP in userinfo for '%s'\n", cl->name); + if ( !NET_IsLocalAddress( cl->netchan.remoteAddress ) ) { + Info_SetValueForKey( cl->userinfo, "ip", NET_AdrToString( cl->netchan.remoteAddress ) ); + } else { + // force the "ip" info key to "localhost" for local clients + Info_SetValueForKey( cl->userinfo, "ip", "localhost" ); + } + } + +} + + +/* +================== +SV_UpdateUserinfo_f +================== +*/ +static void SV_UpdateUserinfo_f( client_t *cl ) { + Q_strncpyz( cl->userinfo, Cmd_Argv( 1 ), sizeof( cl->userinfo ) ); + + SV_UserinfoChanged( cl ); + + // call prog code to allow overrides + VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); +} + +typedef struct { + char *name; + void ( *func )( client_t *cl ); +} ucmd_t; + +static ucmd_t ucmds[] = { + {"userinfo", SV_UpdateUserinfo_f}, + {"disconnect", SV_Disconnect_f}, + {"cp", SV_VerifyPaks_f}, + {"vdr", SV_ResetPureClient_f}, + {"download", SV_BeginDownload_f}, + {"nextdl", SV_NextDownload_f}, + {"stopdl", SV_StopDownload_f}, + {"donedl", SV_DoneDownload_f}, + {NULL, NULL} +}; + +/* +================== +SV_ExecuteClientCommand + +Also called by bot code +================== +*/ +void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) { + ucmd_t *u; + qboolean bProcessed = qfalse; + + Cmd_TokenizeString( s ); + + // see if it is a server level command + for ( u = ucmds ; u->name ; u++ ) { + if ( !strcmp( Cmd_Argv( 0 ), u->name ) ) { + u->func( cl ); + bProcessed = qtrue; + break; + } + } + + if ( clientOK ) { + // pass unknown strings to the game + if ( !u->name && sv.state == SS_GAME ) { + VM_Call( gvm, GAME_CLIENT_COMMAND, cl - svs.clients ); + } + } else if ( !bProcessed ) { + Com_DPrintf( "client text ignored for %s: %s\n", cl->name, Cmd_Argv( 0 ) ); + } +} + +/* +=============== +SV_ClientCommand +=============== +*/ +static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) { + int seq; + const char *s; + qboolean clientOk = qtrue; + qboolean floodprotect = qtrue; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed it + if ( cl->lastClientCommand >= seq ) { + return qtrue; + } + + Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s ); + + // drop the connection if we have somehow lost commands + if ( seq > cl->lastClientCommand + 1 ) { + Com_Printf( "Client %s lost %i clientCommands\n", cl->name, + seq - cl->lastClientCommand + 1 ); + SV_DropClient( cl, "Lost reliable commands" ); + return qfalse; + } + + // NERVE - SMF - some server game-only commands we cannot have flood protect + if ( !Q_strncmp( "team", s, 4 ) || !Q_strncmp( "setspawnpt", s, 10 ) || !Q_strncmp( "score", s, 5 ) ) { +// Com_DPrintf( "Skipping flood protection for: %s\n", s ); + floodprotect = qfalse; + } + + // malicious users may try using too many string commands + // to lag other players. If we decide that we want to stall + // the command, we will stop processing the rest of the packet, + // including the usercmd. This causes flooders to lag themselves + // but not other people + // We don't do this when the client hasn't been active yet since its + // normal to spam a lot of commands when downloading + if ( !com_cl_running->integer && + cl->state >= CS_ACTIVE && // (SA) this was commented out in Wolf. Did we do that? + sv_floodProtect->integer && + svs.time < cl->nextReliableTime && + floodprotect ) { + // ignore any other text messages from this client but let them keep playing + // TTimo - moved the ignored verbose to the actual processing in SV_ExecuteClientCommand, only printing if the core doesn't intercept + clientOk = qfalse; + } + + // don't allow another command for one second + if ( floodprotect ) { + cl->nextReliableTime = svs.time + 800; + } + + SV_ExecuteClientCommand( cl, s, clientOk ); + + cl->lastClientCommand = seq; + Com_sprintf( cl->lastClientCommandString, sizeof( cl->lastClientCommandString ), "%s", s ); + + return qtrue; // continue procesing +} + + +//================================================================================== + + +/* +================== +SV_ClientThink + +Also called by bot code +================== +*/ +void SV_ClientThink( client_t *cl, usercmd_t *cmd ) { + cl->lastUsercmd = *cmd; + + if ( cl->state != CS_ACTIVE ) { + return; // may have been kicked during the last usercmd + } + + VM_Call( gvm, GAME_CLIENT_THINK, cl - svs.clients ); +} + +/* +================== +SV_UserMove + +The message usually contains all the movement commands +that were in the last three packets, so that the information +in dropped packets can be recovered. + +On very fast clients, there may be multiple usercmd packed into +each of the backup packets. +================== +*/ +static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) { + int i, key; + int cmdCount; + usercmd_t nullcmd; + usercmd_t cmds[MAX_PACKET_USERCMDS]; + usercmd_t *cmd, *oldcmd; + + if ( delta ) { + cl->deltaMessage = cl->messageAcknowledge; + } else { + cl->deltaMessage = -1; + } + + cmdCount = MSG_ReadByte( msg ); + + if ( cmdCount < 1 ) { + Com_Printf( "cmdCount < 1\n" ); + return; + } + + if ( cmdCount > MAX_PACKET_USERCMDS ) { + Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" ); + return; + } + + // use the checksum feed in the key + key = sv.checksumFeed; + // also use the message acknowledge + key ^= cl->messageAcknowledge; + // also use the last acknowledged server command in the key + key ^= Com_HashKey( cl->reliableCommands[ cl->reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 ) ], 32 ); + + memset( &nullcmd, 0, sizeof( nullcmd ) ); + oldcmd = &nullcmd; + for ( i = 0 ; i < cmdCount ; i++ ) { + cmd = &cmds[i]; + MSG_ReadDeltaUsercmdKey( msg, key, oldcmd, cmd ); +// MSG_ReadDeltaUsercmd( msg, oldcmd, cmd ); + oldcmd = cmd; + } + + // save time for ping calculation + cl->frames[ cl->messageAcknowledge & PACKET_MASK ].messageAcked = svs.time; + + // TTimo + // catch the no-cp-yet situation before SV_ClientEnterWorld + // if CS_ACTIVE, then it's time to trigger a new gamestate emission + // if not, then we are getting remaining parasite usermove commands, which we should ignore + if ( sv_pure->integer != 0 && cl->pureAuthentic == 0 && !cl->gotCP ) { + if ( cl->state == CS_ACTIVE ) { + // we didn't get a cp yet, don't assume anything and just send the gamestate all over again + Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name, cl->state ); + SV_SendClientGameState( cl ); + } + return; + } + + // if this is the first usercmd we have received + // this gamestate, put the client into the world + if ( cl->state == CS_PRIMED ) { + SV_ClientEnterWorld( cl, &cmds[0] ); + // the moves can be processed normaly + } + + // a bad cp command was sent, drop the client + if ( sv_pure->integer != 0 && cl->pureAuthentic == 0 ) { + SV_DropClient( cl, "Cannot validate pure client!" ); + return; + } + + if ( cl->state != CS_ACTIVE ) { + cl->deltaMessage = -1; + return; + } + + // usually, the first couple commands will be duplicates + // of ones we have previously received, but the servertimes + // in the commands will cause them to be immediately discarded + for ( i = 0 ; i < cmdCount ; i++ ) { + // if this is a cmd from before a map_restart ignore it + if ( cmds[i].serverTime > cmds[cmdCount - 1].serverTime ) { + continue; + } + // extremely lagged or cmd from before a map_restart + //if ( cmds[i].serverTime > svs.time + 3000 ) { + // continue; + //} + if ( sv_gametype->integer != GT_SINGLE_PLAYER ) { // RF, we need to allow this in single player, where loadgame's can cause the player to freeze after reloading if we do this check + // don't execute if this is an old cmd which is already executed + // these old cmds are included when cl_packetdup > 0 + if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) { // Q3_MISSIONPACK +// if ( cmds[i].serverTime > cmds[cmdCount-1].serverTime ) { + continue; // from just before a map_restart + } + } + SV_ClientThink( cl, &cmds[ i ] ); + } +} + + +/* +=========================================================================== + +USER CMD EXECUTION + +=========================================================================== +*/ + +/* +=================== +SV_ExecuteClientMessage + +Parse a client packet +=================== +*/ +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { + int c; + int serverId; + + MSG_Bitstream( msg ); + + serverId = MSG_ReadLong( msg ); + cl->messageAcknowledge = MSG_ReadLong( msg ); + + if ( cl->messageAcknowledge < 0 ) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging +#ifndef NDEBUG + SV_DropClient( cl, "DEBUG: illegible client message" ); +#endif + return; + } + + cl->reliableAcknowledge = MSG_ReadLong( msg ); + + // NOTE: when the client message is fux0red the acknowledgement numbers + // can be out of range, this could cause the server to send thousands of server + // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient + if ( cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS ) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging +#ifndef NDEBUG + SV_DropClient( cl, "DEBUG: illegible client message" ); +#endif + cl->reliableAcknowledge = cl->reliableSequence; + return; + } + // if this is a usercmd from a previous gamestate, + // ignore it or retransmit the current gamestate + // + // if the client was downloading, let it stay at whatever serverId and + // gamestate it was at. This allows it to keep downloading even when + // the gamestate changes. After the download is finished, we'll + // notice and send it a new game state + // + // show_bug.cgi?id=536 + // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" + // but we still need to read the next message to move to next download or send gamestate + // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else + if ( serverId != sv.serverId && !*cl->downloadName && !strstr( cl->lastClientCommandString, "nextdl" ) ) { + if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart + // they just haven't caught the map_restart yet + Com_DPrintf( "%s : ignoring pre map_restart / outdated client message\n", cl->name ); + return; + } + // if we can tell that the client has dropped the last + // gamestate we sent them, resend it + if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { + Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); + SV_SendClientGameState( cl ); + } + return; + } + + // read optional clientCommand strings + do { + c = MSG_ReadByte( msg ); + if ( c == clc_EOF ) { + break; + } + if ( c != clc_clientCommand ) { + break; + } + if ( !SV_ClientCommand( cl, msg ) ) { + return; // we couldn't execute it because of the flood protection + } + if ( cl->state == CS_ZOMBIE ) { + return; // disconnect command + } + } while ( 1 ); + + // read the usercmd_t + if ( c == clc_move ) { + SV_UserMove( cl, msg, qtrue ); + } else if ( c == clc_moveNoDelta ) { + SV_UserMove( cl, msg, qfalse ); + } else if ( c != clc_EOF ) { + Com_Printf( "WARNING: bad command byte for client %i\n", cl - svs.clients ); + } +// if ( msg->readcount != msg->cursize ) { +// Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); +// } +} + diff --git a/src/server/sv_game.c b/src/server/sv_game.c new file mode 100644 index 0000000..1167aeb --- /dev/null +++ b/src/server/sv_game.c @@ -0,0 +1,1028 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// sv_game.c -- interface to the game dll + +#include "server.h" + +#include "../game/botlib.h" + +botlib_export_t *botlib_export; + +void SV_GameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + +void SV_GamePrint( const char *string ) { + Com_Printf( "%s", string ); +} + +// these functions must be used instead of pointer arithmetic, because +// the game allocates gentities with private information after the server shared part +int SV_NumForGentity( sharedEntity_t *ent ) { + int num; + + num = ( (byte *)ent - (byte *)sv.gentities ) / sv.gentitySize; + + return num; +} + +sharedEntity_t *SV_GentityNum( int num ) { + sharedEntity_t *ent; + + ent = ( sharedEntity_t * )( (byte *)sv.gentities + sv.gentitySize * ( num ) ); + + return ent; +} + +playerState_t *SV_GameClientNum( int num ) { + playerState_t *ps; + + ps = ( playerState_t * )( (byte *)sv.gameClients + sv.gameClientSize * ( num ) ); + + return ps; +} + +svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ) { + if ( !gEnt || gEnt->s.number < 0 || gEnt->s.number >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + return &sv.svEntities[ gEnt->s.number ]; +} + +sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) { + int num; + + num = svEnt - sv.svEntities; + return SV_GentityNum( num ); +} + +/* +=============== +SV_GameSendServerCommand + +Sends a command string to a client +=============== +*/ +void SV_GameSendServerCommand( int clientNum, const char *text ) { + if ( clientNum == -1 ) { + SV_SendServerCommand( NULL, "%s", text ); + } else { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_SendServerCommand( svs.clients + clientNum, "%s", text ); + } +} + + +/* +=============== +SV_GameDropClient + +Disconnects the client with a message +=============== +*/ +void SV_GameDropClient( int clientNum, const char *reason ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_DropClient( svs.clients + clientNum, reason ); +} + + +/* +================= +SV_SetBrushModel + +sets mins and maxs for inline bmodels +================= +*/ +void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) { + clipHandle_t h; + vec3_t mins, maxs; + + if ( !name ) { + Com_Error( ERR_DROP, "SV_SetBrushModel: NULL" ); + } + + if ( name[0] != '*' ) { + Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model", name ); + } + + + ent->s.modelindex = atoi( name + 1 ); + + h = CM_InlineModel( ent->s.modelindex ); + CM_ModelBounds( h, mins, maxs ); + VectorCopy( mins, ent->r.mins ); + VectorCopy( maxs, ent->r.maxs ); + ent->r.bmodel = qtrue; + + ent->r.contents = -1; // we don't know exactly what is in the brushes + + SV_LinkEntity( ent ); // FIXME: remove +} + + + +/* +================= +SV_inPVS + +Also checks portalareas so that doors block sight +================= +*/ +qboolean SV_inPVS( const vec3_t p1, const vec3_t p2 ) { + int leafnum; + int cluster; + int area1, area2; + byte *mask; + + leafnum = CM_PointLeafnum( p1 ); + cluster = CM_LeafCluster( leafnum ); + area1 = CM_LeafArea( leafnum ); + mask = CM_ClusterPVS( cluster ); + + leafnum = CM_PointLeafnum( p2 ); + cluster = CM_LeafCluster( leafnum ); + area2 = CM_LeafArea( leafnum ); + if ( mask && ( !( mask[cluster >> 3] & ( 1 << ( cluster & 7 ) ) ) ) ) { + return qfalse; + } + if ( !CM_AreasConnected( area1, area2 ) ) { + return qfalse; // a door blocks sight + } + return qtrue; +} + + +/* +================= +SV_inPVSIgnorePortals + +Does NOT check portalareas +================= +*/ +qboolean SV_inPVSIgnorePortals( const vec3_t p1, const vec3_t p2 ) { + int leafnum; + int cluster; + int area1, area2; + byte *mask; + + leafnum = CM_PointLeafnum( p1 ); + cluster = CM_LeafCluster( leafnum ); + area1 = CM_LeafArea( leafnum ); + mask = CM_ClusterPVS( cluster ); + + leafnum = CM_PointLeafnum( p2 ); + cluster = CM_LeafCluster( leafnum ); + area2 = CM_LeafArea( leafnum ); + + if ( mask && ( !( mask[cluster >> 3] & ( 1 << ( cluster & 7 ) ) ) ) ) { + return qfalse; + } + + return qtrue; +} + + +/* +======================== +SV_AdjustAreaPortalState +======================== +*/ +void SV_AdjustAreaPortalState( sharedEntity_t *ent, qboolean open ) { + svEntity_t *svEnt; + + svEnt = SV_SvEntityForGentity( ent ); + if ( svEnt->areanum2 == -1 ) { + return; + } + CM_AdjustAreaPortalState( svEnt->areanum, svEnt->areanum2, open ); +} + + +/* +================== +SV_GameAreaEntities +================== +*/ +qboolean SV_EntityContact( const vec3_t mins, const vec3_t maxs, const sharedEntity_t *gEnt, const int capsule ) { + const float *origin, *angles; + clipHandle_t ch; + trace_t trace; + + // check for exact collision + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + ch = SV_ClipHandleForEntity( gEnt ); + CM_TransformedBoxTrace( &trace, vec3_origin, vec3_origin, mins, maxs, + ch, -1, origin, angles, capsule ); + + return trace.startsolid; +} + + +/* +=============== +SV_GetServerinfo + +=============== +*/ +void SV_GetServerinfo( char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetServerinfo: bufferSize == %i", bufferSize ); + } + Q_strncpyz( buffer, Cvar_InfoString( CVAR_SERVERINFO ), bufferSize ); +} + +/* +=============== +SV_LocateGameData + +=============== +*/ +void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *clients, int sizeofGameClient ) { + sv.gentities = gEnts; + sv.gentitySize = sizeofGEntity_t; + sv.num_entities = numGEntities; + + sv.gameClients = clients; + sv.gameClientSize = sizeofGameClient; +} + + +/* +=============== +SV_GetUsercmd + +=============== +*/ +void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_GetUsercmd: bad clientNum:%i", clientNum ); + } + *cmd = svs.clients[clientNum].lastUsercmd; +} + +//============================================== + +static int FloatAsInt( float f ) { + int temp; + + *(float *)&temp = f; + + return temp; +} + +/* +==================== +SV_GameSystemCalls + +The module is making a system call +==================== +*/ +//rcg010207 - see my comments in VM_DllSyscall(), in qcommon/vm.c ... +#if ( ( defined __linux__ ) && ( defined __powerpc__ ) ) || ( defined MACOS_X ) +#define VMA( x ) ( (void *) args[x] ) +#else +#define VMA( x ) VM_ArgPtr( args[x] ) +#endif + +#define VMF( x ) ( (float *)args )[x] + +int SV_GameSystemCalls( int *args ) { + switch ( args[0] ) { + case G_PRINT: + Com_Printf( "%s", VMA( 1 ) ); + return 0; + case G_ERROR: + Com_Error( ERR_DROP, "%s", VMA( 1 ) ); + return 0; + case G_MILLISECONDS: + return Sys_Milliseconds(); + case G_CVAR_REGISTER: + Cvar_Register( VMA( 1 ), VMA( 2 ), VMA( 3 ), args[4] ); + return 0; + case G_CVAR_UPDATE: + Cvar_Update( VMA( 1 ) ); + return 0; + case G_CVAR_SET: + Cvar_Set( (const char *)VMA( 1 ), (const char *)VMA( 2 ) ); + return 0; + case G_CVAR_VARIABLE_INTEGER_VALUE: + return Cvar_VariableIntegerValue( (const char *)VMA( 1 ) ); + case G_CVAR_VARIABLE_STRING_BUFFER: + Cvar_VariableStringBuffer( VMA( 1 ), VMA( 2 ), args[3] ); + return 0; + case G_ARGC: + return Cmd_Argc(); + case G_ARGV: + Cmd_ArgvBuffer( args[1], VMA( 2 ), args[3] ); + return 0; + case G_SEND_CONSOLE_COMMAND: + Cbuf_ExecuteText( args[1], VMA( 2 ) ); + return 0; + + case G_FS_FOPEN_FILE: + return FS_FOpenFileByMode( VMA( 1 ), VMA( 2 ), args[3] ); + case G_FS_READ: + FS_Read( VMA( 1 ), args[2], args[3] ); + return 0; + case G_FS_WRITE: + return FS_Write( VMA( 1 ), args[2], args[3] ); + case G_FS_RENAME: + FS_Rename( VMA( 1 ), VMA( 2 ) ); + return 0; + case G_FS_FCLOSE_FILE: + FS_FCloseFile( args[1] ); + return 0; + case G_FS_GETFILELIST: + return FS_GetFileList( VMA( 1 ), VMA( 2 ), VMA( 3 ), args[4] ); + + case G_LOCATE_GAME_DATA: + SV_LocateGameData( VMA( 1 ), args[2], args[3], VMA( 4 ), args[5] ); + return 0; + case G_DROP_CLIENT: + SV_GameDropClient( args[1], VMA( 2 ) ); + return 0; + case G_SEND_SERVER_COMMAND: + SV_GameSendServerCommand( args[1], VMA( 2 ) ); + return 0; + case G_LINKENTITY: + SV_LinkEntity( VMA( 1 ) ); + return 0; + case G_UNLINKENTITY: + SV_UnlinkEntity( VMA( 1 ) ); + return 0; + case G_ENTITIES_IN_BOX: + return SV_AreaEntities( VMA( 1 ), VMA( 2 ), VMA( 3 ), args[4] ); + case G_ENTITY_CONTACT: + return SV_EntityContact( VMA( 1 ), VMA( 2 ), VMA( 3 ), /* int capsule */ qfalse ); + case G_ENTITY_CONTACTCAPSULE: + return SV_EntityContact( VMA( 1 ), VMA( 2 ), VMA( 3 ), /* int capsule */ qtrue ); + case G_TRACE: + SV_Trace( VMA( 1 ), VMA( 2 ), VMA( 3 ), VMA( 4 ), VMA( 5 ), args[6], args[7], /* int capsule */ qfalse ); + return 0; + case G_TRACECAPSULE: + SV_Trace( VMA( 1 ), VMA( 2 ), VMA( 3 ), VMA( 4 ), VMA( 5 ), args[6], args[7], /* int capsule */ qtrue ); + return 0; + case G_POINT_CONTENTS: + return SV_PointContents( VMA( 1 ), args[2] ); + case G_SET_BRUSH_MODEL: + SV_SetBrushModel( VMA( 1 ), VMA( 2 ) ); + return 0; + case G_IN_PVS: + return SV_inPVS( VMA( 1 ), VMA( 2 ) ); + case G_IN_PVS_IGNORE_PORTALS: + return SV_inPVSIgnorePortals( VMA( 1 ), VMA( 2 ) ); + + case G_SET_CONFIGSTRING: + SV_SetConfigstring( args[1], VMA( 2 ) ); + return 0; + case G_GET_CONFIGSTRING: + SV_GetConfigstring( args[1], VMA( 2 ), args[3] ); + return 0; + case G_SET_USERINFO: + SV_SetUserinfo( args[1], VMA( 2 ) ); + return 0; + case G_GET_USERINFO: + SV_GetUserinfo( args[1], VMA( 2 ), args[3] ); + return 0; + case G_GET_SERVERINFO: + SV_GetServerinfo( VMA( 1 ), args[2] ); + return 0; + case G_ADJUST_AREA_PORTAL_STATE: + SV_AdjustAreaPortalState( VMA( 1 ), args[2] ); + return 0; + case G_AREAS_CONNECTED: + return CM_AreasConnected( args[1], args[2] ); + + case G_BOT_ALLOCATE_CLIENT: + return SV_BotAllocateClient(); + case G_BOT_FREE_CLIENT: + SV_BotFreeClient( args[1] ); + return 0; + + case G_GET_USERCMD: + SV_GetUsercmd( args[1], VMA( 2 ) ); + return 0; + case G_GET_ENTITY_TOKEN: + { + const char *s; + + s = COM_Parse( &sv.entityParsePoint ); + Q_strncpyz( VMA( 1 ), s, args[2] ); + if ( !sv.entityParsePoint && !s[0] ) { + return qfalse; + } else { + return qtrue; + } + } + + case G_DEBUG_POLYGON_CREATE: + return BotImport_DebugPolygonCreate( args[1], args[2], VMA( 3 ) ); + case G_DEBUG_POLYGON_DELETE: + BotImport_DebugPolygonDelete( args[1] ); + return 0; + case G_REAL_TIME: + return Com_RealTime( VMA( 1 ) ); + case G_SNAPVECTOR: + Sys_SnapVector( VMA( 1 ) ); + return 0; + case G_GETTAG: + return SV_GetTag( args[1], VMA( 2 ), VMA( 3 ) ); + + //==================================== + + case BOTLIB_SETUP: + return SV_BotLibSetup(); + case BOTLIB_SHUTDOWN: + return SV_BotLibShutdown(); + case BOTLIB_LIBVAR_SET: + return botlib_export->BotLibVarSet( VMA( 1 ), VMA( 2 ) ); + case BOTLIB_LIBVAR_GET: + return botlib_export->BotLibVarGet( VMA( 1 ), VMA( 2 ), args[3] ); + + case BOTLIB_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( VMA( 1 ) ); + case BOTLIB_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( VMA( 1 ) ); + case BOTLIB_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case BOTLIB_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], VMA( 2 ) ); + case BOTLIB_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], VMA( 2 ), VMA( 3 ) ); + + case BOTLIB_START_FRAME: + return botlib_export->BotLibStartFrame( VMF( 1 ) ); + case BOTLIB_LOAD_MAP: + return botlib_export->BotLibLoadMap( VMA( 1 ) ); + case BOTLIB_UPDATENTITY: + return botlib_export->BotLibUpdateEntity( args[1], VMA( 2 ) ); + case BOTLIB_TEST: + return botlib_export->Test( args[1], VMA( 2 ), VMA( 3 ), VMA( 4 ) ); + + case BOTLIB_GET_SNAPSHOT_ENTITY: + return SV_BotGetSnapshotEntity( args[1], args[2] ); + case BOTLIB_GET_CONSOLE_MESSAGE: + return SV_BotGetConsoleMessage( args[1], VMA( 2 ), args[3] ); + case BOTLIB_USER_COMMAND: + SV_ClientThink( &svs.clients[args[1]], VMA( 2 ) ); + return 0; + + case BOTLIB_AAS_ENTITY_INFO: + botlib_export->aas.AAS_EntityInfo( args[1], VMA( 2 ) ); + return 0; + + case BOTLIB_AAS_INITIALIZED: + return botlib_export->aas.AAS_Initialized(); + case BOTLIB_AAS_PRESENCE_TYPE_BOUNDING_BOX: + botlib_export->aas.AAS_PresenceTypeBoundingBox( args[1], VMA( 2 ), VMA( 3 ) ); + return 0; + case BOTLIB_AAS_TIME: + return FloatAsInt( botlib_export->aas.AAS_Time() ); + + // Ridah + case BOTLIB_AAS_SETCURRENTWORLD: + botlib_export->aas.AAS_SetCurrentWorld( args[1] ); + return 0; + // done. + + case BOTLIB_AAS_POINT_AREA_NUM: + return botlib_export->aas.AAS_PointAreaNum( VMA( 1 ) ); + case BOTLIB_AAS_TRACE_AREAS: + return botlib_export->aas.AAS_TraceAreas( VMA( 1 ), VMA( 2 ), VMA( 3 ), VMA( 4 ), args[5] ); + + case BOTLIB_AAS_POINT_CONTENTS: + return botlib_export->aas.AAS_PointContents( VMA( 1 ) ); + case BOTLIB_AAS_NEXT_BSP_ENTITY: + return botlib_export->aas.AAS_NextBSPEntity( args[1] ); + case BOTLIB_AAS_VALUE_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_ValueForBSPEpairKey( args[1], VMA( 2 ), VMA( 3 ), args[4] ); + case BOTLIB_AAS_VECTOR_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_VectorForBSPEpairKey( args[1], VMA( 2 ), VMA( 3 ) ); + case BOTLIB_AAS_FLOAT_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_FloatForBSPEpairKey( args[1], VMA( 2 ), VMA( 3 ) ); + case BOTLIB_AAS_INT_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_IntForBSPEpairKey( args[1], VMA( 2 ), VMA( 3 ) ); + + case BOTLIB_AAS_AREA_REACHABILITY: + return botlib_export->aas.AAS_AreaReachability( args[1] ); + + case BOTLIB_AAS_AREA_TRAVEL_TIME_TO_GOAL_AREA: + return botlib_export->aas.AAS_AreaTravelTimeToGoalArea( args[1], VMA( 2 ), args[3], args[4] ); + + case BOTLIB_AAS_SWIMMING: + return botlib_export->aas.AAS_Swimming( VMA( 1 ) ); + case BOTLIB_AAS_PREDICT_CLIENT_MOVEMENT: + return botlib_export->aas.AAS_PredictClientMovement( VMA( 1 ), args[2], VMA( 3 ), args[4], args[5], + VMA( 6 ), VMA( 7 ), args[8], args[9], VMF( 10 ), args[11], args[12], args[13] ); + + // Ridah, route-tables + case BOTLIB_AAS_RT_SHOWROUTE: + botlib_export->aas.AAS_RT_ShowRoute( VMA( 1 ), args[2], args[3] ); + return 0; + + case BOTLIB_AAS_RT_GETHIDEPOS: + return botlib_export->aas.AAS_RT_GetHidePos( VMA( 1 ), args[2], args[3], VMA( 4 ), args[5], args[6], VMA( 7 ) ); + + case BOTLIB_AAS_FINDATTACKSPOTWITHINRANGE: + return botlib_export->aas.AAS_FindAttackSpotWithinRange( args[1], args[2], args[3], VMF( 4 ), args[5], VMA( 6 ) ); + + case BOTLIB_AAS_SETAASBLOCKINGENTITY: + botlib_export->aas.AAS_SetAASBlockingEntity( VMA( 1 ), VMA( 2 ), args[3] ); + return 0; + // done. + + case BOTLIB_EA_SAY: + botlib_export->ea.EA_Say( args[1], VMA( 2 ) ); + return 0; + case BOTLIB_EA_SAY_TEAM: + botlib_export->ea.EA_SayTeam( args[1], VMA( 2 ) ); + return 0; + case BOTLIB_EA_USE_ITEM: + botlib_export->ea.EA_UseItem( args[1], VMA( 2 ) ); + return 0; + case BOTLIB_EA_DROP_ITEM: + botlib_export->ea.EA_DropItem( args[1], VMA( 2 ) ); + return 0; + case BOTLIB_EA_USE_INV: + botlib_export->ea.EA_UseInv( args[1], VMA( 2 ) ); + return 0; + case BOTLIB_EA_DROP_INV: + botlib_export->ea.EA_DropInv( args[1], VMA( 2 ) ); + return 0; + case BOTLIB_EA_GESTURE: + botlib_export->ea.EA_Gesture( args[1] ); + return 0; + case BOTLIB_EA_COMMAND: + botlib_export->ea.EA_Command( args[1], VMA( 2 ) ); + return 0; + + case BOTLIB_EA_SELECT_WEAPON: + botlib_export->ea.EA_SelectWeapon( args[1], args[2] ); + return 0; + case BOTLIB_EA_TALK: + botlib_export->ea.EA_Talk( args[1] ); + return 0; + case BOTLIB_EA_ATTACK: + botlib_export->ea.EA_Attack( args[1] ); + return 0; + case BOTLIB_EA_RELOAD: + botlib_export->ea.EA_Reload( args[1] ); + return 0; + case BOTLIB_EA_USE: + botlib_export->ea.EA_Use( args[1] ); + return 0; + case BOTLIB_EA_RESPAWN: + botlib_export->ea.EA_Respawn( args[1] ); + return 0; + case BOTLIB_EA_JUMP: + botlib_export->ea.EA_Jump( args[1] ); + return 0; + case BOTLIB_EA_DELAYED_JUMP: + botlib_export->ea.EA_DelayedJump( args[1] ); + return 0; + case BOTLIB_EA_CROUCH: + botlib_export->ea.EA_Crouch( args[1] ); + return 0; + case BOTLIB_EA_MOVE_UP: + botlib_export->ea.EA_MoveUp( args[1] ); + return 0; + case BOTLIB_EA_MOVE_DOWN: + botlib_export->ea.EA_MoveDown( args[1] ); + return 0; + case BOTLIB_EA_MOVE_FORWARD: + botlib_export->ea.EA_MoveForward( args[1] ); + return 0; + case BOTLIB_EA_MOVE_BACK: + botlib_export->ea.EA_MoveBack( args[1] ); + return 0; + case BOTLIB_EA_MOVE_LEFT: + botlib_export->ea.EA_MoveLeft( args[1] ); + return 0; + case BOTLIB_EA_MOVE_RIGHT: + botlib_export->ea.EA_MoveRight( args[1] ); + return 0; + case BOTLIB_EA_MOVE: + botlib_export->ea.EA_Move( args[1], VMA( 2 ), VMF( 3 ) ); + return 0; + case BOTLIB_EA_VIEW: + botlib_export->ea.EA_View( args[1], VMA( 2 ) ); + return 0; + + case BOTLIB_EA_END_REGULAR: + botlib_export->ea.EA_EndRegular( args[1], VMF( 2 ) ); + return 0; + case BOTLIB_EA_GET_INPUT: + botlib_export->ea.EA_GetInput( args[1], VMF( 2 ), VMA( 3 ) ); + return 0; + case BOTLIB_EA_RESET_INPUT: + botlib_export->ea.EA_ResetInput( args[1], VMA( 2 ) ); + return 0; + + case BOTLIB_AI_LOAD_CHARACTER: + return botlib_export->ai.BotLoadCharacter( VMA( 1 ), args[2] ); + case BOTLIB_AI_FREE_CHARACTER: + botlib_export->ai.BotFreeCharacter( args[1] ); + return 0; + case BOTLIB_AI_CHARACTERISTIC_FLOAT: + return FloatAsInt( botlib_export->ai.Characteristic_Float( args[1], args[2] ) ); + case BOTLIB_AI_CHARACTERISTIC_BFLOAT: + return FloatAsInt( botlib_export->ai.Characteristic_BFloat( args[1], args[2], VMF( 3 ), VMF( 4 ) ) ); + case BOTLIB_AI_CHARACTERISTIC_INTEGER: + return botlib_export->ai.Characteristic_Integer( args[1], args[2] ); + case BOTLIB_AI_CHARACTERISTIC_BINTEGER: + return botlib_export->ai.Characteristic_BInteger( args[1], args[2], args[3], args[4] ); + case BOTLIB_AI_CHARACTERISTIC_STRING: + botlib_export->ai.Characteristic_String( args[1], args[2], VMA( 3 ), args[4] ); + return 0; + + case BOTLIB_AI_ALLOC_CHAT_STATE: + return botlib_export->ai.BotAllocChatState(); + case BOTLIB_AI_FREE_CHAT_STATE: + botlib_export->ai.BotFreeChatState( args[1] ); + return 0; + case BOTLIB_AI_QUEUE_CONSOLE_MESSAGE: + botlib_export->ai.BotQueueConsoleMessage( args[1], args[2], VMA( 3 ) ); + return 0; + case BOTLIB_AI_REMOVE_CONSOLE_MESSAGE: + botlib_export->ai.BotRemoveConsoleMessage( args[1], args[2] ); + return 0; + case BOTLIB_AI_NEXT_CONSOLE_MESSAGE: + return botlib_export->ai.BotNextConsoleMessage( args[1], VMA( 2 ) ); + case BOTLIB_AI_NUM_CONSOLE_MESSAGE: + return botlib_export->ai.BotNumConsoleMessages( args[1] ); + case BOTLIB_AI_INITIAL_CHAT: + botlib_export->ai.BotInitialChat( args[1], VMA( 2 ), args[3], VMA( 4 ), VMA( 5 ), VMA( 6 ), VMA( 7 ), VMA( 8 ), VMA( 9 ), VMA( 10 ), VMA( 11 ) ); + return 0; + case BOTLIB_AI_NUM_INITIAL_CHATS: + return botlib_export->ai.BotNumInitialChats( args[1], VMA( 2 ) ); + case BOTLIB_AI_REPLY_CHAT: + return botlib_export->ai.BotReplyChat( args[1], VMA( 2 ), args[3], args[4], VMA( 5 ), VMA( 6 ), VMA( 7 ), VMA( 8 ), VMA( 9 ), VMA( 10 ), VMA( 11 ), VMA( 12 ) ); + case BOTLIB_AI_CHAT_LENGTH: + return botlib_export->ai.BotChatLength( args[1] ); + case BOTLIB_AI_ENTER_CHAT: + botlib_export->ai.BotEnterChat( args[1], args[2], args[3] ); + return 0; + case BOTLIB_AI_GET_CHAT_MESSAGE: + botlib_export->ai.BotGetChatMessage( args[1], VMA( 2 ), args[3] ); + return 0; + case BOTLIB_AI_STRING_CONTAINS: + return botlib_export->ai.StringContains( VMA( 1 ), VMA( 2 ), args[3] ); + case BOTLIB_AI_FIND_MATCH: + return botlib_export->ai.BotFindMatch( VMA( 1 ), VMA( 2 ), args[3] ); + case BOTLIB_AI_MATCH_VARIABLE: + botlib_export->ai.BotMatchVariable( VMA( 1 ), args[2], VMA( 3 ), args[4] ); + return 0; + case BOTLIB_AI_UNIFY_WHITE_SPACES: + botlib_export->ai.UnifyWhiteSpaces( VMA( 1 ) ); + return 0; + case BOTLIB_AI_REPLACE_SYNONYMS: + botlib_export->ai.BotReplaceSynonyms( VMA( 1 ), args[2] ); + return 0; + case BOTLIB_AI_LOAD_CHAT_FILE: + return botlib_export->ai.BotLoadChatFile( args[1], VMA( 2 ), VMA( 3 ) ); + case BOTLIB_AI_SET_CHAT_GENDER: + botlib_export->ai.BotSetChatGender( args[1], args[2] ); + return 0; + case BOTLIB_AI_SET_CHAT_NAME: + botlib_export->ai.BotSetChatName( args[1], VMA( 2 ) ); + return 0; + + case BOTLIB_AI_RESET_GOAL_STATE: + botlib_export->ai.BotResetGoalState( args[1] ); + return 0; + case BOTLIB_AI_RESET_AVOID_GOALS: + botlib_export->ai.BotResetAvoidGoals( args[1] ); + return 0; + case BOTLIB_AI_REMOVE_FROM_AVOID_GOALS: + botlib_export->ai.BotRemoveFromAvoidGoals( args[1], args[2] ); + return 0; + case BOTLIB_AI_PUSH_GOAL: + botlib_export->ai.BotPushGoal( args[1], VMA( 2 ) ); + return 0; + case BOTLIB_AI_POP_GOAL: + botlib_export->ai.BotPopGoal( args[1] ); + return 0; + case BOTLIB_AI_EMPTY_GOAL_STACK: + botlib_export->ai.BotEmptyGoalStack( args[1] ); + return 0; + case BOTLIB_AI_DUMP_AVOID_GOALS: + botlib_export->ai.BotDumpAvoidGoals( args[1] ); + return 0; + case BOTLIB_AI_DUMP_GOAL_STACK: + botlib_export->ai.BotDumpGoalStack( args[1] ); + return 0; + case BOTLIB_AI_GOAL_NAME: + botlib_export->ai.BotGoalName( args[1], VMA( 2 ), args[3] ); + return 0; + case BOTLIB_AI_GET_TOP_GOAL: + return botlib_export->ai.BotGetTopGoal( args[1], VMA( 2 ) ); + case BOTLIB_AI_GET_SECOND_GOAL: + return botlib_export->ai.BotGetSecondGoal( args[1], VMA( 2 ) ); + case BOTLIB_AI_CHOOSE_LTG_ITEM: + return botlib_export->ai.BotChooseLTGItem( args[1], VMA( 2 ), VMA( 3 ), args[4] ); + case BOTLIB_AI_CHOOSE_NBG_ITEM: + return botlib_export->ai.BotChooseNBGItem( args[1], VMA( 2 ), VMA( 3 ), args[4], VMA( 5 ), VMF( 6 ) ); + case BOTLIB_AI_TOUCHING_GOAL: + return botlib_export->ai.BotTouchingGoal( VMA( 1 ), VMA( 2 ) ); + case BOTLIB_AI_ITEM_GOAL_IN_VIS_BUT_NOT_VISIBLE: + return botlib_export->ai.BotItemGoalInVisButNotVisible( args[1], VMA( 2 ), VMA( 3 ), VMA( 4 ) ); + case BOTLIB_AI_GET_LEVEL_ITEM_GOAL: + return botlib_export->ai.BotGetLevelItemGoal( args[1], VMA( 2 ), VMA( 3 ) ); + case BOTLIB_AI_GET_NEXT_CAMP_SPOT_GOAL: + return botlib_export->ai.BotGetNextCampSpotGoal( args[1], VMA( 2 ) ); + case BOTLIB_AI_GET_MAP_LOCATION_GOAL: + return botlib_export->ai.BotGetMapLocationGoal( VMA( 1 ), VMA( 2 ) ); + case BOTLIB_AI_AVOID_GOAL_TIME: + return FloatAsInt( botlib_export->ai.BotAvoidGoalTime( args[1], args[2] ) ); + case BOTLIB_AI_INIT_LEVEL_ITEMS: + botlib_export->ai.BotInitLevelItems(); + return 0; + case BOTLIB_AI_UPDATE_ENTITY_ITEMS: + botlib_export->ai.BotUpdateEntityItems(); + return 0; + case BOTLIB_AI_LOAD_ITEM_WEIGHTS: + return botlib_export->ai.BotLoadItemWeights( args[1], VMA( 2 ) ); + case BOTLIB_AI_FREE_ITEM_WEIGHTS: + botlib_export->ai.BotFreeItemWeights( args[1] ); + return 0; + case BOTLIB_AI_INTERBREED_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotInterbreedGoalFuzzyLogic( args[1], args[2], args[3] ); + return 0; + case BOTLIB_AI_SAVE_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotSaveGoalFuzzyLogic( args[1], VMA( 2 ) ); + return 0; + case BOTLIB_AI_MUTATE_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotMutateGoalFuzzyLogic( args[1], VMF( 2 ) ); + return 0; + case BOTLIB_AI_ALLOC_GOAL_STATE: + return botlib_export->ai.BotAllocGoalState( args[1] ); + case BOTLIB_AI_FREE_GOAL_STATE: + botlib_export->ai.BotFreeGoalState( args[1] ); + return 0; + + case BOTLIB_AI_RESET_MOVE_STATE: + botlib_export->ai.BotResetMoveState( args[1] ); + return 0; + case BOTLIB_AI_MOVE_TO_GOAL: + botlib_export->ai.BotMoveToGoal( VMA( 1 ), args[2], VMA( 3 ), args[4] ); + return 0; + case BOTLIB_AI_MOVE_IN_DIRECTION: + return botlib_export->ai.BotMoveInDirection( args[1], VMA( 2 ), VMF( 3 ), args[4] ); + case BOTLIB_AI_RESET_AVOID_REACH: + botlib_export->ai.BotResetAvoidReach( args[1] ); + return 0; + case BOTLIB_AI_RESET_LAST_AVOID_REACH: + botlib_export->ai.BotResetLastAvoidReach( args[1] ); + return 0; + case BOTLIB_AI_REACHABILITY_AREA: + return botlib_export->ai.BotReachabilityArea( VMA( 1 ), args[2] ); + case BOTLIB_AI_MOVEMENT_VIEW_TARGET: + return botlib_export->ai.BotMovementViewTarget( args[1], VMA( 2 ), args[3], VMF( 4 ), VMA( 5 ) ); + case BOTLIB_AI_PREDICT_VISIBLE_POSITION: + return botlib_export->ai.BotPredictVisiblePosition( VMA( 1 ), args[2], VMA( 3 ), args[4], VMA( 5 ) ); + case BOTLIB_AI_ALLOC_MOVE_STATE: + return botlib_export->ai.BotAllocMoveState(); + case BOTLIB_AI_FREE_MOVE_STATE: + botlib_export->ai.BotFreeMoveState( args[1] ); + return 0; + case BOTLIB_AI_INIT_MOVE_STATE: + botlib_export->ai.BotInitMoveState( args[1], VMA( 2 ) ); + return 0; + // Ridah + case BOTLIB_AI_INIT_AVOID_REACH: + botlib_export->ai.BotInitAvoidReach( args[1] ); + return 0; + // done. + + case BOTLIB_AI_CHOOSE_BEST_FIGHT_WEAPON: + return botlib_export->ai.BotChooseBestFightWeapon( args[1], VMA( 2 ) ); + case BOTLIB_AI_GET_WEAPON_INFO: + botlib_export->ai.BotGetWeaponInfo( args[1], args[2], VMA( 3 ) ); + return 0; + case BOTLIB_AI_LOAD_WEAPON_WEIGHTS: + return botlib_export->ai.BotLoadWeaponWeights( args[1], VMA( 2 ) ); + case BOTLIB_AI_ALLOC_WEAPON_STATE: + return botlib_export->ai.BotAllocWeaponState(); + case BOTLIB_AI_FREE_WEAPON_STATE: + botlib_export->ai.BotFreeWeaponState( args[1] ); + return 0; + case BOTLIB_AI_RESET_WEAPON_STATE: + botlib_export->ai.BotResetWeaponState( args[1] ); + return 0; + + case BOTLIB_AI_GENETIC_PARENTS_AND_CHILD_SELECTION: + return botlib_export->ai.GeneticParentsAndChildSelection( args[1], VMA( 2 ), VMA( 3 ), VMA( 4 ), VMA( 5 ) ); + + case TRAP_MEMSET: + memset( VMA( 1 ), args[2], args[3] ); + return 0; + + case TRAP_MEMCPY: + memcpy( VMA( 1 ), VMA( 2 ), args[3] ); + return 0; + + case TRAP_STRNCPY: + return (int)strncpy( VMA( 1 ), VMA( 2 ), args[3] ); + + case TRAP_SIN: + return FloatAsInt( sin( VMF( 1 ) ) ); + + case TRAP_COS: + return FloatAsInt( cos( VMF( 1 ) ) ); + + case TRAP_ATAN2: + return FloatAsInt( atan2( VMF( 1 ), VMF( 2 ) ) ); + + case TRAP_SQRT: + return FloatAsInt( sqrt( VMF( 1 ) ) ); + + case TRAP_MATRIXMULTIPLY: + MatrixMultiply( VMA( 1 ), VMA( 2 ), VMA( 3 ) ); + return 0; + + case TRAP_ANGLEVECTORS: + AngleVectors( VMA( 1 ), VMA( 2 ), VMA( 3 ), VMA( 4 ) ); + return 0; + + case TRAP_PERPENDICULARVECTOR: + PerpendicularVector( VMA( 1 ), VMA( 2 ) ); + return 0; + + case TRAP_FLOOR: + return FloatAsInt( floor( VMF( 1 ) ) ); + + case TRAP_CEIL: + return FloatAsInt( ceil( VMF( 1 ) ) ); + + + default: + Com_Error( ERR_DROP, "Bad game system trap: %i", args[0] ); + } + return -1; +} + +/* +=============== +SV_ShutdownGameProgs + +Called every time a map changes +=============== +*/ +void SV_ShutdownGameProgs( void ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_SHUTDOWN, qfalse ); + VM_Free( gvm ); + gvm = NULL; +} + +/* +================== +SV_InitGameVM + +Called for both a full init and a restart +================== +*/ +static void SV_InitGameVM( qboolean restart ) { + int i; + + // start the entity parsing at the beginning + sv.entityParsePoint = CM_EntityString(); + + // clear all gentity pointers that might still be set from + // a previous level + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + svs.clients[i].gentity = NULL; + } + + // use the current msec count for a random seed + // init for this gamestate + VM_Call( gvm, GAME_INIT, svs.time, Com_Milliseconds(), restart ); +} + + + +/* +=================== +SV_RestartGameProgs + +Called on a map_restart, but not on a normal map change +=================== +*/ +void SV_RestartGameProgs( void ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_SHUTDOWN, qtrue ); + + // do a restart instead of a free + gvm = VM_Restart( gvm ); + if ( !gvm ) { // bk001212 - as done below + Com_Error( ERR_FATAL, "VM_Restart on game failed" ); + } + + SV_InitGameVM( qtrue ); +} + + +/* +=============== +SV_InitGameProgs + +Called on a normal map change, not on a map_restart +=============== +*/ +void SV_InitGameProgs( void ) { + + // load the dll + gvm = VM_Create( "qagame", SV_GameSystemCalls, VMI_NATIVE ); + if ( !gvm ) { + Com_Error( ERR_FATAL, "VM_Create on game failed" ); + } + + SV_InitGameVM( qfalse ); +} + + +/* +==================== +SV_GameCommand + +See if the current console command is claimed by the game +==================== +*/ +qboolean SV_GameCommand( void ) { + if ( sv.state != SS_GAME ) { + return qfalse; + } + + return VM_Call( gvm, GAME_CONSOLE_COMMAND ); +} + + +/* +==================== +SV_SendMoveSpeedsToGame +==================== +*/ +void SV_SendMoveSpeedsToGame( int entnum, char *text ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_RETRIEVE_MOVESPEEDS_FROM_CLIENT, entnum, text ); +} + +/* +==================== +SV_GetTag + + return qfalse if unable to retrieve tag information for this client +==================== +*/ +extern qboolean CL_GetTag( int clientNum, char *tagname, orientation_t * or ); + +qboolean SV_GetTag( int clientNum, char *tagname, orientation_t *or ) { +#ifndef DEDICATED // TTimo: dedicated only binary defines DEDICATED + if ( com_dedicated->integer ) { + return qfalse; + } + + return CL_GetTag( clientNum, tagname, or ); +#else + return qfalse; +#endif +} diff --git a/src/server/sv_init.c b/src/server/sv_init.c new file mode 100644 index 0000000..2ec835f --- /dev/null +++ b/src/server/sv_init.c @@ -0,0 +1,981 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + * name: sv_init.c + * + * desc: + * +*/ + +#include "server.h" + +/* +=============== +SV_SetConfigstring + +=============== +*/ +void SV_SetConfigstring( int index, const char *val ) { + int len, i; + int maxChunkSize = MAX_STRING_CHARS - 24; + client_t *client; + + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "SV_SetConfigstring: bad index %i\n", index ); + } + + if ( !val ) { + val = ""; + } + + // don't bother broadcasting an update if no change + if ( !strcmp( val, sv.configstrings[ index ] ) ) { + return; + } + + // change the string in sv + Z_Free( sv.configstrings[index] ); + sv.configstrings[index] = CopyString( val ); + + // send it to all the clients if we aren't + // spawning a new server + if ( sv.state == SS_GAME || sv.restarting ) { +// SV_SendServerCommand( NULL, "cs %i \"%s\"\n", index, val ); + + // send the data to all relevent clients + for ( i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++ ) { + if ( client->state < CS_PRIMED ) { + continue; + } + // do not always send server info to all clients + if ( index == CS_SERVERINFO && client->gentity && ( client->gentity->r.svFlags & SVF_NOSERVERINFO ) ) { + continue; + } + + // RF, don't send to bot/AI + if ( sv_gametype->integer == GT_SINGLE_PLAYER && client->gentity && ( client->gentity->r.svFlags & SVF_CASTAI ) ) { + continue; + } + +// SV_SendServerCommand( client, "cs %i \"%s\"\n", index, val ); + + len = strlen( val ); + if ( len >= maxChunkSize ) { + int sent = 0; + int remaining = len; + char *cmd; + char buf[MAX_STRING_CHARS]; + + while ( remaining > 0 ) { + if ( sent == 0 ) { + cmd = "bcs0"; + } else if ( remaining < maxChunkSize ) { + cmd = "bcs2"; + } else { + cmd = "bcs1"; + } + Q_strncpyz( buf, &val[sent], maxChunkSize ); + + SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, index, buf ); + + sent += ( maxChunkSize - 1 ); + remaining -= ( maxChunkSize - 1 ); + } + } else { + // standard cs, just send it + SV_SendServerCommand( client, "cs %i \"%s\"\n", index, val ); + } + } + } +} + + + +/* +=============== +SV_GetConfigstring + +=============== +*/ +void SV_GetConfigstring( int index, char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetConfigstring: bufferSize == %i", bufferSize ); + } + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "SV_GetConfigstring: bad index %i\n", index ); + } + if ( !sv.configstrings[index] ) { + buffer[0] = 0; + return; + } + + Q_strncpyz( buffer, sv.configstrings[index], bufferSize ); +} + + +/* +=============== +SV_SetUserinfo + +=============== +*/ +void SV_SetUserinfo( int index, const char *val ) { + if ( index < 0 || index >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_SetUserinfo: bad index %i\n", index ); + } + + if ( !val ) { + val = ""; + } + + Q_strncpyz( svs.clients[index].userinfo, val, sizeof( svs.clients[ index ].userinfo ) ); + Q_strncpyz( svs.clients[index].name, Info_ValueForKey( val, "name" ), sizeof( svs.clients[index].name ) ); +} + + + +/* +=============== +SV_GetUserinfo + +=============== +*/ +void SV_GetUserinfo( int index, char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize ); + } + if ( index < 0 || index >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_GetUserinfo: bad index %i\n", index ); + } + Q_strncpyz( buffer, svs.clients[ index ].userinfo, bufferSize ); +} + + +/* +================ +SV_CreateBaseline + +Entity baselines are used to compress non-delta messages +to the clients -- only the fields that differ from the +baseline will be transmitted +================ +*/ +void SV_CreateBaseline( void ) { + sharedEntity_t *svent; + int entnum; + + for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) { + svent = SV_GentityNum( entnum ); + if ( !svent->r.linked ) { + continue; + } + svent->s.number = entnum; + + // + // take current state as baseline + // + sv.svEntities[entnum].baseline = svent->s; + } +} + + +/* +=============== +SV_BoundMaxClients + +=============== +*/ +void SV_BoundMaxClients( int minimum ) { + // get the current maxclients value +#ifdef __MACOS__ + Cvar_Get( "sv_maxclients", "16", 0 ); //DAJ HOG +#else + Cvar_Get( "sv_maxclients", "20", 0 ); // NERVE - SMF - changed to 20 from 8 +#endif + + sv_maxclients->modified = qfalse; + + if ( sv_maxclients->integer < minimum ) { + Cvar_Set( "sv_maxclients", va( "%i", minimum ) ); + } else if ( sv_maxclients->integer > MAX_CLIENTS ) { + Cvar_Set( "sv_maxclients", va( "%i", MAX_CLIENTS ) ); + } +} + + +/* +=============== +SV_Startup + +Called when a host starts a map when it wasn't running +one before. Successive map or map_restart commands will +NOT cause this to be called, unless the game is exited to +the menu system first. +=============== +*/ +void SV_Startup( void ) { + if ( svs.initialized ) { + Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); + } + SV_BoundMaxClients( 1 ); + + // RF, avoid trying to allocate large chunk on a fragmented zone + svs.clients = calloc( sizeof( client_t ) * sv_maxclients->integer, 1 ); + if ( !svs.clients ) { + Com_Error( ERR_FATAL, "SV_Startup: unable to allocate svs.clients" ); + } + //svs.clients = Z_Malloc (sizeof(client_t) * sv_maxclients->integer ); + + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } + svs.initialized = qtrue; + + Cvar_Set( "sv_running", "1" ); +} + + +/* +================== +SV_ChangeMaxClients +================== +*/ +void SV_ChangeMaxClients( void ) { + int oldMaxClients; + int i; + client_t *oldClients; + int count; + + // get the highest client number in use + count = 0; + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + if ( i > count ) { + count = i; + } + } + } + count++; + + oldMaxClients = sv_maxclients->integer; + // never go below the highest client number in use + SV_BoundMaxClients( count ); + // if still the same + if ( sv_maxclients->integer == oldMaxClients ) { + return; + } + + oldClients = Hunk_AllocateTempMemory( count * sizeof( client_t ) ); + // copy the clients to hunk memory + for ( i = 0 ; i < count ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + oldClients[i] = svs.clients[i]; + } else { + Com_Memset( &oldClients[i], 0, sizeof( client_t ) ); + } + } + + // free old clients arrays + //Z_Free( svs.clients ); + free( svs.clients ); // RF, avoid trying to allocate large chunk on a fragmented zone + + // allocate new clients + // RF, avoid trying to allocate large chunk on a fragmented zone + svs.clients = calloc( sizeof( client_t ) * sv_maxclients->integer, 1 ); + if ( !svs.clients ) { + Com_Error( ERR_FATAL, "SV_Startup: unable to allocate svs.clients" ); + } + //svs.clients = Z_Malloc ( sv_maxclients->integer * sizeof(client_t) ); + + Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof( client_t ) ); + + // copy the clients over + for ( i = 0 ; i < count ; i++ ) { + if ( oldClients[i].state >= CS_CONNECTED ) { + svs.clients[i] = oldClients[i]; + } + } + + // free the old clients on the hunk + Hunk_FreeTempMemory( oldClients ); + + // allocate new snapshot entities + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } +} + + +/* +==================== +SV_SetExpectedHunkUsage + + Sets com_expectedhunkusage, so the client knows how to draw the percentage bar +==================== +*/ +void SV_SetExpectedHunkUsage( char *mapname ) { + int handle; + char *memlistfile = "hunkusage.dat"; + char *buf; + char *buftrav; + char *token; + int len; + + len = FS_FOpenFileByMode( memlistfile, &handle, FS_READ ); + if ( len >= 0 ) { // the file exists, so read it in, strip out the current entry for this map, and save it out, so we can append the new value + + buf = (char *)Z_Malloc( len + 1 ); + memset( buf, 0, len + 1 ); + + FS_Read( (void *)buf, len, handle ); + FS_FCloseFile( handle ); + + // now parse the file, filtering out the current map + buftrav = buf; + while ( ( token = COM_Parse( &buftrav ) ) && token[0] ) { + if ( !Q_strcasecmp( token, mapname ) ) { + // found a match + token = COM_Parse( &buftrav ); // read the size + if ( token && token[0] ) { + // this is the usage + Cvar_Set( "com_expectedhunkusage", token ); + Z_Free( buf ); + return; + } + } + } + + Z_Free( buf ); + } + // just set it to a negative number,so the cgame knows not to draw the percent bar + Cvar_Set( "com_expectedhunkusage", "-1" ); +} + +/* +================ +SV_ClearServer +================ +*/ +void SV_ClearServer( void ) { + int i; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( sv.configstrings[i] ) { + Z_Free( sv.configstrings[i] ); + } + } + Com_Memset( &sv, 0, sizeof( sv ) ); +} + +/* +================ +SV_TouchCGame + + touch the cgame.vm so that a pure client can load it if it's in a seperate pk3 +================ +*/ +void SV_TouchCGame( void ) { + fileHandle_t f; + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "vm/%s.qvm", "cgame" ); + FS_FOpenFileRead( filename, &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } +} + +/* +================ +SV_TouchCGameDLL + touch the cgame DLL so that a pure client (with DLL sv_pure support) can load do the correct checks +================ +*/ +void SV_TouchCGameDLL( void ) { + fileHandle_t f; + char *filename; + + filename = Sys_GetDLLName( "cgame" ); + FS_FOpenFileRead_Filtered( filename, &f, qfalse, FS_EXCLUDE_DIR ); + if ( f ) { + FS_FCloseFile( f ); + } +} + +/* +================ +SV_SpawnServer + +Change the server to a new map, taking all connected +clients along with it. +This is NOT called for map_restart +================ +*/ +void SV_SpawnServer( char *server, qboolean killBots ) { + int i; + int checksum; + qboolean isBot; + char systemInfo[MAX_INFO_STRING]; + const char *p; + + // shut down the existing game if it is running + SV_ShutdownGameProgs(); + + Com_Printf( "------ Server Initialization ------\n" ); + Com_Printf( "Server: %s\n",server ); + + // if not running a dedicated server CL_MapLoading will connect the client to the server + // also print some status stuff + CL_MapLoading(); + + // make sure all the client stuff is unloaded + CL_ShutdownAll(); + + // clear the whole hunk because we're (re)loading the server + Hunk_Clear(); + + // clear collision map data // (SA) NOTE: TODO: used in missionpack + CM_ClearMap(); + + // wipe the entire per-level structure + SV_ClearServer(); + + // MrE: main zone should be pretty much emtpy at this point + // except for file system data and cached renderer data + Z_LogHeap(); + + // allocate empty config strings + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + sv.configstrings[i] = CopyString( "" ); + } + + // init client structures and svs.numSnapshotEntities + if ( !Cvar_VariableValue( "sv_running" ) ) { + SV_Startup(); + } else { + // check for maxclients change + if ( sv_maxclients->modified ) { + SV_ChangeMaxClients(); + } + } + + // clear pak references + FS_ClearPakReferences( 0 ); + + // allocate the snapshot entities on the hunk + svs.snapshotEntities = Hunk_Alloc( sizeof( entityState_t ) * svs.numSnapshotEntities, h_high ); + svs.nextSnapshotEntities = 0; + + // toggle the server bit so clients can detect that a + // server has changed + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + // set nextmap to the same map, but it may be overriden + // by the game startup or another console command + Cvar_Set( "nextmap", "map_restart 0" ); +// Cvar_Set( "nextmap", va("map %s", server) ); + + // Ridah + // DHM - Nerve :: We want to use the completion bar in multiplayer as well + if ( sv_gametype->integer == GT_SINGLE_PLAYER || sv_gametype->integer >= GT_WOLF ) { + SV_SetExpectedHunkUsage( va( "maps/%s.bsp", server ) ); + } else { + // just set it to a negative number,so the cgame knows not to draw the percent bar + Cvar_Set( "com_expectedhunkusage", "-1" ); + } + + // make sure we are not paused + Cvar_Set( "cl_paused", "0" ); + +#if !defined( DO_LIGHT_DEDICATED ) + // get a new checksum feed and restart the file system + srand( Sys_Milliseconds() ); + sv.checksumFeed = ( ( (int) rand() << 16 ) ^ rand() ) ^ Sys_Milliseconds(); + + // DO_LIGHT_DEDICATED + // only comment out when you need a new pure checksum string and it's associated random feed + //Com_DPrintf("SV_SpawnServer checksum feed: %p\n", sv.checksumFeed); + +#else // DO_LIGHT_DEDICATED implementation below + // we are not able to randomize the checksum feed since the feed is used as key for pure_checksum computations + // files.c 1776 : pack->pure_checksum = Com_BlockChecksumKey( fs_headerLongs, 4 * fs_numHeaderLongs, LittleLong(fs_checksumFeed) ); + // we request a fake randomized feed, files.c knows the answer + srand( Sys_Milliseconds() ); + sv.checksumFeed = FS_RandChecksumFeed(); +#endif + FS_Restart( sv.checksumFeed ); + + CM_LoadMap( va( "maps/%s.bsp", server ), qfalse, &checksum ); + + // set serverinfo visible name + Cvar_Set( "mapname", server ); + + Cvar_Set( "sv_mapChecksum", va( "%i",checksum ) ); + + // serverid should be different each time + sv.serverId = com_frameTime; + sv.restartedServerId = sv.serverId; + sv.checksumFeedServerId = sv.serverId; + Cvar_Set( "sv_serverid", va( "%i", sv.serverId ) ); + + // clear physics interaction links + SV_ClearWorld(); + + // media configstring setting should be done during + // the loading stage, so connected clients don't have + // to load during actual gameplay + sv.state = SS_LOADING; + + Cvar_Set( "sv_serverRestarting", "1" ); + + // load and spawn all other entities + SV_InitGameProgs(); + + // don't allow a map_restart if game is modified + sv_gametype->modified = qfalse; + + // run a few frames to allow everything to settle + for ( i = 0 ; i < 3 ; i++ ) { + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + SV_BotFrame( svs.time ); + svs.time += 100; + } + + // create a baseline for more efficient communications + SV_CreateBaseline(); + + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + // send the new gamestate to all connected clients + if ( svs.clients[i].state >= CS_CONNECTED ) { + char *denied; + + if ( svs.clients[i].netchan.remoteAddress.type == NA_BOT ) { + if ( killBots || Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) { + SV_DropClient( &svs.clients[i], "" ); + continue; + } + isBot = qtrue; + } else { + isBot = qfalse; + } + + // connect the client again + denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); // firstTime = qfalse + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( &svs.clients[i], denied ); + } else { + if ( !isBot ) { + // when we get the next packet from a connected client, + // the new gamestate will be sent + svs.clients[i].state = CS_CONNECTED; + } else { + client_t *client; + sharedEntity_t *ent; + + client = &svs.clients[i]; + client->state = CS_ACTIVE; + ent = SV_GentityNum( i ); + ent->s.number = i; + client->gentity = ent; + + client->deltaMessage = -1; + client->nextSnapshotTime = svs.time; // generate a snapshot immediately + + VM_Call( gvm, GAME_CLIENT_BEGIN, i ); + } + } + } + } + + // run another frame to allow things to look at all the players + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + SV_BotFrame( svs.time ); + svs.time += 100; + + if ( sv_pure->integer ) { + // the server sends these to the clients so they will only + // load pk3s also loaded at the server + p = FS_LoadedPakChecksums(); + Cvar_Set( "sv_paks", p ); + if ( strlen( p ) == 0 ) { + Com_Printf( "WARNING: sv_pure set but no PK3 files loaded\n" ); + } + p = FS_LoadedPakNames(); + Cvar_Set( "sv_pakNames", p ); + } else { + Cvar_Set( "sv_paks", "" ); + Cvar_Set( "sv_pakNames", "" ); + } + // the server sends these to the clients so they can figure + // out which pk3s should be auto-downloaded + // NOTE: we consider the referencedPaks as 'required for operation' + + // we want the server to reference the mp_bin pk3 that the client is expected to load from + SV_TouchCGameDLL(); + + p = FS_ReferencedPakChecksums(); + Cvar_Set( "sv_referencedPaks", p ); + p = FS_ReferencedPakNames(); + Cvar_Set( "sv_referencedPakNames", p ); + + // save systeminfo and serverinfo strings + Q_strncpyz( systemInfo, Cvar_InfoString_Big( CVAR_SYSTEMINFO ), sizeof( systemInfo ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + SV_SetConfigstring( CS_SYSTEMINFO, systemInfo ); + + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + + // NERVE - SMF + SV_SetConfigstring( CS_WOLFINFO, Cvar_InfoString( CVAR_WOLFINFO ) ); + cvar_modifiedFlags &= ~CVAR_WOLFINFO; + + // any media configstring setting now should issue a warning + // and any configstring changes should be reliably transmitted + // to all clients + sv.state = SS_GAME; + + // send a heartbeat now so the master will get up to date info + SV_Heartbeat_f(); + + Hunk_SetMark(); + + Cvar_Set( "sv_serverRestarting", "0" ); + + Com_Printf( "-----------------------------------\n" ); +} + +// DHM - Nerve :: Update Server +#ifdef UPDATE_SERVER +/* +==================== +SV_ParseVersionMapping + + Reads versionmap.cfg which sets up a mapping of client version to installer to download +==================== +*/ +void SV_ParseVersionMapping( void ) { + int handle; + char *filename = "versionmap.cfg"; + char *buf; + char *buftrav; + char *token; + int len; + + len = FS_SV_FOpenFileRead( filename, &handle ); + if ( len >= 0 ) { // the file exists + + buf = (char *)Z_Malloc( len + 1 ); + memset( buf, 0, len + 1 ); + + FS_Read( (void *)buf, len, handle ); + FS_FCloseFile( handle ); + + // now parse the file, setting the version table info + buftrav = buf; + + token = COM_Parse( &buftrav ); + if ( strcmp( token, "RTCW-VersionMap" ) ) { + Z_Free( buf ); + Com_Error( ERR_FATAL, "invalid versionmap.cfg" ); + return; + } + + Com_Printf( "\n------------Update Server-------------\n\nParsing version map..." ); + + while ( ( token = COM_Parse( &buftrav ) ) && token[0] ) { + // read the version number + strcpy( versionMap[ numVersions ].version, token ); + + // read the platform + token = COM_Parse( &buftrav ); + if ( token && token[0] ) { + strcpy( versionMap[ numVersions ].platform, token ); + } else { + Z_Free( buf ); + Com_Error( ERR_FATAL, "error parsing versionmap.cfg, after %s", versionMap[ numVersions ].version ); + return; + } + + // read the installer name + token = COM_Parse( &buftrav ); + if ( token && token[0] ) { + strcpy( versionMap[ numVersions ].installer, token ); + } else { + Z_Free( buf ); + Com_Error( ERR_FATAL, "error parsing versionmap.cfg, after %s", versionMap[ numVersions ].platform ); + return; + } + + numVersions++; + if ( numVersions >= MAX_UPDATE_VERSIONS ) { + Z_Free( buf ); + Com_Error( ERR_FATAL, "Exceeded maximum number of mappings(%d)", MAX_UPDATE_VERSIONS ); + return; + } + + } + + Com_Printf( " found %d mapping%c\n--------------------------------------\n\n", numVersions, numVersions > 1 ? 's' : ' ' ); + + Z_Free( buf ); + } else { + Com_Error( ERR_FATAL, "Couldn't open versionmap.cfg" ); + } +} +#endif + +/* +=============== +SV_Init + +Only called at main exe startup, not for each game +=============== +*/ +void SV_BotInitBotLib( void ); + +void SV_Init( void ) { + SV_AddOperatorCommands(); + + // serverinfo vars + Cvar_Get( "dmflags", "0", /*CVAR_SERVERINFO*/ 0 ); + Cvar_Get( "fraglimit", "0", /*CVAR_SERVERINFO*/ 0 ); + Cvar_Get( "timelimit", "0", CVAR_SERVERINFO ); + // DHM - Nerve :: default to GT_WOLF + sv_gametype = Cvar_Get( "g_gametype", "5", CVAR_SERVERINFO | CVAR_LATCH ); + + // Rafael gameskill + sv_gameskill = Cvar_Get( "g_gameskill", "3", CVAR_SERVERINFO | CVAR_LATCH ); + // done + + Cvar_Get( "sv_keywords", "", CVAR_SERVERINFO ); + Cvar_Get( "protocol", va( "%i", PROTOCOL_VERSION ), CVAR_SERVERINFO | CVAR_ROM ); + sv_mapname = Cvar_Get( "mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM ); + sv_privateClients = Cvar_Get( "sv_privateClients", "0", CVAR_SERVERINFO ); + sv_hostname = Cvar_Get( "sv_hostname", "WolfHost", CVAR_SERVERINFO | CVAR_ARCHIVE ); +#ifdef __MACOS__ + sv_maxclients = Cvar_Get( "sv_maxclients", "16", CVAR_SERVERINFO | CVAR_LATCH ); //DAJ HOG +#else + sv_maxclients = Cvar_Get( "sv_maxclients", "20", CVAR_SERVERINFO | CVAR_LATCH ); // NERVE - SMF - changed to 20 from 8 +#endif + + sv_maxRate = Cvar_Get( "sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_minPing = Cvar_Get( "sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_maxPing = Cvar_Get( "sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_floodProtect = Cvar_Get( "sv_floodProtect", "1", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_allowAnonymous = Cvar_Get( "sv_allowAnonymous", "0", CVAR_SERVERINFO ); + sv_friendlyFire = Cvar_Get( "g_friendlyFire", "1", CVAR_SERVERINFO | CVAR_ARCHIVE ); // NERVE - SMF + sv_maxlives = Cvar_Get( "g_maxlives", "0", CVAR_ARCHIVE | CVAR_LATCH | CVAR_SERVERINFO ); // NERVE - SMF + sv_tourney = Cvar_Get( "g_noTeamSwitching", "0", CVAR_ARCHIVE ); // NERVE - SMF + + // systeminfo + Cvar_Get( "sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM ); + sv_serverid = Cvar_Get( "sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); + sv_pure = Cvar_Get( "sv_pure", "1", CVAR_SYSTEMINFO ); + Cvar_Get( "sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get( "sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get( "sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get( "sv_referencedPakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); + + // server vars + sv_rconPassword = Cvar_Get( "rconPassword", "", CVAR_TEMP ); + sv_privatePassword = Cvar_Get( "sv_privatePassword", "", CVAR_TEMP ); +#ifndef UPDATE_SERVER + sv_fps = Cvar_Get( "sv_fps", "20", CVAR_TEMP ); +#else + sv_fps = Cvar_Get( "sv_fps", "60", CVAR_TEMP ); // this allows faster downloads +#endif + sv_timeout = Cvar_Get( "sv_timeout", "240", CVAR_TEMP ); + sv_zombietime = Cvar_Get( "sv_zombietime", "2", CVAR_TEMP ); + Cvar_Get( "nextmap", "", CVAR_TEMP ); + + sv_allowDownload = Cvar_Get( "sv_allowDownload", "1", CVAR_ARCHIVE ); + sv_master[0] = Cvar_Get( "sv_master1", "wolfmaster.idsoftware.com", 0 ); // NERVE - SMF - wolfMP master server + sv_master[1] = Cvar_Get( "sv_master2", "", CVAR_ARCHIVE ); + sv_master[2] = Cvar_Get( "sv_master3", "", CVAR_ARCHIVE ); + sv_master[3] = Cvar_Get( "sv_master4", "", CVAR_ARCHIVE ); + sv_master[4] = Cvar_Get( "sv_master5", "", CVAR_ARCHIVE ); + sv_reconnectlimit = Cvar_Get( "sv_reconnectlimit", "3", 0 ); + sv_showloss = Cvar_Get( "sv_showloss", "0", 0 ); + sv_padPackets = Cvar_Get( "sv_padPackets", "0", 0 ); + sv_killserver = Cvar_Get( "sv_killserver", "0", 0 ); + sv_mapChecksum = Cvar_Get( "sv_mapChecksum", "", CVAR_ROM ); + sv_lanForceRate = Cvar_Get( "sv_lanForceRate", "1", CVAR_ARCHIVE ); + + sv_onlyVisibleClients = Cvar_Get( "sv_onlyVisibleClients", "0", 0 ); // DHM - Nerve + + sv_showAverageBPS = Cvar_Get( "sv_showAverageBPS", "0", 0 ); // NERVE - SMF - net debugging + + // NERVE - SMF - create user set cvars + Cvar_Get( "g_userTimeLimit", "0", 0 ); + Cvar_Get( "g_userAlliedRespawnTime", "0", 0 ); + Cvar_Get( "g_userAxisRespawnTime", "0", 0 ); + Cvar_Get( "g_maxlives", "0", 0 ); + Cvar_Get( "g_noTeamSwitching", "0", CVAR_ARCHIVE ); + Cvar_Get( "g_altStopwatchMode", "0", CVAR_ARCHIVE ); + Cvar_Get( "g_minGameClients", "8", CVAR_SERVERINFO ); + Cvar_Get( "g_complaintlimit", "3", CVAR_ARCHIVE ); + Cvar_Get( "gamestate", "-1", CVAR_WOLFINFO | CVAR_ROM ); + Cvar_Get( "g_currentRound", "0", CVAR_WOLFINFO ); + Cvar_Get( "g_nextTimeLimit", "0", CVAR_WOLFINFO ); + // -NERVE - SMF + + // TTimo - some UI additions + // NOTE: sucks to have this hardcoded really, I suppose this should be in UI + Cvar_Get( "g_axismaxlives", "0", 0 ); + Cvar_Get( "g_alliedmaxlives", "0", 0 ); + Cvar_Get( "g_fastres", "0", CVAR_ARCHIVE ); + Cvar_Get( "g_fastResMsec", "1000", CVAR_ARCHIVE ); + + // ATVI Tracker Wolfenstein Misc #273 + Cvar_Get( "g_voteFlags", "255", CVAR_ARCHIVE | CVAR_SERVERINFO ); + + // ATVI Tracker Wolfenstein Misc #263 + Cvar_Get( "g_antilag", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + + // TTimo - autodownload speed tweaks +#ifndef UPDATE_SERVER + // the download netcode tops at 18/20 kb/s, no need to make you think you can go above + sv_dl_maxRate = Cvar_Get( "sv_dl_maxRate", "42000", CVAR_ARCHIVE ); +#else + // the update server is on steroids, sv_fps 60 and no snapshotMsec limitation, it can go up to 30 kb/s + sv_dl_maxRate = Cvar_Get( "sv_dl_maxRate", "60000", CVAR_ARCHIVE ); +#endif + + // initialize bot cvars so they are listed and can be set before loading the botlib + SV_BotInitCvars(); + + // init the botlib here because we need the pre-compiler in the UI + SV_BotInitBotLib(); + + // DHM - Nerve +#ifdef UPDATE_SERVER + SV_Startup(); + SV_ParseVersionMapping(); + + // serverid should be different each time + sv.serverId = com_frameTime + 100; + sv.restartedServerId = sv.serverId; // I suppose the init here is just to be safe + sv.checksumFeedServerId = sv.serverId; + Cvar_Set( "sv_serverid", va( "%i", sv.serverId ) ); + Cvar_Set( "mapname", "Update" ); + + // allocate empty config strings + { + int i; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + sv.configstrings[i] = CopyString( "" ); + } + } +#endif +} + + +/* +================== +SV_FinalMessage + +Used by SV_Shutdown to send a final message to all +connected clients before the server goes down. The messages are sent immediately, +not just stuck on the outgoing message list, because the server is going +to totally exit after returning from this function. +================== +*/ +void SV_FinalMessage( char *message ) { + int i, j; + client_t *cl; + + // send it twice, ignoring rate + for ( j = 0 ; j < 2 ; j++ ) { + for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++ ) { + if ( cl->state >= CS_CONNECTED ) { + // don't send a disconnect to a local client + if ( cl->netchan.remoteAddress.type != NA_LOOPBACK ) { + SV_SendServerCommand( cl, "print \"%s\"", message ); + SV_SendServerCommand( cl, "disconnect" ); + } + // force a snapshot to be sent + cl->nextSnapshotTime = -1; + SV_SendClientSnapshot( cl ); + } + } + } +} + + +/* +================ +SV_Shutdown + +Called when each game quits, +before Sys_Quit or Sys_Error +================ +*/ +void SV_Shutdown( char *finalmsg ) { + if ( !com_sv_running || !com_sv_running->integer ) { + return; + } + + Com_Printf( "----- Server Shutdown -----\n" ); + + if ( svs.clients && !com_errorEntered ) { + SV_FinalMessage( finalmsg ); + } + + SV_RemoveOperatorCommands(); + SV_MasterShutdown(); + SV_ShutdownGameProgs(); + + // free current level + SV_ClearServer(); + + // free server static data + if ( svs.clients ) { + //Z_Free( svs.clients ); + free( svs.clients ); // RF, avoid trying to allocate large chunk on a fragmented zone + } + memset( &svs, 0, sizeof( svs ) ); + + Cvar_Set( "sv_running", "0" ); + + Com_Printf( "---------------------------\n" ); + + // disconnect any local clients + CL_Disconnect( qfalse ); +} + diff --git a/src/server/sv_main.c b/src/server/sv_main.c new file mode 100644 index 0000000..effc4d2 --- /dev/null +++ b/src/server/sv_main.c @@ -0,0 +1,1097 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "server.h" + +serverStatic_t svs; // persistant server info +server_t sv; // local server +vm_t *gvm = NULL; // game virtual machine // bk001212 init + +#ifdef UPDATE_SERVER +versionMapping_t versionMap[MAX_UPDATE_VERSIONS]; +int numVersions = 0; +#endif + +cvar_t *sv_fps; // time rate for running non-clients +cvar_t *sv_timeout; // seconds without any message +cvar_t *sv_zombietime; // seconds to sink messages after disconnect +cvar_t *sv_rconPassword; // password for remote server commands +cvar_t *sv_privatePassword; // password for the privateClient slots +cvar_t *sv_allowDownload; +cvar_t *sv_maxclients; + +cvar_t *sv_privateClients; // number of clients reserved for password +cvar_t *sv_hostname; +cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address +cvar_t *sv_reconnectlimit; // minimum seconds between connect messages +cvar_t *sv_showloss; // report when usercmds are lost +cvar_t *sv_padPackets; // add nop bytes to messages +cvar_t *sv_killserver; // menu system can set to 1 to shut server down +cvar_t *sv_mapname; +cvar_t *sv_mapChecksum; +cvar_t *sv_serverid; +cvar_t *sv_maxRate; +cvar_t *sv_minPing; +cvar_t *sv_maxPing; +cvar_t *sv_gametype; +cvar_t *sv_pure; +cvar_t *sv_floodProtect; +cvar_t *sv_allowAnonymous; +cvar_t *sv_lanForceRate; // TTimo - dedicated 1 (LAN) server forces local client rates to 99999 (bug #491) +cvar_t *sv_onlyVisibleClients; // DHM - Nerve +cvar_t *sv_friendlyFire; // NERVE - SMF +cvar_t *sv_maxlives; // NERVE - SMF +cvar_t *sv_tourney; // NERVE - SMF + +cvar_t *sv_dl_maxRate; + +// Rafael gameskill +cvar_t *sv_gameskill; +// done + +cvar_t *sv_showAverageBPS; // NERVE - SMF - net debugging + +void SVC_GameCompleteStatus( netadr_t from ); // NERVE - SMF + +/* +============================================================================= + +EVENT MESSAGES + +============================================================================= +*/ + +/* +=============== +SV_ExpandNewlines + +Converts newlines to "\n" so a line prints nicer +=============== +*/ +char *SV_ExpandNewlines( char *in ) { + static char string[1024]; + int l; + + l = 0; + while ( *in && l < sizeof( string ) - 3 ) { + if ( *in == '\n' ) { + string[l++] = '\\'; + string[l++] = 'n'; + } else { + // NERVE - SMF - HACK - strip out localization tokens before string command is displayed in syscon window + if ( !Q_strncmp( in, "[lon]", 5 ) || !Q_strncmp( in, "[lof]", 5 ) ) { + in += 5; + continue; + } + + string[l++] = *in; + } + in++; + } + string[l] = 0; + + return string; +} + +/* +====================== +SV_AddServerCommand + +The given command will be transmitted to the client, and is guaranteed to +not have future snapshot_t executed before it is executed +====================== +*/ +void SV_AddServerCommand( client_t *client, const char *cmd ) { + int index, i; + + client->reliableSequence++; + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + // we check == instead of >= so a broadcast print added by SV_DropClient() + // doesn't cause a recursive drop client + if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) { + Com_Printf( "===== pending server commands =====\n" ); + for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { + Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & ( MAX_RELIABLE_COMMANDS - 1 ) ] ); + } + Com_Printf( "cmd %5d: %s\n", i, cmd ); + SV_DropClient( client, "Server command overflow" ); + return; + } + index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) ); +} + + +/* +================= +SV_SendServerCommand + +Sends a reliable command string to be interpreted by +the client game module: "cp", "print", "chat", etc +A NULL client will broadcast to all clients +================= +*/ +void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ... ) { + va_list argptr; + byte message[MAX_MSGLEN]; + client_t *client; + int j; + + va_start( argptr,fmt ); + Q_vsnprintf( (char *)message, sizeof( message ), fmt, argptr ); + va_end( argptr ); + + // do not forward server command messages that would be too big to clients + // ( q3infoboom / q3msgboom stuff ) + if ( strlen( (char *)message ) > 1022 ) { + return; + } + + if ( cl != NULL ) { + SV_AddServerCommand( cl, (char *)message ); + return; + } + + // hack to echo broadcast prints to console + if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5 ) ) { + Com_Printf( "broadcast: %s\n", SV_ExpandNewlines( (char *)message ) ); + } + + // send the data to all relevent clients + for ( j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++ ) { + if ( client->state < CS_PRIMED ) { + continue; + } + // Ridah, don't need to send messages to AI + if ( client->gentity && client->gentity->r.svFlags & SVF_CASTAI ) { + continue; + } + // done. + SV_AddServerCommand( client, (char *)message ); + } +} + + +/* +============================================================================== + +MASTER SERVER FUNCTIONS + +============================================================================== +*/ + +/* +================ +SV_MasterHeartbeat + +Send a message to the masters every few minutes to +let it know we are alive, and log information. +We will also have a heartbeat sent when a server +changes from empty to non-empty, and full to non-full, +but not on every player enter or exit. +================ +*/ +#define HEARTBEAT_MSEC 300 * 1000 +#define HEARTBEAT_GAME "Wolfenstein-1" +#define HEARTBEAT_DEAD "WolfFlatline-1" // NERVE - SMF + +void SV_MasterHeartbeat( const char *hbname ) { + static netadr_t adr[MAX_MASTER_SERVERS]; + int i; + + // DHM - Nerve :: Update Server doesn't send heartbeat +#ifdef UPDATE_SERVER + return; +#endif + + // "dedicated 1" is for lan play, "dedicated 2" is for inet public play + if ( !com_dedicated || com_dedicated->integer != 2 ) { + return; // only dedicated servers send heartbeats + } + + // if not time yet, don't send anything + if ( svs.time < svs.nextHeartbeatTime ) { + return; + } + svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC; + + + // send to group masters + for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) { + if ( !sv_master[i]->string[0] ) { + continue; + } + + // see if we haven't already resolved the name + // resolving usually causes hitches on win95, so only + // do it when needed + if ( sv_master[i]->modified ) { + sv_master[i]->modified = qfalse; + + Com_Printf( "Resolving %s\n", sv_master[i]->string ); + if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) { + // if the address failed to resolve, clear it + // so we don't take repeated dns hits + Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string ); + Cvar_Set( sv_master[i]->name, "" ); + sv_master[i]->modified = qfalse; + continue; + } + if ( !strstr( ":", sv_master[i]->string ) ) { + adr[i].port = BigShort( PORT_MASTER ); + } + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string, + adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3], + BigShort( adr[i].port ) ); + } + + + Com_Printf( "Sending heartbeat to %s\n", sv_master[i]->string ); + // this command should be changed if the server info / status format + // ever incompatably changes + NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", hbname ); + } +} + +/* +================= +SV_MasterGameCompleteStatus + +NERVE - SMF - Sends gameCompleteStatus messages to all master servers +================= +*/ +void SV_MasterGameCompleteStatus() { + static netadr_t adr[MAX_MASTER_SERVERS]; + int i; + + // "dedicated 1" is for lan play, "dedicated 2" is for inet public play + if ( !com_dedicated || com_dedicated->integer != 2 ) { + return; // only dedicated servers send master game status + } + + // send to group masters + for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) { + if ( !sv_master[i]->string[0] ) { + continue; + } + + // see if we haven't already resolved the name + // resolving usually causes hitches on win95, so only + // do it when needed + if ( sv_master[i]->modified ) { + sv_master[i]->modified = qfalse; + + Com_Printf( "Resolving %s\n", sv_master[i]->string ); + if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) { + // if the address failed to resolve, clear it + // so we don't take repeated dns hits + Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string ); + Cvar_Set( sv_master[i]->name, "" ); + sv_master[i]->modified = qfalse; + continue; + } + if ( !strstr( ":", sv_master[i]->string ) ) { + adr[i].port = BigShort( PORT_MASTER ); + } + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string, + adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3], + BigShort( adr[i].port ) ); + } + + Com_Printf( "Sending gameCompleteStatus to %s\n", sv_master[i]->string ); + // this command should be changed if the server info / status format + // ever incompatably changes + SVC_GameCompleteStatus( adr[i] ); + } +} + +/* +================= +SV_MasterShutdown + +Informs all masters that this server is going down +================= +*/ +void SV_MasterShutdown( void ) { + // send a hearbeat right now + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat( HEARTBEAT_DEAD ); // NERVE - SMF - changed to flatline + + // send it again to minimize chance of drops +// svs.nextHeartbeatTime = -9999; +// SV_MasterHeartbeat( HEARTBEAT_DEAD ); + + // when the master tries to poll the server, it won't respond, so + // it will be removed from the list +} + + +/* +============================================================================== + +CONNECTIONLESS COMMANDS + +============================================================================== +*/ + +/* +================ +SVC_Status + +Responds with all the info that qplug or qspy can see about the server +and all connected players. Used for getting detailed information after +the simple info query. +================ +*/ +void SVC_Status( netadr_t from ) { + char player[1024]; + char status[MAX_MSGLEN]; + int i; + client_t *cl; + playerState_t *ps; + int statusLength; + int playerLength; + char infostring[MAX_INFO_STRING]; + + // ignore if we are in single player + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) { + return; + } + + // DHM - Nerve +#ifdef UPDATE_SERVER + return; +#endif + + strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); + + // echo back the parameter to status. so master servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv( 1 ) ); + + // add "demo" to the sv_keywords if restricted + if ( Cvar_VariableValue( "fs_restrict" ) ) { + char keywords[MAX_INFO_STRING]; + + Com_sprintf( keywords, sizeof( keywords ), "demo %s", + Info_ValueForKey( infostring, "sv_keywords" ) ); + Info_SetValueForKey( infostring, "sv_keywords", keywords ); + } + + status[0] = 0; + statusLength = 0; + + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if ( cl->state >= CS_CONNECTED ) { + ps = SV_GameClientNum( i ); + Com_sprintf( player, sizeof( player ), "%i %i \"%s\"\n", + ps->persistant[PERS_SCORE], cl->ping, cl->name ); + playerLength = strlen( player ); + if ( statusLength + playerLength >= sizeof( status ) ) { + break; // can't hold any more + } + strcpy( status + statusLength, player ); + statusLength += playerLength; + } + } + + NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status ); +} + +/* +================= +SVC_GameCompleteStatus + +NERVE - SMF - Send serverinfo cvars, etc to master servers when +game complete. Useful for tracking global player stats. +================= +*/ +void SVC_GameCompleteStatus( netadr_t from ) { + char player[1024]; + char status[MAX_MSGLEN]; + int i; + client_t *cl; + playerState_t *ps; + int statusLength; + int playerLength; + char infostring[MAX_INFO_STRING]; + + // ignore if we are in single player + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) { + return; + } + + strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); + + // echo back the parameter to status. so master servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv( 1 ) ); + + // add "demo" to the sv_keywords if restricted + if ( Cvar_VariableValue( "fs_restrict" ) ) { + char keywords[MAX_INFO_STRING]; + + Com_sprintf( keywords, sizeof( keywords ), "demo %s", + Info_ValueForKey( infostring, "sv_keywords" ) ); + Info_SetValueForKey( infostring, "sv_keywords", keywords ); + } + + status[0] = 0; + statusLength = 0; + + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if ( cl->state >= CS_CONNECTED ) { + ps = SV_GameClientNum( i ); + Com_sprintf( player, sizeof( player ), "%i %i \"%s\"\n", + ps->persistant[PERS_SCORE], cl->ping, cl->name ); + playerLength = strlen( player ); + if ( statusLength + playerLength >= sizeof( status ) ) { + break; // can't hold any more + } + strcpy( status + statusLength, player ); + statusLength += playerLength; + } + } + + NET_OutOfBandPrint( NS_SERVER, from, "gameCompleteStatus\n%s\n%s", infostring, status ); +} + +/* +================ +SVC_Info + +Responds with a short info message that should be enough to determine +if a user is interested in a server to do a full status +================ +*/ +void SVC_Info( netadr_t from ) { + int i, count; + char *gamedir; + char infostring[MAX_INFO_STRING]; + char *antilag; + + // DHM - Nerve +#ifdef UPDATE_SERVER + return; +#endif + + // ignore if we are in single player + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) { + return; + } + + // don't count privateclients + count = 0; + for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + + infostring[0] = 0; + + // echo back the parameter to status. so servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv( 1 ) ); + + Info_SetValueForKey( infostring, "protocol", va( "%i", PROTOCOL_VERSION ) ); + Info_SetValueForKey( infostring, "hostname", sv_hostname->string ); + Info_SetValueForKey( infostring, "mapname", sv_mapname->string ); + Info_SetValueForKey( infostring, "clients", va( "%i", count ) ); + Info_SetValueForKey( infostring, "sv_maxclients", + va( "%i", sv_maxclients->integer - sv_privateClients->integer ) ); + Info_SetValueForKey( infostring, "gametype", va( "%i", sv_gametype->integer ) ); + Info_SetValueForKey( infostring, "pure", va( "%i", sv_pure->integer ) ); + + if ( sv_minPing->integer ) { + Info_SetValueForKey( infostring, "minPing", va( "%i", sv_minPing->integer ) ); + } + if ( sv_maxPing->integer ) { + Info_SetValueForKey( infostring, "maxPing", va( "%i", sv_maxPing->integer ) ); + } + gamedir = Cvar_VariableString( "fs_game" ); + if ( *gamedir ) { + Info_SetValueForKey( infostring, "game", gamedir ); + } + Info_SetValueForKey( infostring, "sv_allowAnonymous", va( "%i", sv_allowAnonymous->integer ) ); + + // Rafael gameskill + Info_SetValueForKey( infostring, "gameskill", va( "%i", sv_gameskill->integer ) ); + // done + + Info_SetValueForKey( infostring, "friendlyFire", va( "%i", sv_friendlyFire->integer ) ); // NERVE - SMF + Info_SetValueForKey( infostring, "maxlives", va( "%i", sv_maxlives->integer ? 1 : 0 ) ); // NERVE - SMF + Info_SetValueForKey( infostring, "tourney", va( "%i", sv_tourney->integer ) ); // NERVE - SMF + Info_SetValueForKey( infostring, "gamename", GAMENAME_STRING ); // Arnout: to be able to filter out Quake servers + + // TTimo + antilag = Cvar_VariableString( "g_antilag" ); + if ( antilag ) { + Info_SetValueForKey( infostring, "g_antilag", antilag ); + } + + NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring ); +} + +// DHM - Nerve +#ifdef UPDATE_SERVER +/* +================ +SVC_GetUpdateInfo + +Responds with a short info message that tells the client if they +have an update available for their version +================ +*/ +void SVC_GetUpdateInfo( netadr_t from ) { + char *version; + char *platform; + int i; + qboolean found = qfalse; + + version = Cmd_Argv( 1 ); + platform = Cmd_Argv( 2 ); + + Com_DPrintf( "SVC_GetUpdateInfo: version == %s / %s,\n", version, platform ); + + for ( i = 0; i < numVersions; i++ ) { + if ( !strcmp( versionMap[i].version, version ) && + !strcmp( versionMap[i].platform, platform ) ) { + + // If the installer is set to "current", we will skip over it + if ( strcmp( versionMap[i].installer, "current" ) ) { + found = qtrue; + } + + break; + } + } + + if ( found ) { + NET_OutOfBandPrint( NS_SERVER, from, "updateResponse 1 %s", versionMap[i].installer ); + Com_DPrintf( " SENT: updateResponse 1 %s\n", versionMap[i].installer ); + } else { + NET_OutOfBandPrint( NS_SERVER, from, "updateResponse 0" ); + Com_DPrintf( " SENT: updateResponse 0\n" ); + } +} +#endif +// DHM - Nerve + +/* +============== +SV_FlushRedirect + +============== +*/ +void SV_FlushRedirect( char *outputbuf ) { + NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf ); +} + +/* +=============== +SVC_RemoteCommand + +An rcon packet arrived from the network. +Shift down the remaining args +Redirect all printfs +=============== +*/ +void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { + qboolean valid; + unsigned int time; + char remaining[1024]; + // show_bug.cgi?id=376 + // if we send an OOB print message this size, 1.31 clients die in a Com_Printf buffer overflow + // the buffer overflow will be fixed in > 1.31 clients + // but we want a server side fix + // we must NEVER send an OOB message that will be > 1.31 MAXPRINTMSG (4096) +#define SV_OUTPUTBUF_LENGTH ( 256 - 16 ) + char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; + static unsigned int lasttime = 0; + char *cmd_aux; + + // TTimo - show_bug.cgi?id=534 + time = Com_Milliseconds(); + if ( time < ( lasttime + 500 ) ) { + return; + } + lasttime = time; + + if ( !strlen( sv_rconPassword->string ) || + strcmp( Cmd_Argv( 1 ), sv_rconPassword->string ) ) { + valid = qfalse; + Com_Printf( "Bad rcon from %s:\n%s\n", NET_AdrToString( from ), Cmd_Argv( 2 ) ); + } else { + valid = qtrue; + Com_Printf( "Rcon from %s:\n%s\n", NET_AdrToString( from ), Cmd_Argv( 2 ) ); + } + + // start redirecting all print outputs to the packet + svs.redirectAddress = from; + // FIXME TTimo our rcon redirection could be improved + // big rcon commands such as status lead to sending + // out of band packets on every single call to Com_Printf + // which leads to client overflows + // see show_bug.cgi?id=51 + // (also a Q3 issue) + Com_BeginRedirect( sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect ); + + if ( !strlen( sv_rconPassword->string ) ) { + Com_Printf( "No rconpassword set on the server.\n" ); + } else if ( !valid ) { + Com_Printf( "Bad rconpassword.\n" ); + } else { + remaining[0] = 0; + + // ATVI Wolfenstein Misc #284 + // get the command directly, "rcon " to avoid quoting issues + // extract the command by walking + // since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing + cmd_aux = Cmd_Cmd(); + cmd_aux += 4; + while ( cmd_aux[0] == ' ' ) + cmd_aux++; + while ( cmd_aux[0] && cmd_aux[0] != ' ' ) // password + cmd_aux++; + while ( cmd_aux[0] == ' ' ) + cmd_aux++; + + Q_strcat( remaining, sizeof( remaining ), cmd_aux ); + + Cmd_ExecuteString( remaining ); + + } + + Com_EndRedirect(); +} + +/* +================= +SV_ConnectionlessPacket + +A connectionless packet has four leading 0xff +characters to distinguish it from a game channel. +Clients that are in the game can still send +connectionless packets. +================= +*/ +void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 marker + + if ( !Q_strncmp( "connect", &msg->data[4], 7 ) ) { + Huff_Decompress( msg, 12 ); + } + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv( 0 ); + Com_DPrintf( "SV packet %s : %s\n", NET_AdrToString( from ), c ); + + if ( !Q_stricmp( c,"getstatus" ) ) { + SVC_Status( from ); + } else if ( !Q_stricmp( c,"getinfo" ) ) { + SVC_Info( from ); + } else if ( !Q_stricmp( c,"getchallenge" ) ) { + SV_GetChallenge( from ); + } else if ( !Q_stricmp( c,"connect" ) ) { + SV_DirectConnect( from ); + } else if ( !Q_stricmp( c,"ipAuthorize" ) ) { + SV_AuthorizeIpPacket( from ); + } else if ( !Q_stricmp( c, "rcon" ) ) { + SVC_RemoteCommand( from, msg ); +// DHM - Nerve +#ifdef UPDATE_SERVER + } else if ( !Q_stricmp( c, "getUpdateInfo" ) ) { + SVC_GetUpdateInfo( from ); +#endif +// DHM - Nerve + } else if ( !Q_stricmp( c,"disconnect" ) ) { + // if a client starts up a local server, we may see some spurious + // server disconnect messages when their new server sees our final + // sequenced messages to the old client + } else { + Com_DPrintf( "bad connectionless packet from %s:\n%s\n" + , NET_AdrToString( from ), s ); + } +} + +//============================================================================ + +/* +================= +SV_ReadPackets +================= +*/ +void SV_PacketEvent( netadr_t from, msg_t *msg ) { + int i; + client_t *cl; + int qport; + + // check for connectionless packet (0xffffffff) first + if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { + SV_ConnectionlessPacket( from, msg ); + return; + } + + // read the qport out of the message so we can fix up + // stupid address translating routers + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // sequence number + qport = MSG_ReadShort( msg ) & 0xffff; + + // find which client the message is from + for ( i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( cl->state == CS_FREE ) { + continue; + } + if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) { + continue; + } + // it is possible to have multiple clients from a single IP + // address, so they are differentiated by the qport variable + if ( cl->netchan.qport != qport ) { + continue; + } + + // the IP port can't be used to differentiate them, because + // some address translating routers periodically change UDP + // port assignments + if ( cl->netchan.remoteAddress.port != from.port ) { + Com_Printf( "SV_PacketEvent: fixing up a translated port\n" ); + cl->netchan.remoteAddress.port = from.port; + } + + // make sure it is a valid, in sequence packet + if ( SV_Netchan_Process( cl, msg ) ) { + // zombie clients still need to do the Netchan_Process + // to make sure they don't need to retransmit the final + // reliable message, but they don't do any other processing + if ( cl->state != CS_ZOMBIE ) { + cl->lastPacketTime = svs.time; // don't timeout + SV_ExecuteClientMessage( cl, msg ); + } + } + return; + } + + // if we received a sequenced packet from an address we don't recognize, + // send an out of band disconnect packet to it + NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); +} + + +/* +=================== +SV_CalcPings + +Updates the cl->ping variables +=================== +*/ +void SV_CalcPings( void ) { + int i, j; + client_t *cl; + int total, count; + int delta; + playerState_t *ps; + + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + + // DHM - Nerve +#ifdef UPDATE_SERVER + if ( !cl ) { + continue; + } +#endif + + if ( cl->state != CS_ACTIVE ) { + cl->ping = 999; + continue; + } + if ( !cl->gentity ) { + cl->ping = 999; + continue; + } + if ( cl->gentity->r.svFlags & SVF_BOT ) { + cl->ping = 0; + continue; + } + + total = 0; + count = 0; + for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { + if ( cl->frames[j].messageAcked <= 0 ) { + continue; + } + delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; + count++; + total += delta; + } + if ( !count ) { + cl->ping = 999; + } else { + cl->ping = total / count; + if ( cl->ping > 999 ) { + cl->ping = 999; + } + } + + // let the game dll know about the ping + ps = SV_GameClientNum( i ); + ps->ping = cl->ping; + } +} + +/* +================== +SV_CheckTimeouts + +If a packet has not been received from a client for timeout->integer +seconds, drop the conneciton. Server time is used instead of +realtime to avoid dropping the local client while debugging. + +When a client is normally dropped, the client_t goes into a zombie state +for a few seconds to make sure any final reliable message gets resent +if necessary +================== +*/ +void SV_CheckTimeouts( void ) { + int i; + client_t *cl; + int droppoint; + int zombiepoint; + + droppoint = svs.time - 1000 * sv_timeout->integer; + zombiepoint = svs.time - 1000 * sv_zombietime->integer; + + for ( i = 0,cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + // message times may be wrong across a changelevel + if ( cl->lastPacketTime > svs.time ) { + cl->lastPacketTime = svs.time; + } + + if ( cl->state == CS_ZOMBIE + && cl->lastPacketTime < zombiepoint ) { + // using the client id cause the cl->name is empty at this point + Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for client %d\n", i ); + cl->state = CS_FREE; // can now be reused + continue; + } + if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint ) { + // wait several frames so a debugger session doesn't + // cause a timeout + if ( ++cl->timeoutCount > 5 ) { + SV_DropClient( cl, "timed out" ); + cl->state = CS_FREE; // don't bother with zombie state + } + } else { + cl->timeoutCount = 0; + } + } +} + + +/* +================== +SV_CheckPaused +================== +*/ +qboolean SV_CheckPaused( void ) { + int count; + client_t *cl; + int i; + + if ( !cl_paused->integer ) { + return qfalse; + } + + // only pause if there is just a single client connected + count = 0; + for ( i = 0,cl = svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) { + count++; + } + } + + if ( count > 1 ) { + // don't pause + if ( sv_paused->integer ) { + Cvar_Set( "sv_paused", "0" ); + } + return qfalse; + } + + if ( !sv_paused->integer ) { + Cvar_Set( "sv_paused", "1" ); + } + return qtrue; +} + +/* +================== +SV_Frame + +Player movement occurs as a result of packet events, which +happen before SV_Frame is called +================== +*/ +void SV_Frame( int msec ) { + int frameMsec; + int startTime; + char mapname[MAX_QPATH]; + + // the menu kills the server with this cvar + if ( sv_killserver->integer ) { + SV_Shutdown( "Server was killed.\n" ); + Cvar_Set( "sv_killserver", "0" ); + return; + } + + if ( !com_sv_running->integer ) { + return; + } + + // allow pause if only the local client is connected + if ( SV_CheckPaused() ) { + return; + } + + // if it isn't time for the next frame, do nothing + if ( sv_fps->integer < 1 ) { + Cvar_Set( "sv_fps", "10" ); + } + frameMsec = 1000 / sv_fps->integer ; + + sv.timeResidual += msec; + + if ( !com_dedicated->integer ) { + SV_BotFrame( svs.time + sv.timeResidual ); + } + + if ( com_dedicated->integer && sv.timeResidual < frameMsec ) { + // NET_Sleep will give the OS time slices until either get a packet + // or time enough for a server frame has gone by + NET_Sleep( frameMsec - sv.timeResidual ); + return; + } + + // if time is about to hit the 32nd bit, kick all clients + // and clear sv.time, rather + // than checking for negative time wraparound everywhere. + // 2giga-milliseconds = 23 days, so it won't be too often + if ( svs.time > 0x70000000 ) { + Q_strncpyz( mapname, sv_mapname->string, MAX_QPATH ); + SV_Shutdown( "Restarting server due to time wrapping" ); + // TTimo + // show_bug.cgi?id=388 + // there won't be a map_restart if you have shut down the server + // since it doesn't restart a non-running server + // instead, re-run the current map + Cbuf_AddText( va( "map %s\n", mapname ) ); + return; + } + // this can happen considerably earlier when lots of clients play and the map doesn't change + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) { + Q_strncpyz( mapname, sv_mapname->string, MAX_QPATH ); + SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" ); + // TTimo see above + Cbuf_AddText( va( "map %s\n", mapname ) ); + return; + } + + if ( sv.restartTime && svs.time >= sv.restartTime ) { + sv.restartTime = 0; + Cbuf_AddText( "map_restart 0\n" ); + return; + } + + // update infostrings if anything has been changed + if ( cvar_modifiedFlags & CVAR_SERVERINFO ) { + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + } + if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) { + SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString_Big( CVAR_SYSTEMINFO ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + } + // NERVE - SMF + if ( cvar_modifiedFlags & CVAR_WOLFINFO ) { + SV_SetConfigstring( CS_WOLFINFO, Cvar_InfoString( CVAR_WOLFINFO ) ); + cvar_modifiedFlags &= ~CVAR_WOLFINFO; + } + + if ( com_speeds->integer ) { + startTime = Sys_Milliseconds(); + } else { + startTime = 0; // quite a compiler warning + } + + // update ping based on the all received frames + SV_CalcPings(); + + if ( com_dedicated->integer ) { + SV_BotFrame( svs.time ); + } + + // run the game simulation in chunks + while ( sv.timeResidual >= frameMsec ) { + sv.timeResidual -= frameMsec; + svs.time += frameMsec; + + // let everything in the world think and move +#ifndef UPDATE_SERVER + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); +#endif + } + + if ( com_speeds->integer ) { + time_game = Sys_Milliseconds() - startTime; + } + + // check timeouts + SV_CheckTimeouts(); + + // send messages back to the clients + SV_SendClientMessages(); + + // send a heartbeat to the master if needed + SV_MasterHeartbeat( HEARTBEAT_GAME ); +} + +//============================================================================ diff --git a/src/server/sv_net_chan.c b/src/server/sv_net_chan.c new file mode 100644 index 0000000..58b79df --- /dev/null +++ b/src/server/sv_net_chan.c @@ -0,0 +1,218 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "server.h" + +/* +============== +SV_Netchan_Encode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Encode( client_t *client, msg_t *msg ) { + long reliableAcknowledge, i, index; + byte key, *string; + int srdc, sbit, soob; + + if ( msg->cursize < SV_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = 0; + + reliableAcknowledge = MSG_ReadLong( msg ); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->lastClientCommandString; + index = 0; + // xor the client challenge with the netchan sequence number + key = client->challenge ^ client->netchan.outgoingSequence; + for ( i = SV_ENCODE_START; i < msg->cursize; i++ ) { + // modify the key with the last received and with this message acknowledged client command + if ( !string[index] ) { + index = 0; + } + if ( string[index] > 127 || string[index] == '%' ) { + key ^= '.' << ( i & 1 ); + } else { + key ^= string[index] << ( i & 1 ); + } + index++; + // encode the data with this key + *( msg->data + i ) = *( msg->data + i ) ^ key; + } +} + +/* +============== +SV_Netchan_Decode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Decode( client_t *client, msg_t *msg ) { + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit, soob; + byte key, *string; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = 0; + + serverId = MSG_ReadLong( msg ); + messageAcknowledge = MSG_ReadLong( msg ); + reliableAcknowledge = MSG_ReadLong( msg ); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->reliableCommands[ reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + index = 0; + // + key = client->challenge ^ serverId ^ messageAcknowledge; + for ( i = msg->readcount + SV_DECODE_START; i < msg->cursize; i++ ) { + // modify the key with the last sent and acknowledged server command + if ( !string[index] ) { + index = 0; + } + if ( string[index] > 127 || string[index] == '%' ) { + key ^= '.' << ( i & 1 ); + } else { + key ^= string[index] << ( i & 1 ); + } + index++; + // decode the data with this key + *( msg->data + i ) = *( msg->data + i ) ^ key; + } +} + +/* +================= +SV_Netchan_TransmitNextFragment +================= +*/ +void SV_Netchan_TransmitNextFragment( client_t *client ) { + Netchan_TransmitNextFragment( &client->netchan ); + if ( !client->netchan.unsentFragments ) { + // make sure the netchan queue has been properly initialized (you never know) + if ( !client->netchan_end_queue ) { + Com_Error( ERR_DROP, "netchan queue is not properly initialized in SV_Netchan_TransmitNextFragment\n" ); + } + // the last fragment was transmitted, check wether we have queued messages + if ( client->netchan_start_queue ) { + netchan_buffer_t *netbuf; + //Com_DPrintf("Netchan_TransmitNextFragment: popping a queued message for transmit\n"); + netbuf = client->netchan_start_queue; + + SV_Netchan_Encode( client, &netbuf->msg ); + Netchan_Transmit( &client->netchan, netbuf->msg.cursize, netbuf->msg.data ); + + // pop from queue + client->netchan_start_queue = netbuf->next; + if ( !client->netchan_start_queue ) { + //Com_DPrintf("Netchan_TransmitNextFragment: emptied queue\n"); + client->netchan_end_queue = &client->netchan_start_queue; + } + /* + else + Com_DPrintf("Netchan_TransmitNextFragment: remaining queued message\n"); + */ + Z_Free( netbuf ); + } + } +} + + +/* +=============== +SV_Netchan_Transmit + +TTimo +show_bug.cgi?id=462 +if there are some unsent fragments (which may happen if the snapshots +and the gamestate are fragmenting, and collide on send for instance) +then buffer them and make sure they get sent in correct order +================ +*/ +void SV_Netchan_Transmit( client_t *client, msg_t *msg ) { //int length, const byte *data ) { + MSG_WriteByte( msg, svc_EOF ); + if ( client->netchan.unsentFragments ) { + netchan_buffer_t *netbuf; + //Com_DPrintf("SV_Netchan_Transmit: there are unsent fragments remaining\n"); + netbuf = (netchan_buffer_t *)Z_Malloc( sizeof( netchan_buffer_t ) ); + // store the msg, we can't store it encoded, as the encoding depends on stuff we still have to finish sending + MSG_Copy( &netbuf->msg, netbuf->msgBuffer, sizeof( netbuf->msgBuffer ), msg ); + netbuf->next = NULL; + // insert it in the queue, the message will be encoded and sent later + *client->netchan_end_queue = netbuf; + client->netchan_end_queue = &( *client->netchan_end_queue )->next; + // emit the next fragment of the current message for now + Netchan_TransmitNextFragment( &client->netchan ); + } else { + SV_Netchan_Encode( client, msg ); + Netchan_Transmit( &client->netchan, msg->cursize, msg->data ); + } +} + +/* +================= +Netchan_SV_Process +================= +*/ +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) { + int ret; + ret = Netchan_Process( &client->netchan, msg ); + if ( !ret ) { + return qfalse; + } + SV_Netchan_Decode( client, msg ); + return qtrue; +} + diff --git a/src/server/sv_snapshot.c b/src/server/sv_snapshot.c new file mode 100644 index 0000000..36b5205 --- /dev/null +++ b/src/server/sv_snapshot.c @@ -0,0 +1,858 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#include "server.h" + + +/* +============================================================================= + +Delta encode a client frame onto the network channel + +A normal server packet will look like: + +4 sequence number (high bit set if an oversize fragment) + +1 svc_snapshot +4 last client reliable command +4 serverTime +1 lastframe for delta compression +1 snapFlags +1 areaBytes + + + + +============================================================================= +*/ + +/* +============= +SV_EmitPacketEntities + +Writes a delta update of an entityState_t list to the message. +============= +*/ +static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg ) { + entityState_t *oldent, *newent; + int oldindex, newindex; + int oldnum, newnum; + int from_num_entities; + + // generate the delta update + if ( !from ) { + from_num_entities = 0; + } else { + from_num_entities = from->num_entities; + } + + newent = NULL; + oldent = NULL; + newindex = 0; + oldindex = 0; + while ( newindex < to->num_entities || oldindex < from_num_entities ) { + if ( newindex >= to->num_entities ) { + newnum = 9999; + } else { + newent = &svs.snapshotEntities[( to->first_entity + newindex ) % svs.numSnapshotEntities]; + newnum = newent->number; + } + + if ( oldindex >= from_num_entities ) { + oldnum = 9999; + } else { + oldent = &svs.snapshotEntities[( from->first_entity + oldindex ) % svs.numSnapshotEntities]; + oldnum = oldent->number; + } + + if ( newnum == oldnum ) { + // delta update from old position + // because the force parm is qfalse, this will not result + // in any bytes being emited if the entity has not changed at all + MSG_WriteDeltaEntity( msg, oldent, newent, qfalse ); + oldindex++; + newindex++; + continue; + } + + if ( newnum < oldnum ) { + // this is a new entity, send it from the baseline + MSG_WriteDeltaEntity( msg, &sv.svEntities[newnum].baseline, newent, qtrue ); + newindex++; + continue; + } + + if ( newnum > oldnum ) { + // the old entity isn't present in the new message + MSG_WriteDeltaEntity( msg, oldent, NULL, qtrue ); + oldindex++; + continue; + } + } + + MSG_WriteBits( msg, ( MAX_GENTITIES - 1 ), GENTITYNUM_BITS ); // end of packetentities +} + + + +/* +================== +SV_WriteSnapshotToClient +================== +*/ +static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) { + clientSnapshot_t *frame, *oldframe; + int lastframe; + int i; + int snapFlags; + + // this is the snapshot we are creating + frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; + + // try to use a previous frame as the source for delta compressing the snapshot + if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) { + // client is asking for a retransmit + oldframe = NULL; + lastframe = 0; + } else if ( client->netchan.outgoingSequence - client->deltaMessage + >= ( PACKET_BACKUP - 3 ) ) { + // client hasn't gotten a good message through in a long time + Com_DPrintf( "%s: Delta request from out of date packet.\n", client->name ); + oldframe = NULL; + lastframe = 0; + } else { + // we have a valid snapshot to delta from + oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ]; + lastframe = client->netchan.outgoingSequence - client->deltaMessage; + + // the snapshot's entities may still have rolled off the buffer, though + if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) { + Com_DPrintf( "%s: Delta request from out of date entities.\n", client->name ); + oldframe = NULL; + lastframe = 0; + } + } + + MSG_WriteByte( msg, svc_snapshot ); + + // NOTE, MRE: now sent at the start of every message from server to client + // let the client know which reliable clientCommands we have received + //MSG_WriteLong( msg, client->lastClientCommand ); + + // send over the current server time so the client can drift + // its view of time to try to match + MSG_WriteLong( msg, svs.time ); + + // what we are delta'ing from + MSG_WriteByte( msg, lastframe ); + + snapFlags = svs.snapFlagServerBit; + if ( client->rateDelayed ) { + snapFlags |= SNAPFLAG_RATE_DELAYED; + } + if ( client->state != CS_ACTIVE ) { + snapFlags |= SNAPFLAG_NOT_ACTIVE; + } + + MSG_WriteByte( msg, snapFlags ); + + // send over the areabits + MSG_WriteByte( msg, frame->areabytes ); + MSG_WriteData( msg, frame->areabits, frame->areabytes ); + + // delta encode the playerstate + if ( oldframe ) { + MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps ); + } else { + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps ); + } + + // delta encode the entities + SV_EmitPacketEntities( oldframe, frame, msg ); + + // padding for rate debugging + if ( sv_padPackets->integer ) { + for ( i = 0 ; i < sv_padPackets->integer ; i++ ) { + MSG_WriteByte( msg, svc_nop ); + } + } +} + + +/* +================== +SV_UpdateServerCommandsToClient + +(re)send all server commands the client hasn't acknowledged yet +================== +*/ +void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ) { + int i; + + // write any unacknowledged serverCommands + for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { + MSG_WriteByte( msg, svc_serverCommand ); + MSG_WriteLong( msg, i ); + MSG_WriteString( msg, client->reliableCommands[ i & ( MAX_RELIABLE_COMMANDS - 1 ) ] ); + } + client->reliableSent = client->reliableSequence; +} + +/* +============================================================================= + +Build a client snapshot structure + +============================================================================= +*/ + +//#define MAX_SNAPSHOT_ENTITIES 1024 +#define MAX_SNAPSHOT_ENTITIES 2048 + +typedef struct { + int numSnapshotEntities; + int snapshotEntities[MAX_SNAPSHOT_ENTITIES]; +} snapshotEntityNumbers_t; + +/* +======================= +SV_QsortEntityNumbers +======================= +*/ +static int QDECL SV_QsortEntityNumbers( const void *a, const void *b ) { + int *ea, *eb; + + ea = (int *)a; + eb = (int *)b; + + if ( *ea == *eb ) { + Com_Error( ERR_DROP, "SV_QsortEntityStates: duplicated entity" ); + } + + if ( *ea < *eb ) { + return -1; + } + + return 1; +} + + +/* +=============== +SV_AddEntToSnapshot +=============== +*/ +static void SV_AddEntToSnapshot( svEntity_t *svEnt, sharedEntity_t *gEnt, snapshotEntityNumbers_t *eNums ) { + // if we have already added this entity to this snapshot, don't add again + if ( svEnt->snapshotCounter == sv.snapshotCounter ) { + return; + } + svEnt->snapshotCounter = sv.snapshotCounter; + + // if we are full, silently discard entities + if ( eNums->numSnapshotEntities == MAX_SNAPSHOT_ENTITIES ) { + return; + } + + eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number; + eNums->numSnapshotEntities++; +} + +/* +=============== +SV_AddEntitiesVisibleFromPoint +=============== +*/ +static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, +// snapshotEntityNumbers_t *eNums, qboolean portal, clientSnapshot_t *oldframe, qboolean localClient ) { +// snapshotEntityNumbers_t *eNums, qboolean portal ) { + snapshotEntityNumbers_t *eNums, qboolean portal, qboolean localClient ) { + int e, i; + sharedEntity_t *ent, *playerEnt; + svEntity_t *svEnt; + int l; + int clientarea, clientcluster; + int leafnum; + int c_fullsend; + byte *clientpvs; + byte *bitvector; + + // during an error shutdown message we may need to transmit + // the shutdown message after the server has shutdown, so + // specfically check for it + if ( !sv.state ) { + return; + } + + leafnum = CM_PointLeafnum( origin ); + clientarea = CM_LeafArea( leafnum ); + clientcluster = CM_LeafCluster( leafnum ); + + // calculate the visible areas + frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea ); + + clientpvs = CM_ClusterPVS( clientcluster ); + + c_fullsend = 0; + + playerEnt = SV_GentityNum( frame->ps.clientNum ); + + for ( e = 0 ; e < sv.num_entities ; e++ ) { + ent = SV_GentityNum( e ); + + // never send entities that aren't linked in + if ( !ent->r.linked ) { + continue; + } + + if ( ent->s.number != e ) { + Com_DPrintf( "FIXING ENT->S.NUMBER!!!\n" ); + ent->s.number = e; + } + + // entities can be flagged to explicitly not be sent to the client + if ( ent->r.svFlags & SVF_NOCLIENT ) { + continue; + } + + // entities can be flagged to be sent to only one client + if ( ent->r.svFlags & SVF_SINGLECLIENT ) { + if ( ent->r.singleClient != frame->ps.clientNum ) { + continue; + } + } + // entities can be flagged to be sent to everyone but one client + if ( ent->r.svFlags & SVF_NOTSINGLECLIENT ) { + if ( ent->r.singleClient == frame->ps.clientNum ) { + continue; + } + } + + svEnt = SV_SvEntityForGentity( ent ); + + // don't double add an entity through portals + if ( svEnt->snapshotCounter == sv.snapshotCounter ) { + continue; + } + + // if this client is viewing from a camera, only add ents visible from portal ents + if ( ( playerEnt->s.eFlags & EF_VIEWING_CAMERA ) && !portal ) { + if ( ent->r.svFlags & SVF_PORTAL ) { + SV_AddEntToSnapshot( svEnt, ent, eNums ); +// SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue, oldframe, localClient ); + SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue, localClient ); + } + continue; + } + + // broadcast entities are always sent + if ( ent->r.svFlags & SVF_BROADCAST ) { + SV_AddEntToSnapshot( svEnt, ent, eNums ); + continue; + } + + // ignore if not touching a PV leaf + // check area + if ( !CM_AreasConnected( clientarea, svEnt->areanum ) ) { + // doors can legally straddle two areas, so + // we may need to check another one + if ( !CM_AreasConnected( clientarea, svEnt->areanum2 ) ) { + goto notVisible; // blocked by a door + } + } + + bitvector = clientpvs; + + // check individual leafs + if ( !svEnt->numClusters ) { + goto notVisible; + } + l = 0; + for ( i = 0 ; i < svEnt->numClusters ; i++ ) { + l = svEnt->clusternums[i]; + if ( bitvector[l >> 3] & ( 1 << ( l & 7 ) ) ) { + break; + } + } + + // if we haven't found it to be visible, + // check overflow clusters that coudln't be stored + if ( i == svEnt->numClusters ) { + if ( svEnt->lastCluster ) { + for ( ; l <= svEnt->lastCluster ; l++ ) { + if ( bitvector[l >> 3] & ( 1 << ( l & 7 ) ) ) { + break; + } + } + if ( l == svEnt->lastCluster ) { + goto notVisible; // not visible + } + } else { + goto notVisible; + } + } + + //----(SA) added "visibility dummies" + if ( ent->r.svFlags & SVF_VISDUMMY ) { + sharedEntity_t *ment = 0; + + //find master; + ment = SV_GentityNum( ent->s.otherEntityNum ); + + if ( ment ) { + svEntity_t *master = 0; + master = SV_SvEntityForGentity( ment ); + + if ( master->snapshotCounter == sv.snapshotCounter || !ment->r.linked ) { + goto notVisible; + //continue; + } + + SV_AddEntToSnapshot( master, ment, eNums ); + } + goto notVisible; + //continue; // master needs to be added, but not this dummy ent + } + //----(SA) end + else if ( ent->r.svFlags & SVF_VISDUMMY_MULTIPLE ) { + { + int h; + sharedEntity_t *ment = 0; + svEntity_t *master = 0; + + for ( h = 0; h < sv.num_entities; h++ ) + { + ment = SV_GentityNum( h ); + + if ( ment == ent ) { + continue; + } + + if ( ment ) { + master = SV_SvEntityForGentity( ment ); + } else { + continue; + } + + if ( !( ment->r.linked ) ) { + continue; + } + + if ( ment->s.number != h ) { + Com_DPrintf( "FIXING vis dummy multiple ment->S.NUMBER!!!\n" ); + ment->s.number = h; + } + + if ( ment->r.svFlags & SVF_NOCLIENT ) { + continue; + } + + if ( master->snapshotCounter == sv.snapshotCounter ) { + continue; + } + + if ( ment->s.otherEntityNum == ent->s.number ) { + SV_AddEntToSnapshot( master, ment, eNums ); + } + } + goto notVisible; + } + } + + // add it + SV_AddEntToSnapshot( svEnt, ent, eNums ); + + // if its a portal entity, add everything visible from its camera position + if ( ent->r.svFlags & SVF_PORTAL ) { +// SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue, oldframe, localClient ); + SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue, localClient ); + } + + continue; + +notVisible: + + // Ridah, if this entity has changed events, then send it regardless of whether we can see it or not + // DHM - Nerve :: not in multiplayer please + if ( sv_gametype->integer == GT_SINGLE_PLAYER && localClient ) { + if ( ent->r.eventTime == svs.time ) { + ent->s.eFlags |= EF_NODRAW; // don't draw, just process event + SV_AddEntToSnapshot( svEnt, ent, eNums ); + } else if ( ent->s.eType == ET_PLAYER ) { + // keep players around if they are alive and active (so sounds dont get messed up) + if ( !( ent->s.eFlags & EF_DEAD ) ) { + ent->s.eFlags |= EF_NODRAW; // don't draw, just process events and sounds + SV_AddEntToSnapshot( svEnt, ent, eNums ); + } + } + } + + } +} + +/* +============= +SV_BuildClientSnapshot + +Decides which entities are going to be visible to the client, and +copies off the playerstate and areabits. + +This properly handles multiple recursive portals, but the render +currently doesn't. + +For viewing through other player's eyes, clent can be something other than client->gentity +============= +*/ +static void SV_BuildClientSnapshot( client_t *client ) { + vec3_t org; +// clientSnapshot_t *frame, *oldframe; + clientSnapshot_t *frame; + snapshotEntityNumbers_t entityNumbers; + int i; + sharedEntity_t *ent; + entityState_t *state; + svEntity_t *svEnt; + sharedEntity_t *clent; + int clientNum; + playerState_t *ps; + + // bump the counter used to prevent double adding + sv.snapshotCounter++; + + // this is the frame we are creating + frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; + + // clear everything in this snapshot + entityNumbers.numSnapshotEntities = 0; + memset( frame->areabits, 0, sizeof( frame->areabits ) ); + + // show_bug.cgi?id=62 + frame->num_entities = 0; + + clent = client->gentity; + if ( !clent || client->state == CS_ZOMBIE ) { + return; + } + + // grab the current playerState_t + ps = SV_GameClientNum( client - svs.clients ); + frame->ps = *ps; + + // never send client's own entity, because it can + // be regenerated from the playerstate + clientNum = frame->ps.clientNum; + if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + svEnt = &sv.svEntities[ clientNum ]; + + svEnt->snapshotCounter = sv.snapshotCounter; + + // find the client's viewpoint + VectorCopy( ps->origin, org ); + org[2] += ps->viewheight; + +//----(SA) added for 'lean' + // need to account for lean, so areaportal doors draw properly + if ( frame->ps.leanf != 0 ) { + vec3_t right, v3ViewAngles; + VectorCopy( ps->viewangles, v3ViewAngles ); + v3ViewAngles[2] += frame->ps.leanf / 2.0f; + AngleVectors( v3ViewAngles, NULL, right, NULL ); + VectorMA( org, frame->ps.leanf, right, org ); + } +//----(SA) end + + // add all the entities directly visible to the eye, which + // may include portal entities that merge other viewpoints + SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse, client->netchan.remoteAddress.type == NA_LOOPBACK ); + + // if there were portals visible, there may be out of order entities + // in the list which will need to be resorted for the delta compression + // to work correctly. This also catches the error condition + // of an entity being included twice. + qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities, + sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers ); + + // now that all viewpoint's areabits have been OR'd together, invert + // all of them to make it a mask vector, which is what the renderer wants + for ( i = 0 ; i < MAX_MAP_AREA_BYTES / 4 ; i++ ) { + ( (int *)frame->areabits )[i] = ( (int *)frame->areabits )[i] ^ -1; + } + + // copy the entity states out + frame->num_entities = 0; + frame->first_entity = svs.nextSnapshotEntities; + for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) { + ent = SV_GentityNum( entityNumbers.snapshotEntities[i] ); + state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; + *state = ent->s; + svs.nextSnapshotEntities++; + // this should never hit, map should always be restarted first in SV_Frame + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) { + Com_Error( ERR_FATAL, "svs.nextSnapshotEntities wrapped" ); + } + frame->num_entities++; + } +} + + +/* +==================== +SV_RateMsec + +Return the number of msec a given size message is supposed +to take to clear, based on the current rate +TTimo - use sv_maxRate or sv_dl_maxRate depending on regular or downloading client +==================== +*/ +#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead +static int SV_RateMsec( client_t *client, int messageSize ) { + int rate; + int rateMsec; + int maxRate; + + // individual messages will never be larger than fragment size + if ( messageSize > 1500 ) { + messageSize = 1500; + } + // low watermark for sv_maxRate, never 0 < sv_maxRate < 1000 (0 is no limitation) + if ( sv_maxRate->integer && sv_maxRate->integer < 1000 ) { + Cvar_Set( "sv_MaxRate", "1000" ); + } + rate = client->rate; + // work on the appropriate max rate (client or download) + if ( !*client->downloadName ) { + maxRate = sv_maxRate->integer; + } else + { + maxRate = sv_dl_maxRate->integer; + } + if ( maxRate ) { + if ( maxRate < rate ) { + rate = maxRate; + } + } + rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / rate; + + return rateMsec; +} + +/* +======================= +SV_SendMessageToClient + +Called by SV_SendClientSnapshot and SV_SendClientGameState +======================= +*/ +void SV_SendMessageToClient( msg_t *msg, client_t *client ) { + int rateMsec; + + // record information about the message + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; + + // send the datagram + SV_Netchan_Transmit( client, msg ); + + // set nextSnapshotTime based on rate and requested number of updates + + // local clients get snapshots every frame + // TTimo - show_bug.cgi?id=491 + // added sv_lanForceRate check + if ( client->netchan.remoteAddress.type == NA_LOOPBACK || ( sv_lanForceRate->integer && Sys_IsLANAddress( client->netchan.remoteAddress ) ) ) { + client->nextSnapshotTime = svs.time - 1; + return; + } + + // normal rate / snapshotMsec calculation + rateMsec = SV_RateMsec( client, msg->cursize ); + + // TTimo - during a download, ignore the snapshotMsec + // the update server on steroids, with this disabled and sv_fps 60, the download can reach 30 kb/s + // on a regular server, we will still top at 20 kb/s because of sv_fps 20 + if ( !*client->downloadName && rateMsec < client->snapshotMsec ) { + // never send more packets than this, no matter what the rate is at + rateMsec = client->snapshotMsec; + client->rateDelayed = qfalse; + } else { + client->rateDelayed = qtrue; + } + + client->nextSnapshotTime = svs.time + rateMsec; + + // don't pile up empty snapshots while connecting + if ( client->state != CS_ACTIVE ) { + // a gigantic connection message may have already put the nextSnapshotTime + // more than a second away, so don't shorten it + // do shorten if client is downloading + if ( !*client->downloadName && client->nextSnapshotTime < svs.time + 1000 ) { + client->nextSnapshotTime = svs.time + 1000; + } + } +} + + +/* +======================= +SV_SendClientSnapshot + +Also called by SV_FinalMessage + +======================= +*/ +void SV_SendClientSnapshot( client_t *client ) { + byte msg_buf[MAX_MSGLEN]; + msg_t msg; + + // build the snapshot + SV_BuildClientSnapshot( client ); + + // bots need to have their snapshots build, but + // the query them directly without needing to be sent + if ( client->gentity && client->gentity->r.svFlags & SVF_BOT ) { + return; + } + + MSG_Init( &msg, msg_buf, sizeof( msg_buf ) ); + msg.allowoverflow = qtrue; + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // (re)send any reliable server commands + SV_UpdateServerCommandsToClient( client, &msg ); + + // send over all the relevant entityState_t + // and the playerState_t + SV_WriteSnapshotToClient( client, &msg ); + + // Add any download data if the client is downloading + SV_WriteDownloadToClient( client, &msg ); + + // check for overflow + if ( msg.overflowed ) { + Com_Printf( "WARNING: msg overflowed for %s\n", client->name ); + MSG_Clear( &msg ); + } + + SV_SendMessageToClient( &msg, client ); + + sv.bpsTotalBytes += msg.cursize; // NERVE - SMF - net debugging + sv.ubpsTotalBytes += msg.uncompsize / 8; // NERVE - SMF - net debugging +} + + +/* +======================= +SV_SendClientMessages +======================= +*/ + +void SV_SendClientMessages( void ) { + int i; + client_t *c; + int numclients = 0; // NERVE - SMF - net debugging + + sv.bpsTotalBytes = 0; // NERVE - SMF - net debugging + sv.ubpsTotalBytes = 0; // NERVE - SMF - net debugging + + // send a message to each connected client + for ( i = 0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++ ) { + if ( !c->state ) { + continue; // not connected + } + + if ( svs.time < c->nextSnapshotTime ) { + continue; // not time yet + } + + numclients++; // NERVE - SMF - net debugging + + // send additional message fragments if the last message + // was too large to send at once + if ( c->netchan.unsentFragments ) { + c->nextSnapshotTime = svs.time + + SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart ); + SV_Netchan_TransmitNextFragment( c ); + continue; + } + + // generate and send a new message + SV_SendClientSnapshot( c ); + } + + // NERVE - SMF - net debugging + if ( sv_showAverageBPS->integer && numclients > 0 ) { + float ave = 0, uave = 0; + + for ( i = 0; i < MAX_BPS_WINDOW - 1; i++ ) { + sv.bpsWindow[i] = sv.bpsWindow[i + 1]; + ave += sv.bpsWindow[i]; + + sv.ubpsWindow[i] = sv.ubpsWindow[i + 1]; + uave += sv.ubpsWindow[i]; + } + + sv.bpsWindow[MAX_BPS_WINDOW - 1] = sv.bpsTotalBytes; + ave += sv.bpsTotalBytes; + + sv.ubpsWindow[MAX_BPS_WINDOW - 1] = sv.ubpsTotalBytes; + uave += sv.ubpsTotalBytes; + + if ( sv.bpsTotalBytes >= sv.bpsMaxBytes ) { + sv.bpsMaxBytes = sv.bpsTotalBytes; + } + + if ( sv.ubpsTotalBytes >= sv.ubpsMaxBytes ) { + sv.ubpsMaxBytes = sv.ubpsTotalBytes; + } + + sv.bpsWindowSteps++; + + if ( sv.bpsWindowSteps >= MAX_BPS_WINDOW ) { + float comp_ratio; + + sv.bpsWindowSteps = 0; + + ave = ( ave / (float)MAX_BPS_WINDOW ); + uave = ( uave / (float)MAX_BPS_WINDOW ); + + comp_ratio = ( 1 - ave / uave ) * 100.f; + sv.ucompAve += comp_ratio; + sv.ucompNum++; + + Com_DPrintf( "bpspc(%2.0f) bps(%2.0f) pk(%i) ubps(%2.0f) upk(%i) cr(%2.2f) acr(%2.2f)\n", + ave / (float)numclients, ave, sv.bpsMaxBytes, uave, sv.ubpsMaxBytes, comp_ratio, sv.ucompAve / sv.ucompNum ); + } + } + // -NERVE - SMF +} diff --git a/src/server/sv_world.c b/src/server/sv_world.c new file mode 100644 index 0000000..1334f30 --- /dev/null +++ b/src/server/sv_world.c @@ -0,0 +1,736 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// world.c -- world query functions + +#include "server.h" + +/* +================ +SV_ClipHandleForEntity + +Returns a headnode that can be used for testing or clipping to a +given entity. If the entity is a bsp model, the headnode will +be returned, otherwise a custom box tree will be constructed. +================ +*/ +clipHandle_t SV_ClipHandleForEntity( const sharedEntity_t *ent ) { + if ( ent->r.bmodel ) { + // explicit hulls in the BSP model + return CM_InlineModel( ent->s.modelindex ); + } + if ( ent->r.svFlags & SVF_CAPSULE ) { + // create a temp capsule from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue ); + } + + // create a temp tree from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse ); +} + + + +/* +=============================================================================== + +ENTITY CHECKING + +To avoid linearly searching through lists of entities during environment testing, +the world is carved up with an evenly spaced, axially aligned bsp tree. Entities +are kept in chains either at the final leafs, or at the first node that splits +them, which prevents having to deal with multiple fragments of a single entity. + +=============================================================================== +*/ + +typedef struct worldSector_s { + int axis; // -1 = leaf node + float dist; + struct worldSector_s *children[2]; + svEntity_t *entities; +} worldSector_t; + +#define AREA_DEPTH 4 +#define AREA_NODES 64 + +worldSector_t sv_worldSectors[AREA_NODES]; +int sv_numworldSectors; + + +/* +=============== +SV_SectorList_f +=============== +*/ +void SV_SectorList_f( void ) { + int i, c; + worldSector_t *sec; + svEntity_t *ent; + + for ( i = 0 ; i < AREA_NODES ; i++ ) { + sec = &sv_worldSectors[i]; + + c = 0; + for ( ent = sec->entities ; ent ; ent = ent->nextEntityInWorldSector ) { + c++; + } + Com_Printf( "sector %i: %i entities\n", i, c ); + } +} + +/* +=============== +SV_CreateworldSector + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) { + worldSector_t *anode; + vec3_t size; + vec3_t mins1, maxs1, mins2, maxs2; + + anode = &sv_worldSectors[sv_numworldSectors]; + sv_numworldSectors++; + + 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_CreateworldSector( depth + 1, mins2, maxs2 ); + anode->children[1] = SV_CreateworldSector( depth + 1, mins1, maxs1 ); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld( void ) { + clipHandle_t h; + vec3_t mins, maxs; + + memset( sv_worldSectors, 0, sizeof( sv_worldSectors ) ); + sv_numworldSectors = 0; + + // get world map bounds + h = CM_InlineModel( 0 ); + CM_ModelBounds( h, mins, maxs ); + SV_CreateworldSector( 0, mins, maxs ); +} + + +/* +=============== +SV_UnlinkEntity + +=============== +*/ +void SV_UnlinkEntity( sharedEntity_t *gEnt ) { + svEntity_t *ent; + svEntity_t *scan; + worldSector_t *ws; + + ent = SV_SvEntityForGentity( gEnt ); + + gEnt->r.linked = qfalse; + + ws = ent->worldSector; + if ( !ws ) { + return; // not linked in anywhere + } + ent->worldSector = NULL; + + if ( ws->entities == ent ) { + ws->entities = ent->nextEntityInWorldSector; + return; + } + + for ( scan = ws->entities ; scan ; scan = scan->nextEntityInWorldSector ) { + if ( scan->nextEntityInWorldSector == ent ) { + scan->nextEntityInWorldSector = ent->nextEntityInWorldSector; + return; + } + } + + Com_Printf( "WARNING: SV_UnlinkEntity: not found in worldSector\n" ); +} + + +/* +=============== +SV_LinkEntity + +=============== +*/ +#define MAX_TOTAL_ENT_LEAFS 128 +void SV_LinkEntity( sharedEntity_t *gEnt ) { + worldSector_t *node; + int leafs[MAX_TOTAL_ENT_LEAFS]; + int cluster; + int num_leafs; + int i, j, k; + int area; + int lastLeaf; + float *origin, *angles; + svEntity_t *ent; + + ent = SV_SvEntityForGentity( gEnt ); + + // Ridah, sanity check for possible currentOrigin being reset bug + if ( !gEnt->r.bmodel && VectorCompare( gEnt->r.currentOrigin, vec3_origin ) ) { + Com_DPrintf( "WARNING: BBOX entity is being linked at world origin, this is probably a bug\n" ); + } + + if ( ent->worldSector ) { + SV_UnlinkEntity( gEnt ); // unlink from old position + } + + // encode the size into the entityState_t for client prediction + if ( gEnt->r.bmodel ) { + gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value + } else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { + // assume that x/y are equal and symetric + i = gEnt->r.maxs[0]; + if ( i < 1 ) { + i = 1; + } + if ( i > 255 ) { + i = 255; + } + + // z is not symetric + j = ( -gEnt->r.mins[2] ); + if ( j < 1 ) { + j = 1; + } + if ( j > 255 ) { + j = 255; + } + + // and z maxs can be negative... + k = ( gEnt->r.maxs[2] + 32 ); + if ( k < 1 ) { + k = 1; + } + if ( k > 255 ) { + k = 255; + } + + gEnt->s.solid = ( k << 16 ) | ( j << 8 ) | i; + } else { + gEnt->s.solid = 0; + } + + // get the position + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + // set the abs box + if ( gEnt->r.bmodel && ( angles[0] || angles[1] || angles[2] ) ) { + // expand for rotation + float max; + int i; + + max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs ); + for ( i = 0 ; i < 3 ; i++ ) { + gEnt->r.absmin[i] = origin[i] - max; + gEnt->r.absmax[i] = origin[i] + max; + } + } else { + // normal + VectorAdd( origin, gEnt->r.mins, gEnt->r.absmin ); + VectorAdd( origin, gEnt->r.maxs, gEnt->r.absmax ); + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + gEnt->r.absmin[0] -= 1; + gEnt->r.absmin[1] -= 1; + gEnt->r.absmin[2] -= 1; + gEnt->r.absmax[0] += 1; + gEnt->r.absmax[1] += 1; + gEnt->r.absmax[2] += 1; + + // link to PVS leafs + ent->numClusters = 0; + ent->lastCluster = 0; + ent->areanum = -1; + ent->areanum2 = -1; + + //get all leafs, including solids + num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax, + leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf ); + + // if none of the leafs were inside the map, the + // entity is outside the world and can be considered unlinked + if ( !num_leafs ) { + return; + } + + // set areas, even from clusters that don't fit in the entity array + for ( i = 0 ; i < num_leafs ; i++ ) { + area = CM_LeafArea( leafs[i] ); + if ( area != -1 ) { + // doors may legally straggle two areas, + // but nothing should evern need more than that + if ( ent->areanum != -1 && ent->areanum != area ) { + if ( ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING ) { + Com_DPrintf( "Object %i touching 3 areas at %f %f %f\n", + gEnt->s.number, + gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2] ); + } + ent->areanum2 = area; + } else { + ent->areanum = area; + } + } + } + + // store as many explicit clusters as we can + ent->numClusters = 0; + for ( i = 0 ; i < num_leafs ; i++ ) { + cluster = CM_LeafCluster( leafs[i] ); + if ( cluster != -1 ) { + ent->clusternums[ent->numClusters++] = cluster; + if ( ent->numClusters == MAX_ENT_CLUSTERS ) { + break; + } + } + } + + // store off a last cluster if we need to + if ( i != num_leafs ) { + ent->lastCluster = CM_LeafCluster( lastLeaf ); + } + + gEnt->r.linkcount++; + + // find the first world sector node that the ent's box crosses + node = sv_worldSectors; + while ( 1 ) + { + if ( node->axis == -1 ) { + break; + } + if ( gEnt->r.absmin[node->axis] > node->dist ) { + node = node->children[0]; + } else if ( gEnt->r.absmax[node->axis] < node->dist ) { + node = node->children[1]; + } else { + break; // crosses the node + } + } + + // link it in + ent->worldSector = node; + ent->nextEntityInWorldSector = node->entities; + node->entities = ent; + + gEnt->r.linked = qtrue; +} + +/* +============================================================================ + +AREA QUERY + +Fills in a list of all entities who's absmin / absmax intersects the given +bounds. This does NOT mean that they actually touch in the case of bmodels. +============================================================================ +*/ + +typedef struct { + const float *mins; + const float *maxs; + int *list; + int count, maxcount; +} areaParms_t; + + +/* +==================== +SV_AreaEntities_r + +==================== +*/ +void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { + svEntity_t *check, *next; + sharedEntity_t *gcheck; + int count; + + count = 0; + + for ( check = node->entities ; check ; check = next ) { + next = check->nextEntityInWorldSector; + + gcheck = SV_GEntityForSvEntity( check ); + + if ( gcheck->r.absmin[0] > ap->maxs[0] + || gcheck->r.absmin[1] > ap->maxs[1] + || gcheck->r.absmin[2] > ap->maxs[2] + || gcheck->r.absmax[0] < ap->mins[0] + || gcheck->r.absmax[1] < ap->mins[1] + || gcheck->r.absmax[2] < ap->mins[2] ) { + continue; + } + + if ( ap->count == ap->maxcount ) { + Com_Printf( "SV_AreaEntities: MAXCOUNT\n" ); + return; + } + + ap->list[ap->count] = check - sv.svEntities; + ap->count++; + } + + if ( node->axis == -1 ) { + return; // terminal node + } + + // recurse down both sides + if ( ap->maxs[node->axis] > node->dist ) { + SV_AreaEntities_r( node->children[0], ap ); + } + if ( ap->mins[node->axis] < node->dist ) { + SV_AreaEntities_r( node->children[1], ap ); + } +} + +/* +================ +SV_AreaEntities +================ +*/ +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ) { + areaParms_t ap; + + ap.mins = mins; + ap.maxs = maxs; + ap.list = entityList; + ap.count = 0; + ap.maxcount = maxcount; + + SV_AreaEntities_r( sv_worldSectors, &ap ); + + return ap.count; +} + + + +//=========================================================================== + + +typedef struct { + vec3_t boxmins, boxmaxs; // enclose the test object along entire move + const float *mins; + const float *maxs; // size of the moving object + const float *start; + vec3_t end; + trace_t trace; + int passEntityNum; + int contentmask; + int capsule; +} moveclip_t; + + +/* +==================== +SV_ClipToEntity + +==================== +*/ +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ) { + sharedEntity_t *touch; + clipHandle_t clipHandle; + float *origin, *angles; + + touch = SV_GentityNum( entityNum ); + + memset( trace, 0, sizeof( trace_t ) ); + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( !( contentmask & touch->r.contents ) ) { + trace->fraction = 1.0; + return; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity( touch ); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + +#ifdef __MACOS__ + // compiler bug with const + CM_TransformedBoxTrace( trace, (float *)start, (float *)end, + (float *)mins, (float *)maxs, clipHandle, contentmask, + origin, angles, capsule ); +#else + CM_TransformedBoxTrace( trace, start, end, + mins, maxs, clipHandle, contentmask, + origin, angles, capsule ); +#endif + if ( trace->fraction < 1 ) { + trace->entityNum = touch->s.number; + } +} + + +// FIXME: Copied from cm_local.h +#define BOX_MODEL_HANDLE 511 + +/* +==================== +SV_ClipMoveToEntities + +==================== +*/ +void SV_ClipMoveToEntities( moveclip_t *clip ) { + int i, num; + int touchlist[MAX_GENTITIES]; + sharedEntity_t *touch; + int passOwnerNum; + trace_t trace; + clipHandle_t clipHandle; + float *origin, *angles; + + num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES ); + + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum; + if ( passOwnerNum == ENTITYNUM_NONE ) { + passOwnerNum = -1; + } + } else { + passOwnerNum = -1; + } + + for ( i = 0 ; i < num ; i++ ) { + if ( clip->trace.allsolid ) { + return; + } + touch = SV_GentityNum( touchlist[i] ); + + // see if we should ignore this entity + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + if ( touchlist[i] == clip->passEntityNum ) { + continue; // don't clip against the pass entity + } + if ( touch->r.ownerNum == clip->passEntityNum ) { + continue; // don't clip against own missiles + } + if ( touch->r.ownerNum == passOwnerNum ) { + continue; // don't clip against other missiles from our owner + } + } + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( !( clip->contentmask & touch->r.contents ) ) { + continue; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity( touch ); + + // DHM - Nerve :: If clipping against BBOX, set to correct contents + if ( clipHandle == BOX_MODEL_HANDLE ) { + CM_SetTempBoxModelContents( touch->r.contents ); + } + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + +#ifdef __MACOS__ + // compiler bug with const + CM_TransformedBoxTrace( &trace, (float *)clip->start, (float *)clip->end, + (float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask, + origin, angles, clip->capsule ); +#else + CM_TransformedBoxTrace( &trace, clip->start, clip->end, + clip->mins, clip->maxs, clipHandle, clip->contentmask, + origin, angles, clip->capsule ); +#endif + if ( trace.allsolid ) { + clip->trace.allsolid = qtrue; + trace.entityNum = touch->s.number; + } else if ( trace.startsolid ) { + clip->trace.startsolid = qtrue; + trace.entityNum = touch->s.number; + } + + if ( trace.fraction < clip->trace.fraction ) { + qboolean oldStart; + + // make sure we keep a startsolid from a previous trace + oldStart = clip->trace.startsolid; + + trace.entityNum = touch->s.number; + clip->trace = trace; + clip->trace.startsolid |= oldStart; + } + + // DHM - Nerve :: Reset contents to default + if ( clipHandle == BOX_MODEL_HANDLE ) { + CM_SetTempBoxModelContents( CONTENTS_BODY ); + } + } +} + + +/* +================== +SV_Trace + +Moves the given mins/maxs volume through the world from start to end. +passEntityNum and entities owned by passEntityNum are explicitly not checked. +================== +*/ +void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, int capsule ) { + moveclip_t clip; + int i; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + memset( &clip, 0, sizeof( moveclip_t ) ); + + // clip to world + CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, capsule ); + clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( clip.trace.fraction == 0 ) { + *results = clip.trace; + return; // blocked immediately by the world + } + + clip.contentmask = contentmask; + clip.start = start; +// VectorCopy( clip.trace.endpos, clip.end ); + VectorCopy( end, clip.end ); + clip.mins = mins; + clip.maxs = maxs; + clip.passEntityNum = passEntityNum; + clip.capsule = capsule; + + // create the bounding box of the entire move + // we can limit it to the part of the move not + // already clipped off by the world, which can be + // a significant savings for line of sight and shot traces + for ( i = 0 ; i < 3 ; i++ ) { + if ( end[i] > start[i] ) { + clip.boxmins[i] = clip.start[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.end[i] + clip.maxs[i] + 1; + } else { + clip.boxmins[i] = clip.end[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.start[i] + clip.maxs[i] + 1; + } + } + + // clip to other solid entities + SV_ClipMoveToEntities( &clip ); + + *results = clip.trace; +} + + + +/* +============= +SV_PointContents +============= +*/ +int SV_PointContents( const vec3_t p, int passEntityNum ) { + int touch[MAX_GENTITIES]; + sharedEntity_t *hit; + int i, num; + int contents, c2; + clipHandle_t clipHandle; + float *angles; + + // get base contents from world + contents = CM_PointContents( p, 0 ); + + // or in contents from all the other entities + num = SV_AreaEntities( p, p, touch, MAX_GENTITIES ); + + for ( i = 0 ; i < num ; i++ ) { + if ( touch[i] == passEntityNum ) { + continue; + } + hit = SV_GentityNum( touch[i] ); + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity( hit ); + angles = hit->s.angles; + if ( !hit->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + c2 = CM_TransformedPointContents( p, clipHandle, hit->s.origin, hit->s.angles ); + + contents |= c2; + } + + return contents; +} + + diff --git a/src/splines/Splines.vcproj b/src/splines/Splines.vcproj new file mode 100644 index 0000000..72a670b --- /dev/null +++ b/src/splines/Splines.vcproj @@ -0,0 +1,396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/splines/math_angles.cpp b/src/splines/math_angles.cpp new file mode 100644 index 0000000..3100abb --- /dev/null +++ b/src/splines/math_angles.cpp @@ -0,0 +1,157 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "q_splineshared.h" //DAJ +#include + +angles_t ang_zero( 0.0f, 0.0f, 0.0f ); + +void toAngles( mat3_t &src, angles_t &dst ) { + double theta; + double cp; + double sp; + + sp = src[ 0 ][ 2 ]; + + // cap off our sin value so that we don't get any NANs + if ( sp > 1.0 ) { + sp = 1.0; + } else if ( sp < -1.0 ) { + sp = -1.0; + } + + theta = -asin( sp ); + cp = cos( theta ); + + if ( cp > 8192 * FLT_EPSILON ) { + dst.pitch = theta * 180 / M_PI; + dst.yaw = atan2( src[ 0 ][ 1 ], src[ 0 ][ 0 ] ) * 180 / M_PI; + dst.roll = atan2( src[ 1 ][ 2 ], src[ 2 ][ 2 ] ) * 180 / M_PI; + } else { + dst.pitch = theta * 180 / M_PI; + dst.yaw = -atan2( src[ 1 ][ 0 ], src[ 1 ][ 1 ] ) * 180 / M_PI; + dst.roll = 0; + } +} + +void toAngles( quat_t &src, angles_t &dst ) { + mat3_t temp; + + toMatrix( src, temp ); + toAngles( temp, dst ); +} + +void toAngles( idVec3 &src, angles_t &dst ) { + dst.pitch = src[ 0 ]; + dst.yaw = src[ 1 ]; + dst.roll = src[ 2 ]; +} + +void angles_t::toVectors( idVec3 *forward, idVec3 *right, idVec3 *up ) { + float angle; + static float sr, sp, sy, cr, cp, cy; // static to help MS compiler fp bugs + + angle = yaw * ( M_PI * 2 / 360 ); + sy = sin( angle ); + cy = cos( angle ); + + angle = pitch * ( M_PI * 2 / 360 ); + sp = sin( angle ); + cp = cos( angle ); + + angle = roll * ( M_PI * 2 / 360 ); + sr = sin( angle ); + cr = cos( angle ); + + if ( forward ) { + forward->set( cp * cy, cp * sy, -sp ); + } + + if ( right ) { + right->set( -sr * sp * cy + cr * sy, -sr * sp * sy + - cr * cy, -sr * cp ); + } + + if ( up ) { + up->set( cr * sp * cy + - sr * -sy, cr * sp * sy + - sr * cy, cr * cp ); + } +} + +idVec3 angles_t::toForward( void ) { + float angle; + static float sp, sy, cp, cy; // static to help MS compiler fp bugs + + angle = yaw * ( M_PI * 2 / 360 ); + sy = sin( angle ); + cy = cos( angle ); + + angle = pitch * ( M_PI * 2 / 360 ); + sp = sin( angle ); + cp = cos( angle ); + + return idVec3( cp * cy, cp * sy, -sp ); +} + +/* +================= +Normalize360 + +returns angles normalized to the range [0 <= angle < 360] +================= +*/ +angles_t& angles_t::Normalize360( void ) { + pitch = ( 360.0 / 65536 ) * ( ( int )( pitch * ( 65536 / 360.0 ) ) & 65535 ); + yaw = ( 360.0 / 65536 ) * ( ( int )( yaw * ( 65536 / 360.0 ) ) & 65535 ); + roll = ( 360.0 / 65536 ) * ( ( int )( roll * ( 65536 / 360.0 ) ) & 65535 ); + + return *this; +} + + +/* +================= +Normalize180 + +returns angles normalized to the range [-180 < angle <= 180] +================= +*/ +angles_t& angles_t::Normalize180( void ) { + Normalize360(); + + if ( pitch > 180.0 ) { + pitch -= 360.0; + } + + if ( yaw > 180.0 ) { + yaw -= 360.0; + } + + if ( roll > 180.0 ) { + roll -= 360.0; + } + return *this; +} diff --git a/src/splines/math_angles.h b/src/splines/math_angles.h new file mode 100644 index 0000000..1cb6ba3 --- /dev/null +++ b/src/splines/math_angles.h @@ -0,0 +1,203 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_ANGLES_H__ +#define __MATH_ANGLES_H__ + +#include +#include + +#include "math_vector.h" + +class mat3_t; +class quat_t; +class idVec3; +typedef idVec3 &vec3_p; + +class angles_t { +public: +float pitch; +float yaw; +float roll; + +angles_t(); +angles_t( float pitch, float yaw, float roll ); +angles_t( const idVec3 &vec ); + +friend void toAngles( idVec3 &src, angles_t &dst ); +friend void toAngles( quat_t &src, angles_t &dst ); +friend void toAngles( mat3_t &src, angles_t &dst ); + +operator vec3_p(); + +float operator[]( int index ) const; +float& operator[]( int index ); + +void set( float pitch, float yaw, float roll ); + +void operator=( angles_t const &a ); +void operator=( idVec3 const &a ); + +friend angles_t operator+( const angles_t &a, const angles_t &b ); +angles_t &operator+=( angles_t const &a ); +angles_t &operator+=( idVec3 const &a ); + +friend angles_t operator-( angles_t &a, angles_t &b ); +angles_t &operator-=( angles_t &a ); + +friend angles_t operator*( const angles_t &a, float b ); +friend angles_t operator*( float a, const angles_t &b ); +angles_t &operator*=( float a ); + +friend int operator==( angles_t &a, angles_t &b ); + +friend int operator!=( angles_t &a, angles_t &b ); + +void toVectors( idVec3 *forward, idVec3 *right = NULL, idVec3 *up = NULL ); +idVec3 toForward( void ); + +angles_t &Zero( void ); + +angles_t &Normalize360( void ); +angles_t &Normalize180( void ); +}; + +extern angles_t ang_zero; + +inline angles_t::angles_t() { +} + +inline angles_t::angles_t( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +inline angles_t::angles_t( const idVec3 &vec ) { + this->pitch = vec.x; + this->yaw = vec.y; + this->roll = vec.z; +} + +inline float angles_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +inline float& angles_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +inline angles_t::operator vec3_p( void ) { + return *( idVec3 * )&pitch; +} + +inline void angles_t::set( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +inline void angles_t::operator=( angles_t const &a ) { + pitch = a.pitch; + yaw = a.yaw; + roll = a.roll; +} + +inline void angles_t::operator=( idVec3 const &a ) { + pitch = a[ 0 ]; + yaw = a[ 1 ]; + roll = a[ 2 ]; +} + +inline angles_t operator+( const angles_t &a, const angles_t &b ) { + return angles_t( a.pitch + b.pitch, a.yaw + b.yaw, a.roll + b.roll ); +} + +inline angles_t& angles_t::operator+=( angles_t const &a ) { + pitch += a.pitch; + yaw += a.yaw; + roll += a.roll; + + return *this; +} + +inline angles_t& angles_t::operator+=( idVec3 const &a ) { + pitch += a.x; + yaw += a.y; + roll += a.z; + + return *this; +} + +inline angles_t operator-( angles_t &a, angles_t &b ) { + return angles_t( a.pitch - b.pitch, a.yaw - b.yaw, a.roll - b.roll ); +} + +inline angles_t& angles_t::operator-=( angles_t &a ) { + pitch -= a.pitch; + yaw -= a.yaw; + roll -= a.roll; + + return *this; +} + +inline angles_t operator*( const angles_t &a, float b ) { + return angles_t( a.pitch * b, a.yaw * b, a.roll * b ); +} + +inline angles_t operator*( float a, const angles_t &b ) { + return angles_t( a * b.pitch, a * b.yaw, a * b.roll ); +} + +inline angles_t& angles_t::operator*=( float a ) { + pitch *= a; + yaw *= a; + roll *= a; + + return *this; +} + +inline int operator==( angles_t &a, angles_t &b ) { + return ( ( a.pitch == b.pitch ) && ( a.yaw == b.yaw ) && ( a.roll == b.roll ) ); +} + +inline int operator!=( angles_t &a, angles_t &b ) { + return ( ( a.pitch != b.pitch ) || ( a.yaw != b.yaw ) || ( a.roll != b.roll ) ); +} + +inline angles_t& angles_t::Zero( void ) { + pitch = 0.0f; + yaw = 0.0f; + roll = 0.0f; + + return *this; +} + +#endif /* !__MATH_ANGLES_H__ */ diff --git a/src/splines/math_matrix.cpp b/src/splines/math_matrix.cpp new file mode 100644 index 0000000..0d9dfb3 --- /dev/null +++ b/src/splines/math_matrix.cpp @@ -0,0 +1,141 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "q_splineshared.h" //DAJ + +mat3_t mat3_default( idVec3( 1, 0, 0 ), idVec3( 0, 1, 0 ), idVec3( 0, 0, 1 ) ); + +void toMatrix( quat_t const &src, mat3_t &dst ) { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + + x2 = src.x + src.x; + y2 = src.y + src.y; + z2 = src.z + src.z; + + xx = src.x * x2; + xy = src.x * y2; + xz = src.x * z2; + + yy = src.y * y2; + yz = src.y * z2; + zz = src.z * z2; + + wx = src.w * x2; + wy = src.w * y2; + wz = src.w * z2; + + dst[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + dst[ 0 ][ 1 ] = xy - wz; + dst[ 0 ][ 2 ] = xz + wy; + + dst[ 1 ][ 0 ] = xy + wz; + dst[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + dst[ 1 ][ 2 ] = yz - wx; + + dst[ 2 ][ 0 ] = xz - wy; + dst[ 2 ][ 1 ] = yz + wx; + dst[ 2 ][ 2 ] = 1.0f - ( xx + yy ); +} + +void toMatrix( angles_t const &src, mat3_t &dst ) { + float angle; + static float sr, sp, sy, cr, cp, cy; // static to help MS compiler fp bugs + + angle = src.yaw * ( M_PI * 2.0f / 360.0f ); + sy = sin( angle ); + cy = cos( angle ); + + angle = src.pitch * ( M_PI * 2.0f / 360.0f ); + sp = sin( angle ); + cp = cos( angle ); + + angle = src.roll * ( M_PI * 2.0f / 360.0f ); + sr = sin( angle ); + cr = cos( angle ); + + dst[ 0 ].set( cp * cy, cp * sy, -sp ); + dst[ 1 ].set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + dst[ 2 ].set( cr * sp * cy + - sr * -sy, cr * sp * sy + - sr * cy, cr * cp ); +} + +void toMatrix( idVec3 const &src, mat3_t &dst ) { + angles_t sup = src; + toMatrix( sup, dst ); +} + +void mat3_t::ProjectVector( const idVec3 &src, idVec3 &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; +} + +void mat3_t::UnprojectVector( const idVec3 &src, idVec3 &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z; +} + +void mat3_t::Transpose( mat3_t &matrix ) { + int i; + int j; + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + matrix[ i ][ j ] = mat[ j ][ i ]; + } + } +} + +void mat3_t::Transpose( void ) { + float temp; + int i; + int j; + + for ( i = 0; i < 3; i++ ) { + for ( j = i + 1; j < 3; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } +} + +mat3_t mat3_t::Inverse( void ) const { + mat3_t inv( *this ); + + inv.Transpose(); + + return inv; +} + +void mat3_t::Clear( void ) { + mat[0].set( 1, 0, 0 ); + mat[1].set( 0, 1, 0 ); + mat[2].set( 0, 0, 1 ); +} diff --git a/src/splines/math_matrix.h b/src/splines/math_matrix.h new file mode 100644 index 0000000..e93e683 --- /dev/null +++ b/src/splines/math_matrix.h @@ -0,0 +1,230 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_MATRIX_H__ +#define __MATH_MATRIX_H__ + +#include +#include "math_vector.h" + +#ifndef ID_INLINE +#ifdef _WIN32 +#define ID_INLINE __inline +#else +#define ID_INLINE inline +#endif +#endif + +class quat_t; +class angles_t; + +class mat3_t { +public: +idVec3 mat[ 3 ]; + +mat3_t(); +mat3_t( float src[ 3 ][ 3 ] ); +mat3_t( idVec3 const &x, idVec3 const &y, idVec3 const &z ); +mat3_t( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ); + +friend void toMatrix( quat_t const &src, mat3_t &dst ); +friend void toMatrix( angles_t const &src, mat3_t &dst ); +friend void toMatrix( idVec3 const &src, mat3_t &dst ); + +idVec3 operator[]( int index ) const; +idVec3 &operator[]( int index ); + +idVec3 operator*( const idVec3 &vec ) const; +mat3_t operator*( const mat3_t &a ) const; +mat3_t operator*( float a ) const; +mat3_t operator+( mat3_t const &a ) const; +mat3_t operator-( mat3_t const &a ) const; + +friend idVec3 operator*( const idVec3 &vec, const mat3_t &mat ); +friend mat3_t operator*( float a, mat3_t const &b ); + +mat3_t &operator*=( float a ); +mat3_t &operator+=( mat3_t const &a ); +mat3_t &operator-=( mat3_t const &a ); + +void Clear( void ); + +void ProjectVector( const idVec3 &src, idVec3 &dst ) const; +void UnprojectVector( const idVec3 &src, idVec3 &dst ) const; + +void OrthoNormalize( void ); +void Transpose( mat3_t &matrix ); +void Transpose( void ); +mat3_t Inverse( void ) const; +void Identity( void ); + +friend void InverseMultiply( const mat3_t &inv, const mat3_t &b, mat3_t &dst ); +friend mat3_t SkewSymmetric( idVec3 const &src ); +}; + +ID_INLINE mat3_t::mat3_t() { +} + +ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) { + memcpy( mat, src, sizeof( src ) ); +} + +ID_INLINE mat3_t::mat3_t( idVec3 const &x, idVec3 const &y, idVec3 const &z ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; mat[ 0 ].z = x.z; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; mat[ 1 ].z = y.z; + mat[ 2 ].x = z.x; mat[ 2 ].y = z.y; mat[ 2 ].z = z.z; +} + +ID_INLINE mat3_t::mat3_t( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; mat[ 0 ].z = xz; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; mat[ 1 ].z = yz; + mat[ 2 ].x = zx; mat[ 2 ].y = zy; mat[ 2 ].z = zz; +} + +ID_INLINE idVec3 mat3_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3& mat3_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3 mat3_t::operator*( const idVec3 &vec ) const { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE mat3_t mat3_t::operator*( const mat3_t &a ) const { + return mat3_t( + mat[0].x * a[0].x + mat[0].y * a[1].x + mat[0].z * a[2].x, + mat[0].x * a[0].y + mat[0].y * a[1].y + mat[0].z * a[2].y, + mat[0].x * a[0].z + mat[0].y * a[1].z + mat[0].z * a[2].z, + mat[1].x * a[0].x + mat[1].y * a[1].x + mat[1].z * a[2].x, + mat[1].x * a[0].y + mat[1].y * a[1].y + mat[1].z * a[2].y, + mat[1].x * a[0].z + mat[1].y * a[1].z + mat[1].z * a[2].z, + mat[2].x * a[0].x + mat[2].y * a[1].x + mat[2].z * a[2].x, + mat[2].x * a[0].y + mat[2].y * a[1].y + mat[2].z * a[2].y, + mat[2].x * a[0].z + mat[2].y * a[1].z + mat[2].z * a[2].z ); +} + +ID_INLINE mat3_t mat3_t::operator*( float a ) const { + return mat3_t( + mat[0].x * a, mat[0].y * a, mat[0].z * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a ); +} + +ID_INLINE mat3_t mat3_t::operator+( mat3_t const &a ) const { + return mat3_t( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z ); +} + +ID_INLINE mat3_t mat3_t::operator-( mat3_t const &a ) const { + return mat3_t( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z ); +} + +ID_INLINE idVec3 operator*( const idVec3 &vec, const mat3_t &mat ) { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE mat3_t operator*( float a, mat3_t const &b ) { + return mat3_t( + b[0].x * a, b[0].y * a, b[0].z * a, + b[1].x * a, b[1].y * a, b[1].z * a, + b[2].x * a, b[2].y * a, b[2].z * a ); +} + +ID_INLINE mat3_t &mat3_t::operator*=( float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; + + return *this; +} + +ID_INLINE mat3_t &mat3_t::operator+=( mat3_t const &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; + + return *this; +} + +ID_INLINE mat3_t &mat3_t::operator-=( mat3_t const &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; + + return *this; +} + +ID_INLINE void mat3_t::OrthoNormalize( void ) { + mat[ 0 ].Normalize(); + mat[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + mat[ 2 ].Normalize(); + mat[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + mat[ 1 ].Normalize(); +} + +ID_INLINE void mat3_t::Identity( void ) { + mat[ 0 ].x = 1.f; mat[ 0 ].y = 0.f; mat[ 0 ].z = 0.f; + mat[ 1 ].x = 0.f; mat[ 1 ].y = 1.f; mat[ 1 ].z = 0.f; + mat[ 2 ].x = 0.f; mat[ 2 ].y = 0.f; mat[ 2 ].z = 1.f; +} + +ID_INLINE void InverseMultiply( const mat3_t &inv, const mat3_t &b, mat3_t &dst ) { + dst[0].x = inv[0].x * b[0].x + inv[1].x * b[1].x + inv[2].x * b[2].x; + dst[0].y = inv[0].x * b[0].y + inv[1].x * b[1].y + inv[2].x * b[2].y; + dst[0].z = inv[0].x * b[0].z + inv[1].x * b[1].z + inv[2].x * b[2].z; + dst[1].x = inv[0].y * b[0].x + inv[1].y * b[1].x + inv[2].y * b[2].x; + dst[1].y = inv[0].y * b[0].y + inv[1].y * b[1].y + inv[2].y * b[2].y; + dst[1].z = inv[0].y * b[0].z + inv[1].y * b[1].z + inv[2].y * b[2].z; + dst[2].x = inv[0].z * b[0].x + inv[1].z * b[1].x + inv[2].z * b[2].x; + dst[2].y = inv[0].z * b[0].y + inv[1].z * b[1].y + inv[2].z * b[2].y; + dst[2].z = inv[0].z * b[0].z + inv[1].z * b[1].z + inv[2].z * b[2].z; +} + +ID_INLINE mat3_t SkewSymmetric( idVec3 const &src ) { + return mat3_t( 0.0f, -src.z, src.y, src.z, 0.0f, -src.x, -src.y, src.x, 0.0f ); +} + +extern mat3_t mat3_default; + +#endif /* !__MATH_MATRIX_H__ */ diff --git a/src/splines/math_quaternion.cpp b/src/splines/math_quaternion.cpp new file mode 100644 index 0000000..22d3e6b --- /dev/null +++ b/src/splines/math_quaternion.cpp @@ -0,0 +1,85 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "math_quaternion.h" +#include "math_matrix.h" + +void toQuat( idVec3 &src, quat_t &dst ) { + dst.x = src.x; + dst.y = src.y; + dst.z = src.z; + dst.w = 0.0f; +} + +void toQuat( angles_t &src, quat_t &dst ) { + mat3_t temp; + + toMatrix( src, temp ); + toQuat( temp, dst ); +} + +void toQuat( mat3_t &src, quat_t &dst ) { + float trace; + float s; + int i; + int j; + int k; + + static int next[ 3 ] = { 1, 2, 0 }; + + trace = src[ 0 ][ 0 ] + src[ 1 ][ 1 ] + src[ 2 ][ 2 ]; + if ( trace > 0.0f ) { + s = ( float )sqrt( trace + 1.0f ); + dst.w = s * 0.5f; + s = 0.5f / s; + + dst.x = ( src[ 2 ][ 1 ] - src[ 1 ][ 2 ] ) * s; + dst.y = ( src[ 0 ][ 2 ] - src[ 2 ][ 0 ] ) * s; + dst.z = ( src[ 1 ][ 0 ] - src[ 0 ][ 1 ] ) * s; + } else { + i = 0; + if ( src[ 1 ][ 1 ] > src[ 0 ][ 0 ] ) { + i = 1; + } + if ( src[ 2 ][ 2 ] > src[ i ][ i ] ) { + i = 2; + } + + j = next[ i ]; + k = next[ j ]; + + s = ( float )sqrt( ( src[ i ][ i ] - ( src[ j ][ j ] + src[ k ][ k ] ) ) + 1.0f ); + dst[ i ] = s * 0.5f; + + s = 0.5f / s; + + dst.w = ( src[ k ][ j ] - src[ j ][ k ] ) * s; + dst[ j ] = ( src[ j ][ i ] + src[ i ][ j ] ) * s; + dst[ k ] = ( src[ k ][ i ] + src[ i ][ k ] ) * s; + } +} diff --git a/src/splines/math_quaternion.h b/src/splines/math_quaternion.h new file mode 100644 index 0000000..a94c215 --- /dev/null +++ b/src/splines/math_quaternion.h @@ -0,0 +1,197 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_QUATERNION_H__ +#define __MATH_QUATERNION_H__ + +#include +#include + +class idVec3_t; +class angles_t; +class mat3_t; + +class quat_t { +public: +float x; +float y; +float z; +float w; + +quat_t(); +quat_t( float x, float y, float z, float w ); + +friend void toQuat( idVec3_t &src, quat_t &dst ); +friend void toQuat( angles_t &src, quat_t &dst ); +friend void toQuat( mat3_t &src, quat_t &dst ); + +float *vec4( void ); + +float operator[]( int index ) const; +float &operator[]( int index ); + +void set( float x, float y, float z, float w ); + +void operator=( quat_t a ); + +friend quat_t operator+( quat_t a, quat_t b ); +quat_t &operator+=( quat_t a ); + +friend quat_t operator-( quat_t a, quat_t b ); +quat_t &operator-=( quat_t a ); + +friend quat_t operator*( quat_t a, float b ); +friend quat_t operator*( float a, quat_t b ); +quat_t &operator*=( float a ); + +friend int operator==( quat_t a, quat_t b ); +friend int operator!=( quat_t a, quat_t b ); + +float Length( void ); +quat_t &Normalize( void ); + +quat_t operator-(); +}; + +inline quat_t::quat_t() { +} + +inline quat_t::quat_t( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +inline float *quat_t::vec4( void ) { + return &x; +} + +inline float quat_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +inline float& quat_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +inline void quat_t::set( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +inline void quat_t::operator=( quat_t a ) { + x = a.x; + y = a.y; + z = a.z; + w = a.w; +} + +inline quat_t operator+( quat_t a, quat_t b ) { + return quat_t( a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w ); +} + +inline quat_t& quat_t::operator+=( quat_t a ) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +inline quat_t operator-( quat_t a, quat_t b ) { + return quat_t( a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w ); +} + +inline quat_t& quat_t::operator-=( quat_t a ) { + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +inline quat_t operator*( quat_t a, float b ) { + return quat_t( a.x * b, a.y * b, a.z * b, a.w * b ); +} + +inline quat_t operator*( float a, quat_t b ) { + return b * a; +} + +inline quat_t& quat_t::operator*=( float a ) { + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +inline int operator==( quat_t a, quat_t b ) { + return ( ( a.x == b.x ) && ( a.y == b.y ) && ( a.z == b.z ) && ( a.w == b.w ) ); +} + +inline int operator!=( quat_t a, quat_t b ) { + return ( ( a.x != b.x ) || ( a.y != b.y ) || ( a.z != b.z ) && ( a.w != b.w ) ); +} + +inline float quat_t::Length( void ) { + float length; + + length = x * x + y * y + z * z + w * w; + return ( float )sqrt( length ); +} + +inline quat_t& quat_t::Normalize( void ) { + float length; + float ilength; + + length = this->Length(); + if ( length ) { + ilength = 1 / length; + x *= ilength; + y *= ilength; + z *= ilength; + w *= ilength; + } + + return *this; +} + +inline quat_t quat_t::operator-() { + return quat_t( -x, -y, -z, -w ); +} + +#endif /* !__MATH_QUATERNION_H__ */ diff --git a/src/splines/math_vector.cpp b/src/splines/math_vector.cpp new file mode 100644 index 0000000..2b3c0d0 --- /dev/null +++ b/src/splines/math_vector.cpp @@ -0,0 +1,151 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//#include "../game/q_shared.h" +#include "math_vector.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h + +#define LERP_DELTA 1e-6 + +idVec3 vec_zero( 0.0f, 0.0f, 0.0f ); + +Bounds boundsZero; + +float idVec3::toYaw( void ) { + float yaw; + + if ( ( y == 0 ) && ( x == 0 ) ) { + yaw = 0; + } else { + yaw = atan2( y, x ) * 180 / M_PI; + if ( yaw < 0 ) { + yaw += 360; + } + } + + return yaw; +} + +float idVec3::toPitch( void ) { + float forward; + float pitch; + + if ( ( x == 0 ) && ( y == 0 ) ) { + if ( z > 0 ) { + pitch = 90; + } else { + pitch = 270; + } + } else { + forward = ( float )idSqrt( x * x + y * y ); + pitch = atan2( z, forward ) * 180 / M_PI; + if ( pitch < 0 ) { + pitch += 360; + } + } + + return pitch; +} + +/* +angles_t idVec3::toAngles( void ) { + float forward; + float yaw; + float pitch; + + if ( ( x == 0 ) && ( y == 0 ) ) { + yaw = 0; + if ( z > 0 ) { + pitch = 90; + } else { + pitch = 270; + } + } else { + yaw = atan2( y, x ) * 180 / M_PI; + if ( yaw < 0 ) { + yaw += 360; + } + + forward = ( float )idSqrt( x * x + y * y ); + pitch = atan2( z, forward ) * 180 / M_PI; + if ( pitch < 0 ) { + pitch += 360; + } + } + + return angles_t( -pitch, yaw, 0 ); +} +*/ + +idVec3 LerpVector( idVec3 &w1, idVec3 &w2, const float t ) { + float omega, cosom, sinom, scale0, scale1; + + cosom = w1 * w2; + if ( ( 1.0 - cosom ) > LERP_DELTA ) { + omega = acos( cosom ); + sinom = sin( omega ); + scale0 = sin( ( 1.0 - t ) * omega ) / sinom; + scale1 = sin( t * omega ) / sinom; + } else { + scale0 = 1.0 - t; + scale1 = t; + } + + return ( w1 * scale0 + w2 * scale1 ); +} + +/* +============= +idVec3::string + +This is just a convenience function +for printing vectors +============= +*/ +char *idVec3::string( void ) { + static int index = 0; + static char str[ 8 ][ 36 ]; + char *s; + + // use an array so that multiple toString's won't collide + s = str[ index ]; + index = ( index + 1 ) & 7; + + sprintf( s, "%.2f %.2f %.2f", x, y, z ); + + return s; +} diff --git a/src/splines/math_vector.h b/src/splines/math_vector.h new file mode 100644 index 0000000..d47aaaf --- /dev/null +++ b/src/splines/math_vector.h @@ -0,0 +1,586 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_VECTOR_H__ +#define __MATH_VECTOR_H__ + +#ifndef __linux__ +#pragma warning(disable : 4244) +#endif + +#include +#include + +//#define DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) +//#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +//#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +//#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +//#define VectorCopy(a,b) ((b).x=(a).x,(b).y=(a).y,(b).z=(a).z]) + +//#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define __VectorMA( v, s, b, o ) ( ( o )[0] = ( v )[0] + ( b )[0] * ( s ),( o )[1] = ( v )[1] + ( b )[1] * ( s ),( o )[2] = ( v )[2] + ( b )[2] * ( s ) ) +//#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) + +#define DotProduct4( x,y ) ( ( x )[0] * ( y )[0] + ( x )[1] * ( y )[1] + ( x )[2] * ( y )[2] + ( x )[3] * ( y )[3] ) +#define VectorSubtract4( a,b,c ) ( ( c )[0] = ( a )[0] - ( b )[0],( c )[1] = ( a )[1] - ( b )[1],( c )[2] = ( a )[2] - ( b )[2],( c )[3] = ( a )[3] - ( b )[3] ) +#define VectorAdd4( a,b,c ) ( ( c )[0] = ( a )[0] + ( b )[0],( c )[1] = ( a )[1] + ( b )[1],( c )[2] = ( a )[2] + ( b )[2],( c )[3] = ( a )[3] + ( b )[3] ) +#define VectorCopy4( a,b ) ( ( b )[0] = ( a )[0],( b )[1] = ( a )[1],( b )[2] = ( a )[2],( b )[3] = ( a )[3] ) +#define VectorScale4( v, s, o ) ( ( o )[0] = ( v )[0] * ( s ),( o )[1] = ( v )[1] * ( s ),( o )[2] = ( v )[2] * ( s ),( o )[3] = ( v )[3] * ( s ) ) +#define VectorMA4( v, s, b, o ) ( ( o )[0] = ( v )[0] + ( b )[0] * ( s ),( o )[1] = ( v )[1] + ( b )[1] * ( s ),( o )[2] = ( v )[2] + ( b )[2] * ( s ),( o )[3] = ( v )[3] + ( b )[3] * ( s ) ) + + +//#define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0) +#define VectorNegate( a,b ) ( ( b )[0] = -( a )[0],( b )[1] = -( a )[1],( b )[2] = -( a )[2] ) +//#define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z)) +#define Vector4Copy( a,b ) ( ( b )[0] = ( a )[0],( b )[1] = ( a )[1],( b )[2] = ( a )[2],( b )[3] = ( a )[3] ) + +#define SnapVector( v ) {v[0] = (int)v[0]; v[1] = (int)v[1]; v[2] = (int)v[2];} + +//#include "util_heap.h" + +#ifndef EQUAL_EPSILON +#define EQUAL_EPSILON 0.001 +#endif + +float Q_fabs( float f ); + +#ifndef ID_INLINE +#ifdef _WIN32 +#define ID_INLINE __inline +#else +#define ID_INLINE inline +#endif +#endif + +// if this is defined, vec3 will take four elements, which may allow +// easier SIMD optimizations +//#define FAT_VEC3 +//#ifdef __ppc__ +//#pragma align(16) +//#endif + +class angles_t; +#ifdef __ppc__ +// Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, +// runs *much* faster than calling sqrt(). We'll use two Newton-Raphson +// refinement steps to get bunch more precision in the 1/sqrt() value for very little cost. +// We'll then multiply 1/sqrt times the original value to get the sqrt. +// This is about 12.4 times faster than sqrt() and according to my testing (not exhaustive) +// it returns fairly accurate results (error below 1.0e-5 up to 100000.0 in 0.1 increments). + +static inline float idSqrt( float x ) { + const float half = 0.5; + const float one = 1.0; + float B, y0, y1; + + // This'll NaN if it hits frsqrte. Handle both +0.0 and -0.0 + if ( fabs( x ) == 0.0 ) { + return x; + } + B = x; + +#ifdef __GNUC__ + asm ( "frsqrte %0,%1" : "=f" ( y0 ) : "f" ( B ) ); +#else + y0 = __frsqrte( B ); +#endif + /* First refinement step */ + + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + /* Second refinement step -- copy the output of the last step to the input of this step */ + + y0 = y1; + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + /* Get sqrt(x) from x * 1/sqrt(x) */ + return x * y1; +} +#else +static inline double idSqrt( double x ) { + return sqrt( x ); +} +#endif + + +//class idVec3 : public idHeap { +class idVec3 { +public: +#ifndef FAT_VEC3 + float x,y,z; +#else + float x,y,z,dist; +#endif + +#ifndef FAT_VEC3 + idVec3() { + }; +#else + idVec3() { + dist = 0.0f; + }; +#endif + idVec3( const float x, const float y, const float z ); + + operator float*(); + + float operator[]( const int index ) const; + float &operator[]( const int index ); + + void set( const float x, const float y, const float z ); + + idVec3 operator-() const; + + idVec3 &operator=( const idVec3 &a ); + + float operator*( const idVec3 &a ) const; + idVec3 operator*( const float a ) const; + friend idVec3 operator*( float a, idVec3 b ); + + idVec3 operator+( const idVec3 &a ) const; + idVec3 operator-( const idVec3 &a ) const; + + idVec3 &operator+=( const idVec3 &a ); + idVec3 &operator-=( const idVec3 &a ); + idVec3 &operator*=( const float a ); + + int operator==( const idVec3 &a ) const; + int operator!=( const idVec3 &a ) const; + + idVec3 Cross( const idVec3 &a ) const; + idVec3 &Cross( const idVec3 &a, const idVec3 &b ); + + float Length( void ) const; + float Normalize( void ); + + void Zero( void ); + void Snap( void ); + void SnapTowards( const idVec3 &to ); + + float toYaw( void ); + float toPitch( void ); + angles_t toAngles( void ); + friend idVec3 LerpVector( const idVec3 &w1, const idVec3 &w2, const float t ); + + char *string( void ); +}; + +extern idVec3 vec_zero; + +ID_INLINE idVec3::idVec3( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +#ifdef FAT_VEC3 + this->dist = 0.0f; +#endif +} + +ID_INLINE float idVec3::operator[]( const int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float &idVec3::operator[]( const int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec3::operator float*( void ) { + return &x; +} + +ID_INLINE idVec3 idVec3::operator-() const { + return idVec3( -x, -y, -z ); +} + +ID_INLINE idVec3 &idVec3::operator=( const idVec3 &a ) { + x = a.x; + y = a.y; + z = a.z; + + return *this; +} + +ID_INLINE void idVec3::set( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE idVec3 idVec3::operator-( const idVec3 &a ) const { + return idVec3( x - a.x, y - a.y, z - a.z ); +} + +ID_INLINE float idVec3::operator*( const idVec3 &a ) const { + return x * a.x + y * a.y + z * a.z; +} + +ID_INLINE idVec3 idVec3::operator*( const float a ) const { + return idVec3( x * a, y * a, z * a ); +} + +ID_INLINE idVec3 operator*( const float a, const idVec3 b ) { + return idVec3( b.x * a, b.y * a, b.z * a ); +} + +ID_INLINE idVec3 idVec3::operator+( const idVec3 &a ) const { + return idVec3( x + a.x, y + a.y, z + a.z ); +} + +ID_INLINE idVec3 &idVec3::operator+=( const idVec3 &a ) { + x += a.x; + y += a.y; + z += a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator-=( const idVec3 &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator*=( const float a ) { + x *= a; + y *= a; + z *= a; + + return *this; +} + +ID_INLINE int idVec3::operator==( const idVec3 &a ) const { + if ( Q_fabs( x - a.x ) > EQUAL_EPSILON ) { + return false; + } + + if ( Q_fabs( y - a.y ) > EQUAL_EPSILON ) { + return false; + } + + if ( Q_fabs( z - a.z ) > EQUAL_EPSILON ) { + return false; + } + + return true; +} + +ID_INLINE int idVec3::operator!=( const idVec3 &a ) const { + if ( Q_fabs( x - a.x ) > EQUAL_EPSILON ) { + return true; + } + + if ( Q_fabs( y - a.y ) > EQUAL_EPSILON ) { + return true; + } + + if ( Q_fabs( z - a.z ) > EQUAL_EPSILON ) { + return true; + } + + return false; +} + +ID_INLINE idVec3 idVec3::Cross( const idVec3 &a ) const { + return idVec3( y * a.z - z * a.y, z * a.x - x * a.z, x * a.y - y * a.x ); +} + +ID_INLINE idVec3 &idVec3::Cross( const idVec3 &a, const idVec3 &b ) { + x = a.y * b.z - a.z * b.y; + y = a.z * b.x - a.x * b.z; + z = a.x * b.y - a.y * b.x; + + return *this; +} + +ID_INLINE float idVec3::Length( void ) const { + float length; + + length = x * x + y * y + z * z; + return ( float )idSqrt( length ); +} + +ID_INLINE float idVec3::Normalize( void ) { + float length; + float ilength; + + length = this->Length(); + if ( length ) { + ilength = 1.0f / length; + x *= ilength; + y *= ilength; + z *= ilength; + } + + return length; +} + +ID_INLINE void idVec3::Zero( void ) { + x = 0.0f; + y = 0.0f; + z = 0.0f; +} + +ID_INLINE void idVec3::Snap( void ) { + x = float( int( x ) ); + y = float( int( y ) ); + z = float( int( z ) ); +} + +/* +====================== +SnapTowards + +Round a vector to integers for more efficient network +transmission, but make sure that it rounds towards a given point +rather than blindly truncating. This prevents it from truncating +into a wall. +====================== +*/ +ID_INLINE void idVec3::SnapTowards( const idVec3 &to ) { + if ( to.x <= x ) { + x = float( int( x ) ); + } else { + x = float( int( x ) + 1 ); + } + + if ( to.y <= y ) { + y = float( int( y ) ); + } else { + y = float( int( y ) + 1 ); + } + + if ( to.z <= z ) { + z = float( int( z ) ); + } else { + z = float( int( z ) + 1 ); + } +} + +//=============================================================== + +class Bounds { +public: + idVec3 b[2]; + + Bounds(); + Bounds( const idVec3 &mins, const idVec3 &maxs ); + + void Clear(); + void Zero(); + float Radius(); // radius from origin, not from center + idVec3 Center(); + void AddPoint( const idVec3 &v ); + void AddBounds( const Bounds &bb ); + bool IsCleared(); + bool ContainsPoint( const idVec3 &p ); + bool IntersectsBounds( const Bounds &b2 ); // touching is NOT intersecting +}; + +extern Bounds boundsZero; + +ID_INLINE Bounds::Bounds() { +} + +ID_INLINE bool Bounds::IsCleared() { + return b[0][0] > b[1][0]; +} + +ID_INLINE bool Bounds::ContainsPoint( const idVec3 &p ) { + if ( p[0] < b[0][0] || p[1] < b[0][1] || p[2] < b[0][2] + || p[0] > b[1][0] || p[1] > b[1][1] || p[2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE bool Bounds::IntersectsBounds( const Bounds &b2 ) { + if ( b2.b[1][0] < b[0][0] || b2.b[1][1] < b[0][1] || b2.b[1][2] < b[0][2] + || b2.b[0][0] > b[1][0] || b2.b[0][1] > b[1][1] || b2.b[0][2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE Bounds::Bounds( const idVec3 &mins, const idVec3 &maxs ) { + b[0] = mins; + b[1] = maxs; +} + +ID_INLINE idVec3 Bounds::Center() { + return idVec3( ( b[1][0] + b[0][0] ) * 0.5f, ( b[1][1] + b[0][1] ) * 0.5f, ( b[1][2] + b[0][2] ) * 0.5f ); +} + +ID_INLINE void Bounds::Clear() { + b[0][0] = b[0][1] = b[0][2] = 99999; + b[1][0] = b[1][1] = b[1][2] = -99999; +} + +ID_INLINE void Bounds::Zero() { + b[0][0] = b[0][1] = b[0][2] = + b[1][0] = b[1][1] = b[1][2] = 0; +} + +ID_INLINE void Bounds::AddPoint( const idVec3 &v ) { + if ( v[0] < b[0][0] ) { + b[0][0] = v[0]; + } + if ( v[0] > b[1][0] ) { + b[1][0] = v[0]; + } + if ( v[1] < b[0][1] ) { + b[0][1] = v[1]; + } + if ( v[1] > b[1][1] ) { + b[1][1] = v[1]; + } + if ( v[2] < b[0][2] ) { + b[0][2] = v[2]; + } + if ( v[2] > b[1][2] ) { + b[1][2] = v[2]; + } +} + + +ID_INLINE void Bounds::AddBounds( const Bounds &bb ) { + if ( bb.b[0][0] < b[0][0] ) { + b[0][0] = bb.b[0][0]; + } + if ( bb.b[0][1] < b[0][1] ) { + b[0][1] = bb.b[0][1]; + } + if ( bb.b[0][2] < b[0][2] ) { + b[0][2] = bb.b[0][2]; + } + + if ( bb.b[1][0] > b[1][0] ) { + b[1][0] = bb.b[1][0]; + } + if ( bb.b[1][1] > b[1][1] ) { + b[1][1] = bb.b[1][1]; + } + if ( bb.b[1][2] > b[1][2] ) { + b[1][2] = bb.b[1][2]; + } +} + +ID_INLINE float Bounds::Radius() { + int i; + float total; + float a, aa; + + total = 0; + for ( i = 0 ; i < 3 ; i++ ) { + a = (float)fabs( b[0][i] ); + aa = (float)fabs( b[1][i] ); + if ( aa > a ) { + a = aa; + } + total += a * a; + } + + return (float)idSqrt( total ); +} + +//=============================================================== + + +class idVec2 { +public: + float x; + float y; + + operator float*(); + float operator[]( int index ) const; + float &operator[]( int index ); +}; + +ID_INLINE float idVec2::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec2::operator[]( int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec2::operator float*( void ) { + return &x; +} + +class idVec4 : public idVec3 { +public: +#ifndef FAT_VEC3 + float dist; +#endif + idVec4(); + ~idVec4() { + }; + + idVec4( float x, float y, float z, float dist ); + float operator[]( int index ) const; + float &operator[]( int index ); +}; + +ID_INLINE idVec4::idVec4() { +} +ID_INLINE idVec4::idVec4( float x, float y, float z, float dist ) { + this->x = x; + this->y = y; + this->z = z; + this->dist = dist; +} + +ID_INLINE float idVec4::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec4::operator[]( int index ) { + return ( &x )[ index ]; +} + + +class idVec5_t : public idVec3 { +public: + float s; + float t; + float operator[]( int index ) const; + float &operator[]( int index ); +}; + + +ID_INLINE float idVec5_t::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec5_t::operator[]( int index ) { + return ( &x )[ index ]; +} + +#endif /* !__MATH_VECTOR_H__ */ diff --git a/src/splines/q_parse.cpp b/src/splines/q_parse.cpp new file mode 100644 index 0000000..275a3fc --- /dev/null +++ b/src/splines/q_parse.cpp @@ -0,0 +1,541 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// q_parse.c -- support for parsing text files + +#include "q_splineshared.h" //DAJ + +/* +============================================================================ + +PARSING + +============================================================================ +*/ + +// multiple character punctuation tokens +static const char *punctuation[] = { + "+=", "-=", "*=", "/=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", + NULL +}; + +typedef struct { + char token[MAX_TOKEN_CHARS]; + int lines; + qboolean ungetToken; + char parseFile[MAX_QPATH]; +} parseInfo_t; + +#define MAX_PARSE_INFO 16 +static parseInfo_t parseInfo[MAX_PARSE_INFO]; +static int parseInfoNum; +static parseInfo_t *pi = &parseInfo[0]; + +/* +=================== +Com_BeginParseSession +=================== +*/ +void Com_BeginParseSession( const char *filename ) { + if ( parseInfoNum == MAX_PARSE_INFO - 1 ) { + Com_Error( ERR_FATAL, "Com_BeginParseSession: session overflow" ); + } + parseInfoNum++; + pi = &parseInfo[parseInfoNum]; + + pi->lines = 1; + Q_strncpyz( pi->parseFile, filename, sizeof( pi->parseFile ) ); +} + +/* +=================== +Com_EndParseSession +=================== +*/ +void Com_EndParseSession( void ) { + if ( parseInfoNum == 0 ) { + Com_Error( ERR_FATAL, "Com_EndParseSession: session underflow" ); + } + parseInfoNum--; + pi = &parseInfo[parseInfoNum]; +} + +/* +=================== +Com_GetCurrentParseLine +=================== +*/ +int Com_GetCurrentParseLine( void ) { + return pi->lines; +} + +/* +=================== +Com_ScriptError + +Prints the script name and line number in the message +=================== +*/ +void Com_ScriptError( const char *msg, ... ) { + va_list argptr; + char string[32000]; + + va_start( argptr, msg ); + vsprintf( string, msg,argptr ); + va_end( argptr ); + + Com_Error( ERR_DROP, "File %s, line %i: %s", pi->parseFile, pi->lines, string ); +} + +void Com_ScriptWarning( const char *msg, ... ) { + va_list argptr; + char string[32000]; + + va_start( argptr, msg ); + vsprintf( string, msg,argptr ); + va_end( argptr ); + + Com_Printf( "File %s, line %i: %s", pi->parseFile, pi->lines, string ); +} + + +/* +=================== +Com_UngetToken + +Calling this will make the next Com_Parse return +the current token instead of advancing the pointer +=================== +*/ +void Com_UngetToken( void ) { + if ( pi->ungetToken ) { + Com_ScriptError( "UngetToken called twice" ); + } + pi->ungetToken = qtrue; +} + + +static const char *SkipWhitespace( const char(*data), qboolean *hasNewLines ) { + int c; + + while ( ( c = *data ) <= ' ' ) { + if ( !c ) { + return NULL; + } + if ( c == '\n' ) { + pi->lines++; + *hasNewLines = qtrue; + } + data++; + } + + return data; +} + +/* +============== +Com_ParseExt + +Parse a token out of a string +Will never return NULL, just empty strings. +An empty string will only be returned at end of file. + +If "allowLineBreaks" is qtrue then an empty +string will be returned if the next token is +a newline. +============== +*/ +static char *Com_ParseExt( const char *( *data_p ), qboolean allowLineBreaks ) { + int c = 0, len; + qboolean hasNewLines = qfalse; + const char *data; + const char **punc; + + if ( !data_p ) { + Com_Error( ERR_FATAL, "Com_ParseExt: NULL data_p" ); + } + + data = *data_p; + len = 0; + pi->token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return pi->token; + } + + // skip any leading whitespace + while ( 1 ) { + // skip whitespace + data = SkipWhitespace( data, &hasNewLines ); + if ( !data ) { + *data_p = NULL; + return pi->token; + } + if ( hasNewLines && !allowLineBreaks ) { + *data_p = data; + return pi->token; + } + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) { + while ( *data && *data != '\n' ) { + data++; + } + continue; + } + + // skip /* */ comments + if ( c == '/' && data[1] == '*' ) { + while ( *data && ( *data != '*' || data[1] != '/' ) ) { + if ( *data == '\n' ) { + pi->lines++; + } + data++; + } + if ( *data ) { + data += 2; + } + continue; + } + + // a real token to parse + break; + } + + // handle quoted strings + if ( c == '\"' ) { + data++; + while ( 1 ) { + c = *data++; + if ( ( c == '\\' ) && ( *data == '\"' ) ) { + // allow quoted strings to use \" to indicate the " character + data++; + } else if ( c == '\"' || !c ) { + pi->token[len] = 0; + *data_p = ( char * ) data; + return pi->token; + } else if ( *data == '\n' ) { + pi->lines++; + } + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + } + } + + // check for a number + // is this parsing of negative numbers going to cause expression problems + if ( ( c >= '0' && c <= '9' ) || ( c == '-' && data[ 1 ] >= '0' && data[ 1 ] <= '9' ) || + ( c == '.' && data[ 1 ] >= '0' && data[ 1 ] <= '9' ) ) { + do { + + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( ( c >= '0' && c <= '9' ) || c == '.' ); + + // parse the exponent + if ( c == 'e' || c == 'E' ) { + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + c = *data; + + if ( c == '-' || c == '+' ) { + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + c = *data; + } + + do { + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( c >= '0' && c <= '9' ); + } + + if ( len == MAX_TOKEN_CHARS ) { + len = 0; + } + pi->token[len] = 0; + + *data_p = ( char * ) data; + return pi->token; + } + + // check for a regular word + // we still allow forward and back slashes in name tokens for pathnames + // and also colons for drive letters + if ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_' || c == '/' || c == '\\' ) { + do { + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_' + || ( c >= '0' && c <= '9' ) || c == '/' || c == '\\' || c == ':' || c == '.' ); + + if ( len == MAX_TOKEN_CHARS ) { + len = 0; + } + pi->token[len] = 0; + + *data_p = ( char * ) data; + return pi->token; + } + + // check for multi-character punctuation token + for ( punc = punctuation ; *punc ; punc++ ) { + int l; + int j; + + l = strlen( *punc ); + for ( j = 0 ; j < l ; j++ ) { + if ( data[j] != ( *punc )[j] ) { + break; + } + } + if ( j == l ) { + // a valid multi-character punctuation + memcpy( pi->token, *punc, l ); + pi->token[l] = 0; + data += l; + *data_p = (char *)data; + return pi->token; + } + } + + // single character punctuation + pi->token[0] = *data; + pi->token[1] = 0; + data++; + *data_p = (char *)data; + + return pi->token; +} + +/* +=================== +Com_Parse +=================== +*/ +const char *Com_Parse( const char *( *data_p ) ) { + if ( pi->ungetToken ) { + pi->ungetToken = qfalse; + return pi->token; + } + return Com_ParseExt( data_p, qtrue ); +} + +/* +=================== +Com_ParseOnLine +=================== +*/ +const char *Com_ParseOnLine( const char *( *data_p ) ) { + if ( pi->ungetToken ) { + pi->ungetToken = qfalse; + return pi->token; + } + return Com_ParseExt( data_p, qfalse ); +} + + + +/* +================== +Com_MatchToken +================== +*/ +void Com_MatchToken( const char *( *buf_p ), const char *match, qboolean warning ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( strcmp( token, match ) ) { + if ( warning ) { + Com_ScriptWarning( "MatchToken: %s != %s", token, match ); + } else { + Com_ScriptError( "MatchToken: %s != %s", token, match ); + } + } +} + + +/* +================= +Com_SkipBracedSection + +The next token should be an open brace. +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +void Com_SkipBracedSection( const char *( *program ) ) { + const char *token; + int depth; + + depth = 0; + do { + token = Com_Parse( program ); + if ( token[1] == 0 ) { + if ( token[0] == '{' ) { + depth++; + } else if ( token[0] == '}' ) { + depth--; + } + } + } while ( depth && *program ); +} + +/* +================= +Com_SkipRestOfLine +================= +*/ +void Com_SkipRestOfLine( const char *( *data ) ) { + const char *p; + int c; + + p = *data; + while ( ( c = *p++ ) != 0 ) { + if ( c == '\n' ) { + pi->lines++; + break; + } + } + + *data = p; +} + +/* +==================== +Com_ParseRestOfLine +==================== +*/ +const char *Com_ParseRestOfLine( const char *( *data_p ) ) { + static char line[MAX_TOKEN_CHARS]; + const char *token; + + line[0] = 0; + while ( 1 ) { + token = Com_ParseOnLine( data_p ); + if ( !token[0] ) { + break; + } + if ( line[0] ) { + Q_strcat( line, sizeof( line ), " " ); + } + Q_strcat( line, sizeof( line ), token ); + } + + return line; +} + + +float Com_ParseFloat( const char *( *buf_p ) ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( !token[0] ) { + return 0; + } + return atof( token ); +} + +int Com_ParseInt( const char *( *buf_p ) ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( !token[0] ) { + return 0; + } + return (int)atof( token ); +} + + + +void Com_Parse1DMatrix( const char *( *buf_p ), int x, float *m ) { + const char *token; + int i; + + Com_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < x ; i++ ) { + token = Com_Parse( buf_p ); + m[i] = atof( token ); + } + + Com_MatchToken( buf_p, ")" ); +} + +void Com_Parse2DMatrix( const char *( *buf_p ), int y, int x, float *m ) { + int i; + + Com_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < y ; i++ ) { + Com_Parse1DMatrix( buf_p, x, m + i * x ); + } + + Com_MatchToken( buf_p, ")" ); +} + +void Com_Parse3DMatrix( const char *( *buf_p ), int z, int y, int x, float *m ) { + int i; + + Com_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < z ; i++ ) { + Com_Parse2DMatrix( buf_p, y, x, m + i * x * y ); + } + + Com_MatchToken( buf_p, ")" ); +} + diff --git a/src/splines/q_shared.cpp b/src/splines/q_shared.cpp new file mode 100644 index 0000000..78502d5 --- /dev/null +++ b/src/splines/q_shared.cpp @@ -0,0 +1,993 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// q_shared.c -- stateless support routines that are included in each code dll +#include "q_splineshared.h" //DAJ + +/* +============================================================================ + +GROWLISTS + +============================================================================ +*/ + +// malloc / free all in one place for debugging +extern "C" void *Com_Allocate( int bytes ); +extern "C" void Com_Dealloc( void *ptr ); + +void Com_InitGrowList( growList_t *list, int maxElements ) { + list->maxElements = maxElements; + list->currentElements = 0; + list->elements = (void **)Com_Allocate( list->maxElements * sizeof( void * ) ); +} + +int Com_AddToGrowList( growList_t *list, void *data ) { + void **old; + + if ( list->currentElements != list->maxElements ) { + list->elements[list->currentElements] = data; + return list->currentElements++; + } + + // grow, reallocate and move + old = list->elements; + + if ( list->maxElements < 0 ) { + Com_Error( ERR_FATAL, "Com_AddToGrowList: maxElements = %i", list->maxElements ); + } + + if ( list->maxElements == 0 ) { + // initialize the list to hold 100 elements + Com_InitGrowList( list, 100 ); + return Com_AddToGrowList( list, data ); + } + + list->maxElements *= 2; + + Com_DPrintf( "Resizing growlist to %i maxElements\n", list->maxElements ); + + list->elements = (void **)Com_Allocate( list->maxElements * sizeof( void * ) ); + + if ( !list->elements ) { + Com_Error( ERR_DROP, "Growlist alloc failed" ); + } + + memcpy( list->elements, old, list->currentElements * sizeof( void * ) ); + + Com_Dealloc( old ); + + return Com_AddToGrowList( list, data ); +} + +void *Com_GrowListElement( const growList_t *list, int index ) { + if ( index < 0 || index >= list->currentElements ) { + Com_Error( ERR_DROP, "Com_GrowListElement: %i out of range of %i", + index, list->currentElements ); + } + return list->elements[index]; +} + +int Com_IndexForGrowListElement( const growList_t *list, const void *element ) { + int i; + + for ( i = 0 ; i < list->currentElements ; i++ ) { + if ( list->elements[i] == element ) { + return i; + } + } + return -1; +} + +//============================================================================ + + +float Com_Clamp( float min, float max, float value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +/* +============ +Com_StringContains +============ +*/ +const char *Com_StringContains( const char *str1, const char *str2, int casesensitive ) { + int len, i, j; + + len = strlen( str1 ) - strlen( str2 ); + for ( i = 0; i <= len; i++, str1++ ) { + for ( j = 0; str2[j]; j++ ) { + if ( casesensitive ) { + if ( str1[j] != str2[j] ) { + break; + } + } else { + if ( toupper( str1[j] ) != toupper( str2[j] ) ) { + break; + } + } + } + if ( !str2[j] ) { + return str1; + } + } + return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter( const char *filter, const char *name, int casesensitive ) { + char buf[MAX_TOKEN_CHARS]; + const char *ptr; + int i, found; + + while ( *filter ) { + if ( *filter == '*' ) { + filter++; + for ( i = 0; *filter; i++ ) { + if ( *filter == '*' || *filter == '?' ) { + break; + } + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if ( strlen( buf ) ) { + ptr = Com_StringContains( name, buf, casesensitive ); + if ( !ptr ) { + return qfalse; + } + name = ptr + strlen( buf ); + } + } else if ( *filter == '?' ) { + filter++; + name++; + } else if ( *filter == '[' && *( filter + 1 ) == '[' ) { + filter++; + } else if ( *filter == '[' ) { + filter++; + found = qfalse; + while ( *filter && !found ) { + if ( *filter == ']' && *( filter + 1 ) != ']' ) { + break; + } + if ( *( filter + 1 ) == '-' && *( filter + 2 ) && ( *( filter + 2 ) != ']' || *( filter + 3 ) == ']' ) ) { + if ( casesensitive ) { + if ( *name >= *filter && *name <= *( filter + 2 ) ) { + found = qtrue; + } + } else { + if ( toupper( *name ) >= toupper( *filter ) && + toupper( *name ) <= toupper( *( filter + 2 ) ) ) { + found = qtrue; + } + } + filter += 3; + } else { + if ( casesensitive ) { + if ( *filter == *name ) { + found = qtrue; + } + } else { + if ( toupper( *filter ) == toupper( *name ) ) { + found = qtrue; + } + } + filter++; + } + } + if ( !found ) { + return qfalse; + } + while ( *filter ) { + if ( *filter == ']' && *( filter + 1 ) != ']' ) { + break; + } + filter++; + } + filter++; + name++; + } else { + if ( casesensitive ) { + if ( *filter != *name ) { + return qfalse; + } + } else { + if ( toupper( *filter ) != toupper( *name ) ) { + return qfalse; + } + } + filter++; + name++; + } + } + return qtrue; +} + + +/* +================ +Com_HashString + +================ +*/ +int Com_HashString( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter == '\\' ) { + letter = '/'; // damn path names + } + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( FILE_HASH_SIZE - 1 ); + return hash; +} + + +/* +============ +Com_SkipPath +============ +*/ +char *Com_SkipPath( char *pathname ) { + char *last; + + last = pathname; + while ( *pathname ) + { + if ( *pathname == '/' ) { + last = pathname + 1; + } + pathname++; + } + return last; +} + +/* +============ +Com_StripExtension +============ +*/ +void Com_StripExtension( const char *in, char *out ) { + while ( *in && *in != '.' ) { + *out++ = *in++; + } + *out = 0; +} + + +/* +================== +Com_DefaultExtension +================== +*/ +void Com_DefaultExtension( char *path, int maxSize, const char *extension ) { + char oldPath[MAX_QPATH]; + char *src; + +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen( path ) - 1; + + while ( *src != '/' && src != path ) { + if ( *src == '.' ) { + return; // it has an extension + } + src--; + } + + Q_strncpyz( oldPath, path, sizeof( oldPath ) ); + Com_sprintf( path, maxSize, "%s%s", oldPath, extension ); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +static short ( *_BigShort )( short l ); +static short ( *_LittleShort )( short l ); +static int ( *_BigLong )( int l ); +static int ( *_LittleLong )( int l ); +static float ( *_BigFloat )( float l ); +static float ( *_LittleFloat )( float l ); + +short BigShort( short l ) {return _BigShort( l );} +short LittleShort( short l ) {return _LittleShort( l );} +int BigLong( int l ) {return _BigLong( l );} +int LittleLong( int l ) {return _LittleLong( l );} +float BigFloat( float l ) {return _BigFloat( l );} +float LittleFloat( float l ) {return _LittleFloat( 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; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init( void ) { + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1 ) { + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } else + { + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + +/* +=============== +Com_ParseInfos +=============== +*/ +int Com_ParseInfos( const char *buf, int max, char infos[][MAX_INFO_STRING] ) { + const char *token; + int count; + char key[MAX_TOKEN_CHARS]; + + count = 0; + + while ( 1 ) { + token = Com_Parse( &buf ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + infos[count][0] = 0; + while ( 1 ) { + token = Com_Parse( &buf ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = Com_ParseOnLine( &buf ); + if ( !token[0] ) { + token = ""; + } + Info_SetValueForKey( infos[count], key, token ); + } + count++; + } + + return count; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +int Q_isprint( int c ) { + if ( c >= 0x20 && c <= 0x7E ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_islower( int c ) { + if ( c >= 'a' && c <= 'z' ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_isupper( int c ) { + if ( c >= 'A' && c <= 'Z' ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_isalpha( int c ) { + if ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) ) { + return ( 1 ); + } + return ( 0 ); +} + +char* Q_strrchr( const char* string, int c ) { + char cc = c; + char *s; + char *sp = (char *)0; + + s = (char*)string; + + while ( *s ) + { + if ( *s == cc ) { + sp = s; + } + s++; + } + if ( cc == 0 ) { + sp = s; + } + + return sp; +} + +/* +============= +Q_strncpyz + +Safe strncpy that ensures a trailing zero +============= +*/ +void Q_strncpyz( char *dest, const char *src, int destsize ) { + if ( !src ) { + Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" ); + } + if ( destsize < 1 ) { + Com_Error( ERR_FATAL,"Q_strncpyz: destsize < 1" ); + } + + strncpy( dest, src, destsize - 1 ); + dest[destsize - 1] = 0; +} + +int Q_stricmpn( const char *s1, const char *s2, int n ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + if ( c1 != c2 ) { + return c1 < c2 ? -1 : 1; + } + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_strncmp( const char *s1, const char *s2, int n ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + if ( c1 != c2 ) { + return c1 < c2 ? -1 : 1; + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_stricmp( const char *s1, const char *s2 ) { + return Q_stricmpn( s1, s2, 99999 ); +} + + +char *Q_strlwr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = tolower( *s ); + s++; + } + return s1; +} + +char *Q_strupr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = toupper( *s ); + s++; + } + return s1; +} + + +// never goes past bounds or leaves without a terminating 0 +void Q_strcat( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + Com_Error( ERR_FATAL, "Q_strcat: already overflowed" ); + } + Q_strncpyz( dest + l1, src, size - l1 ); +} + + +int Q_PrintStrlen( const char *string ) { + int len; + const char *p; + + if ( !string ) { + return 0; + } + + len = 0; + p = string; + while ( *p ) { + if ( Q_IsColorString( p ) ) { + p += 2; + continue; + } + p++; + len++; + } + + return len; +} + + +char *Q_CleanStr( char *string ) { + char* d; + char* s; + int c; + + s = string; + d = string; + while ( ( c = *s ) != 0 ) { + if ( Q_IsColorString( s ) ) { + s++; + } else if ( c >= 0x20 && c <= 0x7E ) { + *d++ = c; + } + s++; + } + *d = '\0'; + + return string; +} + +void QDECL Com_sprintf( char *dest, int size, const char *fmt, ... ) { + int len; + va_list argptr; + char bigbuffer[32000]; // big, but small enough to fit in PPC stack + + va_start( argptr,fmt ); + len = vsprintf( bigbuffer,fmt,argptr ); + va_end( argptr ); + if ( len >= (int)sizeof( bigbuffer ) ) { + Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" ); + } + if ( len >= size ) { + Com_Printf( "Com_sprintf: overflow of %i in %i\n", len, size ); + } + Q_strncpyz( dest, bigbuffer, size ); +} + + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +char * QDECL va( char *format, ... ) { + va_list argptr; + static char string[2][32000]; // in case va is called by nested functions + static int index = 0; + char *buf; + + buf = string[index & 1]; + index++; + + va_start( argptr, format ); + vsprintf( buf, format,argptr ); + va_end( argptr ); + + return buf; +} + + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +FIXME: overflow check? +=============== +*/ +char *Info_ValueForKey( const char *s, const char *key ) { + char pkey[MAX_INFO_KEY]; + static char value[2][MAX_INFO_VALUE]; // use two buffers so compares + // work without stomping on each other + static int valueindex = 0; + char *o; + + if ( !s || !key ) { + return ""; + } + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" ); + } + + valueindex ^= 1; + if ( *s == '\\' ) { + s++; + } + while ( 1 ) + { + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) { + return ""; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while ( *s != '\\' && *s ) + { + *o++ = *s++; + } + *o = 0; + + if ( !Q_stricmp( key, pkey ) ) { + return value[valueindex]; + } + + if ( !*s ) { + break; + } + s++; + } + + return ""; +} + + +/* +=================== +Info_NextPair + +Used to itterate through all the key/value pairs in an info string +=================== +*/ +void Info_NextPair( const char *( *head ), char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ) { + char *o; + const char *s; + + s = *head; + + if ( *s == '\\' ) { + s++; + } + key[0] = 0; + value[0] = 0; + + o = key; + while ( *s != '\\' ) { + if ( !*s ) { + *o = 0; + *head = s; + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) { + *o++ = *s++; + } + *o = 0; + + *head = s; +} + + +/* +=================== +Info_RemoveKey +=================== +*/ +void Info_RemoveKey( char *s, const char *key ) { + char *start; + char pkey[MAX_INFO_KEY]; + char value[MAX_INFO_VALUE]; + char *o; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); + } + + if ( strchr( key, '\\' ) ) { + return; + } + + while ( 1 ) + { + start = s; + if ( *s == '\\' ) { + s++; + } + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) { + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) + { + if ( !*s ) { + return; + } + *o++ = *s++; + } + *o = 0; + + if ( !strcmp( key, pkey ) ) { + strcpy( start, s ); // remove this part + return; + } + + if ( !*s ) { + return; + } + } + +} + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate( const char *s ) { + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* +================== +Info_SetValueForKey + +Changes or adds a key/value pair +================== +*/ +void Info_SetValueForKey( char *s, const char *key, const char *value ) { + char newi[MAX_INFO_STRING]; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + if ( strchr( key, '\\' ) || strchr( value, '\\' ) ) { + Com_Printf( "Can't use keys or values with a \\\n" ); + return; + } + + if ( strchr( key, ';' ) || strchr( value, ';' ) ) { + Com_Printf( "Can't use keys or values with a semicolon\n" ); + return; + } + + if ( strchr( key, '\"' ) || strchr( value, '\"' ) ) { + Com_Printf( "Can't use keys or values with a \"\n" ); + return; + } + + Info_RemoveKey( s, key ); + if ( !value || !strlen( value ) ) { + return; + } + + Com_sprintf( newi, sizeof( newi ), "\\%s\\%s", key, value ); + + if ( strlen( newi ) + strlen( s ) > MAX_INFO_STRING ) { + Com_Printf( "Info string length exceeded\n" ); + return; + } + + strcat( s, newi ); +} + +//==================================================================== + + +/* +=============== +ParseHex +=============== +*/ +int ParseHex( const char *text ) { + int value; + int c; + + value = 0; + while ( ( c = *text++ ) != 0 ) { + if ( c >= '0' && c <= '9' ) { + value = value * 16 + c - '0'; + continue; + } + if ( c >= 'a' && c <= 'f' ) { + value = value * 16 + 10 + c - 'a'; + continue; + } + if ( c >= 'A' && c <= 'F' ) { + value = value * 16 + 10 + c - 'A'; + continue; + } + } + + return value; +} diff --git a/src/splines/q_splineshared.h b/src/splines/q_splineshared.h new file mode 100644 index 0000000..7c73967 --- /dev/null +++ b/src/splines/q_splineshared.h @@ -0,0 +1,806 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __Q_SHARED_H +#define __Q_SHARED_H + +// q_splineshared.h -- included first by ALL program modules. +// these are the definitions that have no dependance on +// central system services, and can be used by any part +// of the program without any state issues. + +// A user mod should never modify this file + +#define Q3_VERSION "DOOM 0.01" + +// alignment macros for SIMD +#define ALIGN_ON +#define ALIGN_OFF + +#ifdef _WIN32 + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4201) +#pragma warning(disable : 4214) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable : 4514) +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WIN32 // mac doesn't have malloc.h +#include // for _alloca() +#endif +#ifdef _WIN32 + +//#pragma intrinsic( memset, memcpy ) + +#endif + + +// this is the define for determining if we have an asm version of a C function +#if ( defined _M_IX86 || defined __i386__ ) && !defined __sun__ && !defined __LCC__ +#define id386 1 +#else +#define id386 0 +#endif + +// for windows fastcall option + +#define QDECL + +//======================= WIN32 DEFINES ================================= + +#ifdef WIN32 + +#define MAC_STATIC + +#undef QDECL +#define QDECL __cdecl + +// buildstring will be incorporated into the version string +#ifdef NDEBUG +#ifdef _M_IX86 +#define CPUSTRING "win-x86" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP" +#endif +#else +#ifdef _M_IX86 +#define CPUSTRING "win-x86-debug" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP-debug" +#endif +#endif + + +#define PATH_SEP '\\' + +#endif + +//======================= MAC OS X SERVER DEFINES ===================== + +#if defined( __MACH__ ) && defined( __APPLE__ ) + +#define MAC_STATIC + +#ifdef __ppc__ +#define CPUSTRING "MacOSXS-ppc" +#elif defined __i386__ +#define CPUSTRING "MacOSXS-i386" +#else +#define CPUSTRING "MacOSXS-other" +#endif + +#define PATH_SEP '/' + +#define GAME_HARD_LINKED +#define CGAME_HARD_LINKED +#define UI_HARD_LINKED +#define _alloca alloca + +#undef ALIGN_ON +#undef ALIGN_OFF +#define ALIGN_ON # pragma align( 16 ) +#define ALIGN_OFF # pragma align() + +#ifdef __cplusplus +extern "C" { +#endif + +void *osxAllocateMemory( long size ); +void osxFreeMemory( void *pointer ); + +#ifdef __cplusplus +} +#endif + +#endif + +//======================= MAC DEFINES ================================= + +#ifdef __MACOS__ + +//DAJ #define MAC_STATIC static +#define MAC_STATIC + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +void Sys_PumpEvents( void ); + +#endif + +#ifdef __MRC__ + +#define MAC_STATIC + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +void Sys_PumpEvents( void ); + +#undef QDECL +#define QDECL __cdecl + +#define _alloca alloca +#endif + +//======================= LINUX DEFINES ================================= + +// the mac compiler can't handle >32k of locals, so we +// just waste space and make big arrays static... +#ifdef __linux__ + +#define MAC_STATIC + +#ifdef __i386__ +#define CPUSTRING "linux-i386" +#elif defined __axp__ +#define CPUSTRING "linux-alpha" +#else +#define CPUSTRING "linux-other" +#endif + +#define PATH_SEP '/' + +#endif + +//============================================================= + + + +typedef enum {qfalse, qtrue} qboolean; + +typedef unsigned char byte; + +#define EQUAL_EPSILON 0.001 + +typedef int qhandle_t; +typedef int sfxHandle_t; +typedef int fileHandle_t; +typedef int clipHandle_t; + +typedef enum { + INVALID_JOINT = -1 +} jointHandle_t; + +#ifndef NULL +#define NULL ( (void *)0 ) +#endif + +#define MAX_QINT 0x7fffffff +#define MIN_QINT ( -MAX_QINT - 1 ) + +#ifndef max +#define max( x, y ) ( ( ( x ) > ( y ) ) ? ( x ) : ( y ) ) +#define min( x, y ) ( ( ( x ) < ( y ) ) ? ( x ) : ( y ) ) +#endif + +#ifndef sign +#define sign( f ) ( ( f > 0 ) ? 1 : ( ( f < 0 ) ? -1 : 0 ) ) +#endif + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +// the game guarantees that no string from the network will ever +// exceed MAX_STRING_CHARS +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 256 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 1024 // max length of an individual token + +#define MAX_INFO_STRING 1024 +#define MAX_INFO_KEY 1024 +#define MAX_INFO_VALUE 1024 + + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 128 // max length of a filesystem pathname + +#define MAX_NAME_LENGTH 32 // max length of a client name + +// paramters for command buffer stuffing +typedef enum { + EXEC_NOW, // don't return until completed, a VM should NEVER use this, + // because some commands might cause the VM to be unloaded... + EXEC_INSERT, // insert at current position, but don't run yet + EXEC_APPEND // add to end of the command buffer (normal case) +} cbufExec_t; + + +// +// these aren't needed by any of the VMs. put in another header? +// +#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility + +#undef ERR_FATAL // malloc.h on unix + +// parameters to the main Error routine +typedef enum { + ERR_NONE, + ERR_FATAL, // exit the entire game with a popup window + ERR_DROP, // print to console and disconnect from game + ERR_DISCONNECT, // don't kill server + ERR_NEED_CD // pop up the need-cd dialog +} errorParm_t; + + +// font rendering values used by ui and cgame + +#define PROP_GAP_WIDTH 3 +#define PROP_SPACE_WIDTH 8 +#define PROP_HEIGHT 27 +#define PROP_SMALL_SIZE_SCALE 0.75 + +#define BLINK_DIVISOR 200 +#define PULSE_DIVISOR 75 + +#define UI_LEFT 0x00000000 // default +#define UI_CENTER 0x00000001 +#define UI_RIGHT 0x00000002 +#define UI_FORMATMASK 0x00000007 +#define UI_SMALLFONT 0x00000010 +#define UI_BIGFONT 0x00000020 // default +#define UI_GIANTFONT 0x00000040 +#define UI_DROPSHADOW 0x00000800 +#define UI_BLINK 0x00001000 +#define UI_INVERSE 0x00002000 +#define UI_PULSE 0x00004000 + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ +#ifdef __cplusplus // so we can include this in C code +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +#define Q_PI 3.14159265358979323846 +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#include "math_vector.h" +#include "math_angles.h" +#include "math_matrix.h" +#include "math_quaternion.h" + +class idVec3; // for defining vectors +typedef idVec3 &vec3_p; // for passing vectors as function arguments +typedef const idVec3 &vec3_c; // for passing vectors as const function arguments + +class angles_t; // for defining angle vectors +typedef angles_t &angles_p; // for passing angles as function arguments +typedef const angles_t &angles_c; // for passing angles as const function arguments + +class mat3_t; // for defining matrices +typedef mat3_t &mat3_p; // for passing matrices as function arguments +typedef const mat3_t &mat3_c; // for passing matrices as const function arguments + + + +#define NUMVERTEXNORMALS 162 +extern idVec3 bytedirs[NUMVERTEXNORMALS]; + +// all drawing is done to a 640*480 virtual screen size +// and will be automatically scaled to the real resolution +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define TINYCHAR_WIDTH ( SMALLCHAR_WIDTH ) +#define TINYCHAR_HEIGHT ( SMALLCHAR_HEIGHT / 2 ) + +#define SMALLCHAR_WIDTH 8 +#define SMALLCHAR_HEIGHT 16 + +#define BIGCHAR_WIDTH 16 +#define BIGCHAR_HEIGHT 16 + +#define GIANTCHAR_WIDTH 32 +#define GIANTCHAR_HEIGHT 48 + +extern idVec4 colorBlack; +extern idVec4 colorRed; +extern idVec4 colorGreen; +extern idVec4 colorBlue; +extern idVec4 colorYellow; +extern idVec4 colorMagenta; +extern idVec4 colorCyan; +extern idVec4 colorWhite; +extern idVec4 colorLtGrey; +extern idVec4 colorMdGrey; +extern idVec4 colorDkGrey; + +#define Q_COLOR_ESCAPE '^' +#define Q_IsColorString( p ) ( p && *( p ) == Q_COLOR_ESCAPE && *( ( p ) + 1 ) && *( ( p ) + 1 ) != Q_COLOR_ESCAPE ) + +#define COLOR_BLACK '0' +#define COLOR_RED '1' +#define COLOR_GREEN '2' +#define COLOR_YELLOW '3' +#define COLOR_BLUE '4' +#define COLOR_CYAN '5' +#define COLOR_MAGENTA '6' +#define COLOR_WHITE '7' +#define ColorIndex( c ) ( ( ( c ) - '0' ) & 7 ) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" + +extern idVec4 g_color_table[8]; + +#define MAKERGB( v, r, g, b ) v[0] = r; v[1] = g; v[2] = b +#define MAKERGBA( v, r, g, b, a ) v[0] = r; v[1] = g; v[2] = b; v[3] = a + +#define DEG2RAD( a ) ( ( ( a ) * M_PI ) / 180.0F ) +#define RAD2DEG( a ) ( ( ( a ) * 180.0f ) / M_PI ) + +struct cplane_s; + +extern idVec3 vec3_origin; +extern idVec4 vec4_origin; +extern mat3_t axisDefault; + +#define nanmask ( 255 << 23 ) + +#define IS_NAN( x ) ( ( ( *(int *)&x ) & nanmask ) == nanmask ) + +float Q_fabs( float f ); +float Q_rsqrt( float f ); // reciprocal square root + +#define SQRTFAST( x ) ( 1.0f / Q_rsqrt( x ) ) + +signed char ClampChar( int i ); +signed short ClampShort( int i ); + +// this isn't a real cheap function to call! +int DirToByte( const idVec3 &dir ); +void ByteToDir( int b, vec3_p dir ); + +#define DotProduct( a,b ) ( ( a )[0] * ( b )[0] + ( a )[1] * ( b )[1] + ( a )[2] * ( b )[2] ) +#define VectorSubtract( a,b,c ) ( ( c )[0] = ( a )[0] - ( b )[0],( c )[1] = ( a )[1] - ( b )[1],( c )[2] = ( a )[2] - ( b )[2] ) +#define VectorAdd( a,b,c ) ( ( c )[0] = ( a )[0] + ( b )[0],( c )[1] = ( a )[1] + ( b )[1],( c )[2] = ( a )[2] + ( b )[2] ) +#define VectorCopy( a,b ) ( ( b )[0] = ( a )[0],( b )[1] = ( a )[1],( b )[2] = ( a )[2] ) +//#define VectorCopy(a,b) ((b).x=(a).x,(b).y=(a).y,(b).z=(a).z]) + +#define VectorScale( v, s, o ) ( ( o )[0] = ( v )[0] * ( s ),( o )[1] = ( v )[1] * ( s ),( o )[2] = ( v )[2] * ( s ) ) +#define VectorMA( v, s, b, o ) ( ( o )[0] = ( v )[0] + ( b )[0] * ( s ),( o )[1] = ( v )[1] + ( b )[1] * ( s ),( o )[2] = ( v )[2] + ( b )[2] * ( s ) ) +#define CrossProduct( a,b,c ) ( ( c )[0] = ( a )[1] * ( b )[2] - ( a )[2] * ( b )[1],( c )[1] = ( a )[2] * ( b )[0] - ( a )[0] * ( b )[2],( c )[2] = ( a )[0] * ( b )[1] - ( a )[1] * ( b )[0] ) + +#define DotProduct4( x,y ) ( ( x )[0] * ( y )[0] + ( x )[1] * ( y )[1] + ( x )[2] * ( y )[2] + ( x )[3] * ( y )[3] ) +#define VectorSubtract4( a,b,c ) ( ( c )[0] = ( a )[0] - ( b )[0],( c )[1] = ( a )[1] - ( b )[1],( c )[2] = ( a )[2] - ( b )[2],( c )[3] = ( a )[3] - ( b )[3] ) +#define VectorAdd4( a,b,c ) ( ( c )[0] = ( a )[0] + ( b )[0],( c )[1] = ( a )[1] + ( b )[1],( c )[2] = ( a )[2] + ( b )[2],( c )[3] = ( a )[3] + ( b )[3] ) +#define VectorCopy4( a,b ) ( ( b )[0] = ( a )[0],( b )[1] = ( a )[1],( b )[2] = ( a )[2],( b )[3] = ( a )[3] ) +#define VectorScale4( v, s, o ) ( ( o )[0] = ( v )[0] * ( s ),( o )[1] = ( v )[1] * ( s ),( o )[2] = ( v )[2] * ( s ),( o )[3] = ( v )[3] * ( s ) ) +#define VectorMA4( v, s, b, o ) ( ( o )[0] = ( v )[0] + ( b )[0] * ( s ),( o )[1] = ( v )[1] + ( b )[1] * ( s ),( o )[2] = ( v )[2] + ( b )[2] * ( s ),( o )[3] = ( v )[3] + ( b )[3] * ( s ) ) + + +#define VectorClear( a ) ( ( a )[0] = ( a )[1] = ( a )[2] = 0 ) +#define VectorNegate( a,b ) ( ( b )[0] = -( a )[0],( b )[1] = -( a )[1],( b )[2] = -( a )[2] ) +#define VectorSet( v, x, y, z ) ( ( v )[0] = ( x ), ( v )[1] = ( y ), ( v )[2] = ( z ) ) +#define Vector4Copy( a,b ) ( ( b )[0] = ( a )[0],( b )[1] = ( a )[1],( b )[2] = ( a )[2],( b )[3] = ( a )[3] ) + +#define SnapVector( v ) {v[0] = (int)v[0]; v[1] = (int)v[1]; v[2] = (int)v[2];} + +float NormalizeColor( vec3_c in, vec3_p out ); + +int VectorCompare( vec3_c v1, vec3_c v2 ); +float VectorLength( vec3_c v ); +float Distance( vec3_c p1, vec3_c p2 ); +float DistanceSquared( vec3_c p1, vec3_c p2 ); +float VectorNormalize( vec3_p v ); // returns vector length +void VectorNormalizeFast( vec3_p v ); // does NOT return vector length, uses rsqrt approximation +float VectorNormalize2( vec3_c v, vec3_p out ); +void VectorInverse( vec3_p v ); +void VectorRotate( vec3_c in, mat3_c matrix, vec3_p out ); +void VectorPolar( vec3_p v, float radius, float theta, float phi ); +void VectorSnap( vec3_p v ); +void Vector53Copy( const idVec5_t &in, vec3_p out ); +void Vector5Scale( const idVec5_t &v, float scale, idVec5_t &out ); +void Vector5Add( const idVec5_t &va, const idVec5_t &vb, idVec5_t &out ); +void VectorRotate3( vec3_c vIn, vec3_c vRotation, vec3_p out ); +void VectorRotate3Origin( vec3_c vIn, vec3_c vRotation, vec3_c vOrigin, vec3_p out ); + + +int Q_log2( int val ); + +int Q_rand( int *seed ); +float Q_random( int *seed ); +float Q_crandom( int *seed ); + +#define random() ( ( rand() & 0x7fff ) / ( (float)0x7fff ) ) +#define crandom() ( 2.0 * ( random() - 0.5 ) ) + +float Q_rint( float in ); + +void vectoangles( vec3_c value1, angles_p angles ); +void AnglesToAxis( angles_c angles, mat3_p axis ); + +void AxisCopy( mat3_c in, mat3_p out ); +qboolean AxisRotated( mat3_c in ); // assumes a non-degenerate axis + +int SignbitsForNormal( vec3_c normal ); +int BoxOnPlaneSide( const Bounds &b, struct cplane_s *p ); + +float AngleMod( float a ); +float LerpAngle( float from, float to, float frac ); +float AngleSubtract( float a1, float a2 ); +void AnglesSubtract( angles_c v1, angles_c v2, angles_p v3 ); + +float AngleNormalize360( float angle ); +float AngleNormalize180( float angle ); +float AngleDelta( float angle1, float angle2 ); + +qboolean PlaneFromPoints( idVec4 &plane, vec3_c a, vec3_c b, vec3_c c ); +void ProjectPointOnPlane( vec3_p dst, vec3_c p, vec3_c normal ); +void RotatePointAroundVector( vec3_p dst, vec3_c dir, vec3_c point, float degrees ); +void RotateAroundDirection( mat3_p axis, float yaw ); +void MakeNormalVectors( vec3_c forward, vec3_p right, vec3_p up ); +// perpendicular vector could be replaced by this + +int PlaneTypeForNormal( vec3_c normal ); + +void MatrixMultiply( mat3_c in1, mat3_c in2, mat3_p out ); +void MatrixInverseMultiply( mat3_c in1, mat3_c in2, mat3_p out ); // in2 is transposed during multiply +void MatrixTransformVector( vec3_c in, mat3_c matrix, vec3_p out ); +void MatrixProjectVector( vec3_c in, mat3_c matrix, vec3_p out ); // Places the vector into a new coordinate system. +void AngleVectors( angles_c angles, vec3_p forward, vec3_p right, vec3_p up ); +void PerpendicularVector( vec3_p dst, vec3_c src ); + +float TriangleArea( vec3_c a, vec3_c b, vec3_c c ); +#endif // __cplusplus + +//============================================= + +float Com_Clamp( float min, float max, float value ); + +#define FILE_HASH_SIZE 1024 +int Com_HashString( const char *fname ); + +char *Com_SkipPath( char *pathname ); + +// it is ok for out == in +void Com_StripExtension( const char *in, char *out ); + +// "extension" should include the dot: ".map" +void Com_DefaultExtension( char *path, int maxSize, const char *extension ); + +int Com_ParseInfos( const char *buf, int max, char infos[][MAX_INFO_STRING] ); + +/* +===================================================================================== + +SCRIPT PARSING + +===================================================================================== +*/ + +// this just controls the comment printing, it doesn't actually load a file +void Com_BeginParseSession( const char *filename ); +void Com_EndParseSession( void ); + +int Com_GetCurrentParseLine( void ); + +// Will never return NULL, just empty strings. +// An empty string will only be returned at end of file. +// ParseOnLine will return empty if there isn't another token on this line + +// this funny typedef just means a moving pointer into a const char * buffer +const char *Com_Parse( const char *( *data_p ) ); +const char *Com_ParseOnLine( const char *( *data_p ) ); +const char *Com_ParseRestOfLine( const char *( *data_p ) ); + +void Com_UngetToken( void ); + +#ifdef __cplusplus +void Com_MatchToken( const char *( *buf_p ), const char *match, qboolean warning = qfalse ); +#else +void Com_MatchToken( const char *( *buf_p ), const char *match, qboolean warning ); +#endif + +void Com_ScriptError( const char *msg, ... ); +void Com_ScriptWarning( const char *msg, ... ); + +void Com_SkipBracedSection( const char *( *program ) ); +void Com_SkipRestOfLine( const char *( *data ) ); + +float Com_ParseFloat( const char *( *buf_p ) ); +int Com_ParseInt( const char *( *buf_p ) ); + +void Com_Parse1DMatrix( const char *( *buf_p ), int x, float *m ); +void Com_Parse2DMatrix( const char *( *buf_p ), int y, int x, float *m ); +void Com_Parse3DMatrix( const char *( *buf_p ), int z, int y, int x, float *m ); + +//===================================================================================== +#ifdef __cplusplus +extern "C" { +#endif + +void QDECL Com_sprintf( char *dest, int size, const char *fmt, ... ); + + +// mode parm for FS_FOpenFile +typedef enum { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC +} fsMode_t; + +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +//============================================= + +int Q_isprint( int c ); +int Q_islower( int c ); +int Q_isupper( int c ); +int Q_isalpha( int c ); + +// portable case insensitive compare +int Q_stricmp( const char *s1, const char *s2 ); +int Q_strncmp( const char *s1, const char *s2, int n ); +int Q_stricmpn( const char *s1, const char *s2, int n ); +char *Q_strlwr( char *s1 ); +char *Q_strupr( char *s1 ); +char *Q_strrchr( const char* string, int c ); + +// buffer size safe library replacements +void Q_strncpyz( char *dest, const char *src, int destsize ); +void Q_strcat( char *dest, int size, const char *src ); + +// strlen that discounts Quake color sequences +int Q_PrintStrlen( const char *string ); +// removes color sequences from string +char *Q_CleanStr( char *string ); + +int Com_Filter( const char *filter, const char *name, int casesensitive ); +const char *Com_StringContains( const char *str1, const char *str2, int casesensitive ); + + +//============================================= + +short BigShort( short l ); +short LittleShort( short l ); +int BigLong( int l ); +int LittleLong( int l ); +float BigFloat( float l ); +float LittleFloat( float l ); + +void Swap_Init( void ); +char * QDECL va( char *format, ... ); + +#ifdef __cplusplus +} +#endif + + +//============================================= +#ifdef __cplusplus +// +// mapfile parsing +// +typedef struct ePair_s { + char *key; + char *value; +} ePair_t; + +typedef struct mapSide_s { + char material[MAX_QPATH]; + idVec4 plane; + idVec4 textureVectors[2]; +} mapSide_t; + +typedef struct { + int numSides; + mapSide_t **sides; +} mapBrush_t; + +typedef struct { + idVec3 xyz; + float st[2]; +} patchVertex_t; + +typedef struct { + char material[MAX_QPATH]; + int width, height; + patchVertex_t *patchVerts; +} mapPatch_t; + +typedef struct { + char modelName[MAX_QPATH]; + float matrix[16]; +} mapModel_t; + +typedef struct mapPrimitive_s { + int numEpairs; + ePair_t **ePairs; + + // only one of these will be non-NULL + mapBrush_t *brush; + mapPatch_t *patch; + mapModel_t *model; +} mapPrimitive_t; + +typedef struct mapEntity_s { + int numPrimitives; + mapPrimitive_t **primitives; + + int numEpairs; + ePair_t **ePairs; +} mapEntity_t; + +typedef struct { + int numEntities; + mapEntity_t **entities; +} mapFile_t; + + +// the order of entities, brushes, and sides will be maintained, the +// lists won't be swapped on each load or save +mapFile_t *ParseMapFile( const char *text ); +void FreeMapFile( mapFile_t *mapFile ); +void WriteMapFile( const mapFile_t *mapFile, FILE *f ); + +// key names are case-insensitive +const char *ValueForMapEntityKey( const mapEntity_t *ent, const char *key ); +float FloatForMapEntityKey( const mapEntity_t *ent, const char *key ); +qboolean GetVectorForMapEntityKey( const mapEntity_t *ent, const char *key, idVec3 &vec ); + +typedef struct { + idVec3 xyz; + idVec2 st; + idVec3 normal; + idVec3 tangents[2]; + byte smoothing[4]; // colors for silhouette smoothing +} drawVert_t; + +typedef struct { + int width, height; + drawVert_t *verts; +} drawVertMesh_t; + +// Tesselate a map patch into smoothed, drawable vertexes +// MaxError of around 4 is reasonable +drawVertMesh_t *SubdivideMapPatch( const mapPatch_t *patch, float maxError ); +#endif // __cplusplus + +//========================================= + +#ifdef __cplusplus +extern "C" { +#endif + +void QDECL Com_Error( int level, const char *error, ... ); +void QDECL Com_Printf( const char *msg, ... ); +void QDECL Com_DPrintf( const char *msg, ... ); + +#ifdef __cplusplus +} +#endif + + +typedef struct { + qboolean frameMemory; + int currentElements; + int maxElements; // will reallocate and move when exceeded + void **elements; +} growList_t; + +// you don't need to init the growlist if you don't mind it growing and moving +// the list as it expands +void Com_InitGrowList( growList_t *list, int maxElements ); +int Com_AddToGrowList( growList_t *list, void *data ); +void *Com_GrowListElement( const growList_t *list, int index ); +int Com_IndexForGrowListElement( const growList_t *list, const void *element ); + + +// +// key / value info strings +// +char *Info_ValueForKey( const char *s, const char *key ); +void Info_RemoveKey( char *s, const char *key ); +void Info_SetValueForKey( char *s, const char *key, const char *value ); +qboolean Info_Validate( const char *s ); +void Info_NextPair( const char *( *s ), char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ); + +// get cvar defs, collision defs, etc +//#include "../shared/interface.h" + +// get key code numbers for events +//#include "../shared/keycodes.h" + +#ifdef __cplusplus +// get the polygon winding functions +//#include "../shared/windings.h" + +// get the flags class +//#include "../shared/idflags.h" +#endif // __cplusplus + +#endif // __Q_SHARED_H + diff --git a/src/splines/splines.cpp b/src/splines/splines.cpp new file mode 100644 index 0000000..fb8dc10 --- /dev/null +++ b/src/splines/splines.cpp @@ -0,0 +1,1395 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +//#include "stdafx.h" +//#include "qe3.h" + +#include "q_splineshared.h" //DAJ was q_shared.h conflicted with qcommon +#include "splines.h" + +extern "C" { +int FS_Write( const void *buffer, int len, fileHandle_t h ); +int FS_ReadFile( const char *qpath, void **buffer ); +void FS_FreeFile( void *buffer ); +fileHandle_t FS_FOpenFileWrite( const char *filename ); +void FS_FCloseFile( fileHandle_t f ); +void Cbuf_AddText( const char *text ); +void Cbuf_Execute( void ); +} + +float Q_fabs( float f ) { + int tmp = *( int * ) &f; + tmp &= 0x7FFFFFFF; + return *( float * ) &tmp; +} + +// (SA) making a list of cameras so I can use +// the splines as targets for other things. +// Certainly better ways to do this, but this lets +// me get underway quickly with ents that need spline +// targets. +#define MAX_CAMERAS 64 + +idCameraDef camera[MAX_CAMERAS]; + +extern "C" { +qboolean loadCamera( int camNum, const char *name ) { + if ( camNum < 0 || camNum >= MAX_CAMERAS ) { + return qfalse; + } + camera[camNum].clear(); + // TTimo static_cast confused gcc, went for C-style casting + return (qboolean)( camera[camNum].load( name ) ); +} + +qboolean getCameraInfo( int camNum, int time, float *origin, float *angles, float *fov ) { + idVec3 dir, org; + if ( camNum < 0 || camNum >= MAX_CAMERAS ) { + return qfalse; + } + org[0] = origin[0]; + org[1] = origin[1]; + org[2] = origin[2]; + if ( camera[camNum].getCameraInfo( time, org, dir, fov ) ) { + origin[0] = org[0]; + origin[1] = org[1]; + origin[2] = org[2]; + angles[1] = atan2( dir[1], dir[0] ) * 180 / 3.14159; + angles[0] = asin( dir[2] ) * 180 / 3.14159; + return qtrue; + } + return qfalse; +} + +void startCamera( int camNum, int time ) { + if ( camNum < 0 || camNum >= MAX_CAMERAS ) { + return; + } + camera[camNum].startCamera( time ); +} + +} + + +//#include "../shared/windings.h" +//#include "../qcommon/qcommon.h" +//#include "../sys/sys_public.h" +//#include "../game/game_entity.h" + +idCameraDef splineList; +idCameraDef *g_splineList = &splineList; + +idVec3 idSplineList::zero( 0,0,0 ); + +void glLabeledPoint( idVec3 &color, idVec3 &point, float size, const char *label ) { + qglColor3fv( color ); + qglPointSize( size ); + qglBegin( GL_POINTS ); + qglVertex3fv( point ); + qglEnd(); + idVec3 v = point; + v.x += 1; + v.y += 1; + v.z += 1; + qglRasterPos3fv( v ); + qglCallLists( strlen( label ), GL_UNSIGNED_BYTE, label ); +} + + +void glBox( idVec3 &color, idVec3 &point, float size ) { + idVec3 mins( point ); + idVec3 maxs( point ); + mins[0] -= size; + mins[1] += size; + mins[2] -= size; + maxs[0] += size; + maxs[1] -= size; + maxs[2] += size; + qglColor3fv( color ); + qglBegin( GL_LINE_LOOP ); + qglVertex3f( mins[0],mins[1],mins[2] ); + qglVertex3f( maxs[0],mins[1],mins[2] ); + qglVertex3f( maxs[0],maxs[1],mins[2] ); + qglVertex3f( mins[0],maxs[1],mins[2] ); + qglEnd(); + qglBegin( GL_LINE_LOOP ); + qglVertex3f( mins[0],mins[1],maxs[2] ); + qglVertex3f( maxs[0],mins[1],maxs[2] ); + qglVertex3f( maxs[0],maxs[1],maxs[2] ); + qglVertex3f( mins[0],maxs[1],maxs[2] ); + qglEnd(); + + qglBegin( GL_LINES ); + qglVertex3f( mins[0],mins[1],mins[2] ); + qglVertex3f( mins[0],mins[1],maxs[2] ); + qglVertex3f( mins[0],maxs[1],maxs[2] ); + qglVertex3f( mins[0],maxs[1],mins[2] ); + qglVertex3f( maxs[0],mins[1],mins[2] ); + qglVertex3f( maxs[0],mins[1],maxs[2] ); + qglVertex3f( maxs[0],maxs[1],maxs[2] ); + qglVertex3f( maxs[0],maxs[1],mins[2] ); + qglEnd(); + +} + +void splineTest() { + //g_splineList->load("p:/doom/base/maps/test_base1.camera"); +} + +void splineDraw() { + //g_splineList->addToRenderer(); +} + + +//extern void D_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end ); + +void debugLine( idVec3 &color, float x, float y, float z, float x2, float y2, float z2 ) { +// idVec3 from(x, y, z); +// idVec3 to(x2, y2, z2); + //D_DebugLine(color, from, to); +} + +void idSplineList::addToRenderer() { + + if ( controlPoints.Num() == 0 ) { + return; + } + + idVec3 mins, maxs; + idVec3 yellow( 1.0, 1.0, 0 ); + idVec3 white( 1.0, 1.0, 1.0 ); + int i; + + for ( i = 0; i < controlPoints.Num(); i++ ) { + VectorCopy( *controlPoints[i], mins ); + VectorCopy( mins, maxs ); + mins[0] -= 8; + mins[1] += 8; + mins[2] -= 8; + maxs[0] += 8; + maxs[1] -= 8; + maxs[2] += 8; + debugLine( yellow, mins[0], mins[1], mins[2], maxs[0], mins[1], mins[2] ); + debugLine( yellow, maxs[0], mins[1], mins[2], maxs[0], maxs[1], mins[2] ); + debugLine( yellow, maxs[0], maxs[1], mins[2], mins[0], maxs[1], mins[2] ); + debugLine( yellow, mins[0], maxs[1], mins[2], mins[0], mins[1], mins[2] ); + + debugLine( yellow, mins[0], mins[1], maxs[2], maxs[0], mins[1], maxs[2] ); + debugLine( yellow, maxs[0], mins[1], maxs[2], maxs[0], maxs[1], maxs[2] ); + debugLine( yellow, maxs[0], maxs[1], maxs[2], mins[0], maxs[1], maxs[2] ); + debugLine( yellow, mins[0], maxs[1], maxs[2], mins[0], mins[1], maxs[2] ); + + } + + int step = 0; + idVec3 step1; + for ( i = 3; i < controlPoints.Num(); i++ ) { + for ( float tension = 0.0f; tension < 1.001f; tension += 0.1f ) { + float x = 0; + float y = 0; + float z = 0; + for ( int j = 0; j < 4; j++ ) { + x += controlPoints[i - ( 3 - j )]->x * calcSpline( j, tension ); + y += controlPoints[i - ( 3 - j )]->y * calcSpline( j, tension ); + z += controlPoints[i - ( 3 - j )]->z * calcSpline( j, tension ); + } + if ( step == 0 ) { + step1[0] = x; + step1[1] = y; + step1[2] = z; + step = 1; + } else { + debugLine( white, step1[0], step1[1], step1[2], x, y, z ); + step = 0; + } + + } + } +} + +void idSplineList::buildSpline() { + //int start = Sys_Milliseconds(); + clearSpline(); + for ( int i = 3; i < controlPoints.Num(); i++ ) { + for ( float tension = 0.0f; tension < 1.001f; tension += granularity ) { + float x = 0; + float y = 0; + float z = 0; + for ( int j = 0; j < 4; j++ ) { + x += controlPoints[i - ( 3 - j )]->x * calcSpline( j, tension ); + y += controlPoints[i - ( 3 - j )]->y * calcSpline( j, tension ); + z += controlPoints[i - ( 3 - j )]->z * calcSpline( j, tension ); + } + splinePoints.Append( new idVec3( x, y, z ) ); + } + } + dirty = false; + //Com_Printf("Spline build took %f seconds\n", (float)(Sys_Milliseconds() - start) / 1000); +} + + +void idSplineList::draw( bool editMode ) { + int i; + idVec4 yellow( 1, 1, 0, 1 ); + + if ( controlPoints.Num() == 0 ) { + return; + } + + if ( dirty ) { + buildSpline(); + } + + + qglColor3fv( controlColor ); + qglPointSize( 5 ); + + qglBegin( GL_POINTS ); + for ( i = 0; i < controlPoints.Num(); i++ ) { + qglVertex3fv( *controlPoints[i] ); + } + qglEnd(); + + if ( editMode ) { + for ( i = 0; i < controlPoints.Num(); i++ ) { + glBox( activeColor, *controlPoints[i], 4 ); + } + } + + //Draw the curve + qglColor3fv( pathColor ); + qglBegin( GL_LINE_STRIP ); + int count = splinePoints.Num(); + for ( i = 0; i < count; i++ ) { + qglVertex3fv( *splinePoints[i] ); + } + qglEnd(); + + if ( editMode ) { + qglColor3fv( segmentColor ); + qglPointSize( 3 ); + qglBegin( GL_POINTS ); + for ( i = 0; i < count; i++ ) { + qglVertex3fv( *splinePoints[i] ); + } + qglEnd(); + } + if ( count > 0 ) { + //assert(activeSegment >=0 && activeSegment < count); + if ( activeSegment >= 0 && activeSegment < count ) { + glBox( activeColor, *splinePoints[activeSegment], 6 ); + glBox( yellow, *splinePoints[activeSegment], 8 ); + } + } + +} + +float idSplineList::totalDistance() { + + // FIXME: save dist and return + // + if ( controlPoints.Num() == 0 ) { + return 0.0; + } + + if ( dirty ) { + buildSpline(); + } + + float dist = 0.0; + idVec3 temp; + int count = splinePoints.Num(); + for ( int i = 1; i < count; i++ ) { + temp = *splinePoints[i - 1]; + temp -= *splinePoints[i]; + dist += temp.Length(); + } + return dist; +} + +void idSplineList::initPosition( long bt, long totalTime ) { + + if ( dirty ) { + buildSpline(); + } + + if ( splinePoints.Num() == 0 ) { + return; + } + + baseTime = bt; + time = totalTime; + + // calc distance to travel ( this will soon be broken into time segments ) + splineTime.Clear(); + splineTime.Append( bt ); + double dist = totalDistance(); + double distSoFar = 0.0; + idVec3 temp; + int count = splinePoints.Num(); + //for(int i = 2; i < count - 1; i++) { + for ( int i = 1; i < count; i++ ) { + temp = *splinePoints[i - 1]; + temp -= *splinePoints[i]; + distSoFar += temp.Length(); + double percent = distSoFar / dist; + percent *= totalTime; + splineTime.Append( percent + bt ); + } + assert( splineTime.Num() == splinePoints.Num() ); + activeSegment = 0; +} + + + +float idSplineList::calcSpline( int step, float tension ) { + switch ( step ) { + case 0: return ( pow( 1 - tension, 3 ) ) / 6; + case 1: return ( 3 * pow( tension, 3 ) - 6 * pow( tension, 2 ) + 4 ) / 6; + case 2: return ( -3 * pow( tension, 3 ) + 3 * pow( tension, 2 ) + 3 * tension + 1 ) / 6; + case 3: return pow( tension, 3 ) / 6; + } + return 0.0; +} + + + +void idSplineList::updateSelection( const idVec3 &move ) { + if ( selected ) { + dirty = true; + VectorAdd( *selected, move, *selected ); + } +} + + +void idSplineList::setSelectedPoint( idVec3 *p ) { + if ( p ) { + p->Snap(); + for ( int i = 0; i < controlPoints.Num(); i++ ) { + if ( *p == *controlPoints[i] ) { + selected = controlPoints[i]; + } + } + } else { + selected = NULL; + } +} + +const idVec3 *idSplineList::getPosition( long t ) { + static idVec3 interpolatedPos; +// static long lastTime = -1; + + int count = splineTime.Num(); + if ( count == 0 ) { + return &zero; + } + +// Com_Printf("Time: %d\n", t); + assert( splineTime.Num() == splinePoints.Num() ); + + while ( activeSegment < count ) { + if ( splineTime[activeSegment] >= t ) { + if ( activeSegment > 0 && activeSegment < count - 1 ) { + double timeHi = splineTime[activeSegment + 1]; + double timeLo = splineTime[activeSegment - 1]; + double percent = ( timeHi - t ) / ( timeHi - timeLo ); + // pick two bounding points + idVec3 v1 = *splinePoints[activeSegment - 1]; + idVec3 v2 = *splinePoints[activeSegment + 1]; + v2 *= ( 1.0 - percent ); + v1 *= percent; + v2 += v1; + interpolatedPos = v2; + return &interpolatedPos; + } + return splinePoints[activeSegment]; + } else { + activeSegment++; + } + } + return splinePoints[count - 1]; +} + +void idSplineList::parse( const char *( *text ) ) { + const char *token; + //Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !Q_stricmp( token, "}" ) ) { + break; + } + + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !Q_stricmp( token, "(" ) || !Q_stricmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "granularity" ) == 0 ) { + granularity = atof( token ); + } else if ( Q_stricmp( key.c_str(), "name" ) == 0 ) { + name = token; + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !Q_stricmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + // read the control point + idVec3 point; + Com_Parse1DMatrix( text, 3, point ); + addPoint( point.x, point.y, point.z ); + } while ( 1 ); + + //Com_UngetToken(); + //Com_MatchToken( text, "}" ); + dirty = true; +} + +void idSplineList::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + //s = va("\t\tname %s\n", name.c_str()); + //FS_Write(s.c_str(), s.length(), file); + s = va( "\t\t\tgranularity %f\n", granularity ); + FS_Write( s.c_str(), s.length(), file ); + int count = controlPoints.Num(); + for ( int i = 0; i < count; i++ ) { + s = va( "\t\t\t( %f %f %f )\n", controlPoints[i]->x, controlPoints[i]->y, controlPoints[i]->z ); + FS_Write( s.c_str(), s.length(), file ); + } + s = "\t\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + + +void idCameraDef::getActiveSegmentInfo( int segment, idVec3 &origin, idVec3 &direction, float *fov ) { +#if 0 + if ( !cameraSpline.validTime() ) { + buildCamera(); + } + double d = (double)segment / numSegments(); + getCameraInfo( d * totalTime * 1000, origin, direction, fov ); +#endif +/* + if (!cameraSpline.validTime()) { + buildCamera(); + } + origin = *cameraSpline.getSegmentPoint(segment); + + + idVec3 temp; + + int numTargets = getTargetSpline()->controlPoints.Num(); + int count = cameraSpline.splineTime.Num(); + if (numTargets == 0) { + // follow the path + if (cameraSpline.getActiveSegment() < count - 1) { + temp = *cameraSpline.splinePoints[cameraSpline.getActiveSegment()+1]; + } + } else if (numTargets == 1) { + temp = *getTargetSpline()->controlPoints[0]; + } else { + temp = *getTargetSpline()->getSegmentPoint(segment); + } + + temp -= origin; + temp.Normalize(); + direction = temp; +*/ +} + +bool idCameraDef::getCameraInfo( long time, idVec3 &origin, idVec3 &direction, float *fv ) { + + char buff[1024]; + + if ( ( time - startTime ) / 1000 > totalTime ) { + return false; + } + + + for ( int i = 0; i < events.Num(); i++ ) { + if ( time >= startTime + events[i]->getTime() && !events[i]->getTriggered() ) { + events[i]->setTriggered( true ); + if ( events[i]->getType() == idCameraEvent::EVENT_TARGET ) { + setActiveTargetByName( events[i]->getParam() ); + getActiveTarget()->start( startTime + events[i]->getTime() ); + //Com_Printf("Triggered event switch to target: %s\n",events[i]->getParam()); + } else if ( events[i]->getType() == idCameraEvent::EVENT_TRIGGER ) { + //idEntity *ent = NULL; + //ent = level.FindTarget( ent, events[i]->getParam()); + //if (ent) { + // ent->signal( SIG_TRIGGER ); + // ent->ProcessEvent( &EV_Activate, world ); + //} + } else if ( events[i]->getType() == idCameraEvent::EVENT_FOV ) { + memset( buff, 0, sizeof( buff ) ); + strcpy( buff, events[i]->getParam() ); + const char *param1 = strtok( buff, " \t,\0" ); + const char *param2 = strtok( NULL, " \t,\0" ); + float len = ( param2 ) ? atof( param2 ) : 0; + float newfov = ( param1 ) ? atof( param1 ) : 90; + fov.reset( fov.getFOV( time ), newfov, time, len ); + //*fv = fov = atof(events[i]->getParam()); + } else if ( events[i]->getType() == idCameraEvent::EVENT_FADEIN ) { + float time = atof( events[i]->getParam() ); + Cbuf_AddText( va( "fade 0 0 0 0 %f", time ) ); + Cbuf_Execute(); + } else if ( events[i]->getType() == idCameraEvent::EVENT_FADEOUT ) { + float time = atof( events[i]->getParam() ); + Cbuf_AddText( va( "fade 0 0 0 255 %f", time ) ); + Cbuf_Execute(); + } else if ( events[i]->getType() == idCameraEvent::EVENT_CAMERA ) { + memset( buff, 0, sizeof( buff ) ); + strcpy( buff, events[i]->getParam() ); + const char *param1 = strtok( buff, " \t,\0" ); + const char *param2 = strtok( NULL, " \t,\0" ); + + if ( param2 ) { + loadCamera( atoi( param1 ), va( "cameras/%s.camera", param2 ) ); + startCamera( time ); + } else { + loadCamera( 0, va( "cameras/%s.camera", events[i]->getParam() ) ); + startCamera( time ); + } + return true; + } else if ( events[i]->getType() == idCameraEvent::EVENT_STOP ) { + return false; + } + } + } + + origin = *cameraPosition->getPosition( time ); + + *fv = fov.getFOV( time ); + + idVec3 temp = origin; + + int numTargets = targetPositions.Num(); + if ( numTargets == 0 ) { +/* + // follow the path + if (cameraSpline.getActiveSegment() < count - 1) { + temp = *cameraSpline.splinePoints[cameraSpline.getActiveSegment()+1]; + if (temp == origin) { + int index = cameraSpline.getActiveSegment() + 2; + while (temp == origin && index < count - 1) { + temp = *cameraSpline.splinePoints[index++]; + } + } + } +*/ + } else { + temp = *getActiveTarget()->getPosition( time ); + } + + temp -= origin; + temp.Normalize(); + direction = temp; + + return true; +} + +bool idCameraDef::waitEvent( int index ) { + //for (int i = 0; i < events.Num(); i++) { + // if (events[i]->getSegment() == index && events[i]->getType() == idCameraEvent::EVENT_WAIT) { + // return true; + // } + //} + return false; +} + + +#define NUM_CCELERATION_SEGS 10 +#define CELL_AMT 5 + +void idCameraDef::buildCamera() { + int i; +// int lastSwitch = 0; + idList waits; + idList targets; + + totalTime = baseTime; + cameraPosition->setTime( totalTime * 1000 ); + // we have a base time layout for the path and the target path + // now we need to layer on any wait or speed changes + for ( i = 0; i < events.Num(); i++ ) { +// idCameraEvent *ev = events[i]; + events[i]->setTriggered( false ); + switch ( events[i]->getType() ) { + case idCameraEvent::EVENT_TARGET: { + targets.Append( i ); + break; + } + case idCameraEvent::EVENT_WAIT: { + waits.Append( atof( events[i]->getParam() ) ); + + //FIXME: this is quite hacky for Wolf E3, accel and decel needs + // do be parameter based etc.. + long startTime = events[i]->getTime() - 1000; + if ( startTime < 0 ) { + startTime = 0; + } + float speed = cameraPosition->getBaseVelocity(); + long loopTime = 10; + float steps = speed / ( ( events[i]->getTime() - startTime ) / loopTime ); + while ( startTime <= events[i]->getTime() - loopTime ) { + cameraPosition->addVelocity( startTime, loopTime, speed ); + speed -= steps; + startTime += loopTime; + } + cameraPosition->addVelocity( events[i]->getTime(), atof( events[i]->getParam() ) * 1000, 0 ); + + startTime = ( long int )( events[i]->getTime() + atof( events[i]->getParam() ) * 1000 ); + long endTime = startTime + 1000; + speed = 0; + while ( startTime <= endTime ) { + cameraPosition->addVelocity( startTime, loopTime, speed ); + speed += steps; + startTime += loopTime; + } + break; + } + case idCameraEvent::EVENT_TARGETWAIT: { + //targetWaits.Append(i); + break; + } + case idCameraEvent::EVENT_SPEED: { +/* + // take the average delay between up to the next five segments + float adjust = atof(events[i]->getParam()); + int index = events[i]->getSegment(); + total = 0; + count = 0; + + // get total amount of time over the remainder of the segment + for (j = index; j < cameraSpline.numSegments() - 1; j++) { + total += cameraSpline.getSegmentTime(j + 1) - cameraSpline.getSegmentTime(j); + count++; + } + + // multiply that by the adjustment + double newTotal = total * adjust; + // what is the difference.. + newTotal -= total; + totalTime += newTotal / 1000; + + // per segment difference + newTotal /= count; + int additive = newTotal; + + // now propogate that difference out to each segment + for (j = index; j < cameraSpline.numSegments(); j++) { + cameraSpline.addSegmentTime(j, additive); + additive += newTotal; + } + break; +*/ + default: + break; + } + } + } + + + for ( i = 0; i < waits.Num(); i++ ) { + totalTime += waits[i]; + } + + // on a new target switch, we need to take time to this point ( since last target switch ) + // and allocate it across the active target, then reset time to this point + long timeSoFar = 0; + long total = ( long int )( totalTime * 1000 ); + for ( i = 0; i < targets.Num(); i++ ) { + long t; + if ( i < targets.Num() - 1 ) { + t = events[targets[i + 1]]->getTime(); + } else { + t = total - timeSoFar; + } + // t is how much time to use for this target + setActiveTargetByName( events[targets[i]]->getParam() ); + getActiveTarget()->setTime( t ); + timeSoFar += t; + } + + +} + +void idCameraDef::startCamera( long t ) { + buildCamera(); + cameraPosition->start( t ); + fov.reset( 90, 90, t, 0 ); + //for (int i = 0; i < targetPositions.Num(); i++) { + // targetPositions[i]-> + //} + startTime = t; + cameraRunning = true; +} + + +void idCameraDef::parse( const char *( *text ) ) { + + const char *token; + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !Q_stricmp( token, "}" ) ) { + break; + } + + if ( Q_stricmp( token, "time" ) == 0 ) { + baseTime = Com_ParseFloat( text ); + } else if ( Q_stricmp( token, "camera_fixed" ) == 0 ) { + cameraPosition = new idFixedPosition(); + cameraPosition->parse( text ); + } else if ( Q_stricmp( token, "camera_interpolated" ) == 0 ) { + cameraPosition = new idInterpolatedPosition(); + cameraPosition->parse( text ); + } else if ( Q_stricmp( token, "camera_spline" ) == 0 ) { + cameraPosition = new idSplinePosition(); + cameraPosition->parse( text ); + } else if ( Q_stricmp( token, "target_fixed" ) == 0 ) { + idFixedPosition *pos = new idFixedPosition(); + pos->parse( text ); + targetPositions.Append( pos ); + } else if ( Q_stricmp( token, "target_interpolated" ) == 0 ) { + idInterpolatedPosition *pos = new idInterpolatedPosition(); + pos->parse( text ); + targetPositions.Append( pos ); + } else if ( Q_stricmp( token, "target_spline" ) == 0 ) { + idSplinePosition *pos = new idSplinePosition(); + pos->parse( text ); + targetPositions.Append( pos ); + } else if ( Q_stricmp( token, "fov" ) == 0 ) { + fov.parse( text ); + } else if ( Q_stricmp( token, "event" ) == 0 ) { + idCameraEvent *event = new idCameraEvent(); + event->parse( text ); + addEvent( event ); + } + + + } while ( 1 ); + + if ( !cameraPosition ) { + Com_Printf( "no camera position specified\n" ); + // prevent a crash later on + cameraPosition = new idFixedPosition(); + } + + Com_UngetToken(); + Com_MatchToken( text, "}" ); + +} + +bool idCameraDef::load( const char *filename ) { + char *buf; + const char *buf_p; + FS_ReadFile( filename, (void **)&buf ); + if ( !buf ) { + return false; + } + + clear(); + Com_BeginParseSession( filename ); + buf_p = buf; + parse( &buf_p ); + Com_EndParseSession(); + FS_FreeFile( buf ); + + return true; +} + +void idCameraDef::save( const char *filename ) { + fileHandle_t file = FS_FOpenFileWrite( filename ); + if ( file ) { + int i; + idStr s = "cameraPathDef { \n"; + FS_Write( s.c_str(), s.length(), file ); + s = va( "\ttime %f\n", baseTime ); + FS_Write( s.c_str(), s.length(), file ); + + cameraPosition->write( file, va( "camera_%s",cameraPosition->typeStr() ) ); + + for ( i = 0; i < numTargets(); i++ ) { + targetPositions[i]->write( file, va( "target_%s", targetPositions[i]->typeStr() ) ); + } + + for ( i = 0; i < events.Num(); i++ ) { + events[i]->write( file, "event" ); + } + + fov.write( file, "fov" ); + + s = "}\n"; + FS_Write( s.c_str(), s.length(), file ); + } + FS_FCloseFile( file ); +} + +int idCameraDef::sortEvents( const void *p1, const void *p2 ) { + idCameraEvent *ev1 = ( idCameraEvent* )( p1 ); + idCameraEvent *ev2 = ( idCameraEvent* )( p2 ); + + if ( ev1->getTime() > ev2->getTime() ) { + return -1; + } + if ( ev1->getTime() < ev2->getTime() ) { + return 1; + } + return 0; +} + +void idCameraDef::addEvent( idCameraEvent *event ) { + events.Append( event ); + //events.Sort(&sortEvents); + +} +void idCameraDef::addEvent( idCameraEvent::eventType t, const char *param, long time ) { + addEvent( new idCameraEvent( t, param, time ) ); + buildCamera(); +} + + +const char *idCameraEvent::eventStr[] = { + "NA", + "WAIT", + "TARGETWAIT", + "SPEED", + "TARGET", + "SNAPTARGET", + "FOV", + "CMD", + "TRIGGER", + "STOP", + "CAMERA", + "FADEOUT", + "FADEIN" +}; + +void idCameraEvent::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "type" ) == 0 ) { + type = static_cast( atoi( token ) ); + } else if ( Q_stricmp( key.c_str(), "param" ) == 0 ) { + paramStr = token; + } else if ( Q_stricmp( key.c_str(), "time" ) == 0 ) { + time = atoi( token ); + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +void idCameraEvent::write( fileHandle_t file, const char *name ) { + idStr s = va( "\t%s {\n", name ); + FS_Write( s.c_str(), s.length(), file ); + s = va( "\t\ttype %d\n", static_cast( type ) ); + FS_Write( s.c_str(), s.length(), file ); + s = va( "\t\tparam \"%s\"\n", paramStr.c_str() ); + FS_Write( s.c_str(), s.length(), file ); + s = va( "\t\ttime %d\n", time ); + FS_Write( s.c_str(), s.length(), file ); + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + + +const char *idCameraPosition::positionStr[] = { + "Fixed", + "Interpolated", + "Spline", +}; + + + +const idVec3 *idInterpolatedPosition::getPosition( long t ) { + static idVec3 interpolatedPos; + + float velocity = getVelocity( t ); + float timePassed = t - lastTime; + lastTime = t; + + // convert to seconds + timePassed /= 1000; + + float distToTravel = timePassed * velocity; + + idVec3 temp = startPos; + temp -= endPos; + float distance = temp.Length(); + + distSoFar += distToTravel; + float percent = (float)( distSoFar ) / distance; + + if ( percent > 1.0 ) { + percent = 1.0; + } else if ( percent < 0.0 ) { + percent = 0.0; + } + + // the following line does a straigt calc on percentage of time + // float percent = (float)(startTime + time - t) / time; + + idVec3 v1 = startPos; + idVec3 v2 = endPos; + v1 *= ( 1.0 - percent ); + v2 *= percent; + v1 += v2; + interpolatedPos = v1; + return &interpolatedPos; +} + + +void idCameraFOV::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "fov" ) == 0 ) { + fov = atof( token ); + } else if ( Q_stricmp( key.c_str(), "startFOV" ) == 0 ) { + startFOV = atof( token ); + } else if ( Q_stricmp( key.c_str(), "endFOV" ) == 0 ) { + endFOV = atof( token ); + } else if ( Q_stricmp( key.c_str(), "time" ) == 0 ) { + time = atoi( token ); + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +bool idCameraPosition::parseToken( const char *key, const char *( *text ) ) { + const char *token = Com_Parse( text ); + if ( Q_stricmp( key, "time" ) == 0 ) { + time = atol( token ); + return true; + } else if ( Q_stricmp( key, "type" ) == 0 ) { + type = static_cast( atoi( token ) ); + return true; + } else if ( Q_stricmp( key, "velocity" ) == 0 ) { + long t = atol( token ); + token = Com_Parse( text ); + long d = atol( token ); + token = Com_Parse( text ); + float s = atof( token ); + addVelocity( t, d, s ); + return true; + } else if ( Q_stricmp( key, "baseVelocity" ) == 0 ) { + baseVelocity = atof( token ); + return true; + } else if ( Q_stricmp( key, "name" ) == 0 ) { + name = token; + return true; + } else if ( Q_stricmp( key, "time" ) == 0 ) { + time = atoi( token ); + return true; + } + Com_UngetToken(); + return false; +} + + + +void idFixedPosition::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "pos" ) == 0 ) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, pos ); + } else { + Com_UngetToken(); + idCameraPosition::parseToken( key.c_str(), text ); + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +void idInterpolatedPosition::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "startPos" ) == 0 ) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, startPos ); + } else if ( Q_stricmp( key.c_str(), "endPos" ) == 0 ) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, endPos ); + } else { + Com_UngetToken(); + idCameraPosition::parseToken( key.c_str(), text ); + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + + +void idSplinePosition::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "target" ) == 0 ) { + target.parse( text ); + } else { + Com_UngetToken(); + idCameraPosition::parseToken( key.c_str(), text ); + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + + + +void idCameraFOV::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tfov %f\n", fov ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tstartFOV %f\n", startFOV ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tendFOV %f\n", endFOV ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\ttime %i\n", time ); + FS_Write( s.c_str(), s.length(), file ); + + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + + +void idCameraPosition::write( fileHandle_t file, const char *p ) { + + idStr s = va( "\t\ttime %i\n", time ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\ttype %i\n", static_cast( type ) ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tname %s\n", name.c_str() ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tbaseVelocity %f\n", baseVelocity ); + FS_Write( s.c_str(), s.length(), file ); + + for ( int i = 0; i < velocities.Num(); i++ ) { + s = va( "\t\tvelocity %i %i %f\n", velocities[i]->startTime, velocities[i]->time, velocities[i]->speed ); + FS_Write( s.c_str(), s.length(), file ); + } + +} + +void idFixedPosition::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + idCameraPosition::write( file, p ); + s = va( "\t\tpos ( %f %f %f )\n", pos.x, pos.y, pos.z ); + FS_Write( s.c_str(), s.length(), file ); + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + +void idInterpolatedPosition::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + idCameraPosition::write( file, p ); + s = va( "\t\tstartPos ( %f %f %f )\n", startPos.x, startPos.y, startPos.z ); + FS_Write( s.c_str(), s.length(), file ); + s = va( "\t\tendPos ( %f %f %f )\n", endPos.x, endPos.y, endPos.z ); + FS_Write( s.c_str(), s.length(), file ); + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + +void idSplinePosition::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + idCameraPosition::write( file, p ); + target.write( file, "target" ); + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + +void idCameraDef::addTarget( const char *name, idCameraPosition::positionType type ) { + //const char *text = (name == NULL) ? va("target0%d", numTargets()+1) : name; + idCameraPosition *pos = newFromType( type ); + if ( pos ) { + pos->setName( name ); + targetPositions.Append( pos ); + activeTarget = numTargets() - 1; + if ( activeTarget == 0 ) { + // first one + addEvent( idCameraEvent::EVENT_TARGET, name, 0 ); + } + } +} + +const idVec3 *idSplinePosition::getPosition( long t ) { + static idVec3 interpolatedPos; + + float velocity = getVelocity( t ); + float timePassed = t - lastTime; + lastTime = t; + + // convert to seconds + timePassed /= 1000; + + float distToTravel = timePassed * velocity; + + distSoFar += distToTravel; + double tempDistance = target.totalDistance(); + + double percent = (double)( distSoFar ) / tempDistance; + + double targetDistance = percent * tempDistance; + tempDistance = 0; + + double lastDistance1,lastDistance2; + lastDistance1 = lastDistance2 = 0; + //FIXME: calc distances on spline build + idVec3 temp; + int count = target.numSegments(); + // TTimo fixed MSVCism: for(int i = 1; ... + int i; + for ( i = 1; i < count; i++ ) { + temp = *target.getSegmentPoint( i - 1 ); + temp -= *target.getSegmentPoint( i ); + tempDistance += temp.Length(); + if ( i & 1 ) { + lastDistance1 = tempDistance; + } else { + lastDistance2 = tempDistance; + } + if ( tempDistance >= targetDistance ) { + break; + } + } + + if ( i >= count - 1 ) { + interpolatedPos = *target.getSegmentPoint( i - 1 ); + } else { +#if 0 + double timeHi = target.getSegmentTime( i + 1 ); + double timeLo = target.getSegmentTime( i - 1 ); + double percent = ( timeHi - t ) / ( timeHi - timeLo ); + idVec3 v1 = *target.getSegmentPoint( i - 1 ); + idVec3 v2 = *target.getSegmentPoint( i + 1 ); + v2 *= ( 1.0 - percent ); + v1 *= percent; + v2 += v1; + interpolatedPos = v2; +#else + if ( lastDistance1 > lastDistance2 ) { + double d = lastDistance2; + lastDistance2 = lastDistance1; + lastDistance1 = d; + } + + idVec3 v1 = *target.getSegmentPoint( i - 1 ); + idVec3 v2 = *target.getSegmentPoint( i ); + double percent = ( lastDistance2 - targetDistance ) / ( lastDistance2 - lastDistance1 ); + v2 *= ( 1.0 - percent ); + v1 *= percent; + v2 += v1; + interpolatedPos = v2; +#endif + } + return &interpolatedPos; + +} + + + diff --git a/src/splines/splines.h b/src/splines/splines.h new file mode 100644 index 0000000..90898ab --- /dev/null +++ b/src/splines/splines.h @@ -0,0 +1,1127 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SPLINES_H +#define __SPLINES_H + +extern "C" { +#ifdef Q3RADIANT +#include "../qgl.h" +#else +#include "../renderer/qgl.h" +#endif +} +#include "util_list.h" +#include "util_str.h" +#include "math_vector.h" + +typedef int fileHandle_t; + +extern void glBox( idVec3 &color, idVec3 &point, float size ); +extern void glLabeledPoint( idVec3 &color, idVec3 &point, float size, const char *label ); + +static idVec4 blue( 0, 0, 1, 1 ); +static idVec4 red( 1, 0, 0, 1 ); + +class idPointListInterface { +public: +idPointListInterface() { + selectedPoints.Clear(); +}; +virtual ~idPointListInterface() { +}; + +virtual int numPoints() { + return 0; +} + +virtual void addPoint( const float x, const float y, const float z ) {} +virtual void addPoint( const idVec3 &v ) {} +virtual void removePoint( int index ) {} +virtual idVec3 *getPoint( int index ) { return NULL; } + +int selectPointByRay( float ox, float oy, float oz, float dx, float dy, float dz, bool single ) { + idVec3 origin( ox, oy, oz ); + idVec3 dir( dx, dy, dz ); + return selectPointByRay( origin, dir, single ); +} + +int selectPointByRay( const idVec3 origin, const idVec3 direction, bool single ) { + int i, besti, count; + float d, bestd; + idVec3 temp, temp2; + + // find the point closest to the ray + besti = -1; + bestd = 8; + count = numPoints(); + + for ( i = 0; i < count; i++ ) { + temp = *getPoint( i ); + temp2 = temp; + temp -= origin; + d = DotProduct( temp, direction ); + __VectorMA( origin, d, direction, temp ); + temp2 -= temp; + d = temp2.Length(); + if ( d <= bestd ) { + bestd = d; + besti = i; + } + } + + if ( besti >= 0 ) { + selectPoint( besti, single ); + } + + return besti; +} + +int isPointSelected( int index ) { + int count = selectedPoints.Num(); + for ( int i = 0; i < count; i++ ) { + if ( selectedPoints[i] == index ) { + return i; + } + } + return -1; +} + +int selectPoint( int index, bool single ) { + if ( index >= 0 && index < numPoints() ) { + if ( single ) { + deselectAll(); + } else { + if ( isPointSelected( index ) >= 0 ) { + selectedPoints.Remove( index ); + } + } + return selectedPoints.Append( index ); + } + return -1; +} + +void selectAll() { + selectedPoints.Clear(); + for ( int i = 0; i < numPoints(); i++ ) { + selectedPoints.Append( i ); + } +} + +void deselectAll() { + selectedPoints.Clear(); +} + +int numSelectedPoints(); + +idVec3 *getSelectedPoint( int index ) { + assert( index >= 0 && index < numSelectedPoints() ); + return getPoint( selectedPoints[index] ); +} + +virtual void updateSelection( float x, float y, float z ) { + idVec3 move( x, y, z ); + updateSelection( move ); +} + +virtual void updateSelection( const idVec3 &move ) { + int count = selectedPoints.Num(); + for ( int i = 0; i < count; i++ ) { + *getPoint( selectedPoints[i] ) += move; + } +} + +void drawSelection() { + int count = selectedPoints.Num(); + for ( int i = 0; i < count; i++ ) { + glBox( red, *getPoint( selectedPoints[i] ), 4 ); + } +} + +protected: +idList selectedPoints; + +}; + + +class idSplineList { + +public: + +idSplineList() { + clear(); +} + +idSplineList( const char *p ) { + clear(); + name = p; +}; + +~idSplineList() { + clear(); +}; + +void clearControl() { + for ( int i = 0; i < controlPoints.Num(); i++ ) { + delete controlPoints[i]; + } + controlPoints.Clear(); +} + +void clearSpline() { + for ( int i = 0; i < splinePoints.Num(); i++ ) { + delete splinePoints[i]; + } + splinePoints.Clear(); +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +void clear() { + clearControl(); + clearSpline(); + splineTime.Clear(); + selected = NULL; + dirty = true; + activeSegment = 0; + granularity = 0.025; + pathColor.set( 1.0, 0.5, 0.0 ); + controlColor.set( 0.7, 0.0, 1.0 ); + segmentColor.set( 0.0, 0.0, 1.0 ); + activeColor.set( 1.0, 0.0, 0.0 ); +} + +void initPosition( long startTime, long totalTime ); +const idVec3 *getPosition( long time ); + + +void draw( bool editMode ); +void addToRenderer(); + +void setSelectedPoint( idVec3 *p ); +idVec3 *getSelectedPoint() { + return selected; +} + +void addPoint( const idVec3 &v ) { + controlPoints.Append( new idVec3( v ) ); + dirty = true; +} + +void addPoint( float x, float y, float z ) { + controlPoints.Append( new idVec3( x, y, z ) ); + dirty = true; +} + +void updateSelection( const idVec3 &move ); + +void startEdit() { + editMode = true; +} + +void stopEdit() { + editMode = false; +} + +void buildSpline(); + +void setGranularity( float f ) { + granularity = f; +} + +float getGranularity() { + return granularity; +} + +int numPoints() { + return controlPoints.Num(); +} + +idVec3 *getPoint( int index ) { + assert( index >= 0 && index < controlPoints.Num() ); + return controlPoints[index]; +} + +idVec3 *getSegmentPoint( int index ) { + assert( index >= 0 && index < splinePoints.Num() ); + return splinePoints[index]; +} + + +void setSegmentTime( int index, int time ) { + assert( index >= 0 && index < splinePoints.Num() ); + splineTime[index] = time; +} + +int getSegmentTime( int index ) { + assert( index >= 0 && index < splinePoints.Num() ); + return (int)splineTime[index]; +} +void addSegmentTime( int index, int time ) { + assert( index >= 0 && index < splinePoints.Num() ); + splineTime[index] += time; +} + +float totalDistance(); + +static idVec3 zero; + +int getActiveSegment() { + return activeSegment; +} + +void setActiveSegment( int i ) { + //assert(i >= 0 && (splinePoints.Num() > 0 && i < splinePoints.Num())); + activeSegment = i; +} + +int numSegments() { + return splinePoints.Num(); +} + +void setColors( idVec3 &path, idVec3 &segment, idVec3 &control, idVec3 &active ) { + pathColor = path; + segmentColor = segment; + controlColor = control; + activeColor = active; +} + +const char *getName() { + return name.c_str(); +} + +void setName( const char *p ) { + name = p; +} + +bool validTime() { + if ( dirty ) { + buildSpline(); + } + // gcc doesn't allow static casting away from bools + // why? I've no idea... + return (bool)( splineTime.Num() > 0 && splineTime.Num() == splinePoints.Num() ); +} + +void setTime( long t ) { + time = t; +} + +void setBaseTime( long t ) { + baseTime = t; +} + +protected: +idStr name; +float calcSpline( int step, float tension ); +idList controlPoints; +idList splinePoints; +idList splineTime; +idVec3 *selected; +idVec3 pathColor, segmentColor, controlColor, activeColor; +float granularity; +bool editMode; +bool dirty; +int activeSegment; +long baseTime; +long time; +friend class idCamera; +}; + +// time in milliseconds +// velocity where 1.0 equal rough walking speed +struct idVelocity { + idVelocity( long start, long duration, float s ) { + startTime = start; + time = duration; + speed = s; + } + long startTime; + long time; + float speed; +}; + +// can either be a look at or origin position for a camera +// +class idCameraPosition : public idPointListInterface { +public: + +virtual void clear() { + editMode = false; + for ( int i = 0; i < velocities.Num(); i++ ) { + delete velocities[i]; + velocities[i] = NULL; + } + velocities.Clear(); +} + +idCameraPosition( const char *p ) { + name = p; +} + +idCameraPosition() { + time = 0; + name = "position"; +} + +idCameraPosition( long t ) { + time = t; +} + +virtual ~idCameraPosition() { + clear(); +} + + +// this can be done with RTTI syntax but i like the derived classes setting a type +// makes serialization a bit easier to see +// +enum positionType { + FIXED = 0x00, + INTERPOLATED, + SPLINE, + POSITION_COUNT +}; + + +virtual void start( long t ) { + startTime = t; +} + +long getTime() { + return time; +} + +virtual void setTime( long t ) { + time = t; +} + +float getBaseVelocity() { + return baseVelocity; +} + +float getVelocity( long t ) { + long check = t - startTime; + for ( int i = 0; i < velocities.Num(); i++ ) { + if ( check >= velocities[i]->startTime && check <= velocities[i]->startTime + velocities[i]->time ) { + return velocities[i]->speed; + } + } + return baseVelocity; +} + +void addVelocity( long start, long duration, float speed ) { + velocities.Append( new idVelocity( start, duration, speed ) ); +} + +virtual const idVec3 *getPosition( long t ) { + assert( true ); + return NULL; +} + +virtual void draw( bool editMode ) {}; + +virtual void parse( const char *( *text ) ) {}; +virtual void write( fileHandle_t file, const char *name ); +virtual bool parseToken( const char *key, const char *( *text ) ); + +const char *getName() { + return name.c_str(); +} + +void setName( const char *p ) { + name = p; +} + +virtual void startEdit() { + editMode = true; +} + +virtual void stopEdit() { + editMode = false; +} + +virtual void draw() {}; + +const char *typeStr() { + return positionStr[static_cast( type )]; +} + +void calcVelocity( float distance ) { + if ( time ) { //DAJ BUGFIX + float secs = (float)time / 1000; + baseVelocity = distance / secs; + } +} + +protected: +static const char* positionStr[POSITION_COUNT]; +long startTime; +long time; +idCameraPosition::positionType type; +idStr name; +bool editMode; +idList velocities; +float baseVelocity; +}; + +class idFixedPosition : public idCameraPosition { +public: + +void init() { + pos.Zero(); + type = idCameraPosition::FIXED; +} + +idFixedPosition() : idCameraPosition() { + init(); +} + +idFixedPosition( idVec3 p ) : idCameraPosition() { + init(); + pos = p; +} + +virtual void addPoint( const idVec3 &v ) { + pos = v; +} + +virtual void addPoint( const float x, const float y, const float z ) { + pos.set( x, y, z ); +} + + +~idFixedPosition() { +} + +virtual const idVec3 *getPosition( long t ) { + return &pos; +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +virtual int numPoints() { + return 1; +} + +virtual idVec3 *getPoint( int index ) { + if ( index != 0 ) { + assert( true ); + } + ; + return &pos; +} + +virtual void draw( bool editMode ) { + glLabeledPoint( blue, pos, ( editMode ) ? 5 : 3, "Fixed point" ); +} + +protected: +idVec3 pos; +}; + +class idInterpolatedPosition : public idCameraPosition { +public: + +void init() { + type = idCameraPosition::INTERPOLATED; + first = true; + startPos.Zero(); + endPos.Zero(); +} + +idInterpolatedPosition() : idCameraPosition() { + init(); +} + +idInterpolatedPosition( idVec3 start, idVec3 end, long time ) : idCameraPosition( time ) { + init(); + startPos = start; + endPos = end; +} + +~idInterpolatedPosition() { +} + +virtual const idVec3 *getPosition( long t ); + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +virtual int numPoints() { + return 2; +} + +virtual idVec3 *getPoint( int index ) { + assert( index >= 0 && index < 2 ); + if ( index == 0 ) { + return &startPos; + } + return &endPos; +} + +virtual void addPoint( const float x, const float y, const float z ) { + if ( first ) { + startPos.set( x, y, z ); + first = false; + } else { + endPos.set( x, y, z ); + first = true; + } +} + +virtual void addPoint( const idVec3 &v ) { + if ( first ) { + startPos = v; + first = false; + } else { + endPos = v; + first = true; + } +} + +virtual void draw( bool editMode ) { + glLabeledPoint( blue, startPos, ( editMode ) ? 5 : 3, "Start interpolated" ); + glLabeledPoint( blue, endPos, ( editMode ) ? 5 : 3, "End interpolated" ); + qglBegin( GL_LINES ); + qglVertex3fv( startPos ); + qglVertex3fv( endPos ); + qglEnd(); +} + +virtual void start( long t ) { + idCameraPosition::start( t ); + lastTime = startTime; + distSoFar = 0.0; + idVec3 temp = startPos; + temp -= endPos; + calcVelocity( temp.Length() ); +} + +protected: +bool first; +idVec3 startPos; +idVec3 endPos; +long lastTime; +float distSoFar; +}; + +class idSplinePosition : public idCameraPosition { +public: + +void init() { + type = idCameraPosition::SPLINE; +} + +idSplinePosition() : idCameraPosition() { + init(); +} + +idSplinePosition( long time ) : idCameraPosition( time ) { + init(); +} + +~idSplinePosition() { +} + +virtual void start( long t ) { + idCameraPosition::start( t ); + target.initPosition( t, time ); + lastTime = startTime; + distSoFar = 0.0; + calcVelocity( target.totalDistance() ); +} + +//virtual const idVec3 *getPosition(long t) { +// return target.getPosition(t); +//} +virtual const idVec3 *getPosition( long t ); + + +//virtual const idVec3 *getPosition(long t) const { + +void addControlPoint( idVec3 &v ) { + target.addPoint( v ); +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +virtual int numPoints() { + return target.numPoints(); +} + +virtual idVec3 *getPoint( int index ) { + return target.getPoint( index ); +} + +virtual void addPoint( const idVec3 &v ) { + target.addPoint( v ); +} + +virtual void addPoint( const float x, const float y, const float z ) { + target.addPoint( x, y, z ); +} + +virtual void draw( bool editMode ) { + target.draw( editMode ); +} + +virtual void updateSelection( const idVec3 &move ) { + idCameraPosition::updateSelection( move ); + target.buildSpline(); +} + +protected: +idSplineList target; +long lastTime; +float distSoFar; +}; + +class idCameraFOV { +public: + +idCameraFOV() { + time = 0; + length = 0; + fov = 90; +} + +idCameraFOV( int v ) { + time = 0; + length = 0; + fov = v; +} + +idCameraFOV( int s, int e, long t ) { + startFOV = s; + endFOV = e; + length = t; +} + + +~idCameraFOV() { +} + +void setFOV( float f ) { + fov = f; +} + +float getFOV( long t ) { + if ( length ) { + float percent = ( t - startTime ) / length; + if ( percent < 0.0 ) { + percent = 0.0; + } else if ( percent > 1.0 ) { + percent = 1.0; + } + float temp = endFOV - startFOV; + temp *= percent; + fov = startFOV + temp; + + if ( percent == 1.0 ) { + length = 0.0; + } + } + return fov; +} + +void start( long t ) { //DAJ was returning int + startTime = t; +} + +void reset( float startfov, float endfov, int start, float len ) { + startFOV = startfov; + endFOV = endfov; + startTime = start; + length = len * 1000; +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +protected: +float fov; +float startFOV; +float endFOV; +int startTime; +int time; +float length; +}; + + + + +class idCameraEvent { +public: +enum eventType { + EVENT_NA = 0x00, + EVENT_WAIT, + EVENT_TARGETWAIT, + EVENT_SPEED, + EVENT_TARGET, + EVENT_SNAPTARGET, + EVENT_FOV, + EVENT_CMD, + EVENT_TRIGGER, + EVENT_STOP, + EVENT_CAMERA, + EVENT_FADEOUT, + EVENT_FADEIN, + EVENT_COUNT +}; + +static const char* eventStr[EVENT_COUNT]; + +idCameraEvent() { + paramStr = ""; + type = EVENT_NA; + time = 0; +} + +idCameraEvent( eventType t, const char *param, long n ) { + type = t; + paramStr = param; + time = n; +} + +~idCameraEvent() { +}; + +eventType getType() { + return type; +} + +const char *typeStr() { + return eventStr[static_cast( type )]; +} + +const char *getParam() { + return paramStr.c_str(); +} + +long getTime() { + return time; +} + +void setTime( long n ) { + time = n; +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +void setTriggered( bool b ) { + triggered = b; +} + +bool getTriggered() { + return triggered; +} + +protected: +eventType type; +idStr paramStr; +long time; +bool triggered; + +}; + +class idCameraDef { +public: + +void clear() { + currentCameraPosition = 0; + cameraRunning = false; + lastDirection.Zero(); + baseTime = 30; + activeTarget = 0; + name = "camera01"; + fov.setFOV( 90 ); + int i; + for ( i = 0; i < targetPositions.Num(); i++ ) { + delete targetPositions[i]; + } + for ( i = 0; i < events.Num(); i++ ) { + delete events[i]; + } + delete cameraPosition; + cameraPosition = NULL; + events.Clear(); + targetPositions.Clear(); +} + +idCameraPosition *startNewCamera( idCameraPosition::positionType type ) { + clear(); + if ( type == idCameraPosition::SPLINE ) { + cameraPosition = new idSplinePosition(); + } else if ( type == idCameraPosition::INTERPOLATED ) { + cameraPosition = new idInterpolatedPosition(); + } else { + cameraPosition = new idFixedPosition(); + } + return cameraPosition; +} + +idCameraDef() { + cameraPosition = NULL; + clear(); +} + +~idCameraDef() { + clear(); +} + +void addEvent( idCameraEvent::eventType t, const char *param, long time ); + +void addEvent( idCameraEvent *event ); + +static int sortEvents( const void *p1, const void *p2 ); + +int numEvents() { + return events.Num(); +} + +idCameraEvent *getEvent( int index ) { + assert( index >= 0 && index < events.Num() ); + return events[index]; +} + +void parse( const char *( *text ) ); +bool load( const char *filename ); +void save( const char *filename ); + +void buildCamera(); + +//idSplineList *getcameraPosition() { +// return &cameraPosition; +//} + +static idCameraPosition *newFromType( idCameraPosition::positionType t ) { + switch ( t ) { + case idCameraPosition::FIXED: return new idFixedPosition(); + case idCameraPosition::INTERPOLATED: return new idInterpolatedPosition(); + case idCameraPosition::SPLINE: return new idSplinePosition(); + default: + break; + }; + return NULL; +} + +void addTarget( const char *name, idCameraPosition::positionType type ); + +idCameraPosition *getActiveTarget() { + if ( targetPositions.Num() == 0 ) { + addTarget( NULL, idCameraPosition::FIXED ); + } + return targetPositions[activeTarget]; +} + +idCameraPosition *getActiveTarget( int index ) { + if ( targetPositions.Num() == 0 ) { + addTarget( NULL, idCameraPosition::FIXED ); + return targetPositions[0]; + } + return targetPositions[index]; +} + +int numTargets() { + return targetPositions.Num(); +} + + +void setActiveTargetByName( const char *name ) { + for ( int i = 0; i < targetPositions.Num(); i++ ) { + if ( Q_stricmp( name, targetPositions[i]->getName() ) == 0 ) { + setActiveTarget( i ); + return; + } + } +} + +void setActiveTarget( int index ) { + assert( index >= 0 && index < targetPositions.Num() ); + activeTarget = index; +} + +void setRunning( bool b ) { + cameraRunning = b; +} + +void setBaseTime( float f ) { + baseTime = f; +} + +float getBaseTime() { + return baseTime; +} + +float getTotalTime() { + return totalTime; +} + +void startCamera( long t ); +void stopCamera() { + cameraRunning = true; +} +void getActiveSegmentInfo( int segment, idVec3 &origin, idVec3 &direction, float *fv ); + +bool getCameraInfo( long time, idVec3 &origin, idVec3 &direction, float *fv ); +bool getCameraInfo( long time, float *origin, float *direction, float *fv ) { + idVec3 org, dir; + org[0] = origin[0]; + org[1] = origin[1]; + org[2] = origin[2]; + dir[0] = direction[0]; + dir[1] = direction[1]; + dir[2] = direction[2]; + bool b = getCameraInfo( time, org, dir, fv ); + origin[0] = org[0]; + origin[1] = org[1]; + origin[2] = org[2]; + direction[0] = dir[0]; + direction[1] = dir[1]; + direction[2] = dir[2]; + return b; +} + +void draw( bool editMode ) { + // gcc doesn't allow casting away from bools + // why? I've no idea... + if ( cameraPosition ) { + cameraPosition->draw( (bool)( ( editMode || cameraRunning ) && cameraEdit ) ); + int count = targetPositions.Num(); + for ( int i = 0; i < count; i++ ) { + targetPositions[i]->draw( (bool)( ( editMode || cameraRunning ) && i == activeTarget && !cameraEdit ) ); + } + } +} + +/* + int numSegments() { + if (cameraEdit) { + return cameraPosition.numSegments(); + } + return getTargetSpline()->numSegments(); + } + + int getActiveSegment() { + if (cameraEdit) { + return cameraPosition.getActiveSegment(); + } + return getTargetSpline()->getActiveSegment(); + } + + void setActiveSegment(int i) { + if (cameraEdit) { + cameraPosition.setActiveSegment(i); + } else { + getTargetSpline()->setActiveSegment(i); + } + } +*/ +int numPoints() { + if ( cameraEdit ) { + return cameraPosition->numPoints(); + } + return getActiveTarget()->numPoints(); +} + +const idVec3 *getPoint( int index ) { + if ( cameraEdit ) { + return cameraPosition->getPoint( index ); + } + return getActiveTarget()->getPoint( index ); +} + +void stopEdit() { + editMode = false; + if ( cameraEdit ) { + cameraPosition->stopEdit(); + } else { + getActiveTarget()->stopEdit(); + } +} + +void startEdit( bool camera ) { + cameraEdit = camera; + if ( camera ) { + cameraPosition->startEdit(); + for ( int i = 0; i < targetPositions.Num(); i++ ) { + targetPositions[i]->stopEdit(); + } + } else { + getActiveTarget()->startEdit(); + cameraPosition->stopEdit(); + } + editMode = true; +} + +bool waitEvent( int index ); + +const char *getName() { + return name.c_str(); +} + +void setName( const char *p ) { + name = p; +} + +idCameraPosition *getPositionObj() { + if ( cameraPosition == NULL ) { + cameraPosition = new idFixedPosition(); + } + return cameraPosition; +} + +protected: +idStr name; +int currentCameraPosition; +idVec3 lastDirection; +bool cameraRunning; +idCameraPosition *cameraPosition; +idList targetPositions; +idList events; +idCameraFOV fov; +int activeTarget; +float totalTime; +float baseTime; +long startTime; + +bool cameraEdit; +bool editMode; +}; + +extern bool g_splineMode; + +extern idCameraDef *g_splineList; + + +#endif diff --git a/src/splines/util_list.h b/src/splines/util_list.h new file mode 100644 index 0000000..003ce70 --- /dev/null +++ b/src/splines/util_list.h @@ -0,0 +1,353 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __UTIL_LIST_H__ +#define __UTIL_LIST_H__ + +#include +#include + +template< class type > +class idList { +private: +int m_num; +int m_size; +int m_granularity; +type *m_list; + +public: +idList( int granularity = 16 ); +~idList(); +void Clear( void ); +int Num( void ); +void SetNum( int num ); +void SetGranularity( int granularity ); +void Condense( void ); +int Size( void ); +void Resize( int size ); +type operator[]( int index ) const; +type &operator[]( int index ); +int Append( type const & obj ); +int AddUnique( type const & obj ); +type *Find( type const & obj, int *index = NULL ); +bool RemoveIndex( int index ); +bool Remove( type const & obj ); +typedef int cmp_t ( const void *, const void * ); +void Sort( cmp_t *compare ); +}; + +/* +================ +idList::idList( int ) +================ +*/ +template< class type > +inline idList::idList( int granularity ) { + assert( granularity > 0 ); + + m_list = NULL; + m_granularity = granularity; + Clear(); +} + +/* +================ +idList::~idList +================ +*/ +template< class type > +inline idList::~idList() { + Clear(); +} + +/* +================ +idList::Clear +================ +*/ +template< class type > +inline void idList::Clear( void ) { + if ( m_list ) { + delete[] m_list; + } + + m_list = NULL; + m_num = 0; + m_size = 0; +} + +/* +================ +idList::Num +================ +*/ +template< class type > +inline int idList::Num( void ) { + return m_num; +} + +/* +================ +idList::SetNum +================ +*/ +template< class type > +inline void idList::SetNum( int num ) { + assert( num >= 0 ); + if ( num > m_size ) { + // resize it up to the closest level of granularity + Resize( ( ( num + m_granularity - 1 ) / m_granularity ) * m_granularity ); + } + m_num = num; +} + +/* +================ +idList::SetGranularity +================ +*/ +template< class type > +inline void idList::SetGranularity( int granularity ) { + int newsize; + + assert( granularity > 0 ); + m_granularity = granularity; + + if ( m_list ) { + // resize it to the closest level of granularity + newsize = ( ( m_num + m_granularity - 1 ) / m_granularity ) * m_granularity; + if ( newsize != m_size ) { + Resize( newsize ); + } + } +} + +/* +================ +idList::Condense + +Resizes the array to exactly the number of elements it contains +================ +*/ +template< class type > +inline void idList::Condense( void ) { + if ( m_list ) { + if ( m_num ) { + Resize( m_num ); + } else { + Clear(); + } + } +} + +/* +================ +idList::Size +================ +*/ +template< class type > +inline int idList::Size( void ) { + return m_size; +} + +/* +================ +idList::Resize +================ +*/ +template< class type > +inline void idList::Resize( int size ) { + type *temp; + int i; + + assert( size > 0 ); + + if ( size <= 0 ) { + Clear(); + return; + } + + temp = m_list; + m_size = size; + if ( m_size < m_num ) { + m_num = m_size; + } + + m_list = new type[ m_size ]; + for ( i = 0; i < m_num; i++ ) { + m_list[ i ] = temp[ i ]; + } + + if ( temp ) { + delete[] temp; + } +} + +/* +================ +idList::operator[] const +================ +*/ +template< class type > +inline type idList::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < m_num ); + + return m_list[ index ]; +} + +/* +================ +idList::operator[] +================ +*/ +template< class type > +inline type &idList::operator[]( int index ) { + assert( index >= 0 ); + assert( index < m_num ); + + return m_list[ index ]; +} + +/* +================ +idList::Append +================ +*/ +template< class type > +inline int idList::Append( type const & obj ) { + if ( !m_list ) { + Resize( m_granularity ); + } + + if ( m_num == m_size ) { + Resize( m_size + m_granularity ); + } + + m_list[ m_num ] = obj; + m_num++; + + return m_num - 1; +} + +/* +================ +idList::AddUnique +================ +*/ +template< class type > +inline int idList::AddUnique( type const & obj ) { + int index; + + if ( !Find( obj, &index ) ) { + index = Append( obj ); + } + + return index; +} + +/* +================ +idList::Find +================ +*/ +template< class type > +inline type *idList::Find( type const & obj, int *index ) { + int i; + + for ( i = 0; i < m_num; i++ ) { + if ( m_list[ i ] == obj ) { + if ( index ) { + *index = i; + } + return &m_list[ i ]; + } + } + + return NULL; +} + +/* +================ +idList::RemoveIndex +================ +*/ +template< class type > +inline bool idList::RemoveIndex( int index ) { + int i; + + if ( !m_list || !m_num ) { + return false; + } + + assert( index >= 0 ); + assert( index < m_num ); + + if ( ( index < 0 ) || ( index >= m_num ) ) { + return false; + } + + m_num--; + for ( i = index; i < m_num; i++ ) { + m_list[ i ] = m_list[ i + 1 ]; + } + + return true; +} + +/* +================ +idList::Remove +================ +*/ +template< class type > +inline bool idList::Remove( type const & obj ) { + int index; + + if ( Find( obj, &index ) ) { + return RemoveIndex( index ); + } + + return false; +} + +/* +================ +idList::Sort +================ +*/ +template< class type > +inline void idList::Sort( cmp_t *compare ) { + if ( !m_list ) { + return; + } + + qsort( ( void * )m_list, ( size_t )m_num, sizeof( type ), compare ); +} + +#endif /* !__UTIL_LIST_H__ */ diff --git a/src/splines/util_str.cpp b/src/splines/util_str.cpp new file mode 100644 index 0000000..314bb31 --- /dev/null +++ b/src/splines/util_str.cpp @@ -0,0 +1,571 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//need to rewrite this + +#include "util_str.h" +#include +#include +#include +#include + +#ifdef _WIN32 +#pragma warning(disable : 4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4710) // function 'blah' not inlined +#endif + +static const int STR_ALLOC_GRAN = 20; + +char *idStr::tolower +( + char *s1 +) { + char *s; + + s = s1; + while ( *s ) + { + *s = ::tolower( *s ); + s++; + } + + return s1; +} + +char *idStr::toupper +( + char *s1 +) { + char *s; + + s = s1; + while ( *s ) + { + *s = ::toupper( *s ); + s++; + } + + return s1; +} + +int idStr::icmpn +( + const char *s1, + const char *s2, + int n +) { + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + // idStrings are equal until end point + return 0; + } + + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 < c2 ) { + // strings less than + return -1; + } else if ( c1 > c2 ) { + // strings greater than + return 1; + } + } + } + while ( c1 ); + + // strings are equal + return 0; +} + +int idStr::icmp +( + const char *s1, + const char *s2 +) { + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 < c2 ) { + // strings less than + return -1; + } else if ( c1 > c2 ) { + // strings greater than + return 1; + } + } + } + while ( c1 ); + + // strings are equal + return 0; +} + +int idStr::cmpn +( + const char *s1, + const char *s2, + int n +) { + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + // strings are equal until end point + return 0; + } + + if ( c1 < c2 ) { + // strings less than + return -1; + } else if ( c1 > c2 ) { + // strings greater than + return 1; + } + } + while ( c1 ); + + // strings are equal + return 0; +} + +int idStr::cmp +( + const char *s1, + const char *s2 +) { + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( c1 < c2 ) { + // strings less than + return -1; + } else if ( c1 > c2 ) { + // strings greater than + return 1; + } + } + while ( c1 ); + + // strings are equal + return 0; +} + +/* +============ +IsNumeric + +Checks a string to see if it contains only numerical values. +============ +*/ +bool idStr::isNumeric +( + const char *str +) { + int len; + int i; + bool dot; + + if ( *str == '-' ) { + str++; + } + + dot = false; + len = strlen( str ); + for ( i = 0; i < len; i++ ) + { + if ( !isdigit( str[ i ] ) ) { + if ( ( str[ i ] == '.' ) && !dot ) { + dot = true; + continue; + } + return false; + } + } + + return true; +} + +idStr operator+ +( + const idStr& a, + const float b +) { + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%f", b ); + result.append( text ); + + return result; +} + +idStr operator+ +( + const idStr& a, + const int b +) { + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%d", b ); + result.append( text ); + + return result; +} + +idStr operator+ +( + const idStr& a, + const unsigned b +) { + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%u", b ); + result.append( text ); + + return result; +} + +idStr& idStr::operator+= +( + const float a +) { + char text[ 20 ]; + + sprintf( text, "%f", a ); + append( text ); + + return *this; +} + +idStr& idStr::operator+= +( + const int a +) { + char text[ 20 ]; + + sprintf( text, "%d", a ); + append( text ); + + return *this; +} + +idStr& idStr::operator+= +( + const unsigned a +) { + char text[ 20 ]; + + sprintf( text, "%u", a ); + append( text ); + + return *this; +} + +void idStr::CapLength +( + int newlen +) { + assert( m_data ); + + if ( length() <= newlen ) { + return; + } + + EnsureDataWritable(); + + m_data->data[newlen] = 0; + m_data->len = newlen; +} + +void idStr::EnsureDataWritable +( + void +) { + assert( m_data ); + strdata *olddata; + int len; + + if ( !m_data->refcount ) { + return; + } + + olddata = m_data; + len = length(); + + m_data = new strdata; + + EnsureAlloced( len + 1, false ); + strncpy( m_data->data, olddata->data, len + 1 ); + m_data->len = len; + + olddata->DelRef(); +} + +void idStr::EnsureAlloced( int amount, bool keepold ) { + + if ( !m_data ) { + m_data = new strdata(); + } + + // Now, let's make sure it's writable + EnsureDataWritable(); + + char *newbuffer; + bool wasalloced = ( m_data->alloced != 0 ); + + if ( amount < m_data->alloced ) { + return; + } + + assert( amount ); + if ( amount == 1 ) { + m_data->alloced = 1; + } else { + int newsize, mod; + mod = amount % STR_ALLOC_GRAN; + if ( !mod ) { + newsize = amount; + } else { + newsize = amount + STR_ALLOC_GRAN - mod; + } + m_data->alloced = newsize; + } + + newbuffer = new char[m_data->alloced]; + if ( wasalloced && keepold ) { + strcpy( newbuffer, m_data->data ); + } + + if ( m_data->data ) { + delete [] m_data->data; + } + m_data->data = newbuffer; +} + +void idStr::BackSlashesToSlashes +( + void +) { + int i; + + EnsureDataWritable(); + + for ( i = 0; i < m_data->len; i++ ) + { + if ( m_data->data[i] == '\\' ) { + m_data->data[i] = '/'; + } + } +} + +void idStr::snprintf +( + char *dst, + int size, + const char *fmt, + ... +) { + char buffer[0x10000]; + int len; + va_list argptr; + + va_start( argptr,fmt ); + len = vsprintf( buffer,fmt,argptr ); + va_end( argptr ); + + assert( len < size ); + + strncpy( dst, buffer, size - 1 ); +} + +#ifdef _WIN32 +#pragma warning(disable : 4189) // local variable is initialized but not referenced +#endif +#if 0 +/* +================= +TestStringClass + +This is a fairly rigorous test of the idStr class's functionality. +Because of the fairly global and subtle ramifications of a bug occuring +in this class, it should be run after any changes to the class. +Add more tests as functionality is changed. Tests should include +any possible bounds violation and NULL data tests. +================= +*/ +void TestStringClass +( + void +) { + char ch; // ch == ? + idStr *t; // t == ? + idStr a; // a.len == 0, a.data == "\0" + idStr b; // b.len == 0, b.data == "\0" + idStr c( "test" ); // c.len == 4, c.data == "test\0" + idStr d( c ); // d.len == 4, d.data == "test\0" + idStr e( reinterpret_cast( NULL ) ); + // e.len == 0, e.data == "\0" ASSERT! + int i; // i == ? + + i = a.length(); // i == 0 + i = c.length(); // i == 4 + + const char *s1 = a.c_str(); // s1 == "\0" + const char *s2 = c.c_str(); // s2 == "test\0" + + t = new idStr(); // t->len == 0, t->data == "\0" + delete t; // t == ? + + b = "test"; // b.len == 4, b.data == "test\0" + t = new idStr( "test" ); // t->len == 4, t->data == "test\0" + delete t; // t == ? + + a = c; // a.len == 4, a.data == "test\0" +// a = ""; + a = NULL; // a.len == 0, a.data == "\0" ASSERT! + a = c + d; // a.len == 8, a.data == "testtest\0" + a = c + "wow"; // a.len == 7, a.data == "testwow\0" + a = c + reinterpret_cast( NULL ); + // a.len == 4, a.data == "test\0" ASSERT! + a = "this" + d; // a.len == 8, a.data == "thistest\0" + a = reinterpret_cast( NULL ) + d; + // a.len == 4, a.data == "test\0" ASSERT! + a += c; // a.len == 8, a.data == "testtest\0" + a += "wow"; // a.len == 11, a.data == "testtestwow\0" + a += reinterpret_cast( NULL ); + // a.len == 11, a.data == "testtestwow\0" ASSERT! + + a = "test"; // a.len == 4, a.data == "test\0" + ch = a[ 0 ]; // ch == 't' + ch = a[ -1 ]; // ch == 0 ASSERT! + ch = a[ 1000 ]; // ch == 0 ASSERT! + ch = a[ 0 ]; // ch == 't' + ch = a[ 1 ]; // ch == 'e' + ch = a[ 2 ]; // ch == 's' + ch = a[ 3 ]; // ch == 't' + ch = a[ 4 ]; // ch == '\0' ASSERT! + ch = a[ 5 ]; // ch == '\0' ASSERT! + + a[ 1 ] = 'b'; // a.len == 4, a.data == "tbst\0" + a[ -1 ] = 'b'; // a.len == 4, a.data == "tbst\0" ASSERT! + a[ 0 ] = '0'; // a.len == 4, a.data == "0bst\0" + a[ 1 ] = '1'; // a.len == 4, a.data == "01st\0" + a[ 2 ] = '2'; // a.len == 4, a.data == "012t\0" + a[ 3 ] = '3'; // a.len == 4, a.data == "0123\0" + a[ 4 ] = '4'; // a.len == 4, a.data == "0123\0" ASSERT! + a[ 5 ] = '5'; // a.len == 4, a.data == "0123\0" ASSERT! + a[ 7 ] = '7'; // a.len == 4, a.data == "0123\0" ASSERT! + + a = "test"; // a.len == 4, a.data == "test\0" + b = "no"; // b.len == 2, b.data == "no\0" + + i = ( a == b ); // i == 0 + i = ( a == c ); // i == 1 + + i = ( a == "blow" ); // i == 0 + i = ( a == "test" ); // i == 1 + i = ( a == NULL ); // i == 0 ASSERT! + + i = ( "test" == b ); // i == 0 + i = ( "test" == a ); // i == 1 + i = ( NULL == a ); // i == 0 ASSERT! + + i = ( a != b ); // i == 1 + i = ( a != c ); // i == 0 + + i = ( a != "blow" ); // i == 1 + i = ( a != "test" ); // i == 0 + i = ( a != NULL ); // i == 1 ASSERT! + + i = ( "test" != b ); // i == 1 + i = ( "test" != a ); // i == 0 + i = ( NULL != a ); // i == 1 ASSERT! + + a = "test"; // a.data == "test" + b = a; // b.data == "test" + + a = "not"; // a.data == "not", b.data == "test" + + a = b; // a.data == b.data == "test" + + a += b; // a.data == "testtest", b.data = "test" + + a = b; + + a[1] = '1'; // a.data = "t1st", b.data = "test" +} + +#endif + +#ifdef _WIN32 +#pragma warning(default : 4189) // local variable is initialized but not referenced +#pragma warning(disable : 4514) // unreferenced inline function has been removed +#endif diff --git a/src/splines/util_str.h b/src/splines/util_str.h new file mode 100644 index 0000000..551d60b --- /dev/null +++ b/src/splines/util_str.h @@ -0,0 +1,728 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +//need to rewrite this + +#ifndef __UTIL_STR_H__ +#define __UTIL_STR_H__ + +#include +#include +#include + +#ifdef _WIN32 +#pragma warning(disable : 4710) // function 'blah' not inlined +#endif + +void TestStringClass(); + +class strdata +{ +public: +strdata () : len( 0 ), refcount( 0 ), data( NULL ), alloced( 0 ) { +} +~strdata () { + if ( data ) { + delete [] data; + } +} + +void AddRef() { refcount++; } +bool DelRef() { // True if killed + refcount--; + if ( refcount < 0 ) { + delete this; + return true; + } + + return false; +} + +int len; +int refcount; +char *data; +int alloced; +}; + +class idStr { +protected: +strdata *m_data; +void EnsureAlloced( int, bool keepold = true ); +void EnsureDataWritable(); + +public: +~idStr(); +idStr(); +idStr( const char *text ); +idStr( const idStr& string ); +idStr( const idStr string, int start, int end ); +idStr( const char ch ); +idStr( const int num ); +idStr( const float num ); +idStr( const unsigned num ); +int length( void ) const; +int allocated( void ) const; +const char * c_str( void ) const; + +void append( const char *text ); +void append( const idStr& text ); +char operator[]( int index ) const; +char& operator[]( int index ); + +void operator=( const idStr& text ); +void operator=( const char *text ); + +friend idStr operator+( const idStr& a, const idStr& b ); +friend idStr operator+( const idStr& a, const char *b ); +friend idStr operator+( const char *a, const idStr& b ); + +friend idStr operator+( const idStr& a, const float b ); +friend idStr operator+( const idStr& a, const int b ); +friend idStr operator+( const idStr& a, const unsigned b ); +friend idStr operator+( const idStr& a, const bool b ); +friend idStr operator+( const idStr& a, const char b ); + +idStr& operator+=( const idStr& a ); +idStr& operator+=( const char *a ); +idStr& operator+=( const float a ); +idStr& operator+=( const char a ); +idStr& operator+=( const int a ); +idStr& operator+=( const unsigned a ); +idStr& operator+=( const bool a ); + +friend bool operator==( const idStr& a, const idStr& b ); +friend bool operator==( const idStr& a, const char *b ); +friend bool operator==( const char *a, const idStr& b ); + +friend bool operator!=( const idStr& a, const idStr& b ); +friend bool operator!=( const idStr& a, const char *b ); +friend bool operator!=( const char *a, const idStr& b ); + +operator const char*() const; +operator const char*(); + +int icmpn( const char *text, int n ) const; +int icmpn( const idStr& text, int n ) const; +int icmp( const char *text ) const; +int icmp( const idStr& text ) const; +int cmpn( const char *text, int n ) const; +int cmpn( const idStr& text, int n ) const; +int cmp( const char *text ) const; +int cmp( const idStr& text ) const; + +void tolower( void ); +void toupper( void ); + +static char *tolower( char *s1 ); +static char *toupper( char *s1 ); + +static int icmpn( const char *s1, const char *s2, int n ); +static int icmp( const char *s1, const char *s2 ); +static int cmpn( const char *s1, const char *s2, int n ); +static int cmp( const char *s1, const char *s2 ); + +static void snprintf( char *dst, int size, const char *fmt, ... ); + +static bool isNumeric( const char *str ); +bool isNumeric( void ) const; + +void CapLength( int ); + +void BackSlashesToSlashes(); + +}; + +inline idStr::~idStr() { + if ( m_data ) { + m_data->DelRef(); + m_data = NULL; + } +} + +inline idStr::idStr() : m_data( NULL ) { + EnsureAlloced( 1 ); + m_data->data[ 0 ] = 0; +} + +inline idStr::idStr +( + const char *text +) : m_data( NULL ) { + int len; + + assert( text ); + + if ( text ) { + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; + } else + { + EnsureAlloced( 1 ); + m_data->data[ 0 ] = 0; + m_data->len = 0; + } +} + +inline idStr::idStr +( + const idStr& text +) : m_data( NULL ) { + m_data = text.m_data; + m_data->AddRef(); +} + +inline idStr::idStr +( + const idStr text, + int start, + int end +) : m_data( NULL ) { + int i; + int len; + + if ( end > text.length() ) { + end = text.length(); + } + + if ( start > text.length() ) { + start = text.length(); + } + + len = end - start; + if ( len < 0 ) { + len = 0; + } + + EnsureAlloced( len + 1 ); + + for ( i = 0; i < len; i++ ) + { + m_data->data[ i ] = text[ start + i ]; + } + + m_data->data[ len ] = 0; + m_data->len = len; +} + +inline idStr::idStr +( + const char ch +) : m_data( NULL ) { + EnsureAlloced( 2 ); + + m_data->data[ 0 ] = ch; + m_data->data[ 1 ] = 0; + m_data->len = 1; +} + +inline idStr::idStr +( + const float num +) : m_data( NULL ) { + char text[ 32 ]; + int len; + + sprintf( text, "%.3f", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; +} + +inline idStr::idStr +( + const int num +) : m_data( NULL ) { + char text[ 32 ]; + int len; + + sprintf( text, "%d", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; +} + +inline idStr::idStr +( + const unsigned num +) : m_data( NULL ) { + char text[ 32 ]; + int len; + + sprintf( text, "%u", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; +} + +inline int idStr::length( void ) const { + return ( m_data != NULL ) ? m_data->len : 0; +} + +inline int idStr::allocated( void ) const { + return ( m_data != NULL ) ? m_data->alloced + sizeof( *m_data ) : 0; +} + +inline const char *idStr::c_str( void ) const { + assert( m_data ); + + return m_data->data; +} + +inline void idStr::append +( + const char *text +) { + int len; + + assert( text ); + + if ( text ) { + len = length() + strlen( text ); + EnsureAlloced( len + 1 ); + + strcat( m_data->data, text ); + m_data->len = len; + } +} + +inline void idStr::append +( + const idStr& text +) { + int len; + + len = length() + text.length(); + EnsureAlloced( len + 1 ); + + strcat( m_data->data, text.c_str() ); + m_data->len = len; +} + +inline char idStr::operator[]( int index ) const { + assert( m_data ); + + if ( !m_data ) { + return 0; + } + + // don't include the '/0' in the test, because technically, it's out of bounds + assert( ( index >= 0 ) && ( index < m_data->len ) ); + + // In release mode, give them a null character + // don't include the '/0' in the test, because technically, it's out of bounds + if ( ( index < 0 ) || ( index >= m_data->len ) ) { + return 0; + } + + return m_data->data[ index ]; +} + +inline char& idStr::operator[] +( + int index +) { + // Used for result for invalid indices + static char dummy = 0; + assert( m_data ); + + // We don't know if they'll write to it or not + // if it's not a const object + EnsureDataWritable(); + + if ( !m_data ) { + return dummy; + } + + // don't include the '/0' in the test, because technically, it's out of bounds + assert( ( index >= 0 ) && ( index < m_data->len ) ); + + // In release mode, let them change a safe variable + // don't include the '/0' in the test, because technically, it's out of bounds + if ( ( index < 0 ) || ( index >= m_data->len ) ) { + return dummy; + } + + return m_data->data[ index ]; +} + +inline void idStr::operator= +( + const idStr& text +) { + // adding the reference before deleting our current reference prevents + // us from deleting our string if we are copying from ourself + text.m_data->AddRef(); + m_data->DelRef(); + m_data = text.m_data; +} + +inline void idStr::operator= +( + const char *text +) { + int len; + + assert( text ); + + if ( !text ) { + // safe behaviour if NULL + EnsureAlloced( 1, false ); + m_data->data[0] = 0; + m_data->len = 0; + return; + } + + if ( !m_data ) { + len = strlen( text ); + EnsureAlloced( len + 1, false ); + strcpy( m_data->data, text ); + m_data->len = len; + return; + } + + if ( text == m_data->data ) { + return; // Copying same thing. Punt. + + } + // If we alias and I don't do this, I could corrupt other strings... This + // will get called with EnsureAlloced anyway + EnsureDataWritable(); + + // Now we need to check if we're aliasing.. + if ( text >= m_data->data && text <= m_data->data + m_data->len ) { + // Great, we're aliasing. We're copying from inside ourselves. + // This means that I don't have to ensure that anything is alloced, + // though I'll assert just in case. + int diff = text - m_data->data; + int i; + + assert( strlen( text ) < (unsigned) m_data->len ); + + for ( i = 0; text[i]; i++ ) + { + m_data->data[i] = text[i]; + } + + m_data->data[i] = 0; + + m_data->len -= diff; + + return; + } + + len = strlen( text ); + EnsureAlloced( len + 1, false ); + strcpy( m_data->data, text ); + m_data->len = len; +} + +inline idStr operator+ +( + const idStr& a, + const idStr& b +) { + idStr result( a ); + + result.append( b ); + + return result; +} + +inline idStr operator+ +( + const idStr& a, + const char *b +) { + idStr result( a ); + + result.append( b ); + + return result; +} + +inline idStr operator+ +( + const char *a, + const idStr& b +) { + idStr result( a ); + + result.append( b ); + + return result; +} + +inline idStr operator+ +( + const idStr& a, + const bool b +) { + idStr result( a ); + + result.append( b ? "true" : "false" ); + + return result; +} + +inline idStr operator+ +( + const idStr& a, + const char b +) { + char text[ 2 ]; + + text[ 0 ] = b; + text[ 1 ] = 0; + + return a + text; +} + +inline idStr& idStr::operator+= +( + const idStr& a +) { + append( a ); + return *this; +} + +inline idStr& idStr::operator+= +( + const char *a +) { + append( a ); + return *this; +} + +inline idStr& idStr::operator+= +( + const char a +) { + char text[ 2 ]; + + text[ 0 ] = a; + text[ 1 ] = 0; + append( text ); + + return *this; +} + +inline idStr& idStr::operator+= +( + const bool a +) { + append( a ? "true" : "false" ); + return *this; +} + +inline bool operator== +( + const idStr& a, + const idStr& b +) { + return ( !strcmp( a.c_str(), b.c_str() ) ); +} + +inline bool operator== +( + const idStr& a, + const char *b +) { + assert( b ); + if ( !b ) { + return false; + } + return ( !strcmp( a.c_str(), b ) ); +} + +inline bool operator== +( + const char *a, + const idStr& b +) { + assert( a ); + if ( !a ) { + return false; + } + return ( !strcmp( a, b.c_str() ) ); +} + +inline bool operator!= +( + const idStr& a, + const idStr& b +) { + return !( a == b ); +} + +inline bool operator!= +( + const idStr& a, + const char *b +) { + return !( a == b ); +} + +inline bool operator!= +( + const char *a, + const idStr& b +) { + return !( a == b ); +} + +inline int idStr::icmpn +( + const char *text, + int n +) const { + assert( m_data ); + assert( text ); + + return idStr::icmpn( m_data->data, text, n ); +} + +inline int idStr::icmpn +( + const idStr& text, + int n +) const { + assert( m_data ); + assert( text.m_data ); + + return idStr::icmpn( m_data->data, text.m_data->data, n ); +} + +inline int idStr::icmp +( + const char *text +) const { + assert( m_data ); + assert( text ); + + return idStr::icmp( m_data->data, text ); +} + +inline int idStr::icmp +( + const idStr& text +) const { + assert( c_str() ); + assert( text.c_str() ); + + return idStr::icmp( c_str(), text.c_str() ); +} + +inline int idStr::cmp +( + const char *text +) const { + assert( m_data ); + assert( text ); + + return idStr::cmp( m_data->data, text ); +} + +inline int idStr::cmp +( + const idStr& text +) const { + assert( c_str() ); + assert( text.c_str() ); + + return idStr::cmp( c_str(), text.c_str() ); +} + +inline int idStr::cmpn +( + const char *text, + int n +) const { + assert( c_str() ); + assert( text ); + + return idStr::cmpn( c_str(), text, n ); +} + +inline int idStr::cmpn +( + const idStr& text, + int n +) const { + assert( c_str() ); + assert( text.c_str() ); + + return idStr::cmpn( c_str(), text.c_str(), n ); +} + +inline void idStr::tolower +( + void +) { + assert( m_data ); + + EnsureDataWritable(); + + idStr::tolower( m_data->data ); +} + +inline void idStr::toupper +( + void +) { + assert( m_data ); + + EnsureDataWritable(); + + idStr::toupper( m_data->data ); +} + +inline bool idStr::isNumeric +( + void +) const { + assert( m_data ); + return idStr::isNumeric( m_data->data ); +} + +inline idStr::operator const char*() { + return c_str(); +} + +inline idStr::operator const char* +( + void +) const { + return c_str(); +} + +#endif \ No newline at end of file diff --git a/src/ui/keycodes.h b/src/ui/keycodes.h new file mode 100644 index 0000000..93d5654 --- /dev/null +++ b/src/ui/keycodes.h @@ -0,0 +1,169 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +# ifndef __KEYCODES_H__ +#define __KEYCODES_H__ + +// +// these are the key numbers that should be passed to KeyEvent +// + +// normal keys should be passed as lowercased ascii + +typedef enum { + K_TAB = 9, + K_ENTER = 13, + K_ESCAPE = 27, + K_SPACE = 32, + + K_BACKSPACE = 127, + + K_COMMAND = 128, + K_CAPSLOCK, + K_POWER, + K_PAUSE, + + K_UPARROW, + K_DOWNARROW, + K_LEFTARROW, + K_RIGHTARROW, + + K_ALT, + K_CTRL, + K_SHIFT, + K_INS, + K_DEL, + K_PGDN, + K_PGUP, + K_HOME, + K_END, + + K_F1, + K_F2, + K_F3, + K_F4, + K_F5, + K_F6, + K_F7, + K_F8, + K_F9, + K_F10, + K_F11, + K_F12, + K_F13, + K_F14, + K_F15, + + K_KP_HOME, + K_KP_UPARROW, + K_KP_PGUP, + K_KP_LEFTARROW, + K_KP_5, + K_KP_RIGHTARROW, + K_KP_END, + K_KP_DOWNARROW, + K_KP_PGDN, + K_KP_ENTER, + K_KP_INS, + K_KP_DEL, + K_KP_SLASH, + K_KP_MINUS, + K_KP_PLUS, + K_KP_NUMLOCK, + K_KP_STAR, + K_KP_EQUALS, + + K_MOUSE1, + K_MOUSE2, + K_MOUSE3, + K_MOUSE4, + K_MOUSE5, + + K_MWHEELDOWN, + K_MWHEELUP, + + K_JOY1, + K_JOY2, + K_JOY3, + K_JOY4, + K_JOY5, + K_JOY6, + K_JOY7, + K_JOY8, + K_JOY9, + K_JOY10, + K_JOY11, + K_JOY12, + K_JOY13, + K_JOY14, + K_JOY15, + K_JOY16, + K_JOY17, + K_JOY18, + K_JOY19, + K_JOY20, + K_JOY21, + K_JOY22, + K_JOY23, + K_JOY24, + K_JOY25, + K_JOY26, + K_JOY27, + K_JOY28, + K_JOY29, + K_JOY30, + K_JOY31, + K_JOY32, + + K_AUX1, + K_AUX2, + K_AUX3, + K_AUX4, + K_AUX5, + K_AUX6, + K_AUX7, + K_AUX8, + K_AUX9, + K_AUX10, + K_AUX11, + K_AUX12, + K_AUX13, + K_AUX14, + K_AUX15, + K_AUX16, + + K_LAST_KEY // this had better be <256! +} keyNum_t; + + +// The menu code needs to get both key and char events, but +// to avoid duplicating the paths, the char events are just +// distinguished by or'ing in K_CHAR_FLAG (ugly) +#define K_CHAR_FLAG 1024 + +#endif diff --git a/src/ui/ui.def b/src/ui/ui.def new file mode 100644 index 0000000..2ee748e --- /dev/null +++ b/src/ui/ui.def @@ -0,0 +1,3 @@ +EXPORTS + vmMain + dllEntry diff --git a/src/ui/ui.vcproj b/src/ui/ui.vcproj new file mode 100644 index 0000000..2048dfe --- /dev/null +++ b/src/ui/ui.vcproj @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/ui_atoms.c b/src/ui/ui_atoms.c new file mode 100644 index 0000000..fd0bbff --- /dev/null +++ b/src/ui/ui_atoms.c @@ -0,0 +1,555 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// Copyright (C) 1999-2000 Id Software, Inc. +// +/********************************************************************** + UI_ATOMS.C + + User interface building blocks and support functions. +**********************************************************************/ +#include "ui_local.h" + +uiStatic_t uis; +qboolean m_entersound; // after a frame, so caching won't disrupt the sound + +// these are here so the functions in q_shared.c can link +#ifndef UI_HARD_LINKED + + +// JPW NERVE added Com_DPrintf +#define MAXPRINTMSG 4096 +void QDECL Com_DPrintf( const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + int developer; + + developer = trap_Cvar_VariableValue( "developer" ); + if ( !developer ) { + return; + } + + va_start( argptr,fmt ); + Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ); + va_end( argptr ); + + Com_Printf( "%s", msg ); +} +// jpw + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, error ); + Q_vsnprintf( text, sizeof( text ), error, argptr ); + va_end( argptr ); + + trap_Error( va( "%s", text ) ); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); + va_end( argptr ); + + trap_Print( va( "%s", text ) ); +} + +#endif + +/* +================= +UI_ClampCvar +================= +*/ +float UI_ClampCvar( float min, float max, float value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +/* +================= +UI_StartDemoLoop +================= +*/ +void UI_StartDemoLoop( void ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" ); +} + +/* +// TTimo: unused +static void NeedCDAction( qboolean result ) { + if ( !result ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "quit\n" ); + } +} + +static void NeedCDKeyAction( qboolean result ) { + if ( !result ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "quit\n" ); + } +} +*/ + +char *UI_Argv( int arg ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +char *UI_Cvar_VariableString( const char *var_name ) { + static char buffer[2][MAX_STRING_CHARS]; + static int toggle; + + toggle ^= 1; // flip-flop to allow two returns without clash + + trap_Cvar_VariableStringBuffer( var_name, buffer[toggle], sizeof( buffer[0] ) ); + + return buffer[toggle]; +} + + +#ifdef MISSIONPACK +void UI_SetBestScores( postGameInfo_t *newInfo, qboolean postGame ) { + trap_Cvar_Set( "ui_scoreAccuracy", va( "%i%%", newInfo->accuracy ) ); + trap_Cvar_Set( "ui_scoreImpressives", va( "%i", newInfo->impressives ) ); + trap_Cvar_Set( "ui_scoreExcellents", va( "%i", newInfo->excellents ) ); + trap_Cvar_Set( "ui_scoreDefends", va( "%i", newInfo->defends ) ); + trap_Cvar_Set( "ui_scoreAssists", va( "%i", newInfo->assists ) ); + trap_Cvar_Set( "ui_scoreGauntlets", va( "%i", newInfo->gauntlets ) ); + trap_Cvar_Set( "ui_scoreScore", va( "%i", newInfo->score ) ); + trap_Cvar_Set( "ui_scorePerfect", va( "%i", newInfo->perfects ) ); + trap_Cvar_Set( "ui_scoreTeam", va( "%i to %i", newInfo->redScore, newInfo->blueScore ) ); + trap_Cvar_Set( "ui_scoreBase", va( "%i", newInfo->baseScore ) ); + trap_Cvar_Set( "ui_scoreTimeBonus", va( "%i", newInfo->timeBonus ) ); + trap_Cvar_Set( "ui_scoreSkillBonus", va( "%i", newInfo->skillBonus ) ); + trap_Cvar_Set( "ui_scoreShutoutBonus", va( "%i", newInfo->shutoutBonus ) ); + trap_Cvar_Set( "ui_scoreTime", va( "%02i:%02i", newInfo->time / 60, newInfo->time % 60 ) ); + trap_Cvar_Set( "ui_scoreCaptures", va( "%i", newInfo->captures ) ); + if ( postGame ) { + trap_Cvar_Set( "ui_scoreAccuracy2", va( "%i%%", newInfo->accuracy ) ); + trap_Cvar_Set( "ui_scoreImpressives2", va( "%i", newInfo->impressives ) ); + trap_Cvar_Set( "ui_scoreExcellents2", va( "%i", newInfo->excellents ) ); + trap_Cvar_Set( "ui_scoreDefends2", va( "%i", newInfo->defends ) ); + trap_Cvar_Set( "ui_scoreAssists2", va( "%i", newInfo->assists ) ); + trap_Cvar_Set( "ui_scoreGauntlets2", va( "%i", newInfo->gauntlets ) ); + trap_Cvar_Set( "ui_scoreScore2", va( "%i", newInfo->score ) ); + trap_Cvar_Set( "ui_scorePerfect2", va( "%i", newInfo->perfects ) ); + trap_Cvar_Set( "ui_scoreTeam2", va( "%i to %i", newInfo->redScore, newInfo->blueScore ) ); + trap_Cvar_Set( "ui_scoreBase2", va( "%i", newInfo->baseScore ) ); + trap_Cvar_Set( "ui_scoreTimeBonus2", va( "%i", newInfo->timeBonus ) ); + trap_Cvar_Set( "ui_scoreSkillBonus2", va( "%i", newInfo->skillBonus ) ); + trap_Cvar_Set( "ui_scoreShutoutBonus2", va( "%i", newInfo->shutoutBonus ) ); + trap_Cvar_Set( "ui_scoreTime2", va( "%02i:%02i", newInfo->time / 60, newInfo->time % 60 ) ); + trap_Cvar_Set( "ui_scoreCaptures2", va( "%i", newInfo->captures ) ); + } +} +#endif // #ifdef MISSIONPACK + +void UI_LoadBestScores( const char *map, int game ) { +#ifdef MISSIONPACK + char fileName[MAX_QPATH]; + fileHandle_t f; + postGameInfo_t newInfo; + memset( &newInfo, 0, sizeof( postGameInfo_t ) ); + Com_sprintf( fileName, MAX_QPATH, "games/%s_%i.game", map, game ); + if ( trap_FS_FOpenFile( fileName, &f, FS_READ ) >= 0 ) { + int size = 0; + trap_FS_Read( &size, sizeof( int ), f ); + if ( size == sizeof( postGameInfo_t ) ) { + trap_FS_Read( &newInfo, sizeof( postGameInfo_t ), f ); + } + trap_FS_FCloseFile( f ); + } + UI_SetBestScores( &newInfo, qfalse ); + + Com_sprintf( fileName, MAX_QPATH, "demos/%s_%d.dm_%d", map, game, (int)trap_Cvar_VariableValue( "protocol" ) ); + uiInfo.demoAvailable = qfalse; + if ( trap_FS_FOpenFile( fileName, &f, FS_READ ) >= 0 ) { + uiInfo.demoAvailable = qtrue; + trap_FS_FCloseFile( f ); + } +#endif // #ifdef MISSIONPACK +} + +/* +=============== +UI_ClearScores +=============== +*/ +void UI_ClearScores() { +#ifdef MISSIONPACK + char gameList[4096]; + char *gameFile; + int i, len, count, size; + fileHandle_t f; + postGameInfo_t newInfo; + + count = trap_FS_GetFileList( "games", "game", gameList, sizeof( gameList ) ); + + size = sizeof( postGameInfo_t ); + memset( &newInfo, 0, size ); + + if ( count > 0 ) { + gameFile = gameList; + for ( i = 0; i < count; i++ ) { + len = strlen( gameFile ); + if ( trap_FS_FOpenFile( va( "games/%s",gameFile ), &f, FS_WRITE ) >= 0 ) { + trap_FS_Write( &size, sizeof( int ), f ); + trap_FS_Write( &newInfo, size, f ); + trap_FS_FCloseFile( f ); + } + gameFile += len + 1; + } + } + + UI_SetBestScores( &newInfo, qfalse ); +#endif // #ifdef MISSIONPACK + +} + + + +static void UI_Cache_f() { + Display_CacheAll(); +} + +/* +======================= +UI_CalcPostGameStats +======================= +*/ +static void UI_CalcPostGameStats() { +#ifdef MISSIONPACK + char map[MAX_QPATH]; + char fileName[MAX_QPATH]; + char info[MAX_INFO_STRING]; + fileHandle_t f; + int size, game, time, adjustedTime; + postGameInfo_t oldInfo; + postGameInfo_t newInfo; + qboolean newHigh = qfalse; + + trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ); + Q_strncpyz( map, Info_ValueForKey( info, "mapname" ), sizeof( map ) ); + game = atoi( Info_ValueForKey( info, "g_gametype" ) ); + + // compose file name + Com_sprintf( fileName, MAX_QPATH, "games/%s_%i.game", map, game ); + // see if we have one already + memset( &oldInfo, 0, sizeof( postGameInfo_t ) ); + if ( trap_FS_FOpenFile( fileName, &f, FS_READ ) >= 0 ) { + // if so load it + size = 0; + trap_FS_Read( &size, sizeof( int ), f ); + if ( size == sizeof( postGameInfo_t ) ) { + trap_FS_Read( &oldInfo, sizeof( postGameInfo_t ), f ); + } + trap_FS_FCloseFile( f ); + } + + newInfo.accuracy = atoi( UI_Argv( 3 ) ); + newInfo.impressives = atoi( UI_Argv( 4 ) ); + newInfo.excellents = atoi( UI_Argv( 5 ) ); + newInfo.defends = atoi( UI_Argv( 6 ) ); + newInfo.assists = atoi( UI_Argv( 7 ) ); + newInfo.gauntlets = atoi( UI_Argv( 8 ) ); + newInfo.baseScore = atoi( UI_Argv( 9 ) ); + newInfo.perfects = atoi( UI_Argv( 10 ) ); + newInfo.redScore = atoi( UI_Argv( 11 ) ); + newInfo.blueScore = atoi( UI_Argv( 12 ) ); + time = atoi( UI_Argv( 13 ) ); + newInfo.captures = atoi( UI_Argv( 14 ) ); + + newInfo.time = ( time - trap_Cvar_VariableValue( "ui_matchStartTime" ) ) / 1000; + adjustedTime = uiInfo.mapList[ui_currentMap.integer].timeToBeat[game]; + if ( newInfo.time < adjustedTime ) { + newInfo.timeBonus = ( adjustedTime - newInfo.time ) * 10; + } else { + newInfo.timeBonus = 0; + } + + if ( newInfo.redScore > newInfo.blueScore && newInfo.blueScore <= 0 ) { + newInfo.shutoutBonus = 100; + } else { + newInfo.shutoutBonus = 0; + } + + newInfo.skillBonus = trap_Cvar_VariableValue( "g_spSkill" ); + if ( newInfo.skillBonus <= 0 ) { + newInfo.skillBonus = 1; + } + newInfo.score = newInfo.baseScore + newInfo.shutoutBonus + newInfo.timeBonus; + newInfo.score *= newInfo.skillBonus; + + // see if the score is higher for this one + newHigh = ( newInfo.redScore > newInfo.blueScore && newInfo.score > oldInfo.score ); + + if ( newHigh ) { + // if so write out the new one + uiInfo.newHighScoreTime = uiInfo.uiDC.realTime + 20000; + if ( trap_FS_FOpenFile( fileName, &f, FS_WRITE ) >= 0 ) { + size = sizeof( postGameInfo_t ); + trap_FS_Write( &size, sizeof( int ), f ); + trap_FS_Write( &newInfo, sizeof( postGameInfo_t ), f ); + trap_FS_FCloseFile( f ); + } + } + + if ( newInfo.time < oldInfo.time ) { + uiInfo.newBestTime = uiInfo.uiDC.realTime + 20000; + } + + // put back all the ui overrides + trap_Cvar_Set( "capturelimit", UI_Cvar_VariableString( "ui_saveCaptureLimit" ) ); + trap_Cvar_Set( "fraglimit", UI_Cvar_VariableString( "ui_saveFragLimit" ) ); + trap_Cvar_Set( "cg_drawTimer", UI_Cvar_VariableString( "ui_drawTimer" ) ); + trap_Cvar_Set( "g_doWarmup", UI_Cvar_VariableString( "ui_doWarmup" ) ); + trap_Cvar_Set( "g_Warmup", UI_Cvar_VariableString( "ui_Warmup" ) ); + trap_Cvar_Set( "sv_pure", UI_Cvar_VariableString( "ui_pure" ) ); + trap_Cvar_Set( "g_friendlyFire", UI_Cvar_VariableString( "ui_friendlyFire" ) ); + + UI_SetBestScores( &newInfo, qtrue ); + UI_ShowPostGame( newHigh ); + +#endif // #ifdef MISSIONPACK + +} + + +/* +================= +UI_ConsoleCommand +================= +*/ +qboolean UI_ConsoleCommand( int realTime ) { + char *cmd; + + uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime; + uiInfo.uiDC.realTime = realTime; + + cmd = UI_Argv( 0 ); + + // ensure minimum menu data is available + //Menu_Cache(); + + if ( Q_stricmp( cmd, "ui_test" ) == 0 ) { + UI_ShowPostGame( qtrue ); + } + + if ( Q_stricmp( cmd, "ui_report" ) == 0 ) { + UI_Report(); + return qtrue; + } + + if ( Q_stricmp( cmd, "ui_load" ) == 0 ) { + UI_Load(); + return qtrue; + } + + if ( Q_stricmp( cmd, "remapShader" ) == 0 ) { + if ( trap_Argc() == 4 ) { + char shader1[MAX_QPATH]; + char shader2[MAX_QPATH]; + Q_strncpyz( shader1, UI_Argv( 1 ), sizeof( shader1 ) ); + Q_strncpyz( shader2, UI_Argv( 2 ), sizeof( shader2 ) ); + trap_R_RemapShader( shader1, shader2, UI_Argv( 3 ) ); + return qtrue; + } + } + + if ( Q_stricmp( cmd, "postgame" ) == 0 ) { + UI_CalcPostGameStats(); + return qtrue; + } + + if ( Q_stricmp( cmd, "ui_cache" ) == 0 ) { + UI_Cache_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "ui_teamOrders" ) == 0 ) { + //UI_TeamOrdersMenu_f(); + return qtrue; + } + + + if ( Q_stricmp( cmd, "ui_cdkey" ) == 0 ) { + //UI_CDKeyMenu_f(); + return qtrue; + } + + return qfalse; +} + +/* +================= +UI_Shutdown +================= +*/ +void UI_Shutdown( void ) { +} + +/* +================ +UI_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void UI_AdjustFrom640( float *x, float *y, float *w, float *h ) { + // expect valid pointers +#if 0 + *x = *x * uiInfo.uiDC.scale + uiInfo.uiDC.bias; + *y *= uiInfo.uiDC.scale; + *w *= uiInfo.uiDC.scale; + *h *= uiInfo.uiDC.scale; +#endif + + *x *= uiInfo.uiDC.xscale; + *y *= uiInfo.uiDC.yscale; + *w *= uiInfo.uiDC.xscale; + *h *= uiInfo.uiDC.yscale; + +} + +void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { + qhandle_t hShader; + + hShader = trap_R_RegisterShaderNoMip( picname ); + UI_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ) { + float s0; + float s1; + float t0; + float t1; + + if ( w < 0 ) { // flip about vertical + w = -w; + s0 = 1; + s1 = 0; + } else { + s0 = 0; + s1 = 1; + } + + if ( h < 0 ) { // flip about horizontal + h = -h; + t0 = 1; + t1 = 0; + } else { + t0 = 0; + t1 = 1; + } + + UI_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, w, h, s0, t0, s1, t1, hShader ); +} + +/* +================ +UI_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void UI_FillRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + UI_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + + trap_R_SetColor( NULL ); +} + +void UI_DrawSides( float x, float y, float w, float h ) { + UI_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x + w - 1, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} + +void UI_DrawTopBottom( float x, float y, float w, float h ) { + UI_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x, y + h - 1, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void UI_DrawRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + UI_DrawTopBottom( x, y, width, height ); + UI_DrawSides( x, y, width, height ); + + trap_R_SetColor( NULL ); +} + +void UI_SetColor( const float *rgba ) { + trap_R_SetColor( rgba ); +} + +void UI_UpdateScreen( void ) { + trap_UpdateScreen(); +} + + +void UI_DrawTextBox( int x, int y, int width, int lines ) { + UI_FillRect( x + BIGCHAR_WIDTH / 2, y + BIGCHAR_HEIGHT / 2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorBlack ); + UI_DrawRect( x + BIGCHAR_WIDTH / 2, y + BIGCHAR_HEIGHT / 2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorWhite ); +} + +qboolean UI_CursorInRect( int x, int y, int width, int height ) { + if ( uiInfo.uiDC.cursorx < x || + uiInfo.uiDC.cursory < y || + uiInfo.uiDC.cursorx > x + width || + uiInfo.uiDC.cursory > y + height ) { + return qfalse; + } + + return qtrue; +} diff --git a/src/ui/ui_gameinfo.c b/src/ui/ui_gameinfo.c new file mode 100644 index 0000000..ccc1e11 --- /dev/null +++ b/src/ui/ui_gameinfo.c @@ -0,0 +1,369 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// Copyright (C) 1999-2000 Id Software, Inc. +// +// +// gameinfo.c +// + +#include "ui_local.h" + + +// +// arena and bot info +// + + +int ui_numBots; +static char *ui_botInfos[MAX_BOTS]; + +static int ui_numArenas; +static char *ui_arenaInfos[MAX_ARENAS]; + +//static int ui_numSinglePlayerArenas; // TTimo: unused +//static int ui_numSpecialSinglePlayerArenas; // TTimo: unused + +/* +=============== +UI_ParseInfos +=============== +*/ +int UI_ParseInfos( char *buf, int max, char *infos[] ) { + char *token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; + + count = 0; + + while ( 1 ) { + token = COM_Parse( &buf ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + info[0] = '\0'; + while ( 1 ) { + token = COM_ParseExt( &buf, qtrue ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = COM_ParseExt( &buf, qfalse ); + if ( !token[0] ) { + strcpy( token, "" ); + } + Info_SetValueForKey( info, key, token ); + } + //NOTE: extra space for arena number + infos[count] = UI_Alloc( strlen( info ) + strlen( "\\num\\" ) + strlen( va( "%d", MAX_ARENAS ) ) + 1 ); + if ( infos[count] ) { + strcpy( infos[count], info ); + count++; + } + } + return count; +} + +/* +=============== +UI_LoadArenasFromFile +=============== +*/ +static void UI_LoadArenasFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] ); +} + +/* +=============== +UI_LoadArenas +=============== +*/ +void UI_LoadArenas( void ) { + int numdirs; +// vmCvar_t arenasFile; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + char *type, *str; + + ui_numArenas = 0; + uiInfo.mapCount = 0; + +/* NERVE - SMF - commented out + trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM ); + if( *arenasFile.string ) { + UI_LoadArenasFromFile(arenasFile.string); + } + else { + UI_LoadArenasFromFile("scripts/arenas.txt"); + } +*/ + // get all arenas from .arena files + numdirs = trap_FS_GetFileList( "scripts", ".arena", dirlist, 1024 ); + dirptr = dirlist; + for ( i = 0; i < numdirs; i++, dirptr += dirlen + 1 ) { + dirlen = strlen( dirptr ); + strcpy( filename, "scripts/" ); + strcat( filename, dirptr ); + UI_LoadArenasFromFile( filename ); + } +// trap_DPrint( va( "%i arenas parsed\n", ui_numArenas ) ); // JPW NERVE pulled per atvi req + if ( UI_OutOfMemory() ) { + trap_Print( S_COLOR_YELLOW "WARNING: not anough memory in pool to load all arenas\n" ); + } + + for ( n = 0; n < ui_numArenas; n++ ) { + // determine type + + uiInfo.mapList[uiInfo.mapCount].cinematic = -1; + uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc( Info_ValueForKey( ui_arenaInfos[n], "map" ) ); + uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc( Info_ValueForKey( ui_arenaInfos[n], "longname" ) ); + uiInfo.mapList[uiInfo.mapCount].levelShot = -1; + uiInfo.mapList[uiInfo.mapCount].imageName = String_Alloc( va( "levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName ) ); + uiInfo.mapList[uiInfo.mapCount].typeBits = 0; + + // NERVE - SMF + // set timelimit + str = Info_ValueForKey( ui_arenaInfos[n], "Timelimit" ); + if ( *str ) { + uiInfo.mapList[uiInfo.mapCount].Timelimit = atoi( str ); + } else { + uiInfo.mapList[uiInfo.mapCount].Timelimit = 0; + } + + // set axis respawn time + str = Info_ValueForKey( ui_arenaInfos[n], "AxisRespawnTime" ); + if ( *str ) { + uiInfo.mapList[uiInfo.mapCount].AxisRespawnTime = atoi( str ); + } else { + uiInfo.mapList[uiInfo.mapCount].AxisRespawnTime = 0; + } + + // set allied respawn time + str = Info_ValueForKey( ui_arenaInfos[n], "AlliedRespawnTime" ); + if ( *str ) { + uiInfo.mapList[uiInfo.mapCount].AlliedRespawnTime = atoi( str ); + } else { + uiInfo.mapList[uiInfo.mapCount].AlliedRespawnTime = 0; + } + // -NERVE - SMF + + type = Info_ValueForKey( ui_arenaInfos[n], "type" ); + // if no type specified, it will be treated as "ffa" + if ( *type ) { + if ( strstr( type, "ffa" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_FFA ); + } + if ( strstr( type, "tourney" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_TOURNAMENT ); + } + if ( strstr( type, "ctf" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_CTF ); + } + // NERVE - SMF + if ( strstr( type, "wolfmp" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_WOLF ); + } + if ( strstr( type, "wolfsw" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_WOLF_STOPWATCH ); + } + if ( strstr( type, "wolfcp" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_WOLF_CP ); + } + // -NERVE - SMF +#ifdef MISSIONPACK + if ( strstr( type, "oneflag" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_1FCTF ); + } + if ( strstr( type, "overload" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_OBELISK ); + } + if ( strstr( type, "harvester" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_HARVESTER ); + } +#endif // #ifdef MISSIONPACK + } else { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << GT_FFA ); + } + + uiInfo.mapCount++; + if ( uiInfo.mapCount >= MAX_MAPS ) { + break; + } + } +} + + +/* +=============== +UI_LoadBotsFromFile +=============== +*/ +static void UI_LoadBotsFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress( buf ); + + ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] ); +} + +/* +=============== +UI_LoadBots +=============== +*/ +void UI_LoadBots( void ) { + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + ui_numBots = 0; + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT | CVAR_ROM ); + if ( *botsFile.string ) { + UI_LoadBotsFromFile( botsFile.string ); + } else { + UI_LoadBotsFromFile( "scripts/bots.txt" ); + } + + // get all bots from .bot files + numdirs = trap_FS_GetFileList( "scripts", ".bot", dirlist, 1024 ); + dirptr = dirlist; + for ( i = 0; i < numdirs; i++, dirptr += dirlen + 1 ) { + dirlen = strlen( dirptr ); + strcpy( filename, "scripts/" ); + strcat( filename, dirptr ); + UI_LoadBotsFromFile( filename ); + } + trap_Print( va( "%i bots parsed\n", ui_numBots ) ); +} + + +/* +=============== +UI_GetBotInfoByNumber +=============== +*/ +char *UI_GetBotInfoByNumber( int num ) { + if ( num < 0 || num >= ui_numBots ) { + trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return ui_botInfos[num]; +} + + +/* +=============== +UI_GetBotInfoByName +=============== +*/ +char *UI_GetBotInfoByName( const char *name ) { + int n; + char *value; + + for ( n = 0; n < ui_numBots ; n++ ) { + value = Info_ValueForKey( ui_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return ui_botInfos[n]; + } + } + + return NULL; +} + +int UI_GetNumBots() { + return ui_numBots; +} + + +char *UI_GetBotNameByNumber( int num ) { + char *info = UI_GetBotInfoByNumber( num ); + if ( info ) { + return Info_ValueForKey( info, "name" ); + } + return "Sarge"; +} diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h new file mode 100644 index 0000000..9b2c2c4 --- /dev/null +++ b/src/ui/ui_local.h @@ -0,0 +1,1166 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __UI_LOCAL_H__ +#define __UI_LOCAL_H__ + +#include "../game/q_shared.h" +#include "../cgame/tr_types.h" +#include "ui_public.h" +#include "keycodes.h" +#include "../game/bg_public.h" +#include "ui_shared.h" + +extern vmCvar_t ui_ffa_fraglimit; +extern vmCvar_t ui_ffa_timelimit; + +extern vmCvar_t ui_tourney_fraglimit; +extern vmCvar_t ui_tourney_timelimit; + +extern vmCvar_t ui_team_fraglimit; +extern vmCvar_t ui_team_timelimit; +extern vmCvar_t ui_team_friendly; + +extern vmCvar_t ui_ctf_capturelimit; +extern vmCvar_t ui_ctf_timelimit; +extern vmCvar_t ui_ctf_friendly; + +extern vmCvar_t ui_arenasFile; +extern vmCvar_t ui_botsFile; +extern vmCvar_t ui_spScores1; +extern vmCvar_t ui_spScores2; +extern vmCvar_t ui_spScores3; +extern vmCvar_t ui_spScores4; +extern vmCvar_t ui_spScores5; +extern vmCvar_t ui_spAwards; +extern vmCvar_t ui_spVideos; +extern vmCvar_t ui_spSkill; + +extern vmCvar_t ui_spSelection; +extern vmCvar_t ui_master; + +extern vmCvar_t ui_brassTime; +extern vmCvar_t ui_drawCrosshair; +extern vmCvar_t ui_drawCrosshairNames; +extern vmCvar_t ui_drawCrosshairPickups; //----(SA) added +extern vmCvar_t ui_marks; +// JOSEPH 12-3-99 +extern vmCvar_t ui_autoactivate; +extern vmCvar_t ui_emptyswitch; +// END JOSEPH + +extern vmCvar_t ui_server1; +extern vmCvar_t ui_server2; +extern vmCvar_t ui_server3; +extern vmCvar_t ui_server4; +extern vmCvar_t ui_server5; +extern vmCvar_t ui_server6; +extern vmCvar_t ui_server7; +extern vmCvar_t ui_server8; +extern vmCvar_t ui_server9; +extern vmCvar_t ui_server10; +extern vmCvar_t ui_server11; +extern vmCvar_t ui_server12; +extern vmCvar_t ui_server13; +extern vmCvar_t ui_server14; +extern vmCvar_t ui_server15; +extern vmCvar_t ui_server16; + +extern vmCvar_t ui_smallFont; +extern vmCvar_t ui_bigFont; +extern vmCvar_t ui_cdkey; +extern vmCvar_t ui_cdkeychecked; +extern vmCvar_t ui_selectedPlayer; +extern vmCvar_t ui_selectedPlayerName; +extern vmCvar_t ui_netSource; +extern vmCvar_t ui_menuFiles; +extern vmCvar_t ui_gameType; +extern vmCvar_t ui_netGameType; +extern vmCvar_t ui_actualNetGameType; +extern vmCvar_t ui_joinGameType; +extern vmCvar_t ui_dedicated; +extern vmCvar_t ui_notebookCurrentPage; +extern vmCvar_t ui_clipboardName; +extern vmCvar_t ui_hudAlpha; + +// NERVE - SMF - multiplayer cvars +extern vmCvar_t ui_serverFilterType; +extern vmCvar_t ui_currentNetMap; +extern vmCvar_t ui_currentMap; +extern vmCvar_t ui_mapIndex; + +extern vmCvar_t ui_browserMaster; +extern vmCvar_t ui_browserGameType; +extern vmCvar_t ui_browserSortKey; +extern vmCvar_t ui_browserShowFull; +extern vmCvar_t ui_browserShowEmpty; +extern vmCvar_t ui_browserShowFriendlyFire; +extern vmCvar_t ui_browserShowMaxlives; +extern vmCvar_t ui_browserShowTourney; +extern vmCvar_t ui_browserShowPunkBuster; +extern vmCvar_t ui_browserShowAntilag; + +extern vmCvar_t ui_serverStatusTimeOut; +extern vmCvar_t ui_limboOptions; + +extern vmCvar_t ui_isSpectator; +// -NERVE - SMF + +// +// ui_qmenu.c +// + +#define RCOLUMN_OFFSET ( BIGCHAR_WIDTH ) +#define LCOLUMN_OFFSET ( -BIGCHAR_WIDTH ) + +#define SLIDER_RANGE 10 +#define MAX_EDIT_LINE 256 + +#define MAX_MENUDEPTH 8 +#define MAX_MENUITEMS 128 // JPW NERVE put this back for MP +//#define MAX_MENUITEMS 256 + +#define MTYPE_NULL 0 +#define MTYPE_SLIDER 1 +#define MTYPE_ACTION 2 +#define MTYPE_SPINCONTROL 3 +#define MTYPE_FIELD 4 +#define MTYPE_RADIOBUTTON 5 +#define MTYPE_BITMAP 6 +#define MTYPE_TEXT 7 +#define MTYPE_SCROLLLIST 8 +#define MTYPE_PTEXT 9 +#define MTYPE_BTEXT 10 + +#define QMF_BLINK 0x00000001 +#define QMF_SMALLFONT 0x00000002 +#define QMF_LEFT_JUSTIFY 0x00000004 +#define QMF_CENTER_JUSTIFY 0x00000008 +#define QMF_RIGHT_JUSTIFY 0x00000010 +#define QMF_NUMBERSONLY 0x00000020 // edit field is only numbers +#define QMF_HIGHLIGHT 0x00000040 +#define QMF_HIGHLIGHT_IF_FOCUS 0x00000080 // steady focus +#define QMF_PULSEIFFOCUS 0x00000100 // pulse if focus +#define QMF_HASMOUSEFOCUS 0x00000200 +#define QMF_NOONOFFTEXT 0x00000400 +#define QMF_MOUSEONLY 0x00000800 // only mouse input allowed +#define QMF_HIDDEN 0x00001000 // skips drawing +#define QMF_GRAYED 0x00002000 // grays and disables +#define QMF_INACTIVE 0x00004000 // disables any input +#define QMF_NODEFAULTINIT 0x00008000 // skip default initialization +#define QMF_OWNERDRAW 0x00010000 +#define QMF_PULSE 0x00020000 +#define QMF_LOWERCASE 0x00040000 // edit field is all lower case +#define QMF_UPPERCASE 0x00080000 // edit field is all upper case +#define QMF_SILENT 0x00100000 + +// callback notifications +#define QM_GOTFOCUS 1 +#define QM_LOSTFOCUS 2 +#define QM_ACTIVATED 3 + +typedef struct _tag_menuframework +{ + int cursor; + int cursor_prev; + + int nitems; + void *items[MAX_MENUITEMS]; + + void ( *draw )( void ); + sfxHandle_t ( *key )( int key ); + + qboolean wrapAround; + qboolean fullscreen; + qboolean showlogo; + + // JOSEPH 11-9-99 + int specialmenutype; + // END JOSEPH +} menuframework_s; + +typedef struct +{ + int type; + const char *name; + int id; + int x, y; + int left; + int top; + int right; + int bottom; + menuframework_s *parent; + int menuPosition; + unsigned flags; + + void ( *callback )( void *self, int event ); + void ( *statusbar )( void *self ); + void ( *ownerdraw )( void *self ); +} menucommon_s; + +typedef struct { + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; + int maxchars; +} mfield_t; + +typedef struct +{ + menucommon_s generic; + mfield_t field; +} menufield_s; + +typedef struct +{ + menucommon_s generic; + + float minvalue; + float maxvalue; + float curvalue; + + float range; +} menuslider_s; + +typedef struct +{ + menucommon_s generic; + + int oldvalue; + int curvalue; + int numitems; + int top; + + const char **itemnames; + + int width; + int height; + int columns; + int seperation; +} menulist_s; + +typedef struct +{ + menucommon_s generic; +} menuaction_s; + +typedef struct +{ + menucommon_s generic; + int curvalue; +} menuradiobutton_s; + +typedef struct +{ + menucommon_s generic; + char* focuspic; + char* errorpic; + qhandle_t shader; + qhandle_t focusshader; + int width; + int height; + float* focuscolor; +} menubitmap_s; + +typedef struct +{ + menucommon_s generic; + char* string; + int style; + float* color; +} menutext_s; + +extern void Menu_Cache( void ); +extern void Menu_Focus( menucommon_s *m ); +extern void Menu_AddItem( menuframework_s *menu, void *item ); +extern void Menu_AdjustCursor( menuframework_s *menu, int dir ); +extern void Menu_Draw( menuframework_s *menu ); +// JOSEPH 11-9-99 +extern void Menu_Draw_Inactive( menuframework_s *menu ); +// END JOSEPH +extern void *Menu_ItemAtCursor( menuframework_s *m ); +extern sfxHandle_t Menu_ActivateItem( menuframework_s *s, menucommon_s* item ); +extern void Menu_SetCursor( menuframework_s *s, int cursor ); +extern void Menu_SetCursorToItem( menuframework_s *m, void* ptr ); +extern sfxHandle_t Menu_DefaultKey( menuframework_s *s, int key ); +extern void Bitmap_Init( menubitmap_s *b ); +extern void Bitmap_Draw( menubitmap_s *b ); +extern void ScrollList_Draw( menulist_s *l ); +// JOSEPH 11-23-99 +extern void ScrollList_Draw2( menulist_s *l ); +// END JOSEPH +extern sfxHandle_t ScrollList_Key( menulist_s *l, int key ); +extern sfxHandle_t menu_in_sound; +extern sfxHandle_t menu_move_sound; +extern sfxHandle_t menu_out_sound; +extern sfxHandle_t menu_buzz_sound; +extern sfxHandle_t menu_null_sound; +extern vec4_t menu_text_color; +extern vec4_t menu_grayed_color; +extern vec4_t menu_dark_color; +extern vec4_t menu_highlight_color; +extern vec4_t menu_red_color; +extern vec4_t menu_black_color; +extern vec4_t menu_dim_color; +extern vec4_t color_black; +// JOSEPH 11-29-99 +extern vec4_t color_halfblack; +// END JOSEPH +extern vec4_t color_white; +extern vec4_t color_yellow; +extern vec4_t color_blue; +extern vec4_t color_orange; +extern vec4_t color_red; +extern vec4_t color_dim; +extern vec4_t name_color; +extern vec4_t list_color; +extern vec4_t listbar_color; +// JOSEPH 11-23-99 +extern vec4_t listbar_color2; +// END JOSEPH +extern vec4_t text_color_disabled; +extern vec4_t text_color_normal; +extern vec4_t text_color_highlight; + +extern char *ui_medalNames[]; +extern char *ui_medalPicNames[]; +extern char *ui_medalSounds[]; + +// +// ui_mfield.c +// +extern void MField_Clear( mfield_t *edit ); +extern void MField_KeyDownEvent( mfield_t *edit, int key ); +extern void MField_CharEvent( mfield_t *edit, int ch ); +extern void MField_Draw( mfield_t *edit, int x, int y, int style, vec4_t color ); +// JOSEPH 11-23-99 +extern void MenuField_Draw2( menufield_s *f, int specialtype ); +// END JOSEPH +extern void MenuField_Init( menufield_s* m ); +extern void MenuField_Draw( menufield_s *f ); +extern sfxHandle_t MenuField_Key( menufield_s* m, int* key ); + +// +// ui_main.c +// +void UI_Report(); +void UI_Load(); +void UI_LoadMenus( const char *menuFile, qboolean reset ); +void _UI_SetActiveMenu( uiMenuCommand_t menu ); +uiMenuCommand_t _UI_GetActiveMenu( void ); +int UI_AdjustTimeByGame( int time ); +void UI_ShowPostGame( qboolean newHigh ); +void UI_ClearScores(); +void UI_LoadArenas( void ); + +// +// ui_menu.c +// +extern void MainMenu_Cache( void ); +extern void UI_MainMenu( void ); +extern void UI_RegisterCvars( void ); +extern void UI_UpdateCvars( void ); + +// +// ui_credits.c +// +extern void UI_CreditMenu( void ); + +// +// ui_ingame.c +// +extern void InGame_Cache( void ); +extern void UI_InGameMenu( void ); + +// +// ui_confirm.c +// +extern void ConfirmMenu_Cache( void ); +extern void UI_ConfirmMenu( const char *question, void ( *draw )( void ), void ( *action )( qboolean result ) ); + +// +// ui_setup.c +// +extern void UI_SetupMenu_Cache( void ); +extern void UI_SetupMenu( void ); + +// +// ui_team.c +// +extern void UI_TeamMainMenu( void ); +extern void TeamMain_Cache( void ); + +// +// ui_connect.c +// +extern void UI_DrawConnectScreen( qboolean overlay ); + +// +// ui_controls2.c +// +extern void UI_ControlsMenu( void ); +extern void Controls_Cache( void ); + +// +// ui_demo2.c +// +extern void UI_DemosMenu( void ); +extern void Demos_Cache( void ); + +// +// ui_cinematics.c +// +extern void UI_CinematicsMenu( void ); +extern void UI_CinematicsMenu_f( void ); +extern void UI_CinematicsMenu_Cache( void ); + +// +// ui_cdkey.c +// +extern void UI_CDKeyMenu( void ); +extern void UI_CDKeyMenu_Cache( void ); +extern void UI_CDKeyMenu_f( void ); + +// +// ui_playermodel.c +// +extern void UI_PlayerModelMenu( void ); +extern void PlayerModel_Cache( void ); + +// +// ui_playersettings.c +// +extern void UI_PlayerSettingsMenu( void ); +extern void PlayerSettings_Cache( void ); + +// +// ui_preferences.c +// +extern void UI_PreferencesMenu( void ); +extern void Preferences_Cache( void ); + +// +// ui_specifyserver.c +// +extern void UI_SpecifyServerMenu( void ); +extern void SpecifyServer_Cache( void ); + +// +// ui_servers2.c +// +#define MAX_FAVORITESERVERS 16 + +extern void UI_ArenaServersMenu( void ); +extern void ArenaServers_Cache( void ); + +// +// ui_startserver.c +// +extern void UI_StartServerMenu( qboolean multiplayer ); +extern void StartServer_Cache( void ); +extern void ServerOptions_Cache( void ); +extern void UI_BotSelectMenu( char *bot ); +extern void UI_BotSelectMenu_Cache( void ); + +// +// ui_serverinfo.c +// +extern void UI_ServerInfoMenu( void ); +extern void ServerInfo_Cache( void ); + +// +// ui_video.c +// +extern void UI_GraphicsOptionsMenu( void ); +extern void GraphicsOptions_Cache( void ); +extern void DriverInfo_Cache( void ); + +// +// ui_players.c +// + +//FIXME ripped from cg_local.h +typedef struct { + int oldFrame; + int oldFrameTime; // time when ->oldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + int animationNumber; // may include ANIM_TOGGLEBIT + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact +} lerpFrame_t; + +typedef struct { + // model info + qhandle_t legsModel; + qhandle_t legsSkin; + lerpFrame_t legs; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + lerpFrame_t torso; + + qhandle_t headModel; + qhandle_t headSkin; + + animation_t animations[MAX_ANIMATIONS]; + + qhandle_t weaponModel; + qhandle_t barrelModel; + qhandle_t flashModel; + vec3_t flashDlightColor; + int muzzleFlashTime; + + // currently in use drawing parms + vec3_t viewAngles; + vec3_t moveAngles; + weapon_t currentWeapon; + int legsAnim; + int torsoAnim; + + // animation vars + weapon_t weapon; + weapon_t lastWeapon; + weapon_t pendingWeapon; + int weaponTimer; + int pendingLegsAnim; + int torsoAnimationTimer; + + int pendingTorsoAnim; + int legsAnimationTimer; + + qboolean chat; + qboolean newModel; + + qboolean barrelSpinning; + float barrelAngle; + int barrelTime; + + int realWeapon; + + // NERVE - SMF - added fields so it will work with wolf's skeletal animation system + // parsed from the start of the cfg file + gender_t gender; + footstep_t footsteps; + vec3_t headOffset; + int version; + qboolean isSkeletal; + int numAnimations; + + qhandle_t backpackModel; + qhandle_t helmetModel; + // -NERVE - SMF +} playerInfo_t; + +void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ); +void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model ); +void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNum, qboolean chat ); +qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName ); + +// +// ui_atoms.c +// +typedef struct { + int frametime; + int realtime; + int cursorx; + int cursory; + int menusp; + menuframework_s* activemenu; + menuframework_s* stack[MAX_MENUDEPTH]; + glconfig_t glconfig; + qboolean debug; + qhandle_t whiteShader; + qhandle_t menuBackShader; + qhandle_t menuBackNoLogoShader; + qhandle_t charset; + qhandle_t charsetProp; + qhandle_t charsetPropGlow; + qhandle_t charsetPropB; + qhandle_t cursor; + qhandle_t rb_on; + qhandle_t rb_off; + // JOSEPH 11-9-99 + qhandle_t menu; + qhandle_t menu1a; + qhandle_t menu1b; + qhandle_t menu2a; + qhandle_t menu2b; + qhandle_t menuchars; + // END JOSEPH + float scale; + float bias; + qboolean demoversion; + qboolean firstdraw; +} uiStatic_t; + +// new ui stuff +#define UI_NUMFX 7 +#define MAX_HEADS 64 +#define MAX_ALIASES 64 +#define MAX_HEADNAME 32 +#define MAX_TEAMS 64 +#define MAX_GAMETYPES 16 +#define MAX_MAPS 128 +#define MAX_SPMAPS 16 +#define PLAYERS_PER_TEAM 5 +#define MAX_PINGREQUESTS 16 +#define MAX_ADDRESSLENGTH 64 +#define MAX_HOSTNAMELENGTH 22 +#define MAX_MAPNAMELENGTH 16 +#define MAX_STATUSLENGTH 64 +#define MAX_LISTBOXWIDTH 59 +#define UI_FONT_THRESHOLD 0.1 +#define MAX_DISPLAY_SERVERS 2048 +#define MAX_SERVERSTATUS_LINES 128 +#define MAX_SERVERSTATUS_TEXT 1024 +#define MAX_FOUNDPLAYER_SERVERS 16 +#define TEAM_MEMBERS 5 +#define GAMES_ALL 0 +#define GAMES_FFA 1 +#define GAMES_TEAMPLAY 2 +#define GAMES_TOURNEY 3 +#define GAMES_CTF 4 +#define MAPS_PER_TIER 3 +#define MAX_TIERS 16 +#define MAX_MODS 64 +#define MAX_DEMOS 256 +#define MAX_MOVIES 256 +#define MAX_PLAYERMODELS 256 +#define MAX_SAVEGAMES 256 +#define MAX_SPAWNPOINTS 128 // NERVE - SMF +#define MAX_SPAWNDESC 128 // NERVE - SMF +#define MAX_PBLINES 128 // DHM - Nerve +#define MAX_PBWIDTH 42 // DHM - Nerve + +typedef struct { + const char *name; + const char *imageName; + qhandle_t headImage; + qboolean female; +} characterInfo; + +//----(SA) added +typedef struct { + const char *name; + qhandle_t sshotImage; +} savegameInfo; +//----(SA) end + +typedef struct { + const char *name; + const char *ai; + const char *action; +} aliasInfo; + +typedef struct { + const char *teamName; + const char *imageName; + const char *teamMembers[TEAM_MEMBERS]; + qhandle_t teamIcon; + qhandle_t teamIcon_Metal; + qhandle_t teamIcon_Name; + int cinematic; +} teamInfo; + +typedef struct { + const char *gameType; + int gtEnum; +} gameTypeInfo; + +typedef struct { + const char *mapName; + const char *mapLoadName; + const char *imageName; + const char *opponentName; + + int teamMembers; + int typeBits; + int cinematic; + int timeToBeat[MAX_GAMETYPES]; + + qhandle_t levelShot; + qboolean active; + + // NERVE - SMF + int Timelimit; + int AxisRespawnTime; + int AlliedRespawnTime; + // -NERVE - SMF +} mapInfo; + +typedef struct { + const char *tierName; + const char *maps[MAPS_PER_TIER]; + int gameTypes[MAPS_PER_TIER]; + qhandle_t mapHandles[MAPS_PER_TIER]; +} tierInfo; + +typedef struct serverFilter_s { + const char *description; + const char *basedir; +} serverFilter_t; + +typedef struct { + char adrstr[MAX_ADDRESSLENGTH]; + int start; +} pinglist_t; + + +typedef struct serverStatus_s { + pinglist_t pingList[MAX_PINGREQUESTS]; + int numqueriedservers; + int currentping; + int nextpingtime; + int maxservers; + int refreshtime; + int numServers; + int sortKey; + int sortDir; + int lastCount; + qboolean refreshActive; + int currentServer; + int displayServers[MAX_DISPLAY_SERVERS]; + int numDisplayServers; + int numPlayersOnServers; + int nextDisplayRefresh; + int nextSortTime; + qhandle_t currentServerPreview; + int currentServerCinematic; + int motdLen; + int motdWidth; + int motdPaintX; + int motdPaintX2; + int motdOffset; + int motdTime; + char motd[MAX_STRING_CHARS]; +} serverStatus_t; + + +typedef struct { + char adrstr[MAX_ADDRESSLENGTH]; + char name[MAX_ADDRESSLENGTH]; + int startTime; + int serverNum; + qboolean valid; +} pendingServer_t; + +typedef struct { + int num; + pendingServer_t server[MAX_SERVERSTATUSREQUESTS]; +} pendingServerStatus_t; + +typedef struct { + char address[MAX_ADDRESSLENGTH]; + char *lines[MAX_SERVERSTATUS_LINES][4]; + char text[MAX_SERVERSTATUS_TEXT]; + char pings[MAX_CLIENTS * 3]; + int numLines; +} serverStatusInfo_t; + +typedef struct { + const char *modName; + const char *modDescr; +} modInfo_t; + +typedef struct { + displayContextDef_t uiDC; + int newHighScoreTime; + int newBestTime; + int showPostGameTime; + qboolean newHighScore; + qboolean demoAvailable; + qboolean soundHighScore; + + int characterCount; + int botIndex; + characterInfo characterList[MAX_HEADS]; + + int aliasCount; + aliasInfo aliasList[MAX_ALIASES]; + + int teamCount; + teamInfo teamList[MAX_TEAMS]; + + int numGameTypes; + gameTypeInfo gameTypes[MAX_GAMETYPES]; + + int numJoinGameTypes; + gameTypeInfo joinGameTypes[MAX_GAMETYPES]; + + int redBlue; + int playerCount; + int myTeamCount; + int teamIndex; + int playerRefresh; + int playerIndex; + int playerNumber; + qboolean teamLeader; + char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + int teamClientNums[MAX_CLIENTS]; + + int mapCount; + mapInfo mapList[MAX_MAPS]; + + + int tierCount; + tierInfo tierList[MAX_TIERS]; + + int skillIndex; + + modInfo_t modList[MAX_MODS]; + int modCount; + int modIndex; + + const char *demoList[MAX_DEMOS]; + int demoCount; + int demoIndex; + + const char *movieList[MAX_MOVIES]; + int movieCount; + int movieIndex; + int previewMovie; + +//----(SA) added +// const char *savegameList[MAX_SAVEGAMES]; + savegameInfo savegameList[MAX_SAVEGAMES]; + int savegameCount; + int savegameIndex; +//----(SA) end + + serverStatus_t serverStatus; + + // for the showing the status of a server + char serverStatusAddress[MAX_ADDRESSLENGTH]; + serverStatusInfo_t serverStatusInfo; + int nextServerStatusRefresh; + + // to retrieve the status of server to find a player + pendingServerStatus_t pendingServerStatus; + char findPlayerName[MAX_STRING_CHARS]; + char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; + char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; + int currentFoundPlayerServer; + int numFoundPlayerServers; + int nextFindPlayerRefresh; + + int currentCrosshair; + int startPostGameTime; + sfxHandle_t newHighScoreSound; + + int q3HeadCount; + char q3HeadNames[MAX_PLAYERMODELS][64]; + qhandle_t q3HeadIcons[MAX_PLAYERMODELS]; + int q3SelectedHead; + + int effectsColor; + + qboolean inGameLoad; + + // NERVE - SMF + char spawnPoints[MAX_SPAWNPOINTS][MAX_SPAWNDESC]; + int spawnCount; + + int selectedObjective; + + int activeFont; + // -NERVE - SMF +} uiInfo_t; + +extern uiInfo_t uiInfo; + + +extern void UI_Init( void ); +extern void UI_Shutdown( void ); +extern void UI_KeyEvent( int key ); +extern void UI_MouseEvent( int dx, int dy ); +extern void UI_Refresh( int realtime ); +extern qboolean UI_ConsoleCommand( int realTime ); +extern float UI_ClampCvar( float min, float max, float value ); +extern void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ); +extern void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ); +extern void UI_FillRect( float x, float y, float width, float height, const float *color ); +extern void UI_DrawRect( float x, float y, float width, float height, const float *color ); +extern void UI_DrawTopBottom( float x, float y, float w, float h ); +extern void UI_DrawSides( float x, float y, float w, float h ); +extern void UI_UpdateScreen( void ); +extern void UI_SetColor( const float *rgba ); +extern void UI_LerpColor( vec4_t a, vec4_t b, vec4_t c, float t ); +extern void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ); +extern float UI_ProportionalSizeScale( int style ); +extern void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); +extern int UI_ProportionalStringWidth( const char* str ); +extern void UI_DrawString( int x, int y, const char* str, int style, vec4_t color ); +extern void UI_DrawChar( int x, int y, int ch, int style, vec4_t color ); +extern qboolean UI_CursorInRect( int x, int y, int width, int height ); +extern void UI_AdjustFrom640( float *x, float *y, float *w, float *h ); +extern void UI_DrawTextBox( int x, int y, int width, int lines ); +extern qboolean UI_IsFullscreen( void ); +extern void UI_SetActiveMenu( uiMenuCommand_t menu ); +extern void UI_PushMenu( menuframework_s *menu ); +extern void UI_PopMenu( void ); +extern void UI_ForceMenuOff( void ); +extern char *UI_Argv( int arg ); +extern char *UI_Cvar_VariableString( const char *var_name ); +extern void UI_Refresh( int time ); +extern void UI_KeyEvent( int key ); +extern void UI_StartDemoLoop( void ); +void UI_LoadBestScores( const char *map, int game ); // NERVE - SMF +extern qboolean m_entersound; +extern uiStatic_t uis; + +// +// ui_spLevel.c +// +void UI_SPLevelMenu_Cache( void ); +void UI_SPLevelMenu( void ); +void UI_SPLevelMenu_f( void ); +void UI_SPLevelMenu_ReInit( void ); + +// +// ui_spArena.c +// +void UI_SPArena_Start( const char *arenaInfo ); + +// +// ui_spPostgame.c +// +void UI_SPPostgameMenu_Cache( void ); +void UI_SPPostgameMenu_f( void ); + +// +// ui_spSkill.c +// +void UI_SPSkillMenu( const char *arenaInfo ); +void UI_SPSkillMenu_Cache( void ); + +// +// ui_syscalls.c +// +void trap_Print( const char *string ); +void trap_Error( const char *string ); +int trap_Milliseconds( void ); +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +float trap_Cvar_VariableValue( const char *var_name ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +void trap_Cvar_SetValue( const char *var_name, float value ); +void trap_Cvar_Reset( const char *name ); +void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ); +void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ); +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Cmd_ExecuteText( int exec_when, const char *text ); // don't use EXEC_NOW! +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int trap_FS_Delete( const char *filename ); +qhandle_t trap_R_RegisterModel( const char *name ); +qhandle_t trap_R_RegisterSkin( const char *name ); +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); +void trap_R_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b, int overdraw ); +void trap_R_AddCoronaToScene( const vec3_t org, float r, float g, float b, float scale, int id, qboolean visible ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); +void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +void trap_UpdateScreen( void ); +int trap_CM_LerpTag( orientation_t *tag, const refEntity_t *refent, const char *tagName, int startIndex ); +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +sfxHandle_t trap_S_RegisterSound( const char *sample ); +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ); +void trap_Key_SetBinding( int keynum, const char *binding ); +qboolean trap_Key_IsDown( int keynum ); +qboolean trap_Key_GetOverstrikeMode( void ); +void trap_Key_SetOverstrikeMode( qboolean state ); +void trap_Key_ClearStates( void ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +void trap_GetClipboardData( char *buf, int bufsize ); +void trap_GetClientState( uiClientState_t *state ); +void trap_GetGlconfig( glconfig_t *glconfig ); +int trap_GetConfigString( int index, char* buff, int buffsize ); +int trap_LAN_GetServerCount( int source ); // NERVE - SMF +int trap_LAN_GetLocalServerCount( void ); +void trap_LAN_GetLocalServerAddressString( int n, char *buf, int buflen ); +int trap_LAN_GetGlobalServerCount( void ); +void trap_LAN_GetGlobalServerAddressString( int n, char *buf, int buflen ); +int trap_LAN_GetPingQueueCount( void ); +void trap_LAN_ClearPing( int n ); +void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ); +void trap_LAN_GetPingInfo( int n, char *buf, int buflen ); +int trap_MemoryRemaining( void ); + +// NERVE - SMF - multiplayer traps +qboolean trap_LAN_UpdateVisiblePings( int source ); +void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ); +void trap_LAN_ResetPings( int n ); +void trap_LAN_SaveCachedServers(); +int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ); +void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ); +void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ); +int trap_LAN_AddServer( int source, const char *name, const char *addr ); +void trap_LAN_RemoveServer( int source, const char *addr ); +int trap_LAN_GetServerPing( int source, int n ); +int trap_LAN_ServerIsVisible( int source, int n ); +int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ); +void trap_LAN_SaveCachedServers(); +void trap_LAN_LoadCachedServers(); + +void trap_SetPbClStatus( int status ); // DHM - Nerve +void trap_SetPbSvStatus( int status ); // TTimo + + +// -NERVE - SMF + +void trap_GetCDKey( char *buf, int buflen ); +void trap_SetCDKey( char *buf ); +void trap_R_RegisterFont( const char *pFontname, int pointSize, fontInfo_t *font ); +void trap_S_StopBackgroundTrack( void ); +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ); +e_status trap_CIN_StopCinematic( int handle ); +e_status trap_CIN_RunCinematic( int handle ); +void trap_CIN_DrawCinematic( int handle ); +void trap_CIN_SetExtents( int handle, int x, int y, int w, int h ); +int trap_RealTime( qtime_t *qtime ); +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); +qboolean trap_VerifyCDKey( const char *key, const char *chksum ); +qboolean trap_GetLimboString( int index, char *buf ); // NERVE - SMF +void trap_CheckAutoUpdate( void ); // DHM - Nerve +void trap_GetAutoUpdate( void ); // DHM - Nerve + +void trap_openURL( const char *url ); // TTimo + +char* trap_TranslateString( const char *string ); // NERVE - SMF - localization +// +// ui_addbots.c +// +void UI_AddBots_Cache( void ); +void UI_AddBotsMenu( void ); + +// +// ui_removebots.c +// +void UI_RemoveBots_Cache( void ); +void UI_RemoveBotsMenu( void ); + +// +// ui_teamorders.c +// +extern void UI_TeamOrdersMenu( void ); +extern void UI_TeamOrdersMenu_f( void ); +extern void UI_TeamOrdersMenu_Cache( void ); + +// +// ui_loadconfig.c +// +void UI_LoadConfig_Cache( void ); +void UI_LoadConfigMenu( void ); + +// +// ui_saveconfig.c +// +void UI_SaveConfigMenu_Cache( void ); +void UI_SaveConfigMenu( void ); + +// +// ui_display.c +// +void UI_DisplayOptionsMenu_Cache( void ); +void UI_DisplayOptionsMenu( void ); + +// +// ui_sound.c +// +void UI_SoundOptionsMenu_Cache( void ); +void UI_SoundOptionsMenu( void ); + +// +// ui_network.c +// +void UI_NetworkOptionsMenu_Cache( void ); +void UI_NetworkOptionsMenu( void ); + +// +// ui_gameinfo.c +// +typedef enum { + AWARD_ACCURACY, + AWARD_IMPRESSIVE, + AWARD_EXCELLENT, + AWARD_GAUNTLET, + AWARD_FRAGS, + AWARD_PERFECT +} awardType_t; + +const char *UI_GetArenaInfoByNumber( int num ); +const char *UI_GetArenaInfoByMap( const char *map ); +const char *UI_GetSpecialArenaInfo( const char *tag ); +int UI_GetNumArenas( void ); +int UI_GetNumSPArenas( void ); +int UI_GetNumSPTiers( void ); + +char *UI_GetBotInfoByNumber( int num ); +char *UI_GetBotInfoByName( const char *name ); +int UI_GetNumBots( void ); + +void UI_GetBestScore( int level, int *score, int *skill ); +void UI_SetBestScore( int level, int score ); +int UI_TierCompleted( int levelWon ); +qboolean UI_ShowTierVideo( int tier ); +qboolean UI_CanShowTierVideo( int tier ); +int UI_GetCurrentGame( void ); +void UI_NewGame( void ); +void UI_LogAwardData( int award, int data ); +int UI_GetAwardLevel( int award ); + +void UI_SPUnlock_f( void ); +void UI_SPUnlockMedals_f( void ); + +void UI_InitGameinfo( void ); + +#endif diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c new file mode 100644 index 0000000..71c0263 --- /dev/null +++ b/src/ui/ui_main.c @@ -0,0 +1,7780 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + + + + +/* +======================================================================= + +USER INTERFACE MAIN + +======================================================================= +*/ + +#include "ui_local.h" + +// NERVE - SMF +#define AXIS_TEAM 0 +#define ALLIES_TEAM 1 +#define SPECT_TEAM 2 +// -NERVE - SMF + +extern qboolean g_waitingForKey; +extern qboolean g_editingField; +extern itemDef_t *g_editItem; + +uiInfo_t uiInfo; + +static const char *MonthAbbrev[] = { + "Jan","Feb","Mar", + "Apr","May","Jun", + "Jul","Aug","Sep", + "Oct","Nov","Dec" +}; + + +static const char *skillLevels[] = { + "I Can Win", + "Bring It On", + "Hurt Me Plenty", + "Hardcore", + "Nightmare" +}; + +static const int numSkillLevels = sizeof( skillLevels ) / sizeof( const char* ); + + +static const char *netSources[] = { + "Local", + "Internet", + "Favorites" +// "Mplayer" // NERVE - SMF +}; +static const int numNetSources = sizeof( netSources ) / sizeof( const char* ); + +static const serverFilter_t serverFilters[] = { + {"All", "" } +// {"Quake 3 Arena", "" }, +// {"Team Arena", "missionpack" }, +// {"Rocket Arena", "arena" }, +// {"Alliance", "alliance" }, +}; + +static const char *teamArenaGameTypes[] = { + "FFA", + "TOURNAMENT", + "SP", + "TEAM DM", + "CTF", + "WOLF MP", // NERVE - SMF + "WOLF SW", // NERVE - SMF + "WOLF CP", // NERVE - SMF + "TEAMTOURNAMENT" +}; + +static int const numTeamArenaGameTypes = sizeof( teamArenaGameTypes ) / sizeof( const char* ); + + +static const char *teamArenaGameNames[] = { + "Free For All", + "Tournament", + "Single Player", + "Team Deathmatch", + "Capture the Flag", + "Wolf Multiplayer", + "Wolf Stopwatch", + "Wolf Checkpoint" +}; + +static int const numTeamArenaGameNames = sizeof( teamArenaGameNames ) / sizeof( const char* ); + + +static const int numServerFilters = sizeof( serverFilters ) / sizeof( serverFilter_t ); + +static const char *sortKeys[] = { + "Server Name", + "Map Name", + "Open Player Spots", + "Game Type", + "Ping Time" +}; +static const int numSortKeys = sizeof( sortKeys ) / sizeof( const char* ); + +static char* netnames[] = { + "???", + "UDP", + "IPX", + NULL +}; + +//static char quake3worldMessage[] = "Visit www.quake3world.com - News, Community, Events, Files"; // TTimo: unused + +static int gamecodetoui[] = {4,2,3,0,5,1,6}; +static int uitogamecode[] = {4,6,2,3,1,5,7}; + + +// NERVE - SMF - enabled for multiplayer +static void UI_StartServerRefresh( qboolean full ); +static void UI_StopServerRefresh( void ); +static void UI_DoServerRefresh( void ); +static void UI_FeederSelection( float feederID, int index ); +static void UI_BuildServerDisplayList( qboolean force ); +static void UI_BuildServerStatus( qboolean force ); +static void UI_BuildFindPlayerList( qboolean force ); +static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ); +static int UI_MapCountByGameType( qboolean singlePlayer ); +static const char *UI_SelectedMap( int index, int *actual ); +static int UI_GetIndexFromSelection( int actual ); + +qboolean UI_CheckExecKey( int key ); +// -NERVE - SMF - enabled for multiplayer + +static void UI_ParseGameInfo( const char *teamFile ); +//static void UI_ParseTeamInfo(const char *teamFile); // TTimo: unused + +//int ProcessNewUI( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6 ); + +itemDef_t *Menu_FindItemByName( menuDef_t *menu, const char *p ); +void Menu_ShowItemByName( menuDef_t *menu, const char *p, qboolean bShow ); + +#define ITEM_GRENADES 1 +#define ITEM_MEDKIT 2 + +#define ITEM_PISTOL 1 + +#define DEFAULT_PISTOL + +#define PT_KNIFE ( 1 ) +#define PT_PISTOL ( 1 << 2 ) +#define PT_RIFLE ( 1 << 3 ) +#define PT_LIGHTONLY ( 1 << 4 ) +#define PT_GRENADES ( 1 << 5 ) +#define PT_EXPLOSIVES ( 1 << 6 ) +#define PT_MEDKIT ( 1 << 7 ) + +typedef struct { + const char *name; + int items; +} playerType_t; + +static playerType_t playerTypes[] = { + { "player_window_soldier", PT_KNIFE | PT_PISTOL | PT_RIFLE | PT_GRENADES }, + { "player_window_medic", PT_KNIFE | PT_PISTOL | PT_MEDKIT }, + { "player_window_engineer", PT_KNIFE | PT_PISTOL | PT_LIGHTONLY | PT_EXPLOSIVES | PT_GRENADES }, + { "player_window_lieutenant", PT_KNIFE | PT_PISTOL | PT_RIFLE | PT_EXPLOSIVES } +}; + +// TTimo +static char translated_yes[4], translated_no[4]; + +typedef struct { + int weapindex; + + const char *desc; + int flags; + const char *cvar; + int value; + const char *name; + + const char *torso_anim; + const char *legs_anim; + + const char *large_shader; +} weaponType_t; + +// NERVE - SMF - this is the weapon info list [what can and can't be used by character classes] +// - This list is seperate from the actual text names in the listboxes for localization purposes. +// - The list boxes look up this list by the cvar value. +static weaponType_t weaponTypes[] = { + { 0, "NULL", 0, "none", 0, "none", "", "", "" }, + + { WP_COLT, "1911 pistol", PT_PISTOL, "mp_weapon", 0, "ui_mp/assets/weapon_colt1911.tga", "firing_pistolB_1", "stand_pistolB", "" }, + { WP_LUGER, "Luger pistol", PT_PISTOL, "mp_weapon", 1, "ui_mp/assets/weapon_luger.tga", "firing_pistolB_1", "stand_pistolB", "" }, + + { WP_MP40, "MP 40", PT_LIGHTONLY | PT_RIFLE, "mp_weapon", 3, "ui_mp/assets/weapon_mp40.tga", "relaxed_idle_2h_1", "relaxed_idle_2h_1", "limbo_mp40" }, + { WP_THOMPSON, "Thompson", PT_LIGHTONLY | PT_RIFLE, "mp_weapon", 4, "ui_mp/assets/weapon_thompson.tga", "relaxed_idle_2h_1", "relaxed_idle_2h_1", "limbo_thompson" }, + { WP_STEN, "Sten", PT_LIGHTONLY | PT_RIFLE, "mp_weapon", 5, "ui_mp/assets/weapon_sten.tga", "relaxed_idle_2h_1", "relaxed_idle_2h_1", "limbo_sten" }, + + { WP_MAUSER, "Mauser", PT_RIFLE, "mp_weapon", 6, "ui_mp/assets/weapon_mauser.tga", "stand_rifle", "stand_rifle", "limbo_mauser" }, + { WP_PANZERFAUST, "Panzerfaust", PT_RIFLE, "mp_weapon", 8, "ui_mp/assets/weapon_panzerfaust.tga", "stand_panzer", "stand_panzer", "limbo_panzer" }, + { WP_VENOM, "Venom", PT_RIFLE, "mp_weapon", 9, "ui_mp/assets/weapon_venom.tga", "stand_machinegun", "stand_machinegun", "limbo_venom" }, + { WP_FLAMETHROWER, "Flamethrower", PT_RIFLE, "mp_weapon", 10, "ui_mp/assets/weapon_flamethrower.tga","stand_machinegun", "stand_machinegun", "limbo_flame" }, + + { WP_GRENADE_PINEAPPLE, "Pineapple grenade", PT_GRENADES, "mp_item1", 11, "ui_mp/assets/weapon_grenade.tga", "firing_pistolB_1", "stand_pistolB", "" }, + { WP_GRENADE_LAUNCHER, "Stick grenade", PT_GRENADES, "mp_item1", 12, "ui_mp/assets/weapon_grenade_ger.tga", "firing_pistolB_1", "stand_pistolB", "" }, + + { WP_DYNAMITE, "Explosives", PT_EXPLOSIVES, "mp_item2", 13, "ui_mp/assets/weapon_dynamite.tga", "firing_pistolB_1", "stand_pistolB", "" }, + + { 0, NULL, 0, NULL, 0, NULL, NULL, NULL } +}; + +typedef struct { + char *name; + int flags; + char *shader; +} uiitemType_t; + +#define UI_KNIFE_PIC "window_knife_pic" +#define UI_PISTOL_PIC "window_pistol_pic" +#define UI_WEAPON_PIC "window_weapon_pic" +#define UI_ITEM1_PIC "window_item1_pic" +#define UI_ITEM2_PIC "window_item2_pic" + +static uiitemType_t itemTypes[] = { + { UI_KNIFE_PIC, PT_KNIFE, "ui_mp/assets/weapon_knife.tga" }, + { UI_PISTOL_PIC, PT_PISTOL, "ui_mp/assets/weapon_colt1911.tga" }, + + { UI_WEAPON_PIC, PT_RIFLE, "ui_mp/assets/weapon_mauser.tga" }, + + { UI_ITEM1_PIC, PT_MEDKIT, "ui_mp/assets/item_medkit.tga" }, + + { UI_ITEM1_PIC, PT_GRENADES, "ui_mp/assets/weapon_grenade.tga" }, + { UI_ITEM2_PIC, PT_EXPLOSIVES, "ui_mp/assets/weapon_dynamite.tga" }, + + { NULL, 0, NULL } +}; + +extern displayContextDef_t *DC; + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .qvm file +================ +*/ +vmCvar_t ui_new; +vmCvar_t ui_debug; +vmCvar_t ui_initialized; +vmCvar_t ui_teamArenaFirstRun; + +void _UI_Init( qboolean ); +void _UI_Shutdown( void ); +void _UI_KeyEvent( int key, qboolean down ); +void _UI_MouseEvent( int dx, int dy ); +void _UI_Refresh( int realtime ); +qboolean _UI_IsFullscreen( void ); + +#if defined( __MACOS__ ) +#pragma export on +#endif +int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { +#if defined( __MACOS__ ) +#pragma export off +#endif + + switch ( command ) { + case UI_GETAPIVERSION: + return UI_API_VERSION; + + case UI_INIT: + _UI_Init( arg0 ); + return 0; + + case UI_SHUTDOWN: + _UI_Shutdown(); + return 0; + + case UI_KEY_EVENT: + _UI_KeyEvent( arg0, arg1 ); + return 0; + + case UI_MOUSE_EVENT: + _UI_MouseEvent( arg0, arg1 ); + return 0; + + case UI_REFRESH: + _UI_Refresh( arg0 ); + return 0; + + case UI_IS_FULLSCREEN: + return _UI_IsFullscreen(); + + case UI_SET_ACTIVE_MENU: + _UI_SetActiveMenu( arg0 ); + return 0; + + case UI_GET_ACTIVE_MENU: + return _UI_GetActiveMenu(); + + case UI_CONSOLE_COMMAND: + return UI_ConsoleCommand( arg0 ); + + case UI_DRAW_CONNECT_SCREEN: + UI_DrawConnectScreen( arg0 ); + return 0; + case UI_HASUNIQUECDKEY: // mod authors need to observe this + return qtrue; + // NERVE - SMF + case UI_CHECKEXECKEY: + return UI_CheckExecKey( arg0 ); + } + + return -1; +} + + + +void AssetCache() { + int n; + //if (Assets.textFont == NULL) { + //} + //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); + //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); + uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + uiInfo.uiDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); + uiInfo.uiDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); + uiInfo.uiDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); + uiInfo.uiDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_GREEN ); + uiInfo.uiDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_TEAL ); + uiInfo.uiDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); + uiInfo.uiDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_CYAN ); + uiInfo.uiDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); + uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + uiInfo.uiDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + uiInfo.uiDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + uiInfo.uiDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + uiInfo.uiDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + uiInfo.uiDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + uiInfo.uiDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + uiInfo.uiDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); + + for ( n = 0; n < NUM_CROSSHAIRS; n++ ) { + uiInfo.uiDC.Assets.crosshairShader[n] = trap_R_RegisterShaderNoMip( va( "gfx/2d/crosshair%c", 'a' + n ) ); + } + + //uiInfo.newHighScoreSound = trap_S_RegisterSound("sound/feedback/voc_newhighscore.wav"); + + // NERVE - SMF - WolfMP cache + trap_R_RegisterShaderNoMip( "multi_axisflag" ); + trap_R_RegisterShaderNoMip( "multi_alliedflag" ); + + trap_R_RegisterShaderNoMip( "axis_soldier" ); + trap_R_RegisterShaderNoMip( "axis_medic" ); + trap_R_RegisterShaderNoMip( "axis_eng" ); + trap_R_RegisterShaderNoMip( "axis_lt" ); + + trap_R_RegisterShaderNoMip( "allied_soldier" ); + trap_R_RegisterShaderNoMip( "allied_medic" ); + trap_R_RegisterShaderNoMip( "allied_eng" ); + trap_R_RegisterShaderNoMip( "allied_lt" ); + + trap_R_RegisterShaderNoMip( "multi_spectator" ); + + trap_R_RegisterShaderNoMip( "ui_mp/assets/button_click.tga" ); + trap_R_RegisterShaderNoMip( "ui_mp/assets/button.tga" ); + + trap_R_RegisterShaderNoMip( "ui_mp/assets/ger_flag.tga" ); + trap_R_RegisterShaderNoMip( "ui_mp/assets/usa_flag.tga" ); + + trap_R_RegisterShaderNoMip( "ui_mp/assets/weapon_syringe.tga" ); + trap_R_RegisterShaderNoMip( "ui_mp/assets/weapon_medheal.tga" ); + trap_R_RegisterShaderNoMip( "ui_mp/assets/weapon_pliers.tga" ); + trap_R_RegisterShaderNoMip( "ui_mp/assets/weapon_dynamite.tga" ); + trap_R_RegisterShaderNoMip( "ui_mp/assets/weapon_smokegrenade.tga" ); + trap_R_RegisterShaderNoMip( "ui_mp/assets/weapon_ammo.tga" ); + + for ( n = 1; weaponTypes[n].name; n++ ) { + if ( weaponTypes[n].name ) { + trap_R_RegisterShaderNoMip( weaponTypes[n].name ); + } + } + // -NERVE - SMF +} + +void _UI_DrawSides( float x, float y, float w, float h, float size ) { + UI_AdjustFrom640( &x, &y, &w, &h ); + size *= uiInfo.uiDC.xscale; + trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} + +void _UI_DrawTopBottom( float x, float y, float w, float h, float size ) { + UI_AdjustFrom640( &x, &y, &w, &h ); + size *= uiInfo.uiDC.yscale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ) { + trap_R_SetColor( color ); + + _UI_DrawTopBottom( x, y, width, height, size ); + _UI_DrawSides( x, y, width, height, size ); + + trap_R_SetColor( NULL ); +} + + + +// NERVE - SMF +void Text_SetActiveFont( int font ) { + uiInfo.activeFont = font; +} + + +int Text_Width( const char *text, float scale, int limit ) { + int count,len; + float out; + glyphInfo_t *glyph; + float useScale; + const char *s = text; + fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; + + // NERVE - SMF + if ( uiInfo.activeFont == UI_FONT_DEFAULT ) { + if ( scale <= ui_smallFont.value ) { + font = &uiInfo.uiDC.Assets.smallFont; + } else if ( scale >= ui_bigFont.value ) { + font = &uiInfo.uiDC.Assets.bigFont; + } + } else { + switch ( uiInfo.activeFont ) { + case UI_FONT_NORMAL: + font = &uiInfo.uiDC.Assets.textFont; + break; + case UI_FONT_BIG: + font = &uiInfo.uiDC.Assets.bigFont; + break; + case UI_FONT_SMALL: + font = &uiInfo.uiDC.Assets.smallFont; + break; + default: + font = &uiInfo.uiDC.Assets.textFont; + } + } + useScale = scale * font->glyphScale; + // -NERVE - SMF + + out = 0; + if ( text ) { + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(unsigned char)*s]; // NERVE - SMF - this needs to be an unsigned cast for localization + out += glyph->xSkip; + s++; + count++; + } + } + } + return out * useScale; +} + +int Text_Height( const char *text, float scale, int limit ) { + int len, count; + float max; + glyphInfo_t *glyph; + float useScale; + const char *s = text; + fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; + if ( scale <= ui_smallFont.value ) { + font = &uiInfo.uiDC.Assets.smallFont; + } else if ( scale >= ui_bigFont.value ) { + font = &uiInfo.uiDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + max = 0; + if ( text ) { + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(unsigned char)*s]; // NERVE - SMF - this needs to be an unsigned cast for localization + if ( max < glyph->height ) { + max = glyph->height; + } + s++; + count++; + } + } + } + return max * useScale; +} + +void Text_PaintChar( float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader ) { + float w, h; + w = width * scale; + h = height * scale; + UI_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); +} + +void Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + float useScale; + fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; + int index; + + // NERVE - SMF + if ( uiInfo.activeFont == UI_FONT_DEFAULT ) { + if ( scale <= ui_smallFont.value ) { + font = &uiInfo.uiDC.Assets.smallFont; + } else if ( scale >= ui_bigFont.value ) { + font = &uiInfo.uiDC.Assets.bigFont; + } + } else { + switch ( uiInfo.activeFont ) { + case UI_FONT_NORMAL: + font = &uiInfo.uiDC.Assets.textFont; + break; + case UI_FONT_BIG: + font = &uiInfo.uiDC.Assets.bigFont; + break; + case UI_FONT_SMALL: + font = &uiInfo.uiDC.Assets.smallFont; + break; + default: + font = &uiInfo.uiDC.Assets.textFont; + } + } + useScale = scale * font->glyphScale; + // -NERVE - SMF + + if ( text ) { + const char *s = text; + trap_R_SetColor( color ); + memcpy( &newColor[0], &color[0], sizeof( vec4_t ) ); + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + index = (unsigned char)*s; + + // NERVE - SMF - don't draw tabs and newlines + if ( index < 20 ) { + s++; + count++; + continue; + } + + glyph = &font->glyphs[index]; // NERVE - SMF - this needs to be an unsigned cast for localization + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if ( style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE ) { + int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; + colorBlack[3] = newColor[3]; + trap_R_SetColor( colorBlack ); + Text_PaintChar( x + ofs, y - yadj + ofs, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + trap_R_SetColor( newColor ); + colorBlack[3] = 1.0; + } + Text_PaintChar( x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + + x += ( glyph->xSkip * useScale ) + adjust; + s++; + count++; + } + } + trap_R_SetColor( NULL ); + } +} + +// copied over from Text_Paint +// we use the bulk of Text_Paint to determine were we will hit the max width +// can be used for actual text printing, or dummy run to get the number of lines +// returns the next char to be printed after wrap, or the ending \0 of the string +// NOTE: this is clearly non-optimal implementation, see Item_Text_AutoWrap_Paint for one +// if color_save != NULL, use to keep track of the current color between wraps +char* Text_AutoWrap_Paint_Chunk( float x, float y, int width, float scale, vec4_t color, char *text, float adjust, int limit, int style, qboolean dummy, vec4_t color_save ) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + float useScale; + fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; + int index; + char *wrap_point = NULL; + + float wrap_x = x + width; + + // NERVE - SMF + if ( uiInfo.activeFont == UI_FONT_DEFAULT ) { + if ( scale <= ui_smallFont.value ) { + font = &uiInfo.uiDC.Assets.smallFont; + } else if ( scale >= ui_bigFont.value ) { + font = &uiInfo.uiDC.Assets.bigFont; + } + } else { + switch ( uiInfo.activeFont ) { + case UI_FONT_NORMAL: + font = &uiInfo.uiDC.Assets.textFont; + break; + case UI_FONT_BIG: + font = &uiInfo.uiDC.Assets.bigFont; + break; + case UI_FONT_SMALL: + font = &uiInfo.uiDC.Assets.smallFont; + break; + default: + font = &uiInfo.uiDC.Assets.textFont; + } + } + useScale = scale * font->glyphScale; + // -NERVE - SMF + + if ( text ) { + char *s = text; + trap_R_SetColor( color ); + memcpy( &newColor[0], &color[0], sizeof( vec4_t ) ); + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + index = (unsigned char)*s; + if ( *s == ' ' || *s == '\t' || *s == '\n' ) { + wrap_point = s; + } + + // NERVE - SMF - don't draw tabs and newlines + if ( index < 20 ) { + s++; + count++; + continue; + } + + glyph = &font->glyphs[index]; // NERVE - SMF - this needs to be an unsigned cast for localization + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( newColor ) ); + newColor[3] = color[3]; + if ( !dummy ) { + trap_R_SetColor( newColor ); + } + if ( color_save ) { + memcpy( &color_save[0], &newColor[0], sizeof( vec4_t ) ); + } + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + + if ( x + ( glyph->xSkip * useScale ) + adjust > wrap_x ) { + if ( wrap_point ) { + return wrap_point + 1; // the next char to be printed after line wrap + } + // we haven't found the wrap point .. cut + return s; + } + + if ( !dummy ) { + if ( style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE ) { + int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; + colorBlack[3] = newColor[3]; + trap_R_SetColor( colorBlack ); + Text_PaintChar( x + ofs, y - yadj + ofs, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + trap_R_SetColor( newColor ); + colorBlack[3] = 1.0; + } + Text_PaintChar( x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + } + + x += ( glyph->xSkip * useScale ) + adjust; + s++; + count++; + } + } + if ( !dummy ) { + trap_R_SetColor( NULL ); + } + } + return text + strlen( text ); +} + +// count the lines that we will need to have to print with the given wrap parameters +int Count_Text_AutoWrap_Paint( float x, float y, int width, float scale, vec4_t color, const char *text, float adjust, int style ) { + const char *ret, *end; + int i = 0; + + ret = text; + end = text + strlen( text ); + + do + { + ret = Text_AutoWrap_Paint_Chunk( x, y, width, scale, color, (char *)ret, adjust, 0, style, qtrue, NULL ); + i++; + } while ( ret < end ); + + return i; +} + +void Text_AutoWrap_Paint( float x, float y, int width, int height, float scale, vec4_t color, const char *l_text, float adjust, int style ) { + char text[1024]; + char *ret, *end, *next; + char s; + vec4_t aux_color, next_color; + + Q_strncpyz( text, l_text, sizeof( text ) - 1 ); + ret = text; + end = text + strlen( text ); + memcpy( &aux_color[0], &color[0], sizeof( vec4_t ) ); + + do + { + // one run to get the word wrap + next = Text_AutoWrap_Paint_Chunk( x, y, width, scale, aux_color, ret, adjust, 0, style, qtrue, next_color ); + // now print - hack around a bit to avoid the word wrapped chars + s = *next; *next = '\0'; + Text_Paint( x, y, scale, aux_color, ret, adjust, 0, style ); + *next = s; + ret = next; + memcpy( &aux_color[0], &next_color[0], sizeof( vec4_t ) ); + y += height; + } while ( ret < end ); +} + +void Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style ) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph, *glyph2; + float yadj; + float useScale; + fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; + + // NERVE - SMF + if ( uiInfo.activeFont == UI_FONT_DEFAULT ) { + if ( scale <= ui_smallFont.value ) { + font = &uiInfo.uiDC.Assets.smallFont; + } else if ( scale >= ui_bigFont.value ) { + font = &uiInfo.uiDC.Assets.bigFont; + } + } else { + switch ( uiInfo.activeFont ) { + case UI_FONT_NORMAL: + font = &uiInfo.uiDC.Assets.textFont; + break; + case UI_FONT_BIG: + font = &uiInfo.uiDC.Assets.bigFont; + break; + case UI_FONT_SMALL: + font = &uiInfo.uiDC.Assets.smallFont; + break; + default: + font = &uiInfo.uiDC.Assets.textFont; + } + } + useScale = scale * font->glyphScale; + // -NERVE - SMF + + if ( text ) { + const char *s = text; + trap_R_SetColor( color ); + memcpy( &newColor[0], &color[0], sizeof( vec4_t ) ); + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + glyph2 = &font->glyphs[(unsigned char)cursor]; + while ( s && *s && count < len ) { + glyph = &font->glyphs[(unsigned char)*s]; // NERVE - SMF - this needs to be an unsigned cast for localization + //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; + //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + yadj = useScale * glyph->top; + if ( style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE ) { + int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; + colorBlack[3] = newColor[3]; + trap_R_SetColor( colorBlack ); + Text_PaintChar( x + ofs, y - yadj + ofs, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + colorBlack[3] = 1.0; + trap_R_SetColor( newColor ); + } + Text_PaintChar( x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + + // CG_DrawPic(x, y - yadj, scale * uiDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * uiDC.Assets.textFont.glyphs[text[i]].imageHeight, uiDC.Assets.textFont.glyphs[text[i]].glyph); + yadj = useScale * glyph2->top; + if ( count == cursorPos && !( ( uiInfo.uiDC.realTime / BLINK_DIVISOR ) & 1 ) ) { + Text_PaintChar( x, y - yadj, + glyph2->imageWidth, + glyph2->imageHeight, + useScale, + glyph2->s, + glyph2->t, + glyph2->s2, + glyph2->t2, + glyph2->glyph ); + } + + x += ( glyph->xSkip * useScale ); + s++; + count++; + } + } + // need to paint cursor at end of text + if ( cursorPos == len && !( ( uiInfo.uiDC.realTime / BLINK_DIVISOR ) & 1 ) ) { + yadj = useScale * glyph2->top; + Text_PaintChar( x, y - yadj, + glyph2->imageWidth, + glyph2->imageHeight, + useScale, + glyph2->s, + glyph2->t, + glyph2->s2, + glyph2->t2, + glyph2->glyph ); + + } + + + trap_R_SetColor( NULL ); + } +} + + +static void Text_Paint_Limit( float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit ) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + if ( text ) { + const char *s = text; + float max = *maxX; + float useScale; + fontInfo_t *font = &uiInfo.uiDC.Assets.textFont; + + // NERVE - SMF + if ( uiInfo.activeFont == UI_FONT_DEFAULT ) { + if ( scale <= ui_smallFont.value ) { + font = &uiInfo.uiDC.Assets.smallFont; + } else if ( scale >= ui_bigFont.value ) { + font = &uiInfo.uiDC.Assets.bigFont; + } + } else { + switch ( uiInfo.activeFont ) { + case UI_FONT_NORMAL: + font = &uiInfo.uiDC.Assets.textFont; + break; + case UI_FONT_BIG: + font = &uiInfo.uiDC.Assets.bigFont; + break; + case UI_FONT_SMALL: + font = &uiInfo.uiDC.Assets.smallFont; + break; + default: + font = &uiInfo.uiDC.Assets.textFont; + } + } + useScale = scale * font->glyphScale; + // -NERVE - SMF + + trap_R_SetColor( color ); + len = strlen( text ); + if ( limit > 0 && len > limit ) { + len = limit; + } + count = 0; + while ( s && *s && count < len ) { + glyph = &font->glyphs[(unsigned char)*s]; // NERVE - SMF - this needs to be an unsigned cast for localization + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex( *( s + 1 ) )], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if ( Text_Width( s, useScale, 1 ) + x > max ) { + *maxX = 0; + break; + } + Text_PaintChar( x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph ); + x += ( glyph->xSkip * useScale ) + adjust; + *maxX = x; + count++; + s++; + } + } + trap_R_SetColor( NULL ); + } + +} + + +void UI_ShowPostGame( qboolean newHigh ) { + trap_Cvar_Set( "cg_cameraOrbit", "0" ); + trap_Cvar_Set( "cg_thirdPerson", "0" ); + trap_Cvar_Set( "sv_killserver", "1" ); + uiInfo.soundHighScore = newHigh; + _UI_SetActiveMenu( UIMENU_POSTGAME ); +} +/* +================= +_UI_Refresh +================= +*/ + +void UI_DrawCenteredPic( qhandle_t image, int w, int h ) { + int x, y; + x = ( SCREEN_WIDTH - w ) / 2; + y = ( SCREEN_HEIGHT - h ) / 2; + UI_DrawHandlePic( x, y, w, h, image ); +} + +int frameCount = 0; +int startTime; + +#define UI_FPS_FRAMES 4 +void _UI_Refresh( int realtime ) { + static int index; + static int previousTimes[UI_FPS_FRAMES]; + + //if ( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) { + // return; + //} + + uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime; + uiInfo.uiDC.realTime = realtime; + + previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime; + index++; + if ( index > UI_FPS_FRAMES ) { + int i, total; + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < UI_FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total; + } + + + + UI_UpdateCvars(); + + if ( Menu_Count() > 0 ) { + // paint all the menus + Menu_PaintAll(); + // refresh server browser list + UI_DoServerRefresh(); + // refresh server status + UI_BuildServerStatus( qfalse ); + // refresh find player list + UI_BuildFindPlayerList( qfalse ); + } + + // draw cursor + UI_SetColor( NULL ); + if ( Menu_Count() > 0 ) { + UI_DrawHandlePic( uiInfo.uiDC.cursorx - 16, uiInfo.uiDC.cursory - 16, 32, 32, uiInfo.uiDC.Assets.cursor ); + } + +#ifndef NDEBUG + if ( uiInfo.uiDC.debug ) { + // cursor coordinates + //FIXME + //UI_DrawString( 0, 0, va("(%d,%d)",uis.cursorx,uis.cursory), UI_LEFT|UI_SMALLFONT, colorRed ); + } +#endif + +} + +/* +================= +_UI_Shutdown +================= +*/ +void _UI_Shutdown( void ) { + trap_LAN_SaveCachedServers(); +} + +char *defaultMenu = NULL; + +char *GetMenuBuffer( const char *filename ) { + int len; + fileHandle_t f; + static char buf[MAX_MENUFILE]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return defaultMenu; + } + if ( len >= MAX_MENUFILE ) { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return defaultMenu; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + //COM_Compress(buf); + return buf; + +} + +qboolean Asset_Parse( int handle ) { + pc_token_t token; + const char *tempStr; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( Q_stricmp( token.string, "{" ) != 0 ) { + return qfalse; + } + + while ( 1 ) { + + memset( &token, 0, sizeof( pc_token_t ) ); + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + + if ( Q_stricmp( token.string, "}" ) == 0 ) { + return qtrue; + } + + // font + if ( Q_stricmp( token.string, "font" ) == 0 ) { + int pointSize; + if ( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle,&pointSize ) ) { + return qfalse; + } + trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.textFont ); + uiInfo.uiDC.Assets.fontRegistered = qtrue; + continue; + } + + if ( Q_stricmp( token.string, "smallFont" ) == 0 ) { + int pointSize; + if ( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle,&pointSize ) ) { + return qfalse; + } + trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.smallFont ); + continue; + } + + if ( Q_stricmp( token.string, "bigFont" ) == 0 ) { + int pointSize; + if ( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle,&pointSize ) ) { + return qfalse; + } + trap_R_RegisterFont( tempStr, pointSize, &uiInfo.uiDC.Assets.bigFont ); + continue; + } + + + // gradientbar + if ( Q_stricmp( token.string, "gradientbar" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr ); + continue; + } + + // enterMenuSound + if ( Q_stricmp( token.string, "menuEnterSound" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr ); + continue; + } + + // exitMenuSound + if ( Q_stricmp( token.string, "menuExitSound" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr ); + continue; + } + + // itemFocusSound + if ( Q_stricmp( token.string, "itemFocusSound" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr ); + continue; + } + + // menuBuzzSound + if ( Q_stricmp( token.string, "menuBuzzSound" ) == 0 ) { + if ( !PC_String_Parse( handle, &tempStr ) ) { + return qfalse; + } + uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr ); + continue; + } + + if ( Q_stricmp( token.string, "cursor" ) == 0 ) { + if ( !PC_String_Parse( handle, &uiInfo.uiDC.Assets.cursorStr ) ) { + return qfalse; + } + uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr ); + continue; + } + + if ( Q_stricmp( token.string, "fadeClamp" ) == 0 ) { + if ( !PC_Float_Parse( handle, &uiInfo.uiDC.Assets.fadeClamp ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "fadeCycle" ) == 0 ) { + if ( !PC_Int_Parse( handle, &uiInfo.uiDC.Assets.fadeCycle ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "fadeAmount" ) == 0 ) { + if ( !PC_Float_Parse( handle, &uiInfo.uiDC.Assets.fadeAmount ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "shadowX" ) == 0 ) { + if ( !PC_Float_Parse( handle, &uiInfo.uiDC.Assets.shadowX ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "shadowY" ) == 0 ) { + if ( !PC_Float_Parse( handle, &uiInfo.uiDC.Assets.shadowY ) ) { + return qfalse; + } + continue; + } + + if ( Q_stricmp( token.string, "shadowColor" ) == 0 ) { + if ( !PC_Color_Parse( handle, &uiInfo.uiDC.Assets.shadowColor ) ) { + return qfalse; + } + uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3]; + continue; + } + + } + return qfalse; +} + +void Font_Report() { + int i; + Com_Printf( "Font Info\n" ); + Com_Printf( "=========\n" ); + for ( i = 32; i < 96; i++ ) { + Com_Printf( "Glyph handle %i: %i\n", i, uiInfo.uiDC.Assets.textFont.glyphs[i].glyph ); + } +} + +void UI_Report() { + String_Report(); + //Font_Report(); + +} + +void QDECL Com_DPrintf( const char *fmt, ... ); +qboolean UI_ParseMenu( const char *menuFile ) { + int handle; + pc_token_t token; + + Com_DPrintf( "Parsing menu file:%s\n", menuFile ); + + handle = trap_PC_LoadSource( menuFile ); + if ( !handle ) { + return qfalse; + } + + while ( 1 ) { + memset( &token, 0, sizeof( pc_token_t ) ); + if ( !trap_PC_ReadToken( handle, &token ) ) { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( token.string[0] == '}' ) { + break; + } + + if ( Q_stricmp( token.string, "assetGlobalDef" ) == 0 ) { + if ( Asset_Parse( handle ) ) { + continue; + } else { + break; + } + } + + if ( Q_stricmp( token.string, "menudef" ) == 0 ) { + // start a new menu + Menu_New( handle ); + } + } + trap_PC_FreeSource( handle ); + return qtrue; +} + +qboolean Load_Menu( int handle ) { + pc_token_t token; + int cl_language; // NERVE - SMF + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( token.string[0] != '{' ) { + return qfalse; + } + + while ( 1 ) { + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + + if ( token.string[0] == 0 ) { + return qfalse; + } + + if ( token.string[0] == '}' ) { + return qtrue; + } + + // NERVE - SMF - localization crap + cl_language = atoi( UI_Cvar_VariableString( "cl_language" ) ); + + if ( cl_language ) { + const char *s = NULL; // TTimo: init + const char *filename; + char out[256]; +// char filename[256]; + + COM_StripFilename( token.string, out ); + + filename = COM_SkipPath( token.string ); + + if ( cl_language == 1 ) { + s = va( "%s%s", out, "french/" ); + } else if ( cl_language == 2 ) { + s = va( "%s%s", out, "german/" ); + } else if ( cl_language == 3 ) { + s = va( "%s%s", out, "italian/" ); + } else if ( cl_language == 4 ) { + s = va( "%s%s", out, "spanish/" ); + } + + if ( UI_ParseMenu( va( "%s%s", s, filename ) ) ) { + continue; + } + } + // -NERVE + + UI_ParseMenu( token.string ); + } + return qfalse; +} + +void UI_LoadMenus( const char *menuFile, qboolean reset ) { + pc_token_t token; + int handle; + int start; + + start = trap_Milliseconds(); + + handle = trap_PC_LoadSource( menuFile ); + if ( !handle ) { + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + handle = trap_PC_LoadSource( "ui_mp/menus.txt" ); + if ( !handle ) { + trap_Error( va( S_COLOR_RED "default menu file not found: ui_mp/menus.txt, unable to continue!\n", menuFile ) ); + } + } + + ui_new.integer = 1; + + if ( reset ) { + Menu_Reset(); + } + + while ( 1 ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + break; + } + if ( token.string[0] == 0 || token.string[0] == '}' ) { + break; + } + + if ( token.string[0] == '}' ) { + break; + } + + if ( Q_stricmp( token.string, "loadmenu" ) == 0 ) { + if ( Load_Menu( handle ) ) { + continue; + } else { + break; + } + } + } + + Com_DPrintf( "UI menu load time = %d milli seconds\n", trap_Milliseconds() - start ); + + trap_PC_FreeSource( handle ); +} + +void UI_Load() { + char lastName[1024]; + menuDef_t *menu = Menu_GetFocused(); + char *menuSet = UI_Cvar_VariableString( "ui_menuFiles" ); + if ( menu && menu->window.name ) { + strcpy( lastName, menu->window.name ); + } + if ( menuSet == NULL || menuSet[0] == '\0' ) { + menuSet = "ui_mp/menus.txt"; + } + + String_Init(); + + UI_ParseGameInfo( "gameinfo.txt" ); + UI_LoadArenas(); + + UI_LoadMenus( menuSet, qtrue ); + Menus_CloseAll(); + Menus_ActivateByName( lastName, qtrue ); + +} + +static const char *handicapValues[] = {"None","95","90","85","80","75","70","65","60","55","50","45","40","35","30","25","20","15","10","5",NULL}; +//static int numHandicaps = sizeof(handicapValues) / sizeof(const char*); // TTimo: unused + +static void UI_DrawHandicap( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i, h; + + h = Com_Clamp( 5, 100, trap_Cvar_VariableValue( "handicap" ) ); + i = 20 - h / 5; + + Text_Paint( rect->x, rect->y, scale, color, handicapValues[i], 0, 0, textStyle ); +} + +static void UI_DrawClanName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + Text_Paint( rect->x, rect->y, scale, color, UI_Cvar_VariableString( "ui_teamName" ), 0, 0, textStyle ); +} + + +static void UI_SetCapFragLimits( qboolean uiVars ) { + //int cap = 5; // TTimo:unused + //int frag = 10; // TTimo: unused +#ifdef MISSIONPACK + if ( uiInfo.gameTypes[ui_gameType.integer].gtEnum == GT_OBELISK ) { + cap = 4; + } else if ( uiInfo.gameTypes[ui_gameType.integer].gtEnum == GT_HARVESTER ) { + cap = 15; + } + if ( uiVars ) { + trap_Cvar_Set( "ui_captureLimit", va( "%d", cap ) ); + trap_Cvar_Set( "ui_fragLimit", va( "%d", frag ) ); + } else { + trap_Cvar_Set( "capturelimit", va( "%d", cap ) ); + trap_Cvar_Set( "fraglimit", va( "%d", frag ) ); + } +#endif // #ifdef MISSIONPACK +} +// ui_gameType assumes gametype 0 is -1 ALL and will not show +static void UI_DrawGameType( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + Text_Paint( rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_gameType.integer].gameType, 0, 0, textStyle ); +} + +static void UI_DrawNetGameType( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + if ( ui_netGameType.integer < 0 || ui_netGameType.integer > uiInfo.numGameTypes ) { + trap_Cvar_Set( "ui_netGameType", "0" ); + trap_Cvar_Set( "ui_actualNetGameType", "0" ); + } + Text_Paint( rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_netGameType.integer].gameType, 0, 0, textStyle ); +} + +static void UI_DrawJoinGameType( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + if ( ui_joinGameType.integer < 0 || ui_joinGameType.integer > uiInfo.numJoinGameTypes ) { + trap_Cvar_Set( "ui_joinGameType", "0" ); + } + Text_Paint( rect->x, rect->y, scale, color, trap_TranslateString( uiInfo.joinGameTypes[ui_joinGameType.integer].gameType ), 0, 0, textStyle ); +} + + + +static int UI_TeamIndexFromName( const char *name ) { + int i; + + if ( name && *name ) { + for ( i = 0; i < uiInfo.teamCount; i++ ) { + if ( Q_stricmp( name, uiInfo.teamList[i].teamName ) == 0 ) { + return i; + } + } + } + + return 0; + +} + + +/* +============== +UI_DrawSaveGameShot +============== +*/ +static void UI_DrawSaveGameShot( rectDef_t *rect, float scale, vec4_t color ) { + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.savegameList[uiInfo.savegameIndex].sshotImage ); + trap_R_SetColor( NULL ); +} + + +/* +============== +UI_DrawClanLogo +============== +*/ +static void UI_DrawClanLogo( rectDef_t *rect, float scale, vec4_t color ) { + int i; + i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + if ( i >= 0 && i < uiInfo.teamCount ) { + trap_R_SetColor( color ); + + if ( uiInfo.teamList[i].teamIcon == -1 ) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip( uiInfo.teamList[i].imageName ); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip( va( "%s_metal",uiInfo.teamList[i].imageName ) ); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip( va( "%s_name", uiInfo.teamList[i].imageName ) ); + } + + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); + trap_R_SetColor( NULL ); + } +} + +/* +============== +UI_DrawClanCinematic +============== +*/ +static void UI_DrawClanCinematic( rectDef_t *rect, float scale, vec4_t color ) { + int i; + i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + if ( i >= 0 && i < uiInfo.teamCount ) { + + if ( uiInfo.teamList[i].cinematic >= -2 ) { + if ( uiInfo.teamList[i].cinematic == -1 ) { + uiInfo.teamList[i].cinematic = trap_CIN_PlayCinematic( va( "%s.roq", uiInfo.teamList[i].imageName ), 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + } + if ( uiInfo.teamList[i].cinematic >= 0 ) { + trap_CIN_RunCinematic( uiInfo.teamList[i].cinematic ); + trap_CIN_SetExtents( uiInfo.teamList[i].cinematic, rect->x, rect->y, rect->w, rect->h ); + trap_CIN_DrawCinematic( uiInfo.teamList[i].cinematic ); + } else { + uiInfo.teamList[i].cinematic = -2; + } + } else { + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); + trap_R_SetColor( NULL ); + } + } + +} + +static void UI_DrawPreviewCinematic( rectDef_t *rect, float scale, vec4_t color ) { + if ( uiInfo.previewMovie > -2 ) { + uiInfo.previewMovie = trap_CIN_PlayCinematic( va( "%s.roq", uiInfo.movieList[uiInfo.movieIndex] ), 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + if ( uiInfo.previewMovie >= 0 ) { + trap_CIN_RunCinematic( uiInfo.previewMovie ); + trap_CIN_SetExtents( uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h ); + trap_CIN_DrawCinematic( uiInfo.previewMovie ); + } else { + uiInfo.previewMovie = -2; + } + } + +} + + + +static void UI_DrawSkill( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i; + i = trap_Cvar_VariableValue( "g_spSkill" ); + if ( i < 1 || i > numSkillLevels ) { + i = 1; + } + Text_Paint( rect->x, rect->y, scale, color, skillLevels[i - 1],0, 0, textStyle ); +} + + +static void UI_DrawTeamName( rectDef_t *rect, float scale, vec4_t color, qboolean blue, int textStyle ) { + int i; + i = UI_TeamIndexFromName( UI_Cvar_VariableString( ( blue ) ? "ui_blueTeam" : "ui_redTeam" ) ); + if ( i >= 0 && i < uiInfo.teamCount ) { + Text_Paint( rect->x, rect->y, scale, color, va( "%s: %s", ( blue ) ? "Blue" : "Red", uiInfo.teamList[i].teamName ),0, 0, textStyle ); + } +} + +static void UI_DrawTeamMember( rectDef_t *rect, float scale, vec4_t color, qboolean blue, int num, int textStyle ) { +#ifdef MISSIONPACK + // 0 - None + // 1 - Human + // 2..NumCharacters - Bot + int value = trap_Cvar_VariableValue( va( blue ? "ui_blueteam%i" : "ui_redteam%i", num ) ); + const char *text; + if ( value <= 0 ) { + text = "Closed"; + } else if ( value == 1 ) { + text = "Human"; + } else { + value -= 2; + + if ( ui_actualNetGameType.integer >= GT_TEAM ) { + if ( value >= uiInfo.characterCount ) { + value = 0; + } + text = uiInfo.characterList[value].name; + } else { + if ( value >= UI_GetNumBots() ) { + value = 0; + } + text = UI_GetBotNameByNumber( value ); + } + } + Text_Paint( rect->x, rect->y, scale, color, text, 0, 0, textStyle ); +#endif // #ifdef MISSIONPACK +} + +static void UI_DrawEffects( rectDef_t *rect, float scale, vec4_t color ) { + UI_DrawHandlePic( rect->x, rect->y - 14, 128, 8, uiInfo.uiDC.Assets.fxBasePic ); + UI_DrawHandlePic( rect->x + uiInfo.effectsColor * 16 + 8, rect->y - 16, 16, 12, uiInfo.uiDC.Assets.fxPic[uiInfo.effectsColor] ); +} + +static void UI_DrawMapPreview( rectDef_t *rect, float scale, vec4_t color, qboolean net ) { + int map = ( net ) ? ui_currentNetMap.integer : ui_currentMap.integer; + if ( map < 0 || map > uiInfo.mapCount ) { + if ( net ) { + ui_currentNetMap.integer = 0; + trap_Cvar_Set( "ui_currentNetMap", "0" ); + } else { + ui_currentMap.integer = 0; + trap_Cvar_Set( "ui_currentMap", "0" ); + } + map = 0; + } + + if ( uiInfo.mapList[map].levelShot == -1 ) { + uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip( uiInfo.mapList[map].imageName ); + } + + if ( uiInfo.mapList[map].levelShot > 0 ) { + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot ); + } else { + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ) ); + } +} + + +static void UI_DrawMapTimeToBeat( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int minutes, seconds, time; + if ( ui_currentMap.integer < 0 || ui_currentMap.integer > uiInfo.mapCount ) { + ui_currentMap.integer = 0; + trap_Cvar_Set( "ui_currentMap", "0" ); + } + + time = uiInfo.mapList[ui_currentMap.integer].timeToBeat[uiInfo.gameTypes[ui_gameType.integer].gtEnum]; + + minutes = time / 60; + seconds = time % 60; + + Text_Paint( rect->x, rect->y, scale, color, va( "%02i:%02i", minutes, seconds ), 0, 0, textStyle ); +} + + + +static void UI_DrawMapCinematic( rectDef_t *rect, float scale, vec4_t color, qboolean net ) { + + int map = ( net ) ? ui_currentNetMap.integer : ui_currentMap.integer; + if ( map < 0 || map > uiInfo.mapCount ) { + if ( net ) { + ui_currentNetMap.integer = 0; + trap_Cvar_Set( "ui_currentNetMap", "0" ); + } else { + ui_currentMap.integer = 0; + trap_Cvar_Set( "ui_currentMap", "0" ); + } + map = 0; + } + + if ( uiInfo.mapList[map].cinematic >= -1 ) { + if ( uiInfo.mapList[map].cinematic == -1 ) { + uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic( va( "%s.roq", uiInfo.mapList[map].mapLoadName ), 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + } + if ( uiInfo.mapList[map].cinematic >= 0 ) { + trap_CIN_RunCinematic( uiInfo.mapList[map].cinematic ); + trap_CIN_SetExtents( uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h ); + trap_CIN_DrawCinematic( uiInfo.mapList[map].cinematic ); + } else { + uiInfo.mapList[map].cinematic = -2; + } + } else { + UI_DrawMapPreview( rect, scale, color, net ); + } +} + + + +static qboolean updateModel = qtrue; +static qboolean q3Model = qfalse; + +static void UI_DrawPlayerModel( rectDef_t *rect ) { + static playerInfo_t info; + char model[MAX_QPATH]; + char team[256]; + char head[256]; + vec3_t viewangles; + static vec3_t moveangles = { 0, 0, 0 }; + + if ( trap_Cvar_VariableValue( "ui_Q3Model" ) ) { + // NERVE - SMF + int teamval; + teamval = trap_Cvar_VariableValue( "mp_team" ); + + if ( teamval == ALLIES_TEAM ) { + strcpy( model, "multi" ); + } else { + strcpy( model, "multi_axis" ); + } + // -NERVE - SMF + + strcpy( head, UI_Cvar_VariableString( "headmodel" ) ); + if ( !q3Model ) { + q3Model = qtrue; + updateModel = qtrue; + } + team[0] = '\0'; + } else { + strcpy( model, UI_Cvar_VariableString( "team_model" ) ); + strcpy( head, UI_Cvar_VariableString( "team_headmodel" ) ); + strcpy( team, UI_Cvar_VariableString( "ui_teamName" ) ); + if ( q3Model ) { + q3Model = qfalse; + updateModel = qtrue; + } + } + + moveangles[YAW] += 1; // NERVE - SMF - TEMPORARY + + // compare new cvars to old cvars and see if we need to update + { + int v1, v2; + + v1 = trap_Cvar_VariableValue( "mp_team" ); + v2 = trap_Cvar_VariableValue( "ui_prevTeam" ); + if ( v1 != v2 ) { + trap_Cvar_Set( "ui_prevTeam", va( "%i", v1 ) ); + updateModel = qtrue; + } + + v1 = trap_Cvar_VariableValue( "mp_playerType" ); + v2 = trap_Cvar_VariableValue( "ui_prevClass" ); + if ( v1 != v2 ) { + trap_Cvar_Set( "ui_prevClass", va( "%i", v1 ) ); + updateModel = qtrue; + } + + v1 = trap_Cvar_VariableValue( "mp_weapon" ); + v2 = trap_Cvar_VariableValue( "ui_prevWeapon" ); + if ( v1 != v2 ) { + trap_Cvar_Set( "ui_prevWeapon", va( "%i", v1 ) ); + updateModel = qtrue; + } + } + + if ( updateModel ) { // NERVE - SMF - TEMPORARY + memset( &info, 0, sizeof( playerInfo_t ) ); + viewangles[YAW] = 180 - 10; + viewangles[PITCH] = 0; + viewangles[ROLL] = 0; +// VectorClear( moveangles ); +#ifdef MISSIONPACK + UI_PlayerInfo_SetModel( &info, model, head, team ); +#else + UI_PlayerInfo_SetModel( &info, model ); +#endif // MISSIONPACK + UI_PlayerInfo_SetInfo( &info, LEGS_IDLE, TORSO_STAND, viewangles, moveangles, -1, qfalse ); +// UI_RegisterClientModelname( &info, model, head, team); + updateModel = qfalse; + } else { + VectorCopy( moveangles, info.moveAngles ); + } + +// info.moveAngles[YAW] += 1; +// UI_PlayerInfo_SetInfo( &info, LEGS_IDLE, TORSO_STAND, viewangles, moveangles, WP_MP40, qfalse ); + UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info, uiInfo.uiDC.realTime / 2 ); + +} + +static void UI_DrawNetSource( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + if ( ui_netSource.integer < 0 || ui_netSource.integer > numNetSources /*uiInfo.numGameTypes*/ ) { // NERVE - SMF - possible bug + ui_netSource.integer = 0; + } + Text_Paint( rect->x, rect->y, scale, color, trap_TranslateString( va( "Source: %s", netSources[ui_netSource.integer] ) ), 0, 0, textStyle ); +} + +static void UI_DrawNetMapPreview( rectDef_t *rect, float scale, vec4_t color ) { + + if ( uiInfo.serverStatus.currentServerPreview > 0 ) { + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview ); + } else { + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ) ); + } +} + +static void UI_DrawNetMapCinematic( rectDef_t *rect, float scale, vec4_t color ) { + if ( ui_currentNetMap.integer < 0 || ui_currentNetMap.integer > uiInfo.mapCount ) { + ui_currentNetMap.integer = 0; + trap_Cvar_Set( "ui_currentNetMap", "0" ); + } + + if ( uiInfo.serverStatus.currentServerCinematic >= 0 ) { + trap_CIN_RunCinematic( uiInfo.serverStatus.currentServerCinematic ); + trap_CIN_SetExtents( uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h ); + trap_CIN_DrawCinematic( uiInfo.serverStatus.currentServerCinematic ); + } else { + UI_DrawNetMapPreview( rect, scale, color ); + } +} + + + +static void UI_DrawNetFilter( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + if ( ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters ) { + ui_serverFilterType.integer = 0; + } + Text_Paint( rect->x, rect->y, scale, color, va( "Filter: %s", serverFilters[ui_serverFilterType.integer].description ), 0, 0, textStyle ); +} + + +static void UI_DrawTier( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if ( i < 0 || i >= uiInfo.tierCount ) { + i = 0; + } + Text_Paint( rect->x, rect->y, scale, color, va( "Tier: %s", uiInfo.tierList[i].tierName ),0, 0, textStyle ); +} + +static void UI_DrawTierMap( rectDef_t *rect, int index ) { + int i; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if ( i < 0 || i >= uiInfo.tierCount ) { + i = 0; + } + + if ( uiInfo.tierList[i].mapHandles[index] == -1 ) { + uiInfo.tierList[i].mapHandles[index] = trap_R_RegisterShaderNoMip( va( "levelshots/%s", uiInfo.tierList[i].maps[index] ) ); + } + + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.tierList[i].mapHandles[index] ); +} + +static const char *UI_EnglishMapName( const char *map ) { + int i; + for ( i = 0; i < uiInfo.mapCount; i++ ) { + if ( Q_stricmp( map, uiInfo.mapList[i].mapLoadName ) == 0 ) { + return uiInfo.mapList[i].mapName; + } + } + return ""; +} + +static void UI_DrawTierMapName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i, j; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if ( i < 0 || i >= uiInfo.tierCount ) { + i = 0; + } + j = trap_Cvar_VariableValue( "ui_currentMap" ); + if ( j < 0 || j > MAPS_PER_TIER ) { + j = 0; + } + + Text_Paint( rect->x, rect->y, scale, color, UI_EnglishMapName( uiInfo.tierList[i].maps[j] ), 0, 0, textStyle ); +} + +static void UI_DrawTierGameType( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i, j; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if ( i < 0 || i >= uiInfo.tierCount ) { + i = 0; + } + j = trap_Cvar_VariableValue( "ui_currentMap" ); + if ( j < 0 || j > MAPS_PER_TIER ) { + j = 0; + } + + Text_Paint( rect->x, rect->y, scale, color, uiInfo.gameTypes[uiInfo.tierList[i].gameTypes[j]].gameType, 0, 0, textStyle ); +} + +/* +// TTimo: unused +static const char *UI_OpponentLeaderName() { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + return uiInfo.teamList[i].teamMembers[0]; +} +*/ + +/* +// TTimo: unused +static const char *UI_AIFromName(const char *name) { + int j; + for (j = 0; j < uiInfo.aliasCount; j++) { + if (Q_stricmp(uiInfo.aliasList[j].name, name) == 0) { + return uiInfo.aliasList[j].ai; + } + } + return "James"; +} +*/ + +/* +// TTimo: unused +static const int UI_AIIndex(const char *name) { + int j; + for (j = 0; j < uiInfo.characterCount; j++) { + if (Q_stricmp(name, uiInfo.characterList[j].name) == 0) { + return j; + } + } + return 0; +} +*/ + +/* +// TTimo: unused +static const int UI_AIIndexFromName(const char *name) { + int j; + for (j = 0; j < uiInfo.aliasCount; j++) { + if (Q_stricmp(uiInfo.aliasList[j].name, name) == 0) { + return UI_AIIndex(uiInfo.aliasList[j].ai); + } + } + return 0; +} +*/ + +/* +// TTimo: unused +static const char *UI_OpponentLeaderHead() { + const char *leader = UI_OpponentLeaderName(); + return UI_AIFromName(leader); +} +*/ + +/* +// TTimo: unused +static const char *UI_OpponentLeaderModel() { + int i; + const char *head = UI_OpponentLeaderHead(); + for (i = 0; i < uiInfo.characterCount; i++) { + if (Q_stricmp(head, uiInfo.characterList[i].name) == 0) { + if (uiInfo.characterList[i].female) { + return "Janet"; + } else { + return "James"; + } + } + } + return "James"; +} +*/ + +static qboolean updateOpponentModel = qtrue; +static void UI_DrawOpponent( rectDef_t *rect ) { + static playerInfo_t info2; + char model[MAX_QPATH]; + char headmodel[MAX_QPATH]; + char team[256]; + vec3_t viewangles; + vec3_t moveangles; + + if ( updateOpponentModel ) { + + strcpy( model, UI_Cvar_VariableString( "ui_opponentModel" ) ); + strcpy( headmodel, UI_Cvar_VariableString( "ui_opponentModel" ) ); + team[0] = '\0'; + + memset( &info2, 0, sizeof( playerInfo_t ) ); + viewangles[YAW] = 180 - 10; + viewangles[PITCH] = 0; + viewangles[ROLL] = 0; + VectorClear( moveangles ); +#ifdef MISSIONPACK + UI_PlayerInfo_SetModel( &info2, model, headmodel, "" ); +#else + UI_PlayerInfo_SetModel( &info2, model ); +#endif // #ifdef MISSIONPACK + UI_PlayerInfo_SetInfo( &info2, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MP40, qfalse ); +#ifdef MISSIONPACK + UI_RegisterClientModelname( &info2, model, headmodel, team ); +#else + UI_RegisterClientModelname( &info2, model ); +#endif // #ifdef MISSIONPACK + updateOpponentModel = qfalse; + } + + UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info2, uiInfo.uiDC.realTime / 2 ); + +} + +static void UI_NextOpponent() { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_opponentName" ) ); + int j = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + i++; + if ( i >= uiInfo.teamCount ) { + i = 0; + } + if ( i == j ) { + i++; + if ( i >= uiInfo.teamCount ) { + i = 0; + } + } + trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName ); +} + +static void UI_PriorOpponent() { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_opponentName" ) ); + int j = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + i--; + if ( i < 0 ) { + i = uiInfo.teamCount - 1; + } + if ( i == j ) { + i--; + if ( i < 0 ) { + i = uiInfo.teamCount - 1; + } + } + trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName ); +} + +static void UI_DrawPlayerLogo( rectDef_t *rect, vec3_t color ) { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + + if ( uiInfo.teamList[i].teamIcon == -1 ) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip( uiInfo.teamList[i].imageName ); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip( va( "%s_metal",uiInfo.teamList[i].imageName ) ); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip( va( "%s_name", uiInfo.teamList[i].imageName ) ); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawPlayerLogoMetal( rectDef_t *rect, vec3_t color ) { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + if ( uiInfo.teamList[i].teamIcon == -1 ) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip( uiInfo.teamList[i].imageName ); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip( va( "%s_metal",uiInfo.teamList[i].imageName ) ); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip( va( "%s_name", uiInfo.teamList[i].imageName ) ); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawPlayerLogoName( rectDef_t *rect, vec3_t color ) { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + if ( uiInfo.teamList[i].teamIcon == -1 ) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip( uiInfo.teamList[i].imageName ); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip( va( "%s_metal",uiInfo.teamList[i].imageName ) ); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip( va( "%s_name", uiInfo.teamList[i].imageName ) ); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogo( rectDef_t *rect, vec3_t color ) { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_opponentName" ) ); + if ( uiInfo.teamList[i].teamIcon == -1 ) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip( uiInfo.teamList[i].imageName ); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip( va( "%s_metal",uiInfo.teamList[i].imageName ) ); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip( va( "%s_name", uiInfo.teamList[i].imageName ) ); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogoMetal( rectDef_t *rect, vec3_t color ) { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_opponentName" ) ); + if ( uiInfo.teamList[i].teamIcon == -1 ) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip( uiInfo.teamList[i].imageName ); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip( va( "%s_metal",uiInfo.teamList[i].imageName ) ); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip( va( "%s_name", uiInfo.teamList[i].imageName ) ); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogoName( rectDef_t *rect, vec3_t color ) { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_opponentName" ) ); + if ( uiInfo.teamList[i].teamIcon == -1 ) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip( uiInfo.teamList[i].imageName ); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip( va( "%s_metal",uiInfo.teamList[i].imageName ) ); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip( va( "%s_name", uiInfo.teamList[i].imageName ) ); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawAllMapsSelection( rectDef_t *rect, float scale, vec4_t color, int textStyle, qboolean net ) { +#ifdef MISSIONPACK + int map = ( net ) ? ui_currentNetMap.integer : ui_currentMap.integer; + if ( map >= 0 && map < uiInfo.mapCount ) { + Text_Paint( rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle ); + } +#endif // #ifdef MISSIONPACK +} + +static void UI_DrawOpponentName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + Text_Paint( rect->x, rect->y, scale, color, UI_Cvar_VariableString( "ui_opponentName" ), 0, 0, textStyle ); +} + + +static int UI_OwnerDrawWidth( int ownerDraw, float scale ) { + int i, h, value; + const char *text; + const char *s = NULL; + + switch ( ownerDraw ) { + case UI_HANDICAP: + h = Com_Clamp( 5, 100, trap_Cvar_VariableValue( "handicap" ) ); + i = 20 - h / 5; + s = handicapValues[i]; + break; + case UI_CLANNAME: + s = UI_Cvar_VariableString( "ui_teamName" ); + break; + case UI_GAMETYPE: + s = uiInfo.gameTypes[ui_gameType.integer].gameType; + break; + case UI_SKILL: + i = trap_Cvar_VariableValue( "g_spSkill" ); + if ( i < 1 || i > numSkillLevels ) { + i = 1; + } + s = skillLevels[i - 1]; + break; + case UI_BLUETEAMNAME: + i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_blueTeam" ) ); + if ( i >= 0 && i < uiInfo.teamCount ) { + s = va( "%s: %s", "Blue", uiInfo.teamList[i].teamName ); + } + break; + case UI_REDTEAMNAME: + i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_redTeam" ) ); + if ( i >= 0 && i < uiInfo.teamCount ) { + s = va( "%s: %s", "Red", uiInfo.teamList[i].teamName ); + } + break; + case UI_BLUETEAM1: + case UI_BLUETEAM2: + case UI_BLUETEAM3: + case UI_BLUETEAM4: + case UI_BLUETEAM5: + value = trap_Cvar_VariableValue( va( "ui_blueteam%i", ownerDraw - UI_BLUETEAM1 + 1 ) ); + if ( value <= 0 ) { + text = "Closed"; + } else if ( value == 1 ) { + text = "Human"; + } else { + value -= 2; + if ( value >= uiInfo.aliasCount ) { + value = 0; + } + text = uiInfo.aliasList[value].name; + } + s = va( "%i. %s", ownerDraw - UI_BLUETEAM1 + 1, text ); + break; + case UI_REDTEAM1: + case UI_REDTEAM2: + case UI_REDTEAM3: + case UI_REDTEAM4: + case UI_REDTEAM5: + value = trap_Cvar_VariableValue( va( "ui_redteam%i", ownerDraw - UI_REDTEAM1 + 1 ) ); + if ( value <= 0 ) { + text = "Closed"; + } else if ( value == 1 ) { + text = "Human"; + } else { + value -= 2; + if ( value >= uiInfo.aliasCount ) { + value = 0; + } + text = uiInfo.aliasList[value].name; + } + s = va( "%i. %s", ownerDraw - UI_REDTEAM1 + 1, text ); + break; + case UI_NETSOURCE: + if ( ui_netSource.integer < 0 || ui_netSource.integer > uiInfo.numJoinGameTypes ) { + ui_netSource.integer = 0; + } + s = va( "Source: %s", netSources[ui_netSource.integer] ); + break; + case UI_NETFILTER: + if ( ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters ) { + ui_serverFilterType.integer = 0; + } + s = va( "Filter: %s", serverFilters[ui_serverFilterType.integer].description ); + break; + case UI_TIER: + break; + case UI_TIER_MAPNAME: + break; + case UI_TIER_GAMETYPE: + break; + case UI_ALLMAPS_SELECTION: + break; + case UI_OPPONENT_NAME: + break; + case UI_KEYBINDSTATUS: + if ( Display_KeyBindPending() ) { + s = trap_TranslateString( "Waiting for new key... Press ESCAPE to cancel" ); + } else { + s = trap_TranslateString( "Press ENTER or CLICK to change, Press BACKSPACE to clear" ); + } + break; + case UI_SERVERREFRESHDATE: + s = UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i", ui_netSource.integer ) ); + break; + default: + break; + } + + if ( s ) { + return Text_Width( s, scale, 0 ); + } + return 0; +} + +static void UI_DrawBotName( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { +#ifdef MISSIONPACK + int value = uiInfo.botIndex; + int game = trap_Cvar_VariableValue( "g_gametype" ); + const char *text = ""; + if ( game >= GT_TEAM ) { + if ( value >= uiInfo.characterCount ) { + value = 0; + } + text = uiInfo.characterList[value].name; + } else { + if ( value >= UI_GetNumBots() ) { + value = 0; + } + text = UI_GetBotNameByNumber( value ); + } + Text_Paint( rect->x, rect->y, scale, color, text, 0, 0, textStyle ); +#endif // #ifdef MISSIONPACK +} + +static void UI_DrawBotSkill( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + if ( uiInfo.skillIndex >= 0 && uiInfo.skillIndex < numSkillLevels ) { + Text_Paint( rect->x, rect->y, scale, color, skillLevels[uiInfo.skillIndex], 0, 0, textStyle ); + } +} + +static void UI_DrawRedBlue( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + Text_Paint( rect->x, rect->y, scale, color, ( uiInfo.redBlue == 0 ) ? "Red" : "Blue", 0, 0, textStyle ); +} + +static void UI_DrawCrosshair( rectDef_t *rect, float scale, vec4_t color ) { + trap_R_SetColor( color ); + if ( uiInfo.currentCrosshair < 0 || uiInfo.currentCrosshair >= NUM_CROSSHAIRS ) { + uiInfo.currentCrosshair = 0; + } + UI_DrawHandlePic( rect->x, rect->y - rect->h, rect->w, rect->h, uiInfo.uiDC.Assets.crosshairShader[uiInfo.currentCrosshair] ); + trap_R_SetColor( NULL ); +} + +/* +=============== +UI_BuildPlayerList +=============== +*/ +static void UI_BuildPlayerList() { + uiClientState_t cs; + int n, count, team, team2, playerTeamNumber; + char info[MAX_INFO_STRING]; + + trap_GetClientState( &cs ); + trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING ); + uiInfo.playerNumber = cs.clientNum; + uiInfo.teamLeader = atoi( Info_ValueForKey( info, "tl" ) ); + team = atoi( Info_ValueForKey( info, "t" ) ); + trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ); + count = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + uiInfo.playerCount = 0; + uiInfo.myTeamCount = 0; + playerTeamNumber = 0; + for ( n = 0; n < count; n++ ) { + trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING ); + + if ( info[0] ) { + Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount], Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); + Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] ); + uiInfo.playerCount++; + team2 = atoi( Info_ValueForKey( info, "t" ) ); + if ( team2 == team ) { + Q_strncpyz( uiInfo.teamNames[uiInfo.myTeamCount], Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); + Q_CleanStr( uiInfo.teamNames[uiInfo.myTeamCount] ); + uiInfo.teamClientNums[uiInfo.myTeamCount] = n; + if ( uiInfo.playerNumber == n ) { + playerTeamNumber = uiInfo.myTeamCount; + } + uiInfo.myTeamCount++; + } + } + } + + if ( !uiInfo.teamLeader ) { + trap_Cvar_Set( "cg_selectedPlayer", va( "%d", playerTeamNumber ) ); + } + + n = trap_Cvar_VariableValue( "cg_selectedPlayer" ); + if ( n < 0 || n > uiInfo.myTeamCount ) { + n = 0; + } + if ( n < uiInfo.myTeamCount ) { + trap_Cvar_Set( "cg_selectedPlayerName", uiInfo.teamNames[n] ); + } +} + + +static void UI_DrawSelectedPlayer( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + if ( uiInfo.uiDC.realTime > uiInfo.playerRefresh ) { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } + Text_Paint( rect->x, rect->y, scale, color, ( uiInfo.teamLeader ) ? UI_Cvar_VariableString( "cg_selectedPlayerName" ) : UI_Cvar_VariableString( "name" ), 0, 0, textStyle ); +} + +static void UI_DrawServerRefreshDate( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int serverCount; // NERVE - SMF +//#ifdef MISSIONPACK + if ( uiInfo.serverStatus.refreshActive ) { + vec4_t lowLight, newColor; + lowLight[0] = 0.8 * color[0]; + lowLight[1] = 0.8 * color[1]; + lowLight[2] = 0.8 * color[2]; + lowLight[3] = 0.8 * color[3]; + LerpColor( color,lowLight,newColor,0.5 + 0.5 * sin( uiInfo.uiDC.realTime / PULSE_DIVISOR ) ); + // NERVE - SMF + serverCount = trap_LAN_GetServerCount( ui_netSource.integer ); + if ( serverCount >= 0 ) { + Text_Paint( rect->x, rect->y, scale, newColor, va( trap_TranslateString( "Getting info for %d servers (ESC to cancel)" ), serverCount ), 0, 0, textStyle ); + } else { + Text_Paint( rect->x, rect->y, scale, newColor, trap_TranslateString( "Waiting for response from Master Server" ), 0, 0, textStyle ); + } + } else { + char buff[64]; + Q_strncpyz( buff, UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i", ui_netSource.integer ) ), 64 ); + Text_Paint( rect->x, rect->y, scale, color, va( trap_TranslateString( "Refresh Time: %s" ), buff ), 0, 0, textStyle ); + } +//#endif // #ifdef MISSIONPACK +} + +static void UI_DrawServerMOTD( rectDef_t *rect, float scale, vec4_t color ) { +//#ifdef MISSIONPACK + if ( uiInfo.serverStatus.motdLen ) { + float maxX; + + if ( uiInfo.serverStatus.motdWidth == -1 ) { + uiInfo.serverStatus.motdWidth = 0; + uiInfo.serverStatus.motdPaintX = rect->x + 1; + uiInfo.serverStatus.motdPaintX2 = -1; + } + + if ( uiInfo.serverStatus.motdOffset > uiInfo.serverStatus.motdLen ) { + uiInfo.serverStatus.motdOffset = 0; + uiInfo.serverStatus.motdPaintX = rect->x + 1; + uiInfo.serverStatus.motdPaintX2 = -1; + } + + if ( uiInfo.uiDC.realTime > uiInfo.serverStatus.motdTime ) { + uiInfo.serverStatus.motdTime = uiInfo.uiDC.realTime + 10; + if ( uiInfo.serverStatus.motdPaintX <= rect->x + 2 ) { + if ( uiInfo.serverStatus.motdOffset < uiInfo.serverStatus.motdLen ) { + uiInfo.serverStatus.motdPaintX += Text_Width( &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], scale, 1 ) - 1; + uiInfo.serverStatus.motdOffset++; + } else { + uiInfo.serverStatus.motdOffset = 0; + if ( uiInfo.serverStatus.motdPaintX2 >= 0 ) { + uiInfo.serverStatus.motdPaintX = uiInfo.serverStatus.motdPaintX2; + } else { + uiInfo.serverStatus.motdPaintX = rect->x + rect->w - 2; + } + uiInfo.serverStatus.motdPaintX2 = -1; + } + } else { + //serverStatus.motdPaintX--; + uiInfo.serverStatus.motdPaintX -= 2; + if ( uiInfo.serverStatus.motdPaintX2 >= 0 ) { + //serverStatus.motdPaintX2--; + uiInfo.serverStatus.motdPaintX2 -= 2; + } + } + } + + maxX = rect->x + rect->w - 2; + Text_Paint_Limit( &maxX, uiInfo.serverStatus.motdPaintX, rect->y + rect->h - 3, scale, color, &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], 0, 0 ); + if ( uiInfo.serverStatus.motdPaintX2 >= 0 ) { + float maxX2 = rect->x + rect->w - 2; + Text_Paint_Limit( &maxX2, uiInfo.serverStatus.motdPaintX2, rect->y + rect->h - 3, scale, color, uiInfo.serverStatus.motd, 0, uiInfo.serverStatus.motdOffset ); + } + if ( uiInfo.serverStatus.motdOffset && maxX > 0 ) { + // if we have an offset ( we are skipping the first part of the string ) and we fit the string + if ( uiInfo.serverStatus.motdPaintX2 == -1 ) { + uiInfo.serverStatus.motdPaintX2 = rect->x + rect->w - 2; + } + } else { + uiInfo.serverStatus.motdPaintX2 = -1; + } + + } +//#endif // #ifdef MISSIONPACK +} + +static void UI_DrawKeyBindStatus( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + //int ofs = 0; // TTimo: unused + if ( Display_KeyBindPending() ) { + Text_Paint( rect->x, rect->y, scale, color, trap_TranslateString( "Waiting for new key... Press ESCAPE to cancel" ), 0, 0, textStyle ); + } else { + Text_Paint( rect->x, rect->y, scale, color, trap_TranslateString( "Press ENTER or CLICK to change, Press BACKSPACE to clear" ), 0, 0, textStyle ); + } +} + +static void UI_DrawGLInfo( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + char * eptr; + char buff[4096]; + const char *lines[64]; + int y, numLines, i; + + Text_Paint( rect->x + 2, rect->y, scale, color, va( "VENDOR: %s", uiInfo.uiDC.glconfig.vendor_string ), 0, 30, textStyle ); + Text_Paint( rect->x + 2, rect->y + 15, scale, color, va( "VERSION: %s: %s", uiInfo.uiDC.glconfig.version_string,uiInfo.uiDC.glconfig.renderer_string ), 0, 30, textStyle ); + Text_Paint( rect->x + 2, rect->y + 30, scale, color, va( "PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)", uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits ), 0, 30, textStyle ); + + // build null terminated extension strings + Q_strncpyz( buff, uiInfo.uiDC.glconfig.extensions_string, 4096 ); + eptr = buff; + y = rect->y + 45; + numLines = 0; + // TTimo - don't overflow the line buffer, don't go above 46, as it goes out of the screen anyway + while ( y < rect->y + rect->h && *eptr && numLines < 46 ) + { + while ( *eptr && *eptr == ' ' ) + *eptr++ = '\0'; + + // track start of valid string + if ( *eptr && *eptr != ' ' ) { + lines[numLines++] = eptr; + } + + while ( *eptr && *eptr != ' ' ) + eptr++; + } + + i = 0; + while ( i < numLines ) { + Text_Paint( rect->x + 2, y, scale, color, lines[i++], 0, 20, textStyle ); + if ( i < numLines ) { + Text_Paint( rect->x + rect->w / 2, y, scale, color, lines[i++], 0, 20, textStyle ); + } + y += 10; + if ( y > rect->y + rect->h - 11 ) { + break; + } + } + + +} + +// NERVE - SMF +// TTimo - make the messages wrap and print in the right order (IRC-style) +static void UI_DrawLimboChat( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i, j, count; + char buf[140]; + float x, y; + + memset( buf, 0, 140 ); + + // first count strings + for ( i = 0, count = 0; ; i++, count++ ) { + if ( !trap_GetLimboString( i, buf ) ) { + break; + } + } + + i = 0; j = 0; + do + { + trap_GetLimboString( i, buf ); + x = rect->x; + j += Count_Text_AutoWrap_Paint( x, 0, 410, scale, color, buf, 0, textStyle ); + y = rect->y + 9 * ( count - j ); + if ( j < count ) { + // 410 is the hardcoded UI limbo chat max width + Text_AutoWrap_Paint( x, y, 410, 9, scale, color, buf, 0, textStyle ); + } + i++; + } while ( j < count ); +} +// -NERVE - SMF + +// FIXME: table drive +// +static void UI_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + rectDef_t rect; + + rect.x = x + text_x; + rect.y = y + text_y; + rect.w = w; + rect.h = h; + + switch ( ownerDraw ) { + case UI_HANDICAP: + UI_DrawHandicap( &rect, scale, color, textStyle ); + break; + case UI_EFFECTS: + UI_DrawEffects( &rect, scale, color ); + break; + case UI_PLAYERMODEL: + UI_DrawPlayerModel( &rect ); + break; + case UI_CLANNAME: + UI_DrawClanName( &rect, scale, color, textStyle ); + break; + + case UI_SAVEGAME_SHOT: // (SA) + UI_DrawSaveGameShot( &rect, scale, color ); + break; + + case UI_CLANLOGO: + UI_DrawClanLogo( &rect, scale, color ); + break; + case UI_CLANCINEMATIC: + UI_DrawClanCinematic( &rect, scale, color ); + break; + case UI_PREVIEWCINEMATIC: + UI_DrawPreviewCinematic( &rect, scale, color ); + break; + case UI_GAMETYPE: + UI_DrawGameType( &rect, scale, color, textStyle ); + break; + case UI_NETGAMETYPE: + UI_DrawNetGameType( &rect, scale, color, textStyle ); + break; + case UI_JOINGAMETYPE: + UI_DrawJoinGameType( &rect, scale, color, textStyle ); + break; + case UI_MAPPREVIEW: + UI_DrawMapPreview( &rect, scale, color, qtrue ); + break; + case UI_MAP_TIMETOBEAT: + UI_DrawMapTimeToBeat( &rect, scale, color, textStyle ); + break; + case UI_MAPCINEMATIC: + UI_DrawMapCinematic( &rect, scale, color, qfalse ); + break; + case UI_STARTMAPCINEMATIC: + UI_DrawMapCinematic( &rect, scale, color, qtrue ); + break; + case UI_SKILL: + UI_DrawSkill( &rect, scale, color, textStyle ); + break; + case UI_BLUETEAMNAME: + UI_DrawTeamName( &rect, scale, color, qtrue, textStyle ); + break; + case UI_REDTEAMNAME: + UI_DrawTeamName( &rect, scale, color, qfalse, textStyle ); + break; + case UI_BLUETEAM1: + case UI_BLUETEAM2: + case UI_BLUETEAM3: + case UI_BLUETEAM4: + case UI_BLUETEAM5: + UI_DrawTeamMember( &rect, scale, color, qtrue, ownerDraw - UI_BLUETEAM1 + 1, textStyle ); + break; + case UI_REDTEAM1: + case UI_REDTEAM2: + case UI_REDTEAM3: + case UI_REDTEAM4: + case UI_REDTEAM5: + UI_DrawTeamMember( &rect, scale, color, qfalse, ownerDraw - UI_REDTEAM1 + 1, textStyle ); + break; + case UI_NETSOURCE: + UI_DrawNetSource( &rect, scale, color, textStyle ); + break; + case UI_NETMAPPREVIEW: + UI_DrawNetMapPreview( &rect, scale, color ); + break; + case UI_NETMAPCINEMATIC: + UI_DrawNetMapCinematic( &rect, scale, color ); + break; + case UI_NETFILTER: + UI_DrawNetFilter( &rect, scale, color, textStyle ); + break; + case UI_TIER: + UI_DrawTier( &rect, scale, color, textStyle ); + break; + case UI_OPPONENTMODEL: + UI_DrawOpponent( &rect ); + break; + case UI_TIERMAP1: + UI_DrawTierMap( &rect, 0 ); + break; + case UI_TIERMAP2: + UI_DrawTierMap( &rect, 1 ); + break; + case UI_TIERMAP3: + UI_DrawTierMap( &rect, 2 ); + break; + case UI_PLAYERLOGO: + UI_DrawPlayerLogo( &rect, color ); + break; + case UI_PLAYERLOGO_METAL: + UI_DrawPlayerLogoMetal( &rect, color ); + break; + case UI_PLAYERLOGO_NAME: + UI_DrawPlayerLogoName( &rect, color ); + break; + case UI_OPPONENTLOGO: + UI_DrawOpponentLogo( &rect, color ); + break; + case UI_OPPONENTLOGO_METAL: + UI_DrawOpponentLogoMetal( &rect, color ); + break; + case UI_OPPONENTLOGO_NAME: + UI_DrawOpponentLogoName( &rect, color ); + break; + case UI_TIER_MAPNAME: + UI_DrawTierMapName( &rect, scale, color, textStyle ); + break; + case UI_TIER_GAMETYPE: + UI_DrawTierGameType( &rect, scale, color, textStyle ); + break; + case UI_ALLMAPS_SELECTION: + UI_DrawAllMapsSelection( &rect, scale, color, textStyle, qtrue ); + break; + case UI_MAPS_SELECTION: + UI_DrawAllMapsSelection( &rect, scale, color, textStyle, qfalse ); + break; + case UI_OPPONENT_NAME: + UI_DrawOpponentName( &rect, scale, color, textStyle ); + break; + case UI_BOTNAME: + UI_DrawBotName( &rect, scale, color, textStyle ); + break; + case UI_BOTSKILL: + UI_DrawBotSkill( &rect, scale, color, textStyle ); + break; + case UI_REDBLUE: + UI_DrawRedBlue( &rect, scale, color, textStyle ); + break; + case UI_CROSSHAIR: + UI_DrawCrosshair( &rect, scale, color ); + break; + case UI_SELECTEDPLAYER: + UI_DrawSelectedPlayer( &rect, scale, color, textStyle ); + break; + case UI_SERVERREFRESHDATE: + UI_DrawServerRefreshDate( &rect, scale, color, textStyle ); + break; + case UI_SERVERMOTD: + UI_DrawServerMOTD( &rect, scale, color ); + break; + case UI_GLINFO: + UI_DrawGLInfo( &rect,scale, color, textStyle ); + break; + case UI_KEYBINDSTATUS: + UI_DrawKeyBindStatus( &rect,scale, color, textStyle ); + break; + // NERVE - SMF + case UI_LIMBOCHAT: + UI_DrawLimboChat( &rect,scale, color, textStyle ); + break; + // -NERVE - SMF + default: + break; + } +} + +static qboolean UI_OwnerDrawVisible( int flags ) { + qboolean vis = qtrue; + + while ( flags ) { + + if ( flags & UI_SHOW_FFA ) { + if ( trap_Cvar_VariableValue( "g_gametype" ) != GT_FFA ) { + vis = qfalse; + } + flags &= ~UI_SHOW_FFA; + } + + if ( flags & UI_SHOW_NOTFFA ) { + if ( trap_Cvar_VariableValue( "g_gametype" ) == GT_FFA ) { + vis = qfalse; + } + flags &= ~UI_SHOW_NOTFFA; + } + + if ( flags & UI_SHOW_LEADER ) { + // these need to show when this client can give orders to a player or a group + if ( !uiInfo.teamLeader ) { + vis = qfalse; + } else { + // if showing yourself + if ( ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber ) { + vis = qfalse; + } + } + flags &= ~UI_SHOW_LEADER; + } + if ( flags & UI_SHOW_NOTLEADER ) { + // these need to show when this client is assigning their own status or they are NOT the leader + if ( uiInfo.teamLeader ) { + // if not showing yourself + if ( !( ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber ) ) { + vis = qfalse; + } + } + flags &= ~UI_SHOW_NOTLEADER; + } + if ( flags & UI_SHOW_FAVORITESERVERS ) { + // this assumes you only put this type of display flag on something showing in the proper context + if ( ui_netSource.integer != AS_FAVORITES ) { + vis = qfalse; + } + flags &= ~UI_SHOW_FAVORITESERVERS; + } + if ( flags & UI_SHOW_NOTFAVORITESERVERS ) { + // this assumes you only put this type of display flag on something showing in the proper context + if ( ui_netSource.integer == AS_FAVORITES ) { + vis = qfalse; + } + flags &= ~UI_SHOW_NOTFAVORITESERVERS; + } + if ( flags & UI_SHOW_ANYTEAMGAME ) { + if ( uiInfo.gameTypes[ui_gameType.integer].gtEnum <= GT_TEAM ) { + vis = qfalse; + } + flags &= ~UI_SHOW_ANYTEAMGAME; + } + if ( flags & UI_SHOW_ANYNONTEAMGAME ) { + if ( uiInfo.gameTypes[ui_gameType.integer].gtEnum > GT_TEAM ) { + vis = qfalse; + } + flags &= ~UI_SHOW_ANYNONTEAMGAME; + } + if ( flags & UI_SHOW_NETANYTEAMGAME ) { + if ( uiInfo.gameTypes[ui_netGameType.integer].gtEnum <= GT_TEAM ) { + vis = qfalse; + } + flags &= ~UI_SHOW_NETANYTEAMGAME; + } + if ( flags & UI_SHOW_NETANYNONTEAMGAME ) { + if ( uiInfo.gameTypes[ui_netGameType.integer].gtEnum > GT_TEAM ) { + vis = qfalse; + } + flags &= ~UI_SHOW_NETANYNONTEAMGAME; + } + if ( flags & UI_SHOW_NEWHIGHSCORE ) { + if ( uiInfo.newHighScoreTime < uiInfo.uiDC.realTime ) { + vis = qfalse; + } else { + if ( uiInfo.soundHighScore ) { + if ( trap_Cvar_VariableValue( "sv_killserver" ) == 0 ) { + // wait on server to go down before playing sound + trap_S_StartLocalSound( uiInfo.newHighScoreSound, CHAN_ANNOUNCER ); + uiInfo.soundHighScore = qfalse; + } + } + } + flags &= ~UI_SHOW_NEWHIGHSCORE; + } + if ( flags & UI_SHOW_NEWBESTTIME ) { + if ( uiInfo.newBestTime < uiInfo.uiDC.realTime ) { + vis = qfalse; + } + flags &= ~UI_SHOW_NEWBESTTIME; + } + if ( flags & UI_SHOW_DEMOAVAILABLE ) { + if ( !uiInfo.demoAvailable ) { + vis = qfalse; + } + flags &= ~UI_SHOW_DEMOAVAILABLE; + } else { + flags = 0; + } + } + return vis; +} + +static qboolean UI_Handicap_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + int h; + h = Com_Clamp( 5, 100, trap_Cvar_VariableValue( "handicap" ) ); + if ( key == K_MOUSE2 ) { + h -= 5; + } else { + h += 5; + } + if ( h > 100 ) { + h = 5; + } else if ( h < 0 ) { + h = 100; + } + trap_Cvar_Set( "handicap", va( "%i", h ) ); + return qtrue; + } + return qfalse; +} + +static qboolean UI_Effects_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + + if ( key == K_MOUSE2 ) { + uiInfo.effectsColor--; + } else { + uiInfo.effectsColor++; + } + + if ( uiInfo.effectsColor > 6 ) { + uiInfo.effectsColor = 0; + } else if ( uiInfo.effectsColor < 0 ) { + uiInfo.effectsColor = 6; + } + + trap_Cvar_SetValue( "color", uitogamecode[uiInfo.effectsColor] ); + return qtrue; + } + return qfalse; +} + +static qboolean UI_ClanName_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + int i; + i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + if ( uiInfo.teamList[i].cinematic >= 0 ) { + trap_CIN_StopCinematic( uiInfo.teamList[i].cinematic ); + uiInfo.teamList[i].cinematic = -1; + } + if ( key == K_MOUSE2 ) { + i--; + } else { + i++; + } + if ( i >= uiInfo.teamCount ) { + i = 0; + } else if ( i < 0 ) { + i = uiInfo.teamCount - 1; + } + trap_Cvar_Set( "ui_teamName", uiInfo.teamList[i].teamName ); + updateModel = qtrue; + return qtrue; + } + return qfalse; +} + +static qboolean UI_GameType_HandleKey( int flags, float *special, int key, qboolean resetMap ) { +//#ifdef MISSIONPACK + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + int oldCount = UI_MapCountByGameType( qtrue ); + + // hard coded mess here + if ( key == K_MOUSE2 ) { + ui_gameType.integer--; + if ( ui_gameType.integer == 2 ) { + ui_gameType.integer = 1; + } else if ( ui_gameType.integer < 2 ) { + ui_gameType.integer = uiInfo.numGameTypes - 1; + } + } else { + ui_gameType.integer++; + if ( ui_gameType.integer >= uiInfo.numGameTypes ) { + ui_gameType.integer = 1; + } else if ( ui_gameType.integer == 2 ) { + ui_gameType.integer = 3; + } + } + + if ( uiInfo.gameTypes[ui_gameType.integer].gtEnum == GT_TOURNAMENT ) { + trap_Cvar_Set( "ui_Q3Model", "1" ); + } else { + trap_Cvar_Set( "ui_Q3Model", "0" ); + } + + trap_Cvar_Set( "ui_gameType", va( "%d", ui_gameType.integer ) ); + UI_SetCapFragLimits( qtrue ); + UI_LoadBestScores( uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum ); + if ( resetMap && oldCount != UI_MapCountByGameType( qtrue ) ) { + trap_Cvar_Set( "ui_currentMap", "0" ); + Menu_SetFeederSelection( NULL, FEEDER_MAPS, 0, NULL ); + } + return qtrue; + } +//#endif // #ifdef MISSIONPACK + return qfalse; +} + +static qboolean UI_NetGameType_HandleKey( int flags, float *special, int key ) { +//#ifdef MISSIONPACK + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + + if ( key == K_MOUSE2 ) { + ui_netGameType.integer--; + } else { + ui_netGameType.integer++; + } + + if ( ui_netGameType.integer < 0 ) { + ui_netGameType.integer = uiInfo.numGameTypes - 1; + } else if ( ui_netGameType.integer >= uiInfo.numGameTypes ) { + ui_netGameType.integer = 0; + } + + trap_Cvar_Set( "ui_netGameType", va( "%d", ui_netGameType.integer ) ); + trap_Cvar_Set( "ui_actualnetGameType", va( "%d", uiInfo.gameTypes[ui_netGameType.integer].gtEnum ) ); + trap_Cvar_Set( "ui_currentNetMap", "0" ); + UI_MapCountByGameType( qfalse ); + Menu_SetFeederSelection( NULL, FEEDER_ALLMAPS, 0, NULL ); + return qtrue; + } +//#endif // #ifdef MISSIONPACK + return qfalse; +} + +static qboolean UI_JoinGameType_HandleKey( int flags, float *special, int key ) { +//#ifdef MISSIONPACK + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + + if ( key == K_MOUSE2 ) { + ui_joinGameType.integer--; + } else { + ui_joinGameType.integer++; + } + + if ( ui_joinGameType.integer < 0 ) { + ui_joinGameType.integer = uiInfo.numJoinGameTypes - 1; + } else if ( ui_joinGameType.integer >= uiInfo.numJoinGameTypes ) { + ui_joinGameType.integer = 0; + } + + trap_Cvar_Set( "ui_joinGameType", va( "%d", ui_joinGameType.integer ) ); + UI_BuildServerDisplayList( qtrue ); + return qtrue; + } +//#endif // #ifdef MISSIONPACK + return qfalse; +} + + + +static qboolean UI_Skill_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + int i = trap_Cvar_VariableValue( "g_spSkill" ); + + if ( key == K_MOUSE2 ) { + i--; + } else { + i++; + } + + if ( i < 1 ) { + i = numSkillLevels; + } else if ( i > numSkillLevels ) { + i = 1; + } + + trap_Cvar_Set( "g_spSkill", va( "%i", i ) ); + return qtrue; + } + return qfalse; +} + +static qboolean UI_TeamName_HandleKey( int flags, float *special, int key, qboolean blue ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + int i; + i = UI_TeamIndexFromName( UI_Cvar_VariableString( ( blue ) ? "ui_blueTeam" : "ui_redTeam" ) ); + + if ( key == K_MOUSE2 ) { + i--; + } else { + i++; + } + + if ( i >= uiInfo.teamCount ) { + i = 0; + } else if ( i < 0 ) { + i = uiInfo.teamCount - 1; + } + + trap_Cvar_Set( ( blue ) ? "ui_blueTeam" : "ui_redTeam", uiInfo.teamList[i].teamName ); + + return qtrue; + } + return qfalse; +} + +static qboolean UI_TeamMember_HandleKey( int flags, float *special, int key, qboolean blue, int num ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + // 0 - None + // 1 - Human + // 2..NumCharacters - Bot + char *cvar = va( blue ? "ui_blueteam%i" : "ui_redteam%i", num ); + int value = trap_Cvar_VariableValue( cvar ); + + if ( key == K_MOUSE2 ) { + value--; + } else { + value++; + } + + if ( ui_actualNetGameType.integer >= GT_TEAM ) { + if ( value >= uiInfo.characterCount + 2 ) { + value = 0; + } else if ( value < 0 ) { + value = uiInfo.characterCount + 2 - 1; + } + } else { + if ( value >= UI_GetNumBots() + 2 ) { + value = 0; + } else if ( value < 0 ) { + value = UI_GetNumBots() + 2 - 1; + } + } + + trap_Cvar_Set( cvar, va( "%i", value ) ); + return qtrue; + } + return qfalse; +} + +static qboolean UI_NetSource_HandleKey( int flags, float *special, int key ) { +//#ifdef MISSIONPACK + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + + if ( key == K_MOUSE2 ) { + ui_netSource.integer--; + } else { + ui_netSource.integer++; + } + + if ( ui_netSource.integer >= numNetSources ) { + ui_netSource.integer = 0; + } else if ( ui_netSource.integer < 0 ) { + ui_netSource.integer = numNetSources - 1; + } + + UI_BuildServerDisplayList( qtrue ); + if ( ui_netSource.integer != AS_GLOBAL ) { + UI_StartServerRefresh( qtrue ); + } + trap_Cvar_Set( "ui_netSource", va( "%d", ui_netSource.integer ) ); + return qtrue; + } +//#endif // #ifdef MISSIONPACK + return qfalse; +} + +static qboolean UI_NetFilter_HandleKey( int flags, float *special, int key ) { +//#ifdef MISSIONPACK + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + + if ( key == K_MOUSE2 ) { + ui_serverFilterType.integer--; + } else { + ui_serverFilterType.integer++; + } + + if ( ui_serverFilterType.integer >= numServerFilters ) { + ui_serverFilterType.integer = 0; + } else if ( ui_serverFilterType.integer < 0 ) { + ui_serverFilterType.integer = numServerFilters - 1; + } + UI_BuildServerDisplayList( qtrue ); + return qtrue; + } +//#endif // #ifdef MISSIONPACK + return qfalse; +} + +static qboolean UI_OpponentName_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + if ( key == K_MOUSE2 ) { + UI_PriorOpponent(); + } else { + UI_NextOpponent(); + } + return qtrue; + } + return qfalse; +} + +static qboolean UI_BotName_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + int game = trap_Cvar_VariableValue( "g_gametype" ); + int value = uiInfo.botIndex; + + if ( key == K_MOUSE2 ) { + value--; + } else { + value++; + } + + if ( game >= GT_TEAM ) { + if ( value >= uiInfo.characterCount + 2 ) { + value = 0; + } else if ( value < 0 ) { + value = uiInfo.characterCount + 2 - 1; + } + } else { + if ( value >= UI_GetNumBots() + 2 ) { + value = 0; + } else if ( value < 0 ) { + value = UI_GetNumBots() + 2 - 1; + } + } + uiInfo.botIndex = value; + return qtrue; + } + return qfalse; +} + +static qboolean UI_BotSkill_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + if ( key == K_MOUSE2 ) { + uiInfo.skillIndex--; + } else { + uiInfo.skillIndex++; + } + if ( uiInfo.skillIndex >= numSkillLevels ) { + uiInfo.skillIndex = 0; + } else if ( uiInfo.skillIndex < 0 ) { + uiInfo.skillIndex = numSkillLevels - 1; + } + return qtrue; + } + return qfalse; +} + +static qboolean UI_RedBlue_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + uiInfo.redBlue ^= 1; + return qtrue; + } + return qfalse; +} + +static qboolean UI_Crosshair_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + if ( key == K_MOUSE2 ) { + uiInfo.currentCrosshair--; + } else { + uiInfo.currentCrosshair++; + } + + if ( uiInfo.currentCrosshair >= NUM_CROSSHAIRS ) { + uiInfo.currentCrosshair = 0; + } else if ( uiInfo.currentCrosshair < 0 ) { + uiInfo.currentCrosshair = NUM_CROSSHAIRS - 1; + } + trap_Cvar_Set( "cg_drawCrosshair", va( "%d", uiInfo.currentCrosshair ) ); + return qtrue; + } + return qfalse; +} + + + +static qboolean UI_SelectedPlayer_HandleKey( int flags, float *special, int key ) { + if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER ) { + int selected; + + UI_BuildPlayerList(); + if ( !uiInfo.teamLeader ) { + return qfalse; + } + selected = trap_Cvar_VariableValue( "cg_selectedPlayer" ); + + if ( key == K_MOUSE2 ) { + selected--; + } else { + selected++; + } + + if ( selected > uiInfo.myTeamCount ) { + selected = 0; + } else if ( selected < 0 ) { + selected = uiInfo.myTeamCount; + } + + if ( selected == uiInfo.myTeamCount ) { + trap_Cvar_Set( "cg_selectedPlayerName", "Everyone" ); + } else { + trap_Cvar_Set( "cg_selectedPlayerName", uiInfo.teamNames[selected] ); + } + trap_Cvar_Set( "cg_selectedPlayer", va( "%d", selected ) ); + } + return qfalse; +} + + +static qboolean UI_OwnerDrawHandleKey( int ownerDraw, int flags, float *special, int key ) { + switch ( ownerDraw ) { + case UI_HANDICAP: + return UI_Handicap_HandleKey( flags, special, key ); + break; + case UI_EFFECTS: + return UI_Effects_HandleKey( flags, special, key ); + break; + case UI_CLANNAME: + return UI_ClanName_HandleKey( flags, special, key ); + break; + case UI_GAMETYPE: + return UI_GameType_HandleKey( flags, special, key, qtrue ); + break; + case UI_NETGAMETYPE: + return UI_NetGameType_HandleKey( flags, special, key ); + break; + case UI_JOINGAMETYPE: + return UI_JoinGameType_HandleKey( flags, special, key ); + break; + case UI_SKILL: + return UI_Skill_HandleKey( flags, special, key ); + break; + case UI_BLUETEAMNAME: + return UI_TeamName_HandleKey( flags, special, key, qtrue ); + break; + case UI_REDTEAMNAME: + return UI_TeamName_HandleKey( flags, special, key, qfalse ); + break; + case UI_BLUETEAM1: + case UI_BLUETEAM2: + case UI_BLUETEAM3: + case UI_BLUETEAM4: + case UI_BLUETEAM5: + UI_TeamMember_HandleKey( flags, special, key, qtrue, ownerDraw - UI_BLUETEAM1 + 1 ); + break; + case UI_REDTEAM1: + case UI_REDTEAM2: + case UI_REDTEAM3: + case UI_REDTEAM4: + case UI_REDTEAM5: + UI_TeamMember_HandleKey( flags, special, key, qfalse, ownerDraw - UI_REDTEAM1 + 1 ); + break; + case UI_NETSOURCE: + UI_NetSource_HandleKey( flags, special, key ); + break; + case UI_NETFILTER: + UI_NetFilter_HandleKey( flags, special, key ); + break; + case UI_OPPONENT_NAME: + UI_OpponentName_HandleKey( flags, special, key ); + break; + case UI_BOTNAME: + return UI_BotName_HandleKey( flags, special, key ); + break; + case UI_BOTSKILL: + return UI_BotSkill_HandleKey( flags, special, key ); + break; + case UI_REDBLUE: + UI_RedBlue_HandleKey( flags, special, key ); + break; + case UI_CROSSHAIR: + UI_Crosshair_HandleKey( flags, special, key ); + break; + case UI_SELECTEDPLAYER: + UI_SelectedPlayer_HandleKey( flags, special, key ); + break; + default: + break; + } + + return qfalse; +} + + +static float UI_GetValue( int ownerDraw, int type ) { + return 0; +} + +/* +================= +UI_ServersQsortCompare +================= +*/ +static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ) { +//#ifdef MISSIONPACK + return trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, *(int*)arg1, *(int*)arg2 ); +//#else +// return qfalse; +//#endif // #ifdef MISSIONPACK +} + + +/* +================= +UI_ServersSort +================= +*/ +void UI_ServersSort( int column, qboolean force ) { + + if ( !force ) { + if ( uiInfo.serverStatus.sortKey == column ) { + return; + } + } + + uiInfo.serverStatus.sortKey = column; + qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof( int ), UI_ServersQsortCompare ); +} + + + +/* +=============== +UI_LoadMods +=============== +*/ +static void UI_LoadMods() { + int numdirs; + char dirlist[2048]; + char *dirptr; + char *descptr; + int i; + int dirlen; + + uiInfo.modCount = 0; + numdirs = trap_FS_GetFileList( "$modlist", "", dirlist, sizeof( dirlist ) ); + dirptr = dirlist; + for ( i = 0; i < numdirs; i++ ) { + dirlen = strlen( dirptr ) + 1; + descptr = dirptr + dirlen; + uiInfo.modList[uiInfo.modCount].modName = String_Alloc( dirptr ); + uiInfo.modList[uiInfo.modCount].modDescr = String_Alloc( descptr ); + dirptr += dirlen + strlen( descptr ) + 1; + uiInfo.modCount++; + if ( uiInfo.modCount >= MAX_MODS ) { + break; + } + } + +} + + +/* +=============== +UI_LoadTeams +=============== +*/ +/* +// TTimo: unused +static void UI_LoadTeams() { + char teamList[4096]; + char *teamName; + int i, len, count; + + count = trap_FS_GetFileList( "", "team", teamList, 4096 ); + + if (count) { + teamName = teamList; + for ( i = 0; i < count; i++ ) { + len = strlen( teamName ); + UI_ParseTeamInfo(teamName); + teamName += len + 1; + } + } +} +*/ + +/* +============== +UI_DelSavegame +============== +*/ +static void UI_DelSavegame() { + + int ret; + + ret = trap_FS_Delete( va( "save/%s.svg", uiInfo.savegameList[uiInfo.savegameIndex].name ) ); + trap_FS_Delete( va( "save/images/%s.tga", uiInfo.savegameList[uiInfo.savegameIndex].name ) ); + + if ( ret ) { + Com_Printf( "Deleted savegame: %s.svg\n", uiInfo.savegameList[uiInfo.savegameIndex].name ); + } else { + Com_Printf( "Unable to delete savegame: %s.svg\n", uiInfo.savegameList[uiInfo.savegameIndex].name ); + } +} + +/* +============== +UI_LoadSavegames +============== +*/ +static void UI_LoadSavegames() { + char sglist[4096]; + char *sgname; + int i, len; + + uiInfo.savegameCount = trap_FS_GetFileList( "save", "svg", sglist, 4096 ); + + if ( uiInfo.savegameCount ) { + if ( uiInfo.savegameCount > MAX_SAVEGAMES ) { + uiInfo.savegameCount = MAX_SAVEGAMES; + } + sgname = sglist; + for ( i = 0; i < uiInfo.savegameCount; i++ ) { + + len = strlen( sgname ); + + if ( !Q_strncmp( sgname, "current", 7 ) ) { // ignore current.svg since it has special uses and shouldn't be loaded directly + i--; + uiInfo.savegameCount -= 1; + sgname += len + 1; + continue; + } + + if ( !Q_stricmp( sgname + len - 4,".svg" ) ) { + sgname[len - 4] = '\0'; + } + Q_strupr( sgname ); + uiInfo.savegameList[i].name = String_Alloc( sgname ); + uiInfo.savegameList[i].sshotImage = trap_R_RegisterShaderNoMip( va( "save/images/%s.tga",uiInfo.savegameList[i].name ) ); + sgname += len + 1; + } + } +} + + +/* +=============== +UI_LoadMovies +=============== +*/ +static void UI_LoadMovies() { + char movielist[4096]; + char *moviename; + int i, len; + + uiInfo.movieCount = trap_FS_GetFileList( "video", "roq", movielist, 4096 ); + + if ( uiInfo.movieCount ) { + if ( uiInfo.movieCount > MAX_MOVIES ) { + uiInfo.movieCount = MAX_MOVIES; + } + moviename = movielist; + for ( i = 0; i < uiInfo.movieCount; i++ ) { + len = strlen( moviename ); + if ( !Q_stricmp( moviename + len - 4,".roq" ) ) { + moviename[len - 4] = '\0'; + } + Q_strupr( moviename ); + uiInfo.movieList[i] = String_Alloc( moviename ); + moviename += len + 1; + } + } + +} + + + +/* +=============== +UI_LoadDemos +=============== +*/ +static void UI_LoadDemos() { + char demolist[4096]; + char demoExt[32]; + char *demoname; + int i, len; + + Com_sprintf( demoExt, sizeof( demoExt ), "dm_%d", (int)trap_Cvar_VariableValue( "protocol" ) ); + + uiInfo.demoCount = trap_FS_GetFileList( "demos", demoExt, demolist, 4096 ); + + Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", (int)trap_Cvar_VariableValue( "protocol" ) ); + + if ( uiInfo.demoCount ) { + if ( uiInfo.demoCount > MAX_DEMOS ) { + uiInfo.demoCount = MAX_DEMOS; + } + demoname = demolist; + for ( i = 0; i < uiInfo.demoCount; i++ ) { + len = strlen( demoname ); + if ( !Q_stricmp( demoname + len - strlen( demoExt ), demoExt ) ) { + demoname[len - strlen( demoExt )] = '\0'; + } + Q_strupr( demoname ); + uiInfo.demoList[i] = String_Alloc( demoname ); + demoname += len + 1; + } + } + +} + + +/* +============== +UI_SetNextMap +============== +*/ +/* +// TTimo: unused +static qboolean UI_SetNextMap(int actual, int index) { + int i; + for (i = actual + 1; i < uiInfo.mapCount; i++) { + if (uiInfo.mapList[i].active) { + Menu_SetFeederSelection(NULL, FEEDER_MAPS, index + 1, "skirmish"); + return qtrue; + } + } + return qfalse; +} +*/ + +/* +============== +UI_StartSkirmish +============== +*/ +static void UI_StartSkirmish( qboolean next ) { +#ifdef MISSIONPACK + int i, k, g, delay, temp; + float skill; + char buff[MAX_STRING_CHARS]; + + if ( next ) { + int actual; + int index = trap_Cvar_VariableValue( "ui_mapIndex" ); + UI_MapCountByGameType( qtrue ); + UI_SelectedMap( index, &actual ); + if ( UI_SetNextMap( actual, index ) ) { + } else { + UI_GameType_HandleKey( 0, 0, K_MOUSE1, qfalse ); + UI_MapCountByGameType( qtrue ); + Menu_SetFeederSelection( NULL, FEEDER_MAPS, 0, "skirmish" ); + } + } + + g = uiInfo.gameTypes[ui_gameType.integer].gtEnum; + trap_Cvar_SetValue( "g_gametype", g ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName ) ); + skill = trap_Cvar_VariableValue( "g_spSkill" ); + trap_Cvar_Set( "ui_scoreMap", uiInfo.mapList[ui_currentMap.integer].mapName ); + + k = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_opponentName" ) ); + + trap_Cvar_Set( "ui_singlePlayerActive", "1" ); + + // set up sp overrides, will be replaced on postgame + temp = trap_Cvar_VariableValue( "capturelimit" ); + trap_Cvar_Set( "ui_saveCaptureLimit", va( "%i", temp ) ); + temp = trap_Cvar_VariableValue( "fraglimit" ); + trap_Cvar_Set( "ui_saveFragLimit", va( "%i", temp ) ); + + UI_SetCapFragLimits( qfalse ); + + temp = trap_Cvar_VariableValue( "cg_drawTimer" ); + trap_Cvar_Set( "ui_drawTimer", va( "%i", temp ) ); + temp = trap_Cvar_VariableValue( "g_doWarmup" ); + trap_Cvar_Set( "ui_doWarmup", va( "%i", temp ) ); + temp = trap_Cvar_VariableValue( "g_friendlyFire" ); + trap_Cvar_Set( "ui_friendlyFire", va( "%i", temp ) ); + temp = trap_Cvar_VariableValue( "sv_maxClients" ); + trap_Cvar_Set( "ui_maxClients", va( "%i", temp ) ); + temp = trap_Cvar_VariableValue( "g_warmup" ); + trap_Cvar_Set( "ui_Warmup", va( "%i", temp ) ); + temp = trap_Cvar_VariableValue( "sv_pure" ); + trap_Cvar_Set( "ui_pure", va( "%i", temp ) ); + + trap_Cvar_Set( "cg_cameraOrbit", "0" ); + trap_Cvar_Set( "cg_thirdPerson", "0" ); + trap_Cvar_Set( "cg_drawTimer", "1" ); + trap_Cvar_Set( "g_doWarmup", "1" ); + trap_Cvar_Set( "g_warmup", "15" ); + trap_Cvar_Set( "sv_pure", "0" ); + trap_Cvar_Set( "g_friendlyFire", "0" ); + trap_Cvar_Set( "g_redTeam", UI_Cvar_VariableString( "ui_teamName" ) ); + trap_Cvar_Set( "g_blueTeam", UI_Cvar_VariableString( "ui_opponentName" ) ); + + if ( trap_Cvar_VariableValue( "ui_recordSPDemo" ) ) { + Com_sprintf( buff, MAX_STRING_CHARS, "%s_%i", uiInfo.mapList[ui_currentMap.integer].mapLoadName, g ); + trap_Cvar_Set( "ui_recordSPDemoName", buff ); + } + + delay = 500; + + if ( g == GT_TOURNAMENT ) { + trap_Cvar_Set( "sv_maxClients", "2" ); + Com_sprintf( buff, sizeof( buff ), "wait ; addbot %s %f " ", %i \n", uiInfo.mapList[ui_currentMap.integer].opponentName, skill, delay ); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + } else { + temp = uiInfo.mapList[ui_currentMap.integer].teamMembers * 2; + trap_Cvar_Set( "sv_maxClients", va( "%d", temp ) ); + for ( i = 0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers; i++ ) { + Com_sprintf( buff, sizeof( buff ), "addbot %s %f %s %i %s\n", UI_AIFromName( uiInfo.teamList[k].teamMembers[i] ), skill, ( g == GT_FFA ) ? "" : "Blue", delay, uiInfo.teamList[k].teamMembers[i] ); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + delay += 500; + } + k = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + for ( i = 0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers - 1; i++ ) { + Com_sprintf( buff, sizeof( buff ), "addbot %s %f %s %i %s\n", UI_AIFromName( uiInfo.teamList[k].teamMembers[i] ), skill, ( g == GT_FFA ) ? "" : "Red", delay, uiInfo.teamList[k].teamMembers[i] ); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + delay += 500; + } + } + if ( g >= GT_TEAM ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "wait 5; team Red\n" ); + } +#endif // #ifdef MISSIONPACK +} + +// NERVE - SMF +/* +============== +WM_ChangePlayerType +============== +*/ + +int WM_getWeaponIndex() { + int lookupIndex, i; + + lookupIndex = trap_Cvar_VariableValue( "mp_weapon" ); + + for ( i = 1; weaponTypes[i].name; i++ ) { + if ( weaponTypes[i].value == lookupIndex ) { + return weaponTypes[i].weapindex; + } + } + + return 0; +} + +void WM_getWeaponAnim( const char **torso_anim, const char **legs_anim ) { + int lookupIndex, i; + + lookupIndex = trap_Cvar_VariableValue( "mp_weapon" ); + + for ( i = 1; weaponTypes[i].name; i++ ) { + if ( weaponTypes[i].value == lookupIndex ) { + *torso_anim = weaponTypes[i].torso_anim; + *legs_anim = weaponTypes[i].legs_anim; + return; + } + } +} + +void WM_setItemPic( char *name, const char *shader ) { + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item; + + item = Menu_FindItemByName( menu, name ); + if ( item ) { + item->window.background = DC->registerShaderNoMip( shader ); + } +} + +void WM_setVisibility( char *name, qboolean show ) { + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item; + + item = Menu_FindItemByName( menu, name ); + if ( item ) { + if ( show ) { + item->window.flags |= WINDOW_VISIBLE; + } else { + item->window.flags &= ~WINDOW_VISIBLE; + } + } +} + +void WM_setWeaponPics() { + itemDef_t *knifeDef, *pistolDef, *weaponDef, *grenadeDef, *item1Def, *item2Def; + menuDef_t *menu = Menu_GetFocused(); + int playerType, team, weapon, pistol, item1, i; + const char *gunShader, *grenadeShader; + + knifeDef = Menu_FindItemByName( menu, "window_knife_pic" ); + pistolDef = Menu_FindItemByName( menu, "window_pistol_pic" ); + weaponDef = Menu_FindItemByName( menu, "window_weapon_pic" ); + grenadeDef = Menu_FindItemByName( menu, "window_grenade_pic" ); + item1Def = Menu_FindItemByName( menu, "window_item1_pic" ); + item2Def = Menu_FindItemByName( menu, "window_item2_pic" ); + + if ( !knifeDef ) { + return; + } + + team = trap_Cvar_VariableValue( "mp_team" ); + playerType = trap_Cvar_VariableValue( "mp_playerType" ); + weapon = trap_Cvar_VariableValue( "mp_weapon" ); + pistol = trap_Cvar_VariableValue( "mp_pistol" ); + item1 = trap_Cvar_VariableValue( "mp_item1" ); + + + knifeDef->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_knife.tga" ); + + if ( team == 0 ) { + pistolDef->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_luger.tga" ); + gunShader = "ui_mp/assets/weapon_mp40.tga"; + grenadeShader = "ui_mp/assets/weapon_grenade_ger.tga"; + } else { + pistolDef->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_colt1911.tga" ); + gunShader = "ui_mp/assets/weapon_thompson.tga"; + grenadeShader = "ui_mp/assets/weapon_grenade.tga"; + } + + weaponDef->window.background = DC->registerShaderNoMip( gunShader ); + grenadeDef->window.background = DC->registerShaderNoMip( grenadeShader ); + + if ( playerType == 0 ) { // soldier + item1Def->window.background = DC->registerShaderNoMip( "ui_mp/assets/item_none.tga" ); + item2Def->window.background = DC->registerShaderNoMip( "ui_mp/assets/item_none.tga" ); + + if ( weapon ) { + for ( i = 0; weaponTypes[i].name; i++ ) + if ( weaponTypes[i].value == weapon ) { + weaponDef->window.background = DC->registerShaderNoMip( weaponTypes[i].name ); + break; + } + } + } else if ( playerType == 1 ) { // medic + item1Def->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_syringe.tga" ); + item2Def->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_medheal.tga" ); + } else if ( playerType == 2 ) { // engineer + item1Def->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_pliers.tga" ); + item2Def->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_dynamite.tga" ); + } else if ( playerType == 3 ) { // lieut + item1Def->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_smokegrenade.tga" ); + item2Def->window.background = DC->registerShaderNoMip( "ui_mp/assets/weapon_ammo.tga" ); + + if ( weapon ) { + for ( i = 0; weaponTypes[i].name; i++ ) + if ( weaponTypes[i].value == weapon ) { + weaponDef->window.background = DC->registerShaderNoMip( weaponTypes[i].name ); + break; + } + } + } + + // set button states + WM_setItemPic( "window_axisTeamButton", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_alliedTeamButton", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_specTeamButton", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_classSoldierButton", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_classMedicButton", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_classEngrButton", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_classLieutButton", "ui_mp/assets/button.tga" ); + + if ( team == 0 ) { + WM_setItemPic( "window_axisTeamButton", "ui_mp/assets/button_click.tga" ); + } else if ( team == 1 ) { + WM_setItemPic( "window_alliedTeamButton", "ui_mp/assets/button_click.tga" ); + } else { + WM_setItemPic( "window_specTeamButton", "ui_mp/assets/button_click.tga" ); + } + + if ( playerType == 0 ) { + WM_setItemPic( "window_classSoldierButton", "ui_mp/assets/button_click.tga" ); + } else if ( playerType == 1 ) { + WM_setItemPic( "window_classMedicButton", "ui_mp/assets/button_click.tga" ); + } else if ( playerType == 2 ) { + WM_setItemPic( "window_classEngrButton", "ui_mp/assets/button_click.tga" ); + } else { + WM_setItemPic( "window_classLieutButton", "ui_mp/assets/button_click.tga" ); + } + + // set objective states + WM_setItemPic( "window_objectiveButton0", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_objectiveButton1", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_objectiveButton2", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_objectiveButton3", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_objectiveButton4", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_objectiveButton5", "ui_mp/assets/button.tga" ); + WM_setItemPic( "window_objectiveButton6", "ui_mp/assets/button.tga" ); + + WM_setItemPic( va( "window_objectiveButton%d", uiInfo.selectedObjective ), "ui_mp/assets/button_click.tga" ); + + // set player backgrounds + { + int val; + char *team_str = NULL; // TTimo: init + qboolean skip = qfalse; + + // set team background + val = trap_Cvar_VariableValue( "mp_team" ); + + if ( val == SPECT_TEAM ) { + WM_setItemPic( "modelselection_flag", "multi_spectator" ); + WM_setItemPic( "modelselection_model", "multi_spectator" ); + skip = qtrue; + } else if ( val == AXIS_TEAM ) { + WM_setItemPic( "modelselection_flag", "multi_axisflag" ); + team_str = "axis"; + } else { + WM_setItemPic( "modelselection_flag", "multi_alliedflag" ); + team_str = "allied"; + } + + if ( !skip ) { + // set player type + val = trap_Cvar_VariableValue( "mp_playerType" ); + + if ( val == 0 ) { + WM_setItemPic( "modelselection_model", va( "%s_soldier", team_str ) ); + } else if ( val == 1 ) { + WM_setItemPic( "modelselection_model", va( "%s_medic", team_str ) ); + } else if ( val == 2 ) { + WM_setItemPic( "modelselection_model", va( "%s_eng", team_str ) ); + } else { + WM_setItemPic( "modelselection_model", va( "%s_lt", team_str ) ); + } + + // set weapon pics + if ( weapon ) { + for ( i = 0; weaponTypes[i].name; i++ ) + if ( weaponTypes[i].value == weapon ) { + WM_setItemPic( "modelselection_weap", weaponTypes[i].large_shader ); + break; + } + } + } + } + + // set feeder visibility + if ( playerType == 0 ) { + WM_setVisibility( "window_feeder_soldierweap", qtrue ); + } else { + WM_setVisibility( "window_feeder_soldierweap", qfalse ); + } + + if ( playerType == 3 ) { + WM_setVisibility( "window_feeder_lieutweap", qtrue ); + } else { + WM_setVisibility( "window_feeder_lieutweap", qfalse ); + } + + // don't allow spectators to cycle through menus + if ( team == 2 ) { + WM_setVisibility( "window_pickTeamNext", qfalse ); + WM_setVisibility( "window_pickTeamNextCmd", qfalse ); + WM_setVisibility( "window_pickTeamNextDisabled", qtrue ); + + if ( ui_limboOptions.integer == 1 || ui_limboOptions.integer == 2 ) { + trap_Cvar_Set( "ui_limboOptions", "0" ); + } + } else { + WM_setVisibility( "window_pickTeamNext", qtrue ); + WM_setVisibility( "window_pickTeamNextCmd", qtrue ); + WM_setVisibility( "window_pickTeamNextDisabled", qfalse ); + } +} + +static void WM_ChangePlayerType() { + int i, j, playerType; + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *itemdef, *itemdef2; + + playerType = trap_Cvar_VariableValue( "mp_playerType" ); + + for ( i = 0; i < 4; i++ ) { + itemdef = Menu_FindItemByName( menu, playerTypes[i].name ); + if ( !itemdef ) { + continue; + } + + if ( i == playerType ) { + Menu_ShowItemByName( itemdef->parent, playerTypes[i].name, qtrue ); + } else { + Menu_ShowItemByName( itemdef->parent, playerTypes[i].name, qfalse ); + } + + // selected only settings + if ( i != playerType ) { + continue; + } + + // force all to none first + for ( j = 0; itemTypes[j].name; j++ ) { + itemdef2 = Menu_FindItemByName( menu, itemTypes[j].name ); + if ( itemdef2 ) { + itemdef2->window.background = DC->registerShaderNoMip( "ui_mp/assets/item_none.tga" ); + } + } + + // set values + for ( j = 0; itemTypes[j].name; j++ ) { + itemdef2 = Menu_FindItemByName( menu, itemTypes[j].name ); + if ( itemdef2 && ( playerTypes[i].items & itemTypes[j].flags ) ) { + itemdef2->window.background = DC->registerShaderNoMip( itemTypes[j].shader ); + } + } + } +} + +void WM_GetSpawnPoints() { + char cs[MAX_STRING_CHARS]; + const char *s; + int i; + + trap_GetConfigString( CS_MULTI_INFO, cs, sizeof( cs ) ); + s = Info_ValueForKey( cs, "numspawntargets" ); + + if ( !s || !strlen( s ) ) { + return; + } + + // first index is for autopicking + Q_strncpyz( uiInfo.spawnPoints[0], trap_TranslateString( "Auto Pick" ), MAX_SPAWNDESC ); + + uiInfo.spawnCount = atoi( s ) + 1; + + for ( i = 1; i < uiInfo.spawnCount; i++ ) { + trap_GetConfigString( CS_MULTI_SPAWNTARGETS + i - 1, cs, sizeof( cs ) ); + + s = Info_ValueForKey( cs, "spawn_targ" ); + if ( !s || !strlen( s ) ) { + return; + } + + Q_strncpyz( uiInfo.spawnPoints[i], trap_TranslateString( s ), MAX_SPAWNDESC ); + } +} + +void WM_SetObjective( int objectiveIndex ) { + char cs[MAX_STRING_CHARS], overviewImage[MAX_STRING_CHARS], desc[MAX_STRING_CHARS]; + itemDef_t *def_pic, *def_desc, *def_button; + menuDef_t *menu = Menu_GetFocused(); + int team, numobjectives, i; + char *s, *teamStr; + qboolean playRoq = qfalse; + + uiInfo.selectedObjective = objectiveIndex; + objectiveIndex--; + + // get item defs + def_pic = Menu_FindItemByName( menu, "window_objectivePic" ); + def_desc = Menu_FindItemByName( menu, "window_objectiveDesc" ); + if ( !def_pic || !def_desc ) { + return; + } + + // set proper team + team = trap_Cvar_VariableValue( "mp_team" ); + + if ( team == AXIS_TEAM ) { + teamStr = "axis_desc"; + } else { + teamStr = "allied_desc"; + } + + // get config strings + trap_GetConfigString( CS_MULTI_INFO, cs, sizeof( cs ) ); + s = Info_ValueForKey( cs, "numobjectives" ); + if ( !s || !strlen( s ) ) { + return; + } + numobjectives = atoi( s ); + + // get map overview + s = Info_ValueForKey( cs, "overviewimage" ); + if ( s && strlen( s ) ) { + Q_strncpyz( overviewImage, s, MAX_STRING_CHARS ); + } else { + Q_strncpyz( overviewImage, "menu/art/unknownmap", MAX_STRING_CHARS ); + } + + // enable/disable buttons + for ( i = 0; i < 6; i++ ) { + def_button = Menu_FindItemByName( menu, va( "window_objectiveButton%d", i + 1 ) ); + + if ( !def_button ) { + continue; + } + + if ( i < numobjectives ) { + def_button->window.flags |= WINDOW_VISIBLE; + } else { + def_button->window.flags &= ~WINDOW_VISIBLE; + } + } + + if ( numobjectives < objectiveIndex ) { + return; + } + + // see if we want to play a roq instead + if ( strstr( overviewImage, ".roq" ) ) { + playRoq = qtrue; + } + + // we want overview info + if ( objectiveIndex == -1 ) { + trap_GetConfigString( CS_MULTI_MAPDESC, cs, sizeof( cs ) ); + trap_Cvar_Set( "ui_objective", trap_TranslateString( cs ) ); + + def_pic->window.flags |= WINDOW_VISIBLE; + + if ( playRoq ) { + if ( !atoi( UI_Cvar_VariableString( "r_inGameVideo" ) ) ) { + def_pic->window.style = WINDOW_STYLE_SHADER; + def_pic->window.background = DC->registerShaderNoMip( "menu/art/unknownmap" ); + } else { + def_pic->window.style = WINDOW_STYLE_CINEMATIC; + def_pic->window.cinematic = -1; + def_pic->window.cinematicName = String_Alloc( overviewImage ); + } + } else { + def_pic->window.style = WINDOW_STYLE_SHADER; + def_pic->window.background = DC->registerShaderNoMip( overviewImage ); + } + WM_setWeaponPics(); + return; + } + + trap_GetConfigString( CS_MULTI_OBJECTIVE1 + objectiveIndex, cs, sizeof( cs ) ); + s = Info_ValueForKey( cs, teamStr ); + + if ( s && strlen( s ) ) { + s = trap_TranslateString( s ); + + // NERVE - SMF - get around config strings not having \n by using '*' + for ( i = 0; s[i] != '\0'; i++ ) { + if ( s[i] == '*' ) { + desc[i] = '\n'; + } else { + desc[i] = s[i]; + } + } + desc[i] = '\0'; + + trap_Cvar_Set( "ui_objective", desc ); + } + + // set proper shader + s = Info_ValueForKey( cs, "image" ); + + if ( s && strlen( s ) ) { + def_pic->window.flags |= WINDOW_VISIBLE; + def_pic->window.style = WINDOW_STYLE_SHADER; + def_pic->window.background = DC->registerShaderNoMip( s ); + } else { + def_pic->window.style = WINDOW_STYLE_SHADER; + def_pic->window.background = DC->registerShaderNoMip( overviewImage ); + } + + WM_setWeaponPics(); +} +void WM_SetDefaultWeapon() { + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item; + int startPos, index = 0; + + if ( trap_Cvar_VariableValue( "mp_team" ) == AXIS_TEAM ) { + index = WM_WEAPON_MP40; + trap_Cvar_Set( "mp_weapon", va( "%i", index ) ); + startPos = 0; + } else { + index = WM_WEAPON_THOMPSON; + trap_Cvar_Set( "mp_weapon", va( "%i", index ) ); + startPos = 1; + } + + item = Menu_FindItemByName( menu, "window_feeder_soldierweap" ); + if ( item ) { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if ( listPtr ) { + listPtr->startPos = 0; + } + + item->cursorPos = startPos; + UI_FeederSelection( FEEDER_SOLDIERWEAP, item->cursorPos ); + } + + item = Menu_FindItemByName( menu, "window_feeder_lieutweap" ); + if ( item ) { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if ( listPtr ) { + listPtr->startPos = 0; + } + + item->cursorPos = startPos; + UI_FeederSelection( FEEDER_LIEUTWEAP, item->cursorPos ); + } + + trap_Cvar_Set( weaponTypes[index].cvar, va( "%i", weaponTypes[index].value ) ); + trap_Cvar_Set( "ui_weapon", trap_TranslateString( weaponTypes[index].desc ) ); + + WM_setWeaponPics(); +} + +void WM_PickItem( int selectionType, int itemIndex ) { + //menuDef_t *menu = Menu_GetFocused(); // TTimo: unused + int oldclass, newclass; + + if ( selectionType == WM_SELECT_TEAM ) { + switch ( itemIndex ) { + case WM_AXIS: + trap_Cvar_Set( "mp_team", "0" ); + trap_Cvar_Set( "ui_team", "Axis" ); + WM_SetDefaultWeapon(); + break; + case WM_ALLIES: + trap_Cvar_Set( "mp_team", "1" ); + trap_Cvar_Set( "ui_team", "Allies" ); + WM_SetDefaultWeapon(); + break; + case WM_SPECTATOR: + trap_Cvar_Set( "mp_team", "2" ); + trap_Cvar_Set( "ui_team", "Spectator" ); + WM_SetDefaultWeapon(); + break; + } + } else if ( selectionType == WM_SELECT_CLASS ) { + switch ( itemIndex ) { + case WM_START_SELECT: + break; + case WM_SOLDIER: + oldclass = trap_Cvar_VariableValue( "mp_playerType" ); + newclass = 0; + + trap_Cvar_Set( "mp_playerType", "0" ); + trap_Cvar_Set( "ui_class", "Soldier" ); + + if ( oldclass != newclass ) { + WM_SetDefaultWeapon(); + } + break; + case WM_MEDIC: + trap_Cvar_Set( "mp_playerType", "1" ); + trap_Cvar_Set( "ui_class", "Medic" ); + WM_SetDefaultWeapon(); + break; + case WM_ENGINEER: + trap_Cvar_Set( "mp_playerType", "2" ); + trap_Cvar_Set( "ui_class", "Engineer" ); + WM_SetDefaultWeapon(); + break; + case WM_LIEUTENANT: + oldclass = trap_Cvar_VariableValue( "mp_playerType" ); + newclass = 3; + + trap_Cvar_Set( "mp_playerType", "3" ); + trap_Cvar_Set( "ui_class", "Lieutenant" ); + + if ( oldclass != newclass ) { + WM_SetDefaultWeapon(); + } + break; + } + } else if ( selectionType == WM_SELECT_WEAPON ) { + if ( itemIndex == WM_START_SELECT ) { + } else { + trap_Cvar_Set( weaponTypes[itemIndex].cvar, va( "%i", weaponTypes[itemIndex].value ) ); + trap_Cvar_Set( "ui_weapon", trap_TranslateString( weaponTypes[itemIndex].desc ) ); + } + } + + WM_setWeaponPics(); +} + +void WM_LimboChat() { + char buf[200]; + + trap_Cvar_VariableStringBuffer( "ui_cmd", buf, 200 ); + + if ( strlen( buf ) ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "say %s\n", buf ) ); + } + + trap_Cvar_Set( "ui_cmd", "" ); +} + + +qboolean UI_CheckExecKey( int key ) { + menuDef_t *menu = Menu_GetFocused(); + + if ( g_editingField ) { + return qtrue; + } + + if ( key > 256 ) { + return qfalse; + } + + if ( !menu ) { + if ( !trap_Cvar_VariableValue( "cl_bypassMouseInput" ) ) { + trap_Cvar_Set( "cl_bypassMouseInput", "0" ); + } + return qfalse; + } + + if ( menu->onKey[key] ) { + return qtrue; + } + + return qfalse; +} + +void WM_ActivateLimboChat() { + menuDef_t *menu; + itemDef_t *itemdef; + + menu = Menu_GetFocused(); + menu = Menus_ActivateByName( "wm_limboChat", qtrue ); + + if ( !menu || g_editItem ) { + return; + } + + itemdef = Menu_FindItemByName( menu, "window_limbo_chat" ); + + if ( itemdef ) { + itemdef->cursorPos = 0; + g_editingField = qtrue; + g_editItem = itemdef; + DC->setOverstrikeMode( qtrue ); + } +} +// -NERVE - SMF + +/* +============== +UI_Update +============== +*/ +static void UI_Update( const char *name ) { + int val = trap_Cvar_VariableValue( name ); + + if ( Q_stricmp( name, "ui_SetName" ) == 0 ) { + trap_Cvar_Set( "name", UI_Cvar_VariableString( "ui_Name" ) ); + } else if ( Q_stricmp( name, "ui_setRate" ) == 0 ) { + float rate = trap_Cvar_VariableValue( "rate" ); + if ( rate >= 5000 ) { + trap_Cvar_Set( "cl_maxpackets", "30" ); + trap_Cvar_Set( "cl_packetdup", "1" ); + } else if ( rate >= 4000 ) { + trap_Cvar_Set( "cl_maxpackets", "15" ); + trap_Cvar_Set( "cl_packetdup", "2" ); // favor less prediction errors when there's packet loss + } else { + trap_Cvar_Set( "cl_maxpackets", "15" ); + trap_Cvar_Set( "cl_packetdup", "1" ); // favor lower bandwidth + } + } else if ( Q_stricmp( name, "ui_GetName" ) == 0 ) { + trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString( "name" ) ); + } else if ( Q_stricmp( name, "r_colorbits" ) == 0 ) { + switch ( val ) { + case 0: + trap_Cvar_SetValue( "r_depthbits", 0 ); + trap_Cvar_SetValue( "r_stencilbits", 0 ); + break; + case 16: + trap_Cvar_SetValue( "r_depthbits", 16 ); + trap_Cvar_SetValue( "r_stencilbits", 0 ); + break; + case 32: + trap_Cvar_SetValue( "r_depthbits", 24 ); + break; + } + } else if ( Q_stricmp( name, "r_lodbias" ) == 0 ) { + switch ( val ) { + case 0: + trap_Cvar_SetValue( "r_subdivisions", 4 ); + break; + case 1: + trap_Cvar_SetValue( "r_subdivisions", 12 ); + break; + case 2: + trap_Cvar_SetValue( "r_subdivisions", 20 ); + break; + } + } else if ( Q_stricmp( name, "ui_glCustom" ) == 0 ) { + switch ( val ) { + case 0: // high quality + trap_Cvar_SetValue( "r_fullScreen", 1 ); + trap_Cvar_SetValue( "r_subdivisions", 4 ); + trap_Cvar_SetValue( "r_vertexlight", 0 ); + trap_Cvar_SetValue( "r_lodbias", 0 ); + trap_Cvar_SetValue( "r_colorbits", 32 ); + trap_Cvar_SetValue( "r_depthbits", 24 ); + trap_Cvar_SetValue( "r_picmip", 0 ); + trap_Cvar_SetValue( "r_mode", 4 ); + trap_Cvar_SetValue( "r_texturebits", 32 ); + trap_Cvar_SetValue( "r_fastSky", 0 ); + trap_Cvar_SetValue( "r_inGameVideo", 1 ); + trap_Cvar_SetValue( "cg_shadows", 1 ); + trap_Cvar_SetValue( "cg_brassTime", 2500 ); + trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + break; + case 1: // normal + trap_Cvar_SetValue( "r_fullScreen", 1 ); + trap_Cvar_SetValue( "r_subdivisions", 12 ); + trap_Cvar_SetValue( "r_vertexlight", 0 ); + trap_Cvar_SetValue( "r_lodbias", 0 ); + trap_Cvar_SetValue( "r_colorbits", 0 ); + trap_Cvar_SetValue( "r_depthbits", 24 ); + trap_Cvar_SetValue( "r_picmip", 0 ); + trap_Cvar_SetValue( "r_mode", 3 ); + trap_Cvar_SetValue( "r_texturebits", 0 ); + trap_Cvar_SetValue( "r_fastSky", 0 ); + trap_Cvar_SetValue( "r_inGameVideo", 1 ); + trap_Cvar_SetValue( "cg_brassTime", 2500 ); + trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + trap_Cvar_SetValue( "cg_shadows", 0 ); + break; + case 2: // fast + trap_Cvar_SetValue( "r_fullScreen", 1 ); + trap_Cvar_SetValue( "r_subdivisions", 8 ); + trap_Cvar_SetValue( "r_vertexlight", 0 ); + trap_Cvar_SetValue( "r_lodbias", 1 ); + trap_Cvar_SetValue( "r_colorbits", 0 ); + trap_Cvar_SetValue( "r_depthbits", 0 ); + trap_Cvar_SetValue( "r_picmip", 1 ); + trap_Cvar_SetValue( "r_mode", 3 ); + trap_Cvar_SetValue( "r_texturebits", 0 ); + trap_Cvar_SetValue( "cg_shadows", 0 ); + trap_Cvar_SetValue( "r_fastSky", 1 ); + trap_Cvar_SetValue( "r_inGameVideo", 0 ); + trap_Cvar_SetValue( "cg_brassTime", 0 ); + trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); + break; + case 3: // fastest + trap_Cvar_SetValue( "r_fullScreen", 1 ); + trap_Cvar_SetValue( "r_subdivisions", 20 ); + trap_Cvar_SetValue( "r_vertexlight", 1 ); + trap_Cvar_SetValue( "r_lodbias", 2 ); + trap_Cvar_SetValue( "r_colorbits", 16 ); + trap_Cvar_SetValue( "r_depthbits", 16 ); + trap_Cvar_SetValue( "r_mode", 3 ); + trap_Cvar_SetValue( "r_picmip", 2 ); + trap_Cvar_SetValue( "r_texturebits", 16 ); + trap_Cvar_SetValue( "cg_shadows", 0 ); + trap_Cvar_SetValue( "cg_brassTime", 0 ); + trap_Cvar_SetValue( "r_fastSky", 1 ); + trap_Cvar_SetValue( "r_inGameVideo", 0 ); + trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); + break; + } + } else if ( Q_stricmp( name, "ui_mousePitch" ) == 0 ) { + if ( val == 0 ) { + trap_Cvar_SetValue( "m_pitch", 0.022f ); + } else { + trap_Cvar_SetValue( "m_pitch", -0.022f ); + } + } +} + +/* +============== +UI_UpdateVoteFlags +NOTE TTimo: VOTEFLAGS_RESTART for map_restart vote is present and can be used, but doesn't have any UI binding +============== +*/ +static void UI_UpdateVoteFlags( qboolean open ) { + int flags; + if ( open ) { + flags = trap_Cvar_VariableValue( "g_voteFlags" ); + trap_Cvar_SetValue( "ui_voteRestart", flags & VOTEFLAGS_RESTART ); + trap_Cvar_SetValue( "ui_voteResetMatch", flags & VOTEFLAGS_RESETMATCH ); + trap_Cvar_SetValue( "ui_voteStartMatch", flags & VOTEFLAGS_STARTMATCH ); + trap_Cvar_SetValue( "ui_voteNextMap", flags & VOTEFLAGS_NEXTMAP ); + trap_Cvar_SetValue( "ui_voteSwap", flags & VOTEFLAGS_SWAP ); + trap_Cvar_SetValue( "ui_voteType", flags & VOTEFLAGS_TYPE ); + trap_Cvar_SetValue( "ui_voteKick", flags & VOTEFLAGS_KICK ); + trap_Cvar_SetValue( "ui_voteMap", flags & VOTEFLAGS_MAP ); + } else { + flags = 0; + flags |= trap_Cvar_VariableValue( "ui_voteRestart" ) ? VOTEFLAGS_RESTART : 0; + flags |= trap_Cvar_VariableValue( "ui_voteResetMatch" ) ? VOTEFLAGS_RESETMATCH : 0; + flags |= trap_Cvar_VariableValue( "ui_voteStartMatch" ) ? VOTEFLAGS_STARTMATCH : 0; + flags |= trap_Cvar_VariableValue( "ui_voteNextMap" ) ? VOTEFLAGS_NEXTMAP : 0; + flags |= trap_Cvar_VariableValue( "ui_voteSwap" ) ? VOTEFLAGS_SWAP : 0; + flags |= trap_Cvar_VariableValue( "ui_voteType" ) ? VOTEFLAGS_TYPE : 0; + flags |= trap_Cvar_VariableValue( "ui_voteKick" ) ? VOTEFLAGS_KICK : 0; + flags |= trap_Cvar_VariableValue( "ui_voteMap" ) ? VOTEFLAGS_MAP : 0; + trap_Cvar_SetValue( "g_voteFlags", flags ); + // maintain consistency, if we turned one option back on, set the global on + if ( flags != 0 ) { + trap_Cvar_SetValue( "g_allowVote", 1 ); + } + } +} + +/* +============== +UI_RunMenuScript +============== +*/ +static void UI_RunMenuScript( char **args ) { + const char *name, *name2; + char *s; + char buff[1024]; + int val; // NERVE - SMF + menuDef_t *menu; + + if ( String_Parse( args, &name ) ) { + + if ( Q_stricmp( name, "StartServer" ) == 0 ) { + float skill; + int pb_sv, pb_cl; + + // DHM - Nerve + if ( !ui_dedicated.integer ) { + pb_sv = (int)trap_Cvar_VariableValue( "sv_punkbuster" ); + pb_cl = (int)trap_Cvar_VariableValue( "cl_punkbuster" ); + + if ( pb_sv && !pb_cl ) { + trap_Cvar_Set( "com_errorMessage", "You must either disable PunkBuster on the Server, or enable PunkBuster on the Client before starting a non-dedicated server." ); + return; + } + } + // dhm - Nerve + + trap_Cvar_Set( "cg_thirdPerson", "0" ); + trap_Cvar_Set( "cg_cameraOrbit", "0" ); + trap_Cvar_Set( "ui_singlePlayerActive", "0" ); + trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) ); + trap_Cvar_SetValue( "g_gametype", Com_Clamp( 0, 8, uiInfo.gameTypes[ui_netGameType.integer].gtEnum ) ); + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ) ); + + skill = trap_Cvar_VariableValue( "g_spSkill" ); + + // NERVE - SMF - set user cvars here + // set timelimit + val = trap_Cvar_VariableValue( "ui_userTimelimit" ); + + if ( val != uiInfo.mapList[ui_mapIndex.integer].Timelimit ) { + trap_Cvar_Set( "g_userTimelimit", va( "%i", val ) ); + } else { + trap_Cvar_Set( "g_userTimelimit", "0" ); + } + + // set axis respawn time + val = trap_Cvar_VariableValue( "ui_userAxisRespawnTime" ); + + if ( val != uiInfo.mapList[ui_mapIndex.integer].AxisRespawnTime ) { + trap_Cvar_Set( "g_userAxisRespawnTime", va( "%i", val ) ); + } else { + trap_Cvar_Set( "g_userAxisRespawnTime", "0" ); + } + + // set allied respawn time + val = trap_Cvar_VariableValue( "ui_userAlliedRespawnTime" ); + + if ( val != uiInfo.mapList[ui_mapIndex.integer].AlliedRespawnTime ) { + trap_Cvar_Set( "g_userAlliedRespawnTime", va( "%i", val ) ); + } else { + trap_Cvar_Set( "g_userAlliedRespawnTime", "0" ); + } + // -NERVE - SMF + + } else if ( Q_stricmp( name, "updateSPMenu" ) == 0 ) { + UI_SetCapFragLimits( qtrue ); + UI_MapCountByGameType( qtrue ); + ui_mapIndex.integer = UI_GetIndexFromSelection( ui_currentMap.integer ); + trap_Cvar_Set( "ui_mapIndex", va( "%d", ui_mapIndex.integer ) ); + Menu_SetFeederSelection( NULL, FEEDER_MAPS, ui_mapIndex.integer, "skirmish" ); + UI_GameType_HandleKey( 0, 0, K_MOUSE1, qfalse ); + UI_GameType_HandleKey( 0, 0, K_MOUSE2, qfalse ); + } else if ( Q_stricmp( name, "resetDefaults" ) == 0 ) { + trap_Cmd_ExecuteText( EXEC_NOW, "cvar_restart\n" ); // NERVE - SMF - changed order + trap_Cmd_ExecuteText( EXEC_NOW, "exec default.cfg\n" ); + trap_Cmd_ExecuteText( EXEC_NOW, "exec language.cfg\n" ); // NERVE - SMF + trap_Cmd_ExecuteText( EXEC_NOW, "setRecommended\n" ); // NERVE - SMF + Controls_SetDefaults(); + trap_Cvar_Set( "com_introPlayed", "1" ); + trap_Cvar_Set( "com_recommendedSet", "1" ); // NERVE - SMF + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" ); + } else if ( Q_stricmp( name, "getCDKey" ) == 0 ) { + char out[17]; + trap_GetCDKey( buff, 17 ); + trap_Cvar_Set( "cdkey1", "" ); + trap_Cvar_Set( "cdkey2", "" ); + trap_Cvar_Set( "cdkey3", "" ); + trap_Cvar_Set( "cdkey4", "" ); + if ( strlen( buff ) == CDKEY_LEN ) { + Q_strncpyz( out, buff, 5 ); + trap_Cvar_Set( "cdkey1", out ); + Q_strncpyz( out, buff + 4, 5 ); + trap_Cvar_Set( "cdkey2", out ); + Q_strncpyz( out, buff + 8, 5 ); + trap_Cvar_Set( "cdkey3", out ); + Q_strncpyz( out, buff + 12, 5 ); + trap_Cvar_Set( "cdkey4", out ); + } + + } else if ( Q_stricmp( name, "verifyCDKey" ) == 0 ) { + buff[0] = '\0'; + Q_strcat( buff, 1024, UI_Cvar_VariableString( "cdkey1" ) ); + Q_strcat( buff, 1024, UI_Cvar_VariableString( "cdkey2" ) ); + Q_strcat( buff, 1024, UI_Cvar_VariableString( "cdkey3" ) ); + Q_strcat( buff, 1024, UI_Cvar_VariableString( "cdkey4" ) ); + trap_Cvar_Set( "cdkey", buff ); + if ( trap_VerifyCDKey( buff, UI_Cvar_VariableString( "cdkeychecksum" ) ) ) { + trap_Cvar_Set( "ui_cdkeyvalid", trap_TranslateString( "CD key appears to be valid." ) ); + trap_SetCDKey( buff ); + } else { + trap_Cvar_Set( "ui_cdkeyvalid", trap_TranslateString( "CD key does not appear to be valid." ) ); + } + } else if ( Q_stricmp( name, "loadArenas" ) == 0 ) { + UI_LoadArenas(); + UI_MapCountByGameType( qfalse ); + Menu_SetFeederSelection( NULL, FEEDER_ALLMAPS, 0, NULL ); + } else if ( Q_stricmp( name, "saveControls" ) == 0 ) { + Controls_SetConfig( qtrue ); + } else if ( Q_stricmp( name, "loadControls" ) == 0 ) { + Controls_GetConfig(); + } else if ( Q_stricmp( name, "clearError" ) == 0 ) { + trap_Cvar_Set( "com_errorMessage", "" ); + trap_Cvar_Set( "com_errorDiagnoseIP", "" ); + trap_Cvar_Set( "com_missingFiles", "" ); + } else if ( Q_stricmp( name, "loadGameInfo" ) == 0 ) { + UI_ParseGameInfo( "gameinfo.txt" ); + UI_LoadBestScores( uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum ); + } else if ( Q_stricmp( name, "resetScores" ) == 0 ) { + UI_ClearScores(); + } else if ( Q_stricmp( name, "RefreshServers" ) == 0 ) { + UI_StartServerRefresh( qtrue ); + UI_BuildServerDisplayList( qtrue ); + } else if ( Q_stricmp( name, "RefreshFilter" ) == 0 ) { + UI_StartServerRefresh( qfalse ); + UI_BuildServerDisplayList( qtrue ); + } else if ( Q_stricmp( name, "RunSPDemo" ) == 0 ) { + if ( uiInfo.demoAvailable ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "demo %s_%i", uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum ) ); + } + } else if ( Q_stricmp( name, "LoadDemos" ) == 0 ) { + UI_LoadDemos(); + } else if ( Q_stricmp( name, "LoadMovies" ) == 0 ) { + UI_LoadMovies(); + +//----(SA) added + } else if ( Q_stricmp( name, "LoadSaveGames" ) == 0 ) { // get the list + UI_LoadSavegames(); + } else if ( Q_stricmp( name, "Loadgame" ) == 0 ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "loadgame %s\n", uiInfo.savegameList[uiInfo.savegameIndex].name ) ); + } else if ( Q_stricmp( name, "Savegame" ) == 0 ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "savegame %s\n", UI_Cvar_VariableString( "ui_savegame" ), MAX_NAME_LENGTH ) ); + } else if ( Q_stricmp( name, "DelSavegame" ) == 0 ) { + UI_DelSavegame(); +//----(SA) end + + } else if ( Q_stricmp( name, "LoadMods" ) == 0 ) { + UI_LoadMods(); + } else if ( Q_stricmp( name, "playMovie" ) == 0 ) { + if ( uiInfo.previewMovie >= 0 ) { + trap_CIN_StopCinematic( uiInfo.previewMovie ); + } + trap_Cmd_ExecuteText( EXEC_APPEND, va( "cinematic %s.roq 2\n", uiInfo.movieList[uiInfo.movieIndex] ) ); + } else if ( Q_stricmp( name, "RunMod" ) == 0 ) { + trap_Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName ); + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); + } else if ( Q_stricmp( name, "RunDemo" ) == 0 ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "demo %s\n", uiInfo.demoList[uiInfo.demoIndex] ) ); + } else if ( Q_stricmp( name, "Quake3" ) == 0 ) { + trap_Cvar_Set( "fs_game", "" ); + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); + } else if ( Q_stricmp( name, "closeJoin" ) == 0 ) { + if ( uiInfo.serverStatus.refreshActive ) { + UI_StopServerRefresh(); + uiInfo.serverStatus.nextDisplayRefresh = 0; + uiInfo.nextServerStatusRefresh = 0; + uiInfo.nextFindPlayerRefresh = 0; + UI_BuildServerDisplayList( qtrue ); + } else { + Menus_CloseByName( "joinserver" ); + Menus_OpenByName( "main" ); + } + } else if ( Q_stricmp( name, "StopRefresh" ) == 0 ) { + UI_StopServerRefresh(); + uiInfo.serverStatus.nextDisplayRefresh = 0; + uiInfo.nextServerStatusRefresh = 0; + uiInfo.nextFindPlayerRefresh = 0; + } else if ( Q_stricmp( name, "UpdateFilter" ) == 0 ) { + if ( ui_netSource.integer == AS_LOCAL ) { + UI_StartServerRefresh( qtrue ); + } + UI_BuildServerDisplayList( qtrue ); + UI_FeederSelection( FEEDER_SERVERS, 0 ); + } else if ( Q_stricmp( name, "check_ServerStatus" ) == 0 ) { + s = UI_Cvar_VariableString( "com_errorDiagnoseIP" ); + menu = Menus_FindByName( "ingame_options" ); + if ( strlen( s ) && strcmp( s, "localhost" ) ) { + if ( menu ) { + Menu_ShowItemByName( menu, "ctr_serverinfo", qtrue ); + } + } else + { + if ( menu ) { + Menu_ShowItemByName( menu, "ctr_serverinfo", qfalse ); + } + } + } else if ( Q_stricmp( name, "ServerStatus" ) == 0 ) { + // the server info dialog has been turned into a modal thing + // it can be called in several situations + if ( trap_Cvar_VariableValue( "ui_serverBrowser" ) == 1 ) { + // legacy, from the server browser + trap_LAN_GetServerAddressString( ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], uiInfo.serverStatusAddress, sizeof( uiInfo.serverStatusAddress ) ); + UI_BuildServerStatus( qtrue ); + } else + { + // use com_errorDiagnoseIP otherwise + s = UI_Cvar_VariableString( "com_errorDiagnoseIP" ); + if ( strlen( s ) && strcmp( s, "localhost" ) ) { + trap_Cvar_VariableStringBuffer( "com_errorDiagnoseIP", uiInfo.serverStatusAddress, sizeof( uiInfo.serverStatusAddress ) ); + uiInfo.serverStatus.numDisplayServers = 1; // this is ugly, have to force a non zero display server count to emit the query + UI_BuildServerStatus( qtrue ); + } else + { + // we can't close the menu from here, it's not open yet .. (that's the onOpen script) + Com_Printf( "Can't show Server Info (not found, or local server)\n" ); + } + } + } else if ( Q_stricmp( name, "ServerStatus_diagnose" ) == 0 ) { + // query server and display the URL buttons if the error happened during a server connection situation + s = UI_Cvar_VariableString( "com_errorDiagnoseIP" ); + menu = Menus_FindByName( "error_popmenu_diagnose" ); + if ( strlen( s ) && strcmp( s, "localhost" ) ) { + trap_Cvar_VariableStringBuffer( "com_errorDiagnoseIP", uiInfo.serverStatusAddress, sizeof( uiInfo.serverStatusAddress ) ); + uiInfo.serverStatus.numDisplayServers = 1; // this is ugly, have to force a non zero display server count to emit the query + // toggle the "Server Info" button + if ( menu ) { + Menu_ShowItemByName( menu, "serverinfo", qtrue ); + } + UI_BuildServerStatus( qtrue ); + } else + { + // don't send getinfo packet, hide "Server Info" button + if ( menu ) { + Menu_ShowItemByName( menu, "serverinfo", qfalse ); + } + } + } else if ( Q_stricmp( name, "FoundPlayerServerStatus" ) == 0 ) { + Q_strncpyz( uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof( uiInfo.serverStatusAddress ) ); + UI_BuildServerStatus( qtrue ); + Menu_SetFeederSelection( NULL, FEEDER_FINDPLAYER, 0, NULL ); + } else if ( Q_stricmp( name, "FindPlayer" ) == 0 ) { + UI_BuildFindPlayerList( qtrue ); + // clear the displayed server status info + uiInfo.serverStatusInfo.numLines = 0; + Menu_SetFeederSelection( NULL, FEEDER_FINDPLAYER, 0, NULL ); + } else if ( Q_stricmp( name, "JoinServer" ) == 0 ) { + trap_Cvar_Set( "cg_thirdPerson", "0" ); + trap_Cvar_Set( "cg_cameraOrbit", "0" ); + trap_Cvar_Set( "ui_singlePlayerActive", "0" ); + if ( uiInfo.serverStatus.currentServer >= 0 && uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers ) { + trap_LAN_GetServerAddressString( ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, 1024 ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", buff ) ); + } + } else if ( Q_stricmp( name, "FoundPlayerJoinServer" ) == 0 ) { + trap_Cvar_Set( "ui_singlePlayerActive", "0" ); + if ( uiInfo.currentFoundPlayerServer >= 0 && uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer] ) ); + } + } else if ( Q_stricmp( name, "Quit" ) == 0 ) { + trap_Cvar_Set( "ui_singlePlayerActive", "0" ); + trap_Cmd_ExecuteText( EXEC_NOW, "quit" ); + } else if ( Q_stricmp( name, "Controls" ) == 0 ) { + trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName( "setup_menu2", qtrue ); + } else if ( Q_stricmp( name, "Leave" ) == 0 ) { + // ATVI Wolfenstein Misc #460 + // if we are running a local server, make sure we kill it cleanly for other clients + if ( trap_Cvar_VariableValue( "sv_running" ) ) { + trap_Cvar_Set( "sv_killserver", "1" ); + } else + { + trap_Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName( "main", qtrue ); + } + } else if ( Q_stricmp( name, "ServerSort" ) == 0 ) { + int sortColumn; + if ( Int_Parse( args, &sortColumn ) ) { + // if same column we're already sorting on then flip the direction + if ( sortColumn == uiInfo.serverStatus.sortKey ) { + uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir; + } + // make sure we sort again + UI_ServersSort( sortColumn, qtrue ); + } + } else if ( Q_stricmp( name, "nextSkirmish" ) == 0 ) { + UI_StartSkirmish( qtrue ); + } else if ( Q_stricmp( name, "SkirmishStart" ) == 0 ) { + UI_StartSkirmish( qfalse ); + } else if ( Q_stricmp( name, "closeingame" ) == 0 ) { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + } else if ( Q_stricmp( name, "voteMap" ) == 0 ) { + if ( ui_currentNetMap.integer >= 0 && ui_currentNetMap.integer < uiInfo.mapCount ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote map %s\n",uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ) ); + } + } else if ( Q_stricmp( name, "voteKick" ) == 0 ) { + if ( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote kick \"%s\"\n",uiInfo.playerNames[uiInfo.playerIndex] ) ); + } + } else if ( Q_stricmp( name, "voteGame" ) == 0 ) { + if ( ui_netGameType.integer >= 0 && ui_netGameType.integer < uiInfo.numGameTypes ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote g_gametype %i\n",uiInfo.gameTypes[ui_netGameType.integer].gtEnum ) ); + } + } else if ( Q_stricmp( name, "voteLeader" ) == 0 ) { + if ( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote leader %s\n",uiInfo.teamNames[uiInfo.teamIndex] ) ); + } + } else if ( Q_stricmp( name, "addBot" ) == 0 ) { + if ( trap_Cvar_VariableValue( "g_gametype" ) >= GT_TEAM ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "addbot %s %i %s\n", uiInfo.characterList[uiInfo.botIndex].name, uiInfo.skillIndex + 1, ( uiInfo.redBlue == 0 ) ? "Red" : "Blue" ) ); + } else { + // NERVE - SMF - no bots in wolf multiplayer +// trap_Cmd_ExecuteText( EXEC_APPEND, va("addbot %s %i %s\n", UI_GetBotNameByNumber(uiInfo.botIndex), uiInfo.skillIndex+1, (uiInfo.redBlue == 0) ? "Red" : "Blue") ); + } + } else if ( Q_stricmp( name, "addFavorite" ) == 0 ) { + if ( ui_netSource.integer != AS_FAVORITES ) { + char name[MAX_NAME_LENGTH]; + char addr[MAX_NAME_LENGTH]; + int res; + + trap_LAN_GetServerInfo( ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS ); + name[0] = addr[0] = '\0'; + Q_strncpyz( name, Info_ValueForKey( buff, "hostname" ), MAX_NAME_LENGTH ); + Q_strncpyz( addr, Info_ValueForKey( buff, "addr" ), MAX_NAME_LENGTH ); + if ( strlen( name ) > 0 && strlen( addr ) > 0 ) { + res = trap_LAN_AddServer( AS_FAVORITES, name, addr ); + if ( res == 0 ) { + // server already in the list + Com_Printf( trap_TranslateString( "Favorite already in list\n" ) ); + } else if ( res == -1 ) { + // list full + Com_Printf( trap_TranslateString( "Favorite list full\n" ) ); + } else { + // successfully added + Com_Printf( trap_TranslateString( "Added favorite server %s\n" ), addr ); + } + } + } + } else if ( Q_stricmp( name, "deleteFavorite" ) == 0 ) { + if ( ui_netSource.integer == AS_FAVORITES ) { + char addr[MAX_NAME_LENGTH]; + trap_LAN_GetServerInfo( ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS ); + addr[0] = '\0'; + Q_strncpyz( addr, Info_ValueForKey( buff, "addr" ), MAX_NAME_LENGTH ); + if ( strlen( addr ) > 0 ) { + trap_LAN_RemoveServer( AS_FAVORITES, addr ); + } + } + } else if ( Q_stricmp( name, "createFavorite" ) == 0 ) { + if ( ui_netSource.integer == AS_FAVORITES ) { + char name[MAX_NAME_LENGTH]; + char addr[MAX_NAME_LENGTH]; + int res; + + name[0] = addr[0] = '\0'; + Q_strncpyz( name, UI_Cvar_VariableString( "ui_favoriteName" ), MAX_NAME_LENGTH ); + Q_strncpyz( addr, UI_Cvar_VariableString( "ui_favoriteAddress" ), MAX_NAME_LENGTH ); + if ( strlen( name ) > 0 && strlen( addr ) > 0 ) { + res = trap_LAN_AddServer( AS_FAVORITES, name, addr ); + if ( res == 0 ) { + // server already in the list + Com_Printf( trap_TranslateString( "Favorite already in list\n" ) ); + } else if ( res == -1 ) { + // list full + Com_Printf( trap_TranslateString( "Favorite list full\n" ) ); + } else { + // successfully added + Com_Printf( trap_TranslateString( "Added favorite server %s\n" ), addr ); + } + } + } + } else if ( Q_stricmp( name, "orders" ) == 0 ) { + const char *orders; + if ( String_Parse( args, &orders ) ) { + int selectedPlayer = trap_Cvar_VariableValue( "cg_selectedPlayer" ); + if ( selectedPlayer < uiInfo.myTeamCount ) { + strcpy( buff, orders ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( buff, uiInfo.teamClientNums[selectedPlayer] ) ); + trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); + } else { + int i; + for ( i = 0; i < uiInfo.myTeamCount; i++ ) { + if ( Q_stricmp( UI_Cvar_VariableString( "name" ), uiInfo.teamNames[i] ) == 0 ) { + continue; + } + strcpy( buff, orders ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( buff, uiInfo.teamNames[i] ) ); + trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); + } + } + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + } + } else if ( Q_stricmp( name, "voiceOrdersTeam" ) == 0 ) { + const char *orders; + if ( String_Parse( args, &orders ) ) { + int selectedPlayer = trap_Cvar_VariableValue( "cg_selectedPlayer" ); + if ( selectedPlayer == uiInfo.myTeamCount ) { + trap_Cmd_ExecuteText( EXEC_APPEND, orders ); + trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); + } + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + } + } else if ( Q_stricmp( name, "voiceOrders" ) == 0 ) { + const char *orders; + if ( String_Parse( args, &orders ) ) { + int selectedPlayer = trap_Cvar_VariableValue( "cg_selectedPlayer" ); + if ( selectedPlayer < uiInfo.myTeamCount ) { + strcpy( buff, orders ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( buff, uiInfo.teamClientNums[selectedPlayer] ) ); + trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); + } + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + } + } else if ( Q_stricmp( name, "glCustom" ) == 0 ) { + trap_Cvar_Set( "ui_glCustom", "4" ); + } else if ( Q_stricmp( name, "update" ) == 0 ) { + if ( String_Parse( args, &name2 ) ) { + UI_Update( name2 ); + } + // NERVE - SMF + } else if ( Q_stricmp( name, "startSingleplayer" ) == 0 ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "startSingleplayer\n" ); + } else if ( Q_stricmp( name, "wm_showPickPlayer" ) == 0 ) { + Menus_CloseAll(); + Menus_OpenByName( "wm_pickplayer" ); + } else if ( Q_stricmp( name, "wm_showPickTeam" ) == 0 ) { + Menus_CloseAll(); + Menus_OpenByName( "wm_pickteam" ); + } else if ( Q_stricmp( name, "changePlayerType" ) == 0 ) { + WM_ChangePlayerType(); + } else if ( Q_stricmp( name, "setWeaponPics" ) == 0 ) { + WM_setWeaponPics(); + } else if ( Q_stricmp( name, "getSpawnPoints" ) == 0 ) { + WM_GetSpawnPoints(); + } else if ( Q_stricmp( name, "showSpecScores" ) == 0 ) { + if ( atoi( UI_Cvar_VariableString( "ui_isSpectator" ) ) ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "+scores\n" ); + } + } else if ( Q_stricmp( name, "wm_sayPlayerClass" ) == 0 ) { + int playerType; + const char *s; + + playerType = trap_Cvar_VariableValue( "mp_currentPlayerType" ); + + if ( playerType == 1 ) { + s = "IamMedic"; + } else if ( playerType == 2 ) { + s = "IamEngineer"; + } else if ( playerType == 3 ) { + s = "IamLieutenant"; + } else { + s = "IamSoldier"; + } + + trap_Cmd_ExecuteText( EXEC_APPEND, va( "VoiceTeamChat %s\n", s ) ); + + } else if ( Q_stricmp( name, "showObjectiveView" ) == 0 ) { + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item; + + if ( trap_Cvar_VariableValue( "ui_isSpectator" ) ) { + item = Menu_FindItemByName( menu, "window_tab2" ); + if ( item ) { + item->window.flags &= ~WINDOW_VISIBLE; + } + + item = Menu_FindItemByName( menu, "window_tab1" ); + if ( item ) { + item->window.flags |= WINDOW_VISIBLE; + } + + trap_Cvar_Set( "ui_limboObjective", "1" ); + } else { + item = Menu_FindItemByName( menu, "window_tab2" ); + if ( item ) { + item->window.flags |= WINDOW_VISIBLE; + } + + item = Menu_FindItemByName( menu, "window_tab1" ); + if ( item ) { + item->window.flags &= ~WINDOW_VISIBLE; + } + + trap_Cvar_Set( "ui_limboObjective", "0" ); + } + + } else if ( Q_stricmp( name, "wm_pickitem2" ) == 0 ) { + const char *param, *param2; + int selectType = 0, itemIndex = 0; + + if ( String_Parse( args, ¶m ) && String_Parse( args, ¶m2 ) ) { + selectType = atoi( param ); + itemIndex = atoi( param2 ); + WM_PickItem( selectType, itemIndex ); + } + } else if ( Q_stricmp( name, "setLimboOptionMenu" ) == 0 ) { + int indexNum; + + if ( String_Parse( args, &name ) ) { + indexNum = atoi( name ); + trap_Cvar_Set( "ui_limboOptions", va( "%i", indexNum ) ); + } + } else if ( Q_stricmp( name, "showSpawnWindow" ) == 0 ) { + int indexNum; + + if ( String_Parse( args, &name ) ) { + int options, current; + + current = trap_Cvar_VariableValue( "ui_limboOptions" ); + indexNum = atoi( name ); + + if ( indexNum && current != 3 ) { + options = current; + + trap_Cvar_Set( "ui_limboOptions", "3" ); + trap_Cvar_Set( "ui_limboPrevOptions", va( "%i", options ) ); + } else if ( !indexNum && current == 3 ) { + options = trap_Cvar_VariableValue( "ui_limboPrevOptions" ); + + trap_Cvar_Set( "ui_limboOptions", va( "%i", options ) ); + } + } + } else if ( Q_stricmp( name, "startMultiplayer" ) == 0 ) { + int team, oldteam, playerType, weapon, pistol, item1, i; + const char *teamStr, *classStr, *weapStr; + + // get cvars + team = trap_Cvar_VariableValue( "mp_team" ); + oldteam = trap_Cvar_VariableValue( "mp_currentTeam" ); + playerType = trap_Cvar_VariableValue( "mp_playerType" ); + weapon = trap_Cvar_VariableValue( "mp_weapon" ); + pistol = trap_Cvar_VariableValue( "mp_pistol" ); + item1 = trap_Cvar_VariableValue( "mp_item1" ); + + // print center message + if ( team == AXIS_TEAM ) { + teamStr = "Axis"; + } else if ( team == ALLIES_TEAM ) { + teamStr = "Allied"; + } else { + teamStr = "Spectator"; + } + + if ( playerType == 0 ) { + classStr = "soldier"; + } else if ( playerType == 1 ) { + classStr = "medic"; + } else if ( playerType == 2 ) { + classStr = "engineer"; + } else { + classStr = "lieutenant"; + } + + weapStr = ""; + for ( i = 0; weaponTypes[i].name; i++ ) { + if ( weaponTypes[i].value == weapon ) { + weapStr = weaponTypes[i].desc; + } + } + + if ( team != 2 ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "limbomessage \"%s\" \"%s\" \"%s\"\n", teamStr, classStr, weapStr ) ); + } + + if ( team != oldteam ) { + // join team + if ( team == 0 ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "team %s %i %i %i %i 1\n", "red", playerType, weapon, pistol, item1 ) ); + } else if ( team == 1 ) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "team %s %i %i %i %i 1\n", "blue", playerType, weapon, pistol, item1 ) ); + } else { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "team %s %i %i %i %i 1\n", "s", playerType, weapon, pistol, item1 ) ); + } + } + + // either close menu or bring up zoomed window + Menus_CloseAll(); + } else if ( Q_stricmp( name, "limboChat" ) == 0 ) { + WM_LimboChat(); + } else if ( Q_stricmp( name, "activateLimboChat" ) == 0 ) { + WM_ActivateLimboChat(); + } else if ( Q_stricmp( name, "setObjective" ) == 0 ) { + int objectiveIndex; + + if ( Int_Parse( args, &objectiveIndex ) ) { + WM_SetObjective( objectiveIndex ); + } + // -NERVE - SMF + // DHM - Nerve :: PunkBuster + } else if ( Q_stricmp( name, "setPbClStatus" ) == 0 ) { + int stat; + + if ( Int_Parse( args, &stat ) ) { + trap_SetPbClStatus( stat ); + } + // DHM - Nerve + // TTimo + } else if ( Q_stricmp( name, "togglePbSvStatus" ) == 0 ) { + int sv_pb = trap_Cvar_VariableValue( "sv_punkbuster" ); + if ( sv_pb ) { + trap_SetPbSvStatus( 0 ); + } else { + trap_SetPbSvStatus( 1 ); + } + } else if ( Q_stricmp( name, "openModURL" ) == 0 ) { + trap_Cvar_Set( "ui_finalURL", UI_Cvar_VariableString( "ui_modURL" ) ); + } else if ( Q_stricmp( name, "openServerURL" ) == 0 ) { + trap_Cvar_Set( "ui_finalURL", UI_Cvar_VariableString( "ui_URL" ) ); + } else if ( Q_stricmp( name, "validate_openURL" ) == 0 ) { + // this is the only one that effectively triggers the URL, after the disclaimers are done with + // we use ui_finalURL as an auxiliary variable to gather URLs from various sources + trap_openURL( UI_Cvar_VariableString( "ui_finalURL" ) ); + } else if ( Q_stricmp( name, "update_voteFlags" ) == 0 ) { + // update g_voteFlags according to g_allowVote value change + if ( trap_Cvar_VariableValue( "g_allowVote" ) != 0 ) { + trap_Cvar_SetValue( "g_voteFlags", 255 ); + } else { + trap_Cvar_SetValue( "g_voteFlags", 0 ); + } + UI_UpdateVoteFlags( qtrue ); + } else if ( Q_stricmp( name, "voteFlags" ) == 0 ) { + // createserver.menu, settings allowed / not allowed votes + if ( String_Parse( args, &name ) ) { + if ( Q_stricmp( name, "open" ) == 0 ) { + UI_UpdateVoteFlags( qtrue ); + } else { + UI_UpdateVoteFlags( qfalse ); + } + } + } else if ( Q_stricmp( name, "clientShowVote" ) == 0 ) { + // client side: only show the available votes + int flags; + menu = Menus_FindByName( "ingame_callvote" ); + flags = trap_Cvar_VariableValue( "cg_ui_voteFlags" ); + Menu_ShowItemByName( menu, "misc_resetmatch", flags & VOTEFLAGS_RESETMATCH ); + Menu_ShowItemByName( menu, "misc_startmatch", flags & VOTEFLAGS_STARTMATCH ); + Menu_ShowItemByName( menu, "misc_nextmap", flags & VOTEFLAGS_NEXTMAP ); + Menu_ShowItemByName( menu, "misc_swap", flags & VOTEFLAGS_SWAP ); + Menu_ShowItemByName( menu, "ctr_gametype", flags & VOTEFLAGS_TYPE ); + Menu_ShowItemByName( menu, "ctr_kickplayer", flags & VOTEFLAGS_KICK ); + Menu_ShowItemByName( menu, "ctr_changemap", flags & VOTEFLAGS_MAP ); + } else if ( Q_stricmp( name, "clientCheckVote" ) == 0 ) { + int flags; + flags = trap_Cvar_VariableValue( "cg_ui_voteFlags" ); + if ( ( flags | VOTEFLAGS_RESTART ) == VOTEFLAGS_RESTART ) { + trap_Cvar_SetValue( "cg_ui_novote", 1 ); + } else { + trap_Cvar_SetValue( "cg_ui_novote", 0 ); + } + } else if ( Q_stricmp( name, "reconnect" ) == 0 ) { + // TODO: if dumped because of cl_allowdownload problem, toggle on first (we don't have appropriate support for this yet) + trap_Cmd_ExecuteText( EXEC_APPEND, "reconnect" ); + } else { + Com_Printf( "unknown UI script %s\n", name ); + } + } +} + +static void UI_GetTeamColor( vec4_t *color ) { +} + +/* +================== +UI_MapCountByGameType +================== +*/ +static int UI_MapCountByGameType( qboolean singlePlayer ) { + int i, c, game; + c = 0; + game = singlePlayer ? uiInfo.gameTypes[ui_gameType.integer].gtEnum : uiInfo.gameTypes[ui_netGameType.integer].gtEnum; + if ( game == GT_SINGLE_PLAYER ) { + game++; + } + if ( game == GT_TEAM ) { + game = GT_FFA; + } + + for ( i = 0; i < uiInfo.mapCount; i++ ) { + uiInfo.mapList[i].active = qfalse; + if ( uiInfo.mapList[i].typeBits & ( 1 << game ) ) { + if ( singlePlayer ) { + if ( !( uiInfo.mapList[i].typeBits & ( 1 << GT_SINGLE_PLAYER ) ) ) { + continue; + } + } + c++; + uiInfo.mapList[i].active = qtrue; + } + } + return c; +} + +/* +================== +UI_InsertServerIntoDisplayList +================== +*/ +static void UI_InsertServerIntoDisplayList( int num, int position ) { + int i; + + if ( position < 0 || position > uiInfo.serverStatus.numDisplayServers ) { + return; + } + // + uiInfo.serverStatus.numDisplayServers++; + for ( i = uiInfo.serverStatus.numDisplayServers; i > position; i-- ) { + uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i - 1]; + } + uiInfo.serverStatus.displayServers[position] = num; +} + +/* +================== +UI_RemoveServerFromDisplayList +================== +*/ +static void UI_RemoveServerFromDisplayList( int num ) { + int i, j; + + for ( i = 0; i < uiInfo.serverStatus.numDisplayServers; i++ ) { + if ( uiInfo.serverStatus.displayServers[i] == num ) { + uiInfo.serverStatus.numDisplayServers--; + for ( j = i; j < uiInfo.serverStatus.numDisplayServers; j++ ) { + uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j + 1]; + } + return; + } + } +} + +/* +================== +UI_BinaryServerInsertion +================== +*/ +static void UI_BinaryServerInsertion( int num ) { + int mid, offset, res, len; + + // use binary search to insert server + len = uiInfo.serverStatus.numDisplayServers; + mid = len; + offset = 0; + res = 0; + while ( mid > 0 ) { + mid = len >> 1; + // + res = trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, + uiInfo.serverStatus.sortDir, num, uiInfo.serverStatus.displayServers[offset + mid] ); + // if equal + if ( res == 0 ) { + UI_InsertServerIntoDisplayList( num, offset + mid ); + return; + } + // if larger + else if ( res == 1 ) { + offset += mid; + len -= mid; + } + // if smaller + else { + len -= mid; + } + } + if ( res == 1 ) { + offset++; + } + UI_InsertServerIntoDisplayList( num, offset ); +} + +/* +================== +UI_BuildServerDisplayList +================== +*/ +static void UI_BuildServerDisplayList( qboolean force ) { + int i, count, clients, maxClients, ping, game, len, visible, friendlyFire, tourney, maxlives, punkbuster, antilag; + char info[MAX_STRING_CHARS]; + //qboolean startRefresh = qtrue; // TTimo: unused + static int numinvisible; + + game = 0; // NERVE - SMF - shut up compiler warning + + if ( !( force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh ) ) { + return; + } + // if we shouldn't reset + if ( force == 2 ) { + force = 0; + } + + // do motd updates here too + trap_Cvar_VariableStringBuffer( "cl_motdString", uiInfo.serverStatus.motd, sizeof( uiInfo.serverStatus.motd ) ); + len = strlen( uiInfo.serverStatus.motd ); + if ( len == 0 ) { + strcpy( uiInfo.serverStatus.motd, va( "Wolf Multiplayer - Version: %s", Q3_VERSION ) ); + len = strlen( uiInfo.serverStatus.motd ); + } + if ( len != uiInfo.serverStatus.motdLen ) { + uiInfo.serverStatus.motdLen = len; + uiInfo.serverStatus.motdWidth = -1; + } + + if ( force ) { + numinvisible = 0; + // clear number of displayed servers + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + // set list box index to zero + Menu_SetFeederSelection( NULL, FEEDER_SERVERS, 0, NULL ); + // mark all servers as visible so we store ping updates for them + trap_LAN_MarkServerVisible( ui_netSource.integer, -1, qtrue ); + } + + // get the server count (comes from the master) + count = trap_LAN_GetServerCount( ui_netSource.integer ); + if ( count == -1 || ( ui_netSource.integer == AS_LOCAL && count == 0 ) ) { + // still waiting on a response from the master + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500; + return; + } + + visible = qfalse; + for ( i = 0; i < count; i++ ) { + // if we already got info for this server + if ( !trap_LAN_ServerIsVisible( ui_netSource.integer, i ) ) { + continue; + } + visible = qtrue; + // get the ping for this server + ping = trap_LAN_GetServerPing( ui_netSource.integer, i ); + if ( ping > 0 || ui_netSource.integer == AS_FAVORITES ) { + + trap_LAN_GetServerInfo( ui_netSource.integer, i, info, MAX_STRING_CHARS ); + + clients = atoi( Info_ValueForKey( info, "clients" ) ); + uiInfo.serverStatus.numPlayersOnServers += clients; + + if ( ui_browserShowEmpty.integer == 0 ) { + if ( clients == 0 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + if ( ui_browserShowFull.integer == 0 ) { + maxClients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + if ( clients == maxClients ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + // NERVE - SMF - friendly fire parsing + if ( ui_browserShowFriendlyFire.integer ) { + friendlyFire = atoi( Info_ValueForKey( info, "friendlyFire" ) ); + + if ( friendlyFire && ui_browserShowFriendlyFire.integer == 2 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } else if ( !friendlyFire && ui_browserShowFriendlyFire.integer == 1 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + // NERVE - SMF - maxlives parsing + if ( ui_browserShowMaxlives.integer == 0 ) { + maxlives = atoi( Info_ValueForKey( info, "maxlives" ) ); + if ( maxlives ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + // NERVE - SMF - tourney parsing + if ( ui_browserShowTourney.integer == 0 ) { + tourney = atoi( Info_ValueForKey( info, "tourney" ) ); + if ( tourney ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + // DHM - Nerve - PunkBuster parsing + if ( ui_browserShowPunkBuster.integer ) { + punkbuster = atoi( Info_ValueForKey( info, "punkbuster" ) ); + + if ( punkbuster && ui_browserShowPunkBuster.integer == 2 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } else if ( !punkbuster && ui_browserShowPunkBuster.integer == 1 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + if ( ui_browserShowAntilag.integer ) { + antilag = atoi( Info_ValueForKey( info, "g_antilag" ) ); + + if ( antilag && ui_browserShowAntilag.integer == 2 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } else if ( !antilag && ui_browserShowAntilag.integer == 1 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + if ( uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum != -1 ) { + game = atoi( Info_ValueForKey( info, "gametype" ) ); + if ( game != uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + + if ( ui_serverFilterType.integer > 0 ) { + if ( Q_stricmp( Info_ValueForKey( info, "game" ), serverFilters[ui_serverFilterType.integer].basedir ) != 0 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + continue; + } + } + // make sure we never add a favorite server twice + if ( ui_netSource.integer == AS_FAVORITES ) { + UI_RemoveServerFromDisplayList( i ); + } + // insert the server into the list + UI_BinaryServerInsertion( i ); + // done with this server + if ( ping > 0 ) { + trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse ); + numinvisible++; + } + } + } + + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime; + + // if there were no servers visible for ping updates + if ( !visible ) { +// UI_StopServerRefresh(); +// uiInfo.serverStatus.nextDisplayRefresh = 0; + } +} + +typedef struct +{ + char *name, *altName; +} serverStatusCvar_t; + +serverStatusCvar_t serverStatusCvars[] = { + {"sv_hostname", "Name"}, + {"Address", ""}, + {"gamename", "Game name"}, + {"g_gametype", "Game type"}, + {"mapname", "Map"}, + {"version", ""}, + {"protocol", ""}, + {"timelimit", ""}, + {"fraglimit", ""}, + {NULL, NULL} +}; + +/* +================== +UI_SortServerStatusInfo +================== +*/ +static void UI_SortServerStatusInfo( serverStatusInfo_t *info ) { + int i, j, index; + char *tmp1, *tmp2; + + // FIXME: if "gamename" == "baseq3" or "missionpack" then + // replace the gametype number by FFA, CTF etc. + // + index = 0; + for ( i = 0; serverStatusCvars[i].name; i++ ) { + for ( j = 0; j < info->numLines; j++ ) { + if ( !info->lines[j][1] || info->lines[j][1][0] ) { + continue; + } + if ( !Q_stricmp( serverStatusCvars[i].name, info->lines[j][0] ) ) { + // swap lines + tmp1 = info->lines[index][0]; + tmp2 = info->lines[index][3]; + info->lines[index][0] = info->lines[j][0]; + info->lines[index][3] = info->lines[j][3]; + info->lines[j][0] = tmp1; + info->lines[j][3] = tmp2; + // + if ( strlen( serverStatusCvars[i].altName ) ) { + info->lines[index][0] = serverStatusCvars[i].altName; + } + index++; + } + } + } +} + +/* +================== +UI_GetServerStatusInfo +================== +*/ +static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info ) { + char *p, *score, *ping, *name, *p_val = NULL, *p_name = NULL; + menuDef_t *menu, *menu2; // we use the URL buttons in several menus + int i, len; + + if ( !info ) { + trap_LAN_ServerStatus( serverAddress, NULL, 0 ); + return qfalse; + } + memset( info, 0, sizeof( *info ) ); + if ( trap_LAN_ServerStatus( serverAddress, info->text, sizeof( info->text ) ) ) { + + menu = Menus_FindByName( "serverinfo_popmenu" ); + menu2 = Menus_FindByName( "error_popmenu_diagnose" ); + + Q_strncpyz( info->address, serverAddress, sizeof( info->address ) ); + p = info->text; + info->numLines = 0; + info->lines[info->numLines][0] = "Address"; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + info->lines[info->numLines][3] = info->address; + info->numLines++; + // cleanup of the URL cvars + trap_Cvar_Set( "ui_URL", "" ); + trap_Cvar_Set( "ui_modURL", "" ); + // get the cvars + while ( p && *p ) { + p = strchr( p, '\\' ); + if ( !p ) { + break; + } + *p++ = '\0'; + if ( p_name ) { + if ( !strcmp( p_name, "URL" ) ) { + trap_Cvar_Set( "ui_URL", p_val ); + if ( menu ) { + Menu_ShowItemByName( menu, "serverURL", qtrue ); + } + if ( menu2 ) { + Menu_ShowItemByName( menu2, "serverURL", qtrue ); + } + } else if ( !strcmp( p_name, "mod_url" ) ) { + trap_Cvar_Set( "ui_modURL", p_val ); + if ( menu ) { + Menu_ShowItemByName( menu, "modURL", qtrue ); + } + if ( menu2 ) { + Menu_ShowItemByName( menu2, "modURL", qtrue ); + } + } + } + if ( *p == '\\' ) { + break; + } + p_name = p; + info->lines[info->numLines][0] = p; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + p = strchr( p, '\\' ); + if ( !p ) { + break; + } + *p++ = '\0'; + p_val = p; + info->lines[info->numLines][3] = p; + + info->numLines++; + if ( info->numLines >= MAX_SERVERSTATUS_LINES ) { + break; + } + } + // get the player list + if ( info->numLines < MAX_SERVERSTATUS_LINES - 3 ) { + // empty line + info->lines[info->numLines][0] = ""; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + info->lines[info->numLines][3] = ""; + info->numLines++; + // header + info->lines[info->numLines][0] = "num"; + info->lines[info->numLines][1] = "score"; + info->lines[info->numLines][2] = "ping"; + info->lines[info->numLines][3] = "name"; + info->numLines++; + // parse players + i = 0; + len = 0; + while ( p && *p ) { + if ( *p == '\\' ) { + *p++ = '\0'; + } + if ( !p ) { + break; + } + score = p; + p = strchr( p, ' ' ); + if ( !p ) { + break; + } + *p++ = '\0'; + ping = p; + p = strchr( p, ' ' ); + if ( !p ) { + break; + } + *p++ = '\0'; + name = p; + Com_sprintf( &info->pings[len], sizeof( info->pings ) - len, "%d", i ); + info->lines[info->numLines][0] = &info->pings[len]; + len += strlen( &info->pings[len] ) + 1; + info->lines[info->numLines][1] = score; + info->lines[info->numLines][2] = ping; + info->lines[info->numLines][3] = name; + info->numLines++; + if ( info->numLines >= MAX_SERVERSTATUS_LINES ) { + break; + } + p = strchr( p, '\\' ); + if ( !p ) { + break; + } + *p++ = '\0'; + // + i++; + } + } + UI_SortServerStatusInfo( info ); + return qtrue; + } + return qfalse; +} + +/* +================== +stristr +================== +*/ +static char *stristr( char *str, char *charset ) { + int i; + + while ( *str ) { + for ( i = 0; charset[i] && str[i]; i++ ) { + if ( toupper( charset[i] ) != toupper( str[i] ) ) { + break; + } + } + if ( !charset[i] ) { + return str; + } + str++; + } + return NULL; +} + +/* +================== +UI_BuildFindPlayerList +================== +*/ +static void UI_BuildFindPlayerList( qboolean force ) { + static int numFound, numTimeOuts; + int i, j, resend; + serverStatusInfo_t info; + char name[MAX_NAME_LENGTH + 2]; + char infoString[MAX_STRING_CHARS]; + + if ( !force ) { + if ( !uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime ) { + return; + } + } else { + memset( &uiInfo.pendingServerStatus, 0, sizeof( uiInfo.pendingServerStatus ) ); + uiInfo.numFoundPlayerServers = 0; + uiInfo.currentFoundPlayerServer = 0; + trap_Cvar_VariableStringBuffer( "ui_findPlayer", uiInfo.findPlayerName, sizeof( uiInfo.findPlayerName ) ); + Q_CleanStr( uiInfo.findPlayerName ); + // should have a string of some length + if ( !strlen( uiInfo.findPlayerName ) ) { + uiInfo.nextFindPlayerRefresh = 0; + return; + } + // set resend time + resend = ui_serverStatusTimeOut.integer / 2 - 10; + if ( resend < 50 ) { + resend = 50; + } + trap_Cvar_Set( "cl_serverStatusResendTime", va( "%d", resend ) ); + // reset all server status requests + trap_LAN_ServerStatus( NULL, NULL, 0 ); + // + uiInfo.numFoundPlayerServers = 1; + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + sizeof( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1] ), + "searching %d...", uiInfo.pendingServerStatus.num ); + numFound = 0; + numTimeOuts++; + } + for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) { + // if this pending server is valid + if ( uiInfo.pendingServerStatus.server[i].valid ) { + // try to get the server status for this server + if ( UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, &info ) ) { + // + numFound++; + // parse through the server status lines + for ( j = 0; j < info.numLines; j++ ) { + // should have ping info + if ( !info.lines[j][2] || !info.lines[j][2][0] ) { + continue; + } + // clean string first + Q_strncpyz( name, info.lines[j][3], sizeof( name ) ); + Q_CleanStr( name ); + // if the player name is a substring + if ( stristr( name, uiInfo.findPlayerName ) ) { + // add to found server list if we have space (always leave space for a line with the number found) + if ( uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS - 1 ) { + // + Q_strncpyz( uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers - 1], + uiInfo.pendingServerStatus.server[i].adrstr, + sizeof( uiInfo.foundPlayerServerAddresses[0] ) ); + Q_strncpyz( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + uiInfo.pendingServerStatus.server[i].name, + sizeof( uiInfo.foundPlayerServerNames[0] ) ); + uiInfo.numFoundPlayerServers++; + } else { + // can't add any more so we're done + uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers; + } + } + } + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + sizeof( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1] ), + "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound ); + // retrieved the server status so reuse this spot + uiInfo.pendingServerStatus.server[i].valid = qfalse; + } + } + // if empty pending slot or timed out + if ( !uiInfo.pendingServerStatus.server[i].valid || + uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer ) { + if ( uiInfo.pendingServerStatus.server[i].valid ) { + numTimeOuts++; + } + // reset server status request for this address + UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, NULL ); + // reuse pending slot + uiInfo.pendingServerStatus.server[i].valid = qfalse; + // if we didn't try to get the status of all servers in the main browser yet + if ( uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers ) { + uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime; + trap_LAN_GetServerAddressString( ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], + uiInfo.pendingServerStatus.server[i].adrstr, sizeof( uiInfo.pendingServerStatus.server[i].adrstr ) ); + trap_LAN_GetServerInfo( ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], infoString, sizeof( infoString ) ); + Q_strncpyz( uiInfo.pendingServerStatus.server[i].name, Info_ValueForKey( infoString, "hostname" ), sizeof( uiInfo.pendingServerStatus.server[0].name ) ); + uiInfo.pendingServerStatus.server[i].valid = qtrue; + uiInfo.pendingServerStatus.num++; + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], + sizeof( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1] ), + "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound ); + } + } + } + for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) { + if ( uiInfo.pendingServerStatus.server[i].valid ) { + break; + } + } + // if still trying to retrieve server status info + if ( i < MAX_SERVERSTATUSREQUESTS ) { + uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25; + } else { + // add a line that shows the number of servers found + if ( !uiInfo.numFoundPlayerServers ) { + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], sizeof( uiInfo.foundPlayerServerAddresses[0] ), "no servers found" ); + } else { + Com_sprintf( uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers - 1], sizeof( uiInfo.foundPlayerServerAddresses[0] ), + "%d server%s found with player %s", uiInfo.numFoundPlayerServers - 1, + uiInfo.numFoundPlayerServers == 2 ? "" : "s", uiInfo.findPlayerName ); + } + uiInfo.nextFindPlayerRefresh = 0; + // show the server status info for the selected server + UI_FeederSelection( FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer ); + } +} + +/* +================== +UI_BuildServerStatus +================== +*/ +static void UI_BuildServerStatus( qboolean force ) { + menuDef_t *menu; + + if ( uiInfo.nextFindPlayerRefresh ) { + return; + } + if ( !force ) { + if ( !uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime ) { + return; + } + } else { + Menu_SetFeederSelection( NULL, FEEDER_SERVERSTATUS, 0, NULL ); + uiInfo.serverStatusInfo.numLines = 0; + // TTimo - reset the server URL / mod URL till we get the new ones + // the URL buttons are used in the two menus, serverinfo_popmenu and error_popmenu_diagnose + menu = Menus_FindByName( "serverinfo_popmenu" ); + if ( menu ) { + Menu_ShowItemByName( menu, "serverURL", qfalse ); + Menu_ShowItemByName( menu, "modURL", qfalse ); + } + menu = Menus_FindByName( "error_popmenu_diagnose" ); + if ( menu ) { + Menu_ShowItemByName( menu, "serverURL", qfalse ); + Menu_ShowItemByName( menu, "modURL", qfalse ); + } + // reset all server status requests + trap_LAN_ServerStatus( NULL, NULL, 0 ); + } + if ( uiInfo.serverStatus.currentServer < 0 || uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || uiInfo.serverStatus.numDisplayServers == 0 ) { + return; + } + if ( UI_GetServerStatusInfo( uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo ) ) { + uiInfo.nextServerStatusRefresh = 0; + UI_GetServerStatusInfo( uiInfo.serverStatusAddress, NULL ); + } else { + uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500; + } +} + +/* +================== +UI_FeederCount +================== +*/ +static int UI_FeederCount( float feederID ) { + if ( feederID == FEEDER_HEADS ) { + return uiInfo.characterCount; + } else if ( feederID == FEEDER_Q3HEADS ) { + return uiInfo.q3HeadCount; + } else if ( feederID == FEEDER_CINEMATICS ) { + return uiInfo.movieCount; + } else if ( feederID == FEEDER_SAVEGAMES ) { + return uiInfo.savegameCount; + } else if ( feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS ) { + return UI_MapCountByGameType( feederID == FEEDER_MAPS ? qtrue : qfalse ); + } else if ( feederID == FEEDER_SERVERS ) { + return uiInfo.serverStatus.numDisplayServers; + } else if ( feederID == FEEDER_SERVERSTATUS ) { + return uiInfo.serverStatusInfo.numLines; + } else if ( feederID == FEEDER_FINDPLAYER ) { + return uiInfo.numFoundPlayerServers; + } else if ( feederID == FEEDER_PLAYER_LIST ) { + if ( uiInfo.uiDC.realTime > uiInfo.playerRefresh ) { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } + return uiInfo.playerCount; + } else if ( feederID == FEEDER_TEAM_LIST ) { + if ( uiInfo.uiDC.realTime > uiInfo.playerRefresh ) { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } + return uiInfo.myTeamCount; + } else if ( feederID == FEEDER_MODS ) { + return uiInfo.modCount; + } else if ( feederID == FEEDER_DEMOS ) { + return uiInfo.demoCount; + // NERVE - SMF + } else if ( feederID == FEEDER_PICKSPAWN ) { + return uiInfo.spawnCount; + } else if ( feederID == FEEDER_SOLDIERWEAP ) { + int i, count; + for ( i = 0, count = 0; weaponTypes[i].name; i++ ) + if ( weaponTypes[i].flags & PT_RIFLE ) { + count++; + } + return count; + } else if ( feederID == FEEDER_LIEUTWEAP ) { + int i, count; + for ( i = 0, count = 0; weaponTypes[i].name; i++ ) + if ( weaponTypes[i].flags & PT_LIGHTONLY ) { + count++; + } + return count; + } + // -NERVE - SMF + return 0; +} + +static const char *UI_SelectedMap( int index, int *actual ) { + int i, c; + c = 0; + *actual = 0; + for ( i = 0; i < uiInfo.mapCount; i++ ) { + if ( uiInfo.mapList[i].active ) { + if ( c == index ) { + *actual = i; + return uiInfo.mapList[i].mapName; + } else { + c++; + } + } + } + return ""; +} + +static int UI_GetIndexFromSelection( int actual ) { + int i, c; + c = 0; + for ( i = 0; i < uiInfo.mapCount; i++ ) { + if ( uiInfo.mapList[i].active ) { + if ( i == actual ) { + return c; + } + c++; + } + } + return 0; +} + +static void UI_UpdatePendingPings() { + trap_LAN_ResetPings( ui_netSource.integer ); + uiInfo.serverStatus.refreshActive = qtrue; + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; +} + +// NERVE - SMF +static void UI_FeederAddItem( float feederID, const char *name, int index ) { + +} +// -NERVE - SMF + +//----(SA) added (whoops, this got nuked in a check-in...) +static const char *UI_FileText( char *fileName ) { + int len; + fileHandle_t f; + static char buf[MAX_MENUDEFFILE]; + +// return "flubber"; + + len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + if ( !f ) { + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + return &buf[0]; +} +//----(SA) end + + +static const char *UI_FeederItemText( float feederID, int index, int column, qhandle_t *handle ) { + static char info[MAX_STRING_CHARS]; + static char hostname[1024]; + static char clientBuff[32]; + static char pingstr[10]; + static int lastColumn = -1; + static int lastTime = 0; + *handle = -1; + if ( feederID == FEEDER_HEADS ) { + if ( index >= 0 && index < uiInfo.characterCount ) { + return uiInfo.characterList[index].name; + } + } else if ( feederID == FEEDER_Q3HEADS ) { + if ( index >= 0 && index < uiInfo.q3HeadCount ) { + return uiInfo.q3HeadNames[index]; + } + } else if ( feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS ) { + int actual; + return UI_SelectedMap( index, &actual ); + } else if ( feederID == FEEDER_SERVERS ) { + if ( index >= 0 && index < uiInfo.serverStatus.numDisplayServers ) { + int ping, game, punkbuster, antilag; + if ( lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000 ) { + trap_LAN_GetServerInfo( ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS ); + lastColumn = column; + lastTime = uiInfo.uiDC.realTime; + } + antilag = atoi( Info_ValueForKey( info, "g_antilag" ) ); + ping = atoi( Info_ValueForKey( info, "ping" ) ); + if ( ping == -1 ) { + // if we ever see a ping that is out of date, do a server refresh + // UI_UpdatePendingPings(); + } + switch ( column ) { + case SORT_HOST: + if ( ping <= 0 ) { + return Info_ValueForKey( info, "addr" ); + } else { + if ( ui_netSource.integer == AS_LOCAL ) { + Com_sprintf( hostname, sizeof( hostname ), "%s [%s]", + Info_ValueForKey( info, "hostname" ), + netnames[atoi( Info_ValueForKey( info, "nettype" ) )] ); + return hostname; + } else { + return Info_ValueForKey( info, "hostname" ); + } + } + case SORT_MAP: return Info_ValueForKey( info, "mapname" ); + case SORT_CLIENTS: + Com_sprintf( clientBuff, sizeof( clientBuff ), "%s (%s)", Info_ValueForKey( info, "clients" ), Info_ValueForKey( info, "sv_maxclients" ) ); + return clientBuff; + case SORT_GAME: + game = atoi( Info_ValueForKey( info, "gametype" ) ); + if ( game >= 0 && game < numTeamArenaGameTypes ) { + return teamArenaGameTypes[game]; + } else { + return "Unknown"; + } + case SORT_PING: + if ( ping <= 0 ) { + return "..."; + } else { + if ( !antilag ) { + Com_sprintf( pingstr, sizeof( pingstr ), "^3%s", Info_ValueForKey( info, "ping" ) ); + } else { + Q_strncpyz( pingstr, Info_ValueForKey( info, "ping" ), sizeof( pingstr ) ); + } + return pingstr; + } + case SORT_PUNKBUSTER: + punkbuster = atoi( Info_ValueForKey( info, "punkbuster" ) ); + if ( punkbuster ) { + return translated_yes; + } else { + return translated_no; + } + } + } + } else if ( feederID == FEEDER_SERVERSTATUS ) { + if ( index >= 0 && index < uiInfo.serverStatusInfo.numLines ) { + if ( column >= 0 && column < 4 ) { + return uiInfo.serverStatusInfo.lines[index][column]; + } + } + } else if ( feederID == FEEDER_FINDPLAYER ) { + if ( index >= 0 && index < uiInfo.numFoundPlayerServers ) { + //return uiInfo.foundPlayerServerAddresses[index]; + return uiInfo.foundPlayerServerNames[index]; + } + } else if ( feederID == FEEDER_PLAYER_LIST ) { + if ( index >= 0 && index < uiInfo.playerCount ) { + return uiInfo.playerNames[index]; + } + } else if ( feederID == FEEDER_TEAM_LIST ) { + if ( index >= 0 && index < uiInfo.myTeamCount ) { + return uiInfo.teamNames[index]; + } + } else if ( feederID == FEEDER_MODS ) { + if ( index >= 0 && index < uiInfo.modCount ) { + if ( uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr ) { + return uiInfo.modList[index].modDescr; + } else { + return uiInfo.modList[index].modName; + } + } + } else if ( feederID == FEEDER_CINEMATICS ) { + if ( index >= 0 && index < uiInfo.movieCount ) { + return uiInfo.movieList[index]; + } + } else if ( feederID == FEEDER_SAVEGAMES ) { + if ( index >= 0 && index < uiInfo.savegameCount ) { + return uiInfo.savegameList[index].name; + } + } else if ( feederID == FEEDER_DEMOS ) { + if ( index >= 0 && index < uiInfo.demoCount ) { + return uiInfo.demoList[index]; + } + } + // NERVE - SMF + else if ( feederID == FEEDER_PICKSPAWN ) { + return uiInfo.spawnPoints[index]; + } + // -NERVE - SMF + return ""; +} + + +static qhandle_t UI_FeederItemImage( float feederID, int index ) { + if ( feederID == FEEDER_HEADS ) { + if ( index >= 0 && index < uiInfo.characterCount ) { + if ( uiInfo.characterList[index].headImage == -1 ) { + uiInfo.characterList[index].headImage = trap_R_RegisterShaderNoMip( uiInfo.characterList[index].imageName ); + } + return uiInfo.characterList[index].headImage; + } + } else if ( feederID == FEEDER_Q3HEADS ) { + if ( index >= 0 && index < uiInfo.q3HeadCount ) { + return uiInfo.q3HeadIcons[index]; + } + } else if ( feederID == FEEDER_ALLMAPS || feederID == FEEDER_MAPS ) { + int actual; + + UI_SelectedMap( index, &actual ); + index = actual; + if ( index >= 0 && index < uiInfo.mapCount ) { + if ( uiInfo.mapList[index].levelShot == -1 ) { + uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip( uiInfo.mapList[index].imageName ); + } + return uiInfo.mapList[index].levelShot; + } + } else if ( feederID == FEEDER_SAVEGAMES ) { + if ( index >= 0 && index < uiInfo.savegameCount ) { + if ( uiInfo.savegameList[index].sshotImage == -1 ) { + uiInfo.savegameList[index].sshotImage = trap_R_RegisterShaderNoMip( va( "save/images/%s.tga", uiInfo.savegameList[index].name ) ); + } + return uiInfo.savegameList[index].sshotImage; + } + // NERVE - SMF + } else if ( feederID == FEEDER_SOLDIERWEAP ) { + int i, count; + for ( i = 0, count = 0; weaponTypes[i].name; i++ ) { + if ( weaponTypes[i].flags & PT_RIFLE ) { + count++; + } + if ( count == index + 1 ) { + return trap_R_RegisterShaderNoMip( weaponTypes[i].name ); + } + } + } else if ( feederID == FEEDER_LIEUTWEAP ) { + int i, count; + for ( i = 0, count = 0; weaponTypes[i].name; i++ ) { + if ( weaponTypes[i].flags & PT_LIGHTONLY ) { + count++; + } + if ( count == index + 1 ) { + return trap_R_RegisterShaderNoMip( weaponTypes[i].name ); + } + } + } + // -NERVE - SMF + + return 0; +} + +static void UI_FeederSelection( float feederID, int index ) { + static char info[MAX_STRING_CHARS]; + if ( feederID == FEEDER_HEADS ) { + if ( index >= 0 && index < uiInfo.characterCount ) { + trap_Cvar_Set( "team_model", uiInfo.characterList[index].female ? "janet" : "james" ); + trap_Cvar_Set( "team_headmodel", va( "*%s", uiInfo.characterList[index].name ) ); + updateModel = qtrue; + } + } else if ( feederID == FEEDER_Q3HEADS ) { + if ( index >= 0 && index < uiInfo.q3HeadCount ) { + trap_Cvar_Set( "model", uiInfo.q3HeadNames[index] ); + trap_Cvar_Set( "headmodel", uiInfo.q3HeadNames[index] ); + updateModel = qtrue; + } + } else if ( feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS ) { + int actual, map; + map = ( feederID == FEEDER_ALLMAPS ) ? ui_currentNetMap.integer : ui_currentMap.integer; + if ( uiInfo.mapList[map].cinematic >= 0 ) { + trap_CIN_StopCinematic( uiInfo.mapList[map].cinematic ); + uiInfo.mapList[map].cinematic = -1; + } + UI_SelectedMap( index, &actual ); + trap_Cvar_Set( "ui_mapIndex", va( "%d", index ) ); + ui_mapIndex.integer = index; + + // NERVE - SMF - setup advanced server vars + if ( feederID == FEEDER_ALLMAPS ) { + ui_currentMap.integer = actual; + trap_Cvar_Set( "ui_currentMap", va( "%d", actual ) ); + trap_Cvar_Set( "ui_userTimelimit", va( "%d", uiInfo.mapList[ui_currentMap.integer].Timelimit ) ); + trap_Cvar_Set( "ui_userAxisRespawnTime", va( "%d", uiInfo.mapList[ui_currentMap.integer].AxisRespawnTime ) ); + trap_Cvar_Set( "ui_userAlliedRespawnTime", va( "%d", uiInfo.mapList[ui_currentMap.integer].AlliedRespawnTime ) ); + } + // -NERVE - SMF + + if ( feederID == FEEDER_MAPS ) { + ui_currentMap.integer = actual; + trap_Cvar_Set( "ui_currentMap", va( "%d", actual ) ); + uiInfo.mapList[ui_currentMap.integer].cinematic = trap_CIN_PlayCinematic( va( "%s.roq", uiInfo.mapList[ui_currentMap.integer].mapLoadName ), 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + UI_LoadBestScores( uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum ); + trap_Cvar_Set( "ui_opponentModel", uiInfo.mapList[ui_currentMap.integer].opponentName ); + updateOpponentModel = qtrue; + } else { + ui_currentNetMap.integer = actual; + trap_Cvar_Set( "ui_currentNetMap", va( "%d", actual ) ); + uiInfo.mapList[ui_currentNetMap.integer].cinematic = trap_CIN_PlayCinematic( va( "%s.roq", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ), 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + } + + } else if ( feederID == FEEDER_SERVERS ) { + const char *mapName = NULL; + uiInfo.serverStatus.currentServer = index; + trap_LAN_GetServerInfo( ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS ); + uiInfo.serverStatus.currentServerPreview = trap_R_RegisterShaderNoMip( va( "levelshots/%s", Info_ValueForKey( info, "mapname" ) ) ); + if ( uiInfo.serverStatus.currentServerCinematic >= 0 ) { + trap_CIN_StopCinematic( uiInfo.serverStatus.currentServerCinematic ); + uiInfo.serverStatus.currentServerCinematic = -1; + } + mapName = Info_ValueForKey( info, "mapname" ); + if ( mapName && *mapName ) { + uiInfo.serverStatus.currentServerCinematic = trap_CIN_PlayCinematic( va( "%s.roq", mapName ), 0, 0, 0, 0, ( CIN_loop | CIN_silent ) ); + } + } else if ( feederID == FEEDER_SERVERSTATUS ) { + // + } else if ( feederID == FEEDER_FINDPLAYER ) { + uiInfo.currentFoundPlayerServer = index; + // + if ( index < uiInfo.numFoundPlayerServers - 1 ) { + // build a new server status for this server + Q_strncpyz( uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof( uiInfo.serverStatusAddress ) ); + Menu_SetFeederSelection( NULL, FEEDER_SERVERSTATUS, 0, NULL ); + UI_BuildServerStatus( qtrue ); + } + } else if ( feederID == FEEDER_PLAYER_LIST ) { + uiInfo.playerIndex = index; + } else if ( feederID == FEEDER_TEAM_LIST ) { + uiInfo.teamIndex = index; + } else if ( feederID == FEEDER_MODS ) { + uiInfo.modIndex = index; + } else if ( feederID == FEEDER_CINEMATICS ) { + uiInfo.movieIndex = index; + if ( uiInfo.previewMovie >= 0 ) { + trap_CIN_StopCinematic( uiInfo.previewMovie ); + } + uiInfo.previewMovie = -1; + } else if ( feederID == FEEDER_SAVEGAMES ) { + uiInfo.savegameIndex = index; + } else if ( feederID == FEEDER_DEMOS ) { + uiInfo.demoIndex = index; + // NERVE - SMF + } else if ( feederID == FEEDER_PICKSPAWN ) { + trap_Cmd_ExecuteText( EXEC_NOW, va( "setspawnpt %i\n", index ) ); + } else if ( feederID == FEEDER_SOLDIERWEAP ) { + int i, count; + for ( i = 0, count = 0; weaponTypes[i].name; i++ ) { + if ( weaponTypes[i].flags & PT_RIFLE ) { + count++; + } + if ( count == index + 1 ) { + trap_Cvar_Set( weaponTypes[i].cvar, va( "%i", weaponTypes[i].value ) ); + trap_Cvar_Set( "ui_weapon", trap_TranslateString( weaponTypes[i].desc ) ); + WM_setWeaponPics(); + + break; + } + } + } else if ( feederID == FEEDER_LIEUTWEAP ) { + int i, count; + for ( i = 0, count = 0; weaponTypes[i].name; i++ ) { + if ( weaponTypes[i].flags & PT_LIGHTONLY ) { + count++; + } + if ( count == index + 1 ) { + trap_Cvar_Set( weaponTypes[i].cvar, va( "%i", weaponTypes[i].value ) ); + trap_Cvar_Set( "ui_weapon", trap_TranslateString( weaponTypes[i].desc ) ); + WM_setWeaponPics(); + break; + } + } + } + // -NERVE - SMF +} + +/* +// TTimo: unused +static qboolean Team_Parse(char **p) { + char *token; + const char *tempStr; + int i; + + token = COM_ParseExt(p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + while ( 1 ) { + + token = COM_ParseExt(p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + if (token[0] == '{') { + // seven tokens per line, team name and icon, and 5 team member names + if (!String_Parse(p, &uiInfo.teamList[uiInfo.teamCount].teamName) || !String_Parse(p, &tempStr)) { + return qfalse; + } + + + uiInfo.teamList[uiInfo.teamCount].imageName = tempStr; + uiInfo.teamList[uiInfo.teamCount].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[uiInfo.teamCount].imageName); + uiInfo.teamList[uiInfo.teamCount].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[uiInfo.teamCount].imageName)); + uiInfo.teamList[uiInfo.teamCount].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[uiInfo.teamCount].imageName)); + + uiInfo.teamList[uiInfo.teamCount].cinematic = -1; + + for (i = 0; i < TEAM_MEMBERS; i++) { + uiInfo.teamList[uiInfo.teamCount].teamMembers[i] = NULL; + if (!String_Parse(p, &uiInfo.teamList[uiInfo.teamCount].teamMembers[i])) { + return qfalse; + } + } + + Com_Printf("Loaded team %s with team icon %s.\n", uiInfo.teamList[uiInfo.teamCount].teamName, tempStr); + if (uiInfo.teamCount < MAX_TEAMS) { + uiInfo.teamCount++; + } else { + Com_Printf("Too many teams, last team replaced!\n"); + } + token = COM_ParseExt(p, qtrue); + if (token[0] != '}') { + return qfalse; + } + } + } + + return qfalse; +} +*/ + +/* +// TTimo: unused +static qboolean Character_Parse(char **p) { + char *token; + const char *tempStr; + + token = COM_ParseExt(p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + + while ( 1 ) { + token = COM_ParseExt(p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + if (token[0] == '{') { + // two tokens per line, character name and sex + if (!String_Parse(p, &uiInfo.characterList[uiInfo.characterCount].name) || !String_Parse(p, &tempStr)) { + return qfalse; + } + + uiInfo.characterList[uiInfo.characterCount].headImage = -1; + uiInfo.characterList[uiInfo.characterCount].imageName = String_Alloc(va("models/players/heads/%s/icon_default.tga", uiInfo.characterList[uiInfo.characterCount].name)); + + if (tempStr && (tempStr[0] == 'f' || tempStr[0] == 'F')) { + uiInfo.characterList[uiInfo.characterCount].female = qtrue; + } else { + uiInfo.characterList[uiInfo.characterCount].female = qfalse; + } + + Com_Printf("Loaded %s character %s.\n", tempStr, uiInfo.characterList[uiInfo.characterCount].name); + if (uiInfo.characterCount < MAX_HEADS) { + uiInfo.characterCount++; + } else { + Com_Printf("Too many characters, last character replaced!\n"); + } + + token = COM_ParseExt(p, qtrue); + if (token[0] != '}') { + return qfalse; + } + } + } + + return qfalse; +} +*/ + +/* +// TTimo: unused +static qboolean Alias_Parse(char **p) { + char *token; + + token = COM_ParseExt(p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + while ( 1 ) { + token = COM_ParseExt(p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + if (token[0] == '{') { + // three tokens per line, character name, bot alias, and preferred action a - all purpose, d - defense, o - offense + if (!String_Parse(p, &uiInfo.aliasList[uiInfo.aliasCount].name) || !String_Parse(p, &uiInfo.aliasList[uiInfo.aliasCount].ai) || !String_Parse(p, &uiInfo.aliasList[uiInfo.aliasCount].action)) { + return qfalse; + } + + Com_Printf("Loaded character alias %s using character ai %s.\n", uiInfo.aliasList[uiInfo.aliasCount].name, uiInfo.aliasList[uiInfo.aliasCount].ai); + if (uiInfo.aliasCount < MAX_ALIASES) { + uiInfo.aliasCount++; + } else { + Com_Printf("Too many aliases, last alias replaced!\n"); + } + + token = COM_ParseExt(p, qtrue); + if (token[0] != '}') { + return qfalse; + } + } + } + + return qfalse; +} +*/ + +// mode +// 0 - high level parsing +// 1 - team parsing +// 2 - character parsing +/* +// TTimo: unused +static void UI_ParseTeamInfo(const char *teamFile) { + char *token; + char *p; + char *buff = NULL; + //int mode = 0; // TTimo: unused + + buff = GetMenuBuffer(teamFile); + if (!buff) { + return; + } + + p = buff; + + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if( !token || token[0] == 0 || token[0] == '}') { + break; + } + + if ( Q_stricmp( token, "}" ) == 0 ) { + break; + } + + if (Q_stricmp(token, "teams") == 0) { + + if (Team_Parse(&p)) { + continue; + } else { + break; + } + } + + if (Q_stricmp(token, "characters") == 0) { + Character_Parse(&p); + } + + if (Q_stricmp(token, "aliases") == 0) { + Alias_Parse(&p); + } + } +} +*/ + +/* +============== +GameType_Parse +============== +*/ +static qboolean GameType_Parse( char **p, qboolean join ) { + char *token; + + token = COM_ParseExt( p, qtrue ); + + if ( token[0] != '{' ) { + return qfalse; + } + + if ( join ) { + uiInfo.numJoinGameTypes = 0; + } else { + uiInfo.numGameTypes = 0; + } + + while ( 1 ) { + token = COM_ParseExt( p, qtrue ); + + if ( Q_stricmp( token, "}" ) == 0 ) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + if ( token[0] == '{' ) { + // two tokens per line, character name and sex + if ( join ) { + if ( !String_Parse( p, &uiInfo.joinGameTypes[uiInfo.numJoinGameTypes].gameType ) || !Int_Parse( p, &uiInfo.joinGameTypes[uiInfo.numJoinGameTypes].gtEnum ) ) { + return qfalse; + } + } else { + if ( !String_Parse( p, &uiInfo.gameTypes[uiInfo.numGameTypes].gameType ) || !Int_Parse( p, &uiInfo.gameTypes[uiInfo.numGameTypes].gtEnum ) ) { + return qfalse; + } + } + + if ( join ) { + if ( uiInfo.numJoinGameTypes < MAX_GAMETYPES ) { + uiInfo.numJoinGameTypes++; + } else { + Com_Printf( "Too many net game types, last one replace!\n" ); + } + } else { + if ( uiInfo.numGameTypes < MAX_GAMETYPES ) { + uiInfo.numGameTypes++; + } else { + Com_Printf( "Too many game types, last one replace!\n" ); + } + } + + token = COM_ParseExt( p, qtrue ); + if ( token[0] != '}' ) { + return qfalse; + } + } + } + return qfalse; +} + +static qboolean MapList_Parse( char **p ) { + char *token; + + token = COM_ParseExt( p, qtrue ); + + if ( token[0] != '{' ) { + return qfalse; + } + + uiInfo.mapCount = 0; + + while ( 1 ) { + token = COM_ParseExt( p, qtrue ); + + if ( Q_stricmp( token, "}" ) == 0 ) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + if ( token[0] == '{' ) { + if ( !String_Parse( p, &uiInfo.mapList[uiInfo.mapCount].mapName ) || !String_Parse( p, &uiInfo.mapList[uiInfo.mapCount].mapLoadName ) + || !Int_Parse( p, &uiInfo.mapList[uiInfo.mapCount].teamMembers ) ) { + return qfalse; + } + + if ( !String_Parse( p, &uiInfo.mapList[uiInfo.mapCount].opponentName ) ) { + return qfalse; + } + + uiInfo.mapList[uiInfo.mapCount].typeBits = 0; + + while ( 1 ) { + token = COM_ParseExt( p, qtrue ); + if ( token[0] >= '0' && token[0] <= '9' ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= ( 1 << ( token[0] - 0x030 ) ); + if ( !Int_Parse( p, &uiInfo.mapList[uiInfo.mapCount].timeToBeat[token[0] - 0x30] ) ) { + return qfalse; + } + } else { + break; + } + } + + //mapList[mapCount].imageName = String_Alloc(va("levelshots/%s", mapList[mapCount].mapLoadName)); + //if (uiInfo.mapCount == 0) { + // only load the first cinematic, selection loads the others + // uiInfo.mapList[uiInfo.mapCount].cinematic = trap_CIN_PlayCinematic(va("%s.roq",uiInfo.mapList[uiInfo.mapCount].mapLoadName), qfalse, qfalse, qtrue, 0, 0, 0, 0); + //} + uiInfo.mapList[uiInfo.mapCount].cinematic = -1; + uiInfo.mapList[uiInfo.mapCount].levelShot = trap_R_RegisterShaderNoMip( va( "levelshots/%s_small", uiInfo.mapList[uiInfo.mapCount].mapLoadName ) ); + + if ( uiInfo.mapCount < MAX_MAPS ) { + uiInfo.mapCount++; + } else { + Com_Printf( "Too many maps, last one replaced!\n" ); + } + } + } + return qfalse; +} + +static void UI_ParseGameInfo( const char *teamFile ) { + char *token; + char *p; + char *buff = NULL; + // int mode = 0; // TTimo: unused + + buff = GetMenuBuffer( teamFile ); + if ( !buff ) { + return; + } + + p = buff; + + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( !token || token[0] == 0 || token[0] == '}' ) { + break; + } + + if ( Q_stricmp( token, "}" ) == 0 ) { + break; + } + + if ( Q_stricmp( token, "gametypes" ) == 0 ) { + + if ( GameType_Parse( &p, qfalse ) ) { + continue; + } else { + break; + } + } + + if ( Q_stricmp( token, "joingametypes" ) == 0 ) { + + if ( GameType_Parse( &p, qtrue ) ) { + continue; + } else { + break; + } + } + + if ( Q_stricmp( token, "maps" ) == 0 ) { + // start a new menu + MapList_Parse( &p ); + } + + } +} + +static void UI_Pause( qboolean b ) { + if ( b ) { + // pause the game and set the ui keycatcher + trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + } else { + // unpause the game and clear the ui keycatcher + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + } +} + +/* +// TTimo: unused +static int UI_OwnerDraw_Width(int ownerDraw) { + return 0; +} +*/ + +static int UI_PlayCinematic( const char *name, float x, float y, float w, float h ) { + return trap_CIN_PlayCinematic( name, x, y, w, h, ( CIN_loop | CIN_silent ) ); +} + +static void UI_StopCinematic( int handle ) { + if ( handle >= 0 ) { + trap_CIN_StopCinematic( handle ); + } else { + handle = abs( handle ); + if ( handle == UI_MAPCINEMATIC ) { + if ( uiInfo.mapList[ui_currentMap.integer].cinematic >= 0 ) { + trap_CIN_StopCinematic( uiInfo.mapList[ui_currentMap.integer].cinematic ); + uiInfo.mapList[ui_currentMap.integer].cinematic = -1; + } + } else if ( handle == UI_NETMAPCINEMATIC ) { + if ( uiInfo.serverStatus.currentServerCinematic >= 0 ) { + trap_CIN_StopCinematic( uiInfo.serverStatus.currentServerCinematic ); + uiInfo.serverStatus.currentServerCinematic = -1; + } + } else if ( handle == UI_CLANCINEMATIC ) { + int i = UI_TeamIndexFromName( UI_Cvar_VariableString( "ui_teamName" ) ); + if ( i >= 0 && i < uiInfo.teamCount ) { + if ( uiInfo.teamList[i].cinematic >= 0 ) { + trap_CIN_StopCinematic( uiInfo.teamList[i].cinematic ); + uiInfo.teamList[i].cinematic = -1; + } + } + } + } +} + +static void UI_DrawCinematic( int handle, float x, float y, float w, float h ) { + trap_CIN_SetExtents( handle, x, y, w, h ); + trap_CIN_DrawCinematic( handle ); +} + +static void UI_RunCinematicFrame( int handle ) { + trap_CIN_RunCinematic( handle ); +} + + + +/* +================= +PlayerModel_BuildList +================= +*/ +/* +// TTimo: unused +static void UI_BuildQ3Model_List( void ) +{ + int numdirs; + int numfiles; + char dirlist[2048]; + char filelist[2048]; + char skinname[64]; + char* dirptr; + char* fileptr; + int i; + int j; + int dirlen; + int filelen; + + uiInfo.q3HeadCount = 0; + + // iterate directory of all player models + numdirs = trap_FS_GetFileList("models/players", "/", dirlist, 2048 ); + dirptr = dirlist; + for (i=0; i uiInfo.uiDC.glconfig.vidHeight * 640 ) { + // wide screen + uiInfo.uiDC.bias = 0.5 * ( uiInfo.uiDC.glconfig.vidWidth - ( uiInfo.uiDC.glconfig.vidHeight * ( 640.0 / 480.0 ) ) ); + } else { + // no wide screen + uiInfo.uiDC.bias = 0; + } + + + //UI_Load(); + uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + uiInfo.uiDC.setColor = &UI_SetColor; + uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic; + uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic; + uiInfo.uiDC.drawText = &Text_Paint; + uiInfo.uiDC.textWidth = &Text_Width; + uiInfo.uiDC.textHeight = &Text_Height; + uiInfo.uiDC.textFont = &Text_SetActiveFont; + uiInfo.uiDC.registerModel = &trap_R_RegisterModel; + uiInfo.uiDC.modelBounds = &trap_R_ModelBounds; + uiInfo.uiDC.fillRect = &UI_FillRect; + uiInfo.uiDC.drawRect = &_UI_DrawRect; + uiInfo.uiDC.drawSides = &_UI_DrawSides; + uiInfo.uiDC.drawTopBottom = &_UI_DrawTopBottom; + uiInfo.uiDC.clearScene = &trap_R_ClearScene; + uiInfo.uiDC.drawSides = &_UI_DrawSides; + uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + uiInfo.uiDC.renderScene = &trap_R_RenderScene; + uiInfo.uiDC.registerFont = &trap_R_RegisterFont; + uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw; + uiInfo.uiDC.getValue = &UI_GetValue; + uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible; + uiInfo.uiDC.runScript = &UI_RunMenuScript; + uiInfo.uiDC.getTeamColor = &UI_GetTeamColor; + uiInfo.uiDC.setCVar = trap_Cvar_Set; + uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer; + uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue; + uiInfo.uiDC.drawTextWithCursor = &Text_PaintWithCursor; + uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound; + uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey; + uiInfo.uiDC.feederCount = &UI_FeederCount; + uiInfo.uiDC.feederItemImage = &UI_FeederItemImage; + uiInfo.uiDC.feederItemText = &UI_FeederItemText; + uiInfo.uiDC.fileText = &UI_FileText; //----(SA) re-added + uiInfo.uiDC.feederSelection = &UI_FeederSelection; + uiInfo.uiDC.feederAddItem = &UI_FeederAddItem; // NERVE - SMF + uiInfo.uiDC.setBinding = &trap_Key_SetBinding; + uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf; + uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText; + uiInfo.uiDC.Error = &Com_Error; + uiInfo.uiDC.Print = &Com_Printf; + uiInfo.uiDC.Pause = &UI_Pause; + uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth; + uiInfo.uiDC.registerSound = &trap_S_RegisterSound; + uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + uiInfo.uiDC.playCinematic = &UI_PlayCinematic; + uiInfo.uiDC.stopCinematic = &UI_StopCinematic; + uiInfo.uiDC.drawCinematic = &UI_DrawCinematic; + uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame; + uiInfo.uiDC.translateString = &trap_TranslateString; // NERVE - SMF + uiInfo.uiDC.checkAutoUpdate = &trap_CheckAutoUpdate; // DHM - Nerve + uiInfo.uiDC.getAutoUpdate = &trap_GetAutoUpdate; // DHM - Nerve + + Init_Display( &uiInfo.uiDC ); + + String_Init(); + + uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip( "white" ); + + AssetCache(); + + start = trap_Milliseconds(); + + uiInfo.teamCount = 0; + uiInfo.characterCount = 0; + uiInfo.aliasCount = 0; + + UI_ParseGameInfo( "gameinfo.txt" ); + + UI_LoadMenus( "ui_mp/ingame.txt", qfalse ); + + Menus_CloseAll(); + + trap_LAN_LoadCachedServers(); + UI_LoadBestScores( uiInfo.mapList[0].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum ); + + // sets defaults for ui temp cvars + uiInfo.effectsColor = gamecodetoui[(int)trap_Cvar_VariableValue( "color" ) - 1]; + uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue( "cg_drawCrosshair" ); + trap_Cvar_Set( "ui_mousePitch", ( trap_Cvar_VariableValue( "m_pitch" ) >= 0 ) ? "0" : "1" ); + + uiInfo.serverStatus.currentServerCinematic = -1; + uiInfo.previewMovie = -1; + + if ( trap_Cvar_VariableValue( "ui_TeamArenaFirstRun" ) == 0 ) { + trap_Cvar_Set( "s_volume", "0.8" ); + trap_Cvar_Set( "s_musicvolume", "0.5" ); + trap_Cvar_Set( "ui_TeamArenaFirstRun", "1" ); + } + + trap_Cvar_Register( NULL, "debug_protocol", "", 0 ); + + // NERVE - SMF - hardwire net cvars + trap_Cvar_Set( "ui_netGameType", "0" ); + trap_Cvar_Set( "ui_actualNetGameType", "5" ); + // -NERVE - SMF + + // init Yes/No once for cl_language -> server browser (punkbuster) + Q_strncpyz( translated_yes, DC->translateString( "Yes" ), sizeof( translated_yes ) ); + Q_strncpyz( translated_no, DC->translateString( "NO" ), sizeof( translated_no ) ); +} + + +/* +================= +UI_KeyEvent +================= +*/ +void _UI_KeyEvent( int key, qboolean down ) { + static qboolean bypassKeyClear = qfalse; + + if ( Menu_Count() > 0 ) { + menuDef_t *menu = Menu_GetFocused(); + if ( menu ) { + if ( trap_Cvar_VariableValue( "cl_bypassMouseInput" ) ) { + bypassKeyClear = qtrue; + } + + if ( key == K_ESCAPE && down && !Menus_AnyFullScreenVisible() ) { + Menus_CloseAll(); + } else { + Menu_HandleKey( menu, key, down ); + } + } else { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + + // NERVE - SMF - we don't want to clear key states if bypassing input + if ( !bypassKeyClear ) { + trap_Key_ClearStates(); + } + + bypassKeyClear = qfalse; + + trap_Cvar_Set( "cl_paused", "0" ); + } + } + + //if ((s > 0) && (s != menu_null_sound)) { + // trap_S_StartLocalSound( s, CHAN_LOCAL_SOUND ); + //} +} + +/* +================= +UI_MouseEvent +================= +*/ +void _UI_MouseEvent( int dx, int dy ) { + // update mouse screen position + uiInfo.uiDC.cursorx += dx; + if ( uiInfo.uiDC.cursorx < 0 ) { + uiInfo.uiDC.cursorx = 0; + } else if ( uiInfo.uiDC.cursorx > SCREEN_WIDTH ) { + uiInfo.uiDC.cursorx = SCREEN_WIDTH; + } + + uiInfo.uiDC.cursory += dy; + if ( uiInfo.uiDC.cursory < 0 ) { + uiInfo.uiDC.cursory = 0; + } else if ( uiInfo.uiDC.cursory > SCREEN_HEIGHT ) { + uiInfo.uiDC.cursory = SCREEN_HEIGHT; + } + + if ( Menu_Count() > 0 ) { + //menuDef_t *menu = Menu_GetFocused(); + //Menu_HandleMouseMove(menu, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); + Display_MouseMove( NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory ); + } + +} + +void UI_LoadNonIngame() { + const char *menuSet = UI_Cvar_VariableString( "ui_menuFiles" ); + + if ( menuSet == NULL || menuSet[0] == '\0' ) { + menuSet = "ui_mp/menus.txt"; + } + UI_LoadMenus( menuSet, qfalse ); + uiInfo.inGameLoad = qfalse; +} + + +//----(SA) added +static uiMenuCommand_t menutype = UIMENU_NONE; + +uiMenuCommand_t _UI_GetActiveMenu( void ) { + return menutype; +} +//----(SA) end + +#define MISSING_FILES_MSG "The following packs are missing:" + +void _UI_SetActiveMenu( uiMenuCommand_t menu ) { + char buf[4096]; // com_errorMessage can go up to 4096 + char *missing_files; + + // this should be the ONLY way the menu system is brought up + // enusure minumum menu data is cached + if ( Menu_Count() > 0 ) { + vec3_t v; + v[0] = v[1] = v[2] = 0; + + menutype = menu; //----(SA) added + + switch ( menu ) { + case UIMENU_NONE: + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + + return; + case UIMENU_MAIN: + trap_Key_SetCatcher( KEYCATCH_UI ); + if ( uiInfo.inGameLoad ) { + UI_LoadNonIngame(); + } + Menus_CloseAll(); + Menus_ActivateByName( "main", qtrue ); + trap_Cvar_VariableStringBuffer( "com_errorMessage", buf, sizeof( buf ) ); + // JPW NERVE stricmp() is silly but works, take a look at error.menu to see why. I think this is bustified in q3ta + // NOTE TTimo - I'm not sure Q_stricmp is useful to anything anymore + // show_bug.cgi?id=507 + // TTimo - improved and tweaked that area a whole bunch + if ( ( strlen( buf ) ) && ( Q_stricmp( buf,";" ) ) ) { + trap_Cvar_Set( "com_errorMessage", trap_TranslateString( buf ) ); // NERVE - SMF + // hacky, wanted to have the printout of missing files + // text printing limitations force us to keep it all in a single message + // NOTE: this works thanks to flip flop in UI_Cvar_VariableString + if ( UI_Cvar_VariableString( "com_errorDiagnoseIP" )[0] ) { + missing_files = UI_Cvar_VariableString( "com_missingFiles" ); + if ( missing_files[0] ) { + trap_Cvar_Set( "com_errorMessage", + va( "%s\n\n%s\n%s", + UI_Cvar_VariableString( "com_errorMessage" ), + trap_TranslateString( MISSING_FILES_MSG ), + missing_files ) ); + } + } + Menus_ActivateByName( "error_popmenu_diagnose", qtrue ); + } + return; + + case UIMENU_TEAM: + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_ActivateByName( "team", qtrue ); + return; + + case UIMENU_NEED_CD: + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_ActivateByName( "needcd", qtrue ); + return; + + case UIMENU_BAD_CD_KEY: + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_ActivateByName( "badcd", qtrue ); + return; + + case UIMENU_INGAME: + trap_Key_SetCatcher( KEYCATCH_UI ); + UI_BuildPlayerList(); + Menus_CloseAll(); + Menus_ActivateByName( "ingame", qtrue ); + return; + + // NERVE - SMF + case UIMENU_WM_QUICKMESSAGE: + DC->cursorx = 639; + DC->cursory = 479; + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_OpenByName( "wm_quickmessage" ); + return; + + case UIMENU_WM_QUICKMESSAGEALT: + DC->cursorx = 639; + DC->cursory = 479; + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_OpenByName( "wm_quickmessageAlt" ); + return; + + case UIMENU_WM_LIMBO: + if ( !trap_Cvar_VariableValue( "ui_limboMode" ) ) { + DC->cursorx = 320; + DC->cursory = 240; + } + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_OpenByName( "wm_limboView" ); + return; + + case UIMENU_WM_AUTOUPDATE: + // TTimo - changing the auto-update strategy to a modal prompt + Menus_OpenByName( "wm_autoupdate_modal" ); + return; + // -NERVE - SMF + default: + return; // TTimo: a lot of not handled + } + } +} + +qboolean _UI_IsFullscreen( void ) { + return Menus_AnyFullScreenVisible(); +} + + + +static connstate_t lastConnState; +static char lastLoadingText[MAX_INFO_VALUE]; + +static void UI_ReadableSize( char *buf, int bufsize, int value ) { + if ( value > 1024 * 1024 * 1024 ) { // gigs + Com_sprintf( buf, bufsize, "%d", value / ( 1024 * 1024 * 1024 ) ); + Com_sprintf( buf + strlen( buf ), bufsize - strlen( buf ), ".%02d GB", + ( value % ( 1024 * 1024 * 1024 ) ) * 100 / ( 1024 * 1024 * 1024 ) ); + } else if ( value > 1024 * 1024 ) { // megs + Com_sprintf( buf, bufsize, "%d", value / ( 1024 * 1024 ) ); + Com_sprintf( buf + strlen( buf ), bufsize - strlen( buf ), ".%02d MB", + ( value % ( 1024 * 1024 ) ) * 100 / ( 1024 * 1024 ) ); + } else if ( value > 1024 ) { // kilos + Com_sprintf( buf, bufsize, "%d KB", value / 1024 ); + } else { // bytes + Com_sprintf( buf, bufsize, "%d bytes", value ); + } +} + +// Assumes time is in sec +static void UI_PrintTime( char *buf, int bufsize, int time ) { + //time /= 1000; // change to seconds + + if ( time > 3600 ) { // in the hours range + Com_sprintf( buf, bufsize, "%d hr %d min", time / 3600, ( time % 3600 ) / 60 ); + } else if ( time > 60 ) { // mins + Com_sprintf( buf, bufsize, "%d min %d sec", time / 60, time % 60 ); + } else { // secs + Com_sprintf( buf, bufsize, "%d sec", time ); + } +} + +void Text_PaintCenter( float x, float y, float scale, vec4_t color, const char *text, float adjust ) { + int len = Text_Width( text, scale, 0 ); + Text_Paint( x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); +} + +#define ESTIMATES 80 +static void UI_DisplayDownloadInfo( const char *downloadName, float centerPoint, float yStart, float scale ) { + static char dlText[] = "Downloading:"; + static char etaText[] = "Estimated time left:"; + static char xferText[] = "Transfer rate:"; + static int tleEstimates[ESTIMATES] = { 60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60, + 60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60, + 60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60, + 60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60 }; + static int tleIndex = 0; + + int downloadSize, downloadCount, downloadTime; + char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64]; + int xferRate; + const char *s; + + vec4_t bg_color = { 0.3f, 0.3f, 0.3f, 0.8f }; + + downloadSize = trap_Cvar_VariableValue( "cl_downloadSize" ); + downloadCount = trap_Cvar_VariableValue( "cl_downloadCount" ); + downloadTime = trap_Cvar_VariableValue( "cl_downloadTime" ); + + // Background + UI_FillRect( 0, yStart + 185, 640, 83, bg_color ); + + UI_SetColor( colorYellow ); + Text_Paint( 92, yStart + 210, scale, colorYellow, dlText, 0, 64, ITEM_TEXTSTYLE_SHADOWEDMORE ); + Text_Paint( 35, yStart + 235, scale, colorYellow, etaText, 0, 64, ITEM_TEXTSTYLE_SHADOWEDMORE ); + Text_Paint( 86, yStart + 260, scale, colorYellow, xferText, 0, 64, ITEM_TEXTSTYLE_SHADOWEDMORE ); + + if ( downloadSize > 0 ) { + s = va( "%s (%d%%)", downloadName, downloadCount * 100 / downloadSize ); + } else { + s = downloadName; + } + + Text_Paint( 260, yStart + 210, scale, colorYellow, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); + + UI_ReadableSize( dlSizeBuf, sizeof dlSizeBuf, downloadCount ); + UI_ReadableSize( totalSizeBuf, sizeof totalSizeBuf, downloadSize ); + + if ( downloadCount < 4096 || !downloadTime ) { + Text_PaintCenter( centerPoint, yStart + 235, scale, colorYellow, "estimating", 0 ); + Text_PaintCenter( centerPoint, yStart + 340, scale, colorYellow, va( "(%s of %s copied)", dlSizeBuf, totalSizeBuf ), 0 ); + } else { + if ( ( uiInfo.uiDC.realTime - downloadTime ) / 1000 ) { + xferRate = downloadCount / ( ( uiInfo.uiDC.realTime - downloadTime ) / 1000 ); + } else { + xferRate = 0; + } + UI_ReadableSize( xferRateBuf, sizeof xferRateBuf, xferRate ); + + // Extrapolate estimated completion time + if ( downloadSize && xferRate ) { + int n = downloadSize / xferRate; // estimated time for entire d/l in secs + int timeleft = 0, i; + + // We do it in K (/1024) because we'd overflow around 4MB + tleEstimates[ tleIndex ] = ( n - ( ( ( downloadCount / 1024 ) * n ) / ( downloadSize / 1024 ) ) ); + tleIndex++; + if ( tleIndex >= ESTIMATES ) { + tleIndex = 0; + } + + for ( i = 0; i < ESTIMATES; i++ ) + timeleft += tleEstimates[ i ]; + + timeleft /= ESTIMATES; + + UI_PrintTime( dlTimeBuf, sizeof dlTimeBuf, timeleft ); + + Text_Paint( 260, yStart + 235, scale, colorYellow, dlTimeBuf, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); + Text_PaintCenter( centerPoint, yStart + 340, scale, colorYellow, va( "(%s of %s copied)", dlSizeBuf, totalSizeBuf ), 0 ); + } else { + Text_PaintCenter( centerPoint, yStart + 235, scale, colorYellow, "estimating", 0 ); + if ( downloadSize ) { + Text_PaintCenter( centerPoint, yStart + 340, scale, colorYellow, va( "(%s of %s copied)", dlSizeBuf, totalSizeBuf ), 0 ); + } else { + Text_PaintCenter( centerPoint, yStart + 340, scale, colorYellow, va( "(%s copied)", dlSizeBuf ), 0 ); + } + } + + if ( xferRate ) { + Text_Paint( 260, yStart + 260, scale, colorYellow, va( "%s/Sec", xferRateBuf ), 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); + } + } +} + +/* +======================== +UI_DrawConnectScreen + +This will also be overlaid on the cgame info screen during loading +to prevent it from blinking away too rapidly on local or lan games. +======================== +*/ +#define CP_LINEWIDTH 50 + +void UI_DrawConnectScreen( qboolean overlay ) { + char *s; + uiClientState_t cstate; + char info[MAX_INFO_VALUE]; + char text[256]; + float centerPoint, yStart, scale; + vec4_t color = { 0.3f, 0.3f, 0.3f, 0.8f }; + + char downloadName[MAX_INFO_VALUE]; + + menuDef_t *menu = Menus_FindByName( "Connect" ); + + + if ( !overlay && menu ) { + Menu_Paint( menu, qtrue ); + } + + if ( !overlay ) { + centerPoint = 320; + yStart = 130; + scale = 0.4f; + } else { + centerPoint = 320; + yStart = 32; + scale = 0.6f; + return; + } + + // see what information we should display + trap_GetClientState( &cstate ); + + info[0] = '\0'; + + if ( !Q_stricmp( cstate.servername,"localhost" ) ) { + Text_PaintCenter( centerPoint, yStart + 48, scale, colorWhite,va( "Wolf Multiplayer - Version: %s", Q3_VERSION ), ITEM_TEXTSTYLE_SHADOWEDMORE ); + } else { + strcpy( text, va( trap_TranslateString( "Connecting to %s" ), cstate.servername ) ); + Text_PaintCenter( centerPoint, yStart + 48, scale, colorWhite,text, ITEM_TEXTSTYLE_SHADOWEDMORE ); + } + + // display global MOTD at bottom (don't draw during download, the space is already used) + // moved downloadName query up, this is used in CA_CONNECTED + trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof( downloadName ) ); + if ( !*downloadName ) { + Text_PaintCenter( centerPoint, 475, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0 ); + } + + // print any server info (server full, bad version, etc) + // DHM - Nerve :: This now accepts strings up to 256 chars long, and will break them up into multiple lines. + // They are also now printed in Yellow for readability. + if ( cstate.connState < CA_CONNECTED ) { + char *s; + char ps[60]; + int i, len, index = 0, yPrint = yStart + 210; + qboolean neednewline = qfalse; + + s = trap_TranslateString( cstate.messageString ); + len = strlen( s ); + + for ( i = 0; i < len; i++, index++ ) { + + // copy to temp buffer + ps[index] = s[i]; + + if ( index > ( CP_LINEWIDTH - 10 ) && i > 0 ) { + neednewline = qtrue; + } + + // if out of temp buffer room OR end of string OR it is time to linebreak & we've found a space + if ( ( index >= 58 ) || ( i == ( len - 1 ) ) || ( neednewline && s[i] == ' ' ) ) { + ps[index + 1] = '\0'; + + DC->fillRect( 0, yPrint - 17, 640, 22, color ); + Text_PaintCenter( centerPoint, yPrint, scale, colorYellow, ps, 0 ); + + neednewline = qfalse; + yPrint += 22; // next line + index = -1; // sigh, for loop will increment to 0 + } + } + + } + + if ( lastConnState > cstate.connState ) { + lastLoadingText[0] = '\0'; + } + lastConnState = cstate.connState; + + switch ( cstate.connState ) { + case CA_CONNECTING: + s = va( trap_TranslateString( "Awaiting connection...%i" ), cstate.connectPacketCount ); + break; + case CA_CHALLENGING: + s = va( trap_TranslateString( "Awaiting challenge...%i" ), cstate.connectPacketCount ); + break; + case CA_CONNECTED: + if ( *downloadName ) { + UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, scale ); + return; + } + s = trap_TranslateString( "Awaiting gamestate..." ); + break; + case CA_LOADING: + return; + case CA_PRIMED: + return; + default: + return; + } + + + if ( Q_stricmp( cstate.servername,"localhost" ) ) { + Text_PaintCenter( centerPoint, yStart + 80, scale, colorWhite, s, 0 ); + } + + // password required / connection rejected information goes here +} + + +/* +================ +cvars +================ +*/ + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +vmCvar_t ui_ffa_fraglimit; +vmCvar_t ui_ffa_timelimit; + +vmCvar_t ui_tourney_fraglimit; +vmCvar_t ui_tourney_timelimit; + +vmCvar_t ui_team_fraglimit; +vmCvar_t ui_team_timelimit; +vmCvar_t ui_team_friendly; + +vmCvar_t ui_ctf_capturelimit; +vmCvar_t ui_ctf_timelimit; +vmCvar_t ui_ctf_friendly; + +vmCvar_t ui_arenasFile; +vmCvar_t ui_botsFile; +vmCvar_t ui_spScores1; +vmCvar_t ui_spScores2; +vmCvar_t ui_spScores3; +vmCvar_t ui_spScores4; +vmCvar_t ui_spScores5; +vmCvar_t ui_spAwards; +vmCvar_t ui_spVideos; +vmCvar_t ui_spSkill; + +vmCvar_t ui_spSelection; +vmCvar_t ui_master; + +vmCvar_t ui_brassTime; +vmCvar_t ui_drawCrosshair; +vmCvar_t ui_drawCrosshairNames; +vmCvar_t ui_drawCrosshairPickups; //----(SA) added +vmCvar_t ui_marks; +// JOSEPH 12-3-99 +vmCvar_t ui_autoactivate; +vmCvar_t ui_emptyswitch; //----(SA) added +// END JOSEPH + +vmCvar_t ui_server1; +vmCvar_t ui_server2; +vmCvar_t ui_server3; +vmCvar_t ui_server4; +vmCvar_t ui_server5; +vmCvar_t ui_server6; +vmCvar_t ui_server7; +vmCvar_t ui_server8; +vmCvar_t ui_server9; +vmCvar_t ui_server10; +vmCvar_t ui_server11; +vmCvar_t ui_server12; +vmCvar_t ui_server13; +vmCvar_t ui_server14; +vmCvar_t ui_server15; +vmCvar_t ui_server16; + +vmCvar_t ui_cdkeychecked; +vmCvar_t ui_smallFont; +vmCvar_t ui_bigFont; + +vmCvar_t ui_selectedPlayer; +vmCvar_t ui_selectedPlayerName; +vmCvar_t ui_netSource; +vmCvar_t ui_menuFiles; +vmCvar_t ui_gameType; +vmCvar_t ui_netGameType; +vmCvar_t ui_actualNetGameType; +vmCvar_t ui_joinGameType; +vmCvar_t ui_dedicated; + +vmCvar_t ui_notebookCurrentPage; //----(SA) added +vmCvar_t ui_clipboardName; // the name of the group for the current clipboard item //----(SA) added +vmCvar_t ui_hudAlpha; + +// NERVE - SMF - cvars for multiplayer +vmCvar_t ui_serverFilterType; +vmCvar_t ui_currentNetMap; +vmCvar_t ui_currentMap; +vmCvar_t ui_mapIndex; + +vmCvar_t ui_browserMaster; +vmCvar_t ui_browserGameType; +vmCvar_t ui_browserSortKey; +vmCvar_t ui_browserShowFull; +vmCvar_t ui_browserShowEmpty; +vmCvar_t ui_browserShowFriendlyFire; // NERVE - SMF +vmCvar_t ui_browserShowMaxlives; // NERVE - SMF +vmCvar_t ui_browserShowTourney; // NERVE - SMF +vmCvar_t ui_browserShowPunkBuster; // DHM - Nerve +vmCvar_t ui_browserShowAntilag; // TTimo + +vmCvar_t ui_serverStatusTimeOut; + +vmCvar_t ui_Q3Model; +vmCvar_t ui_headModel; +vmCvar_t ui_model; + +vmCvar_t ui_limboOptions; +vmCvar_t ui_limboPrevOptions; +vmCvar_t ui_limboObjective; + +vmCvar_t ui_cmd; + +vmCvar_t ui_prevTeam; +vmCvar_t ui_prevClass; +vmCvar_t ui_prevWeapon; + +vmCvar_t ui_limboMode; +vmCvar_t ui_objective; + +vmCvar_t ui_team; +vmCvar_t ui_class; +vmCvar_t ui_weapon; + +vmCvar_t ui_isSpectator; + +vmCvar_t ui_friendlyFire; +vmCvar_t ui_allowVote; + +vmCvar_t ui_userTimeLimit; +vmCvar_t ui_userAlliedRespawnTime; +vmCvar_t ui_userAxisRespawnTime; +vmCvar_t ui_glCustom; // JPW NERVE missing from q3ta +// -NERVE - SMF + +cvarTable_t cvarTable[] = { + + { &ui_glCustom, "ui_glCustom", "4", CVAR_ARCHIVE }, // JPW NERVE missing from q3ta + { &ui_ffa_fraglimit, "ui_ffa_fraglimit", "20", CVAR_ARCHIVE }, + { &ui_ffa_timelimit, "ui_ffa_timelimit", "0", CVAR_ARCHIVE }, + + { &ui_tourney_fraglimit, "ui_tourney_fraglimit", "0", CVAR_ARCHIVE }, + { &ui_tourney_timelimit, "ui_tourney_timelimit", "15", CVAR_ARCHIVE }, + + { &ui_team_fraglimit, "ui_team_fraglimit", "0", CVAR_ARCHIVE }, + { &ui_team_timelimit, "ui_team_timelimit", "20", CVAR_ARCHIVE }, + { &ui_team_friendly, "ui_team_friendly", "1", CVAR_ARCHIVE }, + + { &ui_ctf_capturelimit, "ui_ctf_capturelimit", "8", CVAR_ARCHIVE }, + { &ui_ctf_timelimit, "ui_ctf_timelimit", "30", CVAR_ARCHIVE }, + { &ui_ctf_friendly, "ui_ctf_friendly", "0", CVAR_ARCHIVE }, + + { &ui_arenasFile, "g_arenasFile", "", CVAR_INIT | CVAR_ROM }, + { &ui_botsFile, "g_botsFile", "", CVAR_INIT | CVAR_ROM }, + { &ui_spScores1, "g_spScores1", "", CVAR_ARCHIVE | CVAR_ROM }, + { &ui_spScores2, "g_spScores2", "", CVAR_ARCHIVE | CVAR_ROM }, + { &ui_spScores3, "g_spScores3", "", CVAR_ARCHIVE | CVAR_ROM }, + { &ui_spScores4, "g_spScores4", "", CVAR_ARCHIVE | CVAR_ROM }, + { &ui_spScores5, "g_spScores5", "", CVAR_ARCHIVE | CVAR_ROM }, + { &ui_spAwards, "g_spAwards", "", CVAR_ARCHIVE | CVAR_ROM }, + { &ui_spVideos, "g_spVideos", "", CVAR_ARCHIVE | CVAR_ROM }, + { &ui_spSkill, "g_spSkill", "2", CVAR_ARCHIVE | CVAR_LATCH }, + + // NERVE - SMF + { &ui_friendlyFire, "g_friendlyFire", "1", CVAR_ARCHIVE }, + { &ui_allowVote, "g_allowvote", "1", CVAR_ARCHIVE }, + + { &ui_userTimeLimit, "ui_userTimeLimit", "0", 0 }, + { &ui_userAlliedRespawnTime, "ui_userAlliedRespawnTime", "0", 0 }, + { &ui_userAxisRespawnTime, "ui_userAxisRespawnTime", "0", 0 }, + // -NERVE - SMF + +// JPW NERVE + { &ui_teamArenaFirstRun, "ui_teamArenaFirstRun", "0", CVAR_ARCHIVE}, // so sound stuff latches, strange as that seems +// jpw + + { &ui_spSelection, "ui_spSelection", "", CVAR_ROM }, + { &ui_master, "ui_master", "0", CVAR_ARCHIVE }, + + { &ui_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, // JPW NERVE + { &ui_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, + { &ui_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &ui_drawCrosshairPickups, "cg_drawCrosshairPickups", "1", CVAR_ARCHIVE }, //----(SA) added + { &ui_marks, "cg_marktime", "20000", CVAR_ARCHIVE }, + // JOSEPH 12-2-99 + { &ui_autoactivate, "cg_autoactivate", "1", CVAR_ARCHIVE }, + // END JOSEPH + + { &ui_server1, "server1", "", CVAR_ARCHIVE }, + { &ui_server2, "server2", "", CVAR_ARCHIVE }, + { &ui_server3, "server3", "", CVAR_ARCHIVE }, + { &ui_server4, "server4", "", CVAR_ARCHIVE }, + { &ui_server5, "server5", "", CVAR_ARCHIVE }, + { &ui_server6, "server6", "", CVAR_ARCHIVE }, + { &ui_server7, "server7", "", CVAR_ARCHIVE }, + { &ui_server8, "server8", "", CVAR_ARCHIVE }, + { &ui_server9, "server9", "", CVAR_ARCHIVE }, + { &ui_server10, "server10", "", CVAR_ARCHIVE }, + { &ui_server11, "server11", "", CVAR_ARCHIVE }, + { &ui_server12, "server12", "", CVAR_ARCHIVE }, + { &ui_server13, "server13", "", CVAR_ARCHIVE }, + { &ui_server14, "server14", "", CVAR_ARCHIVE }, + { &ui_server15, "server15", "", CVAR_ARCHIVE }, + { &ui_server16, "server16", "", CVAR_ARCHIVE }, + + { &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE }, + { &ui_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, + { &ui_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, + { &ui_cdkeychecked, "ui_cdkeychecked", "0", CVAR_ROM }, + { &ui_selectedPlayer, "cg_selectedPlayer", "0", CVAR_ARCHIVE}, + { &ui_selectedPlayerName, "cg_selectedPlayerName", "", CVAR_ARCHIVE}, + { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE }, + { &ui_menuFiles, "ui_menuFiles", "ui_mp/menus.txt", CVAR_ARCHIVE }, + { &ui_gameType, "ui_gametype", "3", CVAR_ARCHIVE }, + { &ui_joinGameType, "ui_joinGametype", "0", CVAR_ARCHIVE }, + { &ui_netGameType, "ui_netGametype", "0", CVAR_ARCHIVE }, // NERVE - SMF - hardwired for now + { &ui_actualNetGameType, "ui_actualNetGametype", "5", CVAR_ARCHIVE }, // NERVE - SMF - hardwired for now + + { &ui_notebookCurrentPage, "ui_notebookCurrentPage", "1", CVAR_ROM}, + { &ui_clipboardName, "cg_clipboardName", "", CVAR_ROM }, + + // NERVE - SMF - multiplayer cvars + { &ui_mapIndex, "ui_mapIndex", "0", CVAR_ARCHIVE }, + { &ui_currentMap, "ui_currentMap", "0", CVAR_ARCHIVE }, + { &ui_currentNetMap, "ui_currentNetMap", "0", CVAR_ARCHIVE }, + + { &ui_browserMaster, "ui_browserMaster", "0", CVAR_ARCHIVE }, + { &ui_browserGameType, "ui_browserGameType", "0", CVAR_ARCHIVE }, + { &ui_browserSortKey, "ui_browserSortKey", "4", CVAR_ARCHIVE }, + { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE }, + { &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE }, + { &ui_browserShowFriendlyFire, "ui_browserShowFriendlyFire", "0", CVAR_ARCHIVE }, + { &ui_browserShowMaxlives, "ui_browserShowMaxlives", "1", CVAR_ARCHIVE }, + { &ui_browserShowTourney, "ui_browserShowTourney", "1", CVAR_ARCHIVE }, + { &ui_browserShowPunkBuster, "ui_browserShowPunkBuster", "0", CVAR_ARCHIVE }, + { &ui_browserShowAntilag, "ui_browserShowAntilag", "0", CVAR_ARCHIVE }, + + { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE}, + + { &ui_Q3Model, "ui_Q3Model", "1", 0 }, + { &ui_headModel, "headModel", "", 0 }, + + { &ui_limboOptions, "ui_limboOptions", "0", 0 }, + { &ui_limboPrevOptions, "ui_limboPrevOptions", "0", 0 }, + { &ui_limboObjective, "ui_limboObjective", "0", 0 }, + { &ui_cmd, "ui_cmd", "", 0 }, + + { &ui_prevTeam, "ui_prevTeam", "-1", 0 }, + { &ui_prevClass, "ui_prevClass", "-1", 0 }, + { &ui_prevWeapon, "ui_prevWeapon", "-1", 0 }, + + { &ui_limboMode, "ui_limboMode", "0", 0 }, + { &ui_objective, "ui_objective", "", 0 }, + + { &ui_team, "ui_team", "Axis", 0 }, + { &ui_class, "ui_class", "Soldier", 0 }, + { &ui_weapon, "ui_weapon", "MP 40", 0 }, + + { &ui_isSpectator, "ui_isSpectator", "1", 0 }, + // -NERVE - SMF + + { &ui_hudAlpha, "cg_hudAlpha", "1.0", CVAR_ARCHIVE } +}; + +int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + + +/* +================= +UI_RegisterCvars +================= +*/ +void UI_RegisterCvars( void ) { + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags ); + } +} + +/* +================= +UI_UpdateCvars +================= +*/ +void UI_UpdateCvars( void ) { + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Update( cv->vmCvar ); + } +} + +// NERVE - SMF +/* +================= +ArenaServers_StopRefresh +================= +*/ +static void UI_StopServerRefresh( void ) { + int count; + + if ( !uiInfo.serverStatus.refreshActive ) { + // not currently refreshing + return; + } + uiInfo.serverStatus.refreshActive = qfalse; + Com_Printf( "%d servers listed in browser with %d players.\n", + uiInfo.serverStatus.numDisplayServers, + uiInfo.serverStatus.numPlayersOnServers ); + count = trap_LAN_GetServerCount( ui_netSource.integer ); + if ( count - uiInfo.serverStatus.numDisplayServers > 0 ) { + // TTimo - used to be about cl_maxping filtering, that was Q3 legacy, RTCW browser has much more filtering options + Com_Printf( "%d servers not listed (filtered out by game browser settings)\n", + count - uiInfo.serverStatus.numDisplayServers ); + } + +} + +/* +================= +UI_DoServerRefresh +================= +*/ +static void UI_DoServerRefresh( void ) { + qboolean wait = qfalse; + + if ( !uiInfo.serverStatus.refreshActive ) { + return; + } + if ( ui_netSource.integer != AS_FAVORITES ) { + if ( ui_netSource.integer == AS_LOCAL ) { + if ( !trap_LAN_GetServerCount( ui_netSource.integer ) ) { + wait = qtrue; + } + } else { + if ( trap_LAN_GetServerCount( ui_netSource.integer ) < 0 ) { + wait = qtrue; + } + } + } + + if ( uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime ) { + if ( wait ) { + return; + } + } + + // if still trying to retrieve pings + if ( trap_LAN_UpdateVisiblePings( ui_netSource.integer ) ) { + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + } else if ( !wait ) { + // get the last servers in the list + UI_BuildServerDisplayList( 2 ); + // stop the refresh + UI_StopServerRefresh(); + } + // + UI_BuildServerDisplayList( qfalse ); +} + +/* +================= +UI_StartServerRefresh +================= +*/ +static void UI_StartServerRefresh( qboolean full ) { + int i; + char *ptr; + + qtime_t q; + trap_RealTime( &q ); + trap_Cvar_Set( va( "ui_lastServerRefresh_%i", ui_netSource.integer ), va( "%s-%i, %i at %i:%i", MonthAbbrev[q.tm_mon],q.tm_mday, 1900 + q.tm_year,q.tm_hour,q.tm_min ) ); + + if ( !full ) { + UI_UpdatePendingPings(); + return; + } + + uiInfo.serverStatus.refreshActive = qtrue; + uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000; + // clear number of displayed servers + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + // mark all servers as visible so we store ping updates for them + trap_LAN_MarkServerVisible( ui_netSource.integer, -1, qtrue ); + // reset all the pings + trap_LAN_ResetPings( ui_netSource.integer ); + // + if ( ui_netSource.integer == AS_LOCAL ) { + trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" ); + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + return; + } + + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; + if ( ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER ) { + if ( ui_netSource.integer == AS_GLOBAL ) { + i = 0; + } else { + i = 1; + } + + ptr = UI_Cvar_VariableString( "debug_protocol" ); + if ( strlen( ptr ) ) { + trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %s full empty\n", i, ptr ) ); + } else { + trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %d full empty\n", i, (int)trap_Cvar_VariableValue( "protocol" ) ) ); + } + } +} +// -NERVE - SMF diff --git a/src/ui/ui_players.c b/src/ui/ui_players.c new file mode 100644 index 0000000..3ec9607 --- /dev/null +++ b/src/ui/ui_players.c @@ -0,0 +1,1725 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// ui_players.c + +#include "ui_local.h" + + +#define UI_TIMER_GESTURE 2300 +#define UI_TIMER_JUMP 1000 +#define UI_TIMER_LAND 130 +#define UI_TIMER_WEAPON_SWITCH 300 +#define UI_TIMER_ATTACK 500 +#define UI_TIMER_MUZZLE_FLASH 20 +#define UI_TIMER_WEAPON_DELAY 250 + +#define JUMP_HEIGHT 56 + +#define SWINGSPEED 0.3 + +#define SPIN_SPEED 0.9 +#define COAST_TIME 1000 + + +static int dp_realtime; +static float jumpHeight; + + +/* +=============== +UI_PlayerInfo_SetWeapon +=============== +*/ +static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) { + gitem_t * item; + char path[MAX_QPATH]; + + pi->currentWeapon = weaponNum; +tryagain: + pi->realWeapon = weaponNum; + pi->weaponModel = 0; + pi->barrelModel = 0; + pi->flashModel = 0; + + if ( weaponNum == WP_NONE ) { + return; + } + + // NERVE - SMF - multiplayer only hack to show correct panzerfaust and venom barrel + if ( weaponNum == WP_PANZERFAUST ) { + pi->weaponModel = trap_R_RegisterModel( "models/multiplayer/panzerfaust/multi_pf.md3" ); + return; + } else if ( weaponNum == WP_VENOM ) { + pi->barrelModel = trap_R_RegisterModel( "models/weapons2/venom/venom_barrel.md3" ); + } + // -NERVE - SMF + + for ( item = bg_itemlist + 1; item->classname ; item++ ) { + if ( item->giType != IT_WEAPON ) { + continue; + } + if ( item->giTag == weaponNum ) { + break; + } + } + + if ( item->classname ) { + pi->weaponModel = trap_R_RegisterModel( item->world_model[0] ); + } + + if ( pi->weaponModel == 0 ) { +// if( weaponNum == WP_MACHINEGUN ) { //----(SA) removing old weapon references + if ( weaponNum == WP_MP40 ) { + weaponNum = WP_NONE; + goto tryagain; + } +// weaponNum = WP_MACHINEGUN; //----(SA) removing old weapon references + weaponNum = WP_MP40; + goto tryagain; + } + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_flash.md3" ); + pi->flashModel = trap_R_RegisterModel( path ); + + switch ( weaponNum ) { + case WP_GAUNTLET: + MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 ); + break; + +// case WP_MACHINEGUN: +// MAKERGB( pi->flashDlightColor, 1, 1, 0 ); +// break; + +// case WP_SHOTGUN: +// MAKERGB( pi->flashDlightColor, 1, 1, 0 ); +// break; + + case WP_GRENADE_LAUNCHER: + MAKERGB( pi->flashDlightColor, 1, 0.7, 0.5 ); + break; + + case WP_ROCKET_LAUNCHER: + MAKERGB( pi->flashDlightColor, 1, 0.75, 0 ); + break; + + case WP_FLAMETHROWER: + MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 ); + break; + +// case WP_RAILGUN: +// MAKERGB( pi->flashDlightColor, 1, 0.5, 0 ); +// break; + +// case WP_BFG: +// MAKERGB( pi->flashDlightColor, 1, 0.7, 1 ); +// break; + +// case WP_GRAPPLING_HOOK: +// MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 ); +// break; + + default: + MAKERGB( pi->flashDlightColor, 1, 1, 1 ); + break; + } +} + + +/* +=============== +UI_ForceLegsAnim +=============== +*/ +static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) { + pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + + if ( anim == LEGS_JUMP ) { + pi->legsAnimationTimer = UI_TIMER_JUMP; + } +} + + +/* +=============== +UI_SetLegsAnim +=============== +*/ +/* +// TTimo: unused +static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) { + if ( pi->pendingLegsAnim ) { + anim = pi->pendingLegsAnim; + pi->pendingLegsAnim = 0; + } + UI_ForceLegsAnim( pi, anim ); +} +*/ + +/* +=============== +UI_ForceTorsoAnim +=============== +*/ +static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) { + pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + + if ( anim == TORSO_GESTURE ) { + pi->torsoAnimationTimer = UI_TIMER_GESTURE; + } + + if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 ) { + pi->torsoAnimationTimer = UI_TIMER_ATTACK; + } +} + + +/* +=============== +UI_SetTorsoAnim +=============== +*/ +/* +// TTimo: unused +static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) { + if ( pi->pendingTorsoAnim ) { + anim = pi->pendingTorsoAnim; + pi->pendingTorsoAnim = 0; + } + + UI_ForceTorsoAnim( pi, anim ); +} +*/ + +/* +=============== +UI_TorsoSequencing +=============== +*/ +/* +// TTimo: unused +static void UI_TorsoSequencing( playerInfo_t *pi ) { + int currentAnim; + animNumber_t raisetype; //----(SA) added + + currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; + + if ( pi->weapon != pi->currentWeapon ) { + if ( currentAnim != TORSO_DROP ) { + pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; + UI_ForceTorsoAnim( pi, TORSO_DROP ); + } + } + + if ( pi->torsoAnimationTimer > 0 ) { + return; + } + + if( currentAnim == TORSO_GESTURE ) { + UI_SetTorsoAnim( pi, TORSO_STAND ); + return; + } + + if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 || + currentAnim == TORSO_ATTACK3 || currentAnim == TORSO_ATTACK4 || + currentAnim == TORSO_ATTACK5 || currentAnim == TORSO_ATTACK5B) { + UI_SetTorsoAnim( pi, TORSO_STAND ); + return; + } + + if ( currentAnim == TORSO_DROP ) { + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); + pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; + +//----(SA) added + switch(pi->weapon) + { + case WP_MAUSER: + raisetype = TORSO_RAISE2; // (high) + break; + + case WP_GAUNTLET: + case WP_SILENCER: + case WP_LUGER: + case WP_KNIFE: + case WP_KNIFE2: + raisetype = TORSO_RAISE3; // (pistol) + break; + + case WP_ROCKET_LAUNCHER: + raisetype = TORSO_RAISE4; // (shoulder) + break; + + case WP_GRENADE_LAUNCHER: + raisetype = TORSO_RAISE5; // (throw) + break; + + default: + raisetype = TORSO_RAISE; // (low) + break; + } + + UI_ForceTorsoAnim( pi, raisetype ); + + return; + } + + if ( currentAnim == TORSO_RAISE || currentAnim == TORSO_RAISE2 || + currentAnim == TORSO_RAISE3 || currentAnim == TORSO_RAISE4 || + currentAnim == TORSO_RAISE5) { + UI_SetTorsoAnim( pi, TORSO_STAND ); + return; + } +//----(SA) end +} +*/ + +/* +=============== +UI_LegsSequencing +=============== +*/ +/* +// TTimo: unused +static void UI_LegsSequencing( playerInfo_t *pi ) { + int currentAnim; + + currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; + + if ( pi->legsAnimationTimer > 0 ) { + if ( currentAnim == LEGS_JUMP ) { + jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP ); + } + return; + } + + if ( currentAnim == LEGS_JUMP ) { + UI_ForceLegsAnim( pi, LEGS_LAND ); + pi->legsAnimationTimer = UI_TIMER_LAND; + jumpHeight = 0; + return; + } + + if ( currentAnim == LEGS_LAND ) { + UI_SetLegsAnim( pi, LEGS_IDLE ); + return; + } +} +*/ + +/* +====================== +UI_PositionEntityOnTag +====================== +*/ +static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + clipHandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + + // lerp the tag + trap_CM_LerpTag( &lerped, (const refEntity_t *)parent, (const char *)tagName, 0 ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // cast away const because of compiler problems + MatrixMultiply( lerped.axis, ( (refEntity_t*)parent )->axis, entity->axis ); + entity->backlerp = parent->backlerp; +} + + +/* +====================== +UI_PositionRotatedEntityOnTag +====================== +*/ +static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + clipHandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + vec3_t tempAxis[3]; + + // lerp the tag + trap_CM_LerpTag( &lerped, parent, tagName, 0 ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // cast away const because of compiler problems + MatrixMultiply( entity->axis, ( (refEntity_t *)parent )->axis, tempAxis ); + MatrixMultiply( lerped.axis, tempAxis, entity->axis ); +} + + +/* +=============== +UI_SetLerpFrameAnimation +=============== +*/ +/* +// TTimo: unused +static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS ) { + trap_Error( va("Bad animation number (UI_SLFA): %i", newAnimation) ); + } + + anim = &ci->animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; +} +*/ + +/* +=============== +UI_RunLerpFrame +=============== +*/ +/* +// TTimo: unused +static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + int f; + animation_t *anim; + + // see if the animation sequence is switching + if ( newAnimation != lf->animationNumber || !lf->animation ) { + UI_SetLerpFrameAnimation( ci, lf, newAnimation ); + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( dp_realtime >= lf->frameTime ) { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if ( dp_realtime < lf->animationTime ) { + lf->frameTime = lf->animationTime; // initial lerp + } else { + lf->frameTime = lf->oldFrameTime + anim->frameLerp; + } + f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + if ( f >= anim->numFrames ) { + f -= anim->numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = anim->numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = dp_realtime; + } + } + lf->frame = anim->firstFrame + f; + if ( dp_realtime > lf->frameTime ) { + lf->frameTime = dp_realtime; + } + } + + if ( lf->frameTime > dp_realtime + 200 ) { + lf->frameTime = dp_realtime; + } + + if ( lf->oldFrameTime > dp_realtime ) { + lf->oldFrameTime = dp_realtime; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} +*/ + +/* +=============== +UI_PlayerAnimation +=============== +*/ +/* +// TTimo: unused +static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) { + + // legs animation + pi->legsAnimationTimer -= uis.frametime; + if ( pi->legsAnimationTimer < 0 ) { + pi->legsAnimationTimer = 0; + } + + UI_LegsSequencing( pi ); + + if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { + UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN ); + } else { + UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim ); + } + *legsOld = pi->legs.oldFrame; + *legs = pi->legs.frame; + *legsBackLerp = pi->legs.backlerp; + + // torso animation + pi->torsoAnimationTimer -= uis.frametime; + if ( pi->torsoAnimationTimer < 0 ) { + pi->torsoAnimationTimer = 0; + } + + UI_TorsoSequencing( pi ); + + UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim ); + *torsoOld = pi->torso.oldFrame; + *torso = pi->torso.frame; + *torsoBackLerp = pi->torso.backlerp; +} +*/ + +/* +================== +UI_SwingAngles +================== +*/ +static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging ) { + float swing; + float move; + float scale; + + if ( !*swinging ) { + // see if a swing should be started + swing = AngleSubtract( *angle, destination ); + if ( swing > swingTolerance || swing < -swingTolerance ) { + *swinging = qtrue; + } + } + + if ( !*swinging ) { + return; + } + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + if ( scale < swingTolerance * 0.5 ) { + scale = 0.5; + } else if ( scale < swingTolerance ) { + scale = 1.0; + } else { + scale = 2.0; + } + + // swing towards the destination angle + if ( swing >= 0 ) { + move = uis.frametime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = uis.frametime * scale * -speed; + if ( move <= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if ( swing > clampTolerance ) { + *angle = AngleMod( destination - ( clampTolerance - 1 ) ); + } else if ( swing < -clampTolerance ) { + *angle = AngleMod( destination + ( clampTolerance - 1 ) ); + } +} + + +/* +====================== +UI_MovedirAdjustment +====================== +*/ +static float UI_MovedirAdjustment( playerInfo_t *pi ) { + vec3_t relativeAngles; + vec3_t moveVector; + + VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles ); + AngleVectors( relativeAngles, moveVector, NULL, NULL ); + if ( Q_fabs( moveVector[0] ) < 0.01 ) { + moveVector[0] = 0.0; + } + if ( Q_fabs( moveVector[1] ) < 0.01 ) { + moveVector[1] = 0.0; + } + + if ( moveVector[1] == 0 && moveVector[0] > 0 ) { + return 0; + } + if ( moveVector[1] < 0 && moveVector[0] > 0 ) { + return 22; + } + if ( moveVector[1] < 0 && moveVector[0] == 0 ) { + return 45; + } + if ( moveVector[1] < 0 && moveVector[0] < 0 ) { + return -22; + } + if ( moveVector[1] == 0 && moveVector[0] < 0 ) { + return 0; + } + if ( moveVector[1] > 0 && moveVector[0] < 0 ) { + return 22; + } + if ( moveVector[1] > 0 && moveVector[0] == 0 ) { + return -45; + } + + return -22; +} + + +/* +=============== +UI_PlayerAngles +=============== +*/ +static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { + vec3_t legsAngles, torsoAngles, headAngles; + float dest; + float adjust; + + VectorCopy( pi->viewAngles, headAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + + // --------- yaw ------------- + + // allow yaw to drift a bit + if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE + || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { + // if not standing still, always point all in the same direction + pi->torso.yawing = qtrue; // always center + pi->torso.pitching = qtrue; // always center + pi->legs.yawing = qtrue; // always center + } + + // adjust legs for movement dir + adjust = UI_MovedirAdjustment( pi ); + legsAngles[YAW] = headAngles[YAW] + adjust; + torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust; + + + // torso + UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing ); + UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing ); + + torsoAngles[YAW] = pi->torso.yawAngle; + legsAngles[YAW] = pi->legs.yawAngle; + + // --------- pitch ------------- + + // only show a fraction of the pitch angle in the torso + if ( headAngles[PITCH] > 180 ) { + dest = ( -360 + headAngles[PITCH] ) * 0.75; + } else { + dest = headAngles[PITCH] * 0.75; + } + UI_SwingAngles( dest, 15, 30, 0.1, &pi->torso.pitchAngle, &pi->torso.pitching ); + torsoAngles[PITCH] = pi->torso.pitchAngle; + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + + AnglesSubtract( legsAngles, pi->moveAngles, legsAngles ); // NERVE - SMF + + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( torsoAngles, torso ); + AnglesToAxis( headAngles, head ); +} + + +/* +=============== +UI_PlayerFloatSprite +=============== +*/ +static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) { + refEntity_t ent; + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( origin, ent.origin ); + ent.origin[2] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = 0; + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +====================== +UI_MachinegunSpinAngle +====================== +*/ +float UI_MachinegunSpinAngle( playerInfo_t *pi ) { + int delta; + float angle; + float speed; + int torsoAnim; + + delta = dp_realtime - pi->barrelTime; + if ( pi->barrelSpinning ) { + angle = pi->barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = pi->barrelAngle + delta * speed; + } + + torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; + if ( torsoAnim == TORSO_ATTACK2 ) { + torsoAnim = TORSO_ATTACK; + } + if ( pi->barrelSpinning == !( torsoAnim == TORSO_ATTACK ) ) { + pi->barrelTime = dp_realtime; + pi->barrelAngle = AngleMod( angle ); + pi->barrelSpinning = !!( torsoAnim == TORSO_ATTACK ); + } + + return angle; +} + +// NERVE - SMF +/* +=============== +UI_GetAnimation +=============== +*/ +static int UI_GetAnimation( playerInfo_t *pi, const char *name ) { + int i; + + for ( i = 0; i < pi->numAnimations; i++ ) { + if ( !Q_stricmp( pi->animations[i].name, name ) ) { + return pi->animations[i].firstFrame; + } + } + + return 0; +} + +/* +=============== +UI_DrawPlayer +=============== +*/ +void WM_getWeaponAnim( const char **torso_anim, const char **legs_anim ); // NERVE - SMF + +void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) { + refdef_t refdef; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + refEntity_t gun; + refEntity_t barrel; + refEntity_t backpack; + refEntity_t helmet; +// refEntity_t barrel; + refEntity_t flash; + vec3_t origin; + int renderfx; + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + float len; + float xx; + vec4_t hcolor = { 1, 0, 0, 0.5 }; + const char *torso_anim = NULL, *legs_anim = NULL; + + if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) { + return; + } + + dp_realtime = time; + + if ( pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer ) { + pi->weapon = pi->pendingWeapon; + pi->lastWeapon = pi->pendingWeapon; + pi->pendingWeapon = -1; + pi->weaponTimer = 0; + if ( pi->currentWeapon != pi->weapon ) { + trap_S_StartLocalSound( trap_S_RegisterSound( "sound/weapons/change.wav" ), CHAN_LOCAL ); + } + } + + UI_AdjustFrom640( &x, &y, &w, &h ); + + y -= jumpHeight; + + memset( &refdef, 0, sizeof( refdef ) ); + memset( &legs, 0, sizeof( legs ) ); + memset( &torso, 0, sizeof( torso ) ); + memset( &head, 0, sizeof( head ) ); + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.fov_x = (int)( (float)refdef.width / 640.0f * 90.0f ); + xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); + refdef.fov_y = atan2( refdef.height, xx ); + refdef.fov_y *= ( 360 / M_PI ); + + // calculate distance so the player nearly fills the box + len = 1.01 * ( maxs[2] - mins[2] ); // NERVE - SMF - changed from 0.7 + origin[0] = len / tan( DEG2RAD( refdef.fov_x ) * 0.5 ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + + refdef.time = dp_realtime; + + trap_R_SetColor( hcolor ); + trap_R_ClearScene(); + trap_R_SetColor( NULL ); + + // get the rotation information + UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis ); + + // get the animation state (after rotation, to allow feet shuffle) +// UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp, +// &torso.oldframe, &torso.frame, &torso.backlerp ); + + renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; + + // + // add the body + // + legs.hModel = pi->legsModel; + legs.customSkin = pi->legsSkin; + legs.renderfx = renderfx; + + VectorCopy( origin, legs.origin ); + VectorCopy( origin, legs.lightingOrigin ); + VectorCopy( legs.origin, legs.oldorigin ); + + WM_getWeaponAnim( &torso_anim, &legs_anim ); + + if ( torso_anim ) { + legs.torsoFrame = UI_GetAnimation( pi, torso_anim ); + legs.oldTorsoFrame = UI_GetAnimation( pi, torso_anim ); + } + legs.torsoBacklerp = 0; //torso.backlerp; + + if ( legs_anim ) { + legs.frame = UI_GetAnimation( pi, legs_anim ); + legs.oldframe = UI_GetAnimation( pi, legs_anim ); + } + legs.backlerp = 0; + + memcpy( legs.torsoAxis, torso.axis, sizeof( torso.axis ) ); + torso = legs; + + trap_R_AddRefEntityToScene( &torso ); + + // + // add the head + // + head.hModel = pi->headModel; + if ( !head.hModel ) { + return; + } + head.customSkin = pi->headSkin; + + VectorCopy( origin, head.lightingOrigin ); + + UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head" ); + + head.renderfx = renderfx; + + trap_R_AddRefEntityToScene( &head ); + + // + // add the gun + // + if ( pi->currentWeapon != WP_NONE ) { + memset( &gun, 0, sizeof( gun ) ); + gun.hModel = pi->weaponModel; + VectorCopy( origin, gun.lightingOrigin ); + UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon" ); + gun.renderfx = renderfx; + trap_R_AddRefEntityToScene( &gun ); + } + + // + // add the gun barrel + // + if ( pi->currentWeapon != WP_NONE && pi->barrelModel ) { + memset( &barrel, 0, sizeof( barrel ) ); + barrel.hModel = pi->barrelModel; + VectorCopy( origin, barrel.lightingOrigin ); + UI_PositionEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel" ); + barrel.renderfx = renderfx; + trap_R_AddRefEntityToScene( &barrel ); + } + + // + // add muzzle flash + // + if ( dp_realtime <= pi->muzzleFlashTime ) { + if ( pi->flashModel ) { + memset( &flash, 0, sizeof( flash ) ); + flash.hModel = pi->flashModel; + VectorCopy( origin, flash.lightingOrigin ); + UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash" ); + flash.renderfx = renderfx; + trap_R_AddRefEntityToScene( &flash ); + } + + // make a dlight for the flash + if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) { + trap_R_AddLightToScene( flash.origin, 200 + ( rand() & 31 ), pi->flashDlightColor[0], + pi->flashDlightColor[1], pi->flashDlightColor[2], 0 ); + } + } + + // + // add the backpack + // + if ( pi->backpackModel ) { + memset( &backpack, 0, sizeof( backpack ) ); + backpack.hModel = pi->backpackModel; + VectorCopy( origin, backpack.lightingOrigin ); + UI_PositionEntityOnTag( &backpack, &torso, pi->torsoModel, "tag_back" ); + backpack.renderfx = renderfx; + trap_R_AddRefEntityToScene( &backpack ); + } + + // + // add the helmet + // + if ( pi->helmetModel ) { + memset( &helmet, 0, sizeof( helmet ) ); + helmet.hModel = pi->helmetModel; + VectorCopy( origin, helmet.lightingOrigin ); + UI_PositionEntityOnTag( &helmet, &head, pi->headModel, "tag_mouth" ); + helmet.renderfx = renderfx; + trap_R_AddRefEntityToScene( &helmet ); + } + + // + // add the chat icon + // + if ( pi->chat ) { + UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) ); + } + + // + // add an accent light + // + origin[0] -= 100; // + = behind, - = in front + origin[1] += 100; // + = left, - = right + origin[2] += 100; // + = above, - = below + trap_R_AddLightToScene( origin, 1000, 1.0, 1.0, 1.0, 0 ); + + origin[0] -= 100; + origin[1] -= 100; + origin[2] -= 100; + trap_R_AddLightToScene( origin, 1000, 1.0, 1.0, 1.0, 0 ); + + trap_R_RenderScene( &refdef ); +} + + +/* +========================== +UI_RegisterClientSkin +========================== +*/ +static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName ) { + char filename[MAX_QPATH]; + +// Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/body_%s.skin", modelName, skinName ); // NERVE - SMF - make this work with wolf + pi->legsSkin = trap_R_RegisterSkin( filename ); + +// Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/body_%s.skin", modelName, skinName ); // NERVE - SMF - make this work with wolf + pi->torsoSkin = trap_R_RegisterSkin( filename ); + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head_%s.skin", modelName, skinName ); + pi->headSkin = trap_R_RegisterSkin( filename ); + + if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) { + return qfalse; + } + + return qtrue; +} + +/* +================ +return a hash value for the given string +================ +*/ +static long BG_StringHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + hash += (long)( letter ) * ( i + 119 ); + i++; + } + if ( hash == -1 ) { + hash = 0; // never return -1 + } + return hash; +} + +/* +============ +AnimParseAnimConfig + + returns qfalse if error, qtrue otherwise +============ +*/ +static qboolean AnimParseAnimConfig( playerInfo_t *animModelInfo, const char *filename, const char *input ) { + char *text_p, *token; + animation_t *animations; + headAnimation_t *headAnims; + int i, fps, skip = -1; + +// if (!weaponStringsInited) { +// BG_InitWeaponStrings(); +// } + +// globalFilename = (char *)filename; + + animations = animModelInfo->animations; + animModelInfo->numAnimations = 0; +// headAnims = animModelInfo->headAnims; + + text_p = (char *)input; + COM_BeginParseSession( "AnimParseAnimConfig" ); + + animModelInfo->footsteps = FOOTSTEP_NORMAL; + VectorClear( animModelInfo->headOffset ); + animModelInfo->gender = GENDER_MALE; + animModelInfo->isSkeletal = qfalse; + animModelInfo->version = 0; + + // read optional parameters + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { + animModelInfo->footsteps = FOOTSTEP_NORMAL; + } else if ( !Q_stricmp( token, "boot" ) ) { + animModelInfo->footsteps = FOOTSTEP_BOOT; + } else if ( !Q_stricmp( token, "flesh" ) ) { + animModelInfo->footsteps = FOOTSTEP_FLESH; + } else if ( !Q_stricmp( token, "mech" ) ) { + animModelInfo->footsteps = FOOTSTEP_MECH; + } else if ( !Q_stricmp( token, "energy" ) ) { + animModelInfo->footsteps = FOOTSTEP_ENERGY; + } else { +// BG_AnimParseError( "Bad footsteps parm '%s'\n", token ); + } + continue; + } else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animModelInfo->headOffset[i] = atof( token ); + } + continue; + } else if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + animModelInfo->gender = GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + animModelInfo->gender = GENDER_NEUTER; + } else { + animModelInfo->gender = GENDER_MALE; + } + continue; + } else if ( !Q_stricmp( token, "version" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animModelInfo->version = atoi( token ); + continue; + } else if ( !Q_stricmp( token, "skeletal" ) ) { + animModelInfo->isSkeletal = qtrue; + continue; + } + + if ( animModelInfo->version < 2 ) { + // if it is a number, start parsing animations + if ( token[0] >= '0' && token[0] <= '9' ) { + text_p -= strlen( token ); // unget the token + break; + } + } + + // STARTANIMS marks the start of the animations + if ( !Q_stricmp( token, "STARTANIMS" ) ) { + break; + } +// BG_AnimParseError( "unknown token '%s'", token ); + } + + // read information for each frame + for ( i = 0 ; ( animModelInfo->version > 1 ) || ( i < MAX_ANIMATIONS ) ; i++ ) { + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + + if ( animModelInfo->version > 1 ) { // includes animation names at start of each line + + if ( !Q_stricmp( token, "ENDANIMS" ) ) { // end of animations + break; + } + + Q_strncpyz( animations[i].name, token, sizeof( animations[i].name ) ); + // convert to all lower case + Q_strlwr( animations[i].name ); + // + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { +// BG_AnimParseError( "end of file without ENDANIMS" ); + } + } else { + // just set it to the equivalent animStrings[] + Q_strncpyz( animations[i].name, animStrings[i], sizeof( animations[i].name ) ); + // convert to all lower case + Q_strlwr( animations[i].name ); + } + + animations[i].firstFrame = atoi( token ); + + if ( !animModelInfo->isSkeletal ) { // skeletal models dont require adjustment + + // leg only frames are adjusted to not count the upper body only frames + if ( i == LEGS_WALKCR ) { + skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; + } + if ( i >= LEGS_WALKCR ) { + animations[i].firstFrame -= skip; + } + + } + + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { +// BG_AnimParseError( "end of file without ENDANIMS" ); + } + animations[i].numFrames = atoi( token ); + + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { +// BG_AnimParseError( "end of file without ENDANIMS: line %i" ); + } + animations[i].loopFrames = atoi( token ); + + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { +// BG_AnimParseError( "end of file without ENDANIMS: line %i" ); + } + fps = atof( token ); + if ( fps == 0 ) { + fps = 1; + } + animations[i].frameLerp = 1000 / fps; + animations[i].initialLerp = 1000 / fps; + + // movespeed + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { +// BG_AnimParseError( "end of file without ENDANIMS" ); + } + animations[i].moveSpeed = atoi( token ); + + // animation blending + token = COM_ParseExt( &text_p, qfalse ); // must be on same line + if ( !token ) { + animations[i].animBlend = 0; + } else { + animations[i].animBlend = atoi( token ); + } + + // calculate the duration + animations[i].duration = animations[i].initialLerp + + animations[i].frameLerp * animations[i].numFrames + + animations[i].animBlend; + + // get the nameHash + animations[i].nameHash = BG_StringHashValue( animations[i].name ); + + if ( !Q_strncmp( animations[i].name, "climb", 5 ) ) { + animations[i].flags |= ANIMFL_LADDERANIM; + } + if ( strstr( animations[i].name, "firing" ) ) { + animations[i].flags |= ANIMFL_FIRINGANIM; + animations[i].initialLerp = 40; + } + + } + + animModelInfo->numAnimations = i; + + if ( animModelInfo->version < 2 && i != MAX_ANIMATIONS ) { +// BG_AnimParseError( "Incorrect number of animations" ); + return qfalse; + } + + return qtrue; // NERVE - SMF - blah + + // check for head anims + token = COM_Parse( &text_p ); + if ( token && token[0] ) { + if ( animModelInfo->version < 2 || !Q_stricmp( token, "HEADFRAMES" ) ) { + + // read information for each head frame + for ( i = 0 ; i < MAX_HEAD_ANIMS ; i++ ) { + + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) { + break; + } + + if ( animModelInfo->version > 1 ) { // includes animation names at start of each line + // just throw this information away, not required for head + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + break; + } + } + + if ( !i ) { + skip = atoi( token ); + } + + headAnims[i].firstFrame = atoi( token ); + // modify according to last frame of the main animations, since the head is totally seperate + headAnims[i].firstFrame -= animations[MAX_ANIMATIONS - 1].firstFrame + animations[MAX_ANIMATIONS - 1].numFrames + skip; + + token = COM_ParseExt( &text_p, qfalse ); + if ( !token || !token[0] ) { + break; + } + headAnims[i].numFrames = atoi( token ); + + // skip the movespeed + token = COM_ParseExt( &text_p, qfalse ); + } + +// animModelInfo->numHeadAnims = i; + + if ( i != MAX_HEAD_ANIMS ) { +// BG_AnimParseError( "Incorrect number of head frames" ); + return qfalse; + } + + } + } + + return qtrue; +} + +/* +====================== +UI_ParseAnimationFile +====================== +*/ +static qboolean UI_ParseAnimationFile( const char *filename, playerInfo_t *pi ) { + char *text_p, *prev; + int len; + int i; + char *token; + float fps; + int skip; + char text[20000]; + fileHandle_t f; + + token = NULL; + i = 0; + fps = 0; + prev = 0; + + memset( pi->animations, 0, sizeof( animation_t ) * MAX_ANIMATIONS ); + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + if ( len >= ( sizeof( text ) - 1 ) ) { + Com_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + skip = 0; // quite the compiler warning + + // NERVE - SMF - new!!!! + AnimParseAnimConfig( pi, filename, text ); + return qtrue; + + // -NERVE - SMF - This does not work with wolf's new animation system +/* + // read optional parameters + while ( 1 ) { + prev = text_p; // so we can unget + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + continue; + } else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + } + continue; + } else if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + continue; + } + + // if it is a number, start parsing animations + if ( token[0] >= '0' && token[0] <= '9' ) { + text_p = prev; // unget the token + break; + } + + Com_Printf( "unknown token '%s' is %s\n", token, filename ); + } + + // read information for each frame + for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animations[i].firstFrame = atoi( token ); + // leg only frames are adjusted to not count the upper body only frames + if ( i == LEGS_WALKCR ) { + skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; + } + if ( i >= LEGS_WALKCR ) { + animations[i].firstFrame -= skip; + } + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animations[i].numFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animations[i].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + fps = atof( token ); + if ( fps == 0 ) { + fps = 1; + } + animations[i].frameLerp = 1000 / fps; + animations[i].initialLerp = 1000 / fps; + } + + if ( i != MAX_ANIMATIONS ) { + Com_Printf( "Error parsing animation file: %s", filename ); + return qfalse; + } + + return qtrue; +*/ +} + + +/* +========================== +UI_RegisterClientModelname +========================== +*/ +int WM_getWeaponIndex(); + +qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName ) { + char modelName[MAX_QPATH]; + char skinName[MAX_QPATH]; + char filename[MAX_QPATH]; + char *slash; + const char* backpack = NULL; + const char* helmet = NULL; + + pi->torsoModel = 0; + pi->headModel = 0; + + if ( !modelSkinName[0] ) { + return qfalse; + } + + Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) ); + + slash = strchr( modelName, '/' ); + if ( !slash ) { + // modelName did not include a skin name + Q_strncpyz( skinName, "default", sizeof( skinName ) ); + } else { + Q_strncpyz( skinName, slash + 1, sizeof( skinName ) ); + // truncate modelName + *slash = 0; + } + + // NERVE - SMF - set weapon + pi->weapon = WM_getWeaponIndex(); + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); + + // NERVE - SMF - determine skin + { + const char *team; + const char *playerClass; + int var, teamval; + + // DHM - Nerve :: Don't rely on cvar for team, use modelname instead + //teamval = trap_Cvar_VariableValue( "mp_team" ); + if ( !strcmp( modelSkinName, "multi" ) ) { + teamval = 1; + team = "blue"; + } else { + teamval = 0; + team = "red"; + } + + var = trap_Cvar_VariableValue( "mp_playerType" ); + + if ( var == 0 ) { + playerClass = "soldier"; + + if ( teamval == 1 ) { + backpack = "acc/backpack/backpack_sol.md3"; + helmet = "acc/helmet_american/sol.md3"; + } else { + backpack = "acc/backpack/backpack_german_sol.md3"; + helmet = "acc/helmet_german/helmet_sol.md3"; + } + } else if ( var == 1 ) { + playerClass = "medic"; + + if ( teamval == 1 ) { + backpack = "acc/backpack/backpack_med.md3"; + helmet = "acc/helmet_american/med.md3"; + } else { + backpack = "acc/backpack/backpack_german_med.md3"; + helmet = "acc/helmet_german/helmet_med.md3"; + } + } else if ( var == 2 ) { + playerClass = "engineer"; + + if ( teamval == 1 ) { + backpack = "acc/backpack/backpack_eng.md3"; + helmet = "acc/helmet_american/eng.md3"; + } else { + backpack = "acc/backpack/backpack_german_eng.md3"; + helmet = "acc/helmet_german/helmet_eng.md3"; + } + } else { + playerClass = "lieutenant"; + + if ( teamval == 1 ) { + backpack = "acc/backpack/backpack_lieu.md3"; + helmet = "acc/helmet_american/lieu.md3"; + } else { + backpack = "acc/backpack/backpack_german_lieu.md3"; + helmet = "acc/helmet_german/helmet_leiu.md3"; + } + } + + strcpy( skinName, va( "%s%s1", team, playerClass ) ); + } + // -NERVE - SMF + +// Q_strncpyz( skinName, "bluesoldier1", sizeof( skinName ) ); // NERVE - SMF - make this work with wolf - TESTING!!! +// } +// else { +// Q_strncpyz( skinName, "redsoldier1", sizeof( skinName ) ); // NERVE - SMF - make this work with wolf - TESTING!!! +// } + + // load cmodels before models so filecache works + +// Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/body.mds", modelName ); // NERVE - SMF - make this work with wolf + pi->legsModel = trap_R_RegisterModel( filename ); + if ( !pi->legsModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + +// Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/body.mds", modelName ); // NERVE - SMF - make this work with wolf + pi->torsoModel = trap_R_RegisterModel( filename ); + if ( !pi->torsoModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", modelName ); + pi->headModel = trap_R_RegisterModel( filename ); + if ( !pi->headModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + // NERVE - SMF - load backpack and helmet + if ( backpack ) { + pi->backpackModel = trap_R_RegisterModel( va( "models/players/%s/%s", modelName, backpack ) ); + } + + if ( helmet ) { + pi->helmetModel = trap_R_RegisterModel( va( "models/players/%s/%s", modelName, helmet ) ); + } + + // if any skins failed to load, fall back to default + if ( !UI_RegisterClientSkin( pi, modelName, skinName ) ) { + if ( !UI_RegisterClientSkin( pi, modelName, "default" ) ) { + Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); + return qfalse; + } + } + + // load the animations +//----(SA) changing name of config file to avoid backwards or alternate compatibility confustion +// Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); + Com_sprintf( filename, sizeof( filename ), "models/players/%s/wolfanim.cfg", modelName ); +//----(SA) end + if ( !UI_ParseAnimationFile( filename, pi ) ) { // NERVE - SMF - make this work with wolf + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +UI_PlayerInfo_SetModel +=============== +*/ +void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model ) { + memset( pi, 0, sizeof( *pi ) ); + UI_RegisterClientModelname( pi, model ); +// pi->weapon = WP_MACHINEGUN; +// pi->weapon = WP_MP40; + pi->currentWeapon = pi->weapon; + pi->lastWeapon = pi->weapon; + pi->pendingWeapon = -1; + pi->weaponTimer = 0; + pi->chat = qfalse; + pi->newModel = qtrue; + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); +} + + +/* +=============== +UI_PlayerInfo_SetInfo +=============== +*/ +void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) { + int currentAnim; + weapon_t weaponNum; + + pi->chat = chat; + + // view angles + VectorCopy( viewAngles, pi->viewAngles ); + + // move angles + VectorCopy( moveAngles, pi->moveAngles ); + + if ( pi->newModel ) { + pi->newModel = qfalse; + + jumpHeight = 0; + pi->pendingLegsAnim = 0; + UI_ForceLegsAnim( pi, legsAnim ); + pi->legs.yawAngle = viewAngles[YAW]; + pi->legs.yawing = qfalse; + + pi->pendingTorsoAnim = 0; + UI_ForceTorsoAnim( pi, torsoAnim ); + pi->torso.yawAngle = viewAngles[YAW]; + pi->torso.yawing = qfalse; + + if ( weaponNumber != -1 ) { + pi->weapon = weaponNumber; + pi->currentWeapon = weaponNumber; + pi->lastWeapon = weaponNumber; + pi->pendingWeapon = -1; + pi->weaponTimer = 0; + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); + } + + return; + } + + // weapon + if ( weaponNumber == -1 ) { + pi->pendingWeapon = -1; + pi->weaponTimer = 0; + } else if ( weaponNumber != WP_NONE ) { + pi->pendingWeapon = weaponNumber; + pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY; + } + weaponNum = pi->lastWeapon; + pi->weapon = weaponNum; + + if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) { + torsoAnim = legsAnim = BOTH_DEATH1; + pi->weapon = pi->currentWeapon = WP_NONE; + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); + + jumpHeight = 0; + pi->pendingLegsAnim = 0; + UI_ForceLegsAnim( pi, legsAnim ); + + pi->pendingTorsoAnim = 0; + UI_ForceTorsoAnim( pi, torsoAnim ); + + return; + } + + // leg animation + currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; + if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) { + pi->pendingLegsAnim = legsAnim; + } else if ( legsAnim != currentAnim ) { + jumpHeight = 0; + pi->pendingLegsAnim = 0; + UI_ForceLegsAnim( pi, legsAnim ); + } + + // torso animation + if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) { + if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) { + torsoAnim = TORSO_STAND2; + } else { + torsoAnim = TORSO_STAND; + } + } + + if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) { + if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) { + torsoAnim = TORSO_ATTACK2; + } else { + torsoAnim = TORSO_ATTACK; + } + pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH; + //FIXME play firing sound here + } + + currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; + + if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) { + pi->pendingTorsoAnim = torsoAnim; + } else if ( ( currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK ) && ( torsoAnim != currentAnim ) ) { + pi->pendingTorsoAnim = torsoAnim; + } else if ( torsoAnim != currentAnim ) { + pi->pendingTorsoAnim = 0; + UI_ForceTorsoAnim( pi, torsoAnim ); + } +} diff --git a/src/ui/ui_public.h b/src/ui/ui_public.h new file mode 100644 index 0000000..8bad6ad --- /dev/null +++ b/src/ui/ui_public.h @@ -0,0 +1,230 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __UI_PUBLIC_H__ +#define __UI_PUBLIC_H__ + +#define UI_API_VERSION 4 + +typedef struct { + connstate_t connState; + int connectPacketCount; + int clientNum; + char servername[MAX_STRING_CHARS]; + char updateInfoString[MAX_STRING_CHARS]; + char messageString[MAX_STRING_CHARS]; +} uiClientState_t; + +typedef enum { + UI_ERROR, + UI_PRINT, + UI_MILLISECONDS, + UI_CVAR_SET, + UI_CVAR_VARIABLEVALUE, + UI_CVAR_VARIABLESTRINGBUFFER, + UI_CVAR_SETVALUE, + UI_CVAR_RESET, + UI_CVAR_CREATE, + UI_CVAR_INFOSTRINGBUFFER, + UI_ARGC, + UI_ARGV, + UI_CMD_EXECUTETEXT, + UI_FS_FOPENFILE, + UI_FS_READ, + UI_FS_WRITE, + UI_FS_FCLOSEFILE, + UI_FS_GETFILELIST, + UI_FS_DELETEFILE, + UI_R_REGISTERMODEL, + UI_R_REGISTERSKIN, + UI_R_REGISTERSHADERNOMIP, + UI_R_CLEARSCENE, + UI_R_ADDREFENTITYTOSCENE, + UI_R_ADDPOLYTOSCENE, + UI_R_ADDPOLYSTOSCENE, + // JOSEPH 12-6-99 + UI_R_ADDLIGHTTOSCENE, + // END JOSEPH + //----(SA) + UI_R_ADDCORONATOSCENE, + //----(SA) + UI_R_RENDERSCENE, + UI_R_SETCOLOR, + UI_R_DRAWSTRETCHPIC, + UI_UPDATESCREEN, // 30 + UI_CM_LERPTAG, + UI_CM_LOADMODEL, + UI_S_REGISTERSOUND, + UI_S_STARTLOCALSOUND, + UI_KEY_KEYNUMTOSTRINGBUF, + UI_KEY_GETBINDINGBUF, + UI_KEY_SETBINDING, + UI_KEY_ISDOWN, + UI_KEY_GETOVERSTRIKEMODE, + UI_KEY_SETOVERSTRIKEMODE, + UI_KEY_CLEARSTATES, + UI_KEY_GETCATCHER, + UI_KEY_SETCATCHER, + UI_GETCLIPBOARDDATA, + UI_GETGLCONFIG, + UI_GETCLIENTSTATE, + UI_GETCONFIGSTRING, + UI_LAN_GETLOCALSERVERCOUNT, + UI_LAN_GETLOCALSERVERADDRESSSTRING, + UI_LAN_GETGLOBALSERVERCOUNT, // 50 + UI_LAN_GETGLOBALSERVERADDRESSSTRING, + UI_LAN_GETPINGQUEUECOUNT, + UI_LAN_CLEARPING, + UI_LAN_GETPING, + UI_LAN_GETPINGINFO, + UI_CVAR_REGISTER, + UI_CVAR_UPDATE, + UI_MEMORY_REMAINING, + + UI_GET_CDKEY, + UI_SET_CDKEY, + UI_R_REGISTERFONT, + UI_R_MODELBOUNDS, + UI_PC_ADD_GLOBAL_DEFINE, + UI_PC_LOAD_SOURCE, + UI_PC_FREE_SOURCE, + UI_PC_READ_TOKEN, + UI_PC_SOURCE_FILE_AND_LINE, + UI_S_STOPBACKGROUNDTRACK, + UI_S_STARTBACKGROUNDTRACK, + UI_REAL_TIME, + UI_LAN_GETSERVERCOUNT, + UI_LAN_GETSERVERADDRESSSTRING, + UI_LAN_GETSERVERINFO, + UI_LAN_MARKSERVERVISIBLE, + UI_LAN_UPDATEVISIBLEPINGS, + UI_LAN_RESETPINGS, + UI_LAN_LOADCACHEDSERVERS, + UI_LAN_SAVECACHEDSERVERS, + UI_LAN_ADDSERVER, + UI_LAN_REMOVESERVER, + UI_CIN_PLAYCINEMATIC, + UI_CIN_STOPCINEMATIC, + UI_CIN_RUNCINEMATIC, + UI_CIN_DRAWCINEMATIC, + UI_CIN_SETEXTENTS, + UI_R_REMAP_SHADER, + UI_VERIFY_CDKEY, + UI_LAN_SERVERSTATUS, + UI_LAN_GETSERVERPING, + UI_LAN_SERVERISVISIBLE, + UI_LAN_COMPARESERVERS, + UI_CL_GETLIMBOSTRING, // NERVE - SMF + UI_SET_PBCLSTATUS, // DHM - Nerve + UI_CHECKAUTOUPDATE, // DHM - Nerve + UI_GET_AUTOUPDATE, // DHM - Nerve + UI_CL_TRANSLATE_STRING, + UI_OPENURL, + UI_SET_PBSVSTATUS, // TTimo + + UI_MEMSET = 100, + UI_MEMCPY, + UI_STRNCPY, + UI_SIN, + UI_COS, + UI_ATAN2, + UI_SQRT, + UI_FLOOR, + UI_CEIL + +} uiImport_t; + +typedef enum { + UIMENU_NONE, + UIMENU_MAIN, + UIMENU_INGAME, + UIMENU_NEED_CD, + UIMENU_BAD_CD_KEY, + UIMENU_TEAM, + UIMENU_POSTGAME, + UIMENU_NOTEBOOK, + UIMENU_CLIPBOARD, + UIMENU_HELP, + UIMENU_BOOK1, //----(SA) added + UIMENU_BOOK2, //----(SA) added + UIMENU_BOOK3, //----(SA) added + UIMENU_WM_PICKTEAM, // NERVE - SMF + UIMENU_WM_PICKPLAYER, // NERVE - SMF + UIMENU_WM_QUICKMESSAGE, // NERVE - SMF + UIMENU_WM_QUICKMESSAGEALT, // NERVE - SMF + UIMENU_WM_LIMBO, // NERVE - SMF + UIMENU_WM_AUTOUPDATE // NERVE - DHM +} uiMenuCommand_t; + +#define SORT_HOST 0 +#define SORT_MAP 1 +#define SORT_CLIENTS 2 +#define SORT_GAME 3 +#define SORT_PING 4 +#define SORT_PUNKBUSTER 5 + +typedef enum { + UI_GETAPIVERSION = 0, // system reserved + + UI_INIT, +// void UI_Init( void ); + + UI_SHUTDOWN, +// void UI_Shutdown( void ); + + UI_KEY_EVENT, +// void UI_KeyEvent( int key ); + + UI_MOUSE_EVENT, +// void UI_MouseEvent( int dx, int dy ); + + UI_REFRESH, +// void UI_Refresh( int time ); + + UI_IS_FULLSCREEN, +// qboolean UI_IsFullscreen( void ); + + UI_SET_ACTIVE_MENU, +// void UI_SetActiveMenu( uiMenuCommand_t menu ); + + UI_GET_ACTIVE_MENU, +// void UI_GetActiveMenu( void ); + + UI_CONSOLE_COMMAND, +// qboolean UI_ConsoleCommand( void ); + + UI_DRAW_CONNECT_SCREEN, +// void UI_DrawConnectScreen( qboolean overlay ); + UI_HASUNIQUECDKEY, +// if !overlay, the background will be drawn, otherwise it will be +// overlayed over whatever the cgame has drawn. +// a GetClientState syscall will be made to get the current strings + UI_CHECKEXECKEY // NERVE - SMF +} uiExport_t; + +#endif diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c new file mode 100644 index 0000000..330a2b3 --- /dev/null +++ b/src/ui/ui_shared.c @@ -0,0 +1,6395 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// +// string allocation/managment + +#include "ui_shared.h" + +#define SCROLL_TIME_START 500 +#define SCROLL_TIME_ADJUST 150 +#define SCROLL_TIME_ADJUSTOFFSET 40 +#define SCROLL_TIME_FLOOR 20 + +typedef struct scrollInfo_s { + int nextScrollTime; + int nextAdjustTime; + int adjustValue; + int scrollKey; + float xStart; + float yStart; + itemDef_t *item; + qboolean scrollDir; +} scrollInfo_t; + +static scrollInfo_t scrollInfo; + +static void ( *captureFunc )( void *p ) = NULL; +static void *captureData = NULL; +static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any ) + +displayContextDef_t *DC = NULL; + +qboolean g_waitingForKey = qfalse; +qboolean g_editingField = qfalse; + +static itemDef_t *g_bindItem = NULL; +itemDef_t *g_editItem = NULL; + +menuDef_t Menus[MAX_MENUS]; // defined menus +int menuCount = 0; // how many + +// TTimo +// a stack for modal menus only, stores the menus to come back to +// (an item can be NULL, goes back to main menu / no action required) +menuDef_t *modalMenuStack[MAX_MODAL_MENUS]; +int modalMenuCount = 0; + +static qboolean debugMode = qfalse; + +#define DOUBLE_CLICK_DELAY 300 +static int lastListBoxClickTime = 0; + +void Item_RunScript( itemDef_t *item, const char *s ); +void Item_SetupKeywordHash( void ); +void Menu_SetupKeywordHash( void ); +int BindingIDFromName( const char *name ); +qboolean Item_Bind_HandleKey( itemDef_t *item, int key, qboolean down ); +itemDef_t *Menu_SetPrevCursorItem( menuDef_t *menu ); +itemDef_t *Menu_SetNextCursorItem( menuDef_t *menu ); +static qboolean Menu_OverActiveItem( menuDef_t *menu, float x, float y ); + +#ifdef CGAME +#define MEM_POOL_SIZE 128 * 1024 +#else +#define MEM_POOL_SIZE 1024 * 1024 +#endif + +static char memoryPool[MEM_POOL_SIZE]; +static int allocPoint, outOfMemory; + + +/* +=============== +UI_Alloc +=============== +*/ +void *UI_Alloc( int size ) { + char *p; + + if ( allocPoint + size > MEM_POOL_SIZE ) { + outOfMemory = qtrue; + if ( DC->Print ) { + DC->Print( "UI_Alloc: Failure. Out of memory!\n" ); + } + //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n"); + return NULL; + } + + p = &memoryPool[allocPoint]; + + allocPoint += ( size + 15 ) & ~15; + + return p; +} + +/* +=============== +UI_InitMemory +=============== +*/ +void UI_InitMemory( void ) { + allocPoint = 0; + outOfMemory = qfalse; +} + +qboolean UI_OutOfMemory() { + return outOfMemory; +} + + + + + +#define HASH_TABLE_SIZE 2048 +/* +================ +return a hash value for the string +================ +*/ +static long hashForString( const char *str ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( str[i] != '\0' ) { + letter = tolower( str[i] ); + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( HASH_TABLE_SIZE - 1 ); + return hash; +} + +typedef struct stringDef_s { + struct stringDef_s *next; + const char *str; +} stringDef_t; + +static int strPoolIndex = 0; +static char strPool[STRING_POOL_SIZE]; + +static int strHandleCount = 0; +static stringDef_t *strHandle[HASH_TABLE_SIZE]; + + +const char *String_Alloc( const char *p ) { + int len; + long hash; + stringDef_t *str, *last; + static const char *staticNULL = ""; + + if ( p == NULL ) { + return NULL; + } + + if ( *p == 0 ) { + return staticNULL; + } + + hash = hashForString( p ); + + str = strHandle[hash]; + while ( str ) { + if ( strcmp( p, str->str ) == 0 ) { + return str->str; + } + str = str->next; + } + + len = strlen( p ); + if ( len + strPoolIndex + 1 < STRING_POOL_SIZE ) { + int ph = strPoolIndex; + strcpy( &strPool[strPoolIndex], p ); + strPoolIndex += len + 1; + + str = strHandle[hash]; + last = str; + while ( str && str->next ) { + last = str; + str = str->next; + } + + str = UI_Alloc( sizeof( stringDef_t ) ); + str->next = NULL; + str->str = &strPool[ph]; + if ( last ) { + last->next = str; + } else { + strHandle[hash] = str; + } + return &strPool[ph]; + } + return NULL; +} + +void String_Report() { + float f; + Com_Printf( "Memory/String Pool Info\n" ); + Com_Printf( "----------------\n" ); + f = strPoolIndex; + f /= STRING_POOL_SIZE; + f *= 100; + Com_Printf( "String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE ); + f = allocPoint; + f /= MEM_POOL_SIZE; + f *= 100; + Com_Printf( "Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE ); +} + +/* +================= +String_Init +================= +*/ +void String_Init() { + int i; + for ( i = 0; i < HASH_TABLE_SIZE; i++ ) { + strHandle[i] = 0; + } + strHandleCount = 0; + strPoolIndex = 0; + menuCount = 0; + modalMenuCount = 0; + UI_InitMemory(); + Item_SetupKeywordHash(); + Menu_SetupKeywordHash(); + if ( DC && DC->getBindingBuf ) { + Controls_GetConfig(); + } +} + +/* +================= +PC_SourceWarning +================= +*/ +void PC_SourceWarning( int handle, char *format, ... ) { + int line; + char filename[128]; + va_list argptr; + static char string[4096]; + + va_start( argptr, format ); + vsprintf( string, format, argptr ); + va_end( argptr ); + + filename[0] = '\0'; + line = 0; + trap_PC_SourceFileAndLine( handle, filename, &line ); + + Com_Printf( S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string ); +} + +/* +================= +PC_SourceError +================= +*/ +void PC_SourceError( int handle, char *format, ... ) { + int line; + char filename[128]; + va_list argptr; + static char string[4096]; + + va_start( argptr, format ); + vsprintf( string, format, argptr ); + va_end( argptr ); + + filename[0] = '\0'; + line = 0; + trap_PC_SourceFileAndLine( handle, filename, &line ); + + Com_Printf( S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string ); +} + +/* +================= +LerpColor + lerp and clamp each component of and into by the fraction +================= +*/ +void LerpColor( vec4_t a, vec4_t b, vec4_t c, float t ) { + int i; + for ( i = 0; i < 4; i++ ) + { + c[i] = a[i] + t * ( b[i] - a[i] ); + if ( c[i] < 0 ) { + c[i] = 0; + } else if ( c[i] > 1.0 ) { + c[i] = 1.0; + } + } +} + +/* +================= +Float_Parse +================= +*/ +qboolean Float_Parse( char **p, float *f ) { + char *token; + token = COM_ParseExt( p, qfalse ); + if ( token && token[0] != 0 ) { + *f = atof( token ); + return qtrue; + } else { + return qfalse; + } +} + +/* +================= +PC_Float_Parse +================= +*/ +qboolean PC_Float_Parse( int handle, float *f ) { + pc_token_t token; + int negative = qfalse; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( token.string[0] == '-' ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + negative = qtrue; + } + if ( token.type != TT_NUMBER ) { + PC_SourceError( handle, "expected float but found %s\n", token.string ); + return qfalse; + } + if ( negative ) { + *f = -token.floatvalue; + } else { + *f = token.floatvalue; + } + return qtrue; +} + +/* +================= +Color_Parse +================= +*/ +qboolean Color_Parse( char **p, vec4_t *c ) { + int i; + float f; + + for ( i = 0; i < 4; i++ ) { + if ( !Float_Parse( p, &f ) ) { + return qfalse; + } + ( *c )[i] = f; + } + return qtrue; +} + +/* +================= +PC_Color_Parse +================= +*/ +qboolean PC_Color_Parse( int handle, vec4_t *c ) { + int i; + float f; + + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + ( *c )[i] = f; + } + return qtrue; +} + +/* +================= +Int_Parse +================= +*/ +qboolean Int_Parse( char **p, int *i ) { + char *token; + token = COM_ParseExt( p, qfalse ); + + if ( token && token[0] != 0 ) { + *i = atoi( token ); + return qtrue; + } else { + return qfalse; + } +} + +/* +================= +PC_Int_Parse +================= +*/ +qboolean PC_Int_Parse( int handle, int *i ) { + pc_token_t token; + int negative = qfalse; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( token.string[0] == '-' ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + negative = qtrue; + } + if ( token.type != TT_NUMBER ) { + PC_SourceError( handle, "expected integer but found %s\n", token.string ); + return qfalse; + } + *i = token.intvalue; + if ( negative ) { + *i = -*i; + } + return qtrue; +} + +/* +================= +Rect_Parse +================= +*/ +qboolean Rect_Parse( char **p, rectDef_t *r ) { + if ( Float_Parse( p, &r->x ) ) { + if ( Float_Parse( p, &r->y ) ) { + if ( Float_Parse( p, &r->w ) ) { + if ( Float_Parse( p, &r->h ) ) { + return qtrue; + } + } + } + } + return qfalse; +} + +/* +================= +PC_Rect_Parse +================= +*/ +qboolean PC_Rect_Parse( int handle, rectDef_t *r ) { + if ( PC_Float_Parse( handle, &r->x ) ) { + if ( PC_Float_Parse( handle, &r->y ) ) { + if ( PC_Float_Parse( handle, &r->w ) ) { + if ( PC_Float_Parse( handle, &r->h ) ) { + return qtrue; + } + } + } + } + return qfalse; +} + +/* +================= +String_Parse +================= +*/ +qboolean String_Parse( char **p, const char **out ) { + char *token; + + token = COM_ParseExt( p, qfalse ); + if ( token && token[0] != 0 ) { + *( out ) = String_Alloc( token ); + return qtrue; + } + return qfalse; +} + +/* +================= +PC_String_Parse +================= +*/ +qboolean PC_String_Parse( int handle, const char **out ) { + pc_token_t token; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + + *( out ) = String_Alloc( token.string ); + return qtrue; +} + +/* +================= +PC_String_Parse_Trans + +NERVE - SMF - translates string +================= +*/ +qboolean PC_String_Parse_Trans( int handle, const char **out ) { + pc_token_t token; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + + *( out ) = String_Alloc( DC->translateString( token.string ) ); + return qtrue; +} + +// NERVE - SMF +/* +================= +PC_Char_Parse +================= +*/ +qboolean PC_Char_Parse( int handle, char *out ) { + pc_token_t token; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + + *( out ) = token.string[0]; + return qtrue; +} +// -NERVE - SMF + +/* +================= +PC_Script_Parse +================= +*/ +qboolean PC_Script_Parse( int handle, const char **out ) { + char script[1024]; + pc_token_t token; + + memset( script, 0, sizeof( script ) ); + // scripts start with { and have ; separated command lists.. commands are command, arg.. + // basically we want everything between the { } as it will be interpreted at run time + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( Q_stricmp( token.string, "{" ) != 0 ) { + return qfalse; + } + + while ( 1 ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + + if ( Q_stricmp( token.string, "}" ) == 0 ) { + *out = String_Alloc( script ); + return qtrue; + } + + if ( token.string[1] != '\0' ) { + Q_strcat( script, 1024, va( "\"%s\"", token.string ) ); + } else { + Q_strcat( script, 1024, token.string ); + } + Q_strcat( script, 1024, " " ); + } + return qfalse; // bk001105 - LCC missing return value +} + +// display, window, menu, item code +// + +/* +================== +Init_Display + +Initializes the display with a structure to all the drawing routines + ================== +*/ +void Init_Display( displayContextDef_t *dc ) { + DC = dc; +} + + + +// type and style painting + +void GradientBar_Paint( rectDef_t *rect, vec4_t color ) { + // gradient bar takes two paints + DC->setColor( color ); + DC->drawHandlePic( rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar ); + DC->setColor( NULL ); +} + + +/* +================== +Window_Init + +Initializes a window structure ( windowDef_t ) with defaults + +================== +*/ +void Window_Init( Window *w ) { + memset( w, 0, sizeof( windowDef_t ) ); + w->borderSize = 1; + w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0; + w->cinematic = -1; +} + +void Fade( int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount ) { + if ( *flags & ( WINDOW_FADINGOUT | WINDOW_FADINGIN ) ) { + if ( DC->realTime > *nextTime ) { + *nextTime = DC->realTime + offsetTime; + if ( *flags & WINDOW_FADINGOUT ) { + *f -= fadeAmount; + if ( bFlags && *f <= 0.0 ) { + *flags &= ~( WINDOW_FADINGOUT | WINDOW_VISIBLE ); + } + } else { + *f += fadeAmount; + if ( *f >= clamp ) { + *f = clamp; + if ( bFlags ) { + *flags &= ~WINDOW_FADINGIN; + } + } + } + } + } +} + + + +void Window_Paint( Window *w, float fadeAmount, float fadeClamp, float fadeCycle ) { + //float bordersize = 0; + vec4_t color; + rectDef_t fillRect = w->rect; + + if ( debugMode ) { + color[0] = color[1] = color[2] = color[3] = 1; + DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color ); + } + + if ( w == NULL || ( w->style == 0 && w->border == 0 ) ) { + return; + } + + if ( w->border != 0 ) { + fillRect.x += w->borderSize; + fillRect.y += w->borderSize; + fillRect.w -= w->borderSize + 1; + fillRect.h -= w->borderSize + 1; + } + + if ( w->style == WINDOW_STYLE_FILLED ) { + // box, but possible a shader that needs filled + if ( w->background ) { + Fade( &w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount ); + DC->setColor( w->backColor ); + DC->drawHandlePic( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background ); + DC->setColor( NULL ); + } else { + DC->fillRect( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor ); + } + } else if ( w->style == WINDOW_STYLE_GRADIENT ) { + GradientBar_Paint( &fillRect, w->backColor ); + // gradient bar + } else if ( w->style == WINDOW_STYLE_SHADER ) { + if ( w->flags & WINDOW_FORECOLORSET ) { + DC->setColor( w->foreColor ); + } + DC->drawHandlePic( fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background ); + DC->setColor( NULL ); + } else if ( w->style == WINDOW_STYLE_TEAMCOLOR ) { + if ( DC->getTeamColor ) { + DC->getTeamColor( &color ); + DC->fillRect( fillRect.x, fillRect.y, fillRect.w, fillRect.h, color ); + } + } else if ( w->style == WINDOW_STYLE_CINEMATIC ) { + if ( w->cinematic == -1 ) { + w->cinematic = DC->playCinematic( w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h ); + if ( w->cinematic == -1 ) { + w->cinematic = -2; + } + } + if ( w->cinematic >= 0 ) { + DC->runCinematicFrame( w->cinematic ); + DC->drawCinematic( w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h ); + } + } + + if ( w->border == WINDOW_BORDER_FULL ) { + // full + // HACK HACK HACK + if ( w->style == WINDOW_STYLE_TEAMCOLOR ) { + if ( color[0] > 0 ) { + // red + color[0] = 1; + color[1] = color[2] = .5; + + } else { + color[2] = 1; + color[0] = color[1] = .5; + } + color[3] = 1; + DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, color ); + } else { + DC->drawRect( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor ); + } + } else if ( w->border == WINDOW_BORDER_HORZ ) { + // top/bottom + DC->setColor( w->borderColor ); + DC->drawTopBottom( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize ); + DC->setColor( NULL ); + } else if ( w->border == WINDOW_BORDER_VERT ) { + // left right + DC->setColor( w->borderColor ); + DC->drawSides( w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize ); + DC->setColor( NULL ); + } else if ( w->border == WINDOW_BORDER_KCGRADIENT ) { + // this is just two gradient bars along each horz edge + rectDef_t r = w->rect; + r.h = w->borderSize; + GradientBar_Paint( &r, w->borderColor ); + r.y = w->rect.y + w->rect.h - 1; + GradientBar_Paint( &r, w->borderColor ); + } + +} + + +void Item_SetScreenCoords( itemDef_t *item, float x, float y ) { + + if ( item == NULL ) { + return; + } + + if ( item->window.border != 0 ) { + x += item->window.borderSize; + y += item->window.borderSize; + } + + item->window.rect.x = x + item->window.rectClient.x; + item->window.rect.y = y + item->window.rectClient.y; + item->window.rect.w = item->window.rectClient.w; + item->window.rect.h = item->window.rectClient.h; + + // force the text rects to recompute + item->textRect.w = 0; + item->textRect.h = 0; +} + +// FIXME: consolidate this with nearby stuff +void Item_UpdatePosition( itemDef_t *item ) { + float x, y; + menuDef_t *menu; + + if ( item == NULL || item->parent == NULL ) { + return; + } + + menu = item->parent; + + x = menu->window.rect.x; + y = menu->window.rect.y; + + if ( menu->window.border != 0 ) { + x += menu->window.borderSize; + y += menu->window.borderSize; + } + + Item_SetScreenCoords( item, x, y ); + +} + +// menus +void Menu_UpdatePosition( menuDef_t *menu ) { + int i; + float x, y; + + if ( menu == NULL ) { + return; + } + + x = menu->window.rect.x; + y = menu->window.rect.y; + if ( menu->window.border != 0 ) { + x += menu->window.borderSize; + y += menu->window.borderSize; + } + + for ( i = 0; i < menu->itemCount; i++ ) { + Item_SetScreenCoords( menu->items[i], x, y ); + } +} + +void Menu_PostParse( menuDef_t *menu ) { + if ( menu == NULL ) { + return; + } + if ( menu->fullScreen ) { + menu->window.rect.x = 0; + menu->window.rect.y = 0; + menu->window.rect.w = 640; + menu->window.rect.h = 480; + } + Menu_UpdatePosition( menu ); +} + +itemDef_t *Menu_ClearFocus( menuDef_t *menu ) { + int i; + itemDef_t *ret = NULL; + + if ( menu == NULL ) { + return NULL; + } + + for ( i = 0; i < menu->itemCount; i++ ) { + if ( menu->items[i]->window.flags & WINDOW_HASFOCUS ) { + ret = menu->items[i]; + } + menu->items[i]->window.flags &= ~WINDOW_HASFOCUS; + if ( menu->items[i]->leaveFocus ) { + Item_RunScript( menu->items[i], menu->items[i]->leaveFocus ); + } + } + + return ret; +} + +qboolean IsVisible( int flags ) { + return ( flags & WINDOW_VISIBLE && !( flags & WINDOW_FADINGOUT ) ); +} + +qboolean Rect_ContainsPoint( rectDef_t *rect, float x, float y ) { + if ( rect ) { + if ( x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h ) { + return qtrue; + } + } + return qfalse; +} + +int Menu_ItemsMatchingGroup( menuDef_t *menu, const char *name ) { + int i; + int count = 0; + char *pdest; + int wildcard = -1; // if wildcard is set, it's value is the number of characters to compare + + + pdest = strstr( name, "*" ); // allow wildcard strings (ex. "hide nb_*" would translate to "hide nb_pg1; hide nb_extra" etc) + if ( pdest ) { + wildcard = pdest - name; + } + + for ( i = 0; i < menu->itemCount; i++ ) { + if ( wildcard != -1 ) { + if ( Q_strncmp( menu->items[i]->window.name, name, wildcard ) == 0 || ( menu->items[i]->window.group && Q_strncmp( menu->items[i]->window.group, name, wildcard ) == 0 ) ) { + count++; + } + } else { + if ( Q_stricmp( menu->items[i]->window.name, name ) == 0 || ( menu->items[i]->window.group && Q_stricmp( menu->items[i]->window.group, name ) == 0 ) ) { + count++; + } + } + } + + return count; +} + +itemDef_t *Menu_GetMatchingItemByNumber( menuDef_t *menu, int index, const char *name ) { + int i; + int count = 0; + char *pdest; + int wildcard = -1; // if wildcard is set, it's value is the number of characters to compare + + pdest = strstr( name, "*" ); // allow wildcard strings (ex. "hide nb_*" would translate to "hide nb_pg1; hide nb_extra" etc) + if ( pdest ) { + wildcard = pdest - name; + } + + for ( i = 0; i < menu->itemCount; i++ ) { + if ( wildcard != -1 ) { + if ( Q_strncmp( menu->items[i]->window.name, name, wildcard ) == 0 || ( menu->items[i]->window.group && Q_strncmp( menu->items[i]->window.group, name, wildcard ) == 0 ) ) { + if ( count == index ) { + return menu->items[i]; + } + count++; + } + } else { + if ( Q_stricmp( menu->items[i]->window.name, name ) == 0 || ( menu->items[i]->window.group && Q_stricmp( menu->items[i]->window.group, name ) == 0 ) ) { + if ( count == index ) { + return menu->items[i]; + } + count++; + } + } + } + return NULL; +} + + + +void Script_SetColor( itemDef_t *item, char **args ) { + const char *name; + int i; + float f; + vec4_t *out; + // expecting type of color to set and 4 args for the color + if ( String_Parse( args, &name ) ) { + out = NULL; + if ( Q_stricmp( name, "backcolor" ) == 0 ) { + out = &item->window.backColor; + item->window.flags |= WINDOW_BACKCOLORSET; + } else if ( Q_stricmp( name, "forecolor" ) == 0 ) { + out = &item->window.foreColor; + item->window.flags |= WINDOW_FORECOLORSET; + } else if ( Q_stricmp( name, "bordercolor" ) == 0 ) { + out = &item->window.borderColor; + } + + if ( out ) { + for ( i = 0; i < 4; i++ ) { + if ( !Float_Parse( args, &f ) ) { + return; + } + ( *out )[i] = f; + } + } + } +} + +void Script_SetAsset( itemDef_t *item, char **args ) { + const char *name; + // expecting name to set asset to + if ( String_Parse( args, &name ) ) { + // check for a model + if ( item->type == ITEM_TYPE_MODEL ) { + } + } +} + +void Script_SetBackground( itemDef_t *item, char **args ) { + const char *name; + // expecting name to set asset to + if ( String_Parse( args, &name ) ) { + item->window.background = DC->registerShaderNoMip( name ); + } +} + + + + +itemDef_t *Menu_FindItemByName( menuDef_t *menu, const char *p ) { + int i; + + if ( menu == NULL || p == NULL ) { + return NULL; + } + + for ( i = 0; i < menu->itemCount; i++ ) { + if ( Q_stricmp( p, menu->items[i]->window.name ) == 0 ) { + return menu->items[i]; + } + } + + return NULL; +} + +void Script_SetTeamColor( itemDef_t *item, char **args ) { + if ( DC->getTeamColor ) { + int i; + vec4_t color; + DC->getTeamColor( &color ); + for ( i = 0; i < 4; i++ ) { + item->window.backColor[i] = color[i]; + } + } +} + +void Script_SetItemColor( itemDef_t *item, char **args ) { + const char *itemname; + const char *name; + vec4_t color; + int i; + vec4_t *out; + // expecting type of color to set and 4 args for the color + if ( String_Parse( args, &itemname ) && String_Parse( args, &name ) ) { + itemDef_t *item2; + int j; + int count = Menu_ItemsMatchingGroup( item->parent, itemname ); + + if ( !Color_Parse( args, &color ) ) { + return; + } + + for ( j = 0; j < count; j++ ) { + item2 = Menu_GetMatchingItemByNumber( item->parent, j, itemname ); + if ( item2 != NULL ) { + out = NULL; + if ( Q_stricmp( name, "backcolor" ) == 0 ) { + out = &item2->window.backColor; + } else if ( Q_stricmp( name, "forecolor" ) == 0 ) { + out = &item2->window.foreColor; + item2->window.flags |= WINDOW_FORECOLORSET; + } else if ( Q_stricmp( name, "bordercolor" ) == 0 ) { + out = &item2->window.borderColor; + } + + if ( out ) { + for ( i = 0; i < 4; i++ ) { + ( *out )[i] = color[i]; + } + } + } + } + } +} + + +void Menu_ShowItemByName( menuDef_t *menu, const char *p, qboolean bShow ) { + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup( menu, p ); + for ( i = 0; i < count; i++ ) { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + if ( item != NULL ) { + if ( bShow ) { + item->window.flags |= WINDOW_VISIBLE; + } else { + item->window.flags &= ~WINDOW_VISIBLE; + // stop cinematics playing in the window + if ( item->window.cinematic >= 0 ) { + DC->stopCinematic( item->window.cinematic ); + item->window.cinematic = -1; + } + } + } + } +} + +void Menu_FadeItemByName( menuDef_t *menu, const char *p, qboolean fadeOut ) { + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup( menu, p ); + for ( i = 0; i < count; i++ ) { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + if ( item != NULL ) { + if ( fadeOut ) { + item->window.flags |= ( WINDOW_FADINGOUT | WINDOW_VISIBLE ); + item->window.flags &= ~WINDOW_FADINGIN; + } else { + item->window.flags |= ( WINDOW_VISIBLE | WINDOW_FADINGIN ); + item->window.flags &= ~WINDOW_FADINGOUT; + } + } + } +} + +menuDef_t *Menus_FindByName( const char *p ) { + int i; + for ( i = 0; i < menuCount; i++ ) { + if ( Q_stricmp( Menus[i].window.name, p ) == 0 ) { + return &Menus[i]; + } + } + return NULL; +} + +void Menus_ShowByName( const char *p ) { + menuDef_t *menu = Menus_FindByName( p ); + if ( menu ) { + Menus_Activate( menu ); + } +} + +void Menus_OpenByName( const char *p ) { + Menus_ActivateByName( p, qtrue ); +} + +static void Menu_RunCloseScript( menuDef_t *menu ) { + if ( menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose ) { + itemDef_t item; + item.parent = menu; + Item_RunScript( &item, menu->onClose ); + } +} + +void Menus_CloseByName( const char *p ) { + menuDef_t *menu = Menus_FindByName( p ); + if ( menu != NULL ) { + Menu_RunCloseScript( menu ); + menu->window.flags &= ~( WINDOW_VISIBLE | WINDOW_HASFOCUS ); + if ( menu->window.flags & WINDOW_MODAL ) { + if ( modalMenuCount <= 0 ) { + Com_Printf( S_COLOR_YELLOW "WARNING: tried closing a modal window with an empty modal stack!\n" ); + } else + { + modalMenuCount--; + // if modal doesn't have a parent, the stack item may be NULL .. just go back to the main menu then + if ( modalMenuStack[modalMenuCount] ) { + Menus_ActivateByName( modalMenuStack[modalMenuCount]->window.name, qfalse ); // don't try to push the one we are opening to the stack + } + } + } + } +} + +void Menus_CloseAll() { + int i; + for ( i = 0; i < menuCount; i++ ) { + Menu_RunCloseScript( &Menus[i] ); + Menus[i].window.flags &= ~( WINDOW_HASFOCUS | WINDOW_VISIBLE ); + } +} + + +void Script_Show( itemDef_t *item, char **args ) { + const char *name; + if ( String_Parse( args, &name ) ) { + Menu_ShowItemByName( item->parent, name, qtrue ); + } +} + +void Script_Hide( itemDef_t *item, char **args ) { + const char *name; + if ( String_Parse( args, &name ) ) { + Menu_ShowItemByName( item->parent, name, qfalse ); + } +} + +void Script_FadeIn( itemDef_t *item, char **args ) { + const char *name; + if ( String_Parse( args, &name ) ) { + Menu_FadeItemByName( item->parent, name, qfalse ); + } +} + +void Script_FadeOut( itemDef_t *item, char **args ) { + const char *name; + if ( String_Parse( args, &name ) ) { + Menu_FadeItemByName( item->parent, name, qtrue ); + } +} + + + +void Script_Open( itemDef_t *item, char **args ) { + const char *name; + if ( String_Parse( args, &name ) ) { + Menus_OpenByName( name ); + } +} + +// DHM - Nerve + +void Script_ConditionalOpen( itemDef_t *item, char **args ) { + const char *cvar; + const char *name1; + const char *name2; + float val; + + if ( String_Parse( args, &cvar ) && String_Parse( args, &name1 ) && String_Parse( args, &name2 ) ) { + + val = DC->getCVarValue( cvar ); + if ( val == 0.f ) { + Menus_OpenByName( name2 ); + } else { + Menus_OpenByName( name1 ); + } + } +} + +// DHM - Nerve + +void Script_Close( itemDef_t *item, char **args ) { + const char *name; + if ( String_Parse( args, &name ) ) { + Menus_CloseByName( name ); + } +} + + + + +/* +============== +Script_Clipboard +============== +*/ +void Script_Clipboard( itemDef_t *item, char **args ) { + char curscript[64]; + DC->getCVarString( "cg_clipboardName", curscript, sizeof( curscript ) ); // grab the string the client set + Menu_ShowItemByName( item->parent, curscript, qtrue ); +} + + + + +#define NOTEBOOK_MAX_PAGES 6 // this will not be a define + + +/* +============== +Script_NotebookShowpage + hide all notebook pages and show just the active one + + inc == 0 - show current page + inc == val - turn inc pages in the notebook (negative numbers are backwards) + inc == 999 - key number. +999 is jump to last page, -999 is jump to cover page +============== +*/ +void Script_NotebookShowpage( itemDef_t *item, char **args ) { + int i, inc, curpage, newpage = 0, pages; + + pages = DC->getCVarValue( "cg_notebookpages" ); + + if ( Int_Parse( args, &inc ) ) { + + curpage = DC->getCVarValue( "ui_notebookCurrentPage" ); + + + if ( inc == 0 ) { // opening + if ( pages && !curpage ) { // only open to cover if no pages exist + inc = 1; // otherwise, go to first available page + } + } + + if ( inc == 999 ) { // jump to end +// newpage = NOTEBOOK_MAX_PAGES; // = lastpage; + curpage = 0; + inc = -1; + } else if ( inc == -999 ) { // jump to start + curpage = 0; + inc = 0; + } else if ( inc > 500 ) { +// curpage = DEBRIEFING_BASE + (inc - 500); + curpage = inc; + inc = 0; + } + + if ( inc ) { + int dec = 0; + + if ( inc > 0 ) { + for ( i = 1; i < NOTEBOOK_MAX_PAGES; i++ ) { + newpage = curpage + i; + if ( newpage > NOTEBOOK_MAX_PAGES ) { + newpage = newpage % NOTEBOOK_MAX_PAGES; + } + + if ( newpage == 0 ) { + continue; + } + + if ( pages & ( 1 << ( newpage - 1 ) ) ) { + dec++; +// if(dec == inc) +// break; + break; + } + } + if ( i < NOTEBOOK_MAX_PAGES ) { // a valid page was found + curpage = newpage; + } + + } else { + for ( i = 1; i < NOTEBOOK_MAX_PAGES; i++ ) { + newpage = curpage - i; + if ( newpage <= 0 ) { + newpage = newpage + NOTEBOOK_MAX_PAGES; + } + + if ( pages & ( 1 << ( newpage - 1 ) ) ) { + break; + } + } + if ( i < NOTEBOOK_MAX_PAGES ) { // a valid page was found + curpage = newpage; + } + + } + } + + + // hide all the pages +// Menu_ShowItemByName(item->parent, "page_*", qfalse); + Menu_ShowItemByName( item->parent, "cover", qfalse ); + for ( i = 1; i <= NOTEBOOK_MAX_PAGES; i++ ) { + Menu_ShowItemByName( item->parent, va( "page%d", i ), qfalse ); + } + + // show the visible one + + if ( curpage ) { + Menu_ShowItemByName( item->parent, va( "page%d", curpage ), qtrue ); + } else { + Menu_ShowItemByName( item->parent, "cover", qtrue ); + } + + DC->setCVar( "ui_notebookCurrentPage", va( "%d", curpage ) ); // store new current page + + } +} + + + +void Menu_TransitionItemByName( menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, int time, float amt ) { + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup( menu, p ); + for ( i = 0; i < count; i++ ) { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + if ( item != NULL ) { + item->window.flags |= ( WINDOW_INTRANSITION | WINDOW_VISIBLE ); + item->window.offsetTime = time; + memcpy( &item->window.rectClient, &rectFrom, sizeof( rectDef_t ) ); + memcpy( &item->window.rectEffects, &rectTo, sizeof( rectDef_t ) ); + item->window.rectEffects2.x = abs( rectTo.x - rectFrom.x ) / amt; + item->window.rectEffects2.y = abs( rectTo.y - rectFrom.y ) / amt; + item->window.rectEffects2.w = abs( rectTo.w - rectFrom.w ) / amt; + item->window.rectEffects2.h = abs( rectTo.h - rectFrom.h ) / amt; + Item_UpdatePosition( item ); + } + } +} + + +void Script_Transition( itemDef_t *item, char **args ) { + const char *name; + rectDef_t rectFrom, rectTo; + int time; + float amt; + + if ( String_Parse( args, &name ) ) { + if ( Rect_Parse( args, &rectFrom ) && Rect_Parse( args, &rectTo ) && Int_Parse( args, &time ) && Float_Parse( args, &amt ) ) { + Menu_TransitionItemByName( item->parent, name, rectFrom, rectTo, time, amt ); + } + } +} + + +void Menu_OrbitItemByName( menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time ) { + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup( menu, p ); + for ( i = 0; i < count; i++ ) { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + if ( item != NULL ) { + item->window.flags |= ( WINDOW_ORBITING | WINDOW_VISIBLE ); + item->window.offsetTime = time; + item->window.rectEffects.x = cx; + item->window.rectEffects.y = cy; + item->window.rectClient.x = x; + item->window.rectClient.y = y; + Item_UpdatePosition( item ); + } + } +} + + +void Script_Orbit( itemDef_t *item, char **args ) { + const char *name; + float cx, cy, x, y; + int time; + + if ( String_Parse( args, &name ) ) { + if ( Float_Parse( args, &x ) && Float_Parse( args, &y ) && Float_Parse( args, &cx ) && Float_Parse( args, &cy ) && Int_Parse( args, &time ) ) { + Menu_OrbitItemByName( item->parent, name, x, y, cx, cy, time ); + } + } +} + + + +void Script_SetFocus( itemDef_t *item, char **args ) { + const char *name; + itemDef_t *focusItem; + + if ( String_Parse( args, &name ) ) { + focusItem = Menu_FindItemByName( item->parent, name ); + if ( focusItem && !( focusItem->window.flags & WINDOW_DECORATION ) && !( focusItem->window.flags & WINDOW_HASFOCUS ) ) { + Menu_ClearFocus( item->parent ); + focusItem->window.flags |= WINDOW_HASFOCUS; + if ( focusItem->onFocus ) { + Item_RunScript( focusItem, focusItem->onFocus ); + } + if ( DC->Assets.itemFocusSound ) { + DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND ); + } + } + } +} + +void Script_SetPlayerModel( itemDef_t *item, char **args ) { + const char *name; + if ( String_Parse( args, &name ) ) { + DC->setCVar( "team_model", name ); + } +} + +void Script_SetPlayerHead( itemDef_t *item, char **args ) { + const char *name; + if ( String_Parse( args, &name ) ) { + DC->setCVar( "team_headmodel", name ); + } +} + +// ATVI Wolfenstein Misc #304 +// the parser misreads setCvar "bleh" "" +// you have to use clearCvar "bleh" +void Script_ClearCvar( itemDef_t *item, char **args ) { + const char *cvar; + if ( String_Parse( args, &cvar ) ) { + DC->setCVar( cvar, "" ); + } +} + +void Script_SetCvar( itemDef_t *item, char **args ) { + const char *cvar, *val; + if ( String_Parse( args, &cvar ) && String_Parse( args, &val ) ) { + DC->setCVar( cvar, val ); + } +} + +void Script_Exec( itemDef_t *item, char **args ) { + const char *val; + if ( String_Parse( args, &val ) ) { + DC->executeText( EXEC_APPEND, va( "%s ; ", val ) ); + } +} + +void Script_Play( itemDef_t *item, char **args ) { + const char *val; + if ( String_Parse( args, &val ) ) { + DC->startLocalSound( DC->registerSound( val ), CHAN_LOCAL_SOUND ); // all sounds are not 3d + } +} + +void Script_playLooped( itemDef_t *item, char **args ) { + const char *val; + if ( String_Parse( args, &val ) ) { + DC->stopBackgroundTrack(); + DC->startBackgroundTrack( val, val ); + } +} + +// NERVE - SMF +void Script_AddListItem( itemDef_t *item, char **args ) { + const char *itemname, *val, *name; + itemDef_t *t; + + if ( String_Parse( args, &itemname ) && String_Parse( args, &val ) && String_Parse( args, &name ) ) { + t = Menu_FindItemByName( item->parent, itemname ); + if ( t && t->special ) { + DC->feederAddItem( t->special, name, atoi( val ) ); + } + } +} +// -NERVE - SMF +// DHM - Nerve +void Script_CheckAutoUpdate( itemDef_t *item, char **args ) { + DC->checkAutoUpdate(); +} + +void Script_GetAutoUpdate( itemDef_t *item, char **args ) { + DC->getAutoUpdate(); +} +// DHM - Nerve + +commandDef_t commandList[] = +{ + {"fadein", &Script_FadeIn}, // group/name + {"fadeout", &Script_FadeOut}, // group/name + {"show", &Script_Show}, // group/name + {"hide", &Script_Hide}, // group/name + {"setcolor", &Script_SetColor}, // works on this + {"open", &Script_Open}, // menu + + {"conditionalopen", &Script_ConditionalOpen}, // DHM - Nerve:: cvar menu menu + // opens first menu if cvar is true[non-zero], second if false + + {"close", &Script_Close}, // menu + {"clipboard", &Script_Clipboard}, // show the current clipboard group by name + {"showpage", &Script_NotebookShowpage}, // + {"setasset", &Script_SetAsset}, // works on this + {"setbackground", &Script_SetBackground}, // works on this + {"setitemcolor", &Script_SetItemColor}, // group/name + {"setteamcolor", &Script_SetTeamColor}, // sets this background color to team color + {"setfocus", &Script_SetFocus}, // sets this background color to team color + {"setplayermodel", &Script_SetPlayerModel}, // sets this background color to team color + {"setplayerhead", &Script_SetPlayerHead}, // sets this background color to team color + {"transition", &Script_Transition}, // group/name + {"setcvar", &Script_SetCvar}, // group/name + {"clearcvar", &Script_ClearCvar}, + {"exec", &Script_Exec}, // group/name + {"play", &Script_Play}, // group/name + {"playlooped", &Script_playLooped}, // group/name + {"orbit", &Script_Orbit}, // group/name + {"addlistitem", &Script_AddListItem}, // NERVE - SMF - special command to add text items to list box + {"checkautoupdate", &Script_CheckAutoUpdate}, // DHM - Nerve + {"getautoupdate", &Script_GetAutoUpdate} // DHM - Nerve +}; + +int scriptCommandCount = sizeof( commandList ) / sizeof( commandDef_t ); + + +void Item_RunScript( itemDef_t *item, const char *s ) { + char script[1024], *p; + int i; + qboolean bRan; + memset( script, 0, sizeof( script ) ); + if ( item && s && s[0] ) { + Q_strcat( script, 1024, s ); + p = script; + while ( 1 ) { + const char *command; + // expect command then arguments, ; ends command, NULL ends script + if ( !String_Parse( &p, &command ) ) { + return; + } + + if ( command[0] == ';' && command[1] == '\0' ) { + continue; + } + + bRan = qfalse; + for ( i = 0; i < scriptCommandCount; i++ ) { + if ( Q_stricmp( command, commandList[i].name ) == 0 ) { + ( commandList[i].handler( item, &p ) ); + bRan = qtrue; + break; + } + } + // not in our auto list, pass to handler + if ( !bRan ) { + DC->runScript( &p ); + } + } + } +} + + +qboolean Item_EnableShowViaCvar( itemDef_t *item, int flag ) { + char script[1024], *p; + memset( script, 0, sizeof( script ) ); + if ( item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest ) { + char buff[1024]; + DC->getCVarString( item->cvarTest, buff, sizeof( buff ) ); + + Q_strcat( script, 1024, item->enableCvar ); + p = script; + while ( 1 ) { + const char *val; + // expect value then ; or NULL, NULL ends list + if ( !String_Parse( &p, &val ) ) { + return ( item->cvarFlags & flag ) ? qfalse : qtrue; + } + + if ( val[0] == ';' && val[1] == '\0' ) { + continue; + } + + // enable it if any of the values are true + if ( item->cvarFlags & flag ) { + if ( Q_stricmp( buff, val ) == 0 ) { + return qtrue; + } + } else { + // disable it if any of the values are true + if ( Q_stricmp( buff, val ) == 0 ) { + return qfalse; + } + } + + } + return ( item->cvarFlags & flag ) ? qfalse : qtrue; + } + return qtrue; +} + + +// will optionaly set focus to this item +qboolean Item_SetFocus( itemDef_t *item, float x, float y ) { + int i; + itemDef_t *oldFocus; + sfxHandle_t *sfx = &DC->Assets.itemFocusSound; + qboolean playSound = qfalse; + menuDef_t *parent; // bk001206: = (menuDef_t*)item->parent; + // sanity check, non-null, not a decoration and does not already have the focus + if ( item == NULL || item->window.flags & WINDOW_DECORATION || item->window.flags & WINDOW_HASFOCUS || !( item->window.flags & WINDOW_VISIBLE ) ) { + return qfalse; + } + + // bk001206 - this can be NULL. + parent = (menuDef_t*)item->parent; + + // items can be enabled and disabled based on cvars + if ( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) { + return qfalse; + } + + if ( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( item, CVAR_SHOW ) ) { + return qfalse; + } + + oldFocus = Menu_ClearFocus( item->parent ); + + if ( item->type == ITEM_TYPE_TEXT ) { + rectDef_t r; + r = item->textRect; + r.y -= r.h; + if ( Rect_ContainsPoint( &r, x, y ) ) { + item->window.flags |= WINDOW_HASFOCUS; + if ( item->focusSound ) { + sfx = &item->focusSound; + } + playSound = qtrue; + } else { + if ( oldFocus ) { + oldFocus->window.flags |= WINDOW_HASFOCUS; + if ( oldFocus->onFocus ) { + Item_RunScript( oldFocus, oldFocus->onFocus ); + } + } + } + } else { + item->window.flags |= WINDOW_HASFOCUS; + if ( item->onFocus ) { + Item_RunScript( item, item->onFocus ); + } + if ( item->focusSound ) { + sfx = &item->focusSound; + } + playSound = qtrue; + } + + if ( playSound && sfx ) { + DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND ); + } + + for ( i = 0; i < parent->itemCount; i++ ) { + if ( parent->items[i] == item ) { + parent->cursorItem = i; + break; + } + } + + return qtrue; +} + +int Item_ListBox_MaxScroll( itemDef_t *item ) { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + int count = DC->feederCount( item->special ); + int max; + + if ( item->window.flags & WINDOW_HORIZONTAL ) { + max = count - ( item->window.rect.w / listPtr->elementWidth ) + 1; + } else { + max = count - ( item->window.rect.h / listPtr->elementHeight ) + 1; + } + if ( max < 0 ) { + return 0; + } + return max; +} + +int Item_ListBox_ThumbPosition( itemDef_t *item ) { + float max, pos, size; + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + + max = Item_ListBox_MaxScroll( item ); + if ( item->window.flags & WINDOW_HORIZONTAL ) { + size = item->window.rect.w - ( SCROLLBAR_SIZE * 2 ) - 2; + if ( max > 0 ) { + pos = ( size - SCROLLBAR_SIZE ) / (float) max; + } else { + pos = 0; + } + pos *= listPtr->startPos; + return item->window.rect.x + 1 + SCROLLBAR_SIZE + pos; + } else { + size = item->window.rect.h - ( SCROLLBAR_SIZE * 2 ) - 2; + if ( max > 0 ) { + pos = ( size - SCROLLBAR_SIZE ) / (float) max; + } else { + pos = 0; + } + pos *= listPtr->startPos; + return item->window.rect.y + 1 + SCROLLBAR_SIZE + pos; + } +} + +int Item_ListBox_ThumbDrawPosition( itemDef_t *item ) { + int min, max; + + if ( itemCapture == item ) { + if ( item->window.flags & WINDOW_HORIZONTAL ) { + min = item->window.rect.x + SCROLLBAR_SIZE + 1; + max = item->window.rect.x + item->window.rect.w - 2 * SCROLLBAR_SIZE - 1; + if ( DC->cursorx >= min + SCROLLBAR_SIZE / 2 && DC->cursorx <= max + SCROLLBAR_SIZE / 2 ) { + return DC->cursorx - SCROLLBAR_SIZE / 2; + } else { + return Item_ListBox_ThumbPosition( item ); + } + } else { + min = item->window.rect.y + SCROLLBAR_SIZE + 1; + max = item->window.rect.y + item->window.rect.h - 2 * SCROLLBAR_SIZE - 1; + if ( DC->cursory >= min + SCROLLBAR_SIZE / 2 && DC->cursory <= max + SCROLLBAR_SIZE / 2 ) { + return DC->cursory - SCROLLBAR_SIZE / 2; + } else { + return Item_ListBox_ThumbPosition( item ); + } + } + } else { + return Item_ListBox_ThumbPosition( item ); + } +} + +float Item_Slider_ThumbPosition( itemDef_t *item ) { + float value, range, x; + editFieldDef_t *editDef = item->typeData; + + if ( item->text ) { + x = item->textRect.x + item->textRect.w + 8; + } else { + x = item->window.rect.x; + } + + if ( editDef == NULL && item->cvar ) { + return x; + } + + value = DC->getCVarValue( item->cvar ); + + if ( value < editDef->minVal ) { + value = editDef->minVal; + } else if ( value > editDef->maxVal ) { + value = editDef->maxVal; + } + + range = editDef->maxVal - editDef->minVal; + value -= editDef->minVal; + value /= range; + //value /= (editDef->maxVal - editDef->minVal); + value *= SLIDER_WIDTH; + x += value; + // vm fuckage + //x = x + (((float)value / editDef->maxVal) * SLIDER_WIDTH); + return x; +} + +int Item_Slider_OverSlider( itemDef_t *item, float x, float y ) { + rectDef_t r; + + r.x = Item_Slider_ThumbPosition( item ) - ( SLIDER_THUMB_WIDTH / 2 ); + r.y = item->window.rect.y - 2; + r.w = SLIDER_THUMB_WIDTH; + r.h = SLIDER_THUMB_HEIGHT; + + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_THUMB; + } + return 0; +} + +int Item_ListBox_OverLB( itemDef_t *item, float x, float y ) { + rectDef_t r; + listBoxDef_t *listPtr; + int thumbstart; + int count; + + count = DC->feederCount( item->special ); + listPtr = (listBoxDef_t*)item->typeData; + if ( item->window.flags & WINDOW_HORIZONTAL ) { + // check if on left arrow + r.x = item->window.rect.x; + r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + r.h = r.w = SCROLLBAR_SIZE; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_LEFTARROW; + } + // check if on right arrow + r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_RIGHTARROW; + } + // check if on thumb + thumbstart = Item_ListBox_ThumbPosition( item ); + r.x = thumbstart; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_THUMB; + } + r.x = item->window.rect.x + SCROLLBAR_SIZE; + r.w = thumbstart - r.x; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_PGUP; + } + r.x = thumbstart + SCROLLBAR_SIZE; + r.w = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_PGDN; + } + } else { + r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; + r.y = item->window.rect.y; + r.h = r.w = SCROLLBAR_SIZE; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_LEFTARROW; + } + r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_RIGHTARROW; + } + thumbstart = Item_ListBox_ThumbPosition( item ); + r.y = thumbstart; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_THUMB; + } + r.y = item->window.rect.y + SCROLLBAR_SIZE; + r.h = thumbstart - r.y; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_PGUP; + } + r.y = thumbstart + SCROLLBAR_SIZE; + r.h = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + if ( Rect_ContainsPoint( &r, x, y ) ) { + return WINDOW_LB_PGDN; + } + } + return 0; +} + + +void Item_ListBox_MouseEnter( itemDef_t *item, float x, float y ) { + rectDef_t r; + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + + item->window.flags &= ~( WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN ); + item->window.flags |= Item_ListBox_OverLB( item, x, y ); + + if ( item->window.flags & WINDOW_HORIZONTAL ) { + if ( !( item->window.flags & ( WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN ) ) ) { + // check for selection hit as we have exausted buttons and thumb + if ( listPtr->elementStyle == LISTBOX_IMAGE ) { + r.x = item->window.rect.x; + r.y = item->window.rect.y; + r.h = item->window.rect.h - SCROLLBAR_SIZE; + r.w = item->window.rect.w - listPtr->drawPadding; + if ( Rect_ContainsPoint( &r, x, y ) ) { + listPtr->cursorPos = (int)( ( x - r.x ) / listPtr->elementWidth ) + listPtr->startPos; + if ( listPtr->cursorPos >= listPtr->endPos ) { + listPtr->cursorPos = listPtr->endPos; + } + } + } else { + // text hit.. + } + } + } else if ( !( item->window.flags & ( WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN ) ) ) { + r.x = item->window.rect.x; + r.y = item->window.rect.y; + r.w = item->window.rect.w - SCROLLBAR_SIZE; + r.h = item->window.rect.h - listPtr->drawPadding; + if ( Rect_ContainsPoint( &r, x, y ) ) { + listPtr->cursorPos = (int)( ( y - 2 - r.y ) / listPtr->elementHeight ) + listPtr->startPos; + if ( listPtr->cursorPos > listPtr->endPos ) { + listPtr->cursorPos = listPtr->endPos; + } + } + } +} + +void Item_MouseEnter( itemDef_t *item, float x, float y ) { + rectDef_t r; + if ( item ) { + r = item->textRect; + r.y -= r.h; + // in the text rect? + + // items can be enabled and disabled based on cvars + if ( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) { + return; + } + + if ( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( item, CVAR_SHOW ) ) { + return; + } + + if ( Rect_ContainsPoint( &r, x, y ) ) { + if ( !( item->window.flags & WINDOW_MOUSEOVERTEXT ) ) { + Item_RunScript( item, item->mouseEnterText ); + item->window.flags |= WINDOW_MOUSEOVERTEXT; + } + if ( !( item->window.flags & WINDOW_MOUSEOVER ) ) { + Item_RunScript( item, item->mouseEnter ); + item->window.flags |= WINDOW_MOUSEOVER; + } + + } else { + // not in the text rect + if ( item->window.flags & WINDOW_MOUSEOVERTEXT ) { + // if we were + Item_RunScript( item, item->mouseExitText ); + item->window.flags &= ~WINDOW_MOUSEOVERTEXT; + } + if ( !( item->window.flags & WINDOW_MOUSEOVER ) ) { + Item_RunScript( item, item->mouseEnter ); + item->window.flags |= WINDOW_MOUSEOVER; + } + + if ( item->type == ITEM_TYPE_LISTBOX ) { + Item_ListBox_MouseEnter( item, x, y ); + } + } + } +} + +void Item_MouseLeave( itemDef_t *item ) { + if ( item ) { + if ( item->window.flags & WINDOW_MOUSEOVERTEXT ) { + Item_RunScript( item, item->mouseExitText ); + item->window.flags &= ~WINDOW_MOUSEOVERTEXT; + } + Item_RunScript( item, item->mouseExit ); + item->window.flags &= ~( WINDOW_LB_RIGHTARROW | WINDOW_LB_LEFTARROW ); + } +} + +itemDef_t *Menu_HitTest( menuDef_t *menu, float x, float y ) { + int i; + for ( i = 0; i < menu->itemCount; i++ ) { + if ( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) ) { + return menu->items[i]; + } + } + return NULL; +} + +void Item_SetMouseOver( itemDef_t *item, qboolean focus ) { + if ( item ) { + if ( focus ) { + item->window.flags |= WINDOW_MOUSEOVER; + } else { + item->window.flags &= ~WINDOW_MOUSEOVER; + } + } +} + + +qboolean Item_OwnerDraw_HandleKey( itemDef_t *item, int key ) { + if ( item && DC->ownerDrawHandleKey ) { + return DC->ownerDrawHandleKey( item->window.ownerDraw, item->window.ownerDrawFlags, &item->special, key ); + } + return qfalse; +} + +qboolean Item_ListBox_HandleKey( itemDef_t *item, int key, qboolean down, qboolean force ) { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + int count = DC->feederCount( item->special ); + int max, viewmax; + + if ( force || ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && item->window.flags & WINDOW_HASFOCUS ) ) { + max = Item_ListBox_MaxScroll( item ); + if ( item->window.flags & WINDOW_HORIZONTAL ) { + viewmax = ( item->window.rect.w / listPtr->elementWidth ); + if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) { + if ( !listPtr->notselectable ) { + listPtr->cursorPos--; + if ( listPtr->cursorPos < 0 ) { + listPtr->cursorPos = 0; + } + if ( listPtr->cursorPos < listPtr->startPos ) { + listPtr->startPos = listPtr->cursorPos; + } + if ( listPtr->cursorPos >= listPtr->startPos + viewmax ) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->special, item->cursorPos ); + } else { + listPtr->startPos--; + if ( listPtr->startPos < 0 ) { + listPtr->startPos = 0; + } + } + return qtrue; + } + if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) { + if ( !listPtr->notselectable ) { + listPtr->cursorPos++; + if ( listPtr->cursorPos < listPtr->startPos ) { + listPtr->startPos = listPtr->cursorPos; + } + if ( listPtr->cursorPos >= count ) { + listPtr->cursorPos = count - 1; + } + if ( listPtr->cursorPos >= listPtr->startPos + viewmax ) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->special, item->cursorPos ); + } else { + listPtr->startPos++; + if ( listPtr->startPos >= count ) { + listPtr->startPos = count - 1; + } + } + return qtrue; + } + } else { + viewmax = ( item->window.rect.h / listPtr->elementHeight ); + if ( key == K_UPARROW || key == K_KP_UPARROW ) { + if ( !listPtr->notselectable ) { + listPtr->cursorPos--; + if ( listPtr->cursorPos < 0 ) { + listPtr->cursorPos = 0; + } + if ( listPtr->cursorPos < listPtr->startPos ) { + listPtr->startPos = listPtr->cursorPos; + } + if ( listPtr->cursorPos >= listPtr->startPos + viewmax ) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->special, item->cursorPos ); + } else { + listPtr->startPos--; + if ( listPtr->startPos < 0 ) { + listPtr->startPos = 0; + } + } + return qtrue; + } + if ( key == K_DOWNARROW || key == K_KP_DOWNARROW ) { + if ( !listPtr->notselectable ) { + listPtr->cursorPos++; + if ( listPtr->cursorPos < listPtr->startPos ) { + listPtr->startPos = listPtr->cursorPos; + } + if ( listPtr->cursorPos >= count ) { + listPtr->cursorPos = count - 1; + } + if ( listPtr->cursorPos >= listPtr->startPos + viewmax ) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->special, item->cursorPos ); + } else { + listPtr->startPos++; + if ( listPtr->startPos > max ) { + listPtr->startPos = max; + } + } + return qtrue; + } + } + // mouse hit + if ( key == K_MOUSE1 || key == K_MOUSE2 ) { + if ( item->window.flags & WINDOW_LB_LEFTARROW ) { + listPtr->startPos--; + if ( listPtr->startPos < 0 ) { + listPtr->startPos = 0; + } + } else if ( item->window.flags & WINDOW_LB_RIGHTARROW ) { + // one down + listPtr->startPos++; + if ( listPtr->startPos > max ) { + listPtr->startPos = max; + } + } else if ( item->window.flags & WINDOW_LB_PGUP ) { + // page up + listPtr->startPos -= viewmax; + if ( listPtr->startPos < 0 ) { + listPtr->startPos = 0; + } + } else if ( item->window.flags & WINDOW_LB_PGDN ) { + // page down + listPtr->startPos += viewmax; + if ( listPtr->startPos > max ) { + listPtr->startPos = max; + } + } else if ( item->window.flags & WINDOW_LB_THUMB ) { + // Display_SetCaptureItem(item); + } else { + // select an item + if ( DC->realTime < lastListBoxClickTime && listPtr->doubleClick ) { + Item_RunScript( item, listPtr->doubleClick ); + } + lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY; + if ( item->cursorPos != listPtr->cursorPos ) { + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->special, item->cursorPos ); + } + } + return qtrue; + } + if ( key == K_HOME || key == K_KP_HOME ) { + // home + listPtr->startPos = 0; + return qtrue; + } + if ( key == K_END || key == K_KP_END ) { + // end + listPtr->startPos = max; + return qtrue; + } + if ( key == K_PGUP || key == K_KP_PGUP ) { + // page up + if ( !listPtr->notselectable ) { + listPtr->cursorPos -= viewmax; + if ( listPtr->cursorPos < 0 ) { + listPtr->cursorPos = 0; + } + if ( listPtr->cursorPos < listPtr->startPos ) { + listPtr->startPos = listPtr->cursorPos; + } + if ( listPtr->cursorPos >= listPtr->startPos + viewmax ) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->special, item->cursorPos ); + } else { + listPtr->startPos -= viewmax; + if ( listPtr->startPos < 0 ) { + listPtr->startPos = 0; + } + } + return qtrue; + } + if ( key == K_PGDN || key == K_KP_PGDN ) { + // page down + if ( !listPtr->notselectable ) { + listPtr->cursorPos += viewmax; + if ( listPtr->cursorPos < listPtr->startPos ) { + listPtr->startPos = listPtr->cursorPos; + } + if ( listPtr->cursorPos >= count ) { + listPtr->cursorPos = count - 1; + } + if ( listPtr->cursorPos >= listPtr->startPos + viewmax ) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection( item->special, item->cursorPos ); + } else { + listPtr->startPos += viewmax; + if ( listPtr->startPos > max ) { + listPtr->startPos = max; + } + } + return qtrue; + } + } + return qfalse; +} + +qboolean Item_YesNo_HandleKey( itemDef_t *item, int key ) { + if ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && item->window.flags & WINDOW_HASFOCUS && item->cvar ) { + if ( key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3 ) { + // ATVI Wolfenstein Misc #462 + // added the flag to toggle via action script only + if ( !( item->cvarFlags & CVAR_NOTOGGLE ) ) { + DC->setCVar( item->cvar, va( "%i", !DC->getCVarValue( item->cvar ) ) ); + } + return qtrue; + } + } + return qfalse; +} + +int Item_Multi_CountSettings( itemDef_t *item ) { + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if ( multiPtr == NULL ) { + return 0; + } + return multiPtr->count; +} + +int Item_Multi_FindCvarByValue( itemDef_t *item ) { + char buff[1024]; + float value = 0; + int i; + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if ( multiPtr ) { + if ( multiPtr->strDef ) { + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + } else { + value = DC->getCVarValue( item->cvar ); + } + for ( i = 0; i < multiPtr->count; i++ ) { + if ( multiPtr->strDef ) { + if ( Q_stricmp( buff, multiPtr->cvarStr[i] ) == 0 ) { + return i; + } + } else { + if ( multiPtr->cvarValue[i] == value ) { + return i; + } + } + } + } + return 0; +} + +const char *Item_Multi_Setting( itemDef_t *item ) { + char buff[1024]; + float value = 0; + int i; + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if ( multiPtr ) { + if ( multiPtr->strDef ) { + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + } else { + value = DC->getCVarValue( item->cvar ); + } + for ( i = 0; i < multiPtr->count; i++ ) { + if ( multiPtr->strDef ) { + if ( Q_stricmp( buff, multiPtr->cvarStr[i] ) == 0 ) { + return multiPtr->cvarList[i]; + } + } else { + if ( multiPtr->cvarValue[i] == value ) { + return multiPtr->cvarList[i]; + } + } + } + } + return ""; +} + +qboolean Item_Multi_HandleKey( itemDef_t *item, int key ) { + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if ( multiPtr ) { + if ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && item->window.flags & WINDOW_HASFOCUS && item->cvar ) { + if ( key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3 ) { + int current = Item_Multi_FindCvarByValue( item ) + 1; + int max = Item_Multi_CountSettings( item ); + if ( current < 0 || current >= max ) { + current = 0; + } + if ( multiPtr->strDef ) { + DC->setCVar( item->cvar, multiPtr->cvarStr[current] ); + } else { + float value = multiPtr->cvarValue[current]; + if ( ( (float)( (int) value ) ) == value ) { + DC->setCVar( item->cvar, va( "%i", (int) value ) ); + } else { + DC->setCVar( item->cvar, va( "%f", value ) ); + } + } + return qtrue; + } + } + } + return qfalse; +} + +qboolean Item_TextField_HandleKey( itemDef_t *item, int key ) { + char buff[1024]; + int len; + itemDef_t *newItem = NULL; + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + + if ( item->cvar ) { + + memset( buff, 0, sizeof( buff ) ); + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + len = strlen( buff ); + if ( editPtr->maxChars && len > editPtr->maxChars ) { + len = editPtr->maxChars; + } + if ( key & K_CHAR_FLAG ) { + key &= ~K_CHAR_FLAG; + + + if ( key == 'h' - 'a' + 1 ) { // ctrl-h is backspace + if ( item->cursorPos > 0 ) { + memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos ); + item->cursorPos--; + if ( item->cursorPos < editPtr->paintOffset ) { + editPtr->paintOffset--; + } + } + DC->setCVar( item->cvar, buff ); + return qtrue; + } + + + // + // ignore any non printable chars + // + if ( key < 32 || !item->cvar ) { + return qtrue; + } + + if ( item->type == ITEM_TYPE_NUMERICFIELD ) { + if ( key < '0' || key > '9' ) { + return qfalse; + } + } + + if ( !DC->getOverstrikeMode() ) { + if ( ( len == MAX_EDITFIELD - 1 ) || ( editPtr->maxChars && len >= editPtr->maxChars ) ) { + return qtrue; + } + memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos ); + } else { + if ( editPtr->maxChars && item->cursorPos >= editPtr->maxChars ) { + return qtrue; + } + } + + buff[item->cursorPos] = key; + + DC->setCVar( item->cvar, buff ); + + if ( item->cursorPos < len + 1 ) { + item->cursorPos++; + if ( editPtr->maxPaintChars && item->cursorPos > editPtr->maxPaintChars ) { + editPtr->paintOffset++; + } + } + + } else { + + if ( key == K_DEL || key == K_KP_DEL ) { + if ( item->cursorPos < len ) { + memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos ); + DC->setCVar( item->cvar, buff ); + } + return qtrue; + } + + if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) { + if ( editPtr->maxPaintChars && item->cursorPos >= editPtr->paintOffset + editPtr->maxPaintChars && item->cursorPos < len ) { + item->cursorPos++; + editPtr->paintOffset++; + return qtrue; + } + if ( item->cursorPos < len ) { + item->cursorPos++; + } + return qtrue; + } + + if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) { + if ( item->cursorPos > 0 ) { + item->cursorPos--; + } + if ( item->cursorPos < editPtr->paintOffset ) { + editPtr->paintOffset--; + } + return qtrue; + } + + if ( key == K_HOME || key == K_KP_HOME ) { // || ( tolower(key) == 'a' && trap_Key_IsDown( K_CTRL ) ) ) { + item->cursorPos = 0; + editPtr->paintOffset = 0; + return qtrue; + } + + if ( key == K_END || key == K_KP_END ) { // ( tolower(key) == 'e' && trap_Key_IsDown( K_CTRL ) ) ) { + item->cursorPos = len; + if ( item->cursorPos > editPtr->maxPaintChars ) { + editPtr->paintOffset = len - editPtr->maxPaintChars; + } + return qtrue; + } + + if ( key == K_INS || key == K_KP_INS ) { + DC->setOverstrikeMode( !DC->getOverstrikeMode() ); + return qtrue; + } + } + + if ( key == K_TAB || key == K_DOWNARROW || key == K_KP_DOWNARROW ) { + newItem = Menu_SetNextCursorItem( item->parent ); + if ( newItem && ( newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD ) ) { + g_editItem = newItem; + } + } + + if ( key == K_UPARROW || key == K_KP_UPARROW ) { + newItem = Menu_SetPrevCursorItem( item->parent ); + if ( newItem && ( newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD ) ) { + g_editItem = newItem; + } + } + + // NERVE - SMF + if ( key == K_ENTER || key == K_KP_ENTER ) { + if ( item->onAccept ) { + Item_RunScript( item, item->onAccept ); + } + } + // -NERVE - SMF + + if ( key == K_ENTER || key == K_KP_ENTER || key == K_ESCAPE ) { + return qfalse; + } + + return qtrue; + } + return qfalse; + +} + +static void Scroll_ListBox_AutoFunc( void *p ) { + scrollInfo_t *si = (scrollInfo_t*)p; + if ( DC->realTime > si->nextScrollTime ) { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_ListBox_HandleKey( si->item, si->scrollKey, qtrue, qfalse ); + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if ( DC->realTime > si->nextAdjustTime ) { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + if ( si->adjustValue > SCROLL_TIME_FLOOR ) { + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } + } +} + +static void Scroll_ListBox_ThumbFunc( void *p ) { + scrollInfo_t *si = (scrollInfo_t*)p; + rectDef_t r; + int pos, max; + + listBoxDef_t *listPtr = (listBoxDef_t*)si->item->typeData; + if ( si->item->window.flags & WINDOW_HORIZONTAL ) { + if ( DC->cursorx == si->xStart ) { + return; + } + r.x = si->item->window.rect.x + SCROLLBAR_SIZE + 1; + r.y = si->item->window.rect.y + si->item->window.rect.h - SCROLLBAR_SIZE - 1; + r.h = SCROLLBAR_SIZE; + r.w = si->item->window.rect.w - ( SCROLLBAR_SIZE * 2 ) - 2; + max = Item_ListBox_MaxScroll( si->item ); + // + pos = ( DC->cursorx - r.x - SCROLLBAR_SIZE / 2 ) * max / ( r.w - SCROLLBAR_SIZE ); + if ( pos < 0 ) { + pos = 0; + } else if ( pos > max ) { + pos = max; + } + listPtr->startPos = pos; + si->xStart = DC->cursorx; + } else if ( DC->cursory != si->yStart ) { + + r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_SIZE - 1; + r.y = si->item->window.rect.y + SCROLLBAR_SIZE + 1; + r.h = si->item->window.rect.h - ( SCROLLBAR_SIZE * 2 ) - 2; + r.w = SCROLLBAR_SIZE; + max = Item_ListBox_MaxScroll( si->item ); + // + pos = ( DC->cursory - r.y - SCROLLBAR_SIZE / 2 ) * max / ( r.h - SCROLLBAR_SIZE ); + if ( pos < 0 ) { + pos = 0; + } else if ( pos > max ) { + pos = max; + } + listPtr->startPos = pos; + si->yStart = DC->cursory; + } + + if ( DC->realTime > si->nextScrollTime ) { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_ListBox_HandleKey( si->item, si->scrollKey, qtrue, qfalse ); + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if ( DC->realTime > si->nextAdjustTime ) { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + if ( si->adjustValue > SCROLL_TIME_FLOOR ) { + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } + } +} + +static void Scroll_Slider_ThumbFunc( void *p ) { + float x, value, cursorx; + scrollInfo_t *si = (scrollInfo_t*)p; + editFieldDef_t *editDef = si->item->typeData; + + if ( si->item->text ) { + x = si->item->textRect.x + si->item->textRect.w + 8; + } else { + x = si->item->window.rect.x; + } + + cursorx = DC->cursorx; + + if ( cursorx < x ) { + cursorx = x; + } else if ( cursorx > x + SLIDER_WIDTH ) { + cursorx = x + SLIDER_WIDTH; + } + value = cursorx - x; + value /= SLIDER_WIDTH; + value *= ( editDef->maxVal - editDef->minVal ); + value += editDef->minVal; + DC->setCVar( si->item->cvar, va( "%f", value ) ); +} + +void Item_StartCapture( itemDef_t *item, int key ) { + int flags; + switch ( item->type ) { + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + + case ITEM_TYPE_LISTBOX: + { + flags = Item_ListBox_OverLB( item, DC->cursorx, DC->cursory ); + if ( flags & ( WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW ) ) { + scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START; + scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + scrollInfo.adjustValue = SCROLL_TIME_START; + scrollInfo.scrollKey = key; + scrollInfo.scrollDir = ( flags & WINDOW_LB_LEFTARROW ) ? qtrue : qfalse; + scrollInfo.item = item; + captureData = &scrollInfo; + captureFunc = &Scroll_ListBox_AutoFunc; + itemCapture = item; + } else if ( flags & WINDOW_LB_THUMB ) { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + captureData = &scrollInfo; + captureFunc = &Scroll_ListBox_ThumbFunc; + itemCapture = item; + } + break; + } + case ITEM_TYPE_SLIDER: + { + flags = Item_Slider_OverSlider( item, DC->cursorx, DC->cursory ); + if ( flags & WINDOW_LB_THUMB ) { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + captureData = &scrollInfo; + captureFunc = &Scroll_Slider_ThumbFunc; + itemCapture = item; + } + break; + } + } +} + +void Item_StopCapture( itemDef_t *item ) { + +} + +qboolean Item_Slider_HandleKey( itemDef_t *item, int key, qboolean down ) { + float x, value, width, work; + + //DC->Print("slider handle key\n"); + if ( item->window.flags & WINDOW_HASFOCUS && item->cvar && Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) ) { + if ( key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3 ) { + editFieldDef_t *editDef = item->typeData; + if ( editDef ) { + rectDef_t testRect; + width = SLIDER_WIDTH; + if ( item->text ) { + x = item->textRect.x + item->textRect.w + 8; + } else { + x = item->window.rect.x; + } + + testRect = item->window.rect; + testRect.x = x; + value = (float)SLIDER_THUMB_WIDTH / 2; + testRect.x -= value; + //DC->Print("slider x: %f\n", testRect.x); + testRect.w = ( SLIDER_WIDTH + (float)SLIDER_THUMB_WIDTH / 2 ); + //DC->Print("slider w: %f\n", testRect.w); + if ( Rect_ContainsPoint( &testRect, DC->cursorx, DC->cursory ) ) { + work = DC->cursorx - x; + value = work / width; + value *= ( editDef->maxVal - editDef->minVal ); + // vm fuckage + // value = (((float)(DC->cursorx - x)/ SLIDER_WIDTH) * (editDef->maxVal - editDef->minVal)); + value += editDef->minVal; + DC->setCVar( item->cvar, va( "%f", value ) ); + return qtrue; + } + } + } + } + DC->Print( "slider handle key exit\n" ); + return qfalse; +} + + +qboolean Item_HandleKey( itemDef_t *item, int key, qboolean down ) { + + if ( itemCapture ) { + Item_StopCapture( itemCapture ); + itemCapture = NULL; + captureFunc = NULL; + captureData = NULL; + } else { + // bk001206 - parentheses + if ( down && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) { + Item_StartCapture( item, key ); + } + } + + if ( !down ) { + return qfalse; + } + + switch ( item->type ) { + case ITEM_TYPE_BUTTON: + return qfalse; + break; + case ITEM_TYPE_RADIOBUTTON: + return qfalse; + break; + case ITEM_TYPE_CHECKBOX: + return qfalse; + break; + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + //return Item_TextField_HandleKey(item, key); + return qfalse; + break; + case ITEM_TYPE_COMBO: + return qfalse; + break; + case ITEM_TYPE_LISTBOX: + return Item_ListBox_HandleKey( item, key, down, qfalse ); + break; + case ITEM_TYPE_YESNO: + return Item_YesNo_HandleKey( item, key ); + break; + case ITEM_TYPE_MULTI: + return Item_Multi_HandleKey( item, key ); + break; + case ITEM_TYPE_OWNERDRAW: + return Item_OwnerDraw_HandleKey( item, key ); + break; + case ITEM_TYPE_BIND: + return Item_Bind_HandleKey( item, key, down ); + break; + case ITEM_TYPE_SLIDER: + return Item_Slider_HandleKey( item, key, down ); + break; + //case ITEM_TYPE_IMAGE: + // Item_Image_Paint(item); + // break; + default: + return qfalse; + break; + } + + //return qfalse; +} + +void Item_Action( itemDef_t *item ) { + if ( item ) { + Item_RunScript( item, item->action ); + } +} + +itemDef_t *Menu_SetPrevCursorItem( menuDef_t *menu ) { + qboolean wrapped = qfalse; + int oldCursor = menu->cursorItem; + + if ( menu->cursorItem < 0 ) { + menu->cursorItem = menu->itemCount - 1; + wrapped = qtrue; + } + + while ( menu->cursorItem > -1 ) { + + menu->cursorItem--; + if ( menu->cursorItem < 0 && !wrapped ) { + wrapped = qtrue; + menu->cursorItem = menu->itemCount - 1; + } + // NERVE - SMF + if ( menu->cursorItem < 0 ) { + menu->cursorItem = oldCursor; + return NULL; + } + // -NERVE - SMF + + if ( Item_SetFocus( menu->items[menu->cursorItem], DC->cursorx, DC->cursory ) ) { + Menu_HandleMouseMove( menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1 ); + return menu->items[menu->cursorItem]; + } + } + menu->cursorItem = oldCursor; + return NULL; + +} + +itemDef_t *Menu_SetNextCursorItem( menuDef_t *menu ) { + + qboolean wrapped = qfalse; + int oldCursor = menu->cursorItem; + + + if ( menu->cursorItem == -1 ) { + menu->cursorItem = 0; + wrapped = qtrue; + } + + while ( menu->cursorItem < menu->itemCount ) { + + menu->cursorItem++; + if ( menu->cursorItem >= menu->itemCount ) { // (SA) had a problem 'tabbing' in dialogs with only one possible button + if ( !wrapped ) { + wrapped = qtrue; + menu->cursorItem = 0; + } else { + return menu->items[oldCursor]; + } + } + + if ( Item_SetFocus( menu->items[menu->cursorItem], DC->cursorx, DC->cursory ) ) { + Menu_HandleMouseMove( menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1 ); + return menu->items[menu->cursorItem]; + } + } + + menu->cursorItem = oldCursor; + return NULL; +} + + +static void Window_CloseCinematic( windowDef_t *window ) { + if ( window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0 ) { + DC->stopCinematic( window->cinematic ); + window->cinematic = -1; + } +} + +static void Menu_CloseCinematics( menuDef_t *menu ) { + if ( menu ) { + int i; + Window_CloseCinematic( &menu->window ); + for ( i = 0; i < menu->itemCount; i++ ) { + Window_CloseCinematic( &menu->items[i]->window ); + if ( menu->items[i]->type == ITEM_TYPE_OWNERDRAW ) { + DC->stopCinematic( 0 - menu->items[i]->window.ownerDraw ); + } + } + } +} + +static void Display_CloseCinematics() { + int i; + for ( i = 0; i < menuCount; i++ ) { + Menu_CloseCinematics( &Menus[i] ); + } +} + +void Menus_Activate( menuDef_t *menu ) { + menu->window.flags |= ( WINDOW_HASFOCUS | WINDOW_VISIBLE ); + if ( menu->onOpen ) { + itemDef_t item; + item.parent = menu; + Item_RunScript( &item, menu->onOpen ); + } + + if ( menu->soundName && *menu->soundName ) { +// DC->stopBackgroundTrack(); // you don't want to do this since it will reset s_rawend + DC->startBackgroundTrack( menu->soundName, menu->soundName ); + } + + Display_CloseCinematics(); + +} + +int Display_VisibleMenuCount() { + int i, count; + count = 0; + for ( i = 0; i < menuCount; i++ ) { + if ( Menus[i].window.flags & ( WINDOW_FORCED | WINDOW_VISIBLE ) ) { + count++; + } + } + return count; +} + +void Menus_HandleOOBClick( menuDef_t *menu, int key, qboolean down ) { + if ( menu ) { + int i; + // basically the behaviour we are looking for is if there are windows in the stack.. see if + // the cursor is within any of them.. if not close them otherwise activate them and pass the + // key on.. force a mouse move to activate focus and script stuff + if ( down && menu->window.flags & WINDOW_OOB_CLICK ) { + Menu_RunCloseScript( menu ); + menu->window.flags &= ~( WINDOW_HASFOCUS | WINDOW_VISIBLE ); + } + + for ( i = 0; i < menuCount; i++ ) { + if ( Menu_OverActiveItem( &Menus[i], DC->cursorx, DC->cursory ) ) { +// Menu_RunCloseScript(menu); // NERVE - SMF - why do we close the calling menu instead of just removing the focus? +// menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); + menu->window.flags &= ~( WINDOW_HASFOCUS ); + Menus_Activate( &Menus[i] ); + Menu_HandleMouseMove( &Menus[i], DC->cursorx, DC->cursory ); + Menu_HandleKey( &Menus[i], key, down ); + } + } + + if ( Display_VisibleMenuCount() == 0 ) { + if ( DC->Pause ) { + DC->Pause( qfalse ); + } + } + Display_CloseCinematics(); + } +} + +static rectDef_t *Item_CorrectedTextRect( itemDef_t *item ) { + static rectDef_t rect; + memset( &rect, 0, sizeof( rectDef_t ) ); + if ( item ) { + rect = item->textRect; + if ( rect.w ) { + rect.y -= rect.h; + } + } + return ▭ +} + +void Menu_HandleKey( menuDef_t *menu, int key, qboolean down ) { + int i; + itemDef_t *item = NULL; + qboolean inHandler = qfalse; + + Menu_HandleMouseMove( menu, DC->cursorx, DC->cursory ); // NERVE - SMF - fix for focus not resetting on unhidden buttons + + if ( inHandler ) { + return; + } + + inHandler = qtrue; + if ( g_waitingForKey && down ) { + Item_Bind_HandleKey( g_bindItem, key, down ); + inHandler = qfalse; + return; + } + + if ( g_editingField && down ) { + if ( !Item_TextField_HandleKey( g_editItem, key ) ) { + g_editingField = qfalse; + g_editItem = NULL; + inHandler = qfalse; + return; + } else if ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) { + g_editingField = qfalse; + g_editItem = NULL; + Display_MouseMove( NULL, DC->cursorx, DC->cursory ); + } else if ( key == K_TAB || key == K_UPARROW || key == K_DOWNARROW ) { + return; + } + } + + if ( menu == NULL ) { + inHandler = qfalse; + return; + } + + // see if the mouse is within the window bounds and if so is this a mouse click + if ( down && !( menu->window.flags & WINDOW_POPUP ) && !Rect_ContainsPoint( &menu->window.rect, DC->cursorx, DC->cursory ) ) { + static qboolean inHandleKey = qfalse; + // bk001206 - parentheses + if ( !inHandleKey && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) { + inHandleKey = qtrue; + Menus_HandleOOBClick( menu, key, down ); + inHandleKey = qfalse; + inHandler = qfalse; + return; + } + } + + // get the item with focus + for ( i = 0; i < menu->itemCount; i++ ) { + if ( menu->items[i]->window.flags & WINDOW_HASFOCUS ) { + item = menu->items[i]; + } + } + + if ( item != NULL ) { + if ( Item_HandleKey( item, key, down ) ) { + Item_Action( item ); + inHandler = qfalse; + return; + } + } + + if ( !down ) { + inHandler = qfalse; + return; + } + + // NERVE - SMF + if ( key > 0 && key <= 255 && menu->onKey[key] ) { + itemDef_t it; + it.parent = menu; + Item_RunScript( &it, menu->onKey[key] ); + return; + } + // - NERVE - SMF + + // default handling + switch ( key ) { + + case K_F11: + if ( DC->getCVarValue( "developer" ) ) { + debugMode ^= 1; + } + break; + + case K_F12: + if ( DC->getCVarValue( "developer" ) ) { + DC->executeText( EXEC_APPEND, "screenshot\n" ); + } + break; + case K_KP_UPARROW: + case K_UPARROW: + Menu_SetPrevCursorItem( menu ); + break; + + case K_ESCAPE: + if ( !g_waitingForKey && menu->onESC ) { + itemDef_t it; + it.parent = menu; + Item_RunScript( &it, menu->onESC ); + } + break; + + case K_TAB: + case K_KP_DOWNARROW: + case K_DOWNARROW: + Menu_SetNextCursorItem( menu ); + break; + + case K_MOUSE1: + case K_MOUSE2: + if ( item ) { + if ( item->type == ITEM_TYPE_TEXT ) { + if ( Rect_ContainsPoint( Item_CorrectedTextRect( item ), DC->cursorx, DC->cursory ) ) { + Item_Action( item ); + } + } else if ( item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD ) { + if ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) ) { + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + + // NERVE - SMF - reset scroll offset so we can see what we're editing + if ( editPtr ) { + editPtr->paintOffset = 0; + } + + item->cursorPos = 0; + g_editingField = qtrue; + g_editItem = item; + + DC->setOverstrikeMode( qtrue ); + } + } else { + if ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) ) { + Item_Action( item ); + } + } + } + break; + + case K_JOY1: + case K_JOY2: + case K_JOY3: + case K_JOY4: + case K_AUX1: + case K_AUX2: + case K_AUX3: + case K_AUX4: + case K_AUX5: + case K_AUX6: + case K_AUX7: + case K_AUX8: + case K_AUX9: + case K_AUX10: + case K_AUX11: + case K_AUX12: + case K_AUX13: + case K_AUX14: + case K_AUX15: + case K_AUX16: + break; + case K_KP_ENTER: + case K_ENTER: + case K_MOUSE3: + if ( item ) { + if ( item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD ) { + item->cursorPos = 0; + g_editingField = qtrue; + g_editItem = item; + DC->setOverstrikeMode( qtrue ); + } else { + Item_Action( item ); + } + } + break; + } + inHandler = qfalse; +} + +void ToWindowCoords( float *x, float *y, windowDef_t *window ) { + if ( window->border != 0 ) { + *x += window->borderSize; + *y += window->borderSize; + } + *x += window->rect.x; + *y += window->rect.y; +} + +void Rect_ToWindowCoords( rectDef_t *rect, windowDef_t *window ) { + ToWindowCoords( &rect->x, &rect->y, window ); +} + +void Item_SetTextExtents( itemDef_t *item, int *width, int *height, const char *text ) { + const char *textPtr = ( text ) ? text : item->text; + + if ( textPtr == NULL ) { + return; + } + + *width = item->textRect.w; + *height = item->textRect.h; + + // keeps us from computing the widths and heights more than once + if ( *width == 0 || ( item->type == ITEM_TYPE_OWNERDRAW && item->textalignment == ITEM_ALIGN_CENTER ) || item->textalignment == ITEM_ALIGN_CENTER2 ) { + int originalWidth = DC->textWidth( item->text, item->textscale, 0 ); + + if ( item->type == ITEM_TYPE_OWNERDRAW && ( item->textalignment == ITEM_ALIGN_CENTER || item->textalignment == ITEM_ALIGN_RIGHT ) ) { + originalWidth += DC->ownerDrawWidth( item->window.ownerDraw, item->textscale ); + } else if ( item->type == ITEM_TYPE_EDITFIELD && item->textalignment == ITEM_ALIGN_CENTER && item->cvar ) { + char buff[256]; + DC->getCVarString( item->cvar, buff, 256 ); + originalWidth += DC->textWidth( buff, item->textscale, 0 ); + } else if ( item->textalignment == ITEM_ALIGN_CENTER2 ) { + // NERVE - SMF - default centering case + originalWidth += DC->textWidth( text, item->textscale, 0 ); + } + + *width = DC->textWidth( textPtr, item->textscale, 0 ); + *height = DC->textHeight( textPtr, item->textscale, 0 ); + item->textRect.w = *width; + item->textRect.h = *height; + item->textRect.x = item->textalignx; + item->textRect.y = item->textaligny; + if ( item->textalignment == ITEM_ALIGN_RIGHT ) { + item->textRect.x = item->textalignx - originalWidth; + } else if ( item->textalignment == ITEM_ALIGN_CENTER || item->textalignment == ITEM_ALIGN_CENTER2 ) { + // NERVE - SMF - default centering case + item->textRect.x = item->textalignx - originalWidth / 2; + } + + ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window ); + } +} + +void Item_TextColor( itemDef_t *item, vec4_t *newColor ) { + vec4_t lowLight; + menuDef_t *parent = (menuDef_t*)item->parent; + + Fade( &item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount ); + + if ( item->window.flags & WINDOW_HASFOCUS ) { + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor( parent->focusColor,lowLight,*newColor,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } else if ( item->textStyle == ITEM_TEXTSTYLE_BLINK && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) ) { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor( item->window.foreColor,lowLight,*newColor,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } else { + memcpy( newColor, &item->window.foreColor, sizeof( vec4_t ) ); + // items can be enabled and disabled based on cvars + } + + if ( item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest ) { + if ( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) { + memcpy( newColor, &parent->disableColor, sizeof( vec4_t ) ); + } + } +} + +void Item_Text_AutoWrapped_Paint( itemDef_t *item ) { + char text[1024]; + const char *p, *textPtr, *newLinePtr; + char buff[1024]; + int width, height, len, textWidth, newLine, newLineWidth; + float y; + vec4_t color; + + textWidth = 0; + newLinePtr = NULL; + + if ( item->text == NULL ) { + if ( item->cvar == NULL ) { + return; + } else { + DC->getCVarString( item->cvar, text, sizeof( text ) ); + textPtr = text; + } + } else { + textPtr = item->text; + } + if ( *textPtr == '\0' ) { + return; + } + Item_TextColor( item, &color ); + Item_SetTextExtents( item, &width, &height, textPtr ); + + y = item->textaligny; + len = 0; + buff[0] = '\0'; + newLine = 0; + newLineWidth = 0; + p = textPtr; + while ( p ) { + if ( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' ) { + newLine = len; + newLinePtr = p + 1; + newLineWidth = textWidth; + } + textWidth = DC->textWidth( buff, item->textscale, 0 ); + if ( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' ) { + if ( len ) { + if ( item->textalignment == ITEM_ALIGN_LEFT ) { + item->textRect.x = item->textalignx; + } else if ( item->textalignment == ITEM_ALIGN_RIGHT ) { + item->textRect.x = item->textalignx - newLineWidth; + } else if ( item->textalignment == ITEM_ALIGN_CENTER ) { + item->textRect.x = item->textalignx - newLineWidth / 2; + } + item->textRect.y = y; + ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window ); + // + buff[newLine] = '\0'; + DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color, buff, 0, 0, item->textStyle ); + } + if ( *p == '\0' ) { + break; + } + // + y += height + 5; + p = newLinePtr; + len = 0; + newLine = 0; + newLineWidth = 0; + continue; + } + buff[len++] = *p++; + + if ( buff[len - 1] == 13 ) { + buff[len - 1] = ' '; + } + + buff[len] = '\0'; + } +} + +void Item_Text_Wrapped_Paint( itemDef_t *item ) { + char text[1024]; + const char *p, *start, *textPtr; + char buff[1024]; + int width, height; + float x, y; + vec4_t color; + + // now paint the text and/or any optional images + // default to left + + if ( item->text == NULL ) { + if ( item->cvar == NULL ) { + return; + } else { + DC->getCVarString( item->cvar, text, sizeof( text ) ); + textPtr = text; + } + } else { + textPtr = item->text; + } + if ( *textPtr == '\0' ) { + return; + } + + Item_TextColor( item, &color ); + Item_SetTextExtents( item, &width, &height, textPtr ); + + x = item->textRect.x; + y = item->textRect.y; + start = textPtr; + p = strchr( textPtr, '\r' ); + while ( p && *p ) { + strncpy( buff, start, p - start + 1 ); + buff[p - start] = '\0'; + DC->drawText( x, y, item->textscale, color, buff, 0, 0, item->textStyle ); + y += height + 5; + start += p - start + 1; + p = strchr( p + 1, '\r' ); + } + DC->drawText( x, y, item->textscale, color, start, 0, 0, item->textStyle ); +} + +void Item_Text_Paint( itemDef_t *item ) { + char text[1024]; + const char *textPtr; + int height, width; + vec4_t color; + + if ( item->window.flags & WINDOW_WRAPPED ) { + Item_Text_Wrapped_Paint( item ); + return; + } + if ( item->window.flags & WINDOW_AUTOWRAPPED ) { + Item_Text_AutoWrapped_Paint( item ); + return; + } + + if ( item->text == NULL ) { + if ( item->cvar == NULL ) { + return; + } else { + DC->getCVarString( item->cvar, text, sizeof( text ) ); + if ( item->window.flags & CG_SHOW_TEXTASINT ) { + COM_StripExtension( text, text ); + } + textPtr = text; + } + } else { + textPtr = item->text; + } + + // this needs to go here as it sets extents for cvar types as well + Item_SetTextExtents( item, &width, &height, textPtr ); + + if ( *textPtr == '\0' ) { + return; + } + + + Item_TextColor( item, &color ); + + //FIXME: this is a fucking mess +/* + adjust = 0; + if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { + adjust = 0.5; + } + + if (item->textStyle == ITEM_TEXTSTYLE_SHADOWED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { + Fade(&item->window.flags, &DC->Assets.shadowColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse); + DC->drawText(item->textRect.x + DC->Assets.shadowX, item->textRect.y + DC->Assets.shadowY, item->textscale, DC->Assets.shadowColor, textPtr, adjust); + } +*/ + + +// if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) { +// Fade(&item->window.flags, &item->window.outlineColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse); +// /* +// Text_Paint(item->textRect.x-1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); +// Text_Paint(item->textRect.x, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); +// Text_Paint(item->textRect.x+1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust); +// Text_Paint(item->textRect.x-1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust); +// Text_Paint(item->textRect.x+1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust); +// Text_Paint(item->textRect.x-1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); +// Text_Paint(item->textRect.x, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); +// Text_Paint(item->textRect.x+1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust); +// */ +// DC->drawText(item->textRect.x - 1, item->textRect.y + 1, item->textscale * 1.02, item->window.outlineColor, textPtr, adjust); +// } + + DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle ); +} + + + +void Item_TextField_Paint( itemDef_t *item ) { + char buff[1024]; + vec4_t newColor, lowLight; + int offset; + int text_len = 0; // screen length of the editfield text that will be printed + int field_offset; // character offset in the editfield string + int screen_offset; // offset on screen for precise placement + menuDef_t *parent = (menuDef_t*)item->parent; + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + + Item_Text_Paint( item ); + + buff[0] = '\0'; + + if ( item->cvar ) { + DC->getCVarString( item->cvar, buff, sizeof( buff ) ); + } + + parent = (menuDef_t*)item->parent; + + if ( item->window.flags & WINDOW_HASFOCUS ) { + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor( parent->focusColor,lowLight,newColor,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } else { + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + } + + // NOTE: offset from the editfield prefix (like "Say: " in limbo menu) + offset = ( item->text && *item->text ) ? 8 : 0; + + // TTimo + // text length control + // if the edit field goes beyond the available width, drop some characters at the beginning of the string and apply some offseting + // FIXME: we could cache the text length and offseting, but given the low count of edit fields, I abstained for now + // FIXME: this won't handle going back into the line of the editfield to the hidden area + // start of text painting: item->textRect.x + item->textRect.w + offset + // our window limit: item->window.rect.x + item->window.rect.w + field_offset = -1; + do + { + field_offset++; + if ( buff + editPtr->paintOffset + field_offset == '\0' ) { + break; // keep it safe + } + text_len = DC->textWidth( buff + editPtr->paintOffset + field_offset, item->textscale, 0 ); + } while ( text_len + item->textRect.x + item->textRect.w + offset > item->window.rect.x + item->window.rect.w ); + if ( field_offset ) { + // we had to take out some chars to make it fit in, there is an additional screen offset to compute + screen_offset = item->window.rect.x + item->window.rect.w - ( text_len + item->textRect.x + item->textRect.w + offset ); + } else { + screen_offset = 0; + } + + if ( item->window.flags & WINDOW_HASFOCUS && g_editingField ) { + char cursor = DC->getOverstrikeMode() ? '_' : '|'; + DC->drawTextWithCursor( item->textRect.x + item->textRect.w + offset + screen_offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset + field_offset, item->cursorPos - editPtr->paintOffset - field_offset, cursor, editPtr->maxPaintChars, item->textStyle ); + } else { + DC->drawText( item->textRect.x + item->textRect.w + offset + screen_offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset + field_offset, 0, editPtr->maxPaintChars, item->textStyle ); + } + +} + +void Item_YesNo_Paint( itemDef_t *item ) { + vec4_t newColor, lowLight; + float value; + menuDef_t *parent = (menuDef_t*)item->parent; + + value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0; + + if ( item->window.flags & WINDOW_HASFOCUS ) { + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor( parent->focusColor,lowLight,newColor,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } else { + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + } + + if ( item->text ) { + Item_Text_Paint( item ); + DC->drawText( item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, + ( value != 0 ) ? DC->translateString( "Yes" ) : DC->translateString( "No" ), 0, 0, item->textStyle ); + } else { + DC->drawText( item->textRect.x, item->textRect.y, item->textscale, newColor, ( value != 0 ) ? "Yes" : "No", 0, 0, item->textStyle ); + } +} + +void Item_Multi_Paint( itemDef_t *item ) { + vec4_t newColor, lowLight; + const char *text = ""; + menuDef_t *parent = (menuDef_t*)item->parent; + + if ( item->window.flags & WINDOW_HASFOCUS ) { + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor( parent->focusColor,lowLight,newColor,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } else { + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + } + + text = Item_Multi_Setting( item ); + + if ( item->text ) { + Item_Text_Paint( item ); + DC->drawText( item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle ); + } else { + DC->drawText( item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle ); + } +} + + +typedef struct { + char *command; + int id; + int defaultbind1; + int defaultbind2; + int bind1; + int bind2; +} bind_t; + +typedef struct +{ + char* name; + float defaultvalue; + float value; +} configcvar_t; + + +static bind_t g_bindings[] = +{ + {"+scores", -1, -1, -1, -1}, + {"+speed", K_SHIFT, -1, -1, -1}, + {"+forward", K_UPARROW, -1, -1, -1}, + {"+back", K_DOWNARROW, -1, -1, -1}, + {"+moveleft", ',', -1, -1, -1}, + {"+moveright", '.', -1, -1, -1}, + {"+moveup", K_SPACE, -1, -1, -1}, + {"+movedown", 'c', -1, -1, -1}, + {"+left", K_LEFTARROW, -1, -1, -1}, + {"+right", K_RIGHTARROW, -1, -1, -1}, + {"+strafe", K_ALT, -1, -1, -1}, + {"+lookup", K_PGDN, -1, -1, -1}, + {"+lookdown", K_DEL, -1, -1, -1}, + {"+mlook", '/', -1, -1, -1}, + {"centerview", K_END, -1, -1, -1}, + {"+zoom", 'z', -1, -1, -1}, + {"weaponbank 1", '1', -1, -1, -1}, + {"weaponbank 2", '2', -1, -1, -1}, + {"weaponbank 3", '3', -1, -1, -1}, + {"weaponbank 4", '4', -1, -1, -1}, + {"weaponbank 5", '5', -1, -1, -1}, + {"weaponbank 6", '6', -1, -1, -1}, + {"weaponbank 7", '7', -1, -1, -1}, + {"weaponbank 8", '8', -1, -1, -1}, + {"weaponbank 9", '9', -1, -1, -1}, + {"weaponbank 10", '0', -1, -1, -1}, + {"+attack", K_CTRL, -1, -1, -1}, + {"weapprev", K_MWHEELDOWN, -1, -1, -1}, + {"weapnext", K_MWHEELUP, -1, -1, -1}, + {"weapalt", -1, -1, -1, -1}, + {"weaplastused", -1, -1, -1, -1}, //----(SA) added + {"weapnextinbank", -1, -1, -1, -1}, //----(SA) added + {"weapprevinbank", -1, -1, -1, -1}, //----(SA) added + {"+useitem", K_ENTER, -1, -1, -1}, + {"itemprev", '[', -1, -1, -1}, + {"itemnext", ']', -1, -1, -1}, + {"+button3", K_MOUSE3, -1, -1, -1}, + +/* + {"prevTeamMember", -1, -1, -1, -1}, + {"nextTeamMember", -1, -1, -1, -1}, + {"nextOrder", -1, -1, -1, -1}, + {"confirmOrder", -1, -1, -1, -1}, + {"denyOrder", -1, -1, -1, -1}, + {"taskOffense", -1, -1, -1, -1}, + {"taskDefense", -1, -1, -1, -1}, + {"taskPatrol", -1, -1, -1, -1}, + {"taskCamp", -1, -1, -1, -1}, + {"taskFollow", -1, -1, -1, -1}, + {"taskRetrieve", -1, -1, -1, -1}, + {"taskEscort", -1, -1, -1, -1}, + {"taskOwnFlag", -1, -1, -1, -1}, + {"taskSuicide", -1, -1, -1, -1}, + {"tauntKillInsult", -1, -1, -1, -1}, + {"tauntPraise", -1, -1, -1, -1}, + {"tauntTaunt", -1, -1, -1, -1}, + {"tauntDeathInsult",-1, -1, -1, -1}, + {"tauntGauntlet", -1, -1, -1, -1}, +*/ + {"scoresUp", -1, -1, -1, -1}, + {"scoresDown", -1, -1, -1, -1}, + {"messagemode", -1, -1, -1, -1}, + {"messagemode2", -1, -1, -1, -1}, + {"messagemode3", -1, -1, -1, -1}, + {"messagemode4", -1, -1, -1, -1}, + + {"+activate", -1, -1, -1, -1}, + {"zoomin", -1, -1, -1, -1}, + {"zoomout", -1, -1, -1, -1}, + {"+kick", -1, -1, -1, -1}, + {"+reload", -1, -1, -1, -1}, + {"+sprint", -1, -1, -1, -1}, + {"notebook", K_TAB, -1, -1, -1}, + {"help", K_F1, -1, -1, -1}, + {"+leanleft", -1, -1, -1, -1}, + {"+leanright", -1, -1, -1, -1}, + + // DHM - Nerve + {"vote yes", -1, -1, -1, -1}, + {"vote no", -1, -1, -1, -1}, + // dhm + // NERVE - SMF + {"OpenLimboMenu", -1, -1, -1, -1}, + {"mp_QuickMessage", -1, -1, -1, -1}, + {"+dropweapon", -1, -1, -1, -1}, + // -NERVE - SMF + + {"weapon 1", -1, -1, -1, -1}, + {"weapon 2", -1, -1, -1, -1}, + {"weapon 3", -1, -1, -1, -1}, + {"weapon 4", -1, -1, -1, -1}, + {"weapon 5", -1, -1, -1, -1}, + {"weapon 6", -1, -1, -1, -1}, + {"weapon 7", -1, -1, -1, -1}, + {"weapon 8", -1, -1, -1, -1}, + {"weapon 9", -1, -1, -1, -1}, + {"weapon 10", -1, -1, -1, -1}, + {"weapon 11", -1, -1, -1, -1}, + {"weapon 12", -1, -1, -1, -1}, + {"weapon 13", -1, -1, -1, -1}, + {"weapon 14", -1, -1, -1, -1}, + {"weapon 15", -1, -1, -1, -1}, + {"weapon 16", -1, -1, -1, -1}, + {"weapon 17", -1, -1, -1, -1}, + {"weapon 18", -1, -1, -1, -1}, + {"weapon 19", -1, -1, -1, -1}, + {"weapon 20", -1, -1, -1, -1}, + {"weapon 21", -1, -1, -1, -1}, + {"weapon 22", -1, -1, -1, -1}, + {"weapon 23", -1, -1, -1, -1}, + {"weapon 24", -1, -1, -1, -1}, + {"weapon 25", -1, -1, -1, -1}, + {"weapon 26", -1, -1, -1, -1}, + {"weapon 27", -1, -1, -1, -1}, + {"weapon 28", -1, -1, -1, -1}, + {"weapon 29", -1, -1, -1, -1}, + {"weapon 30", -1, -1, -1, -1}, + {"weapon 31", -1, -1, -1, -1}, + {"weapon 32", -1, -1, -1, -1} +}; + + +static const int g_bindCount = sizeof( g_bindings ) / sizeof( bind_t ); + +/* +// TTimo unused +static configcvar_t g_configcvars[] = +{ + {"cl_run", 0, 0}, + {"m_pitch", 0, 0}, + {"cg_autoswitch", 0, 0}, + {"sensitivity", 0, 0}, + {"in_joystick", 0, 0}, + {"joy_threshold", 0, 0}, + {"m_filter", 0, 0}, + {"cl_freelook", 0, 0}, + {"cg_quickMessageAlt", 0, 0}, // NERVE - SMF + {NULL, 0, 0} +}; +*/ + +/* +================= +Controls_GetKeyAssignment +================= +*/ +void Controls_GetKeyAssignment( char *command, int *twokeys ) { + int count; + int j; + char b[256]; + + twokeys[0] = twokeys[1] = -1; + count = 0; + + for ( j = 0; j < 256; j++ ) + { + DC->getBindingBuf( j, b, 256 ); + if ( *b == 0 ) { + continue; + } + if ( !Q_stricmp( b, command ) ) { + twokeys[count] = j; + count++; + if ( count == 2 ) { + break; + } + } + } +} + +/* +================= +Controls_GetConfig +================= +*/ +void Controls_GetConfig( void ) { + int i; + int twokeys[2]; + + // iterate each command, get its numeric binding + for ( i = 0; i < g_bindCount; i++ ) + { + + Controls_GetKeyAssignment( g_bindings[i].command, twokeys ); + + g_bindings[i].bind1 = twokeys[0]; + g_bindings[i].bind2 = twokeys[1]; + } + + //s_controls.invertmouse.curvalue = DC->getCVarValue( "m_pitch" ) < 0; + //s_controls.smoothmouse.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "m_filter" ) ); + //s_controls.alwaysrun.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_run" ) ); + //s_controls.autoswitch.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cg_autoswitch" ) ); + //s_controls.sensitivity.curvalue = UI_ClampCvar( 2, 30, Controls_GetCvarValue( "sensitivity" ) ); + //s_controls.joyenable.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "in_joystick" ) ); + //s_controls.joythreshold.curvalue = UI_ClampCvar( 0.05, 0.75, Controls_GetCvarValue( "joy_threshold" ) ); + //s_controls.freelook.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_freelook" ) ); +} + +/* +================= +Controls_SetConfig +================= +*/ +void Controls_SetConfig( qboolean restart ) { + int i; + + // iterate each command, get its numeric binding + for ( i = 0; i < g_bindCount; i++ ) + { + + if ( g_bindings[i].bind1 != -1 ) { + DC->setBinding( g_bindings[i].bind1, g_bindings[i].command ); + + if ( g_bindings[i].bind2 != -1 ) { + DC->setBinding( g_bindings[i].bind2, g_bindings[i].command ); + } + } + } + + //if ( s_controls.invertmouse.curvalue ) + // DC->setCVar("m_pitch", va("%f),-fabs( DC->getCVarValue( "m_pitch" ) ) ); + //else + // trap_Cvar_SetValue( "m_pitch", fabs( trap_Cvar_VariableValue( "m_pitch" ) ) ); + + //trap_Cvar_SetValue( "m_filter", s_controls.smoothmouse.curvalue ); + //trap_Cvar_SetValue( "cl_run", s_controls.alwaysrun.curvalue ); + //trap_Cvar_SetValue( "cg_autoswitch", s_controls.autoswitch.curvalue ); + //trap_Cvar_SetValue( "sensitivity", s_controls.sensitivity.curvalue ); + //trap_Cvar_SetValue( "in_joystick", s_controls.joyenable.curvalue ); + //trap_Cvar_SetValue( "joy_threshold", s_controls.joythreshold.curvalue ); + //trap_Cvar_SetValue( "cl_freelook", s_controls.freelook.curvalue ); +#if !defined( __MACOS__ ) + DC->executeText( EXEC_APPEND, "in_restart\n" ); +#endif + //trap_Cmd_ExecuteText( EXEC_APPEND, "in_restart\n" ); +} + +/* +================= +Controls_SetDefaults +================= +*/ +void Controls_SetDefaults( void ) { + int i; + + // iterate each command, set its default binding + for ( i = 0; i < g_bindCount; i++ ) + { + g_bindings[i].bind1 = g_bindings[i].defaultbind1; + g_bindings[i].bind2 = g_bindings[i].defaultbind2; + } + + //s_controls.invertmouse.curvalue = Controls_GetCvarDefault( "m_pitch" ) < 0; + //s_controls.smoothmouse.curvalue = Controls_GetCvarDefault( "m_filter" ); + //s_controls.alwaysrun.curvalue = Controls_GetCvarDefault( "cl_run" ); + //s_controls.autoswitch.curvalue = Controls_GetCvarDefault( "cg_autoswitch" ); + //s_controls.sensitivity.curvalue = Controls_GetCvarDefault( "sensitivity" ); + //s_controls.joyenable.curvalue = Controls_GetCvarDefault( "in_joystick" ); + //s_controls.joythreshold.curvalue = Controls_GetCvarDefault( "joy_threshold" ); + //s_controls.freelook.curvalue = Controls_GetCvarDefault( "cl_freelook" ); +} + +int BindingIDFromName( const char *name ) { + int i; + for ( i = 0; i < g_bindCount; i++ ) + { + if ( Q_stricmp( name, g_bindings[i].command ) == 0 ) { + return i; + } + } + return -1; +} + +char g_nameBind1[32]; +char g_nameBind2[32]; + +char* BindingFromName( const char *cvar ) { + int i, b1, b2; + + // iterate each command, set its default binding + for ( i = 0; i < g_bindCount; i++ ) + { + if ( Q_stricmp( cvar, g_bindings[i].command ) == 0 ) { + b1 = g_bindings[i].bind1; + if ( b1 == -1 ) { + break; + } + DC->keynumToStringBuf( b1, g_nameBind1, 32 ); + Q_strupr( g_nameBind1 ); + + b2 = g_bindings[i].bind2; + if ( b2 != -1 ) { + DC->keynumToStringBuf( b2, g_nameBind2, 32 ); + Q_strupr( g_nameBind2 ); + strcat( g_nameBind1, DC->translateString( " or " ) ); + strcat( g_nameBind1, g_nameBind2 ); + } + return g_nameBind1; // NERVE - SMF + } + } + strcpy( g_nameBind1, "???" ); + return g_nameBind1; // NERVE - SMF +} + +void Item_Slider_Paint( itemDef_t *item ) { + vec4_t newColor, lowLight; + float x, y, value; + menuDef_t *parent = (menuDef_t*)item->parent; + + value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0; + + if ( item->window.flags & WINDOW_HASFOCUS ) { + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor( parent->focusColor,lowLight,newColor,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } else { + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + } + + y = item->window.rect.y; + if ( item->text ) { + Item_Text_Paint( item ); + x = item->textRect.x + item->textRect.w + 8; + } else { + x = item->window.rect.x; + } + DC->setColor( newColor ); + DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT, DC->Assets.sliderBar ); + + x = Item_Slider_ThumbPosition( item ); + DC->drawHandlePic( x - ( SLIDER_THUMB_WIDTH / 2 ), y - 2, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT, DC->Assets.sliderThumb ); +} + +void Item_Bind_Paint( itemDef_t *item ) { + vec4_t newColor, lowLight; + float value; + int maxChars = 0; + menuDef_t *parent = (menuDef_t*)item->parent; + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + if ( editPtr ) { + maxChars = editPtr->maxPaintChars; + } + + value = ( item->cvar ) ? DC->getCVarValue( item->cvar ) : 0; + + if ( item->window.flags & WINDOW_HASFOCUS ) { + if ( g_bindItem == item ) { + lowLight[0] = 0.8f * 1.0f; + lowLight[1] = 0.8f * 0.0f; + lowLight[2] = 0.8f * 0.0f; + lowLight[3] = 0.8f * 1.0f; + } else { + lowLight[0] = 0.8f * parent->focusColor[0]; + lowLight[1] = 0.8f * parent->focusColor[1]; + lowLight[2] = 0.8f * parent->focusColor[2]; + lowLight[3] = 0.8f * parent->focusColor[3]; + } + LerpColor( parent->focusColor,lowLight,newColor,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } else { + memcpy( &newColor, &item->window.foreColor, sizeof( vec4_t ) ); + } + + if ( item->text ) { + Item_Text_Paint( item ); + BindingFromName( item->cvar ); + DC->drawText( item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, g_nameBind1, 0, maxChars, item->textStyle ); + } else { + DC->drawText( item->textRect.x, item->textRect.y, item->textscale, newColor, ( value != 0 ) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle ); + } +} + +qboolean Display_KeyBindPending() { + return g_waitingForKey; +} + +qboolean Item_Bind_HandleKey( itemDef_t *item, int key, qboolean down ) { + int id; + int i; + + if ( Rect_ContainsPoint( &item->window.rect, DC->cursorx, DC->cursory ) && !g_waitingForKey ) { + if ( down && ( key == K_MOUSE1 || key == K_ENTER ) ) { + g_waitingForKey = qtrue; + g_bindItem = item; + } + return qtrue; + } else + { + if ( !g_waitingForKey || g_bindItem == NULL ) { + return qtrue; + } + + if ( key & K_CHAR_FLAG ) { + return qtrue; + } + + switch ( key ) + { + case K_ESCAPE: + g_waitingForKey = qfalse; + return qtrue; + + case K_BACKSPACE: + id = BindingIDFromName( item->cvar ); + if ( id != -1 ) { + g_bindings[id].bind1 = -1; + g_bindings[id].bind2 = -1; + } + Controls_SetConfig( qtrue ); + g_waitingForKey = qfalse; + g_bindItem = NULL; + return qtrue; + + case '`': + return qtrue; + } + } + + if ( key != -1 ) { + + for ( i = 0; i < g_bindCount; i++ ) + { + + if ( g_bindings[i].bind2 == key ) { + g_bindings[i].bind2 = -1; + } + + if ( g_bindings[i].bind1 == key ) { + g_bindings[i].bind1 = g_bindings[i].bind2; + g_bindings[i].bind2 = -1; + } + } + } + + + id = BindingIDFromName( item->cvar ); + + if ( id != -1 ) { + if ( key == -1 ) { + if ( g_bindings[id].bind1 != -1 ) { + DC->setBinding( g_bindings[id].bind1, "" ); + g_bindings[id].bind1 = -1; + } + if ( g_bindings[id].bind2 != -1 ) { + DC->setBinding( g_bindings[id].bind2, "" ); + g_bindings[id].bind2 = -1; + } + } else if ( g_bindings[id].bind1 == -1 ) { + g_bindings[id].bind1 = key; + } else if ( g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1 ) { + g_bindings[id].bind2 = key; + } else { + DC->setBinding( g_bindings[id].bind1, "" ); + DC->setBinding( g_bindings[id].bind2, "" ); + g_bindings[id].bind1 = key; + g_bindings[id].bind2 = -1; + } + } + + Controls_SetConfig( qtrue ); + g_waitingForKey = qfalse; + + return qtrue; +} + + + +void AdjustFrom640( float *x, float *y, float *w, float *h ) { + //*x = *x * DC->scale + DC->bias; + *x *= DC->xscale; + *y *= DC->yscale; + *w *= DC->xscale; + *h *= DC->yscale; +} + +void Item_Model_Paint( itemDef_t *item ) { + float x, y, w, h; //,xx; + refdef_t refdef; + qhandle_t hModel; + refEntity_t ent; + vec3_t mins, maxs, origin; + vec3_t angles; + modelDef_t *modelPtr = (modelDef_t*)item->typeData; + int backLerpWhole; + + if ( modelPtr == NULL ) { + return; + } + + if ( !item->asset ) { + return; + } + + hModel = item->asset; + + // setup the refdef + memset( &refdef, 0, sizeof( refdef ) ); + refdef.rdflags = RDF_NOWORLDMODEL; + AxisClear( refdef.viewaxis ); + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + w = item->window.rect.w - 2; + h = item->window.rect.h - 2; + + AdjustFrom640( &x, &y, &w, &h ); + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + DC->modelBounds( hModel, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the model nearly fills the box + if ( qtrue ) { + float len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + //origin[0] = len / tan(w/2); + } else { + origin[0] = item->textscale; + } + +#define NEWWAY +#ifdef NEWWAY + refdef.fov_x = ( modelPtr->fov_x ) ? modelPtr->fov_x : w; + refdef.fov_y = ( modelPtr->fov_y ) ? modelPtr->fov_y : h; +#else + refdef.fov_x = (int)( (float)refdef.width / 640.0f * 90.0f ); + xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); + refdef.fov_y = atan2( refdef.height, xx ); + refdef.fov_y *= ( 360 / M_PI ); +#endif + DC->clearScene(); + + refdef.time = DC->realTime; + + // add the model + + memset( &ent, 0, sizeof( ent ) ); + + //adjust = 5.0 * sin( (float)uis.realtime / 500 ); + //adjust = 360 % (int)((float)uis.realtime / 1000); + //VectorSet( angles, 0, 0, 1 ); + + // use item storage to track + if ( modelPtr->rotationSpeed ) { + if ( DC->realTime > item->window.nextTime ) { + item->window.nextTime = DC->realTime + modelPtr->rotationSpeed; + modelPtr->angle = (int)( modelPtr->angle + 1 ) % 360; + } + } + VectorSet( angles, 0, modelPtr->angle, 0 ); + AnglesToAxis( angles, ent.axis ); + + ent.hModel = hModel; + + + if ( modelPtr->frameTime ) { // don't advance on the first frame + modelPtr->backlerp += ( ( ( DC->realTime - modelPtr->frameTime ) / 1000.0f ) * (float)modelPtr->fps ); + } + + if ( modelPtr->backlerp > 1 ) { + backLerpWhole = floor( modelPtr->backlerp ); + + modelPtr->frame += ( backLerpWhole ); + if ( ( modelPtr->frame - modelPtr->startframe ) > modelPtr->numframes ) { + modelPtr->frame = modelPtr->startframe + modelPtr->frame % modelPtr->numframes; // todo: ignoring loopframes + + } + modelPtr->oldframe += ( backLerpWhole ); + if ( ( modelPtr->oldframe - modelPtr->startframe ) > modelPtr->numframes ) { + modelPtr->oldframe = modelPtr->startframe + modelPtr->oldframe % modelPtr->numframes; // todo: ignoring loopframes + + } + modelPtr->backlerp = modelPtr->backlerp - backLerpWhole; + } + + modelPtr->frameTime = DC->realTime; + + ent.frame = modelPtr->frame; + ent.oldframe = modelPtr->oldframe; + ent.backlerp = 1.0f - modelPtr->backlerp; + + VectorCopy( origin, ent.origin ); + VectorCopy( origin, ent.lightingOrigin ); + ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; + VectorCopy( ent.origin, ent.oldorigin ); + + DC->addRefEntityToScene( &ent ); + DC->renderScene( &refdef ); + +} + + +void Item_Image_Paint( itemDef_t *item ) { + if ( item == NULL ) { + return; + } + DC->drawHandlePic( item->window.rect.x + 1, item->window.rect.y + 1, item->window.rect.w - 2, item->window.rect.h - 2, item->asset ); +} + +void Item_ListBox_Paint( itemDef_t *item ) { + float x, y, size, count, i, thumb; + qhandle_t image; + qhandle_t optionalImage; + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + + // the listbox is horizontal or vertical and has a fixed size scroll bar going either direction + // elements are enumerated from the DC and either text or image handles are acquired from the DC as well + // textscale is used to size the text, textalignx and textaligny are used to size image elements + // there is no clipping available so only the last completely visible item is painted + count = DC->feederCount( item->special ); + // default is vertical if horizontal flag is not here + if ( item->window.flags & WINDOW_HORIZONTAL ) { + // draw scrollbar in bottom of the window + // bar + x = item->window.rect.x + 1; + y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE - 1; + DC->drawHandlePic( x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowLeft ); + x += SCROLLBAR_SIZE - 1; + size = item->window.rect.w - ( SCROLLBAR_SIZE * 2 ); + DC->drawHandlePic( x, y, size + 1, SCROLLBAR_SIZE, DC->Assets.scrollBar ); + x += size - 1; + DC->drawHandlePic( x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowRight ); + // thumb + thumb = Item_ListBox_ThumbDrawPosition( item ); //Item_ListBox_ThumbPosition(item); + if ( thumb > x - SCROLLBAR_SIZE - 1 ) { + thumb = x - SCROLLBAR_SIZE - 1; + } + DC->drawHandlePic( thumb, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb ); + // + listPtr->endPos = listPtr->startPos; + size = item->window.rect.w - 2; + // items + // size contains max available space + if ( listPtr->elementStyle == LISTBOX_IMAGE ) { + // fit = 0; + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + for ( i = listPtr->startPos; i < count; i++ ) { + // always draw at least one + // which may overdraw the box if it is too small for the element + image = DC->feederItemImage( item->special, i ); + if ( image ) { + DC->drawHandlePic( x + 1, y + 1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image ); + } + + if ( i == item->cursorPos ) { + DC->drawRect( x, y, listPtr->elementWidth - 1, listPtr->elementHeight - 1, item->window.borderSize, item->window.borderColor ); + } + + size -= listPtr->elementWidth; + if ( size < listPtr->elementWidth ) { + listPtr->drawPadding = size; //listPtr->elementWidth - size; + break; + } + x += listPtr->elementWidth; + listPtr->endPos++; + // fit++; + } + } else { + // + } + } else { + // draw scrollbar to right side of the window + x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE - 1; + y = item->window.rect.y + 1; + DC->drawHandlePic( x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowUp ); + y += SCROLLBAR_SIZE - 1; + + listPtr->endPos = listPtr->startPos; + size = item->window.rect.h - ( SCROLLBAR_SIZE * 2 ); + DC->drawHandlePic( x, y, SCROLLBAR_SIZE, size + 1, DC->Assets.scrollBar ); + y += size - 1; + DC->drawHandlePic( x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowDown ); + // thumb + thumb = Item_ListBox_ThumbDrawPosition( item ); //Item_ListBox_ThumbPosition(item); + if ( thumb > y - SCROLLBAR_SIZE - 1 ) { + thumb = y - SCROLLBAR_SIZE - 1; + } + DC->drawHandlePic( x, thumb, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb ); + + // adjust size for item painting + size = item->window.rect.h - 2; + if ( listPtr->elementStyle == LISTBOX_IMAGE ) { + // fit = 0; + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + for ( i = listPtr->startPos; i < count; i++ ) { + // always draw at least one + // which may overdraw the box if it is too small for the element + image = DC->feederItemImage( item->special, i ); + if ( image ) { + DC->drawHandlePic( x + 1, y + 1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image ); + } + + if ( i == item->cursorPos ) { + DC->drawRect( x, y, listPtr->elementWidth - 1, listPtr->elementHeight - 1, item->window.borderSize, item->window.borderColor ); + } + + listPtr->endPos++; + size -= listPtr->elementWidth; + if ( size < listPtr->elementHeight ) { + listPtr->drawPadding = listPtr->elementHeight - size; + break; + } + y += listPtr->elementHeight; + // fit++; + } + } else { + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + for ( i = listPtr->startPos; i < count; i++ ) { + const char *text; + // always draw at least one + // which may overdraw the box if it is too small for the element + + if ( listPtr->numColumns > 0 ) { + int j; + for ( j = 0; j < listPtr->numColumns; j++ ) { + text = DC->feederItemText( item->special, i, j, &optionalImage ); + if ( optionalImage >= 0 ) { + DC->drawHandlePic( x + 4 + listPtr->columnInfo[j].pos, y - 1 + listPtr->elementHeight / 2, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage ); + } else if ( text ) { + DC->drawText( x + 4 + listPtr->columnInfo[j].pos + item->textalignx, + y + listPtr->elementHeight + item->textaligny, item->textscale, item->window.foreColor, text, 0, listPtr->columnInfo[j].maxChars, item->textStyle ); + } + } + } else { + text = DC->feederItemText( item->special, i, 0, &optionalImage ); + if ( optionalImage >= 0 ) { + //DC->drawHandlePic(x + 4 + listPtr->elementHeight, y, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage); + } else if ( text ) { + DC->drawText( x + 4, y + listPtr->elementHeight, item->textscale, item->window.foreColor, text, 0, 0, item->textStyle ); + } + } + + if ( i == item->cursorPos ) { + DC->fillRect( x, y, item->window.rect.w - SCROLLBAR_SIZE - 4, listPtr->elementHeight - 1, item->window.outlineColor ); + } + + size -= listPtr->elementHeight; + if ( size < listPtr->elementHeight ) { + listPtr->drawPadding = listPtr->elementHeight - size; + break; + } + listPtr->endPos++; + y += listPtr->elementHeight; + // fit++; + } + } + } +} + + +void Item_OwnerDraw_Paint( itemDef_t *item ) { + menuDef_t *parent; + + if ( item == NULL ) { + return; + } + + parent = (menuDef_t*)item->parent; + + if ( DC->ownerDrawItem ) { + vec4_t color, lowLight; + menuDef_t *parent = (menuDef_t*)item->parent; + Fade( &item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount ); + memcpy( &color, &item->window.foreColor, sizeof( color ) ); + if ( item->numColors > 0 && DC->getValue ) { + // if the value is within one of the ranges then set color to that, otherwise leave at default + int i; + float f = DC->getValue( item->window.ownerDraw, item->colorRangeType ); + for ( i = 0; i < item->numColors; i++ ) { + if ( f >= item->colorRanges[i].low && f <= item->colorRanges[i].high ) { + memcpy( &color, &item->colorRanges[i].color, sizeof( color ) ); + break; + } + } + } + + // take hudalpha into account unless explicitly ignoring + if ( !( item->window.flags & WINDOW_IGNORE_HUDALPHA ) ) { + color[3] *= DC->getCVarValue( "cg_hudAlpha" );; + } + + + if ( item->window.flags & WINDOW_HASFOCUS ) { + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor( parent->focusColor,lowLight,color,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } else if ( item->textStyle == ITEM_TEXTSTYLE_BLINK && !( ( DC->realTime / BLINK_DIVISOR ) & 1 ) ) { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor( item->window.foreColor,lowLight,color,0.5 + 0.5 * sin( DC->realTime / PULSE_DIVISOR ) ); + } + + if ( item->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( item, CVAR_ENABLE ) ) { + memcpy( color, parent->disableColor, sizeof( vec4_t ) ); + } + + if ( item->text ) { + Item_Text_Paint( item ); + if ( item->text[0] ) { + // +8 is an offset kludge to properly align owner draw items that have text combined with them + DC->ownerDrawItem( item->textRect.x + item->textRect.w + 8, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); + } else { + DC->ownerDrawItem( item->textRect.x + item->textRect.w, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); + } + } else { + DC->ownerDrawItem( item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, item->textalignx, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle ); + } + } +} + + +void Item_Paint( itemDef_t *item ) { + vec4_t red; + menuDef_t *parent = (menuDef_t*)item->parent; + red[0] = red[3] = 1; + red[1] = red[2] = 0; + + if ( item == NULL ) { + return; + } + + // NERVE - SMF + if ( DC->textFont ) { + DC->textFont( item->font ); + } + + if ( item->window.flags & WINDOW_ORBITING ) { + if ( DC->realTime > item->window.nextTime ) { + float rx, ry, a, c, s, w, h; + + item->window.nextTime = DC->realTime + item->window.offsetTime; + // translate + w = item->window.rectClient.w / 2; + h = item->window.rectClient.h / 2; + rx = item->window.rectClient.x + w - item->window.rectEffects.x; + ry = item->window.rectClient.y + h - item->window.rectEffects.y; + a = 3 * M_PI / 180; + c = cos( a ); + s = sin( a ); + item->window.rectClient.x = ( rx * c - ry * s ) + item->window.rectEffects.x - w; + item->window.rectClient.y = ( rx * s + ry * c ) + item->window.rectEffects.y - h; + Item_UpdatePosition( item ); + + } + } + + + if ( item->window.flags & WINDOW_INTRANSITION ) { + if ( DC->realTime > item->window.nextTime ) { + int done = 0; + item->window.nextTime = DC->realTime + item->window.offsetTime; + // transition the x,y + if ( item->window.rectClient.x == item->window.rectEffects.x ) { + done++; + } else { + if ( item->window.rectClient.x < item->window.rectEffects.x ) { + item->window.rectClient.x += item->window.rectEffects2.x; + if ( item->window.rectClient.x > item->window.rectEffects.x ) { + item->window.rectClient.x = item->window.rectEffects.x; + done++; + } + } else { + item->window.rectClient.x -= item->window.rectEffects2.x; + if ( item->window.rectClient.x < item->window.rectEffects.x ) { + item->window.rectClient.x = item->window.rectEffects.x; + done++; + } + } + } + if ( item->window.rectClient.y == item->window.rectEffects.y ) { + done++; + } else { + if ( item->window.rectClient.y < item->window.rectEffects.y ) { + item->window.rectClient.y += item->window.rectEffects2.y; + if ( item->window.rectClient.y > item->window.rectEffects.y ) { + item->window.rectClient.y = item->window.rectEffects.y; + done++; + } + } else { + item->window.rectClient.y -= item->window.rectEffects2.y; + if ( item->window.rectClient.y < item->window.rectEffects.y ) { + item->window.rectClient.y = item->window.rectEffects.y; + done++; + } + } + } + if ( item->window.rectClient.w == item->window.rectEffects.w ) { + done++; + } else { + if ( item->window.rectClient.w < item->window.rectEffects.w ) { + item->window.rectClient.w += item->window.rectEffects2.w; + if ( item->window.rectClient.w > item->window.rectEffects.w ) { + item->window.rectClient.w = item->window.rectEffects.w; + done++; + } + } else { + item->window.rectClient.w -= item->window.rectEffects2.w; + if ( item->window.rectClient.w < item->window.rectEffects.w ) { + item->window.rectClient.w = item->window.rectEffects.w; + done++; + } + } + } + if ( item->window.rectClient.h == item->window.rectEffects.h ) { + done++; + } else { + if ( item->window.rectClient.h < item->window.rectEffects.h ) { + item->window.rectClient.h += item->window.rectEffects2.h; + if ( item->window.rectClient.h > item->window.rectEffects.h ) { + item->window.rectClient.h = item->window.rectEffects.h; + done++; + } + } else { + item->window.rectClient.h -= item->window.rectEffects2.h; + if ( item->window.rectClient.h < item->window.rectEffects.h ) { + item->window.rectClient.h = item->window.rectEffects.h; + done++; + } + } + } + + Item_UpdatePosition( item ); + + if ( done == 4 ) { + item->window.flags &= ~WINDOW_INTRANSITION; + } + + } + } + + if ( item->window.ownerDrawFlags && DC->ownerDrawVisible ) { + if ( !DC->ownerDrawVisible( item->window.ownerDrawFlags ) ) { + item->window.flags &= ~WINDOW_VISIBLE; + } else { + item->window.flags |= WINDOW_VISIBLE; + } + } + + if ( item->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) ) { + if ( !Item_EnableShowViaCvar( item, CVAR_SHOW ) ) { + return; + } + } + + if ( item->window.flags & WINDOW_TIMEDVISIBLE ) { + } + + if ( !( item->window.flags & WINDOW_VISIBLE ) ) { + return; + } + + // paint the rect first.. + Window_Paint( &item->window, parent->fadeAmount, parent->fadeClamp, parent->fadeCycle ); + + if ( debugMode ) { + vec4_t color; + rectDef_t *r = Item_CorrectedTextRect( item ); + color[1] = color[3] = 1; + color[0] = color[2] = 0; + DC->drawRect( r->x, r->y, r->w, r->h, 1, color ); + } + + //DC->drawRect(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, 1, red); + + switch ( item->type ) { + case ITEM_TYPE_OWNERDRAW: + Item_OwnerDraw_Paint( item ); + break; + case ITEM_TYPE_TEXT: + case ITEM_TYPE_BUTTON: + Item_Text_Paint( item ); + break; + case ITEM_TYPE_RADIOBUTTON: + break; + case ITEM_TYPE_CHECKBOX: + break; + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + Item_TextField_Paint( item ); + break; + case ITEM_TYPE_COMBO: + break; + case ITEM_TYPE_LISTBOX: + Item_ListBox_Paint( item ); + break; +// case ITEM_TYPE_IMAGE: +// Item_Image_Paint(item); +// break; + case ITEM_TYPE_MENUMODEL: + Item_Model_Paint( item ); + break; + case ITEM_TYPE_MODEL: + Item_Model_Paint( item ); + break; + case ITEM_TYPE_YESNO: + Item_YesNo_Paint( item ); + break; + case ITEM_TYPE_MULTI: + Item_Multi_Paint( item ); + break; + case ITEM_TYPE_BIND: + Item_Bind_Paint( item ); + break; + case ITEM_TYPE_SLIDER: + Item_Slider_Paint( item ); + break; + default: + break; + } +} + +void Menu_Init( menuDef_t *menu ) { + memset( menu, 0, sizeof( menuDef_t ) ); + menu->cursorItem = -1; + menu->fadeAmount = DC->Assets.fadeAmount; + menu->fadeClamp = DC->Assets.fadeClamp; + menu->fadeCycle = DC->Assets.fadeCycle; + Window_Init( &menu->window ); +} + +itemDef_t *Menu_GetFocusedItem( menuDef_t *menu ) { + int i; + if ( menu ) { + for ( i = 0; i < menu->itemCount; i++ ) { + if ( menu->items[i]->window.flags & WINDOW_HASFOCUS ) { + return menu->items[i]; + } + } + } + return NULL; +} + +menuDef_t *Menu_GetFocused() { + int i; + for ( i = 0; i < menuCount; i++ ) { + if ( Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE ) { + return &Menus[i]; + } + } + return NULL; +} + +void Menu_ScrollFeeder( menuDef_t *menu, int feeder, qboolean down ) { + if ( menu ) { + int i; + for ( i = 0; i < menu->itemCount; i++ ) { + if ( menu->items[i]->special == feeder ) { + Item_ListBox_HandleKey( menu->items[i], ( down ) ? K_DOWNARROW : K_UPARROW, qtrue, qtrue ); + return; + } + } + } +} + + + +void Menu_SetFeederSelection( menuDef_t *menu, int feeder, int index, const char *name ) { + if ( menu == NULL ) { + if ( name == NULL ) { + menu = Menu_GetFocused(); + } else { + menu = Menus_FindByName( name ); + } + } + + if ( menu ) { + int i; + for ( i = 0; i < menu->itemCount; i++ ) { + if ( menu->items[i]->special == feeder ) { + if ( index == 0 ) { + listBoxDef_t *listPtr = (listBoxDef_t*)menu->items[i]->typeData; + listPtr->cursorPos = 0; + listPtr->startPos = 0; + } + menu->items[i]->cursorPos = index; + DC->feederSelection( menu->items[i]->special, menu->items[i]->cursorPos ); + return; + } + } + } +} + +qboolean Menus_AnyFullScreenVisible() { + int i; + for ( i = 0; i < menuCount; i++ ) { + if ( Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen ) { + return qtrue; + } + } + return qfalse; +} + +menuDef_t *Menus_ActivateByName( const char *p, qboolean modalStack ) { + int i; + menuDef_t *m = NULL; + menuDef_t *focus = Menu_GetFocused(); + for ( i = 0; i < menuCount; i++ ) { + if ( Q_stricmp( Menus[i].window.name, p ) == 0 ) { + m = &Menus[i]; + Menus_Activate( m ); + if ( modalStack && m->window.flags & WINDOW_MODAL ) { + if ( modalMenuCount >= MAX_MODAL_MENUS ) { + Com_Error( ERR_DROP, "MAX_MODAL_MENUS exceeded\n" ); + } + modalMenuStack[modalMenuCount++] = focus; + } + } else { + Menus[i].window.flags &= ~WINDOW_HASFOCUS; + } + } + Display_CloseCinematics(); + return m; +} + + +void Item_Init( itemDef_t *item ) { + memset( item, 0, sizeof( itemDef_t ) ); + item->textscale = 0.55f; + Window_Init( &item->window ); +} + +void Menu_HandleMouseMove( menuDef_t *menu, float x, float y ) { + int i, pass; + qboolean focusSet = qfalse; + + itemDef_t *overItem; + if ( menu == NULL ) { + return; + } + + if ( !( menu->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) ) { + return; + } + + if ( itemCapture ) { + if ( itemCapture->type == ITEM_TYPE_LISTBOX ) { + // NERVE - SMF - lose capture if out of client rect + if ( !Rect_ContainsPoint( &itemCapture->window.rect, x, y ) ) { + Item_StopCapture( itemCapture ); + itemCapture = NULL; + captureFunc = NULL; + captureData = NULL; + } + + } + //Item_MouseMove(itemCapture, x, y); + return; + } + + if ( g_waitingForKey || g_editingField ) { + return; + } + + // FIXME: this is the whole issue of focus vs. mouse over.. + // need a better overall solution as i don't like going through everything twice + for ( pass = 0; pass < 2; pass++ ) { + for ( i = 0; i < menu->itemCount; i++ ) { + // turn off focus each item + // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + + if ( !( menu->items[i]->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) ) { + continue; + } + + // items can be enabled and disabled based on cvars + if ( menu->items[i]->cvarFlags & ( CVAR_ENABLE | CVAR_DISABLE ) && !Item_EnableShowViaCvar( menu->items[i], CVAR_ENABLE ) ) { + continue; + } + + if ( menu->items[i]->cvarFlags & ( CVAR_SHOW | CVAR_HIDE ) && !Item_EnableShowViaCvar( menu->items[i], CVAR_SHOW ) ) { + continue; + } + + + + if ( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) ) { + if ( pass == 1 ) { + overItem = menu->items[i]; + if ( overItem->type == ITEM_TYPE_TEXT && overItem->text ) { + if ( !Rect_ContainsPoint( Item_CorrectedTextRect( overItem ), x, y ) ) { + continue; + } + } + // if we are over an item + if ( IsVisible( overItem->window.flags ) ) { + // different one + Item_MouseEnter( overItem, x, y ); + // Item_SetMouseOver(overItem, qtrue); + + // if item is not a decoration see if it can take focus + if ( !focusSet ) { + focusSet = Item_SetFocus( overItem, x, y ); + } + } + } + } else if ( menu->items[i]->window.flags & WINDOW_MOUSEOVER ) { + Item_MouseLeave( menu->items[i] ); + Item_SetMouseOver( menu->items[i], qfalse ); + } + } + } + +} + +void Menu_Paint( menuDef_t *menu, qboolean forcePaint ) { + int i; + + if ( menu == NULL ) { + return; + } + + if ( !( menu->window.flags & WINDOW_VISIBLE ) && !forcePaint ) { + return; + } + + if ( menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible( menu->window.ownerDrawFlags ) ) { + return; + } + + if ( forcePaint ) { + menu->window.flags |= WINDOW_FORCED; + } + + // draw the background if necessary + if ( menu->fullScreen ) { + // implies a background shader + // FIXME: make sure we have a default shader if fullscreen is set with no background + DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background ); + } else if ( menu->window.background ) { + // this allows a background shader without being full screen + //UI_DrawHandlePic(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, menu->backgroundShader); + } + + // paint the background and or border + Window_Paint( &menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle ); + + for ( i = 0; i < menu->itemCount; i++ ) { + Item_Paint( menu->items[i] ); + } + + if ( debugMode ) { + vec4_t color; + color[0] = color[2] = color[3] = 1; + color[1] = 0; + DC->drawRect( menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color ); + } +} + +/* +=============== +Item_ValidateTypeData +=============== +*/ +void Item_ValidateTypeData( itemDef_t *item ) { + if ( item->typeData ) { + return; + } + + if ( item->type == ITEM_TYPE_LISTBOX ) { + item->typeData = UI_Alloc( sizeof( listBoxDef_t ) ); + memset( item->typeData, 0, sizeof( listBoxDef_t ) ); + } else if ( item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD || item->type == ITEM_TYPE_YESNO || item->type == ITEM_TYPE_BIND || item->type == ITEM_TYPE_SLIDER || item->type == ITEM_TYPE_TEXT ) { + item->typeData = UI_Alloc( sizeof( editFieldDef_t ) ); + memset( item->typeData, 0, sizeof( editFieldDef_t ) ); + if ( item->type == ITEM_TYPE_EDITFIELD ) { + if ( !( (editFieldDef_t *) item->typeData )->maxPaintChars ) { + ( (editFieldDef_t *) item->typeData )->maxPaintChars = MAX_EDITFIELD; + } + } + } else if ( item->type == ITEM_TYPE_MULTI ) { + item->typeData = UI_Alloc( sizeof( multiDef_t ) ); + } else if ( item->type == ITEM_TYPE_MODEL ) { + item->typeData = UI_Alloc( sizeof( modelDef_t ) ); + } else if ( item->type == ITEM_TYPE_MENUMODEL ) { + item->typeData = UI_Alloc( sizeof( modelDef_t ) ); + } +} + +/* +=============== +Keyword Hash +=============== +*/ + +#define KEYWORDHASH_SIZE 512 + +typedef struct keywordHash_s +{ + char *keyword; + qboolean ( *func )( itemDef_t *item, int handle ); + struct keywordHash_s *next; +} keywordHash_t; + +int KeywordHash_Key( char *keyword ) { + int register hash, i; + + hash = 0; + for ( i = 0; keyword[i] != '\0'; i++ ) { + if ( keyword[i] >= 'A' && keyword[i] <= 'Z' ) { + hash += ( keyword[i] + ( 'a' - 'A' ) ) * ( 119 + i ); + } else { + hash += keyword[i] * ( 119 + i ); + } + } + hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) ) & ( KEYWORDHASH_SIZE - 1 ); + return hash; +} + +void KeywordHash_Add( keywordHash_t *table[], keywordHash_t *key ) { + int hash; + + hash = KeywordHash_Key( key->keyword ); +/* + if (table[hash]) { + int collision = qtrue; + } +*/ + key->next = table[hash]; + table[hash] = key; +} + +keywordHash_t *KeywordHash_Find( keywordHash_t *table[], char *keyword ) { + keywordHash_t *key; + int hash; + + hash = KeywordHash_Key( keyword ); + for ( key = table[hash]; key; key = key->next ) { + if ( !Q_stricmp( key->keyword, keyword ) ) { + return key; + } + } + return NULL; +} + +/* +=============== +Item Keyword Parse functions +=============== +*/ + +// name +qboolean ItemParse_name( itemDef_t *item, int handle ) { + if ( !PC_String_Parse( handle, &item->window.name ) ) { + return qfalse; + } + return qtrue; +} + +// name +qboolean ItemParse_focusSound( itemDef_t *item, int handle ) { + const char *temp; + if ( !PC_String_Parse( handle, &temp ) ) { + return qfalse; + } + item->focusSound = DC->registerSound( temp ); + return qtrue; +} + + +// text +qboolean ItemParse_text( itemDef_t *item, int handle ) { + if ( !PC_String_Parse( handle, &item->text ) ) { + return qfalse; + } + return qtrue; +} + +//----(SA) added + +// textfile +// read an external textfile into item->text +qboolean ItemParse_textfile( itemDef_t *item, int handle ) { + const char *newtext; + pc_token_t token; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + + newtext = DC->fileText( token.string ); + item->text = String_Alloc( newtext ); + + return qtrue; +} +//----(SA) + +// group +qboolean ItemParse_group( itemDef_t *item, int handle ) { + if ( !PC_String_Parse( handle, &item->window.group ) ) { + return qfalse; + } + return qtrue; +} + + +// asset_model +qboolean ItemParse_asset_model( itemDef_t *item, int handle ) { + const char *temp; + modelDef_t *modelPtr; + Item_ValidateTypeData( item ); + modelPtr = (modelDef_t*)item->typeData; + + if ( !PC_String_Parse( handle, &temp ) ) { + return qfalse; + } + if ( !( item->asset ) ) { + item->asset = DC->registerModel( temp ); +// modelPtr->angle = rand() % 360; + } + return qtrue; +} + +// asset_shader +qboolean ItemParse_asset_shader( itemDef_t *item, int handle ) { + const char *temp; + + if ( !PC_String_Parse( handle, &temp ) ) { + return qfalse; + } + item->asset = DC->registerShaderNoMip( temp ); + return qtrue; +} + +// model_origin +qboolean ItemParse_model_origin( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData( item ); + modelPtr = (modelDef_t*)item->typeData; + + if ( PC_Float_Parse( handle, &modelPtr->origin[0] ) ) { + if ( PC_Float_Parse( handle, &modelPtr->origin[1] ) ) { + if ( PC_Float_Parse( handle, &modelPtr->origin[2] ) ) { + return qtrue; + } + } + } + return qfalse; +} + +// model_fovx +qboolean ItemParse_model_fovx( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData( item ); + modelPtr = (modelDef_t*)item->typeData; + + if ( !PC_Float_Parse( handle, &modelPtr->fov_x ) ) { + return qfalse; + } + return qtrue; +} + +// model_fovy +qboolean ItemParse_model_fovy( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData( item ); + modelPtr = (modelDef_t*)item->typeData; + + if ( !PC_Float_Parse( handle, &modelPtr->fov_y ) ) { + return qfalse; + } + return qtrue; +} + +// model_rotation +qboolean ItemParse_model_rotation( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData( item ); + modelPtr = (modelDef_t*)item->typeData; + + if ( !PC_Int_Parse( handle, &modelPtr->rotationSpeed ) ) { + return qfalse; + } + return qtrue; +} + +// model_angle +qboolean ItemParse_model_angle( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData( item ); + modelPtr = (modelDef_t*)item->typeData; + + if ( !PC_Int_Parse( handle, &modelPtr->angle ) ) { + return qfalse; + } + return qtrue; +} + +// model_animplay +qboolean ItemParse_model_animplay( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData( item ); + modelPtr = (modelDef_t*)item->typeData; + + modelPtr->animated = 1; + + if ( !PC_Int_Parse( handle, &modelPtr->startframe ) ) { + return qfalse; + } + if ( !PC_Int_Parse( handle, &modelPtr->numframes ) ) { + return qfalse; + } + if ( !PC_Int_Parse( handle, &modelPtr->loopframes ) ) { + return qfalse; + } + if ( !PC_Int_Parse( handle, &modelPtr->fps ) ) { + return qfalse; + } + + modelPtr->frame = modelPtr->startframe + 1; + modelPtr->oldframe = modelPtr->startframe; + modelPtr->backlerp = 0.0f; + modelPtr->frameTime = DC->realTime; + return qtrue; +} + + +// rect +qboolean ItemParse_rect( itemDef_t *item, int handle ) { + if ( !PC_Rect_Parse( handle, &item->window.rectClient ) ) { + return qfalse; + } + return qtrue; +} + +// NERVE - SMF +// origin +qboolean ItemParse_origin( itemDef_t *item, int handle ) { + int x, y; + + if ( !PC_Int_Parse( handle, &x ) ) { + return qfalse; + } + if ( !PC_Int_Parse( handle, &y ) ) { + return qfalse; + } + + item->window.rectClient.x += x; + item->window.rectClient.y += y; + + return qtrue; +} +// -NERVE - SMF + +// style +qboolean ItemParse_style( itemDef_t *item, int handle ) { + if ( !PC_Int_Parse( handle, &item->window.style ) ) { + return qfalse; + } + return qtrue; +} + +// decoration +qboolean ItemParse_decoration( itemDef_t *item, int handle ) { + item->window.flags |= WINDOW_DECORATION; + return qtrue; +} + +// notselectable +qboolean ItemParse_notselectable( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + Item_ValidateTypeData( item ); + listPtr = (listBoxDef_t*)item->typeData; + if ( item->type == ITEM_TYPE_LISTBOX && listPtr ) { + listPtr->notselectable = qtrue; + } + return qtrue; +} + +// manually wrapped +qboolean ItemParse_wrapped( itemDef_t *item, int handle ) { + item->window.flags |= WINDOW_WRAPPED; + return qtrue; +} + +// auto wrapped +qboolean ItemParse_autowrapped( itemDef_t *item, int handle ) { + item->window.flags |= WINDOW_AUTOWRAPPED; + return qtrue; +} + + +// horizontalscroll +qboolean ItemParse_horizontalscroll( itemDef_t *item, int handle ) { + item->window.flags |= WINDOW_HORIZONTAL; + return qtrue; +} + +// type +qboolean ItemParse_type( itemDef_t *item, int handle ) { + if ( !PC_Int_Parse( handle, &item->type ) ) { + return qfalse; + } + Item_ValidateTypeData( item ); + return qtrue; +} + +// elementwidth, used for listbox image elements +// uses textalignx for storage +qboolean ItemParse_elementwidth( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + + Item_ValidateTypeData( item ); + listPtr = (listBoxDef_t*)item->typeData; + if ( !PC_Float_Parse( handle, &listPtr->elementWidth ) ) { + return qfalse; + } + return qtrue; +} + +// elementheight, used for listbox image elements +// uses textaligny for storage +qboolean ItemParse_elementheight( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + + Item_ValidateTypeData( item ); + listPtr = (listBoxDef_t*)item->typeData; + if ( !PC_Float_Parse( handle, &listPtr->elementHeight ) ) { + return qfalse; + } + return qtrue; +} + +// feeder +qboolean ItemParse_feeder( itemDef_t *item, int handle ) { + if ( !PC_Float_Parse( handle, &item->special ) ) { + return qfalse; + } + return qtrue; +} + +// elementtype, used to specify what type of elements a listbox contains +// uses textstyle for storage +qboolean ItemParse_elementtype( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + + Item_ValidateTypeData( item ); + if ( !item->typeData ) { + return qfalse; + } + listPtr = (listBoxDef_t*)item->typeData; + if ( !PC_Int_Parse( handle, &listPtr->elementStyle ) ) { + return qfalse; + } + return qtrue; +} + +// columns sets a number of columns and an x pos and width per.. +qboolean ItemParse_columns( itemDef_t *item, int handle ) { + int num, i; + listBoxDef_t *listPtr; + + Item_ValidateTypeData( item ); + if ( !item->typeData ) { + return qfalse; + } + listPtr = (listBoxDef_t*)item->typeData; + if ( PC_Int_Parse( handle, &num ) ) { + if ( num > MAX_LB_COLUMNS ) { + num = MAX_LB_COLUMNS; + } + listPtr->numColumns = num; + for ( i = 0; i < num; i++ ) { + int pos, width, maxChars; + + if ( PC_Int_Parse( handle, &pos ) && PC_Int_Parse( handle, &width ) && PC_Int_Parse( handle, &maxChars ) ) { + listPtr->columnInfo[i].pos = pos; + listPtr->columnInfo[i].width = width; + listPtr->columnInfo[i].maxChars = maxChars; + } else { + return qfalse; + } + } + } else { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_border( itemDef_t *item, int handle ) { + if ( !PC_Int_Parse( handle, &item->window.border ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_bordersize( itemDef_t *item, int handle ) { + if ( !PC_Float_Parse( handle, &item->window.borderSize ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_visible( itemDef_t *item, int handle ) { + int i; + + if ( !PC_Int_Parse( handle, &i ) ) { + return qfalse; + } + if ( i ) { + item->window.flags |= WINDOW_VISIBLE; + } + return qtrue; +} + +qboolean ItemParse_ownerdraw( itemDef_t *item, int handle ) { + if ( !PC_Int_Parse( handle, &item->window.ownerDraw ) ) { + return qfalse; + } + item->type = ITEM_TYPE_OWNERDRAW; + return qtrue; +} + +qboolean ItemParse_align( itemDef_t *item, int handle ) { + if ( !PC_Int_Parse( handle, &item->alignment ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_textalign( itemDef_t *item, int handle ) { + if ( !PC_Int_Parse( handle, &item->textalignment ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_textalignx( itemDef_t *item, int handle ) { + if ( !PC_Float_Parse( handle, &item->textalignx ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_textaligny( itemDef_t *item, int handle ) { + if ( !PC_Float_Parse( handle, &item->textaligny ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_textscale( itemDef_t *item, int handle ) { + if ( !PC_Float_Parse( handle, &item->textscale ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_textstyle( itemDef_t *item, int handle ) { + if ( !PC_Int_Parse( handle, &item->textStyle ) ) { + return qfalse; + } + return qtrue; +} + +//----(SA) added for forcing a font for a given item +qboolean ItemParse_textfont( itemDef_t *item, int handle ) { + if ( !PC_Int_Parse( handle, &item->font ) ) { + return qfalse; + } + return qtrue; +} +//----(SA) end + +qboolean ItemParse_backcolor( itemDef_t *item, int handle ) { + int i; + float f; + + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + item->window.backColor[i] = f; + } + return qtrue; +} + +qboolean ItemParse_forecolor( itemDef_t *item, int handle ) { + int i; + float f; + + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + item->window.foreColor[i] = f; + item->window.flags |= WINDOW_FORECOLORSET; + } + return qtrue; +} + +qboolean ItemParse_bordercolor( itemDef_t *item, int handle ) { + int i; + float f; + + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + item->window.borderColor[i] = f; + } + return qtrue; +} + +qboolean ItemParse_outlinecolor( itemDef_t *item, int handle ) { + if ( !PC_Color_Parse( handle, &item->window.outlineColor ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_background( itemDef_t *item, int handle ) { + const char *temp; + + if ( !PC_String_Parse( handle, &temp ) ) { + return qfalse; + } + item->window.background = DC->registerShaderNoMip( temp ); + return qtrue; +} + +qboolean ItemParse_cinematic( itemDef_t *item, int handle ) { + if ( !PC_String_Parse( handle, &item->window.cinematicName ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_doubleClick( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + + Item_ValidateTypeData( item ); + if ( !item->typeData ) { + return qfalse; + } + + listPtr = (listBoxDef_t*)item->typeData; + + if ( !PC_Script_Parse( handle, &listPtr->doubleClick ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_onFocus( itemDef_t *item, int handle ) { + if ( !PC_Script_Parse( handle, &item->onFocus ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_leaveFocus( itemDef_t *item, int handle ) { + if ( !PC_Script_Parse( handle, &item->leaveFocus ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_mouseEnter( itemDef_t *item, int handle ) { + if ( !PC_Script_Parse( handle, &item->mouseEnter ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_mouseExit( itemDef_t *item, int handle ) { + if ( !PC_Script_Parse( handle, &item->mouseExit ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle ) { + if ( !PC_Script_Parse( handle, &item->mouseEnterText ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_mouseExitText( itemDef_t *item, int handle ) { + if ( !PC_Script_Parse( handle, &item->mouseExitText ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_action( itemDef_t *item, int handle ) { + if ( !PC_Script_Parse( handle, &item->action ) ) { + return qfalse; + } + return qtrue; +} + +// NERVE - SMF +qboolean ItemParse_accept( itemDef_t *item, int handle ) { + if ( !PC_Script_Parse( handle, &item->onAccept ) ) { + return qfalse; + } + return qtrue; +} +// -NERVE - SMF + +qboolean ItemParse_special( itemDef_t *item, int handle ) { + if ( !PC_Float_Parse( handle, &item->special ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_cvarTest( itemDef_t *item, int handle ) { + if ( !PC_String_Parse( handle, &item->cvarTest ) ) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_cvar( itemDef_t *item, int handle ) { + editFieldDef_t *editPtr; + + Item_ValidateTypeData( item ); + if ( !PC_String_Parse( handle, &item->cvar ) ) { + return qfalse; + } + if ( item->typeData ) { + editPtr = (editFieldDef_t*)item->typeData; + editPtr->minVal = -1; + editPtr->maxVal = -1; + editPtr->defVal = -1; + } + return qtrue; +} + +qboolean ItemParse_maxChars( itemDef_t *item, int handle ) { + editFieldDef_t *editPtr; + int maxChars; + + Item_ValidateTypeData( item ); + if ( !item->typeData ) { + return qfalse; + } + + if ( !PC_Int_Parse( handle, &maxChars ) ) { + return qfalse; + } + editPtr = (editFieldDef_t*)item->typeData; + editPtr->maxChars = maxChars; + return qtrue; +} + +qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle ) { + editFieldDef_t *editPtr; + int maxChars; + + Item_ValidateTypeData( item ); + if ( !item->typeData ) { + return qfalse; + } + + if ( !PC_Int_Parse( handle, &maxChars ) ) { + return qfalse; + } + editPtr = (editFieldDef_t*)item->typeData; + editPtr->maxPaintChars = maxChars; + return qtrue; +} + + + +qboolean ItemParse_cvarFloat( itemDef_t *item, int handle ) { + editFieldDef_t *editPtr; + + Item_ValidateTypeData( item ); + if ( !item->typeData ) { + return qfalse; + } + editPtr = (editFieldDef_t*)item->typeData; + if ( PC_String_Parse( handle, &item->cvar ) && + PC_Float_Parse( handle, &editPtr->defVal ) && + PC_Float_Parse( handle, &editPtr->minVal ) && + PC_Float_Parse( handle, &editPtr->maxVal ) ) { + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_cvarStrList( itemDef_t *item, int handle ) { + pc_token_t token; + multiDef_t *multiPtr; + int pass; + + Item_ValidateTypeData( item ); + if ( !item->typeData ) { + return qfalse; + } + multiPtr = (multiDef_t*)item->typeData; + multiPtr->count = 0; + multiPtr->strDef = qtrue; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( *token.string != '{' ) { + return qfalse; + } + + pass = 0; + while ( 1 ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + PC_SourceError( handle, "end of file inside menu item\n" ); + return qfalse; + } + + if ( *token.string == '}' ) { + return qtrue; + } + + if ( *token.string == ',' || *token.string == ';' ) { + continue; + } + + if ( pass == 0 ) { + multiPtr->cvarList[multiPtr->count] = String_Alloc( token.string ); + pass = 1; + } else { + multiPtr->cvarStr[multiPtr->count] = String_Alloc( token.string ); + pass = 0; + multiPtr->count++; + if ( multiPtr->count >= MAX_MULTI_CVARS ) { + return qfalse; + } + } + + } + return qfalse; // bk001205 - LCC missing return value +} + +qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle ) { + pc_token_t token; + multiDef_t *multiPtr; + + Item_ValidateTypeData( item ); + if ( !item->typeData ) { + return qfalse; + } + multiPtr = (multiDef_t*)item->typeData; + multiPtr->count = 0; + multiPtr->strDef = qfalse; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( *token.string != '{' ) { + return qfalse; + } + + while ( 1 ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + PC_SourceError( handle, "end of file inside menu item\n" ); + return qfalse; + } + + if ( *token.string == '}' ) { + return qtrue; + } + + if ( *token.string == ',' || *token.string == ';' ) { + continue; + } + + multiPtr->cvarList[multiPtr->count] = String_Alloc( token.string ); + if ( !PC_Float_Parse( handle, &multiPtr->cvarValue[multiPtr->count] ) ) { + return qfalse; + } + + multiPtr->count++; + if ( multiPtr->count >= MAX_MULTI_CVARS ) { + return qfalse; + } + + } + return qfalse; // bk001205 - LCC missing return value +} + + +qboolean ParseColorRange( itemDef_t *item, int handle, int type ) { + colorRangeDef_t color; + + if ( item->numColors && type != item->colorRangeType ) { + PC_SourceError( handle, "both addColorRange and addColorRangeRel - set within same itemdef\n" ); + return qfalse; + } + + item->colorRangeType = type; + + if ( PC_Float_Parse( handle, &color.low ) && + PC_Float_Parse( handle, &color.high ) && + PC_Color_Parse( handle, &color.color ) ) { + if ( item->numColors < MAX_COLOR_RANGES ) { + memcpy( &item->colorRanges[item->numColors], &color, sizeof( color ) ); + item->numColors++; + } + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_addColorRangeRel( itemDef_t *item, int handle ) { + return ParseColorRange( item, handle, RANGETYPE_RELATIVE ); +} + +qboolean ItemParse_addColorRange( itemDef_t *item, int handle ) { + return ParseColorRange( item, handle, RANGETYPE_ABSOLUTE ); +} + + + +qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle ) { + int i; + if ( !PC_Int_Parse( handle, &i ) ) { + return qfalse; + } + item->window.ownerDrawFlags |= i; + return qtrue; +} + +qboolean ItemParse_enableCvar( itemDef_t *item, int handle ) { + if ( PC_Script_Parse( handle, &item->enableCvar ) ) { + item->cvarFlags = CVAR_ENABLE; + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_disableCvar( itemDef_t *item, int handle ) { + if ( PC_Script_Parse( handle, &item->enableCvar ) ) { + item->cvarFlags = CVAR_DISABLE; + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_noToggle( itemDef_t *item, int handle ) { + item->cvarFlags |= CVAR_NOTOGGLE; + return qtrue; +} + +qboolean ItemParse_showCvar( itemDef_t *item, int handle ) { + if ( PC_Script_Parse( handle, &item->enableCvar ) ) { + item->cvarFlags = CVAR_SHOW; + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_hideCvar( itemDef_t *item, int handle ) { + if ( PC_Script_Parse( handle, &item->enableCvar ) ) { + item->cvarFlags = CVAR_HIDE; + return qtrue; + } + return qfalse; +} + + +keywordHash_t itemParseKeywords[] = { + {"name", ItemParse_name, NULL}, + {"text", ItemParse_text, NULL}, + {"textfile", ItemParse_textfile, NULL}, //----(SA) added + {"group", ItemParse_group, NULL}, + {"asset_model", ItemParse_asset_model, NULL}, + {"asset_shader", ItemParse_asset_shader, NULL}, + {"model_origin", ItemParse_model_origin, NULL}, + {"model_fovx", ItemParse_model_fovx, NULL}, + {"model_fovy", ItemParse_model_fovy, NULL}, + {"model_rotation", ItemParse_model_rotation, NULL}, + {"model_angle", ItemParse_model_angle, NULL}, + {"model_animplay", ItemParse_model_animplay, NULL}, + {"rect", ItemParse_rect, NULL}, + {"origin", ItemParse_origin, NULL}, // NERVE - SMF + {"style", ItemParse_style, NULL}, + {"decoration", ItemParse_decoration, NULL}, + {"notselectable", ItemParse_notselectable, NULL}, + {"wrapped", ItemParse_wrapped, NULL}, + {"autowrapped", ItemParse_autowrapped, NULL}, + {"horizontalscroll", ItemParse_horizontalscroll, NULL}, + {"type", ItemParse_type, NULL}, + {"elementwidth", ItemParse_elementwidth, NULL}, + {"elementheight", ItemParse_elementheight, NULL}, + {"feeder", ItemParse_feeder, NULL}, + {"elementtype", ItemParse_elementtype, NULL}, + {"columns", ItemParse_columns, NULL}, + {"border", ItemParse_border, NULL}, + {"bordersize", ItemParse_bordersize, NULL}, + {"visible", ItemParse_visible, NULL}, + {"ownerdraw", ItemParse_ownerdraw, NULL}, + {"align", ItemParse_align, NULL}, + {"textalign", ItemParse_textalign, NULL}, + {"textalignx", ItemParse_textalignx, NULL}, + {"textaligny", ItemParse_textaligny, NULL}, + {"textscale", ItemParse_textscale, NULL}, + {"textstyle", ItemParse_textstyle, NULL}, + {"textfont", ItemParse_textfont, NULL}, // (SA) + {"backcolor", ItemParse_backcolor, NULL}, + {"forecolor", ItemParse_forecolor, NULL}, + {"bordercolor", ItemParse_bordercolor, NULL}, + {"outlinecolor", ItemParse_outlinecolor, NULL}, + {"background", ItemParse_background, NULL}, + {"onFocus", ItemParse_onFocus, NULL}, + {"leaveFocus", ItemParse_leaveFocus, NULL}, + {"mouseEnter", ItemParse_mouseEnter, NULL}, + {"mouseExit", ItemParse_mouseExit, NULL}, + {"mouseEnterText", ItemParse_mouseEnterText, NULL}, + {"mouseExitText", ItemParse_mouseExitText, NULL}, + {"action", ItemParse_action, NULL}, + {"accept", ItemParse_accept, NULL}, // NERVE - SMF + {"special", ItemParse_special, NULL}, + {"cvar", ItemParse_cvar, NULL}, + {"maxChars", ItemParse_maxChars, NULL}, + {"maxPaintChars", ItemParse_maxPaintChars, NULL}, + {"focusSound", ItemParse_focusSound, NULL}, + {"cvarFloat", ItemParse_cvarFloat, NULL}, + {"cvarStrList", ItemParse_cvarStrList, NULL}, + {"cvarFloatList", ItemParse_cvarFloatList, NULL}, + {"addColorRange", ItemParse_addColorRange, NULL}, + {"addColorRangeRel", ItemParse_addColorRangeRel, NULL}, + {"ownerdrawFlag", ItemParse_ownerdrawFlag, NULL}, + {"enableCvar", ItemParse_enableCvar, NULL}, + {"cvarTest", ItemParse_cvarTest, NULL}, + {"disableCvar", ItemParse_disableCvar, NULL}, + {"showCvar", ItemParse_showCvar, NULL}, + {"hideCvar", ItemParse_hideCvar, NULL}, + {"cinematic", ItemParse_cinematic, NULL}, + {"doubleclick", ItemParse_doubleClick, NULL}, + {"noToggle", ItemParse_noToggle, NULL}, // TTimo: use with ITEM_TYPE_YESNO and an action script (see sv_punkbuster) + {NULL, NULL, NULL} +}; + +keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +Item_SetupKeywordHash +=============== +*/ +void Item_SetupKeywordHash( void ) { + int i; + + memset( itemParseKeywordHash, 0, sizeof( itemParseKeywordHash ) ); + for ( i = 0; itemParseKeywords[i].keyword; i++ ) { + KeywordHash_Add( itemParseKeywordHash, &itemParseKeywords[i] ); + } +} + +/* +=============== +Item_Parse +=============== +*/ +qboolean Item_Parse( int handle, itemDef_t *item ) { + pc_token_t token; + keywordHash_t *key; + + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( *token.string != '{' ) { + return qfalse; + } + while ( 1 ) { + if ( !trap_PC_ReadToken( handle, &token ) ) { + PC_SourceError( handle, "end of file inside menu item\n" ); + return qfalse; + } + + if ( *token.string == '}' ) { + return qtrue; + } + + key = KeywordHash_Find( itemParseKeywordHash, token.string ); + if ( !key ) { + PC_SourceError( handle, "unknown menu item keyword %s", token.string ); + continue; + } + if ( !key->func( item, handle ) ) { + PC_SourceError( handle, "couldn't parse menu item keyword %s", token.string ); + return qfalse; + } + } + return qfalse; // bk001205 - LCC missing return value +} + + +// Item_InitControls +// init's special control types +void Item_InitControls( itemDef_t *item ) { + if ( item == NULL ) { + return; + } + if ( item->type == ITEM_TYPE_LISTBOX ) { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + item->cursorPos = 0; + if ( listPtr ) { + listPtr->cursorPos = 0; + listPtr->startPos = 0; + listPtr->endPos = 0; + listPtr->cursorPos = 0; + } + } +} + +/* +=============== +Menu Keyword Parse functions +=============== +*/ + +qboolean MenuParse_font( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_String_Parse( handle, &menu->font ) ) { + return qfalse; + } + if ( !DC->Assets.fontRegistered ) { + DC->registerFont( menu->font, 48, &DC->Assets.textFont ); + DC->Assets.fontRegistered = qtrue; + } + return qtrue; +} + +qboolean MenuParse_name( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_String_Parse( handle, &menu->window.name ) ) { + return qfalse; + } + if ( Q_stricmp( menu->window.name, "main" ) == 0 ) { + // default main as having focus + //menu->window.flags |= WINDOW_HASFOCUS; + } + return qtrue; +} + +qboolean MenuParse_fullscreen( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Int_Parse( handle, (int*)&menu->fullScreen ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_rect( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Rect_Parse( handle, &menu->window.rect ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_style( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Int_Parse( handle, &menu->window.style ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_visible( itemDef_t *item, int handle ) { + int i; + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_Int_Parse( handle, &i ) ) { + return qfalse; + } + if ( i ) { + menu->window.flags |= WINDOW_VISIBLE; + } + return qtrue; +} + +qboolean MenuParse_onOpen( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Script_Parse( handle, &menu->onOpen ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_onClose( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Script_Parse( handle, &menu->onClose ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_onESC( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Script_Parse( handle, &menu->onESC ) ) { + return qfalse; + } + return qtrue; +} + + + +qboolean MenuParse_border( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Int_Parse( handle, &menu->window.border ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_borderSize( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Float_Parse( handle, &menu->window.borderSize ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_backcolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + menu->window.backColor[i] = f; + } + return qtrue; +} + +qboolean MenuParse_forecolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + menu->window.foreColor[i] = f; + menu->window.flags |= WINDOW_FORECOLORSET; + } + return qtrue; +} + +qboolean MenuParse_bordercolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + menu->window.borderColor[i] = f; + } + return qtrue; +} + +qboolean MenuParse_focuscolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + menu->focusColor[i] = f; + } + return qtrue; +} + +qboolean MenuParse_disablecolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + for ( i = 0; i < 4; i++ ) { + if ( !PC_Float_Parse( handle, &f ) ) { + return qfalse; + } + menu->disableColor[i] = f; + } + return qtrue; +} + + +qboolean MenuParse_outlinecolor( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( !PC_Color_Parse( handle, &menu->window.outlineColor ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_background( itemDef_t *item, int handle ) { + const char *buff; + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_String_Parse( handle, &buff ) ) { + return qfalse; + } + menu->window.background = DC->registerShaderNoMip( buff ); + return qtrue; +} + +qboolean MenuParse_cinematic( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_String_Parse( handle, &menu->window.cinematicName ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle ) { + int i; + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_Int_Parse( handle, &i ) ) { + return qfalse; + } + menu->window.ownerDrawFlags |= i; + return qtrue; +} + +qboolean MenuParse_ownerdraw( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_Int_Parse( handle, &menu->window.ownerDraw ) ) { + return qfalse; + } + return qtrue; +} + + +// decoration +qboolean MenuParse_popup( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + menu->window.flags |= WINDOW_POPUP; + return qtrue; +} + + +qboolean MenuParse_outOfBounds( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + menu->window.flags |= WINDOW_OOB_CLICK; + return qtrue; +} + +qboolean MenuParse_soundLoop( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_String_Parse( handle, &menu->soundName ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_fadeClamp( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_Float_Parse( handle, &menu->fadeClamp ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_fadeAmount( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_Float_Parse( handle, &menu->fadeAmount ) ) { + return qfalse; + } + return qtrue; +} + + +qboolean MenuParse_fadeCycle( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if ( !PC_Int_Parse( handle, &menu->fadeCycle ) ) { + return qfalse; + } + return qtrue; +} + + +qboolean MenuParse_itemDef( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if ( menu->itemCount < MAX_MENUITEMS ) { + menu->items[menu->itemCount] = UI_Alloc( sizeof( itemDef_t ) ); + Item_Init( menu->items[menu->itemCount] ); + if ( !Item_Parse( handle, menu->items[menu->itemCount] ) ) { + return qfalse; + } + Item_InitControls( menu->items[menu->itemCount] ); + menu->items[menu->itemCount++]->parent = menu; + } + return qtrue; +} + +// NERVE - SMF +qboolean MenuParse_execKey( itemDef_t *item, int handle ) { + menuDef_t *menu = ( menuDef_t* )item; + char keyname; + short int keyindex; + + if ( !PC_Char_Parse( handle, &keyname ) ) { + return qfalse; + } + keyindex = keyname; + + if ( !PC_Script_Parse( handle, &menu->onKey[keyindex] ) ) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_execKeyInt( itemDef_t *item, int handle ) { + menuDef_t *menu = ( menuDef_t* )item; + int keyname; + + if ( !PC_Int_Parse( handle, &keyname ) ) { + return qfalse; + } + + if ( !PC_Script_Parse( handle, &menu->onKey[keyname] ) ) { + return qfalse; + } + return qtrue; +} +// -NERVE - SMF + +// TTimo +qboolean MenuParse_modal( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + menu->window.flags |= WINDOW_MODAL; + return qtrue; +} + + +keywordHash_t menuParseKeywords[] = { + {"font", MenuParse_font, NULL}, + {"name", MenuParse_name, NULL}, + {"fullscreen", MenuParse_fullscreen, NULL}, + {"rect", MenuParse_rect, NULL}, + {"style", MenuParse_style, NULL}, + {"visible", MenuParse_visible, NULL}, + {"onOpen", MenuParse_onOpen, NULL}, + {"onClose", MenuParse_onClose, NULL}, + {"onESC", MenuParse_onESC, NULL}, + {"border", MenuParse_border, NULL}, + {"borderSize", MenuParse_borderSize, NULL}, + {"backcolor", MenuParse_backcolor, NULL}, + {"forecolor", MenuParse_forecolor, NULL}, + {"bordercolor", MenuParse_bordercolor, NULL}, + {"focuscolor", MenuParse_focuscolor, NULL}, + {"disablecolor", MenuParse_disablecolor, NULL}, + {"outlinecolor", MenuParse_outlinecolor, NULL}, + {"background", MenuParse_background, NULL}, + {"ownerdraw", MenuParse_ownerdraw, NULL}, + {"ownerdrawFlag", MenuParse_ownerdrawFlag, NULL}, + {"outOfBoundsClick", MenuParse_outOfBounds, NULL}, + {"soundLoop", MenuParse_soundLoop, NULL}, + {"itemDef", MenuParse_itemDef, NULL}, + {"cinematic", MenuParse_cinematic, NULL}, + {"popup", MenuParse_popup, NULL}, + {"fadeClamp", MenuParse_fadeClamp, NULL}, + {"fadeCycle", MenuParse_fadeCycle, NULL}, + {"fadeAmount", MenuParse_fadeAmount, NULL}, + {"execKey", MenuParse_execKey, NULL}, // NERVE - SMF + {"execKeyInt", MenuParse_execKeyInt, NULL}, // NERVE - SMF + {"modal", MenuParse_modal, NULL }, + {NULL, NULL, NULL} +}; + +keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +Menu_SetupKeywordHash +=============== +*/ +void Menu_SetupKeywordHash( void ) { + int i; + + memset( menuParseKeywordHash, 0, sizeof( menuParseKeywordHash ) ); + for ( i = 0; menuParseKeywords[i].keyword; i++ ) { + KeywordHash_Add( menuParseKeywordHash, &menuParseKeywords[i] ); + } +} + +/* +=============== +Menu_Parse +=============== +*/ +qboolean Menu_Parse( int handle, menuDef_t *menu ) { + pc_token_t token; + keywordHash_t *key; + + if ( !trap_PC_ReadToken( handle, &token ) ) { + return qfalse; + } + if ( *token.string != '{' ) { + return qfalse; + } + + while ( 1 ) { + + memset( &token, 0, sizeof( pc_token_t ) ); + if ( !trap_PC_ReadToken( handle, &token ) ) { + PC_SourceError( handle, "end of file inside menu\n" ); + return qfalse; + } + + if ( *token.string == '}' ) { + return qtrue; + } + + key = KeywordHash_Find( menuParseKeywordHash, token.string ); + if ( !key ) { + PC_SourceError( handle, "unknown menu keyword %s", token.string ); + continue; + } + if ( !key->func( (itemDef_t*)menu, handle ) ) { + PC_SourceError( handle, "couldn't parse menu keyword %s", token.string ); + return qfalse; + } + } + return qfalse; // bk001205 - LCC missing return value +} + +/* +=============== +Menu_New +=============== +*/ +void Menu_New( int handle ) { + menuDef_t *menu = &Menus[menuCount]; + + if ( menuCount < MAX_MENUS ) { + Menu_Init( menu ); + if ( Menu_Parse( handle, menu ) ) { + Menu_PostParse( menu ); + menuCount++; + } + } +} + +int Menu_Count() { + return menuCount; +} + +void Menu_PaintAll() { + int i; + if ( captureFunc ) { + captureFunc( captureData ); + } + + for ( i = 0; i < Menu_Count(); i++ ) { + Menu_Paint( &Menus[i], qfalse ); + } + + if ( debugMode ) { + vec4_t v = {1, 1, 1, 1}; + DC->drawText( 5, 25, .5, v, va( "fps: %f", DC->FPS ), 0, 0, 0 ); + } +} + +void Menu_Reset() { + menuCount = 0; +} + +displayContextDef_t *Display_GetContext() { + return DC; +} + +//static float captureX; // TTimo: unused +//static float captureY; // TTimo: unused + +void *Display_CaptureItem( int x, int y ) { + int i; + + for ( i = 0; i < menuCount; i++ ) { + // turn off focus each item + // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + if ( Rect_ContainsPoint( &Menus[i].window.rect, x, y ) ) { + return &Menus[i]; + } + } + return NULL; +} + + +// FIXME: +qboolean Display_MouseMove( void *p, int x, int y ) { + int i; + menuDef_t *menu = p; + + if ( menu == NULL ) { + menu = Menu_GetFocused(); + if ( menu ) { + if ( menu->window.flags & WINDOW_POPUP ) { + Menu_HandleMouseMove( menu, x, y ); + return qtrue; + } + } + for ( i = 0; i < menuCount; i++ ) { + Menu_HandleMouseMove( &Menus[i], x, y ); + } + } else { + menu->window.rect.x += x; + menu->window.rect.y += y; + Menu_UpdatePosition( menu ); + } + return qtrue; + +} + +int Display_CursorType( int x, int y ) { + int i; + for ( i = 0; i < menuCount; i++ ) { + rectDef_t r2; + r2.x = Menus[i].window.rect.x - 3; + r2.y = Menus[i].window.rect.y - 3; + r2.w = r2.h = 7; + if ( Rect_ContainsPoint( &r2, x, y ) ) { + return CURSOR_SIZER; + } + } + return CURSOR_ARROW; +} + + +void Display_HandleKey( int key, qboolean down, int x, int y ) { + menuDef_t *menu = Display_CaptureItem( x, y ); + if ( menu == NULL ) { + menu = Menu_GetFocused(); + } + if ( menu ) { + Menu_HandleKey( menu, key, down ); + } +} + +static void Window_CacheContents( windowDef_t *window ) { + if ( window ) { + if ( window->cinematicName ) { + int cin = DC->playCinematic( window->cinematicName, 0, 0, 0, 0 ); + DC->stopCinematic( cin ); + } + } +} + + +static void Item_CacheContents( itemDef_t *item ) { + if ( item ) { + Window_CacheContents( &item->window ); + } + +} + +static void Menu_CacheContents( menuDef_t *menu ) { + if ( menu ) { + int i; + Window_CacheContents( &menu->window ); + for ( i = 0; i < menu->itemCount; i++ ) { + Item_CacheContents( menu->items[i] ); + } + + if ( menu->soundName && *menu->soundName ) { + DC->registerSound( menu->soundName ); + } + } + +} + +void Display_CacheAll() { + int i; + for ( i = 0; i < menuCount; i++ ) { + Menu_CacheContents( &Menus[i] ); + } +} + + +static qboolean Menu_OverActiveItem( menuDef_t *menu, float x, float y ) { + if ( menu && menu->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) { + if ( Rect_ContainsPoint( &menu->window.rect, x, y ) ) { + int i; + for ( i = 0; i < menu->itemCount; i++ ) { + // turn off focus each item + // menu->items[i]->window.flags &= ~WINDOW_HASFOCUS; + + if ( !( menu->items[i]->window.flags & ( WINDOW_VISIBLE | WINDOW_FORCED ) ) ) { + continue; + } + + if ( menu->items[i]->window.flags & WINDOW_DECORATION ) { + continue; + } + + if ( Rect_ContainsPoint( &menu->items[i]->window.rect, x, y ) ) { + itemDef_t *overItem = menu->items[i]; + if ( overItem->type == ITEM_TYPE_TEXT && overItem->text ) { + if ( Rect_ContainsPoint( Item_CorrectedTextRect( overItem ), x, y ) ) { + return qtrue; + } else { + continue; + } + } else { + return qtrue; + } + } + } + + } + } + return qfalse; +} + diff --git a/src/ui/ui_shared.h b/src/ui/ui_shared.h new file mode 100644 index 0000000..42abcdb --- /dev/null +++ b/src/ui/ui_shared.h @@ -0,0 +1,494 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __UI_SHARED_H +#define __UI_SHARED_H + + +#include "../game/q_shared.h" +#include "../cgame/tr_types.h" +#include "keycodes.h" + +// TTimo case sensitivity +#include "../../MAIN/ui_mp/menudef.h" + +#define MAX_MENUNAME 32 +#define MAX_ITEMTEXT 64 +#define MAX_ITEMACTION 64 +#define MAX_MENUDEFFILE 4096 +#define MAX_MENUFILE 32768 +#define MAX_MENUS 64 +//#define MAX_MENUITEMS 256 +#define MAX_MENUITEMS 128 // JPW NERVE q3ta was 96 +#define MAX_COLOR_RANGES 10 +#define MAX_MODAL_MENUS 16 + +#define WINDOW_MOUSEOVER 0x00000001 // mouse is over it, non exclusive +#define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive +#define WINDOW_VISIBLE 0x00000004 // is visible +#define WINDOW_GREY 0x00000008 // is visible but grey ( non-active ) +#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc.. +#define WINDOW_FADINGOUT 0x00000020 // fading out, non-active +#define WINDOW_FADINGIN 0x00000040 // fading in +#define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive +#define WINDOW_INTRANSITION 0x00000100 // window is in transition +#define WINDOW_FORECOLORSET 0x00000200 // forecolor was explicitly set ( used to color alpha images or not ) +#define WINDOW_HORIZONTAL 0x00000400 // for list boxes and sliders, vertical is default this is set of horizontal +#define WINDOW_LB_LEFTARROW 0x00000800 // mouse is over left/up arrow +#define WINDOW_LB_RIGHTARROW 0x00001000 // mouse is over right/down arrow +#define WINDOW_LB_THUMB 0x00002000 // mouse is over thumb +#define WINDOW_LB_PGUP 0x00004000 // mouse is over page up +#define WINDOW_LB_PGDN 0x00008000 // mouse is over page down +#define WINDOW_ORBITING 0x00010000 // item is in orbit +#define WINDOW_OOB_CLICK 0x00020000 // close on out of bounds click +#define WINDOW_WRAPPED 0x00040000 // manually wrap text +#define WINDOW_AUTOWRAPPED 0x00080000 // auto wrap text +#define WINDOW_FORCED 0x00100000 // forced open +#define WINDOW_POPUP 0x00200000 // popup +#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set +#define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented ) +#define WINDOW_IGNORE_HUDALPHA 0x01000000 // window will apply cg_hudAlpha value to colors unless this flag is set +#define WINDOW_MODAL 0x02000000 // window is modal, the window to go back to is stored in a stack + +// CGAME cursor type bits +#define CURSOR_NONE 0x00000001 +#define CURSOR_ARROW 0x00000002 +#define CURSOR_SIZER 0x00000004 + +#ifdef CGAME +#define STRING_POOL_SIZE 128 * 1024 +#else +#define STRING_POOL_SIZE 384 * 1024 +#endif + +#define MAX_STRING_HANDLES 4096 +#define MAX_SCRIPT_ARGS 12 +#define MAX_EDITFIELD 256 + +#define ART_FX_BASE "menu/art/fx_base" +#define ART_FX_BLUE "menu/art/fx_blue" +#define ART_FX_CYAN "menu/art/fx_cyan" +#define ART_FX_GREEN "menu/art/fx_grn" +#define ART_FX_RED "menu/art/fx_red" +#define ART_FX_TEAL "menu/art/fx_teal" +#define ART_FX_WHITE "menu/art/fx_white" +#define ART_FX_YELLOW "menu/art/fx_yel" + +#define ASSET_GRADIENTBAR "ui_mp/assets/gradientbar2.tga" +#define ASSET_SCROLLBAR "ui_mp/assets/scrollbar.tga" +#define ASSET_SCROLLBAR_ARROWDOWN "ui_mp/assets/scrollbar_arrow_dwn_a.tga" +#define ASSET_SCROLLBAR_ARROWUP "ui_mp/assets/scrollbar_arrow_up_a.tga" +#define ASSET_SCROLLBAR_ARROWLEFT "ui_mp/assets/scrollbar_arrow_left.tga" +#define ASSET_SCROLLBAR_ARROWRIGHT "ui_mp/assets/scrollbar_arrow_right.tga" +#define ASSET_SCROLL_THUMB "ui_mp/assets/scrollbar_thumb.tga" +#define ASSET_SLIDER_BAR "ui_mp/assets/slider2.tga" +#define ASSET_SLIDER_THUMB "ui_mp/assets/sliderbutt_1.tga" + +#define SCROLLBAR_SIZE 16.0 +#define SLIDER_WIDTH 96.0 +#define SLIDER_HEIGHT 16.0 +#define SLIDER_THUMB_WIDTH 12.0 +#define SLIDER_THUMB_HEIGHT 20.0 +#define NUM_CROSSHAIRS 10 + +typedef struct { + const char *command; + const char *args[MAX_SCRIPT_ARGS]; +} scriptDef_t; + + +typedef struct { + float x; // horiz position + float y; // vert position + float w; // width + float h; // height; +} rectDef_t; + +typedef rectDef_t Rectangle; + +// FIXME: do something to separate text vs window stuff +typedef struct { + Rectangle rect; // client coord rectangle + Rectangle rectClient; // screen coord rectangle + const char *name; // + const char *model; // + const char *group; // if it belongs to a group + const char *cinematicName; // cinematic name + int cinematic; // cinematic handle + int style; // + int border; // + int ownerDraw; // ownerDraw style + int ownerDrawFlags; // show flags for ownerdraw items + float borderSize; // + int flags; // visible, focus, mouseover, cursor + Rectangle rectEffects; // for various effects + Rectangle rectEffects2; // for various effects + int offsetTime; // time based value for various effects + int nextTime; // time next effect should cycle + vec4_t foreColor; // text color + vec4_t backColor; // border color + vec4_t borderColor; // border color + vec4_t outlineColor; // border color + qhandle_t background; // background asset +} windowDef_t; + +typedef windowDef_t Window; + + +typedef struct { + vec4_t color; + int type; + float low; + float high; +} colorRangeDef_t; + +// FIXME: combine flags into bitfields to save space +// FIXME: consolidate all of the common stuff in one structure for menus and items +// THINKABOUTME: is there any compelling reason not to have items contain items +// and do away with a menu per say.. major issue is not being able to dynamically allocate +// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have +// the engine just allocate the pool for it based on a cvar +// many of the vars are re-used for different item types, as such they are not always named appropriately +// the benefits of c++ in DOOM will greatly help crap like this +// FIXME: need to put a type ptr that points to specific type info per type +// +#define MAX_LB_COLUMNS 16 + +typedef struct columnInfo_s { + int pos; + int width; + int maxChars; +} columnInfo_t; + +typedef struct listBoxDef_s { + int startPos; + int endPos; + int drawPadding; + int cursorPos; + float elementWidth; + float elementHeight; + int elementStyle; + int numColumns; + columnInfo_t columnInfo[MAX_LB_COLUMNS]; + const char *doubleClick; + qboolean notselectable; +} listBoxDef_t; + +typedef struct editFieldDef_s { + float minVal; // edit field limits + float maxVal; // + float defVal; // + float range; // + int maxChars; // for edit fields + int maxPaintChars; // for edit fields + int paintOffset; // +} editFieldDef_t; + +#define MAX_MULTI_CVARS 32 + +typedef struct multiDef_s { + const char *cvarList[MAX_MULTI_CVARS]; + const char *cvarStr[MAX_MULTI_CVARS]; + float cvarValue[MAX_MULTI_CVARS]; + int count; + qboolean strDef; +} multiDef_t; + +typedef struct modelDef_s { + int angle; + vec3_t origin; + float fov_x; + float fov_y; + int rotationSpeed; + + int animated; + int startframe; + int numframes; + int loopframes; + int fps; + + int frame; + int oldframe; + float backlerp; + int frameTime; +} modelDef_t; + +#define CVAR_ENABLE 0x00000001 +#define CVAR_DISABLE 0x00000002 +#define CVAR_SHOW 0x00000004 +#define CVAR_HIDE 0x00000008 +#define CVAR_NOTOGGLE 0x00000010 + +#define UI_MAX_TEXT_LINES 64 + +typedef struct itemDef_s { + Window window; // common positional, border, style, layout info + Rectangle textRect; // rectangle the text ( if any ) consumes + int type; // text, button, radiobutton, checkbox, textfield, listbox, combo + int alignment; // left center right + int textalignment; // ( optional ) alignment for text within rect based on text width + float textalignx; // ( optional ) text alignment x coord + float textaligny; // ( optional ) text alignment x coord + float textscale; // scale percentage from 72pts + int font; // (SA) + int textStyle; // ( optional ) style, normal and shadowed are it for now + const char *text; // display text + void *parent; // menu owner + qhandle_t asset; // handle to asset + const char *mouseEnterText; // mouse enter script + const char *mouseExitText; // mouse exit script + const char *mouseEnter; // mouse enter script + const char *mouseExit; // mouse exit script + const char *action; // select script + const char *onAccept; // NERVE - SMF - run when the users presses the enter key + const char *onFocus; // select script + const char *leaveFocus; // select script + const char *cvar; // associated cvar + const char *cvarTest; // associated cvar for enable actions + const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list + int cvarFlags; // what type of action to take on cvarenables + sfxHandle_t focusSound; + int numColors; // number of color ranges + colorRangeDef_t colorRanges[MAX_COLOR_RANGES]; + int colorRangeType; // either + float special; // used for feeder id's etc.. diff per type + int cursorPos; // cursor position in characters + void *typeData; // type specific data ptr's +} itemDef_t; + +typedef struct { + Window window; + const char *font; // font + qboolean fullScreen; // covers entire screen + int itemCount; // number of items; + int fontIndex; // + int cursorItem; // which item as the cursor + int fadeCycle; // + float fadeClamp; // + float fadeAmount; // + const char *onOpen; // run when the menu is first opened + const char *onClose; // run when the menu is closed + const char *onESC; // run when the menu is closed + const char *onKey[255]; // NERVE - SMF - execs commands when a key is pressed + const char *soundName; // background loop sound for menu + + vec4_t focusColor; // focus color for items + vec4_t disableColor; // focus color for items + itemDef_t *items[MAX_MENUITEMS]; // items this menu contains +} menuDef_t; + +typedef struct { + const char *fontStr; + const char *cursorStr; + const char *gradientStr; + fontInfo_t textFont; + fontInfo_t smallFont; + fontInfo_t bigFont; + qhandle_t cursor; + qhandle_t gradientBar; + qhandle_t scrollBarArrowUp; + qhandle_t scrollBarArrowDown; + qhandle_t scrollBarArrowLeft; + qhandle_t scrollBarArrowRight; + qhandle_t scrollBar; + qhandle_t scrollBarThumb; + qhandle_t buttonMiddle; + qhandle_t buttonInside; + qhandle_t solidBox; + qhandle_t sliderBar; + qhandle_t sliderThumb; + sfxHandle_t menuEnterSound; + sfxHandle_t menuExitSound; + sfxHandle_t menuBuzzSound; + sfxHandle_t itemFocusSound; + float fadeClamp; + int fadeCycle; + float fadeAmount; + float shadowX; + float shadowY; + vec4_t shadowColor; + float shadowFadeClamp; + qboolean fontRegistered; + + // player settings + qhandle_t fxBasePic; + qhandle_t fxPic[7]; + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + +} cachedAssets_t; + +typedef struct { + const char *name; + void ( *handler )( itemDef_t *item, char** args ); +} commandDef_t; + +typedef struct { + qhandle_t ( *registerShaderNoMip )( const char *p ); + void ( *setColor )( const vec4_t v ); + void ( *drawHandlePic )( float x, float y, float w, float h, qhandle_t asset ); + void ( *drawStretchPic )( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); + void ( *drawText )( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style ); + int ( *textWidth )( const char *text, float scale, int limit ); + int ( *textHeight )( const char *text, float scale, int limit ); + void ( *textFont )( int font ); // NERVE - SMF + qhandle_t ( *registerModel )( const char *p ); + void ( *modelBounds )( qhandle_t model, vec3_t min, vec3_t max ); + void ( *fillRect )( float x, float y, float w, float h, const vec4_t color ); + void ( *drawRect )( float x, float y, float w, float h, float size, const vec4_t color ); + void ( *drawSides )( float x, float y, float w, float h, float size ); + void ( *drawTopBottom )( float x, float y, float w, float h, float size ); + void ( *clearScene )(); + void ( *addRefEntityToScene )( const refEntity_t *re ); + void ( *renderScene )( const refdef_t *fd ); + void ( *registerFont )( const char *pFontname, int pointSize, fontInfo_t *font ); + void ( *ownerDrawItem )( float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle ); + float ( *getValue )( int ownerDraw, int type ); + qboolean ( *ownerDrawVisible )( int flags ); + void ( *runScript )( char **p ); + void ( *getTeamColor )( vec4_t *color ); + void ( *getCVarString )( const char *cvar, char *buffer, int bufsize ); + float ( *getCVarValue )( const char *cvar ); + void ( *setCVar )( const char *cvar, const char *value ); + void ( *drawTextWithCursor )( float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style ); + void ( *setOverstrikeMode )( qboolean b ); + qboolean ( *getOverstrikeMode )(); + void ( *startLocalSound )( sfxHandle_t sfx, int channelNum ); + qboolean ( *ownerDrawHandleKey )( int ownerDraw, int flags, float *special, int key ); + int ( *feederCount )( float feederID ); + const char *( *feederItemText )( float feederID, int index, int column, qhandle_t * handle ); + const char *( *fileText )( char *flieName ); + qhandle_t ( *feederItemImage )( float feederID, int index ); + void ( *feederSelection )( float feederID, int index ); + void ( *feederAddItem )( float feederID, const char *name, int index ); // NERVE - SMF + char* ( *translateString )( const char *string ); // NERVE - SMF + void ( *checkAutoUpdate )(); // DHM - Nerve + void ( *getAutoUpdate )(); // DHM - Nerve + + void ( *keynumToStringBuf )( int keynum, char *buf, int buflen ); + void ( *getBindingBuf )( int keynum, char *buf, int buflen ); + void ( *setBinding )( int keynum, const char *binding ); + void ( *executeText )( int exec_when, const char *text ); + void ( *Error )( int level, const char *error, ... ); + void ( *Print )( const char *msg, ... ); + void ( *Pause )( qboolean b ); + int ( *ownerDrawWidth )( int ownerDraw, float scale ); +// sfxHandle_t (*registerSound)(const char *name, qboolean compressed); + sfxHandle_t ( *registerSound )( const char *name ); + void ( *startBackgroundTrack )( const char *intro, const char *loop ); + void ( *stopBackgroundTrack )(); + int ( *playCinematic )( const char *name, float x, float y, float w, float h ); + void ( *stopCinematic )( int handle ); + void ( *drawCinematic )( int handle, float x, float y, float w, float h ); + void ( *runCinematicFrame )( int handle ); + + float yscale; + float xscale; + float bias; + int realTime; + int frameTime; + int cursorx; + int cursory; + qboolean debug; + + cachedAssets_t Assets; + + glconfig_t glconfig; + qhandle_t whiteShader; + qhandle_t gradientImage; + qhandle_t cursor; + float FPS; + +} displayContextDef_t; + +const char *String_Alloc( const char *p ); +void String_Init(); +void String_Report(); +void Init_Display( displayContextDef_t *dc ); +void Display_ExpandMacros( char * buff ); +void Menu_Init( menuDef_t *menu ); +void Item_Init( itemDef_t *item ); +void Menu_PostParse( menuDef_t *menu ); +menuDef_t *Menu_GetFocused(); +void Menu_HandleKey( menuDef_t *menu, int key, qboolean down ); +void Menu_HandleMouseMove( menuDef_t *menu, float x, float y ); +void Menu_ScrollFeeder( menuDef_t *menu, int feeder, qboolean down ); +qboolean Float_Parse( char **p, float *f ); +qboolean Color_Parse( char **p, vec4_t *c ); +qboolean Int_Parse( char **p, int *i ); +qboolean Rect_Parse( char **p, rectDef_t *r ); +qboolean String_Parse( char **p, const char **out ); +qboolean Script_Parse( char **p, const char **out ); +qboolean PC_Float_Parse( int handle, float *f ); +qboolean PC_Color_Parse( int handle, vec4_t *c ); +qboolean PC_Int_Parse( int handle, int *i ); +qboolean PC_Rect_Parse( int handle, rectDef_t *r ); +qboolean PC_String_Parse( int handle, const char **out ); +qboolean PC_Script_Parse( int handle, const char **out ); +qboolean PC_Char_Parse( int handle, char *out ); // NERVE - SMF +int Menu_Count(); +void Menu_New( int handle ); +void Menu_PaintAll(); +menuDef_t *Menus_ActivateByName( const char *p, qboolean modalStack ); +void Menu_Reset(); +qboolean Menus_AnyFullScreenVisible(); +void Menus_Activate( menuDef_t *menu ); + +displayContextDef_t *Display_GetContext(); +void *Display_CaptureItem( int x, int y ); +qboolean Display_MouseMove( void *p, int x, int y ); +int Display_CursorType( int x, int y ); +qboolean Display_KeyBindPending(); +void Menus_OpenByName( const char *p ); +menuDef_t *Menus_FindByName( const char *p ); +void Menus_ShowByName( const char *p ); +void Menus_CloseByName( const char *p ); +void Display_HandleKey( int key, qboolean down, int x, int y ); +void LerpColor( vec4_t a, vec4_t b, vec4_t c, float t ); +void Menus_CloseAll(); +void Menu_Paint( menuDef_t *menu, qboolean forcePaint ); +void Menu_SetFeederSelection( menuDef_t *menu, int feeder, int index, const char *name ); +void Display_CacheAll(); + +// TTimo +void Menu_ShowItemByName( menuDef_t *menu, const char *p, qboolean bShow ); + +void *UI_Alloc( int size ); +void UI_InitMemory( void ); +qboolean UI_OutOfMemory(); + +void Controls_GetConfig( void ); +void Controls_SetConfig( qboolean restart ); +void Controls_SetDefaults( void ); + +int trap_PC_AddGlobalDefine( char *define ); +int trap_PC_LoadSource( const char *filename ); +int trap_PC_FreeSource( int handle ); +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ); +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ); + +#endif diff --git a/src/ui/ui_syscalls.c b/src/ui/ui_syscalls.c new file mode 100644 index 0000000..dadd83c --- /dev/null +++ b/src/ui/ui_syscalls.c @@ -0,0 +1,473 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "ui_local.h" + +// this file is only included when building a dll +// syscalls.asm is included instead when building a qvm + +static int ( QDECL * syscall )( int arg, ... ) = ( int ( QDECL * )( int, ... ) ) - 1; + +#if defined( __MACOS__ ) +#pragma export on +#endif +void dllEntry( int ( QDECL *syscallptr )( int arg,... ) ) { + syscall = syscallptr; +} +#if defined( __MACOS__ ) +#pragma export off +#endif + +int PASSFLOAT( float x ) { + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_Print( const char *string ) { + syscall( UI_PRINT, string ); +} + +void trap_Error( const char *string ) { + syscall( UI_ERROR, string ); +} + +int trap_Milliseconds( void ) { + return syscall( UI_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) { + syscall( UI_CVAR_REGISTER, cvar, var_name, value, flags ); +} + +void trap_Cvar_Update( vmCvar_t *cvar ) { + syscall( UI_CVAR_UPDATE, cvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( UI_CVAR_SET, var_name, value ); +} + +float trap_Cvar_VariableValue( const char *var_name ) { + int temp; + temp = syscall( UI_CVAR_VARIABLEVALUE, var_name ); + return ( *(float*)&temp ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +void trap_Cvar_SetValue( const char *var_name, float value ) { + syscall( UI_CVAR_SETVALUE, var_name, PASSFLOAT( value ) ); +} + +void trap_Cvar_Reset( const char *name ) { + syscall( UI_CVAR_RESET, name ); +} + +void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ) { + syscall( UI_CVAR_CREATE, var_name, var_value, flags ); +} + +void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ) { + syscall( UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize ); +} + +int trap_Argc( void ) { + return syscall( UI_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( UI_ARGV, n, buffer, bufferLength ); +} + +void trap_Cmd_ExecuteText( int exec_when, const char *text ) { + syscall( UI_CMD_EXECUTETEXT, exec_when, text ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( UI_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( UI_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( UI_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( UI_FS_FCLOSEFILE, f ); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + return syscall( UI_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +int trap_FS_Delete( const char *filename ) { + return syscall( UI_FS_DELETEFILE, filename ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + return syscall( UI_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { + return syscall( UI_R_REGISTERSKIN, name ); +} + +void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ) { + syscall( UI_R_REGISTERFONT, fontName, pointSize, font ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + return syscall( UI_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_ClearScene( void ) { + syscall( UI_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( UI_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts ) { + syscall( UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b, int overdraw ) { + syscall( UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT( intensity ), PASSFLOAT( r ), PASSFLOAT( g ), PASSFLOAT( b ), overdraw ); +} + +void trap_R_AddCoronaToScene( const vec3_t org, float r, float g, float b, float scale, int id, qboolean visible ) { + syscall( UI_R_ADDCORONATOSCENE, org, PASSFLOAT( r ), PASSFLOAT( g ), PASSFLOAT( b ), PASSFLOAT( scale ), id, visible ); +} + +void trap_R_RenderScene( const refdef_t *fd ) { + syscall( UI_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( UI_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) { + syscall( UI_R_DRAWSTRETCHPIC, PASSFLOAT( x ), PASSFLOAT( y ), PASSFLOAT( w ), PASSFLOAT( h ), PASSFLOAT( s1 ), PASSFLOAT( t1 ), PASSFLOAT( s2 ), PASSFLOAT( t2 ), hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( UI_R_MODELBOUNDS, model, mins, maxs ); +} + +void trap_UpdateScreen( void ) { + syscall( UI_UPDATESCREEN ); +} + +int trap_CM_LerpTag( orientation_t *tag, const refEntity_t *refent, const char *tagName, int startIndex ) { + return syscall( UI_CM_LERPTAG, tag, refent, tagName, 0 ); // NEFVE - SMF - fixed +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( UI_S_STARTLOCALSOUND, sfx, channelNum ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample ) { + return syscall( UI_S_REGISTERSOUND, sample ); +} + +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { + syscall( UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen ); +} + +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + syscall( UI_KEY_GETBINDINGBUF, keynum, buf, buflen ); +} + +void trap_Key_SetBinding( int keynum, const char *binding ) { + syscall( UI_KEY_SETBINDING, keynum, binding ); +} + +qboolean trap_Key_IsDown( int keynum ) { + return syscall( UI_KEY_ISDOWN, keynum ); +} + +qboolean trap_Key_GetOverstrikeMode( void ) { + return syscall( UI_KEY_GETOVERSTRIKEMODE ); +} + +void trap_Key_SetOverstrikeMode( qboolean state ) { + syscall( UI_KEY_SETOVERSTRIKEMODE, state ); +} + +void trap_Key_ClearStates( void ) { + syscall( UI_KEY_CLEARSTATES ); +} + +int trap_Key_GetCatcher( void ) { + return syscall( UI_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { + syscall( UI_KEY_SETCATCHER, catcher ); +} + +void trap_GetClipboardData( char *buf, int bufsize ) { + syscall( UI_GETCLIPBOARDDATA, buf, bufsize ); +} + +void trap_GetClientState( uiClientState_t *state ) { + syscall( UI_GETCLIENTSTATE, state ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( UI_GETGLCONFIG, glconfig ); +} + +int trap_GetConfigString( int index, char* buff, int buffsize ) { + return syscall( UI_GETCONFIGSTRING, index, buff, buffsize ); +} + +int trap_LAN_GetLocalServerCount( void ) { + return syscall( UI_LAN_GETLOCALSERVERCOUNT ); +} + +void trap_LAN_GetLocalServerAddressString( int n, char *buf, int buflen ) { + syscall( UI_LAN_GETLOCALSERVERADDRESSSTRING, n, buf, buflen ); +} + +int trap_LAN_GetGlobalServerCount( void ) { + return syscall( UI_LAN_GETGLOBALSERVERCOUNT ); +} + +void trap_LAN_GetGlobalServerAddressString( int n, char *buf, int buflen ) { + syscall( UI_LAN_GETGLOBALSERVERADDRESSSTRING, n, buf, buflen ); +} + +int trap_LAN_GetPingQueueCount( void ) { + return syscall( UI_LAN_GETPINGQUEUECOUNT ); +} + +void trap_LAN_ClearPing( int n ) { + syscall( UI_LAN_CLEARPING, n ); +} + +void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { + syscall( UI_LAN_GETPING, n, buf, buflen, pingtime ); +} + +void trap_LAN_GetPingInfo( int n, char *buf, int buflen ) { + syscall( UI_LAN_GETPINGINFO, n, buf, buflen ); +} + +// NERVE - SMF +qboolean trap_LAN_UpdateVisiblePings( int source ) { + return syscall( UI_LAN_UPDATEVISIBLEPINGS, source ); +} + +int trap_LAN_GetServerCount( int source ) { + return syscall( UI_LAN_GETSERVERCOUNT, source ); +} + +int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { + return syscall( UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2 ); +} + +void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { + syscall( UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen ); +} + +void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { + syscall( UI_LAN_GETSERVERINFO, source, n, buf, buflen ); +} + +int trap_LAN_AddServer( int source, const char *name, const char *addr ) { + return syscall( UI_LAN_ADDSERVER, source, name, addr ); +} + +void trap_LAN_RemoveServer( int source, const char *addr ) { + syscall( UI_LAN_REMOVESERVER, source, addr ); +} + +int trap_LAN_GetServerPing( int source, int n ) { + return syscall( UI_LAN_GETSERVERPING, source, n ); +} + +int trap_LAN_ServerIsVisible( int source, int n ) { + return syscall( UI_LAN_SERVERISVISIBLE, source, n ); +} + +int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ) { + return syscall( UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen ); +} + +void trap_LAN_SaveCachedServers() { + syscall( UI_LAN_SAVECACHEDSERVERS ); +} + +void trap_LAN_LoadCachedServers() { + syscall( UI_LAN_LOADCACHEDSERVERS ); +} + +void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ) { + syscall( UI_LAN_MARKSERVERVISIBLE, source, n, visible ); +} + +// DHM - Nerve :: PunkBuster +void trap_SetPbClStatus( int status ) { + syscall( UI_SET_PBCLSTATUS, status ); +} +// DHM - Nerve + +// TTimo: also for Sv +void trap_SetPbSvStatus( int status ) { + syscall( UI_SET_PBSVSTATUS, status ); +} + +void trap_LAN_ResetPings( int n ) { + syscall( UI_LAN_RESETPINGS, n ); +} +// -NERVE - SMF + +int trap_MemoryRemaining( void ) { + return syscall( UI_MEMORY_REMAINING ); +} + +void trap_GetCDKey( char *buf, int buflen ) { + syscall( UI_GET_CDKEY, buf, buflen ); +} + +void trap_SetCDKey( char *buf ) { + syscall( UI_SET_CDKEY, buf ); +} + +int trap_PC_AddGlobalDefine( char *define ) { + return syscall( UI_PC_ADD_GLOBAL_DEFINE, define ); +} + +int trap_PC_LoadSource( const char *filename ) { + return syscall( UI_PC_LOAD_SOURCE, filename ); +} + +int trap_PC_FreeSource( int handle ) { + return syscall( UI_PC_FREE_SOURCE, handle ); +} + +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { + return syscall( UI_PC_READ_TOKEN, handle, pc_token ); +} + +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { + return syscall( UI_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_S_StopBackgroundTrack( void ) { + syscall( UI_S_STOPBACKGROUNDTRACK ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { + syscall( UI_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +int trap_RealTime( qtime_t *qtime ) { + return syscall( UI_REAL_TIME, qtime ); +} + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits ) { + return syscall( UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits ); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic( int handle ) { + return syscall( UI_CIN_STOPCINEMATIC, handle ); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic( int handle ) { + return syscall( UI_CIN_RUNCINEMATIC, handle ); +} + + +// draws the current frame +void trap_CIN_DrawCinematic( int handle ) { + syscall( UI_CIN_DRAWCINEMATIC, handle ); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents( int handle, int x, int y, int w, int h ) { + syscall( UI_CIN_SETEXTENTS, handle, x, y, w, h ); +} + + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { + syscall( UI_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +qboolean trap_VerifyCDKey( const char *key, const char *chksum ) { + return syscall( UI_VERIFY_CDKEY, key, chksum ); +} + +// NERVE - SMF +qboolean trap_GetLimboString( int index, char *buf ) { + return syscall( UI_CL_GETLIMBOSTRING, index, buf ); +} + +#define MAX_VA_STRING 32000 + +char* trap_TranslateString( const char *string ) { + static char staticbuf[2][MAX_VA_STRING]; + static int bufcount = 0; + char *buf; + + buf = staticbuf[bufcount++ % 2]; + + syscall( UI_CL_TRANSLATE_STRING, string, buf ); + + return buf; +} +// -NERVE - SMF + +// DHM - Nerve +void trap_CheckAutoUpdate( void ) { + syscall( UI_CHECKAUTOUPDATE ); +} + +void trap_GetAutoUpdate( void ) { + syscall( UI_GET_AUTOUPDATE ); +} +// DHM - Nerve + +void trap_openURL( const char *s ) { + syscall( UI_OPENURL, s ); +} diff --git a/src/ui/ui_util.c b/src/ui/ui_util.c new file mode 100644 index 0000000..230797e --- /dev/null +++ b/src/ui/ui_util.c @@ -0,0 +1,36 @@ +/* +=========================================================================== + +Return to Castle Wolfenstein multiplayer GPL Source Code +Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. + +This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). + +RTCW MP Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +RTCW MP Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with RTCW MP Source Code. If not, see . + +In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// ui_util.c +// +// origin: rad +// new ui support stuff +// +// memory, string alloc + + diff --git a/src/unix/ChangeLog b/src/unix/ChangeLog new file mode 100644 index 0000000..af16704 --- /dev/null +++ b/src/unix/ChangeLog @@ -0,0 +1,1463 @@ +2006-05-03 Timothee Besset + + * fixed a few warning and errors when compiling with gcc-4.0 ( didn't go all the way through, will stick to 2.95 ) + * 1.41-3 Linux setup is hosed. it does not install the right binaries ( looks like the 1.40 stuff actually ) + going to redo an installer from scratch more or less, and put everything right in there + recovered 1.41 mp_bin.pk3 cgame/ui from Win32 installation, need a qagame? ( I can compile a new one myself ) + +2006-05-02 Timothee Besset + + no working Mac source! latest Mac source is likely at Aspyr. requested it + + revert changes to tr_shader.c and go back to the previous "broken" SkipBracedSection calls + need to release an updated binary to 1.41 and that fix would probably mean changing shaders as well + +2003-09-16 Timothee Besset + + java master and auth code. bind to specify interface for operation on multi-homed etmaster (192.246.40.38) + + wolfKEY is NOT checked in to CVS + +2003-08-31 Timothee Besset + + show_bug.cgi?id=626 + built new RTCW 1.41 setup working for more recent distributions (glibc 2.3 based ones) + +2003-01-22 Timothee Besset + + fix typo in Sys_Chmod (linux) + +2003-01-16 Timothee Besset + + update cons scripts for building on debian sid (default gcc version has changed) + + show_bug.cgi?id=570 + fixed SkipBracedSection usage to actually do it's job + fixed a syntax error in common.shader, updated version is REQUIRED to run current code + and yeah, that brings a considerable speedup to map loading + +2002-4-12 Timothee Besset + + releasing 1.41 update: + - modified the setup scripts to generate a 1.4 -> 1.41 incremental + - only putting the qagame as updated content + - adding RTCW-README-1.4.txt (server admin information) to the setup + - changed the INSTALL installation instructions to state about update and + not full setup + - commented out the full setup builds, update Conscript-setup if you need + them + +2002-13-11 Timothee Besset + + show_bug.cgi?id=569 + merged Rhea's g_antilag.c changes + - cancel out syntax changes (makes diff harder to read) + - IS_ACTIVE macro replaces our former test: + x->r.linked == qtrue && \ + x->client->ps.stats[STAT_HEALTH] > 0 && \ + x->client->sess.sessionTeam != TEAM_SPECTATOR && \ + (x->client->ps.pm_flags & PMF_LIMBO) == 0 \ + - we only walk through active clients + +2002-12-11 Timothee Besset + + show_bug.cgi?id=569 + fix for medic bug was validated with today's testing\ + haven't merged against Rhea's code yet. will do + +2002-11-11 Timothee Besset + + show_bug.cgi?id=568 + Rhea: better fix for loss of weapon functionality + +2002-11-5 Timothee Besset + + added in_subframe to toggle X subframe event handling + + reworked the timing code to be more reliable + + cleaned up dgamouse/in_mouse code, removed unnecessary dgamouse var + + made the mouse grabbing an in_nograb cvar, no longer a compile time option + in_nograb 1 forces in_dgamouse 0 and r_fullscreen 0 (any of those two will b0rk) + + last safe check for screwed up X window server was too agressive + tweaked to ignore when the subframe correction is too high + +2002-28-10 Timothee Besset + + ATVI Wolfenstein Misc #270 + checking in a fix that doesn't crash g_syncronousClients + + no longer prevent demo record on g_synchronousClients 0, just warn + + ATVI Wolfenstein Misc #472 + gun clipping, made the leaning bbox a bit bigger (along z<0) to cover gun clipping + +2002-25-10 Timothee Besset + + ATVI Wolfenstein Misc #474 + spectator following a client, client gets kicked, was not sending spectator back to a correct free fly mode + + ATVI Wolfenstein Misc #462 + added a 'noToggle' flag to itemDef, uses a CVAR_NOTOGGLE + this allows for ITEM_TYPE_YESNO that rely on an action uiScript to set their value (see sv_punkbuster in createserver.menu) + added trap_PbSetSvStatus, can't set Punkbuster on server side if not available + new mp_pak5 with updated createserver.menu (for all i18n) + + updated ui scripts for mp_pak5 against ATVI's latest version (merged back new elements) + +2002-24-10 Timothee Besset + + ATVI Wolfenstein Misc #469 + MG42 switching view angles to the player and back (prediction issue) + + ATVI Wolfenstein Misc #470 + fixed MG42 repair hint to work again when crounching or standing on top of MG42 + (breakage was a side effect of mg42 mount hint fix, see 2002-17-10) + + ATVI Wolfenstein Misc #413 + maintain correct mp_currentTeam while doing free fly spectating (was causing problems joining teams) + +2002-21-10 Timothee Besset + + ATVI Wolfenstein Misc #461 + 'voting disabled on server' was not setting cg_draw2d to 0, parasite text was showing + tweaked the translation for 'or callvote a start_match' by 'or call a vote to start match' + wrote French traductions, others are pending (ATVI) + +2002-19-10 Timothee Besset + + ATVI Wolfenstein Misc #439 + making sure we have a default empty error message for the connection diagnostics + so that we don't pop ghost ones back + + ATVI Wolfenstein Misc #460 + cleanly shutting down listen server when using "exit to main" from UI + + ATVI Wolfenstein Misc #467 + mg42 client and client being pushed both become CONTENT_CORPSE till they get in a correct position again + problem was, the call to trap_EntitiesInBox returns the mg42 client to the list when it queries for itself + so numtouch stuck to 1, mg42 never returning to solid again + added a safe check that hit != self + + added g_dbgRevive to verbose the WolfReviveBBox stuff, was getting too noisy + +2002-17-10 Timothee Besset + + merged in mp_pak5.pk3 update from ATVI + + ATVI Wolfenstein Misc #433 + Fixed the screen shaking situation on the client + + Arnout: + + Fixed double MG42 fire sound playing. + + ATVI Wolfenstein Misc #436 + Fixed MG42 muzzle flash not always drawing. + + ATVI Wolfenstein Misc #437 + Fixed revived client getting stuck in MG42 user client + + ATVI Wolfenstein Misc #438 + Fixed ability to use MG42's from above + + ATVI Wolfenstein Misc #449 + Fixed client getting stuck in walls after MG42 gets destroyed + + Fixed MG42 hint icon not being consistent with the actual usage + +2002-16-10 Timothee Besset + + show_bug.cgi?id=562 + Rhea - sniper zoom bug when playing demos + applied fix to demo playing / sniper zoom + +2002-13-10 Timothee Besset + + ATVI Wolfenstein Misc #414 + anytime enterTime gets reset, player is forbidden to switch team for 30s + (with the exception of warmups and 10s after a map_restart) + if player went back to free fly from follow spectating, enterTime was reset. Fixed. + map_restart (i.e. warmup ends) was causing a reset of enterTime for all players. Fixed. + now we only reset enterTime and prevent joining new team only when player explicitely sets new team + +2002-12-10 Timothee Besset + + ATVI Wolfenstein Misc #374 + on first run the ui_vote* variables where not initialized, causing a g_voteFlags 0 + (even if UI showed 'Voting Enabled', as long as you didn't raise the precise voting menu) + added a uiScript voteFlags open in the open menu to cope with this + affects all translations, new mp_pak5.pk3 + + show_bug.cgi?id=560 + LT getting too much ammo (thks Rhea) + +2002-7-10 Timothee Besset + + ATVI Wolfenstein Misc #304 + setCvar "com_errorDiagnoseIP" "" is misread by the parser, second argument is ignored + adding a clearCvar ui script, and updating UI accordingly + +2002-2-10 Timothee Besset + + ATVI Wolfenstein Misc #345 + server URL message text lights up like a button - fixed + tweaked the layout of URL confirm and serverinfo dialogs for all i18n + made the various buttons look right (and not overflow their box and stuff) + + ATVI Wolfenstein Misc #281 + removed map_restart line from server creation menu (we never had that one in the UIs) + +2002-1-10 Timothee Besset + + ATVI Wolfenstein Misc #339 + added switch back to free spectating, binded to the activate key (usually enter) + you can't free spectate if you are in a team already (otherwise you could send indications) + had to go around some ugly things in the limbo management on client side + + ATVI Wolfenstein Misc #350 + made server prediction off by default + +2002-28-9 Timothee Besset + + merged a new mp_pak5.pk3 with ATVI updates + +2002-24-9 Timothee Besset + + SV_CheckPaused using Cvar_Set "sv_paused" instead of setting the integer directly + could have bad consequences over PB cvar hack checks + actually this is not relevant to RTCW because cl_paused is never set to 1, always remains 0 + but it might matter later on, better be on the safe side of things + + cons post build on win32 copies PB stuff + +2002-19-9 Timothee Besset + + show_bug.cgi?id=543 + more rcon fixes propagated from Q3 + +2002-18-9 Timothee Besset + + ATVI Tracker Wolfenstein Misc #322 + global 'Allow Voting' UI toggle was broken + g_allowVote toggles all g_voteFlags on or off when used + + NOTE: we have newer ui_mp/french/filter.menu than the one that was in ATVI's Wolf14 zip + newer mp_pak5.pk3 altogether with #322 + +2002-17-9 Timothee Besset + + ATVI Tracker Wolfenstein Misc #320 + line wrap + skipnotify bug + + ATVI Tracker Wolfenstein Misc #336 + show_bug.cgi?id=550 + quick fix to new rcon code + +2002-5-9 Timothee Besset + + ui_mp/ translations update from ATVI + translated french/filter.menu + FIXME: other filter.menu tranlsations still have to be done + + update build system with an optional 'pk3' on build command line, defaults to off + + show_bug.cgi?id=443 + Linux client: propagated sub-frame event timing from Q3 + +2002-2-9 Timothee Besset + + added g_antilag in filter.menu (Both/Yes/No) + + handy, keep around: __asm__ __volatile__ ("int $0x03"); + + show_bug.cgi?id=540 + 'ui failed pure server check' not doing UI properly was caused by the search path reorder on server join + using FS_Restart in FS_PureServerSetLoadedPaks to fix + + copied over ingame.txt to menus.txt, even if menus.txt is not used, it's safer to have the same unique ver as ingame.txt (which is our actual reference) + + fixed Com_DPrintf being fucked up in ui code + + show_bug.cgi?id=541 + XP backport (script_mover_blocked) + + ATVI Tracker Wolfenstein Misc #263 + non anti-lag servers have their ping highlighted in yellow + +2002-31-8 Timothee Besset + + translations: got updated translations from John Fritts on 30/08 + integrated, fixed french/createserver.menu french/serverinfo.menu which wouldn't load (missing newline) + French/German 'Friendly Fire' in filter.menu was still 'Both Yes No', replaced by correct phrasing + server browser was displaying server lines with Yes/No for punkbuster. Made it translation sensitive + +2002-30-8 Timothee Besset + + ATVI Tracker Wolfenstein Misc #284 + rcon commands where sent after being tokenized and rebuilt + that was breaking any quoting, for instance 'rcon g_motd "hooka pooka"' + added Cmd_Cmd() to retrieve the un-tokenized command and transmit as is on both ends + +2002-28-8 Timothee Besset + + ATVI Tracker Wolfenstein Misc #263 + renamed g_hitscanPredict into g_antilag, made it a CVAR_SERVERINFO + added UI in server creation menu (defaults to 1) + + internationalisation: + added placeholder ui_mp/french/ ui_mp/german etc. .menu files + need the new ones to put into mp_pak5.pk3 + hardcoded translations: translations/translation-1_4.cfg + looks like the "WARNING: You are missing" is a mistake on my side. That has been removed. + NOTE: trailing \n can be omitted in the translation file + NOTE: no 'need paks' translation for spanish / 'need paks' is only printed to console, was not strongly necessary + NOTE: PROTOCOL_MISMATCH_ERROR is a server print, we can't translate this (we do PROTOCOL_MISMATCH_ERROR_LONG) + added CL_TranslateStringBuf and support for \n in translation files (for PROTOCOL_MISMATCH_ERROR_LONG) + NOTE: PB_MESSAGE is server side too, can't deal with it + +2002-27-8 Timothee Besset + + ATVI Tracker Wolfenstein Misc #218 + using [skipnotify], only printing to console + + ATVI Tracker Wolfenstein Misc #244 + fixed Refresh button being broken in server browser server info dialog + + ATVI Tracker Wolfenstein Misc #273 + voting config defaulted to no, the g_voteFlags was initialized in game code only + if you go to ui first, you were getting g_voteFlags 0 (doesn't exist yet) + had to add the init in sv_init.c (which makes it kinda hardcoded, something I don't like) + for testing the fix, need to start from a fresh install (or delete wolfconfig_mp.cfg) + + ATVI Tracker Wolfenstein Misc #270 + disabled lean while tossing dynamite + + ATVI Tracker Wolfenstein Misc #271 + have serverinfo menu be modal over the ingame options window + had to remove the menus.txt + ingame.txt loading order and put everything seperate in menus.txt and ingame.txt + NOTE: the UI_LoadMenus is a mess, ingame or not, that's still ingame.txt that gets loads (previously menus.txt + ingame.txt) + in short, menus.txt is no longer used + +2002-26-8 Timothee Besset + + show_bug.cgi?id=472 + propagate Linux client keyboard fix from Q3 + + show_bug.cgi?id=62 + propagate the invisible players/entities fix from Q3 + (not sure this had ever been happening in Wolf though) + +2002-23-8 Timothee Besset + + ATVI Tracker Wolfenstein Misc #200 #201 + fixed allies timeleft warning not working (was loading the wrong sound) + issue is not new in 1.4, we had that in 1.33 already + + ATVI Tracker Wolfenstein Misc #221 + colored g_motd string is now supported + + improved the pre-map_restart ignoring of cient messages, handles multiple map_restarts in a row + + SV_InitGameVM: clearing the entity array before calling into the VM (reported by djbob, XP backport) + (NOTE: does this fix show_bug.cgi?id=62 invis player?) + + reworked the server 'client text ignored' message to only trigger when there's actually a message that doesn't get to the game VM + + show_bug.cgi?id=376 + had to shrink the rcon buffer to 256-16, seems it's the only value that won't cause to loose part of the output + + ATVI Tracker Wolfenstein Misc #191 + FF off + airstrike -> spectator was still hurting teammates + + updated cons script to run under win32 and do misc handy tasks + +2002-22-8 Timothee Besset + + ATVI Tracker Wolfenstein Misc #196 + fixed voting UI to remember settings with low ACCEPT and BACK buttons + fixed parasite grpVoting still showing when you go back to the menu + + ATVI Tracker Wolfenstein Misc #210 + fixed callvote UI getting messed up in Misc tab + + ATVI Tracker Wolfenstein Misc #193 & #195 + overflow causing stack trashing. We are lucky it didn't cause a crash + limit the numer of lines to 23 to keep in screen + + ATVI Tracker Wolfenstein Misc #219 + made g_motd a CVAR_ARCHIVE + + ATVI Tracker Wolfenstein Misc #212 + correct blue background on buttons in error diagnostic and server info + +2002-20-8 Timothee Besset + + show_bug.cgi?id=536 + tweaking the SV_ExecuteClientMessage code some more to also let through the messages + with bad serverId but last client command was a nextdl + kind of hackish, but that's really the only fix that I can see for now + +2002-15-8 Timothee Besset + + show_bug.cgi?id=534 + fixing rcon being broken on NT/XP with > 23 days uptime (or so) + NOTE: this is ATVI Tracker Wolfenstein Misc #203 + +2002-9-8 Timothee Besset + + show_bug.cgi?id=500 + re-enabled ingame (meaning, non PB) IP banning code with the needed fixes: + + addip doesn't cause an ERR_DROP and overflow on the g_banIPs cvar anymore + + syntax for subnet matching has changed: use 192.168.1.* + the former 192.168.1.0 syntax would have bad consequences, + see show_bug.cgi?id=529 + + added a 'Reconnect' button next to the 'OK' button on the diagnostic screen + (realized that not everyone does have the reflex to toggle console and hit 'reconnect') + + updated build system, pushed version to 1.4 + +2002-8-8 Timothee Besset + + show_bug.cgi?id=405 + dedicated server signal handling fix, propagated from Q3 + + show_bug.cgi?id=480 + dropped clients under heavy load, applied the patch, haven't been able to verify that it's an effective fix at this point + + show_bug.cgi?id=425 + splines code cleanup + + fixed 'vote, die, vote again' exploit + +2002-7-8 Timothee Besset + + latching com_maxfps, this should considerably annoy everyone who's using it for various cheats + + fixing lean / see through walls. this was happening because of bg_pmove.c and cg_view.c + not computing the view offsets the same way. + +2002-6-8 Timothee Besset + + prevent centerview exploit on sniper rifle recoil + blocking centerview for 1s after a zoomed sniper rifle shoot + +2002-3-8 Timothee Besset + + configure voting with the serverinfo cvar g_voteFlags + UI on server side startmenu + UI on client side: retrieve the vote flags, only show the allowed votes + +2002-1-8 Timothee Besset + + fixed the AfterBuild code, now checking for unresolved in the DLLs + unfortunately, this is broken with pcons! + + show_bug.cgi?id=498 + fixed NET_AdrToString to print the port as unsigned int (for ports > 1^^15, was showing negative) + + show_bug.cgi?id=524 + changed default GL driver from libGL.so to libGL.so.1 + see LSB 1.2 spec: http://www.linuxbase.org/spec/refspecs/LSB_1.2.0/gLSB/libgl.html + +2002-31-7 Timothee Besset + + show_bug.cgi?id=517 + ERR_DROP if PB client off / server on conflict when starting local server + +2002-30-7 Timothee Besset + + flame thrower cheat, run and aim at your feet, you don't take damage + fixed by a server side trace, check hit on the ground within a small radius of the player + (might need to configure the tests some more if this triggers too easily) + +2002-24-7 Timothee Besset + + on FF-off servers, if you drop an airstrike and join spectator, teammates would get killed + now the airstrike won't hurt anyone, neither team or opponents + + cleaned up cg_emtpySwitch. replaced by cg_autoReload, pollutes userinfo + + move Sys_PBSendUdpPacket to qcommon/common.c to avoid dupe code win_net.c unix_net.c + +2002-23-7 Timothee Besset + + merged autoreload branch back into RTCW-1_34 + cg_autoReload cvar (defaults to 1) + finalized auto reload code, added UI in the Controls > Shoot menu (next to the reload weapon binding) + + backported pmoveExt_t from XP, storing the auto reload settings for bg_* code there + (deals with transmission to the server too) + + win32 .dsp: added RTCWBASE env variable, copying as post-build step + + show_bug.cgi?id=512 + Wolf server buffer overflow vulnerability, several vsprintf -> Q_vsnprintf to secure + + MAX_SHADERS bumped from 2048 to 8192 (XP backport) + + backport of g_antilag.c from XP (Gordon code) + included in build with appropriate modifs, udpated the data init and trace calls (Bullet_Fire_Extended) + TODO: in XP, we'll have to make antilag the default of regular game + also fixes the headshot behaviour, headshot was checked only if there's collision with main player bbox + + all new g_antilag.c stuff is controled by g_hitscanPredict cvar (default 1) + +2002-12-7 Timothee Besset + + lieutenant ammo pack gives 8 colt/luger rounds + NOTE: the max rounds are 32 for colt/luger, 90 for 2-handed weapon + + using /kill while at MG42 forced player to stay in MG42 anim mode after respawn, fixed + +2002-10-7 Timothee Besset + + Soldier/lieutenant could loose all weapon functionality when dropping weapon while throwing a grenade + fixed, can't drop weapon while holding grenade + + updated the Linux build system, files with the same name compiled to both cgame and game or ui and game + files with same names are confusing gdb when setting breakpoints. dupe cgame files are prefixed with cg_ + and dupe ui files are prefixed with ui_ (cons rocks) + +2002-9-7 Timothee Besset + + added ui in advanced server menu for g_useralliedmaxlives g_useraxismaxlives g_fastres and fastres milliseconds + + added fastres milliseconds config to the game code (I thought this was in already, but it was not) + +2002-8-7 Timothee Besset + + show_bug.cgi?id=509 + strategy for referenced pk3 in mod dirs (fs_game) is now the same as in the basegame + post-connection diagnostic screen shows the list of missing pack files if any + +2002-6-7 Timothee Besset + + not drawing motd while doing auto-download (there's download progress instead) + + using a grey filled rectangle below autodownload messages for readability + misc UI tweaks + +2002-5-7 Timothee Besset + + show_bug.cgi?id=509 + see branch 'test_dl_ratio' for an experiment with pushing up the rate ratio specifically for downloads (foxed) + improved speed of the auto-download + regular server tops at 20kb/s max autodownload + tweaked update server tops at 30kb/s (by pushing up the sv_fps to 60) + added sv_dl_maxRate for independant autodownload max rate (autodownload used to be limited by sv_maxRate) + + show_bug.cgi?id=491 + SV_SendMessageToClient was defeating the sv_lanForceRate setting by forcing snapshot emitting for LAN clients + +2002-4-7 Timothee Besset + + auto-update UI: now showing a modal dialog box on the main menu, forcing the user to click either 'OK' or 'Later' + + misc Linux build system tweaking for auto-update server build + +2002-3-7 Timothee Besset + + show_bug.cgi?id=490 + draw motd on connection screen + + drawing a grey rectangle below error message on connect screen, changed font scale (better readability) + fixed the global motd which was not displaying on connect screen (y offset was offscreen) + +2002-2-7 Timothee Besset + + modified edit field to prevent text getting written outside of it's bounding box + the editfield scrolls like the console does + fixes the "Say: " and Name edit field in limbo menu + + added Paint_AutoWrapped_Text + the hardcoded ui areas (such as UI_LIMBOMENU) didn't allow text wrapping on proportional fonts so far + used the Paint_Text code to make a text wrapping version + also allows to evaluate the size requirements (number of lines) without actually drawing anything + + making limbo chat text wrap and be multiline (based on above) + limbo chat prints text and scrolls irc-style, much more intuitive + +2002-28-6 Timothee Besset + + modified the protocol error code: + the connect screen can't draw too much information, 256 chars is the max + then we print a more complete message in the error diagnostic screen + (i.e. after the client hits esc and aborts connection) + TODO: i18n of those error messages? + + tweaked the diagnostic window, have to click through a button to show server info in a new window + the diagnostic window itself only has the error message and the URL buttons + + reworked the URL button disclaimer completely, prompting each time an URL is going to be opened + (reverted multi_setup.menu to it's old version, removed the persistent pref toggle) + + unified the com_errorMessage code to use the same UI scripts wether or not a server IP is available for diagnostic + + in-game server info button, in the Options menu + the button is present / disabled depending wether the server is local + NOTE: it would be useful to have customizable origin for modal windows too + +2002-27-6 Timothee Besset + + show_bug.cgi?id=507 + adding a "com_errorDiagnoseIP" + if this is set to a server IP, the UI error dialog switches to a different and more complete dialog + which displays server information with clickable URL buttons + +2002-26-6 Timothee Besset + + making protocol mismatch sending a print OOB message with a markup tag to propagate into a com_errorMessage + + made CL_DisconnectPacket use com_errorMessage too to provide a dialog box + +2002-25-6 Timothee Besset + + adding ui_doURL CVAR_ARCHIVE, editable in mutliplayer > multiplayer setup + + adding MAIN/ui_mp/urls.menu (and binding into the loaded set) + + adding SERVERINFO "URL" to g_main.c, defaults empty + + adding 'modal' menuDef keyword, causes menus to be called as truly modal + (the caller menu to return to is stored in a stack) + added WINDOW_MODAL window flag + existing menuStack array, re-using (only for modal windows though instead of at all times) + +2002-24-6 Timothee Besset + + adding CVAR_SERVERINFO|CVAR_ROM "mod_url" to g_main.c + defaults to http://www.activision.com/games/wolfenstein/ + this is meant to be configured programmatically by mod authors and not altered by server config + + addding 'Go to server URL' 'Go to mod URL' to the server browser's serverinfo_popmenu + (with toggles visible / non visible depending on presence of the variable) + +2002-21-6 Timothee Besset + + foreign characters in name confuse clients that don't use the matching locale + adding securities and cleanups to remove all foreign chars from CVAR_USERINFO cvars + (check in Cvar_Set2, and in Cvar_Get) + the cvar in the general case can hold foreign chars (ex. ui_objective), only CVAR_USERINFO are restricted + we could let in foreign chars with the userinfo if there's a cleanup client side + (wether or not the incoming name is in the same local as we are) + that would be a bunch of extra work, and is kinda overkill.. + +2002-20-6 Timothee Besset + + show_bug.cgi?id=506 + multiple/on-demand mp_bin management (mod request, and general utility otherwise) + made mp_bin referenced by server (that's for solving test case #1) + + show_bug.cgi?id=506 + client reorganizes the pk3 search order to match the server's + this solves test case #3, and makes likelyhood of 'invalid .PK3 files referenced' much lower. + + win32 fixups (win32 Sys_GetDLLName) + +2002-19-6 Timothee Besset + + cleanups, dead code removal + + tagging last 1.33 source before starting some 1.34 work + post-release-1_33 + + added securities ldd -r on the .so + needs backporting to 1.33 mod source release + + applying Xian g_maxlives and max lives enforcements code + (with some tweaking) + + propagate tr_image.c ResampleTexture overflow fix from Q3 + +2002-13-6 Timothee Besset + + SAD spawn bug has reappeared (Wolfenstein Misc #114) + attempting a fix: putting a correct srand call in SAD, storing a randomized table of the feeds + +2002-11-6 Timothee Besset + + built a seperate GOTY map pack + building release candidate for 1.33 full, 1.32->1.33 update, and Map Pack + + make lightded setup go look for the correct version string (in SP) + + partly renamed some 'Light Dedicated' to 'Standalone Dedicated' + + using new loki_setup code on the way + +2002-9-6 Timothee Besset + + tweaked the 'Server uses protocol %i' to 'Server uses protocol %i (your client is using %i)' + + show_bug.cgi?id=502 + changed CopyDLLForMod to handle copy error correctly + (only affects Linux client, suspect this could have caused sv_pure bypassing) + +2002-5-6 Timothee Besset + + fix a screwup in light ded code (something very weird broke the checksum generation) + +2002-4-6 Timothee Besset + + nobinaries="yes" in sdk setup, run sdk tests only on spoutnik + +2002-3-6 Timothee Besset + + build_sdk.sh script, added setup content, ready to release mod sdk for Linux + +2002-31-5 Timothee Besset + + cleared up bad fix on cl_guid (PB) + + made g_enforcemaxlives a CVAR_ARCHIVE, and defaulted it to 1 + (cheat exploit protections should always be on by default) + + moved ClearMaxLivesIP call post G_RegisterCvars (otherwise, filterban is not initialized) + + fixed the g_filterBan disabled (forgot to comment out a line) + + the IP information was not enforced in userinfo on server side, added a check in SV_UserinfoChanged + + made sure we only do maxlives enforcement when a game with maxlives is actually running + +2002-30-5 Timothee Besset + + PB DLLs for Mac (v 989) from Tray + + show_bug.cgi?id=500 + disabled g_filterBan again (not functional yet, need good source for 1.33) + + imported changes from Xian, polished and cleaned up a bit for Linux build + + show_bug.cgi?id=491 + added sv_lanForceRate, defaults to 1 + if 0, the clients on a LAN are not forced by the server to max rate, server listens to their settings request + + new WolfReviveBbox code from Splash Damage (fixes a medic revival stucking) + + Xian changes: + * Added g_axismaxlives and g_alliedmaxlives. These cvars superscede + g_maxlives when set. Used for setting maxlives independantly for each team + * Added g_fastres 1 (Defaults to 0). Player instantly gets up, is instantly + mobile, but is only invulnerable for a second vs 3 seconds the old way + * Added g_knifeonly 1 (defaults to 0). This is a novelty for the + mp_destruction crowd on my servers. If this gets ganked, I won't lose any + sleep over it. + + Players spawn in with knives only. Medics get 20 needles instead of 10 + and can drop health as normally. Engineers can drop and arm dynomite. All + the MG42's are removed from the world. + + * Added g_enforcemaxlives 1 (defaults to 0). This code is still a little + buggy for reasons unknown to me. Timothee will give it a once over. We may + choose to make this undocumented. + + The way this works, is that when a client enters the game, their IP gets + added to a list. Any IP on the list is banned from connecting until the + next round starts. This is to prevent people from disconnecting and + reconnecting to get by the maxlives limitation. + + * listmaxlivesip (debug console command). Prints the current list of + clients who's IP's are being enforced with maxlives. + +2002-29-5 Timothee Besset + + PB 989 Linux from Tony (where are win32 DLLs?) + + tracker #414, missing g_filterBan, broken IP filtering + added g_filterBan back, and the corresponding IP filtering calls (upon client connection) + +2002-28-5 Timothee Besset + + adding pcons script, configuring for parallell build + + adding mp_pak4 to light ded checksum, updating the setup + +2002-23-5 Timothee Besset + + last minute update from Activision & SplashDamage for 1.32 setup + +2002-22-5 Timothee Besset + + 1.32 release, got win32 RC, building Linux RC + - some changes in main/ + mp_pak4.pk3 was in SplashDamage's 1.32 zip, is not installed + new/modified files: mp_tram.pk3 sp_pak3.pk3 + - no changes in Docs/, only installs PB manual + + updated rotate.cfg to rotate on mp_tram + + updated CHANGES file (public CHANGES file in setup) + +2002-13-5 Timothee Besset + + new code 1.32-MP from SplashDamage, rebuild + + merge back into trunk, there is going to be a new build + + 1.32g is in QA at Activision, creating a post-1_32g branch + + show_bug.cgi?id=476 + CVAR_CHEAT|CVAR_LATCH .. the latching was happening before the cheating check + switched cheat check before latch + + added anjuta project file and run.sh wrappers + +2002-12-5 Timothee Besset + + added TuxGames info in faq + +2002-7-5 Timothee Besset + + import of splashdamage's updated source + + cleared CG_SELECTEDPLAYER_ARMOR stuff, the menudefs.h doesn't have it + +2002-26-3 Timothee Besset + + SOS update, fixes shader overflow bug + show_bug.cgi?id=471 + +2002-25-3 Timothee Besset + + 1.31 update, grabbed from SOS and rebuilt + + build_setup.sh update: antares needs -L option for cp, spoutnik doesn't + + new PB files, pb/htm/ directory + +2002-16-3 Timothee Besset + + new set of checksums for the light dedicated server (merged in the new mp_ice map pak) + +-- tagging tree as Release-1_3-tag + + + last minute fix to build_setup.sh script + + built Wolf 1.3 setup + +2002-15-3 Timothee Besset + + blanking default CD path + + dedicated + mod -> cl_cdkey buffer overflow, causing a corrupted ucmds + static struct + cleaned up the cl_cdkey and associated functions stuff, + moved declaration to qcommon.h + and cl_cdkey alloc from server/cl_main.c to common/common.c + + media-only mods failed to load because the DLL was not found + we handle this a bit differently than how SP code does, if we can't find a + DLL, we will try to copy it from a search in the base game (with the usual + homepath/basepath search order) + + rebuilt light ded's pak0.pk3, generate new checksums + +2002-13-3 Timothee Besset + + merge from SOS, breaks the custom auto-update server compile time define + (going through several autoupdate servers now) + +2002-12-3 Timothee Besset + + wolf-beta/2002-March/000079.html + switched from system to execl in Sys_DoStartProcess + hacked around to keep a system() call when a command line is needed (HACK!) + + text dialog installation: dialog box wraps the text + +2002-11-3 Timothee Besset + + modified setup, PB is in a seperate option, has his own license prompt + +2002-02-3 Timothee Besset + + autodownload was breaking for files > something between 3Mb and 6Mb + because of autoupdateFilename pointing to a char * returned by va() + (va wrapping past some critical size corrupted the name) + + fixed Cbuf_ExecuteText("quit") to Cbuf_ExecuteText("quit\n") + +2002-28-2 Timothee Besset + + show_bug.cgi?id=464 + changed the 'servers not listed' message, it was misleading + + added ./cons -- nograb compile option to avoid mouse grabbing / easier debug + + show_bug.cgi?id=463 + checking the .so version in fs_homepath against main binary version to avoid outdated ones + + show_bug.cgi?id=462 + send potential remaining fragmented packets before sending a gamestate + +2002-27-2 Timothee Besset + + 1.3.b1 with new setup code (xsu-enabled) + auto update server pointing to zerowing + +2002-26-2 Timothee Besset + + show_bug.cgi?id=455 + removed old libMesaVoodooGL.so loading code + Voodoo cards should use XF4/DRI, that load code was outdated and confusing people with broken OpenGL + + bumped GL extension string buffer to avoid truncate + + added NV fog extension to MP, propagated existing SP code + added corresponding UI code + + validated on win32 + + updated Linux faq + +2002-02-25 +- fixed missing param in 'dropped gamestate, resending' Com_DPrintf message + +2002-02-21 Timothee Besset +- update_mp_bin.sh to update new stripped .so to the mp_bin.pk3 + ./update_mp_bin.sh + +2002-02-20 Timothee Besset +- tweaks to win32 auto-update Sys_StartProcess + +2002-02-19 Timothee Besset +- adapted the auto-update code to linux + saving in the right spot, doing a chmod +x before execution +- tweaking Sys_StartProcess on linux, kernel panic if no sleep() temporisation between the spawn and the exit +- added a Sys_Chmod on linux + +2002-02-18 Timothee Besset +- merging in auto-update server code from SOS +- added the appropriate build system changes to compile update server + +2002-02-17 Timothee Besset +- merged DLL path in vm_s from SOS + +2002-02-15 Timothee Besset +- merged all dllchecksum stuff to trunk +- updated from more recent SOS +- fixed win32, tested on win32, build light dedicated bins on win32 + +--- branch dllchecksum --- + + TODO: hanging at map load screen on disconnect is still present (linux client only issue AFAIK) + +- renamed FS_ExtractFromPakFile to FS_CL_ExtractFromPakFile, only compiled with client code + +- light dedicated server: + we won't collapse mp_bin.pk3 into the single light dedicated pak0 + because it's bound to change regularly and that would mean recomputing the set of checksums each time + tested light dedicated to work the same against the test cases used for full server + +- isolated on branch for testing and polish of the dll checksum code / testing of light dedicated + + TODO: if server w/ different mp_dll.pk3 than the client, doesn't pass the sv_pure check + client bails with: + Sys_Error: Couldn't extract ui.mp.i386.so + that would be the most common error users would get, would be better if we could go back to UI / Disconnect cleanly + + TODO: need also to make sure that it's not possible to auto-download the mp_dll pk3? could be used to hack clients + from a fake server (this situation doesn't happen right now because of the above error) + +- cleanup to easier cross platformness of the DLL checksuming code +- plugged destData mem leak in FS_ExtractFromPakFile +- moved the declaration of FS_BuildOSPath and FS_ExtractFromPakFile to qcommon.h for cleaner stuff +- adding centralized per-OS SYS_DLLNAME_* defines in qcommon.h for the DLL strings + (makes the code in SV_VerifyPaks_f and FS_ExtractFromPakFile unified between OSes) +- string shifting func decs moved to qcommon.h + +2002-02-14 Timothee Besset +- modified unix_main.c Sys_LoadDll to scan pwd/homepath/basepath (homepath/basepath only for release build) + made the basics of the DLL checksuming work on linux (SV_VerifyPaks_f, extraction of the DLL files) +- sv_pure instead of cl_connectedToPureServer in Sys_LoadDll (so that dedicated builds too) + (TODO: same on win_main.c) +- modified FS_ExtractFromPakFile to take the file to check as input + wrote the *nix-style path scanning in unix_main.c, passing to FS_ExtractFromPakFile + (TODO: fix the win32 build) + +2002-02-13 Timothee Besset +- merging in sv_pure 1 / pb 0 DLL checksuming code + TODO: linux / linux client server + linux / win32 client server combinations + light dedicated + TODO: make sure disabled if PB is on + + TODO: start testing for mods too? + + Current state: + builds on linux, won't run anything with sv_pure 1 + (the linux parts of the checks are not in) + +2002-02-11 Timothee Besset +- updated the cons build script + +2002-02-08 Timothee Besset +- tweaking the 'out of disk space' error message for linux ver + +2002-02-06 Timothee Besset +- client/cl_main.c, localisation code disabled on Mac +- merged in latest version from SOS +- looking at various constants that are different for the Mac version + client/snd_local.h + MAX_CHANNELS was changed from 96 to 64 for Mac + MAX_STREAMINGSOUNDS from 24 to 12 for Mac + client/snd_mem.c + sfxScratchBuffer moved from malloc to Hunk_Alloc (all ver) + game/bg_public.h + various MAX_* constants: + #if defined(__MACOS__) //DAJ HOG + #define MAX_ANIMSCRIPT_MODELS 32 //DAJ tried 24 // allocated dynamically, so limit is scalable + #define MAX_ANIMSCRIPT_ITEMS_PER_MODEL 1024 //512 + #define MAX_MODEL_ANIMATIONS 256 // animations per model + #define MAX_ANIMSCRIPT_ANIMCOMMANDS 9 + #define MAX_ANIMSCRIPT_ITEMS 64 + #else + #define MAX_ANIMSCRIPT_MODELS 32 + #define MAX_ANIMSCRIPT_ITEMS_PER_MODEL 2048 + #define MAX_MODEL_ANIMATIONS 512 // animations per model + #define MAX_ANIMSCRIPT_ANIMCOMMANDS 8 + #define MAX_ANIMSCRIPT_ITEMS 128 + #endif + qcommon/common.c + MacOS isn't forced to a minimum 16Mb com_zoneMegs + server/sv_ccmds.c + sv_maxclients defaults to 16 on Mac (32 otherwise) + server/sv_init.c + sv_maxclients Cvar_Get default to 16 on Mac (20 otherwise) <- is this a dupe? + ui/ui_shared.c + Controls_SetConfig doesn't do a in_restart on Mac, does otherwise + +2002-02-05 Timothee Besset +- merged from WestLake-1_1b into the trunk +- checked on win32 (full and light dedicated, + PB code) + + -- branch WestLake-1_1b -- +- merging in Mac MP code update + byte swap for aas_routingcache_t + smaller memory footprint on some static structs (grep for HOG) + renderer/tr_model.c: the r_cache 1 (R_Hunk_Begin, R_Hunk_Alloc) code is disabled in the Mac version + relies on direct ri.Z_Malloc calls + keeping the original version working with a 24Mb hunk + in_restart command removed from Controls_SetConfig (ui_shared.c) + adding the whole mac/ directory tree +- guarding the #pragma export on/off with __MACOS__ +- guarding DLL_ONLY for __MACOS__ +- cl_main.c, 'Disconnected from server' message was commented out? (re-enabled) +- fixed dupe CL_SaveTranslations_f definition +- tr_image.c R_FindImageFile, uppercase search with developer messages + +2002-2-4 Timothee Besset +- bumped the versions to 1.2.b4 +- merged the lightweight dedicated server code from Release-1_1b-branch into trunk +- merged and tested linux PB code +- wrote linux setup code for light dedicated server (./cons -- release light setup) +- upgrade makeself to fix a CRC bug + +2002-2-2 Timothee Besset +- updated from SOS, more linux fixes around the PunkBuster code + +2002-1-30 Timothee Besset +dedicated server branch + - defaulting to dedicated 2 (i.e. internet) in the dedicated server code + - started linux setup code for light dedicated + +2002-16-1 Timothee Besset +- show_bug.cgi?id=441 + adding brandelf calls to the setup building process so that our binaries run on BSD + +- preparing for 1.1b release, set version to 1.1b (first RC) + +2002-1-16 Timothee Besset +- show_bug.cgi?id=452 + if doexit, spawn the process at actual process exit + (after GL shutdown and other resources have been freed) +- show_bug.cgi?id=439 + Alt+ENTER fullscreen/windowed toggle + +2002-1-15 Timothee Besset +- show_bug.cgi?id=440 + we were leaking 24Mb of RAM on each R_Init, + re-allocating, not freeing existing chunk + not doing the re-alloc now, sticking to the initial mem chunk +- finalize lightweight dedicated code after recent merges + allow .bsp load from pak0.pk3 in lightweight and demo.. + generated 5 checksumfeed / pure checksums pairs to use randomly + shifted the strings stored in the .exe to be safe + validated individually each of the 5 checksum/string pairs + +2002-1-11 Timothee Besset +- merged in from SOS, PB stuff and new gameplay type +- updated win32 project files, Dedicated Debug/Dedicated Release + added PB files to the .dsp + PB currently disabled on win32 light dedicated (and disabled on linux) + +2002-1-10 Timothee Besset +- started internal testing on light dedicated, building test binaries +- win32 fix: need to #define DEDICATED and DO_LIGHT_DEDICATED + +2002-1-8 Timothee Besset +- implemented sv_pure checksums faking for light dedicated server + currently restricted, runs with an hardcoded fs_checksumFeed +- fixed openurl.sh not being executable in setup + +2002-1-7 Timothee Besset +- building .pk3 for dedicated only server, 22Mb +- unix/extract-dedicated.pl util script +- unix/dedicated-only.txt +- building a dedicated on win32, added Debug Dedicated and Release Dedicated + (also checked in the A3D .lib) + +2002-1-2 Timothee Besset +- full Wolf setup on linux now includes SP stuff + will spawn cons in the SP tree for build and copy the binaries + added a WolfMP.xpm icon and the relevant script stuff + updated the INSTALL file displayed by the setup + +2002-1-1 Timothee Besset +- updated FAQ with BSD info (bug #441) + +2001-12-31 Timothee Besset +- bumped version to 1.2-MP +- show_bug.cgi?id=419 + reorganizing the Sys_OpenURL to use a working Sys_StartProcess + propagating that code to Wolf SP + +2001-12-25 Timothee Besset +- building release setups for 1.1-MP (and Multiplayer DEMO) +- tagging the tree Release-1_1-MP + (Wolfenstein WolfMedia-demo WolfMedia-main modules) + +2001-12-24 Timothee Besset +- show_bug.cgi?id=371 + hacked out to force PROT_READ and safe memset code in all cases +- show_bug.cgi?id=427 + opening URL through an openurl.sh script file +- Sys_OpenURL and Sys_StartProcess exit with Cbuf_ExecuteText( EXEC_APPEND, "quit" ); + +2001-12-23 Timothee Besset +- show_bug.cgi?id=422 + using .mp. (qagame.mp.i386.so etc.) for all MP stuff +- protocol 50 for the demo +- bringing the setups up to speed again, full setup and demo setup living together in the tree + +2001-12-13 Timothee Besset +- Sys_OpenURL stub + +2001-12-12 Timothee Besset +- show_bug.cgi?id=390 + SIG_IGN on SIGTTIN SIGTTOU + +2001-12-10 Timothee Besset +- tweaking Sys_LoadDll load order: pwd first, then basepath + (NOTE: currently there's no homepath loading, should add that) +- tweaked vm_* defaults code + +2001-12-06 Timothee Besset +- show_bug.cgi?id=402 + full scene bug fix + +2001-12-02 Timothee Besset +- unix/const-arg.c file, about const vec3_t[3] problems + +2001-12-01 Timothee Besset +- Wolf MP source passes the -Wall -Werror + +2001-11-30 Timothee Besset +- Updated the Wolfenstein FAQ + http://zerowing.idsoftware.com/linux/wolf + +2001-11-29 Timothee Besset +- Wolf 1.0.b3 Linux + fixed FreeBSD/OpenBSD/NetBSD setup + +2001-11-27 Timothee Besset +- Wolf 1.0.b2 Linux + setup scripts and media for the complete multiplayer version of the game + single player spawn in the menu is inoperant + +2001-11-19 Timothee Besset +- show_bug.cgi?id=51 + propagated a Q3 fix, Wolf 0.9.20-5 + +2001-11-17 Timothee Besset +- building 0.9.20-4 for release, tagging the tree as MPTest-0_9_20-4 +- merging bug #371 fixes into trunk, 0.9.20-4 + +2001-11-14 Timothee Besset +- show_bug.cgi?id=388 + server not restarting correctly at time wrap + modified the way we restart when time wrap occurs + will no longer hang with no server after some time + +2001-11-12 Timothee Besset +- third iteration of sound code fixes, should be all good + and ready to rollback to Q3 source too + +2001-11-09 Timothee Besset +- using temp branch Bug-371 for sound crash stuff testing (bug #371) + PROT_READ|PROT_WRITE fails on Alsa, defaulting to PROT_WRITE only after initial failure + (mmap failure returns -1 and not NULL, our failure check was bugged) + +2001-11-08 Timothee Besset +- setup script fix propagated from Q3 (correct return code) + +2001-11-04 Timothee Besset +- removed game/g_arenas.c (SP stuff) + +2001-11-01 Timothee Besset +- updating for MP test2, version 0.9.20 + linux version of extractfuncs (has different cmd line) + -- demo switch in the cons scripts + updated the setup script, auto-extracting the Wolf version + no longer hacking our way to assemble the .nasm files + +2001-10-15 Timothee Besset +- updated from the SOS source, rebuilt (added a dummy Sys_StartProcess) + branch Files-1_30, Q3 1.30's filesystem with ~/.wolf support + +- merging the Q3 filesystem code for home directory support: + files.c uses a MAX_ZPATH instead of MAX_QPATH for some names + wolf source has a fs_verbose cvar? (only read usage, removed it) + fs_basegame support is new + fs_patchpath and fs_patch_oldpath in Wolf? are used to generate lists + of the used files and generate a .bat of the files to remove + doesn't seem to be a Wolf addition, is not in Q3 .. getting rid of it + fs_filecompare removed + FS_Delete is not in Q3 1.30, but used in Wolf + some file streaming code not there either? + maintained mp_*.pk3 sp_*.pk3 code in FS_AddGameDirectory + FS_ComparePaks has unpure diagnostics (more verbosive) + FS_PatchDeletedFiles_r removed + leaving early exit FS_SetRestrictions (bypass copy protections) + updated qcommon.h for MAX_FILE_HANDLES + + Sys_DefaultInstallPath / sys_DefaultHomePath + we have implementation in unix/unix_shared.c + missing the win32 implementation, grabbing the one from Q3 (win_shared.c) + declaration is in qcommon.h + + TODO: qcommon/files.c has some Com_ReadCDKey code and stuff + +- fixed recursive errors related to logfile 1 + +2001-10-08 Timothee Besset +- added PROT_READ to mmap on dma sound buffer (cf Q3 bug) +show_bug.cgi?id=371 +Com_Memset on dma buffer in Wolf doesn't happen systematically on startup +but happens on map load + +- applied Sys_LoadDll changes from Q3 + +2001-09-28 Timothee Besset + +Planning the merge with wolfMPTest branch +updated from SOS, merged current trunk content + +cvs merge with WolfMPTest branch +checked build system, all fine +there is new media, building from the MP Test and new stuff in SOS +specifically ui_mp/ directory +need to build as regular binary, dropping PRE_RELEASE_DEMO define +dropping the -debug suffix on debug .so name, useless +NOTE: should drop the loading into base path too, and use ./ ? + +Checked the merge result on win32: +- cleaned CritialSection into CriticalSection +- fixed extractfuncs to ignore "qmin" and "qmax" (OSX) + +WolfMPTest branch Changelog ========== BEGIN +2001-09-28 Timothee Besset + +Updated the FAQ (http://zerowing.idosftware.com/linux) +Answered some tech support email and started bug tracking +This version is going to be merged back in the trunk +Tagging it to WolfMPTest-Merge + +2001-09-21 Timothee Besset + +Current SOS is not compatible with MPTest +branched in the last comaptible state +building linux clients for release +removing wolfconfig.cfg from the setup +patched vm.c to vm_* 0 + +added cons -- setup to call build_setup.sh +heavily patched setup script to turn it into a full setup. +updated the INSTALL file + +merging in Graeme's source +merged in - cgame/cg_main.c + Graeme particles re-init fixes +I have newer - cgame/cg_players.c CG_TrailItem + Darin / Version 2 on 09/18/2001 + Don't draw icon above your own head + built cg_players.c.patch +I have newer - cgame/cg_scoreboard.c WM_DrawClientScore + Darin / Version 2 on 09/18/2001 + added drawing of treasure holder in scoreboard + built cg_scoreboard.c.patch +merged in - cgame/cg_servercmds.c + Graeme CG_ClearTrails call commented out in CG_MapRestart +replaced - cgame/cg_trails.c + Graeme various changes +merged in - client/cl_cin.c + Graeme commented out some OSX only code +I have newer - client/cl_main.c + up to Sean / Version 11 on 09/18/2001 + morv cvar inits in CL_Init mostly (other small fixes) + built cl_main.c.patch +leaving as is - client/snd_dma.c + I have fixed Sys_InitializeCriticalSection (was missing a 'c') +leaving as is - client/snd_mix.c + I have moved some vars to static for asm implementation snd_mixa.s +leaving as is - client/snd_public.h + only comments differ +I have newer - extractfuncs/extractfuncs.h + SMF - workaround for Graeme's predifined MACOSX functions + built extractfuncs.c.patch +I have newer - game/bg_misc.c + DHM - Nerve :: updated delay so prediction is correct (panzerfaust) + built bg_misc.c.patch +leaving as is - game/g_client.c + comments differ +I have newer - game/g_cmds.c + Darin / Version 7 on 09/17/2001 + Can't mount an mg42 while ducking + built g_cmds.c.patch +reverting - game/g_misc.c + my version has Darin / Version 3 on 09/17/2001 + Testing new firing rate and damage + since it was testing we should probably not release a new server + that does the MG42 differently +I have newer - game/g_weapon.c + Darin / Version 7 on 09/17/2001 + Don't allow engineer to repair mg42's if he is mounting one! + built g_weapon.c.patch +merging in - game/q_shared.c + MACOS_X implements qmax / qmin + FIXME: I also added #ifndef max #define max.. for linux in q_shared.h a while ago + should we use qmax everywhere instead? (prolly so) +merging in - game/q_shared.h + idSqrt ppc implementation + FIXME: this is currently guarded for OSX, it should be a CPU guard? + linux ppc for instance should benefit? +replaced - macosx/ + copying over the whole macosx/ tree in my source + NOTE: newly added SOS files might be missed in the process, + hopefully there's none +I have newer - qcommon/common.c + changes 'generated by quake' into 'generated by RTCW' + built common.c.patch +leaving as is - qcommon/qcommon.h + critical section missing a 'c' +leaving as is - qcommon/unzip.c + my version has gcc warnings bug fixes, not important for OSX building +partial merge - qcommon/vm.c + I have cleaned up vm_* init, but OSX should still init on vm_* 0 + so I'm not building a patch for this + Graeme switched MACOS_X to original id code in VM_DllSyscall + (merged in) +conflict - renderer/tr_animation.c + merging in R_CalcBoneLerp, exiting in some cases + (OSX fails on random memory reads) + I had a different solution for baseIndex = ((int)pIndexes - tess.indexes); + in RB_SurfaceAnim .. used + baseIndex = (int)(pIndexes - (int *)tess.indexes); + that does a int difference + Graeme's fix denotes a better understanding of what's going on + using his code +I have newer - server/server.h + Sean / Version 3 on 09/17/2001 + TA Merge - adds a qboolean connected; to challenge_t + built server.h.patch +I have newer - server/sv_client.c + Sean / Version 4 on 09/17/2001 + TA Merge, see above + built sv_client.c.patch + NOTE: also has VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); commented out +I have newer - server/sv_main.c + Sean / Version 5 on 09/17/2001 + TA Merge, see above + also has some comment of my own about rcon redirect issues + built sv_main.c.patch +leaving as is - splines/splines.cpp + gcc warning fix and MSVC-isms + NOTE: still not sure the splines code is used in the client +leaving as is - splines/splines.h + comments differ +I have newer - ui/ui_local.h + Sean / Version 2 on 09/18/2001 + NERVE - SMF - new font stuff + built ui_local.h.patch +I have newer - ui/ui_main.c + Sean / Version 14 on 09/18/2001 + NERVE - SMF - new font stuf + built ui_main.c.patch +I have newer - ui/ui_shared.c + see above + built ui_shared.c.patch +I have newer - ui/ui_shared.h + see above + MAIN/UI include case sensitivity fix + built ui_shared.h.patch +leaving as is - unix/ChangeLog / Cons* / build_setup.sh + that's my stuff +merging in - unix/unix_net.c + OSX fixes +merging in - unix/unix_shared.c + Sys_DefaultBasePath deals with installPath + init critical section returns (void *)-1 + NOTE: some changes by me, leaving as is for now +leaving as is - unix/setup subdirectories +leaving as is - win32 + +got a crash on zerowing server, fixed in g_cmds.c +rebuilt g_cmds.c.patch for zaphod +same as previously happened: + +#0 0x805ee66 in CM_Trace (results=0xbfff6d60, start=0x14, end=0xbfff6ef8, + mins=0x45a1e3c0, maxs=0x45a1e3c0, model=0, origin=0x80b97a0, + brushmask=1, capsule=0, sphere=0x0) + at debug-x86-Linux-2.1/dedicated/qcommon/cm_trace.c:1170 + +start=0x14 is fucked + +(gdb) frame +#7 0x459bc9a1 in ClientDamage (clent=0x47841e3c, entnum=3, enemynum=74, + id=182) at debug-x86-Linux-2.1/game/game/g_cmds.c:1970 +1970 in debug-x86-Linux-2.1/game/game/g_cmds.c + +(gdb) print enemy->client->ps.origin +Cannot access memory at address 0x14. + +WolfMPTest branch Changelog ========== END + +2001-09-20 Timothee Besset + +First native linux Wolf client ran on: +Thu Sep 20 09:53:38 CEST 2001 + +some crashes because of my tr_animation.c stuff, +need to use Graeme's .. patching from his info + +cleanup of cons script, building debug and release + +merging again against latest SOS +recompiling and checking back in? +NOOO .. the protocol has changed .. +tagging the WolfMP test compatible as WolfMPTest + +no conflict M src/cgame/cg_event.c +no conflict M src/cgame/cg_main.c +no conflict M src/cgame/cg_newDraw.c +no conflict M src/cgame/cg_players.c +no conflict M src/cgame/cg_weapons.c +no conflict M src/client/cl_main.c +M src/game/bg_animation.c +M src/game/bg_misc.c +M src/game/bg_pmove.c +M src/game/bg_public.h +M src/game/g_client.c +M src/game/g_cmds.c +M src/game/g_missile.c +M src/game/g_session.c +M src/game/g_weapon.c +M src/game/q_shared.h +no conflict M src/qcommon/msg.c +M src/qcommon/qcommon.h +no conflict M src/ui/ui_players.c + +2001-09-19 Timothee Besset + +Merging new SOS stuff in my CVS, modified files are: +M src/cgame/cg_main.c +M src/cgame/cg_players.c +M src/cgame/cg_scoreboard.c +M src/cgame/cg_trails.c +M src/client/cl_main.c +merged M src/client/snd_public.h +M src/extractfuncs/extractfuncs.c +merged M src/game/bg_misc.c +merged M src/game/g_client.c +merged M src/game/g_cmds.c +merged M src/game/g_misc.c +merged M src/game/g_weapon.c +merged M src/qcommon/common.c +M src/qcommon/unzip.c +M src/server/server.h +merged M src/server/sv_client.c +merged M src/server/sv_main.c +M src/ui/ui_local.h +M src/ui/ui_main.c +M src/ui/ui_shared.c +M src/ui/ui_shared.h + +Also need to add on Vendor side the files I added in SOS +those files might have been modified by Id since then too! + +merged unix/unix_net.c +merged unix/unix_shared.c + +#1 merge the ones we know +#2 do a diff + +merge completed fine, gonna rebuild dbg of server and +restart it on zerowing + +unzip.c modified to use statics (OSX conflict), merged with +the Q3 version / comment out unused statics to fix gcc warnings + +using new version tag: +Wolf 0.7.16-1 ( -1 for patchlevel 1 on linux, not checked in SOS) + +writing cons script for full client now +would need to share some stuff? +botlib dependency etc.? +(different targets will need different compile flags and config, +but the idea is that the files need to be listed only once +in our scripts) + +additional issue: the splines code +is that going to require g++ everywhere? +or g++ for splines compile +gcc for regular stuff +g++ for linking + +copied back a bunch of stuff from Q3 source +using snd_dma.s again for S_WriteLinearBlastStereo16 + +2001-09-17 Timothee Besset + +Update from SOS, post MP Test from Nerve, OSX check-ins, +merging everything and checking in too + +2001-09-16 Timothee Besset + +added -fshort-enums to the flags, catches enum warnings +adding a -1 valued enum in enum declarations + +.//botlib/be_aas_entity.c:453: + // TTimo WTF?? qboolean blocking + // warning: comparison is always false due to limited range of data type + // if (VectorCompare( absmin, absmax ) && blocking < 0) { + if (VectorCompare( absmin, absmax )) { + +game/ai_cast_characters.c:1117 + // TTimo gcc: warning: comparison is always false due to limited range of data type + // initial line: if (trap_GetTag( ent->s.number, painTagNames[0], &or ) < 0) + if (!trap_GetTag( ent->s.number, painTagNames[0], &or )) { + +game/ai_cast_characters.c:1128 + // TTimo gcc: warning: comparison is always true due to limited range of data type + // initial line: if (trap_GetTag( ent->s.number, painTagNames[tagIndex], &or ) >= 0) + if (trap_GetTag( ent->s.number, painTagNames[tagIndex], &or )) { + +2001-09-15 Timothee Besset + +cons-based building system is now equivalent to existing +one based on "regular makefiles". not gonna use it for +MP test builds though, don't want to take the risk. + +final MP Test source and media is ready +updating the code and building final binaries +merging with recent SOS changes +new media! + +adding wolfconfig.cfg to the distribution (vm_game 0 etc.) +there is a potential problem with the mp_beachtest.script +it doesn't seem to be broadcasted to clients + +0.7.15 win32 release doesn't use Id's SOS source tree the +linux dedicated got built upon. +Patched qcommon/files.c to let .script files go through +FS_FOpenFileRead outside of the directory (fs_restrict 1 test) + +2001-09-14 Timothee Besset + +updated from SOS, this is supposed to be the source used for the MP test release +merging in with my CVS tree +NOTE: CVS conflicts solving, only looking at the diff if the file is in the + list of modified files from SOS + +some of Zaphod's fixes overlap my stuff (OSX builds with gcc) + +nothing in botai + +nothing in botlib + +client: +cl_cgame.c +cl_parse.c +not affecting my changes + +game: +ok M src/game/ai_cast_script_actions.c +ok M src/game/bg_animation.c +ok M src/game/bg_misc.c +ok M src/game/bg_pmove.c +ok M src/game/bg_public.h +ok M src/game/g_active.c +ok M src/game/g_client.c +ok M src/game/g_cmds.c +ok (no diff) M src/game/g_local.h +ok M src/game/g_mover.c +ok M src/game/g_script.c +ok M src/game/g_tramcar.c +ok M src/game/g_weapon.c +ok M src/game/q_shared.h + +nothing in qcommon + +in server: +ok (no diff) sv_init.c + +2001-09-13 Timothee Besset + +patched the build system and pak checksum to run in demo mode + +2001-09-11 Timothee Besset + +setting -Werror back on +cleaning up gcc warnings + +some non-benign things: + +ai_cast_think.c:739: warning: left-hand operand of comma expression has no effect +// TTimo gcc: left-hand operand of comma expression has no effect +// initial line was: for (i = 0; i < aicast_maxclients, clCount < level.numPlayingClients; i++, ent++) +for (i = 0; ( i < aicast_maxclients ) && ( clCount < level.numPlayingClients ) ; i++, ent++) + +same on ai_cast_think.c:869 + +g_items.c:1000: warning: left-hand operand of comma expression has no effect + +now builds completely without warnings + +next: build with cons? +started putting up cons scripts, saw the major issues, no functional build system yet. have to switch to compile new binaries for Id testing this afternoon. + +2001-09-08 Timothee Besset + +building dedicated server: + +- game/bg_public.h uses key_t conflicts with + /usr/include/sys/types.h:122: previous declaration of `key_t' + renamed to bg_key_t +- some FS_ReadFile casting to (void **) +- stricmp usage is banned for portability reasons, use Q_stricmp +- pak checkum with unsigned ints (gcc warning) +- md4.c guarded #pragma between #ifdef WIN32 +- net_chan.c comment out Netchan_ScramblePacket (defined but not used) +- added () around TFL_DEFAULT #define +- fixed some %d into %ld (warning: int format, long int arg (arg 3)) +- moved the key completion stuff from cl_key.c to common.c +- be_aas_routetable.c is new +- suspicious be_aas_routetable 954, while (trav = next) +- SV_GetTag in server/sv_game.c +- new function Sys_DefaultBasePath in win_main.c + used in Cvar_Get "fs_basepath" default in files.c (filesystem init) + Q3 uses Sys_DefaultInstallPath instead, stubs to Sys_Cwd as Sys_DefaultBasePath does for win32 + adding equivalent Sys_DefaultBasePath to unix_shared.c + +building game dll: + +- game compilation defines GAMEDLL +- duplicate trunk_states in g_tramcar, named plane_states correctly +- unresolved BoxOnPlaneSide + after looking at Q3 qagame source, the symbol is not used + but it's extracted from g_funcs.h and g_func_decs.h + patched the two files to solve the problem +- other unresolved: + undefined symbol: strlwr (debugi386-glibc/baseq3/qagamei386.so) + undefined symbol: stricmp (debugi386-glibc/baseq3/qagamei386.so) + use of strlw and stricmp should be PROHIBITED for portability reasons, use the provided + Q_strlwr and Q_stricmp instead + undefined symbol: max (debugi386-glibc/baseq3/qagamei386.so) + undefined symbol: min (debugi386-glibc/baseq3/qagamei386.so) + added #define in q_shared.h + +the very first linux RTCW dedicated server was born on Mon Sep 10 9:45:00 CEST 2001 +ran a few minutes, checked that I could join the game from win32 client + +since the g_func.h and g_func_dec.h had to be patched, gonna use linux_ prefixed one and maintain by hand diff --git a/src/unix/Conscript-cgame b/src/unix/Conscript-cgame new file mode 100644 index 0000000..4e25f6c --- /dev/null +++ b/src/unix/Conscript-cgame @@ -0,0 +1,74 @@ +# cgame + +Import qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR BASEGAME do_lddabort CC CXX LINK ); + +$env = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CPPPATH => '#../game:#../ui', # needed for cg_ files + CFLAGS => $BASE_CFLAGS . '-fPIC -DCGAMEDLL', + LDFLAGS => '-shared -ldl -lm' +); + +# some files are compiled and linked in to several .so +# this confuses gdb when setting breakpoints + +@CG_FILES_IN = qw ( + ../game/bg_animation.c + ../game/bg_misc.c + ../game/bg_pmove.c + ../game/bg_slidemove.c + ../game/q_math.c + ../game/q_shared.c + ../ui/ui_shared.c + ); + +# run through and process into cgame specific +my @CG_FILES_OUT; +foreach (@CG_FILES_IN) +{ + $source = $_; + $source =~ s/.*\/(.*)/..\/cgame\/cg_\1/; + push @CG_FILES_OUT, $source; +} + +InstallAs $env [@CG_FILES_OUT], [@CG_FILES_IN]; + +$CG_FILESREF = \@CG_FILES_OUT; + +@FILES = qw( + ../cgame/cg_consolecmds.c + ../cgame/cg_draw.c + ../cgame/cg_drawtools.c + ../cgame/cg_effects.c + ../cgame/cg_ents.c + ../cgame/cg_event.c + ../cgame/cg_flamethrower.c + ../cgame/cg_info.c + ../cgame/cg_localents.c + ../cgame/cg_main.c + ../cgame/cg_marks.c + ../cgame/cg_newDraw.c + ../cgame/cg_particles.c + ../cgame/cg_players.c + ../cgame/cg_playerstate.c + ../cgame/cg_predict.c + ../cgame/cg_scoreboard.c + ../cgame/cg_servercmds.c + ../cgame/cg_snapshot.c + ../cgame/cg_sound.c + ../cgame/cg_spawn.c + ../cgame/cg_syscalls.c + ../cgame/cg_trails.c + ../cgame/cg_view.c + ../cgame/cg_weapons.c + ); +$FILESREF = \@FILES; + +# FIXME CPU string +Program $env 'cgame.mp.i386.so', @$FILESREF, @$CG_FILESREF; +my $path = FilePath('cgame.mp.i386.so'); +AfterBuild $env 'cgame.mp.i386.so', "[perl] &ldd_check::do_check(\'$path\', $do_lddabort)"; + +Install $env '#' . $CONFIG_DIR . '/out/' . $BASEGAME, 'cgame.mp.i386.so'; diff --git a/src/unix/Conscript-client b/src/unix/Conscript-client new file mode 100644 index 0000000..af60f98 --- /dev/null +++ b/src/unix/Conscript-client @@ -0,0 +1,264 @@ +# full client build script + +Import qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR CC CXX LINK ); + +# splines +$env_splines = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CFLAGS => $BASE_CFLAGS + ); + +@SPLINES_FILES = qw( + ../splines/math_angles.cpp + ../splines/math_matrix.cpp + ../splines/math_quaternion.cpp + ../splines/math_vector.cpp + ../splines/q_parse.cpp + ../splines/q_shared.cpp + ../splines/splines.cpp + ../splines/util_str.cpp + ); +$SPLINES_FILESREF = \@SPLINES_FILES; + +Library $env_splines 'splines', @$SPLINES_FILESREF; + +# botlib +# FIXME TTimo +# we already deal with botlib on dedicated target +# the compilation options are a bit different but we could certainly rely on +# the same Conscript and make things cleaner + +$env_botlib = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CFLAGS => $BASE_CFLAGS . '-DBOTLIB ' +); + +@BOTLIB_FILES = qw( + ../botlib/be_aas_bspq3.c + ../botlib/be_aas_cluster.c + ../botlib/be_aas_debug.c + ../botlib/be_aas_entity.c + ../botlib/be_aas_file.c + ../botlib/be_aas_main.c + ../botlib/be_aas_move.c + ../botlib/be_aas_optimize.c + ../botlib/be_aas_reach.c + ../botlib/be_aas_route.c + ../botlib/be_aas_routealt.c + ../botlib/be_aas_routetable.c + ../botlib/be_aas_sample.c + ../botlib/be_ai_char.c + ../botlib/be_ai_chat.c + ../botlib/be_ai_gen.c + ../botlib/be_ai_goal.c + ../botlib/be_ai_move.c + ../botlib/be_ai_weap.c + ../botlib/be_ai_weight.c + ../botlib/be_ea.c + ../botlib/be_interface.c + ../botlib/l_crc.c + ../botlib/l_libvar.c + ../botlib/l_log.c + ../botlib/l_memory.c + ../botlib/l_precomp.c + ../botlib/l_script.c + ../botlib/l_struct.c +); +$BOTLIB_REF = \@BOTLIB_FILES; + +Library $env_botlib 'botlib', @$BOTLIB_REF; + +# jpeg +# NOTE TTimo we might need this one on other targets +$env_jpeglib = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CFLAGS => $BASE_CFLAGS + ); + +@JPEGLIB_FILES = qw( + ../jpeg-6/jcapimin.c + ../jpeg-6/jchuff.c + ../jpeg-6/jcinit.c + ../jpeg-6/jccoefct.c + ../jpeg-6/jccolor.c + ../jpeg-6/jfdctflt.c + ../jpeg-6/jcdctmgr.c + ../jpeg-6/jcphuff.c + ../jpeg-6/jcmainct.c + ../jpeg-6/jcmarker.c + ../jpeg-6/jcmaster.c + ../jpeg-6/jcomapi.c + ../jpeg-6/jcparam.c + ../jpeg-6/jcprepct.c + ../jpeg-6/jcsample.c + ../jpeg-6/jdapimin.c + ../jpeg-6/jdapistd.c + ../jpeg-6/jdatasrc.c + ../jpeg-6/jdcoefct.c + ../jpeg-6/jdcolor.c + ../jpeg-6/jddctmgr.c + ../jpeg-6/jdhuff.c + ../jpeg-6/jdinput.c + ../jpeg-6/jdmainct.c + ../jpeg-6/jdmarker.c + ../jpeg-6/jdmaster.c + ../jpeg-6/jdpostct.c + ../jpeg-6/jdsample.c + ../jpeg-6/jdtrans.c + ../jpeg-6/jerror.c + ../jpeg-6/jidctflt.c + ../jpeg-6/jmemmgr.c + ../jpeg-6/jmemnobs.c + ../jpeg-6/jutils.c + ); +$JPEGLIB_REF = \@JPEGLIB_FILES; + +Library $env_jpeglib 'jpeglib', @$JPEGLIB_REF; + +# NOTE TTimo this requires patched cons version to work (see unix/cons) +%nasm_hash = new cons()->copy( + CC => 'nasm', + CCCOM => '%CC -f elf -o %> %<' +); +$nasm_hash{SUFMAP}{'.nasm'} = 'build::command::cc'; +$nasm_env = new cons(%nasm_hash); + +Library $nasm_env 'asmlib', 'ftol.nasm', 'snapvector.nasm'; + +# compiling files with inlined assembly +$env_inlined = new cons( + CFLAGS => '-m32 -DELF -x assembler-with-cpp' + ); + +Library $env_inlined 'inlinelib', '../unix/matha.s', '../unix/snd_mixa.s'; + +# putting it all together + +$env = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CFLAGS => $BASE_CFLAGS, + LIBS => ' ' . $BUILD_DIR . '/unix/splines.a ' + . $BUILD_DIR . '/unix/botlib.a ' + . $BUILD_DIR . '/unix/jpeglib.a ' + . $BUILD_DIR . '/unix/asmlib.a ' + . $BUILD_DIR . '/unix/inlinelib.a ' + . '-L/usr/X11R6/lib -lX11 -lXext -lXxf86dga -lXxf86vm -ldl -lm' +); + +@RENDERER_FILES = qw( + ../renderer/tr_animation.c + ../renderer/tr_backend.c + ../renderer/tr_bsp.c + ../renderer/tr_cmds.c + ../renderer/tr_cmesh.c + ../renderer/tr_curve.c + ../renderer/tr_flares.c + ../renderer/tr_font.c + ../renderer/tr_image.c + ../renderer/tr_init.c + ../renderer/tr_light.c + ../renderer/tr_main.c + ../renderer/tr_marks.c + ../renderer/tr_mesh.c + ../renderer/tr_model.c + ../renderer/tr_noise.c + ../renderer/tr_scene.c + ../renderer/tr_shade.c + ../renderer/tr_shade_calc.c + ../renderer/tr_shader.c + ../renderer/tr_shadows.c + ../renderer/tr_sky.c + ../renderer/tr_surface.c + ../renderer/tr_world.c + ); +$RENDERER_REF = \@RENDERER_FILES; + +@CLIENT_FILES = qw( + ../client/cl_cgame.c + ../client/cl_cin.c + ../client/cl_console.c + ../client/cl_input.c + ../client/cl_keys.c + ../client/cl_main.c + ../client/cl_net_chan.c + ../client/cl_parse.c + ../client/cl_scrn.c + ../client/cl_ui.c + ); +$CLIENT_REF = \@CLIENT_FILES; + +@COMMON_FILES = qw( + ../qcommon/cm_load.c + ../qcommon/cm_patch.c + ../qcommon/cm_polylib.c + ../qcommon/cm_test.c + ../qcommon/cm_trace.c + ../qcommon/cmd.c + ../qcommon/common.c + ../qcommon/cvar.c + ../qcommon/files.c + ../qcommon/md4.c + ../qcommon/msg.c + ../qcommon/net_chan.c + ../qcommon/huffman.c + ../qcommon/unzip.c + ); +$COMMON_REF = \@COMMON_FILES; + +@SOUND_FILES = qw( + ../client/snd_adpcm.c + ../client/snd_dma.c + ../client/snd_mem.c + ../client/snd_mix.c + ../client/snd_wavelet.c + ); +$SOUND_REF = \@SOUND_FILES; + +@UNIX_FILES = qw( + ../unix/unix_main.c + ../unix/unix_net.c + ../unix/unix_shared.c + ../unix/linux_common.c + ../unix/linux_qgl.c + ../unix/linux_glimp.c + ../unix/linux_joystick.c + ../unix/linux_snd.c + ../unix/linux_signals.c + ); +$UNIX_REF = \@UNIX_FILES; + +@SERVER_FILES = qw( + ../server/sv_bot.c + ../server/sv_ccmds.c + ../server/sv_client.c + ../server/sv_game.c + ../server/sv_init.c + ../server/sv_main.c + ../server/sv_net_chan.c + ../server/sv_snapshot.c + ../server/sv_world.c + ); +$SERVER_REF = \@SERVER_FILES; + +# FIXME TTimo vm_.c +# anyway, qvm support is not planned in Wolf .. could get rid of it +@VM_FILES = qw( + ../qcommon/vm.c + ../qcommon/vm_x86.c + ../qcommon/vm_interpreted.c + ); +$VM_REF = \@VM_FILES; + +# FIXME: import the CPU string to build the name of the target +Program $env 'wolf.x86', '../game/q_shared.c', '../game/q_math.c', + @$RENDERER_REF, @$CLIENT_REF, @$COMMON_REF, @$SOUND_REF, + @$UNIX_REF, @$SERVER_REF, @$VM_REF; +Install $env '#' . $CONFIG_DIR . '/out', 'wolf.x86'; diff --git a/src/unix/Conscript-dedicated b/src/unix/Conscript-dedicated new file mode 100644 index 0000000..828e4b2 --- /dev/null +++ b/src/unix/Conscript-dedicated @@ -0,0 +1,112 @@ +# dedicated server build script + +Import qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR DEDICATED_NAME CC CXX LINK ); + +$env_botlib = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CFLAGS => $BASE_CFLAGS . '-DBOTLIB ' +); + +@BOTLIB_FILES = qw( + ../botlib/be_aas_bspq3.c + ../botlib/be_aas_cluster.c + ../botlib/be_aas_debug.c + ../botlib/be_aas_entity.c + ../botlib/be_aas_file.c + ../botlib/be_aas_main.c + ../botlib/be_aas_move.c + ../botlib/be_aas_optimize.c + ../botlib/be_aas_reach.c + ../botlib/be_aas_route.c + ../botlib/be_aas_routealt.c + ../botlib/be_aas_routetable.c + ../botlib/be_aas_sample.c + ../botlib/be_ai_char.c + ../botlib/be_ai_chat.c + ../botlib/be_ai_gen.c + ../botlib/be_ai_goal.c + ../botlib/be_ai_move.c + ../botlib/be_ai_weap.c + ../botlib/be_ai_weight.c + ../botlib/be_ea.c + ../botlib/be_interface.c + ../botlib/l_crc.c + ../botlib/l_libvar.c + ../botlib/l_log.c + ../botlib/l_memory.c + ../botlib/l_precomp.c + ../botlib/l_script.c + ../botlib/l_struct.c +); +$BOTLIB_REF = \@BOTLIB_FILES; + +Library $env_botlib 'botlib', @$BOTLIB_REF; + +# NOTE TTimo this requires patched cons version to work (see unix/cons) +%nasm_hash = new cons()->copy( + CC => 'nasm', + CCCOM => '%CC -f elf -o %> %<' +); +$nasm_hash{SUFMAP}{'.nasm'} = 'build::command::cc'; +$nasm_env = new cons(%nasm_hash); + +Library $nasm_env 'asmlib', 'ftol.nasm', 'snapvector.nasm'; + +$env = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + # FIXME TTimo I'm not sure about what C_ONLY is for + CFLAGS => $BASE_CFLAGS . '-DDEDICATED -DC_ONLY', + LDFLAGS => '-ldl -lm', + LIBS => ' ' + . $BUILD_DIR . '/unix/botlib.a ' + . $BUILD_DIR . '/unix/asmlib.a' +); + +# list the files for the dedicated server +@FILES = qw( + ../null/null_client.c + ../null/null_input.c + ../null/null_snddma.c + ../server/sv_bot.c + ../server/sv_ccmds.c + ../server/sv_client.c + ../server/sv_game.c + ../server/sv_init.c + ../server/sv_main.c + ../server/sv_net_chan.c + ../server/sv_snapshot.c + ../server/sv_world.c + ../qcommon/cm_load.c + ../qcommon/cm_patch.c + ../qcommon/cm_polylib.c + ../qcommon/cm_test.c + ../qcommon/cm_trace.c + ../qcommon/cmd.c + ../qcommon/common.c + ../qcommon/cvar.c + ../qcommon/files.c + ../qcommon/huffman.c + ../qcommon/md4.c + ../qcommon/msg.c + ../qcommon/net_chan.c + ../qcommon/unzip.c + ../qcommon/vm.c + ../qcommon/vm_interpreted.c + ../game/q_math.c + ../game/q_shared.c + ../unix/linux_common.c + ../unix/unix_main.c + ../unix/unix_net.c + ../unix/unix_shared.c + ../unix/linux_signals.c + ); +$FILESREF = \@FILES; + +# DEDICATED_NAME is imported, holds the name of the target +# wolfded.x86 usually +Program $env $DEDICATED_NAME, '../qcommon/vm_x86.c', @$FILESREF; +Install $env '#' . $CONFIG_DIR . '/out', $DEDICATED_NAME; diff --git a/src/unix/Conscript-game b/src/unix/Conscript-game new file mode 100644 index 0000000..9ea0fc1 --- /dev/null +++ b/src/unix/Conscript-game @@ -0,0 +1,77 @@ +# game + +Import qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR BASEGAME do_lddabort CC CXX LINK ); + +$env = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CPPPATH => '#../game:#../botai', + # NOTE TTimo what about the GAMEDLL define? where is it needed? + CFLAGS => $BASE_CFLAGS . '-fPIC -DGAMEDLL', + LDFLAGS => '-shared -ldl -lm' +); + +@FILES = qw( + ../game/ai_cast.c + ../game/ai_cast_characters.c + ../game/ai_cast_debug.c + ../game/ai_cast_events.c + ../game/ai_cast_fight.c + ../game/ai_cast_func_attack.c + ../game/ai_cast_func_boss1.c + ../game/ai_cast_funcs.c + ../game/ai_cast_script.c + ../game/ai_cast_script_actions.c + ../game/ai_cast_script_ents.c + ../game/ai_cast_sight.c + ../game/ai_cast_think.c + ../botai/ai_chat.c + ../botai/ai_cmd.c + ../botai/ai_dmnet.c + ../botai/ai_dmq3.c + ../botai/ai_main.c + ../botai/ai_team.c + ../game/bg_animation.c + ../game/bg_misc.c + ../game/bg_pmove.c + ../game/bg_slidemove.c + ../game/g_active.c + ../game/g_antilag.c + ../game/g_alarm.c + ../game/g_bot.c + ../game/g_client.c + ../game/g_cmds.c + ../game/g_combat.c + ../game/g_items.c + ../game/g_main.c + ../game/g_mem.c + ../game/g_misc.c + ../game/g_missile.c + ../game/g_mover.c + ../game/g_props.c + ../game/g_save.c + ../game/g_script.c + ../game/g_script_actions.c + ../game/g_session.c + ../game/g_spawn.c + ../game/g_svcmds.c + ../game/g_syscalls.c + ../game/g_target.c + ../game/g_team.c + ../game/g_tramcar.c + ../game/g_trigger.c + ../game/g_utils.c + ../game/g_vehicles.c + ../game/g_weapon.c + ../game/q_math.c + ../game/q_shared.c + ); +$FILESREF = \@FILES; + +# NOTE: could use an ldd -r after .so build to catch unresolved symbols? (PostBuild command) +Program $env 'qagame.mp.i386.so', @$FILESREF; +my $path = FilePath('qagame.mp.i386.so'); +AfterBuild $env 'qagame.mp.i386.so', "[perl] &ldd_check::do_check(\'$path\', $do_lddabort)"; + +Install $env '#' . $CONFIG_DIR . '/out/' . $BASEGAME, 'qagame.mp.i386.so'; diff --git a/src/unix/Conscript-pk3 b/src/unix/Conscript-pk3 new file mode 100644 index 0000000..d9ebc20 --- /dev/null +++ b/src/unix/Conscript-pk3 @@ -0,0 +1,144 @@ +# build pk3 on the fly + +Import qw( INSTALL_DIR BUILD_DIR ); + +#use Data::Dumper; + +$env = new cons(); # the env on which we will be working for all pk3s + +$hcf_do_exec = 1; +sub do_command($) +{ + printf("@_[0]\n"); + if ($hcf_do_exec) + { + system("@_[0]"); + } +} + +sub build_pk3 { + + sub launch { + $Data::Dumper::Indent = 2; + #print "In launch\n"; + #print Dumper(@_); + $tmpdir = "/tmp/pk3-builder$$"; + do_command("rm -rf $tmpdir"); + + ($target, $sets) = @_; + $base=`basename $target`; chomp($base); + $dirname=`dirname $target`; chomp($dirname); + + foreach (@{$sets}) + { + ($sourcepath, $destpath, $file) = @{$_}; + #print "source: $sourcepath dest: $destpath file: $file\n"; + do_command("mkdir -p $tmpdir/$destpath"); + if ($sourcepath =~ /#.*/) + { + #print "$sourcepath is absolute\n"; + $sourcepath =~ s/#//; + if (ref($file)) + { + foreach(@{$file}) + { + do_command("cp $sourcepath/$_ $tmpdir/$destpath/$_"); + } + } + else + { + do_command("cp $sourcepath/$file $tmpdir/$destpath/$file"); + } + } + else + { + #print "$sourcepath in linked dir\n"; + if (ref($file)) + { + foreach(@{$file}) + { + do_command("cp $BUILD_DIR/$sourcepath/$_ $tmpdir/$destpath/$_"); + } + } + else + { + do_command("cp $BUILD_DIR/$sourcepath/$file $tmpdir/$destpath/$file"); + } + } + } + + do_command("cd $tmpdir ; zip -r $base *"); + do_command("mkdir -p $BUILD_DIR/$dirname"); + do_command("cp $tmpdir/$base $BUILD_DIR/$target"); + do_command("rm -rf $tmpdir"); + + return 1; + } + + # extract the parameters + ($target, $sets) = @_; + + $base=`basename $target`; chomp($base); + $dirname=`dirname $target`; chomp($dirname); + + # the build command is stored and called later on by cons + # this makes it impossible to have several build_pk3 working together + # there is probably a cleaner solution than this hack, but this works + $target_uniquename="target_$base"; + $target_uniquename=~s/\.//g; + eval("\$$target_uniquename=\$target"); + $sets_uniquename="sets_$base"; + $sets_uniquename=~s/\.//g; + eval("\$$sets_uniquename=\$sets"); + #print "name: $target_uniquename after the hack: $target_pak8pk3"; + + # don't pass @{@_} .. since this will be called during the process + $command = "[perl] &launch( \$$target_uniquename, [ \@{\$$sets_uniquename} ] )"; + #print "$command\n"; + + foreach(@{$sets}) + { + ($sourcepath, $destpath, $file) = @{$_}; + if (ref($file)) + { + foreach(@{$file}) + { + Depends $env $target, $sourcepath . '/' . $_; + } + } + else + { + Depends $env $target, $sourcepath . '/' . $file; + } + } + Command $env $target, $command; + Install $env $INSTALL_DIR . "/$dirname", $target; +} + +@menus = qw( + error.menu serverinfo.menu urls.menu main.menu ingame_options.menu joinserver.menu auto_update.menu + createserver.menu controls.menu ingame_controls.menu ingame_callvote.menu ingame.menu filter.menu +); +$menus_ref = \@menus; + +#build_pk3('auto-pk3/mp_pak5.pk3', +# [ [ '#../../MAIN/ui_mp', 'ui_mp', +# [ 'menus.txt', 'ingame.txt' ] ], +# +# [ '#../../MAIN/translations', 'translations', 'translation-1_4.cfg' ], +# +# [ '#../../MAIN/ui_mp', 'ui_mp', $menus_ref ], +# +# [ '#../../MAIN/ui_mp/french', 'ui_mp/french', $menus_ref ], +# [ '#../../MAIN/ui_mp/german', 'ui_mp/german', $menus_ref ], +# [ '#../../MAIN/ui_mp/italian', 'ui_mp/italian', $menus_ref ], +# [ '#../../MAIN/ui_mp/spanish', 'ui_mp/spanish', $menus_ref ], +# +# # some last minute additions +# [ '#../../MAIN/ui_mp/french', 'ui_mp/french', 'credit.menu' ], +# [ '#../../MAIN/ui_mp/german', 'ui_mp/german', [ 'credit.menu', 'options.menu' ] ], +# +# ] ); + +build_pk3('auto-pk3/mp_pak6.pk3', + [ [ '#../../MAIN/scripts', 'scripts', 'common.shader' ] ] ); diff --git a/src/unix/Conscript-setup b/src/unix/Conscript-setup new file mode 100644 index 0000000..1bbfc4c --- /dev/null +++ b/src/unix/Conscript-setup @@ -0,0 +1,77 @@ +# setup + +Import qw( CONFIG_DIR WOLF_VER BASEGAME do_demo do_light ); + +$env = new cons(); + +sub launch { + if ($do_demo eq 1) + { + system("./build_setup.sh demo $CONFIG_DIR/out $WOLF_VER"); + } + elsif ($do_light eq 1) + { + # need to get the correct version (i.e. from SP source) + $line = `cat ../../../WolfSP/src/game/q_shared.h | grep Q3_VERSION`; + chomp $line; + $line =~ s/.*Wolf\ (.*)\"/$1/; + $WOLF_SP_VER = $line; + system("./build_setup.sh light $CONFIG_DIR/out $WOLF_SP_VER"); + } + else + { + # unified setup: SP and MP code + # we need a version tag, not necessarily the MP one + + # get SP version as tag + # FIXME: at some point we will want to use a completely different tag + # such as '1.2' (being 1.2-MP and 1.2-SP inside) + # extract the wolf version from q_shared.h + $BASEPATH = '../../../WolfSP/src'; + $line = `cat $BASEPATH/game/q_shared.h | grep Q3_VERSION`; + chomp $line; + $line =~ s/.*Wolf\ (.*)\"/$1/; + $WOLF_VER = $line; + + # we need to pass the path to the SP files + $line = `cd $BASEPATH/unix; ./cons -- release info`; + chomp $line; + $WOLFSPBIN = $BASEPATH . '/unix/' . $line . '/out'; + + # spawn a cons build in the SP source tree prior to run unified setup + # this is hacky, but probably the least painful solution + # if SP building fails, it will abort and not try to build setup + system("cd $BASEPATH/unix; ./cons -- release"); + #system("./build_setup.sh $CONFIG_DIR/out $WOLFSPBIN $WOLF_VER"); + + # now build the full setup + #system("./build_setup.sh full $CONFIG_DIR/out $WOLFSPBIN $WOLF_VER"); + + # and incremental + # NOTE: the content of this one changes for every release + system("./build_setup.sh incremental $CONFIG_DIR/out $WOLFSPBIN $WOLF_VER"); + } + return 1; +} + +# common dependency +Depends $env 'out/foo', + 'out/wolfded.x86', + 'out/' . $BASEGAME . '/qagame.mp.i386.so'; + +# pb dependency +Depends $env 'out/foo', + 'out/pb/pbag.so', + 'out/pb/pbcl.so', + 'out/pb/pbsv.so'; + +if ($do_light ne 1) +{ +# additional dep, only for the full build +Depends $env 'out/foo', + 'out/wolf.x86', + 'out/' . $BASEGAME . '/cgame.mp.i386.so', + 'out/' . $BASEGAME . '/ui.mp.i386.so'; +} +Command $env 'out/foo', "[perl] &launch()"; + diff --git a/src/unix/Conscript-ui b/src/unix/Conscript-ui new file mode 100644 index 0000000..e2917d4 --- /dev/null +++ b/src/unix/Conscript-ui @@ -0,0 +1,52 @@ +# ui + +Import qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR BASEGAME do_lddabort CC CXX LINK ); + +$env = new cons( + CC => $CC, + CXX => $CXX, + LINK => $LINK, + CPPPATH => '#../game', # needed for ui_ files + CFLAGS => $BASE_CFLAGS . '-fPIC', + LDFLAGS => '-shared -ldl -lm' +); + +# some files are compiled and linked in to several .so +# this confuses gdb when setting breakpoints + +@UI_FILES_IN = qw ( + ../game/bg_misc.c + ../game/q_math.c + ../game/q_shared.c + ); + +# run through and process into ui specific +my @UI_FILES_OUT; +foreach (@UI_FILES_IN) +{ + $source = $_; + $source =~ s/.*\/(.*)/..\/ui\/ui_\1/; + push @UI_FILES_OUT, $source; +} + +InstallAs $env [@UI_FILES_OUT], [@UI_FILES_IN]; + +$UI_FILESREF = \@UI_FILES_OUT; + +@FILES = qw( + ../ui/ui_atoms.c + ../ui/ui_gameinfo.c + ../ui/ui_main.c + ../ui/ui_players.c + ../ui/ui_shared.c + ../ui/ui_syscalls.c + ../ui/ui_util.c + ); +$FILESREF = \@FILES; + +# FIXME CPU string +Program $env 'ui.mp.i386.so', @$FILESREF, @$UI_FILESREF; +my $path = FilePath('ui.mp.i386.so'); +AfterBuild $env 'ui.mp.i386.so', "[perl] &ldd_check::do_check(\'$path\', $do_lddabort)"; + +Install $env '#' . $CONFIG_DIR . '/out/' . $BASEGAME, 'ui.mp.i386.so'; diff --git a/src/unix/Construct b/src/unix/Construct new file mode 100644 index 0000000..d081b75 --- /dev/null +++ b/src/unix/Construct @@ -0,0 +1,405 @@ +# Wolfenstein top-level Construct +# +# Sep. 2001 TTimo +# + +# source the ldd utility +BEGIN { + push @INC, "."; +} +use ldd_check; + +# the top directory is +# --- +# where: +# is "debug" or "release" +# is "x86" or "ppc" +# is "Linux" "BSD" "IRIX" etc. +# is major.minor of libc config + +# parse command line arguments and do the setup + +# defaults +$config = 'debug'; +$do_setup = 0; +$do_demo = 0; +$do_light = 0; +$do_update = 0; +$do_updateserver = 0; +#$update_server = ''; +$do_lddabort = 1; +$do_pk3 = 0; +# look for Conscript-client to decide about mod +if (scalar(stat('Conscript-client'))) +{ + $do_mod = 0; +} else +{ + $do_mod = 1; +} + +# compiler +$CC='gcc -m32'; +$CXX='g++ -m32'; +$LINK=$CXX; + +# detection of CPU type +$cpu = `uname -m`; +chop ($cpu); +if ($cpu +~ /i?86/) +{ + $cpu = 'x86'; +} +# OS +$OS = `uname`; +chop ($OS); +if ($OS =~ CYGWIN) +{ + $DO_WIN32 = 1; + $SHARED_DRIVE = "/cygdrive/e/incoming/Id/wolf-1.4"; + print("Win32 build\n"); + + # do some blunt processing + # we don't really use any cons features + + # TODO: read Release/Debug from cmdline + # TODO: option to override $RTCWBASE from command line + $WIN32_CONF = 'Release'; + + # build an mp_bin + system("rm -rf mp_bin.tmp && mkdir mp_bin.tmp && cp ../$WIN32_CONF/cgame_mp_x86.dll ../$WIN32_CONF/ui_mp_x86.dll mp_bin.tmp"); + system("cd mp_bin.tmp && zip -r mp_bin.pk3 *"); + + system("cp -v ../$WIN32_CONF/WolfMP.exe \$RTCWBASE"); + system("cp -v ../$WIN32_CONF/cgame_mp_x86.dll \$RTCWBASE/Main"); + system("cp -v ../$WIN32_CONF/qagame_mp_x86.dll \$RTCWBASE/Main"); + system("cp -v ../$WIN32_CONF/ui_mp_x86.dll \$RTCWBASE/Main"); + system("cp -v mp_bin.tmp/mp_bin.pk3 \$RTCWBASE/Main"); + # PB + system("rm -rf \$RTCWBASE/pb ; mkdir -p \$RTCWBASE/pb/htm"); + system("cp -v ../pb/win32/*.dll \$RTCWBASE/pb"); + system("cp -v ../pb/htm/*.htm \$RTCWBASE/pb/htm"); + + # copy to shared drive + system("cp -v ../$WIN32_CONF/WolfMP.exe $SHARED_DRIVE"); + system("cp -v ../$WIN32_CONF/cgame_mp_x86.dll $SHARED_DRIVE/Main"); + system("cp -v ../$WIN32_CONF/qagame_mp_x86.dll $SHARED_DRIVE/Main"); + system("cp -v ../$WIN32_CONF/ui_mp_x86.dll $SHARED_DRIVE/Main"); + system("cp -v mp_bin.tmp/mp_bin.pk3 $SHARED_DRIVE/Main"); + # PB + system("rm -rf $SHARED_DRIVE/pb ; mkdir -p $SHARED_DRIVE/pb/htm"); + system("cp -v ../pb/win32/*.dll $SHARED_DRIVE/pb"); + system("cp -v ../pb/htm/*.htm $SHARED_DRIVE/pb/htm"); + + exit; +} + +# libc .. do the little magic! +$libc_cmd = '/lib/libc.so.6 |grep "GNU C "|grep version|awk -F "version " \'{ print $2 }\'|cut -b -3'; +$libc = `$libc_cmd`; +chop ($libc); + +if(@ARGV gt 0) +{ + foreach $cmdopt (@ARGV) + { + if(lc($cmdopt) eq 'release') + { + $config = 'release'; + next; + } + elsif(lc($cmdopt) eq 'debug') + { + $config = 'debug'; + next; + } + elsif(lc($cmdopt) eq 'setup') + { + $do_setup = 1; + next; + } + elsif(lc($cmdopt) eq 'demo') + { + $do_demo = 1; + next; + } + elsif(lc($cmdopt) eq 'light') + { + $do_light = 1; + next; + } + elsif(lc($cmdopt) eq 'update') + { + $do_update = 1; + next; + } + elsif(lc($cmdopt) eq 'noldd') + { + $do_lddabort = 0; + next; + } + elsif(lc($cmdopt) eq 'pk3') + { + $do_pk3 = 1; + next; + } + elsif(lc($cmdopt) =~ 'update_server=.*') + { + $do_updateserver = 1; + $update_server = lc($cmdopt); + $update_server =~ s/update_server=(.*)/\1/; + next; + } + elsif(lc($cmdopt) =~ 'gcc=.*') + { + $CC=lc($cmdopt); + $CC =~ s/gcc=(.*)/\1/; + next; + } + elsif(lc($cmdopt) =~ 'g\+\+=.*') + { + $CXX=lc($cmdopt); + $CXX=~s/g\+\+=(.*)/\1/; + # ( looks like only 2.95 would link as C correctly ) + $LINK=$CXX; + next; + } + else + { + # output an error & exit + print("Error\n $0: Unknown command line option: [ $cmdopt ]\n"); + system("cons -h"); + exit; + } + } +} + +if (($do_demo eq 1) && ($do_light eq 1)) +{ + die "Can't mix demo and light"; +} + +$DEDICATED_NAME = 'wolfded.x86'; +# build the config directory +$CONFIG_DIR = $config . '-' . $cpu . '-' . $OS . '-' . $libc; +if ($do_demo eq 1) +{ + $CONFIG_DIR .= '-demo'; +} +if ($do_light eq 1) +{ + $CONFIG_DIR .= '-light'; +} +if ($do_update eq 1) +{ + $CONFIG_DIR .= '-update'; + $DEDICATED_NAME = 'wolfupdate.x86'; +} + +# this is a safety, avoid releasing any setup with all debugging symbols :-) +if (($do_setup eq 1) && ($do_demo eq 1) && ($config ne 'release')) +{ + print "Forcing release build for setup build\n"; + $config = 'release'; +} + +# FIXME: we will have to parse command line between release and debug flags +# NOTE TTimo PRE_RELEASE_DEMO define can be added as general flag for MP Test +if ($do_demo eq 1) +{ + $COMMON_CFLAGS = '-pipe -fsigned-char -DPRE_RELEASE_DEMO '; + $BASEGAME = 'demomain'; +} +elsif ($do_light eq 1) +{ + $COMMON_CFLAGS = '-pipe -fsigned-char -DDO_LIGHT_DEDICATED '; + $BASEGAME = 'main'; +} +elsif ($do_update eq 1) +{ + $COMMON_CFLAGS = '-pipe -fsigned-char -DUPDATE_SERVER '; + $BASEGAME = 'main'; +} +else +{ + $COMMON_CFLAGS = '-pipe -fsigned-char '; + $BASEGAME = 'main'; +} + +if ($do_updateserver eq 1) +{ + $COMMON_CFLAGS .= "-DAUTOUPDATE_SERVER_NAME=\\\"$update_server\\\" "; +} + +# NOTE TTimo using -fshort-enums increases warnings on enum issues +# this is for debugging and QA ONLY, the flag has ABI issues (OpenGL headers) +#$COMMON_CFLAGS = $COMMON_CFLAGS . '-fshort-enums '; + +if ($config eq 'debug') +{ + $BASE_CFLAGS = $COMMON_CFLAGS . '-g -Wall -O '; +} +else +{ + $BASE_CFLAGS = $COMMON_CFLAGS . '-DNDEBUG -O6 -mcpu=pentiumpro -march=pentium -fomit-frame-pointer -ffast-math -malign-loops=2 -malign-jumps=2 -malign-functions=2 -fno-strict-aliasing -fstrength-reduce '; +} + +# extract the wolf version from q_shared.h +$line = `cat ../game/q_shared.h | grep Q3_VERSION`; +chomp $line; +$line =~ s/.*Wolf\ (.*)\"/$1/; +$WOLF_VER = $line; + +print "Wolfenstein version $WOLF_VER\n"; +print 'cpu : ' . $cpu . "\nOS : " . $OS . "\nlibc: " . $libc . "\n"; +print "configured for $config build, in directory $CONFIG_DIR\n"; +if ($do_demo eq 1) +{ + print "building in demo mode\n"; +} +if ($do_light eq 1) +{ + print "building light dedicated server\n"; +} +print 'CFLAGS: ' . $BASE_CFLAGS . "\n"; + +# by default, build everything below the config dir +if ($do_pk3 eq 1) +{ + Default $CONFIG_DIR . '/out', 'auto-pk3'; +} +else +{ + Default $CONFIG_DIR . '/out'; +} + +#---------------------------------------------- +if ($do_light eq 0 && $do_update eq 0 && $do_mod eq 0) +{ + $TARGET_DIR = 'full'; + + $BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR; + + Link $BUILD_DIR => '..'; + + Export qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR CC CXX LINK ); + + Build $BUILD_DIR . '/unix/Conscript-client'; +} + +#--------------------------------------------- +$TARGET_DIR = 'dedicated'; + +$BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR; + +Export qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR DEDICATED_NAME CC CXX LINK ); + +Link $BUILD_DIR => '..'; + +if ($do_mod eq 0) +{ + Build $BUILD_DIR . '/unix/Conscript-dedicated'; +} + +if ($do_update eq 0) +{ + $TARGET_DIR = 'game'; + + $BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR; + + Link $BUILD_DIR => '..'; + + Export qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR BASEGAME do_lddabort CC CXX LINK ); + + Build $BUILD_DIR . '/unix/Conscript-game'; + + if ($do_light eq 0) + { + #---------------------------------------------- + # NOTE TTimo for cgame and ui, we have to + # link the dir starting below Wolfenstein/ + # because ui_shared.h reads in ../../MAIN/UI + $TARGET_DIR = 'cgame'; + + $BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR; + + Link $BUILD_DIR => '../..'; + + Export qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR BASEGAME do_lddabort CC CXX LINK ); + + Build $BUILD_DIR . '/src/unix/Conscript-cgame'; + + #---------------------------------------------- + $TARGET_DIR = 'ui'; + + $BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR; + + Link $BUILD_DIR => '../..'; + + Export qw( BASE_CFLAGS CONFIG_DIR BUILD_DIR BASEGAME do_lddabort CC CXX LINK ); + + Build $BUILD_DIR . '/src/unix/Conscript-ui'; + } +} + +#---------------------------------------------- +# rebuild UI pk3 stuff on the fly too unless an override is provided +if ($do_pk3 eq 1) +{ + $INSTALL_DIR = ''; + $BUILD_DIR = $CONFIG_DIR; + Export qw( INSTALL_DIR BUILD_DIR CC CXX LINK ); + Build "Conscript-pk3"; +} + +#---------------------------------------------- +if ($do_setup eq 1) +{ + # we bail out if config is not release + if ($config ne 'release') + { + print "Not building setups with debug config\n"; + exit; + } + Link $CONFIG_DIR => '.'; + Export qw( CONFIG_DIR WOLF_VER BASEGAME do_demo do_light CC CXX LINK ); + Build $CONFIG_DIR . "/Conscript-setup"; +} +#-------------------------------------------------- +# cons help for those that ask for it (with 'cons -h') +Help +" +Usage: cons [-h] [ -- [release|debug] [update_server=] [noldd] [demo|light|update] [setup] [gcc=] [g++=] ] + +options: +[release|debug] + Default build type is Debug, specifying '-- release' on the + command line builds a Release version. + +[update_server=] + Compile with a custom AUTOUPDATE_SERVER_NAME as given in + +[noldd] +Don't abort if ldd checks fail + +[pk3] +Build the pk3s from Conscript-pk3 (default off) + +[demo] + This switch uses -DPRE_RELEASE_DEMO and builds for MPTest + +[light] + This switch builds the lightweight dedicated server + (only wolfded and qagame targets, -DDO_LIGHT_DEDICATED) + +[update] + This builds an update server, serves update installers + +[setup] + 'cons -- setup' will build then execute the setup building scripts + NOTE: 'cons -- setup' will default to release configuration + you can use 'cons -- setup debug' to force debug setups building + reminder: distributed binaries must be configured in release + and stripped from symbols +" +; diff --git a/src/unix/build_mappack.sh b/src/unix/build_mappack.sh new file mode 100644 index 0000000..79cd4ac --- /dev/null +++ b/src/unix/build_mappack.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +MEDIADIR=$HOME/Id/media/WolfMedia-GOTY-maps +MAKESELF=makeself/makeself.sh +SPDIR=../../../WolfSP + +VERSION=`cat $SPDIR/src/game/q_shared.h | grep Q3_VERSION | sed -e 's/.*Wolf \(.*\)"/\1/'` +echo "Building GOTY map pack (version $VERSION)" +DIRNAME=GOTY-map-$VERSION + +rm -rf $DIRNAME +mkdir $DIRNAME + +# we rely on new sdk-setup, and push our own tweaks on top +cp -R sdk-setup/* $DIRNAME +cp -R mappack-setup/* $DIRNAME + +# copy the content +cp -R $MEDIADIR/* $DIRNAME + +# final pass: cleanup CVS entries +find $DIRNAME -name CVS -exec rm -rf {} \; 2>/dev/null + +# build the setup -------------------- +$MAKESELF $DIRNAME wolf-linux-GOTY-maps.x86.run "Return To Castle Wolfenstein GOTY Map Pack" ./setup.sh $VERSION diff --git a/src/unix/build_sdk.sh b/src/unix/build_sdk.sh new file mode 100644 index 0000000..8ccd877 --- /dev/null +++ b/src/unix/build_sdk.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# put together everything for mod sdk +# no setup, just a zip +# this is both MP and SP +# we use the version from the SP tree + +MEDIADIR=$HOME/Id/media/WolfMedia-sdk +MAKESELF=makeself/makeself.sh +SPDIR=../../../WolfSP +MPDIR=../.. +VERSION=`cat $SPDIR/src/game/q_shared.h | grep Q3_VERSION | sed -e 's/.*Wolf \(.*\)"/\1/'` +echo "Building mod sdk for version $VERSION" +DIRNAME=RTCW-mod-sdk-$VERSION +TESTDIR=RTCW-mod-sdk-test + +rm -rf $DIRNAME + +# SP --------------- + +mkdir -p $DIRNAME/SP/src/unix +mkdir -p $DIRNAME/SP/main/ui + +cp -R $SPDIR/src/botai $DIRNAME/SP/src +cp -R $SPDIR/src/cgame $DIRNAME/SP/src +cp -R $SPDIR/src/game $DIRNAME/SP/src +cp -R $SPDIR/src/ui $DIRNAME/SP/src + +cp -R $SPDIR/main/ui/menudef.h $DIRNAME/SP/main/ui + +# the build system +cp $SPDIR/src/unix/Construct $DIRNAME/SP/src/unix +cp $SPDIR/src/unix/Conscript-game $DIRNAME/SP/src/unix +cp $SPDIR/src/unix/Conscript-cgame $DIRNAME/SP/src/unix +cp $SPDIR/src/unix/Conscript-ui $DIRNAME/SP/src/unix +cp $SPDIR/src/unix/cons $DIRNAME/SP/src/unix + +# extractfuncs binary +( +cd $SPDIR/src/unix +./cons extractfuncs +strip extractfuncs +) +cp $SPDIR/src/unix/extractfuncs $DIRNAME/SP/src/unix + +# MP ---------------- + +mkdir -p $DIRNAME/MP/src/unix +mkdir -p $DIRNAME/MP/MAIN/ui_mp + +cp -R $MPDIR/src/botai $DIRNAME/MP/src +cp -R $MPDIR/src/cgame $DIRNAME/MP/src +cp -R $MPDIR/src/game $DIRNAME/MP/src +cp -R $MPDIR/src/ui $DIRNAME/MP/src + +cp -R $MPDIR/MAIN/ui_mp/menudef.h $DIRNAME/MP/MAIN/ui_mp + +# the build system +cp $MPDIR/src/unix/Construct $DIRNAME/MP/src/unix +cp $MPDIR/src/unix/Conscript-game $DIRNAME/MP/src/unix +cp $MPDIR/src/unix/Conscript-cgame $DIRNAME/MP/src/unix +cp $MPDIR/src/unix/Conscript-ui $DIRNAME/MP/src/unix +cp $MPDIR/src/unix/cons $DIRNAME/MP/src/unix +cp $MPDIR/src/unix/ldd_check.pm $DIRNAME/MP/src/unix + +# static content (MP & SP) ----------- +cp -R $MEDIADIR/* $DIRNAME + +# copy setup dirs -------------------- +cp -R sdk-setup/* $DIRNAME + +# final pass: cleanup CVS entries +find $DIRNAME -name CVS -exec rm -rf {} \; 2>/dev/null + +# proceed to testing this ------------ +if [ `hostname` == vmspoutnik32 ] +then + rm -rf $TESTDIR + cp -R $DIRNAME $TESTDIR + cd $TESTDIR + cd MP/src/unix + ./cons || exit + cd ../../.. + cd SP/src/unix + ./cons || exit + cd ../../.. + cd .. +fi + +# build the setup -------------------- +$MAKESELF $DIRNAME wolf-linux-sdk-$VERSION.x86.run "Return To Castle Wolfenstein MOD SDK" ./setup.sh $VERSION + +exit 0 diff --git a/src/unix/build_setup.sh b/src/unix/build_setup.sh new file mode 100644 index 0000000..47ec9a9 --- /dev/null +++ b/src/unix/build_setup.sh @@ -0,0 +1,347 @@ +#!/bin/bash + +usage() +{ +echo "syntax: build_setup.sh [] " +echo " (ex: build_setup.sh ../../../WolfSP/src/unix/release-x86-Linux-2.1/out release-x86-Linux-2.1/out 0.7.16-1)" +echo "syntax: build_setup.sh demo " +echo " builds an MP demo version setup" +echo "syntax: build_setup.sh light " +echo " builds a light dedicated server setup" +echo "syntax: build_setup.sh full " +echo " builds the full setup (default is the -GOTY setup)" +} + +check_brandelf() +{ + # make sure brandelf is installed to avoid any problem when building the setups + # NOTE: when cons spawns this, the environement variables are greatly restricted (which makes things very annoying) + BRAND=`which brandelf`; + if [ -n "$BRAND" ] && [ -x "$BRAND" ] + then + echo "brandelf is present: $BRAND" + else + #export + BRAND=/home/timo/usr/bin/brandelf; + if [ -n "$BRAND" ] && [ -x "$BRAND" ] + then + echo "brandelf is present: $BRAND" + else + echo "brandelf not found" + exit + fi + fi +} + +# safe checks +check_brandelf + +DO_DEMO=0 +DO_LIGHT=0 +DO_FULL=0 +DO_INCREMENTAL=0 + +# process command line +if [ $# -ne 3 ] && [ $# -ne 4 ] +then + echo "bad options" + usage + exit +fi +if [ $1 == "demo" ] +then + DO_DEMO=1 + WOLFBIN=$2 + VERSION=$3 +elif [ $1 == "light" ] +then + DO_LIGHT=1 + WOLFBIN=$2 + VERSION=$3 +elif [ $1 == "full" ] +then + DO_FULL=1 + WOLFBIN=$2 + WOLFSPBIN=$3 + VERSION=$4 +elif [ $1 == "incremental" ] +then + DO_INCREMENTAL=1 + WOLFBIN=$2 + WOLFSPBIN=$3 + VERSION=$4 +else + WOLFBIN=$1 + WOLFSPBIN=$2 + VERSION=$3 +fi + +echo "Building setup ================================" +echo "Version :$VERSION" +echo "Multiplayer directory :$WOLFBIN" +if [ $DO_DEMO -eq 1 ] +then + echo "Building Mutliplayer demo setup" +elif [ $DO_LIGHT -eq 1 ] +then + echo "Building Light dedicated server" +else + if [ $DO_FULL -eq 1 ] + then + echo "Building Full setup" + else + echo "Building GOTY setup" + fi + echo " Single Player directory: $WOLFSPBIN" +fi +echo "===============================================" + +# media +# NOTE TTimo: I maintain this directly in my local CVS +# module name is: WolfMedia- ( == main or demo) +WOLFMEDIA_LINUX=../../../../WolfMedia-dedicated-linux +WOLFMEDIA_FULL=../../../../WolfMedia-GOTY-maps +if [ $DO_DEMO -eq 1 ] +then + WOLFMEDIA=../../../../WolfMedia-demo + BASEGAME=demomain +elif [ $DO_LIGHT -eq 1 ] +then + WOLFMEDIA=../../../../WolfMedia-dedicated + BASEGAME=main +else + WOLFMEDIA=../../../../WolfMedia-main + BASEGAME=main +fi + +# location of the setup dir (for graphical installer and makeself) +# IMPORTANT NOTE: the same reference tree is used for both full and demo setups +SETUPDIR=setup + +# copy all the relevant data in the relevant places +prepare_core() +{ + echo "Cleaning up and rebuilding $TMPDIR" + rm -rf $TMPDIR + + # binaries, copy and strip + mkdir -p $TMPDIR/bin/x86 + if [ $DO_LIGHT -ne 1 ] + then + cp $WOLFBIN/wolf.x86 $TMPDIR/bin/x86/wolf.x86 + strip $TMPDIR/bin/x86/wolf.x86 + $BRAND -t Linux $TMPDIR/bin/x86/wolf.x86 + fi + cp $WOLFBIN/wolfded.x86 $TMPDIR/bin/x86/wolfded.x86 + strip $TMPDIR/bin/x86/wolfded.x86 + $BRAND -t Linux $TMPDIR/bin/x86/wolfded.x86 + mkdir $TMPDIR/$BASEGAME + cp $WOLFBIN/$BASEGAME/qagame.mp.i386.so $TMPDIR/$BASEGAME + strip $TMPDIR/$BASEGAME/qagame.mp.i386.so + if [ $DO_LIGHT -ne 1 ] + then + cp $WOLFBIN/$BASEGAME/cgame.mp.i386.so $TMPDIR/$BASEGAME + strip $TMPDIR/$BASEGAME/cgame.mp.i386.so + cp $WOLFBIN/$BASEGAME/ui.mp.i386.so $TMPDIR/$BASEGAME + strip $TMPDIR/$BASEGAME/ui.mp.i386.so + fi + + # punkbuster + mkdir $TMPDIR/pb + cp $WOLFBIN/pb/*.so $TMPDIR/pb + strip $TMPDIR/pb/*.so + # punkbuster html files + mkdir $TMPDIR/pb/htm + cp ../pb/htm/* $TMPDIR/pb/htm + + # copy various accompagnying media files + cp $CPOPT -R $WOLFMEDIA/* $TMPDIR + if [ $DO_DEMO -eq 1 ] + then + # the demo is a bit tricky, we don't copy the main pak systematically + # cp doesn't provide easy exclusion based on name + # so we just copy in full and take out the pak0.pk3 afterwards (yuck) + rm $TMPDIR/$BASEGAME/pak0.pk3 + fi + + if [ $DO_DEMO -eq 0 ] + then + echo "Copying mp_bin.pk3 from CVS" + # mp_bin.pk3: + # grab from the CVS, that's the reference location + cp ../../MAIN/mp_bin.pk3 $TMPDIR/$BASEGAME + md5sum $TMPDIR/$BASEGAME/mp_bin.pk3 + fi + + # copy base setup files + cp $CPOPT -R $SETUPDIR/setup.sh $SETUPDIR/setup.data $TMPDIR + + # copy the full/demo specific files + if [ $DO_DEMO -eq 1 ] + then + cp $SETUPDIR/setup.data.demo/* $TMPDIR/setup.data + elif [ $DO_LIGHT -eq 1 ] + then + cp $SETUPDIR/setup.data.light/* $TMPDIR/setup.data + else + cp $SETUPDIR/setup.data.full/* $TMPDIR/setup.data + fi + + # make the installer BSD friendly: use a symlink + ( + cd $TMPDIR/setup.data/bin + ln -s Linux FreeBSD + ln -s Linux OpenBSD + ln -s Linux NetBSD + ) + + # menu shortcut to the game + # FIXME current setup doesn't have a way to set symlinks on arbitrary things + # so we use a dummy script for each of the symlink targets + # (scripts which will be overwritten by postinstall.sh) + if [ $DO_DEMO -eq 1 ] + then + echo -e "#!/bin/sh\necho \"If you read this, then the setup script failed miserably.\nPlease report to ttimo@idsoftware.com\n\"" > $TMPDIR/bin/x86/wolfmpdemo + elif [ $DO_LIGHT -eq 1 ] + then + echo -e "#!/bin/sh\necho \"If you read this, then the setup script failed miserably.\nPlease report to ttimo@idsoftware.com\n\"" > $TMPDIR/bin/x86/wolflightded + else + echo -e "#!/bin/sh\necho \"If you read this, then the setup script failed miserably.\nPlease report to ttimo@idsoftware.com\n\"" > $TMPDIR/bin/x86/wolfmp + fi + + # remove CVS entries (always last thing to do, safer) + find $TMPDIR -name CVS | xargs rm -rf +} + +# single player specific files +prepare_spcore() +{ + cp $WOLFSPBIN/wolfsp.x86 $TMPDIR/bin/x86/wolfsp.x86 + strip $TMPDIR/bin/x86/wolfsp.x86 + $BRAND -t Linux $TMPDIR/bin/x86/wolfsp.x86 + cp $WOLFSPBIN/$BASEGAME/qagamei386.so $TMPDIR/$BASEGAME + strip $TMPDIR/$BASEGAME/qagamei386.so + cp $WOLFSPBIN/$BASEGAME/cgamei386.so $TMPDIR/$BASEGAME + strip $TMPDIR/$BASEGAME/cgamei386.so + cp $WOLFSPBIN/$BASEGAME/uii386.so $TMPDIR/$BASEGAME + strip $TMPDIR/$BASEGAME/uii386.so + + # dummy symlink script + echo -e "#!/bin/sh\necho \"If you read this, then the setup script failed miserably.\nPlease report to ttimo@idsoftware.com\n\"" > $TMPDIR/bin/x86/wolfsp +} + +# cp setup phase +# we need to copy the symlinked files, and not the symlinks themselves +# on antares this is forced with a cp -L +# on spoutnik, -L is not recognized, and dereference is the default behaviour +# we need a robust way of checking +TESTFILE=/tmp/foo$$ +touch $TESTFILE +# see if option is supported +cp -L $TESTFILE $TESTFILE.cp 2>/dev/null +if [ $? -eq 1 ] +then + # option not supported, should be on by default + echo "cp doesn't have -L option" + unset CPOPT +else + # option supported, use it + echo "cp supports -L option" + CPOPT="-L" +fi +rm $TESTFILE + +if [ $DO_DEMO -eq 1 ] +then + + TMPDIR=wolf-setup-mpdemo-nomedia + prepare_core + # build the auto-extractible archive + ./$SETUPDIR/makeself/makeself.sh $TMPDIR wolfmpdemo-linux-nomedia-$VERSION.x86.run "Return To Castle Wolfenstein Multiplayer DEMO (nomedia)" ./setup.sh $VERSION + + # stuff for building the full version remotely (faster than uploading it over DSL) + SCRIPTDIR=build-full-setup + rm -rf $SCRIPTDIR + mkdir $SCRIPTDIR + cp ./$SETUPDIR/makeself/makeself.sh $SCRIPTDIR + # make sure to watch this one + echo -e " + # this will build a full setup + # the content needs to be in $TMPDIR + # and copy the appropriate pk3 in there first + FULLTMPDIR=wolf-setup-mpdemo + mv $TMPDIR \$FULLTMPDIR + ./makeself.sh \$FULLTMPDIR wolfmpdemo-linux-$VERSION.x86.run \"Return To Castle Wolfenstein Multiplayer DEMO\" ./setup.sh $VERSION + " > $SCRIPTDIR/build.sh + chmod +x $SCRIPTDIR/build.sh + tar cvzf $SCRIPTDIR.tgz $SCRIPTDIR + +elif [ $DO_LIGHT -eq 1 ] +then + + TMPDIR=wolf-setup-lightded + prepare_core + # deal with the second linux only dir + cp $CPOPT -R $WOLFMEDIA_LINUX/* $TMPDIR + # remove CVS entries (always last thing to do, safer) + find $TMPDIR -name CVS | xargs rm -rf + # dump the command to consol + echo "./$SETUPDIR/makeself/makeself.sh $TMPDIR wolflightded-linux-$VERSION.x86.run \"Return To Castle Wolfenstein - Standalone Dedicated Server\" ./setup.sh $VERSION" + # build the auto-extractible archive + ./$SETUPDIR/makeself/makeself.sh $TMPDIR wolflightded-linux-$VERSION.x86.run "Return To Castle Wolfenstein - Standalone Dedicated Server" ./setup.sh $VERSION + +elif [ $DO_FULL -eq 1 ] +then + + # could have unified with above, kept seperate for readability + TMPDIR=wolf-setup-full + prepare_core + prepare_spcore + # copy content that GOTY provides + cp $CPOPT -R $WOLFMEDIA_FULL/* $TMPDIR + # dump the command to the console, so that it is easy to build manual auto-update files + echo "./$SETUPDIR/makeself/makeself.sh $TMPDIR wolf-linux-$VERSION-full.x86.run \"Return To Castle Wolfenstein\" ./setup.sh $VERSION" + # build the auto-extractible archive + ./$SETUPDIR/makeself/makeself.sh $TMPDIR wolf-linux-$VERSION-full.x86.run "Return To Castle Wolfenstein" ./setup.sh $VERSION + +elif [ $DO_INCREMENTAL -eq 1 ] +then + + # incremental, this needs to be adapted for each major / incremental release + INCR_FROM=1.4 + TMPDIR=wolf-setup-incr + prepare_core + prepare_spcore + # 1.33 -> 1.4 + ## do the incremental stuff + #cp $CPOPT $WOLFMEDIA_FULL/main/sp_pak3.pk3 $WOLFMEDIA_FULL/main/sp_pak4.pk3 $TMPDIR/main/ + ## build the auto-extractible archive + #echo "./$SETUPDIR/makeself/makeself.sh $TMPDIR wolf-linux-up-$INCR_FROM-$VERSION.x86.run \"Return To Castle Wolfenstein Incremental Update $INCR_FROM $VERSION\" ./setup.sh $VERSION" + #./$SETUPDIR/makeself/makeself.sh $TMPDIR wolf-linux-up-$INCR_FROM-$VERSION.x86.run "Return To Castle Wolfenstein Incremental Update $INCR_FROM $VERSION" ./setup.sh $VERSION + + # 1.4 -> 1.41 + WOLFMEDIA_UPDATE=../../../../WolfMedia-1.41 + # go back and only put the stuff we actually want + rm -rf $TMPDIR/$BASEGAME + rm -rf $TMPDIR/Docs/Help + rm -rf $TMPDIR/Docs/PunkBuster + mkdir $TMPDIR/$BASEGAME + cp $WOLFBIN/$BASEGAME/qagame.mp.i386.so $TMPDIR/$BASEGAME + strip $TMPDIR/$BASEGAME/qagame.mp.i386.so + cp $CPOPT -R $WOLFMEDIA_UPDATE/* $TMPDIR + # build the auto-extractible archive + echo "./$SETUPDIR/makeself/makeself.sh $TMPDIR wolf-linux-update-$VERSION.x86.run \"Return To Castle Wolfenstein Update $VERSION\" ./setup.sh $VERSION" + ./$SETUPDIR/makeself/makeself.sh $TMPDIR wolf-linux-update-$VERSION.x86.run "Return To Castle Wolfenstein Update $VERSION" ./setup.sh $VERSION + +else + + TMPDIR=wolf-setup + prepare_core + # the single player specific stuff + prepare_spcore + # dump the command to the console, so that it is easy to build manual auto-update files + echo "./$SETUPDIR/makeself/makeself.sh $TMPDIR wolf-linux-$VERSION-GOTY.x86.run \"Return To Castle Wolfenstein\" ./setup.sh $VERSION" + # build the auto-extractible archive + ./$SETUPDIR/makeself/makeself.sh $TMPDIR wolf-linux-$VERSION-GOTY.x86.run "Return To Castle Wolfenstein" ./setup.sh $VERSION + +fi diff --git a/src/unix/cons b/src/unix/cons new file mode 100644 index 0000000..e4f36eb --- /dev/null +++ b/src/unix/cons @@ -0,0 +1,6828 @@ +#!/usr/bin/env perl + +# NOTE: Cons intentionally does not use the "perl -w" option or +# "use strict." Because Cons "configuration files" are actually +# Perl scripts, enabling those restrictions here would force them +# on every user's config files, wanted or not. Would users write +# "better" Construct and Conscript files if we forced "use strict" +# on them? Probably. But we want people to use Cons to get work +# done, not force everyone to become a Perl guru to use it, so we +# don't insist. +# +# That said, Cons' code is both "perl -w" and "use strict" clean. +# Regression tests keep the code honest by checking for warnings +# and "use strict" failures. + +use vars qw( $CVS_id $CVS_ver $ver_num $ver_rev $version ); + +$CVS_id = 'Id'; +$CVS_ver = (split(/\s+/, $CVS_id))[2]; + +$ver_num = "2.3"; +$ver_rev = ".1"; + +$version = "This is Cons $ver_num$ver_rev ($CVS_id)\n"; + +# Cons: A Software Construction Tool. +# Copyright (c) 1996-2001 Free Software Foundation, 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; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +require 5.003; +# See the NOTE above about why Cons doesn't "use strict". +use integer; +use Cwd; +use File::Copy; + +use vars qw( $_WIN32 $_a $_exe $_o $_so ); + +#------------------------------------------------------------------ +# Determine if running on win32 platform - either Windows NT or 95 +#------------------------------------------------------------------ + +use vars qw( $PATH_SEPARATOR $iswin32 $_WIN32 $usage $indent @targets ); + +BEGIN { + use Config; + + # if the version is 5.003, we can check $^O + if ($] < 5.003) { + eval("require Win32"); + $_WIN32 = (!$@); + } else { + $_WIN32 = ($^O eq "MSWin32") ? 1 : 0; + } + + # Fetch the PATH separator from Config; + # provide our old defaults in case it's not set. + $PATH_SEPARATOR = $Config{path_sep}; + $PATH_SEPARATOR = $_WIN32 ? ';' : ':' if ! defined $PATH_SEPARATOR; + + # Fetch file suffixes from Config, + # accomodating differences in the Config variables + # used by different Perl versions. + $_exe = $Config{_exe}; + $_exe = $Config{exe_ext} if ! defined $_exe; + $_exe = $_WIN32 ? '.exe' : '' if ! defined $_exe; + $_o = $Config{_o}; + $_o = $Config{obj_ext} if ! defined $_o; + $_o = $_WIN32 ? '.obj' : '.o' if ! defined $_o; + $_a = $Config{_a}; + $_a = $Config{lib_ext} if ! defined $_a; + $_a = $_WIN32 ? '.lib' : '.a' if ! defined $_a; + $_so = ".$Config{so}"; + $_so = $_WIN32 ? '.dll' : '.so' if ! defined $_so; +} + +# Flush stdout each time. +$| = 1; + +# Seed random number generator. +srand(time . $$); # this works better than time ^ $$ in perlfunc manpage. + +$usage = q( +Usage: cons -- + +Arguments can be any of the following, in any order: + + Build the specified targets. If is a directory + recursively build everything within that directory. + + + Limit the cons scripts considered to just those that + match . Multiple + arguments are accepted. + + = Sets to value in the ARG hash passed to the + top-level Construct file. + + -cc Show command that would have been executed, when + retrieving from cache. No indication that the file + has been retrieved is given; this is useful for + generating build logs that can be compared with + real build logs. + + -cd Disable all caching. Do not retrieve from cache nor + flush to cache. + + -cr Build dependencies in random order. This is useful when + building multiple similar trees with caching enabled. + + -cs Synchronize existing build targets that are found to be + up-to-date with cache. This is useful if caching has + been disabled with -cc or just recently enabled with + UseCache. + + -d Enable dependency debugging. + + -f Use the specified file instead of "Construct" (but first + change to containing directory of ). + + -h Show a help message local to the current build if + one such is defined, and exit. + + -k Keep going as far as possible after errors. + + -o Read override file . + + -p Show construction products in specified trees. + -pa Show construction products and associated actions. + -pw Show products and where they are defined. + + -q Be quiet; multiple -q flags increase quietness level: + 1: quiet about Installing and Removing targets + 2: quiet about build commands, up-to-date targets + + -r Remove construction products associated with + + -R Search for files in . Multiple -R + directories are searched in the order specified. + + -S Use package sig:: to calculate file signatures. + Currently supported values are "md5" for MD5 + signatures (the default) and "md5::debug" for MD5 + signature debug information. + + -t Traverse up the directory hierarchy looking for a + Construct file, if none exists in the current directory. + (Targets will be modified to be relative to the + Construct file.) + + -v Show cons version and continue processing. + -V Show cons version and exit. + + -wf Write all filenames considered into . + + -x Show this message and exit. + + + Please report any suggestions through the cons-discuss@gnu.org mailing + list. + + To subscribe, send mail to cons-discuss-request@gnu.org with body + 'subscribe'. + + If you find a bug, please report it through the bug-cons@gnu.org + mailing list. + + Information about CONS can be obtained from the official cons web site + http://www.dsmit.com/cons/ or its mirrors (listed there). + + The cons maintainers can be contacted by email at cons-maintainers@gnu.org + + User documentation of cons is contained in cons and can be obtained + by doing 'perldoc /path/to/cons'. + +); + +# Simplify program name, if it is a path. +{ + my ($vol, $dir, $file) = File::Spec->splitpath(File::Spec->canonpath($0)); + $0 = $file; +} + +# Default parameters. +$param::topfile = 'Construct'; # Top-level construction file. +$param::install = 1; # Show installations +$param::build = 1; # Build targets +### $param::show = 1; # Show building of targets. +$param::sigpro = 'md5'; # Signature protocol. +$param::depfile = ''; # Write all deps out to this file +$param::salt = ''; # Salt derived file signatures with this. +$param::sourcesig = ['*' => 'content'];# Source file signature calculation +$param::rep_sig_times_ok = 1; # Repository .consign times are in sync + # w/files. +$param::conscript_chdir = 0; # Change dir to Conscript directory +$param::quiet = 0; # should we show the command being executed. + +@param::defaults = (); + +# +$indent = ''; + +# Display a command while executing or otherwise. This +# should be called by command builder action methods. +sub showcom { + print($indent . $_[0] . "\n") if ($param::quiet < 2); +} + +# Default environment. +# This contains only the completely platform-independent information +# we can figure out. Platform-specific information (UNIX, Win32) +# gets added below. +@param::base = ( + 'SIGNATURE' => [ '*' => 'build' ], + 'SUFEXE' => $_exe, # '' on UNIX systems + 'SUFLIB' => $_a, # '.a' on UNIX systems + 'SUFLIBS' => "$_so:$_a", # '.so:.a' on UNIX + 'SUFOBJ' => $_o, # '.o' on UNIX systems + 'SUFMAP' => { + '.c' => 'build::command::cc', + '.s' => 'build::command::cc', + '.S' => 'build::command::cc', + '.C' => 'build::command::cxx', + '.cc' => 'build::command::cxx', + '.cxx'=> 'build::command::cxx', + '.cpp'=> 'build::command::cxx', + '.c++'=> 'build::command::cxx', + '.C++'=> 'build::command::cxx', + }, + 'PERL' => $^X, +); + +%param::rulesets = + ( + # Defaults for Win32. + # Defined for VC++ 6.0 by Greg Spencer + # Your mileage may vary. + 'msvc' => [ + 'CC' => 'cl', + 'CFLAGS' => '/nologo', + 'CCCOM' => '%CC %CFLAGS %_IFLAGS /c %< /Fo%>', + 'CXX' => '%CC', + 'CXXFLAGS' => '%CFLAGS', + 'CXXCOM' => '%CXX %CXXFLAGS %_IFLAGS /c %< /Fo%>', + 'INCDIRPREFIX' => '/I', + 'INCDIRSUFFIX' => '', + 'LINK' => 'link', + 'LINKCOM' => '%LINK %LDFLAGS /out:%> %< %_LDIRS %LIBS', + 'LINKMODULECOM' => '%LD /r /o %> %<', + 'LIBDIRPREFIX' => '/LIBPATH:', + 'LIBDIRSUFFIX' => '', + 'AR' => 'lib', + 'ARFLAGS' => '/nologo ', + 'ARCOM' => "%AR %ARFLAGS /out:%> %<", + 'RANLIB' => '', + 'LD' => 'link', + 'LDFLAGS' => '/nologo ', + 'PREFLIB' => '', + ], + # Defaults for a typical (?) UNIX platform. + # Your mileage may vary. + 'unix' => [ + 'CC' => 'cc', + 'CFLAGS' => '', + 'CCCOM' => '%CC %CFLAGS %_IFLAGS -c %< -o %>', + 'CXX' => '%CC', + 'CXXFLAGS' => '%CFLAGS', + 'CXXCOM' => '%CXX %CXXFLAGS %_IFLAGS -c %< -o %>', + 'INCDIRPREFIX' => '-I', + 'INCDIRSUFFIX' => '', + 'LINK' => '%CXX', + 'LINKCOM' => '%LINK %LDFLAGS -o %> %< %_LDIRS %LIBS', + 'LINKMODULECOM' => '%LD -r -o %> %<', + 'LIBDIRPREFIX' => '-L', + 'LIBDIRSUFFIX' => '', + 'AR' => 'ar', + 'ARFLAGS' => 'r', # rs? + 'ARCOM' => ['%AR %ARFLAGS %> %<', '%RANLIB %>'], + 'RANLIB' => 'ranlib', + 'AS' => 'as', + 'ASFLAGS' => '', + 'ASCOM' => '%AS %ASFLAGS %< -o %>', + 'LD' => 'ld', + 'LDFLAGS' => '', + 'PREFLIB' => 'lib', + 'ENV' => { 'PATH' => '/bin:/usr/bin' }, + ], + ); + +# Set the rules based on the platform. +script::DefaultRules(script::RuleSet($_WIN32 ? 'msvc' : 'unix')); + +# Handle command line arguments. +while (@ARGV) { + $_ = shift @ARGV; + last if /^--$/; # Argument passing to Construct. + &option, next if s/^-//; + push (@param::include, $_), next if s/^\+//; + &equate, next if /=/; + push (@targets, $_), next; +} + +sub option { + my %opt = ( + 'cc' => sub { $param::cachecom = 1; }, + 'cd' => sub { $param::cachedisable = 1; }, + 'cr' => sub { $param::random = 1; }, + 'cs' => sub { $param::cachesync = 1; }, + 'd' => sub { $param::depends = 1; }, + 'h' => sub { $param::localhelp = 1; }, + 'k' => sub { $param::kflag = 1; }, + 'p' => sub { $param::pflag = 1; + $param::build = 0; }, + 'pa' => sub { $param::pflag = 1; + $param::aflag = 1; + $indent = "... "; + $param::build = 0; }, + 'pw' => sub { $param::pflag = 1; + $param::wflag = 1; + $param::build = 0; }, + 'q' => sub { $param::quiet++; }, + 'r' => sub { $param::rflag = 1; + $param::build = 0; }, + 't' => sub { $param::traverse = 1; }, + 'v' => sub { print($version); }, + 'V' => sub { print($version), exit(0); }, + 'x' => sub { print($usage), exit 0; }, + ); + + my %opt_arg = ( + 'f' => sub { $param::topfile = $_[0]; }, + 'o' => sub { $param::overfile = $_[0]; }, + 'R' => sub { script::Repository($_[0]); }, + 'S' => sub { $param::sigpro = $_[0]; }, + 'wf' => sub { $param::depfile = $_[0]; }, + ); + + if (defined $opt{$_}) { + &{$opt{$_}}(); + return; + } + while ($_) { + $_ =~ m/(.)(.*)/; + if (defined $opt{$1}) { + &{$opt{$1}}(); + $_ = $2; + next; + } + if (defined $opt_arg{$1}) { + if (! $2) { + $_ = shift @ARGV; + die("$0: -$1 option requires an argument.\n") if ! $_; + } + &{$opt_arg{$1}}($2 || $_); + return; + } + $_ =~ m/(..)(.*)/; + if (defined $opt_arg{$1}) { + if (! $2) { + $_ = shift @ARGV; + die("$0: -$1 option requires an argument.\n") if ! $_; + } + &{$opt_arg{$1}}($2 || $_); + return; + } + if ($_) { + die qq($0: unrecognized option "-$_". Use -x for a usage message.\n); + } + } +} + +# Process an equate argument (var=val). +sub equate { + my($var, $val) = /([^=]*)=(.*)/; + $script::ARG{$var} = $val; +} + +# Define file signature protocol. +'sig'->select($param::sigpro); + +# Cleanup after an interrupt. +$SIG{INT} = $SIG{QUIT} = $SIG{TERM} = sub { + $SIG{PIPE} = $SIG{INT} = $SIG{QUIT} = $SIG{TERM} = 'IGNORE'; + $SIG{HUP} = $SIG{INT} if ! $main::_WIN32; + warn("\n$0: killed\n"); + # Call this first, to make sure that this processing + # occurs even if a child process does not die (and we + # hang on the wait). + sig::hash::END(); + wait(); + exit(1); +}; +$SIG{HUP} = $SIG{INT} if ! $main::_WIN32; + +# Cleanup after a broken pipe (someone piped our stdout?) +$SIG{PIPE} = sub { + $SIG{PIPE} = $SIG{HUP} = $SIG{INT} = $SIG{QUIT} = $SIG{TERM} = 'IGNORE'; + warn("\n$0: broken pipe\n"); + sig::hash::END(); + wait(); + exit(1); +}; + +if ($param::depfile) { + open (main::DEPFILE, ">".$param::depfile) || + die ("$0: couldn't open $param::depfile ($!)\n"); +} + +# If the supplied top-level Conscript file is not in the +# current directory, then change to that directory. +{ + my ($vol, $dir, $file) = + File::Spec->splitpath(File::Spec->canonpath($param::topfile)); + if ($vol || $dir) { + my($cd) = File::Spec->catpath($vol, $dir, undef); + chdir($cd) || die("$0: couldn't change to directory $cd ($!)\n"); + $param::topfile = $file; + } +} + +# Walk up the directory hierarchy looking for a Conscript file (if -t set). +my($target_top); +my(@targetdir) = (); +if ($param::traverse && ! -f $param::topfile) { + my($vol, $dirs, $file) = File::Spec->splitpath(cwd()); + my(@dirs) = (File::Spec->splitdir($dirs), $file); + while (! -f File::Spec->catpath($vol, File::Spec->catdir(@dirs), + $param::topfile)) { + die("$0: unable to find $param::topfile.\n") if ! @dirs; + unshift(@targetdir, pop(@dirs)); + } + my($cwd) = File::Spec->catpath($vol, File::Spec->catdir(@dirs), ''); + print "$0: Entering directory `$cwd'\n"; + chdir($cwd); + @targets = map {File::Spec->catdir(@targetdir, $_)} @targets; +} + +# Set up $dir::top and $dir::cwd, now that we are in the right directory. +dir::init(); + +# +if (@targetdir) { + $target_top = $dir::top->lookupdir(File::Spec->catdir(@targetdir)); +} + +# Now handle override file. +package override; +if ($param::overfile) { + my($ov) = $param::overfile; + die qq($0: can\'t read override file "$ov" ($!)\n) if ! -f $ov; #' + do $ov; + if ($@) { + chop($@); + die qq($0: errors in override file "$ov" ($@)\n); + } +} + +# Provide this to user to setup override patterns. +sub Override { + my($re, @env) = @_; + return if $param::overrides{$re}; # if identical, first will win. + $param::overrides = 1; + $param::overrides{$re} = \@env; + push(@param::overrides, $re); +} + +package main; + +use vars qw( %priority $errors ); + +# Check script inclusion regexps +my $re; +for $re (@param::include) { + if (! defined eval {"" =~ /$re/}) { + my($err) = $@; + $err =~ s/in regexp at .*$//; + die("$0: error in regexp $err"); + } +} + +# Read the top-level construct file and its included scripts. +doscripts($param::topfile); + +# Status priorities. This lets us aggregate status for directories +# and print an appropriate message (at the top-level). +%priority = + ('none' => 1, 'handled' => 2, 'built' => 3, 'unknown' => 4, 'errors' => 5); + +# If no targets were specified, supply default targets (if any). +@targets = @param::default_targets if ! @targets; + +$errors = 0; + +# Build the supplied target patterns. +my $tgt; +for $tgt (map($dir::top->lookup($_), @targets)) { + if ($target_top && ! $tgt->is_under($target_top)) { + # A -t option was used, and this target is not underneath + # the directory where we were invoked via -t. + # If the target is a directory and the -t directory + # is underneath it, then build the -t directory. + if (ref $tgt ne "dir" || ! $target_top->is_under($tgt)) { + next; + } + $tgt = $target_top; + } + buildtoptarget($tgt); +} + +exit 0 + ($errors != 0); + +sub buildtoptarget { + my($tgt) = @_; + return if ! $tgt; + my($status) = buildtarget($tgt); + if ($status ne 'built') { + my($path) = $tgt->path; + if ($status eq "errors") { + print qq($0: "$path" not remade because of errors.\n); + $errors++; + } elsif ($status eq "handled") { + print qq($0: "$path" is up-to-date.\n) if ($param::quiet < 2); + } elsif ($status eq "unknown") { + # cons error already reported. + $errors++; + } elsif ($status eq "none") { + # search for targets that may be linked to the given path. + my @linked = dir::linked_targets($tgt) if $target_top; + if (@linked) { + my @names = map($_->path, @linked); + print "Linked targets: @names\n" if ($param::quiet < 1); + map(buildtoptarget($_), @linked); + } else { + print qq($0: nothing to be built in "$path".\n) + if $param::build && ($param::quiet < 2); + } + } else { + print qq($0: don\'t know how to construct "$path".\n); #' + $errors++; + } + } +} + +# Build the supplied target directory or files. Return aggregated status. +sub buildtarget { + my($tgt) = @_; + if (ref($tgt) eq "dir") { + my($result) = "none"; + my($priority) = $priority{$result}; + if (exists $tgt->{member}) { + my($members) = $tgt->{member}; + my $entry; + for $entry (sort keys %$members) { + next if $entry eq $dir::CURDIR || $entry eq $dir::UPDIR; + my($tgt) = $members->{$entry}; + next if ref($tgt) ne "dir" && !exists($tgt->{builder}); + my($stat) = buildtarget($members->{$entry}); + my($pri) = $priority{$stat}; + if ($pri > $priority) { + $priority = $pri; + $result = $stat; + } + } + } + return $result; + } + if ($param::depends) { + my($path) = $tgt->path; + if ($tgt->{builder}) { + my(@dep) = (@{$tgt->{dep}}, @{$tgt->{sources}}); + my($dep) = join(' ',map($_->path, @dep)); + print("Target $path: $dep\n"); + } else { + print("Target $path: not a derived file\n"); + } + } + if ($param::build) { + return build $tgt; + } elsif ($param::pflag || $param::wflag || $param::aflag) { + if ($tgt->{builder}) { + if ($param::wflag) { + print qq(${\$tgt->path}: $tgt->{script}\n); + } elsif ($param::pflag) { + print qq(${\$tgt->path}:\n) if $param::aflag; + print qq(${\$tgt->path}\n) if !$param::aflag; + } + if ($param::aflag) { + $tgt->{builder}->action($tgt); + } + } + } elsif ($param::rflag && $tgt->{builder}) { + my($path) = $tgt->path; + if (-f $path) { + if (unlink($path)) { + print("Removed $path\n") if ($param::quiet < 1); + } else { + warn("$0: couldn't remove $path\n"); + } + } + } + + return "none"; +} + +package NameSpace; + +# Return a hash that maps the name of symbols in a namespace to an +# array of refs for all types for which the name has a defined value. +# A list of symbols may be specified; default is all symbols in the +# name space. +sub save { + my $package = shift; + my(%namerefs, $var, $type); + no strict 'refs'; + @_ = keys %{$package."::"} if ! @_; + foreach $var (@_) { + $namerefs{$var} = []; + my $fqvar = $package."::".$var; + # If the scalar for this variable name doesn't already + # exist, *foo{SCALAR} will autovivify the reference + # instead of returning undef, so unlike the other types, + # we have to dereference to find out if it exists. + push(@{$namerefs{$var}}, *{$fqvar}{SCALAR}) + if defined ${*{$fqvar}{SCALAR}}; + foreach $type (qw(ARRAY HASH CODE IO)) { + push(@{$namerefs{$var}}, *{$fqvar}{$type}) + if defined *{$fqvar}{$type}; + } + } + return \%namerefs; +} + +# Remove the specified symbols from the namespace. +# Default is to remove all. +sub remove { + my $package = shift; + my(%namerefs, $var); + no strict 'refs'; + @_ = keys %{$package."::"} if ! @_; + foreach $var (@_) { + delete ${$package."::"}{$var}; + } +} + +# Restore values to symbols specified in a hash as returned +# by NameSpace::save. +sub restore { + my($package, $namerefs) = @_; + my($var, $ref); + no strict 'refs'; + foreach $var (keys %$namerefs) { + my $fqvar = $package."::".$var; + foreach $ref (@{$namerefs->{$var}}) { + *{$fqvar} = $ref; + } + } +} + +# Support for "building" scripts, importing and exporting variables. +# With the exception of the top-level routine here (invoked from the +# main package by cons), these are all invoked by user scripts. +package script; + +use vars qw( $ARG $caller_dir_path %special_var ); + +BEGIN { + # We can't Export or Import the following variables because Perl always + # treats them as part of the "main::" package (see perlvar(1)). + %special_var = map {$_ => 1} qw(ENV INC ARGV ARGVOUT SIG + STDIN STDOUT STDERR); +} + +# This is called from main to interpret/run the top-level Construct +# file, passed in as the single argument. +sub main::doscripts { + my($script) = @_; + Build($script); + # Now set up the includes/excludes (after the Construct file is read). + $param::include = join('|', @param::include); + + # Save the original variable names from the script package. + # These will stay intact, but any other "script::" variables + # defined in a Conscript file will get saved, deleted, + # and (when necessary) restored. + my(%orig_script_var) = map {$_ => 1} keys %script::; + $caller_dir_path = undef; + my $cwd = Cwd::cwd(); + my(@scripts) = pop(@priv::scripts); + while ($priv::self = shift(@scripts)) { + my($path) = $priv::self->{script}->rsrcpath; + if (-f $path) { + $dir::cwd = $priv::self->{script}->{dir}; + # Handle chdir to the Conscript file directory, if necessary. + my ($vol, $dir, $file); + if ($param::conscript_chdir) { + ($vol, $dir, $file) = + File::Spec->splitpath(File::Spec->canonpath($path)); + if ($vol ne '' || $dir ne '') { + $caller_dir_path = File::Spec->catpath($vol, $dir, undef); + chdir($caller_dir_path) || + die "Could not chdir to $caller_dir_path: $!\n"; + } + } else { + $file = $path; + } + # Actually process the Conscript file. + do $file; + # Save any variables defined by the Conscript file + # so we can restore them later, if needed; + # then delete them from the script:: namespace. + my(@del) = grep(! $orig_script_var{$_}, keys %script::); + if (@del) { + $priv::self->{script}->{pkgvars} = NameSpace::save('script', + @del); + NameSpace::remove('script', @del); + } + if ($caller_dir_path) { + chdir($cwd); + $caller_dir_path = undef; + } + if ($@) { + chomp($@); + my $err = ($@ =~ /\n/ms) ? ":\n$@" : " ($@)"; + print qq($0: error in file "$path"$err\n); + $run::errors++; + } else { + # Only process subsidiary scripts if no errors in parent. + unshift(@scripts, @priv::scripts); + } + undef @priv::scripts; + } else { + my $where = ''; + my $cref = $priv::self->{script}->creator; + if (defined $cref) { + my($_foo, $script, $line, $sub) = @$cref; + $where = " ($sub in $script, line $line)"; + } + warn qq(Ignoring missing script "$path"$where); + } + } + die("$0: script errors encountered: construction aborted\n") + if $run::errors; +} + +# Return caller info about the method being invoked. +# This is everything from the Perl "caller" builtin function, +# including which Construct/Conscript file, line number, +# subroutine name, etc. +sub caller_info { + my($lev) = 1; + my(@frame); + do { + @frame = caller ++$lev; + if (defined($frame[3]) && $frame[3] eq '(eval)') { + @frame = caller --$lev; + if ($caller_dir_path) { + $frame[1] = File::Spec->catfile($caller_dir_path, $frame[1]); + } + return @frame; + } + } while ($frame[3]); + return; +} + +# Link a directory to another. This simply means set up the *source* +# for the directory to be the other directory. +sub Link { + dir::link(@_); +} + +# Add directories to the repository search path for files. +# Strip our current directory from the list so Repository +# (or -R options) can be used from within the repository. +sub Repository { + my($my_dir) = Cwd::cwd(); + my $dir; + foreach $dir (@_) { + # The following more direct call isn't available in + # Cwd.pm until some time after 5.003... + # my($d) = Cwd::abs_path($dir); + chdir($dir); + my($d) = Cwd::cwd(); + chdir($my_dir); + # + next if ! $d || ! -d $d || $d eq $my_dir; + # We know we can get away with passing undef to lookupdir + # as the directory because $dir is an absolute path. + push(@param::rpath, dir::lookupdir(undef, $dir)); + push @INC, $d; + } +} + +# Return the list of Repository directories specified. +sub Repository_List { + map($_->path, @param::rpath); +} + +# Specify whether the .consign signature times in repository files are, +# in fact, consistent with the times on the files themselves. +sub Repository_Sig_Times_OK { + $param::rep_sig_times_ok = shift; +} + +sub SourceSignature { + $param::sourcesig = [@_]; +} + +# Specify whether we should chdir to the containing directories +# of Conscript files. +sub Conscript_chdir { + $param::conscript_chdir = shift; +} + +# Specify files/targets that must be present and built locally, +# even if they exist already-built in a Repository. +sub Local { + my(@files) = map($dir::cwd->lookupfile($_), @_); + map($_->local(1), @files); +} + +# Export variables to any scripts invoked from this one. +sub Export { + my(@illegal) = grep($special_var{$_}, @_); + if (@illegal) { + die qq($0: cannot Export special Perl variables: @illegal\n); + } + @{$priv::self->{exports}} = grep(! defined $special_var{$_}, @_); +} + +# Import variables from the export list of the caller +# of the current script. +sub Import { + my(@illegal) = grep($special_var{$_}, @_); + if (@illegal) { + die qq($0: cannot Import special Perl variables: @illegal\n); + } + my($parent) = $priv::self->{parent}; + my($imports) = $priv::self->{imports}; + @{$priv::self->{exports}} = keys %$imports; + my($var); + foreach $var (grep(! defined $special_var{$_}, @_)) { + if (!exists $imports->{$var}) { + my($path) = $parent->{script}->path; + die qq($0: variable "$var" not exported by file "$path"\n); + } + if (!defined $imports->{$var}) { + my $path = $parent->{script}->path; + my $err = "$0: variable \"$var\" exported but not " . + "defined by file \"$path\"\n"; + die $err; + } + ${"script::$var"} = $imports->{$var}; + } +} + +# Build an inferior script. That is, arrange to read and execute +# the specified script, passing to it any exported variables from +# the current script. +sub Build { + my(@files) = map($dir::cwd->lookupfile($_), @_); + my(%imports) = map {$_ => ${"script::$_"}} @{$priv::self->{exports}}; + my $file; + for $file (@files) { + next if $param::include && $file->path !~ /$param::include/o; + my($self) = {'script' => $file, + 'parent' => $priv::self, + 'imports' => \%imports}; + bless $self; # may want to bless into class of parent in future + push(@priv::scripts, $self); + } +} + +# Set up regexps dependencies to ignore. Should only be called once. +sub Ignore { + die("Ignore called more than once\n") if $param::ignore; + $param::ignore = join("|", map("($_)", @_)) if @_; +} + +# Specification of default targets. +sub Default { + push(@param::default_targets, map($dir::cwd->lookup($_)->path, @_)); +} + +# Local Help. Should only be called once. +sub Help { + if ($param::localhelp) { + print "@_\n"; + exit 2; + } +} + +# For windows platforms which use unix tool sets, the msvc defaults may +# not be useful. Also, in the future, other platforms (Mac?) may have the +# same problem. +sub RuleSet { + my $style = shift; + my @rulesets = sort keys %param::rulesets; + die "Unknown style for rules: $style.\n" . + "Supported rules are: (" . join(" ", @rulesets) . ")" + unless eval(join("||", map("\$style eq '$_'", @rulesets))); + return @param::base, @{$param::rulesets{$style}}; +} + +sub DefaultRules { + @param::defaults = (); + push @param::defaults, @_; +} + +# Return the build name(s) of a file or file list. +sub FilePath { + wantarray + ? map($dir::cwd->lookupfile($_)->path, @_) + : $dir::cwd->lookupfile($_[0])->path; +} + +# Return the build name(s) of a directory or directory list. +sub DirPath { + wantarray + ? map($dir::cwd->lookupdir($_)->path, @_) + : $dir::cwd->lookupdir($_[0])->path; +} + +# Split the search path provided into components. Look each up +# relative to the current directory. +# The usual path separator problems abound; for now we'll use : +sub SplitPath { + my($dirs) = @_; + if (ref($dirs) ne "ARRAY") { + $dirs = [ split(/$main::PATH_SEPARATOR/o, $dirs) ]; + } + map { DirPath($_) } @$dirs; +} + +# Return true if the supplied path is available as a source file +# or is buildable (by rules seen to-date in the build). +sub ConsPath { + my($path) = @_; + my($file) = $dir::cwd->lookup($path); + return $file->accessible; +} + +# Return the source path of the supplied path. +sub SourcePath { + wantarray + ? map($dir::cwd->lookupfile($_)->rsrcpath, @_) + : $dir::cwd->lookupfile($_[0])->rsrcpath; +} + +# Search up the tree for the specified cache directory, starting with +# the current directory. Returns undef if not found, 1 otherwise. +# If the directory is found, then caching is enabled. The directory +# must be readable and writable. If the argument "mixtargets" is provided, +# then targets may be mixed in the cache (two targets may share the same +# cache file--not recommended). +sub UseCache($@) { + my($dir, @args) = @_; + # NOTE: it's important to process arguments here regardless of whether + # the cache is disabled temporarily, since the mixtargets option affects + # the salt for derived signatures. + for (@args) { + if ($_ eq "mixtargets") { + # When mixtargets is enabled, we salt the target signatures. + # This is done purely to avoid a scenario whereby if + # mixtargets is turned on or off after doing builds, and + # if cache synchronization with -cs is used, then + # cache files may be shared in the cache itself (linked + # under more than one name in the cache). This is not bad, + # per se, but simply would mean that a cache cleaning algorithm + # that looked for a link count of 1 would never find those + # particular files; they would always appear to be in use. + $param::salt = 'M' . $param::salt; + $param::mixtargets = 1; + } else { + die qq($0: UseCache unrecognized option "$_"\n); + } + } + if ($param::cachedisable) { + warn("Note: caching disabled by -cd flag\n"); + return 1; + } + my($depth) = 15; + while ($depth-- && ! -d $dir) { + $dir = File::Spec->catdir($dir::UPDIR, $dir); + } + if (-d $dir) { + $param::cache = $dir; + return 1; + } + return undef; +} + +# Salt the signature generator. The salt (a number of string) is added +# into the signature of each derived file. Changing the salt will +# force recompilation of all derived files. +sub Salt($) { + # We append the value, so that UseCache and Salt may be used + # in either order without changing the signature calculation. + $param::salt .= $_[0]; +} + +# Mark files (or directories) to not be removed before building. +sub Precious { + map($_->{precious} = 1, map($dir::cwd->lookup($_), @_)); +} + + +# These methods are callable from Conscript files, via a cons +# object. Procs beginning with _ are intended for internal use. +package cons; + +use vars qw( %envcache ); + +# This is passed the name of the base environment to instantiate. +# Overrides to the base environment may also be passed in +# as key/value pairs. +sub new { + my($package) = shift; + my ($env) = {@param::defaults, @_}; + @{$env->{_envcopy}} = %$env; # Note: we never change PATH + $env->{_cwd} = $dir::cwd; # Save directory of environment for + bless $env, $package; # any deferred name interpretation. +} + +# Clone an environment. +# Note that the working directory will be the initial directory +# of the original environment. +sub clone { + my($env) = shift; + my $clone = {@{$env->{_envcopy}}, @_}; + @{$clone->{_envcopy}} = %$clone; # Note: we never change PATH + $clone->{_cwd} = $env->{_cwd}; + bless $clone, ref $env; +} + +# Create a flattened hash representing the environment. +# It also contains a copy of the PATH, so that the path +# may be modified if it is converted back to a hash. +sub copy { + my($env) = shift; + (@{$env->{_envcopy}}, 'ENV' => {%{$env->{ENV}}}, @_) +} + +# Resolve which environment to actually use for a given +# target. This is just used for simple overrides. +sub _resolve { + return $_[0] if !$param::overrides; + my($env, $tgt) = @_; + my($path) = $tgt->path; + my $re; + for $re (@param::overrides) { + next if $path !~ /$re/; + # Found one. Return a combination of the original environment + # and the override. + my($ovr) = $param::overrides{$re}; + return $envcache{$env,$re} if $envcache{$env,$re}; + my($newenv) = {@{$env->{_envcopy}}, @$ovr}; + @{$newenv->{_envcopy}} = %$env; + $newenv->{_cwd} = $env->{_cwd}; + return $envcache{$env,$re} = bless $newenv, ref $env; + } + return $env; +} + +# Substitute construction environment variables into a string. +# Internal function/method. +sub _subst { + my($env, $str) = @_; + if (! defined $str) { + return undef; + } elsif (ref($str) eq "ARRAY") { + return [ map($env->_subst($_), @$str) ]; + } else { + # % expansion. %% gets converted to % later, so expand any + # %keyword construction that doesn't have a % in front of it, + # modulo multiple %% pairs in between. + # In Perl 5.005 and later, we could actually do this in one regex + # using a conditional expression as follows, + # while ($str =~ s/($pre)\%(\{)?([_a-zA-Z]\w*)(?(2)\})/"$1". + # $env->{$3}/ge) {} + # The following two-step approach is backwards-compatible + # to (at least) Perl5.003. + my $pre = '^|[^\%](?:\%\%)*'; + while (($str =~ s/($pre)\%([_a-zA-Z]\w*)/$1.($env->{$2}||'')/ge) || + ($str =~ s/($pre)\%\{([_a-zA-Z]\w*)\}/$1.($env->{$2}||'')/ge)) { + } + return $str; + } +} + +sub AfterBuild { + my($env) = shift; + my($perl_eval_str) = pop(@_); + my $file; + for $file (map($dir::cwd->lookup($_), @_)) { + $file->{after_build_func} = $perl_eval_str; + } +} + +sub Install { + my($env) = shift; + my($tgtdir) = $dir::cwd->lookupdir($env->_subst(shift)); + my $file; + for $file (map($dir::cwd->lookupfile($env->_subst($_)), @_)) { + my($tgt) = $tgtdir->lookupfile($file->{entry}); + $tgt->bind(find build::install($env), $file); + } +} + +sub InstallAs { + my $env = shift; + my $tgt = shift; + my $src = shift; + my @sources = (); + my @targets = (); + + if (ref $tgt) { + die "InstallAs: Source is a file and target is a list!\n" + if (!ref($src)); + @sources = @$src; + @targets = @$tgt; + } elsif (ref $src) { + die "InstallAs: Target is a file and source is a list!\n"; + } else { + push @sources, $src; + push @targets, $tgt; + } + + if ($#sources != $#targets) { + my $tn = $#targets+1; + my $sn = $#sources+1; + die "InstallAs: Source file list ($sn) and target file list ($tn) " . + "are inconsistent in length!\n"; + } else { + foreach (0..$#sources) { + my $tfile = $dir::cwd->lookupfile($env->_subst($targets[$_])); + my $sfile = $dir::cwd->lookupfile($env->_subst($sources[$_])); + $tfile->bind(find build::install($env), $sfile); + } + } +} + +# Installation in a local build directory, +# copying from the repository if it's already built there. +# Functionally equivalent to: +# Install $env $dir, $file; +# Local "$dir/$file"; +sub Install_Local { + my($env) = shift; + my($tgtdir) = $dir::cwd->lookupdir($env->_subst(shift)); + my $file; + for $file (map($dir::cwd->lookupfile($env->_subst($_)), @_)) { + my($tgt) = $tgtdir->lookupfile($file->{entry}); + $tgt->bind(find build::install($env), $file); + $tgt->local(1); + } +} + +sub Objects { + my($env) = shift; + map($dir::cwd->relpath($_), $env->_Objects(@_)); +} + +# Called with multiple source file references (or object files). +# Returns corresponding object files references. +sub _Objects { + my($env) = shift; + my($suffix) = $env->{SUFOBJ}; + map($env->_Object($_, $_->{dir}->lookupfile($_->base_suf($suffix))), + map { ref $_ ? $_ : $dir::cwd->lookupfile($env->_subst($_)) } + grep(defined $_, @_)); +} + +# Called with an object and source reference. If no object reference +# is supplied, then the object file is determined implicitly from the +# source file's extension. Sets up the appropriate rules for creating +# the object from the source. Returns the object reference. +sub _Object { + my($env, $src, $obj) = @_; + return $obj if $src eq $obj; # don't need to build self from self. + my($objenv) = $env->_resolve($obj); + my($suffix) = $src->suffix; + + my($builder) = $env->{SUFMAP}{$suffix}; + + if ($builder) { + $obj->bind((find $builder($objenv)), $src); + } else { + die("don't know how to construct ${\$obj->path} from " . + "${\$src->path}.\n"); + } + $obj +} + +sub Program { + my($env) = shift; + my($tgt) = $dir::cwd->lookupfile(file::addsuffix($env->_subst(shift), + $env->{SUFEXE})); + my($progenv) = $env->_resolve($tgt); + $tgt->bind(find build::command::link($progenv, $progenv->{LINKCOM}), + $env->_Objects(@_)); +} + +sub Module { + my($env) = shift; + my($tgt) = $dir::cwd->lookupfile($env->_subst(shift)); + my($modenv) = $env->_resolve($tgt); + my($com) = pop(@_); + $tgt->bind(find build::command::link($modenv, $com), $env->_Objects(@_)); +} + +sub LinkedModule { + my($env) = shift; + my($tgt) = $dir::cwd->lookupfile($env->_subst(shift)); + my($progenv) = $env->_resolve($tgt); + $tgt->bind(find build::command::linkedmodule + ($progenv, $progenv->{LINKMODULECOM}), + $env->_Objects(@_)); +} + +sub Library { + my($env) = shift; + my($lib) = $dir::cwd->lookupfile(file::addsuffix($env->_subst(shift), + $env->{SUFLIB})); + my($libenv) = $env->_resolve($lib); + $lib->bind(find build::command::library($libenv), $env->_Objects(@_)); +} + +# Simple derivation: you provide target, source(s), command. +# Special variables substitute into the rule. +# Target may be a reference, in which case it is taken +# to be a multiple target (all targets built at once). +sub Command { + my($env) = shift; + my($tgt) = $env->_subst(shift); + my($builder) = find build::command::user($env, pop(@_), 'script'); + my(@sources) = map($dir::cwd->lookupfile($env->_subst($_)), @_); + if (ref($tgt)) { + # A multi-target command. + my(@tgts) = map($dir::cwd->lookupfile($_), @$tgt); + die("empty target list in multi-target command\n") if !@tgts; + $env = $env->_resolve($tgts[0]); + my($multi) = build::multiple->new($builder, \@tgts); + for $tgt (@tgts) { + $tgt->bind($multi, @sources); + } + } else { + $tgt = $dir::cwd->lookupfile($tgt); + $env = $env->_resolve($tgt); + $tgt->bind($builder, @sources); + } +} + +sub Depends { + my($env) = shift; + my($tgt) = $env->_subst(shift); + my(@deps) = map($dir::cwd->lookup($env->_subst($_)), @_); + if (! ref($tgt)) { + $tgt = [ $tgt ]; + } + my($t); + foreach $t (map($dir::cwd->lookupfile($_), @$tgt)) { + push(@{$t->{dep}}, @deps); + } +} + +# Setup a quick scanner for the specified input file, for the +# associated environment. Any use of the input file will cause the +# scanner to be invoked, once only. The scanner sees just one line at +# a time of the file, and is expected to return a list of +# dependencies. +sub QuickScan { + my($env, $code, $file, $path) = @_; + $dir::cwd->lookup($env->_subst($file))->{'srcscan',$env} = + find scan::quickscan($code, $env, $env->_subst($path)); +} + +# Generic builder module. Just a few default methods. Every derivable +# file must have a builder object of some sort attached. Usually +# builder objects are shared. +package build; + +use vars qw( %builder ); + +# Every builder must now have at least an associated environment, +# so we can find its sigarray and calculate the proper signature. +sub find { + my($class, $env) = @_; + $builder{$env} || do { + my $self = { env => $env }; + $builder{$env} = bless $self, $class; + } +} + +# Null signature for dynamic includes. +sub includes { () } + +# Null signature for build script. +sub scriptsig { () } + +# Not compatible with any other builder, by default. +sub compatible { 0 } + + +# Builder module for the Install command. +package build::install; + +use vars qw( @ISA ); + +BEGIN { @ISA = qw(build) } + +# Caching not supported for Install: generally install is trivial anyway, +# and we don't want to clutter the cache. +sub cachin { undef } +sub cachout { } + +# Do the installation. +sub action { + my($self, $tgt) = @_; + my($src) = $tgt->{sources}[0]; + main::showcom("Install ${\$src->rpath} as ${\$tgt->path}") + if ($param::install && $param::quiet < 1); + return unless $param::build; + futil::install($src->rpath, $tgt); + return 1; +} + + +# Builder module for generic UNIX commands. +package build::command; + +use vars qw( @ISA %com ); + +BEGIN { @ISA = qw(build) } + +sub find { + my($class, $env, $cmd, $package) = @_; + my($act) = action::new($env, $cmd); + $package ||= ''; + $com{$env,$act,$package} || do { + my $self = { env => $env, act => $act, 'package' => $package }; + $com{$env,$act,$package} = bless $self, $class; + } +} + +# Default cache in function. +sub cachin { + my($self, $tgt, $sig) = @_; + if (cache::in($tgt, $sig)) { + if ($param::cachecom) { + $self->{act}->show($self->{env}, $tgt); + } else { + printf("Retrieved %s from cache\n", $tgt->path) + if ($param::quiet < 1); + } + return 1; + } + return undef; +} + +# Default cache out function. +sub cachout { + my($self, $tgt, $sig) = @_; + cache::out($tgt, $sig); +} + +# Build the target using the previously specified commands. +sub action { + my($self, $tgt) = @_; + $self->{act}->execute($self->{env}, $tgt, $self->{'package'}); +} + +# Return script signature. +sub scriptsig { + $_[0]->{act}->scriptsig +} + + +# Create a linked module. +package build::command::link; + +use vars qw( @ISA ); + +BEGIN { @ISA = qw(build::command) } + +# Find an appropriate linker. +sub find { + my($class, $env, $command) = @_; + if (!exists $env->{_LDIRS}) { + my($ldirs) = ''; + my($wd) = $env->{_cwd}; + my($pdirs) = $env->{LIBPATH}; + if (! defined $pdirs) { + $pdirs = [ ]; + } elsif (ref($pdirs) ne 'ARRAY') { + $pdirs = [ split(/$main::PATH_SEPARATOR/o, $pdirs) ]; + } + my($dir, $dpath); + for $dir (map($wd->lookupdir($env->_subst($_)), @$pdirs)) { + $dpath = $dir->path; + # Add the (presumably local) directory to the -L flags + # if we're not using repositories, the directory exists, + # or it's Linked to a source directory (that is, it *will* + # exist by the time the link occurs). + $ldirs .= " ".$env->{LIBDIRPREFIX}.$dpath.$env->{LIBDIRSUFFIX} + if ! @param::rpath || -d $dpath || $dir->is_linked; + next if File::Spec->file_name_is_absolute($dpath); + if (@param::rpath) { + my $d; + if ($dpath eq $dir::CURDIR) { + foreach $d (map($_->path, @param::rpath)) { + $ldirs .= " " . $env->{LIBDIRPREFIX} . + $d . $env->{LIBDIRSUFFIX}; + } + } else { + my($rpath); + foreach $d (map($_->path, @param::rpath)) { + $rpath = File::Spec->catfile($d, $dpath); + $ldirs .= " ". $env->{LIBDIRPREFIX} . + $rpath . $env->{LIBDIRSUFFIX} if -d $rpath; + } + } + } + } + $env->{_LDIRS} = "%($ldirs%)"; + } + + # Introduce a new magic _LIBS symbol which allows to use the + # Unix-style -lNAME syntax for Win32 only. -lNAME will be replaced + # with %{PREFLIB}NAME%{SUFLIB}. 1998-06-18 + + if ($main::_WIN32 && !exists $env->{_LIBS}) { + my $libs; + my $name; + for $name (split(' ', $env->_subst($env->{LIBS} || ''))) { + if ($name =~ /^-l(.*)/) { + $name = "$env->{PREFLIB}$1$env->{SUFLIB}"; + } + $libs .= ' ' . $name; + } + $env->{_LIBS} = $libs ? "%($libs%)" : ''; + } + bless find build::command($env, $command); +} + +# Called from file::build. Make sure any libraries needed by the +# environment are built, and return the collected signatures +# of the libraries in the path. +sub includes { + return $_[0]->{'bsig'} if exists $_[0]->{'bsig'}; + my($self, $tgt) = @_; + my($env) = $self->{env}; + my($ewd) = $env->{_cwd}; + my $ldirs = $env->{LIBPATH}; + if (! defined $ldirs) { + $ldirs = [ ]; + } elsif (ref($ldirs) ne 'ARRAY') { + $ldirs = [ split(/$main::PATH_SEPARATOR/o, $ldirs) ]; + } + my @lpath = map($ewd->lookupdir($_), @$ldirs); + my(@sigs); + my(@names); + + # Pass %LIBS symbol through %-substituition + # 1998-06-18 + @names = split(' ', $env->_subst($env->{LIBS} || '')); + my $name; + for $name (@names) { + my ($lpath, @allnames); + if ($name =~ /^-l(.*)/) { + # -l style names are looked up on LIBPATH, using all + # possible lib suffixes in the same search order the + # linker uses (according to SUFLIBS). + # Recognize new PREFLIB symbol, which should be 'lib' on + # Unix, and empty on Win32. TODO: What about shared + # library suffixes? 1998-05-13 + @allnames = map("$env->{PREFLIB}$1$_", + split(/:/, $env->{SUFLIBS})); + $lpath = \@lpath; + } else { + @allnames = ($name); + # On Win32, all library names are looked up in LIBPATH + # 1998-05-13 + if ($main::_WIN32) { + $lpath = [$dir::top, @lpath]; + } + else { + $lpath = [$dir::top]; + } + } + my $dir; + DIR: for $dir (@$lpath) { + my $n; + for $n (@allnames) { + my($lib) = $dir->lookup_accessible($n); + if ($lib) { + last DIR if $lib->ignore; + if ((build $lib) eq 'errors') { + $tgt->{status} = 'errors'; + return undef; + } + push(@sigs, 'sig'->signature($lib)); + last DIR; + } + } + } + } + $self->{'bsig'} = 'sig'->collect(@sigs); +} + +# Always compatible with other such builders, so the user +# can define a single program or module from multiple places. +sub compatible { + my($self, $other) = @_; + ref($other) eq "build::command::link"; +} + +# Link a program. +package build::command::linkedmodule; + +use vars qw( @ISA ); + +BEGIN { @ISA = qw(build::command) } + +# Always compatible with other such builders, so the user +# can define a single linked module from multiple places. +sub compatible { + my($self, $other) = @_; + ref($other) eq "build::command::linkedmodule"; +} + +# Builder for a C module +package build::command::cc; + +use vars qw( @ISA ); + +BEGIN { @ISA = qw(build::command) } + +sub find { + $_[1]->{_cc} || do { + my($class, $env) = @_; + my($cpppath) = $env->_subst($env->{CPPPATH}); + my($cscanner) = find scan::cpp($env->{_cwd}, $cpppath); + $env->{_IFLAGS} = "%(" . $cscanner->iflags($env) . "%)"; + my($self) = find build::command($env, $env->{CCCOM}); + $self->{scanner} = $cscanner; + bless $env->{_cc} = $self; + } +} + +# Invoke the associated C scanner to get signature of included files. +sub includes { + my($self, $tgt) = @_; + $self->{scanner}->includes($tgt, $tgt->{sources}[0]); +} + +# Builder for a C++ module +package build::command::cxx; + +use vars qw( @ISA ); + +BEGIN { @ISA = qw(build::command) } + +sub find { + $_[1]->{_cxx} || do { + my($class, $env) = @_; + my($cpppath) = $env->_subst($env->{CPPPATH}); + my($cscanner) = find scan::cpp($env->{_cwd}, $cpppath); + $env->{_IFLAGS} = "%(" . $cscanner->iflags($env) . "%)"; + my($self) = find build::command($env, $env->{CXXCOM}); + $self->{scanner} = $cscanner; + bless $env->{_cxx} = $self; + } +} + +# Invoke the associated C scanner to get signature of included files. +sub includes { + my($self, $tgt) = @_; + $self->{scanner}->includes($tgt, $tgt->{sources}[0]); +} + +# Builder for a user command (cons::Command). We assume that a user +# command might be built and implement the appropriate dependencies on +# the command itself (actually, just on the first word of the command +# line). +package build::command::user; + +use vars qw( @ISA ); + +BEGIN { @ISA = qw(build::command) } + +sub includes { + my($self, $tgt) = @_; + my($sig) = ''; + + # Check for any quick scanners attached to source files. + my $dep; + for $dep (@{$tgt->{dep}}, @{$tgt->{sources}}) { + my($scanner) = $dep->{'srcscan',$self->{env}}; + if ($scanner) { + $sig .= $scanner->includes($tgt, $dep); + } + } + + # XXX Optimize this to not use ignored paths. + if (! exists $self->{_comsig}) { + my($env) = $self->{env}; + $self->{_comsig} = ''; + my($com, $dir); + com: + for $com ($self->{act}->commands) { + my($pdirs) = $env->{ENV}->{PATH}; + if (! defined $pdirs) { + $pdirs = [ ]; + } elsif (ref($pdirs) ne 'ARRAY') { + $pdirs = [ split(/$main::PATH_SEPARATOR/o, $pdirs) ]; + } + for $dir (map($dir::top->lookupdir($_), @$pdirs)) { + my($prog) = $dir->lookup_accessible($com); + if ($prog) { # XXX Not checking execute permission. + if ((build $prog) eq 'errors') { + $tgt->{status} = 'errors'; + return $sig; + } + next com if $prog->ignore; + $self->{_comsig} .= 'sig'->signature($prog); + next com; + } + } + } + } + + return $self->{_comsig} . $sig +} + + +# Builder for a library module (archive). +# We assume that a user command might be built and implement the +# appropriate dependencies on the command itself. +package build::command::library; + +use vars qw( @ISA ); + +BEGIN { @ISA = qw(build::command) } + +sub find { + my($class, $env) = @_; + bless find build::command($env, $env->{ARCOM}) +} + +# Always compatible with other library builders, so the user +# can define a single library from multiple places. +sub compatible { + my($self, $other) = @_; + ref($other) eq "build::command::library"; +} + +# A multi-target builder. +# This allows multiple targets to be associated with a single build +# script, without forcing all the code to be aware of multiple targets. +package build::multiple; + +sub new { + my($class, $builder, $tgts) = @_; + bless { 'builder' => $builder, 'env' => $builder->{env}, 'tgts' => $tgts }; +} + +sub scriptsig { + my($self, $tgt) = @_; + $self->{builder}->scriptsig($tgt); +} + +sub includes { + my($self, $tgt) = @_; + $self->{builder}->includes($tgt); +} + +sub compatible { + my($self, $tgt) = @_; + $self->{builder}->compatible($tgt); +} + +sub cachin { + my($self, $tgt, $sig) = @_; + $self->{builder}->cachin($tgt, $sig); +} + +sub cachout { + my($self, $tgt, $sig) = @_; + $self->{builder}->cachout($tgt, $sig); +} + +sub action { + my($self, $invoked_tgt) = @_; + return $self->{built} if exists $self->{built}; + + # Make sure all targets in the group are unlinked before building any. + my($tgts) = $self->{tgts}; + my $tgt; + for $tgt (@$tgts) { + futil::mkdir($tgt->{dir}); + unlink($tgt->path) if ! $tgt->precious; + } + + # Now do the action to build all the targets. For consistency + # we always call the action on the first target, just so that + # $> is deterministic. + $self->{built} = $self->{builder}->action($tgts->[0]); + + # Now "build" all the other targets (except for the one + # we were called with). This guarantees that the signature + # of each target is updated appropriately. We force the + # targets to be built even if they have been previously + # considered and found to be OK; the only effect this + # has is to make sure that signature files are updated + # correctly. + for $tgt (@$tgts) { + if ($tgt ne $invoked_tgt) { + delete $tgt->{status}; + 'sig'->invalidate($tgt); + build $tgt; + } + } + + # Status of action. + $self->{built}; +} + +package action; + +sub new { + my($env, $act) = @_; + if (ref($act) eq 'CODE') { + return action::perl->new($act); + } else { + return action::command->new($env, $act); + } +} + +package action::command; + +use vars qw( @ISA %cmd %_varopts $_varletters ); + +BEGIN { + @ISA = $main::_WIN32 ? 'action::command::win32' : 'action::command::unix'; + + # Internal hash for processing variable options. + # f: return file part + # d: return directory part + # F: return file part, but strip any suffix + # b: return full path, but strip any suffix (a.k.a. return basename) + # s: return only the suffix (or an empty string, if no suffix is there) + # a: return the absolute path to the file + # S: return the absolute path to a Linked source file + %_varopts = ( + 'f' => sub { return $_[0]->{entry}; }, + 'd' => sub { return $_[0]->{dir}->path; }, + 'F' => sub { my $subst = $_[0]->{entry}; + $subst =~ s/\.[^\.]+$//; + return $subst; }, + 'b' => sub { my $subst = $_[0]->path; + $subst =~ s/\.[^\.]+$//; + return $subst; }, + 's' => sub { my $subst = $_[0]->{entry}; + $subst =~ m/(\.[^\.]+)$/; + return $1; }, + 'a' => sub { my $path = $_[0]->path; + if (! File::Spec->file_name_is_absolute($path)) { + $path = File::Spec->catfile(Cwd::cwd(), $path); + } + return $path; }, + 'S' => sub { my $path = $_[0]->srcpath; + if (! File::Spec->file_name_is_absolute($path)) { + my $cwd = File::Spec->canonpath(Cwd::cwd()); + $path = File::Spec->catfile($cwd, $path); + } + return $path; }, + ); + + $_varletters = join('', keys %_varopts); +} + +# Internal routine for processing variable options. +# Options are specified in hash in the BEGIN block above. +# no option: return path to file (relative to top, +# or absolute if it's outside) +sub _variant { + my($opt, $file) = @_; + $opt = '' if ! defined $opt; + if (defined $_varopts{$opt}) { + return &{$_varopts{$opt}}($file); + } + return $file->path; +} + +sub new { + my($class, $env, $cmd) = @_; + $cmd = $env->_subst($cmd); + $cmd{$env,$cmd} || do { + # Remove unwanted bits from signature -- those bracketed by %( ... %) + my $sigs = $cmd; + my $sig = ''; + if (ref($sigs) eq 'ARRAY') { + # This is an array of commands.. + my $f; + foreach $f (@$sigs) { + $sig .= _strip($f); + } + } else { + $sig = _strip($sigs); + } + my $self = { cmd => $cmd, cmdsig => 'sig'->cmdsig($sig) }; + $cmd{$env,$cmd} = bless $self, $class; + } +} + +sub _strip { + my $sig = shift; + $sig =~ s/^\@\s*//mg; + while ($sig =~ s/%\(([^%]|%[^\(])*?%\)//g) { } + $sig; +} + +sub scriptsig { + $_[0]->{cmdsig}; +} + +# Return an array of all the commands (first word on each line). +sub commands { + my($self) = @_; + my(@cmds) = (); + my $com; + my $cmd = $self->{'cmd'}; + my @allcoms; + + push @allcoms, ref $cmd ? @{$cmd} : split(/\n/, $cmd); + + for $com (@allcoms) { + $com =~ s/^\s*//; + $com =~ s/\s.*//; + next if ! $com; # blank line + push @cmds, $com; + } + @cmds; +} + +# For the signature of a basic command, we don't bother +# including the command itself. This is not strictly correct, +# and if we wanted to be rigorous, we might want to insist +# that the command was checked for all the basic commands +# like gcc, etc. For this reason we don't have an includes +# method. + +# Call this to get the command line script: an array of +# fully substituted commands. +sub getcoms { + my($self, $env, $tgt) = @_; + my(@coms); + my $com; + my @allcoms = (); + my $cmd = $self->{'cmd'}; + + push @allcoms, ref $cmd ? @{$cmd} : split(/\n/, $cmd); + + for $com (@allcoms) { + my(@src) = (undef, @{$tgt->{sources}}); + my(@src1) = @src; + + next if $com =~ /^\s*$/; + + # NOTE: we used to have a more elegant s//.../e solution + # for the items below, but this caused a bus error... + + # Remove %( and %) -- those are only used to bracket parts + # of the command that we don't depend on. + $com =~ s/%[()]//g; + + # Deal with %n, n=1,9 and variants. + while ($com =~ /%([1-9])(:([$_varletters]?))?/o) { + my($match) = $&; + my($src) = $src1[$1]; + my($subst) = _variant($3, $src1[$1]->rfile); + undef $src[$1]; + $com =~ s/$match/$subst/; + } + + # Deal with %0 aka %> and variants. + while ($com =~ /%[0>](:([$_varletters]?))?/o) { + my($match) = $&; + my($subst) = _variant($2, $tgt); + $com =~ s/$match/$subst/; + } + + # Deal with %< (all sources except %n's already used) + while ($com =~ /%<(:([$_varletters]?))?/o) { + my($match) = $&; + my @list = (); + foreach (@src) { + push(@list, _variant($2, $_->rfile)) if $_; + } + my($subst) = join(' ', @list); + $com =~ s/$match/$subst/; + } + + # Deal with %[ %]. + $com =~ s{%\[(.*?)%\]}{ + my($func, @args) = grep { $_ ne '' } split(/\s+/, $1); + die("$0: \"$func\" is not defined.\n") + unless ($env->{$func}); + &{$env->{$func}}(@args); + }gex; + + # Convert left-over %% into %. + $com =~ s/%%/%/g; + + # White space cleanup. XXX NO WAY FOR USER TO HAVE QUOTED SPACES + $com = join(' ', split(' ', $com)); + next if $com =~ /^:/ && $com !~ /^:\S/; + push(@coms, $com); + } + @coms +} + +# Build the target using the previously specified commands. +sub execute { + my($self, $env, $tgt, $package) = @_; + + if ($param::build) { + futil::mkdir($tgt->{dir}); + unlink($tgt->path) if ! $tgt->precious; + } + + # Set environment. + map(delete $ENV{$_}, keys %ENV); + %ENV = %{$env->{ENV}}; + + # Handle multi-line commands. + my $com; + for $com ($self->getcoms($env, $tgt)) { + if ($com !~ s/^\@\s*//) { + main::showcom($com); + } + next if ! $param::build; + + if ($com =~ /^\[perl\]\s*/) { + my $perlcmd = $'; + my $status; + { + # Restore the script package variables that were defined + # in the Conscript file that defined this [perl] build, + # so the code executes with the expected variables. + # Then actually execute (eval) the [perl] command to build + # the target, followed by cleaning up the name space + # by deleting the package variables we just restored. + my($pkgvars) = $tgt->{conscript}->{pkgvars}; + NameSpace::restore($package, $pkgvars) if $pkgvars; + $status = eval "package $package; $perlcmd"; + NameSpace::remove($package, keys %$pkgvars) if $pkgvars; + } + if (!defined($status)) { + warn "$0: *** Error during perl command eval: $@.\n"; + return undef; + } elsif ($status == 0) { + warn "$0: *** Perl command returned $status " + . "(this indicates an error).\n"; + return undef; + } + next; + } + if (! $self->do_command($com, $tgt->path)) { + return undef; + } + } + + # success. + return 1; +} + +sub show { + my($self, $env, $tgt) = @_; + my $com; + for $com ($self->getcoms($env, $tgt)) { + if ($com !~ /^\@\s*/) { + main::showcom($com); + } + } +} + +package action::command::unix; + +sub do_command { + my($class, $com, $path) = @_; + my($pid) = fork(); + die("$0: unable to fork child process ($!)\n") if !defined $pid; + if (!$pid) { + # This is the child. We eval the command to suppress -w + # warnings about not reaching the statements afterwards. + eval 'exec($com)'; + $com =~ s/\s.*//; + die qq($0: failed to execute "$com" ($!). ) + . qq(Is this an executable on path "$ENV{PATH}"?\n); + } + for (;;) { + do {} until wait() == $pid; + my ($b0, $b1) = ($? & 0xFF, $? >> 8); + # Don't actually see 0177 on stopped process; is this necessary? + next if $b0 == 0177; # process stopped; we can wait. + if ($b0) { + my($core, $sig) = ($b0 & 0200, $b0 & 0177); + my($coremsg) = $core ? "; core dumped" : ""; + $com =~ s/\s.*//; + my $err = "$0: *** \[$path\] $com terminated by signal " . + "$sig$coremsg\n"; + warn $err; + return undef; + } + if ($b1) { + warn qq($0: *** [$path] Error $b1\n); # trying to be like make. + return undef; + } + last; + } + return 1; +} + +package action::command::win32; + +sub do_command { + my($class, $com, $path) = @_; + system($com); + if ($?) { + my ($b0, $b1) = ($? & 0xFF, $? >> 8); + my $err = $b1 || $?; + my $warn = qq($0: *** [$path] Error $err); + $warn .= " (executable not found in path?)" if $b1 == 0xFF; + warn "$warn\n"; + return undef; + } + return 1; +} + +package action::perl; + +# THIS IS AN EXPERIMENTAL PACKAGE. It's entirely possible that the +# interface may change as this gets completed, so use at your own risk. +# +# There are (at least) two issues that need to be solved before blessing +# this as a real, fully-supported feature: +# +# -- We need to calculate a signature value for a Perl code ref, in +# order to rebuild the target if there's a change to the Perl code +# used to generate it. +# +# This is not straightforward. A B::Deparse package exists that +# decompiles a coderef into text. It's reportedly not completely +# reliable for closures; it misses which variables are global, and +# the values of private lexicals. Nevertheless, it'd probably +# be perfect for our purposes, except that it wasn't added until +# some time between Perl 5.00502 and 5.00554, and doesn't seem to +# really work until Perl 5.6.0, so by relying on it, we'd lose +# support for Perl versions back to 5.003*. +# +# -- Ideally, a code ref should be able to use something like +# $env->_subst to fetch values from the construction environment +# to modify its behavior without having to cut-and-paste code. +# (Actually, since we pass the environment to the executed code +# ref, there's no reason you can't do this with the code as it +# stands today.) But this REALLY complicates the signature +# calculation, because now the actual signature would depend not +# just on the code contents, but on the construction variables (or +# maybe just the environment). +# +# A potentially valid workaround would be to use the contents of the +# Conscript file in which the code reference is defined as the code +# ref's signature. This has the drawback of causing a recompilation of +# the target file even in response to unrelated changes in the Conscript +# file, but it would ensure correct builds without having to solve the +# messy issues of generating a signature directly from a code ref. +# +# Nevertheless, this seemed a useful enough skeleton of a feature that +# it made sense to release it in hopes that some practical experience +# will encourage someone to figure out how to solve the signature +# issues. Or maybe we'll discover these aren't big issues in practice +# and end up blessing it as is. + +use vars qw( %code ); + +sub new { + my($class, $cref) = @_; + $code{$cref} || do { + my $sig = ''; + # Generating a code signature using B::Deparse doesn't really + # work for us until Perl 5.6.0. Here's the code in case + # someone wants to use it. + #use B::Deparse; + #my $deparse = B::Deparse->new(); + #my $body = $deparse->coderef2text($cref); + #$sig = $body; # should be an MD5 sig + my($self) = { cref => $cref, crefsig => $sig }; + $code{$cref} = bless $self, $class; + } +} + +sub scriptsig { + $_[0]->{crefsig} +} + +sub execute { + my($self, $env, $tgt) = @_; + if ($param::build) { + futil::mkdir($tgt->{dir}); + unlink($tgt->path) if ! $tgt->precious; + my($cref) = $self->{cref}; + &$cref($env, $tgt->path, map($_->rpath, @{$tgt->{sources}})); + } +} + +sub commands { + return (); +} + + +# Generic scanning module. +package scan; + +# Returns the signature of files included by the specified files on +# behalf of the associated target. Any errors in handling the included +# files are propagated to the target on whose behalf this processing +# is being done. Signatures are cached for each unique file/scanner +# pair. +sub includes { + my($self, $tgt, @files) = @_; + my(%files, $file); + my($inc) = $self->{includes} || ($self->{includes} = {}); + while ($file = pop @files) { + next if exists $files{$file}; + if ($inc->{$file}) { + push(@files, @{$inc->{$file}}); + $files{$file} = 'sig'->signature($file->rfile); + } else { + if ((build $file) eq 'errors') { + $tgt->{status} = 'errors'; # tgt inherits build status + return (); + } + $files{$file} = 'sig'->signature($file->rfile); + my(@includes) = $self->scan($file); + $inc->{$file} = \@includes; + push(@files, @includes); + } + } + 'sig'->collect(sort values %files) +} + + +# A simple scanner. This is used by the QuickScanfunction, to setup +# one-time target and environment-independent scanning for a source +# file. Only used for commands run by the Command method. +package scan::quickscan; + +use vars qw( @ISA %scanner ); + +BEGIN { @ISA = qw(scan) } + +sub find { + my($class, $code, $env, $pdirs) = @_; + if (! defined $pdirs) { + $pdirs = [ ] ; + } elsif (ref($pdirs) ne 'ARRAY') { + $pdirs = [ split(/$main::PATH_SEPARATOR/o, $pdirs) ]; + } + my(@path) = map { $dir::cwd->lookupdir($_) } @$pdirs; + my($spath) = "@path"; + $scanner{$code,$env,$spath} || do { + my($self) = { code => $code, env => $env, path => \@path }; + $scanner{$code,$env,$spath} = bless $self; + } +} + +# Scan the specified file for included file names. +sub scan { + my($self, $file) = @_; + my($code) = $self->{code}; + my(@includes); + # File should have been built by now. If not, we'll ignore it. + return () unless open(SCAN, $file->rpath); + while() { + push(@includes, grep($_ ne '', &$code)); + } + close(SCAN); + my($wd) = $file->{dir}; + my(@files); + my $name; + for $name (@includes) { + my $dir; + for $dir ($file->{dir}, @{$self->{path}}) { + my($include) = $dir->lookup_accessible($name); + if ($include) { + push(@files, $include) unless $include->ignore; + last; + } + } + } + @files +} + + +# CPP (C preprocessor) scanning module +package scan::cpp; + +use vars qw( @ISA %scanner ); + +BEGIN { @ISA = qw(scan) } + +# For this constructor, provide the include path argument (colon +# separated). Each path is taken relative to the provided directory. + +# Note: a particular scanning object is assumed to always return the +# same result for the same input. This is why the search path is a +# parameter to the constructor for a CPP scanning object. We go to +# some pains to make sure that we return the same scanner object +# for the same path: otherwise we will unecessarily scan files. +sub find { + my($class, $dir, $pdirs) = @_; + if (! defined $pdirs) { + $pdirs = [ ]; + } elsif (ref($pdirs) ne 'ARRAY') { + $pdirs = [ split(/$main::PATH_SEPARATOR/o, $pdirs) ]; + } + my @path = map($dir->lookupdir($_), @$pdirs); + my($spath) = "@path"; + $scanner{$spath} || do { + my($self) = {'path' => \@path}; + $scanner{$spath} = bless $self; + } +} + +# Scan the specified file for include lines. +sub scan { + my($self, $file) = @_; + my($angles, $quotes); + + if (exists $file->{angles}) { + $angles = $file->{angles}; + $quotes = $file->{quotes}; + } else { + my(@anglenames, @quotenames); + return () unless open(SCAN, $file->rpath); + while () { + next unless /^\s*#/; + if (/^\s*#\s*include\s*([<"])(.*?)[>"]/) { + if ($1 eq "<") { + push(@anglenames, $2); + } else { + push(@quotenames, $2); + } + } + } + close(SCAN); + $angles = $file->{angles} = \@anglenames; + $quotes = $file->{quotes} = \@quotenames; + } + + + my(@shortpath) = @{$self->{path}}; # path for <> style includes + my(@longpath) = ($file->{dir}, @shortpath); # path for "" style includes + + my(@includes); + + my $name; + for $name (@$angles) { + my $dir; + for $dir (@shortpath) { + my($include) = $dir->lookup_accessible($name); + if ($include) { + push(@includes, $include) unless $include->ignore; + last; + } + } + } + + for $name (@$quotes) { + my $dir; + for $dir(@longpath) { + my($include) = $dir->lookup_accessible($name); + if ($include) { + push(@includes, $include) unless $include->ignore; + last; + } + } + } + + return @includes +} + +# Return the include flags that would be used for a C Compile. +sub iflags { + my($self, $env) = @_; + my($iflags) = ''; + my($dir, $dpath); + for $dir (@{$self->{path}}) { + $dpath = $dir->path; + # Add the (presumably local) directory to the -I flags + # if we're not using repositories, the directory exists, + # or it's Linked to a source directory (that is, it *will* + # exist by the time the compilation occurs). + $iflags .= " ".$env->{INCDIRPREFIX}.$dpath.$env->{INCDIRSUFFIX} + if ! @param::rpath || -d $dpath || $dir->is_linked; + next if File::Spec->file_name_is_absolute($dpath); + if (@param::rpath) { + my $d; + if ($dpath eq $dir::CURDIR) { + foreach $d (map($_->path, @param::rpath)) { + $iflags .= " ".$env->{INCDIRPREFIX}.$d.$env->{INCDIRSUFFIX}; + } + } else { + my($rpath); + foreach $d (map($_->path, @param::rpath)) { + $rpath = File::Spec->catfile($d, $dpath); + $iflags .= " ".$env->{INCDIRPREFIX}.$rpath.$env->{INCDIRSUFFIX} + if -d $rpath; + } + } + } + } + $iflags +} + +package File::Spec; + +use vars qw( $_SEP $_MATCH_SEP $_MATCH_VOL ); + +# Cons is migrating to using File::Spec for portable path name +# manipulation. This is the right long-term direction, but there are +# some problems with making the transition: +# +# For multi-volume support, we need to use newer interfaces +# (splitpath, catpath, splitdir) that are only available in +# File::Spec 0.8. +# +# File::Spec 0.8 doesn't work with Perl 5.00[34] due to +# regular expression incompatibilities (use of \z). +# +# Forcing people to use a new version of a module is painful +# because (in the workplace) their administrators aren't +# always going to agree to install it everywhere. +# +# As a middle ground, we provide our own versions of all the File::Spec +# methods we use, supporting both UNIX and Win32. Some of these methods +# are home brew, some are cut-and-pasted from the real File::Spec methods. +# This way, we're not reinventing the whole wheel, at least. +# +# We can (and should) get rid of this class whenever 5.00[34] and +# versions of File::Spec prior to 0.9 (?) have faded sufficiently. +# We also may need to revisit whenever someone first wants to use +# Cons on some platform other than UNIX or Win32. + +BEGIN { + if ($main::_WIN32) { + $_SEP = '\\'; + $_MATCH_SEP = "[\Q/$_SEP\E]"; + $_MATCH_VOL = "([a-z]:)?$_MATCH_SEP"; + } else { + $_SEP = '/'; + $_MATCH_SEP = "\Q$_SEP\E"; + $_MATCH_VOL = $_MATCH_SEP; + } +} + +sub canonpath { + my ($self, $path) = @_; + if ($main::_WIN32) { + $path =~ s/^([a-z]:)/\u$1/s; + $path =~ s|/|\\|g; + $path =~ s|([^\\])\\+|$1\\|g; # xx////xx -> xx/xx + $path =~ s|(\\\.)+\\|\\|g; # xx/././xx -> xx/xx + $path =~ s|^(\.\\)+||s unless $path eq ".\\"; # ./xx -> xx + $path =~ s|\\$|| + unless $path =~ m#^([A-Z]:)?\\$#s; # xx/ -> xx + } else { + $path =~ s|/+|/|g unless($^O eq 'cygwin'); # xx////xx -> xx/xx + $path =~ s|(/\.)+/|/|g; # xx/././xx -> xx/xx + $path =~ s|^(\./)+||s unless $path eq "./"; # ./xx -> xx + $path =~ s|^/(\.\./)+|/|s; # /../../xx -> xx + $path =~ s|/$|| unless $path eq "/"; # xx/ -> xx + } + return $path; +} + +sub catdir { + my $self = shift; + my @args = @_; + foreach (@args) { + # append a slash to each argument unless it has one there + $_ .= $_SEP if $_ eq '' || substr($_,-1) ne $_SEP; + } + return $self->canonpath(join('', @args)); +} + +sub catfile { + my $self = shift; + my $file = pop @_; + return $file unless @_; + my $dir = $self->catdir(@_); + $dir .= $_SEP unless substr($dir,-1) eq $_SEP; + $file = '' if ! defined($file); + return $dir.$file; +} + +sub catpath { + my $path = $_[1] . $_[0]->catfile(@_[2..$#_]); + $path =~ s/(.)$_MATCH_SEP*$/$1/; + $path; +} + +sub curdir { + '.' +} + +sub file_name_is_absolute { + my ($self, $file) = @_; + return scalar($file =~ m{^$_MATCH_VOL}is); +} + +sub splitdir { + my @dirs = split(/$_MATCH_SEP/, $_[1], -1); + push(@dirs, '') if $dirs[$#dirs]; + @dirs; +} + +sub splitpath { + my ($self, $path) = @_; + my $vol = ''; + my $sep = $_SEP; + if ($main::_WIN32) { + if ($path =~ s#^([A-Za-z]:|(?:\\\\|//)[^\\/]+[\\/][^\\/]+)([\\/])#$2#) { + $vol = $1; + $sep = $2; + } + } + my(@path) = split(/$_MATCH_SEP/, $path, -1); + my $file = pop @path; + my $dirs = join($sep, @path, ''); + return ($vol, $dirs, $file); +} + +sub updir { + '..' +} + +sub case_tolerant { + return $main::_WIN32; +} + +# Directory and file handling. Files/dirs are represented by objects. +# Other packages are welcome to add component-specific attributes. +package dir; + +use vars qw( $SEPARATOR $MATCH_SEPARATOR $CURDIR $UPDIR + $cwd_vol %root $top $cwd ); + +BEGIN { + # A portable way of determing our directory separator. + $SEPARATOR = File::Spec->catdir('', ''); + # A fast-path regular expression to match a directory separator + # anywhere in a path name. + if ($SEPARATOR eq '/') { + $MATCH_SEPARATOR = "\Q$SEPARATOR\E"; + } else { + $MATCH_SEPARATOR = "[\Q/$SEPARATOR\E]"; + } + # Cache these values so we don't have to make a method call + # every time we need them. + $CURDIR = File::Spec->curdir; # '.' on UNIX + $UPDIR = File::Spec->updir; # '..' on UNIX + # + $cwd_vol = ''; +} + +# Annotate a node (file or directory) with info about the +# method that created it. +sub creator { + my($self, @frame) = @_; + $self->{'creator'} = \@frame if @frame; + $self->{'creator'}; +} + +# Handle a file|dir type exception. We only die if we find we were +# invoked by something in a Conscript/Construct file, because +# dependencies created directly by Cons' analysis shouldn't cause +# an error. +sub _type_exception { + my($e) = @_; + my($line, $sub); + (undef, undef, $line, $sub) = script::caller_info; + if (defined $line) { + my $err = "\"${\$e->path}\" already in use as a " . ref($e) . " before $sub on line $line"; + if ($e->{'creator'}) { + my $script; + (undef, $script, $line, $sub) = @{$e->{'creator'}}; + $err = "\t" . $err . ",\n\t\tdefined by $sub in $script, line $line"; + } + $err .= "\n"; + die $err; + } +} + +# This wraps up all the common File::Spec logic that we use for parsing +# directory separators in a path and turning it into individual +# subdirectories that we must create, as well as creation of root +# nodes for any new file system volumes we find. File::Spec doesn't have +# intuitively obvious interfaces, so this is heavily commented. +# +# Note: This is NOT an object or class method; +# it's just a utility subroutine. +sub _parse_path { + my($dir, $path) = @_; + + # Convert all slashes to the native directory separator. + # This allows Construct files to always be written with good + # old POSIX path names, regardless of what we're running on. + $path = File::Spec->canonpath($path); + + # File::Spec doesn't understand the Cons convention of + # an initial '#' for top-relative files. Strip it. + my($toprel) = $path =~ s/^#//; + + # Let File::Spec do the heavy lifting of parsing the path name. + my($vol, $directories, $entry) = File::Spec->splitpath($path); + my @dirs = File::Spec->splitdir($directories); + + # If there was a file entry on the end of the path, then the + # last @dirs element is '' and we don't need it. If there + # wasn't a file entry on the end (File::Spec->splitpath() knew + # the last component was a directory), then the last @dirs + # element becomes the entry we want to look up. + my($e) = pop @dirs; + $entry = $e if $entry eq ''; + + if (File::Spec->file_name_is_absolute($path)) { + # An absolute path name. If no volume was supplied, + # use the volume of our current directory. + $vol = $cwd_vol if $vol eq ''; + $vol = uc($vol) if File::Spec->case_tolerant; + if (! defined $root{$vol}) { + # This is our first time looking up a path name + # on this volume, so create a root node for it. + # (On UNIX systems, $vol is always '', so '/' + # always maps to the $root{''} node.) + $root{$vol} = {path => $vol.$SEPARATOR, + prefix => $vol.$SEPARATOR, + srcpath => $vol.$SEPARATOR, + 'exists' => 1 }; + $root{$vol}->{'srcdir'} = $root{$vol}; + bless $root{$vol}; + } + # We're at the top, so strip the blank entry from the front of + # the @dirs array since the initial '/' it represents will now + # be supplied by the root node we return. + shift @dirs; + $dir = $root{$vol}; + } elsif ($toprel) { + $dir = $dir::top; + } + ($dir, \@dirs, $entry); +} + +# Common subroutine for creating directory nodes. +sub _create_dirs { + my ($dir, @dirs) = @_; + my $e; + foreach $e (@dirs) { + my $d = $dir->{member}->{$e}; + if (! defined $d) { + bless $d = { 'entry' => $e, 'dir' => $dir, }, 'dir'; + $d->creator(script::caller_info); + $d->{member}->{$dir::CURDIR} = $d; + $d->{member}->{$dir::UPDIR} = $dir; + $dir->{member}->{$e} = $d; + } elsif (ref $d eq 'entry') { + bless $d, 'dir'; + $d->{member}->{$dir::CURDIR} = $d; + $d->{member}->{$dir::UPDIR} = $dir; + } elsif (ref $d eq 'file') { + # This clause is to supply backwards compatibility, + # with a warning, for anyone that's used FilePath + # to refer to a directory. After people have using + # 1.8 have had time to adjust (sometime in version + # 1.9 or later), we should remove this entire clause. + my($script, $line, $sub); + (undef, $script, $line, $sub) = @{$d->{'creator'}}; + if ($sub eq 'script::FilePath') { + print STDERR "$0: Warning: $sub used to refer to a directory\n" + . "\tat line $line of $script. Use DirPath instead.\n"; + bless $d, 'dir'; + } else { + _type_exception($d); + } + } elsif (ref $d ne 'dir') { + _type_exception($d); + } + $dir = $d; + } + $dir; +} + +# Look up an entry in a directory. This method is for when we don't +# care whether a file or directory is returned, so if the entry already +# exists, it will simply be returned. If not, we create it as a +# generic "entry" which can be later turned into a file or directory +# by a more-specific lookup. +# +# The file entry may be specified as relative, absolute (starts with /), +# or top-relative (starts with #). +sub lookup { + my($dir, $entry) = @_; + + if ($entry !~ m#$MATCH_SEPARATOR#o) { + # Fast path: simple entry name in a known directory. + if ($entry =~ s/^#//) { + # Top-relative names begin with #. + $dir = $dir::top; + } elsif ($entry =~ s/^!//) { + $dir = $dir::cwd->srcdir; + } + } else { + my $dirsref; + ($dir, $dirsref, $entry) = _parse_path($dir, $entry); + $dir = _create_dirs($dir, @$dirsref) if @$dirsref; + return if ! defined $dir; + return $dir if $entry eq ''; + } + + my $e = $dir->{member}->{$entry}; + if (! defined $e) { + bless $e = { 'entry' => $entry, 'dir' => $dir, }, 'entry'; + $e->creator(script::caller_info); + $dir->{member}->{$entry} = $e; + } + + $e; +} + +# Look up a file entry in a directory. +# +# The file entry may be specified as relative, absolute (starts with /), +# or top-relative (starts with #). +sub lookupfile { + my($dir, $entry) = @_; + + if ($entry !~ m#$MATCH_SEPARATOR#o) { + # Fast path: simple entry name in a known directory. + if ($entry =~ s/^#//) { + # Top-relative names begin with #. + $dir = $dir::top; + } elsif ($entry =~ s/^!//) { + $dir = $dir::cwd->srcdir; + } + } else { + my $dirsref; + ($dir, $dirsref, $entry) = _parse_path($dir, $entry); + $dir = _create_dirs($dir, @$dirsref) if @$dirsref; + return undef if $entry eq ''; + } + + my $f = $dir->{member}->{$entry}; + if (! defined $f) { + bless $f = { 'entry' => $entry, 'dir' => $dir, }, 'file'; + $f->creator(script::caller_info); + $dir->{member}->{$entry} = $f; + } elsif (ref $f eq 'entry') { + bless $f, 'file'; + } elsif (ref $f ne 'file') { + _type_exception($f); + } + + $f; +} + +# Look up a (sub-)directory entry in a directory. +# +# The (sub-)directory entry may be specified as relative, absolute +# (starts with /), or top-relative (starts with #). +sub lookupdir { + my($dir, $entry) = @_; + + my $dirsref; + if ($entry !~ m#$MATCH_SEPARATOR#o) { + # Fast path: simple entry name in a known directory. + if ($entry =~ s/^#//) { + # Top-relative names begin with #. + $dir = $dir::top; + } elsif ($entry =~ s/^!//) { + $dir = $dir::cwd->srcdir; + } + } else { + ($dir, $dirsref, $entry) = _parse_path($dir, $entry); + } + push(@$dirsref, $entry) if $entry ne ''; + _create_dirs($dir, @$dirsref); +} + +# Look up a file entry and return it if it's accessible. +sub lookup_accessible { + my $file = $_[0]->lookupfile($_[1]); + return ($file && $file->accessible) ? $file : undef; +} + +# Return the parent directory without doing a lookupdir, +# which would create a parent if it doesn't already exist. +# A return value of undef (! $dir->up) indicates a root directory. +sub up { + $_[0]->{member}->{$dir::UPDIR}; +} + +# Return whether this is an entry somewhere underneath the +# specified directory. +sub is_under { + my $dir = $_[0]; + while ($dir) { + return 1 if $_[1] == $dir; + $dir = $dir->up; + } + return undef; +} + +# Return the relative path from the calling directory ($_[1]) +# to the object. If the object is not under the directory, then +# we return it as a top-relative or absolute path name. +sub relpath { + my ($dir, $obj) = @_; + my @dirs; + my $o = $obj; + while ($o) { + if ($dir == $o) { + if (@dirs < 2) { + return $dirs[0] || ''; + } else { + return File::Spec->catdir(@dirs); + } + } + unshift(@dirs, $o->{entry}); + $o = $o->up; + } + # The object was not underneath the specified directory. + # Use the node's cached path, which is either top-relative + # (in which case we append '#' to the beginning) or + # absolute. + my $p = $obj->path; + $p = '#' . $p if ! File::Spec->file_name_is_absolute($p); + return $p; +} + +# Return the path of the directory (file paths implemented +# separately, below). +sub path { + $_[0]->{path} || + ($_[0]->{path} = $_[0]->{dir}->prefix . $_[0]->{entry}); +} + +# Return the pathname as a prefix to be concatenated with an entry. +sub prefix { + return $_[0]->{prefix} if exists $_[0]->{prefix}; + $_[0]->{prefix} = $_[0]->path . $SEPARATOR; +} + +# Return the related source path prefix. +sub srcprefix { + return $_[0]->{srcprefix} if exists $_[0]->{srcprefix}; + my($srcdir) = $_[0]->srcdir; + $srcdir->{srcprefix} = $srcdir eq $_[0] ? $srcdir->prefix + : $srcdir->srcprefix; +} + +# Return the related source directory. +sub srcdir { + $_[0]->{'srcdir'} || + ($_[0]->{'srcdir'} = $_[0]->{dir}->srcdir->lookupdir($_[0]->{entry})) +} + +# Return if the directory is linked to a separate source directory. +sub is_linked { + return $_[0]->{is_linked} if defined $_[0]->{is_linked}; + $_[0]->{is_linked} = $_[0]->path ne $_[0]->srcdir->path; +} + +sub link { + my(@paths) = @_; + my($srcdir) = $dir::cwd->lookupdir(pop @paths)->srcdir; + map($dir::cwd->lookupdir($_)->{'srcdir'} = $srcdir, @paths); + + # make a reverse lookup for the link. + $srcdir->{links} = [] if ! $srcdir->{links}; + push @{$srcdir->{links}}, @paths; +} + +use vars qw( @tail ); # TODO: Why global ???? + +sub linked_targets { + my $tgt = shift; + my @targets = (); + my $dir; + if (ref $tgt eq 'dir') { + $dir = $tgt; + } else { + push @tail, $tgt; + $dir = $tgt->{dir}; + } + while ($dir) { + if (defined $dir->{links} && @{$dir->{links}}) { + push @targets, + map(File::Spec->catdir($_, @tail), @{$dir->{links}}); + #print STDERR "Found Link: ${\$dir->path} -> @{\$dir->{links}}\n"; + } + unshift @tail, $dir->{entry}; + $dir = $dir->up; + } + + return map($dir::top->lookupdir($_), @targets); +} + +sub accessible { + my $path = $_[0]->path; + my $err = "$0: you have attempted to use path \"$path\" both as a file " . + "and as a directory!\n"; + die $err; +} + +sub init { + my $path = Cwd::cwd(); + + # We know we can get away with passing undef to lookupdir + # as the directory because $dir is an absolute path. + $top = lookupdir(undef, $path); + $top->{'path'} = $top->{srcpath} = $dir::CURDIR; + $top->{'prefix'} = ''; + $top->{'srcdir'} = $top; + + $cwd = $top; + + ($cwd_vol, undef, undef) = File::Spec->splitpath($path); + $cwd_vol = '' if ! defined $cwd_vol; + $cwd_vol = uc($cwd_vol) if File::Spec->case_tolerant; +} + +package file; + +use vars qw( @ISA $level ); + +BEGIN { @ISA = qw(dir); $level = 0 } + +# Return the pathname of the file. +# Define this separately from dir::path because we don't want to +# cache all file pathnames (just directory pathnames). +sub path { + $_[0]->{dir}->prefix . $_[0]->{entry} +} + +# Return the related source file path. +sub srcpath { + $_[0]->{dir}->srcprefix . $_[0]->{entry} +} + +# Return if the file is (should be) linked to a separate source file. +sub is_linked { + $_[0]->{dir}->is_linked +} + +# Repository file search. If the local file exists, that wins. +# Otherwise, return the first existing same-named file under a +# Repository directory. If there isn't anything with the same name +# under a Repository directory, return the local file name anyway +# so that some higher layer can try to construct it. +sub rfile { + return $_[0]->{rfile} if exists $_[0]->{rfile}; + my($self) = @_; + my($rfile) = $self; + if (@param::rpath) { + my($path) = $self->path; + if (! File::Spec->file_name_is_absolute($path) && ! -f $path) { + my($dir); + foreach $dir (@param::rpath) { + my($t) = $dir->prefix . $path; + if (-f $t) { + $rfile = $_[0]->lookupfile($t); + $rfile->{'lfile'} = $self; + last; + } + } + } + } + $self->{rfile} = $rfile; +} + +# Returns the local file for a repository file; +# returns self if it's already a local file. +sub lfile { + $_[0]->{'lfile'} || $_[0] +} + +# returns the "precious" status of this file. +sub precious { + return $_[0]->{precious}; +} + +# "Erase" reference to a Repository file, +# making this a completely local file object +# by pointing it back to itself. +sub no_rfile { + $_[0]->{'rfile'} = $_[0]; +} + +# Return a path to the first existing file under a Repository directory, +# implicitly returning the current file's path if there isn't a +# same-named file under a Repository directory. +sub rpath { + $_[0]->{rpath} || + ($_[0]->{rpath} = $_[0]->rfile->path) +} + +# Return a path to the first linked srcpath file under a Repositoy +# directory, implicitly returning the current file's srcpath if there +# isn't a same-named file under a Repository directory. +sub rsrcpath { + return $_[0]->{rsrcpath} if exists $_[0]->{rsrcpath}; + my($self) = @_; + my($path) = $self->{rsrcpath} = $self->srcpath; + if (@param::rpath && ! File::Spec->file_name_is_absolute($path) && ! -f $path) { + my($dir); + foreach $dir (@param::rpath) { + my($t) = $dir->prefix . $path; + if (-f $t) { + $self->{rsrcpath} = $t; + last; + } + } + } + $self->{rsrcpath}; +} + +# Return if a same-named file source file exists. +# This handles the interaction of Link and Repository logic. +# As a side effect, it will link a source file from its Linked +# directory (preferably local, but maybe in a repository) +# into a build directory from its proper Linked directory. +sub source_exists { + return $_[0]->{source_exists} if defined $_[0]->{source_exists}; + my($self) = @_; + my($path) = $self->path; + my($mtime, $ctime) = (stat($path))[9,10]; + if ($self->is_linked) { + # Linked directory, local logic. + my($srcpath) = $self->srcpath; + my($src_mtime, $src_ctime) = (stat($srcpath))[9,10]; + if ($src_mtime) { + if (! $mtime || $src_mtime != $mtime || $src_ctime != $ctime) { + futil::install($srcpath, $self); + } + return $self->{source_exists} = 1; + } + # Linked directory, repository logic. + if (@param::rpath) { + if ($self != $self->rfile) { + return $self->{source_exists} = 1; + } + my($rsrcpath) = $self->rsrcpath; + if ($path ne $rsrcpath) { + my($rsrc_mtime, $rsrc_ctime) = (stat($rsrcpath))[9,10]; + if ($rsrc_mtime) { + if (! $mtime || $rsrc_mtime != $mtime + || $rsrc_ctime != $ctime) { + futil::install($rsrcpath, $self); + } + return $self->{source_exists} = 1; + } + } + } + # There was no source file in any Linked directory + # under any Repository. If there's one in the local + # build directory, it no longer belongs there. + if ($mtime) { + unlink($path) || die("$0: couldn't unlink $path ($!)\n"); + } + return $self->{source_exists} = ''; + } else { + if ($mtime) { + return $self->{source_exists} = 1; + } + if (@param::rpath && $self != $self->rfile) { + return $self->{source_exists} = 1; + } + return $self->{source_exists} = ''; + } +} + +# Return if a same-named derived file exists under a Repository directory. +sub derived_exists { + $_[0]->{derived_exists} || + ($_[0]->{derived_exists} = ($_[0] != $_[0]->rfile)); +} + +# Return if this file is somewhere under a Repository directory. +sub is_on_rpath { + defined $_[0]->{'lfile'}; +} + +sub local { + my($self, $arg) = @_; + if (defined $arg) { + $self->{'local'} = $arg; + } + $self->{'local'}; +} + +# Return the entry name of the specified file with the specified +# suffix appended. Leave it untouched if the suffix is already there. +# Differs from the addsuffix function, below, in that this strips +# the existing suffix (if any) before appending the desired one. +sub base_suf { + my($entry) = $_[0]->{entry}; + if ($entry !~ m/$_[1]$/) { + $entry =~ s/\.[^\.]*$//; + $entry .= $_[1]; + } + $entry; +} + +# Return the suffix of the file; everything including and to the +# right of the last dot. +sub suffix { + my @pieces = split(/\./, $_[0]->{entry}); + my $suffix = pop(@pieces); + return ".$suffix"; +} + +# Called as a simple function file::addsuffix(name, suffix) +sub addsuffix { + my($name, $suffix) = @_; + + if ($suffix && substr($name, -length($suffix)) ne $suffix) { + return $name .= $suffix; + } + $name; +} + +# Return true if the file is (or will be) accessible. +# That is, if we can build it, or if it is already present. +sub accessible { + (exists $_[0]->{builder}) || ($_[0]->source_exists); +} + +# Return true if the file should be ignored for the purpose +# of computing dependency information (should not be considered +# as a dependency and, further, should not be scanned for +# dependencies). +sub ignore { + return 0 if !$param::ignore; + return $_[0]->{ignore} if exists $_[0]->{ignore}; + $_[0]->{ignore} = $_[0]->path =~ /$param::ignore/o; +} + +# Build the file, if necessary. +sub build { + return $_[0]->{status} if $_[0]->{status}; + my($status) = &file::_build; + if ($_[0]->{after_build_func}) { + my($pkgvars) = $_[0]->{conscript}->{pkgvars}; + NameSpace::restore('script', $pkgvars) if $pkgvars; + eval("package script; " . $_[0]->{after_build_func}); + print "Error running AfterBuild for ${\$_[0]->path}: $@\n" if ($@); + NameSpace::remove('script', keys %$pkgvars) if $pkgvars; + } + return $status; +} + +sub _build { + my($self) = @_; + print main::DEPFILE $self->path, "\n" if $param::depfile; + print((' ' x $level), "Checking ", $self->path, "\n") if $param::depends; + if (!exists $self->{builder}) { + # We don't know how to build the file. This is OK, if + # the file is present as a source file, under either the + # local tree or a Repository. + if ($self->source_exists) { + return $self->{status} = 'handled'; + } else { + my($name) = $self->path; + print("$0: don't know how to construct \"$name\"\n"); + exit(1) unless $param::kflag; + return $self->{status} = 'errors'; # xxx used to be 'unknown' + } + } + + # An associated build object exists, so we know how to build + # the file. We first compute the signature of the file, based + # on its dependendencies, then only rebuild the file if the + # signature has changed. + my($builder) = $self->{builder}; + $level += 2; + + my(@deps) = (@{$self->{dep}}, @{$self->{sources}}); + my($rdeps) = \@deps; + + if ($param::random) { + # If requested, build in a random order, instead of the + # order that the dependencies were listed. + my(%rdeps); + map { $rdeps{$_,'*' x int(rand 10)} = $_ } @deps; + $rdeps = [values(%rdeps)]; + } + + $self->{status} = ''; + + my $dep; + for $dep (@$rdeps) { + if ((build $dep) eq 'errors') { + # Propagate dependent errors to target. + # but try to build all dependents regardless of errors. + $self->{status} = 'errors'; + } + } + + # If any dependents had errors, then we abort. + if ($self->{status} eq 'errors') { + $level -= 2; + return 'errors'; + } + + # Compute the final signature of the file, based on + # the static dependencies (in order), dynamic dependencies, + # output path name, and (non-substituted) build script. + my($sig) = 'sig'->collect(map('sig'->signature($_->rfile), @deps), + $builder->includes($self), + $builder->scriptsig); + + # May have gotten errors during computation of dynamic + # dependency signature, above. + $level -= 2; + return 'errors' if $self->{status} eq 'errors'; + + if (@param::rpath && $self->derived_exists) { + # There is no local file of this name, but there is one + # under a Repository directory. + + if ('sig'->current($self->rfile, $sig)) { + # The Repository copy is current (its signature matches + # our calculated signature). + if ($self->local) { + # ...but they want a local copy, so provide it. + main::showcom("Local copy of ${\$self->path} from " . + "${\$self->rpath}"); + futil::install($self->rpath, $self); + 'sig'->bsig($self, $sig); + } + return $self->{status} = 'handled'; + } + + # The signatures don't match, implicitly because something + # on which we depend exists locally. Get rid of the reference + # to the Repository file; we'll build this (and anything that + # depends on it) locally. + $self->no_rfile; + } + + # Then check for currency. + if (! 'sig'->current($self, $sig)) { + # We have to build/derive the file. + print((' ' x $level), "Rebuilding ", $self->path, ": out of date.\n") + if $param::depends; + # First check to see if the built file is cached. + if ($builder->cachin($self, $sig)) { + 'sig'->bsig($self, $sig); + return $self->{status} = 'built'; + } elsif ($builder->action($self)) { + $builder->cachout($self, $sig); + 'sig'->bsig($self, $sig); + return $self->{status} = 'built'; + } else { + die("$0: errors constructing ${\$self->path}\n") + unless $param::kflag; + return $self->{status} = 'errors'; + } + } else { + # Push this out to the cache if we've been asked to (-C option). + # Don't normally do this because it slows us down. + # In a fully built system, no accesses to the cache directory + # are required to check any files. This is a win if cache is + # heavily shared. Enabling this option puts the directory in the + # loop. Useful only when you wish to recreate a cache from a build. + if ($param::cachesync) { + $builder->cachout($self, $sig); + 'sig'->bsig($self, $sig); + } + return $self->{status} = 'handled'; + } +} + +# Bind an action to a file, with the specified sources. No return value. +sub bind { + my($self, $builder, @sources) = @_; + if ($self->{builder} && !$self->{builder}->compatible($builder)) { + # Even if not "compatible", we can still check to see if the + # derivation is identical. It should be identical if the builder is + # the same and the sources are the same. + if ("$self->{builder} @{$self->{sources}}" ne "$builder @sources") { + $main::errors++; + my($_foo1, $script1, $line1, $sub1) = @{$self->creator}; + my($_foo2, $script2, $line2, $sub2) = script::caller_info; + my $err = "\t${\$self->path}\n" . + "\tbuilt (at least) two different ways:\n" . + "\t\t$script1, line $line1: $sub1\n" . + "\t\t$script2, line $line2: $sub2\n"; + die $err; + } + return; + } + if ($param::wflag) { + my($script, $line, $sub); + (undef, $script, $line, $sub) = script::caller_info; + $self->{script} = '' if ! defined $self->{script}; + $self->{script} .= "; " if $self->{script}; + $self->{script} .= qq($sub in "$script", line $line); + } + $self->{builder} = $builder; + push(@{$self->{sources}}, @sources); + @{$self->{dep}} = () if ! defined $self->{dep}; + $self->{conscript} = $priv::self->{script}; +} + +sub is_under { + $_[0]->{dir}->is_under($_[1]); +} + +sub relpath { + my $dirpath = $_[0]->relpath($_[1]->{dir}); + if (! $dirpath) { + return $_[1]->{entry}; + } else { + File::Spec->catfile($dirpath, $_[1]->{entry}); + } +} + +# Return the signature array for this file. +# This probably belongs in its own "sigarray" package, +# which would make it easier to optimize performance. +sub sigarray { + if ($_[0]->{sigaref}) { + return @{$_[0]->{sigaref}}; + } + my $self = shift; + # glob2pat based on The Perl Cookbook, p. 180. + sub glob2pat { + my $globstr = shift; + my %patmap = ( + '*' => '.*', + '?' => '.', + '[' => '[', + ']' => ']', + '/' => "\Q$dir::SEPARATOR", # Cons-specific modification + ); + $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge; + return '^' . $globstr . '$'; + } + my @sigarray; + my $default; + my $builder = $self->lfile->{builder}; + if (! $builder) { + @sigarray = @$param::sourcesig; + $default = [qw(content)]; + } else { + if ($builder->{env} && $builder->{env}->{SIGNATURE}) { + @sigarray = @{$builder->{env}->{SIGNATURE}}; + } else { + my $class = ref $builder; + my $path = $self->path; + warn qq($0: Warning: Builder package $class did not record\n) . + qq(\tthe calling environment for '$path'.\n) . + qq(\tUnable to use any %SIGNATURE construction variable\n) . + qq(\tfor signature configuration.\n); + } + $default = [qw(build)]; + } + my $path = $self->path; + while (@sigarray) { + my($glob, $aref) = splice(@sigarray, 0, 2); + my $re = glob2pat($glob); + if ($path =~ /$re/) { + $aref = [split(/\s+/, $aref)] if ! ref $aref; + $self->{sigaref} = $aref; + return @$aref; + } + } + $self->{sigaref} = $default; + return @{$self->{sigaref}} +} + +# Decide if this file's signature should be the content or build signature. +sub sigtype { + if ($_[0]->{sigtype}) { + return $_[0]->{sigtype}; + } + my $self = shift; + my @sigarray = $self->sigarray; + my $sigtype; + if (grep($_ eq "build", @sigarray)) { + $sigtype = 'bsig'; + } elsif (grep($_ =~ /content$/, @sigarray)) { + $sigtype = 'csig'; + } + return $self->{sigtype} = $sigtype; +} + +# Return whether this file is configured to use stored +# signature values from the .consign file. +sub stored { + if (! defined $_[0]->{stored}) { + $_[0]->{stored} = grep($_ eq "stored-content", $_[0]->sigarray); + } + return $_[0]->{stored}; +} + +# Generic entry (file or directory) handling. +# This is an empty subclass for nodes that haven't +# quite decided whether they're files or dirs. +# Use file methods until someone blesses them one way or the other. +package entry; + +use vars qw( @ISA ); + +BEGIN { @ISA = qw(file) } + +# File utilities +package futil; + +# Install one file as another. +# Links them if possible (hard link), otherwise copies. +# Don't ask why, but the source is a path, the tgt is a file obj. +sub install { + my($sp, $tgt) = @_; + my($tp) = $tgt->path; + return 1 if $tp eq $sp; + return 1 if eval { link($sp, $tp) }; + unlink($tp); + if (! futil::mkdir($tgt->{dir})) { + return undef; + } + return 1 if eval { link($sp, $tp) }; + futil::copy($sp, $tp); +} + +# Copy one file to another. Arguments are actual file names. +# Returns undef on failure. Preserves mtime and mode. +sub copy { + my ($sp, $tp) = @_; + my ($mode, $length, $atime, $mtime) = (stat($sp))[2,7,8,9]; + + # Use Perl standard library module for file copying, which handles + # binary copies. 1998-06-18 + if (! File::Copy::copy($sp, $tp)) { + warn qq($0: can\'t install "$sp" to "$tp" ($!)\n); #' + return undef; + } + # The file has been created, so try both the chmod and utime, + # first making sure the copy is writable (because permissions + # affect the ability to modify file times on some operating + # systems), and then changing permissions back if necessary. + my $ret = 1; + my $wmode = $mode | 0700; + if (! chmod $wmode, $tp) { + warn qq($0: can\'t set mode $wmode on file "$tp" ($!)\n); #' + $ret = undef; + } + if (! utime $atime, $mtime, $tp) { + warn qq($0: can\'t set modification time for file "$tp" ($!)\n); #' + $ret = undef; + } + if ($mode != $wmode && ! chmod $mode, $tp) { + warn qq($0: can\'t set mode $mode on file "$tp" ($!)\n); #' + $ret = undef; + } + return $ret; +} + +# Ensure that the specified directory exists. +# Aborts on failure. +sub mkdir { + return 1 if $_[0]->{'exists'}; + if (! futil::mkdir($_[0]->{dir})) { # Recursively make parent. + return undef; + } + my($path) = $_[0]->path; + if (!-d $path && !mkdir($path, 0777)) { + warn qq($0: can't create directory $path ($!).\n); #' + return undef; + } + $_[0]->{'exists'} = 1; +} + + +# Signature package. +package sig::hash; + +use vars qw( $called ); + +sub init { + my($dir) = @_; + my($consign) = $dir->prefix . ".consign"; + my($dhash) = $dir->{consign} = {}; + if (-f $consign) { + open(CONSIGN, $consign) || die("$0: can't open $consign ($!)\n"); + while() { + chop; + my ($file, $sig) = split(/:/,$_); + $dhash->{$file} = $sig; + } + close(CONSIGN); + } + $dhash +} + +# Read the hash entry for a particular file. +sub in { + my($dir) = $_[0]->{dir}; + ($dir->{consign} || init($dir))->{$_[0]->{entry}} +} + +# Write the hash entry for a particular file. +sub out { + my($file, $sig) = @_; + my($dir) = $file->{dir}; + ($dir->{consign} || init($dir))->{$file->{entry}} = $sig; + $sig::hash::dirty{$dir} = $dir; +} + +# Eliminate the hash entry for a particular file. +sub clear { + my($file) = @_; + my($dir) = $file->{dir}; + delete $dir->{consign}->{$file->{entry}} if $dir->{consign}; + $sig::hash::dirty{$dir} = $dir; +} + +# Flush hash entries. Called at end or via ^C interrupt. +sub END { + return if $called++; # May be called twice. + close(CONSIGN); # in case this came in via ^C. + my $dir; + for $dir (values %sig::hash::dirty) { + my($consign) = $dir->prefix . ".consign"; + my($constemp) = $consign . ".$$"; + if (! open(CONSIGN, ">$constemp")) { + die("$0: can't create $constemp ($!)\n"); + } + my($entry, $sig); + while (($entry, $sig) = each %{$dir->{consign}}) { + if (! print CONSIGN "$entry:$sig\n") { + die("$0: error writing to $constemp ($!)\n"); + } + } + close(CONSIGN); + if (! rename($constemp, $consign)) { + if (futil::copy($constemp, $consign)) { + unlink($constemp); + } else { + die("$0: couldn't rename or copy $constemp to $consign " . + "($!)\n"); + } + } + } +} + + +# Derived file caching. +package cache; + +# Find a file in the cache. Return non-null if the file is in the cache. +sub in { + return undef unless $param::cache; + my($file, $sig) = @_; + # Add the path to the signature, to make it unique. + $sig = 'sig'->collect($sig, $file->path) unless $param::mixtargets; + my($dir) = substr($sig, 0, 1); + my($cp) = File::Spec->catfile($param::cache, $dir, $sig); + return -f $cp && futil::install($cp, $file); +} + +# Try to flush a file to the cache, if not already there. +# If it doesn't make it out, due to an error, then that doesn't +# really matter. +sub out { + return unless $param::cache; + my($file, $sig) = @_; + # Add the path to the signature, to make it unique. + $sig = 'sig'->collect($sig, $file->path) unless $param::mixtargets; + my($dir) = substr($sig, 0, 1); + my($sp) = $file->path; + my($cp) = File::Spec->catfile($param::cache, $dir, $sig); + my($cdir) = File::Spec->catfile($param::cache, $dir); + if (! -d $cdir) { + mkdir($cdir, 0777) || + die("$0: can't create cache directory $cdir ($!).\n"); + } elsif (-f $cp) { + # Already cached: try to use that instead, to save space. + # This can happen if the -cs option is used on a previously + # uncached build, or if two builds occur simultaneously. + my($lp) = ".$sig"; + unlink($lp); + return if ! eval { link($cp, $lp) }; + rename($lp, $sp); + # Unix98 says, "If the old argument and the new argument both + # [refer] to the same existing file, the rename() function + # returns successfully and performs no other action." So, if + # $lp and $sp are links (i.e., $cp and $sp are links), $lp is + # left, and we must unlink it ourselves. If the rename failed + # for any reason, it is also good form to unlink the temporary + # $lp. Otherwise $lp no longer exists and, barring some race, + # the unlink fails silently. + unlink($lp); + return; + } + + return if eval { link($sp, $cp) }; + return if ! -f $sp; # if nothing to cache. + if (futil::copy($sp, "$cp.new")) { + rename("$cp.new", $cp); + } +} + + +# Generic signature handling package. +# This handles the higher-layer distinction between content and build +# signatures, relying on an underlying calculation package like +# "sig::md5"" to provide the signature values themselves. +package sig; + +use vars qw( @ISA ); + +# Select the underlying package to be used for signature calculation. +# We play a few namespace games here. Specifically, we append +# "sig::" to the beginning of the subclass we're passed. Then, +# if the package ends in "::debug", we actually subclass the +# "sig::debug" package and as a wrapper around the underlying +# (e.g.) "sig::md5" package that's doing the real calculation. +sub select { + my($package, $subclass) = @_; + my $p = $package . "::" . $subclass; + my $sigpkg = $p; + if ($p =~ /(.*)::debug$/) { + $sigpkg = $1; + $p = 'sig::debug'; + } + @ISA = ($p); + $p->init($sigpkg); +}; + +# Set or return the build signature of a file. +# This is computed elsewhere and passed in to us. +sub bsig { + my($self, $file, $sig) = @_; + if (defined $sig) { + $file->{'bsig'} = $sig; + $self->set($file); + } elsif (! defined $file->{'bsig'}) { + $file->{'bsig'} = ''; + } + $file->{'bsig'} +} + +# Determine the content signature of a file. +# This also sets the .consign entry unless the file is in a +# repository; we don't write into repositories, only read from them. +sub csig { + my($self, $file) = @_; + if (! $file->{'csig'}) { + $file->{'csig'} = $self->srcsig($file->path); + $self->set($file) if ! $file->is_on_rpath; + } + $_[1]->{'csig'} +} + +# Determine the current signature of an already-existing or +# non-existant file. Unless a specific signature type (bsig +# or csig) is requested, this consults the file's signature +# array to decide whether to return content or build signature, +# and whether to use a cached value from a .consign file. +sub signature { + my($self, $file, $sigtype) = @_; + $sigtype = $file->sigtype if ! $sigtype; + #open(TTY, ">/dev/tty"); + #print TTY $file->path, ": $sigtype\n"; + #close(TTY); + my($path) = $file->path; + my($time) = (stat($path))[9]; + if ($time) { + if ($file->{$sigtype}) { + return $file->{$sigtype}; + } + if ($file->is_on_rpath || $file->stored) { + if ('sig'->fetch($file) && $file->{$sigtype}) { + if ($file->{'sigtime'} == $time || + ! $param::rep_sig_times_ok + && $file->is_on_rpath) { + return $file->{$sigtype}; + } + } + $file->{$sigtype} = undef; + } + if ($file->is_on_rpath || ! File::Spec->file_name_is_absolute($path)) { + my $sig = ''; + if ($sigtype eq 'bsig') { $sig = $self->bsig($file); } + elsif ($sigtype eq 'csig') { $sig = $self->csig($file); } + return $sig; + } + # This file is not in a repository or under the local directory + # structure. In the canonical case, it's a utility that will be + # executed by a command. Historically, Cons has returned the + # name of the command concatenated with the modification time. + # Note that this is *not* the path ("cc" not "/bin/cc"), so it + # would lose in the unlikely event that a different copy of the + # utility was used that happened to have the same modification + # time (due to living in a different directory on the PATH, for + # example). The obvious "fix" of using the path like so, however: + # return $path . $time; + # is wrong. In a multi-machine build environment, different + # systems may have the same utility in different locations (due + # to different NFS mount points, for example), which would + # cause a lot of unnecessary builds if we used the full path. + # A better solution to strengthen this signature would be to + # also concatenate the size of the file, but that would cause + # unnecessary rebuilds when coming from .consign files that used + # the old scheme. All of which is to merely explain why we're + # leaving this as it has been, but documenting it here in case + # there's reason to change it in the future. + return $file->{entry} . $time; + } + return $file->{$sigtype} = ''; +} + +sub bsignature { + my($self, $file) = @_; + my($path) = $file->path; + my($time) = (stat($path))[9]; + if ($time) { + if ($file->{'bsig'}) { + return $file->{'bsig'}; + } + if ('sig'->fetch($file, 'bsig') && $file->{'bsig'}) { + if ($file->{'sigtime'} == $time || + ! $param::rep_sig_times_ok + && $file->is_on_rpath) { + return $file->{'bsig'}; + } + } + if ($file->is_on_rpath || ! File::Spec->file_name_is_absolute($path)) { + return $self->bsig($file); + } + return $path . $time; + } + return $file->{'bsig'} = ''; +} + +# Invalidate a file's signature, also clearing its .consign entry. +sub invalidate { + my($self, $file) = @_; + delete $file->{'sigtime'}; + delete $file->{'bsig'}; + delete $file->{'csig'}; + sig::hash::clear($file); +} + +# Store the signature for a file. +sub set { + my($self, $file) = @_; + my $sig = (stat($file->path))[9]; + $sig .= " " . ($file->{'bsig'} || '-'); + $sig .= " " . $file->{'csig'} if $file->{'csig'}; + sig::hash::out($file, $sig); +} + +# Fetch the signature(s) for a file. +# Returns whether there was a signature to fetch. +sub fetch { + my($self, $file, @kw) = @_; + @kw = ('bsig', 'csig') if ! @kw; + my $sig = sig::hash::in($file) || ''; + my($sigtime, $bsig, $csig) = split(/ /, $sig); + $file->{'sigtime'} = $sigtime; + $file->{'bsig'} = $bsig || '' if grep($_ eq 'bsig', @kw); + $file->{'csig'} = $csig || '' if grep($_ eq 'csig', @kw); + $file->{'bsig'} = '' if $file->{'bsig'} eq '-'; + return $sig ne ''; +} + +# MD5-based signature package. +package sig::md5; + +use vars qw( $md5 ); + +# Initialize MD5 signature calculation by finding an appropriate +# module and creating the proper object. +sub init { + my $self = shift; + my @md5_modules = qw(Digest::MD5 MD5 Digest::Perl::MD5); + # We used to find the right module more simply, using $_ as the + # loop iterator and just doing: + # + # eval "use $_"; + # $module = $_, $last if ! $@; + # + # in the loop. Empirically, though, this doesn't pass back the + # right value in $module on some ActiveState versions. (Maybe + # it's something to do with the eval in a for loop, I dunno.) + # Work around it by using $_ to pass the value out of the loop, + # which seems to work everywhere. + my $module; + for $module (@md5_modules) { + eval "use $module"; + $_ = $module, last if ! $@; + } + $module = $_; + die "Cannot find any MD5 module from: @md5_modules" if $@; + + $md5 = new $module; +} + +# Is the provided signature equal to the signature of the current +# instantiation of the target (and does the target exist)? +sub current { + my($self, $file, $sig, $sigtype) = @_; + $self->bsignature($file) eq $sig; +} + +# Return an aggregate signature for a list of signature values. +sub collect { + my($self, @sigs) = @_; + # The following sequence is faster than calling the hex interface. + $md5->reset(); + $md5->add(join('', $param::salt, @sigs)); + unpack("H*", $md5->digest()); +} + +# Directly compute a file signature as the MD5 checksum of the +# bytes in the file. +sub srcsig { + my($self, $path) = @_; + $md5->reset(); + open(FILE, $path) || return ''; + binmode(FILE); + $md5->addfile(\*FILE); + close(FILE); + unpack("H*", $md5->digest()); +} + +# Compute the signature of a command string. +# For MD5, this is just the string itself, since MD5 will condense +# the string contents into the ultimate signature. Other signature +# schemes may need to figure this out differently. +sub cmdsig { + my($self, $sig) = @_; + return $sig +} + +# Generic debug package for signature calculation. +# Because of the way we're called by sig::select() and then use +# the specified value to set up @ISA, this package is essentially a +# factory that creates packages like sig::md5::debug, etc., on the fly. +package sig::debug; + +use vars qw( @ISA $sigpkg $outfh ); + +local *FH; + +sub init { + my $self = shift; + $sigpkg = shift; + @ISA = ($sigpkg); + $sigpkg->init(); + my $file = $ENV{CONS_SIG_DEBUG}; + if ($file) { + if (! open(FH, ">$file")) { + die "Cannot open $file: $!"; + } + $outfh = \*FH; + } else { + $outfh = \*STDOUT; + } +} + +sub current { + my($self, $file, $sig, $sigtype) = @_; + my $fsig = $self->bsignature($file); + my $sub = "${sigpkg}::current"; + my $sep = "\n" . ' ' x (length($sub) + 1 - 3); + print $outfh "$sub(|$fsig|${sep}eq |$sig|)\n"; + return $fsig eq $sig; +} + +sub collect { + my($self, @sigs) = @_; + my $sig = $sigpkg->collect(@sigs); + my $sub = "${sigpkg}::collect"; + my $sep = ",\n" . ' ' x (length($sub) + 1); + my $buf = join($sep, @sigs); + $buf = $param::salt . $sep . $buf if $param::salt; + print $outfh "$sub($buf)\n\t=> |$sig|\n"; + return $sig; +} + +sub srcsig { + my($self, $path) = @_; + my $sig = $sigpkg->srcsig($path); + print $outfh "${sigpkg}::srcsig($path)\n\t=> |$sig|\n"; + return $sig; +} + +__END__; + +=head1 NAME + +Cons - A Software Construction System + +=head1 DESCRIPTION + +A guide and reference for version 2.3.1 + +Copyright (c) 1996-2001 Free Software Foundation, 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; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + +=head1 Introduction + +B is a system for constructing, primarily, software, but is quite +different from previous software construction systems. Cons was designed +from the ground up to deal easily with the construction of software spread +over multiple source directories. Cons makes it easy to create build scripts +that are simple, understandable and maintainable. Cons ensures that complex +software is easily and accurately reproducible. + +Cons uses a number of techniques to accomplish all of this. Construction +scripts are just Perl scripts, making them both easy to comprehend and very +flexible. Global scoping of variables is replaced with an import/export +mechanism for sharing information between scripts, significantly improving +the readability and maintainability of each script. B are introduced: these are Perl objects that capture the +information required for controlling the build process. Multiple +environments are used when different semantics are required for generating +products in the build tree. Cons implements automatic dependency analysis +and uses this to globally sequence the entire build. Variant builds are +easily produced from a single source tree. Intelligent build subsetting is +possible, when working on localized changes. Overrides can be setup to +easily override build instructions without modifying any scripts. MD5 +cryptographic B are associated with derived files, and are used +to accurately determine whether a given file needs to be rebuilt. + +While offering all of the above, and more, Cons remains simple and easy to +use. This will, hopefully, become clear as you read the remainder of this +document. + + +=head1 Why Cons? Why not Make? + +Cons is a B replacement. In the following paragraphs, we look at a few +of the undesirable characteristics of make--and typical build environments +based on make--that motivated the development of Cons. + +=head2 Build complexity + +Traditional make-based systems of any size tend to become quite complex. The +original make utility and its derivatives have contributed to this tendency +in a number of ways. Make is not good at dealing with systems that are +spread over multiple directories. Various work-arounds are used to overcome +this difficulty; the usual choice is for make to invoke itself recursively +for each sub-directory of a build. This leads to complicated code, in which +it is often unclear how a variable is set, or what effect the setting of a +variable will have on the build as a whole. The make scripting language has +gradually been extended to provide more possibilities, but these have +largely served to clutter an already overextended language. Often, builds +are done in multiple passes in order to provide appropriate products from +one directory to another directory. This represents a further increase in +build complexity. + + +=head2 Build reproducibility + +The bane of all makes has always been the correct handling of +dependencies. Most often, an attempt is made to do a reasonable job of +dependencies within a single directory, but no serious attempt is made to do +the job between directories. Even when dependencies are working correctly, +make's reliance on a simple time stamp comparison to determine whether a +file is out of date with respect to its dependents is not, in general, +adequate for determining when a file should be rederived. If an external +library, for example, is rebuilt and then ``snapped'' into place, the +timestamps on its newly created files may well be earlier than the last +local build, since it was built before it became visible. + + +=head2 Variant builds + +Make provides only limited facilities for handling variant builds. With the +proliferation of hardware platforms and the need for debuggable +vs. optimized code, the ability to easily create these variants is +essential. More importantly, if variants are created, it is important to +either be able to separate the variants or to be able to reproduce the +original or variant at will. With make it is very difficult to separate the +builds into multiple build directories, separate from the source. And if +this technique isn't used, it's also virtually impossible to guarantee at +any given time which variant is present in the tree, without resorting to a +complete rebuild. + + +=head2 Repositories + +Make provides only limited support for building software from code that +exists in a central repository directory structure. The VPATH feature of +GNU make (and some other make implementations) is intended to provide this, +but doesn't work as expected: it changes the path of target file to the +VPATH name too early in its analysis, and therefore searches for all +dependencies in the VPATH directory. To ensure correct development builds, +it is important to be able to create a file in a local build directory and +have any files in a code repository (a VPATH directory, in make terms) that +depend on the local file get rebuilt properly. This isn't possible with +VPATH, without coding a lot of complex repository knowledge directly into +the makefiles. + + +=head1 Keeping it simple + +A few of the difficulties with make have been cited above. In this and +subsequent sections, we shall introduce Cons and show how these issues are +addressed. + +=head2 Perl scripts + +Cons is Perl-based. That is, Cons scripts--F and F +files, the equivalent to F or F--are all written in +Perl. This provides an immediate benefit: the language for writing scripts +is a familiar one. Even if you don't happen to be a Perl programmer, it +helps to know that Perl is basically just a simple declarative language, +with a well-defined flow of control, and familiar semantics. It has +variables that behave basically the way you would expect them to, +subroutines, flow of control, and so on. There is no special syntax +introduced for Cons. The use of Perl as a scripting language simplifies +the task of expressing the appropriate solution to the often complex +requirements of a build. + + +=head2 Hello, World! + +To ground the following discussion, here's how you could build the B C application with Cons: + + + + $env = new cons(); + Program $env 'hello', 'hello.c'; + +If you install this script in a directory, naming the script F, +and create the F source file in the same directory, then you can +type C to build the application: + + + + % cons hello + cc -c hello.c -o hello.o + cc -o hello hello.o + + +=head2 Construction environments + +A key simplification of Cons is the idea of a B. A +construction environment is an B characterized by a set of key/value +pairs and a set of B. In order to tell Cons how to build something, +you invoke the appropriate method via an appropriate construction +environment. Consider the following example: + + + + $env = new cons( + CC => 'gcc', + LIBS => 'libworld.a' + ); + + Program $env 'hello', 'hello.c'; + +In this case, rather than using the default construction environment, as is, +we have overridden the value of C so that the GNU C Compiler equivalent +is used, instead. Since this version of B requires a library, +F, we have specified that any program linked in this environment +should be linked with that library. If the library exists already, well and +good, but if not, then we'll also have to include the statement: + + + + Library $env 'libworld', 'world.c'; + +Now if you type C, the library will be built before the program +is linked, and, of course, C will be used to compile both modules: + + + + % cons hello + gcc -c hello.c -o hello.o + gcc -c world.c -o world.o + ar r libworld.a world.o + ar: creating libworld.a + ranlib libworld.a + gcc -o hello hello.o libworld.a + + +=head2 Automatic and complete dependency analysis + +With Cons, dependencies are handled automatically. Continuing the previous +example, note that when we modify F, F is recompiled, +F recreated, and F relinked: + + + + % vi world.c + [EDIT] + % cons hello + gcc -c world.c -o world.o + ar r libworld.a world.o + ar: creating libworld.a + ranlib libworld.a + gcc -o hello hello.o libworld.a + +This is a relatively simple example: Cons ``knows'' F depends upon +F, because the dependency is explicitly set up by the C +method. It also knows that F depends upon F and that +F depends upon F, all for similar reasons. + +Now it turns out that F also includes the interface definition +file, F: + + + + % emacs world.h + [EDIT] + % cons hello + gcc -c hello.c -o hello.o + gcc -o hello hello.o libworld.a + +How does Cons know that F includes F, and that F +must therefore be recompiled? For now, suffice it to say that when +considering whether or not F is up-to-date, Cons invokes a scanner +for its dependency, F. This scanner enumerates the files included +by F to come up with a list of further dependencies, beyond those +made explicit by the Cons script. This process is recursive: any files +included by included files will also be scanned. + +Isn't this expensive? The answer is--it depends. If you do a full build of a +large system, the scanning time is insignificant. If you do a rebuild of a +large system, then Cons will spend a fair amount of time thinking about it +before it decides that nothing has to be done (although not necessarily more +time than make!). The good news is that Cons makes it very easy to +intelligently subset your build, when you are working on localized changes. + + +=head2 Automatic global build sequencing + +Because Cons does full and accurate dependency analysis, and does this +globally, for the entire build, Cons is able to use this information to take +full control of the B of the build. This sequencing is evident +in the above examples, and is equivalent to what you would expect for make, +given a full set of dependencies. With Cons, this extends trivially to +larger, multi-directory builds. As a result, all of the complexity involved +in making sure that a build is organized correctly--including multi-pass +hierarchical builds--is eliminated. We'll discuss this further in the next +sections. + +=head1 Building large trees--still just as simple + + +=head2 A hierarchy of build scripts + +A larger build, in Cons, is organized by creating a hierarchy of B. At the top of the tree is a script called F. The rest +of the scripts, by convention, are each called F. These scripts +are connected together, very simply, by the C, C, and +C commands. + + +=head2 The Build command + +The C command takes a list of F file names, and arranges +for them to be included in the build. For example: + + Build qw( + drivers/display/Conscript + drivers/mouse/Conscript + parser/Conscript + utilities/Conscript + ); + +This is a simple two-level hierarchy of build scripts: all the subsidiary +F files are mentioned in the top-level F file. Notice +that not all directories in the tree necessarily have build scripts +associated with them. + +This could also be written as a multi-level script. For example, the +F file might contain this command: + + Build qw( + parser/Conscript + drivers/Conscript + utilities/Conscript + ); + +and the F file in the F directory might contain this: + + Build qw( + display/Conscript + mouse/Conscript + ); + +Experience has shown that the former model is a little easier to understand, +since the whole construction tree is laid out in front of you, at the +top-level. Hybrid schemes are also possible. A separately maintained +component that needs to be incorporated into a build tree, for example, +might hook into the build tree in one place, but define its own construction +hierarchy. + +By default, Cons does not change its working directory to the directory +containing a subsidiary F file it is including. This behavior +can be enabled for a build by specifying, in the top-level F +file: + + Conscript_chdir 1; + +When enabled, Cons will change to the subsidiary F file's +containing directory while reading in that file, and then change back +to the top-level directory once the file has been processed. + +It is expected that this behavior will become the default in some future +version of Cons. To prepare for this transition, builds that expect +Cons to remain at the top of the build while it reads in a subsidiary +F file should explicitly disable this feature as follows: + + Conscript_chdir 0; + + +=head2 Relative, top-relative, and absolute file names + +You may have noticed that the file names specified to the Build command are +relative to the location of the script it is invoked from. This is generally +true for other filename arguments to other commands, too, although we might +as well mention here that if you begin a file name with a hash mark, ``#'', +then that file is interpreted relative to the top-level directory (where the +F file resides). And, not surprisingly, if you begin it with ``/'', +then it is considered to be an absolute pathname. This is true even on +systems which use a back slash rather than a forward slash to name absolute +paths. + +(There is another file prefix, ``!'', that is interpreted specially by +Cons. See discussion of the C command, below, for details.) + + +=head2 Using modules in build scripts + +You may pull modules into each F file using the normal Perl +C or C statements: + + use English; + require My::Module; + +Each C or C only affects the one F file in which +it appears. To use a module in multiple F files, you must +put a C or C statement in each one that needs the module. + + +=head2 Scope of variables + +The top-level F file and all F files begin life in +a common, separate Perl package. B controls the symbol table for +the package so that, the symbol table for each script is empty, except +for the F file, which gets some of the command line arguments. +All of the variables that are set or used, therefore, are set by the +script itself--not by some external script. + +Variables can be explicitly B by a script from its parent +script. To import a variable, it must have been B by the parent +and initialized (otherwise an error will occur). + + +=head2 The Export command + +The C command is used as in the following example: + + $env = new cons(); + $INCLUDE = "#export/include"; + $LIB = "#export/lib"; + Export qw( env INCLUDE LIB ); + Build qw( util/Conscript ); + +The values of the simple variables mentioned in the C list will be +squirreled away by any subsequent C commands. The C command +will only export Perl B variables, that is, variables whose name +begins with C<$>. Other variables, objects, etc. can be exported by +reference--but all scripts will refer to the same object, and this object +should be considered to be read-only by the subsidiary scripts and by the +original exporting script. It's acceptable, however, to assign a new value +to the exported scalar variable--that won't change the underlying variable +referenced. This sequence, for example, is OK: + + $env = new cons(); + Export qw( env INCLUDE LIB ); + Build qw( util/Conscript ); + $env = new cons(CFLAGS => '-O'); + Build qw( other/Conscript ); + +It doesn't matter whether the variable is set before or after the C +command. The important thing is the value of the variable at the time the +C command is executed. This is what gets squirreled away. Any +subsequent C commands, by the way, invalidate the first: you must +mention all the variables you wish to export on each C command. + + +=head2 The Import command + +Variables exported by the C command can be imported into subsidiary +scripts by the C command. The subsidiary script always imports +variables directly from the superior script. Consider this example: + + Import qw( env INCLUDE ); + +This is only legal if the parent script exported both C<$env> and +C<$INCLUDE>. It also must have given each of these variables values. It is +OK for the subsidiary script to only import a subset of the exported +variables (in this example, C<$LIB>, which was exported by the previous +example, is not imported). + +All the imported variables are automatically re-exported, so the sequence: + + Import qw ( env INCLUDE ); + Build qw ( beneath-me/Conscript ); + +will supply both C<$env> and C<$INCLUDE> to the subsidiary file. If only +C<$env> is to be exported, then the following will suffice: + + Import qw ( env INCLUDE ); + Export qw ( env ); + Build qw ( beneath-me/Conscript ); + +Needless to say, the variables may be modified locally before invoking +C on the subsidiary script. + + +=head2 Build script evaluation order + +The only constraint on the ordering of build scripts is that superior +scripts are evaluated before their inferior scripts. The top-level +F file, for instance, is evaluated first, followed by any +inferior scripts. This is all you really need to know about the evaluation +order, since order is generally irrelevant. Consider the following C +command: + + Build qw( + drivers/display/Conscript + drivers/mouse/Conscript + parser/Conscript + utilities/Conscript + ); + +We've chosen to put the script names in alphabetical order, simply because +that's the most convenient for maintenance purposes. Changing the order will +make no difference to the build. + + +=head1 A Model for sharing files + + +=head2 Some simple conventions + +In any complex software system, a method for sharing build products needs to +be established. We propose a simple set of conventions which are trivial to +implement with Cons, but very effective. + +The basic rule is to require that all build products which need to be shared +between directories are shared via an intermediate directory. We have +typically called this F, and, in a C environment, provided +conventional sub-directories of this directory, such as F, F, +F, etc. + +These directories are defined by the top-level F file. A simple +F file for a B application, organized using +multiple directories, might look like this: + + # Construct file for Hello, World! + + # Where to put all our shared products. + $EXPORT = '#export'; + + Export qw( CONS INCLUDE LIB BIN ); + + # Standard directories for sharing products. + $INCLUDE = "$EXPORT/include"; + $LIB = "$EXPORT/lib"; + $BIN = "$EXPORT/bin"; + + # A standard construction environment. + $CONS = new cons ( + CPPPATH => $INCLUDE, # Include path for C Compilations + LIBPATH => $LIB, # Library path for linking programs + LIBS => '-lworld', # List of standard libraries + ); + + Build qw( + hello/Conscript + world/Conscript + ); + +The F directory's F file looks like this: + + # Conscript file for directory world + Import qw( CONS INCLUDE LIB ); + + # Install the products of this directory + Install $CONS $LIB, 'libworld.a'; + Install $CONS $INCLUDE, 'world.h'; + + # Internal products + Library $CONS 'libworld.a', 'world.c'; + +and the F directory's F file looks like this: + + # Conscript file for directory hello + Import qw( CONS BIN ); + + # Exported products + Install $CONS $BIN, 'hello'; + + # Internal products + Program $CONS 'hello', 'hello.c'; + +To construct a B program with this directory structure, go to +the top-level directory, and invoke C with the appropriate +arguments. In the following example, we tell Cons to build the directory +F. To build a directory, Cons recursively builds all known products +within that directory (only if they need rebuilding, of course). If any of +those products depend upon other products in other directories, then those +will be built, too. + + % cons export + Install world/world.h as export/include/world.h + cc -Iexport/include -c hello/hello.c -o hello/hello.o + cc -Iexport/include -c world/world.c -o world/world.o + ar r world/libworld.a world/world.o + ar: creating world/libworld.a + ranlib world/libworld.a + Install world/libworld.a as export/lib/libworld.a + cc -o hello/hello hello/hello.o -Lexport/lib -lworld + Install hello/hello as export/bin/hello + + +=head2 Clean, understandable, location-independent scripts + +You'll note that the two F files are very clean and +to-the-point. They simply specify products of the directory and how to build +those products. The build instructions are minimal: they specify which +construction environment to use, the name of the product, and the name of +the inputs. Note also that the scripts are location-independent: if you wish +to reorganize your source tree, you are free to do so: you only have to +change the F file (in this example), to specify the new locations +of the F files. The use of an export tree makes this goal easy. + +Note, too, how Cons takes care of little details for you. All the F +directories, for example, were made automatically. And the installed files +were really hard-linked into the respective export directories, to save +space and time. This attention to detail saves considerable work, and makes +it even easier to produce simple, maintainable scripts. + + +=head1 Separating source and build trees + +It's often desirable to keep any derived files from the build completely +separate from the source files. This makes it much easier to keep track of +just what is a source file, and also makes it simpler to handle B +builds, especially if you want the variant builds to co-exist. + + +=head2 Separating build and source directories using the Link command + +Cons provides a simple mechanism that handles all of these requirements. The +C command is invoked as in this example: + + Link 'build' => 'src'; + +The specified directories are ``linked'' to the specified source +directory. Let's suppose that you setup a source directory, F, with the +sub-directories F and F below it, as in the previous +example. You could then substitute for the original build lines the +following: + + Build qw( + build/world/Conscript + build/hello/Conscript + ); + +Notice that you treat the F file as if it existed in the build +directory. Now if you type the same command as before, you will get the +following results: + + % cons export + Install build/world/world.h as export/include/world.h + cc -Iexport/include -c build/hello/hello.c -o build/hello/hello.o + cc -Iexport/include -c build/world/world.c -o build/world/world.o + ar r build/world/libworld.a build/world/world.o + ar: creating build/world/libworld.a + ranlib build/world/libworld.a + Install build/world/libworld.a as export/lib/libworld.a + cc -o build/hello/hello build/hello/hello.o -Lexport/lib -lworld + Install build/hello/hello as export/bin/hello + +Again, Cons has taken care of the details for you. In particular, you will +notice that all the builds are done using source files and object files from +the build directory. For example, F is compiled from +F, and F is installed from +F. This is accomplished on most systems by the simple +expedient of ``hard'' linking the required files from each source directory +into the appropriate build directory. + +The links are maintained correctly by Cons, no matter what you do to the +source directory. If you modify a source file, your editor may do this ``in +place'' or it may rename it first and create a new file. In the latter case, +any hard link will be lost. Cons will detect this condition the next time +the source file is needed, and will relink it appropriately. + +You'll also notice, by the way, that B changes were required to the +underlying F files. And we can go further, as we shall see in the +next section. + +=head2 Explicit references to the source directory + +When using the C command on some operating systems or with some +tool chains, it's sometimes useful to have a command actually use +the path name to the source directory, not the build directory. For +example, on systems that must copy, not "hard link," the F and +F copies of C files, using the F path of a file +name might make an editor aware that a syntax error must be fixed in the +source directory, not the build directory. + +You can tell Cons that you want to use the "source path" for a file by +preceding the file name with a ``!'' (exclamation point). For example, +if we add a ``!'' to the beginning of a source file: + + Program $env "foo", "!foo.c"; # Notice initial ! on foo.c + +Cons will compile the target as follows: + + cc -c src/foo.c -o build/foo.o + cc -o build/foo build/foo.o + +Notice that Cons has compiled the program from the the F +source file. Without the initial ``!'', Cons would have compiled the +program using the F path name. + + + +=head1 Variant builds + + +=head2 Hello, World! for baNaNa and peAcH OS's + +Variant builds require just another simple extension. Let's take as an +example a requirement to allow builds for both the baNaNa and peAcH +operating systems. In this case, we are using a distributed file system, +such as NFS to access the particular system, and only one or the other of +the systems has to be compiled for any given invocation of C. Here's +one way we could set up the F file for our B +application: + + # Construct file for Hello, World! + + die qq(OS must be specified) unless $OS = $ARG{OS}; + die qq(OS must be "peach" or "banana") + if $OS ne "peach" && $OS ne "banana"; + + # Where to put all our shared products. + $EXPORT = "#export/$OS"; + + Export qw( CONS INCLUDE LIB BIN ); + + # Standard directories for sharing products. + $INCLUDE = "$EXPORT/include"; + $LIB = "$EXPORT/lib"; + $BIN = "$EXPORT/bin"; + + # A standard construction environment. + $CONS = new cons ( + CPPPATH => $INCLUDE, # Include path for C Compilations + LIBPATH => $LIB, # Library path for linking programs + LIBS => '-lworld', # List of standard libraries + ); + + # $BUILD is where we will derive everything. + $BUILD = "#build/$OS"; + + # Tell cons where the source files for $BUILD are. + Link $BUILD => 'src'; + + Build ( + "$BUILD/hello/Conscript", + "$BUILD/world/Conscript", + ); + +Now if we login to a peAcH system, we can build our B +application for that platform: + + % cons export OS=peach + Install build/peach/world/world.h as export/peach/include/world.h + cc -Iexport/peach/include -c build/peach/hello/hello.c -o build/peach/hello/hello.o + cc -Iexport/peach/include -c build/peach/world/world.c -o build/peach/world/world.o + ar r build/peach/world/libworld.a build/peach/world/world.o + ar: creating build/peach/world/libworld.a + ranlib build/peach/world/libworld.a + Install build/peach/world/libworld.a as export/peach/lib/libworld.a + cc -o build/peach/hello/hello build/peach/hello/hello.o -Lexport/peach/lib -lworld + Install build/peach/hello/hello as export/peach/bin/hello + + +=head2 Variations on a theme + +Other variations of this model are possible. For example, you might decide +that you want to separate out your include files into platform dependent and +platform independent files. In this case, you'd have to define an +alternative to C<$INCLUDE> for platform-dependent files. Most F +files, generating purely platform-independent include files, would not have +to change. + +You might also want to be able to compile your whole system with debugging +or profiling, for example, enabled. You could do this with appropriate +command line options, such as C. This would then be translated +into the appropriate platform-specific requirements to enable debugging +(this might include turning off optimization, for example). You could +optionally vary the name space for these different types of systems, but, as +we'll see in the next section, it's not B to do this, since Cons +is pretty smart about rebuilding things when you change options. + + +=head1 Signatures + +Cons uses file B to decide if a derived file is out-of-date +and needs rebuilding. In essence, if the contents of a file change, +or the manner in which the file is built changes, the file's signature +changes as well. This allows Cons to decide with certainty when a file +needs rebuilding, because Cons can detect, quickly and reliably, whether +any of its dependency files have been changed. + + +=head2 MD5 content and build signatures + +Cons uses the B (B) algorithm to compute file +signatures. The MD5 algorithm computes a strong cryptographic checksum +for any given input string. Cons can, based on configuration, use two +different MD5 signatures for a given file: + +The B of a file is an MD5 checksum of the file's +contents. Consequently, when the contents of a file change, its content +signature changes as well. + +The B of a file is a combined MD5 checksum of: + +=over 4 + +the signatures of all the input files used to build the file + +the signatures of all dependency files discovered by source scanners +(for example, C<.h> files) + +the signatures of all dependency files specified explicitly via the +C method) + +the command-line string used to build the file + +=back + +The build signature is, in effect, a digest of all the dependency +information for the specified file. Consequently, a file's build +signature changes whenever any part of its dependency information +changes: a new file is added, the contents of a file on which it depends +change, there's a change to the command line used to build the file (or +any of its dependency files), etc. + +For example, in the previous section, the build signature of the +F file will include: + +=over 4 + +the signature of the F file + +the signatures of any header files that Cons detects are included, +directly or indirectly, by F + +the text of the actual command line was used to generate F + +=back + +Similarly, the build signature of the F file will include +all the signatures of its constituents (and hence, transitively, the +signatures of B constituents), as well as the command line that +created the file. + +Note that there is no need for a derived file to depend upon any +particular F or F file. If changes to these files +affect a file, then this will be automatically reflected in its build +signature, since relevant parts of the command line are included in the +signature. Unrelated F or F changes will have no +effect. + + +=head2 Storing signatures in .consign files + +Before Cons exits, it stores the calculated signatures for all of the +files it built or examined in F<.consign> files, one per directory. +Cons uses this stored information on later invocations to decide if +derived files need to be rebuilt. + +After the previous example was compiled, the F<.consign> file in the +F directory looked like this: + + world.h:985533370 - d181712f2fdc07c1f05d97b16bfad904 + world.o:985533372 2a0f71e0766927c0532977b0d2158981 + world.c:985533370 - c712f77189307907f4189b5a7ab62ff3 + libworld.a:985533374 69e568fc5241d7d25be86d581e1fb6aa + +After the file name and colon, the first number is a timestamp of the +file's modification time (on UNIX systems, this is typically the number +of seconds since January 1st, 1970). The second value is the build +signature of the file (or ``-'' in the case of files with no build +signature--that is, source files). The third value, if any, is the +content signature of the file. + + +=head2 Using build signatures to decide when to rebuild files + +When Cons is deciding whether to build or rebuild a derived file, it +first computes the file's current build signature. If the file doesn't +exist, it must obviously be built. + +If, however, the file already exists, Cons next compares the +modification timestamp of the file against the timestamp value in +the F<.consign> file. If the timestamps match, Cons compares the +newly-computed build signature against the build signature in the +F<.consign> file. If the timestamps do not match or the build +signatures do not match, the derived file is rebuilt. + +After the file is built or rebuilt, Cons arranges to store the +newly-computed build signature in the F<.consign> file when it exits. + + +=head2 Signature example + +The use of these signatures is an extremely simple, efficient, and +effective method of improving--dramatically--the reproducibility of a +system. + +We'll demonstrate this with a simple example: + + # Simple "Hello, World!" Construct file + $CFLAGS = '-g' if $ARG{DEBUG} eq 'on'; + $CONS = new cons(CFLAGS => $CFLAGS); + Program $CONS 'hello', 'hello.c'; + +Notice how Cons recompiles at the appropriate times: + + % cons hello + cc -c hello.c -o hello.o + cc -o hello hello.o + % cons hello + cons: "hello" is up-to-date. + % cons DEBUG=on hello + cc -g -c hello.c -o hello.o + cc -o hello hello.o + % cons DEBUG=on hello + cons: "hello" is up-to-date. + % cons hello + cc -c hello.c -o hello.o + cc -o hello hello.o + + +=head2 Source-file signature configuration + +Cons provides a C method that allows you to configure +how the signature should be calculated for any source file when its +signature is being used to decide if a dependent file is up-to-date. +The arguments to the C method consist of one or more +pairs of strings: + + SourceSignature 'auto/*.c' => 'content', + '*' => 'stored-content'; + +The first string in each pair is a pattern to match against derived file +path names. The pattern is a file-globbing pattern, not a Perl regular +expression; the pattern <*.l> will match all Lex source files. The C<*> +wildcard will match across directory separators; the pattern C +would match all C source files in any subdirectory underneath the C +subdirectory. + +The second string in each pair contains one of the following keywords to +specify how signatures should be calculated for source files that match +the pattern. The available keywords are: + +=over 4 + +=item content + +Use the content signature of the source file when calculating signatures +of files that depend on it. This guarantees correct calculation of the +file's signature for all builds, by telling Cons to read the contents of +a source file to calculate its content signature each time it is run. + +=item stored-content + +Use the source file's content signature as stored in the F<.consign> +file, provided the file's timestamp matches the cached timestamp value +in the F<.consign> file. This optimizes performance, with the slight +risk of an incorrect build if a source file's contents have been changed +so quickly after its previous update that the timestamp still matches +the stored timestamp in the F<.consign> file even though the contents +have changed. + +=back + +The Cons default behavior of always calculating a source file's +signature from the file's contents is equivalent to specifying: + + SourceSignature '*' => 'content'; + +The C<*> will match all source files. The C keyword +specifies that Cons will read the contents of a source file to calculate +its signature each time it is run. + +A useful global performance optimization is: + + SourceSignature '*' => 'stored-content'; + +This specifies that Cons will use pre-computed content signatures +from F<.consign> files, when available, rather than re-calculating a +signature from the the source file's contents each time Cons is run. In +practice, this is safe for most build situations, and only a problem +when source files are changed automatically (by scripts, for example). +The Cons default, however, errs on the side of guaranteeing a correct +build in all situations. + +Cons tries to match source file path names against the patterns in the +order they are specified in the C arguments: + + SourceSignature '/usr/repository/objects/*' => 'stored-content', + '/usr/repository/*' => 'content', + '*.y' => 'content', + '*' => 'stored-content'; + +In this example, all source files under the F +directory will use F<.consign> file content signatures, source files +anywhere else underneath F will not use F<.consign> +signature values, all Yacc source files (C<*.y>) anywhere else will not +use F<.consign> signature values, and any other source file will use +F<.consign> signature values. + + +=head2 Derived-file signature configuration + +Cons provides a C construction variable that allows you to +configure how signatures are calculated for any derived file when its +signature is being used to decide if a dependent file is up-to-date. +The value of the C construction variable is a Perl array +reference that holds one or more pairs of strings, like the arguments to +the C method. + +The first string in each pair is a pattern to match against derived file +path names. The pattern is a file-globbing pattern, not a Perl regular +expression; the pattern `*.obj' will match all (Win32) object files. +The C<*> wildcard will match across directory separators; the pattern +`foo/*.a' would match all (UNIX) library archives in any subdirectory +underneath the foo subdirectory. + +The second string in each pair contains one of the following keywords +to specify how signatures should be calculated for derived files that +match the pattern. The available keywords are the same as for the +C method, with an additional keyword: + +=over 4 + +=item build + +Use the build signature of the derived file when calculating signatures +of files that depend on it. This guarantees correct builds by forcing +Cons to rebuild any and all files that depend on the derived file. + +=item content + +Use the content signature of the derived file when calculating signatures +of files that depend on it. This guarantees correct calculation of the +file's signature for all builds, by telling Cons to read the contents of +a derived file to calculate its content signature each time it is run. + +=item stored-content + +Use the derived file's content signature as stored in the F<.consign> +file, provided the file's timestamp matches the cached timestamp value +in the F<.consign> file. This optimizes performance, with the slight +risk of an incorrect build if a derived file's contents have been +changed so quickly after a Cons build that the file's timestamp still +matches the stored timestamp in the F<.consign> file. + +=back + +The Cons default behavior (as previously described) for using +derived-file signatures is equivalent to: + + $env = new cons(SIGNATURE => ['*' => 'build']); + +The C<*> will match all derived files. The C keyword specifies +that all derived files' build signatures will be used when calculating +whether a dependent file is up-to-date. + +A useful alternative default C configuration for many sites: + + $env = new cons(SIGNATURE => ['*' => 'content']); + +In this configuration, derived files have their signatures calculated +from the file contents. This adds slightly to Cons' workload, but has +the useful effect of "stopping" further rebuilds if a derived file is +rebuilt to exactly the same file contents as before, which usually +outweighs the additional computation Cons must perform. + +For example, changing a comment in a C file and recompiling should +generate the exact same object file (assuming the compiler doesn't +insert a timestamp in the object file's header). In that case, +specifying C or C for the signature calculation +will cause Cons to recognize that the object file did not actually +change as a result of being rebuilt, and libraries or programs that +include the object file will not be rebuilt. When C is +specified, however, Cons will only "know" that the object file was +rebuilt, and proceed to rebuild any additional files that include the +object file. + +Note that Cons tries to match derived file path names against the +patterns in the order they are specified in the C array +reference: + + $env = new cons(SIGNATURE => ['foo/*.o' => 'build', + '*.o' => 'content', + '*.a' => 'stored-content', + '*' => 'content']); + +In this example, all object files underneath the F subdirectory +will use build signatures, all other object files (including object +files underneath other subdirectories!) will use F<.consign> file +content signatures, libraries will use F<.consign> file build +signatures, and all other derived files will use content signatures. + + +=head2 Debugging signature calculation + +Cons provides a C<-S> option that can be used to specify what internal +Perl package Cons should use to calculate signatures. The default Cons +behavior is equivalent to specifying C<-S md5> on the command line. + +The only other package (currently) available is an C +package that prints out detailed information about the MD5 signature +calculations performed by Cons: + + % cons -S md5::debug hello + sig::md5::srcsig(hello.c) + => |52d891204c62fe93ecb95281e1571938| + sig::md5::collect(52d891204c62fe93ecb95281e1571938) + => |fb0660af4002c40461a2f01fbb5ffd03| + sig::md5::collect(52d891204c62fe93ecb95281e1571938, + fb0660af4002c40461a2f01fbb5ffd03, + cc -c %< -o %>) + => |f7128da6c3fe3c377dc22ade70647b39| + sig::md5::current(|| + eq |f7128da6c3fe3c377dc22ade70647b39|) + cc -c hello.c -o hello.o + sig::md5::collect() + => |d41d8cd98f00b204e9800998ecf8427e| + sig::md5::collect(f7128da6c3fe3c377dc22ade70647b39, + d41d8cd98f00b204e9800998ecf8427e, + cc -o %> %< ) + => |a0bdce7fd09e0350e7efbbdb043a00b0| + sig::md5::current(|| + eq |a0bdce7fd09e0350e7efbbdb043a00b0|) + cc -o hello, hello.o + + +=head1 Code Repositories + +Many software development organizations will have one or more central +repository directory trees containing the current source code for one or +more projects, as well as the derived object files, libraries, and +executables. In order to reduce unnecessary recompilation, it is useful to +use files from the repository to build development software--assuming, of +course, that no newer dependency file exists in the local build tree. + + +=head2 Repository + +Cons provides a mechanism to specify a list of code repositories that will +be searched, in-order, for source files and derived files not found in the +local build directory tree. + +The following lines in a F file will instruct Cons to look first +under the F directory and then under the +F directory: + + Repository qw ( + /usr/experiment/repository + /usr/product/repository + ); + +The repository directories specified may contain source files, derived files +(objects, libraries and executables), or both. If there is no local file +(source or derived) under the directory in which Cons is executed, then the +first copy of a same-named file found under a repository directory will be +used to build any local derived files. + +Cons maintains one global list of repositories directories. Cons will +eliminate the current directory, and any non-existent directories, from the +list. + + +=head2 Finding the Construct file in a Repository + +Cons will also search for F and F files in the +repository tree or trees. This leads to a chicken-and-egg situation, +though: how do you look in a repository tree for a F file if the +F file tells you where the repository is? To get around this, +repositories may be specified via C<-R> options on the command line: + + % cons -R /usr/experiment/repository -R /usr/product/repository . + +Any repository directories specified in the F or F +files will be appended to the repository directories specified by +command-line C<-R> options. + +=head2 Repository source files + +If the source code (include the F file) for the library version +of the I C application is in a repository (with no derived +files), Cons will use the repository source files to create the local object +files and executable file: + + % cons -R /usr/src_only/repository hello + gcc -c /usr/src_only/repository/hello.c -o hello.o + gcc -c /usr/src_only/repository/world.c -o world.o + ar r libworld.a world.o + ar: creating libworld.a + ranlib libworld.a + gcc -o hello hello.o libworld.a + +Creating a local source file will cause Cons to rebuild the appropriate +derived file or files: + + % pico world.c + [EDIT] + % cons -R /usr/src_only/repository hello + gcc -c world.c -o world.o + ar r libworld.a world.o + ar: creating libworld.a + ranlib libworld.a + gcc -o hello hello.o libworld.a + +And removing the local source file will cause Cons to revert back to +building the derived files from the repository source: + + % rm world.c + % cons -R /usr/src_only/repository hello + gcc -c /usr/src_only/repository/world.c -o world.o + ar r libworld.a world.o + ar: creating libworld.a + ranlib libworld.a + gcc -o hello hello.o libworld.a + + +=head2 Repository derived files + +If a repository tree contains derived files (usually object files, +libraries, or executables), Cons will perform its normal signature +calculation to decide whether the repository file is up-to-date or a derived +file must be built locally. This means that, in order to ensure correct +signature calculation, a repository tree must also contain the F<.consign> +files that were created by Cons when generating the derived files. + +This would usually be accomplished by building the software in the +repository (or, alternatively, in a build directory, and then copying the +result to the repository): + + % cd /usr/all/repository + % cons hello + gcc -c hello.c -o hello.o + gcc -c world.c -o world.o + ar r libworld.a world.o + ar: creating libworld.a + ranlib libworld.a + gcc -o hello hello.o libworld.a + +(This is safe even if the F file lists the F +directory in a C command because Cons will remove the current +directory from the repository list.) + +Now if we want to build a copy of the application with our own F +file, we only need to create the one necessary source file, and use the +C<-R> option to have Cons use other files from the repository: + + % mkdir $HOME/build1 + % cd $HOME/build1 + % ed hello.c + [EDIT] + % cons -R /usr/all/repository hello + gcc -c hello.c -o hello.o + gcc -o hello hello.o /usr/all/repository/libworld.a + +Notice that Cons has not bothered to recreate a local F library +(or recompile the F module), but instead uses the already-compiled +version from the repository. + +Because the MD5 signatures that Cons puts in the F<.consign> file contain +timestamps for the derived files, the signature timestamps must match the +file timestamps for a signature to be considered valid. + +Some software systems may alter the timestamps on repository files (by +copying them, e.g.), in which case Cons will, by default, assume the +repository signatures are invalid and rebuild files unnecessarily. This +behavior may be altered by specifying: + + Repository_Sig_Times_OK 0; + +This tells Cons to ignore timestamps when deciding whether a signature is +valid. (Note that avoiding this sanity check means there must be proper +control over the repository tree to ensure that the derived files cannot be +modified without updating the F<.consign> signature.) + + +=head2 Local copies of files + +If the repository tree contains the complete results of a build, and we try +to build from the repository without any files in our local tree, something +moderately surprising happens: + + % mkdir $HOME/build2 + % cd $HOME/build2 + % cons -R /usr/all/repository hello + cons: "hello" is up-to-date. + +Why does Cons say that the F program is up-to-date when there is no +F program in the local build directory? Because the repository (not +the local directory) contains the up-to-date F program, and Cons +correctly determines that nothing needs to be done to rebuild this +up-to-date copy of the file. + +There are, however, many times in which it is appropriate to ensure that a +local copy of a file always exists. A packaging or testing script, for +example, may assume that certain generated files exist locally. Instead of +making these subsidiary scripts aware of the repository directory, the +C command may be added to a F or F file to +specify that a certain file or files must appear in the local build +directory: + + Local qw( + hello + ); + +Then, if we re-run the same command, Cons will make a local copy of the +program from the repository copy (telling you that it is doing so): + + % cons -R /usr/all/repository hello + Local copy of hello from /usr/all/repository/hello + cons: "hello" is up-to-date. + +Notice that, because the act of making the local copy is not considered a +"build" of the F file, Cons still reports that it is up-to-date. + +Creating local copies is most useful for files that are being installed into +an intermediate directory (for sharing with other directories) via the +C command. Accompanying the C command for a file with a +companion C command is so common that Cons provides a +C command as a convenient way to do both: + + Install_Local $env, '#export', 'hello'; + +is exactly equivalent to: + + Install $env '#export', 'hello'; + Local '#export/hello'; + +Both the C and C commands update the local F<.consign> +file with the appropriate file signatures, so that future builds are +performed correctly. + + +=head2 Repository dependency analysis + +Due to its built-in scanning, Cons will search the specified repository +trees for included F<.h> files. Unless the compiler also knows about the +repository trees, though, it will be unable to find F<.h> files that only +exist in a repository. If, for example, the F file includes the +F file in its current directory: + + % cons -R /usr/all/repository hello + gcc -c /usr/all/repository/hello.c -o hello.o + /usr/all/repository/hello.c:1: hello.h: No such file or directory + +Solving this problem forces some requirements onto the way construction +environments are defined and onto the way the C C<#include> preprocessor +directive is used to include files. + +In order to inform the compiler about the repository trees, Cons will add +appropriate C<-I> flags to the compilation commands. This means that the +C variable in the construction environment must explicitly specify +all subdirectories which are to be searched for included files, including the +current directory. Consequently, we can fix the above example by changing +the environment creation in the F file as follows: + + $env = new cons( + CC => 'gcc', + CPPPATH => '.', + LIBS => 'libworld.a', + ); + +Due to the definition of the C variable, this yields, when we +re-execute the command: + + % cons -R /usr/all/repository hello + gcc -c -I. -I/usr/all/repository /usr/all/repository/hello.c -o hello.o + gcc -o hello hello.o /usr/all/repository/libworld.a + +The order of the C<-I> flags replicates, for the C preprocessor, the same +repository-directory search path that Cons uses for its own dependency +analysis. If there are multiple repositories and multiple C +directories, Cons will append the repository directories to the beginning of +each C directory, rapidly multiplying the number of C<-I> flags. +As an extreme example, a F file containing: + + Repository qw( + /u1 + /u2 + ); + + $env = new cons( + CPPPATH => 'a:b:c', + ); + +Would yield a compilation command of: + + cc -Ia -I/u1/a -I/u2/a -Ib -I/u1/b -I/u2/b -Ic -I/u1/c -I/u2/c -c hello.c -o hello.o + +In order to shorten the command lines as much as possible, Cons will +remove C<-I> flags for any directories, locally or in the repositories, +which do not actually exist. (Note that the C<-I> flags are not included +in the MD5 signature calculation for the target file, so the target will +not be recompiled if the compilation command changes due to a directory +coming into existence.) + +Because Cons relies on the compiler's C<-I> flags to communicate the +order in which repository directories must be searched, Cons' handling +of repository directories is fundamentally incompatible with using +double-quotes on the C<#include> directives in any C source code that +you plan to modify: + + #include "file.h" /* DON'T USE DOUBLE-QUOTES LIKE THIS */ + +This is because most C preprocessors, when faced with such a directive, will +always first search the directory containing the source file. This +undermines the elaborate C<-I> options that Cons constructs to make the +preprocessor conform to its preferred search path. + +Consequently, when using repository trees in Cons, B use +angle-brackets for included files in any C source (.c or .h) files that +you plan to modify locally: + + #include /* USE ANGLE-BRACKETS INSTEAD */ + +Code that will not change can still safely use double quotes on #include +lines. + + +=head2 Repository_List + +Cons provides a C command to return a list of all +repository directories in their current search order. This can be used for +debugging, or to do more complex Perl stuff: + + @list = Repository_List; + print join(' ', @list), "\n"; + + +=head2 Repository interaction with other Cons features + +Cons' handling of repository trees interacts correctly with other Cons +features--which is to say, it generally does what you would expect. + +Most notably, repository trees interact correctly, and rather powerfully, +with the 'Link' command. A repository tree may contain one or more +subdirectories for version builds established via C to a source +subdirectory. Cons will search for derived files in the appropriate build +subdirectories under the repository tree. + + +=head1 Default targets + +Until now, we've demonstrated invoking Cons with an explicit target +to build: + + % cons hello + +Normally, Cons does not build anything unless a target is specified, +but specifying '.' (the current directory) will build everything: + + % cons # does not build anything + + % cons . # builds everything under the top-level directory + +Adding the C method to any F or F file will add +the specified targets to a list of default targets. Cons will build +these defaults if there are no targets specified on the command line. +So adding the following line to the top-level F file will mimic +Make's typical behavior of building everything by default: + + Default '.'; + +The following would add the F and F commands (in the +same directory as the F or F file) to the default list: + + Default qw( + hello + goodbye + ); + +The C method may be used more than once to add targets to the +default list. + +=head1 Selective builds + +Cons provides two methods for reducing the size of given build. The first is +by specifying targets on the command line, and the second is a method for +pruning the build tree. We'll consider target specification first. + + +=head2 Selective targeting + +Like make, Cons allows the specification of ``targets'' on the command +line. Cons targets may be either files or directories. When a directory is +specified, this is simply a short-hand notation for every derivable +product--that Cons knows about--in the specified directory and below. For +example: + + % cons build/hello/hello.o + +means build F and everything that F might need. This is +from a previous version of the B program in which F +depended upon F. If that file is not up-to-date +(because someone modified F, then it will be rebuilt, +even though it is in a directory remote from F. + +In this example: + + % cons build + +Everything in the F directory is built, if necessary. Again, this may +cause more files to be built. In particular, both F +and F are required by the F directory, +and so they will be built if they are out-of-date. + +If we do, instead: + + % cons export + +then only the files that should be installed in the export directory will be +rebuilt, if necessary, and then installed there. Note that C +might build files that C doesn't build, and vice-versa. + + +=head2 No ``special'' targets + +With Cons, make-style ``special'' targets are not required. The simplest +analog with Cons is to use special F directories, instead. Let's +suppose, for example, that you have a whole series of unit tests that are +associated with your code. The tests live in the source directory near the +code. Normally, however, you don't want to build these tests. One solution +is to provide all the build instructions for creating the tests, and then to +install the tests into a separate part of the tree. If we install the tests +in a top-level directory called F, then: + + % cons tests + +will build all the tests. + + % cons export + +will build the production version of the system (but not the tests), and: + + % cons build + +should probably be avoided (since it will compile tests unnecessarily). + +If you want to build just a single test, then you could explicitly name the +test (in either the F directory or the F directory). You could +also aggregate the tests into a convenient hierarchy within the tests +directory. This hierarchy need not necessarily match the source hierarchy, +in much the same manner that the include hierarchy probably doesn't match +the source hierarchy (the include hierarchy is unlikely to be more than two +levels deep, for C programs). + +If you want to build absolutely everything in the tree (subject to whatever +options you select), you can use: + + % cons . + +This is not particularly efficient, since it will redundantly walk all the +trees, including the source tree. The source tree, of course, may have +buildable objects in it--nothing stops you from doing this, even if you +normally build in a separate build tree. + + +=head1 Build Pruning + +In conjunction with target selection, B can be used to reduce +the scope of the build. In the previous peAcH and baNaNa example, we have +already seen how script-driven build pruning can be used to make only half +of the potential build available for any given invocation of C. Cons +also provides, as a convenience, a command line convention that allows you +to specify which F files actually get ``built''--that is, +incorporated into the build tree. For example: + + % cons build +world + +The C<+> argument introduces a Perl regular expression. This must, of +course, be quoted at the shell level if there are any shell meta-characters +within the expression. The expression is matched against each F +file which has been mentioned in a C statement, and only those +scripts with matching names are actually incorporated into the build +tree. Multiple such arguments are allowed, in which case a match against any +of them is sufficient to cause a script to be included. + +In the example, above, the F program will not be built, since Cons +will have no knowledge of the script F. The F +archive will be built, however, if need be. + +There are a couple of uses for build pruning via the command line. Perhaps +the most useful is the ability to make local changes, and then, with +sufficient knowledge of the consequences of those changes, restrict the size +of the build tree in order to speed up the rebuild time. A second use for +build pruning is to actively prevent the recompilation of certain files that +you know will recompile due to, for example, a modified header file. You may +know that either the changes to the header file are immaterial, or that the +changes may be safely ignored for most of the tree, for testing +purposes.With Cons, the view is that it is pragmatic to admit this type of +behavior, with the understanding that on the next full build everything that +needs to be rebuilt will be. There is no equivalent to a ``make touch'' +command, to mark files as permanently up-to-date. So any risk that is +incurred by build pruning is mitigated. For release quality work, obviously, +we recommend that you do not use build pruning (it's perfectly OK to use +during integration, however, for checking compilation, etc. Just be sure to +do an unconstrained build before committing the integration). + + +=head1 Temporary overrides + +Cons provides a very simple mechanism for overriding aspects of a build. The +essence is that you write an override file containing one or more +C commands, and you specify this on the command line, when you run +C: + + % cons -o over export + +will build the F directory, with all derived files subject to the +overrides present in the F file. If you leave out the C<-o> option, +then everything necessary to remove all overrides will be rebuilt. + + +=head2 Overriding environment variables + +The override file can contain two types of overrides. The first is incoming +environment variables. These are normally accessible by the F +file from the C<%ENV> hash variable. These can trivially be overridden in +the override file by setting the appropriate elements of C<%ENV> (these +could also be overridden in the user's environment, of course). + + +=head2 The Override command + +The second type of override is accomplished with the C command, +which looks like this: + + Override , => , => , ...; + +The regular expression I is matched against every derived file that +is a candidate for the build. If the derived file matches, then the +variable/value pairs are used to override the values in the construction +environment associated with the derived file. + +Let's suppose that we have a construction environment like this: + + $CONS = new cons( + COPT => '', + CDBG => '-g', + CFLAGS => '%COPT %CDBG', + ); + +Then if we have an override file F containing this command: + + Override '\.o$', COPT => '-O', CDBG => ''; + +then any C invocation with C<-o over> that creates F<.o> files via +this environment will cause them to be compiled with C<-O >and no C<-g>. The +override could, of course, be restricted to a single directory by the +appropriate selection of a regular expression. + +Here's the original version of the Hello, World! program, built with this +environment. Note that Cons rebuilds the appropriate pieces when the +override is applied or removed: + + % cons hello + cc -g -c hello.c -o hello.o + cc -o hello hello.o + % cons -o over hello + cc -O -c hello.c -o hello.o + cc -o hello hello.o + % cons -o over hello + cons: "hello" is up-to-date. + % cons hello + cc -g -c hello.c -o hello.o + cc -o hello hello.o + +It's important that the C command only be used for temporary, +on-the-fly overrides necessary for development because the overrides are not +platform independent and because they rely too much on intimate knowledge of +the workings of the scripts. For temporary use, however, they are exactly +what you want. + +Note that it is still useful to provide, say, the ability to create a fully +optimized version of a system for production use--from the F and +F files. This way you can tailor the optimized system to the +platform. Where optimizer trade-offs need to be made (particular files may +not be compiled with full optimization, for example), then these can be +recorded for posterity (and reproducibility) directly in the scripts. + + +=head1 More on construction environments + +As previously mentioned, a B is an object that +has a set of keyword/value pairs and a set of methods, and which is used +to tell Cons how target files should be built. This section describes +how Cons uses and expands construction environment values to control its +build behavior. + +=head2 Construction variable expansion + +Construction variables from a construction environment are expanded +by preceding the keyword with a C<%> (percent sign): + + Construction variables: + XYZZY => 'abracadabra', + + The string: "The magic word is: %XYZZY!" + expands to: "The magic word is: abracadabra!" + +A construction variable name may be surrounded by C<{> and C<}> (curly +braces), which are stripped as part of the expansion. This can +sometimes be necessary to separate a variable expansion from trailing +alphanumeric characters: + + Construction variables: + OPT => 'value1', + OPTION => 'value2', + + The string: "%OPT %{OPT}ION %OPTION %{OPTION}" + expands to: "value1 value1ION value2 value2" + +Construction variable expansion is recursive--that is, a string +containing C<%->expansions after substitution will be re-expanded until +no further substitutions can be made: + + Construction variables: + STRING => 'The result is: %FOO', + FOO => '%BAR', + BAR => 'final value', + + The string: "The string says: %STRING" + expands to: "The string says: The result is: final value" + +If a construction variable is not defined in an environment, then the +null string is substituted: + + Construction variables: + FOO => 'value1', + BAR => 'value2', + + The string: "%FOO <%NO_VARIABLE> %BAR" + expands to: "value1 <> value2" + +A doubled C<%%> will be replaced by a single C<%>: + + The string: "Here is a percent sign: %%" + expands to: "Here is a percent sign: %" + +=head2 Default construction variables + +When you specify no arguments when creating a new construction +environment: + + $env = new cons(); + +Cons creates a reference to a new, default construction +environment. This contains a number of construction variables and some +methods. At the present writing, the default construction variables on a +UNIX system are: + + CC => 'cc', + CFLAGS => '', + CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', + CXX => '%CC', + CXXFLAGS => '%CFLAGS', + CXXCOM => '%CXX %CXXFLAGS %_IFLAGS -c %< -o %>', + INCDIRPREFIX => '-I', + INCDIRSUFFIX => '', + LINK => '%CXX', + LINKCOM => '%LINK %LDFLAGS -o %> %< %_LDIRS %LIBS', + LINKMODULECOM => '%LD -r -o %> %<', + LIBDIRPREFIX => '-L', + LIBDIRSUFFIX => '', + AR => 'ar', + ARFLAGS => 'r', + ARCOM => ['%AR %ARFLAGS %> %<', '%RANLIB %>'], + RANLIB => 'ranlib', + AS => 'as', + ASFLAGS => '', + ASCOM => '%AS %ASFLAGS %< -o %>', + LD => 'ld', + LDFLAGS => '', + PREFLIB => 'lib', + SUFLIB => '.a', + SUFLIBS => '.so:.a', + SUFOBJ => '.o', + SIGNATURE => [ '*' => 'build' ], + ENV => { 'PATH' => '/bin:/usr/bin' }, + + +And on a Win32 system (Windows NT), the default construction variables +are (unless the default rule style is set using the B +method): + + CC => 'cl', + CFLAGS => '/nologo', + CCCOM => '%CC %CFLAGS %_IFLAGS /c %< /Fo%>', + CXXCOM => '%CXX %CXXFLAGS %_IFLAGS /c %< /Fo%>', + INCDIRPREFIX => '/I', + INCDIRSUFFIX => '', + LINK => 'link', + LINKCOM => '%LINK %LDFLAGS /out:%> %< %_LDIRS %LIBS', + LINKMODULECOM => '%LD /r /o %> %<', + LIBDIRPREFIX => '/LIBPATH:', + LIBDIRSUFFIX => '', + AR => 'lib', + ARFLAGS => '/nologo ', + ARCOM => "%AR %ARFLAGS /out:%> %<", + RANLIB => '', + LD => 'link', + LDFLAGS => '/nologo ', + PREFLIB => '', + SUFEXE => '.exe', + SUFLIB => '.lib', + SUFLIBS => '.dll:.lib', + SUFOBJ => '.obj', + SIGNATURE => [ '*' => 'build' ], + +These variables are used by the various methods associated with the +environment. In particular, any method that ultimately invokes an external +command will substitute these variables into the final command, as +appropriate. For example, the C method takes a number of source +files and arranges to derive, if necessary, the corresponding object +files: + + Objects $env 'foo.c', 'bar.c'; + +This will arrange to produce, if necessary, F and F. The +command invoked is simply C<%CCCOM>, which expands, through substitution, +to the appropriate external command required to build each object. The +substitution rules will be discussed in detail in the next section. + +The construction variables are also used for other purposes. For example, +C is used to specify a colon-separated path of include +directories. These are intended to be passed to the C preprocessor and are +also used by the C-file scanning machinery to determine the dependencies +involved in a C Compilation. + +Variables beginning with underscore are created by various methods, +and should normally be considered ``internal'' variables. For example, +when a method is called which calls for the creation of an object from +a C source, the variable C<_IFLAGS> is created: this corresponds to the +C<-I> switches required by the C compiler to represent the directories +specified by C. + +Note that, for any particular environment, the value of a variable is set +once, and then never reset (to change a variable, you must create a new +environment. Methods are provided for copying existing environments for this +purpose). Some internal variables, such as C<_IFLAGS> are created on demand, +but once set, they remain fixed for the life of the environment. + +The C, C, and C variables all supply a place +for passing options to the compiler, loader, and archiver, respectively. + +The C and C variables specify option +strings to be appended to the beginning and end, respectively, of each +include directory so that the compiler knows where to find F<.h> files. +Similarly, the C and C variables specify the +option string to be appended to the beginning of and end, respectively, +of each directory that the linker should search for libraries. + +Another variable, C, is used to determine the system environment during +the execution of an external command. By default, the only environment +variable that is set is C, which is the execution path for a UNIX +command. For the utmost reproducibility, you should really arrange to set +your own execution path, in your top-level F file (or perhaps by +importing an appropriate construction package with the Perl C +command). The default variables are intended to get you off the ground. + +=head2 Expanding variables in construction commands + +Within a construction command, construction variables will be expanded +according to the rules described above. In addition to normal variable +expansion from the construction environment, construction commands also +expand the following pseudo-variables to insert the specific input and +output files in the command line that will be executed: + +=over 10 + +=item %> + +The target file name. In a multi-target command, this expands to the +first target mentioned.) + +=item %0 + +Same as C<%E>. + +=item %1, %2, ..., %9 + +These refer to the first through ninth input file, respectively. + +=item %E + +The full set of input file names. If any of these have been used +anywhere else in the current command line (via C<%1>, C<%2>, etc.), then +those will be deleted from the list provided by C<%E>. Consider the +following command found in a F file in the F directory: + + Command $env 'tgt', qw(foo bar baz), qq( + echo %< -i %1 > %> + echo %< -i %2 >> %> + echo %< -i %3 >> %> + ); + +If F needed to be updated, then this would result in the execution of +the following commands, assuming that no remapping has been established for +the F directory: + + echo test/bar test/baz -i test/foo > test/tgt + echo test/foo test/baz -i test/bar >> test/tgt + echo test/foo test/bar -i test/baz >> test/tgt + +=back + +Any of the above pseudo-variables may be followed immediately by one of +the following suffixes to select a portion of the expanded path name: + + :a the absolute path to the file name + :b the directory plus the file name stripped of any suffix + :d the directory + :f the file name + :s the file name suffix + :F the file name stripped of any suffix + :S the absolute path path to a Linked source file + +Continuing with the above example, C<%E:f> would expand to C, +and C<%E:d> would expand to C. + +There are additional C<%> elements which affect the command line(s): + +=over 10 + +=item %[ %] + +It is possible to programmatically rewrite part of the command by +enclosing part of it between C<%[> and C<%]>. This will call the +construction variable named as the first word enclosed in the brackets +as a Perl code reference; the results of this call will be used to +replace the contents of the brackets in the command line. For example, +given an existing input file named F: + + @keywords = qw(foo bar baz); + $env = new cons(X_COMMA => sub { join(",", @_) }); + Command $env 'tgt', 'tgt.in', qq( + echo '# Keywords: %[X_COMMA @keywords %]' > %> + cat %< >> %> + ); + +This will execute: + + echo '# Keywords: foo,bar,baz' > tgt + cat tgt.in >> tgt + +=item %( %) + +Cons includes the text of the command line in the MD5 signature for a +build, so that targets get rebuilt if you change the command line (to +add or remove an option, for example). Command-line text in between +C<%(> and C<%)>, however, will be ignored for MD5 signature calculation. + +Internally, Cons uses C<%(> and C<%)> around include and library +directory options (C<-I> and C<-L> on UNIX systems, C and +C on Windows NT) to avoid rebuilds just because the directory +list changes. Rebuilds occur only if the changed directory list causes +any included I to change, and a changed include file is detected +by the MD5 signature calculation on the actual file contents. + +=back + +=head2 Expanding construction variables in file names + +Cons expands construction variables in the source and target file names +passed to the various construction methods according to the expansion +rules described above: + + $env = new cons( + DESTDIR => 'programs', + SRCDIR => 'src', + ); + Program $env '%DESTDIR/hello', '%SRCDIR/hello.c'; + +This allows for flexible configuration, through the construction +environment, of directory names, suffixes, etc. + + +=head1 Build actions + +Cons supports several types of B that can be performed +to construct one or more target files. Usually, a build action is +a construction command--that is, a command-line string that invokes +an external command. Cons can also execute Perl code embedded in a +command-line string, and even supports an experimental ability to build +a target file by executing a Perl code reference directly. + +A build action is usually specified as the value of a construction +variable: + + $env = new cons( + CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', + LINKCOM => '[perl] &link_executable("%>", "%<")', + ARCOM => sub { my($env, $target, @sources) = @_; + # code to create an archive + } + ); + +A build action may be associated directly with one or more target files +via the C method; see below. + +=head2 Construction commands + +A construction command goes through expansion of construction variables +and C<%-> pseudo-variables, as described above, to create the actual +command line that Cons will execute to generate the target file or +files. + +After substitution occurs, strings of white space are converted into +single blanks, and leading and trailing white space is eliminated. It +is therefore currently not possible to introduce variable length white +space in strings passed into a command. + +If a multi-line command string is provided, the commands are executed +sequentially. If any of the commands fails, then none of the rest are +executed, and the target is not marked as updated, i.e. a new signature is +not stored for the target. + +Normally, if all the commands succeed, and return a zero status (or whatever +platform-specific indication of success is required), then a new signature +is stored for the target. If a command erroneously reports success even +after a failure, then Cons will assume that the target file created by that +command is accurate and up-to-date. + +The first word of each command string, after expansion, is assumed to be an +executable command looked up on the C environment variable (which is, +in turn, specified by the C construction variable). If this command is +found on the path, then the target will depend upon it: the command will +therefore be automatically built, as necessary. It's possible to write +multi-part commands to some shells, separated by semi-colons. Only the first +command word will be depended upon, however, so if you write your command +strings this way, you must either explicitly set up a dependency (with the +C method), or be sure that the command you are using is a system +command which is expected to be available. If it isn't available, you will, +of course, get an error. + +Cons normally prints a command before executing it. This behavior is +suppressed if the first character of the command is C<@>. Note that +you may need to separate the C<@> from the command name or escape it to +prevent C<@cmd> from looking like an array to Perl quote operators that +perform interpolation: + + # The first command line is incorrect, + # because "@cp" looks like an array + # to the Perl qq// function. + # Use the second form instead. + Command $env 'foo', 'foo.in', qq( + @cp %< tempfile + @ cp tempfile %> + ); + +If there are shell meta characters anywhere in the expanded command line, +such as C>, C>, quotes, or semi-colon, then the command +will actually be executed by invoking a shell. This means that a command +such as: + + cd foo + +alone will typically fail, since there is no command C on the path. But +the command string: + + cd $<:d; tar cf $>:f $<:f + +when expanded will still contain the shell meta character semi-colon, and a +shell will be invoked to interpret the command. Since C is interpreted +by this sub-shell, the command will execute as expected. + +=head2 Perl expressions + +If any command (even one within a multi-line command) begins with +C<[perl]>, the remainder of that command line will be evaluated by the +running Perl instead of being forked by the shell. If an error occurs +in parsing the Perl code, or if the Perl expression returns 0 or undef, +the command will be considered to have failed. For example, here is a +simple command which creates a file C directly from Perl: + + $env = new cons(); + Command $env 'foo', + qq([perl] open(FOO,'>foo');print FOO "hi\\n"; close(FOO); 1); + +Note that when the command is executed, you are in the same package as +when the F or F file was read, so you can call +Perl functions you've defined in the same F or F +file in which the C appears: + + $env = new cons(); + sub create_file { + my $file = shift; + open(FILE, ">$file"); + print FILE "hi\n"; + close(FILE); + return 1; + } + Command $env 'foo', "[perl] &create_file('%>')"; + +The Perl string will be used to generate the signature for the derived +file, so if you change the string, the file will be rebuilt. The contents +of any subroutines you call, however, are not part of the signature, +so if you modify a called subroutine such as C above, +the target will I be rebuilt. Caveat user. + +=head2 Perl code references [EXPERIMENTAL] + +Cons supports the ability to create a derived file by directly executing +a Perl code reference. This feature is considered EXPERIMENTAL and +subject to change in the future. + +A code reference may either be a named subroutine referenced by the +usual C<\&> syntax: + + sub build_output { + my($env, $target, @sources) = @_; + print "build_output building $target\n"; + open(OUT, ">$target"); + foreach $src (@sources) { + if (! open(IN, "<$src")) { + print STDERR "cannot open '$src': $!\n"; + return undef; + } + print OUT, ; + } + close(OUT); + return 1; + } + Command $env 'output', \&build_output; + +or the code reference may be an anonymous subroutine: + + Command $env 'output', sub { + my($env, $target, @sources) = @_; + print "building $target\n"; + open(FILE, ">$target"); + print FILE "hello\n"; + close(FILE); + return 1; + }; + +To build the target file, the referenced subroutine is passed, in order: +the construction environment used to generate the target; the path +name of the target itself; and the path names of all the source files +necessary to build the target file. + +The code reference is expected to generate the target file, of course, +but may manipulate the source and target files in any way it chooses. +The code reference must return a false value (C or C<0>) if +the build of the file failed. Any true value indicates a successful +build of the target. + +Building target files using code references is considered EXPERIMENTAL +due to the following current limitations: + +=over 4 + +Cons does I print anything to indicate the code reference is being +called to build the file. The only way to give the user any indication +is to have the code reference explicitly print some sort of "building" +message, as in the above examples. + +Cons does not generate any signatures for code references, so if the +code in the reference changes, the target will I be rebuilt. + +Cons has no public method to allow a code reference to extract +construction variables. This would be good to allow generalization of +code references based on the current construction environment, but would +also complicate the problem of generating meaningful signatures for code +references. + +=back + +Support for building targets via code references has been released in +this version to encourage experimentation and the seeking of possible +solutions to the above limitations. + + +=head1 Default construction methods + +The list of default construction methods includes the following: + + +=head2 The C constructor + +The C method is a Perl object constructor. That is, it is not invoked +via a reference to an existing construction environment B, but, +rather statically, using the name of the Perl B where the +constructor is defined. The method is invoked like this: + + $env = new cons(); + +The environment you get back is blessed into the package C, which +means that it will have associated with it the default methods described +below. Individual construction variables can be overridden by providing +name/value pairs in an override list. Note that to override any command +environment variable (i.e. anything under C), you will have to override +all of them. You can get around this difficulty by using the C method +on an existing construction environment. + + +=head2 The C method + +The C method creates a clone of an existing construction environment, +and can be called as in the following example: + + $env2 = $env1->clone(); + +You can provide overrides in the usual manner to create a different +environment from the original. If you just want a new name for the same +environment (which may be helpful when exporting environments to existing +components), you can just use simple assignment. + + +=head2 The C method + +The C method extracts the externally defined construction variables +from an environment and returns them as a list of name/value +pairs. Overrides can also be provided, in which case, the overridden values +will be returned, as appropriate. The returned list can be assigned to a +hash, as shown in the prototype, below, but it can also be manipulated in +other ways: + + %env = $env1->copy(); + +The value of C, which is itself a hash, is also copied to a new hash, +so this may be changed without fear of affecting the original +environment. So, for example, if you really want to override just the +C variable in the default environment, you could do the following: + + %cons = new cons()->copy(); + $cons{ENV}{PATH} = ""; + $cons = new cons(%cons); + +This will leave anything else that might be in the default execution +environment undisturbed. + + +=head2 The C method + +The C method arranges for the specified files to be installed in +the specified directory. The installation is optimized: the file is not +copied if it can be linked. If this is not the desired behavior, you will +need to use a different method to install the file. It is called as follows: + + Install $env , ; + +Note that, while the files to be installed may be arbitrarily named, +only the last component of each name is used for the installed target +name. So, for example, if you arrange to install F in F, +this will create a F file in the F directory (not F). + + +=head2 The C method + +The C method arranges for the specified source file(s) to be +installed as the specified target file(s). Multiple files should be +specified as a file list. The installation is optimized: the file is not +copied if it can be linked. If this is not the desired behavior, you will +need to use a different method to install the file. It is called as follows: + +C works in two ways: + +Single file install: + + InstallAs $env TgtFile, SrcFile; + +Multiple file install: + + InstallAs $env ['tgt1', 'tgt2'], ['src1', 'src2']; + +Or, even as: + + @srcs = qw(src1 src2 src3); + @tgts = qw(tgt1 tgt2 tgt3); + InstallAs $env [@tgts], [@srcs]; + +Both the target and the sources lists should be of the same length. + +=head2 The C method + +The C method asks cons not to delete the specified file or +list of files before building them again. It is invoked as: + + Precious ; + +This is especially useful for allowing incremental updates to libraries +or debug information files which are updated rather than rebuilt anew each +time. Cons will still delete the files when the C<-r> flag is specified. + +=head2 The C method + +The C method evaluates the specified perl string after +building the given file or files (or finding that they are up to date). +The eval will happen once per specified file. C is called +as follows: + + AfterBuild $env 'foo.o', qq(print "foo.o is up to date!\n"); + +The perl string is evaluated in the C